mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-08 02:53:25 -08:00
[Client] Improve card database update visibility (#7458)
* [Client] Improve card database update visibility Addresses Cockatrice/Cockatrice#7431. Cockatrice previously offered no in-client feedback while the card database updater ran: the only progress UI lived in the first-run wizard, stderr/stdout were not parsed in interactive runs, and starting a second update with Oracle already open or with a hidden background run active just showed a confusing 'already running' guard - even though Oracle is launched as a separate process with no lifecycle tracking. - Add a status-bar widget (CardDatabaseUpdateStatusBar) that appears while an update runs and shows the Downloading/Parsing/Importing stages with percent progress; it is also used in the quit-confirm prompt so users see an unfinished update before cancelling it. - Parse the updater's PROGRESS lines (new CardUpdateProgressParser, unit-tested) in both foreground and background runs and log stage changes to the new card_database.update category, throttled to at most one line per second; forward Oracle stderr to the logs. - Show a tray balloon when a background update starts and on finish or failure instead of staying silent. - Request a running Oracle instance to raise itself (QLocalServer CockatriceOracleRaise-<user>) when the user starts another update, and demote the 'already running' guard to an informative message only when no running instance can be found. - Emit download/scan/import progress unconditionally from Oracle so interactive and background runs behave the same. - Document the new log category in the troubleshooting guide. * [Client] Address card database update review feedback * [Client] Add setting to control card database update tray notifications * [Client] Address remaining card database update review nits - Rename card_update_progress_parser.{h,cpp} to card_update_progress.{h,cpp} - Store the CardUpdateProgress struct in the status bar widget * Lint. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
6bed5d6f67
commit
a44757bd11
26 changed files with 702 additions and 67 deletions
|
|
@ -0,0 +1,117 @@
|
|||
#include "card_database_update_status_bar.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QEvent>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QProgressBar>
|
||||
#include <QtGlobal>
|
||||
#include <climits>
|
||||
|
||||
namespace
|
||||
{
|
||||
/**
|
||||
* @brief Translated name of one updater stage.
|
||||
* @param fallback shown when the stage is unknown or not reported yet
|
||||
*/
|
||||
QString stageName(CardUpdateStage stage, const QString &fallback)
|
||||
{
|
||||
switch (stage) {
|
||||
case CardUpdateStage::Download:
|
||||
return QCoreApplication::translate("CardDatabaseUpdateStatusBar", "Downloading");
|
||||
case CardUpdateStage::Scan:
|
||||
return QCoreApplication::translate("CardDatabaseUpdateStatusBar", "Parsing");
|
||||
case CardUpdateStage::Import:
|
||||
return QCoreApplication::translate("CardDatabaseUpdateStatusBar", "Importing");
|
||||
case CardUpdateStage::Unknown:
|
||||
return fallback;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
CardDatabaseUpdateStatusBar::CardDatabaseUpdateStatusBar(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
layout = new QHBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
stageLabel = new QLabel(this);
|
||||
progressBar = new QProgressBar(this);
|
||||
progressBar->setMinimumWidth(120);
|
||||
|
||||
layout->addWidget(stageLabel);
|
||||
layout->addWidget(progressBar);
|
||||
|
||||
setLayout(layout);
|
||||
refresh();
|
||||
hide();
|
||||
}
|
||||
|
||||
QString CardDatabaseUpdateStatusBar::statusText() const
|
||||
{
|
||||
if (!active) {
|
||||
return {};
|
||||
}
|
||||
const QString name = stageName(progress.stage, tr("Updating the card database"));
|
||||
if (progress.total <= 0) {
|
||||
return name;
|
||||
}
|
||||
const int percent = static_cast<int>((100.0 * progress.done) / progress.total);
|
||||
return tr("%1 (%2%)").arg(name).arg(percent);
|
||||
}
|
||||
|
||||
void CardDatabaseUpdateStatusBar::updateStarted()
|
||||
{
|
||||
active = true;
|
||||
progress = {};
|
||||
refresh();
|
||||
show();
|
||||
}
|
||||
|
||||
void CardDatabaseUpdateStatusBar::updateProgress(const CardUpdateProgress &progress)
|
||||
{
|
||||
active = true;
|
||||
this->progress = progress;
|
||||
refresh();
|
||||
}
|
||||
|
||||
void CardDatabaseUpdateStatusBar::updateFinished()
|
||||
{
|
||||
active = false;
|
||||
progress = {};
|
||||
refresh();
|
||||
hide();
|
||||
}
|
||||
|
||||
void CardDatabaseUpdateStatusBar::changeEvent(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::LanguageChange) {
|
||||
refresh();
|
||||
}
|
||||
QWidget::changeEvent(event);
|
||||
}
|
||||
|
||||
void CardDatabaseUpdateStatusBar::refresh()
|
||||
{
|
||||
const QString name = stageName(progress.stage, tr("Updating the card database"));
|
||||
stageLabel->setText(name);
|
||||
|
||||
const qint64 cappedTotal = qMin<qint64>(progress.total, INT_MAX);
|
||||
const qint64 cappedDone = qMin<qint64>(progress.done, INT_MAX);
|
||||
const int minimum = 0;
|
||||
const int maximum = static_cast<int>(cappedTotal);
|
||||
if (progressBar->minimum() != minimum || progressBar->maximum() != maximum) {
|
||||
// A zero range is the busy indicator, used until the first report arrives.
|
||||
progressBar->setRange(minimum, maximum);
|
||||
}
|
||||
progressBar->setValue(static_cast<int>(cappedDone));
|
||||
|
||||
if (active && progress.total > 0) {
|
||||
const int percent = static_cast<int>((100.0 * progress.done) / progress.total);
|
||||
setToolTip(tr("Card database update in progress: %1 (%2%)").arg(name).arg(percent));
|
||||
} else if (active) {
|
||||
setToolTip(tr("Card database update in progress."));
|
||||
} else {
|
||||
setToolTip({});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
#ifndef CARD_DATABASE_UPDATE_STATUS_BAR_H
|
||||
#define CARD_DATABASE_UPDATE_STATUS_BAR_H
|
||||
|
||||
#include "card_update_progress.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
class QEvent;
|
||||
class QHBoxLayout;
|
||||
class QLabel;
|
||||
class QProgressBar;
|
||||
|
||||
/**
|
||||
* @class CardDatabaseUpdateStatusBar
|
||||
* @brief Status bar widget showing what the card database updater (Oracle) is doing.
|
||||
*
|
||||
* The updater runs as a separate process, so this widget is the in-client view of
|
||||
* its progress: it appears when an update starts, tracks the download/scan/import
|
||||
* stages reported by the updater, and disappears when the update finishes.
|
||||
*/
|
||||
class CardDatabaseUpdateStatusBar : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a hidden progress widget.
|
||||
* @param parent Parent widget
|
||||
*/
|
||||
explicit CardDatabaseUpdateStatusBar(QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Human readable description of the running stage, e.g. "Downloading (42%)".
|
||||
* @return the description, or an empty string when no update is running
|
||||
*/
|
||||
QString statusText() const;
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Shows the widget and resets it to the indeterminate state.
|
||||
*/
|
||||
void updateStarted();
|
||||
|
||||
/**
|
||||
* @brief Shows the progress of one update stage.
|
||||
*/
|
||||
void updateProgress(const CardUpdateProgress &progress);
|
||||
|
||||
/**
|
||||
* @brief Hides the widget and clears the reported progress.
|
||||
*/
|
||||
void updateFinished();
|
||||
|
||||
protected:
|
||||
void changeEvent(QEvent *event) override;
|
||||
|
||||
private:
|
||||
void refresh();
|
||||
|
||||
QHBoxLayout *layout; ///< Horizontal layout holding the stage label and the progress bar
|
||||
QLabel *stageLabel; ///< Name of the stage currently running
|
||||
QProgressBar *progressBar; ///< Progress within the current stage
|
||||
CardUpdateProgress progress; ///< Last reported progress
|
||||
bool active = false; ///< Whether an update is currently running
|
||||
};
|
||||
|
||||
#endif // CARD_DATABASE_UPDATE_STATUS_BAR_H
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
#include "card_update_progress.h"
|
||||
|
||||
#include <QList>
|
||||
|
||||
namespace
|
||||
{
|
||||
CardUpdateStage parseStage(const QByteArray &token)
|
||||
{
|
||||
if (token == "download") {
|
||||
return CardUpdateStage::Download;
|
||||
}
|
||||
if (token == "scan") {
|
||||
return CardUpdateStage::Scan;
|
||||
}
|
||||
if (token == "import") {
|
||||
return CardUpdateStage::Import;
|
||||
}
|
||||
return CardUpdateStage::Unknown;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::optional<CardUpdateProgress> CardUpdateProgress::fromProtocolLine(const QByteArray &line)
|
||||
{
|
||||
const QByteArray trimmed = line.trimmed();
|
||||
if (!trimmed.startsWith("PROGRESS ")) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const QList<QByteArray> parts = trimmed.split(' ');
|
||||
if (parts.size() != 4) {
|
||||
return std::nullopt;
|
||||
}
|
||||
bool doneOk = false;
|
||||
bool totalOk = false;
|
||||
const qint64 done = parts.at(2).toLongLong(&doneOk);
|
||||
const qint64 total = parts.at(3).toLongLong(&totalOk);
|
||||
if (!doneOk || !totalOk || done < 0 || total < 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
CardUpdateProgress progress;
|
||||
progress.stage = parseStage(parts.at(1));
|
||||
progress.done = done;
|
||||
progress.total = total;
|
||||
return progress;
|
||||
}
|
||||
|
||||
QString CardUpdateProgress::stageToken() const
|
||||
{
|
||||
switch (stage) {
|
||||
case CardUpdateStage::Download:
|
||||
return QStringLiteral("download");
|
||||
case CardUpdateStage::Scan:
|
||||
return QStringLiteral("scan");
|
||||
case CardUpdateStage::Import:
|
||||
return QStringLiteral("import");
|
||||
case CardUpdateStage::Unknown:
|
||||
return QStringLiteral("unknown");
|
||||
}
|
||||
return QStringLiteral("unknown");
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
#ifndef CARD_UPDATE_PROGRESS_H
|
||||
#define CARD_UPDATE_PROGRESS_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QtGlobal>
|
||||
#include <optional>
|
||||
|
||||
/**
|
||||
* @brief One stage of the card database updater's progress protocol.
|
||||
*/
|
||||
enum class CardUpdateStage
|
||||
{
|
||||
Download, ///< downloading the source data
|
||||
Scan, ///< parsing the downloaded data
|
||||
Import, ///< importing cards into the database
|
||||
Unknown, ///< a stage the client does not recognize
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief One progress line read from the card database updater's stdout.
|
||||
*/
|
||||
struct CardUpdateProgress
|
||||
{
|
||||
/**
|
||||
* @brief Parses one line of the updater stdout protocol: "PROGRESS <stage> <done> <total>".
|
||||
* @param line a single stdout line, with or without its trailing newline
|
||||
* @return the parsed progress, or @c std::nullopt when the line is not a valid progress line
|
||||
*/
|
||||
static std::optional<CardUpdateProgress> fromProtocolLine(const QByteArray &line);
|
||||
|
||||
/**
|
||||
* @brief Protocol token of the stage ("download", "scan", "import" or "unknown"), for logging.
|
||||
*/
|
||||
QString stageToken() const;
|
||||
|
||||
CardUpdateStage stage = CardUpdateStage::Unknown;
|
||||
qint64 done = 0;
|
||||
qint64 total = 0;
|
||||
};
|
||||
|
||||
#endif // CARD_UPDATE_PROGRESS_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue