From 298493169ca81da714f138f99a5c7052b40cb0a3 Mon Sep 17 00:00:00 2001 From: kkostiuk <kateryna.kostiuk@savoirfairelinux.com> Date: Tue, 25 Jan 2022 11:56:34 -0500 Subject: [PATCH] updater: add sparkle - add sparkle submodule - add an option to enable sparkle - modify entitlements - cleanup Info.plist Gitlab: #578 Change-Id: I7f562112a72a33e008ab316479fbaa68dc0e07f1 --- .gitmodules | 4 + CMakeLists.txt | 71 +++-- cmake/macos_qt_deploy.cmake | 8 +- resources/Info.plist | 68 ++--- resources/entitlements/Jami.entitlements | 6 +- sparkle/Sparkle | 1 + sparkle/dsa_pub.pem | 36 +++ sparkle/sign_update.sh | 11 + sparkle/sparkle-xml-updater.sh | 46 +++ src/MainApplicationWindow.qml | 12 +- src/{ => os/macos}/connectivitymonitor.mm | 0 src/os/macos/updatemanager.mm | 132 +++++++++ .../components/GeneralSettingsPage.qml | 3 +- .../components/UpdateSettings.qml | 6 +- src/updatemanager.cpp | 268 +++++++++++------- src/updatemanager.h | 20 +- 16 files changed, 502 insertions(+), 190 deletions(-) create mode 160000 sparkle/Sparkle create mode 100644 sparkle/dsa_pub.pem create mode 100755 sparkle/sign_update.sh create mode 100755 sparkle/sparkle-xml-updater.sh rename src/{ => os/macos}/connectivitymonitor.mm (100%) create mode 100644 src/os/macos/updatemanager.mm diff --git a/.gitmodules b/.gitmodules index 971ddabe0..2cb2b72ee 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,7 @@ path = 3rdparty/qrencode-win32 url = https://github.com/BlueDragon747/qrencode-win32.git ignore = dirty +[submodule "sparkle/Sparkle"] + path = sparkle/Sparkle + url = https://github.com/sparkle-project/Sparkle.git + ignore = dirty diff --git a/CMakeLists.txt b/CMakeLists.txt index b9d94f24b..ca3b06c7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,7 +98,6 @@ set(COMMON_SOURCES ${SRC_DIR}/accountlistmodel.cpp ${SRC_DIR}/networkmanager.cpp ${SRC_DIR}/instancemanager.cpp - ${SRC_DIR}/updatemanager.cpp ${SRC_DIR}/main.cpp ${SRC_DIR}/smartlistmodel.cpp ${SRC_DIR}/utils.cpp @@ -214,7 +213,9 @@ if(MSVC) ) list(APPEND COMMON_SOURCES - ${SRC_DIR}/connectivitymonitor.cpp) + ${SRC_DIR}/connectivitymonitor.cpp + ${SRC_DIR}/updatemanager.cpp + ) # preprocessor defines add_definitions(-DUNICODE -DQT_NO_DEBUG -DNDEBUG) @@ -269,7 +270,8 @@ elseif (NOT APPLE) list(APPEND COMMON_SOURCES ${SRC_DIR}/xrectsel.c ${SRC_DIR}/dbuserrorhandler.cpp - ${SRC_DIR}/connectivitymonitor.cpp) + ${SRC_DIR}/connectivitymonitor.cpp + ${SRC_DIR}/updatemanager.cpp) list(APPEND COMMON_HEADERS ${SRC_DIR}/dbuserrorhandler.h ${SRC_DIR}/xrectsel.h) @@ -348,10 +350,10 @@ elseif (NOT APPLE) find_library(ringclient ringclient ${LRCLIBDIR} NO_DEFAULT_PATH) find_library(qrencode qrencode) find_library(X11 X11) -else() +else() # APPLE list(APPEND COMMON_SOURCES - ${SRC_DIR}/connectivitymonitor.mm) - find_package(PkgConfig REQUIRED) + ${SRC_DIR}/os/macos/updatemanager.mm + ${SRC_DIR}/os/macos/connectivitymonitor.mm) if(NOT DEFINED LRC) if(EXISTS ${PROJECT_SOURCE_DIR}/../install/lrc) set(LRC ${PROJECT_SOURCE_DIR}/../install/lrc) @@ -388,9 +390,24 @@ else() find_library(ringclient ringclient ${LRCLIBDIR} NO_DEFAULT_PATH) find_library(SYSTEM_CONFIGURATUION SystemConfiguration) - SET(myApp_ICON ${CMAKE_CURRENT_SOURCE_DIR}/resources/images/jami.icns) - SET_SOURCE_FILES_PROPERTIES(${myApp_ICON} PROPERTIES + set(myApp_ICON ${CMAKE_CURRENT_SOURCE_DIR}/resources/images/jami.icns) + set_source_files_properties(${myApp_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + if(ENABLE_SPARKLE) + message("Sparkle auto-update enabled") + find_library(SPARKLE_FRAMEWORK + NAMES Sparkle + HINTS ${CMAKE_CURRENT_SOURCE_DIR}/sparkle) + add_definitions(-DENABLE_SPARKLE) + message("Sparkle is here:" ${SPARKLE_FRAMEWORK}) + set(PUBLIC_KEY_PATH "${CMAKE_CURRENT_SOURCE_DIR}/sparkle/dsa_pub.pem") + set_source_files_properties(${PUBLIC_KEY_PATH} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + set(PUBLIC_KEY ${PUBLIC_KEY_PATH}) + endif() + if(BETA) + message(STATUS "Beta config enabled") + add_definitions(-DBETA) + endif() endif() # Qt find package @@ -607,12 +624,14 @@ elseif (NOT APPLE) add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) else() - target_sources(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/resources/images/jami.icns) - target_link_libraries(${PROJECT_NAME} PRIVATE - ${QT_LIBS} - ${LRC_LIB_NAME} - ${SYSTEM_CONFIGURATUION} - qrencode) + set(resources ${CMAKE_CURRENT_SOURCE_DIR}/resources/images/jami.icns) + set(libs ${QT_LIBS} ${LRC_LIB_NAME} ${SYSTEM_CONFIGURATUION} qrencode) + if(ENABLE_SPARKLE) + set(resources ${resources} ${PUBLIC_KEY} ${SPARKLE_FRAMEWORK}) + set(libs ${libs} ${SPARKLE_FRAMEWORK}) + endif(ENABLE_SPARKLE) + target_sources(${PROJECT_NAME} PRIVATE ${resources}) + target_link_libraries(${PROJECT_NAME} PRIVATE ${libs}) # translations if(Qt${QT_VERSION_MAJOR}LinguistTools_FOUND) @@ -636,21 +655,23 @@ else() DESTINATION ${CMAKE_INSTALL_PREFIX}/share/libringclient/translations) endif() set_target_properties(${PROJECT_NAME} PROPERTIES - MACOSX_BUNDLE TRUE - MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/resources/Info.plist" - MACOSX_BUNDLE_EXECUTABLE_NAME "${PROJ_NAME}" - MACOSX_BUNDLE_ICON_FILE "jami.icns" - MACOSX_BUNDLE_GUI_IDENTIFIER "${BUNDLE_ID}" - MACOSX_BUNDLE_BUNDLE_NAME "${PROJ_NAME}" - MACOSX_BUNDLE_SHORT_VERSION_STRING "${JAMI_VERSION}" - MACOSX_BUNDLE_BUNDLE_VERSION "${JAMI_BUILD}" - MACOSX_BUNDLE_COPYRIGHT "${PROJ_COPYRIGHT}" - XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/resources/entitlements/Jami.entitlements" - XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME TRUE) + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/resources/Info.plist" + MACOSX_BUNDLE_EXECUTABLE_NAME "${PROJ_NAME}" + MACOSX_BUNDLE_ICON_FILE "jami.icns" + MACOSX_BUNDLE_GUI_IDENTIFIER "${BUNDLE_ID}" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${JAMI_VERSION}" + MACOSX_BUNDLE_BUNDLE_VERSION "${JAMI_BUILD}" + MACOSX_BUNDLE_COPYRIGHT "${PROJ_COPYRIGHT}" + SPARKLE_URL "${SPARKLE_URL}" + XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/resources/entitlements/Jami.entitlements" + XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME TRUE) add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -DQML_SRC_DIR=${SRC_DIR} -DMAC_DEPLOY_QT_PATH=${CMAKE_PREFIX_PATH}/bin -DEXE_NAME="${CMAKE_BINARY_DIR}/${PROJECT_NAME}.app" + -DSPARKLE_PATH=${SPARKLE_FRAMEWORK} + -DENABLE_SPARKLE=${ENABLE_SPARKLE} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/macos_qt_deploy.cmake) endif() diff --git a/cmake/macos_qt_deploy.cmake b/cmake/macos_qt_deploy.cmake index 5af951040..cf12292a0 100644 --- a/cmake/macos_qt_deploy.cmake +++ b/cmake/macos_qt_deploy.cmake @@ -1,5 +1,7 @@ - - message("Qt deploying in dir " ${QML_SRC_DIR}) - execute_process(COMMAND "${MAC_DEPLOY_QT_PATH}/macdeployqt" +message("Qt deploying in dir " ${QML_SRC_DIR}) +execute_process(COMMAND "${MAC_DEPLOY_QT_PATH}/macdeployqt" ${EXE_NAME} -qmldir=${QML_SRC_DIR}) +if(${ENABLE_SPARKLE} MATCHES true) + file(COPY ${SPARKLE_PATH} DESTINATION ${EXE_NAME}/Contents/Frameworks/) +endif() \ No newline at end of file diff --git a/resources/Info.plist b/resources/Info.plist index 9ae6f7a74..6bc475055 100644 --- a/resources/Info.plist +++ b/resources/Info.plist @@ -2,39 +2,39 @@ <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> - <key>CFBundleDevelopmentRegion</key> - <string>English</string> - <key>CFBundleExecutable</key> - <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string> - <key>CFBundleIconFile</key> - <string>${MACOSX_BUNDLE_ICON_FILE}</string> - <key>CFBundleIdentifier</key> - <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string> - <key>CFBundleName</key> - <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string> - <key>CFBundlePackageType</key> - <string>APPL</string> - <key>CFBundleShortVersionString</key> - <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string> - <key>LSMinimumSystemVersion</key> - <string>10.13</string> - <key>CFBundleVersion</key> - <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string> - <key>CSResourcesFileMapped</key> - <true/> - <key>LSApplicationCategoryType</key> - <string>public.app-category.social-networking</string> - <key>LSRequiresCarbon</key> - <true/> - <key>NSHumanReadableCopyright</key> - <string>${MACOSX_BUNDLE_COPYRIGHT}</string> - <key>NSHighResolutionCapable</key> - <string>True</string> - <key>NSCameraUsageDescription</key> - <string>Jami requires to access your camera to make calls and record video</string> - <key>NSMicrophoneUsageDescription</key> - <string>Jami requires to access your microphone to make calls and record audio</string> - <key>NSPhotoLibraryUsageDescription</key> - <string>Jami requires to access your photo library to show image on profile and send via chat</string> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string> + <key>CFBundleIconFile</key> + <string>${MACOSX_BUNDLE_ICON_FILE}</string> + <key>CFBundleIdentifier</key> + <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string> + <key>CFBundleName</key> + <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string> + <key>LSMinimumSystemVersion</key> + <string>10.13</string> + <key>CFBundleVersion</key> + <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string> + <key>LSApplicationCategoryType</key> + <string>public.app-category.social-networking</string> + <key>NSHumanReadableCopyright</key> + <string>${MACOSX_BUNDLE_COPYRIGHT}</string> + <key>SUPublicDSAKeyFile</key> + <string>dsa_pub.pem</string> + <key>SUFeedURL</key> + <string>${SPARKLE_URL}</string> + <key>NSPrincipalClass</key> + <string>NSApplication</string> + <key>NSCameraUsageDescription</key> + <string>Jami requires to access your camera to make calls and record video</string> + <key>NSMicrophoneUsageDescription</key> + <string>Jami requires to access your microphone to make calls and record audio</string> + <key>NSPhotoLibraryUsageDescription</key> + <string>Jami requires to access your photo library to show image on profile and send via chat</string> </dict> </plist> diff --git a/resources/entitlements/Jami.entitlements b/resources/entitlements/Jami.entitlements index 725c1ee5f..80ca05b1f 100644 --- a/resources/entitlements/Jami.entitlements +++ b/resources/entitlements/Jami.entitlements @@ -2,13 +2,13 @@ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> - <key>com.apple.security.app-sandbox</key> - <true/> <key>com.apple.security.device.camera</key> <true/> - <key>com.apple.security.device.microphone</key> + <key>com.apple.security.device.audio-input</key> <true/> <key>com.apple.security.files.user-selected.read-write</key> <true/> + <key>com.apple.security.cs.allow-jit</key> + <true/> </dict> </plist> diff --git a/sparkle/Sparkle b/sparkle/Sparkle new file mode 160000 index 000000000..2195ee088 --- /dev/null +++ b/sparkle/Sparkle @@ -0,0 +1 @@ +Subproject commit 2195ee0883efc92828a0cf33b830f43f9bd7e01b diff --git a/sparkle/dsa_pub.pem b/sparkle/dsa_pub.pem new file mode 100644 index 000000000..97455e2f2 --- /dev/null +++ b/sparkle/dsa_pub.pem @@ -0,0 +1,36 @@ +-----BEGIN PUBLIC KEY----- +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 +ZnO5P9x/rPIJLCXY4xQYBryQCMu6JC5ibWzP +-----END PUBLIC KEY----- diff --git a/sparkle/sign_update.sh b/sparkle/sign_update.sh new file mode 100755 index 000000000..bb2fbab5e --- /dev/null +++ b/sparkle/sign_update.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e +set -o pipefail +if [ "$#" -ne 2 ]; then + echo "Usage: $0 update_archive private_key" + exit 1 +fi + +openssl=/usr/bin/openssl +$openssl dgst -sha1 -binary < "$1" | $openssl dgst -dss1 -sign "$2" | base64 $BASE64_OPTS + diff --git a/sparkle/sparkle-xml-updater.sh b/sparkle/sparkle-xml-updater.sh new file mode 100755 index 000000000..62ca00323 --- /dev/null +++ b/sparkle/sparkle-xml-updater.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Take the package to add as argument ./sparkle-xml-updater.sh jami.dmg + +REPO_FOLDER=$1 +SPARKLE_FILE=$2 +REPO_URL=$3 +PACKAGE=$4 +DSA_KEY=$5 +CHANNEL_NAME=$6 +VERSION=$7 +BUILD=$8 + +if [ ! -f ${PACKAGE} -o ! -f ${DSA_KEY} ]; then + echo "Can't find package or dsa key, aborting..." + exit 1 +fi + +if [ -f ${REPO_FOLDER}/${SPARKLE_FILE} ]; then + ITEMS=$(sed -n "/<item>/,/<\/item>/p" ${REPO_FOLDER}/${SPARKLE_FILE}) +fi + +PACKAGE_SIZE=`stat -f%z ${PACKAGE}` +DATE_RFC2822=`date "+%a, %d %b %Y %T %z"` + +cat << EOFILE > ${REPO_FOLDER}/${SPARKLE_FILE} +<?xml version="1.0" encoding="utf-8"?> +<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" xmlns:dc="http://purl.org/dc/elements/1.1/"> + <channel> + <title>${CHANNEL_NAME}</title> + <link>${REPO_URL}/${SPARKLE_FILE}</link> + <description>Most recent changes with links to updates.</description> + <language>en</language> + <item> + <title>"${CHANNEL_NAME}-${BUILD}"</title> + <pubDate>$DATE_RFC2822</pubDate> + <sparkle:version>${BUILD}</sparkle:version> + <sparkle:shortVersionString>${VERSION}</sparkle:shortVersionString> + <sparkle:minimumSystemVersion>10.13</sparkle:minimumSystemVersion> + <enclosure url="${REPO_URL}/$(basename ${PACKAGE})" length="$PACKAGE_SIZE" type="application/octet-stream" sparkle:dsaSignature="$(./sign_update.sh ${PACKAGE} ${DSA_KEY})" /> + </item> +$(echo -e "${ITEMS}") + </channel> +</rss> +EOFILE + diff --git a/src/MainApplicationWindow.qml b/src/MainApplicationWindow.qml index fa337d5ee..d03d75d08 100644 --- a/src/MainApplicationWindow.qml +++ b/src/MainApplicationWindow.qml @@ -155,9 +155,11 @@ ApplicationWindow { windowSettingsLoaded = true // Quiet check for updates on start if set to. - if (UtilsAdapter.getAppValue(Settings.AutoUpdate)) { - UpdateManager.checkForUpdates(true) - UpdateManager.setAutoUpdateCheck(true) + if (Qt.platform.os.toString() !== "osx") { + if (UtilsAdapter.getAppValue(Settings.AutoUpdate)) { + UpdateManager.checkForUpdates(true) + UpdateManager.setAutoUpdateCheck(true) + } } // Handle a start URI if set as start option. @@ -190,7 +192,7 @@ ApplicationWindow { Connections { target: { - if (Qt.platform.os !== "windows" && Qt.platform.os !== "macos") + if (Qt.platform.os.toString() !== "windows" && Qt.platform.os.toString() !== "osx") return DBusErrorHandler return null } @@ -220,7 +222,7 @@ ApplicationWindow { JamiQmlUtils.mainApplicationScreen = root.screen - if (Qt.platform.os !== "windows" && Qt.platform.os !== "macos") + if (Qt.platform.os.toString() !== "windows" && Qt.platform.os.toString() !== "osx") DBusErrorHandler.setActive(true) } } diff --git a/src/connectivitymonitor.mm b/src/os/macos/connectivitymonitor.mm similarity index 100% rename from src/connectivitymonitor.mm rename to src/os/macos/connectivitymonitor.mm diff --git a/src/os/macos/updatemanager.mm b/src/os/macos/updatemanager.mm new file mode 100644 index 000000000..3ff7dfae6 --- /dev/null +++ b/src/os/macos/updatemanager.mm @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2021-2022 Savoir-faire Linux Inc. + * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.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 3 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "updatemanager.h" + +#ifdef ENABLE_SPARKLE +#include <Sparkle/Sparkle.h> +#endif + +#ifdef BETA +static constexpr bool isBeta = true; +#else +static constexpr bool isBeta = false; +#endif + +#ifdef ENABLE_SPARKLE + struct UpdateManager::Impl +{ + + Impl() + { + updaterController_ = [[SPUStandardUpdaterController alloc] initWithStartingUpdater: true + updaterDelegate: nil + userDriverDelegate: nil]; + }; + + void checkForUpdates() + { + [updaterController_ checkForUpdates: nil]; + }; + + void setAutoUpdateCheck(bool state) + { + updaterController_.updater.automaticallyChecksForUpdates = state; + }; + + bool isAutoUpdaterEnabled() + { + return updaterController_.updater.automaticallyChecksForUpdates; + }; + + bool isUpdaterEnabled() { + return true; + }; + + SPUStandardUpdaterController* updaterController_; + +}; +#else +struct UpdateManager::Impl +{ + void checkForUpdates() {}; + + void setAutoUpdateCheck(bool state) {}; + + bool isAutoUpdaterEnabled() + { + return false; + }; + bool isUpdaterEnabled() + { + return false; + }; +}; +#endif + +UpdateManager::UpdateManager(const QString& url, + ConnectivityMonitor* cm, + LRCInstance* instance, + QObject* parent) + : NetWorkManager(cm, parent) + , pimpl_(std::make_unique<Impl>()) +{} + +UpdateManager::~UpdateManager() +{} + +void +UpdateManager::checkForUpdates(bool quiet) +{ + Q_UNUSED(quiet) + pimpl_->checkForUpdates(); +} + +void +UpdateManager::applyUpdates(bool beta) +{ + Q_UNUSED(beta) +} + +void +UpdateManager::cancelUpdate() +{} + +void +UpdateManager::setAutoUpdateCheck(bool state) +{ + pimpl_->setAutoUpdateCheck(state); +} + +bool +UpdateManager::isCurrentVersionBeta() +{ + return isBeta; +} + +bool +UpdateManager::isUpdaterEnabled() +{ + return pimpl_->isUpdaterEnabled(); +} + +bool +UpdateManager::isAutoUpdaterEnabled() +{ + return pimpl_->isAutoUpdaterEnabled(); +} diff --git a/src/settingsview/components/GeneralSettingsPage.qml b/src/settingsview/components/GeneralSettingsPage.qml index 478fda3ca..82f7a93a4 100644 --- a/src/settingsview/components/GeneralSettingsPage.qml +++ b/src/settingsview/components/GeneralSettingsPage.qml @@ -23,6 +23,7 @@ import net.jami.Models 1.1 import net.jami.Adapters 1.1 import net.jami.Enums 1.1 import net.jami.Constants 1.1 +import net.jami.Helpers 1.1 import "../../commoncomponents" @@ -99,7 +100,7 @@ Rectangle { Layout.leftMargin: JamiTheme.preferredMarginSize Layout.rightMargin: JamiTheme.preferredMarginSize Layout.bottomMargin: JamiTheme.preferredMarginSize - visible: Qt.platform.os == "windows" ? true : false + visible: UpdateManager.isUpdaterEnabled() } } } diff --git a/src/settingsview/components/UpdateSettings.qml b/src/settingsview/components/UpdateSettings.qml index af3bb96a1..f56530bc1 100644 --- a/src/settingsview/components/UpdateSettings.qml +++ b/src/settingsview/components/UpdateSettings.qml @@ -50,7 +50,9 @@ ColumnLayout { Layout.fillWidth: true Layout.leftMargin: JamiTheme.preferredMarginSize - checked: UtilsAdapter.getAppValue(Settings.Key.AutoUpdate) + checked: Qt.platform.os.toString() === "windows" ? + UtilsAdapter.getAppValue(Settings.Key.AutoUpdate) : + UpdateManager.isAutoUpdaterEnabled() labelText: JamiStrings.update tooltipText: JamiStrings.enableAutoUpdates @@ -84,7 +86,7 @@ ColumnLayout { MaterialButton { id: installBetaButton - visible: !UpdateManager.isCurrentVersionBeta() + visible: !UpdateManager.isCurrentVersionBeta() && Qt.platform.os.toString() === "windows" Layout.alignment: Qt.AlignHCenter diff --git a/src/updatemanager.cpp b/src/updatemanager.cpp index 59c7f1092..b9af40444 100644 --- a/src/updatemanager.cpp +++ b/src/updatemanager.cpp @@ -1,4 +1,4 @@ -/*! +/* * Copyright (C) 2020-2022 Savoir-faire Linux Inc. * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> * @@ -38,135 +38,191 @@ static constexpr char betaVersionSubUrl[] = "/beta/version"; static constexpr char msiSubUrl[] = "/jami.release.x64.msi"; static constexpr char betaMsiSubUrl[] = "/beta/jami.beta.x64.msi"; +struct UpdateManager::Impl : public QObject +{ + Impl(const QString& url, ConnectivityMonitor* cm, LRCInstance* instance, UpdateManager& parent) + : QObject(nullptr) + , parent_(parent) + , lrcInstance_(instance) + , baseUrlString_(url.isEmpty() ? downloadUrl : url) + , tempPath_(Utils::WinGetEnv("TEMP")) + , updateTimer_(new QTimer(this)) + { + connect(updateTimer_, &QTimer::timeout, [this] { + // Quiet period update check. + parent_.checkForUpdates(true); + }); + }; + ~Impl() = default; + + void checkForUpdates(bool quiet) + { + parent_.disconnect(); + // Fail without UI if this is a programmatic check. + if (!quiet) + connect(&parent_, + &NetWorkManager::errorOccured, + &parent_, + &UpdateManager::updateCheckErrorOccurred); + + cleanUpdateFiles(); + QUrl versionUrl {isBeta ? QUrl::fromUserInput(baseUrlString_ + betaVersionSubUrl) + : QUrl::fromUserInput(baseUrlString_ + versionSubUrl)}; + parent_.get(versionUrl, [this, quiet](const QString& latestVersionString) { + if (latestVersionString.isEmpty()) { + qWarning() << "Error checking version"; + if (!quiet) + Q_EMIT parent_.updateCheckReplyReceived(false); + return; + } + auto currentVersion = QString(VERSION_STRING).toULongLong(); + auto latestVersion = latestVersionString.toULongLong(); + qDebug() << "latest: " << latestVersion << " current: " << currentVersion; + if (latestVersion > currentVersion) { + qDebug() << "New version found"; + Q_EMIT parent_.updateCheckReplyReceived(true, true); + } else { + qDebug() << "No new version found"; + if (!quiet) + Q_EMIT parent_.updateCheckReplyReceived(true, false); + } + }); + }; + + void applyUpdates(bool beta = false) + { + parent_.disconnect(); + connect(&parent_, + &NetWorkManager::errorOccured, + &parent_, + &UpdateManager::updateDownloadErrorOccurred); + connect(&parent_, &NetWorkManager::statusChanged, [this](GetStatus status) { + switch (status) { + case GetStatus::STARTED: + connect(&parent_, + &NetWorkManager::downloadProgressChanged, + &parent_, + &UpdateManager::updateDownloadProgressChanged); + Q_EMIT parent_.updateDownloadStarted(); + break; + case GetStatus::FINISHED: + Q_EMIT parent_.updateDownloadFinished(); + break; + default: + break; + } + }); + + QUrl downloadUrl {(beta || isBeta) ? QUrl::fromUserInput(baseUrlString_ + betaMsiSubUrl) + : QUrl::fromUserInput(baseUrlString_ + msiSubUrl)}; + + parent_.get( + downloadUrl, + [this, downloadUrl](const QString&) { + lrcInstance_->finish(); + Q_EMIT lrcInstance_->quitEngineRequested(); + auto args = QString(" /passive /norestart WIXNONUILAUNCH=1"); + QProcess process; + process.start("powershell ", + QStringList() << tempPath_ + "\\" + downloadUrl.fileName() << "/L*V" + << tempPath_ + "\\jami_x64_install.log" + args); + process.waitForFinished(); + }, + tempPath_); + }; + + void cancelUpdate() + { + parent_.cancelRequest(); + }; + + void setAutoUpdateCheck(bool state) + { + // Quiet check for updates periodically, if set to. + if (!state) { + updateTimer_->stop(); + return; + } + updateTimer_->start(updatePeriod); + }; + + void cleanUpdateFiles() + { + // Delete all logs and msi in the %TEMP% directory before launching. + QString dir = QString(Utils::WinGetEnv("TEMP")); + QDir log_dir(dir, {"jami*.log"}); + for (const QString& filename : log_dir.entryList()) { + log_dir.remove(filename); + } + QDir msi_dir(dir, {"jami*.msi"}); + for (const QString& filename : msi_dir.entryList()) { + msi_dir.remove(filename); + } + QDir version_dir(dir, {"version"}); + for (const QString& filename : version_dir.entryList()) { + version_dir.remove(filename); + } + }; + + UpdateManager& parent_; + + LRCInstance* lrcInstance_ {nullptr}; + QString baseUrlString_; + QString tempPath_; + QTimer* updateTimer_; +}; + UpdateManager::UpdateManager(const QString& url, ConnectivityMonitor* cm, LRCInstance* instance, QObject* parent) : NetWorkManager(cm, parent) - , lrcInstance_(instance) - , baseUrlString_(url.isEmpty() ? downloadUrl : url) - , tempPath_(Utils::WinGetEnv("TEMP")) - , updateTimer_(new QTimer(this)) + , pimpl_(std::make_unique<Impl>(url, cm, instance, *this)) +{} + +UpdateManager::~UpdateManager() {} + +void +UpdateManager::checkForUpdates(bool quiet) { - connect(updateTimer_, &QTimer::timeout, [this] { - // Quiet period update check. - checkForUpdates(true); - }); + pimpl_->checkForUpdates(quiet); } void -UpdateManager::setAutoUpdateCheck(bool state) +UpdateManager::applyUpdates(bool beta) { - // Quiet check for updates periodically, if set to. - if (!state) { - updateTimer_->stop(); - return; - } - updateTimer_->start(updatePeriod); + pimpl_->applyUpdates(beta); } -bool -UpdateManager::isCurrentVersionBeta() +void +UpdateManager::cancelUpdate() { - return isBeta; + pimpl_->cancelUpdate(); } void -UpdateManager::checkForUpdates(bool quiet) +UpdateManager::setAutoUpdateCheck(bool state) { - disconnect(); - - // Fail without UI if this is a programmatic check. - if (!quiet) - connect(this, &NetWorkManager::errorOccured, this, &UpdateManager::updateCheckErrorOccurred); - - cleanUpdateFiles(); - QUrl versionUrl {isBeta ? QUrl::fromUserInput(baseUrlString_ + betaVersionSubUrl) - : QUrl::fromUserInput(baseUrlString_ + versionSubUrl)}; - get(versionUrl, [this, quiet](const QString& latestVersionString) { - if (latestVersionString.isEmpty()) { - qWarning() << "Error checking version"; - if (!quiet) - Q_EMIT updateCheckReplyReceived(false); - return; - } - auto currentVersion = QString(VERSION_STRING).toULongLong(); - auto latestVersion = latestVersionString.toULongLong(); - qDebug() << "latest: " << latestVersion << " current: " << currentVersion; - if (latestVersion > currentVersion) { - qDebug() << "New version found"; - Q_EMIT updateCheckReplyReceived(true, true); - } else { - qDebug() << "No new version found"; - if (!quiet) - Q_EMIT updateCheckReplyReceived(true, false); - } - }); + pimpl_->setAutoUpdateCheck(state); } -void -UpdateManager::applyUpdates(bool beta) +bool +UpdateManager::isCurrentVersionBeta() { - disconnect(); - connect(this, &NetWorkManager::errorOccured, this, &UpdateManager::updateDownloadErrorOccurred); - connect(this, &NetWorkManager::statusChanged, [this](GetStatus status) { - switch (status) { - case GetStatus::STARTED: - connect(this, - &NetWorkManager::downloadProgressChanged, - this, - &UpdateManager::updateDownloadProgressChanged); - Q_EMIT updateDownloadStarted(); - break; - case GetStatus::FINISHED: - Q_EMIT updateDownloadFinished(); - break; - default: - break; - } - }); - - QUrl downloadUrl {(beta || isBeta) ? QUrl::fromUserInput(baseUrlString_ + betaMsiSubUrl) - : QUrl::fromUserInput(baseUrlString_ + msiSubUrl)}; - - get( - downloadUrl, - [this, downloadUrl](const QString&) { - lrcInstance_->finish(); - Q_EMIT lrcInstance_->quitEngineRequested(); - auto args = QString(" /passive /norestart WIXNONUILAUNCH=1"); - QProcess process; - process.start("powershell ", - QStringList() << tempPath_ + "\\" + downloadUrl.fileName() << "/L*V" - << tempPath_ + "\\jami_x64_install.log" + args); - process.waitForFinished(); - }, - tempPath_); + return isBeta; } -void -UpdateManager::cancelUpdate() +bool +UpdateManager::isUpdaterEnabled() { - cancelRequest(); +#ifdef Q_OS_WIN + return true; +#endif + return false; } -void -UpdateManager::cleanUpdateFiles() +bool +UpdateManager::isAutoUpdaterEnabled() { - /* - * Delete all logs and msi in the %TEMP% directory before launching. - */ - QString dir = QString(Utils::WinGetEnv("TEMP")); - QDir log_dir(dir, {"jami*.log"}); - for (const QString& filename : log_dir.entryList()) { - log_dir.remove(filename); - } - QDir msi_dir(dir, {"jami*.msi"}); - for (const QString& filename : msi_dir.entryList()) { - msi_dir.remove(filename); - } - QDir version_dir(dir, {"version"}); - for (const QString& filename : version_dir.entryList()) { - version_dir.remove(filename); - } + return false; } diff --git a/src/updatemanager.h b/src/updatemanager.h index d506a542c..cf8279c12 100644 --- a/src/updatemanager.h +++ b/src/updatemanager.h @@ -1,4 +1,4 @@ -/*! +/* * Copyright (C) 2020-2022 Savoir-faire Linux Inc. * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> * @@ -20,25 +20,29 @@ #include "networkmanager.h" +#include <memory> + class LRCInstance; class ConnectivityMonitor; -class QTimer; class UpdateManager final : public NetWorkManager { Q_OBJECT + Q_DISABLE_COPY(UpdateManager) public: explicit UpdateManager(const QString& url, ConnectivityMonitor* cm, LRCInstance* instance = nullptr, QObject* parent = nullptr); - ~UpdateManager() = default; + ~UpdateManager(); Q_INVOKABLE void checkForUpdates(bool quiet = false); Q_INVOKABLE void applyUpdates(bool beta = false); Q_INVOKABLE void cancelUpdate(); Q_INVOKABLE void setAutoUpdateCheck(bool state); Q_INVOKABLE bool isCurrentVersionBeta(); + Q_INVOKABLE bool isUpdaterEnabled(); + Q_INVOKABLE bool isAutoUpdaterEnabled(); Q_SIGNALS: void updateCheckReplyReceived(bool ok, bool found = false); @@ -50,13 +54,7 @@ Q_SIGNALS: void appCloseRequested(); private: - // LRCInstance pointer - LRCInstance* lrcInstance_ {nullptr}; - - QString baseUrlString_; - QString tempPath_; - QTimer* updateTimer_; - - void cleanUpdateFiles(); + struct Impl; + std::unique_ptr<Impl> pimpl_; }; Q_DECLARE_METATYPE(UpdateManager*) -- GitLab