mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-03 00:38:58 +00:00
Add a rate limiter to Qt's progress dialog
To work around a macOS specific issue where theh progress bar is not updated. With this the previous workaround for other OSes also isn't needed.
This commit is contained in:
@@ -15,6 +15,8 @@ add_executable(threeSD
|
|||||||
helpers/frontend_common.h
|
helpers/frontend_common.h
|
||||||
helpers/multi_job.cpp
|
helpers/multi_job.cpp
|
||||||
helpers/multi_job.h
|
helpers/multi_job.h
|
||||||
|
helpers/rate_limited_progress_dialog.cpp
|
||||||
|
helpers/rate_limited_progress_dialog.h
|
||||||
helpers/simple_job.cpp
|
helpers/simple_job.cpp
|
||||||
helpers/simple_job.h
|
helpers/simple_job.h
|
||||||
cia_build_dialog.cpp
|
cia_build_dialog.cpp
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ void MultiJob::run() {
|
|||||||
|
|
||||||
Common::ProgressCallbackWrapper wrapper{total_size};
|
Common::ProgressCallbackWrapper wrapper{total_size};
|
||||||
for (const auto& content : contents) {
|
for (const auto& content : contents) {
|
||||||
emit NextContent(count + 1, content, eta);
|
emit NextContent(count + 1, wrapper.current_done_size + wrapper.current_pending_size,
|
||||||
|
content, eta);
|
||||||
if (!execute_func(importer, content, wrapper.Wrap(Callback))) {
|
if (!execute_func(importer, content, wrapper.Wrap(Callback))) {
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
failed_contents.emplace_back(content);
|
failed_contents.emplace_back(content);
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ signals:
|
|||||||
void ProgressUpdated(u64 current_imported_size, u64 total_imported_size, int eta);
|
void ProgressUpdated(u64 current_imported_size, u64 total_imported_size, int eta);
|
||||||
|
|
||||||
/// Dumping of a content has been finished, go on to the next. Called at start as well.
|
/// Dumping of a content has been finished, go on to the next. Called at start as well.
|
||||||
void NextContent(std::size_t count, const Core::ContentSpecifier& next_content, int eta);
|
void NextContent(std::size_t count, u64 total_imported_size,
|
||||||
|
const Core::ContentSpecifier& next_content, int eta);
|
||||||
|
|
||||||
void Completed();
|
void Completed();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2021 Pengfei Zhu
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "frontend/helpers/rate_limited_progress_dialog.h"
|
||||||
|
|
||||||
|
RateLimitedProgressDialog::RateLimitedProgressDialog(const QString& label_text,
|
||||||
|
const QString& cancel_button_text, int minimum,
|
||||||
|
int maximum, QWidget* parent)
|
||||||
|
: QProgressDialog(label_text, cancel_button_text, minimum, maximum, parent) {
|
||||||
|
|
||||||
|
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
|
||||||
|
setWindowModality(Qt::WindowModal);
|
||||||
|
setMinimumDuration(0);
|
||||||
|
setValue(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
RateLimitedProgressDialog::~RateLimitedProgressDialog() = default;
|
||||||
|
|
||||||
|
void RateLimitedProgressDialog::Update(int progress, const QString& label_text) {
|
||||||
|
if (progress == maximum()) { // always set the maximum
|
||||||
|
setValue(progress);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto current_time = std::chrono::steady_clock::now();
|
||||||
|
if (current_time - last_update_time < MinimumInterval) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(progress);
|
||||||
|
setLabelText(label_text);
|
||||||
|
last_update_time = current_time;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2021 Pengfei Zhu
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <QProgressDialog>
|
||||||
|
|
||||||
|
class RateLimitedProgressDialog : public QProgressDialog {
|
||||||
|
public:
|
||||||
|
explicit RateLimitedProgressDialog(const QString& label_text, const QString& cancel_button_text,
|
||||||
|
int minimum, int maximum, QWidget* parent = nullptr);
|
||||||
|
~RateLimitedProgressDialog() override;
|
||||||
|
|
||||||
|
void Update(int progress, const QString& label_text);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::chrono::steady_clock::time_point last_update_time = std::chrono::steady_clock::now();
|
||||||
|
static constexpr auto MinimumInterval = std::chrono::milliseconds{100};
|
||||||
|
};
|
||||||
@@ -3,9 +3,8 @@
|
|||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QProgressBar>
|
|
||||||
#include <QProgressDialog>
|
|
||||||
#include "frontend/helpers/frontend_common.h"
|
#include "frontend/helpers/frontend_common.h"
|
||||||
|
#include "frontend/helpers/rate_limited_progress_dialog.h"
|
||||||
#include "frontend/helpers/simple_job.h"
|
#include "frontend/helpers/simple_job.h"
|
||||||
|
|
||||||
SimpleJob::SimpleJob(QObject* parent, ExecuteFunc execute_, AbortFunc abort_)
|
SimpleJob::SimpleJob(QObject* parent, ExecuteFunc execute_, AbortFunc abort_)
|
||||||
@@ -30,34 +29,25 @@ void SimpleJob::Cancel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SimpleJob::StartWithProgressDialog(QWidget* widget) {
|
void SimpleJob::StartWithProgressDialog(QWidget* widget) {
|
||||||
// We need to create the bar ourselves to circumvent an issue caused by modal ProgressDialog's
|
auto* dialog = new RateLimitedProgressDialog(tr("Initializing..."), tr("Cancel"), 0, 0, widget);
|
||||||
// event handling.
|
connect(this, &SimpleJob::ProgressUpdated, this, [dialog](u64 current, u64 total) {
|
||||||
auto* bar = new QProgressBar(widget);
|
if (dialog->wasCanceled()) {
|
||||||
bar->setRange(0, 100);
|
return;
|
||||||
bar->setValue(0);
|
}
|
||||||
|
|
||||||
auto* dialog = new QProgressDialog(tr("Initializing..."), tr("Cancel"), 0, 0, widget);
|
|
||||||
dialog->setWindowFlags(dialog->windowFlags() & (~Qt::WindowContextHelpButtonHint));
|
|
||||||
dialog->setWindowModality(Qt::WindowModal);
|
|
||||||
dialog->setBar(bar);
|
|
||||||
dialog->setMinimumDuration(0);
|
|
||||||
|
|
||||||
connect(this, &SimpleJob::ProgressUpdated, this, [bar, dialog](u64 current, u64 total) {
|
|
||||||
// Try to map total to int range
|
// Try to map total to int range
|
||||||
// This is equal to ceil(total / INT_MAX)
|
// This is equal to ceil(total / INT_MAX)
|
||||||
const u64 multiplier =
|
const u64 multiplier =
|
||||||
(total + std::numeric_limits<int>::max() - 1) / std::numeric_limits<int>::max();
|
(total + std::numeric_limits<int>::max() - 1) / std::numeric_limits<int>::max();
|
||||||
bar->setMaximum(static_cast<int>(total / multiplier));
|
dialog->setMaximum(static_cast<int>(total / multiplier));
|
||||||
bar->setValue(static_cast<int>(current / multiplier));
|
dialog->Update(static_cast<int>(current / multiplier),
|
||||||
dialog->setLabelText(
|
tr("%1 / %2").arg(ReadableByteSize(current), ReadableByteSize(total)));
|
||||||
tr("%1 / %2").arg(ReadableByteSize(current)).arg(ReadableByteSize(total)));
|
|
||||||
});
|
});
|
||||||
connect(this, &SimpleJob::ErrorOccured, this, [widget, dialog] {
|
connect(this, &SimpleJob::ErrorOccured, this, [widget, dialog] {
|
||||||
QMessageBox::critical(widget, tr("threeSD"),
|
QMessageBox::critical(widget, tr("threeSD"),
|
||||||
tr("Operation failed. Please refer to the log."));
|
tr("Operation failed. Please refer to the log."));
|
||||||
dialog->hide();
|
dialog->hide();
|
||||||
});
|
});
|
||||||
connect(this, &SimpleJob::Completed, this, [dialog] { dialog->setValue(dialog->maximum()); });
|
connect(this, &SimpleJob::Completed, dialog, &QProgressDialog::hide);
|
||||||
connect(dialog, &QProgressDialog::canceled, this, &SimpleJob::Cancel);
|
connect(dialog, &QProgressDialog::canceled, this, &SimpleJob::Cancel);
|
||||||
|
|
||||||
start();
|
start();
|
||||||
|
|||||||
@@ -12,8 +12,6 @@
|
|||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
#include <QProgressBar>
|
|
||||||
#include <QProgressDialog>
|
|
||||||
#include <QStorageInfo>
|
#include <QStorageInfo>
|
||||||
#include <QtConcurrent/QtConcurrentRun>
|
#include <QtConcurrent/QtConcurrentRun>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
@@ -23,6 +21,7 @@
|
|||||||
#include "frontend/cia_build_dialog.h"
|
#include "frontend/cia_build_dialog.h"
|
||||||
#include "frontend/helpers/frontend_common.h"
|
#include "frontend/helpers/frontend_common.h"
|
||||||
#include "frontend/helpers/multi_job.h"
|
#include "frontend/helpers/multi_job.h"
|
||||||
|
#include "frontend/helpers/rate_limited_progress_dialog.h"
|
||||||
#include "frontend/helpers/simple_job.h"
|
#include "frontend/helpers/simple_job.h"
|
||||||
#include "frontend/import_dialog.h"
|
#include "frontend/import_dialog.h"
|
||||||
#include "frontend/title_info_dialog.h"
|
#include "frontend/title_info_dialog.h"
|
||||||
@@ -128,12 +127,9 @@ void ImportDialog::SetContentSizes(int previous_width, int previous_height) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ImportDialog::RelistContent() {
|
void ImportDialog::RelistContent() {
|
||||||
auto* dialog = new QProgressDialog(tr("Loading Contents..."), tr("Cancel"), 0, 0, this);
|
auto* dialog =
|
||||||
dialog->setWindowFlags(dialog->windowFlags() & (~Qt::WindowContextHelpButtonHint));
|
new RateLimitedProgressDialog(tr("Loading Contents..."), tr("Cancel"), 0, 0, this);
|
||||||
dialog->setWindowModality(Qt::WindowModal);
|
|
||||||
dialog->setCancelButton(nullptr);
|
dialog->setCancelButton(nullptr);
|
||||||
dialog->setMinimumDuration(0);
|
|
||||||
dialog->setValue(0);
|
|
||||||
|
|
||||||
using FutureWatcher = QFutureWatcher<void>;
|
using FutureWatcher = QFutureWatcher<void>;
|
||||||
auto* future_watcher = new FutureWatcher(this);
|
auto* future_watcher = new FutureWatcher(this);
|
||||||
@@ -601,48 +597,47 @@ void ImportDialog::RunMultiJob(MultiJob* job, std::size_t total_count, u64 total
|
|||||||
label->setWordWrap(true);
|
label->setWordWrap(true);
|
||||||
label->setFixedWidth(600);
|
label->setFixedWidth(600);
|
||||||
|
|
||||||
// We need to create the bar ourselves to circumvent an issue caused by modal ProgressDialog's
|
auto* dialog = new RateLimitedProgressDialog(tr("Initializing..."), tr("Cancel"), 0,
|
||||||
// event handling.
|
static_cast<int>(total_size / multiplier), this);
|
||||||
auto* bar = new QProgressBar(this);
|
|
||||||
bar->setRange(0, static_cast<int>(total_size / multiplier));
|
|
||||||
bar->setValue(0);
|
|
||||||
|
|
||||||
auto* dialog = new QProgressDialog(tr("Initializing..."), tr("Cancel"), 0, 0, this);
|
|
||||||
dialog->setWindowFlags(dialog->windowFlags() & (~Qt::WindowContextHelpButtonHint));
|
|
||||||
dialog->setWindowModality(Qt::WindowModal);
|
|
||||||
dialog->setBar(bar);
|
|
||||||
dialog->setLabel(label);
|
dialog->setLabel(label);
|
||||||
dialog->setMinimumDuration(0);
|
|
||||||
|
|
||||||
connect(job, &MultiJob::NextContent, this,
|
connect(job, &MultiJob::NextContent, this,
|
||||||
[this, dialog, total_count](std::size_t count,
|
[this, dialog, multiplier, total_count](std::size_t count, u64 total_imported_size,
|
||||||
const Core::ContentSpecifier& next_content, int eta) {
|
const Core::ContentSpecifier& next_content,
|
||||||
dialog->setLabelText(
|
int eta) {
|
||||||
tr("<p>(%1/%2) %3 (%4)</p><p> </p><p align=\"right\">%5</p>")
|
if (dialog->wasCanceled()) {
|
||||||
.arg(count)
|
return;
|
||||||
.arg(total_count)
|
}
|
||||||
.arg(GetContentName(next_content))
|
dialog->Update(static_cast<int>(total_imported_size / multiplier),
|
||||||
.arg(GetContentTypeName<false>(next_content.type))
|
tr("<p>(%1/%2) %3 (%4)</p><p> </p><p align=\"right\">%5</p>")
|
||||||
.arg(FormatETA(eta)));
|
.arg(count)
|
||||||
|
.arg(total_count)
|
||||||
|
.arg(GetContentName(next_content))
|
||||||
|
.arg(GetContentTypeName<false>(next_content.type))
|
||||||
|
.arg(FormatETA(eta)));
|
||||||
current_content = next_content;
|
current_content = next_content;
|
||||||
current_count = count;
|
current_count = count;
|
||||||
});
|
});
|
||||||
connect(job, &MultiJob::ProgressUpdated, this,
|
connect(
|
||||||
[this, bar, dialog, multiplier, total_count](u64 current_imported_size,
|
job, &MultiJob::ProgressUpdated, this,
|
||||||
u64 total_imported_size, int eta) {
|
[this, dialog, multiplier, total_count](u64 current_imported_size, u64 total_imported_size,
|
||||||
bar->setValue(static_cast<int>(total_imported_size / multiplier));
|
int eta) {
|
||||||
dialog->setLabelText(tr("<p>(%1/%2) %3 (%4)</p><p align=\"center\">%5 "
|
if (dialog->wasCanceled()) {
|
||||||
"/ %6</p><p align=\"right\">%7</p>")
|
return;
|
||||||
.arg(current_count)
|
}
|
||||||
.arg(total_count)
|
dialog->Update(
|
||||||
.arg(GetContentName(current_content))
|
static_cast<int>(total_imported_size / multiplier),
|
||||||
.arg(GetContentTypeName<false>(current_content.type))
|
tr("<p>(%1/%2) %3 (%4)</p><p align=\"center\">%5 / %6</p><p align=\"right\">%7</p>")
|
||||||
.arg(ReadableByteSize(current_imported_size))
|
.arg(current_count)
|
||||||
.arg(ReadableByteSize(current_content.maximum_size))
|
.arg(total_count)
|
||||||
.arg(FormatETA(eta)));
|
.arg(GetContentName(current_content))
|
||||||
});
|
.arg(GetContentTypeName<false>(current_content.type))
|
||||||
|
.arg(ReadableByteSize(current_imported_size))
|
||||||
|
.arg(ReadableByteSize(current_content.maximum_size))
|
||||||
|
.arg(FormatETA(eta)));
|
||||||
|
});
|
||||||
connect(job, &MultiJob::Completed, this, [this, dialog, job] {
|
connect(job, &MultiJob::Completed, this, [this, dialog, job] {
|
||||||
dialog->setValue(dialog->maximum());
|
dialog->hide();
|
||||||
|
|
||||||
const auto failed_contents = job->GetFailedContents();
|
const auto failed_contents = job->GetFailedContents();
|
||||||
if (failed_contents.empty()) {
|
if (failed_contents.empty()) {
|
||||||
@@ -662,13 +657,9 @@ void ImportDialog::RunMultiJob(MultiJob* job, std::size_t total_count, u64 total
|
|||||||
});
|
});
|
||||||
connect(dialog, &QProgressDialog::canceled, this, [this, job] {
|
connect(dialog, &QProgressDialog::canceled, this, [this, job] {
|
||||||
// Add yet-another-ProgressDialog to indicate cancel progress
|
// Add yet-another-ProgressDialog to indicate cancel progress
|
||||||
auto* cancel_dialog = new QProgressDialog(tr("Canceling..."), tr("Cancel"), 0, 0, this);
|
auto* cancel_dialog =
|
||||||
cancel_dialog->setWindowFlags(cancel_dialog->windowFlags() &
|
new RateLimitedProgressDialog(tr("Canceling..."), tr("Cancel"), 0, 0, this);
|
||||||
(~Qt::WindowContextHelpButtonHint));
|
|
||||||
cancel_dialog->setWindowModality(Qt::WindowModal);
|
|
||||||
cancel_dialog->setCancelButton(nullptr);
|
cancel_dialog->setCancelButton(nullptr);
|
||||||
cancel_dialog->setMinimumDuration(0);
|
|
||||||
cancel_dialog->setValue(0);
|
|
||||||
connect(job, &MultiJob::Completed, cancel_dialog, &QProgressDialog::hide);
|
connect(job, &MultiJob::Completed, cancel_dialog, &QProgressDialog::hide);
|
||||||
job->Cancel();
|
job->Cancel();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user