[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:
BruebachL 2026-10-09 11:07:48 +02:00 • committed by GitHub
parent 6bed5d6f67
commit a44757bd11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 702 additions and 67 deletions

View file

@ -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({});
}
}

View file

@ -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

View file

@ -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");
}

View file

@ -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

View file

@ -185,10 +185,10 @@ void FirstRunWizard::onCardDatabaseUpdateFinished(bool success)
}
}
void FirstRunWizard::onCardDatabaseUpdateProgress(const QString &stage, qint64 done, qint64 total)
void FirstRunWizard::onCardDatabaseUpdateProgress(const CardUpdateProgress &progress)
{
if (cardDatabasePage) {
cardDatabasePage->onUpdateProgress(stage, done, total);
cardDatabasePage->onUpdateProgress(progress);
}
}

View file

@ -1,6 +1,8 @@
#ifndef FIRST_RUN_WIZARD_H
#define FIRST_RUN_WIZARD_H
#include "../../card_database_update/card_update_progress.h"
#include <QDialog>
#include <QList>
#include <QString>
@ -42,7 +44,7 @@ public slots:
void onCardDatabaseUpdateFinished(bool success);
/** @brief Forwarded from MainWindow while the background card database update process runs. */
void onCardDatabaseUpdateProgress(const QString &stage, qint64 done, qint64 total);
void onCardDatabaseUpdateProgress(const CardUpdateProgress &progress);
protected:
void closeEvent(QCloseEvent *event) override;

View file

@ -188,21 +188,29 @@ void CardDatabaseSetupPage::onUpdateFinished(bool success)
}
}
void CardDatabaseSetupPage::onUpdateProgress(const QString &stage, qint64 done, qint64 total)
void CardDatabaseSetupPage::onUpdateProgress(const CardUpdateProgress &progress)
{
if (state != State::Running) {
return;
}
const qint64 total = progress.total;
const qint64 done = progress.done;
progressBar->setRange(0, total > 0 ? static_cast<int>(qMin<qint64>(total, INT_MAX)) : 0);
progressBar->setValue(static_cast<int>(qMin<qint64>(done, INT_MAX)));
if (total > 0) {
const int percent = static_cast<int>((100.0 * done) / total);
if (stage == QLatin1String("download")) {
statusLabel->setText(tr("Downloading the card database (%1%)…").arg(percent));
} else if (stage == QLatin1String("scan")) {
statusLabel->setText(tr("Parsing the card database (%1%)…").arg(percent));
} else if (stage == QLatin1String("import")) {
statusLabel->setText(tr("Importing cards (%1%)…").arg(percent));
switch (progress.stage) {
case CardUpdateStage::Download:
statusLabel->setText(tr("Downloading the card database (%1%)…").arg(percent));
break;
case CardUpdateStage::Scan:
statusLabel->setText(tr("Parsing the card database (%1%)…").arg(percent));
break;
case CardUpdateStage::Import:
statusLabel->setText(tr("Importing cards (%1%)…").arg(percent));
break;
case CardUpdateStage::Unknown:
break;
}
}
}

View file

@ -1,6 +1,7 @@
#ifndef CARD_DATABASE_SETUP_PAGE_H
#define CARD_DATABASE_SETUP_PAGE_H
#include "../../../card_database_update/card_update_progress.h"
#include "../first_run_wizard_page.h"
#include <QSize>
@ -33,7 +34,7 @@ public:
void retranslateUi() override;
void onUpdateFinished(bool success);
void onUpdateProgress(const QString &stage, qint64 done, qint64 total);
void onUpdateProgress(const CardUpdateProgress &progress);
signals:
void updateRequested();

View file

@ -161,12 +161,18 @@ GeneralSettingsPage::GeneralSettingsPage()
newVersionOracleCheckBox.setChecked(settings.updates().getNotifyAboutNewVersion());
cardDatabaseUpdateTrayNotificationCheckBox.setChecked(settings.updates().getCardDatabaseUpdateTrayNotification());
connect(&cardDatabaseUpdateTrayNotificationCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings.updates(),
&UpdatesSettings::setCardDatabaseUpdateTrayNotification);
auto *cardDatabaseGrid = new QGridLayout;
cardDatabaseGrid->addWidget(&startupCardUpdateCheckBehaviorLabel, 0, 0);
cardDatabaseGrid->addWidget(&startupCardUpdateCheckBehaviorSelector, 0, 1);
cardDatabaseGrid->addWidget(&cardUpdateCheckIntervalLabel, 1, 0);
cardDatabaseGrid->addWidget(&cardUpdateCheckIntervalSpinBox, 1, 1);
cardDatabaseGrid->addWidget(&lastCardUpdateCheckDateLabel, 2, 1);
cardDatabaseGrid->addWidget(&cardDatabaseUpdateTrayNotificationCheckBox, 3, 0, 1, 2);
cardDatabaseGroupBox = new QGroupBox;
cardDatabaseGroupBox->setLayout(cardDatabaseGrid);
@ -565,6 +571,7 @@ void GeneralSettingsPage::retranslateUi()
tr("Always update in the background"));
cardUpdateCheckIntervalLabel.setText(tr("Check for card database updates every"));
cardUpdateCheckIntervalSpinBox.setSuffix(tr(" days"));
cardDatabaseUpdateTrayNotificationCheckBox.setText(tr("Notify in the system tray while the card database updates"));
QDate lastCheckDate = settings.updates().getLastCardUpdateCheck();
int daysAgo = lastCheckDate.daysTo(QDate::currentDate());

View file

@ -75,6 +75,7 @@ private:
QLabel cardUpdateCheckIntervalLabel;
QSpinBox cardUpdateCheckIntervalSpinBox;
QLabel lastCardUpdateCheckDateLabel;
QCheckBox cardDatabaseUpdateTrayNotificationCheckBox;
QCheckBox showTipsOnStartup;
QLabel startupTabLabel;

View file

@ -37,6 +37,8 @@
#include "../interface/widgets/tabs/tab_supervisor.h"
#include "../main.h"
#include "abstract_client.h"
#include "card_database_update/card_database_update_status_bar.h"
#include "card_database_update/card_update_progress.h"
#include "intents/contexts/context_connect_to_server.h"
#include "intents/contexts/context_join_room.h"
#include "intents/intent.h"
@ -67,6 +69,7 @@
#include <QFileInfo>
#include <QFlags>
#include <QIODevice>
#include <QLocalSocket>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
@ -103,6 +106,7 @@
#include <libcockatrice/settings/servers_settings.h>
#include <libcockatrice/settings/tabs_settings.h>
#include <libcockatrice/settings/updates_settings.h>
#include <libcockatrice/utility/local_server_name.h>
#include <memory>
#include <qlogging.h>
#include <qnamespace.h>
@ -574,6 +578,13 @@ MainWindow::MainWindow(QWidget *parent)
connect(connectionController, &ConnectionController::pingStatsUpdated, latencyStatus,
&LatencyStatusWidget::updateData);
cardUpdateStatusBar = new CardDatabaseUpdateStatusBar(this);
statusBar()->addPermanentWidget(cardUpdateStatusBar);
connect(this, &MainWindow::cardDatabaseUpdateProgress, cardUpdateStatusBar,
&CardDatabaseUpdateStatusBar::updateProgress);
connect(this, &MainWindow::cardDatabaseUpdateFinished, cardUpdateStatusBar,
&CardDatabaseUpdateStatusBar::updateFinished);
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&MainWindow::refreshShortcuts);
refreshShortcuts();
@ -896,10 +907,15 @@ void MainWindow::closeEvent(QCloseEvent *event)
bClosingDown = true;
if (cardUpdateProcess && cardUpdateProcess->state() != QProcess::NotRunning) {
if (QMessageBox::question(this, tr("Are you sure?"),
tr("A card database update is still running. Quitting now will cancel it.\n"
"Are you sure you want to quit?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) {
const QString runningFor = cardUpdateStatusBar ? cardUpdateStatusBar->statusText() : QString();
const QString message = runningFor.isEmpty()
? tr("A card database update is still running. Quitting now will cancel it.\n"
"Are you sure you want to quit?")
: tr("A card database update is still running: %1.\nQuitting now will cancel it.\n"
"Are you sure you want to quit?")
.arg(runningFor);
if (QMessageBox::question(this, tr("Are you sure?"), message, QMessageBox::Yes | QMessageBox::No,
QMessageBox::No) == QMessageBox::No) {
event->ignore();
bClosingDown = false;
return;
@ -1116,16 +1132,28 @@ void MainWindow::actCheckCardUpdatesBackground()
void MainWindow::createCardUpdateProcess(bool background)
{
if (cardUpdateProcess) {
QMessageBox::information(this, tr("Information"), tr("A card database update is already running."));
qCDebug(CardDatabaseUpdateLog) << "A card database update is already running; asking it to come to the front";
if (requestCardUpdateRaise()) {
return;
}
if (background) {
// A hidden run has nothing to raise: keep the user informed without blocking.
announceCardUpdateRunning();
return;
}
QMessageBox::information(this, tr("Information"),
tr("The card database updater is already open.\n"
"Look for the Oracle window - it may be behind this one."));
return;
}
cardUpdateProcess = new QProcess(this);
connect(cardUpdateProcess, &QProcess::errorOccurred, this, &MainWindow::cardUpdateError);
connect(cardUpdateProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this,
&MainWindow::cardUpdateFinished);
connect(cardUpdateProcess, &QProcess::readyReadStandardOutput, this, &MainWindow::cardUpdateProgressOutput);
connect(cardUpdateProcess, &QProcess::readyReadStandardError, this, &MainWindow::cardUpdateStandardError);
// full "run the update" command; leave empty if not present
QString updaterCmd;
@ -1170,6 +1198,8 @@ void MainWindow::createCardUpdateProcess(bool background)
}
if (updaterCmd.isEmpty()) {
qCWarning(CardDatabaseUpdateLog) << "Unable to locate the card database updater; looked for"
<< dir.absoluteFilePath(binaryName);
QMessageBox::warning(this, tr("Error"),
tr("Unable to run the card database updater: ") + dir.absoluteFilePath(binaryName));
exitCardDatabaseUpdate();
@ -1177,13 +1207,24 @@ void MainWindow::createCardUpdateProcess(bool background)
return;
}
cardUpdateOutputBuffer.clear();
cardUpdateErrorOutput.clear();
cardUpdateErrorPartial.clear();
cardUpdateLoggedStage = CardUpdateStage::Unknown;
cardUpdateProgressLogTimer.start();
qCInfo(CardDatabaseUpdateLog) << "Starting the card database updater" << updaterCmd << "background:" << background;
cardUpdateStatusBar->updateStarted();
if (!background) {
cardUpdateProcess->start(updaterCmd, QStringList());
} else {
cardUpdateOutputBuffer.clear();
connect(cardUpdateProcess, &QProcess::readyReadStandardOutput, this, &MainWindow::cardUpdateProgressOutput);
cardUpdateProcess->start(updaterCmd, QStringList("-b"));
statusBar()->showMessage(tr("Card database update running."));
if (trayIcon && SettingsCache::instance().updates().getCardDatabaseUpdateTrayNotification()) {
trayIcon->showMessage(tr("Card database update started"),
tr("Cockatrice is updating the card database in the background."));
}
}
}
@ -1198,24 +1239,43 @@ void MainWindow::cardUpdateProgressOutput()
if (newline < 0) {
break;
}
const QByteArray line = cardUpdateOutputBuffer.left(newline).trimmed();
const QByteArray line = cardUpdateOutputBuffer.left(newline);
cardUpdateOutputBuffer.remove(0, newline + 1);
// Protocol emitted by `oracle -b`: "PROGRESS <stage> <done> <total>"
if (!line.startsWith("PROGRESS ")) {
// Protocol emitted by `oracle`: "PROGRESS <stage> <done> <total>"
const std::optional<CardUpdateProgress> progress = CardUpdateProgress::fromProtocolLine(line);
if (!progress.has_value()) {
continue;
}
const QList<QByteArray> parts = line.split(' ');
if (parts.size() != 4) {
continue;
// Log stage changes and, while a stage runs, one line per second at most, so the
// debug log shows liveness without flooding it.
if (progress->stage != cardUpdateLoggedStage || cardUpdateProgressLogTimer.elapsed() >= 1000) {
qCDebug(CardDatabaseUpdateLog)
<< "Card database update" << progress->stageToken() << progress->done << "/" << progress->total;
cardUpdateLoggedStage = progress->stage;
cardUpdateProgressLogTimer.restart();
}
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) {
continue;
emit cardDatabaseUpdateProgress(progress.value());
}
}
void MainWindow::cardUpdateStandardError()
{
if (!cardUpdateProcess) {
return;
}
const QByteArray chunk = cardUpdateProcess->readAllStandardError();
if (chunk.isEmpty()) {
return;
}
cardUpdateErrorOutput.append(chunk);
cardUpdateErrorPartial.append(chunk);
int newline;
while ((newline = cardUpdateErrorPartial.indexOf('\n')) >= 0) {
const QByteArray line = cardUpdateErrorPartial.left(newline).trimmed();
cardUpdateErrorPartial.remove(0, newline + 1);
if (!line.isEmpty()) {
qCWarning(CardDatabaseUpdateLog) << "Card database updater:" << line;
}
emit cardDatabaseUpdateProgress(QString::fromLatin1(parts.at(1)), done, total);
}
}
@ -1240,7 +1300,6 @@ void MainWindow::cardUpdateError(QProcess::ProcessError err)
break;
case QProcess::Crashed:
error = tr("The process crashed some time after starting successfully.");
error += "\n\nError output:\n" + cardUpdateProcess->readAllStandardError();
break;
case QProcess::Timedout:
error = tr("Timed out. The process took too long to respond. The last waitFor...() function timed out.");
@ -1259,6 +1318,12 @@ void MainWindow::cardUpdateError(QProcess::ProcessError err)
break;
}
if (!cardUpdateErrorOutput.isEmpty()) {
error += "\n\n" + tr("Error output:") + "\n" + QString::fromUtf8(cardUpdateErrorOutput);
cardUpdateErrorOutput.clear();
}
qCWarning(CardDatabaseUpdateLog) << "The card database updater exited with an error:" << error;
exitCardDatabaseUpdate();
QMessageBox::warning(this, tr("Error"), tr("The card database updater exited with an error:\n%1").arg(error));
emit cardDatabaseUpdateFinished(false);
@ -1266,16 +1331,56 @@ void MainWindow::cardUpdateError(QProcess::ProcessError err)
void MainWindow::cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
if (!cardUpdateProcess) {
return;
}
cardUpdateProgressOutput(); // drain any progress lines not yet parsed
const bool success = (exitStatus == QProcess::NormalExit) && (exitCode == 0);
if (exitStatus == QProcess::NormalExit) {
SettingsCache::instance().updates().setLastCardUpdateCheck(QDateTime::currentDateTime().date());
}
qCInfo(CardDatabaseUpdateLog) << "The card database updater finished with exit code" << exitCode << "exit status"
<< static_cast<int>(exitStatus) << "success:" << success;
exitCardDatabaseUpdate();
notifyCardUpdateFinishedFromTray(success);
emit cardDatabaseUpdateFinished(success);
}
bool MainWindow::requestCardUpdateRaise()
{
const QString serverName = scopedLocalServerName(QStringLiteral("CockatriceOracleRaise"));
QLocalSocket socket;
socket.connectToServer(serverName);
if (!socket.waitForConnected(250)) {
return false;
}
socket.write("RAISE\n");
const bool acked = socket.waitForBytesWritten(250);
socket.disconnectFromServer();
return acked;
}
void MainWindow::announceCardUpdateRunning()
{
if (SettingsCache::instance().userInterface().getShowStatusBar()) {
statusBar()->showMessage(tr("Card database update running."));
} else if (trayIcon && SettingsCache::instance().updates().getCardDatabaseUpdateTrayNotification()) {
trayIcon->showMessage(tr("Card database update running"),
tr("Cockatrice is updating the card database in the background."));
}
}
void MainWindow::notifyCardUpdateFinishedFromTray(bool success)
{
if (SettingsCache::instance().userInterface().getShowStatusBar() || !trayIcon ||
!SettingsCache::instance().updates().getCardDatabaseUpdateTrayNotification()) {
return;
}
trayIcon->showMessage(tr("Card database update finished"),
success ? tr("The card database is up to date.") : tr("The card database update failed."));
}
void MainWindow::actCheckServerUpdates()
{
auto hps = new HandlePublicServers(this);

View file

@ -27,10 +27,12 @@
#include "../client/lag_monitor.h"
#include "../client/network/update/client/release_channel.h"
#include "card_database_update/card_update_progress.h"
#include "connection_controller/remote_connection_controller.h"
#include <QByteArray>
#include <QDir>
#include <QElapsedTimer>
#include <QList>
#include <QLoggingCategory>
#include <QMainWindow>
@ -52,11 +54,13 @@ inline Q_LOGGING_CATEGORY(WindowMainStartupLog, "window_main.startup");
inline Q_LOGGING_CATEGORY(WindowMainStartupVersionLog, "window_main.startup.version");
inline Q_LOGGING_CATEGORY(WindowMainStartupShortcutsLog, "window_main.startup.shortcuts");
inline Q_LOGGING_CATEGORY(WindowMainStartupAutoconnectLog, "window_main.startup.autoconnect");
inline Q_LOGGING_CATEGORY(CardDatabaseUpdateLog, "card_database.update");
class DlgViewLog;
class GameReplay;
class LocalServer;
class LatencyStatusWidget;
class CardDatabaseUpdateStatusBar;
class RemoteClient;
class TabSupervisor;
class WndSets;
@ -68,13 +72,13 @@ class MainWindow : public QMainWindow
{
Q_OBJECT
signals:
/** @brief Emitted after the background card-database update subprocess exits. */
/** @brief Emitted after the card-database update subprocess exits. */
void cardDatabaseUpdateFinished(bool success);
/** @brief Emitted while the background card-database update subprocess runs.
* @p stage is one of "download", "scan" or "import"; @p done/@p total
* are byte counts for the first two stages and set indices for "import". */
void cardDatabaseUpdateProgress(const QString &stage, qint64 done, qint64 total);
/** @brief Emitted while the card-database update subprocess runs.
* @p progress.done/@p progress.total are byte counts for the first two stages
* and set indices for "import". */
void cardDatabaseUpdateProgress(const CardUpdateProgress &progress);
public slots:
void actCheckCardUpdates();
@ -106,6 +110,7 @@ private slots:
void cardUpdateError(QProcess::ProcessError err);
void cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatus);
void cardUpdateProgressOutput();
void cardUpdateStandardError();
void refreshShortcuts();
void cardDatabaseLoadingFailed();
void cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknownSetsNames);
@ -150,6 +155,15 @@ private:
}
void createCardUpdateProcess(bool background = false);
void exitCardDatabaseUpdate();
/**
* @brief Tries to bring the open card database updater window to the front.
* @return true when the updater acknowledged the request
*/
bool requestCardUpdateRaise();
/** @brief Tells the user an update is running without blocking, honoring a hidden status bar. */
void announceCardUpdateRunning();
/** @brief Posts a tray notification when the status bar cannot show progress. */
void notifyCardUpdateFinishedFromTray(bool success);
void startLocalGame(const LocalGameOptions &options);
@ -176,6 +190,11 @@ private:
bool firstRunWizardActive = false;
QProcess *cardUpdateProcess;
QByteArray cardUpdateOutputBuffer;
QByteArray cardUpdateErrorOutput; ///< diagnostic output collected from the updater's stderr
QByteArray cardUpdateErrorPartial; ///< incomplete trailing stderr line, kept for line-based logging
CardUpdateStage cardUpdateLoggedStage = CardUpdateStage::Unknown; ///< stage of the last logged progress update
QElapsedTimer cardUpdateProgressLogTimer; ///< throttles progress logging to the running stage
CardDatabaseUpdateStatusBar *cardUpdateStatusBar = nullptr; ///< status bar widget tracking the card database update
DlgViewLog *logviewDialog;
GameReplay *replay;
DlgTipOfTheDay *tip;