diff options
-rw-r--r-- | afdnotebook.h | 1 | ||||
-rw-r--r-- | notebookmodel.cc | 13 | ||||
-rw-r--r-- | replaydata.cc | 183 | ||||
-rw-r--r-- | replaydata.h | 53 | ||||
-rw-r--r-- | scribiu.pro | 6 | ||||
-rw-r--r-- | smartpen.cc | 48 | ||||
-rw-r--r-- | smartpen.h | 7 | ||||
-rw-r--r-- | smartpenmanager.cc | 5 | ||||
-rw-r--r-- | smartpenmanager.h | 2 | ||||
-rw-r--r-- | smartpensyncer.cc | 37 | ||||
-rw-r--r-- | smartpensyncer.h | 1 |
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(); @@ -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: |