aboutsummaryrefslogtreecommitdiff
path: root/smartpenmanager.cc
diff options
context:
space:
mode:
Diffstat (limited to 'smartpenmanager.cc')
-rw-r--r--smartpenmanager.cc261
1 files changed, 233 insertions, 28 deletions
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<Smartpen::Address, SmartpenSyncer*>::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<SmartpenSyncer*>(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;
}