diff options
Diffstat (limited to 'libs/installer_fomod_plus/installer/lib')
| -rw-r--r-- | libs/installer_fomod_plus/installer/lib/ConditionTester.cpp | 176 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/installer/lib/ConditionTester.h | 42 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/installer/lib/CrashHandler.cpp | 122 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/installer/lib/CrashHandler.h | 20 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/installer/lib/FileInstaller.cpp | 274 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/installer/lib/FileInstaller.h | 94 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/installer/lib/FlagMap.h | 113 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/installer/lib/Logger.h | 116 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/installer/lib/ViewModels.h | 120 |
9 files changed, 0 insertions, 1077 deletions
diff --git a/libs/installer_fomod_plus/installer/lib/ConditionTester.cpp b/libs/installer_fomod_plus/installer/lib/ConditionTester.cpp deleted file mode 100644 index c35bf47..0000000 --- a/libs/installer_fomod_plus/installer/lib/ConditionTester.cpp +++ /dev/null @@ -1,176 +0,0 @@ -#include "ConditionTester.h" - -#include "ui/FomodViewModel.h" - -#include <iplugingame.h> -#include <ipluginlist.h> - -std::string setToString(const std::set<int> &set) -{ - std::string str; - for (const auto& i : set) { - str += std::to_string(i) + ", "; - } - return str; -} - -bool ConditionTester::isStepVisible(const std::shared_ptr<FlagMap>& flags, - const CompositeDependency& compositeDependency, - const int stepIndex, - const std::vector<std::shared_ptr<StepViewModel>>& steps) const -{ - - // first things first: is it visible? - if (!testCompositeDependency(flags, compositeDependency)) { - return false; - } - - const auto flagDependencies = compositeDependency.flagDependencies; - if (flagDependencies.empty()) { - return true; - } - - std::set<int> stepsThatSetThisFlag; - - for (const auto& flagDependency : flagDependencies) { - // for this flag, find the plugins that set it - for (int i = stepIndex - 1; i >= 0; --i) { - for (const auto& group : steps[i]->getGroups()) { - for (const auto& plugin : group->getPlugins()) { - if (std::ranges::any_of(plugin->getPlugin()->conditionFlags.flags, - [&flagDependency](const ConditionFlag& flag) { - return flag.name == flagDependency.flag && flag.value == flagDependency.value; - })) { - stepsThatSetThisFlag.insert(i); - } - } - } - } - } - const auto anyVisible = std::ranges::any_of(stepsThatSetThisFlag, [this, &steps, &flags](const int index) { - return isStepVisible(flags, steps[index]->getVisibilityConditions(), index, steps); - }); - if (!anyVisible) { - log.logMessage(DEBUG, "Step " + steps[stepIndex]->getName() + " has no dependent steps that are visible."); - log.logMessage(DEBUG, "Steps that set this flag: " + setToString(stepsThatSetThisFlag)); - } - return anyVisible; - -} - -bool ConditionTester::testCompositeDependency(const std::shared_ptr<FlagMap>& flags, - const CompositeDependency& compositeDependency) const -{ - const auto fileDependencies = compositeDependency.fileDependencies; - const auto flagDependencies = compositeDependency.flagDependencies; - const auto gameDependencies = compositeDependency.gameDependencies; - const auto nestedDependencies = compositeDependency.nestedDependencies; - const auto globalOperatorType = compositeDependency.operatorType; - - // For the globalOperatorType - // Evaluate all conditions and store the results in a vector<bool>, then return based on operator. - // These aren't expensive to calculate so rather than do some fancy logic to short-circuit, just calculate all of 'em. - std::vector<bool> results; - for (const auto& fileDependency : fileDependencies) { - results.emplace_back(testFileDependency(fileDependency)); - } - for (const auto& flagDependency : flagDependencies) { - results.emplace_back(testFlagDependency(flags, flagDependency)); - } - for (const auto& gameDependency : gameDependencies) { - results.emplace_back(testGameDependency(gameDependency)); - } - for (const auto& nestedDependency : nestedDependencies) { - results.emplace_back(testCompositeDependency(flags, nestedDependency)); - } - - if (globalOperatorType == OperatorTypeEnum::AND) { - return std::ranges::all_of(results, [](const bool result) { return result; }); - } - return std::ranges::any_of(results, [](const bool result) { return result; }); -} - - -bool ConditionTester::testFlagDependency(const std::shared_ptr<FlagMap>& flags, const FlagDependency& flagDependency) -{ - // Every instance of this flag being set in the map. - const auto flagList = flags->getFlagsByKey(flagDependency.flag); - - // Find the first instance of this flag being set (in the order specified by getFlagsByKey) - if (flagList.empty()) { - // If the dependency value is an empty string, it means this flag should be unset. - // So if we don't have any value for this flag, the result is true. - return flagDependency.value.empty(); - } - - return flagList.front().second == flagDependency.value; -} - -bool ConditionTester::testFileDependency(const FileDependency& fileDependency) const -{ - const std::string& pluginName = fileDependency.file; - const auto pluginState = getFileDependencyStateForPlugin(pluginName); - return pluginState == fileDependency.state; -} - -bool ConditionTester::testGameDependency(const GameDependency& gameDependency) const -{ - const auto gameVersion = mOrganizer->managedGame()->gameVersion().toStdString(); - log.logMessage(DEBUG, "Comparing condition version " + gameDependency.version + " against " + gameVersion); - if ( gameDependency.version <= gameVersion) { - log.logMessage(DEBUG, "Version matches!"); - } - return gameDependency.version <= gameVersion; -} - -FileDependencyTypeEnum ConditionTester::getFileDependencyStateForPlugin(const std::string& pluginName) const -{ - if (const auto it = pluginStateCache.find(pluginName); it != pluginStateCache.end()) { - return it->second; - } - - const QFlags<MOBase::IPluginList::PluginState> pluginState = mOrganizer->pluginList()->state( - QString::fromStdString(pluginName)); - - FileDependencyTypeEnum state; - - if (pluginState == MOBase::IPluginList::STATE_MISSING) { - state = FileDependencyTypeEnum::Missing; - } else if (pluginState == MOBase::IPluginList::STATE_INACTIVE) { - state = FileDependencyTypeEnum::Inactive; - } else if (pluginState == MOBase::IPluginList::STATE_ACTIVE) { - state = FileDependencyTypeEnum::Active; - } else { - state = FileDependencyTypeEnum::UNKNOWN_STATE; - } - - pluginStateCache[pluginName] = state; - return state; -} - -PluginTypeEnum ConditionTester::getPluginTypeDescriptorState(const std::shared_ptr<Plugin>& plugin, - const std::shared_ptr<FlagMap>& flags) const -{ - // NOTE: A plugin's ConditionFlags aren't the same thing as a step visibility one. - // A plugin's ConditionFlags are toggled based on the selection state of the plugin - // We only evaluate the typeDescriptor here. - - // We will return the 'winning' type or the default. If multiple conditions are met, - // ...well, I'm not sure. - // ReSharper disable once CppTooWideScopeInitStatement - const auto& dependencyType = plugin->typeDescriptor.dependencyType; - for (const auto& pattern : dependencyType.patterns.patterns) { - if (testCompositeDependency(flags, pattern.dependencies)) { - return pattern.type; - } - } - - // Sometimes authors do this. - if (plugin->typeDescriptor.type != PluginTypeEnum::Optional) { - return plugin->typeDescriptor.type; - } - if (plugin->typeDescriptor.dependencyType.defaultType.has_value()) { - return plugin->typeDescriptor.dependencyType.defaultType.value(); - } - return PluginTypeEnum::Optional; -}
\ No newline at end of file diff --git a/libs/installer_fomod_plus/installer/lib/ConditionTester.h b/libs/installer_fomod_plus/installer/lib/ConditionTester.h deleted file mode 100644 index 09aa26e..0000000 --- a/libs/installer_fomod_plus/installer/lib/ConditionTester.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include <imoinfo.h> - -#include "FlagMap.h" -#include "Logger.h" -#include "xml/ModuleConfiguration.h" - -class CompositeDependency; -class StepViewModel; - -class ConditionTester { -public: - explicit ConditionTester(MOBase::IOrganizer* organizer) : mOrganizer(organizer) {} - - bool isStepVisible(const std::shared_ptr<FlagMap>& flags, const CompositeDependency& compositeDependency, - int stepIndex, - const std::vector<std::shared_ptr<StepViewModel>>& steps) const; - - bool testCompositeDependency(const std::shared_ptr<FlagMap>& flags, - const CompositeDependency& compositeDependency) const; - - static bool testFlagDependency(const std::shared_ptr<FlagMap>& flags, const FlagDependency& flagDependency); - - [[nodiscard]] bool testFileDependency(const FileDependency& fileDependency) const; - - bool testGameDependency(const GameDependency& gameDependency) const; - -private: - Logger& log = Logger::getInstance(); - MOBase::IOrganizer* mOrganizer; - - friend class FomodViewModel; - - [[nodiscard]] FileDependencyTypeEnum getFileDependencyStateForPlugin(const std::string& pluginName) const; - - PluginTypeEnum getPluginTypeDescriptorState(const std::shared_ptr<Plugin>& plugin, - const std::shared_ptr<FlagMap>& flags) const; - - mutable std::unordered_map<std::string, FileDependencyTypeEnum> pluginStateCache; - -}; diff --git a/libs/installer_fomod_plus/installer/lib/CrashHandler.cpp b/libs/installer_fomod_plus/installer/lib/CrashHandler.cpp deleted file mode 100644 index 98ed1f3..0000000 --- a/libs/installer_fomod_plus/installer/lib/CrashHandler.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#include "CrashHandler.h" -#include <iostream> - -#ifdef _WIN32 -#include <windows.h> -#include <dbghelp.h> -#include <sstream> -#include <fstream> - -LPTOP_LEVEL_EXCEPTION_FILTER CrashHandler::previousFilter = nullptr; -PVOID CrashHandler::vectoredHandler = nullptr; -#endif - -void CrashHandler::initialize() { -#ifdef _WIN32 - std::cout << "CrashHandler initializing..." << std::endl; - vectoredHandler = AddVectoredExceptionHandler(1, vectoredExceptionHandler); - previousFilter = SetUnhandledExceptionFilter(unhandledExceptionFilter); - std::cout << "CrashHandler initialized successfully" << std::endl; -#else - // No-op on Linux — Windows crash handler not needed. -#endif -} - -void CrashHandler::cleanup() { -#ifdef _WIN32 - if (vectoredHandler) { - RemoveVectoredExceptionHandler(vectoredHandler); - vectoredHandler = nullptr; - } - if (previousFilter) { - SetUnhandledExceptionFilter(previousFilter); - previousFilter = nullptr; - } -#endif -} - -#ifdef _WIN32 -LONG WINAPI CrashHandler::vectoredExceptionHandler(EXCEPTION_POINTERS* exceptionInfo) { - // Log all exceptions but only create dumps for serious ones - std::cout << "Exception caught: 0x" << std::hex << exceptionInfo->ExceptionRecord->ExceptionCode << std::endl; - - // Only create dumps for serious crashes, not benign exceptions - if (exceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION || - exceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW || - exceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) { - - // For stack overflow, we need to handle it on a different thread with its own stack - if (exceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) { - // Create a new thread to handle the crash dump since our stack is corrupted - HANDLE dumpThread = CreateThread(nullptr, 0, [](LPVOID param) -> DWORD { - auto* exceptionInfo = static_cast<EXCEPTION_POINTERS*>(param); - logCrashInfo(exceptionInfo); - - HANDLE file = CreateFileA("fomod_plus_stack_overflow.dmp", GENERIC_WRITE, 0, nullptr, - CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - - if (file != INVALID_HANDLE_VALUE) { - MINIDUMP_EXCEPTION_INFORMATION dumpInfo = {0}; - dumpInfo.ThreadId = GetCurrentThreadId(); - dumpInfo.ExceptionPointers = exceptionInfo; - dumpInfo.ClientPointers = FALSE; - - MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, - MiniDumpNormal, &dumpInfo, nullptr, nullptr); - CloseHandle(file); - - std::cout << "Stack overflow crash dump written to fomod_plus_stack_overflow.dmp" << std::endl; - } - return 0; - }, exceptionInfo, 0, nullptr); - - if (dumpThread) { - WaitForSingleObject(dumpThread, 5000); // Wait up to 5 seconds - CloseHandle(dumpThread); - } - } else { - logCrashInfo(exceptionInfo); - - // Generate minidump - HANDLE file = CreateFileA("fomod_plus_crash.dmp", GENERIC_WRITE, 0, nullptr, - CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - - if (file != INVALID_HANDLE_VALUE) { - MINIDUMP_EXCEPTION_INFORMATION dumpInfo = {0}; - dumpInfo.ThreadId = GetCurrentThreadId(); - dumpInfo.ExceptionPointers = exceptionInfo; - dumpInfo.ClientPointers = FALSE; - - MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, - MiniDumpNormal, &dumpInfo, nullptr, nullptr); - CloseHandle(file); - - std::cout << "Crash dump written to fomod_plus_crash.dmp" << std::endl; - } - } - } - - return EXCEPTION_CONTINUE_SEARCH; // Let other handlers process it -} - -LONG WINAPI CrashHandler::unhandledExceptionFilter(EXCEPTION_POINTERS* exceptionInfo) { - logCrashInfo(exceptionInfo); - return EXCEPTION_EXECUTE_HANDLER; -} - -void CrashHandler::logCrashInfo(const EXCEPTION_POINTERS* exceptionInfo) { - std::ostringstream oss; - oss << "FOMOD PLUS CRASH DETECTED!" << std::endl; - oss << "Exception Code: 0x" << std::hex << exceptionInfo->ExceptionRecord->ExceptionCode << std::endl; - oss << "Exception Address: 0x" << std::hex << exceptionInfo->ExceptionRecord->ExceptionAddress << std::endl; - - // Write to stdout immediately - std::cout << oss.str() << std::endl; - - // Also write to crash log file - std::ofstream crashLog("fomod_plus_crash.log", std::ios::app); - if (crashLog.is_open()) { - crashLog << oss.str() << std::endl; - } -} -#endif diff --git a/libs/installer_fomod_plus/installer/lib/CrashHandler.h b/libs/installer_fomod_plus/installer/lib/CrashHandler.h deleted file mode 100644 index 78c8552..0000000 --- a/libs/installer_fomod_plus/installer/lib/CrashHandler.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#ifdef _WIN32 -#include <windows.h> -#endif - -class CrashHandler { -public: - static void initialize(); - static void cleanup(); - -private: -#ifdef _WIN32 - static LONG WINAPI unhandledExceptionFilter(EXCEPTION_POINTERS* exceptionInfo); - static LONG WINAPI vectoredExceptionHandler(EXCEPTION_POINTERS* exceptionInfo); - static void logCrashInfo(const EXCEPTION_POINTERS* exceptionInfo); - static LPTOP_LEVEL_EXCEPTION_FILTER previousFilter; - static PVOID vectoredHandler; -#endif -}; diff --git a/libs/installer_fomod_plus/installer/lib/FileInstaller.cpp b/libs/installer_fomod_plus/installer/lib/FileInstaller.cpp deleted file mode 100644 index e47e088..0000000 --- a/libs/installer_fomod_plus/installer/lib/FileInstaller.cpp +++ /dev/null @@ -1,274 +0,0 @@ -#include "FileInstaller.h" - -#include <utility> - -#include "ui/FomodViewModel.h" - -using namespace MOBase; - -FileInstaller::FileInstaller( - IOrganizer* organizer, - QString fomodPath, - const std::shared_ptr<IFileTree>& fileTree, - std::unique_ptr<ModuleConfiguration> fomodFile, - const std::shared_ptr<FlagMap>& flagMap, - const std::vector<std::shared_ptr<StepViewModel> >& steps) : mOrganizer(organizer), - mFomodPath(std::move(fomodPath)), - mFileTree(fileTree), - mFomodFile(std::move(fomodFile)), - mFlagMap(flagMap), - mConditionTester(organizer), mSteps(steps) -{ - const auto requiredCount = - (mFomodFile != nullptr) ? mFomodFile->requiredInstallFiles.files.size() : 0; - const auto stepCount = mSteps.size(); - logMessage(DEBUG, - "FileInstaller constructed. steps=" + std::to_string(stepCount) + ", required files=" + - std::to_string(requiredCount) + ", fomodPath=" + mFomodPath.toStdString()); -} - -std::shared_ptr<IFileTree> FileInstaller::install() const -{ - logMessage(DEBUG, "Starting FileInstaller::install()"); - const auto filesToInstall = collectFilesToInstall(); - logMessage(INFO, "Installing " + std::to_string(filesToInstall.size()) + " files"); - logMessage(INFO, "FlagMap"); - logMessage(INFO, mFlagMap->toString()); - - // update the file tree with the new files - const std::shared_ptr<IFileTree> installTree = mFileTree->createOrphanTree(); - - for (const auto& file : filesToInstall) { - - logMessage(DEBUG, - "Processing install entry source=" + file.source + ", destination=" + - (file.destination.has_value() ? file.destination.value() : "<default>") + - ", priority=" + std::to_string(file.priority)); - const auto sourcePath = getQualifiedFilePath(file.source); - const auto sourceNode = mFileTree->find(QString::fromStdString(sourcePath)); - if (sourceNode == nullptr) { - logMessage(ERR, "Could not find source: " + file.source); - continue; - } - const auto targetPath = file.destination.has_value() - ? QString::fromStdString(file.destination.value()) - : QString::fromStdString(sourcePath); - - // If it's a folder, copy the contents of the folder, not the folder itself. - if (sourceNode->isDir()) { - logMessage(DEBUG, - "Copying directory '" + file.source + "' into target '" + targetPath.toStdString() + - "'"); - // TODO: Check if target path is literally undefined/null - const auto& tree = sourceNode->astree(); - for (auto it = tree->begin(); it != tree->end(); ++it) { - const auto& entry = *it; - const auto path = targetPath.isEmpty() ? entry->name() : targetPath + "/" + entry->name(); - logMessage(DEBUG, - " Copying entry '" + entry->name().toStdString() + "' to '" + - path.toStdString() + "'"); - installTree->copy(entry, path, IFileTree::InsertPolicy::MERGE); - } - } else { - logMessage(DEBUG, - "Copying file '" + file.source + "' to '" + targetPath.toStdString() + "'"); - installTree->copy(sourceNode, targetPath, IFileTree::InsertPolicy::MERGE); - } - } - - // This file will be written by the InstallationManager later. - const auto jsonFilePath = "fomod.json"; - installTree->addFile(QString::fromStdString(jsonFilePath), true); - logMessage(DEBUG, "Added fomod.json placeholder file to install tree."); - - logMessage(DEBUG, "FileInstaller::install completed."); - return installTree; -} - -nlohmann::json FileInstaller::generateFomodJson() const -{ - nlohmann::json fomodJson; - logMessage(DEBUG, "Generating fomod.json representation for " + std::to_string(mSteps.size()) + - " steps."); - - fomodJson["steps"] = nlohmann::json::array(); - for (const auto& stepViewModel : mSteps) { - auto stepJson = nlohmann::json::object(); - stepJson["name"] = stepViewModel->getName(); - stepJson["groups"] = nlohmann::json::array(); - logMessage(DEBUG, "Serializing step '" + stepViewModel->getName() + "'"); - - for (const auto& groupViewModel : stepViewModel->getGroups()) { - auto groupJson = nlohmann::json::object(); - groupJson["name"] = groupViewModel->getName(); - auto pluginArray = nlohmann::json::array(); - auto deselectedArray = nlohmann::json::array(); - logMessage(DEBUG, - " Serializing group '" + groupViewModel->getName() + - "' with " + std::to_string(groupViewModel->getPlugins().size()) + " plugins"); - - for (const auto& pluginViewModel : groupViewModel->getPlugins()) { - logMessage(DEBUG, - " Plugin '" + pluginViewModel->getName() + "' selected=" + - (pluginViewModel->isSelected() ? "true" : "false") + ", manually-set=" + - (pluginViewModel->wasManuallySet() ? "true" : "false")); - if (pluginViewModel->isSelected()) { - pluginArray.emplace_back(pluginViewModel->getName()); - } - // Add deselected plugins here. (TODO: This will be replaced with an embedded db.) - if (!pluginViewModel->isSelected() && pluginViewModel->wasManuallySet()) { - deselectedArray.emplace_back(pluginViewModel->getName()); - } - } - groupJson["plugins"] = pluginArray; - groupJson["deselected"] = deselectedArray; - stepJson["groups"].emplace_back(groupJson); - } - fomodJson["steps"].emplace_back(stepJson); - } - return fomodJson; -} - -std::string FileInstaller::getQualifiedFilePath(const std::string& treePath) const -{ - // We need to prepend the fomod path to whatever source we reference. Guess we're passing that path around. - const auto qualifiedPath = mFomodPath.toStdString() + "/" + treePath; - logMessage(DEBUG, "Qualifying tree path '" + treePath + "' to '" + qualifiedPath + "'"); - return qualifiedPath; -} - -std::vector<std::string> FileInstaller::collectPositiveFileNamesFromDependencyPatterns( - const std::vector<DependencyPattern>& patterns) -{ - std::vector<std::string> usableFileDependencyPluginNames = {}; - logMessage(DEBUG, - "Collecting positive file names from " + std::to_string(patterns.size()) + " patterns."); - - for (const auto& pattern : patterns) { - if (pattern.type == PluginTypeEnum::NotUsable) { - logMessage(DEBUG, "Skipping pattern marked as NotUsable."); - continue; - } - - if (pattern.dependencies.fileDependencies.empty() && pattern.dependencies.nestedDependencies.empty()) { - logMessage(DEBUG, "Skipping pattern with no dependencies."); - continue; - } - - const auto fileDependencies = pattern.dependencies.fileDependencies; - const auto nestedDependencies = pattern.dependencies.nestedDependencies; - - for (const auto& fileDependency : fileDependencies) { - if (fileDependency.state != FileDependencyTypeEnum::Active) { - continue; - } - usableFileDependencyPluginNames.emplace_back(fileDependency.file); - logMessage(DEBUG, "Adding active file dependency: " + fileDependency.file); - } - - for (const auto& nestedDependency : nestedDependencies) { - for (const auto& fileDependency : nestedDependency.fileDependencies) { - if (fileDependency.state != FileDependencyTypeEnum::Active) { - continue; - } - usableFileDependencyPluginNames.emplace_back(fileDependency.file); - logMessage(DEBUG, "Adding nested active file dependency: " + fileDependency.file); - } - } - // Not handling twice-nested dependencies now. IDK if that's even feasible. - } - - logMessage(DEBUG, - "CollectPositiveFileNamesFromDependencyPatterns found " + - std::to_string(usableFileDependencyPluginNames.size()) + " entries."); - return usableFileDependencyPluginNames; -} - -// Generic vector appender -void FileInstaller::addFiles(std::vector<File>& main, std::vector<File> toAdd) const -{ - for (const auto& add : toAdd) { - logMessage(INFO, "Adding file with source: " + add.source); - } - main.insert(main.end(), toAdd.begin(), toAdd.end()); - logMessage(DEBUG, - "addFiles merged " + std::to_string(toAdd.size()) + " files. Total is now " + - std::to_string(main.size())); -} - -// TODO: Unclear if we're copying. oh well. -// TODO: Rebuild flagmap and step indeces before installing -std::vector<File> FileInstaller::collectFilesToInstall() const -{ - logMessage(DEBUG, "collectFilesToInstall started."); - std::vector<File> allFiles; - - // Required files from FOMOD - const FileList requiredInstallFiles = mFomodFile->requiredInstallFiles; - logMessage(DEBUG, - "Adding " + std::to_string(requiredInstallFiles.files.size()) + - " required install files from fomod."); - addFiles(allFiles, requiredInstallFiles.files); - - // Selected files from visible steps - for (const auto& stepViewModel : mSteps) { - if (!mConditionTester.testCompositeDependency(mFlagMap, stepViewModel->getVisibilityConditions())) { - logMessage(DEBUG, - "Skipping invisible step '" + stepViewModel->getName() + "'"); - continue; - } - logMessage(DEBUG, - "Processing visible step '" + stepViewModel->getName() + "' with " + - std::to_string(stepViewModel->getGroups().size()) + " groups."); - for (const auto& groupViewModel : stepViewModel->getGroups()) { - logMessage(DEBUG, - " Processing group '" + groupViewModel->getName() + "' with " + - std::to_string(groupViewModel->getPlugins().size()) + " plugins."); - for (const auto& pluginViewModel : groupViewModel->getPlugins()) { - if (pluginViewModel->isSelected()) { - logMessage(DEBUG, - " Adding selected plugin '" + pluginViewModel->getName() + - "' with " + - std::to_string(pluginViewModel->getPlugin()->files.files.size()) + - " files."); - addFiles(allFiles, pluginViewModel->getPlugin()->files.files); - } else { - logMessage(DEBUG, - " Skipping plugin '" + pluginViewModel->getName() + - "' (not selected)."); - } - } - } - } - - // ConditionalInstall files - for (const auto conditionals = mFomodFile->conditionalFileInstalls; const auto& pattern : conditionals.patterns) { - //<folder source="CR\Dagi-Raht LL\VLrn_Custom Race - Dagi-Raht LL" destination="" priority="2" /> - - if (mConditionTester.testCompositeDependency(mFlagMap, pattern.dependencies)) { - // also check if the plugins setting these flags are visible. at least one - - addFiles(allFiles, pattern.files.files); - logMessage(DEBUG, - "Conditional install pattern matched; added " + - std::to_string(pattern.files.files.size()) + " files."); - } else { - logMessage(DEBUG, "Conditional install pattern did not match."); - } - } - - // Files will all have a default priority of 0 if not specified, so the order should also be informed by the - // order they appear within XML. That's why we put conditionalFileInstalls after. - logMessage(DEBUG, "Sorting " + std::to_string(allFiles.size()) + " files by priority."); - std::ranges::sort(allFiles, [](const auto& a, const auto& b) { - return a.priority < b.priority; - }); - - for (const auto& toInstall : allFiles) { - logMessage(DEBUG, "File to install: " + toInstall.source); - } - logMessage(DEBUG, - "collectFilesToInstall completed with " + std::to_string(allFiles.size()) + " files."); - - return allFiles; -} diff --git a/libs/installer_fomod_plus/installer/lib/FileInstaller.h b/libs/installer_fomod_plus/installer/lib/FileInstaller.h deleted file mode 100644 index 1c122a7..0000000 --- a/libs/installer_fomod_plus/installer/lib/FileInstaller.h +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -#include <ifiletree.h> -#include <nlohmann/json.hpp> - -#include "ConditionTester.h" -#include "FlagMap.h" -#include "Logger.h" -#include "xml/ModuleConfiguration.h" - - -/* This is what legacy fomodInstaller does: - modName.update(dialog.getName(), GUESS_USER); - return dialog.updateTree(tree); - - This is a link to the legacy updateTree method: - https://github.com/ModOrganizer2/modorganizer-installer_fomod/blob/fc263f2d923c704b4853c11ed4f8b8cf3920f30d/src/fomodinstallerdialog.cpp#L546 - -*/ - -// Things this should do: -// - Copy all requiredInstall files -// - Copy all conditionalInstall files -// - Copy all selected files from steps that were visible to the user at the point of install - -using namespace MOBase; - -using FileGlobalIndex = int; -using FileDescriptor = std::pair<File, FileGlobalIndex>; - -class StepViewModel; - -class FileInstaller { -public: - FileInstaller( - IOrganizer* organizer, - QString fomodPath, - const std::shared_ptr<IFileTree>& fileTree, - std::unique_ptr<ModuleConfiguration> fomodFile, - const std::shared_ptr<FlagMap>& flagMap, - const std::vector<std::shared_ptr<StepViewModel> >& steps); - - std::shared_ptr<IFileTree> install() const; - - /** - * @brief Create a 'fomod.json' file to add to the base of the installTree. Functionally similar to MO2's meta.ini. - * - * Until there's more utility in the JSON structure itself, it will simply be of this format: - * @code - * { - * "steps": [ - * { - * "name": "Step 1", - * "groups": [ - * { - * "name": "Group 1", - * "plugins: [ - * "Plugin1.esp", - * "Plugin2.esp" - * ] - * } - * ] - * } - * ] - * } - * @endcode - * - * @return nhlohmann::json - */ - nlohmann::json generateFomodJson() const; - - std::string getQualifiedFilePath(const std::string& treePath) const; - - std::vector<std::string> collectPositiveFileNamesFromDependencyPatterns(const std::vector<DependencyPattern> &patterns); - - void addFiles(std::vector<File>& main, std::vector<File> toAdd) const; - -private: - IOrganizer* mOrganizer; - Logger& log = Logger::getInstance(); - QString mFomodPath; - std::shared_ptr<IFileTree> mFileTree; - std::unique_ptr<ModuleConfiguration> mFomodFile; - std::shared_ptr<FlagMap> mFlagMap; - ConditionTester mConditionTester; - std::vector<std::shared_ptr<StepViewModel> > mSteps; // TODO: Maybe this is nasty. Idk. - - std::vector<File> collectFilesToInstall() const; - - void logMessage(LogLevel level, const std::string& message) const - { - log.logMessage(level, "[INSTALLER] " + message); - } -}; diff --git a/libs/installer_fomod_plus/installer/lib/FlagMap.h b/libs/installer_fomod_plus/installer/lib/FlagMap.h deleted file mode 100644 index 4959cec..0000000 --- a/libs/installer_fomod_plus/installer/lib/FlagMap.h +++ /dev/null @@ -1,113 +0,0 @@ -#pragma once - -#include "ViewModels.h" -#include "stringutil.h" - -#include <ranges> -#include <string> -#include <unordered_map> - -using Flag = std::pair<std::string, std::string>; -using FlagList = std::vector<Flag>; - -class FlagMap { -public: - - [[nodiscard]] std::vector<std::shared_ptr<PluginViewModel>> getPluginsSettingFlag(const std::string& key, const std::string& value) const - { - std::vector<std::shared_ptr<PluginViewModel>> result; - for (const auto& [plugin, theseFlags] : flags) { - for (const auto& [fst, snd] : theseFlags) { - if (toLower(fst) == toLower(key) && snd == value) { - result.emplace_back(plugin); - } - } - } - return result; - } - - /** - * - * @param key The flag key - * @return A list of flags currently set in this map with the given key. The list is ordered by step descending, then plugin ascending. - * So if steps 1, 2, and 3 set flag X in their first two plugins, it'll be ordered [3:1, 3:2, 2:1, 2:2, 1:2, 1:1] - */ - [[nodiscard]] FlagList getFlagsByKey(const std::string& key) const - { - FlagList result; - std::vector<std::pair<int, std::shared_ptr<PluginViewModel>>> orderedPlugins; - - // Collect all plugins with their stepIndex and ownIndex - for (const auto& plugin : flags | std::views::keys) { - orderedPlugins.emplace_back(plugin->getStepIndex(), plugin); - } - - // Sort plugins by stepIndex and ownIndex, stepIndex descending and ownIndex ascending - // TODO: Might need group sorting too. How can I just do the natural order?? - std::ranges::sort(orderedPlugins, [](const auto& a, const auto& b) { - return a.first > b.first || (a.first == b.first && a.second->getOwnIndex() < b.second->getOwnIndex()); - }); - - // Collect flags in the sorted order - for (const auto& plugin : orderedPlugins | std::views::values) { - for (const auto& flag : flags.at(plugin)) { - if (toLower(flag.first) == toLower(key)) { - result.emplace_back(flag); - } - } - } - return result; - } - - void setFlagsForPlugin(PluginRef plugin) - { - // Don't clutter the map with empty key-vals - if (plugin->getConditionFlags().empty()) { - return; - } - unsetFlagsForPlugin(plugin); - - FlagList flagList; - for (const auto& conditionFlag : plugin->getConditionFlags()) { - flagList.emplace_back(toLower(conditionFlag.name), conditionFlag.value); - } - flags[plugin] = flagList; - } - - void unsetFlagsForPlugin(PluginRef plugin) - { - if (flags.contains(plugin)) { - flags.erase(plugin); - } - } - - std::string toString() - { - auto result = std::string(); - result += "FlagMap:\n"; - - for (const auto& [plugin, theseFlags] : flags) { - result += plugin->getName() + " ["; - for (const auto& [fst, snd] : theseFlags) { - result += fst + ": " + snd + ", "; - } - result.erase(result.size() - 2); - result += "]\n"; - } - return result; - } - - void clearAll() - { - flags.clear(); - } - - [[nodiscard]] size_t getFlagCount() const - { - return flags.size(); - } - - -private: - std::unordered_map<std::shared_ptr<PluginViewModel>, FlagList> flags; -}; diff --git a/libs/installer_fomod_plus/installer/lib/Logger.h b/libs/installer_fomod_plus/installer/lib/Logger.h deleted file mode 100644 index 53fb7d9..0000000 --- a/libs/installer_fomod_plus/installer/lib/Logger.h +++ /dev/null @@ -1,116 +0,0 @@ -#pragma once -#include <fstream> -#include <iostream> -#include <mutex> -#include <regex> - - -// Log levels -enum LogLevel { - DEBUG = 0, - INFO = 1, - WARN = 2, - ERR = 3 -}; - -// Convert LogLevel to string -inline const char* logLevelToString(const LogLevel level) { - switch (level) { - case DEBUG: return "DEBUG"; - case INFO: return "INFO"; - case WARN: return "WARN"; - case ERR: return "ERROR"; - default: return "UNKNOWN"; - } -} - - -class Logger { -public: - static Logger& getInstance() - { - static Logger instance; - return instance; - } - - void setLogFilePath(const std::string& filePath) - { - std::lock_guard lock(mMutex); - if (mLogFile.is_open()) { - mLogFile.close(); - } - mLogFile.open(filePath, std::ios::out); - // std::ios::app is an option for appending but dont wanna grow it forever. - } - - void setDebugMode(bool debug) - { - std::lock_guard lock(mMutex); - mDebugMode = debug; - } - - void logMessage(const LogLevel level, const std::string& message) - { - -#if defined(__GNUC__) || defined(__clang__) - std::string functionName = __PRETTY_FUNCTION__; -#elif defined(_MSC_VER) - std::string functionName = __FUNCSIG__; -#else - std::string functionName = "UnknownFunction"; -#endif - - std::regex classNameRegex(R"((\w+)::\w+\()"); - std::smatch match; - std::string className = "UnknownClass"; - - if (std::regex_search(functionName, match, classNameRegex) && match.size() > 1) { - className = match.str(1); - } - - std::lock_guard lock(mMutex); - - auto writeLog = [&](std::ostream& stream) { - switch (level) { - case DEBUG: - stream << "[DEBUG] " << message << std::endl; - break; - case INFO: - stream << "[INFO] " << message << std::endl; - break; - case WARN: - stream << "[WARN] " << message << std::endl; - break; - case ERR: - stream << "[ERROR] " << message << std::endl; - break; - } - }; - - if (mLogFile.is_open()) { - writeLog(mLogFile); - } - } - - Logger& operator=(const Logger&) = delete; - -private: - Logger() = default; - - ~Logger() - { - if (mLogFile.is_open()) { - mLogFile.close(); - } - } - - Logger(const Logger&) = delete; - - std::ofstream mLogFile; - std::mutex mMutex; -#if !defined(NDEBUG) || defined(CMAKE_BUILD_TYPE_RELWITHDEBINFO) - bool mDebugMode = true; // Auto-enable in debug/RelWithDebInfo builds -#else - bool mDebugMode = false; // Disable in release builds -#endif -};
\ No newline at end of file diff --git a/libs/installer_fomod_plus/installer/lib/ViewModels.h b/libs/installer_fomod_plus/installer/lib/ViewModels.h deleted file mode 100644 index 062445a..0000000 --- a/libs/installer_fomod_plus/installer/lib/ViewModels.h +++ /dev/null @@ -1,120 +0,0 @@ -#pragma once - -#include <memory> -#include <vector> - -#include "xml/ModuleConfiguration.h" - - -template <typename T> -using shared_ptr_list = std::vector<std::shared_ptr<T> >; - - -/* --------------------------------------------------------------------------------- - Plugins --------------------------------------------------------------------------------- -*/ -class PluginViewModel { -public: - PluginViewModel(const std::shared_ptr<Plugin>& plugin_, const bool selected, bool, const int index) - : ownIndex(index), selected(selected), enabled(true), manuallySet(false), plugin(plugin_) {} - - void setSelected(const bool selected) { this->selected = selected; } - void setEnabled(const bool enabled) { this->enabled = enabled; } - [[nodiscard]] std::string getName() const { return plugin ? plugin->name : std::string(); } - [[nodiscard]] std::string getDescription() const { return plugin ? plugin->description : std::string(); } - [[nodiscard]] std::string getImagePath() const { return plugin ? plugin->image.path : std::string(); } - [[nodiscard]] bool isSelected() const { return selected; } - [[nodiscard]] bool isEnabled() const { return enabled; } - [[nodiscard]] int getOwnIndex() const { return ownIndex; } - [[nodiscard]] std::vector<ConditionFlag> getConditionFlags() const { return plugin->conditionFlags.flags; } - [[nodiscard]] PluginTypeEnum getCurrentPluginType() const { return currentPluginType; } - [[nodiscard]] bool wasManuallySet() const { return manuallySet; } - - void setCurrentPluginType(const PluginTypeEnum type) { currentPluginType = type; } - void setStepIndex(const int stepIndex) { this->stepIndex = stepIndex; } - void setGroupIndex(const int groupIndex) { this->groupIndex = groupIndex; } - - [[nodiscard]] int getStepIndex() const { return stepIndex; } - [[nodiscard]] int getGroupIndex() const { return groupIndex; } - - friend class FomodViewModel; - friend class FileInstaller; - friend class ConditionTester; - -protected: - [[nodiscard]] std::shared_ptr<Plugin> getPlugin() const { return plugin; } - -private: - int ownIndex; - bool selected; - bool enabled; - bool manuallySet; - PluginTypeEnum currentPluginType = PluginTypeEnum::UNKNOWN; - std::shared_ptr<Plugin> plugin; - - int stepIndex{ -1 }; - int groupIndex{ -1 }; -}; - -/* --------------------------------------------------------------------------------- - Groups --------------------------------------------------------------------------------- -*/ -class GroupViewModel { -public: - GroupViewModel(const std::shared_ptr<Group>& group_, const shared_ptr_list<PluginViewModel>& plugins, - const int index, const int stepIndex) - : plugins(plugins), group(group_), ownIndex(index), stepIndex(stepIndex) {} - - void addPlugin(const std::shared_ptr<PluginViewModel>& plugin) { plugins.emplace_back(plugin); } - - [[nodiscard]] std::string getName() const { return group->name; } - [[nodiscard]] GroupTypeEnum getType() const { return group->type; } - [[nodiscard]] const shared_ptr_list<PluginViewModel>& getPlugins() const { return plugins; } - [[nodiscard]] int getOwnIndex() const { return ownIndex; } - [[nodiscard]] int getStepIndex() const { return stepIndex; } - -private: - shared_ptr_list<PluginViewModel> plugins; - std::shared_ptr<Group> group; - int ownIndex; - int stepIndex; -}; - -/* --------------------------------------------------------------------------------- - Steps --------------------------------------------------------------------------------- -*/ -class StepViewModel { -public: - StepViewModel(const std::shared_ptr<InstallStep>& installStep_, const shared_ptr_list<GroupViewModel>& groups, - const int index) - : installStep(installStep_), groups(groups), ownIndex(index) {} - - [[nodiscard]] CompositeDependency& getVisibilityConditions() const { return installStep->visible; } - [[nodiscard]] std::string getName() const { return installStep->name; } - [[nodiscard]] const shared_ptr_list<GroupViewModel>& getGroups() const { return groups; } - [[nodiscard]] int getOwnIndex() const { return ownIndex; } - [[nodiscard]] bool getHasVisited() const { return visited; } - void setVisited(const bool visited) { this->visited = visited; } - -private: - bool visited{ false }; - std::shared_ptr<InstallStep> installStep; - shared_ptr_list<GroupViewModel> groups; - int ownIndex; -}; - - -/* --------------------------------------------------------------------------------- - Outbound Types --------------------------------------------------------------------------------- -*/ -using StepRef = const std::shared_ptr<StepViewModel>&; -using GroupRef = const std::shared_ptr<GroupViewModel>&; -using PluginRef = const std::shared_ptr<PluginViewModel>&; |
