From 45e12265c152eb0432fcc6c997cc0a0c6e2cea80 Mon Sep 17 00:00:00 2001 From: Gres Date: Thu, 2 Apr 2020 21:00:20 +0300 Subject: [PATCH] Show update download progress --- src/service/updates.cpp | 139 ++++++++++++++++++++++++++++++++++------ src/service/updates.h | 14 +++- src/settingseditor.cpp | 3 +- 3 files changed, 131 insertions(+), 25 deletions(-) diff --git a/src/service/updates.cpp b/src/service/updates.cpp index eb6c64e..a1fdd0d 100644 --- a/src/service/updates.cpp +++ b/src/service/updates.cpp @@ -1,6 +1,7 @@ #include "updates.h" #include "debug.h" +#include #include #include #include @@ -95,6 +96,8 @@ void Loader::applyUserActions() break; } + connect(reply, &QNetworkReply::downloadProgress, // + this, &Loader::updateProgress); file.downloadPath = downloadPath_ + '/' + file.rawPath; componentReplyToPath_.emplace(reply, file.downloadPath); } @@ -169,6 +172,7 @@ void Loader::commitUpdate() { SOFT_ASSERT(installer_, return ); if (installer_->commit()) { + model_->resetProgress(); emit updated(); } else { emit error(tr("Update failed: %1").arg(installer_->errorString())); @@ -176,6 +180,16 @@ void Loader::commitUpdate() finishUpdate(); } +void Loader::updateProgress(qint64 bytesSent, qint64 bytesTotal) +{ + if (bytesTotal < 1) + return; + const auto reply = qobject_cast(sender()); + SOFT_ASSERT(reply, return ); + const auto progress = int(100.0 * bytesSent / bytesTotal); + model_->updateProgress(reply->url(), progress); +} + Model *Loader::model() const { return model_; @@ -273,6 +287,36 @@ std::unique_ptr Model::parse(const QJsonObject &json) const return result; } +void Model::updateProgress(Model::Component &component, const QUrl &url, + int progress) +{ + if (!component.files.empty()) { + for (auto &file : component.files) { + if (!url.isEmpty() && file.url != url) + continue; + + file.progress = progress; + component.progress = progress; + + for (const auto &file : component.files) + component.progress = std::max(file.progress, component.progress); + + const auto index = toIndex(component, int(Column::Progress)); + emit dataChanged(index, index, {Qt::DisplayRole}); + + if (!url.isEmpty()) + break; + } + return; + } + + if (!component.children.empty()) { + for (auto &child : component.children) + updateProgress(*child, url, progress); + return; + } +} + void Model::setExpansions(const std::map &expansions) { expansions_ = expansions; @@ -321,6 +365,22 @@ bool Model::hasUpdates() const return hasUpdates(*root_); } +void Model::updateProgress(const QUrl &url, int progress) +{ + if (!root_) + return; + + updateProgress(*root_, url, progress); +} + +void Model::resetProgress() +{ + if (!root_) + return; + + updateProgress(*root_, {}, 0); +} + bool Model::hasUpdates(const Model::Component &component) const { for (const auto &i : component.children) { @@ -465,9 +525,9 @@ QVariant Model::headerData(int section, Qt::Orientation orientation, return section + 1; const QMap names{ - {Column::Name, tr("Name")}, {Column::State, tr("State")}, - {Column::Action, tr("Action")}, {Column::Version, tr("Version")}, - {Column::Files, tr("Files")}, + {Column::Name, tr("Name")}, {Column::State, tr("State")}, + {Column::Action, tr("Action")}, {Column::Version, tr("Version")}, + {Column::Progress, tr("Progress")}, {Column::Files, tr("Files")}, }; return names.value(Column(section)); } @@ -485,6 +545,8 @@ QVariant Model::data(const QModelIndex &index, int role) const case int(Column::State): return toString(ptr->state); case int(Column::Action): return toString(ptr->action); case int(Column::Version): return ptr->version; + case int(Column::Progress): + return ptr->progress > 0 ? ptr->progress : QVariant(); case int(Column::Files): { QStringList files; files.reserve(ptr->files.size()); @@ -536,36 +598,73 @@ Qt::ItemFlags Model::flags(const QModelIndex &index) const return result; } -ActionDelegate::ActionDelegate(QObject *parent) +UpdateDelegate::UpdateDelegate(QObject *parent) : QStyledItemDelegate(parent) { } -QWidget *ActionDelegate::createEditor(QWidget *parent, - const QStyleOptionViewItem & /*option*/, - const QModelIndex & /*index*/) const +void UpdateDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const { - auto combo = new QComboBox(parent); - combo->setEditable(false); - combo->addItems({toString(Action::NoAction), toString(Action::Remove), - toString(Action::Install)}); - return combo; + if (index.column() == int(Model::Column::Progress) && + !index.data().isNull()) { + QStyleOptionProgressBar progressBarOption; + progressBarOption.rect = option.rect; + progressBarOption.minimum = 0; + progressBarOption.maximum = 100; + const auto progress = index.data().toInt(); + progressBarOption.progress = progress; + progressBarOption.text = QString::number(progress) + "%"; + progressBarOption.textVisible = true; + + QApplication::style()->drawControl(QStyle::CE_ProgressBar, + &progressBarOption, painter); + return; + } + + QStyledItemDelegate::paint(painter, option, index); } -void ActionDelegate::setEditorData(QWidget *editor, +QWidget *UpdateDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + if (index.column() == int(Model::Column::Action)) { + auto combo = new QComboBox(parent); + combo->setEditable(false); + combo->addItems({toString(Action::NoAction), toString(Action::Remove), + toString(Action::Install)}); + return combo; + } + + return QStyledItemDelegate::createEditor(parent, option, index); +} + +void UpdateDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { - auto combo = qobject_cast(editor); - SOFT_ASSERT(combo, return ); - combo->setCurrentText(index.data(Qt::EditRole).toString()); + if (index.column() == int(Model::Column::Action)) { + auto combo = qobject_cast(editor); + SOFT_ASSERT(combo, return ); + combo->setCurrentText(index.data(Qt::EditRole).toString()); + return; + } + + return QStyledItemDelegate::setEditorData(editor, index); } -void ActionDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, +void UpdateDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { - auto combo = qobject_cast(editor); - SOFT_ASSERT(combo, return ); - model->setData(index, combo->currentIndex()); + if (index.column() == int(Model::Column::Action)) { + auto combo = qobject_cast(editor); + SOFT_ASSERT(combo, return ); + model->setData(index, combo->currentIndex()); + return; + } + + return QStyledItemDelegate::setModelData(editor, model, index); } Installer::Installer(const UserActions &actions) diff --git a/src/service/updates.h b/src/service/updates.h index 7091626..97b71f0 100644 --- a/src/service/updates.h +++ b/src/service/updates.h @@ -19,15 +19,18 @@ struct File { QString downloadPath; QString md5; QDateTime versionDate; + int progress{0}; }; using UserActions = std::multimap; -class ActionDelegate : public QStyledItemDelegate +class UpdateDelegate : public QStyledItemDelegate { Q_OBJECT public: - explicit ActionDelegate(QObject* parent = nullptr); + explicit UpdateDelegate(QObject* parent = nullptr); + void paint(QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const override; QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; void setEditorData(QWidget* editor, const QModelIndex& index) const override; @@ -39,7 +42,7 @@ class Model : public QAbstractItemModel { Q_OBJECT public: - enum class Column { Name, State, Action, Version, Files, Count }; + enum class Column { Name, State, Action, Version, Progress, Files, Count }; explicit Model(QObject* parent = nullptr); @@ -48,6 +51,8 @@ public: UserActions userActions() const; void updateStates(); bool hasUpdates() const; + void updateProgress(const QUrl& url, int progress); + void resetProgress(); QModelIndex index(int row, int column, const QModelIndex& parent) const override; @@ -72,9 +77,11 @@ private: std::vector> children; Component* parent{nullptr}; int index{-1}; + int progress{0}; }; std::unique_ptr parse(const QJsonObject& json) const; + void updateProgress(Component& component, const QUrl& url, int progress); void updateState(Component& component); bool hasUpdates(const Component& component) const; void fillUserActions(UserActions& actions, Component& component) const; @@ -126,6 +133,7 @@ private: QString toError(QNetworkReply& reply) const; void finishUpdate(const QString& error = {}); void commitUpdate(); + void updateProgress(qint64 bytesSent, qint64 bytesTotal); QNetworkAccessManager* network_; Model* model_; diff --git a/src/settingseditor.cpp b/src/settingseditor.cpp index dc8248d..ff847f0 100644 --- a/src/settingseditor.cpp +++ b/src/settingseditor.cpp @@ -89,8 +89,7 @@ SettingsEditor::SettingsEditor(Manager &manager, update::Loader &updater) auto updatesProxy = new QSortFilterProxyModel(this); updatesProxy->setSourceModel(updater_.model()); ui->updatesView->setModel(updatesProxy); - ui->updatesView->setItemDelegateForColumn(int(update::Model::Column::Action), - new update::ActionDelegate(this)); + ui->updatesView->setItemDelegate(new update::UpdateDelegate(this)); #ifndef DEVELOP ui->updatesView->hideColumn(int(update::Model::Column::Files)); #endif