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);
QUrl downloadScriptUrl
Updater script download URL.
static const QString UPDATER_INFO_URL_BASE
Base URL to the directory where "update-info*" JSON files are contained.
static QString combinePaths(QString pathFront, QString pathEnd)
One of packages has no revision info.
int parse(const QByteArray &json)
Parses updater info JSON and sets certain internal properties which can then be accessed through gett...
QNetworkReply::NetworkError lastNetworkError() const
The network error that caused the updater to fail.
QUrl downloadUrl
Package download URL.
Failed to create directory for updates storage.
static QString updaterScriptPath()
Path to updater script XML file.
No valid UpdateChannel was specified.
const UpdateChannel & channel() const
setChannel() .
Failed to download update package.
Interface to Mendeley updater .xml script files.
QUrl.isValid() for package download URL returned false or QUrl.isRelative() returned true...
QString displayName
Name displayed to the user.
Update script can't be merged and stored on the local filesystem.
const QList< UpdatePackage > & newUpdatePackages() const
List of new update packages to install.
static QString userAgent()
WWW User Agent used for HTTP communications.
One of packages has no download URL.
void setChannel(const UpdateChannel &updateChannel)
Update channel name.
void setIgnoreRevisions(const QMap< QString, QList< QString > > &packagesRevisions)
Revisions set in this map will not be treated as updates even if they differ from the currently insta...
File was parseable but there was no main program information inside.
Deals with program updates/upgrades.
const QStringList & downloadedPackagesFilenames() const
Filenames for packages which are ready to install.
void setRequireDownloadAndInstallConfirmation(bool b)
Controls if the download&installation process is automated.
Network error when downloading updater info file.
Filters UpdatePackage information basing on what is requested by the program.
void merge(const QDomDocument &otherDoc)
Merges other script with current script.
Updater info file can't be parsed.
Update was aborted by the user or by the program.
bool wasAnyUpdatePackageIgnored() const
After filter() flag which says if any package was ignored.
Package file can't be stored on the local filesystem.
void downloadAndInstallConfirmationRequested()
Information on update packages has been received and install confirmation is requested.
QString name
Name of the package (program name or plugin name).
void finished()
AutoUpdater has finished its job.