mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-12 04:50:01 -08:00
Compare commits
2 commits
bac2d285e3
...
24874b10cf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24874b10cf | ||
|
|
a712553c81 |
22 changed files with 571 additions and 131 deletions
|
|
@ -10,6 +10,7 @@
|
|||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QFutureWatcher>
|
||||
#include <QIODevice>
|
||||
#include <QList>
|
||||
#include <QLocale>
|
||||
|
|
@ -177,7 +178,15 @@ bool SpoilerBackgroundUpdater::saveDownloadedFile(QByteArray data)
|
|||
|
||||
// Data written, so reload the card database
|
||||
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
|
||||
// when the database was last updated
|
||||
|
|
|
|||
|
|
@ -133,6 +133,10 @@ SettingsCache::SettingsCache()
|
|||
// Migrate settings from global.ini to per-class INI files (runs at most once)
|
||||
SettingsMigration::migrateSettingsFromGlobalIni(settingsPath);
|
||||
|
||||
// Repair gamefilters.ini keys renamed to camelCase by #7084 for profiles that migrated
|
||||
// before that rename (idempotent, safe to run on every startup, portable builds included).
|
||||
SettingsMigration::migrateGameFiltersToCamelCase(settingsPath);
|
||||
|
||||
shortcutsSettings = new ShortcutsSettings(settingsPath, this);
|
||||
cardDatabaseSettings = new CardDatabaseSettings(settingsPath, this);
|
||||
serversSettings = new ServersSettings(settingsPath, this);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ struct GameFilterConfigs
|
|||
static constexpr int DEFAULT_MAX_PLAYERS_MAX = 99;
|
||||
|
||||
bool hideBuddiesOnlyGames = false;
|
||||
bool hideIgnoredUserGames = false;
|
||||
// Defaults to true to match GameFiltersSettings::isHideIgnoredUserGames(), so freshly loaded
|
||||
// filters compare equal to a default-constructed config.
|
||||
bool hideIgnoredUserGames = true;
|
||||
bool hideFullGames = false;
|
||||
bool hideGamesThatStarted = false;
|
||||
bool hidePasswordProtectedGames = false;
|
||||
|
|
|
|||
|
|
@ -113,6 +113,17 @@ GameSelector::GameSelector(AbstractClient *_client,
|
|||
quickFilterToolBar->setVisible(
|
||||
SettingsCache::instance().userInterface().getShowGameSelectorFilterToolbar());
|
||||
});
|
||||
// The quick-filter toolbar only updates the in-memory model, so persist every change.
|
||||
// Otherwise each new GameSelector (room switch, reconnect, restart) would revert to stale
|
||||
// saved values and e.g. hide full/started games the user had unchecked. The save is
|
||||
// debounced so typing in the search field does not rewrite the settings file on every
|
||||
// keystroke; a pending save is flushed in the destructor.
|
||||
filterSaveDebounceTimer.setSingleShot(true);
|
||||
filterSaveDebounceTimer.setInterval(500);
|
||||
connect(&filterSaveDebounceTimer, &QTimer::timeout, this,
|
||||
[this] { gameListProxyModel->saveFilterParameters(gameTypeMap); });
|
||||
connect(gameListProxyModel, &GamesProxyModel::filtersChanged, this,
|
||||
[this] { filterSaveDebounceTimer.start(); });
|
||||
} else {
|
||||
quickFilterToolBar = nullptr;
|
||||
}
|
||||
|
|
@ -187,6 +198,18 @@ GameSelector::GameSelector(AbstractClient *_client,
|
|||
connect(client, &AbstractClient::removeFromListEventReceived, this, &GameSelector::processRemoveFromListEvent);
|
||||
}
|
||||
|
||||
GameSelector::~GameSelector()
|
||||
{
|
||||
// Nothing else persists quick-filter toolbar edits: the toolbar only updates the in-memory model,
|
||||
// and the debounced timer above is the sole writer for that path (actSetFilter/actClearFilter save
|
||||
// synchronously, but the toolbar does not). Flush a pending debounced save so edits made within
|
||||
// the debounce interval before destruction are not lost on room switch, reconnect, or close.
|
||||
if (filterSaveDebounceTimer.isActive()) {
|
||||
filterSaveDebounceTimer.stop();
|
||||
gameListProxyModel->saveFilterParameters(gameTypeMap);
|
||||
}
|
||||
}
|
||||
|
||||
void GameSelector::ignoreListReceived(const QList<ServerInfo_User> &)
|
||||
{
|
||||
gameListProxyModel->refresh();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "game_type_map.h"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QTimer>
|
||||
#include <qtmetamacros.h>
|
||||
// IWYU pragma: keep
|
||||
// ServerInfo_User appears by value and inside QList in slot signatures, so the
|
||||
|
|
@ -148,6 +149,9 @@ private:
|
|||
const bool showFilters; /**< Determines whether filter buttons are displayed. */
|
||||
GameTypeMap gameTypeMap; /**< Mapping of game types for the current room. */
|
||||
|
||||
QTimer filterSaveDebounceTimer; /**< Delays persisting quick-filter edits so typing does not rewrite the settings
|
||||
file on every keystroke. */
|
||||
|
||||
/**
|
||||
* @brief Updates the widget title to reflect the current number of displayed games.
|
||||
*
|
||||
|
|
@ -211,6 +215,7 @@ public:
|
|||
const bool restoresettings,
|
||||
const bool _showfilters,
|
||||
QWidget *parent = nullptr);
|
||||
~GameSelector() override;
|
||||
|
||||
/**
|
||||
* @brief Updates UI text for translation/localization.
|
||||
|
|
|
|||
|
|
@ -523,6 +523,9 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow) const
|
|||
|
||||
void GamesProxyModel::refresh()
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0))
|
||||
beginFilterChange();
|
||||
#endif
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 10, 0))
|
||||
endFilterChange(QSortFilterProxyModel::Direction::Rows);
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@
|
|||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QFlags>
|
||||
#include <QFutureWatcher>
|
||||
#include <QIODevice>
|
||||
#include <QLocalSocket>
|
||||
#include <QMenu>
|
||||
|
|
@ -640,10 +641,10 @@ void MainWindow::startupConfigCheck()
|
|||
|
||||
const bool isCleanInstall = SettingsCache::instance().network().getClientVersion() == CLIENT_INFO_NOT_SET;
|
||||
|
||||
// checkUnknownSets() is intentionally deferred from the card database load
|
||||
// (which runs in main() before MainWindow exists) so that
|
||||
// cardDatabaseNewSetsFound / cardDatabaseAllNewSetsEnabled have live
|
||||
// receivers when emitted.
|
||||
// The loader never runs checkUnknownSets() itself (see main.cpp): the
|
||||
// front-loaded parse runs before MainWindow exists, so
|
||||
// cardDatabaseNewSetsFound / cardDatabaseAllNewSetsEnabled would have no
|
||||
// receivers. MainWindow owns the check after startup.
|
||||
// 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
|
||||
// Manage Sets dialog don't appear first.
|
||||
|
|
@ -1137,18 +1138,35 @@ void MainWindow::cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknow
|
|||
|
||||
void MainWindow::cardDatabaseAllNewSetsEnabled()
|
||||
{
|
||||
if (firstRunWizardActive || CardDatabaseManager::getInstance()->getCardList().isEmpty()) {
|
||||
// The onboarding wizard owns the first-run messaging on a clean install,
|
||||
// and with no card data there are no sets to have enabled.
|
||||
auto *cardDatabase = CardDatabaseManager::getInstance();
|
||||
|
||||
// With no card data there is nothing to have enabled and nothing to reparse.
|
||||
if (cardDatabase->getCardList().isEmpty()) {
|
||||
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."));
|
||||
actManageSets();
|
||||
// checkUnknownSets() enabled every set after the database had already been
|
||||
// parsed under the (empty) first-run enablement, so the parser dropped every
|
||||
// printing of every set. Reparse now that the sets are enabled, otherwise the
|
||||
// deck editor's card database display stays empty until the next launch.
|
||||
auto *watcher = new QFutureWatcher<void>(this);
|
||||
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 */
|
||||
|
|
@ -1321,7 +1339,19 @@ void MainWindow::exitCardDatabaseUpdate()
|
|||
cardUpdateProcess = nullptr;
|
||||
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)
|
||||
|
|
@ -1523,7 +1553,8 @@ void MainWindow::actAddCustomSet()
|
|||
QMessageBox::information(
|
||||
this, tr("Load sets/cards"),
|
||||
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 {
|
||||
QMessageBox::warning(this, tr("Load sets/cards"), tr("Sets/cards failed to import."));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,6 +158,14 @@ private:
|
|||
}
|
||||
void createCardUpdateProcess(bool background = false);
|
||||
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.
|
||||
* @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.
|
||||
// The CardDatabaseModel populates from the already-loaded data in its
|
||||
// constructor, so the window appears fully populated with no startup lag.
|
||||
// Note: checkUnknownSets() is deferred from the initial load to
|
||||
// MainWindow::startupConfigCheck() so that
|
||||
// cardDatabaseNewSetsFound / cardDatabaseAllNewSetsEnabled have live
|
||||
// receivers when emitted. Subsequent reloads (e.g. path changes) call
|
||||
// checkUnknownSets() directly from the loader after the first load.
|
||||
// Note: checkUnknownSets() is deliberately not run for this front-loaded
|
||||
// parse. It would run before MainWindow exists, so
|
||||
// cardDatabaseNewSetsFound / cardDatabaseAllNewSetsEnabled would have no
|
||||
// receivers, and enabling the sets up-front would suppress the first-run
|
||||
// experience MainWindow::startupConfigCheck() and the onboarding wizard
|
||||
// provide. Callers that load after startup own the check.
|
||||
CardDatabaseManager::getInstance()->loadCardDatabases();
|
||||
|
||||
MainWindow ui;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include <QDebug>
|
||||
#include <QList>
|
||||
#include <QMessageLogger>
|
||||
#include <QMetaObject>
|
||||
#include <QMetaType>
|
||||
#include <QMutexLocker>
|
||||
#include <QSharedPointer>
|
||||
|
|
@ -80,15 +81,17 @@ void CardDatabase::reloadCardDatabasesAndNotify()
|
|||
{
|
||||
// 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.
|
||||
// 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);
|
||||
|
||||
if (loadStatus == Ok) {
|
||||
checkUnknownSets();
|
||||
// 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);
|
||||
QMetaObject::invokeMethod(this, &CardDatabase::checkUnknownSets, Qt::AutoConnection);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -207,15 +210,19 @@ void CardDatabase::checkUnknownSets()
|
|||
if (numUnknownSets > 0) {
|
||||
emit cardDatabaseNewSetsFound(numUnknownSets, unknownSetNames);
|
||||
} else {
|
||||
_sets.markAllAsKnown();
|
||||
_sets.markAllAsKnownBatched(setPriorityController);
|
||||
}
|
||||
} else {
|
||||
// No set enabled. Probably this is the first time running trice
|
||||
_sets.guessSortKeys();
|
||||
_sets.enableAllBatched(setPriorityController);
|
||||
_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();
|
||||
}
|
||||
}
|
||||
|
|
@ -223,13 +230,13 @@ void CardDatabase::checkUnknownSets()
|
|||
void CardDatabase::enableAllUnknownSets()
|
||||
{
|
||||
auto _sets = getSetList();
|
||||
_sets.enableAllUnknown();
|
||||
_sets.enableAllUnknownBatched(setPriorityController);
|
||||
}
|
||||
|
||||
void CardDatabase::markAllSetsAsKnown()
|
||||
{
|
||||
auto _sets = getSetList();
|
||||
_sets.markAllAsKnown();
|
||||
_sets.markAllAsKnownBatched(setPriorityController);
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
// 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()
|
||||
|
|
@ -136,7 +142,23 @@ LoadStatus CardDatabaseLoader::doLoadCardDatabases(bool ignoreCache)
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -148,18 +170,15 @@ LoadStatus CardDatabaseLoader::doLoadCardDatabases(bool ignoreCache)
|
|||
database->refreshCachedReverseRelatedCards(data.cards);
|
||||
|
||||
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 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 {
|
||||
qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Failed";
|
||||
emit loadingFailed(); // bring up the settings dialog
|
||||
|
|
|
|||
|
|
@ -179,7 +179,6 @@ private:
|
|||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -227,6 +227,10 @@ public:
|
|||
* @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.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
#include "card_set_list.h"
|
||||
|
||||
#include "libcockatrice/card/set/card_set.h"
|
||||
#include "libcockatrice/interfaces/interface_card_set_priority_controller.h"
|
||||
|
||||
#include <QDate>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <algorithm>
|
||||
#include <compare>
|
||||
|
||||
|
|
@ -62,60 +64,87 @@ QStringList CardSetList::getUnknownSetsNames()
|
|||
return sets;
|
||||
}
|
||||
|
||||
void CardSetList::enableAllUnknown()
|
||||
void CardSetList::enableAllUnknownBatched(ICardSetPriorityController *controller)
|
||||
{
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
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();
|
||||
QVector<ICardSetPriorityController::SetSaveData> saveData;
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
CardSetPtr set = at(i);
|
||||
if (set.isNull()) {
|
||||
// qCWarning(CardInfoLog) << "guessSortKeys set is null";
|
||||
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();
|
||||
|
||||
/**
|
||||
* @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
|
||||
* to all sets according to their resulting positions, replacing
|
||||
* any existing sort keys to ensure consistent ordering.
|
||||
* Assigns sequential sort keys in default-sorted order and marks every set
|
||||
* known and enabled, writing all records through the controller's batched
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* Equivalent to calling `setEnabled(true)` on each entry.
|
||||
*/
|
||||
void enableAll();
|
||||
|
||||
/**
|
||||
* @brief Marks all sets as known and adjusts their enabled state.
|
||||
* @brief Marks all sets as known and adjusts their enabled state, persisting in one flush.
|
||||
*
|
||||
* Unknown, non-ignored sets become known and 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.
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ public:
|
|||
QString shortName;
|
||||
unsigned int sortKey;
|
||||
bool enabled;
|
||||
bool isKnown = false;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -57,6 +58,14 @@ public:
|
|||
*/
|
||||
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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ void SetsModel::save(CardDatabase *db)
|
|||
const bool enabled = enabledSets.contains(sets[i]);
|
||||
sets[i]->setSortKeyInMemory(sortKey);
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ void CardDatabaseSettings::saveSets(const QVector<ICardSetPriorityController::Se
|
|||
s.beginGroup(entry.shortName);
|
||||
s.setValue("sortKey", entry.sortKey);
|
||||
s.setValue("enabled", entry.enabled);
|
||||
s.setValue("isKnown", entry.isKnown);
|
||||
s.endGroup();
|
||||
}
|
||||
s.endGroup();
|
||||
|
|
@ -121,5 +122,6 @@ void CardDatabaseSettings::saveSets(const QVector<ICardSetPriorityController::Se
|
|||
SetOptions &o = setOptionsCache[entry.shortName];
|
||||
o.sortKey = entry.sortKey;
|
||||
o.enabled = entry.enabled;
|
||||
o.isKnown = entry.isKnown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -642,6 +642,34 @@ static void migrateLegacyMessages(const QString &settingsPath)
|
|||
legacySetting.endGroup();
|
||||
}
|
||||
|
||||
/**
|
||||
* gamefilters.ini keys renamed to camelCase by #7084. Profiles that migrated from the legacy
|
||||
* native store before that rename still hold the old snake_case keys, which nothing reads
|
||||
* anymore, so their saved filters silently stopped loading.
|
||||
*/
|
||||
static const QMap<QString, QString> &renamedGameFilterKeys()
|
||||
{
|
||||
static const QMap<QString, QString> keyMap = {
|
||||
{"hide_buddies_only_games", "hideBuddiesOnlyGames"},
|
||||
{"hide_full_games", "hideFullGames"},
|
||||
{"hide_games_that_started", "hideGamesThatStarted"},
|
||||
{"hide_password_protected_games", "hidePasswordProtectedGames"},
|
||||
{"hide_ignored_user_games", "hideIgnoredUserGames"},
|
||||
{"hide_not_buddy_created_games", "hideNotBuddyCreatedGames"},
|
||||
{"hide_open_decklist_games", "hideOpenDecklistGames"},
|
||||
{"game_name_filter", "gameNameFilter"},
|
||||
{"creator_name_filter", "hostNameFilter"},
|
||||
{"min_players", "minPlayers"},
|
||||
{"max_players", "maxPlayers"},
|
||||
{"max_game_age_time", "maxGameAgeTime"},
|
||||
{"show_only_if_spectators_can_watch", "showOnlyIfSpectatorsCanWatch"},
|
||||
{"show_spectator_password_protected", "showSpectatorPasswordProtected"},
|
||||
{"show_only_if_spectators_can_chat", "showOnlyIfSpectatorsCanChat"},
|
||||
{"show_only_if_spectators_can_see_hands", "showOnlyIfSpectatorsCanSeeHands"},
|
||||
};
|
||||
return keyMap;
|
||||
}
|
||||
|
||||
static void migrateLegacyGameFilters(const QString &settingsPath)
|
||||
{
|
||||
QSettings legacySetting;
|
||||
|
|
@ -652,24 +680,7 @@ static void migrateLegacyGameFilters(const QString &settingsPath)
|
|||
return;
|
||||
}
|
||||
|
||||
const QMap<QString, QString> filterKeyMap = {
|
||||
{"hide_buddies_only_games", "hideBuddiesOnlyGames"},
|
||||
{"hide_full_games", "hideFullGames"},
|
||||
{"hide_games_that_started", "hideGamesThatStarted"},
|
||||
{"hide_password_protected_games", "hidePasswordProtectedGames"},
|
||||
{"hide_ignored_user_games", "hideIgnoredUserGames"},
|
||||
{"hide_not_buddy_created_games", "hideNotBuddyCreatedGames"},
|
||||
{"hide_open_decklist_games", "hideOpenDecklistGames"},
|
||||
{"game_name_filter", "gameNameFilter"},
|
||||
{"creator_name_filter", "creatorNameFilter"},
|
||||
{"min_players", "minPlayers"},
|
||||
{"max_players", "maxPlayers"},
|
||||
{"max_game_age_time", "maxGameAgeTime"},
|
||||
{"show_only_if_spectators_can_watch", "showOnlyIfSpectatorsCanWatch"},
|
||||
{"show_spectator_password_protected", "showSpectatorPasswordProtected"},
|
||||
{"show_only_if_spectators_can_chat", "showOnlyIfSpectatorsCanChat"},
|
||||
{"show_only_if_spectators_can_see_hands", "showOnlyIfSpectatorsCanSeeHands"},
|
||||
};
|
||||
const QMap<QString, QString> &filterKeyMap = renamedGameFilterKeys();
|
||||
|
||||
QSettings filtersIni(settingsPath + "gamefilters.ini", QSettings::IniFormat);
|
||||
for (auto it = filterKeyMap.constBegin(); it != filterKeyMap.constEnd(); ++it) {
|
||||
|
|
@ -686,6 +697,68 @@ static void migrateLegacyGameFilters(const QString &settingsPath)
|
|||
legacySetting.endGroup();
|
||||
}
|
||||
|
||||
static const QString LEGACY_GAME_FILTERS_GAME_TYPE_PREFIX = QStringLiteral("filter_games/game_type/");
|
||||
static const QString GAME_FILTERS_GAME_TYPE_PREFIX = QStringLiteral("filter_games/gameType/");
|
||||
|
||||
static bool gameFiltersCamelCaseMigrationPending(const QString &settingsPath)
|
||||
{
|
||||
if (!QFile::exists(settingsPath + "gamefilters.ini")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QSettings filtersIni(settingsPath + "gamefilters.ini", QSettings::IniFormat);
|
||||
const QMap<QString, QString> &keyMap = renamedGameFilterKeys();
|
||||
for (auto it = keyMap.constBegin(); it != keyMap.constEnd(); ++it) {
|
||||
if (filtersIni.contains("filter_games/" + it.key()) && !filtersIni.contains("filter_games/" + it.value())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Orphaned by #7084, which migrated the legacy host-name filter to a key nothing reads.
|
||||
if (filtersIni.contains("filter_games/creatorNameFilter") && !filtersIni.contains("filter_games/hostNameFilter")) {
|
||||
return true;
|
||||
}
|
||||
for (const auto &key : filtersIni.allKeys()) {
|
||||
if (key.startsWith(LEGACY_GAME_FILTERS_GAME_TYPE_PREFIX) &&
|
||||
!filtersIni.contains(GAME_FILTERS_GAME_TYPE_PREFIX +
|
||||
key.mid(LEGACY_GAME_FILTERS_GAME_TYPE_PREFIX.size()))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SettingsMigration::migrateGameFiltersToCamelCase(const QString &settingsPath)
|
||||
{
|
||||
if (!gameFiltersCamelCaseMigrationPending(settingsPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QSettings filtersIni(settingsPath + "gamefilters.ini", QSettings::IniFormat);
|
||||
const QMap<QString, QString> &keyMap = renamedGameFilterKeys();
|
||||
for (auto it = keyMap.constBegin(); it != keyMap.constEnd(); ++it) {
|
||||
const QString oldKey = "filter_games/" + it.key();
|
||||
const QString newKey = "filter_games/" + it.value();
|
||||
if (filtersIni.contains(oldKey) && !filtersIni.contains(newKey)) {
|
||||
filtersIni.setValue(newKey, filtersIni.value(oldKey));
|
||||
}
|
||||
}
|
||||
if (filtersIni.contains("filter_games/creatorNameFilter") && !filtersIni.contains("filter_games/hostNameFilter")) {
|
||||
filtersIni.setValue("filter_games/hostNameFilter", filtersIni.value("filter_games/creatorNameFilter"));
|
||||
}
|
||||
for (const auto &key : filtersIni.allKeys()) {
|
||||
if (key.startsWith(LEGACY_GAME_FILTERS_GAME_TYPE_PREFIX)) {
|
||||
const QString newKey = GAME_FILTERS_GAME_TYPE_PREFIX + key.mid(LEGACY_GAME_FILTERS_GAME_TYPE_PREFIX.size());
|
||||
if (!filtersIni.contains(newKey)) {
|
||||
filtersIni.setValue(newKey, filtersIni.value(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
// The old keys are deliberately kept: pre-#7084 builds still read them, so removing them
|
||||
// would break downgrades, and nothing in the current code reads them anymore.
|
||||
filtersIni.sync();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SettingsMigration::migrateLegacySettings(const QString &settingsPath)
|
||||
{
|
||||
QSettings personalIni(settingsPath + "personal.ini", QSettings::IniFormat);
|
||||
|
|
@ -744,6 +817,10 @@ bool SettingsMigration::isMigrationPending(const QString &settingsPath)
|
|||
return true;
|
||||
}
|
||||
|
||||
if (gameFiltersCamelCaseMigrationPending(settingsPath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QSettings personalIni(settingsPath + "personal.ini", QSettings::IniFormat);
|
||||
if (personalIni.value("migration/legacy_complete", false).toBool()) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,15 @@ public:
|
|||
static bool migrateSettingsFromGlobalIni(const QString &settingsPath);
|
||||
static bool migrateLegacySettings(const QString &settingsPath);
|
||||
|
||||
/**
|
||||
* Moves pre-camelCase gamefilters.ini keys (snake_case, game_type/<hash>) to the camelCase
|
||||
* keys used since #7084, for profiles that already migrated before that rename and therefore
|
||||
* never pass through migrateLegacySettings again. Copies values only where the new key is
|
||||
* absent, never deletes the old keys, and returns true when anything was copied. Idempotent,
|
||||
* so it runs on every startup.
|
||||
*/
|
||||
static bool migrateGameFiltersToCamelCase(const QString &settingsPath);
|
||||
|
||||
/**
|
||||
* Returns true when at least one of the two migrations would still do work, i.e. when a
|
||||
* backup of the settings directory is worth taking before they run.
|
||||
|
|
|
|||
|
|
@ -159,16 +159,19 @@ public:
|
|||
|
||||
void setSortKey(QString shortName, unsigned int sortKey) override
|
||||
{
|
||||
++individualWrites;
|
||||
setOptions[shortName].sortKey = sortKey;
|
||||
}
|
||||
|
||||
void setEnabled(QString shortName, bool enabled) override
|
||||
{
|
||||
++individualWrites;
|
||||
setOptions[shortName].enabled = enabled;
|
||||
}
|
||||
|
||||
void setIsKnown(QString shortName, bool isknown) override
|
||||
{
|
||||
++individualWrites;
|
||||
setOptions[shortName].isKnown = isknown;
|
||||
}
|
||||
|
||||
|
|
@ -206,12 +209,17 @@ public:
|
|||
|
||||
void saveSets(const QVector<SetSaveData> &data) override
|
||||
{
|
||||
++batchedWrites;
|
||||
for (const SetSaveData &entry : data) {
|
||||
setOptions[entry.shortName].sortKey = entry.sortKey;
|
||||
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:
|
||||
QHash<QString, SetOptions> setOptions;
|
||||
};
|
||||
|
|
@ -375,6 +383,131 @@ TEST_F(CardDatabaseCacheInvalidationTest, ExplicitReloadBypassesCache)
|
|||
EXPECT_FALSE(db.getCardList().value("Cat")->getSets().contains("CAT"));
|
||||
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
|
||||
|
||||
int main(int argc, char **argv)
|
||||
|
|
|
|||
|
|
@ -705,6 +705,71 @@ TEST_F(SettingsMigrationTest, LegacyMigrationCamelCasesKeys)
|
|||
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/gameType/deadbeef"), QVariant(true));
|
||||
}
|
||||
|
||||
TEST_F(SettingsMigrationTest, LegacyMigrationMapsCreatorNameFilterToHostNameFilter)
|
||||
{
|
||||
{
|
||||
QSettings nativeSettings;
|
||||
nativeSettings.setValue("filter_games/creator_name_filter", QStringList({"Alice", "Bob"}));
|
||||
nativeSettings.sync();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(SettingsMigration::migrateLegacySettings(settingsPath));
|
||||
|
||||
ASSERT_TRUE(fileExists("gamefilters.ini"));
|
||||
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/hostNameFilter"), QVariant(QStringList({"Alice", "Bob"})));
|
||||
}
|
||||
|
||||
TEST_F(SettingsMigrationTest, GameFiltersIniSnakeCaseMigratesToCamelCase)
|
||||
{
|
||||
{
|
||||
QSettings filtersIni(settingsPath + "gamefilters.ini", QSettings::IniFormat);
|
||||
filtersIni.setValue("filter_games/hide_full_games", true);
|
||||
filtersIni.setValue("filter_games/hide_games_that_started", true);
|
||||
filtersIni.setValue("filter_games/game_name_filter", "test game");
|
||||
filtersIni.setValue("filter_games/creator_name_filter", QStringList({"Alice"}));
|
||||
filtersIni.setValue("filter_games/game_type/deadbeef", true);
|
||||
filtersIni.sync();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(SettingsMigration::migrateGameFiltersToCamelCase(settingsPath));
|
||||
|
||||
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/hideFullGames"), QVariant(true));
|
||||
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/hideGamesThatStarted"), QVariant(true));
|
||||
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/gameNameFilter"), QVariant("test game"));
|
||||
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/hostNameFilter"), QVariant(QStringList({"Alice"})));
|
||||
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/gameType/deadbeef"), QVariant(true));
|
||||
|
||||
// Idempotent: a second run has nothing left to do
|
||||
ASSERT_FALSE(SettingsMigration::migrateGameFiltersToCamelCase(settingsPath));
|
||||
}
|
||||
|
||||
TEST_F(SettingsMigrationTest, GameFiltersIniOrphanCreatorNameFilterMigratesToHostName)
|
||||
{
|
||||
{
|
||||
QSettings filtersIni(settingsPath + "gamefilters.ini", QSettings::IniFormat);
|
||||
filtersIni.setValue("filter_games/creatorNameFilter", QStringList({"Alice"}));
|
||||
filtersIni.sync();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(SettingsMigration::migrateGameFiltersToCamelCase(settingsPath));
|
||||
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/hostNameFilter"), QVariant(QStringList({"Alice"})));
|
||||
ASSERT_FALSE(SettingsMigration::migrateGameFiltersToCamelCase(settingsPath));
|
||||
}
|
||||
|
||||
TEST_F(SettingsMigrationTest, GameFiltersIniKeepsNewerCamelCaseValues)
|
||||
{
|
||||
{
|
||||
QSettings filtersIni(settingsPath + "gamefilters.ini", QSettings::IniFormat);
|
||||
filtersIni.setValue("filter_games/hide_full_games", true);
|
||||
filtersIni.setValue("filter_games/hideFullGames", false);
|
||||
filtersIni.sync();
|
||||
}
|
||||
|
||||
// Nothing to do: the current-format key already exists and must win
|
||||
ASSERT_FALSE(SettingsMigration::migrateGameFiltersToCamelCase(settingsPath));
|
||||
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/hideFullGames"), QVariant(false));
|
||||
}
|
||||
|
||||
TEST_F(SettingsMigrationTest, LegacyMigrationEmptyNativeFormatWritesSentinel)
|
||||
{
|
||||
QSettings().clear();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue