Cockatrice/libcockatrice_card/libcockatrice/card/database/card_database_loader.h
BruebachL 205e1ecebf
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
[CardDB] Keep parse-time set filtering and bust the cache on enablement changes (#7448)
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>
2026-10-09 06:09:18 +02:00

185 lines
6.8 KiB
C++

#ifndef COCKATRICE_CARD_DATABASE_LOADER_H
#define COCKATRICE_CARD_DATABASE_LOADER_H
#include <QBasicMutex>
#include <QByteArray>
#include <QList>
#include <QLoggingCategory>
#include <QObject>
#include <QString>
#include <QStringList>
#include <qtmetamacros.h>
class ICardDatabasePathProvider;
class ICardSetPriorityController;
// IWYU pragma: keep
// CardDatabaseData is passed by value to the databaseDataReady() signal, so the
// moc-generated code needs the complete type.
#include "card_database_data.h"
inline Q_LOGGING_CATEGORY(CardDatabaseLoadingLog, "card_database.loading");
inline Q_LOGGING_CATEGORY(CardDatabaseLoadingSuccessOrFailureLog, "card_database.loading.success_or_failure");
class CardDatabase;
class ICardDatabaseParser;
/**
* @enum LoadStatus
* @brief Represents the result of attempting to load a card database.
*/
enum LoadStatus
{
Ok, /**< Database loaded successfully. */
VersionTooOld, /**< Database version is too old to load. */
Invalid, /**< Database is invalid or unparsable. */
NotLoaded, /**< Database has not been loaded. */
FileError, /**< Error opening or reading the file. */
NoCards /**< Database contains no cards. */
};
/**
* @class CardDatabaseLoader
* @ingroup CardDatabase
* @brief Handles loading card databases from disk and saving custom tokens.
*
* This class is responsible for:
* - Discovering configured card database paths.
* - Loading main, token, spoiler, and custom databases.
* - Populating a CardDatabase instance using connected parsers.
* - Emitting signals about loading progress and new sets.
*/
class CardDatabaseLoader : public QObject
{
Q_OBJECT
public:
/**
* @brief Constructs a CardDatabaseLoader.
* @param parent QObject parent.
* @param db Pointer to the CardDatabase to populate (non-owning).
* @param pathProvider Provider for card database file paths.
*/
explicit CardDatabaseLoader(QObject *parent,
CardDatabase *db,
ICardDatabasePathProvider *pathProvider,
ICardSetPriorityController *_priorityController);
/** @brief Destructor cleans up allocated parsers. */
~CardDatabaseLoader() override;
public slots:
/**
* @brief Loads all configured card databases.
*
* 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(bool ignoreCache = false);
/**
* @brief Loads a single card database file into the given snapshot.
* @param path Path to the database file.
* @param data Snapshot to populate.
* @return LoadStatus indicating success or failure.
*/
LoadStatus loadCardDatabase(const QString &path, CardDatabaseData &data);
/**
* @brief Saves custom tokens to the user-defined custom database path.
* @return True if the save was successful.
*/
bool saveCustomTokensToFile();
signals:
/** @brief Emitted when loading starts. */
void loadingStarted();
/** @brief Emitted when loading finishes successfully. */
void loadingFinished();
/** @brief Emitted when loading fails. */
void loadingFailed();
/**
* @brief Emitted with the fully-built snapshot once background loading
* completes. The receiver swaps it into the live database.
*/
void databaseDataReady(CardDatabaseData data);
/**
* @brief Emitted when new sets are discovered during loading.
* @param numSets Number of new sets.
* @param setNames Names of the discovered sets.
*/
void newSetsFound(int numSets, const QStringList &setNames);
/** @brief Emitted when all newly discovered sets have been enabled. */
void allNewSetsEnabled();
private:
/**
* @brief Parses a single file into the given snapshot using the available parsers.
* @param fileName Path to the database file.
* @param data Snapshot to populate.
* @return LoadStatus indicating success or failure.
*/
LoadStatus loadFromFile(const QString &fileName, CardDatabaseData &data);
/**
* @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(bool ignoreCache);
/**
* @brief Collects custom card database paths recursively.
* @return Sorted list of file paths to custom databases.
*/
[[nodiscard]] QStringList collectCustomDatabasePaths() const;
/**
* @brief Computes a hash identifying the current set of input database files.
* @return Hash over the paths, sizes and modification times of all inputs.
*/
[[nodiscard]] QByteArray computeSourceHash(const QStringList &customPaths) const;
/**
* @brief Path of the binary cache file derived from the main card database path.
* @return Cache file path.
*/
[[nodiscard]] QString cachePath() const;
/**
* @brief Attempts to populate the snapshot from the binary cache.
* @param data Snapshot to populate.
* @param sourceHash Pre-computed hash identifying the current input files.
* @return True if a valid, up-to-date cache was read.
*/
bool loadFromCache(CardDatabaseData &data, const QByteArray &sourceHash);
/**
* @brief Writes the snapshot to the binary cache for the current inputs.
* @param data Snapshot to serialize.
* @param sourceHash Pre-computed hash identifying the current input files.
* @return True if the cache was written successfully.
*/
bool saveToCache(const CardDatabaseData &data, const QByteArray &sourceHash);
private:
CardDatabase *database; /**< Non-owning pointer to the target CardDatabase. */
ICardDatabasePathProvider *pathProvider; /**< Pointer to the path provider. */
ICardSetPriorityController *priorityController; /**< Controller for reconstructing set state. */
QList<ICardDatabaseParser *> availableParsers; /**< List of available parsers for different formats. */
QBasicMutex *loadFromFileMutex = new QBasicMutex(); /**< Mutex for single-file loading. */
QBasicMutex *reloadDatabaseMutex = new QBasicMutex(); /**< Mutex for reloading entire database. */
bool initialLoadComplete = false; /**< Set after the first successful load. */
};
#endif // COCKATRICE_CARD_DATABASE_LOADER_H