From b0655c499cffc9cd8b4351849650162dcc17906b Mon Sep 17 00:00:00 2001 From: Gemba Date: Sat, 31 Jan 2026 19:15:00 +0100 Subject: [PATCH] cache operation fixes when using subcommands on all platforms - strict validation of cache subcommands - cache commands operating on all platforms fixed - updated cache CLI documentation - mute chatty libpng lib info messages --- docs/CLIHELP.md | 6 +- src/cache.cpp | 163 +++++++++++++++++++++++++-------------------- src/cache.h | 13 ++-- src/cli.cpp | 7 +- src/main.cpp | 19 ++++-- src/settings.cpp | 101 ++++++++++++++++++++-------- src/settings.h | 4 ++ src/skyscraper.cpp | 57 +++++++++------- src/skyscraper.h | 13 +++- 9 files changed, 239 insertions(+), 144 deletions(-) diff --git a/docs/CLIHELP.md b/docs/CLIHELP.md index b330bed3..c9e54d82 100644 --- a/docs/CLIHELP.md +++ b/docs/CLIHELP.md @@ -284,7 +284,7 @@ This is a powerful option that allows you to purge the requested resources from You can purge _all_ resources from the cache for the chosen platform using the keyword `all`. -If no platform is specified, the `purge:all` operation will apply to all existing platforms stored in the cache. In this scenario, any platform-specific configurations defined in the configfile will be disregarded. +If _no platform is specified_, then the `purge:all` operation will apply to all existing platforms stored in the cache. In this scenario, any platform-specific configurations defined in Skyscraper's configfile will be disregarded. You can purge specific resources from a certain module with `m=` or of a certain type with `t=` or a combination of the two separated by a comma. If both `m=` and `t=` are provided, then only resources are removed which match both criterias. See also examples below. @@ -333,7 +333,7 @@ You can use any of the following: Supported resource types are: `title`, `platform`, `description`, `publisher`, `developer`, `ages`, `tags`, `rating`, `releasedate`, `cover`, `screenshot`, `wheel`, `marquee`, `video`. -If no platform is specified, reports will be generated for all existing platforms stored in the cache. In this scenario, any platform-specific configurations defined in the config.ini file will be disregarded. +If _no platform is specified_, reports will be generated for all existing platforms stored in the cache. In this scenario, any platform-specific configurations defined in Skyscraper's configfile will be disregarded, especially any settings or commandline parameters for [`addExtensions`](CONFIGINI.md#addextensions) or [`extensions`](CONFIGINI.md#extensions). !!! tip @@ -362,7 +362,7 @@ Skyscraper -p snes --cache show You can purge all resources that don't have any connection to your current romset for the selected platform by using the `vacuum` command. This is extremely useful if you've removed a bunch of roms from your collection and you wish to purge any cached data you don't need anymore. -If no platform is specified, the vacuum operation will apply to all existing platforms stored in the cache. In this scenario, any platform-specific configurations defined in the config.ini file will be disregarded. +If no platform is specified, the vacuum operation will apply to all existing platforms stored in the cache. In this scenario, any platform-specific configurations defined in Skyscraper's configfile will be disregarded, especially any settings or commandline parameters for [`addExtensions`](CONFIGINI.md#addextensions) or [`extensions`](CONFIGINI.md#extensions). !!! danger "Possible dangerous command" diff --git a/src/cache.cpp b/src/cache.cpp index cca4e933..216486be 100644 --- a/src/cache.cpp +++ b/src/cache.cpp @@ -211,7 +211,9 @@ bool Cache::read() { } } printf("\033[1;32mDone!\033[0m\n"); - printf("Cached %d files\n\n", static_cast(fileEntries.count())); + int count = static_cast(fileEntries.count()); + printf("Cached %d %s\n\n", count, + pluralizeWordStd("file", count != 1).c_str()); printf("Reading and parsing resource cache, please wait... "); fflush(stdout); @@ -905,14 +907,20 @@ bool Cache::purgeResources(QString purgeStr) { purged++; } } - printf("Successfully purged %d %s from the cache.\n", purged, - pluralizeWordStd("resource", purged != 1).c_str()); + if (purged == 0) { + printf("No resources for the current platform found or no match for " + "the criteria.\n"); + return false; + } else { + printf("Successfully purged %d %s from the resource cache.\n", purged, + pluralizeWordStd("resource", purged != 1).c_str()); + } return true; } -bool Cache::purgeAll(const bool unattend) { +bool Cache::purgeAllOnSinglePlatform(const bool unattend) { if (!unattend) { - printf("\033[1;31mWARNING! You are about to purge / remove ALL " + printf("\033[1;31mWARNING! You are about to remove ALL " "resources from the Skyscaper cache connected to the currently " "selected platform. THIS CANNOT BE UNDONE!\033[0m\n\n"); printf("\033[1;34mDo you wish to continue\033[0m (y/N)? "); @@ -946,12 +954,13 @@ bool Cache::purgeAll(const bool unattend) { it.remove(); purged++; } - printf("\033[1;32m Done!\033[0m\n"); if (purged == 0) { + printf("\033[1;32m Foiled!\033[0m\n"); printf("No resources for the current platform found in the resource " "cache.\n"); return false; } else { + printf("\033[1;32m Done!\033[0m\n"); printf("Successfully purged %d %s from the resource cache.\n", purged, pluralizeWordStd("resource", purged != 1).c_str()); } @@ -962,12 +971,14 @@ bool Cache::purgeAll(const bool unattend) { bool Cache::isCommandValidOnAllPlatform(const QString &command) { QList validCommands({"help", "purge:all", "vacuum", "validate"}); return validCommands.contains(command) || - command.contains("report:missing"); + command.startsWith("report:missing="); } -void Cache::purgeAllPlatform(Settings config, Skyscraper *app) { - printf("\033[1;31mWARNING! You are about to purge / remove ALL " - "resources from the Skyscaper cache. \033[0m\n\n"); +void Cache::purgeAllOnAllPlatforms(Settings &config, Skyscraper *app) { + printf("\033[1;31mWARNING! You are about to remove ALL " + "resources for EVERY platform from the Skyscaper cache. Please " + "consider making a backup of your Skyscraper cache before " + "performing this action. THIS CANNOT BE UNDONE!\033[0m\n\n"); printf("\033[1;34mDo you wish to continue\033[0m (y/N)? "); std::string userInput = ""; getline(std::cin, userInput); @@ -982,7 +993,7 @@ void Cache::purgeAllPlatform(Settings config, Skyscraper *app) { cacheDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { config.platform = platform; Cache cache(cacheDir.filePath(platform)); - if (cache.read() && cache.purgeAll(true)) { + if (cache.read() && cache.purgeAllOnSinglePlatform(true)) { app->state = Skyscraper::OpMode::NO_INTR; cache.write(); app->state = Skyscraper::OpMode::SINGLE; @@ -990,19 +1001,28 @@ void Cache::purgeAllPlatform(Settings config, Skyscraper *app) { } } -void Cache::reportAllPlatform(Settings config, Skyscraper *app) { +bool Cache::reportAllPlatform(Settings &config, Skyscraper *app) { QDir cacheDir(config.cacheFolder); + QString initCacheFolder = config.cacheFolder; + QString initInputFolder = config.inputFolder; for (const auto &platform : cacheDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { config.platform = platform; - Cache cache(cacheDir.filePath(platform)); + config.cacheFolder = Config::concatPath(initCacheFolder, platform); + config.inputFolder = Config::concatPath(initInputFolder, platform); + qDebug() << "reportAllPlatform()" << config.inputFolder; + Cache cache(config.cacheFolder); if (cache.read()) { - cache.assembleReport(config, app->getPlatformFileExtensions()); - } + if (!cache.assembleReport( + config, app->getPlatformFileExtensions(platform))) { + return false; + } + } // ignore empty cache: cache.read() returns false } + return true; } -void Cache::vacuumAllPlatform(Settings config, Skyscraper *app) { +void Cache::vacuumAllPlatform(Settings &config, Skyscraper *app) { printf("\033[1;33mWARNING! Vacuuming your Skyscraper cache removes all " "resources that don't match your current romset. Please consider " "making a backup of your " @@ -1017,14 +1037,19 @@ void Cache::vacuumAllPlatform(Settings config, Skyscraper *app) { return; } + QString initCacheFolder = config.cacheFolder; + QString initInputFolder = config.inputFolder; QDir cacheDir(config.cacheFolder); for (const auto &platform : cacheDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { - Cache cache(cacheDir.filePath(platform)); config.platform = platform; + config.cacheFolder = Config::concatPath(initCacheFolder, platform); + config.inputFolder = Config::concatPath(initInputFolder, platform); + qDebug() << "vacuumAllPlatform()" << config.inputFolder; + Cache cache(config.cacheFolder); if (cache.read() && cache.vacuumResources(QDir(config.inputFolder).filePath(platform), - app->getPlatformFileExtensions(), + app->getPlatformFileExtensions(platform), config.verbosity, true)) { app->state = Skyscraper::OpMode::NO_INTR; cache.write(); @@ -1033,11 +1058,16 @@ void Cache::vacuumAllPlatform(Settings config, Skyscraper *app) { } } -void Cache::validateAllPlatform(Settings config, Skyscraper *app) { +void Cache::validateAllPlatform(Settings &config, Skyscraper *app) { + QString initCacheFolder = config.cacheFolder; + QString initInputFolder = config.inputFolder; QDir cacheDir(config.cacheFolder); for (const auto &platform : cacheDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { config.platform = platform; + config.cacheFolder = Config::concatPath(initCacheFolder, platform); + config.inputFolder = Config::concatPath(initInputFolder, platform); + qDebug() << "validateAllPlatform()" << config.inputFolder; Cache cache(cacheDir.filePath(platform)); if (cache.read()) { cache.validate(); @@ -1063,7 +1093,7 @@ QList Cache::getFileInfos(const QString &inputFolder, fileInfos.append(dirIt.fileInfo()); } if (fileInfos.isEmpty()) { - printf("\nInput folder returned no entries...\n\n"); + printf("Input folder returned no entries...\n"); } } else { printf("Found less than two suffix filters. Something is wrong...\n"); @@ -1091,15 +1121,8 @@ QList Cache::getCacheIdList(const QList &fileInfos) { return cacheIdList; } -void Cache::assembleReport(const Settings &config, const QString filter) { +bool Cache::assembleReport(const Settings &config, const QString filter) { QString reportStr = config.cacheOptions; - - if (!reportStr.contains("report:missing=")) { - printf("\033[1;31mAmbiguous cache report option '%s'.\n\033[0m", - reportStr.toStdString().c_str()); - Cli::cacheReportMissingUsage(); - return; - } reportStr.remove("report:missing="); QString missingOption = reportStr.simplified(); @@ -1132,22 +1155,15 @@ void Cache::assembleReport(const Settings &config, const QString filter) { printf("\033[1;31mUnknown resource type '%s'!\033[0m\n", resType.toStdString().c_str()); Cli::cacheReportMissingUsage(); - return; - } - } - if (resTypeList.isEmpty()) { - printf("Resource type list is empty, cancelling...\n"); - return; - } else { - printf("Creating %s for resource %s:\n", - pluralizeWordStd("report", resTypeList.size() != 1).c_str(), - pluralizeWordStd("type", resTypeList.size() != 1).c_str()); - for (const auto &resType : resTypeList) { - printf(" %s\n", resType.toStdString().c_str()); + return false; } - printf("\n"); } + printf("Creating %s for resource %s:\n", + pluralizeWordStd("report", resTypeList.size() != 1).c_str(), + pluralizeWordStd("type", resTypeList.size() != 1).c_str()); + printf(" %s\n", resTypeList.join(", ").toStdString().c_str()); + // Create the reports folder QDir reportsDir(Config::getSkyFolder(Config::SkyFolderType::REPORT)); if (!reportsDir.exists()) { @@ -1155,7 +1171,7 @@ void Cache::assembleReport(const Settings &config, const QString filter) { printf("Couldn't create reports folder '%s'. Please check " "permissions then try again...\n", reportsDir.absolutePath().toStdString().c_str()); - return; + return false; } } @@ -1167,8 +1183,9 @@ void Cache::assembleReport(const Settings &config, const QString filter) { if (!config.includePattern.isEmpty()) { fileInfos.filterFiles(config.includePattern, true); } - printf("%d compatible files found for the '%s' platform!\n", - static_cast(fileInfos.length()), + int count = static_cast(fileInfos.length()); + printf("%d compatible %s found for the '%s' platform!\n", count, + pluralizeWordStd("file", count != 1).c_str(), config.platform.toStdString().c_str()); printf("Creating file id list for all files, please wait..."); QList cacheIdList = getCacheIdList(fileInfos); @@ -1177,10 +1194,11 @@ void Cache::assembleReport(const Settings &config, const QString filter) { if (fileInfos.length() != cacheIdList.length()) { printf("Length of cache id list mismatch the number of files, " "something is wrong! Please file an issue. Can't continue...\n"); - return; + return false; } QString dateTime = QDateTime::currentDateTime().toString("yyyyMMdd"); + bool hasMissing = false; for (const auto &resType : resTypeList) { QString rFn = QString("%1/report-%2-missing_%3-%4.txt") .arg(reportsDir.absolutePath()) @@ -1188,10 +1206,12 @@ void Cache::assembleReport(const Settings &config, const QString filter) { .arg(resType) .arg(dateTime); QFile reportFile(rFn); - printf("Report filename: '\033[1;32m%s\033[0m'\nAssembling report, " - "please wait...", - Config::pathToCStr(rFn)); - if (reportFile.open(QIODevice::WriteOnly)) { + printf("Assembling report for platform '\033[1;32m%s\033[0m' and " + "resource '\033[1;32m%s\033[0m', please wait...", + config.platform.toStdString().c_str(), + resType.toStdString().c_str()); + + if (fileInfos.length() > 0 && reportFile.open(QIODevice::WriteOnly)) { int missing = 0; int dots = 0; int dotMod = fileInfos.size() * 0.1 + 1; @@ -1216,11 +1236,17 @@ void Cache::assembleReport(const Settings &config, const QString filter) { fileInfos.at(a).absoluteFilePath().toUtf8() + "\n"); } } + hasMissing |= missing > 0; reportFile.close(); - printf("\033[1;32m Done!\033[0m\n\033[1;33m%d %s " - "do miss the '%s' resource.\033[0m\n\n", - missing, pluralizeWordStd("file", missing != 1).c_str(), + printf("\033[1;32m Done!\033[0m\n\033[1;33m %d of %d %s " + "miss the '%s' resource.\033[0m\n", + missing, fileInfos.length(), + pluralizeWordStd("file", fileInfos.length() != 1).c_str(), resType.toStdString().c_str()); + if (missing > 0) { + printf(" Files in: %s\n", Config::pathToCStr(rFn)); + } + printf("\n"); } else { printf("Report file could not be opened for writing, please check " "permissions of folder '%s', then try " @@ -1228,14 +1254,17 @@ void Cache::assembleReport(const Settings &config, const QString filter) { Config::getSkyFolder(Config::SkyFolderType::REPORT) .toStdString() .c_str()); - return; + return false; } } - printf("\033[1;32mAll done!\033[0m\nConsider using the '\033[1;33m--cache " - "edit --includefrom \033[0m' or the '\033[1;33m-s " - "import\033[0m' module to add the missing resources. Check " - "'\033[1;33m--help\033[0m' and '\033[1;33m--cache help\033[0m' for " - "more information.\n\n"); + if (hasMissing) { + printf("\033[1;32mAll done!\033[0m\nConsider using the " + "'\033[1;33m--cache edit --includefrom \033[0m' or " + "the '\033[1;33m-s import\033[0m' module to add the missing " + "resources. Check '\033[1;33m--help\033[0m' and " + "'\033[1;33m--cache help\033[0m' for more information.\n\n"); + } + return true; } bool Cache::vacuumResources(const QString inputFolder, const QString filter, @@ -1264,9 +1293,6 @@ bool Cache::vacuumResources(const QString inputFolder, const QString filter, } } - printf("Vacuuming cache for %s platform, this can take several minutes, " - "please wait...", - cacheDir.dirName().toStdString().c_str()); QList fileInfos = getFileInfos(inputFolder, filter); // Clean the quick id's aswell QMap> quickIdsCleaned; @@ -1283,18 +1309,13 @@ bool Cache::vacuumResources(const QString inputFolder, const QString filter, return false; } + printf("Vacuuming cache for %s platform, hang on...\n", + cacheDir.dirName().toStdString().c_str()); + int vacuumed = 0; { - int dots = 0; - int dotMod = resources.size() * 0.1 + 1; - QMutableListIterator it(resources); while (it.hasNext()) { - if (dots % dotMod == 0) { - printf("."); - fflush(stdout); - } - dots++; Resource res = it.next(); bool remove = true; for (const auto &cacheId : cacheIdList) { @@ -1318,8 +1339,8 @@ bool Cache::vacuumResources(const QString inputFolder, const QString filter, } printf("\033[1;32m Done!\033[0m\n"); if (vacuumed == 0) { - printf("All resources match a file in your romset. No resources " - "vacuumed.\n"); + printf("All resources match a file in your romset. Done with " + "housekeeping.\n"); return false; } else { printf("Successfully vacuumed %d resources from the resource cache.\n", diff --git a/src/cache.h b/src/cache.h index c09c5b5c..1b00d01b 100644 --- a/src/cache.h +++ b/src/cache.h @@ -75,10 +75,11 @@ class Cache { Cache(const QString &cacheFolder); static bool isCommandValidOnAllPlatform(const QString &command); - static void purgeAllPlatform(Settings config, Skyscraper *app); - static void reportAllPlatform(Settings config, Skyscraper *app); - static void vacuumAllPlatform(Settings config, Skyscraper *app); - static void validateAllPlatform(Settings config, Skyscraper *app); + + static void purgeAllOnAllPlatforms(Settings &config, Skyscraper *app); + static bool reportAllPlatform(Settings &config, Skyscraper *app); + static void vacuumAllPlatform(Settings &config, Skyscraper *app); + static void validateAllPlatform(Settings &config, Skyscraper *app); static const QStringList getAllResourceTypes(); static const QStringList getBinResourceTypes(); @@ -88,11 +89,11 @@ class Cache { void printPriorities(QString cacheId); void editResources(QSharedPointer queue, const QString &command = "", const QString &type = ""); - bool purgeAll(const bool unattend = false); + bool purgeAllOnSinglePlatform(const bool unattend = false); bool purgeResources(QString purgeStr); bool vacuumResources(const QString inputFolder, const QString filters, const int verbosity, const bool unattend = false); - void assembleReport(const Settings &config, const QString filters); + bool assembleReport(const Settings &config, const QString filters); void showStats(int verbosity); void readPriorities(); bool write(const bool onlyQuickId = false); diff --git a/src/cli.cpp b/src/cli.cpp index 4eaf2be3..4386145f 100644 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -304,8 +304,9 @@ void Cli::subCommandUsage(const QString subCmd) { "following is a list of valid flags\nand what they do.\n"); } - printf("\nShowing '\033[1;33m--%s ...\033[0m' help:\n\n", - subCmd.toUtf8().constData()); + printf("\nShowing '\033[1;33m--%s%s...\033[0m' help:\n\n", + subCmd.toUtf8().constData(), + subCmd == "cache report:missing=" ? "" : " "); QMap subOptions; subOptions = getSubCommandOpts(subCmd); @@ -551,7 +552,7 @@ void Cli::showHint() { #ifdef Q_OS_LINUX hint = hint.replace(QDir::homePath(), "~"); #endif - hint = "\033[1;33mDID YOU KNOW:\033[0m " + hint; + hint = "\033[1;33mDID YOU KNOW\033[0m: " + hint; QStringList hintWrapped; int ptr = 0; diff --git a/src/main.cpp b/src/main.cpp index 8248ecad..4be1d00b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -45,11 +45,18 @@ int sigIntRequests = 0; void customMessageHandler(QtMsgType type, const QMessageLogContext &, const QString &msg) { + const QStringList libpngNoise = QStringList( + {"known incorrect sRGB profile", "cHRM chunk does not match sRGB", + "profile matches sRGB but writing iCCP instead", + "known incorrect sRGB profile"}); QString txt; // Decide which type of debug message it is, and add string to signify it // Then append the debug message itself to the same string. switch (type) { case QtInfoMsg: + for (auto const &pngMsg : libpngNoise) + if (msg.contains(pngMsg)) + return; txt += QString(" INFO: %1").arg(msg); break; case QtDebugMsg: @@ -58,18 +65,16 @@ void customMessageHandler(QtMsgType type, const QMessageLogContext &, case QtWarningMsg: if (msg.contains("NetManager") || msg.contains( - "QSqlQuery::value: not positioned on a valid record") || - // libpng warnings - msg.contains("known incorrect sRGB profile") || - msg.contains("cHRM chunk does not match sRGB") || - msg.contains("profile matches sRGB but writing iCCP instead") || - msg.contains("known incorrect sRGB profile")) { + "QSqlQuery::value: not positioned on a valid record")) { /* ugly, needs proper fix: */ // NetManager "Cannot create children for a parent that is in a // different thread." return; } - txt += QString(" WARN: %1").arg(msg); + for (auto const &pngMsg : libpngNoise) + if (msg.contains(pngMsg)) + return; + txt += QString(" \033[1;33mWARN\033[0m: %1").arg(msg); break; case QtCriticalMsg: txt += QString(" CRIT: %1").arg(msg); diff --git a/src/settings.cpp b/src/settings.cpp index b2c3596e..fbcd53a1 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -47,7 +47,7 @@ RuntimeCfg::~RuntimeCfg(){}; void RuntimeCfg::reportInvalidPlatform() { if (parser->isSet("p")) { - printf("\033[1;31mUnknown platform '%s' provided. \033[0m", + printf("\033[1;31mUnknown platform '%s' provided: \033[0m\n", parser->value("p").toUtf8().constData()); } printf("\033[1;31mPlease set a valid platform with '-p " @@ -73,20 +73,49 @@ void RuntimeCfg::applyConfigIni(CfgType type, QSettings *settings, // config.ini may set platform= in [main] config->platform = settings->value("platform").toString(); } else { - bool isCacheOK = - parser->isSet("cache") && - Cache::isCommandValidOnAllPlatform(parser->value("cache")); - bool isFlagsOK = - parser->isSet("flags") && parseFlags().contains("help"); + // no platform given or unknown platform provided + bool purgeOnPlatform = false; + bool noPlatformCmd = false; + bool infoAndExitOption = false; + if (parser->isSet("cache")) { + QString cacheOpts = + parser->value("cache").simplified().replace(" ", ""); + if (!validateCacheSubCommand(cacheOpts)) { + printf("\033[1;31mAmbiguous cache subcommand '--cache " + "%s'\033[0m, check '%s' for more info.\n", + cacheOpts.toStdString().c_str(), + cacheOpts.startsWith("report:") + ? "--cache report:missing=help" + : "--cache help"); + exit(2); + } + noPlatformCmd = Cache::isCommandValidOnAllPlatform(cacheOpts); + // noPlatformCmd is true for purge:all + if (!noPlatformCmd && cacheOpts.startsWith("purge:")) { + purgeOnPlatform = validatePurgeParameters(cacheOpts); + if (!purgeOnPlatform) { + printf("\033[1;31mInvalid purge: parameter '--cache " + "%s'\033[0m, please check '--cache help' for " + "more info.\n", + cacheOpts.toStdString().c_str()); + exit(2); + } + } + } else { + infoAndExitOption = + (parser->isSet("flags") && parseFlags().contains("help")) || + parser->isSet("hint") || parser->isSet("buildinfo"); + } + bool invalidPlatform = !infoAndExitOption && parser->isSet("p") && + !plafs.contains(parser->value("p")); - if (!isCacheOK && !isFlagsOK && !parser->isSet("hint") && - !parser->isSet("buildinfo")) { + if (invalidPlatform || purgeOnPlatform || + (!noPlatformCmd && !infoAndExitOption)) { reportInvalidPlatform(); - exit(1); + exit(2); } } } - config->arcadePlatform = isArcadePlatform(config->platform); // get all enabled/set keys of this section @@ -171,6 +200,9 @@ void RuntimeCfg::applyConfigIni(CfgType type, QSettings *settings, ? Config::concatPath(v, config->platform) : v; config->cacheFolder = toAbsolutePath(false, v); + if (type != CfgType::MAIN) { + config->cacheFolderNotMain = true; + } continue; } if (k == "emulator") { @@ -232,10 +264,9 @@ void RuntimeCfg::applyConfigIni(CfgType type, QSettings *settings, if (config->frontend == "emulationstation") { config->gameListVariants = v; } else { - printf( - "\033[1;33mParameter %s is ignored. Only " - "applicable with frontend=emulationstation.\n\033[0m", - k.toUtf8().constData()); + printf("\033[1;33mParameter %s is ignored. Only applicable " + "with frontend=emulationstation.\n\033[0m", + k.toUtf8().constData()); } continue; } @@ -261,6 +292,9 @@ void RuntimeCfg::applyConfigIni(CfgType type, QSettings *settings, ? Config::concatPath(v, config->platform) : v; inputFolderSet = true; + if (type != CfgType::MAIN) { + config->inputFolderNotMain = true; + } continue; } if (k == "lang") { @@ -497,10 +531,10 @@ void RuntimeCfg::applyConfigIni(CfgType type, QSettings *settings, bool intOk; int v = ss.toInt(&intOk); if (!intOk) { - printf( - "\033[1;31mConversion of %s to integer failed for option " - "%s. Please fix in config.ini.\n\033[0m", - ss.toString().toUtf8().constData(), k.toUtf8().constData()); + printf("\033[1;31mConversion of %s to integer failed for " + "option %s. Please fix in config.ini.\n\033[0m", + ss.toString().toUtf8().constData(), + k.toUtf8().constData()); exit(1); } if (k == "jpgQuality") { @@ -583,6 +617,7 @@ void RuntimeCfg::applyCli(bool &inputFolderSet, bool &gameListFolderSet, if (parser->isSet("i")) { config->inputFolder = parser->value("i"); inputFolderSet = true; + config->inputFolderNotMain = true; } if (parser->isSet("g")) { config->gameListFolder = toAbsolutePath(true, parser->value("g")); @@ -609,6 +644,7 @@ void RuntimeCfg::applyCli(bool &inputFolderSet, bool &gameListFolderSet, } if (parser->isSet("d")) { config->cacheFolder = toAbsolutePath(true, parser->value("d")); + config->cacheFolderNotMain = true; } else if (config->cacheFolder.isEmpty()) { // failsafe: no CLI and no config.ini cacheFolder provided config->cacheFolder = Config::concatPath( @@ -673,8 +709,8 @@ void RuntimeCfg::applyCli(bool &inputFolderSet, bool &gameListFolderSet, Cli::cacheReportMissingUsage(); exit(0); } else if (config->cacheOptions.startsWith("purge:")) { - bool invalid = validatePurgeParameters(config->cacheOptions); - if (invalid) { + bool valid = validatePurgeParameters(config->cacheOptions); + if (!valid) { printf( "\033[1;31mInvalid purge: parameter '--cache %s'\033[0m, " "please check '--cache help' for more info.\n", @@ -875,6 +911,17 @@ bool RuntimeCfg::validateFileParameter(const QString ¶m, QString &val) { return true; } +bool RuntimeCfg::validateCacheSubCommand(const QString &subCmd) { + const QStringList cmdsRe = QStringList( + {"^(edit(:new=[a-z]{4,})?", "help", "merge:.+", "purge:all", + "purge:(m|t)=[a-z]+(,(m|t)=[a-z]+)?", "refresh", + "report:missing=[a-z]{3,}", "show", "vacuum", "validate)$"}); + const QRegularExpression REGEX_CACHE_SUBCMD = + QRegularExpression(cmdsRe.join("|")); + bool valid = REGEX_CACHE_SUBCMD.match(subCmd).hasMatch(); + return valid; +} + bool RuntimeCfg::validatePurgeParameters(QString &purgeParam) { const QRegularExpression REGEX_PURGE_OPTS = QRegularExpression( "^purge:(?all$)|(?m=\\w+|t=\\w+)(,(?m=\\w+|t=\\w+))?$"); @@ -885,16 +932,16 @@ bool RuntimeCfg::validatePurgeParameters(QString &purgeParam) { qDebug() << para1; QString para2 = m.captured("p2"); qDebug() << para2; - bool invalid = false; + bool valid = true; if (all.isNull() && para1.isNull()) { // no further suboption given - invalid = true; + valid = false; } else if (!para1.isNull() || !para2.isNull()) { if (!para2.isNull() && (para1.split("=")[0] == para2.split("=")[0])) { printf("\033[1;31mpurge:%s=\033[0m may only be applied " "once.\n", para1.split("=")[0].toStdString().c_str()); - invalid = true; + valid = false; } // validate t= QStringList resTypes = Cache::getAllResourceTypes(); @@ -912,7 +959,7 @@ bool RuntimeCfg::validatePurgeParameters(QString &purgeParam) { typeParam = para2.split("=")[1]; } if (!typeParam.isEmpty() && !resTypes.contains(typeParam)) { - invalid = true; + valid = false; printf("\033[1;31mpurge:t=%s is invalid\033[0m: t= may only " "contain one of: %s.\n", typeParam.toStdString().c_str(), @@ -930,21 +977,21 @@ bool RuntimeCfg::validatePurgeParameters(QString &purgeParam) { moduleParam = para2.split("=")[1]; } if (!moduleParam.isEmpty() && !scrapers.contains(moduleParam)) { - invalid = true; + valid = false; printf("\033[1;31mpurge:m=%s is invalid\033[0m: m= may only " "contain one of: %s.\n", moduleParam.toStdString().c_str(), scrapers.join(", ").toStdString().c_str()); } } - if (!invalid && all.isNull()) { + if (valid && all.isNull()) { // rewrite clean parameters for further processing purgeParam = "purge:" % para1; if (!para2.isNull()) { purgeParam.append("," % para2); } } - return invalid; + return valid; } bool RuntimeCfg::scraperAllowedForMatch(const QString &providedScraper, diff --git a/src/settings.h b/src/settings.h index d4633ea0..ff36ba01 100644 --- a/src/settings.h +++ b/src/settings.h @@ -99,6 +99,9 @@ struct Settings { bool skipped = false; bool tidyDesc = true; bool ignoreYearInFilename = false; + // in use for the cache commands possible without providing a platform + bool inputFolderNotMain = false; + bool cacheFolderNotMain = false; QString artworkConfig = ""; QByteArray artworkXml = ""; QString excludePattern = ""; @@ -191,6 +194,7 @@ class RuntimeCfg : public QObject { void reportInvalidPlatform(); bool validateFileParameter(const QString ¶m, QString &val); bool validatePurgeParameters(QString &purgeParam); + bool validateCacheSubCommand(const QString &subcommand); bool scraperAllowedForMatch(const QString &providedScraper, const QString &opt); QString toAbsolutePath(bool isCliOpt, QString optionVal); diff --git a/src/skyscraper.cpp b/src/skyscraper.cpp index c941e8b3..4d5f0343 100644 --- a/src/skyscraper.cpp +++ b/src/skyscraper.cpp @@ -65,12 +65,26 @@ Skyscraper::~Skyscraper() { frontend->deleteLater(); } void Skyscraper::run() { // called after loadConfig() if (config.platform.isEmpty()) { - if (config.cacheOptions == "purge:all") { - Cache::purgeAllPlatform(config, this); + if (config.inputFolderNotMain || config.cacheFolderNotMain) { + printf("\033[1;33m'The hat trick never works!'\033[0m You have set " + "either the input folder or cache folder outside the [main] " + "configuration section. Paired with the ALL cache command " + "this may produce inconsistent results. Use the command " + "with a explicit platform (-p) or remove the setting(s). " + "Quitting...\n"); exit(0); - } else if (config.cacheOptions.contains("report:missing")) { - Cache::reportAllPlatform(config, this); + } + if (config.cacheOptions == "purge:all") { + Cache::purgeAllOnAllPlatforms(config, this); exit(0); + } else if (config.cacheOptions.startsWith("report:missing=")) { + bool ok = Cache::reportAllPlatform(config, this); + if (ok) { + exit(0); + } else { + printf("Premature end of Skyscraper run.\n"); + exit(1); + } } else if (config.cacheOptions == "vacuum") { Cache::vacuumAllPlatform(config, this); exit(0); @@ -197,17 +211,17 @@ void Skyscraper::run() { exit(0); } } - if (config.cacheOptions.contains("purge:") || - config.cacheOptions.contains("vacuum")) { + if (config.cacheOptions.startsWith("purge:") || + config.cacheOptions == "vacuum") { bool success = true; if (config.cacheOptions == "purge:all") { - success = cache->purgeAll(config.unattend || config.unattendSkip); + success = cache->purgeAllOnSinglePlatform(config.unattend || + config.unattendSkip); } else if (config.cacheOptions == "vacuum") { success = cache->vacuumResources( config.inputFolder, getPlatformFileExtensions(), config.verbosity, config.unattend || config.unattendSkip); - } else if (config.cacheOptions.contains("purge:m=") || - config.cacheOptions.contains("purge:t=")) { + } else if (config.cacheOptions.startsWith("purge:")) { success = cache->purgeResources(config.cacheOptions); } if (success) { @@ -217,7 +231,7 @@ void Skyscraper::run() { } exit(0); } - if (config.cacheOptions.contains("report:")) { + if (config.cacheOptions.startsWith("report:")) { cache->assembleReport(config, getPlatformFileExtensions()); exit(0); } @@ -228,7 +242,7 @@ void Skyscraper::run() { state = SINGLE; exit(0); } - if (config.cacheOptions.contains("merge:")) { + if (config.cacheOptions.startsWith("merge:")) { QFileInfo mergeCacheInfo(config.cacheOptions.replace("merge:", "")); if (mergeCacheInfo.isRelative()) { @@ -248,19 +262,13 @@ void Skyscraper::run() { printf("Path to merge from '%s' does not exist or is not a path, " "can't continue...\n", absMergeCacheFilePath.toStdString().c_str()); + exit(1); } exit(0); } - // remaining cache subcommand validation - bool cacheEditCmd = config.cacheOptions.left(4) == "edit"; - if (!config.cacheOptions.isEmpty() && !cacheEditCmd) { - printf("\033[1;31mAmbiguous cache subcommand '--cache %s'\033[0m, " - "check '--cache help' for more info.\n", - config.cacheOptions.toStdString().c_str()); - exit(0); - } - + // remaining cache subcommand check + bool cacheEditCmd = config.cacheOptions.startsWith("edit"); cache->readPriorities(); // Create shared queue with files to process @@ -929,10 +937,6 @@ void Skyscraper::loadConfig(const QCommandLineParser &parser) { // 5. Command line configs, overrides all rtConf->applyCli(inputFolderSet, gameListFolderSet, mediaFolderSet); - if (config.platform.isEmpty() && !config.cacheOptions.isEmpty()) { - return; // cache option to be applied to all platform - } - if (config.frontend == "emulationstation" || config.frontend == "retrobat") { frontend = QSharedPointer(new EmulationStation()); @@ -1023,6 +1027,11 @@ void Skyscraper::loadConfig(const QCommandLineParser &parser) { Config::makeAbsolutePath(config.inputFolder, config.mediaFolder); } config.inputFolder = Config::lexicallyNormalPath(config.inputFolder); + + if (config.platform.isEmpty() && !config.cacheOptions.isEmpty()) { + return; // cache option to be applied to all platform + } + if (!QFile::exists(config.inputFolder)) { printf("\033[1;31mBummer!\033[0m The provided input folder " "'\033[1;31m%s\033[0m' does not exist.\nFix your setup. Now " diff --git a/src/skyscraper.h b/src/skyscraper.h index 9aad7602..4aa2850f 100644 --- a/src/skyscraper.h +++ b/src/skyscraper.h @@ -52,9 +52,16 @@ class Skyscraper : public QObject { int state = SINGLE; void loadConfig(const QCommandLineParser &parser); - const inline QString getPlatformFileExtensions() { - return Platform::get().getFormats(config.platform, config.extensions, - config.addExtensions); + const inline QString getPlatformFileExtensions(QString platform = "") { + QString exts; + if (platform.isEmpty()) { + exts = Platform::get().getFormats( + config.platform, config.extensions, config.addExtensions); + } else { + // ignore extension variations (for cache ALL operations) + exts = Platform::get().getFormats(config.platform, "", ""); + } + return exts; } public slots: