Qt Utilities 6.17.0
Common Qt related C++ classes and routines used by my applications such as dialogs, widgets and models
Loading...
Searching...
No Matches
updater.cpp
Go to the documentation of this file.
1#include "./updater.h"
2
3#include "resources/config.h"
4
5#include <QSettings>
6#include <QTimer>
7
8#include <c++utilities/application/argumentparser.h>
9
10#include <optional>
11
12#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
13#include <c++utilities/io/ansiescapecodes.h>
14#include <c++utilities/io/archive.h>
15
16#include <QCoreApplication>
17#include <QDebug>
18#include <QFile>
19#include <QFileInfo>
20#include <QFutureWatcher>
21#include <QJsonArray>
22#include <QJsonDocument>
23#include <QJsonObject>
24#include <QJsonParseError>
25#include <QNetworkAccessManager>
26#include <QNetworkReply>
27#include <QRegularExpression>
28#include <QStringBuilder>
29#include <QVersionNumber>
30#include <QtConcurrentRun>
31#include <QtGlobal> // for QtProcessorDetection and QtSystemDetection keeping it Qt 5 compatible
32
33#if defined(QT_UTILITIES_GUI_QTWIDGETS)
34#include <QMessageBox>
35#endif
36
37#include <iostream>
38#endif
39
40#if defined(QT_UTILITIES_GUI_QTWIDGETS)
41#include <QCoreApplication>
42#include <QLabel>
43
44#if defined(QT_UTILITIES_SETUP_TOOLS_ENABLED)
45#include "ui_updateoptionpage.h"
46#else
47namespace QtUtilities {
48namespace Ui {
49class UpdateOptionPage {
50public:
51 void setupUi(QWidget *)
52 {
53 }
54 void retranslateUi(QWidget *)
55 {
56 }
57};
58} // namespace Ui
59} // namespace QtUtilities
60#endif
61#endif
62
63#include "resources/config.h"
64
65#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
66#define QT_UTILITIES_VERSION_SUFFIX QString()
67#else
68#define QT_UTILITIES_VERSION_SUFFIX QStringLiteral("-qt5")
69#endif
70
71#if defined(Q_OS_WINDOWS)
72#define QT_UTILITIES_EXE_REGEX "\\.exe"
73#else
74#define QT_UTILITIES_EXE_REGEX ""
75#endif
76
77#if defined(Q_OS_WIN64)
78#if defined(Q_PROCESSOR_X86_64)
79#define QT_UTILITIES_DOWNLOAD_REGEX "-.*-x86_64-w64-mingw32"
80#elif defined(Q_PROCESSOR_ARM_64)
81#define QT_UTILITIES_DOWNLOAD_REGEX "-.*-aarch64-w64-mingw32"
82#endif
83#elif defined(Q_OS_WIN32)
84#define QT_UTILITIES_DOWNLOAD_REGEX "-.*-i686-w64-mingw32"
85#elif defined(__GNUC__) && defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
86#if defined(Q_PROCESSOR_X86_64)
87#define QT_UTILITIES_DOWNLOAD_REGEX "-.*-x86_64-pc-linux-gnu"
88#elif defined(Q_PROCESSOR_ARM_64)
89#define QT_UTILITIES_DOWNLOAD_REGEX "-.*-aarch64-pc-linux-gnu"
90#endif
91#endif
92
93namespace QtUtilities {
94
95#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
96struct UpdateNotifierPrivate {
97 QNetworkAccessManager *nm = nullptr;
98 CppUtilities::DateTime lastCheck;
99 QNetworkRequest::CacheLoadControl cacheLoadControl = QNetworkRequest::PreferNetwork;
100 QVersionNumber currentVersion = QVersionNumber();
101 QRegularExpression gitHubRegex = QRegularExpression(QStringLiteral(".*/github.com/([^/]+)/([^/]+)(/.*)?"));
102 QRegularExpression gitHubRegex2 = QRegularExpression(QStringLiteral(".*/([^/.]+)\\.github.io/([^/]+)(/.*)?"));
103 QRegularExpression assetRegex = QRegularExpression();
104 QString executableName;
105 QString newVersion;
106 QString latestVersion;
107 QString additionalInfo;
108 QString error;
109 QUrl downloadUrl;
110 QUrl signatureUrl;
111 QUrl releasesUrl;
112 bool inProgress = false;
113 bool updateAvailable = false;
114 bool verbose = false;
115};
116#else
118 QString error;
119};
120#endif
121
123 : QObject(parent)
124 , m_p(std::make_unique<UpdateNotifierPrivate>())
125{
126#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
127 return;
128#else
129 m_p->verbose = qEnvironmentVariableIntValue(PROJECT_VARNAME_UPPER "_UPDATER_VERBOSE");
130
131 const auto &appInfo = CppUtilities::applicationInfo;
132 const auto url = QString::fromUtf8(appInfo.url);
133 auto gitHubMatch = m_p->gitHubRegex.match(url);
134 if (!gitHubMatch.hasMatch()) {
135 gitHubMatch = m_p->gitHubRegex2.match(url);
136 }
137 const auto gitHubOrga = gitHubMatch.captured(1);
138 const auto gitHubRepo = gitHubMatch.captured(2);
139 if (gitHubOrga.isNull() || gitHubRepo.isNull()) {
140 return;
141 }
142 m_p->executableName = gitHubRepo + QT_UTILITIES_VERSION_SUFFIX;
143 m_p->releasesUrl
144 = QStringLiteral("https://api.github.com/repos/") % gitHubOrga % QChar('/') % gitHubRepo % QStringLiteral("/releases?per_page=25");
145 m_p->currentVersion = QVersionNumber::fromString(QLatin1String(appInfo.version));
146#ifdef QT_UTILITIES_DOWNLOAD_REGEX
147 m_p->assetRegex = QRegularExpression(m_p->executableName + QStringLiteral(QT_UTILITIES_DOWNLOAD_REGEX "\\..+"));
148#endif
149 if (m_p->verbose) {
150 qDebug() << "deduced executable name: " << m_p->executableName;
151 qDebug() << "assumed current version: " << m_p->currentVersion;
152 qDebug() << "asset regex for current platform: " << m_p->assetRegex;
153 }
154
155 connect(this, &UpdateNotifier::checkedForUpdate, this, &UpdateNotifier::lastCheckNow);
156#endif
157
158#ifdef QT_UTILITIES_FAKE_NEW_VERSION_AVAILABLE
159 QTimer::singleShot(10000, Qt::VeryCoarseTimer, this, [this] { emit updateAvailable(QStringLiteral("foo"), QString()); });
160#endif
161}
162
166
168{
169#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
170 return false;
171#else
172 return !m_p->assetRegex.pattern().isEmpty();
173#endif
174}
175
177{
178#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
179 return false;
180#else
181 return m_p->inProgress;
182#endif
183}
184
186{
187#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
188 return false;
189#else
190 return m_p->updateAvailable;
191#endif
192}
193
194const QString &UpdateNotifier::executableName() const
195{
196#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
197 static const auto v = QString();
198 return v;
199#else
200 return m_p->executableName;
201#endif
202}
203
204const QString &UpdateNotifier::newVersion() const
205{
206#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
207 static const auto v = QString();
208 return v;
209#else
210 return m_p->newVersion;
211#endif
212}
213
214const QString &UpdateNotifier::latestVersion() const
215{
216#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
217 static const auto v = QString();
218 return v;
219#else
220 return m_p->latestVersion;
221#endif
222}
223
224const QString &UpdateNotifier::additionalInfo() const
225{
226#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
227 static const auto v = QString();
228 return v;
229#else
230 return m_p->additionalInfo;
231#endif
232}
233
234const QString &UpdateNotifier::error() const
235{
236 return m_p->error;
237}
238
240{
241#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
242 static const auto v = QUrl();
243 return v;
244#else
245 return m_p->downloadUrl;
246#endif
247}
248
250{
251#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
252 static const auto v = QUrl();
253 return v;
254#else
255 return m_p->signatureUrl;
256#endif
257}
258
259CppUtilities::DateTime UpdateNotifier::lastCheck() const
260{
261#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
262 return CppUtilities::DateTime();
263#else
264 return m_p->lastCheck;
265#endif
266}
267
268void UpdateNotifier::restore(QSettings *settings)
269{
270#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
271 Q_UNUSED(settings)
272#else
273 settings->beginGroup(QStringLiteral("updating"));
274 m_p->newVersion = settings->value("newVersion").toString();
275 m_p->latestVersion = settings->value("latestVersion").toString();
276 m_p->downloadUrl = settings->value("downloadUrl").toUrl();
277 m_p->signatureUrl = settings->value("signatureUrl").toUrl();
278 m_p->lastCheck = CppUtilities::DateTime(settings->value("lastCheck").toULongLong());
279 settings->endGroup();
280#endif
281}
282
283void UpdateNotifier::save(QSettings *settings)
284{
285#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
286 Q_UNUSED(settings)
287#else
288 settings->beginGroup(QStringLiteral("updating"));
289 settings->setValue("newVersion", m_p->newVersion);
290 settings->setValue("latestVersion", m_p->latestVersion);
291 settings->setValue("downloadUrl", m_p->downloadUrl);
292 settings->setValue("signatureUrl", m_p->signatureUrl);
293 settings->setValue("lastCheck", static_cast<qulonglong>(m_p->lastCheck.ticks()));
294 settings->endGroup();
295#endif
296}
297
299{
300#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
301 if (m_p->inProgress) {
302 return tr("checking …");
303 }
304#endif
305 if (!m_p->error.isEmpty()) {
306 return tr("unable to check: %1").arg(m_p->error);
307 }
308#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
309 if (!m_p->newVersion.isEmpty()) {
310 return tr("new version available: %1 (last checked: %2)").arg(m_p->newVersion, QString::fromStdString(m_p->lastCheck.toIsoString()));
311 } else if (!m_p->latestVersion.isEmpty()) {
312 return tr("no new version available, latest release is: %1 (last checked: %2)")
313 .arg(m_p->latestVersion, QString::fromStdString(m_p->lastCheck.toIsoString()));
314 }
315#endif
316 return tr("unknown");
317}
318
319void UpdateNotifier::setNetworkAccessManager(QNetworkAccessManager *nm)
320{
321#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
322 Q_UNUSED(nm)
323#else
324 m_p->nm = nm;
325#endif
326}
327
328#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
329void UpdateNotifier::setCacheLoadControl(QNetworkRequest::CacheLoadControl cacheLoadControl)
330{
331 m_p->cacheLoadControl = cacheLoadControl;
332}
333#endif
334
335void UpdateNotifier::setError(const QString &context, QNetworkReply *reply)
336{
337#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
338 Q_UNUSED(context)
339 Q_UNUSED(reply)
340#else
341 m_p->error = context + reply->errorString();
342 emit checkedForUpdate();
343 emit inProgressChanged(m_p->inProgress = false);
344#endif
345}
346
347void UpdateNotifier::setError(const QString &context, const QJsonParseError &jsonError, const QByteArray &response, QNetworkReply *)
348{
349#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
350 Q_UNUSED(context)
351 Q_UNUSED(jsonError)
352 Q_UNUSED(response)
353#else
354 m_p->error = context % jsonError.errorString() % QChar(' ') % QChar('(') % tr("at offset %1").arg(jsonError.offset) % QChar(')');
355 if (!response.isEmpty()) {
356 m_p->error += QStringLiteral("\nResponse was: ");
357 m_p->error += QString::fromUtf8(response);
358 }
359 emit inProgressChanged(m_p->inProgress = false);
360#endif
361}
362
364{
365#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
366 m_p->error = tr("This build of the application does not support checking for updates.");
367 emit inProgressChanged(false);
368 return;
369#else
370 if (!m_p->nm || m_p->inProgress) {
371 return;
372 }
373 emit inProgressChanged(m_p->inProgress = true);
374 auto request = QNetworkRequest(m_p->releasesUrl);
375 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, m_p->cacheLoadControl);
376 auto *const reply = m_p->nm->get(request);
377 connect(reply, &QNetworkReply::finished, this, &UpdateNotifier::readReleases);
378#endif
379}
380
382{
383#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
384 m_p->updateAvailable = false;
385 m_p->downloadUrl.clear();
386 m_p->signatureUrl.clear();
387 m_p->latestVersion.clear();
388 m_p->newVersion.clear();
389#endif
390}
391
392void UpdateNotifier::lastCheckNow() const
393{
394#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
395 m_p->lastCheck = CppUtilities::DateTime::now();
396#endif
397}
398
399void UpdateNotifier::readReleases()
400{
401#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
402 auto *const reply = static_cast<QNetworkReply *>(sender());
403 reply->deleteLater();
404 switch (reply->error()) {
405 case QNetworkReply::NoError: {
406 // parse JSON
407 auto jsonError = QJsonParseError();
408 const auto response = reply->readAll();
409 const auto replyDoc = QJsonDocument::fromJson(response, &jsonError);
410 if (jsonError.error != QJsonParseError::NoError) {
411 setError(tr("Unable to parse releases: "), jsonError, response, reply);
412 return;
413 }
415#if !defined(QT_JSON_READONLY)
416 if (m_p->verbose) {
417 qDebug().noquote() << "Update check: found releases: " << QString::fromUtf8(replyDoc.toJson(QJsonDocument::Indented));
418 }
419#endif
420 const auto replyArray = replyDoc.array();
421 auto latestVersionFound = QVersionNumber();
422 auto latestVersionAsset = QJsonValue();
423 for (const auto &releaseInfoVal : replyArray) {
424 const auto releaseInfo = releaseInfoVal.toObject();
425 const auto tag = releaseInfo.value(QLatin1String("tag_name")).toString();
426 if (releaseInfo.value(QLatin1String("prerelease")).toBool() || releaseInfo.value(QLatin1String("draft")).toBool()) {
427 qDebug() << "Update check: skipping prerelease/draft: " << tag;
428 continue;
429 }
430 const auto version = QVersionNumber::fromString(tag.startsWith(QChar('v')) ? tag.mid(1) : tag);
431 const auto assets = releaseInfo.value(QLatin1String("assets"));
432 if (latestVersionFound.isNull() || version > latestVersionFound) {
433 latestVersionFound = version;
434 latestVersionAsset = assets;
435 }
436 if (!version.isNull() && version > m_p->currentVersion) {
437 m_p->latestVersion = latestVersionFound.toString();
438 m_p->newVersion = version.toString();
439 if (assets.isArray()) {
440 processAssets(assets.toArray(), true);
441 emit inProgressChanged(m_p->inProgress = false);
442 } else {
443 queryRelease(releaseInfo.value(QLatin1String("assets_url")).toString());
444 }
445 return;
446 }
447 if (m_p->verbose) {
448 qDebug() << "Update check: skipping release: " << tag;
449 }
450 }
451 m_p->latestVersion = latestVersionFound.toString();
452 if (latestVersionAsset.isArray()) {
453 processAssets(latestVersionAsset.toArray(), false);
454 }
455 emit checkedForUpdate();
456 emit inProgressChanged(m_p->inProgress = false);
457 break;
458 }
459 case QNetworkReply::OperationCanceledError:
460 emit inProgressChanged(m_p->inProgress = false);
461 return;
462 default:
463 setError(tr("Unable to request releases: "), reply);
464 }
465#endif
466}
467
468void UpdateNotifier::queryRelease(const QUrl &releaseUrl)
469{
470#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
471 Q_UNUSED(releaseUrl)
472#else
473 auto request = QNetworkRequest(releaseUrl);
474 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, m_p->cacheLoadControl);
475 auto *const reply = m_p->nm->get(request);
476 connect(reply, &QNetworkReply::finished, this, &UpdateNotifier::readRelease);
477#endif
478}
479
480void UpdateNotifier::readRelease()
481{
482#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
483 auto *const reply = static_cast<QNetworkReply *>(sender());
484 reply->deleteLater();
485 switch (reply->error()) {
486 case QNetworkReply::NoError: {
487 // parse JSON
488 auto jsonError = QJsonParseError();
489 const auto response = reply->readAll();
490 const auto replyDoc = QJsonDocument::fromJson(response, &jsonError);
491 if (jsonError.error != QJsonParseError::NoError) {
492 setError(tr("Unable to parse release: "), jsonError, response, reply);
493 return;
494 }
495#if !defined(QT_JSON_READONLY)
496 if (m_p->verbose) {
497 qDebug().noquote() << "Update check: found release info: " << QString::fromUtf8(replyDoc.toJson(QJsonDocument::Indented));
498 }
499#endif
500 processAssets(replyDoc.object().value(QLatin1String("assets")).toArray(), true);
501 emit inProgressChanged(m_p->inProgress = false);
502 break;
503 }
504 case QNetworkReply::OperationCanceledError:
505 emit inProgressChanged(m_p->inProgress = false);
506 return;
507 default:
508 setError(tr("Unable to request release: "), reply);
509 }
510#endif
511}
512
513void UpdateNotifier::processAssets(const QJsonArray &assets, bool forUpdate)
514{
515#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
516 Q_UNUSED(assets)
517 Q_UNUSED(forUpdate)
518#else
519 for (const auto &assetVal : assets) {
520 if (!m_p->downloadUrl.isEmpty() && !m_p->signatureUrl.isEmpty()) {
521 break;
522 }
523 const auto asset = assetVal.toObject();
524 const auto assetName = asset.value(QLatin1String("name")).toString();
525 if (assetName.isEmpty()) {
526 continue;
527 }
528 if (m_p->assetRegex.match(assetName).hasMatch()) {
529 const auto url = asset.value(QLatin1String("browser_download_url")).toString();
530 if (assetName.endsWith(QLatin1String(".sig"))) {
531 m_p->signatureUrl = url;
532 } else {
533 m_p->downloadUrl = url;
534 }
535 continue;
536 }
537 if (m_p->verbose) {
538 qDebug() << "Update check: skipping asset: " << assetName;
539 }
540 }
541 if (forUpdate) {
542 m_p->updateAvailable = !m_p->downloadUrl.isEmpty();
543 }
544 emit checkedForUpdate();
545 if (m_p->updateAvailable) {
546 emit updateAvailable(m_p->newVersion, m_p->additionalInfo);
547 }
548#endif
549}
550
551#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
552struct UpdaterPrivate {
553 QNetworkAccessManager *nm = nullptr;
554 QFile *fakeDownload = nullptr;
555 QNetworkReply *currentDownload = nullptr;
556 QNetworkReply *signatureDownload = nullptr;
557 QNetworkRequest::CacheLoadControl cacheLoadControl = QNetworkRequest::PreferNetwork;
558 QString error, statusMessage;
559 QByteArray signature;
560 QFutureWatcher<QPair<QString, QString>> watcher;
561 QString executableName;
562 QString signatureExtension;
563 QRegularExpression executableRegex = QRegularExpression();
564 QString storedPath;
565 Updater::VerifyFunction verifyFunction;
566};
567#else
569 QString error;
570};
571#endif
572
573Updater::Updater(const QString &executableName, QObject *parent)
574 : Updater(executableName, QString(), parent)
575{
576}
577
578Updater::Updater(const QString &executableName, const QString &signatureExtension, QObject *parent)
579 : QObject(parent)
580 , m_p(std::make_unique<UpdaterPrivate>())
581{
582#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
583 Q_UNUSED(executableName)
584 Q_UNUSED(signatureExtension)
585#else
586 connect(&m_p->watcher, &QFutureWatcher<void>::finished, this, &Updater::concludeUpdate);
587 m_p->executableName = executableName;
588 m_p->signatureExtension = signatureExtension;
589 const auto signatureRegex = signatureExtension.isEmpty()
590 ? QString()
591 : QString(QStringLiteral("(") % QRegularExpression::escape(signatureExtension) % QStringLiteral(")?"));
592#ifdef QT_UTILITIES_EXE_REGEX
593 m_p->executableRegex = QRegularExpression(executableName % QStringLiteral(QT_UTILITIES_EXE_REGEX) % signatureRegex);
594#endif
595#endif
596}
597
601
603{
604#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
605 return m_p->currentDownload != nullptr || m_p->signatureDownload != nullptr || m_p->watcher.isRunning();
606#else
607 return false;
608#endif
609}
610
612{
613#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
614 return isInProgress() ? tr("Update in progress …") : (m_p->error.isEmpty() ? tr("Update done") : tr("Update failed"));
615#else
616 return QString();
617#endif
618}
619
620const QString &Updater::error() const
621{
622 return m_p->error;
623}
624
626{
627#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
628 return m_p->statusMessage.isEmpty() ? m_p->error : m_p->statusMessage;
629#else
630 return m_p->error;
631#endif
632}
633
635{
636#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
637 return m_p->storedPath;
638#else
639 static const auto empty = QString();
640 return empty;
641#endif
642}
643
644void Updater::setNetworkAccessManager(QNetworkAccessManager *nm)
645{
646#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
647 m_p->nm = nm;
648#else
649 Q_UNUSED(nm)
650#endif
651}
652
654{
655#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
656 m_p->verifyFunction = std::move(verifyFunction);
657#else
658 Q_UNUSED(verifyFunction)
659#endif
660}
661
662#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
663void Updater::setCacheLoadControl(QNetworkRequest::CacheLoadControl cacheLoadControl)
664{
665 m_p->cacheLoadControl = cacheLoadControl;
666}
667#endif
668
669bool Updater::performUpdate(const QString &downloadUrl, const QString &signatureUrl)
670{
671#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
672 Q_UNUSED(downloadUrl)
673 Q_UNUSED(signatureUrl)
674 setError(tr("This build of the application does not support self-updating."));
675 return false;
676#else
677 if (isInProgress()) {
678 return false;
679 }
680 startDownload(downloadUrl, signatureUrl);
681 return true;
682#endif
683}
684
686{
687#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
688 if (m_p->currentDownload) {
689 m_p->currentDownload->abort();
690 }
691 if (m_p->signatureDownload) {
692 m_p->signatureDownload->abort();
693 }
694 if (m_p->watcher.isRunning()) {
695 m_p->watcher.cancel();
696 }
697#endif
698}
699
700void Updater::setError(const QString &error)
701{
702#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
703 m_p->statusMessage.clear();
704#endif
705 emit updateFailed(m_p->error = error);
706 emit updateStatusChanged(m_p->error);
707 emit updatePercentageChanged(0, 0);
708 emit inProgressChanged(false);
709}
710
711void Updater::startDownload(const QString &downloadUrl, const QString &signatureUrl)
712{
713#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
714 Q_UNUSED(downloadUrl)
715 Q_UNUSED(signatureUrl)
716#else
717 m_p->error.clear();
718 m_p->storedPath.clear();
719 m_p->signature.clear();
720
721 if (const auto fakeDownloadPath = qEnvironmentVariable(PROJECT_VARNAME_UPPER "_UPDATER_FAKE_DOWNLOAD"); !fakeDownloadPath.isEmpty()) {
722 m_p->fakeDownload = new QFile(fakeDownloadPath);
723 m_p->fakeDownload->open(QFile::ReadOnly);
724 emit inProgressChanged(true);
725 storeExecutable();
726 return;
727 }
728
729 auto request = QNetworkRequest(QUrl(downloadUrl));
730 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, m_p->cacheLoadControl);
731 m_p->statusMessage = tr("Downloading %1").arg(downloadUrl);
732 m_p->currentDownload = m_p->nm->get(request);
733 emit updateStatusChanged(m_p->statusMessage);
734 emit updatePercentageChanged(0, 0);
735 emit inProgressChanged(true);
736 connect(m_p->currentDownload, &QNetworkReply::finished, this, &Updater::handleDownloadFinished);
737 connect(m_p->currentDownload, &QNetworkReply::downloadProgress, this, &Updater::updatePercentageChanged);
738 if (!signatureUrl.isEmpty()) {
739 request.setUrl(signatureUrl);
740 m_p->signatureDownload = m_p->nm->get(request);
741 connect(m_p->signatureDownload, &QNetworkReply::finished, this, &Updater::handleDownloadFinished);
742 }
743#endif
744}
745
746void Updater::handleDownloadFinished()
747{
748#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
749 if (m_p->signatureDownload && !m_p->signatureDownload->isFinished()) {
750 emit updateStatusChanged(tr("Waiting for signature download …"));
751 emit updatePercentageChanged(0, 0);
752 return;
753 }
754 if (!m_p->currentDownload->isFinished()) {
755 return;
756 }
757
758 if (m_p->signatureDownload) {
759 readSignature();
760 m_p->signatureDownload->deleteLater();
761 m_p->signatureDownload = nullptr;
762 }
763
764 if (m_p->error.isEmpty()) {
765 storeExecutable();
766 } else {
767 m_p->currentDownload->deleteLater();
768 }
769 m_p->currentDownload = nullptr;
770#endif
771}
772
773void Updater::readSignature()
774{
775#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
776 switch (m_p->signatureDownload->error()) {
777 case QNetworkReply::NoError:
778 m_p->signature = m_p->signatureDownload->readAll();
779 break;
780 default:
781 setError(tr("Unable to download signature: ") + m_p->signatureDownload->errorString());
782 }
783#endif
784}
785
786void Updater::storeExecutable()
787{
788#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
789 m_p->statusMessage = tr("Extracting …");
790 emit updateStatusChanged(m_p->statusMessage);
791 emit updatePercentageChanged(0, 0);
792 auto *reply = static_cast<QIODevice *>(m_p->fakeDownload);
793 auto archiveName = QString();
794 auto hasError = false;
795 if (reply) {
796 archiveName = m_p->fakeDownload->fileName();
797 hasError = m_p->fakeDownload->error() != QFileDevice::NoError;
798 } else {
799 reply = m_p->currentDownload;
800 archiveName = m_p->currentDownload->request().url().fileName();
801 hasError = m_p->currentDownload->error() != QNetworkReply::NoError;
802 }
803 if (hasError) {
804 reply->deleteLater();
805 setError(tr("Unable to download update: ") + reply->errorString());
806 return;
807 }
808 auto res = QtConcurrent::run([this, reply, archiveName] {
809 const auto data = reply->readAll();
810 const auto dataView = std::string_view(data.data(), static_cast<std::size_t>(data.size()));
811 auto foundExecutable = false, foundSignature = false;
812 auto error = QString(), storePath = QString();
813 auto newExeName = std::string(), signatureName = std::string();
814 auto newExeData = std::string();
815 auto newExe = QFile();
816 reply->deleteLater();
817
818 // determine current executable path
819 const auto appDirPath = QCoreApplication::applicationDirPath();
820 const auto appFilePath = QCoreApplication::applicationFilePath();
821 if (appDirPath.isEmpty() || appFilePath.isEmpty()) {
822 error = tr("Unable to determine application path.");
823 return QPair<QString, QString>(error, storePath);
824 }
825
826 // handle cancellations
827 const auto checkCancellation = [this, &error] {
828 if (m_p->watcher.isCanceled()) {
829 error = tr("Extraction was cancelled.");
830 return true;
831 } else {
832 return false;
833 }
834 };
835 if (checkCancellation()) {
836 return QPair<QString, QString>(error, storePath);
837 }
838
839 try {
840 CppUtilities::walkThroughArchiveFromBuffer(
841 dataView, archiveName.toStdString(),
842 [this](const char *filePath, const char *fileName, mode_t mode) {
843 Q_UNUSED(filePath)
844 Q_UNUSED(mode)
845 if (m_p->watcher.isCanceled()) {
846 return true;
847 }
848 return m_p->executableRegex.match(QString::fromUtf8(fileName)).hasMatch();
849 },
850 [&](std::string_view path, CppUtilities::ArchiveFile &&file) {
851 Q_UNUSED(path)
852 if (checkCancellation()) {
853 return true;
854 }
855 if (file.type != CppUtilities::ArchiveFileType::Regular) {
856 return false;
857 }
858
859 // read signature file
860 const auto fileName = QString::fromUtf8(file.name.data(), static_cast<QString::size_type>(file.name.size()));
861 if (!m_p->signatureExtension.isEmpty() && fileName.endsWith(m_p->signatureExtension)) {
862 m_p->signature = QByteArray::fromStdString(file.content);
863 foundSignature = true;
864 signatureName = file.name;
865 return foundExecutable && foundSignature;
866 }
867
868 // write executable from archive to disk (using a temporary filename)
869 foundExecutable = true;
870 newExeName = file.name;
871 newExe.setFileName(appDirPath % QChar('/') % fileName % QStringLiteral(".tmp"));
872 if (!newExe.open(QFile::WriteOnly | QFile::Truncate)) {
873 error = tr("Unable to create new executable under \"%1\": %2").arg(newExe.fileName(), newExe.errorString());
874 return true;
875 }
876 const auto size = static_cast<qint64>(file.content.size());
877 if (!(newExe.write(file.content.data(), size) == size) || !newExe.flush()) {
878 error = tr("Unable to write new executable under \"%1\": %2").arg(newExe.fileName(), newExe.errorString());
879 return true;
880 }
881 if (!newExe.setPermissions(
882 newExe.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther)) {
883 error = tr("Unable to make new binary under \"%1\" executable.").arg(newExe.fileName());
884 return true;
885 }
886
887 storePath = newExe.fileName();
888 newExeData = std::move(file.content);
889 return foundExecutable && foundSignature;
890 });
891 } catch (const CppUtilities::ArchiveException &e) {
892 error = tr("Unable to open downloaded archive: %1").arg(e.what());
893 }
894 if (error.isEmpty() && foundExecutable) {
895 // verify whether downloaded binary is valid if a verify function was assigned
896 if (m_p->verifyFunction) {
897 if (const auto verifyError = m_p->verifyFunction(Updater::Update{ .executableName = newExeName,
898 .signatureName = signatureName,
899 .data = newExeData,
900 .signature = std::string_view(m_p->signature.data(), static_cast<std::size_t>(m_p->signature.size())) });
901 !verifyError.isEmpty()) {
902 error = tr("Unable to verify whether downloaded binary is valid: %1").arg(verifyError);
903 return QPair<QString, QString>(error, storePath);
904 }
905 }
906
907 // rename current executable to keep it as backup
908 auto currentExeInfo = QFileInfo(appFilePath);
909 auto currentExe = QFile(appFilePath);
910 const auto completeSuffix = currentExeInfo.completeSuffix();
911 const auto suffixWithDot = completeSuffix.isEmpty() ? QString() : QChar('.') + completeSuffix;
912 for (auto i = 0; i < 100; ++i) {
913 const auto backupNumber = i ? QString::number(i) : QString();
914 const auto backupPath = QString(currentExeInfo.path() % QChar('/') % currentExeInfo.baseName() % QStringLiteral("-backup")
915 % backupNumber % QChar('-') % QString::fromUtf8(CppUtilities::applicationInfo.version) % suffixWithDot);
916 if (QFile::exists(backupPath)) {
917 continue;
918 }
919 if (!currentExe.rename(backupPath)) {
920 error = tr("Unable to move current executable to \"%1\": %2").arg(backupPath, currentExe.errorString());
921 return QPair<QString, QString>(error, storePath);
922 }
923 break;
924 }
925
926 // rename new executable to use it in place of current executable
927 if (!newExe.rename(appFilePath)) {
928 error = tr("Unable to rename new executable \"%1\" to \"%2\": %3").arg(newExe.fileName(), appFilePath, newExe.errorString());
929 return QPair<QString, QString>(error, storePath);
930 }
931 storePath = newExe.fileName();
932 }
933 if (error.isEmpty() && !foundExecutable) {
934 error = tr("Unable to find executable in downloaded archive.");
935 }
936 return QPair<QString, QString>(error, storePath);
937 });
938 m_p->watcher.setFuture(std::move(res));
939#endif
940}
941
942void Updater::concludeUpdate()
943{
944#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
945 auto res = m_p->watcher.result();
946 m_p->error = res.first;
947 m_p->storedPath = res.second;
948 if (!m_p->error.isEmpty()) {
949 m_p->statusMessage.clear();
950 emit updateFailed(m_p->error);
951 } else {
952 m_p->statusMessage = tr("Update stored under: %1").arg(m_p->storedPath);
953 emit updateStored();
954 }
955 emit updateStatusChanged(statusMessage());
956 emit updatePercentageChanged(0, 0);
957 emit inProgressChanged(false);
958#endif
959}
960
962 explicit UpdateHandlerPrivate(const QString &executableName, const QString &signatureExtension)
963 : updater(executableName.isEmpty() ? notifier.executableName() : executableName, signatureExtension)
964 {
965 }
966
969 QTimer timer;
970 QSettings *settings;
971 std::optional<UpdateHandler::CheckInterval> checkInterval;
974};
975
976UpdateHandler *UpdateHandler::s_mainInstance = nullptr;
977
981UpdateHandler::UpdateHandler(QSettings *settings, QNetworkAccessManager *nm, QObject *parent)
982 : QtUtilities::UpdateHandler(QString(), QString(), settings, nm, parent)
983{
984}
985
990 const QString &executableName, const QString &signatureExtension, QSettings *settings, QNetworkAccessManager *nm, QObject *parent)
991 : QObject(parent)
992 , m_p(std::make_unique<UpdateHandlerPrivate>(executableName, signatureExtension))
993{
994 m_p->notifier.setNetworkAccessManager(nm);
995 m_p->updater.setNetworkAccessManager(nm);
996 m_p->timer.setSingleShot(true);
997 m_p->timer.setTimerType(Qt::VeryCoarseTimer);
998 m_p->settings = settings;
999 connect(&m_p->timer, &QTimer::timeout, &m_p->notifier, &UpdateNotifier::checkForUpdate);
1000 connect(&m_p->notifier, &UpdateNotifier::checkedForUpdate, this, &UpdateHandler::handleUpdateCheckDone);
1001}
1002
1006
1008{
1009 return &m_p->notifier;
1010}
1011
1012Updater *UpdateHandler::updater()
1013{
1014 return &m_p->updater;
1015}
1016
1018{
1019 if (m_p->checkInterval.has_value()) {
1020 return m_p->checkInterval.value();
1021 }
1022 m_p->settings->beginGroup(QStringLiteral("updating"));
1023 auto &checkInterval = m_p->checkInterval.emplace();
1024 checkInterval.duration = CppUtilities::TimeSpan::fromMilliseconds(m_p->settings->value("checkIntervalMs", 60 * 60 * 1000).toInt());
1025 checkInterval.enabled = m_p->settings->value("automaticChecksEnabled", false).toBool();
1026 m_p->settings->endGroup();
1027 return checkInterval;
1028}
1029
1031{
1032 m_p->checkInterval = checkInterval;
1033 m_p->settings->beginGroup(QStringLiteral("updating"));
1034 m_p->settings->setValue("checkIntervalMs", checkInterval.duration.totalMilliseconds());
1035 m_p->settings->setValue("automaticChecksEnabled", checkInterval.enabled);
1036 m_p->settings->endGroup();
1037#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1038 scheduleNextUpdateCheck();
1039#endif
1040}
1041
1043{
1044 return m_p->considerSeparateSignature;
1045}
1046
1047void UpdateHandler::setConsideringSeparateSignature(bool consideringSeparateSignature)
1048{
1049 m_p->considerSeparateSignature = consideringSeparateSignature;
1050}
1051
1052#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1053void UpdateHandler::setCacheLoadControl(QNetworkRequest::CacheLoadControl cacheLoadControl)
1054{
1055 m_p->notifier.setCacheLoadControl(cacheLoadControl);
1056 m_p->updater.setCacheLoadControl(cacheLoadControl);
1057}
1058#endif
1059
1061{
1062#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1063 m_p->notifier.restore(m_p->settings);
1064 scheduleNextUpdateCheck();
1065#endif
1066}
1067
1069{
1070 m_p->updater.performUpdate(
1071 m_p->notifier.downloadUrl().toString(), m_p->considerSeparateSignature ? m_p->notifier.signatureUrl().toString() : QString());
1072}
1073
1074void UpdateHandler::handleUpdateCheckDone()
1075{
1076#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1077 m_p->notifier.save(m_p->settings);
1078 scheduleNextUpdateCheck();
1079#endif
1080}
1081
1082#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1083void UpdateHandler::scheduleNextUpdateCheck()
1084{
1085 m_p->timer.stop();
1086
1087 const auto &interval = checkInterval();
1088 if (!interval.enabled || (interval.duration.isNull() && m_p->hasCheckedOnceSinceStartup)) {
1089 return;
1090 }
1091 const auto timeLeft = interval.duration - (CppUtilities::DateTime::now() - m_p->notifier.lastCheck());
1092 std::cerr << CppUtilities::EscapeCodes::Phrases::Info
1093 << "Check for updates due in: " << timeLeft.toString(CppUtilities::TimeSpanOutputFormat::WithMeasures)
1094 << CppUtilities::EscapeCodes::Phrases::End;
1095 m_p->hasCheckedOnceSinceStartup = true; // the attempt counts
1096 m_p->timer.start(std::max(1000, static_cast<int>(timeLeft.totalMilliseconds())));
1097}
1098#endif
1099
1100#ifdef QT_UTILITIES_GUI_QTWIDGETS
1101struct UpdateOptionPagePrivate {
1102 UpdateOptionPagePrivate(UpdateHandler *updateHandler)
1103 : updateHandler(updateHandler)
1104 {
1105 }
1106 UpdateHandler *updateHandler = nullptr;
1107 std::function<void()> restartHandler;
1108};
1109
1110UpdateOptionPage::UpdateOptionPage(UpdateHandler *updateHandler, QWidget *parentWidget)
1111 : UpdateOptionPageBase(parentWidget)
1112#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1113 , m_p(std::make_unique<UpdateOptionPagePrivate>(updateHandler))
1114#endif
1115{
1116#ifndef QT_UTILITIES_SETUP_TOOLS_ENABLED
1117 Q_UNUSED(updateHandler)
1118#endif
1119}
1120
1121UpdateOptionPage::~UpdateOptionPage()
1122{
1123}
1124
1125void UpdateOptionPage::setRestartHandler(std::function<void()> &&handler)
1126{
1127 m_p->restartHandler = std::move(handler);
1128}
1129
1130bool UpdateOptionPage::apply()
1131{
1132#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1133 if (!m_p->updateHandler) {
1134 return true;
1135 }
1136 m_p->updateHandler->setCheckInterval(UpdateHandler::CheckInterval{
1137 .duration = CppUtilities::TimeSpan::fromMinutes(ui()->checkIntervalSpinBox->value()), .enabled = ui()->enabledCheckBox->isChecked() });
1138#endif
1139 return true;
1140}
1141
1142void UpdateOptionPage::reset()
1143{
1144#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1145 if (!m_p->updateHandler) {
1146 return;
1147 }
1148 const auto &checkInterval = m_p->updateHandler->checkInterval();
1149 ui()->checkIntervalSpinBox->setValue(static_cast<int>(checkInterval.duration.totalMinutes()));
1150 ui()->enabledCheckBox->setChecked(checkInterval.enabled);
1151#endif
1152}
1153
1154QWidget *UpdateOptionPage::setupWidget()
1155{
1156#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1157 // call base implementation first, so ui() is available
1158 if (m_p->updateHandler && m_p->updateHandler->notifier()->isSupported()) {
1159 auto *const widget = UpdateOptionPageBase::setupWidget();
1160 ui()->versionInUseValueLabel->setText(QString::fromUtf8(CppUtilities::applicationInfo.version));
1161 ui()->updateWidget->hide();
1162 updateLatestVersion();
1163 QObject::connect(ui()->checkNowPushButton, &QPushButton::clicked, m_p->updateHandler->notifier(), &UpdateNotifier::checkForUpdate);
1164 QObject::connect(ui()->updatePushButton, &QPushButton::clicked, m_p->updateHandler, &UpdateHandler::performUpdate);
1165 QObject::connect(ui()->abortUpdatePushButton, &QPushButton::clicked, m_p->updateHandler->updater(), &Updater::abortUpdate);
1166 QObject::connect(ui()->restartPushButton, &QPushButton::clicked, widget, m_p->restartHandler);
1167 QObject::connect(
1168 m_p->updateHandler->notifier(), &UpdateNotifier::inProgressChanged, widget, [this](bool inProgress) { updateLatestVersion(inProgress); });
1169 QObject::connect(m_p->updateHandler->updater(), &Updater::inProgressChanged, widget, [this](bool inProgress) {
1170 const auto *const updater = m_p->updateHandler->updater();
1171 ui()->updateWidget->setVisible(true);
1172 ui()->updateInProgressLabel->setText(updater->overallStatus());
1173 ui()->updateProgressBar->setVisible(inProgress);
1174 ui()->abortUpdatePushButton->setVisible(inProgress);
1175 ui()->restartPushButton->setVisible(!inProgress && !updater->storedPath().isEmpty() && updater->error().isEmpty());
1176 });
1177 QObject::connect(m_p->updateHandler->updater(), &Updater::updateStatusChanged, widget,
1178 [this](const QString &statusMessage) { ui()->updateStatusLabel->setText(statusMessage); });
1179 QObject::connect(m_p->updateHandler->updater(), &Updater::updatePercentageChanged, widget, [this](qint64 bytesReceived, qint64 bytesTotal) {
1180 if (bytesTotal == 0) {
1181 ui()->updateProgressBar->setMaximum(0);
1182 } else {
1183 ui()->updateProgressBar->setValue(static_cast<int>(bytesReceived * 100 / bytesTotal));
1184 ui()->updateProgressBar->setMaximum(100);
1185 }
1186 });
1187 return widget;
1188 }
1189#endif
1190
1191 auto *const label = new QLabel;
1192 label->setWindowTitle(QCoreApplication::translate("QtGui::UpdateOptionPage", "Updating"));
1193 label->setAlignment(Qt::AlignCenter);
1194 label->setWordWrap(true);
1195#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1196 label->setText(QCoreApplication::translate("QtUtilities::UpdateOptionPage", "Checking for updates is not supported on this platform."));
1197#else
1198 label->setText(QCoreApplication::translate("QtUtilities::UpdateOptionPage",
1199 "This build of %1 has automatic updates disabled. You may update the application in an automated way via your package manager, though.")
1200 .arg(CppUtilities::applicationInfo.name));
1201#endif
1202 return label;
1203}
1204
1205void UpdateOptionPage::updateLatestVersion(bool)
1206{
1207#ifdef QT_UTILITIES_SETUP_TOOLS_ENABLED
1208 if (!m_p->updateHandler) {
1209 return;
1210 }
1211 const auto &notifier = *m_p->updateHandler->notifier();
1212 const auto &downloadUrl = notifier.downloadUrl();
1213 const auto downloadUrlEscaped = downloadUrl.toString().toHtmlEscaped();
1214 ui()->latestVersionValueLabel->setText(notifier.status());
1215 ui()->downloadUrlLabel->setText(downloadUrl.isEmpty()
1216 ? (notifier.latestVersion().isEmpty()
1217 ? QCoreApplication::translate("QtUtilities::UpdateOptionPage", "no new version available for download")
1218 : QCoreApplication::translate(
1219 "QtUtilities::UpdateOptionPage", "new version available but no build for the current platform present yet"))
1220 : (QStringLiteral("<a href=\"") % downloadUrlEscaped % QStringLiteral("\">") % downloadUrlEscaped % QStringLiteral("</a>")));
1221 ui()->updatePushButton->setDisabled(downloadUrl.isEmpty());
1222#endif
1223}
1224
1225#endif
1226
1227} // namespace QtUtilities
1228
1229#if defined(QT_UTILITIES_GUI_QTWIDGETS)
1231#endif
The UpdateHandler class manages the non-graphical aspects of checking for new updates and performing ...
Definition updater.h:157
bool isConsideringSeparateSignature() const
Definition updater.cpp:1042
void setConsideringSeparateSignature(bool consideringSeparateSignature)
Definition updater.cpp:1047
UpdateHandler(QSettings *settings, QNetworkAccessManager *nm, QObject *parent=nullptr)
Handles checking for updates and performing an update of the application if available.
Definition updater.cpp:981
const CheckInterval & checkInterval() const
Definition updater.cpp:1017
UpdateNotifier * notifier
Definition updater.h:159
void setCheckInterval(CheckInterval checkInterval)
Definition updater.cpp:1030
The UpdateNotifier class allows checking for new updates.
Definition updater.h:39
void setNetworkAccessManager(QNetworkAccessManager *nm)
Definition updater.cpp:319
UpdateNotifier(QObject *parent=nullptr)
Definition updater.cpp:122
void save(QSettings *settings)
Definition updater.cpp:283
bool isUpdateAvailable() const
Definition updater.cpp:185
void inProgressChanged(bool inProgress)
const QString & latestVersion() const
Definition updater.cpp:214
void restore(QSettings *settings)
Definition updater.cpp:268
CppUtilities::DateTime lastCheck() const
Definition updater.cpp:259
The Updater class allows downloading and applying an update.
Definition updater.h:99
Updater(const QString &executableName, QObject *parent=nullptr)
Definition updater.cpp:573
void updatePercentageChanged(qint64 bytesReceived, qint64 bytesTotal)
void updateStatusChanged(const QString &statusMessage)
void updateFailed(const QString &error)
QString overallStatus
Definition updater.h:102
std::function< QString(const Update &)> VerifyFunction
Definition updater.h:114
QString statusMessage
Definition updater.h:104
bool performUpdate(const QString &downloadUrl, const QString &signatureUrl)
Definition updater.cpp:669
void setVerifier(VerifyFunction &&verifyFunction)
Definition updater.cpp:653
void inProgressChanged(bool inProgress)
void setNetworkAccessManager(QNetworkAccessManager *nm)
Definition updater.cpp:644
~Updater() override
Definition updater.cpp:598
bool isInProgress() const
Definition updater.cpp:602
#define INSTANTIATE_UI_FILE_BASED_OPTION_PAGE(SomeClass)
Instantiates a class declared with BEGIN_DECLARE_UI_FILE_BASED_OPTION_PAGE in a convenient way.
Definition optionpage.h:250
UpdateHandlerPrivate(const QString &executableName, const QString &signatureExtension)
Definition updater.cpp:962
std::optional< UpdateHandler::CheckInterval > checkInterval
Definition updater.cpp:971
The CheckInterval struct specifies whether automatic checks for updates are enabled and of often they...
Definition updater.h:164
#define QT_UTILITIES_EXE_REGEX
Definition updater.cpp:74
#define QT_UTILITIES_VERSION_SUFFIX
Definition updater.cpp:66