aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--afdnotebook.h1
-rw-r--r--notebookmodel.cc13
-rw-r--r--replaydata.cc183
-rw-r--r--replaydata.h53
-rw-r--r--scribiu.pro6
-rw-r--r--smartpen.cc48
-rw-r--r--smartpen.h7
-rw-r--r--smartpenmanager.cc5
-rw-r--r--smartpenmanager.h2
-rw-r--r--smartpensyncer.cc37
-rw-r--r--smartpensyncer.h1
11 files changed, 341 insertions, 15 deletions
diff --git a/afdnotebook.h b/afdnotebook.h
index ea90661..38f926f 100644
--- a/afdnotebook.h
+++ b/afdnotebook.h
@@ -40,6 +40,7 @@ private:
PageAddress();
explicit PageAddress(uint shelf, uint segment, uint book, uint page);
explicit PageAddress(uint series, uint shelf, uint segment, uint book, uint page);
+ explicit PageAddress(quint64 addr);
explicit PageAddress(const QString &str);
QString toString() const;
diff --git a/notebookmodel.cc b/notebookmodel.cc
index 1d8228c..13e3b21 100644
--- a/notebookmodel.cc
+++ b/notebookmodel.cc
@@ -3,6 +3,7 @@
#include <QtGui/QIcon>
#include <QtGui/QDesktopServices>
#include <QtGui/QStyle>
+#include "replaydata.h"
#include "notebookmodel.h"
#define NUM_COLUMNS 3
@@ -263,7 +264,14 @@ void NotebookModel::refreshPen(const QString &name)
endInsertRows();
}
- qDebug() << "Found" << curNotebooks.size() << "notebook for pen" << name;
+ qDebug() << "Found" << curNotebooks.size() << "notebooks for pen" << name;
+
+ ReplayData replay;
+ if (replay.open(penDir.filePath("PaperReplay"))) {
+ qDebug() << "Paper replay opened";
+ } else {
+ qDebug() << "No paper replay for pen" << name;
+ }
}
int NotebookModel::indexOfPen(const QString &name)
@@ -319,5 +327,8 @@ void NotebookModel::handleChangedDirectory(const QString &path)
qDebug() << "changed" << path;
if (path == _dataDir.absolutePath()) {
refresh();
+ } else if (path.endsWith(".pen")) {
+ QFileInfo finfo(path);
+ refreshPen(finfo.baseName());
}
}
diff --git a/replaydata.cc b/replaydata.cc
new file mode 100644
index 0000000..021a79f
--- /dev/null
+++ b/replaydata.cc
@@ -0,0 +1,183 @@
+#include <QtCore/QDebug>
+#include <QtCore/QDataStream>
+#include <QtCore/QDirIterator>
+#include "smartpen.h"
+#include "replaydata.h"
+
+namespace
+{
+bool readUtfString(QDataStream &stream, QString &s)
+{
+ quint16 len;
+ stream >> len;
+ QByteArray buffer(len, Qt::Uninitialized);
+ if (stream.readRawData(buffer.data(), len) != len) {
+ return false;
+ }
+ s = QString::fromUtf8(buffer);
+ return true;
+}
+}
+
+ReplayData::ReplayData(QObject *parent) :
+ QObject(parent)
+{
+}
+
+bool ReplayData::open(const QString &path)
+{
+ _dir.setPath(path);
+ if (!_dir.exists()) {
+ return false;
+ }
+
+ QDirIterator iter(_dir.path(), QStringList("session.info"), QDir::Files, QDirIterator::Subdirectories);
+ while (iter.hasNext()) {
+ QFileInfo finfo(iter.next());
+ QDir sessionDir(finfo.path());
+ Session session;
+ if (!parseSessionInfo(session, finfo.filePath())) {
+ qWarning() << "Could not parse:" << finfo.absoluteFilePath();
+ }
+ if (!parseSessionPages(session, sessionDir.filePath("session.pages"))) {
+ qWarning() << "Could not parse:" << sessionDir.absoluteFilePath("session.pages");
+ }
+ }
+
+ return true;
+}
+
+bool ReplayData::parseSessionInfo(Session &session, const QString &path)
+{
+ QFile f(path);
+ if (f.open(QIODevice::ReadOnly)) {
+ return parseSessionInfo(session, &f);
+ } else {
+ return false;
+ }
+}
+
+bool ReplayData::parseSessionInfo(Session &session, QIODevice *dev)
+{
+ unsigned char magic[2];
+ if (dev->read(reinterpret_cast<char*>(magic), 2) != 2 ||
+ magic[0] != 0xFA || magic[1] != 0xCE) {
+ qWarning() << "Invalid magic";
+ return false;
+ }
+
+ char version = 0;
+ if (!dev->getChar(&version)) {
+ qWarning() << "Short read while getting version number";
+ return false;
+ }
+
+ switch (version) {
+ case 3:
+ return parseSessionInfoV3(session, dev);
+ default:
+ qWarning() << "Unknown version:" << version;
+ return false;
+ }
+}
+
+bool ReplayData::parseSessionInfoV3(Session &session, QIODevice *dev)
+{
+ QDataStream s(dev);
+ if (s.skipRawData(5) != 5) return false;
+
+ qint64 startTime, endTime, creationTime;
+ QString name;
+
+ s >> startTime >> endTime >> creationTime;
+ if (!readUtfString(s, name)) {
+ return false;
+ }
+
+ qDebug() << "Name:" << name << Smartpen::fromPenTime(startTime) << Smartpen::fromPenTime(endTime) << creationTime;
+
+ quint16 num_clips;
+ s >> num_clips;
+
+ for (uint i = 0; i < num_clips; ++i) {
+ QString file;
+ if (!readUtfString(s, file)) {
+ return false;
+ }
+ s >> startTime >> endTime;
+ qDebug() << " Clip:" << file << startTime << endTime;
+ qDebug() << " " << QDateTime::fromTime_t(startTime) << QDateTime::fromTime_t(endTime);
+ }
+
+ quint16 num_strokes;
+ s >> num_strokes;
+
+ for (uint i = 0; i < num_strokes; ++i) {
+ quint64 a, b, c;
+ quint32 d;
+ QString nbGuid;
+ quint8 f, g;
+ s >> a >> b >> c >> d;
+ if (!readUtfString(s, nbGuid)) {
+ return false;
+ }
+ s >> f >> g;
+ qDebug() << " Stroke:" << a << b << c << d << nbGuid << f << g;
+ }
+
+ return true;
+}
+
+bool ReplayData::parseSessionPages(Session &session, const QString &path)
+{
+ QFile f(path);
+ if (f.open(QIODevice::ReadOnly)) {
+ return parseSessionPages(session, &f);
+ } else {
+ return false;
+ }
+}
+
+bool ReplayData::parseSessionPages(Session &session, QIODevice *dev)
+{
+ unsigned char magic[2];
+ if (dev->read(reinterpret_cast<char*>(magic), 2) != 2 ||
+ magic[0] != 0xCA || magic[1] != 0xBE) {
+ qWarning() << "Invalid magic";
+ return false;
+ }
+
+ char version = 0;
+ if (!dev->getChar(&version)) {
+ qWarning() << "Short read while getting version number";
+ return false;
+ }
+
+ switch (version) {
+ case 1:
+ return parseSessionPagesV1(session, dev);
+ default:
+ qWarning() << "Unknown version:" << version;
+ return false;
+ }
+}
+
+bool ReplayData::parseSessionPagesV1(Session &session, QIODevice *dev)
+{
+ QDataStream s(dev);
+ if (s.skipRawData(1) != 1) return false;
+
+ quint16 num_pages = 0;
+ s >> num_pages;
+ session.pages.reserve(session.pages.size() + num_pages);
+
+ for (uint i = 0; i < num_pages; ++i) {
+ quint64 address, unk;
+ s >> address >> unk;
+
+ session.pages.append(address);
+ qDebug() << " Page:" << address << unk;
+ }
+
+ return true;
+}
diff --git a/replaydata.h b/replaydata.h
new file mode 100644
index 0000000..39b1e0e
--- /dev/null
+++ b/replaydata.h
@@ -0,0 +1,53 @@
+#ifndef REPLAYDATA_H
+#define REPLAYDATA_H
+
+#include <QtCore/QDateTime>
+#include <QtCore/QDir>
+#include <QtCore/QHash>
+#include <QtCore/QMultiMap>
+#include <QtCore/QVector>
+
+class ReplayData : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit ReplayData(QObject *parent = 0);
+
+ bool open(const QString &path);
+ void close();
+
+ struct Session {
+ QDateTime start, end;
+ QString name;
+ QVector<quint64> pages;
+ QString file;
+ };
+
+ QList<Session> sessions(quint64 penId, quint64 notebookId);
+ QList<Session> sessions(quint64 penId, quint64 notebookId, quint64 pageAddress);
+
+private:
+ struct NotebookData {
+ QHash<uint, Session> sessions;
+ QMultiMap<quint64, uint> byPage;
+ };
+
+private:
+ bool findSessions();
+
+ static bool parseSessionInfo(Session &session, const QString &path);
+ static bool parseSessionInfo(Session &session, QIODevice *dev);
+ static bool parseSessionInfoV3(Session &session, QIODevice *dev);
+
+ static bool parseSessionPages(Session &session, const QString &path);
+ static bool parseSessionPages(Session &session, QIODevice *dev);
+ static bool parseSessionPagesV1(Session &session, QIODevice *dev);
+
+private:
+ QDir _dir;
+ QHash<quint64, QHash<quint64, NotebookData> > _notebooks; // TODO Cache on demand
+ // /userdata/XXX-XXX-XXX-XX/Paper Replay/99/0bf11a726d11f3f3/sessions/PRS-21977890a4
+};
+
+#endif // REPLAYDATA_H
diff --git a/scribiu.pro b/scribiu.pro
index 641f286..31d05ea 100644
--- a/scribiu.pro
+++ b/scribiu.pro
@@ -19,7 +19,8 @@ SOURCES += main.cc\
notebookview.cc \
afdnotebook.cc \
pageitem.cc \
- stfgraphicsitem.cc
+ stfgraphicsitem.cc \
+ replaydata.cc
HEADERS += mainwindow.h \
smartpenmanager.h \
@@ -30,6 +31,7 @@ HEADERS += mainwindow.h \
notebookview.h \
afdnotebook.h \
pageitem.h \
- stfgraphicsitem.h
+ stfgraphicsitem.h \
+ replaydata.h
FORMS += mainwindow.ui
diff --git a/smartpen.cc b/smartpen.cc
index 6df3fd1..13743ab 100644
--- a/smartpen.cc
+++ b/smartpen.cc
@@ -1,16 +1,20 @@
#include <QtCore/QDateTime>
#include <QtCore/QDebug>
+#include <QtCore/QStringList>
#include <QtCore/QtEndian>
#include <usb.h>
#include "xmlutils.h"
#include "smartpen.h"
-#define PEN_EPOCH (1289335960000LL)
+#define PEN_EPOCH (1289335960000LL) // This is probably not correct
#define PEN_MTU 900
#define PEN_TIMEOUT_SECONDS 10
#define INVALID_CID 0xFFFFFFFFU
+static const char pen_serial_chars[] = "ABCDEFGHJKMNPQRSTUWXYZ23456789";
+static const unsigned int pen_serial_num_chars = sizeof(pen_serial_chars) - 1;
+
/* Terrible hack comes now: */
struct obex_usb_intf_transport_t {
struct obex_usb_intf_transport_t *prev, *next; /* Next and previous interfaces in the list */
@@ -155,6 +159,10 @@ QList<Smartpen::ChangeReport> Smartpen::getChangeList(const QDateTime &from)
report.guid = attrs.value("guid").toString();
report.title = attrs.value("title").toString();
result.append(report);
+ } else if (attrs.hasAttribute("classname")) {
+ report.className = attrs.value("classname").toString();
+ report.title = attrs.value("title").toString();
+ result.append(report);
}
r.skipCurrentElement();
} else {
@@ -174,6 +182,11 @@ QByteArray Smartpen::getLspData(const QString &name, const QDateTime &from)
return getObject(QString("lspdata?name=%1&start_time=%2").arg(name).arg(toPenTime(from)));
}
+QByteArray Smartpen::getPaperReplay(const QDateTime &from)
+{
+ return getObject(QString("lspdata?name=com.livescribe.paperreplay.PaperReplay&start_time=%1&returnVersion=0.3&remoteCaller=WIN_LD_200").arg(toPenTime(from)));
+}
+
qint64 Smartpen::toPenTime(const QDateTime &dt)
{
if (dt.isValid()) {
@@ -208,6 +221,19 @@ QString Smartpen::toPenSerial(quint64 id)
return serial;
}
+quint64 Smartpen::toPenId(const QString &serial)
+{
+ QStringList segments = serial.split('-');
+ if (segments.size() != 4) {
+ return 0;
+ }
+
+ quint64 id = quint64(fromPenSerialSegment(segments[0])) << 32;
+ id |= fromPenSerialSegment(segments[1] + segments[2]) * 0x36D;
+ id |= fromPenSerialSegment(segments[3]);
+ return id;
+}
+
bool Smartpen::connectToPen(const Address &addr)
{
if (_obex) {
@@ -389,19 +415,31 @@ void Smartpen::handleObexRequestDone(obex_object_t *object, int obex_cmd, int ob
QString Smartpen::toPenSerialSegment(quint32 id, int len)
{
- static const char chars[] = "ABCDEFGHJKMNPQRSTUWXYZ23456789";
- static const unsigned int num_chars = sizeof(chars) - 1;
QString segment(len, Qt::Uninitialized);
for (int i = 0; i < len; i++) {
- segment[len - (i + 1)] = chars[id % num_chars];
- id /= num_chars;
+ segment[len - (i + 1)] = pen_serial_chars[id % pen_serial_num_chars];
+ id /= pen_serial_num_chars;
}
return segment;
}
+quint32 Smartpen::fromPenSerialSegment(const QString &s)
+{
+ const int len = s.length();
+ quint32 id = 0;
+
+ for (int i = 0; i < len; i++) {
+ uint val = qFind(&pen_serial_chars[0], &pen_serial_chars[pen_serial_num_chars], s[i]) - &pen_serial_chars[0];
+ if (val >= pen_serial_num_chars) return 0;
+ id = val + id * pen_serial_num_chars;
+ }
+
+ return id;
+}
+
QByteArray Smartpen::encodeUtf16(const QString &s)
{
const int size = s.size();
diff --git a/smartpen.h b/smartpen.h
index c93e016..c44ef71 100644
--- a/smartpen.h
+++ b/smartpen.h
@@ -21,7 +21,7 @@ public:
explicit Smartpen(QObject *parent = 0);
~Smartpen();
- typedef QPair<int, int> Address;
+ typedef QPair<unsigned int, unsigned int> Address;
bool isConnected() const;
@@ -37,18 +37,20 @@ public:
struct ChangeReport {
QString guid;
+ QString className;
QString title;
};
QList<ChangeReport> getChangeList(const QDateTime &from = QDateTime());
QByteArray getLspData(const QString &name, const QDateTime &from = QDateTime());
- QByteArray getPaperReplay();
+ QByteArray getPaperReplay(const QDateTime &from = QDateTime());
static qint64 toPenTime(const QDateTime &dt);
static QDateTime fromPenTime(qint64 t);
static QString toPenSerial(quint64 id);
+ static quint64 toPenId(const QString &serial);
public slots:
bool connectToPen(const Address &addr);
@@ -65,6 +67,7 @@ private:
void handleObexRequestDone(obex_object_t *object, int obex_cmd, int obex_rsp);
static QString toPenSerialSegment(quint32 id, int len);
+ static quint32 fromPenSerialSegment(const QString &s);
static QByteArray encodeUtf16(const QString &s);
void addConnHeader(obex_object_t *object) const;
diff --git a/smartpenmanager.cc b/smartpenmanager.cc
index d560b5b..ad6416b 100644
--- a/smartpenmanager.cc
+++ b/smartpenmanager.cc
@@ -42,7 +42,10 @@ void SmartpenManager::handleMonitorActivity()
{
qDebug() << "udev activity";
udev_device *dev = udev_monitor_receive_device(_monitor);
- udev_device_unref(dev);
+ if (dev) {
+ processDevice(dev);
+ udev_device_unref(dev);
+ }
}
void SmartpenManager::handleSyncerFinished()
diff --git a/smartpenmanager.h b/smartpenmanager.h
index d1b4800..ab6f159 100644
--- a/smartpenmanager.h
+++ b/smartpenmanager.h
@@ -35,7 +35,7 @@ private:
udev *_udev;
udev_monitor *_monitor;
QSocketNotifier *_notifier;
- QMap<QPair<int, int>, SmartpenSyncer*> _syncers;
+ QMap<Smartpen::Address, SmartpenSyncer*> _syncers;
};
diff --git a/smartpensyncer.cc b/smartpensyncer.cc
index 50304a3..fe7a281 100644
--- a/smartpensyncer.cc
+++ b/smartpensyncer.cc
@@ -108,9 +108,16 @@ bool SmartpenSyncer::syncPen()
QList<Smartpen::ChangeReport> changes = _pen->getChangeList(lastSyncTime);
foreach(const Smartpen::ChangeReport &change, changes) {
- qDebug() << "Synchronizing guid: " << change.guid << change.title;
- if (!syncNotebook(change)) {
- return false;
+ if (!change.guid.isEmpty()) {
+ qDebug() << "Synchronizing guid: " << change.guid << change.title;
+ if (!syncNotebook(change)) {
+ return false;
+ }
+ } else if (change.className == "com.livescribe.paperreplay.PaperReplay") {
+ qDebug() << "Synchronizing paper replay";
+ if (!syncPaperReplay()) {
+ return false;
+ }
}
}
@@ -143,6 +150,30 @@ bool SmartpenSyncer::syncNotebook(const Smartpen::ChangeReport &change)
return true;
}
+bool SmartpenSyncer::syncPaperReplay()
+{
+ QDir replayDir(_penDataDir.filePath("PaperReplay"));
+ if (!replayDir.exists()) {
+ if (!replayDir.mkpath(".")) {
+ qWarning() << "Cannot create PaperReplay data directory:" << replayDir.absolutePath();
+ }
+ }
+
+ setTimestampFileDate(replayDir.filePath(".sync.lck"));
+
+ QDateTime lastSyncTime = getTimestampFileDate(replayDir.filePath(".lastsync"));
+ QByteArray replayData = _pen->getPaperReplay(lastSyncTime);
+
+ if (!extractZip(replayData, replayDir)) {
+ return false;
+ }
+
+ setTimestampFileDate(replayDir.filePath(".lastsync"));
+ removeTimestampFile(replayDir.filePath(".sync.lck"));
+
+ return true;
+}
+
bool SmartpenSyncer::extractZip(QByteArray &zipData, QDir &dir)
{
QBuffer zipBuffer(&zipData);
diff --git a/smartpensyncer.h b/smartpensyncer.h
index 6279485..7d8e0c9 100644
--- a/smartpensyncer.h
+++ b/smartpensyncer.h
@@ -23,6 +23,7 @@ private:
void run();
bool syncPen();
bool syncNotebook(const Smartpen::ChangeReport &change);
+ bool syncPaperReplay();
bool extractZip(QByteArray &zipData, QDir &dir);
private: