summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGatis Paeglis <gatis.paeglis@qt.io>2016-12-12 13:42:42 +0100
committerGatis Paeglis <gatis.paeglis@qt.io>2016-12-16 16:29:59 +0000
commitac0ab6b8d6803c38a5ea317840de59bd9da6b8d6 (patch)
treef226ed40f0349d51670d86b83dc95de7bb81bcf5
parent348ebfa6fca29aa7a1f509976e7ca0ec35f04d24 (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.pro6
-rw-r--r--examples/cpp/basic-daemon/main.cpp130
-rw-r--r--examples/qml/basic/main.qml43
-rw-r--r--src/imports/pluginmain.cpp20
-rw-r--r--src/lib/qotaclient.cpp113
-rw-r--r--src/lib/qotaclient.h12
-rw-r--r--src/lib/qotaclient_p.h7
-rw-r--r--src/lib/qotaclientasync.cpp158
-rw-r--r--src/lib/qotaclientasync_p.h24
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(),
&currentCommitV, 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