Add snapd plugin

master
Simon Stürz 2018-01-24 10:02:07 +01:00 committed by Michael Zanetti
parent 7976a67424
commit 42636ea14f
15 changed files with 2014 additions and 0 deletions

16
debian/control vendored
View File

@ -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

1
debian/guh-plugin-snapd.install.in vendored Normal file
View File

@ -0,0 +1 @@
usr/lib/@DEB_HOST_MULTIARCH@/guh/plugins/libguh_devicepluginsnapd.so

View File

@ -31,6 +31,7 @@ PLUGIN_DIRS = \
denon \
avahimonitor \
gpio \
snapd \
CONFIG+=all

259
snapd/devicepluginsnapd.cpp Normal file
View File

@ -0,0 +1,259 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2017 Simon Stürz <simon.stuerz@guh.io *
* *
* This file is part of guh. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#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<DeviceDescriptor>() << 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 &paramTypeId, 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<DeviceDescriptor>() << 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());
}
}
}

71
snapd/devicepluginsnapd.h Normal file
View File

@ -0,0 +1,71 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2017 Simon Stürz <simon.stuerz@guh.io *
* *
* This file is part of guh. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef DEVICEPLUGINSNAPD_H
#define DEVICEPLUGINSNAPD_H
#include "devicemanager.h"
#include "plugin/deviceplugin.h"
#include "plugintimer.h"
#include <QDebug>
#include <QNetworkInterface>
#include <QProcess>
#include <QUrlQuery>
#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<QString, Device *> m_snapDevices;
private slots:
void onPluginConfigurationChanged(const ParamTypeId &paramTypeId, const QVariant &value);
void onRefreshTimer();
void onUpdateTimer();
void onSnapListUpdated(const QVariantList &snapList);
};
#endif // DEVICEPLUGINSNAPD_H

View File

@ -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": "-"
}
]
}
]
}
]
}

21
snapd/snapd.pro Normal file
View File

@ -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

327
snapd/snapdconnection.cpp Normal file
View File

@ -0,0 +1,327 @@
#include "snapdconnection.h"
#include "extern-plugininfo.h"
#include <QJsonDocument>
#include <QJsonParseError>
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<QString, QString> 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();
}
}
}

54
snapd/snapdconnection.h Normal file
View File

@ -0,0 +1,54 @@
#ifndef SNAPDCONNECTION_H
#define SNAPDCONNECTION_H
#include <QQueue>
#include <QObject>
#include <QLocalSocket>
#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<SnapdReply *> 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

424
snapd/snapdcontrol.cpp Normal file
View File

@ -0,0 +1,424 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2017 Simon Stürz <simon.stuerz@guh.io *
* *
* This file is part of guh. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "snapdcontrol.h"
#include "extern-plugininfo.h"
#include <QFileInfo>
#include <QDateTime>
#include <QJsonDocument>
#include <QJsonParseError>
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<SnapdReply *>(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<SnapdReply *>(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<SnapdReply *>(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<SnapdReply *>(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<SnapdReply *>(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<SnapdReply *>(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<SnapdReply *>(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<SnapdReply *>(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);
}

91
snapd/snapdcontrol.h Normal file
View File

@ -0,0 +1,91 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2017 Simon Stürz <simon.stuerz@guh.io *
* *
* This file is part of guh. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SNAPDCONTROL_H
#define SNAPDCONTROL_H
#include <QObject>
#include <QLocalSocket>
#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<int> 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

95
snapd/snapdreply.cpp Normal file
View File

@ -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<QString, QString> 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<QString, QString> 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();
}

60
snapd/snapdreply.h Normal file
View File

@ -0,0 +1,60 @@
#ifndef SNAPDREPLY_H
#define SNAPDREPLY_H
#include <QHash>
#include <QObject>
#include <QVariantMap>
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<QString, QString> 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<QString, QString> 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<QString, QString> header);
void setDataMap(const QVariantMap &dataMap);
void setFinished(const bool &valid = true);
signals:
void finished();
};
#endif // SNAPDREPLY_H

199
snapd/translations/de_DE.ts Normal file
View File

@ -0,0 +1,199 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="de_DE">
<context>
<name>Snapd</name>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="65"/>
<source>Snapd</source>
<extracomment>The name of the plugin Snapd (b82bce59-59bf-48b3-b781-54a6f45800f3)</extracomment>
<translation>Snapd</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="68"/>
<source>Advanced mode</source>
<extracomment>The name of the paramType (017fe4c5-fc41-41fe-8e67-08fdaccb89ea) of Snapd</extracomment>
<translation>Fortgeschrittener Modus</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="71"/>
<source>Canonical</source>
<extracomment>The name of the vendor (60582ddf-32ea-4fcd-a6f2-f3beaaf21517)</extracomment>
<translation>Canonical</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="83"/>
<source>System update available changed</source>
<extracomment>The name of the autocreated EventType (a6b1d24b-d523-4516-9bce-5b467e5e09b2)</extracomment>
<translation>Systemupdate Verfügbarkeit geändert</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="86"/>
<source>System update available</source>
<extracomment>The name of the ParamType of StateType (a6b1d24b-d523-4516-9bce-5b467e5e09b2) of DeviceClass Update manager</extracomment>
<translation>Systemupdate verfügbar</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="95"/>
<source>Last automatic system update time changed</source>
<extracomment>The name of the autocreated EventType (c671545a-6bde-4c08-8e37-0d256841a3a5)</extracomment>
<translation>Letztes automatisches Systemupdate geändert</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="98"/>
<source>Last automatic system update</source>
<extracomment>The name of the ParamType of StateType (c671545a-6bde-4c08-8e37-0d256841a3a5) of DeviceClass Update manager</extracomment>
<translation>Letztes automatisches Systemupdate</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="101"/>
<source>Next automatic system update time changed</source>
<extracomment>The name of the autocreated EventType (122c2423-a1d9-400f-80f8-b1f798975914)</extracomment>
<translation>Nächstes automatisches Systemupdate geändert</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="104"/>
<source>Next automatic system update</source>
<extracomment>The name of the ParamType of StateType (122c2423-a1d9-400f-80f8-b1f798975914) of DeviceClass Update manager</extracomment>
<translation>Nächstes automatisches Systemupdate</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="125"/>
<source>ID</source>
<extracomment>The name of the paramType (9afb98fb-f717-4f4c-8009-1a6514054c5f) of Snap</extracomment>
<translation>ID</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="128"/>
<source>Summary</source>
<extracomment>The name of the paramType (12b9a65f-970b-49b5-b1d0-1625fc6d8758) of Snap</extracomment>
<translation>Zusammenfassung</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="131"/>
<source>Description</source>
<extracomment>The name of the paramType (fe24c61b-e154-4259-b7ca-6f0602e9d1c3) of Snap</extracomment>
<translation>Beschreibung</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="134"/>
<source>Developer</source>
<extracomment>The name of the paramType (76ead9c5-0a18-40a2-b31d-f6bb6dfea0a5) of Snap</extracomment>
<translation>Entwickler</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="140"/>
<source>Channel</source>
<extracomment>The name of the ParamType of StateType (7be2b61e-3f59-4b92-b2bb-50d027bb92ff) of DeviceClass Snap</extracomment>
<translation>Kanal</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="143"/>
<source>Set channel</source>
<extracomment>The name of the autocreated ActionType (7be2b61e-3f59-4b92-b2bb-50d027bb92ff)</extracomment>
<translation>Setze Kanal</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="146"/>
<source>Version changed</source>
<extracomment>The name of the autocreated EventType (532a95f3-db29-427e-bb32-d5a22029e586)</extracomment>
<translation>Version geändert</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="149"/>
<source>Version</source>
<extracomment>The name of the ParamType of StateType (532a95f3-db29-427e-bb32-d5a22029e586) of DeviceClass Snap</extracomment>
<translation>Version</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="152"/>
<source>Revision changed</source>
<extracomment>The name of the autocreated EventType (f26a6404-e011-11e7-9224-2350048461eb)</extracomment>
<translation>Revision geändert</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="155"/>
<source>Revision</source>
<extracomment>The name of the ParamType of StateType (f26a6404-e011-11e7-9224-2350048461eb) of DeviceClass Snap</extracomment>
<translation>Revision</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="158"/>
<source>Rollback to previous version</source>
<extracomment>The name of the ActionType e061dee6-62fc-45cc-9c9f-403c2be52939 of deviceClass Snap</extracomment>
<translation>Zurücksetzen auf vorhergehende Version</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="113"/>
<source>Start update</source>
<extracomment>The name of the ActionType 45626b75-f09d-4dd1-b6c4-ee33201b47b0 of deviceClass Update manager</extracomment>
<translation>Starte Systemupdate</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="74"/>
<source>Update manager</source>
<extracomment>The name of the DeviceClass (d90cda58-4d8c-4b7f-a982-38e56a95b72a)</extracomment>
<translation>Update Manager</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="77"/>
<source>Update manager available changed</source>
<extracomment>The name of the autocreated EventType (6b662b3e-fd12-4f24-be77-aec066f16d8c)</extracomment>
<translation>Update Manager Verfügbarkeit geändert</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="80"/>
<source>Update manager available</source>
<extracomment>The name of the ParamType of StateType (6b662b3e-fd12-4f24-be77-aec066f16d8c) of DeviceClass Update manager</extracomment>
<translation>Update Manager verfügbar</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="119"/>
<source>Snap</source>
<extracomment>The name of the DeviceClass (ff0840d7-fcfc-4403-9d9f-301610d5a437)</extracomment>
<translation>Snap</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="89"/>
<source>System update running changed</source>
<extracomment>The name of the autocreated EventType (01ca7a22-5607-4c5e-a465-a2ae7e8b529c)</extracomment>
<translation>Systemupdate aktiv geändert</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="92"/>
<source>System update running</source>
<extracomment>The name of the ParamType of StateType (01ca7a22-5607-4c5e-a465-a2ae7e8b529c) of DeviceClass Update manager</extracomment>
<translation>Systemupdate aktiv</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="107"/>
<source>Status changed</source>
<extracomment>The name of the autocreated EventType (4987aca3-3916-4cb3-938f-df6c99d04dbf)</extracomment>
<translation>Status geändert</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="110"/>
<source>Status</source>
<extracomment>The name of the ParamType of StateType (4987aca3-3916-4cb3-938f-df6c99d04dbf) of DeviceClass Update manager</extracomment>
<translation>Status</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="122"/>
<source>Name</source>
<extracomment>The name of the paramType (4f38614d-8be0-48dc-a24d-cee9ff1f2a89) of Snap</extracomment>
<translation>Name</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="137"/>
<source>Channel changed</source>
<extracomment>The name of the autocreated EventType (7be2b61e-3f59-4b92-b2bb-50d027bb92ff)</extracomment>
<translation>Kanal geändert</translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="116"/>
<source>Check for updates</source>
<extracomment>The name of the ActionType 4738f2c9-666e-45b9-91d3-7bcbf722b669 of deviceClass Update manager</extracomment>
<translation>Nach Aktualisierungen suchen</translation>
</message>
</context>
</TS>

199
snapd/translations/en_US.ts Normal file
View File

@ -0,0 +1,199 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>Snapd</name>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="65"/>
<source>Snapd</source>
<extracomment>The name of the plugin Snapd (b82bce59-59bf-48b3-b781-54a6f45800f3)</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="68"/>
<source>Advanced mode</source>
<extracomment>The name of the paramType (017fe4c5-fc41-41fe-8e67-08fdaccb89ea) of Snapd</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="71"/>
<source>Canonical</source>
<extracomment>The name of the vendor (60582ddf-32ea-4fcd-a6f2-f3beaaf21517)</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="83"/>
<source>System update available changed</source>
<extracomment>The name of the autocreated EventType (a6b1d24b-d523-4516-9bce-5b467e5e09b2)</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="86"/>
<source>System update available</source>
<extracomment>The name of the ParamType of StateType (a6b1d24b-d523-4516-9bce-5b467e5e09b2) of DeviceClass Update manager</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="95"/>
<source>Last automatic system update time changed</source>
<extracomment>The name of the autocreated EventType (c671545a-6bde-4c08-8e37-0d256841a3a5)</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="98"/>
<source>Last automatic system update</source>
<extracomment>The name of the ParamType of StateType (c671545a-6bde-4c08-8e37-0d256841a3a5) of DeviceClass Update manager</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="101"/>
<source>Next automatic system update time changed</source>
<extracomment>The name of the autocreated EventType (122c2423-a1d9-400f-80f8-b1f798975914)</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="104"/>
<source>Next automatic system update</source>
<extracomment>The name of the ParamType of StateType (122c2423-a1d9-400f-80f8-b1f798975914) of DeviceClass Update manager</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="125"/>
<source>ID</source>
<extracomment>The name of the paramType (9afb98fb-f717-4f4c-8009-1a6514054c5f) of Snap</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="128"/>
<source>Summary</source>
<extracomment>The name of the paramType (12b9a65f-970b-49b5-b1d0-1625fc6d8758) of Snap</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="131"/>
<source>Description</source>
<extracomment>The name of the paramType (fe24c61b-e154-4259-b7ca-6f0602e9d1c3) of Snap</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="134"/>
<source>Developer</source>
<extracomment>The name of the paramType (76ead9c5-0a18-40a2-b31d-f6bb6dfea0a5) of Snap</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="140"/>
<source>Channel</source>
<extracomment>The name of the ParamType of StateType (7be2b61e-3f59-4b92-b2bb-50d027bb92ff) of DeviceClass Snap</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="143"/>
<source>Set channel</source>
<extracomment>The name of the autocreated ActionType (7be2b61e-3f59-4b92-b2bb-50d027bb92ff)</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="146"/>
<source>Version changed</source>
<extracomment>The name of the autocreated EventType (532a95f3-db29-427e-bb32-d5a22029e586)</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="149"/>
<source>Version</source>
<extracomment>The name of the ParamType of StateType (532a95f3-db29-427e-bb32-d5a22029e586) of DeviceClass Snap</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="152"/>
<source>Revision changed</source>
<extracomment>The name of the autocreated EventType (f26a6404-e011-11e7-9224-2350048461eb)</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="155"/>
<source>Revision</source>
<extracomment>The name of the ParamType of StateType (f26a6404-e011-11e7-9224-2350048461eb) of DeviceClass Snap</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="158"/>
<source>Rollback to previous version</source>
<extracomment>The name of the ActionType e061dee6-62fc-45cc-9c9f-403c2be52939 of deviceClass Snap</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="113"/>
<source>Start update</source>
<extracomment>The name of the ActionType 45626b75-f09d-4dd1-b6c4-ee33201b47b0 of deviceClass Update manager</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="74"/>
<source>Update manager</source>
<extracomment>The name of the DeviceClass (d90cda58-4d8c-4b7f-a982-38e56a95b72a)</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="77"/>
<source>Update manager available changed</source>
<extracomment>The name of the autocreated EventType (6b662b3e-fd12-4f24-be77-aec066f16d8c)</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="80"/>
<source>Update manager available</source>
<extracomment>The name of the ParamType of StateType (6b662b3e-fd12-4f24-be77-aec066f16d8c) of DeviceClass Update manager</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="119"/>
<source>Snap</source>
<extracomment>The name of the DeviceClass (ff0840d7-fcfc-4403-9d9f-301610d5a437)</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="89"/>
<source>System update running changed</source>
<extracomment>The name of the autocreated EventType (01ca7a22-5607-4c5e-a465-a2ae7e8b529c)</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="92"/>
<source>System update running</source>
<extracomment>The name of the ParamType of StateType (01ca7a22-5607-4c5e-a465-a2ae7e8b529c) of DeviceClass Update manager</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="107"/>
<source>Status changed</source>
<extracomment>The name of the autocreated EventType (4987aca3-3916-4cb3-938f-df6c99d04dbf)</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="110"/>
<source>Status</source>
<extracomment>The name of the ParamType of StateType (4987aca3-3916-4cb3-938f-df6c99d04dbf) of DeviceClass Update manager</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="122"/>
<source>Name</source>
<extracomment>The name of the paramType (4f38614d-8be0-48dc-a24d-cee9ff1f2a89) of Snap</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="137"/>
<source>Channel changed</source>
<extracomment>The name of the autocreated EventType (7be2b61e-3f59-4b92-b2bb-50d027bb92ff)</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-guh-plugins-Desktop-Debug/snapd/plugininfo.h" line="116"/>
<source>Check for updates</source>
<extracomment>The name of the ActionType 4738f2c9-666e-45b9-91d3-7bcbf722b669 of deviceClass Update manager</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>