23 #include "autoupdater.h"
25 #include "configuration/doomseekerconfig.h"
26 #include "datapaths.h"
28 #include "strings.hpp"
29 #include "updater/updatechannel.h"
30 #include "updater/updatepackagefilter.h"
31 #include "updater/updaterinfoparser.h"
32 #include "updater/updaterscriptparser.h"
37 #include <QNetworkAccessManager>
38 #include <QNetworkRequest>
39 #include <QTemporaryFile>
45 QList<QDomDocument> allScripts;
46 bool bDownloadAndInstallRequireConfirmation;
49 bool bPackageDownloadStarted;
53 QStringList downloadedPackagesFilenames;
55 QMap<QString, QList<QString> > ignoredPackagesRevisions;
56 QList<UpdatePackage> newUpdatePackages;
57 QList<UpdatePackage> packagesInDownloadQueue;
58 QTemporaryFile *pCurrentPackageFile;
59 QNetworkAccessManager *pNam;
60 QNetworkReply *pNetworkReply;
68 const QString
AutoUpdater::MAIN_PROGRAM_PACKAGE_NAME = "doomseeker-core";
70 const QString
AutoUpdater::WADSEEKER_PACKAGE_NAME = "wadseeker";
71 const QString
AutoUpdater::UPDATER_INFO_URL_BASE = "https:
76 d->bDownloadAndInstallRequireConfirmation =
false;
77 d->bPackageDownloadStarted =
false;
78 d->bIsRunning =
false;
81 d->pCurrentPackageFile =
nullptr;
82 d->pNam =
new QNetworkAccessManager();
83 d->pNetworkReply =
nullptr;
86 AutoUpdater::~AutoUpdater()
88 if (d->pCurrentPackageFile !=
nullptr)
90 delete d->pCurrentPackageFile;
92 if (d->pNetworkReply !=
nullptr)
94 d->pNetworkReply->disconnect();
95 d->pNetworkReply->abort();
96 d->pNetworkReply->deleteLater();
98 d->pNam->disconnect();
99 d->pNam->deleteLater();
102 void AutoUpdater::abort()
104 if (d->pNetworkReply !=
nullptr)
106 d->pNetworkReply->disconnect();
107 d->pNetworkReply->abort();
108 d->pNetworkReply->deleteLater();
109 d->pNetworkReply =
nullptr;
114 QDomDocument AutoUpdater::adjustUpdaterScriptXml(
const QByteArray &xmlSource)
120 if (!xmlDoc.setContent(xmlSource, &xmlError, &xmlErrLine, &xmlErrCol))
122 gLog << tr(
"Failed to parse updater XML script: %1, l: %2, c: %3")
123 .arg(xmlError).arg(xmlErrLine).arg(xmlErrCol);
124 return QDomDocument();
127 QFileInfo currentPackageFileInfo(d->pCurrentPackageFile->fileName());
128 QString scriptParserErrMsg = scriptParser.setPackageName(
129 currentPackageFileInfo.completeBaseName());
130 if (!scriptParserErrMsg.isNull())
132 gLog << tr(
"Failed to modify package name in updater script: %1")
133 .arg(scriptParserErrMsg);
134 return QDomDocument();
144 void AutoUpdater::confirmDownloadAndInstall()
146 d->packagesInDownloadQueue = d->newUpdatePackages;
147 d->bPackageDownloadStarted =
true;
148 startNextPackageDownload();
153 return d->downloadedPackagesFilenames;
156 void AutoUpdater::dumpUpdatePackagesToLog(
const QList<UpdatePackage> &packages)
160 gLog << tr(R
"(Detected update for package "%1" from version "%2" to version "%3".)")
161 .arg(pkg.displayName, pkg.currentlyInstalledDisplayVersion, pkg.displayVersion);
165 void AutoUpdater::emitOverallProgress(
const QString &message)
169 if (!d->newUpdatePackages.isEmpty() && d->bPackageDownloadStarted)
171 total = d->newUpdatePackages.size();
174 current = total - (d->packagesInDownloadQueue.size() + 1);
175 assert(current >= 0 &&
"AutoUpdater::emitOverallProgress()");
177 emit overallProgress(current, total, message);
180 void AutoUpdater::emitStatusMessage(
const QString &message)
182 emit statusMessage(message);
190 QString AutoUpdater::errorCodeToString(ErrorCode code)
197 return tr(
"Update was aborted.");
199 return tr(
"Update channel is not configured. Please check your configuration.");
201 return tr(
"Failed to download updater info file.");
203 return tr(
"Cannot parse updater info file.");
205 return tr(
"Main program node is missing from updater info file.");
207 return tr(
"Revision info on one of the packages is missing from the "
208 "updater info file. Check the log for details.");
210 return tr(
"Download URL for one of the packages is missing from the "
211 "updater info file. Check the log for details.");
213 return tr(
"Download URL for one of the packages is invalid. "
214 "Check the log for details.");
216 return tr(
"Update package download failed. Check the log for details.");
218 return tr(
"Failed to create directory for updates packages storage.");
220 return tr(
"Failed to save update package.");
222 return tr(
"Failed to save update script.");
224 return tr(
"Unknown error.");
228 QString AutoUpdater::errorString()
const
230 return errorCodeToString(errorCode());
233 void AutoUpdater::finishWithError(ErrorCode code)
235 d->bIsRunning =
false;
240 bool AutoUpdater::isRunning()
const
242 return d->bIsRunning;
247 if (errorCode() != EC_Ok && d->pNetworkReply !=
nullptr)
249 return d->pNetworkReply->error();
251 return QNetworkReply::NoError;
254 QUrl AutoUpdater::mkVersionDataFileUrl()
261 return d->newUpdatePackages;
264 void AutoUpdater::onPackageDownloadFinish()
266 if (d->pNetworkReply->error() == QNetworkReply::NoError)
268 emitStatusMessage(tr(
"Finished downloading package \"%1\".")
269 .arg(d->currentlyDownloadedPackage.displayName));
270 startPackageScriptDownload(d->currentlyDownloadedPackage);
274 emitStatusMessage(tr(
"Network error when downloading package \"%1\": [%2] %3")
275 .arg(d->currentlyDownloadedPackage.displayName)
276 .arg(d->pNetworkReply->error())
277 .arg(d->pNetworkReply->errorString()));
282 void AutoUpdater::onPackageDownloadReadyRead()
284 const int MAX_CHUNK_SIZE = 2 * 1024 * 1024;
285 QByteArray data = d->pNetworkReply->read(MAX_CHUNK_SIZE);
286 while (!data.isEmpty())
288 d->pCurrentPackageFile->write(data);
289 data = d->pNetworkReply->read(MAX_CHUNK_SIZE);
293 void AutoUpdater::onPackageScriptDownloadFinish()
295 if (d->pNetworkReply->error() == QNetworkReply::NoError)
297 emitStatusMessage(tr(
"Finished downloading package script \"%1\".")
298 .arg(d->currentlyDownloadedPackage.displayName));
299 QByteArray xmlData = d->pNetworkReply->readAll();
300 QDomDocument xmlDoc = adjustUpdaterScriptXml(xmlData);
306 d->allScripts.append(xmlDoc);
308 if (!d->packagesInDownloadQueue.isEmpty())
310 startNextPackageDownload();
314 emitStatusMessage(tr(
"All packages downloaded. Building updater script."));
316 finishWithError(result);
321 emitStatusMessage(tr(
"Network error when downloading package script \"%1\": [%2] %3")
322 .arg(d->currentlyDownloadedPackage.displayName)
323 .arg(d->pNetworkReply->error())
324 .arg(d->pNetworkReply->errorString()));
329 void AutoUpdater::onUpdaterInfoDownloadFinish()
331 if (d->pNetworkReply->error() != QNetworkReply::NoError)
336 QByteArray json = d->pNetworkReply->readAll();
339 if (parseResult == EC_Ok)
342 filter.setIgnoreRevisions(d->ignoredPackagesRevisions);
343 QList<UpdatePackage> packagesList = filter.filter(parser.packages());
344 if (!packagesList.isEmpty())
346 dumpUpdatePackagesToLog(packagesList);
347 d->newUpdatePackages = packagesList;
348 if (d->bDownloadAndInstallRequireConfirmation)
350 emitStatusMessage(tr(
"Requesting update confirmation."));
351 emitOverallProgress(tr(
"Confirm"));
356 confirmDownloadAndInstall();
362 emitStatusMessage(tr(
"No new program updates detected."));
365 emitStatusMessage(tr(
"Some update packages were ignored. To install them "
366 "select \"Check for updates\" option from \"Help\" menu."));
368 finishWithError(EC_Ok);
373 finishWithError(parseResult);
379 QDomDocument xmlDocAllScripts;
381 for (
const QDomDocument &doc : d->allScripts)
383 scriptParser.merge(doc);
386 if (!f.open(QIODevice::WriteOnly))
390 f.write(xmlDocAllScripts.toByteArray());
397 d->channel = updateChannel;
402 d->ignoredPackagesRevisions = packagesRevisions;
407 d->bDownloadAndInstallRequireConfirmation = b;
410 void AutoUpdater::start()
414 qDebug() <<
"Cannot start AutoUpdater more than once.";
417 assert(
false &&
"Cannot start AutoUpdater more than once.");
421 d->bPackageDownloadStarted =
false;
422 if (d->channel.isNull())
427 QDir storageDir(updateStorageDirPath());
428 if (!storageDir.mkpath(
"."))
430 gLog << tr(
"Failed to create directory for updates storage: %1")
431 .arg(storageDir.path());
434 d->bIsRunning =
true;
435 QNetworkRequest request;
437 request.setUrl(mkVersionDataFileUrl());
438 QNetworkReply *pReply = d->pNam->get(request);
441 this->connect(pReply,
443 SLOT(onUpdaterInfoDownloadFinish()));
444 this->connect(pReply,
445 SIGNAL(downloadProgress(qint64,qint64)),
446 SIGNAL(packageDownloadProgress(qint64,qint64)));
447 d->pNetworkReply = pReply;
448 emitOverallProgress(tr(
"Update info"));
451 void AutoUpdater::startNextPackageDownload()
453 assert(!d->packagesInDownloadQueue.isEmpty() &&
"AutoUpdater::startNextPackageDownload()");
455 startPackageDownload(pkg);
458 void AutoUpdater::startPackageDownload(
const UpdatePackage &pkg)
461 if (!url.isValid() || url.isRelative())
465 gLog << tr(
"Invalid download URL for package \"%1\": %2")
470 emitOverallProgress(tr(
"Package: %1").arg(pkg.
displayName));
471 gLog << tr(
"Downloading package \"%1\" from URL: %2.").arg(pkg.
displayName,
474 QString fileNameTemplate = QString(
"%1%2-XXXXXX.zip")
475 .arg(DataPaths::UPDATE_PACKAGE_FILENAME_PREFIX).arg(pkg.
name);
477 qDebug() <<
"filePathTemplate: " << filePathTemplate;
478 if (d->pCurrentPackageFile !=
nullptr)
480 delete d->pCurrentPackageFile;
482 d->pCurrentPackageFile =
new QTemporaryFile(filePathTemplate);
483 d->pCurrentPackageFile->setAutoRemove(
false);
484 if (!d->pCurrentPackageFile->open())
486 gLog << tr(
"Couldn't save file in path: %1").arg(updateStorageDirPath());
487 delete d->pCurrentPackageFile;
488 d->pCurrentPackageFile =
nullptr;
492 QFileInfo fileInfo(d->pCurrentPackageFile->fileName());
493 d->downloadedPackagesFilenames << fileInfo.fileName();
495 QNetworkRequest request;
498 QNetworkReply *pReply = d->pNam->get(request);
499 d->currentlyDownloadedPackage = pkg;
500 d->pNetworkReply = pReply;
501 this->connect(pReply, SIGNAL(readyRead()),
502 SLOT(onPackageDownloadReadyRead()));
503 this->connect(pReply, SIGNAL(
finished()),
504 SLOT(onPackageDownloadFinish()));
505 this->connect(pReply, SIGNAL(downloadProgress(qint64,qint64)),
506 SIGNAL(packageDownloadProgress(qint64,qint64)));
509 void AutoUpdater::startPackageScriptDownload(
const UpdatePackage &pkg)
512 if (!url.isValid() || url.isRelative())
516 gLog << tr(
"Invalid download URL for package script \"%1\": %2")
521 gLog << tr(
"Downloading package script \"%1\" from URL: %2.").arg(pkg.
displayName,
524 QNetworkRequest request;
527 QNetworkReply *pReply = d->pNam->get(request);
528 d->currentlyDownloadedPackage = pkg;
529 d->pNetworkReply = pReply;
532 this->connect(pReply, SIGNAL(
finished()),
533 SLOT(onPackageScriptDownloadFinish()));
534 this->connect(pReply,
535 SIGNAL(downloadProgress(qint64,qint64)),
536 SIGNAL(packageDownloadProgress(qint64,qint64)));
541 QString dirPath = gDefaultDataPaths->localDataLocationPath(DataPaths::UPDATE_PACKAGES_DIR_NAME);
542 QString name = DataPaths::UPDATE_PACKAGE_FILENAME_PREFIX +
"-updater-script.xml";
546 QString AutoUpdater::updateStorageDirPath()
548 return gDefaultDataPaths->localDataLocationPath(DataPaths::UPDATE_PACKAGES_DIR_NAME);