23 #include "autoupdater.h"
25 #include "configuration/doomseekerconfig.h"
26 #include "updater/updatechannel.h"
27 #include "updater/updatepackagefilter.h"
28 #include "updater/updaterinfoparser.h"
29 #include "updater/updaterscriptparser.h"
30 #include "datapaths.h"
34 #include <wadseeker/protocols/fixednetworkaccessmanager.h>
37 #include <QNetworkRequest>
38 #include <QTemporaryFile>
45 QList<QDomDocument> allScripts;
46 bool bDownloadAndInstallRequireConfirmation;
49 bool bPackageDownloadStarted;
53 QStringList downloadedPackagesFilenames;
55 QMap<QString, QList<unsigned long long> > ignoredPackagesRevisions;
56 QList<UpdatePackage> newUpdatePackages;
57 QList<UpdatePackage> packagesInDownloadQueue;
58 QTemporaryFile* pCurrentPackageFile;
59 FixedNetworkAccessManager* pNam;
60 QNetworkReply* pNetworkReply;
68 const QString
AutoUpdater::MAIN_PROGRAM_PACKAGE_NAME = "doomseeker";
69 const QString
AutoUpdater::UPDATER_INFO_URL_BASE = "http:
74 d->bDownloadAndInstallRequireConfirmation =
false;
75 d->bPackageDownloadStarted =
false;
76 d->bIsRunning =
false;
79 d->pCurrentPackageFile = NULL;
80 d->pNam =
new FixedNetworkAccessManager();
81 d->pNetworkReply = NULL;
84 AutoUpdater::~AutoUpdater()
86 if (d->pCurrentPackageFile != NULL)
88 delete d->pCurrentPackageFile;
90 if (d->pNetworkReply != NULL)
92 d->pNetworkReply->disconnect();
93 d->pNetworkReply->abort();
94 d->pNetworkReply->deleteLater();
96 d->pNam->disconnect();
97 d->pNam->deleteLater();
100 void AutoUpdater::abort()
102 if (d->pNetworkReply != NULL)
104 d->pNetworkReply->disconnect();
105 d->pNetworkReply->abort();
106 d->pNetworkReply->deleteLater();
107 d->pNetworkReply = NULL;
112 QDomDocument AutoUpdater::adjustUpdaterScriptXml(
const QByteArray& xmlSource)
118 if (!xmlDoc.setContent(xmlSource, &xmlError, &xmlErrLine, &xmlErrCol))
120 gLog << tr(
"Failed to parse updater XML script: %1, l: %2, c: %3")
121 .arg(xmlError).arg(xmlErrLine).arg(xmlErrCol);
122 return QDomDocument();
124 UpdaterScriptParser scriptParser(xmlDoc);
125 QFileInfo currentPackageFileInfo(d->pCurrentPackageFile->fileName());
126 QString scriptParserErrMsg = scriptParser.setPackageName(
127 currentPackageFileInfo.completeBaseName());
128 if (!scriptParserErrMsg.isNull())
130 gLog << tr(
"Failed to modify package name in updater script: %1")
131 .arg(scriptParserErrMsg);
132 return QDomDocument();
142 void AutoUpdater::confirmDownloadAndInstall()
144 d->packagesInDownloadQueue = d->newUpdatePackages;
145 d->bPackageDownloadStarted =
true;
146 startNextPackageDownload();
151 return d->downloadedPackagesFilenames;
154 void AutoUpdater::dumpUpdatePackagesToLog(
const QList<UpdatePackage>& packages)
158 gLog << tr(
"Detected update for package \"%1\" from version \"%2\" to version \"%3\".")
163 void AutoUpdater::emitOverallProgress(
const QString& message)
167 if (!d->newUpdatePackages.isEmpty() && d->bPackageDownloadStarted)
169 total = d->newUpdatePackages.size();
172 current = total - (d->packagesInDownloadQueue.size() + 1);
173 assert(current >= 0 &&
"AutoUpdater::emitOverallProgress()");
175 emit overallProgress(current, total, message);
178 void AutoUpdater::emitStatusMessage(
const QString &message)
180 emit statusMessage(message);
188 QString AutoUpdater::errorCodeToString(ErrorCode code)
195 return tr(
"Update was aborted.");
197 return tr(
"Update channel is not configured. Please check your configuration.");
199 return tr(
"Failed to download updater info file.");
201 return tr(
"Cannot parse updater info file.");
203 return tr(
"Main program node is missing from updater info file.");
205 return tr(
"Revision info on one of the packages is missing from the "
206 "updater info file. Check the log for details.");
208 return tr(
"Download URL for one of the packages is missing from the "
209 "updater info file. Check the log for details.");
211 return tr(
"Download URL for one of the packages is invalid. "
212 "Check the log for details.");
214 return tr(
"Update package download failed. Check the log for details.");
216 return tr(
"Failed to create directory for updates packages storage.");
218 return tr(
"Failed to save update package.");
220 return tr(
"Failed to save update script.");
222 return tr(
"Unknown error.");
226 QString AutoUpdater::errorString()
const
228 return errorCodeToString(errorCode());
231 void AutoUpdater::finishWithError(ErrorCode code)
233 d->bIsRunning =
false;
238 bool AutoUpdater::isRunning()
const
240 return d->bIsRunning;
245 if (errorCode() != EC_Ok && d->pNetworkReply != NULL)
247 return d->pNetworkReply->error();
249 return QNetworkReply::NoError;
252 QUrl AutoUpdater::mkVersionDataFileUrl()
259 return d->newUpdatePackages;
262 void AutoUpdater::onPackageDownloadFinish()
264 if (d->pNetworkReply->error() == QNetworkReply::NoError)
266 emitStatusMessage(tr(
"Finished downloading package \"%1\".")
267 .arg(d->currentlyDownloadedPackage.displayName));
268 startPackageScriptDownload(d->currentlyDownloadedPackage);
272 emitStatusMessage(tr(
"Network error when downloading package \"%1\": [%2] %3")
273 .arg(d->currentlyDownloadedPackage.displayName)
274 .arg(d->pNetworkReply->error())
275 .arg(d->pNetworkReply->errorString()));
280 void AutoUpdater::onPackageDownloadReadyRead()
282 const int MAX_CHUNK_SIZE = 2 * 1024 * 1024;
283 QByteArray data = d->pNetworkReply->read(MAX_CHUNK_SIZE);
284 while (!data.isEmpty())
286 d->pCurrentPackageFile->write(data);
287 data = d->pNetworkReply->read(MAX_CHUNK_SIZE);
291 void AutoUpdater::onPackageScriptDownloadFinish()
293 if (d->pNetworkReply->error() == QNetworkReply::NoError)
295 emitStatusMessage(tr(
"Finished downloading package script \"%1\".")
296 .arg(d->currentlyDownloadedPackage.displayName));
297 QByteArray xmlData = d->pNetworkReply->readAll();
298 QDomDocument xmlDoc = adjustUpdaterScriptXml(xmlData);
304 d->allScripts.append(xmlDoc);
306 if (!d->packagesInDownloadQueue.isEmpty())
308 startNextPackageDownload();
312 emitStatusMessage(tr(
"All packages downloaded. Building updater script."));
314 finishWithError(result);
319 emitStatusMessage(tr(
"Network error when downloading package script \"%1\": [%2] %3")
320 .arg(d->currentlyDownloadedPackage.displayName)
321 .arg(d->pNetworkReply->error())
322 .arg(d->pNetworkReply->errorString()));
327 void AutoUpdater::onUpdaterInfoDownloadFinish()
329 if (d->pNetworkReply->error() != QNetworkReply::NoError)
334 QByteArray json = d->pNetworkReply->readAll();
337 if (parseResult == EC_Ok)
340 filter.setIgnoreRevisions(d->ignoredPackagesRevisions);
341 QList<UpdatePackage> packagesList = filter.filter(parser.packages());
342 if (!packagesList.isEmpty())
344 dumpUpdatePackagesToLog(packagesList);
345 d->newUpdatePackages = packagesList;
346 if (d->bDownloadAndInstallRequireConfirmation)
348 emitStatusMessage(tr(
"Requesting update confirmation."));
349 emitOverallProgress(tr(
"Confirm"));
354 confirmDownloadAndInstall();
360 emitStatusMessage(tr(
"No new program updates detected."));
363 emitStatusMessage(tr(
"Some update packages were ignored. To install them "
364 "select \"Check for updates\" option from \"Help\" menu."));
366 finishWithError(EC_Ok);
371 finishWithError(parseResult);
377 QDomDocument xmlDocAllScripts;
378 UpdaterScriptParser scriptParser(xmlDocAllScripts);
379 foreach (
const QDomDocument& doc, d->allScripts)
381 scriptParser.merge(doc);
384 if (!f.open(QIODevice::WriteOnly))
388 f.write(xmlDocAllScripts.toByteArray());
395 d->channel = updateChannel;
400 d->ignoredPackagesRevisions = packagesRevisions;
405 d->bDownloadAndInstallRequireConfirmation = b;
408 void AutoUpdater::start()
412 qDebug() <<
"Cannot start AutoUpdater more than once.";
415 assert(
false &&
"Cannot start AutoUpdater more than once.");
419 d->bPackageDownloadStarted =
false;
420 if (d->channel.isNull())
425 QDir storageDir(updateStorageDirPath());
426 if (!storageDir.mkpath(
"."))
428 gLog << tr(
"Failed to create directory for updates storage: %1")
429 .arg(storageDir.path());
432 d->bIsRunning =
true;
433 QNetworkRequest request;
435 request.setUrl(mkVersionDataFileUrl());
436 QNetworkReply* pReply = d->pNam->get(request);
439 this->connect(pReply,
441 SLOT(onUpdaterInfoDownloadFinish()));
442 this->connect(pReply,
443 SIGNAL(downloadProgress(qint64, qint64)),
444 SIGNAL(packageDownloadProgress(qint64, qint64)));
445 d->pNetworkReply = pReply;
446 emitOverallProgress(tr(
"Update info"));
449 void AutoUpdater::startNextPackageDownload()
451 assert(!d->packagesInDownloadQueue.isEmpty() &&
"AutoUpdater::startNextPackageDownload()");
453 startPackageDownload(pkg);
456 void AutoUpdater::startPackageDownload(
const UpdatePackage& pkg)
459 if (!url.isValid() || url.isRelative())
463 gLog << tr(
"Invalid download URL for package \"%1\": %2")
468 emitOverallProgress(tr(
"Package: %1").arg(pkg.
displayName));
469 gLog << tr(
"Downloading package \"%1\" from URL: %2.").arg(pkg.
displayName,
472 QString fileNameTemplate = QString(
"%1%2-XXXXXX.zip")
473 .arg(DataPaths::UPDATE_PACKAGE_FILENAME_PREFIX).arg(pkg.
name);
475 qDebug() <<
"filePathTemplate: " << filePathTemplate;
476 if (d->pCurrentPackageFile != NULL)
478 delete d->pCurrentPackageFile;
480 d->pCurrentPackageFile =
new QTemporaryFile(filePathTemplate);
481 d->pCurrentPackageFile->setAutoRemove(
false);
482 if (!d->pCurrentPackageFile->open())
484 gLog << tr(
"Couldn't save file in path: %1").arg(updateStorageDirPath());
485 delete d->pCurrentPackageFile;
486 d->pCurrentPackageFile = NULL;
490 QFileInfo fileInfo(d->pCurrentPackageFile->fileName());
491 d->downloadedPackagesFilenames << fileInfo.fileName();
493 QNetworkRequest request;
496 QNetworkReply* pReply = d->pNam->get(request);
497 d->currentlyDownloadedPackage = pkg;
498 d->pNetworkReply = pReply;
499 this->connect(pReply, SIGNAL(readyRead()),
500 SLOT(onPackageDownloadReadyRead()));
501 this->connect(pReply, SIGNAL(
finished()),
502 SLOT(onPackageDownloadFinish()));
503 this->connect(pReply, SIGNAL(downloadProgress(qint64, qint64)),
504 SIGNAL(packageDownloadProgress(qint64, qint64)));
507 void AutoUpdater::startPackageScriptDownload(
const UpdatePackage& pkg)
510 if (!url.isValid() || url.isRelative())
514 gLog << tr(
"Invalid download URL for package script \"%1\": %2")
519 gLog << tr(
"Downloading package script \"%1\" from URL: %2.").arg(pkg.
displayName,
522 QNetworkRequest request;
525 QNetworkReply* pReply = d->pNam->get(request);
526 d->currentlyDownloadedPackage = pkg;
527 d->pNetworkReply = pReply;
530 this->connect(pReply, SIGNAL(
finished()),
531 SLOT(onPackageScriptDownloadFinish()));
532 this->connect(pReply,
533 SIGNAL(downloadProgress(qint64, qint64)),
534 SIGNAL(packageDownloadProgress(qint64, qint64)));
539 QString dirPath = gDefaultDataPaths->localDataLocationPath(DataPaths::UPDATE_PACKAGES_DIR_NAME);
540 QString name = DataPaths::UPDATE_PACKAGE_FILENAME_PREFIX +
"-updater-script.xml";
544 QString AutoUpdater::updateStorageDirPath()
546 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.
void setIgnoreRevisions(const QMap< QString, QList< unsigned long long > > &packagesRevisions)
Revisions set in this map will not be treated as updates even if they differ from the currently insta...
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.
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.
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.
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.
QString displayVersion
Version displayed to the user.
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 currentlyInstalledDisplayVersion
Currently installed version, displayed to the user.
QString name
Name of the package (program name or plugin name).
void finished()
AutoUpdater has finished its job.