Cockatrice/libcockatrice_card/libcockatrice/card/set/card_set.h
BruebachL a712553c81
[CardDB] Reload after the first run enables all sets without freezing startup (#7470)
* [CardDB] Reload when the first run enables all sets

checkUnknownSets() enabled every set when none was enabled, but the
database had already been parsed under that empty enablement, so the
parsers had stripped every printing. It only notified the models and never
reloaded, leaving the deck editor's card database display empty until the
next launch.

Route the all-sets-enabled case through the same reload the new-sets path
uses, and skip caching a snapshot whose sets are all disabled.

* [CardDB] Own checkUnknownSets after startup and fix the cache skip

The loader's initialLoadComplete guard skipped checkUnknownSets() on the
first successful load of the session, so a download that landed after
startupConfigCheck() and the onboarding wizard had already run (e.g.
skipping the wizard download and fetching later from the menu) left every
set disabled and the deck editor blank until the next launch. The guard
also let the check run on the worker before the queued snapshot swap, and
alongside reloadCardDatabasesAndNotify()'s own check, double-emitting
cardDatabaseNewSetsFound on manual reloads.

Move the check to the callers that load after startup: the post-update,
custom-import, and spoiler paths run it via a QFutureWatcher once the
finished snapshot has been swapped in, and the loader's synchronous
path-change reload runs it directly.

Judge the cache skip by the parsed sets' own enablement, what the parsers
actually saw, instead of getEnabledSetNames(), which reports empty for the
noop controller even though it enables every set. Wait for the first-run
reparse before opening Manage Sets so the dialog cannot bind to the
pre-reload set instances.

* [CardDB] Queue post-reload check and unblock first-run Manage Sets

reloadCardDatabasesAndNotify() no longer emits cardDatabaseEnabledSetsChanged after the swap's Reset already rebuilt the models (second 36k rebuild froze startup), and its follow-up checkUnknownSets() is queued to the GUI thread so it sees swapped-in data. cardDatabaseAllNewSetsEnabled() chains Welcome/Manage Sets off a QFutureWatcher instead of waitForFinished() blocking the event loop during the reparse.

* [CardDB] Batch the first-run enable-all into a single settings flush

checkUnknownSets() enabled every set with ~3 QSettings rewrite cycles per set (sort key, known, enabled), and each setValue re-opens and re-syncs the whole cardDatabase.ini. With a full database (874 sets) that froze the GUI thread for ~9s after the database download (LagMonitor: event loop stalled for 9129 ms, measured offscreen with a real 59MB cards.xml), reading as a total application hang that recovers on its own.

Persist the enable-all through the controller's batched saveSets() in a single flush instead, mirroring what SetsModel::save() already does: CardSetList::enableAllBatched() assigns fresh sort keys and marks every set known+enabled in memory, then writes all records at once. SetSaveData gains the isKnown flag so the batch covers all three options, and SetsModel::save() passes the sets' known state through (unchanged behavior there).

Also drop the cardDatabaseEnabledSetsChanged emission from the all-sets-enabled branch: the reload chained off cardDatabaseAllNewSetsEnabled rebuilds the models from the re-parsed snapshot via cardDatabaseReset, so emitting both rebuilt the 36k-card models twice on the GUI thread.

Adds a cache-invalidation test driving the real checkUnknownSets() first-run path.

* [CardDB] Batch the new-sets-found enable-all the same way

The first-run batching fixed the 874-set case, but the new-sets-found dialog path (enableAllUnknownSets/markAllSetsAsKnown) still wrote per set and per option, each setValue re-opening and re-syncing the whole cardDatabase.ini. Users with hundreds of custom sets hit the same multi-second GUI stall when answering the dialog.

Persist both through the controller's batched saveSets() in a single flush, writing only changed sets (so the common no-op check skips the flush entirely). Removes the now-unused write-through CardSetList methods (enableAll, enableAllUnknown, markAllAsKnown, guessSortKeys) so the batched flush is the only way to persist bulk set changes.

Extends the test controller mock with per-write/batched-flush counters and adds tests asserting both dialog paths persist with exactly one flush and zero individual writes.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-13 06:29:16 +02:00

246 lines
7.4 KiB
C++

#ifndef COCKATRICE_CARD_SET_H
#define COCKATRICE_CARD_SET_H
#include <QDate>
#include <QList>
#include <QSharedPointer>
#include <QString>
class CardInfo;
class ICardSetPriorityController;
using CardInfoPtr = QSharedPointer<CardInfo>;
class CardSet;
using CardSetPtr = QSharedPointer<CardSet>;
/**
* @class CardSet
* @ingroup CardSets
*
* @brief A collection of cards grouped under a common identifier.
*
* A set serves both as metadata (identifier, title, category, release date, and priority)
* and as a container of all cards that belong to it. Each set can be enabled/disabled
* and marked as known/unknown depending on context.
*
* The class inherits from `QList<CardInfoPtr>`, so it can be iterated over directly
* to access its contents.
*
* Typical usage:
* - Query metadata such as identifier, category, or release date.
* - Enable or disable sets according to user preference.
* - Store and retrieve CardInfo objects associated with the set.
*/
class CardSet : public QList<CardInfoPtr>
{
public:
/**
* @enum Priority
* @brief Defines relative ordering and importance of sets.
*/
enum Priority
{
PriorityFallback = 0, ///< Used when no other priority is defined.
PriorityPrimary = 10, ///< Primary, canonical set.
PrioritySecondary = 20, ///< Secondary but relevant.
PriorityReprint = 30, ///< Duplicate or reprint category.
PriorityOther = 40, ///< Miscellaneous grouping.
PriorityLowest = 100, ///< Lowest sorting priority.
};
static const char *TOKENS_SETNAME; ///< Reserved identifier for token-like sets.
private:
ICardSetPriorityController *priorityController; ///< Interface to the card set priority controller.
QString shortName; ///< Short identifier for the set.
QString longName; ///< Full name for the set.
unsigned int sortKey; ///< Custom numeric sort key.
QDate releaseDate; ///< Release date, may be empty if unknown.
QString setType; ///< Type/category label for the set.
Priority priority; ///< Priority level for sorting and relevance.
bool enabled; ///< Whether the set is active/enabled.
bool isknown; ///< Whether the set is considered known.
public:
/**
* @brief Constructs a CardSet.
*
* @param priorityController Interface to a card set priority controller.
* @param _shortName Identifier string.
* @param _longName Full descriptive name.
* @param _setType Type/category string.
* @param _releaseDate Release date (optional).
* @param _priority Sorting/priority level.
*/
explicit CardSet(ICardSetPriorityController *priorityController,
const QString &_shortName = QString(),
const QString &_longName = QString(),
const QString &_setType = QString(),
const QDate &_releaseDate = QDate(),
const Priority _priority = PriorityFallback);
/**
* @brief Creates and returns a new shared CardSet instance.
*
* @param priorityController Interface to a card set priority controller.
* @param _shortName Identifier string.
* @param _longName Full descriptive name.
* @param _setType Type/category string.
* @param _releaseDate Release date (optional).
* @param _priority Sorting/priority level.
* @return A shared pointer to the new CardSet.
*/
static CardSetPtr newInstance(ICardSetPriorityController *priorityController,
const QString &_shortName = QString(),
const QString &_longName = QString(),
const QString &_setType = QString(),
const QDate &_releaseDate = QDate(),
const Priority _priority = PriorityFallback);
/**
* @brief Returns a safe, sanitized version of the short name.
*
* Intended for file paths or identifiers where only certain
* characters are allowed.
*
* @return Sanitized short name.
*/
[[nodiscard]] QString getCorrectedShortName() const;
/** @return Short identifier of the set. */
[[nodiscard]] QString getShortName() const
{
return shortName;
}
/** @return Descriptive name of the set. */
[[nodiscard]] QString getLongName() const
{
return longName;
}
/** @return Type/category string of the set. */
[[nodiscard]] QString getSetType() const
{
return setType;
}
/** @return Release date of the set. */
[[nodiscard]] QDate getReleaseDate() const
{
return releaseDate;
}
/** @return Priority level of the set. */
[[nodiscard]] Priority getPriority() const
{
return priority;
}
/**
* @brief Sets the full name of the set.
* @param _longName New full name.
*/
void setLongName(const QString &_longName)
{
longName = _longName;
}
/**
* @brief Sets the category/type of the set.
* @param _setType New category string.
*/
void setSetType(const QString &_setType)
{
setType = _setType;
}
/**
* @brief Sets the release date of the set.
* @param _releaseDate New release date.
*/
void setReleaseDate(const QDate &_releaseDate)
{
releaseDate = _releaseDate;
}
/**
* @brief Updates the priority of the set.
* @param _priority New priority value.
*/
void setPriority(const Priority _priority)
{
priority = _priority;
}
/**
* @brief Loads state values (enabled, known, sort key) from configuration.
*
* Reads external configuration and applies it to this set.
*/
void loadSetOptions();
void setSortKeyInMemory(unsigned int _sortKey)
{
sortKey = _sortKey;
}
void setEnabledInMemory(bool _enabled)
{
enabled = _enabled;
}
/** @return The sort key assigned to this set. */
[[nodiscard]] int getSortKey() const
{
return sortKey;
}
/**
* @brief Assigns a new sort key to this set.
* @param _sortKey The numeric key to use for sorting.
*/
void setSortKey(unsigned int _sortKey);
/** @return True if the set is enabled. */
[[nodiscard]] bool getEnabled() const
{
return enabled;
}
/**
* @brief Enables or disables the set.
* @param _enabled True to enable, false to disable.
*/
void setEnabled(bool _enabled);
/** @return True if the set is considered known. */
[[nodiscard]] bool getIsKnown() const
{
return isknown;
}
/**
* @brief Marks the set as known or unknown.
* @param _isknown True if known, false if unknown.
*/
void setIsKnown(bool _isknown);
void setIsKnownInMemory(bool _isknown)
{
isknown = _isknown;
}
/**
* @brief Determines whether the set has incomplete metadata and should be ignored.
*
* @return True if the long name, type, and release date are all empty.
*/
[[nodiscard]] bool getIsKnownIgnored() const
{
return longName.length() + setType.length() + releaseDate.toString().length() == 0;
}
};
#endif // COCKATRICE_CARD_SET_H