[CardDB] Keep parse-time set filtering and bust the cache on enablement changes (#7448)
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 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (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 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

The parsers keep dropping printings for disabled sets so that disabling a
set hides them everywhere, not just in deck editor search. The cached parse
output therefore depends on enablement, which the source hash did not cover:
the first parse froze whatever enablement state existed at that moment and
later toggles hit the stale cache.

- Fold a parser schema version and the sorted enabled set names into the
  binary cache source hash so toggling a set or enabling newly discovered
  sets (including custom ones) forces a re-parse on the next load.
- Harden the legacy set migration to only write keys that exist so missing
  legacy values can no longer clobber cardDatabase.ini and silently disable
  sets on upgrade.
- Make CardDatabase::reloadCardDatabasesAndNotify() bypass the binary cache
  so an explicit reload always re-reads the files on disk, even when their
  size/mtime hash still matches.
- Cover cache invalidation end to end: sandboxed loads assert that disabling
  a set, adding a custom database and enabling a fresh set each rewrite the
  cache, that a no-op load keeps it, and that an explicit reload picks up
  edits the source hash cannot see.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-10-09 06:09:18 +02:00 • committed by GitHub
parent aaafa72c0e
commit 205e1ecebf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 366 additions and 14 deletions

View file

@ -78,7 +78,9 @@ void CardDatabase::loadCardDatabases()
void CardDatabase::reloadCardDatabasesAndNotify()
{
loadCardDatabases();
// An explicit reload bypasses the binary cache so the files on disk are
// always re-read, even when their size/mtime hash still matches the cache.
loadStatus = loader->loadCardDatabases(/* ignoreCache */ true);
if (loadStatus == Ok) {
checkUnknownSets();

View file

@ -177,8 +177,12 @@ public slots:
void addFormat(const FormatRulesPtr &format);
/** @brief Loads card databases from configured paths. */
/** @brief Loads card databases from configured paths, using the binary cache when valid. */
void loadCardDatabases();
/**
* @brief Reloads card databases from disk, bypassing the binary cache,
* and notifies observers of the fresh state.
*/
void reloadCardDatabasesAndNotify();
/** @brief Saves custom tokens to file.

View file

@ -6,6 +6,7 @@
#include "libcockatrice/card/database/card_database_data.h"
#include "libcockatrice/card/set/card_set.h"
#include "libcockatrice/interfaces/interface_card_database_path_provider.h"
#include "libcockatrice/interfaces/interface_card_set_priority_controller.h"
#include "parser/card_database_parser.h"
#include "parser/cockatrice_xml_3.h"
#include "parser/cockatrice_xml_4.h"
@ -28,7 +29,6 @@
#include <utility>
class ICardPreferenceProvider;
class ICardSetPriorityController;
CardDatabaseLoader::CardDatabaseLoader(QObject *parent,
CardDatabase *db,
@ -44,8 +44,7 @@ CardDatabaseLoader::CardDatabaseLoader(QObject *parent,
// the finished snapshot is swapped into the live database on the GUI thread.
// when SettingsCache's path changes, trigger reloads
connect(pathProvider, &ICardDatabasePathProvider::cardDatabasePathChanged, this,
&CardDatabaseLoader::loadCardDatabases);
connect(pathProvider, &ICardDatabasePathProvider::cardDatabasePathChanged, this, [this] { loadCardDatabases(); });
}
CardDatabaseLoader::~CardDatabaseLoader()
@ -90,12 +89,12 @@ LoadStatus CardDatabaseLoader::loadCardDatabase(const QString &path, CardDatabas
return tempLoadStatus;
}
LoadStatus CardDatabaseLoader::loadCardDatabases()
LoadStatus CardDatabaseLoader::loadCardDatabases(bool ignoreCache)
{
return doLoadCardDatabases();
return doLoadCardDatabases(ignoreCache);
}
LoadStatus CardDatabaseLoader::doLoadCardDatabases()
LoadStatus CardDatabaseLoader::doLoadCardDatabases(bool ignoreCache)
{
QMutexLocker locker(reloadDatabaseMutex);
@ -113,9 +112,14 @@ LoadStatus CardDatabaseLoader::doLoadCardDatabases()
LoadStatus loadStatus = NotLoaded;
// Try the binary cache first: a cache hit avoids re-parsing the (large) XML.
// An explicit reload skips the lookup so the files on disk are always
// re-read, even when their size/mtime hash still matches the cache.
const QStringList customPaths = collectCustomDatabasePaths();
const QByteArray sourceHash = computeSourceHash(customPaths);
if (loadFromCache(data, sourceHash)) {
if (ignoreCache) {
qCInfo(CardDatabaseLoadingLog) << "Ignoring binary cache, re-parsing source files";
}
if (!ignoreCache && loadFromCache(data, sourceHash)) {
qCInfo(CardDatabaseLoadingLog) << "Loaded card database from binary cache";
loadStatus = Ok;
} else {
@ -180,6 +184,12 @@ QByteArray CardDatabaseLoader::computeSourceHash(const QStringList &customPaths)
hash.addData(QCoreApplication::applicationVersion().toUtf8());
hash.addData(QByteArray(1, '\0'));
// Include parser schema version to invalidate caches when parser behavior changes.
// Increment this if the parser logic changes in a way that affects cached data.
static const char SCHEMA_VERSION[] = "carddb-parse-schema-v1";
hash.addData(QByteArray(SCHEMA_VERSION, sizeof(SCHEMA_VERSION) - 1));
hash.addData(QByteArray(1, '\0'));
const QStringList inputs = QStringList()
<< pathProvider->getCardDatabasePath() << pathProvider->getTokenDatabasePath()
<< pathProvider->getSpoilerCardDatabasePath() << customPaths;
@ -194,6 +204,17 @@ QByteArray CardDatabaseLoader::computeSourceHash(const QStringList &customPaths)
hash.addData(QByteArray(1, '\0'));
}
}
// The parsers drop printings of disabled sets at parse time, so the cached
// snapshot depends on enablement: fold the enabled set names in so that
// toggling a set invalidates the cache and forces a re-parse.
if (priorityController) {
const QStringList enabledSets = priorityController->getEnabledSetNames();
for (const QString &setName : enabledSets) {
hash.addData(setName.toUtf8());
hash.addData(QByteArray(1, '\0'));
}
}
return hash.result();
}

View file

@ -73,9 +73,13 @@ public slots:
*
* Runs synchronously on the calling thread. The caller should ensure
* that any signal receivers are already connected before invoking this.
* @param ignoreCache Skip the binary cache lookup and re-parse the source
* files from disk, refreshing the cache afterwards. Used for
* explicit reloads so that edits the source hash cannot see are
* still picked up.
* @return Status of the main database load.
*/
LoadStatus loadCardDatabases();
LoadStatus loadCardDatabases(bool ignoreCache = false);
/**
* @brief Loads a single card database file into the given snapshot.
@ -128,9 +132,10 @@ private:
/**
* @brief Performs the actual load work synchronously on the calling thread.
* @param ignoreCache Skip the binary cache lookup and re-parse from disk.
* @return Status of the main database load.
*/
LoadStatus doLoadCardDatabases();
LoadStatus doLoadCardDatabases(bool ignoreCache);
/**
* @brief Collects custom card database paths recursively.