diff options
author | Fabian Müller <fmueller@owncloud.com> | 2022-01-19 15:26:37 +0300 |
---|---|---|
committer | Hannah von Reth <vonreth@kde.org> | 2022-03-14 16:23:05 +0300 |
commit | d76f27f19ee91d4018dc2b407fb6516cc2d535f0 (patch) | |
tree | 364a9a39d54fb6e35bcdd39c848a7690109ea775 /src/gui/updater | |
parent | 71fe3f2e1e0f3c1948f8b72b31117cb90631f2db (diff) |
Add built-in AppImage self updater
Performs upgrades in background if available. Uses libappimageupdate internally to update efficiently.
Diffstat (limited to 'src/gui/updater')
-rw-r--r-- | src/gui/updater/CMakeLists.txt | 28 | ||||
-rw-r--r-- | src/gui/updater/appimageupdater.cpp | 165 | ||||
-rw-r--r-- | src/gui/updater/appimageupdater.h | 41 | ||||
-rw-r--r-- | src/gui/updater/ocupdater.h | 22 | ||||
-rw-r--r-- | src/gui/updater/updater.cpp | 20 |
5 files changed, 264 insertions, 12 deletions
diff --git a/src/gui/updater/CMakeLists.txt b/src/gui/updater/CMakeLists.txt index baf68498d..0f8021962 100644 --- a/src/gui/updater/CMakeLists.txt +++ b/src/gui/updater/CMakeLists.txt @@ -25,3 +25,31 @@ if(SPARKLE_FOUND) ) target_link_libraries(owncloudCore PRIVATE ${SPARKLE_LIBRARY}) endif() + +if(WITH_APPIMAGEUPDATER) + # needed for set_source_files_properties(... TARGET_DIRECTORY ...) + cmake_minimum_required(VERSION 3.18) + + message(STATUS "Including built-in libappimageupdate based updater") + + set(appimageupdater_sources + ${CMAKE_CURRENT_SOURCE_DIR}/appimageupdater.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/appimageupdater.h + ) + + target_sources(owncloudCore PRIVATE ${appimageupdater_sources}) + + # as libappimageupdate uses exceptions, we define a shim that catches all incoming exceptions + # and handles them in a suitable way + # we have to enable exceptions for this one compilation unit + set_source_files_properties(${appimageupdater_sources} + TARGET_DIRECTORY owncloudCore + PROPERTIES COMPILE_OPTIONS "-fexceptions" + ) + + target_compile_definitions(owncloudCore PRIVATE WITH_APPIMAGEUPDATER) + target_link_libraries(owncloudCore PRIVATE libappimageupdate-qt) + + find_package(Threads REQUIRED) + target_link_libraries(owncloudCore PUBLIC ${CMAKE_THREAD_LIBS_INIT}) +endif() diff --git a/src/gui/updater/appimageupdater.cpp b/src/gui/updater/appimageupdater.cpp new file mode 100644 index 000000000..d28b99a38 --- /dev/null +++ b/src/gui/updater/appimageupdater.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2022 by Fabian Müller <fmueller@owncloud.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include <QTimer> +#include <appimage/update.h> +#include <chrono> + +#include "appimageupdater.h" +#include "common/version.h" + +using namespace OCC; +using namespace std::chrono_literals; + +namespace { + +/** + * libappimageupdate uses exceptions, but the client does not + * This little shim adapts the interface to one usable within this project + */ +class AppImageUpdaterShim : public QObject +{ + Q_OBJECT + +private: + explicit AppImageUpdaterShim(const QString &zsyncFileUrl, QObject *parent = nullptr) + : QObject(parent) + , _updater(Utility::appImageLocation().toStdString(), true) + { + QString updateInformation(QStringLiteral("zsync|") + zsyncFileUrl); + _updater.setUpdateInformation(updateInformation.toStdString()); + } + + void _logStatusMessages() + { + std::string currentStatusMessage; + + while (_updater.nextStatusMessage(currentStatusMessage)) { + qCInfo(lcUpdater) << "AppImageUpdate:" << QString::fromStdString(currentStatusMessage); + } + } + +public: + static AppImageUpdaterShim *makeInstance(const QString &updateInformation, QObject *parent) + { + try { + return new AppImageUpdaterShim(updateInformation, parent); + } catch (const std::exception &e) { + qCCritical(lcUpdater) << "Failed to create updater shim:" << e.what(); + return nullptr; + } + } + + bool isUpdateAvailable() noexcept + { + try { + bool updateAvailable; + + if (!_updater.checkForChanges(updateAvailable)) { + _logStatusMessages(); + return false; + } + + _logStatusMessages(); + return updateAvailable; + } catch (const std::exception &e) { + _logStatusMessages(); + qCCritical(lcUpdater) << "Checking for update failed:" << e.what(); + return false; + } + } + + void startUpdateInBackground() noexcept + { + // monitor progress and log status messages + auto *timer = new QTimer(this); + + timer->setInterval(100ms); + + connect(timer, &QTimer::timeout, this, [=]() { + _logStatusMessages(); + + if (_updater.isDone()) { + emit finished(!_updater.hasError()); + timer->stop(); + } + }); + + _updater.start(); + timer->start(); + } + +signals: + void finished(bool successfully); + +private: + appimage::update::Updater _updater; +}; + +} // namespace + +AppImageUpdater::AppImageUpdater(const QUrl &url) + : OCUpdater(url) +{ +} + + +bool AppImageUpdater::handleStartup() +{ + // nothing to do, update will be performed while app is running, if anything + return false; +} + +void AppImageUpdater::versionInfoArrived(const UpdateInfo &info) +{ + if (info.version().isEmpty() || Version::versionWithBuildNumber() >= QVersionNumber::fromString(info.version())) { + qCInfo(lcUpdater) << "Client is on latest version!"; + setDownloadState(UpToDate); + return; + } + + const auto AppImageUpdaterShim = AppImageUpdaterShim::makeInstance(info.downloadUrl(), this); + + if (AppImageUpdaterShim == nullptr) { + setDownloadState(DownloadFailed); + return; + } + + if (!AppImageUpdaterShim->isUpdateAvailable()) { + qCCritical(lcUpdater) << "Update server reported that update is available, but AppImageUpdate disagrees, aborting"; + setDownloadState(DownloadFailed); + return; + } + + // binding AppImageUpdaterShim shared pointer to finished callback makes sure the updater is cleaned up when it's done + connect(AppImageUpdaterShim, &AppImageUpdaterShim::finished, this, [this](bool succeeded) { + if (succeeded) { + qCInfo(lcUpdater) << "AppImage update complete"; + setDownloadState(DownloadComplete); + } else { + qCInfo(lcUpdater) << "AppImage update failed"; + setDownloadState(DownloadFailed); + } + }); + + setDownloadState(Downloading); + AppImageUpdaterShim->startUpdateInBackground(); +} + +void AppImageUpdater::backgroundCheckForUpdate() +{ + OCUpdater::backgroundCheckForUpdate(); +} + +#include "appimageupdater.moc" diff --git a/src/gui/updater/appimageupdater.h b/src/gui/updater/appimageupdater.h new file mode 100644 index 000000000..183126db1 --- /dev/null +++ b/src/gui/updater/appimageupdater.h @@ -0,0 +1,41 @@ +/* +* Copyright (C) 2022 by Fabian Müller <fmueller@owncloud.com> +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, but +* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +* for more details. +*/ + +#pragma once + +#include <QObject> +#include <QString> + +#include <updater/ocupdater.h> + +namespace OCC { + +/** + * @brief AppImage Updater using AppImageUpdate + * @ingroup gui + */ +class AppImageUpdater : public OCUpdater +{ + Q_OBJECT + +public: + explicit AppImageUpdater(const QUrl &url); + bool handleStartup() override; + void backgroundCheckForUpdate() override; + +private: + void versionInfoArrived(const UpdateInfo &succeeded) override; +}; + +} // namespace OCC diff --git a/src/gui/updater/ocupdater.h b/src/gui/updater/ocupdater.h index dbdb6be51..791ab0660 100644 --- a/src/gui/updater/ocupdater.h +++ b/src/gui/updater/ocupdater.h @@ -49,17 +49,17 @@ namespace OCC { * Simple class diagram of the updater: * * +---------------------------+ - * +-----+ UpdaterScheduler +-----+ - * | +------------+--------------+ | - * v v v - * +------------+ +---------------------+ +----------------+ - * |NSISUpdater | |PassiveUpdateNotifier| | SparkleUpdater | - * +-+----------+ +---+-----------------+ +-----+----------+ - * | | | - * | v +------------------+ - * | +---------------+ v - * +-->| OCUpdater +------+ - * +--------+------+ | + * +-----+ UpdaterScheduler +-----+------------------+ + * | +------------+--------------+ | | + * v v v v + * +------------+ +---------------------+ +----------------+ +-----------------+ + * |NSISUpdater | |PassiveUpdateNotifier| | SparkleUpdater | | AppImageUpdater | + * +-+----------+ +---+-----------------+ +-----+----------+ +-----------------+ + * | | | | + * | v +------------------+ | + * | +---------------+ v | + * +-->| OCUpdater +------+ | + * +--------+------+ |<--------------------------------+ * | Updater | * +-------------+ */ diff --git a/src/gui/updater/updater.cpp b/src/gui/updater/updater.cpp index 8713989e9..8b8a6d854 100644 --- a/src/gui/updater/updater.cpp +++ b/src/gui/updater/updater.cpp @@ -20,6 +20,10 @@ #include "updater/sparkleupdater.h" #include "updater/ocupdater.h" +#ifdef WITH_APPIMAGEUPDATER +#include "updater/appimageupdater.h" +#endif + #include "common/utility.h" #include "common/version.h" #include "configfile.h" @@ -72,7 +76,15 @@ QUrlQuery Updater::getQueryParams() Theme *theme = Theme::instance(); QString platform = QStringLiteral("stranger"); if (Utility::isLinux()) { - platform = QStringLiteral("linux"); +#ifdef WITH_APPIMAGEUPDATER + if (Utility::runningInAppImage()) { + platform = "linux-appimage-" + QSysInfo::buildCpuArchitecture(); + } else { +#endif + platform = QStringLiteral("linux"); +#ifdef WITH_APPIMAGEUPDATER + } +#endif } else if (Utility::isBSD()) { platform = QStringLiteral("bsd"); } else if (Utility::isWindows()) { @@ -135,6 +147,12 @@ Updater *Updater::create() // Also for MSI return new NSISUpdater(url); #else +#ifdef WITH_APPIMAGEUPDATER + if (Utility::runningInAppImage()) { + return new AppImageUpdater(url); + } +#endif + // the best we can do is notify about updates return new PassiveUpdateNotifier(url); #endif |