diff --git a/debian/control b/debian/control
index 5be8fc63..ead887ba 100644
--- a/debian/control
+++ b/debian/control
@@ -461,6 +461,22 @@ Description: guh.io plugin for senic
This package will install the guh.io plugin for senic
+Package: guh-plugin-snapd
+Architecture: any
+Depends: ${shlibs:Depends},
+ ${misc:Depends},
+ guh-plugins-translations,
+Description: guh.io plugin for snapd
+ The guh daemon is a plugin based IoT (Internet of Things) server. The
+ server works like a translator for devices, things and services and
+ allows them to interact.
+ With the powerful rule engine you are able to connect any device available
+ in the system and create individual scenes and behaviors for your environment.
+ .
+ This package will install the guh.io plugin for snapd
+
+
+
Package: guh-plugins-translations
Section: misc
diff --git a/debian/guh-plugin-snapd.install.in b/debian/guh-plugin-snapd.install.in
new file mode 100644
index 00000000..281b705a
--- /dev/null
+++ b/debian/guh-plugin-snapd.install.in
@@ -0,0 +1 @@
+usr/lib/@DEB_HOST_MULTIARCH@/guh/plugins/libguh_devicepluginsnapd.so
diff --git a/guh-plugins.pro b/guh-plugins.pro
index 2540de02..3e46678f 100644
--- a/guh-plugins.pro
+++ b/guh-plugins.pro
@@ -31,6 +31,7 @@ PLUGIN_DIRS = \
denon \
avahimonitor \
gpio \
+ snapd \
CONFIG+=all
diff --git a/snapd/devicepluginsnapd.cpp b/snapd/devicepluginsnapd.cpp
new file mode 100644
index 00000000..60ed3802
--- /dev/null
+++ b/snapd/devicepluginsnapd.cpp
@@ -0,0 +1,259 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * *
+ * Copyright (C) 2017 Simon Stürz . *
+ * *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "devicepluginsnapd.h"
+#include "plugin/device.h"
+#include "plugininfo.h"
+#include "network/networkaccessmanager.h"
+
+DevicePluginSnapd::DevicePluginSnapd()
+{
+
+}
+
+void DevicePluginSnapd::init()
+{
+ // Check advanced mode
+ m_advancedMode = configValue(advancedModeParamTypeId).toBool();
+ connect(this, &DevicePluginSnapd::configValueChanged, this, &DevicePluginSnapd::onPluginConfigurationChanged);
+
+ // Setup timers
+ m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(2);
+ connect(m_refreshTimer, &PluginTimer::timeout, this, &DevicePluginSnapd::onRefreshTimer);
+
+ // Check all 5 min if there is an update available
+ m_updateTimer = hardwareManager()->pluginTimerManager()->registerTimer(300);
+ connect(m_refreshTimer, &PluginTimer::timeout, this, &DevicePluginSnapd::onUpdateTimer);
+}
+
+void DevicePluginSnapd::startMonitoringAutoDevices()
+{
+ // Check if we already have a controller and if snapd is available
+ if (m_snapdControl && !m_snapdControl->available()) {
+ return;
+ }
+
+ // Check if we already have a device for the snapd control
+ bool deviceAlreadyExists = false;
+ foreach (Device *device, myDevices()) {
+ if (device->deviceClassId() == snapdControlDeviceClassId) {
+ deviceAlreadyExists = true;
+ }
+ }
+
+ // Add device if there isn't already one
+ if (!deviceAlreadyExists) {
+ DeviceDescriptor descriptor(snapdControlDeviceClassId, "Update manager");
+ emit autoDevicesAppeared(snapdControlDeviceClassId, QList() << descriptor);
+ }
+}
+
+void DevicePluginSnapd::postSetupDevice(Device *device)
+{
+ if (m_snapdControl && m_snapdControl->device() == device)
+ m_snapdControl->update();
+
+}
+
+void DevicePluginSnapd::deviceRemoved(Device *device)
+{
+ if (device->deviceClassId() == snapdControlDeviceClassId) {
+ qCWarning(dcSnapd()) << "User deleted snapd control.";
+
+ // Clean up old device
+ if (m_snapdControl) {
+ delete m_snapdControl;
+ m_snapdControl = nullptr;
+ }
+
+ // Respawn device immediately
+ startMonitoringAutoDevices();
+
+ } else if (device->deviceClassId() == snapDeviceClassId) {
+ if (m_snapDevices.values().contains(device)) {
+ QString snapId = m_snapDevices.key(device);
+ m_snapDevices.remove(snapId);
+ }
+ }
+}
+
+DeviceManager::DeviceSetupStatus DevicePluginSnapd::setupDevice(Device *device)
+{
+ qCDebug(dcSnapd()) << "Setup" << device->name() << device->params();
+
+ if (device->deviceClassId() == snapdControlDeviceClassId) {
+ if (m_snapdControl) {
+ delete m_snapdControl;
+ m_snapdControl = nullptr;
+ }
+
+ m_snapdControl = new SnapdControl(device, this);
+ connect(m_snapdControl, &SnapdControl::snapListUpdated, this, &DevicePluginSnapd::onSnapListUpdated);
+
+ } else if (device->deviceClassId() == snapDeviceClassId) {
+ device->setName(QString("%1").arg(device->paramValue(snapNameParamTypeId).toString()));
+ m_snapDevices.insert(device->paramValue(snapIdParamTypeId).toString(), device);
+ }
+
+ return DeviceManager::DeviceSetupStatusSuccess;
+}
+
+DeviceManager::DeviceError DevicePluginSnapd::executeAction(Device *device, const Action &action) {
+
+ if (device->deviceClassId() == snapdControlDeviceClassId) {
+
+ if (!m_snapdControl) {
+ qCDebug(dcSnapd()) << "There is currently no snapd controller.";
+ return DeviceManager::DeviceErrorHardwareFailure;
+ }
+
+ if (!m_snapdControl->connected()) {
+ qCDebug(dcSnapd()) << "Snapd controller not connected to to backend.";
+ return DeviceManager::DeviceErrorHardwareFailure;
+ }
+
+ if (action.actionTypeId() == startUpdateActionTypeId) {
+ m_snapdControl->snapRefresh();
+ return DeviceManager::DeviceErrorNoError;
+ } else if (action.actionTypeId() == checkUpdatesActionTypeId) {
+ m_snapdControl->checkForUpdates();
+ return DeviceManager::DeviceErrorNoError;
+ }
+
+ return DeviceManager::DeviceErrorActionTypeNotFound;
+
+ } else if (device->deviceClassId() == snapDeviceClassId) {
+
+ if (!m_snapdControl) {
+ qCDebug(dcSnapd()) << "There is currently no snapd controller.";
+ return DeviceManager::DeviceErrorHardwareFailure;
+ }
+
+ if (!m_snapdControl->connected()) {
+ qCDebug(dcSnapd()) << "Snapd controller not connected to to backend.";
+ return DeviceManager::DeviceErrorHardwareFailure;
+ }
+
+ if (action.actionTypeId() == snapChannelActionTypeId) {
+ QString snapName = device->paramValue(snapNameParamTypeId).toString();
+ m_snapdControl->changeSnapChannel(snapName, action.param(snapChannelStateParamTypeId).value().toString());
+ return DeviceManager::DeviceErrorNoError;
+ } else if (action.actionTypeId() == snapRevertActionTypeId) {
+ QString snapName = device->paramValue(snapNameParamTypeId).toString();
+ m_snapdControl->snapRevert(snapName);
+ return DeviceManager::DeviceErrorNoError;
+ }
+
+ return DeviceManager::DeviceErrorActionTypeNotFound;
+ }
+
+ return DeviceManager::DeviceErrorDeviceClassNotFound;
+}
+
+void DevicePluginSnapd::onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value)
+{
+ if (paramTypeId == advancedModeParamTypeId) {
+ qCDebug(dcSnapd()) << "Advanced mode" << (value.toBool() ? "enabled." : "disabled.");
+ m_advancedMode = value.toBool();
+
+ // If advanced mode disabled, clean up all snap devices
+ if (!m_advancedMode) {
+ foreach (const QString deviceSnapId, m_snapDevices.keys()) {
+ Device *device = m_snapDevices.take(deviceSnapId);
+ qCDebug(dcSnapd()) << "Remove device for snap" << device->paramValue(snapNameParamTypeId).toString();
+ emit autoDeviceDisappeared(device->id());
+ }
+ } else {
+ if (!m_snapdControl)
+ return;
+
+ m_snapdControl->update();
+ }
+ }
+}
+
+void DevicePluginSnapd::onRefreshTimer()
+{
+ if (!m_snapdControl) {
+ startMonitoringAutoDevices();
+ return;
+ }
+
+ m_snapdControl->update();
+}
+
+void DevicePluginSnapd::onUpdateTimer()
+{
+ if (!m_snapdControl)
+ return;
+
+ m_snapdControl->checkForUpdates();
+}
+
+void DevicePluginSnapd::onSnapListUpdated(const QVariantList &snapList)
+{
+ // Check for new snap devices only in advanced mode
+ if (!m_advancedMode)
+ return;
+
+ // Collect list of snap id's from snapList for remove checking
+ QStringList snapIdList;
+
+ // Check if we have to create a new device
+ foreach (const QVariant &snapVariant, snapList) {
+ QVariantMap snapMap = snapVariant.toMap();
+ snapIdList.append(snapMap.value("id").toString());
+ // If there is no device for this snap yet
+ if (!m_snapDevices.contains(snapMap.value("id").toString())) {
+ DeviceDescriptor descriptor(snapDeviceClassId, QString("Snap %1").arg(snapMap.value("name").toString()));
+ ParamList params;
+ params.append(Param(snapNameParamTypeId, snapMap.value("name")));
+ params.append(Param(snapIdParamTypeId, snapMap.value("id")));
+ params.append(Param(snapSummaryParamTypeId, snapMap.value("summary")));
+ params.append(Param(snapDescriptionParamTypeId, snapMap.value("description")));
+ params.append(Param(snapDeveloperParamTypeId, snapMap.value("developer")));
+ descriptor.setParams(params);
+
+ emit autoDevicesAppeared(snapDeviceClassId, QList() << descriptor);
+ } else {
+ // Update the states
+ Device *device = m_snapDevices.value(snapMap.value("id").toString(), nullptr);
+ if (!device) {
+ qCWarning(dcSnapd()) << "Holding invalid snap device. This should never happen. Please report a bug if you see this message.";
+ continue;
+ }
+
+ device->setStateValue(snapChannelStateTypeId, snapMap.value("channel").toString());
+ device->setStateValue(snapVersionStateTypeId, snapMap.value("version").toString());
+ device->setStateValue(snapRevisionStateTypeId, snapMap.value("revision").toString());
+ }
+ }
+
+ // Check if we have to remove a device
+ foreach (const QString deviceSnapId, m_snapDevices.keys()) {
+ if (!snapIdList.contains(deviceSnapId)) {
+ Device *device = m_snapDevices.take(deviceSnapId);
+ qCDebug(dcSnapd()) << "The snap" << device->paramValue(snapNameParamTypeId).toString() << "is not installed any more.";
+ emit autoDeviceDisappeared(device->id());
+ }
+ }
+}
diff --git a/snapd/devicepluginsnapd.h b/snapd/devicepluginsnapd.h
new file mode 100644
index 00000000..e0b2d001
--- /dev/null
+++ b/snapd/devicepluginsnapd.h
@@ -0,0 +1,71 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * *
+ * Copyright (C) 2017 Simon Stürz . *
+ * *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef DEVICEPLUGINSNAPD_H
+#define DEVICEPLUGINSNAPD_H
+
+#include "devicemanager.h"
+#include "plugin/deviceplugin.h"
+#include "plugintimer.h"
+
+#include
+#include
+#include
+#include
+
+#include "snapdcontrol.h"
+
+
+class DevicePluginSnapd: public DevicePlugin {
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "guru.guh.DevicePlugin" FILE "devicepluginsnapd.json")
+ Q_INTERFACES(DevicePlugin)
+
+public:
+ explicit DevicePluginSnapd();
+ void init() override;
+ void startMonitoringAutoDevices() override;
+ void postSetupDevice(Device *device) override;
+ void deviceRemoved(Device *device) override;
+
+ DeviceManager::DeviceSetupStatus setupDevice(Device *device) override;
+ DeviceManager::DeviceError executeAction(Device *device, const Action &action) override;
+
+private:
+ SnapdControl *m_snapdControl = nullptr;
+ PluginTimer *m_refreshTimer = nullptr;
+ PluginTimer *m_updateTimer = nullptr;
+
+ bool m_advancedMode = false;
+
+ // Snap list for faster access (snap id, device)
+ QHash m_snapDevices;
+
+private slots:
+ void onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value);
+ void onRefreshTimer();
+ void onUpdateTimer();
+ void onSnapListUpdated(const QVariantList &snapList);
+
+};
+
+#endif // DEVICEPLUGINSNAPD_H
diff --git a/snapd/devicepluginsnapd.json b/snapd/devicepluginsnapd.json
new file mode 100644
index 00000000..308fb7db
--- /dev/null
+++ b/snapd/devicepluginsnapd.json
@@ -0,0 +1,196 @@
+{
+ "name": "Snapd",
+ "idName": "Snapd",
+ "id": "b82bce59-59bf-48b3-b781-54a6f45800f3",
+ "paramTypes": [
+ {
+ "id": "017fe4c5-fc41-41fe-8e67-08fdaccb89ea",
+ "idName": "advancedMode",
+ "name": "Advanced mode",
+ "type": "bool",
+ "defaultValue": false
+ }
+ ],
+ "vendors": [
+ {
+ "name": "Canonical",
+ "idName": "canonical",
+ "id": "60582ddf-32ea-4fcd-a6f2-f3beaaf21517",
+ "deviceClasses": [
+ {
+ "id": "d90cda58-4d8c-4b7f-a982-38e56a95b72a",
+ "idName": "snapdControl",
+ "name": "Update manager",
+ "createMethods": [ "auto" ],
+ "basicTags": [ "Gateway" ],
+ "deviceIcon": "Gateway",
+ "paramTypes": [ ],
+ "actionTypes": [
+ {
+ "id": "45626b75-f09d-4dd1-b6c4-ee33201b47b0",
+ "idName": "startUpdate",
+ "name": "Start update",
+ "paramTypes": [ ]
+ },
+ {
+ "id": "4738f2c9-666e-45b9-91d3-7bcbf722b669",
+ "idName": "checkUpdates",
+ "name": "Check for updates",
+ "paramTypes": [ ]
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "6b662b3e-fd12-4f24-be77-aec066f16d8c",
+ "idName": "snapdAvailable",
+ "name": "Update manager available",
+ "eventTypeName": "Update manager available changed",
+ "type": "bool",
+ "defaultValue": false
+ },
+ {
+ "id": "a6b1d24b-d523-4516-9bce-5b467e5e09b2",
+ "idName": "updateAvailable",
+ "name": "System update available",
+ "eventTypeName": "System update available changed",
+ "type": "bool",
+ "defaultValue": false
+ },
+ {
+ "id": "01ca7a22-5607-4c5e-a465-a2ae7e8b529c",
+ "idName": "updateRunning",
+ "name": "System update running",
+ "eventTypeName": "System update running changed",
+ "type": "bool",
+ "defaultValue": false
+ },
+ {
+ "id": "c671545a-6bde-4c08-8e37-0d256841a3a5",
+ "idName": "lastUpdateTime",
+ "name": "Last automatic system update",
+ "eventTypeName": "Last automatic system update time changed",
+ "unit": "UnixTime",
+ "type": "int",
+ "eventRuleRelevant": false,
+ "stateRuleRelevant": false,
+ "defaultValue": 0
+ },
+ {
+ "id": "122c2423-a1d9-400f-80f8-b1f798975914",
+ "idName": "nextUpdateTime",
+ "name": "Next automatic system update",
+ "eventTypeName": "Next automatic system update time changed",
+ "unit": "UnixTime",
+ "type": "int",
+ "eventRuleRelevant": false,
+ "stateRuleRelevant": false,
+ "defaultValue": 0
+ },
+ {
+ "id": "4987aca3-3916-4cb3-938f-df6c99d04dbf",
+ "idName": "status",
+ "name": "Status",
+ "eventTypeName": "Status changed",
+ "type": "QString",
+ "eventRuleRelevant": false,
+ "stateRuleRelevant": false,
+ "defaultValue": "-"
+ }
+ ]
+ },
+ {
+ "id": "ff0840d7-fcfc-4403-9d9f-301610d5a437",
+ "idName": "snap",
+ "name": "Snap",
+ "createMethods": [ "auto" ],
+ "basicTags": [ "Gateway" ],
+ "deviceIcon": "Network",
+ "paramTypes": [
+ {
+ "id": "4f38614d-8be0-48dc-a24d-cee9ff1f2a89",
+ "idName": "snapName",
+ "name": "Name",
+ "type": "QString",
+ "defaultValue": "-"
+ },
+ {
+ "id": "9afb98fb-f717-4f4c-8009-1a6514054c5f",
+ "idName": "snapId",
+ "name": "ID",
+ "type": "QString",
+ "defaultValue": "-"
+ },
+ {
+ "id": "12b9a65f-970b-49b5-b1d0-1625fc6d8758",
+ "idName": "snapSummary",
+ "name": "Summary",
+ "type": "QString",
+ "defaultValue": "-"
+ },
+ {
+ "id": "fe24c61b-e154-4259-b7ca-6f0602e9d1c3",
+ "idName": "snapDescription",
+ "name": "Description",
+ "type": "QString",
+ "defaultValue": "-"
+ },
+ {
+ "id": "76ead9c5-0a18-40a2-b31d-f6bb6dfea0a5",
+ "idName": "snapDeveloper",
+ "name": "Developer",
+ "type": "QString",
+ "defaultValue": "-"
+ }
+ ],
+ "actionTypes": [
+ {
+ "id": "e061dee6-62fc-45cc-9c9f-403c2be52939",
+ "idName": "snapRevert",
+ "name": "Rollback to previous version"
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "7be2b61e-3f59-4b92-b2bb-50d027bb92ff",
+ "idName": "snapChannel",
+ "name": "Channel",
+ "eventTypeName": "Channel changed",
+ "type": "QString",
+ "eventRuleRelevant": false,
+ "stateRuleRelevant": false,
+ "actionTypeName": "Set channel",
+ "defaultValue": "stable",
+ "writable": true,
+ "possibleValues": [
+ "stable",
+ "candidate",
+ "beta",
+ "edge"
+ ]
+ },
+ {
+ "id": "532a95f3-db29-427e-bb32-d5a22029e586",
+ "idName": "snapVersion",
+ "name": "Version",
+ "eventTypeName": "Version changed",
+ "type": "QString",
+ "eventRuleRelevant": false,
+ "stateRuleRelevant": false,
+ "defaultValue": "-"
+ },
+ {
+ "id": "f26a6404-e011-11e7-9224-2350048461eb",
+ "idName": "snapRevision",
+ "name": "Revision",
+ "eventTypeName": "Revision changed",
+ "type": "QString",
+ "eventRuleRelevant": false,
+ "stateRuleRelevant": false,
+ "defaultValue": "-"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/snapd/snapd.pro b/snapd/snapd.pro
new file mode 100644
index 00000000..c48b8661
--- /dev/null
+++ b/snapd/snapd.pro
@@ -0,0 +1,21 @@
+TRANSLATIONS = translations/en_US.ts \
+ translations/de_DE.ts
+
+# Note: include after the TRANSLATIONS definition
+include(../plugins.pri)
+
+TARGET = $$qtLibraryTarget(guh_devicepluginsnapd)
+
+QT += network
+
+SOURCES += \
+ devicepluginsnapd.cpp \
+ snapdcontrol.cpp \
+ snapdconnection.cpp \
+ snapdreply.cpp
+
+HEADERS += \
+ devicepluginsnapd.h \
+ snapdcontrol.h \
+ snapdconnection.h \
+ snapdreply.h
diff --git a/snapd/snapdconnection.cpp b/snapd/snapdconnection.cpp
new file mode 100644
index 00000000..6140019a
--- /dev/null
+++ b/snapd/snapdconnection.cpp
@@ -0,0 +1,327 @@
+#include "snapdconnection.h"
+#include "extern-plugininfo.h"
+
+#include
+#include
+
+
+SnapdConnection::SnapdConnection(QObject *parent) :
+ QLocalSocket(parent)
+{
+ connect(this, &QLocalSocket::connected, this, &SnapdConnection::onConnected);
+ connect(this, &QLocalSocket::disconnected, this, &SnapdConnection::onDisconnected);
+ connect(this, &QLocalSocket::readyRead, this, &SnapdConnection::onReadyRead);
+ connect(this, &QLocalSocket::stateChanged, this, &SnapdConnection::onStateChanged);
+ connect(this, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(onError(QLocalSocket::LocalSocketError)));
+}
+
+SnapdReply *SnapdConnection::get(const QString &path)
+{
+ SnapdReply *reply = new SnapdReply(this);
+ reply->setRequestPath(path);
+ reply->setRequestMethod("GET");
+ reply->setRequestRawMessage(createRequestHeader("GET", path));
+
+ // Check if currently a reply is running
+ if (m_currentReply) {
+ m_replyQueue.enqueue(reply);
+ } else {
+ // Send request
+ m_currentReply = reply;
+ if (m_debug)
+ qCDebug(dcSnapd()) << "-->" << reply->requestMethod() << reply->requestPath();
+
+ if (write(reply->requestRawMessage()) <= 0) {
+ m_currentReply = nullptr;
+ reply->setFinished(false);
+ sendNextRequest();
+ }
+ }
+
+ // Note: the caller owns the object now
+ return reply;
+}
+
+SnapdReply *SnapdConnection::post(const QString &path, const QByteArray &payload)
+{
+ SnapdReply *reply = new SnapdReply(this);
+ reply->setRequestPath(path);
+ reply->setRequestMethod("POST");
+ QByteArray header = createRequestHeader("POST", path, payload);
+ reply->setRequestRawMessage(header.append(payload));
+
+ // Check if currently a reply is running
+ if (m_currentReply) {
+ m_replyQueue.enqueue(reply);
+ } else {
+ // Send request
+ m_currentReply = reply;
+ if (m_debug)
+ qCDebug(dcSnapd()) << "-->" << reply->requestMethod() << reply->requestPath() << payload;
+
+ if (write(reply->requestRawMessage()) <= 0) {
+ m_currentReply = nullptr;
+ reply->setFinished(false);
+ sendNextRequest();
+ }
+ }
+
+ // Note: the caller owns the object now
+ return reply;
+}
+
+bool SnapdConnection::isConnected() const
+{
+ return m_connected;
+}
+
+void SnapdConnection::setConnected(const bool &connected)
+{
+ if (m_connected == connected)
+ return;
+
+ qCDebug(dcSnapd()) << "Connected";
+
+ m_connected = connected;
+ emit connectedChanged(m_connected);
+}
+
+QByteArray SnapdConnection::createRequestHeader(const QString &method, const QString &path, const QByteArray &payload)
+{
+ QByteArray request;
+ request.append(QString("%1 %2 HTTP/1.1\r\n").arg(method).arg(path).toUtf8());
+ request.append("Host: http\r\n");
+ request.append("Accept: *\r\n");
+ if (!payload.isEmpty()) {
+ request.append("Content-Type: application/json\r\n");
+ request.append(QString("Content-Length: %1\r\n").arg(payload.count()).toUtf8());
+ }
+
+ request.append("\r\n");
+ return request;
+}
+
+QByteArray SnapdConnection::getChunckedPayload(const QByteArray &payload)
+{
+ // Read line by line
+ QStringList payloadLines = QString::fromUtf8(payload).split(QRegExp("\r\n"));
+ if (payloadLines.count() < 4) {
+ qCWarning(dcSnapd()) << "Chuncked payload invalid linecount" << payloadLines.count();
+ return QByteArray();
+ }
+
+ int payloadSize = payloadLines.at(2).toInt(0, 16);
+
+ if (m_debug)
+ qCDebug(dcSnapd()) << "Payload size" << payloadSize;
+
+ if (payloadLines.at(3).toUtf8().size() != payloadSize) {
+ qCWarning(dcSnapd()) << "Invalid payload size" << payloadLines.at(3).toUtf8().size() << "!=" << payloadSize;
+ return QByteArray();
+ }
+
+ // Return just the payload
+ return payloadLines.at(3).toUtf8();
+}
+
+void SnapdConnection::processData()
+{
+ if (!m_currentReply) {
+ qCWarning(dcSnapd()) << "Data received without current reply" << m_payload;
+ return;
+ }
+
+ if (m_header.isEmpty()) {
+ qCWarning(dcSnapd()) << "Could not process data. There is no header.";
+ m_currentReply->setFinished();
+ return;
+ }
+
+ // Get the raw payload
+ QByteArray payloadData;
+ if (m_chuncked) {
+ payloadData = getChunckedPayload(m_payload);
+ } else {
+ payloadData = m_payload;
+ }
+
+ // Check if there are data to process
+ if (m_payload.isEmpty()) {
+ qCWarning(dcSnapd()) << "Could not process data. There is no payload to process.";
+ return;
+ }
+
+ // Parse header
+ QHash parsedHeader;
+ QStringList headerLines = QString::fromUtf8(m_header).split(QRegExp("\r\n"));
+
+ // Read status line
+ QString statusLine = headerLines.takeFirst();
+ QStringList statusLineTokens = statusLine.split(QRegExp("[ \r\n][ \r\n]*"));
+ if (statusLineTokens.count() < 3) {
+ qCWarning(dcSnapd()) << "Could not parse HTTP status line:" << statusLine;
+ return;
+ }
+
+ bool statusCodeOk = false;
+ int statusCode = statusLineTokens.at(1).simplified().toInt(&statusCodeOk);
+ if (!statusCodeOk) {
+ qCWarning(dcSnapd()) << "Could not parse HTTP status code:" << statusLineTokens.at(1);
+ return;
+ }
+
+ QString statusMessage;
+ for (int i = 2; i < statusLineTokens.count(); i++) {
+ statusMessage.append(statusLineTokens.at(i).simplified());
+ if (i < statusLineTokens.count() -1) {
+ statusMessage.append(" ");
+ }
+ }
+
+ // Verify header formating
+ foreach (const QString &line, headerLines) {
+ if (!line.contains(":")) {
+ qCWarning(dcSnapd()) << "Invalid HTTP header. Missing \":\" in line" << line;
+ return;
+ }
+
+ int index = line.indexOf(":");
+ QString key = line.left(index).toUtf8().simplified();
+ QString value = line.right(line.count() - index - 1).toUtf8().simplified();
+ //qCDebug(dcSnapd()) << " Key:" << key << "Value:" << value;
+ parsedHeader.insert(key, value);
+ }
+
+ // Parse payload
+ QJsonParseError error;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(payloadData, &error);
+ if (error.error != QJsonParseError::NoError) {
+ qCWarning(dcSnapd()) << "Got invalid JSON data from snapd:" << error.offset << error.errorString();
+ qCWarning(dcSnapd()) << qUtf8Printable(payloadData);
+ return;
+ }
+
+ if (m_debug)
+ qCDebug(dcSnapd()) << "<--" << m_currentReply->requestPath() << statusCode << statusMessage;
+
+ // Fill reply
+ m_currentReply->setStatusCode(statusCode);
+ m_currentReply->setStatusMessage(statusMessage);
+ m_currentReply->setHeader(parsedHeader);
+ m_currentReply->setDataMap(jsonDoc.toVariant().toMap());
+ m_currentReply->setFinished();
+ m_currentReply = nullptr;
+
+ sendNextRequest();
+
+ // Current data stream finished, reset for new messages
+ m_payload.clear();
+ m_header.clear();
+ m_chuncked = false;
+}
+
+void SnapdConnection::sendNextRequest()
+{
+ if (m_replyQueue.isEmpty())
+ return;
+
+ SnapdReply *reply = m_replyQueue.dequeue();
+ m_currentReply = reply;
+ if (m_debug)
+ qCDebug(dcSnapd()) << "-->" << reply->requestMethod() << reply->requestPath();
+
+ if (write(reply->requestRawMessage()) < 0) {
+ m_currentReply->setFinished(false);
+ m_currentReply = nullptr;
+ sendNextRequest();
+ }
+}
+
+void SnapdConnection::onConnected()
+{
+ setConnected(true);
+}
+
+void SnapdConnection::onDisconnected()
+{
+ setConnected(false);
+}
+
+void SnapdConnection::onError(const QLocalSocket::LocalSocketError &socketError)
+{
+ qCWarning(dcSnapd()) << "Socket error" << socketError << errorString();
+}
+
+void SnapdConnection::onStateChanged(const QLocalSocket::LocalSocketState &state)
+{
+ switch (state) {
+ case QLocalSocket::UnconnectedState:
+ qCDebug(dcSnapd()) << "Disconnected from snapd.";
+ break;
+ case QLocalSocket::ConnectingState:
+ qCDebug(dcSnapd()) << "Connecting to snapd...";
+ break;
+ case QLocalSocket::ConnectedState:
+ qCDebug(dcSnapd()) << "Connected to snapd.";
+ break;
+ case QLocalSocket::ClosingState:
+ qCDebug(dcSnapd()) << "Closing connection to snapd.";
+ break;
+ default:
+ break;
+ }
+}
+
+void SnapdConnection::onReadyRead()
+{
+ QByteArray data = readAll();
+
+ if (m_debug)
+ qCDebug(dcSnapd()) << "Data received:" << data;
+
+ // If we are not appending to a reply
+ if (!m_chuncked) {
+
+ // Parse header
+ int headerIndex = data.indexOf("\r\n\r\n");
+ if (headerIndex < 0) {
+ qCWarning(dcSnapd()) << "Invalid response format. Could not find header/payload mark.";
+ return;
+ }
+
+ m_header = data.left(headerIndex);
+
+ if (m_debug)
+ qCDebug(dcSnapd()) << "Header:" << m_header;
+
+ QByteArray payload = data.right(data.length() - (headerIndex));
+
+ if (m_debug)
+ qCDebug(dcSnapd()) << "Payload" << payload;
+
+ // Check if this message is chuncked
+ if (m_header.contains("chunked")) {
+
+ if (m_debug)
+ qCDebug(dcSnapd()) << "Chuncked message receiving";
+
+ m_chuncked = true;
+ m_payload.append(payload);
+
+ if (m_payload.endsWith("\r\n0\r\n\r\n")) {
+ // Chuncked message finished
+ processData();
+ }
+ } else {
+ // Not chucked
+ m_payload = payload;
+ processData();
+ }
+ } else {
+ m_payload.append(data);
+ if (m_payload.endsWith("\r\n0\r\n\r\n")) {
+ // Chuncked message finished
+ processData();
+ }
+ }
+}
diff --git a/snapd/snapdconnection.h b/snapd/snapdconnection.h
new file mode 100644
index 00000000..d079839e
--- /dev/null
+++ b/snapd/snapdconnection.h
@@ -0,0 +1,54 @@
+#ifndef SNAPDCONNECTION_H
+#define SNAPDCONNECTION_H
+
+#include
+#include
+#include
+
+#include "snapdreply.h"
+
+class SnapdConnection : public QLocalSocket
+{
+ Q_OBJECT
+public:
+ explicit SnapdConnection(QObject *parent = nullptr);
+
+ SnapdReply *get(const QString &path);
+ SnapdReply *post(const QString &path, const QByteArray &payload);
+
+ bool isConnected() const;
+
+private:
+ bool m_chuncked = false;
+
+ QByteArray m_header;
+ QByteArray m_payload;
+
+ bool m_connected = false;
+ bool m_debug = false;
+
+ SnapdReply *m_currentReply = nullptr;
+ QQueue m_replyQueue;
+
+ void setConnected(const bool &connected);
+
+ // Helper methods
+ QByteArray createRequestHeader(const QString &method, const QString &path, const QByteArray &payload = QByteArray());
+ QByteArray getChunckedPayload(const QByteArray &payload);
+
+ void processData();
+ void sendNextRequest();
+
+private slots:
+ void onConnected();
+ void onDisconnected();
+ void onError(const QLocalSocket::LocalSocketError &socketError);
+ void onStateChanged(const QLocalSocket::LocalSocketState &state);
+ void onReadyRead();
+
+signals:
+ void connectedChanged(const bool &connected);
+
+};
+
+#endif // SNAPDCONNECTION_H
diff --git a/snapd/snapdcontrol.cpp b/snapd/snapdcontrol.cpp
new file mode 100644
index 00000000..706e32d7
--- /dev/null
+++ b/snapd/snapdcontrol.cpp
@@ -0,0 +1,424 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * *
+ * Copyright (C) 2017 Simon Stürz . *
+ * *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "snapdcontrol.h"
+#include "extern-plugininfo.h"
+
+#include
+#include
+#include
+#include
+
+SnapdControl::SnapdControl(Device *device, QObject *parent) :
+ QObject(parent),
+ m_device(device),
+ m_snapdSocketPath("/run/snapd.socket")
+{
+ m_snapConnection = new SnapdConnection(this);
+ connect(m_snapConnection, &SnapdConnection::connectedChanged, this, &SnapdControl::onConnectedChanged);
+}
+
+Device *SnapdControl::device()
+{
+ return m_device;
+}
+
+bool SnapdControl::available() const
+{
+ QFileInfo fileInfo(m_snapdSocketPath);
+ if (!fileInfo.exists()) {
+ qCWarning(dcSnapd()) << "The socket descriptor" << m_snapdSocketPath << "does not exist";
+ return false;
+ }
+
+ if (!fileInfo.isReadable()) {
+ qCWarning(dcSnapd()) << "The socket descriptor" << m_snapdSocketPath << "is not readable";
+ return false;
+ }
+
+ if (!fileInfo.isWritable()) {
+ qCWarning(dcSnapd()) << "The socket descriptor" << m_snapdSocketPath << "is not writable";
+ return false;
+ }
+
+ return true;
+}
+
+bool SnapdControl::connected() const
+{
+ return m_snapConnection->isConnected();
+}
+
+bool SnapdControl::enabled() const
+{
+ return m_enabled;
+}
+
+void SnapdControl::loadSystemInfo()
+{
+ if (!m_snapConnection)
+ return;
+
+ if (!m_snapConnection->isConnected())
+ return;
+
+ SnapdReply *reply = m_snapConnection->get("/v2/system-info");
+ connect(reply, &SnapdReply::finished, this, &SnapdControl::onLoadSystemInfoFinished);
+}
+
+void SnapdControl::loadSnapList()
+{
+ if (!m_snapConnection)
+ return;
+
+ if (!m_snapConnection->isConnected())
+ return;
+
+ SnapdReply *reply = m_snapConnection->get("/v2/snaps");
+ connect(reply, &SnapdReply::finished, this, &SnapdControl::onLoadSnapListFinished);
+}
+
+void SnapdControl::loadRunningChanges()
+{
+ if (!m_snapConnection)
+ return;
+
+ if (!m_snapConnection->isConnected())
+ return;
+
+ SnapdReply *reply = m_snapConnection->get("/v2/changes");
+ connect(reply, &SnapdReply::finished, this, &SnapdControl::onLoadRunningChangesFinished);
+}
+
+void SnapdControl::loadChange(const int &change)
+{
+ if (!m_snapConnection)
+ return;
+
+ if (!m_snapConnection->isConnected())
+ return;
+
+ SnapdReply *reply = m_snapConnection->get(QString("/v2/changes/%1").arg(QString::number(change)));
+ connect(reply, &SnapdReply::finished, this, &SnapdControl::onLoadChangeFinished);
+}
+
+void SnapdControl::processChange(const QVariantMap &changeMap)
+{
+ int changeId = changeMap.value("id").toInt();
+ bool changeReady = changeMap.value("ready").toBool();
+ QString changeKind = changeMap.value("kind").toString();
+ QString changeStatus = changeMap.value("status").toString();
+ QString changeSummary = changeMap.value("summary").toString();
+
+ qCDebug(dcSnapd()) << changeStatus << changeKind << changeSummary;
+
+ // Add this change if not alreade finishished or added
+ if (!m_watchingChanges.contains(changeId) && !changeReady)
+ m_watchingChanges.append(changeId);
+
+ // If change is on Doing, update the status
+ if (changeStatus == "Doing") {
+ device()->setStateValue(statusStateTypeId, changeSummary);
+ }
+
+ if (changeReady) {
+ qCDebug(dcSnapd()).noquote() << changeKind << (changeReady ? "finished." : "running.") << changeSummary;
+ m_watchingChanges.removeAll(changeId);
+ }
+}
+
+bool SnapdControl::validAsyncResponse(const QVariantMap &responseMap)
+{
+ if (!responseMap.contains("type"))
+ return false;
+
+ if (responseMap.value("type") == "error")
+ return false;
+
+ return true;
+}
+
+void SnapdControl::onConnectedChanged(const bool &connected)
+{
+ if (connected) {
+ device()->setStateValue(snapdAvailableStateTypeId, true);
+ update();
+ } else {
+ device()->setStateValue(snapdAvailableStateTypeId, false);
+ }
+}
+
+void SnapdControl::onLoadSystemInfoFinished()
+{
+ SnapdReply *reply = static_cast(sender());
+ if (!reply->isValid()) {
+ qCDebug(dcSnapd()) << "Load system info request finished with error" << reply->requestPath();
+ reply->deleteLater();
+ return;
+ }
+
+ QVariantMap result = reply->dataMap().value("result").toMap();
+ QDateTime lastRefreshTime = QDateTime::fromString(result.value("refresh").toMap().value("last").toString(), Qt::ISODate);
+ QDateTime nextRefreshTime = QDateTime::fromString(result.value("refresh").toMap().value("next").toString(), Qt::ISODate);
+
+ // Set update time information
+ device()->setStateValue(lastUpdateTimeStateTypeId, lastRefreshTime.toTime_t());
+ device()->setStateValue(nextUpdateTimeStateTypeId, nextRefreshTime.toTime_t());
+
+ reply->deleteLater();
+}
+
+void SnapdControl::onLoadSnapListFinished()
+{
+ SnapdReply *reply = static_cast(sender());
+ if (!reply->isValid()) {
+ qCDebug(dcSnapd()) << "Load system info request finished with error" << reply->requestPath();
+ reply->deleteLater();
+ return;
+ }
+
+ emit snapListUpdated(reply->dataMap().value("result").toList());
+ reply->deleteLater();
+}
+
+void SnapdControl::onLoadRunningChangesFinished()
+{
+ SnapdReply *reply = static_cast(sender());
+ if (!reply->isValid()) {
+ qCDebug(dcSnapd()) << "Load running changes request finished with error" << reply->requestPath();
+ reply->deleteLater();
+ return;
+ }
+
+ foreach (const QVariant &changeVariant, reply->dataMap().value("result").toList()) {
+ processChange(changeVariant.toMap());
+ }
+
+ if (reply->dataMap().value("result").toList().isEmpty() && m_watchingChanges.isEmpty()) {
+ device()->setStateValue(updateRunningStateTypeId, false);
+ device()->setStateValue(statusStateTypeId, "-");
+ } else {
+ device()->setStateValue(updateRunningStateTypeId, true);
+ }
+
+ reply->deleteLater();
+}
+
+void SnapdControl::onLoadChangeFinished()
+{
+ SnapdReply *reply = static_cast(sender());
+ if (!reply->isValid()) {
+ qCDebug(dcSnapd()) << "Load change request finished with error" << reply->requestPath();
+ reply->deleteLater();
+ return;
+ }
+
+ processChange(reply->dataMap().value("result").toMap());
+
+ if (m_watchingChanges.isEmpty()) {
+ device()->setStateValue(updateRunningStateTypeId, false);
+
+ }
+
+ reply->deleteLater();
+}
+
+void SnapdControl::onSnapRefreshFinished()
+{
+ SnapdReply *reply = static_cast(sender());
+ if (!reply->isValid()) {
+ qCDebug(dcSnapd()) << "Snap refresh request finished with error" << reply->requestPath();
+ reply->deleteLater();
+ return;
+ }
+
+ //qCDebug(dcSnapd()) << qUtf8Printable(QJsonDocument::fromVariant(reply->dataMap()).toJson(QJsonDocument::Indented));
+ if (!validAsyncResponse(reply->dataMap())) {
+ qCWarning(dcSnapd()) << "Async change request finished with error" << reply->dataMap().value("status").toString() << reply->dataMap().value("").toString();
+ } else {
+ loadChange(reply->dataMap().value("change").toInt());
+ }
+
+ reply->deleteLater();
+}
+
+void SnapdControl::onSnapRevertFinished()
+{
+ SnapdReply *reply = static_cast(sender());
+ if (!reply->isValid()) {
+ qCDebug(dcSnapd()) << "Snap revert request finished with error" << reply->requestPath();
+ reply->deleteLater();
+ return;
+ }
+
+ //qCDebug(dcSnapd()) << qUtf8Printable(QJsonDocument::fromVariant(reply->dataMap()).toJson(QJsonDocument::Indented));
+ if (!validAsyncResponse(reply->dataMap())) {
+ qCWarning(dcSnapd()) << "Async change request finished with error" << reply->dataMap().value("status").toString() << reply->dataMap().value("").toString();
+ } else {
+ loadChange(reply->dataMap().value("change").toInt());
+ }
+ reply->deleteLater();
+}
+
+void SnapdControl::onCheckForUpdatesFinished()
+{
+ SnapdReply *reply = static_cast(sender());
+ if (!reply->isValid()) {
+ qCDebug(dcSnapd()) << "Snap check for updates request finished with error" << reply->requestPath();
+ reply->deleteLater();
+ return;
+ }
+
+ //qCDebug(dcSnapd()) << qUtf8Printable(QJsonDocument::fromVariant(reply->dataMap()).toJson(QJsonDocument::Indented));
+ device()->setStateValue(updateAvailableStateTypeId, !reply->dataMap().value("result").toList().isEmpty());
+ reply->deleteLater();
+}
+
+void SnapdControl::onChangeSnapChannelFinished()
+{
+ SnapdReply *reply = static_cast(sender());
+ if (!reply->isValid()) {
+ qCDebug(dcSnapd()) << "Change snap channel request finished with error" << reply->requestPath();
+ reply->deleteLater();
+ return;
+ }
+
+ if (!validAsyncResponse(reply->dataMap())) {
+ qCWarning(dcSnapd()) << "Async change request finished with error" << reply->dataMap().value("status").toString() << reply->dataMap().value("").toString();
+ } else {
+ loadChange(reply->dataMap().value("change").toInt());
+ }
+ reply->deleteLater();
+}
+
+void SnapdControl::enable()
+{
+ m_enabled = true;
+ update();
+}
+
+void SnapdControl::disable()
+{
+ m_enabled = false;
+
+ if (m_snapConnection) {
+ m_snapConnection->close();
+ }
+}
+
+void SnapdControl::update()
+{
+ if (!m_snapConnection)
+ return;
+
+ if (!available())
+ return;
+
+ if (!enabled())
+ return;
+
+ // Try to reconnect if unconnected
+ if (m_snapConnection->state() == QLocalSocket::UnconnectedState) {
+ m_snapConnection->connectToServer(m_snapdSocketPath, QLocalSocket::ReadWrite);
+ return;
+ }
+
+ // Note: this makes sure the state is realy connected (including connection initialisation stuff)
+ if (!m_snapConnection->isConnected())
+ return;
+
+ // Update information
+ if (!m_watchingChanges.isEmpty()) {
+ // We are watching currently changes
+ foreach (const int &change, m_watchingChanges) {
+ loadChange(change);
+ }
+ loadRunningChanges();
+ } else {
+ // Normal refresh
+ loadSystemInfo();
+ loadSnapList();
+ checkForUpdates();
+ loadRunningChanges();
+ }
+}
+
+void SnapdControl::snapRefresh()
+{
+ if (!m_snapConnection)
+ return;
+
+ if (!m_snapConnection->isConnected())
+ return;
+
+ QVariantMap request;
+ request.insert("action", "refresh");
+
+ qCDebug(dcSnapd()) << "Refresh all snaps";
+ SnapdReply *reply = m_snapConnection->post("/v2/snaps", QJsonDocument::fromVariant(request).toJson(QJsonDocument::Compact));
+ connect(reply, &SnapdReply::finished, this, &SnapdControl::onSnapRefreshFinished);
+}
+
+void SnapdControl::changeSnapChannel(const QString &snapName, const QString &channel)
+{
+ if (!m_snapConnection)
+ return;
+
+ if (!m_snapConnection->isConnected())
+ return;
+
+ QVariantMap request;
+ request.insert("action", "refresh");
+ request.insert("channel", channel);
+
+ qCDebug(dcSnapd()) << "Refresh snap" << snapName << "to channel" << channel;
+ SnapdReply *reply = m_snapConnection->post(QString("/v2/snaps/%1").arg(snapName), QJsonDocument::fromVariant(request).toJson(QJsonDocument::Compact));
+ connect(reply, &SnapdReply::finished, this, &SnapdControl::onChangeSnapChannelFinished);
+}
+
+void SnapdControl::checkForUpdates()
+{
+ if (!m_snapConnection)
+ return;
+
+ if (!m_snapConnection->isConnected())
+ return;
+
+ SnapdReply *reply = m_snapConnection->get("/v2/find?select=refresh");
+ connect(reply, &SnapdReply::finished, this, &SnapdControl::onCheckForUpdatesFinished);
+}
+
+void SnapdControl::snapRevert(const QString &snapName)
+{
+ if (!m_snapConnection)
+ return;
+
+ if (!m_snapConnection->isConnected())
+ return;
+
+ QVariantMap request;
+ request.insert("action", "revert");
+
+ qCDebug(dcSnapd()) << "Revert snap" << snapName;
+ SnapdReply *reply = m_snapConnection->post(QString("/v2/snaps/%1").arg(snapName), QJsonDocument::fromVariant(request).toJson(QJsonDocument::Compact));
+ connect(reply, &SnapdReply::finished, this, &SnapdControl::onSnapRevertFinished);
+}
diff --git a/snapd/snapdcontrol.h b/snapd/snapdcontrol.h
new file mode 100644
index 00000000..4b5bfc9d
--- /dev/null
+++ b/snapd/snapdcontrol.h
@@ -0,0 +1,91 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * *
+ * Copyright (C) 2017 Simon Stürz . *
+ * *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef SNAPDCONTROL_H
+#define SNAPDCONTROL_H
+
+#include
+#include
+
+#include "plugin/device.h"
+#include "snapdconnection.h"
+
+class SnapdControl : public QObject
+{
+ Q_OBJECT
+public:
+ explicit SnapdControl(Device *device, QObject *parent = nullptr);
+
+ Device *device();
+
+ bool available() const;
+ bool connected() const;
+ bool enabled() const;
+
+private:
+ Device *m_device = nullptr;
+ SnapdConnection *m_snapConnection = nullptr;
+
+ QString m_snapdSocketPath;
+ bool m_enabled = true;
+
+ QList m_watchingChanges;
+
+ // Update calls
+ void loadSystemInfo();
+ void loadSnapList();
+ void loadRunningChanges();
+ void loadChange(const int &change);
+
+ void processChange(const QVariantMap &changeMap);
+ bool validAsyncResponse(const QVariantMap &responseMap);
+
+private slots:
+ void onConnectedChanged(const bool &connected);
+
+ // Response handler for different requests
+ void onLoadSystemInfoFinished();
+ void onLoadSnapListFinished();
+ void onLoadRunningChangesFinished();
+ void onLoadChangeFinished();
+
+ void onSnapRefreshFinished();
+ void onSnapRevertFinished();
+ void onCheckForUpdatesFinished();
+ void onChangeSnapChannelFinished();
+
+signals:
+ void snapListUpdated(const QVariantList &snapList);
+
+public slots:
+ void enable();
+ void disable();
+
+ // Snapd request calls
+ void update();
+ void snapRefresh();
+ void checkForUpdates();
+ void snapRevert(const QString &snapName);
+ void changeSnapChannel(const QString &snapName, const QString &channel);
+};
+
+#endif // SNAPDCONTROL_H
diff --git a/snapd/snapdreply.cpp b/snapd/snapdreply.cpp
new file mode 100644
index 00000000..4b0ee290
--- /dev/null
+++ b/snapd/snapdreply.cpp
@@ -0,0 +1,95 @@
+#include "snapdreply.h"
+
+QString SnapdReply::requestPath() const
+{
+ return m_requestPath;
+}
+
+QString SnapdReply::requestMethod() const
+{
+ return m_requestMethod;
+}
+
+QByteArray SnapdReply::requestRawMessage() const
+{
+ return m_requestRawMessage;
+}
+
+int SnapdReply::statusCode() const
+{
+ return m_statusCode;
+}
+
+QString SnapdReply::statusMessage() const
+{
+ return m_statusMessage;
+}
+
+QHash SnapdReply::header() const
+{
+ return m_header;
+}
+
+QVariantMap SnapdReply::dataMap() const
+{
+ return m_dataMap;
+}
+
+bool SnapdReply::isFinished() const
+{
+ return m_isFinished;
+}
+
+bool SnapdReply::isValid() const
+{
+ return m_valid;
+}
+
+SnapdReply::SnapdReply(QObject *parent) :
+ QObject(parent)
+{
+
+}
+
+void SnapdReply::setRequestPath(const QString &requestPath)
+{
+ m_requestPath = requestPath;
+}
+
+void SnapdReply::setRequestMethod(const QString &requestMethod)
+{
+ m_requestMethod = requestMethod;
+}
+
+void SnapdReply::setRequestRawMessage(const QByteArray &rawMessage)
+{
+ m_requestRawMessage = rawMessage;
+}
+
+void SnapdReply::setStatusCode(const int &statusCode)
+{
+ m_statusCode = statusCode;
+}
+
+void SnapdReply::setStatusMessage(const QString &statusMessage)
+{
+ m_statusMessage = statusMessage;
+}
+
+void SnapdReply::setHeader(const QHash header)
+{
+ m_header = header;
+}
+
+void SnapdReply::setDataMap(const QVariantMap &dataMap)
+{
+ m_dataMap = dataMap;
+}
+
+void SnapdReply::setFinished(const bool &valid)
+{
+ m_isFinished = true;
+ m_valid = valid;
+
+ emit finished();
+}
diff --git a/snapd/snapdreply.h b/snapd/snapdreply.h
new file mode 100644
index 00000000..0251335f
--- /dev/null
+++ b/snapd/snapdreply.h
@@ -0,0 +1,60 @@
+#ifndef SNAPDREPLY_H
+#define SNAPDREPLY_H
+
+#include
+#include
+#include
+
+class SnapdReply : public QObject
+{
+ Q_OBJECT
+
+ friend class SnapdConnection;
+
+public:
+ // Request
+ QString requestPath() const;
+ QString requestMethod() const;
+ QByteArray requestRawMessage() const;
+
+ // Response
+ int statusCode() const;
+ QString statusMessage() const;
+ QHash header() const;
+ QVariantMap dataMap() const;
+
+ bool isFinished() const;
+ bool isValid() const;
+
+private:
+ explicit SnapdReply(QObject *parent = nullptr);
+
+ QString m_requestPath;
+ QString m_requestMethod;
+ QByteArray m_requestRawMessage;
+
+ int m_statusCode;
+ QString m_statusMessage;
+ QHash m_header;
+ QVariantMap m_dataMap;
+
+ bool m_isFinished = false;
+ bool m_valid = false;
+
+ // Methods for SnapdConnection
+ void setRequestPath(const QString &requestPath);
+ void setRequestMethod(const QString &requestMethod);
+ void setRequestRawMessage(const QByteArray &rawMessage);
+
+ void setStatusCode(const int &statusCode);
+ void setStatusMessage(const QString &statusMessage);
+ void setHeader(const QHash header);
+ void setDataMap(const QVariantMap &dataMap);
+ void setFinished(const bool &valid = true);
+
+signals:
+ void finished();
+
+};
+
+#endif // SNAPDREPLY_H
diff --git a/snapd/translations/de_DE.ts b/snapd/translations/de_DE.ts
new file mode 100644
index 00000000..21c86613
--- /dev/null
+++ b/snapd/translations/de_DE.ts
@@ -0,0 +1,199 @@
+
+
+
+
+ Snapd
+
+
+ Snapd
+ The name of the plugin Snapd (b82bce59-59bf-48b3-b781-54a6f45800f3)
+ Snapd
+
+
+
+ Advanced mode
+ The name of the paramType (017fe4c5-fc41-41fe-8e67-08fdaccb89ea) of Snapd
+ Fortgeschrittener Modus
+
+
+
+ Canonical
+ The name of the vendor (60582ddf-32ea-4fcd-a6f2-f3beaaf21517)
+ Canonical
+
+
+
+ System update available changed
+ The name of the autocreated EventType (a6b1d24b-d523-4516-9bce-5b467e5e09b2)
+ Systemupdate Verfügbarkeit geändert
+
+
+
+ System update available
+ The name of the ParamType of StateType (a6b1d24b-d523-4516-9bce-5b467e5e09b2) of DeviceClass Update manager
+ Systemupdate verfügbar
+
+
+
+ Last automatic system update time changed
+ The name of the autocreated EventType (c671545a-6bde-4c08-8e37-0d256841a3a5)
+ Letztes automatisches Systemupdate geändert
+
+
+
+ Last automatic system update
+ The name of the ParamType of StateType (c671545a-6bde-4c08-8e37-0d256841a3a5) of DeviceClass Update manager
+ Letztes automatisches Systemupdate
+
+
+
+ Next automatic system update time changed
+ The name of the autocreated EventType (122c2423-a1d9-400f-80f8-b1f798975914)
+ Nächstes automatisches Systemupdate geändert
+
+
+
+ Next automatic system update
+ The name of the ParamType of StateType (122c2423-a1d9-400f-80f8-b1f798975914) of DeviceClass Update manager
+ Nächstes automatisches Systemupdate
+
+
+
+ ID
+ The name of the paramType (9afb98fb-f717-4f4c-8009-1a6514054c5f) of Snap
+ ID
+
+
+
+ Summary
+ The name of the paramType (12b9a65f-970b-49b5-b1d0-1625fc6d8758) of Snap
+ Zusammenfassung
+
+
+
+ Description
+ The name of the paramType (fe24c61b-e154-4259-b7ca-6f0602e9d1c3) of Snap
+ Beschreibung
+
+
+
+ Developer
+ The name of the paramType (76ead9c5-0a18-40a2-b31d-f6bb6dfea0a5) of Snap
+ Entwickler
+
+
+
+ Channel
+ The name of the ParamType of StateType (7be2b61e-3f59-4b92-b2bb-50d027bb92ff) of DeviceClass Snap
+ Kanal
+
+
+
+ Set channel
+ The name of the autocreated ActionType (7be2b61e-3f59-4b92-b2bb-50d027bb92ff)
+ Setze Kanal
+
+
+
+ Version changed
+ The name of the autocreated EventType (532a95f3-db29-427e-bb32-d5a22029e586)
+ Version geändert
+
+
+
+ Version
+ The name of the ParamType of StateType (532a95f3-db29-427e-bb32-d5a22029e586) of DeviceClass Snap
+ Version
+
+
+
+ Revision changed
+ The name of the autocreated EventType (f26a6404-e011-11e7-9224-2350048461eb)
+ Revision geändert
+
+
+
+ Revision
+ The name of the ParamType of StateType (f26a6404-e011-11e7-9224-2350048461eb) of DeviceClass Snap
+ Revision
+
+
+
+ Rollback to previous version
+ The name of the ActionType e061dee6-62fc-45cc-9c9f-403c2be52939 of deviceClass Snap
+ Zurücksetzen auf vorhergehende Version
+
+
+
+ Start update
+ The name of the ActionType 45626b75-f09d-4dd1-b6c4-ee33201b47b0 of deviceClass Update manager
+ Starte Systemupdate
+
+
+
+ Update manager
+ The name of the DeviceClass (d90cda58-4d8c-4b7f-a982-38e56a95b72a)
+ Update Manager
+
+
+
+ Update manager available changed
+ The name of the autocreated EventType (6b662b3e-fd12-4f24-be77-aec066f16d8c)
+ Update Manager Verfügbarkeit geändert
+
+
+
+ Update manager available
+ The name of the ParamType of StateType (6b662b3e-fd12-4f24-be77-aec066f16d8c) of DeviceClass Update manager
+ Update Manager verfügbar
+
+
+
+ Snap
+ The name of the DeviceClass (ff0840d7-fcfc-4403-9d9f-301610d5a437)
+ Snap
+
+
+
+ System update running changed
+ The name of the autocreated EventType (01ca7a22-5607-4c5e-a465-a2ae7e8b529c)
+ Systemupdate aktiv geändert
+
+
+
+ System update running
+ The name of the ParamType of StateType (01ca7a22-5607-4c5e-a465-a2ae7e8b529c) of DeviceClass Update manager
+ Systemupdate aktiv
+
+
+
+ Status changed
+ The name of the autocreated EventType (4987aca3-3916-4cb3-938f-df6c99d04dbf)
+ Status geändert
+
+
+
+ Status
+ The name of the ParamType of StateType (4987aca3-3916-4cb3-938f-df6c99d04dbf) of DeviceClass Update manager
+ Status
+
+
+
+ Name
+ The name of the paramType (4f38614d-8be0-48dc-a24d-cee9ff1f2a89) of Snap
+ Name
+
+
+
+ Channel changed
+ The name of the autocreated EventType (7be2b61e-3f59-4b92-b2bb-50d027bb92ff)
+ Kanal geändert
+
+
+
+ Check for updates
+ The name of the ActionType 4738f2c9-666e-45b9-91d3-7bcbf722b669 of deviceClass Update manager
+ Nach Aktualisierungen suchen
+
+
+
diff --git a/snapd/translations/en_US.ts b/snapd/translations/en_US.ts
new file mode 100644
index 00000000..872b2680
--- /dev/null
+++ b/snapd/translations/en_US.ts
@@ -0,0 +1,199 @@
+
+
+
+
+ Snapd
+
+
+ Snapd
+ The name of the plugin Snapd (b82bce59-59bf-48b3-b781-54a6f45800f3)
+
+
+
+
+ Advanced mode
+ The name of the paramType (017fe4c5-fc41-41fe-8e67-08fdaccb89ea) of Snapd
+
+
+
+
+ Canonical
+ The name of the vendor (60582ddf-32ea-4fcd-a6f2-f3beaaf21517)
+
+
+
+
+ System update available changed
+ The name of the autocreated EventType (a6b1d24b-d523-4516-9bce-5b467e5e09b2)
+
+
+
+
+ System update available
+ The name of the ParamType of StateType (a6b1d24b-d523-4516-9bce-5b467e5e09b2) of DeviceClass Update manager
+
+
+
+
+ Last automatic system update time changed
+ The name of the autocreated EventType (c671545a-6bde-4c08-8e37-0d256841a3a5)
+
+
+
+
+ Last automatic system update
+ The name of the ParamType of StateType (c671545a-6bde-4c08-8e37-0d256841a3a5) of DeviceClass Update manager
+
+
+
+
+ Next automatic system update time changed
+ The name of the autocreated EventType (122c2423-a1d9-400f-80f8-b1f798975914)
+
+
+
+
+ Next automatic system update
+ The name of the ParamType of StateType (122c2423-a1d9-400f-80f8-b1f798975914) of DeviceClass Update manager
+
+
+
+
+ ID
+ The name of the paramType (9afb98fb-f717-4f4c-8009-1a6514054c5f) of Snap
+
+
+
+
+ Summary
+ The name of the paramType (12b9a65f-970b-49b5-b1d0-1625fc6d8758) of Snap
+
+
+
+
+ Description
+ The name of the paramType (fe24c61b-e154-4259-b7ca-6f0602e9d1c3) of Snap
+
+
+
+
+ Developer
+ The name of the paramType (76ead9c5-0a18-40a2-b31d-f6bb6dfea0a5) of Snap
+
+
+
+
+ Channel
+ The name of the ParamType of StateType (7be2b61e-3f59-4b92-b2bb-50d027bb92ff) of DeviceClass Snap
+
+
+
+
+ Set channel
+ The name of the autocreated ActionType (7be2b61e-3f59-4b92-b2bb-50d027bb92ff)
+
+
+
+
+ Version changed
+ The name of the autocreated EventType (532a95f3-db29-427e-bb32-d5a22029e586)
+
+
+
+
+ Version
+ The name of the ParamType of StateType (532a95f3-db29-427e-bb32-d5a22029e586) of DeviceClass Snap
+
+
+
+
+ Revision changed
+ The name of the autocreated EventType (f26a6404-e011-11e7-9224-2350048461eb)
+
+
+
+
+ Revision
+ The name of the ParamType of StateType (f26a6404-e011-11e7-9224-2350048461eb) of DeviceClass Snap
+
+
+
+
+ Rollback to previous version
+ The name of the ActionType e061dee6-62fc-45cc-9c9f-403c2be52939 of deviceClass Snap
+
+
+
+
+ Start update
+ The name of the ActionType 45626b75-f09d-4dd1-b6c4-ee33201b47b0 of deviceClass Update manager
+
+
+
+
+ Update manager
+ The name of the DeviceClass (d90cda58-4d8c-4b7f-a982-38e56a95b72a)
+
+
+
+
+ Update manager available changed
+ The name of the autocreated EventType (6b662b3e-fd12-4f24-be77-aec066f16d8c)
+
+
+
+
+ Update manager available
+ The name of the ParamType of StateType (6b662b3e-fd12-4f24-be77-aec066f16d8c) of DeviceClass Update manager
+
+
+
+
+ Snap
+ The name of the DeviceClass (ff0840d7-fcfc-4403-9d9f-301610d5a437)
+
+
+
+
+ System update running changed
+ The name of the autocreated EventType (01ca7a22-5607-4c5e-a465-a2ae7e8b529c)
+
+
+
+
+ System update running
+ The name of the ParamType of StateType (01ca7a22-5607-4c5e-a465-a2ae7e8b529c) of DeviceClass Update manager
+
+
+
+
+ Status changed
+ The name of the autocreated EventType (4987aca3-3916-4cb3-938f-df6c99d04dbf)
+
+
+
+
+ Status
+ The name of the ParamType of StateType (4987aca3-3916-4cb3-938f-df6c99d04dbf) of DeviceClass Update manager
+
+
+
+
+ Name
+ The name of the paramType (4f38614d-8be0-48dc-a24d-cee9ff1f2a89) of Snap
+
+
+
+
+ Channel changed
+ The name of the autocreated EventType (7be2b61e-3f59-4b92-b2bb-50d027bb92ff)
+
+
+
+
+ Check for updates
+ The name of the ActionType 4738f2c9-666e-45b9-91d3-7bcbf722b669 of deviceClass Update manager
+
+
+
+