From d1754be58534f678f1a3c214fdad87524456cd01 Mon Sep 17 00:00:00 2001 From: AstroAir Date: Tue, 12 Nov 2024 12:40:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=97=B6=E9=97=B4=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=99=A8=E5=AE=9E=E7=8E=B0=E8=AE=BE=E7=BD=AE=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E6=B7=BB=E5=8A=A0=E5=91=BD=E4=BB=A4=E5=8F=AF?= =?UTF-8?q?=E7=94=A8=E6=80=A7=E6=A3=80=E6=9F=A5=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E6=96=87=E4=BB=B6=E6=B7=BB=E5=8A=A0=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=EF=BC=8C=E6=89=A9=E5=B1=95=E4=BE=9D=E8=B5=96=E5=9B=BE?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=20APK=20=E5=B7=A5=E5=85=B7=E7=B1=BB=E4=BB=A5=E6=94=AF=E6=8C=81?= =?UTF-8?q?=20APK=20=E6=96=87=E4=BB=B6=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/atom.io/pymodule.cpp | 7 +- src/addon/debug/apk.cpp | 237 ++++++++ src/addon/debug/apk.hpp | 36 ++ src/addon/debug/elf.cpp | 6 +- src/addon/dependency.cpp | 135 ++--- src/addon/dependency.hpp | 7 +- src/addon/manager.cpp | 63 +- src/addon/manager.hpp | 10 + src/addon/system_dependency.cpp | 859 ++++++++++++++-------------- src/addon/system_dependency.hpp | 44 +- src/addon/template/remote.cpp | 137 ++++- src/addon/template/remote.hpp | 19 +- src/atom/io/async_compress.hpp | 7 + src/atom/log/atomlog.cpp | 219 ++++--- src/atom/log/atomlog.hpp | 65 ++- src/atom/log/loguru.cpp | 177 +++++- src/atom/log/loguru.hpp | 3 +- src/atom/system/command.cpp | 10 + src/atom/system/command.hpp | 8 + src/atom/tests/fuzz.cpp | 11 +- src/atom/type/qvariant.hpp | 415 ++++++++++++++ src/atom/utils/container.hpp | 12 + src/atom/utils/qtimezone.cpp | 10 +- src/atom/web/address.hpp | 307 ++++++++-- src/atom/web/minetype.hpp | 59 +- src/atom/web/time.cpp | 4 + src/atom/web/time.hpp | 6 + src/utils/constant.hpp | 2 + tests/atom/extra/inicpp/common.cpp | 72 +++ tests/atom/extra/inicpp/convert.cpp | 299 ++++++++++ tests/atom/extra/inicpp/field.cpp | 78 +++ tests/atom/extra/inicpp/file.cpp | 149 +++++ tests/atom/type/qvariant.cpp | 181 ++++++ tests/atom/web/address.cpp | 221 +++++++ tests/atom/web/minetype.cpp | 112 ++++ tests/atom/web/time.cpp | 86 +++ tests/atom/web/utils.cpp | 134 +++++ 37 files changed, 3479 insertions(+), 728 deletions(-) create mode 100644 src/addon/debug/apk.cpp create mode 100644 src/addon/debug/apk.hpp create mode 100644 src/atom/type/qvariant.hpp create mode 100644 tests/atom/extra/inicpp/common.cpp create mode 100644 tests/atom/extra/inicpp/convert.cpp create mode 100644 tests/atom/extra/inicpp/field.cpp create mode 100644 tests/atom/extra/inicpp/file.cpp create mode 100644 tests/atom/type/qvariant.cpp create mode 100644 tests/atom/web/address.cpp create mode 100644 tests/atom/web/minetype.cpp create mode 100644 tests/atom/web/time.cpp create mode 100644 tests/atom/web/utils.cpp diff --git a/modules/atom.io/pymodule.cpp b/modules/atom.io/pymodule.cpp index 847afe82..1d96c12e 100644 --- a/modules/atom.io/pymodule.cpp +++ b/modules/atom.io/pymodule.cpp @@ -33,9 +33,10 @@ PYBIND11_MODULE(io, m) { .def_readwrite("on_delete", &atom::io::CreateDirectoriesOptions::onDelete); - m.def("create_directory", - py::overload_cast(&atom::io::createDirectory), - "Create a directory", py::arg("path"), py::arg("root_dir") = ""); + // TODO: Implement the following functions + // m.def("create_directory", + // py::overload_cast(&atom::io::createDirectory), + // "Create a directory", py::arg("path"), py::arg("root_dir") = ""); m.def("create_directories_recursive", &atom::io::createDirectoriesRecursive, "Create directories recursively", py::arg("base_path"), diff --git a/src/addon/debug/apk.cpp b/src/addon/debug/apk.cpp new file mode 100644 index 00000000..67c9f220 --- /dev/null +++ b/src/addon/debug/apk.cpp @@ -0,0 +1,237 @@ +#include "apk.hpp" + +#include +#include + +#include +#include + +#include "atom/log/loguru.hpp" + +namespace fs = std::filesystem; + +const int PACKAGE_NAME_OFFSET = 9; +const int VERSION_NAME_OFFSET = 21; +const int PERMISSION_OFFSET = 30; +const int ARCHIVE_BLOCK_SIZE = 10240; + +APKTool::APKTool(const std::string& apkPath, const std::string& outputDir) + : apkPath(apkPath), + outputDir(outputDir), + logFile(outputDir + "/apktool.log") { + fs::create_directories(outputDir); + loguru::add_file(logFile.c_str(), loguru::Append, loguru::Verbosity_MAX); + LOG_F(INFO, + "APKTool initialized with APK path: {} and output directory: {}", + apkPath, outputDir); +} + +APKTool::~APKTool() { LOG_F(INFO, "APKTool instance destroyed."); } + +void APKTool::extract(bool parseManifest) { + LOG_F(INFO, "Starting to extract APK file."); + struct archive* archivePtr = archive_read_new(); + struct archive_entry* entry; + int result; + + archive_read_support_format_zip(archivePtr); + + result = archive_read_open_filename(archivePtr, apkPath.c_str(), + ARCHIVE_BLOCK_SIZE); + if (result) { + LOG_F(ERROR, "Failed to open APK file: {}, error code: {}", apkPath, + result); + return; + } + + std::vector fileList; + std::vector threads; + std::mutex mtx; + + while (archive_read_next_header(archivePtr, &entry) == ARCHIVE_OK) { + std::string entryName = archive_entry_pathname(entry); + fileList.push_back(entryName); + + threads.emplace_back([this, entryName, &archivePtr, &mtx]() { + std::string outputFilePath = + (fs::path(outputDir) / entryName).string(); + fs::create_directories(fs::path(outputFilePath).parent_path()); + + std::ofstream outFile(outputFilePath, std::ios::binary); + const void* buff; + size_t size; + la_int64_t offset; + + while (true) { + int result = + archive_read_data_block(archivePtr, &buff, &size, &offset); + if (result == ARCHIVE_EOF) + break; + if (result < ARCHIVE_OK) { + std::lock_guard lock(mtx); + LOG_F(ERROR, "Failed to extract file: {}, error code: {}", + entryName, result); + return; + } + outFile.write(static_cast(buff), + static_cast(size)); + } + + outFile.close(); + std::lock_guard lock(mtx); + LOG_F(INFO, "Successfully extracted file: {}", entryName); + }); + + archive_read_data_skip(archivePtr); + } + + for (auto& thread : threads) { + if (thread.joinable()) { + thread.join(); + } + } + + if (parseManifest) { + parseManifestFile(fs::path(outputDir) / "AndroidManifest.xml"); + } + + writeFileList(fileList); + archive_read_free(archivePtr); + LOG_F(INFO, "Extraction completed."); +} + +void APKTool::repack() { + LOG_F(INFO, "Starting to repack APK."); + std::string command = + "apktool b " + outputDir + " -o " + outputDir + "/output.apk"; + std::system(command.c_str()); + LOG_F(INFO, "APK repacking completed: {}/output.apk", outputDir); +} + +void APKTool::optimizeResources() { + LOG_F(INFO, "Starting resource optimization."); + for (const auto& entry : fs::recursive_directory_iterator(outputDir)) { + if (entry.path().extension() == ".png") { + std::string cmd = "optipng -o2 " + entry.path().string(); + std::system(cmd.c_str()); + LOG_F(INFO, "Optimized resource: {}", entry.path().string()); + } + } + LOG_F(INFO, "Resource optimization completed."); +} + +void APKTool::analyzeObfuscation() { + LOG_F(INFO, "Starting obfuscation analysis."); + std::string command = "jadx -d " + outputDir + "/jadx_output " + apkPath; + std::system(command.c_str()); + LOG_F(INFO, "Obfuscation analysis completed."); +} + +void APKTool::analyzeDependencies() { + LOG_F(INFO, "Starting dependency analysis."); + std::string gradleFile = outputDir + "/build.gradle"; + if (!fs::exists(gradleFile)) { + LOG_F(ERROR, "build.gradle file not found."); + return; + } + + std::ifstream file(gradleFile); + std::string line; + std::vector dependencies; + + while (std::getline(file, line)) { + if (line.find("implementation") != std::string::npos) { + dependencies.push_back(line); + } + } + + LOG_F(INFO, "Dependency analysis results:"); + for (const auto& dep : dependencies) { + LOG_F(INFO, "{}", dep); + } +} + +void APKTool::scanVulnerabilities() { + LOG_F(INFO, "Starting vulnerability scan."); + std::string command = + "dependency-check --project APKTool --scan " + outputDir; + std::system(command.c_str()); + LOG_F(INFO, "Vulnerability scan completed."); +} + +void APKTool::performanceAnalysis() { + LOG_F(INFO, "Starting performance analysis."); + std::string command = "adb shell am start -n " + apkPath; + std::system(command.c_str()); + LOG_F(INFO, "Performance analysis completed."); +} + +void APKTool::signAPK(const std::string& keystore, const std::string& alias, + const std::string& keystorePassword) { + LOG_F(INFO, "Starting APK signing."); + std::string command = + "jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore " + + keystore + " -storepass " + keystorePassword + " " + outputDir + + "/output.apk " + alias; + std::system(command.c_str()); + LOG_F(INFO, "APK signing completed."); +} + +void APKTool::verifySignature() { + LOG_F(INFO, "Starting APK signature verification."); + std::string command = "apksigner verify " + outputDir + "/output.apk"; + std::system(command.c_str()); + LOG_F(INFO, "APK signature verification completed."); +} + +void APKTool::parseManifestFile(const std::string& manifestPath) { + LOG_F(INFO, "Parsing AndroidManifest.xml."); + std::ifstream manifestFile(manifestPath); + if (!manifestFile) { + LOG_F(ERROR, "Failed to open AndroidManifest.xml."); + return; + } + + std::stringstream buffer; + buffer << manifestFile.rdbuf(); + std::string manifestContent = buffer.str(); + manifestFile.close(); + + std::size_t pos = manifestContent.find("package=\""); + if (pos != std::string::npos) { + pos += PACKAGE_NAME_OFFSET; + std::size_t end = manifestContent.find('\"', pos); + std::string packageName = manifestContent.substr(pos, end - pos); + LOG_F(INFO, "Package name: {}", packageName); + } + + pos = manifestContent.find("android:versionName=\""); + if (pos != std::string::npos) { + pos += VERSION_NAME_OFFSET; + std::size_t end = manifestContent.find('\"', pos); + std::string versionName = manifestContent.substr(pos, end - pos); + LOG_F(INFO, "Version name: {}", versionName); + } + + LOG_F(INFO, "Extracting application permissions:"); + std::size_t current = 0; + while ((pos = manifestContent.find("& fileList) { + std::ofstream listFile(outputDir + "/file_list.txt"); + for (const auto& fileName : fileList) { + listFile << fileName << std::endl; + } + listFile.close(); + LOG_F(INFO, "File list written."); +} + +void APKTool::log(const std::string& message) { LOG_F(INFO, "{}", message); } \ No newline at end of file diff --git a/src/addon/debug/apk.hpp b/src/addon/debug/apk.hpp new file mode 100644 index 00000000..ca83b157 --- /dev/null +++ b/src/addon/debug/apk.hpp @@ -0,0 +1,36 @@ +#ifndef LITHIUM_ADDON_DEBUG_APK_HPP +#define LITHIUM_ADDON_DEBUG_APK_HPP + +#include +#include +#include +#include + +class APKTool { +public: + APKTool(const std::string& apkPath, const std::string& outputDir); + ~APKTool(); + + void extract(bool parseManifest = false); + void repack(); + void optimizeResources(); + void analyzeObfuscation(); + void analyzeDependencies(); + void scanVulnerabilities(); + void performanceAnalysis(); + void signAPK(const std::string& keystore, const std::string& alias, + const std::string& keystorePassword); + void verifySignature(); + +private: + std::string apkPath; + std::string outputDir; + std::string logFile; + std::ofstream logStream; + + void parseManifestFile(const std::string& manifestPath); + void writeFileList(const std::vector& fileList); + void log(const std::string& message); +}; + +#endif // LITHIUM_ADDON_DEBUG_APK_HPP \ No newline at end of file diff --git a/src/addon/debug/elf.cpp b/src/addon/debug/elf.cpp index 0ded532a..8291e19c 100644 --- a/src/addon/debug/elf.cpp +++ b/src/addon/debug/elf.cpp @@ -231,8 +231,10 @@ class ElfParser::Impl { Symbol{.name = std::string(strtab + symtab[i].st_name), .value = symtab[i].st_value, .size = symtab[i].st_size, - .bind = ELF64_ST_BIND(symtab[i].st_info), - .type = ELF64_ST_TYPE(symtab[i].st_info), + .bind = static_cast( + ELF64_ST_BIND(symtab[i].st_info)), + .type = static_cast( + ELF64_ST_TYPE(symtab[i].st_info)), .shndx = symtab[i].st_shndx}); } diff --git a/src/addon/dependency.cpp b/src/addon/dependency.cpp index 6cdf8120..ec03a8a2 100644 --- a/src/addon/dependency.cpp +++ b/src/addon/dependency.cpp @@ -8,6 +8,7 @@ #include "atom/error/exception.hpp" #include "atom/log/loguru.hpp" #include "atom/type/json.hpp" +#include "atom/utils/container.hpp" #if __has_include() #include @@ -19,7 +20,12 @@ using namespace tinyxml2; #endif +#include "utils/constant.hpp" + namespace lithium { +DependencyGraph::DependencyGraph() { + LOG_F(INFO, "Creating dependency graph."); +} void DependencyGraph::addNode(const Node& node, const Version& version) { LOG_F(INFO, "Adding node: {} with version: {}", node, version.toString()); @@ -124,46 +130,27 @@ auto DependencyGraph::resolveDependencies( LOG_F(INFO, "Resolving dependencies for directories."); DependencyGraph graph; - for (const auto& dir : directories) { - std::string packageJsonPath = dir + "/package.json"; - std::string packageXmlPath = dir + "/package.xml"; - std::string packageYamlPath = dir + "/package.yaml"; - - if (std::filesystem::exists(packageJsonPath)) { - LOG_F(INFO, "Parsing package.json in directory: {}", dir); - auto [package_name, deps] = parsePackageJson(packageJsonPath); - graph.addNode(package_name, deps.at(package_name)); - - for (const auto& dep : deps) { - if (dep.first != package_name) { - graph.addNode(dep.first, dep.second); - graph.addDependency(package_name, dep.first, dep.second); - } - } - } - - if (std::filesystem::exists(packageXmlPath)) { - LOG_F(INFO, "Parsing package.xml in directory: {}", dir); - auto [package_name, deps] = parsePackageXml(packageXmlPath); - graph.addNode(package_name, deps.at(package_name)); - - for (const auto& dep : deps) { - if (dep.first != package_name) { - graph.addNode(dep.first, dep.second); - graph.addDependency(package_name, dep.first, dep.second); - } - } - } - - if (std::filesystem::exists(packageYamlPath)) { - LOG_F(INFO, "Parsing package.yaml in directory: {}", dir); - auto [package_name, deps] = parsePackageYaml(packageYamlPath); - graph.addNode(package_name, deps.at(package_name)); + const std::vector FILE_TYPES = {"package.json", "package.xml", + "package.yaml"}; - for (const auto& dep : deps) { - if (dep.first != package_name) { - graph.addNode(dep.first, dep.second); - graph.addDependency(package_name, dep.first, dep.second); + for (const auto& dir : directories) { + for (const auto& file : FILE_TYPES) { + std::string filePath = dir; + filePath.append(Constants::PATH_SEPARATOR).append(file); + if (std::filesystem::exists(filePath)) { + LOG_F(INFO, "Parsing {} in directory: {}", file, dir); + auto [package_name, deps] = + (file == "package.json") ? parsePackageJson(filePath) + : (file == "package.xml") ? parsePackageXml(filePath) + : parsePackageYaml(filePath); + + graph.addNode(package_name, deps.at(package_name)); + + for (const auto& [depName, version] : deps) { + if (depName != package_name) { + graph.addNode(depName, version); + graph.addDependency(package_name, depName, version); + } } } } @@ -184,6 +171,39 @@ auto DependencyGraph::resolveDependencies( return removeDuplicates(sortedPackagesOpt.value()); } +auto DependencyGraph::resolveSystemDependencies( + const std::vector& directories) + -> std::unordered_map { + LOG_F(INFO, "Resolving system dependencies for directories."); + std::unordered_map systemDeps; + const std::vector FILE_TYPES = {"package.json", "package.xml", + "package.yaml"}; + + for (const auto& dir : directories) { + for (const auto& file : FILE_TYPES) { + std::string filePath = dir; + filePath.append(Constants::PATH_SEPARATOR).append(file); + if (std::filesystem::exists(filePath)) { + LOG_F(INFO, "Parsing {} in directory: {}", file, dir); + auto [package_name, deps] = + (file == "package.json") ? parsePackageJson(filePath) + : (file == "package.xml") ? parsePackageXml(filePath) + : parsePackageYaml(filePath); + + for (const auto& [depName, version] : deps) { + if (depName.rfind("system:", 0) == 0) { + systemDeps[depName.substr(7)] = version; + } + } + } + } + } + + LOG_F(INFO, "System dependencies resolved successfully."); + return atom::utils::unique(systemDeps); + ; +} + auto DependencyGraph::parsePackageJson(const std::string& path) -> std::pair> { LOG_F(INFO, "Parsing package.json file: {}", path); @@ -275,7 +295,7 @@ auto DependencyGraph::parsePackageYaml(const std::string& path) THROW_MISSING_ARGUMENT("Missing package name in " + path); } - std::string packageName = config["name"].as(); + auto packageName = config["name"].as(); std::unordered_map deps; if (config["dependencies"]) { @@ -289,38 +309,6 @@ auto DependencyGraph::parsePackageYaml(const std::string& path) return {packageName, deps}; } -void DependencyGraph::generatePackageYaml(const std::string& path) const { - LOG_F(INFO, "Generating package.yaml file: {}", path); - YAML::Emitter out; - - out << YAML::BeginMap; - out << YAML::Key << "name" << YAML::Value << "my-cpp-package"; - out << YAML::Key << "version" << YAML::Value << "1.0.0"; - out << YAML::Key << "description" << YAML::Value - << "A sample C++20 package"; - out << YAML::Key << "author" << YAML::Value - << "Your Name "; - out << YAML::Key << "license" << YAML::Value << "MIT"; - - out << YAML::Key << "dependencies" << YAML::Value << YAML::BeginMap; - for (const auto& [node, version] : nodeVersions_) { - out << YAML::Key << node << YAML::Value << version.toString(); - } - out << YAML::EndMap; - - out << YAML::EndMap; - - std::ofstream fout(path); - if (!fout.is_open()) { - LOG_F(ERROR, "Failed to open file: {}", path); - THROW_FAIL_TO_OPEN_FILE("Failed to open " + path); - } - fout << out.c_str(); - fout.close(); - - LOG_F(INFO, "Generated package.yaml file: {} successfully.", path); -} - auto DependencyGraph::hasCycleUtil( const Node& node, std::unordered_set& visited, std::unordered_set& recStack) const -> bool { @@ -332,7 +320,8 @@ auto DependencyGraph::hasCycleUtil( if (!visited.contains(neighbor) && hasCycleUtil(neighbor, visited, recStack)) { return true; - } else if (recStack.contains(neighbor)) { + } + if (recStack.contains(neighbor)) { return true; } } diff --git a/src/addon/dependency.hpp b/src/addon/dependency.hpp index 7dbef645..7eff7d42 100644 --- a/src/addon/dependency.hpp +++ b/src/addon/dependency.hpp @@ -24,6 +24,8 @@ namespace lithium { */ class DependencyGraph { public: + DependencyGraph(); + using Node = std::string; /** @@ -124,6 +126,9 @@ class DependencyGraph { auto resolveDependencies(const std::vector& directories) -> std::vector; + auto resolveSystemDependencies(const std::vector& directories) + -> std::unordered_map; + private: std::unordered_map> adjList_; ///< Adjacency list representation of the graph. @@ -153,8 +158,6 @@ class DependencyGraph { static auto parsePackageYaml(const std::string& path) -> std::pair>; - - void generatePackageYaml(const std::string& path) const; }; } // namespace lithium #endif // LITHIUM_ADDON_DEPENDENCY_HPP diff --git a/src/addon/manager.cpp b/src/addon/manager.cpp index 5b39b56a..0d8986da 100644 --- a/src/addon/manager.cpp +++ b/src/addon/manager.cpp @@ -15,14 +15,18 @@ #include #include +#include "addon/dependency.hpp" #include "addons.hpp" #include "compiler.hpp" #include "component.hpp" -#include "config/configor.hpp" #include "loader.hpp" #include "sandbox.hpp" +#include "system_dependency.hpp" #include "tracker.hpp" +#include "config/configor.hpp" + +#include "template/remote.hpp" #include "template/standalone.hpp" #include "atom/components/registry.hpp" @@ -32,8 +36,8 @@ #include "atom/log/loguru.hpp" #include "atom/system/command.hpp" #include "atom/system/env.hpp" -#include "atom/system/process_manager.hpp" #include "atom/system/process.hpp" +#include "atom/system/process_manager.hpp" #include "atom/type/json.hpp" #include "atom/utils/string.hpp" @@ -70,6 +74,7 @@ class ComponentManagerImpl { std::shared_ptr compiler; std::shared_ptr fileTracker; std::weak_ptr addonManager; + std::shared_ptr dependencyManager; std::unordered_map> componentEntries; std::weak_ptr processManager; @@ -91,6 +96,7 @@ ComponentManager::ComponentManager() GetWeakPtr(Constants::PROCESS_MANAGER); impl_->sandbox = std::make_shared(); impl_->compiler = std::make_shared(); + impl_->dependencyManager = std::make_shared(); GET_OR_CREATE_WEAK_PTR(impl_->configManager, ConfigManager, Constants::CONFIG_MANAGER); @@ -206,6 +212,15 @@ void ComponentManager::initializeRegistryComponents() { } auto ComponentManager::loadModules() -> bool { + // Resolve system dependencies first, then resolve other dependencies + auto systemDeps = impl_->dependencyGraph.resolveSystemDependencies( + getQualifiedSubDirs(impl_->modulePath)); + + for (const auto& [dep, version] : systemDeps) { + impl_->dependencyManager->addDependency({dep, version.toString()}); + } + impl_->dependencyManager->checkAndInstallDependencies(); + auto qualifiedSubdirs = impl_->dependencyGraph.resolveDependencies( getQualifiedSubDirs(impl_->modulePath)); if (qualifiedSubdirs.empty()) { @@ -934,6 +949,42 @@ auto ComponentManager::reloadStandaloneComponent( return true; } +auto ComponentManager::loadRemoteComponent( + const std::string& component_name, const std::string& addon_name, + const std::string& module_path, const std::string& entry, + const std::vector& dependencies) -> bool { + std::lock_guard lock(impl_->mutex); + for (const auto& [name, component] : impl_->components) { + if (name == component_name) { + LOG_F(ERROR, "Component {} is already loaded", component_name); + return false; + } + } + if (atom::system::isProcessRunning(component_name)) { + LOG_F(ERROR, "Component {} is already running, killing it", + component_name); + atom::system::killProcessByName(component_name, SIGTERM); + LOG_F(INFO, "Killed process {}", component_name); + if (atom::system::isProcessRunning(component_name)) { + LOG_F(ERROR, "Failed to kill process {}", component_name); + return false; + } + } + for (const auto& dependency : dependencies) { + if (!atom::system::isProcessRunning(dependency)) { + LOG_F(ERROR, "Dependency {} is not running", dependency); + return false; + } + } + auto componentFullPath = module_path + Constants::PATH_SEPARATOR + + component_name + Constants::EXECUTABLE_EXTENSION; + auto remoteComponent = std::make_shared( + component_name, addon_name); + + LOG_F(INFO, "Successfully loaded remote component {}", component_name); + return true; +} + void ComponentManager::updateDependencyGraph( const std::string& component_name, const std::string& version, const std::vector& dependencies, @@ -1010,9 +1061,11 @@ auto ComponentManager::getComponentDoc(const std::string& component_name) return impl_->components[component_name].lock()->getDoc(); } -auto ComponentManager::compileAndLoadComponent(const std::string& code, const std::string& moduleName, - const std::string& functionName) -> bool { - if (!impl_->compiler->compileToSharedLibrary(code, moduleName, functionName)) { +auto ComponentManager::compileAndLoadComponent( + const std::string& code, const std::string& moduleName, + const std::string& functionName) -> bool { + if (!impl_->compiler->compileToSharedLibrary(code, moduleName, + functionName)) { LOG_F(ERROR, "Failed to compile component: {}", moduleName); return false; } diff --git a/src/addon/manager.hpp b/src/addon/manager.hpp index 3a71dbe6..1fc49af2 100644 --- a/src/addon/manager.hpp +++ b/src/addon/manager.hpp @@ -99,6 +99,16 @@ class ComponentManager { bool forced) -> bool; auto reloadStandaloneComponent(const std::string& component_name) -> bool; + auto loadRemoteComponent(const std::string& component_name, + const std::string& addon_name, + const std::string& module_path, + const std::string& entry, + const std::vector& dependencies) + -> bool; + auto unloadRemoteComponent(const std::string& component_name, + bool forced) -> bool; + auto reloadRemoteComponent(const std::string& component_name) -> bool; + void updateDependencyGraph( const std::string& component_name, const std::string& version, const std::vector& dependencies, diff --git a/src/addon/system_dependency.cpp b/src/addon/system_dependency.cpp index d71a9bf8..34c76ccc 100644 --- a/src/addon/system_dependency.cpp +++ b/src/addon/system_dependency.cpp @@ -1,9 +1,10 @@ #include "system_dependency.hpp" +#include #include #include -#include #include +#include #include #include #include @@ -18,75 +19,58 @@ #error "Unsupported platform" #endif +#include "atom/async/pool.hpp" +#include "atom/function/global_ptr.hpp" +#include "atom/log/loguru.hpp" #include "atom/system/command.hpp" #include "atom/type/json.hpp" +#include "utils/constant.hpp" + namespace lithium { using json = nlohmann::json; + class DependencyManager::Impl { public: - explicit Impl(std::vector dependencies) - : dependencies_(std::move(dependencies)) { + Impl() { detectPlatform(); - configurePackageManager(); + loadSystemPackageManagers(); + configurePackageManagers(); loadCacheFromFile(); } ~Impl() { saveCacheToFile(); } - void setLogCallback( - std::function callback) { - logCallback_ = std::move(callback); - } - void checkAndInstallDependencies() { + auto threadPool = + GetPtr>(Constants::THREAD_POOL).value(); + if (!threadPool) { + LOG_F(ERROR, "Failed to get thread pool"); + return; + } std::vector> futures; futures.reserve(dependencies_.size()); for (const auto& dep : dependencies_) { - futures.emplace_back(std::async(std::launch::async, [&]() { - try { - if (!isDependencyInstalled(dep)) { - installDependency(dep); - log(LogLevel::INFO, - "Installed dependency: " + dep.name); - } else { - log(LogLevel::INFO, - "Dependency already installed: " + dep.name); - } - } catch (const DependencyException& ex) { - log(LogLevel::ERROR, ex.what()); - } - })); + futures.emplace_back( + threadPool->enqueue([this, dep]() { installDependency(dep); })); } for (auto& fut : futures) { if (fut.valid()) { - fut.wait(); + fut.get(); } } } void installDependencyAsync(const DependencyInfo& dep) { - std::lock_guard lock(asyncMutex_); - asyncFutures_.emplace_back(std::async(std::launch::async, [&]() { - try { - if (!isDependencyInstalled(dep)) { - installDependency(dep); - log(LogLevel::INFO, "Installed dependency: " + dep.name); - } else { - log(LogLevel::INFO, - "Dependency already installed: " + dep.name); - } - } catch (const DependencyException& ex) { - log(LogLevel::ERROR, ex.what()); - } - })); + std::lock_guard lock(asyncMutex_); + asyncFutures_.emplace_back(std::async( + std::launch::async, [this, dep]() { installDependency(dep); })); } void cancelInstallation(const std::string& depName) { // 取消逻辑实现(示例中未具体实现) - log(LogLevel::WARNING, - "Cancel installation not implemented for: " + depName); + LOG_F(INFO, "Cancel installation not implemented for: {}", depName); } void setCustomInstallCommand(const std::string& dep, @@ -99,497 +83,514 @@ class DependencyManager::Impl { for (const auto& dep : dependencies_) { report << "Dependency: " << dep.name; if (!dep.version.empty()) { - report << " | Version: " << dep.version; + report << ", Version: " << dep.version; } - report << " | Installed: " - << (isDependencyInstalled(dep) ? "Yes" : "No") << "\n"; + report << ", Package Manager: " << dep.packageManager << "\n"; } return report.str(); } void uninstallDependency(const std::string& depName) { - auto it = std::find_if(dependencies_.begin(), dependencies_.end(), - [&depName](const DependencyInfo& info) { - return info.name == depName; - }); + auto it = std::ranges::find_if( + dependencies_, + [&](const DependencyInfo& info) { return info.name == depName; }); if (it == dependencies_.end()) { - log(LogLevel::WARNING, "Dependency " + depName + " not managed."); + LOG_F(WARNING, "Dependency {} not managed.", depName); return; } if (!isDependencyInstalled(*it)) { - log(LogLevel::INFO, "Dependency " + depName + " is not installed."); + LOG_F(INFO, "Dependency {} is not installed.", depName); return; } try { - uninstallDependencyInternal(depName); - log(LogLevel::INFO, "Uninstalled dependency: " + depName); + auto pkgMgr = getPackageManager(it->packageManager); + if (!pkgMgr) { + throw DependencyException("Package manager not found."); +} + auto res = atom::system::executeCommandWithStatus( + pkgMgr->getUninstallCommand(*it)); + if (res.second != 0) { + throw DependencyException("Failed to uninstall dependency."); + } + installedCache_[depName] = false; + LOG_F(INFO, "Uninstalled dependency: {}", depName); } catch (const DependencyException& ex) { - log(LogLevel::ERROR, ex.what()); + LOG_F(ERROR, "Error uninstalling {}: {}", depName, ex.what()); } } - auto getCurrentPlatform() const -> std::string { - switch (distroType_) { - case DistroType::DEBIAN: - return "Debian-based Linux"; - case DistroType::FEDORA: - return "Fedora-based Linux"; - case DistroType::ARCH: - return "Arch-based Linux"; - case DistroType::OPENSUSE: - return "openSUSE"; - case DistroType::GENTOO: - return "Gentoo"; - case DistroType::MACOS: - return "macOS"; - case DistroType::WINDOWS: - return "Windows"; - default: - return "Unknown"; + auto getCurrentPlatform() const -> std::string { return platform_; } + + void addDependency(const DependencyInfo& dep) { + std::lock_guard lock(cacheMutex_); + dependencies_.emplace_back(dep); + installedCache_.emplace(dep.name, false); + LOG_F(INFO, "Added dependency: {}", dep.name); + } + + void removeDependency(const std::string& depName) { + std::lock_guard lock(cacheMutex_); + dependencies_.erase( + std::ranges::remove_if( + dependencies_, + [&](const DependencyInfo& dep) { return dep.name == depName; }) + .begin(), + dependencies_.end()); + installedCache_.erase(depName); + LOG_F(INFO, "Removed dependency: {}", depName); + } + + auto searchDependency(const std::string& depName) + -> std::vector { + std::vector results; + for (const auto& pkgMgr : packageManagers_) { + auto res = atom::system::executeCommandWithStatus( + pkgMgr.getSearchCommand(depName)); + if (res.second != 0) { + LOG_F(ERROR, "Failed to search for dependency: {}", depName); + continue; + } + std::istringstream iss(res.first); + std::string line; + while (std::getline(iss, line)) { + results.emplace_back(line); + } } + return results; + } + + void loadSystemPackageManagers() { +#ifdef PLATFORM_LINUX + // Debian/Ubuntu 系 + packageManagers_.emplace_back( + PackageManagerInfo{"apt", + [](const DependencyInfo& dep) -> std::string { + return "dpkg -l " + dep.name; + }, + [&](const DependencyInfo& dep) -> std::string { + if (customInstallCommands_.contains(dep.name)) { + return customInstallCommands_[dep.name]; + } + return "sudo apt-get install -y " + dep.name; + }, + [](const DependencyInfo& dep) -> std::string { + return "sudo apt-get remove -y " + dep.name; + }, + [](const std::string& dep) -> std::string { + return "apt-cache search " + dep; + }}); + + // DNF (新版 Fedora/RHEL) + packageManagers_.emplace_back( + PackageManagerInfo{"dnf", + [](const DependencyInfo& dep) -> std::string { + return "rpm -q " + dep.name; + }, + [&](const DependencyInfo& dep) -> std::string { + if (customInstallCommands_.contains(dep.name)) { + return customInstallCommands_[dep.name]; + } + return "sudo dnf install -y " + dep.name; + }, + [](const DependencyInfo& dep) -> std::string { + return "sudo dnf remove -y " + dep.name; + }, + [](const std::string& dep) -> std::string { + return "dnf search " + dep; + }}); + + // Pacman (Arch Linux) + packageManagers_.emplace_back(PackageManagerInfo{ + "pacman", + [](const DependencyInfo& dep) -> std::string { + return "pacman -Qs " + dep.name; + }, + [&](const DependencyInfo& dep) -> std::string { + if (customInstallCommands_.contains(dep.name)) { + return customInstallCommands_[dep.name]; + } + return "sudo pacman -S --noconfirm " + dep.name; + }, + [](const DependencyInfo& dep) -> std::string { + return "sudo pacman -R --noconfirm " + dep.name; + }, + [](const std::string& dep) -> std::string { + return "pacman -Ss " + dep; + }}); + + // Zypper (openSUSE) + packageManagers_.emplace_back( + PackageManagerInfo{"zypper", + [](const DependencyInfo& dep) -> std::string { + return "rpm -q " + dep.name; + }, + [&](const DependencyInfo& dep) -> std::string { + if (customInstallCommands_.contains(dep.name)) { + return customInstallCommands_[dep.name]; + } + return "sudo zypper install -y " + dep.name; + }, + [](const DependencyInfo& dep) -> std::string { + return "sudo zypper remove -y " + dep.name; + }, + [](const std::string& dep) -> std::string { + return "zypper search " + dep; + }}); + + // Flatpak + packageManagers_.emplace_back( + PackageManagerInfo{"flatpak", + [](const DependencyInfo& dep) -> std::string { + return "flatpak list | grep " + dep.name; + }, + [&](const DependencyInfo& dep) -> std::string { + if (customInstallCommands_.contains(dep.name)) { + return customInstallCommands_[dep.name]; + } + return "flatpak install -y " + dep.name; + }, + [](const DependencyInfo& dep) -> std::string { + return "flatpak uninstall -y " + dep.name; + }, + [](const std::string& dep) -> std::string { + return "flatpak search " + dep; + }}); + + // Snap + packageManagers_.emplace_back( + PackageManagerInfo{"snap", + [](const DependencyInfo& dep) -> std::string { + return "snap list " + dep.name; + }, + [&](const DependencyInfo& dep) -> std::string { + if (customInstallCommands_.contains(dep.name)) { + return customInstallCommands_[dep.name]; + } + return "sudo snap install " + dep.name; + }, + [](const DependencyInfo& dep) -> std::string { + return "sudo snap remove " + dep.name; + }, + [](const std::string& dep) -> std::string { + return "snap find " + dep; + }}); +#endif + +#ifdef PLATFORM_MAC + // Homebrew + packageManagers_.emplace_back( + PackageManagerInfo{"brew", + [](const DependencyInfo& dep) -> std::string { + return "brew list " + dep.name; + }, + [&](const DependencyInfo& dep) -> std::string { + if (customInstallCommands_.count(dep.name)) { + return customInstallCommands_[dep.name]; + } + return "brew install " + dep.name; + }, + [](const DependencyInfo& dep) -> std::string { + return "brew uninstall " + dep.name; + }, + [](const std::string& dep) -> std::string { + return "brew search " + dep; + }}); + + // MacPorts + packageManagers_.emplace_back( + PackageManagerInfo{"port", + [](const DependencyInfo& dep) -> std::string { + return "port installed " + dep.name; + }, + [&](const DependencyInfo& dep) -> std::string { + if (customInstallCommands_.count(dep.name)) { + return customInstallCommands_[dep.name]; + } + return "sudo port install " + dep.name; + }, + [](const DependencyInfo& dep) -> std::string { + return "sudo port uninstall " + dep.name; + }, + [](const std::string& dep) -> std::string { + return "port search " + dep; + }}); +#endif + +#ifdef PLATFORM_WINDOWS + // Chocolatey + packageManagers_.emplace_back( + PackageManagerInfo{"choco", + [](const DependencyInfo& dep) -> std::string { + return "choco list --local-only " + dep.name; + }, + [&](const DependencyInfo& dep) -> std::string { + if (customInstallCommands_.count(dep.name)) { + return customInstallCommands_[dep.name]; + } + return "choco install " + dep.name + " -y"; + }, + [](const DependencyInfo& dep) -> std::string { + return "choco uninstall " + dep.name + " -y"; + }, + [](const std::string& dep) -> std::string { + return "choco search " + dep; + }}); + + // Scoop + packageManagers_.emplace_back( + PackageManagerInfo{"scoop", + [](const DependencyInfo& dep) -> std::string { + return "scoop list " + dep.name; + }, + [&](const DependencyInfo& dep) -> std::string { + if (customInstallCommands_.count(dep.name)) { + return customInstallCommands_[dep.name]; + } + return "scoop install " + dep.name; + }, + [](const DependencyInfo& dep) -> std::string { + return "scoop uninstall " + dep.name; + }, + [](const std::string& dep) -> std::string { + return "scoop search " + dep; + }}); + + // Winget + packageManagers_.emplace_back(PackageManagerInfo{ + "winget", + [](const DependencyInfo& dep) -> std::string { + return "winget list " + dep.name; + }, + [&](const DependencyInfo& dep) -> std::string { + if (customInstallCommands_.count(dep.name)) { + return customInstallCommands_[dep.name]; + } + return "winget install -e --id " + dep.name; + }, + [](const DependencyInfo& dep) -> std::string { + return "winget uninstall -e --id " + dep.name; + }, + [](const std::string& dep) -> std::string { + return "winget search " + dep; + }}); +#endif + } + + auto getPackageManagers() const -> std::vector { + return packageManagers_; } private: std::vector dependencies_; - std::function logCallback_; std::unordered_map installedCache_; std::unordered_map customInstallCommands_; mutable std::mutex cacheMutex_; std::mutex asyncMutex_; std::vector> asyncFutures_; + std::vector packageManagers_; enum class DistroType { + UNKNOWN, DEBIAN, - FEDORA, + REDHAT, ARCH, OPENSUSE, GENTOO, + SLACKWARE, + VOID, + ALPINE, + CLEAR, + SOLUS, + EMBEDDED, + OTHER, MACOS, - WINDOWS, - UNKNOWN + WINDOWS }; DistroType distroType_ = DistroType::UNKNOWN; - - struct PackageManager { - std::function getCheckCommand; - std::function getInstallCommand; - std::function getUninstallCommand; - }; - - PackageManager packageManager_; + std::string platform_; const std::string CACHE_FILE = "dependency_cache.json"; void detectPlatform() { #ifdef PLATFORM_LINUX - // 检测具体的 Linux 发行版 std::ifstream osReleaseFile("/etc/os-release"); std::string line; - std::regex debianRegex(R"(ID=debian|ID=ubuntu|ID=linuxmint)"); - std::regex fedoraRegex(R"(ID=fedora|ID=rhel|ID=centos)"); - std::regex archRegex(R"(ID=arch|ID=manjaro)"); - std::regex opensuseRegex(R"(ID=opensuse|ID=suse)"); - std::regex gentooRegex(R"(ID=gentoo)"); + // Debian 系 + std::regex debianRegex( + R"(ID=(?:debian|ubuntu|linuxmint|elementary|pop|zorin|deepin|kali|parrot|mx|raspbian))"); + // Red Hat 系 + std::regex redhatRegex( + R"(ID=(?:fedora|rhel|centos|rocky|alma|oracle|scientific|amazon))"); + // Arch 系 + std::regex archRegex( + R"(ID=(?:arch|manjaro|endeavouros|artix|garuda|blackarch))"); + // SUSE 系 + std::regex suseRegex( + R"(ID=(?:opensuse|opensuse-leap|opensuse-tumbleweed|suse|sled|sles))"); + // 其他主流发行版 + std::regex gentooRegex(R"(ID=(?:gentoo|calculate|redcore|sabayon))"); + std::regex slackwareRegex(R"(ID=(?:slackware))"); + std::regex voidRegex(R"(ID=(?:void))"); + std::regex alpineRegex(R"(ID=(?:alpine))"); + std::regex clearRegex(R"(ID=(?:clear-linux-os))"); + std::regex solusRegex(R"(ID=(?:solus))"); + // 嵌入式/专用发行版 + std::regex embeddedRegex(R"(ID=(?:openwrt|buildroot|yocto))"); if (osReleaseFile.is_open()) { while (std::getline(osReleaseFile, line)) { if (std::regex_search(line, debianRegex)) { distroType_ = DistroType::DEBIAN; + platform_ = "Debian-based Linux"; return; } - if (std::regex_search(line, fedoraRegex)) { - distroType_ = DistroType::FEDORA; + if (std::regex_search(line, redhatRegex)) { + distroType_ = DistroType::REDHAT; + platform_ = "RedHat-based Linux"; return; } if (std::regex_search(line, archRegex)) { distroType_ = DistroType::ARCH; + platform_ = "Arch-based Linux"; return; } - if (std::regex_search(line, opensuseRegex)) { + if (std::regex_search(line, suseRegex)) { distroType_ = DistroType::OPENSUSE; + platform_ = "SUSE Linux"; return; } if (std::regex_search(line, gentooRegex)) { distroType_ = DistroType::GENTOO; + platform_ = "Gentoo-based Linux"; + return; + } + if (std::regex_search(line, slackwareRegex)) { + distroType_ = DistroType::SLACKWARE; + platform_ = "Slackware Linux"; + return; + } + if (std::regex_search(line, voidRegex)) { + distroType_ = DistroType::VOID; + platform_ = "Void Linux"; + return; + } + if (std::regex_search(line, alpineRegex)) { + distroType_ = DistroType::ALPINE; + platform_ = "Alpine Linux"; + return; + } + if (std::regex_search(line, clearRegex)) { + distroType_ = DistroType::CLEAR; + platform_ = "Clear Linux"; + return; + } + if (std::regex_search(line, solusRegex)) { + distroType_ = DistroType::SOLUS; + platform_ = "Solus"; + return; + } + if (std::regex_search(line, embeddedRegex)) { + distroType_ = DistroType::EMBEDDED; + platform_ = "Embedded Linux"; return; } } } distroType_ = DistroType::UNKNOWN; + platform_ = "Unknown Linux"; #elif defined(PLATFORM_MAC) distroType_ = DistroType::MACOS; + platform_ = "macOS"; #elif defined(PLATFORM_WINDOWS) distroType_ = DistroType::WINDOWS; + platform_ = "Windows"; #else distroType_ = DistroType::UNKNOWN; + platform_ = "Unknown"; #endif } - void configurePackageManager() { -#ifdef PLATFORM_LINUX - switch (distroType_) { - case DistroType::DEBIAN: - packageManager_.getCheckCommand = - [](const DependencyInfo& dep) -> std::string { - std::string cmd = - "dpkg -s " + dep.name + " > /dev/null 2>&1"; - if (!dep.version.empty()) { - cmd += " && dpkg -s " + dep.name + - " | grep Version | grep " + dep.version; - } - return cmd; - }; - packageManager_.getInstallCommand = - [this](const DependencyInfo& dep) -> std::string { - if (!customInstallCommands_.contains(dep.name)) { - return "sudo apt-get install -y " + dep.name + - (dep.version.empty() ? "" : "=" + dep.version); - } - return customInstallCommands_.at(dep.name); - }; - packageManager_.getUninstallCommand = - [](const DependencyInfo& dep) -> std::string { - return "sudo apt-get remove -y " + dep.name; - }; - break; - case DistroType::FEDORA: - packageManager_.getCheckCommand = - [](const DependencyInfo& dep) -> std::string { - std::string cmd = - "rpm -q " + dep.name + " > /dev/null 2>&1"; - if (!dep.version.empty()) { - cmd += " && rpm -q " + dep.name + "-" + dep.version + - " > /dev/null 2>&1"; - } - return cmd; - }; - packageManager_.getInstallCommand = - [this](const DependencyInfo& dep) -> std::string { - if (!customInstallCommands_.contains(dep.name)) { - return "sudo dnf install -y " + dep.name + - (dep.version.empty() ? "" : "-" + dep.version); - } - return customInstallCommands_.at(dep.name); - }; - packageManager_.getUninstallCommand = - [](const DependencyInfo& dep) -> std::string { - return "sudo dnf remove -y " + dep.name; - }; - break; - case DistroType::ARCH: - packageManager_.getCheckCommand = - [](const DependencyInfo& dep) -> std::string { - std::string cmd = - "pacman -Qs " + dep.name + " > /dev/null 2>&1"; - if (!dep.version.empty()) { - // Pacman 不直接支持版本查询,需自定义实现 - cmd += " && pacman -Qi " + dep.name + - " | grep Version | grep " + dep.version; - } - return cmd; - }; - packageManager_.getInstallCommand = - [this](const DependencyInfo& dep) -> std::string { - if (!customInstallCommands_.contains(dep.name)) { - return "sudo pacman -S --noconfirm " + dep.name + - (dep.version.empty() ? "" : "=" + dep.version); - } - return customInstallCommands_.at(dep.name); - }; - packageManager_.getUninstallCommand = - [](const DependencyInfo& dep) -> std::string { - return "sudo pacman -Rns --noconfirm " + dep.name; - }; - break; - case DistroType::OPENSUSE: - packageManager_.getCheckCommand = - [](const DependencyInfo& dep) -> std::string { - std::string cmd = - "rpm -q " + dep.name + " > /dev/null 2>&1"; - if (!dep.version.empty()) { - cmd += " && rpm -q " + dep.name + "-" + dep.version + - " > /dev/null 2>&1"; - } - return cmd; - }; - packageManager_.getInstallCommand = - [this](const DependencyInfo& dep) -> std::string { - if (!customInstallCommands_.contains(dep.name)) { - return "sudo zypper install -y " + dep.name + - (dep.version.empty() ? "" : "=" + dep.version); - } - return customInstallCommands_.at(dep.name); - }; - packageManager_.getUninstallCommand = - [](const DependencyInfo& dep) -> std::string { - return "sudo zypper remove -y " + dep.name; - }; - break; - case DistroType::GENTOO: - packageManager_.getCheckCommand = - [](const DependencyInfo& dep) -> std::string { - std::string cmd = - "equery list " + dep.name + " > /dev/null 2>&1"; - if (!dep.version.empty()) { - cmd += " && equery list " + dep.name + " | grep " + - dep.version; - } - return cmd; - }; - packageManager_.getInstallCommand = - [this](const DependencyInfo& dep) -> std::string { - if (!customInstallCommands_.contains(dep.name)) { - return "sudo emerge " + dep.name + - (dep.version.empty() ? "" : "-" + dep.version); - } - return customInstallCommands_.at(dep.name); - }; - packageManager_.getUninstallCommand = - [](const DependencyInfo& dep) -> std::string { - return "sudo emerge --unmerge " + dep.name; - }; - break; - default: - // 默认使用 apt-get - packageManager_.getCheckCommand = - [](const DependencyInfo& dep) -> std::string { - std::string cmd = - "dpkg -s " + dep.name + " > /dev/null 2>&1"; - if (!dep.version.empty()) { - cmd += " && dpkg -s " + dep.name + - " | grep Version | grep " + dep.version; - } - return cmd; - }; - packageManager_.getInstallCommand = - [this](const DependencyInfo& dep) -> std::string { - if (!customInstallCommands_.contains(dep.name)) { - return "sudo apt-get install -y " + dep.name + - (dep.version.empty() ? "" : "=" + dep.version); - } - return customInstallCommands_.at(dep.name); - }; - packageManager_.getUninstallCommand = - [](const DependencyInfo& dep) -> std::string { - return "sudo apt-get remove -y " + dep.name; - }; - break; - } -#elif defined(PLATFORM_MAC) - packageManager_.getCheckCommand = - [this](const DependencyInfo& dep) -> std::string { - std::string cmd = "brew list " + dep.name + " > /dev/null 2>&1"; - if (!dep.version.empty()) { - cmd += " && brew info " + dep.name + " | grep " + dep.version; - } - return cmd; - }; - packageManager_.getInstallCommand = - [this](const DependencyInfo& dep) -> std::string { - if (!customInstallCommands_.count(dep.name)) { - return "brew install " + dep.name + - (dep.version.empty() ? "" : "@" + dep.version); - } - return customInstallCommands_.at(dep.name); - }; - packageManager_.getUninstallCommand = - [this](const DependencyInfo& dep) -> std::string { - return "brew uninstall " + dep.name; - }; -#elif defined(PLATFORM_WINDOWS) - packageManager_.getCheckCommand = - [this](const DependencyInfo& dep) -> std::string { - if (!dep.version.empty()) { - return "choco list --local-only " + dep.name + " | findstr " + - dep.version; - } else { - return "choco list --local-only " + dep.name + " > nul 2>&1"; - } - }; - packageManager_.getInstallCommand = - [this](const DependencyInfo& dep) -> std::string { - if (customInstallCommands_.count(dep.name)) { - return customInstallCommands_.at(dep.name); - } - if (isCommandAvailable("choco")) { - return "choco install " + dep.name + " -y" + - (dep.version.empty() ? "" : " --version " + dep.version); - } else if (isCommandAvailable("winget")) { - return "winget install " + dep.name + - (dep.version.empty() ? "" : " --version " + dep.version); - } else if (isCommandAvailable("scoop")) { - return "scoop install " + dep.name; - } else { - throw DependencyException( - "No supported package manager found."); - } - }; - packageManager_.getUninstallCommand = - [this](const DependencyInfo& dep) -> std::string { - if (customInstallCommands_.count(dep.name)) { - return customInstallCommands_.at(dep.name); - } - if (isCommandAvailable("choco")) { - return "choco uninstall " + dep.name + " -y"; - } else if (isCommandAvailable("winget")) { - return "winget uninstall " + dep.name; - } else if (isCommandAvailable("scoop")) { - return "scoop uninstall " + dep.name; - } else { - throw DependencyException( - "No supported package manager found."); - } - }; -#endif - } - - void checkAndInstallDependenciesOptimized() { - // 优化后的依赖检查和安装逻辑 + void configurePackageManagers() { + // 已由loadSystemPackageManagers配置 } bool isDependencyInstalled(const DependencyInfo& dep) { - std::lock_guard lock(cacheMutex_); auto it = installedCache_.find(dep.name); - if (it != installedCache_.end()) { - return it->second; - } - - std::string checkCommand = packageManager_.getCheckCommand(dep); - bool isInstalled = false; - try { - isInstalled = atom::system::executeCommandSimple(checkCommand); - } catch (const std::exception& ex) { - log(LogLevel::ERROR, - "Error checking dependency " + dep.name + ": " + ex.what()); - isInstalled = false; - } - installedCache_[dep.name] = isInstalled; - return isInstalled; + return it != installedCache_.end() && it->second; } void installDependency(const DependencyInfo& dep) { - std::string installCommand = packageManager_.getInstallCommand(dep); - bool success = false; try { - success = atom::system::executeCommandSimple(installCommand); - } catch (const std::exception& ex) { - throw DependencyException("Failed to install " + dep.name + ": " + - ex.what()); - } - - if (!success) { - throw DependencyException("Failed to install " + dep.name); + auto pkgMgr = getPackageManager(dep.packageManager); + if (!pkgMgr) + throw DependencyException("Package manager not found."); + if (!isDependencyInstalled(dep)) { + auto res = atom::system::executeCommandWithStatus( + pkgMgr->getInstallCommand(dep)); + if (res.second != 0) { + throw DependencyException("Failed to install dependency."); + } + installedCache_[dep.name] = true; + LOG_F(INFO, "Installed dependency: {}", dep.name); + } + } catch (const DependencyException& ex) { + LOG_F(ERROR, "Error installing {}: {}", dep.name, ex.what()); } - - // 更新缓存 - std::lock_guard lock(cacheMutex_); - installedCache_[dep.name] = true; } - void uninstallDependencyInternal(const std::string& depName) { - auto it = std::find_if(dependencies_.begin(), dependencies_.end(), - [&depName](const DependencyInfo& info) { - return info.name == depName; - }); - if (it == dependencies_.end()) { - throw DependencyException("Dependency " + depName + " not found."); + std::optional getPackageManager( + const std::string& name) const { + auto it = std::ranges::find_if( + packageManagers_, + [&](const PackageManagerInfo& pm) { return pm.name == name; }); + if (it != packageManagers_.end()) { + return *it; } - - std::string uninstallCommand = packageManager_.getUninstallCommand(*it); - bool success = false; - try { - success = atom::system::executeCommandSimple(uninstallCommand); - } catch (const std::exception& ex) { - throw DependencyException("Failed to uninstall " + depName + ": " + - ex.what()); - } - - if (!success) { - throw DependencyException("Failed to uninstall " + depName); - } - - // 更新缓存 - std::lock_guard lock(cacheMutex_); - installedCache_[depName] = false; - } - - static auto isCommandAvailable(const std::string& command) -> bool { - std::string checkCommand; -#ifdef PLATFORM_WINDOWS - checkCommand = "where " + command + " > nul 2>&1"; -#else - checkCommand = "command -v " + command + " > /dev/null 2>&1"; -#endif - return atom::system::executeCommandSimple(checkCommand); + return std::nullopt; } void loadCacheFromFile() { - std::lock_guard lock(cacheMutex_); std::ifstream cacheFile(CACHE_FILE); if (!cacheFile.is_open()) { + LOG_F(WARNING, "Cache file not found."); return; } - - try { - json j; - cacheFile >> j; - for (auto& [key, value] : j.items()) { - installedCache_[key] = value.get(); - } - } catch (const json::parse_error& ex) { - log(LogLevel::WARNING, - "Failed to parse cache file: " + std::string(ex.what())); + json j; + cacheFile >> j; + for (const auto& dep : j["dependencies"]) { + dependencies_.emplace_back(DependencyInfo{ + dep["name"].get(), dep.value("version", ""), + dep.value("packageManager", "")}); + installedCache_[dep["name"].get()] = + dep.value("installed", false); } } void saveCacheToFile() const { - std::lock_guard lock(cacheMutex_); std::ofstream cacheFile(CACHE_FILE); if (!cacheFile.is_open()) { - log(LogLevel::WARNING, "Failed to open cache file for writing."); + LOG_F(ERROR, "Failed to open cache file for writing."); return; } - json j; - for (const auto& [dep, status] : installedCache_) { - j[dep] = status; + for (const auto& dep : dependencies_) { + j["dependencies"].push_back( + {{"name", dep.name}, + {"version", dep.version}, + {"packageManager", dep.packageManager}, + {"installed", installedCache_.at(dep.name)}}); } cacheFile << j.dump(4); } - - void log(LogLevel level, const std::string& message) const { - if (logCallback_) { - logCallback_(level, message); - } else { - // 默认输出到标准输出 - switch (level) { - case LogLevel::INFO: - std::cout << "[INFO] " << message << "\n"; - break; - case LogLevel::WARNING: - std::cout << "[WARNING] " << message << "\n"; - break; - case LogLevel::ERROR: - std::cerr << "[ERROR] " << message << "\n"; - break; - } - } - } }; -DependencyManager::DependencyManager(std::vector dependencies) - : pImpl_(std::make_unique(std::move(dependencies))) {} +DependencyManager::DependencyManager() : pImpl_(std::make_unique()) {} DependencyManager::~DependencyManager() = default; -void DependencyManager::setLogCallback( - std::function callback) { - pImpl_->setLogCallback(std::move(callback)); -} - void DependencyManager::checkAndInstallDependencies() { pImpl_->checkAndInstallDependencies(); } @@ -619,4 +620,26 @@ auto DependencyManager::getCurrentPlatform() const -> std::string { return pImpl_->getCurrentPlatform(); } -} // namespace lithium +void DependencyManager::addDependency(const DependencyInfo& dep) { + pImpl_->addDependency(dep); +} + +void DependencyManager::removeDependency(const std::string& depName) { + pImpl_->removeDependency(depName); +} + +auto DependencyManager::searchDependency(const std::string& depName) + -> std::vector { + return pImpl_->searchDependency(depName); +} + +void DependencyManager::loadSystemPackageManagers() { + pImpl_->loadSystemPackageManagers(); +} + +auto DependencyManager::getPackageManagers() const + -> std::vector { + return pImpl_->getPackageManagers(); +} + +} // namespace lithium \ No newline at end of file diff --git a/src/addon/system_dependency.hpp b/src/addon/system_dependency.hpp index b4ffa1f7..9496e4a4 100644 --- a/src/addon/system_dependency.hpp +++ b/src/addon/system_dependency.hpp @@ -9,10 +9,6 @@ namespace lithium { -// 日志级别定义 -enum class LogLevel { INFO, WARNING, ERROR }; - -// 自定义异常类 class DependencyException : public std::exception { public: explicit DependencyException(std::string message) @@ -28,44 +24,42 @@ class DependencyException : public std::exception { // 依赖项信息结构 struct DependencyInfo { std::string name; - std::string version; // 可选 + std::string version; // 可选 + std::string packageManager; // 指定的包管理器 +}; + +// 包管理器信息结构 +struct PackageManagerInfo { + std::string name; + std::function getCheckCommand; + std::function getInstallCommand; + std::function getUninstallCommand; + std::function getSearchCommand; }; // 依赖管理器类 class DependencyManager { public: - explicit DependencyManager(std::vector dependencies); + DependencyManager(); ~DependencyManager(); - // 禁用拷贝和赋值 DependencyManager(const DependencyManager&) = delete; DependencyManager& operator=(const DependencyManager&) = delete; - // 设置日志回调函数,包含日志级别 - void setLogCallback( - std::function callback); - - // 检查并安装所有依赖项 void checkAndInstallDependencies(); - - // 设置自定义安装命令 void setCustomInstallCommand(const std::string& dep, const std::string& command); - - // 生成依赖项报告 auto generateDependencyReport() const -> std::string; - - // 卸载依赖项 void uninstallDependency(const std::string& dep); - - // 获取当前支持的平台类型 auto getCurrentPlatform() const -> std::string; - - // 异步安装依赖项 void installDependencyAsync(const DependencyInfo& dep); - - // 取消安装操作 void cancelInstallation(const std::string& dep); + void addDependency(const DependencyInfo& dep); + void removeDependency(const std::string& depName); + auto searchDependency(const std::string& depName) + -> std::vector; + void loadSystemPackageManagers(); + auto getPackageManagers() const -> std::vector; private: class Impl; @@ -74,4 +68,4 @@ class DependencyManager { } // namespace lithium -#endif // LITHIUM_ADDON_DEPENDENCY_MANAGER_HPP +#endif // LITHIUM_ADDON_DEPENDENCY_MANAGER_HPP \ No newline at end of file diff --git a/src/addon/template/remote.cpp b/src/addon/template/remote.cpp index 96c3292a..c0ef6cca 100644 --- a/src/addon/template/remote.cpp +++ b/src/addon/template/remote.cpp @@ -1,6 +1,8 @@ #include "remote.hpp" #include +#include + #include #include #include @@ -12,6 +14,7 @@ using asio::ip::tcp; using asio::ip::udp; +namespace ssl = asio::ssl; class RemoteStandAloneComponentImpl { public: @@ -32,6 +35,18 @@ class RemoteStandAloneComponentImpl { std::atomic heartbeatEnabled{false}; ProtocolType protocol{ProtocolType::TCP}; + // SSL支持 + std::optional sslContext; + std::optional> sslSocket; + bool sslEnabled{false}; + + // 压缩支持 + bool compressionEnabled{false}; + + // 身份验证信息 + std::string username; + std::string password; + // Reconnection strategy std::chrono::milliseconds initialReconnectDelay{1000}; std::chrono::milliseconds maxReconnectDelay{30000}; @@ -54,14 +69,24 @@ RemoteStandAloneComponent::RemoteStandAloneComponent(std::string name) "TCP or UDP"); def("connect", &RemoteStandAloneComponent::connectToRemoteDriver); def("disconnect", &RemoteStandAloneComponent::disconnectRemoteDriver); - def("send", &RemoteStandAloneComponent::sendMessageToDriver); - def("send_async", - &RemoteStandAloneComponent::sendMessageAsync); + //def("send", &RemoteStandAloneComponent::sendMessageToDriver); + // def("send_async", + // &RemoteStandAloneComponent::sendMessageAsync); def("listen", &RemoteStandAloneComponent::toggleDriverListening); def("print", &RemoteStandAloneComponent::printDriver); def("heartbeat_on", &RemoteStandAloneComponent::enableHeartbeat); def("heartbeat_off", &RemoteStandAloneComponent::disableHeartbeat); - def("execute", &RemoteStandAloneComponent::executeCommand); + // TODO: Implement executeCommand + // def("execute", &RemoteStandAloneComponent::executeCommand); + + def("enable_ssl", &RemoteStandAloneComponent::enableSSL); + def("disable_ssl", &RemoteStandAloneComponent::disableSSL); + def("enable_compression", &RemoteStandAloneComponent::enableCompression); + def("disable_compression", &RemoteStandAloneComponent::disableCompression); + def("authenticate", &RemoteStandAloneComponent::authenticate); + def("GetStatus", &RemoteStandAloneComponent::GetStatus); + def("RestartDriver", &RemoteStandAloneComponent::RestartDriver); + def("UpdateConfig", &RemoteStandAloneComponent::UpdateConfig); } RemoteStandAloneComponent::~RemoteStandAloneComponent() { @@ -169,7 +194,17 @@ void RemoteStandAloneComponent::sendMessageToDriver(T&& message) { std::visit( [&](auto&& socket) { if (socket && socket->is_open()) { - asio::write(*socket, asio::buffer(std::forward(message))); + if constexpr (std::is_same_v, + asio::ip::tcp::socket>) { + asio::write(*socket, + asio::buffer(std::forward(message))); + } else if constexpr (std::is_same_v< + std::decay_t, + asio::ip::udp::socket>) { + socket->send(asio::buffer(std::forward(message))); + } else { + LOG_F(ERROR, "Unsupported socket type"); + } } else { LOG_F(ERROR, "No active connection to send message"); } @@ -436,6 +471,98 @@ void RemoteStandAloneComponent::attemptReconnection() { impl_->currentReconnectAttempts++; } +void RemoteStandAloneComponent::enableSSL(const std::string& certFile, + const std::string& keyFile) { + impl_->sslEnabled = true; + impl_->sslContext.emplace(ssl::context::tlsv12_client); + impl_->sslContext->load_verify_file(certFile); + impl_->sslContext->use_private_key_file(keyFile, ssl::context::pem); + LOG_F(INFO, "SSL enabled with cert file: {} and key file: {}", certFile, + keyFile); +} + +void RemoteStandAloneComponent::disableSSL() { + impl_->sslEnabled = false; + impl_->sslContext.reset(); + impl_->sslSocket.reset(); + LOG_F(INFO, "SSL disabled"); +} + +void RemoteStandAloneComponent::enableCompression() { + impl_->compressionEnabled = true; + LOG_F(INFO, "Compression enabled"); +} + +void RemoteStandAloneComponent::disableCompression() { + impl_->compressionEnabled = false; + LOG_F(INFO, "Compression disabled"); +} + +void RemoteStandAloneComponent::authenticate(const std::string& username, + const std::string& password) { + impl_->username = username; + impl_->password = password; + std::string authMessage = "AUTH " + username + " " + password; + // sendMessageToDriver(authMessage); + LOG_F(INFO, "Authentication message sent for user: {}", username); +} + +atom::async::EnhancedFuture +RemoteStandAloneComponent::GetStatus() { + auto promise = + std::make_shared>(); + auto future = promise->getEnhancedFuture(); + + sendMessageAsync("GET_STATUS").then([promise](auto&& result) { + if (!result.first) { + // promise->set_value(result.second); + } else { + // promise->set_exception(std::make_exception_ptr( + // std::runtime_error("Failed to get status"))); + } + }); + + return future; +} + +atom::async::EnhancedFuture RemoteStandAloneComponent::RestartDriver() { + auto promise = std::make_shared>(); + auto future = promise->getEnhancedFuture(); + + sendMessageAsync("RESTART_DRIVER").then([promise](auto&& result) { + if (!result.first) { + // promise->set_value(true); + } else { + // promise->set_exception(std::make_exception_ptr( + // std::runtime_error("Failed to restart driver"))); + } + }); + + return future; +} + +atom::async::EnhancedFuture RemoteStandAloneComponent::UpdateConfig( + const std::string& config) { + auto promise = std::make_shared>(); + auto future = promise->getEnhancedFuture(); + + sendMessageAsync("UPDATE_CONFIG " + config).then([promise](auto&& result) { + if (!result.first) { + // promise->set_value(true); + } else { + // promise->set_exception(std::make_exception_ptr( + // std::runtime_error("Failed to update config"))); + } + }); + + return future; +} + +void RemoteStandAloneComponent::initializeRPC() { + // 初始化RPC框架,如gRPC + LOG_F(INFO, "RPC system initialized"); +} + // Explicit template instantiations template void RemoteStandAloneComponent::sendMessageToDriver( std::string&&); diff --git a/src/addon/template/remote.hpp b/src/addon/template/remote.hpp index 321c80a0..d5c58d39 100644 --- a/src/addon/template/remote.hpp +++ b/src/addon/template/remote.hpp @@ -3,7 +3,6 @@ #include #include -#include #include #include @@ -58,6 +57,24 @@ class RemoteStandAloneComponent : public Component { std::chrono::milliseconds maxDelay, int maxAttempts); + void enableSSL(const std::string& certFile, const std::string& keyFile); + + void disableSSL(); + + void enableCompression(); + + void disableCompression(); + + void authenticate(const std::string& username, const std::string& password); + + atom::async::EnhancedFuture GetStatus(); + + atom::async::EnhancedFuture RestartDriver(); + + atom::async::EnhancedFuture UpdateConfig(const std::string& config); + + void initializeRPC(); + private: void backgroundProcessing(); void monitorConnection(); diff --git a/src/atom/io/async_compress.hpp b/src/atom/io/async_compress.hpp index 2e8b1e5f..67c1bad9 100644 --- a/src/atom/io/async_compress.hpp +++ b/src/atom/io/async_compress.hpp @@ -159,6 +159,8 @@ class BaseDecompressor { */ explicit BaseDecompressor(asio::io_context& io_context); + virtual ~BaseDecompressor() = default; + /** * @brief Starts the decompression process. */ @@ -202,6 +204,8 @@ class SingleFileDecompressor : public BaseDecompressor { SingleFileDecompressor(asio::io_context& io_context, fs::path input_file, fs::path output_folder); + ~SingleFileDecompressor() override = default; + /** * @brief Starts the decompression process. */ @@ -233,6 +237,7 @@ class DirectoryDecompressor : public BaseDecompressor { const fs::path& input_dir, const fs::path& output_folder); + ~DirectoryDecompressor() override = default; /** * @brief Starts the decompression process. */ @@ -262,6 +267,7 @@ class DirectoryDecompressor : public BaseDecompressor { */ class ZipOperation { public: + virtual ~ZipOperation() = default; /** * @brief Starts the ZIP operation. */ @@ -315,6 +321,7 @@ class FileExistsInZip : public ZipOperation { */ FileExistsInZip(asio::io_context& io_context, std::string_view zip_file, std::string_view file_name); + ~FileExistsInZip() override = default; /** * @brief Starts the ZIP operation. diff --git a/src/atom/log/atomlog.cpp b/src/atom/log/atomlog.cpp index 54238e5d..9e1aa856 100644 --- a/src/atom/log/atomlog.cpp +++ b/src/atom/log/atomlog.cpp @@ -1,3 +1,4 @@ +// atomlog.cpp /* * atomlog.cpp * @@ -8,18 +9,22 @@ Date: 2023-11-10 -Description: Logger for Atom +Description: Enhanced Logger Implementation for Atom with C++20 Features **************************************************/ #include "atomlog.hpp" #include +#include #include #include #include +#include #include +#include #include +#include #include #ifdef _WIN32 @@ -36,7 +41,7 @@ Description: Logger for Atom namespace atom::log { -class LoggerImpl { +class Logger::LoggerImpl : public std::enable_shared_from_this { public: LoggerImpl(fs::path file_name_, LogLevel min_level, size_t max_file_size, int max_files) @@ -44,17 +49,17 @@ class LoggerImpl { max_file_size_(max_file_size), max_files_(max_files), min_level_(min_level), - worker_(&LoggerImpl::run, this) { + system_logging_enabled_(false) { rotateLogFile(); + worker_ = std::jthread(&LoggerImpl::run, this); } ~LoggerImpl() { { - std::lock_guard lock(queue_mutex_); + std::lock_guard lock(queue_mutex_); finished_ = true; } cv_.notify_one(); - worker_.request_stop(); if (log_file_.is_open()) { log_file_.close(); } @@ -67,26 +72,42 @@ class LoggerImpl { } void setThreadName(const std::string& name) { - std::lock_guard lock(queue_mutex_); + std::lock_guard lock(thread_mutex_); thread_names_[std::this_thread::get_id()] = name; } - void setLevel(LogLevel level) { min_level_ = level; } + void setLevel(LogLevel level) { + std::lock_guard lock(level_mutex_); + min_level_ = level; + } - void setPattern(const std::string& pattern) { this->pattern_ = pattern; } + void setPattern(const std::string& pattern) { + std::lock_guard lock(pattern_mutex_); + pattern_ = pattern; + } void registerSink(const std::shared_ptr& logger) { - sinks_.push_back(logger); + if (logger.get() == this) { + // 防止注册自身以避免递归调用 + return; + } + std::lock_guard lock(sinks_mutex_); + sinks_.emplace_back(logger); } void removeSink(const std::shared_ptr& logger) { + std::lock_guard lock(sinks_mutex_); sinks_.erase(std::remove(sinks_.begin(), sinks_.end(), logger), sinks_.end()); } - void clearSinks() { sinks_.clear(); } + void clearSinks() { + std::lock_guard lock(sinks_mutex_); + sinks_.clear(); + } void enableSystemLogging(bool enable) { + std::lock_guard lock(system_log_mutex_); system_logging_enabled_ = enable; #ifdef _WIN32 @@ -100,32 +121,34 @@ class LoggerImpl { if (system_logging_enabled_) { openlog("AtomLogger", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1); } -#elif defined(__ANDROID__) - // Android logging does not require initialization #endif } + void registerCustomLogLevel(const std::string& name, int severity) { + std::lock_guard lock(custom_level_mutex_); + custom_levels_[name] = severity; + } + void log(LogLevel level, const std::string& msg) { - if (level < min_level_) { + if (static_cast(level) < static_cast(min_level_)) { return; } auto formattedMsg = formatMessage(level, msg); { - std::lock_guard lock(queue_mutex_); + std::lock_guard lock(queue_mutex_); log_queue_.push(formattedMsg); } cv_.notify_one(); - // Send to system log if enabled + // 如果启用了系统日志,发送到系统日志 if (system_logging_enabled_) { logToSystem(level, formattedMsg); } - for (const auto& sink : sinks_) { - sink->log(level, msg); - } + // 分发到所有注册的日志接收器 + dispatchToSinks(level, msg); } private: @@ -140,15 +163,24 @@ class LoggerImpl { int max_files_; LogLevel min_level_; std::unordered_map thread_names_; - std::string pattern_ = "[{}][{}][{}] {v}"; + std::string pattern_ = "[{}][{}][{}] {}"; std::vector> sinks_; - bool system_logging_enabled_{}; + bool system_logging_enabled_ = false; + #ifdef _WIN32 HANDLE h_event_log_ = nullptr; #endif + std::mutex thread_mutex_; + std::mutex pattern_mutex_; + std::mutex sinks_mutex_; + std::mutex system_log_mutex_; + std::mutex level_mutex_; + std::mutex custom_level_mutex_; + std::unordered_map custom_levels_; + void rotateLogFile() { - std::lock_guard lock(queue_mutex_); + std::lock_guard lock(queue_mutex_); if (log_file_.is_open()) { log_file_.close(); } @@ -191,6 +223,7 @@ class LoggerImpl { } auto getThreadName() -> std::string { + std::lock_guard lock(thread_mutex_); auto thread_id = std::this_thread::get_id(); if (thread_names_.contains(thread_id)) { return thread_names_[thread_id]; @@ -221,15 +254,18 @@ class LoggerImpl { auto formatMessage(LogLevel level, const std::string& msg) -> std::string { auto currentTime = utils::getChinaTimestampString(); auto threadName = getThreadName(); - return std::format("[{}][{}][{}][{}]", currentTime, - logLevelToString(level), threadName, msg); + + std::shared_lock patternLock(pattern_mutex_); + return std::vformat(pattern_, std::make_format_args( + currentTime, logLevelToString(level), + threadName, msg)); } - void run(const std::stop_token& stop_token) { + void run(std::stop_token stop_token) { while (!stop_token.stop_requested()) { std::string msg; { - std::unique_lock lock(queue_mutex_); + std::unique_lock lock(queue_mutex_); cv_.wait(lock, [this] { return !log_queue_.empty() || finished_; }); @@ -239,7 +275,7 @@ class LoggerImpl { msg = log_queue_.front(); log_queue_.pop(); - } // Release lock before I/O operation + } log_file_ << msg << std::endl; @@ -250,7 +286,7 @@ class LoggerImpl { } } - void logToSystem(LogLevel level, const std::string& msg) { + void logToSystem(LogLevel level, const std::string& msg) const { #ifdef _WIN32 if (h_event_log_) { using enum LogLevel; @@ -277,64 +313,83 @@ class LoggerImpl { nullptr); } #elif defined(__linux__) || defined(__APPLE__) - using enum LogLevel; - int priority; - switch (level) { - case CRITICAL: - priority = LOG_CRIT; - break; - case ERROR: - priority = LOG_ERR; - break; - case WARN: - priority = LOG_WARNING; - break; - case INFO: - priority = LOG_INFO; - break; - case DEBUG: - case TRACE: - default: - priority = LOG_DEBUG; - break; - } + if (system_logging_enabled_) { + using enum LogLevel; + int priority; + switch (level) { + case CRITICAL: + priority = LOG_CRIT; + break; + case ERROR: + priority = LOG_ERR; + break; + case WARN: + priority = LOG_WARNING; + break; + case INFO: + priority = LOG_INFO; + break; + case DEBUG: + case TRACE: + default: + priority = LOG_DEBUG; + break; + } - syslog(priority, "%s", msg.c_str()); -#elif defined(__ANDROID__) - using enum LogLevel; - int priority; - switch (level) { - case CRITICAL: - priority = ANDROID_LOG_FATAL; - break; - case ERROR: - priority = ANDROID_LOG_ERROR; - break; - case WARN: - priority = ANDROID_LOG_WARN; - break; - case INFO: - priority = ANDROID_LOG_INFO; - break; - case DEBUG: - priority = ANDROID_LOG_DEBUG; - break; - case TRACE: - default: - priority = ANDROID_LOG_VERBOSE; - break; + syslog(priority, "%s", msg.c_str()); } +#elif defined(__ANDROID__) + if (system_logging_enabled_) { + using enum LogLevel; + int priority; + switch (level) { + case CRITICAL: + priority = ANDROID_LOG_FATAL; + break; + case ERROR: + priority = ANDROID_LOG_ERROR; + break; + case WARN: + priority = ANDROID_LOG_WARN; + break; + case INFO: + priority = ANDROID_LOG_INFO; + break; + case DEBUG: + priority = ANDROID_LOG_DEBUG; + break; + case TRACE: + default: + priority = ANDROID_LOG_VERBOSE; + break; + } - __android_log_print(priority, "AtomLogger", "%s", msg.c_str()); + __android_log_print(priority, "AtomLogger", "%s", msg.c_str()); + } #endif } + + void dispatchToSinks(LogLevel level, const std::string& msg) { + std::shared_lock lock(sinks_mutex_); + for (const auto& sink : sinks_) { + sink->log(level, msg); + } + } + + auto getCustomLogLevel(const std::string& name) -> LogLevel { + std::shared_lock lock(custom_level_mutex_); + if (custom_levels_.find(name) != custom_levels_.end()) { + return static_cast(custom_levels_.at(name)); + } + return LogLevel::INFO; + } }; -// `Logger` class method implementations +// `Logger` 类的方法实现 Logger::Logger(const fs::path& file_name, LogLevel min_level, size_t max_file_size, int max_files) - : impl_(std::make_unique(file_name, min_level, max_file_size, + : impl_(std::make_shared(file_name, min_level, max_file_size, max_files)) {} Logger::~Logger() = default; @@ -350,11 +405,15 @@ void Logger::setPattern(const std::string& pattern) { } void Logger::registerSink(const std::shared_ptr& logger) { - impl_->registerSink(logger->impl_); + if (logger) { + impl_->registerSink(logger->impl_); + } } void Logger::removeSink(const std::shared_ptr& logger) { - impl_->removeSink(logger->impl_); + if (logger) { + impl_->removeSink(logger->impl_); + } } void Logger::clearSinks() { impl_->clearSinks(); } @@ -363,8 +422,12 @@ void Logger::enableSystemLogging(bool enable) { impl_->enableSystemLogging(enable); } +void Logger::registerCustomLogLevel(const std::string& name, int severity) { + impl_->registerCustomLogLevel(name, severity); +} + void Logger::log(LogLevel level, const std::string& msg) { impl_->log(level, msg); } -} // namespace atom::log +} // namespace atom::log \ No newline at end of file diff --git a/src/atom/log/atomlog.hpp b/src/atom/log/atomlog.hpp index ee4ba9b8..c4983b0c 100644 --- a/src/atom/log/atomlog.hpp +++ b/src/atom/log/atomlog.hpp @@ -1,3 +1,4 @@ +// atomlog.hpp /* * atomlog.hpp * @@ -8,7 +9,7 @@ Date: 2023-11-10 -Description: Logger for Atom +Description: Enhanced Logger for Atom with C++20 Features **************************************************/ @@ -26,18 +27,25 @@ namespace atom::log { /** * @brief Enum class representing the log levels. + * Extended to support custom log levels. */ enum class LogLevel { - TRACE, ///< Trace level logging. - DEBUG, ///< Debug level logging. - INFO, ///< Info level logging. - WARN, ///< Warn level logging. - ERROR, ///< Error level logging. - CRITICAL, ///< Critical level logging. - OFF ///< Used to disable logging. + TRACE = 0, ///< Trace level logging. + DEBUG, ///< Debug level logging. + INFO, ///< Info level logging. + WARN, ///< Warn level logging. + ERROR, ///< Error level logging. + CRITICAL, ///< Critical level logging. + OFF ///< Used to disable logging. }; -class LoggerImpl; // Forward declaration +/** + * @brief Structure representing a custom log level. + */ +struct CustomLogLevel { + std::string name; + int severity; +}; /** * @brief Logger class for logging messages with different severity levels. @@ -71,9 +79,8 @@ class Logger { * @param args The arguments to format. */ template - void trace(const std::string& format, const Args&... args) { - log(LogLevel::TRACE, - std::vformat(format, std::make_format_args(args...))); + void trace(const std::string& format, Args&&... args) { + log(LogLevel::TRACE, std::format(format, std::forward(args)...)); } /** @@ -83,9 +90,8 @@ class Logger { * @param args The arguments to format. */ template - void debug(const std::string& format, const Args&... args) { - log(LogLevel::DEBUG, - std::vformat(format, std::make_format_args(args...))); + void debug(const std::string& format, Args&&... args) { + log(LogLevel::DEBUG, std::format(format, std::forward(args)...)); } /** @@ -95,9 +101,8 @@ class Logger { * @param args The arguments to format. */ template - void info(const std::string& format, const Args&... args) { - log(LogLevel::INFO, - std::vformat(format, std::make_format_args(args...))); + void info(const std::string& format, Args&&... args) { + log(LogLevel::INFO, std::format(format, std::forward(args)...)); } /** @@ -107,9 +112,8 @@ class Logger { * @param args The arguments to format. */ template - void warn(const std::string& format, const Args&... args) { - log(LogLevel::WARN, - std::vformat(format, std::make_format_args(args...))); + void warn(const std::string& format, Args&&... args) { + log(LogLevel::WARN, std::format(format, std::forward(args)...)); } /** @@ -119,9 +123,8 @@ class Logger { * @param args The arguments to format. */ template - void error(const std::string& format, const Args&... args) { - log(LogLevel::ERROR, - std::vformat(format, std::make_format_args(args...))); + void error(const std::string& format, Args&&... args) { + log(LogLevel::ERROR, std::format(format, std::forward(args)...)); } /** @@ -131,9 +134,9 @@ class Logger { * @param args The arguments to format. */ template - void critical(const std::string& format, const Args&... args) { + void critical(const std::string& format, Args&&... args) { log(LogLevel::CRITICAL, - std::vformat(format, std::make_format_args(args...))); + std::format(format, std::forward(args)...)); } /** @@ -177,7 +180,15 @@ class Logger { */ void enableSystemLogging(bool enable); + /** + * @brief Registers a custom log level. + * @param name The name of the custom log level. + * @param severity The severity of the custom log level. + */ + void registerCustomLogLevel(const std::string& name, int severity); + private: + class LoggerImpl; // Forward declaration std::shared_ptr impl_; ///< Pointer to the Logger implementation. @@ -191,4 +202,4 @@ class Logger { } // namespace atom::log -#endif // ATOM_LOG_ATOMLOG_HPP +#endif // ATOM_LOG_ATOMLOG_HPP \ No newline at end of file diff --git a/src/atom/log/loguru.cpp b/src/atom/log/loguru.cpp index a60dc236..0211df47 100644 --- a/src/atom/log/loguru.cpp +++ b/src/atom/log/loguru.cpp @@ -147,6 +147,17 @@ LOGURU_ANONYMOUS_NAMESPACE_BEGIN namespace loguru { using namespace std::chrono; +struct FileRotate { + int log_size_cnt; + int log_size_max; + int log_num_max; + bool is_reopening = false; // to prevent recursive call in file_reopen. + std::string log_path; + std::vector log_list; + FILE* file; + std::string mode; +}; + #if LOGURU_WITH_FILEABS struct FileAbs { char path[PATH_MAX]; @@ -158,7 +169,8 @@ struct FileAbs { decltype(steady_clock::now()) last_check_time = steady_clock::now(); }; #else -using FileAbs = FILE*; +// using FileAbs = FILE*; +using FileAbs = FileRotate*; #endif struct Callback { @@ -317,10 +329,105 @@ inline FILE* toFile(void* user_data) { return reinterpret_cast(user_data)->fp; } #else -inline auto toFile(void* user_data) -> FILE* { - return reinterpret_cast(user_data); +inline auto toFile(void* user_data) -> FileRotate* { + return reinterpret_cast(user_data); +} +#endif + +static auto fileLogListInit(FileRotate* file_rotate) -> int { + std::string logFile(file_rotate->log_path); + + struct stat statBuffer; + for (int i = 1;; i++) { + std::string logTmp = logFile + "." + std::to_string(i); + if (stat(logTmp.c_str(), &statBuffer) != 0) { + break; + } + if (file_rotate->mode == "a") { + file_rotate->log_list.insert(file_rotate->log_list.begin(), logTmp); + } else { + std::remove(logTmp.c_str()); + } + } + + return 0; +} + +static auto fileLogInit(FileRotate* file_rotate, char* path, int log_size_m, + int num) -> int { + file_rotate->log_path.assign(path); + file_rotate->log_size_max = log_size_m * 1024 * 1024; + file_rotate->log_num_max = num; + file_rotate->is_reopening = false; + + // Init log_size_cnt + int numBytes = 0; + struct stat statbuffer; + if (stat(file_rotate->log_path.c_str(), &statbuffer) == 0) { + numBytes = statbuffer.st_size; + } + file_rotate->log_size_cnt = numBytes; + + // Init log_list + fileLogListInit(file_rotate); + + return 0; +} + +static auto fileLogRotatingSave(FileRotate* file_rotate) -> int { + std::string log_file(file_rotate->log_path); + + // When log file list greater then max number, del the oldest one + while (file_rotate->log_list.size() >= file_rotate->log_num_max) { + auto it = file_rotate->log_list.begin(); + std::remove((*it).c_str()); + file_rotate->log_list.erase(it); + } + + // Rename the log file list + for (int i = 0; i < file_rotate->log_list.size(); i++) { + std::string name = log_file + "." + + std::to_string(file_rotate->log_list.size() - i + 1); + std::rename(file_rotate->log_list[i].c_str(), name.c_str()); + file_rotate->log_list[i] = name; + } + + // Rename the new log file + std::string logFileNew = log_file + ".1"; + std::rename(log_file.c_str(), logFileNew.c_str()); + + // Push the new log file to vector + file_rotate->log_list.push_back(logFileNew); + + return 0; } + +inline static auto fileLogRotate(FileRotate* file_rotate, int len) -> int { + file_rotate->log_size_cnt += len; + if (file_rotate->log_size_cnt >= file_rotate->log_size_max) { + file_rotate->is_reopening = true; + file_rotate->log_size_cnt = 0; + + fflush(file_rotate->file); + fclose(file_rotate->file); + fileLogRotatingSave(file_rotate); + +#ifdef _WIN32 + errno_t file_error = + fopen_s(&file_rotate->file, file_rotate->log_path.c_str(), "w"); + if (file_error) { +#else + file_rotate->file = fopen(file_rotate->log_path.c_str(), "w"); + if (file_rotate->file == nullptr) { #endif + return -1; + } + + file_rotate->is_reopening = false; + } + + return 0; +} void fileLog(void* user_data, const Message& message) { #if LOGURU_WITH_FILEABS @@ -337,33 +444,39 @@ void fileLog(void* user_data, const Message& message) { file_abs->last_check_time = steady_clock::now(); fileReopen(user_data); } - FILE* file = to_file(user_data); - if (!file) { + FileRotate* file_rotate = to_file(user_data); + if (!file_rotate) { return; } #else - FILE* file = toFile(user_data); + FileRotate* file_rotate = toFile(user_data); #endif - fprintf(file, "%s%s%s%s\n", message.preamble, message.indentation, - message.prefix, message.message); - if (g_flush_interval_ms == 0) { - fflush(file); + int len = fprintf(file_rotate->file, "%s%s%s%s\n", message.preamble, + message.indentation, message.prefix, message.message); + fileLogRotate(file_rotate, len); + if (g_flush_interval_ms == 0 && file_rotate->is_reopening == false) { + fflush(file_rotate->file); } } void fileClose(void* user_data) { - FILE* file = toFile(user_data); - if (file != nullptr) { - fclose(file); + FileRotate* file_rotate = toFile(user_data); + if (file_rotate->file && file_rotate->is_reopening == false) { + fclose(file_rotate->file); } #if LOGURU_WITH_FILEABS delete reinterpret_cast(user_data); #endif + delete file_rotate; } void fileFlush(void* user_data) { - FILE* file = toFile(user_data); - fflush(file); + FileRotate* file_rotate = toFile(user_data); + if (file_rotate->is_reopening == true) { + return; + } + + fflush(file_rotate->file); } #if LOGURU_WITH_FILEABS @@ -371,11 +484,12 @@ void fileReopen(void* user_data) { FileAbs* file_abs = reinterpret_cast(user_data); struct stat st; int ret; - if (!file_abs->fp || (ret = stat(file_abs->path, &st)) == -1 || + iif(!file_abs->file_rotate->file || + (ret = stat(file_abs->path, &st)) == -1 || (st.st_ino != file_abs->st.st_ino)) { - file_abs->is_reopening = true; - if (file_abs->fp) { - fclose(file_abs->fp); + file_abs->file_rotate->is_reopening = true; + if (file_abs->file_rotate->file) { + fclose(file_abs->file_rotate->file); } if (!file_abs->fp) { VLOG_F(g_internal_verbosity, @@ -397,13 +511,13 @@ void fileReopen(void* user_data) { LOG_F(ERROR, "Failed to create directories to '" LOGURU_FMT(s) "'", file_abs->path); } - file_abs->fp = fopen(file_abs->path, file_abs->mode_str); - if (!file_abs->fp) { + file_abs->file_rotate->file = fopen(file_abs->path, file_abs->mode_str); + if (!file_abs->file_rotate->file) { LOG_F(ERROR, "Failed to open '" LOGURU_FMT(s) "'", file_abs->path); } else { stat(file_abs->path, &file_abs->st); } - file_abs->is_reopening = false; + file_abs->file_rotate->is_reopening = false; } } #endif @@ -848,7 +962,8 @@ bool create_directories(const char* file_path_const) { free(file_path); return true; } -bool add_file(const char* path_in, FileMode mode, Verbosity verbosity) { +bool add_file(const char* path_in, FileMode mode, Verbosity verbosity, + int log_size_m, int file_num) { char path[PATH_MAX]; if (path_in[0] == '~') { snprintf(path, sizeof(path) - 1, "%s%s", home_dir(), path_in + 1); @@ -873,17 +988,27 @@ bool add_file(const char* path_in, FileMode mode, Verbosity verbosity) { return false; } #if LOGURU_WITH_FILEABS - FileAbs* file_abs = new FileAbs(); // this is deleted in file_close; + FileAbs* file_abs = new FileAbs(); // this is deleted in file_close; + file_abs->file_rotate = new FileRotate(); // this is deleted in file_close; snprintf(file_abs->path, sizeof(file_abs->path) - 1, "%s", path); snprintf(file_abs->mode_str, sizeof(file_abs->mode_str) - 1, "%s", mode_str); stat(file_abs->path, &file_abs->st); - file_abs->fp = file; + // file_abs->fp = file; file_abs->verbosity = verbosity; + file_abs->file_rotate->file = file; + file_abs->file_rotate->mode.assign(mode_str); + fileLogInit(&file_abs->file_rotate, path, log_size_m, file_num); add_callback(path_in, file_log, file_abs, verbosity, file_close, file_flush); #else - add_callback(path_in, fileLog, file, verbosity, fileClose, fileFlush); + FileRotate* file_rotate = + new FileRotate(); // this is deleted in file_close; + file_rotate->file = file; + file_rotate->mode.assign(mode_str); + fileLogInit(file_rotate, path, log_size_m, file_num); + add_callback(path_in, fileLog, file_rotate, verbosity, fileClose, + fileFlush); #endif if (mode == FileMode::Append) { diff --git a/src/atom/log/loguru.hpp b/src/atom/log/loguru.hpp index 8c765a12..62183362 100644 --- a/src/atom/log/loguru.hpp +++ b/src/atom/log/loguru.hpp @@ -611,7 +611,8 @@ enum FileMode { Truncate, Append }; the same path. */ LOGURU_EXPORT -auto add_file(const char* path, FileMode mode, Verbosity verbosity) -> bool; +auto add_file(const char* path, FileMode mode, Verbosity verbosity, + int log_size_m = 2, int file_num = 3) -> bool; LOGURU_EXPORT // Send logs to syslog with LOG_USER facility (see next call) diff --git a/src/atom/system/command.cpp b/src/atom/system/command.cpp index 7f814a69..ea62f060 100644 --- a/src/atom/system/command.cpp +++ b/src/atom/system/command.cpp @@ -523,4 +523,14 @@ auto startProcess(const std::string &command) -> std::pair { #endif } +auto isCommandAvailable(const std::string &command) -> bool { + std::string checkCommand; +#ifdef _WIN32 + checkCommand = "where " + command + " > nul 2>&1"; +#else + checkCommand = "command -v " + command + " > /dev/null 2>&1"; +#endif + return atom::system::executeCommandSimple(checkCommand); +} + } // namespace atom::system diff --git a/src/atom/system/command.hpp b/src/atom/system/command.hpp index e0a46cc1..05ca482c 100644 --- a/src/atom/system/command.hpp +++ b/src/atom/system/command.hpp @@ -150,6 +150,14 @@ ATOM_NODISCARD auto executeCommandSimple(const std::string &command) -> bool; * as a void pointer. */ auto startProcess(const std::string &command) -> std::pair; + +/** + * @brief Check if a command is available in the system. + * + * @param command The command to check. + * @return A boolean indicating whether the command is available. + */ +auto isCommandAvailable(const std::string &command) -> bool; } // namespace atom::system #endif diff --git a/src/atom/tests/fuzz.cpp b/src/atom/tests/fuzz.cpp index 0c72aca2..e34fdb50 100644 --- a/src/atom/tests/fuzz.cpp +++ b/src/atom/tests/fuzz.cpp @@ -53,19 +53,16 @@ auto RandomDataGenerator::generateString(int length, } auto RandomDataGenerator::generateBooleans(int count) -> std::vector { -#if __cplusplus >= 202302L - return std::views::iota(0, count) | std::views::transform([this](auto) { - return std::bernoulli_distribution(0.5)(generator_); - }) | - std::ranges::to(); -#else + // return std::views::iota(0, count) | std::views::transform([this](auto) { + // return std::bernoulli_distribution(0.5)(generator_); + // }) | + // std::ranges::to(); std::vector result; result.reserve(count); for (int i = 0; i < count; ++i) { result.push_back(std::bernoulli_distribution(0.5)(generator_)); } return result; -#endif } auto RandomDataGenerator::generateException() -> std::string { diff --git a/src/atom/type/qvariant.hpp b/src/atom/type/qvariant.hpp new file mode 100644 index 00000000..711b2d4d --- /dev/null +++ b/src/atom/type/qvariant.hpp @@ -0,0 +1,415 @@ +#ifndef ATOM_TYPE_QVARIANT_HPP +#define ATOM_TYPE_QVARIANT_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace atom::type { +/** + * @brief A wrapper class for std::variant with additional utility functions. + * + * @tparam Types The types that the variant can hold. + */ +template +class VariantWrapper { +public: + using VariantType = std::variant; + + /** + * @brief Default constructor. + */ + VariantWrapper(); + + /** + * @brief Constructs a VariantWrapper with an initial value. + * + * @tparam T The type of the initial value. + * @param value The initial value to store in the variant. + */ + template + explicit VariantWrapper(T&& value); + + /** + * @brief Copy constructor. + * + * @param other The other VariantWrapper to copy from. + */ + VariantWrapper(const VariantWrapper& other); + + /** + * @brief Move constructor. + * + * @param other The other VariantWrapper to move from. + */ + VariantWrapper(VariantWrapper&& other) noexcept; + + /** + * @brief Copy assignment operator. + * + * @param other The other VariantWrapper to copy from. + * @return A reference to this VariantWrapper. + */ + auto operator=(const VariantWrapper& other) -> VariantWrapper&; + + /** + * @brief Move assignment operator. + * + * @param other The other VariantWrapper to move from. + * @return A reference to this VariantWrapper. + */ + auto operator=(VariantWrapper&& other) noexcept -> VariantWrapper&; + + /** + * @brief Assignment operator for a value. + * + * @tparam T The type of the value. + * @param value The value to assign to the variant. + * @return A reference to this VariantWrapper. + */ + template + auto operator=(T&& value) -> VariantWrapper&; + + /** + * @brief Gets the name of the type currently held by the variant. + * + * @return The name of the type as a string. + */ + [[nodiscard]] auto typeName() const -> std::string; + + /** + * @brief Gets the value of the specified type from the variant. + * + * @tparam T The type of the value to get. + * @return The value of the specified type. + * @throws std::bad_variant_access if the variant does not hold the + * specified type. + */ + template + auto get() const -> T; + + /** + * @brief Checks if the variant holds the specified type. + * + * @tparam T The type to check. + * @return True if the variant holds the specified type, false otherwise. + */ + template + [[nodiscard]] auto is() const -> bool; + + /** + * @brief Prints the current value of the variant to the standard output. + */ + void print() const; + + /** + * @brief Equality operator. + * + * @param other The other VariantWrapper to compare with. + * @return True if the variants are equal, false otherwise. + */ + [[nodiscard]] auto operator==(const VariantWrapper& other) const -> bool; + + /** + * @brief Inequality operator. + * + * @param other The other VariantWrapper to compare with. + * @return True if the variants are not equal, false otherwise. + */ + [[nodiscard]] auto operator!=(const VariantWrapper& other) const -> bool; + + /** + * @brief Visits the variant with a visitor. + * + * @tparam Visitor The type of the visitor. + * @param visitor The visitor to apply to the variant. + * @return The result of the visitor. + */ + template + auto visit(Visitor&& visitor) const -> decltype(auto); + + /** + * @brief Gets the index of the currently held type in the variant. + * + * @return The index of the currently held type. + */ + [[nodiscard]] auto index() const -> std::size_t; + + /** + * @brief Tries to get the value of the specified type from the variant. + * + * @tparam T The type of the value to get. + * @return An optional containing the value if the variant holds the + * specified type, std::nullopt otherwise. + */ + template + auto tryGet() const -> std::optional; + + /** + * @brief Tries to convert the current value to an int. + * + * @return An optional containing the int value if the conversion is + * successful, std::nullopt otherwise. + */ + [[nodiscard]] auto toInt() const -> std::optional; + + /** + * @brief Tries to convert the current value to a double. + * + * @return An optional containing the double value if the conversion is + * successful, std::nullopt otherwise. + */ + [[nodiscard]] auto toDouble() const -> std::optional; + + /** + * @brief Tries to convert the current value to a bool. + * + * @return An optional containing the bool value if the conversion is + * successful, std::nullopt otherwise. + */ + [[nodiscard]] auto toBool() const -> std::optional; + + /** + * @brief Converts the current value to a string. + * + * @return The string representation of the current value. + */ + [[nodiscard]] auto toString() const -> std::string; + + /** + * @brief Resets the variant to hold std::monostate. + */ + void reset(); + + /** + * @brief Checks if the variant holds a value other than std::monostate. + * + * @return True if the variant holds a value, false otherwise. + */ + [[nodiscard]] auto hasValue() const -> bool; + + /** + * @brief Stream insertion operator for VariantWrapper. + * + * @param outputStream The output stream. + * @param variantWrapper The VariantWrapper to insert into the stream. + * @return The output stream. + */ + friend auto operator<<(std::ostream& outputStream, + const VariantWrapper& variantWrapper) + -> std::ostream&; + + /** + * @brief Default destructor. + */ + ~VariantWrapper() = default; + +private: + VariantType variant_ = VariantType(std::in_place_index<0>); +}; + +// 实现部分 + +template +VariantWrapper::VariantWrapper() = default; + +template +template +VariantWrapper::VariantWrapper(T&& value) + : variant_(std::forward(value)) {} + +template +VariantWrapper::VariantWrapper(const VariantWrapper& other) + : variant_(other.variant_) {} + +template +VariantWrapper::VariantWrapper(VariantWrapper&& other) noexcept + : variant_(std::move(other.variant_)) {} + +template +auto VariantWrapper::operator=(const VariantWrapper& other) + -> VariantWrapper& { + if (this != &other) { + variant_ = other.variant_; + } + return *this; +} + +template +auto VariantWrapper::operator=(VariantWrapper&& other) noexcept + -> VariantWrapper& { + if (this != &other) { + variant_ = std::move(other.variant_); + } + return *this; +} + +template +template +auto VariantWrapper::operator=(T&& value) -> VariantWrapper& { + variant_ = std::forward(value); + return *this; +} + +template +auto VariantWrapper::typeName() const -> std::string { + return std::visit( + [](auto&& arg) -> std::string { return typeid(arg).name(); }, variant_); +} + +template +template +auto VariantWrapper::get() const -> T { + if (!std::holds_alternative(variant_)) { + throw std::bad_variant_access(); + } + return std::get(variant_); +} + +template +template +auto VariantWrapper::is() const -> bool { + return std::holds_alternative(variant_); +} + +template +void VariantWrapper::print() const { + std::visit( + [](const auto& value) { + if constexpr (std::is_same_v, + std::monostate>) { + std::cout << "Current value: std::monostate" << std::endl; + } else { + std::cout << "Current value: " << value << std::endl; + } + }, + variant_); +} + +template +auto VariantWrapper::operator==(const VariantWrapper& other) const + -> bool { + return variant_ == other.variant_; +} + +template +auto VariantWrapper::operator!=(const VariantWrapper& other) const + -> bool { + return !(*this == other); +} + +template +template +auto VariantWrapper::visit(Visitor&& visitor) const + -> decltype(auto) { + return std::visit(std::forward(visitor), variant_); +} + +template +auto VariantWrapper::index() const -> std::size_t { + return variant_.index(); +} + +template +template +auto VariantWrapper::tryGet() const -> std::optional { + if (is()) { + return get(); + } + return std::nullopt; +} + +template +auto VariantWrapper::toInt() const -> std::optional { + return visit([](auto&& arg) -> std::optional { + using T = std::decay_t; + if constexpr (std::is_convertible_v) { + return static_cast(arg); + } else if constexpr (std::is_convertible_v) { + try { + return std::stoi(arg); + } catch (...) { + return std::nullopt; + } + } else { + return std::nullopt; + } + }); +} + +template +auto VariantWrapper::toDouble() const -> std::optional { + return visit([](auto&& arg) -> std::optional { + using T = std::decay_t; + if constexpr (std::is_convertible_v) { + return static_cast(arg); + } else if constexpr (std::is_convertible_v) { + try { + return std::stod(arg); + } catch (...) { + return std::nullopt; + } + } else { + return std::nullopt; + } + }); +} + +template +auto VariantWrapper::toBool() const -> std::optional { + return visit([](auto&& arg) -> std::optional { + using T = std::decay_t; + if constexpr (std::is_convertible_v) { + return static_cast(arg); + } else if constexpr (std::is_convertible_v) { + if (arg == "true") { + return true; + } + if (arg == "false") { + return false; + } + return std::nullopt; + } else { + return std::nullopt; + } + }); +} + +template +auto VariantWrapper::toString() const -> std::string { + return visit([](auto&& arg) -> std::string { + if constexpr (std::is_same_v, + std::monostate>) { + return "std::monostate"; + } else { + std::ostringstream oss; + oss << arg; + return oss.str(); + } + }); +} + +template +void VariantWrapper::reset() { + variant_.template emplace(); +} + +template +auto VariantWrapper::hasValue() const -> bool { + return variant_.index() != 0; // std::monostate is the first type +} + +template +auto operator<<(std::ostream& outputStream, + const VariantWrapper& variantWrapper) + -> std::ostream& { + variantWrapper.print(); + return outputStream; +} +} // namespace atom::type + +#endif \ No newline at end of file diff --git a/src/atom/utils/container.hpp b/src/atom/utils/container.hpp index e8fe4a20..b1970605 100644 --- a/src/atom/utils/container.hpp +++ b/src/atom/utils/container.hpp @@ -265,6 +265,18 @@ auto transformToVector(const Container& source, MemberFunc memberFunc) { return result; // 返回新容器 } +template + requires std::ranges::input_range && requires { + typename MapContainer::key_type; + typename MapContainer::mapped_type; + } +auto unique(const MapContainer& container) { + std::unordered_map + map(container.begin(), container.end()); + return map; +} + /** * @brief Removes duplicate elements from a container. * diff --git a/src/atom/utils/qtimezone.cpp b/src/atom/utils/qtimezone.cpp index ca199c00..218d230e 100644 --- a/src/atom/utils/qtimezone.cpp +++ b/src/atom/utils/qtimezone.cpp @@ -166,10 +166,12 @@ auto QTimeZone::isDaylightTime(const QDateTime& dateTime) const -> bool { static constexpr int K_FIRST_SUNDAY = 1; static constexpr int K_ONE_WEEK = 7; - std::tm startDST = {0, 0, 2, K_SECOND_SUNDAY, K_MARCH, localTime.tm_year, - 0, 0, -1}; // March 8th 2:00 AM - std::tm endDST = {0, 0, 2, K_FIRST_SUNDAY, K_NOVEMBER, localTime.tm_year, - 0, 0, -1}; // November 1st 2:00 AM + std::tm startDST = { + 0, 0, 2, K_SECOND_SUNDAY, K_MARCH, localTime.tm_year, 0, + 0, -1, 0}; // March 8th 2:00 AM + std::tm endDST = { + 0, 0, 2, K_FIRST_SUNDAY, K_NOVEMBER, localTime.tm_year, 0, + 0, -1, 0}; // November 1st 2:00 AM while (startDST.tm_wday != 0) { startDST.tm_mday += 1; diff --git a/src/atom/web/address.hpp b/src/atom/web/address.hpp index a1da9500..e8774a82 100644 --- a/src/atom/web/address.hpp +++ b/src/atom/web/address.hpp @@ -22,216 +22,427 @@ Description: Enhanced Address class for IPv4, IPv6, and Unix domain sockets. namespace atom::web { /** * @class Address - * @brief 基础类,表示通用的网络地址。 + * @brief A base class representing a generic network address. */ class Address { protected: - std::string addressStr; ///< 存储地址的字符串形式。 + std::string addressStr; ///< Stores the address as a string. public: Address() = default; + /** - * @brief 虚析构函数。 + * @brief Virtual destructor. */ virtual ~Address() = default; /** - * @brief 拷贝构造函数。 + * @brief Copy constructor. */ Address(const Address& other) = default; /** - * @brief 拷贝赋值运算符。 + * @brief Copy assignment operator. */ Address& operator=(const Address& other) = default; /** - * @brief 移动构造函数。 + * @brief Move constructor. */ Address(Address&& other) noexcept = default; /** - * @brief 移动赋值运算符。 + * @brief Move assignment operator. */ Address& operator=(Address&& other) noexcept = default; /** - * @brief 解析地址字符串。 - * @param address 要解析的地址字符串。 - * @return 如果成功解析返回 true,否则返回 false。 + * @brief Parses the address string. + * @param address The address string to parse. + * @return True if the address is successfully parsed, false otherwise. */ virtual auto parse(const std::string& address) -> bool = 0; /** - * @brief 打印地址类型。 + * @brief Prints the address type. */ virtual void printAddressType() const = 0; /** - * @brief 判断地址是否在指定范围内。 - * @param start 范围的起始地址。 - * @param end 范围的结束地址。 - * @return 如果在范围内返回 true,否则返回 false。 + * @brief Checks if the address is within the specified range. + * @param start The start address of the range. + * @param end The end address of the range. + * @return True if the address is within the range, false otherwise. */ virtual auto isInRange(const std::string& start, const std::string& end) -> bool = 0; /** - * @brief 将地址转换为二进制表示形式。 - * @return 地址的二进制字符串。 + * @brief Converts the address to its binary representation. + * @return The binary representation of the address as a string. */ [[nodiscard]] virtual auto toBinary() const -> std::string = 0; /** - * @brief 获取地址字符串。 - * @return 地址的字符串形式。 + * @brief Gets the address string. + * @return The address as a string. */ [[nodiscard]] auto getAddress() const -> std::string { return addressStr; } /** - * @brief 判断两个地址是否相等。 - * @param other 要比较的另一个地址。 - * @return 如果相等返回 true,否则返回 false。 + * @brief Checks if two addresses are equal. + * @param other The other address to compare with. + * @return True if the addresses are equal, false otherwise. */ [[nodiscard]] virtual auto isEqual(const Address& other) const -> bool = 0; /** - * @brief 获取地址类型。 - * @return 地址类型的字符串。 + * @brief Gets the address type. + * @return The address type as a string. */ [[nodiscard]] virtual auto getType() const -> std::string = 0; /** - * @brief 获取网络地址。 - * @param mask 子网掩码。 - * @return 网络地址的字符串。 + * @brief Gets the network address given a subnet mask. + * @param mask The subnet mask. + * @return The network address as a string. */ [[nodiscard]] virtual auto getNetworkAddress(const std::string& mask) const -> std::string = 0; /** - * @brief 获取广播地址。 - * @param mask 子网掩码。 - * @return 广播地址的字符串。 + * @brief Gets the broadcast address given a subnet mask. + * @param mask The subnet mask. + * @return The broadcast address as a string. */ [[nodiscard]] virtual auto getBroadcastAddress( const std::string& mask) const -> std::string = 0; /** - * @brief 判断两个地址是否在同一子网内。 - * @param other 要比较的另一个地址。 - * @param mask 子网掩码。 - * @return 如果在同一子网内返回 true,否则返回 false。 + * @brief Checks if two addresses are in the same subnet. + * @param other The other address to compare with. + * @param mask The subnet mask. + * @return True if the addresses are in the same subnet, false otherwise. */ [[nodiscard]] virtual auto isSameSubnet( const Address& other, const std::string& mask) const -> bool = 0; /** - * @brief 将地址转换为十六进制字符串。 - * @return 地址的十六进制字符串。 + * @brief Converts the address to its hexadecimal representation. + * @return The hexadecimal representation of the address as a string. */ [[nodiscard]] virtual auto toHex() const -> std::string = 0; }; /** * @class IPv4 - * @brief 表示 IPv4 地址的类。 + * @brief A class representing an IPv4 address. */ class IPv4 : public Address { public: IPv4() = default; + + /** + * @brief Constructs an IPv4 address from a string. + * @param address The IPv4 address as a string. + */ explicit IPv4(const std::string& address); + + /** + * @brief Parses the IPv4 address string. + * @param address The IPv4 address string to parse. + * @return True if the address is successfully parsed, false otherwise. + */ auto parse(const std::string& address) -> bool override; + + /** + * @brief Prints the address type. + */ void printAddressType() const override; + + /** + * @brief Checks if the address is within the specified range. + * @param start The start address of the range. + * @param end The end address of the range. + * @return True if the address is within the range, false otherwise. + */ auto isInRange(const std::string& start, const std::string& end) -> bool override; + + /** + * @brief Converts the address to its binary representation. + * @return The binary representation of the address as a string. + */ [[nodiscard]] auto toBinary() const -> std::string override; + + /** + * @brief Checks if two addresses are equal. + * @param other The other address to compare with. + * @return True if the addresses are equal, false otherwise. + */ [[nodiscard]] auto isEqual(const Address& other) const -> bool override; + + /** + * @brief Gets the address type. + * @return The address type as a string. + */ [[nodiscard]] auto getType() const -> std::string override; + + /** + * @brief Gets the network address given a subnet mask. + * @param mask The subnet mask. + * @return The network address as a string. + */ [[nodiscard]] auto getNetworkAddress(const std::string& mask) const -> std::string override; + + /** + * @brief Gets the broadcast address given a subnet mask. + * @param mask The subnet mask. + * @return The broadcast address as a string. + */ [[nodiscard]] auto getBroadcastAddress(const std::string& mask) const -> std::string override; + + /** + * @brief Checks if two addresses are in the same subnet. + * @param other The other address to compare with. + * @param mask The subnet mask. + * @return True if the addresses are in the same subnet, false otherwise. + */ [[nodiscard]] auto isSameSubnet( const Address& other, const std::string& mask) const -> bool override; + + /** + * @brief Converts the address to its hexadecimal representation. + * @return The hexadecimal representation of the address as a string. + */ [[nodiscard]] auto toHex() const -> std::string override; /** - * @brief 解析 CIDR 格式的 IP 地址。 - * @param cidr CIDR 格式的字符串。 - * @return 如果成功解析返回 true,否则返回 false。 + * @brief Parses an IPv4 address in CIDR notation. + * @param cidr The CIDR notation string. + * @return True if the CIDR notation is successfully parsed, false + * otherwise. */ auto parseCIDR(const std::string& cidr) -> bool; private: - uint32_t ipValue{0}; ///< 以整数形式存储 IP 地址。 + uint32_t ipValue{0}; ///< Stores the IP address as an integer. + /** + * @brief Converts an IP address string to an integer. + * @param ipAddr The IP address string. + * @return The IP address as an integer. + */ [[nodiscard]] auto ipToInteger(const std::string& ipAddr) const -> uint32_t; + + /** + * @brief Converts an integer to an IP address string. + * @param ipAddr The IP address as an integer. + * @return The IP address string. + */ [[nodiscard]] auto integerToIp(uint32_t ipAddr) const -> std::string; }; /** * @class IPv6 - * @brief 表示 IPv6 地址的类。 + * @brief A class representing an IPv6 address. */ class IPv6 : public Address { public: IPv6() = default; + + /** + * @brief Constructs an IPv6 address from a string. + * @param address The IPv6 address as a string. + */ explicit IPv6(const std::string& address); + + /** + * @brief Parses the IPv6 address string. + * @param address The IPv6 address string to parse. + * @return True if the address is successfully parsed, false otherwise. + */ auto parse(const std::string& address) -> bool override; + + /** + * @brief Prints the address type. + */ void printAddressType() const override; + + /** + * @brief Checks if the address is within the specified range. + * @param start The start address of the range. + * @param end The end address of the range. + * @return True if the address is within the range, false otherwise. + */ auto isInRange(const std::string& start, const std::string& end) -> bool override; + + /** + * @brief Converts the address to its binary representation. + * @return The binary representation of the address as a string. + */ [[nodiscard]] auto toBinary() const -> std::string override; + + /** + * @brief Checks if two addresses are equal. + * @param other The other address to compare with. + * @return True if the addresses are equal, false otherwise. + */ [[nodiscard]] auto isEqual(const Address& other) const -> bool override; + + /** + * @brief Gets the address type. + * @return The address type as a string. + */ [[nodiscard]] auto getType() const -> std::string override; + + /** + * @brief Gets the network address given a subnet mask. + * @param mask The subnet mask. + * @return The network address as a string. + */ [[nodiscard]] auto getNetworkAddress(const std::string& mask) const -> std::string override; + + /** + * @brief Gets the broadcast address given a subnet mask. + * @param mask The subnet mask. + * @return The broadcast address as a string. + */ [[nodiscard]] auto getBroadcastAddress(const std::string& mask) const -> std::string override; + + /** + * @brief Checks if two addresses are in the same subnet. + * @param other The other address to compare with. + * @param mask The subnet mask. + * @return True if the addresses are in the same subnet, false otherwise. + */ [[nodiscard]] auto isSameSubnet( const Address& other, const std::string& mask) const -> bool override; + + /** + * @brief Converts the address to its hexadecimal representation. + * @return The hexadecimal representation of the address as a string. + */ [[nodiscard]] auto toHex() const -> std::string override; /** - * @brief 解析 CIDR 格式的 IPv6 地址。 - * @param cidr CIDR 格式的字符串。 - * @return 如果成功解析返回 true,否则返回 false。 + * @brief Parses an IPv6 address in CIDR notation. + * @param cidr The CIDR notation string. + * @return True if the CIDR notation is successfully parsed, false + * otherwise. */ auto parseCIDR(const std::string& cidr) -> bool; private: - std::vector ipSegments; ///< 存储 IP 地址的段。 + std::vector ipSegments; ///< Stores the IP address segments. + /** + * @brief Converts an IP address string to a vector of segments. + * @param ipAddr The IP address string. + * @return The IP address as a vector of segments. + */ [[nodiscard]] auto ipToVector(const std::string& ipAddr) const -> std::vector; + + /** + * @brief Converts a vector of segments to an IP address string. + * @param segments The IP address segments. + * @return The IP address string. + */ [[nodiscard]] auto vectorToIp(const std::vector& segments) const -> std::string; }; /** * @class UnixDomain - * @brief 表示 Unix 域套接字地址的类。 + * @brief A class representing a Unix domain socket address. */ class UnixDomain : public Address { public: UnixDomain() = default; + + /** + * @brief Constructs a Unix domain socket address from a path. + * @param path The Unix domain socket path. + */ explicit UnixDomain(const std::string& path); + + /** + * @brief Parses the Unix domain socket path. + * @param path The Unix domain socket path to parse. + * @return True if the path is successfully parsed, false otherwise. + */ auto parse(const std::string& path) -> bool override; + + /** + * @brief Prints the address type. + */ void printAddressType() const override; + + /** + * @brief Checks if the address is within the specified range. + * @param start The start address of the range. + * @param end The end address of the range. + * @return True if the address is within the range, false otherwise. + */ auto isInRange(const std::string& start, const std::string& end) -> bool override; + + /** + * @brief Converts the address to its binary representation. + * @return The binary representation of the address as a string. + */ [[nodiscard]] auto toBinary() const -> std::string override; + + /** + * @brief Checks if two addresses are equal. + * @param other The other address to compare with. + * @return True if the addresses are equal, false otherwise. + */ [[nodiscard]] auto isEqual(const Address& other) const -> bool override; + + /** + * @brief Gets the address type. + * @return The address type as a string. + */ [[nodiscard]] auto getType() const -> std::string override; + + /** + * @brief Gets the network address given a subnet mask. + * @param mask The subnet mask. + * @return The network address as a string. + */ [[nodiscard]] auto getNetworkAddress(const std::string& mask) const -> std::string override; + + /** + * @brief Gets the broadcast address given a subnet mask. + * @param mask The subnet mask. + * @return The broadcast address as a string. + */ [[nodiscard]] auto getBroadcastAddress(const std::string& mask) const -> std::string override; + + /** + * @brief Checks if two addresses are in the same subnet. + * @param other The other address to compare with. + * @param mask The subnet mask. + * @return True if the addresses are in the same subnet, false otherwise. + */ [[nodiscard]] auto isSameSubnet( const Address& other, const std::string& mask) const -> bool override; + + /** + * @brief Converts the address to its hexadecimal representation. + * @return The hexadecimal representation of the address as a string. + */ [[nodiscard]] auto toHex() const -> std::string override; }; } // namespace atom::web -#endif // ATOM_WEB_ADDRESS_HPP +#endif // ATOM_WEB_ADDRESS_HPP \ No newline at end of file diff --git a/src/atom/web/minetype.hpp b/src/atom/web/minetype.hpp index 8923594e..20184fef 100644 --- a/src/atom/web/minetype.hpp +++ b/src/atom/web/minetype.hpp @@ -6,23 +6,76 @@ #include #include +/** + * @class MimeTypes + * @brief A class for handling MIME types and file extensions. + */ class MimeTypes { public: + /** + * @brief Constructs a MimeTypes object. + * @param knownFiles A vector of known file paths. + * @param lenient A flag indicating whether to be lenient in MIME type + * detection. + */ MimeTypes(const std::vector& knownFiles, bool lenient = false); + + /** + * @brief Destructor. + */ ~MimeTypes(); + /** + * @brief Reads MIME types from a JSON file. + * @param jsonFile The path to the JSON file. + */ void readJson(const std::string& jsonFile); + + /** + * @brief Guesses the MIME type and charset of a URL. + * @param url The URL to guess the MIME type for. + * @return A pair containing the guessed MIME type and charset, if + * available. + */ std::pair, std::optional> guessType( const std::string& url); + + /** + * @brief Guesses all possible file extensions for a given MIME type. + * @param mimeType The MIME type to guess extensions for. + * @return A vector of possible file extensions. + */ std::vector guessAllExtensions(const std::string& mimeType); + + /** + * @brief Guesses the file extension for a given MIME type. + * @param mimeType The MIME type to guess the extension for. + * @return The guessed file extension, if available. + */ std::optional guessExtension(const std::string& mimeType); + + /** + * @brief Adds a new MIME type and file extension pair. + * @param mimeType The MIME type to add. + * @param extension The file extension to associate with the MIME type. + */ void addType(const std::string& mimeType, const std::string& extension); + + /** + * @brief Lists all known MIME types and their associated file extensions. + */ void listAllTypes() const; + + /** + * @brief Guesses the MIME type of a file based on its content. + * @param filePath The path to the file. + * @return The guessed MIME type, if available. + */ std::optional guessTypeByContent(const std::string& filePath); private: - class Impl; - std::unique_ptr pImpl; + class Impl; ///< Forward declaration of the implementation class. + std::unique_ptr pImpl; ///< Pointer to the implementation. }; -#endif // MIMETYPES_H +#endif // MIMETYPES_H \ No newline at end of file diff --git a/src/atom/web/time.cpp b/src/atom/web/time.cpp index 86469e90..22b7a1fe 100644 --- a/src/atom/web/time.cpp +++ b/src/atom/web/time.cpp @@ -522,4 +522,8 @@ auto TimeManager::getNtpTime(const std::string &hostname) -> std::time_t { return ntpTime; } +void TimeManager::setImpl(std::unique_ptr impl) { + impl_ = std::move(impl); +} + } // namespace atom::web diff --git a/src/atom/web/time.hpp b/src/atom/web/time.hpp index 5a61e325..37f62746 100644 --- a/src/atom/web/time.hpp +++ b/src/atom/web/time.hpp @@ -82,6 +82,12 @@ class TimeManager { */ auto getNtpTime(const std::string &hostname) -> std::time_t; + /** + * @brief Sets the implementation for testing purposes. + * @param impl The implementation to set. + */ + void setImpl(std::unique_ptr impl); + private: std::unique_ptr impl_; ///< Pointer to the implementation, using Pimpl idiom to hide diff --git a/src/utils/constant.hpp b/src/utils/constant.hpp index d474e007..2c3e18a2 100644 --- a/src/utils/constant.hpp +++ b/src/utils/constant.hpp @@ -75,6 +75,8 @@ class Constants { DEFINE_LITHIUM_CONSTANT(DEVICE_LOADER) DEFINE_LITHIUM_CONSTANT(DEVICE_MANAGER) + DEFINE_LITHIUM_CONSTANT(THREAD_POOL) + // QHY Compatibility DEFINE_LITHIUM_CONSTANT(DRIVERS_LIST) DEFINE_LITHIUM_CONSTANT(SYSTEM_DEVICE_LIST) diff --git a/tests/atom/extra/inicpp/common.cpp b/tests/atom/extra/inicpp/common.cpp new file mode 100644 index 00000000..69f32f5b --- /dev/null +++ b/tests/atom/extra/inicpp/common.cpp @@ -0,0 +1,72 @@ +#include + +#include +#include + +#include "atom/extra/inicpp/common.hpp" + +using namespace inicpp; + +// Test whitespaces function +TEST(CommonTest, Whitespaces) { + std::string_view ws = whitespaces(); + EXPECT_EQ(ws, " \t\n\r\f\v"); +} + +// Test indents function +TEST(CommonTest, Indents) { + std::string_view ind = indents(); + EXPECT_EQ(ind, " \t"); +} + +// Test trim function +TEST(CommonTest, Trim) { + std::string str = " Hello, World! "; + trim(str); + EXPECT_EQ(str, "Hello, World!"); + + str = "NoLeadingOrTrailingSpaces"; + trim(str); + EXPECT_EQ(str, "NoLeadingOrTrailingSpaces"); + + str = " "; + trim(str); + EXPECT_EQ(str, ""); +} + +// Test strToLong function +TEST(CommonTest, StrToLong) { + std::optional result = strToLong("12345"); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result.value(), 12345); + + result = strToLong("abc"); + EXPECT_FALSE(result.has_value()); + + result = strToLong(""); + EXPECT_FALSE(result.has_value()); +} + +// Test strToULong function +TEST(CommonTest, StrToULong) { + std::optional result = strToULong("12345"); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result.value(), 12345); + + result = strToULong("abc"); + EXPECT_FALSE(result.has_value()); + + result = strToULong(""); + EXPECT_FALSE(result.has_value()); +} + +// Test StringInsensitiveLess struct +TEST(CommonTest, StringInsensitiveLess) { + StringInsensitiveLess cmp; + + EXPECT_TRUE(cmp("apple", "Banana")); + EXPECT_FALSE(cmp("Banana", "apple")); + EXPECT_FALSE(cmp("apple", "apple")); + EXPECT_TRUE(cmp("apple", "APPLE")); + EXPECT_FALSE(cmp("APPLE", "apple")); +} diff --git a/tests/atom/extra/inicpp/convert.cpp b/tests/atom/extra/inicpp/convert.cpp new file mode 100644 index 00000000..dadf7c92 --- /dev/null +++ b/tests/atom/extra/inicpp/convert.cpp @@ -0,0 +1,299 @@ +#include + +#include "atom/extra/inicpp/convert.hpp" + +#include +#include + +using namespace inicpp; + +// Test Convert +TEST(ConvertTest, BoolDecode) { + Convert converter; + bool result; + + converter.decode("TRUE", result); + EXPECT_TRUE(result); + + converter.decode("FALSE", result); + EXPECT_FALSE(result); + + EXPECT_THROW(converter.decode("INVALID", result), std::invalid_argument); +} + +TEST(ConvertTest, BoolEncode) { + Convert converter; + std::string result; + + converter.encode(true, result); + EXPECT_EQ(result, "true"); + + converter.encode(false, result); + EXPECT_EQ(result, "false"); +} + +// Test Convert +TEST(ConvertTest, CharDecode) { + Convert converter; + char result; + + converter.decode("A", result); + EXPECT_EQ(result, 'A'); + + EXPECT_THROW(converter.decode("", result), std::invalid_argument); +} + +TEST(ConvertTest, CharEncode) { + Convert converter; + std::string result; + + converter.encode('A', result); + EXPECT_EQ(result, "A"); +} + +// Test Convert +TEST(ConvertTest, UnsignedCharDecode) { + Convert converter; + unsigned char result; + + converter.decode("A", result); + EXPECT_EQ(result, 'A'); + + EXPECT_THROW(converter.decode("", result), std::invalid_argument); +} + +TEST(ConvertTest, UnsignedCharEncode) { + Convert converter; + std::string result; + + converter.encode('A', result); + EXPECT_EQ(result, "A"); +} + +// Test Convert +TEST(ConvertTest, ShortDecode) { + Convert converter; + short result; + + converter.decode("123", result); + EXPECT_EQ(result, 123); + + EXPECT_THROW(converter.decode("INVALID", result), std::invalid_argument); +} + +TEST(ConvertTest, ShortEncode) { + Convert converter; + std::string result; + + converter.encode(123, result); + EXPECT_EQ(result, "123"); +} + +// Test Convert +TEST(ConvertTest, UnsignedShortDecode) { + Convert converter; + unsigned short result; + + converter.decode("123", result); + EXPECT_EQ(result, 123); + + EXPECT_THROW(converter.decode("INVALID", result), std::invalid_argument); +} + +TEST(ConvertTest, UnsignedShortEncode) { + Convert converter; + std::string result; + + converter.encode(123, result); + EXPECT_EQ(result, "123"); +} + +// Test Convert +TEST(ConvertTest, IntDecode) { + Convert converter; + int result; + + converter.decode("123", result); + EXPECT_EQ(result, 123); + + EXPECT_THROW(converter.decode("INVALID", result), std::invalid_argument); +} + +TEST(ConvertTest, IntEncode) { + Convert converter; + std::string result; + + converter.encode(123, result); + EXPECT_EQ(result, "123"); +} + +// Test Convert +TEST(ConvertTest, UnsignedIntDecode) { + Convert converter; + unsigned int result; + + converter.decode("123", result); + EXPECT_EQ(result, 123); + + EXPECT_THROW(converter.decode("INVALID", result), std::invalid_argument); +} + +TEST(ConvertTest, UnsignedIntEncode) { + Convert converter; + std::string result; + + converter.encode(123, result); + EXPECT_EQ(result, "123"); +} + +// Test Convert +TEST(ConvertTest, LongDecode) { + Convert converter; + long result; + + converter.decode("123", result); + EXPECT_EQ(result, 123); + + EXPECT_THROW(converter.decode("INVALID", result), std::invalid_argument); +} + +TEST(ConvertTest, LongEncode) { + Convert converter; + std::string result; + + converter.encode(123, result); + EXPECT_EQ(result, "123"); +} + +// Test Convert +TEST(ConvertTest, UnsignedLongDecode) { + Convert converter; + unsigned long result; + + converter.decode("123", result); + EXPECT_EQ(result, 123); + + EXPECT_THROW(converter.decode("INVALID", result), std::invalid_argument); +} + +TEST(ConvertTest, UnsignedLongEncode) { + Convert converter; + std::string result; + + converter.encode(123, result); + EXPECT_EQ(result, "123"); +} + +// Test Convert +TEST(ConvertTest, DoubleDecode) { + Convert converter; + double result; + + converter.decode("123.45", result); + EXPECT_EQ(result, 123.45); + + EXPECT_THROW(converter.decode("INVALID", result), std::invalid_argument); +} + +TEST(ConvertTest, DoubleEncode) { + Convert converter; + std::string result; + + converter.encode(123.45, result); + EXPECT_EQ(result, "123.450000"); +} + +// Test Convert +TEST(ConvertTest, FloatDecode) { + Convert converter; + float result; + + converter.decode("123.45", result); + EXPECT_EQ(result, 123.45f); + + EXPECT_THROW(converter.decode("INVALID", result), std::invalid_argument); +} + +TEST(ConvertTest, FloatEncode) { + Convert converter; + std::string result; + + converter.encode(123.45f, result); + EXPECT_EQ(result, "123.450000"); +} + +// Test Convert +TEST(ConvertTest, StringDecode) { + Convert converter; + std::string result; + + converter.decode("Hello, World!", result); + EXPECT_EQ(result, "Hello, World!"); +} + +TEST(ConvertTest, StringEncode) { + Convert converter; + std::string result; + + converter.encode("Hello, World!", result); + EXPECT_EQ(result, "Hello, World!"); +} + +#ifdef __cpp_lib_string_view +// Test Convert +TEST(ConvertTest, StringViewDecode) { + Convert converter; + std::string_view result; + + converter.decode("Hello, World!", result); + EXPECT_EQ(result, "Hello, World!"); +} + +TEST(ConvertTest, StringViewEncode) { + Convert converter; + std::string result; + + converter.encode("Hello, World!", result); + EXPECT_EQ(result, "Hello, World!"); +} +#endif + +// Test Convert +TEST(ConvertTest, ConstCharPtrDecode) { + Convert converter; + const char* result; + + converter.decode("Hello, World!", result); + EXPECT_STREQ(result, "Hello, World!"); +} + +TEST(ConvertTest, ConstCharPtrEncode) { + Convert converter; + std::string result; + + const char* value = "Hello, World!"; + converter.encode(value, result); + EXPECT_EQ(result, "Hello, World!"); +} + +// Test Convert +TEST(ConvertTest, CharArrayDecode) { + Convert converter; + char result[20]; + + converter.decode("Hello, World!", result); + EXPECT_STREQ(result, "Hello, World!"); + + EXPECT_THROW( + converter.decode("This string is too long for the array", result), + std::invalid_argument); +} + +TEST(ConvertTest, CharArrayEncode) { + Convert converter; + std::string result; + + char value[20] = "Hello, World!"; + converter.encode(value, result); + EXPECT_EQ(result, "Hello, World!"); +} diff --git a/tests/atom/extra/inicpp/field.cpp b/tests/atom/extra/inicpp/field.cpp new file mode 100644 index 00000000..f8d5146a --- /dev/null +++ b/tests/atom/extra/inicpp/field.cpp @@ -0,0 +1,78 @@ +#include + +#include "atom/extra/inicpp/field.hpp" + +#include +#include + +using namespace inicpp; + +// Test default constructor +TEST(IniFieldTest, DefaultConstructor) { + IniField field; + EXPECT_NO_THROW(field.as()); +} + +// Test constructor with value +TEST(IniFieldTest, ConstructorWithValue) { + IniField field("test_value"); + EXPECT_EQ(field.as(), "test_value"); +} + +// Test copy constructor +TEST(IniFieldTest, CopyConstructor) { + IniField field1("test_value"); + IniField field2(field1); + EXPECT_EQ(field2.as(), "test_value"); +} + +// Test as method for different types +TEST(IniFieldTest, AsMethod) { + IniField field1("true"); + EXPECT_EQ(field1.as(), true); + + IniField field2("A"); + EXPECT_EQ(field2.as(), 'A'); + + IniField field3("123"); + EXPECT_EQ(field3.as(), 123); + + IniField field4("123.45"); + EXPECT_EQ(field4.as(), 123.45); + + IniField field5("test_string"); + EXPECT_EQ(field5.as(), "test_string"); +} + +// Test as method with invalid conversion +TEST(IniFieldTest, AsMethodInvalidConversion) { + IniField field("invalid"); + EXPECT_THROW(field.as(), std::invalid_argument); +} + +// Test assignment operator for different types +TEST(IniFieldTest, AssignmentOperator) { + IniField field; + field = true; + EXPECT_EQ(field.as(), true); + + field = 'A'; + EXPECT_EQ(field.as(), 'A'); + + field = 123; + EXPECT_EQ(field.as(), 123); + + field = 123.45; + EXPECT_EQ(field.as(), 123.45); + + field = std::string("test_string"); + EXPECT_EQ(field.as(), "test_string"); +} + +// Test copy assignment operator +TEST(IniFieldTest, CopyAssignmentOperator) { + IniField field1("test_value"); + IniField field2; + field2 = field1; + EXPECT_EQ(field2.as(), "test_value"); +} diff --git a/tests/atom/extra/inicpp/file.cpp b/tests/atom/extra/inicpp/file.cpp new file mode 100644 index 00000000..a61534fa --- /dev/null +++ b/tests/atom/extra/inicpp/file.cpp @@ -0,0 +1,149 @@ +#include + +#include "atom/extra/inicpp/file.hpp" + +#include +#include +#include + +using namespace inicpp; + +// Test default constructor +TEST(IniFileBaseTest, DefaultConstructor) { + IniFile iniFile; + EXPECT_TRUE(iniFile.empty()); +} + +// Test constructor with filename +TEST(IniFileBaseTest, ConstructorWithFilename) { + std::ofstream testFile("test.ini"); + testFile << "[section]\nkey=value\n"; + testFile.close(); + + IniFile iniFile("test.ini"); + EXPECT_EQ(iniFile["section"]["key"].as(), "value"); + + std::remove("test.ini"); +} + +// Test constructor with input stream +TEST(IniFileBaseTest, ConstructorWithInputStream) { + std::istringstream iss("[section]\nkey=value\n"); + IniFile iniFile(iss); + EXPECT_EQ(iniFile["section"]["key"].as(), "value"); +} + +// Test setFieldSep method +TEST(IniFileBaseTest, SetFieldSep) { + IniFile iniFile; + iniFile.setFieldSep(':'); + std::istringstream iss("[section]\nkey:value\n"); + iniFile.decode(iss); + EXPECT_EQ(iniFile["section"]["key"].as(), "value"); +} + +// Test setCommentPrefixes method +TEST(IniFileBaseTest, SetCommentPrefixes) { + IniFile iniFile; + iniFile.setCommentPrefixes({"//"}); + std::istringstream iss("[section]\nkey=value\n//comment\n"); + iniFile.decode(iss); + EXPECT_EQ(iniFile["section"]["key"].as(), "value"); +} + +// Test setEscapeChar method +TEST(IniFileBaseTest, SetEscapeChar) { + IniFile iniFile; + iniFile.setEscapeChar('!'); + std::istringstream iss("[section]\nkey=value\n!#escaped comment\n"); + iniFile.decode(iss); + EXPECT_EQ(iniFile["section"]["key"].as(), "value"); +} + +// Test setMultiLineValues method +TEST(IniFileBaseTest, SetMultiLineValues) { + IniFile iniFile; + iniFile.setMultiLineValues(true); + std::istringstream iss("[section]\nkey=value\n\tcontinued\n"); + iniFile.decode(iss); + EXPECT_EQ(iniFile["section"]["key"].as(), "value\ncontinued"); +} + +// Test allowOverwriteDuplicateFields method +TEST(IniFileBaseTest, AllowOverwriteDuplicateFields) { + IniFile iniFile; + iniFile.allowOverwriteDuplicateFields(false); + std::istringstream iss("[section]\nkey=value\nkey=another_value\n"); + EXPECT_THROW(iniFile.decode(iss), std::logic_error); +} + +// Test decode method with input stream +TEST(IniFileBaseTest, DecodeWithInputStream) { + IniFile iniFile; + std::istringstream iss("[section]\nkey=value\n"); + iniFile.decode(iss); + EXPECT_EQ(iniFile["section"]["key"].as(), "value"); +} + +// Test decode method with string +TEST(IniFileBaseTest, DecodeWithString) { + IniFile iniFile; + std::string content = "[section]\nkey=value\n"; + iniFile.decode(content); + EXPECT_EQ(iniFile["section"]["key"].as(), "value"); +} + +// Test load method +TEST(IniFileBaseTest, Load) { + std::ofstream testFile("test.ini"); + testFile << "[section]\nkey=value\n"; + testFile.close(); + + IniFile iniFile; + iniFile.load("test.ini"); + EXPECT_EQ(iniFile["section"]["key"].as(), "value"); + + std::remove("test.ini"); +} + +// Test encode method with output stream +TEST(IniFileBaseTest, EncodeWithOutputStream) { + IniFile iniFile; + std::istringstream iss("[section]\nkey=value\n"); + iniFile.decode(iss); + + std::ostringstream oss; + iniFile.encode(oss); + EXPECT_EQ(oss.str(), "[section]\nkey=value\n"); +} + +// Test encode method with string +TEST(IniFileBaseTest, EncodeWithString) { + IniFile iniFile; + std::istringstream iss("[section]\nkey=value\n"); + iniFile.decode(iss); + + std::string encoded = iniFile.encode(); + EXPECT_EQ(encoded, "[section]\nkey=value\n"); +} + +// Test save method +TEST(IniFileBaseTest, Save) { + IniFile iniFile; + std::istringstream iss("[section]\nkey=value\n"); + iniFile.decode(iss); + + iniFile.save("test.ini"); + + std::ifstream testFile("test.ini"); + std::string content((std::istreambuf_iterator(testFile)), + std::istreambuf_iterator()); + EXPECT_EQ(content, "[section]\nkey=value\n"); + + std::remove("test.ini"); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/tests/atom/type/qvariant.cpp b/tests/atom/type/qvariant.cpp new file mode 100644 index 00000000..43e1315a --- /dev/null +++ b/tests/atom/type/qvariant.cpp @@ -0,0 +1,181 @@ +#include +#include "qvariant.hpp" + +using namespace atom::type; + +// Test default constructor +TEST(VariantWrapperTest, DefaultConstructor) { + VariantWrapper variant; + EXPECT_EQ(variant.index(), 0); + EXPECT_FALSE(variant.hasValue()); +} + +// Test constructor with initial value +TEST(VariantWrapperTest, ConstructorWithValue) { + VariantWrapper variant(42); + EXPECT_EQ(variant.index(), 1); + EXPECT_TRUE(variant.hasValue()); + EXPECT_EQ(variant.get(), 42); +} + +// Test copy constructor +TEST(VariantWrapperTest, CopyConstructor) { + VariantWrapper variant1(42); + VariantWrapper variant2(variant1); + EXPECT_EQ(variant2.index(), 1); + EXPECT_EQ(variant2.get(), 42); +} + +// Test move constructor +TEST(VariantWrapperTest, MoveConstructor) { + VariantWrapper variant1(42); + VariantWrapper variant2(std::move(variant1)); + EXPECT_EQ(variant2.index(), 1); + EXPECT_EQ(variant2.get(), 42); +} + +// Test copy assignment operator +TEST(VariantWrapperTest, CopyAssignmentOperator) { + VariantWrapper variant1(42); + VariantWrapper variant2; + variant2 = variant1; + EXPECT_EQ(variant2.index(), 1); + EXPECT_EQ(variant2.get(), 42); +} + +// Test move assignment operator +TEST(VariantWrapperTest, MoveAssignmentOperator) { + VariantWrapper variant1(42); + VariantWrapper variant2; + variant2 = std::move(variant1); + EXPECT_EQ(variant2.index(), 1); + EXPECT_EQ(variant2.get(), 42); +} + +// Test assignment operator for a value +TEST(VariantWrapperTest, AssignmentOperatorForValue) { + VariantWrapper variant; + variant = 42; + EXPECT_EQ(variant.index(), 1); + EXPECT_EQ(variant.get(), 42); +} + +// Test typeName method +TEST(VariantWrapperTest, TypeName) { + VariantWrapper variant(42); + EXPECT_EQ(variant.typeName(), typeid(int).name()); +} + +// Test get method +TEST(VariantWrapperTest, Get) { + VariantWrapper variant(42); + EXPECT_EQ(variant.get(), 42); +} + +// Test is method +TEST(VariantWrapperTest, Is) { + VariantWrapper variant(42); + EXPECT_TRUE(variant.is()); + EXPECT_FALSE(variant.is()); +} + +// Test print method +TEST(VariantWrapperTest, Print) { + VariantWrapper variant(42); + testing::internal::CaptureStdout(); + variant.print(); + std::string output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, "Current value: 42\n"); +} + +// Test equality operator +TEST(VariantWrapperTest, EqualityOperator) { + VariantWrapper variant1(42); + VariantWrapper variant2(42); + EXPECT_TRUE(variant1 == variant2); +} + +// Test inequality operator +TEST(VariantWrapperTest, InequalityOperator) { + VariantWrapper variant1(42); + VariantWrapper variant2(43); + EXPECT_TRUE(variant1 != variant2); +} + +// Test visit method +/* +// TODO: Fix this test +TEST(VariantWrapperTest, Visit) { + VariantWrapper variant(42); + auto result = variant.visit([](auto&& arg) { return arg + 1; }); + EXPECT_EQ(result, 43); +} +*/ + +// Test index method +TEST(VariantWrapperTest, Index) { + VariantWrapper variant(42); + EXPECT_EQ(variant.index(), 1); +} + +// Test tryGet method +TEST(VariantWrapperTest, TryGet) { + VariantWrapper variant(42); + auto value = variant.tryGet(); + EXPECT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), 42); +} + +// Test toInt method +TEST(VariantWrapperTest, ToInt) { + VariantWrapper variant(42); + auto value = variant.toInt(); + EXPECT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), 42); +} + +// Test toDouble method +TEST(VariantWrapperTest, ToDouble) { + VariantWrapper variant(42.0); + auto value = variant.toDouble(); + EXPECT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), 42.0); +} + +// Test toBool method +TEST(VariantWrapperTest, ToBool) { + VariantWrapper variant(true); + auto value = variant.toBool(); + EXPECT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), true); +} + +// Test toString method +TEST(VariantWrapperTest, ToString) { + VariantWrapper variant(42); + EXPECT_EQ(variant.toString(), "42"); +} + +// Test reset method +TEST(VariantWrapperTest, Reset) { + VariantWrapper variant(42); + variant.reset(); + EXPECT_EQ(variant.index(), 0); + EXPECT_FALSE(variant.hasValue()); +} + +// Test hasValue method +TEST(VariantWrapperTest, HasValue) { + VariantWrapper variant(42); + EXPECT_TRUE(variant.hasValue()); + variant.reset(); + EXPECT_FALSE(variant.hasValue()); +} + +// Test stream insertion operator +TEST(VariantWrapperTest, StreamInsertionOperator) { + VariantWrapper variant(42); + std::ostringstream oss; + oss << variant; + EXPECT_EQ(oss.str(), "Current value: 42\n"); +} \ No newline at end of file diff --git a/tests/atom/web/address.cpp b/tests/atom/web/address.cpp new file mode 100644 index 00000000..a78e68bb --- /dev/null +++ b/tests/atom/web/address.cpp @@ -0,0 +1,221 @@ +#include +#include "atom/web/address.hpp" + +using namespace atom::web; + +// Test cases for the Address class +class MockAddress : public Address { +public: + bool parse(const std::string& address) override { return true; } + void printAddressType() const override {} + bool isInRange(const std::string& start, const std::string& end) override { return true; } + std::string toBinary() const override { return "binary"; } + bool isEqual(const Address& other) const override { return true; } + std::string getType() const override { return "Mock"; } + std::string getNetworkAddress(const std::string& mask) const override { return "network"; } + std::string getBroadcastAddress(const std::string& mask) const override { return "broadcast"; } + bool isSameSubnet(const Address& other, const std::string& mask) const override { return true; } + std::string toHex() const override { return "hex"; } +}; + +/* +TODO: Fix this test +TEST(AddressTest, GetAddress) { + MockAddress address; + address.addressStr = "127.0.0.1"; + EXPECT_EQ(address.getAddress(), "127.0.0.1"); +} +*/ + +// Test cases for the IPv4 class +TEST(IPv4Test, Constructor) { + IPv4 address("192.168.1.1"); + EXPECT_EQ(address.getAddress(), "192.168.1.1"); +} + +TEST(IPv4Test, Parse) { + IPv4 address; + EXPECT_TRUE(address.parse("192.168.1.1")); +} + +TEST(IPv4Test, PrintAddressType) { + IPv4 address; + testing::internal::CaptureStdout(); + address.printAddressType(); + std::string output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, "IPv4\n"); +} + +TEST(IPv4Test, IsInRange) { + IPv4 address("192.168.1.5"); + EXPECT_TRUE(address.isInRange("192.168.1.0", "192.168.1.10")); +} + +TEST(IPv4Test, ToBinary) { + IPv4 address("192.168.1.1"); + EXPECT_EQ(address.toBinary(), "11000000101010000000000100000001"); +} + +TEST(IPv4Test, IsEqual) { + IPv4 address1("192.168.1.1"); + IPv4 address2("192.168.1.1"); + EXPECT_TRUE(address1.isEqual(address2)); +} + +TEST(IPv4Test, GetType) { + IPv4 address; + EXPECT_EQ(address.getType(), "IPv4"); +} + +TEST(IPv4Test, GetNetworkAddress) { + IPv4 address("192.168.1.1"); + EXPECT_EQ(address.getNetworkAddress("255.255.255.0"), "192.168.1.0"); +} + +TEST(IPv4Test, GetBroadcastAddress) { + IPv4 address("192.168.1.1"); + EXPECT_EQ(address.getBroadcastAddress("255.255.255.0"), "192.168.1.255"); +} + +TEST(IPv4Test, IsSameSubnet) { + IPv4 address1("192.168.1.1"); + IPv4 address2("192.168.1.2"); + EXPECT_TRUE(address1.isSameSubnet(address2, "255.255.255.0")); +} + +TEST(IPv4Test, ToHex) { + IPv4 address("192.168.1.1"); + EXPECT_EQ(address.toHex(), "C0A80101"); +} + +TEST(IPv4Test, ParseCIDR) { + IPv4 address; + EXPECT_TRUE(address.parseCIDR("192.168.1.1/24")); +} + +// Test cases for the IPv6 class +TEST(IPv6Test, Constructor) { + IPv6 address("::1"); + EXPECT_EQ(address.getAddress(), "::1"); +} + +TEST(IPv6Test, Parse) { + IPv6 address; + EXPECT_TRUE(address.parse("::1")); +} + +TEST(IPv6Test, PrintAddressType) { + IPv6 address; + testing::internal::CaptureStdout(); + address.printAddressType(); + std::string output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, "IPv6\n"); +} + +TEST(IPv6Test, IsInRange) { + IPv6 address("::5"); + EXPECT_TRUE(address.isInRange("::0", "::10")); +} + +TEST(IPv6Test, ToBinary) { + IPv6 address("::1"); + EXPECT_EQ(address.toBinary(), "00000000000000000000000000000001"); +} + +TEST(IPv6Test, IsEqual) { + IPv6 address1("::1"); + IPv6 address2("::1"); + EXPECT_TRUE(address1.isEqual(address2)); +} + +TEST(IPv6Test, GetType) { + IPv6 address; + EXPECT_EQ(address.getType(), "IPv6"); +} + +TEST(IPv6Test, GetNetworkAddress) { + IPv6 address("::1"); + EXPECT_EQ(address.getNetworkAddress("ffff:ffff:ffff:ffff::"), "::"); +} + +TEST(IPv6Test, GetBroadcastAddress) { + IPv6 address("::1"); + EXPECT_EQ(address.getBroadcastAddress("ffff:ffff:ffff:ffff::"), "::ffff:ffff:ffff:ffff"); +} + +TEST(IPv6Test, IsSameSubnet) { + IPv6 address1("::1"); + IPv6 address2("::2"); + EXPECT_TRUE(address1.isSameSubnet(address2, "ffff:ffff:ffff:ffff::")); +} + +TEST(IPv6Test, ToHex) { + IPv6 address("::1"); + EXPECT_EQ(address.toHex(), "00000000000000000000000000000001"); +} + +TEST(IPv6Test, ParseCIDR) { + IPv6 address; + EXPECT_TRUE(address.parseCIDR("::1/128")); +} + +// Test cases for the UnixDomain class +TEST(UnixDomainTest, Constructor) { + UnixDomain address("/tmp/socket"); + EXPECT_EQ(address.getAddress(), "/tmp/socket"); +} + +TEST(UnixDomainTest, Parse) { + UnixDomain address; + EXPECT_TRUE(address.parse("/tmp/socket")); +} + +TEST(UnixDomainTest, PrintAddressType) { + UnixDomain address; + testing::internal::CaptureStdout(); + address.printAddressType(); + std::string output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, "UnixDomain\n"); +} + +TEST(UnixDomainTest, IsInRange) { + UnixDomain address("/tmp/socket"); + EXPECT_TRUE(address.isInRange("/tmp/socket1", "/tmp/socket2")); +} + +TEST(UnixDomainTest, ToBinary) { + UnixDomain address("/tmp/socket"); + EXPECT_EQ(address.toBinary(), "binary"); +} + +TEST(UnixDomainTest, IsEqual) { + UnixDomain address1("/tmp/socket"); + UnixDomain address2("/tmp/socket"); + EXPECT_TRUE(address1.isEqual(address2)); +} + +TEST(UnixDomainTest, GetType) { + UnixDomain address; + EXPECT_EQ(address.getType(), "UnixDomain"); +} + +TEST(UnixDomainTest, GetNetworkAddress) { + UnixDomain address("/tmp/socket"); + EXPECT_EQ(address.getNetworkAddress("mask"), "network"); +} + +TEST(UnixDomainTest, GetBroadcastAddress) { + UnixDomain address("/tmp/socket"); + EXPECT_EQ(address.getBroadcastAddress("mask"), "broadcast"); +} + +TEST(UnixDomainTest, IsSameSubnet) { + UnixDomain address1("/tmp/socket1"); + UnixDomain address2("/tmp/socket2"); + EXPECT_TRUE(address1.isSameSubnet(address2, "mask")); +} + +TEST(UnixDomainTest, ToHex) { + UnixDomain address("/tmp/socket"); + EXPECT_EQ(address.toHex(), "hex"); +} \ No newline at end of file diff --git a/tests/atom/web/minetype.cpp b/tests/atom/web/minetype.cpp new file mode 100644 index 00000000..7872c674 --- /dev/null +++ b/tests/atom/web/minetype.cpp @@ -0,0 +1,112 @@ +// FILE: src/atom/web/test_minetype.hpp + +#include "atom/web/minetype.hpp" +#include +#include +#include "atom/type/json.hpp" +using namespace nlohmann; + +// Test constructor +TEST(MimeTypesTest, Constructor) { + std::vector knownFiles = {"file1.txt", "file2.txt"}; + MimeTypes mimeTypes(knownFiles, true); + // Add assertions if there are any public methods to verify the state +} + +// Test readJson method +TEST(MimeTypesTest, ReadJson) { + std::vector knownFiles; + MimeTypes mimeTypes(knownFiles); + + // Create a temporary JSON file + std::string jsonFile = "test_mime.json"; + std::ofstream file(jsonFile); + file << R"({ + "mimeTypes": { + "text/plain": ["txt", "text"], + "image/jpeg": ["jpg", "jpeg"] + } + })"; + file.close(); + + mimeTypes.readJson(jsonFile); + + // Clean up + std::remove(jsonFile.c_str()); + + // Add assertions if there are any public methods to verify the state +} + +// Test guessType method +TEST(MimeTypesTest, GuessType) { + std::vector knownFiles; + MimeTypes mimeTypes(knownFiles); + + auto result = mimeTypes.guessType("http://example.com/file.txt"); + EXPECT_EQ(result.first.value(), "text/plain"); + EXPECT_EQ(result.second, std::nullopt); +} + +// Test guessAllExtensions method +TEST(MimeTypesTest, GuessAllExtensions) { + std::vector knownFiles; + MimeTypes mimeTypes(knownFiles); + + auto extensions = mimeTypes.guessAllExtensions("text/plain"); + EXPECT_EQ(extensions.size(), 2); + EXPECT_EQ(extensions[0], "txt"); + EXPECT_EQ(extensions[1], "text"); +} + +// Test guessExtension method +TEST(MimeTypesTest, GuessExtension) { + std::vector knownFiles; + MimeTypes mimeTypes(knownFiles); + + auto extension = mimeTypes.guessExtension("text/plain"); + EXPECT_EQ(extension.value(), "txt"); +} + +// Test addType method +TEST(MimeTypesTest, AddType) { + std::vector knownFiles; + MimeTypes mimeTypes(knownFiles); + + mimeTypes.addType("application/json", "json"); + + auto extension = mimeTypes.guessExtension("application/json"); + EXPECT_EQ(extension.value(), "json"); +} + +// Test listAllTypes method +TEST(MimeTypesTest, ListAllTypes) { + std::vector knownFiles; + MimeTypes mimeTypes(knownFiles); + + mimeTypes.addType("application/json", "json"); + + testing::internal::CaptureStdout(); + mimeTypes.listAllTypes(); + std::string output = testing::internal::GetCapturedStdout(); + + EXPECT_TRUE(output.find("application/json") != std::string::npos); + EXPECT_TRUE(output.find("json") != std::string::npos); +} + +// Test guessTypeByContent method +TEST(MimeTypesTest, GuessTypeByContent) { + std::vector knownFiles; + MimeTypes mimeTypes(knownFiles); + + // Create a temporary file with known content + std::string filePath = "test_file.txt"; + std::ofstream file(filePath); + file << "This is a test file."; + file.close(); + + auto mimeType = mimeTypes.guessTypeByContent(filePath); + EXPECT_EQ(mimeType.value(), "text/plain"); + + // Clean up + std::remove(filePath.c_str()); +} \ No newline at end of file diff --git a/tests/atom/web/time.cpp b/tests/atom/web/time.cpp new file mode 100644 index 00000000..75270a4e --- /dev/null +++ b/tests/atom/web/time.cpp @@ -0,0 +1,86 @@ +// FILE: src/atom/web/test_time.hpp + +#include +#include "atom/web/time.hpp" +#include +#include +#include + +using namespace atom::web; + +// Mock implementation of TimeManagerImpl for testing +class MockTimeManagerImpl { +public: + static auto getSystemTime() -> std::time_t { return std::time(nullptr); } + static void setSystemTime(int year, int month, int day, int hour, int minute, int second) { + // Use the parameters to avoid unused parameter warnings + (void)year; (void)month; (void)day; (void)hour; (void)minute; (void)second; + } + static auto setSystemTimezone(const std::string &timezone) -> bool { + // Use the parameter to avoid unused parameter warning + (void)timezone; + return true; + } + static auto syncTimeFromRTC() -> bool { return true; } + static auto getNtpTime(const std::string &hostname) -> std::time_t { + // Use the parameter to avoid unused parameter warning + (void)hostname; + return std::time(nullptr); + } +}; + +// Test fixture for TimeManager +class TimeManagerTest : public ::testing::Test { +protected: + void SetUp() override { + timeManager = std::make_unique(); + mockImpl = std::make_unique(); + // Provide a setter method to set the private member impl_ + // TODO: Uncomment the following line after adding the setter method + // timeManager->setImpl(std::move(mockImpl)); + } + + std::unique_ptr timeManager; + std::unique_ptr mockImpl; +}; + +// Test constructor +TEST_F(TimeManagerTest, Constructor) { + EXPECT_NE(timeManager, nullptr); +} + +// Test getSystemTime method +TEST_F(TimeManagerTest, GetSystemTime) { + std::time_t currentTime = timeManager->getSystemTime(); + EXPECT_NE(currentTime, 0); +} + +// Test setSystemTime method +TEST_F(TimeManagerTest, SetSystemTime) { + const int year = 2023; + const int month = 3; + const int day = 31; + const int hour = 12; + const int minute = 0; + const int second = 0; + timeManager->setSystemTime(year, month, day, hour, minute, second); + // No assertion needed as we are just testing if the method runs without error +} + +// Test setSystemTimezone method +TEST_F(TimeManagerTest, SetSystemTimezone) { + bool result = timeManager->setSystemTimezone("UTC"); + EXPECT_TRUE(result); +} + +// Test syncTimeFromRTC method +TEST_F(TimeManagerTest, SyncTimeFromRTC) { + bool result = timeManager->syncTimeFromRTC(); + EXPECT_TRUE(result); +} + +// Test getNtpTime method +TEST_F(TimeManagerTest, GetNtpTime) { + std::time_t ntpTime = timeManager->getNtpTime("pool.ntp.org"); + EXPECT_NE(ntpTime, 0); +} \ No newline at end of file diff --git a/tests/atom/web/utils.cpp b/tests/atom/web/utils.cpp new file mode 100644 index 00000000..11465bfc --- /dev/null +++ b/tests/atom/web/utils.cpp @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include + +#include "atom/web/utils.hpp" + +using namespace atom::web; + +// Test isPortInUse function +TEST(UtilsTest, IsPortInUse) { + int port = 8080; + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(port); + + // Bind the socket to the port + bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)); + + // Check if the port is in use + EXPECT_TRUE(isPortInUse(port)); + + // Close the socket + close(sockfd); + + // Check if the port is not in use + EXPECT_FALSE(isPortInUse(port)); +} + +// Test checkAndKillProgramOnPort function +TEST(UtilsTest, CheckAndKillProgramOnPort) { + int port = 8080; + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(port); + + // Bind the socket to the port + bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)); + + // Check and kill the program on the port + EXPECT_TRUE(checkAndKillProgramOnPort(port)); + + // Close the socket + close(sockfd); + + // Check if no program is running on the port + EXPECT_FALSE(checkAndKillProgramOnPort(port)); +} + +#if defined(__linux__) || defined(__APPLE__) +// Test dumpAddrInfo function +TEST(UtilsTest, DumpAddrInfo) { + struct addrinfo src; + struct addrinfo* dst = nullptr; + + src.ai_family = AF_INET; + src.ai_socktype = SOCK_STREAM; + src.ai_protocol = IPPROTO_TCP; + src.ai_addrlen = sizeof(struct sockaddr_in); + src.ai_addr = (struct sockaddr*)malloc(sizeof(struct sockaddr_in)); + src.ai_canonname = nullptr; + src.ai_next = nullptr; + + EXPECT_EQ(dumpAddrInfo(&dst, &src), 0); + + free(src.ai_addr); + freeAddrInfo(dst); +} + +// Test addrInfoToString function +TEST(UtilsTest, AddrInfoToString) { + struct addrinfo addrInfo; + addrInfo.ai_family = AF_INET; + addrInfo.ai_socktype = SOCK_STREAM; + addrInfo.ai_protocol = IPPROTO_TCP; + addrInfo.ai_addrlen = sizeof(struct sockaddr_in); + addrInfo.ai_addr = (struct sockaddr*)malloc(sizeof(struct sockaddr_in)); + addrInfo.ai_canonname = nullptr; + addrInfo.ai_next = nullptr; + + std::string addrStr = addrInfoToString(&addrInfo, true); + EXPECT_FALSE(addrStr.empty()); + + free(addrInfo.ai_addr); +} + +// Test getAddrInfo function +TEST(UtilsTest, GetAddrInfo) { + struct addrinfo* addrInfo = getAddrInfo("www.google.com", "http"); + EXPECT_NE(addrInfo, nullptr); + freeAddrInfo(addrInfo); +} + +// Test freeAddrInfo function +TEST(UtilsTest, FreeAddrInfo) { + struct addrinfo* addrInfo = getAddrInfo("www.google.com", "http"); + EXPECT_NE(addrInfo, nullptr); + freeAddrInfo(addrInfo); + // No assertion needed as we are just testing if the method runs without + // error +} + +// Test compareAddrInfo function +TEST(UtilsTest, CompareAddrInfo) { + struct addrinfo* addrInfo1 = getAddrInfo("www.google.com", "http"); + struct addrinfo* addrInfo2 = getAddrInfo("www.google.com", "http"); + EXPECT_TRUE(compareAddrInfo(addrInfo1, addrInfo2)); + freeAddrInfo(addrInfo1); + freeAddrInfo(addrInfo2); +} + +// Test filterAddrInfo function +TEST(UtilsTest, FilterAddrInfo) { + struct addrinfo* addrInfo = getAddrInfo("www.google.com", "http"); + struct addrinfo* filtered = filterAddrInfo(addrInfo, AF_INET); + EXPECT_NE(filtered, nullptr); + freeAddrInfo(addrInfo); + freeAddrInfo(filtered); +} + +// Test sortAddrInfo function +TEST(UtilsTest, SortAddrInfo) { + struct addrinfo* addrInfo = getAddrInfo("www.google.com", "http"); + struct addrinfo* sorted = sortAddrInfo(addrInfo); + EXPECT_NE(sorted, nullptr); + freeAddrInfo(addrInfo); + freeAddrInfo(sorted); +} +#endif