mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 09:33:25 -08:00
The per-card text size lives in the theme now, so a theme can pick the size it was designed for instead of every user overriding it globally. - CardText stores family + max point size; CardTextMinSize stores the overlay shrink-to-fit floor (omitted from fonts.toml when 1). - ThemeManager::cardTextMaxSize()/cardTextMinSize() resolve the range with the retired defaults (12 max, 1 min), and cardTextMaxSize() falls back to the CardText role size that #7541 made live. - The overlay widget starts at the explicit size when one is set (deck preview), else the theme max, and shrinks down to the theme floor. - The appearance page's "Maximum font size" spin is gone; the range is edited in the theme's font grid, whose min/max spins now keep the saved range consistent with each other. - Migration: appearance/maxFontSize is seeded into every user theme folder and into the active theme's folder (created when the theme is a shipped one that only exists in the system directory). The retired game/maxfontsize -> appearance/maxFontSize mapping stays, so users still on global.ini keep their value through the chain.
1069 lines
43 KiB
C++
1069 lines
43 KiB
C++
#include "settings_migration.h"
|
|
|
|
#include <QChar>
|
|
#include <QDateTime>
|
|
#include <QDebug>
|
|
#include <QDir>
|
|
#include <QDirIterator>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QFileInfoList>
|
|
#include <QFlags>
|
|
#include <QList>
|
|
#include <QLoggingCategory>
|
|
#include <QMap>
|
|
#include <QMessageLogger>
|
|
#include <QSettings>
|
|
#include <QSharedPointer>
|
|
#include <QStringList>
|
|
#include <QStringLiteral>
|
|
#include <QTextStream>
|
|
#include <QVariant>
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
inline Q_LOGGING_CATEGORY(SettingsMigrationLog, "settings_migration");
|
|
|
|
static const QString MIGRATION_SENTINEL_KEY = QStringLiteral("migration/perfile_complete");
|
|
|
|
/**
|
|
* True when global.ini holds keys that migrateSettingsFromGlobalIni() would still move, i.e. the
|
|
* exact condition that function uses to decide whether it has work to do. If a user runs an older
|
|
* Cockatrice build that writes to global.ini (non-sentinel keys) and then upgrades back, those new
|
|
* keys would otherwise be silently ignored, so they are re-migrated instead.
|
|
*/
|
|
static bool hasPendingGlobalIniSettings(const QString &settingsPath)
|
|
{
|
|
if (!QFile::exists(settingsPath + "global.ini")) {
|
|
return false;
|
|
}
|
|
|
|
QSettings globalIni(settingsPath + "global.ini", QSettings::IniFormat);
|
|
if (!globalIni.value(MIGRATION_SENTINEL_KEY, false).toBool()) {
|
|
return true;
|
|
}
|
|
|
|
// The sentinel is set, so any key besides it was written after the migration, i.e. by an
|
|
// older build sharing this file. Those are exactly the keys to re-migrate.
|
|
QStringList otherKeys = globalIni.allKeys();
|
|
otherKeys.removeAll(MIGRATION_SENTINEL_KEY);
|
|
return !otherKeys.isEmpty();
|
|
}
|
|
|
|
static void migrateTabsSettings(const QString &settingsPath, QSettings &globalIni)
|
|
{
|
|
if (!globalIni.contains("tabs/visualDeckStorage") && !globalIni.contains("tabs/server") &&
|
|
!globalIni.contains("tabs/account") && !globalIni.contains("tabs/deckStorage") &&
|
|
!globalIni.contains("tabs/replays") && !globalIni.contains("tabs/admin") && !globalIni.contains("tabs/log")) {
|
|
return;
|
|
}
|
|
|
|
QSettings tabsIni(settingsPath + "tabs.ini", QSettings::IniFormat);
|
|
tabsIni.setValue("tabs/visualDeckStorage", globalIni.value("tabs/visualDeckStorage", true));
|
|
tabsIni.setValue("tabs/server", globalIni.value("tabs/server", true));
|
|
tabsIni.setValue("tabs/account", globalIni.value("tabs/account", true));
|
|
tabsIni.setValue("tabs/deckStorage", globalIni.value("tabs/deckStorage", true));
|
|
tabsIni.setValue("tabs/replays", globalIni.value("tabs/replays", true));
|
|
tabsIni.setValue("tabs/admin", globalIni.value("tabs/admin", true));
|
|
tabsIni.setValue("tabs/log", globalIni.value("tabs/log", true));
|
|
}
|
|
|
|
static void migrateSoundSettings(const QString &settingsPath, QSettings &globalIni)
|
|
{
|
|
if (!globalIni.contains("sound/enabled") && !globalIni.contains("sound/theme") &&
|
|
!globalIni.contains("sound/mastervolume")) {
|
|
return;
|
|
}
|
|
|
|
QSettings soundIni(settingsPath + "sound.ini", QSettings::IniFormat);
|
|
soundIni.setValue("sound/enabled", globalIni.value("sound/enabled", false));
|
|
soundIni.setValue("sound/theme", globalIni.value("sound/theme"));
|
|
soundIni.setValue("sound/masterVolume", globalIni.value("sound/mastervolume", 100));
|
|
}
|
|
|
|
static void migrateGameSettings(const QString &settingsPath, QSettings &globalIni)
|
|
{
|
|
const QMap<QString, QString> gameKeyMap = {
|
|
{"game/maxplayers", "game/maxPlayers"},
|
|
{"game/gamedescription", "game/gameDescription"},
|
|
{"game/gametypes", "game/gameTypes"},
|
|
{"game/onlybuddies", "game/onlyBuddies"},
|
|
{"game/onlyregistered", "game/onlyRegistered"},
|
|
{"game/spectatorsallowed", "game/spectatorsAllowed"},
|
|
{"game/spectatorsneedpassword", "game/spectatorsNeedPassword"},
|
|
{"game/spectatorscantalk", "game/spectatorsCanTalk"},
|
|
{"game/spectatorscanseeeverything", "game/spectatorsCanSeeEverything"},
|
|
{"game/creategameasspectator", "game/createGameAsSpectator"},
|
|
{"game/defaultstartinglifetotal", "game/defaultStartingLifeTotal"},
|
|
{"game/sharedecklistsonload", "game/shareDecklistsOnLoad"},
|
|
{"game/remembergamesettings", "game/rememberGameSettings"},
|
|
{"localgameoptions/maxplayers", "localgameoptions/maxPlayers"},
|
|
{"localgameoptions/startinglifetotal", "localgameoptions/startingLifeTotal"},
|
|
{"localgameoptions/remembersettings", "localgameoptions/rememberSettings"},
|
|
};
|
|
|
|
bool hasAny = false;
|
|
for (auto it = gameKeyMap.constBegin(); it != gameKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
hasAny = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasAny) {
|
|
return;
|
|
}
|
|
|
|
QSettings gameIni(settingsPath + "game.ini", QSettings::IniFormat);
|
|
for (auto it = gameKeyMap.constBegin(); it != gameKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
gameIni.setValue(it.value(), globalIni.value(it.key()));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void migrateChatSettings(const QString &settingsPath, QSettings &globalIni)
|
|
{
|
|
const QMap<QString, QString> chatKeyMap = {
|
|
{"chat/mention", "chat/mention"},
|
|
{"chat/mentioncompleter", "chat/mentionCompleter"},
|
|
{"chat/mentioncolor", "chat/mentionColor"},
|
|
{"chat/highlightcolor", "chat/highlightColor"},
|
|
{"chat/mentionforeground", "chat/mentionForeground"},
|
|
{"chat/highlightforeground", "chat/highlightForeground"},
|
|
{"chat/ignore_unregistered", "chat/ignoreUnregistered"},
|
|
{"chat/ignore_unregistered_messages", "chat/ignoreUnregisteredMessages"},
|
|
{"chat/ignore_nonbuddy_messages", "chat/ignoreNonBuddyMessages"},
|
|
{"chat/showmessagepopups", "chat/showMessagePopups"},
|
|
{"chat/showmentionpopups", "chat/showMentionPopups"},
|
|
{"chat/roomhistory", "chat/roomHistory"},
|
|
{"chat/highlightwords", "chat/highlightWords"},
|
|
// Legacy highlight words lived under [personal], but the chat settings
|
|
// class reads them from [chat]
|
|
{"personal/highlightWords", "chat/highlightWords"},
|
|
};
|
|
bool hasAny = false;
|
|
for (auto it = chatKeyMap.constBegin(); it != chatKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
hasAny = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasAny) {
|
|
return;
|
|
}
|
|
|
|
QSettings chatIni(settingsPath + "chat.ini", QSettings::IniFormat);
|
|
for (auto it = chatKeyMap.constBegin(); it != chatKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
chatIni.setValue(it.value(), globalIni.value(it.key()));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void migrateCacheStorageSettings(const QString &settingsPath, QSettings &globalIni)
|
|
{
|
|
const QMap<QString, QString> cacheStorageKeyMap = {
|
|
{"personal/pixmapCacheSize", "cache_storage/pixmapCacheSize"},
|
|
{"personal/networkCacheSize", "cache_storage/networkCacheSize"},
|
|
{"personal/redirectCacheTtl", "cache_storage/redirectCacheTtl"},
|
|
{"personal/cardPictureLoaderCacheMethod", "cache_storage/cardPictureLoaderCacheMethod"},
|
|
{"personal/localCardImageStorageNamingScheme", "cache_storage/localCardImageStorageNamingScheme"},
|
|
};
|
|
bool hasAny = false;
|
|
for (auto it = cacheStorageKeyMap.constBegin(); it != cacheStorageKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
hasAny = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasAny) {
|
|
return;
|
|
}
|
|
|
|
QSettings cacheStorageIni(settingsPath + "cache_storage.ini", QSettings::IniFormat);
|
|
for (auto it = cacheStorageKeyMap.constBegin(); it != cacheStorageKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
cacheStorageIni.setValue(it.value(), globalIni.value(it.key()));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void migrateUpdatesSettings(const QString &settingsPath, QSettings &globalIni)
|
|
{
|
|
const QMap<QString, QString> updateKeyMap = {
|
|
{"personal/startupUpdateCheck", "updates/startupUpdateCheck"},
|
|
{"personal/startupCardUpdateCheckPromptForUpdate", "updates/startupCardUpdateCheckPromptForUpdate"},
|
|
{"personal/startupCardUpdateCheckAlwaysUpdate", "updates/startupCardUpdateCheckAlwaysUpdate"},
|
|
{"personal/cardUpdateCheckInterval", "updates/cardUpdateCheckInterval"},
|
|
{"personal/lastCardUpdateCheck", "updates/lastCardUpdateCheck"},
|
|
{"personal/alwaysEnableNewSets", "updates/alwaysEnableNewSets"},
|
|
{"personal/updatenotification", "updates/updateNotification"},
|
|
{"personal/newversionnotification", "updates/newVersionNotification"},
|
|
{"personal/updatereleasechannel", "updates/updateReleaseChannel"},
|
|
};
|
|
bool hasAny = false;
|
|
for (auto it = updateKeyMap.constBegin(); it != updateKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
hasAny = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasAny) {
|
|
return;
|
|
}
|
|
|
|
QSettings updatesIni(settingsPath + "updates.ini", QSettings::IniFormat);
|
|
for (auto it = updateKeyMap.constBegin(); it != updateKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
updatesIni.setValue(it.value(), globalIni.value(it.key()));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void migratePersonalSettings(const QString &settingsPath, QSettings &globalIni)
|
|
{
|
|
const QStringList personalRootKeys = {"personal/lang"};
|
|
const QStringList tipKeys = {"tipOfDay/showTips", "tipOfDay/seenTips"};
|
|
|
|
bool hasAny = false;
|
|
for (const auto &key : personalRootKeys) {
|
|
if (globalIni.contains(key)) {
|
|
hasAny = true;
|
|
}
|
|
}
|
|
for (const auto &key : tipKeys) {
|
|
if (globalIni.contains(key)) {
|
|
hasAny = true;
|
|
}
|
|
}
|
|
if (!hasAny) {
|
|
return;
|
|
}
|
|
|
|
QSettings personalIni(settingsPath + "personal.ini", QSettings::IniFormat);
|
|
for (const auto &key : personalRootKeys) {
|
|
if (globalIni.contains(key)) {
|
|
personalIni.setValue(key, globalIni.value(key));
|
|
}
|
|
}
|
|
for (const auto &key : tipKeys) {
|
|
if (globalIni.contains(key)) {
|
|
personalIni.setValue(key, globalIni.value(key));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void migrateCardsDisplaySettings(const QString &settingsPath, QSettings &globalIni)
|
|
{
|
|
const QMap<QString, QString> cardsKeyMap = {
|
|
{"cards/displaycardnames", "cards/displayCardNames"},
|
|
{"cards/roundcardcorners", "cards/roundCardCorners"},
|
|
{"cards/overrideallcardartwithpersonalpreference", "cards/overrideAllCardArtWithPersonalPreference"},
|
|
{"cards/bumpsetswithcardsindecktotop", "cards/bumpSetsWithCardsInDeckToTop"},
|
|
{"cards/tapanimation", "cards/tapAnimation"},
|
|
{"cards/autorotatesidewayslayoutcards", "cards/autoRotateSidewaysLayoutCards"},
|
|
{"cards/scaleCards", "cards/scaleCards"},
|
|
{"cards/verticalCardOverlapPercent", "cards/verticalCardOverlapPercent"},
|
|
{"cards/cardinfoviewmode", "cards/cardInfoViewMode"},
|
|
{"cards/printingselectorsortorder", "cards/printingSelector/sortOrder"},
|
|
{"cards/printingselectornavigationbuttonsvisible", "cards/printingSelector/navigationButtonsVisible"},
|
|
{"cards/printingselectorcardsize", "cards/cardSize/printingSelector"},
|
|
{"interface/visualdeckstoragecardsize", "cards/cardSize/visualDeckStorage"},
|
|
{"interface/visualdatabasedisplaycardsize", "cards/cardSize/visualDatabaseDisplay"},
|
|
{"interface/visualdeckeditorcardsize", "cards/cardSize/visualDeckEditor"},
|
|
{"interface/edhreccardsize", "cards/cardSize/edhrec"},
|
|
{"interface/archidektpreviewsize", "cards/cardSize/archidektPreview"},
|
|
{"interface/visualdeckeditorsamplehandsize", "cards/cardSize/sampleHandSize"},
|
|
};
|
|
|
|
bool hasAny = false;
|
|
for (auto it = cardsKeyMap.constBegin(); it != cardsKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
hasAny = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasAny) {
|
|
return;
|
|
}
|
|
|
|
QSettings cardsIni(settingsPath + "cards_display.ini", QSettings::IniFormat);
|
|
for (auto it = cardsKeyMap.constBegin(); it != cardsKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
cardsIni.setValue(it.value(), globalIni.value(it.key()));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void migrateCardCounterSettings(const QString &settingsPath, QSettings &globalIni)
|
|
{
|
|
QStringList counterKeys;
|
|
const QStringList allKeys = globalIni.allKeys();
|
|
for (const auto &key : allKeys) {
|
|
if (key.startsWith("cards/counters/")) {
|
|
counterKeys.append(key);
|
|
}
|
|
}
|
|
if (counterKeys.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QSettings countersIni(settingsPath + "card_counters.ini", QSettings::IniFormat);
|
|
for (const auto &key : counterKeys) {
|
|
countersIni.setValue(key, globalIni.value(key));
|
|
}
|
|
}
|
|
|
|
static void migrateInterfaceSettings(const QString &settingsPath, QSettings &globalIni)
|
|
{
|
|
const QMap<QString, QString> interfaceKeyMap = {
|
|
{"interface/usetearoffmenus", "interface/useTearOffMenus"},
|
|
{"interface/cardViewInitialRowsMax", "interface/cardViewInitialRowsMax"},
|
|
{"interface/cardViewExpandedRowsMax", "interface/cardViewExpandedRowsMax"},
|
|
{"interface/closeEmptyCardView", "interface/closeEmptyCardView"},
|
|
{"interface/focusCardViewSearchBar", "interface/focusCardViewSearchBar"},
|
|
{"interface/keepGameChatFocus", "interface/keepGameChatFocus"},
|
|
{"interface/doubleclicktoplay", "interface/doubleClickToPlay"},
|
|
{"interface/clickPlaysAllSelected", "interface/clickPlaysAllSelected"},
|
|
{"interface/playtostack", "interface/playToStack"},
|
|
{"interface/doNotDeleteArrowsInSubPhases", "interface/doNotDeleteArrowsInSubPhases"},
|
|
{"interface/startinghandsize", "interface/startingHandSize"},
|
|
{"interface/annotatetokens", "interface/annotateTokens"},
|
|
{"interface/showlassoselectioncount", "interface/showLassoSelectionCount"},
|
|
{"interface/showpersistentselectioncount", "interface/showPersistentSelectionCount"},
|
|
{"interface/tallyType", "interface/tallyType"},
|
|
{"interface/leftjustified", "interface/leftJustified"},
|
|
{"interface/min_players_multicolumn", "interface/minPlayersMulticolumn"},
|
|
{"hand/horizontal", "hand/horizontal"},
|
|
{"table/invert_vertical", "table/invertVertical"},
|
|
{"replay/rewindBufferingMs", "replay/rewindBufferingMs"},
|
|
{"replay/fastForwardSpeed", "replay/fastForwardSpeed"},
|
|
{"zoneview/groupby", "zoneview/groupBy"},
|
|
{"zoneview/sortby", "zoneview/sortBy"},
|
|
{"zoneview/pileview", "zoneview/pileView"},
|
|
{"personal/showStatusBar", "interface/showStatusBar"},
|
|
{"menu/showshortcuts", "interface/showShortcuts"},
|
|
{"menu/showgameselectorfiltertoolbar", "interface/showGameSelectorFilterToolbar"},
|
|
{"interface/notificationsenabled", "interface/notifications/enabled"},
|
|
{"interface/specnotificationsenabled", "interface/notifications/spectatorsEnabled"},
|
|
{"interface/buddyconnectnotificationsenabled", "interface/notifications/buddyConnectEnabled"},
|
|
};
|
|
bool hasAny = false;
|
|
for (auto it = interfaceKeyMap.constBegin(); it != interfaceKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
hasAny = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasAny) {
|
|
return;
|
|
}
|
|
|
|
QSettings interfaceIni(settingsPath + "interface.ini", QSettings::IniFormat);
|
|
for (auto it = interfaceKeyMap.constBegin(); it != interfaceKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
interfaceIni.setValue(it.value(), globalIni.value(it.key()));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void migrateDownloadSettings(const QString &settingsPath, QSettings &globalIni)
|
|
{
|
|
const QMap<QString, QString> downloadKeyMap = {
|
|
{"personal/picturedownload", "downloads/pictureDownload"},
|
|
{"personal/downloadspoilers", "downloads/downloadSpoilers"},
|
|
};
|
|
bool hasAny = false;
|
|
for (auto it = downloadKeyMap.constBegin(); it != downloadKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
hasAny = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasAny) {
|
|
return;
|
|
}
|
|
|
|
QSettings downloadsIni(settingsPath + "downloads.ini", QSettings::IniFormat);
|
|
for (auto it = downloadKeyMap.constBegin(); it != downloadKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
downloadsIni.setValue(it.value(), globalIni.value(it.key()));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void migrateAppearanceSettings(const QString &settingsPath, QSettings &globalIni)
|
|
{
|
|
const QMap<QString, QString> appearanceKeyMap = {
|
|
{"theme/name", "appearance/themeName"},
|
|
// Retired into the theme's CardText size, but still read here so users
|
|
// who have not migrated past global.ini keep their card text size:
|
|
// SettingsCache runs migrateSettingsFromGlobalIni() before
|
|
// migrateAppearanceMaxFontSizeToTheme().
|
|
{"game/maxfontsize", "appearance/maxFontSize"},
|
|
{"home/background", "appearance/homeTabBackgroundSource"},
|
|
{"home/background/shuffleTimer", "appearance/homeTabBackgroundShuffleFrequency"},
|
|
{"home/background/displayCardName", "appearance/homeTabDisplayCardName"},
|
|
{"appearance/styleUserList", "appearance/styleUserList"},
|
|
};
|
|
bool hasAny = false;
|
|
for (auto it = appearanceKeyMap.constBegin(); it != appearanceKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
hasAny = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasAny) {
|
|
return;
|
|
}
|
|
|
|
QSettings appearanceIni(settingsPath + "appearance.ini", QSettings::IniFormat);
|
|
for (auto it = appearanceKeyMap.constBegin(); it != appearanceKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
QVariant value = globalIni.value(it.key());
|
|
if (it.key() == "theme/name" && value.toString() == "Default") {
|
|
value = "System";
|
|
}
|
|
appearanceIni.setValue(it.value(), value);
|
|
}
|
|
}
|
|
}
|
|
|
|
static QString themesPathForSettings(const QString &settingsPath)
|
|
{
|
|
QSettings pathsIni(settingsPath + "paths.ini", QSettings::IniFormat);
|
|
const QString configured = pathsIni.value("paths/themes").toString().trimmed();
|
|
if (!configured.isEmpty()) {
|
|
return configured;
|
|
}
|
|
// Default layout: <data>/themes/ sibling of <data>/settings/.
|
|
return QDir::cleanPath(QDir(settingsPath).absoluteFilePath("../themes"));
|
|
}
|
|
|
|
static bool fontsTomlHasExplicitCardTextMax(const QString &filePath)
|
|
{
|
|
QFile f(filePath);
|
|
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
return false;
|
|
}
|
|
|
|
QString currentSection;
|
|
QTextStream in(&f);
|
|
while (!in.atEnd()) {
|
|
QString line = in.readLine().trimmed();
|
|
if (line.isEmpty() || line.startsWith('#') || line.startsWith(';')) {
|
|
continue;
|
|
}
|
|
if (line.startsWith('[') && line.endsWith(']')) {
|
|
currentSection = line.mid(1, line.length() - 2).trimmed();
|
|
continue;
|
|
}
|
|
if (currentSection.compare("Fonts", Qt::CaseInsensitive) != 0) {
|
|
continue;
|
|
}
|
|
int eq = line.indexOf('=');
|
|
if (eq < 0) {
|
|
continue;
|
|
}
|
|
QString key = line.left(eq).trimmed();
|
|
QString value = line.mid(eq + 1).trimmed();
|
|
for (int i = 1; i < value.size(); ++i) {
|
|
if ((value[i] == '#' || value[i] == ';') && value[i - 1].isSpace()) {
|
|
value = value.left(i).trimmed();
|
|
break;
|
|
}
|
|
}
|
|
if (value.size() >= 2 &&
|
|
((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\'')))) {
|
|
value = value.mid(1, value.size() - 2).trimmed();
|
|
}
|
|
if (key.startsWith("AppFont::")) {
|
|
key = key.mid(QStringLiteral("AppFont::").size());
|
|
}
|
|
if (key.compare("CardText", Qt::CaseInsensitive) != 0) {
|
|
continue;
|
|
}
|
|
int comma = value.lastIndexOf(',');
|
|
if (comma < 0) {
|
|
continue;
|
|
}
|
|
QString family = value.left(comma).trimmed();
|
|
if (family.size() >= 2 &&
|
|
((family.startsWith('"') && family.endsWith('"')) || (family.startsWith('\'') && family.endsWith('\'')))) {
|
|
family = family.mid(1, family.size() - 2).trimmed();
|
|
}
|
|
bool ok = false;
|
|
int pointSize = value.mid(comma + 1).trimmed().toInt(&ok);
|
|
if (!ok || family.isEmpty() || pointSize <= 0) {
|
|
continue;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool seedCardTextMaxIntoThemeDir(const QString &themeDirPath, int maxSize)
|
|
{
|
|
if (themeDirPath.isEmpty() || maxSize <= 0) {
|
|
return false;
|
|
}
|
|
QDir dir(themeDirPath);
|
|
if (!dir.exists()) {
|
|
return false;
|
|
}
|
|
const QString filePath = dir.absoluteFilePath("fonts.toml");
|
|
if (fontsTomlHasExplicitCardTextMax(filePath)) {
|
|
return false;
|
|
}
|
|
|
|
QString existing;
|
|
if (QFile::exists(filePath)) {
|
|
QFile in(filePath);
|
|
if (!in.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
return false;
|
|
}
|
|
existing = QString::fromUtf8(in.readAll());
|
|
}
|
|
|
|
const QString entry =
|
|
QString("%1 = \"%2, %3\"\n").arg(QStringLiteral("CardText"), -20).arg(QStringLiteral("Serif")).arg(maxSize);
|
|
|
|
QString out;
|
|
if (existing.isEmpty()) {
|
|
out = "[Fonts]\n" + entry + "\n";
|
|
} else if (existing.contains("[Fonts]", Qt::CaseInsensitive)) {
|
|
out = existing;
|
|
if (!out.endsWith('\n')) {
|
|
out += '\n';
|
|
}
|
|
out += entry;
|
|
} else {
|
|
out = existing;
|
|
if (!out.endsWith('\n')) {
|
|
out += '\n';
|
|
}
|
|
out += "\n[Fonts]\n" + entry + "\n";
|
|
}
|
|
|
|
QFile f(filePath);
|
|
if (!f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
|
|
return false;
|
|
}
|
|
QTextStream stream(&f);
|
|
stream << out;
|
|
return true;
|
|
}
|
|
|
|
static bool appearanceMaxFontSizeMigrationPending(const QString &settingsPath)
|
|
{
|
|
if (!QFile::exists(settingsPath + "appearance.ini")) {
|
|
return false;
|
|
}
|
|
QSettings appearanceIni(settingsPath + "appearance.ini", QSettings::IniFormat);
|
|
return appearanceIni.contains("appearance/maxFontSize");
|
|
}
|
|
|
|
bool SettingsMigration::migrateAppearanceMaxFontSizeToTheme(const QString &settingsPath)
|
|
{
|
|
if (!appearanceMaxFontSizeMigrationPending(settingsPath)) {
|
|
return false;
|
|
}
|
|
|
|
QSettings appearanceIni(settingsPath + "appearance.ini", QSettings::IniFormat);
|
|
// Retired default was 12 in AppearanceSettings::getMaxFontSize; only
|
|
// non-default values are seeded, default users rely on the theme fallback.
|
|
const int legacy = appearanceIni.value("appearance/maxFontSize").toInt();
|
|
if (legacy != 12 && legacy > 0) {
|
|
const QString themesPath = themesPathForSettings(settingsPath);
|
|
QDir themesDir(themesPath);
|
|
const QStringList themes = themesDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
|
|
for (const QString &theme : themes) {
|
|
seedCardTextMaxIntoThemeDir(themesDir.absoluteFilePath(theme), legacy);
|
|
}
|
|
|
|
// Shipped themes (Fabric, Leather, ...) resolve to the system theme
|
|
// directory unless the user has already saved something for them, and
|
|
// System/Fusion only ever exist in the user directory. Seeding existing
|
|
// folders alone would therefore drop the retired size for every user on
|
|
// a stock theme, so create the active theme's user directory the same
|
|
// way ThemeManager::writableThemeDir() does for palette and font saves.
|
|
const QString activeTheme = appearanceIni.value("appearance/themeName").toString().trimmed();
|
|
if (!activeTheme.isEmpty() && !themesDir.exists(activeTheme) && themesDir.mkpath(activeTheme)) {
|
|
seedCardTextMaxIntoThemeDir(themesDir.absoluteFilePath(activeTheme), legacy);
|
|
}
|
|
}
|
|
|
|
appearanceIni.remove("appearance/maxFontSize");
|
|
appearanceIni.sync();
|
|
return true;
|
|
}
|
|
|
|
static void migrateNetworkSettings(const QString &settingsPath, QSettings &globalIni)
|
|
{
|
|
const QMap<QString, QString> networkKeyMap = {
|
|
{"personal/clientid", "network/clientId"},
|
|
{"personal/clientversion", "network/clientVersion"},
|
|
{"personal/keepalive", "network/keepAlive"},
|
|
{"personal/timeout", "network/timeout"},
|
|
{"interface/knownmissingfeatures", "network/knownMissingFeatures"},
|
|
};
|
|
bool hasAny = false;
|
|
for (auto it = networkKeyMap.constBegin(); it != networkKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
hasAny = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasAny) {
|
|
return;
|
|
}
|
|
|
|
QSettings networkIni(settingsPath + "network.ini", QSettings::IniFormat);
|
|
for (auto it = networkKeyMap.constBegin(); it != networkKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
networkIni.setValue(it.value(), globalIni.value(it.key()));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void migratePathsSettings(const QString &settingsPath, QSettings &globalIni)
|
|
{
|
|
const QMap<QString, QString> pathsKeyMap = {
|
|
{"paths/decks", "paths/decks"},
|
|
{"paths/filters", "paths/filters"},
|
|
{"paths/replays", "paths/replays"},
|
|
{"paths/pics", "paths/pics"},
|
|
{"paths/custompics", "paths/customPics"},
|
|
{"paths/themes", "paths/themes"},
|
|
{"paths/carddatabase", "paths/cardDatabase"},
|
|
{"paths/customsets", "paths/customSets"},
|
|
{"paths/tokendatabase", "paths/tokenDatabase"},
|
|
{"paths/spoilerdatabase", "paths/spoilerDatabase"},
|
|
{"paths/redirects", "paths/redirects"},
|
|
};
|
|
bool hasAny = false;
|
|
for (auto it = pathsKeyMap.constBegin(); it != pathsKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
hasAny = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasAny) {
|
|
return;
|
|
}
|
|
|
|
QSettings pathsIni(settingsPath + "paths.ini", QSettings::IniFormat);
|
|
for (auto it = pathsKeyMap.constBegin(); it != pathsKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
pathsIni.setValue(it.value(), globalIni.value(it.key()));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void migrateVisualDeckStorageSettings(const QString &settingsPath, QSettings &globalIni)
|
|
{
|
|
const QMap<QString, QString> vdsKeyMap = {
|
|
{"interface/visualdeckstoragesortingorder", "interface/visualDeckStorage/sortingOrder"},
|
|
{"interface/visualdeckstorageshowfolders", "interface/visualDeckStorage/showFolders"},
|
|
{"interface/visualdeckstorageshowtagfilter", "interface/visualDeckStorage/showTagFilter"},
|
|
{"interface/visualdeckstoragedefaulttagslist", "interface/visualDeckStorage/defaultTagsList"},
|
|
{"interface/visualdeckstoragesearchfoldernames", "interface/visualDeckStorage/searchFolderNames"},
|
|
{"interface/visualdeckstorageshowcoloridentity", "interface/visualDeckStorage/showColorIdentity"},
|
|
{"interface/visualdeckstorageshowbannercardcombobox", "interface/visualDeckStorage/showBannerCardComboBox"},
|
|
{"interface/visualdeckstorageshowtagsondeckpreviews", "interface/visualDeckStorage/showTagsOnDeckPreviews"},
|
|
{"interface/visualdeckstoragedrawunusedcoloridentities",
|
|
"interface/visualDeckStorage/drawUnusedColorIdentities"},
|
|
{"interface/visualdeckstorageunusedcoloridentitiesopacity",
|
|
"interface/visualDeckStorage/unusedColorIdentitiesOpacity"},
|
|
{"interface/visualdeckstoragetooltiptype", "interface/visualDeckStorage/tooltipType"},
|
|
{"interface/visualdeckstoragepromptforconversion", "interface/visualDeckStorage/promptForConversion"},
|
|
{"interface/visualdeckstoragealwaysconvert", "interface/visualDeckStorage/alwaysConvert"},
|
|
{"interface/visualdeckstorageingame", "interface/visualDeckStorage/inGame"},
|
|
{"interface/visualdeckstorageselectionanimation", "interface/visualDeckStorage/selectionAnimation"},
|
|
{"interface/visualdatabasedisplayfiltertomostrecentsetsenabled",
|
|
"interface/visualDatabaseDisplay/filterToMostRecentSetsEnabled"},
|
|
{"interface/visualdatabasedisplayfiltertomostrecentsetsamount",
|
|
"interface/visualDatabaseDisplay/filterToMostRecentSetsAmount"},
|
|
};
|
|
bool hasAny = false;
|
|
for (auto it = vdsKeyMap.constBegin(); it != vdsKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
hasAny = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasAny) {
|
|
return;
|
|
}
|
|
|
|
QSettings vdsIni(settingsPath + "visual_deck_storage.ini", QSettings::IniFormat);
|
|
for (auto it = vdsKeyMap.constBegin(); it != vdsKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
vdsIni.setValue(it.value(), globalIni.value(it.key()));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void migrateDeckEditorSettings(const QString &settingsPath, QSettings &globalIni)
|
|
{
|
|
const QMap<QString, QString> deckEditorKeyMap = {
|
|
{"editor/openDeckInNewTab", "deckeditor/openDeckInNewTab"},
|
|
{"interface/deckeditorbannercardcomboboxvisible", "deckeditor/bannerCardComboBoxVisible"},
|
|
{"interface/deckeditortagswidgetvisible", "deckeditor/tagsWidgetVisible"},
|
|
{"interface/defaultDeckEditorType", "deckeditor/defaultDeckEditorType"},
|
|
};
|
|
bool hasAny = false;
|
|
for (auto it = deckEditorKeyMap.constBegin(); it != deckEditorKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
hasAny = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasAny) {
|
|
return;
|
|
}
|
|
|
|
QSettings deckEditorIni(settingsPath + "deck_editor.ini", QSettings::IniFormat);
|
|
for (auto it = deckEditorKeyMap.constBegin(); it != deckEditorKeyMap.constEnd(); ++it) {
|
|
if (globalIni.contains(it.key())) {
|
|
deckEditorIni.setValue(it.value(), globalIni.value(it.key()));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void migrateLegacySets(const QString &settingsPath)
|
|
{
|
|
QSettings legacySetting;
|
|
legacySetting.beginGroup("sets");
|
|
QStringList groups = legacySetting.childGroups();
|
|
if (groups.isEmpty()) {
|
|
legacySetting.endGroup();
|
|
return;
|
|
}
|
|
|
|
QSettings cardDbIni(settingsPath + "cardDatabase.ini", QSettings::IniFormat);
|
|
for (const auto &shortName : groups) {
|
|
legacySetting.beginGroup(shortName);
|
|
if (legacySetting.contains("sortkey")) {
|
|
cardDbIni.setValue("sets/" + shortName + "/sortKey", legacySetting.value("sortkey"));
|
|
}
|
|
if (legacySetting.contains("enabled")) {
|
|
cardDbIni.setValue("sets/" + shortName + "/enabled", legacySetting.value("enabled"));
|
|
}
|
|
if (legacySetting.contains("isknown")) {
|
|
cardDbIni.setValue("sets/" + shortName + "/isKnown", legacySetting.value("isknown"));
|
|
}
|
|
legacySetting.endGroup();
|
|
}
|
|
legacySetting.endGroup();
|
|
}
|
|
|
|
static void migrateLegacyServers(const QString &settingsPath)
|
|
{
|
|
QSettings legacySetting;
|
|
legacySetting.beginGroup("server");
|
|
QStringList keys = legacySetting.allKeys();
|
|
if (keys.isEmpty()) {
|
|
legacySetting.endGroup();
|
|
return;
|
|
}
|
|
|
|
const QMap<QString, QString> serverKeyMap = {
|
|
{"previoushostlogin", "previousHostLogin"},
|
|
{"previoushosts", "previousHosts"},
|
|
{"previoushostName", "previousHostName"},
|
|
{"auto_connect", "autoConnect"},
|
|
{"fphostname", "fpHostName"},
|
|
{"fpport", "fpPort"},
|
|
{"fpplayername", "fpPlayerName"},
|
|
{"save_debug_log", "saveDebugLog"},
|
|
};
|
|
|
|
QSettings serversIni(settingsPath + "servers.ini", QSettings::IniFormat);
|
|
for (auto it = serverKeyMap.constBegin(); it != serverKeyMap.constEnd(); ++it) {
|
|
if (legacySetting.contains(it.key())) {
|
|
serversIni.setValue("server/" + it.value(), legacySetting.value(it.key()));
|
|
}
|
|
}
|
|
|
|
legacySetting.beginGroup("server_details");
|
|
const QStringList detailsKeys = legacySetting.allKeys();
|
|
for (const auto &key : detailsKeys) {
|
|
serversIni.setValue("server/server_details/" + key, legacySetting.value(key));
|
|
}
|
|
legacySetting.endGroup();
|
|
legacySetting.endGroup();
|
|
}
|
|
|
|
static void migrateLegacyMessages(const QString &settingsPath)
|
|
{
|
|
QSettings legacySetting;
|
|
legacySetting.beginGroup("messages");
|
|
QStringList keys = legacySetting.allKeys();
|
|
if (keys.isEmpty()) {
|
|
legacySetting.endGroup();
|
|
return;
|
|
}
|
|
|
|
QSettings messageIni(settingsPath + "messages.ini", QSettings::IniFormat);
|
|
for (const auto &key : keys) {
|
|
if (key == "count") {
|
|
messageIni.setValue("messages/count", legacySetting.value("count"));
|
|
} else {
|
|
messageIni.setValue("messages/" + key, legacySetting.value(key));
|
|
}
|
|
}
|
|
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;
|
|
legacySetting.beginGroup("filter_games");
|
|
QStringList keys = legacySetting.allKeys();
|
|
if (keys.isEmpty()) {
|
|
legacySetting.endGroup();
|
|
return;
|
|
}
|
|
|
|
const QMap<QString, QString> &filterKeyMap = renamedGameFilterKeys();
|
|
|
|
QSettings filtersIni(settingsPath + "gamefilters.ini", QSettings::IniFormat);
|
|
for (auto it = filterKeyMap.constBegin(); it != filterKeyMap.constEnd(); ++it) {
|
|
if (legacySetting.contains(it.key())) {
|
|
filtersIni.setValue("filter_games/" + it.value(), legacySetting.value(it.key()));
|
|
}
|
|
}
|
|
const QString gameTypePrefix = "game_type/";
|
|
for (const auto &key : keys) {
|
|
if (key.startsWith(gameTypePrefix)) {
|
|
filtersIni.setValue("filter_games/gameType/" + key.mid(gameTypePrefix.size()), legacySetting.value(key));
|
|
}
|
|
}
|
|
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);
|
|
if (personalIni.value("migration/legacy_complete", false).toBool()) {
|
|
return false;
|
|
}
|
|
|
|
migrateLegacySets(settingsPath);
|
|
migrateLegacyServers(settingsPath);
|
|
migrateLegacyMessages(settingsPath);
|
|
migrateLegacyGameFilters(settingsPath);
|
|
|
|
personalIni.setValue("migration/legacy_complete", true);
|
|
personalIni.sync();
|
|
return true;
|
|
}
|
|
|
|
bool SettingsMigration::migrateSettingsFromGlobalIni(const QString &settingsPath)
|
|
{
|
|
if (!hasPendingGlobalIniSettings(settingsPath)) {
|
|
return false;
|
|
}
|
|
|
|
QSettings globalIni(settingsPath + "global.ini", QSettings::IniFormat);
|
|
|
|
migrateTabsSettings(settingsPath, globalIni);
|
|
migrateSoundSettings(settingsPath, globalIni);
|
|
migrateGameSettings(settingsPath, globalIni);
|
|
migrateChatSettings(settingsPath, globalIni);
|
|
migrateCacheStorageSettings(settingsPath, globalIni);
|
|
migrateUpdatesSettings(settingsPath, globalIni);
|
|
migratePersonalSettings(settingsPath, globalIni);
|
|
migrateDownloadSettings(settingsPath, globalIni);
|
|
migrateCardsDisplaySettings(settingsPath, globalIni);
|
|
migrateCardCounterSettings(settingsPath, globalIni);
|
|
migrateInterfaceSettings(settingsPath, globalIni);
|
|
migrateAppearanceSettings(settingsPath, globalIni);
|
|
migrateNetworkSettings(settingsPath, globalIni);
|
|
migratePathsSettings(settingsPath, globalIni);
|
|
migrateVisualDeckStorageSettings(settingsPath, globalIni);
|
|
migrateDeckEditorSettings(settingsPath, globalIni);
|
|
|
|
QFile::remove(settingsPath + "global.ini.old");
|
|
QFile::rename(settingsPath + "global.ini", settingsPath + "global.ini.old");
|
|
|
|
QSettings newGlobalIni(settingsPath + "global.ini", QSettings::IniFormat);
|
|
newGlobalIni.setValue(MIGRATION_SENTINEL_KEY, true);
|
|
newGlobalIni.sync();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SettingsMigration::isMigrationPending(const QString &settingsPath)
|
|
{
|
|
if (hasPendingGlobalIniSettings(settingsPath)) {
|
|
return true;
|
|
}
|
|
|
|
if (gameFiltersCamelCaseMigrationPending(settingsPath)) {
|
|
return true;
|
|
}
|
|
|
|
if (appearanceMaxFontSizeMigrationPending(settingsPath)) {
|
|
return true;
|
|
}
|
|
|
|
QSettings personalIni(settingsPath + "personal.ini", QSettings::IniFormat);
|
|
if (personalIni.value("migration/legacy_complete", false).toBool()) {
|
|
return false;
|
|
}
|
|
|
|
// migrateLegacySettings() also runs on an empty profile, where it only writes its sentinel.
|
|
// Reporting that as pending would leave a backup folder behind on every fresh install, so
|
|
// only count it when there really are legacy keys left to move.
|
|
return !QSettings().allKeys().isEmpty();
|
|
}
|
|
|
|
QString SettingsMigration::backupSettingsDirectory(const QString &settingsPath)
|
|
{
|
|
QDir settingsDir(QDir::cleanPath(settingsPath));
|
|
if (!settingsDir.exists()) {
|
|
return QString();
|
|
}
|
|
|
|
const QDir::Filters fileFilters = QDir::Files | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot;
|
|
|
|
// A profile holding nothing but empty files has nothing to lose, and backing it up would only
|
|
// leave clutter next to the settings folder.
|
|
const QFileInfoList files = settingsDir.entryInfoList(fileFilters);
|
|
const bool hasContent =
|
|
std::any_of(files.cbegin(), files.cend(), [](const QFileInfo &file) { return file.size() > 0; });
|
|
if (!hasContent) {
|
|
return QString();
|
|
}
|
|
|
|
// Keep the backup a sibling of the settings folder rather than a child: a folder that is never
|
|
// copied into its own backup, and one the "Open settings folder" action does not have to skip.
|
|
QDir backupParentDir(settingsDir.absolutePath());
|
|
if (!backupParentDir.cdUp()) {
|
|
qCWarning(SettingsMigrationLog) << "[SettingsMigration] Could not resolve the parent of the settings folder:"
|
|
<< settingsDir.absolutePath();
|
|
return QString();
|
|
}
|
|
|
|
const QString backupName =
|
|
QStringLiteral("settings-backup-") + QDateTime::currentDateTime().toString("yyyyMMdd-HHmmss");
|
|
QString backupPath = backupParentDir.absoluteFilePath(backupName);
|
|
// Two migrations inside the same second must not merge into a single folder.
|
|
for (int suffix = 1; QFileInfo::exists(backupPath); ++suffix) {
|
|
backupPath = backupParentDir.absoluteFilePath(backupName + '-' + QString::number(suffix));
|
|
}
|
|
|
|
if (!QDir().mkpath(backupPath)) {
|
|
qCWarning(SettingsMigrationLog) << "[SettingsMigration] Could not create the settings backup folder:"
|
|
<< backupPath;
|
|
return QString();
|
|
}
|
|
|
|
QDir backupDir(backupPath);
|
|
QDirIterator it(settingsDir.absolutePath(), fileFilters, QDirIterator::Subdirectories);
|
|
while (it.hasNext()) {
|
|
it.next();
|
|
|
|
const QString target = backupDir.absoluteFilePath(settingsDir.relativeFilePath(it.filePath()));
|
|
QDir().mkpath(QFileInfo(target).absolutePath());
|
|
if (!QFile::copy(it.filePath(), target)) {
|
|
// A partially copied backup still beats none: report it and let the migration run.
|
|
qCWarning(SettingsMigrationLog) << "[SettingsMigration] Could not back up settings file:" << it.filePath();
|
|
}
|
|
}
|
|
|
|
qCInfo(SettingsMigrationLog) << "[SettingsMigration] Settings backed up to" << backupPath;
|
|
return backupPath;
|
|
}
|