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);