mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-06 18:13:29 -08:00
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 / vcpkg baseline (push) Waiting to run
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 sanitize (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Fedora 44 (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 / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (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 27 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (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
* Preserve installed card databases when Oracle refresh fails * Fix the Oracle refresh verdict and cover the parser write path A background refresh that already installed cards.xml exited 1 when the token save afterwards failed, so the client kept its old in-memory database and reported the update as failed. Return the same verdict main() already computes: a reject after the database was written is a successful update. Cover the writer Oracle actually uses: add a short-write death test for CockatriceXml4Parser::saveToFile, which reverted to QFile would truncate an installed database without any test noticing. Route a failed background token download through reject() instead of a modal box on a hidden wizard, which left the client waiting with no timeout and blocked later updates. Test the first non-space byte in place rather than copying the payload, and report an import refusal with the importer's own message. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
229 lines
6.5 KiB
C++
229 lines
6.5 KiB
C++
#include "oraclewizard.h"
|
|
|
|
#include "client/settings/cache_settings.h"
|
|
#include "database_writer.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/network/network_manager.h>
|
|
#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)
|
|
{
|
|
#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 = NetworkManagerFactory::create(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();
|
|
// A hidden wizard does not close the application's last visible window.
|
|
// Stop background refreshes explicitly so the hosting client receives a verdict,
|
|
// using the same rule as main(): a reject after the database was written is still
|
|
// a successful update, only a reject that wrote nothing counts as a failure.
|
|
if (backgroundMode) {
|
|
QCoreApplication::exit(hasSavedDatabase() ? 0 : 1);
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
QString error;
|
|
if (!OracleDatabase::saveXml(fileName, tokensData, &error)) {
|
|
qWarning() << error;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|