mirror of
https://github.com/eclipse-openvehicle-api/openvehicle-api.git
synced 2026-04-19 10:58:16 +00:00
72
sdv_services/core/CMakeLists.txt
Normal file
72
sdv_services/core/CMakeLists.txt
Normal file
@@ -0,0 +1,72 @@
|
||||
# Define project
|
||||
project(sdv_services_core VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Define target
|
||||
add_library(core_services SHARED
|
||||
"memory.cpp"
|
||||
"memory.h"
|
||||
"sdv_core.h"
|
||||
"sdv_core.cpp"
|
||||
"module_control.h"
|
||||
"module_control.cpp"
|
||||
"app_control.h"
|
||||
"app_control.cpp"
|
||||
"repository.h"
|
||||
"repository.cpp"
|
||||
"toml_parser/character_reader_utf_8.h"
|
||||
"toml_parser/lexer_toml.h"
|
||||
"toml_parser/parser_toml.h"
|
||||
"toml_parser/parser_toml.cpp"
|
||||
"toml_parser/lexer_toml.cpp"
|
||||
"toml_parser/character_reader_utf_8.cpp"
|
||||
"toml_parser/parser_node_toml.h"
|
||||
"toml_parser/parser_node_toml.cpp"
|
||||
"toml_parser/exception.h"
|
||||
"module.cpp"
|
||||
"app_config.h"
|
||||
"app_config.cpp"
|
||||
"logger_control.h"
|
||||
"logger_control.cpp"
|
||||
"logger.h"
|
||||
"logger.cpp"
|
||||
"log_csv_writer.h"
|
||||
"log_csv_writer.cpp"
|
||||
"object_lifetime_control.h"
|
||||
"object_lifetime_control.cpp"
|
||||
"toml_parser_util.h"
|
||||
"installation_manifest.h"
|
||||
"installation_manifest.cpp"
|
||||
"local_shutdown_request.h"
|
||||
"iso_monitor.h"
|
||||
"iso_monitor.cpp"
|
||||
"installation_composer.h"
|
||||
"installation_composer.cpp"
|
||||
|
||||
)
|
||||
|
||||
# Link target
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
target_link_options(core_services PRIVATE)
|
||||
target_link_libraries(core_services ${CMAKE_THREAD_LIBS_INIT} stdc++fs)
|
||||
if (WIN32)
|
||||
target_link_libraries(core_services Ws2_32 Winmm Rpcrt4.lib)
|
||||
else()
|
||||
target_link_libraries(core_services ${CMAKE_DL_LIBS} rt)
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries(core_services Rpcrt4.lib)
|
||||
endif()
|
||||
|
||||
set_target_properties(core_services PROPERTIES PREFIX "")
|
||||
set_target_properties(core_services PROPERTIES SUFFIX ".sdv")
|
||||
|
||||
# Build dependencies
|
||||
add_dependencies(core_services CompileCoreIDL)
|
||||
add_dependencies(core_services hardware_ident)
|
||||
add_dependencies(core_services ipc_shared_mem)
|
||||
add_dependencies(core_services process_control)
|
||||
add_dependencies(core_services core_ps)
|
||||
add_dependencies(core_services ipc_com)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} core_services PARENT_SCOPE)
|
||||
739
sdv_services/core/app_config.cpp
Normal file
739
sdv_services/core/app_config.cpp
Normal file
@@ -0,0 +1,739 @@
|
||||
#include "app_config.h"
|
||||
#include "sdv_core.h"
|
||||
#include "../../global/exec_dir_helper.h"
|
||||
#include <interfaces/com.h>
|
||||
#include <interfaces/process.h>
|
||||
#include <interfaces/ipc.h>
|
||||
#include <support/crc.h>
|
||||
|
||||
#if __unix__
|
||||
#include <utime.h>
|
||||
#endif
|
||||
|
||||
bool CAppConfig::LoadInstallationManifests()
|
||||
{
|
||||
if (!GetAppControl().IsMainApplication() && !GetAppControl().IsIsolatedApplication()) return false;
|
||||
|
||||
std::filesystem::path pathCore = GetCoreDirectory();
|
||||
std::filesystem::path pathExe = GetExecDirectory();
|
||||
std::filesystem::path pathInstall = GetAppControl().GetInstallDir();
|
||||
|
||||
// Load the core manifest (should work in any case).
|
||||
m_manifestCore = CInstallManifest();
|
||||
if (!m_manifestCore.Load(pathCore)) return false;
|
||||
|
||||
// Check whether the exe path is identical to the core. If so, skip the manifest. If there is no manifest, this is not an error.
|
||||
if (pathCore != pathExe)
|
||||
{
|
||||
m_manifestExe = CInstallManifest();
|
||||
m_manifestExe.Load(pathExe);
|
||||
}
|
||||
else
|
||||
m_manifestExe.Clear();
|
||||
|
||||
// Get the user manifests. If there is no manifest, this is not an error.
|
||||
for (auto const& dir_entry : std::filesystem::directory_iterator{ pathInstall })
|
||||
{
|
||||
if (!dir_entry.is_directory()) continue;
|
||||
if (!std::filesystem::exists(dir_entry.path() / "install_manifest.toml")) continue;
|
||||
CInstallManifest manifestUser;
|
||||
if (manifestUser.Load(dir_entry.path(), true))
|
||||
m_vecUserManifests.push_back(manifestUser);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CAppConfig::UnloadInstallatonManifests()
|
||||
{
|
||||
if (!GetAppControl().IsMainApplication() && !GetAppControl().IsIsolatedApplication()) return;
|
||||
|
||||
m_vecUserManifests.clear();
|
||||
m_manifestExe.Clear();
|
||||
m_manifestCore.Clear();
|
||||
}
|
||||
|
||||
sdv::core::EConfigProcessResult CAppConfig::ProcessConfig(/*in*/ const sdv::u8string& ssContent)
|
||||
{
|
||||
if (GetAppControl().GetOperationState() != sdv::app::EAppOperationState::configuring)
|
||||
return sdv::core::EConfigProcessResult::failed;
|
||||
|
||||
// Reset the current baseline
|
||||
ResetConfigBaseline();
|
||||
|
||||
CParserTOML parser(ssContent);
|
||||
|
||||
// Check for config file compatibility
|
||||
auto ptrConfigVersion = parser.GetRoot().GetDirect("Configuration.Version");
|
||||
if (!ptrConfigVersion)
|
||||
{
|
||||
SDV_LOG_ERROR("Configuration version must be present in the configuration file.");
|
||||
return sdv::core::EConfigProcessResult::failed;
|
||||
}
|
||||
if (ptrConfigVersion->GetValue() != SDVFrameworkInterfaceVersion)
|
||||
{
|
||||
SDV_LOG_ERROR("Incompatible configuration file version.");
|
||||
return sdv::core::EConfigProcessResult::failed;
|
||||
}
|
||||
|
||||
// If this is not a main, isolated or maintenance application, load all modules in the component and module sections.
|
||||
std::map<std::filesystem::path, sdv::core::TModuleID> mapModules;
|
||||
size_t nLoadable = 0;
|
||||
size_t nNotLoadable = 0;
|
||||
if (!GetAppControl().IsMainApplication() && !GetAppControl().IsIsolatedApplication() &&
|
||||
!GetAppControl().IsMaintenanceApplication())
|
||||
{
|
||||
// Load all modules in the component section
|
||||
auto ptrComponents = parser.GetRoot().GetDirect("Component");
|
||||
if (ptrComponents && ptrComponents->GetArray())
|
||||
{
|
||||
for (uint32_t uiIndex = 0; uiIndex < ptrComponents->GetArray()->GetCount(); uiIndex++)
|
||||
{
|
||||
auto ptrComponent = ptrComponents->GetArray()->Get(uiIndex);
|
||||
if (ptrComponent)
|
||||
{
|
||||
// Get the information from the component.
|
||||
std::filesystem::path pathModule;
|
||||
auto ptrModule = ptrComponent->GetDirect("Path");
|
||||
if (ptrModule) pathModule = static_cast<std::string>(ptrModule->GetValue());
|
||||
|
||||
// If there is no path, this is allowed... skip this module
|
||||
if (pathModule.empty()) continue;
|
||||
|
||||
// Load the module.
|
||||
sdv::core::TModuleID tModuleID = GetModuleControl().Load(pathModule.generic_u8string());
|
||||
if (tModuleID)
|
||||
{
|
||||
mapModules[pathModule] = tModuleID;
|
||||
nLoadable++;
|
||||
}
|
||||
else
|
||||
{
|
||||
SDV_LOG_ERROR("Failed to load module: ", pathModule);
|
||||
nNotLoadable++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load all modules from the module section.
|
||||
auto ptrModules = parser.GetRoot().GetDirect("Module");
|
||||
if (ptrModules && ptrModules->GetArray())
|
||||
{
|
||||
for (uint32_t uiIndex = 0; uiIndex < ptrModules->GetArray()->GetCount(); uiIndex++)
|
||||
{
|
||||
auto ptrModule = ptrModules->GetArray()->Get(uiIndex);
|
||||
if (ptrModule)
|
||||
{
|
||||
// Get the information from the component.
|
||||
std::filesystem::path pathModule;
|
||||
auto ptrModulePath = ptrModule->GetDirect("Path");
|
||||
if (ptrModulePath) pathModule = static_cast<std::string>(ptrModulePath->GetValue());
|
||||
|
||||
if (pathModule.empty())
|
||||
{
|
||||
SDV_LOG_ERROR("Missing module path for component configuration.");
|
||||
nNotLoadable++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Load the module.
|
||||
sdv::core::TModuleID tModuleID = GetModuleControl().Load(pathModule.generic_u8string());
|
||||
if (tModuleID)
|
||||
{
|
||||
mapModules[pathModule] = tModuleID;
|
||||
nLoadable++;
|
||||
}
|
||||
else
|
||||
{
|
||||
SDV_LOG_ERROR("Failed to load module: ", pathModule);
|
||||
nNotLoadable++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start all components from the component section
|
||||
std::filesystem::path pathLastModule;
|
||||
auto ptrComponents = parser.GetRoot().GetDirect("Component");
|
||||
if (ptrComponents && ptrComponents->GetArray())
|
||||
{
|
||||
for (uint32_t uiIndex = 0; uiIndex < ptrComponents->GetArray()->GetCount(); uiIndex++)
|
||||
{
|
||||
auto ptrComponent = ptrComponents->GetArray()->Get(uiIndex);
|
||||
if (ptrComponent)
|
||||
{
|
||||
// Get the information from the component.
|
||||
std::filesystem::path pathModule;
|
||||
auto ptrModule = ptrComponent->GetDirect("Path");
|
||||
if (ptrModule) pathModule = static_cast<std::string>(ptrModule->GetValue());
|
||||
std::string ssClass;
|
||||
auto ptrClass = ptrComponent->GetDirect("Class");
|
||||
if (ptrClass) ssClass = static_cast<std::string>(ptrClass->GetValue());
|
||||
std::string ssName;
|
||||
auto ptrName = ptrComponent->GetDirect("Name");
|
||||
if (ptrName) ssName = static_cast<std::string>(ptrName->GetValue());
|
||||
|
||||
// If there is a path, store it. If there is none, take the last stored
|
||||
if (!pathModule.empty())
|
||||
pathLastModule = pathModule;
|
||||
else
|
||||
pathModule = pathLastModule;
|
||||
|
||||
if (pathModule.empty())
|
||||
{
|
||||
SDV_LOG_ERROR("Missing module path for component configuration.");
|
||||
nNotLoadable++;
|
||||
continue;
|
||||
}
|
||||
if (ssClass.empty())
|
||||
{
|
||||
SDV_LOG_ERROR("Missing component class name in the configuration.");
|
||||
nNotLoadable++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// In case of a main, isolated or maintenance application, ignore the module. In all other cases, the module was
|
||||
// loaded; get the module ID.
|
||||
sdv::core::TObjectID tObjectID = 0;
|
||||
if (!GetAppControl().IsMainApplication() && !GetAppControl().IsIsolatedApplication() &&
|
||||
!GetAppControl().IsMaintenanceApplication())
|
||||
{
|
||||
auto itModule = mapModules.find(pathModule);
|
||||
if (itModule == mapModules.end()) continue; // Module was not loaded before...
|
||||
tObjectID = GetRepository().CreateObjectFromModule(itModule->second, ssClass, ssName, ptrComponent->CreateTOMLText());
|
||||
}
|
||||
else
|
||||
tObjectID = GetRepository().CreateObject2(ssClass, ssName, ptrComponent->CreateTOMLText());
|
||||
|
||||
if (!tObjectID)
|
||||
{
|
||||
SDV_LOG_ERROR("Failed to load component: ", ssClass);
|
||||
nNotLoadable++;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
nLoadable++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!nNotLoadable)
|
||||
return sdv::core::EConfigProcessResult::successful;
|
||||
if (!nLoadable)
|
||||
return sdv::core::EConfigProcessResult::failed;
|
||||
return sdv::core::EConfigProcessResult::partially_successful;
|
||||
}
|
||||
|
||||
sdv::core::EConfigProcessResult CAppConfig::LoadConfig(/*in*/ const sdv::u8string& ssConfigPath)
|
||||
{
|
||||
if (GetAppControl().GetOperationState() != sdv::app::EAppOperationState::configuring)
|
||||
return sdv::core::EConfigProcessResult::failed;
|
||||
if (ssConfigPath.empty())
|
||||
return sdv::core::EConfigProcessResult::failed;
|
||||
|
||||
std::filesystem::path pathConfig;
|
||||
if (GetAppControl().IsMainApplication())
|
||||
{
|
||||
pathConfig = GetAppControl().GetInstallDir() / static_cast<std::string>(ssConfigPath);
|
||||
if (!std::filesystem::exists(pathConfig) || !std::filesystem::is_regular_file(pathConfig))
|
||||
pathConfig.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
AddCurrentPath();
|
||||
|
||||
try
|
||||
{
|
||||
// Get the search paths if the module path is relative
|
||||
std::list<std::filesystem::path> lstSearchPaths;
|
||||
std::filesystem::path pathSupplied(static_cast<std::string>(ssConfigPath));
|
||||
if (pathSupplied.is_relative())
|
||||
lstSearchPaths = m_lstSearchPaths;
|
||||
|
||||
// Add an empty path to allow the OS to search when our own search paths could not find the module.
|
||||
lstSearchPaths.push_back(std::filesystem::path());
|
||||
|
||||
// Run through the search paths and try to find the config file.
|
||||
for (const std::filesystem::path& rpathDirectory : lstSearchPaths)
|
||||
{
|
||||
// Compose the path
|
||||
std::filesystem::path pathConfigTemp;
|
||||
if (rpathDirectory.is_absolute())
|
||||
pathConfigTemp = (rpathDirectory / pathSupplied).lexically_normal();
|
||||
else
|
||||
{
|
||||
if (rpathDirectory.empty())
|
||||
pathConfigTemp = pathSupplied.lexically_normal();
|
||||
else
|
||||
pathConfigTemp = (GetExecDirectory() / rpathDirectory / pathSupplied).lexically_normal();
|
||||
}
|
||||
if (std::filesystem::exists(pathConfigTemp))
|
||||
{
|
||||
pathConfig = pathConfigTemp;
|
||||
m_pathLastSearchDir = rpathDirectory;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception& re)
|
||||
{
|
||||
SDV_LOG_ERROR("Supplied path to config load is not valid \"", ssConfigPath, "\": ", re.what());
|
||||
return sdv::core::EConfigProcessResult::failed;
|
||||
}
|
||||
}
|
||||
if (pathConfig.empty())
|
||||
{
|
||||
SDV_LOG_ERROR("Configuration file was not found \"", ssConfigPath, "\"");
|
||||
return sdv::core::EConfigProcessResult::failed;
|
||||
}
|
||||
|
||||
std::ifstream fstream(pathConfig);
|
||||
if (!fstream.is_open()) return sdv::core::EConfigProcessResult::failed;
|
||||
std::string ssContent((std::istreambuf_iterator<char>(fstream)), std::istreambuf_iterator<char>());
|
||||
return ProcessConfig(ssContent);
|
||||
}
|
||||
|
||||
bool CAppConfig::SaveConfig(/*in*/ const sdv::u8string& ssConfigPath) const
|
||||
{
|
||||
std::filesystem::path pathConfig = static_cast<std::string>(ssConfigPath);
|
||||
if (pathConfig.is_relative())
|
||||
{
|
||||
if (GetAppControl().IsMainApplication())
|
||||
pathConfig = GetAppControl().GetInstallDir() / pathConfig;
|
||||
else
|
||||
{
|
||||
if (m_pathLastSearchDir.empty())
|
||||
pathConfig = GetExecDirectory() / pathConfig;
|
||||
else
|
||||
pathConfig = m_pathLastSearchDir / pathConfig;
|
||||
}
|
||||
}
|
||||
|
||||
std::ofstream fstream(pathConfig, std::ios::out | std::ios::trunc);
|
||||
if (!fstream.is_open())
|
||||
{
|
||||
SDV_LOG_ERROR("Cannot open config file \"", ssConfigPath, "\"");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write header
|
||||
fstream << "[Configuration]" << std::endl;
|
||||
fstream << "Version = " << SDVFrameworkInterfaceVersion << " # Configuration file version." << std::endl;
|
||||
|
||||
// Write all objects and modules
|
||||
fstream << GetRepository().SaveConfig();
|
||||
|
||||
fstream.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAppConfig::AddConfigSearchDir(/*in*/ const sdv::u8string& ssDir)
|
||||
{
|
||||
if (GetAppControl().GetOperationState() != sdv::app::EAppOperationState::configuring) return false;
|
||||
|
||||
// Add initial paths if not done so already
|
||||
AddCurrentPath();
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxSearchPaths);
|
||||
|
||||
std::filesystem::path pathDir(ssDir.c_str());
|
||||
|
||||
// Relative paths are always relative of the executable
|
||||
if (pathDir.is_relative())
|
||||
pathDir = GetExecDirectory() / ssDir.c_str();
|
||||
|
||||
// Remove any indirections
|
||||
pathDir = pathDir.lexically_normal();
|
||||
|
||||
// If the current path is not a directory, it cannot be added
|
||||
if (!std::filesystem::is_directory(pathDir)) return false;
|
||||
|
||||
// Check whether the path is already in the list
|
||||
if (std::find(m_lstSearchPaths.begin(), m_lstSearchPaths.end(), pathDir) != m_lstSearchPaths.end())
|
||||
return true; // This is not an error
|
||||
|
||||
// Add the path
|
||||
m_lstSearchPaths.push_back(pathDir);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CAppConfig::ResetConfigBaseline()
|
||||
{
|
||||
GetRepository().ResetConfigBaseline();
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// Prevent bogus warning about uninitialized memory for the variable *hFile.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 6001)
|
||||
#endif
|
||||
|
||||
bool CAppConfig::Install(/*in*/ const sdv::u8string& ssInstallName, /*in*/ const sdv::sequence<sdv::installation::SFileDesc>& seqFiles)
|
||||
{
|
||||
// Installations can only be done in the main application.
|
||||
if (!GetAppControl().IsMainApplication()) return false;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxInstallations);
|
||||
|
||||
// Check whether the installation already exists. If so, cancel the installation.
|
||||
if (m_mapInstallations.find(ssInstallName) != m_mapInstallations.end())
|
||||
{
|
||||
throw sdv::installation::XDuplicateInstall();
|
||||
}
|
||||
|
||||
// Rollback class - automatically reverses the loading.
|
||||
class CRollback
|
||||
{
|
||||
public:
|
||||
~CRollback()
|
||||
{
|
||||
bool bSuccess = true;
|
||||
for (sdv::core::TModuleID tModuleID : m_vecLoadedModules)
|
||||
{
|
||||
try
|
||||
{
|
||||
bSuccess &= GetModuleControl().ContextUnload(tModuleID, true);
|
||||
}
|
||||
catch (const sdv::XSysExcept&)
|
||||
{
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
}
|
||||
}
|
||||
if (!bSuccess)
|
||||
{
|
||||
SDV_LOG_ERROR("Failed to rollback \"", m_pathInstallRoot, "\".");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_pathInstallRoot.empty())
|
||||
{
|
||||
try
|
||||
{
|
||||
std::filesystem::remove_all(m_pathInstallRoot);
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error& rexcept)
|
||||
{
|
||||
SDV_LOG_ERROR("Failed to rollback \"", m_pathInstallRoot, "\": ", rexcept.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
void Commit()
|
||||
{
|
||||
// Do not roll back.
|
||||
m_pathInstallRoot.clear();
|
||||
m_vecLoadedModules.clear();
|
||||
}
|
||||
void SetInstallPath(const std::filesystem::path& rPathInstallDir) { m_pathInstallRoot = rPathInstallDir; }
|
||||
void AddLoadedModule(sdv::core::TModuleID tModuleID) { m_vecLoadedModules.push_back(tModuleID); }
|
||||
private:
|
||||
std::filesystem::path m_pathInstallRoot; ///< Installation root directory
|
||||
std::vector<sdv::core::TModuleID> m_vecLoadedModules; ///< loaded modules
|
||||
} rollback;
|
||||
|
||||
std::filesystem::path pathInstallation = GetAppControl().GetInstallDir() / static_cast<std::string>(ssInstallName);
|
||||
std::vector<std::filesystem::path> vecComponents;
|
||||
try
|
||||
{
|
||||
// A sub-directory with the name must not already exist, if so, delete it.
|
||||
if (std::filesystem::exists(pathInstallation))
|
||||
std::filesystem::remove_all(pathInstallation);
|
||||
|
||||
// Create a directory for the installation
|
||||
std::filesystem::create_directories(pathInstallation);
|
||||
rollback.SetInstallPath(pathInstallation);
|
||||
|
||||
// Add each file
|
||||
for (const sdv::installation::SFileDesc& rsFileDesc : seqFiles)
|
||||
{
|
||||
// File path
|
||||
std::filesystem::path pathFileDir = pathInstallation;
|
||||
//if (!rsFileDesc.ssRelativeDir.empty())
|
||||
//{
|
||||
// std::filesystem::path pathTemp = static_cast<std::string>(rsFileDesc.ssRelativeDir);
|
||||
// if (pathTemp.is_absolute() || !pathTemp.is_relative())
|
||||
// throw sdv::installation::XIncorrectPath();
|
||||
// pathFileDir /= pathTemp;
|
||||
// if (!std::filesystem::exists(pathFileDir))
|
||||
// std::filesystem::create_directories(pathFileDir);
|
||||
//}
|
||||
std::filesystem::path pathFile = pathFileDir;
|
||||
if (std::filesystem::exists(pathFile))
|
||||
throw sdv::installation::XIncorrectPath();
|
||||
|
||||
// TODO EVE
|
||||
//// Check the file CRC
|
||||
//sdv::crcCRC32C crc;
|
||||
//uint32_t uiCRC = crc.calc_checksum(rsFileDesc.ptrContent.get(), rsFileDesc.ptrContent.size());
|
||||
//if (uiCRC != rsFileDesc.uiCRC32c)
|
||||
// throw sdv::installation::XIncorrectCRC();
|
||||
|
||||
// Create the file
|
||||
std::ofstream fstream(pathFile, std::ios_base::out | std::ios_base::binary);
|
||||
if (!fstream.is_open()) throw sdv::installation::XIncorrectPath();
|
||||
if (rsFileDesc.ptrContent.size())
|
||||
fstream.write(reinterpret_cast<const char*>(rsFileDesc.ptrContent.get()), rsFileDesc.ptrContent.size());
|
||||
fstream.close();
|
||||
|
||||
// Set file times
|
||||
// Note: use dedicated OS functions. C++11 filesystem library doesn't define a proper way to convert between UNIX time
|
||||
// and the time defined by the C++11 functions. This deficit has been solved in C++20.
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows uses file access for this.
|
||||
HANDLE hFile = CreateFile(pathFile.native().c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (hFile == nullptr || hFile == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
SDV_LOG_ERROR("Cannot create files or directories in \"", pathInstallation, "\"");
|
||||
throw sdv::installation::XIncorrectPath();
|
||||
}
|
||||
|
||||
// Set the filetime on the file.
|
||||
// The filetime starts counting from 1st of January 1601. The resolution is 100ns.
|
||||
// The UNIX time starts counting from 1st of January 1970. The resolution is 1s.
|
||||
// Both times ignore leap seconds.
|
||||
// Thus the UNIX time is running 11644473600 seconds behind the filetime and is a factor 10000000 larger.
|
||||
ULARGE_INTEGER sCreateTime{}, sChangeTime{};
|
||||
sCreateTime.QuadPart = (rsFileDesc.uiCreationDate + 11644473600ull) * 10000000ull;
|
||||
FILETIME ftCreateTime{ sCreateTime.LowPart, sChangeTime.HighPart };
|
||||
sChangeTime.QuadPart = (rsFileDesc.uiChangeDate + 11644473600ull) * 10000000ull;
|
||||
FILETIME ftChangeTime{ sChangeTime.LowPart, sChangeTime.HighPart };
|
||||
BOOL bRes = SetFileTime(hFile, &ftCreateTime, nullptr, &ftChangeTime);
|
||||
|
||||
// Close our handle.
|
||||
CloseHandle(hFile);
|
||||
|
||||
if (!bRes)
|
||||
{
|
||||
SDV_LOG_ERROR("Cannot create files or directories in \"", pathInstallation, "\"");
|
||||
throw sdv::installation::XIncorrectPath();
|
||||
}
|
||||
#elif defined __unix__
|
||||
// Note: POSIX doesn't define the creation time.
|
||||
utimbuf sTimes{};
|
||||
sTimes.actime = std::time(0);
|
||||
sTimes.modtime = static_cast<time_t>(rsFileDesc.uiChangeDate);
|
||||
if (utime(pathFile.native().c_str(), &sTimes))
|
||||
{
|
||||
SDV_LOG_ERROR("Cannot create files or directories in \"", pathInstallation, "\"");
|
||||
throw sdv::installation::XIncorrectPath();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Set attributes
|
||||
if (rsFileDesc.bAttrExecutable)
|
||||
std::filesystem::permissions(pathFile, std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec |
|
||||
std::filesystem::perms::others_exec, std::filesystem::perm_options::add);
|
||||
if (rsFileDesc.bAttrReadonly)
|
||||
std::filesystem::permissions(pathFile, std::filesystem::perms::owner_write | std::filesystem::perms::group_write |
|
||||
std::filesystem::perms::others_write, std::filesystem::perm_options::remove);
|
||||
|
||||
// Component paths are stored
|
||||
//if (rsFileDesc.bAttrComponent) vecComponents.push_back(pathFile);
|
||||
}
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error& rexcept)
|
||||
{
|
||||
SDV_LOG_ERROR("Cannot create files or directories in \"", pathInstallation, "\": ", rexcept.what());
|
||||
throw sdv::installation::XIncorrectPath();
|
||||
}
|
||||
|
||||
// TODO: Restriction. Only complex services will be started dynamically and will be added to the configuration. Utilities will
|
||||
// also be installed but will not be started. Devices and basic services will not be available this way.
|
||||
|
||||
// TODO:
|
||||
// For each component, get the manifest using the manifest helper utility.
|
||||
// Note: to get the manifest, the component module needs to be loaded and the GetManifest exported function is called. Loading
|
||||
// a component module causes part of the component to start already and dependencies to be resolved. If this malfunctions, the
|
||||
// component could crash or do other malicious things. OR if on purpose, an application or complex service component, that
|
||||
// normally would run in an isolated environment, gets access to the memory and everything else within the process. If this
|
||||
// module is not isolated from the core, it could access the core and do nasty things. THEREFORE, the manifest helper utility is
|
||||
// started as a component in its own isolated environment and then loading the component. No component code is executed within
|
||||
// the core process.
|
||||
sdv::TObjectPtr ptrManifestUtil = sdv::core::CreateUtility("ManifestHelperUtility");
|
||||
sdv::helper::IModuleManifestHelper* pManifestHelper = ptrManifestUtil.GetInterface<sdv::helper::IModuleManifestHelper>();
|
||||
if (!pManifestHelper)
|
||||
{
|
||||
SDV_LOG_ERROR("Manifest helper utility cannot be loaded.");
|
||||
throw sdv::installation::XComponentNotLoadable();
|
||||
}
|
||||
|
||||
// Get the manifest of all components
|
||||
std::map<std::filesystem::path, sdv::u8string> mapComponentManifests;
|
||||
for (const std::filesystem::path& rpathComponent : vecComponents)
|
||||
{
|
||||
sdv::u8string ssManifest = pManifestHelper->ReadModuleManifest(rpathComponent.generic_u8string());
|
||||
if (!ssManifest.empty())
|
||||
mapComponentManifests[rpathComponent] = ssManifest;
|
||||
}
|
||||
|
||||
// Check whether there ary any manifests
|
||||
if (mapComponentManifests.empty())
|
||||
{
|
||||
SDV_LOG_ERROR("No component in installation", ssInstallName, ".");
|
||||
throw sdv::installation::XNoManifest();
|
||||
}
|
||||
|
||||
// Load the modules
|
||||
bool bSuccess = true;
|
||||
for (const auto& rvtModule : mapComponentManifests)
|
||||
{
|
||||
sdv::core::TModuleID tModuleID = GetModuleControl().ContextLoad(rvtModule.first, rvtModule.second);
|
||||
if (!tModuleID)
|
||||
{
|
||||
bSuccess = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: What about the dependent modules that were started as well. They are not rolled back...
|
||||
rollback.AddLoadedModule(tModuleID);
|
||||
}
|
||||
if (!bSuccess)
|
||||
{
|
||||
SDV_LOG_ERROR("Manifest helper utility cannot be loaded.");
|
||||
throw sdv::installation::XComponentNotLoadable();
|
||||
}
|
||||
|
||||
// Add the installation to the installation map
|
||||
//SInstallation sInstallation{ seqFiles };
|
||||
//for (sdv::core::)
|
||||
//m_mapInstallations[ssInstallName] = SInstallation{ seqFiles };
|
||||
|
||||
// Commit the changes - prevent rollback
|
||||
rollback.Commit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
bool CAppConfig::Update(/*in*/ const sdv::u8string& ssInstallName, /*in*/ const sdv::sequence<sdv::installation::SFileDesc>& seqFiles)
|
||||
{
|
||||
// TODO: as soon as the configuration is updatable as well, the update uses the same configuration and possibly updates the
|
||||
// configuration.
|
||||
// Until the config update feature is available, the update can stop and update and start the installation by uninstall and
|
||||
// install.
|
||||
|
||||
bool bRet = Uninstall(ssInstallName);
|
||||
if (bRet) bRet = Install(ssInstallName, seqFiles);
|
||||
return bRet;
|
||||
}
|
||||
|
||||
bool CAppConfig::Uninstall(/*in*/ const sdv::u8string& /*ssInstallName*/)
|
||||
{
|
||||
// Installations can only be done in the main application.
|
||||
if (!GetAppControl().IsMainApplication()) return false;
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::u8string> CAppConfig::GetInstallations() const
|
||||
{
|
||||
return sdv::sequence<sdv::u8string>();
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::installation::SFileDesc> CAppConfig::GetInstallationFiles(/*in*/ const sdv::u8string& /*ssInstallName*/) const
|
||||
{
|
||||
return sdv::sequence<sdv::installation::SFileDesc>();
|
||||
}
|
||||
|
||||
std::filesystem::path CAppConfig::FindInstalledModule(const std::filesystem::path& rpathRelModule)
|
||||
{
|
||||
// Search for the module in the following order:
|
||||
// - Core manifest
|
||||
// - Exe manifest
|
||||
// - User manifests
|
||||
|
||||
std::filesystem::path pathModule = m_manifestCore.FindModule(rpathRelModule);
|
||||
if (pathModule.empty())
|
||||
pathModule = m_manifestExe.FindModule(rpathRelModule);
|
||||
for (const CInstallManifest& rmanifest : m_vecUserManifests)
|
||||
{
|
||||
if (!pathModule.empty()) break;
|
||||
pathModule = rmanifest.FindModule(rpathRelModule);
|
||||
}
|
||||
return pathModule;
|
||||
}
|
||||
|
||||
std::string CAppConfig::FindInstalledModuleManifest(const std::filesystem::path& rpathRelModule)
|
||||
{
|
||||
// Search for the module in the following order:
|
||||
// - Core manifest
|
||||
// - Exe manifest
|
||||
// - User manifests
|
||||
|
||||
std::string ssManifest = m_manifestCore.FindModuleManifest(rpathRelModule);
|
||||
if (ssManifest.empty())
|
||||
ssManifest = m_manifestExe.FindModuleManifest(rpathRelModule);
|
||||
for (const CInstallManifest& rmanifest : m_vecUserManifests)
|
||||
{
|
||||
if (!ssManifest.empty()) break;
|
||||
ssManifest = rmanifest.FindModuleManifest(rpathRelModule);
|
||||
}
|
||||
return ssManifest;
|
||||
}
|
||||
|
||||
std::optional<CInstallManifest::SComponent> CAppConfig::FindInstalledComponent(const std::string& rssClass) const
|
||||
{
|
||||
// For main and isolated applications, check whether the module is in one of the installation manifests.
|
||||
if (!GetAppControl().IsMainApplication() && !GetAppControl().IsIsolatedApplication()) return {};
|
||||
|
||||
// Search for the component in the following order:
|
||||
// - Core manifest
|
||||
// - Exe manifest
|
||||
// - User manifests
|
||||
auto optManifest = m_manifestCore.FindComponentByClass(rssClass);
|
||||
if (!optManifest) optManifest = m_manifestExe.FindComponentByClass(rssClass);
|
||||
for (const CInstallManifest& rmanifest : m_vecUserManifests)
|
||||
{
|
||||
if (optManifest) break;
|
||||
optManifest = rmanifest.FindComponentByClass(rssClass);
|
||||
}
|
||||
return optManifest;
|
||||
}
|
||||
|
||||
void CAppConfig::AddCurrentPath()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxSearchPaths);
|
||||
|
||||
// Do this only once.
|
||||
if (!m_lstSearchPaths.empty()) return;
|
||||
|
||||
// Add the core directory
|
||||
std::filesystem::path pathCoreDir = GetCoreDirectory().lexically_normal();
|
||||
m_lstSearchPaths.push_back(pathCoreDir / "config/");
|
||||
|
||||
// Add the exe dir
|
||||
std::filesystem::path pathExeDir = GetExecDirectory().lexically_normal();
|
||||
if (pathExeDir != pathCoreDir) m_lstSearchPaths.push_back(pathExeDir / "config/");
|
||||
}
|
||||
|
||||
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
|
||||
CAppConfig& CAppConfigService::GetAppConfig()
|
||||
{
|
||||
return ::GetAppConfig();
|
||||
}
|
||||
|
||||
bool CAppConfigService::EnableAppConfigAccess()
|
||||
{
|
||||
return GetAppControl().IsStandaloneApplication() ||
|
||||
GetAppControl().IsEssentialApplication();
|
||||
}
|
||||
|
||||
bool CAppConfigService::EnableAppInstallAccess()
|
||||
{
|
||||
return GetAppControl().IsMainApplication();
|
||||
}
|
||||
|
||||
#endif // !defined DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
299
sdv_services/core/app_config.h
Normal file
299
sdv_services/core/app_config.h
Normal file
@@ -0,0 +1,299 @@
|
||||
#ifndef APP_CONFIG_H
|
||||
#define APP_CONFIG_H
|
||||
|
||||
#include "app_control.h"
|
||||
#include "installation_manifest.h"
|
||||
#include <interfaces/config.h>
|
||||
#include <mutex>
|
||||
#include <list>
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
// @cond DOXYGEN_IGNORE
|
||||
// Components are installed using two procedures:
|
||||
// Procedure 1: by loading a configuration file
|
||||
// mainly done when running standalone or in essential mode
|
||||
// Procedure 2: by installing using an interface
|
||||
// mainly done when running as server
|
||||
//
|
||||
// The installation of components must be executed using a separated process to ensure the components are not interfering with
|
||||
// the main process during installation.
|
||||
// The component information is stored in the manifest, exposed through an exported function, and read during installation and
|
||||
// instantiation.
|
||||
// Installed files have attributes: creation and change dates, executable file flag, CRC, relative sub-directory, compoonent flag.
|
||||
// The installation might need companion files to be installed in various relative sub-directories.
|
||||
// @endcond
|
||||
|
||||
/**
|
||||
* @brief Application configuration service
|
||||
* @details In the configuration system objects, devices, basic services, complex services and apps are defined and will be started
|
||||
* suring the load process. The objects are loaded in this order (system objects first, apps last) unless dependencies define
|
||||
* a different order. Utilities, proxy and stub objects are not specified here, since they can be started 'on-the-fly' if needed.
|
||||
* Loading a configuration extends the current configuration. Saving a configuration includes all objects since the last
|
||||
* configuration not including the components present before the last load.
|
||||
*
|
||||
* The configuration file is a TOML file with the following format:
|
||||
* @code
|
||||
* [Configuration]
|
||||
* Version = 100 # Configuration file version.
|
||||
*
|
||||
* [[Component]]
|
||||
* Path = "my_module.sdv # Relative to the executable or absolute path to the module - not used for main and isolated
|
||||
* # applications since the components must be installed.
|
||||
* Class = "MyComponent" # Class name of the component.
|
||||
* Name = "MyPersonalComponent" # Optional instance name of the component. If not provided, the name will automatically be the
|
||||
* # default name of the component or if not available the class name of the component.
|
||||
* AttributeXYZ = 123 # Additional settings for the component provided during initialization.
|
||||
*
|
||||
* [[Component]]
|
||||
* Class = "MyComponent2" # Class name of the component - if also present in "my_module.sdv" doesn't need additional
|
||||
* # 'Path' value. The component name is taken from the default name of the component.
|
||||
*
|
||||
* [[Component]]
|
||||
* Class = "MyComponent" # Class name of the component; class is the same as before.
|
||||
* Name = "MyPersonalComponent2" # Specific name identifying another instance of the component.
|
||||
*
|
||||
* [[Module]]
|
||||
* Path = "my_other_module.sdv # Relative to the executable or absolute path to the module - not used for main and isolated
|
||||
* # applications since the components must be installed. This path might contain components not
|
||||
* # started, but known by the repository (utilities).
|
||||
* @endcode
|
||||
*
|
||||
* For the main application there are several configuration files:
|
||||
* - Platform configuration (OS support, middleware support, vehicle bus and Ethernet interface configuration - system objects)
|
||||
* - Vehicle interface configuration (DBC, port assignments - devices)
|
||||
* - Vehicle abstraction interface configuration (basic services)
|
||||
* - Application configuration (complex services and applications)
|
||||
*
|
||||
* If components are added dynamically (or removed dynamically) they are added to and removed from the application configuration.
|
||||
* The configuration files are located in the installation directory.
|
||||
*
|
||||
* TODO: Add additional interface function: set component operation mode - config, running...
|
||||
* When switching to config mode, switch the apps, complex services, basic services, devices and system objects (in that order) to config mode.
|
||||
* When switching back to running mode, enable running mode for the system objects, devices, base services, complex services and apps (in that order).
|
||||
*
|
||||
*/
|
||||
class CAppConfig : public sdv::IInterfaceAccess, public sdv::core::IConfig, public sdv::installation::IAppInstall
|
||||
{
|
||||
public:
|
||||
CAppConfig() = default;
|
||||
|
||||
// Interface map
|
||||
// Note: only add globally accessible interfaces here (which are not limited by the type of application).
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Load the installation manifests for core, executable and user components. Main and isolated applications only.
|
||||
* @return Returns 'true when successful; 'false' when not.
|
||||
*/
|
||||
bool LoadInstallationManifests();
|
||||
|
||||
/**
|
||||
* @brief Unload all manifest during shutdown.
|
||||
*/
|
||||
void UnloadInstallatonManifests();
|
||||
|
||||
/**
|
||||
* @brief Process the provided configuration by loading modules and creating objects/stubs/proxies defined in the
|
||||
* configuration string. Processing a configuration resets the baseline before processing. Overload of
|
||||
* sdv::core::IConfig::ProcessConfig.
|
||||
* @attention Configuration changes can only occur when the system is in configuration mode.
|
||||
* @param[in] ssContent The contents of the configuration file (TOML).
|
||||
* @return Returns a config process result enum value.
|
||||
*/
|
||||
virtual sdv::core::EConfigProcessResult ProcessConfig(/*in*/ const sdv::u8string& ssContent) override;
|
||||
|
||||
/**
|
||||
* @brief Read file pointed to by the provided file path and load modules and create objects/stubs/proxies as defined
|
||||
* in the configuration file. Loading a configuration resets the baseline before processing. Overload of
|
||||
* sdv::core::IConfig::LoadConfig.
|
||||
* @attention Configuration changes can only occur when the system is in configuration mode.
|
||||
* @param[in] ssConfigPath Path to the file containing the configuration (TOML). The path can be an absolute as well as a
|
||||
* relative path. In case a relative path is provided, the current directory is searched as well as all directories
|
||||
* supplied through the AddConfigSearchDir function.
|
||||
* @return Returns a config process result enum value.
|
||||
*/
|
||||
virtual sdv::core::EConfigProcessResult LoadConfig(/*in*/ const sdv::u8string& ssConfigPath) override;
|
||||
|
||||
/**
|
||||
* @brief Save a configuration file pointed to by the provided file path. All components are saved that were added after
|
||||
* the last baseline with the configuration specific settings. Overload of sdv::core::IConfig::SaveConfig.
|
||||
* @attention Configuration changes can only occur when the system is in configuration mode.
|
||||
* @param[in] ssConfigPath Path to the file containing the configuration (TOML). The path can be an absolute as well as a
|
||||
* relative path. In case a relative path is provided, the configuration is stored relative to the executable directory.
|
||||
* @return Returns 'true' on success; 'false' otherwise.
|
||||
*/
|
||||
virtual bool SaveConfig(/*in*/ const sdv::u8string& ssConfigPath) const override;
|
||||
|
||||
/**
|
||||
* @brief Add a search path to a folder where a config file can be found. Overload of
|
||||
* sdv::core::IConfig::AddDirectorySearchDir.
|
||||
* @param[in] ssDir Relative or absolute path to an existing folder.
|
||||
* @return Returns 'true' on success; 'false' otherwise.
|
||||
*/
|
||||
virtual bool AddConfigSearchDir(/*in*/ const sdv::u8string& ssDir) override;
|
||||
|
||||
/**
|
||||
* @brief Reset the configuration baseline.
|
||||
* @details The configuration baseline determines what belongs to the current configuration. Any object being added
|
||||
* after this baseline will be stored in a configuration file.
|
||||
*/
|
||||
virtual void ResetConfigBaseline() override;
|
||||
|
||||
/**
|
||||
* @brief Make an installation onto the system. Overload of sdv::installation::IAppInstall::Install.
|
||||
* @details Make an installation with from component modules and supporting files. At least one component module must
|
||||
* be provided for this installation to be successful (component attribute flag must be set). Components are only
|
||||
* installed, not activated.
|
||||
* @remarks The system needs to run in configuration mode.
|
||||
* @param[in] ssInstallName Name of the installation. Must be unique to the system.
|
||||
* @param[in] seqFiles Files belonging to the installation. This could be component modules as well as supporting files.
|
||||
* @return Returns whether the installation was successful - installation name was unique and at least one loadable
|
||||
* component was provided.
|
||||
*/
|
||||
virtual bool Install(/*in*/ const sdv::u8string& ssInstallName,
|
||||
/*in*/ const sdv::sequence<sdv::installation::SFileDesc>& seqFiles) override;
|
||||
|
||||
/**
|
||||
* @brief Update an installation. Overload of sdv::installation::IAppInstall::Update.
|
||||
* @details Stops a component if the component is running, makes an update and if the component was running, starts
|
||||
* the component again.
|
||||
* @todo: Currently limited to complex services only.
|
||||
* @remarks The system needs to run in configuration mode.
|
||||
* @param[in] ssInstallName Name of the installation. Must be unique to the system.
|
||||
* @param[in] seqFiles Files belonging to the installation. This could be component modules as well as supporting files.
|
||||
* @return Returns whether the installation was successful - installation name was unique and at least one loadable
|
||||
* component was provided.
|
||||
*/
|
||||
virtual bool Update(/*in*/ const sdv::u8string& ssInstallName,
|
||||
/*in*/ const sdv::sequence<sdv::installation::SFileDesc>& seqFiles) override;
|
||||
|
||||
/**
|
||||
* @brief Uninstall of a previous installation. Overload of sdv::installation::IAppInstall::Uninstall.
|
||||
* @details Stops a component if the component is running and removes the files and modules.
|
||||
* @todo: Currently limited to complex services only.
|
||||
* @remarks The system needs to run in configuration mode.
|
||||
* @param[in] ssInstallName Installation name.
|
||||
* @return Returns whether the uninstallation was successful.
|
||||
*/
|
||||
virtual bool Uninstall(/*in*/ const sdv::u8string& ssInstallName) override;
|
||||
|
||||
/**
|
||||
* @brief Get a sequence of installations. Overload of sdv::installation::IAppInstall::GetInstallations.
|
||||
* @return The sequence with installations.
|
||||
*/
|
||||
virtual sdv::sequence<sdv::u8string> GetInstallations() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the installed files from the installation. Overload of sdv::installation::IAppInstall::GetInstallationFiles.
|
||||
* @param[in] ssInstallName The installation to get the files for.
|
||||
* @return Sequence containing the file structures without the binary file content.
|
||||
*/
|
||||
virtual sdv::sequence<sdv::installation::SFileDesc> GetInstallationFiles(/*in*/ const sdv::u8string& ssInstallName) const override;
|
||||
|
||||
/**
|
||||
* @brief Find the module stored in the installation manifest (core, exe or user installations).
|
||||
* @param[in] rpathRelModule Reference to the path containing the relative path to a module.
|
||||
* @return Returns the full path if the module was found or an empty path when not.
|
||||
*/
|
||||
std::filesystem::path FindInstalledModule(const std::filesystem::path& rpathRelModule);
|
||||
|
||||
/**
|
||||
* @brief Find the module stored in the installation manifest (core, exe or user installations).
|
||||
* @param[in] rpathRelModule Reference to the path containing the relative path to a module.
|
||||
* @return Returns the module manifest if the module was found or an empty string when not.
|
||||
*/
|
||||
std::string FindInstalledModuleManifest(const std::filesystem::path& rpathRelModule);
|
||||
|
||||
/**
|
||||
* @brief Search for the installed component with the specific class name.
|
||||
* @details Find the first component containing a class with the specified name. For main and isolated applications.
|
||||
* The order of checking the installation manifest is core-manifest, manifest in executable directory and manifest in supplied
|
||||
* installation directory.
|
||||
* @remarks Components of system objects specified in the user installation are not returned.
|
||||
* @param[in] rssClass Reference to the class that should be searched for. The class is compared to the class name and the
|
||||
* default name in the manifest.
|
||||
* @return Optionally returns the component manifest.
|
||||
*/
|
||||
std::optional<CInstallManifest::SComponent> FindInstalledComponent(const std::string& rssClass) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Add config folders of the core_services and the executable to the search paths if not done so already.
|
||||
*/
|
||||
void AddCurrentPath();
|
||||
|
||||
/**
|
||||
* @brief Installation structure
|
||||
*/
|
||||
struct SInstallation
|
||||
{
|
||||
sdv::sequence<sdv::installation::SFileDesc> seqFiles; ///< Companion files
|
||||
};
|
||||
|
||||
std::mutex m_mtxSearchPaths; ///< Access protection to directory list.
|
||||
std::list<std::filesystem::path> m_lstSearchPaths; ///< List of search directories.
|
||||
std::filesystem::path m_pathLastSearchDir; ///< The last search directory to also save the file to.
|
||||
std::mutex m_mtxInstallations; ///< Access protection to the installations.
|
||||
CInstallManifest m_manifestCore; ///< Install manifest for core components (main and isolated apps).
|
||||
CInstallManifest m_manifestExe; ///< Install manifest for exe components (main and isolated apps).
|
||||
std::vector<CInstallManifest> m_vecUserManifests; ///< Install manifests for user components (main and isolated apps).
|
||||
std::map<std::string, SInstallation> m_mapInstallations; ///< Installation map.
|
||||
};
|
||||
|
||||
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
|
||||
/**
|
||||
* @brief App config service class.
|
||||
*/
|
||||
class CAppConfigService : public sdv::CSdvObject
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CAppConfigService() = default;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_CHAIN_MEMBER(GetAppConfig())
|
||||
SDV_INTERFACE_SET_SECTION_CONDITION(EnableAppConfigAccess(), 1)
|
||||
SDV_INTERFACE_SECTION(1)
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::IConfig, GetAppConfig())
|
||||
SDV_INTERFACE_DEFAULT_SECTION()
|
||||
SDV_INTERFACE_SET_SECTION_CONDITION(EnableAppInstallAccess(), 2)
|
||||
SDV_INTERFACE_SECTION(2)
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::installation::IAppInstall, GetAppConfig())
|
||||
SDV_INTERFACE_DEFAULT_SECTION()
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
|
||||
DECLARE_OBJECT_CLASS_NAME("ConfigService")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Get access to the application config.
|
||||
* @return Returns the one global instance of the application config.
|
||||
*/
|
||||
static CAppConfig& GetAppConfig();
|
||||
|
||||
/**
|
||||
* @brief When set, the application control access will be enabled.
|
||||
* @return Returns 'true' when the access to the application configuration is granted; otherwise returns 'false'.
|
||||
*/
|
||||
static bool EnableAppConfigAccess();
|
||||
|
||||
/**
|
||||
* @brief When set, the application install will be enabled.
|
||||
* @return Returns whether access to the application install is granted.
|
||||
*/
|
||||
static bool EnableAppInstallAccess();
|
||||
};
|
||||
DEFINE_SDV_OBJECT_NO_EXPORT(CAppConfigService)
|
||||
|
||||
#endif
|
||||
|
||||
#endif // !defined APP_CONFIG_H
|
||||
975
sdv_services/core/app_control.cpp
Normal file
975
sdv_services/core/app_control.cpp
Normal file
@@ -0,0 +1,975 @@
|
||||
#include "app_control.h"
|
||||
#include "module_control.h"
|
||||
#include "repository.h"
|
||||
#include "sdv_core.h"
|
||||
#include "../../global/exec_dir_helper.h"
|
||||
#include "../../global/base64.h"
|
||||
#include "../../global/flags.h"
|
||||
#include "../../global/tracefifo/trace_fifo.cpp"
|
||||
#include "toml_parser/parser_toml.h"
|
||||
#include "local_shutdown_request.h"
|
||||
|
||||
const char* szSettingsTemplate = R"code(# Settings file
|
||||
[Settings]
|
||||
Version = 100
|
||||
|
||||
# The system config array can contain zero or more configurations that are loaded at the time
|
||||
# the system ist started. It is advisable to split the configurations in:
|
||||
# platform config - containing all the components needed to interact with the OS,
|
||||
# middleware, vehicle bus, Ethernet.
|
||||
# vehicle interface - containing the vehicle bus interpretation components like data link
|
||||
# based on DBC and devices for their abstraction.
|
||||
# vehicle abstraction - containing the basic services
|
||||
# Load the system configurations by providing the "SystemConfig" keyword as an array of strings.
|
||||
# A relative path is relative to the installation directory (being "exe_location/instance_id").
|
||||
#
|
||||
# Example:
|
||||
# SystemConfig = [ "platform.toml", "vehicle_ifc.toml", "vehicle_abstract.toml" ]
|
||||
#
|
||||
|
||||
# The application config contains the configuration file that can be updated when services and
|
||||
# apps are being added to the system (or being removed from the system). Load the application
|
||||
# config by providing the "AppConfig" keyword as a string value. A relative path is relative to
|
||||
# the installation directory (being "exe_location/instance_id").
|
||||
#
|
||||
# Example
|
||||
# AppConfig = "app_config.toml"
|
||||
)code";
|
||||
|
||||
/**
|
||||
* @brief Specific exit handler helping to shut down after startup. In case the shutdown wasn't explicitly executed.
|
||||
*/
|
||||
void ExitHandler()
|
||||
{
|
||||
// Check if the system was properly shut down
|
||||
switch (GetAppControl().GetOperationState())
|
||||
{
|
||||
case sdv::app::EAppOperationState::shutting_down:
|
||||
case sdv::app::EAppOperationState::not_started:
|
||||
break;
|
||||
default:
|
||||
GetAppControl().Shutdown(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CAppControl::CAppControl()
|
||||
{}
|
||||
|
||||
CAppControl::~CAppControl()
|
||||
{}
|
||||
|
||||
bool CAppControl::IsMainApplication() const
|
||||
{
|
||||
return m_eContextMode == sdv::app::EAppContext::main;
|
||||
}
|
||||
|
||||
bool CAppControl::IsIsolatedApplication() const
|
||||
{
|
||||
return m_eContextMode == sdv::app::EAppContext::isolated;
|
||||
}
|
||||
|
||||
bool CAppControl::IsStandaloneApplication() const
|
||||
{
|
||||
return m_eContextMode == sdv::app::EAppContext::standalone;
|
||||
}
|
||||
|
||||
bool CAppControl::IsEssentialApplication() const
|
||||
{
|
||||
return m_eContextMode == sdv::app::EAppContext::essential;
|
||||
}
|
||||
|
||||
bool CAppControl::IsMaintenanceApplication() const
|
||||
{
|
||||
return m_eContextMode == sdv::app::EAppContext::maintenance;
|
||||
}
|
||||
|
||||
bool CAppControl::IsExternalApplication() const
|
||||
{
|
||||
return m_eContextMode == sdv::app::EAppContext::external;
|
||||
}
|
||||
|
||||
sdv::app::EAppContext CAppControl::GetContextType() const
|
||||
{
|
||||
return m_eContextMode;
|
||||
}
|
||||
|
||||
uint32_t CAppControl::GetInstanceID() const
|
||||
{
|
||||
return m_uiInstanceID;
|
||||
}
|
||||
|
||||
uint32_t CAppControl::GetRetries() const
|
||||
{
|
||||
return m_uiRetries;
|
||||
}
|
||||
|
||||
bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfaceAccess* pEventHandler)
|
||||
{
|
||||
m_pEvent = pEventHandler ? pEventHandler->GetInterface<sdv::app::IAppEvent>() : nullptr;
|
||||
|
||||
// Intercept the logging...
|
||||
std::stringstream sstreamCOUT, sstreamCLOG, sstreamCERR;
|
||||
std::streambuf* pstreambufCOUT = std::cout.rdbuf(sstreamCOUT.rdbuf());
|
||||
std::streambuf* pstreambufCLOG = std::clog.rdbuf(sstreamCLOG.rdbuf());
|
||||
std::streambuf* pstreambufCERR = std::cerr.rdbuf(sstreamCERR.rdbuf());
|
||||
|
||||
// Write initial startup message
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto in_time_t = std::chrono::system_clock::to_time_t(now);
|
||||
SDV_LOG_INFO("SDV Application start at ", std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %H:%M:%S %Z"));
|
||||
|
||||
BroadcastOperationState(sdv::app::EAppOperationState::initializing);
|
||||
|
||||
// Process the application config.
|
||||
bool bRet = ProcessAppConfig(ssConfig);
|
||||
|
||||
// Undo logging interception
|
||||
sstreamCOUT.rdbuf()->pubsync();
|
||||
sstreamCLOG.rdbuf()->pubsync();
|
||||
sstreamCERR.rdbuf()->pubsync();
|
||||
std::cout.rdbuf(pstreambufCOUT);
|
||||
std::clog.rdbuf(pstreambufCLOG);
|
||||
std::cerr.rdbuf(pstreambufCERR);
|
||||
|
||||
// Process config error...
|
||||
if (!bRet)
|
||||
{
|
||||
std::cout << sstreamCOUT.str();
|
||||
std::clog << sstreamCLOG.str();
|
||||
std::cerr << sstreamCERR.str();
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Cannot continue!" << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open the stream buffer and attach the streams if the application control is initialized as main app.
|
||||
if (m_eContextMode == sdv::app::EAppContext::main)
|
||||
{
|
||||
m_fifoTraceStreamBuffer.SetInstanceID(m_uiInstanceID);
|
||||
m_fifoTraceStreamBuffer.Open(1000);
|
||||
std::cout << "**********************************************" << std::endl;
|
||||
}
|
||||
|
||||
// Stream the starting logging to the output streams...
|
||||
std::cout << sstreamCOUT.str();
|
||||
std::clog << sstreamCLOG.str();
|
||||
std::cerr << sstreamCERR.str();
|
||||
|
||||
// Check for a correctly opened stream buffer
|
||||
if (m_eContextMode == sdv::app::EAppContext::main && !m_fifoTraceStreamBuffer.IsOpened())
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Log streaming could not be initialized; cannot continue!" << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow only one instance if running as main application
|
||||
if (m_eContextMode == sdv::app::EAppContext::main)
|
||||
{
|
||||
m_pathLockFile = GetExecDirectory() / ("sdv_core_" + std::to_string(m_uiInstanceID) + ".lock");
|
||||
#ifdef _WIN32
|
||||
// Checkf or the existence of a lock file. If existing, do not continue.
|
||||
if (std::filesystem::exists(m_pathLockFile)) return false;
|
||||
|
||||
// Create temp file
|
||||
m_pLockFile = _wfopen(m_pathLockFile.native().c_str(), L"a+TD");
|
||||
#elif defined __unix__
|
||||
// Create a lock file if not existing.
|
||||
m_pLockFile = fopen(m_pathLockFile.native().c_str(), "a+");
|
||||
if (!m_pLockFile || lockf(fileno(m_pLockFile), F_TLOCK, 0) != 0)
|
||||
{
|
||||
// File exists already or could not be opened.
|
||||
// No continuation possible.
|
||||
if (m_pLockFile) fclose(m_pLockFile);
|
||||
m_pLockFile = nullptr;
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string ssErrorString;
|
||||
auto fnLoadModule = [&ssErrorString](const sdv::u8string& rssModule) -> bool
|
||||
{
|
||||
bool bLocalRet = GetModuleControl().Load(rssModule);
|
||||
if (!bLocalRet) ssErrorString = std::string("Failed to load module '") + rssModule + "'.";
|
||||
return bLocalRet;
|
||||
};
|
||||
auto fnCreateObject = [&ssErrorString](const sdv::u8string& rssClass, const sdv::u8string& rssObject, const sdv::u8string& rssConfig) -> bool
|
||||
{
|
||||
bool bLocalRet = GetRepository().CreateObject2(rssClass, rssObject, rssConfig);
|
||||
if (!bLocalRet)
|
||||
{
|
||||
ssErrorString = std::string("Failed to instantiate a new object from class '") + rssClass + "'";
|
||||
if (!rssObject.empty())
|
||||
ssErrorString += std::string(" with name '") + rssObject + "'.";
|
||||
}
|
||||
return bLocalRet;
|
||||
};
|
||||
|
||||
// Load the core services
|
||||
if (!fnLoadModule("core_services.sdv"))
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to load the Core Services. Cannot continue!" << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load the logger module if one is specified
|
||||
if (!m_pathLoggerModule.empty())
|
||||
{
|
||||
m_tLoggerModuleID = fnLoadModule(m_pathLoggerModule.u8string());
|
||||
if (!m_tLoggerModuleID)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to load the custom logger. Cannot continue!" << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Start the logger and assign it to the logger control.
|
||||
fnCreateObject(m_ssLoggerClass, m_ssLoggerClass, "");
|
||||
sdv::IInterfaceAccess* pLoggerObj = GetRepository().GetObject(m_ssLoggerClass);
|
||||
if (!pLoggerObj)
|
||||
{
|
||||
GetRepository().DestroyObject2(m_ssLoggerClass);
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to start the logger. Cannot continue!" << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
sdv::core::ILoggerConfig* pLoggerConfig = pLoggerObj->GetInterface<sdv::core::ILoggerConfig>();
|
||||
sdv::core::ILogger* pLogger = pLoggerObj->GetInterface<sdv::core::ILogger>();
|
||||
if (!pLoggerConfig || !pLogger)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to start the logger. Cannot continue!" << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
if (!m_ssProgramTag.empty())
|
||||
pLoggerConfig->SetProgramTag(m_ssProgramTag);
|
||||
pLoggerConfig->SetLogFilter(m_eSeverityFilter, m_eSeverityViewFilter);
|
||||
GetLoggerControl().SetLogger(pLogger);
|
||||
|
||||
// Create the core service objects
|
||||
bRet = fnCreateObject("AppControlService", "AppControlService", "");
|
||||
bRet = bRet && fnCreateObject("RepositoryService", "RepositoryService", "");
|
||||
bRet = bRet && fnCreateObject("ModuleControlService", "ModuleControlService", "");
|
||||
bRet = bRet && fnCreateObject("ConfigService", "ConfigService", "");
|
||||
if (!bRet)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
{
|
||||
std::cerr << "ERROR: Failed to start the Core Services. Cannot continue!" << std::endl;
|
||||
if (!ssErrorString.empty())
|
||||
std::cerr << "REASON: " << ssErrorString << std::endl;
|
||||
}
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load specific services
|
||||
bool bLoadRPCClient = false, bLoadRPCServer = false;
|
||||
bool bLoadInstallationManifests = false;
|
||||
switch (m_eContextMode)
|
||||
{
|
||||
case sdv::app::EAppContext::standalone:
|
||||
break;
|
||||
case sdv::app::EAppContext::external:
|
||||
bLoadRPCClient = true;
|
||||
break;
|
||||
case sdv::app::EAppContext::isolated:
|
||||
bLoadInstallationManifests = true;
|
||||
bLoadRPCClient = true;
|
||||
break;
|
||||
case sdv::app::EAppContext::main:
|
||||
bLoadInstallationManifests = true;
|
||||
bLoadRPCClient = true;
|
||||
bLoadRPCServer = true;
|
||||
break;
|
||||
case sdv::app::EAppContext::essential:
|
||||
//bLoadRPCClient = true;
|
||||
break;
|
||||
case sdv::app::EAppContext::maintenance:
|
||||
bLoadRPCClient = true;
|
||||
break;
|
||||
default:
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Invalid Run-As mode. Cannot continue!" << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load installation manifests for main and isolated applications.
|
||||
if (bLoadInstallationManifests)
|
||||
{
|
||||
if (!GetAppConfig().LoadInstallationManifests())
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to load installation manifests. Cannot continue!" << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Load process control
|
||||
if (bRet) bRet = fnLoadModule("process_control.sdv") ? true : false;
|
||||
if (bRet) bRet = fnCreateObject("ProcessControlService", "", "");
|
||||
|
||||
// Load RPC components
|
||||
if (bRet && bLoadRPCServer)
|
||||
{
|
||||
// Load hardware identification
|
||||
bRet = fnLoadModule("hardware_ident.sdv") ? true : false;
|
||||
if (bRet) bRet = fnCreateObject("HardwareIdentificationService", "", "");
|
||||
|
||||
// Load shared memory channel
|
||||
if (bRet) bRet = fnLoadModule("ipc_shared_mem.sdv") ? true : false;
|
||||
if (bRet) bRet = fnCreateObject("DefaultSharedMemoryChannelControl", "", "");
|
||||
|
||||
// Load IPC service
|
||||
if (bRet) bRet = fnLoadModule("ipc_com.sdv") ? true : false;
|
||||
if (bRet) bRet = fnCreateObject("CommunicationControl", "", "");
|
||||
|
||||
// Load IPC service and create listener local connections
|
||||
if (bRet) bRet = fnLoadModule("ipc_listener.sdv") ? true : false;
|
||||
if (bRet) bRet = fnCreateObject("ConnectionListenerService", "", R"code([Listener]
|
||||
Type = "Local"
|
||||
)code");
|
||||
|
||||
// Load IPC service
|
||||
if (bRet) bRet = fnLoadModule("ipc_connect.sdv") ? true : false;
|
||||
if (bRet) bRet = fnCreateObject("ConnectionService", "", "");
|
||||
|
||||
// Load proxy/stub for core interfaces
|
||||
if (bRet) bRet = fnLoadModule("core_ps.sdv") ? true : false;
|
||||
|
||||
// // Start the listener
|
||||
// if (bRet) bRet = fnLoadModule("ipc_listener.sdv") ? true : false;
|
||||
// if (bRet) bRet = GetRepository().CreateObject("ConnectionListenerService", "ConnectionListenerService", R"code([Listener]
|
||||
//Type="local"
|
||||
//Instance=)code" + std::to_string(GetInstanceID()));
|
||||
|
||||
if (!bRet)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to load IPC server components. Cannot continue!" << std::endl;
|
||||
if (!ssErrorString.empty())
|
||||
std::cerr << "REASON: " << ssErrorString << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (bRet && bLoadRPCClient)
|
||||
{
|
||||
// Interpret the connect string...
|
||||
|
||||
|
||||
// Load hardware identification
|
||||
bRet = fnLoadModule("hardware_ident.sdv") ? true : false;
|
||||
if (bRet) bRet = fnCreateObject("HardwareIdentificationService", "", "");
|
||||
|
||||
// Load shared memory channel
|
||||
if (bRet) bRet = fnLoadModule("ipc_shared_mem.sdv") ? true : false;
|
||||
if (bRet) bRet = fnCreateObject("DefaultSharedMemoryChannelControl", "", "");
|
||||
|
||||
// Load IPC service
|
||||
if (bRet) bRet = fnLoadModule("ipc_com.sdv") ? true : false;
|
||||
if (bRet) bRet = fnCreateObject("CommunicationControl", "", "");
|
||||
|
||||
// Load IPC service
|
||||
if (bRet) bRet = fnLoadModule("ipc_connect.sdv") ? true : false;
|
||||
if (bRet) bRet = fnCreateObject("ConnectionService", "", "");
|
||||
|
||||
// Load proxy/stub for core interfaces
|
||||
if (bRet) bRet = fnLoadModule("core_ps.sdv") ? true : false;
|
||||
|
||||
if (!bRet)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to load IPC client components. Cannot continue!" << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bRet)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to start core components. Cannot continue!" << std::endl;
|
||||
if (!ssErrorString.empty())
|
||||
std::cerr << "REASON: " << ssErrorString << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Register the exit handler to force a proper shutdown in case the application exits without a call to shutdown.
|
||||
std::atexit(ExitHandler);
|
||||
|
||||
BroadcastOperationState(sdv::app::EAppOperationState::initialized);
|
||||
|
||||
SetConfigMode();
|
||||
|
||||
if (IsMainApplication())
|
||||
{
|
||||
// Load system configs - they need to be present completely
|
||||
for (const std::filesystem::path& rpathConfig : m_vecSysConfigs)
|
||||
{
|
||||
sdv::core::EConfigProcessResult eResult = GetAppConfig().LoadConfig(rpathConfig.generic_u8string());
|
||||
if (eResult != sdv::core::EConfigProcessResult::successful)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Cannot load or partly load the system configuration: " <<
|
||||
rpathConfig.generic_u8string() << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The system configs should not be stored once more.
|
||||
GetAppConfig().ResetConfigBaseline();
|
||||
|
||||
// Load the application config - they can also be partly there
|
||||
if (!m_pathAppConfig.empty())
|
||||
{
|
||||
sdv::core::EConfigProcessResult eResult = GetAppConfig().LoadConfig(m_pathAppConfig.generic_u8string());
|
||||
if (eResult == sdv::core::EConfigProcessResult::failed)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Cannot load application configuration: " << m_pathAppConfig.generic_u8string() << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
else if (eResult != sdv::core::EConfigProcessResult::partially_successful)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "WARNING: Partially could not load application configuration: " <<
|
||||
m_pathAppConfig.generic_u8string() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetRunningMode();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CAppControl::RunLoop()
|
||||
{
|
||||
// Only allowed to run during configuration and running mode.
|
||||
switch (m_eState)
|
||||
{
|
||||
case sdv::app::EAppOperationState::configuring :
|
||||
case sdv::app::EAppOperationState::running :
|
||||
break;
|
||||
default:
|
||||
throw sdv::XInvalidState();
|
||||
}
|
||||
|
||||
// Treat local running differently from main and isolated app running.
|
||||
// Note... Maintenance apps should not use the loop
|
||||
bool bLocal = true;
|
||||
switch (m_eContextMode)
|
||||
{
|
||||
case sdv::app::EAppContext::main:
|
||||
case sdv::app::EAppContext::isolated:
|
||||
bLocal = false;
|
||||
break;
|
||||
case sdv::app::EAppContext::maintenance:
|
||||
throw sdv::XAccessDenied();
|
||||
break;
|
||||
default:
|
||||
bLocal = true;
|
||||
break;
|
||||
}
|
||||
|
||||
CShutdownRequestListener listener;
|
||||
if (bLocal)
|
||||
{
|
||||
listener = std::move(CShutdownRequestListener(m_uiInstanceID));
|
||||
if (!listener.IsValid())
|
||||
throw sdv::XAccessDenied(); // Another instance is already running.
|
||||
}
|
||||
|
||||
if (m_bVerbose)
|
||||
std::cout << "Entering loop" << std::endl;
|
||||
|
||||
m_bRunLoop = true;
|
||||
while (m_bRunLoop)
|
||||
{
|
||||
// Check and wait 2 milliseconds.
|
||||
if (bLocal)
|
||||
m_bRunLoop = !listener.HasTriggered(2);
|
||||
else
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
||||
|
||||
// Inform about running.
|
||||
if (m_pEvent)
|
||||
{
|
||||
sdv::app::SAppEvent sEvent{};
|
||||
sEvent.uiEventID = sdv::app::EVENT_RUNNING_LOOP;
|
||||
sEvent.uiInfo = 0u;
|
||||
m_pEvent->ProcessEvent(sEvent);
|
||||
}
|
||||
}
|
||||
if (m_bVerbose)
|
||||
std::cout << "Leaving loop" << std::endl;
|
||||
}
|
||||
|
||||
void CAppControl::Shutdown(/*in*/ bool bForce)
|
||||
{
|
||||
if (m_eState == sdv::app::EAppOperationState::not_started) return;
|
||||
|
||||
// Shutdown the running loop
|
||||
if (m_bRunLoop)
|
||||
{
|
||||
m_bRunLoop = false;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
|
||||
BroadcastOperationState(sdv::app::EAppOperationState::shutting_down);
|
||||
|
||||
// Set the config state for all objects
|
||||
GetRepository().SetConfigMode();
|
||||
|
||||
// Disable automatic configuration saving.
|
||||
m_bAutoSaveConfig = false;
|
||||
|
||||
// Destroy all objects... this should also remove any registered services, except the custom logger.
|
||||
GetRepository().DestroyAllObjects(std::vector<std::string>({ m_ssLoggerClass }), bForce);
|
||||
|
||||
// Unload all modules... this should destroy all running services, except the custom logger
|
||||
GetModuleControl().UnloadAll(std::vector<sdv::core::TModuleID>({ m_tLoggerModuleID }));
|
||||
|
||||
// Remove all installation manifests
|
||||
GetAppConfig().UnloadInstallatonManifests();
|
||||
|
||||
// End logging
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto in_time_t = std::chrono::system_clock::to_time_t(now);
|
||||
if (bForce) SDV_LOG_INFO("Forced shutdown of application.");
|
||||
SDV_LOG_INFO("SDV Application end at ", std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %H:%M:%S %Z"));
|
||||
GetLoggerControl().SetLogger(nullptr);
|
||||
|
||||
// Unload all modules... this should also destroy the custom logger.
|
||||
GetModuleControl().UnloadAll(std::vector<sdv::core::TModuleID>());
|
||||
|
||||
// Release the lock on the instance (only for main application)
|
||||
if (m_pLockFile)
|
||||
{
|
||||
#ifdef __unix__
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-result"
|
||||
#endif
|
||||
lockf(fileno(m_pLockFile), F_ULOCK, 0);
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
#endif
|
||||
fclose(m_pLockFile);
|
||||
m_pLockFile = nullptr;
|
||||
try
|
||||
{
|
||||
if (std::filesystem::exists(m_pathLockFile))
|
||||
std::filesystem::remove(m_pathLockFile);
|
||||
} catch (const std::filesystem::filesystem_error&)
|
||||
{}
|
||||
m_pathLockFile.clear();
|
||||
}
|
||||
|
||||
// End trace streaming
|
||||
if (m_eContextMode == sdv::app::EAppContext::main)
|
||||
{
|
||||
std::cout << "**********************************************" << std::endl;
|
||||
|
||||
// Shutdown log streaming
|
||||
m_fifoTraceStreamBuffer.Close();
|
||||
}
|
||||
|
||||
BroadcastOperationState(sdv::app::EAppOperationState::not_started);
|
||||
m_eContextMode = sdv::app::EAppContext::no_context;
|
||||
m_uiInstanceID = 0u;
|
||||
m_pEvent = nullptr;
|
||||
m_ssLoggerClass.clear();
|
||||
m_tLoggerModuleID = 0;
|
||||
m_pathLoggerModule.clear();
|
||||
m_ssProgramTag.clear();
|
||||
m_eSeverityFilter = sdv::core::ELogSeverity::info;
|
||||
m_pathInstallDir.clear();
|
||||
m_pathRootDir.clear();
|
||||
m_vecSysConfigs.clear();
|
||||
m_pathAppConfig.clear();
|
||||
m_bAutoSaveConfig = false;
|
||||
m_bEnableAutoSave = false;
|
||||
m_bSilent = false;
|
||||
m_bVerbose = false;
|
||||
}
|
||||
|
||||
void CAppControl::RequestShutdown()
|
||||
{
|
||||
SDV_LOG_INFO("Shutdown request received.");
|
||||
m_bRunLoop = false;
|
||||
}
|
||||
|
||||
sdv::app::EAppOperationState CAppControl::GetOperationState() const
|
||||
{
|
||||
return m_eState;
|
||||
}
|
||||
|
||||
uint32_t CAppControl::GetInstance() const
|
||||
{
|
||||
return m_uiInstanceID;
|
||||
}
|
||||
|
||||
void CAppControl::SetConfigMode()
|
||||
{
|
||||
GetRepository().SetConfigMode();
|
||||
if (m_eState == sdv::app::EAppOperationState::running || m_eState == sdv::app::EAppOperationState::initialized)
|
||||
BroadcastOperationState(sdv::app::EAppOperationState::configuring);
|
||||
}
|
||||
|
||||
void CAppControl::SetRunningMode()
|
||||
{
|
||||
GetRepository().SetRunningMode();
|
||||
if (m_eState == sdv::app::EAppOperationState::configuring || m_eState == sdv::app::EAppOperationState::initialized)
|
||||
BroadcastOperationState(sdv::app::EAppOperationState::running);
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::u8string> CAppControl::GetNames() const
|
||||
{
|
||||
sdv::sequence<sdv::u8string> seqNames = {"app.instance_id", "console.info_level"};
|
||||
return seqNames;
|
||||
}
|
||||
|
||||
sdv::any_t CAppControl::Get(/*in*/ const sdv::u8string& ssAttribute) const
|
||||
{
|
||||
if (ssAttribute == "app.instance_id") return sdv::any_t(m_uiInstanceID);
|
||||
if (ssAttribute == "console.info_level")
|
||||
{
|
||||
if (m_bSilent) return "silent";
|
||||
if (m_bVerbose) return "verbose";
|
||||
return "normal";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool CAppControl::Set(/*in*/ const sdv::u8string& /*ssAttribute*/, /*in*/ sdv::any_t /*anyAttribute*/)
|
||||
{
|
||||
// Currently there are not setting attributes...
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t CAppControl::GetFlags(/*in*/ const sdv::u8string& ssAttribute) const
|
||||
{
|
||||
if (ssAttribute == "app.instance_id") return hlpr::flags<sdv::EAttributeFlags>(sdv::EAttributeFlags::read_only);
|
||||
if (ssAttribute == "console.info_level") return hlpr::flags<sdv::EAttributeFlags>(sdv::EAttributeFlags::read_only);
|
||||
return 0u;
|
||||
}
|
||||
|
||||
std::filesystem::path CAppControl::GetInstallDir() const
|
||||
{
|
||||
return m_pathInstallDir;
|
||||
}
|
||||
|
||||
void CAppControl::DisableAutoConfigUpdate()
|
||||
{
|
||||
m_bEnableAutoSave = false;
|
||||
}
|
||||
|
||||
void CAppControl::EnableAutoConfigUpdate()
|
||||
{
|
||||
m_bEnableAutoSave = true;
|
||||
}
|
||||
|
||||
void CAppControl::TriggerConfigUpdate()
|
||||
{
|
||||
if (!m_bAutoSaveConfig || !m_bEnableAutoSave) return;
|
||||
if (m_pathAppConfig.empty()) return;
|
||||
|
||||
if (!GetAppConfig().SaveConfig(m_pathAppConfig.generic_u8string()))
|
||||
SDV_LOG_ERROR("Failed to automatically save the configuration ", m_pathAppConfig.generic_u8string());
|
||||
}
|
||||
|
||||
bool CAppControl::IsConsoleSilent() const
|
||||
{
|
||||
return m_bSilent;
|
||||
}
|
||||
|
||||
bool CAppControl::IsConsoleVerbose() const
|
||||
{
|
||||
return m_bVerbose;
|
||||
}
|
||||
|
||||
void CAppControl::BroadcastOperationState(sdv::app::EAppOperationState eState)
|
||||
{
|
||||
m_eState = eState;
|
||||
if (m_pEvent)
|
||||
{
|
||||
sdv::app::SAppEvent sEvent{};
|
||||
sEvent.uiEventID = sdv::app::EVENT_OPERATION_STATE_CHANGED;
|
||||
sEvent.uiInfo = static_cast<uint32_t>(eState);
|
||||
m_pEvent->ProcessEvent(sEvent);
|
||||
}
|
||||
}
|
||||
|
||||
bool CAppControl::ProcessAppConfig(const sdv::u8string& rssConfig)
|
||||
{
|
||||
CParserTOML parserConfig;
|
||||
std::string ssError;
|
||||
try
|
||||
{
|
||||
// Read the configuration
|
||||
if (!parserConfig.Process(rssConfig)) return false;
|
||||
|
||||
} catch (const sdv::toml::XTOMLParseException& rexcept)
|
||||
{
|
||||
ssError = std::string("ERROR: Failed to parse application configuration: ") + rexcept.what();
|
||||
}
|
||||
|
||||
// Get the reporting settings (if this succeeded at all...)
|
||||
auto ptrReport = parserConfig.GetRoot().GetDirect("Console.Report");
|
||||
if (ptrReport && ptrReport->GetValue() == "Silent") m_bSilent = true;
|
||||
if (ptrReport && ptrReport->GetValue() == "Verbose") m_bVerbose = true;
|
||||
|
||||
// Report the outstanding error (if there is one...)
|
||||
if (!ssError.empty())
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << ssError << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow a custom logger to be defined
|
||||
std::filesystem::path pathLoggerModule;
|
||||
std::string ssLoggerClass;
|
||||
std::shared_ptr<CNode> ptrLogHandlerPath = parserConfig.GetRoot().GetDirect("LogHandler.Path");
|
||||
std::shared_ptr<CNode> ptrLogHandlerClass = parserConfig.GetRoot().GetDirect("LogHandler.Class");
|
||||
if (ptrLogHandlerPath && !ptrLogHandlerClass)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to process application log: custom logger handler module path supplied, but no class "
|
||||
"defined!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (!ptrLogHandlerPath && ptrLogHandlerClass)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to process application log: custom logger handler class supplied, but no module "
|
||||
"defined!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (ptrLogHandlerPath)
|
||||
{
|
||||
m_pathLoggerModule = static_cast<std::string>(ptrLogHandlerPath->GetValue());
|
||||
m_ssLoggerClass = static_cast<std::string>(ptrLogHandlerClass->GetValue());
|
||||
} else
|
||||
{
|
||||
// Default logger
|
||||
m_ssLoggerClass = "DefaultLoggerService";
|
||||
}
|
||||
|
||||
// Get an optional program tag for the logger
|
||||
std::shared_ptr<CNode> ptrLogPogramTag = parserConfig.GetRoot().GetDirect("LogHandler.Tag");
|
||||
if (ptrLogPogramTag) m_ssProgramTag = static_cast<std::string>(ptrLogPogramTag->GetValue());
|
||||
|
||||
// Get the application-mode
|
||||
std::string ssApplication = "Standalone";
|
||||
std::shared_ptr<CNode> ptrApplication = parserConfig.GetRoot().GetDirect("Application.Mode");
|
||||
if (ptrApplication) ssApplication = static_cast<std::string>(ptrApplication->GetValue());
|
||||
if (ssApplication == "Standalone") m_eContextMode = sdv::app::EAppContext::standalone;
|
||||
else if (ssApplication == "External") m_eContextMode = sdv::app::EAppContext::external;
|
||||
else if (ssApplication == "Isolated") m_eContextMode = sdv::app::EAppContext::isolated;
|
||||
else if (ssApplication == "Main") m_eContextMode = sdv::app::EAppContext::main;
|
||||
else if (ssApplication == "Essential") m_eContextMode = sdv::app::EAppContext::essential;
|
||||
else if (ssApplication == "Maintenance") m_eContextMode = sdv::app::EAppContext::maintenance;
|
||||
else
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to process startup config: invalid application-mode specified for core library: " <<
|
||||
ssApplication << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the severity level filter for the logger
|
||||
auto fnTranslateSevFilter = [this](const std::string& rssLogFilter, const sdv::core::ELogSeverity eDefault)
|
||||
{
|
||||
sdv::core::ELogSeverity eSeverityFilter = eDefault;
|
||||
if (rssLogFilter == "Trace") eSeverityFilter = sdv::core::ELogSeverity::trace;
|
||||
else if (rssLogFilter == "Debug") eSeverityFilter = sdv::core::ELogSeverity::debug;
|
||||
else if (rssLogFilter == "Info") eSeverityFilter = sdv::core::ELogSeverity::info;
|
||||
else if (rssLogFilter == "Warning") eSeverityFilter = sdv::core::ELogSeverity::warning;
|
||||
else if (rssLogFilter == "Error") eSeverityFilter = sdv::core::ELogSeverity::error;
|
||||
else if (rssLogFilter == "Fatal") eSeverityFilter = sdv::core::ELogSeverity::fatal;
|
||||
else if (!rssLogFilter.empty())
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to process application log: invalid severity level filter: '" << rssLogFilter <<
|
||||
"'" << std::endl;
|
||||
}
|
||||
return eSeverityFilter;
|
||||
};
|
||||
sdv::core::ELogSeverity eLogDefaultViewSeverityFilter = sdv::core::ELogSeverity::error;
|
||||
if (IsMainApplication() || IsIsolatedApplication())
|
||||
eLogDefaultViewSeverityFilter = sdv::core::ELogSeverity::info;
|
||||
std::shared_ptr<CNode> ptrLogSeverityFilter = parserConfig.GetRoot().GetDirect("LogHandler.Filter");
|
||||
m_eSeverityFilter = fnTranslateSevFilter(ptrLogSeverityFilter ? ptrLogSeverityFilter->GetValue() : "",
|
||||
sdv::core::ELogSeverity::info);
|
||||
ptrLogSeverityFilter = parserConfig.GetRoot().GetDirect("LogHandler.ViewFilter");
|
||||
m_eSeverityViewFilter = fnTranslateSevFilter(ptrLogSeverityFilter ? ptrLogSeverityFilter->GetValue() : "",
|
||||
eLogDefaultViewSeverityFilter);
|
||||
|
||||
// Get the optional instance ID.
|
||||
std::shared_ptr<CNode> ptrInstance = parserConfig.GetRoot().GetDirect("Application.Instance");
|
||||
if (ptrInstance)
|
||||
m_uiInstanceID = ptrInstance->GetValue();
|
||||
else
|
||||
m_uiInstanceID = 1000u;
|
||||
// Number of attempts to establish a connection to a running instance.
|
||||
std::shared_ptr<CNode> ptrRetries = parserConfig.GetRoot().GetDirect("Application.Retries");
|
||||
if (ptrRetries)
|
||||
{
|
||||
m_uiRetries = ptrRetries->GetValue();
|
||||
if (m_uiRetries > 30)
|
||||
m_uiRetries = 30;
|
||||
else if (m_uiRetries < 3)
|
||||
m_uiRetries = 3;
|
||||
}
|
||||
|
||||
// Main and isolated apps specific information.
|
||||
if (IsMainApplication() || IsIsolatedApplication())
|
||||
{
|
||||
// Get the optional installation directory.
|
||||
std::shared_ptr<CNode> ptrInstallDir = parserConfig.GetRoot().GetDirect("Application.InstallDir");
|
||||
if (ptrInstallDir)
|
||||
{
|
||||
m_pathRootDir = ptrInstallDir->GetValue();
|
||||
if (m_pathRootDir.is_relative())
|
||||
m_pathRootDir = GetExecDirectory() / m_pathRootDir;
|
||||
}
|
||||
else
|
||||
m_pathRootDir = GetExecDirectory() / std::to_string(m_uiInstanceID);
|
||||
m_pathInstallDir = m_pathRootDir;
|
||||
try
|
||||
{
|
||||
std::filesystem::create_directories(m_pathRootDir);
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error& rexcept)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
{
|
||||
std::cerr << "Cannot create root directory: " << m_pathRootDir << std::endl;
|
||||
std::cerr << " Reason: " << rexcept.what() << std::endl;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Maintenance and isolated applications cannot load specific configs. The others can specify a configuration file, but
|
||||
// not auto-updateable.
|
||||
if (!IsMaintenanceApplication() && !IsIsolatedApplication())
|
||||
{
|
||||
auto ptrConfigFile = parserConfig.GetRoot().GetDirect("Application.Config");
|
||||
if (ptrConfigFile)
|
||||
m_pathAppConfig = ptrConfigFile->GetValue();
|
||||
}
|
||||
|
||||
// Read the settings... if existing. And only for the main application
|
||||
if (IsMainApplication())
|
||||
{
|
||||
// If the template is not existing, create the template...
|
||||
if (!std::filesystem::exists(m_pathRootDir / "settings.toml"))
|
||||
{
|
||||
std::ofstream fstream(m_pathRootDir / "settings.toml");
|
||||
if (!fstream.is_open())
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to store application settings." << std::endl;
|
||||
return false;
|
||||
}
|
||||
fstream << szSettingsTemplate;
|
||||
fstream.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::ifstream fstream(m_pathRootDir / "settings.toml");
|
||||
std::string ssContent((std::istreambuf_iterator<char>(fstream)), std::istreambuf_iterator<char>());
|
||||
try
|
||||
{
|
||||
// Read the configuration
|
||||
CParserTOML parserSettings(ssContent);
|
||||
|
||||
// Check for the version
|
||||
auto ptrVersion = parserSettings.GetRoot().GetDirect("Settings.Version");
|
||||
if (!ptrVersion)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Missing version in application settings file." << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (ptrVersion->GetValue() != SDVFrameworkInterfaceVersion)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Invalid version of application settings file (expected version " <<
|
||||
SDVFrameworkInterfaceVersion << ", but available version " <<
|
||||
static_cast<uint32_t>(ptrVersion->GetValue()) << ")" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the system configurations
|
||||
auto ptrSystemConfigs = parserSettings.GetRoot().GetDirect("Settings.SystemConfig");
|
||||
if (ptrSystemConfigs && ptrSystemConfigs->GetArray())
|
||||
{
|
||||
for (uint32_t uiIndex = 0; uiIndex < ptrSystemConfigs->GetArray()->GetCount(); uiIndex++)
|
||||
{
|
||||
auto ptrSystemConfig = ptrSystemConfigs->GetArray()->Get(uiIndex);
|
||||
if (!ptrSystemConfig) continue;
|
||||
m_vecSysConfigs.push_back(static_cast<std::string>(ptrSystemConfig->GetValue()));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the application config - but only when not specified over the app-control-config.
|
||||
if (m_pathAppConfig.empty())
|
||||
{
|
||||
auto ptrAppConfig = parserSettings.GetRoot().GetDirect("Settings.AppConfig");
|
||||
if (ptrAppConfig)
|
||||
{
|
||||
// Path available. Enable auto-update.
|
||||
m_pathAppConfig = static_cast<std::string>(ptrAppConfig->GetValue());
|
||||
m_bAutoSaveConfig = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const sdv::toml::XTOMLParseException& rexcept)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to parse application settings: " << rexcept.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
|
||||
CAppControl& CAppControlService::GetAppControl()
|
||||
{
|
||||
return ::GetAppControl();
|
||||
}
|
||||
|
||||
bool CAppControlService::EnableAppShutdownRequestAccess() const
|
||||
{
|
||||
return ::GetAppControl().IsMainApplication() || ::GetAppControl().IsIsolatedApplication();
|
||||
}
|
||||
|
||||
#endif
|
||||
344
sdv_services/core/app_control.h
Normal file
344
sdv_services/core/app_control.h
Normal file
@@ -0,0 +1,344 @@
|
||||
#ifndef APP_CONTROL_H
|
||||
#define APP_CONTROL_H
|
||||
|
||||
#include <interfaces/app.h>
|
||||
#include <support/component_impl.h>
|
||||
#include <support/interface_ptr.h>
|
||||
#include "../../global/tracefifo/trace_fifo.h"
|
||||
|
||||
///
|
||||
/// @brief Application control class.
|
||||
/// @details The application control class is responsible for the startup and shutdown of the system. Since the system is (re-)used
|
||||
/// in many applications, the startup behavior can be determined by the provided configuration as string as argument to the
|
||||
/// startup function.
|
||||
/// The configuration uses the TOML format and is defined as follows:
|
||||
/// @code
|
||||
/// # Optional use of customized log handler
|
||||
/// [LogHandler]
|
||||
/// Class = "" # Component class name of a custom logger (optional)
|
||||
/// Path = "" # Component module path of a custom logger (optional)
|
||||
/// Tag = "" # Program tag to use instead of the name SDV_LOG_<pid>
|
||||
/// Filter = "" # Lowest severity filter to use when logging (Trace, Debug, Info, Warning, Error, Fatal). Default severity
|
||||
/// # level filter is Info (meaning Debug and Trace messages are not being stored).
|
||||
/// ViewFilter = "" # Lowest severity filter to use when logging (Trace, Debug, Info, Warning, Error, Fatal). Default severity
|
||||
/// # level filter is Error (meaning Debug, Trace, Info and Warning messages are not being shown).
|
||||
///
|
||||
/// # Application behavior definition
|
||||
/// # Mode = "Standalone" (default) app->no RPC + core services + additional configurations allowed
|
||||
/// # Mode = "External" app->RPC client only + local services + target service(s) --> connection information through listener
|
||||
/// # Mode = "Isolated" app->RPC client only + local services + target service(s) --> connection information needed
|
||||
/// # Mode = "Main" app->RPC server + core services --> access key needed
|
||||
/// # Mode = "Essential" app->local services + additional configurations allowed
|
||||
/// # Mode = "Maintenance" app->RPC client only + local services + maintenance service --> connection information needed + access key
|
||||
/// # Instance = 1234
|
||||
/// [Application]
|
||||
/// Mode = "Main"
|
||||
/// Instance = 1234 # Optional instance ID to be used with main and isolated applications. Has no influence on other
|
||||
/// # applications. Default instance ID is 1000. The connection listener is using the instance ID to allow
|
||||
/// # connections from an external application to the main application. Furthermore, the instance ID is
|
||||
/// # used to locate the installation of SDV components. The location of the SDV components is relative to
|
||||
/// # the executable (unless a target directory is supplied) added with the instance and the installations:
|
||||
/// # <exe_path>/<instance>/<installation>
|
||||
/// InstallDir = "./test" # Optional custom installation directory to be used with main and isolated applications. Has no
|
||||
/// # influence on other applications. The default location for installations is the location of the
|
||||
/// # executable. Specifying a different directory will change the location of installations to
|
||||
/// # <install_directory>/<instance>/<installation>
|
||||
/// # NOTE The directory of the core library and the directory of the running executable are always added
|
||||
/// # to the system if they contain an installation manifest.
|
||||
///
|
||||
/// # Optional configuration that should be loaded (not for maintenance and isolated applications). This overrides the application
|
||||
/// # config from the settings (only main application). Automatic saving the configuration is not supported.
|
||||
/// Config = "abc.toml"
|
||||
///
|
||||
/// #Console output
|
||||
/// [Console]
|
||||
/// Report = "Silent" # Either "Silent", "Normal" or "Verbose" for no, normal or extensive messages.
|
||||
///
|
||||
/// # Search directories
|
||||
/// @endcode
|
||||
///
|
||||
/// TODO: Add config ignore list (e.g. platform.toml, vehicle_ifc.toml and vehicle_abstract.toml).
|
||||
/// Add dedicated config (rather than standard config) as startup param.
|
||||
///
|
||||
class CAppControl : public sdv::IInterfaceAccess, public sdv::app::IAppContext, public sdv::app::IAppControl,
|
||||
public sdv::app::IAppOperation, public sdv::app::IAppShutdownRequest, public sdv::IAttributes
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
CAppControl();
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CAppControl();
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::app::IAppOperation)
|
||||
SDV_INTERFACE_ENTRY(sdv::app::IAppContext)
|
||||
SDV_INTERFACE_ENTRY(sdv::app::IAppControl)
|
||||
SDV_INTERFACE_ENTRY(sdv::app::IAppShutdownRequest)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Return whether the current application is the main application.
|
||||
* @return Returns 'true' when the current application is the main application; otherwise returns 'false'.
|
||||
*/
|
||||
bool IsMainApplication() const;
|
||||
|
||||
/**
|
||||
* @brief Return whether the current application is an isolated application.
|
||||
* @return Returns 'true' when the current application is an isolated application; otherwise returns 'false'.
|
||||
*/
|
||||
bool IsIsolatedApplication() const;
|
||||
|
||||
/**
|
||||
* @brief Return whether the current application is a standalone application.
|
||||
* @return Returns 'true' when the current application is a standalone application; otherwise returns 'false'.
|
||||
*/
|
||||
bool IsStandaloneApplication() const;
|
||||
|
||||
/**
|
||||
* @brief Return whether the current application is an essential application.
|
||||
* @return Returns 'true' when the current application is an essential application; otherwise returns 'false'.
|
||||
*/
|
||||
bool IsEssentialApplication() const;
|
||||
|
||||
/**
|
||||
* @brief Return whether the current application is a maintenance application.
|
||||
* @return Returns 'true' when the current application is a maintenance application; otherwise returns 'false'.
|
||||
*/
|
||||
bool IsMaintenanceApplication() const;
|
||||
|
||||
/**
|
||||
* @brief Return whether the current application is an external application.
|
||||
* @return Returns 'true' when the current application is an external application; otherwise returns 'false'.
|
||||
*/
|
||||
bool IsExternalApplication() const;
|
||||
|
||||
/**
|
||||
* @brief Return the application context mode. Overload of sdv::app::IAppContext::GetContextType.
|
||||
* @return The context mode.
|
||||
*/
|
||||
sdv::app::EAppContext GetContextType() const override;
|
||||
|
||||
/**
|
||||
* @brief Return the core instance ID. Overload of sdv::app::IAppContext::GetContextType.
|
||||
* @return The instance ID.
|
||||
*/
|
||||
uint32_t GetInstanceID() const override;
|
||||
|
||||
/**
|
||||
* @brief Return the number of retries to establish a connection.
|
||||
* @return Number of retries.
|
||||
*/
|
||||
uint32_t GetRetries() const override;
|
||||
|
||||
/**
|
||||
* @brief Start the application. Overload of sdv::app::IAppControl::Startup.
|
||||
* @details The core will prepare for an application start based on the provided configuration. Per default, the
|
||||
* application will be in running mode after successful startup.
|
||||
* @param[in] ssConfig String containing the configuration to be used by the core during startup. This configuration
|
||||
* is optional. If not provided the application runs as standalone application without any RPC support.
|
||||
* @param[in] pEventHandler Pointer to the event handler receiving application events. For the handler to receive
|
||||
* events, the handler needs to expose the IAppEvent interface. This pointer is optionally and can be NULL.
|
||||
* @return Returns 'true' on successful start-up and 'false' on failed startup.
|
||||
*/
|
||||
virtual bool Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfaceAccess* pEventHandler) override;
|
||||
|
||||
/**
|
||||
* @brief Running loop until shutdown request is triggered.
|
||||
*/
|
||||
virtual void RunLoop() override;
|
||||
|
||||
/**
|
||||
* @brief Initiate a shutdown of the application. Overload of sdv::app::IAppControl::Shutdown.
|
||||
* @details The objects will be called to shutdown allowing them to clean up and gracefully shutdown. If, for some reason, the
|
||||
* object cannot shut down (e.g. pointers are still in use or threads are not finalized), the object will be kept alive and the
|
||||
* application state will stay in shutting-down-state. In that case the exception is called. A new call to the shutdown function
|
||||
* using the force-flag might force a shutdown. Alternatively the application can wait until the application state changes to
|
||||
* not-started.
|
||||
* @remarks Application shutdown is only possible when all components are released.
|
||||
* @param[in] bForce When set, forces an application shutdown. This might result in loss of data and should only be used as a
|
||||
* last resort.
|
||||
*/
|
||||
virtual void Shutdown(/*in*/ bool bForce) override;
|
||||
|
||||
/**
|
||||
* @brief Request shutdown. Overload of sdv::app::IAppShutdownRequest::RequestShutdown.
|
||||
*/
|
||||
virtual void RequestShutdown() override;
|
||||
|
||||
/**
|
||||
* @brief Get the current operation state. This information is also supplied through the event handler function. Overload of
|
||||
* sdv::app::IAppOperation::GetOperationState.
|
||||
* @return Returns the operation state of the application.
|
||||
*/
|
||||
virtual sdv::app::EAppOperationState GetOperationState() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the current running instance.
|
||||
* @details Get the instance. If not otherwise specified, the current instance depends on whether the application is running
|
||||
* as main or isolated application, in which case the instance is 1000. In all other cases the instance is 0. A dedicated
|
||||
* instance can be supplied through the app control.
|
||||
* @return The instance number.
|
||||
*/
|
||||
uint32_t GetInstance() const;
|
||||
|
||||
/**
|
||||
* @brief Switch from running mode to the configuration mode. Overload of sdv::app::IAppOperation::SetConfigMode.
|
||||
*/
|
||||
virtual void SetConfigMode() override;
|
||||
|
||||
/**
|
||||
* @brief Switch from the configuration mode to the running mode. Overload of sdv::app::IAppOperation::SetRunningMode.
|
||||
*/
|
||||
virtual void SetRunningMode() override;
|
||||
|
||||
/**
|
||||
* @brief Get a sequence with the available attribute names. Overload of sdv::IAttributes::GetNames.
|
||||
* @return The sequence of attribute names.
|
||||
*/
|
||||
virtual sdv::sequence<sdv::u8string> GetNames() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the attribute value. Overload of sdv::IAttributes::Get.
|
||||
* @param[in] ssAttribute Name of the attribute.
|
||||
* @return The attribute value or an empty any-value if the attribute wasn't found or didn't have a value.
|
||||
*/
|
||||
virtual sdv::any_t Get(/*in*/ const sdv::u8string& ssAttribute) const override;
|
||||
|
||||
/**
|
||||
* @brief Set the attribute value. Overload of sdv::IAttributes::Set.
|
||||
* @param[in] ssAttribute Name of the attribute.
|
||||
* @param[in] anyAttribute Attribute value to set.
|
||||
* @return Returns 'true' when setting the attribute was successful or 'false' when the attribute was not found or the
|
||||
* attribute is read-only or another error occurred.
|
||||
*/
|
||||
virtual bool Set(/*in*/ const sdv::u8string& ssAttribute, /*in*/ sdv::any_t anyAttribute) override;
|
||||
|
||||
/**
|
||||
* @brief Get the attribute flags belonging to a certain attribute. Overload of sdv::IAttributes::GetFlags.
|
||||
* @param[in] ssAttribute Name of the attribute.
|
||||
* @return Returns the attribute flags (zero or more EAttributeFlags flags) or 0 when the attribute could not be found.
|
||||
*/
|
||||
virtual uint32_t GetFlags(/*in*/ const sdv::u8string& ssAttribute) const override;
|
||||
|
||||
/**
|
||||
* @brief Get the installation directory of user components.
|
||||
* @return The location of the user components. Only is valid when used in main and isolated applications.
|
||||
*/
|
||||
std::filesystem::path GetInstallDir() const;
|
||||
|
||||
/**
|
||||
* @brief Disable the current auto update feature if enabled in the system settings.
|
||||
*/
|
||||
void DisableAutoConfigUpdate();
|
||||
|
||||
/**
|
||||
* @brief Enable the current auto update feature if enabled in the system settings.
|
||||
*/
|
||||
void EnableAutoConfigUpdate();
|
||||
|
||||
/**
|
||||
* @brief Trigger the config update if enabled in the system settings.
|
||||
*/
|
||||
void TriggerConfigUpdate();
|
||||
|
||||
/**
|
||||
* @brief Should the console output be silent?
|
||||
* @return Returns whether the console output is silent.
|
||||
*/
|
||||
bool IsConsoleSilent() const;
|
||||
|
||||
/**
|
||||
* @brief Should the console output be verbose?
|
||||
* @return Returns whether the verbose console output is activated.
|
||||
*/
|
||||
bool IsConsoleVerbose() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Set the operation state and broadcast the state through the event.
|
||||
* @param[in] eState The new state the app control is in.
|
||||
*/
|
||||
void BroadcastOperationState(sdv::app::EAppOperationState eState);
|
||||
|
||||
/**
|
||||
* @brief Process the application configuration before starting the system
|
||||
* @param[in] rssConfig Reference to the configuration content.
|
||||
* @return Returns 'true' when processing was successful; false when not.
|
||||
*/
|
||||
bool ProcessAppConfig(const sdv::u8string& rssConfig);
|
||||
|
||||
sdv::app::EAppContext m_eContextMode = sdv::app::EAppContext::no_context; ///< The application is running as...
|
||||
sdv::app::EAppOperationState m_eState = sdv::app::EAppOperationState::not_started; ///< The current operation state.
|
||||
sdv::app::IAppEvent* m_pEvent = nullptr; ///< Pointer to the app event interface.
|
||||
std::string m_ssLoggerClass; ///< Class name of a logger service.
|
||||
sdv::core::TModuleID m_tLoggerModuleID = 0; ///< ID of the logger module.
|
||||
std::filesystem::path m_pathLoggerModule; ///< Module name of a custom logger.
|
||||
std::string m_ssProgramTag; ///< Program tag to use when logging.
|
||||
sdv::core::ELogSeverity m_eSeverityFilter = sdv::core::ELogSeverity::info; ///< Severity level filter while logging.
|
||||
sdv::core::ELogSeverity m_eSeverityViewFilter = sdv::core::ELogSeverity::error; ///< Severity level filter while logging.
|
||||
uint32_t m_uiRetries = 0u; ///< Number of retries to establish a connection.
|
||||
uint32_t m_uiInstanceID = 0u; ///< Instance number.
|
||||
std::filesystem::path m_pathInstallDir; ///< Location of user component installations.
|
||||
std::filesystem::path m_pathRootDir; ///< Location of user component root directory.
|
||||
std::vector<std::filesystem::path> m_vecSysConfigs; ///< The system configurations from the settings file.
|
||||
std::filesystem::path m_pathAppConfig; ///< The application configuration from the settings file.
|
||||
bool m_bAutoSaveConfig = false; ///< System setting for automatic saving of the configuration.
|
||||
bool m_bEnableAutoSave = false; ///< When set and when enabled in the system settings, allows
|
||||
///< the automatic saving of the configuration.
|
||||
bool m_bRunLoop = false; ///< Used to detect end of running loop function.
|
||||
bool m_bSilent = false; ///< When set, no console reporting takes place.
|
||||
bool m_bVerbose = false; ///< When set, extensive console reporting takes place.
|
||||
std::filesystem::path m_pathLockFile; ///< Lock file path name.
|
||||
FILE* m_pLockFile = nullptr; ///< Lock file to test for other instances.
|
||||
CTraceFifoStdBuffer m_fifoTraceStreamBuffer; ///< Trace stream buffer to redirect std::log, std::out and
|
||||
///< std::err when running as service.
|
||||
};
|
||||
|
||||
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
|
||||
/**
|
||||
* @brief App config service class.
|
||||
*/
|
||||
class CAppControlService : public sdv::CSdvObject
|
||||
{
|
||||
public:
|
||||
CAppControlService() = default;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::app::IAppOperation, GetAppControl())
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::IAttributes, GetAppControl())
|
||||
SDV_INTERFACE_SET_SECTION_CONDITION(EnableAppShutdownRequestAccess(), 1)
|
||||
SDV_INTERFACE_SECTION(1)
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::app::IAppShutdownRequest, GetAppControl())
|
||||
SDV_INTERFACE_DEFAULT_SECTION()
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
|
||||
DECLARE_OBJECT_CLASS_NAME("AppControlService")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Get access to the application control.
|
||||
* @return Returns the one global instance of the application config.
|
||||
*/
|
||||
static CAppControl& GetAppControl();
|
||||
|
||||
/**
|
||||
* @brief When set, the application shutdown request interface access will be enabled.
|
||||
* @return Returns 'true' when the access to the application configuration is granted; otherwise returns 'false'.
|
||||
*/
|
||||
bool EnableAppShutdownRequestAccess() const;
|
||||
};
|
||||
DEFINE_SDV_OBJECT_NO_EXPORT(CAppControlService)
|
||||
|
||||
#endif
|
||||
|
||||
#endif // !defined APP_CONTROL_H
|
||||
1650
sdv_services/core/installation_composer.cpp
Normal file
1650
sdv_services/core/installation_composer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
470
sdv_services/core/installation_composer.h
Normal file
470
sdv_services/core/installation_composer.h
Normal file
@@ -0,0 +1,470 @@
|
||||
#ifndef INSTALL_PACKAGING_H
|
||||
#define INSTALL_PACKAGING_H
|
||||
|
||||
#include <interfaces/config.h>
|
||||
#include "installation_manifest.h"
|
||||
#include <list>
|
||||
#include <filesystem>
|
||||
|
||||
/// When enabled, support the read-only flag for files. By default this flag is not enabled due to limited support by the OS.
|
||||
/// - The Windows OS provides native support for this flag
|
||||
/// - The Linux allows installing additional libraries providing support, but this is not supported by all Linux versions.
|
||||
/// - The Posix API doesn't provide support for the read-only flag.
|
||||
#define COMPOSER_SUPPORT_READONLY_LINUX 0
|
||||
|
||||
#ifdef _WIN32
|
||||
// Resolve conflict
|
||||
#pragma push_macro("interface")
|
||||
#undef interface
|
||||
#pragma push_macro("GetObject")
|
||||
#undef GetObject
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
#include <objbase.h>
|
||||
|
||||
// Resolve conflict
|
||||
#pragma pop_macro("GetObject")
|
||||
#pragma pop_macro("interface")
|
||||
#ifdef GetClassInfo
|
||||
#undef GetClassInfo
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Convert the Windows FILETIME to the POSIX file time in micro-seconds.
|
||||
* @details The Windows FILETIME structure counts with 100ns resolution and starts on the 1st of January 1601. The POSIX time
|
||||
* counts with 1 second resolution and starts on the 1st of January 1970. V-API uses 1 micro-second resolution.
|
||||
* @param[in] sWinTime The WIndows file time.
|
||||
* @return The POSIX time in micro-seconds.
|
||||
*/
|
||||
inline uint64_t WindowsTimeToPosixTime(FILETIME sWinTime)
|
||||
{
|
||||
const uint64_t uiTicksPerMicroSecond = 10ull; // Windows counts in 100ns; POSIX in seconds
|
||||
const uint64_t uiEpochDifference = 11644473600000000ull; // Windows starts counting on 1st Jan. 1601; POSIX on 1st Jan. 1970
|
||||
return ((static_cast<uint64_t>(sWinTime.dwHighDateTime) << 32 | static_cast<uint64_t>(sWinTime.dwLowDateTime)) /
|
||||
uiTicksPerMicroSecond) - uiEpochDifference;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert the POSIX file time in micro-seconds to the Windows FILETIME.
|
||||
* @details The Windows FILETIME structure counts with 100ns resolution and starts on the 1st of January 1601. The POSIX time
|
||||
* counts with 1 second resolution and starts on the 1st of January 1970. V-API uses 1 micro-second resolution.
|
||||
* @param[in] uiTime The POSIX time in micro-seconds.
|
||||
* @return The Windows FILETIME.
|
||||
*/
|
||||
inline FILETIME PosixTimeToWindowsTime(uint64_t uiTime)
|
||||
{
|
||||
FILETIME sWinTime{};
|
||||
const uint64_t uiTicksPerMicroSecond = 10ull; // Windows counts in 100ns; POSIX in seconds
|
||||
const uint64_t uiEpochDifference = 11644473600000000ull; // Windows starts counting on 1st Jan. 1601; POSIX on 1st Jan. 1970
|
||||
sWinTime.dwLowDateTime = static_cast<uint32_t>(((uiTime + uiEpochDifference) * uiTicksPerMicroSecond) & 0xffffffffull);
|
||||
sWinTime.dwHighDateTime = static_cast<uint32_t>(((uiTime + uiEpochDifference) * uiTicksPerMicroSecond) >> 32ull);
|
||||
return sWinTime;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Installation packager class
|
||||
*/
|
||||
class CInstallComposer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor.
|
||||
*/
|
||||
CInstallComposer() = default;
|
||||
|
||||
/**
|
||||
* @brief Destructor, finalizing the installation if not done so before.
|
||||
*/
|
||||
~CInstallComposer();
|
||||
|
||||
/**
|
||||
* @brief Clear the current package to allow a new package composition.
|
||||
*/
|
||||
void Clear();
|
||||
|
||||
/**
|
||||
* @brief Flags to use with the AddModule function.
|
||||
*/
|
||||
enum class EAddModuleFlags : uint32_t
|
||||
{
|
||||
wildcards = 0x00, ///< Add all modules that fit the modules path using wildcards to search for modules; could also
|
||||
///< point to a directory. Wildcards are '*', '**' and '?'. The '*' will allow matching zero or
|
||||
///< more characters until the next separator ('.' or '/'). A '**' allows matching of zero or
|
||||
///< more characters across the directory separator '/'. A '?' allows matching one character.
|
||||
///< Examples (using path: /dir1/dir2/dir3/dir4/file.txt)
|
||||
///< - Pattern /dir1/**/*.txt - match
|
||||
///< - Pattern /dir?/dir?/**/dir4 - match
|
||||
///< - Pattern /**/*.txt - match
|
||||
regex = 0x01, ///< Add all modules that fit the modules path using regular expression to search for modules;
|
||||
///< could also point to a directory.
|
||||
keep_structure = 0x10, ///< Maintain the directory structure when adding modules (requires a valid base directory).
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Installation update rules.
|
||||
*/
|
||||
enum class EUpdateRules
|
||||
{
|
||||
not_allowed, ///< Not allowed to update an existing installation.
|
||||
update_when_new, ///< Allow to update when the installation is newer.
|
||||
overwrite, ///< Always allow to update.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Add a module to the installation.
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @attention This function is part of the composer. Since it is loading a module to retrieve the component manifest of the
|
||||
* module, module code could be executed. it therefore imposes a security risk. Do not call this function in sdv_core!
|
||||
* @details Add a module to the installation package.
|
||||
* @param[in] rpathBasePath Reference to the base path to start searching for modules. Could be empty if the module path is
|
||||
* absolute, in which case the directory structure cannot be stored at the target.
|
||||
* @param[in] rssModulePath Reference to the string containing the module path to install. Depending on the options, this path
|
||||
* could contain a pattern with wildcards or regular expressions. If the path is relative, the module base path must be valid.
|
||||
* The module path is optional. If not supplied, the base path must be provided and all files within all subdirectories will
|
||||
* be added.
|
||||
* @param[in] rpathRelTargetDir Reference to the relative target directory the module(s) should be stored at.
|
||||
* @param[in] uiFlags Zero or more flags from EAddModuleFlags.
|
||||
* @return Returns the a vector of modules that were added. In case of using wildcards or regular expression, this could also
|
||||
* be an empty list, which is not an error.
|
||||
*/
|
||||
std::vector<std::filesystem::path> AddModule(const std::filesystem::path& rpathBasePath, const std::string& rssModulePath,
|
||||
const std::filesystem::path& rpathRelTargetDir = ".", uint32_t uiFlags = 0);
|
||||
|
||||
/**
|
||||
* @brief Add a property value. This value will be added to the installation manifest.
|
||||
* @details Add a property value, which will be included in the installation manifest. The properties "Description" and
|
||||
* "Version" are used during package management.
|
||||
* @param[in] rssName Name of the property. Spaces are allowed. Quotes are not allowed.
|
||||
* @param[in] rssValue Property value.
|
||||
*/
|
||||
void AddProperty(const std::string& rssName, const std::string& rssValue);
|
||||
|
||||
/**
|
||||
* @brief Compose the package in memory.
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @attention This function is part of the composer. Since it is loading a module to retrieve the component manifest of the
|
||||
* module, module code could be executed. it therefore imposes a security risk. Do not call this function in sdv_core!
|
||||
* @details Compose a package from all the modules added through the AddModule function. Additionally add an installation
|
||||
* manifest containing the module and component details. If the module is an SDV module, the component manifest will
|
||||
* automatically extracted from the component and added to the installation manifest.
|
||||
* @param[in] rssInstallName Reference to the string containing the installation name.
|
||||
* @return Returns a buffer to the package content.
|
||||
*/
|
||||
sdv::pointer<uint8_t> Compose(const std::string& rssInstallName) const;
|
||||
|
||||
/**
|
||||
* @brief Compose the package to disk.
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @attention This function is part of the composer. Since it is loading a module to retrieve the component manifest of the
|
||||
* module, module code could be executed. it therefore imposes a security risk. Do not call this function in sdv_core!
|
||||
* @details Compose a package from all the modules added through the AddModule function. Additionally add an installation
|
||||
* manifest containing the module and component details. If the module is an SDV module, the component manifest will
|
||||
* automatically extracted from the component and added to the installation manifest.
|
||||
* @param[in] rpathPackage Reference to the path receiving the package content. Any existing package will be overwritten.
|
||||
* @param[in] rssInstallName Reference to the string containing the installation name.
|
||||
* @return Returns whether the package composing was successful.
|
||||
*/
|
||||
bool Compose(const std::filesystem::path& rpathPackage, const std::string& rssInstallName) const;
|
||||
|
||||
/**
|
||||
* @brief Compose the installation directly at the target directory (without package composing and extracting).
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @attention This function is part of the composer. Since it is loading a module to retrieve the component manifest of the
|
||||
* module, module code could be executed. it therefore imposes a security risk. Do not call this function in sdv_core!
|
||||
* @details Compose a package from all the modules added through the AddModule function. Additionally add an installation
|
||||
* manifest containing the module and component details. If the module is an SDV module, the component manifest will
|
||||
* automatically extracted from the component and added to the installation manifest.
|
||||
* @param[in] rssInstallName Reference to the string containing the installation name.
|
||||
* @param[in] rpathInstallDir Reference to the installation directory.
|
||||
* @param[in] eUpdateRule Decide how to deal with updating an existing installation.
|
||||
* @return Returns the installation manifest when the package extraction was successful; or an empty manifest when not.
|
||||
*/
|
||||
CInstallManifest ComposeDirect(const std::string& rssInstallName, const std::filesystem::path& rpathInstallDir,
|
||||
EUpdateRules eUpdateRule = EUpdateRules::not_allowed) const;
|
||||
|
||||
/**
|
||||
* @brief Compose an installation manifest.
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @attention This function is part of the composer. Since it is loading a module to retrieve the component manifest of the
|
||||
* module, module code could be executed. it therefore imposes a security risk. Do not call this function in sdv_core!
|
||||
* @details Compose a package from all the modules added through the AddModule function. Additionally add an installation
|
||||
* manifest containing the module and component details. If the module is an SDV module, the component manifest will
|
||||
* automatically extracted from the component and added to the installation manifest.
|
||||
* @param[in] rssInstallName Reference to the string containing the installation name.
|
||||
* @return Returns an initialized installation manifest if successful, or an empty manifest if not.
|
||||
*/
|
||||
CInstallManifest ComposeInstallManifest(const std::string& rssInstallName) const;
|
||||
|
||||
/**
|
||||
* @brief Extract a package to an installation directory.
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @pre An installation directory must be available.
|
||||
* @param[in] rptrPackage Reference to the pointer containing the package content.
|
||||
* @param[in] rpathInstallDir Reference to the installation directory.
|
||||
* @param[in] eUpdateRule Decide how to deal with updating an existing installation.
|
||||
* @return Returns the installation manifest when the package extraction was successful; or an empty manifest when not.
|
||||
*/
|
||||
static CInstallManifest Extract(const sdv::pointer<uint8_t>& rptrPackage, const std::filesystem::path& rpathInstallDir,
|
||||
EUpdateRules eUpdateRule = EUpdateRules::not_allowed);
|
||||
|
||||
/**
|
||||
* @brief Extract a package to an installation directory.
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @pre An installation directory must be available.
|
||||
* @param[in] rpathPackage Reference to the path of the package file.
|
||||
* @param[in] rpathInstallDir Reference to the installation directory.
|
||||
* @param[in] eUpdateRule Decide how to deal with updating an existing installation.
|
||||
* @return Returns the installation manifest when the package extraction was successful; or an empty manifest when not.
|
||||
*/
|
||||
static CInstallManifest Extract(const std::filesystem::path& rpathPackage, const std::filesystem::path& rpathInstallDir,
|
||||
EUpdateRules eUpdateRule = EUpdateRules::not_allowed);
|
||||
|
||||
/**
|
||||
* @brief Remove an installation.
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @param[in] rssInstallName Reference to the string containing the installation name.
|
||||
* @param[in] rpathInstallDir Reference to the installation directory.
|
||||
* @return Returns the installation manifest of the removed installation when the removal was successful; or an empty manifest
|
||||
* when not.
|
||||
*/
|
||||
static CInstallManifest Remove(const std::string& rssInstallName, const std::filesystem::path& rpathInstallDir);
|
||||
|
||||
/**
|
||||
* @brief Verify the integrity of an installation package.
|
||||
* @throw Could throw a sdv::XSysExcept based exception with information about the integrity violation.
|
||||
* @param[in] rptrPackage Reference to the pointer containing the package content.
|
||||
* @return Returns 'true' when the package extraction was successful; 'false' when not.
|
||||
*/
|
||||
static bool Verify(const sdv::pointer<uint8_t>& rptrPackage);
|
||||
|
||||
/**
|
||||
* @brief Verify the integrity of an installation package.
|
||||
* @throw Could throw a sdv::XSysExcept based exception with information about the integrity violation.
|
||||
* @param[in] rpathPackage Reference to the path of the package file.
|
||||
* @return Returns 'true' when the package extraction was successful; 'false' when not.
|
||||
*/
|
||||
static bool Verify(const std::filesystem::path& rpathPackage);
|
||||
|
||||
/**
|
||||
* @brief Extract an installation manifest from a package.
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @param[in] rptrPackage Reference to the pointer containing the package content.
|
||||
* @return Returns 'true' when the package extraction was successful; 'false' when not.
|
||||
*/
|
||||
static CInstallManifest ExtractInstallManifest(const sdv::pointer<uint8_t>& rptrPackage);
|
||||
|
||||
/**
|
||||
* @brief Extract an installation manifest from a package.
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @param[in] rpathPackage Reference to the path of the package file.
|
||||
* @return Returns 'true' when the package extraction was successful; 'false' when not.
|
||||
*/
|
||||
static CInstallManifest ExtractInstallManifest(const std::filesystem::path& rpathPackage);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief File entry (modules and companion files).
|
||||
*/
|
||||
struct SFileEntry
|
||||
{
|
||||
std::filesystem::path pathSrcModule; ///! Source path to the module.
|
||||
std::filesystem::path pathRelDir; ///< Relative directory within the installation.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Compose and serialize a package header.
|
||||
* @param[in, out] rptrPackage Reference to a pointer object receiving the serialized header. The pointer object will be
|
||||
* adjusted in size to fit the header.
|
||||
* @param[in] rmanifest Reference to the installation manifest to add.
|
||||
* @return The checksum of data stored in the package pointer.
|
||||
*/
|
||||
static uint32_t SerializePackageHeader(sdv::pointer<uint8_t>& rptrPackage, const CInstallManifest& rmanifest);
|
||||
|
||||
/**
|
||||
* @brief Serialize a module into a package BLOB.
|
||||
* @param[in] uiChecksumInit The initial checksum to start calculating the BLOB checksum with.
|
||||
* @param[in, out] rptrPackage Reference to a pointer object receiving the BLOB. The pointer object will be extended to fit
|
||||
* the BLOB, which will be placed following the data stored in the pointer buffer. The existing data will not be touched.
|
||||
* @param[in] rsFile Reference to the module entry to add as a BLOB.
|
||||
* @return The checksum of data stored in the package pointer.
|
||||
*/
|
||||
static uint32_t SerializeModuleBLOB(uint32_t uiChecksumInit, sdv::pointer<uint8_t>& rptrPackage, const SFileEntry& rsFile);
|
||||
|
||||
/**
|
||||
* @brief Serialize a final BLOB. This marks the end of the BLOBs within the package.
|
||||
* @param[in] uiChecksumInit The initial checksum to start calculating the BLOB checksum with.
|
||||
* @param[in, out] rptrPackage Reference to a pointer object receiving the BLOB. The pointer object will be extended to fit
|
||||
* the BLOB, which will be placed following the data stored in the pointer buffer. The existing data will not be touched.
|
||||
* @return The checksum of data stored in the package pointer.
|
||||
*/
|
||||
static uint32_t SerializeFinalBLOB(uint32_t uiChecksumInit, sdv::pointer<uint8_t>& rptrPackage);
|
||||
|
||||
/**
|
||||
* @brief Serialize the package footer.
|
||||
* @param[in] uiChecksum The checksum of the data to store in the footer.
|
||||
* @param[in, out] rptrPackage Reference to a pointer object receiving the footer. The pointer object will be extended to fit
|
||||
* the footer, which will be placed following the data stored in the pointer buffer. The existing data will not be touched.
|
||||
*/
|
||||
static void SerializePackageFooter(uint32_t uiChecksum, sdv::pointer<uint8_t>& rptrPackage);
|
||||
|
||||
/**
|
||||
* @brief Extracts the header from the file.
|
||||
* @details This function reads the header from the file from the current position and checks whether the content fits the
|
||||
* checksum.
|
||||
* @param[in, out] rfstream Reference to the opened package stream at the initial position.
|
||||
* @param[out] ruiChecksum Reference to the variable receiving the checksum calculated over the complete header. This checksum
|
||||
* is used as input for the checksum of the following structures.
|
||||
* @return Returns the content of the package header.
|
||||
*/
|
||||
static sdv::installation::SPackageHeader DeserializeHeader(std::ifstream& rfstream, uint32_t& ruiChecksum);
|
||||
|
||||
/**
|
||||
* @brief Extracts the header from the file.
|
||||
* @details This function reads the header from the file from the current position and checks whether the content fits the
|
||||
* checksum.
|
||||
* @param[in] rptrPackage Reference to the package containing the header.
|
||||
* @param[out] rnOffset Reference to the variable receiving the offset location following the header.
|
||||
* @param[out] ruiChecksum Reference to the variable receiving the checksum calculated over the complete header. This checksum
|
||||
* is used as input for the checksum of the following structures.
|
||||
* @return Returns the content of the package header.
|
||||
*/
|
||||
static sdv::installation::SPackageHeader DeserializeHeader(const sdv::pointer<uint8_t>& rptrPackage, size_t& rnOffset,
|
||||
uint32_t& ruiChecksum);
|
||||
|
||||
/**
|
||||
* @brief Extracts the BLOB from the file.
|
||||
* @details This function reads the BLOB from the file from the current position and checks whether the content fits the
|
||||
* checksum.
|
||||
* @param[in, out] rfstream Reference to the opened package stream at the current position.
|
||||
* @param[in, out] ruiChecksum Reference to the variable receiving the checksum calculated over the complete BLOB. This
|
||||
* checksum is used as input for the checksum of the following structures.
|
||||
* @return Returns the content of the package header.
|
||||
*/
|
||||
static sdv::installation::SPackageBLOB DeserializeBLOB(std::ifstream& rfstream, uint32_t& ruiChecksum);
|
||||
|
||||
/**
|
||||
* @brief Extracts the BLOB from the file.
|
||||
* @details This function reads the BLOB from the file from the current position and checks whether the content fits the
|
||||
* checksum.
|
||||
* @param[in] rptrPackage Reference to the package containing the BLOB.
|
||||
* @param[in, out] rnOffset Reference to the variable containing the offset to the BLOB and receiving the offset following the
|
||||
* BLOB.
|
||||
* @param[in, out] ruiChecksum Reference to the variable receiving the checksum calculated over the complete BLOB. This
|
||||
* checksum is used as input for the checksum of the following structures.
|
||||
* @return Returns the content of the package header.
|
||||
*/
|
||||
static sdv::installation::SPackageBLOB DeserializeBLOB(const sdv::pointer<uint8_t>& rptrPackage, size_t& rnOffset, uint32_t& ruiChecksum);
|
||||
|
||||
/**
|
||||
* @brief Check the final checksum using the package footer.
|
||||
* @param[in, out] rfstream Reference to the opened package stream at the current position.
|
||||
* @param[in] uiChecksum The calculated checksum of the package content to use for the calculation of the checksum of the
|
||||
* footer.
|
||||
*/
|
||||
static void DeserializeFinalChecksum(std::ifstream& rfstream, uint32_t uiChecksum);
|
||||
|
||||
/**
|
||||
* @brief Check the final checksum using the package footer.
|
||||
* @param[in] rptrPackage Reference to the package containing the footer with the checksum.
|
||||
* @param[in] nOffset The variable containing the offset to the footer.
|
||||
* @param[in] uiChecksum The calculated checksum of the package content to use for the calculation of the checksum of the
|
||||
* footer.
|
||||
*/
|
||||
static void DeserializeFinalChecksum(const sdv::pointer<uint8_t>& rptrPackage, size_t nOffset, uint32_t uiChecksum);
|
||||
|
||||
/**
|
||||
* @brief Store the module from the supplied BLOB.
|
||||
* @param[in] rsModuleBLOB Reference to the BLOB structure containing the module. If the BLOB doesn't contain the module,
|
||||
* nothing occurs.
|
||||
* @param[in] rpathLocation Reference to the path containing the location to store the module to.
|
||||
*/
|
||||
static void StoreModuleBLOB(const sdv::installation::SPackageBLOB& rsModuleBLOB, const std::filesystem::path& rpathLocation);
|
||||
|
||||
/**
|
||||
* @brief Store the installation manifest at the provided location and set the creation time for the manifest file.
|
||||
* @param[in] uiCreationTime The time of the package creation (will be used for the manifest file).
|
||||
* @param[in] rpathLocation Reference to the path containing the location to store the manifest to.
|
||||
* @param[in] rmanifest Reference to the manifest to store.
|
||||
*/
|
||||
static void StoreManifest(const std::filesystem::path& rpathLocation, const CInstallManifest& rmanifest, int64_t uiCreationTime);
|
||||
|
||||
/**
|
||||
* @brief Set the file to read-only.
|
||||
* @param[in] rpathFile Reference to the path of the file.
|
||||
*/
|
||||
static void SetReadOnly(const std::filesystem::path& rpathFile);
|
||||
|
||||
/**
|
||||
* @brief Get the file read-only-state.
|
||||
* @param[in] rpathFile Reference to the path of the file.
|
||||
*/
|
||||
static bool IsReadOnly(const std::filesystem::path& rpathFile);
|
||||
|
||||
/**
|
||||
* @brief Set the file to executable.
|
||||
* @remarks Not available for Windows.
|
||||
* @param[in] rpathFile Reference to the path of the file.
|
||||
*/
|
||||
static void SetExecutable(const std::filesystem::path& rpathFile);
|
||||
|
||||
/**
|
||||
* @brief Get the file to executable state.
|
||||
* @remarks Not available for WIndows.
|
||||
* @param[in] rpathFile Reference to the path of the file.
|
||||
*/
|
||||
static bool IsExecutable(const std::filesystem::path& rpathFile);
|
||||
|
||||
/**
|
||||
* @brief Set the file creation time.
|
||||
* @remarks Only available for Windows.
|
||||
* @param[in] rpathFile Reference to the path of the file.
|
||||
* @param[in] uiTimeMicrosec The unix epoch time in microseconds.
|
||||
*/
|
||||
static void SetCreateTime(const std::filesystem::path& rpathFile, uint64_t uiTimeMicrosec);
|
||||
|
||||
/**
|
||||
* @brief Get the file creation time.
|
||||
* @remarks Only available for Windows.
|
||||
* @param[in] rpathFile Reference to the path of the file.
|
||||
* @return Returns the unix epoch time in microseconds.
|
||||
*/
|
||||
static uint64_t GetCreateTime(const std::filesystem::path& rpathFile);
|
||||
|
||||
/**
|
||||
* @brief Set the file change time.
|
||||
* @param[in] rpathFile Reference to the path of the file.
|
||||
* @param[in] uiTimeMicrosec The unix epoch time in microseconds.
|
||||
*/
|
||||
static void SetChangeTime(const std::filesystem::path& rpathFile, uint64_t uiTimeMicrosec);
|
||||
|
||||
/**
|
||||
* @brief Get the file change time.
|
||||
* @param[in] rpathFile Reference to the path of the file.
|
||||
* @return Returns the unix epoch time in microseconds.
|
||||
*/
|
||||
static uint64_t GetChangeTime(const std::filesystem::path& rpathFile);
|
||||
|
||||
/**
|
||||
* @brief Check whether thr installed version can be overwritten. This is the case when the version number of the current
|
||||
* package is larger than the version number of the installed package.
|
||||
* @remarks Returns 'true' if the current installation doesn't have an installation manifest.
|
||||
* @remarks If there is no version stored in the installation manifest, the version number is considered 0.0.0.
|
||||
* @param[in] rpathInstall Reference to the installation directory of the current installation. This directory must contain a
|
||||
* "install_manifest.toml" file.
|
||||
* @param[in] sVersionNew The version to use for comparison.
|
||||
* @param[in] eUpdateRule Decide how to deal with updating an existing installation.
|
||||
* @return Returns whether the existing installation can be updated (removed and installed again).
|
||||
*/
|
||||
static bool UpdateExistingInstallation(const std::filesystem::path& rpathInstall, sdv::installation::SPackageVersion sVersionNew, EUpdateRules eUpdateRule);
|
||||
|
||||
std::list<SFileEntry> m_lstFiles; ///< List of modules added to the package
|
||||
std::map<std::string, std::string> m_mapProperties; ///< Property map.
|
||||
};
|
||||
|
||||
#endif // !defined INSTALL_PACKAGING_H
|
||||
478
sdv_services/core/installation_manifest.cpp
Normal file
478
sdv_services/core/installation_manifest.cpp
Normal file
@@ -0,0 +1,478 @@
|
||||
#include "installation_manifest.h"
|
||||
#include "toml_parser/parser_toml.h"
|
||||
#include <support/serdes.h>
|
||||
|
||||
#if defined _WIN32 && defined __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wcast-function-type"
|
||||
#endif
|
||||
|
||||
#ifdef __unix__
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Read the module manifest from the binary.
|
||||
* @param[in] rpathModule Reference to the module to read the manifest from.
|
||||
* @return The manifest, if existing or an empty string when not.
|
||||
*/
|
||||
std::string ReadModuleManifest(const std::filesystem::path& rpathModule)
|
||||
{
|
||||
if (rpathModule.extension() != ".sdv") return {};
|
||||
if (!std::filesystem::exists(rpathModule)) return {};
|
||||
|
||||
// Load the module
|
||||
#ifdef _WIN32
|
||||
sdv::core::TModuleID tModuleID = reinterpret_cast<sdv::core::TModuleID>(LoadLibrary(rpathModule.native().c_str()));
|
||||
#elif defined __unix__
|
||||
sdv::core::TModuleID tModuleID = reinterpret_cast<sdv::core::TModuleID>(dlopen(rpathModule.native().c_str(), RTLD_LAZY));
|
||||
#else
|
||||
#error OS not supported!
|
||||
#endif
|
||||
if (!tModuleID) return {};
|
||||
|
||||
// Check whether the module exposes the necessary functions
|
||||
using TFNHasActiveObjects = bool();
|
||||
using TFNGetModuleFactory = sdv::IInterfaceAccess*(uint32_t);
|
||||
using TFNGetManifest = const char*();
|
||||
|
||||
#ifdef _WIN32
|
||||
std::function<TFNGetModuleFactory> fnGetFactory = reinterpret_cast<TFNGetModuleFactory*>(GetProcAddress(reinterpret_cast<HMODULE>(tModuleID), "GetModuleFactory"));
|
||||
std::function<TFNHasActiveObjects> fnActiveObjects = reinterpret_cast<TFNHasActiveObjects*>(GetProcAddress(reinterpret_cast<HMODULE>(tModuleID), "HasActiveObjects"));
|
||||
std::function<TFNGetManifest> fnGetManifest = reinterpret_cast<TFNGetManifest*>(GetProcAddress(reinterpret_cast<HMODULE>(tModuleID), "GetManifest"));
|
||||
#elif defined __unix__
|
||||
std::function<TFNGetModuleFactory> fnGetFactory = reinterpret_cast<TFNGetModuleFactory*>(dlsym(reinterpret_cast<void*>(tModuleID), "GetModuleFactory"));
|
||||
std::function<TFNHasActiveObjects> fnActiveObjects = reinterpret_cast<TFNHasActiveObjects*>(dlsym(reinterpret_cast<void*>(tModuleID), "HasActiveObjects"));
|
||||
std::function<TFNGetManifest> fnGetManifest = reinterpret_cast<TFNGetManifest*>(dlsym(reinterpret_cast<void*>(tModuleID), "GetManifest"));
|
||||
#else
|
||||
#error OS not supported!
|
||||
#endif
|
||||
|
||||
// Check for functions and the correct version
|
||||
std::string ssManifest;
|
||||
if (fnGetFactory && fnActiveObjects && fnGetManifest && fnGetManifest())
|
||||
ssManifest = fnGetManifest();
|
||||
|
||||
// Release the library
|
||||
#ifdef _WIN32
|
||||
FreeLibrary(reinterpret_cast<HMODULE>(tModuleID));
|
||||
#elif defined __unix__
|
||||
dlclose(reinterpret_cast<void*>(tModuleID));
|
||||
#else
|
||||
#error OS not supported!
|
||||
#endif
|
||||
|
||||
// Return the manifest
|
||||
return ssManifest;
|
||||
}
|
||||
|
||||
bool CInstallManifest::IsValid() const
|
||||
{
|
||||
// Must have an installation name to be valid.
|
||||
return !m_ssInstallName.empty();
|
||||
}
|
||||
|
||||
void CInstallManifest::Clear()
|
||||
{
|
||||
m_ssInstallName.clear();
|
||||
m_vecModules.clear();
|
||||
m_bBlockSystemObjects = false;
|
||||
}
|
||||
|
||||
const std::string& CInstallManifest::InstallName() const
|
||||
{
|
||||
return m_ssInstallName;
|
||||
}
|
||||
|
||||
sdv::installation::SPackageVersion CInstallManifest::Version() const
|
||||
{
|
||||
auto optProperty = Property("Version");
|
||||
if (!optProperty) return {};
|
||||
|
||||
// A property string is composed of: major.minor.patch (numbers only; characters and whitespace are ignored).
|
||||
sdv::installation::SPackageVersion sVersion{};
|
||||
size_t nPos = optProperty->find('.');
|
||||
sVersion.uiMajor = static_cast<uint32_t>(std::atoi(optProperty->substr(0, nPos).c_str()));
|
||||
size_t nStart = nPos;
|
||||
if (nStart != std::string::npos)
|
||||
{
|
||||
nStart++;
|
||||
nPos = optProperty->find('.', nPos + 1);
|
||||
sVersion.uiMinor = static_cast<uint32_t>(std::atoi(optProperty->substr(nStart, nPos).c_str()));
|
||||
}
|
||||
nStart = nPos;
|
||||
if (nStart != std::string::npos)
|
||||
{
|
||||
nStart++;
|
||||
sVersion.uiPatch = static_cast<uint32_t>(std::atoi(optProperty->substr(nStart).c_str()));
|
||||
}
|
||||
return sVersion;
|
||||
}
|
||||
|
||||
const std::filesystem::path& CInstallManifest::InstallDir() const
|
||||
{
|
||||
return m_pathInstallDir;
|
||||
}
|
||||
|
||||
bool CInstallManifest::Create(const std::string& rssInstallName)
|
||||
{
|
||||
Clear();
|
||||
|
||||
m_ssInstallName = rssInstallName;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CInstallManifest::Load(const std::filesystem::path& rpathInstallDir, bool bBlockSystemObjects /*= false*/)
|
||||
{
|
||||
if (rpathInstallDir.empty() || !std::filesystem::exists(rpathInstallDir) ||
|
||||
!std::filesystem::is_directory(rpathInstallDir)) return {};
|
||||
m_pathInstallDir = rpathInstallDir;
|
||||
|
||||
// Create the manifest file
|
||||
std::ifstream fstream(rpathInstallDir / "install_manifest.toml");
|
||||
if (!fstream.is_open()) return false;
|
||||
|
||||
// Read the manifest in memory
|
||||
std::stringstream sstreamManifest;
|
||||
sstreamManifest << fstream.rdbuf();
|
||||
fstream.close();
|
||||
|
||||
return Read(sstreamManifest.str(), bBlockSystemObjects);
|
||||
}
|
||||
|
||||
bool CInstallManifest::Save(const std::filesystem::path& rpathInstallDir) const
|
||||
{
|
||||
if (!IsValid() || rpathInstallDir.empty()) return false;
|
||||
m_pathInstallDir = rpathInstallDir;
|
||||
|
||||
// Create the manifest file
|
||||
std::ofstream fstream(rpathInstallDir / "install_manifest.toml", std::ios_base::out | std::ios_base::trunc);
|
||||
if (!fstream.is_open()) return false;
|
||||
|
||||
std::string ssManifest = Write();
|
||||
if (!ssManifest.empty()) fstream << ssManifest;
|
||||
|
||||
fstream.close();
|
||||
|
||||
return !ssManifest.empty();
|
||||
}
|
||||
|
||||
bool CInstallManifest::Read(const std::string& rssManifest, bool bBlockSystemObjects /*= false*/)
|
||||
{
|
||||
Clear();
|
||||
m_bBlockSystemObjects = bBlockSystemObjects;
|
||||
|
||||
// Parse the manifest
|
||||
CParserTOML parser(rssManifest);
|
||||
|
||||
// Get the installation version - must be identical to the interface version
|
||||
auto ptrInstallVersionNode = parser.GetRoot().GetDirect("Installation.Version");
|
||||
if (!ptrInstallVersionNode || ptrInstallVersionNode->GetValue() != SDVFrameworkInterfaceVersion) return false;
|
||||
|
||||
// Get the installation name
|
||||
auto ptrInstallNameNode = parser.GetRoot().GetDirect("Installation.Name");
|
||||
if (!ptrInstallNameNode) return false;
|
||||
m_ssInstallName = static_cast<std::string>(ptrInstallNameNode->GetValue());
|
||||
if (m_ssInstallName.empty()) return false;
|
||||
|
||||
// Get installation properties. The properties are optional
|
||||
auto ptrProperties = parser.GetRoot().GetDirect("Properties");
|
||||
std::shared_ptr<CTable> ptrPropertyTable;
|
||||
if (ptrProperties) ptrPropertyTable = ptrProperties->GetTable();
|
||||
if (ptrPropertyTable)
|
||||
{
|
||||
for (uint32_t uiIndex = 0; uiIndex < ptrPropertyTable->GetCount(); uiIndex++)
|
||||
{
|
||||
auto ptrProperty = ptrPropertyTable->Get(uiIndex);
|
||||
if (ptrProperty)
|
||||
m_mapProperties[ptrProperty->GetName()] = static_cast<std::string>(ptrProperty->GetValue());
|
||||
}
|
||||
}
|
||||
|
||||
// Build the module list
|
||||
auto ptrModulesNode = parser.GetRoot().GetDirect("Module");
|
||||
if (!ptrModulesNode) return true; // No modules in the manifest
|
||||
auto ptrModuleArrayNode = ptrModulesNode->GetArray();
|
||||
if (!ptrModuleArrayNode) return false; // Must be array
|
||||
for (uint32_t uiModuleIndex = 0; uiModuleIndex < ptrModuleArrayNode->GetCount(); uiModuleIndex++)
|
||||
{
|
||||
// Get the module
|
||||
auto ptrModule = ptrModuleArrayNode->Get(uiModuleIndex);
|
||||
if (!ptrModule) continue;
|
||||
|
||||
// Get the module path
|
||||
auto ptrModulePath = ptrModule->GetDirect("Path");
|
||||
if (!ptrModulePath) continue;
|
||||
std::filesystem::path pathModule = static_cast<std::string>(ptrModulePath->GetValue());
|
||||
std::string ssModuleManifest;
|
||||
|
||||
// Get the component list (if available)
|
||||
auto ptrModuleComponents = ptrModule->GetDirect("Component");
|
||||
if (ptrModuleComponents)
|
||||
{
|
||||
// The module manifest contains the TOML text of the component array
|
||||
auto ptrModuleComponentArray = ptrModuleComponents->GetArray();
|
||||
if (ptrModuleComponentArray)
|
||||
ssModuleManifest = ptrModuleComponents->CreateTOMLText();
|
||||
}
|
||||
|
||||
// Add the module
|
||||
m_vecModules.push_back(SModule(pathModule, ssModuleManifest,
|
||||
m_bBlockSystemObjects));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string CInstallManifest::Write() const
|
||||
{
|
||||
if (!IsValid()) return {};
|
||||
|
||||
std::stringstream sstream;
|
||||
|
||||
// Add the installation section
|
||||
sstream << "[Installation]" << std::endl << "Version = " << SDVFrameworkSubbuildVersion << std::endl << "Name = \"" <<
|
||||
m_ssInstallName << "\"" << std::endl << std::endl;
|
||||
|
||||
// Add the properties section (if there are any properties)
|
||||
if (!m_mapProperties.empty())
|
||||
{
|
||||
sstream << "[Properties]" << std::endl;
|
||||
for (const auto& rvtProperty : m_mapProperties)
|
||||
{
|
||||
if (NeedQuotedName(rvtProperty.first))
|
||||
sstream << "\"" << rvtProperty.first << "\"";
|
||||
else
|
||||
sstream << rvtProperty.first;
|
||||
sstream << " = \"" << rvtProperty.second << "\"" << std::endl;
|
||||
}
|
||||
sstream << std::endl;
|
||||
}
|
||||
|
||||
// Add the modules
|
||||
for (const SModule& rsEntry : m_vecModules)
|
||||
{
|
||||
sstream << "[[Module]]" << std::endl << "Path=\"" << rsEntry.pathRelModule.generic_u8string() << "\"" << std::endl;
|
||||
|
||||
// Read the module manifest
|
||||
CParserTOML parser(rsEntry.ssManifest);
|
||||
|
||||
// Add the module manifest as part of the installation manifest.
|
||||
sstream << parser.CreateTOMLText("Module") << std::endl;
|
||||
}
|
||||
|
||||
return sstream.str();
|
||||
}
|
||||
|
||||
bool CInstallManifest::AddModule(const std::filesystem::path& rpathModulePath,
|
||||
const std::filesystem::path& rpathRelTargetDir /*= "."*/)
|
||||
{
|
||||
if (!IsValid()) return false;
|
||||
|
||||
// Check for the existence of the module.
|
||||
if (!std::filesystem::exists(rpathModulePath)) return false;
|
||||
if (!std::filesystem::is_regular_file(rpathModulePath)) return false;
|
||||
|
||||
// Check for a relative path
|
||||
if (!rpathRelTargetDir.is_relative()) return false;
|
||||
if (RefersToRelativeParent(rpathRelTargetDir)) return false;
|
||||
|
||||
// Read the manifest...
|
||||
std::string ssManifest = ReadModuleManifest(rpathModulePath);
|
||||
|
||||
// Read the component manifest if existing
|
||||
std::string ssComponentsManifest;
|
||||
if (!ssManifest.empty())
|
||||
{
|
||||
// Check the interface version for compatibility
|
||||
CParserTOML parser(ssManifest);
|
||||
auto ptrInterfaceNode = parser.GetRoot().GetDirect("Interface.Version");
|
||||
if (!ptrInterfaceNode) return false;
|
||||
if (ptrInterfaceNode->GetValue() != SDVFrameworkInterfaceVersion) return false;
|
||||
auto ptrComponentsNode = parser.GetRoot().GetDirect("Component");
|
||||
if (!ptrComponentsNode) return true; // No component available in the manifest
|
||||
if (!ptrComponentsNode->GetArray()) return false;
|
||||
ssComponentsManifest = ptrComponentsNode->CreateTOMLText();
|
||||
}
|
||||
|
||||
// Store path and component
|
||||
std::filesystem::path pathRelModule = (rpathRelTargetDir / rpathModulePath.filename()).lexically_normal();
|
||||
m_vecModules.push_back(SModule(pathRelModule,
|
||||
ssComponentsManifest, m_bBlockSystemObjects));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::filesystem::path CInstallManifest::FindModule(const std::filesystem::path& rpathRelModule) const
|
||||
{
|
||||
if (!IsValid() || m_pathInstallDir.empty())
|
||||
return {};
|
||||
|
||||
// Search for the correct module
|
||||
auto itModule = std::find_if(
|
||||
m_vecModules.begin(), m_vecModules.end(), [&](const SModule& rsEntry) { return rsEntry.pathRelModule == rpathRelModule; });
|
||||
if (itModule != m_vecModules.end())
|
||||
return m_pathInstallDir / rpathRelModule;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string CInstallManifest::FindModuleManifest(const std::filesystem::path& rpathRelModule) const
|
||||
{
|
||||
if (!IsValid() || m_pathInstallDir.empty())
|
||||
return {};
|
||||
|
||||
// Search for the correct module
|
||||
auto itModule = std::find_if(
|
||||
m_vecModules.begin(), m_vecModules.end(), [&](const SModule& rsEntry) { return rsEntry.pathRelModule == rpathRelModule; });
|
||||
if (itModule != m_vecModules.end())
|
||||
return itModule->ssManifest;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<CInstallManifest::SComponent> CInstallManifest::FindComponentByClass(const std::string& rssClass) const
|
||||
{
|
||||
// Search for the correct module
|
||||
SComponent sRet{};
|
||||
auto itModule = std::find_if(m_vecModules.begin(), m_vecModules.end(), [&](const SModule& rsEntry)
|
||||
{
|
||||
return std::find_if(rsEntry.vecComponents.begin(), rsEntry.vecComponents.end(),
|
||||
[&](const SComponent& sComponent)
|
||||
{
|
||||
// Note, use the class, alias and the default object name for searching...
|
||||
if (sComponent.ssClassName == rssClass ||
|
||||
std::find(sComponent.seqAliases.begin(), sComponent.seqAliases.end(), rssClass) !=
|
||||
sComponent.seqAliases.end())
|
||||
{
|
||||
sRet = sComponent;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}) != rsEntry.vecComponents.end();
|
||||
});
|
||||
if (itModule != m_vecModules.end()) return sRet;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<CInstallManifest::SComponent> CInstallManifest::ComponentList() const
|
||||
{
|
||||
std::vector<SComponent> vecComponents;
|
||||
for (const auto& rsModule : m_vecModules)
|
||||
{
|
||||
for (const auto& rsComponent : rsModule.vecComponents)
|
||||
vecComponents.push_back(rsComponent);
|
||||
}
|
||||
return vecComponents;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> CInstallManifest::ModuleList() const
|
||||
{
|
||||
std::vector<std::filesystem::path> vecModules;
|
||||
for (const auto& rsModule : m_vecModules)
|
||||
vecModules.push_back(rsModule.pathRelModule);
|
||||
return vecModules;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> CInstallManifest::PropertyList() const
|
||||
{
|
||||
return std::vector<std::pair<std::string, std::string>>(m_mapProperties.begin(), m_mapProperties.end());
|
||||
}
|
||||
|
||||
void CInstallManifest::Property(const std::string& rssName, const std::string& rssValue)
|
||||
{
|
||||
// Check for a quote in the name... if existing then do not allow insertion.
|
||||
if (rssName.find_first_of("\"'") != std::string::npos)
|
||||
return;
|
||||
m_mapProperties[rssName] = rssValue;
|
||||
}
|
||||
|
||||
std::optional<std::string> CInstallManifest::Property(const std::string& rssName) const
|
||||
{
|
||||
auto itProperty = m_mapProperties.find(rssName);
|
||||
if (itProperty == m_mapProperties.end())
|
||||
return {};
|
||||
return itProperty->second;
|
||||
}
|
||||
|
||||
bool CInstallManifest::NeedQuotedName(const std::string& rssName)
|
||||
{
|
||||
for (char c : rssName)
|
||||
{
|
||||
if (!std::isalnum(c) && c != '_' && c != '-')
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CInstallManifest::SModule::SModule(const std::filesystem::path& rpathRelModule, const std::string& rssManifest,
|
||||
bool bBlockSystemObjects) : pathRelModule(rpathRelModule), ssManifest(rssManifest)
|
||||
{
|
||||
// Parse the manifest and extract information from them...
|
||||
CParserTOML parser(rssManifest);
|
||||
auto ptrComponents = parser.GetRoot().GetDirect("Component");
|
||||
if (!ptrComponents) return; // No objects...
|
||||
auto ptrComponentArray = ptrComponents->GetArray();
|
||||
if (!ptrComponentArray) return; // No objects...
|
||||
for (uint32_t uiIndex = 0; uiIndex < ptrComponentArray->GetCount(); uiIndex++)
|
||||
{
|
||||
auto ptrComponent = ptrComponentArray->Get(uiIndex);
|
||||
if (!ptrComponent) continue;
|
||||
|
||||
// Fill in the component structure
|
||||
SComponent sComponent{};
|
||||
//sComponent.pathModule = rpathModule;
|
||||
sComponent.pathRelModule = rpathRelModule;
|
||||
sComponent.ssManifest = ptrComponent->CreateTOMLText("Component");
|
||||
auto ptrClassName = ptrComponent->GetDirect("Class");
|
||||
if (!ptrClassName) continue;
|
||||
sComponent.ssClassName = static_cast<std::string>(ptrClassName->GetValue());
|
||||
auto ptrAliases = ptrComponent->GetDirect("Aliases");
|
||||
if (ptrAliases)
|
||||
{
|
||||
auto ptrAliasesArray = ptrAliases->GetArray();
|
||||
for (uint32_t uiAliasIndex = 0; ptrAliasesArray && uiAliasIndex < ptrAliasesArray->GetCount(); uiAliasIndex++)
|
||||
{
|
||||
auto ptrClassAlias = ptrAliasesArray->Get(uiAliasIndex);
|
||||
if (ptrClassAlias)
|
||||
sComponent.seqAliases.push_back(static_cast<sdv::u8string>(ptrClassAlias->GetValue()));
|
||||
}
|
||||
}
|
||||
auto ptrDefaultName = ptrComponent->GetDirect("DefaultName");
|
||||
if (ptrDefaultName) sComponent.ssDefaultObjectName = static_cast<std::string>(ptrDefaultName->GetValue());
|
||||
else sComponent.ssDefaultObjectName = sComponent.ssClassName;
|
||||
auto ptrType = ptrComponent->GetDirect("Type");
|
||||
if (!ptrType) continue;
|
||||
std::string ssType = static_cast<std::string>(ptrType->GetValue());
|
||||
if (ssType == "System") sComponent.eType = sdv::EObjectType::SystemObject;
|
||||
else if (ssType == "Device") sComponent.eType = sdv::EObjectType::Device;
|
||||
else if (ssType == "BasicService") sComponent.eType = sdv::EObjectType::BasicService;
|
||||
else if (ssType == "ComplexService") sComponent.eType = sdv::EObjectType::ComplexService;
|
||||
else if (ssType == "App") sComponent.eType = sdv::EObjectType::Application;
|
||||
else if (ssType == "Proxy") sComponent.eType = sdv::EObjectType::Proxy;
|
||||
else if (ssType == "Stub") sComponent.eType = sdv::EObjectType::Stub;
|
||||
else if (ssType == "Utility") sComponent.eType = sdv::EObjectType::Utility;
|
||||
else continue;
|
||||
if (bBlockSystemObjects && sComponent.eType == sdv::EObjectType::SystemObject) continue;
|
||||
auto ptrSingleton = ptrComponent->GetDirect("Singleton");
|
||||
if (ptrSingleton && static_cast<bool>(ptrSingleton->GetValue()))
|
||||
sComponent.uiFlags = static_cast<uint32_t>(sdv::EObjectFlags::singleton);
|
||||
auto ptrDependencies = ptrComponent->GetDirect("Dependencies");
|
||||
if (ptrDependencies)
|
||||
{
|
||||
auto ptrDependencyArray = ptrDependencies->GetArray();
|
||||
for (uint32_t uiDependencyIndex = 0; ptrDependencyArray && uiDependencyIndex < ptrDependencyArray->GetCount();
|
||||
uiDependencyIndex++)
|
||||
{
|
||||
auto ptrDependsOn = ptrDependencyArray->Get(uiDependencyIndex);
|
||||
if (ptrDependsOn)
|
||||
sComponent.seqDependencies.push_back(static_cast<sdv::u8string>(ptrDependsOn->GetValue()));
|
||||
}
|
||||
}
|
||||
|
||||
vecComponents.push_back(sComponent);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined _WIN32 && defined __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
402
sdv_services/core/installation_manifest.h
Normal file
402
sdv_services/core/installation_manifest.h
Normal file
@@ -0,0 +1,402 @@
|
||||
#ifndef INSTALL_MANIFEST_H
|
||||
#define INSTALL_MANIFEST_H
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <interfaces/core.h>
|
||||
#include <interfaces/config.h>
|
||||
#include <map>
|
||||
#include <cstdlib>
|
||||
|
||||
/**
|
||||
* @brief Check whether a relative path is directing to a parent of the base path.
|
||||
* @details Detect whether a relative path joined to the base path is referring to a parent of the base path. For example, the
|
||||
* base path is "/home/user/project" and the relative path is "..". Both joined together would make "/home/user", which is a parent
|
||||
* of the base path.
|
||||
* @param[in] rpathBase Reference to the base path.
|
||||
* @param[in] rpathRelative Reference to the relative path following the base.
|
||||
* @return Returns whether the relative path is a parent of the base (not the base or a child of the base).
|
||||
*/
|
||||
inline bool IsParentPath(const std::filesystem::path& rpathBase, const std::filesystem::path& rpathRelative)
|
||||
{
|
||||
auto pathAbsBase = std::filesystem::weakly_canonical(rpathBase);
|
||||
auto pathAbsPotentialParent = std::filesystem::weakly_canonical(rpathBase / rpathRelative);
|
||||
|
||||
// Check if pathAbsPotentialParent supersedes pathAbsBase. If not, it points to or derives from a parent.
|
||||
auto itBase = pathAbsBase.begin();
|
||||
auto itPotentialParent = pathAbsPotentialParent.begin();
|
||||
while (itBase != pathAbsBase.end())
|
||||
{
|
||||
// If the potential path has finished, it is pointing to a parent.
|
||||
if (itPotentialParent == pathAbsPotentialParent.end()) return true;
|
||||
|
||||
// If the path-parts are not equal, the potential parent part is really deriving from a parent.
|
||||
if (*itBase != *itPotentialParent) return true;
|
||||
|
||||
// Check next
|
||||
itBase++;
|
||||
itPotentialParent++;
|
||||
}
|
||||
|
||||
// Even if the potential path might be still have more parts, they derive from the base path.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether the relative path directs to a parent path.
|
||||
* @details Detect whether a relative path directs to a parent path. For example with "./dir1/dir2/../dir3"; this is not the case,
|
||||
* but with "./dir1/dir2/../../../dir3" this is the case.
|
||||
* @param[in] rpathRelative Reference to the relative path to check for.
|
||||
* @return Returns whether the relative path refers to the
|
||||
*/
|
||||
inline bool RefersToRelativeParent(const std::filesystem::path& rpathRelative)
|
||||
{
|
||||
int iDepth = 0;
|
||||
for (const auto& pathPart : rpathRelative)
|
||||
{
|
||||
if (pathPart == "..")
|
||||
--iDepth;
|
||||
else if (pathPart != "." && pathPart != "")
|
||||
++iDepth;
|
||||
if (iDepth < 0) return true; // Not allowed to be negative.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Class managing the installation manifest.
|
||||
* @details The installation manifest is a TOML file with the name "install_manifest.toml" and the following format:
|
||||
* @code
|
||||
* [Installation]
|
||||
* Version = 100 # Installation manifest version.
|
||||
* Name = "Duck" # Name of the installation. This typically is identical to the installation directory name.
|
||||
*
|
||||
* [Properties]
|
||||
* Product = "Wild goose components" # Product name
|
||||
* Description = "Mallard installation" # Description
|
||||
* Author = "Nils Holgerson" # Author
|
||||
* Address = """Vildgåsvägen 7
|
||||
* Skanör med Falsterbo
|
||||
* Skåne län, 123 45
|
||||
* Sverige""" # Address
|
||||
* Copyrights = "(C) 2025 Wild goose" # Copyrights
|
||||
* Version = "0.1.2.3" # Package version
|
||||
*
|
||||
* [[Module]]
|
||||
* Path = "mallard.sdv # Relative path to the module
|
||||
*
|
||||
* [[Module.Component]] # Component manifest
|
||||
* Class = "Mallard class" # The name of the class
|
||||
* Aliases = ["Duck", "Pont duck"] # Optional list of aliases
|
||||
* DefaultName = "Duck" # Optional default name for the class instance
|
||||
* Type = "Complex service" # Component type (Device, BasicService, ComplexService, App, Proxy, Stub, Utility)
|
||||
* Singleton = false # Optional singleton flag
|
||||
* Dependencies = ["Bird", "Animal"] # Optional list of dependencies
|
||||
*
|
||||
* [[Module.Component]] # Another component manifest
|
||||
* #...
|
||||
*
|
||||
* [[Module]] # Another module
|
||||
* Path = "large/greylag_goose.sdv # Relative path to the module
|
||||
* @endcode
|
||||
* @remarks The installation directory path is used to create relative paths to the modules. It is not stored in the manifest
|
||||
* itself allowing the manifest to be copied from one location to another as long as the relative path to the modules is maintained
|
||||
* (meaning copying the modules along with the manifest).
|
||||
*/
|
||||
class CInstallManifest
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Manifest information belonging to the component.
|
||||
*/
|
||||
struct SComponent
|
||||
{
|
||||
std::filesystem::path pathRelModule; ///< Relative module path (relative to the installation directory).
|
||||
std::string ssManifest; ///< Component manifest.
|
||||
std::string ssClassName; ///< String representing the class name.
|
||||
sdv::sequence<sdv::u8string> seqAliases; ///< Sequence containing zero or more class name aliases.
|
||||
std::string ssDefaultObjectName; ///< The default object name.
|
||||
sdv::EObjectType eType; ///< Type of object.
|
||||
uint32_t uiFlags; ///< Zero or more object flags from EObjectFlags.
|
||||
sdv::sequence<sdv::u8string> seqDependencies; ///< This component depends on...
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Default constructor.
|
||||
*/
|
||||
CInstallManifest() = default;
|
||||
|
||||
/**
|
||||
* @brief Is this a valid installation manifest (installation directory path is known and a name is given)?
|
||||
* @return Returns whether the installation manifest is valid.
|
||||
*/
|
||||
bool IsValid() const;
|
||||
|
||||
/**
|
||||
* @brief Clear the current manifest to start a new manifest.
|
||||
*/
|
||||
void Clear();
|
||||
|
||||
/**
|
||||
* @brief Get the installation name.
|
||||
* @return Reference to the string holding the installation name.
|
||||
*/
|
||||
const std::string& InstallName() const;
|
||||
|
||||
/**
|
||||
* @brief Get the package version from the version property.
|
||||
* @remarks The version property can hold additional information, which is not available through this function.
|
||||
* @return Structure containing the version information.
|
||||
*/
|
||||
sdv::installation::SPackageVersion Version() const;
|
||||
|
||||
/**
|
||||
* @brief Get the installation directory path.
|
||||
* @return Reference to the path to the installation.
|
||||
*/
|
||||
const std::filesystem::path& InstallDir() const;
|
||||
|
||||
/**
|
||||
* @brief Create a new manifest.
|
||||
* @param[in] rssInstallName Reference to the string containing the installaltion name.
|
||||
* @return Returns whether manifest creation was successful.
|
||||
*/
|
||||
bool Create(const std::string& rssInstallName);
|
||||
|
||||
/**
|
||||
* @brief Load a manifest TOML file.
|
||||
* @param[in] rpathInstallDir Reference to the installation directory.
|
||||
* @param[in] bBlockSystemObjects When set, system objects are not stored in the repository.
|
||||
* @return Returns whether loading the TOML file was successful.
|
||||
*/
|
||||
bool Load(const std::filesystem::path& rpathInstallDir, bool bBlockSystemObjects = false);
|
||||
|
||||
/**
|
||||
* @brief Save a manifest TOML file.
|
||||
* @param[in] rpathInstallDir Reference to the installation directory.
|
||||
* @return Returns whether saving the TOML file was successful.
|
||||
*/
|
||||
bool Save(const std::filesystem::path& rpathInstallDir) const;
|
||||
|
||||
/**
|
||||
* @brief Read a manifest TOML string.
|
||||
* @param[in] rssManifest Reference to the string containing the manifest.
|
||||
* @param[in] bBlockSystemObjects When set, system objects are not stored in the repository.
|
||||
* @return Returns whether reading the TOML string was successful.
|
||||
*/
|
||||
bool Read(const std::string& rssManifest, bool bBlockSystemObjects = false);
|
||||
|
||||
/**
|
||||
* @brief Write a manifest TOML string.
|
||||
* @return The manifest TOML string when successful or an empty string when not.
|
||||
*/
|
||||
std::string Write() const;
|
||||
|
||||
/**
|
||||
* @brief Add a module to the installation manifest (if the module contains components).
|
||||
* @attention This function should not be called by the core application, since it imposes a security risk!
|
||||
* @param[in] rpathModulePath Reference to the module path.
|
||||
* @param[in] rpathRelTargetDir The relative target directory the module should be stored at.
|
||||
* @return Returns 'true' when successful; 'false' when not.
|
||||
*/
|
||||
bool AddModule(const std::filesystem::path& rpathModulePath, const std::filesystem::path& rpathRelTargetDir = ".");
|
||||
|
||||
/**
|
||||
* @brief Find the module stored in the installation manifest.
|
||||
* @pre Only successful for manifests having an installation directory.
|
||||
* @param[in] rpathRelModule Reference to the path containing the relative path to a module.
|
||||
* @return Returns the full path if the module was found or an empty path when not.
|
||||
*/
|
||||
std::filesystem::path FindModule(const std::filesystem::path& rpathRelModule) const;
|
||||
|
||||
/**
|
||||
* @brief Find the module manifest.
|
||||
* @pre Only successful for manifests having an installation directory.
|
||||
* @param[in] rpathRelModule Reference to the path containing the relative path to a module.
|
||||
* @return Returns the string containing the module manifest.
|
||||
*/
|
||||
std::string FindModuleManifest(const std::filesystem::path& rpathRelModule) const;
|
||||
|
||||
/**
|
||||
* @brief Find the component stored in this installation manifest.
|
||||
* @param[in] rssClass Reference to the class name of the component.
|
||||
* @return The component manifest information.
|
||||
*/
|
||||
std::optional<SComponent> FindComponentByClass(const std::string& rssClass) const;
|
||||
|
||||
/**
|
||||
* @brief Get a vector of all components stored in this installation manifest.
|
||||
* @return The component manifest vector.
|
||||
*/
|
||||
std::vector<SComponent> ComponentList() const;
|
||||
|
||||
/**
|
||||
* @brief Get the module list.
|
||||
* @return Returns a vector with paths to all modules relative to the installation directory.
|
||||
*/
|
||||
std::vector<std::filesystem::path> ModuleList() const;
|
||||
|
||||
/**
|
||||
* @brief Get the property list.
|
||||
* @return Returns a vector with properties and the corresponding values.
|
||||
*/
|
||||
std::vector<std::pair<std::string, std::string>> PropertyList() const;
|
||||
|
||||
/**
|
||||
* @brief Set a property value.
|
||||
* @details Set a property value, which will be included in the installation manifest. The properties "Description" and
|
||||
* "Version" are used during package management.
|
||||
* @remarks Adding a property with the same name as a previously added property will replace the previous property value.
|
||||
* @param[in] rssName Name of the property. Spaces are allowed. Quotes are not allowed.
|
||||
* @param[in] rssValue Property value.
|
||||
*/
|
||||
void Property(const std::string& rssName, const std::string& rssValue);
|
||||
|
||||
/**
|
||||
* @brief Get a property value.
|
||||
* @param[in] rssName Name of the property. Spaces are allowed. Quotes are not allowed.
|
||||
* @return Returns the property value if existing.
|
||||
*/
|
||||
std::optional<std::string> Property(const std::string& rssName) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Checks the name for adherence to the bare key rule of TOML. If a bare key is not possible, a quoted key is required.
|
||||
* @remarks TOML defines for bare keys that they can only be composed of ASCII letters, ASCII digits, underscores and dashes.
|
||||
* @param[in] rssName Name to check for.
|
||||
* @return Returns whether quotes are required.
|
||||
*/
|
||||
static bool NeedQuotedName(const std::string& rssName);
|
||||
|
||||
/**
|
||||
* @brief Manifest information belonging to the module.
|
||||
*/
|
||||
struct SModule
|
||||
{
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rpathRelModule Reference to the relative module path.
|
||||
* @param[in] rssManifest Reference to the manifest file.
|
||||
* @param[in] bBlockSystemObjects Set when to block system objects.
|
||||
*/
|
||||
SModule(const std::filesystem::path& rpathRelModule, const std::string& rssManifest, bool bBlockSystemObjects);
|
||||
|
||||
std::filesystem::path pathRelModule; ///< Relative module path (relative to the installation directory).
|
||||
std::string ssManifest; ///< Manifest containing the components.
|
||||
std::vector<SComponent> vecComponents; ///< Vector with contained components
|
||||
};
|
||||
|
||||
std::string m_ssInstallName; ///< Installation name.
|
||||
mutable std::filesystem::path m_pathInstallDir; ///< Installation directory when install manifest was
|
||||
///< loaded or saved.
|
||||
bool m_bBlockSystemObjects = false; ///< When set, do not store system objects.
|
||||
std::vector<SModule> m_vecModules; ///< Vector containing the modules.
|
||||
std::map<std::string, std::string> m_mapProperties; ///< Property map.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Interpret a version number as string.
|
||||
* @details A version string is composed of: major.minor.patch (numbers only; characters and whitespace are ignored).
|
||||
* @param rssVersion Reference to the version string.
|
||||
* @return The interpreted version.
|
||||
*/
|
||||
inline sdv::installation::SPackageVersion InterpretVersionString(const std::string& rssVersion)
|
||||
{
|
||||
//
|
||||
sdv::installation::SPackageVersion sVersion{};
|
||||
size_t nPos = rssVersion.find('.');
|
||||
sVersion.uiMajor = static_cast<uint32_t>(std::atoi(rssVersion.substr(0, nPos).c_str()));
|
||||
size_t nStart = nPos;
|
||||
if (nStart != std::string::npos)
|
||||
{
|
||||
nStart++;
|
||||
nPos = rssVersion.find('.', nPos + 1);
|
||||
sVersion.uiMinor = static_cast<uint32_t>(std::atoi(rssVersion.substr(nStart, nPos).c_str()));
|
||||
}
|
||||
nStart = nPos;
|
||||
if (nStart != std::string::npos)
|
||||
{
|
||||
nStart++;
|
||||
sVersion.uiPatch = static_cast<uint32_t>(std::atoi(rssVersion.substr(nStart).c_str()));
|
||||
}
|
||||
return sVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Equality operator for package version.
|
||||
* @param[in] sFirst First package version used for the comparison.
|
||||
* @param[in] sSecond Second package version used for the comparison.
|
||||
* @return The result of the comparison.
|
||||
*/
|
||||
constexpr inline bool operator==(sdv::installation::SPackageVersion sFirst, sdv::installation::SPackageVersion sSecond)
|
||||
{
|
||||
return sFirst.uiMajor == sSecond.uiMajor && sFirst.uiMinor == sSecond.uiMinor && sFirst.uiPatch == sSecond.uiPatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Equal or larger than operator for package version.
|
||||
* @param[in] sFirst First package version used for the comparison.
|
||||
* @param[in] sSecond Second package version used for the comparison.
|
||||
* @return The result of the comparison.
|
||||
*/
|
||||
constexpr inline bool operator>=(sdv::installation::SPackageVersion sFirst, sdv::installation::SPackageVersion sSecond)
|
||||
{
|
||||
return sFirst.uiMajor > sSecond.uiMajor
|
||||
|| (sFirst.uiMajor == sSecond.uiMajor
|
||||
&& (sFirst.uiMinor > sSecond.uiMinor ||
|
||||
(sFirst.uiMinor == sSecond.uiMinor && sFirst.uiPatch >= sSecond.uiPatch)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Larger than operator for package version.
|
||||
* @param[in] sFirst First package version used for the comparison.
|
||||
* @param[in] sSecond Second package version used for the comparison.
|
||||
* @return The result of the comparison.
|
||||
*/
|
||||
constexpr inline bool operator>(sdv::installation::SPackageVersion sFirst, sdv::installation::SPackageVersion sSecond)
|
||||
{
|
||||
return sFirst.uiMajor > sSecond.uiMajor
|
||||
|| (sFirst.uiMajor == sSecond.uiMajor
|
||||
&& (sFirst.uiMinor > sSecond.uiMinor || (sFirst.uiMinor == sSecond.uiMinor && sFirst.uiPatch > sSecond.uiPatch)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Inequality operator for package version.
|
||||
* @param[in] sFirst First package version used for the comparison.
|
||||
* @param[in] sSecond Second package version used for the comparison.
|
||||
* @return The result of the comparison.
|
||||
*/
|
||||
constexpr inline bool operator!=(sdv::installation::SPackageVersion sFirst, sdv::installation::SPackageVersion sSecond)
|
||||
{
|
||||
return sFirst.uiMajor != sSecond.uiMajor || sFirst.uiMinor != sSecond.uiMinor || sFirst.uiPatch != sSecond.uiPatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Equality operator for package version.
|
||||
* @param[in] sFirst First package version used for the comparison.
|
||||
* @param[in] sSecond Second package version used for the comparison.
|
||||
* @return The result of the comparison.
|
||||
*/
|
||||
constexpr inline bool operator<(sdv::installation::SPackageVersion sFirst, sdv::installation::SPackageVersion sSecond)
|
||||
{
|
||||
return sFirst.uiMajor < sSecond.uiMajor
|
||||
|| (sFirst.uiMajor == sSecond.uiMajor
|
||||
&& (sFirst.uiMinor < sSecond.uiMinor || (sFirst.uiMinor == sSecond.uiMinor && sFirst.uiPatch < sSecond.uiPatch)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Equality operator for package version.
|
||||
* @param[in] sFirst First package version used for the comparison.
|
||||
* @param[in] sSecond Second package version used for the comparison.
|
||||
* @return The result of the comparison.
|
||||
*/
|
||||
constexpr inline bool operator<=(sdv::installation::SPackageVersion sFirst, sdv::installation::SPackageVersion sSecond)
|
||||
{
|
||||
return sFirst.uiMajor < sSecond.uiMajor
|
||||
|| (sFirst.uiMajor == sSecond.uiMajor
|
||||
&& (sFirst.uiMinor < sSecond.uiMinor || (sFirst.uiMinor == sSecond.uiMinor && sFirst.uiPatch <= sSecond.uiPatch)));
|
||||
}
|
||||
|
||||
#endif // !defined INSTALL_MANIFEST_H
|
||||
52
sdv_services/core/iso_monitor.cpp
Normal file
52
sdv_services/core/iso_monitor.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "iso_monitor.h"
|
||||
#include "sdv_core.h"
|
||||
#include "app_control.h"
|
||||
|
||||
CIsoMonitor::CIsoMonitor(sdv::IInterfaceAccess* pObject) :
|
||||
m_ptrObject(pObject), m_pObjectControl(m_ptrObject.GetInterface<sdv::IObjectControl>())
|
||||
{}
|
||||
|
||||
CIsoMonitor::~CIsoMonitor()
|
||||
{
|
||||
GetAppControl().RequestShutdown();
|
||||
}
|
||||
|
||||
void CIsoMonitor::Initialize(/*in*/ const sdv::u8string& ssObjectConfig)
|
||||
{
|
||||
if (m_pObjectControl)
|
||||
{
|
||||
m_pObjectControl->Initialize(ssObjectConfig);
|
||||
m_eObjectStatus = m_pObjectControl->GetStatus();
|
||||
}
|
||||
else
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialized;
|
||||
}
|
||||
|
||||
sdv::EObjectStatus CIsoMonitor::GetStatus() const
|
||||
{
|
||||
if (m_pObjectControl) return m_pObjectControl->GetStatus();
|
||||
return m_eObjectStatus;
|
||||
}
|
||||
|
||||
void CIsoMonitor::SetOperationMode(/*in*/ sdv::EOperationMode eMode)
|
||||
{
|
||||
if (m_pObjectControl) m_pObjectControl->SetOperationMode(eMode);
|
||||
}
|
||||
|
||||
void CIsoMonitor::Shutdown()
|
||||
{
|
||||
m_eObjectStatus = sdv::EObjectStatus::shutdown_in_progress;
|
||||
if (m_pObjectControl)
|
||||
{
|
||||
m_pObjectControl->Shutdown();
|
||||
m_eObjectStatus = m_pObjectControl->GetStatus();
|
||||
}
|
||||
m_eObjectStatus = sdv::EObjectStatus::destruction_pending;
|
||||
GetAppControl().RequestShutdown();
|
||||
m_pObjectControl = nullptr;
|
||||
}
|
||||
|
||||
sdv::IInterfaceAccess* CIsoMonitor::GetContainedObject()
|
||||
{
|
||||
return m_ptrObject;
|
||||
}
|
||||
69
sdv_services/core/iso_monitor.h
Normal file
69
sdv_services/core/iso_monitor.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#ifndef ISOLATION_OBJECT_MONITOR_H
|
||||
#define ISOLATION_OBJECT_MONITOR_H
|
||||
|
||||
#include <support/component_impl.h>
|
||||
|
||||
/**
|
||||
* @brief Isolation object monitor. If shutdown is called, the application leaves the running loop.
|
||||
* @remarks Only valid when running in an isolated application.
|
||||
*/
|
||||
class CIsoMonitor : public sdv::IInterfaceAccess, public sdv::IObjectControl
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] pObject Pointer to the object to monitor
|
||||
*/
|
||||
CIsoMonitor(sdv::IInterfaceAccess* pObject);
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CIsoMonitor();
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectControl)
|
||||
SDV_INTERFACE_CHAIN_MEMBER(m_ptrObject)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Initialize the object. Overload of sdv::IObjectControl::Initialize.
|
||||
* @param[in] ssObjectConfig Optional configuration string.
|
||||
*/
|
||||
virtual void Initialize(/*in*/ const sdv::u8string& ssObjectConfig) override;
|
||||
|
||||
/**
|
||||
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
|
||||
* @return Return the current status of the object.
|
||||
*/
|
||||
virtual sdv::EObjectStatus GetStatus() const override;
|
||||
|
||||
/**
|
||||
* @brief Set the component operation mode. Overload of sdv::IObjectControl::SetOperationMode.
|
||||
* @param[in] eMode The operation mode, the component should run in.
|
||||
*/
|
||||
virtual void SetOperationMode(/*in*/ sdv::EOperationMode eMode) override;
|
||||
|
||||
/**
|
||||
* @brief Shutdown called before the object is destroyed. Overload of sdv::IObjectControl::Shutdown.
|
||||
* @attention Implement calls to other SDV objects here as this is no longer considered safe during the destructor of the object!
|
||||
* After a call to shutdown any threads/callbacks/etc that could call other SDV objects need to have been stopped.
|
||||
* The SDV object itself is to remain in a state where it can respond to calls to its interfaces as other objects may still call it during the shutdown sequence!
|
||||
* Any subsequent call to GetStatus should return EObjectStatus::destruction_pending
|
||||
*/
|
||||
virtual void Shutdown() override;
|
||||
|
||||
/**
|
||||
* @brief Get the contained object from the isolation monitor.
|
||||
* @return The contained object.
|
||||
*/
|
||||
sdv::IInterfaceAccess* GetContainedObject();
|
||||
|
||||
private:
|
||||
sdv::TInterfaceAccessPtr m_ptrObject; ///< Smart pointer to the object.
|
||||
sdv::IObjectControl* m_pObjectControl = nullptr; ///< Pointer to the object control of the application
|
||||
sdv::EObjectStatus m_eObjectStatus = sdv::EObjectStatus::initialization_pending; ///< Object status (in case there is no object control).
|
||||
};
|
||||
|
||||
#endif // !defined ISOLATION_OBJECT_MONITOR_H
|
||||
240
sdv_services/core/local_shutdown_request.h
Normal file
240
sdv_services/core/local_shutdown_request.h
Normal file
@@ -0,0 +1,240 @@
|
||||
#ifndef LOCAL_SHUTDOWN_REQUEST_H
|
||||
#define LOCAL_SHUTDOWN_REQUEST_H
|
||||
|
||||
#ifdef _WIN32
|
||||
// Resolve conflict
|
||||
#pragma push_macro("interface")
|
||||
#undef interface
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
|
||||
// Resolve conflict
|
||||
#pragma pop_macro("interface")
|
||||
#ifdef GetClassInfo
|
||||
#undef GetClassInfo
|
||||
#endif
|
||||
#elif defined __unix__
|
||||
#include <semaphore.h>
|
||||
#include <time.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
#include "../../global/exec_dir_helper.h"
|
||||
|
||||
/**
|
||||
* @brief Send a exit loop request using a local signaling solution (used when running as essential or standalone application).
|
||||
* @attention There is no feedback that the shutdown was successful!
|
||||
* @param[in] uiInstanceID The instance to shut down.
|
||||
* @return Returns 'true' when the signal could be triggered, 'false' when no signal exists.
|
||||
*/
|
||||
inline bool RequestShutdown(uint32_t uiInstanceID = 1000u)
|
||||
{
|
||||
std::string m_ssSignalName = "SDV_EXIT_LOOP_REQUEST_" + std::to_string(uiInstanceID);
|
||||
#ifdef _WIN32
|
||||
HANDLE hEvent = OpenEventA(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, m_ssSignalName.c_str());
|
||||
if (hEvent && hEvent != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
SetEvent(hEvent);
|
||||
CloseHandle(hEvent);
|
||||
return true;
|
||||
}
|
||||
#elif defined __unix__
|
||||
sem_t* pSemaphore = sem_open(m_ssSignalName.c_str(), 0);
|
||||
if (pSemaphore && pSemaphore != SEM_FAILED)
|
||||
{
|
||||
// Since detecting whether or not the semaphore is triggered can only happen in the wait function, trigger 5 times with
|
||||
// each 1 ms apart.
|
||||
for (uint32_t uiIndex = 0; uiIndex < 5; uiIndex++)
|
||||
{
|
||||
sem_post(pSemaphore);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
sem_close(pSemaphore);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Obviously there is no signal available... no standalone instance is running.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the signal to receive a shutdown request and check whether a request was fired.
|
||||
*/
|
||||
class CShutdownRequestListener
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CShutdownRequestListener() = default;
|
||||
|
||||
/**
|
||||
* @brief Constructor.
|
||||
* @param[in] uiInstanceID The instance ID to check for.
|
||||
*/
|
||||
CShutdownRequestListener(uint32_t uiInstanceID);
|
||||
|
||||
/**
|
||||
* @brief No support for copy constructor.
|
||||
* @param[in] rListener Reference to the listener to copy.
|
||||
*/
|
||||
CShutdownRequestListener([[maybe_unused]] const CShutdownRequestListener& rListener) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
* @param[in] rListener Reference to the listener to move.
|
||||
*/
|
||||
CShutdownRequestListener(CShutdownRequestListener&& rListener) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CShutdownRequestListener();
|
||||
|
||||
/**
|
||||
* @brief Assignment operator is not supported.
|
||||
* @param[in] rListener Reference to the listener to copy.
|
||||
* @return Reference to the listener class.
|
||||
*/
|
||||
CShutdownRequestListener& operator=([[maybe_unused]] const CShutdownRequestListener& rListener) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move operator.
|
||||
* @param[in] rListener Reference to the listener to move.
|
||||
* @return Reference to the listener class.
|
||||
*/
|
||||
CShutdownRequestListener& operator=(CShutdownRequestListener&& rListener) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Check for validity.
|
||||
* @return Returns whether the signal was initialized correctly.
|
||||
*/
|
||||
bool IsValid() const;
|
||||
|
||||
/**
|
||||
* @brief Check whether a trigger occurred. To be called cyclicly.
|
||||
* @param[in] uiWaitForMs The amount of ms to wait for the shutdown request.
|
||||
* @return Returns 'true' when triggered; 'false' when not.
|
||||
*/
|
||||
bool HasTriggered(uint32_t uiWaitForMs) const;
|
||||
|
||||
private:
|
||||
std::string m_ssSignalName; ///< The signal name used for the synchronization.
|
||||
#ifdef _WIN32
|
||||
HANDLE m_hEvent = INVALID_HANDLE_VALUE; ///< The event handle
|
||||
#elif defined __unix__
|
||||
sem_t* m_pSemaphore = nullptr; ///< The semaphore
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
inline CShutdownRequestListener::CShutdownRequestListener(uint32_t uiInstanceID) :
|
||||
m_ssSignalName("SDV_EXIT_LOOP_REQUEST_" + std::to_string(uiInstanceID))
|
||||
{
|
||||
// Prevent multiple instances by opening the named event first (this should fail)...
|
||||
HANDLE hEvent = OpenEventA(SYNCHRONIZE, FALSE, m_ssSignalName.c_str());
|
||||
if (hEvent)
|
||||
CloseHandle(hEvent); // Another instance is already running. Do not create a new event.
|
||||
else
|
||||
m_hEvent = CreateEventA(nullptr, TRUE, FALSE, m_ssSignalName.c_str());
|
||||
}
|
||||
|
||||
inline CShutdownRequestListener::CShutdownRequestListener(CShutdownRequestListener&& rListener) noexcept :
|
||||
m_ssSignalName(std::move(rListener.m_ssSignalName)), m_hEvent(rListener.m_hEvent)
|
||||
{
|
||||
rListener.m_ssSignalName.clear();
|
||||
rListener.m_hEvent = 0;
|
||||
}
|
||||
|
||||
inline CShutdownRequestListener::~CShutdownRequestListener()
|
||||
{
|
||||
// Free the event object
|
||||
if (m_hEvent && m_hEvent != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(m_hEvent);
|
||||
}
|
||||
|
||||
inline CShutdownRequestListener& CShutdownRequestListener::operator=(CShutdownRequestListener&& rListener) noexcept
|
||||
{
|
||||
m_ssSignalName = std::move(rListener.m_ssSignalName);
|
||||
m_hEvent = rListener.m_hEvent;
|
||||
rListener.m_ssSignalName.clear();
|
||||
rListener.m_hEvent = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline bool CShutdownRequestListener::IsValid() const
|
||||
{
|
||||
return m_hEvent && m_hEvent != INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
inline bool CShutdownRequestListener::HasTriggered(uint32_t uiWaitForMs) const
|
||||
{
|
||||
return m_hEvent && m_hEvent != INVALID_HANDLE_VALUE && WaitForSingleObject(m_hEvent, uiWaitForMs) == WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
#elif defined __unix__
|
||||
|
||||
inline CShutdownRequestListener::CShutdownRequestListener(uint32_t uiInstanceID) :
|
||||
m_ssSignalName("SDV_EXIT_LOOP_REQUEST_" + std::to_string(uiInstanceID))
|
||||
{
|
||||
// Create semaphore object
|
||||
sem_unlink(m_ssSignalName.c_str());
|
||||
m_pSemaphore = sem_open(m_ssSignalName.c_str(), O_CREAT | O_EXCL, 0777 /*O_RDWR*/, 0);
|
||||
}
|
||||
|
||||
inline CShutdownRequestListener::CShutdownRequestListener(CShutdownRequestListener&& rListener) noexcept :
|
||||
m_ssSignalName(std::move(rListener.m_ssSignalName)), m_pSemaphore(rListener.m_pSemaphore)
|
||||
{
|
||||
rListener.m_ssSignalName.clear();
|
||||
rListener.m_pSemaphore = 0;
|
||||
}
|
||||
|
||||
inline CShutdownRequestListener::~CShutdownRequestListener()
|
||||
{
|
||||
if (m_pSemaphore && m_pSemaphore != SEM_FAILED)
|
||||
sem_unlink(m_ssSignalName.c_str());
|
||||
}
|
||||
|
||||
inline CShutdownRequestListener& CShutdownRequestListener::operator=(CShutdownRequestListener&& rListener) noexcept
|
||||
{
|
||||
m_ssSignalName = std::move(rListener.m_ssSignalName);
|
||||
m_pSemaphore = rListener.m_pSemaphore;
|
||||
rListener.m_ssSignalName.clear();
|
||||
rListener.m_pSemaphore = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline bool CShutdownRequestListener::IsValid() const
|
||||
{
|
||||
return m_pSemaphore && m_pSemaphore != SEM_FAILED;
|
||||
}
|
||||
|
||||
inline bool CShutdownRequestListener::HasTriggered(uint32_t uiWaitForMs) const
|
||||
{
|
||||
// Get the time from the realtime clock
|
||||
timespec sTimespec{};
|
||||
if (clock_gettime(CLOCK_REALTIME, &sTimespec) == -1)
|
||||
return false;
|
||||
uint64_t uiTimeNs = sTimespec.tv_nsec + uiWaitForMs * 1000000ull;
|
||||
sTimespec.tv_nsec = uiTimeNs % 1000000000ull;
|
||||
sTimespec.tv_sec += uiTimeNs / 1000000000ull;
|
||||
|
||||
// Wait for the semaphore
|
||||
return m_pSemaphore && m_pSemaphore != SEM_FAILED && sem_timedwait(m_pSemaphore, &sTimespec) == 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif // !defined LOCAL_SHUTDOWN_REQUEST_H
|
||||
71
sdv_services/core/log_csv_writer.cpp
Normal file
71
sdv_services/core/log_csv_writer.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#include "log_csv_writer.h"
|
||||
#include <sstream>
|
||||
|
||||
CLogCSVWriter::CLogCSVWriter(std::shared_ptr<std::ostream> ostream, const char separator /*= ';'*/)
|
||||
: m_streamOutput(std::move(ostream))
|
||||
, m_cSeparator(separator)
|
||||
{
|
||||
if ((m_cSeparator <= 0x08) || (m_cSeparator >= 0x0A && m_cSeparator <= 0x1F)
|
||||
|| (m_cSeparator >= 0x30 && m_cSeparator <= 0x39) || (m_cSeparator >= 0x41 && m_cSeparator <= 0x5A)
|
||||
|| (m_cSeparator >= 0x61 && m_cSeparator <= 0x7A) || static_cast<uint8_t>(m_cSeparator) >= 0x7F
|
||||
|| m_cSeparator == '\"' || m_cSeparator == '.' || m_cSeparator == ':' || m_cSeparator == ' ')
|
||||
{
|
||||
throw SForbiddenSeparatorException((std::string("Forbidden Separator '") + m_cSeparator + "'").c_str());
|
||||
}
|
||||
|
||||
std::string intro = std::string("sep=") + m_cSeparator + "\nTimestamp" + m_cSeparator + "Severity" + m_cSeparator
|
||||
+ "Tag" + m_cSeparator + "Message\n";
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxOutput);
|
||||
*m_streamOutput << intro;
|
||||
m_streamOutput->flush();
|
||||
}
|
||||
|
||||
void CLogCSVWriter::Write(const std::string& message, const sdv::core::ELogSeverity severity,
|
||||
const std::chrono::time_point<std::chrono::system_clock> timestamp)
|
||||
{
|
||||
std::string output = GetDateTime(timestamp) + m_cSeparator + GetSeverityString(severity) +
|
||||
m_cSeparator + "\"" + m_cSeparator + "\"" + Escape(message) + "\"\n";
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxOutput);
|
||||
*m_streamOutput << output;
|
||||
m_streamOutput->flush();
|
||||
}
|
||||
|
||||
std::string CLogCSVWriter::GetDateTime(const std::chrono::time_point<std::chrono::system_clock> timestamp)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
std::stringstream output;
|
||||
microseconds micros = duration_cast<microseconds>(timestamp.time_since_epoch());
|
||||
uint32_t fractionalSeconds = micros.count() % 1000000;
|
||||
std::string timeFormat = "%Y.%m.%d %H:%M:%S.";
|
||||
std::time_t time = system_clock::to_time_t(timestamp);
|
||||
output << std::put_time(std::localtime(&time), timeFormat.c_str());
|
||||
output << std::setw(6) << std::setfill('0') << fractionalSeconds;
|
||||
return output.str();
|
||||
}
|
||||
|
||||
std::string CLogCSVWriter::GetSeverityString(sdv::core::ELogSeverity severity)
|
||||
{
|
||||
switch (severity)
|
||||
{
|
||||
case sdv::core::ELogSeverity::trace: return "Trace";
|
||||
case sdv::core::ELogSeverity::debug: return "Debug";
|
||||
case sdv::core::ELogSeverity::info: return "Info";
|
||||
case sdv::core::ELogSeverity::warning: return "Warning";
|
||||
case sdv::core::ELogSeverity::error: return "Error";
|
||||
case sdv::core::ELogSeverity::fatal: return "Fatal";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
std::string CLogCSVWriter::Escape(const std::string& toEscape)
|
||||
{
|
||||
std::string escaped = toEscape;
|
||||
const std::string with("\"\"");
|
||||
for (std::string::size_type pos{}; std::string::npos != (pos = escaped.find('\"', pos)); pos += with.length())
|
||||
{
|
||||
escaped.replace(pos, 1, with.data(), with.length());
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
68
sdv_services/core/log_csv_writer.h
Normal file
68
sdv_services/core/log_csv_writer.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#ifndef LOG_CSV_WRITER_H
|
||||
#define LOG_CSV_WRITER_H
|
||||
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <interfaces/log.h>
|
||||
#include <mutex>
|
||||
|
||||
/**
|
||||
* @brief Writes CSV entries to a given stream.
|
||||
*/
|
||||
class CLogCSVWriter
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a CSV writer.
|
||||
* @param[in] ostream stream the CSV writer will write to.
|
||||
* @param[in] separator sets the separator of the CSV file, default is ';'.
|
||||
*/
|
||||
CLogCSVWriter(std::shared_ptr<std::ostream> ostream, const char separator = ';');
|
||||
|
||||
/**
|
||||
* @brief Creates a log entry in the log.
|
||||
* @param[in] message message of the log entry.
|
||||
* @param[in] severity severity of the log entry.
|
||||
* @param[in] timestamp timestamp of the log entry.
|
||||
*/
|
||||
void Write(const std::string& message, const sdv::core::ELogSeverity severity,
|
||||
const std::chrono::time_point<std::chrono::system_clock> timestamp);
|
||||
|
||||
/**
|
||||
* @brief Creates a string of the given timestamp including nano seconds.
|
||||
* @param[in] timestamp Timestamp to be converted to a string.
|
||||
* @return Returns the string representing the timestamp including nano seconds.
|
||||
*/
|
||||
std::string GetDateTime(const std::chrono::time_point<std::chrono::system_clock> timestamp);
|
||||
|
||||
/**
|
||||
* @brief Creates string from the given severity.
|
||||
* @param[in] severity Severity to be converted to a string.
|
||||
* @return Returns the string representing the severity.
|
||||
*/
|
||||
static std::string GetSeverityString(sdv::core::ELogSeverity severity);
|
||||
|
||||
/**
|
||||
* @brief Exception if an invalid separator is set.
|
||||
*/
|
||||
struct SForbiddenSeparatorException : public std::runtime_error
|
||||
{
|
||||
/**
|
||||
* @brief Construct a forbidden separator Exception object.
|
||||
* @param[in] message The reason for throwing this exception.
|
||||
*/
|
||||
SForbiddenSeparatorException(const char* message)
|
||||
: std::runtime_error(message)
|
||||
{}
|
||||
};
|
||||
|
||||
private:
|
||||
static std::string Escape(const std::string& toEscape);
|
||||
|
||||
std::shared_ptr<std::ostream> m_streamOutput; ///< Output stream
|
||||
const char m_cSeparator; ///< The separator character
|
||||
mutable std::mutex m_mtxOutput; ///< Protect stream access
|
||||
};
|
||||
|
||||
#endif // !defined LOG_CSV_WRITER_H
|
||||
151
sdv_services/core/logger.cpp
Normal file
151
sdv_services/core/logger.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
#include "logger.h"
|
||||
#include <sstream>
|
||||
#include "sdv_core.h"
|
||||
#include "../../global/exec_dir_helper.h"
|
||||
|
||||
#ifdef __unix__
|
||||
#include <syslog.h>
|
||||
#endif
|
||||
#ifdef __GNUC__
|
||||
#include <unistd.h> // for getpid
|
||||
#endif
|
||||
|
||||
CLogger::~CLogger()
|
||||
{
|
||||
#ifdef __unix__
|
||||
if (m_bLogOpen) closelog();
|
||||
#endif
|
||||
}
|
||||
|
||||
void CLogger::Log(sdv::core::ELogSeverity eSeverity, /*in*/ const sdv::u8string& ssSrcFile, /*in*/ uint32_t uiSrcLine,
|
||||
/*in*/ sdv::process::TProcessID tProcessID, /*in*/ const sdv:: u8string& ssObjectName, /*in*/ const sdv::u8string& ssMessage)
|
||||
{
|
||||
if (static_cast<uint32_t>(eSeverity) >= static_cast<uint32_t>(m_eViewFilter))
|
||||
{
|
||||
if (tProcessID) std::clog << "[PID#" << static_cast<int64_t>(tProcessID) << "] ";
|
||||
if (!ssObjectName.empty()) std::clog << ssObjectName << " ";
|
||||
switch (eSeverity)
|
||||
{
|
||||
case sdv::core::ELogSeverity::debug:
|
||||
std::clog << "Debug: " << ssMessage << std::endl;
|
||||
break;
|
||||
case sdv::core::ELogSeverity::trace:
|
||||
std::clog << "Trace: " << ssMessage << std::endl;
|
||||
break;
|
||||
case sdv::core::ELogSeverity::info:
|
||||
std::clog << "Info: " << ssMessage << std::endl;
|
||||
break;
|
||||
case sdv::core::ELogSeverity::warning:
|
||||
std::clog << "Warning: " << ssMessage << std::endl;
|
||||
break;
|
||||
case sdv::core::ELogSeverity::error:
|
||||
std::clog << "Error: " << ssMessage << std::endl;
|
||||
break;
|
||||
case sdv::core::ELogSeverity::fatal:
|
||||
std::clog << "Fatal: " << ssMessage << std::endl;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the log filter
|
||||
if (static_cast<uint32_t>(eSeverity) < static_cast<uint32_t>(m_eFilter)) return;
|
||||
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxLogger);
|
||||
if (m_ssProgramtag.empty())
|
||||
m_ssProgramtag = std::string("SDV_LOG_") + std::to_string(getpid());
|
||||
|
||||
sdv::u8string ssExtendedMessage;
|
||||
ssExtendedMessage += ssSrcFile + "(" + std::to_string(uiSrcLine) + "): " + ssMessage;
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!m_ptrWriter)
|
||||
{
|
||||
std::string logFileName = m_ssProgramtag + "_" +
|
||||
GetDateTime(std::chrono::system_clock::now()) + ".log";
|
||||
|
||||
auto path = GetExecDirectory() / logFileName;
|
||||
auto stream = std::make_shared<std::ofstream>(path, std::ios_base::out | std::ios_base::app);
|
||||
if (!stream->is_open())
|
||||
{
|
||||
std::cerr << "ERROR: Log file cannot be created!" << std::endl;
|
||||
return;
|
||||
}
|
||||
m_ptrWriter = std::move(std::make_unique<CLogCSVWriter>(stream));
|
||||
if (!m_ptrWriter)
|
||||
{
|
||||
std::cerr << "ERROR: Cannot create CSV write object!" << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_ptrWriter->Write(ssExtendedMessage, eSeverity, std::chrono::system_clock::now());
|
||||
#elif defined __unix__
|
||||
if (!m_bLogOpen)
|
||||
openlog(m_ssProgramtag.c_str(), LOG_PID | LOG_CONS, LOG_USER);
|
||||
m_bLogOpen = true;
|
||||
switch(eSeverity)
|
||||
{
|
||||
case sdv::core::ELogSeverity::trace : syslog(LOG_NOTICE, "%s", ssExtendedMessage.c_str()); break;
|
||||
case sdv::core::ELogSeverity::debug : syslog(LOG_DEBUG, "%s", ssExtendedMessage.c_str()); break;
|
||||
case sdv::core::ELogSeverity::info : syslog(LOG_INFO, "%s", ssExtendedMessage.c_str()); break;
|
||||
case sdv::core::ELogSeverity::warning : syslog(LOG_WARNING, "%s", ssExtendedMessage.c_str()); break;
|
||||
case sdv::core::ELogSeverity::error : syslog(LOG_ERR, "%s", ssExtendedMessage.c_str()); break;
|
||||
case sdv::core::ELogSeverity::fatal : syslog(LOG_CRIT, "%s", ssExtendedMessage.c_str()); break;
|
||||
default: std::cerr << "ERROR: Logging level is undefined!"; break;
|
||||
}
|
||||
#else
|
||||
#error OS not supported!
|
||||
#endif
|
||||
}
|
||||
|
||||
void CLogger::SetProgramTag(const sdv::u8string& ssTag)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxLogger);
|
||||
|
||||
if (m_ssProgramtag.empty())
|
||||
m_ssProgramtag = ssTag;
|
||||
}
|
||||
|
||||
sdv::u8string CLogger::GetProgramTag() const
|
||||
{
|
||||
return m_ssProgramtag;
|
||||
}
|
||||
|
||||
void CLogger::SetLogFilter(/*in*/ sdv::core::ELogSeverity eSeverity, /*in*/ sdv::core::ELogSeverity eViewSeverity)
|
||||
{
|
||||
m_eFilter = eSeverity;
|
||||
m_eViewFilter = eViewSeverity;
|
||||
}
|
||||
|
||||
sdv::core::ELogSeverity CLogger::GetLogFilter() const
|
||||
{
|
||||
return m_eFilter;
|
||||
}
|
||||
|
||||
sdv::core::ELogSeverity CLogger::GetViewFilter() const
|
||||
{
|
||||
return m_eViewFilter;
|
||||
}
|
||||
|
||||
std::string CLogger::GetDateTime(std::chrono::time_point<std::chrono::system_clock> timestamp)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
std::stringstream output;
|
||||
microseconds micros = duration_cast<microseconds>(timestamp.time_since_epoch());
|
||||
|
||||
std::string timeFormat = "_%Y.%m.%d_%H-%M-%S";
|
||||
|
||||
std::time_t time = system_clock::to_time_t(timestamp);
|
||||
output << std::put_time(std::localtime(&time), timeFormat.c_str());
|
||||
return output.str();
|
||||
}
|
||||
|
||||
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
|
||||
CLogger& CLoggerService::GetDefaultLogger()
|
||||
{
|
||||
return ::GetDefaultLogger();
|
||||
}
|
||||
|
||||
#endif
|
||||
128
sdv_services/core/logger.h
Normal file
128
sdv_services/core/logger.h
Normal file
@@ -0,0 +1,128 @@
|
||||
#ifndef LOGGER_H
|
||||
#define LOGGER_H
|
||||
|
||||
#include <interfaces/log.h>
|
||||
#include <support/interface_ptr.h>
|
||||
#include <support/component_impl.h>
|
||||
#include "log_csv_writer.h"
|
||||
|
||||
/**
|
||||
* @brief Logger class implementation for Windows to enable logging in a logfile.
|
||||
*/
|
||||
class CLogger : public sdv::IInterfaceAccess, public sdv::core::ILogger, public sdv::core::ILoggerConfig
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CLogger() = default;
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CLogger();
|
||||
|
||||
// Interface table
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::core::ILogger)
|
||||
SDV_INTERFACE_ENTRY(sdv::core::ILoggerConfig)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Log a message to the SDV log. Overload of sdv::core::ILogger::Log.
|
||||
* @param[in] eSeverity Severity level of the log message which will be logged, e.g. info, warning, error etc.
|
||||
* @param[in] ssSrcFile The source file that caused the log entry.
|
||||
* @param[in] uiSrcLine The line number in the source file that caused the log entry.
|
||||
* @param[in] tProcessID Process ID of the process reporting this log entry.
|
||||
* @param[in] ssObjectName Name of the object if the log entry is supplied by a component.
|
||||
* @param[in] ssMessage The log message that will be logged.
|
||||
*/
|
||||
virtual void Log(/*in*/ sdv::core::ELogSeverity eSeverity, /*in*/ const sdv::u8string& ssSrcFile, /*in*/ uint32_t uiSrcLine,
|
||||
/*in*/ sdv::process::TProcessID tProcessID, /*in*/ const sdv:: u8string& ssObjectName, /*in*/ const sdv::u8string& ssMessage) override;
|
||||
|
||||
/**
|
||||
* @brief Initialize the logging from SDV platform abstraction. Overload of sdv::core::ILoggerConfig::SetProgramTag.
|
||||
* @details This function needs to be called before starting to log.
|
||||
* @param[in] ssTag Provided tag to create log.
|
||||
*/
|
||||
virtual void SetProgramTag(/*in*/ const sdv::u8string& ssTag) override;
|
||||
|
||||
/**
|
||||
* @brief Get the program tag used for logging. Overload of sdv::core::ILoggerConfig::GetProgramTag.
|
||||
* @return The string containing the program tag.
|
||||
*/
|
||||
virtual sdv::u8string GetProgramTag() const override;
|
||||
|
||||
/**
|
||||
* @brief Filter the log messages based on severity. Overload of sdv::core::ILoggerConfig::SetLogFilter.
|
||||
* @param[in] eSeverity The severity level to use as a lowest level for logging. Default is "info" meaning, that
|
||||
* debug and trace messages will not be logged.
|
||||
* @param[in] eViewSeverity The severity level to use as a lowest level for viewing. Default is "error" meaning, that
|
||||
* debug, trace, info, warning and error messages will not be shown in console output.
|
||||
*/
|
||||
virtual void SetLogFilter(/*in*/ sdv::core::ELogSeverity eSeverity, /*in*/ sdv::core::ELogSeverity eViewSeverity) override;
|
||||
|
||||
/**
|
||||
* @brief Get the current log severity filter level. Overload of sdv::core::ILoggerConfig::GetLogFilter.
|
||||
* @return The severity level of the log filter.
|
||||
*/
|
||||
virtual sdv::core::ELogSeverity GetLogFilter() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the current view severity level. Overload of sdv::core::ILoggerConfig::GetViewFilter.
|
||||
* @return The severity level of the view filter.
|
||||
*/
|
||||
virtual sdv::core::ELogSeverity GetViewFilter() const override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Creates a string of the given timestamp without nano seconds.
|
||||
* @param[in] timestamp Timestamp to be converted to a string.
|
||||
* @return Returns the string representing the timestamp without nano seconds.
|
||||
*/
|
||||
static std::string GetDateTime(std::chrono::time_point<std::chrono::system_clock> timestamp);
|
||||
|
||||
std::recursive_mutex m_mtxLogger; ///< Mutex for logging
|
||||
sdv::core::ELogSeverity m_eFilter = sdv::core::ELogSeverity::info; ///< Severity filter for logging
|
||||
sdv::core::ELogSeverity m_eViewFilter = sdv::core::ELogSeverity::info; ///< Severity filter for viewing
|
||||
std::string m_ssProgramtag; ///< Program tag to use for logging.
|
||||
#ifdef _WIN32
|
||||
std::unique_ptr<CLogCSVWriter> m_ptrWriter; ///< Pointer to CSVWriter
|
||||
#elif defined __unix__
|
||||
bool m_bLogOpen = false; ///< Check if the logger is opened.
|
||||
#else
|
||||
#error OS currently not supported!
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
|
||||
/**
|
||||
* @brief Logger service
|
||||
*/
|
||||
class CLoggerService : public sdv::CSdvObject
|
||||
{
|
||||
public:
|
||||
CLoggerService() = default;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_CHAIN_MEMBER(GetDefaultLogger())
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
|
||||
DECLARE_OBJECT_CLASS_NAME("DefaultLoggerService")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Get access to the repository.
|
||||
* @return Returns a reference to the one repository of this module.
|
||||
*/
|
||||
static CLogger& GetDefaultLogger();
|
||||
};
|
||||
|
||||
DEFINE_SDV_OBJECT_NO_EXPORT(CLoggerService)
|
||||
#endif
|
||||
|
||||
#endif // !defined LOGGER_H
|
||||
77
sdv_services/core/logger_control.cpp
Normal file
77
sdv_services/core/logger_control.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "logger_control.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <process.h> // Needed for _getpid
|
||||
#endif
|
||||
|
||||
CLoggerControl::~CLoggerControl()
|
||||
{
|
||||
// Reset the logger. This will finialize an open log.
|
||||
SetLogger(nullptr);
|
||||
}
|
||||
|
||||
void CLoggerControl::Log(/*in*/ sdv::core::ELogSeverity eSeverity, /*in*/ const sdv::u8string& ssSrcFile, /*in*/ uint32_t uiSrcLine,
|
||||
/*in*/ const sdv::process::TProcessID tProcessID, /*in*/ const sdv::u8string& ssObjectName, /*in*/ const sdv::u8string& ssMessage)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxPrematureLog);
|
||||
|
||||
// Is a registered logger available?
|
||||
if (m_pLogger)
|
||||
{
|
||||
sdv::core::ILogger* pLogger = m_pLogger;
|
||||
lock.unlock();
|
||||
pLogger->Log(eSeverity, ssSrcFile, uiSrcLine, tProcessID, ssObjectName, ssMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the log entry for later use. Store at the most 1000 messages.
|
||||
if (m_queuePrematureLog.size() >= 1000) return;
|
||||
if (m_queuePrematureLog.size() == 999)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
sdv::process::TProcessID tProcessIDLocal = _getpid();
|
||||
#elif defined __unix__
|
||||
sdv::process::TProcessID tProcessIDLocal = getpid();
|
||||
#else
|
||||
#error The OS currently is not supported!
|
||||
#endif
|
||||
SLogEntry entryTruncated{ sdv::core::ELogSeverity::warning, "", 0, tProcessIDLocal, "", "Log truncated..." };
|
||||
m_queuePrematureLog.push(entryTruncated);
|
||||
return;
|
||||
}
|
||||
SLogEntry entry{ eSeverity, ssSrcFile, uiSrcLine, tProcessID, ssObjectName, ssMessage };
|
||||
m_queuePrematureLog.push(entry);
|
||||
}
|
||||
|
||||
void CLoggerControl::SetLogger(sdv::core::ILogger* pLogger)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxPrematureLog);
|
||||
if (pLogger == m_pLogger) return;
|
||||
|
||||
// When assigning a logger, write the stored entries to the log
|
||||
#ifdef _WIN32
|
||||
sdv::process::TProcessID tProcessID = _getpid();
|
||||
#elif defined __unix__
|
||||
sdv::process::TProcessID tProcessID = getpid();
|
||||
#else
|
||||
#error The OS currently is not supported!
|
||||
#endif
|
||||
if (pLogger)
|
||||
{
|
||||
pLogger->Log(sdv::core::ELogSeverity::info, "", 0, tProcessID, "", "Begin logging...");
|
||||
while (!m_queuePrematureLog.empty())
|
||||
{
|
||||
SLogEntry& rentry = m_queuePrematureLog.front();
|
||||
pLogger->Log(rentry.eSeverity, rentry.ssSrcFile, rentry.uiSrcLine, rentry.tProcessID, rentry.ssObjectName,
|
||||
rentry.ssMessage);
|
||||
m_queuePrematureLog.pop();
|
||||
}
|
||||
}
|
||||
else // Finish the log
|
||||
{
|
||||
m_pLogger->Log(sdv::core::ELogSeverity::info, "", 0, tProcessID, "", "End logging...");
|
||||
}
|
||||
|
||||
// Store the new logger pointer
|
||||
m_pLogger = pLogger;
|
||||
}
|
||||
69
sdv_services/core/logger_control.h
Normal file
69
sdv_services/core/logger_control.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#ifndef LOGGER_CONTROL_H
|
||||
#define LOGGER_CONTROL_H
|
||||
|
||||
#include <support/interface_ptr.h>
|
||||
#include <interfaces/log.h>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* @brief Logger control class.
|
||||
*/
|
||||
class CLoggerControl : public sdv::IInterfaceAccess, public sdv::core::ILogger
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CLoggerControl() = default;
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CLoggerControl();
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::core::ILogger)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Log a message to the SDV log. Overload of sdv::core::ILogger::Log.
|
||||
* @param[in] eSeverity Severity level of the log message which will be logged, e.g. info, warning, error etc.
|
||||
* @param[in] ssSrcFile The source file that caused the log entry.
|
||||
* @param[in] uiSrcLine The line number in the source file that caused the log entry.
|
||||
* @param[in] tProcessID Process ID of the process reporting this log entry.
|
||||
* @param[in] ssObjectName Name of the object if the log entry is supplied by a component.
|
||||
* @param[in] ssMessage The log message that will be logged.
|
||||
*/
|
||||
virtual void Log(/*in*/ sdv::core::ELogSeverity eSeverity, /*in*/ const sdv::u8string& ssSrcFile, /*in*/ uint32_t uiSrcLine,
|
||||
/*in*/ const sdv::process::TProcessID tProcessID, /*in*/ const sdv::u8string& ssObjectName,
|
||||
/*in*/ const sdv::u8string& ssMessage) override;
|
||||
|
||||
/**
|
||||
* @brief Set the logger to use for logging.
|
||||
* @param[in] pLogger
|
||||
*/
|
||||
void SetLogger(sdv::core::ILogger* pLogger);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Log entry to store.
|
||||
*/
|
||||
struct SLogEntry
|
||||
{
|
||||
sdv::core::ELogSeverity eSeverity; ///< Severity level of the log message.
|
||||
sdv::u8string ssSrcFile; ///< The source file that caused the log entry.
|
||||
uint32_t uiSrcLine; ///< The line number in the source file that caused the log entry.
|
||||
sdv::process::TProcessID tProcessID; ///< Process ID of the process submitting the log entry.
|
||||
sdv::u8string ssObjectName; ///< Name of the object if the log entry is submitted by an object.
|
||||
sdv::u8string ssMessage; ///< The log message that will be logged.
|
||||
};
|
||||
|
||||
mutable std::mutex m_mtxPrematureLog; ///< Protect the logger queue access.
|
||||
std::queue<SLogEntry> m_queuePrematureLog; ///< Logger queue to store messages before a logger was assigned.
|
||||
sdv::core::ILogger* m_pLogger = nullptr; ///< Interface for the actual logger.
|
||||
};
|
||||
|
||||
#endif // !defined LOGGER_CONTROL_H
|
||||
62
sdv_services/core/memory.cpp
Normal file
62
sdv_services/core/memory.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "memory.h"
|
||||
|
||||
sdv::pointer<uint8_t> CMemoryManager::Allocate(uint32_t uiLength)
|
||||
{
|
||||
return sdv::internal::make_ptr<uint8_t>(this, uiLength);
|
||||
}
|
||||
|
||||
void* CMemoryManager::Alloc(size_t nSize)
|
||||
{
|
||||
void* pAlloc = malloc(nSize);
|
||||
#ifdef MEMORY_TRACKER
|
||||
std::unique_lock<std::mutex> lock(m_mtxTracker);
|
||||
if (pAlloc)
|
||||
m_mapTracker.insert(std::make_pair(pAlloc, nSize));
|
||||
#endif
|
||||
return pAlloc;
|
||||
}
|
||||
|
||||
void* CMemoryManager::Realloc(void* pData, size_t nSize)
|
||||
{
|
||||
#ifdef MEMORY_TRACKER
|
||||
std::unique_lock<std::mutex> lock(m_mtxTracker);
|
||||
auto itAlloc = m_mapTracker.find(pData);
|
||||
if (pData && itAlloc == m_mapTracker.end())
|
||||
{
|
||||
std::cout << "Illegal request for resizing memory at location 0x" << (void*)pData << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
lock.unlock();
|
||||
#endif
|
||||
|
||||
void* pAlloc = realloc(pData, nSize);
|
||||
|
||||
#ifdef MEMORY_TRACKER
|
||||
if (pAlloc)
|
||||
{
|
||||
lock.lock();
|
||||
m_mapTracker.erase(pData);
|
||||
m_mapTracker.insert(std::make_pair(pAlloc, nSize));
|
||||
}
|
||||
#endif
|
||||
|
||||
return pAlloc;
|
||||
}
|
||||
|
||||
void CMemoryManager::Free(void* pData)
|
||||
{
|
||||
#ifdef MEMORY_TRACKER
|
||||
std::unique_lock<std::mutex> lock(m_mtxTracker);
|
||||
auto itAlloc = m_mapTracker.find(pData);
|
||||
if (itAlloc == m_mapTracker.end())
|
||||
{
|
||||
std::cout << "Illegal request for freeing memory at location 0x" << (void*)pData << std::endl;
|
||||
return;
|
||||
}
|
||||
else
|
||||
m_mapTracker.erase(itAlloc);
|
||||
lock.unlock();
|
||||
#endif
|
||||
|
||||
free(pData);
|
||||
}
|
||||
57
sdv_services/core/memory.h
Normal file
57
sdv_services/core/memory.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#ifndef MEMORY_H
|
||||
#define MEMORY_H
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
#include <support/pointer.h>
|
||||
#include <interfaces/mem.h>
|
||||
#include <support/component_impl.h>
|
||||
#include <support/mem_access.h>
|
||||
|
||||
/**
|
||||
* @brief Memory Manager service
|
||||
*/
|
||||
class CMemoryManager : public sdv::core::IMemoryAlloc, public sdv::IInterfaceAccess, public sdv::internal::IInternalMemAlloc
|
||||
{
|
||||
public:
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::core::IMemoryAlloc)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Allocate a memory block of the provided length. Overload of sdv::core::IMemoryAlloc::Allocate.
|
||||
* @param[in] uiLength The length of the memory block to allocate.
|
||||
* @return Smart pointer to the memory allocation or NULL when allocating was not possible.
|
||||
*/
|
||||
virtual sdv::pointer<uint8_t> Allocate(/*in*/ uint32_t uiLength) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Allocate memory. Overload of internal::IInternalMemAlloc::Alloc.
|
||||
* @param[in] nSize The size of the memory to allocate (in bytes).
|
||||
* @return Pointer to the memory allocation or NULL when memory allocation failed.
|
||||
*/
|
||||
virtual void* Alloc(size_t nSize) override;
|
||||
|
||||
/**
|
||||
* @brief Reallocate memory. Overload of internal::IInternalMemAlloc::Realloc.
|
||||
* @param[in] pData Pointer to a previous allocation or NULL when no previous allocation was available.
|
||||
* @param[in] nSize The size of the memory to allocate (in bytes).
|
||||
* @return Pointer to the memory allocation or NULL when memory allocation failed.
|
||||
*/
|
||||
virtual void* Realloc(void* pData, size_t nSize) override;
|
||||
|
||||
/**
|
||||
* @brief Free a memory allocation. Overload of internal::IInternalMemAlloc::Free.
|
||||
* @param[in] pData Pointer to a previous allocation.
|
||||
*/
|
||||
virtual void Free(void* pData) override;
|
||||
|
||||
#ifdef MEMORY_TRACKER
|
||||
std::mutex m_mtxTracker; ///< Synchronize map access
|
||||
std::map<void*, size_t> m_mapTracker; ///< Memory manager allocation tracker
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // !define MEMORY_H
|
||||
374
sdv_services/core/module.cpp
Normal file
374
sdv_services/core/module.cpp
Normal file
@@ -0,0 +1,374 @@
|
||||
#include "module.h"
|
||||
#include "sdv_core.h"
|
||||
#include <support/local_service_access.h>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef _WIN32
|
||||
// Resolve conflict
|
||||
#pragma push_macro("interface")
|
||||
#pragma push_macro("GetObject")
|
||||
#undef interface
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
#include <objbase.h>
|
||||
|
||||
// Resolve conflict
|
||||
#pragma pop_macro("interface")
|
||||
#pragma pop_macro("GetObject")
|
||||
#ifdef GetClassInfo
|
||||
#undef GetClassInfo
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wcast-function-type"
|
||||
#endif
|
||||
#endif // defined _WIN32
|
||||
|
||||
#ifdef __unix__
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
CModuleInst::CModuleInst(const std::filesystem::path& rpathModuleConfigPath, const std::filesystem::path& rpathModule) noexcept :
|
||||
m_pathModuleConfigPath(rpathModuleConfigPath)
|
||||
{
|
||||
if (!rpathModule.empty())
|
||||
Load(rpathModule);
|
||||
}
|
||||
|
||||
CModuleInst::~CModuleInst()
|
||||
{
|
||||
Unload(true);
|
||||
}
|
||||
|
||||
bool CModuleInst::IsValid() const noexcept
|
||||
{
|
||||
return m_tModuleID != 0 && m_pFactory;
|
||||
}
|
||||
|
||||
std::string CModuleInst::GetDefaultObjectName(const std::string& ssClassName) const
|
||||
{
|
||||
auto itClass = m_mapClassInfo.find(ssClassName);
|
||||
if (itClass == m_mapClassInfo.end()) return std::string();
|
||||
return itClass->second.ssDefaultObjectName.empty() ? itClass->second.ssClassName : itClass->second.ssDefaultObjectName;
|
||||
}
|
||||
|
||||
bool CModuleInst::IsSingleton(const std::string& ssClassName) const
|
||||
{
|
||||
auto itClass = m_mapClassInfo.find(ssClassName);
|
||||
if (itClass == m_mapClassInfo.end()) return false;
|
||||
return (itClass->second.uiFlags & static_cast<uint32_t>(sdv::EObjectFlags::singleton)) ? true : false;
|
||||
}
|
||||
|
||||
std::vector<std::string> CModuleInst::GetAvailableClasses() const
|
||||
{
|
||||
std::vector<std::string> vecClasses;
|
||||
for (const auto& rvtClass : m_mapClassInfo)
|
||||
vecClasses.push_back(rvtClass.first);
|
||||
return vecClasses;
|
||||
}
|
||||
|
||||
sdv::IInterfaceAccess* CModuleInst::CreateObject(const std::string& ssClassName)
|
||||
{
|
||||
if(!m_pFactory)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return m_pFactory->CreateObject(ssClassName.c_str());
|
||||
}
|
||||
|
||||
void CModuleInst::DestroyObject(sdv::IInterfaceAccess* object)
|
||||
{
|
||||
if(m_pFactory)
|
||||
{
|
||||
m_pFactory->DestroyObject(object);
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path CModuleInst::GetModuleConfigPath() const
|
||||
{
|
||||
return m_pathModuleConfigPath;
|
||||
}
|
||||
|
||||
std::filesystem::path CModuleInst::GetModulePath() const
|
||||
{
|
||||
return m_pathModule;
|
||||
}
|
||||
|
||||
sdv::core::TModuleID CModuleInst::GetModuleID() const
|
||||
{
|
||||
return m_tModuleID;
|
||||
}
|
||||
|
||||
sdv::core::SModuleInfo CModuleInst::GetModuleInfo() const
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModule);
|
||||
sdv::core::SModuleInfo sInfo{};
|
||||
sInfo.tModuleID = m_tModuleID;
|
||||
sInfo.ssPath = m_pathModule.filename().generic_u8string();
|
||||
sInfo.ssFilename = m_pathModule.filename().generic_u8string();
|
||||
sInfo.uiVersion = m_uiIfcVersion;
|
||||
sInfo.bActive = m_fnActiveObjects ? m_fnActiveObjects() : false;
|
||||
return sInfo;
|
||||
}
|
||||
|
||||
std::optional<sdv::SClassInfo> CModuleInst::GetClassInfo(const std::string& rssClassName) const
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModule);
|
||||
const auto itClass = m_mapClassInfo.find(rssClassName);
|
||||
if (itClass == m_mapClassInfo.end()) return {};
|
||||
std::optional<sdv::SClassInfo> optClassInfo = itClass->second;
|
||||
|
||||
// Add the module path to the result
|
||||
optClassInfo->ssModulePath = m_pathModule.generic_u8string();
|
||||
|
||||
return itClass->second;
|
||||
}
|
||||
|
||||
bool CModuleInst::Load(const std::filesystem::path& rpathModule) noexcept
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModule);
|
||||
|
||||
// Cannot load again.
|
||||
if (m_tModuleID) return false;
|
||||
m_pathModule = rpathModule;
|
||||
|
||||
// Core services bypass.
|
||||
if (rpathModule == "core_services.sdv")
|
||||
{
|
||||
m_tModuleID = sdv::core::tCoreLibModule;
|
||||
m_fnActiveObjects = &::HasActiveObjects;
|
||||
m_fnGetFactory = &::GetModuleFactory;
|
||||
m_fnGetManifest = &::GetManifest;
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef _WIN32
|
||||
m_tModuleID = reinterpret_cast<uint64_t>(LoadLibrary(rpathModule.native().c_str()));
|
||||
#elif defined __unix__
|
||||
m_tModuleID = reinterpret_cast<uint64_t>(dlopen(rpathModule.native().c_str(), RTLD_LAZY));
|
||||
#else
|
||||
#error OS not supported!
|
||||
#endif
|
||||
if (!m_tModuleID)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
std::string ssError(1024, '\0');
|
||||
FormatMessageA(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL,
|
||||
GetLastError(),
|
||||
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
|
||||
&ssError.front(),
|
||||
static_cast<DWORD>(ssError.size()), NULL);
|
||||
ssError[1023] = '\0';
|
||||
ssError.resize(strlen(ssError.c_str()));
|
||||
#elif defined __unix__
|
||||
std::string ssError = dlerror();
|
||||
#else
|
||||
#error OS not supported!
|
||||
#endif
|
||||
SDV_LOG_ERROR("Error opening SDV module: \"", rpathModule.generic_u8string(), "\" error: ", ssError);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check whether the module exposes the necessary functions
|
||||
#ifdef _WIN32
|
||||
m_fnGetFactory = reinterpret_cast<TFNGetModuleFactory*>(GetProcAddress(reinterpret_cast<HMODULE>(m_tModuleID), "GetModuleFactory"));
|
||||
m_fnActiveObjects = reinterpret_cast<TFNHasActiveObjects*>(GetProcAddress(reinterpret_cast<HMODULE>(m_tModuleID), "HasActiveObjects"));
|
||||
m_fnGetManifest = reinterpret_cast<TFNGetManifest*>(GetProcAddress(reinterpret_cast<HMODULE>(m_tModuleID), "GetManifest"));
|
||||
#elif defined __unix__
|
||||
m_fnGetFactory = reinterpret_cast<TFNGetModuleFactory*>(dlsym(reinterpret_cast<void*>(m_tModuleID), "GetModuleFactory"));
|
||||
m_fnActiveObjects = reinterpret_cast<TFNHasActiveObjects*>(dlsym(reinterpret_cast<void*>(m_tModuleID), "HasActiveObjects"));
|
||||
m_fnGetManifest = reinterpret_cast<TFNGetManifest*>(dlsym(reinterpret_cast<void*>(m_tModuleID), "GetManifest"));
|
||||
#else
|
||||
#error OS not supported!
|
||||
#endif
|
||||
if (!m_fnGetFactory || !m_fnActiveObjects || !m_fnGetManifest || !m_fnGetManifest())
|
||||
{
|
||||
SDV_LOG_ERROR("Error opening SDV module: ", rpathModule.generic_u8string(),
|
||||
" error: The module doesn't expose access functions!");
|
||||
Unload(true);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the manifest
|
||||
std::string ssManifest = m_fnGetManifest();
|
||||
|
||||
try
|
||||
{
|
||||
CParserTOML parser(ssManifest);
|
||||
|
||||
// Check for the interface version - currently must be equal.
|
||||
auto ptrVersion = parser.GetRoot().GetDirect("Interface.Version");
|
||||
if (ptrVersion) m_uiIfcVersion = ptrVersion->GetValue();
|
||||
if (!ptrVersion || m_uiIfcVersion != SDVFrameworkInterfaceVersion)
|
||||
{
|
||||
// Incompatible interface.
|
||||
SDV_LOG_ERROR("Error opening SDV module: ", rpathModule.generic_u8string(), " error: incompatible version ", m_uiIfcVersion,
|
||||
" (required ", SDVFrameworkInterfaceVersion, ")");
|
||||
Unload(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get a pointer to the factory for the current interface.
|
||||
m_pFactory = sdv::TInterfaceAccessPtr(m_fnGetFactory(SDVFrameworkInterfaceVersion)).GetInterface<sdv::IObjectFactory>();
|
||||
if (!m_pFactory)
|
||||
{
|
||||
SDV_LOG_ERROR("Error opening SDV module: ", rpathModule.generic_u8string(), " error: no object factory available");
|
||||
Unload(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get available classes
|
||||
auto ptrComponents = parser.GetRoot().GetDirect("Component");
|
||||
if (!ptrComponents || !ptrComponents->GetArray())
|
||||
{
|
||||
SDV_LOG_ERROR("Error opening SDV module: ", rpathModule.generic_u8string(), " error: no components available");
|
||||
Unload(true);
|
||||
return false;
|
||||
}
|
||||
for (std::uint32_t uiIndex = 0; uiIndex < ptrComponents->GetArray()->GetCount(); uiIndex++)
|
||||
{
|
||||
// Fill in the class info.
|
||||
sdv::SClassInfo sInfo{};
|
||||
auto ptrComponent = ptrComponents->GetArray()->Get(uiIndex);
|
||||
if (!ptrComponent) continue;
|
||||
auto ptrClassName = ptrComponent->GetDirect("Class");
|
||||
if (!ptrClassName) continue;
|
||||
sInfo.ssClassName = static_cast<std::string>(ptrClassName->GetValue());
|
||||
auto ptrAliases = ptrComponent->GetDirect("Aliases");
|
||||
if (ptrAliases)
|
||||
{
|
||||
auto ptrAliasesArray = ptrAliases->GetArray();
|
||||
for (uint32_t uiAliasIndex = 0; ptrAliasesArray && uiAliasIndex < ptrAliasesArray->GetCount(); uiAliasIndex++)
|
||||
{
|
||||
auto ptrClassAlias = ptrAliasesArray->Get(uiAliasIndex);
|
||||
if (ptrClassAlias)
|
||||
sInfo.seqClassAliases.push_back(static_cast<sdv::u8string>(ptrClassAlias->GetValue()));
|
||||
}
|
||||
}
|
||||
auto ptrDefaultName = ptrComponent->GetDirect("DefaultName");
|
||||
if (ptrDefaultName) sInfo.ssDefaultObjectName = static_cast<std::string>(ptrDefaultName->GetValue());
|
||||
else sInfo.ssDefaultObjectName = sInfo.ssClassName;
|
||||
auto ptrType = ptrComponent->GetDirect("Type");
|
||||
if (!ptrType) continue;
|
||||
std::string ssType = static_cast<std::string>(ptrType->GetValue());
|
||||
if (ssType == "System") sInfo.eType = sdv::EObjectType::SystemObject;
|
||||
else if (ssType == "Device") sInfo.eType = sdv::EObjectType::Device;
|
||||
else if (ssType == "BasicService") sInfo.eType = sdv::EObjectType::BasicService;
|
||||
else if (ssType == "ComplexService") sInfo.eType = sdv::EObjectType::ComplexService;
|
||||
else if (ssType == "App") sInfo.eType = sdv::EObjectType::Application;
|
||||
else if (ssType == "Proxy") sInfo.eType = sdv::EObjectType::Proxy;
|
||||
else if (ssType == "Stub") sInfo.eType = sdv::EObjectType::Stub;
|
||||
else if (ssType == "Utility") sInfo.eType = sdv::EObjectType::Utility;
|
||||
else continue;
|
||||
auto ptrSingleton = ptrComponent->GetDirect("Singleton");
|
||||
if (ptrSingleton && static_cast<bool>(ptrSingleton->GetValue()))
|
||||
sInfo.uiFlags = static_cast<uint32_t>(sdv::EObjectFlags::singleton);
|
||||
auto ptrDependencies = ptrComponent->GetDirect("Dependencies");
|
||||
if (ptrDependencies)
|
||||
{
|
||||
auto ptrDependencyArray = ptrDependencies->GetArray();
|
||||
for (uint32_t uiDependencyIndex = 0; ptrDependencyArray && uiDependencyIndex < ptrDependencyArray->GetCount();
|
||||
uiDependencyIndex++)
|
||||
{
|
||||
auto ptrDependsOn = ptrDependencyArray->Get(uiDependencyIndex);
|
||||
if (ptrDependsOn)
|
||||
sInfo.seqDependencies.push_back(static_cast<sdv::u8string>(ptrDependsOn->GetValue()));
|
||||
}
|
||||
}
|
||||
|
||||
m_mapClassInfo[sInfo.ssClassName] = sInfo;
|
||||
}
|
||||
}
|
||||
catch (const sdv::toml::XTOMLParseException&)
|
||||
{
|
||||
SDV_LOG_ERROR("Manifest interpretation error for: \"", m_pathModule.filename().generic_u8string(), "\"");
|
||||
return false;
|
||||
}
|
||||
|
||||
SDV_LOG_INFO("Successfully loaded SDV module: \"", m_pathModule.filename().generic_u8string(), "\"");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CModuleInst::Unload(bool bForce) noexcept
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModule);
|
||||
|
||||
// Request the repository to unload any running objects
|
||||
try
|
||||
{
|
||||
GetRepository().DestroyModuleObjects(m_tModuleID);
|
||||
}
|
||||
catch (const sdv::XSysExcept&)
|
||||
{
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
}
|
||||
|
||||
// Check for a active objects first
|
||||
bool bHasActive = m_fnActiveObjects && m_fnActiveObjects();
|
||||
if (!bForce && bHasActive)
|
||||
{
|
||||
SDV_LOG_WARNING("Trying to unload module \"", m_pathModule.filename().generic_u8string(),
|
||||
"\" but there are still active objects. Unloading the module will be blocked.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear the members
|
||||
m_fnActiveObjects = nullptr;
|
||||
m_fnGetFactory = nullptr;
|
||||
m_pFactory = nullptr;
|
||||
m_mapClassInfo.clear();
|
||||
m_uiIfcVersion = 0u;
|
||||
|
||||
// Unload the module
|
||||
if (!bHasActive)
|
||||
{
|
||||
if (m_tModuleID && m_tModuleID != sdv::core::tCoreLibModule)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
FreeLibrary(reinterpret_cast<HMODULE>(m_tModuleID));
|
||||
#elif defined __unix__
|
||||
dlclose(reinterpret_cast<void*>(m_tModuleID));
|
||||
#else
|
||||
#error OS not supported!
|
||||
#endif
|
||||
SDV_LOG_INFO("Successfully unloaded SDV module: \"", m_pathModule.filename().generic_u8string(), "\"");
|
||||
}
|
||||
} else
|
||||
{
|
||||
SDV_LOG_ERROR("Trying to force to unload module \"", m_pathModule.filename().generic_u8string(),
|
||||
"\" but there are still active objects. The module will not be unloaded! "
|
||||
"However, the management instance will be destroyed!");
|
||||
}
|
||||
|
||||
// Clear other members
|
||||
m_tModuleID = 0;
|
||||
m_pathModule.clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CModuleInst::HasActiveObjects() const
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModule);
|
||||
|
||||
return m_fnActiveObjects && m_fnActiveObjects();
|
||||
}
|
||||
|
||||
#if defined _WIN32 && defined __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
170
sdv_services/core/module.h
Normal file
170
sdv_services/core/module.h
Normal file
@@ -0,0 +1,170 @@
|
||||
#ifndef MODULE_H
|
||||
#define MODULE_H
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <interfaces/core.h>
|
||||
#include <interfaces/module.h>
|
||||
|
||||
/**
|
||||
* @brief Module instance class
|
||||
*/
|
||||
class CModuleInst
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rpathModuleConfigPath Reference to the path of the module as it was specified in the configuration.
|
||||
* @param[in] rpathModule Reference to the absolute path of the module to load. If empty, the module is not instanceable, but
|
||||
* is used as a placeholder for rewriting the configuration.
|
||||
*/
|
||||
CModuleInst(const std::filesystem::path& rpathModuleConfigPath, const std::filesystem::path& rpathModule) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor is not allowed.
|
||||
*/
|
||||
CModuleInst(const CModuleInst&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor is not allowed.
|
||||
*/
|
||||
CModuleInst(CModuleInst&&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CModuleInst();
|
||||
|
||||
/**
|
||||
* @brief Assignment operator is not allowed.
|
||||
* @return Returns a reference to this class.
|
||||
*/
|
||||
CModuleInst& operator=(const CModuleInst&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move operator is not allowed.
|
||||
* @return Returns a reference to this class.
|
||||
*/
|
||||
CModuleInst& operator=(CModuleInst&&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Return whether the module instance was loaded correctly.
|
||||
* @return Returns 'true' when the module was lloaded successfully. Otherwise returns 'false'.
|
||||
*/
|
||||
bool IsValid() const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Get the default object name.
|
||||
* @param[in] ssClassName Name of the class
|
||||
* @return Returns the default object name or an empty string if the class doesn't exist.
|
||||
*/
|
||||
std::string GetDefaultObjectName(const std::string& ssClassName) const;
|
||||
|
||||
/**
|
||||
* @brief Is the object class marked as singleton (only one instance is allowed)?
|
||||
* @param[in] ssClassName Name of the class
|
||||
* @return Returns whether the object class is a singleton.
|
||||
*/
|
||||
bool IsSingleton(const std::string& ssClassName) const;
|
||||
|
||||
/**
|
||||
* @brief Gets the set of all available classes in the module
|
||||
* @returns Returns the vector of all available classes in the module
|
||||
*/
|
||||
std::vector<std::string> GetAvailableClasses() const;
|
||||
|
||||
/**
|
||||
* @brief Creates an object instance of a given SDV class via the loaded module
|
||||
* @param[in] ssClassName the class from which an object is to be created
|
||||
* @return Returns a pointer to the created object or nullptr in case of failure
|
||||
*/
|
||||
sdv::IInterfaceAccess* CreateObject(const std::string& ssClassName);
|
||||
|
||||
/**
|
||||
* @brief Destroys an object previously created via CreateObject
|
||||
* @param[in] object The object to be destroyed.
|
||||
* The object is only destroyed if it has been allocated via the loaded module and has not yet been destroyed.
|
||||
*/
|
||||
void DestroyObject(sdv::IInterfaceAccess* object);
|
||||
|
||||
/**
|
||||
* @brief Return the module config path (as it was specified in the configuration).
|
||||
* @return The config path of the module.
|
||||
*/
|
||||
std::filesystem::path GetModuleConfigPath() const;
|
||||
|
||||
/**
|
||||
* @brief Return the module path.
|
||||
* @return The path of the module.
|
||||
*/
|
||||
std::filesystem::path GetModulePath() const;
|
||||
|
||||
/**
|
||||
* @brief Return the module ID.
|
||||
* @return The path of the module.
|
||||
*/
|
||||
sdv::core::TModuleID GetModuleID() const;
|
||||
|
||||
/**
|
||||
* @brief Get the module information.
|
||||
* @return Returns the module information structure.
|
||||
*/
|
||||
sdv::core::SModuleInfo GetModuleInfo() const;
|
||||
|
||||
/**
|
||||
* @brief Return the class information of the supplied class name.
|
||||
* @param[in] rssClassName Reference to string containing the class name.
|
||||
* @return Returns the information structure of the requested class or an empty structure when the class could not be found.
|
||||
*/
|
||||
std::optional<sdv::SClassInfo> GetClassInfo(const std::string& rssClassName) const;
|
||||
|
||||
/**
|
||||
* @brief Load the module.
|
||||
* @param[in] rpathModule Reference to the path of the module to load.
|
||||
* @return Returns 'true' when successful; 'false' when not.
|
||||
*/
|
||||
bool Load(const std::filesystem::path& rpathModule) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Unload the module and clear the module instance class.
|
||||
* @attention If there are any active objects still running, this module is not unloaded. In cooperation with the bForce
|
||||
* flag this causes the module to stay in memory indefinitely.
|
||||
* @param[in] bForce When set, the module instance class is cleared whether or not the module is unloaded or not. Use with
|
||||
* extreme care (e.g. during shutdown).
|
||||
* @return Returns 'true' when successfully cleared the module instance class, 'false' when there are still active objects
|
||||
* blocking the unloading.
|
||||
*/
|
||||
bool Unload(bool bForce) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Return whether active objects created through this module are still running.
|
||||
* @return Returns 'true' when active objects are running; 'false' when not.
|
||||
*/
|
||||
bool HasActiveObjects() const;
|
||||
|
||||
private:
|
||||
/// HasActiveObjects exported function type definition.
|
||||
using TFNHasActiveObjects = bool();
|
||||
|
||||
/// GetModuleFactory exported function type definition.
|
||||
using TFNGetModuleFactory = sdv::IInterfaceAccess*(uint32_t);
|
||||
|
||||
/// GetManifest exported function type definition.
|
||||
using TFNGetManifest = const char*();
|
||||
|
||||
mutable std::recursive_mutex m_mtxModule; ///< Access regulation for the class members.
|
||||
std::filesystem::path m_pathModuleConfigPath; ///< Module path as it was specified in the configuration.
|
||||
std::filesystem::path m_pathModule; ///< Module path or filename.
|
||||
sdv::core::TModuleID m_tModuleID = 0; ///< Module handle.
|
||||
std::function<TFNHasActiveObjects> m_fnActiveObjects; ///< Module exported function for requesting active objects.
|
||||
std::function<TFNGetModuleFactory> m_fnGetFactory; ///< Module exported function for getting factory.
|
||||
std::function<TFNGetManifest> m_fnGetManifest; ///< Module exported function for getting manifest.
|
||||
sdv::IObjectFactory* m_pFactory = nullptr; ///< Factory interface of the module.
|
||||
std::map<std::string, sdv::SClassInfo> m_mapClassInfo; ///< Available classes
|
||||
uint32_t m_uiIfcVersion = 0; ///< Interface version.
|
||||
};
|
||||
|
||||
#endif // !defined MODULE_H
|
||||
411
sdv_services/core/module_control.cpp
Normal file
411
sdv_services/core/module_control.cpp
Normal file
@@ -0,0 +1,411 @@
|
||||
#include "module_control.h"
|
||||
#include "sdv_core.h"
|
||||
#include <algorithm>
|
||||
#include "../../global/exec_dir_helper.h"
|
||||
#include "toml_parser/parser_toml.h"
|
||||
#include "toml_parser//parser_node_toml.h"
|
||||
|
||||
/// @cond DOXYGEN_IGNORE
|
||||
#ifdef _WIN32
|
||||
#include <Shlobj.h>
|
||||
#elif defined __unix__
|
||||
#include <ctime>
|
||||
#include <utime.h>
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
/// @endcond
|
||||
|
||||
CModuleControl::CModuleControl()
|
||||
{}
|
||||
|
||||
CModuleControl::~CModuleControl()
|
||||
{
|
||||
try
|
||||
{
|
||||
UnloadAll(std::vector<sdv::core::TModuleID>());
|
||||
}
|
||||
catch (const sdv::XSysExcept&)
|
||||
{
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool CModuleControl::AddModuleSearchDir(const sdv::u8string& ssDir)
|
||||
{
|
||||
// Add initial paths if not done so already
|
||||
AddCurrentPath();
|
||||
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
|
||||
std::filesystem::path pathDir(ssDir.c_str());
|
||||
|
||||
// Relative paths are always relative to the executable
|
||||
if (pathDir.is_relative())
|
||||
pathDir = GetExecDirectory() / ssDir.c_str();
|
||||
|
||||
// Remove any indirections
|
||||
pathDir = pathDir.lexically_normal();
|
||||
|
||||
// If the current path is not a directory, it cannot be added
|
||||
if (!std::filesystem::is_directory(pathDir)) return false;
|
||||
|
||||
// Check whether the path is already in the list
|
||||
if (std::find(m_lstSearchPaths.begin(), m_lstSearchPaths.end(), pathDir) != m_lstSearchPaths.end())
|
||||
return true; // This is not an error
|
||||
|
||||
// Add the path
|
||||
m_lstSearchPaths.push_back(pathDir);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::u8string> CModuleControl::GetModuleSearchDirs() const
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
sdv::sequence<sdv::u8string> seqSearchDirs;
|
||||
for (const std::filesystem::path& rpathSearchDir : m_lstSearchPaths)
|
||||
seqSearchDirs.push_back(rpathSearchDir.generic_u8string());
|
||||
return seqSearchDirs;
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::core::SModuleInfo> CModuleControl::GetModuleList() const
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
sdv::sequence<sdv::core::SModuleInfo> seqModuleInfos;
|
||||
|
||||
// Build the module information sequence
|
||||
for (const std::shared_ptr<CModuleInst>& ptrModule : m_lstModules)
|
||||
{
|
||||
if (!ptrModule) continue;
|
||||
seqModuleInfos.push_back(ptrModule->GetModuleInfo());
|
||||
}
|
||||
return seqModuleInfos;
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::SClassInfo> CModuleControl::GetClassList(/*in*/ sdv::core::TModuleID tModuleID) const
|
||||
{
|
||||
// Find the module instance
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
sdv::sequence<sdv::SClassInfo> seqClassInfos;
|
||||
auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr<CModuleInst>& ptrModule)
|
||||
{
|
||||
return ptrModule && ptrModule->GetModuleID() == tModuleID;
|
||||
});
|
||||
if (itModule == m_lstModules.end()) return seqClassInfos; // Cannot find the module.
|
||||
|
||||
// Request the class infos from the module instance.
|
||||
std::vector<std::string> vecClasses = (*itModule)->GetAvailableClasses();
|
||||
for (const std::string& rssClass : vecClasses)
|
||||
{
|
||||
auto optClassInfo = (*itModule)->GetClassInfo(rssClass);
|
||||
if (!optClassInfo) continue;
|
||||
seqClassInfos.push_back(*optClassInfo);
|
||||
}
|
||||
return seqClassInfos;
|
||||
}
|
||||
|
||||
sdv::core::TModuleID CModuleControl::Load(const sdv::u8string& ssModulePath)
|
||||
{
|
||||
if(ssModulePath.empty())
|
||||
return 0;
|
||||
|
||||
// Add initial paths if not done so already
|
||||
AddCurrentPath();
|
||||
|
||||
// Core services bypass.
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
std::filesystem::path pathModule;
|
||||
if (ssModulePath == "core_services.sdv")
|
||||
pathModule = static_cast<std::string>(ssModulePath);
|
||||
else
|
||||
{
|
||||
if (GetAppControl().IsMainApplication() || GetAppControl().IsIsolatedApplication())
|
||||
{
|
||||
// Check the installation for the module.
|
||||
pathModule = GetAppConfig().FindInstalledModule(static_cast<std::string>(ssModulePath));
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get the search paths if the module path is relative
|
||||
std::list<std::filesystem::path> lstSearchPaths;
|
||||
std::filesystem::path pathSupplied(static_cast<std::string>(ssModulePath));
|
||||
if (pathSupplied.is_relative())
|
||||
lstSearchPaths = m_lstSearchPaths;
|
||||
|
||||
// Add an empty path to allow the OS to search when our own search paths could not find the module.
|
||||
lstSearchPaths.push_back(std::filesystem::path());
|
||||
|
||||
// Run through the search paths and try to find the module.
|
||||
for (const std::filesystem::path& rpathDirectory : lstSearchPaths)
|
||||
{
|
||||
// Compose the path
|
||||
std::filesystem::path pathModuleTemp;
|
||||
if (rpathDirectory.is_absolute())
|
||||
pathModuleTemp = (rpathDirectory / pathSupplied).lexically_normal();
|
||||
else
|
||||
{
|
||||
if (rpathDirectory.empty())
|
||||
pathModuleTemp = pathSupplied.lexically_normal();
|
||||
else
|
||||
pathModuleTemp = (GetExecDirectory() / rpathDirectory / pathSupplied).lexically_normal();
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(pathModuleTemp))
|
||||
{
|
||||
pathModule = pathModuleTemp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception& re)
|
||||
{
|
||||
SDV_LOG_ERROR("Supplied path to module load is not valid \"", ssModulePath, "\": ", re.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for an exisiting module. If existing, return the existing module.
|
||||
for (const std::shared_ptr<CModuleInst>& rptrModule : m_lstModules)
|
||||
{
|
||||
if (rptrModule && rptrModule->GetModulePath() == pathModule)
|
||||
return rptrModule->GetModuleID();
|
||||
}
|
||||
|
||||
// Create a new instance (even if the module could not be found).
|
||||
std::shared_ptr<CModuleInst> ptrModule = std::make_shared<CModuleInst>(static_cast<std::string>(ssModulePath), pathModule);
|
||||
m_lstModules.push_back(ptrModule);
|
||||
m_setConfigModules.insert(ptrModule->GetModuleID());
|
||||
if (!ptrModule->IsValid())
|
||||
{
|
||||
SDV_LOG_ERROR("The module was not found \"", ssModulePath, "\"");
|
||||
return 0; // Do not return the module ID of not loadable modules.
|
||||
}
|
||||
return ptrModule->GetModuleID();
|
||||
}
|
||||
|
||||
bool CModuleControl::Unload(sdv::core::TModuleID tModuleID)
|
||||
{
|
||||
// Cannot unload the core services
|
||||
if (tModuleID == sdv::core::tCoreLibModule)
|
||||
return true;
|
||||
|
||||
return ContextUnload(tModuleID, false);
|
||||
}
|
||||
|
||||
bool CModuleControl::HasActiveObjects(sdv::core::TModuleID tModuleID) const
|
||||
{
|
||||
// Find the module instance
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr<CModuleInst>& ptrModule)
|
||||
{
|
||||
return ptrModule && ptrModule->GetModuleID() == tModuleID;
|
||||
});
|
||||
if (itModule == m_lstModules.end()) return false; // Cannot find the module; no active objects known.
|
||||
|
||||
// Redirect the call to the module instance.
|
||||
return (*itModule)->HasActiveObjects();
|
||||
}
|
||||
|
||||
std::shared_ptr<CModuleInst> CModuleControl::FindModuleByClass(const std::string& rssClass)
|
||||
{
|
||||
// Find the module instance
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
for (const std::shared_ptr<CModuleInst>& ptrModule : m_lstModules)
|
||||
{
|
||||
if (!ptrModule) continue;
|
||||
std::vector<std::string> vecClasses = ptrModule->GetAvailableClasses();
|
||||
for (const std::string& rssClassLocal : vecClasses)
|
||||
{
|
||||
if (rssClassLocal == rssClass) return ptrModule;
|
||||
auto optClassInfo = ptrModule->GetClassInfo(rssClass);
|
||||
if (!optClassInfo) continue;
|
||||
if (std::find(optClassInfo->seqClassAliases.begin(), optClassInfo->seqClassAliases.end(), rssClass) !=
|
||||
optClassInfo->seqClassAliases.end())
|
||||
return ptrModule;
|
||||
}
|
||||
}
|
||||
|
||||
// For main and isolated applications, check whether the module is in one of the installation manifests.
|
||||
auto optManifest = GetAppConfig().FindInstalledComponent(rssClass);
|
||||
if (!optManifest) return nullptr;
|
||||
auto ssManifest = GetAppConfig().FindInstalledModuleManifest(optManifest->pathRelModule);
|
||||
if (ssManifest.empty()) return nullptr;
|
||||
lock.unlock();
|
||||
|
||||
// Load the module
|
||||
return GetModule(ContextLoad(optManifest->pathRelModule, ssManifest));
|
||||
}
|
||||
|
||||
std::shared_ptr<CModuleInst> CModuleControl::GetModule(sdv::core::TModuleID tModuleID) const
|
||||
{
|
||||
// Find the module instance
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr<CModuleInst>& ptrModule)
|
||||
{
|
||||
return ptrModule && ptrModule->GetModuleID() == tModuleID;
|
||||
});
|
||||
if (itModule == m_lstModules.end()) return nullptr; // Cannot find the module.
|
||||
return *itModule;
|
||||
}
|
||||
|
||||
void CModuleControl::UnloadAll(const std::vector<sdv::core::TModuleID>& rvecIgnoreModules)
|
||||
{
|
||||
// Force unloading all modules in reverse order of their loading
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
std::list<std::shared_ptr<CModuleInst>> lstCopy = m_lstModules;
|
||||
lock.unlock();
|
||||
while (lstCopy.size())
|
||||
{
|
||||
std::shared_ptr<CModuleInst> ptrModule = std::move(lstCopy.back());
|
||||
lstCopy.pop_back();
|
||||
if (!ptrModule) continue;
|
||||
|
||||
// On the ignore list?
|
||||
if (std::find(rvecIgnoreModules.begin(), rvecIgnoreModules.end(), ptrModule->GetModuleID()) != rvecIgnoreModules.end())
|
||||
continue;
|
||||
|
||||
// Unload... -> call the module control function to remove it from the list.
|
||||
ContextUnload(ptrModule->GetModuleID(), true);
|
||||
}
|
||||
}
|
||||
|
||||
void CModuleControl::ResetConfigBaseline()
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
m_setConfigModules.clear();
|
||||
}
|
||||
|
||||
std::string CModuleControl::SaveConfig(const std::set<std::filesystem::path>& rsetIgnoreModule)
|
||||
{
|
||||
std::stringstream sstream;
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
|
||||
// Add all the loaded modules
|
||||
for (const std::shared_ptr<CModuleInst>& rptrModule : m_lstModules)
|
||||
{
|
||||
if (m_setConfigModules.find(rptrModule->GetModuleID()) != m_setConfigModules.end() &&
|
||||
rsetIgnoreModule.find(rptrModule->GetModuleConfigPath()) != rsetIgnoreModule.end())
|
||||
{
|
||||
sstream << std::endl;
|
||||
sstream << "[[Module]]" << std::endl;
|
||||
sstream << "Path = \"" << rptrModule->GetModuleConfigPath().generic_u8string() << "\"" << std::endl;
|
||||
}
|
||||
}
|
||||
return sstream.str();
|
||||
}
|
||||
|
||||
sdv::core::TModuleID CModuleControl::ContextLoad(const std::filesystem::path& rpathModule, const std::string& rssManifest)
|
||||
{
|
||||
if (GetAppControl().IsMaintenanceApplication()) return 0; // Not allowed
|
||||
|
||||
// Run through the manifest and check for complex services, applications and utilities.
|
||||
// TODO EVE: Temporary suppression of cppcheck warning.
|
||||
// cppcheck-suppress variableScope
|
||||
[[maybe_unused]] bool bIsolationNeeded = false;
|
||||
try
|
||||
{
|
||||
size_t nIndex = 0;
|
||||
size_t nUnsupportedObjectCount = 0;
|
||||
CParserTOML parser(rssManifest);
|
||||
do
|
||||
{
|
||||
std::shared_ptr<CNode> ptrComponentType =
|
||||
parser.GetRoot().GetDirect(std::string("Component[") + std::to_string(nIndex++) + "].Type");
|
||||
if (!ptrComponentType) break;
|
||||
std::string ssType = static_cast<std::string>(ptrComponentType->GetValue());
|
||||
if (ssType == "Device") continue; // Okay
|
||||
if (ssType == "BasicService") continue; // Okay
|
||||
if (ssType == "Proxy") continue; // Okay
|
||||
if (ssType == "Stub") continue; // Okay
|
||||
if (ssType == "ComplexService" || ssType == "Application" || ssType == "Utility")
|
||||
{
|
||||
bIsolationNeeded = true;
|
||||
continue; // Okay
|
||||
}
|
||||
|
||||
// Not okay
|
||||
nUnsupportedObjectCount++;
|
||||
} while (true);
|
||||
if (nIndex == nUnsupportedObjectCount) return 0; // No object to load.
|
||||
}
|
||||
catch (const sdv::toml::XTOMLParseException& /*rexcept*/)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: If the module to load needs isolation...
|
||||
// If this is a main application: isolate modules that should be isolated
|
||||
// If this is an isolated application: check for another loaded module that needs isolation already. If this do not allow
|
||||
// this module to load.
|
||||
|
||||
// Load the module
|
||||
return Load(rpathModule.generic_u8string());
|
||||
}
|
||||
|
||||
bool CModuleControl::ContextUnload(sdv::core::TModuleID tModuleID, bool bForce)
|
||||
{
|
||||
// Find the module instance
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr<CModuleInst>& ptrModule)
|
||||
{
|
||||
return ptrModule && ptrModule->GetModuleID() == tModuleID;
|
||||
});
|
||||
if (itModule == m_lstModules.end()) return true; // Cannot find the module to unload. This is not an error!
|
||||
m_setConfigModules.erase(tModuleID); // Remove from config if part of it.
|
||||
|
||||
// Check whether it is possible to unload.
|
||||
bool bSuccess = (*itModule)->Unload(bForce);
|
||||
if (bSuccess)
|
||||
m_lstModules.erase(itModule);
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
std::shared_ptr<CModuleInst> CModuleControl::FindModuleByPath(const std::filesystem::path& rpathModule) const
|
||||
{
|
||||
// Find the module instance
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr<CModuleInst>& ptrModule)
|
||||
{
|
||||
return ptrModule && ptrModule->GetModulePath() == rpathModule;
|
||||
});
|
||||
if (itModule == m_lstModules.end()) return nullptr; // Cannot find the module
|
||||
return *itModule;
|
||||
}
|
||||
|
||||
void CModuleControl::AddCurrentPath()
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
|
||||
// Do this only once.
|
||||
if (!m_lstSearchPaths.empty()) return;
|
||||
|
||||
// Add the core directory
|
||||
std::filesystem::path pathCoreDir = GetCoreDirectory().lexically_normal();
|
||||
m_lstSearchPaths.push_back(pathCoreDir);
|
||||
|
||||
// Add the exe dir
|
||||
std::filesystem::path pathExeDir = GetExecDirectory().lexically_normal();
|
||||
if (pathExeDir != pathCoreDir) m_lstSearchPaths.push_back(pathExeDir);
|
||||
}
|
||||
|
||||
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
|
||||
CModuleControl& CModuleControlService::GetModuleControl()
|
||||
{
|
||||
return ::GetModuleControl();
|
||||
}
|
||||
|
||||
bool CModuleControlService::EnableModuleControlAccess()
|
||||
{
|
||||
return GetAppControl().IsStandaloneApplication() ||
|
||||
GetAppControl().IsEssentialApplication();
|
||||
}
|
||||
|
||||
#endif
|
||||
265
sdv_services/core/module_control.h
Normal file
265
sdv_services/core/module_control.h
Normal file
@@ -0,0 +1,265 @@
|
||||
/**
|
||||
* @file module_control.h
|
||||
* @author Sudipta Babu Durjoy FRD DISDS1 (mailto:sudipta.durjoy@zf.com) & Erik Verhoeven FRD DISDS1 (mailto:erik.verhoeven@zf.com)
|
||||
* @brief This file contains the implementation for loading VAPI/SDV modules and accessing the exported functions related to
|
||||
* VAPI/SDV modules on Windows and Posix
|
||||
* @version 2.0
|
||||
* @date 2024-04-03
|
||||
*
|
||||
* @copyright Copyright ZF Friedrichshafen AG (c) 2021-2025
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MODULE_CONTROL_H
|
||||
#define MODULE_CONTROL_H
|
||||
|
||||
#include <support/interface_ptr.h>
|
||||
#include <support/local_service_access.h>
|
||||
#include <support/component_impl.h>
|
||||
#include <mutex>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include "toml_parser/parser_toml.h"
|
||||
#include "module.h"
|
||||
|
||||
/**
|
||||
* @brief Module control class
|
||||
* @remarks This class is also used to load the object from the core library without reloading the library itself again. It uses
|
||||
* the module file name "core_services.sdv" and the ID -1 to identify the library.
|
||||
*/
|
||||
class CModuleControl : public sdv::IInterfaceAccess, public sdv::core::IModuleControl, public sdv::core::IModuleControlConfig,
|
||||
public sdv::core::IModuleInfo
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CModuleControl();
|
||||
|
||||
/**
|
||||
* @brief No copy constructor
|
||||
* @param[in] rctrl Reference to the module control class.
|
||||
*/
|
||||
CModuleControl(const CModuleControl& rctrl) = delete;
|
||||
|
||||
/**
|
||||
* @brief No move constructor
|
||||
* @param[in] rctrl Reference to the module control class.
|
||||
*/
|
||||
CModuleControl(CModuleControl&& rctrl) = delete;
|
||||
|
||||
/**
|
||||
* @brief Destructor unloads all modules
|
||||
*/
|
||||
virtual ~CModuleControl();
|
||||
|
||||
// Interface map
|
||||
// NOTE: Only the sdv::core::IModuleControlConfig interface should be accessible from here, since the module control is
|
||||
// accessible through sdv::GetCore() function, which should not make all other interfaces accessible (the system needs to start
|
||||
// and access should happen over sdv::core::GetObject()).
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::core::IModuleControlConfig)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief No assignment operator
|
||||
* @return Returns a reference to this class.
|
||||
*/
|
||||
CModuleControl& operator=(const CModuleControl&) = delete;
|
||||
|
||||
/**
|
||||
* @brief No move operator
|
||||
* @return Returns a reference to this class.
|
||||
*/
|
||||
CModuleControl& operator=(CModuleControl&&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Sets a search path to a folder where a module can be found. Overload of
|
||||
* sdv::core::IModuleControlConfig::AddModuleSearchDir.
|
||||
* @param[in] ssDir Relative or absolute path to an existing folder.
|
||||
* To load a module out of the folder only the filename is required.
|
||||
* @return returns true if folder exists, otherwise false
|
||||
*/
|
||||
virtual bool AddModuleSearchDir(const sdv::u8string& ssDir) override;
|
||||
|
||||
/**
|
||||
* @brief Get a sequence containing the current module search directories.
|
||||
* @return The sequence with the module search directories.
|
||||
*/
|
||||
virtual sdv::sequence<sdv::u8string> GetModuleSearchDirs() const override;
|
||||
|
||||
/**
|
||||
* @brief Get a list of loaded modules. Overload of sdv::core::IModuleInfo::GetModuleList.
|
||||
* @return The module list.
|
||||
*/
|
||||
virtual sdv::sequence<sdv::core::SModuleInfo> GetModuleList() const override;
|
||||
|
||||
/**
|
||||
* @brief Get a list of classes exposed by the provided module.
|
||||
* @param[in] tModuleID The module ID to request the list of classes for.
|
||||
* @return Sequence with the class information structures.
|
||||
*/
|
||||
virtual sdv::sequence<sdv::SClassInfo> GetClassList(/*in*/ sdv::core::TModuleID tModuleID) const override;
|
||||
|
||||
/**
|
||||
* @brief Load the module. Overload of sdv::core::IModuleControl::Load.
|
||||
* @param[in] ssModulePath File name of the module to load.
|
||||
* @return Returns the ID of the module or 0 if the module could not be loaded
|
||||
*/
|
||||
virtual sdv::core::TModuleID Load(const sdv::u8string& ssModulePath) override;
|
||||
|
||||
/**
|
||||
* @brief Unload the module. Overload of sdv::core::IModuleControl::Unload.
|
||||
* @param[in] tModuleID Id representing a module, which has previous been loaded by Load
|
||||
* @return Returns true if module is unloaded successfully otherwise false.
|
||||
*/
|
||||
virtual bool Unload(sdv::core::TModuleID tModuleID) override;
|
||||
|
||||
/**
|
||||
* @brief Checking for active objects. Overload of sdv::core::IModuleControl::HasActiveObjects.
|
||||
* @param[in] tModuleID Id representing a module, which has previous been loaded by Load
|
||||
* @return Returns true if there is an active object otherwise false.
|
||||
*/
|
||||
virtual bool HasActiveObjects(sdv::core::TModuleID tModuleID) const override;
|
||||
|
||||
/**
|
||||
* @brief Find a module containing a class with the specified name.
|
||||
* @details Find the first module containing a class with the specified name. For main and isolated applications, if the
|
||||
* module wasn't loaded, the installation manifests are checked and if a module with the class has been found, the module will
|
||||
* be loaded. The order of checking the installation manifest is core-manifest, manifest in executable directory and manifest
|
||||
* in supplied installation directory.
|
||||
* @remarks Modules of system objects specified in the user installation are not returned.
|
||||
* @param[in] rssClass Reference to the class that should be searched for. The class is compared to the class name and the
|
||||
* default name in the manifest.
|
||||
* @return Returns a smart pointer to the module instance or NULL when the module hasn't been found.
|
||||
*/
|
||||
std::shared_ptr<CModuleInst> FindModuleByClass(const std::string& rssClass);
|
||||
|
||||
/**
|
||||
* @brief Get a class instance for the module.
|
||||
* @param[in] tModuleID Id representing a module, which has previous been loaded by Load
|
||||
* @return Returns a smart pointer to the module instance or NULL when the module hasn't been found.
|
||||
*/
|
||||
std::shared_ptr<CModuleInst> GetModule(sdv::core::TModuleID tModuleID) const;
|
||||
|
||||
/**
|
||||
* @brief Unload all modules (with force-flag).
|
||||
* @param[in] rvecIgnoreModules Reference to a vector containing all the modules to not unload.
|
||||
*/
|
||||
void UnloadAll(const std::vector<sdv::core::TModuleID>& rvecIgnoreModules);
|
||||
|
||||
/**
|
||||
* @brief Reset the current config baseline.
|
||||
*/
|
||||
void ResetConfigBaseline();
|
||||
|
||||
/**
|
||||
* @brief Save the configuration of all modules.
|
||||
* @param[in] rsetIgnoreModule Set of modules not needing to add.
|
||||
* @return The string containing all the modules.
|
||||
*/
|
||||
std::string SaveConfig(const std::set<std::filesystem::path>& rsetIgnoreModule);
|
||||
|
||||
/**
|
||||
* @brief Load the module with the supplied module manifest. Only for main and isolated applications.
|
||||
* @remarks When running the main application all complex services, applications and utilities run in their isolated
|
||||
* environment. If multiple components are implemented in the module, of which one is suppose to run isolated, all of them run
|
||||
* isolated. Isolated applications cannot load other modules if one was loaded already. Maintenance applications cannot load
|
||||
* modules. All other applications load modules in their own process. Exception to the rul are proxy and stub components.
|
||||
* System objects cannot be loaded this way at all.
|
||||
* @param[in] rpathModule Absolute file path of the module to load.
|
||||
* @param[in] rssManifest The module manifest deciding on where the module should run.
|
||||
* @return Returns the ID of the module or 0 if the module could not be loaded
|
||||
*/
|
||||
sdv::core::TModuleID ContextLoad(const std::filesystem::path& rpathModule, const std::string& rssManifest);
|
||||
|
||||
/**
|
||||
* @brief Unload the module.
|
||||
* @param[in] tModuleID Id representing a module, which has previous been loaded by ContextLoad.
|
||||
* @param[in] bForce When set, the module instance class is cleared whether or not the module is unloaded or not. Use with
|
||||
* extreme care (e.g. during shutdown).
|
||||
* @return Returns true if module is unloaded successfully otherwise false.
|
||||
*/
|
||||
bool ContextUnload(sdv::core::TModuleID tModuleID, bool bForce);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Find a module by its path.
|
||||
* @param[in] rpathModule File path of the module to load. Relative paths are relative to the installation directory for main
|
||||
* and isolated applications and relative to the exe for all other applications.
|
||||
* @return Returns a smart pointer to the module instance or NULL when the module hasn't been found (wasn't loaded).
|
||||
*/
|
||||
std::shared_ptr<CModuleInst> FindModuleByPath(const std::filesystem::path& rpathModule) const;
|
||||
|
||||
/**
|
||||
* @brief HasActiveObjects exported function type definition.
|
||||
*/
|
||||
using TFNHasActiveObjects = bool();
|
||||
|
||||
/**
|
||||
* @brief GetModuleFactory exported function type definition.
|
||||
*/
|
||||
using TFNGetModuleFactory = sdv::IInterfaceAccess*(uint32_t);
|
||||
|
||||
/**
|
||||
* @brief Add core_services and executable folders to the search paths if not done so already.
|
||||
*/
|
||||
void AddCurrentPath();
|
||||
|
||||
using TConfigSet = std::set<sdv::core::TModuleID>;
|
||||
mutable std::recursive_mutex m_mtxModules; ///< Control of the loaded library access
|
||||
std::list<std::shared_ptr<CModuleInst>> m_lstModules; ///< List of modules. The order of loading is preserved.
|
||||
std::list<std::filesystem::path> m_lstSearchPaths; ///< List of search directories.
|
||||
TConfigSet m_setConfigModules; ///< Set with the modules for storing in the configuration.
|
||||
};
|
||||
|
||||
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
|
||||
/**
|
||||
* @brief Module control service
|
||||
*/
|
||||
class CModuleControlService : public sdv::CSdvObject
|
||||
{
|
||||
public:
|
||||
CModuleControlService() = default;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_CHAIN_MEMBER(GetModuleControl())
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::IModuleInfo, GetModuleControl())
|
||||
SDV_INTERFACE_SET_SECTION_CONDITION(EnableModuleControlAccess(), 1)
|
||||
SDV_INTERFACE_SECTION(1)
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::IModuleControl, GetModuleControl())
|
||||
SDV_INTERFACE_DEFAULT_SECTION()
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
|
||||
DECLARE_OBJECT_CLASS_NAME("ModuleControlService")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Get access to the module control
|
||||
* @return Returns the one global instance of the module control.
|
||||
*/
|
||||
static CModuleControl& GetModuleControl();
|
||||
|
||||
/**
|
||||
* @brief When set, the module control will be enabled.
|
||||
* @return Returns whether access to the module control is granted.
|
||||
*/
|
||||
static bool EnableModuleControlAccess();
|
||||
};
|
||||
|
||||
DEFINE_SDV_OBJECT_NO_EXPORT(CModuleControlService)
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#endif // !define MODULE_CONTROL_H
|
||||
43
sdv_services/core/object_lifetime_control.cpp
Normal file
43
sdv_services/core/object_lifetime_control.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "object_lifetime_control.h"
|
||||
#include "sdv_core.h"
|
||||
|
||||
CObjectLifetimeControl::CObjectLifetimeControl(sdv::IInterfaceAccess* pObject, IObjectDestroyHandler& rHandler,
|
||||
bool bAutoDestroy /*= true*/) : m_rHandler(rHandler), m_ptrObject(pObject), m_bAutoDestroy(bAutoDestroy)
|
||||
{
|
||||
m_pObjectDestroy = m_ptrObject.GetInterface<sdv::IObjectDestroy>();
|
||||
m_pObjectLifetime = m_ptrObject.GetInterface<sdv::IObjectLifetime>();
|
||||
}
|
||||
|
||||
void CObjectLifetimeControl::DestroyObject()
|
||||
{
|
||||
if (m_pObjectDestroy)
|
||||
m_pObjectDestroy->DestroyObject();
|
||||
else if (m_pObjectLifetime)
|
||||
m_pObjectLifetime->Decrement();
|
||||
|
||||
m_rHandler.OnDestroyObject(m_ptrObject);
|
||||
|
||||
if (m_bAutoDestroy) delete this;
|
||||
}
|
||||
|
||||
void CObjectLifetimeControl::Increment()
|
||||
{
|
||||
if (m_pObjectLifetime) m_pObjectLifetime->Increment();
|
||||
}
|
||||
|
||||
bool CObjectLifetimeControl::Decrement()
|
||||
{
|
||||
bool bRet = false;
|
||||
if (m_pObjectLifetime) bRet = m_pObjectLifetime->Decrement();
|
||||
if (bRet)
|
||||
{
|
||||
m_rHandler.OnDestroyObject(m_ptrObject);
|
||||
if (m_bAutoDestroy) delete this;
|
||||
}
|
||||
return bRet;
|
||||
}
|
||||
|
||||
uint32_t CObjectLifetimeControl::GetCount() const
|
||||
{
|
||||
return m_pObjectLifetime ? m_pObjectLifetime->GetCount() : 0u;
|
||||
}
|
||||
87
sdv_services/core/object_lifetime_control.h
Normal file
87
sdv_services/core/object_lifetime_control.h
Normal file
@@ -0,0 +1,87 @@
|
||||
#ifndef OBJECT_LIFETIME_CONTROL_H
|
||||
#define OBJECT_LIFETIME_CONTROL_H
|
||||
|
||||
#include <support/interface_ptr.h>
|
||||
|
||||
/**
|
||||
* @brief Object destroy handler - callback interface
|
||||
*/
|
||||
interface IObjectDestroyHandler
|
||||
{
|
||||
/**
|
||||
* @brief Inform about an object destruction. This function might delete the calling class, so no other function must be
|
||||
* called after the call to this function.
|
||||
* @param[in] pObject Pointer to the object being destroyed.
|
||||
*/
|
||||
virtual void OnDestroyObject(sdv::IInterfaceAccess* pObject) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Wrapper class around the object to allow objects to be destroyed through sdv::IObjectDestroy.
|
||||
*/
|
||||
class CObjectLifetimeControl : public sdv::IInterfaceAccess, public sdv::IObjectDestroy, public sdv::IObjectLifetime
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor assigning the object instance.
|
||||
* @param[in] pObject Interface pointer to the object instance. Must not be nullptr.
|
||||
* @param[in] rHandler Reference to the handler being informed about the object destruction.
|
||||
* @param[in] bAutoDestroy When set, the wrapper class will be destroyed automatically when the object is destroyed.
|
||||
*/
|
||||
CObjectLifetimeControl(sdv::IInterfaceAccess* pObject, IObjectDestroyHandler& rHandler, bool bAutoDestroy = true);
|
||||
|
||||
/**
|
||||
* @brief Virtual destructor
|
||||
*/
|
||||
virtual ~CObjectLifetimeControl() = default;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_SET_SECTION_CONDITION(!m_pObjectLifetime, 1)
|
||||
SDV_INTERFACE_SECTION(1)
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectDestroy)
|
||||
SDV_INTERFACE_DEFAULT_SECTION()
|
||||
SDV_INTERFACE_SET_SECTION_CONDITION(m_pObjectLifetime, 2)
|
||||
SDV_INTERFACE_SECTION(2)
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectLifetime)
|
||||
SDV_INTERFACE_DEFAULT_SECTION()
|
||||
SDV_INTERFACE_DENY_ENTRY(sdv::IObjectControl) // Do not allow users to directly access the object control.
|
||||
SDV_INTERFACE_CHAIN_MEMBER(m_ptrObject)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Destroy the object. Overload of sdv::IObjectDestroy::DestroyObject.
|
||||
* @attention After a call of this function, all exposed interfaces render invalid and should not be used any more.
|
||||
*/
|
||||
virtual void DestroyObject() override;
|
||||
|
||||
/**
|
||||
* @brief Increment the lifetime. Needs to be balanced by a call to Decrement. Overload of sdv::IObjectLifetime::Increment.
|
||||
*/
|
||||
virtual void Increment() override;
|
||||
|
||||
/**
|
||||
* @brief Decrement the lifetime. If the lifetime reaches zero, the object will be destroyed (through the exposed
|
||||
* IObjectDestroy interface). Overload of sdv::IObjectLifetime::Decrement.
|
||||
* @return Returns 'true' if the object was destroyed, false if not.
|
||||
*/
|
||||
virtual bool Decrement() override;
|
||||
|
||||
/**
|
||||
* @brief Get the current lifetime count. Overload of sdv::IObjectLifetime::GetCount.
|
||||
* @remarks The GetCount function returns a momentary value, which can be changed at any moment.
|
||||
* @return Returns the current counter value.
|
||||
*/
|
||||
virtual uint32_t GetCount() const override;
|
||||
|
||||
private:
|
||||
IObjectDestroyHandler& m_rHandler; ///< Reference to the handler class being informed about the
|
||||
///< destruction.
|
||||
sdv::IObjectDestroy* m_pObjectDestroy = nullptr; ///< IObjectDestroy interface is exposed by object
|
||||
sdv::IObjectLifetime* m_pObjectLifetime = nullptr; ///< IObjectLifetime interface is exposed by object
|
||||
sdv::TInterfaceAccessPtr m_ptrObject; ///< Interface pointer to the object.
|
||||
bool m_bAutoDestroy = false; ///< When set, the lifetime wrapper object is destroyed after an
|
||||
///< object destroy call.
|
||||
};
|
||||
|
||||
#endif // !defined OBJECT_LIFETIME_CONTROL_H
|
||||
1229
sdv_services/core/repository.cpp
Normal file
1229
sdv_services/core/repository.cpp
Normal file
File diff suppressed because it is too large
Load Diff
397
sdv_services/core/repository.h
Normal file
397
sdv_services/core/repository.h
Normal file
@@ -0,0 +1,397 @@
|
||||
|
||||
#ifndef VAPI_REPOSITORY_H
|
||||
#define VAPI_REPOSITORY_H
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <shared_mutex>
|
||||
#include <interfaces/repository.h>
|
||||
#include <support/component_impl.h>
|
||||
#include <condition_variable>
|
||||
#include "module.h"
|
||||
#include "object_lifetime_control.h"
|
||||
#include "iso_monitor.h"
|
||||
|
||||
/**
|
||||
* @brief repository service providing functionality to load modules, create objects and access exiting objects
|
||||
*/
|
||||
class CRepository :
|
||||
public sdv::IInterfaceAccess, public sdv::core::IObjectAccess, public sdv::core::IRepositoryUtilityCreate,
|
||||
public sdv::core::IRepositoryMarshallCreate, public sdv::core::IRepositoryControl, public sdv::core::IRegisterForeignObject,
|
||||
public sdv::core::IRepositoryInfo, public IObjectDestroyHandler, public sdv::core::ILinkCoreRepository
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CRepository() = default;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::core::IObjectAccess)
|
||||
SDV_INTERFACE_ENTRY(sdv::core::IRepositoryMarshallCreate)
|
||||
SDV_INTERFACE_ENTRY(sdv::core::IRepositoryUtilityCreate)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Switch all components in config mode.
|
||||
*/
|
||||
void SetConfigMode();
|
||||
|
||||
/**
|
||||
* @brief Switch all components in running mode.
|
||||
*/
|
||||
void SetRunningMode();
|
||||
|
||||
/**
|
||||
* @brief Get the object instance previously created through the repository service. Overload of IObjectAccess::GetObject.
|
||||
* @param[in] ssObjectName The name of the requested object.
|
||||
* @return Returns the IInterfaceAccess interface of the object instance if found and nullptr otherwise.
|
||||
*/
|
||||
virtual sdv::IInterfaceAccess* GetObject(/*in*/ const sdv::u8string& ssObjectName) override;
|
||||
|
||||
/**
|
||||
* @brief Get the object instance previously created through the repository service.
|
||||
* @attention Utilities cannot be returned by this function.
|
||||
* @param[in] tObjectID The ID of the requested object.
|
||||
* @return Returns the IInterfaceAccess interface of the object instance if found and nullptr otherwise.
|
||||
*/
|
||||
virtual sdv::IInterfaceAccess* GetObjectByID(/*in*/ sdv::core::TObjectID tObjectID) override;
|
||||
|
||||
/**
|
||||
* @brief Creates an utility object from a previously loaded module. Overload of IRepositoryUtilityCreate::CreateUtility.
|
||||
* @attention Utilities are standalone objects. Use IObjectDestroy to destroy the utility.
|
||||
* @param[in] ssClassName The name of the object class to be created.
|
||||
* @param[in] ssObjectConfig Optional configuration handed over to the object upon creation via IObjectControl.
|
||||
* @return Returns the IInterfaceAccess interface of newly created utility.
|
||||
*/
|
||||
virtual sdv::IInterfaceAccess* CreateUtility(/*in*/ const sdv::u8string& ssClassName,
|
||||
/*in*/ const sdv::u8string& ssObjectConfig) override;
|
||||
|
||||
/**
|
||||
* @brief Create a proxy object for the interface with the supplied ID. If successful, this object is initialized, but
|
||||
* not linked to any other object within the system. Overload of sdv::core::IRepositoryMarshallCreate::CreateProxyObject.
|
||||
* @details Create a proxy object with the name "proxy_<ifc id>". "ifc_id" is the decimal value of an interface ID.
|
||||
* @param[in] id The interface ID to create the object for.
|
||||
* @return Returns the interface to the proxy object. Destruction of the object can be achieved through IObjectDestroy.
|
||||
*/
|
||||
virtual sdv::IInterfaceAccess* CreateProxyObject(/*in*/ sdv::interface_id id) override;
|
||||
|
||||
/**
|
||||
* @brief Create a stub object for the interface with the supplied ID. If successful, this object is initialized, but
|
||||
* not linked to any other object within the system. Overload of sdv::core::IRepositoryMarshallCreate::CreateStubObject.
|
||||
* @details Create a stub object with the name "stub_<ifc id>". "ifc_id" is the decimal value of an interface ID.
|
||||
* @param[in] id The interface ID to create the object for.
|
||||
* @return Returns the interface to the stub object. Destruction of the object can be achieved through IObjectDestroy.
|
||||
*/
|
||||
virtual sdv::IInterfaceAccess* CreateStubObject(/*in*/ sdv::interface_id id) override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Create an object and all its objects it depends on. Overload of Overload of
|
||||
* sdv::core::IRepositoryControl::CreateObject.
|
||||
* @details For standalone and essential applications, this function allows the creation of system, device and service
|
||||
* objects if the module was loaded previously. For the main and isolated application, this function allows the
|
||||
* creation of complex services only and only those that are in the installation. For the isolated application only one
|
||||
* complex service can be created. External apps, utilities, and proxy and stub objects cannot be created at all using
|
||||
* this function.
|
||||
* Objects that the to be create object is depending on will be created as well. For the main application this is
|
||||
* limited to complex services. Isolated applications cannot load other services; this is taken over by the main
|
||||
* application.
|
||||
* @param[in] ssClassName The name of the object class to be created. For the main application, the class string could
|
||||
* be empty for the main application if the object was defined in the installation.
|
||||
* @param[in] ssObjectName Name of the object, required to be unique. For standalone and essential applications, the
|
||||
* name string can be empty, in which case the object might either provide a name proposal or the name is the same as
|
||||
* the class name. Use the returned object ID to request the name of the object.
|
||||
* @param[in] ssObjectConfig Optional configuration handed over to the object upon creation via IObjectControl. Only
|
||||
* valid for standalone, essential and isolated applications.
|
||||
* @return Returns the object ID when the object creation was successful or 0 when not. On success the object is
|
||||
* available through the IObjectAccess interface. If the object already exists (class and object names are identical),
|
||||
* the object ID of the existing object is returned.
|
||||
*/
|
||||
virtual sdv::core::TObjectID CreateObject(/*in*/ const sdv::u8string& ssClassName, /*in*/ const sdv::u8string& ssObjectName,
|
||||
/*in*/ const sdv::u8string& ssObjectConfig) override;
|
||||
public:
|
||||
/**
|
||||
* @brief Creates an object from a previously loaded module. Provide the module ID to explicitly define what module to
|
||||
* use during object creation. Overload of sdv::core::IRepositoryControl::CreateObjectFromModule.
|
||||
* @param[in] tModuleID Module ID that contains the object class to create the object with.
|
||||
* @param[in] ssClassName The name of the object class to be created.
|
||||
* @param[in] ssObjectName Optional name of the object - required to be unique. If not supplied, the object might
|
||||
* either provide a name proposal or the name is the same as the class name. Use the returned object ID to request
|
||||
* the name of the object.
|
||||
* @param[in] ssObjectConfig Optional configuration handed over to the object upon creation via IObjectControl.
|
||||
* @return Returns the object ID when the object creation was successful or 0 when not. On success the object is
|
||||
* available through the IObjectAccess interface. If the object already exists (class and object names are identical),
|
||||
* the object ID of the existing object is returned.
|
||||
*/
|
||||
virtual sdv::core::TObjectID CreateObjectFromModule(/*in*/ sdv::core::TModuleID tModuleID,
|
||||
/*in*/ const sdv::u8string& ssClassName, /*in*/ const sdv::u8string& ssObjectName,
|
||||
/*in*/ const sdv::u8string& ssObjectConfig) override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Destroy a previously created object with the supplied name. Overload of sdv::core::IRepositoryControl::DestroyObject.
|
||||
* @details For standalone and essential applications previously created system, device and service objects can be
|
||||
* destroyed. For the main and isolated applications, only the complex service can be destroyed. For isolated
|
||||
* applications a destruction of the object will end the application.
|
||||
* @param[in] ssObjectName The name of the object to destroy.
|
||||
* @return Returns whether the object destruction was successful.
|
||||
*/
|
||||
virtual bool DestroyObject(/*in*/ const sdv::u8string& ssObjectName) override;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Create an object and all its objects it depends on. Internal function not accessible through the interface.
|
||||
* @param[in] ssClassName The name of the object class to be created. For the main application, the class string could
|
||||
* be empty for the main application if the object was defined in the installation.
|
||||
* @param[in] ssObjectName Name of the object, required to be unique. For standalone and essential applications, the
|
||||
* name string can be empty, in which case the object might either provide a name proposal or the name is the same as
|
||||
* the class name. Use the returned object ID to request the name of the object.
|
||||
* @param[in] ssObjectConfig Optional configuration handed over to the object upon creation via IObjectControl. Only
|
||||
* valid for standalone, essential and isolated applications.
|
||||
* @return Returns the object ID when the object creation was successful or 0 when not. On success the object is
|
||||
* available through the IObjectAccess interface. If the object already exists (class and object names are identical),
|
||||
* the object ID of the existing object is returned.
|
||||
*/
|
||||
sdv::core::TObjectID CreateObject2(/*in*/ const sdv::u8string& ssClassName, /*in*/ const sdv::u8string& ssObjectName,
|
||||
/*in*/ const sdv::u8string& ssObjectConfig);
|
||||
|
||||
/**
|
||||
* @brief Destroy a previously created object with the supplied name. Internal function not accessible through the interface.
|
||||
* @details For standalone and essential applications previously created system, device and service objects can be
|
||||
* destroyed. For the main and isolated applications, only the complex service can be destroyed. For isolated
|
||||
* applications a destruction of the object will end the application.
|
||||
* @param[in] ssObjectName The name of the object to destroy.
|
||||
* @return Returns whether the object destruction was successful.
|
||||
*/
|
||||
bool DestroyObject2(/*in*/ const sdv::u8string& ssObjectName);
|
||||
|
||||
/**
|
||||
* @brief Register as foreign object and make it public to the system with the given name. Overload of
|
||||
* sdv::core::IRegisterForeignObject::RegisterObject.
|
||||
* @param[in] pObjectIfc Interface of the object to be registered.
|
||||
* @param[in] ssObjectName The name under which the object - required to be unique.
|
||||
* @return Returns the object ID when the object creation was successful or 0 when not. On success the object is
|
||||
* available through the IObjectAccess interface. If the object already exists (class and object names are identical),
|
||||
* the object ID of the existing object is returned.
|
||||
*/
|
||||
virtual sdv::core::TObjectID RegisterObject(/*in*/ sdv::IInterfaceAccess* pObjectIfc,
|
||||
/*in*/ const sdv::u8string& ssObjectName) override;
|
||||
|
||||
/**
|
||||
* @brief Register the core repository.
|
||||
* @param[in] pCoreRepository Pointer to the proxy interface of the core repository.
|
||||
*/
|
||||
virtual void LinkCoreRepository(/*in*/ sdv::IInterfaceAccess* pCoreRepository) override;
|
||||
|
||||
/**
|
||||
* @brief Unlink a previously linked core repository.
|
||||
*/
|
||||
virtual void UnlinkCoreRepository() override;
|
||||
|
||||
/**
|
||||
* @brief Find the class information of an object with the supplied name. Overload of
|
||||
* sdv::core::IRepositoryControl::IRepositoryInfo::FindClass.
|
||||
* @param[in] ssClassName Object class name.
|
||||
* @return The object class information.
|
||||
*/
|
||||
virtual sdv::SClassInfo FindClass(/*in*/ const sdv::u8string& ssClassName) const override;
|
||||
|
||||
/**
|
||||
* @brief Get a list of all the instantiated objects. Overload of sdv::core::IRepositoryControl::IRepositoryInfo::GetObjectList.
|
||||
* @return Sequence containing the object information structures.
|
||||
*/
|
||||
virtual sdv::sequence<sdv::core::SObjectInfo> GetObjectList() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the object info for the requested object. Overload of sdv::core::IRepositoryInfo::GetObjectInfo.
|
||||
* @param[in] tObjectID The object ID to return the object information for.
|
||||
* @return The object information structure if the object is available or an empty structure if not.
|
||||
*/
|
||||
virtual sdv::core::SObjectInfo GetObjectInfo(/*in*/ sdv::core::TObjectID tObjectID) const override;
|
||||
|
||||
/**
|
||||
* @brief Find an object with the supplied name. Only object instances that are in the service list can be found with this
|
||||
* function (devices, basic and complex services, and system objects). Overload of sdv::core::IRepositoryInfo::FindObject.
|
||||
* @param[in] ssObjectName Object name to search for.
|
||||
* @return The object information structure if the object is available or an empty structure if not.
|
||||
*/
|
||||
virtual sdv::core::SObjectInfo FindObject(/*in*/ const sdv::u8string& ssObjectName) const override;
|
||||
|
||||
/**
|
||||
* @brief Remove an object instance from the local object map. Overload of IObjectDestroyHandler::OnDestroyObject.
|
||||
* @param[in] pObject Interface pointer to the object instance.
|
||||
*/
|
||||
virtual void OnDestroyObject(sdv::IInterfaceAccess* pObject) override;
|
||||
|
||||
/**
|
||||
* @brief Destroy all objects from the service map created for a specific module.
|
||||
* @param[in] tModuleID Module ID that contains the object class to destroy.
|
||||
*/
|
||||
void DestroyModuleObjects(sdv::core::TModuleID tModuleID);
|
||||
|
||||
/**
|
||||
* @brief Destroy all objects from the service map.
|
||||
* @remarks In Emergency, the objects should be removed without unloading to prevent any more calls to take place. The reason
|
||||
* is, that the system could do an emergency shutdown and part of the system might have been cleaned up already and part not.
|
||||
* @param[in] rvecIgnoreObjects Reference to the vector of objects to not destroy.
|
||||
* @param[in] bForce Force destruction (remove without calling any more function).
|
||||
*/
|
||||
void DestroyAllObjects(const std::vector<std::string>&rvecIgnoreObjects, bool bForce = false);
|
||||
|
||||
/**
|
||||
* @brief Reset the current config baseline.
|
||||
*/
|
||||
void ResetConfigBaseline();
|
||||
|
||||
/**
|
||||
* @brief Save the configuration of all components.
|
||||
* @return The string containing all the components.
|
||||
*/
|
||||
std::string SaveConfig();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Create an isolated object (object running in a separate process). Only allowed to be called by the main application.
|
||||
* @param[in] rsClassInfo Reference to the class information structure of the object.
|
||||
* @param[in] rssObjectName Reference to the name of the object - required to be unique.
|
||||
* @param[in] rssObjectConfig Reference to an optional configuration handed over to the object upon creation via IObjectControl.
|
||||
* @return Returns the object ID when the object creation was successful or 0 when not. On success the object is
|
||||
* available through the IObjectAccess interface. If the object already exists (class and object names are identical),
|
||||
* the object ID of the existing object is returned.
|
||||
*/
|
||||
sdv::core::TObjectID CreateIsolatedObject(const sdv::SClassInfo& rsClassInfo, const sdv::u8string& rssObjectName,
|
||||
const sdv::u8string& rssObjectConfig);
|
||||
|
||||
/**
|
||||
* @brief Creates an object from the supplied module instance.
|
||||
* @param[in] rptrModule Reference to the module that contains the object class to create the object with.
|
||||
* @param[in] rsClassInfo Reference to the class information structure of the object.
|
||||
* @param[in] rssObjectName Reference to the name of the object - required to be unique.
|
||||
* @param[in] rssObjectConfig Reference to an optional configuration handed over to the object upon creation via IObjectControl.
|
||||
* @return Returns the object ID when the object creation was successful or 0 when not. On success the object is
|
||||
* available through the IObjectAccess interface. If the object already exists (class and object names are identical),
|
||||
* the object ID of the existing object is returned.
|
||||
*/
|
||||
sdv::core::TObjectID InternalCreateObject(const std::shared_ptr<CModuleInst>& rptrModule, const sdv::SClassInfo& rsClassInfo,
|
||||
const sdv::u8string& rssObjectName, const sdv::u8string& rssObjectConfig);
|
||||
|
||||
/**
|
||||
* @brief Create a new unique object ID.
|
||||
* @return The created object ID.
|
||||
*/
|
||||
static sdv::core::TObjectID CreateObjectID();
|
||||
|
||||
/**
|
||||
* @brief Get a list of depending object instances of a specific class.
|
||||
* @param[in] rssClass Reference to the class name.
|
||||
* @return Returns a list of object instances.
|
||||
*/
|
||||
std::vector<sdv::core::TObjectID> GetDependingObjectInstancesByClass(const std::string& rssClass);
|
||||
|
||||
/**
|
||||
* @brief Object entry
|
||||
* @details The object instance information. Objects that are running remotely are represented by their proxy. Objects can have
|
||||
* multiple references by stubs.
|
||||
*/
|
||||
struct SObjectEntry
|
||||
{
|
||||
sdv::core::TObjectID tObjectID = 0; ///< Object ID (local to this process).
|
||||
sdv::SClassInfo sClassInfo; ///< Object class name.
|
||||
std::string ssName; ///< Object name (can be zero with local objects).
|
||||
std::string ssConfig; ///< Object configuration.
|
||||
sdv::TInterfaceAccessPtr ptrObject; ///< Object interface (could be proxy).
|
||||
std::shared_ptr<CModuleInst> ptrModule; ///< Module instance.
|
||||
bool bControlled = false; ///< When set, the object is controlled.
|
||||
bool bIsolated = false; ///< When set, the object is isolated and running in another process.
|
||||
std::mutex mtxConnect; ///< Mutex used to wait for connection
|
||||
std::condition_variable cvConnect; ///< Condition variable used to wait for connection
|
||||
std::shared_ptr<CIsoMonitor> ptrIsoMon; ///< Object being monitored for shutdown.
|
||||
};
|
||||
|
||||
using TObjectMap = std::map<sdv::core::TObjectID, std::shared_ptr<SObjectEntry>>;
|
||||
using TOrderedObjectList = std::list<std::shared_ptr<SObjectEntry>>;
|
||||
using TObjectIDList = std::list<std::shared_ptr<SObjectEntry>>;
|
||||
using TServiceMap = std::map<std::string, TOrderedObjectList::iterator>;
|
||||
using TIsolationMap = std::map<std::string, std::shared_ptr<SObjectEntry>>;
|
||||
using TLocalObjectMap = std::map<sdv::IInterfaceAccess*, std::shared_ptr<SObjectEntry>>;
|
||||
using TConfigSet = std::set<sdv::core::TObjectID>;
|
||||
mutable std::shared_mutex m_mtxObjects; ///< Protects against concurrent access.
|
||||
TOrderedObjectList m_lstOrderedServiceObjects; ///< List of service object IDs in order of creation.
|
||||
TServiceMap m_mapServiceObjects; ///< Map of service objects indexed by object name an pointing
|
||||
///< to the entry in the list.
|
||||
TLocalObjectMap m_mapLocalObjects; ///< Map with local objects indexed by object pointer.
|
||||
TIsolationMap m_mapIsolatedObjects; ///< Map with isolated objects.
|
||||
TObjectMap m_mapObjects; ///< Map with all objects indexed by the object ID.
|
||||
TConfigSet m_setConfigObjects; ///< Set with the objects for storing in the configuration.
|
||||
sdv::TInterfaceAccessPtr m_ptrCoreRepoAccess; ///< Linked core repository access (proxy interface).
|
||||
bool m_bIsoObjectLoaded = false; ///< When set, the isolated object has loaded. Do not allow
|
||||
///< another object of type complex service or utility to be
|
||||
///< created.
|
||||
};
|
||||
|
||||
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
|
||||
/**
|
||||
* @brief Repository service
|
||||
*/
|
||||
class CRepositoryService : public sdv::CSdvObject
|
||||
{
|
||||
public:
|
||||
CRepositoryService() = default;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_CHAIN_MEMBER(GetRepository())
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::IRepositoryInfo, GetRepository())
|
||||
SDV_INTERFACE_SET_SECTION_CONDITION(EnableRepositoryObjectControl(), 1)
|
||||
SDV_INTERFACE_SECTION(1)
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::IRepositoryControl, GetRepository())
|
||||
SDV_INTERFACE_DEFAULT_SECTION()
|
||||
SDV_INTERFACE_SET_SECTION_CONDITION(EnableRepositoryRegisterForeignApp(), 2)
|
||||
SDV_INTERFACE_SECTION(2)
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::IRegisterForeignObject, GetRepository())
|
||||
SDV_INTERFACE_DEFAULT_SECTION()
|
||||
SDV_INTERFACE_SET_SECTION_CONDITION(EnableRepositoryLink(), 3)
|
||||
SDV_INTERFACE_SECTION(3)
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::ILinkCoreRepository, GetRepository())
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
|
||||
DECLARE_OBJECT_CLASS_NAME("RepositoryService")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Get access to the repository.
|
||||
* @return Returns a reference to the one repository of this module.
|
||||
*/
|
||||
static CRepository& GetRepository();
|
||||
|
||||
/**
|
||||
* @brief When set, the repository object control access will be enabled.
|
||||
* @return Returns whether object control interface access is granted.
|
||||
*/
|
||||
static bool EnableRepositoryObjectControl();
|
||||
|
||||
/**
|
||||
* @brief When set, the foreign app registration into the repository will be enabled.
|
||||
* @return Returns whether foreign app rgeistration interface access is granted.
|
||||
*/
|
||||
static bool EnableRepositoryRegisterForeignApp();
|
||||
|
||||
/**
|
||||
* @brief When set, the core repository link access will be enabled.
|
||||
* @return Returns whether core repository link interface access is granted.
|
||||
*/
|
||||
static bool EnableRepositoryLink();
|
||||
};
|
||||
|
||||
DEFINE_SDV_OBJECT_NO_EXPORT(CRepositoryService)
|
||||
#endif
|
||||
|
||||
#endif // !define VAPI_REPOSITORY_H
|
||||
140
sdv_services/core/sdv_core.cpp
Normal file
140
sdv_services/core/sdv_core.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
#include "sdv_core.h"
|
||||
#include "toml_parser_util.h" // Insert to create the factory of this utility
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
extern sdv::IInterfaceAccess* SDVCore()
|
||||
{
|
||||
static CSDVCore& rcore = CSDVCore::GetInstance();
|
||||
return &rcore;
|
||||
}
|
||||
|
||||
CSDVCore::CSDVCore()
|
||||
{}
|
||||
|
||||
CSDVCore::~CSDVCore()
|
||||
{}
|
||||
|
||||
CSDVCore& CSDVCore::GetInstance()
|
||||
{
|
||||
static CSDVCore core;
|
||||
return core;
|
||||
}
|
||||
|
||||
CAppControl& CSDVCore::GetAppControl()
|
||||
{
|
||||
return m_appctrl;
|
||||
}
|
||||
|
||||
CModuleControl& CSDVCore::GetModuleControl()
|
||||
{
|
||||
return m_modulectrl;
|
||||
}
|
||||
|
||||
CMemoryManager& CSDVCore::GetMemoryManager()
|
||||
{
|
||||
return m_memmgr;
|
||||
}
|
||||
|
||||
CRepository& CSDVCore::GetRepository()
|
||||
{
|
||||
return m_repository;
|
||||
}
|
||||
|
||||
CLoggerControl& CSDVCore::GetLoggerControl()
|
||||
{
|
||||
return m_loggerctrl;
|
||||
}
|
||||
|
||||
CLogger& CSDVCore::GetDefaultLogger()
|
||||
{
|
||||
return m_defaultlogger;
|
||||
}
|
||||
|
||||
CAppConfig& CSDVCore::GetAppConfig()
|
||||
{
|
||||
return m_appconfig;
|
||||
}
|
||||
|
||||
CAppControl& GetAppControl()
|
||||
{
|
||||
return CSDVCore::GetInstance().GetAppControl();
|
||||
}
|
||||
|
||||
CModuleControl& GetModuleControl()
|
||||
{
|
||||
return CSDVCore::GetInstance().GetModuleControl();
|
||||
}
|
||||
|
||||
CMemoryManager& GetMemoryManager()
|
||||
{
|
||||
return CSDVCore::GetInstance().GetMemoryManager();
|
||||
}
|
||||
|
||||
CRepository& GetRepository()
|
||||
{
|
||||
return CSDVCore::GetInstance().GetRepository();
|
||||
}
|
||||
|
||||
CLoggerControl& GetLoggerControl()
|
||||
{
|
||||
return CSDVCore::GetInstance().GetLoggerControl();
|
||||
}
|
||||
|
||||
CLogger& GetDefaultLogger()
|
||||
{
|
||||
return CSDVCore::GetInstance().GetDefaultLogger();
|
||||
}
|
||||
|
||||
CAppConfig& GetAppConfig()
|
||||
{
|
||||
return CSDVCore::GetInstance().GetAppConfig();
|
||||
}
|
||||
|
||||
std::filesystem::path GetCoreDirectory()
|
||||
{
|
||||
static std::filesystem::path pathCoreDir;
|
||||
if (!pathCoreDir.empty()) return pathCoreDir;
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows specific
|
||||
std::wstring ssPath(32768, '\0');
|
||||
|
||||
MEMORY_BASIC_INFORMATION sMemInfo{};
|
||||
if (!VirtualQuery(&pathCoreDir, &sMemInfo, sizeof(sMemInfo))) return pathCoreDir;
|
||||
DWORD dwLength = GetModuleFileNameW(reinterpret_cast<HINSTANCE>(sMemInfo.AllocationBase), ssPath.data(), 32767);
|
||||
ssPath.resize(dwLength);
|
||||
pathCoreDir = std::filesystem::path(ssPath);
|
||||
return pathCoreDir.remove_filename();
|
||||
#elif __linux__
|
||||
// Read the maps file. It contains all loaded SOs.
|
||||
std::ifstream fstream("/proc/self/maps");
|
||||
std::stringstream sstreamMap;
|
||||
sstreamMap << fstream.rdbuf();
|
||||
std::string ssMap = sstreamMap.str();
|
||||
if (ssMap.empty())
|
||||
return pathCoreDir; // Some error
|
||||
|
||||
// Find the "core_services.sdv"
|
||||
size_t nPos = ssMap.find("core_services.sdv");
|
||||
if (nPos == std::string::npos) return pathCoreDir;
|
||||
size_t nEnd = nPos;
|
||||
|
||||
// Find the start... runbackwards until the beginning of the line and remember the earliest occurance of a slash
|
||||
size_t nBegin = 0;
|
||||
while (nPos && ssMap[nPos] != '\n')
|
||||
{
|
||||
if (ssMap[nPos] == '/')
|
||||
nBegin = nPos;
|
||||
nPos--;
|
||||
}
|
||||
if (!nBegin) nBegin = nPos;
|
||||
|
||||
// Return the path
|
||||
pathCoreDir = ssMap.substr(nBegin, nEnd - nBegin);
|
||||
|
||||
return pathCoreDir;
|
||||
#else
|
||||
#error The OS is not supported!
|
||||
#endif
|
||||
}
|
||||
150
sdv_services/core/sdv_core.h
Normal file
150
sdv_services/core/sdv_core.h
Normal file
@@ -0,0 +1,150 @@
|
||||
#ifndef CORE_H
|
||||
#define CORE_H
|
||||
|
||||
#include <support/component_impl.h>
|
||||
#include "app_control.h"
|
||||
#include "module_control.h"
|
||||
#include "memory.h"
|
||||
#include "repository.h"
|
||||
#include "logger_control.h"
|
||||
#include "logger.h"
|
||||
#include "app_config.h"
|
||||
|
||||
/**
|
||||
* @brief SDV core instance class containing containing the instances for the core services.
|
||||
*/
|
||||
class CSDVCore : public sdv::IInterfaceAccess
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
CSDVCore();
|
||||
|
||||
/**
|
||||
* \brief Destructor
|
||||
*/
|
||||
~CSDVCore();
|
||||
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_CHAIN_MEMBER(m_appctrl)
|
||||
SDV_INTERFACE_CHAIN_MEMBER(m_modulectrl)
|
||||
SDV_INTERFACE_CHAIN_MEMBER(m_memmgr)
|
||||
SDV_INTERFACE_CHAIN_MEMBER(m_repository)
|
||||
SDV_INTERFACE_CHAIN_MEMBER(m_loggerctrl)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief The one and only instance.
|
||||
* @return Reference to this class.
|
||||
*/
|
||||
static CSDVCore& GetInstance();
|
||||
|
||||
/**
|
||||
* @brief Return the application control.
|
||||
* @return Reference to the application control.
|
||||
*/
|
||||
CAppControl& GetAppControl();
|
||||
|
||||
/**
|
||||
* @brief Return the module control.
|
||||
* @return Reference to the module control.
|
||||
*/
|
||||
CModuleControl& GetModuleControl();
|
||||
|
||||
/**
|
||||
* @brief Return the memory manager.
|
||||
* @return Reference to the memory manager.
|
||||
*/
|
||||
CMemoryManager& GetMemoryManager();
|
||||
|
||||
/**
|
||||
* @brief Return the repository.
|
||||
* @return Reference to the repository.
|
||||
*/
|
||||
CRepository& GetRepository();
|
||||
|
||||
/**
|
||||
* @brief Return the logger control.
|
||||
* @return Reference to the logger control.
|
||||
*/
|
||||
CLoggerControl& GetLoggerControl();
|
||||
|
||||
/**
|
||||
* @brief Return the default logger.
|
||||
* @return Reference to the default logger.
|
||||
*/
|
||||
CLogger& GetDefaultLogger();
|
||||
|
||||
/**
|
||||
* @brief Return the application config class.
|
||||
* @return Reference to the application config class.
|
||||
*/
|
||||
CAppConfig& GetAppConfig();
|
||||
|
||||
private:
|
||||
CMemoryManager m_memmgr; ///< Memory manager - note: needs to be first in the list of members!
|
||||
CAppControl m_appctrl; ///< Application control
|
||||
CRepository m_repository; ///< Repository - note: repository should be present before module control!
|
||||
CModuleControl m_modulectrl; ///< Module control
|
||||
CLogger m_defaultlogger; ///< Default logger - note: the logger must be present before the logger control!
|
||||
CLoggerControl m_loggerctrl; ///< Logger control
|
||||
CAppConfig m_appconfig; ///< Application configuration class
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Exported function for core access.
|
||||
* @return Pointer to the interface of the core library.
|
||||
*/
|
||||
extern "C" SDV_SYMBOL_PUBLIC sdv::IInterfaceAccess* SDVCore();
|
||||
|
||||
/**
|
||||
* @brief Return the application control.
|
||||
* @return Reference to the application control.
|
||||
*/
|
||||
CAppControl& GetAppControl();
|
||||
|
||||
/**
|
||||
* @brief Return the module control.
|
||||
* @return Reference to the module control.
|
||||
*/
|
||||
CModuleControl& GetModuleControl();
|
||||
|
||||
/**
|
||||
* @brief Return the memory manager.
|
||||
* @return Reference to the memory manager.
|
||||
*/
|
||||
CMemoryManager& GetMemoryManager();
|
||||
|
||||
/**
|
||||
* @brief Return the repository.
|
||||
* @return Reference to the repository.
|
||||
*/
|
||||
CRepository& GetRepository();
|
||||
|
||||
/**
|
||||
* @brief Return the logger control.
|
||||
* @return Reference to the logger control.
|
||||
*/
|
||||
CLoggerControl& GetLoggerControl();
|
||||
|
||||
/**
|
||||
* @brief Return the default logger.
|
||||
* @attention Use the logger control to access the logger.
|
||||
* @return Reference to the default logger.
|
||||
*/
|
||||
CLogger& GetDefaultLogger();
|
||||
|
||||
/**
|
||||
* @brief Return the application config class.
|
||||
* @return Reference to the application config class.
|
||||
*/
|
||||
CAppConfig& GetAppConfig();
|
||||
|
||||
/**
|
||||
* @brief Get the location of the core_services.sdv.
|
||||
* @return Path to the directory containing the loaded core directory.
|
||||
*/
|
||||
std::filesystem::path GetCoreDirectory();
|
||||
|
||||
#endif // !defined CORE_H
|
||||
323
sdv_services/core/toml_parser/character_reader_utf_8.cpp
Normal file
323
sdv_services/core/toml_parser/character_reader_utf_8.cpp
Normal file
@@ -0,0 +1,323 @@
|
||||
#include "character_reader_utf_8.h"
|
||||
#include "exception.h"
|
||||
|
||||
CCharacterReaderUTF8::CCharacterReaderUTF8() : m_nDataLength{0}, m_nCursor{0}
|
||||
{}
|
||||
|
||||
CCharacterReaderUTF8::CCharacterReaderUTF8(const std::string& rssString) :
|
||||
m_ssString{rssString}, m_nDataLength{rssString.size()}, m_nCursor{0}
|
||||
{
|
||||
CheckForInvalidUTF8Bytes();
|
||||
CheckForInvalidUTF8Sequences();
|
||||
}
|
||||
|
||||
void CCharacterReaderUTF8::Feed(const std::string& rssString)
|
||||
{
|
||||
m_ssString = rssString;
|
||||
m_nDataLength = rssString.size();
|
||||
m_nCursor = 0;
|
||||
|
||||
CheckForInvalidUTF8Bytes();
|
||||
CheckForInvalidUTF8Sequences();
|
||||
}
|
||||
|
||||
void CCharacterReaderUTF8::Reset()
|
||||
{
|
||||
m_ssString.clear();
|
||||
m_nDataLength = 0;
|
||||
m_nCursor = 0;
|
||||
}
|
||||
|
||||
std::string CCharacterReaderUTF8::Peek()
|
||||
{
|
||||
return GetNextCharacter();
|
||||
}
|
||||
|
||||
std::string CCharacterReaderUTF8::Peek(std::size_t n)
|
||||
{
|
||||
if (n < 1)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
size_t offset{0};
|
||||
for (std::size_t i = 0; i < n - 1; ++i)
|
||||
{
|
||||
offset += GetLengthOfNextCharacter(offset);
|
||||
if (m_nDataLength <= m_nCursor + offset)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return GetNextCharacter(offset);
|
||||
}
|
||||
|
||||
std::string CCharacterReaderUTF8::PeekUntil(const std::vector<std::string>& lstCollection)
|
||||
{
|
||||
size_t offset{0};
|
||||
bool found{false};
|
||||
std::string accumulation;
|
||||
while (!found && m_nDataLength > m_nCursor + offset)
|
||||
{
|
||||
std::string character = GetNextCharacter(offset);
|
||||
offset += GetLengthOfNextCharacter(offset);
|
||||
for (const auto& delimiter : lstCollection)
|
||||
{
|
||||
if (delimiter == character)
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
accumulation += character;
|
||||
}
|
||||
}
|
||||
return accumulation;
|
||||
}
|
||||
|
||||
std::string CCharacterReaderUTF8::Consume()
|
||||
{
|
||||
if (IsEOF())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
std::string character{GetNextCharacter()};
|
||||
m_nCursor += GetLengthOfNextCharacter();
|
||||
return character;
|
||||
}
|
||||
|
||||
std::string CCharacterReaderUTF8::Consume(std::size_t n)
|
||||
{
|
||||
if (n < 1)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
size_t offset{0};
|
||||
for (uint32_t i = 0; i < n - 1; ++i)
|
||||
{
|
||||
offset += GetLengthOfNextCharacter(offset);
|
||||
if (m_nDataLength < m_nCursor + offset)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
std::string character{GetNextCharacter(offset)};
|
||||
m_nCursor += offset + GetLengthOfNextCharacter(offset);
|
||||
return character;
|
||||
}
|
||||
|
||||
std::string CCharacterReaderUTF8::ConsumeUntil(const std::vector<std::string>& lstCollection)
|
||||
{
|
||||
std::size_t offset{0};
|
||||
bool found{false};
|
||||
std::string accumulation;
|
||||
while (!found && m_nDataLength > m_nCursor + offset)
|
||||
{
|
||||
std::string character = GetNextCharacter(offset);
|
||||
offset += GetLengthOfNextCharacter(offset);
|
||||
for (const auto& delimiter : lstCollection)
|
||||
{
|
||||
if (delimiter == character)
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
accumulation += character;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset -= character.size();
|
||||
}
|
||||
}
|
||||
m_nCursor += offset;
|
||||
return accumulation;
|
||||
}
|
||||
|
||||
bool CCharacterReaderUTF8::IsEOF() const
|
||||
{
|
||||
return m_nDataLength < m_nCursor + 1;
|
||||
}
|
||||
|
||||
void CCharacterReaderUTF8::CheckForInvalidUTF8Bytes() const
|
||||
{
|
||||
const unsigned char invalidByteC0{0xC0};
|
||||
const unsigned char invalidByteC1{0xC1};
|
||||
const unsigned char lowerBoundInvalidRegion{0xF5};
|
||||
for (std::size_t i = 0; i < m_ssString.size(); ++i)
|
||||
{
|
||||
unsigned char uc = m_ssString[i];
|
||||
if (uc == invalidByteC0 || uc == invalidByteC1 || uc >= lowerBoundInvalidRegion)
|
||||
{
|
||||
std::stringstream message;
|
||||
message << "Invalid byte " << std::hex << uc << std::dec << " at position " << i << "\n";
|
||||
throw XTOMLParseException(message.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CCharacterReaderUTF8::CheckForInvalidUTF8Sequences() const
|
||||
{
|
||||
enum class EState
|
||||
{
|
||||
state_neutral,
|
||||
state_two_byte,
|
||||
state_three_byte,
|
||||
state_four_byte,
|
||||
state_error
|
||||
};
|
||||
EState eCurrentState{EState::state_neutral};
|
||||
uint8_t uiIndex{0};
|
||||
auto fnCheckByteInNeutralState = [&uiIndex, &eCurrentState](unsigned char uc)
|
||||
{
|
||||
if ((uc & m_uiOneByteCheckMask) == m_OneByteCheckValue)
|
||||
{
|
||||
eCurrentState = EState::state_neutral;
|
||||
uiIndex = 0;
|
||||
}
|
||||
else if ((uc & m_uiFourByteCheckMask) == m_uiFourByteCheckValue)
|
||||
{
|
||||
eCurrentState = EState::state_four_byte;
|
||||
uiIndex = 1;
|
||||
}
|
||||
else if ((uc & m_uiThreeByteCheckMask) == m_uiThreeByteCheckValue)
|
||||
{
|
||||
eCurrentState = EState::state_three_byte;
|
||||
uiIndex = 1;
|
||||
}
|
||||
else if ((uc & m_uiTwoByteCheckMask) == m_uiTwoByteCheckValue)
|
||||
{
|
||||
eCurrentState = EState::state_two_byte;
|
||||
uiIndex = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
eCurrentState = EState::state_error;
|
||||
}
|
||||
};
|
||||
auto fnCheckByteInTwoByteState = [&uiIndex, &eCurrentState](unsigned char uc)
|
||||
{
|
||||
if ((uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue)
|
||||
{
|
||||
uiIndex = 0;
|
||||
eCurrentState = EState::state_neutral;
|
||||
}
|
||||
else
|
||||
{
|
||||
eCurrentState = EState::state_error;
|
||||
}
|
||||
};
|
||||
auto fnCheckByteInThreeByteState = [&uiIndex, &eCurrentState](unsigned char uc)
|
||||
{
|
||||
if (uiIndex == 1 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue)
|
||||
{
|
||||
uiIndex = 2;
|
||||
}
|
||||
else if (uiIndex == 2 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue)
|
||||
{
|
||||
uiIndex = 0;
|
||||
eCurrentState = EState::state_neutral;
|
||||
}
|
||||
else
|
||||
{
|
||||
eCurrentState = EState::state_error;
|
||||
}
|
||||
};
|
||||
auto fnCheckByteInFourByteState = [&uiIndex, &eCurrentState](unsigned char uc)
|
||||
{
|
||||
if (uiIndex <= 2 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue)
|
||||
{
|
||||
++uiIndex;
|
||||
}
|
||||
else if (uiIndex == 3 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue)
|
||||
{
|
||||
uiIndex = 0;
|
||||
eCurrentState = EState::state_neutral;
|
||||
}
|
||||
else
|
||||
{
|
||||
eCurrentState = EState::state_error;
|
||||
}
|
||||
};
|
||||
for (std::size_t i = 0; i < m_ssString.size(); ++i)
|
||||
{
|
||||
uint8_t uiCurrentByte = m_ssString[i];
|
||||
switch (eCurrentState)
|
||||
{
|
||||
case EState::state_neutral:
|
||||
fnCheckByteInNeutralState(uiCurrentByte);
|
||||
break;
|
||||
case EState::state_two_byte:
|
||||
fnCheckByteInTwoByteState(uiCurrentByte);
|
||||
break;
|
||||
case EState::state_three_byte:
|
||||
fnCheckByteInThreeByteState(uiCurrentByte);
|
||||
break;
|
||||
case EState::state_four_byte:
|
||||
fnCheckByteInFourByteState(uiCurrentByte);
|
||||
break;
|
||||
default:
|
||||
std::stringstream sstreamMessage;
|
||||
sstreamMessage << "Invalid character with byte " << std::hex << m_ssString[i - 1] << std::dec << "("
|
||||
<< static_cast<int32_t>(m_ssString[i - 1]) << ") at index " << i - 1 << "\n";
|
||||
throw XTOMLParseException(sstreamMessage.str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (eCurrentState != EState::state_neutral)
|
||||
{
|
||||
std::stringstream sstreamMessage;
|
||||
sstreamMessage << "Unfinished character at the end of file\n";
|
||||
throw XTOMLParseException(sstreamMessage.str());
|
||||
}
|
||||
}
|
||||
|
||||
std::string CCharacterReaderUTF8::GetNextCharacter()
|
||||
{
|
||||
if (IsEOF())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return {m_ssString, m_nCursor, GetLengthOfNextCharacter()};
|
||||
}
|
||||
|
||||
std::string CCharacterReaderUTF8::GetNextCharacter(std::size_t offset)
|
||||
{
|
||||
return {m_ssString, m_nCursor + offset, GetLengthOfNextCharacter(offset)};
|
||||
}
|
||||
|
||||
size_t CCharacterReaderUTF8::GetLengthOfNextCharacter() const
|
||||
{
|
||||
return GetLengthOfNextCharacter(0);
|
||||
}
|
||||
|
||||
size_t CCharacterReaderUTF8::GetLengthOfNextCharacter(std::size_t offset) const
|
||||
{
|
||||
uint8_t ui = m_ssString[m_nCursor + offset];
|
||||
int32_t ret;
|
||||
if ((ui & m_uiOneByteCheckMask) == m_OneByteCheckValue)
|
||||
{
|
||||
ret = 1;
|
||||
}
|
||||
else if ((ui & m_uiFourByteCheckMask) == m_uiFourByteCheckValue)
|
||||
{
|
||||
ret = 4;
|
||||
}
|
||||
else if ((ui & m_uiThreeByteCheckMask) == m_uiThreeByteCheckValue)
|
||||
{
|
||||
ret = 3;
|
||||
}
|
||||
else if ((ui & m_uiTwoByteCheckMask) == m_uiTwoByteCheckValue)
|
||||
{
|
||||
ret = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::stringstream sstreamMessage;
|
||||
sstreamMessage << "Invalid character sequence with byte " << std::hex << ui << std::dec
|
||||
<< " as start byte\n";
|
||||
throw XTOMLParseException(sstreamMessage.str());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
127
sdv_services/core/toml_parser/character_reader_utf_8.h
Normal file
127
sdv_services/core/toml_parser/character_reader_utf_8.h
Normal file
@@ -0,0 +1,127 @@
|
||||
#ifndef CHARACTER_READER_UTF_8_H
|
||||
#define CHARACTER_READER_UTF_8_H
|
||||
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief Reads a given input string, interprets bytes as UTF-8 and returns UTF-8 characters or strings in order on
|
||||
* demand
|
||||
*/
|
||||
class CCharacterReaderUTF8
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Standard constructor for empty character reader
|
||||
*/
|
||||
CCharacterReaderUTF8();
|
||||
|
||||
/**
|
||||
* @brief Constructs a character reader from a given string
|
||||
* @param[in] rssString UTF-8 input string.
|
||||
* @throw InvalidCharacterException Throws an InvalidCharacterException if the input contains invalid UTF-8 characters
|
||||
* @throw InvalidByteException Throws an InvalidByteException if the input contains for UTF-8 invalid bytes
|
||||
*/
|
||||
CCharacterReaderUTF8(const std::string& rssString);
|
||||
|
||||
/**
|
||||
* @brief Feed the character reader from the given string.
|
||||
* @param[in] rssString UTF-8 input string.
|
||||
*/
|
||||
void Feed(const std::string& rssString);
|
||||
|
||||
/**
|
||||
* @brief Reset the character reader content.
|
||||
*/
|
||||
void Reset();
|
||||
|
||||
/**
|
||||
* @brief Gets the next UTF-8 character without advancing the cursor
|
||||
* @return Returns the next UTF-8 character after the current cursor or an empty string if the cursor is at the
|
||||
* end
|
||||
*/
|
||||
std::string Peek();
|
||||
|
||||
/**
|
||||
* @brief Gets the next n-th UTF-8 character without advancing the cursor
|
||||
* @param[in] n Step size
|
||||
* @return Returns the n-th UTF-8 character after the current cursor or an empty string if n<1 or it would read
|
||||
* after the last character
|
||||
*/
|
||||
std::string Peek(std::size_t n);
|
||||
|
||||
/**
|
||||
* @brief Gets all upcoming UTF-8 characters until a terminating character without advancing the cursor
|
||||
* @param[in] lstCollection A collection of terminating characters
|
||||
* @return Returns a string of UTF-8 characters until (excluding) the first occurrence of one of the given
|
||||
* characters
|
||||
*/
|
||||
std::string PeekUntil(const std::vector<std::string>& lstCollection);
|
||||
|
||||
/**
|
||||
* @brief Gets the next UTF-8 character and advancing the cursor by one
|
||||
* @return Returns the next UTF-8 character after the current cursor or an empty string if the cursor is at the
|
||||
* end
|
||||
*/
|
||||
std::string Consume();
|
||||
|
||||
/**
|
||||
* @brief Gets the next n-th UTF-8 character and advancing the cursor by n
|
||||
* @param[in] n Step size
|
||||
* @return Returns the n-th UTF-8 character after the current cursor or an empty string if n<1 or it would read
|
||||
* after the last character
|
||||
*/
|
||||
std::string Consume(std::size_t n);
|
||||
|
||||
/**
|
||||
* @brief Gets all upcoming UTF-8 characters until a terminating character and advancing the cursor by the number
|
||||
* of characters in the returned string
|
||||
* @param[in] lstCollection A collection of terminating characters
|
||||
* @return Returns a string of UTF-8 characters until excludingg) the first occurrence of one of the given
|
||||
* characters
|
||||
*/
|
||||
std::string ConsumeUntil(const std::vector<std::string>& lstCollection);
|
||||
|
||||
/**
|
||||
* @brief Checks if the cursor is at the end of the data to read
|
||||
* @return Returns true if the cursor is at the end of the readable data, false otherwise
|
||||
*/
|
||||
bool IsEOF() const;
|
||||
|
||||
private:
|
||||
void CheckForInvalidUTF8Bytes() const;
|
||||
|
||||
void CheckForInvalidUTF8Sequences() const;
|
||||
|
||||
std::string GetNextCharacter();
|
||||
|
||||
std::string GetNextCharacter(std::size_t offset);
|
||||
|
||||
size_t GetLengthOfNextCharacter() const;
|
||||
|
||||
size_t GetLengthOfNextCharacter(std::size_t offset) const;
|
||||
|
||||
static const uint8_t m_uiOneByteCheckMask{0b10000000}; //!< Checkmask for 1-Byte UTF-8 characters
|
||||
static const uint8_t m_OneByteCheckValue{0b00000000}; //!< Value of a 1-Byte UTF-8 character
|
||||
//!< after being or-ed with the checkmask
|
||||
static const uint8_t m_uiFollowByteCheckMask{0b11000000}; //!< Checkmask for followbyte of a multi-Byte UTF-8 character
|
||||
static const uint8_t m_uiFollowByteValue{0b10000000}; //!< Value of a followbyte of a multi-Byte UTF-8 character
|
||||
//!< after being or-ed with the checkmask
|
||||
static const uint8_t m_uiTwoByteCheckMask{0b11100000}; //!< Checkmask for startbyte of 2-Byte UTF-8 characters
|
||||
static const uint8_t m_uiTwoByteCheckValue{0b11000000}; //!< Value of a startbyte of a 2-Byte UTF-8 character
|
||||
//!< after being or-ed with the checkmask
|
||||
static const uint8_t m_uiThreeByteCheckMask{0b11110000}; //!< Checkmask for startbyte of 3-Byte UTF-8 characters
|
||||
static const uint8_t m_uiThreeByteCheckValue{0b11100000}; //!< Value of a startbyte of a 3-Byte UTF-8 character
|
||||
//!< after being or-ed with the checkmask
|
||||
static const uint8_t m_uiFourByteCheckMask{0b11111000}; //!< Checkmask for startbyte of 4-Byte UTF-8 characters
|
||||
static const uint8_t m_uiFourByteCheckValue{0b11110000}; //!< Value of a startbyte of a 4-Byte UTF-8 character
|
||||
//!< after being or-ed with the checkmask
|
||||
|
||||
std::string m_ssString;
|
||||
std::size_t m_nDataLength;
|
||||
std::size_t m_nCursor;
|
||||
};
|
||||
|
||||
#endif // CHARACTER_READER_UTF_8_H
|
||||
20
sdv_services/core/toml_parser/exception.h
Normal file
20
sdv_services/core/toml_parser/exception.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef CONFIG_EXCEPTION_H
|
||||
#define CONFIG_EXCEPTION_H
|
||||
|
||||
#include <interfaces/toml.h>
|
||||
|
||||
except XTOMLParseException : public sdv::toml::XTOMLParseException
|
||||
{
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
XTOMLParseException(const std::string& rss) { ssMessage = rss; };
|
||||
|
||||
/**
|
||||
* @brief Return the explanatory string.
|
||||
* @return The descriptive string.
|
||||
*/
|
||||
virtual const char* what() const noexcept override { return ssMessage.c_str(); }
|
||||
};
|
||||
|
||||
#endif // !defined CONFIG_EXCEPTION_H
|
||||
1022
sdv_services/core/toml_parser/lexer_toml.cpp
Normal file
1022
sdv_services/core/toml_parser/lexer_toml.cpp
Normal file
File diff suppressed because it is too large
Load Diff
226
sdv_services/core/toml_parser/lexer_toml.h
Normal file
226
sdv_services/core/toml_parser/lexer_toml.h
Normal file
@@ -0,0 +1,226 @@
|
||||
#ifndef LEXER_TOML_H
|
||||
#define LEXER_TOML_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <stack>
|
||||
#include "character_reader_utf_8.h"
|
||||
|
||||
/**
|
||||
* @brief Tokenizes the output of a character reader in regard of the TOML format and returns tokens in order on demand
|
||||
*/
|
||||
class CLexerTOML
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Enum for all possible token categories
|
||||
*/
|
||||
enum class ETokenCategory : uint8_t
|
||||
{
|
||||
token_none, ///< Default
|
||||
token_syntax_assignment, ///< '='
|
||||
token_syntax_array_open, ///< '[' after '='
|
||||
token_syntax_array_close, ///< ']' after an array open
|
||||
token_syntax_table_open, ///< '['
|
||||
token_syntax_table_close, ///< ']'
|
||||
token_syntax_table_array_open, ///< '[['
|
||||
token_syntax_table_array_close, ///< ']]'
|
||||
token_syntax_inline_table_open, ///< '{'
|
||||
token_syntax_inline_table_close, ///< '}'
|
||||
token_syntax_comma, ///< ','
|
||||
token_syntax_dot, ///< '.'
|
||||
token_syntax_new_line, ///< Line break
|
||||
token_key, ///< Key of a Key-Value-Pair
|
||||
token_string, ///< A string for a Value of a Key-Value-Pair or Array
|
||||
token_integer, ///< An integer for a Value of a Key-Value-Pair or Array
|
||||
token_float, ///< A floating point number for a Value of a Key-Value-Pair or Array
|
||||
token_boolean, ///< A bool for a Value of a Key-Value-Pair or Array
|
||||
token_time_local, ///< Unused for now
|
||||
token_date_time_offset, ///< Unused for now
|
||||
token_date_time_local, ///< Unused for now
|
||||
token_date_local, ///< Unused for now
|
||||
token_eof, ///< End of File Token; may only be at the end of the token array
|
||||
token_error, ///< Error token containing an error message; further lexing is not affected
|
||||
token_empty, ///< Empty token for trying to read out of bounds
|
||||
token_terminated, ///< Terminated token containing an error message; further lexing is terminated
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Contains lexed information for the parser
|
||||
*/
|
||||
struct SToken
|
||||
{
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
SToken() = default;
|
||||
|
||||
/**
|
||||
* @brief Constructs a new Token object with a given category
|
||||
* @param[in] category The initial token category value for the token to be constructed
|
||||
*/
|
||||
explicit SToken(ETokenCategory category) : eCategory(category)
|
||||
{}
|
||||
|
||||
std::string ssContentString; ///< Token string content
|
||||
int64_t iContentInteger{}; ///< Token integer content
|
||||
double dContentFloatingpoint{}; ///< Token floatingpoint content
|
||||
bool bContentBoolean{}; ///< Token boolean content
|
||||
ETokenCategory eCategory{ETokenCategory::token_none}; ///< Token category
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CLexerTOML() = default;
|
||||
|
||||
/**
|
||||
* @brief Constructs a new LexerTOML object with given input data that will be lexed
|
||||
* @param[in] rssString The UTF-8 encoded content of a TOML source
|
||||
*/
|
||||
CLexerTOML(const std::string& rssString);
|
||||
|
||||
/**
|
||||
* @brief Feed the lexer with the given string.
|
||||
* @param[in] rssString UTF-8 input string.
|
||||
*/
|
||||
void Feed(const std::string& rssString);
|
||||
|
||||
/**
|
||||
* @brief Reset the lexer content.
|
||||
*/
|
||||
void Reset();
|
||||
|
||||
/**
|
||||
* @brief Gets the next token after the current cursor position without advancing the cursor
|
||||
* @return Returns the next token after the current cursor position or a End-of-File-Token if there are no
|
||||
* tokens
|
||||
*/
|
||||
SToken Peek() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the next token after the current cursor position and advancing the cursor by one
|
||||
* @return Returns the next token after the current cursor position or a End-of-File-Token if there are no
|
||||
* tokens
|
||||
*/
|
||||
SToken Consume();
|
||||
|
||||
/**
|
||||
* @brief Gets the n-th token after the current cursor without advancing the cursor
|
||||
* @param[in] n Step size
|
||||
* @return Returns the n-th token after the current cursor position or and empty token if n<1 or the end-token
|
||||
* if a step of n would read a position after the end-token
|
||||
*/
|
||||
SToken Peek(int32_t n);
|
||||
|
||||
/**
|
||||
* @brief Gets the n-th token after the current cursor and advancing the cursor by n
|
||||
* @param[in] n Step size
|
||||
* @return Returns the n-th token after the current cursor position or and empty token if n<1 or the end-token
|
||||
* if a step of n would read a position after the end-token
|
||||
*/
|
||||
SToken Consume(int32_t n);
|
||||
|
||||
/**
|
||||
* @brief Checks if the end-token was consumed
|
||||
* @return Returns true if the end-token was consumed by Consume() or Consume(n) or if there are no tokens;
|
||||
* false otherwise
|
||||
*/
|
||||
bool IsEnd() const;
|
||||
|
||||
private:
|
||||
void GenerateTokens();
|
||||
|
||||
bool IsBasicQuotedKey();
|
||||
|
||||
void ReadBasicQuotedKey();
|
||||
|
||||
bool IsLiteralQuotedKey();
|
||||
|
||||
void ReadLiteralQuotedKey();
|
||||
|
||||
bool IsBareKey();
|
||||
|
||||
void ReadBareKey();
|
||||
|
||||
bool IsBasicString();
|
||||
|
||||
void ReadBasicString();
|
||||
|
||||
bool IsBasicMultilineString();
|
||||
|
||||
void ReadBasicMultilineString();
|
||||
|
||||
bool IsLiteralString();
|
||||
|
||||
void ReadLiteralString();
|
||||
|
||||
bool IsLiteralMultilineString();
|
||||
|
||||
void ReadLiteralMultilineString();
|
||||
|
||||
bool IsInteger();
|
||||
|
||||
void ReadInteger();
|
||||
|
||||
bool IsFloat();
|
||||
|
||||
void ReadFloat();
|
||||
|
||||
bool IsBool();
|
||||
|
||||
void ReadBool();
|
||||
|
||||
bool IsWhitespace();
|
||||
|
||||
void ReadWhitespace();
|
||||
|
||||
bool IsSyntaxElement();
|
||||
|
||||
void ReadSyntaxElement();
|
||||
|
||||
bool IsComment();
|
||||
|
||||
void ReadComment();
|
||||
|
||||
void ReadUnknownSequence();
|
||||
|
||||
std::string Unescape();
|
||||
|
||||
std::string Unicode4DigitToUTF8();
|
||||
|
||||
std::string Unicode8DigitToUTF8();
|
||||
|
||||
std::string UnicodeToUTF8(uint8_t numCharacters);
|
||||
|
||||
static uint32_t HexToDecimal(const char character);
|
||||
|
||||
static uint32_t DecimalToDecimal(const char character);
|
||||
|
||||
static uint32_t OctalToDecimal(const char character);
|
||||
|
||||
static uint32_t BinaryToDecimal(const char character);
|
||||
|
||||
CCharacterReaderUTF8 m_reader;
|
||||
std::vector<SToken> m_vecTokens;
|
||||
std::size_t m_nCursor{0};
|
||||
|
||||
/**
|
||||
* @brief Enum for differentiating between keys and values that are potentially indifferent like '"value" =
|
||||
* "value"'
|
||||
*/
|
||||
enum class EExpectation
|
||||
{
|
||||
expect_key, ///< A key is expected over a value
|
||||
expect_value, ///< A value is expected over a key
|
||||
expect_value_once, ///< A value is expected over a key once
|
||||
};
|
||||
std::stack<EExpectation> m_stackExpectations; ///< Tracking of key or value expectations in nested structures
|
||||
// int32_t m_LineCount{0};
|
||||
|
||||
const std::vector<std::string> m_vecKeyDelimiters{
|
||||
"\n", "\t", "\r", " ", "", ".", "=", "]"}; ///< Characters that delimit a key
|
||||
const std::vector<std::string> m_vecValueDelimiters{
|
||||
"\n", "\t", "\r", " ", ",", "", "]", "}"}; ///< Characters that delimit a value
|
||||
};
|
||||
|
||||
#endif // LEXER_TOML_H
|
||||
760
sdv_services/core/toml_parser/parser_node_toml.cpp
Normal file
760
sdv_services/core/toml_parser/parser_node_toml.cpp
Normal file
@@ -0,0 +1,760 @@
|
||||
#include <algorithm>
|
||||
#include "parser_node_toml.h"
|
||||
#include "exception.h"
|
||||
#include <sstream>
|
||||
|
||||
size_t FindFirst(const std::string& rss, const std::string& rssSeparator /*= "."*/)
|
||||
{
|
||||
enum class EType {normal, single_quoted_string, double_quoted_string} eType = EType::normal;
|
||||
size_t nPos = 0;
|
||||
while (nPos < rss.size())
|
||||
{
|
||||
switch (rss[nPos])
|
||||
{
|
||||
case '\'':
|
||||
if (eType == EType::normal)
|
||||
eType = EType::single_quoted_string;
|
||||
else if (eType == EType::single_quoted_string)
|
||||
eType = EType::normal;
|
||||
break;
|
||||
case '\"':
|
||||
if (eType == EType::normal)
|
||||
eType = EType::double_quoted_string;
|
||||
else if (eType == EType::double_quoted_string)
|
||||
eType = EType::normal;
|
||||
break;
|
||||
case '\\':
|
||||
nPos++;
|
||||
break;
|
||||
default:
|
||||
if (eType == EType::normal && rssSeparator.find(rss[nPos]) != std::string::npos)
|
||||
return nPos;
|
||||
break;
|
||||
}
|
||||
nPos++;
|
||||
}
|
||||
return nPos >= rss.size() ? std::string::npos : nPos;
|
||||
}
|
||||
|
||||
size_t FindLast(const std::string& rss, const std::string& rssSeparator /*= "."*/)
|
||||
{
|
||||
enum class EType {normal, single_quoted_string, double_quoted_string} eType = EType::normal;
|
||||
size_t nPos = rss.size();
|
||||
while (nPos)
|
||||
{
|
||||
nPos--;
|
||||
bool bEscaped = nPos && rss[nPos - 1] == '\\';
|
||||
switch (rss[nPos])
|
||||
{
|
||||
case '\'':
|
||||
if (bEscaped)
|
||||
nPos--;
|
||||
else if (eType == EType::normal)
|
||||
eType = EType::single_quoted_string;
|
||||
else if (eType == EType::single_quoted_string)
|
||||
eType = EType::normal;
|
||||
break;
|
||||
case '\"':
|
||||
if (bEscaped)
|
||||
nPos--;
|
||||
else if (eType == EType::normal)
|
||||
eType = EType::double_quoted_string;
|
||||
else if (eType == EType::double_quoted_string)
|
||||
eType = EType::normal;
|
||||
break;
|
||||
default:
|
||||
if (eType == EType::normal && rssSeparator.find(rss[nPos]) != std::string::npos)
|
||||
return nPos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return std::string::npos;
|
||||
}
|
||||
|
||||
bool CompareEqual(const std::string& rss1, const std::string& rss2)
|
||||
{
|
||||
size_t nStart1 = 0, nStop1 = rss1.size();
|
||||
if (rss1.size() && rss1.find_first_of("\"\'") == 0 && rss1.find_last_of("\"\'") == (rss1.size() - 1))
|
||||
{
|
||||
nStart1++;
|
||||
nStop1--;
|
||||
}
|
||||
size_t nStart2 = 0, nStop2 = rss2.size();
|
||||
if (rss2.size() && rss2.find_first_of("\"\'") == 0 && rss2.find_last_of("\"\'") == (rss2.size() - 1))
|
||||
{
|
||||
nStart2++;
|
||||
nStop2--;
|
||||
}
|
||||
|
||||
if (nStop1 - nStart1 != nStop2 - nStart2) return false;
|
||||
for (size_t n = 0; n < (nStop1 - nStart1); n++)
|
||||
{
|
||||
if (rss1[nStart1 + n] != rss2[nStart2 + n])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string EscapeString(const std::string& rssString, const char cQuoteType /*= '\"'*/)
|
||||
{
|
||||
// Iterate through the string
|
||||
std::stringstream sstream;
|
||||
size_t nPos = 0;
|
||||
uint32_t uiUTFChar = 0;
|
||||
while (nPos < rssString.size())
|
||||
{
|
||||
uint8_t uiChar = static_cast<uint8_t>(rssString[nPos]);
|
||||
switch (uiChar)
|
||||
{
|
||||
case '\a': sstream << "\\a"; break;
|
||||
case '\b': sstream << "\\b"; break;
|
||||
case '\f': sstream << "\\f"; break;
|
||||
case '\n': sstream << "\\n"; break;
|
||||
case '\r': sstream << "\\r"; break;
|
||||
case '\t': sstream << "\\t"; break;
|
||||
case '\v': sstream << "\\v"; break;
|
||||
case '\\': sstream << "\\\\"; break;
|
||||
case '\'': if (static_cast<uint8_t>(cQuoteType) == uiChar) sstream << "\\"; sstream << "\'"; break;
|
||||
case '\"': if (static_cast<uint8_t>(cQuoteType) == uiChar) sstream << "\\"; sstream << "\""; break;
|
||||
default:
|
||||
if (uiChar >= 0x20 && uiChar < 0x7f)
|
||||
{
|
||||
// Standard ASCII
|
||||
sstream << static_cast<char>(uiChar);
|
||||
break;
|
||||
}
|
||||
else if (uiChar <= 0x80) // One byte UTF-8
|
||||
uiUTFChar = static_cast<uint32_t>(uiChar);
|
||||
else if (uiChar <= 0xDF) // Two bytes UTF-8
|
||||
{
|
||||
uiUTFChar = static_cast<size_t>(uiChar & 0b00011111) << 6;
|
||||
|
||||
// Expecting the next character to be between 0x80 and 0xBF
|
||||
nPos++;
|
||||
if (nPos >= rssString.size()) break;
|
||||
uiUTFChar |= static_cast<size_t>(rssString[nPos] & 0b00111111);
|
||||
}
|
||||
else if (uiChar <= 0xEF) // Three bytes UTF-8
|
||||
{
|
||||
uiUTFChar = static_cast<size_t>(uiChar & 0b00001111) << 6;
|
||||
|
||||
// Expecting the next character to be between 0x80 and 0xBF
|
||||
nPos++;
|
||||
if (nPos >= rssString.size()) break;
|
||||
uiUTFChar |= static_cast<size_t>(rssString[nPos] & 0b00111111);
|
||||
uiUTFChar <<= 6;
|
||||
|
||||
// Expecting the next character to be between 0x80 and 0xBF
|
||||
nPos++;
|
||||
if (nPos >= rssString.size()) break;
|
||||
uiUTFChar |= static_cast<size_t>(rssString[nPos] & 0b00111111);
|
||||
}
|
||||
else if (uiChar <= 0xF7) // Four bytes UTF-8
|
||||
{
|
||||
uiUTFChar = static_cast<size_t>(uiChar & 0b00000111) << 6;
|
||||
|
||||
// Expecting the next character to be between 0x80 and 0xBF
|
||||
nPos++;
|
||||
if (nPos >= rssString.size()) break;
|
||||
uiUTFChar |= static_cast<size_t>(rssString[nPos] & 0b00111111);
|
||||
uiUTFChar <<= 6;
|
||||
|
||||
// Expecting the next character to be between 0x80 and 0xBF
|
||||
nPos++;
|
||||
if (nPos >= rssString.size()) break;
|
||||
uiUTFChar |= static_cast<size_t>(rssString[nPos] & 0b00111111);
|
||||
uiUTFChar <<= 6;
|
||||
|
||||
// Expecting the next character to be between 0x80 and 0xBF
|
||||
nPos++;
|
||||
if (nPos >= rssString.size()) break;
|
||||
uiUTFChar |= static_cast<size_t>(rssString[nPos] & 0b00111111);
|
||||
}
|
||||
|
||||
// Stream the UTF character
|
||||
if (uiUTFChar <= 0xFFFF)
|
||||
sstream << "\\u" << std::uppercase << std::hex << std::setfill('0') << std::setw(4) << uiUTFChar;
|
||||
else
|
||||
sstream << "\\U" << std::uppercase << std::hex << std::setfill('0') << std::setw(8) << uiUTFChar;
|
||||
break;
|
||||
}
|
||||
nPos++;
|
||||
}
|
||||
return sstream.str();
|
||||
}
|
||||
|
||||
|
||||
CNode::CNode(const std::string& rssName) : m_ssName(rssName)
|
||||
{}
|
||||
|
||||
CNode::~CNode()
|
||||
{}
|
||||
|
||||
sdv::u8string CNode::GetName() const
|
||||
{
|
||||
return m_ssName;
|
||||
}
|
||||
|
||||
sdv::any_t CNode::GetValue() const
|
||||
{
|
||||
return sdv::any_t();
|
||||
}
|
||||
|
||||
sdv::u8string CNode::GetTOML() const
|
||||
{
|
||||
std::string ssLastTable;
|
||||
std::string ssParent;
|
||||
return CreateTOMLText(ssParent, ssLastTable, true, false, true, true);
|
||||
}
|
||||
|
||||
std::shared_ptr<const CArray> CNode::GetArray() const
|
||||
{
|
||||
if (!dynamic_cast<const CArray*>(this)) return {};
|
||||
return std::static_pointer_cast<const CArray>(shared_from_this());
|
||||
}
|
||||
|
||||
std::shared_ptr<CArray> CNode::GetArray()
|
||||
{
|
||||
if (!dynamic_cast<CArray*>(this)) return {};
|
||||
return std::static_pointer_cast<CArray>(shared_from_this());
|
||||
}
|
||||
|
||||
std::shared_ptr<const CTable> CNode::GetTable() const
|
||||
{
|
||||
if (!dynamic_cast<const CTable*>(this)) return {};
|
||||
return std::static_pointer_cast<const CTable>(shared_from_this());
|
||||
}
|
||||
|
||||
std::shared_ptr<CTable> CNode::GetTable()
|
||||
{
|
||||
if (!dynamic_cast<const CTable*>(this)) return {};
|
||||
return std::static_pointer_cast<CTable>(shared_from_this());
|
||||
}
|
||||
|
||||
std::weak_ptr<const CNode> CNode::GetParent() const
|
||||
{
|
||||
return m_ptrParent;
|
||||
}
|
||||
|
||||
void CNode::SetParent(const std::shared_ptr<CNode>& rptrParent)
|
||||
{
|
||||
m_ptrParent = rptrParent;
|
||||
}
|
||||
|
||||
std::shared_ptr<CNode> CNode::Find(const std::string& /*rssPath*/) const
|
||||
{
|
||||
return std::shared_ptr<CNode>();
|
||||
}
|
||||
|
||||
std::shared_ptr<CNode> CNode::GetDirect(const std::string& /*rssPath*/) const
|
||||
{
|
||||
// The CNode implementation doesn't have any children. Therefore there is nothing to get.
|
||||
return std::shared_ptr<CNode>();
|
||||
}
|
||||
|
||||
std::string CNode::CreateTOMLText(const std::string& rssParent /*= std::string()*/) const
|
||||
{
|
||||
std::string ssLastTable;
|
||||
return CreateTOMLText(rssParent, ssLastTable);
|
||||
}
|
||||
|
||||
void CNode::Add(const std::string& rssPath, const std::shared_ptr<CNode>& /*rptrNode*/, bool /*bDefinedExplicitly = true*/)
|
||||
{
|
||||
throw XTOMLParseException(("Not allowed to add '" + rssPath + "'; parent node is final").c_str());
|
||||
}
|
||||
|
||||
CBooleanNode::CBooleanNode(const std::string& rssName, bool bVal) : CNode(rssName), m_bVal(bVal)
|
||||
{}
|
||||
|
||||
sdv::toml::ENodeType CBooleanNode::GetType() const
|
||||
{
|
||||
return sdv::toml::ENodeType::node_boolean;
|
||||
}
|
||||
|
||||
sdv::any_t CBooleanNode::GetValue() const
|
||||
{
|
||||
return sdv::any_t(m_bVal);
|
||||
}
|
||||
|
||||
std::string CBooleanNode::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool /*bRoot*/) const
|
||||
{
|
||||
std::stringstream sstreamEntry;
|
||||
|
||||
// Do we need to start a table?
|
||||
if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable)
|
||||
{
|
||||
sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl;
|
||||
rssLastPrintedTable = rssParent;
|
||||
}
|
||||
|
||||
if (bEmbedded && !bFirst) // 2nd or higher array entry
|
||||
sstreamEntry << ", ";
|
||||
if (!bEmbedded || bAssignment) // Not an array entry
|
||||
sstreamEntry << GetName() << " = ";
|
||||
sstreamEntry << (m_bVal ? "true" : "false");
|
||||
if (!bEmbedded) // Not an array entry
|
||||
sstreamEntry << std::endl;
|
||||
|
||||
return sstreamEntry.str();
|
||||
}
|
||||
|
||||
CIntegerNode::CIntegerNode(const std::string& rssName, int64_t iVal) : CNode(rssName), m_iVal(iVal)
|
||||
{}
|
||||
|
||||
sdv::toml::ENodeType CIntegerNode::GetType() const
|
||||
{
|
||||
return sdv::toml::ENodeType::node_integer;
|
||||
}
|
||||
|
||||
sdv::any_t CIntegerNode::GetValue() const
|
||||
{
|
||||
return sdv::any_t(m_iVal);
|
||||
}
|
||||
|
||||
std::string CIntegerNode::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool /*bRoot*/) const
|
||||
{
|
||||
std::stringstream sstreamEntry;
|
||||
|
||||
// Do we need to start a table?
|
||||
if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable)
|
||||
{
|
||||
sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl;
|
||||
rssLastPrintedTable = rssParent;
|
||||
}
|
||||
|
||||
if (bEmbedded && !bFirst) // 2nd or higher array entry
|
||||
sstreamEntry << ", ";
|
||||
if (!bEmbedded || bAssignment) // Not an array entry
|
||||
sstreamEntry << GetName() << " = ";
|
||||
sstreamEntry << m_iVal;
|
||||
if (!bEmbedded) // Not an array entry
|
||||
sstreamEntry << std::endl;
|
||||
|
||||
return sstreamEntry.str();
|
||||
}
|
||||
|
||||
CFloatingPointNode::CFloatingPointNode(const std::string& rssName, double dVal) : CNode(rssName), m_dVal(dVal)
|
||||
{}
|
||||
|
||||
sdv::toml::ENodeType CFloatingPointNode::GetType() const
|
||||
{
|
||||
return sdv::toml::ENodeType::node_floating_point;
|
||||
}
|
||||
|
||||
sdv::any_t CFloatingPointNode::GetValue() const
|
||||
{
|
||||
return sdv::any_t(m_dVal);
|
||||
}
|
||||
|
||||
std::string CFloatingPointNode::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst,
|
||||
bool bEmbedded, bool bAssignment, bool /*bRoot*/) const
|
||||
{
|
||||
std::stringstream sstreamEntry;
|
||||
|
||||
// Do we need to start a table?
|
||||
if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable)
|
||||
{
|
||||
sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl;
|
||||
rssLastPrintedTable = rssParent;
|
||||
}
|
||||
|
||||
if (bEmbedded && !bFirst) // 2nd or higher array entry
|
||||
sstreamEntry << ", ";
|
||||
if (!bEmbedded || bAssignment) // Not an array entry
|
||||
sstreamEntry << GetName() << " = ";
|
||||
sstreamEntry << std::setprecision(15) << std::defaultfloat << m_dVal;
|
||||
if (!bEmbedded) // Not an array entry
|
||||
sstreamEntry << std::endl;
|
||||
|
||||
return sstreamEntry.str();
|
||||
}
|
||||
|
||||
CStringNode::CStringNode(const std::string& rssName, const std::string& rssVal) : CNode(rssName), m_ssVal(rssVal)
|
||||
{}
|
||||
|
||||
sdv::toml::ENodeType CStringNode::GetType() const
|
||||
{
|
||||
return sdv::toml::ENodeType::node_string;
|
||||
}
|
||||
|
||||
sdv::any_t CStringNode::GetValue() const
|
||||
{
|
||||
return sdv::any_t(m_ssVal);
|
||||
}
|
||||
|
||||
std::string CStringNode::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool /*bRoot*/) const
|
||||
{
|
||||
std::stringstream sstreamEntry;
|
||||
|
||||
// Do we need to start a table?
|
||||
if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable)
|
||||
{
|
||||
sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl;
|
||||
rssLastPrintedTable = rssParent;
|
||||
}
|
||||
|
||||
if (bEmbedded && !bFirst) // 2nd or higher array entry
|
||||
sstreamEntry << ", ";
|
||||
if (!bEmbedded || bAssignment) // Not an array entry
|
||||
sstreamEntry << GetName() << " = ";
|
||||
sstreamEntry << "\"" << EscapeString(m_ssVal) << "\"";
|
||||
if (!bEmbedded) // Not an array entry
|
||||
sstreamEntry << std::endl;
|
||||
|
||||
return sstreamEntry.str();
|
||||
}
|
||||
|
||||
CNodeCollection::CNodeCollection(const std::string& rssName) : CNode(rssName)
|
||||
{}
|
||||
|
||||
uint32_t CNodeCollection::GetCount() const
|
||||
{
|
||||
return static_cast<uint32_t>(m_vecContent.size());
|
||||
}
|
||||
|
||||
sdv::IInterfaceAccess* CNodeCollection::GetNode(/*in*/ uint32_t uiIndex) const
|
||||
{
|
||||
auto ptrNode = Get(uiIndex);
|
||||
return static_cast<sdv::IInterfaceAccess*>(ptrNode.get());
|
||||
}
|
||||
|
||||
std::shared_ptr<CNode> CNodeCollection::Get(uint32_t uiIndex) const
|
||||
{
|
||||
if (static_cast<size_t>(uiIndex) >= m_vecContent.size()) return nullptr;
|
||||
|
||||
return m_vecContent[uiIndex];
|
||||
}
|
||||
|
||||
sdv::IInterfaceAccess* CNodeCollection::GetNodeDirect(/*in*/ const sdv::u8string& ssPath) const
|
||||
{
|
||||
auto ptrNode = GetDirect(ssPath);
|
||||
return static_cast<sdv::IInterfaceAccess*>(ptrNode.get());
|
||||
}
|
||||
|
||||
bool CNodeCollection::AddElement(const std::shared_ptr<CNode>& rptrNode, bool bUnique /*= false*/)
|
||||
{
|
||||
if (!rptrNode) return false;
|
||||
if (bUnique && std::find_if(m_vecContent.begin(), m_vecContent.end(), [&](const std::shared_ptr<CNode>& rptrNodeEntry)
|
||||
{
|
||||
return CompareEqual(rptrNodeEntry->GetName(), rptrNode->GetName());
|
||||
}) != m_vecContent.end()) return false;
|
||||
m_vecContent.push_back(rptrNode);
|
||||
return true;
|
||||
}
|
||||
|
||||
CTable::CTable(const std::string& rssName) : CNodeCollection(rssName)
|
||||
{}
|
||||
|
||||
sdv::toml::ENodeType CTable::GetType() const
|
||||
{
|
||||
return sdv::toml::ENodeType::node_table;
|
||||
}
|
||||
|
||||
std::shared_ptr<CNode> CTable::GetDirect(const std::string& rssPath) const
|
||||
{
|
||||
size_t nSeparator = FindFirst(rssPath, ".[");
|
||||
std::string ssKey = rssPath.substr(0, nSeparator);
|
||||
std::shared_ptr<CNode> ptrNode;
|
||||
for (uint32_t uiIndex = 0; !ptrNode && uiIndex < GetCount(); uiIndex++)
|
||||
{
|
||||
std::shared_ptr<CNode> ptrNodeEntry = Get(uiIndex);
|
||||
if (!ptrNodeEntry) continue;
|
||||
if (CompareEqual(ptrNodeEntry->GetName(), ssKey)) ptrNode = ptrNodeEntry;
|
||||
}
|
||||
if (!ptrNode) return ptrNode; // Not found
|
||||
|
||||
// Done?
|
||||
if (nSeparator == std::string::npos) return ptrNode;
|
||||
|
||||
// There is more...
|
||||
if (rssPath[nSeparator] == '.') nSeparator++; // Skip dot
|
||||
return ptrNode->GetDirect(rssPath.substr(nSeparator));
|
||||
}
|
||||
|
||||
std::string CTable::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool bRoot) const
|
||||
{
|
||||
// Create the full name
|
||||
std::string ssFullName = rssParent;
|
||||
if (!GetName().empty() && !bRoot)
|
||||
{
|
||||
if (!ssFullName.empty()) ssFullName += ".";
|
||||
ssFullName += GetName();
|
||||
}
|
||||
|
||||
// Stream the table
|
||||
std::stringstream sstreamEntry;
|
||||
if (bEmbedded && !bFirst) // 2nd or higher array entry
|
||||
sstreamEntry << ", ";
|
||||
if (bEmbedded) // Embedded table in an array
|
||||
sstreamEntry << "{";
|
||||
for (uint32_t uiIndex = 0; uiIndex < GetCount(); uiIndex++)
|
||||
{
|
||||
std::shared_ptr<CNode> ptrNode = Get(uiIndex);
|
||||
if (!ptrNode) continue;
|
||||
sstreamEntry << ptrNode->CreateTOMLText(ssFullName, rssLastPrintedTable, uiIndex == 0, bEmbedded);
|
||||
}
|
||||
if (bEmbedded) // Embedded table in an array
|
||||
sstreamEntry << "}";
|
||||
if (!bEmbedded && !bAssignment && !GetName().empty())
|
||||
sstreamEntry << std::endl;
|
||||
|
||||
return sstreamEntry.str();
|
||||
}
|
||||
|
||||
void CTable::Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly)
|
||||
{
|
||||
if (!m_bOpenToAddChildren)
|
||||
throw XTOMLParseException(("Not allowed to add '" + rssPath + "'; parent node is final").c_str());
|
||||
|
||||
size_t nDotPos = FindFirst(rssPath);
|
||||
std::string ssFirst = rssPath.substr(0, nDotPos);
|
||||
std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1);
|
||||
auto ptrParent = Find(ssFirst);
|
||||
|
||||
// Element does not already exist at given path
|
||||
if (!ptrParent)
|
||||
{
|
||||
// Add the new element as a direct child
|
||||
if (nDotPos == std::string::npos)
|
||||
{
|
||||
rptrNode->SetParent(shared_from_this());
|
||||
GetTable()->AddElement(rptrNode);
|
||||
return;
|
||||
}
|
||||
// Add the new element as a descendant further down
|
||||
auto ptrIntermediateTable = std::make_shared<CNormalTable>(ssFirst);
|
||||
ptrIntermediateTable->SetParent(shared_from_this());
|
||||
ptrIntermediateTable->m_bDefinedExplicitly = bDefinedExplicitly;
|
||||
GetTable()->AddElement(ptrIntermediateTable);
|
||||
static_cast<CNode*>(ptrIntermediateTable.get())->Add(ssSecond, rptrNode, bDefinedExplicitly);
|
||||
return;
|
||||
}
|
||||
if (dynamic_cast<CTableArray*>(ptrParent.get()) && nDotPos == std::string::npos)
|
||||
{
|
||||
ptrParent->Add(ssFirst, rptrNode, bDefinedExplicitly);
|
||||
return;
|
||||
}
|
||||
// Element already exists but would be inserted as a direct child
|
||||
if (nDotPos == std::string::npos)
|
||||
{
|
||||
// Make an already implicitly defined table explicitly defined
|
||||
if (ptrParent->GetType() == rptrNode->GetType() && dynamic_cast<CNodeCollection*>(ptrParent.get()) &&
|
||||
!static_cast<CNodeCollection*>(ptrParent.get())->m_bDefinedExplicitly)
|
||||
{
|
||||
static_cast<CNodeCollection*>(ptrParent.get())->m_bDefinedExplicitly = true;
|
||||
return;
|
||||
}
|
||||
throw XTOMLParseException(("Name '" + ssFirst + "' already exists").c_str());
|
||||
}
|
||||
// Element already exists and new element would be added as descendant
|
||||
ptrParent->Add(ssSecond, rptrNode, bDefinedExplicitly);
|
||||
}
|
||||
|
||||
std::shared_ptr<CNode> CTable::Find(const std::string& rssPath) const
|
||||
{
|
||||
size_t nDotPos = FindFirst(rssPath);
|
||||
std::string ssFirst = rssPath.substr(0, nDotPos);
|
||||
std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1);
|
||||
|
||||
for (uint32_t uiIndex = 0; uiIndex < GetCount(); uiIndex++)
|
||||
{
|
||||
std::shared_ptr<CNode> ptrNode;
|
||||
ptrNode = Get(uiIndex);
|
||||
if (!ptrNode) continue;
|
||||
if (CompareEqual(ptrNode->GetName(), ssFirst))
|
||||
{
|
||||
if (nDotPos == std::string::npos)
|
||||
return ptrNode;
|
||||
return ptrNode->Find(ssSecond);
|
||||
}
|
||||
}
|
||||
|
||||
// No node found...
|
||||
return std::shared_ptr<CNode>();
|
||||
}
|
||||
|
||||
CArray::CArray(const std::string& rssName) : CNodeCollection(rssName)
|
||||
{}
|
||||
|
||||
sdv::toml::ENodeType CArray::GetType() const
|
||||
{
|
||||
return sdv::toml::ENodeType::node_array;
|
||||
}
|
||||
|
||||
std::shared_ptr<CNode> CArray::GetDirect(const std::string& rssPath) const
|
||||
{
|
||||
size_t nIndexBegin = FindFirst(rssPath, "[");
|
||||
if (nIndexBegin == std::string::npos) return std::shared_ptr<CNode>(); // Unexpected
|
||||
|
||||
size_t nIndexEnd = rssPath.find_first_not_of("0123456789", nIndexBegin + 1);
|
||||
if (nIndexEnd == std::string::npos) return std::shared_ptr<CNode>(); // Unexpected
|
||||
if (rssPath[nIndexEnd] != ']') return std::shared_ptr<CNode>(); // Unexpected
|
||||
std::string ssIndex = rssPath.substr(nIndexBegin + 1, nIndexEnd - nIndexBegin - 1);
|
||||
if (ssIndex.empty()) return std::shared_ptr<CNode>(); // Unexpected
|
||||
uint32_t uiIndex = std::atol(ssIndex.c_str());
|
||||
nIndexEnd++;
|
||||
|
||||
// Get the node
|
||||
if (uiIndex >= GetCount()) return std::shared_ptr<CNode>(); // Not found
|
||||
std::shared_ptr<CNode> ptrNode = Get(uiIndex);
|
||||
|
||||
// Done?
|
||||
if (nIndexEnd == rssPath.size()) return ptrNode;
|
||||
|
||||
// Expecting a dot?
|
||||
size_t nSeparator = nIndexEnd;
|
||||
if (rssPath[nSeparator] == '.') nSeparator++; // Skip dot
|
||||
return ptrNode->GetDirect(rssPath.substr(nSeparator));
|
||||
}
|
||||
|
||||
std::string CArray::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool /*bRoot*/) const
|
||||
{
|
||||
std::stringstream sstreamEntry;
|
||||
|
||||
// Do we need to start a table?
|
||||
if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable)
|
||||
{
|
||||
sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl;
|
||||
rssLastPrintedTable = rssParent;
|
||||
}
|
||||
|
||||
// Stream the array
|
||||
if (bEmbedded && !bFirst) // 2nd or higher array entry
|
||||
sstreamEntry << ", ";
|
||||
if (!bEmbedded || bAssignment) // Not an array entry
|
||||
sstreamEntry << GetName() << " = ";
|
||||
sstreamEntry << "[";
|
||||
for (uint32_t ui = 0; ui < GetCount(); ui++)
|
||||
sstreamEntry << Get(ui)->CreateTOMLText(rssParent, rssLastPrintedTable, ui == 0, true, false);
|
||||
sstreamEntry << "]";
|
||||
if (!bEmbedded) // Not an array entry
|
||||
sstreamEntry << std::endl;
|
||||
|
||||
return sstreamEntry.str();
|
||||
}
|
||||
|
||||
void CArray::Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly)
|
||||
{
|
||||
size_t nDotPos = FindFirst(rssPath);
|
||||
std::string ssFirst = rssPath.substr(0, nDotPos);
|
||||
std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1);
|
||||
|
||||
// Add new element to array
|
||||
if (nDotPos == std::string::npos)
|
||||
{
|
||||
GetArray()->AddElement(rptrNode);
|
||||
return;
|
||||
}
|
||||
// Add new element to subelement of array
|
||||
if (std::any_of(ssFirst.begin(), ssFirst.end(), [](char digit) { return (digit < '0') || (digit > '9'); }))
|
||||
{
|
||||
throw XTOMLParseException(("Invalid array access subscript '" + ssFirst + "'").c_str());
|
||||
}
|
||||
uint32_t uiIndex = std::stoi(ssFirst);
|
||||
if (uiIndex >= GetArray()->GetCount())
|
||||
{
|
||||
// This indicates an array within an arrays. Add the intermediate array
|
||||
auto ptrIntermediateArray = std::make_shared<CNormalArray>(ssFirst);
|
||||
ptrIntermediateArray->SetParent(shared_from_this());
|
||||
ptrIntermediateArray->m_bDefinedExplicitly = bDefinedExplicitly;
|
||||
GetArray()->AddElement(ptrIntermediateArray);
|
||||
static_cast<CNode*>(ptrIntermediateArray.get())->Add(ssSecond, rptrNode, bDefinedExplicitly);
|
||||
//throw XTOMLParseException(("Invalid array access index '" + ssFirst + "'; out of bounds").c_str());
|
||||
}
|
||||
GetArray()->Get(uiIndex)->Add(ssSecond, rptrNode, bDefinedExplicitly);
|
||||
}
|
||||
|
||||
std::shared_ptr<CNode> CArray::Find(const std::string& rssPath) const
|
||||
{
|
||||
size_t nDotPos = FindFirst(rssPath);
|
||||
std::string ssFirst = rssPath.substr(0, nDotPos);
|
||||
std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1);
|
||||
|
||||
if (ssFirst.empty())
|
||||
throw XTOMLParseException("Missing array subscript");
|
||||
|
||||
if (std::any_of(ssFirst.begin(), ssFirst.end(), [](char digit) { return (digit < '0') || (digit > '9'); }))
|
||||
throw XTOMLParseException(("Invalid array access subscript '" + ssFirst + "'").c_str());
|
||||
uint32_t uiIndex = std::stoi(ssFirst);
|
||||
if (GetArray()->GetCount() <= uiIndex)
|
||||
throw XTOMLParseException(
|
||||
("Invalid array access index '" + ssFirst + "'; out of bounds").c_str());
|
||||
if (nDotPos == std::string::npos)
|
||||
return GetArray()->Get(uiIndex);
|
||||
return GetArray()->Get(uiIndex)->Find(ssSecond);
|
||||
}
|
||||
|
||||
std::string CTableArray::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool /*bFirst*/, bool /*bEmbedded*/,
|
||||
bool /*bAssignment*/, bool bRoot) const
|
||||
{
|
||||
// Create the full name
|
||||
std::string ssFullName = rssParent;
|
||||
if (!ssFullName.empty() && !bRoot) ssFullName += ".";
|
||||
ssFullName += GetName();
|
||||
|
||||
// Stream the array
|
||||
std::stringstream sstreamEntry;
|
||||
for (uint32_t ui = 0; ui < GetCount(); ui++)
|
||||
{
|
||||
sstreamEntry << std::endl << "[[" << ssFullName << "]]" << std::endl;
|
||||
rssLastPrintedTable = ssFullName;
|
||||
sstreamEntry << Get(ui)->CreateTOMLText(ssFullName, rssLastPrintedTable, ui == 0, false, false);
|
||||
}
|
||||
//sstreamEntry << std::endl;
|
||||
|
||||
return sstreamEntry.str();
|
||||
}
|
||||
|
||||
void CTableArray::Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly)
|
||||
{
|
||||
//size_t nDotPos = FindFirst(rssPath);
|
||||
//std::string ssFirst = rssPath.substr(0, nDotPos);
|
||||
//std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1);
|
||||
|
||||
uint32_t uiSize = GetArray()->GetCount();
|
||||
if (uiSize == 0)
|
||||
{
|
||||
throw XTOMLParseException("Trying to access table in an empty array of tables");
|
||||
}
|
||||
GetArray()->Get(uiSize - 1)->Add(rssPath, rptrNode, bDefinedExplicitly);
|
||||
}
|
||||
|
||||
std::shared_ptr<CNode> CTableArray::Find(const std::string& rssPath) const
|
||||
{
|
||||
//size_t nDotPos = FindFirst(rssPath);
|
||||
//std::string ssFirst = rssPath.substr(0, nDotPos);
|
||||
//std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1);
|
||||
|
||||
if (!GetArray()->GetCount())
|
||||
throw XTOMLParseException(("Trying to access empty table array; " + rssPath).c_str());
|
||||
return GetArray()->Get(GetArray()->GetCount() - 1)->Find(rssPath);
|
||||
}
|
||||
|
||||
std::string CRootTable::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool /*bFirst*/,
|
||||
bool /*bEmbedded*/, bool /*bAssignment*/, bool /*bRoot*/) const
|
||||
{
|
||||
// Create the full name
|
||||
std::string ssFullName = rssParent;
|
||||
|
||||
// Stream the table
|
||||
std::stringstream sstreamEntry;
|
||||
for (uint32_t uiIndex = 0; uiIndex < GetCount(); uiIndex++)
|
||||
{
|
||||
std::shared_ptr<CNode> ptrNode = Get(uiIndex);
|
||||
if (!ptrNode) continue;
|
||||
sstreamEntry << ptrNode->CreateTOMLText(ssFullName, rssLastPrintedTable);
|
||||
}
|
||||
|
||||
sstreamEntry << std::endl;
|
||||
|
||||
// Skip whitespace at the beginning
|
||||
std::string ssRet = sstreamEntry.str();
|
||||
size_t nStart = ssRet.find_first_not_of(" \t\f\r\n\v");
|
||||
if (nStart == std::string::npos) return std::string();
|
||||
return ssRet.substr(nStart);
|
||||
}
|
||||
718
sdv_services/core/toml_parser/parser_node_toml.h
Normal file
718
sdv_services/core/toml_parser/parser_node_toml.h
Normal file
@@ -0,0 +1,718 @@
|
||||
#ifndef PARSER_NODE_TOML_H
|
||||
#define PARSER_NODE_TOML_H
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <interfaces/toml.h>
|
||||
#include <support/interface_ptr.h>
|
||||
|
||||
// Forward declaration
|
||||
class CArray;
|
||||
class CTable;
|
||||
|
||||
/**
|
||||
* @brief Find the first separator character. Do not include string content (single/or double quoted) and escape characters.
|
||||
* @param[in] rss Reference to the string.
|
||||
* @param[in] rssSeparator One of the characters to find in the string. Must not be an empty string!
|
||||
* @return The position of the first separator character or std::string::npos when none has found.
|
||||
*/
|
||||
size_t FindFirst(const std::string& rss, const std::string& rssSeparator = ".");
|
||||
|
||||
/**
|
||||
* @brief Find the last separator character. Do not include string content (single/or double quoted) and escape characters.
|
||||
* @param[in] rss Reference to the string.
|
||||
* @param[in] rssSeparator One of the characters to find in the string. Must not be an empty string!
|
||||
* @return The position of the last separator character or std::string::npos when none has found.
|
||||
*/
|
||||
size_t FindLast(const std::string& rss, const std::string& rssSeparator = ".");
|
||||
|
||||
/**
|
||||
* @brief Compare both string ignoring the quotes at the first position and last position.
|
||||
* @param[in] rss1 Reference to the first string.
|
||||
* @param[in] rss2 Reference to the second string.
|
||||
* @return The comparison result.
|
||||
*/
|
||||
bool CompareEqual(const std::string& rss1, const std::string& rss2);
|
||||
|
||||
/**
|
||||
* @brief Escape a string using escape characters and UTF values.
|
||||
*/
|
||||
std::string EscapeString(const std::string& rssString, const char cQuoteType = '\"');
|
||||
|
||||
/**
|
||||
* @brief Node to build up the parse tree
|
||||
*/
|
||||
class CNode : public std::enable_shared_from_this<CNode>, public sdv::IInterfaceAccess, public sdv::toml::INodeInfo
|
||||
{
|
||||
protected:
|
||||
/**
|
||||
* @brief Constructs a new node object representing a table or an array.
|
||||
* @param[in] rssName Reference to the name of the node.
|
||||
*/
|
||||
CNode(const std::string& rssName);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Deleted since Nodes should only be handled via smart-pointer
|
||||
* @{
|
||||
*/
|
||||
CNode(const CNode&) = delete;
|
||||
CNode& operator=(const CNode&) = delete;
|
||||
CNode(const CNode&&) = delete;
|
||||
CNode& operator=(const CNode&&) = delete;
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Destroy the node object
|
||||
*/
|
||||
~CNode();
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::toml::INodeInfo)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Get the node name. Overload of sdv::toml::INodeInfo::GetName.
|
||||
* @return String containing the name of the node.
|
||||
*/
|
||||
virtual sdv::u8string GetName() const override;
|
||||
|
||||
/**
|
||||
* @brief The node value. Overload of sdv::toml::INodeInfo::GetValue.
|
||||
* @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function
|
||||
* returns empty.
|
||||
*/
|
||||
virtual sdv::any_t GetValue() const override;
|
||||
|
||||
/**
|
||||
* @brief The node value. Overload of sdv::toml::INodeInfo::GetTOML.
|
||||
* @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function
|
||||
* returns empty.
|
||||
*/
|
||||
virtual sdv::u8string GetTOML() const override;
|
||||
|
||||
/**
|
||||
* @brief Gets the array value of a node
|
||||
* @return Returns a shared pointer of the array value stored in the node if the stored type is array
|
||||
*/
|
||||
std::shared_ptr<const CArray> GetArray() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the array value of a node
|
||||
* @return Returns a shared pointer of the array value stored in the node if the stored type is array
|
||||
*/
|
||||
std::shared_ptr<CArray> GetArray();
|
||||
|
||||
/**
|
||||
* @brief Gets the table value of a node
|
||||
* @return Returns a shared pointer of the table value stored in the node if the stored type is table
|
||||
*/
|
||||
std::shared_ptr<const CTable> GetTable() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the table value of a node
|
||||
* @return Returns a shared pointer of the table value stored in the node if the stored type is table
|
||||
*/
|
||||
std::shared_ptr<CTable> GetTable();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Gets the Parent Node
|
||||
* @return Returns the parent Node
|
||||
* @attention Beware of expiring pointers
|
||||
*/
|
||||
std::weak_ptr<const CNode> GetParent() const;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Set the parent node.
|
||||
* @param[in] rptrParent Reference to the node to assign to this node as a parent.
|
||||
*/
|
||||
void SetParent(const std::shared_ptr<CNode>& rptrParent);
|
||||
|
||||
/**
|
||||
* @brief Accesses a node by its key in the parse tree.
|
||||
* @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name.
|
||||
* E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and traversed
|
||||
* by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'. These access
|
||||
* conventions can also be chained like 'table.array[2][1].subtable.integerElement'.
|
||||
* @attention Array indexing starts with 0!
|
||||
* @param[in] rssPath The path of the node to searched for.
|
||||
* @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found.
|
||||
*/
|
||||
virtual std::shared_ptr<CNode> GetDirect(const std::string& rssPath) const;
|
||||
|
||||
/**
|
||||
* @brief Create the TOML text based on the content using an optional parent node.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
std::string CreateTOMLText(const std::string& rssParent = std::string()) const;
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
|
||||
* different table was printed in between.
|
||||
* @param[in] bFirst When set, this is the first entry in an array or table.
|
||||
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
|
||||
* @param[in] bAssignment When set, this is a table assignment.
|
||||
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst = true,
|
||||
bool bEmbedded = false, bool bAssignment = true, bool bRoot = false) const = 0;
|
||||
|
||||
private:
|
||||
std::weak_ptr<CNode> m_ptrParent; ///< Weak pointer to the parent node (if existing).
|
||||
std::string m_ssName; ///< Name of the node.
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Searches the subtree of the node for a node at the given location using the provided path.
|
||||
* @remarks The path elements of arrays and tables are separated by a dot.
|
||||
* @param[in] rssPath Reference to the string containing the path of the node to find.
|
||||
* @return Returns a shared pointer to the wanted Node if it is found or an error-Node if it is not
|
||||
*/
|
||||
virtual std::shared_ptr<CNode> Find(const std::string& rssPath) const;
|
||||
|
||||
/**
|
||||
* @brief Adds a given node to a given path in the tree
|
||||
* @remarks The path elements of arrays and tables are separated by a dot.
|
||||
* @param[in] rssPath Reference to the string containing the path in the tree of the location to the new node to be inserted.
|
||||
* @param[in] rptrNode Reference to the smart pointer containing the new node to be added.
|
||||
* @param[in] bDefinedExplicitly If a table that is created to create the path of the node to be added is defined explicitly.
|
||||
* @throw XInvalidAccessException Throws an XInvalidAccessException if a ancestor node is not open to add children.
|
||||
* @throw XDuplicateNameException Throws a XDuplicateNameException if a node with the same path as the node to
|
||||
* be added is already defined explicitly
|
||||
*/
|
||||
virtual void Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly = true);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Boolean value node.
|
||||
*/
|
||||
class CBooleanNode : public CNode
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the string containing the name of the node.
|
||||
* @param[in] bVal The value to assign.
|
||||
*/
|
||||
CBooleanNode(const std::string& rssName, bool bVal);
|
||||
|
||||
/**
|
||||
* @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType.
|
||||
* @return Type of the node.
|
||||
*/
|
||||
virtual sdv::toml::ENodeType GetType() const override;
|
||||
|
||||
/**
|
||||
* @brief The node value. Overload of sdv::toml::INodeInfo::GetValue.
|
||||
* @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function
|
||||
* returns empty.
|
||||
*/
|
||||
virtual sdv::any_t GetValue() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
|
||||
* different table was printed in between.
|
||||
* @param[in] bFirst When set, this is the first entry in an array or table.
|
||||
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
|
||||
* @param[in] bAssignment When set, this is a table assignment.
|
||||
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool bRoot) const override;
|
||||
|
||||
private:
|
||||
bool m_bVal; ///< Value in case of boolean node.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Integer value node.
|
||||
*/
|
||||
class CIntegerNode : public CNode
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the string containing the name of the node.
|
||||
* @param[in] iVal The value to assign.
|
||||
*/
|
||||
CIntegerNode(const std::string& rssName, int64_t iVal);
|
||||
|
||||
/**
|
||||
* @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType.
|
||||
* @return Type of the node.
|
||||
*/
|
||||
virtual sdv::toml::ENodeType GetType() const override;
|
||||
|
||||
/**
|
||||
* @brief The node value. Overload of sdv::toml::INodeInfo::GetValue.
|
||||
* @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function
|
||||
* returns empty.
|
||||
*/
|
||||
virtual sdv::any_t GetValue() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
|
||||
* different table was printed in between.
|
||||
* @param[in] bFirst When set, this is the first entry in an array or table.
|
||||
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
|
||||
* @param[in] bAssignment When set, this is a table assignment.
|
||||
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool bRoot) const override;
|
||||
|
||||
private:
|
||||
int64_t m_iVal; ///< Value in case of integer node.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Floating point value node.
|
||||
*/
|
||||
class CFloatingPointNode : public CNode
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the string containing the name of the node.
|
||||
* @param[in] dVal The value to assign.
|
||||
*/
|
||||
CFloatingPointNode(const std::string& rssName, double dVal);
|
||||
|
||||
/**
|
||||
* @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType.
|
||||
* @return Type of the node.
|
||||
*/
|
||||
virtual sdv::toml::ENodeType GetType() const override;
|
||||
|
||||
/**
|
||||
* @brief The node value. Overload of sdv::toml::INodeInfo::GetValue.
|
||||
* @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function
|
||||
* returns empty.
|
||||
*/
|
||||
virtual sdv::any_t GetValue() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
|
||||
* different table was printed in between.
|
||||
* @param[in] bFirst When set, this is the first entry in an array or table.
|
||||
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
|
||||
* @param[in] bAssignment When set, this is a table assignment.
|
||||
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool bRoot) const override;
|
||||
|
||||
private:
|
||||
double m_dVal; ///< Value in case of floating point node.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief String value node.
|
||||
*/
|
||||
class CStringNode : public CNode
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the string containing the name of the node.
|
||||
* @param[in] rssVal The value to assign.
|
||||
*/
|
||||
CStringNode(const std::string& rssName, const std::string& rssVal);
|
||||
|
||||
/**
|
||||
* @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType.
|
||||
* @return Type of the node.
|
||||
*/
|
||||
virtual sdv::toml::ENodeType GetType() const override;
|
||||
|
||||
/**
|
||||
* @brief The node value. Overload of sdv::toml::INodeInfo::GetValue.
|
||||
* @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function
|
||||
* returns empty.
|
||||
*/
|
||||
virtual sdv::any_t GetValue() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
|
||||
* different table was printed in between.
|
||||
* @param[in] bFirst When set, this is the first entry in an array or table.
|
||||
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
|
||||
* @param[in] bAssignment When set, this is a table assignment.
|
||||
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool bRoot) const override;
|
||||
|
||||
private:
|
||||
std::string m_ssVal; ///< Value in case of string or illegal (error) node.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Base structure for arrays and tables.
|
||||
*/
|
||||
class CNodeCollection : public CNode, public sdv::toml::INodeCollection
|
||||
{
|
||||
protected:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the name of the node.
|
||||
*/
|
||||
CNodeCollection(const std::string& rssName);
|
||||
|
||||
public:
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::toml::INodeCollection)
|
||||
SDV_INTERFACE_CHAIN_BASE(CNode)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Returns the amount of nodes. Overload of sdv::toml::INodeCollection::GetCount.
|
||||
* @return The amount of nodes.
|
||||
*/
|
||||
virtual uint32_t GetCount() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the node. Overload of sdv::toml::INodeCollection::GetNode.
|
||||
* @param[in] uiIndex Index of the node to get.
|
||||
* @return Interface to the node object.
|
||||
*/
|
||||
virtual IInterfaceAccess* GetNode(/*in*/ uint32_t uiIndex) const override;
|
||||
|
||||
/**
|
||||
* @brief Get the node.
|
||||
* @param[in] uiIndex Index of the node to get.
|
||||
* @return Smart pointer to the node object.
|
||||
*/
|
||||
std::shared_ptr<CNode> Get(uint32_t uiIndex) const;
|
||||
|
||||
/**
|
||||
* @brief Searches a node by its key in the parse tree
|
||||
* @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child
|
||||
* name. E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be
|
||||
* accessed and traversed by using the index number in brackets. E.g. 'array[3]' would access the fourth element of
|
||||
* the array 'array'. These access conventions can also be chained like 'table.array[2][1].subtable.integerElement'.
|
||||
* @attention Array indexing starts with 0!
|
||||
* @param[in] ssPath The path of the node to searched for.
|
||||
* @return Returns an interface the requested node if available.
|
||||
*/
|
||||
virtual sdv::IInterfaceAccess* GetNodeDirect(/*in*/ const sdv::u8string& ssPath) const override;
|
||||
|
||||
/**
|
||||
* @brief Add an element to the collection.
|
||||
* @param[in] rptrNode Reference to the node element smart pointer.
|
||||
* @param[in] bUnique When set, check prevents adding an element with the same name.
|
||||
* @return Returns whether the element addition was successful.
|
||||
*/
|
||||
bool AddElement(const std::shared_ptr<CNode>& rptrNode, bool bUnique = false);
|
||||
|
||||
private:
|
||||
|
||||
std::vector<std::shared_ptr<CNode>> m_vecContent; ///< Vector holding the child elements
|
||||
|
||||
public:
|
||||
bool m_bDefinedExplicitly = true; ///< WHen set, the array/table is defined explicitly
|
||||
///< (not internal).
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A dynamic table structure that allows mixed data in form of key value pairs
|
||||
*/
|
||||
class CTable : public CNodeCollection
|
||||
{
|
||||
protected:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the name of the node.
|
||||
*/
|
||||
CTable(const std::string& rssName);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType.
|
||||
* @return Type of the node.
|
||||
*/
|
||||
virtual sdv::toml::ENodeType GetType() const override;
|
||||
|
||||
/**
|
||||
* @brief Accesses a node by its key in the parse tree. Overload of CNode::GetDirect.
|
||||
* @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name.
|
||||
* E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and traversed
|
||||
* by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'. These access
|
||||
* conventions can also be chained like 'table.array[2][1].subtable.integerElement'.
|
||||
* @attention Array indexing starts with 0!
|
||||
* @param[in] rssPath The path of the node to searched for.
|
||||
* @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found.
|
||||
*/
|
||||
virtual std::shared_ptr<CNode> GetDirect(const std::string& rssPath) const override;
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
|
||||
* different table was printed in between.
|
||||
* @param[in] bFirst When set, this is the first entry in an array or table.
|
||||
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
|
||||
* @param[in] bAssignment When set, this is a table assignment.
|
||||
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool bRoot) const override;
|
||||
|
||||
/**
|
||||
* @brief Adds a given node to a given path in the tree. Overload of CNode::Add.
|
||||
* @param[in] rssPath Reference to the path in the tree where the new node is to be added
|
||||
* @param[in] rptrNode Reference to the smart pointer holding the node.
|
||||
* @param[in] bDefinedExplicitly If a table that is created to create the path of the node to be added is
|
||||
* defined explicitly
|
||||
* @throw XInvalidAccessException Throws an XInvalidAccessException if a ancestor node is not open to add
|
||||
* children
|
||||
* @throw XDuplicateNameException Throws a XDuplicateNameException if a node with the same path as the node to
|
||||
* be added is already defined explicitly
|
||||
*/
|
||||
virtual void Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly) override;
|
||||
|
||||
/**
|
||||
* @brief Searches the subtree of the node for a node at the given location using the provided path. Overload of CNode::Find.
|
||||
* @remarks The path elements of arrays and tables are separated by a dot.
|
||||
* @param[in] rssPath Reference to the path in the tree where the new node is to be added
|
||||
* @return Returns a shared pointer to the wanted Node if it is found or an error-Node if it is not
|
||||
*/
|
||||
virtual std::shared_ptr<CNode> Find(const std::string& rssPath) const override;
|
||||
|
||||
bool m_bOpenToAddChildren = true; ///< If internal table, the table can be extended until the table
|
||||
///< is closed.
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A dynamic array structure that allows mixed data
|
||||
* @details The definition of an array in TOML differentiate massively from the syntax to access the elements. For example an array
|
||||
* in TOML could be defined by:
|
||||
* @code
|
||||
* integers = [ 1, 2, 3 ]
|
||||
* nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ]
|
||||
* [[products]]
|
||||
* name = "Hammer"
|
||||
* sku = 738594937
|
||||
* @endcode
|
||||
* The first two examples define the complete array at once. The third example defines one element to be added to an array. Random
|
||||
* access to previous definitions is not required.
|
||||
* The access functions need random access to each element. The GetDirect function uses the syntax similar to C++:
|
||||
* @code
|
||||
* integers[1] --> gives: 2
|
||||
* nested_mixed_array[1][2] --> gives: "c"
|
||||
* products[0].sku --> gives: 738594937
|
||||
* @endcode
|
||||
* To find array elements, the path names are composed of elements separated by a dot. The Add and Find functions use the following
|
||||
* syntax:
|
||||
* @code
|
||||
* integers.1 --> stores: 2
|
||||
* nested_mixed_array.1.2 --> stores: "c"
|
||||
* products.0.sku --> stores: 738594937
|
||||
* @endcode
|
||||
*/
|
||||
class CArray : public CNodeCollection
|
||||
{
|
||||
protected:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the name of the node.
|
||||
*/
|
||||
CArray(const std::string& rssName);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType.
|
||||
* @return Type of the node.
|
||||
*/
|
||||
virtual sdv::toml::ENodeType GetType() const override;
|
||||
|
||||
/**
|
||||
* @brief Accesses a node by its key in the parse tree. Overload of CNode::GetDirect.
|
||||
* @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name.
|
||||
* E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and traversed
|
||||
* by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'. These access
|
||||
* conventions can also be chained like 'table.array[2][1].subtable.integerElement'.
|
||||
* @attention Array indexing starts with 0!
|
||||
* @param[in] rssPath Reference to the path of the node to searched for.
|
||||
* @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found.
|
||||
*/
|
||||
virtual std::shared_ptr<CNode> GetDirect(const std::string& rssPath) const override;
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
|
||||
* different table was printed in between.
|
||||
* @param[in] bFirst When set, this is the first entry in an array or table.
|
||||
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
|
||||
* @param[in] bAssignment When set, this is a table assignment.
|
||||
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool bRoot) const override;
|
||||
|
||||
/**
|
||||
* @brief Adds a given node to a given path in the tree. Overload of CNode::Add.
|
||||
* @param[in] rssPath Reference to the path in the tree where the new node is to be added.
|
||||
* @param[in] rptrNode Reference top the smart pointer holding the node.
|
||||
* @param[in] bDefinedExplicitly If a table that is created to create the path of the node to be added is
|
||||
* defined explicitly
|
||||
* @throw XInvalidAccessException Throws an XInvalidAccessException if a ancestor node is not open to add
|
||||
* children
|
||||
* @throw XDuplicateNameException Throws a XDuplicateNameException if a node with the same path as the node to
|
||||
* be added is already defined explicitly
|
||||
*/
|
||||
virtual void Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly) override;
|
||||
|
||||
/**
|
||||
* @brief Searches the subtree of the node for a node at the given location using the provided path. Overload of CNode::Find.
|
||||
* @remarks The path elements of arrays and tables are separated by a dot.
|
||||
* @param[in] rssPath Reference to the path of the node to find.
|
||||
* @return Returns a shared pointer to the wanted Node if it is found or an error-Node if it is not
|
||||
*/
|
||||
virtual std::shared_ptr<CNode> Find(const std::string& rssPath) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Normal table
|
||||
*/
|
||||
class CNormalTable : public CTable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the name of the node.
|
||||
*/
|
||||
CNormalTable(const std::string& rssName) : CTable(rssName) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Inline table
|
||||
*/
|
||||
class CInlineTable : public CTable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the name of the node.
|
||||
*/
|
||||
CInlineTable(const std::string& rssName) : CTable(rssName) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Normal array
|
||||
*/
|
||||
class CNormalArray : public CArray
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the name of the node.
|
||||
*/
|
||||
CNormalArray(const std::string& rssName) : CArray(rssName) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Array of tables
|
||||
*/
|
||||
class CTableArray : public CArray
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the name of the node.
|
||||
*/
|
||||
CTableArray(const std::string& rssName) : CArray(rssName) {}
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
|
||||
* different table was printed in between.
|
||||
* @param[in] bFirst When set, this is the first entry in an array or table.
|
||||
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
|
||||
* @param[in] bAssignment When set, this is a table assignment.
|
||||
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool bRoot) const override;
|
||||
|
||||
/**
|
||||
* @brief Adds a given node to a given path in the tree. Overload of CNode::Add.
|
||||
* @param[in] rssPath Reference to the path in the tree where the new node is to be added.
|
||||
* @param[in] rptrNode Reference top the smart pointer holding the node.
|
||||
* @param[in] bDefinedExplicitly If a table that is created to create the path of the node to be added is
|
||||
* defined explicitly
|
||||
* @throw XInvalidAccessException Throws an XInvalidAccessException if a ancestor node is not open to add
|
||||
* children
|
||||
* @throw XDuplicateNameException Throws a XDuplicateNameException if a node with the same path as the node to
|
||||
* be added is already defined explicitly
|
||||
*/
|
||||
virtual void Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly) override;
|
||||
|
||||
/**
|
||||
* @brief Searches the subtree of the node for a node at the given location using the provided path. Overload of CNode::Find.
|
||||
* @remarks The path elements of arrays and tables are separated by a dot.
|
||||
* @param[in] rssPath Reference to the string containing the path of the node to find.
|
||||
* @return Returns a shared pointer to the wanted Node if it is found or an error-Node if it is not
|
||||
*/
|
||||
virtual std::shared_ptr<CNode> Find(const std::string& rssPath) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Root table
|
||||
*/
|
||||
class CRootTable : public CNormalTable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
CRootTable() : CNormalTable("root") {}
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
|
||||
* different table was printed in between.
|
||||
* @param[in] bFirst When set, this is the first entry in an array or table.
|
||||
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
|
||||
* @param[in] bAssignment When set, this is a table assignment.
|
||||
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool bRoot) const override;
|
||||
};
|
||||
|
||||
#endif // !defined PARSER_NODE_TOML_H
|
||||
380
sdv_services/core/toml_parser/parser_toml.cpp
Normal file
380
sdv_services/core/toml_parser/parser_toml.cpp
Normal file
@@ -0,0 +1,380 @@
|
||||
#include "parser_toml.h"
|
||||
#include <iostream>
|
||||
#include "exception.h"
|
||||
|
||||
CParserTOML::CParserTOML(const std::string& rssString) : m_lexer(rssString)
|
||||
{
|
||||
Process(rssString);
|
||||
}
|
||||
|
||||
void CParserTOML::Clear()
|
||||
{
|
||||
m_ptrRoot = std::make_shared<CRootTable>();
|
||||
m_ssCurrentTable.clear();
|
||||
m_lexer.Reset();
|
||||
while (!m_stackEnvironment.empty()) m_stackEnvironment.pop();
|
||||
}
|
||||
|
||||
bool CParserTOML::Process(/*in*/ const sdv::u8string& ssContent)
|
||||
{
|
||||
Clear();
|
||||
m_lexer.Feed(ssContent);
|
||||
try
|
||||
{
|
||||
// Run through all tokens of the lexer and process the tokens.
|
||||
while (!m_lexer.IsEnd())
|
||||
{
|
||||
CLexerTOML::SToken current = m_lexer.Peek();
|
||||
switch (current.eCategory)
|
||||
{
|
||||
case CLexerTOML::ETokenCategory::token_syntax_table_open:
|
||||
ProcessTable();
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_syntax_table_array_open:
|
||||
ProcessTableArray();
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_key:
|
||||
ProcessValueKey();
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_syntax_new_line:
|
||||
m_lexer.Consume();
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_terminated:
|
||||
case CLexerTOML::ETokenCategory::token_error:
|
||||
throw XTOMLParseException(current.ssContentString);
|
||||
break;
|
||||
default:
|
||||
throw XTOMLParseException("Invalid Syntax; not a Key, Table or Tablearray");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const sdv::toml::XTOMLParseException& e)
|
||||
{
|
||||
std::cout << e.what() << '\n';
|
||||
throw;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const CNodeCollection& CParserTOML::GetRoot() const
|
||||
{
|
||||
auto ptrCollection = m_ptrRoot->GetTable();
|
||||
return *ptrCollection.get();
|
||||
}
|
||||
|
||||
CNodeCollection& CParserTOML::GetRoot()
|
||||
{
|
||||
auto ptrCollection = m_ptrRoot->GetTable();
|
||||
return *ptrCollection.get();
|
||||
}
|
||||
|
||||
std::string CParserTOML::CreateTOMLText(const std::string& rssParent) const
|
||||
{
|
||||
std::string ssLastPrintedTable;
|
||||
return m_ptrRoot->CreateTOMLText(rssParent, ssLastPrintedTable);
|
||||
}
|
||||
|
||||
bool CParserTOML::Add(const std::string& rssPath, bool bVal)
|
||||
{
|
||||
size_t nOffset = FindLast(rssPath);
|
||||
if (nOffset == std::string::npos)
|
||||
nOffset = 0;
|
||||
else
|
||||
nOffset++;
|
||||
std::string ssName = rssPath.substr(nOffset);
|
||||
|
||||
m_ptrRoot->Add(rssPath, std::make_shared<CBooleanNode>(ssName, bVal));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CParserTOML::Add(const std::string& rssPath, int64_t iVal)
|
||||
{
|
||||
size_t nOffset = FindLast(rssPath);
|
||||
if (nOffset == std::string::npos)
|
||||
nOffset = 0;
|
||||
else
|
||||
nOffset++;
|
||||
std::string ssName = rssPath.substr(nOffset);
|
||||
|
||||
m_ptrRoot->Add(rssPath, std::make_shared<CIntegerNode>(ssName, iVal));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CParserTOML::Add(const std::string& rssPath, double dVal)
|
||||
{
|
||||
size_t nOffset = FindLast(rssPath);
|
||||
if (nOffset == std::string::npos)
|
||||
nOffset = 0;
|
||||
else
|
||||
nOffset++;
|
||||
std::string ssName = rssPath.substr(nOffset);
|
||||
|
||||
m_ptrRoot->Add(rssPath, std::make_shared<CFloatingPointNode>(ssName, dVal));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CParserTOML::Add(const std::string& rssPath, const std::string& rssVal)
|
||||
{
|
||||
size_t nOffset = FindLast(rssPath);
|
||||
if (nOffset == std::string::npos)
|
||||
nOffset = 0;
|
||||
else
|
||||
nOffset++;
|
||||
std::string ssName = rssPath.substr(nOffset);
|
||||
|
||||
m_ptrRoot->Add(rssPath, std::make_shared<CStringNode>(ssName, rssVal));
|
||||
return true;
|
||||
}
|
||||
|
||||
void CParserTOML::ProcessTable()
|
||||
{
|
||||
// Get the table path (table name preceded by parent tables separated with dots).
|
||||
m_lexer.Consume();
|
||||
std::string ssPath = ComposePath();
|
||||
CLexerTOML::SToken sToken = m_lexer.Consume();
|
||||
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_table_close)
|
||||
{
|
||||
throw XTOMLParseException("invalid Table construct");
|
||||
}
|
||||
|
||||
// Find the last dot - the name follows
|
||||
size_t nOffset = FindLast(ssPath);
|
||||
if (nOffset == std::string::npos)
|
||||
nOffset = 0; // No dot found, the whole path is one table name
|
||||
else
|
||||
nOffset++; // Skip the dot
|
||||
std::string ssName = ssPath.substr(nOffset);
|
||||
|
||||
// Add the table to the root
|
||||
m_ptrRoot->Add(ssPath, std::make_shared<CNormalTable>(ssName), false);
|
||||
|
||||
m_ssCurrentTable = ssPath;
|
||||
}
|
||||
|
||||
void CParserTOML::ProcessTableArray()
|
||||
{
|
||||
m_lexer.Consume();
|
||||
std::string rssKeyPath = ComposePath();
|
||||
auto ptrNode = m_ptrRoot->Find(rssKeyPath);
|
||||
if (!ptrNode)
|
||||
{
|
||||
Add<CTableArray>(rssKeyPath);
|
||||
ptrNode = m_ptrRoot->Find(rssKeyPath);
|
||||
}
|
||||
if (!ptrNode) return;
|
||||
if (dynamic_cast<CTableArray*>(ptrNode.get()))
|
||||
ptrNode->GetArray()->AddElement(std::make_shared<CNormalTable>(""));
|
||||
else
|
||||
throw XTOMLParseException(("'" + rssKeyPath + "' already defined as a non-table-array").c_str());
|
||||
m_ssCurrentTable = rssKeyPath;
|
||||
|
||||
CLexerTOML::SToken sToken = m_lexer.Consume();
|
||||
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_table_array_close)
|
||||
{
|
||||
throw XTOMLParseException("invalid Table Array construct");
|
||||
}
|
||||
}
|
||||
|
||||
void CParserTOML::ProcessValueKey()
|
||||
{
|
||||
std::string rssKeyPath = (m_ssCurrentTable.empty() ? "" : (m_ssCurrentTable + ".")) + ComposePath();
|
||||
|
||||
CLexerTOML::SToken sToken = m_lexer.Consume();
|
||||
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_assignment)
|
||||
{
|
||||
throw XTOMLParseException("Assignment expected");
|
||||
}
|
||||
|
||||
ProcessValue(rssKeyPath);
|
||||
}
|
||||
|
||||
void CParserTOML::ProcessValue(const std::string& rssKeyPath)
|
||||
{
|
||||
CLexerTOML::SToken assignmentValue = m_lexer.Consume();
|
||||
switch (assignmentValue.eCategory)
|
||||
{
|
||||
case CLexerTOML::ETokenCategory::token_boolean:
|
||||
Add(rssKeyPath, assignmentValue.bContentBoolean);
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_integer:
|
||||
Add(rssKeyPath, assignmentValue.iContentInteger);
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_float:
|
||||
Add(rssKeyPath, assignmentValue.dContentFloatingpoint);
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_string:
|
||||
Add(rssKeyPath, assignmentValue.ssContentString);
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_syntax_array_open:
|
||||
Add<CNormalArray>(rssKeyPath);
|
||||
m_stackEnvironment.push(EEnvironment::env_array);
|
||||
ProcessArray(rssKeyPath);
|
||||
m_stackEnvironment.pop();
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_syntax_inline_table_open:
|
||||
Add<CInlineTable>(rssKeyPath);
|
||||
m_stackEnvironment.push(EEnvironment::env_inline_table);
|
||||
ProcessInlineTable(rssKeyPath);
|
||||
m_stackEnvironment.pop();
|
||||
break;
|
||||
default:
|
||||
throw XTOMLParseException("Missing value");
|
||||
break;
|
||||
}
|
||||
|
||||
CLexerTOML::SToken sToken = m_lexer.Peek();
|
||||
if (!m_stackEnvironment.empty())
|
||||
{
|
||||
switch (m_stackEnvironment.top())
|
||||
{
|
||||
case EEnvironment::env_array:
|
||||
{
|
||||
int32_t index = 2;
|
||||
while (sToken.eCategory == CLexerTOML::ETokenCategory::token_syntax_new_line)
|
||||
{
|
||||
sToken = m_lexer.Peek(index++);
|
||||
}
|
||||
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_comma
|
||||
&& sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_array_close)
|
||||
{
|
||||
throw XTOMLParseException(
|
||||
"Invalid Token after value assignment in array; ',' or ']' needed");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EEnvironment::env_inline_table:
|
||||
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_comma
|
||||
&& sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_inline_table_close)
|
||||
{
|
||||
throw XTOMLParseException(
|
||||
"Invalid Token after value assignment in inline table; ',' or '}' needed ");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_new_line && sToken.eCategory != CLexerTOML::ETokenCategory::token_eof)
|
||||
{
|
||||
throw XTOMLParseException("Invalid Token after value assignment; newline needed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CParserTOML::ProcessArray(const std::string& rssKeyPath)
|
||||
{
|
||||
/*
|
||||
Arrays are defined as follow: array_name = [value, value, ...]
|
||||
Arrays can have new-lines between their values.
|
||||
And can end with a comma.
|
||||
For example:
|
||||
integers = [ 1, 2, 3 ]
|
||||
colors = [ "red", "yellow", "green", ]
|
||||
nested_arrays_of_ints = [ [ 1, 2 ],
|
||||
[3, 4, 5] ]
|
||||
nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ]
|
||||
string_array = [ "all", 'strings', """are the same""", '''type''' ]
|
||||
*/
|
||||
|
||||
CLexerTOML::SToken sToken = m_lexer.Peek();
|
||||
|
||||
size_t nIndex = 0;
|
||||
enum class EExpect {value_comma_end, comma_end} eExpect = EExpect::value_comma_end;
|
||||
while (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_array_close)
|
||||
{
|
||||
switch (sToken.eCategory)
|
||||
{
|
||||
//case CLexerTOML::ETokenCategory::token_syntax_array_open: // Embedded array
|
||||
// if (eExpect == comma_end) throw XTOMLParseException("Expecting comma or table end.");
|
||||
// m_lexer.Consume();
|
||||
// ProcessArray(rssKeyPath + "." + std::to_string(nIndex++));
|
||||
// eExpect = comma_end;
|
||||
// break;
|
||||
case CLexerTOML::ETokenCategory::token_syntax_new_line:
|
||||
m_lexer.Consume();
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_syntax_comma:
|
||||
m_lexer.Consume();
|
||||
eExpect = EExpect::value_comma_end;
|
||||
break;
|
||||
default:
|
||||
if (eExpect == EExpect::comma_end)
|
||||
throw XTOMLParseException("Expecting comma or table end.");
|
||||
ProcessValue(rssKeyPath + "." + std::to_string(nIndex++));
|
||||
eExpect = EExpect::comma_end;
|
||||
break;
|
||||
}
|
||||
sToken = m_lexer.Peek();
|
||||
}
|
||||
m_lexer.Consume();
|
||||
}
|
||||
|
||||
void CParserTOML::ProcessInlineTable(const std::string& rssKeyPath)
|
||||
{
|
||||
/*
|
||||
Inline tables are defined as follow: table_name = {value, value, ...}
|
||||
For example:
|
||||
name = { first = "Tom", last = "Preston-Werner" }
|
||||
point = { x = 1, y = 2 }
|
||||
animal = { type.name = "pug" }
|
||||
*/
|
||||
|
||||
CLexerTOML::SToken sToken = m_lexer.Peek();
|
||||
|
||||
std::string ssCurrentTableTemp = m_ssCurrentTable;
|
||||
m_ssCurrentTable = rssKeyPath;
|
||||
enum class EExpect { value_comma_end, value, comma_end } eExpect = EExpect::value_comma_end;
|
||||
while (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_inline_table_close)
|
||||
{
|
||||
switch (sToken.eCategory)
|
||||
{
|
||||
case CLexerTOML::ETokenCategory::token_syntax_new_line:
|
||||
throw XTOMLParseException("No newlines allowed in inline table");
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_syntax_comma:
|
||||
if (eExpect == EExpect::value)
|
||||
throw XTOMLParseException("Unexpected comma.");
|
||||
m_lexer.Consume();
|
||||
eExpect = EExpect::value;
|
||||
break;
|
||||
default:
|
||||
if (eExpect == EExpect::comma_end)
|
||||
throw XTOMLParseException("Expecting comma or table end.");
|
||||
ProcessValueKey();
|
||||
eExpect = EExpect::comma_end;
|
||||
break;
|
||||
}
|
||||
sToken = m_lexer.Peek();
|
||||
}
|
||||
if (eExpect == EExpect::value)
|
||||
throw XTOMLParseException("Expecting a value before inline table end.");
|
||||
|
||||
m_ptrRoot->Find(rssKeyPath)->GetTable()->m_bOpenToAddChildren = false;
|
||||
|
||||
m_lexer.Consume();
|
||||
m_ssCurrentTable = ssCurrentTableTemp;
|
||||
}
|
||||
|
||||
std::string CParserTOML::ComposePath()
|
||||
{
|
||||
std::string ssPath;
|
||||
CLexerTOML::SToken sToken = m_lexer.Peek();
|
||||
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_dot
|
||||
&& sToken.eCategory != CLexerTOML::ETokenCategory::token_key)
|
||||
throw XTOMLParseException("Invalid Token to assemble path from keys");
|
||||
|
||||
while (sToken.eCategory == CLexerTOML::ETokenCategory::token_syntax_dot
|
||||
|| sToken.eCategory == CLexerTOML::ETokenCategory::token_key)
|
||||
{
|
||||
m_lexer.Consume();
|
||||
if (sToken.eCategory == CLexerTOML::ETokenCategory::token_key)
|
||||
ssPath += sToken.ssContentString;
|
||||
else
|
||||
ssPath += ".";
|
||||
sToken = m_lexer.Peek();
|
||||
}
|
||||
return ssPath;
|
||||
}
|
||||
|
||||
173
sdv_services/core/toml_parser/parser_toml.h
Normal file
173
sdv_services/core/toml_parser/parser_toml.h
Normal file
@@ -0,0 +1,173 @@
|
||||
#ifndef PARSER_TOML_H
|
||||
#define PARSER_TOML_H
|
||||
|
||||
#include "lexer_toml.h"
|
||||
#include "parser_node_toml.h"
|
||||
|
||||
/**
|
||||
* @brief Creates a tree structure from input of UTF-8 encoded TOML source data
|
||||
*/
|
||||
class CParserTOML : public sdv::IInterfaceAccess, public sdv::toml::ITOMLParser
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CParserTOML() = default;
|
||||
|
||||
/**
|
||||
* @brief Construct a new Parser object
|
||||
* @param[in] rssString UTF-8 encoded data of a TOML source
|
||||
*/
|
||||
CParserTOML(const std::string& rssString);
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::toml::ITOMLParser)
|
||||
SDV_INTERFACE_CHAIN_MEMBER(m_ptrRoot)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Clears the current parse result.
|
||||
* @attention This will render any pointer invalid!
|
||||
*/
|
||||
void Clear();
|
||||
|
||||
// Ignore cppcheck warning for not using dynamic binding when being called through the constructor.
|
||||
// cppcheck-suppress virtualCallInConstructor
|
||||
/**
|
||||
* @brief Process the configuration from the supplied content string. Overload of sdv::toml::ITOMLParser.
|
||||
* @param[in] ssContent Configuration string.
|
||||
* @return Returns 'true' when the configuration could be read successfully, false when not.
|
||||
*/
|
||||
virtual bool Process(/*in*/ const sdv::u8string& ssContent) override;
|
||||
|
||||
/**
|
||||
* @{
|
||||
* @brief Return the root node.
|
||||
* @return Reference to the root node collection.
|
||||
*/
|
||||
const CNodeCollection& GetRoot() const;
|
||||
CNodeCollection& GetRoot();
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
std::string CreateTOMLText(const std::string& rssParent = std::string()) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Add a collection node (table or array).
|
||||
* @tparam TCollectionNode The collection node class to add (to create).
|
||||
* @param[in] rssPath Reference to the node path.
|
||||
* @return Returns whether the node could be added.
|
||||
*/
|
||||
template <class TCollectionNode>
|
||||
bool Add(const std::string& rssPath);
|
||||
|
||||
/**
|
||||
* @brief Add a boolean value node.
|
||||
* @param[in] rssPath Reference to the node path.
|
||||
* @param[in] bVal The boolean value.
|
||||
* @return Returns whether the node could be added.
|
||||
*/
|
||||
bool Add(const std::string& rssPath, bool bVal);
|
||||
|
||||
/**
|
||||
* @brief Add a integer value node.
|
||||
* @param[in] rssPath Reference to the node path.
|
||||
* @param[in] iVal The integer value.
|
||||
* @return Returns whether the node could be added.
|
||||
*/
|
||||
bool Add(const std::string& rssPath, int64_t iVal);
|
||||
|
||||
/**
|
||||
* @brief Add a floating point value node.
|
||||
* @param[in] rssPath Reference to the node path.
|
||||
* @param[in] dVal The floating point value.
|
||||
* @return Returns whether the node could be added.
|
||||
*/
|
||||
bool Add(const std::string& rssPath, double dVal);
|
||||
|
||||
/**
|
||||
* @brief Add a string value node.
|
||||
* @param[in] rssPath Reference to the node path.
|
||||
* @param[in] rssVal Reference to the string value.
|
||||
* @return Returns whether the node could be added.
|
||||
*/
|
||||
bool Add(const std::string& rssPath, const std::string& rssVal);
|
||||
|
||||
/**
|
||||
* @brief Process a table declaration.
|
||||
*/
|
||||
void ProcessTable();
|
||||
|
||||
/**
|
||||
* @brief Process a table array declaration.
|
||||
*/
|
||||
void ProcessTableArray();
|
||||
|
||||
/**
|
||||
* @brief Process the value key.
|
||||
*/
|
||||
void ProcessValueKey();
|
||||
|
||||
/**
|
||||
* @brief Process the value with the supplied key.
|
||||
* @param[in] rssKeyPath Reference to the key path string.
|
||||
*/
|
||||
void ProcessValue(const std::string& rssKeyPath);
|
||||
|
||||
/**
|
||||
* @brief Process the array value with the supplied key.
|
||||
* @param[in] rssKeyPath Reference to the key path string.
|
||||
*/
|
||||
void ProcessArray(const std::string& rssKeyPath);
|
||||
|
||||
/**
|
||||
* @brief Process the inline table value with the supplied key.
|
||||
* @param[in] rssKeyPath Reference to the key path string.
|
||||
*/
|
||||
void ProcessInlineTable(const std::string& rssKeyPath);
|
||||
|
||||
/**
|
||||
* @brief Compose a path from lexer tokens. A path is composed of table and array elements separated with a dot.
|
||||
* @return The composed path.
|
||||
*/
|
||||
std::string ComposePath();
|
||||
|
||||
/**
|
||||
* @brief Enum for differentiating between an array environment and an inline table environment for syntax checks.
|
||||
*/
|
||||
enum class EEnvironment
|
||||
{
|
||||
env_array, //!< Environment for an array
|
||||
env_inline_table //!< Environment for a table
|
||||
};
|
||||
std::stack<EEnvironment> m_stackEnvironment; ///< Tracking of environments in nested structures.
|
||||
std::shared_ptr<CNode> m_ptrRoot = std::make_shared<CRootTable>(); ///< The one root node.
|
||||
std::string m_ssCurrentTable; ///< Path to the current table.
|
||||
CLexerTOML m_lexer; ///< Lexer.
|
||||
};
|
||||
|
||||
template <class TCollectionNode>
|
||||
inline bool CParserTOML::Add(const std::string& rssPath)
|
||||
{
|
||||
size_t nOffset = rssPath.rfind('.');
|
||||
if (nOffset == std::string::npos)
|
||||
nOffset = 0;
|
||||
else
|
||||
nOffset++;
|
||||
std::string ssName = rssPath.substr(nOffset);
|
||||
|
||||
m_ptrRoot->Add(rssPath, std::make_shared<TCollectionNode>(ssName), true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // PARSER_TOML_H
|
||||
33
sdv_services/core/toml_parser_util.h
Normal file
33
sdv_services/core/toml_parser_util.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef CONFIG_UTILITY_H
|
||||
#define CONFIG_UTILITY_H
|
||||
|
||||
#include <support/component_impl.h>
|
||||
#include "toml_parser/parser_toml.h"
|
||||
|
||||
/**
|
||||
* @brief Configuration utility component.
|
||||
*/
|
||||
class CTOMLParserUtility : public sdv::CSdvObject
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CTOMLParserUtility() = default;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_CHAIN_MEMBER(m_parser)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::Utility)
|
||||
DECLARE_OBJECT_CLASS_NAME("TOMLParserUtility")
|
||||
|
||||
private:
|
||||
CParserTOML m_parser; ///< Configuration parser
|
||||
};
|
||||
|
||||
DEFINE_SDV_OBJECT_NO_EXPORT(CTOMLParserUtility)
|
||||
|
||||
#endif // !defined CONFIG_UTILITY_H
|
||||
Reference in New Issue
Block a user