mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -08:00
[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>
This commit is contained in:
parent
bac2d285e3
commit
a712553c81
14 changed files with 364 additions and 112 deletions
|
|
@ -10,6 +10,7 @@
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QFutureWatcher>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QLocale>
|
#include <QLocale>
|
||||||
|
|
@ -177,7 +178,15 @@ bool SpoilerBackgroundUpdater::saveDownloadedFile(QByteArray data)
|
||||||
|
|
||||||
// Data written, so reload the card database
|
// Data written, so reload the card database
|
||||||
qCInfo(SpoilerBackgroundUpdaterLog) << "Spoiler Service Data Written";
|
qCInfo(SpoilerBackgroundUpdaterLog) << "Spoiler Service Data Written";
|
||||||
const auto reloadOk = QtConcurrent::run([] { CardDatabaseManager::getInstance()->loadCardDatabases(); });
|
// Spoilers can introduce sets the config has never seen; the loader never
|
||||||
|
// runs checkUnknownSets() itself, so run it once the finished snapshot has
|
||||||
|
// been swapped in.
|
||||||
|
auto *watcher = new QFutureWatcher<void>(this);
|
||||||
|
connect(watcher, &QFutureWatcher<void>::finished, this, [watcher] {
|
||||||
|
watcher->deleteLater();
|
||||||
|
CardDatabaseManager::getInstance()->checkUnknownSets();
|
||||||
|
});
|
||||||
|
watcher->setFuture(QtConcurrent::run([] { CardDatabaseManager::getInstance()->loadCardDatabases(); }));
|
||||||
|
|
||||||
// If the user has notifications enabled, let them know
|
// If the user has notifications enabled, let them know
|
||||||
// when the database was last updated
|
// when the database was last updated
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QFlags>
|
#include <QFlags>
|
||||||
|
#include <QFutureWatcher>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
|
|
@ -640,10 +641,10 @@ void MainWindow::startupConfigCheck()
|
||||||
|
|
||||||
const bool isCleanInstall = SettingsCache::instance().network().getClientVersion() == CLIENT_INFO_NOT_SET;
|
const bool isCleanInstall = SettingsCache::instance().network().getClientVersion() == CLIENT_INFO_NOT_SET;
|
||||||
|
|
||||||
// checkUnknownSets() is intentionally deferred from the card database load
|
// The loader never runs checkUnknownSets() itself (see main.cpp): the
|
||||||
// (which runs in main() before MainWindow exists) so that
|
// front-loaded parse runs before MainWindow exists, so
|
||||||
// cardDatabaseNewSetsFound / cardDatabaseAllNewSetsEnabled have live
|
// cardDatabaseNewSetsFound / cardDatabaseAllNewSetsEnabled would have no
|
||||||
// receivers when emitted.
|
// receivers. MainWindow owns the check after startup.
|
||||||
// On a clean install the onboarding wizard owns the first-run experience;
|
// On a clean install the onboarding wizard owns the first-run experience;
|
||||||
// wait until it closes so the legacy "all sets enabled" welcome and the
|
// wait until it closes so the legacy "all sets enabled" welcome and the
|
||||||
// Manage Sets dialog don't appear first.
|
// Manage Sets dialog don't appear first.
|
||||||
|
|
@ -1137,18 +1138,35 @@ void MainWindow::cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknow
|
||||||
|
|
||||||
void MainWindow::cardDatabaseAllNewSetsEnabled()
|
void MainWindow::cardDatabaseAllNewSetsEnabled()
|
||||||
{
|
{
|
||||||
if (firstRunWizardActive || CardDatabaseManager::getInstance()->getCardList().isEmpty()) {
|
auto *cardDatabase = CardDatabaseManager::getInstance();
|
||||||
// The onboarding wizard owns the first-run messaging on a clean install,
|
|
||||||
// and with no card data there are no sets to have enabled.
|
// With no card data there is nothing to have enabled and nothing to reparse.
|
||||||
|
if (cardDatabase->getCardList().isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QMessageBox::information(
|
// checkUnknownSets() enabled every set after the database had already been
|
||||||
this, tr("Welcome"),
|
// parsed under the (empty) first-run enablement, so the parser dropped every
|
||||||
tr("Hi! It seems like you're running this version of Cockatrice for the first time.\nAll the sets in the card "
|
// printing of every set. Reparse now that the sets are enabled, otherwise the
|
||||||
"database have been enabled.\nRead more about changing the set order or disabling specific sets and "
|
// deck editor's card database display stays empty until the next launch.
|
||||||
"consequent effects in the \"Manage Sets\" dialog."));
|
auto *watcher = new QFutureWatcher<void>(this);
|
||||||
actManageSets();
|
connect(watcher, &QFutureWatcher<void>::finished, this, [this, watcher] {
|
||||||
|
watcher->deleteLater();
|
||||||
|
if (firstRunWizardActive) {
|
||||||
|
// The onboarding wizard owns the first-run messaging on a clean install.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QMessageBox::information(
|
||||||
|
this, tr("Welcome"),
|
||||||
|
tr("Hi! It seems like you're running this version of Cockatrice for the first time.\nAll the sets in the "
|
||||||
|
"card "
|
||||||
|
"database have been enabled.\nRead more about changing the set order or disabling specific sets and "
|
||||||
|
"consequent effects in the \"Manage Sets\" dialog."));
|
||||||
|
// The reload above replaces the set instances, so Manage Sets must open
|
||||||
|
// only after the finished snapshot has been swapped in.
|
||||||
|
actManageSets();
|
||||||
|
});
|
||||||
|
watcher->setFuture(QtConcurrent::run([cardDatabase] { cardDatabase->reloadCardDatabasesAndNotify(); }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CARD UPDATER */
|
/* CARD UPDATER */
|
||||||
|
|
@ -1321,7 +1339,19 @@ void MainWindow::exitCardDatabaseUpdate()
|
||||||
cardUpdateProcess = nullptr;
|
cardUpdateProcess = nullptr;
|
||||||
statusBar()->clearMessage();
|
statusBar()->clearMessage();
|
||||||
|
|
||||||
const auto reloadOk1 = QtConcurrent::run([] { CardDatabaseManager::getInstance()->loadCardDatabases(); });
|
// This can be the first successful load of the session (e.g. the onboarding
|
||||||
|
// download), so it must own the set check for that load.
|
||||||
|
reloadCardDatabaseAndCheckSets();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::reloadCardDatabaseAndCheckSets()
|
||||||
|
{
|
||||||
|
auto *watcher = new QFutureWatcher<void>(this);
|
||||||
|
connect(watcher, &QFutureWatcher<void>::finished, this, [watcher] {
|
||||||
|
watcher->deleteLater();
|
||||||
|
CardDatabaseManager::getInstance()->checkUnknownSets();
|
||||||
|
});
|
||||||
|
watcher->setFuture(QtConcurrent::run([] { CardDatabaseManager::getInstance()->loadCardDatabases(); }));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::cardUpdateError(QProcess::ProcessError err)
|
void MainWindow::cardUpdateError(QProcess::ProcessError err)
|
||||||
|
|
@ -1523,7 +1553,8 @@ void MainWindow::actAddCustomSet()
|
||||||
QMessageBox::information(
|
QMessageBox::information(
|
||||||
this, tr("Load sets/cards"),
|
this, tr("Load sets/cards"),
|
||||||
tr("The new sets/cards have been added successfully.\nCockatrice will now reload the card database."));
|
tr("The new sets/cards have been added successfully.\nCockatrice will now reload the card database."));
|
||||||
const auto reloadOk1 = QtConcurrent::run([] { CardDatabaseManager::getInstance()->loadCardDatabases(); });
|
// Imported files can introduce sets the config has never seen.
|
||||||
|
reloadCardDatabaseAndCheckSets();
|
||||||
} else {
|
} else {
|
||||||
QMessageBox::warning(this, tr("Load sets/cards"), tr("Sets/cards failed to import."));
|
QMessageBox::warning(this, tr("Load sets/cards"), tr("Sets/cards failed to import."));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,14 @@ private:
|
||||||
}
|
}
|
||||||
void createCardUpdateProcess(bool background = false);
|
void createCardUpdateProcess(bool background = false);
|
||||||
void exitCardDatabaseUpdate();
|
void exitCardDatabaseUpdate();
|
||||||
|
/**
|
||||||
|
* @brief Reloads the card database on a worker thread and runs checkUnknownSets() once the finished
|
||||||
|
* snapshot has been swapped in.
|
||||||
|
*
|
||||||
|
* The loader never runs checkUnknownSets() itself (see main.cpp); post-update loads that can introduce
|
||||||
|
* new sets must own the check, and it has to run after the queued snapshot swap, not on the worker.
|
||||||
|
*/
|
||||||
|
void reloadCardDatabaseAndCheckSets();
|
||||||
/**
|
/**
|
||||||
* @brief Tries to bring the open card database updater window to the front.
|
* @brief Tries to bring the open card database updater window to the front.
|
||||||
* @return true when the updater acknowledged the request
|
* @return true when the updater acknowledged the request
|
||||||
|
|
|
||||||
|
|
@ -343,11 +343,12 @@ int main(int argc, char *argv[])
|
||||||
// contention that happens when the load runs alongside window construction.
|
// contention that happens when the load runs alongside window construction.
|
||||||
// The CardDatabaseModel populates from the already-loaded data in its
|
// The CardDatabaseModel populates from the already-loaded data in its
|
||||||
// constructor, so the window appears fully populated with no startup lag.
|
// constructor, so the window appears fully populated with no startup lag.
|
||||||
// Note: checkUnknownSets() is deferred from the initial load to
|
// Note: checkUnknownSets() is deliberately not run for this front-loaded
|
||||||
// MainWindow::startupConfigCheck() so that
|
// parse. It would run before MainWindow exists, so
|
||||||
// cardDatabaseNewSetsFound / cardDatabaseAllNewSetsEnabled have live
|
// cardDatabaseNewSetsFound / cardDatabaseAllNewSetsEnabled would have no
|
||||||
// receivers when emitted. Subsequent reloads (e.g. path changes) call
|
// receivers, and enabling the sets up-front would suppress the first-run
|
||||||
// checkUnknownSets() directly from the loader after the first load.
|
// experience MainWindow::startupConfigCheck() and the onboarding wizard
|
||||||
|
// provide. Callers that load after startup own the check.
|
||||||
CardDatabaseManager::getInstance()->loadCardDatabases();
|
CardDatabaseManager::getInstance()->loadCardDatabases();
|
||||||
|
|
||||||
MainWindow ui;
|
MainWindow ui;
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QMessageLogger>
|
#include <QMessageLogger>
|
||||||
|
#include <QMetaObject>
|
||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
#include <QMutexLocker>
|
#include <QMutexLocker>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
|
|
@ -80,15 +81,17 @@ void CardDatabase::reloadCardDatabasesAndNotify()
|
||||||
{
|
{
|
||||||
// An explicit reload bypasses the binary cache so the files on disk are
|
// 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.
|
// always re-read, even when their size/mtime hash still matches the cache.
|
||||||
|
// Models refresh from the swap's cardDatabaseReset in a single batch, so
|
||||||
|
// no cardDatabaseEnabledSetsChanged is emitted here: that rebuilt the
|
||||||
|
// 36k-card models a second time on the GUI thread, which froze startup
|
||||||
|
// after enabling new/custom sets. The follow-up unknown-sets check is
|
||||||
|
// queued to the GUI thread so it sees the swapped-in data (this may run
|
||||||
|
// on a worker via QtConcurrent::run); it is silent unless new sets
|
||||||
|
// appeared mid-reload.
|
||||||
loadStatus = loader->loadCardDatabases(/* ignoreCache */ true);
|
loadStatus = loader->loadCardDatabases(/* ignoreCache */ true);
|
||||||
|
|
||||||
if (loadStatus == Ok) {
|
if (loadStatus == Ok) {
|
||||||
checkUnknownSets();
|
QMetaObject::invokeMethod(this, &CardDatabase::checkUnknownSets, Qt::AutoConnection);
|
||||||
// A reload reconstructs the exact same enabled-set state, so the cached
|
|
||||||
// set-name / alt-name data computed during construction is still valid.
|
|
||||||
// Skipping the per-card refresh avoids a full 36k-card recompute that
|
|
||||||
// would otherwise duplicate the work already done while building cards.
|
|
||||||
notifyEnabledSetsChanged(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,15 +210,19 @@ void CardDatabase::checkUnknownSets()
|
||||||
if (numUnknownSets > 0) {
|
if (numUnknownSets > 0) {
|
||||||
emit cardDatabaseNewSetsFound(numUnknownSets, unknownSetNames);
|
emit cardDatabaseNewSetsFound(numUnknownSets, unknownSetNames);
|
||||||
} else {
|
} else {
|
||||||
_sets.markAllAsKnown();
|
_sets.markAllAsKnownBatched(setPriorityController);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No set enabled. Probably this is the first time running trice
|
// No set enabled. Probably this is the first time running trice
|
||||||
_sets.guessSortKeys();
|
_sets.enableAllBatched(setPriorityController);
|
||||||
_sets.sortByKey();
|
_sets.sortByKey();
|
||||||
_sets.enableAll();
|
|
||||||
notifyEnabledSetsChanged();
|
|
||||||
|
|
||||||
|
// No cardDatabaseEnabledSetsChanged here: the reload chained off
|
||||||
|
// cardDatabaseAllNewSetsEnabled re-parses with the sets enabled and its
|
||||||
|
// swap's cardDatabaseReset rebuilds the models in one batch. Emitting
|
||||||
|
// both back-to-back rebuilt the 36k-card models twice on the GUI thread,
|
||||||
|
// stalling startup. (When the card list is empty the slot returns early
|
||||||
|
// without reloading, but there is nothing to display either.)
|
||||||
emit cardDatabaseAllNewSetsEnabled();
|
emit cardDatabaseAllNewSetsEnabled();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -223,13 +230,13 @@ void CardDatabase::checkUnknownSets()
|
||||||
void CardDatabase::enableAllUnknownSets()
|
void CardDatabase::enableAllUnknownSets()
|
||||||
{
|
{
|
||||||
auto _sets = getSetList();
|
auto _sets = getSetList();
|
||||||
_sets.enableAllUnknown();
|
_sets.enableAllUnknownBatched(setPriorityController);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CardDatabase::markAllSetsAsKnown()
|
void CardDatabase::markAllSetsAsKnown()
|
||||||
{
|
{
|
||||||
auto _sets = getSetList();
|
auto _sets = getSetList();
|
||||||
_sets.markAllAsKnown();
|
_sets.markAllAsKnownBatched(setPriorityController);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CardDatabase::notifyEnabledSetsChanged(bool recomputeCachedSets)
|
void CardDatabase::notifyEnabledSetsChanged(bool recomputeCachedSets)
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,13 @@ CardDatabaseLoader::CardDatabaseLoader(QObject *parent,
|
||||||
// the finished snapshot is swapped into the live database on the GUI thread.
|
// the finished snapshot is swapped into the live database on the GUI thread.
|
||||||
|
|
||||||
// when SettingsCache's path changes, trigger reloads
|
// when SettingsCache's path changes, trigger reloads
|
||||||
connect(pathProvider, &ICardDatabasePathProvider::cardDatabasePathChanged, this, [this] { loadCardDatabases(); });
|
connect(pathProvider, &ICardDatabasePathProvider::cardDatabasePathChanged, this, [this] {
|
||||||
|
loadCardDatabases();
|
||||||
|
// This reload runs synchronously on the emitting (GUI) thread, so the
|
||||||
|
// finished snapshot is already swapped in when it returns: enable any
|
||||||
|
// sets the newly pointed-at database introduced.
|
||||||
|
database->checkUnknownSets();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
CardDatabaseLoader::~CardDatabaseLoader()
|
CardDatabaseLoader::~CardDatabaseLoader()
|
||||||
|
|
@ -136,7 +142,23 @@ LoadStatus CardDatabaseLoader::doLoadCardDatabases(bool ignoreCache)
|
||||||
loadCardDatabase(p, data);
|
loadCardDatabase(p, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!saveToCache(data, sourceHash)) {
|
// A snapshot parsed while no set is enabled has had every printing
|
||||||
|
// stripped by the parsers, and the cache reader re-derives enablement
|
||||||
|
// from the priority controller, so such an entry can never be reused
|
||||||
|
// meaningfully. Skip writing it so a first-run (blank config) load
|
||||||
|
// cannot poison the cache with an empty snapshot. The check mirrors
|
||||||
|
// what the parsers saw: any set still reporting enabled means the
|
||||||
|
// snapshot is complete for the current enablement.
|
||||||
|
bool anySetEnabled = false;
|
||||||
|
for (const CardSetPtr &set : data.sets) {
|
||||||
|
if (set && set->getEnabled()) {
|
||||||
|
anySetEnabled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!data.sets.isEmpty() && !anySetEnabled) {
|
||||||
|
qCInfo(CardDatabaseLoadingLog) << "Skipping binary cache: no set is enabled yet, snapshot was stripped";
|
||||||
|
} else if (!saveToCache(data, sourceHash)) {
|
||||||
qCWarning(CardDatabaseLoadingLog) << "Failed to write binary cache to" << cachePath();
|
qCWarning(CardDatabaseLoadingLog) << "Failed to write binary cache to" << cachePath();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -148,18 +170,15 @@ LoadStatus CardDatabaseLoader::doLoadCardDatabases(bool ignoreCache)
|
||||||
database->refreshCachedReverseRelatedCards(data.cards);
|
database->refreshCachedReverseRelatedCards(data.cards);
|
||||||
|
|
||||||
qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Success";
|
qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Success";
|
||||||
|
// NOTE: checkUnknownSets() is intentionally NOT called here. During the
|
||||||
|
// front-loaded parse in main() MainWindow does not exist yet, so
|
||||||
|
// cardDatabaseNewSetsFound / cardDatabaseAllNewSetsEnabled would be
|
||||||
|
// emitted with no receivers, and enabling the sets up-front would
|
||||||
|
// suppress the first-run experience MainWindow::startupConfigCheck()
|
||||||
|
// and the onboarding wizard are designed around. Callers that load
|
||||||
|
// after startup own the check (and any reload it triggers).
|
||||||
emit databaseDataReady(std::move(data));
|
emit databaseDataReady(std::move(data));
|
||||||
emit loadingFinished();
|
emit loadingFinished();
|
||||||
// During the front-loaded parse in main() this runs before MainWindow
|
|
||||||
// exists, so cardDatabaseNewSetsFound / cardDatabaseAllNewSetsEnabled
|
|
||||||
// would be emitted with no receivers. Skip the check on the first load;
|
|
||||||
// MainWindow::startupConfigCheck() calls checkUnknownSets() once its
|
|
||||||
// signal connections are live. On subsequent reloads (e.g. path change)
|
|
||||||
// MainWindow exists and signals have live receivers.
|
|
||||||
if (initialLoadComplete) {
|
|
||||||
database->checkUnknownSets();
|
|
||||||
}
|
|
||||||
initialLoadComplete = true;
|
|
||||||
} else {
|
} else {
|
||||||
qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Failed";
|
qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Failed";
|
||||||
emit loadingFailed(); // bring up the settings dialog
|
emit loadingFailed(); // bring up the settings dialog
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,6 @@ private:
|
||||||
|
|
||||||
QBasicMutex *loadFromFileMutex = new QBasicMutex(); /**< Mutex for single-file loading. */
|
QBasicMutex *loadFromFileMutex = new QBasicMutex(); /**< Mutex for single-file loading. */
|
||||||
QBasicMutex *reloadDatabaseMutex = new QBasicMutex(); /**< Mutex for reloading entire database. */
|
QBasicMutex *reloadDatabaseMutex = new QBasicMutex(); /**< Mutex for reloading entire database. */
|
||||||
bool initialLoadComplete = false; /**< Set after the first successful load. */
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COCKATRICE_CARD_DATABASE_LOADER_H
|
#endif // COCKATRICE_CARD_DATABASE_LOADER_H
|
||||||
|
|
|
||||||
|
|
@ -227,6 +227,10 @@ public:
|
||||||
* @param _isknown True if known, false if unknown.
|
* @param _isknown True if known, false if unknown.
|
||||||
*/
|
*/
|
||||||
void setIsKnown(bool _isknown);
|
void setIsKnown(bool _isknown);
|
||||||
|
void setIsKnownInMemory(bool _isknown)
|
||||||
|
{
|
||||||
|
isknown = _isknown;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Determines whether the set has incomplete metadata and should be ignored.
|
* @brief Determines whether the set has incomplete metadata and should be ignored.
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
#include "card_set_list.h"
|
#include "card_set_list.h"
|
||||||
|
|
||||||
#include "libcockatrice/card/set/card_set.h"
|
#include "libcockatrice/card/set/card_set.h"
|
||||||
|
#include "libcockatrice/interfaces/interface_card_set_priority_controller.h"
|
||||||
|
|
||||||
#include <QDate>
|
#include <QDate>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QVector>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <compare>
|
#include <compare>
|
||||||
|
|
||||||
|
|
@ -62,60 +64,87 @@ QStringList CardSetList::getUnknownSetsNames()
|
||||||
return sets;
|
return sets;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CardSetList::enableAllUnknown()
|
void CardSetList::enableAllUnknownBatched(ICardSetPriorityController *controller)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < size(); ++i) {
|
QVector<ICardSetPriorityController::SetSaveData> saveData;
|
||||||
CardSetPtr set = at(i);
|
|
||||||
if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) {
|
|
||||||
set->setIsKnown(true);
|
|
||||||
set->setEnabled(true);
|
|
||||||
} else if (set && set->getIsKnownIgnored() && !set->getEnabled()) {
|
|
||||||
set->setEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CardSetList::enableAll()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < size(); ++i) {
|
|
||||||
CardSetPtr set = at(i);
|
|
||||||
|
|
||||||
if (set == nullptr) {
|
|
||||||
// qCWarning(CardInfoLog) << "enabledAll has null";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!set->getIsKnownIgnored()) {
|
|
||||||
set->setIsKnown(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
set->setEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CardSetList::markAllAsKnown()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < size(); ++i) {
|
|
||||||
CardSetPtr set = at(i);
|
|
||||||
if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) {
|
|
||||||
set->setIsKnown(true);
|
|
||||||
set->setEnabled(false);
|
|
||||||
} else if (set && set->getIsKnownIgnored() && !set->getEnabled()) {
|
|
||||||
set->setEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CardSetList::guessSortKeys()
|
|
||||||
{
|
|
||||||
defaultSort();
|
|
||||||
for (int i = 0; i < size(); ++i) {
|
for (int i = 0; i < size(); ++i) {
|
||||||
CardSetPtr set = at(i);
|
CardSetPtr set = at(i);
|
||||||
if (set.isNull()) {
|
if (set.isNull()) {
|
||||||
// qCWarning(CardInfoLog) << "guessSortKeys set is null";
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
set->setSortKey(i);
|
|
||||||
|
bool changed = false;
|
||||||
|
if (!set->getIsKnown() && !set->getIsKnownIgnored()) {
|
||||||
|
set->setIsKnownInMemory(true);
|
||||||
|
set->setEnabledInMemory(true);
|
||||||
|
changed = true;
|
||||||
|
} else if (set->getIsKnownIgnored() && !set->getEnabled()) {
|
||||||
|
set->setEnabledInMemory(true);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
saveData.append(
|
||||||
|
{set->getShortName(), static_cast<unsigned int>(set->getSortKey()), true, set->getIsKnown()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!saveData.isEmpty()) {
|
||||||
|
controller->saveSets(saveData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardSetList::enableAllBatched(ICardSetPriorityController *controller)
|
||||||
|
{
|
||||||
|
defaultSort();
|
||||||
|
|
||||||
|
QVector<ICardSetPriorityController::SetSaveData> saveData;
|
||||||
|
saveData.reserve(size());
|
||||||
|
for (int i = 0; i < size(); ++i) {
|
||||||
|
CardSetPtr set = at(i);
|
||||||
|
if (set.isNull()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto sortKey = static_cast<unsigned int>(i);
|
||||||
|
set->setSortKeyInMemory(sortKey);
|
||||||
|
if (!set->getIsKnownIgnored()) {
|
||||||
|
set->setIsKnownInMemory(true);
|
||||||
|
}
|
||||||
|
set->setEnabledInMemory(true);
|
||||||
|
saveData.append({set->getShortName(), sortKey, true, set->getIsKnown()});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!saveData.isEmpty()) {
|
||||||
|
controller->saveSets(saveData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardSetList::markAllAsKnownBatched(ICardSetPriorityController *controller)
|
||||||
|
{
|
||||||
|
QVector<ICardSetPriorityController::SetSaveData> saveData;
|
||||||
|
for (int i = 0; i < size(); ++i) {
|
||||||
|
CardSetPtr set = at(i);
|
||||||
|
if (set.isNull()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool changed = false;
|
||||||
|
if (!set->getIsKnown() && !set->getIsKnownIgnored()) {
|
||||||
|
set->setIsKnownInMemory(true);
|
||||||
|
set->setEnabledInMemory(false);
|
||||||
|
changed = true;
|
||||||
|
} else if (set->getIsKnownIgnored() && !set->getEnabled()) {
|
||||||
|
set->setEnabledInMemory(true);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
saveData.append({set->getShortName(), static_cast<unsigned int>(set->getSortKey()), set->getEnabled(),
|
||||||
|
set->getIsKnown()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!saveData.isEmpty()) {
|
||||||
|
controller->saveSets(saveData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,36 +40,37 @@ public:
|
||||||
void sortByKey();
|
void sortByKey();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Reassigns sort keys based on the current order.
|
* @brief Enables every set with fresh sort keys, persisting in one flush.
|
||||||
*
|
*
|
||||||
* Calls defaultSort() and then assigns sequential sort keys
|
* Assigns sequential sort keys in default-sorted order and marks every set
|
||||||
* to all sets according to their resulting positions, replacing
|
* known and enabled, writing all records through the controller's batched
|
||||||
* any existing sort keys to ensure consistent ordering.
|
* saveSets() in a single settings flush instead of ~3 QSettings rewrite
|
||||||
|
* cycles per set, which froze the GUI thread for seconds on a first run
|
||||||
|
* with a full database.
|
||||||
|
* @param controller The set priority controller to persist the records with.
|
||||||
*/
|
*/
|
||||||
void guessSortKeys();
|
void enableAllBatched(ICardSetPriorityController *controller);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Enables all sets that are unknown or ignored.
|
* @brief Enables all sets that are unknown or ignored, persisting in one flush.
|
||||||
*
|
*
|
||||||
* Sets that are not marked as known and not ignored are marked as known
|
* Sets that are not marked as known and not ignored are marked as known
|
||||||
* and enabled. Ignored-known sets are also enabled, but remain ignored.
|
* and enabled. Ignored-known sets are also enabled, but remain ignored.
|
||||||
|
* Only changed sets are written, so the common case of a few new sets costs
|
||||||
|
* a single settings flush no matter how large the database is.
|
||||||
|
* @param controller The set priority controller to persist the records with.
|
||||||
*/
|
*/
|
||||||
void enableAllUnknown();
|
void enableAllUnknownBatched(ICardSetPriorityController *controller);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Enables all sets in the list.
|
* @brief Marks all sets as known and adjusts their enabled state, persisting in one flush.
|
||||||
*
|
|
||||||
* Equivalent to calling `setEnabled(true)` on each entry.
|
|
||||||
*/
|
|
||||||
void enableAll();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Marks all sets as known and adjusts their enabled state.
|
|
||||||
*
|
*
|
||||||
* Unknown, non-ignored sets become known and disabled.
|
* Unknown, non-ignored sets become known and disabled.
|
||||||
* Ignored-known sets are enabled if they were previously disabled.
|
* Ignored-known sets are enabled if they were previously disabled.
|
||||||
|
* Only changed sets are written; when nothing is unknown no flush happens at all.
|
||||||
|
* @param controller The set priority controller to persist the records with.
|
||||||
*/
|
*/
|
||||||
void markAllAsKnown();
|
void markAllAsKnownBatched(ICardSetPriorityController *controller);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Counts the number of sets that are currently enabled.
|
* @brief Counts the number of sets that are currently enabled.
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ public:
|
||||||
QString shortName;
|
QString shortName;
|
||||||
unsigned int sortKey;
|
unsigned int sortKey;
|
||||||
bool enabled;
|
bool enabled;
|
||||||
|
bool isKnown = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -57,6 +58,14 @@ public:
|
||||||
*/
|
*/
|
||||||
virtual QStringList getEnabledSetNames() const = 0;
|
virtual QStringList getEnabledSetNames() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Persists per-set options for every entry in a single settings flush.
|
||||||
|
*
|
||||||
|
* Each individual setSortKey/setEnabled/setIsKnown call re-opens and re-syncs
|
||||||
|
* the whole .ini when backed by QSettings, so persisting hundreds of sets
|
||||||
|
* one option at a time freezes the GUI thread for seconds. Callers that touch
|
||||||
|
* more than a handful of sets must bundle the records and call this once.
|
||||||
|
*/
|
||||||
virtual void saveSets(const QVector<SetSaveData> &data) = 0;
|
virtual void saveSets(const QVector<SetSaveData> &data) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -266,7 +266,7 @@ void SetsModel::save(CardDatabase *db)
|
||||||
const bool enabled = enabledSets.contains(sets[i]);
|
const bool enabled = enabledSets.contains(sets[i]);
|
||||||
sets[i]->setSortKeyInMemory(sortKey);
|
sets[i]->setSortKeyInMemory(sortKey);
|
||||||
sets[i]->setEnabledInMemory(enabled);
|
sets[i]->setEnabledInMemory(enabled);
|
||||||
saveData.append({sets[i]->getShortName(), sortKey, enabled});
|
saveData.append({sets[i]->getShortName(), sortKey, enabled, sets[i]->getIsKnown()});
|
||||||
}
|
}
|
||||||
|
|
||||||
db->getPriorityController()->saveSets(saveData);
|
db->getPriorityController()->saveSets(saveData);
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,7 @@ void CardDatabaseSettings::saveSets(const QVector<ICardSetPriorityController::Se
|
||||||
s.beginGroup(entry.shortName);
|
s.beginGroup(entry.shortName);
|
||||||
s.setValue("sortKey", entry.sortKey);
|
s.setValue("sortKey", entry.sortKey);
|
||||||
s.setValue("enabled", entry.enabled);
|
s.setValue("enabled", entry.enabled);
|
||||||
|
s.setValue("isKnown", entry.isKnown);
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
}
|
}
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
@ -121,5 +122,6 @@ void CardDatabaseSettings::saveSets(const QVector<ICardSetPriorityController::Se
|
||||||
SetOptions &o = setOptionsCache[entry.shortName];
|
SetOptions &o = setOptionsCache[entry.shortName];
|
||||||
o.sortKey = entry.sortKey;
|
o.sortKey = entry.sortKey;
|
||||||
o.enabled = entry.enabled;
|
o.enabled = entry.enabled;
|
||||||
|
o.isKnown = entry.isKnown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -159,16 +159,19 @@ public:
|
||||||
|
|
||||||
void setSortKey(QString shortName, unsigned int sortKey) override
|
void setSortKey(QString shortName, unsigned int sortKey) override
|
||||||
{
|
{
|
||||||
|
++individualWrites;
|
||||||
setOptions[shortName].sortKey = sortKey;
|
setOptions[shortName].sortKey = sortKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setEnabled(QString shortName, bool enabled) override
|
void setEnabled(QString shortName, bool enabled) override
|
||||||
{
|
{
|
||||||
|
++individualWrites;
|
||||||
setOptions[shortName].enabled = enabled;
|
setOptions[shortName].enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setIsKnown(QString shortName, bool isknown) override
|
void setIsKnown(QString shortName, bool isknown) override
|
||||||
{
|
{
|
||||||
|
++individualWrites;
|
||||||
setOptions[shortName].isKnown = isknown;
|
setOptions[shortName].isKnown = isknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -206,12 +209,17 @@ public:
|
||||||
|
|
||||||
void saveSets(const QVector<SetSaveData> &data) override
|
void saveSets(const QVector<SetSaveData> &data) override
|
||||||
{
|
{
|
||||||
|
++batchedWrites;
|
||||||
for (const SetSaveData &entry : data) {
|
for (const SetSaveData &entry : data) {
|
||||||
setOptions[entry.shortName].sortKey = entry.sortKey;
|
setOptions[entry.shortName].sortKey = entry.sortKey;
|
||||||
setOptions[entry.shortName].enabled = entry.enabled;
|
setOptions[entry.shortName].enabled = entry.enabled;
|
||||||
|
setOptions[entry.shortName].isKnown = entry.isKnown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int individualWrites = 0; ///< Per-set/per-option controller writes (the slow path).
|
||||||
|
int batchedWrites = 0; ///< Batched saveSets() flushes (the fast path).
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QHash<QString, SetOptions> setOptions;
|
QHash<QString, SetOptions> setOptions;
|
||||||
};
|
};
|
||||||
|
|
@ -375,6 +383,131 @@ TEST_F(CardDatabaseCacheInvalidationTest, ExplicitReloadBypassesCache)
|
||||||
EXPECT_FALSE(db.getCardList().value("Cat")->getSets().contains("CAT"));
|
EXPECT_FALSE(db.getCardList().value("Cat")->getSets().contains("CAT"));
|
||||||
EXPECT_TRUE(db.getCardList().value("Cat")->getSets().contains("DOG"));
|
EXPECT_TRUE(db.getCardList().value("Cat")->getSets().contains("DOG"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(CardDatabaseCacheInvalidationTest, FirstRunWithNoSetsEnabledSkipsCacheWrite)
|
||||||
|
{
|
||||||
|
// A blank config: the parser discovers every set, but none of them has an
|
||||||
|
// ini entry, so all of them default to disabled (first run).
|
||||||
|
SeededSetPriorityController blankController;
|
||||||
|
NoopCardPreferenceProvider prefs;
|
||||||
|
SandboxPathProvider pathProvider(root);
|
||||||
|
CardDatabase db(nullptr, &prefs, &pathProvider, &blankController);
|
||||||
|
|
||||||
|
db.loadCardDatabases();
|
||||||
|
ASSERT_FALSE(db.getCardList().value("Cat").isNull()) << "cards are parsed even while their sets are disabled";
|
||||||
|
EXPECT_TRUE(db.getCardList().value("Cat")->getSets().isEmpty())
|
||||||
|
<< "no printings survive an all-sets-disabled parse";
|
||||||
|
EXPECT_TRUE(cacheContents().isEmpty()) << "a stripped first-run snapshot must not be cached";
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CardDatabaseCacheInvalidationTest, EnablingAllSetsAfterFirstRunRestoresPrintings)
|
||||||
|
{
|
||||||
|
SeededSetPriorityController blankController;
|
||||||
|
NoopCardPreferenceProvider prefs;
|
||||||
|
SandboxPathProvider pathProvider(root);
|
||||||
|
CardDatabase db(nullptr, &prefs, &pathProvider, &blankController);
|
||||||
|
|
||||||
|
db.loadCardDatabases();
|
||||||
|
ASSERT_TRUE(cacheContents().isEmpty());
|
||||||
|
|
||||||
|
// This mirrors what checkUnknownSets() does on a first run: enable every
|
||||||
|
// set, then reload so the parser keeps the printings and the complete
|
||||||
|
// snapshot gets cached under the enabled-set hash.
|
||||||
|
for (const CardSetPtr &set : db.getSetList()) {
|
||||||
|
blankController.setEnabled(set->getShortName(), true);
|
||||||
|
}
|
||||||
|
db.reloadCardDatabasesAndNotify();
|
||||||
|
|
||||||
|
ASSERT_FALSE(db.getCardList().value("Cat").isNull());
|
||||||
|
EXPECT_TRUE(db.getCardList().value("Cat")->getSets().contains("CAT"));
|
||||||
|
EXPECT_FALSE(cacheContents().isEmpty()) << "the enabled-set snapshot must be cached";
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CardDatabaseCacheInvalidationTest, CheckUnknownSetsBatchesFirstRunEnableAll)
|
||||||
|
{
|
||||||
|
// Same first-run starting point as above, but driven through the real
|
||||||
|
// checkUnknownSets() path, which must persist the enable-all with a single
|
||||||
|
// batched saveSets() instead of per-set/per-option controller writes.
|
||||||
|
SeededSetPriorityController blankController;
|
||||||
|
NoopCardPreferenceProvider prefs;
|
||||||
|
SandboxPathProvider pathProvider(root);
|
||||||
|
CardDatabase db(nullptr, &prefs, &pathProvider, &blankController);
|
||||||
|
|
||||||
|
db.loadCardDatabases();
|
||||||
|
ASSERT_TRUE(db.getSetList().getEnabledSetsNum() == 0);
|
||||||
|
|
||||||
|
db.checkUnknownSets();
|
||||||
|
|
||||||
|
EXPECT_EQ(blankController.individualWrites, 0) << "first-run enable-all must not write per set";
|
||||||
|
EXPECT_EQ(blankController.batchedWrites, 1) << "first-run enable-all must persist with a single flush";
|
||||||
|
for (const CardSetPtr &set : db.getSetList()) {
|
||||||
|
EXPECT_TRUE(set->getEnabled()) << set->getShortName().toStdString();
|
||||||
|
EXPECT_TRUE(blankController.isEnabled(set->getShortName())) << set->getShortName().toStdString();
|
||||||
|
if (!set->getIsKnownIgnored()) {
|
||||||
|
EXPECT_TRUE(set->getIsKnown()) << set->getShortName().toStdString();
|
||||||
|
EXPECT_TRUE(blankController.isKnown(set->getShortName())) << set->getShortName().toStdString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The follow-up reload re-parses with the sets enabled, restoring printings.
|
||||||
|
db.reloadCardDatabasesAndNotify();
|
||||||
|
ASSERT_FALSE(db.getCardList().value("Cat").isNull());
|
||||||
|
EXPECT_TRUE(db.getCardList().value("Cat")->getSets().contains("CAT"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CardDatabaseCacheInvalidationTest, EnableAllUnknownSetsIsBatched)
|
||||||
|
{
|
||||||
|
// The "new sets found" dialog path: enabling the unknown delta must persist
|
||||||
|
// with a single flush no matter how many sets the database holds, so users
|
||||||
|
// with hundreds of custom sets don't stall the GUI thread. Built directly
|
||||||
|
// on CardSet objects because the fixture sets carry no metadata (and would
|
||||||
|
// all count as ignored).
|
||||||
|
SeededSetPriorityController controller;
|
||||||
|
CardSetList sets;
|
||||||
|
sets.append(
|
||||||
|
CardSet::newInstance(&controller, "AAA", "Alpha Set", "core", QDate(2020, 1, 1), CardSet::PriorityPrimary));
|
||||||
|
sets.append(
|
||||||
|
CardSet::newInstance(&controller, "BBB", "Beta Set", "expansion", QDate(2021, 6, 1), CardSet::PriorityPrimary));
|
||||||
|
sets.append(CardSet::newInstance(&controller, "XXX")); // no metadata: ignored
|
||||||
|
|
||||||
|
sets.enableAllUnknownBatched(&controller);
|
||||||
|
|
||||||
|
EXPECT_EQ(controller.individualWrites, 0) << "unknown-set enable must not write per set";
|
||||||
|
EXPECT_EQ(controller.batchedWrites, 1) << "unknown-set enable must persist with a single flush";
|
||||||
|
for (const CardSetPtr &set : sets) {
|
||||||
|
EXPECT_TRUE(set->getEnabled()) << set->getShortName().toStdString();
|
||||||
|
EXPECT_TRUE(controller.isEnabled(set->getShortName())) << set->getShortName().toStdString();
|
||||||
|
if (set->getIsKnownIgnored()) {
|
||||||
|
EXPECT_FALSE(set->getIsKnown()) << set->getShortName().toStdString();
|
||||||
|
} else {
|
||||||
|
EXPECT_TRUE(set->getIsKnown()) << set->getShortName().toStdString();
|
||||||
|
EXPECT_TRUE(controller.isKnown(set->getShortName())) << set->getShortName().toStdString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CardDatabaseCacheInvalidationTest, MarkAllSetsAsKnownIsBatched)
|
||||||
|
{
|
||||||
|
// The dialog's "No" path: same single-flush requirement as enabling.
|
||||||
|
SeededSetPriorityController controller;
|
||||||
|
CardSetList sets;
|
||||||
|
sets.append(
|
||||||
|
CardSet::newInstance(&controller, "AAA", "Alpha Set", "core", QDate(2020, 1, 1), CardSet::PriorityPrimary));
|
||||||
|
sets.append(
|
||||||
|
CardSet::newInstance(&controller, "BBB", "Beta Set", "expansion", QDate(2021, 6, 1), CardSet::PriorityPrimary));
|
||||||
|
sets.append(CardSet::newInstance(&controller, "XXX")); // no metadata: ignored
|
||||||
|
|
||||||
|
sets.markAllAsKnownBatched(&controller);
|
||||||
|
|
||||||
|
EXPECT_EQ(controller.individualWrites, 0) << "mark-known must not write per set";
|
||||||
|
EXPECT_EQ(controller.batchedWrites, 1) << "mark-known must persist with a single flush";
|
||||||
|
EXPECT_TRUE(controller.isKnown("AAA"));
|
||||||
|
EXPECT_TRUE(controller.isKnown("BBB"));
|
||||||
|
EXPECT_FALSE(controller.isEnabled("AAA"));
|
||||||
|
EXPECT_FALSE(controller.isEnabled("BBB"));
|
||||||
|
// Ignored sets keep their known state but are enabled.
|
||||||
|
EXPECT_TRUE(controller.isEnabled("XXX"));
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue