/******************************************************************************** * Copyright (c) 2025-2026 ZF Friedrichshafen AG * * This program and the accompanying materials are made available under the * terms of the Apache License Version 2.0 which is available at * https://www.apache.org/licenses/LICENSE-2.0 * * SPDX-License-Identifier: Apache-2.0 * * Contributors: * Erik Verhoeven - initial API and implementation ********************************************************************************/ #include "app_config_file.h" #include #include "app_settings.h" #include "../../global/ipc_named_mutex.h" #include "toml_parser/parser_toml.h" #include #include "installation_manifest.h" #include CAppConfigFile::CAppConfigFile(const std::filesystem::path& rpathConfigFile) { switch (GetAppSettings().GetContextType()) { case sdv::app::EAppContext::main: case sdv::app::EAppContext::isolated: case sdv::app::EAppContext::maintenance: m_pathConfigFile = GetAppSettings().GetInstallDir() / rpathConfigFile; break; default: m_pathConfigFile = rpathConfigFile; break; } if (!LoadConfigFile()) { SDV_LOG_ERROR("ERROR: failed to load the configuration file: ", rpathConfigFile.generic_u8string()); } } void CAppConfigFile::Clear() { m_pathConfigFile.clear(); m_bLoaded = false; m_vecComponentList.clear(); m_vecClassList.clear(); m_vecModuleList.clear(); } bool CAppConfigFile::IsLoaded() const { return m_bLoaded; } const std::filesystem::path& CAppConfigFile::ConfigPath() const { return m_pathConfigFile; } bool CAppConfigFile::LoadConfigFile() { if (!m_pathConfigFile.has_filename() || !std::filesystem::exists(m_pathConfigFile.parent_path())) return false; if (!std::filesystem::exists(m_pathConfigFile)) { m_bLoaded = true; return true; } std::ifstream fstream(m_pathConfigFile); std::string ssConfig((std::istreambuf_iterator(fstream)), std::istreambuf_iterator()); fstream.close(); return LoadConfigFromString(ssConfig); } bool CAppConfigFile::LoadConfigFromString(const std::string& rssConfig) { // Determine whether running in main, isolation or maintenance mode. bool bServerApp = false; switch (GetAppSettings().GetContextType()) { case sdv::app::EAppContext::main: case sdv::app::EAppContext::isolated: case sdv::app::EAppContext::maintenance: bServerApp = true; break; default: break; } try { // Read the configuration toml_parser::CParser parserConfig(rssConfig); // Check for the version sdv::toml::CNodeCollection nodeConfig(&parserConfig.Root()); uint32_t uiVersion = nodeConfig.GetDirect("Configuration.Version").GetValue(); if (uiVersion != SDVFrameworkInterfaceVersion) { SDV_LOG_ERROR("Invalid version of application settings file (expected version ", SDVFrameworkInterfaceVersion, ", but available version ", uiVersion, ")"); return false; } // Iterate through the module list (if not running as server application). if (!bServerApp) { sdv::toml::CNodeCollection nodeModules = nodeConfig.GetDirect("Module"); for (size_t nIndex = 0; nIndex < nodeModules.GetCount(); nIndex++) { sdv::toml::CNodeCollection nodeModule = nodeModules.Get(nIndex); SModule sModule; sModule.pathModule = nodeModule.GetDirect("Path").GetValueAsPath(); auto itModule = std::find_if(m_vecModuleList.begin(), m_vecModuleList.end(), [&](const SModule& rsModuleStored) { return rsModuleStored.pathModule == sModule.pathModule; }); if (itModule == m_vecModuleList.end()) m_vecModuleList.push_back(std::move(sModule)); } } // Iterate through the class list sdv::toml::CNodeCollection arrayClasses = nodeConfig.GetDirect("Class"); for (size_t nIndex = 0; nIndex < arrayClasses.GetCount(); nIndex++) { sdv::toml::CNodeCollection tableClass = arrayClasses.Get(nIndex); sdv::SClassInfo sClass = TOML2ClassInfo(parserConfig.Root().Cast(), nIndex); if (sClass.ssName.empty() || sClass.eType == sdv::EObjectType::undefined) continue; auto itClass = std::find_if(m_vecClassList.begin(), m_vecClassList.end(), [&](const sdv::SClassInfo& rsClassStored) { return rsClassStored.ssName == sClass.ssName; }); if (itClass == m_vecClassList.end()) m_vecClassList.push_back(std::move(sClass)); } // Iterate through the component list sdv::toml::CNodeCollection nodeComponents = nodeConfig.GetDirect("Component"); for (size_t nIndex = 0; nIndex < nodeComponents.GetCount(); nIndex++) { sdv::toml::CNodeCollection tableComponent = nodeComponents.Get(nIndex); SComponent sComponent; if (!bServerApp) sComponent.pathModule = tableComponent.GetDirect("Path").GetValue().get(); sComponent.ssClassName = tableComponent.GetDirect("Class").GetValue().get(); if (sComponent.ssClassName.empty()) { SDV_LOG_ERROR("Missing class name for the class definition in the configuration file '", m_pathConfigFile, "'."); return false; } sComponent.ssInstanceName = tableComponent.GetDirect("Name").GetValue().get(); // NOTE: The name could be empty. The system will automatically select a name. sdv::toml::CNodeCollection tableParams = tableComponent.GetDirect("Parameters"); if (tableParams.IsValid() && tableParams.GetCount()) sComponent.ssParameterTOML = tableParams.GetTOML(); auto itComponent = std::find_if(m_vecComponentList.begin(), m_vecComponentList.end(), [&](const SComponent& rsComponentStored) { return rsComponentStored.ssClassName == sComponent.ssClassName && rsComponentStored.ssInstanceName == sComponent.ssInstanceName; }); if (itComponent == m_vecComponentList.end()) m_vecComponentList.push_back(std::move(sComponent)); } } catch (const sdv::toml::XTOMLParseException& rexcept) { SDV_LOG_ERROR("Failed to parse application configuration '", m_pathConfigFile.generic_u8string(), "': ", rexcept.what()); return false; } m_bLoaded = true; return true; } bool CAppConfigFile::SaveConfigFile(const std::filesystem::path& rpathTarget /*= std::filesystem::path()*/, bool bSafeParamsOnly /*= false*/) const { // Protect against multiple write actions at the same time. std::string ssMutexName = "LockSdvConfigFile_" + std::to_string(GetAppSettings().GetInstanceID()) + "_" + rpathTarget.filename().generic_u8string(); ipc::named_mutex mtx(ssMutexName); // Warning of cppcheck for locking a local mutex, which doesn't have any effect. Since this is a named mutex between // applciations, the warning is not correct. Suppress warning. // cppcheck-suppress localMutex std::unique_lock lock(mtx); std::filesystem::path pathTargetCopy = rpathTarget; if (pathTargetCopy.empty()) pathTargetCopy = m_pathConfigFile; if (pathTargetCopy.empty()) return false; if (!std::filesystem::exists(pathTargetCopy.parent_path())) return false; // If the configuration is not existing, create a default configuration file... std::string ssConfig; if (std::filesystem::exists(pathTargetCopy)) { // Open the existing settings file std::ifstream fstream(m_pathConfigFile); if (!fstream.is_open()) { if (!GetAppSettings().IsConsoleSilent()) std::cerr << "ERROR: Cannot open the application configuration file." << std::endl; return false; } // Read the config file ssConfig = std::string((std::istreambuf_iterator(fstream)), std::istreambuf_iterator()); if (ssConfig.empty()) { if (!GetAppSettings().IsConsoleSilent()) std::cerr << "ERROR: Cannot read the application settings file; will use default." << std::endl; } } bool bChanged = false; if (!UpdateConfigString(ssConfig, bChanged, bSafeParamsOnly)) return false; if (!bChanged) return true; // No change needed std::ofstream fstream(pathTargetCopy, std::ios::trunc); if (!fstream.is_open()) { SDV_LOG_ERROR("Cannot write the application settings file '", m_pathConfigFile.generic_u8string(), "'."); return false; } fstream << ssConfig; return true; } bool CAppConfigFile::UpdateConfigString(std::string& rssContent, bool& rbChanged, bool /*bSaveParamsOnly*/ /*= false*/) const { // Reset change flag rbChanged = false; // Determine whether running in main, isolation or maintenance mode. bool bServerApp = false; switch (GetAppSettings().GetContextType()) { case sdv::app::EAppContext::main: case sdv::app::EAppContext::isolated: case sdv::app::EAppContext::maintenance: bServerApp = true; break; default: break; } // If the configuration is not existing, create a default configuration file... std::string ssConfig = rssContent; if (ssConfig.empty()) { ssConfig = R"toml(# Configuration file [Configuration] Version = )toml" + std::to_string(SDVFrameworkInterfaceVersion) + R"toml( # The configuration consists of several sections: # - Modules Modules can contain component classes, which are only made accessible, but not automatically loaded. This # section applies to standalone applications only. Server applications use installations to provide accessible # component classes. # - Classes The classes that are present in a module and contain specific class information. # - Components The components that are started when loading the configuration. # # # The modules specify the location of the modules relative to the executable or with an absolute path. They are only available for # standalone applications and are ignored by server based application, who install the modules rather than provide them in a # configuration. A module has the following attribute: # - Path [Compulsory] Identifying the module path of components which should be made accessible. The path can be # relative to the application executable (or relocation path) or can be an absolute path to the module in the # system. # # Example: # [[Module]] # Path = "my_module.sdv" # # # Classes are used to specify default parameter for a component instance. These parameter can be overridden by the component instance # when specifying the parameter with identical name in the component section. The class has the following attributes: # - Path [Optional] Identifying the module path of the component. Only used for standalone applications. If not # specified the component class must be known in the system (e.g. through the module section or the component # section). # - Class [Compulsory] Name of the class that has the default pateters. # - Parameters [Optional] Table containing component instance specific parameters which are default parameters of all # instances of the components with of this class. # Example: # [[Class]] # Path = "my_module.sdv" # Class = "MyComponent" # [Class.Parameters] # AttributeA = "default_blep" # AttributeB = 1234 # [Component.Parameters.GroupC] # AttributeC1 = 4567.7890 # AttributeC2 = "This is a string" # # # Components are class instances that are instantiated during the loading of the configuration. They are stored as table array # and contain as a minimum a class name. They can have instance specific parameters, which are stored as a table within the # component. Components are instantiated in the order of appearance. A component can have the following attributes: # - Path [Optional] Identifying the module path of the component. Only used for standalone applications. If not # specified the component class must be known in the system (e.g. through the module section or the class # section). # - Class [Compulsory] Name of the class that has to be instantiated. # - Name [Optional] Name of the component instance. If not available, the class name determines the component name. # - Parameters [Optional] Table containing the component instance specific parameters. They override default component class # parameters. # # Example: # [[Component]] # Path = "my_module.sdv" # Class = "MyComponent" # Name = "MyPersonalComponent" # [Component.Parameters] # AttributeA = "blep" # AttributeB = 123 # [Component.Parameters.GroupC] # AttributeC1 = 456.789 # AttributeC2 = "This is a text" # )toml"; rbChanged = true; } try { // Read the configuration toml_parser::CParser parserConfig(ssConfig); // Check for the version sdv::toml::CNodeCollection nodeConfig(&parserConfig.Root()); uint32_t uiVersion = nodeConfig.GetDirect("Configuration.Version").GetValue(); if (uiVersion != SDVFrameworkInterfaceVersion) { SDV_LOG_ERROR("Invalid version of application settings file (expected version ", SDVFrameworkInterfaceVersion, ", but available version ", uiVersion, ")"); return false; } // Iterate and update through the module list (if not running as server application). if (!bServerApp) { // Remove modules not in the list any more. // Modules both in the vector and in the list are removed from the vector // Modules leftover in the vector are added to the list auto vecModuleListCopy = m_vecModuleList; sdv::toml::CNodeCollection nodeModules = nodeConfig.GetDirect("Module"); for (size_t nIndex = nodeModules.GetCount() - 1; nIndex < nodeModules.GetCount(); --nIndex) { sdv::toml::CNodeCollection tableModule = nodeModules.Get(nIndex); std::filesystem::path pathModule = tableModule.GetDirect("Path").GetValue().get(); auto itModule = std::find_if(vecModuleListCopy.begin(), vecModuleListCopy.end(), [&](const SModule& rsModule) { return pathModule == rsModule.pathModule; }); if (itModule == vecModuleListCopy.end()) { tableModule.Delete(); rbChanged = true; } else vecModuleListCopy.erase(itModule); } for (const SModule& rsModule : vecModuleListCopy) { sdv::toml::CNodeCollection tableModule = nodeConfig.AddTableArray("Module"); tableModule.AddValue("Path", rsModule.pathModule); rbChanged = true; } } // Iterate through and update the class list // Remove classes not in the list any more. // Classes both in the vector and in the list are removed from the vector // Classes leftover in the vector are added to the list auto vecClassListCopy = m_vecClassList; sdv::toml::CNodeCollection arrayClasses = nodeConfig.GetDirect("Class"); for (size_t nIndex = arrayClasses.GetCount() -1; nIndex < arrayClasses.GetCount(); --nIndex) { sdv::toml::CNodeCollection tableClass = arrayClasses.Get(nIndex); std::filesystem::path pathModule; if (!bServerApp) pathModule = tableClass.GetDirect("Path").GetValue().get(); std::string ssClassName = tableClass.GetDirect("Class").GetValue().get(); auto itClass = std::find_if(vecClassListCopy.begin(), vecClassListCopy.end(), [&](const sdv::SClassInfo& rsClass) { return ssClassName == rsClass.ssName; }); if (itClass == vecClassListCopy.end()) { tableClass.Delete(); rbChanged = true; } else { // Update of path if (!bServerApp) { if (pathModule.generic_u8string() != itClass->ssModulePath) { pathModule = std::filesystem::u8path(static_cast(itClass->ssModulePath)); sdv::toml::CNode nodePath = tableClass.GetDirect("Path"); if (!pathModule.empty()) { if (nodePath) nodePath.SetValue(pathModule); else tableClass.AddValue("Path", pathModule); } else if (nodePath) nodePath.Delete(); rbChanged = true; } } // Update the parameters - class parameters are default parameters provided by the object. Overwrite existing // default parameters. sdv::toml::CNodeCollection tableParams = tableClass.GetDirect("Parameters"); sdv::u8string ssExistingTOML; if (tableParams) ssExistingTOML = tableParams.GetTOML(); if (ssExistingTOML != itClass->ssDefaultConfig) { if (tableParams) tableParams.Delete(); if (!itClass->ssDefaultConfig.empty()) tableClass.InsertTOML(sdv::toml::npos, itClass->ssDefaultConfig); rbChanged = true; } vecClassListCopy.erase(itClass); } } for (const sdv::SClassInfo& rsClass : vecClassListCopy) { sdv::toml::CNodeCollection tableClass = nodeConfig.AddTableArray("Class"); tableClass.AddValue("Class", rsClass.ssName); if (!bServerApp) tableClass.AddValue("Path", std::filesystem::u8path(static_cast(rsClass.ssModulePath))); if (!rsClass.ssDefaultConfig.empty()) tableClass.InsertTOML(sdv::toml::npos, rsClass.ssDefaultConfig); rbChanged = true; } // Iterate through and update the component list // Remove components not in the list any more. // Components both in the vector and in the list are removed from the vector // Components leftover in the vector are added to the list auto vecComponentListCopy = m_vecComponentList; sdv::toml::CNodeCollection nodeComponents = nodeConfig.GetDirect("Component"); for (size_t nIndex = nodeComponents.GetCount() - 1; nIndex < nodeComponents.GetCount(); --nIndex) { sdv::toml::CNodeCollection tableComponent = nodeComponents.Get(nIndex); std::filesystem::path pathModule; if (!bServerApp) pathModule = tableComponent.GetDirect("Path").GetValue().get(); std::string ssClassName = tableComponent.GetDirect("Class").GetValue().get(); std::string ssInstanceName = tableComponent.GetDirect("Name").GetValue().get(); auto itComponent = std::find_if(vecComponentListCopy.begin(), vecComponentListCopy.end(), [&](const SComponent& rsComponent) { return ssInstanceName.empty() ? (ssClassName == rsComponent.ssClassName && rsComponent.ssInstanceName.empty()) : ssInstanceName == rsComponent.ssInstanceName; }); if (itComponent == vecComponentListCopy.end()) { // The class and the instance names must be identical tableComponent.Delete(); rbChanged = true; } else { // Update of path if (!bServerApp) { if (pathModule != itComponent->pathModule) { pathModule = itComponent->pathModule; sdv::toml::CNode nodePath = tableComponent.GetDirect("Path"); if (!pathModule.empty()) { if (nodePath) nodePath.SetValue(pathModule); else tableComponent.AddValue("Path", pathModule); } else if (nodePath) nodePath.Delete(); rbChanged = true; } } // Update the parameters - component parameters should be updated, not simply overwritten. Use the combine function. sdv::toml::CNodeCollection tableParams = tableComponent.GetDirect("Parameters"); sdv::u8string ssExistingTOML; if (tableParams) ssExistingTOML = tableParams.GetTOML(); if (!itComponent->ssParameterTOML.empty()) { // Update needed? if (tableParams && ssExistingTOML != itComponent->ssParameterTOML) { // Get the underlying node collection toml_parser::CTable* pComponent = dynamic_cast(static_cast(tableParams.GetInterface())); if (!pComponent) { SDV_LOG_ERROR("Failure to get access to an internal interface within the TOML parser."); return false; } try { toml_parser::CParser parserParams(itComponent->ssParameterTOML); pComponent->Combine(parserParams.Root().Cast()); } catch (const toml_parser::XTOMLParseException& /*rexcept*/) { SDV_LOG_WARNING("The parameter TOML cannot be interpreted; no saving possible."); } rbChanged = true; } else if (!tableParams) { // Simply add the parameters tableParams = tableComponent.InsertTable(sdv::toml::npos, "Parameters"); tableParams.InsertTOML(sdv::toml::npos, itComponent->ssParameterTOML); rbChanged = true; } } vecComponentListCopy.erase(itComponent); } } for (const SComponent& rsComponent : vecComponentListCopy) { sdv::toml::CNodeCollection tableComponent = nodeConfig.AddTableArray("Component"); if (!bServerApp && !rsComponent.pathModule.empty()) tableComponent.AddValue("Path", rsComponent.pathModule); tableComponent.AddValue("Class", rsComponent.ssClassName); if (!rsComponent.ssInstanceName.empty()) tableComponent.AddValue("Name", rsComponent.ssInstanceName); if (!rsComponent.ssParameterTOML.empty()) { sdv::toml::CNodeCollection tableParams = tableComponent.InsertTable(sdv::toml::npos, "Parameters"); tableParams.InsertTOML(sdv::toml::npos, rsComponent.ssParameterTOML); } rbChanged = true; } // Save the configuration file if needed if (rbChanged) { rssContent = parserConfig.GenerateTOML(); rbChanged = true; } } catch (const sdv::toml::XTOMLParseException& rexcept) { SDV_LOG_ERROR("Failed to parse configuration: ", rexcept.what()); return false; } return true; } const std::vector& CAppConfigFile::GetComponentList() const { return m_vecComponentList; } const std::vector& CAppConfigFile::GetClassList() const { return m_vecClassList; } const std::vector& CAppConfigFile::GetModuleList() const { return m_vecModuleList; } bool CAppConfigFile::InsertComponent(size_t nIndex, const std::filesystem::path& rpathModule, const std::string& rssClassName, const std::string& rssInstanceName, const TParameterVector& rvecParameters) { // Valid parameter? if (rssClassName.empty()) return false; // Check for a duplicate instance name. If there is no instance name, check for a component with the same class name without // the instance name. This is not an error! if (!rssInstanceName.empty()) { if (std::find_if(m_vecComponentList.begin(), m_vecComponentList.end(), [&](const SComponent& rsComponent) { return rsComponent.ssInstanceName == rssInstanceName; }) != m_vecComponentList.end()) return true; } else { if (std::find_if(m_vecComponentList.begin(), m_vecComponentList.end(), [&](const SComponent& rsComponent) { return rsComponent.ssInstanceName.empty() && rsComponent.ssClassName == rssClassName; }) != m_vecComponentList.end()) return true; } // Check for the module to already be added to the module list. Remove it from the list if it is. if (!rpathModule.empty()) { auto itModule = std::find_if(m_vecModuleList.begin(), m_vecModuleList.end(), [&](const SModule& rsModule) { return rsModule.pathModule == rpathModule; }); if (itModule != m_vecModuleList.end()) m_vecModuleList.erase(itModule); } // Create a parameter TOML std::string ssParameterTOML; try { // Create a TOML string toml_parser::CParser parser(""); sdv::toml::CNodeCollection root(&parser.Root()); // Split the key in a group and a parameter name auto fnSplit = [](const std::string& rssKey) -> std::pair { size_t nSeparator = rssKey.find_last_of('.'); if (nSeparator == std::string::npos) return std::make_pair("", rssKey); return std::make_pair(rssKey.substr(0, nSeparator), rssKey.substr(nSeparator + 1)); }; // Iterate through the parameters std::string ssGroup; sdv::toml::CNodeCollection group(root); for (const auto& prParameter : rvecParameters) { // Split the key in a group and parameter name. auto prKey = fnSplit(prParameter.first); // Need to add a group? if (prKey.first != ssGroup) { group = root.InsertTable(sdv::toml::npos, prKey.first); ssGroup = prKey.first; } // Add the parameter group.InsertValue(sdv::toml::npos, prKey.second, prParameter.second); } ssParameterTOML = parser.GenerateTOML(); } catch (const toml_parser::XTOMLParseException& /*rexcept*/) { return false; } // Add the instance SComponent sComponent; sComponent.pathModule = rpathModule; sComponent.ssClassName = rssClassName; sComponent.ssInstanceName = rssInstanceName; sComponent.ssParameterTOML = std::move(ssParameterTOML); m_vecComponentList.insert((nIndex < m_vecComponentList.size() ? m_vecComponentList.begin() + nIndex : m_vecComponentList.end()), std::move(sComponent)); return true; } void CAppConfigFile::RemoveComponent(const std::string& rssInstanceName) { // Find and destroy auto itComponent = std::find_if(m_vecComponentList.begin(), m_vecComponentList.end(), [&](const SComponent& rsComponent) { return rsComponent.ssInstanceName == rssInstanceName; }); if (itComponent != m_vecComponentList.end()) m_vecComponentList.erase(itComponent); } bool CAppConfigFile::InsertModule(size_t nIndex, const std::filesystem::path& rpathModule) { // Check for the proper context; only allowed when running as standalone (not main, isolated or maintenace). switch (GetAppSettings().GetContextType()) { case sdv::app::EAppContext::main: case sdv::app::EAppContext::isolated: case sdv::app::EAppContext::maintenance: return true; default: break; } if (rpathModule.empty()) return false; // Check for the module to exist already in either the component or in the module list if (std::find_if(m_vecComponentList.begin(), m_vecComponentList.end(), [&](const SComponent& rsComponent) { return rsComponent.pathModule == rpathModule; }) != m_vecComponentList.end()) return true; if (std::find_if(m_vecModuleList.begin(), m_vecModuleList.end(), [&](const SModule& rsModule) { return rsModule.pathModule == rpathModule; }) != m_vecModuleList.end()) return true; // Add the module to the module list SModule sModule; sModule.pathModule = rpathModule; m_vecModuleList.insert( (nIndex < m_vecModuleList.size() ? m_vecModuleList.begin() + nIndex : m_vecModuleList.end()), std::move(sModule)); return true; } void CAppConfigFile::RemoveModule(const std::filesystem::path& rpathModule) { // Check for the proper context; only allowed when running as standalone (not main, isolated or maintenace). switch (GetAppSettings().GetContextType()) { case sdv::app::EAppContext::main: case sdv::app::EAppContext::isolated: case sdv::app::EAppContext::maintenance: return; default: break; } // Find and destroy (any module and component with the provided path). auto itModule = std::find_if(m_vecModuleList.begin(), m_vecModuleList.end(), [&](const SModule& rsModule) { return rsModule.pathModule == rpathModule; }); if (itModule != m_vecModuleList.end()) m_vecModuleList.erase(itModule); while (true) { auto itComponent = std::find_if(m_vecComponentList.begin(), m_vecComponentList.end(), [&](const SComponent& rsComponent) { return rsComponent.pathModule == rpathModule; }); if (itComponent == m_vecComponentList.end()) break; m_vecComponentList.erase(itComponent); } } CAppConfigFile::EMergeResult CAppConfigFile::MergeConfigFile(const std::filesystem::path& rpathConfigFile) { CAppConfigFile configNew(rpathConfigFile); if (!configNew.LoadConfigFile()) return EMergeResult::not_successful; size_t nSucceeded = 0; size_t nFailed = 0; // Add modules auto vecNewModules = configNew.GetModuleList(); for (const auto& sNewModule : vecNewModules) { if (std::find_if(m_vecModuleList.begin(), m_vecModuleList.end(), [&sNewModule](const SModule& rsExistingModule) { return sNewModule.pathModule == rsExistingModule.pathModule; }) == m_vecModuleList.end()) m_vecModuleList.push_back(sNewModule); ++nSucceeded; } // Add/update classes auto vecNewClasses = configNew.GetClassList(); for (const auto& sNewClass : vecNewClasses) { auto itClass = std::find_if(m_vecClassList.begin(), m_vecClassList.end(), [&sNewClass](const sdv::SClassInfo& rsExistingClass) { return sNewClass.ssName == rsExistingClass.ssName; }); if (itClass == m_vecClassList.end()) m_vecClassList.push_back(sNewClass); else *itClass = sNewClass; ++nSucceeded; } // Merge components auto vecNewComponents = configNew.GetComponentList(); for (const auto& sNewComponent : vecNewComponents) { auto itComponent = std::find_if(m_vecComponentList.begin(), m_vecComponentList.end(), [&sNewComponent](const SComponent& rsExistingComponent) { if (sNewComponent.ssInstanceName.empty()) { if (!rsExistingComponent.ssInstanceName.empty()) return false; return sNewComponent.ssClassName == rsExistingComponent.ssClassName; } else return sNewComponent.ssInstanceName == rsExistingComponent.ssInstanceName; }); if (itComponent == m_vecComponentList.end()) m_vecComponentList.push_back(sNewComponent); else { // Only update component information if the components are of identical type if (itComponent->ssClassName != sNewComponent.ssClassName) { ++nFailed; continue; } // TODO: Add additional possibilty to replace the parameters // Merge the parameters of the existing component. if (!sNewComponent.ssParameterTOML.empty()) { if (itComponent->ssParameterTOML.empty()) itComponent->ssParameterTOML = sNewComponent.ssParameterTOML; else try { toml_parser::CParser parserNew(itComponent->ssParameterTOML), parserExisting(sNewComponent.ssParameterTOML); parserExisting.Root().Combine(parserNew.Root().Cast()); itComponent->ssParameterTOML = parserExisting.GenerateTOML(); } catch (const toml_parser::XTOMLParseException&) { ++nFailed; continue; } } ++nSucceeded; } } return nFailed ? (nSucceeded ? EMergeResult::partly_successfull : EMergeResult::not_successful) : EMergeResult::successful; }