Merge current upstream into finite-copy legality review

Preserve upstream master and the original nine-file copy-limit change. Retain QIODevice alongside upstream Oracle test includes. Invalidate derived format rules where the new upstream import reset and failure paths clear their cards.
This commit is contained in:
Bryce! 2026-11-04 16:01:48 -05:00
commit 3de8351b8b
443 changed files with 15637 additions and 11767 deletions

View file

@ -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](
@ -795,7 +799,17 @@ int OracleImporter::startImport()
{
static ICardSetPriorityController *noOpController = new NoopCardSetPriorityController();
importCancelled.storeRelease(0);
importSuccessful = false;
importError.clear();
sets.clear();
cards.clear();
magicFormats.clear();
magicFormatsDirty = true;
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
@ -817,9 +831,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(),
@ -829,30 +841,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;
@ -869,7 +871,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());
@ -877,8 +886,25 @@ int OracleImporter::startImport()
return setIndex;
}
int OracleImporter::failImport(const QString &message)
{
importSuccessful = false;
importError = message;
sets.clear();
cards.clear();
magicFormats.clear();
magicFormatsDirty = true;
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);
@ -892,6 +918,7 @@ void OracleImporter::releaseSetData()
void OracleImporter::clear()
{
prepareImport();
sets.clear();
cards.clear();
magicFormats.clear();