From 1a22e3733f9715f4e681c0515caed63937dc64d4 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 12 Nov 2023 11:02:15 -0500 Subject: [PATCH 01/13] Handle Fp::Toolkit addition and FP install path as QDir change --- CMakeLists.txt | 6 +++--- app/src/clifp.cpp | 2 +- app/src/import-worker.cpp | 14 +++++++++----- app/src/mainwindow.cpp | 2 +- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b72e0e1..c1aae6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,18 +64,18 @@ set(FIL_QX_COMPONENTS include(OB/FetchQx) ob_fetch_qx( - REF "v0.5.5.1" + REF "2bbf83e59b0aadc3891440193892be1a2a19c00e" COMPONENTS ${FIL_QX_COMPONENTS} ) # Fetch libfp (build and import from source) include(OB/Fetchlibfp) -ob_fetch_libfp("v0.5.1.1") +ob_fetch_libfp("c903a9eaca5f8cfeb3abc851f075f660d281f46a") # Fetch CLIFp (build and import from source) include(OB/FetchCLIFp) -ob_fetch_clifp("v0.9.9") +ob_fetch_clifp("42a14ec1dc140c5389017db74c4a79fd3831f739") # TODO: The shared build of this is essentially useless as only the CLIFp executable # is deployed, which only works if it's statically linked. There isn't a simple way diff --git a/app/src/clifp.cpp b/app/src/clifp.cpp index 7342441..94bd939 100644 --- a/app/src/clifp.cpp +++ b/app/src/clifp.cpp @@ -13,7 +13,7 @@ //-Class Functions-------------------------------------------------------------------------------------------- //Public: -QString CLIFp::standardCLIFpPath(const Fp::Install& fpInstall) { return fpInstall.fullPath() + '/' + EXE_NAME; } +QString CLIFp::standardCLIFpPath(const Fp::Install& fpInstall) { return fpInstall.dir().absoluteFilePath(EXE_NAME); } bool CLIFp::hasCLIFp(const Fp::Install& fpInstall) { diff --git a/app/src/import-worker.cpp b/app/src/import-worker.cpp index 1eaee8e..0fdc6bb 100644 --- a/app/src/import-worker.cpp +++ b/app/src/import-worker.cpp @@ -231,6 +231,8 @@ bool ImportWorker::performImageJobs(const QList& jobs, bo ImportWorker::ImportResult ImportWorker::processPlatformGames(Qx::Error& errorReport, std::unique_ptr& platformDoc, Fp::Db::QueryBuffer& gameQueryResult) { + const Fp::Toolkit* tk = mFlashpointInstall->toolkit(); + // Add/Update games for(int j = 0; j < gameQueryResult.size; j++) { @@ -272,8 +274,8 @@ ImportWorker::ImportResult ImportWorker::processPlatformGames(Qx::Error& errorRe Fp::Set builtSet = sb.build(); // Get image information - QFileInfo logoLocalInfo(mFlashpointInstall->entryImageLocalPath(Fp::ImageType::Logo, builtGame.id())); - QFileInfo ssLocalInfo(mFlashpointInstall->entryImageLocalPath(Fp::ImageType::Screenshot, builtGame.id())); + QFileInfo logoLocalInfo(tk->entryImageLocalPath(Fp::ImageType::Logo, builtGame.id())); + QFileInfo ssLocalInfo(tk->entryImageLocalPath(Fp::ImageType::Screenshot, builtGame.id())); // Add set to doc QString checkedLogoPath = (logoLocalInfo.exists() || mOptionSet.downloadImages) ? logoLocalInfo.absoluteFilePath() : QString(); @@ -288,7 +290,7 @@ ImportWorker::ImportResult ImportWorker::processPlatformGames(Qx::Error& errorRe { if(!logoLocalInfo.exists()) { - QUrl logoRemotePath = mFlashpointInstall->entryImageRemoteUrl(Fp::ImageType::Logo, builtGame.id()); + QUrl logoRemotePath = tk->entryImageRemotePath(Fp::ImageType::Logo, builtGame.id()); mImageDownloadManager.appendTask(Qx::DownloadTask{logoRemotePath, logoLocalInfo.absoluteFilePath()}); } else @@ -296,7 +298,7 @@ ImportWorker::ImportResult ImportWorker::processPlatformGames(Qx::Error& errorRe if(!ssLocalInfo.exists()) { - QUrl ssRemotePath = mFlashpointInstall->entryImageRemoteUrl(Fp::ImageType::Screenshot, builtGame.id()); + QUrl ssRemotePath = tk->entryImageRemotePath(Fp::ImageType::Screenshot, builtGame.id()); mImageDownloadManager.appendTask(Qx::DownloadTask{ssRemotePath, ssLocalInfo.absoluteFilePath()}); } else @@ -606,6 +608,8 @@ ImportWorker::ImportResult ImportWorker::processIcons(Qx::Error& errorReport, co std::optional platformDestDir = mFrontendInstall->platformIconsDirectory(); std::optional playlistDestDir = mFrontendInstall->playlistIconsDirectory(); + const Fp::Toolkit* tk = mFlashpointInstall->toolkit(); + // Main Job if(!mainDest.isEmpty()) jobs.emplace_back(Fe::Install::ImageMap{.sourcePath = u":/flashpoint/icon.png"_s, .destPath = mainDest}); @@ -616,7 +620,7 @@ ImportWorker::ImportResult ImportWorker::processIcons(Qx::Error& errorReport, co QDir pdd = platformDestDir.value(); for(const QString& p : platforms) { - QString src = mFlashpointInstall->platformLogoPath(p); + QString src = tk->platformLogoPath(p); if(QFile::exists(src)) jobs.emplace_back(Fe::Install::ImageMap{.sourcePath = src, .destPath = pdd.absoluteFilePath(p + ".png")}); diff --git a/app/src/mainwindow.cpp b/app/src/mainwindow.cpp index a1a5939..822fddd 100644 --- a/app/src/mainwindow.cpp +++ b/app/src/mainwindow.cpp @@ -386,7 +386,7 @@ void MainWindow::redoInputChecks() { // Check existing locations again validateInstall(mFrontendInstall->path(), InstallType::Frontend); - validateInstall(mFlashpointInstall->fullPath(), InstallType::Flashpoint); + validateInstall(mFlashpointInstall->dir().absolutePath(), InstallType::Flashpoint); } void MainWindow::invalidateInstall(InstallType install, bool informUser) From 73c6c42d1ea0e85ff7ee9145150b64911429e80b Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 12 Nov 2023 15:19:49 -0500 Subject: [PATCH 02/13] Fix playlist doc typing Playlists were treated as platforms due to typos. Likely broke AM taglists, but had no sideffects for LB platforms as modifiedPlaylists() was being used before playlists were added, and seemingly didn't affect LB playlists either as the null playlist ID would cause the check functions to think the bogus playlists had already been handled due to the platform entries in Parents.xml --- app/src/frontend/fe-data.cpp | 2 +- app/src/frontend/fe-installfoundation.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/frontend/fe-data.cpp b/app/src/frontend/fe-data.cpp index 87b24e4..03abec1 100644 --- a/app/src/frontend/fe-data.cpp +++ b/app/src/frontend/fe-data.cpp @@ -331,7 +331,7 @@ PlaylistDoc::PlaylistDoc(Install* const parent, const QString& docPath, QString //-Instance Functions-------------------------------------------------------------------------------------------------- //Private: -DataDoc::Type PlaylistDoc::type() const { return Type::Platform; } +DataDoc::Type PlaylistDoc::type() const { return Type::Playlist; } //=============================================================================================================== // PlaylistDoc::Reader diff --git a/app/src/frontend/fe-installfoundation.cpp b/app/src/frontend/fe-installfoundation.cpp index 6f6e56f..20f7b62 100644 --- a/app/src/frontend/fe-installfoundation.cpp +++ b/app/src/frontend/fe-installfoundation.cpp @@ -211,7 +211,7 @@ Fe::DocHandlingError InstallFoundation::commitDataDocument(DataDoc* docToSave, s QList InstallFoundation::modifiedPlatforms() const { return modifiedDataDocs(DataDoc::Type::Platform); } -QList InstallFoundation::modifiedPlaylists() const { return modifiedDataDocs(DataDoc::Type::Platform);} +QList InstallFoundation::modifiedPlaylists() const { return modifiedDataDocs(DataDoc::Type::Playlist);} //Public: bool InstallFoundation::isValid() const { return mValid; } From 91b60685cf3a5f49b09d52a50973b788c0783035 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 12 Nov 2023 16:28:43 -0500 Subject: [PATCH 03/13] Set reasonable max for LB free index tracker Previous max of -1 was a leftover from the previous implementation of Qx::FreeIndexTracker who's index type was templated, so -1 represented a reasonable max (likely 32-bit integer limit); however, now that the tracker uses quint64 for indexing, -1 was interpreted as the maximum value for 64-bit integers and caused allocaiton of its internal bit- array to fail due to absurd size requirements. This was undiscovered due to the previous playlist bugs. --- app/src/frontend/launchbox/lb-install.cpp | 2 +- app/src/frontend/launchbox/lb-install.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/frontend/launchbox/lb-install.cpp b/app/src/frontend/launchbox/lb-install.cpp index cee1d41..17b8d83 100644 --- a/app/src/frontend/launchbox/lb-install.cpp +++ b/app/src/frontend/launchbox/lb-install.cpp @@ -307,7 +307,7 @@ void Install::softReset() { Fe::Install::softReset(); - mLbDatabaseIdTracker = Qx::FreeIndexTracker(0, -1); + mLbDatabaseIdTracker = Qx::FreeIndexTracker(0, LB_DB_ID_TRACKER_MAX); mPlaylistGameDetailsCache.clear(); mWorkerImageJobs.clear(); } diff --git a/app/src/frontend/launchbox/lb-install.h b/app/src/frontend/launchbox/lb-install.h index 9e9aaad..f985fef 100644 --- a/app/src/frontend/launchbox/lb-install.h +++ b/app/src/frontend/launchbox/lb-install.h @@ -44,6 +44,7 @@ class Install : public Fe::Install // Other static inline const QString PLATFORM_CATEGORY = u"Flashpoint"_s; + static const quint64 LB_DB_ID_TRACKER_MAX = 100000; // Support static inline const QList IMAGE_MODE_ORDER { @@ -72,7 +73,7 @@ class Install : public Fe::Install std::unique_ptr mParents; // Other trackers - Qx::FreeIndexTracker mLbDatabaseIdTracker = Qx::FreeIndexTracker(0, -1, {}); + Qx::FreeIndexTracker mLbDatabaseIdTracker = Qx::FreeIndexTracker(0, LB_DB_ID_TRACKER_MAX, {}); QHash mPlaylistGameDetailsCache; QHash mModifiedPlaylistIds; // TODO: Even though the playlist game IDs don't seem to matter, at some point for for completeness scan all playlists when hooking an install to get the From e6ad5f66898ef16f32a5cc1d26b7b71dac2b6e93 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 12 Nov 2023 16:30:10 -0500 Subject: [PATCH 04/13] Fix LB DB index reservation for existing playlist games Accidentally was calling release() instead of reserve(). --- app/src/frontend/launchbox/lb-data.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/frontend/launchbox/lb-data.cpp b/app/src/frontend/launchbox/lb-data.cpp index ace5a11..426c052 100644 --- a/app/src/frontend/launchbox/lb-data.cpp +++ b/app/src/frontend/launchbox/lb-data.cpp @@ -720,7 +720,7 @@ void PlaylistDoc::Reader::parsePlaylistGame() existingPlaylistGame->setLBDatabaseId(optIdx.value_or(0)); } else - static_cast(mTargetDocument)->mLaunchBoxDatabaseIdTracker->release(existingPlaylistGame->lbDatabaseId()); + static_cast(mTargetDocument)->mLaunchBoxDatabaseIdTracker->reserve(existingPlaylistGame->lbDatabaseId()); // Add to document targetDocExistingPlaylistGames()[existingPlaylistGame->gameId()] = existingPlaylistGame; From c81ba21cb6c47b5973d44714b3d1f220b527a0b5 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 12 Nov 2023 19:40:16 -0500 Subject: [PATCH 05/13] LB: Use modified playlist ID cache directly No need to store as a QHash with the playlist name as the key since the name is not needed. This also avoids the awkwardness that is the conversion between the original playlist name and the translated name, specific to LB. Before, playlists with names that differed between their original and translated names were not being added to the Flashpoint platform category as the keys in the hash were the pre-translated names, while the list checked against at the end were the translated names. --- app/src/frontend/launchbox/lb-install.cpp | 13 ++++++------- app/src/frontend/launchbox/lb-install.h | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/src/frontend/launchbox/lb-install.cpp b/app/src/frontend/launchbox/lb-install.cpp index 17b8d83..ed1c8ac 100644 --- a/app/src/frontend/launchbox/lb-install.cpp +++ b/app/src/frontend/launchbox/lb-install.cpp @@ -213,8 +213,9 @@ std::shared_ptr Install::preparePlaylistDocCommit(const // Work with native type auto lbPlaylistDoc = static_cast(playlistDoc.get()); - // Store playlist ID - mModifiedPlaylistIds[lbPlaylistDoc->playlistHeader()->name()] = lbPlaylistDoc->playlistHeader()->id(); + // Store playlist ID (if playlist will remain + if(!playlistDoc->isEmpty()) + mModifiedPlaylistIds.insert(lbPlaylistDoc->playlistHeader()->id()); // Construct doc writer std::shared_ptr docWriter = std::make_shared(lbPlaylistDoc); @@ -404,15 +405,13 @@ Qx::Error Install::postImageProcessing() Qx::Error Install::postPlaylistsImport() { // Add playlists to Parents.xml - const QList affectedPlaylists = modifiedPlaylists(); - for(const QString& pn :affectedPlaylists) + for(const QUuid& pId : qAsConst(mModifiedPlaylistIds)) { - QUuid playlistId = mModifiedPlaylistIds.value(pn); - if(!mParents->containsPlaylistUnderCategory(playlistId, PLATFORM_CATEGORY)) + if(!mParents->containsPlaylistUnderCategory(pId, PLATFORM_CATEGORY)) { Lb::Parent::Builder pb; pb.wParentPlatformCategoryName(PLATFORM_CATEGORY); - pb.wPlaylistId(playlistId); + pb.wPlaylistId(pId); mParents->addParent(pb.build()); } } diff --git a/app/src/frontend/launchbox/lb-install.h b/app/src/frontend/launchbox/lb-install.h index f985fef..86e64c1 100644 --- a/app/src/frontend/launchbox/lb-install.h +++ b/app/src/frontend/launchbox/lb-install.h @@ -75,7 +75,7 @@ class Install : public Fe::Install // Other trackers Qx::FreeIndexTracker mLbDatabaseIdTracker = Qx::FreeIndexTracker(0, LB_DB_ID_TRACKER_MAX, {}); QHash mPlaylistGameDetailsCache; - QHash mModifiedPlaylistIds; + QSet mModifiedPlaylistIds; // TODO: Even though the playlist game IDs don't seem to matter, at some point for for completeness scan all playlists when hooking an install to get the // full list of in use IDs From c0e4d25ed4ff2b366766d8db901acae88b41644b Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 12 Nov 2023 20:00:47 -0500 Subject: [PATCH 06/13] Distinguish between modified docs and deleted docs Frontends often use modifiedPlatforms()/modifiedPlaylists() to check which Platforms/Playlists were affected by the important and need to be taken into account by other parts of the frontend; however, this obviously only applies if the doc was stricly modified and not outright removed, which can happen if the commited modified doc is empty. This change ensures that only modified docs that remain will be part of the modified list. --- app/src/frontend/fe-installfoundation.cpp | 40 ++++++++++++++++------- app/src/frontend/fe-installfoundation.h | 1 + 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/app/src/frontend/fe-installfoundation.cpp b/app/src/frontend/fe-installfoundation.cpp index 20f7b62..b8abb66 100644 --- a/app/src/frontend/fe-installfoundation.cpp +++ b/app/src/frontend/fe-installfoundation.cpp @@ -126,6 +126,7 @@ void InstallFoundation::softReset() { mRevertableFilePaths.clear(); mModifiedDocuments.clear(); + mDeletedDocuments.clear(); mLeasedDocuments.clear(); mImportDetails.reset(); } @@ -170,13 +171,16 @@ Fe::DocHandlingError InstallFoundation::checkoutDataDocument(DataDoc* docToOpen, Fe::DocHandlingError InstallFoundation::commitDataDocument(DataDoc* docToSave, std::shared_ptr docWriter) { - // Create backup and add to modified list if required - if(!mModifiedDocuments.contains(docToSave->identifier())) + DataDoc::Identifier id = docToSave->identifier(); + bool wasDeleted = mDeletedDocuments.contains(id); + bool wasModified = mDeletedDocuments.contains(id); + bool wasUntouched = !wasDeleted && !wasModified; + + // Handle backup/revert prep + if(wasUntouched) { - // Insert QString docPath = docToSave->path(); - mModifiedDocuments.insert(docToSave->identifier()); - mRevertableFilePaths.append(docPath); + mRevertableFilePaths.append(docPath); // Correctly handles if doc ends up deleted // Backup if(QFile::exists(docPath)) @@ -190,23 +194,35 @@ Fe::DocHandlingError InstallFoundation::commitDataDocument(DataDoc* docToSave, s } if(!QFile::copy(docPath, backupPath)) - return Fe::DocHandlingError(*docToSave, Fe::DocHandlingError::CantRemoveBackup); + return Fe::DocHandlingError(*docToSave, Fe::DocHandlingError::CantCreateBackup); } } - // Write to file if it contains content - Fe::DocHandlingError saveWriteError; + // Error State + Fe::DocHandlingError commitError; + + // Handle modification if(!docToSave->isEmpty()) - saveWriteError = docWriter->writeOutOf(); + { + mModifiedDocuments.insert(id); + if(wasDeleted) + mDeletedDocuments.remove(id); - // Set document permissions - allowUserWriteOnFile(docToSave->path()); + commitError = docWriter->writeOutOf(); + allowUserWriteOnFile(docToSave->path()); + } + else // Handle deletion + { + mDeletedDocuments.insert(id); + if(wasModified) + mModifiedDocuments.remove(id); + } // Remove handle reservation mLeasedDocuments.remove(docToSave->identifier()); // Return write status and let document ptr auto delete - return saveWriteError; + return commitError; } QList InstallFoundation::modifiedPlatforms() const { return modifiedDataDocs(DataDoc::Type::Platform); } diff --git a/app/src/frontend/fe-installfoundation.h b/app/src/frontend/fe-installfoundation.h index 2d232b1..5c71e97 100644 --- a/app/src/frontend/fe-installfoundation.h +++ b/app/src/frontend/fe-installfoundation.h @@ -108,6 +108,7 @@ class InstallFoundation // Document tracking QSet mExistingDocuments; QSet mModifiedDocuments; + QSet mDeletedDocuments; QSet mLeasedDocuments; // Backup/Deletion tracking From 518315a9bcbabe0b6a2fb7e70fc577b3817f484e Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 12 Nov 2023 20:20:48 -0500 Subject: [PATCH 07/13] LB: Fix playlist game title being used as platform --- app/src/frontend/launchbox/lb-items.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/frontend/launchbox/lb-items.cpp b/app/src/frontend/launchbox/lb-items.cpp index 7f55ea2..a661b16 100644 --- a/app/src/frontend/launchbox/lb-items.cpp +++ b/app/src/frontend/launchbox/lb-items.cpp @@ -222,7 +222,7 @@ PlaylistGame::EntryDetails::EntryDetails(const Game& refGame) : //Public: QString PlaylistGame::EntryDetails::title() const { return mTitle; } QString PlaylistGame::EntryDetails::filename() const { return mFilename; } -QString PlaylistGame::EntryDetails::platform() const { return mTitle; } +QString PlaylistGame::EntryDetails::platform() const { return mPlatform; } //=============================================================================================================== // PlaylistGame From e2456d441e4b7c38a5276fa527147577d53b6a85 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Mon, 13 Nov 2023 07:00:06 -0500 Subject: [PATCH 08/13] Make translateDocName() public Needed for some ImportWorker tasks --- app/src/frontend/attractmode/am-install.cpp | 28 ++++++++++----------- app/src/frontend/attractmode/am-install.h | 2 +- app/src/frontend/fe-install.cpp | 11 ++++---- app/src/frontend/fe-install.h | 2 +- app/src/frontend/launchbox/lb-install.cpp | 28 ++++++++++----------- app/src/frontend/launchbox/lb-install.h | 2 +- 6 files changed, 37 insertions(+), 36 deletions(-) diff --git a/app/src/frontend/attractmode/am-install.cpp b/app/src/frontend/attractmode/am-install.cpp index a80bf16..d8f8c1f 100644 --- a/app/src/frontend/attractmode/am-install.cpp +++ b/app/src/frontend/attractmode/am-install.cpp @@ -118,20 +118,6 @@ Qx::Error Install::populateExistingDocs() return Qx::Error(); } -QString Install::translateDocName(const QString& originalName, Fe::DataDoc::Type type) const -{ - // Perform general kosherization - QString translatedName = Qx::kosherizeFileName(originalName); - - // Prefix platforms/playlists - if(type == Fe::DataDoc::Type::Platform) - translatedName.prepend(PLATFORM_TAG_PREFIX); - else if(type == Fe::DataDoc::Type::Playlist) - translatedName.prepend(PLAYLIST_TAG_PREFIX); - - return translatedName; -} - QString Install::executableSubPath() const { return MAIN_EXE_PATH; } QString Install::imageDestinationPath(Fp::ImageType imageType, const Fe::Game* game) const @@ -350,6 +336,20 @@ QString Install::versionString() const return u"UNKNOWN VERSION"_s; } +QString Install::translateDocName(const QString& originalName, Fe::DataDoc::Type type) const +{ + // Perform general kosherization + QString translatedName = Qx::kosherizeFileName(originalName); + + // Prefix platforms/playlists + if(type == Fe::DataDoc::Type::Platform) + translatedName.prepend(PLATFORM_TAG_PREFIX); + else if(type == Fe::DataDoc::Type::Playlist) + translatedName.prepend(PLAYLIST_TAG_PREFIX); + + return translatedName; +} + Qx::Error Install::preImport(const ImportDetails& details) { //-Ensure that required directories exist---------------------------------------------------------------- diff --git a/app/src/frontend/attractmode/am-install.h b/app/src/frontend/attractmode/am-install.h index efdd363..ec341ae 100644 --- a/app/src/frontend/attractmode/am-install.h +++ b/app/src/frontend/attractmode/am-install.h @@ -90,7 +90,6 @@ class Install : public Fe::Install // Install management void nullify() override; Qx::Error populateExistingDocs() override; - QString translateDocName(const QString& originalName, Fe::DataDoc::Type type) const override; // Info QString executableSubPath() const override; @@ -119,6 +118,7 @@ class Install : public Fe::Install QString name() const override; QList preferredImageModeOrder() const override; QString versionString() const override; + QString translateDocName(const QString& originalName, Fe::DataDoc::Type type) const override; // Import stage notifier hooks Qx::Error preImport(const ImportDetails& details) override; diff --git a/app/src/frontend/fe-install.cpp b/app/src/frontend/fe-install.cpp index d1f3622..92e4f4f 100644 --- a/app/src/frontend/fe-install.cpp +++ b/app/src/frontend/fe-install.cpp @@ -51,11 +51,6 @@ void Install::nullify() InstallFoundation::nullify(); } -QString Install::translateDocName(const QString& originalName, DataDoc::Type type) const -{ - // Redundant with base version, but here to make it clear its part of the main Install interface - return InstallFoundation::translateDocName(originalName, type); -} //Public: void Install::softReset() @@ -106,6 +101,12 @@ Qx::Error Install::postImageProcessing() { return {}; } Qx::Error Install::prePlaylistsImport() { return {}; } Qx::Error Install::postPlaylistsImport() { return {}; } +QString Install::translateDocName(const QString& originalName, DataDoc::Type type) const +{ + // Redundant with base version, but here to make it clear its part of the main Install interface + return InstallFoundation::translateDocName(originalName, type); +} + Fe::DocHandlingError Install::checkoutPlatformDoc(std::unique_ptr& returnBuffer, const QString& name) { // Translate to frontend doc name diff --git a/app/src/frontend/fe-install.h b/app/src/frontend/fe-install.h index 4889289..3600f8d 100644 --- a/app/src/frontend/fe-install.h +++ b/app/src/frontend/fe-install.h @@ -62,7 +62,6 @@ class Install : public InstallFoundation // Install management virtual void nullify() override; virtual Qx::Error populateExistingDocs() override = 0; - virtual QString translateDocName(const QString& originalName, DataDoc::Type type) const override; // Doc Handling virtual std::shared_ptr preparePlatformDocCheckout(std::unique_ptr& platformDoc, const QString& translatedName) = 0; @@ -94,6 +93,7 @@ class Install : public InstallFoundation virtual Qx::Error postPlaylistsImport(); // Doc handling + virtual QString translateDocName(const QString& originalName, DataDoc::Type type) const override; Fe::DocHandlingError checkoutPlatformDoc(std::unique_ptr& returnBuffer, const QString& name); Fe::DocHandlingError checkoutPlaylistDoc(std::unique_ptr& returnBuffer, const QString& name); Fe::DocHandlingError commitPlatformDoc(std::unique_ptr platformDoc); diff --git a/app/src/frontend/launchbox/lb-install.cpp b/app/src/frontend/launchbox/lb-install.cpp index ed1c8ac..5fe525d 100644 --- a/app/src/frontend/launchbox/lb-install.cpp +++ b/app/src/frontend/launchbox/lb-install.cpp @@ -96,20 +96,6 @@ Qx::Error Install::populateExistingDocs() return Qx::Error(); } -QString Install::translateDocName(const QString& originalName, Fe::DataDoc::Type type) const -{ - Q_UNUSED(type); - - // Perform general kosherization - QString translatedName = Qx::kosherizeFileName(originalName); - - // LB specific changes - translatedName.replace('#','_'); - translatedName.replace('\'','_'); - - return translatedName; -} - QString Install::executableSubPath() const { return MAIN_EXE_PATH; } QString Install::imageDestinationPath(Fp::ImageType imageType, const Fe::Game* game) const @@ -316,6 +302,20 @@ void Install::softReset() QString Install::name() const { return NAME; } QList Install::preferredImageModeOrder() const { return IMAGE_MODE_ORDER; } +QString Install::translateDocName(const QString& originalName, Fe::DataDoc::Type type) const +{ + Q_UNUSED(type); + + // Perform general kosherization + QString translatedName = Qx::kosherizeFileName(originalName); + + // LB specific changes + translatedName.replace('#','_'); + translatedName.replace('\'','_'); + + return translatedName; +} + Qx::Error Install::prePlatformsImport() { if(Qx::Error superErr = Fe::Install::prePlatformsImport(); superErr.isValid()) diff --git a/app/src/frontend/launchbox/lb-install.h b/app/src/frontend/launchbox/lb-install.h index 86e64c1..65d72e6 100644 --- a/app/src/frontend/launchbox/lb-install.h +++ b/app/src/frontend/launchbox/lb-install.h @@ -88,7 +88,6 @@ class Install : public Fe::Install // Install management void nullify() override; Qx::Error populateExistingDocs() override; - QString translateDocName(const QString& originalName, Fe::DataDoc::Type type) const override; // Info QString executableSubPath() const override; @@ -116,6 +115,7 @@ class Install : public Fe::Install // Info QString name() const override; QList preferredImageModeOrder() const override; + QString translateDocName(const QString& originalName, Fe::DataDoc::Type type) const override; // Import stage notifier hooks Qx::Error prePlatformsImport() override; From 56518be75607c1a26c90c71a531910c7469db135 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Mon, 13 Nov 2023 07:01:28 -0500 Subject: [PATCH 09/13] Don't use FP's favorities icon LB, the only platform that supports them currently, already has its own. Will need to revisit this if other platforms that support them are added. --- app/src/import-worker.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/import-worker.cpp b/app/src/import-worker.cpp index 0fdc6bb..6a8b237 100644 --- a/app/src/import-worker.cpp +++ b/app/src/import-worker.cpp @@ -649,7 +649,14 @@ ImportWorker::ImportResult ImportWorker::processIcons(Qx::Error& errorReport, co if(icon.isNull()) continue; - QString filename = p.title() + ".png"; + /* NOTE: This is LaunchBox specific since it's currently the only FE to support icons. If this changes a general solution is needed + * Like allowing the frontend to filter out specific icons + * + * Don't copy the favorites icon as LB already has its own. + */ + if(p.title().trimmed() == u"Favorites"_s) + continue; + QString source = iconInflateDir.filePath(filename); QString dest = pdd.absoluteFilePath(filename); From e26f858e4133b64c7d5dcea2ed86b239d01bcb5b Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Mon, 13 Nov 2023 07:01:46 -0500 Subject: [PATCH 10/13] Use translated name for playlist icon installation --- app/src/import-worker.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/import-worker.cpp b/app/src/import-worker.cpp index 6a8b237..4d64236 100644 --- a/app/src/import-worker.cpp +++ b/app/src/import-worker.cpp @@ -657,8 +657,14 @@ ImportWorker::ImportResult ImportWorker::processIcons(Qx::Error& errorReport, co if(p.title().trimmed() == u"Favorites"_s) continue; - QString source = iconInflateDir.filePath(filename); - QString dest = pdd.absoluteFilePath(filename); + /* NOTE: This may not work for all frontends + * + * Use translated name for destination since that's what the frontend is expecting + */ + QString sFilename = p.title() + ".png"; + QString dFilename = mFrontendInstall->translateDocName(p.title(), Fe::DataDoc::Type::Playlist) + ".png";; + QString source = iconInflateDir.filePath(sFilename); + QString dest = pdd.absoluteFilePath(dFilename); iw.setFileName(source); if(!iw.write(icon)) From 7a2122e1b6df20024547d07e11b04098f3e0e7d8 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Mon, 13 Nov 2023 07:03:37 -0500 Subject: [PATCH 11/13] Update CLIFp --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c1aae6c..1c6bef0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,7 +75,7 @@ ob_fetch_libfp("c903a9eaca5f8cfeb3abc851f075f660d281f46a") # Fetch CLIFp (build and import from source) include(OB/FetchCLIFp) -ob_fetch_clifp("42a14ec1dc140c5389017db74c4a79fd3831f739") +ob_fetch_clifp("afb63841b5b2f30eb106dbb721886be67a4c5570") # TODO: The shared build of this is essentially useless as only the CLIFp executable # is deployed, which only works if it's statically linked. There isn't a simple way From 88e74a3677ee9e8461184a0d0213daf36926c06a Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Mon, 13 Nov 2023 11:12:02 -0500 Subject: [PATCH 12/13] LB: Nest platforms and playlists under their own platform categories --- app/src/frontend/launchbox/lb-data.cpp | 62 ++++++++++++++++------- app/src/frontend/launchbox/lb-data.h | 20 ++++++-- app/src/frontend/launchbox/lb-install.cpp | 48 ++++++++++++++---- app/src/frontend/launchbox/lb-install.h | 8 ++- 4 files changed, 107 insertions(+), 31 deletions(-) diff --git a/app/src/frontend/launchbox/lb-data.cpp b/app/src/frontend/launchbox/lb-data.cpp index 426c052..b64622a 100644 --- a/app/src/frontend/launchbox/lb-data.cpp +++ b/app/src/frontend/launchbox/lb-data.cpp @@ -1090,36 +1090,64 @@ ParentsDoc::ParentsDoc(Install* const parent, const QString& xmlPath, const DocK //Private: Fe::DataDoc::Type ParentsDoc::type() const { return Fe::DataDoc::Type::Config; } -//Public: -bool ParentsDoc::isEmpty() const { return mParents.isEmpty(); } - -bool ParentsDoc::containsPlatformCategory(QStringView platformCategory) +bool ParentsDoc::removeIfPresent(qsizetype idx) { - for(const Parent& p : mParents) - if(p.platformCategoryName() == platformCategory) - return true; + if(idx != -1) + { + mParents.remove(idx); + return true; + } return false; } -bool ParentsDoc::containsPlatformUnderCategory(QStringView platform, QStringView platformCategory) +qsizetype ParentsDoc::findPlatformCategory(QStringView platformCategory, QStringView parentCategory) const { - for(const Parent& p : mParents) - if(p.parentPlatformCategoryName() == platformCategory && p.platformName() == platform) - return true; + for(auto i = 0; i < mParents.size(); i++) + { + auto p = mParents.at(i); + if(p.platformCategoryName() == platformCategory && p.parentPlatformCategoryName() == parentCategory) + return i; + } - return false; + return -1; } -bool ParentsDoc::containsPlaylistUnderCategory(const QUuid& playlistId, QStringView platformCategory) +qsizetype ParentsDoc::findPlatform(QStringView platform, QStringView parentCategory) const { - for(const Parent& p : mParents) - if(p.parentPlatformCategoryName() == platformCategory && p.playlistId() == playlistId) - return true; + for(auto i = 0; i < mParents.size(); i++) + { + auto p = mParents.at(i); + if(p.platformName() == platform && p.parentPlatformCategoryName() == parentCategory) + return i; + } - return false; + return -1; } +qsizetype ParentsDoc::findPlaylist(const QUuid& playlistId, QStringView parentCategory) const +{ + for(auto i = 0; i < mParents.size(); i++) + { + auto p = mParents.at(i); + if(p.playlistId() == playlistId && p.parentPlatformCategoryName() == parentCategory) + return i; + } + + return -1; +} + +//Public: +bool ParentsDoc::isEmpty() const { return mParents.isEmpty(); } + +bool ParentsDoc::containsPlatformCategory(QStringView platformCategory, QStringView parentCategory) const { return findPlatformCategory(platformCategory, parentCategory) != -1; } +bool ParentsDoc::containsPlatform(QStringView platform, QStringView parentCategory) const { return findPlatform(platform, parentCategory) != -1; } +bool ParentsDoc::containsPlaylist(const QUuid& playlistId, QStringView parentCategory) const { return findPlaylist(playlistId, parentCategory) != -1; } + +bool ParentsDoc::removePlatformCategory(QStringView platformCategory, QStringView parentCategory) { return removeIfPresent(findPlatformCategory(platformCategory, parentCategory)); } +bool ParentsDoc::removePlatform(QStringView platform, QStringView parentCategory) { return removeIfPresent(findPlatform(platform, parentCategory)); } +bool ParentsDoc::removePlaylist(const QUuid& playlistId, QStringView parentCategory) { return removeIfPresent(findPlaylist(playlistId, parentCategory)); } + const QList& ParentsDoc::parents() const { return mParents; } void ParentsDoc::addParent(const Parent& parent) { mParents.append(parent); } diff --git a/app/src/frontend/launchbox/lb-data.h b/app/src/frontend/launchbox/lb-data.h index 16951e3..5146c62 100644 --- a/app/src/frontend/launchbox/lb-data.h +++ b/app/src/frontend/launchbox/lb-data.h @@ -279,13 +279,27 @@ class ParentsDoc : public Fe::DataDoc //-Instance Functions-------------------------------------------------------------------------------------------------- private: Type type() const override; + bool removeIfPresent(qsizetype idx); + + qsizetype findPlatformCategory(QStringView platformCategory, QStringView parentCategory) const; + qsizetype findPlatform(QStringView platform, QStringView parentCategory) const; + qsizetype findPlaylist(const QUuid& playlistId, QStringView parentCategory) const; public: bool isEmpty() const override; - bool containsPlatformCategory(QStringView platformCategory); - bool containsPlatformUnderCategory(QStringView platform, QStringView platformCategory); - bool containsPlaylistUnderCategory(const QUuid& playlistId, QStringView platformCategory); + /* NOTE: The methods here than take an optional parent category won't look for any of their type + * if no parent is specified, but rather one that explicitly has no parent. If these is needed for + * some reason, consider adding "any" variants of the functions (i.e. containsAnyPlatformCategory(), etc.) + */ + bool containsPlatformCategory(QStringView platformCategory, QStringView parentCategory = {}) const; + bool containsPlatform(QStringView platform, QStringView parentCategory = {}) const; + bool containsPlaylist(const QUuid& playlistId, QStringView parentCategory = {}) const; + + bool removePlatformCategory(QStringView platformCategory, QStringView parentCategory = {}); + bool removePlatform(QStringView platform, QStringView parentCategory = {}); + bool removePlaylist(const QUuid& playlistId, QStringView parentCategory = {}); + const QList& parents() const; diff --git a/app/src/frontend/launchbox/lb-install.cpp b/app/src/frontend/launchbox/lb-install.cpp index 5fe525d..b2bb2ea 100644 --- a/app/src/frontend/launchbox/lb-install.cpp +++ b/app/src/frontend/launchbox/lb-install.cpp @@ -334,17 +334,39 @@ Qx::Error Install::postPlatformsImport() if(Fe::DocHandlingError dhe = checkoutParentsDoc(mParents); dhe.isValid()) return dhe; - // Add PlatformCategory to Platforms.xml + // Add PlatformCategories to Platforms.xml Lb::PlatformCategory::Builder pcb; - pcb.wName(PLATFORM_CATEGORY); - pcb.wNestedName(PLATFORM_CATEGORY); + pcb.wName(MAIN_PLATFORM_CATEGORY); + pcb.wNestedName(MAIN_PLATFORM_CATEGORY); mPlatformsConfig->addPlatformCategory(pcb.build()); + pcb.wName(PLATFORMS_PLATFORM_CATEGORY); + pcb.wNestedName(PLATFORMS_PLATFORM_CATEGORY_NESTED); + mPlatformsConfig->addPlatformCategory(pcb.build()); + pcb.wName(PLAYLISTS_PLATFORM_CATEGORY); + pcb.wNestedName(PLAYLISTS_PLATFORM_CATEGORY_NESTED); + mPlatformsConfig->addPlatformCategory(pcb.build()); + + // Add categories to Parents.xml + if(!mParents->containsPlatformCategory(MAIN_PLATFORM_CATEGORY)) + { + Lb::Parent::Builder pb; + pb.wPlatformCategoryName(MAIN_PLATFORM_CATEGORY); + mParents->addParent(pb.build()); + } - // Add ParentCategory to Parents.xml - if(!mParents->containsPlatformCategory(PLATFORM_CATEGORY)) + if(!mParents->containsPlatformCategory(PLATFORMS_PLATFORM_CATEGORY, MAIN_PLATFORM_CATEGORY)) { Lb::Parent::Builder pb; - pb.wPlatformCategoryName(PLATFORM_CATEGORY); + pb.wPlatformCategoryName(PLATFORMS_PLATFORM_CATEGORY); + pb.wParentPlatformCategoryName(MAIN_PLATFORM_CATEGORY); + mParents->addParent(pb.build()); + } + + if(!mParents->containsPlatformCategory(PLAYLISTS_PLATFORM_CATEGORY, MAIN_PLATFORM_CATEGORY)) + { + Lb::Parent::Builder pb; + pb.wPlatformCategoryName(PLAYLISTS_PLATFORM_CATEGORY); + pb.wParentPlatformCategoryName(MAIN_PLATFORM_CATEGORY); mParents->addParent(pb.build()); } @@ -356,13 +378,16 @@ Qx::Error Install::postPlatformsImport() pb.wName(pn); mPlatformsConfig->addPlatform(pb.build()); - if(!mParents->containsPlatformUnderCategory(pn, PLATFORM_CATEGORY)) + if(!mParents->containsPlatform(pn, PLATFORMS_PLATFORM_CATEGORY)) { Lb::Parent::Builder pb; - pb.wParentPlatformCategoryName(PLATFORM_CATEGORY); + pb.wParentPlatformCategoryName(PLATFORMS_PLATFORM_CATEGORY); pb.wPlatformName(pn); mParents->addParent(pb.build()); } + + // Remove old categorization directly under top level category if present + mParents->removePlatform(pn, MAIN_PLATFORM_CATEGORY); } return Qx::Error(); @@ -407,13 +432,16 @@ Qx::Error Install::postPlaylistsImport() // Add playlists to Parents.xml for(const QUuid& pId : qAsConst(mModifiedPlaylistIds)) { - if(!mParents->containsPlaylistUnderCategory(pId, PLATFORM_CATEGORY)) + if(!mParents->containsPlaylist(pId, PLAYLISTS_PLATFORM_CATEGORY)) { Lb::Parent::Builder pb; - pb.wParentPlatformCategoryName(PLATFORM_CATEGORY); + pb.wParentPlatformCategoryName(PLAYLISTS_PLATFORM_CATEGORY); pb.wPlaylistId(pId); mParents->addParent(pb.build()); } + + // Remove old categorization directly under top level category if present + mParents->removePlaylist(pId, MAIN_PLATFORM_CATEGORY); } // Close Parents.xml diff --git a/app/src/frontend/launchbox/lb-install.h b/app/src/frontend/launchbox/lb-install.h index 65d72e6..f4cc8ac 100644 --- a/app/src/frontend/launchbox/lb-install.h +++ b/app/src/frontend/launchbox/lb-install.h @@ -42,8 +42,14 @@ class Install : public Fe::Install // Files static inline const QString XML_EXT = u"xml"_s; + // Parents.xml + static inline const QString MAIN_PLATFORM_CATEGORY = u"Flashpoint"_s; + static inline const QString PLATFORMS_PLATFORM_CATEGORY = u"Flashpoint Platforms"_s; + static inline const QString PLATFORMS_PLATFORM_CATEGORY_NESTED = u"Platforms"_s; + static inline const QString PLAYLISTS_PLATFORM_CATEGORY = u"Flashpoint Playlists"_s; + static inline const QString PLAYLISTS_PLATFORM_CATEGORY_NESTED = u"Playlists"_s; + // Other - static inline const QString PLATFORM_CATEGORY = u"Flashpoint"_s; static const quint64 LB_DB_ID_TRACKER_MAX = 100000; // Support From ac077d75be7567deb53431f9db59740fd82f0951 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 26 Nov 2023 19:11:09 -0500 Subject: [PATCH 13/13] Bump --- CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c6bef0..4b6098a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,14 +6,14 @@ cmake_minimum_required(VERSION 3.24.0...3.26.0) # Project # NOTE: DON'T USE TRAILING ZEROS IN VERSIONS project(FIL - VERSION 0.7.3.1 + VERSION 0.7.4 LANGUAGES CXX DESCRIPTION "Flashpoint Importer for Launchers" ) # Get helper scripts include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/FetchOBCMake.cmake) -fetch_ob_cmake("v0.3.3") +fetch_ob_cmake("v0.3.4") # Initialize project according to standard rules include(OB/Project) @@ -64,18 +64,18 @@ set(FIL_QX_COMPONENTS include(OB/FetchQx) ob_fetch_qx( - REF "2bbf83e59b0aadc3891440193892be1a2a19c00e" + REF "v0.5.6" COMPONENTS ${FIL_QX_COMPONENTS} ) # Fetch libfp (build and import from source) include(OB/Fetchlibfp) -ob_fetch_libfp("c903a9eaca5f8cfeb3abc851f075f660d281f46a") +ob_fetch_libfp("v0.5.2") # Fetch CLIFp (build and import from source) include(OB/FetchCLIFp) -ob_fetch_clifp("afb63841b5b2f30eb106dbb721886be67a4c5570") +ob_fetch_clifp("v0.9.10") # TODO: The shared build of this is essentially useless as only the CLIFp executable # is deployed, which only works if it's statically linked. There isn't a simple way