summaryrefslogtreecommitdiff
path: root/daemon/appmsgmanager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'daemon/appmsgmanager.cpp')
-rw-r--r--daemon/appmsgmanager.cpp389
1 files changed, 389 insertions, 0 deletions
diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp
new file mode 100644
index 0000000..1a4a424
--- /dev/null
+++ b/daemon/appmsgmanager.cpp
@@ -0,0 +1,389 @@
+#include <QTimer>
+
+#include "appmsgmanager.h"
+#include "unpacker.h"
+#include "packer.h"
+
+// TODO D-Bus server for non JS kit apps!!!!
+
+AppMsgManager::AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *parent)
+ : QObject(parent), l(metaObject()->className()), apps(apps),
+ watch(watch), _lastTransactionId(0), _timeout(new QTimer(this))
+{
+ connect(watch, &WatchConnector::connectedChanged,
+ this, &AppMsgManager::handleWatchConnectedChanged);
+
+ _timeout->setSingleShot(true);
+ _timeout->setInterval(3000);
+ connect(_timeout, &QTimer::timeout,
+ this, &AppMsgManager::handleTimeout);
+
+ watch->setEndpointHandler(WatchConnector::watchLAUNCHER,
+ [this](const QByteArray &data) {
+ switch (data.at(0)) {
+ case WatchConnector::appmsgPUSH:
+ handleLauncherPushMessage(data);
+ break;
+ case WatchConnector::appmsgACK:
+ case WatchConnector::appmsgNACK:
+ // TODO we ignore those for now.
+ break;
+ }
+
+ return true;
+ });
+
+ watch->setEndpointHandler(WatchConnector::watchAPPLICATION_MESSAGE,
+ [this](const QByteArray &data) {
+ switch (data.at(0)) {
+ case WatchConnector::appmsgPUSH:
+ handlePushMessage(data);
+ break;
+ case WatchConnector::appmsgACK:
+ handleAckMessage(data, true);
+ break;
+ case WatchConnector::appmsgNACK:
+ handleAckMessage(data, false);
+ break;
+ default:
+ qCWarning(l) << "Unknown application message type:" << int(data.at(0));
+ break;
+ }
+
+ return true;
+ });
+}
+
+void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data, const std::function<void ()> &ackCallback, const std::function<void ()> &nackCallback)
+{
+ PendingTransaction trans;
+ trans.uuid = uuid;
+ trans.transactionId = ++_lastTransactionId;
+ trans.dict = mapAppKeys(uuid, data);
+ trans.ackCallback = ackCallback;
+ trans.nackCallback = nackCallback;
+
+ qCDebug(l) << "Queueing appmsg" << trans.transactionId << "to" << trans.uuid
+ << "with dict" << trans.dict;
+
+ _pending.enqueue(trans);
+ if (_pending.size() == 1) {
+ // This is the only transaction on the queue
+ // Therefore, we were idle before: we can submit this transaction right now.
+ transmitNextPendingTransaction();
+ }
+}
+
+void AppMsgManager::setMessageHandler(const QUuid &uuid, MessageHandlerFunc func)
+{
+ _handlers.insert(uuid, func);
+}
+
+void AppMsgManager::clearMessageHandler(const QUuid &uuid)
+{
+ _handlers.remove(uuid);
+}
+
+uint AppMsgManager::lastTransactionId() const
+{
+ return _lastTransactionId;
+}
+
+uint AppMsgManager::nextTransactionId() const
+{
+ return _lastTransactionId + 1;
+}
+
+void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data)
+{
+ std::function<void()> nullCallback;
+ send(uuid, data, nullCallback, nullCallback);
+}
+
+void AppMsgManager::launchApp(const QUuid &uuid)
+{
+ WatchConnector::Dict dict;
+ dict.insert(1, WatchConnector::launcherSTARTED);
+
+ qCDebug(l) << "Sending message to launcher" << uuid << dict;
+
+ QByteArray msg = buildPushMessage(++_lastTransactionId, uuid, dict);
+ watch->sendMessage(WatchConnector::watchLAUNCHER, msg);
+}
+
+void AppMsgManager::closeApp(const QUuid &uuid)
+{
+ WatchConnector::Dict dict;
+ dict.insert(1, WatchConnector::launcherSTOPPED);
+
+ qCDebug(l) << "Sending message to launcher" << uuid << dict;
+
+ QByteArray msg = buildPushMessage(++_lastTransactionId, uuid, dict);
+ watch->sendMessage(WatchConnector::watchLAUNCHER, msg);
+}
+
+WatchConnector::Dict AppMsgManager::mapAppKeys(const QUuid &uuid, const QVariantMap &data)
+{
+ AppInfo info = apps->info(uuid);
+ if (info.uuid() != uuid) {
+ qCWarning(l) << "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 {
+ qCWarning(l) << "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) {
+ qCWarning(l) << "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 {
+ qCWarning(l) << "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, quint8 *transaction, QUuid *uuid, WatchConnector::Dict *dict)
+{
+ Unpacker u(msg);
+ quint8 code = u.read<quint8>();
+ Q_UNUSED(code);
+ Q_ASSERT(code == WatchConnector::appmsgPUSH);
+
+ *transaction = u.read<quint8>();
+ *uuid = u.readUuid();
+ *dict = u.readDict();
+
+ if (u.bad()) {
+ return false;
+ }
+
+ return true;
+}
+
+QByteArray AppMsgManager::buildPushMessage(quint8 transaction, const QUuid &uuid, const WatchConnector::Dict &dict)
+{
+ QByteArray ba;
+ Packer p(&ba);
+ p.write<quint8>(WatchConnector::appmsgPUSH);
+ p.write<quint8>(transaction);
+ p.writeUuid(uuid);
+ p.writeDict(dict);
+
+ return ba;
+}
+
+QByteArray AppMsgManager::buildAckMessage(quint8 transaction)
+{
+ QByteArray ba(2, Qt::Uninitialized);
+ ba[0] = WatchConnector::appmsgACK;
+ ba[1] = transaction;
+ return ba;
+}
+
+QByteArray AppMsgManager::buildNackMessage(quint8 transaction)
+{
+ QByteArray ba(2, Qt::Uninitialized);
+ ba[0] = WatchConnector::appmsgNACK;
+ ba[1] = transaction;
+ return ba;
+}
+
+void AppMsgManager::handleLauncherPushMessage(const QByteArray &data)
+{
+ quint8 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
+ qCWarning(l) << "Failed to parser LAUNCHER PUSH message";
+ return;
+ }
+ if (!dict.contains(1)) {
+ qCWarning(l) << "LAUNCHER message has no item in dict";
+ return;
+ }
+
+ switch (dict.value(1).toInt()) {
+ case WatchConnector::launcherSTARTED:
+ qCDebug(l) << "App starting in watch:" << uuid;
+ this->watch->sendMessage(WatchConnector::watchLAUNCHER,
+ buildAckMessage(transaction));
+ emit appStarted(uuid);
+ break;
+ case WatchConnector::launcherSTOPPED:
+ qCDebug(l) << "App stopping in watch:" << uuid;
+ this->watch->sendMessage(WatchConnector::watchLAUNCHER,
+ buildAckMessage(transaction));
+ emit appStopped(uuid);
+ break;
+ default:
+ qCWarning(l) << "LAUNCHER pushed unknown message:" << uuid << dict;
+ this->watch->sendMessage(WatchConnector::watchLAUNCHER,
+ buildNackMessage(transaction));
+ break;
+ }
+}
+
+void AppMsgManager::handlePushMessage(const QByteArray &data)
+{
+ quint8 transaction;
+ QUuid uuid;
+ WatchConnector::Dict dict;
+
+ if (!unpackPushMessage(data, &transaction, &uuid, &dict)) {
+ qCWarning(l) << "Failed to parse APP_MSG PUSH";
+ watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE,
+ buildNackMessage(transaction));
+ return;
+ }
+
+ qCDebug(l) << "Received appmsg PUSH from" << uuid << "with" << dict;
+
+ QVariantMap msg = mapAppKeys(uuid, dict);
+ qCDebug(l) << "Mapped dict" << msg;
+
+ bool result;
+
+ MessageHandlerFunc handler = _handlers.value(uuid);
+ if (handler) {
+ result = handler(msg);
+ } else {
+ // No handler? Let's just send an ACK.
+ result = false;
+ }
+
+ if (result) {
+ qCDebug(l) << "ACKing transaction" << transaction;
+ watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE,
+ buildAckMessage(transaction));
+ } else {
+ qCDebug(l) << "NACKing transaction" << transaction;
+ watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE,
+ buildNackMessage(transaction));
+ }
+}
+
+void AppMsgManager::handleAckMessage(const QByteArray &data, bool ack)
+{
+ if (data.size() < 2) {
+ qCWarning(l) << "invalid ack/nack message size";
+ return;
+ }
+
+ const quint8 type = data[0]; Q_UNUSED(type);
+ const quint8 recv_transaction = data[1];
+
+ Q_ASSERT(type == WatchConnector::appmsgACK || type == WatchConnector::appmsgNACK);
+
+ if (_pending.empty()) {
+ qCWarning(l) << "received an ack/nack for transaction" << recv_transaction << "but no transaction is pending";
+ return;
+ }
+
+ PendingTransaction &trans = _pending.head();
+ if (trans.transactionId != recv_transaction) {
+ qCWarning(l) << "received an ack/nack but for the wrong transaction";
+ }
+
+ qCDebug(l) << "Got " << (ack ? "ACK" : "NACK") << " to transaction" << trans.transactionId;
+
+ _timeout->stop();
+
+ if (ack) {
+ if (trans.ackCallback) {
+ trans.ackCallback();
+ }
+ } else {
+ if (trans.nackCallback) {
+ trans.nackCallback();
+ }
+ }
+
+ _pending.dequeue();
+
+ if (!_pending.empty()) {
+ transmitNextPendingTransaction();
+ }
+}
+
+void AppMsgManager::handleWatchConnectedChanged()
+{
+ // If the watch is disconnected, everything breaks loose
+ // TODO In the future we may want to avoid doing the following.
+ if (!watch->isConnected()) {
+ abortPendingTransactions();
+ }
+}
+
+void AppMsgManager::handleTimeout()
+{
+ // Abort the first transaction
+ Q_ASSERT(!_pending.empty());
+ PendingTransaction trans = _pending.dequeue();
+
+ qCWarning(l) << "timeout on appmsg transaction" << trans.transactionId;
+
+ if (trans.nackCallback) {
+ trans.nackCallback();
+ }
+
+ if (!_pending.empty()) {
+ transmitNextPendingTransaction();
+ }
+}
+
+void AppMsgManager::transmitNextPendingTransaction()
+{
+ Q_ASSERT(!_pending.empty());
+ PendingTransaction &trans = _pending.head();
+
+ QByteArray msg = buildPushMessage(trans.transactionId, trans.uuid, trans.dict);
+
+ watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE, msg);
+
+ _timeout->start();
+}
+
+void AppMsgManager::abortPendingTransactions()
+{
+ // Invoke all the NACK callbacks in the pending queue, then drop them.
+ Q_FOREACH(const PendingTransaction &trans, _pending) {
+ if (trans.nackCallback) {
+ trans.nackCallback();
+ }
+ }
+
+ _pending.clear();
+}