diff --git a/.github/workflows/build-project.yml b/.github/workflows/build-project.yml
index dd88b75..6e02549 100644
--- a/.github/workflows/build-project.yml
+++ b/.github/workflows/build-project.yml
@@ -12,6 +12,7 @@ on:
# the "pull_request" trigger works (it works off master correctly for a merged PR but is triggered
# by dev
+# No GCC because it's not packaged for 20.04 or 22.04 yet
jobs:
trigger-build:
name: Build Project
@@ -23,5 +24,24 @@ jobs:
runs_exclude: >
[
{ "linkage": "shared" },
- { "compiler": "g++-10" }
+ { "compiler": "g++-10" },
+ { "compiler": "g++-12" },
+ { "compiler": "clang++-12" },
+ { "compiler": "clang++-14" }
]
+ runs_include: >
+ [
+ { "os": "ubuntu-20.04", "compiler": "clang++-18", "linkage": "static" },
+ { "os": "ubuntu-22.04", "compiler": "clang++-18", "linkage": "static" }
+ ]
+ pre_build_steps: |
+ - name: Install Clang 18 [Linux]
+ if: env.run_is_linux == 'true' && env.run_compiler == 'clang++-18'
+ shell: bash
+ run: |
+ wget https://apt.llvm.org/llvm.sh
+ chmod u+x llvm.sh
+ sudo ./llvm.sh 18
+
+
+
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 43dfe32..3eadcf8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -6,28 +6,28 @@ cmake_minimum_required(VERSION 3.24.0...3.30.0)
# Project
# NOTE: DON'T USE TRAILING ZEROS IN VERSIONS
project(FIL
- VERSION 0.7.5.5
+ VERSION 0.8
LANGUAGES CXX
DESCRIPTION "Flashpoint Importer for Launchers"
)
# Get helper scripts
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/FetchOBCMake.cmake)
-fetch_ob_cmake("9f45dec8dc1cd09d99862b7b7ea335b361cf7ff7")
+fetch_ob_cmake("v0.3.9")
# Initialize project according to standard rules
include(OB/Project)
ob_standard_project_setup()
# Additional Project Variables
-set(TARGET_FP_VERSION_PREFIX 13.0)
+set(TARGET_FP_VERSION_PREFIX 14.0)
# Configuration options
# Handled by fetched libs, but set this here formally since they aren't part of the main project
option(BUILD_SHARED_LIBS "Build FIL with shared libraries" OFF)
# C++
-set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Build augmentation
@@ -74,20 +74,20 @@ endif()
include(OB/FetchQx)
ob_fetch_qx(
- REF "0572d288936afd63ff6f5b6ce4b1fbfc0f03b0eb"
+ REF "v0.6.1"
COMPONENTS
${FIL_QX_COMPONENTS}
)
# Fetch libfp (build and import from source)
include(OB/Fetchlibfp)
-ob_fetch_libfp("183a479d00235d332aa1046a9b5ba98f62699752")
+ob_fetch_libfp("v0.5.5")
# Fetch CLIFp (build and import from source)
include(OB/Utility)
ob_cache_project_version(CLIFp)
include(OB/FetchCLIFp)
-ob_fetch_clifp("7139ae998b292eb595e751ba4cb8599230435358")
+ob_fetch_clifp("v0.9.13")
# 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
@@ -104,7 +104,7 @@ ob_fetch_clifp("7139ae998b292eb595e751ba4cb8599230435358")
# Fetch Neargye's Magic Enum
include(OB/FetchMagicEnum)
-ob_fetch_magicenum("v0.9.3")
+ob_fetch_magicenum("v0.9.7")
# Process Targets
set(APP_TARGET_NAME ${PROJECT_NAMESPACE_LC}_${PROJECT_NAMESPACE_LC})
diff --git a/README.md b/README.md
index 637a15b..8038edb 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,11 @@
# FIL (Flashpoint Importer for Launchers)
-
+
FIL is an importer tool for several launchers/frontends that allows one to add platforms and playlists from [Flashpoint Archive](https://flashpointarchive.org/) to their collection. It is fully automated and only requires the user to provide the paths to their launcher and Flashpoint installs, choose which Platforms/Playlists they wish to import, and select between a few import mode options. Once the import is started the current progress is displayed and any errors that occur are shown to the user, with resolvable errors including a prompt for what the user would like to do. After the process has completed, the specified launcher can be started and the games from Flashpoint can be played like those from any other Platform.
For Platforms, the importer is capable of importing each game/animation along with any additional apps, images, and most of the metadata fields (i.e. Title, Description, etc, see below).
-Checkout **[Usage (Primary)](#usage-primary)** to get started.
+Checkout **[Usage](#usage)** to get started.
[](https://github.com/oblivioncth/FIL/actions/workflows/build-project.yml)
@@ -45,118 +45,14 @@ Using a version of FIL that does not target the version of Flashpoint you wish t
The title of each [release](github.com/oblivioncth/FIL/releases) will indicate which version of Flashpoint it targets.
## Launcher Specific Details
-*If enough frontends are added this section will likely be converted into a wiki.*
+See [LAUNCHER](doc/LAUNCHER.md)
---------------------------------------------------------------------------------------------------
-**LaunchBox**
+## Usage
+Essentially, you just need to download and run the program, provide paths to both your Flashpoint and Launcher installs, select your options, and go.
-The import strategy for LaunchBox results in a setup that is straightforward and very similar to when Flashpoint Archive used LaunchBox as its frontend. Platforms to platforms, playlists to playlists, games to games, additional apps to additional apps, and so forth.
+For more details, see [USAGE](doc/USAGE.md)
-Each platform is grouped within the platform category "Flashpoint".
-
-All entry metadata is converted to its nearest LaunchBox equivalent, with nearly all fields being covered. One minor exception is the Flashpoint "Language" field, as it is added as a LaunchBox Custom Field, which requires a premium license to see.
-
-Everything should work out-of-the-box after an import.
-
---------------------------------------------------------------------------------------------------
-**AttractMode**
-
-Summary:
- - Everything is considered to be tied to the platform/system "Flashpoint", as well as an emulator by the same name
- - All selections are imported to a master "Flashpoint" romlist
- - A tag list is created for each Platform and Playlist with the prefixes "[Platform]" and "[Playlist]" respectively
- - Game descriptions are added as overviews
- - After each import, if a Display titled "Flashpoint" is not present in your config, one will be created with sensible defaults
- - A Flashpoint system marquee is provided
- - Additional applications are added as romlist entries using the following naming scheme for their title `[parent_game_name] |> [add_app_name]`
- - Title images are added as 'flyers' and screenshots are added as 'snaps'
- - Everything should work out-of-the-box after an import
-
-Details:
-
-The default Display entry will only be created if it's missing, allowing you to customize it as you see fit afterwards; however, the Platform/Playlist specific filters will always be updated to match your selections from the most recent import. Alternatively you can simply make your own Display entry under a different name and leave the default alone (as well as potentially.
-
-The default sort of all Display filters uses the 'AltTitle' field, which is based on Flashpoint's 'sortTitle' field. This guarantees the that all games appear in the same order as they do within Flashpoint and that additional applications appear directly under their parent games.
-
-The romlist fields are mapped as follows (AttractMode `->` Flashpoint):
-
- - Name `->` Title ID
- - Title `->` Title
- - Platform `->` Platform
- - Emulator `->` "Flashpoint"
- - CloneOf `->` Parent Title ID (if an additional app)
- - Year `->` Release date-time (date portion only)
- - Manufacturer `->` Developer
- - Players `->` Play Mode
- - Status `->` Status
- - AltTitle `->` Sort Title (use for correct sorting)
- - Series `->` Series
- - Language `->` Language
-
-Any fields not listed are unused or set to a general default.
-
-All of the default AttractMode layouts don't work particularly well with Flashpoint. The main issues are:
-
- - Not enough space for many titles, especially additional apps
- - Some layouts showing the "AltTitle" of each entry beside them, wasting further space since that field is used for sorting purposes in this use case and isn't really intended to be displayed.
- - Most layouts stretch the images instead of preserving their aspect ratio. This can be changed for some layouts, though since layout settings are global this will affect all of your displays so I did not configure the importer to make this change automatically.
- - No layouts actually display overviews
-
-For this reason it is recommended to use a third-party layout that avoids these issues as best as possible. I cannot recommend one as at this time I do not use AttractMode personally. In the future I may try creating a simple one that is ideal for Flashpoint, thought I cannot promise this. If someone wants to share one they end up creating or recommend an existing one that works well that would be appreciated.
-
-Given that AttractMode is highly customizable and designed to encourage each user to have a unique-to-them setup, ultimately you can do whatever you want with the resultant romlist, tag lists, and overviews. The default Display/Filters are just for getting started.
-
-## Usage (Primary)
-
- **Before using FIL, be sure to have ran Flashpoint through its regular launcher at least once**
-
- 1. Download and run the latest [release](https://github.com/oblivioncth/FIL/releases) (the static variant is recommended)
- 2. Ensure Flashpoint and the launcher are both not running
- 3. Manually specify or browse for the path to your launcher install, the utility will let you know if there are any problems. If everything is OK the icon next to the install path will change to a green check
- 4. Manually specify or browse for the path to your Flashpoint install, the utility will let you know if there are any problems. If everything is OK the icon next to the install path will change to a green check
- 5. The lists of available Platforms and Playlists will quickly load
- 6. Select which Platforms and Playlists you want to import. Existing entries that are considered an update will be highlighted in green
- 7. If importing Playlists, select a Playlist Game Mode. These are described with the nearby Help button in the program, but here is a basic overview of their differences:
- - **Selected Platforms Only** - Only games that are present within the selected platforms will be included
- - **Force All** - All games in the playlist will be included, importing portions of unselected platforms as required
- 8. If any entries you have selected are for updates you may select update mode settings. These are described with the nearby Help button in the program, but here is a basic overview of their differences:
- - (Exclusive) **New Only** - Only adds new games
- - (Exclusive) **New & Existing** - Adds new games and updates the non-user specific metadata for games already in your collection
- - (Applies to either of the above) **Remove Missing** - Removes any games from your collection for the selected Platforms that are no longer in Flashpoint
- 9. Select a method to handle game images. These are described with the nearby Help button in the program, but here is a basic overview of their differences:
- - **Copy** - Copies all relevant images from Flashpoint into your launcher install (slow import)
- - **Reference** - Changes your launcher install configuration to directly use the Flashpoint images in-place (slow image refresh)
- - **Symlink** - Creates a symbolic link to all relevant images from Flashpoint into your launcher install. Overall the best option
-
- 10. Press the "Start Import" button
-
-The symbolic link related options for handling images require the importer to be run as an administrator or for you to enable [Developer mode](https://www.howtogeek.com/292914/what-is-developer-mode-in-windows-10/#:~:text=How%20to%20Enable%20Developer%20Mode,be%20put%20into%20Developer%20Mode.) within Windows 10
-
-**Example:**
-
-
-
-## Usage (Tools)
-
-### Tag Filter
-The tag filter editor allows you to customize which titles will be imported based on their tags.
-
-
-
-Tags are listed alphabetically, nested under their categories names so that you can select or unselect an entire category easily. Exclusions take precedence, so if a title features a single tag that you have unselected it will not be included in the import.
-
-All tags are included by default.
-
-### Image Downloading
-Only available when using Flashpoint Infinity, the "Force Download Images" option will download the cover art and screenshot for each imported title if they have not yet been retrieved through normal use of Infinity.
-
-**WARNING:** The Flashpoint Infinity client was only designed to download images gradually while scrolling through titles within its interface, and so the Flashpoint image server has bandwidth restrictions that severely limit the practicality of downloading a large number of images in bulk. Therefore, it is recommended to only use this feature when using Infinity to access a small subset of the Flashpoint collection, such as a specific playlist, or curated list of favorites. Otherwise, if having all game images available in your launcher is important to you, you should be using Ultimate, or be prepared to wait an **extremely** long time.
-
-### Animations
-Since most launchers are game oriented, animations are ignored by default. If you wish to include them you can do so by selecting the "Include Animations" option.
-
-### CLIFp Distribution
-This tool automatically handles installing/updating the command-line interface Flashpoint client as needed; however, if for whatever reason you deem it necessary/useful to manually insert a copy of FIL's bundled CLIFp version, you can do so using the "Deploy CLIFp" option.
+
## Other Features
- The playlist import feature is "smart" in the sense that it won't include games that you aren't importing. So if you only want to import the Flash platform for example and a couple playlists, you wont have to worry about useless entries in the playlist that point to games from other platforms you didn't import. This of course does not apply if you are using the "Force All" playlist game mode.
@@ -170,9 +66,11 @@ This tool automatically handles installing/updating the command-line interface F
### Summary
- - C++20
+ - C++23
- CMake >= 3.24.0
- - Targets Windows 10 and above
+ - Targets
+ - Windows 10+
+ - Ubuntu 20.04+
### Dependencies
- Qt6
@@ -186,26 +84,4 @@ This tool automatically handles installing/updating the command-line interface F
The source for this project is managed by a sensible CMake configuration that allows for straightforward compilation and consumption of its target(s), either as a sub-project or as an imported package. All required dependencies except for Qt6 are automatically acquired via CMake's FetchContent mechanism.
### Building
-Ensure Qt6 is installed and locatable by CMake (or alternatively use the `qt-cmake` script that comes with Qt in-place of the`cmake` command).
-
-Right now, a static build is required in order for CLIFp to work correctly.
-
-Should work with MSVC, MINGW64, clang, and gcc.
-
-```
-# Acquire source
-git clone https://github.com/oblivioncth/FIL
-
-# Configure (ninja optional, but recommended)
-cmake -S FIL -B build-FIL -G "Ninja Multi-config"
-
-# Build
-cmake --build build-FIL
-
-# Install
-cmake --install build-FIL
-
-# Run
-cd "build-FIL/out/install/bin"
-fil
-```
+See [COMPILING](doc/COMPILING.md)
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index d5e7e9b..c5dfd75 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -8,9 +8,9 @@
set(CLIFP_RES_PATH "${CMAKE_CURRENT_BINARY_DIR}/res/file/clifp")
add_custom_command(OUTPUT
"${CLIFP_RES_PATH}"
- COMMAND ${CMAKE_COMMAND} -E copy $
+ COMMAND ${CMAKE_COMMAND} -E copy $
"${CLIFP_RES_PATH}"
- DEPENDS CLIFp::CLIFp
+ DEPENDS CLIFp::FrontendGui
)
# This statement creates a target that by itself does nothing, but since it depends
@@ -23,48 +23,67 @@ add_custom_target(fil_copy_clifp
# ------------------ Setup FIL --------------------------
set(FIL_SOURCE
- frontend/fe-data.h
- frontend/fe-data.cpp
- frontend/fe-installfoundation.h
- frontend/fe-installfoundation.cpp
- frontend/fe-installfoundation_win.cpp
- frontend/fe-installfoundation_linux.cpp
- frontend/fe-install.h
- frontend/fe-install.cpp
- frontend/fe-items.h
- frontend/fe-items.cpp
- frontend/attractmode/am-data.h
- frontend/attractmode/am-data.cpp
- frontend/attractmode/am-install.h
- frontend/attractmode/am-install.cpp
- frontend/attractmode/am-install_win.cpp
- frontend/attractmode/am-install_linux.cpp
- frontend/attractmode/am-items.h
- frontend/attractmode/am-items.cpp
- frontend/attractmode/am-settings-data.h
- frontend/attractmode/am-settings-data.cpp
- frontend/attractmode/am-settings-items.h
- frontend/attractmode/am-settings-items.cpp
+ import/backup.h
+ import/backup.cpp
+ import/details.h
+ import/details.cpp
+ import/properties.h
+ import/properties.cpp
+ import/settings.h
+ import/settings.cpp
+ import/worker.h
+ import/worker.cpp
+ launcher/abstract/lr-data.h
+ launcher/abstract/lr-data.tpp
+ launcher/abstract/lr-install.h
+ launcher/abstract/lr-install.tpp
+ launcher/abstract/lr-registration.h
+ launcher/abstract/lr-registration.cpp
+ launcher/implementation/attractmode/am-data.h
+ launcher/implementation/attractmode/am-data.tpp
+ launcher/implementation/attractmode/am-data.cpp
+ launcher/implementation/attractmode/am-install.h
+ launcher/implementation/attractmode/am-install.cpp
+ launcher/implementation/attractmode/am-install_win.cpp
+ launcher/implementation/attractmode/am-install_linux.cpp
+ launcher/implementation/attractmode/am-items.h
+ launcher/implementation/attractmode/am-items.cpp
+ launcher/implementation/attractmode/am-settings-data.h
+ launcher/implementation/attractmode/am-settings-data.cpp
+ launcher/implementation/attractmode/am-settings-items.h
+ launcher/implementation/attractmode/am-settings-items.cpp
+ launcher/implementation/attractmode/am-registration.h
+ launcher/implementation/attractmode/am-registration.cpp
+ launcher/interface/lr-data-interface.h
+ launcher/interface/lr-data-interface.cpp
+ launcher/interface/lr-install-interface.h
+ launcher/interface/lr-install-interface.cpp
+ launcher/interface/lr-install-interface_win.cpp
+ launcher/interface/lr-install-interface_linux.cpp
+ launcher/interface/lr-items-interface.h
+ launcher/interface/lr-items-interface.cpp
ui/mainwindow.h
ui/mainwindow.cpp
ui/mainwindow.ui
ui/progresspresenter.h
ui/progresspresenter.cpp
- clifp.h
- clifp.cpp
- import-worker.h
- import-worker.cpp
+ kernel/controller.h
+ kernel/controller.cpp
+ kernel/clifp.h
+ kernel/clifp.cpp
main.cpp
)
if(CMAKE_SYSTEM_NAME STREQUAL Windows)
list(APPEND FIL_SOURCE
- frontend/launchbox/lb-data.h
- frontend/launchbox/lb-data.cpp
- frontend/launchbox/lb-install.h
- frontend/launchbox/lb-install.cpp
- frontend/launchbox/lb-items.h
- frontend/launchbox/lb-items.cpp
+ launcher/implementation/launchbox/lb-data.h
+ launcher/implementation/launchbox/lb-data.cpp
+ launcher/implementation/launchbox/lb-install.h
+ launcher/implementation/launchbox/lb-install.cpp
+ launcher/implementation/launchbox/lb-items.h
+ launcher/implementation/launchbox/lb-items.cpp
+ launcher/implementation/launchbox/lb-registration.h
+ launcher/implementation/launchbox/lb-registration.cpp
)
endif()
@@ -104,6 +123,11 @@ ob_add_standard_executable(${APP_TARGET_NAME}
WIN32
)
+target_sources(fil_fil
+ PRIVATE
+
+)
+
# Note that the executable depends on CLIFp being copied into its build dir
add_dependencies(${APP_TARGET_NAME} fil_copy_clifp)
diff --git a/app/res/frontend/AttractMode/icon.png b/app/res/launcher/AttractMode/icon.png
similarity index 100%
rename from app/res/frontend/AttractMode/icon.png
rename to app/res/launcher/AttractMode/icon.png
diff --git a/app/res/frontend/AttractMode/marquee.png b/app/res/launcher/AttractMode/marquee.png
similarity index 100%
rename from app/res/frontend/AttractMode/marquee.png
rename to app/res/launcher/AttractMode/marquee.png
diff --git a/app/res/frontend/LaunchBox/icon.svg b/app/res/launcher/LaunchBox/icon.svg
similarity index 100%
rename from app/res/frontend/LaunchBox/icon.svg
rename to app/res/launcher/LaunchBox/icon.svg
diff --git a/app/res/resources.qrc b/app/res/resources.qrc
index badfcbb..0ba26f3 100644
--- a/app/res/resources.qrc
+++ b/app/res/resources.qrc
@@ -8,9 +8,9 @@
ui/CLIFp.png
ui/Exit.png
ui/GitHub.png
- frontend/LaunchBox/icon.svg
- frontend/AttractMode/icon.png
- frontend/AttractMode/marquee.png
+ launcher/LaunchBox/icon.svg
+ launcher/AttractMode/icon.png
+ launcher/AttractMode/marquee.png
flashpoint/icon.png
ui/About.png
diff --git a/app/src/frontend/fe-data.cpp b/app/src/frontend/fe-data.cpp
deleted file mode 100644
index 7d8377e..0000000
--- a/app/src/frontend/fe-data.cpp
+++ /dev/null
@@ -1,575 +0,0 @@
-// Unit Include
-#include "fe-data.h"
-
-// Qx Includes
-#include
-#include
-
-// Project Includes
-#include "fe-install.h"
-
-namespace Fe
-{
-//===============================================================================================================
-// DocHandlingError
-//===============================================================================================================
-
-//-Constructor-------------------------------------------------------------
-//Public:
-DocHandlingError::DocHandlingError() :
- mType(NoError)
-{}
-
-
-DocHandlingError::DocHandlingError(const DataDoc& doc, Type t, const QString& s) :
- mType(t),
- mErrorStr(generatePrimaryString(doc, t)),
- mSpecific(s)
-{}
-
-//-Class Functions-------------------------------------------------------------
-//Private:
-QString DocHandlingError::generatePrimaryString(const DataDoc& doc, Type t)
-{
- QString formattedError = ERR_STRINGS[t];
- formattedError.replace(M_DOC_TYPE, doc.identifier().docTypeString());
- formattedError.replace(M_DOC_NAME, doc.identifier().docName());
- formattedError.replace(M_DOC_PARENT, doc.parent()->name());
-
- return formattedError;
-}
-
-//-Instance Functions-------------------------------------------------------------
-//Public:
-bool DocHandlingError::isValid() const { return mType != NoError; }
-QString DocHandlingError::errorString() const { return mErrorStr; }
-QString DocHandlingError::specific() const { return mSpecific; }
-DocHandlingError::Type DocHandlingError::type() const { return mType; }
-
-//Private:
-Qx::Severity DocHandlingError::deriveSeverity() const { return Qx::Critical; }
-quint32 DocHandlingError::deriveValue() const { return mType; }
-QString DocHandlingError::derivePrimary() const { return mErrorStr; }
-QString DocHandlingError::deriveSecondary() const { return mSpecific; }
-
-//===============================================================================================================
-// ImageSources
-//===============================================================================================================
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-//Public:
-ImageSources::ImageSources() {}
-ImageSources::ImageSources(const QString& logoPath, const QString& screenshotPath) :
- mLogoPath(logoPath),
- mScreenshotPath(screenshotPath)
-{}
-
-//-Instance Functions--------------------------------------------------------------------------------------------------
-//Public:
-bool ImageSources::isNull() const { return mLogoPath.isEmpty() && mScreenshotPath.isEmpty(); }
-QString ImageSources::logoPath() const { return mLogoPath; }
-QString ImageSources::screenshotPath() const { return mScreenshotPath; }
-void ImageSources::setLogoPath(const QString& path) { mLogoPath = path; }
-void ImageSources::setScreenshotPath(const QString& path) { mScreenshotPath = path; }
-
-//===============================================================================================================
-// DataDoc::Identifier
-//===============================================================================================================
-
-//-Operators----------------------------------------------------------------------------------------------------
-//Public:
-bool operator== (const DataDoc::Identifier& lhs, const DataDoc::Identifier& rhs) noexcept
-{
- return lhs.mDocType == rhs.mDocType && lhs.mDocName == rhs.mDocName;
-}
-
-//-Hashing------------------------------------------------------------------------------------------------------
-uint qHash(const DataDoc::Identifier& key, uint seed) noexcept
-{
- seed = qHash(key.mDocType, seed);
- seed = qHash(key.mDocName, seed);
-
- return seed;
-}
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-//Public:
-DataDoc::Identifier::Identifier(Type docType, QString docName) :
- mDocType(docType),
- mDocName(docName)
-{}
-
-//-Instance Functions--------------------------------------------------------------------------------------------------
-//Public:
-DataDoc::Type DataDoc::Identifier::docType() const { return mDocType; }
-QString DataDoc::Identifier::docTypeString() const { return TYPE_STRINGS.value(mDocType); }
-QString DataDoc::Identifier::docName() const { return mDocName; }
-
-//===============================================================================================================
-// DataDoc
-//===============================================================================================================
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-//Public:
-DataDoc::DataDoc(Install* const parent, const QString& docPath, QString docName) :
- mParent(parent),
- mDocumentPath(docPath),
- mName(docName)
-{}
-
-//-Destructor------------------------------------------------------------------------------------------------
-//Public:
-DataDoc::~DataDoc() {}
-
-//-Instance Functions--------------------------------------------------------------------------------------------------
-//Public:
-Install* DataDoc::parent() const { return mParent; }
-QString DataDoc::path() const { return mDocumentPath; }
-DataDoc::Identifier DataDoc::identifier() const { return Identifier(type(), mName); }
-
-//===============================================================================================================
-// DataDoc::Reader
-//===============================================================================================================
-
-//-Constructor-----------------------------------------------------------------------------------------------------
-//Protected:
-DataDoc::Reader::Reader(DataDoc* targetDoc) :
- mTargetDocument(targetDoc)
-{}
-
-//-Destructor------------------------------------------------------------------------------------------------
-//Public:
-DataDoc::Reader::~Reader() {}
-
-//===============================================================================================================
-// DataDoc::Writer
-//===============================================================================================================
-
-//-Constructor-----------------------------------------------------------------------------------------------------
-//Protected:
-DataDoc::Writer::Writer(DataDoc* sourceDoc) :
- mSourceDocument(sourceDoc)
-{}
-
-//-Destructor------------------------------------------------------------------------------------------------
-//Public:
-DataDoc::Writer::~Writer() {}
-
-//===============================================================================================================
-// Errorable
-//===============================================================================================================
-
-//-Constructor-----------------------------------------------------------------------------------------------------
-//Protected:
-Errorable::Errorable() {}
-
-//-Destructor------------------------------------------------------------------------------------------------
-//Public:
-Errorable::~Errorable() {}
-
-//-Instance Functions-------------------------------------------------------------------------------------------------
-//Protected:
-bool Errorable::hasError() const { return mError.isValid(); }
-Qx::Error Errorable::error() const { return mError; }
-
-//===============================================================================================================
-// UpdateableDoc
-//===============================================================================================================
-
-//-Constructor-----------------------------------------------------------------------------------------------------
-//Protected:
-UpdateableDoc::UpdateableDoc(Install* const parent, const QString& docPath, QString docName, const UpdateOptions& updateOptions) :
- DataDoc(parent, docPath, docName),
- mUpdateOptions(updateOptions)
-{}
-
-//-Instance Functions--------------------------------------------------------------------------------------------------
-//Public:
-void UpdateableDoc::finalize() {} // Does nothing for base class
-
-//===============================================================================================================
-// PlatformDoc
-//===============================================================================================================
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-//Protected:
-PlatformDoc::PlatformDoc(Install* const parent, const QString& docPath, QString docName, const UpdateOptions& updateOptions) :
- UpdateableDoc(parent, docPath, docName, updateOptions)
-{}
-
-//-Instance Functions--------------------------------------------------------------------------------------------------
-//Private:
-DataDoc::Type PlatformDoc::type() const { return Type::Platform; }
-
-//===============================================================================================================
-// PlatformDoc::Reader
-//===============================================================================================================
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-//Protected:
-PlatformDoc::Reader::Reader(DataDoc* targetDoc) :
- DataDoc::Reader(targetDoc)
-{}
-
-//===============================================================================================================
-// PlatformDoc::Writer
-//===============================================================================================================
-
-PlatformDoc::Writer::Writer(DataDoc* sourceDoc) :
- DataDoc::Writer(sourceDoc)
-{}
-
-//===============================================================================================================
-// BasicPlatformDoc
-//===============================================================================================================
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-//Protected:
-BasicPlatformDoc::BasicPlatformDoc(Install* const parent, const QString& docPath, QString docName, const UpdateOptions& updateOptions) :
- PlatformDoc(parent, docPath, docName, updateOptions)
-{}
-
-//-Instance Functions--------------------------------------------------------------------------------------------------
-//Public:
-bool BasicPlatformDoc::isEmpty() const
-{
- return mGamesFinal.isEmpty() && mGamesExisting.isEmpty() && mAddAppsFinal.isEmpty() && mAddAppsExisting.isEmpty();
-}
-
-const QHash>& BasicPlatformDoc::finalGames() const { return mGamesFinal; }
-const QHash>& BasicPlatformDoc::finalAddApps() const { return mAddAppsFinal; }
-
-bool BasicPlatformDoc::containsGame(QUuid gameId) const { return mGamesFinal.contains(gameId) || mGamesExisting.contains(gameId); }
-bool BasicPlatformDoc::containsAddApp(QUuid addAppId) const { return mAddAppsFinal.contains(addAppId) || mAddAppsExisting.contains(addAppId); }
-
-void BasicPlatformDoc::addSet(const Fp::Set& set, const ImageSources& images)
-{
- if(!mError.isValid())
- {
- // Prepare game
- std::shared_ptr feGame = prepareGame(set.game(), images);
-
- // Add game
- addUpdateableItem(mGamesExisting, mGamesFinal, feGame);
-
- // Handle additional apps
- for(const Fp::AddApp& addApp : set.addApps())
- {
- // Prepare
- std::shared_ptr feAddApp = prepareAddApp(addApp);
-
- // Add
- addUpdateableItem(mAddAppsExisting, mAddAppsFinal, feAddApp);
- }
-
- // Allow install to handle images if needed
- parent()->processDirectGameImages(feGame.get(), images);
-
- }
-}
-
-void BasicPlatformDoc::finalize()
-{
- if(!mError.isValid())
- {
- /* TODO: Have this (and all other implementations of finalize() do something like return
- * the IDs of titles that were removed, or otherwise populate an internal variable so that afterwards
- * the list can be used to purge all images or other title related files (like overviews with AM).
- * Right now only the data portion of old games is removed)
- */
-
- // Finalize item stores
- finalizeUpdateableItems(mGamesExisting, mGamesFinal);
- finalizeUpdateableItems(mAddAppsExisting, mAddAppsFinal);
-
- // Perform base finalization
- UpdateableDoc::finalize();
- }
-}
-
-//===============================================================================================================
-// BasicPlatformDoc::Reader
-//===============================================================================================================
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-//Protected:
-BasicPlatformDoc::Reader::Reader(DataDoc* targetDoc) :
- PlatformDoc::Reader(targetDoc)
-{}
-
-//-Instance Functions--------------------------------------------------------------------------------------------------
-//Protected:
-/* TODO: Consider removing the following and similar, and just making public getters for existing items.
- * Right now this is considered to break encapsulation too much, but it might not be that big of a deal
- * and would be cleaner from a usability standpoint that doing this
- */
-QHash>& BasicPlatformDoc::Reader::targetDocExistingGames()
-{
- return static_cast(mTargetDocument)->mGamesExisting;
-}
-
-QHash>& BasicPlatformDoc::Reader::targetDocExistingAddApps()
-{
- return static_cast(mTargetDocument)->mAddAppsExisting;
-}
-
-//===============================================================================================================
-// BasicPlatformDoc::Writer
-//===============================================================================================================
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-//Protected:
-BasicPlatformDoc::Writer::Writer(DataDoc* sourceDoc) :
- PlatformDoc::Writer(sourceDoc)
-{}
-
-//===============================================================================================================
-// PlaylistDoc
-//===============================================================================================================
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-//Public:
-PlaylistDoc::PlaylistDoc(Install* const parent, const QString& docPath, QString docName, const UpdateOptions& updateOptions) :
- UpdateableDoc(parent, docPath, docName, updateOptions)
-{}
-
-//-Instance Functions--------------------------------------------------------------------------------------------------
-//Private:
-DataDoc::Type PlaylistDoc::type() const { return Type::Playlist; }
-
-//===============================================================================================================
-// PlaylistDoc::Reader
-//===============================================================================================================
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-//Protected:
-PlaylistDoc::Reader::Reader(DataDoc* targetDoc) :
- DataDoc::Reader(targetDoc)
-{}
-
-//===============================================================================================================
-// PlaylistDoc::Writer
-//===============================================================================================================
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-//Protected:
-PlaylistDoc::Writer::Writer(DataDoc* sourceDoc) :
- DataDoc::Writer(sourceDoc)
-{}
-
-//===============================================================================================================
-// BasicPlaylistDoc
-//===============================================================================================================
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-//Public:
-/* NOTE: Right now mPlaylistHeader is left uninitialized (unless done so explicitly by a derivative). This is fine,
- * as currently 'void PlaylistDoc::setPlaylistHeader(Fp::Playlist playlist)' checks to see if an existing header
- * is present before performing a field transfer (i.e. in case the playlist doc didn't already exist); however,
- * if more parts of the process end up needing to interact with a doc that has a potentially null playlist header,
- * it may be better to require a value for it in this base class' constructor so that all derivatives must provide
- * a default (likely null/empty) playlist header.
- */
-BasicPlaylistDoc::BasicPlaylistDoc(Install* const parent, const QString& docPath, QString docName, const UpdateOptions& updateOptions) :
- PlaylistDoc(parent, docPath, docName, updateOptions)
-{}
-
-//-Instance Functions--------------------------------------------------------------------------------------------------
-//Public:
-bool BasicPlaylistDoc::isEmpty() const
-{
- // The playlist header doesn't matter if there are no games
- return mPlaylistGamesFinal.isEmpty() && mPlaylistGamesExisting.isEmpty();
-}
-
-const std::shared_ptr& BasicPlaylistDoc::playlistHeader() const { return mPlaylistHeader; }
-const QHash>& BasicPlaylistDoc::finalPlaylistGames() const { return mPlaylistGamesFinal; }
-
-bool BasicPlaylistDoc::containsPlaylistGame(QUuid gameId) const { return mPlaylistGamesFinal.contains(gameId) || mPlaylistGamesExisting.contains(gameId); }
-
-
-void BasicPlaylistDoc::setPlaylistData(const Fp::Playlist& playlist)
-{
- if(!mError.isValid())
- {
- std::shared_ptr fePlaylistHeader = preparePlaylistHeader(playlist);
-
- // Ensure doc already existed before transferring (null check)
- if(mPlaylistHeader)
- fePlaylistHeader->transferOtherFields(mPlaylistHeader->otherFields());
-
- // Set instance header to new one
- mPlaylistHeader = fePlaylistHeader;
-
- for(const auto& plg : playlist.playlistGames())
- {
- // Prepare playlist game
- std::shared_ptr fePlaylistGame = preparePlaylistGame(plg);
-
- // Add playlist game
- addUpdateableItem(mPlaylistGamesExisting, mPlaylistGamesFinal, fePlaylistGame);
- }
- }
-}
-
-void BasicPlaylistDoc::finalize()
-{
- if(!mError.isValid())
- {
- // Finalize item stores
- finalizeUpdateableItems(mPlaylistGamesExisting, mPlaylistGamesFinal);
-
- // Perform base finalization
- UpdateableDoc::finalize();
- }
-}
-
-//===============================================================================================================
-// BasicPlaylistDoc::Reader
-//===============================================================================================================
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-//Protected:
-BasicPlaylistDoc::Reader::Reader(DataDoc* targetDoc) :
- PlaylistDoc::Reader(targetDoc)
-{}
-
-//-Instance Functions--------------------------------------------------------------------------------------------------
-//Protected:
-QHash>& BasicPlaylistDoc::Reader::targetDocExistingPlaylistGames()
-{
- return static_cast(mTargetDocument)->mPlaylistGamesExisting;
-}
-
-std::shared_ptr& BasicPlaylistDoc::Reader::targetDocPlaylistHeader()
-{
- return static_cast(mTargetDocument)->mPlaylistHeader;
-}
-
-//===============================================================================================================
-// BasicPlaylistDoc::Writer
-//===============================================================================================================
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-//Protected:
-BasicPlaylistDoc::Writer::Writer(DataDoc* sourceDoc) :
- PlaylistDoc::Writer(sourceDoc)
-{}
-
-//===============================================================================================================
-// XmlDocReader
-//===============================================================================================================
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-//Public:
-XmlDocReader::XmlDocReader(Fe::DataDoc* targetDoc, const QString& root) :
- Fe::DataDoc::Reader(targetDoc),
- mXmlFile(targetDoc->path()),
- mStreamReader(&mXmlFile),
- mRootElement(root)
-{}
-
-//-Instance Functions-------------------------------------------------------------------------------------------------
-//Protected:
-Fe::DocHandlingError XmlDocReader::streamStatus() const
-{
- if(mStreamReader.hasError())
- {
- Qx::XmlStreamReaderError xmlError(mStreamReader);
- return Fe::DocHandlingError(*mTargetDocument, Fe::DocHandlingError::DocReadFailed, xmlError.text());
- }
-
- return Fe::DocHandlingError();
-}
-
-//Public:
-Fe::DocHandlingError XmlDocReader::readInto()
-{
- // Open File
- if(!mXmlFile.open(QFile::ReadOnly))
- return Fe::DocHandlingError(*mTargetDocument, Fe::DocHandlingError::DocCantOpen, mXmlFile.errorString());
-
- if(!mStreamReader.readNextStartElement())
- {
- Qx::XmlStreamReaderError xmlError(mStreamReader);
- return Fe::DocHandlingError(*mTargetDocument, Fe::DocHandlingError::DocReadFailed, xmlError.text());
- }
-
- if(mStreamReader.name() != mRootElement)
- return Fe::DocHandlingError(*mTargetDocument, Fe::DocHandlingError::NotParentDoc);
-
- return readTargetDoc();
-
- // File is automatically closed when reader is destroyed...
-}
-
-//===============================================================================================================
-// XmlDocWriter
-//===============================================================================================================
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-//Public:
-XmlDocWriter::XmlDocWriter(Fe::DataDoc* sourceDoc, const QString& root) :
- Fe::DataDoc::Writer(sourceDoc),
- mXmlFile(sourceDoc->path()),
- mStreamWriter(&mXmlFile),
- mRootElement(root)
-{}
-
-//-Instance Functions-------------------------------------------------------------------------------------------------
-//Protected:
-void XmlDocWriter::writeCleanTextElement(const QString& qualifiedName, const QString& text)
-{
- if(text.isEmpty())
- mStreamWriter.writeEmptyElement(qualifiedName);
- else
- mStreamWriter.writeTextElement(qualifiedName, Qx::xmlSanitized(text));
-}
-
-void XmlDocWriter::writeOtherFields(const QHash& otherFields)
-{
- for(QHash::const_iterator i = otherFields.constBegin(); i != otherFields.constEnd(); ++i)
- writeCleanTextElement(i.key(), i.value());
-}
-
-Fe::DocHandlingError XmlDocWriter::streamStatus() const
-{
- return mStreamWriter.hasError() ? Fe::DocHandlingError(*mSourceDocument, Fe::DocHandlingError::DocWriteFailed, mStreamWriter.device()->errorString()) :
- Fe::DocHandlingError();
-}
-
-//Public:
-Fe::DocHandlingError XmlDocWriter::writeOutOf()
-{
- // Open File
- if(!mXmlFile.open(QFile::WriteOnly | QFile::Truncate)) // Discard previous contents
- return Fe::DocHandlingError(*mSourceDocument, Fe::DocHandlingError::DocCantSave, mXmlFile.errorString());
-
- // Enable auto formatting
- mStreamWriter.setAutoFormatting(true);
- mStreamWriter.setAutoFormattingIndent(2);
-
- // Write standard XML header
- mStreamWriter.writeStartDocument(u"1.0"_s, true);
-
- // Write main LaunchBox tag
- mStreamWriter.writeStartElement(mRootElement);
-
- // Write main body
- if(!writeSourceDoc())
- return streamStatus();
-
- // Close main LaunchBox tag
- mStreamWriter.writeEndElement();
-
- // Finish document
- mStreamWriter.writeEndDocument();
-
- // Return null string on success
- return streamStatus();
-
- // File is automatically closed when writer is destroyed...
-}
-
-}
-
diff --git a/app/src/frontend/fe-data.h b/app/src/frontend/fe-data.h
deleted file mode 100644
index c6486cf..0000000
--- a/app/src/frontend/fe-data.h
+++ /dev/null
@@ -1,617 +0,0 @@
-#ifndef FE_DATA
-#define FE_DATA
-
-// Standard Library Includes
-#include
-#include
-
-// Qt Includes
-#include
-#include
-
-// Qx Includes
-#include
-#include
-#include
-#include
-
-// libfp Includes
-#include
-
-// Project Includes
-#include "fe-items.h"
-
-/* TODO: Right now all docs that need to be constructed by an install have that install marked as their friend,
- * but they also are using the Passkey Idiom, a key class with a private constructor that they are also friends
- * with, which is is redundant for the purposes of construction. First see if the docs really need to be friends
- * with the Installs (I think they do for the parent() Install pointer to be used as it is). Then, if they do,
- * the only reason the Passkey Idiom is also being used is because these docs are constructed using
- * std::make_shared<>(); even if the doc itself has the Install marked as a friend, it doesnt have
- * the function std::make_shared() marked as a friend, so it can't be constructed that way. Because
- * of this the Install constructor has to be public and the idiom used. So, double check the minor differences
- * between constructing an instance on the heap and then creating a smart pointer with the regular pointer vs.
- * using std::make_shared<>(), and see if allowing for its use when creating the docs is really worth also
- * having to do Passkey.
- */
-
-namespace Fe
-{
-//-Concepts------------------------------------------------------------------------------------------------------
-template
-concept raw_item = std::derived_from;
-
-template
-concept shared_item = Qx::specializes && std::derived_from;
-
-template
-concept raw_basic_item = std::derived_from;
-
-template
-concept shared_basic_item = Qx::specializes && std::derived_from;
-
-template
-concept item = raw_item || shared_item;
-
-template
-concept basic_item = raw_basic_item || shared_basic_item;
-
-template
-concept updateable_item_container = Qx::qassociative && item;
-
-template
-concept updateable_basicitem_container = Qx::qassociative && basic_item &&
- std::same_as;
-
-//-External Reference--------------------------------------------------------------------------------------------
-class Install;
-class DataDoc;
-
-//-Enums----------------------------------------------------------------------------------------------------------
-enum class ImportMode {OnlyNew, NewAndExisting};
-
-//-Structs---------------------------------------------------------------------------------------------------------
-struct UpdateOptions
-{
- ImportMode importMode;
- bool removeObsolete;
-};
-
-//-Classes-----------------------------------------------------------------------------------------------------------
-class QX_ERROR_TYPE(DocHandlingError, "Fe::DocHandlingError", 1310)
-{
-//-Class Enums-------------------------------------------------------------
-public:
- enum Type
- {
- NoError = 0,
- DocAlreadyOpen = 1,
- DocCantOpen = 2,
- DocCantSave = 3,
- NotParentDoc = 4,
- CantRemoveBackup = 5,
- CantCreateBackup = 6,
- DocInvalidType = 7,
- DocReadFailed = 8,
- DocWriteFailed = 9
- };
-
-//-Class Variables-------------------------------------------------------------
-private:
- // Message Macros
- static inline const QString M_DOC_TYPE = u""_s;
- static inline const QString M_DOC_NAME = u""_s;
- static inline const QString M_DOC_PARENT = u""_s;
-
- static inline const QHash ERR_STRINGS{
- {NoError, u""_s},
- {DocAlreadyOpen, u"The target document ("_s + M_DOC_TYPE + u" | "_s + M_DOC_NAME + u") is already open."_s},
- {DocCantOpen, u"The target document ("_s + M_DOC_TYPE + u" | "_s + M_DOC_NAME + u") cannot be opened."_s},
- {DocCantSave, u"The target document ("_s + M_DOC_TYPE + u" | "_s + M_DOC_NAME + u") cannot be saved."_s},
- {NotParentDoc, u"The target document ("_s + M_DOC_TYPE + u" | "_s + M_DOC_NAME + u") is not a"_s + M_DOC_PARENT + u"document."_s},
- {CantRemoveBackup, u"The existing backup of the target document ("_s + M_DOC_TYPE + u" | "_s + M_DOC_NAME + u") could not be removed."_s},
- {CantCreateBackup, u"Could not create a backup of the target document ("_s + M_DOC_TYPE + u" | "_s + M_DOC_NAME + u")."_s},
- {DocInvalidType, u"The document ("_s + M_DOC_TYPE + u" | "_s + M_DOC_NAME + u") is invalid or of the wrong type."_s},
- {DocReadFailed, u"Reading the target document ("_s + M_DOC_TYPE + u" | "_s + M_DOC_NAME + u") failed."_s},
- {DocWriteFailed, u"Writing to the target document ("_s + M_DOC_TYPE + u" | "_s + M_DOC_NAME + u") failed."_s}
- };
-
-//-Instance Variables-------------------------------------------------------------
-private:
- Type mType;
- QString mErrorStr;
- QString mSpecific;
-
-//-Constructor-------------------------------------------------------------
-public:
- DocHandlingError();
- DocHandlingError(const DataDoc& doc, Type t, const QString& s = {});
-
-//-Class Functions-------------------------------------------------------------
-private:
- static QString generatePrimaryString(const DataDoc& doc, Type t);
-
-//-Instance Functions-------------------------------------------------------------
-public:
- bool isValid() const;
- Type type() const;
-
- QString errorString() const;
- QString specific() const;
-
-private:
- Qx::Severity deriveSeverity() const override;
- quint32 deriveValue() const override;
- QString derivePrimary() const override;
- QString deriveSecondary() const override;
-};
-
-class ImageSources
-{
-//-Instance Members--------------------------------------------------------------------------------------------------
-private:
- QString mLogoPath;
- QString mScreenshotPath;
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-public:
- ImageSources();
- ImageSources(const QString& logoPath, const QString& screenshotPath);
-
-//-Instance Functions--------------------------------------------------------------------------------------------------
-public:
- bool isNull() const;
- QString logoPath() const;
- QString screenshotPath() const;
- void setLogoPath(const QString& path);
- void setScreenshotPath(const QString& path);
-};
-
-class DataDoc
-{
- /* TODO: Consider making this a template class where T is the type argument for the doc's parent, so that the
- * parent() method can return the type directly, without a derived document needing to cast to it's parent's type
- */
-
-//-Class Enums---------------------------------------------------------------------------------------------------------
-public:
- enum class Type {Platform, Playlist, Config};
-
-//-Inner Classes----------------------------------------------------------------------------------------------------
-public:
- class Reader;
- class Writer;
-
- class Identifier
- {
- friend bool operator== (const Identifier& lhs, const Identifier& rhs) noexcept;
- friend uint qHash(const Identifier& key, uint seed) noexcept;
-
- private:
- Type mDocType;
- QString mDocName;
-
- public:
- Identifier(Type docType, QString docName);
-
- Type docType() const;
- QString docTypeString() const;
- QString docName() const;
- };
-
-//-Class Variables-----------------------------------------------------------------------------------------------------
-private:
- static inline const QHash TYPE_STRINGS = {
- {Type::Platform, u"Platform"_s},
- {Type::Playlist, u"Playlist"_s},
- {Type::Config, u"Config"_s}
- };
-
-//-Instance Variables--------------------------------------------------------------------------------------------------
-protected:
- Install* const mParent;
- const QString mDocumentPath;
- const QString mName;
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-protected:
- DataDoc(Install* const parent, const QString& docPath, QString docName);
-
-//-Destructor-------------------------------------------------------------------------------------------------
-public:
- virtual ~DataDoc();
-
-//-Instance Functions--------------------------------------------------------------------------------------------------
-protected:
- virtual Type type() const = 0;
-
-public:
- Install* parent() const;
- QString path() const;
- Identifier identifier() const;
- virtual bool isEmpty() const = 0;
-};
-QX_SCOPED_ENUM_HASH_FUNC(DataDoc::Type);
-
-class DataDoc::Reader
-{
-//-Instance Variables--------------------------------------------------------------------------------------------------
-protected:
- DataDoc* mTargetDocument;
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-protected:
- Reader(DataDoc* targetDoc);
-
-//-Destructor-------------------------------------------------------------------------------------------------
-public:
- virtual ~Reader();
-
-//-Instance Functions-------------------------------------------------------------------------------------------------
-public:
- virtual Fe::DocHandlingError readInto() = 0;
-};
-
-class DataDoc::Writer
-{
-//-Instance Variables--------------------------------------------------------------------------------------------------
-protected:
- DataDoc* mSourceDocument;
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-protected:
- Writer(DataDoc* sourceDoc);
-
-//-Destructor-------------------------------------------------------------------------------------------------
-public:
- virtual ~Writer();
-
-//-Instance Functions-------------------------------------------------------------------------------------------------
-public:
- virtual Fe::DocHandlingError writeOutOf() = 0;
-};
-
-class Errorable
-{
-//-Instance Variables--------------------------------------------------------------------------------------------------
-protected:
- Qx::Error mError;
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-protected:
- Errorable();
-
-//-Destructor-------------------------------------------------------------------------------------------------
-public:
- virtual ~Errorable();
-
-//-Instance Functions--------------------------------------------------------------------------------------------------
-public:
- bool hasError() const;
- Qx::Error error() const;
-};
-
-class UpdateableDoc : public DataDoc
-{
-//-Instance Variables--------------------------------------------------------------------------------------------------
-protected:
- UpdateOptions mUpdateOptions;
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-protected:
- explicit UpdateableDoc(Install* const parent, const QString& docPath, QString docName, const UpdateOptions& updateOptions);
-
-//-Class Functions-----------------------------------------------------------------------------------------------------
-template
-T* itemPtr(T& item) { return &item; }
-
-template
-T* itemPtr(std::shared_ptr item) { return item.get(); }
-
-//-Instance Functions--------------------------------------------------------------------------------------------------
-protected:
- template
- requires updateable_item_container
- void finalizeUpdateableItems(C& existingItems,
- C& finalItems)
- {
- // Copy items to final list if obsolete entries are to be kept
- if(!mUpdateOptions.removeObsolete)
- finalItems.insert(existingItems);
-
- // Clear existing lists
- existingItems.clear();
- }
-
- template
- requires updateable_item_container
- void addUpdateableItem(C& existingItems,
- C& finalItems,
- typename C::key_type key,
- typename C::mapped_type newItem)
- {
- // Check if item exists
- if(existingItems.contains(key))
- {
- // Replace if existing update is on, move existing otherwise
- if(mUpdateOptions.importMode == ImportMode::NewAndExisting)
- {
- itemPtr(newItem)->transferOtherFields(itemPtr(existingItems[key])->otherFields());
- finalItems[key] = newItem;
- existingItems.remove(key);
- }
- else
- {
- finalItems[key] = std::move(existingItems[key]);
- existingItems.remove(key);
- }
-
- }
- else
- finalItems[key] = newItem;
- }
-
- template
- requires updateable_basicitem_container
- void addUpdateableItem(C& existingItems,
- C& finalItems,
- typename C::mapped_type newItem)
- {
- addUpdateableItem(existingItems,
- finalItems,
- std::static_pointer_cast(newItem)->id(),
- newItem);
- }
-
-public:
- virtual void finalize();
-};
-
-class PlatformDoc : public UpdateableDoc, public Errorable
-{
-//-Inner Classes----------------------------------------------------------------------------------------------------
-public:
- class Reader;
- class Writer;
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-protected:
- explicit PlatformDoc(Install* const parent, const QString& docPath, QString docName, const UpdateOptions& updateOptions);
-
-//-Instance Functions--------------------------------------------------------------------------------------------------
-private:
- Type type() const override;
-
-public:
- virtual bool containsGame(QUuid gameId) const = 0;
- virtual bool containsAddApp(QUuid addAppId) const = 0;
-
- /* NOTE: The image paths provided here can be null (i.e. images unavailable). Handle accordingly in derived.
- * Also in most cases, addSet should call parent()->processDirectGameImages().
- *
- * TODO: The back and forth here between this and derived documents is a little silly. It mostly exists
- * so that BasicPlatformDoc can call processDirectGameImages() directly; since installs need to always
- * implement custom image processing logic anyway, this maybe should be changed so that BasicPlatformDoc
- * has a pure virtual function similar to processDirectGameImages() with derived installs then implementing
- * that if they need to use BasicPlatform doc. This would free Installs that do not use said class from having
- * to re-implement that function at all (even if they have a close equivalent anyway).
- */
- virtual void addSet(const Fp::Set& set, const ImageSources& images) = 0;
-};
-
-class PlatformDoc::Reader : public virtual DataDoc::Reader
-{
-//-Constructor-------------------------------------------------------------------------------------------------------
-protected:
- Reader(DataDoc* targetDoc);
-};
-
-class PlatformDoc::Writer : public virtual DataDoc::Writer
-{
-//-Constructor-------------------------------------------------------------------------------------------------------
-protected:
- Writer(DataDoc* sourceDoc);
-};
-
-class BasicPlatformDoc : public PlatformDoc
-{
-//-Inner Classes----------------------------------------------------------------------------------------------------
-public:
- class Reader;
- class Writer;
-
-//-Instance Variables--------------------------------------------------------------------------------------------------
-protected:
- QHash> mGamesFinal;
- QHash> mGamesExisting;
- QHash> mAddAppsFinal;
- QHash> mAddAppsExisting;
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-protected:
- explicit BasicPlatformDoc(Install* const parent, const QString& docPath, QString docName, const UpdateOptions& updateOptions);
-
-//-Instance Functions--------------------------------------------------------------------------------------------------
-protected:
- virtual std::shared_ptr prepareGame(const Fp::Game& game, const ImageSources& images) = 0;
- virtual std::shared_ptr prepareAddApp(const Fp::AddApp& game) = 0;
-
-public:
- virtual bool isEmpty() const override;
-
- const QHash>& finalGames() const;
- const QHash>& finalAddApps() const;
-
- bool containsGame(QUuid gameId) const override; // NOTE: UNUSED
- bool containsAddApp(QUuid addAppId) const override; // NOTE: UNUSED
-
- void addSet(const Fp::Set& set, const ImageSources& images) override;
-
- void finalize() override;
-};
-
-class BasicPlatformDoc::Reader : public PlatformDoc::Reader
-{
-//-Constructor-------------------------------------------------------------------------------------------------------
-protected:
- Reader(DataDoc* targetDoc);
-
-//-Instance Functions-------------------------------------------------------------------------------------------------
-protected:
- QHash>& targetDocExistingGames();
- QHash>& targetDocExistingAddApps();
-};
-
-class BasicPlatformDoc::Writer : public PlatformDoc::Writer
-{
-//-Constructor-------------------------------------------------------------------------------------------------------
-protected:
- Writer(DataDoc* sourceDoc);
-};
-
-class PlaylistDoc : public UpdateableDoc, public Errorable
-{
-//-Inner Classes----------------------------------------------------------------------------------------------------
-public:
- class Reader;
- class Writer;
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-protected:
- explicit PlaylistDoc(Install* const parent, const QString& docPath, QString docName, const UpdateOptions& updateOptions);
-
-//-Instance Functions--------------------------------------------------------------------------------------------------
-private:
- Type type() const override;
-
-public:
- virtual bool containsPlaylistGame(QUuid gameId) const = 0; // NOTE: UNUSED
-
- virtual void setPlaylistData(const Fp::Playlist& playlist) = 0;
-};
-
-class PlaylistDoc::Reader : public virtual DataDoc::Reader
-{
-//-Constructor-------------------------------------------------------------------------------------------------------
-protected:
- Reader(DataDoc* targetDoc);
-};
-
-class PlaylistDoc::Writer : public virtual DataDoc::Writer
-{
-//-Constructor-------------------------------------------------------------------------------------------------------
-protected:
- Writer(DataDoc* sourceDoc);
-};
-
-class BasicPlaylistDoc : public PlaylistDoc
-{
-//-Inner Classes----------------------------------------------------------------------------------------------------
-public:
- class Reader;
- class Writer;
-
-//-Instance Variables--------------------------------------------------------------------------------------------------
-protected:
- std::shared_ptr mPlaylistHeader;
- QHash> mPlaylistGamesFinal;
- QHash> mPlaylistGamesExisting;
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-protected:
- explicit BasicPlaylistDoc(Install* const parent, const QString& docPath, QString docName, const UpdateOptions& updateOptions);
-
-//-Instance Functions--------------------------------------------------------------------------------------------------
-protected:
- virtual std::shared_ptr preparePlaylistHeader(const Fp::Playlist& playlist) = 0;
- virtual std::shared_ptr preparePlaylistGame(const Fp::PlaylistGame& game) = 0;
-
-public:
- virtual bool isEmpty() const override;
-
- const std::shared_ptr& playlistHeader() const;
- const QHash>& finalPlaylistGames() const;
-
- bool containsPlaylistGame(QUuid gameId) const override;
-
- void setPlaylistData(const Fp::Playlist& playlist) override;
-
- void finalize() override;
-};
-
-/* TODO: Consider making the existing items accessible through a public getter, or at least a function to add
- * them through a public function (similar todo already exists). If this is done then these base readers and writers
- * for specific docs can be removed since they only exist to define the "workaround" getters for existing items.
- *
- * This would mean that virtual inheritance wouldn't be required for the other readers/writers and greatly simplify
- * things
- */
-class BasicPlaylistDoc::Reader : public PlaylistDoc::Reader
-{
-//-Constructor-------------------------------------------------------------------------------------------------------
-protected:
- Reader(DataDoc* targetDoc);
-
-//-Instance Functions-------------------------------------------------------------------------------------------------
-protected:
- QHash>& targetDocExistingPlaylistGames();
- std::shared_ptr& targetDocPlaylistHeader();
-};
-
-class BasicPlaylistDoc::Writer : public PlaylistDoc::Writer
-{
-//-Constructor-------------------------------------------------------------------------------------------------------
-protected:
- Writer(DataDoc* sourceDoc);
-};
-
-/*
- * Not used by base implementation, but useful for multiple frontends
- */
-class XmlDocReader : public virtual Fe::DataDoc::Reader
-{
-//-Instance Variables--------------------------------------------------------------------------------------------------
-protected:
- QFile mXmlFile;
- QXmlStreamReader mStreamReader;
- QString mRootElement;
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-public:
- XmlDocReader(Fe::DataDoc* targetDoc, const QString& root);
-
-//-Instance Functions-------------------------------------------------------------------------------------------------
-private:
- virtual Fe::DocHandlingError readTargetDoc() = 0;
-
-protected:
- Fe::DocHandlingError streamStatus() const;
-
-public:
- Fe::DocHandlingError readInto() override;
-};
-
-class XmlDocWriter : public virtual Fe::DataDoc::Writer
-{
-//-Instance Variables--------------------------------------------------------------------------------------------------
-protected:
- QFile mXmlFile;
- QXmlStreamWriter mStreamWriter;
- QString mRootElement;
-
-//-Constructor--------------------------------------------------------------------------------------------------------
-public:
- XmlDocWriter(Fe::DataDoc* sourceDoc, const QString& root);
-
-//-Instance Functions-------------------------------------------------------------------------------------------------
-protected:
- virtual bool writeSourceDoc() = 0;
- void writeCleanTextElement(const QString& qualifiedName, const QString& text);
- void writeOtherFields(const QHash& otherFields);
- Fe::DocHandlingError streamStatus() const;
-
-public:
- Fe::DocHandlingError writeOutOf() override;
-};
-
-}
-#endif // FE_DATA
diff --git a/app/src/frontend/fe-install.cpp b/app/src/frontend/fe-install.cpp
deleted file mode 100644
index 9a2a5dd..0000000
--- a/app/src/frontend/fe-install.cpp
+++ /dev/null
@@ -1,172 +0,0 @@
-// Unit Include
-#include "fe-install.h"
-
-// Qt Includes
-#include
-
-namespace Fe
-{
-
-//===============================================================================================================
-// Install
-//===============================================================================================================
-
-//-Constructor---------------------------------------------------------------------------------------------------
-Install::Install(const QString& installPath) :
- InstallFoundation(installPath)
-{}
-
-//-Class Functions--------------------------------------------------------------------------------------------
-//Public:
-QMap& Install::registry() { static QMap registry; return registry; }
-
-void Install::registerInstall(const QString& name, const Entry& entry) { registry()[name] = entry; }
-
-std::shared_ptr Install::acquireMatch(const QString& installPath)
-{
- // Check all installs against path and return match if found
- QMap::const_iterator i;
-
- for(i = registry().constBegin(); i != registry().constEnd(); ++i)
- {
- Entry entry = i.value();
- std::shared_ptr possibleMatch = entry.factory->produce(installPath);
-
- if(possibleMatch->isValid())
- return possibleMatch;
- }
-
- // Return nullptr on failure to find match
- return nullptr;
-}
-
-//-Instance Functions--------------------------------------------------------------------------------------------
-//Protected:
-void Install::nullify()
-{
- // Redundant with base version, but here to make it clear its part of the main Install interface
- InstallFoundation::nullify();
-}
-
-
-//Public:
-void Install::softReset()
-{
- // Redundant with base version, but here to make it clear its part of the main Install interface
- InstallFoundation::softReset();
-}
-
-bool Install::supportsImageMode(ImageMode imageMode) const { return preferredImageModeOrder().contains(imageMode); }
-
-QString Install::versionString() const { return u"Unknown Version"_s; }
-
-/* These functions can be overridden by children as needed.
- * Work within them should be kept as minimal as possible since they are not accounted
- * for by the import progress indicator.
- */
-Qx::Error Install::preImport(const ImportDetails& details)
-{
- mImportDetails = std::make_unique(details);
- return Qx::Error();
-}
-
-Qx::Error Install::postImport() { return {}; }
-Qx::Error Install::prePlatformsImport() { return {}; }
-Qx::Error Install::postPlatformsImport() { return {}; }
-
-Qx::Error Install::preImageProcessing(QList& workerTransfers, const ImageSources& bulkSources)
-{
- Q_UNUSED(workerTransfers);
- Q_UNUSED(bulkSources);
- return {};
-}
-
-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
- QString translatedName = translateDocName(name, DataDoc::Type::Platform);
-
- // Get initialized blank doc and reader
- std::shared_ptr docReader = preparePlatformDocCheckout(returnBuffer, translatedName);
-
- // Open document
- Fe::DocHandlingError readErrorStatus = checkoutDataDocument(returnBuffer.get(), docReader);
-
- // Set return null on failure
- if(readErrorStatus.isValid())
- returnBuffer.reset();
-
- // Return status
- return readErrorStatus;
-}
-
-Fe::DocHandlingError Install::checkoutPlaylistDoc(std::unique_ptr& returnBuffer, const QString& name)
-{
- // Translate to frontend doc name
- QString translatedName = translateDocName(name, DataDoc::Type::Playlist);
-
- // Get initialized blank doc and reader
- std::shared_ptr docReader = preparePlaylistDocCheckout(returnBuffer, translatedName);
-
- // Open document
- Fe::DocHandlingError readErrorStatus = checkoutDataDocument(returnBuffer.get(), docReader);
-
- // Set return null on failure
- if(readErrorStatus.isValid())
- returnBuffer.reset();
-
- // Return status
- return readErrorStatus;
-}
-
-Fe::DocHandlingError Install::commitPlatformDoc(std::unique_ptr document)
-{
- // Doc should belong to this install
- assert(document->parent() == this);
-
- // Prepare writer
- std::shared_ptr docWriter = preparePlatformDocCommit(document);
-
- // Write
- Fe::DocHandlingError writeErrorStatus = commitDataDocument(document.get(), docWriter);
-
- // Ensure document is cleared
- document.reset();
-
- // Return write status and let document ptr auto delete
- return writeErrorStatus;
-}
-
-Fe::DocHandlingError Install::commitPlaylistDoc(std::unique_ptr document)
-{
- // Doc should belong to this install
- assert(document->parent() == this);
-
- // Prepare writer
- std::shared_ptr docWriter = preparePlaylistDocCommit(document);
-
- // Write
- Fe::DocHandlingError writeErrorStatus = commitDataDocument(document.get(), docWriter);
-
- // Ensure document is cleared
- document.reset();
-
- // Return write status and let document ptr auto delete
- return writeErrorStatus;
-}
-
-QString Install::platformCategoryIconPath() const { return QString(); } // Unsupported in default implementation
-std::optional Install::platformIconsDirectory() const { return std::nullopt; } // Unsupported in default implementation
-std::optional Install::playlistIconsDirectory() const { return std::nullopt; } // Unsupported in default implementation
-
-}
diff --git a/app/src/frontend/fe-install.h b/app/src/frontend/fe-install.h
deleted file mode 100644
index 471ea9f..0000000
--- a/app/src/frontend/fe-install.h
+++ /dev/null
@@ -1,112 +0,0 @@
-#ifndef FE_INSTALL_H
-#define FE_INSTALL_H
-
-// Qt Includes
-#include
-
-// Project Includes
-#include "fe-installfoundation.h"
-
-//-Macros-------------------------------------------------------------------------------------------------------------------
-#define REGISTER_FRONTEND(fe_name, fe_install, fe_icon_path, fe_helpUrl) \
- class fe_install##Factory : public Fe::InstallFactory \
- { \
- public: \
- fe_install##Factory() \
- { \
- Install::Entry entry { \
- .factory = this, \
- .iconPath = fe_icon_path, \
- .helpUrl = fe_helpUrl \
- }; \
- Fe::Install::registerInstall(fe_name, entry); \
- } \
- virtual std::shared_ptr produce(const QString& installPath) const { return std::make_shared(installPath); } \
- }; \
- static fe_install##Factory _##install##Factory;
-
-namespace Fe
-{
-
-class InstallFactory
-{
-//-Instance Functions------------------------------------------------------------------------------------------------------
-public:
- virtual std::shared_ptr produce(const QString& installPath) const = 0;
-};
-
-class Install : public InstallFoundation
-{
-//-Structs------------------------------------------------------------------------------------------------------
-public:
- struct Entry
- {
- const InstallFactory* factory;
- const QString* iconPath;
- const QUrl* helpUrl;
- };
-
-//-Constructor---------------------------------------------------------------------------------------------------
-public:
- Install(const QString& installPath);
-
-//-Class Functions------------------------------------------------------------------------------------------------------
-public:
- // NOTE: Registry put behind function call to avoid SIOF since otherwise initialization of static registry before calls to registerFrontend would not be guaranteed
- static QMap& registry();
- static void registerInstall(const QString& name, const Entry& entry);
- static std::shared_ptr acquireMatch(const QString& installPath);
-
-//-Instance Functions---------------------------------------------------------------------------------------------------------
-protected:
- // Install management
- virtual void nullify() override;
- virtual Qx::Error populateExistingDocs() override = 0;
-
- // Doc Handling
- virtual std::shared_ptr preparePlatformDocCheckout(std::unique_ptr& platformDoc, const QString& translatedName) = 0;
- virtual std::shared_ptr preparePlaylistDocCheckout(std::unique_ptr& playlistDoc, const QString& translatedName) = 0;
- virtual std::shared_ptr preparePlatformDocCommit(const std::unique_ptr& document) = 0;
- virtual std::shared_ptr preparePlaylistDocCommit(const std::unique_ptr& document) = 0;
-
-public:
- // Install management
- virtual void softReset() override;
-
- // Info
- virtual QString name() const = 0;
- virtual QList preferredImageModeOrder() const = 0;
- bool supportsImageMode(ImageMode imageMode) const;
- virtual QString versionString() const;
- virtual bool isRunning() const = 0;
-
- // Import stage notifier hooks
- virtual Qx::Error preImport(const ImportDetails& details);
- virtual Qx::Error postImport();
- virtual Qx::Error prePlatformsImport();
- virtual Qx::Error postPlatformsImport();
- virtual Qx::Error preImageProcessing(QList& workerTransfers, const ImageSources& bulkSources);
- virtual Qx::Error postImageProcessing();
- virtual Qx::Error prePlaylistsImport();
- 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);
- Fe::DocHandlingError commitPlaylistDoc(std::unique_ptr playlistDoc);
-
- // Image handling
- // NOTE: The image paths provided here can be null (i.e. images unavailable). Handle accordingly in derived.
- virtual void processDirectGameImages(const Game* game, const Fe::ImageSources& imageSources) = 0;
-
- // TODO: These might need to be changed to support launchers where the platform images are tied closely to the platform documents,
- // but currently none do this so this works.
- virtual QString platformCategoryIconPath() const; // Unsupported in default implementation, needs to return path with .png extension
- virtual std::optional platformIconsDirectory() const; // Unsupported in default implementation
- virtual std::optional playlistIconsDirectory() const; // Unsupported in default implementation
-};
-
-}
-#endif // FE_INSTALL_H
diff --git a/app/src/frontend/fe-installfoundation.cpp b/app/src/frontend/fe-installfoundation.cpp
deleted file mode 100644
index 1e6148e..0000000
--- a/app/src/frontend/fe-installfoundation.cpp
+++ /dev/null
@@ -1,264 +0,0 @@
-// Unit Include
-#include "fe-installfoundation.h"
-
-namespace Fe
-{
-
-//===============================================================================================================
-// RevertError
-//===============================================================================================================
-
-//-Constructor-------------------------------------------------------------
-//Private:
-RevertError::RevertError(Type t, const QString& s) :
- mType(t),
- mSpecific(s)
-{}
-
-//Public:
-RevertError::RevertError() :
- mType(NoError)
-{}
-
-//-Instance Functions-------------------------------------------------------------
-//Public:
-bool RevertError::isValid() const { return mType != NoError; }
-QString RevertError::specific() const { return mSpecific; }
-RevertError::Type RevertError::type() const { return mType; }
-
-//Private:
-Qx::Severity RevertError::deriveSeverity() const { return Qx::Err; }
-quint32 RevertError::deriveValue() const { return mType; }
-QString RevertError::derivePrimary() const { return ERR_STRINGS.value(mType); }
-QString RevertError::deriveSecondary() const { return mSpecific; }
-QString RevertError::deriveCaption() const { return CAPTION_REVERT_ERR; }
-
-//===============================================================================================================
-// InstallFoundation
-//===============================================================================================================
-
-//-Constructor---------------------------------------------------------------------------------------------------
-InstallFoundation::InstallFoundation(const QString& installPath) :
- mValid(false), // Path is invalid until proven otherwise
- mRootDirectory(installPath)
-{}
-
-//-Destructor------------------------------------------------------------------------------------------------
-//Public:
-InstallFoundation::~InstallFoundation() {}
-
-//Public:
-QString InstallFoundation::filePathToBackupPath(const QString& filePath)
-{
- return filePath + '.' + BACKUP_FILE_EXT;
-}
-
-//-Instance Functions--------------------------------------------------------------------------------------------
-//Private:
-bool InstallFoundation::containsAnyDataDoc(DataDoc::Type type, const QList& names) const
-{
- // Create identifier set of names
- QSet searchSet;
- for(const QString& docName : names)
- searchSet << DataDoc::Identifier(type, translateDocName(docName, type));
-
- // Cross reference with existing documents
- return mExistingDocuments.intersects(searchSet);
-}
-
-QList InstallFoundation::modifiedDataDocs(DataDoc::Type type) const
-{
- QList modList;
-
- for(const DataDoc::Identifier& dataDocId : mModifiedDocuments)
- if(dataDocId.docType() == type)
- modList.append(dataDocId.docName());
-
- return modList;
-}
-
-//Protected:
-void InstallFoundation::nullify()
-{
- mValid = false;
- mRootDirectory = QDir();
-}
-
-void InstallFoundation::softReset()
-{
- mRevertableFilePaths.clear();
- mModifiedDocuments.clear();
- mDeletedDocuments.clear();
- mLeasedDocuments.clear();
- mImportDetails.reset();
-}
-
-void InstallFoundation::declareValid(bool valid)
-{
- mValid = valid;
- if(!valid)
- nullify();
-}
-
-QString InstallFoundation::translateDocName(const QString& originalName, DataDoc::Type type) const
-{
- Q_UNUSED(type);
- return originalName;
-}
-
-void InstallFoundation::catalogueExistingDoc(DataDoc::Identifier existingDoc) { mExistingDocuments.insert(existingDoc); }
-
-Fe::DocHandlingError InstallFoundation::checkoutDataDocument(DataDoc* docToOpen, std::shared_ptr docReader)
-{
- // Error report to return
- Fe::DocHandlingError openReadError; // Defaults to no error
-
- // Check if lease is already out
- if(mLeasedDocuments.contains(docToOpen->identifier()))
- openReadError = Fe::DocHandlingError(*docToOpen, Fe::DocHandlingError::DocAlreadyOpen);
- else
- {
- // Read existing file if present and a reader was provided
- if(docReader && mExistingDocuments.contains(docToOpen->identifier()))
- openReadError = docReader->readInto();
-
- // Add lease to ledger if no error occurred while reading
- if(!openReadError.isValid())
- mLeasedDocuments.insert(docToOpen->identifier());
- }
-
- // Return opened document and status
- return openReadError;
-}
-
-Fe::DocHandlingError InstallFoundation::commitDataDocument(DataDoc* docToSave, std::shared_ptr docWriter)
-{
- 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)
- {
- QString docPath = docToSave->path();
- mRevertableFilePaths.append(docPath); // Correctly handles if doc ends up deleted
-
- // Backup
- if(QFile::exists(docPath))
- {
- QString backupPath = filePathToBackupPath(docPath);
-
- if(QFile::exists(backupPath) && QFileInfo(backupPath).isFile())
- {
- if(!QFile::remove(backupPath))
- return Fe::DocHandlingError(*docToSave, Fe::DocHandlingError::CantRemoveBackup);
- }
-
- if(!QFile::copy(docPath, backupPath))
- return Fe::DocHandlingError(*docToSave, Fe::DocHandlingError::CantCreateBackup);
- }
- }
-
- // Error State
- Fe::DocHandlingError commitError;
-
- // Handle modification
- if(!docToSave->isEmpty())
- {
- mModifiedDocuments.insert(id);
- if(wasDeleted)
- mDeletedDocuments.remove(id);
-
- commitError = docWriter->writeOutOf();
- ensureModifiable(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 commitError;
-}
-
-QList InstallFoundation::modifiedPlatforms() const { return modifiedDataDocs(DataDoc::Type::Platform); }
-QList InstallFoundation::modifiedPlaylists() const { return modifiedDataDocs(DataDoc::Type::Playlist);}
-
-//Public:
-bool InstallFoundation::isValid() const { return mValid; }
-QString InstallFoundation::path() const { return mRootDirectory.absolutePath(); }
-
-Qx::Error InstallFoundation::refreshExistingDocs(bool* changed)
-{
- QSet oldDocSet;
- oldDocSet.swap(mExistingDocuments);
- Qx::Error error = populateExistingDocs();
- if(changed)
- *changed = mExistingDocuments != oldDocSet;
- return error;
-}
-
-bool InstallFoundation::containsPlatform(const QString& name) const
-{
- return containsAnyDataDoc(DataDoc::Type::Platform, {name});
-}
-
-bool InstallFoundation::containsPlaylist(const QString& name) const
-{
- return containsAnyDataDoc(DataDoc::Type::Playlist, {name});
-}
-
-bool InstallFoundation::containsAnyPlatform(const QList& names) const
-{
- return containsAnyDataDoc(DataDoc::Type::Platform, names);
-}
-
-bool InstallFoundation::containsAnyPlaylist(const QList& names) const
-{
- return containsAnyDataDoc(DataDoc::Type::Playlist, names);
-}
-
-void InstallFoundation::addRevertableFile(const QString& filePath) { mRevertableFilePaths.append(filePath); }
-int InstallFoundation::revertQueueCount() const { return mRevertableFilePaths.size(); }
-
-int InstallFoundation::revertNextChange(RevertError& error, bool skipOnFail)
-{
- // Ensure error message is null
- error = RevertError();
-
- // Get operation count for return
- int operationsLeft = mRevertableFilePaths.size();
-
- // Delete new files and restore backups if present
- if(!mRevertableFilePaths.isEmpty())
- {
- QString filePath = mRevertableFilePaths.takeFirst();
- QString backupPath = filePathToBackupPath(filePath);
-
- if(QFile::exists(filePath) && !QFile::remove(filePath) && !skipOnFail)
- {
- error = RevertError(RevertError::FileWontDelete, filePath);
- return operationsLeft;
- }
-
- if(!QFile::exists(filePath) && QFile::exists(backupPath) && !QFile::rename(backupPath, filePath) && !skipOnFail)
- {
- error = RevertError(RevertError::FileWontRestore, backupPath);
- return operationsLeft;
- }
-
- // Decrement op count
- return operationsLeft - 1;
- }
-
- // Return 0 if all empty (shouldn't be reached if function is used correctly)
- return 0;
-}
-
-}
diff --git a/app/src/frontend/fe-installfoundation.h b/app/src/frontend/fe-installfoundation.h
deleted file mode 100644
index cc9e307..0000000
--- a/app/src/frontend/fe-installfoundation.h
+++ /dev/null
@@ -1,173 +0,0 @@
-#ifndef FE_INSTALLFOUNDATION_H
-#define FE_INSTALLFOUNDATION_H
-
-// Qt Includes
-#include
-
-// Project Includes
-#include "fe-data.h"
-
-namespace Fe
-{
-
-//-Enums----------------------------------------------------------------------------------------------------------
-enum class ImageMode {Copy, Reference, Link};
-
-class QX_ERROR_TYPE(RevertError, "Fe::RevertError", 1301)
-{
- friend class InstallFoundation;
-//-Class Enums-------------------------------------------------------------
-public:
- enum Type
- {
- NoError = 0,
- FileWontDelete = 1,
- FileWontRestore = 2
- };
-
-//-Class Variables-------------------------------------------------------------
-private:
- static inline const QHash ERR_STRINGS{
- {NoError, u""_s},
- {FileWontDelete, u"Cannot remove a file. It may need to be deleted manually."_s},
- {FileWontRestore, u"Cannot restore a file backup. It may need to be renamed manually.."_s}
- };
-
- static inline const QString CAPTION_REVERT_ERR = u"Error reverting changes"_s;
-
-//-Instance Variables-------------------------------------------------------------
-private:
- Type mType;
- QString mSpecific;
-
-//-Constructor-------------------------------------------------------------
-private:
- RevertError(Type t, const QString& s);
-
-public:
- RevertError();
-
-//-Instance Functions-------------------------------------------------------------
-public:
- bool isValid() const;
- Type type() const;
- QString specific() const;
-
-private:
- Qx::Severity deriveSeverity() const override;
- quint32 deriveValue() const override;
- QString derivePrimary() const override;
- QString deriveSecondary() const override;
- QString deriveCaption() const override;
-};
-
-class InstallFoundation
-{
-//-Class Structs------------------------------------------------------------------------------------------------------
-public:
- struct ImportDetails
- {
- UpdateOptions updateOptions;
- ImageMode imageMode;
- QString clifpPath;
- QList involvedPlatforms;
- QList involvedPlaylists;
- };
-
- struct ImageMap
- {
- QString sourcePath;
- QString destPath;
- };
-
-//-Class Variables-----------------------------------------------------------------------------------------------
-private:
- // Files
- static inline const QString BACKUP_FILE_EXT = u"fbk"_s;
-
-protected:
- // Files
- static inline const QString IMAGE_EXT = u"png"_s;
-
-public:
- // Base errors
- // TODO: This is unused, should it be in-use somewhere?
- static inline const QString ERR_UNSUPPORTED_FEATURE = u"A feature unsupported by the frontend was called upon!"_s;
-
- // Image Errors
- static inline const QString CAPTION_IMAGE_ERR = u"Error importing game image(s)"_s;
-
-//-Instance Variables--------------------------------------------------------------------------------------------
-private:
- // Validity
- bool mValid;
-
- // Files and directories
- QDir mRootDirectory;
-
- // Document tracking
- QSet mExistingDocuments;
- QSet mModifiedDocuments;
- QSet mDeletedDocuments;
- QSet mLeasedDocuments;
-
- // Backup/Deletion tracking
- QStringList mRevertableFilePaths;
-
-protected:
- // Import details
- std::unique_ptr mImportDetails;
-
-//-Constructor---------------------------------------------------------------------------------------------------
-public:
- InstallFoundation(const QString& installPath);
-
-//-Destructor-------------------------------------------------------------------------------------------------
-public:
- virtual ~InstallFoundation();
-
-//-Class Functions------------------------------------------------------------------------------------------------------
-private:
- static void ensureModifiable(const QString& filePath);
-
-public:
- static QString filePathToBackupPath(const QString& filePath);
-
-//-Instance Functions---------------------------------------------------------------------------------------------------------
-private:
- bool containsAnyDataDoc(DataDoc::Type type, const QList& names) const;
- QList modifiedDataDocs(DataDoc::Type type) const;
-
-protected:
- virtual void nullify();
- virtual void softReset();
- void declareValid(bool valid);
- virtual Qx::Error populateExistingDocs() = 0; // Stated redundantly again in Install to make it clear its part of the main interface
-
- virtual QString translateDocName(const QString& originalName, DataDoc::Type type) const;
- void catalogueExistingDoc(DataDoc::Identifier existingDoc);
-
- Fe::DocHandlingError checkoutDataDocument(DataDoc* docToOpen, std::shared_ptr docReader);
- Fe::DocHandlingError commitDataDocument(DataDoc* docToSave, std::shared_ptr docWriter);
-
- QList modifiedPlatforms() const;
- QList modifiedPlaylists() const;
-
-public:
- bool isValid() const;
- QString path() const;
-
- Qx::Error refreshExistingDocs(bool* changed = nullptr);
- bool containsPlatform(const QString& name) const;
- bool containsPlaylist(const QString& name) const;
- bool containsAnyPlatform(const QList& names) const;
- bool containsAnyPlaylist(const QList& names) const;
-
- void addRevertableFile(const QString& filePath);
- int revertQueueCount() const;
- int revertNextChange(RevertError& error, bool skipOnFail);
-};
-
-}
-
-#endif // FE_INSTALLFOUNDATION_H
diff --git a/app/src/import/backup.cpp b/app/src/import/backup.cpp
new file mode 100644
index 0000000..05e8ee1
--- /dev/null
+++ b/app/src/import/backup.cpp
@@ -0,0 +1,181 @@
+// Unit Includes
+#include "backup.h"
+
+// Qt Includes
+#include
+#include
+
+namespace Import
+{
+
+//===============================================================================================================
+// BackupError
+//===============================================================================================================
+
+//-Constructor-------------------------------------------------------------
+//Private:
+BackupError::BackupError(Type t, const QString& s) :
+ mType(t),
+ mSpecific(s)
+{}
+
+//Public:
+BackupError::BackupError() :
+ mType(NoError)
+{}
+
+//-Instance Functions-------------------------------------------------------------
+//Public:
+bool BackupError::isValid() const { return mType != NoError; }
+QString BackupError::specific() const { return mSpecific; }
+BackupError::Type BackupError::type() const { return mType; }
+
+//Private:
+Qx::Severity BackupError::deriveSeverity() const { return Qx::Err; }
+quint32 BackupError::deriveValue() const { return mType; }
+QString BackupError::derivePrimary() const { return ERR_STRINGS.value(mType); }
+QString BackupError::deriveSecondary() const { return mSpecific; }
+QString BackupError::deriveCaption() const { return CAPTION_REVERT_ERR; }
+
+//===============================================================================================================
+// BackupManager
+//===============================================================================================================
+
+//-Constructor-------------------------------------------------------------
+//Private:
+BackupManager::BackupManager() {}
+
+//-Class Functions-------------------------------------------------------------
+//Private:
+QString BackupManager::filePathToBackupPath(const QString& filePath)
+{
+ return filePath + '.' + BACKUP_FILE_EXT;
+}
+
+//Public:
+BackupManager* BackupManager::instance() { static BackupManager inst; return &inst; }
+
+//-Instance Functions-------------------------------------------------------------
+//Private:
+BackupError BackupManager::backup(const QString& path, bool (*fn)(const QString& a, const QString& b))
+{
+ // Prevent double+ backups (THIS IS CRITICAL, HENCE WHY A SET IS USED)
+ if(mRevertablePaths.contains(path))
+ return BackupError();
+
+ // Note revertable
+ mRevertablePaths.insert(path);
+
+ // Backup if exists
+ if(QFile::exists(path))
+ {
+ QString backupPath = filePathToBackupPath(path);
+
+ if(QFile::exists(backupPath) && QFileInfo(backupPath).isFile())
+ {
+ if(!QFile::remove(backupPath))
+ return BackupError(BackupError::FileWontDelete, backupPath);
+ }
+
+ if(!fn(path, backupPath))
+ return BackupError(BackupError::FileWontBackup, path);
+ }
+
+ return BackupError();
+}
+
+BackupError BackupManager::restore(QSet::const_iterator pathItr)
+{
+ Q_ASSERT(pathItr != mRevertablePaths.cend());
+
+ const QString path = *pathItr;
+ mRevertablePaths.erase(pathItr);
+ QString backupPath = filePathToBackupPath(path);
+
+ if(QFile::exists(path) && !QFile::remove(path))
+ return BackupError(BackupError::FileWontDelete, path);
+
+ if(!QFile::exists(path) && QFile::exists(backupPath) && !QFile::rename(backupPath, path))
+ return BackupError(BackupError::FileWontRestore, backupPath);
+
+ return BackupError();
+}
+
+//Public:
+BackupError BackupManager::backupCopy(const QString& path)
+{
+ return backup(path, [](const QString& a, const QString& b){ return QFile::copy(a, b); });
+}
+
+BackupError BackupManager::backupRename(const QString& path)
+{
+ return backup(path, [](const QString& a, const QString& b){ return QFile::rename(a, b); });
+}
+
+BackupError BackupManager::restore(const QString& path)
+{
+ auto store = mRevertablePaths.constFind(path);
+ if(store == mRevertablePaths.cend())
+ return BackupError();
+
+ return restore(store);
+}
+
+BackupError BackupManager::safeReplace(const QString& src, const QString& dst, bool symlink)
+{
+ // Maybe make sure destination folder exists here?
+
+ // Backup
+ QString backupPath = filePathToBackupPath(dst);
+ bool dstOccupied = QFile::exists(dst);
+ if(dstOccupied)
+ if(!QFile::rename(dst, backupPath)) // Temp backup
+ return BackupError(BackupError::FileWontBackup, dst);
+
+ // Replace
+ std::error_code replaceError;
+ if(symlink)
+ std::filesystem::create_symlink(src.toStdString(), dst.toStdString(), replaceError);
+ else
+ replaceError = QFile::copy(src, dst) ? std::error_code() : std::make_error_code(std::io_errc::stream);
+
+ // Restore on fail
+ if(replaceError)
+ {
+ if(dstOccupied)
+ QFile::rename(backupPath, dst);
+ return BackupError(BackupError::FileWontReplace, src);
+ }
+
+ // Remove backup immediately
+ if(dstOccupied)
+ QFile::remove(backupPath);
+ else // Mark new files (only) as revertible so that existing ones will remain in the event of a revert
+ mRevertablePaths.insert(dst);
+
+ return BackupError();
+}
+
+int BackupManager::revertQueueCount() const { return mRevertablePaths.size(); }
+
+int BackupManager::revertNextChange(BackupError& error, bool skipOnFail)
+{
+ // Ensure error message is null
+ error = BackupError();
+
+ // Delete new files and restore backups if present
+ if(!mRevertablePaths.isEmpty())
+ {
+ BackupError rErr = restore(mRevertablePaths.cbegin());
+ if(rErr && !skipOnFail)
+ error = rErr;
+
+ return mRevertablePaths.size();
+ }
+
+ // Return 0 if all empty (shouldn't be reached if function is used correctly)
+ qWarning("Reversion function called with no reverts left!");
+ return 0;
+}
+
+}
diff --git a/app/src/import/backup.h b/app/src/import/backup.h
new file mode 100644
index 0000000..8620966
--- /dev/null
+++ b/app/src/import/backup.h
@@ -0,0 +1,111 @@
+#ifndef BACKUP_H
+#define BACKUP_H
+
+// Qt Includes
+#include
+#include
+
+// Qx Includes
+#include
+
+using namespace Qt::StringLiterals;
+
+/* TODO: The approach, or at least the language around doing a full revert (i.e. emptying the revert
+ * queue) could use some touch-up.
+ */
+
+namespace Import
+{
+
+class QX_ERROR_TYPE(BackupError, "Lr::BackupError", 1301)
+{
+ friend class BackupManager;
+//-Class Enums-------------------------------------------------------------
+public:
+ enum Type
+ {
+ NoError,
+ FileWontDelete,
+ FileWontRestore,
+ FileWontBackup,
+ FileWontReplace
+ };
+
+//-Class Variables-------------------------------------------------------------
+private:
+ static inline const QHash ERR_STRINGS{
+ {NoError, u""_s},
+ {FileWontDelete, u"Cannot remove a file. It may need to be deleted manually."_s},
+ {FileWontRestore, u"Cannot restore a file backup. It may need to be renamed manually.."_s},
+ {FileWontBackup, u"Cannot backup file."_s},
+ {FileWontReplace, u"A file that was part of a safe replace operation could not be transfered."_s}
+ };
+
+ static inline const QString CAPTION_REVERT_ERR = u"Error managing backups"_s;
+
+//-Instance Variables-------------------------------------------------------------
+private:
+ Type mType;
+ QString mSpecific;
+
+//-Constructor-------------------------------------------------------------
+private:
+ BackupError(Type t, const QString& s);
+
+public:
+ BackupError();
+
+//-Instance Functions-------------------------------------------------------------
+public:
+ bool isValid() const;
+ Type type() const;
+ QString specific() const;
+
+private:
+ Qx::Severity deriveSeverity() const override;
+ quint32 deriveValue() const override;
+ QString derivePrimary() const override;
+ QString deriveSecondary() const override;
+ QString deriveCaption() const override;
+};
+
+class BackupManager
+{
+//-Class Variables-----------------------------------------------------------------------------------------------
+private:
+ // Files
+ static inline const QString BACKUP_FILE_EXT = u"fbk"_s;
+
+//-Instance Variables-------------------------------------------------------------
+private:
+ QSet mRevertablePaths;
+
+//-Constructor-------------------------------------------------------------
+private:
+ BackupManager();
+
+//-Class Functions-------------------------------------------------------------
+private:
+ static QString filePathToBackupPath(const QString& filePath);
+
+public:
+ static BackupManager* instance();
+
+//-Instance Functions-------------------------------------------------------------
+private:
+ BackupError backup(const QString& path, bool (*fn)(const QString& a, const QString& b));
+ BackupError restore(QSet::const_iterator pathItr);
+
+public:
+ BackupError backupCopy(const QString& path);
+ BackupError backupRename(const QString& path);
+ BackupError restore(const QString& path);
+ BackupError safeReplace(const QString& src, const QString& dst, bool symlink);
+
+ int revertQueueCount() const;
+ int revertNextChange(BackupError& error, bool skipOnFail);
+};
+
+}
+
+#endif // BACKUP_H
diff --git a/app/src/import/details.cpp b/app/src/import/details.cpp
new file mode 100644
index 0000000..4df090f
--- /dev/null
+++ b/app/src/import/details.cpp
@@ -0,0 +1,23 @@
+// Unit Includes
+#include "details.h"
+
+namespace Import
+{
+
+//===============================================================================================================
+// Details
+//===============================================================================================================
+
+//-Class Variables--------------------------------------------------------------------------------------------
+//Private:
+constinit std::optional Details::mCurrent = std::nullopt;
+
+//-Class Functions--------------------------------------------------------------------------------------------
+//Public:
+Details Details::current() { Q_ASSERT(mCurrent); return mCurrent.value(); }
+
+//Private:
+void Details::setCurrent(const Details& details) { Q_ASSERT(!mCurrent); mCurrent = details; }
+void Details::clearCurrent() { mCurrent = std::nullopt; }
+
+}
diff --git a/app/src/import/details.h b/app/src/import/details.h
new file mode 100644
index 0000000..39e2231
--- /dev/null
+++ b/app/src/import/details.h
@@ -0,0 +1,34 @@
+#ifndef IMPORT_DETAILS_H
+#define IMPORT_DETAILS_H
+
+/* Although somewhat redundant with settings.h, this is a collection of
+ * Import related info that is specifically collected to be shared with
+ * Installs
+ */
+
+// Project Includes
+#include "import/settings.h"
+
+namespace Import
+{
+
+struct Details
+{
+ friend class Worker;
+ Import::UpdateOptions updateOptions;
+ Import::ImageMode imageMode;
+ QString clifpPath;
+ QList involvedPlatforms;
+ QList involvedPlaylists;
+
+ static Details current();
+
+private:
+ static constinit std::optional mCurrent;
+ static void setCurrent(const Details& details);
+ static void clearCurrent();
+};
+
+}
+
+#endif // IMPORT_DETAILS_H
diff --git a/app/src/import/properties.cpp b/app/src/import/properties.cpp
new file mode 100644
index 0000000..4c9b3d9
--- /dev/null
+++ b/app/src/import/properties.cpp
@@ -0,0 +1,151 @@
+// Unit Include
+#include "properties.h"
+
+// Qx Includes
+#include
+
+// libfp Includes
+#include
+
+// Project Includes
+#include "launcher/interface/lr-install-interface.h"
+
+namespace Import
+{
+
+//===============================================================================================================
+// Properties
+//===============================================================================================================
+
+//-Constructor-------------------------------------------------------------
+//Public:
+Properties::Properties() :
+ mHasLinkPerms(testForLinkPermissions()),
+ mLauncher(nullptr),
+ mFlashpoint(nullptr)
+{
+ mLauncherReady.setBinding([this]{ return mLauncher.value() && mLauncher->isValid(); });
+ mFlashpointReady.setBinding([this]{ return mFlashpoint.value() && mFlashpoint->isValid(); });
+ mBothTargetsReady.setBinding([this]{ return mLauncherReady && mFlashpointReady; });
+ mBothTargetsReady.addLifetimeNotifier([this]{
+ if(mBothTargetsReady)
+ gatherTargetData();
+ else
+ {
+ // Clear out selection lists
+ mPlatforms = QList();
+ mPlaylists = QList();
+ }
+ });
+ mFlashpointTargetSeries.setBinding([this]{ return mFlashpointReady && installMatchesTargetSeries(*mFlashpoint.value()); });
+ mImageModeOrder.setBinding([this]{
+ /* Even though technically we only need the launcher, check for both installs to prevent the selection
+ * from moving until its section is available
+ */
+ static QList defOrder{Import::ImageMode::Link, Import::ImageMode::Reference, Import::ImageMode::Copy};
+ bool def = !mBothTargetsReady;
+ auto order = def ? defOrder : mLauncher->preferredImageModeOrder();
+ if(!mHasLinkPerms)
+ order.removeAll(Import::ImageMode::Link);
+
+ return order;
+ });
+ mImageDownloadable.setBinding([this]{
+ return mFlashpointReady && mFlashpoint->preferences().onDemandImages;
+ } );
+ mLauncherInfo.setBinding([this]{ return mLauncherReady ? mLauncher->name() + ' ' + mLauncher->versionString() : QString(); });
+ mFlashpointInfo.setBinding([this]{ return mFlashpointReady ? mFlashpoint->versionInfo()->fullString() : QString(); });
+ mTagMap.setBinding([this]{ return mFlashpointReady ? mFlashpoint->database()->tags() : QMap(); });
+}
+
+//-Class Functions-------------------------------------------------------------
+//Private:
+bool Properties::testForLinkPermissions()
+{
+ QTemporaryDir testLinkDir;
+ if(testLinkDir.isValid())
+ {
+ QFile testLinkTarget(testLinkDir.filePath(u"linktarget.tmp"_s));
+
+ if(testLinkTarget.open(QIODevice::WriteOnly))
+ {
+ testLinkTarget.close();
+ std::error_code symlinkError;
+ std::filesystem::create_symlink(testLinkTarget.fileName().toStdString(), testLinkDir.filePath(u"testlink.tmp"_s).toStdString(), symlinkError);
+
+ if(!symlinkError)
+ return true;
+ }
+ }
+
+ // Default
+ return false;
+}
+
+bool Properties::installMatchesTargetSeries(const Fp::Install& fpInstall)
+{
+ Qx::VersionNumber fpVersion = fpInstall.versionInfo()->version();
+ return TARGET_FP_VERSION_PREFIX.isPrefixOf(fpVersion) ||
+ TARGET_FP_VERSION_PREFIX.normalized() == fpVersion; // Accounts for if FP doesn't use a trailing zero for major releases
+}
+
+//-Instance Functions-------------------------------------------------------------
+//Private:
+void Properties::gatherTargetData()
+{
+ // IO Error check instance
+ Qx::Error existingCheck;
+
+ // Get list of existing platforms and playlists
+ existingCheck = mLauncher->refreshExistingDocs();
+
+ // IO Error Check
+ if(existingCheck.isValid())
+ {
+ Qx::postBlockingError(existingCheck);
+ mLauncher = nullptr;
+ return;
+ }
+
+ /* We set the platform/playlist properties here instead of using a binding because gatherLauncherData()
+ * might need to be called in contexts where there is no trivial way to cause the binding to re-evaluate
+ * without adding a hacky bool property specifically for that purpose.
+ */
+ QList plats;
+ for(const QString& p : mFlashpoint->database()->platformNames())
+ plats.append({.name = p, .existing = mLauncher->containsPlatform(p)});
+
+ QList plays;
+ for(const QString& p : mFlashpoint->playlistManager()->playlistTitles())
+ plays.append({.name = p, .existing = mLauncher->containsPlaylist(p)});
+
+ mPlatforms.setValue(std::move(plats));
+ mPlaylists.setValue(std::move(plays));
+}
+
+//Public:
+bool Properties::hasLinkPermissions() const { return mHasLinkPerms; }
+bool Properties::isLauncherReady() const { return mLauncherReady; }
+bool Properties::isFlashpointReady() const { return mFlashpointReady; }
+bool Properties::isBothTargetsReady() const { return mBothTargetsReady; }
+bool Properties::isFlashpointTargetSeries() const { return mFlashpointTargetSeries; }
+const Qx::Bindable> Properties::bindableImageModeOrder() const { return mImageModeOrder; }
+QList Properties::imageModeOrder() const { return mImageModeOrder; }
+bool Properties::isImageDownloadable() const { return mImageDownloadable; }
+QString Properties::launcherInfo() const { return mLauncherInfo; }
+QString Properties::flashpointInfo() const { return mFlashpointInfo; }
+const Qx::Bindable> Properties::bindableTagMap() const { return mTagMap; }
+QMap Properties::tagMap() const { return mTagMap; }
+const Qx::Bindable> Properties::bindablePlatforms() const { return mPlatforms; }
+QList Properties::platforms() const { return mPlatforms; }
+const Qx::Bindable> Properties::bindablePlaylists() const { return mPlaylists; }
+QList Properties::playlists() const { return mPlaylists; }
+
+void Properties::setLauncher(std::unique_ptr&& launcher) { mLauncher = std::move(launcher); }
+void Properties::setFlashpoint(std::unique_ptr&& flashpoint) { mFlashpoint = std::move(flashpoint); }
+void Properties::refreshInstallData() { gatherTargetData(); }
+
+Lr::IInstall* Properties::launcher() { Q_ASSERT(*mLauncher); return (*mLauncher).get(); }
+Fp::Install* Properties::flashpoint() { Q_ASSERT(*mFlashpoint); return (*mFlashpoint).get(); };
+
+}
diff --git a/app/src/import/properties.h b/app/src/import/properties.h
new file mode 100644
index 0000000..b45aae8
--- /dev/null
+++ b/app/src/import/properties.h
@@ -0,0 +1,90 @@
+#ifndef IMPORT_PROPERTIES_H
+#define IMPORT_PROPERTIES_H
+
+// Qx Includes
+#include
+
+// Qx Includes
+#include
+
+// libfp Includes
+#include
+
+// Project Includes
+#include "import/settings.h"
+#include "project_vars.h"
+
+namespace Lr { class IInstall; }
+namespace Fp { class Install; }
+
+namespace Import
+{
+
+class Properties
+{
+//-Class Variables---------------------------------------------------------------
+private:
+ // Flashpoint version check
+ static inline const Qx::VersionNumber TARGET_FP_VERSION_PREFIX = Qx::VersionNumber::fromString(PROJECT_TARGET_FP_VER_PFX_STR);
+
+//-Instance Variables-------------------------------------------------------------
+private:
+ bool mHasLinkPerms;
+ Qx::Property> mLauncher;
+ Qx::Property> mFlashpoint;
+ Qx::Property mLauncherReady;
+ Qx::Property mFlashpointReady;
+ Qx::Property mBothTargetsReady;
+ Qx::Property mFlashpointTargetSeries;
+ Qx::Property> mImageModeOrder;
+ Qx::Property mImageDownloadable;
+ Qx::Property mLauncherInfo;
+ Qx::Property mFlashpointInfo;
+ Qx::Property> mTagMap;
+ Qx::Property> mPlatforms;
+ Qx::Property> mPlaylists;
+
+//-Constructor-------------------------------------------------------------
+public:
+ Properties();
+
+//-Class Functions-------------------------------------------------------------
+private:
+ static bool testForLinkPermissions();
+
+public:
+ static bool installMatchesTargetSeries(const Fp::Install& fpInstall);
+
+//-Instance Functions-------------------------------------------------------------
+private:
+ void gatherTargetData();
+
+public:
+ bool hasLinkPermissions() const;
+ bool isLauncherReady() const;
+ bool isFlashpointReady() const;
+ bool isBothTargetsReady() const;
+ bool isFlashpointTargetSeries() const;
+ const Qx::Bindable> bindableImageModeOrder() const;
+ QList imageModeOrder() const;
+ bool isImageDownloadable() const;
+ QString launcherInfo() const;
+ QString flashpointInfo() const;
+ const Qx::Bindable> bindableTagMap() const;
+ QMap tagMap() const;
+ const Qx::Bindable> bindablePlatforms() const;
+ QList platforms() const;
+ const Qx::Bindable> bindablePlaylists() const;
+ QList playlists() const;
+
+ void setLauncher(std::unique_ptr&& launcher);
+ void setFlashpoint(std::unique_ptr&& flashpoint);
+ void refreshInstallData();
+
+ Lr::IInstall* launcher();
+ Fp::Install* flashpoint();
+};
+
+}
+
+#endif // IMPORT_PROPERTIES_H
diff --git a/app/src/import/settings.cpp b/app/src/import/settings.cpp
new file mode 100644
index 0000000..e2269e6
--- /dev/null
+++ b/app/src/import/settings.cpp
@@ -0,0 +1,27 @@
+// Unit Include
+#include "settings.h"
+
+namespace Import
+{
+
+//===============================================================================================================
+// ImageSources
+//===============================================================================================================
+
+//-Constructor--------------------------------------------------------------------------------------------------------
+//Public:
+ImagePaths::ImagePaths() {}
+ImagePaths::ImagePaths(const QString& logoPath, const QString& screenshotPath) :
+ mLogoPath(logoPath),
+ mScreenshotPath(screenshotPath)
+{}
+
+//-Instance Functions--------------------------------------------------------------------------------------------------
+//Public:
+bool ImagePaths::isNull() const { return mLogoPath.isEmpty() && mScreenshotPath.isEmpty(); }
+QString ImagePaths::logoPath() const { return mLogoPath; }
+QString ImagePaths::screenshotPath() const { return mScreenshotPath; }
+void ImagePaths::setLogoPath(const QString& path) { mLogoPath = path; }
+void ImagePaths::setScreenshotPath(const QString& path) { mScreenshotPath = path; }
+
+}
diff --git a/app/src/import/settings.h b/app/src/import/settings.h
new file mode 100644
index 0000000..f8c16d7
--- /dev/null
+++ b/app/src/import/settings.h
@@ -0,0 +1,71 @@
+#ifndef IMPORT_SETTINGS_H
+#define IMPORT_SETTINGS_H
+
+// Qt Includes
+#include
+#include
+
+// libfp Includes
+#include
+
+namespace Import
+{
+
+// Enums
+enum class Install{ Launcher, Flashpoint };
+enum class UpdateMode {OnlyNew, NewAndExisting};
+enum class ImageMode {Copy, Reference, Link};
+enum class PlaylistGameMode {SelectedPlatform, ForceAll};
+
+// Structs
+struct Importee
+{
+ QString name;
+ bool existing = false;
+};
+
+struct Selections
+{
+ QStringList platforms;
+ QStringList playlists;
+};
+
+struct UpdateOptions
+{
+ UpdateMode importMode;
+ bool removeObsolete;
+};
+
+struct OptionSet
+{
+ UpdateOptions updateOptions;
+ ImageMode imageMode;
+ bool downloadImages;
+ PlaylistGameMode playlistMode;
+ Fp::Db::InclusionOptions inclusionOptions;
+};
+
+class ImagePaths
+{
+//-Instance Members--------------------------------------------------------------------------------------------------
+private:
+ QString mLogoPath;
+ QString mScreenshotPath;
+
+//-Constructor--------------------------------------------------------------------------------------------------------
+public:
+ ImagePaths();
+ ImagePaths(const QString& logoPath, const QString& screenshotPath);
+
+//-Instance Functions--------------------------------------------------------------------------------------------------
+public:
+ bool isNull() const;
+ QString logoPath() const;
+ QString screenshotPath() const;
+ void setLogoPath(const QString& path);
+ void setScreenshotPath(const QString& path);
+};
+
+}
+
+#endif // IMPORT_SETTINGS_H
diff --git a/app/src/import-worker.cpp b/app/src/import/worker.cpp
similarity index 77%
rename from app/src/import-worker.cpp
rename to app/src/import/worker.cpp
index 8207e55..fedf4a1 100644
--- a/app/src/import-worker.cpp
+++ b/app/src/import/worker.cpp
@@ -1,5 +1,5 @@
// Unit Include
-#include "import-worker.h"
+#include "worker.h"
// Standard Library Includes
#include
@@ -12,8 +12,12 @@
#include
// Project Includes
-#include "clifp.h"
+#include "kernel/clifp.h"
+#include "import/details.h"
+#include "import/backup.h"
+namespace Import
+{
//===============================================================================================================
// ImageTransferError
//===============================================================================================================
@@ -55,25 +59,27 @@ QString ImageTransferError::deriveDetails() const
QString ImageTransferError::deriveCaption() const { return CAPTION_IMAGE_ERR; }
//===============================================================================================================
-// IMPORT WORKER
+// Worker
//===============================================================================================================
//-Constructor---------------------------------------------------------------------------------------------------
-ImportWorker::ImportWorker(std::shared_ptr fpInstallForWork,
- std::shared_ptr feInstallForWork,
- ImportSelections importSelections,
- OptionSet optionSet) :
- mFlashpointInstall(fpInstallForWork),
- mFrontendInstall(feInstallForWork),
+//Public:
+Worker::Worker(Fp::Install* flashpoint, Lr::IInstall* launcher, Selections importSelections, OptionSet optionSet) :
+ mFlashpointInstall(flashpoint),
+ mLauncherInstall(launcher),
mImportSelections(importSelections),
mOptionSet(optionSet),
mCurrentProgress(0),
mCanceled(false)
{}
+//-Destructor---------------------------------------------------------------------------------------------------
+//Public:
+Worker::~Worker() { Details::clearCurrent(); }
+
//-Instance Functions--------------------------------------------------------------------------------------------
//Private:
-Qx::ProgressGroup* ImportWorker::initializeProgressGroup(const QString& groupName, quint64 weight)
+Qx::ProgressGroup* Worker::initializeProgressGroup(const QString& groupName, quint64 weight)
{
Qx::ProgressGroup* pg = mProgressManager.addGroup(groupName);
pg->setWeight(weight);
@@ -82,7 +88,7 @@ Qx::ProgressGroup* ImportWorker::initializeProgressGroup(const QString& groupNam
return pg;
}
-Qx::Error ImportWorker::preloadPlaylists(QList& targetPlaylists)
+Qx::Error Worker::preloadPlaylists(QList& targetPlaylists)
{
// Reset playlists
targetPlaylists.clear();
@@ -103,7 +109,7 @@ Qx::Error ImportWorker::preloadPlaylists(QList& targetPlaylists)
return Qx::Error();
}
-QList ImportWorker::getPlaylistSpecificGameIds(const QList& playlists)
+QList Worker::getPlaylistSpecificGameIds(const QList& playlists)
{
QList playlistSpecGameIds;
@@ -114,7 +120,7 @@ QList ImportWorker::getPlaylistSpecificGameIds(const QList&
return playlistSpecGameIds;
}
-ImageTransferError ImportWorker::transferImage(bool symlink, QString sourcePath, QString destinationPath)
+ImageTransferError Worker::transferImage(bool symlink, QString sourcePath, QString destinationPath)
{
/* TODO: Ideally the error handlers here don't need to include "Retry?" text and therefore need less use of QString::arg(); however, this largely
* would require use of a button labeled "Ignore All" so that the errors could presented as is without a prompt, with the prompt being inferred
@@ -143,6 +149,7 @@ ImageTransferError ImportWorker::transferImage(bool symlink, QString sourcePath,
QString sourceChecksum;
QString destinationChecksum;
+ // TODO: Probably better to just byte-wise compare
if(!Qx::calculateFileChecksum(sourceChecksum, source, QCryptographicHash::Md5).isFailure() &&
!Qx::calculateFileChecksum(destinationChecksum, destination, QCryptographicHash::Md5).isFailure() &&
sourceChecksum.compare(destinationChecksum, Qt::CaseInsensitive) == 0)
@@ -155,56 +162,30 @@ ImageTransferError ImportWorker::transferImage(bool symlink, QString sourcePath,
if(!destinationDir.mkpath(u"."_s))
return ImageTransferError(ImageTransferError::CantCreateDirectory, QString(), destinationDir.absolutePath());
- // Determine backup path
- QString backupPath = Fe::Install::filePathToBackupPath(destinationInfo.absoluteFilePath());
-
- // Temporarily backup image if it already exists (also acts as deletion marking in case images for the title were removed in an update)
- if(destinationOccupied)
- if(!QFile::rename(destinationPath, backupPath)) // Temp backup
- return ImageTransferError(ImageTransferError::ImageWontBackup, QString(), destinationPath);
-
- // Linking error tracker
- std::error_code linkError;
-
- // Handle transfer
- if(symlink)
+ // Transfer image
+ BackupError bErr = BackupManager::instance()->safeReplace(sourcePath, destinationPath, symlink);
+ if(bErr)
{
- std::filesystem::create_symlink(sourcePath.toStdString(), destinationPath.toStdString(), linkError);
- if(linkError)
- {
- QFile::rename(backupPath, destinationPath); // Restore Backup
- return ImageTransferError(ImageTransferError::ImageWontLink, sourcePath, destinationPath);
- }
- else if(QFile::exists(backupPath))
- QFile::remove(backupPath);
- else
- mFrontendInstall->addRevertableFile(destinationPath); // Only queue image to be removed on failure if its new, so existing images aren't deleted on revert
- }
- else
- {
- if(!QFile::copy(sourcePath, destinationPath))
- {
- QFile::rename(backupPath, destinationPath); // Restore Backup
- return ImageTransferError(ImageTransferError::ImageWontCopy, sourcePath, destinationPath);
- }
- else if(QFile::exists(backupPath))
- QFile::remove(backupPath);
+ if(bErr.type() == BackupError::FileWontBackup)
+ return ImageTransferError(ImageTransferError::ImageWontBackup, QString(), destinationPath);
+ else if(bErr.type() == BackupError::FileWontReplace)
+ return ImageTransferError(symlink ? ImageTransferError::ImageWontLink : ImageTransferError::ImageWontCopy, QString(), destinationPath);
else
- mFrontendInstall->addRevertableFile(destinationPath); // Only queue image to be removed on failure if its new, so existing images aren't deleted on revert
+ qFatal("Unhandled image transfer error type.");
}
// Return null error on success
return ImageTransferError();
}
-bool ImportWorker::performImageJobs(const QList& jobs, bool symlink, Qx::ProgressGroup* pg)
+bool Worker::performImageJobs(const QList& jobs, bool symlink, Qx::ProgressGroup* pg)
{
// Setup for image transfers
ImageTransferError imageTransferError; // Error return reference
*mBlockingErrorResponse = QMessageBox::NoToAll; // Default to choice "NoToAll" in case the signal is not correctly connected using Qt::BlockingQueuedConnection
bool ignoreAllTransferErrors = false; // NoToAll response tracker
- for(const Fe::Install::ImageMap& imageJob : jobs)
+ for(const ImageMap& imageJob : jobs)
{
while((imageTransferError = transferImage(symlink, imageJob.sourcePath, imageJob.destPath)).isValid() && !ignoreAllTransferErrors)
{
@@ -229,7 +210,7 @@ bool ImportWorker::performImageJobs(const QList& jobs, bo
return true;
}
-ImportWorker::ImportResult ImportWorker::processPlatformGames(Qx::Error& errorReport, std::unique_ptr& platformDoc, Fp::Db::QueryBuffer& gameQueryResult)
+Worker::Result Worker::processPlatformGames(Qx::Error& errorReport, std::unique_ptr& platformDoc, Fp::Db::QueryBuffer& gameQueryResult)
{
const Fp::Toolkit* tk = mFlashpointInstall->toolkit();
@@ -276,11 +257,14 @@ ImportWorker::ImportResult ImportWorker::processPlatformGames(Qx::Error& errorRe
// Get image information
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();
QString checkedScreenshotPath = (ssLocalInfo.exists() || mOptionSet.downloadImages) ? ssLocalInfo.absoluteFilePath() : QString();
- platformDoc->addSet(builtSet, Fe::ImageSources(checkedLogoPath, checkedScreenshotPath));
+ Import::ImagePaths imagePaths(checkedLogoPath, checkedScreenshotPath);
+ ImageMap logoMap{.sourcePath = imagePaths.logoPath(), .destPath = ""};
+ ImageMap screenshotMap{.sourcePath = imagePaths.screenshotPath(), .destPath = ""};
+
+ // Add set to doc
+ platformDoc->addSet(builtSet, imagePaths);
// Add ID to imported game cache
mImportedGameIdsCache.insert(builtGame.id());
@@ -305,13 +289,20 @@ ImportWorker::ImportResult ImportWorker::processPlatformGames(Qx::Error& errorRe
mProgressManager.group(Pg::ImageDownload)->decrementMaximum(); // Already exists, remove download step from progress bar
}
- // Handle image transfer progress
- if(mOptionSet.imageMode == Fe::ImageMode::Copy || mOptionSet.imageMode == Fe::ImageMode::Link)
+ // Handle image transfer
+ if(mOptionSet.imageMode == ImageMode::Copy || mOptionSet.imageMode == ImageMode::Link)
{
- // Adjust progress if images aren't available
- if(checkedLogoPath.isEmpty())
+ logoMap.destPath = imagePaths.logoPath();
+ screenshotMap.destPath = imagePaths.screenshotPath();
+
+ if(!logoMap.destPath.isEmpty())
+ mImageTransferJobs.append(logoMap);
+ else
mProgressManager.group(Pg::ImageTransfer)->decrementMaximum(); // Can't transfer image that doesn't/won't exist
- if(checkedScreenshotPath.isEmpty())
+
+ if(!screenshotMap.destPath.isEmpty())
+ mImageTransferJobs.append(screenshotMap);
+ else
mProgressManager.group(Pg::ImageTransfer)->decrementMaximum(); // Can't transfer image that doesn't/won't exist
}
@@ -330,7 +321,7 @@ ImportWorker::ImportResult ImportWorker::processPlatformGames(Qx::Error& errorRe
return Successful;
}
-void ImportWorker::cullUnimportedPlaylistGames(QList& playlists)
+void Worker::cullUnimportedPlaylistGames(QList& playlists)
{
const auto& idCache = mImportedGameIdsCache;
for(auto& pl : playlists)
@@ -341,7 +332,7 @@ void ImportWorker::cullUnimportedPlaylistGames(QList& playlists)
}
}
-ImportWorker::ImportResult ImportWorker::preloadAddApps(Qx::Error& errorReport, Fp::Db::QueryBuffer& addAppQuery)
+Worker::Result Worker::preloadAddApps(Qx::Error& errorReport, Fp::Db::QueryBuffer& addAppQuery)
{
mAddAppsCache.reserve(addAppQuery.size);
for(int i = 0; i < addAppQuery.size; i++)
@@ -380,25 +371,25 @@ ImportWorker::ImportResult ImportWorker::preloadAddApps(Qx::Error& errorReport,
return Successful;
}
-ImportWorker::ImportResult ImportWorker::processGames(Qx::Error& errorReport, QList& primary, QList& playlistSpecific)
+Worker::Result Worker::processGames(Qx::Error& errorReport, QList& primary, QList& playlistSpecific)
{
// Status tracking
- ImportResult platformImportStatus;
+ Result platformImportStatus;
// Track total platforms that have been handled
qsizetype remainingPlatforms = primary.size() + playlistSpecific.size();
// Use lambda to handle both lists due to major overlap
- auto platformsHandler = [&remainingPlatforms, &errorReport, this](QList& platformQueryResults, QString label) -> ImportResult {
- ImportResult result;
+ auto platformsHandler = [&remainingPlatforms, &errorReport, this](QList& platformQueryResults, QString label) -> Result {
+ Result result;
for(int i = 0; i < platformQueryResults.size(); i++)
{
Fp::Db::QueryBuffer& currentQueryResult = platformQueryResults[i];
- // Open frontend platform doc
- std::unique_ptr currentPlatformDoc;
- Fe::DocHandlingError platformReadError = mFrontendInstall->checkoutPlatformDoc(currentPlatformDoc, currentQueryResult.source);
+ // Open launcher platform doc
+ std::unique_ptr currentPlatformDoc;
+ Lr::DocHandlingError platformReadError = mLauncherInstall->checkoutPlatformDoc(currentPlatformDoc, currentQueryResult.source);
// Stop import if error occurred
if(platformReadError.isValid())
@@ -416,16 +407,9 @@ ImportWorker::ImportResult ImportWorker::processGames(Qx::Error& errorReport, QL
//---Finalize document----------------------------------
currentPlatformDoc->finalize();
- // Check for internal doc errors
- if(currentPlatformDoc->hasError())
- {
- errorReport = currentPlatformDoc->error();
- return Failed;
- }
-
// Forfeit document lease and save it
- Fe::DocHandlingError saveError;
- if((saveError = mFrontendInstall->commitPlatformDoc(std::move(currentPlatformDoc))).isValid())
+ Lr::DocHandlingError saveError;
+ if((saveError = mLauncherInstall->commitPlatformDoc(std::move(currentPlatformDoc))).isValid())
{
errorReport = saveError;
return Failed;
@@ -453,16 +437,16 @@ ImportWorker::ImportResult ImportWorker::processGames(Qx::Error& errorReport, QL
return Successful;
}
-ImportWorker::ImportResult ImportWorker::processPlaylists(Qx::Error& errorReport, const QList& playlists)
+Worker::Result Worker::processPlaylists(Qx::Error& errorReport, const QList& playlists)
{
for(const auto& currentPlaylist : playlists)
{
// Update progress dialog label
emit progressStepChanged(STEP_IMPORTING_PLAYLISTS.arg(currentPlaylist.title()));
- // Open frontend playlist doc
- std::unique_ptr currentPlaylistDoc;
- Fe::DocHandlingError playlistReadError = mFrontendInstall->checkoutPlaylistDoc(currentPlaylistDoc, currentPlaylist.title());
+ // Open launcher playlist doc
+ std::unique_ptr currentPlaylistDoc;
+ Lr::DocHandlingError playlistReadError = mLauncherInstall->checkoutPlaylistDoc(currentPlaylistDoc, currentPlaylist.title());
// Stop import if error occurred
if(playlistReadError.isValid())
@@ -477,16 +461,9 @@ ImportWorker::ImportResult ImportWorker::processPlaylists(Qx::Error& errorReport
// Finalize document
currentPlaylistDoc->finalize();
- // Check for internal doc errors
- if(currentPlaylistDoc->hasError())
- {
- errorReport = currentPlaylistDoc->error();
- return Failed;
- }
-
// Forfeit document lease and save it
- Fe::DocHandlingError saveError;
- if((saveError = mFrontendInstall->commitPlaylistDoc(std::move(currentPlaylistDoc))).isValid())
+ Lr::DocHandlingError saveError;
+ if((saveError = mLauncherInstall->commitPlaylistDoc(std::move(currentPlaylistDoc))).isValid())
{
errorReport = saveError;
return Failed;
@@ -507,7 +484,7 @@ ImportWorker::ImportResult ImportWorker::processPlaylists(Qx::Error& errorReport
return Successful;
}
-ImportWorker::ImportResult ImportWorker::processImages(Qx::Error& errorReport)
+Worker::Result Worker::processImages(Qx::Error& errorReport)
{
//-Image Download---------------------------------------------------------------------------------
if(mOptionSet.downloadImages && mImageDownloadManager.hasTasks())
@@ -528,8 +505,8 @@ ImportWorker::ImportResult ImportWorker::processImages(Qx::Error& errorReport)
*ignore = *mBlockingErrorResponse == QMessageBox::Yes;
});
- connect(&mImageDownloadManager, &Qx::SyncDownloadManager::authenticationRequired, this, &ImportWorker::authenticationRequired);
- connect(&mImageDownloadManager, &Qx::SyncDownloadManager::proxyAuthenticationRequired, this, &ImportWorker::authenticationRequired);
+ connect(&mImageDownloadManager, &Qx::SyncDownloadManager::authenticationRequired, this, &Worker::authenticationRequired);
+ connect(&mImageDownloadManager, &Qx::SyncDownloadManager::proxyAuthenticationRequired, this, &Worker::authenticationRequired);
connect(&mImageDownloadManager, &Qx::SyncDownloadManager::downloadFinished, this, [this]() { // clazy:exclude=lambda-in-connect
mProgressManager.group(Pg::ImageDownload)->incrementValue();
@@ -558,17 +535,8 @@ ImportWorker::ImportResult ImportWorker::processImages(Qx::Error& errorReport)
// Update progress dialog label
emit progressStepChanged(STEP_IMPORTING_IMAGES);
- // Provide frontend with bulk reference locations and acquire any transfer tasks
- QList imageTransferJobs;
- Fe::ImageSources bulkSources;
- if(mOptionSet.imageMode == Fe::ImageMode::Reference)
- {
- bulkSources.setLogoPath(QDir::toNativeSeparators(mFlashpointInstall->entryLogosDirectory().absolutePath()));
- bulkSources.setScreenshotPath(QDir::toNativeSeparators(mFlashpointInstall->entryScreenshotsDirectory().absolutePath()));
- }
-
- Qx::Error imageExchangeError = mFrontendInstall->preImageProcessing(imageTransferJobs, bulkSources);
-
+ // Notify of step
+ Qx::Error imageExchangeError = mLauncherInstall->preImageProcessing();
if(imageExchangeError.isValid())
{
// Emit import failure
@@ -576,43 +544,54 @@ ImportWorker::ImportResult ImportWorker::processImages(Qx::Error& errorReport)
return Failed;
}
+ // Provide launcher with bulk reference locations
+ if(mOptionSet.imageMode == ImageMode::Reference)
+ {
+ Import::ImagePaths bulkSources(QDir::toNativeSeparators(mFlashpointInstall->entryLogosDirectory().absolutePath()),
+ QDir::toNativeSeparators(mFlashpointInstall->entryScreenshotsDirectory().absolutePath()));
+
+ mLauncherInstall->processBulkImageSources(bulkSources);
+ }
+
// Perform transfers if required
- if(mOptionSet.imageMode == Fe::ImageMode::Copy || mOptionSet.imageMode == Fe::ImageMode::Link)
+ if(mOptionSet.imageMode == ImageMode::Copy || mOptionSet.imageMode == ImageMode::Link)
{
/*
* Account for potential mismatch between assumed and actual job count.
* For example, this may happen with infinity if a game hasn't been clicked on, as the logo
* will have been downloaded but not the screenshot
*/
- if(static_cast(imageTransferJobs.size()) != mProgressManager.group(Pg::ImageTransfer)->maximum())
- mProgressManager.group(Pg::ImageTransfer)->setMaximum(imageTransferJobs.size());
+ if(static_cast