Add support of multiple component urls

This commit is contained in:
Gres 2020-04-04 17:34:55 +03:00
parent 35cbdb17e8
commit 5f4ef955e1
2 changed files with 86 additions and 42 deletions

View File

@ -1,4 +1,4 @@
#include "updates.h" #include "updates.h"
#include "debug.h" #include "debug.h"
#include <QApplication> #include <QApplication>
@ -39,6 +39,22 @@ QString toString(Action action)
return names.value(action); return names.value(action);
} }
QStringList toList(const QJsonValue &value)
{
if (value.isString())
return {value.toString()};
if (!value.isArray())
return {};
const auto array = value.toArray();
QStringList result;
for (const auto &i : array) {
if (i.isString())
result.append(i.toString());
}
return result;
}
} // namespace } // namespace
Loader::Loader(const QUrl &updateUrl, QObject *parent) Loader::Loader(const QUrl &updateUrl, QObject *parent)
@ -96,55 +112,76 @@ QString Loader::toError(QNetworkReply &reply) const
void Loader::applyUserActions() void Loader::applyUserActions()
{ {
SOFT_ASSERT(model_, return ); SOFT_ASSERT(model_, return );
if (installer_ || !componentReplyToPath_.empty()) { if (!currentActions_.empty() || !downloads_.empty()) {
emit error(tr("Update already in process")); emit error(tr("Update already in process"));
return; return;
} }
auto actions = model_->userActions(); currentActions_ = model_->userActions();
if (actions.empty()) { if (currentActions_.empty()) {
emit error(tr("No actions to apply")); emit error(tr("No actions to apply"));
return; return;
} }
for (auto &action : actions) { for (auto &action : currentActions_) {
if (action.first != Action::Install) if (action.first != Action::Install)
continue; continue;
auto &file = action.second; auto &file = action.second;
auto reply = network_->get(QNetworkRequest(file.url));
if (reply->error() != QNetworkReply::NoError) {
finishUpdate(toError(*reply));
break;
}
connect(reply, &QNetworkReply::downloadProgress, //
this, &Loader::updateProgress);
file.downloadPath = downloadPath_ + '/' + file.rawPath; file.downloadPath = downloadPath_ + '/' + file.rawPath;
componentReplyToPath_.emplace(reply, file.downloadPath);
if (!startDownload(file)) {
finishUpdate();
return;
}
} }
installer_ = std::make_unique<Installer>(actions); if (downloads_.empty()) // no downloads
if (componentReplyToPath_.empty()) // no downloads
commitUpdate(); commitUpdate();
} }
void Loader::handleComponentReply(QNetworkReply *reply) bool Loader::startDownload(File &file)
{
if (file.urls.empty())
return false;
auto reply = network_->get(QNetworkRequest(file.urls.takeFirst()));
downloads_.emplace(reply, &file);
connect(reply, &QNetworkReply::downloadProgress, //
this, &Loader::updateProgress);
if (reply->error() == QNetworkReply::NoError)
return true;
return handleComponentReply(reply);
}
bool Loader::handleComponentReply(QNetworkReply *reply)
{ {
reply->deleteLater(); reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) { if (currentActions_.empty()) { // aborted?
finishUpdate(toError(*reply)); finishUpdate();
return; return false;
} }
SOFT_ASSERT(componentReplyToPath_.count(reply) == 1, return ); SOFT_ASSERT(downloads_.count(reply) == 1, return false);
auto *file = downloads_[reply];
SOFT_ASSERT(file, return false);
auto replyIt = componentReplyToPath_.find(reply); downloads_.erase(reply);
const auto &fileName = replyIt->second;
if (reply->error() != QNetworkReply::NoError) {
emit error(toError(*reply));
if (!startDownload(*file))
finishUpdate();
return false;
}
const auto &fileName = file->downloadPath;
auto dir = QFileInfo(fileName).absoluteDir(); auto dir = QFileInfo(fileName).absoluteDir();
if (!dir.exists()) if (!dir.exists())
dir.mkpath("."); dir.mkpath(".");
@ -155,23 +192,24 @@ void Loader::handleComponentReply(QNetworkReply *reply)
tr("Failed to save downloaded file %1 to %2. Error %3") tr("Failed to save downloaded file %1 to %2. Error %3")
.arg(reply->url().toString(), f.fileName(), f.errorString()); .arg(reply->url().toString(), f.fileName(), f.errorString());
finishUpdate(error); finishUpdate(error);
return; return false;
} }
const auto replyData = reply->readAll(); const auto replyData = reply->readAll();
f.write(replyData); f.write(replyData);
f.close(); f.close();
componentReplyToPath_.erase(replyIt); if (downloads_.empty())
if (componentReplyToPath_.empty())
commitUpdate(); commitUpdate();
return true;
} }
void Loader::finishUpdate(const QString &error) void Loader::finishUpdate(const QString &error)
{ {
installer_.reset(); currentActions_.clear();
for (const auto &i : componentReplyToPath_) i.first->deleteLater(); for (const auto &i : downloads_) i.first->deleteLater();
componentReplyToPath_.clear(); downloads_.clear();
if (!error.isEmpty()) if (!error.isEmpty())
emit this->error(error); emit this->error(error);
SOFT_ASSERT(model_, return ); SOFT_ASSERT(model_, return );
@ -180,12 +218,13 @@ void Loader::finishUpdate(const QString &error)
void Loader::commitUpdate() void Loader::commitUpdate()
{ {
SOFT_ASSERT(installer_, return ); SOFT_ASSERT(!currentActions_.empty(), return );
if (installer_->commit()) { Installer installer(currentActions_);
if (installer.commit()) {
model_->resetProgress(); model_->resetProgress();
emit updated(); emit updated();
} else { } else {
emit error(tr("Update failed: %1").arg(installer_->errorString())); emit error(tr("Update failed: %1").arg(installer.errorString()));
} }
finishUpdate(); finishUpdate();
} }
@ -267,8 +306,12 @@ std::unique_ptr<Model::Component> Model::parse(const QJsonObject &json) const
for (const auto &fileInfo : files) { for (const auto &fileInfo : files) {
const auto object = fileInfo.toObject(); const auto object = fileInfo.toObject();
File file; File file;
file.url = object["url"].toString(); for (const auto &s : toList(object["url"])) {
if (!file.url.isValid()) const auto url = QUrl(s);
if (url.isValid())
file.urls.append(url);
}
if (file.urls.isEmpty())
result->checkOnly = true; result->checkOnly = true;
file.rawPath = object["path"].toString(); file.rawPath = object["path"].toString();
file.md5 = object["md5"].toString(); file.md5 = object["md5"].toString();
@ -302,7 +345,7 @@ void Model::updateProgress(Model::Component &component, const QUrl &url,
{ {
if (!component.files.empty()) { if (!component.files.empty()) {
for (auto &file : component.files) { for (auto &file : component.files) {
if (!url.isEmpty() && file.url != url) if (!url.isEmpty() && !file.urls.contains(url))
continue; continue;
file.progress = progress; file.progress = progress;

View File

@ -13,7 +13,7 @@ enum class State { NotAvailable, NotInstalled, UpdateAvailable, Actual };
enum class Action { NoAction, Remove, Install }; enum class Action { NoAction, Remove, Install };
struct File { struct File {
QUrl url; QVector<QUrl> urls;
QString rawPath; QString rawPath;
QString expandedPath; QString expandedPath;
QString downloadPath; QString downloadPath;
@ -130,19 +130,20 @@ signals:
private: private:
void handleReply(QNetworkReply* reply); void handleReply(QNetworkReply* reply);
void handleComponentReply(QNetworkReply* reply); bool handleComponentReply(QNetworkReply* reply);
void handleUpdateReply(QNetworkReply* reply); void handleUpdateReply(QNetworkReply* reply);
QString toError(QNetworkReply& reply) const; QString toError(QNetworkReply& reply) const;
void finishUpdate(const QString& error = {}); void finishUpdate(const QString& error = {});
void commitUpdate(); void commitUpdate();
void updateProgress(qint64 bytesSent, qint64 bytesTotal); void updateProgress(qint64 bytesSent, qint64 bytesTotal);
bool startDownload(File& file);
QNetworkAccessManager* network_; QNetworkAccessManager* network_;
Model* model_; Model* model_;
QUrl updateUrl_; QUrl updateUrl_;
QString downloadPath_; QString downloadPath_;
std::map<QNetworkReply*, QString> componentReplyToPath_; std::map<QNetworkReply*, File*> downloads_;
std::unique_ptr<Installer> installer_; UserActions currentActions_;
}; };
class AutoChecker : public QObject class AutoChecker : public QObject