mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -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
|
|
@ -140,6 +140,10 @@ set(cockatrice_SOURCES
|
||||||
src/game_graphics/zones/view_zone.cpp
|
src/game_graphics/zones/view_zone.cpp
|
||||||
src/game_graphics/zones/view_zone_widget.cpp
|
src/game_graphics/zones/view_zone_widget.cpp
|
||||||
src/game_graphics/board/abstract_graphics_item.cpp
|
src/game_graphics/board/abstract_graphics_item.cpp
|
||||||
|
src/interface/card_database_update/card_database_update_status_bar.cpp
|
||||||
|
src/interface/card_database_update/card_database_update_status_bar.h
|
||||||
|
src/interface/card_database_update/card_update_progress.cpp
|
||||||
|
src/interface/card_database_update/card_update_progress.h
|
||||||
src/interface/card_picture_loader/card_picture_loader.cpp
|
src/interface/card_picture_loader/card_picture_loader.cpp
|
||||||
src/interface/card_picture_loader/card_picture_loader_local.cpp
|
src/interface/card_picture_loader/card_picture_loader_local.cpp
|
||||||
src/interface/card_picture_loader/card_picture_loader_request_status_display_widget.cpp
|
src/interface/card_picture_loader/card_picture_loader_request_status_display_widget.cpp
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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) {
|
if (cardDatabasePage) {
|
||||||
cardDatabasePage->onUpdateProgress(stage, done, total);
|
cardDatabasePage->onUpdateProgress(progress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
#ifndef FIRST_RUN_WIZARD_H
|
#ifndef FIRST_RUN_WIZARD_H
|
||||||
#define FIRST_RUN_WIZARD_H
|
#define FIRST_RUN_WIZARD_H
|
||||||
|
|
||||||
|
#include "../../card_database_update/card_update_progress.h"
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
@ -42,7 +44,7 @@ public slots:
|
||||||
void onCardDatabaseUpdateFinished(bool success);
|
void onCardDatabaseUpdateFinished(bool success);
|
||||||
|
|
||||||
/** @brief Forwarded from MainWindow while the background card database update process runs. */
|
/** @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:
|
protected:
|
||||||
void closeEvent(QCloseEvent *event) override;
|
void closeEvent(QCloseEvent *event) override;
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
if (state != State::Running) {
|
||||||
return;
|
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->setRange(0, total > 0 ? static_cast<int>(qMin<qint64>(total, INT_MAX)) : 0);
|
||||||
progressBar->setValue(static_cast<int>(qMin<qint64>(done, INT_MAX)));
|
progressBar->setValue(static_cast<int>(qMin<qint64>(done, INT_MAX)));
|
||||||
if (total > 0) {
|
if (total > 0) {
|
||||||
const int percent = static_cast<int>((100.0 * done) / total);
|
const int percent = static_cast<int>((100.0 * done) / total);
|
||||||
if (stage == QLatin1String("download")) {
|
switch (progress.stage) {
|
||||||
statusLabel->setText(tr("Downloading the card database (%1%)…").arg(percent));
|
case CardUpdateStage::Download:
|
||||||
} else if (stage == QLatin1String("scan")) {
|
statusLabel->setText(tr("Downloading the card database (%1%)…").arg(percent));
|
||||||
statusLabel->setText(tr("Parsing the card database (%1%)…").arg(percent));
|
break;
|
||||||
} else if (stage == QLatin1String("import")) {
|
case CardUpdateStage::Scan:
|
||||||
statusLabel->setText(tr("Importing cards (%1%)…").arg(percent));
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#ifndef CARD_DATABASE_SETUP_PAGE_H
|
#ifndef CARD_DATABASE_SETUP_PAGE_H
|
||||||
#define 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 "../first_run_wizard_page.h"
|
||||||
|
|
||||||
#include <QSize>
|
#include <QSize>
|
||||||
|
|
@ -33,7 +34,7 @@ public:
|
||||||
void retranslateUi() override;
|
void retranslateUi() override;
|
||||||
|
|
||||||
void onUpdateFinished(bool success);
|
void onUpdateFinished(bool success);
|
||||||
void onUpdateProgress(const QString &stage, qint64 done, qint64 total);
|
void onUpdateProgress(const CardUpdateProgress &progress);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void updateRequested();
|
void updateRequested();
|
||||||
|
|
|
||||||
|
|
@ -161,12 +161,18 @@ GeneralSettingsPage::GeneralSettingsPage()
|
||||||
|
|
||||||
newVersionOracleCheckBox.setChecked(settings.updates().getNotifyAboutNewVersion());
|
newVersionOracleCheckBox.setChecked(settings.updates().getNotifyAboutNewVersion());
|
||||||
|
|
||||||
|
cardDatabaseUpdateTrayNotificationCheckBox.setChecked(settings.updates().getCardDatabaseUpdateTrayNotification());
|
||||||
|
|
||||||
|
connect(&cardDatabaseUpdateTrayNotificationCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings.updates(),
|
||||||
|
&UpdatesSettings::setCardDatabaseUpdateTrayNotification);
|
||||||
|
|
||||||
auto *cardDatabaseGrid = new QGridLayout;
|
auto *cardDatabaseGrid = new QGridLayout;
|
||||||
cardDatabaseGrid->addWidget(&startupCardUpdateCheckBehaviorLabel, 0, 0);
|
cardDatabaseGrid->addWidget(&startupCardUpdateCheckBehaviorLabel, 0, 0);
|
||||||
cardDatabaseGrid->addWidget(&startupCardUpdateCheckBehaviorSelector, 0, 1);
|
cardDatabaseGrid->addWidget(&startupCardUpdateCheckBehaviorSelector, 0, 1);
|
||||||
cardDatabaseGrid->addWidget(&cardUpdateCheckIntervalLabel, 1, 0);
|
cardDatabaseGrid->addWidget(&cardUpdateCheckIntervalLabel, 1, 0);
|
||||||
cardDatabaseGrid->addWidget(&cardUpdateCheckIntervalSpinBox, 1, 1);
|
cardDatabaseGrid->addWidget(&cardUpdateCheckIntervalSpinBox, 1, 1);
|
||||||
cardDatabaseGrid->addWidget(&lastCardUpdateCheckDateLabel, 2, 1);
|
cardDatabaseGrid->addWidget(&lastCardUpdateCheckDateLabel, 2, 1);
|
||||||
|
cardDatabaseGrid->addWidget(&cardDatabaseUpdateTrayNotificationCheckBox, 3, 0, 1, 2);
|
||||||
|
|
||||||
cardDatabaseGroupBox = new QGroupBox;
|
cardDatabaseGroupBox = new QGroupBox;
|
||||||
cardDatabaseGroupBox->setLayout(cardDatabaseGrid);
|
cardDatabaseGroupBox->setLayout(cardDatabaseGrid);
|
||||||
|
|
@ -565,6 +571,7 @@ void GeneralSettingsPage::retranslateUi()
|
||||||
tr("Always update in the background"));
|
tr("Always update in the background"));
|
||||||
cardUpdateCheckIntervalLabel.setText(tr("Check for card database updates every"));
|
cardUpdateCheckIntervalLabel.setText(tr("Check for card database updates every"));
|
||||||
cardUpdateCheckIntervalSpinBox.setSuffix(tr(" days"));
|
cardUpdateCheckIntervalSpinBox.setSuffix(tr(" days"));
|
||||||
|
cardDatabaseUpdateTrayNotificationCheckBox.setText(tr("Notify in the system tray while the card database updates"));
|
||||||
|
|
||||||
QDate lastCheckDate = settings.updates().getLastCardUpdateCheck();
|
QDate lastCheckDate = settings.updates().getLastCardUpdateCheck();
|
||||||
int daysAgo = lastCheckDate.daysTo(QDate::currentDate());
|
int daysAgo = lastCheckDate.daysTo(QDate::currentDate());
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ private:
|
||||||
QLabel cardUpdateCheckIntervalLabel;
|
QLabel cardUpdateCheckIntervalLabel;
|
||||||
QSpinBox cardUpdateCheckIntervalSpinBox;
|
QSpinBox cardUpdateCheckIntervalSpinBox;
|
||||||
QLabel lastCardUpdateCheckDateLabel;
|
QLabel lastCardUpdateCheckDateLabel;
|
||||||
|
QCheckBox cardDatabaseUpdateTrayNotificationCheckBox;
|
||||||
|
|
||||||
QCheckBox showTipsOnStartup;
|
QCheckBox showTipsOnStartup;
|
||||||
QLabel startupTabLabel;
|
QLabel startupTabLabel;
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@
|
||||||
#include "../interface/widgets/tabs/tab_supervisor.h"
|
#include "../interface/widgets/tabs/tab_supervisor.h"
|
||||||
#include "../main.h"
|
#include "../main.h"
|
||||||
#include "abstract_client.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_connect_to_server.h"
|
||||||
#include "intents/contexts/context_join_room.h"
|
#include "intents/contexts/context_join_room.h"
|
||||||
#include "intents/intent.h"
|
#include "intents/intent.h"
|
||||||
|
|
@ -67,6 +69,7 @@
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QFlags>
|
#include <QFlags>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
|
#include <QLocalSocket>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QMenuBar>
|
#include <QMenuBar>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
|
@ -103,6 +106,7 @@
|
||||||
#include <libcockatrice/settings/servers_settings.h>
|
#include <libcockatrice/settings/servers_settings.h>
|
||||||
#include <libcockatrice/settings/tabs_settings.h>
|
#include <libcockatrice/settings/tabs_settings.h>
|
||||||
#include <libcockatrice/settings/updates_settings.h>
|
#include <libcockatrice/settings/updates_settings.h>
|
||||||
|
#include <libcockatrice/utility/local_server_name.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qnamespace.h>
|
#include <qnamespace.h>
|
||||||
|
|
@ -574,6 +578,13 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
connect(connectionController, &ConnectionController::pingStatsUpdated, latencyStatus,
|
connect(connectionController, &ConnectionController::pingStatsUpdated, latencyStatus,
|
||||||
&LatencyStatusWidget::updateData);
|
&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,
|
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
|
||||||
&MainWindow::refreshShortcuts);
|
&MainWindow::refreshShortcuts);
|
||||||
refreshShortcuts();
|
refreshShortcuts();
|
||||||
|
|
@ -896,10 +907,15 @@ void MainWindow::closeEvent(QCloseEvent *event)
|
||||||
bClosingDown = true;
|
bClosingDown = true;
|
||||||
|
|
||||||
if (cardUpdateProcess && cardUpdateProcess->state() != QProcess::NotRunning) {
|
if (cardUpdateProcess && cardUpdateProcess->state() != QProcess::NotRunning) {
|
||||||
if (QMessageBox::question(this, tr("Are you sure?"),
|
const QString runningFor = cardUpdateStatusBar ? cardUpdateStatusBar->statusText() : QString();
|
||||||
tr("A card database update is still running. Quitting now will cancel it.\n"
|
const QString message = runningFor.isEmpty()
|
||||||
"Are you sure you want to quit?"),
|
? tr("A card database update is still running. Quitting now will cancel it.\n"
|
||||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) {
|
"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();
|
event->ignore();
|
||||||
bClosingDown = false;
|
bClosingDown = false;
|
||||||
return;
|
return;
|
||||||
|
|
@ -1116,16 +1132,28 @@ void MainWindow::actCheckCardUpdatesBackground()
|
||||||
void MainWindow::createCardUpdateProcess(bool background)
|
void MainWindow::createCardUpdateProcess(bool background)
|
||||||
{
|
{
|
||||||
if (cardUpdateProcess) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cardUpdateProcess = new QProcess(this);
|
cardUpdateProcess = new QProcess(this);
|
||||||
|
|
||||||
connect(cardUpdateProcess, &QProcess::errorOccurred, this, &MainWindow::cardUpdateError);
|
connect(cardUpdateProcess, &QProcess::errorOccurred, this, &MainWindow::cardUpdateError);
|
||||||
|
|
||||||
connect(cardUpdateProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this,
|
connect(cardUpdateProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this,
|
||||||
&MainWindow::cardUpdateFinished);
|
&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
|
// full "run the update" command; leave empty if not present
|
||||||
QString updaterCmd;
|
QString updaterCmd;
|
||||||
|
|
@ -1170,6 +1198,8 @@ void MainWindow::createCardUpdateProcess(bool background)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updaterCmd.isEmpty()) {
|
if (updaterCmd.isEmpty()) {
|
||||||
|
qCWarning(CardDatabaseUpdateLog) << "Unable to locate the card database updater; looked for"
|
||||||
|
<< dir.absoluteFilePath(binaryName);
|
||||||
QMessageBox::warning(this, tr("Error"),
|
QMessageBox::warning(this, tr("Error"),
|
||||||
tr("Unable to run the card database updater: ") + dir.absoluteFilePath(binaryName));
|
tr("Unable to run the card database updater: ") + dir.absoluteFilePath(binaryName));
|
||||||
exitCardDatabaseUpdate();
|
exitCardDatabaseUpdate();
|
||||||
|
|
@ -1177,13 +1207,24 @@ void MainWindow::createCardUpdateProcess(bool background)
|
||||||
return;
|
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) {
|
if (!background) {
|
||||||
cardUpdateProcess->start(updaterCmd, QStringList());
|
cardUpdateProcess->start(updaterCmd, QStringList());
|
||||||
} else {
|
} else {
|
||||||
cardUpdateOutputBuffer.clear();
|
|
||||||
connect(cardUpdateProcess, &QProcess::readyReadStandardOutput, this, &MainWindow::cardUpdateProgressOutput);
|
|
||||||
cardUpdateProcess->start(updaterCmd, QStringList("-b"));
|
cardUpdateProcess->start(updaterCmd, QStringList("-b"));
|
||||||
statusBar()->showMessage(tr("Card database update running."));
|
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) {
|
if (newline < 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const QByteArray line = cardUpdateOutputBuffer.left(newline).trimmed();
|
const QByteArray line = cardUpdateOutputBuffer.left(newline);
|
||||||
cardUpdateOutputBuffer.remove(0, newline + 1);
|
cardUpdateOutputBuffer.remove(0, newline + 1);
|
||||||
// Protocol emitted by `oracle -b`: "PROGRESS <stage> <done> <total>"
|
// Protocol emitted by `oracle`: "PROGRESS <stage> <done> <total>"
|
||||||
if (!line.startsWith("PROGRESS ")) {
|
const std::optional<CardUpdateProgress> progress = CardUpdateProgress::fromProtocolLine(line);
|
||||||
|
if (!progress.has_value()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const QList<QByteArray> parts = line.split(' ');
|
// Log stage changes and, while a stage runs, one line per second at most, so the
|
||||||
if (parts.size() != 4) {
|
// debug log shows liveness without flooding it.
|
||||||
continue;
|
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;
|
emit cardDatabaseUpdateProgress(progress.value());
|
||||||
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) {
|
void MainWindow::cardUpdateStandardError()
|
||||||
continue;
|
{
|
||||||
|
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;
|
break;
|
||||||
case QProcess::Crashed:
|
case QProcess::Crashed:
|
||||||
error = tr("The process crashed some time after starting successfully.");
|
error = tr("The process crashed some time after starting successfully.");
|
||||||
error += "\n\nError output:\n" + cardUpdateProcess->readAllStandardError();
|
|
||||||
break;
|
break;
|
||||||
case QProcess::Timedout:
|
case QProcess::Timedout:
|
||||||
error = tr("Timed out. The process took too long to respond. The last waitFor...() function timed out.");
|
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;
|
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();
|
exitCardDatabaseUpdate();
|
||||||
QMessageBox::warning(this, tr("Error"), tr("The card database updater exited with an error:\n%1").arg(error));
|
QMessageBox::warning(this, tr("Error"), tr("The card database updater exited with an error:\n%1").arg(error));
|
||||||
emit cardDatabaseUpdateFinished(false);
|
emit cardDatabaseUpdateFinished(false);
|
||||||
|
|
@ -1266,16 +1331,56 @@ void MainWindow::cardUpdateError(QProcess::ProcessError err)
|
||||||
|
|
||||||
void MainWindow::cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
void MainWindow::cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
||||||
{
|
{
|
||||||
|
if (!cardUpdateProcess) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
cardUpdateProgressOutput(); // drain any progress lines not yet parsed
|
cardUpdateProgressOutput(); // drain any progress lines not yet parsed
|
||||||
|
|
||||||
const bool success = (exitStatus == QProcess::NormalExit) && (exitCode == 0);
|
const bool success = (exitStatus == QProcess::NormalExit) && (exitCode == 0);
|
||||||
if (exitStatus == QProcess::NormalExit) {
|
if (exitStatus == QProcess::NormalExit) {
|
||||||
SettingsCache::instance().updates().setLastCardUpdateCheck(QDateTime::currentDateTime().date());
|
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();
|
exitCardDatabaseUpdate();
|
||||||
|
notifyCardUpdateFinishedFromTray(success);
|
||||||
emit cardDatabaseUpdateFinished(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()
|
void MainWindow::actCheckServerUpdates()
|
||||||
{
|
{
|
||||||
auto hps = new HandlePublicServers(this);
|
auto hps = new HandlePublicServers(this);
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,12 @@
|
||||||
|
|
||||||
#include "../client/lag_monitor.h"
|
#include "../client/lag_monitor.h"
|
||||||
#include "../client/network/update/client/release_channel.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 "connection_controller/remote_connection_controller.h"
|
||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QElapsedTimer>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
#include <QMainWindow>
|
#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(WindowMainStartupVersionLog, "window_main.startup.version");
|
||||||
inline Q_LOGGING_CATEGORY(WindowMainStartupShortcutsLog, "window_main.startup.shortcuts");
|
inline Q_LOGGING_CATEGORY(WindowMainStartupShortcutsLog, "window_main.startup.shortcuts");
|
||||||
inline Q_LOGGING_CATEGORY(WindowMainStartupAutoconnectLog, "window_main.startup.autoconnect");
|
inline Q_LOGGING_CATEGORY(WindowMainStartupAutoconnectLog, "window_main.startup.autoconnect");
|
||||||
|
inline Q_LOGGING_CATEGORY(CardDatabaseUpdateLog, "card_database.update");
|
||||||
|
|
||||||
class DlgViewLog;
|
class DlgViewLog;
|
||||||
class GameReplay;
|
class GameReplay;
|
||||||
class LocalServer;
|
class LocalServer;
|
||||||
class LatencyStatusWidget;
|
class LatencyStatusWidget;
|
||||||
|
class CardDatabaseUpdateStatusBar;
|
||||||
class RemoteClient;
|
class RemoteClient;
|
||||||
class TabSupervisor;
|
class TabSupervisor;
|
||||||
class WndSets;
|
class WndSets;
|
||||||
|
|
@ -68,13 +72,13 @@ class MainWindow : public QMainWindow
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
signals:
|
signals:
|
||||||
/** @brief Emitted after the background card-database update subprocess exits. */
|
/** @brief Emitted after the card-database update subprocess exits. */
|
||||||
void cardDatabaseUpdateFinished(bool success);
|
void cardDatabaseUpdateFinished(bool success);
|
||||||
|
|
||||||
/** @brief Emitted while the background card-database update subprocess runs.
|
/** @brief Emitted while the card-database update subprocess runs.
|
||||||
* @p stage is one of "download", "scan" or "import"; @p done/@p total
|
* @p progress.done/@p progress.total are byte counts for the first two stages
|
||||||
* are byte counts for the first two stages and set indices for "import". */
|
* and set indices for "import". */
|
||||||
void cardDatabaseUpdateProgress(const QString &stage, qint64 done, qint64 total);
|
void cardDatabaseUpdateProgress(const CardUpdateProgress &progress);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void actCheckCardUpdates();
|
void actCheckCardUpdates();
|
||||||
|
|
@ -106,6 +110,7 @@ private slots:
|
||||||
void cardUpdateError(QProcess::ProcessError err);
|
void cardUpdateError(QProcess::ProcessError err);
|
||||||
void cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
void cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||||
void cardUpdateProgressOutput();
|
void cardUpdateProgressOutput();
|
||||||
|
void cardUpdateStandardError();
|
||||||
void refreshShortcuts();
|
void refreshShortcuts();
|
||||||
void cardDatabaseLoadingFailed();
|
void cardDatabaseLoadingFailed();
|
||||||
void cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknownSetsNames);
|
void cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknownSetsNames);
|
||||||
|
|
@ -150,6 +155,15 @@ private:
|
||||||
}
|
}
|
||||||
void createCardUpdateProcess(bool background = false);
|
void createCardUpdateProcess(bool background = false);
|
||||||
void exitCardDatabaseUpdate();
|
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);
|
void startLocalGame(const LocalGameOptions &options);
|
||||||
|
|
||||||
|
|
@ -176,6 +190,11 @@ private:
|
||||||
bool firstRunWizardActive = false;
|
bool firstRunWizardActive = false;
|
||||||
QProcess *cardUpdateProcess;
|
QProcess *cardUpdateProcess;
|
||||||
QByteArray cardUpdateOutputBuffer;
|
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;
|
DlgViewLog *logviewDialog;
|
||||||
GameReplay *replay;
|
GameReplay *replay;
|
||||||
DlgTipOfTheDay *tip;
|
DlgTipOfTheDay *tip;
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,14 @@
|
||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
#include <QDir>
|
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QLocalServer>
|
#include <QLocalServer>
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QStringLiteral>
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
#include <libcockatrice/utility/local_server_name.h>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
@ -26,17 +25,9 @@ SingleInstanceManager::SingleInstanceManager(QObject *parent) : QObject(parent)
|
||||||
|
|
||||||
bool SingleInstanceManager::tryRun(const QStringList &filesToSend)
|
bool SingleInstanceManager::tryRun(const QStringList &filesToSend)
|
||||||
{
|
{
|
||||||
// Scope the socket name to the current user. On Linux the default abstract
|
// Scope the socket name to the current user so one user's instance cannot
|
||||||
// namespace is system-wide, so a plain name would let one user's instance
|
|
||||||
// hijack another user's session.
|
// hijack another user's session.
|
||||||
QString userName = qEnvironmentVariable("USER");
|
serverName = scopedLocalServerName(QStringLiteral("CockatriceSingleInstance"));
|
||||||
if (userName.isEmpty()) {
|
|
||||||
userName = qEnvironmentVariable("USERNAME");
|
|
||||||
}
|
|
||||||
if (userName.isEmpty()) {
|
|
||||||
userName = QDir::home().dirName();
|
|
||||||
}
|
|
||||||
serverName = QStringLiteral("CockatriceSingleInstance-%1").arg(userName);
|
|
||||||
|
|
||||||
// Hand off to an already-running primary instance if one exists. Never steal
|
// Hand off to an already-running primary instance if one exists. Never steal
|
||||||
// the socket of a busy primary: it is alive and will act on the payload.
|
// the socket of a busy primary: it is alive and will act on the payload.
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,7 @@ suffix, e.g. `card_picture_loader.*`, to include all sub categories).
|
||||||
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||||
| Card picture loading (URLs, local file hits/misses, downloads, redirects) | `card_picture_loader.*` |
|
| Card picture loading (URLs, local file hits/misses, downloads, redirects) | `card_picture_loader.*` |
|
||||||
| Card database loading and parsing | `card_database`, `card_database.loading`, `card_database.loading.success_or_failure`, `cockatrice_xml.*` |
|
| Card database loading and parsing | `card_database`, `card_database.loading`, `card_database.loading.success_or_failure`, `cockatrice_xml.*` |
|
||||||
|
| Card database update (running Oracle, progress, exit status, errors) | `card_database.update` |
|
||||||
| Card, set, and deck information | `card_info`, `card_list`, `deck_loader` |
|
| Card, set, and deck information | `card_info`, `card_list`, `deck_loader` |
|
||||||
| Startup sequence and update checks | `window_main.startup.*`, `release_channel`, `spoiler_background_updater` |
|
| Startup sequence and update checks | `window_main.startup.*`, `release_channel`, `spoiler_background_updater` |
|
||||||
| User interface and themes | `theme_manager`, `sound_engine`, `flow_layout`, `flow_widget.*`, `pixel_map_generator`, `card_info_picture_widget` |
|
| User interface and themes | `theme_manager`, `sound_engine`, `flow_layout`, `flow_widget.*`, `pixel_map_generator`, `card_info_picture_widget` |
|
||||||
|
|
@ -128,6 +129,7 @@ For example, to investigate why a card database update seems to fail, enable the
|
||||||
card_database = true
|
card_database = true
|
||||||
card_database.loading = true
|
card_database.loading = true
|
||||||
card_database.loading.success_or_failure = true
|
card_database.loading.success_or_failure = true
|
||||||
|
card_database.update = true
|
||||||
cockatrice_xml.* = true
|
cockatrice_xml.* = true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,11 @@ bool UpdatesSettings::getNotifyAboutNewVersion() const
|
||||||
return getValue("newVersionNotification", QString(), QString(), true).toBool();
|
return getValue("newVersionNotification", QString(), QString(), true).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UpdatesSettings::getCardDatabaseUpdateTrayNotification() const
|
||||||
|
{
|
||||||
|
return getValue("cardDatabaseUpdateTrayNotification", QString(), QString(), true).toBool();
|
||||||
|
}
|
||||||
|
|
||||||
int UpdatesSettings::getUpdateReleaseChannelIndex() const
|
int UpdatesSettings::getUpdateReleaseChannelIndex() const
|
||||||
{
|
{
|
||||||
return getValue("updateReleaseChannel", QString(), QString(), 0).toInt();
|
return getValue("updateReleaseChannel", QString(), QString(), 0).toInt();
|
||||||
|
|
@ -105,6 +110,11 @@ void UpdatesSettings::setNotifyAboutNewVersion(bool _notifyaboutnewversion)
|
||||||
setValue(_notifyaboutnewversion, "newVersionNotification");
|
setValue(_notifyaboutnewversion, "newVersionNotification");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UpdatesSettings::setCardDatabaseUpdateTrayNotification(bool value)
|
||||||
|
{
|
||||||
|
setValue(value, "cardDatabaseUpdateTrayNotification");
|
||||||
|
}
|
||||||
|
|
||||||
void UpdatesSettings::setUpdateReleaseChannelIndex(int value)
|
void UpdatesSettings::setUpdateReleaseChannelIndex(int value)
|
||||||
{
|
{
|
||||||
setValue(value, "updateReleaseChannel");
|
setValue(value, "updateReleaseChannel");
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ public:
|
||||||
[[nodiscard]] bool getAlwaysEnableNewSets() const override;
|
[[nodiscard]] bool getAlwaysEnableNewSets() const override;
|
||||||
[[nodiscard]] bool getNotifyAboutUpdates() const override;
|
[[nodiscard]] bool getNotifyAboutUpdates() const override;
|
||||||
[[nodiscard]] bool getNotifyAboutNewVersion() const override;
|
[[nodiscard]] bool getNotifyAboutNewVersion() const override;
|
||||||
|
[[nodiscard]] bool getCardDatabaseUpdateTrayNotification() const;
|
||||||
[[nodiscard]] int getUpdateReleaseChannelIndex() const override;
|
[[nodiscard]] int getUpdateReleaseChannelIndex() const override;
|
||||||
|
|
||||||
void setCheckUpdatesOnStartup(bool value);
|
void setCheckUpdatesOnStartup(bool value);
|
||||||
|
|
@ -35,6 +36,7 @@ public:
|
||||||
void setAlwaysEnableNewSets(bool value);
|
void setAlwaysEnableNewSets(bool value);
|
||||||
void setNotifyAboutUpdates(bool _notifyaboutupdate);
|
void setNotifyAboutUpdates(bool _notifyaboutupdate);
|
||||||
void setNotifyAboutNewVersion(bool _notifyaboutnewversion);
|
void setNotifyAboutNewVersion(bool _notifyaboutnewversion);
|
||||||
|
void setCardDatabaseUpdateTrayNotification(bool value);
|
||||||
void setUpdateReleaseChannelIndex(int value);
|
void setUpdateReleaseChannelIndex(int value);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ set(UTILITY_HEADERS
|
||||||
libcockatrice/utility/cryptoutil.h
|
libcockatrice/utility/cryptoutil.h
|
||||||
libcockatrice/utility/expression.h
|
libcockatrice/utility/expression.h
|
||||||
libcockatrice/utility/levenshtein.h
|
libcockatrice/utility/levenshtein.h
|
||||||
|
libcockatrice/utility/local_server_name.h
|
||||||
libcockatrice/utility/macros.h
|
libcockatrice/utility/macros.h
|
||||||
libcockatrice/utility/passwordhasher.h
|
libcockatrice/utility/passwordhasher.h
|
||||||
libcockatrice/utility/playmat_params.h
|
libcockatrice/utility/playmat_params.h
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
#ifndef COCKATRICE_LOCAL_SERVER_NAME_H
|
||||||
|
#define COCKATRICE_LOCAL_SERVER_NAME_H
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QString>
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Builds a QLocalServer/QLocalSocket name scoped to the current user.
|
||||||
|
*
|
||||||
|
* On Linux the default local socket namespace is system-wide, so an unscoped name
|
||||||
|
* would let one user's process talk to — or hijack — another user's session.
|
||||||
|
* @param prefix application specific socket name prefix
|
||||||
|
* @return the scoped socket name
|
||||||
|
*/
|
||||||
|
inline QString scopedLocalServerName(const QString &prefix)
|
||||||
|
{
|
||||||
|
QString userName = qEnvironmentVariable("USER");
|
||||||
|
if (userName.isEmpty()) {
|
||||||
|
userName = qEnvironmentVariable("USERNAME");
|
||||||
|
}
|
||||||
|
if (userName.isEmpty()) {
|
||||||
|
userName = QDir::home().dirName();
|
||||||
|
}
|
||||||
|
return prefix + QLatin1Char('-') + userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // COCKATRICE_LOCAL_SERVER_NAME_H
|
||||||
|
|
@ -185,9 +185,10 @@ private:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether readSetsFromByteArray() should report scan progress via
|
* Whether readSetsFromByteArray() should report scan progress via
|
||||||
* dataReadProgress. A background run routes that signal to stdout (for the
|
* dataReadProgress. The wizard streams that signal to stdout (for the
|
||||||
* hosting Cockatrice client to parse); the flag exists to skip the scanner
|
* hosting Cockatrice client to parse) in both interactive and background
|
||||||
* instrumentation entirely when no consumer needs it.
|
* runs; the flag exists to skip the scanner instrumentation entirely when
|
||||||
|
* no consumer needs it.
|
||||||
*/
|
*/
|
||||||
bool progressReporting = true;
|
bool progressReporting = true;
|
||||||
|
|
||||||
|
|
@ -255,9 +256,9 @@ public:
|
||||||
* @brief Controls whether readSetsFromByteArray() instruments the raw scan.
|
* @brief Controls whether readSetsFromByteArray() instruments the raw scan.
|
||||||
*
|
*
|
||||||
* When enabled (the default) the raw scanner reports progress via
|
* When enabled (the default) the raw scanner reports progress via
|
||||||
* dataReadProgress(), which an interactive wizard shows on its progress bar
|
* dataReadProgress(), which the wizard streams to stdout (so the hosting
|
||||||
* and a background run routes to stdout for the hosting client. Switch it
|
* Cockatrice client can show it) and also displays on its own progress bar.
|
||||||
* off only when nothing will consume scan progress.
|
* Switch it off only when nothing will consume scan progress.
|
||||||
*/
|
*/
|
||||||
void setProgressReporting(bool enabled)
|
void setProgressReporting(bool enabled)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,17 @@
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
#include <QLocalServer>
|
||||||
|
#include <QLocalSocket>
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
#include <QWindow>
|
||||||
#include <QWizardPage>
|
#include <QWizardPage>
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <libcockatrice/settings/cards_display_settings.h>
|
#include <libcockatrice/settings/cards_display_settings.h>
|
||||||
#include <libcockatrice/settings/personal_settings.h>
|
#include <libcockatrice/settings/personal_settings.h>
|
||||||
|
#include <libcockatrice/utility/local_server_name.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
|
|
||||||
class QWidget;
|
class QWidget;
|
||||||
|
|
@ -71,6 +75,7 @@ OracleWizard::OracleWizard(QWidget *parent) : QWizard(parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
retranslateUi();
|
retranslateUi();
|
||||||
|
startRaiseServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -139,6 +144,54 @@ void OracleWizard::runInBackground()
|
||||||
currentPage()->initializePage();
|
currentPage()->initializePage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OracleWizard::startRaiseServer()
|
||||||
|
{
|
||||||
|
// Scope the socket name to the current user, mirroring the hosting client.
|
||||||
|
raiseServer = new QLocalServer(this);
|
||||||
|
connect(raiseServer, &QLocalServer::newConnection, this, &OracleWizard::handleRaiseRequest);
|
||||||
|
const QString serverName = scopedLocalServerName(QStringLiteral("CockatriceOracleRaise"));
|
||||||
|
if (raiseServer->listen(serverName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// A stale socket left behind by a crashed run holds the name: take it back.
|
||||||
|
QLocalServer::removeServer(serverName);
|
||||||
|
if (!raiseServer->listen(serverName)) {
|
||||||
|
qWarning() << "Oracle: could not listen for raise requests on" << serverName;
|
||||||
|
delete raiseServer;
|
||||||
|
raiseServer = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OracleWizard::handleRaiseRequest()
|
||||||
|
{
|
||||||
|
while (raiseServer && raiseServer->hasPendingConnections()) {
|
||||||
|
QLocalSocket *socket = raiseServer->nextPendingConnection();
|
||||||
|
connect(socket, &QLocalSocket::readyRead, this, [this, socket]() {
|
||||||
|
socket->readAll();
|
||||||
|
socket->deleteLater();
|
||||||
|
raiseWizard();
|
||||||
|
});
|
||||||
|
connect(socket, &QLocalSocket::disconnected, socket, &QLocalSocket::deleteLater);
|
||||||
|
}
|
||||||
|
raiseWizard();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OracleWizard::raiseWizard()
|
||||||
|
{
|
||||||
|
// An ongoing background run is hidden; the user asked to see it, so promote it to a window.
|
||||||
|
if (backgroundMode && !isVisible()) {
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
if (isMinimized()) {
|
||||||
|
showNormal();
|
||||||
|
}
|
||||||
|
raise();
|
||||||
|
activateWindow();
|
||||||
|
if (windowHandle()) {
|
||||||
|
windowHandle()->requestActivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void OracleWizard::enableButtons()
|
void OracleWizard::enableButtons()
|
||||||
{
|
{
|
||||||
button(QWizard::NextButton)->setDisabled(false);
|
button(QWizard::NextButton)->setDisabled(false);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
class QNetworkAccessManager;
|
class QNetworkAccessManager;
|
||||||
class OracleImporter;
|
class OracleImporter;
|
||||||
|
class QLocalServer;
|
||||||
class QSettings;
|
class QSettings;
|
||||||
class QWidget;
|
class QWidget;
|
||||||
|
|
||||||
|
|
@ -60,6 +61,7 @@ public:
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void updateLanguage();
|
void updateLanguage();
|
||||||
|
void handleRaiseRequest();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QByteArray tokensData;
|
QByteArray tokensData;
|
||||||
|
|
@ -67,6 +69,13 @@ private:
|
||||||
QString cardSourceVersion;
|
QString cardSourceVersion;
|
||||||
|
|
||||||
void migrateOracleSettings();
|
void migrateOracleSettings();
|
||||||
|
/**
|
||||||
|
* @brief Starts the local socket the hosting Cockatrice client uses to bring this wizard forward.
|
||||||
|
*/
|
||||||
|
void startRaiseServer();
|
||||||
|
/** @brief Shows, raises and activates the wizard window. */
|
||||||
|
void raiseWizard();
|
||||||
|
QLocalServer *raiseServer = nullptr; ///< listens for raise requests from the hosting client
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void changeEvent(QEvent *event) override;
|
void changeEvent(QEvent *event) override;
|
||||||
|
|
|
||||||
|
|
@ -82,13 +82,12 @@ class QWidget;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Emits one machine-readable background-run progress line to stdout.
|
* @brief Emits one machine-readable progress line to stdout.
|
||||||
*
|
*
|
||||||
* Used only in background mode, so the hosting Cockatrice client can parse these
|
* The hosting Cockatrice client parses these lines to drive a determinate
|
||||||
* lines to drive a determinate progress bar. stderr stays reserved for
|
* progress bar. stderr stays reserved for human-readable log output.
|
||||||
* human-readable log output.
|
|
||||||
*/
|
*/
|
||||||
static void emitBackgroundProgress(const char *stage, qint64 done, qint64 total)
|
static void emitProgress(const char *stage, qint64 done, qint64 total)
|
||||||
{
|
{
|
||||||
QTextStream out(stdout);
|
QTextStream out(stdout);
|
||||||
out << "PROGRESS " << stage << ' ' << done << ' ' << total << '\n';
|
out << "PROGRESS " << stage << ' ' << done << ' ' << total << '\n';
|
||||||
|
|
@ -473,9 +472,7 @@ void LoadSetsPage::downloadSetsFile(const QUrl &url)
|
||||||
|
|
||||||
void LoadSetsPage::actDownloadProgressSetsFile(qint64 received, qint64 total)
|
void LoadSetsPage::actDownloadProgressSetsFile(qint64 received, qint64 total)
|
||||||
{
|
{
|
||||||
if (wizard()->backgroundMode) {
|
emitProgress("download", received, total);
|
||||||
emitBackgroundProgress("download", received, total);
|
|
||||||
}
|
|
||||||
if (total > 0) {
|
if (total > 0) {
|
||||||
progressBar->setMaximum(static_cast<int>(total));
|
progressBar->setMaximum(static_cast<int>(total));
|
||||||
progressBar->setValue(static_cast<int>(received));
|
progressBar->setValue(static_cast<int>(received));
|
||||||
|
|
@ -534,7 +531,7 @@ void LoadSetsPage::updateParsingProgress(int bytesRead, int totalBytes)
|
||||||
|
|
||||||
void LoadSetsPage::scanProgressToStdout(int bytesRead, int totalBytes)
|
void LoadSetsPage::scanProgressToStdout(int bytesRead, int totalBytes)
|
||||||
{
|
{
|
||||||
emitBackgroundProgress("scan", bytesRead, totalBytes);
|
emitProgress("scan", bytesRead, totalBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadSetsPage::beginLoadSets(bool compressedFile)
|
void LoadSetsPage::beginLoadSets(bool compressedFile)
|
||||||
|
|
@ -841,18 +838,18 @@ void SaveSetsPage::updateTotalProgress(int cardsImported, int setIndex, const QS
|
||||||
const int cardCount = wizard()->importer->getCardList().size();
|
const int cardCount = wizard()->importer->getCardList().size();
|
||||||
if (wizard()->backgroundMode) {
|
if (wizard()->backgroundMode) {
|
||||||
qInfo() << tr("Import finished: %1 cards.").arg(cardCount);
|
qInfo() << tr("Import finished: %1 cards.").arg(cardCount);
|
||||||
emitBackgroundProgress("import", totalSets, totalSets);
|
|
||||||
} else {
|
} else {
|
||||||
messageLog->append("<b>" + tr("Import finished: %1 cards.").arg(cardCount) + "</b>");
|
messageLog->append("<b>" + tr("Import finished: %1 cards.").arg(cardCount) + "</b>");
|
||||||
}
|
}
|
||||||
|
emitProgress("import", totalSets, totalSets);
|
||||||
} else {
|
} else {
|
||||||
progressBar->setValue(setIndex);
|
progressBar->setValue(setIndex);
|
||||||
if (wizard()->backgroundMode) {
|
if (wizard()->backgroundMode) {
|
||||||
qInfo() << tr("%1: %2 cards imported").arg(setName).arg(cardsImported);
|
qInfo() << tr("%1: %2 cards imported").arg(setName).arg(cardsImported);
|
||||||
emitBackgroundProgress("import", setIndex, totalSets);
|
|
||||||
} else {
|
} else {
|
||||||
messageLog->append(tr("%1: %2 cards imported").arg(setName).arg(cardsImported));
|
messageLog->append(tr("%1: %2 cards imported").arg(setName).arg(cardsImported));
|
||||||
}
|
}
|
||||||
|
emitProgress("import", setIndex, totalSets);
|
||||||
}
|
}
|
||||||
|
|
||||||
messageLog->verticalScrollBar()->setValue(messageLog->verticalScrollBar()->maximum());
|
messageLog->verticalScrollBar()->setValue(messageLog->verticalScrollBar()->maximum());
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ add_test(NAME report_categories_test COMMAND report_categories_test)
|
||||||
add_test(NAME lag_monitor_test COMMAND lag_monitor_test)
|
add_test(NAME lag_monitor_test COMMAND lag_monitor_test)
|
||||||
add_test(NAME latency_tracker_test COMMAND latency_tracker_test)
|
add_test(NAME latency_tracker_test COMMAND latency_tracker_test)
|
||||||
add_test(NAME metrics_registry_test COMMAND metrics_registry_test)
|
add_test(NAME metrics_registry_test COMMAND metrics_registry_test)
|
||||||
|
add_test(NAME card_update_progress_test COMMAND card_update_progress_test)
|
||||||
add_test(NAME loader_local_matching_test COMMAND loader_local_matching_test)
|
add_test(NAME loader_local_matching_test COMMAND loader_local_matching_test)
|
||||||
|
|
||||||
add_test(NAME deck_hash_performance_test COMMAND deck_hash_performance_test)
|
add_test(NAME deck_hash_performance_test COMMAND deck_hash_performance_test)
|
||||||
|
|
@ -75,6 +76,11 @@ add_executable(lag_monitor_test ${CMAKE_SOURCE_DIR}/cockatrice/src/client/lag_mo
|
||||||
target_include_directories(lag_monitor_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src)
|
target_include_directories(lag_monitor_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src)
|
||||||
add_executable(latency_tracker_test latency_tracker_test.cpp)
|
add_executable(latency_tracker_test latency_tracker_test.cpp)
|
||||||
add_executable(metrics_registry_test ../servatrice/src/metrics_registry.cpp metrics_registry_test.cpp)
|
add_executable(metrics_registry_test ../servatrice/src/metrics_registry.cpp metrics_registry_test.cpp)
|
||||||
|
add_executable(
|
||||||
|
card_update_progress_test ${CMAKE_SOURCE_DIR}/cockatrice/src/interface/card_database_update/card_update_progress.cpp
|
||||||
|
card_update_progress_test.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(card_update_progress_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src)
|
||||||
add_executable(
|
add_executable(
|
||||||
loader_local_matching_test
|
loader_local_matching_test
|
||||||
${CMAKE_SOURCE_DIR}/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp
|
${CMAKE_SOURCE_DIR}/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp
|
||||||
|
|
@ -94,6 +100,7 @@ if(WITH_CLIENT)
|
||||||
single_instance_manager_test.cpp
|
single_instance_manager_test.cpp
|
||||||
)
|
)
|
||||||
target_include_directories(single_instance_manager_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src)
|
target_include_directories(single_instance_manager_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src)
|
||||||
|
target_include_directories(single_instance_manager_test PRIVATE ${CMAKE_SOURCE_DIR}/libcockatrice_utility)
|
||||||
|
|
||||||
add_test(NAME single_instance_manager_test COMMAND single_instance_manager_test)
|
add_test(NAME single_instance_manager_test COMMAND single_instance_manager_test)
|
||||||
endif()
|
endif()
|
||||||
|
|
@ -143,6 +150,7 @@ if(NOT GTEST_FOUND)
|
||||||
add_dependencies(lag_monitor_test gtest)
|
add_dependencies(lag_monitor_test gtest)
|
||||||
add_dependencies(latency_tracker_test gtest)
|
add_dependencies(latency_tracker_test gtest)
|
||||||
add_dependencies(metrics_registry_test gtest)
|
add_dependencies(metrics_registry_test gtest)
|
||||||
|
add_dependencies(card_update_progress_test gtest)
|
||||||
add_dependencies(loader_local_matching_test gtest)
|
add_dependencies(loader_local_matching_test gtest)
|
||||||
add_dependencies(single_instance_manager_test gtest)
|
add_dependencies(single_instance_manager_test gtest)
|
||||||
endif()
|
endif()
|
||||||
|
|
@ -201,6 +209,7 @@ target_link_libraries(
|
||||||
)
|
)
|
||||||
target_include_directories(metrics_registry_test PRIVATE ${CMAKE_SOURCE_DIR}/servatrice/src)
|
target_include_directories(metrics_registry_test PRIVATE ${CMAKE_SOURCE_DIR}/servatrice/src)
|
||||||
target_link_libraries(metrics_registry_test ${TEST_QT_MODULES} Threads::Threads ${GTEST_BOTH_LIBRARIES})
|
target_link_libraries(metrics_registry_test ${TEST_QT_MODULES} Threads::Threads ${GTEST_BOTH_LIBRARIES})
|
||||||
|
target_link_libraries(card_update_progress_test Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES})
|
||||||
target_link_libraries(
|
target_link_libraries(
|
||||||
loader_local_matching_test libcockatrice_settings Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}
|
loader_local_matching_test libcockatrice_settings Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
93
tests/card_update_progress_test.cpp
Normal file
93
tests/card_update_progress_test.cpp
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
#include "interface/card_database_update/card_update_progress.h"
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::optional<CardUpdateProgress> parseLine(const QByteArray &line)
|
||||||
|
{
|
||||||
|
return CardUpdateProgress::fromProtocolLine(line);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(CardUpdateProgressTest, ParsesAValidDownloadLine)
|
||||||
|
{
|
||||||
|
const std::optional<CardUpdateProgress> progress = parseLine("PROGRESS download 42 100");
|
||||||
|
ASSERT_TRUE(progress.has_value());
|
||||||
|
EXPECT_EQ(CardUpdateStage::Download, progress->stage);
|
||||||
|
EXPECT_EQ(42, progress->done);
|
||||||
|
EXPECT_EQ(100, progress->total);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CardUpdateProgressTest, ParsesTrailingNewline)
|
||||||
|
{
|
||||||
|
const std::optional<CardUpdateProgress> progress = parseLine("PROGRESS scan 7 99\n");
|
||||||
|
ASSERT_TRUE(progress.has_value());
|
||||||
|
EXPECT_EQ(CardUpdateStage::Scan, progress->stage);
|
||||||
|
EXPECT_EQ(7, progress->done);
|
||||||
|
EXPECT_EQ(99, progress->total);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CardUpdateProgressTest, ParsesWithSurroundingWhitespace)
|
||||||
|
{
|
||||||
|
const std::optional<CardUpdateProgress> progress = parseLine(QByteArray(" PROGRESS import 3 10 "));
|
||||||
|
ASSERT_TRUE(progress.has_value());
|
||||||
|
EXPECT_EQ(CardUpdateStage::Import, progress->stage);
|
||||||
|
EXPECT_EQ(3, progress->done);
|
||||||
|
EXPECT_EQ(10, progress->total);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CardUpdateProgressTest, AcceptsZeroProgress)
|
||||||
|
{
|
||||||
|
const std::optional<CardUpdateProgress> progress = parseLine("PROGRESS download 0 0");
|
||||||
|
ASSERT_TRUE(progress.has_value());
|
||||||
|
EXPECT_EQ(0, progress->done);
|
||||||
|
EXPECT_EQ(0, progress->total);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CardUpdateProgressTest, RejectsLinesWithoutProgressPrefix)
|
||||||
|
{
|
||||||
|
EXPECT_FALSE(parseLine("download 42 100").has_value());
|
||||||
|
EXPECT_FALSE(parseLine("PROGRESSdownload 42 100").has_value());
|
||||||
|
EXPECT_FALSE(parseLine("").has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CardUpdateProgressTest, RejectsMalformedLines)
|
||||||
|
{
|
||||||
|
EXPECT_FALSE(parseLine("PROGRESS").has_value());
|
||||||
|
EXPECT_FALSE(parseLine("PROGRESS download").has_value());
|
||||||
|
EXPECT_FALSE(parseLine("PROGRESS download 42 100 extra").has_value());
|
||||||
|
EXPECT_FALSE(parseLine("PROGRESS download 42").has_value());
|
||||||
|
EXPECT_FALSE(parseLine("PROGRESS download notANumber 100").has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CardUpdateProgressTest, RejectsNegativeValues)
|
||||||
|
{
|
||||||
|
EXPECT_FALSE(parseLine("PROGRESS download -1 100").has_value());
|
||||||
|
EXPECT_FALSE(parseLine("PROGRESS download 42 -100").has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CardUpdateProgressTest, AcceptsUnknownStagesSchemalessly)
|
||||||
|
{
|
||||||
|
const std::optional<CardUpdateProgress> progress = parseLine("PROGRESS spoilers 1 2");
|
||||||
|
ASSERT_TRUE(progress.has_value());
|
||||||
|
EXPECT_EQ(CardUpdateStage::Unknown, progress->stage);
|
||||||
|
EXPECT_EQ(1, progress->done);
|
||||||
|
EXPECT_EQ(2, progress->total);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CardUpdateProgressTest, StageTokensRoundTrip)
|
||||||
|
{
|
||||||
|
EXPECT_EQ("download", CardUpdateProgress{CardUpdateStage::Download}.stageToken());
|
||||||
|
EXPECT_EQ("scan", CardUpdateProgress{CardUpdateStage::Scan}.stageToken());
|
||||||
|
EXPECT_EQ("import", CardUpdateProgress{CardUpdateStage::Import}.stageToken());
|
||||||
|
EXPECT_EQ("unknown", CardUpdateProgress{CardUpdateStage::Unknown}.stageToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
||||||
|
|
@ -166,6 +166,12 @@ TEST_F(SettingsDefaultsTest, Updates_NotifyAboutNewVersion_Default)
|
||||||
ASSERT_EQ(s.getNotifyAboutNewVersion(), true);
|
ASSERT_EQ(s.getNotifyAboutNewVersion(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(SettingsDefaultsTest, Updates_CardDatabaseUpdateTrayNotification_Default)
|
||||||
|
{
|
||||||
|
UpdatesSettings s(settingsPath, nullptr);
|
||||||
|
ASSERT_EQ(s.getCardDatabaseUpdateTrayNotification(), true);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(SettingsDefaultsTest, Updates_UpdateReleaseChannelIndex_Default)
|
TEST_F(SettingsDefaultsTest, Updates_UpdateReleaseChannelIndex_Default)
|
||||||
{
|
{
|
||||||
UpdatesSettings s(settingsPath, nullptr);
|
UpdatesSettings s(settingsPath, nullptr);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue