diff --git a/JamiInstaller/Product.wxs b/JamiInstaller/Product.wxs index fe6a02bd955103e9c0fd1eec357b06d88a985104..5c2987ec84316f510192d652646438f63a01825d 100644 --- a/JamiInstaller/Product.wxs +++ b/JamiInstaller/Product.wxs @@ -4,7 +4,7 @@ <Product Id="*" Name="$(var.Name)" Language="1033" Version="$(fun.AutoVersion(1.0))" Manufacturer="$(var.Manufacturer)" UpgradeCode="7c45b52b-0390-4fe8-947a-3f13e82dd346"> <Package InstallerVersion="301" Compressed="yes" InstallScope="perMachine" /> - <MajorUpgrade AllowSameVersionUpgrades="yes" DowngradeErrorMessage="A newer version of [ProductName] is already installed." /> + <MajorUpgrade Schedule="afterInstallInitialize" AllowDowngrades="yes"/> <MediaTemplate EmbedCab="yes" CompressionLevel="high" /> <!--Icon File should be in release folder(not wix project), otherwise cannot be read--> @@ -12,6 +12,9 @@ <Property Id="ARPPRODUCTICON" Value="icon.ico" /> <Property Id="ARPNOMODIFY" Value="1" /> + <!-- It seems that QtWebEngineProcess.exe versioning requires us to force reinstall. --> + <Property Id="REINSTALLMODE" Value="dm" /> + <Feature Id="ProductFeature" Title="Main" Level="1" Absent="disallow"> <ComponentGroupRef Id="StandardComponents" Primary="yes" /> <ComponentGroupRef Id="HeatGenerated" /> @@ -38,7 +41,13 @@ <WixVariable Id="WixUIDialogBmp" Value="main-banner.bmp" /> <WixVariable Id="WixUISupportPerUser" Value="0" /> - <CustomAction Id="removeOldJamiFiles" Directory="APPLICATIONFOLDER" ExeCommand="cmd /c "del vc_redist.x64.exe; del uninstall.exe; del WinSparkle.dll;"" Execute="deferred" Return="ignore" HideTarget="no" Impersonate="no" /> + <CustomAction Id="removeOldJamiFiles" + Directory="APPLICATIONFOLDER" + ExeCommand="cmd /c "del vc_redist.x64.exe; del uninstall.exe; del WinSparkle.dll;"" + Execute="deferred" + Return="ignore" + HideTarget="no" + Impersonate="no"/> <Property Id="QtExecCmdLine" Value='"[WindowsFolder]\System32\taskkill.exe" /F /IM QtWebEngineProcess.exe /IM Jami.exe'/> <CustomAction Id="JamiProcesses.TaskKill" diff --git a/jami-qt.pro b/jami-qt.pro index d3933c6e7bed8c4b2f275949937ed8a7522dace1..762114c51274b5109797b0a3077cb91f95828ba3 100644 --- a/jami-qt.pro +++ b/jami-qt.pro @@ -111,7 +111,9 @@ unix { # Input HEADERS += \ + src/networkmanager.h \ src/smartlistmodel.h \ + src/updatemanager.h \ src/utils.h \ src/bannedlistmodel.h \ src/version.h \ @@ -159,7 +161,9 @@ HEADERS += \ SOURCES += \ src/bannedlistmodel.cpp \ src/accountlistmodel.cpp \ + src/networkmanager.cpp \ src/runguard.cpp \ + src/updatemanager.cpp \ src/webchathelpers.cpp \ src/main.cpp \ src/smartlistmodel.cpp \ diff --git a/src/MainApplicationWindow.qml b/src/MainApplicationWindow.qml index 8d0da0d6f74fe4ec10ef3729e150fc1890d99808..632e0355a3340147621a5683b58606a55cf6f8d9 100644 --- a/src/MainApplicationWindow.qml +++ b/src/MainApplicationWindow.qml @@ -15,6 +15,8 @@ import "commoncomponents" ApplicationWindow { id: root + property ApplicationWindow appWindow: root + AccountMigrationDialog{ id: accountMigrationDialog @@ -25,11 +27,11 @@ ApplicationWindow { } } - function close() { + function close(force = false) { // If we're in the onboarding wizard or 'MinimizeOnClose' // is set, then we can quit - if (!SettingsAdapter.getAppValue(Settings.MinimizeOnClose) || - !UtilsAdapter.getAccountListSize()) { + if (force || !SettingsAdapter.getAppValue(Settings.MinimizeOnClose) || + !UtilsAdapter.getAccountListSize()) { Qt.quit() } else { // hide to the systray diff --git a/src/commoncomponents/SimpleMessageDialog.qml b/src/commoncomponents/SimpleMessageDialog.qml index 01a0c72b09c0773f385c9348a762fb5599e34969..5ee049ffb95eac7fd35a815c0880b1ae44e5f68f 100644 --- a/src/commoncomponents/SimpleMessageDialog.qml +++ b/src/commoncomponents/SimpleMessageDialog.qml @@ -35,27 +35,29 @@ BaseDialog { property var buttonTitles: [] property var buttonCallBacks: [] property var buttonStyles: [] - property alias description: descriptionText.text + property alias infoText: infoText.text + property alias innerContentData: innerContent.data - function openWithParameters(title, info) { + function openWithParameters(title, info = "") { root.title = title - descriptionText.text = info + if (info !== "") + root.infoText = info open() } contentItem: Rectangle { - id: simpleMessageDialogContentRect + id: container implicitWidth: Math.max(JamiTheme.preferredDialogWidth, buttonTitles.length * (JamiTheme.preferredFieldWidth / 2 + JamiTheme.preferredMarginSize)) - implicitHeight: JamiTheme.preferredDialogHeight / 2 + implicitHeight: JamiTheme.preferredDialogHeight / 2 - JamiTheme.preferredMarginSize ColumnLayout { anchors.fill: parent Label { - id: descriptionText + id: infoText Layout.alignment: Qt.AlignCenter Layout.preferredWidth: JamiTheme.preferredDialogWidth - JamiTheme.preferredMarginSize @@ -67,6 +69,13 @@ BaseDialog { verticalAlignment: Text.AlignVCenter } + Item { + id: innerContent + Layout.topMargin: JamiTheme.preferredMarginSize / 2 + Layout.fillWidth: true + Layout.fillHeight: true + } + RowLayout { spacing: JamiTheme.preferredMarginSize diff --git a/src/connectivitymonitor.cpp b/src/connectivitymonitor.cpp index 04c048acd58e6fb3728faef2cd2e2b673f0df9c8..014faa4fc3dcd967c942c6523f4a28eef4493664 100644 --- a/src/connectivitymonitor.cpp +++ b/src/connectivitymonitor.cpp @@ -18,9 +18,8 @@ #include "connectivitymonitor.h" -#include <QDebug> - #ifdef Q_OS_WIN +#include <QDebug> #include <atlbase.h> #include <netlistmgr.h> @@ -159,4 +158,4 @@ ConnectivityMonitor::~ConnectivityMonitor() destroy(); CoUninitialize(); } -#endif // Q_OS_WIN \ No newline at end of file +#endif // Q_OS_WIN diff --git a/src/connectivitymonitor.h b/src/connectivitymonitor.h index 916b4666c00da6350aad16d8b48de111e2deef5a..f746669e8750d59aa9f755911aa7053c3ead88a9 100644 --- a/src/connectivitymonitor.h +++ b/src/connectivitymonitor.h @@ -21,10 +21,9 @@ #include <QObject> #ifdef Q_OS_WIN -class ConnectivityMonitor : public QObject +class ConnectivityMonitor final : public QObject { Q_OBJECT - public: explicit ConnectivityMonitor(QObject* parent = 0); ~ConnectivityMonitor(); @@ -43,4 +42,22 @@ private: class NetworkEventHandler* netEventHandler_; unsigned long cookie_; }; -#endif // Q_OS_WIN \ No newline at end of file + +#else +// Dummy implementation for non-Windows platforms. +// TODO: platform implementations should be in the daemon. + +// clang-format off +class ConnectivityMonitor final : public QObject +{ + Q_OBJECT +public: + explicit ConnectivityMonitor(QObject* parent = 0) : QObject(parent) {}; + ~ConnectivityMonitor() = default; + + bool isOnline() { return false; }; +signals: + void connectivityChanged(); +}; +// clang-format on +#endif // Q_OS_WIN diff --git a/src/constant/JamiStrings.qml b/src/constant/JamiStrings.qml index e3a2df84470c0281807d94c5ee1ed5a3e9cd8309..19a0d1d155a6f6bfa6fa23a9d721fad555b95b3f 100644 --- a/src/constant/JamiStrings.qml +++ b/src/constant/JamiStrings.qml @@ -268,9 +268,22 @@ Item { property string tipChooseDownloadFolder: qsTr("Choose download directory") property string recordCall: qsTr("Record call") - // UpdateSettings + // Updates property string betaInstall: qsTr("Install beta version") + property string checkForUpdates: qsTr("Check for updates now") property string enableAutoUpdates: qsTr("Enable/Disable automatic updates") + property string tipAutoUpdate: qsTr("toggle automatic updates") + property string updatesTitle: qsTr("Updates") + property string updateDialogTitle: qsTr("Update") + property string updateFound: qsTr("A new version of Jami was found\n Would you like to update now?") + property string updateNotFound: qsTr("No new version of Jami was found") + property string updateCheckError: qsTr("An error occured when checking for a new version") + property string updateDownloadNetworkError: qsTr("Installer download failed due to a network error") + property string updateDownloadCanceled: qsTr("Installer download canceled") + property string updateDownloading: "Downloading" + property string confirmBeta: qsTr("This will uninstall your current Release version and you can always download the latest Release version on our website") + property string networkDisconnected: qsTr("Network disconnected") + property string genericError: qsTr("Something went wrong") // Recording Settings property string tipRecordFolder: qsTr("Select a record directory") @@ -365,4 +378,8 @@ Item { // Update settings property string update: qsTr("Automatically check for updates") + + // Generic dialog options + property string optionOk: qsTr("Ok") + property string optionCancel: qsTr("Cancel") } diff --git a/src/constant/JamiTheme.qml b/src/constant/JamiTheme.qml index b4b7bf2907185b8d1c2ff726dae12dfe7d150765..b9a3707f2f107a5128aa237512226d63801c1f86 100644 --- a/src/constant/JamiTheme.qml +++ b/src/constant/JamiTheme.qml @@ -32,7 +32,7 @@ Item { property string notificationRed: "#ff3b30" property string unPresenceOrange: "orange" property string backgroundColor: lightGrey_ - property string backgroundDarkColor: lightGreyTab_ + property string backgroundDarkColor: rgb256(220, 220, 220) property string screenSelectionBorderGreen: "green" @@ -97,28 +97,21 @@ Item { property int preferredDialogWidth: 400 property int preferredDialogHeight: 300 + // Misc. + property color white: "white" + property color darkGrey: rgb256(63, 63, 63) + // Jami theme colors function rgb256(r, g, b) { - return Qt.rgba(r / 256, g / 256, b / 256, 1) + return Qt.rgba(r / 255, g / 255, b / 255, 1.0) } - property color blue_: "#109ede" property color wizardBlueButtons: "#28b1ed" property color blueLogo_: rgb256(0, 7, 71) - property color lightBlue_: "#c1ebf0" property color lightGrey_: rgb256(242, 242, 242) - property color lightGreyTab_: rgb256(220, 220, 220) - property color imGrey_: "#dedee0" - property color imBlue_: "#cfebf5" - property color lightBlack_: rgb256(63, 63, 63) property color grey_: rgb256(160, 160, 160) property color red_: rgb256(251, 72, 71) - property color lightRed_: rgb256(252, 91, 90) - property color darkRed_: rgb256(219, 55, 54) - property color notificationRed_: rgb256(255, 59, 48) property color urgentOrange_: rgb256(255, 165, 0) property color green_: rgb256(127, 255, 0) property color presenceGreen_: rgb256(76, 217, 100) - property color smartlistSelection_: rgb256(240, 240, 240) - property color smartlistHighlight_: rgb256(245, 245, 245) } diff --git a/src/lrcinstance.h b/src/lrcinstance.h index 145cb6bf5d4aa383e8a3a3a644cd25d8f772d294..ebebd20da893a49e52666e74d44f431146331430 100644 --- a/src/lrcinstance.h +++ b/src/lrcinstance.h @@ -25,6 +25,7 @@ #endif #include "accountlistmodel.h" +#include "updatemanager.h" #include "rendermanager.h" #include "appsettingsmanager.h" #include "utils.h" @@ -55,6 +56,8 @@ #include <memory> +class ConnectivityMonitor; + using namespace lrc::api; using migrateCallback = std::function<void()>; @@ -65,15 +68,21 @@ class LRCInstance : public QObject Q_OBJECT public: - static LRCInstance& instance(migrateCallback willMigrate = {}, migrateCallback didMigrate = {}) + static LRCInstance& instance(migrateCallback willMigrate = {}, + migrateCallback didMigrate = {}, + const QString& updateUrl = {}, + ConnectivityMonitor* connectivityMonitor = {}) { - static LRCInstance instance_(willMigrate, didMigrate); + static LRCInstance instance_(willMigrate, didMigrate, updateUrl, connectivityMonitor); return instance_; } - static void init(migrateCallback willMigrate = {}, migrateCallback didMigrate = {}) + static void init(migrateCallback willMigrate = {}, + migrateCallback didMigrate = {}, + const QString& updateUrl = {}, + ConnectivityMonitor* connectivityMonitor = {}) { - instance(willMigrate, didMigrate); + instance(willMigrate, didMigrate, updateUrl, connectivityMonitor); } static Lrc& getAPI() @@ -86,6 +95,11 @@ public: return instance().renderer_.get(); } + static UpdateManager* getUpdateManager() + { + return instance().updateManager_.get(); + } + static void connectivityChanged() { instance().lrc_->connectivityChanged(); @@ -443,16 +457,22 @@ signals: void restoreAppRequested(); void notificationClicked(bool forceToTop = false); void updateSmartList(); + void quitEngineRequested(); private: - LRCInstance(migrateCallback willMigrateCb = {}, migrateCallback didMigrateCb = {}) + LRCInstance(migrateCallback willMigrateCb = {}, + migrateCallback didMigrateCb = {}, + const QString& updateUrl = {}, + ConnectivityMonitor* connectivityMonitor = {}) { lrc_ = std::make_unique<Lrc>(willMigrateCb, didMigrateCb); renderer_ = std::make_unique<RenderManager>(lrc_->getAVModel()); + updateManager_ = std::make_unique<UpdateManager>(updateUrl, connectivityMonitor); }; std::unique_ptr<Lrc> lrc_; std::unique_ptr<RenderManager> renderer_; + std::unique_ptr<UpdateManager> updateManager_; AccountListModel accountListModel_; QString selectedAccountId_; QString selectedConvUid_; diff --git a/src/main.cpp b/src/main.cpp index 12a7fbd4b90988f49de1f5e530a959f012878f41..20ca27dde922613ba6ce88dd62bb7612491ebb10 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,7 @@ #include "mainapplication.h" #include "runguard.h" +#include "version.h" #include <QCryptographicHash> #include <QtWebEngine> @@ -60,14 +61,12 @@ main(int argc, char* argv[]) QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QApplication::setQuitOnLastWindowClosed(false); QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); + QCoreApplication::setApplicationVersion(QString(VERSION_STRING)); QApplication::setHighDpiScaleFactorRoundingPolicy( Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); QtWebEngine::initialize(); - // Allow QtWebEngine to load local resources. - char ARG_DISABLE_WEB_SECURITY[] = "--disable-web-security"; - auto newArgv = parseInputArgument(argc, argv, ARG_DISABLE_WEB_SECURITY); - MainApplication app(argc, newArgv); + MainApplication app(argc, argv); /* * Runguard to make sure that only one instance runs at a time. diff --git a/src/mainapplication.cpp b/src/mainapplication.cpp index e27965c1a99318e44d8d3c6b75b96e85ebbaffec..bdb6676fe560dc4ae2e869c91863177375c0c0d1 100644 --- a/src/mainapplication.cpp +++ b/src/mainapplication.cpp @@ -22,14 +22,18 @@ #include "mainapplication.h" #include "appsettingsmanager.h" -#include "globalinstances.h" +#include "connectivitymonitor.h" #include "globalsystemtray.h" #include "qmlregister.h" #include "qrimageprovider.h" #include "pixbufmanipulator.h" #include "tintedbuttonimageprovider.h" +#include "globalinstances.h" + #include <QAction> +#include <QCommandLineParser> +#include <QCoreApplication> #include <QFontDatabase> #include <QMenu> #include <QQmlContext> @@ -45,6 +49,15 @@ #include <gnutls/gnutls.h> #endif +namespace opts { +// Keys used to store command-line options. +constexpr static const char STARTMINIMIZED[] = "STARTMINIMIZED"; +constexpr static const char DEBUG[] = "DEBUG"; +constexpr static const char DEBUGCONSOLE[] = "DEBUGCONSOLE"; +constexpr static const char DEBUGFILE[] = "DEBUGFILE"; +constexpr static const char UPDATEURL[] = "UPDATEURL"; +} // namespace opts + static void consoleDebug() { @@ -106,6 +119,7 @@ fileDebug(QFile* debugFile) MainApplication::MainApplication(int& argc, char** argv) : QApplication(argc, argv) , engine_(new QQmlApplicationEngine()) + , connectivityMonitor_(new ConnectivityMonitor(this)) { QObject::connect(this, &QApplication::aboutToQuit, [this] { cleanup(); }); } @@ -120,10 +134,10 @@ MainApplication::init() setenv("QT_QPA_PLATFORMTHEME", "gtk3", true); #endif - for (auto string : QCoreApplication::arguments()) { - if (string == "-d" || string == "--debug") { - consoleDebug(); - } + auto results = parseArguments(); + + if (results[opts::DEBUG].toBool()) { + consoleDebug(); } Utils::removeOldVersions(); @@ -135,31 +149,33 @@ MainApplication::init() #endif GlobalInstances::setPixmapManipulator(std::make_unique<PixbufManipulator>()); - initLrc(); - - initConnectivityMonitor(); + initLrc(results[opts::UPDATEURL].toString(), connectivityMonitor_); -#ifdef Q_OS_WINDOWS - QObject::connect(&LRCInstance::instance(), &LRCInstance::notificationClicked, [] { - for (QWindow* appWindow : qApp->allWindows()) { - if (appWindow->objectName().compare("mainViewWindow")) - continue; - // clang-format off - ::SetWindowPos((HWND) appWindow->winId(), - HWND_TOPMOST, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); - ::SetWindowPos((HWND) appWindow->winId(), - HWND_NOTOPMOST, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); - // clang-format on - return; - } +#ifdef Q_OS_WIN + connect(connectivityMonitor_, &ConnectivityMonitor::connectivityChanged, [] { + LRCInstance::connectivityChanged(); }); -#endif +#endif // Q_OS_WIN - bool startMinimized {false}; - parseArguments(startMinimized); + QObject::connect( + &LRCInstance::instance(), + &LRCInstance::quitEngineRequested, + this, + [this] { engine_->quit(); }, + Qt::DirectConnection); + + if (results[opts::DEBUGFILE].toBool()) { + debugFile_.reset(new QFile(getDebugFilePath())); + debugFile_->open(QIODevice::WriteOnly | QIODevice::Truncate); + debugFile_->close(); + fileDebug(debugFile_.get()); + } + if (results[opts::DEBUGCONSOLE].toBool()) { + vsConsoleDebug(); + } + + connectForceWindowToTop(); initSettings(); initSystray(); initQmlEngine(); @@ -205,7 +221,7 @@ MainApplication::loadTranslations() } void -MainApplication::initLrc() +MainApplication::initLrc(const QString& downloadUrl, ConnectivityMonitor* cm) { /* * Init mainwindow and finish splash when mainwindow shows up. @@ -226,51 +242,59 @@ MainApplication::initLrc() std::this_thread::sleep_for(std::chrono::milliseconds(10)); } isMigrating = false; - }); + }, + downloadUrl, + cm); LRCInstance::subscribeToDebugReceived(); LRCInstance::getAPI().holdConferences = false; } -void -MainApplication::initConnectivityMonitor() +const QVariantMap +MainApplication::parseArguments() { -#ifdef Q_OS_WIN - connectivityMonitor_.reset(new ConnectivityMonitor(this)); - connect(connectivityMonitor_.get(), &ConnectivityMonitor::connectivityChanged, [] { - LRCInstance::connectivityChanged(); - }); -#endif // Q_OS_WIN -} + QVariantMap results; + QCommandLineParser parser; + parser.addHelpOption(); + parser.addVersionOption(); + + QCommandLineOption minimizedOption(QStringList() << "m" + << "minimized", + "Start minimized."); + parser.addOption(minimizedOption); + + QCommandLineOption debugOption(QStringList() << "d" + << "debug", + "Debug out."); + parser.addOption(debugOption); -void -MainApplication::parseArguments(bool& startMinimized) -{ - QString uri = ""; - - for (auto string : QCoreApplication::arguments()) { - if (string.startsWith("jami:")) { - uri = string; - } else { - if (string == "-m" || string == "--minimized") { - startMinimized = true; - } #ifdef Q_OS_WINDOWS - debugFile_.reset(new QFile(getDebugFilePath())); - auto dbgFile = string == "-f" || string == "--file"; - auto dbgConsole = string == "-c" || string == "--vsconsole"; - if (dbgFile || dbgConsole) { - if (dbgFile) { - debugFile_->open(QIODevice::WriteOnly | QIODevice::Truncate); - debugFile_->close(); - fileDebug(debugFile_.get()); - } - if (dbgConsole) { - vsConsoleDebug(); - } - } + QCommandLineOption debugConsoleOption(QStringList() << "c" + << "console", + "Debug out to IDE console."); + parser.addOption(debugConsoleOption); + + QCommandLineOption debugFileOption(QStringList() << "f" + << "file", + "Debug to file."); + parser.addOption(debugFileOption); + + QCommandLineOption updateUrlOption(QStringList() << "u" + << "url", + "Reference <url> for client versioning.", + "url"); + parser.addOption(updateUrlOption); #endif - } - } + + parser.process(*this); + + results[opts::STARTMINIMIZED] = parser.isSet(minimizedOption); + results[opts::DEBUG] = parser.isSet(debugOption); +#ifdef Q_OS_WINDOWS + results[opts::DEBUGCONSOLE] = parser.isSet(debugConsoleOption); + results[opts::DEBUGFILE] = parser.isSet(debugFileOption); + results[opts::UPDATEURL] = parser.value(updateUrlOption); +#endif + return results; } void @@ -333,3 +357,25 @@ MainApplication::cleanup() #endif QApplication::exit(0); } + +void +MainApplication::connectForceWindowToTop() +{ +#ifdef Q_OS_WINDOWS + QObject::connect(&LRCInstance::instance(), &LRCInstance::notificationClicked, [] { + for (QWindow* appWindow : qApp->allWindows()) { + if (appWindow->objectName().compare("mainViewWindow")) + continue; + // clang-format off + ::SetWindowPos((HWND) appWindow->winId(), + HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + ::SetWindowPos((HWND) appWindow->winId(), + HWND_NOTOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + // clang-format on + return; + } + }); +#endif +} diff --git a/src/mainapplication.h b/src/mainapplication.h index fd72c947bd650c882a4ac12029a26ad19705ddf1..5c3585e9658ad4e6270d1638804a4302294f1b3e 100644 --- a/src/mainapplication.h +++ b/src/mainapplication.h @@ -1,4 +1,4 @@ -/* +/*! * Copyright (C) 2020 by Savoir-faire Linux * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com> * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> @@ -20,8 +20,6 @@ #pragma once -#include "connectivitymonitor.h" - #include <QFile> #include <QApplication> #include <QQmlApplicationEngine> @@ -29,6 +27,8 @@ #include <memory> +class ConnectivityMonitor; + class MainApplication : public QApplication { Q_OBJECT @@ -41,19 +41,17 @@ public: private: void loadTranslations(); - void initLrc(); - void initConnectivityMonitor(); - void parseArguments(bool& startMinimized); + void initLrc(const QString& downloadUrl, ConnectivityMonitor* cm); + const QVariantMap parseArguments(); void setApplicationFont(); void initQmlEngine(); void initSettings(); void initSystray(); void cleanup(); + void connectForceWindowToTop(); private: -#ifdef Q_OS_WIN - QScopedPointer<ConnectivityMonitor> connectivityMonitor_; -#endif // Q_OS_WIN QScopedPointer<QFile> debugFile_; QQmlApplicationEngine* engine_; + ConnectivityMonitor* connectivityMonitor_; }; diff --git a/src/networkmanager.cpp b/src/networkmanager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9f4a3f7337495d116829096ef062fa1f0652928b --- /dev/null +++ b/src/networkmanager.cpp @@ -0,0 +1,166 @@ +/*! + * Copyright (C) 2019-2020 by Savoir-faire Linux + * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> + * Author: Andreas Traczyk <andreas.traczyk@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 "networkmanager.h" + +#include "connectivitymonitor.h" +#include "utils.h" + +#include <QMetaEnum> +#include <QtNetwork> + +NetWorkManager::NetWorkManager(ConnectivityMonitor* cm, QObject* parent) + : QObject(parent) + , manager_(new QNetworkAccessManager(this)) + , reply_(nullptr) + , connectivityMonitor_(cm) +{ + emit statusChanged(GetStatus::IDLE); + connect(connectivityMonitor_, &ConnectivityMonitor::connectivityChanged, [this] { + auto connected = connectivityMonitor_->isOnline(); + if (connected && !lastConnectionState_) { + manager_->deleteLater(); + manager_ = new QNetworkAccessManager(this); + qWarning() << "connectivity changed, reset QNetworkAccessManager"; + } + lastConnectionState_ = connected; + }); +} + +void +NetWorkManager::get(const QUrl& url, const DoneCallBack& doneCb, const QString& path) +{ + if (!connectivityMonitor_->isOnline()) { + emit errorOccured(GetError::DISCONNECTED); + return; + } + + if (reply_ && reply_->isRunning()) { + qWarning() << Q_FUNC_INFO << "currently downloading"; + return; + } else if (url.isEmpty()) { + qWarning() << Q_FUNC_INFO << "missing url"; + return; + } + + if (!path.isEmpty()) { + QFileInfo fileInfo(url.path()); + QString fileName = fileInfo.fileName(); + + file_.reset(new QFile(path + "/" + fileName)); + if (!file_->open(QIODevice::WriteOnly)) { + emit errorOccured(GetError::ACCESS_DENIED); + file_.reset(nullptr); + return; + } + } + + QNetworkRequest request(url); + reply_ = manager_->get(request); + + emit statusChanged(GetStatus::STARTED); + + connect(reply_, + QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::errorOccurred), + [this, doneCb, path](QNetworkReply::NetworkError error) { + reply_->disconnect(); + reset(true); + qWarning() << Q_FUNC_INFO << "NetworkError: " + << QMetaEnum::fromType<QNetworkReply::NetworkError>().valueToKey(error); + emit errorOccured(GetError::NETWORK_ERROR); + }); + + connect(reply_, &QNetworkReply::finished, [this, doneCb, path]() { + reply_->disconnect(); + QString response = {}; + if (path.isEmpty()) + response = QString(reply_->readAll()); + reset(!path.isEmpty()); + emit statusChanged(GetStatus::FINISHED); + if (doneCb) + doneCb(response); + }); + + connect(reply_, + &QNetworkReply::downloadProgress, + this, + &NetWorkManager::downloadProgressChanged); + + connect(reply_, &QNetworkReply::readyRead, this, &NetWorkManager::onHttpReadyRead); + +#if QT_CONFIG(ssl) + connect(reply_, + SIGNAL(sslErrors(const QList<QSslError>&)), + this, + SLOT(onSslErrors(QList<QSslError>)), + Qt::UniqueConnection); +#endif +} + +void +NetWorkManager::reset(bool flush) +{ + reply_->deleteLater(); + reply_ = nullptr; + if (file_ && flush) { + file_->flush(); + file_->close(); + file_.reset(nullptr); + } +} + +void +NetWorkManager::onSslErrors(const QList<QSslError>& sslErrors) +{ +#if QT_CONFIG(ssl) + QString errorsString; + for (const QSslError& error : sslErrors) { + if (errorsString.length() > 0) { + errorsString += "\n"; + } + errorsString += error.errorString(); + } + emit errorOccured(GetError::SSL_ERROR, errorsString); + return; +#else + Q_UNUSED(sslErrors); +#endif +} + +void +NetWorkManager::onHttpReadyRead() +{ + /* + * This slot gets called every time the QNetworkReply has new data. + * We read all of its new data and write it into the file. + * That way we use less RAM than when reading it at the finished() + * signal of the QNetworkReply + */ + if (file_) + file_->write(reply_->readAll()); +} + +void +NetWorkManager::cancelRequest() +{ + if (reply_) { + reply_->abort(); + emit errorOccured(GetError::CANCELED); + } +} diff --git a/src/networkmanager.h b/src/networkmanager.h new file mode 100644 index 0000000000000000000000000000000000000000..b5ddab5f18626a0a1d0d4e2528fe6c9957aaa01c --- /dev/null +++ b/src/networkmanager.h @@ -0,0 +1,76 @@ +/*! + * Copyright (C) 2019-2020 by Savoir-faire Linux + * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> + * Author: Andreas Traczyk <andreas.traczyk@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/>. + */ + +#pragma once + +#include <QObject> +#include <QFile> +#include <QSslError> +#include <QNetworkReply> + +class QNetworkAccessManager; +class ConnectivityMonitor; + +class NetWorkManager : public QObject +{ + Q_OBJECT +public: + explicit NetWorkManager(ConnectivityMonitor* cm, QObject* parent = nullptr); + virtual ~NetWorkManager() = default; + + enum GetStatus { IDLE, STARTED, FINISHED }; + + enum GetError { DISCONNECTED, NETWORK_ERROR, ACCESS_DENIED, SSL_ERROR, CANCELED }; + Q_ENUM(GetError) + + using DoneCallBack = std::function<void(const QString&)>; + + /*! + * using qt get request to store the reply in file + * @param url - network address + * @param doneCb - done callback + * @param path - optional file saving path, if empty + * a string will be passed as the second paramter of doneCb + */ + void get(const QUrl& url, const DoneCallBack& doneCb = {}, const QString& path = {}); + + /*! + * manually abort the current request + */ + Q_INVOKABLE void cancelRequest(); + +signals: + void statusChanged(GetStatus error); + void downloadProgressChanged(qint64 bytesRead, qint64 totalBytes); + void errorOccured(GetError error, const QString& msg = {}); + +private slots: + void onSslErrors(const QList<QSslError>& sslErrors); + void onHttpReadyRead(); + +private: + void reset(bool flush = true); + + QNetworkAccessManager* manager_; + QNetworkReply* reply_; + QScopedPointer<QFile> file_; + ConnectivityMonitor* connectivityMonitor_; + bool lastConnectionState_; +}; +Q_DECLARE_METATYPE(NetWorkManager*) diff --git a/src/qmlregister.cpp b/src/qmlregister.cpp index 29ef8019b2778db9f472aadaa0383702f40edf77..5130a11f49d016989f7fd8a5b70fddeb489dfd59 100644 --- a/src/qmlregister.cpp +++ b/src/qmlregister.cpp @@ -35,6 +35,7 @@ #include "mediahandleritemlistmodel.h" #include "messagesadapter.h" #include "namedirectory.h" +#include "updatemanager.h" #include "preferenceitemlistmodel.h" #include "pluginitemlistmodel.h" #include "pluginlistpreferencemodel.h" @@ -137,6 +138,7 @@ registerTypes() QML_REGISTERSINGLETONTYPE_CUSTOM("net.jami.Models", AVModel, 1, 0, &LRCInstance::avModel()) QML_REGISTERSINGLETONTYPE_CUSTOM("net.jami.Models", PluginModel, 1, 0, &LRCInstance::pluginModel()) QML_REGISTERSINGLETONTYPE_CUSTOM("net.jami.Models", RenderManager, 1, 0, LRCInstance::renderer()) + QML_REGISTERSINGLETONTYPE_CUSTOM("net.jami.Models", UpdateManager, 1, 0, LRCInstance::getUpdateManager()) /* * Qml singleton components @@ -179,5 +181,6 @@ registerTypes() * Enums */ QML_REGISTERUNCREATABLE("net.jami.Enums", Settings, 1, 0); + QML_REGISTERUNCREATABLE("net.jami.Enums", NetWorkManager, 1, 0); } // clang-format on diff --git a/src/settingsview/components/GeneralSettingsPage.qml b/src/settingsview/components/GeneralSettingsPage.qml index 1dc91db2ac73026e594f680997e0c4c90dc8802d..be925d8266acfc0903f1818023b4cee2c2849d81 100644 --- a/src/settingsview/components/GeneralSettingsPage.qml +++ b/src/settingsview/components/GeneralSettingsPage.qml @@ -87,6 +87,7 @@ Rectangle { UpdateSettings { Layout.fillWidth: true Layout.leftMargin: JamiTheme.preferredMarginSize + Layout.rightMargin: JamiTheme.preferredMarginSize Layout.bottomMargin: JamiTheme.preferredMarginSize visible: Qt.platform.os == "windows"? true : false } diff --git a/src/settingsview/components/LinkedDevices.qml b/src/settingsview/components/LinkedDevices.qml index 759843d23861ec0a4adeb23ebe5b1faa1dd38a1d..85434d235b9e5bd5d1d49cda8092eb3590a0161e 100644 --- a/src/settingsview/components/LinkedDevices.qml +++ b/src/settingsview/components/LinkedDevices.qml @@ -94,7 +94,7 @@ ColumnLayout { property string idOfDev: "" title: qsTr("Remove Device") - description: qsTr("Are you sure you wish to remove this device?") + infoText: qsTr("Are you sure you wish to remove this device?") buttonTitles: [qsTr("Ok"), qsTr("Cancel")] buttonStyles: [SimpleMessageDialog.ButtonStyle.TintedBlue, diff --git a/src/settingsview/components/RecordingSettings.qml b/src/settingsview/components/RecordingSettings.qml index 879c7496f6e031b79da811c68abb8d6b82e41212..897163d49076738ea2d3fa78c2aea9173085d9b6 100644 --- a/src/settingsview/components/RecordingSettings.qml +++ b/src/settingsview/components/RecordingSettings.qml @@ -72,6 +72,7 @@ ColumnLayout { ToggleSwitch { id: alwaysRecordingCheckBox + Layout.fillWidth: true Layout.leftMargin: JamiTheme.preferredMarginSize @@ -85,6 +86,7 @@ ColumnLayout { ToggleSwitch { id: recordPreviewCheckBox + Layout.fillWidth: true Layout.leftMargin: JamiTheme.preferredMarginSize @@ -185,4 +187,4 @@ ColumnLayout { onClicked: recordPathDialog.open() } } -} \ No newline at end of file +} diff --git a/src/settingsview/components/UpdateSettings.qml b/src/settingsview/components/UpdateSettings.qml index 21e6b8efb7ca222a56d63808149664b02a12acd2..849e8e6daeab279d03423c028131854e3c6ec505 100644 --- a/src/settingsview/components/UpdateSettings.qml +++ b/src/settingsview/components/UpdateSettings.qml @@ -1,6 +1,7 @@ /* * Copyright (C) 2020 by Savoir-faire Linux * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * Author: Andreas Traczyk <andreas.traczyk@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 @@ -17,30 +18,23 @@ */ import QtQuick 2.15 -import QtQuick.Window 2.14 import QtQuick.Controls 2.15 +import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Universal 2.12 import QtQuick.Layouts 1.3 -import QtGraphicalEffects 1.14 -import QtQuick.Controls.Styles 1.4 -import net.jami.Models 1.0 import net.jami.Adapters 1.0 -import Qt.labs.platform 1.1 import net.jami.Enums 1.0 +import net.jami.Models 1.0 import "../../commoncomponents" ColumnLayout { id: root - //TODO: complete check for update and check for Beta slot functions - function checkForUpdateSlot() {} - function installBetaSlot() {} - Label { Layout.fillWidth: true Layout.preferredHeight: JamiTheme.preferredFieldHeight - text: qsTr("Updates") + text: JamiStrings.updatesTitle font.pointSize: JamiTheme.headerFontSize font.kerning: true @@ -51,48 +45,205 @@ ColumnLayout { ToggleSwitch { id: autoUpdateCheckBox + Layout.fillWidth: true + Layout.leftMargin: JamiTheme.preferredMarginSize + checked: SettingsAdapter.getAppValue(Settings.Key.AutoUpdate) - labelText: JamiStrings.update - fontPointSize: JamiTheme.settingsFontSize + labelText: JamiStrings.update tooltipText: JamiStrings.enableAutoUpdates + fontPointSize: JamiTheme.settingsFontSize - onSwitchToggled: SettingsAdapter.setAppValue(Settings.Key.AutoUpdate, checked) + onSwitchToggled: { + SettingsAdapter.setAppValue(Settings.Key.AutoUpdate, checked) + UpdateManager.setAutoUpdateCheck(checked) + } } - HoverableRadiusButton { + MaterialButton { id: checkUpdateButton Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: JamiTheme.preferredFieldWidth Layout.preferredHeight: JamiTheme.preferredFieldHeight - radius: height / 2 + color: enabled? JamiTheme.buttonTintedBlack : JamiTheme.buttonTintedGrey + hoveredColor: JamiTheme.buttonTintedBlackHovered + pressedColor: JamiTheme.buttonTintedBlackPressed + outlined: true - toolTipText: qsTr("Check for updates now") - text: qsTr("Updates") - fontPointSize: JamiTheme.buttonFontSize + toolTipText: JamiStrings.checkForUpdates + text: JamiStrings.checkForUpdates - onClicked: { - checkForUpdateSlot() - } + onClicked: UpdateManager.checkForUpdates() } - HoverableRadiusButton { + MaterialButton { id: installBetaButton Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: JamiTheme.preferredFieldWidth Layout.preferredHeight: JamiTheme.preferredFieldHeight - radius: height / 2 + color: enabled? JamiTheme.buttonTintedBlack : JamiTheme.buttonTintedGrey + hoveredColor: JamiTheme.buttonTintedBlackHovered + pressedColor: JamiTheme.buttonTintedBlackPressed + outlined: true - toolTipText: qsTr("Install the latest beta version") + toolTipText: JamiStrings.betaInstall text: JamiStrings.betaInstall - fontPointSize: JamiTheme.buttonFontSize onClicked: { - installBetaSlot() + confirmInstallDialog.beta = true + confirmInstallDialog.openWithParameters(JamiStrings.updateDialogTitle, + JamiStrings.confirmBeta) + } + } + + Component.onCompleted: { + // Quiet check for updates on start if set to. + if (SettingsAdapter.getAppValue(Settings.AutoUpdate)) { + UpdateManager.checkForUpdates(true) + UpdateManager.setAutoUpdateCheck(true) + } + } + + Connections { + target: UpdateManager + + function errorToString(error) { + switch(error){ + case NetWorkManager.ACCESS_DENIED: + return JamiStrings.genericError + case NetWorkManager.DISCONNECTED: + return JamiStrings.networkDisconnected + case NetWorkManager.NETWORK_ERROR: + case NetWorkManager.SSL_ERROR: + return JamiStrings.updateDownloadNetworkError + case NetWorkManager.CANCELED: + return JamiStrings.updateDownloadCanceled + default: return {} + } + } + + function onUpdateCheckReplyReceived(ok, found) { + if (!ok) { + issueDialog.openWithParameters(JamiStrings.updateDialogTitle, + JamiStrings.updateCheckError) + return + } + if (!found) { + issueDialog.openWithParameters(JamiStrings.updateDialogTitle, + JamiStrings.updateNotFound) + } else { + confirmInstallDialog.openWithParameters(JamiStrings.updateDialogTitle, + JamiStrings.updateFound) + } + } + + function onUpdateCheckErrorOccurred(error) { + issueDialog.openWithParameters(JamiStrings.updateDialogTitle, + errorToString(error)) + } + + function onUpdateDownloadStarted() { + downloadDialog.setDownloadProgress(0, 0) + downloadDialog.openWithParameters(JamiStrings.updateDialogTitle) + } + + function onUpdateDownloadProgressChanged(bytesRead, totalBytes) { + downloadDialog.setDownloadProgress(bytesRead, totalBytes) + } + + function onUpdateDownloadErrorOccurred(error) { + downloadDialog.close() + issueDialog.openWithParameters(JamiStrings.updateDialogTitle, + errorToString(error)) } + + function onUpdateDownloadFinished() { downloadDialog.close() } + } + + SimpleMessageDialog { + id: confirmInstallDialog + + property bool beta: false + + buttonTitles: [JamiStrings.optionOk, JamiStrings.optionCancel] + buttonStyles: [ + SimpleMessageDialog.ButtonStyle.TintedBlue, + SimpleMessageDialog.ButtonStyle.TintedBlue + ] + buttonCallBacks: [function() {UpdateManager.applyUpdates(beta)}] + } + + SimpleMessageDialog { + id: issueDialog + + buttonTitles: [JamiStrings.optionOk] + buttonStyles: [SimpleMessageDialog.ButtonStyle.TintedBlue] + buttonCallBacks: [] + } + + SimpleMessageDialog { + id: downloadDialog + + property int bytesRead: 0 + property int totalBytes: 0 + property string hSizeRead: UtilsAdapter.humanFileSize(bytesRead) + property string hTotalBytes: UtilsAdapter.humanFileSize(totalBytes) + property alias progressBarValue: progressBar.value + + function setDownloadProgress(bytesRead, totalBytes) { + downloadDialog.bytesRead = bytesRead + downloadDialog.totalBytes = totalBytes + } + + infoText: JamiStrings.updateDownloading + + " (%1 / %2)".arg(hSizeRead).arg(hTotalBytes) + + innerContentData: ProgressBar { + id: progressBar + + value: downloadDialog.bytesRead / + downloadDialog.totalBytes + + anchors.left: parent.left + anchors.leftMargin: JamiTheme.preferredMarginSize + anchors.right: parent.right + anchors.rightMargin: JamiTheme.preferredMarginSize + + background: Rectangle { + implicitWidth: parent.width + implicitHeight: 24 + color: JamiTheme.darkGrey + } + + contentItem: Item { + implicitWidth: parent.width + implicitHeight: 22 + + Rectangle { + width: progressBar.visualPosition * parent.width + height: parent.height + color: JamiTheme.selectionBlue + } + Label { + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + + color: JamiTheme.white + font.bold: true + font.pointSize: JamiTheme.textFontSize + 1 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text: Math.ceil(progressBar.value * 100).toString() + "%" + } + } + } + + buttonTitles: [JamiStrings.optionCancel] + buttonStyles: [SimpleMessageDialog.ButtonStyle.TintedBlue] + buttonCallBacks: [function() {UpdateManager.cancelUpdate()}] } -} \ No newline at end of file +} diff --git a/src/updatemanager.cpp b/src/updatemanager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5a72fa30d6f0492d2e9ce7e3665fca2c4708e557 --- /dev/null +++ b/src/updatemanager.cpp @@ -0,0 +1,159 @@ +/*! + * Copyright (C) 2020 by Savoir-faire Linux + * Author: Andreas Traczyk <andreas.traczyk@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" + +#include "appsettingsmanager.h" +#include "lrcinstance.h" +#include "utils.h" +#include "version.h" + +#include <QProcess> +#include <QTimer> + +#ifdef BETA +static constexpr bool isBeta = true; +#else +static constexpr bool isBeta = false; +#endif + +static constexpr int updatePeriod = 1000 * 60 * 60 * 24; // one day in millis + +UpdateManager::UpdateManager(const QString& url, ConnectivityMonitor* cm, QObject* parent) + : NetWorkManager(cm, parent) + , baseUrl_(url.isEmpty() ? "https://dl.jami.net/windows" : url.toLatin1()) + , tempPath_(Utils::WinGetEnv("TEMP")) + , updateTimer_(new QTimer(this)) +{ + connect(updateTimer_, &QTimer::timeout, [this] { + // Quiet period update check. + checkForUpdates(true); + }); +} + +void +UpdateManager::setAutoUpdateCheck(bool state) +{ + // Quiet check for updates periodically, if set to. + if (!state) { + updateTimer_->stop(); + return; + } + updateTimer_->start(updatePeriod); +} + +void +UpdateManager::checkForUpdates(bool quiet) +{ + disconnect(); + + // Fail without UI if this is a programmatic check. + if (!quiet) + connect(this, &NetWorkManager::errorOccured, this, &UpdateManager::updateCheckErrorOccurred); + + cleanUpdateFiles(); + QUrl versionUrl {isBeta ? QUrl::fromEncoded(baseUrl_ + "/beta/version") + : QUrl::fromEncoded(baseUrl_ + "/version")}; + + get(versionUrl, [this, quiet](const QString& latestVersionString) { + if (latestVersionString.isEmpty()) { + qWarning() << "Error checking version"; + if (!quiet) + 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"; + emit updateCheckReplyReceived(true, true); + } else { + qDebug() << "No new version found"; + if (!quiet) + emit updateCheckReplyReceived(true, false); + } + }); +} + +void +UpdateManager::applyUpdates(bool beta) +{ + 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); + emit updateDownloadStarted(); + break; + case GetStatus::FINISHED: + emit updateDownloadFinished(); + break; + default: + break; + } + }); + + QUrl downloadUrl {(beta || isBeta) ? QUrl::fromEncoded(baseUrl_ + "/beta/jami.beta.x64.msi") + : QUrl::fromEncoded(baseUrl_ + "/jami.release.x64.msi")}; + + get( + downloadUrl, + [this, downloadUrl](const QString&) { + LRCInstance::reset(); + emit LRCInstance::instance().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 +UpdateManager::cancelUpdate() +{ + cancelRequest(); +} + +void +UpdateManager::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); + } +} diff --git a/src/updatemanager.h b/src/updatemanager.h new file mode 100644 index 0000000000000000000000000000000000000000..ba9fa6475bfc834888c4759c8c8331402edd1ac3 --- /dev/null +++ b/src/updatemanager.h @@ -0,0 +1,54 @@ +/*! + * Copyright (C) 2020 by Savoir-faire Linux + * Author: Andreas Traczyk <andreas.traczyk@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/>. + */ + +#pragma once + +#include "networkmanager.h" + +class ConnectivityMonitor; +class QTimer; + +class UpdateManager final : public NetWorkManager +{ + Q_OBJECT +public: + explicit UpdateManager(const QString& url, ConnectivityMonitor* cm, QObject* parent = nullptr); + ~UpdateManager() = default; + + Q_INVOKABLE void checkForUpdates(bool quiet = false); + Q_INVOKABLE void applyUpdates(bool beta = false); + Q_INVOKABLE void cancelUpdate(); + Q_INVOKABLE void setAutoUpdateCheck(bool state); + +signals: + void updateCheckReplyReceived(bool ok, bool found = false); + void updateCheckErrorOccurred(GetError error); + void updateDownloadStarted(); + void updateDownloadProgressChanged(qint64 bytesRead, qint64 totalBytes); + void updateDownloadErrorOccurred(GetError error); + void updateDownloadFinished(); + void appCloseRequested(); + +private: + QByteArray baseUrl_; + QString tempPath_; + QTimer* updateTimer_; + + void cleanUpdateFiles(); +}; +Q_DECLARE_METATYPE(UpdateManager*) diff --git a/src/utils.cpp b/src/utils.cpp index 805d803f148dcf48470016f9a934f718a1e30c91..858f44bedfd96be712b7fee31a748a8ce2c7ac5e 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -403,47 +403,6 @@ Utils::getProjectCredits() return credits; } -void -Utils::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); - } -} - -void -Utils::checkForUpdates(bool withUI, QWidget* parent) -{ - Q_UNUSED(withUI) - Q_UNUSED(parent) - /* - * TODO: check update logic. - */ -} - -void -Utils::applyUpdates(bool updateToBeta, QWidget* parent) -{ - Q_UNUSED(updateToBeta) - Q_UNUSED(parent) - /* - * TODO: update logic. - */ -} - inline QString removeEndlines(const QString& str) { diff --git a/src/utils.h b/src/utils.h index 3bb7a08a27504516ff8ad5f256d27f41f8ce141e..ccecb8752835848394ed16eba370154c301572ca 100644 --- a/src/utils.h +++ b/src/utils.h @@ -77,18 +77,6 @@ QString getChangeLog(); QString getProjectCredits(); void removeOldVersions(); -/* - * Updates - */ -#ifdef BETA -static constexpr bool isBeta = true; -#else -static constexpr bool isBeta = false; -#endif -void cleanUpdateFiles(); -void checkForUpdates(bool withUI, QWidget* parent = nullptr); -void applyUpdates(bool updateToBeta, QWidget* parent = nullptr); - /* * LRC helpers */ diff --git a/src/utilsadapter.cpp b/src/utilsadapter.cpp index dea883ed11fc57b3768108439e1deba28243525d..81eaaf80549ff3cf06d1b64eb1d3010bec1449a0 100644 --- a/src/utilsadapter.cpp +++ b/src/utilsadapter.cpp @@ -234,11 +234,11 @@ UtilsAdapter::hasVideoCall() } bool -UtilsAdapter::hasCall(const QString &accountId) +UtilsAdapter::hasCall(const QString& accountId) { auto activeCalls = LRCInstance::getActiveCalls(); - for (const auto &callId : activeCalls) { - auto &accountInfo = LRCInstance::accountModel().getAccountInfo(accountId); + for (const auto& callId : activeCalls) { + auto& accountInfo = LRCInstance::accountModel().getAccountInfo(accountId); if (accountInfo.callModel->hasCall(callId)) { return true; } @@ -247,11 +247,11 @@ UtilsAdapter::hasCall(const QString &accountId) } const QString -UtilsAdapter::getCallConvForAccount(const QString &accountId) +UtilsAdapter::getCallConvForAccount(const QString& accountId) { // TODO: Currently returning first call, establish priority according to state? - for (const auto &callId : LRCInstance::getActiveCalls()) { - auto &accountInfo = LRCInstance::accountModel().getAccountInfo(accountId); + for (const auto& callId : LRCInstance::getActiveCalls()) { + auto& accountInfo = LRCInstance::accountModel().getAccountInfo(accountId); if (accountInfo.callModel->hasCall(callId)) { return LRCInstance::getConversationFromCallId(callId, accountId).uid; } @@ -278,10 +278,9 @@ UtilsAdapter::getCallId(const QString& accountId, const QString& convUid) } int -UtilsAdapter::getCallStatus(const QString &callId) +UtilsAdapter::getCallStatus(const QString& callId) { - const auto callStatus = LRCInstance::getCallInfo( - callId, LRCInstance::getCurrAccId()); + const auto callStatus = LRCInstance::getCallInfo(callId, LRCInstance::getCurrAccId()); return static_cast<int>(callStatus->status); } @@ -400,3 +399,9 @@ UtilsAdapter::isImage(const QString& fileExt) { return Utils::isImage(fileExt); } + +QString +UtilsAdapter::humanFileSize(qint64 fileSize) +{ + return Utils::humanFileSize(fileSize); +} diff --git a/src/utilsadapter.h b/src/utilsadapter.h index 3306e5ccff6df7b072520c7d4de0c977471c060f..1d955312a2ae6f56c04802c1152fd13fcdbac1fe 100644 --- a/src/utilsadapter.h +++ b/src/utilsadapter.h @@ -64,10 +64,10 @@ public: Q_INVOKABLE void startPreviewing(bool force); Q_INVOKABLE void stopPreviewing(); Q_INVOKABLE bool hasVideoCall(); - Q_INVOKABLE bool hasCall(const QString &accountId); - Q_INVOKABLE const QString getCallConvForAccount(const QString &accountId); + Q_INVOKABLE bool hasCall(const QString& accountId); + Q_INVOKABLE const QString getCallConvForAccount(const QString& accountId); Q_INVOKABLE const QString getCallId(const QString& accountId, const QString& convUid); - Q_INVOKABLE int getCallStatus(const QString &callId); + Q_INVOKABLE int getCallStatus(const QString& callId); Q_INVOKABLE const QString getCallStatusStr(int statusInt); Q_INVOKABLE QString getStringUTF8(QString string); Q_INVOKABLE bool validateRegNameForm(const QString& regName); @@ -83,6 +83,7 @@ public: Q_INVOKABLE QString fileName(const QString& path); Q_INVOKABLE QString getExt(const QString& path); Q_INVOKABLE bool isImage(const QString& fileExt); + Q_INVOKABLE QString humanFileSize(qint64 fileSize); private: QClipboard* clipboard_;