Cockatrice/oracle/src/oraclewizard.cpp
BruebachL 96b0888a73
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / Prune caches (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
[Oracle] Don't report a cancelled card database update as successful (#7082)
* [Oracle] Don't report a cancelled card database update as successful (#3470)

Took 3 minutes

* [Oracle] Only report cancel when nothing was saved yet

Rejecting the wizard after SaveSetsPage or a download page already
wrote the database to disk still replaced cards.xml on disk, but
wasCancelled() forced exit code 1 so Cockatrice skipped the reload.
Track successful saves with OracleWizard::markDatabaseSaved() and only
treat a cancel as a failure when nothing was saved.

* [Oracle] Restore updater stderr in card update error dialog

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-15 15:21:07 +02:00

230 lines
6.3 KiB
C++

#include "oraclewizard.h"
#include "client/settings/cache_settings.h"
#include "main.h"
#include "oracleimporter.h"
#include "pages.h"
#include "pagetemplates.h"
#include <QAbstractButton>
#include <QApplication>
#include <QDebug>
#include <QDialog>
#include <QEvent>
#include <QFile>
#include <QIODevice>
#include <QList>
#include <QLocalServer>
#include <QLocalSocket>
#include <QNetworkAccessManager>
#include <QSettings>
#include <QVariant>
#include <QWindow>
#include <QWizardPage>
#include <QtGlobal>
#include <libcockatrice/settings/cards_display_settings.h>
#include <libcockatrice/settings/personal_settings.h>
#include <libcockatrice/utility/local_server_name.h>
#include <qlogging.h>
class QWidget;
OracleWizard::OracleWizard(QWidget *parent) : QWizard(parent)
{
// define a dummy context that will be used where needed
QString dummy = QT_TRANSLATE_NOOP("i18n", "English");
#ifdef Q_OS_WIN
setWizardStyle(QWizard::ModernStyle);
#endif
QString oracleSettingsFile = SettingsCache::instance().getSettingsPath() + "oracle.ini";
settings = new QSettings(oracleSettingsFile, QSettings::IniFormat, this);
// We moved the oracle-specific settings from global.ini to a separate oracle.ini after 2.10
if (!QFile::exists(oracleSettingsFile)) {
migrateOracleSettings();
}
connect(&SettingsCache::instance().personal(), &PersonalSettings::langChanged, this, &OracleWizard::updateLanguage);
importer = new OracleImporter(this);
// Import card text in the language the client displays, if supported:
// foreignData for any other language is never imported.
importer->setCardLang(SettingsCache::instance().cardsDisplay().getCardLang());
nam = new QNetworkAccessManager(this);
QList<OracleWizardPage *> pages;
if (!isSpoilersOnly) {
pages << new IntroPage << new LoadSetsPage << new SaveSetsPage << new LoadTokensPage << new OutroPage;
} else {
pages << new LoadSpoilersPage << new OutroPage;
}
for (OracleWizardPage *page : pages) {
addPage(page);
// Connect background auto-advance
connect(page, &OracleWizardPage::readyToContinue, this, [this]() {
if (backgroundMode) {
next();
}
});
}
retranslateUi();
startRaiseServer();
}
/**
* Migrates the oracle-specific settings from global.ini to oracle.ini
*/
void OracleWizard::migrateOracleSettings()
{
QString filePath = SettingsCache::instance().getSettingsPath() + "global.ini";
auto globalSettings = QSettings(filePath, QSettings::IniFormat, this);
auto tryMigrateValue = [this, &globalSettings](const QString &name) {
QVariant variant = globalSettings.value(name);
if (variant.isValid()) {
settings->setValue(name, variant.toString());
}
};
tryMigrateValue("allsetsurl");
tryMigrateValue("tokensurl");
tryMigrateValue("spoilersurl");
}
void OracleWizard::updateLanguage()
{
qApp->removeTranslator(translator);
installNewTranslator();
}
void OracleWizard::changeEvent(QEvent *event)
{
if (event->type() == QEvent::LanguageChange) {
retranslateUi();
}
QDialog::changeEvent(event);
}
void OracleWizard::retranslateUi()
{
setWindowTitle(tr("Oracle Importer"));
for (int i = 0; i < pageIds().count(); i++) {
dynamic_cast<OracleWizardPage *>(page(i))->retranslateUi();
}
}
void OracleWizard::accept()
{
QDialog::accept();
}
void OracleWizard::reject()
{
cancelled = true;
// The wizard is being closed while a page may still run a worker on the
// importer. Ask it to stop before the wizard (and the importer child) is
// destroyed, so the worker thread never touches freed memory.
if (auto *active = dynamic_cast<OracleWizardPage *>(currentPage())) {
active->cancelWork();
}
const auto replies = nam->findChildren<QNetworkReply *>();
for (QNetworkReply *reply : replies) {
reply->abort();
}
QWizard::reject();
}
void OracleWizard::runInBackground()
{
backgroundMode = true;
hide();
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()
{
button(QWizard::NextButton)->setDisabled(false);
button(QWizard::BackButton)->setDisabled(false);
}
void OracleWizard::disableButtons()
{
button(QWizard::NextButton)->setDisabled(true);
button(QWizard::BackButton)->setDisabled(true);
}
bool OracleWizard::saveTokensToFile(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly)) {
qDebug() << "File open (w) failed for" << fileName;
return false;
}
if (file.write(tokensData) == -1) {
qDebug() << "File write (w) failed for" << fileName;
return false;
}
file.close();
return true;
}