diff --git a/src/manager.cpp b/src/manager.cpp index 6c54a0a..523344d 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -28,10 +28,11 @@ const auto updatesUrl = #endif const auto resultHideWaitUs = 300'000; } // namespace +using Loader = update::Loader; Manager::Manager() : settings_(std::make_unique()) - , updater_(std::make_unique(QUrl(updatesUrl))) + , updater_(std::make_unique(Loader::Urls{{updatesUrl}})) , updateAutoChecker_(std::make_unique(*updater_)) , models_(std::make_unique()) { diff --git a/src/service/updates.cpp b/src/service/updates.cpp index 00c0089..5beb1f3 100644 --- a/src/service/updates.cpp +++ b/src/service/updates.cpp @@ -92,22 +92,26 @@ QStringList toList(const QJsonValue &value) } // namespace -Loader::Loader(const QUrl &updateUrl, QObject *parent) +Loader::Loader(const update::Loader::Urls &updateUrls, QObject *parent) : QObject(parent) , network_(new QNetworkAccessManager(this)) , model_(new Model(this)) - , updateUrl_(updateUrl) + , updateUrls_(updateUrls) , downloadPath_( QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/updates") { + std::random_device device; + std::mt19937 generator(device()); + std::shuffle(updateUrls_.begin(), updateUrls_.end(), generator); + connect(network_, &QNetworkAccessManager::finished, // this, &Loader::handleReply); } void Loader::handleReply(QNetworkReply *reply) { - if (reply->url() == updateUrl_) { + if (updateUrls_.contains(reply->url())) { handleUpdateReply(reply); } else { handleComponentReply(reply); @@ -116,7 +120,28 @@ void Loader::handleReply(QNetworkReply *reply) void Loader::checkForUpdates() { - auto reply = network_->get(QNetworkRequest(updateUrl_)); + startDownloadUpdates({}); +} + +void Loader::startDownloadUpdates(const QUrl &previous) +{ + SOFT_ASSERT(!updateUrls_.isEmpty(), return ); + + QUrl url; + if (previous.isEmpty()) + url = updateUrls_.first(); + else { + const auto index = updateUrls_.indexOf(previous); + SOFT_ASSERT(index != -1, return ); + if (index == updateUrls_.size() - 1) + return; + url = updateUrls_[index + 1]; + } + + if (url.isEmpty()) + return; + + auto reply = network_->get(QNetworkRequest(url)); if (reply->error() != QNetworkReply::NoError) handleUpdateReply(reply); } @@ -127,13 +152,28 @@ void Loader::handleUpdateReply(QNetworkReply *reply) if (reply->error() != QNetworkReply::NoError) { emit error(toError(*reply)); + startDownloadUpdates(reply->url()); return; } const auto replyData = reply->readAll(); + if (replyData.isEmpty()) { + emit error( + tr("Received empty updates info from %1").arg(reply->url().toString())); + startDownloadUpdates(reply->url()); + return; + } + SOFT_ASSERT(model_, return ); - model_->parse(replyData); + const auto parseError = model_->parse(replyData); + if (!parseError.isEmpty()) { + emit error(tr("Failed to parse updates from %1 (%2)") + .arg(reply->url().toString(), parseError)); + startDownloadUpdates(reply->url()); + return; + } + if (model_->hasUpdates()) emit updatesAvailable(); } @@ -324,20 +364,20 @@ void Model::initView(QTreeView *view) }); } -void Model::parse(const QByteArray &data) +QString Model::parse(const QByteArray &data) { QJsonParseError error; const auto doc = QJsonDocument::fromJson(data, &error); if (doc.isNull()) { - LERROR() << error.errorString(); - return; + return tr("Failed to parse: %1 at %2") + .arg(error.errorString()) + .arg(error.offset); } const auto json = doc.object(); const auto version = json[versionKey].toInt(); if (version != 1) { - LERROR() << "Wrong updates.json version" << version; - return; + return tr("Wrong updates version %1").arg(version); } beginResetModel(); @@ -347,6 +387,11 @@ void Model::parse(const QByteArray &data) updateState(*root_); endResetModel(); + + if (!root_) { + return tr("No data parsed"); + } + return {}; } std::unique_ptr Model::parse(const QJsonObject &json) const diff --git a/src/service/updates.h b/src/service/updates.h index 781c276..3f79872 100644 --- a/src/service/updates.h +++ b/src/service/updates.h @@ -58,7 +58,7 @@ public: void initView(QTreeView* view); - void parse(const QByteArray& data); + QString parse(const QByteArray& data); void setExpansions(const std::map& expansions); UserActions userActions() const; void updateStates(); @@ -130,7 +130,8 @@ class Loader : public QObject { Q_OBJECT public: - explicit Loader(const QUrl& updateUrl, QObject* parent = nullptr); + using Urls = QVector; + explicit Loader(const Urls& updateUrls, QObject* parent = nullptr); void checkForUpdates(); void applyUserActions(); @@ -150,10 +151,11 @@ private: void commitUpdate(); void updateProgress(qint64 bytesSent, qint64 bytesTotal); bool startDownload(File& file); + void startDownloadUpdates(const QUrl& previous); QNetworkAccessManager* network_; Model* model_; - QUrl updateUrl_; + Urls updateUrls_; QString downloadPath_; std::map downloads_; UserActions currentActions_;