Qt: Use QtAsyncTaskWithProgress for cover downloader

This commit is contained in:
Stenzek
2025-11-30 19:05:18 +10:00
parent d1d9008ffe
commit 1fccee229b
2 changed files with 55 additions and 168 deletions

View File

@@ -3,10 +3,11 @@
#include "coverdownloadwindow.h"
#include "qthost.h"
#include "qtprogresscallback.h"
#include "core/game_list.h"
#include "common/assert.h"
#include "common/error.h"
#include "moc_coverdownloadwindow.cpp"
@@ -17,152 +18,77 @@ CoverDownloadWindow::CoverDownloadWindow() : QWidget()
updateEnabled();
connect(m_ui.start, &QPushButton::clicked, this, &CoverDownloadWindow::onStartClicked);
connect(m_ui.close, &QPushButton::clicked, this, &CoverDownloadWindow::onCloseClicked);
connect(m_ui.close, &QPushButton::clicked, this, &CoverDownloadWindow::close);
connect(m_ui.urls, &QTextEdit::textChanged, this, &CoverDownloadWindow::updateEnabled);
}
CoverDownloadWindow::~CoverDownloadWindow()
{
Assert(!m_thread);
}
CoverDownloadWindow::~CoverDownloadWindow() = default;
void CoverDownloadWindow::closeEvent(QCloseEvent* ev)
{
QtUtils::SaveWindowGeometry(this);
QWidget::closeEvent(ev);
cancelThread();
if (m_task)
m_task->cancel();
emit closed();
}
void CoverDownloadWindow::onDownloadStatus(const QString& text)
{
m_ui.status->setText(text);
}
void CoverDownloadWindow::onDownloadProgress(int value, int range)
{
// Limit to once every five seconds, otherwise it's way too flickery.
// Ideally in the future we'd have some way to invalidate only a single cover.
if (m_last_refresh_time.GetTimeSeconds() >= 5.0f)
{
emit coverRefreshRequested();
m_last_refresh_time.Reset();
}
if (range != m_ui.progress->maximum())
m_ui.progress->setMaximum(range);
m_ui.progress->setValue(value);
}
void CoverDownloadWindow::onDownloadComplete()
{
emit coverRefreshRequested();
m_ui.status->setText(tr("Download complete."));
QString error;
if (m_thread)
{
m_thread->wait();
if (!m_thread->getResult())
{
if (const std::string& err_str = m_thread->getError().GetDescription(); !err_str.empty())
m_ui.status->setText(QString::fromStdString(err_str));
}
delete m_thread;
m_thread = nullptr;
}
updateEnabled();
}
void CoverDownloadWindow::onStartClicked()
{
if (m_thread)
cancelThread();
else
startThread();
if (m_task)
{
m_task->cancel();
return;
}
std::vector<std::string> urls;
const bool use_serials = m_ui.useSerialFileNames->isChecked();
for (const QString& str : m_ui.urls->toPlainText().split(QChar('\n')))
urls.push_back(str.toStdString());
m_task = QtAsyncTaskWithProgress::create(
this, [this, urls = std::move(urls), use_serials](ProgressCallback* const progress) {
Error error;
const bool result = GameList::DownloadCovers(urls, use_serials, progress, &error);
return [this, result, error = std::move(error)]() { downloadComplete(result, error); };
});
m_task->connectWidgets(m_ui.status, m_ui.progress, m_ui.start);
connect(m_task, &QtAsyncTaskWithProgress::progressValueUpdated, this, [this]() {
// Limit to once every five seconds, otherwise it's way too flickery.
// Ideally in the future we'd have some way to invalidate only a single cover.
if (m_last_refresh_time.GetTimeSeconds() >= 5.0f)
{
emit coverRefreshRequested();
m_last_refresh_time.Reset();
}
});
m_task->start();
updateEnabled();
}
void CoverDownloadWindow::onCloseClicked()
void CoverDownloadWindow::downloadComplete(bool result, const Error& error)
{
if (m_thread)
cancelThread();
emit coverRefreshRequested();
m_ui.status->setText(tr("Download complete."));
if (!result)
{
if (const std::string& err_str = error.GetDescription(); !err_str.empty())
m_ui.status->setText(QString::fromStdString(err_str));
}
close();
m_task = nullptr;
updateEnabled();
}
void CoverDownloadWindow::updateEnabled()
{
const bool running = static_cast<bool>(m_thread);
const bool running = static_cast<bool>(m_task);
m_ui.start->setText(running ? tr("Stop") : tr("Start"));
m_ui.start->setEnabled(running || !m_ui.urls->toPlainText().isEmpty());
m_ui.close->setEnabled(!running);
m_ui.urls->setEnabled(!running);
}
void CoverDownloadWindow::startThread()
{
m_thread = new CoverDownloadThread(m_ui.urls->toPlainText(), m_ui.useSerialFileNames->isChecked());
m_last_refresh_time.Reset();
m_thread->moveToThread(m_thread);
connect(m_thread, &CoverDownloadThread::statusUpdated, this, &CoverDownloadWindow::onDownloadStatus);
connect(m_thread, &CoverDownloadThread::progressUpdated, this, &CoverDownloadWindow::onDownloadProgress);
connect(m_thread, &CoverDownloadThread::threadFinished, this, &CoverDownloadWindow::onDownloadComplete);
m_thread->start();
updateEnabled();
}
void CoverDownloadWindow::cancelThread()
{
if (!m_thread)
return;
m_thread->requestInterruption();
m_thread->wait();
delete m_thread;
m_thread = nullptr;
}
CoverDownloadThread::CoverDownloadThread(const QString& urls, bool use_serials) : QThread(), m_use_serials(use_serials)
{
for (const QString& str : urls.split(QChar('\n')))
m_urls.push_back(str.toStdString());
}
CoverDownloadThread::~CoverDownloadThread() = default;
bool CoverDownloadThread::IsCancelled() const
{
return isInterruptionRequested();
}
void CoverDownloadThread::SetTitle(const std::string_view title)
{
emit titleUpdated(QtUtils::StringViewToQString(title));
}
void CoverDownloadThread::SetStatusText(const std::string_view text)
{
ProgressCallback::SetStatusText(text);
emit statusUpdated(QtUtils::StringViewToQString(text));
}
void CoverDownloadThread::SetProgressRange(u32 range)
{
ProgressCallback::SetProgressRange(range);
emit progressUpdated(static_cast<int>(m_progress_value), static_cast<int>(m_progress_range));
}
void CoverDownloadThread::SetProgressValue(u32 value)
{
ProgressCallback::SetProgressValue(value);
emit progressUpdated(static_cast<int>(m_progress_value), static_cast<int>(m_progress_range));
}
void CoverDownloadThread::run()
{
m_result = GameList::DownloadCovers(m_urls, m_use_serials, static_cast<ProgressCallback*>(this), &m_error);
emit threadFinished();
}

View File

@@ -5,8 +5,6 @@
#include "ui_coverdownloadwindow.h"
#include "common/error.h"
#include "common/progress_callback.h"
#include "common/timer.h"
#include "common/types.h"
@@ -16,7 +14,9 @@
#include <memory>
#include <string>
class CoverDownloadThread;
class Error;
class QtAsyncTaskWithProgress;
class CoverDownloadWindow final : public QWidget
{
@@ -34,50 +34,11 @@ protected:
void closeEvent(QCloseEvent* ev) override;
private:
void startThread();
void cancelThread();
void onDownloadStatus(const QString& text);
void onDownloadProgress(int value, int range);
void onDownloadComplete();
void onStartClicked();
void onCloseClicked();
void downloadComplete(bool result, const Error& error);
void updateEnabled();
Ui::CoverDownloadWindow m_ui;
CoverDownloadThread* m_thread = nullptr;
QtAsyncTaskWithProgress* m_task = nullptr;
Timer m_last_refresh_time;
};
class CoverDownloadThread final : public QThread, private ProgressCallback
{
Q_OBJECT
public:
CoverDownloadThread(const QString& urls, bool use_serials);
~CoverDownloadThread();
ALWAYS_INLINE const Error& getError() const { return m_error; }
ALWAYS_INLINE bool getResult() const { return m_result; }
Q_SIGNALS:
void titleUpdated(const QString& title);
void statusUpdated(const QString& status);
void progressUpdated(int value, int range);
void threadFinished();
protected:
void run() override;
bool IsCancelled() const override;
void SetTitle(const std::string_view title) override;
void SetStatusText(const std::string_view text) override;
void SetProgressRange(u32 range) override;
void SetProgressValue(u32 value) override;
private:
std::vector<std::string> m_urls;
Error m_error;
bool m_use_serials = false;
bool m_result = false;
};