mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 09:33:25 -08:00
Preserve installed card databases when Oracle refresh fails (#7739)
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
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>
This commit is contained in:
parent
e558c0dffa
commit
3a656c9c4a
14 changed files with 517 additions and 72 deletions
|
|
@ -100,6 +100,10 @@ static CardSet::Priority getSetPriority(const QString &setType, const QString &s
|
|||
|
||||
bool OracleImporter::readSetsFromByteArray(QByteArray data)
|
||||
{
|
||||
importSuccessful = false;
|
||||
importError.clear();
|
||||
allSets.clear();
|
||||
rawSetsData.clear();
|
||||
const RawJson::ScanProgressCallback progress =
|
||||
progressReporting
|
||||
? [this](
|
||||
|
|
@ -764,7 +768,15 @@ int OracleImporter::startImport()
|
|||
{
|
||||
static ICardSetPriorityController *noOpController = new NoopCardSetPriorityController();
|
||||
|
||||
importCancelled.storeRelease(0);
|
||||
importSuccessful = false;
|
||||
importError.clear();
|
||||
sets.clear();
|
||||
cards.clear();
|
||||
localizedEntries.clear();
|
||||
splitLocalizedTexts.clear();
|
||||
if (allSets.isEmpty()) {
|
||||
return failImport(tr("No set has been selected for import."));
|
||||
}
|
||||
|
||||
// Pre-allocate the cards hash to avoid rehashing during import. Keys are
|
||||
// distinct card names while raw ranges only count printings (AllPrintings
|
||||
|
|
@ -786,9 +798,7 @@ int OracleImporter::startImport()
|
|||
|
||||
for (const SetToDownload &curSetToParse : allSets) {
|
||||
if (importCancelled.loadAcquire()) {
|
||||
// The wizard was closed mid-import: stop at the next set boundary so
|
||||
// the caller can wait for this future without processing every set.
|
||||
break;
|
||||
return failImport(tr("The card database import was cancelled."));
|
||||
}
|
||||
|
||||
CardSetPtr newSet = CardSet::newInstance(noOpController, curSetToParse.getShortName(),
|
||||
|
|
@ -798,30 +808,20 @@ int OracleImporter::startImport()
|
|||
// parse only this set's slice of the raw document so the whole JSON tree is
|
||||
// never kept in memory at once
|
||||
const RawJson::SetDataRange &rawRange = curSetToParse.getRawRange();
|
||||
const qsizetype rangeEnd = rawRange.start + rawRange.length;
|
||||
if (rawRange.start < 0 || rawRange.length <= 0 || rangeEnd > rawSetsData.size()) {
|
||||
// rawSetsData is cleared by releaseSetData() while SetToDownload copies
|
||||
// taken from getSets() keep their ranges, and nothing else enforces the
|
||||
// pairing — so never index past the buffer on stale/mismatched ranges.
|
||||
qWarning() << "error: out-of-bounds raw range for set" << curSetToParse.getShortName() << "skipping";
|
||||
++setIndex;
|
||||
emit setIndexChanged(0, setIndex, curSetToParse.getLongName());
|
||||
continue;
|
||||
if (rawRange.start < 0 || rawRange.length <= 0 || rawRange.start > rawSetsData.size() ||
|
||||
rawRange.length > rawSetsData.size() - rawRange.start) {
|
||||
return failImport(tr("The source data for set %1 is unavailable. Load the sets file again.")
|
||||
.arg(curSetToParse.getShortName()));
|
||||
}
|
||||
// sliced() shares the buffer instead of deep-copying the slice; the largest
|
||||
// sets in AllPrintings are tens of MB, so the copy is worth avoiding here.
|
||||
const QByteArray setBytes = rawSetsData.sliced(rawRange.start, rawRange.length);
|
||||
QJsonParseError parseError;
|
||||
const QJsonDocument setDoc = QJsonDocument::fromJson(setBytes, &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qWarning() << "error: parsing card data for set" << curSetToParse.getShortName() << ":"
|
||||
<< parseError.errorString();
|
||||
++setIndex;
|
||||
// Keep the progress accounting honest: a set that failed to parse
|
||||
// still advanced the index, so report it (with zero imported cards)
|
||||
// rather than letting SaveSetsPage's bar stall per failed set.
|
||||
emit setIndexChanged(0, setIndex, curSetToParse.getLongName());
|
||||
continue;
|
||||
return failImport(
|
||||
tr("Could not import set %1: %2").arg(curSetToParse.getShortName(), parseError.errorString()));
|
||||
}
|
||||
if (!setDoc.isObject() || !setDoc.object().value("cards").isArray()) {
|
||||
return failImport(tr("Set %1 does not contain a card list.").arg(curSetToParse.getShortName()));
|
||||
}
|
||||
|
||||
// Only add the set to the database once its slice parsed cleanly;
|
||||
|
|
@ -838,7 +838,14 @@ int OracleImporter::startImport()
|
|||
emit setIndexChanged(numCardsInSet, setIndex, curSetToParse.getLongName());
|
||||
}
|
||||
|
||||
if (importCancelled.loadAcquire()) {
|
||||
return failImport(tr("The card database import was cancelled."));
|
||||
}
|
||||
if (cards.isEmpty()) {
|
||||
return failImport(tr("No cards were imported. The existing card database has been kept."));
|
||||
}
|
||||
applyLocalizedData();
|
||||
importSuccessful = true;
|
||||
|
||||
emit setIndexChanged(0, setIndex, QString());
|
||||
|
||||
|
|
@ -846,8 +853,23 @@ int OracleImporter::startImport()
|
|||
return setIndex;
|
||||
}
|
||||
|
||||
int OracleImporter::failImport(const QString &message)
|
||||
{
|
||||
importSuccessful = false;
|
||||
importError = message;
|
||||
sets.clear();
|
||||
cards.clear();
|
||||
localizedEntries.clear();
|
||||
splitLocalizedTexts.clear();
|
||||
qWarning() << message;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool OracleImporter::saveToFile(const QString &fileName, const QString &sourceUrl, const QString &sourceVersion)
|
||||
{
|
||||
if (!importSuccessful) {
|
||||
return false;
|
||||
}
|
||||
CockatriceXml4Parser parser(new NoopCardSetPriorityController());
|
||||
|
||||
return parser.saveToFile(createDefaultMagicFormats(), sets, cards, fileName, sourceUrl, sourceVersion);
|
||||
|
|
@ -861,6 +883,7 @@ void OracleImporter::releaseSetData()
|
|||
|
||||
void OracleImporter::clear()
|
||||
{
|
||||
prepareImport();
|
||||
sets.clear();
|
||||
cards.clear();
|
||||
allSets.clear();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue