diff --git a/snapd/devicepluginsnapd.cpp b/snapd/devicepluginsnapd.cpp index 3d011560..91bb6d50 100644 --- a/snapd/devicepluginsnapd.cpp +++ b/snapd/devicepluginsnapd.cpp @@ -30,19 +30,26 @@ DevicePluginSnapd::DevicePluginSnapd() } +DevicePluginSnapd::~DevicePluginSnapd() +{ + hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer); + hardwareManager()->pluginTimerManager()->unregisterTimer(m_updateTimer); +} + void DevicePluginSnapd::init() { - // Check advanced mode + // Initialize plugin configurations m_advancedMode = configValue(SnapdAdvancedModeParamTypeId).toBool(); + m_refreshTime = configValue(SnapdRefreshScheduleParamTypeId).toInt(); connect(this, &DevicePluginSnapd::configValueChanged, this, &DevicePluginSnapd::onPluginConfigurationChanged); - // Setup timers + // Refresh timer for snapd checks 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); + // Check all 4 hours if there is an update available + m_updateTimer = hardwareManager()->pluginTimerManager()->registerTimer(14400); + connect(m_updateTimer, &PluginTimer::timeout, this, &DevicePluginSnapd::onUpdateTimer); } void DevicePluginSnapd::startMonitoringAutoDevices() @@ -69,9 +76,9 @@ void DevicePluginSnapd::startMonitoringAutoDevices() void DevicePluginSnapd::postSetupDevice(Device *device) { - if (m_snapdControl && m_snapdControl->device() == device) + if (m_snapdControl && m_snapdControl->device() == device) { m_snapdControl->update(); - + } } void DevicePluginSnapd::deviceRemoved(Device *device) @@ -107,6 +114,7 @@ DeviceManager::DeviceSetupStatus DevicePluginSnapd::setupDevice(Device *device) } m_snapdControl = new SnapdControl(device, this); + m_snapdControl->setPreferedRefreshTime(configValue(SnapdRefreshScheduleParamTypeId).toInt()); connect(m_snapdControl, &SnapdControl::snapListUpdated, this, &DevicePluginSnapd::onSnapListUpdated); } else if (device->deviceClassId() == snapDeviceClassId) { @@ -171,6 +179,9 @@ DeviceManager::DeviceError DevicePluginSnapd::executeAction(Device *device, cons void DevicePluginSnapd::onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value) { + qCDebug(dcSnapd()) << "Plugin configuration changed"; + + // Check advanced mode if (paramTypeId == SnapdAdvancedModeParamTypeId) { qCDebug(dcSnapd()) << "Advanced mode" << (value.toBool() ? "enabled." : "disabled."); m_advancedMode = value.toBool(); @@ -189,6 +200,16 @@ void DevicePluginSnapd::onPluginConfigurationChanged(const ParamTypeId ¶mTyp m_snapdControl->update(); } } + + // Check refresh schedule + if (paramTypeId == SnapdRefreshScheduleParamTypeId) { + if (!m_snapdControl) + return; + + m_refreshTime = value.toInt(); + qCDebug(dcSnapd()) << "Refresh schedule start time" << QTime(m_refreshTime, 0, 0).toString("hh:mm"); + m_snapdControl->setPreferedRefreshTime(m_refreshTime); + } } void DevicePluginSnapd::onRefreshTimer() diff --git a/snapd/devicepluginsnapd.h b/snapd/devicepluginsnapd.h index b55b1792..c08d3899 100644 --- a/snapd/devicepluginsnapd.h +++ b/snapd/devicepluginsnapd.h @@ -42,6 +42,8 @@ class DevicePluginSnapd: public DevicePlugin { public: explicit DevicePluginSnapd(); + ~DevicePluginSnapd(); + void init() override; void startMonitoringAutoDevices() override; void postSetupDevice(Device *device) override; @@ -56,6 +58,7 @@ private: PluginTimer *m_updateTimer = nullptr; bool m_advancedMode = false; + int m_refreshTime = 2; // Snap list for faster access (snap id, device) QHash m_snapDevices; diff --git a/snapd/devicepluginsnapd.json b/snapd/devicepluginsnapd.json index 152b2643..7a030a5e 100644 --- a/snapd/devicepluginsnapd.json +++ b/snapd/devicepluginsnapd.json @@ -9,6 +9,16 @@ "displayName": "Advanced mode", "type": "bool", "defaultValue": false + }, + { + "id": "d2e697d1-9a68-4666-bf40-8d70fa694eec", + "name": "refreshSchedule", + "displayName": "Automatic daily refresh schedule", + "type": "int", + "unit": "Hours", + "minValue": 0, + "maxValue": 23, + "defaultValue": 2 } ], "vendors": [ diff --git a/snapd/snapdconnection.cpp b/snapd/snapdconnection.cpp index ff21caa4..f42e837c 100644 --- a/snapd/snapdconnection.cpp +++ b/snapd/snapdconnection.cpp @@ -39,6 +39,11 @@ SnapdConnection::SnapdConnection(QObject *parent) : connect(this, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(onError(QLocalSocket::LocalSocketError))); } +SnapdConnection::~SnapdConnection() +{ + close(); +} + SnapdReply *SnapdConnection::get(const QString &path, QObject *parent) { SnapdReply *reply = new SnapdReply(parent); @@ -70,6 +75,22 @@ SnapdReply *SnapdConnection::post(const QString &path, const QByteArray &payload return reply; } +SnapdReply *SnapdConnection::put(const QString &path, const QByteArray &payload, QObject *parent) +{ + SnapdReply *reply = new SnapdReply(parent); + reply->setRequestPath(path); + reply->setRequestMethod("PUT"); + QByteArray header = createRequestHeader("PUT", path, payload); + reply->setRequestRawMessage(header.append(payload)); + + // Enqueue the new reply + m_replyQueue.enqueue(reply); + sendNextRequest(); + + // Note: the caller owns the object now + return reply; +} + bool SnapdConnection::isConnected() const { return m_connected; @@ -95,7 +116,7 @@ void SnapdConnection::setConnected(const bool &connected) while (!m_replyQueue.isEmpty()) { QPointer reply = m_replyQueue.dequeue(); if (!reply.isNull()) { - reply->setFinished(false); + reply->deleteLater(); } } } else { @@ -258,8 +279,10 @@ void SnapdConnection::sendNextRequest() if (m_debug) qCDebug(dcSnapd()) << "-->" << reply->requestMethod() << reply->requestPath(); - // If write failes, the reply is finished invalid and the owner has to delete it - if (write(reply->requestRawMessage()) < 0) { + // Send current reply request. If write failes, the reply is finished invalid and the owner has to delete it + qint64 bytesWritten = write(reply->requestRawMessage()); + if (bytesWritten < 0) { + qCWarning(dcSnapd()) << "Could not write request data" << reply->requestMethod() << reply->requestMethod(); m_currentReply->setFinished(false); m_currentReply = nullptr; sendNextRequest(); diff --git a/snapd/snapdconnection.h b/snapd/snapdconnection.h index a90a97b3..366d8040 100644 --- a/snapd/snapdconnection.h +++ b/snapd/snapdconnection.h @@ -34,9 +34,11 @@ class SnapdConnection : public QLocalSocket Q_OBJECT public: explicit SnapdConnection(QObject *parent = nullptr); + ~SnapdConnection(); SnapdReply *get(const QString &path, QObject *parent); SnapdReply *post(const QString &path, const QByteArray &payload, QObject *parent); + SnapdReply *put(const QString &path, const QByteArray &payload, QObject *parent); bool isConnected() const; diff --git a/snapd/snapdcontrol.cpp b/snapd/snapdcontrol.cpp index 0be1d6fb..af94d925 100644 --- a/snapd/snapdcontrol.cpp +++ b/snapd/snapdcontrol.cpp @@ -33,6 +33,12 @@ SnapdControl::SnapdControl(Device *device, QObject *parent) : m_device(device), m_snapdSocketPath("/run/snapd.socket") { + // If a change is one of following kind, the plugin will recognize it as update running + m_updateChangeKinds.append("install-snap"); + m_updateChangeKinds.append("remove-snap"); + m_updateChangeKinds.append("refresh-snap"); + m_updateChangeKinds.append("revert-snap"); + m_snapConnection = new SnapdConnection(this); connect(m_snapConnection, &SnapdConnection::connectedChanged, this, &SnapdControl::onConnectedChanged); } @@ -73,6 +79,11 @@ bool SnapdControl::enabled() const return m_enabled; } +bool SnapdControl::timerBasedSchedule() const +{ + return m_timerBasedSchedule; +} + void SnapdControl::loadSystemInfo() { if (!m_snapConnection) @@ -109,7 +120,7 @@ void SnapdControl::loadRunningChanges() connect(reply, &SnapdReply::finished, this, &SnapdControl::onLoadRunningChangesFinished); } -void SnapdControl::loadChange(const int &change) +void SnapdControl::configureRefreshSchedule() { if (!m_snapConnection) return; @@ -117,34 +128,15 @@ void SnapdControl::loadChange(const int &change) if (!m_snapConnection->isConnected()) return; - SnapdReply *reply = m_snapConnection->get(QString("/v2/changes/%1").arg(QString::number(change)), this); - connect(reply, &SnapdReply::finished, this, &SnapdControl::onLoadChangeFinished); -} + QVariantMap configuration; QVariantMap configMap; + configMap.insert("timer", m_preferedRefreshSchedule); + configMap.insert("schedule", m_preferedRefreshSchedule); + configuration.insert("refresh", configMap); -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()) << "Configure refresh schedule from" << m_currentRefreshSchedule << "-->" << m_preferedRefreshSchedule; - qCDebug(dcSnapd()) << changeStatus << changeKind << changeSummary; - - // Add this change if not already 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(snapdControlStatusStateTypeId, changeSummary); - } - - // If this change is on ready, we can remove it from our watch list - if (changeReady) { - qCDebug(dcSnapd()).noquote() << changeKind << (changeReady ? "finished." : "running.") << changeSummary; - m_watchingChanges.removeAll(changeId); - } + SnapdReply *reply = m_snapConnection->put(QString("/v2/snaps/core/conf"), QJsonDocument::fromVariant(configuration).toJson(QJsonDocument::Compact), this); + connect(reply, &SnapdReply::finished, this, &SnapdControl::onConfigureRefreshScheduleFinished); } bool SnapdControl::validAsyncResponse(const QVariantMap &responseMap) @@ -185,7 +177,23 @@ void SnapdControl::onLoadSystemInfoFinished() device()->setStateValue(snapdControlLastUpdateTimeStateTypeId, lastRefreshTime.toTime_t()); device()->setStateValue(snapdControlNextUpdateTimeStateTypeId, nextRefreshTime.toTime_t()); + // Check if we are working on refresh timer or refresh schedule + if (result.value("refresh").toMap().contains("schedule")) { + // Schedule based core snap + m_timerBasedSchedule = false; + m_currentRefreshSchedule = result.value("refresh").toMap().value("schedule").toString(); + } else if (result.value("refresh").toMap().contains("timer")) { + // Timer based core snap: snapd >= 2.31 + m_timerBasedSchedule = true; + m_currentRefreshSchedule = result.value("refresh").toMap().value("timer").toString(); + } + reply->deleteLater(); + + // Check if the refresh schedule should be updated + if (m_currentRefreshSchedule != m_preferedRefreshSchedule) { + configureRefreshSchedule(); + } } void SnapdControl::onLoadSnapListFinished() @@ -210,42 +218,63 @@ void SnapdControl::onLoadRunningChangesFinished() return; } - foreach (const QVariant &changeVariant, reply->dataMap().value("result").toList()) { - processChange(changeVariant.toMap()); - } - - // Check if there are still changes around - if (reply->dataMap().value("result").toList().isEmpty()) { - // If there are no running changes, we can forget old ones - m_watchingChanges.clear(); + // Load changes list + QVariantList changes = reply->dataMap().value("result").toList(); + reply->deleteLater(); + // If there are no running changes, update is not running + if (changes.isEmpty()) { // Update not running any more device()->setStateValue(snapdControlUpdateRunningStateTypeId, false); device()->setStateValue(snapdControlStatusStateTypeId, "-"); - } else { - // Update running - device()->setStateValue(snapdControlUpdateRunningStateTypeId, true); + return; } - reply->deleteLater(); + bool updateRunning = false; + QString updateStatus = "-"; + + // Verifiy if a change is running and which one is currently doing something + foreach (const QVariant &changeVariant, changes) { + QVariantMap changeMap = changeVariant.toMap(); + + 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(); + + // If there is a change kind "doing" or "Do" + if ( (changeStatus == "Doing" || changeStatus == "Do") && m_updateChangeKinds.contains(changeKind)) { + // Set the status of the current running change + updateRunning = true; + if (changeStatus == "Doing") { + updateStatus = changeSummary; + qCDebug(dcSnapd()).noquote() << "Current change:" << changeId << (changeReady ? "ready" : "not ready") << changeStatus << changeKind << changeSummary; + + } + } + } + + device()->setStateValue(snapdControlUpdateRunningStateTypeId, updateRunning); + device()->setStateValue(snapdControlStatusStateTypeId, updateStatus); } -void SnapdControl::onLoadChangeFinished() +void SnapdControl::onConfigureRefreshScheduleFinished() { SnapdReply *reply = static_cast(sender()); if (!reply->isValid()) { - qCDebug(dcSnapd()) << "Load change request finished with error" << reply->requestPath(); + qCDebug(dcSnapd()) << "Set refresh schedule request finished with error" << reply->requestPath(); reply->deleteLater(); return; } - processChange(reply->dataMap().value("result").toMap()); - - if (m_watchingChanges.isEmpty()) { - device()->setStateValue(snapdControlUpdateRunningStateTypeId, false); - device()->setStateValue(snapdControlStatusStateTypeId, "-"); + if (!validAsyncResponse(reply->dataMap())) { + qCWarning(dcSnapd()) << "Async refresh configuration request finished with error" << reply->dataMap().value("status").toString() << reply->dataMap().value("status-code").toInt(); + reply->deleteLater(); + return; } + qCDebug(dcSnapd()) << "Configure refresh schedule finished successfully"; reply->deleteLater(); } @@ -258,11 +287,10 @@ void SnapdControl::onSnapRefreshFinished() 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(); + qCWarning(dcSnapd()) << "Async refresh request finished with error" << reply->dataMap().value("status").toString() << reply->dataMap().value("status-code").toInt(); } else { - loadChange(reply->dataMap().value("change").toInt()); + loadRunningChanges(); } reply->deleteLater(); @@ -277,12 +305,12 @@ void SnapdControl::onSnapRevertFinished() 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(); + qCWarning(dcSnapd()) << "Async change request finished with error" << reply->dataMap().value("status").toString() << reply->dataMap().value("status-code").toInt();; } else { - loadChange(reply->dataMap().value("change").toInt()); + loadRunningChanges(); } + reply->deleteLater(); } @@ -290,13 +318,26 @@ void SnapdControl::onCheckForUpdatesFinished() { SnapdReply *reply = static_cast(sender()); if (!reply->isValid()) { - qCDebug(dcSnapd()) << "Snap check for updates request finished with error" << reply->requestPath(); + qCDebug(dcSnapd()) << "Check for snap updates request finished with error" << reply->requestPath(); reply->deleteLater(); return; } - //qCDebug(dcSnapd()) << qUtf8Printable(QJsonDocument::fromVariant(reply->dataMap()).toJson(QJsonDocument::Indented)); - device()->setStateValue(snapdControlUpdateAvailableStateTypeId, !reply->dataMap().value("result").toList().isEmpty()); + qCDebug(dcSnapd()) << "Check for available snap updates finished."; + if (reply->dataMap().value("result").toList().isEmpty()) { + qCDebug(dcSnapd()) << "There are no snap updates available."; + device()->setStateValue(snapdControlUpdateAvailableStateTypeId, false); + } else { + // Print available snap updates + qCDebug(dcSnapd()) << "Following snaps can be updated:"; + foreach (const QVariant &resultVariant, reply->dataMap().value("result").toList()) { + QVariantMap resultMap = resultVariant.toMap(); + qCDebug(dcSnapd()) << " -->" << resultMap.value("name").toString() << resultMap.value("version").toString(); + } + + device()->setStateValue(snapdControlUpdateAvailableStateTypeId, true); + } + reply->deleteLater(); } @@ -310,10 +351,11 @@ void SnapdControl::onChangeSnapChannelFinished() } if (!validAsyncResponse(reply->dataMap())) { - qCWarning(dcSnapd()) << "Async change request finished with error" << reply->dataMap().value("status").toString() << reply->dataMap().value("").toString(); + qCWarning(dcSnapd()) << "Async change request finished with error" << reply->dataMap().value("status").toString() << reply->dataMap().value("status-code").toInt(); } else { - loadChange(reply->dataMap().value("change").toInt()); + loadRunningChanges(); } + reply->deleteLater(); } @@ -354,17 +396,13 @@ void SnapdControl::update() return; // Update information - if (!m_watchingChanges.isEmpty()) { - // We are watching currently changes - foreach (const int &change, m_watchingChanges) { - loadChange(change); - } + if (device()->stateValue(snapdControlUpdateRunningStateTypeId).toBool()) { + // Note: if an update is running, just load the changes to save system resources loadRunningChanges(); } else { - // Normal refresh + // Normal update loadSystemInfo(); loadSnapList(); - checkForUpdates(); loadRunningChanges(); } } @@ -410,10 +448,20 @@ void SnapdControl::checkForUpdates() if (!m_snapConnection->isConnected()) return; + qCDebug(dcSnapd()) << "Checking for available snap updates"; SnapdReply *reply = m_snapConnection->get("/v2/find?select=refresh", this); connect(reply, &SnapdReply::finished, this, &SnapdControl::onCheckForUpdatesFinished); } +void SnapdControl::setPreferedRefreshTime(int startTime) +{ + // Schedule the refresh between startTime and startTime + 59 minutes + QTime start(startTime, 0, 0); + QTime end = start.addSecs(3540); + m_preferedRefreshSchedule = QString("%1-%2").arg(start.toString("h:mm")).arg(end.toString("h:mm")); + qCDebug(dcSnapd()) << "Set prefered refresh schedule to " << m_preferedRefreshSchedule; +} + void SnapdControl::snapRevert(const QString &snapName) { if (!m_snapConnection) diff --git a/snapd/snapdcontrol.h b/snapd/snapdcontrol.h index dd857fd4..719a5544 100644 --- a/snapd/snapdcontrol.h +++ b/snapd/snapdcontrol.h @@ -40,6 +40,7 @@ public: bool available() const; bool connected() const; bool enabled() const; + bool timerBasedSchedule() const; private: Device *m_device = nullptr; @@ -48,15 +49,19 @@ private: QString m_snapdSocketPath; bool m_enabled = true; - QList m_watchingChanges; + QStringList m_updateChangeKinds; + + bool m_timerBasedSchedule = false; + QString m_currentRefreshSchedule; + QString m_preferedRefreshSchedule; // Update calls void loadSystemInfo(); void loadSnapList(); void loadRunningChanges(); - void loadChange(const int &change); - void processChange(const QVariantMap &changeMap); + void configureRefreshSchedule(); + bool validAsyncResponse(const QVariantMap &responseMap); private slots: @@ -66,7 +71,7 @@ private slots: void onLoadSystemInfoFinished(); void onLoadSnapListFinished(); void onLoadRunningChangesFinished(); - void onLoadChangeFinished(); + void onConfigureRefreshScheduleFinished(); void onSnapRefreshFinished(); void onSnapRevertFinished(); @@ -84,6 +89,9 @@ public slots: void update(); void snapRefresh(); void checkForUpdates(); + + void setPreferedRefreshTime(int startTime); + void snapRevert(const QString &snapName); void changeSnapChannel(const QString &snapName, const QString &channel); };