diff --git a/src/app/MainApplicationWindow.qml b/src/app/MainApplicationWindow.qml index 6ce0e7020d08a43caf2de8723cb0e8622dcbdd33..31fe17bd89af114d98491ec70c0f8822b7631c14 100644 --- a/src/app/MainApplicationWindow.qml +++ b/src/app/MainApplicationWindow.qml @@ -268,7 +268,7 @@ ApplicationWindow { } function presentUpdateInfoDialog(infoText) { - viewCoordinator.presentDialog(appWindow, "commoncomponents/SimpleMessageDialog.qml", { + return viewCoordinator.presentDialog(appWindow, "commoncomponents/SimpleMessageDialog.qml", { "title": JamiStrings.updateDialogTitle, "infoText": infoText, "buttonTitles": [JamiStrings.optionOk], @@ -277,6 +277,36 @@ ApplicationWindow { }); } + function presentUpdateConfirmInstallDialog(switchToBeta=false) { + return viewCoordinator.presentDialog(appWindow, "commoncomponents/SimpleMessageDialog.qml", { + "title": JamiStrings.updateDialogTitle, + "infoText": switchToBeta ? JamiStrings.confirmBeta : JamiStrings.updateFound, + "buttonTitles": [JamiStrings.optionUpgrade, JamiStrings.optionLater], + "buttonStyles": [SimpleMessageDialog.ButtonStyle.TintedBlue, SimpleMessageDialog.ButtonStyle.TintedBlue], + "buttonCallBacks": [function () { + AppVersionManager.applyUpdates(switchToBeta); + }] + }); + } + + function translateErrorToString(error) { + switch (error) { + case NetworkManager.DISCONNECTED: + return JamiStrings.networkDisconnected; + case NetworkManager.CONTENT_NOT_FOUND: + return JamiStrings.contentNotFoundError; + case NetworkManager.ACCESS_DENIED: + return JamiStrings.accessError; + case NetworkManager.SSL_ERROR: + return JamiStrings.updateSSLError; + case NetworkManager.CANCELED: + return JamiStrings.updateDownloadCanceled; + case NetworkManager.NETWORK_ERROR: + default: + return JamiStrings.updateNetworkError; + } + } + Connections { target: AppVersionManager @@ -288,41 +318,26 @@ ApplicationWindow { function onUpdateCheckReplyReceived(ok, found) { if (!ok) { + // Show an error dialog describing that we could not successfully check for an update. presentUpdateInfoDialog(JamiStrings.updateCheckError); return; } if (!found) { + // Show a dialog describing that no update was found. presentUpdateInfoDialog(JamiStrings.updateNotFound); } else { - viewCoordinator.presentDialog(appWindow, "commoncomponents/SimpleMessageDialog.qml", { - "title": JamiStrings.updateDialogTitle, - "infoText": JamiStrings.updateFound, - "buttonTitles": [JamiStrings.optionUpgrade, JamiStrings.optionLater], - "buttonStyles": [SimpleMessageDialog.ButtonStyle.TintedBlue, SimpleMessageDialog.ButtonStyle.TintedBlue], - "buttonCallBacks": [function () { - AppVersionManager.applyUpdates(); - }] - }); + // Show a dialog describing that an update were found, and offering to install it. + presentUpdateConfirmInstallDialog() } } - function onUpdateErrorOccurred(error) { - presentUpdateInfoDialog((function () { - switch (error) { - case NetworkManager.ACCESS_DENIED: - return JamiStrings.genericError; - case NetworkManager.DISCONNECTED: - return JamiStrings.networkDisconnected; - case NetworkManager.NETWORK_ERROR: - return JamiStrings.updateNetworkError; - case NetworkManager.SSL_ERROR: - return JamiStrings.updateSSLError; - case NetworkManager.CANCELED: - return JamiStrings.updateDownloadCanceled; - default: - return {}; - } - })()); + function onNetworkErrorOccurred(error) { + var errorStr = translateErrorToString(error); + presentUpdateInfoDialog(errorStr); + } + + function onInstallErrorOccurred(errorMsg) { + presentUpdateInfoDialog(errorMsg); } } diff --git a/src/app/appversionmanager.cpp b/src/app/appversionmanager.cpp index aead337ab942f3424971a2abc5c7e3197c9a8d4c..ba6cd6730ccf76c9e0703a38996730e8644bac75 100644 --- a/src/app/appversionmanager.cpp +++ b/src/app/appversionmanager.cpp @@ -62,11 +62,11 @@ struct AppVersionManager::Impl : public QObject connect(&parent_, &NetworkManager::errorOccurred, &parent_, - &AppVersionManager::updateErrorOccurred); + &AppVersionManager::networkErrorOccurred); cleanUpdateFiles(); - QUrl versionUrl {isBeta ? QUrl::fromUserInput(baseUrlString_ + betaVersionSubUrl) - : QUrl::fromUserInput(baseUrlString_ + versionSubUrl)}; + const QUrl versionUrl {isBeta ? QUrl::fromUserInput(baseUrlString_ + betaVersionSubUrl) + : QUrl::fromUserInput(baseUrlString_ + versionSubUrl)}; parent_.sendGetRequest(versionUrl, [this, quiet](const QByteArray& latestVersionString) { if (latestVersionString.isEmpty()) { qWarning() << "Error checking version"; @@ -76,14 +76,15 @@ struct AppVersionManager::Impl : public QObject } auto currentVersion = QString(VERSION_STRING).toULongLong(); auto latestVersion = latestVersionString.toULongLong(); - qDebug() << "latest: " << latestVersion << " current: " << currentVersion; - if (latestVersion > currentVersion) { - qDebug() << "New version found"; + const QString channelStr = isBeta ? "beta" : "stable"; + const auto newVersionFound = latestVersion > currentVersion; + qInfo().noquote() << "--------- Version info ------------" + << QString("\n - Current: %1 (%2)").arg(currentVersion).arg(channelStr); + if (newVersionFound) { + qDebug() << " - Latest: " << latestVersion; Q_EMIT parent_.updateCheckReplyReceived(true, true); - } else { - qDebug() << "No new version found"; - if (!quiet) - Q_EMIT parent_.updateCheckReplyReceived(true, false); + } else if (!quiet) { + Q_EMIT parent_.updateCheckReplyReceived(true, false); } }); }; @@ -94,35 +95,56 @@ struct AppVersionManager::Impl : public QObject connect(&parent_, &NetworkManager::errorOccurred, &parent_, - &AppVersionManager::updateErrorOccurred); + &AppVersionManager::networkErrorOccurred); const QUrl downloadUrl {(beta || isBeta) ? QUrl::fromUserInput(baseUrlString_ + betaMsiSubUrl) : QUrl::fromUserInput(baseUrlString_ + msiSubUrl)}; - int uuid = parent_.downloadFile( + const auto lastDownloadReplyId = parent_.replyId_; + parent_.replyId_ = parent_.downloadFile( downloadUrl, - *(parent_.replyId_), + lastDownloadReplyId, [this, downloadUrl](bool success, const QString& errorMessage) { Q_UNUSED(success) Q_UNUSED(errorMessage) - const QProcess process; + QProcess process; auto basePath = tempPath_ + QDir::separator(); auto msiPath = QDir::toNativeSeparators(basePath + downloadUrl.fileName()); auto logPath = QDir::toNativeSeparators(basePath + "jami_x64_install.log"); - process.startDetached("msiexec", - QStringList() << "/i" << msiPath << "/passive" - << "/norestart" - << "WIXNONUILAUNCH=1" - << "/L*V" << logPath); + connect(&process, &QProcess::errorOccurred, this, [&](QProcess::ProcessError error) { + QString errorMsg; + if (error == QProcess::ProcessError::Timedout) { + errorMsg = tr("The installer process has timed out."); + } else { + errorMsg = process.readAllStandardError(); + if (errorMsg.isEmpty()) + errorMsg = tr("The installer process has failed."); + } + Q_EMIT parent_.installErrorOccurred(errorMsg); + }); + connect(&process, + &QProcess::finished, + this, + [&](int exitCode, QProcess::ExitStatus exitStatus) { + if (exitStatus != QProcess::ExitStatus::NormalExit || exitCode != 0) { + auto errorMsg = process.readAllStandardOutput(); + Q_EMIT parent_.installErrorOccurred(errorMsg); + } + }); + process.start("msiexec", + QStringList() << "/i" << msiPath << "/passive" + << "/norestart" + << "WIXNONUILAUNCH=1" + << "/L*V" << logPath); + process.waitForFinished(); }, tempPath_); - parent_.replyId_.reset(&uuid); }; void cancelUpdate() { - parent_.cancelDownload(*(parent_.replyId_)); + parent_.cancelDownload(parent_.replyId_); }; void setAutoUpdateCheck(bool state) @@ -138,7 +160,7 @@ struct AppVersionManager::Impl : public QObject void cleanUpdateFiles() { // Delete all logs and msi in the temporary directory before launching. - QString dir = QDir::tempPath(); + const QString dir = QDir::tempPath(); QDir log_dir(dir, {"jami*.log"}); for (const QString& filename : log_dir.entryList()) { log_dir.remove(filename); @@ -166,13 +188,13 @@ AppVersionManager::AppVersionManager(const QString& url, LRCInstance* instance, QObject* parent) : NetworkManager(cm, parent) - , replyId_(new int(0)) + , replyId_(0) , pimpl_(std::make_unique<Impl>(url, instance, *this)) {} AppVersionManager::~AppVersionManager() { - cancelDownload(*replyId_); + cancelDownload(replyId_); } void diff --git a/src/app/appversionmanager.h b/src/app/appversionmanager.h index 55fa94c5ff756e085024d3f67ab36b34828ae9c2..e2a2539ed610d556a33dae61dcaafb06a960488c 100644 --- a/src/app/appversionmanager.h +++ b/src/app/appversionmanager.h @@ -47,10 +47,11 @@ Q_SIGNALS: void appCloseRequested(); void updateCheckReplyReceived(bool ok, bool found = false); void updateDownloadProgressChanged(qint64 bytesRead, qint64 totalBytes); - void updateErrorOccurred(const NetworkManager::GetError& error); + void networkErrorOccurred(const NetworkManager::GetError& error); + void installErrorOccurred(const QString& errorMsg); private: - QScopedPointer<int> replyId_; + int replyId_; struct Impl; friend struct Impl; std::unique_ptr<Impl> pimpl_; diff --git a/src/app/constant/JamiStrings.qml b/src/app/constant/JamiStrings.qml index 817ce6425f218fc2921ced6edcfc27565c8c0e94..553dcc14f7d38bc98b3a060bfc8b35cf693a1ce8 100644 --- a/src/app/constant/JamiStrings.qml +++ b/src/app/constant/JamiStrings.qml @@ -513,6 +513,8 @@ Item { 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 accessError: qsTr("Content access error") + property string contentNotFoundError: qsTr("Content not found") property string genericError: qsTr("Something went wrong") //Troubleshoot Settings diff --git a/src/app/networkmanager.cpp b/src/app/networkmanager.cpp index 501758fbaa03f3923001da1e26ead9a30eb20ca9..dc31bd21b62d0aa8cdab85ef6cbf55ae1def7602 100644 --- a/src/app/networkmanager.cpp +++ b/src/app/networkmanager.cpp @@ -25,6 +25,28 @@ #include <QtNetwork> #include <QScopedPointer> +namespace { +NetworkManager::GetError +translateErrorCode(QNetworkReply::NetworkError error) +{ + // From qnetworkreply.h: + // network layer errors (1-99): / proxy errors (101-199): + // content errors (201-299): ContentAccessDenied = 201, + // protocol errors / Server side errors (401-499) + static auto inRange = [](int value, int min, int max) -> bool { + return (value >= min && value <= max); + }; + if (inRange(error, 1, 199)) + return NetworkManager::NETWORK_ERROR; + if (inRange(error, 201, 201)) + return NetworkManager::ACCESS_DENIED; + if (inRange(error, 202, 299)) + return NetworkManager::CONTENT_NOT_FOUND; + return NetworkManager::NETWORK_ERROR; +} + +} // namespace + NetworkManager::NetworkManager(ConnectivityMonitor* cm, QObject* parent) : QObject(parent) , manager_(new QNetworkAccessManager(this)) @@ -45,7 +67,7 @@ NetworkManager::NetworkManager(ConnectivityMonitor* cm, QObject* parent) }); #endif connect(connectivityMonitor_, &ConnectivityMonitor::connectivityChanged, this, [this] { - auto connected = connectivityMonitor_->isOnline(); + const auto connected = connectivityMonitor_->isOnline(); if (connected && !lastConnectionState_) { manager_->deleteLater(); manager_ = new QNetworkAccessManager(this); @@ -59,7 +81,7 @@ void NetworkManager::sendGetRequest(const QUrl& url, std::function<void(const QByteArray&)>&& onDoneCallback) { - QNetworkRequest request = QNetworkRequest(url); + const QNetworkRequest request = QNetworkRequest(url); sendGetRequest(request, std::move(onDoneCallback)); } @@ -98,9 +120,8 @@ NetworkManager::downloadFile(const QUrl& url, const QString& filePath, const QString& extension) { - // If there is already a download in progress, return. - if ((downloadReplies_.value(replyId) != NULL || !(replyId == 0)) - && downloadReplies_[replyId]->isRunning()) { + // Don't replace the download if there is already a download in progress for this id. + if (downloadReplies_.contains(replyId) && downloadReplies_.value(replyId)->isRunning()) { qWarning() << Q_FUNC_INFO << "Download already in progress"; return replyId; } @@ -121,6 +142,7 @@ NetworkManager::downloadFile(const QUrl& url, } // set the id for the request + // NOLINTNEXTLINE(misc-const-correctness) std::uniform_int_distribution<int> dist(1, std::numeric_limits<int>::max()); auto uuid = dist(rng_); @@ -167,7 +189,7 @@ NetworkManager::downloadFile(const QUrl& url, resetDownload(uuid); qWarning() << Q_FUNC_INFO << QMetaEnum::fromType<QNetworkReply::NetworkError>().valueToKey(error); - Q_EMIT errorOccurred(GetError::NETWORK_ERROR); + Q_EMIT errorOccurred(translateErrorCode(error)); }); connect(reply, &QNetworkReply::finished, this, [this, uuid, onDoneCallback, reply, file]() { diff --git a/src/app/networkmanager.h b/src/app/networkmanager.h index 569f5e794b3da0f4fd46325c0e3583c51d98011d..dc8b27333f61041a7863d61d21d3a998e0c34144 100644 --- a/src/app/networkmanager.h +++ b/src/app/networkmanager.h @@ -35,7 +35,14 @@ public: explicit NetworkManager(ConnectivityMonitor* cm, QObject* parent = nullptr); virtual ~NetworkManager() = default; - enum GetError { DISCONNECTED, NETWORK_ERROR, ACCESS_DENIED, SSL_ERROR, CANCELED }; + enum GetError { + DISCONNECTED, + CONTENT_NOT_FOUND, + ACCESS_DENIED, + SSL_ERROR, + CANCELED, + NETWORK_ERROR, + }; Q_ENUM(GetError) void sendGetRequest(const QUrl& url, std::function<void(const QByteArray&)>&& onDoneCallback); @@ -48,7 +55,7 @@ public: int replyId, std::function<void(bool, const QString&)>&& onDoneCallback, const QString& filePath, - const QString& extension = ""); + const QString& extension = {}); void resetDownload(int replyId); void cancelDownload(int replyId); Q_SIGNALS: diff --git a/src/app/settingsview/components/UpdateDownloadDialog.qml b/src/app/settingsview/components/UpdateDownloadDialog.qml index f183bd845d078a4a5e8e2f4fda1b81fc189b6775..9607f7621813c0953754cc107d3b0fe15b846ac4 100644 --- a/src/app/settingsview/components/UpdateDownloadDialog.qml +++ b/src/app/settingsview/components/UpdateDownloadDialog.qml @@ -36,8 +36,7 @@ SimpleMessageDialog { Connections { target: AppVersionManager - function onErrorOccurred(error, msg) { - console.warn("Error while downloading update: " + error + " - " + msg); + function onNetworkErrorOccurred(error) { downloadDialog.close(); } diff --git a/src/app/settingsview/components/UpdateSettingsPage.qml b/src/app/settingsview/components/UpdateSettingsPage.qml index e2a02e5468c02aba7913fc3baea46c2d2abb8e83..4dc50ff43ad62f87cd32f371a22c44daf9600b00 100644 --- a/src/app/settingsview/components/UpdateSettingsPage.qml +++ b/src/app/settingsview/components/UpdateSettingsPage.qml @@ -32,28 +32,6 @@ SettingsPageBase { title: JamiStrings.updatesTitle - function presentInfoDialog(infoText) { - viewCoordinator.presentDialog(appWindow, "commoncomponents/SimpleMessageDialog.qml", { - "title": JamiStrings.updateDialogTitle, - "infoText": infoText, - "buttonTitles": [JamiStrings.optionOk], - "buttonStyles": [SimpleMessageDialog.ButtonStyle.TintedBlue], - "buttonCallBacks": [] - }); - } - - function presentConfirmInstallDialog(infoText, beta) { - viewCoordinator.presentDialog(appWindow, "commoncomponents/SimpleMessageDialog.qml", { - "title": JamiStrings.updateDialogTitle, - "infoText": infoText, - "buttonTitles": [JamiStrings.optionUpgrade, JamiStrings.optionLater], - "buttonStyles": [SimpleMessageDialog.ButtonStyle.TintedBlue, SimpleMessageDialog.ButtonStyle.TintedBlue], - "buttonCallBacks": [function () { - AppVersionManager.applyUpdates(beta); - }] - }); - } - flickableContent: ColumnLayout { id: manageAccountEnableColumnLayout width: contentFlickableWidth @@ -119,15 +97,7 @@ SettingsPageBase { toolTipText: JamiStrings.betaInstall text: JamiStrings.betaInstall - onClicked: viewCoordinator.presentDialog(appWindow, "commoncomponents/SimpleMessageDialog.qml", { - "title": JamiStrings.updateDialogTitle, - "infoText": JamiStrings.confirmBeta, - "buttonTitles": [JamiStrings.optionUpgrade, JamiStrings.optionLater], - "buttonStyles": [SimpleMessageDialog.ButtonStyle.TintedBlue, SimpleMessageDialog.ButtonStyle.TintedBlue], - "buttonCallBacks": [function () { - AppVersionManager.applyUpdates(true); - }] - }) + onClicked: appWindow.presentUpdateConfirmInstallDialog(true) } } }