From 5cb277888995edecfafd83fed4cf2bd510052a4b Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 12 Apr 2020 00:45:32 +0200 Subject: port to qt5, libusb 1.0, and openobex 1.7 --- smartpenmanager.cc | 261 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 233 insertions(+), 28 deletions(-) (limited to 'smartpenmanager.cc') diff --git a/smartpenmanager.cc b/smartpenmanager.cc index 0ac56e2..1eeb685 100644 --- a/smartpenmanager.cc +++ b/smartpenmanager.cc @@ -21,17 +21,30 @@ #include "smartpenmanager.h" +// Sync with a connected pen every 120 seconds +#define SYNC_INTERVAL 120 +// If the previous sync failed, try in 5 seconds +#define FAILED_SYNC_INTERVAL 5 +#define MAX_SYNC_RETRIES 3 + SmartpenManager::SmartpenManager(QObject *parent) : QObject(parent), _udev(udev_new()), _monitor(udev_monitor_new_from_netlink(_udev, "udev")), - _notifier(new QSocketNotifier(udev_monitor_get_fd(_monitor), QSocketNotifier::Read)) + _notifier(new QSocketNotifier(udev_monitor_get_fd(_monitor), QSocketNotifier::Read)), + _nextTry(new QTimer(this)) { - udev_monitor_filter_add_match_tag(_monitor, "livescribe-pen"); - connect(_notifier, SIGNAL(activated(int)), SLOT(handleMonitorActivity())); + connect(_nextTry, SIGNAL(timeout()), SLOT(handleTimerNextTry())); + _nextTry->setSingleShot(true); + _nextTry->setTimerType(Qt::VeryCoarseTimer); + + // Start udev monitoring + udev_monitor_filter_add_match_subsystem_devtype(_monitor, "usb", "usb_device"); + udev_monitor_filter_add_match_tag(_monitor, "livescribe-pen"); udev_monitor_enable_receiving(_monitor); udev_enumerate *scan = udev_enumerate_new(_udev); + udev_enumerate_add_match_subsystem(scan, "usb"); udev_enumerate_add_match_tag(scan, "livescribe-pen"); if (udev_enumerate_scan_devices(scan) == 0) { @@ -39,7 +52,7 @@ SmartpenManager::SmartpenManager(QObject *parent) udev_list_entry_foreach(i, l) { const char *path = udev_list_entry_get_name(i); udev_device *dev = udev_device_new_from_syspath(_udev, path); - processDevice(dev); + processDeviceAdded(dev); udev_device_unref(dev); } } else { @@ -56,18 +69,26 @@ SmartpenManager::~SmartpenManager() udev_unref(_udev); } +QStringList SmartpenManager::pensConnected() const +{ + QStringList pens; + pens.reserve(_pens.size()); + foreach (const PenInfo &pen, _pens) { + if (pen.connected) { + pens.append(pen.name); + } + } + return pens; +} + QStringList SmartpenManager::pensBeingSynchronized() const { QStringList pens; - pens.reserve(_syncers.size()); - for (QMap::const_iterator it = _syncers.begin(); - it != _syncers.end(); ++it) { - QString name = it.value()->penName(); - if (name.isEmpty()) { - Smartpen::Address addr = it.value()->penAddress(); - name = QString("%1-%2").arg(addr.first).arg(addr.second); + pens.reserve(_pens.size()); + foreach (const PenInfo &pen, _pens) { + if (pen.syncer && pen.syncer->isRunning()) { + pens.append(pen.name); } - pens.append(name); } return pens; } @@ -78,8 +99,11 @@ void SmartpenManager::handleMonitorActivity() udev_device *dev = udev_monitor_receive_device(_monitor); if (dev) { const char *action = udev_device_get_action(dev); - if (action && strcmp(action, "add") == 0) { - processDevice(dev); + Q_ASSERT(action); + if (strcmp(action, "add") == 0) { + processDeviceAdded(dev); + } else if (strcmp(action, "remove") == 0) { + processDeviceRemoved(dev); } udev_device_unref(dev); } @@ -92,31 +116,212 @@ void SmartpenManager::handleSyncerFinished() qDebug() << "Finished synchronization with pen with address:" << addr; - _syncers.remove(addr); - emit pensBeingSynchronizedChanged(); + Q_ASSERT(_pens.contains(addr)); + + PenInfo &pen = _pens[addr]; + + bool failed = syncer->hasErrors(); + bool disconnected = false; - if (syncer->hasErrors()) { + if (failed) { qWarning() << "Synchronization with address" << addr << "failed"; - emit syncFailed(syncer->penName()); + emit syncFailed(pen.name); + if (pen.connected) { + if (pen.numRetries < MAX_SYNC_RETRIES) { + // Try resetting USB device first + if (Smartpen::reset(addr)) { + pen.numRetries++; + pen.nextTry = QDateTime::currentDateTimeUtc().addSecs(FAILED_SYNC_INTERVAL); + } else { + qWarning() << "Failed to reset; assuming disconnected"; + pen.connected = false; + disconnected = true; + } + } else { + qWarning() << "Too many failures; assuming disconnected"; + pen.connected = false; + disconnected = true; + } + } } else { - emit syncComplete(syncer->penName()); + emit syncComplete(pen.name); + if (pen.connected) { + pen.numRetries = 0; + pen.nextTry = QDateTime::currentDateTimeUtc().addSecs(SYNC_INTERVAL); + } + } + + pen.syncer->deleteLater(); + pen.syncer = 0; + + emit pensBeingSynchronizedChanged(); + if (disconnected) + emit pensConnectedChanged(); + + scheduleNextTry(); +} + +void SmartpenManager::handleGotPenName(const QString &name) +{ + SmartpenSyncer *syncer = static_cast(sender()); + Smartpen::Address addr = syncer->penAddress(); + + Q_ASSERT(_pens.contains(addr)); + + PenInfo &pen = _pens[addr]; + pen.name = name; + + if (pen.connected) { + emit pensConnectedChanged(); } + if (pen.syncer) { + emit pensBeingSynchronizedChanged(); + } +} + +void SmartpenManager::handleTimerNextTry() +{ + QDateTime now = QDateTime::currentDateTimeUtc().addSecs(1); - syncer->deleteLater(); + foreach (const PenInfo &pen, _pens) { + // Not connected or already syncing: ignore + if (!pen.connected || pen.syncer) continue; + + if (now >= pen.nextTry) { + trySync(pen.addr); + } + } + + scheduleNextTry(); } -void SmartpenManager::processDevice(udev_device *dev) +void SmartpenManager::processDeviceAdded(udev_device *dev) { uint busnum = atol(udev_device_get_sysattr_value(dev, "busnum")); uint devnum = atol(udev_device_get_sysattr_value(dev, "devnum")); + Smartpen::Address addr(busnum, devnum); + QString name = parseUdevEscapedString(udev_device_get_property_value(dev, "ID_MODEL_ENC")); + + qDebug() << "Found smartpen with address:" << addr << name; + + PenInfo &pen = _pens[addr]; + pen.addr = addr; + if (pen.name.isEmpty()) pen.name = name; + pen.connected = true; + + // Schedule an attempt to sync in 1 sec + if (!pen.syncer) { + pen.numRetries = 0; + pen.nextTry = QDateTime::currentDateTimeUtc().addSecs(1); + + scheduleNextTry(); + } + + emit pensConnectedChanged(); +} + +void SmartpenManager::processDeviceRemoved(udev_device *dev) +{ + uint busnum = atol(udev_device_get_sysattr_value(dev, "busnum")); + uint devnum = atol(udev_device_get_sysattr_value(dev, "devnum")); Smartpen::Address addr(busnum, devnum); - if (!_syncers.contains(addr)) { - SmartpenSyncer *syncer = new SmartpenSyncer(addr, this); - _syncers.insert(addr, syncer); - connect(syncer, SIGNAL(finished()), SLOT(handleSyncerFinished())); - connect(syncer, SIGNAL(penNameChanged()), SIGNAL(pensBeingSynchronizedChanged())); - syncer->start(); - emit pensBeingSynchronizedChanged(); + + qDebug() << "Device removed with address:" << addr; + + if (_pens.contains(addr)) { + PenInfo &pen = _pens[addr]; + if (pen.syncer) { + pen.syncer->abort(); + } + pen.connected = false; + emit pensConnectedChanged(); + } +} + +void SmartpenManager::trySync(const Smartpen::Address &addr) +{ + Q_ASSERT(_pens.contains(addr)); + PenInfo &pen = _pens[addr]; + + qDebug() << "Starting sync for" << pen.name; + + Q_ASSERT(pen.addr == addr); + Q_ASSERT(pen.connected); + Q_ASSERT(!pen.syncer); + + pen.syncer = new SmartpenSyncer(addr, this); + connect(pen.syncer, SIGNAL(finished()), SLOT(handleSyncerFinished())); + connect(pen.syncer, SIGNAL(gotPenName(QString)), SLOT(handleGotPenName(QString))); + pen.syncer->start(); + + emit pensBeingSynchronizedChanged(); +} + +void SmartpenManager::scheduleNextTry() +{ + QDateTime nearest; + foreach (const PenInfo &pen, _pens) { + // Not connected or already syncing: ignore + if (!pen.connected || pen.syncer) continue; + + if (nearest.isNull() || pen.nextTry < nearest) { + nearest = pen.nextTry; + } + } + if (nearest.isValid()) { + qint64 msecs = QDateTime::currentDateTimeUtc().msecsTo(nearest); + qDebug() << "Sleeping for" << msecs << "ms"; + _nextTry->start(msecs); + } else { + qDebug() << "Nothing else to do"; + _nextTry->stop(); + } +} + +QString SmartpenManager::parseUdevEscapedString(const char *s) +{ + // This just tries to parse \xddd C-style escape sequences + // Likely broken, see if there's a generic way to do this + const int l = strlen(s); + + QString r; + r.reserve(l); + + int prev = 0; + const char *bs; + while (prev < l && (bs = strchr(s + prev, '\\')) != NULL) { + int pos = bs - s; + r.append(QString::fromLatin1(s + prev, pos - prev)); + if (pos + 1 >= l || s[pos + 1] == '\\') { + r.append('\\'); + pos += 2; + } else { + char c = s[pos + 1]; + if (c == 'x') { + int start = pos + 2; + int i = start; + for ( ; i < l; ++i) { + c = s[i]; + if (s[i] >= '0' && s[i] <= '9') continue; + if (s[i] >= 'A' && s[i] <= 'F') continue; + if (s[i] >= 'a' && s[i] <= 'f') continue; + break; + } + bool ok = false; + uint charNum = QString::fromLatin1(s + start, i - start).toUInt(&ok, 16); + if (ok) { + r.append(QChar::fromLatin1(charNum)); + } else { + r.append(QString::fromLatin1(s + pos, i - pos)); + } + pos = i; + } + } + prev = pos; + } + if (prev < l) { + r.append(QString::fromLatin1(s + prev)); } + return r; } -- cgit v1.2.3