diff options
author | Gatis Paeglis <gatis.paeglis@qt.io> | 2016-12-12 13:42:42 +0100 |
---|---|---|
committer | Gatis Paeglis <gatis.paeglis@qt.io> | 2016-12-16 16:29:59 +0000 |
commit | ac0ab6b8d6803c38a5ea317840de59bd9da6b8d6 (patch) | |
tree | f226ed40f0349d51670d86b83dc95de7bb81bcf5 | |
parent | 348ebfa6fca29aa7a1f509976e7ca0ec35f04d24 (diff) |
Add support for multi-process usecase
Change-Id: I16d308e9c86d580d9eccc37cd75034fc03f0c168
Reviewed-by: Gatis Paeglis <gatis.paeglis@qt.io>
-rw-r--r-- | examples/cpp/basic-daemon/basic-daemon.pro | 6 | ||||
-rw-r--r-- | examples/cpp/basic-daemon/main.cpp | 130 | ||||
-rw-r--r-- | examples/qml/basic/main.qml | 43 | ||||
-rw-r--r-- | src/imports/pluginmain.cpp | 20 | ||||
-rw-r--r-- | src/lib/qotaclient.cpp | 113 | ||||
-rw-r--r-- | src/lib/qotaclient.h | 12 | ||||
-rw-r--r-- | src/lib/qotaclient_p.h | 7 | ||||
-rw-r--r-- | src/lib/qotaclientasync.cpp | 158 | ||||
-rw-r--r-- | src/lib/qotaclientasync_p.h | 24 |
9 files changed, 324 insertions, 189 deletions
diff --git a/examples/cpp/basic-daemon/basic-daemon.pro b/examples/cpp/basic-daemon/basic-daemon.pro new file mode 100644 index 0000000..ddc1e7d --- /dev/null +++ b/examples/cpp/basic-daemon/basic-daemon.pro @@ -0,0 +1,6 @@ +QT += core qtotaupdate + +TARGET = basic-daemon +TEMPLATE = app + +SOURCES += main.cpp diff --git a/examples/cpp/basic-daemon/main.cpp b/examples/cpp/basic-daemon/main.cpp new file mode 100644 index 0000000..f67d1bd --- /dev/null +++ b/examples/cpp/basic-daemon/main.cpp @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt OTA Update module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QtCore/QObject> +#include <QtCore/QTimer> +#include <QtCore/QString> +#include <QtCore/QCommandLineParser> +#include <QtCore/QCoreApplication> +#include <QtCore/QLoggingCategory> +#include <QtOtaUpdate/QtOtaUpdate> + +Q_LOGGING_CATEGORY(daemon, "ota.daemon.demo", QtDebugMsg) + +class UpdateChecker : public QObject +{ + Q_OBJECT +public: + UpdateChecker(const QString &guiUpdater) : m_guiUpdaterPath(guiUpdater) + { + connect(&m_device, &QOtaClient::fetchRemoteInfoFinished, this, &UpdateChecker::fetchFinished); + connect(&m_device, &QOtaClient::statusStringChanged, this, &UpdateChecker::log); + connect(&m_device, &QOtaClient::errorOccurred, this, &UpdateChecker::logError); + connect(&m_fetchTimer, &QTimer::timeout, this, &UpdateChecker::startFetch); + + m_repoConfig.setUrl(QStringLiteral("http://www.b2qtupdate.com/ostree-repo")); + if (!m_device.isRepositoryConfigSet(&m_repoConfig)) + m_device.setRepositoryConfig(&m_repoConfig); + + m_fetchTimer.setSingleShot(true); + m_fetchTimer.start(); + } + + void log(const QString &message) const { qCInfo(daemon) << message; } + void logError(const QString &error) const { + qCInfo(daemon) << QString(error).prepend(QStringLiteral("error: ")); + } + + void startFetch() + { + log(QStringLiteral("verifying remote server for system updates...")); + m_device.fetchRemoteInfo(); + } + + void fetchFinished(bool success) + { + if (success && m_device.updateAvailable()) { + log(QStringLiteral("update available")); + // Any inter-process communication mechanism could be used here. In this demo we + // simply launch a GUI that can be used to execute the update commands (such as examples/qml/basic/). + // A more sophisticated approach would be to use IPC (such as a push notification) to let the + // already running UI know that there is an system update available. Then this UI can open + // OTA control view or call OtaClient::refreshInfo() if it is already at the OTA control view. + QString cmd = QString(m_guiUpdaterPath).prepend(QStringLiteral("/usr/bin/appcontroller ")); + log(QString(cmd).prepend(QStringLiteral("starting GUI: "))); + bool ok = QProcess::startDetached(cmd); + if (!ok) + logError(QString(cmd).prepend(QStringLiteral("failed to start updater GUI: "))); + // Here we assume that the system restarts the daemon on the next reboot. Alternatively + // GUI could use IPC to give further instructions to the daemon (based on users actions). + qApp->quit(); + } else { + log(QStringLiteral("no updates")); + // Check again 2 seconds later. + m_fetchTimer.start(2000); + } + } + +private: + QOtaClient m_device; + QOtaRepositoryConfig m_repoConfig; + QTimer m_fetchTimer; + QString m_guiUpdaterPath; +}; + +#include "main.moc" + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + QCommandLineParser parser; + parser.addHelpOption(); + parser.setApplicationDescription(QStringLiteral("Qt OTA Update daemon demo.")); + QCommandLineOption verboseOption(QStringLiteral("v"), + QStringLiteral("Print verbose debug messages from the Qt OTA Update library.")); + parser.addOption(verboseOption); + QCommandLineOption guiUpdaterOption(QStringLiteral("gui-path"), + QStringLiteral("A path to the GUI updater to start when a new system update is detected."), + QStringLiteral("Path")); + parser.addOption(guiUpdaterOption); + + parser.process(app); + + if (parser.isSet(verboseOption)) + QLoggingCategory::setFilterRules(QStringLiteral("b2qt.ota.debug=true")); + QString guiAppPath = parser.value(guiUpdaterOption); + if (guiAppPath.isEmpty()) { + qWarning() << "--gui-path is required."; + return EXIT_FAILURE; + } + + UpdateChecker checker(guiAppPath); + + return app.exec(); +} diff --git a/examples/qml/basic/main.qml b/examples/qml/basic/main.qml index 368dd0b..2021ecf 100644 --- a/examples/qml/basic/main.qml +++ b/examples/qml/basic/main.qml @@ -50,15 +50,11 @@ Window { logView.positionViewAtEnd() } - function otaReady() { + function otaEnabled() { if (!OtaClient.otaEnabled) { log("OTA Update functionality is not enabled on this device") return false; } - if (!OtaClient.initialized) { - log("Initialization is not ready") - return false; - } return true; } @@ -145,7 +141,7 @@ Window { Button { text: "Use basic config" onClicked: { - if (!otaReady()) + if (!otaEnabled()) return; configureRepository(basicConfig, false) } @@ -153,7 +149,7 @@ Window { Button { text: "Use secure config" onClicked: { - if (!otaReady()) + if (!otaEnabled()) return; configureRepository(secureConfig, false) @@ -162,7 +158,7 @@ Window { Button { text: "Fetch OTA info" onClicked: { - if (!otaReady()) + if (!otaEnabled()) return; log("Fetcing OTA info...") OtaClient.fetchRemoteInfo() @@ -171,7 +167,7 @@ Window { Button { text: "Update from Package" onClicked: { - if (!otaReady()) + if (!otaEnabled()) return; log("Starting update from the package ...") OtaClient.updateOffline("/var/superblock") @@ -181,7 +177,7 @@ Window { visible: OtaClient.rollbackAvailable text: "Rollback" onClicked: { - if (!otaReady()) + if (!otaEnabled()) return; log("Roolback...") OtaClient.rollback() @@ -191,7 +187,7 @@ Window { visible: OtaClient.updateAvailable text: "Update" onClicked: { - if (!otaReady()) + if (!otaEnabled()) return; log("Updating...") OtaClient.update() @@ -201,7 +197,7 @@ Window { visible: OtaClient.restartRequired text: "Restart" onClicked: { - if (!otaReady()) + if (!otaEnabled()) return; log("Restarting (unimplemented) ...") } @@ -250,14 +246,6 @@ Window { target: OtaClient onErrorChanged: logError(error) onStatusChanged: log(status) - onInitializationFinished: { - logWithCondition("Initialization", success) - if (!configureRepository(basicConfig, true)) - updateConfigView(OtaClient.repositoryConfig()) - updateBootedMetadataLabel() - updateRemoteMetadataLabel() - updateRollbackMetadataLabel() - } onFetchRemoteInfoFinished: { logWithCondition("Fetching info from a remote server", success) if (success) @@ -272,8 +260,19 @@ Window { } Component.onCompleted: { - if (!OtaClient.otaEnabled) + if (!OtaClient.otaEnabled) { log("OTA Update functionality is not enabled on this device") - updateConfigView(0) + return; + } + + var configuredOk = configureRepository(basicConfig, true); + if (!configuredOk) + // Already configured, so won't be handled by onRepositoryConfigChanged. + // But config view still needs to be updated. + updateConfigView(OtaClient.repositoryConfig()) + + updateBootedMetadataLabel() + updateRemoteMetadataLabel() + updateRollbackMetadataLabel() } } diff --git a/src/imports/pluginmain.cpp b/src/imports/pluginmain.cpp index 9c72267..1355232 100644 --- a/src/imports/pluginmain.cpp +++ b/src/imports/pluginmain.cpp @@ -203,6 +203,7 @@ QT_BEGIN_NAMESPACE This method is asynchronous and returns immediately. The return value holds whether the operation was started successfully. + \note This method mutates system's state/metadata. \sa rollbackFinished(), restartRequired */ @@ -244,19 +245,16 @@ QT_BEGIN_NAMESPACE */ /*! - \qmlproperty bool OtaClient::otaEnabled - \readonly + \qmlmethod bool OtaClient::refreshInfo() - This property holds whether a device supports OTA updates. + \include qotaclient.cpp refresh-info */ /*! - \qmlproperty bool OtaClient::initialized + \qmlproperty bool OtaClient::otaEnabled \readonly - \include qotaclient.cpp initialized-description - - \sa initializationFinished() + This property holds whether a device supports OTA updates. */ /*! @@ -338,14 +336,6 @@ QT_BEGIN_NAMESPACE */ /*! - \qmlsignal OtaClient::initializationFinished(bool success) - - This signal is emitted when the object has finished initialization. The - object is not ready for use until this signal is received. The \a success - argument indicates whether the initialization was successful. -*/ - -/*! \qmlmethod bool OtaClient::setRepositoryConfig(OtaRepositoryConfig config) \include qotaclient.cpp set-repository-config diff --git a/src/lib/qotaclient.cpp b/src/lib/qotaclient.cpp index 710d4be..4fa3bee 100644 --- a/src/lib/qotaclient.cpp +++ b/src/lib/qotaclient.cpp @@ -45,7 +45,6 @@ const QString repoConfigPath(QStringLiteral("/etc/ostree/remotes.d/qt-os.conf")) QOtaClientPrivate::QOtaClientPrivate(QOtaClient *client) : q_ptr(client), - m_initialized(false), m_updateAvailable(false), m_rollbackAvailable(false), m_restartRequired(false) @@ -65,7 +64,7 @@ QOtaClientPrivate::~QOtaClientPrivate() if (m_otaEnabled) { if (m_otaAsyncThread->isRunning()) { m_otaAsyncThread->quit(); - if (Q_UNLIKELY(m_otaAsyncThread->wait(4000))) + if (!Q_UNLIKELY(m_otaAsyncThread->wait(4000))) qCWarning(qota) << "Timed out waiting for worker thread to exit."; } delete m_otaAsyncThread; @@ -83,20 +82,7 @@ static void updateInfoMembers(const QJsonDocument &json, QByteArray *info, QStri *description = root.value(QStringLiteral("description")).toString(QStringLiteral("unknown")); } -bool QOtaClientPrivate::isReady() const -{ - if (!m_otaEnabled) { - qCWarning(qota) << "over-the-air update functionality is not enabled for this device"; - return false; - } - if (!m_initialized) { - qCWarning(qota) << "initialization is not ready"; - return false; - } - return true; -} - -void QOtaClientPrivate::refreshState() +void QOtaClientPrivate::handleStateChanges() { Q_Q(QOtaClient); @@ -113,15 +99,11 @@ void QOtaClientPrivate::refreshState() } } -void QOtaClientPrivate::initializeFinished(bool success, const QString &bootedRev, - const QJsonDocument &bootedInfo) +void QOtaClientPrivate::setBootedInfo(QString &bootedRev, const QJsonDocument &bootedInfo) { Q_Q(QOtaClient); m_bootedRev = bootedRev; updateInfoMembers(bootedInfo, &m_bootedInfo, &m_bootedVersion, &m_bootedDescription); - refreshState(); - m_initialized = success; - emit q->initializationFinished(m_initialized); } void QOtaClientPrivate::statusStringChanged(const QString &status) @@ -167,7 +149,8 @@ void QOtaClientPrivate::remoteInfoChanged(const QString &remoteRev, const QJsonD m_remoteRev = remoteRev; updateInfoMembers(remoteInfo, &m_remoteInfo, &m_remoteVersion, &m_remoteDescription); - refreshState(); + handleStateChanges(); + emit q->remoteInfoChanged(); } @@ -177,7 +160,7 @@ void QOtaClientPrivate::defaultRevisionChanged(const QString &defaultRevision) return; m_defaultRev = defaultRevision; - refreshState(); + handleStateChanges(); } /*! @@ -197,6 +180,17 @@ void QOtaClientPrivate::defaultRevisionChanged(const QString &defaultRevision) A remote needs to be configured for a device to be able to locate a server that is hosting an OTA update, see setRepositoryConfig(). + When utilizing this API from several processes, precautions need to be taken + to ensure that the processes' view of the system state and metadata is up to date. + This can be achieved by using any IPC mechanism of choice to ensure that this + information is being modified by only a single process at a time. + + Methods that modify the system's state and/or metadata are marked as such. In a + multi-process scenario, refreshInfo() updates the processes' view of the system state + and metadata. A typical example would be a daemon that periodically checks a remote + server (with fetchRemoteInfo()) for system updates, and then uses IPC (such as a push + notification) to let the system's main GUI know when a new version is available. + //! [client-description] */ @@ -290,14 +284,6 @@ void QOtaClientPrivate::defaultRevisionChanged(const QString &defaultRevision) */ /*! - \fn void QOtaClient::initializationFinished(bool success) - - This signal is emitted when the object has finished initialization. The - object is not ready for use until this signal is received. The \a success - argument indicates whether the initialization was successful. -*/ - -/*! \fn void QOtaClient::repositoryConfigChanged(QOtaRepositoryConfig *repository) This signal is emitted when the configuration file was updated (\a repository @@ -312,7 +298,6 @@ QOtaClient::QOtaClient(QObject *parent) : Q_D(QOtaClient); if (d->m_otaEnabled) { QOtaClientAsync *async = d->m_otaAsync.data(); - connect(async, &QOtaClientAsync::initializeFinished, d, &QOtaClientPrivate::initializeFinished); connect(async, &QOtaClientAsync::fetchRemoteInfoFinished, this, &QOtaClient::fetchRemoteInfoFinished); connect(async, &QOtaClientAsync::updateFinished, this, &QOtaClient::updateFinished); connect(async, &QOtaClientAsync::rollbackFinished, this, &QOtaClient::rollbackFinished); @@ -323,7 +308,7 @@ QOtaClient::QOtaClient(QObject *parent) : connect(async, &QOtaClientAsync::rollbackInfoChanged, d, &QOtaClientPrivate::rollbackInfoChanged); connect(async, &QOtaClientAsync::remoteInfoChanged, d, &QOtaClientPrivate::remoteInfoChanged); connect(async, &QOtaClientAsync::defaultRevisionChanged, d, &QOtaClientPrivate::defaultRevisionChanged); - d->m_otaAsync->initialize(); + d->m_otaAsync->refreshInfo(d); } } @@ -340,6 +325,8 @@ QOtaClient::~QOtaClient() This method is asynchronous and returns immediately. The return value holds whether the operation was started successfully. + + \note This method mutates system's state/metadata. //! [fetchremoteinfo-description] \sa fetchRemoteInfoFinished(), updateAvailable, remoteInfo @@ -347,7 +334,7 @@ QOtaClient::~QOtaClient() bool QOtaClient::fetchRemoteInfo() const { Q_D(const QOtaClient); - if (!d->isReady()) + if (!d->m_otaEnabled) return false; d->m_otaAsync->fetchRemoteInfo(); @@ -360,6 +347,8 @@ bool QOtaClient::fetchRemoteInfo() const This method is asynchronous and returns immediately. The return value holds whether the operation was started successfully. + + \note This method mutates system's state/metadata. //! [update-description] \sa updateFinished(), fetchRemoteInfo, restartRequired, setRepositoryConfig @@ -367,7 +356,7 @@ bool QOtaClient::fetchRemoteInfo() const bool QOtaClient::update() const { Q_D(const QOtaClient); - if (!d->isReady() || !updateAvailable()) + if (!d->m_otaEnabled || !updateAvailable()) return false; d->m_otaAsync->update(d->m_remoteRev); @@ -380,12 +369,13 @@ bool QOtaClient::update() const This method is asynchronous and returns immediately. The return value holds whether the operation was started successfully. + \note This method mutates system's state/metadata. \sa rollbackFinished(), restartRequired */ bool QOtaClient::rollback() const { Q_D(const QOtaClient); - if (!d->isReady()) + if (!d->m_otaEnabled) return false; d->m_otaAsync->rollback(); @@ -402,6 +392,7 @@ bool QOtaClient::rollback() const holds whether the operation was started successfully. The \a packagePath holds a path to the update package. + \note This method mutates system's state/metadata. //! [update-offline] \sa updateOfflineFinished() @@ -409,7 +400,7 @@ bool QOtaClient::rollback() const bool QOtaClient::updateOffline(const QString &packagePath) { Q_D(QOtaClient); - if (!d->isReady()) + if (!d->m_otaEnabled) return false; QString package = QFileInfo(packagePath).absoluteFilePath(); @@ -430,6 +421,7 @@ bool QOtaClient::updateOffline(const QString &packagePath) This method is asynchronous and returns immediately. The return value holds whether the operation was started successfully. + \note This method mutates system's state/metadata. //! [update-remote-offline] \sa remoteInfoChanged @@ -437,7 +429,7 @@ bool QOtaClient::updateOffline(const QString &packagePath) bool QOtaClient::updateRemoteInfoOffline(const QString &packagePath) { Q_D(QOtaClient); - if (!d->isReady()) + if (!d->m_otaEnabled) return false; QFileInfo package(packagePath); @@ -452,6 +444,24 @@ bool QOtaClient::updateRemoteInfoOffline(const QString &packagePath) } /*! +//! [refresh-info] + Refreshes the instances view of the system's state from the local metadata cache. + Returns \c true if info is refreshed successfully; otherwise returns \c false. + + Using this method is not required when only one process is responsible for all OTA tasks. + +//! [refresh-info] +*/ +bool QOtaClient::refreshInfo() const +{ + Q_D(const QOtaClient); + if (!d->m_otaEnabled) + return false; + + return d->m_otaAsync->refreshInfo(); +} + +/*! //! [remove-repository-config] Remove a configuration file for the repository. @@ -516,7 +526,7 @@ bool QOtaClient::isRepositoryConfigSet(QOtaRepositoryConfig *config) const bool QOtaClient::setRepositoryConfig(QOtaRepositoryConfig *config) { Q_D(QOtaClient); - if (!d->isReady() || !config) + if (!d->m_otaEnabled || !config) return false; if (QDir().exists(repoConfigPath)) { @@ -581,7 +591,8 @@ bool QOtaClient::setRepositoryConfig(QOtaRepositoryConfig *config) /*! Returns a configuration object for the repository or \c nullptr if the - configuration file does not exist or could not be read. + configuration file does not exist or could not be read. The caller is + responsible for deleting the returned object. \sa setRepositoryConfig(), removeRepositoryConfig() */ @@ -603,28 +614,6 @@ bool QOtaClient::otaEnabled() const } /*! - \property QOtaClient::initialized -//! [initialized-description] - \brief whether the object has completed the initialization. - - When an object of this class is created, it asynchronously (from a non-GUI - thread) pre-populates the internal state, sets this property accordingly, - and signals initializationFinished(). - - Initialization is fast unless there is another process locking access to - the OSTree repository on a device, for example, a daemon process calling - fetchRemoteInfo(). -//! [initialized-description] - - \sa initializationFinished() -*/ -bool QOtaClient::initialized() const -{ - Q_D(const QOtaClient); - return d->m_initialized; -} - -/*! \property QOtaClient::error \brief a string containing the last error occurred. */ diff --git a/src/lib/qotaclient.h b/src/lib/qotaclient.h index dfd171d..fda1727 100644 --- a/src/lib/qotaclient.h +++ b/src/lib/qotaclient.h @@ -42,16 +42,15 @@ class Q_DECL_EXPORT QOtaClient : public QObject { Q_OBJECT Q_PROPERTY(bool otaEnabled READ otaEnabled) - Q_PROPERTY(bool initialized READ initialized NOTIFY initializationFinished) Q_PROPERTY(bool updateAvailable READ updateAvailable NOTIFY updateAvailableChanged) Q_PROPERTY(bool rollbackAvailable READ rollbackAvailable NOTIFY rollbackAvailableChanged) Q_PROPERTY(bool restartRequired READ restartRequired NOTIFY restartRequiredChanged) Q_PROPERTY(QString error READ errorString NOTIFY errorOccurred) Q_PROPERTY(QString status READ statusString NOTIFY statusStringChanged) - Q_PROPERTY(QString bootedVersion READ bootedVersion NOTIFY initializationFinished) - Q_PROPERTY(QString bootedDescription READ bootedDescription NOTIFY initializationFinished) - Q_PROPERTY(QString bootedRevision READ bootedRevision NOTIFY initializationFinished) - Q_PROPERTY(QByteArray bootedInfo READ bootedInfo NOTIFY initializationFinished) + Q_PROPERTY(QString bootedVersion READ bootedVersion) + Q_PROPERTY(QString bootedDescription READ bootedDescription) + Q_PROPERTY(QString bootedRevision READ bootedRevision) + Q_PROPERTY(QByteArray bootedInfo READ bootedInfo) Q_PROPERTY(QString remoteVersion READ remoteVersion NOTIFY remoteInfoChanged) Q_PROPERTY(QString remoteDescription READ remoteDescription NOTIFY remoteInfoChanged) Q_PROPERTY(QString remoteRevision READ remoteRevision NOTIFY remoteInfoChanged) @@ -68,7 +67,6 @@ public: bool rollbackAvailable() const; bool restartRequired() const; bool otaEnabled() const; - bool initialized() const; QString errorString() const; QString statusString() const; @@ -77,6 +75,7 @@ public: Q_INVOKABLE bool rollback() const; Q_INVOKABLE bool updateOffline(const QString &packagePath); Q_INVOKABLE bool updateRemoteInfoOffline(const QString &packagePath); + Q_INVOKABLE bool refreshInfo() const; Q_INVOKABLE bool setRepositoryConfig(QOtaRepositoryConfig *config); Q_INVOKABLE QOtaRepositoryConfig *repositoryConfig() const; @@ -108,7 +107,6 @@ Q_SIGNALS: void errorOccurred(const QString &error); void repositoryConfigChanged(QOtaRepositoryConfig *config); - void initializationFinished(bool success); void fetchRemoteInfoFinished(bool success); void updateFinished(bool success); void rollbackFinished(bool success); diff --git a/src/lib/qotaclient_p.h b/src/lib/qotaclient_p.h index 58625f0..f4e5c90 100644 --- a/src/lib/qotaclient_p.h +++ b/src/lib/qotaclient_p.h @@ -52,20 +52,17 @@ public: QOtaClientPrivate(QOtaClient *client); virtual ~QOtaClientPrivate(); - void refreshState(); - void initializeFinished(bool success, const QString &bootedRev, const QJsonDocument &bootedInfo); + void handleStateChanges(); void statusStringChanged(const QString &status); void errorOccurred(const QString &error); bool verifyPathExist(const QString &path); + void setBootedInfo(QString &bootedRev, const QJsonDocument &bootedInfo); void rollbackInfoChanged(const QString &rollbackRev, const QJsonDocument &rollbackInfo, int treeCount); void remoteInfoChanged(const QString &remoteRev, const QJsonDocument &remoteInfo); void defaultRevisionChanged(const QString &defaultRevision); - bool isReady() const; - // members QOtaClient *const q_ptr; - bool m_initialized; bool m_updateAvailable; bool m_rollbackAvailable; bool m_restartRequired; diff --git a/src/lib/qotaclientasync.cpp b/src/lib/qotaclientasync.cpp index aadd4bf..73f0f04 100644 --- a/src/lib/qotaclientasync.cpp +++ b/src/lib/qotaclientasync.cpp @@ -32,20 +32,25 @@ #include "qotaclientasync_p.h" #include "qotaclient_p.h" -#include <QtCore/QFile> -#include <QtCore/QTime> - QT_BEGIN_NAMESPACE #define OSTREE_STATIC_DELTA_META_ENTRY_FORMAT "(uayttay)" #define OSTREE_STATIC_DELTA_FALLBACK_FORMAT "(yaytt)" #define OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT "(a{sv}tayay" OSTREE_COMMIT_GVARIANT_STRING "aya" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT "a" OSTREE_STATIC_DELTA_FALLBACK_FORMAT ")" -QOtaClientAsync::QOtaClientAsync() : - m_sysroot(ostree_sysroot_new_default()) +// from libglnx +#define GLNX_DEFINE_CLEANUP_FUNCTION0(Type, name, func) \ + static inline void name (void *v) \ + { \ + if (*(Type*)v) \ + func (*(Type*)v); \ + } +#define glnx_unref_object __attribute__ ((cleanup(glnx_local_obj_unref))) +GLNX_DEFINE_CLEANUP_FUNCTION0(GObject*, glnx_local_obj_unref, g_object_unref) + +QOtaClientAsync::QOtaClientAsync() { // async mapper - connect(this, &QOtaClientAsync::initialize, this, &QOtaClientAsync::_initialize); connect(this, &QOtaClientAsync::fetchRemoteInfo, this, &QOtaClientAsync::_fetchRemoteInfo); connect(this, &QOtaClientAsync::update, this, &QOtaClientAsync::_update); connect(this, &QOtaClientAsync::rollback, this, &QOtaClientAsync::_rollback); @@ -55,7 +60,6 @@ QOtaClientAsync::QOtaClientAsync() : QOtaClientAsync::~QOtaClientAsync() { - ostree_sysroot_unload (m_sysroot); } static void parseErrorString(QString *error) @@ -110,6 +114,15 @@ QString QOtaClientAsync::ostree(const QString &command, bool *ok, bool updateSta return out; } +OstreeSysroot* QOtaClientAsync::defaultSysroot() +{ + GError *error = nullptr; + OstreeSysroot *sysroot = ostree_sysroot_new_default(); + if (!ostree_sysroot_load (sysroot, 0, &error)) + emitGError(error); + return sysroot; +} + QJsonDocument QOtaClientAsync::infoFromRev(const QString &rev, bool *ok) { QString jsonData; @@ -129,33 +142,34 @@ QJsonDocument QOtaClientAsync::infoFromRev(const QString &rev, bool *ok) return jsonInfo; } -void QOtaClientAsync::_initialize() +bool QOtaClientAsync::refreshInfo(QOtaClientPrivate *d) { - GError *error = nullptr; - if (!ostree_sysroot_load (m_sysroot, 0, &error) || - !ostree_sysroot_get_repo (m_sysroot, &m_repo, 0, &error)) { - emitGError(error); - emit initializeFinished(false); - return; - } - - handleRevisionChanges(); + glnx_unref_object OstreeSysroot *sysroot = defaultSysroot(); + if (!sysroot) + return false; bool ok = true; + if (d) { + // This is non-nullptr only when called from QOtaClient's constructor. + // Booted revision can change only when a device is rebooted. + OstreeDeployment *bootedDeployment = (OstreeDeployment*)ostree_sysroot_get_booted_deployment (sysroot); + QString bootedRev = QLatin1String(ostree_deployment_get_csum (bootedDeployment)); + QJsonDocument bootedInfo = infoFromRev(bootedRev, &ok); + if (!ok) + return false; + d->setBootedInfo(bootedRev, bootedInfo); + } + // prepopulate with what we think is on the remote server (head of the local repo) QString remoteRev = ostree(QStringLiteral("ostree rev-parse linux/qt"), &ok); QJsonDocument remoteInfo; if (ok) remoteInfo = infoFromRev(remoteRev, &ok); - if (!ok) { - emit initializeFinished(false); - return; - } + if (!ok) + return false; emit remoteInfoChanged(remoteRev, remoteInfo); - OstreeDeployment *bootedDeployment = (OstreeDeployment*)ostree_sysroot_get_booted_deployment (m_sysroot); - QString bootedRev = QLatin1String(ostree_deployment_get_csum (bootedDeployment)); - QJsonDocument bootedInfo = infoFromRev(bootedRev, &ok); - emit initializeFinished(ok, bootedRev, bootedInfo); + ok = handleRevisionChanges(sysroot); + return ok; } void QOtaClientAsync::_fetchRemoteInfo() @@ -164,21 +178,24 @@ void QOtaClientAsync::_fetchRemoteInfo() QJsonDocument remoteInfo; bool ok = true; ostree(QStringLiteral("ostree pull --commit-metadata-only --disable-static-deltas qt-os linux/qt"), &ok); - if (ok) ostree(QStringLiteral("ostree pull --subpath=/usr/etc/qt-ota.json qt-os linux/qt"), &ok); if (ok) remoteRev = ostree(QStringLiteral("ostree rev-parse linux/qt"), &ok); + if (ok) ostree(QString(QStringLiteral("ostree pull --subpath=/usr/etc/qt-ota.json qt-os %1")).arg(remoteRev), &ok); if (ok) remoteInfo = infoFromRev(remoteRev, &ok); if (ok) emit remoteInfoChanged(remoteRev, remoteInfo); emit fetchRemoteInfoFinished(ok); } -bool QOtaClientAsync::deployCommit(const QString &commit) +bool QOtaClientAsync::deployCommit(const QString &commit, OstreeSysroot *sysroot) { bool ok = true; QString kernelArgs; GError *error = nullptr; g_autoptr(GFile) root = nullptr; - if (!ostree_repo_read_commit (m_repo, commit.toLatin1().constData(), - &root, nullptr, nullptr, &error)) { + OstreeRepo *repo = nullptr; + + // read kernel args for rev + if (!ostree_sysroot_get_repo (sysroot, &repo, 0, &error) || + !ostree_repo_read_commit (repo, commit.toLatin1().constData(), &root, nullptr, nullptr, &error)) { emitGError(error); return false; } @@ -195,28 +212,27 @@ bool QOtaClientAsync::deployCommit(const QString &commit) void QOtaClientAsync::_update(const QString &updateToRev) { - bool ok = true; - GError *error = nullptr; - emit statusStringChanged(QStringLiteral("Checking for missing objects...")); - ostree(QString(QStringLiteral("ostree pull qt-os:%1")).arg(updateToRev), &ok, true); - if (!ok || !deployCommit(updateToRev)) { + glnx_unref_object OstreeSysroot *sysroot = defaultSysroot(); + if (!sysroot) { emit updateFinished(false); return; } - if (!ostree_sysroot_load (m_sysroot, 0, &error)) { - emitGError(error); + bool ok = true; + emit statusStringChanged(QStringLiteral("Checking for missing objects...")); + ostree(QString(QStringLiteral("ostree pull qt-os:%1")).arg(updateToRev), &ok, true); + if (!ok || !deployCommit(updateToRev, sysroot)) { emit updateFinished(false); return; } - handleRevisionChanges(); + ok = handleRevisionChanges(sysroot, true); emit updateFinished(ok); } -int QOtaClientAsync::rollbackIndex() +int QOtaClientAsync::rollbackIndex(OstreeSysroot *sysroot) { - g_autoptr(GPtrArray) deployments = ostree_sysroot_get_deployments (m_sysroot); + g_autoptr(GPtrArray) deployments = ostree_sysroot_get_deployments (sysroot); if (deployments->len < 2) return -1; @@ -227,50 +243,60 @@ int QOtaClientAsync::rollbackIndex() return 1; } -void QOtaClientAsync::handleRevisionChanges() +bool QOtaClientAsync::handleRevisionChanges(OstreeSysroot *sysroot, bool reloadSysroot) { - g_autoptr(GPtrArray) deployments = ostree_sysroot_get_deployments (m_sysroot); + if (reloadSysroot) { + GError *error = nullptr; + if (!ostree_sysroot_load (sysroot, 0, &error)) { + emitGError(error); + return false; + } + } + + g_autoptr(GPtrArray) deployments = ostree_sysroot_get_deployments (sysroot); OstreeDeployment *firstDeployment = (OstreeDeployment*)deployments->pdata[0]; QString defaultRev(QLatin1String(ostree_deployment_get_csum (firstDeployment))); emit defaultRevisionChanged(defaultRev); - int index = rollbackIndex(); + int index = rollbackIndex(sysroot); if (index != -1) { OstreeDeployment *rollbackDeployment = (OstreeDeployment*)deployments->pdata[index]; QString rollbackRev(QLatin1String(ostree_deployment_get_csum (rollbackDeployment))); bool ok = true; QJsonDocument rollbackInfo = infoFromRev(rollbackRev, &ok); + if (!ok) + return false; emit rollbackInfoChanged(rollbackRev, rollbackInfo, deployments->len); } + + return true; } -bool QOtaClientAsync::emitGError(GError *error) +void QOtaClientAsync::emitGError(GError *error) { if (!error) - return false; + return; emit errorOccurred(QString::fromLatin1((error->message))); g_error_free (error); - return true; } void QOtaClientAsync::_rollback() { - GError *error = nullptr; - if (!ostree_sysroot_load (m_sysroot, 0, &error)) { - emitGError(error); + glnx_unref_object OstreeSysroot *sysroot = defaultSysroot(); + if (!sysroot) { emit rollbackFinished(false); return; } - int index = rollbackIndex(); + int index = rollbackIndex(sysroot); if (index == -1) { emit errorOccurred(QStringLiteral("At least 2 system versions required for rollback")); emit rollbackFinished(false); return; } - g_autoptr(GPtrArray) deployments = ostree_sysroot_get_deployments (m_sysroot); + g_autoptr(GPtrArray) deployments = ostree_sysroot_get_deployments (sysroot); g_autoptr(GPtrArray) newDeployments = g_ptr_array_new_with_free_func (g_object_unref); g_ptr_array_add (newDeployments, g_object_ref (deployments->pdata[index])); for (uint i = 0; i < deployments->len; i++) { @@ -280,17 +306,18 @@ void QOtaClientAsync::_rollback() } // atomically update bootloader configuration - if (!ostree_sysroot_write_deployments (m_sysroot, newDeployments, 0, &error)) { + GError *error = nullptr; + if (!ostree_sysroot_write_deployments (sysroot, newDeployments, 0, &error)) { emitGError(error); emit rollbackFinished(false); return; } - handleRevisionChanges(); - emit rollbackFinished(true); + bool ok = handleRevisionChanges(sysroot, true); + emit rollbackFinished(ok); } -bool QOtaClientAsync::extractPackage(const QString &packagePath, QString *updateToRev) +bool QOtaClientAsync::extractPackage(const QString &packagePath, OstreeSysroot *sysroot, QString *updateToRev) { GError *error = nullptr; // load delta superblock @@ -316,8 +343,13 @@ bool QOtaClientAsync::extractPackage(const QString &packagePath, QString *update // get timestamp of the head commit from the repository bool ok = true; g_autoptr(GVariant) currentCommitV = nullptr; + OstreeRepo *repo = nullptr; + if (!ostree_sysroot_get_repo (sysroot, &repo, 0, &error)) { + emitGError(error); + return false; + } QString currentCommit = ostree(QStringLiteral("ostree rev-parse linux/qt"), &ok); - if (!ok || !ostree_repo_load_commit (m_repo, currentCommit.toLatin1().constData(), + if (!ok || !ostree_repo_load_commit (repo, currentCommit.toLatin1().constData(), ¤tCommitV, nullptr, &error)) { emitGError(error); return false; @@ -354,21 +386,19 @@ bool QOtaClientAsync::extractPackage(const QString &packagePath, QString *update void QOtaClientAsync::_updateRemoteInfoOffline(const QString &packagePath) { QString rev; - bool success = extractPackage(packagePath, &rev); - emit updateRemoteInfoOfflineFinished(success); + glnx_unref_object OstreeSysroot *sysroot = defaultSysroot(); + bool ok = sysroot && extractPackage(packagePath, sysroot, &rev); + emit updateRemoteInfoOfflineFinished(ok); } void QOtaClientAsync::_updateOffline(const QString &packagePath) { - bool success = true; QString rev; - if (!extractPackage(packagePath, &rev) || !deployCommit(rev)) - success = false; - - if (success) - handleRevisionChanges(); + glnx_unref_object OstreeSysroot *sysroot = defaultSysroot(); + bool ok = sysroot && extractPackage(packagePath, sysroot, &rev) && + deployCommit(rev, sysroot) && handleRevisionChanges(sysroot, true); - emit updateOfflineFinished(success); + emit updateOfflineFinished(ok); } QT_END_NAMESPACE diff --git a/src/lib/qotaclientasync_p.h b/src/lib/qotaclientasync_p.h index db7a02c..6f1dd48 100644 --- a/src/lib/qotaclientasync_p.h +++ b/src/lib/qotaclientasync_p.h @@ -39,22 +39,22 @@ QT_BEGIN_NAMESPACE struct OstreeSysroot; -struct OstreeRepo; // from gerror.h typedef struct _GError GError; +class QOtaClientPrivate; + class QOtaClientAsync : public QObject { Q_OBJECT public: QOtaClientAsync(); virtual ~QOtaClientAsync(); + QString ostree(const QString &command, bool *ok, bool updateStatus = false); + bool refreshInfo(QOtaClientPrivate *d = nullptr); signals: - void initialize(); - void initializeFinished(bool success, const QString &bootedRev = QString(), - const QJsonDocument &bootedInfo = QJsonDocument()); void fetchRemoteInfo(); void fetchRemoteInfoFinished(bool success); void update(const QString &updateToRev); @@ -72,23 +72,19 @@ signals: void defaultRevisionChanged(const QString &defaultRevision); protected: + OstreeSysroot* defaultSysroot(); QJsonDocument infoFromRev(const QString &rev, bool *ok); - int rollbackIndex(); - void handleRevisionChanges(); - bool emitGError(GError *error); - bool deployCommit(const QString &commit); - bool extractPackage(const QString &packagePath, QString *updateToRev); + int rollbackIndex(OstreeSysroot *sysroot); + bool handleRevisionChanges(OstreeSysroot *sysroot, bool reloadSysroot = false); + void emitGError(GError *error); + bool deployCommit(const QString &commit, OstreeSysroot *sysroot); + bool extractPackage(const QString &packagePath, OstreeSysroot *sysroot, QString *updateToRev); - void _initialize(); void _fetchRemoteInfo(); void _update(const QString &updateToRev); void _rollback(); void _updateOffline(const QString &packagePath); void _updateRemoteInfoOffline(const QString &packagePath); - -private: - OstreeSysroot *m_sysroot; - OstreeRepo *m_repo; }; QT_END_NAMESPACE |