From 49f1261bf9d635d5e3d881e87a93ed4e76abfe90 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 30 Nov 2014 17:27:08 +0100 Subject: allow receiving responses to commands in watchconnector * the skeleton is in place for watchconnector to allow query->response messages. I've used call/cc style because it is impossible to make QBluetoothSocket synchronous (waitForReadyRead() is a no-op) * remove watchcommands, instead create musicmanager to listen for the music endpoint. The other (simpler) endpoints are now listened in watchconnector itself. hangupAll() slot is moved to voicecallmanager. * instead of emitting signals for each received message, listeners can now register for receiving messages targeted towards a given endpoint * when reading from bluetoothsocket, properly handle short reads * remove useless 'watch' namespace * create appmanager, which mantains a database of installed apps (installed on the phone, that is; watch installed apps will come later) * all the *Managers are now instantiated by the main Manager itself * introduce Unpacker helper class for decoding watch messages * implement getAppbankStatus and getAppbankUuids messages and response parsers * remove file logging for now (20MB is bad for eMMC!) * use dbus object path /org/pebbled instead of / --- daemon/watchconnector.h | 57 ++++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 22 deletions(-) (limited to 'daemon/watchconnector.h') diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index b64e31b..01c3dac 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -30,6 +30,7 @@ #ifndef WATCHCONNECTOR_H #define WATCHCONNECTOR_H +#include #include #include #include @@ -39,25 +40,18 @@ #include #include -#if QT_VERSION < QT_VERSION_CHECK(5, 2, 0) -using namespace QtBluetooth; -#endif - -namespace watch -{ - class WatchConnector : public QObject { Q_OBJECT LOG4QT_DECLARE_QCLASS_LOGGER - Q_ENUMS(Endpoints) + Q_ENUMS(Endpoint) Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString connected READ isConnected NOTIFY connectedChanged) public: - enum Endpoints { + enum Endpoint { watchTIME = 11, watchVERSION = 16, watchPHONE_VERSION = 17, @@ -111,6 +105,11 @@ public: systemBLUETOOTH_START_DISCOVERABLE = 6, systemBLUETOOTH_END_DISCOVERABLE = 7 }; + enum AppManager { + appmgrGET_APPBANK_STATUS = 1, + appmgrGET_APPBANK_UUIDS = 5 + }; + enum { leadEMAIL = 0, leadSMS = 1, @@ -139,27 +138,41 @@ public: osLINUX = 4, osWINDOWS = 5 }; + enum { + DEFAULT_TIMEOUT_MSECS = 100 + }; + typedef std::function EndpointHandlerFunc; explicit WatchConnector(QObject *parent = 0); virtual ~WatchConnector(); - bool isConnected() const { return is_connected; } - QString name() const { return socket != nullptr ? socket->peerName() : ""; } - QString timeStamp(); - QString decodeEndpoint(uint val); + inline bool isConnected() const { return is_connected; } + inline QString name() const { return socket != nullptr ? socket->peerName() : ""; } + + void setEndpointHandler(uint endpoint, EndpointHandlerFunc func); + void clearEndpointHandler(uint endpoint); + + static QString timeStamp(); + static QString decodeEndpoint(uint val); + + void getAppbankStatus(const std::function& callback); + void getAppbankUuids(const std::function &uuids)>& callback); signals: - void messageReceived(QString peer, QString msg); - void messageDecoded(uint endpoint, QByteArray data); + void messageReceived(uint endpoint, const QByteArray &data); void nameChanged(); void connectedChanged(); public slots: - void sendData(const QByteArray &data); - void sendMessage(uint endpoint, QByteArray data); + void deviceConnect(const QString &name, const QString &address); + void disconnect(); + void reconnect(); + + void sendMessage(uint endpoint, const QByteArray &data); void ping(uint val); void time(); + void sendNotification(uint lead, QString sender, QString data, QString subject); void sendSMSNotification(QString sender, QString data); void sendEmailNotification(QString sender, QString data, QString subject); @@ -176,8 +189,7 @@ public slots: void startPhoneCall(uint cookie=0); void endPhoneCall(uint cookie=0); - void deviceConnect(const QString &name, const QString &address); - void disconnect(); +private slots: void deviceDiscovered(const QBluetoothDeviceInfo&); void handleWatch(const QString &name, const QString &address); void onReadSocket(); @@ -185,18 +197,19 @@ public slots: void onConnected(); void onDisconnected(); void onError(QBluetoothSocket::SocketError error); - void reconnect(); private: - void decodeMsg(QByteArray data); + void sendData(const QByteArray &data); + bool dispatchMessage(uint endpoint, const QByteArray &data); QPointer socket; + QHash> tmpHandlers; + QHash handlers; bool is_connected; QByteArray writeData; QTimer reconnectTimer; QString _last_name; QString _last_address; }; -} #endif // WATCHCONNECTOR_H -- cgit v1.2.3 From 4527ab9a4147a8f15bf8ca5613341df9d0029d0c Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 30 Nov 2014 18:49:07 +0100 Subject: add stub datalogmanager --- daemon/daemon.pro | 6 ++++-- daemon/datalogmanager.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ daemon/datalogmanager.h | 25 +++++++++++++++++++++++++ daemon/manager.cpp | 9 +++++---- daemon/manager.h | 2 ++ daemon/watchconnector.cpp | 7 +++++++ daemon/watchconnector.h | 13 ++++++++++++- 7 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 daemon/datalogmanager.cpp create mode 100644 daemon/datalogmanager.h (limited to 'daemon/watchconnector.h') diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 768b50e..d4d7dbf 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -22,7 +22,8 @@ SOURCES += \ dbusconnector.cpp \ dbusadaptor.cpp \ appmanager.cpp \ - musicmanager.cpp + musicmanager.cpp \ + datalogmanager.cpp HEADERS += \ manager.h \ @@ -35,7 +36,8 @@ HEADERS += \ settings.h \ appmanager.h \ musicmanager.h \ - unpacker.h + unpacker.h \ + datalogmanager.h OTHER_FILES += \ org.pebbled.xml \ diff --git a/daemon/datalogmanager.cpp b/daemon/datalogmanager.cpp new file mode 100644 index 0000000..8026e15 --- /dev/null +++ b/daemon/datalogmanager.cpp @@ -0,0 +1,42 @@ +#include "datalogmanager.h" +#include "unpacker.h" + +DataLogManager::DataLogManager(WatchConnector *watch, QObject *parent) : + QObject(parent), watch(watch) +{ + watch->setEndpointHandler(WatchConnector::watchDATA_LOGGING, [this](const QByteArray& data) { + if (data.size() < 2) { + logger()->warn() << "small data_logging packet"; + return false; + } + + const char command = data[0]; + const int session = data[1]; + + switch (command) { + case WatchConnector::datalogOPEN: + logger()->debug() << "open datalog session" << session; + return true; + case WatchConnector::datalogCLOSE: + logger()->debug() << "close datalog session" << session; + return true; + case WatchConnector::datalogTIMEOUT: + logger()->debug() << "timeout datalog session" << session; + return true; + case WatchConnector::datalogDATA: + handleDataCommand(session, data.mid(2)); + return true; + default: + return false; + } + }); +} + +void DataLogManager::handleDataCommand(int session, const QByteArray &data) +{ + Unpacker u(data); + + // TODO Seemingly related to analytics, so not important. + + logger()->debug() << "got datalog data" << session << data.size(); +} diff --git a/daemon/datalogmanager.h b/daemon/datalogmanager.h new file mode 100644 index 0000000..47fc948 --- /dev/null +++ b/daemon/datalogmanager.h @@ -0,0 +1,25 @@ +#ifndef DATALOGMANAGER_H +#define DATALOGMANAGER_H + +#include "watchconnector.h" + +class DataLogManager : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + +public: + explicit DataLogManager(WatchConnector *watch, QObject *parent = 0); + +signals: + +public slots: + +private: + void handleDataCommand(int session, const QByteArray &data); + +private: + WatchConnector *watch; +}; + +#endif // DATALOGMANAGER_H diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 8a8acf4..7a02c86 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -12,6 +12,7 @@ Manager::Manager(Settings *settings, QObject *parent) : voice(new VoiceCallManager(settings, this)), notifications(new NotificationManager(settings, this)), music(new MusicManager(watch, this)), + datalog(new DataLogManager(watch, this)), apps(new AppManager(this)), notification(MNotification::DeviceEvent) { @@ -35,15 +36,15 @@ Manager::Manager(Settings *settings, QObject *parent) : return true; }); watch->setEndpointHandler(WatchConnector::watchPHONE_CONTROL, - [this](const QByteArray& data) { + [this](const QByteArray &data) { if (data.at(0) == WatchConnector::callHANGUP) { voice->hangupAll(); } return true; }); - watch->setEndpointHandler(WatchConnector::watchDATA_LOGGING, - [this](const QByteArray& data) { - //logger()->debug() << data.toHex(); + watch->setEndpointHandler(WatchConnector::watchLAUNCHER, + [this](const QByteArray &data) { + logger()->debug() << "LAUNCHER msg:" << data.toHex(); return true; }); diff --git a/daemon/manager.h b/daemon/manager.h index b210700..8b2fd96 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -6,6 +6,7 @@ #include "voicecallmanager.h" #include "notificationmanager.h" #include "musicmanager.h" +#include "datalogmanager.h" #include "appmanager.h" #include "settings.h" @@ -42,6 +43,7 @@ class Manager : VoiceCallManager *voice; NotificationManager *notifications; MusicManager *music; + DataLogManager *datalog; AppManager *apps; MNotification notification; diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index 61eeb67..a3ea181 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -160,6 +160,13 @@ void WatchConnector::onReadSocket() message_length = qFromBigEndian(&header[0]); endpoint = qFromBigEndian(&header[sizeof(quint16)]); + if (message_length > 8 * 1024) { + // Protocol does not allow messages more than 8K long, seemingly. + logger()->warn() << "received message size too long: " << message_length; + socket->readAll(); // drop input buffer + return; + } + // Now wait for the entire message if (socket->bytesAvailable() < header_length + message_length) { logger()->debug() << "incomplete msg body in read buffer"; diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 01c3dac..c5ec332 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -109,7 +109,18 @@ public: appmgrGET_APPBANK_STATUS = 1, appmgrGET_APPBANK_UUIDS = 5 }; - + enum AppMessage { + appmsgPUSH = 1, + appmsgREQUEST = 2, + appmsgACK = 0xFF, + appmsgNACK = 0x7F + }; + enum DataLogMessage { + datalogOPEN = 1, + datalogDATA = 2, + datalogCLOSE = 3, + datalogTIMEOUT = 7 + }; enum { leadEMAIL = 0, leadSMS = 1, -- cgit v1.2.3 From f7c8244641a4242f6a8c706bd918494b23e48075 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 30 Nov 2014 20:58:57 +0100 Subject: introduce the AppMsgManager and the JsKitManager will be used to handle application messages (push, etc) while the JSKitManager will run PebbleKit JS scripts. also add the ability to unpack PebbleDicts --- daemon/appmanager.cpp | 15 +++++++++ daemon/appmanager.h | 3 +- daemon/appmsgmanager.cpp | 77 ++++++++++++++++++++++++++++++++++++++++++ daemon/appmsgmanager.h | 30 +++++++++++++++++ daemon/daemon.pro | 12 +++++-- daemon/jskitmanager.cpp | 77 ++++++++++++++++++++++++++++++++++++++++++ daemon/jskitmanager.h | 41 ++++++++++++++++++++++ daemon/jskitmanager_p.h | 15 +++++++++ daemon/manager.cpp | 15 ++++----- daemon/manager.h | 8 +++-- daemon/unpacker.cpp | 88 ++++++++++++++++++++++++++++++++++++++++++++++++ daemon/unpacker.h | 29 +++++++++------- daemon/watchconnector.h | 12 +++++++ 13 files changed, 395 insertions(+), 27 deletions(-) create mode 100644 daemon/appmsgmanager.cpp create mode 100644 daemon/appmsgmanager.h create mode 100644 daemon/jskitmanager.cpp create mode 100644 daemon/jskitmanager.h create mode 100644 daemon/jskitmanager_p.h create mode 100644 daemon/unpacker.cpp (limited to 'daemon/watchconnector.h') diff --git a/daemon/appmanager.cpp b/daemon/appmanager.cpp index 34af3af..d06681e 100644 --- a/daemon/appmanager.cpp +++ b/daemon/appmanager.cpp @@ -29,6 +29,21 @@ QStringList AppManager::appPaths() const QStandardPaths::LocateDirectory); } +const AppManager::AppInfo & AppManager::info(const QUuid &uuid) const +{ + return _apps.value(uuid); +} + +const AppManager::AppInfo & AppManager::info(const QString &name) const +{ + QUuid uuid = _names.value(name); + if (!uuid.isNull()) { + return info(uuid); + } else { + return AppInfo(); + } +} + void AppManager::rescan() { QStringList watchedDirs = _watcher->directories(); diff --git a/daemon/appmanager.h b/daemon/appmanager.h index 5e150ab..dc2a979 100644 --- a/daemon/appmanager.h +++ b/daemon/appmanager.h @@ -30,7 +30,8 @@ public: QStringList appPaths() const; - bool installPebbleApp(const QString &pbwFile); + const AppInfo & info(const QUuid &uuid) const; + const AppInfo & info(const QString &shortName) const; public slots: void rescan(); diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp new file mode 100644 index 0000000..9eb948e --- /dev/null +++ b/daemon/appmsgmanager.cpp @@ -0,0 +1,77 @@ +#include "appmsgmanager.h" +#include "unpacker.h" + +// TODO D-Bus server for non JS kit apps!!!! + +AppMsgManager::AppMsgManager(WatchConnector *watch, QObject *parent) + : QObject(parent), watch(watch) +{ + watch->setEndpointHandler(WatchConnector::watchLAUNCHER, + [this](const QByteArray &data) { + if (data.at(0) == WatchConnector::appmsgPUSH) { + logger()->debug() << "LAUNCHER is PUSHing" << data.toHex(); + Unpacker u(data); + u.skip(1); // skip data.at(0) which we just already checked above. + uint transaction = u.read(); + QUuid uuid = u.readUuid(); + WatchConnector::Dict dict = u.readDict(); + if (u.bad() || !dict.contains(1)) { + logger()->warn() << "Failed to parse LAUNCHER message"; + return true; + } + + switch (dict.value(1).toInt()) { + case WatchConnector::launcherSTARTED: + logger()->debug() << "App starting in watch:" << uuid; + this->watch->sendMessage(WatchConnector::watchLAUNCHER, + buildAckMessage(transaction)); + emit appStarted(uuid); + break; + case WatchConnector::launcherSTOPPED: + logger()->debug() << "App stopping in watch:" << uuid; + this->watch->sendMessage(WatchConnector::watchLAUNCHER, + buildAckMessage(transaction)); + emit appStopped(uuid); + break; + default: + logger()->warn() << "LAUNCHER pushed unknown message:" << uuid << dict; + this->watch->sendMessage(WatchConnector::watchLAUNCHER, + buildNackMessage(transaction)); + break; + } + } + + return true; + }); + + watch->setEndpointHandler(WatchConnector::watchAPPLICATION_MESSAGE, + [this](const QByteArray &data) { + switch (data.at(0)) { + case WatchConnector::appmsgPUSH: + break; + } + + return true; + }); +} + +void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data) +{ + // TODO +} + +QByteArray AppMsgManager::buildAckMessage(uint transaction) +{ + QByteArray ba(2, Qt::Uninitialized); + ba[0] = WatchConnector::appmsgACK; + ba[1] = transaction; + return ba; +} + +QByteArray AppMsgManager::buildNackMessage(uint transaction) +{ + QByteArray ba(2, Qt::Uninitialized); + ba[0] = WatchConnector::appmsgNACK; + ba[1] = transaction; + return ba; +} diff --git a/daemon/appmsgmanager.h b/daemon/appmsgmanager.h new file mode 100644 index 0000000..651d84e --- /dev/null +++ b/daemon/appmsgmanager.h @@ -0,0 +1,30 @@ +#ifndef APPMSGMANAGER_H +#define APPMSGMANAGER_H + +#include "watchconnector.h" + +class AppMsgManager : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + +public: + explicit AppMsgManager(WatchConnector *watch, QObject *parent); + +public slots: + void send(const QUuid &uuid, const QVariantMap &data); + +signals: + void appStarted(const QUuid &uuid); + void appStopped(const QUuid &uuid); + void dataReceived(const QUuid &uuid, const QVariantMap &data); + +private: + static QByteArray buildAckMessage(uint transaction); + static QByteArray buildNackMessage(uint transaction); + +private: + WatchConnector *watch; +}; + +#endif // APPMSGMANAGER_H diff --git a/daemon/daemon.pro b/daemon/daemon.pro index d4d7dbf..21c15c8 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -4,7 +4,7 @@ CONFIG += console CONFIG += link_pkgconfig QT -= gui -QT += bluetooth dbus contacts +QT += bluetooth dbus contacts qml PKGCONFIG += mlite5 icu-i18n CONFIG += c++11 @@ -23,7 +23,10 @@ SOURCES += \ dbusadaptor.cpp \ appmanager.cpp \ musicmanager.cpp \ - datalogmanager.cpp + datalogmanager.cpp \ + unpacker.cpp \ + appmsgmanager.cpp \ + jskitmanager.cpp HEADERS += \ manager.h \ @@ -37,7 +40,10 @@ HEADERS += \ appmanager.h \ musicmanager.h \ unpacker.h \ - datalogmanager.h + datalogmanager.h \ + appmsgmanager.h \ + jskitmanager.h \ + jskitmanager_p.h OTHER_FILES += \ org.pebbled.xml \ diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp new file mode 100644 index 0000000..698b22b --- /dev/null +++ b/daemon/jskitmanager.cpp @@ -0,0 +1,77 @@ +#include +#include +#include "jskitmanager.h" +#include "jskitmanager_p.h" + +JSKitPebble::JSKitPebble(JSKitManager *mgr) + : QObject(mgr) +{ +} + +JSKitPebble::~JSKitPebble() +{ +} + +JSKitManager::JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *parent) : + QObject(parent), _apps(apps), _appmsg(appmsg), _engine(0) +{ + connect(_appmsg, &AppMsgManager::appStarted, this, &JSKitManager::handleAppStarted); + connect(_appmsg, &AppMsgManager::appStopped, this, &JSKitManager::handleAppStopped); +} + +JSKitManager::~JSKitManager() +{ + if (_engine) { + stopJsApp(); + } +} + +void JSKitManager::handleAppStarted(const QUuid &uuid) +{ + const auto &info = _apps->info(uuid); + if (!info.uuid.isNull() && info.isJSKit) { + logger()->debug() << "Preparing to start JSKit app" << info.uuid << info.shortName; + _curApp = info; + startJsApp(); + } +} + +void JSKitManager::handleAppStopped(const QUuid &uuid) +{ + if (!_curApp.uuid.isNull()) { + if (_curApp.uuid != uuid) { + logger()->warn() << "Closed app with invalid UUID"; + } + + _curApp = AppManager::AppInfo(); + } +} + +void JSKitManager::startJsApp() +{ + if (_engine) stopJsApp(); + _engine = new QJSEngine(this); + _jspebble = new JSKitPebble(this); + + QJSValue globalObj = _engine->globalObject(); + + globalObj.setProperty("Pebble", _engine->newQObject(_jspebble)); + + QJSValueIterator it(globalObj); + while (it.hasNext()) { + it.next(); + logger()->debug() << "JS property:" << it.name(); + } +} + +void JSKitManager::stopJsApp() +{ + if (!_engine) return; // Nothing to do! + + _engine->collectGarbage(); + + delete _engine; + _engine = 0; + delete _jspebble; + _jspebble = 0; +} diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h new file mode 100644 index 0000000..5e2880f --- /dev/null +++ b/daemon/jskitmanager.h @@ -0,0 +1,41 @@ +#ifndef JSKITMANAGER_H +#define JSKITMANAGER_H + +#include +#include "appmanager.h" +#include "appmsgmanager.h" + +class JSKitPebble; + +class JSKitManager : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + +public: + explicit JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *parent = 0); + ~JSKitManager(); + +signals: + +public slots: + +private slots: + void handleAppStarted(const QUuid &uuid); + void handleAppStopped(const QUuid &uuid); + +private: + void startJsApp(); + void stopJsApp(); + +private: + friend class JSKitPebble; + + AppManager *_apps; + AppMsgManager *_appmsg; + AppManager::AppInfo _curApp; + QJSEngine *_engine; + QPointer _jspebble; +}; + +#endif // JSKITMANAGER_H diff --git a/daemon/jskitmanager_p.h b/daemon/jskitmanager_p.h new file mode 100644 index 0000000..690a0ec --- /dev/null +++ b/daemon/jskitmanager_p.h @@ -0,0 +1,15 @@ +#ifndef JSKITMANAGER_P_H +#define JSKITMANAGER_P_H + +#include "jskitmanager.h" + +class JSKitPebble : public QObject +{ + Q_OBJECT + +public: + explicit JSKitPebble(JSKitManager *mgr); + ~JSKitPebble(); +}; + +#endif // JSKITMANAGER_P_H diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 7a02c86..778fdc6 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -1,19 +1,21 @@ -#include "manager.h" -#include "dbusadaptor.h" - #include #include #include +#include "manager.h" +#include "dbusadaptor.h" + Manager::Manager(Settings *settings, QObject *parent) : QObject(parent), settings(settings), watch(new WatchConnector(this)), dbus(new DBusConnector(this)), + apps(new AppManager(this)), voice(new VoiceCallManager(settings, this)), notifications(new NotificationManager(settings, this)), music(new MusicManager(watch, this)), datalog(new DataLogManager(watch, this)), - apps(new AppManager(this)), + appmsg(new AppMsgManager(watch, this)), + js(new JSKitManager(apps, appmsg, this)), notification(MNotification::DeviceEvent) { connect(settings, SIGNAL(valueChanged(QString)), SLOT(onSettingChanged(const QString&))); @@ -42,11 +44,6 @@ Manager::Manager(Settings *settings, QObject *parent) : } return true; }); - watch->setEndpointHandler(WatchConnector::watchLAUNCHER, - [this](const QByteArray &data) { - logger()->debug() << "LAUNCHER msg:" << data.toHex(); - return true; - }); connect(voice, SIGNAL(activeVoiceCallChanged()), SLOT(onActiveVoiceCallChanged())); connect(voice, SIGNAL(error(const QString &)), SLOT(onVoiceError(const QString &))); diff --git a/daemon/manager.h b/daemon/manager.h index 8b2fd96..f1dd53e 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -7,6 +7,8 @@ #include "notificationmanager.h" #include "musicmanager.h" #include "datalogmanager.h" +#include "appmsgmanager.h" +#include "jskitmanager.h" #include "appmanager.h" #include "settings.h" @@ -40,11 +42,13 @@ class Manager : WatchConnector *watch; DBusConnector *dbus; + AppManager *apps; VoiceCallManager *voice; NotificationManager *notifications; MusicManager *music; DataLogManager *datalog; - AppManager *apps; + AppMsgManager *appmsg; + JSKitManager *js; MNotification notification; @@ -73,7 +77,7 @@ protected: void transliterateMessage(const QString &text); signals: - void mprisMetadataChanged(QVariantMap); + void mprisMetadataChanged(const QVariantMap &metadata); public slots: void applyProfile(); diff --git a/daemon/unpacker.cpp b/daemon/unpacker.cpp new file mode 100644 index 0000000..fc38020 --- /dev/null +++ b/daemon/unpacker.cpp @@ -0,0 +1,88 @@ +#include "unpacker.h" +#include "watchconnector.h" + +QByteArray Unpacker::readBytes(int n) +{ + if (checkBad(n)) return QByteArray(); + const char *u = &_buf.constData()[_offset]; + _offset += n; + return QByteArray(u, n); +} + +QString Unpacker::readFixedString(int n) +{ + if (checkBad(n)) return QString(); + const char *u = &_buf.constData()[_offset]; + _offset += n; + return QString::fromUtf8(u, strnlen(u, n)); +} + +QUuid Unpacker::readUuid() +{ + if (checkBad(16)) return QString(); + _offset += 16; + return QUuid::fromRfc4122(_buf.mid(_offset - 16, 16)); +} + +QMap Unpacker::readDict() +{ + QMap d; + if (checkBad(1)) return d; + + const int n = read(); + + for (int i = 0; i < n; i++) { + if (checkBad(4 + 1 + 2)) return d; + const int key = readLE(); // For some reason, this is little endian. + const int type = readLE(); + const int width = readLE(); + + switch (type) { + case WatchConnector::typeBYTES: + d.insert(key, QVariant::fromValue(readBytes(width))); + break; + case WatchConnector::typeSTRING: + d.insert(key, QVariant::fromValue(readFixedString(width))); + break; + case WatchConnector::typeUINT: + switch (width) { + case sizeof(quint8): + d.insert(key, QVariant::fromValue(readLE())); + break; + case sizeof(quint16): + d.insert(key, QVariant::fromValue(readLE())); + break; + case sizeof(quint32): + d.insert(key, QVariant::fromValue(readLE())); + break; + default: + _bad = true; + return d; + } + + break; + case WatchConnector::typeINT: + switch (width) { + case sizeof(qint8): + d.insert(key, QVariant::fromValue(readLE())); + break; + case sizeof(qint16): + d.insert(key, QVariant::fromValue(readLE())); + break; + case sizeof(qint32): + d.insert(key, QVariant::fromValue(readLE())); + break; + default: + _bad = true; + return d; + } + + break; + default: + _bad = true; + return d; + } + } + + return d; +} diff --git a/daemon/unpacker.h b/daemon/unpacker.h index 94908cb..000c3e8 100644 --- a/daemon/unpacker.h +++ b/daemon/unpacker.h @@ -5,19 +5,30 @@ #include #include #include +#include +#include class Unpacker { + LOG4QT_DECLARE_STATIC_LOGGER(logger, Unpacker) + public: Unpacker(const QByteArray &data); template T read(); + template + T readLE(); + + QByteArray readBytes(int n); + QString readFixedString(int n); QUuid readUuid(); + QMap readDict(); + void skip(int n); bool bad() const; @@ -45,19 +56,13 @@ inline T Unpacker::read() return qFromBigEndian(u); } -inline QString Unpacker::readFixedString(int n) -{ - if (checkBad(n)) return QString(); - const char *u = &_buf.constData()[_offset]; - _offset += n; - return QString::fromUtf8(u, strnlen(u, n)); -} - -inline QUuid Unpacker::readUuid() +template +inline T Unpacker::readLE() { - if (checkBad(16)) return QString(); - _offset += 16; - return QUuid::fromRfc4122(_buf.mid(_offset - 16, 16)); + if (checkBad(sizeof(T))) return 0; + const uchar *u = p(); + _offset += sizeof(T); + return qFromLittleEndian(u); } inline void Unpacker::skip(int n) diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index c5ec332..8a7d574 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -121,6 +121,10 @@ public: datalogCLOSE = 3, datalogTIMEOUT = 7 }; + enum { + launcherSTARTED = 1, + launcherSTOPPED = 0 + }; enum { leadEMAIL = 0, leadSMS = 1, @@ -153,6 +157,14 @@ public: DEFAULT_TIMEOUT_MSECS = 100 }; + typedef QMap Dict; + enum DictItemType { + typeBYTES, + typeSTRING, + typeUINT, + typeINT + }; + typedef std::function EndpointHandlerFunc; explicit WatchConnector(QObject *parent = 0); -- cgit v1.2.3 From 1e3794c476caf5c41360c36cc13c8425ec0dd26c Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 1 Dec 2014 02:21:30 +0100 Subject: implement message passing around jskit apps and watch --- daemon/appinfo.cpp | 26 +++++-- daemon/appinfo.h | 8 ++- daemon/appmsgmanager.cpp | 171 +++++++++++++++++++++++++++++++++++++++++++--- daemon/appmsgmanager.h | 17 ++++- daemon/daemon.pro | 6 +- daemon/jskitmanager.cpp | 25 ++++++- daemon/jskitmanager.h | 5 +- daemon/jskitobjects.cpp | 35 ++++++++-- daemon/jskitobjects.h | 14 +++- daemon/manager.cpp | 2 +- daemon/packer.cpp | 91 ++++++++++++++++++++++++ daemon/packer.h | 67 ++++++++++++++++++ daemon/unpacker.cpp | 2 +- daemon/watchconnector.cpp | 19 +++--- daemon/watchconnector.h | 4 +- 15 files changed, 448 insertions(+), 44 deletions(-) create mode 100644 daemon/packer.cpp create mode 100644 daemon/packer.h (limited to 'daemon/watchconnector.h') diff --git a/daemon/appinfo.cpp b/daemon/appinfo.cpp index a4442a3..e2406b8 100644 --- a/daemon/appinfo.cpp +++ b/daemon/appinfo.cpp @@ -10,7 +10,8 @@ struct AppInfoData : public QSharedData { QString versionLabel; bool watchface; bool jskit; - QHash appKeys; + QHash keyInts; + QHash keyNames; QString path; }; @@ -116,19 +117,30 @@ void AppInfo::setJSKit(bool b) d->jskit = b; } -QHash AppInfo::appKeys() const +void AppInfo::addAppKey(const QString &key, int value) { - return d->appKeys; + d->keyInts.insert(key, value); + d->keyNames.insert(value, key); } -void AppInfo::setAppKeys(const QHash &appKeys) +bool AppInfo::hasAppKeyValue(int value) const { - d->appKeys = appKeys; + return d->keyNames.contains(value); } -void AppInfo::addAppKey(const QString &key, int value) +QString AppInfo::appKeyForValue(int value) const +{ + return d->keyNames.value(value); +} + +bool AppInfo::hasAppKey(const QString &key) const +{ + return d->keyInts.contains(key); +} + +int AppInfo::valueForAppKey(const QString &key) const { - d->appKeys.insert(key, value); + return d->keyInts.value(key, -1); } QString AppInfo::path() const diff --git a/daemon/appinfo.h b/daemon/appinfo.h index da71dfc..038a708 100644 --- a/daemon/appinfo.h +++ b/daemon/appinfo.h @@ -52,10 +52,14 @@ public: bool isJSKit() const; void setJSKit(bool b); - QHash appKeys() const; - void setAppKeys(const QHash &string); void addAppKey(const QString &key, int value); + bool hasAppKeyValue(int value) const; + QString appKeyForValue(int value) const; + + bool hasAppKey(const QString &key) const; + int valueForAppKey(const QString &key) const; + QString path() const; void setPath(const QString &string); diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp index 23bf802..b620078 100644 --- a/daemon/appmsgmanager.cpp +++ b/daemon/appmsgmanager.cpp @@ -1,21 +1,28 @@ #include "appmsgmanager.h" #include "unpacker.h" +#include "packer.h" // TODO D-Bus server for non JS kit apps!!!! -AppMsgManager::AppMsgManager(WatchConnector *watch, QObject *parent) - : QObject(parent), watch(watch) +AppMsgManager::AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *parent) + : QObject(parent), apps(apps), watch(watch), lastTransactionId(0) { watch->setEndpointHandler(WatchConnector::watchLAUNCHER, [this](const QByteArray &data) { if (data.at(0) == WatchConnector::appmsgPUSH) { - Unpacker u(data); - u.skip(1); // skip data.at(0) which we just already checked above. - uint transaction = u.read(); - QUuid uuid = u.readUuid(); - WatchConnector::Dict dict = u.readDict(); - if (u.bad() || !dict.contains(1)) { - logger()->warn() << "Failed to parse LAUNCHER message"; + uint transaction; + QUuid uuid; + WatchConnector::Dict dict; + + if (!unpackPushMessage(data, &transaction, &uuid, &dict)) { + // Failed to parse! + // Since we're the only one handling this endpoint, + // all messages must be accepted + logger()->warn() << "Failed to parser LAUNCHER PUSH message"; + return true; + } + if (!dict.contains(1)) { + logger()->warn() << "LAUNCHER message has no item in dict"; return true; } @@ -46,7 +53,26 @@ AppMsgManager::AppMsgManager(WatchConnector *watch, QObject *parent) watch->setEndpointHandler(WatchConnector::watchAPPLICATION_MESSAGE, [this](const QByteArray &data) { switch (data.at(0)) { - case WatchConnector::appmsgPUSH: + case WatchConnector::appmsgPUSH: { + uint transaction; + QUuid uuid; + WatchConnector::Dict dict; + + if (!unpackPushMessage(data, &transaction, &uuid, &dict)) { + logger()->warn() << "Failed to parse APP_MSG PUSH"; + return true; + } + + logger()->debug() << "Received appmsg PUSH from" << uuid << "with" << dict; + + QVariantMap data = mapAppKeys(uuid, dict); + logger()->debug() << "Mapped dict" << data; + + emit messageReceived(uuid, data); + break; + } + default: + logger()->warn() << "Unknown application message type:" << data.at(0); break; } @@ -54,9 +80,132 @@ AppMsgManager::AppMsgManager(WatchConnector *watch, QObject *parent) }); } +void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data, const std::function &ackCallback, const std::function &nackCallback) +{ + WatchConnector::Dict dict = mapAppKeys(uuid, data); + quint8 transaction = ++lastTransactionId; + QByteArray msg = buildPushMessage(transaction, uuid, dict); + + logger()->debug() << "Sending appmsg" << transaction << "to" << uuid << "with" << dict; + + WatchConnector::Dict t_dict; + QUuid t_uuid; + uint t_trans; + if (unpackPushMessage(msg, &t_trans, &t_uuid, &t_dict)) { + logger()->debug() << t_trans << t_uuid << t_dict; + } else { + logger()->warn() << "not unpack my own"; + } + + + watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE, msg, + [this, ackCallback, nackCallback, transaction](const QByteArray &reply) { + if (reply.size() < 2) return false; + + quint8 type = reply[0]; + quint8 recv_transaction = reply[1]; + + logger()->debug() << "Got response to transaction" << transaction; + + if (recv_transaction != transaction) return false; + + switch (type) { + case WatchConnector::appmsgACK: + logger()->debug() << "Got ACK to transaction" << transaction; + if (ackCallback) ackCallback(); + return true; + case WatchConnector::appmsgNACK: + logger()->info() << "Got NACK to transaction" << transaction; + if (nackCallback) nackCallback(); + return true; + default: + return false; + } + }); +} + void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data) { - // TODO + std::function nullCallback; + send(uuid, data, nullCallback, nullCallback); +} + +WatchConnector::Dict AppMsgManager::mapAppKeys(const QUuid &uuid, const QVariantMap &data) +{ + AppInfo info = apps->info(uuid); + if (info.uuid() != uuid) { + logger()->warn() << "Unknown app GUID while sending message:" << uuid; + } + + WatchConnector::Dict d; + + for (QVariantMap::const_iterator it = data.constBegin(); it != data.constEnd(); ++it) { + if (info.hasAppKey(it.key())) { + d.insert(info.valueForAppKey(it.key()), it.value()); + } else { + // Even if we do not know about this appkey, try to see if it's already a numeric key we + // can send to the watch. + bool ok = false; + int num = it.key().toInt(&ok); + if (ok) { + d.insert(num, it.value()); + } else { + logger()->warn() << "Unknown appKey" << it.key() << "for app with GUID" << uuid; + } + } + } + + return d; +} + +QVariantMap AppMsgManager::mapAppKeys(const QUuid &uuid, const WatchConnector::Dict &dict) +{ + AppInfo info = apps->info(uuid); + if (info.uuid() != uuid) { + logger()->warn() << "Unknown app GUID while sending message:" << uuid; + } + + QVariantMap data; + + for (WatchConnector::Dict::const_iterator it = dict.constBegin(); it != dict.constEnd(); ++it) { + if (info.hasAppKeyValue(it.key())) { + data.insert(info.appKeyForValue(it.key()), it.value()); + } else { + logger()->warn() << "Unknown appKey value" << it.key() << "for app with GUID" << uuid; + data.insert(QString::number(it.key()), it.value()); + } + } + + return data; +} + +bool AppMsgManager::unpackPushMessage(const QByteArray &msg, uint *transaction, QUuid *uuid, WatchConnector::Dict *dict) +{ + Unpacker u(msg); + quint8 code = u.read(); + Q_ASSERT(code == WatchConnector::appmsgPUSH); + + *transaction = u.read(); + *uuid = u.readUuid(); + *dict = u.readDict(); + + if (u.bad()) { + return false; + } + + return true; +} + +QByteArray AppMsgManager::buildPushMessage(uint transaction, const QUuid &uuid, const WatchConnector::Dict &dict) +{ + QByteArray ba; + Packer p(&ba); + p.write(WatchConnector::appmsgPUSH); + p.write(transaction); + p.writeUuid(uuid); + p.writeDict(dict); + + return ba; } QByteArray AppMsgManager::buildAckMessage(uint transaction) diff --git a/daemon/appmsgmanager.h b/daemon/appmsgmanager.h index 651d84e..ca1d484 100644 --- a/daemon/appmsgmanager.h +++ b/daemon/appmsgmanager.h @@ -2,6 +2,7 @@ #define APPMSGMANAGER_H #include "watchconnector.h" +#include "appmanager.h" class AppMsgManager : public QObject { @@ -9,7 +10,11 @@ class AppMsgManager : public QObject LOG4QT_DECLARE_QCLASS_LOGGER public: - explicit AppMsgManager(WatchConnector *watch, QObject *parent); + explicit AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *parent); + + void send(const QUuid &uuid, const QVariantMap &data, + const std::function &ackCallback, + const std::function &nackCallback); public slots: void send(const QUuid &uuid, const QVariantMap &data); @@ -17,14 +22,22 @@ public slots: signals: void appStarted(const QUuid &uuid); void appStopped(const QUuid &uuid); - void dataReceived(const QUuid &uuid, const QVariantMap &data); + void messageReceived(const QUuid &uuid, const QVariantMap &data); private: + WatchConnector::Dict mapAppKeys(const QUuid &uuid, const QVariantMap &data); + QVariantMap mapAppKeys(const QUuid &uuid, const WatchConnector::Dict &dict); + + static bool unpackPushMessage(const QByteArray &msg, uint *transaction, QUuid *uuid, WatchConnector::Dict *dict); + + static QByteArray buildPushMessage(uint transaction, const QUuid &uuid, const WatchConnector::Dict &dict); static QByteArray buildAckMessage(uint transaction); static QByteArray buildNackMessage(uint transaction); private: + AppManager *apps; WatchConnector *watch; + quint8 lastTransactionId; }; #endif // APPMSGMANAGER_H diff --git a/daemon/daemon.pro b/daemon/daemon.pro index a6631df..c59d408 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -28,7 +28,8 @@ SOURCES += \ appmsgmanager.cpp \ jskitmanager.cpp \ appinfo.cpp \ - jskitobjects.cpp + jskitobjects.cpp \ + packer.cpp HEADERS += \ manager.h \ @@ -46,7 +47,8 @@ HEADERS += \ appmsgmanager.h \ jskitmanager.h \ appinfo.h \ - jskitobjects.h + jskitobjects.h \ + packer.h OTHER_FILES += \ org.pebbled.xml \ diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 2da6c09..f8ec34a 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -9,6 +9,7 @@ JSKitManager::JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *par { connect(_appmsg, &AppMsgManager::appStarted, this, &JSKitManager::handleAppStarted); connect(_appmsg, &AppMsgManager::appStopped, this, &JSKitManager::handleAppStopped); + connect(_appmsg, &AppMsgManager::messageReceived, this, &JSKitManager::handleAppMessage); } JSKitManager::~JSKitManager() @@ -40,6 +41,23 @@ void JSKitManager::handleAppStopped(const QUuid &uuid) } } +void JSKitManager::handleAppMessage(const QUuid &uuid, const QVariantMap &data) +{ + if (_curApp.uuid() == uuid) { + logger()->debug() << "received a message for the current JSKit app"; + + if (!_engine) { + logger()->debug() << "but engine is stopped"; + return; + } + + QJSValue eventObj = _engine->newObject(); + eventObj.setProperty("payload", _engine->toScriptValue(data)); + + _jspebble->invokeCallbacks("appmessage", QJSValueList({eventObj})); + } +} + void JSKitManager::startJsApp() { if (_engine) stopJsApp(); @@ -49,7 +67,8 @@ void JSKitManager::startJsApp() } _engine = new QJSEngine(this); - _jspebble = new JSKitPebble(this); + _jspebble = new JSKitPebble(_curApp, this); + _jsconsole = new JSKitConsole(this); _jsstorage = new JSKitLocalStorage(_curApp.uuid(), this); logger()->debug() << "starting JS app"; @@ -57,8 +76,12 @@ void JSKitManager::startJsApp() QJSValue globalObj = _engine->globalObject(); globalObj.setProperty("Pebble", _engine->newQObject(_jspebble)); + globalObj.setProperty("console", _engine->newQObject(_jsconsole)); globalObj.setProperty("localStorage", _engine->newQObject(_jsstorage)); + QJSValue windowObj = _engine->newObject(); + windowObj.setProperty("localStorage", globalObj.property("localStorage")); + globalObj.setProperty("window", windowObj); QFile scriptFile(_curApp.path() + "/pebble-js-app.js"); if (!scriptFile.open(QIODevice::ReadOnly | QIODevice::Text)) { diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index f25a96f..d09bf54 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -6,6 +6,7 @@ #include "appmsgmanager.h" class JSKitPebble; +class JSKitConsole; class JSKitLocalStorage; class JSKitManager : public QObject @@ -18,12 +19,14 @@ public: ~JSKitManager(); signals: + void appNotification(const QUuid &uuid, const QString &title, const QString &body); public slots: private slots: void handleAppStarted(const QUuid &uuid); void handleAppStopped(const QUuid &uuid); + void handleAppMessage(const QUuid &uuid, const QVariantMap &data); private: void startJsApp(); @@ -37,8 +40,8 @@ private: AppInfo _curApp; QJSEngine *_engine; QPointer _jspebble; + QPointer _jsconsole; QPointer _jsstorage; - }; #endif // JSKITMANAGER_H diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index f40138a..aecc55d 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -4,8 +4,8 @@ #include #include "jskitobjects.h" -JSKitPebble::JSKitPebble(JSKitManager *mgr) - : QObject(mgr), _mgr(mgr) +JSKitPebble::JSKitPebble(const AppInfo &info, JSKitManager *mgr) + : QObject(mgr), _appInfo(info), _mgr(mgr) { } @@ -34,17 +34,28 @@ void JSKitPebble::removeEventListener(const QString &type, QJSValue function) void JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSValue callbackForNack) { - // TODO contact _mgr->appmsg->... - logger()->debug() << "sendAppMessage" << message.toString(); + QVariantMap data = message.toVariant().toMap(); + + logger()->debug() << "sendAppMessage" << data; + + _mgr->_appmsg->send(_appInfo.uuid(), data, [this, callbackForAck]() mutable { + logger()->debug() << "Invoking ack callback"; + callbackForAck.call(); + }, [this, callbackForNack]() mutable { + logger()->debug() << "Invoking nack callback"; + callbackForNack.call(); + }); } void JSKitPebble::showSimpleNotificationOnPebble(const QString &title, const QString &body) { logger()->debug() << "showSimpleNotificationOnPebble" << title << body; + emit _mgr->appNotification(_appInfo.uuid(), title, body); } void JSKitPebble::openUrl(const QUrl &url) { + logger()->debug() << "opening url" << url.toString(); if (!QDesktopServices::openUrl(url)) { logger()->warn() << "Failed to open URL:" << url; } @@ -56,6 +67,7 @@ void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args) QList &callbacks = _callbacks[type]; for (QList::iterator it = callbacks.begin(); it != callbacks.end(); ++it) { + logger()->debug() << "invoking callback" << type << it->toString(); QJSValue result = it->call(args); if (result.isError()) { logger()->warn() << "error while invoking callback" << type << it->toString() << ":" @@ -64,6 +76,16 @@ void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args) } } +JSKitConsole::JSKitConsole(JSKitManager *mgr) + : QObject(mgr) +{ +} + +void JSKitConsole::log(const QString &msg) +{ + logger()->info() << msg; +} + JSKitLocalStorage::JSKitLocalStorage(const QUuid &uuid, JSKitManager *mgr) : QObject(mgr), _storage(new QSettings(getStorageFileFor(uuid), QSettings::IniFormat, this)) { @@ -117,5 +139,8 @@ QString JSKitLocalStorage::getStorageFileFor(const QUuid &uuid) { QDir dataDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); dataDir.mkdir("js-storage"); - return dataDir.absoluteFilePath("js-storage/" + uuid.toString() + ".ini"); + QString fileName = uuid.toString(); + fileName.remove('{'); + fileName.remove('}'); + return dataDir.absoluteFilePath("js-storage/" + fileName + ".ini"); } diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 2375084..c59bfac 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -10,7 +10,7 @@ class JSKitPebble : public QObject LOG4QT_DECLARE_QCLASS_LOGGER public: - explicit JSKitPebble(JSKitManager *mgr); + explicit JSKitPebble(const AppInfo &appInfo, JSKitManager *mgr); Q_INVOKABLE void addEventListener(const QString &type, QJSValue function); Q_INVOKABLE void removeEventListener(const QString &type, QJSValue function); @@ -24,10 +24,22 @@ public: void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList()); private: + AppInfo _appInfo; JSKitManager *_mgr; QHash> _callbacks; }; +class JSKitConsole : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + +public: + explicit JSKitConsole(JSKitManager *mgr); + + Q_INVOKABLE void log(const QString &msg); +}; + class JSKitLocalStorage : public QObject { Q_OBJECT diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 778fdc6..8d41c89 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -14,7 +14,7 @@ Manager::Manager(Settings *settings, QObject *parent) : notifications(new NotificationManager(settings, this)), music(new MusicManager(watch, this)), datalog(new DataLogManager(watch, this)), - appmsg(new AppMsgManager(watch, this)), + appmsg(new AppMsgManager(apps, watch, this)), js(new JSKitManager(apps, appmsg, this)), notification(MNotification::DeviceEvent) { diff --git a/daemon/packer.cpp b/daemon/packer.cpp new file mode 100644 index 0000000..569f7a8 --- /dev/null +++ b/daemon/packer.cpp @@ -0,0 +1,91 @@ +#include "packer.h" +#include "watchconnector.h" + +void Packer::writeBytes(int n, const QByteArray &b) +{ + if (b.size() > n) { + _buf->append(b.constData(), n); + } else { + int diff = n - b.size(); + _buf->append(b); + if (diff > 0) { + _buf->append(QByteArray(diff, '\0')); + } + } +} + +void Packer::writeUuid(const QUuid &uuid) +{ + writeBytes(16, uuid.toRfc4122()); +} + +void Packer::writeDict(const QMap &d) +{ + int size = d.size(); + if (size > 0xFF) { + logger()->warn() << "Dictionary is too large to encode"; + writeLE(0); + return; + } + + writeLE(size); + + for (QMap::const_iterator it = d.constBegin(); it != d.constEnd(); ++it) { + writeLE(it.key()); + + switch (int(it.value().type())) { + case QMetaType::Char: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(char)); + writeLE(it.value().value()); + break; + case QMetaType::Short: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(short)); + writeLE(it.value().value()); + break; + case QMetaType::Int: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(int)); + writeLE(it.value().value()); + break; + + case QMetaType::UChar: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(char)); + writeLE(it.value().value()); + break; + case QMetaType::UShort: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(short)); + writeLE(it.value().value()); + break; + case QMetaType::UInt: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(int)); + writeLE(it.value().value()); + break; + + case QMetaType::QByteArray: { + QByteArray ba = it.value().toByteArray(); + writeLE(WatchConnector::typeBYTES); + writeLE(ba.size()); + _buf->append(ba); + break; + } + + default: + logger()->warn() << "Unknown dict item type:" << it.value().typeName(); + /* Fallthrough */ + case QMetaType::QString: + case QMetaType::QUrl: + { + QByteArray s = it.value().toString().toUtf8(); + writeLE(WatchConnector::typeSTRING); + writeLE(s.size()); + _buf->append(s); + break; + } + } + } +} diff --git a/daemon/packer.h b/daemon/packer.h new file mode 100644 index 0000000..d22072c --- /dev/null +++ b/daemon/packer.h @@ -0,0 +1,67 @@ +#ifndef PACKER_H +#define PACKER_H + +#include +#include +#include +#include +#include +#include + +class Packer +{ + LOG4QT_DECLARE_STATIC_LOGGER(logger, Packer) + +public: + Packer(QByteArray *buf); + + template + void write(T v); + + template + void writeLE(T v); + + void writeBytes(int n, const QByteArray &b); + + void writeFixedString(int n, const QString &s); + + void writeUuid(const QUuid &uuid); + + void writeDict(const QMap &d); + +private: + char *p(int n); + uchar *up(int n); + QByteArray *_buf; +}; + +inline Packer::Packer(QByteArray *buf) + : _buf(buf) +{ +} + +template +void Packer::write(T v) +{ + qToBigEndian(v, up(sizeof(T))); +} + +template +void Packer::writeLE(T v) +{ + qToLittleEndian(v, up(sizeof(T))); +} + +inline char * Packer::p(int n) +{ + int size = _buf->size(); + _buf->resize(size + n); + return &_buf->data()[size]; +} + +inline uchar * Packer::up(int n) +{ + return reinterpret_cast(p(n)); +} + +#endif // PACKER_H diff --git a/daemon/unpacker.cpp b/daemon/unpacker.cpp index fc38020..e904db8 100644 --- a/daemon/unpacker.cpp +++ b/daemon/unpacker.cpp @@ -29,7 +29,7 @@ QMap Unpacker::readDict() QMap d; if (checkBad(1)) return d; - const int n = read(); + const int n = readLE(); for (int i = 0; i < n; i++) { if (checkBad(4 + 1 + 2)) return d; diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index a3ea181..dd95821 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -89,7 +89,7 @@ QString WatchConnector::decodeEndpoint(uint val) return endpoint ? QString(endpoint) : QString("watchUNKNOWN_%1").arg(val); } -void WatchConnector::setEndpointHandler(uint endpoint, EndpointHandlerFunc func) +void WatchConnector::setEndpointHandler(uint endpoint, const EndpointHandlerFunc &func) { if (func) { handlers.insert(endpoint, func); @@ -241,6 +241,7 @@ void WatchConnector::sendData(const QByteArray &data) reconnect(); } else if (is_connected) { logger()->debug() << "Writing" << data.length() << "bytes to socket"; + logger()->debug() << data.toHex(); socket->write(data); } } @@ -251,7 +252,7 @@ void WatchConnector::onBytesWritten(qint64 bytes) logger()->debug() << "Socket written" << bytes << "bytes," << writeData.length() << "left"; } -void WatchConnector::sendMessage(uint endpoint, const QByteArray &data) +void WatchConnector::sendMessage(uint endpoint, const QByteArray &data, const EndpointHandlerFunc &callback) { logger()->debug() << "sending message to endpoint" << decodeEndpoint(endpoint); QByteArray msg; @@ -268,6 +269,10 @@ void WatchConnector::sendMessage(uint endpoint, const QByteArray &data) msg.append(data); sendData(msg); + + if (callback) { + tmpHandlers[endpoint].append(callback); + } } void WatchConnector::buildData(QByteArray &res, QStringList data) @@ -445,9 +450,8 @@ void WatchConnector::endPhoneCall(uint cookie) void WatchConnector::getAppbankStatus(const std::function& callback) { - sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_STATUS)); - - tmpHandlers[watchAPP_MANAGER].append([this, callback](const QByteArray &data) { + sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_STATUS), + [this, callback](const QByteArray &data) { if (data.at(0) != appmgrGET_APPBANK_STATUS) { return false; } @@ -491,9 +495,8 @@ void WatchConnector::getAppbankStatus(const std::function &)>& callback) { - sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_UUIDS)); - - tmpHandlers[watchAPP_MANAGER].append([this, callback](const QByteArray &data) { + sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_UUIDS), + [this, callback](const QByteArray &data) { if (data.at(0) != appmgrGET_APPBANK_UUIDS) { return false; } diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 8a7d574..6c7fdd4 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -173,7 +173,7 @@ public: inline bool isConnected() const { return is_connected; } inline QString name() const { return socket != nullptr ? socket->peerName() : ""; } - void setEndpointHandler(uint endpoint, EndpointHandlerFunc func); + void setEndpointHandler(uint endpoint, const EndpointHandlerFunc &func); void clearEndpointHandler(uint endpoint); static QString timeStamp(); @@ -192,7 +192,7 @@ public slots: void disconnect(); void reconnect(); - void sendMessage(uint endpoint, const QByteArray &data); + void sendMessage(uint endpoint, const QByteArray &data, const EndpointHandlerFunc &callback = EndpointHandlerFunc()); void ping(uint val); void time(); -- cgit v1.2.3 From 49c20eb7e2933ae6e9e4337fc0fe9b49a39efde8 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 7 Dec 2014 01:31:51 +0100 Subject: add bankmanager and ability to unload apps --- daemon/bankmanager.cpp | 233 ++++++++++++++++++++++++++++++++++++++++++++++ daemon/bankmanager.h | 51 ++++++++++ daemon/daemon.pro | 6 +- daemon/manager.cpp | 12 +++ daemon/manager.h | 4 + daemon/watchconnector.cpp | 90 ------------------ daemon/watchconnector.h | 4 +- org.pebbled.Watch.xml | 5 + 8 files changed, 310 insertions(+), 95 deletions(-) create mode 100644 daemon/bankmanager.cpp create mode 100644 daemon/bankmanager.h (limited to 'daemon/watchconnector.h') diff --git a/daemon/bankmanager.cpp b/daemon/bankmanager.cpp new file mode 100644 index 0000000..194ec77 --- /dev/null +++ b/daemon/bankmanager.cpp @@ -0,0 +1,233 @@ +#include "unpacker.h" +#include "packer.h" +#include "bankmanager.h" + +BankManager::BankManager(WatchConnector *watch, AppManager *apps, QObject *parent) : + QObject(parent), watch(watch), apps(apps) +{ + connect(watch, &WatchConnector::connectedChanged, this, &BankManager::handleWatchConnected); +} + +int BankManager::numSlots() const +{ + return _slots.size(); +} + +bool BankManager::uploadApp(const QUuid &uuid, int slot) +{ + AppInfo info = apps->info(uuid); + if (info.uuid() != uuid) { + logger()->warn() << "uuid" << uuid << "is not installed"; + return false; + } + if (slot == -1) { + slot = findUnusedSlot(); + if (slot == -1) { + logger()->warn() << "no free slots!"; + return false; + } + } + if (slot < 0 || slot > _slots.size()) { + logger()->warn() << "invalid slot index"; + return false; + } + + // TODO + + return false; +} + +bool BankManager::unloadApp(int slot) +{ + if (slot < 0 || slot > _slots.size()) { + logger()->warn() << "invalid slot index"; + return false; + } + if (!_slots[slot].used) { + logger()->warn() << "slot is empty"; + return false; + } + + logger()->debug() << "going to unload app" << _slots[slot].name << "in slot" << slot; + + int installId = _slots[slot].id; + + QByteArray msg; + msg.reserve(2 * sizeof(quint32)); + Packer p(&msg); + p.write(WatchConnector::appmgrREMOVE_APP); + p.write(installId); + p.write(slot); + + watch->sendMessage(WatchConnector::watchAPP_MANAGER, msg, + [this](const QByteArray &data) { + Unpacker u(data); + if (u.read() != WatchConnector::appmgrREMOVE_APP) { + return false; + } + + uint result = u.read(); + switch (result) { + case 1: /* Success */ + logger()->debug() << "sucessfully unloaded app"; + break; + default: + logger()->warn() << "could not unload app. result code:" << result; + break; + } + + QMetaObject::invokeMethod(this, "refresh", Qt::QueuedConnection); + + return true; + }); + + return true; // Operation in progress +} + +void BankManager::refresh() +{ + logger()->debug() << "refreshing bank status"; + + watch->sendMessage(WatchConnector::watchAPP_MANAGER, + QByteArray(1, WatchConnector::appmgrGET_APPBANK_STATUS), + [this](const QByteArray &data) { + if (data.at(0) != WatchConnector::appmgrGET_APPBANK_STATUS) { + return false; + } + + if (data.size() < 9) { + logger()->warn() << "invalid getAppbankStatus response"; + return true; + } + + Unpacker u(data); + + u.skip(sizeof(quint8)); + + unsigned int num_banks = u.read(); + unsigned int apps_installed = u.read(); + + logger()->debug() << "Bank status:" << apps_installed << "/" << num_banks; + + _slots.resize(num_banks); + for (unsigned int i = 0; i < num_banks; i++) { + _slots[i].used = false; + _slots[i].id = 0; + _slots[i].name.clear(); + _slots[i].company.clear(); + _slots[i].flags = 0; + _slots[i].version = 0; + _slots[i].uuid = QUuid(); + } + + for (unsigned int i = 0; i < apps_installed; i++) { + unsigned int id = u.read(); + int index = u.read(); + QString name = u.readFixedString(32); + QString company = u.readFixedString(32); + unsigned int flags = u.read(); + unsigned short version = u.read(); + + if (index < 0 || index >= _slots.size()) { + logger()->warn() << "Invalid slot index" << index; + continue; + } + + if (u.bad()) { + logger()->warn() << "short read"; + return true; + } + + _slots[index].used = true; + _slots[index].id = id; + _slots[index].name = name; + _slots[index].company = company; + _slots[index].flags = flags; + _slots[index].version = version; + + AppInfo info = apps->info(name); + QUuid uuid = info.uuid(); + _slots[index].uuid = uuid; + + logger()->debug() << index << id << name << company << flags << version << uuid; + } + + emit this->slotsChanged(); + + return true; + }); +} + +int BankManager::findUnusedSlot() const +{ + for (int i = 0; i < _slots.size(); ++i) { + if (!_slots[i].used) { + return i; + } + } + + return -1; +} + +void BankManager::handleWatchConnected() +{ + if (watch->isConnected()) { + refresh(); + } +} + +#if 0 + +void WatchConnector::getAppbankStatus(const std::function& callback) +{ + sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_STATUS), + [this, callback](const QByteArray &data) { + + }); +} + +void WatchConnector::getAppbankUuids(const function &)>& callback) +{ + sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_UUIDS), + [this, callback](const QByteArray &data) { + if (data.at(0) != appmgrGET_APPBANK_UUIDS) { + return false; + } + logger()->debug() << "getAppbankUuids response" << data.toHex(); + + if (data.size() < 5) { + logger()->warn() << "invalid getAppbankUuids response"; + return true; + } + + Unpacker u(data); + + u.skip(sizeof(quint8)); + + unsigned int apps_installed = u.read(); + + logger()->debug() << apps_installed; + + QList uuids; + + for (unsigned int i = 0; i < apps_installed; i++) { + QUuid uuid = u.readUuid(); + + logger()->debug() << uuid.toString(); + + if (u.bad()) { + logger()->warn() << "short read"; + return true; + } + + uuids.push_back(uuid); + } + + logger()->debug() << "finished"; + + callback(uuids); + + return true; + }); +} +#endif diff --git a/daemon/bankmanager.h b/daemon/bankmanager.h new file mode 100644 index 0000000..28729b9 --- /dev/null +++ b/daemon/bankmanager.h @@ -0,0 +1,51 @@ +#ifndef BANKMANAGER_H +#define BANKMANAGER_H + +#include "watchconnector.h" +#include "appmanager.h" + +class BankManager : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + +public: + explicit BankManager(WatchConnector *watch, AppManager *apps, QObject *parent = 0); + + int numSlots() const; + + +signals: + void slotsChanged(); + +public slots: + bool uploadApp(const QUuid &uuid, int slot = -1); + bool unloadApp(int slot); + + void refresh(); + +private: + int findUnusedSlot() const; + + +private slots: + void handleWatchConnected(); + +private: + WatchConnector *watch; + AppManager *apps; + + struct SlotInfo { + bool used; + quint32 id; + QString name; + QString company; + quint32 flags; + quint16 version; + QUuid uuid; + }; + + QVector _slots; +}; + +#endif // BANKMANAGER_H diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 81570c7..21ebffa 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -28,7 +28,8 @@ SOURCES += \ jskitmanager.cpp \ appinfo.cpp \ jskitobjects.cpp \ - packer.cpp + packer.cpp \ + bankmanager.cpp HEADERS += \ manager.h \ @@ -46,7 +47,8 @@ HEADERS += \ jskitmanager.h \ appinfo.h \ jskitobjects.h \ - packer.h + packer.h \ + bankmanager.h OTHER_FILES += \ ../log4qt-debug.conf \ diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 8a00373..27bb870 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -11,6 +11,7 @@ Manager::Manager(Settings *settings, QObject *parent) : watch(new WatchConnector(this)), dbus(new DBusConnector(this)), apps(new AppManager(this)), + bank(new BankManager(watch, apps, this)), voice(new VoiceCallManager(settings, this)), notifications(new NotificationManager(settings, this)), music(new MusicManager(watch, this)), @@ -498,3 +499,14 @@ void PebbledProxy::SendAppConfigurationData(const QString &uuid, const QString & manager()->js->handleWebviewClosed(data); } + +void PebbledProxy::UnloadApp(uint slot) +{ + Q_ASSERT(calledFromDBus()); + const QDBusMessage msg = message(); + + if (!manager()->bank->unloadApp(slot)) { + sendErrorReply(msg.interface() + ".Error.CannotUnload", + "Cannot unload application"); + } +} diff --git a/daemon/manager.h b/daemon/manager.h index 9347289..0e55190 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -10,6 +10,7 @@ #include "appmsgmanager.h" #include "jskitmanager.h" #include "appmanager.h" +#include "bankmanager.h" #include "settings.h" #include @@ -45,6 +46,7 @@ class Manager : public QObject, protected QDBusContext WatchConnector *watch; DBusConnector *dbus; AppManager *apps; + BankManager *bank; VoiceCallManager *voice; NotificationManager *notifications; MusicManager *music; @@ -143,6 +145,8 @@ public slots: QString StartAppConfiguration(const QString &uuid); void SendAppConfigurationData(const QString &uuid, const QString &data); + void UnloadApp(uint slot); + signals: void NameChanged(); void AddressChanged(); diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index baec52c..21f5ad5 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -451,93 +451,3 @@ void WatchConnector::endPhoneCall(uint cookie) { phoneControl(callEND, cookie, QStringList()); } - -void WatchConnector::getAppbankStatus(const std::function& callback) -{ - sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_STATUS), - [this, callback](const QByteArray &data) { - if (data.at(0) != appmgrGET_APPBANK_STATUS) { - return false; - } - logger()->debug() << "getAppbankStatus response" << data.toHex(); - - if (data.size() < 9) { - logger()->warn() << "invalid getAppbankStatus response"; - return true; - } - - Unpacker u(data); - - u.skip(sizeof(quint8)); - - unsigned int num_banks = u.read(); - unsigned int apps_installed = u.read(); - - logger()->debug() << num_banks << "/" << apps_installed; - - for (unsigned int i = 0; i < apps_installed; i++) { - unsigned int id = u.read(); - unsigned int index = u.read(); - QString name = u.readFixedString(32); - QString company = u.readFixedString(32); - unsigned int flags = u.read(); - unsigned short version = u.read(); - - logger()->debug() << id << index << name << company << flags << version; - - if (u.bad()) { - logger()->warn() << "short read"; - return true; - } - } - - logger()->debug() << "finished"; - - return true; - }); -} - -void WatchConnector::getAppbankUuids(const function &)>& callback) -{ - sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_UUIDS), - [this, callback](const QByteArray &data) { - if (data.at(0) != appmgrGET_APPBANK_UUIDS) { - return false; - } - logger()->debug() << "getAppbankUuids response" << data.toHex(); - - if (data.size() < 5) { - logger()->warn() << "invalid getAppbankUuids response"; - return true; - } - - Unpacker u(data); - - u.skip(sizeof(quint8)); - - unsigned int apps_installed = u.read(); - - logger()->debug() << apps_installed; - - QList uuids; - - for (unsigned int i = 0; i < apps_installed; i++) { - QUuid uuid = u.readUuid(); - - logger()->debug() << uuid.toString(); - - if (u.bad()) { - logger()->warn() << "short read"; - return true; - } - - uuids.push_back(uuid); - } - - logger()->debug() << "finished"; - - callback(uuids); - - return true; - }); -} diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 6c7fdd4..a5fe1ea 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -107,6 +107,7 @@ public: }; enum AppManager { appmgrGET_APPBANK_STATUS = 1, + appmgrREMOVE_APP = 2, appmgrGET_APPBANK_UUIDS = 5 }; enum AppMessage { @@ -179,9 +180,6 @@ public: static QString timeStamp(); static QString decodeEndpoint(uint val); - void getAppbankStatus(const std::function& callback); - void getAppbankUuids(const std::function &uuids)>& callback); - signals: void messageReceived(uint endpoint, const QByteArray &data); void nameChanged(); diff --git a/org.pebbled.Watch.xml b/org.pebbled.Watch.xml index 87a22b9..ce71248 100644 --- a/org.pebbled.Watch.xml +++ b/org.pebbled.Watch.xml @@ -49,5 +49,10 @@ + + + + + -- cgit v1.2.3 From a60c1cb3c4afd6dfd305115ec4c52e993172fa7d Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 7 Dec 2014 23:39:29 +0100 Subject: ability to upload apps --- daemon/bankmanager.cpp | 134 +++++++++++++++++++++--- daemon/bankmanager.h | 14 ++- daemon/daemon.pro | 8 +- daemon/manager.cpp | 14 ++- daemon/manager.h | 3 + daemon/stm32crc.cpp | 119 +++++++++++++++++++++ daemon/stm32crc.h | 24 +++++ daemon/uploadmanager.cpp | 266 +++++++++++++++++++++++++++++++++++++++++++++++ daemon/uploadmanager.h | 65 ++++++++++++ daemon/watchconnector.h | 16 ++- org.pebbled.Watch.xml | 4 + 11 files changed, 643 insertions(+), 24 deletions(-) create mode 100644 daemon/stm32crc.cpp create mode 100644 daemon/stm32crc.h create mode 100644 daemon/uploadmanager.cpp create mode 100644 daemon/uploadmanager.h (limited to 'daemon/watchconnector.h') diff --git a/daemon/bankmanager.cpp b/daemon/bankmanager.cpp index 194ec77..fe5dc21 100644 --- a/daemon/bankmanager.cpp +++ b/daemon/bankmanager.cpp @@ -1,11 +1,19 @@ +#include +#include #include "unpacker.h" #include "packer.h" #include "bankmanager.h" -BankManager::BankManager(WatchConnector *watch, AppManager *apps, QObject *parent) : - QObject(parent), watch(watch), apps(apps) +BankManager::BankManager(WatchConnector *watch, UploadManager *upload, AppManager *apps, QObject *parent) : + QObject(parent), watch(watch), upload(upload), apps(apps), _refresh(new QTimer(this)) { - connect(watch, &WatchConnector::connectedChanged, this, &BankManager::handleWatchConnected); + connect(watch, &WatchConnector::connectedChanged, + this, &BankManager::handleWatchConnected); + + _refresh->setInterval(0); + _refresh->setSingleShot(true); + connect(_refresh, &QTimer::timeout, + this, &BankManager::refresh); } int BankManager::numSlots() const @@ -32,9 +40,82 @@ bool BankManager::uploadApp(const QUuid &uuid, int slot) return false; } - // TODO + QDir appDir(info.path()); + + logger()->debug() << "about to install app from" << appDir.absolutePath() << "into slot" << slot; + + QFile *binaryFile = new QFile(appDir.absoluteFilePath("pebble-app.bin"), this); + if (!binaryFile->open(QIODevice::ReadOnly)) { + logger()->warn() << "failed to open" << binaryFile->fileName() << ":" << binaryFile->errorString(); + delete binaryFile; + return false; + } + + logger()->debug() << "binary file size is" << binaryFile->size(); + + QFile *resourceFile = 0; + if (appDir.exists("app_resources.pbpack")) { + resourceFile = new QFile(appDir.absoluteFilePath("app_resources.pbpack"), this); + if (!resourceFile->open(QIODevice::ReadOnly)) { + logger()->warn() << "failed to open" << resourceFile->fileName() << ":" << resourceFile->errorString(); + delete resourceFile; + return false; + } + } + + // Mark the slot as used, but without any app, just in case. + _slots[slot].used = true; + _slots[slot].name.clear(); + _slots[slot].uuid = QUuid(); + + upload->upload(WatchConnector::uploadBINARY, slot, binaryFile, -1, + [this, binaryFile, resourceFile, slot]() { + logger()->debug() << "app binary upload succesful"; + delete binaryFile; + + // Proceed to upload the resource file + if (resourceFile) { + upload->upload(WatchConnector::uploadRESOURCES, slot, resourceFile, -1, + [this, resourceFile, slot]() { + logger()->debug() << "app resources upload succesful"; + delete resourceFile; + + // Upload succesful + // Tell the watch to reload the slot + refreshWatchApp(slot, [this]() { + logger()->debug() << "app refresh succesful"; + _refresh->start(); + }, [this](int code) { + logger()->warn() << "app refresh failed" << code; + _refresh->start(); + }); + }, [this, resourceFile](int code) { + logger()->warn() << "app resources upload failed" << code; + delete resourceFile; + + _refresh->start(); + }); + + } else { + // No resource file + // Tell the watch to reload the slot + refreshWatchApp(slot, [this]() { + logger()->debug() << "app refresh succesful"; + _refresh->start(); + }, [this](int code) { + logger()->warn() << "app refresh failed" << code; + _refresh->start(); + }); + } + }, [this, binaryFile, resourceFile](int code) { + logger()->warn() << "app binary upload failed" << code; + delete binaryFile; + delete resourceFile; + + _refresh->start(); + }); - return false; + return true; } bool BankManager::unloadApp(int slot) @@ -76,7 +157,7 @@ bool BankManager::unloadApp(int slot) break; } - QMetaObject::invokeMethod(this, "refresh", Qt::QueuedConnection); + _refresh->start(); return true; }); @@ -169,24 +250,43 @@ int BankManager::findUnusedSlot() const return -1; } +void BankManager::refreshWatchApp(int slot, std::function successCallback, std::function errorCallback) +{ + QByteArray msg; + Packer p(&msg); + p.write(WatchConnector::appmgrREFRESH_APP); + p.write(slot); + + watch->sendMessage(WatchConnector::watchAPP_MANAGER, msg, + [this, successCallback, errorCallback](const QByteArray &data) { + Unpacker u(data); + if (u.read() != WatchConnector::appmgrREFRESH_APP) { + return false; + } + int code = u.read(); + if (code == Success) { + if (successCallback) { + successCallback(); + } + } else { + if (errorCallback) { + errorCallback(code); + } + } + + return true; + }); +} + void BankManager::handleWatchConnected() { if (watch->isConnected()) { - refresh(); + _refresh->start(); } } #if 0 - -void WatchConnector::getAppbankStatus(const std::function& callback) -{ - sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_STATUS), - [this, callback](const QByteArray &data) { - - }); -} - -void WatchConnector::getAppbankUuids(const function &)>& callback) +void BankManager::getAppbankUuids(const function &)>& callback) { sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_UUIDS), [this, callback](const QByteArray &data) { diff --git a/daemon/bankmanager.h b/daemon/bankmanager.h index 28729b9..6abedc8 100644 --- a/daemon/bankmanager.h +++ b/daemon/bankmanager.h @@ -2,6 +2,7 @@ #define BANKMANAGER_H #include "watchconnector.h" +#include "uploadmanager.h" #include "appmanager.h" class BankManager : public QObject @@ -10,11 +11,10 @@ class BankManager : public QObject LOG4QT_DECLARE_QCLASS_LOGGER public: - explicit BankManager(WatchConnector *watch, AppManager *apps, QObject *parent = 0); + explicit BankManager(WatchConnector *watch, UploadManager *upload, AppManager *apps, QObject *parent = 0); int numSlots() const; - signals: void slotsChanged(); @@ -26,6 +26,7 @@ public slots: private: int findUnusedSlot() const; + void refreshWatchApp(int slot, std::function successCallback, std::function errorCallback); private slots: @@ -33,8 +34,16 @@ private slots: private: WatchConnector *watch; + UploadManager *upload; AppManager *apps; + enum ResultCodes { + Success = 1, + BankInUse = 2, + InvalidCommand = 3, + GeneralFailure = 4 + }; + struct SlotInfo { bool used; quint32 id; @@ -46,6 +55,7 @@ private: }; QVector _slots; + QTimer *_refresh; }; #endif // BANKMANAGER_H diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 21ebffa..6eea288 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -29,7 +29,9 @@ SOURCES += \ appinfo.cpp \ jskitobjects.cpp \ packer.cpp \ - bankmanager.cpp + bankmanager.cpp \ + uploadmanager.cpp \ + stm32crc.cpp HEADERS += \ manager.h \ @@ -48,7 +50,9 @@ HEADERS += \ appinfo.h \ jskitobjects.h \ packer.h \ - bankmanager.h + bankmanager.h \ + uploadmanager.h \ + stm32crc.h OTHER_FILES += \ ../log4qt-debug.conf \ diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 27bb870..136d7f3 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -10,8 +10,9 @@ Manager::Manager(Settings *settings, QObject *parent) : proxy(new PebbledProxy(this)), watch(new WatchConnector(this)), dbus(new DBusConnector(this)), + upload(new UploadManager(watch, this)), apps(new AppManager(this)), - bank(new BankManager(watch, apps, this)), + bank(new BankManager(watch, upload, apps, this)), voice(new VoiceCallManager(settings, this)), notifications(new NotificationManager(settings, this)), music(new MusicManager(watch, this)), @@ -510,3 +511,14 @@ void PebbledProxy::UnloadApp(uint slot) "Cannot unload application"); } } + +void PebbledProxy::UploadApp(const QString &uuid, uint slot) +{ + Q_ASSERT(calledFromDBus()); + const QDBusMessage msg = message(); + + if (!manager()->bank->uploadApp(QUuid(uuid), slot)) { + sendErrorReply(msg.interface() + ".Error.CannotUpload", + "Cannot upload application"); + } +} diff --git a/daemon/manager.h b/daemon/manager.h index 0e55190..f27da98 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -3,6 +3,7 @@ #include "watchconnector.h" #include "dbusconnector.h" +#include "uploadmanager.h" #include "voicecallmanager.h" #include "notificationmanager.h" #include "musicmanager.h" @@ -45,6 +46,7 @@ class Manager : public QObject, protected QDBusContext WatchConnector *watch; DBusConnector *dbus; + UploadManager *upload; AppManager *apps; BankManager *bank; VoiceCallManager *voice; @@ -146,6 +148,7 @@ public slots: void SendAppConfigurationData(const QString &uuid, const QString &data); void UnloadApp(uint slot); + void UploadApp(const QString &uuid, uint slot); signals: void NameChanged(); diff --git a/daemon/stm32crc.cpp b/daemon/stm32crc.cpp new file mode 100644 index 0000000..dd09f38 --- /dev/null +++ b/daemon/stm32crc.cpp @@ -0,0 +1,119 @@ +#include "stm32crc.h" + +/** Precomputed CRC polynomial + * Generated by pycrc v0.8.2, http://www.tty1.net/pycrc/ + * using the configuration: + * Width = 32 + * Poly = 0x04c11db7 + * XorIn = 0xffffffff + * ReflectIn = False + * XorOut = 0xffffffff + * ReflectOut = False + * Algorithm = table-driven + * Modified to use STM32-like word size + *****************************************************************************/ +static const quint32 crc_table[256] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, + 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, + 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, + 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, + 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, + 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, + 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, + 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, + 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, + 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, + 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, + 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, + 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, + 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, + 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, + 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, + 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, + 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, + 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, + 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, + 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, + 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, + 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, + 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, + 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, + 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, + 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, + 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, + 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, + 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, + 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, + 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, + 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, + 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, + 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +Stm32Crc::Stm32Crc() +{ + reset(); +} + +void Stm32Crc::reset() +{ + crc = 0xFFFFFFFFU; +} + +void Stm32Crc::addData(const char *data, int length) +{ + const int word_len = sizeof(quint32); + int i = 0; + + Q_ASSERT(length % word_len == 0); + Q_ASSERT(quintptr(data) % word_len == 0); + + for (; i < (length/word_len)*word_len; i+=word_len) { + const quint32 *word = reinterpret_cast(&data[i]); + + crc ^= *word; + crc = (crc << 8) ^ crc_table[(crc >> 24) & 0xFF]; + crc = (crc << 8) ^ crc_table[(crc >> 24) & 0xFF]; + crc = (crc << 8) ^ crc_table[(crc >> 24) & 0xFF]; + crc = (crc << 8) ^ crc_table[(crc >> 24) & 0xFF]; + } +} + +void Stm32Crc::addData(const QByteArray &data) +{ + addData(data.constData(), data.length()); +} + +quint32 Stm32Crc::result() const +{ + return crc; +} diff --git a/daemon/stm32crc.h b/daemon/stm32crc.h new file mode 100644 index 0000000..1361de3 --- /dev/null +++ b/daemon/stm32crc.h @@ -0,0 +1,24 @@ +#ifndef STM32CRC_H +#define STM32CRC_H + +#include + +class Stm32Crc +{ +public: + Stm32Crc(); + + void reset(); + + // Data size must be multiple of 4, data must be aligned to 4. + + void addData(const char *data, int length); + void addData(const QByteArray &data); + + quint32 result() const; + +private: + quint32 crc; +}; + +#endif // STM32CRC_H diff --git a/daemon/uploadmanager.cpp b/daemon/uploadmanager.cpp new file mode 100644 index 0000000..89d70f7 --- /dev/null +++ b/daemon/uploadmanager.cpp @@ -0,0 +1,266 @@ +#include "uploadmanager.h" +#include "unpacker.h" +#include "packer.h" +#include "stm32crc.h" + +static const int CHUNK_SIZE = 2000; +using std::function; + +UploadManager::UploadManager(WatchConnector *watch, QObject *parent) : + QObject(parent), watch(watch), _lastUploadId(0), _state(StateNotStarted) +{ + watch->setEndpointHandler(WatchConnector::watchPUTBYTES, + [this](const QByteArray &msg) { + if (_pending.empty()) { + logger()->warn() << "putbytes message, but queue is empty!"; + return false; + } + handleMessage(msg); + return true; + }); +} + +uint UploadManager::upload(WatchConnector::UploadType type, int index, QIODevice *device, int size, + function successCallback, function errorCallback) +{ + PendingUpload upload; + upload.id = ++_lastUploadId; + upload.type = type; + upload.index = index; + upload.device = device; + if (size < 0) { + upload.remaining = device->size(); + } else { + upload.remaining = size; + } + upload.successCallback = successCallback; + upload.errorCallback = errorCallback; + + if (upload.remaining <= 0) { + logger()->warn() << "upload is empty"; + if (errorCallback) { + errorCallback(-1); + return -1; + } + } + + _pending.enqueue(upload); + + if (_pending.size() == 1) { + startNextUpload(); + } + + return upload.id; +} + +void UploadManager::cancel(uint id, int code) +{ + if (_pending.empty()) { + logger()->warn() << "cannot cancel, empty queue"; + return; + } + + if (id == _pending.head().id) { + PendingUpload upload = _pending.dequeue(); + logger()->debug() << "aborting current upload" << id << "(code:" << code << ")"; + + if (_state != StateNotStarted && _state != StateWaitForToken && _state != StateComplete) { + QByteArray msg; + Packer p(&msg); + p.write(WatchConnector::putbytesABORT); + p.write(_token); + + logger()->debug() << "sending abort for upload" << id; + + watch->sendMessage(WatchConnector::watchPUTBYTES, msg); + } + + _state = StateNotStarted; + _token = 0; + + if (upload.errorCallback) { + upload.errorCallback(code); + } + + if (!_pending.empty()) { + startNextUpload(); + } + } else { + for (int i = 1; i < _pending.size(); ++i) { + if (_pending[i].id == id) { + logger()->debug() << "cancelling upload" << id << "(code:" << code << ")"; + if (_pending[i].errorCallback) { + _pending[i].errorCallback(code); + } + _pending.removeAt(i); + return; + } + } + logger()->warn() << "cannot cancel, id" << id << "not found"; + } +} + +void UploadManager::startNextUpload() +{ + Q_ASSERT(!_pending.empty()); + Q_ASSERT(_state == StateNotStarted); + + PendingUpload &upload = _pending.head(); + QByteArray msg; + Packer p(&msg); + p.write(WatchConnector::putbytesINIT); + p.write(upload.remaining); + p.write(upload.type); + p.write(upload.index); + + _state = StateWaitForToken; + watch->sendMessage(WatchConnector::watchPUTBYTES, msg); +} + +void UploadManager::handleMessage(const QByteArray &msg) +{ + Q_ASSERT(!_pending.empty()); + PendingUpload &upload = _pending.head(); + + logger()->debug() << "get message" << msg.toHex(); + + Unpacker u(msg); + int status = u.read(); + + if (u.bad() || status != 1) { + logger()->warn() << "upload" << upload.id << "got error code=" << status; + cancel(upload.id, status); + return; + } + + quint32 recv_token = u.read(); + + if (u.bad()) { + logger()->warn() << "upload" << upload.id << ": could not read the token"; + cancel(upload.id, -1); + return; + } + + if (_state != StateNotStarted && _state != StateWaitForToken && _state != StateComplete) { + if (recv_token != _token) { + logger()->warn() << "upload" << upload.id << ": invalid token"; + cancel(upload.id, -1); + return; + } + } + + switch (_state) { + case StateNotStarted: + logger()->warn() << "got packet when upload is not started"; + break; + case StateWaitForToken: + logger()->debug() << "token received"; + _token = recv_token; + _state = StateInProgress; + + /* fallthrough */ + case StateInProgress: + logger()->debug() << "moving to the next chunk"; + if (upload.remaining > 0) { + if (!uploadNextChunk(upload)) { + cancel(upload.id, -1); + return; + } + } else { + logger()->debug() << "no additional chunks, commit"; + _state = StateCommit; + if (!commit(upload)) { + cancel(upload.id, -1); + return; + } + } + break; + case StateCommit: + logger()->debug() << "commited succesfully"; + _state = StateComplete; + if (!complete(upload)) { + cancel(upload.id, -1); + return; + } + break; + case StateComplete: + logger()->debug() << "upload" << upload.id << "succesful, invoking callback"; + if (upload.successCallback) { + upload.successCallback(); + } + _pending.dequeue(); + _token = 0; + _state = StateNotStarted; + if (!_pending.empty()) { + startNextUpload(); + } + break; + } +} + +bool UploadManager::uploadNextChunk(PendingUpload &upload) +{ + QByteArray chunk = upload.device->read(qMin(upload.remaining, CHUNK_SIZE)); + + if (upload.remaining < CHUNK_SIZE && chunk.size() < upload.remaining) { + // Short read! + logger()->warn() << "short read during upload" << upload.id; + return false; + } + + Q_ASSERT(!chunk.isEmpty()); + Q_ASSERT(_state = StateInProgress); + + QByteArray msg; + Packer p(&msg); + p.write(WatchConnector::putbytesSEND); + p.write(_token); + p.write(chunk.size()); + msg.append(chunk); + + logger()->debug() << "sending a chunk of" << chunk.size() << "bytes"; + + watch->sendMessage(WatchConnector::watchPUTBYTES, msg); + + upload.remaining -= chunk.size(); + upload.crc.addData(chunk); + + logger()->debug() << "remaining" << upload.remaining << "bytes"; + + return true; +} + +bool UploadManager::commit(PendingUpload &upload) +{ + Q_ASSERT(_state == StateCommit); + Q_ASSERT(upload.remaining == 0); + + QByteArray msg; + Packer p(&msg); + p.write(WatchConnector::putbytesCOMMIT); + p.write(_token); + p.write(upload.crc.result()); + + logger()->debug() << "commiting upload" << upload.id + << "with crc" << qPrintable(QString("0x%1").arg(upload.crc.result(), 0, 16)); + + watch->sendMessage(WatchConnector::watchPUTBYTES, msg); + + return true; +} + +bool UploadManager::complete(PendingUpload &upload) +{ + Q_ASSERT(_state == StateComplete); + + QByteArray msg; + Packer p(&msg); + p.write(WatchConnector::putbytesCOMPLETE); + p.write(_token); + + logger()->debug() << "completing upload" << upload.id; + + watch->sendMessage(WatchConnector::watchPUTBYTES, msg); + + return true; +} diff --git a/daemon/uploadmanager.h b/daemon/uploadmanager.h new file mode 100644 index 0000000..1d42237 --- /dev/null +++ b/daemon/uploadmanager.h @@ -0,0 +1,65 @@ +#ifndef UPLOADMANAGER_H +#define UPLOADMANAGER_H + +#include +#include +#include "watchconnector.h" +#include "stm32crc.h" + +class UploadManager : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + +public: + explicit UploadManager(WatchConnector *watch, QObject *parent = 0); + + typedef std::function Callback; + + uint upload(WatchConnector::UploadType type, int index, QIODevice *device, int size = -1, + std::function successCallback = std::function(), + std::function errorCallback = std::function()); + void cancel(uint id, int code = 0); + +signals: + +public slots: + + +private: + enum State { + StateNotStarted, + StateWaitForToken, + StateInProgress, + StateCommit, + StateComplete + }; + + struct PendingUpload { + uint id; + + WatchConnector::UploadType type; + int index; + QIODevice *device; + int remaining; + Stm32Crc crc; + + std::function successCallback; + std::function errorCallback; + }; + + void startNextUpload(); + void handleMessage(const QByteArray &msg); + bool uploadNextChunk(PendingUpload &upload); + bool commit(PendingUpload &upload); + bool complete(PendingUpload &upload); + +private: + WatchConnector *watch; + QQueue _pending; + uint _lastUploadId; + State _state; + quint32 _token; +}; + +#endif // UPLOADMANAGER_H diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index a5fe1ea..1aaf39d 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -108,6 +108,7 @@ public: enum AppManager { appmgrGET_APPBANK_STATUS = 1, appmgrREMOVE_APP = 2, + appmgrREFRESH_APP = 3, appmgrGET_APPBANK_UUIDS = 5 }; enum AppMessage { @@ -154,8 +155,19 @@ public: osLINUX = 4, osWINDOWS = 5 }; - enum { - DEFAULT_TIMEOUT_MSECS = 100 + enum UploadType { + uploadFIRMWARE = 1, + uploadRECOVERY = 2, + uploadSYS_RESOURCES = 3, + uploadRESOURCES = 4, + uploadBINARY = 5 + }; + enum PutBytesCommand { + putbytesINIT = 1, + putbytesSEND = 2, + putbytesCOMMIT = 3, + putbytesABORT = 4, + putbytesCOMPLETE = 5 }; typedef QMap Dict; diff --git a/org.pebbled.Watch.xml b/org.pebbled.Watch.xml index ce71248..2c5202f 100644 --- a/org.pebbled.Watch.xml +++ b/org.pebbled.Watch.xml @@ -54,5 +54,9 @@ + + + + -- cgit v1.2.3 From 4280a9bda38046f702a4151d7b831a3bf46ef169 Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 8 Dec 2014 00:16:06 +0100 Subject: add d-bus API to list slot contents and fix other API issues --- daemon/bankmanager.cpp | 14 ++++++++++++++ daemon/bankmanager.h | 4 +++- daemon/manager.cpp | 25 +++++++++++++++++++++++-- daemon/manager.h | 8 ++++++-- daemon/uploadmanager.cpp | 5 +++-- daemon/watchconnector.cpp | 16 +++++++++------- daemon/watchconnector.h | 1 - org.pebbled.Watch.xml | 6 ++++-- 8 files changed, 62 insertions(+), 17 deletions(-) (limited to 'daemon/watchconnector.h') diff --git a/daemon/bankmanager.cpp b/daemon/bankmanager.cpp index fe5dc21..8636e95 100644 --- a/daemon/bankmanager.cpp +++ b/daemon/bankmanager.cpp @@ -21,6 +21,16 @@ int BankManager::numSlots() const return _slots.size(); } +bool BankManager::isUsed(int slot) const +{ + return _slots.at(slot).used; +} + +QUuid BankManager::appAt(int slot) const +{ + return _slots.at(slot).uuid; +} + bool BankManager::uploadApp(const QUuid &uuid, int slot) { AppInfo info = apps->info(uuid); @@ -39,6 +49,10 @@ bool BankManager::uploadApp(const QUuid &uuid, int slot) logger()->warn() << "invalid slot index"; return false; } + if (_slots[slot].used) { + logger()->warn() << "slot in use"; + return false; + } QDir appDir(info.path()); diff --git a/daemon/bankmanager.h b/daemon/bankmanager.h index 6abedc8..871db6b 100644 --- a/daemon/bankmanager.h +++ b/daemon/bankmanager.h @@ -15,6 +15,9 @@ public: int numSlots() const; + bool isUsed(int slot) const; + QUuid appAt(int slot) const; + signals: void slotsChanged(); @@ -28,7 +31,6 @@ private: int findUnusedSlot() const; void refreshWatchApp(int slot, std::function successCallback, std::function errorCallback); - private slots: void handleWatchConnected(); diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 136d7f3..469e92b 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -70,6 +70,7 @@ Manager::Manager(Settings *settings, QObject *parent) : connect(dbus, &DBusConnector::pebbleChanged, proxy, &PebbledProxy::NameChanged); connect(dbus, &DBusConnector::pebbleChanged, proxy, &PebbledProxy::AddressChanged); connect(watch, &WatchConnector::connectedChanged, proxy, &PebbledProxy::ConnectedChanged); + connect(bank, &BankManager::slotsChanged, proxy, &PebbledProxy::AppSlotsChanged); QString currentProfile = getCurrentProfile(); defaultProfile = currentProfile.isEmpty() ? "ambience" : currentProfile; @@ -414,6 +415,26 @@ void Manager::onAppClosed(const QUuid &uuid) emit proxy->AppUuidChanged(); } +QStringList PebbledProxy::AppSlots() const +{ + const int num_slots = manager()->bank->numSlots(); + QStringList l; + l.reserve(num_slots); + + for (int i = 0; i < num_slots; ++i) { + if (manager()->bank->isUsed(i)) { + QUuid uuid = manager()->bank->appAt(i); + l.append(uuid.toString()); + } else { + l.append(QString()); + } + } + + Q_ASSERT(l.size() == num_slots); + + return l; +} + bool PebbledProxy::SendAppMessage(const QString &uuid, const QVariantMap &data) { Q_ASSERT(calledFromDBus()); @@ -501,7 +522,7 @@ void PebbledProxy::SendAppConfigurationData(const QString &uuid, const QString & manager()->js->handleWebviewClosed(data); } -void PebbledProxy::UnloadApp(uint slot) +void PebbledProxy::UnloadApp(int slot) { Q_ASSERT(calledFromDBus()); const QDBusMessage msg = message(); @@ -512,7 +533,7 @@ void PebbledProxy::UnloadApp(uint slot) } } -void PebbledProxy::UploadApp(const QString &uuid, uint slot) +void PebbledProxy::UploadApp(const QString &uuid, int slot) { Q_ASSERT(calledFromDBus()); const QDBusMessage msg = message(); diff --git a/daemon/manager.h b/daemon/manager.h index f27da98..0588705 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -122,6 +122,7 @@ class PebbledProxy : public QObject, protected QDBusContext Q_PROPERTY(QString Address READ Address NOTIFY AddressChanged) Q_PROPERTY(bool Connected READ Connected NOTIFY ConnectedChanged) Q_PROPERTY(QString AppUuid READ AppUuid NOTIFY AppUuidChanged) + Q_PROPERTY(QStringList AppSlots READ AppSlots NOTIFY AppSlotsChanged) inline Manager* manager() const { return static_cast(parent()); } inline QVariantMap pebble() const { return manager()->dbus->pebble(); } @@ -134,6 +135,8 @@ public: inline bool Connected() const { return manager()->watch->isConnected(); } inline QString AppUuid() const { return manager()->currentAppUuid.toString(); } + QStringList AppSlots() const; + public slots: inline void Disconnect() { manager()->watch->disconnect(); } inline void Reconnect() { manager()->watch->reconnect(); } @@ -147,14 +150,15 @@ public slots: QString StartAppConfiguration(const QString &uuid); void SendAppConfigurationData(const QString &uuid, const QString &data); - void UnloadApp(uint slot); - void UploadApp(const QString &uuid, uint slot); + void UnloadApp(int slot); + void UploadApp(const QString &uuid, int slot); signals: void NameChanged(); void AddressChanged(); void ConnectedChanged(); void AppUuidChanged(); + void AppSlotsChanged(); void AppOpened(const QString &uuid); void AppClosed(const QString &uuid); }; diff --git a/daemon/uploadmanager.cpp b/daemon/uploadmanager.cpp index 89d70f7..ccbf12a 100644 --- a/daemon/uploadmanager.cpp +++ b/daemon/uploadmanager.cpp @@ -122,8 +122,6 @@ void UploadManager::handleMessage(const QByteArray &msg) Q_ASSERT(!_pending.empty()); PendingUpload &upload = _pending.head(); - logger()->debug() << "get message" << msg.toHex(); - Unpacker u(msg); int status = u.read(); @@ -195,6 +193,9 @@ void UploadManager::handleMessage(const QByteArray &msg) startNextUpload(); } break; + default: + logger()->warn() << "received message in wrong state"; + break; } } diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index 21f5ad5..e66ec0f 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -5,6 +5,7 @@ #include "unpacker.h" static const int RECONNECT_TIMEOUT = 500; //ms +static const bool PROTOCOL_DEBUG = false; using std::function; @@ -135,7 +136,7 @@ bool WatchConnector::dispatchMessage(uint endpoint, const QByteArray &data) } logger()->info() << "message to endpoint" << decodeEndpoint(endpoint) << "was not dispatched"; - emit messageReceived(endpoint, data); + logger()->debug() << data.toHex(); return false; } @@ -182,6 +183,7 @@ void WatchConnector::onReadSocket() QByteArray data = socket->read(message_length); logger()->debug() << "received message of length" << message_length << "to endpoint" << decodeEndpoint(endpoint); + if (PROTOCOL_DEBUG) logger()->debug() << data.toHex(); dispatchMessage(endpoint, data); } @@ -225,7 +227,7 @@ void WatchConnector::onDisconnected() reconnectTimer.setInterval(reconnectTimer.interval() + RECONNECT_TIMEOUT); } reconnectTimer.start(); - logger()->debug() << "Will reconnect in" << reconnectTimer.interval() << "ms"; + logger()->debug() << "will reconnect in" << reconnectTimer.interval() << "ms"; } void WatchConnector::onError(QBluetoothSocket::SocketError error) @@ -233,7 +235,7 @@ void WatchConnector::onError(QBluetoothSocket::SocketError error) if (error == QBluetoothSocket::UnknownSocketError) { logger()->info() << error << socket->errorString(); } else { - logger()->error() << "Error connecting Pebble:" << error << socket->errorString(); + logger()->error() << "error connecting Pebble:" << error << socket->errorString(); } } @@ -241,11 +243,11 @@ void WatchConnector::sendData(const QByteArray &data) { writeData.append(data); if (socket == nullptr) { - logger()->debug() << "No socket - reconnecting"; + logger()->debug() << "no socket - reconnecting"; reconnect(); } else if (is_connected) { - logger()->debug() << "Writing" << data.length() << "bytes to socket"; - logger()->debug() << data.toHex(); + logger()->debug() << "writing" << data.length() << "bytes to socket"; + if (PROTOCOL_DEBUG) logger()->debug() << data.toHex(); socket->write(data); } } @@ -253,7 +255,7 @@ void WatchConnector::sendData(const QByteArray &data) void WatchConnector::onBytesWritten(qint64 bytes) { writeData.remove(0, bytes); - logger()->debug() << "Socket written" << bytes << "bytes," << writeData.length() << "left"; + logger()->debug() << "socket written" << bytes << "bytes," << writeData.length() << "left"; } void WatchConnector::sendMessage(uint endpoint, const QByteArray &data, const EndpointHandlerFunc &callback) diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 1aaf39d..2e89b8c 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -193,7 +193,6 @@ public: static QString decodeEndpoint(uint val); signals: - void messageReceived(uint endpoint, const QByteArray &data); void nameChanged(); void connectedChanged(); diff --git a/org.pebbled.Watch.xml b/org.pebbled.Watch.xml index 2c5202f..e076d6c 100644 --- a/org.pebbled.Watch.xml +++ b/org.pebbled.Watch.xml @@ -51,12 +51,14 @@ + + - + - + -- cgit v1.2.3 From 8cf0a301a6fba635a3e1bf66b23548fb23cbcad6 Mon Sep 17 00:00:00 2001 From: Javier Date: Thu, 11 Dec 2014 00:27:10 +0100 Subject: minor changes (some new protocol constants (2.8?)) --- daemon/dbusconnector.h | 1 - daemon/watchconnector.h | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'daemon/watchconnector.h') diff --git a/daemon/dbusconnector.h b/daemon/dbusconnector.h index d4c1bcb..c24bb9b 100644 --- a/daemon/dbusconnector.h +++ b/daemon/dbusconnector.h @@ -33,7 +33,6 @@ public slots: protected slots: void onServiceRegistered(const QString &); void onServiceUnregistered(const QString &); - }; #endif // DBUSCONNECTOR_H diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 2e89b8c..4194e9a 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -71,6 +71,8 @@ public: watchAPP_MANAGER = 6000, watchDATA_LOGGING = 6778, watchSCREENSHOT = 8000, + watchFILE_MANAGER = 8181, + watchCORE_DUMP = 9000, watchPUTBYTES = 48879 }; enum { @@ -160,7 +162,9 @@ public: uploadRECOVERY = 2, uploadSYS_RESOURCES = 3, uploadRESOURCES = 4, - uploadBINARY = 5 + uploadBINARY = 5, + uploadFILE = 6, + uploadWORKER = 7 }; enum PutBytesCommand { putbytesINIT = 1, -- cgit v1.2.3 From d0b0090e951668f9160632c5c30b9f1e0d0aa5a0 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 13 Dec 2014 19:27:07 +0100 Subject: do not listen for every d-bus owner change rather, just update the current mpris service when a signal comes in; seems much more efficient. --- daemon/musicmanager.cpp | 217 ++++++++++++++++++++++++++---------------------- daemon/musicmanager.h | 15 ++-- daemon/watchconnector.h | 3 +- 3 files changed, 126 insertions(+), 109 deletions(-) (limited to 'daemon/watchconnector.h') diff --git a/daemon/musicmanager.cpp b/daemon/musicmanager.cpp index 05e3727..d34ae5c 100644 --- a/daemon/musicmanager.cpp +++ b/daemon/musicmanager.cpp @@ -1,69 +1,143 @@ -#include +#include +#include #include "musicmanager.h" MusicManager::MusicManager(WatchConnector *watch, QObject *parent) - : QObject(parent), watch(watch) + : QObject(parent), watch(watch), _watcher(new QDBusServiceWatcher(this)) { QDBusConnection bus = QDBusConnection::sessionBus(); QDBusConnectionInterface *bus_iface = bus.interface(); - // Listen for MPRIS signals from every player - bus.connect("", "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "PropertiesChanged", - this, SLOT(handleMprisPropertiesChanged(QString,QMap,QStringList))); - - // Listen for D-Bus name registered signals to see if a MPRIS service comes up - connect(bus_iface, &QDBusConnectionInterface::serviceOwnerChanged, - this, &MusicManager::handleServiceOwnerChanged); + // This watcher will be used to find when the current MPRIS service dies + // (and thus we must clear the metadata) + _watcher->setConnection(bus); + connect(_watcher, &QDBusServiceWatcher::serviceOwnerChanged, + this, &MusicManager::handleMprisServiceOwnerChanged); - // But also try to find an already active MPRIS service + // Try to find an active MPRIS service to initially connect to const QStringList &services = bus_iface->registeredServiceNames(); foreach (QString service, services) { if (service.startsWith("org.mpris.MediaPlayer2.")) { switchToService(service); + fetchMetadataFromService(); + // The watch is not connected by this point, + // so we don't send the current metadata. break; } } - // Set up watch endpoint handler for music control + // Even if we didn't find any service, we still listen for metadataChanged signals + // from every MPRIS-compatible player + // If such a signal comes in, we will connect to the source service for that signal + bus.connect("", "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "PropertiesChanged", + this, SLOT(handleMprisPropertiesChanged(QString,QMap,QStringList))); + + // Now set up the Pebble endpoint handler for music control commands watch->setEndpointHandler(WatchConnector::watchMUSIC_CONTROL, [this](const QByteArray& data) { - musicControl(WatchConnector::MusicControl(data.at(0))); + handleMusicControl(WatchConnector::MusicControl(data.at(0))); return true; }); + + // If the watch disconnects, we will send the current metadata when it comes back. connect(watch, &WatchConnector::connectedChanged, this, &MusicManager::handleWatchConnected); } -void MusicManager::musicControl(WatchConnector::MusicControl operation) +void MusicManager::switchToService(const QString &service) +{ + if (_curService != service) { + logger()->debug() << "switching to mpris service" << service; + _curService = service; + + if (_curService.isEmpty()) { + _watcher->setWatchedServices(QStringList()); + } else { + _watcher->setWatchedServices(QStringList(_curService)); + } + } +} + +void MusicManager::fetchMetadataFromService() +{ + _curMetadata.clear(); + + if (!_curService.isEmpty()) { + QDBusMessage call = QDBusMessage::createMethodCall(_curService, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get"); + call << "org.mpris.MediaPlayer2.Player" << "Metadata"; + QDBusReply reply = QDBusConnection::sessionBus().call(call); + if (reply.isValid()) { + logger()->debug() << "got mpris metadata from service" << _curService; + _curMetadata = qdbus_cast(reply.value().variant().value()); + } else { + logger()->error() << reply.error().message(); + } + } +} + +void MusicManager::sendCurrentMprisMetadata() +{ + Q_ASSERT(watch->isConnected()); + + QString track = _curMetadata.value("xesam:title").toString().left(30); + QString album = _curMetadata.value("xesam:album").toString().left(30); + QString artist = _curMetadata.value("xesam:artist").toString().left(30); + + logger()->debug() << "sending mpris metadata:" << track << album << artist; + + watch->sendMusicNowPlaying(track, album, artist); +} + +void MusicManager::callMprisMethod(const QString &method) +{ + Q_ASSERT(!method.isEmpty()); + Q_ASSERT(!_curService.isEmpty()); + + logger()->debug() << _curService << "->" << method; + + QDBusConnection bus = QDBusConnection::sessionBus(); + QDBusMessage call = QDBusMessage::createMethodCall(_curService, + "/org/mpris/MediaPlayer2", + "org.mpris.MediaPlayer2.Player", + method); + + QDBusError err = bus.call(call); + + if (err.isValid()) { + logger()->error() << "while calling mpris method on" << _curService << ":" << err.message(); + } +} + +void MusicManager::handleMusicControl(WatchConnector::MusicControl operation) { logger()->debug() << "operation from watch:" << operation; if (_curService.isEmpty()) { - logger()->info() << "No mpris interface active"; + logger()->info() << "can't do any music operation, no mpris interface active"; return; } - QString method; - - switch(operation) { + switch (operation) { case WatchConnector::musicPLAY_PAUSE: - method = "PlayPause"; + callMprisMethod("PlayPause"); break; case WatchConnector::musicPAUSE: - method = "Pause"; + callMprisMethod("Pause"); break; case WatchConnector::musicPLAY: - method = "Play"; + callMprisMethod("Play"); break; case WatchConnector::musicNEXT: - method = "Next"; + callMprisMethod("Next"); break; case WatchConnector::musicPREVIOUS: - method = "Previous"; + callMprisMethod("Previous"); break; + case WatchConnector::musicVOLUME_UP: case WatchConnector::musicVOLUME_DOWN: { QDBusConnection bus = QDBusConnection::sessionBus(); - QDBusMessage call = QDBusMessage::createMethodCall(_curService, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get"); + QDBusMessage call = QDBusMessage::createMethodCall(_curService, "/org/mpris/MediaPlayer2", + "org.freedesktop.DBus.Properties", "Get"); call << "org.mpris.MediaPlayer2.Player" << "Volume"; QDBusReply volumeReply = bus.call(call); if (volumeReply.isValid()) { @@ -87,79 +161,28 @@ void MusicManager::musicControl(WatchConnector::MusicControl operation) logger()->error() << volumeReply.error().message(); } } - return; + break; + case WatchConnector::musicGET_NOW_PLAYING: - setMprisMetadata(_curMetadata); - return; + sendCurrentMprisMetadata(); + break; - case WatchConnector::musicSEND_NOW_PLAYING: default: logger()->warn() << "Operation" << operation << "not supported"; - return; - } - - if (method.isEmpty()) { - logger()->error() << "Requested unsupported operation" << operation; - return; - } - - logger()->debug() << operation << "->" << method; - - QDBusMessage call = QDBusMessage::createMethodCall(_curService, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player", method); - QDBusError err = QDBusConnection::sessionBus().call(call); - if (err.isValid()) { - logger()->error() << err.message(); - } -} - -void MusicManager::switchToService(const QString &service) -{ - if (_curService != service) { - logger()->debug() << "switching to mpris service" << service; - _curService = service; - } -} - -void MusicManager::setMprisMetadata(const QVariantMap &metadata) -{ - _curMetadata = metadata; - QString track = metadata.value("xesam:title").toString(); - QString album = metadata.value("xesam:album").toString(); - QString artist = metadata.value("xesam:artist").toString(); - - logger()->debug() << "new mpris metadata:" << track << album << artist; - - if (watch->isConnected()) { - watch->sendMusicNowPlaying(track, album, artist); - } -} - -void MusicManager::handleServiceRegistered(const QString &service) -{ - if (service.startsWith("org.mpris.MediaPlayer2.")) { - if (_curService.isEmpty()) { - switchToService(service); - } - } -} - -void MusicManager::handleServiceUnregistered(const QString &service) -{ - if (service == _curService) { - // Oops! Losing the current MPRIS service - // We must assume it's been closed and thus remove current metadata - setMprisMetadata(QVariantMap()); - switchToService(QString()); + break; } } -void MusicManager::handleServiceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) +void MusicManager::handleMprisServiceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) { Q_UNUSED(oldOwner); - if (newOwner.isEmpty()) { - handleServiceUnregistered(name); - } else { - handleServiceRegistered(name); + if (name == _curService && newOwner.isEmpty()) { + // Oops, current service is going away + switchToService(QString()); + _curMetadata.clear(); + if (watch->isConnected()) { + sendCurrentMprisMetadata(); + } } } @@ -172,32 +195,26 @@ void MusicManager::handleMprisPropertiesChanged(const QString &interface, const if (changed.contains("Metadata")) { QVariantMap metadata = qdbus_cast(changed.value("Metadata").value()); logger()->debug() << "received new metadata" << metadata; - setMprisMetadata(metadata); + _curMetadata = metadata; } if (changed.contains("PlaybackStatus")) { QString status = changed.value("PlaybackStatus").toString(); if (status == "Stopped") { - setMprisMetadata(QVariantMap()); + _curMetadata.clear(); } } + if (watch->isConnected()) { + sendCurrentMprisMetadata(); + } + switchToService(message().service()); } void MusicManager::handleWatchConnected() { if (watch->isConnected()) { - if (!_curService.isEmpty()) { - QDBusMessage call = QDBusMessage::createMethodCall(_curService, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get"); - call << "org.mpris.MediaPlayer2.Player" << "Metadata"; - QDBusReply metadata = QDBusConnection::sessionBus().call(call); - if (metadata.isValid()) { - setMprisMetadata(qdbus_cast(metadata.value().variant().value())); - } else { - logger()->error() << metadata.error().message(); - setMprisMetadata(QVariantMap()); - } - } + sendCurrentMprisMetadata(); } } diff --git a/daemon/musicmanager.h b/daemon/musicmanager.h index 88c46c3..89e5fd7 100644 --- a/daemon/musicmanager.h +++ b/daemon/musicmanager.h @@ -3,6 +3,7 @@ #include #include +#include #include "watchconnector.h" class MusicManager : public QObject, protected QDBusContext @@ -14,22 +15,22 @@ public: explicit MusicManager(WatchConnector *watch, QObject *parent = 0); private: - void musicControl(WatchConnector::MusicControl operation); void switchToService(const QString &service); - void setMprisMetadata(const QVariantMap &data); + void fetchMetadataFromService(); + void sendCurrentMprisMetadata(); + void callMprisMethod(const QString &method); private slots: - void handleServiceRegistered(const QString &service); - void handleServiceUnregistered(const QString &service); - void handleServiceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner); + void handleMusicControl(WatchConnector::MusicControl operation); + void handleMprisServiceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner); void handleMprisPropertiesChanged(const QString &interface, const QMap &changed, const QStringList &invalidated); void handleWatchConnected(); private: WatchConnector *watch; - - QVariantMap _curMetadata; + QDBusServiceWatcher *_watcher; QString _curService; + QVariantMap _curMetadata; }; #endif // MUSICMANAGER_H diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 4194e9a..6546302 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -94,8 +94,7 @@ public: musicPREVIOUS = 5, musicVOLUME_UP = 6, musicVOLUME_DOWN = 7, - musicGET_NOW_PLAYING = 8, - musicSEND_NOW_PLAYING = 9 + musicGET_NOW_PLAYING = 8 }; enum SystemMessage { systemFIRMWARE_AVAILABLE = 0, -- cgit v1.2.3 From ab362bf43227552bab209fea792d60d370ce0260 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 14 Dec 2014 06:03:21 +0100 Subject: implement account and device tokens --- daemon/jskitmanager.cpp | 4 +-- daemon/jskitmanager.h | 5 +++- daemon/jskitobjects.cpp | 39 +++++++++++++++++++++++++++++ daemon/jskitobjects.h | 3 +++ daemon/manager.cpp | 2 +- daemon/settings.h | 29 ++++++++++----------- daemon/watchconnector.cpp | 64 +++++++++++++++++++++++++++++++++++++++++------ daemon/watchconnector.h | 2 ++ 8 files changed, 123 insertions(+), 25 deletions(-) (limited to 'daemon/watchconnector.h') diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index c73d32e..6d40970 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -4,8 +4,8 @@ #include "jskitmanager.h" #include "jskitobjects.h" -JSKitManager::JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *parent) : - QObject(parent), _apps(apps), _appmsg(appmsg), _engine(0) +JSKitManager::JSKitManager(WatchConnector *watch, AppManager *apps, AppMsgManager *appmsg, Settings *settings, QObject *parent) : + QObject(parent), _watch(watch), _apps(apps), _appmsg(appmsg), _settings(settings), _engine(0) { connect(_appmsg, &AppMsgManager::appStarted, this, &JSKitManager::handleAppStarted); connect(_appmsg, &AppMsgManager::appStopped, this, &JSKitManager::handleAppStopped); diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index 7ffc0ad..871ab8e 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -4,6 +4,7 @@ #include #include "appmanager.h" #include "appmsgmanager.h" +#include "settings.h" class JSKitPebble; class JSKitConsole; @@ -16,7 +17,7 @@ class JSKitManager : public QObject LOG4QT_DECLARE_QCLASS_LOGGER public: - explicit JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *parent = 0); + explicit JSKitManager(WatchConnector *watch, AppManager *apps, AppMsgManager *appmsg, Settings *settings, QObject *parent = 0); ~JSKitManager(); QJSEngine * engine(); @@ -44,8 +45,10 @@ private: private: friend class JSKitPebble; + WatchConnector *_watch; AppManager *_apps; AppMsgManager *_appmsg; + Settings *_settings; AppInfo _curApp; QJSEngine *_engine; QPointer _jspebble; diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index 5f5acaf..fe924e8 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -4,9 +4,12 @@ #include #include #include +#include #include #include "jskitobjects.h" +static const char *token_salt = "0feeb7416d3c4546a19b04bccd8419b1"; + JSKitPebble::JSKitPebble(const AppInfo &info, JSKitManager *mgr) : QObject(mgr), _appInfo(info), _mgr(mgr) { @@ -88,6 +91,42 @@ void JSKitPebble::openURL(const QUrl &url) emit _mgr->appOpenUrl(url); } +QString JSKitPebble::getAccountToken() const +{ + // We do not have any account system, so we just fake something up. + QCryptographicHash hasher(QCryptographicHash::Md5); + + hasher.addData(token_salt, strlen(token_salt)); + hasher.addData(_appInfo.uuid().toByteArray()); + + QString token = _mgr->_settings->property("accountToken").toString(); + if (token.isEmpty()) { + token = QUuid::createUuid().toString(); + logger()->debug() << "created new account token" << token; + _mgr->_settings->setProperty("accountToken", token); + } + hasher.addData(token.toLatin1()); + + QString hash = hasher.result().toHex(); + logger()->debug() << "returning account token" << hash; + + return hash; +} + +QString JSKitPebble::getWatchToken() const +{ + QCryptographicHash hasher(QCryptographicHash::Md5); + + hasher.addData(token_salt, strlen(token_salt)); + hasher.addData(_appInfo.uuid().toByteArray()); + hasher.addData(_mgr->_watch->serialNumber().toLatin1()); + + QString hash = hasher.result().toHex(); + logger()->debug() << "returning watch token" << hash; + + return hash; +} + QJSValue JSKitPebble::createXMLHttpRequest() { JSKitXMLHttpRequest *xhr = new JSKitXMLHttpRequest(_mgr, 0); diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 23dfabe..532ce1b 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -25,6 +25,9 @@ public: Q_INVOKABLE void openURL(const QUrl &url); + Q_INVOKABLE QString getAccountToken() const; + Q_INVOKABLE QString getWatchToken() const; + Q_INVOKABLE QJSValue createXMLHttpRequest(); void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList()); diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 212a1d7..32b313f 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -18,7 +18,7 @@ Manager::Manager(Settings *settings, QObject *parent) : music(new MusicManager(watch, this)), datalog(new DataLogManager(watch, this)), appmsg(new AppMsgManager(apps, watch, this)), - js(new JSKitManager(apps, appmsg, this)), + js(new JSKitManager(watch, apps, appmsg, settings, this)), notification(MNotification::DeviceEvent) { connect(settings, SIGNAL(valueChanged(QString)), SLOT(onSettingChanged(const QString&))); diff --git a/daemon/settings.h b/daemon/settings.h index d6db9b6..90e25e2 100644 --- a/daemon/settings.h +++ b/daemon/settings.h @@ -18,6 +18,8 @@ class Settings : public MDConfGroup Q_PROPERTY(bool notificationsFacebook MEMBER notificationsFacebook NOTIFY notificationsFacebookChanged) Q_PROPERTY(bool notificationsOther MEMBER notificationsOther NOTIFY notificationsOtherChanged) Q_PROPERTY(bool notificationsAll MEMBER notificationsAll NOTIFY notificationsAllChanged) + Q_PROPERTY(QString accountToken MEMBER accountToken NOTIFY accountTokenChanged) + bool silentWhenConnected; bool transliterateMessage; bool incomingCallNotification; @@ -29,6 +31,7 @@ class Settings : public MDConfGroup bool notificationsFacebook; bool notificationsOther; bool notificationsAll; + QString accountToken; public: explicit Settings(QObject *parent = 0) : @@ -36,20 +39,18 @@ public: { resolveMetaObject(); } signals: - void silentWhenConnectedChanged(bool); - void transliterateMessageChanged(bool); - void incomingCallNotificationChanged(bool); - void notificationsCommhistorydChanged(bool); - void notificationsMissedCallChanged(bool); - void notificationsEmailsChanged(bool); - void notificationsMitakuuluuChanged(bool); - void notificationsTwitterChanged(bool); - void notificationsFacebookChanged(bool); - void notificationsOtherChanged(bool); - void notificationsAllChanged(bool); - -public slots: - + void silentWhenConnectedChanged(); + void transliterateMessageChanged(); + void incomingCallNotificationChanged(); + void notificationsCommhistorydChanged(); + void notificationsMissedCallChanged(); + void notificationsEmailsChanged(); + void notificationsMitakuuluuChanged(); + void notificationsTwitterChanged(); + void notificationsFacebookChanged(); + void notificationsOtherChanged(); + void notificationsAllChanged(); + void accountTokenChanged(); }; #endif // SETTINGS_H diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index 22f4a8a..27a5511 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -1,19 +1,60 @@ #include #include -#include "watchconnector.h" #include "unpacker.h" +#include "watchconnector.h" static const int RECONNECT_TIMEOUT = 500; //ms static const bool PROTOCOL_DEBUG = false; -using std::function; - WatchConnector::WatchConnector(QObject *parent) : QObject(parent), socket(nullptr), is_connected(false) { reconnectTimer.setSingleShot(true); connect(&reconnectTimer, SIGNAL(timeout()), SLOT(reconnect())); + + setEndpointHandler(watchVERSION, [this](const QByteArray &data) { + Unpacker u(data); + + u.skip(1); + + quint32 version = u.read(); + QString version_string = u.readFixedString(32); + QString commit = u.readFixedString(8); + bool is_recovery = u.read(); + quint8 hw_platform = u.read(); + quint8 metadata_version = u.read(); + + quint32 safe_version = u.read(); + QString safe_version_string = u.readFixedString(32); + QString safe_commit = u.readFixedString(8); + bool safe_is_recovery = u.read(); + quint8 safe_hw_platform = u.read(); + quint8 safe_metadata_version = u.read(); + + quint32 bootLoaderTimestamp = u.read(); + QString hardwareRevision = u.readFixedString(9); + QString serialNumber = u.readFixedString(12); + QByteArray address = u.readBytes(6); + + if (u.bad()) { + logger()->warn() << "short read while reading firmware version"; + } + + logger()->debug() << "got version information" + << version << version_string << commit + << is_recovery << hw_platform << metadata_version; + logger()->debug() << "recovery version information" + << safe_version << safe_version_string << safe_commit + << safe_is_recovery << safe_hw_platform << safe_metadata_version; + logger()->debug() << "hardware information" << bootLoaderTimestamp << hardwareRevision; + logger()->debug() << "serial number" << serialNumber.left(3) << "..."; + logger()->debug() << "bt address" << address.toHex(); + + this->_serialNumber = serialNumber; + + return true; + }); } WatchConnector::~WatchConnector() @@ -46,11 +87,11 @@ void WatchConnector::reconnect() void WatchConnector::disconnect() { - logger()->debug() << __FUNCTION__; + logger()->debug() << "disconnecting"; socket->close(); socket->deleteLater(); reconnectTimer.stop(); - logger()->debug() << "Stopped reconnect timer"; + logger()->debug() << "stopped reconnect timer"; } void WatchConnector::handleWatch(const QString &name, const QString &address) @@ -67,6 +108,11 @@ void WatchConnector::handleWatch(const QString &name, const QString &address) _last_address = address; if (emit_name) emit nameChanged(); + if (emit_name) { + // If we've changed names, don't reuse cached serial number! + _serialNumber.clear(); + } + logger()->debug() << "Creating socket"; #if QT_VERSION < QT_VERSION_CHECK(5, 2, 0) socket = new QBluetoothSocket(QBluetoothSocket::RfcommSocket); @@ -198,11 +244,15 @@ void WatchConnector::onConnected() is_connected = true; reconnectTimer.stop(); reconnectTimer.setInterval(0); - if (not was_connected) { - if (not writeData.isEmpty()) { + if (!was_connected) { + if (!writeData.isEmpty()) { logger()->info() << "Found" << writeData.length() << "bytes in write buffer - resending"; sendData(writeData); } + if (_serialNumber.isEmpty()) { + // Ask for version information from the watch + sendMessage(watchVERSION, QByteArray(1, 0)); + } emit connectedChanged(); } } diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 6546302..45fd3c7 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -188,6 +188,7 @@ public: inline bool isConnected() const { return is_connected; } inline QString name() const { return socket != nullptr ? socket->peerName() : ""; } + inline QString serialNumber() const { return _serialNumber; } void setEndpointHandler(uint endpoint, const EndpointHandlerFunc &func); void clearEndpointHandler(uint endpoint); @@ -245,6 +246,7 @@ private: QTimer reconnectTimer; QString _last_name; QString _last_address; + QString _serialNumber; }; #endif // WATCHCONNECTOR_H -- cgit v1.2.3