#include #include #include #include #include #include "paperreplay.h" #include "notebookmodel.h" #include "smartpensyncer.h" #define BUFFER_SIZE 16 * 1024 namespace { static QDateTime getTimestampFileDate(const QString &path) { QFileInfo info(path); qDebug() << "Checking timestamp" << info.filePath(); if (info.exists()) { return info.lastModified(); } else { return QDateTime(); } } static void setTimestampFileDate(const QString &path) { QFile f(path); if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { qWarning() << "Could not set timestamp file:" << path; return; } f.close(); } void removeTimestampFile(const QString &path) { QFile f(path); if (!f.remove()) { qWarning() << "Cannot remove timestamp file:" << path; } } } SmartpenSyncer::SmartpenSyncer(const Smartpen::Address &addr, QObject *parent) : QThread(parent), _addr(addr), _pen(new Smartpen(this)), _errored(false), _aborted(false) { } SmartpenSyncer::~SmartpenSyncer() { if (isRunning()) { _aborted = true; wait(); } } Smartpen::Address SmartpenSyncer::penAddress() const { return _addr; } QString SmartpenSyncer::penName() const { return _penName; } void SmartpenSyncer::abort() { _aborted = true; } void SmartpenSyncer::run() { if (!_pen->connectToPen(_addr)) { qWarning() << "Could not connect to pen with USB address: " << _addr; _errored = true; return; } _penName = _pen->getPenName(); qDebug() << "got pen name:" << _penName; emit penNameChanged(); QVariantMap penInfo = _pen->getPenInfo(); if (penInfo.isEmpty()) { qWarning() << "Could not get pen info"; _errored = true; return; } _penSerial = penInfo["penserial"].toString(); _penDataDir.setPath(NotebookModel::userDataDirectory() + "/" + _penName + ".pen"); if (!_penDataDir.exists()) { if (!_penDataDir.mkpath(".")) { qWarning() << "Cannot create pen data directory:" << _penDataDir.absolutePath(); } } if (!syncPen()) { _errored = true; } _pen->disconnectFromPen(); } bool SmartpenSyncer::syncPen() { QDateTime lastSyncTime = getTimestampFileDate(_penDataDir.filePath(".lastsync")); QList changes = _pen->getChangeList(lastSyncTime); setTimestampFileDate(_penDataDir.filePath(".sync.lck")); foreach(const Smartpen::ChangeReport &change, changes) { 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; } } } setTimestampFileDate(_penDataDir.filePath(".lastsync")); removeTimestampFile(_penDataDir.filePath(".sync.lck")); return true; } bool SmartpenSyncer::syncNotebook(const Smartpen::ChangeReport &change) { QDir notebookDir(_penDataDir.filePath(change.title + ".afd")); if (!notebookDir.exists()) { if (!notebookDir.mkpath(".")) { qWarning() << "Cannot create notebook data directory:" << notebookDir.absolutePath(); } } setTimestampFileDate(notebookDir.filePath(".sync.lck")); QDateTime lastSyncTime = getTimestampFileDate(notebookDir.filePath(".lastsync")); QByteArray lspData = _pen->getLspData(change.guid, lastSyncTime); if (!extractZip(lspData, notebookDir)) { return false; } setTimestampFileDate(notebookDir.filePath(".lastsync")); removeTimestampFile(notebookDir.filePath(".sync.lck")); return true; } bool SmartpenSyncer::syncPaperReplay() { QDir replayDir(_penDataDir.filePath(PAPER_REPLAY)); 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); QuaZip zip(&zipBuffer); QuaZipFile zipFile(&zip); if (!zip.open(QuaZip::mdUnzip)) { qWarning() << "Could not open zip file"; return false; } QScopedArrayPointer buffer(new char[BUFFER_SIZE]); for (bool more=zip.goToFirstFile(); more; more=zip.goToNextFile()) { QString zipName = zip.getCurrentFileName(); if (!dir.absoluteFilePath(zipName).startsWith(dir.absolutePath())) { qWarning() << "broken zip filename:" << zipName; continue; } QFileInfo finfo(dir.filePath(zipName)); if (!dir.mkpath(finfo.path())) { qWarning() << "cannot mkpath for:" << finfo.absoluteFilePath(); } if (zipName.endsWith('/')) { // Nothing to do continue; } if (!zipFile.open(QIODevice::ReadOnly)) { qWarning() << "cannot open zip file for reading:" << zipName; continue; } QFile file(finfo.filePath()); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { qWarning() << "cannot open for writing:" << finfo.absoluteFilePath(); zipFile.close(); continue; } while (!zipFile.atEnd()) { qint64 read = zipFile.read(buffer.data(), BUFFER_SIZE); if (read <= 0) { qWarning() << "short read on:" << zipName; zipFile.close(); continue; } qint64 written = file.write(buffer.data(), read); if (written != read) { qWarning() << "short write on:" << file.fileName(); zipFile.close(); continue; } } file.close(); zipFile.close(); } buffer.reset(); if (zip.getZipError() == UNZ_OK) { return true; } else { qWarning() << "Error while decompressing"; return false; } }