diff --git a/src/cache.cpp b/src/cache.cpp index e08d2a55..51b47cab 100644 --- a/src/cache.cpp +++ b/src/cache.cpp @@ -25,6 +25,7 @@ #include "cache.h" +#include "cli.h" #include "nametools.h" #include "queue.h" @@ -61,26 +62,24 @@ static inline QStringList binTypes(bool withVideo = true, return binTypes; }; +const QStringList Cache::getAllResourceTypes() { + return txtTypes() + binTypes(); +} + Cache::Cache(const QString &cacheFolder) { cacheDir = QDir(cacheFolder); } bool Cache::createFolders(const QString &scraper) { - if (scraper != "cache") { - for (auto const &f : binTypes()) { - if (!cacheDir.mkpath( - QString("%1/%2s/%3") // plural 's' - .arg(cacheDir.absolutePath(), f, scraper))) { - return false; - } + for (auto const &f : binTypes()) { + if (!cacheDir.mkpath(QString("%1/%2s/%3") // plural 's' + .arg(cacheDir.absolutePath(), f, scraper))) { + return false; } } // Copy priorities.xml example file to cache folder if it doesn't already // exist - if (!QFileInfo::exists(cacheDir.absolutePath() + "/priorities.xml")) { - QFile::copy("cache/priorities.xml.example", - cacheDir.absolutePath() + "/priorities.xml"); - } - + QFile::copy("cache/priorities.xml.example", + cacheDir.absolutePath() + "/priorities.xml"); return true; } @@ -862,7 +861,7 @@ QList Cache::getFileInfos(const QString &inputFolder, printf("\nInput folder returned no entries...\n\n"); } } else { - printf("Found less than 2 suffix filters. Something is wrong...\n"); + printf("Found less than two suffix filters. Something is wrong...\n"); } return fileInfos; } @@ -893,8 +892,9 @@ void Cache::assembleReport(const Settings &config, const QString filter) { QString reportStr = config.cacheOptions; if (!reportStr.contains("report:missing=")) { - printf("Don't understand report option, please check '--cache help' " - "for more info.\n"); + printf("\033[1;31mAmbiguous cache report option '%s'.\n\033[0m", + reportStr.toStdString().c_str()); + Cli::cacheReportMissingUsage(); return; } reportStr.replace("report:missing=", ""); @@ -906,13 +906,19 @@ void Cache::assembleReport(const Settings &config, const QString filter) { } else { if (missingOption == "all") { resTypeList += txtTypes(false); // contains 'tags' instead 'genres' - resTypeList += binTypes(); + resTypeList.sort(); + QStringList bt = binTypes(); + bt.sort(); + resTypeList += bt; } else if (missingOption == "textual") { resTypeList += txtTypes(false); + resTypeList.sort(); } else if (missingOption == "artwork") { - resTypeList += binTypes(false, false); // w/o 'video' or 'manual' + resTypeList += binTypes(false, false); // w/o 'video' and 'manual' + resTypeList.sort(); } else if (missingOption == "media") { resTypeList += binTypes(); + resTypeList.sort(); } else { resTypeList.append(missingOption); // If a single type is given } @@ -920,42 +926,9 @@ void Cache::assembleReport(const Settings &config, const QString filter) { for (const auto &resType : resTypeList) { if (!binTypes().contains(resType) && !txtTypes(false).contains(resType)) { - if (resType != "help") { - printf("\033[1;31mUnknown resource type '%s'!\033[0m\n", - resType.toStdString().c_str()); - } - printf("Please use one of the following:\n"); - printf(" \033[1;32mhelp\033[0m: Shows this help message\n"); - printf(" \033[1;32mall\033[0m: Creates reports for all resource " - "types\n"); - printf(" \033[1;32mtextual\033[0m: Creates reports for all " - "textual resource types\n"); - printf(" \033[1;32martwork\033[0m: Creates reports for all " - "artwork related resource types excluding 'video'\n"); - printf(" \033[1;32mmedia\033[0m: Creates reports for all media " - "resource types including 'video'\n"); - printf( - " \033[1;32mtype1,type2,type3,...\033[0m: Creates reports for " - "selected types. Example: 'developer,screenshot,rating'\n"); - printf("\nAvailable resource types:\n"); - printf(" \033[1;32mtitle\033[0m\n"); - printf(" \033[1;32mplatform\033[0m\n"); - printf(" \033[1;32mdescription\033[0m\n"); - printf(" \033[1;32mpublisher\033[0m\n"); - printf(" \033[1;32mdeveloper\033[0m\n"); - printf(" \033[1;32mplayers\033[0m\n"); - printf(" \033[1;32mages\033[0m\n"); - printf(" \033[1;32mtags\033[0m\n"); - printf(" \033[1;32mrating\033[0m\n"); - printf(" \033[1;32mreleasedate\033[0m\n"); - printf(" \033[1;32mcover\033[0m\n"); - printf(" \033[1;32mscreenshot\033[0m\n"); - printf(" \033[1;32mwheel\033[0m\n"); - printf(" \033[1;32mmarquee\033[0m\n"); - printf(" \033[1;32mtexture\033[0m\n"); - printf(" \033[1;32mvideo\033[0m\n"); - printf(" \033[1;32mmanual\033[0m\n"); - printf("\n"); + printf("\033[1;31mUnknown resource type '%s'!\033[0m\n", + resType.toStdString().c_str()); + Cli::cacheReportMissingUsage(); return; } } diff --git a/src/cache.h b/src/cache.h index 8b5b8e4b..455eaa1c 100644 --- a/src/cache.h +++ b/src/cache.h @@ -69,6 +69,7 @@ struct ResCounts { class Cache { public: Cache(const QString &cacheFolder); + static const QStringList getAllResourceTypes(); bool createFolders(const QString &scraper); bool read(); void printPriorities(QString cacheId); diff --git a/src/cli.cpp b/src/cli.cpp index 77fbad40..5911423c 100644 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -20,6 +20,7 @@ #include "cli.h" +#include "cache.h" #include "strtools.h" #include @@ -378,7 +379,7 @@ QMap Cli::getSubCommandOpts(const QString subCmd) { "scraping module. Requires a scraping module set with " "'-s'. Similar to '--refresh'."}, }; - } else { + } else if (subCmd == "flags") { m = { {"help", "Prints this help and exits."}, {"forcefilename", @@ -473,6 +474,26 @@ QMap Cli::getSubCommandOpts(const QString subCmd) { "Enables scraping and caching of manuals for the scraping modules " "that support them."}, }; + } else { + QStringList resTypes = Cache::getAllResourceTypes(); + resTypes.sort(); + m = { + {"help", "Shows this help message and exits."}, + {"all", "Creates reports for all resource types."}, + {"textual", "Creates reports for all textual resource types."}, + {"artwork", "Creates reports for all artwork related resource " + "types excluding 'video' and 'manual'."}, + {"media", "Creates reports for all media resource types including " + "'video' and 'manual'."}, + {"type1,type2,type3,...", + "Creates reports for selected types. Example: " + "'developer,screenshot,rating'. Available resource types: " + + resTypes.join(", ")}, + }; } return m; } + +void Cli::cacheReportMissingUsage() { + subCommandUsage("cache report:missing="); +} diff --git a/src/cli.h b/src/cli.h index fd97c285..996dae68 100644 --- a/src/cli.h +++ b/src/cli.h @@ -27,6 +27,7 @@ namespace Cli { void createParser(QCommandLineParser *parser, const QString platforms); void subCommandUsage(const QString subCmd); QMap getSubCommandOpts(const QString subCmd); + void cacheReportMissingUsage(); } // namespace Cli #endif // CLI_H \ No newline at end of file diff --git a/src/settings.cpp b/src/settings.cpp index 511affaf..0946cee9 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -32,10 +32,10 @@ #endif static inline bool isArcadePlatform(const QString &platform) { - const QStringList arcadePlaforms = {"arcade", "fba", - "mame-advmame", "mame-libretro", - "mame-mame4all", "neogeo"}; - return arcadePlaforms.contains(platform); + const QStringList arcadePlatforms = {"arcade", "fba", + "mame-advmame", "mame-libretro", + "mame-mame4all", "neogeo"}; + return arcadePlatforms.contains(platform); } RuntimeCfg::RuntimeCfg(Settings *config, const QCommandLineParser *parser) { @@ -62,8 +62,9 @@ void RuntimeCfg::applyConfigIni(CfgType type, QSettings *settings, // config.ini may set platform= in [main] config->platform = settings->value("platform").toString(); } else { - bool cacheHelp = - parser->isSet("cache") && parser->value("cache") == "help"; + bool cacheHelp = parser->isSet("cache") && + (parser->value("cache") == "help" || + parser->value("cache") == "report:missing=help"); QStringList flags = parseFlags(); if (!cacheHelp && !flags.contains("help")) { if (parser->isSet("p")) { @@ -585,9 +586,13 @@ void RuntimeCfg::applyCli(bool &inputFolderSet, bool &gameListFolderSet, config->cacheOptions = parser->value("cache"); if (config->cacheOptions == "refresh") { config->refresh = true; + config->cacheOptions = ""; } else if (config->cacheOptions == "help") { Cli::subCommandUsage("cache"); exit(0); + } else if (config->cacheOptions == "report:missing=help") { + Cli::cacheReportMissingUsage(); + exit(0); } } if (parser->isSet("startat")) { diff --git a/src/skyscraper.cpp b/src/skyscraper.cpp index 749be725..0063d3f2 100644 --- a/src/skyscraper.cpp +++ b/src/skyscraper.cpp @@ -73,12 +73,13 @@ Skyscraper::Skyscraper(const QCommandLineParser &parser, Skyscraper::~Skyscraper() { frontend->deleteLater(); } void Skyscraper::run() { - bool useCacheScraper = config.scraper == "cache"; + cacheScrapeMode = config.scraper == "cache"; + doCacheScraping = cacheScrapeMode && !config.pretend; printf("Platform: '\033[1;32m%s\033[0m'\n", config.platform.toStdString().c_str()); printf("Scraping module: '\033[1;32m%s\033[0m'\n", config.scraper.toStdString().c_str()); - if (useCacheScraper) { + if (cacheScrapeMode) { printf("Frontend: '\033[1;32m%s\033[0m'\n", config.frontend.toStdString().c_str()); if (!config.frontendExtra.isEmpty()) { @@ -145,8 +146,8 @@ void Skyscraper::run() { if (!config.cacheFolder.isEmpty()) { cache = QSharedPointer(new Cache(config.cacheFolder)); - if (cache->createFolders(config.scraper)) { - if (!cache->read() && useCacheScraper) { + if (cacheScrapeMode || cache->createFolders(config.scraper)) { + if (!cache->read() && cacheScrapeMode) { printf("No resources for this platform found in the resource " "cache. Please specify a scraping module with '-s' to " "gather some resources before trying to generate a game " @@ -159,10 +160,12 @@ void Skyscraper::run() { exit(1); } } + if (config.verbosity || config.cacheOptions == "show") { cache->showStats(config.cacheOptions == "show" ? 2 : config.verbosity); - if (config.cacheOptions == "show") + if (config.cacheOptions == "show") { exit(0); + } } if (config.cacheOptions.contains("purge:") || config.cacheOptions.contains("vacuum")) { @@ -211,161 +214,23 @@ void Skyscraper::run() { } exit(0); } - cache->readPriorities(); - QDir::Filters filter = QDir::Files; - // special case scummvm: user do use .svm in folder name to work around the - // limitation of the ScummVM / lr-scummvm launch integration in ES/RetroPie - if (config.platform == "scummvm") { - filter |= QDir::Dirs; - } - QDir inputDir(config.inputFolder, platformFileExtensions(), QDir::Name, - filter); - if (!inputDir.exists()) { - printf("Input folder '\033[1;32m%s\033[0m' doesn't exist or can't be " - "accessed by current user. Please check path and permissions.\n", - inputDir.absolutePath().toStdString().c_str()); - exit(1); - } - config.inputFolder = inputDir.absolutePath(); - - const bool isCacheScraping = useCacheScraper && !config.pretend; - - setFolder(isCacheScraping, config.gameListFolder); - setFolder(isCacheScraping, config.coversFolder); - setFolder(isCacheScraping, config.screenshotsFolder); - setFolder(isCacheScraping, config.wheelsFolder); - setFolder(isCacheScraping, config.marqueesFolder); - setFolder(isCacheScraping, config.texturesFolder); - if (config.videos) { - setFolder(isCacheScraping, config.videosFolder); - } - if (config.manuals) { - setFolder(isCacheScraping, config.manualsFolder); + // remaining cache subcommand validation + bool cacheEditCmd = config.cacheOptions.left(4) == "edit"; + if (!config.cacheOptions.isEmpty() && !cacheEditCmd) { + printf("\033[1;31mAmbiguous cache subcommand '--cache %s', " + "please check '--cache help' for more info.\n\033[0m", + config.cacheOptions.toStdString().c_str()); + exit(0); } - setFolder(isCacheScraping, config.importFolder, false); - - gameListFileString = - config.gameListFolder + "/" + frontend->getGameListFileName(); - - QFile gameListFile(gameListFileString); + cache->readPriorities(); // Create shared queue with files to process - queue = QSharedPointer(new Queue()); - QList infoList = inputDir.entryInfoList(); - if (!useCacheScraper && - QFileInfo::exists(config.inputFolder + "/.skyscraperignore")) { - infoList.clear(); - } - if (!config.startAt.isEmpty() && !infoList.isEmpty()) { - QFileInfo startAt(config.startAt); - if (!startAt.exists()) { - startAt.setFile(config.currentDir + "/" + config.startAt); - } - if (!startAt.exists()) { - startAt.setFile(inputDir.absolutePath() + "/" + config.startAt); - } - if (startAt.exists()) { - while (infoList.first().fileName() != startAt.fileName() && - !infoList.isEmpty()) { - infoList.removeFirst(); - } - } - } - if (!config.endAt.isEmpty() && !infoList.isEmpty()) { - QFileInfo endAt(config.endAt); - if (!endAt.exists()) { - endAt.setFile(config.currentDir + "/" + config.endAt); - } - if (!endAt.exists()) { - endAt.setFile(inputDir.absolutePath() + "/" + config.endAt); - } - if (endAt.exists()) { - while (infoList.last().fileName() != endAt.fileName() && - !infoList.isEmpty()) { - infoList.removeLast(); - } - } - } - queue->append(infoList); - if (config.subdirs) { - QDirIterator dirIt(config.inputFolder, - QDir::Dirs | QDir::NoDotAndDotDot, - QDirIterator::Subdirectories); - QString exclude = ""; - while (dirIt.hasNext()) { - QString subdir = dirIt.next(); - if (!useCacheScraper && - QFileInfo::exists(subdir + "/.skyscraperignoretree")) { - exclude = subdir; - } - if (!exclude.isEmpty() && - (subdir == exclude || - (subdir.left(exclude.length()) == exclude && - subdir.mid(exclude.length(), 1) == "/"))) { - continue; - } else { - exclude.clear(); - } - if (!useCacheScraper && - QFileInfo::exists(subdir + "/.skyscraperignore")) { - continue; - } - inputDir.setPath(subdir); - queue->append(inputDir.entryInfoList()); - if (config.verbosity > 0) { - printf("Adding files from subdir: '%s'\n", - subdir.toStdString().c_str()); - } - } - if (config.verbosity > 0) - printf("\n"); - } - if (!config.excludePattern.isEmpty()) { - queue->filterFiles(config.excludePattern); - } - if (!config.includePattern.isEmpty()) { - queue->filterFiles(config.includePattern, true); - } - - if (!cliFiles.isEmpty()) { - queue->clear(); - for (const auto &cliFile : cliFiles) { - queue->append(QFileInfo(cliFile)); - } - } - - // Remove files from excludeFrom, if any - if (!config.excludeFrom.isEmpty()) { - QFileInfo excludeFromInfo(config.excludeFrom); - if (!excludeFromInfo.exists()) { - excludeFromInfo.setFile(config.currentDir + "/" + - config.excludeFrom); - } - if (excludeFromInfo.exists()) { - QFile excludeFrom(excludeFromInfo.absoluteFilePath()); - if (excludeFrom.open(QIODevice::ReadOnly)) { - QList excludes; - while (!excludeFrom.atEnd()) { - excludes.append( - QString(excludeFrom.readLine().simplified())); - } - excludeFrom.close(); - if (!excludes.isEmpty()) { - queue->removeFiles(excludes); - } - } - } else { - printf("File: '\033[1;32m%s\033[0m' does not exist.\n\nPlease " - "verify the filename and try again...\n", - excludeFromInfo.absoluteFilePath().toStdString().c_str()); - exit(1); - } - } + prepareFileQueue(); state = CACHE_EDIT; // Clear queue on ctrl+c - if (config.cacheOptions.left(4) == "edit") { + if (cacheEditCmd) { QString editCommand = ""; QString editType = ""; if (config.cacheOptions.contains(":") && @@ -386,7 +251,12 @@ void Skyscraper::run() { } state = SINGLE; - if (isCacheScraping && config.gameListBackup) { + gameListFileString = + config.gameListFolder + "/" + frontend->getGameListFileName(); + + QFile gameListFile(gameListFileString); + + if (doCacheScraping && config.gameListBackup) { QString gameListBackup = gameListFile.fileName() + "-" + QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss"); @@ -395,15 +265,14 @@ void Skyscraper::run() { gameListFile.copy(gameListBackup); } - if (isCacheScraping && !config.unattend && !config.unattendSkip && + if (doCacheScraping && !config.unattend && !config.unattendSkip && gameListFile.exists()) { std::string userInput = ""; printf("\033[1;34m'\033[0m\033[1;33m%s\033[0m\033[1;34m' already " "exists, do you want to overwrite it\033[0m (y/N)? ", frontend->getGameListFileName().toStdString().c_str()); getline(std::cin, userInput); - if (userInput == "y" || userInput == "Y") { - } else { + if (userInput != "y" && userInput != "Y") { printf("User chose not to overwrite, now exiting...\n"); exit(0); } @@ -420,7 +289,7 @@ void Skyscraper::run() { } printf("\n"); } - if (config.pretend && useCacheScraper) { + if (config.pretend && cacheScrapeMode) { printf("Pretend set! Not changing any files, just showing output.\n\n"); } @@ -521,10 +390,157 @@ void Skyscraper::run() { } } -void Skyscraper::setFolder(const bool isCacheScraping, QString &outFolder, +void Skyscraper::prepareFileQueue() { + QDir::Filters filter = QDir::Files; + // special case scummvm: user do use .svm in folder name to work around the + // limitation of the ScummVM / lr-scummvm launch integration in ES/RetroPie + if (config.platform == "scummvm") { + filter |= QDir::Dirs; + } + QDir inputDir(config.inputFolder, platformFileExtensions(), QDir::Name, + filter); + if (!inputDir.exists()) { + printf("Input folder '\033[1;32m%s\033[0m' doesn't exist or can't be " + "accessed by current user. Please check path and permissions.\n", + inputDir.absolutePath().toStdString().c_str()); + exit(1); + } + config.inputFolder = inputDir.absolutePath(); + + setFolder(doCacheScraping, config.gameListFolder); + setFolder(doCacheScraping, config.coversFolder); + setFolder(doCacheScraping, config.screenshotsFolder); + setFolder(doCacheScraping, config.wheelsFolder); + setFolder(doCacheScraping, config.marqueesFolder); + setFolder(doCacheScraping, config.texturesFolder); + if (config.videos) { + setFolder(doCacheScraping, config.videosFolder); + } + if (config.manuals) { + setFolder(doCacheScraping, config.manualsFolder); + } + + setFolder(doCacheScraping, config.importFolder, false); + + QList infoList = inputDir.entryInfoList(); + if (!cacheScrapeMode && + QFileInfo::exists(config.inputFolder + "/.skyscraperignore")) { + infoList.clear(); + } + if (!config.startAt.isEmpty() && !infoList.isEmpty()) { + QFileInfo startAt(config.startAt); + if (!startAt.exists()) { + startAt.setFile(config.currentDir + "/" + config.startAt); + } + if (!startAt.exists()) { + startAt.setFile(inputDir.absolutePath() + "/" + config.startAt); + } + if (startAt.exists()) { + while (infoList.first().fileName() != startAt.fileName() && + !infoList.isEmpty()) { + infoList.removeFirst(); + } + } + } + if (!config.endAt.isEmpty() && !infoList.isEmpty()) { + QFileInfo endAt(config.endAt); + if (!endAt.exists()) { + endAt.setFile(config.currentDir + "/" + config.endAt); + } + if (!endAt.exists()) { + endAt.setFile(inputDir.absolutePath() + "/" + config.endAt); + } + if (endAt.exists()) { + while (infoList.last().fileName() != endAt.fileName() && + !infoList.isEmpty()) { + infoList.removeLast(); + } + } + } + + queue = QSharedPointer(new Queue()); + queue->append(infoList); + + if (config.subdirs) { + QDirIterator dirIt(config.inputFolder, + QDir::Dirs | QDir::NoDotAndDotDot, + QDirIterator::Subdirectories); + QString exclude = ""; + while (dirIt.hasNext()) { + QString subdir = dirIt.next(); + if (!cacheScrapeMode && + QFileInfo::exists(subdir + "/.skyscraperignoretree")) { + exclude = subdir; + } + if (!exclude.isEmpty() && + (subdir == exclude || + (subdir.left(exclude.length()) == exclude && + subdir.mid(exclude.length(), 1) == "/"))) { + continue; + } else { + exclude.clear(); + } + if (!cacheScrapeMode && + QFileInfo::exists(subdir + "/.skyscraperignore")) { + continue; + } + inputDir.setPath(subdir); + queue->append(inputDir.entryInfoList()); + if (config.verbosity > 0) { + printf("Adding files from subdir: '%s'\n", + subdir.toStdString().c_str()); + } + } + if (config.verbosity > 0) + printf("\n"); + } + if (!config.excludePattern.isEmpty()) { + queue->filterFiles(config.excludePattern); + } + if (!config.includePattern.isEmpty()) { + queue->filterFiles(config.includePattern, true); + } + + if (!cliFiles.isEmpty()) { + queue->clear(); + for (const auto &cliFile : cliFiles) { + queue->append(QFileInfo(cliFile)); + } + } + + // Remove files from excludeFrom, if any + if (!config.excludeFrom.isEmpty()) { + QFileInfo excludeFromInfo(config.excludeFrom); + if (!excludeFromInfo.exists()) { + excludeFromInfo.setFile(config.currentDir + "/" + + config.excludeFrom); + } + if (excludeFromInfo.exists()) { + QFile excludeFrom(excludeFromInfo.absoluteFilePath()); + if (excludeFrom.open(QIODevice::ReadOnly)) { + QList excludes; + while (!excludeFrom.atEnd()) { + excludes.append( + QString(excludeFrom.readLine().simplified())); + } + excludeFrom.close(); + if (!excludes.isEmpty()) { + queue->removeFiles(excludes); + } + } + } else { + printf("File: '\033[1;32m%s\033[0m' does not exist.\n\nPlease " + "verify the filename and try again...\n", + excludeFromInfo.absoluteFilePath().toStdString().c_str()); + exit(1); + } + } +} + +void Skyscraper::setFolder(const bool doCacheScraping, QString &outFolder, const bool createMissingFolder) { QDir dir(outFolder); - if (isCacheScraping) { + if (doCacheScraping) { checkForFolder(dir, createMissingFolder); } outFolder = dir.absolutePath(); diff --git a/src/skyscraper.h b/src/skyscraper.h index fef7e367..604721e5 100644 --- a/src/skyscraper.h +++ b/src/skyscraper.h @@ -68,6 +68,7 @@ private slots: void checkForFolder(QDir &folder, bool create = true); void showHint(); void prepareScraping(); + void prepareFileQueue(); void updateWhdloadDb(NetComm &netComm, QEventLoop &q); void prepareIgdb(NetComm &netComm, QEventLoop &q); void prepareScreenscraper(NetComm &netComm, QEventLoop &q); @@ -82,7 +83,7 @@ private slots: return Platform::get().getFormats(config.platform, config.extensions, config.addExtensions); } - void setFolder(const bool isCacheScraping, QString &outFolder, + void setFolder(const bool doCacheScraping, QString &outFolder, const bool createMissingFolder = true); AbstractFrontend *frontend; @@ -103,6 +104,8 @@ private slots: int avgCompleteness; int currentFile; int totalFiles; + bool cacheScrapeMode; // config.scraper == "cache" + bool doCacheScraping; // cacheScrapeMode && pretend == false }; #endif // SKYSCRAPER_H