diff --git a/News.qml b/News.qml index dff5163..1c32e81 100644 --- a/News.qml +++ b/News.qml @@ -32,6 +32,11 @@ Item { bottomMargin: 10 } + function fetchFallbackNews() { + console.log("fetching fallback posts json"); + fetchNews(downloader.newsFallbackUrl); + } + function fetchNews(jsonUrl) { var news = new XMLHttpRequest(); @@ -55,11 +60,8 @@ Item { } if (newsObj === null) { - var fallbackJsonUrl = 'qrc:/resources/disconnected_posts.json'; - - if (jsonUrl !== fallbackJsonUrl) { - console.log("fetching fallback posts json"); - fetchNews(fallbackJsonUrl); + if (jsonUrl !== downloader.newsFallbackUrl) { + fetchFallbackNews(); return; } } @@ -98,6 +100,14 @@ Item { news.send(); } + Connections { + target: splashController + + onNewsUrlFetched: { + fetchNews(newsUrl); + } + } + SwipeView { id: swipe @@ -111,7 +121,10 @@ Item { } Component.onCompleted: { - fetchNews('https://unvanquished.net/api/get_recent_posts/'); + var newsUrl = downloader.newsUrl; + if (newsUrl != "") { + fetchNews(newsUrl); + } } } diff --git a/ariadownloader.cpp b/ariadownloader.cpp index 8339be9..693ef41 100644 --- a/ariadownloader.cpp +++ b/ariadownloader.cpp @@ -65,7 +65,7 @@ AriaDownloader::AriaDownloader(const std::string& ariaLogFilename) : callback_(n AriaDownloader::~AriaDownloader() { aria2::sessionFinal(session_); - aria2::libraryDeinit(); +// aria2::libraryDeinit(); } bool AriaDownloader::addUri(const std::string& uri) diff --git a/currentversionfetcher.cpp b/currentversionfetcher.cpp index 1b21656..fae9ceb 100644 --- a/currentversionfetcher.cpp +++ b/currentversionfetcher.cpp @@ -16,6 +16,7 @@ */ #include "currentversionfetcher.h" +#include "system.h" #include #include @@ -23,47 +24,149 @@ #include #include #include +#include CurrentVersionFetcher::CurrentVersionFetcher(QObject* parent) : QObject(parent), manager_(new QNetworkAccessManager(this)) { connect(manager_.get(), SIGNAL(finished(QNetworkReply*)), this, SLOT(reply(QNetworkReply*))); } -void CurrentVersionFetcher::fetchCurrentVersion(QString url) +static const QString versionMirrors[] = { + "https://cdn.unvanquished.net/", + "https://cdn.illwieckz.net/unvanquished/", + "https://webseed.unv.kangz.net/", + nullptr, +}; + +static const QString *versionMirror = &versionMirrors[0]; + +void CurrentVersionFetcher::fetchCurrentVersion() +{ + static const QString versionFile = "current.json"; + + if (versionMirror) { + QString versionURL = versionMirror + versionFile; + qDebug() << "Fetching" << versionURL; + QNetworkRequest request = QNetworkRequest(QUrl(versionURL)); + manager_->get(request); + versionMirror++; + } +} + +void ComponentVersionFetcher(QJsonObject components, QString name, QString system, QString *version, QStringList *urls) { - QNetworkRequest request = QNetworkRequest(QUrl(url)); - manager_->get(request); + QString path; + QJsonArray mirrors; + + QJsonObject component = components[name].toObject(); + + if (component.isEmpty()) { + qDebug() << "ComponentVersionFetcher: undefined “" << name << "” key"; + } else { + QJsonValue versionValue = component.value("version"); + if (versionValue == QJsonValue::Undefined) { + qDebug() << "ComponentVersionFetcher: undefined “version” value for" << name; + } else { + *version = versionValue.toString(); + } + + mirrors = component["mirrors"].toArray(); + if (!mirrors.count()) { + qDebug() << "ComponentVersionFetcher: undefined “mirrors” key for " << name; + } + + QJsonObject parcels = component["parcels"].toObject(); + if (parcels.isEmpty()) { + qDebug() << "ComponentVersionFetcher: undefined “parcels” key for" << name; + } else { + QJsonObject systemObject = parcels[system].toObject(); + if (systemObject.isEmpty()) { + qDebug() << "ComponentVersionFetcher: undefined “" << system << "” key for " << name; + } else { + QJsonValue pathValue = systemObject.value("path"); + if (pathValue == QJsonValue::Undefined) { + qDebug() << "ComponentVersionFetcher: undefined “path” value for" << name; + } else { + path = pathValue.toString(); + } + } + } + } + + qDebug() << "ComponentVersionFetcher: fetched component =" << name; + qDebug() << "ComponentVersionFetcher: fetched system =" << system; + qDebug() << "ComponentVersionFetcher: fetched version =" << *version; + qDebug() << "ComponentVersionFetcher: fetched path =" << path; + + for (auto m : mirrors) + { + QString mirror = m.toString(); + qDebug() << "ComponentVersionFetcher: fetched mirror =" << mirror; + QString url = mirror + path; + qDebug() << "ComponentVersionFetcher: fetched url =" << url; + urls->append(url); + } } void CurrentVersionFetcher::reply(QNetworkReply* reply) { - QString game; - QString updater; + QString formatVersion; + QString updaterVersion; + QStringList updaterUrls; + QString gameVersion; + QStringList gameUrls; + QString newsVersion; + QStringList newsUrls; + if (reply->error() != QNetworkReply::NoError) { qDebug() << "CurrentVersionFetcher: network error"; - emit onCurrentVersions(updater, game); + + if (versionMirror) { + fetchCurrentVersion(); + } else { + emit onCurrentVersions(updaterVersion, updaterUrls, gameVersion, gameUrls, ""); + } return; } + QJsonParseError error; QJsonDocument json = QJsonDocument::fromJson(reply->readAll(), &error); if (error.error != QJsonParseError::NoError) { qDebug() << "CurrentVersionFetcher: JSON parsing error"; - emit onCurrentVersions(updater, game); + emit onCurrentVersions(updaterVersion, updaterUrls, gameVersion, gameUrls, ""); return; } - QJsonValue value = json.object().value("updater"); - if (value != QJsonValue::Undefined) { - updater = value.toString(); + + QJsonObject jsonObject = json.object(); + + QJsonValue formatVersionValue = jsonObject.value("format"); + + if (formatVersionValue == QJsonValue::Undefined) { + qDebug() << "ComponentVersionFetcher: missing “version” value in current.json"; + emit onCurrentVersions(updaterVersion, updaterUrls, gameVersion, gameUrls, ""); + return; } else { - qDebug() << "CurrentVersionFetcher: undefined “updater” value"; + formatVersion = formatVersionValue.toString(); } - value = json.object().value("unvanquished"); - if (value != QJsonValue::Undefined) { - game = value.toString(); + + QJsonValue componentsValue = jsonObject.value("components"); + + if (componentsValue == QJsonValue::Undefined) { + qDebug() << "ComponentVersionFetcher: missing “components” array in current.json"; + emit onCurrentVersions(updaterVersion, updaterUrls, gameVersion, gameUrls, ""); + return; } else { - qDebug() << "CurrentVersionFetcher: undefined “unvanquished” value"; + qDebug() << "ComponentVersionFetcher: fetched format = " << formatVersion; + + QJsonObject components = componentsValue.toObject(); + + ComponentVersionFetcher(components, "updater", Sys::updaterSystem(), &updaterVersion, &updaterUrls); + + ComponentVersionFetcher(components, "game", "all-all", &gameVersion, &gameUrls); + + ComponentVersionFetcher(components, "news", "all-all", &newsVersion, &newsUrls); } - qDebug() << "CurrentVersionFetcher: fetched versions: updater =" << updater << "game =" << game; - emit onCurrentVersions(updater, game); + + emit onCurrentVersions(updaterVersion, updaterUrls, gameVersion, gameUrls, newsUrls.at(0)); } diff --git a/currentversionfetcher.h b/currentversionfetcher.h index df8b2f3..5660d61 100644 --- a/currentversionfetcher.h +++ b/currentversionfetcher.h @@ -28,10 +28,10 @@ class CurrentVersionFetcher : public QObject Q_OBJECT public: explicit CurrentVersionFetcher(QObject *parent = nullptr); - void fetchCurrentVersion(QString url); + void fetchCurrentVersion(); signals: - void onCurrentVersions(QString updater, QString game); + void onCurrentVersions(QString updaterVersion, QStringList updaterUrls, QString gameVersion, QStringList gameUrls, QString newsUrl); private slots: void reply(QNetworkReply* reply); diff --git a/deployment.md b/deployment.md index 1ad3193..43281df 100644 --- a/deployment.md +++ b/deployment.md @@ -1,6 +1,6 @@ ## Network resources used by the updater - News REST endpoint which returns links to Wordpress articles on unvanquished.net. The featured image in each news article must be a type which is supported by the updater (see issue #51). Currently PNG and JPEG are known to work. -- versions.json file on unvanquished.net, used to determine whether update is needed +- current.json file on unvanquished.net, used to determine whether update is needed - Github releases. These are targeted by download links on unvanquished.net and by the updater's self-update process. - Torrent URL used to download the latest game version @@ -36,4 +36,4 @@ ``` 2. Upload `UnvUpdaterWin.zip` and `UnvanquishedUpdater.exe` from `build-docker/release-win/`. -4. Bump the updater version on unvanquished.net to the new tag, so that it is reflected in versions.json and the download links. +4. Bump the updater version on unvanquished.net to the new tag, so that it is reflected in current.json and the download links. diff --git a/downloadworker.cpp b/downloadworker.cpp index 9330385..bb20c15 100644 --- a/downloadworker.cpp +++ b/downloadworker.cpp @@ -143,8 +143,6 @@ std::string DownloadWorker::getAriaIndexOut(size_t index, std::string path) return std::to_string(index) + "=" + oldPath.toStdString(); } - - void DownloadWorker::download() { auto start = std::chrono::steady_clock::now(); diff --git a/gamelauncher.cpp b/gamelauncher.cpp index 7c1f14d..1362433 100644 --- a/gamelauncher.cpp +++ b/gamelauncher.cpp @@ -31,7 +31,7 @@ GameLauncher::GameLauncher(const QString& connectUrl, const Settings& settings) // Does our version of daemon have -connect-trusted? static bool haveConnectTrusted(const QString& gameVersion) { - // Updater version up to v0.2.0 may set "unknown" as game version if versions.json request fails + // Updater version up to v0.2.0 may set "unknown" as game version if current.json request fails if (gameVersion == "unknown") return false; // Hacky string comparison, assume we won't go down to 0.9 or up to 0.100 :) diff --git a/linux.cpp b/linux.cpp index 488e617..752e064 100644 --- a/linux.cpp +++ b/linux.cpp @@ -82,6 +82,11 @@ QString archiveName() return "linux-amd64.zip"; } +QString updaterSystem() +{ + return "linux-amd64"; +} + void migrateHomePath() { QString legacyHomePath = QDir::homePath() + "/.unvanquished"; @@ -265,11 +270,6 @@ bool updateUpdater(const QString& updaterArchive, const QString& connectUrl) return false; } -QString updaterArchiveName() -{ - return "UnvUpdaterLinux.zip"; -} - std::string getCertStore() { // From Go: https://golang.org/src/crypto/x509/root_linux.go diff --git a/main.cpp b/main.cpp index 9ae9a89..8aeebd5 100644 --- a/main.cpp +++ b/main.cpp @@ -63,7 +63,7 @@ struct CommandLineOptions { QString ariaLogFilename; int splashMilliseconds = 3000; RelaunchCommand relaunchCommand = RelaunchCommand::NONE; - QString updateUpdaterVersion; + QStringList updateUpdaterUrls; QString connectUrl; }; @@ -122,6 +122,8 @@ CommandLineOptions getCommandLineOptions(const QApplication& app) { splashMsOption.setValueName("duration in milliseconds"); QCommandLineOption internalCommandOption("internalcommand"); internalCommandOption.setValueName("command"); + QCommandLineOption updaterUrl("updaterurl"); + updaterUrl.setValueName("url"); QCommandLineParser optionParser; optionParser.addHelpOption(); optionParser.addVersionOption(); @@ -129,6 +131,7 @@ CommandLineOptions getCommandLineOptions(const QApplication& app) { optionParser.addOption(ariaLogFilenameOption); optionParser.addOption(splashMsOption); optionParser.addOption(internalCommandOption); + optionParser.addOption(updaterUrl); optionParser.addPositionalArgument("URL", "address of Unvanquished server to connect to", "[URL]"); optionParser.process(app); CommandLineOptions options; @@ -145,9 +148,13 @@ CommandLineOptions getCommandLineOptions(const QApplication& app) { options.relaunchCommand = RelaunchCommand::PLAY_NOW; } else if (command == "updategame") { options.relaunchCommand = RelaunchCommand::UPDATE_GAME; - } else if (command.startsWith("updateupdater:")) { + } else if (command.startsWith("updateupdater")) { options.relaunchCommand = RelaunchCommand::UPDATE_UPDATER; - options.updateUpdaterVersion = command.section(':', 1); + if (optionParser.isSet(updaterUrl)) { + options.updateUpdaterUrls = optionParser.values(updaterUrl); + } else { + options.updateUpdaterUrls = QStringList(); + } } else { argParseError("Invalid --internalcommand option: " + command); } @@ -209,9 +216,9 @@ int main(int argc, char *argv[]) } SplashController splashController( - options.relaunchCommand, options.updateUpdaterVersion, options.connectUrl, settings); + options.relaunchCommand, options.updateUpdaterUrls, options.connectUrl, settings); splashController.checkForUpdate(); - QmlDownloader downloader(options.ariaLogFilename, options.connectUrl, settings); + QmlDownloader downloader(options.ariaLogFilename, options.connectUrl, splashController, settings); QQmlApplicationEngine engine; engine.addImportPath(QLatin1String("qrc:/")); engine.addImageProvider(QLatin1String("fluidicons"), new IconsImageProvider()); diff --git a/osx.cpp b/osx.cpp index a532444..51214b1 100644 --- a/osx.cpp +++ b/osx.cpp @@ -53,6 +53,11 @@ QString archiveName() return "macos-amd64.zip"; } +QString updaterSystem() +{ + return "macos-amd64"; +} + QString defaultInstallPath() { return QDir::homePath() + "/Games/Unvanquished"; @@ -163,11 +168,6 @@ bool updateUpdater(const QString& updaterArchive, const QString&) return true; } -QString updaterArchiveName() -{ - return "UnvUpdaterOSX.zip"; -} - std::string getCertStore() { return ""; // Not used on OSX. diff --git a/qmldownloader.cpp b/qmldownloader.cpp index a362f7c..da3decb 100644 --- a/qmldownloader.cpp +++ b/qmldownloader.cpp @@ -23,11 +23,11 @@ #include "qmldownloader.h" #include "system.h" -static const QString UPDATER_BASE_URL("https://github.com/Unvanquished/updater/releases/download"); -QmlDownloader::QmlDownloader(QString ariaLogFilename, QString connectUrl, Settings& settings) : +QmlDownloader::QmlDownloader(QString ariaLogFilename, QString connectUrl, SplashController& splashController, Settings& settings) : ariaLogFilename_(ariaLogFilename), connectUrl_(connectUrl), + splashController_(splashController), settings_(settings), downloadSpeed_(0), uploadSpeed_(0), @@ -42,6 +42,14 @@ QmlDownloader::~QmlDownloader() stopAria(); } +QString QmlDownloader::newsFallbackUrl() const { + return "qrc:/resources/disconnected_posts.json"; +} + +QString QmlDownloader::newsUrl() const { + return splashController_.newsUrl(); +} + int QmlDownloader::downloadSpeed() const { return downloadSpeed_; } @@ -114,7 +122,17 @@ void QmlDownloader::onDownloadEvent(int event) break; case aria2::EVENT_ON_DOWNLOAD_ERROR: - emit fatalMessage("Error received while downloading"); + if (!downloadRestarter_) { + emit fatalMessage("Error: downloadRestarter unset"); + } + setState(COMPLETED); + setDownloadSpeed(0); + setUploadSpeed(0); + setCompletedSize(0); + stopAria(); + if (!(this->*downloadRestarter_)()) { + emit fatalMessage("Error received while downloading"); + } break; case aria2::EVENT_ON_DOWNLOAD_PAUSE: @@ -135,7 +153,7 @@ void QmlDownloader::onDownloadEvent(int event) } } -void QmlDownloader::startUpdate(const QString& selectedInstallPath) +void QmlDownloader::setInstallPath(const QString& selectedInstallPath) { qDebug() << "Selected install path:" << selectedInstallPath; if (!Sys::validateInstallPath(selectedInstallPath)) { @@ -151,10 +169,12 @@ void QmlDownloader::startUpdate(const QString& selectedInstallPath) return; } } + if (!QFileInfo(selectedInstallPath).isWritable()) { emit fatalMessage("Install dir not writable. Please select another"); return; } + // Persist the install path only now that download has been initiated and we know the path is good emit statusMessage("Installing to " + dir.canonicalPath()); if (settings_.installPath() != selectedInstallPath) { @@ -162,12 +182,25 @@ void QmlDownloader::startUpdate(const QString& selectedInstallPath) settings_.setInstalledVersion(""); } settings_.setInstallPath(selectedInstallPath); +} + +bool QmlDownloader::startUpdate() +{ + QString gameUrl = splashController_.gameUrl(); + if (gameUrl.isEmpty()) { + return false; + } + + qDebug() << "Using torrent URL:" << gameUrl; + + QDir dir(settings_.installPath()); setState(DOWNLOADING); worker_ = new DownloadWorker(ariaLogFilename_); worker_->setDownloadDirectory(dir.canonicalPath().toStdString()); - worker_->addTorrent("https://cdn.unvanquished.net/current.torrent"); + worker_->addTorrent(gameUrl.toStdString()); worker_->moveToThread(&thread_); + setDownloadRestarter(&QmlDownloader::startUpdate); connect(&thread_, SIGNAL(finished()), worker_, SLOT(deleteLater())); connect(worker_, SIGNAL(onDownloadEvent(int)), this, SLOT(onDownloadEvent(int))); connect(worker_, SIGNAL(downloadSpeedChanged(int)), this, SLOT(setDownloadSpeed(int))); @@ -176,6 +209,8 @@ void QmlDownloader::startUpdate(const QString& selectedInstallPath) connect(worker_, SIGNAL(completedSizeChanged(int)), this, SLOT(setCompletedSize(int))); connect(&thread_, SIGNAL(started()), worker_, SLOT(download())); thread_.start(); + + return true; } void QmlDownloader::toggleDownload(QString installPath) @@ -183,7 +218,8 @@ void QmlDownloader::toggleDownload(QString installPath) qDebug() << "QmlDownloader::toggleDownload called"; if (state() == COMPLETED) return; if (!worker_) { - startUpdate(installPath); + setInstallPath(installPath); + startUpdate(); return; } QMetaObject::invokeMethod(worker_, "toggle"); @@ -201,15 +237,22 @@ void QmlDownloader::stopAria() } } -void QmlDownloader::startUpdaterUpdate(QString version) +bool QmlDownloader::startUpdaterUpdate() { - QString url = UPDATER_BASE_URL + "/" + version + "/" + Sys::updaterArchiveName(); + QString updaterUrl = splashController_.updaterUrl(); + if (updaterUrl.isEmpty()) { + return false; + } + + qDebug() << "Using updater URL:" << updaterUrl; + temp_dir_.reset(new QTemporaryDir()); worker_ = new DownloadWorker(ariaLogFilename_); worker_->setDownloadDirectory(QDir(temp_dir_->path()).canonicalPath().toStdString()); worker_->setConnectUrl(connectUrl_); - worker_->addUpdaterUri(url.toStdString()); + worker_->addUpdaterUri(updaterUrl.toStdString()); worker_->moveToThread(&thread_); + setDownloadRestarter(&QmlDownloader::startUpdaterUpdate); connect(&thread_, SIGNAL(finished()), worker_, SLOT(deleteLater())); connect(worker_, SIGNAL(onDownloadEvent(int)), this, SLOT(onDownloadEvent(int))); connect(worker_, SIGNAL(downloadSpeedChanged(int)), this, SLOT(setDownloadSpeed(int))); @@ -218,6 +261,8 @@ void QmlDownloader::startUpdaterUpdate(QString version) connect(worker_, SIGNAL(completedSizeChanged(int)), this, SLOT(setCompletedSize(int))); connect(&thread_, SIGNAL(started()), worker_, SLOT(download())); thread_.start(); + + return true; } QmlDownloader::DownloadState QmlDownloader::state() const @@ -230,3 +275,8 @@ void QmlDownloader::setState(DownloadState state) state_ = state; emit stateChanged(state); } + +void QmlDownloader::setDownloadRestarter(DownloadRestarter downloadRestarter) +{ + downloadRestarter_ = downloadRestarter; +} diff --git a/qmldownloader.h b/qmldownloader.h index 4827bb1..a740df1 100644 --- a/qmldownloader.h +++ b/qmldownloader.h @@ -29,11 +29,15 @@ #include "downloadworker.h" #include "downloadtimecalculator.h" +#include "splashcontroller.h" #include "settings.h" + class QmlDownloader : public QObject { Q_OBJECT + Q_PROPERTY(QString newsFallbackUrl READ newsFallbackUrl) + Q_PROPERTY(QString newsUrl READ newsUrl) Q_PROPERTY(int downloadSpeed READ downloadSpeed NOTIFY downloadSpeedChanged) Q_PROPERTY(int uploadSpeed READ uploadSpeed NOTIFY uploadSpeedChanged) Q_PROPERTY(int eta READ eta NOTIFY etaChanged) @@ -50,7 +54,7 @@ class QmlDownloader : public QObject }; Q_ENUM(DownloadState) - QmlDownloader(QString ariaLogFilename, QString connectUrl, Settings& settings); + QmlDownloader(QString ariaLogFilename, QString connectUrl, SplashController& splashController, Settings& settings); ~QmlDownloader(); int downloadSpeed() const; int uploadSpeed() const; @@ -70,6 +74,8 @@ class QmlDownloader : public QObject void stateChanged(DownloadState state); public slots: + QString newsFallbackUrl() const; + QString newsUrl() const; void setDownloadSpeed(int speed); void setUploadSpeed(int speed); void setTotalSize(int size); @@ -77,17 +83,22 @@ public slots: void onDownloadEvent(int event); Q_INVOKABLE void toggleDownload(QString installPath); - Q_INVOKABLE void startUpdaterUpdate(QString version); + Q_INVOKABLE bool startUpdaterUpdate(); private: + using DownloadRestarter = bool (QmlDownloader::*)(); + void stopAria(); void setState(DownloadState state); + void setInstallPath(const QString& selectedInstallPath); void startDownload(const QUrl& url, const QDir& destination); - void startUpdate(const QString& selectedInstallPath); + void setDownloadRestarter(DownloadRestarter downloadRestarter); + bool startUpdate(); void launchGameIfInstalled(); QString ariaLogFilename_; QString connectUrl_; // used for updater update + SplashController& splashController_; Settings& settings_; QThread thread_; @@ -96,6 +107,7 @@ public slots: std::chrono::seconds eta_; int totalSize_; int completedSize_; + DownloadRestarter downloadRestarter_ = nullptr; DownloadWorker* worker_; DownloadTimeCalculator downloadTime_; diff --git a/splash.qml b/splash.qml index d944ac8..4af5320 100644 --- a/splash.qml +++ b/splash.qml @@ -70,8 +70,12 @@ ApplicationWindow { } } - function onUpdaterUpdate(version) { - downloader.startUpdaterUpdate(version); + function onUpdaterUpdateNeeded() { + splashController.autoLaunchOrUpdate(); + } + + function onUpdaterUpdate() { + downloader.startUpdaterUpdate(); updaterUpdateLabel.visible = true; // Now allow the window to go behind other windows in the z-order. diff --git a/splashcontroller.cpp b/splashcontroller.cpp index 2d35545..dfa0114 100644 --- a/splashcontroller.cpp +++ b/splashcontroller.cpp @@ -23,29 +23,69 @@ #include "system.h" SplashController::SplashController( - RelaunchCommand command, const QString& updateUpdaterVersion, + RelaunchCommand command, const QStringList& updateUpdaterUrls, const QString& connectUrl, const Settings& settings) : - relaunchCommand_(command), updateUpdaterVersion_(updateUpdaterVersion), + relaunchCommand_(command), updateUpdaterUrls_(updateUpdaterUrls), connectUrl_(connectUrl), settings_(settings) {} // Possibly initiates an asynchronous request for the latest available versions. void SplashController::checkForUpdate() { - // Don't need versions.json if we already know what to do next - if (relaunchCommand_ != RelaunchCommand::NONE) { - return; + // Don't need current.json if we already know what to do next + switch(relaunchCommand_) { + case RelaunchCommand::NONE: + case RelaunchCommand::UPDATE_GAME: + break; + case RelaunchCommand::UPDATE_UPDATER: + if (!updateUpdaterUrls_.isEmpty()) { + return; + } + break; + default: + return; } - connect(&fetcher_, SIGNAL(onCurrentVersions(QString, QString)), - this, SLOT(onCurrentVersions(QString, QString))); - fetcher_.fetchCurrentVersion("https://dl.unvanquished.net/versions.json"); + connect(&fetcher_, SIGNAL(onCurrentVersions(QString, QStringList, QString, QStringList, QString)), + this, SLOT(onCurrentVersions(QString, QStringList, QString, QStringList, QString))); + + fetcher_.fetchCurrentVersion(); +} + +QString SplashController::updaterUrl() { + if (latestUpdaterUrls_.isEmpty()) { + return ""; + } + + return latestUpdaterUrls_.takeFirst(); +} + +QString SplashController::gameUrl() { + if (latestGameUrls_.isEmpty()) { + return ""; + } + + return latestGameUrls_.takeFirst(); +} + +QString SplashController::newsUrl() const { + return latestNewsUrl_; } // Receives the results of the checkForUpdate request. -void SplashController::onCurrentVersions(QString updater, QString game) +void SplashController::onCurrentVersions(QString updaterVersion, QStringList updaterUrls, QString gameVersion, QStringList gameUrls, QString newsUrl) { - latestUpdaterVersion_ = updater; - latestGameVersion_ = game; + latestUpdaterVersion_ = updaterVersion; + latestUpdaterUrls_ = updaterUrls; + latestGameVersion_ = gameVersion; + latestGameUrls_ = gameUrls; + latestNewsUrl_ = newsUrl; + + emit newsUrlFetched(newsUrl); + + if (relaunchCommand_ == RelaunchCommand::UPDATE_UPDATER && updateUpdaterUrls_.isEmpty()) { + updateUpdaterUrls_ = latestUpdaterUrls_; + emit updaterUpdateNeeded(); + } } // Return value is whether the program should exit @@ -69,7 +109,7 @@ void SplashController::launchGameIfInstalled() // This runs after the splash screen has been displayed for the programmed amount of time (and the // user did not click the settings button). If the CurrentVersionFetcher didn't emit anything yet, -// proceed as if the request for versions.json failed. +// proceed as if the request for current.json failed. void SplashController::autoLaunchOrUpdate() { qDebug() << "Previously-installed game version:" << settings_.installedVersion(); @@ -82,9 +122,18 @@ void SplashController::autoLaunchOrUpdate() return; case RelaunchCommand::UPDATE_UPDATER: - qDebug() << "Updater update to" << updateUpdaterVersion_ << "requested as relaunch action"; - // It is assumed the process is already elevated - emit updaterUpdate(updateUpdaterVersion_); + // When the splash screen is displayed, this function is called, but the current.json + // fetch has not succeeded yet. Only do something at that time when the updater url is + // passed by command line. When the current.json fetch has succeeded, this function is + // called again if there was no url passed by command line. + if (!updateUpdaterUrls_.isEmpty()) { + qDebug() << "Updater update requested as relaunch action"; + for (auto& url : updateUpdaterUrls_) { + qDebug() << "Updater update available: " << url; + } + // It is assumed the process is already elevated + emit updaterUpdate(); + } return; case RelaunchCommand::PLAY_NOW: @@ -97,14 +146,19 @@ void SplashController::autoLaunchOrUpdate() // If no relaunch action, detect update needed based on versions.json if (!latestUpdaterVersion_.isEmpty() && latestUpdaterVersion_ != updaterAppVersion()) { qDebug() << "Updater update to version" << latestUpdaterVersion_ << "required"; + QString updaterArgs = "--splashms 1 --internalcommand updateupdater"; + + for (auto& url : latestUpdaterUrls_) { + updaterArgs += " --updaterurl " + url; + } + // Remember the URL if we are doing updater update - QString updaterArgs = "--splashms 1 --internalcommand updateupdater:" + latestUpdaterVersion_; if (!connectUrl_.isEmpty()) { updaterArgs += " -- " + connectUrl_; } switch (Sys::RelaunchElevated(updaterArgs)) { case Sys::ElevationResult::UNNEEDED: - emit updaterUpdate(latestUpdaterVersion_); + emit updaterUpdate(); return; case Sys::ElevationResult::RELAUNCHED: QCoreApplication::quit(); diff --git a/splashcontroller.h b/splashcontroller.h index 50e913b..beb459d 100644 --- a/splashcontroller.h +++ b/splashcontroller.h @@ -26,7 +26,7 @@ inline QString updaterAppVersion() { - return "v0.2.1"; + return "0.2.1"; } // These are used only on Windows where relaunching is needed for admin (de)elevation. @@ -48,7 +48,7 @@ class SplashController : public QObject private: // Actions from command line args RelaunchCommand relaunchCommand_; - QString updateUpdaterVersion_; // If command is UPDATE_UPDATER + QStringList updateUpdaterUrls_; // If command is UPDATE_UPDATER QString connectUrl_; // for pre-updater-update elevation const Settings& settings_; @@ -56,22 +56,30 @@ class SplashController : public QObject // Latest versions fetching CurrentVersionFetcher fetcher_; QString latestUpdaterVersion_; + QStringList latestUpdaterUrls_; QString latestGameVersion_; + QStringList latestGameUrls_; + QString latestNewsUrl_; public: SplashController( - RelaunchCommand command, const QString& updateUpdaterVersion, + RelaunchCommand command, const QStringList& updateUpdaterUrls, const QString& connectUrl, const Settings& settings); void checkForUpdate(); + QString gameUrl(); + QString updaterUrl(); + QString newsUrl() const; Q_INVOKABLE bool relaunchForSettings(); Q_INVOKABLE void autoLaunchOrUpdate(); signals: void updateNeeded(bool updateNeeded); - void updaterUpdate(QString version); + void updaterUpdateNeeded(); + void updaterUpdate(); + void newsUrlFetched(QString newsUrl); private slots: - void onCurrentVersions(QString updater, QString game); + void onCurrentVersions(QString updaterVersion, QStringList updaterUrls, QString gameVersion, QStringList gameUrls, QString newsUrl); private: void launchGameIfInstalled(); diff --git a/system.h b/system.h index 195c654..673e3b1 100644 --- a/system.h +++ b/system.h @@ -25,13 +25,13 @@ namespace Sys { QString archiveName(); +QString updaterSystem(); QString defaultInstallPath(); void initApplicationName(); // influences default storage location for QSettings bool validateInstallPath(const QString& installPath); // Checks installing as root in homepath on Linux bool installShortcuts(); // Install launch menu entries and protocol handlers bool installUpdater(const QString& installPath); // Copies current application to /updater[.exe|.app] bool updateUpdater(const QString& updaterArchive, const QString& connectUrl); -QString updaterArchiveName(); std::string getCertStore(); QSettings* makePersistentSettings(QObject* parent); QString getGameCommand(const QString& installPath); // Substitution for %command% diff --git a/updater.pro b/updater.pro index 6ea764a..9064ce2 100644 --- a/updater.pro +++ b/updater.pro @@ -3,6 +3,8 @@ QT += qml quick network widgets include(fluid/fluid.pri) CONFIG += c++11 +CONFIG += debug +CONFIG -= release HEADERS += ariadownloader.h \ downloadtimecalculator.h \ diff --git a/win.cpp b/win.cpp index 6ead20b..f4a0c19 100644 --- a/win.cpp +++ b/win.cpp @@ -171,6 +171,11 @@ QString archiveName() } } +QString updaterSystem() +{ + return "windows-i686"; +} + QString defaultInstallPath() { static const char* PROGRAM_FILES_VAR = "programfiles"; @@ -284,11 +289,6 @@ bool updateUpdater(const QString& updaterArchive, const QString& connectUrl) return true; } -QString updaterArchiveName() -{ - return "UnvUpdaterWin.zip"; -} - std::string getCertStore() { return ""; // Not used on Windows.