3#if defined(QT_UTILITIES_GUI_QTWIDGETS)
7#include "resources/config.h"
12#include <c++utilities/application/argumentparser.h>
16#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
17#include <c++utilities/io/ansiescapecodes.h>
18#include <c++utilities/io/archive.h>
20#include <QCoreApplication>
25#include <QFutureWatcher>
27#include <QJsonDocument>
29#include <QJsonParseError>
32#include <QNetworkAccessManager>
33#include <QNetworkReply>
35#include <QRegularExpression>
36#include <QStringBuilder>
37#include <QVersionNumber>
38#include <QtConcurrentRun>
41#if defined(QT_UTILITIES_GUI_QTWIDGETS)
48#if defined(QT_UTILITIES_GUI_QTWIDGETS)
49#include <QCoreApplication>
52#if defined(QT_UTILITIES_SETUP_TOOLS_ENABLED)
53#include "ui_updateoptionpage.h"
57class UpdateOptionPage {
59 void setupUi(QWidget *)
62 void retranslateUi(QWidget *)
71#include "resources/config.h"
73#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
74#define QT_UTILITIES_VERSION_SUFFIX QString()
76#define QT_UTILITIES_VERSION_SUFFIX QStringLiteral("-qt5")
79#if defined(Q_OS_WINDOWS)
80#define QT_UTILITIES_EXE_REGEX "\\.exe"
82#define QT_UTILITIES_EXE_REGEX ""
85#if defined(Q_OS_WIN64)
86#if defined(Q_PROCESSOR_X86_64)
87#define QT_UTILITIES_DOWNLOAD_REGEX "-.*-x86_64-w64-mingw32"
88#elif defined(Q_PROCESSOR_ARM_64)
89#define QT_UTILITIES_DOWNLOAD_REGEX "-.*-aarch64-w64-mingw32"
91#elif defined(Q_OS_WIN32)
92#define QT_UTILITIES_DOWNLOAD_REGEX "-.*-i686-w64-mingw32"
93#elif defined(__GNUC__) && defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
94#if defined(Q_PROCESSOR_X86_64)
95#define QT_UTILITIES_DOWNLOAD_REGEX "-.*-x86_64-pc-linux-gnu"
96#elif defined(Q_PROCESSOR_ARM_64)
97#define QT_UTILITIES_DOWNLOAD_REGEX "-.*-aarch64-pc-linux-gnu"
101#if defined(Q_OS_WINDOWS) && (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
102#include <QNtfsPermissionCheckGuard>
107#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0))
113#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
114struct VersionAndSuffix {
115 operator bool()
const
117 return !version.isNull();
119 bool operator>(
const VersionAndSuffix &rhs)
const
121 const auto cmp = QVersionNumber::compare(version, rhs.version);
124 }
else if (cmp < 0) {
127 if (!suffix.isEmpty() && rhs.suffix.isEmpty()) {
130 if (suffix.isEmpty() && !rhs.suffix.isEmpty()) {
134 return suffix > rhs.suffix;
136 QString toString()
const
138 return version.toString() + suffix;
140 static VersionAndSuffix fromString(
const QString &versionString)
142 auto res = VersionAndSuffix();
144 res.version = QVersionNumber::fromString(versionString, &suffixIndex);
145 res.suffix = suffixIndex >= 0 ? versionString.mid(suffixIndex) : QString();
147 if (
static const auto validSuffixRegex = QRegularExpression(QRegularExpression::anchoredPattern(QStringLiteral(
"-?\\w+\\d?")));
148 !validSuffixRegex.match(res.suffix).hasMatch()) {
153 QVersionNumber version;
157struct UpdateNotifierPrivate {
158 QNetworkAccessManager *nm =
nullptr;
159 CppUtilities::DateTime lastCheck;
161 QNetworkRequest::CacheLoadControl cacheLoadControl = QNetworkRequest::PreferNetwork;
162 VersionAndSuffix currentVersion;
163 QRegularExpression gitHubRegex = QRegularExpression(QStringLiteral(
".*/github.com/([^/]+)/([^/]+)(/.*)?"));
164 QRegularExpression gitHubRegex2 = QRegularExpression(QStringLiteral(
".*/([^/.]+)\\.github.io/([^/]+)(/.*)?"));
165 QRegularExpression assetRegex = QRegularExpression();
166 QString executableName;
167 QString previouslyFoundNewVersion;
169 QString latestVersion;
170 QString additionalInfo;
171 QString releaseNotes;
176 QUrl previousVersionDownloadUrl;
177 QUrl previousVersionSignatureUrl;
178 QList<std::variant<QJsonArray, QString>> previousVersionAssets;
179 bool inProgress =
false;
180 bool updateAvailable =
false;
181 bool verbose =
false;
193#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
196 m_p->verbose = qEnvironmentVariableIntValue(PROJECT_VARNAME_UPPER
"_UPDATER_VERBOSE");
198 const auto &appInfo = CppUtilities::applicationInfo;
199 const auto url = QString::fromUtf8(appInfo.url);
200 auto gitHubMatch = m_p->gitHubRegex.match(url);
201 if (!gitHubMatch.hasMatch()) {
202 gitHubMatch = m_p->gitHubRegex2.match(url);
204 const auto gitHubOrga = gitHubMatch.captured(1);
205 const auto gitHubRepo = gitHubMatch.captured(2);
206 if (gitHubOrga.isNull() || gitHubRepo.isNull()) {
211 = QStringLiteral(
"https://api.github.com/repos/") % gitHubOrga % QChar(
'/') % gitHubRepo % QStringLiteral(
"/releases?per_page=25");
212 m_p->currentVersion = VersionAndSuffix::fromString(QString::fromUtf8(appInfo.version));
213#ifdef QT_UTILITIES_DOWNLOAD_REGEX
214 m_p->assetRegex = QRegularExpression(m_p->executableName + QStringLiteral(QT_UTILITIES_DOWNLOAD_REGEX
"\\..+"));
217 qDebug() <<
"deduced executable name: " << m_p->executableName;
218 qDebug() <<
"assumed current version: " << m_p->currentVersion.version;
219 qDebug() <<
"asset regex for current platform: " << m_p->assetRegex;
225#ifdef QT_UTILITIES_FAKE_NEW_VERSION_AVAILABLE
226 QTimer::singleShot(10000, Qt::VeryCoarseTimer,
this, [
this] { emit
updateAvailable(QStringLiteral(
"foo"), QString()); });
236#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
239 return !m_p->assetRegex.pattern().isEmpty();
245#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
248 return m_p->inProgress;
254#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
257 return m_p->updateAvailable;
263#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
272#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
281#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
282 static const auto v = QString();
285 return m_p->executableName;
291#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
292 static const auto v = QString();
295 return m_p->newVersion;
301#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
302 static const auto v = QString();
305 return m_p->latestVersion;
311#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
312 static const auto v = QString();
315 return m_p->additionalInfo;
321#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
322 static const auto v = QString();
325 return m_p->releaseNotes;
336#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
337 static const auto v = QUrl();
340 return m_p->downloadUrl;
346#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
347 static const auto v = QUrl();
350 return m_p->signatureUrl;
356#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
357 static const auto v = QUrl();
360 return m_p->previousVersionDownloadUrl;
366#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
367 static const auto v = QUrl();
370 return m_p->previousVersionSignatureUrl;
376#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
377 return CppUtilities::DateTime();
379 return m_p->lastCheck;
385#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
388 settings->beginGroup(QStringLiteral(
"updating"));
389 m_p->newVersion = settings->value(
"newVersion").toString();
390 m_p->latestVersion = settings->value(
"latestVersion").toString();
391 m_p->releaseNotes = settings->value(
"releaseNotes").toString();
392 m_p->downloadUrl = settings->value(
"downloadUrl").toUrl();
393 m_p->signatureUrl = settings->value(
"signatureUrl").toUrl();
394 m_p->previousVersionDownloadUrl = settings->value(
"previousVersionDownloadUrl").toUrl();
395 m_p->previousVersionSignatureUrl = settings->value(
"previousVersionSignatureUrl").toUrl();
396 m_p->lastCheck = CppUtilities::DateTime(settings->value(
"lastCheck").toULongLong());
397 m_p->flags =
static_cast<UpdateCheckFlags>(settings->value(
"flags").toULongLong());
398 settings->endGroup();
404#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
407 settings->beginGroup(QStringLiteral(
"updating"));
408 settings->setValue(
"newVersion", m_p->newVersion);
409 settings->setValue(
"latestVersion", m_p->latestVersion);
410 settings->setValue(
"releaseNotes", m_p->releaseNotes);
411 settings->setValue(
"downloadUrl", m_p->downloadUrl);
412 settings->setValue(
"signatureUrl", m_p->signatureUrl);
413 settings->setValue(
"previousVersionDownloadUrl", m_p->previousVersionDownloadUrl);
414 settings->setValue(
"previousVersionSignatureUrl", m_p->previousVersionSignatureUrl);
415 settings->setValue(
"lastCheck",
static_cast<qulonglong
>(m_p->lastCheck.ticks()));
416 settings->setValue(
"flags",
static_cast<qulonglong
>(m_p->flags));
417 settings->endGroup();
423#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
424 if (m_p->inProgress) {
425 return tr(
"checking …");
428 if (!m_p->error.isEmpty()) {
429 return tr(
"unable to check: %1").arg(m_p->error);
431#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
432 if (!m_p->newVersion.isEmpty()) {
433 return tr(
"new version available: %1 (last checked: %2)").arg(m_p->newVersion, QString::fromStdString(m_p->lastCheck.toIsoString()));
434 }
else if (!m_p->latestVersion.isEmpty()) {
435 return tr(
"no new version available, latest release is: %1 (last checked: %2)")
436 .arg(m_p->latestVersion, QString::fromStdString(m_p->lastCheck.toIsoString()));
439 return tr(
"unknown");
444#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
451#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
452void UpdateNotifier::setCacheLoadControl(QNetworkRequest::CacheLoadControl cacheLoadControl)
454 m_p->cacheLoadControl = cacheLoadControl;
458void UpdateNotifier::setError(
const QString &context, QNetworkReply *reply)
460#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
464 m_p->error = context + reply->errorString();
470void UpdateNotifier::setError(
const QString &context,
const QJsonParseError &jsonError,
const QByteArray &response)
472#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
477 m_p->error = context % jsonError.errorString() % QChar(
' ') % QChar(
'(') % tr(
"at offset %1").arg(jsonError.offset) % QChar(
')');
478 if (!response.isEmpty()) {
479 m_p->error += QStringLiteral(
"\nResponse was: ");
480 m_p->error += QString::fromUtf8(response);
488#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
489 m_p->error = tr(
"This build of the application does not support checking for updates.");
493 if (!m_p->nm || m_p->inProgress) {
497 auto request = QNetworkRequest(m_p->releasesUrl);
498 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, m_p->cacheLoadControl);
499 auto *
const reply = m_p->nm->get(request);
500 connect(reply, &QNetworkReply::finished,
this, &UpdateNotifier::readReleases);
506#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
507 m_p->updateAvailable =
false;
508 m_p->downloadUrl.clear();
509 m_p->signatureUrl.clear();
510 m_p->latestVersion.clear();
511 m_p->newVersion.clear();
512 m_p->releaseNotes.clear();
516void UpdateNotifier::lastCheckNow()
const
518#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
519 m_p->lastCheck = CppUtilities::DateTime::now();
523#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
528bool UpdateNotifier::isVersionHigher(
const QString &lhs,
const QString &rhs)
530 return VersionAndSuffix::fromString(lhs) > VersionAndSuffix::fromString(rhs);
536#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
540 auto jsonError = QJsonParseError();
541 const auto replyDoc = QJsonDocument::fromJson(data, &jsonError);
542 if (jsonError.error != QJsonParseError::NoError) {
543 setError(tr(
"Unable to parse releases: "), jsonError, data);
547#if !defined(QT_JSON_READONLY)
549 qDebug().noquote() <<
"Update check: found releases: " << QString::fromUtf8(replyDoc.toJson(QJsonDocument::Indented));
553 const auto replyArray = replyDoc.array();
556 auto latestVersionFound = VersionAndSuffix();
557 auto latestVersionAssets = QJsonValue();
558 auto latestVersionAssetsUrl = QString();
559 auto latestVersionReleaseNotes = QString();
560 auto previousVersionAssets = QMap<QVersionNumber, std::variant<QJsonArray, QString>>();
561 for (
const auto &releaseInfoVal : replyArray) {
562 const auto releaseInfo = releaseInfoVal.toObject();
563 const auto tag = releaseInfo.value(QLatin1String(
"tag_name")).toString();
564 if ((skipPreReleases && releaseInfo.value(QLatin1String(
"prerelease")).toBool())
565 || (skipDrafts && releaseInfo.value(QLatin1String(
"draft")).toBool())) {
566 qDebug() <<
"Update check: skipping prerelease/draft: " << tag;
569 const auto versionStr = tag.startsWith(QChar(
'v')) ? tag.mid(1) : tag;
570 const auto version = VersionAndSuffix::fromString(versionStr);
571 const auto assets = releaseInfo.value(QLatin1String(
"assets"));
572 const auto assetsUrl = releaseInfo.value(QLatin1String(
"assets_url")).toString();
573 if (!latestVersionFound || version > latestVersionFound) {
574 latestVersionFound = version;
575 latestVersionAssets = assets;
576 latestVersionAssetsUrl = assetsUrl;
577 latestVersionReleaseNotes = releaseInfo.value(QLatin1String(
"body")).toString();
579 if (assets.isArray()) {
580 previousVersionAssets[version.version] = assets.toArray();
581 }
else if (!assetsUrl.isEmpty()) {
582 previousVersionAssets[version.version] = assetsUrl;
585 qDebug() <<
"Update check: skipping release: " << tag;
588 if (latestVersionFound) {
589 m_p->latestVersion = latestVersionFound.toString();
590 m_p->releaseNotes = latestVersionReleaseNotes;
591 previousVersionAssets.remove(latestVersionFound.version);
593 m_p->previousVersionAssets = previousVersionAssets.values();
595 const auto foundUpdate = latestVersionFound && latestVersionFound > m_p->currentVersion;
597 m_p->newVersion = latestVersionFound.toString();
599 if (latestVersionAssets.isArray()) {
600 return processAssets(latestVersionAssets.toArray(), foundUpdate,
false);
601 }
else if (foundUpdate) {
602 return queryRelease(latestVersionAssetsUrl, foundUpdate,
false);
609void UpdateNotifier::readReleases()
611#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
612 auto *
const reply =
static_cast<QNetworkReply *
>(sender());
613 reply->deleteLater();
614 switch (reply->error()) {
615 case QNetworkReply::NoError: {
619 case QNetworkReply::OperationCanceledError:
623 setError(tr(
"Unable to request releases: "), reply);
628void UpdateNotifier::queryRelease(
const QUrl &releaseUrl,
bool forUpdate,
bool forPreviousVersion)
630#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
633 Q_UNUSED(forPreviousVersion)
635 auto request = QNetworkRequest(releaseUrl);
636 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, m_p->cacheLoadControl);
637 auto *
const reply = m_p->nm->get(request);
638 reply->setProperty(
"forUpdate", forUpdate);
639 reply->setProperty(
"forPreviousVersion", forPreviousVersion);
640 connect(reply, &QNetworkReply::finished,
this, &UpdateNotifier::readRelease);
644void UpdateNotifier::readRelease()
646#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
647 auto *
const reply =
static_cast<QNetworkReply *
>(sender());
648 reply->deleteLater();
649 switch (reply->error()) {
650 case QNetworkReply::NoError: {
652 auto jsonError = QJsonParseError();
653 const auto response = reply->readAll();
654 const auto replyDoc = QJsonDocument::fromJson(response, &jsonError);
655 if (jsonError.error != QJsonParseError::NoError) {
656 setError(tr(
"Unable to parse release: "), jsonError, response);
659#if !defined(QT_JSON_READONLY)
661 qDebug().noquote() <<
"Update check: found release info: " << QString::fromUtf8(replyDoc.toJson(QJsonDocument::Indented));
664 processAssets(replyDoc.object().value(QLatin1String(
"assets")).toArray(), reply->property(
"forUpdate").toBool(),
665 reply->property(
"forPreviousVersion").toBool());
668 case QNetworkReply::OperationCanceledError:
672 setError(tr(
"Unable to request release: "), reply);
677void UpdateNotifier::processAssets(
const QJsonArray &assets,
bool forUpdate,
bool forPreviousVersion)
679#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
682 Q_UNUSED(forPreviousVersion)
684 for (
const auto &assetVal : assets) {
685 if (forPreviousVersion ? !m_p->previousVersionDownloadUrl.isEmpty() && !m_p->previousVersionSignatureUrl.isEmpty()
686 : !m_p->downloadUrl.isEmpty() && !m_p->signatureUrl.isEmpty()) {
689 const auto asset = assetVal.toObject();
690 const auto assetName = asset.value(QLatin1String(
"name")).toString();
691 if (assetName.isEmpty()) {
694 if (!m_p->assetRegex.match(assetName).hasMatch()) {
696 qDebug() <<
"Update check: skipping asset: " << assetName;
700 const auto url = asset.value(QLatin1String(
"browser_download_url")).toString();
701 if (assetName.endsWith(QLatin1String(
".sig"))) {
702 (forPreviousVersion ? m_p->previousVersionSignatureUrl : m_p->signatureUrl) = url;
704 (forPreviousVersion ? m_p->previousVersionDownloadUrl : m_p->downloadUrl) = url;
708 m_p->updateAvailable = !m_p->downloadUrl.isEmpty();
710 if (m_p->downloadUrl.isEmpty() && m_p->previousVersionDownloadUrl.isEmpty() && !m_p->previousVersionAssets.isEmpty()) {
711 auto previousVersionAssets = m_p->previousVersionAssets.takeLast();
712 if (std::holds_alternative<QJsonArray>(previousVersionAssets)) {
713 return processAssets(std::get<QJsonArray>(previousVersionAssets), forUpdate,
true);
715 return queryRelease(std::get<QString>(previousVersionAssets), forUpdate,
true);
720 if (forUpdate && m_p->updateAvailable && m_p->newVersion != m_p->previouslyFoundNewVersion) {
722 m_p->previouslyFoundNewVersion = m_p->newVersion;
728#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
730 QNetworkAccessManager *nm =
nullptr;
731 QFile *fakeDownload =
nullptr;
732 QNetworkReply *currentDownload =
nullptr;
733 QNetworkReply *signatureDownload =
nullptr;
734 QNetworkRequest::CacheLoadControl cacheLoadControl = QNetworkRequest::PreferNetwork;
735 QString
error, statusMessage;
736 QByteArray signature;
737 QFutureWatcher<QPair<QString, QString>> watcher;
738 QString executableName;
739 QString signatureExtension;
740 QRegularExpression executableRegex = QRegularExpression();
751 :
Updater(executableName, QString(), parent)
755Updater::Updater(
const QString &executableName,
const QString &signatureExtension, QObject *parent)
759#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
760 Q_UNUSED(executableName)
761 Q_UNUSED(signatureExtension)
763 connect(&m_p->watcher, &QFutureWatcher<void>::finished,
this, &Updater::concludeUpdate);
764 m_p->executableName = executableName;
765 m_p->signatureExtension = signatureExtension;
766 const auto signatureRegex = signatureExtension.isEmpty()
768 : QString(QStringLiteral(
"(") % QRegularExpression::escape(signatureExtension) % QStringLiteral(
")?"));
769#ifdef QT_UTILITIES_EXE_REGEX
770 m_p->executableRegex = QRegularExpression(executableName % QStringLiteral(
QT_UTILITIES_EXE_REGEX) % signatureRegex);
781#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
782 return m_p->currentDownload !=
nullptr || m_p->signatureDownload !=
nullptr || m_p->watcher.isRunning();
790#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
791 return isInProgress() ? tr(
"Update in progress …") : (m_p->error.isEmpty() ? tr(
"Update done") : tr(
"Update failed"));
804#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
805 return m_p->statusMessage.isEmpty() ? m_p->error : m_p->statusMessage;
813#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
814 return m_p->storedPath;
816 static const auto empty = QString();
823#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
832#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
833 m_p->verifyFunction = std::move(verifyFunction);
835 Q_UNUSED(verifyFunction)
839#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
840void Updater::setCacheLoadControl(QNetworkRequest::CacheLoadControl cacheLoadControl)
842 m_p->cacheLoadControl = cacheLoadControl;
848#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
849 Q_UNUSED(downloadUrl)
850 Q_UNUSED(signatureUrl)
851 setError(tr(
"This build of the application does not support self-updating."));
857 startDownload(downloadUrl, signatureUrl);
864#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
865 if (m_p->currentDownload) {
866 m_p->currentDownload->abort();
868 if (m_p->signatureDownload) {
869 m_p->signatureDownload->abort();
871 if (m_p->watcher.isRunning()) {
872 m_p->watcher.cancel();
877void Updater::setError(
const QString &error)
879#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
880 m_p->statusMessage.clear();
888void Updater::startDownload(
const QString &downloadUrl,
const QString &signatureUrl)
890#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
891 Q_UNUSED(downloadUrl)
892 Q_UNUSED(signatureUrl)
895 m_p->storedPath.clear();
896 m_p->signature.clear();
898 if (
const auto fakeDownloadPath = qEnvironmentVariable(PROJECT_VARNAME_UPPER
"_UPDATER_FAKE_DOWNLOAD"); !fakeDownloadPath.isEmpty()) {
899 m_p->fakeDownload =
new QFile(fakeDownloadPath);
900 m_p->fakeDownload->open(QFile::ReadOnly);
906 auto request = QNetworkRequest(QUrl(downloadUrl));
907 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, m_p->cacheLoadControl);
908 m_p->statusMessage = tr(
"Downloading %1").arg(downloadUrl);
909 m_p->currentDownload = m_p->nm->get(request);
913 connect(m_p->currentDownload, &QNetworkReply::finished,
this, &Updater::handleDownloadFinished);
915 if (!signatureUrl.isEmpty()) {
916 request.setUrl(signatureUrl);
917 m_p->signatureDownload = m_p->nm->get(request);
918 connect(m_p->signatureDownload, &QNetworkReply::finished,
this, &Updater::handleDownloadFinished);
923void Updater::handleDownloadFinished()
925#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
926 if (m_p->signatureDownload && !m_p->signatureDownload->isFinished()) {
931 if (!m_p->currentDownload->isFinished()) {
935 if (m_p->signatureDownload) {
937 m_p->signatureDownload->deleteLater();
938 m_p->signatureDownload =
nullptr;
941 if (m_p->error.isEmpty()) {
944 m_p->currentDownload->deleteLater();
946 m_p->currentDownload =
nullptr;
950void Updater::readSignature()
952#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
953 switch (m_p->signatureDownload->error()) {
954 case QNetworkReply::NoError:
955 m_p->signature = m_p->signatureDownload->readAll();
958 setError(tr(
"Unable to download signature: ") + m_p->signatureDownload->errorString());
963void Updater::storeExecutable()
965#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
966 m_p->statusMessage = tr(
"Extracting …");
969 auto *reply =
static_cast<QIODevice *
>(m_p->fakeDownload);
970 auto archiveName = QString();
971 auto hasError =
false;
973 archiveName = m_p->fakeDownload->fileName();
974 hasError = m_p->fakeDownload->error() != QFileDevice::NoError;
976 reply = m_p->currentDownload;
977 archiveName = m_p->currentDownload->request().url().fileName();
978 hasError = m_p->currentDownload->error() != QNetworkReply::NoError;
981 reply->deleteLater();
982 setError(tr(
"Unable to download update: ") + reply->errorString());
985 auto res = QtConcurrent::run([
this, reply, archiveName] {
986 const auto data = reply->readAll();
987 const auto dataView = std::string_view(data.data(),
static_cast<std::size_t
>(data.size()));
988 auto foundExecutable =
false, foundSignature =
false;
989 auto error = QString(), storePath = QString();
990 auto newExeName = std::string(), signatureName = std::string();
991 auto newExeData = std::string();
992 auto newExe = QFile();
993 reply->deleteLater();
996 const auto appDirPath = QCoreApplication::applicationDirPath();
997 const auto appFilePath = QCoreApplication::applicationFilePath();
998 if (appDirPath.isEmpty() || appFilePath.isEmpty()) {
999 error = tr(
"Unable to determine application path.");
1000 return QPair<QString, QString>(
error, storePath);
1004 const auto checkCancellation = [
this, &
error] {
1005 if (m_p->watcher.isCanceled()) {
1006 error = tr(
"Extraction was cancelled.");
1012 if (checkCancellation()) {
1013 return QPair<QString, QString>(
error, storePath);
1017 CppUtilities::walkThroughArchiveFromBuffer(
1018 dataView, archiveName.toStdString(),
1019 [
this](
const char *filePath,
const char *fileName, mode_t mode) {
1022 if (m_p->watcher.isCanceled()) {
1025 return m_p->executableRegex.match(QString::fromUtf8(fileName)).hasMatch();
1027 [&](std::string_view path, CppUtilities::ArchiveFile &&file) {
1029 if (checkCancellation()) {
1032 if (file.type != CppUtilities::ArchiveFileType::Regular) {
1037 const auto fileName = QString::fromUtf8(file.name.data(),
static_cast<QString::size_type
>(file.name.size()));
1038 if (!m_p->signatureExtension.isEmpty() && fileName.endsWith(m_p->signatureExtension)) {
1039 m_p->signature = QByteArray::fromStdString(file.content);
1040 foundSignature =
true;
1041 signatureName = file.name;
1042 return foundExecutable;
1046 if (fileName.endsWith(QLatin1String(
".sig"))) {
1051 foundExecutable =
true;
1052 newExeName = file.name;
1053 newExe.setFileName(appDirPath % QChar(
'/') % fileName % QStringLiteral(
".tmp"));
1054 if (!newExe.open(QFile::WriteOnly | QFile::Truncate)) {
1055 error = tr(
"Unable to create new executable under \"%1\": %2").arg(newExe.fileName(), newExe.errorString());
1058 const auto size =
static_cast<qint64
>(file.content.size());
1059 if (!(newExe.write(file.content.data(), size) == size) || !newExe.flush()) {
1060 error = tr(
"Unable to write new executable under \"%1\": %2").arg(newExe.fileName(), newExe.errorString());
1063 if (!newExe.setPermissions(
1064 newExe.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther)) {
1065 error = tr(
"Unable to make new binary under \"%1\" executable.").arg(newExe.fileName());
1069 storePath = newExe.fileName();
1070 newExeData = std::move(file.content);
1071 return foundSignature || m_p->signatureExtension.isEmpty();
1073 }
catch (
const CppUtilities::ArchiveException &e) {
1074 error = tr(
"Unable to open downloaded archive: %1").arg(e.what());
1076 if (
error.isEmpty() && foundExecutable) {
1078 if (m_p->verifyFunction) {
1079 if (const auto verifyError = m_p->verifyFunction(Updater::Update{ .executableName = newExeName,
1080 .signatureName = signatureName,
1082 .signature = std::string_view(m_p->signature.data(), static_cast<std::size_t>(m_p->signature.size())) });
1083 !verifyError.isEmpty()) {
1084 error = tr(
"Unable to verify whether downloaded binary is valid: %1").arg(verifyError);
1085 return QPair<QString, QString>(error, storePath);
1090 auto currentExeInfo = QFileInfo(appFilePath);
1091 auto currentExe = QFile(appFilePath);
1092 const auto completeSuffix = currentExeInfo.completeSuffix();
1093 const auto suffixWithDot = completeSuffix.isEmpty() ? QString() : QChar(
'.') + completeSuffix;
1094 for (
auto i = 0; i < 100; ++i) {
1095 const auto backupNumber = i ? QString::number(i) : QString();
1096 const auto backupPath = QString(currentExeInfo.path() % QChar(
'/') % currentExeInfo.baseName() % QStringLiteral(
"-backup")
1097 % backupNumber % QChar(
'-') % QString::fromUtf8(CppUtilities::applicationInfo.version) % suffixWithDot);
1098 if (QFile::exists(backupPath)) {
1101 if (!currentExe.rename(backupPath)) {
1102 error = tr(
"Unable to move current executable to \"%1\": %2").arg(backupPath, currentExe.errorString());
1103 return QPair<QString, QString>(
error, storePath);
1109 if (!newExe.rename(appFilePath)) {
1110 error = tr(
"Unable to rename new executable \"%1\" to \"%2\": %3").arg(newExe.fileName(), appFilePath, newExe.errorString());
1111 return QPair<QString, QString>(error, storePath);
1113 storePath = newExe.fileName();
1115 if (error.isEmpty() && !foundExecutable) {
1116 error = tr(
"Unable to find executable in downloaded archive.");
1118 return QPair<QString, QString>(error, storePath);
1120 m_p->watcher.setFuture(std::move(res));
1124void Updater::concludeUpdate()
1126#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1127 auto res = m_p->watcher.result();
1128 m_p->error = res.first;
1129 m_p->storedPath = res.second;
1130 if (!m_p->error.isEmpty()) {
1131 m_p->statusMessage.clear();
1132 emit updateFailed(m_p->error);
1134 m_p->statusMessage = tr(
"Update stored under: %1").arg(m_p->storedPath);
1135 emit updateStored();
1137 emit updateStatusChanged(statusMessage());
1138 emit updatePercentageChanged(0, 0);
1139 emit inProgressChanged(
false);
1145 :
updater(executableName.isEmpty() ?
notifier.executableName() : executableName, signatureExtension)
1172 const QString &executableName,
const QString &signatureExtension, QSettings *settings, QNetworkAccessManager *nm, QObject *parent)
1176 m_p->notifier.setNetworkAccessManager(nm);
1177 m_p->updater.setNetworkAccessManager(nm);
1178 m_p->timer.setSingleShot(
true);
1179 m_p->timer.setTimerType(Qt::VeryCoarseTimer);
1180 m_p->settings = settings;
1191 return &m_p->notifier;
1196 return &m_p->updater;
1201 if (m_p->checkInterval.has_value()) {
1202 return m_p->checkInterval.value();
1204 m_p->settings->beginGroup(QStringLiteral(
"updating"));
1206 checkInterval.duration = CppUtilities::TimeSpan::fromMilliseconds(m_p->settings->value(
"checkIntervalMs", 60 * 60 * 1000).toInt());
1207 checkInterval.enabled = m_p->settings->value(
"automaticChecksEnabled",
false).toBool();
1208 m_p->settings->endGroup();
1215 m_p->settings->beginGroup(QStringLiteral(
"updating"));
1216 m_p->settings->setValue(
"checkIntervalMs",
checkInterval.duration.totalMilliseconds());
1217 m_p->settings->setValue(
"automaticChecksEnabled",
checkInterval.enabled);
1218 m_p->settings->endGroup();
1219#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1220 scheduleNextUpdateCheck();
1226 return m_p->considerSeparateSignature;
1231 m_p->considerSeparateSignature = consideringSeparateSignature;
1236 auto error = QString();
1237#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1238 static const auto appDirPath = QCoreApplication::applicationDirPath();
1239 if (appDirPath.isEmpty()) {
1240 return tr(
"Unable to determine the application directory.");
1242#if defined(Q_OS_WINDOWS) && (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
1243 const auto permissionGuard = QNtfsPermissionCheckGuard();
1245 const auto dirInfo = QFileInfo(appDirPath);
1246 if (!dirInfo.isWritable()) {
1247 return tr(
"The directory where the executable is stored (%1) is not writable.").arg(appDirPath);
1253#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1254void UpdateHandler::setCacheLoadControl(QNetworkRequest::CacheLoadControl cacheLoadControl)
1256 m_p->notifier.setCacheLoadControl(cacheLoadControl);
1257 m_p->updater.setCacheLoadControl(cacheLoadControl);
1263#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1264 m_p->notifier.restore(m_p->settings);
1265 scheduleNextUpdateCheck();
1271 const auto &downloadUrl = !m_p->notifier.downloadUrl().isEmpty() ? m_p->notifier.downloadUrl() : m_p->notifier.previousVersionDownloadUrl();
1272 const auto &signatureUrl = !m_p->notifier.downloadUrl().isEmpty() ? m_p->notifier.signatureUrl() : m_p->notifier.previousVersionSignatureUrl();
1273 m_p->updater.performUpdate(downloadUrl.toString(), m_p->considerSeparateSignature ? signatureUrl.toString() : QString());
1278#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1279 m_p->notifier.save(m_p->settings);
1283void UpdateHandler::handleUpdateCheckDone()
1285#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1287 scheduleNextUpdateCheck();
1291#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1292void UpdateHandler::scheduleNextUpdateCheck()
1297 if (!interval.enabled || (interval.duration.isNull() && m_p->hasCheckedOnceSinceStartup)) {
1300 const auto timeLeft = interval.duration - (CppUtilities::DateTime::now() - m_p->notifier.lastCheck());
1301 std::cerr << CppUtilities::EscapeCodes::Phrases::Info
1302 <<
"Check for updates due in: " << timeLeft.toString(CppUtilities::TimeSpanOutputFormat::WithMeasures)
1303 << CppUtilities::EscapeCodes::Phrases::End;
1304 m_p->hasCheckedOnceSinceStartup =
true;
1305 m_p->timer.start(std::max(1000,
static_cast<int>(timeLeft.totalMilliseconds())));
1311 m_restartRequested =
true;
1312#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1313 QCoreApplication::quit();
1319#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1320 if (!m_restartRequested) {
1323 auto *
const process =
new QProcess(QCoreApplication::instance());
1324 auto args = QCoreApplication::arguments();
1326 process->setProgram(QCoreApplication::applicationFilePath());
1327 process->setArguments(args);
1328 process->startDetached();
1332#ifdef QT_UTILITIES_GUI_QTWIDGETS
1333struct UpdateOptionPagePrivate {
1335 : updateHandler(updateHandler)
1338 UpdateHandler *updateHandler =
nullptr;
1339 std::function<void()> restartHandler;
1342UpdateOptionPage::UpdateOptionPage(
UpdateHandler *updateHandler, QWidget *parentWidget)
1343 : UpdateOptionPageBase(parentWidget)
1344#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1345 , m_p(std::make_unique<UpdateOptionPagePrivate>(updateHandler))
1348#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
1349 Q_UNUSED(updateHandler)
1353UpdateOptionPage::~UpdateOptionPage()
1357void UpdateOptionPage::setRestartHandler(std::function<
void()> &&handler)
1359 m_p->restartHandler = std::move(handler);
1360#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1361 if (ui() && m_p->restartHandler) {
1362 QObject::connect(ui()->restartPushButton, &QPushButton::clicked, widget(), m_p->restartHandler);
1367bool UpdateOptionPage::apply()
1369#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1370 if (!m_p->updateHandler) {
1374 .duration = CppUtilities::TimeSpan::fromMinutes(ui()->checkIntervalSpinBox->value()), .enabled = ui()->enabledCheckBox->isChecked() });
1378 m_p->updateHandler->notifier()->setFlags(flags);
1379 m_p->updateHandler->saveNotifierState();
1384void UpdateOptionPage::reset()
1386#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1387 if (!m_p->updateHandler) {
1390 const auto &checkInterval = m_p->updateHandler->checkInterval();
1391 ui()->checkIntervalSpinBox->setValue(
static_cast<int>(checkInterval.duration.totalMinutes()));
1392 ui()->enabledCheckBox->setChecked(checkInterval.enabled);
1393 const auto flags = m_p->updateHandler->notifier()->flags();
1399#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1400static QString formatReleaseNotes(
const QString &version,
const QString &releaseNotes)
1402 auto res = QCoreApplication::translate(
"QtGui::UpdateOptionPage",
"**Release notes of version %1:**\n\n").arg(version) + releaseNotes;
1405 static const auto re = QRegularExpression(R
"(https://github\.com/[^\s)]+)");
1406 static constexpr auto replacementLengthDiff = qsizetype(2);
1407 auto offset = qsizetype();
1408 for (
auto it = re.globalMatch(res); it.hasNext(); offset += replacementLengthDiff) {
1409 const auto match = it.next();
1410 const auto replacement = QChar(
'<') % match.captured(0) % QChar(
'>');
1411 res.replace(match.capturedStart() + offset, match.capturedLength(), replacement);
1418QWidget *UpdateOptionPage::setupWidget()
1420#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1421 if (m_p->updateHandler && m_p->updateHandler->notifier()->isSupported()) {
1422 auto *
const widget = UpdateOptionPageBase::setupWidget();
1423 ui()->versionInUseValueLabel->setText(QString::fromUtf8(CppUtilities::applicationInfo.version));
1424 ui()->updateWidget->hide();
1425 ui()->releaseNotesPushButton->hide();
1426 updateLatestVersion();
1428 QObject::connect(ui()->updatePushButton, &QPushButton::clicked, widget, [
this, widget] {
1429 if (
const auto preCheckError = m_p->updateHandler->preCheck(); preCheckError.isEmpty()
1430 || QMessageBox::critical(widget, QCoreApplication::applicationName(),
1431 QCoreApplication::translate(
"QtGui::UpdateOptionPage",
"<p>%1</p><p><strong>Try the update nevertheless?</strong></p>")
1432 .arg(preCheckError),
1433 QMessageBox::Yes | QMessageBox::No)
1434 == QMessageBox::Yes) {
1435 m_p->updateHandler->performUpdate();
1438 QObject::connect(ui()->abortUpdatePushButton, &QPushButton::clicked, m_p->updateHandler->updater(), &
Updater::abortUpdate);
1439 if (m_p->restartHandler) {
1440 QObject::connect(ui()->restartPushButton, &QPushButton::clicked, widget, m_p->restartHandler);
1442 QObject::connect(ui()->releaseNotesPushButton, &QPushButton::clicked, widget, [
this, widget] {
1443 const auto *
const notifier = m_p->updateHandler->notifier();
1444 auto infobox = QMessageBox(widget);
1445 infobox.setWindowTitle(QCoreApplication::applicationName());
1446 infobox.setIcon(QMessageBox::Information);
1447 infobox.setText(formatReleaseNotes(notifier->latestVersion(), notifier->releaseNotes()));
1448#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
1449 infobox.setTextFormat(Qt::MarkdownText);
1451 infobox.setTextFormat(Qt::PlainText);
1458 const auto *const updater = m_p->updateHandler->updater();
1459 ui()->updateWidget->setVisible(true);
1460 ui()->updateInProgressLabel->setText(updater->overallStatus());
1461 ui()->updateProgressBar->setVisible(inProgress);
1462 ui()->abortUpdatePushButton->setVisible(inProgress);
1463 ui()->restartPushButton->setVisible(!inProgress && !updater->storedPath().isEmpty() && updater->error().isEmpty());
1466 [
this](
const QString &statusMessage) { ui()->updateStatusLabel->setText(statusMessage); });
1468 if (bytesTotal == 0) {
1469 ui()->updateProgressBar->setMaximum(0);
1471 ui()->updateProgressBar->setValue(static_cast<int>(bytesReceived * 100 / bytesTotal));
1472 ui()->updateProgressBar->setMaximum(100);
1479 auto *
const label =
new QLabel;
1480 label->setWindowTitle(QCoreApplication::translate(
"QtGui::UpdateOptionPage",
"Updating"));
1481 label->setAlignment(Qt::AlignCenter);
1482 label->setWordWrap(
true);
1483#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1484 label->setText(QCoreApplication::translate(
"QtUtilities::UpdateOptionPage",
"Checking for updates is not supported on this platform."));
1486 label->setText(QCoreApplication::translate(
"QtUtilities::UpdateOptionPage",
1487 "This build of %1 has automatic updates disabled. You may update the application in an automated way via your package manager, though.")
1488 .arg(CppUtilities::applicationInfo.name));
1493void UpdateOptionPage::updateLatestVersion(
bool)
1495#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1496 if (!m_p->updateHandler) {
1499 const auto ¬ifier = *m_p->updateHandler->notifier();
1500 const auto &downloadUrl = notifier.downloadUrl();
1501 const auto &previousVersionDownloadUrl = notifier.previousVersionDownloadUrl();
1502 const auto downloadUrlEscaped = downloadUrl.toString().toHtmlEscaped();
1503 const auto previousVersionDownloadUrlEscaped = previousVersionDownloadUrl.toString().toHtmlEscaped();
1504 ui()->latestVersionValueLabel->setText(notifier.status());
1505 ui()->downloadUrlLabel->setText(downloadUrl.isEmpty()
1506 ? (notifier.latestVersion().isEmpty()
1507 ? QCoreApplication::translate(
"QtUtilities::UpdateOptionPage",
"no new version available for download")
1508 : (QCoreApplication::translate(
"QtUtilities::UpdateOptionPage",
"latest version provides no build for the current platform yet")
1509 + (previousVersionDownloadUrl.isEmpty()
1511 : QString(QStringLiteral(
"<br>")
1512 % QCoreApplication::translate(
"QtUtilities::UpdateOptionPage",
"for latest build: ")
1513 % QStringLiteral(
"<a href=\"") % previousVersionDownloadUrlEscaped % QStringLiteral(
"\">")
1514 % previousVersionDownloadUrlEscaped % QStringLiteral(
"</a>")))))
1515 : (QStringLiteral(
"<a href=\"") % downloadUrlEscaped % QStringLiteral(
"\">") % downloadUrlEscaped % QStringLiteral(
"</a>")));
1516 ui()->updatePushButton->setText(!downloadUrl.isEmpty() || previousVersionDownloadUrl.isEmpty()
1517 ? QCoreApplication::translate(
"QtUtilities::UpdateOptionPage",
"Update to latest version")
1518 : QCoreApplication::translate(
"QtUtilities::UpdateOptionPage",
"Update to latest available build"));
1519 ui()->updatePushButton->setDisabled(downloadUrl.isEmpty() && previousVersionDownloadUrl.isEmpty());
1520 ui()->releaseNotesPushButton->setHidden(notifier.releaseNotes().isEmpty());
1524VerificationErrorMessageBox::VerificationErrorMessageBox()
1526#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1527 setWindowTitle(QCoreApplication::applicationName());
1528 setStandardButtons(QMessageBox::Cancel | QMessageBox::Ignore);
1529 setDefaultButton(QMessageBox::Cancel);
1530 setIcon(QMessageBox::Critical);
1534VerificationErrorMessageBox::~VerificationErrorMessageBox()
1538int VerificationErrorMessageBox::execForError(QString &errorMessage,
const QString &explanation)
1540#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1541 auto loop = QEventLoop();
1542 QObject::connect(
this, &QDialog::finished, &loop, &QEventLoop::exit);
1543 QMetaObject::invokeMethod(
this,
"openForError", Qt::QueuedConnection, Q_ARG(QString, errorMessage), Q_ARG(QString, explanation));
1544 auto res = loop.exec();
1545 if (res == QMessageBox::Ignore) {
1546 errorMessage.clear();
1550 Q_UNUSED(errorMessage)
1551 Q_UNUSED(explanation)
1556void VerificationErrorMessageBox::openForError(
const QString &errorMessage,
const QString &explanation)
1558#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1559 setText(tr(
"<p>The signature of the downloaded executable could not be verified: %1</p>").arg(errorMessage) + explanation);
1562 Q_UNUSED(errorMessage)
1563 Q_UNUSED(explanation)
1567struct UpdateDialogPrivate {
1568 UpdateOptionPage *updateOptionPage =
nullptr;
1571UpdateDialog::UpdateDialog(QWidget *parent)
1573 , m_p(std::make_unique<UpdateDialogPrivate>())
1575 auto *
const category =
new OptionCategory;
1577 category->assignPages({ m_p->updateOptionPage });
1578 setWindowTitle(m_p->updateOptionPage->widget()->windowTitle());
1579 setTabBarAlwaysVisible(
false);
1580 setSingleCategory(category);
1583UpdateDialog::~UpdateDialog()
1587UpdateOptionPage *UpdateDialog::page()
1589 return m_p->updateOptionPage;
1592const UpdateOptionPage *UpdateDialog::page()
const
1594 return m_p->updateOptionPage;
1601#if defined(QT_UTILITIES_GUI_QTWIDGETS)
void respawnIfRestartRequested()
The SettingsDialog class provides a framework for creating settings dialogs with different categories...
The UpdateHandler class manages the non-graphical aspects of checking for new updates and performing ...
~UpdateHandler() override
bool isConsideringSeparateSignature() const
static UpdateHandler * mainInstance()
void setConsideringSeparateSignature(bool consideringSeparateSignature)
UpdateHandler(QSettings *settings, QNetworkAccessManager *nm, QObject *parent=nullptr)
Handles checking for updates and performing an update of the application if available.
const CheckInterval & checkInterval() const
UpdateNotifier * notifier
void setCheckInterval(CheckInterval checkInterval)
The UpdateNotifier class allows checking for new updates.
void setFlags(UpdateCheckFlags flags)
void setNetworkAccessManager(QNetworkAccessManager *nm)
UpdateNotifier(QObject *parent=nullptr)
void save(QSettings *settings)
bool isInProgress() const
QUrl previousVersionSignatureUrl
bool isUpdateAvailable() const
void inProgressChanged(bool inProgress)
QUrl previousVersionDownloadUrl
void supplyNewReleaseData(const QByteArray &data)
UpdateCheckFlags flags() const
const QString & latestVersion() const
void restore(QSettings *settings)
CppUtilities::DateTime lastCheck() const
~UpdateNotifier() override
The Updater class allows downloading and applying an update.
Updater(const QString &executableName, QObject *parent=nullptr)
void updatePercentageChanged(qint64 bytesReceived, qint64 bytesTotal)
void updateStatusChanged(const QString &statusMessage)
void updateFailed(const QString &error)
std::function< QString(const Update &)> VerifyFunction
bool performUpdate(const QString &downloadUrl, const QString &signatureUrl)
void setVerifier(VerifyFunction &&verifyFunction)
void inProgressChanged(bool inProgress)
void setNetworkAccessManager(QNetworkAccessManager *nm)
bool isInProgress() const
qsizetype VersionSuffixIndex
#define INSTANTIATE_UI_FILE_BASED_OPTION_PAGE(SomeClass)
Instantiates a class declared with BEGIN_DECLARE_UI_FILE_BASED_OPTION_PAGE in a convenient way.
bool considerSeparateSignature
UpdateHandlerPrivate(const QString &executableName, const QString &signatureExtension)
std::optional< UpdateHandler::CheckInterval > checkInterval
bool hasCheckedOnceSinceStartup
The CheckInterval struct specifies whether automatic checks for updates are enabled and of often they...
#define QT_UTILITIES_EXE_REGEX
#define QT_UTILITIES_VERSION_SUFFIX