diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml index 5f66ece4..c84cd1ee 100644 --- a/.github/workflows/greetings.yml +++ b/.github/workflows/greetings.yml @@ -9,8 +9,23 @@ jobs: issues: write pull-requests: write steps: + # Check if the issue or PR already has the greeted label + - name: Check if Already Greeted + id: check_greeted + uses: actions/github-script@v6 + with: + result-encoding: string + script: | + const labels = await github.rest.issues.listLabelsOnIssue({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo + }); + return labels.data.some(label => label.name === 'greeted'); + # Greeting for first interaction using actions/first-interaction - name: First Interaction Greeting + if: steps.check_greeted.outputs.result == 'false' uses: actions/first-interaction@v1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} @@ -19,7 +34,7 @@ jobs: # General greeting for every new issue - name: Greet Every Issue - if: github.event_name == 'issues' + if: github.event_name == 'issues' && steps.check_greeted.outputs.result == 'false' uses: actions/github-script@v6 with: script: | @@ -29,11 +44,11 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, body: issueComment - }) + }); # General greeting for every new pull request - name: Greet Every PR - if: github.event_name == 'pull_request_target' + if: github.event_name == 'pull_request_target' && steps.check_greeted.outputs.result == 'false' uses: actions/github-script@v6 with: script: | @@ -44,33 +59,25 @@ jobs: repo: context.repo.repo, body: prComment, event: 'COMMENT' - }) + }); # Add labels to new issues and PRs - name: Label New Issues and PRs + if: steps.check_greeted.outputs.result == 'false' uses: actions/github-script@v6 with: script: | - const labels = ['needs-triage']; - if (context.eventName === 'issues') { - github.rest.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: labels - }) - } else if (context.eventName === 'pull_request_target') { - github.rest.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: labels - }) - } + const labels = ['needs-triage', 'greeted']; + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: labels + }); # Auto Assign Reviewers based on the time of day - name: Auto-assign Reviewers Based on Time of Day - if: github.event_name == 'pull_request_target' + if: github.event_name == 'pull_request_target' && steps.check_greeted.outputs.result == 'false' uses: actions/github-script@v6 with: script: | @@ -82,4 +89,4 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, reviewers: reviewers - }) + }); diff --git a/.github/workflows/windows-mingw.yml b/.github/workflows/windows-mingw.yml index 42c39043..1d3b6838 100644 --- a/.github/workflows/windows-mingw.yml +++ b/.github/workflows/windows-mingw.yml @@ -6,10 +6,12 @@ on: branches: - 'master' - 'dev' + - 'reborn' pull_request: branches: - 'master' - 'dev' + - 'reborn' env: # Path to the solution file relative to the root of the project. @@ -43,7 +45,6 @@ jobs: update: true install: >- mingw-w64-${{matrix.env}}-openssl - base-devel mingw-w64-${{matrix.env}}-cmake mingw-w64-${{matrix.env}}-gcc @@ -58,9 +59,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - - name: Build INDI Core + - name: Build and Test run: | - mkdir build - cd build - cmake .. - cmake --build . + ./scripts/build_win.sh + ./scripts/test_win.sh diff --git a/.gitignore b/.gitignore index aa7184a5..03d90896 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,8 @@ test *.xml .xmake +.cache + +cmake-build-debug/ + +.venv/ diff --git a/.gitmodules b/.gitmodules index bac355b9..ddb22ab7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "libs"] path = libs - url = https://github.com/ElementAstro/LithiumLibrary.git \ No newline at end of file + url = https://github.com/ElementAstro/LithiumLibrary.git diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/Lithium.iml b/.idea/Lithium.iml new file mode 100644 index 00000000..8afe22e0 --- /dev/null +++ b/.idea/Lithium.iml @@ -0,0 +1,2 @@ + + diff --git a/.sonarlint/connectedMode.json b/.sonarlint/connectedMode.json new file mode 100644 index 00000000..26648e83 --- /dev/null +++ b/.sonarlint/connectedMode.json @@ -0,0 +1,4 @@ +{ + "sonarCloudOrganization": "elementastro", + "projectKey": "ElementAstro_Lithium" +} diff --git a/CMakeLists.txt b/CMakeLists.txt index ce2d2377..eb3b879d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,35 +66,164 @@ include_directories(${CMAKE_SOURCE_DIR}/libs/) include_directories(${CMAKE_SOURCE_DIR}/driverlibs/) include_directories(${lithium_src_dir}) include_directories(${lithium_module_dir}) -include_directories(${CMAKE_SOURCE_DIR}/libs/oatpp) -include_directories(${CMAKE_SOURCE_DIR}/libs/oatpp-swagger) -include_directories(${CMAKE_SOURCE_DIR}/libs/oatpp-websocket) +include_directories(${CMAKE_SOURCE_DIR}/libs/oatpp/oatpp) +include_directories(${CMAKE_SOURCE_DIR}/libs/oatpp-swagger/oatpp-swagger) +include_directories(${CMAKE_SOURCE_DIR}/libs/oatpp-websocket/oatpp-websocket) +include_directories(${CMAKE_SOURCE_DIR}/libs/oatpp-openssl/oatpp-openssl) # Find packages find_package(OpenSSL REQUIRED) find_package(ZLIB REQUIRED) find_package(SQLite3 REQUIRED) find_package(fmt REQUIRED) +find_package(Readline REQUIRED) + +find_package(Python COMPONENTS Interpreter REQUIRED) + +# Specify the path to requirements.txt +set(REQUIREMENTS_FILE "${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt") + +# Define a function to check if a Python package is installed +function(check_python_package package version) + # Replace hyphens with underscores for the import statement + string(REPLACE "-" "_" import_name ${package}) + + # Check if the package can be imported + execute_process( + COMMAND ${Python_EXECUTABLE} -c "import ${import_name}" + RESULT_VARIABLE result + ) + + if(NOT result EQUAL 0) + set(result FALSE PARENT_SCOPE) + return() + endif() + + # Get the installed package version + execute_process( + COMMAND ${Python_EXECUTABLE} -m pip show ${package} + OUTPUT_VARIABLE package_info + ) + + # Extract version information from the output + string(FIND "${package_info}" "Version:" version_pos) + + if(version_pos EQUAL -1) + set(result FALSE PARENT_SCOPE) + return() # Return false if version not found + endif() + + # Extract the version string + string(SUBSTRING "${package_info}" ${version_pos} 1000 version_string) + string(REGEX REPLACE "Version: ([^ ]+).*" "\\1" installed_version "${version_string}") + + # Compare versions + if("${installed_version}" VERSION_LESS "${version}") + set(result FALSE PARENT_SCOPE) # Return false if installed version is less than required + return() + endif() + + set(result TRUE PARENT_SCOPE) +endfunction() + +if (EXISTS "${CMAKE_BINARY_DIR}/check_marker.txt") + message(STATUS "Check marker file found, skipping the checks.") +else() +# Create a virtual environment +set(VENV_DIR "${CMAKE_BINARY_DIR}/venv") +execute_process( + COMMAND ${Python_EXECUTABLE} -m venv ${VENV_DIR} +) + +set(PYTHON_EXECUTABLE "${VENV_DIR}/bin/python") +set(PIP_EXECUTABLE "${VENV_DIR}/bin/pip") + +# Upgrade pip in the virtual environment +execute_process( + COMMAND ${PIP_EXECUTABLE} install --upgrade pip +) + +# Read the requirements.txt file and install missing packages +file(READ ${REQUIREMENTS_FILE} requirements_content) + +# Split the requirements file content into lines +string(REPLACE "\n" ";" requirements_list "${requirements_content}") + +# Check and install each package +foreach(requirement ${requirements_list}) + # Skip empty lines + string(STRIP ${requirement} trimmed_requirement) + if(trimmed_requirement STREQUAL "") + continue() + endif() + + # Get the package name and version (without the version number) + if(${trimmed_requirement} MATCHES "==") + string(REPLACE "==" ";" parts ${trimmed_requirement}) + elseif(${trimmed_requirement} MATCHES ">=") + string(REPLACE ">=" ";" parts ${trimmed_requirement}) + else() + message(WARNING "Could not parse requirement '${trimmed_requirement}'. Skipping...") + continue() + endif() + + list(GET parts 0 package_name) + list(GET parts 1 package_version) + + # If the package name or version could not be parsed, output a warning and skip + if(NOT package_name OR NOT package_version) + message(WARNING "Could not parse requirement '${trimmed_requirement}'. Skipping...") + continue() + endif() + + # Check if the package is installed + message(STATUS "Checking if Python package '${package_name}' is installed...") + check_python_package(${package_name} ${package_version}) + if(NOT result) + message(STATUS "Package '${package_name}' is not installed or needs an upgrade. Installing...") + execute_process( + COMMAND ${PIP_EXECUTABLE} install ${trimmed_requirement} + RESULT_VARIABLE install_result + ) + if(NOT install_result EQUAL 0) + message(FATAL_ERROR "Failed to install Python package '${package_name}'.") + endif() + else() + message(STATUS "Package '${package_name}' is already installed with a suitable version.") + endif() +endforeach() +execute_process( + COMMAND ${CMAKE_COMMAND} -E touch "${CMAKE_BINARY_DIR}/check_marker.txt" + RESULT_VARIABLE result +) +endif() # Configure config.h -configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) +configure_file(${lithium_src_dir}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) + +set(BUILD_SHARED_LIBS ON) # Add subdirectories add_subdirectory(libs) -add_subdirectory(${lithium_module_dir}) add_subdirectory(modules) -add_subdirectory(driver) +add_subdirectory(${lithium_module_dir}) add_subdirectory(${lithium_src_dir}/config) +add_subdirectory(${lithium_src_dir}/task) +add_subdirectory(${lithium_src_dir}/server) +add_subdirectory(${lithium_src_dir}/utils) +add_subdirectory(${lithium_src_dir}/addon) +add_subdirectory(${lithium_src_dir}/client) +add_subdirectory(${lithium_src_dir}/device) add_subdirectory(tests) # Set source files set(component_module ${lithium_component_dir}/addons.cpp ${lithium_component_dir}/compiler.cpp + ${lithium_component_dir}/dependency.cpp ${lithium_component_dir}/loader.cpp ${lithium_component_dir}/manager.cpp ${lithium_component_dir}/sandbox.cpp - ${lithium_component_dir}/sort.cpp ) set(config_module @@ -106,25 +235,24 @@ set(debug_module ${lithium_src_dir}/debug/suggestion.cpp ${lithium_src_dir}/debug/command.cpp ${lithium_src_dir}/debug/console.cpp + ${lithium_src_dir}/debug/history.cpp + ${lithium_src_dir}/debug/progress.cpp + ${lithium_src_dir}/debug/output_style.cpp + ${lithium_src_dir}/debug/check.cpp +) + +set(device_module + ${lithium_src_dir}/device/manager.cpp + + ${lithium_src_dir}/device/template/device.cpp + ${lithium_src_dir}/device/template/camera.cpp ) set(script_module ${lithium_src_dir}/script/manager.cpp - ${lithium_src_dir}/script/custom/sys.cpp - ${lithium_src_dir}/script/custom/config.cpp ${lithium_src_dir}/script/sheller.cpp ) -set(task_module - ${lithium_task_dir}/manager.cpp - ${lithium_task_dir}/generator.cpp - ${lithium_task_dir}/container.cpp - ${lithium_task_dir}/tick.cpp - ${lithium_task_dir}/loader.cpp - ${lithium_task_dir}/list.cpp - ${lithium_task_dir}/pool.cpp -) - set(Lithium_module ${lithium_src_dir}/LithiumApp.cpp ${lithium_src_dir}/utils/constant.cpp @@ -135,8 +263,8 @@ add_library(lithium_server-library STATIC ${component_module} ${config_module} ${debug_module} + ${device_module} ${script_module} - ${task_module} ${Lithium_module} ) @@ -152,25 +280,26 @@ add_executable(lithium_server ${lithium_src_dir}/App.cpp) target_link_libraries(lithium_server PRIVATE lithium_server-library - lithium_webserver + lithium.server-lib + lithium-config + lithium-task + lithium-addons oatpp-websocket oatpp-swagger oatpp-openssl oatpp-zlib oatpp loguru - libzippp - atomstatic - carbon + atom fmt::fmt OpenSSL::SSL OpenSSL::Crypto ${ZLIB_LIBRARIES} sqlite3 cpp_httplib - backward tinyxml2 pocketpy + ${Readline_LIBRARIES} ) if(WIN32) @@ -187,6 +316,11 @@ if(WIN32) ) elseif(UNIX OR LINUX OR APPLE) target_link_libraries(lithium_server PRIVATE dl) + find_package(Seccomp REQUIRED) + if(Seccomp_FOUND) + include_directories(${Seccomp_INCLUDE_DIRS}) + target_link_libraries(lithium_server PRIVATE ${Seccomp_LIBRARIES}) + endif() else() message(FATAL_ERROR "Unsupported platform") endif() diff --git a/README.md b/README.md index 14388641..24c27eb1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Lithium +# Lithium - The Lightweight Suite for Astronomical Imaging

@@ -15,138 +15,54 @@ [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=ElementAstro_Lithium&metric=bugs)](https://sonarcloud.io/summary/new_code?id=ElementAstro_Lithium) [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=ElementAstro_Lithium&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=ElementAstro_Lithium) -## Introduction +## Overview -Lithium, a lively and lightweight astrophotography terminal. +The Lithium project is designed as a comprehensive, lightweight platform for astronomy enthusiasts and professionals alike, offering not just capture software but also serving as a device control server, system manager, and a platform for extensive customization, adaptable to various applications and research domains. -Features: +## Key Features -- Can be used as imaging software, device server, and system manager. -- Based on the latest C++20 standard, providing efficient functionality implementation (compatible with some C++17 features). -- Supports open loading, allowing dynamic loading of C++ dynamic libraries for hot updates. -- Built-in optimized Chaiscript parsing engine, providing flexible script support. -- Supports various types of plugins, facilitating feature expansion. -- Cross-platform compatibility, fully supporting Windows and Linux operating systems. -- Lightweight software design, while maintaining excellent performance. -- Provides a rich API, covering all necessary functions for astrophotography. ~~If not? Mods!~~ -- Supports complex shooting sequences, enabling a programmable user experience. -- Uses the GPL3 open source license, **where the world belongs to open source** +- **Versatile Functionality**: Supports full-spectrum application from image acquisition to device management and system operations. +- **Contemporary Language Standard**: Built using the latest C++20 standard with compatibility for select C++23 features, ensuring modernity and efficiency in code. +- **Dynamic Module Loading**: Enables hot updates through C++ dynamic libraries, facilitating instant expansion of capabilities and enhancing flexibility. +- **Embedded Scripting Engine**: Integrates a high-performance pocketpy interpreter supporting Python scripts for rapid development of tailored logic. +- **Broad Platform Compatibility**: Fully supports Windows and Linux environments (including x86_64 and ARM architectures), with partial support for MacOS. +- **Comprehensive APIs and Components**: Offers a wide range of APIs and functional components that cater to diverse astronomical imaging needs, encouraging users to develop modules for missing functionalities. +- **Open-Source Licensing Model**: Adheres to the GPLv3 license, fostering community sharing and collaboration while allowing for proprietary plugins to protect business-sensitive code. +- **Educational and Inspirational**: Encourages learning and innovation through high-quality code examples and documentation, promoting knowledge dissemination and skill enhancement. -## About Mod/Plugin +## Building Instructions -In Lithium, the component function is the most special function, providing a mod mechanism similar to Minecraft. The component function supports dynamic addition and insertion of functions, but due to the limitations of C++, we have imposed certain restrictions on the insertion of components to ensure system stability and security. +### System Preparation -### Form of Components +#### Windows -- Injective Components: These components replace the implemented functions in `Lithium`. They inject `shared_ptr` into each Manager (similar to `ConfigManager`). The target of the injected function is the same as that of the Manager that has been injected into `GlobalPtrManager`. Components in this form can flexibly replace existing functions. - -- Independent Components: These components use a distributed architecture and run in independent processes to ensure system security. When it is necessary to process sensitive data or perform complex calculations, these independent components can provide additional protection and isolation. To increase the security of components, `Lithium` also provides sandboxing functionality. - -It should be noted that except for injective and independent components, other forms of components will be considered illegal and unsupported for loading, and will be directly ignored by the system. - -### Component Levels - -- Addon: The highest level of component, containing a series of Modules and Components - -- Module: A module containing a dynamic library of an indefinite number of Components (depending on the platform) - -- Component: A `shared_ptr` of a specific actual function or an executable function - -All functions are declared in `package.json` for ease of use. - -## How to build - -### Install dependencies - -Although efforts have been made to minimize the use of libraries, a few dependencies still need to be installed. - -#### On Windows +It is recommended to use the MSYS2 environment and leverage the Tsinghua University Open Source Software Mirror for expedited downloads. The following commands install necessary dependencies: ```shell -sed -i "s#https\?://mirror.msys2.org/#https://mirrors.tuna.tsinghua.edu.cn/msys2/#g" /etc/pacman.d/mirrorlist* -pacman -Syu -pacman -S mingw-w64-x86_64-toolchain -pacman -S mingw-w64-x86_64-dlfcn -pacman -S mingw-w64-x86_64-cfitsio -pacman -S mingw-w64-x86_64-cmake -pacman -S mingw-w64-x86_64-libzip -pacman -S mingw-w64-x86_64-zlib -pacman -S mingw-w64-x86_64-fmt -pacman -S mingw-w64-x86_64-libnova -pacman -S mingw-w64-x86_64-gsl - -# for test -pacman -S mingw-w64-x86_64-gtest -``` - -#### On Ubuntu or other similar Linux platforms (No INDI needed) +# Add Tsinghua University mirror source +sed -i 's|https://mirror.msys2.org/|https://mirrors.tuna.tsinghua.edu.cn/msys2/|g' /etc/pacman.d/mirrorlist.mingw64 +sed -i 's|https://mirror.msys2.org/|https://mirrors.tuna.tsinghua.edu.cn/msys2/|g' /etc/pacman.d/mirrorlist -```shell -sudo apt-get update && sudo apt-get upgrade -y -sudo apt install gcc g++ cmake -sudo apt install libcfitsio-dev zlib1g-dev libssl-dev libzip-dev libnova-dev libfmt-dev libudev-dev -``` - -Alternatively, you can directly run the provided script according to your platform: - -```shell -sudo sh scripts/build_ci.sh -sh scripts/build_win.sh -``` - -#### Update GCC and Cmake - -Unfortunately, the newest GCC and CMake are not available on Github Codespace, so we must install them manually. - -```shell -sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y -sudo apt-get update -sudo apt-get install gcc-13 g++-13 # GCC13 is the best choice, clang is alse OK - -wget https://cmake.org/files/v3.28/cmake-3.28.0-rc5.tar.gz -tar -zxvf cmake-3.28.0-rc5.tar.gz -cd cmake-3.28.0-rc5 -./bootstrap && make && sudo make install - -#install newest clang-format -wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - -sudo nano /etc/apt/sources.list -#deb http://apt.llvm.org/focal/ llvm-toolchain-focal-17 main -#deb-src http://apt.llvm.org/focal/ llvm-toolchain-focal-17 main -sudo apt install -y clang-format-17 +# Update system packages and install build tools and dependencies +pacman -Syu +pacman -S mingw-w64-x86_64-toolchain mingw-w64-x86_64-dlfcn mingw-w64-x86_64-cfitsio mingw-w64-x86_64-cmake mingw-w64-x86_64-libzip mingw-w64-x86_64-zlib mingw-w64-x86_64-fmt mingw-w64-x86_64-libnova make mingw-w64-x86_64-gtest ``` -Build the code: +#### Ubuntu/Debian ```shell -mkdir build && cd build -cmake .. -make -j4 or cmake --build . -j4 - -./lithium_server +sudo apt-get update && sudo apt-get upgrade +sudo apt-get install build-essential cmake libcfitsio-dev zlib1g-dev libssl-dev libzip-dev libfmt-dev ``` -Everything is very simple. The entire process is straightforward. +Alternatively, utilize the provided quick-build scripts to streamline the process. -Here is a poem adapted from a quote : +### Building Steps -```txt -Learning requires not mere imagination, -Nor can it be attained through mediocre dedication. -It is through diligent accumulation, -That we shall grow in our education. - -Our efforts may falter and fail, -But we must not surrender and bail. -We shall not halt our stride for fear of stumbling, -For setbacks are the price of pursuing enlightenment. - -On this quest for truth, we shall encounter obstacles and doubts, -Yet we shall keep our resolve to seek understanding throughout. -Let us nurture a heart that yearns for wisdom and grace, -And never lose sight of this noble race. -``` +1. **Create Build Directory**: `mkdir build && cd build` +2. **Configure Project**: `cmake ..` +3. **Compile and Execute**: Use `make -jN` or `cmake --build . --parallel N` commands to compile in parallel, where N denotes the number of threads. Afterwards, launch the program via `./lithium_server`. -## Technical Support +### Intellectual Inspiration -[![SonarCloud](https://sonarcloud.io/images/project_badges/sonarcloud-orange.svg)](https://sonarcloud.io/summary/new_code?id=ElementAstro_Lithium) +Embarking on the journey with Lithium, we embrace curiosity and an unwavering pursuit of knowledge, echoing the adapted verse which reminds us that every attempt, though fraught with challenges and setbacks, is a necessary step toward wisdom and understanding. Together, let us navigate the vast cosmos of astronomical imaging, our technology the vessel, innovation our sail, advancing relentlessly forward. diff --git a/README_ZH.md b/README_ZH.md index 82848dda..263ae0d6 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -1,4 +1,4 @@ -# 锂 +# 锂 - 高度集成的天文摄影解决方案

@@ -15,112 +15,53 @@ [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=ElementAstro_Lithium&metric=bugs)](https://sonarcloud.io/summary/new_code?id=ElementAstro_Lithium) [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=ElementAstro_Lithium&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=ElementAstro_Lithium) -## 简介 +## 概览 -锂,轻量化的开放加载框架 +锂项目旨在为天文摄影爱好者及专业人士提供一个高度整合、轻量级的综合平台。它不仅是一个拍摄软件,还兼具设备控制服务器、系统管理以及广泛的可定制扩展能力,适用于多种应用场景和研究领域。 -## 特性 +## 核心特性 -- 可用作成像软件、设备服务器和系统管理器,或者是其他轻量级应用 -- 基于最新的 C++20 标准,提供高效实现(兼容部分 **C++23** 特性) -- 支持开放式加载,允许动态加载 C++动态库热更新,基于Atom架构 -- 内置经过优化的 Pocketpy 解析引擎,提供灵活的Python脚本支持 -- 跨平台兼容性,完全支持 Windows 和 Linux 操作系统 -- 提供丰富的 API,覆盖所有天文摄影所需功能,~~功能没有怎么办?写模组!~~ -- 采用 GPL3 开源许可协议,**世界属于开源** +- **多面手功能**:支持从图像捕获到设备管理,乃至系统级操作的全方位应用。 +- **先进语言标准**:采用最新C++20标准编写,并兼容C++23部分特性,确保代码的现代性和高效性。 +- **动态模块加载**:通过C++动态库实现的热更新机制,允许用户即时扩展功能,增强灵活性。 +- **嵌入式脚本引擎**:集成高性能的pocketpy解析器,支持Python脚本,便于快速开发定制化逻辑。 +- **广泛平台兼容**:全面适配Windows与Linux环境(包括x86_64及ARM架构),并具备部分MacOS支持。 +- **丰富接口与组件**:提供全面的API接口和功能组件,满足天文摄影的多样化需求,鼓励用户开发缺失功能的模组。 +- **开源授权模式**:遵循GPLv3协议,促进社区共享与合作,同时允许开发闭源插件以保护商业敏感代码。 -## 模组/插件 +## 构建指南 -在 Lithium,组件功能是最特殊的功能,提供类似于 Minecraft 的模组机制。组件功能支持动态添加和插入功能,但由于 C++语言限制,组件功能没有办法做到像python等动态语言那样自由,具有一定的限制和标准: +### 系统准备 -- 注入式组件:这些组件替换了`Lithium`中已实现的功能。它们通过使用`shared_ptr`注入各个 Manager(类似与`ConfigManager`),目标与已注入`GlobalPtrManager`的管理器相同。这种形式的组件可以灵活替换现有功能。 +#### Windows -- 独立式组件:这些组件采用分布式架构,在独立的进程中运行,以确保系统的安全性。当需要处理敏感数据或进行复杂的计算时,这种独立的组件能够提供额外的保护和隔离。为了增加组件的安全性,`Lithium`还提供了沙盒功能. - -需要注意的是,除了注入式和独立式组件外,其他形式的组件都将被视为非法形式,不支持加载,并将被系统直接忽略。 - -### 组件级别 - -- Addon:最高级的组件,包含一系列的 Module 和 Component - -- Module:模块,包含不定数量 Component 的动态库(根据平台而定) - -- Component:组件,具体实际功能的`shared_ptr`,或者是可执行的函 - -所有功能均在`package.json`中声明,以方便使用。 - -## 如何构建 - -### 安装依赖项 - -尽管已经尽最大努力减少了库的使用,但仍需要安装一些依赖项 - -#### 在 Windows 平台下 +推荐使用MSYS2环境,并通过清华大学开源软件镜像站加速下载。以下命令用于安装必要依赖: ```shell -# 添加清华镜像源,下载速度嘎嘎的 -sed -i "s#https\?://mirror.msys2.org/#https://mirrors.tuna.tsinghua.edu.cn/msys2/#g" /etc/pacman.d/mirrorlist* -pacman -Syu -pacman -S mingw-w64-x86_64-toolchain -pacman -S mingw-w64-x86_64-dlfcn -pacman -S mingw-w64-x86_64-cfitsio -pacman -S mingw-w64-x86_64-cmake -pacman -S mingw-w64-x86_64-libzip -pacman -S mingw-w64-x86_64-zlib -pacman -S mingw-w64-x86_64-fmt -pacman -S mingw-w64-x86_64-libnova -# 如果想用make构建 -pacman -S make # 注意添加对应的目录,否则会当场爆炸 - -pacman -S mingw-w64-x86_64-gsl - -# 测试用 -pacman -S mingw-w64-x86_64-gtest -``` - -#### Ubuntu/Debian/Other Linux - -```shell -sudo apt-get update && sudo apt-get upgrade -y -sudo apt install gcc g++ cmake -sudo apt install libcfitsio-dev zlib1g-dev libssl-dev libzip-dev libnova-dev libfmt-dev -``` +# 添加清华大学镜像源 +sed -i 's|https://mirror.msys2.org/|https://mirrors.tuna.tsinghua.edu.cn/msys2/|g' /etc/pacman.d/mirrorlist.mingw64 +sed -i 's|https://mirror.msys2.org/|https://mirrors.tuna.tsinghua.edu.cn/msys2/|g' /etc/pacman.d/mirrorlist -或者您可以直接根据您的平台运行提供的脚本: - -```shell -sudo sh scripts/build_ci.sh -sh scripts/build_win.sh +# 更新系统包并安装编译工具链等 +pacman -Syu +pacman -S mingw-w64-x86_64-toolchain mingw-w64-x86_64-dlfcn mingw-w64-x86_64-cfitsio mingw-w64-x86_64-cmake mingw-w64-x86_64-libzip mingw-w64-x86_64-zlib mingw-w64-x86_64-fmt mingw-w64-x86_64-libnova make mingw-w64-x86_64-gtest ``` -#### 构建代码 +#### Ubuntu/Debian ```shell -mkdir build && cd build -cmake .. -# -jn,n取决于你的电脑性能,一般是cpu核心数+2 -make -j4 或 cmake --build . -j4 - -./lithium_server +sudo apt-get update && sudo apt-get upgrade +sudo apt-get install build-essential cmake libcfitsio-dev zlib1g-dev libssl-dev libzip-dev libfmt-dev ``` -一切都非常简单整个过程很简单 +或使用提供的快捷构建脚本简化过程。 -下面是一首小诗,改编自《三体》中的一句话: +### 构建步骤 -```text -学习不仅仅需要想象, -也不能只凭平庸的奉献 -通过勤奋的积累, -我们在教育中成长 +1. **创建构建目录**:`mkdir build && cd build` +2. **配置项目**:`cmake ..` +3. **编译执行**:使用`make -jN`或`cmake --build . -j N`命令进行并行编译,其中N为线程数。完成后,通过`./lithium_server`启动程序。 -我们的努力可能会摇摇欲坠,甚至失败, -但我们不能放弃和退缩 -我们不应因为恐惧而停下脚步, -因为挫折是追求智慧的代价 +### 思维启迪 -在这探寻真理的旅途上,我们会遇到困难和疑虑, -但我们要始终坚定地追求理解 -让我们培养一颗渴望智慧和优雅的心灵, -永远不要忘记这个崇高的竞赛 -``` +在深入探索锂项目的旅程中,我们秉承着对未知的好奇心与不懈的求知欲,正如那首改编的诗句所言,每一步尝试虽可能遭遇挑战与失败,却是通往智慧与理解的必经之路。让我们携手共进,在天文摄影的浩瀚星海中,以技术为舟,以创新为帆,不断前行。 diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..a5e7a937 --- /dev/null +++ b/TODO.md @@ -0,0 +1,34 @@ +# TODO List for Project Lithium + +## High Priority + +- [ ] Complete the development of the OATPP server + + - [ ] Complete the definition of HTTP and WS interfaces + - [ ] Implement all interfaces of q-box + - [ ] Complete user login verification + +- [ ] Complete the internal core of the server + - [ ] Implement internal message bus + - [ ] Implement internal task allocation mechanism + +## Medium Priority + +## Low Priority + +## Future Enhancements + +- [ ] Add support for additional languages + - [ ] Localization for Spanish and French + - [ ] Implement language selection feature + +## Completed Tasks + +- [x] Setup project structure +- [x] Configure build system with CMake + +## Notes + +- Ensure all code follows the project's coding standards and guidelines. +- Use the issue tracker for reporting bugs and tracking progress on larger tasks. +- Regularly review and update this TODO list to reflect the current state of the project. diff --git a/cmake_modules/FindGMock.cmake b/cmake_modules/FindGMock.cmake new file mode 100644 index 00000000..8fb858fd --- /dev/null +++ b/cmake_modules/FindGMock.cmake @@ -0,0 +1,34 @@ +# FindGMock.cmake +# This module finds the Google Mock library + +# Set the name of the required package +find_package(GTest REQUIRED) + +# Try to locate the GMock library +find_path(GMOCK_INCLUDE_DIR NAMES gmock/gmock.h HINTS ${GTEST_INCLUDE_DIR} ${GTEST_ROOT} PATHS /usr/local/include /usr/include) + +find_library(GMOCK_LIBRARY NAMES gmock HINTS ${GTEST_LIBRARY_DIR} ${GTEST_ROOT} PATHS /usr/local/lib /usr/lib) + +find_library(GMOCK_MAIN_LIBRARY NAMES gmock_main HINTS ${GTEST_LIBRARY_DIR} ${GTEST_ROOT} PATHS /usr/local/lib /usr/lib) + +# Check if found +if (GMOCK_INCLUDE_DIR AND GMOCK_LIBRARY) + set(GMOCK_FOUND TRUE) +else() + set(GMOCK_FOUND FALSE) +endif() + +# Provide the results of the search, either found or not found +if (GMOCK_FOUND) + message(STATUS "Found Google Mock: ${GMOCK_LIBRARY}") + message(STATUS "Includes: ${GMOCK_INCLUDE_DIR}") + + # Set the variables necessary for using GMock + set(GMOCK_INCLUDE_DIRS ${GMOCK_INCLUDE_DIR} PARENT_SCOPE) + set(GMOCK_LIBRARIES ${GMOCK_LIBRARY} ${GMOCK_MAIN_LIBRARY} PARENT_SCOPE) +else() + message(WARNING "Google Mock not found, please install it or set the paths correctly.") +endif() + +# Provide the version information if necessary +# set(GMOCK_VERSION "x.y.z") # Optionally set the version diff --git a/cmake_modules/FindINDI.cmake b/cmake_modules/FindINDI.cmake index b6433aa1..a0e3bd2c 100644 --- a/cmake_modules/FindINDI.cmake +++ b/cmake_modules/FindINDI.cmake @@ -1,5 +1,5 @@ macro(_INDI_check_version) - file(READ "${INDI_INCLUDE_DIR}/lithiumapi.h" _INDI_version_header) + file(READ "${INDI_INCLUDE_DIR}/indiapi.h" _INDI_version_header) string(REGEX MATCH "#define INDI_VERSION_MAJOR[ \t]+([0-9]+)" _INDI_version_major_match "${_INDI_version_header}") set(INDI_VERSION_MAJOR "${CMAKE_MATCH_1}") diff --git a/cmake_modules/FindReadline.cmake b/cmake_modules/FindReadline.cmake new file mode 100644 index 00000000..cb78aaa9 --- /dev/null +++ b/cmake_modules/FindReadline.cmake @@ -0,0 +1,27 @@ +# - Try to find the Readline library +# Once done, this will define +# Readline_FOUND - System has Readline +# Readline_INCLUDE_DIRS - The Readline include directories +# Readline_LIBRARIES - The libraries needed to use Readline + +find_path(Readline_INCLUDE_DIR + NAMES readline/readline.h + PATHS /usr/include /usr/local/include +) + +find_library(Readline_LIBRARY + NAMES readline + PATHS /usr/lib /usr/local/lib +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Readline DEFAULT_MSG + Readline_LIBRARY Readline_INCLUDE_DIR +) + +if(Readline_FOUND) + set(Readline_LIBRARIES ${Readline_LIBRARY}) + set(Readline_INCLUDE_DIRS ${Readline_INCLUDE_DIR}) +endif() + +mark_as_advanced(Readline_INCLUDE_DIR Readline_LIBRARY) diff --git a/cmake_modules/FindSeccomp.cmake b/cmake_modules/FindSeccomp.cmake new file mode 100644 index 00000000..b8b31674 --- /dev/null +++ b/cmake_modules/FindSeccomp.cmake @@ -0,0 +1,30 @@ +# - Try to find libseccomp +# Once done, this will define +# +# Seccomp_FOUND - system has libseccomp +# Seccomp_INCLUDE_DIRS - the libseccomp include directories +# Seccomp_LIBRARIES - link these to use libseccomp + +find_path(Seccomp_INCLUDE_DIR + NAMES seccomp.h + PATH_SUFFIXES seccomp + PATHS /usr/local/include /usr/include +) + +find_library(Seccomp_LIBRARY + NAMES seccomp + PATHS /usr/local/lib /usr/lib +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Seccomp DEFAULT_MSG Seccomp_LIBRARY Seccomp_INCLUDE_DIR) + +if(Seccomp_FOUND) + set(Seccomp_LIBRARIES ${Seccomp_LIBRARY}) + set(Seccomp_INCLUDE_DIRS ${Seccomp_INCLUDE_DIR}) +else() + set(Seccomp_LIBRARIES "") + set(Seccomp_INCLUDE_DIRS "") +endif() + +mark_as_advanced(Seccomp_INCLUDE_DIR Seccomp_LIBRARY) diff --git a/cmake_modules/Findtomlplusplus.cmake b/cmake_modules/Findtomlplusplus.cmake deleted file mode 100644 index b12f8595..00000000 --- a/cmake_modules/Findtomlplusplus.cmake +++ /dev/null @@ -1,35 +0,0 @@ -# Findtomlplusplus.cmake - -# 首先检查是否已经定义了变量 tomlplusplus_FOUND -if (tomlplusplus_FOUND) - return() -endif() - -# 定义 tomldir 用于指定 toml++ 的安装路径 -set(tomldir "" CACHE PATH "Path to the tomlplusplus installation directory") - -# 设置默认的库名和头文件路径 -set(tomlplusplus_LIBRARIES "") -set(tomlplusplus_INCLUDE_DIRS "") - -# 检查给定路径是否存在 toml++ 库文件 -find_path(tomlplusplus_INCLUDE_DIRS "toml.hpp" HINTS ${tomldir}/include) - -# 如果找到头文件路径,则设置 tomlplusplus_FOUND 为真 -if (tomlplusplus_INCLUDE_DIRS) - set(tomlplusplus_FOUND TRUE) -endif() - -# 检查给定路径是否存在 toml++ 库文件 -find_library(tomlplusplus_LIBRARIES NAMES tomlplusplus libtomlplusplus HINTS ${tomldir}/lib) - -# 如果找到库文件,则设置 tomlplusplus_FOUND 为真 -if (tomlplusplus_LIBRARIES) - set(tomlplusplus_FOUND TRUE) -endif() - -# 导出结果变量 -if (tomlplusplus_FOUND) - set(tomlplusplus_INCLUDE_DIRS ${tomlplusplus_INCLUDE_DIRS} CACHE PATH "Path to the tomlplusplus include directory.") - set(tomlplusplus_LIBRARIES ${tomlplusplus_LIBRARIES} CACHE FILEPATH "Path to the tomlplusplus library.") -endif() diff --git a/cmake_modules/ScanModule.cmake b/cmake_modules/ScanModule.cmake new file mode 100644 index 00000000..8a54b8c6 --- /dev/null +++ b/cmake_modules/ScanModule.cmake @@ -0,0 +1,17 @@ +function(scan_and_generate_modules source_dir return_var) + set(modules_name_r "") + file(GLOB_RECURSE CPP_FILES "${source_dir}/*.cpp") + foreach(cpp_file ${CPP_FILES}) + file(READ ${cpp_file} file_content) + string(REGEX MATCH "ATOM_MODULE\\(([a-zA-Z0-9_]+)," match ${file_content}) + if(match) + string(REGEX REPLACE "ATOM_MODULE\\(([a-zA-Z0-9_]+),.*" "\\1" module_name ${match}) + if(NOT module_name) + message(WARNING "Found ATOM_MODULE macro in ${cpp_file} but could not extract module name.") + continue() + endif() + set(modules_name_r ${module_name}) + endif() + endforeach() + set(${return_var} "${module_name_r}" PARENT_SCOPE) +endfunction() diff --git a/conanfile.py b/conanfile.py index 99ed9e98..a2462b84 100644 --- a/conanfile.py +++ b/conanfile.py @@ -14,9 +14,7 @@ class ConanFile(ConanFile): requires = [ "argparse/3.0", - "backward-cpp/1.6", "cpp-httplib/0.15.3", - "libzippp/7.1-1.10.1", "openssl/3.2.1", "zlib/1.3.1", "oatpp/1.3.0", @@ -24,11 +22,8 @@ class ConanFile(ConanFile): "oatpp-openssl/1.3.0", "oatpp-swagger/1.3.0", "loguru/cci.20230406", - "magic_enum/0.9.5", "cfitsio/4.3.1", "tinyxml2/10.0.0", - "pybind11/2.12.0", - "pybind11_json/0.2.13", "cpython/3.12.2", "fmt/10.2.1", "opencv/4.9.0" diff --git a/conanfile.txt b/conanfile.txt index 235be8c4..1d181bcc 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -1,13 +1,10 @@ [requires] argparse/3.0 -backward-cpp/1.6 cfitsio/4.3.1 cpython/3.12.2 cpp-httplib/0.15.3 fmt/10.2.1 -libzippp/7.1-1.10.1 loguru/cci.20230406 -magic_enum/0.9.5 oatpp/1.3.0 oatpp-websocket/1.3.0 oatpp-openssl/1.3.0 diff --git a/config/template/goto_center.json b/config/template/goto_center.json index ffb2b3be..f3df6a02 100644 --- a/config/template/goto_center.json +++ b/config/template/goto_center.json @@ -1,186 +1,264 @@ { - "name": "GotoCenter", - "version": "1.0", - "author": "Max Qian", - "type": "group", - "error": "yes", - "warning": "yes", - "tasks": [ - [ - { - "name": "Goto", - "type": "action", - "action": "goto_target", - "next_step": "CheckCrood", - "priority": 1, - "async": false, - "retry": 1, - "target_type": "device.telescope", - "target": "Telescope Simulator", - "params": { - "target": "target_name", - "ra": "xx.xx.xx", - "dec": "xx.xx.xx", - "az": "xx.xx.xx", - "alt": "xx.xx.xx" - } + "name": "GotoCenter", + "version": "2.0", + "author": "Max Qian", + "functions": [ + { + "name": "Goto", + "type": "action", + "action": "goto_target", + "target_type": "device.telescope", + "target": "Telescope Simulator", + "params": { + "target": { + "type": "string", + "required": true + }, + "ra": { + "type": "string", + "required": true, + "format": "xx.xx.xx" + }, + "dec": { + "type": "string", + "required": true, + "format": "xx.xx.xx" + }, + "az": { + "type": "string", + "required": true, + "format": "xx.xx.xx" + }, + "alt": { + "type": "string", + "required": true, + "format": "xx.xx.xx" + } + } + }, + { + "name": "CheckCrood", + "type": "loop", + "loop_iterations": 3, + "steps": [ + { + "name": "Exposure", + "type": "action", + "action": "start_exposure", + "target_type": "device.camera", + "target": "CCD Simulator", + "params": { + "exposure": { + "type": "string", + "required": true, + "format": "xx.xx" + }, + "is_save": { + "type": "boolean", + "required": true + }, + "filename": { + "type": "string", + "required": true, + "default": "solve_tmp.fits" } - ], - [ - { - "name": "CheckCrood", - "type": "loop", - "loop_iterations": 3, - "error": "yes", - "warning": "yes", - "loop_steps": [ - { - "name": "CheckCrood.Exposure", - "type": "action", - "action": "start_exposure", - "next_step": "CheckCrood.Platesolve", - "priority": 1, - "async": false, - "retry": 1, - "target_type": "device.camera", - "target": "CCD Simulator", - "params": { - "exposure": "xx.xx", - "is_save": true, - "filename": "solve_tmp.fits" - } - }, - { - "name": "CheckCrood.Platesolve", - "type": "condition", - "action": "palte_solve", - "next_step": "StartTrack", - "priority": 1, - "async": false, - "retry": 3, - "result": true, - "result_name": "solve_result", - "result_type": "memory", - "result_template": { - "ra": { - "type": "string" - }, - "dec": { - "type": "string" - }, - "ra_error": { - "type": "string" - }, - "dec_error": { - "type": "string" - }, - "radius": { - "type": "string" - } - }, - "target_type": "device.solver", - "target": "Astrometry.net OffLine", - "params": { - "ra": "xx.xx.xx", - "dec": "xx.xx.xx", - "radius": "xx.xx", - "height": "xx.xx", - "width": "xx.xx", - "downsample": 1, - "timeout": 100, - "depth": [ - 1, - 1 - ], - "filename": "solve_tmp.fits" - }, - "condition": { - "ra": "xx.xx.xx", - "dec": "xx.xx.xx", - "az": "xx.xx.xx", - "alt": "xx.xx.xx", - "radius": "xx.xx" - }, - "false": [ - { - "name": "CheckCrood.Calculate", - "type": "action", - "action": "calculate_crood", - "next_step": "CheckCrood.Exposure", - "priority": 1, - "async": false, - "retry": 1, - "target_type": "lithium.device", - "target": "", - "result": true, - "result_name": "proceed_result", - "result_type": "memory", - "result_template": { - "ra": { - "type": "string" - }, - "dec": { - "type": "string" - }, - "radius": { - "type": "string" - } - }, - "import_params": "solve_result", - "params": { - "ra": "solve_result.ra", - "dec": "solve_result.dec", - "radius": "xx.xx" - } - }, - { - "name": "Goto", - "type": "action", - "action": "goto_target", - "next_step": "CheckCrood", - "priority": 1, - "async": false, - "retry": 1, - "target_type": "device.telescope", - "target": "Telescope Simulator", - "result": false, - "import_params": "proceed_result", - "params": { - "target": "target_name", - "ra": "proceed_result.ra", - "dec": "proceed_result.dec", - "az": "proceed_result.az", - "alt": "proceed_result.alt", - "radius": "proceed_result.radius" - } - } - ] - } - ], - "loop_error": [ - { - "name": "GotoCenterError", - "error": "Failed to goto target center" - } - ] + } + }, + { + "name": "Platesolve", + "type": "condition", + "action": "palte_solve", + "target_type": "device.solver", + "target": "Astrometry.net OffLine", + "params": { + "ra": { + "type": "string", + "required": true, + "format": "xx.xx.xx" + }, + "dec": { + "type": "string", + "required": true, + "format": "xx.xx.xx" + }, + "radius": { + "type": "string", + "required": true, + "format": "xx.xx" + }, + "height": { + "type": "string", + "required": true, + "format": "xx.xx" + }, + "width": { + "type": "string", + "required": true, + "format": "xx.xx" + }, + "downsample": { + "type": "integer", + "required": true, + "default": 1, + "range": [1, 10] + }, + "timeout": { + "type": "integer", + "required": true, + "default": 100, + "range": [10, 600] + }, + "depth": { + "type": "array", + "required": true, + "default": [1, 1], + "items": { + "type": "integer", + "range": [1, 10] + } + }, + "filename": { + "type": "string", + "required": true, + "default": "solve_tmp.fits" } - ], - [ + }, + "condition": { + "ra": { + "type": "string", + "required": true, + "format": "xx.xx.xx" + }, + "dec": { + "type": "string", + "required": true, + "format": "xx.xx.xx" + }, + "az": { + "type": "string", + "required": true, + "format": "xx.xx.xx" + }, + "alt": { + "type": "string", + "required": true, + "format": "xx.xx.xx" + }, + "radius": { + "type": "string", + "required": true, + "format": "xx.xx" + } + }, + "true": { + "type": "break" + }, + "false": [ + { + "name": "Calculate", + "type": "action", + "action": "calculate_crood", + "target_type": "lithium.device", + "params": { + "ra": { + "type": "string", + "required": true, + "source": "solve_result.ra" + }, + "dec": { + "type": "string", + "required": true, + "source": "solve_result.dec" + }, + "radius": { + "type": "string", + "required": true, + "format": "xx.xx" + } + } + }, { - "name": "StartTrack", - "type": "action", - "action": "start_track", - "next_step": "StartTrack", - "priority": 2, - "async": true, - "retry": 1, - "result": false, - "target_type": "device.telescope", - "target": "Telescope Simulator", - "params": { - "speed": "star" + "name": "Goto", + "type": "call", + "function": "Goto", + "params": { + "ra": { + "type": "string", + "required": true, + "source": "proceed_result.ra" + }, + "dec": { + "type": "string", + "required": true, + "source": "proceed_result.dec" + }, + "az": { + "type": "string", + "required": true, + "source": "proceed_result.az" + }, + "alt": { + "type": "string", + "required": true, + "source": "proceed_result.alt" + }, + "radius": { + "type": "string", + "required": true, + "source": "proceed_result.radius" } + } } - ] - ] + ] + } + ], + "error": "Failed to goto target center" + }, + { + "name": "StartTrack", + "type": "action", + "action": "start_track", + "target_type": "device.telescope", + "target": "Telescope Simulator", + "params": { + "speed": { + "type": "string", + "required": true, + "default": "star", + "enum": ["star", "lunar", "solar"] + } + } + } + ], + "steps": [ + { + "name": "Goto", + "type": "call", + "function": "Goto", + "params": { + "target": "M42", + "ra": "05:35:17.3", + "dec": "-05:23:28", + "az": "180:00:00", + "alt": "60:00:00" + } + }, + { + "name": "CheckCrood", + "type": "call", + "function": "CheckCrood", + "params": { + "loop_iterations": 5 + } + }, + { + "name": "StartTrack", + "type": "call", + "function": "StartTrack", + "async": true, + "params": { + "speed": "star" + } + } + ] } diff --git a/doc/asyncapi.json b/doc/asyncapi.json deleted file mode 100644 index d9389b97..00000000 --- a/doc/asyncapi.json +++ /dev/null @@ -1,298 +0,0 @@ -{ - "asyncapi": "2.6.0", - "info": { - "title": "Lithium Server API", - "version": "1.0.0", - "description": "An open and light astrophotography terminal like lithium", - "license": { - "name": "GNU GENERAL PUBLIC LICENSE, Version 3", - "url": "https://www.gnu.org/licenses/gpl-3.0.en.html" - } - }, - "servers": { - "main": { - "url": "http://127.0.0.1:8000", - "protocol": "websocket", - "description": "Main websocket server" - } - }, - "channels": { - "GetDeviceList": { - "publish": { - "operationId": "GetDeviceList", - "message": { - "name": "GetDeviceList", - "payload": { - "type": "object", - "properties": { - "device_type": { - "type": "string", - "description": "Type of device to retrieve the list" - } - }, - "required": [ - "device_type" - ] - } - } - } - }, - "AddDevice": { - "publish": { - "operationId": "AddDevice", - "message": { - "name": "AddDevice", - "payload": { - "type": "object", - "properties": { - "device_type": { - "type": "string", - "description": "Type of device to add" - }, - "device_name": { - "type": "string", - "description": "Name of device to add" - }, - "lib_name": { - "type": "string", - "description": "Name of library to load device" - } - }, - "required": [ - "device_name", - "device_type" - ] - } - } - } - }, - "AddDeviceLibrary": { - "publish": { - "operationId": "AddDeviceLibrary", - "message": { - "name": "AddDeviceLibrary", - "payload": { - "type": "object", - "properties": { - "lib_path": { - "type": "string", - "description": "Path of device library to add" - }, - "lib_name": { - "type": "string", - "description": "Name of device library to add" - } - }, - "required": [ - "lib_name", - "lib_path" - ] - } - } - } - }, - "RemoveDevice": { - "publish": { - "operationId": "RemoveDevice", - "message": { - "name": "RemoveDevice", - "payload": { - "type": "object", - "properties": { - "device_type": { - "type": "string", - "description": "Type of device to remove" - }, - "device_name": { - "type": "string", - "description": "Name of device to remove" - } - }, - "required": [ - "device_name", - "device_type" - ] - } - } - } - }, - "RemoveDevicesByName": { - "publish": { - "operationId": "RemoveDevicesByName", - "message": { - "name": "RemoveDevicesByName", - "payload": { - "type": "object", - "properties": { - "device_name": { - "type": "string", - "description": "Name of device to remove" - } - }, - "required": [ - "device_name" - ] - } - } - } - }, - "RemoveDeviceLibrary": { - "publish": { - "operationId": "RemoveDeviceLibrary", - "message": { - "name": "RemoveDeviceLibrary", - "payload": { - "type": "object", - "properties": { - "lib_name": { - "type": "string", - "description": "Name of device library to remove" - } - }, - "required": [ - "lib_name" - ] - } - } - } - }, - "RunDeviceTask": { - "publish": { - "operationId": "RunDeviceTask", - "message": { - "name": "RunDeviceTask", - "payload": { - "type": "object", - "properties": { - "device_name": { - "type": "string" - }, - "device_uuid": { - "type": "string" - }, - "device_type": { - "type": "string" - }, - "task_name": { - "type": "string" - } - }, - "required": [ - "device_type", - "task_name" - ] - } - } - } - }, - "CreateProcess": { - "publish": { - "operationId": "CreateProcess", - "message": { - "name": "CreateProcess", - "title": "Create a Process", - "contentType" :"application/json", - "summary":"Create a alone process to run system command (depend on platform)", - "payload": { - "type": "object", - "properties": { - "command": { - "type": "string" - }, - "cmd_id": { - "type": "string" - } - }, - "required": [ - "command", - "cmd_id" - ] - } - } - } - }, - "RunScript": { - "publish": { - "operationId": "RunScript", - "message": { - "name": "RunScript", - "title": "Run a script", - "contentType" :"application/json", - "summary":"Create a alone process to run shell script (depend on platform)", - "payload": { - "type": "object", - "properties": { - "script_name": { - "type": "string", - "description": "Name of script to run" - }, - "script_id": { - "type": "string", - "description": "ID of script to run , required in TerminateProcessByName" - } - }, - "required": [ - "script_name", - "script_id" - ] - } - } - } - }, - "TerminateProcessByName": { - "publish": { - "operationId": "TerminateProcessByName", - "message": { - "name": "TerminateProcessByName", - "title": "Terminate an exists process", - "contentType" :"application/json", - "summary":"Terminate an existed process created by ProcessManager.Can not operate with other software", - "payload": { - "type": "object", - "properties": { - "process_name": { - "type": "string", - "description": "Name of process to terminate" - } - }, - "required": [ - "process_name" - ] - } - } - } - }, - "GetRunningProcesses": { - "publish": { - "operationId": "GetRunningProcesses", - "message": { - "name": "GetRunningProcesses", - "title": "Get current running processes", - "contentType" :"application/json", - "summary":"获取当前正在运行的所有进程的信息(只包含由ProcessManager创建的,不能获取系统进程,如有需要请调用其他的API)", - "payload": {} - } - } - }, - "GetProcessOutput": { - "publish": { - "operationId": "GetProcessOutput", - "message": { - "name": "GetProcessOutput", - "payload": { - "type": "object", - "properties": { - "process_name": { - "type": "string", - "description": "Name of process to get output" - } - }, - "required": [ - "process_name" - ] - } - } - } - } - } -} diff --git a/doc/atom/algorithm/algorithm_zh.md b/doc/atom/algorithm/algorithm_zh.md new file mode 100644 index 00000000..a13a8202 --- /dev/null +++ b/doc/atom/algorithm/algorithm_zh.md @@ -0,0 +1,198 @@ +# 算法库文档 + +## 概述 + +该库是一个 C++实现的算法集合,包括 Knuth-Morris-Pratt (KMP)字符串搜索算法、Boyer-Moore 字符串搜索算法和一个通用的布隆过滤器(Bloom Filter)数据结构。 + +## 命名空间 + +### `atom::algorithm` + +此命名空间包含了库中提供的所有算法实现的类和函数。 + +## 类 + +### `KMP` + +实现了 Knuth-Morris-Pratt (KMP)字符串搜索算法。 + +#### 构造函数 + +- `explicit KMP(std::string_view pattern)` + + 使用给定的模式构造一个`KMP`对象。 + + **参数:** + + - `pattern` - 要在文本中搜索的模式。 + +#### 公共方法 + +- `[[nodiscard]] auto search(std::string_view text) const -> std::vector` + + 使用 KMP 算法在给定文本中搜索模式的出现位置。 + + **参数:** + + - `text` - 要搜索的文本。 + + **返回:** + + - `std::vector` - 包含模式在文本中起始位置的向量。 + +- `void setPattern(std::string_view pattern)` + + 设置新的搜索模式。 + + **参数:** + + - `pattern` - 要搜索的新模式。 + +#### 私有方法 + +- `auto computeFailureFunction(std::string_view pattern) -> std::vector` + + 计算给定模式的失败函数(部分匹配表)。 + + **参数:** + + - `pattern` - 要计算失败函数的模式。 + + **返回:** + + - `std::vector` - 计算出的失败函数(部分匹配表)。 + +#### 数据成员 + +- `std::string pattern_` + + 要搜索的模式。 + +- `std::vector failure_` + + 模式的失败函数(部分匹配表)。 + +### `BloomFilter` + +实现了布隆过滤器(Bloom Filter)数据结构。 + +#### 模板参数 + +- `N` - 布隆过滤器的大小(位数)。 + +#### 构造函数 + +- `explicit BloomFilter(std::size_t num_hash_functions)` + + 使用指定数量的哈希函数构造一个新的`BloomFilter`对象。 + + **参数:** + + - `num_hash_functions` - 用于布隆过滤器的哈希函数数量。 + +#### 公共方法 + +- `void insert(std::string_view element)` + + 将一个元素插入到布隆过滤器中。 + + **参数:** + + - `element` - 要插入的元素。 + +- `[[nodiscard]] auto contains(std::string_view element) const -> bool` + + 检查布隆过滤器中是否可能包含某个元素。 + + **参数:** + + - `element` - 要检查的元素。 + + **返回:** + + - `bool` - 如果元素可能存在则返回`true`,否则返回`false`。 + +#### 私有方法 + +- `auto hash(std::string_view element, std::size_t seed) const -> std::size_t` + + 使用特定的种子计算元素的哈希值。 + + **参数:** + + - `element` - 要哈希的元素。 + - `seed` - 哈希函数的种子值。 + + **返回:** + + - `std::size_t` - 元素的哈希值。 + +#### 数据成员 + +- `std::bitset m_bits_` + + 代表布隆过滤器的位集。 + +- `std::size_t m_num_hash_functions_` + + 使用的哈希函数数量。 + +### `BoyerMoore` + +实现了 Boyer-Moore 字符串搜索算法。 + +#### 构造函数 + +- `explicit BoyerMoore(std::string_view pattern)` + + 使用给定的模式构造一个`BoyerMoore`对象。 + + **参数:** + + - `pattern` - 要在文本中搜索的模式。 + +#### 公共方法 + +- `auto search(std::string_view text) const -> std::vector` + + 使用 Boyer-Moore 算法在给定文本中搜索模式的出现位置。 + + **参数:** + + - `text` - 要搜索的文本。 + + **返回:** + + - `std::vector` - 包含模式在文本中起始位置的向量。 + +- `void setPattern(std::string_view pattern)` + + 设置新的搜索模式。 + + **参数:** + + - `pattern` - 要搜索的新模式。 + +#### 私有方法 + +- `void computeBadCharacterShift()` + + 计算当前模式的坏字符偏移表。 + +- `void computeGoodSuffixShift()` + + 计算当前模式的好后缀偏移表。 + +#### 数据成员 + +- `std::string pattern_` + + 要搜索的模式。 + +- `std::unordered_map bad_char_shift_` + + 坏字符偏移表。 + +- `std::vector good_suffix_shift_` + + 好后缀偏移表。 diff --git a/doc/components/README_ZH.md b/doc/components/README_ZH.md index f1f00128..cf6c317e 100644 --- a/doc/components/README_ZH.md +++ b/doc/components/README_ZH.md @@ -1,125 +1,130 @@ # Lithium & Atom Components -Atom 的组件机制是元素天文所有组建的基础,正如其名,组件是最基础的,你可以通过组件增强服务器的功能。 +Atom的组件机制是元素天文所有组件的基础,正如其名,组件是最基础的,是一切功能的核心,通过组件可以个性化扩展服务器功能。 ## 加载机制 -由于 c++的特殊性,导致无法像 java 那样通过类名加载类,所以组件的加载机制是通过加载动态库后获取对应的共享类指针完成的 +由于C++的特殊性,无法像Java那样通过类名加载类,更加不能像python那样导入文件即可。目前采用的组件加载机制是加载动态库后获取指定共享类指针。 ## 组件组成 -每个组件都是有动态库和对应的 package.json 组成的,如果后需要有需要的话,可以加入 package.xml 之类的文件来描述组件的依赖关系。 +每个组件由动态库和对应的`package.json`组成,如果需要的话,可以加入`package.xml`等文件来描述组件的依赖关系。 + +> [!IMPORTANT] +> 优先支持`package.json`,不一定会兼容xml格式 举一个例子: +> [!NOTE] +> 一个文件夹中可以有多个不同名称的动态,只需要在`package.json`中指定即可 + ```txt component-demo ├── package.json └── component-demo.so + └── component-demo-2.so ``` -在这个例子中,我们的 component-demo 组件中包含一个 component-demo.so(可以与文件夹名称不同,而且后缀名根据平台而定)动态库,以及 package.json 文件。 +在这个例子中,`component-demo`组件包含一个或多个动态库(后缀名根据平台而定,__需要特别注意的是,Mingw64环境需要使用so作为后缀,虽然也是Windows环境__)和`package.json`文件。 ### package.json -package.json 是组件的配置文件,它包含了组件的基本信息,比如组件的名称,版本,作者等。 +`package.json`是组件的配置文件,包含组件的基本信息,比如组件的名称、版本、作者等。 ```json { - "name": "example", - "version": "0.0.0", - "type": "shared", - "description": "An example project", - "license": "LGPL-3.0-or-later", - "author": "Max Qian", - "repository": { - "type": "git", - "url": "https://github.com/maxqian/cobalt-example.git" - }, - "bugs": { - "url": "https://github.com/maxqian/cobalt-example/issues" - }, - "homepage": "https://github.com/maxqian/cobalt-example", - "keywords": ["atom", "example", "component"], - "scripts": { - "dev": "run" - }, - "dependencies": ["atom.core"], - "main": { - "example1": { - "func": "getExample1", - "type": "shared" - } - } + "name": "atom.io", + "version": "1.0.0", + "type": "shared", + "description": "Atom IO Module", + "license": "GPL-3.0-or-later", + "author": "Max Qian", + "repository": { + "type": "git", + "url": "https://github.com/ElementAstro/Lithium" + }, + "bugs": { + "type": "git", + "url": "https://github.com/ElementAstro/Lithium/issues" + }, + "homepage": { + "type": "git", + "url": "https://github.com/ElementAstro/Lithium" + }, + "keywords": [ + "lithium", + "config" + ], + "scripts": { + "build": "cmake --build-type=Release -- -j 4", + "lint": "clang-format -i src/*.cpp src/*.h" + }, + "modules": [ + { + "name": "io", + "entry": "getInstance" + } + ] } + ``` -其中`main`是最为重要的部分,它定义了组件的入口函数,以及组件的类型,目前支持`shared` 、 `inject`、`alone`、`executable`四种类型。 ++ `type`声明组件类型,分为`shared`和`standalone`两种,`shared`表示共享组件,`standalone`表示独立组件,加载机制完全不同! ++ `scripts`是脚本,用于构建和格式化代码。 ++ `modules`是一个json数组,其中包括若干组件信息,包括组件的名称(是注册在模块管理器中的名称)、和入口函数(需要与动态中完全相同,可以是C++翻译之后的名称)。 -Warning: 每种组件的加载机制不太一样,请注意区分。 +__Warning__: 整个`package.json`文件必须包含`modules`字段,否则无法正常加载。 ### 动态库 -动态库的加载具体加载逻辑请参考`atom/module/module_loader.cpp`,在 Windows 下平台我们使用了`dl-win`库,因此函数形式与 Linux 下一致。 +加载逻辑请参考`atom/module/module_loader.cpp`,在Windows平台我们使用了`dl-win`库(后续会添加Windows原生API的支持),因此函数形式与Linux下保持一致。 -你可以自己写一个小函数进行测试,后续也会提供对应的构建工具 +你可以自己写一个小函数进行测试,后续也会提供对应的构建工具。 ```cpp -#include +// example.cpp #include -int main() -{ - void* handle = dlopen("./component-demo.so", RTLD_LAZY); - if (handle == nullptr) - { - std::cout << dlerror() << std::endl; - return -1; +// 这个函数将被导出到动态库 +extern "C" void helloWorld() { + std::cout << "Hello, World!" << std::endl; +} + +// 这个类将不会被导出,因为它是C++特定的 +class MyClass { +public: + void sayHello() { + std::cout << "MyClass says hello!" << std::endl; } +}; - return 0; +// 这个函数将被导出,它将创建一个MyClass的实例并调用其sayHello方法 +extern "C" void callMyClass() { + MyClass myObj; + myObj.sayHello(); } ``` -动态库中必须要存在 package.json 中声明的函数,否则将无法正常加载。 - ## 组件注册 -组件的注册与管理使用`ComponentManager`完成,下面是 Lithium 服务器中组件管理的目录架构 +组件的注册与管理使用`ComponentManager`完成,下面是Lithium服务器中组件管理的目录架构: ```txt components - ├── component-finder.cpp - ├── component_finder.hpp - ├── component_info.cpp - ├── component_info.hpp - ├── component_manager.cpp - ├── component_manager.hpp - ├── package_manager.cpp - └── package_manager.hpp - ├── project_info.cpp - └── project_info.hpp - ├── project_manager.cpp - └── project_manager.hpp - ├── sanbox.cpp - └── sanbox.hpp + ├── addons.cpp + ├── addons.hpp + ├── compiler.cpp + ├── compiler.hpp + ├── component.hpp + ├── dependency.cpp + ├── dependency.hpp + ├── loader.cpp + ├── loader.hpp + ├── manager.cpp + ├── manager.hpp + ├── module.cpp + ├── sandbox.cpp + ├── sandbox.hpp + ├── sort.cpp + ├── sort.hpp ``` - -其中每个组件的功能: -`component-finder`是组件的查找器,主要负责遍历`modules`目录下所有符合条件的文件夹,具体的条件为存在至少一个动态库和 package.json 文件 -`component_info`是组件信息的定义,主要用来处理 package.json 中的组件信息 -`component_manager`是组件管理器,主要用来管理组件的加载和卸载 -`package_manager`是包管理器,主要用来管理组件的包 -`project_info`是项目信息的定义,主要用来组件之间的依赖关系和防止循环引用 -`project_manager`是项目管理器,主要用来管理项目的加载和卸载,可以更加方便的更新组件,提供 Git 项目管理和基础的 CI 构建机制 -`sanbox`是沙盒,主要用来隔离组件的运行环境,创建`独立组件`的安全运行环境,在 Linux 下的运行效果较好 - -### 设备组件 - -设备组件不同于普通的组件,它的交互机制类似于 INDI,但是协议更加简单,后续会单独作为 atom.driver 驱动库进行实现。 - -## 组件通信 - -### 共享组件 - -共享组件间的通信是使用`MessageBus(Global)`实现的 diff --git a/doc/device/ascom_switch.md b/doc/device/ascom_switch.md new file mode 100644 index 00000000..48352622 --- /dev/null +++ b/doc/device/ascom_switch.md @@ -0,0 +1,84 @@ +# AscomSwitch + +The `AscomSwitch` class is part of the `NINA.Equipment.Equipment.MySwitch.Ascom` namespace and is used to interact with ASCOM switch devices. + +## Namespace + +```csharp +namespace NINA.Equipment.Equipment.MySwitch.Ascom +``` + +## Class Declaration + +```csharp +internal class AscomSwitch : BaseINPC, ISwitch +``` + +## Properties + +### `Id` + +- **Type:** `short` +- **Description:** The unique identifier for the switch. + +### `Name` + +- **Type:** `string` +- **Description:** The name of the switch. +- **Property Change Notification:** Uses `RaisePropertyChanged` when updated. + +### `Description` + +- **Type:** `string` +- **Description:** A description of the switch. + +### `Value` + +- **Type:** `double` +- **Description:** The current value of the switch. +- **Property Change Notification:** Uses `RaisePropertyChanged` when updated. + +## Constructors + +### `AscomSwitch` + +```csharp +public AscomSwitch(ISwitchV2 s, short id) +``` + +- **Parameters:** + - `s`: An instance of `ISwitchV2` representing the ASCOM switch hub. + - `id`: The unique identifier for the switch. +- **Description:** Initializes the `AscomSwitch` instance with the provided switch hub and identifier. Retrieves initial values for `Name`, `Description`, and `Value`. + +## Methods + +### `Poll` + +```csharp +public async Task Poll() +``` + +- **Description:** Polls the switch for updated values. +- **Flowchart:** + +```mermaid +graph TD; + A[Poll Method] --> B[Start Task] + B --> C[Try to Retrieve Name and Value] + C -- Success --> D[Update Name and Value] + C -- Failure --> E[Log Failure] + D --> F[Raise PropertyChanged] + E --> F + F --> G[Return Success] + G --> H[End] +``` + +**Detailed Steps:** + +1. **Start Task:** Begins an asynchronous task to retrieve the switch values. +2. **Try to Retrieve Name and Value:** Attempts to get the `Name` and `Value` from the switch hub. + - **Success:** Updates the `Name` and `Value` properties and logs the retrieved values. + - **Failure:** Logs a failure message. +3. **Raise PropertyChanged:** Notifies that the properties have changed if retrieval was successful. +4. **Return Success:** Returns a boolean indicating whether the operation was successful. diff --git a/doc/device/ascom_switch_hub.md b/doc/device/ascom_switch_hub.md new file mode 100644 index 00000000..58e364ba --- /dev/null +++ b/doc/device/ascom_switch_hub.md @@ -0,0 +1,156 @@ +# AscomSwitchHub + +The `AscomSwitchHub` class is part of the `NINA.Equipment.Equipment.MySwitch.Ascom` namespace and manages a collection of ASCOM switches. It extends `AscomDevice` and implements `ISwitchHub`, providing functionality to discover and interact with ASCOM switch devices. + +## Namespace + +```csharp +namespace NINA.Equipment.Equipment.MySwitch.Ascom +``` + +## Class Declaration + +```csharp +public partial class AscomSwitchHub : AscomDevice, ISwitchHub, IDisposable +``` + +## Properties + +### `Switches` + +- **Type:** `ICollection` +- **Description:** A collection of switches managed by the hub. +- **Property Change Notification:** Uses `ObservableProperty` for automatic property change notification. + +## Constructors + +### `AscomSwitchHub(string id, string name)` + +```csharp +public AscomSwitchHub(string id, string name) : base(id, name) +``` + +- **Parameters:** + - `id`: The identifier for the switch hub. + - `name`: The name of the switch hub. +- **Description:** Initializes a new instance of the `AscomSwitchHub` class with the specified ID and name. Initializes an empty collection of switches. + +### `AscomSwitchHub(AscomDevice deviceMeta)` + +```csharp +public AscomSwitchHub(AscomDevice deviceMeta) : base(deviceMeta) +``` + +- **Parameters:** + - `deviceMeta`: Metadata for the ASCOM device. +- **Description:** Initializes a new instance using the metadata from an `AscomDevice`. Initializes an empty collection of switches. + +## Methods + +### `ScanForSwitches` + +```csharp +private async Task ScanForSwitches() +``` + +- **Description:** Scans for switches connected to the ASCOM switch hub and adds them to the `Switches` collection. +- **Flowchart:** + +```mermaid +graph TD; + A[Start ScanForSwitches] --> B[Get MaxSwitch Count] + B --> C[Loop through Switch Indices] + C --> D[Try to Get Switch] + D --> E[If CanWrite] + E -- Yes --> F[Create AscomWritableSwitch] + E -- No --> G[Create AscomSwitch] + F --> H[Add to Switches] + G --> H + D -- Exception --> I[Handle Exception] + I --> J[Try AscomWritableV1Switch] + J --> K[Add to Switches] + I -- Failure --> L[Create AscomV1Switch] + L --> K + K --> M[End] +``` + +**Detailed Steps:** + +1. **Start ScanForSwitches:** Begins scanning for connected switches. +2. **Get MaxSwitch Count:** Retrieves the maximum number of switches from the device. +3. **Loop through Switch Indices:** Iterates through each switch index. +4. **Try to Get Switch:** Attempts to create a switch object for the given index. + - **If CanWrite:** + - **Yes:** Creates an `AscomWritableSwitch` and adds it to the `Switches` collection. + - **No:** Creates an `AscomSwitch` and adds it to the `Switches` collection. + - **Exception Handling:** + - **Handle Exception:** Catches `MethodNotImplementedException` and tries to create an `AscomWritableV1Switch`. If this fails, creates an `AscomV1Switch`. + +### `PostConnect` + +```csharp +protected override async Task PostConnect() +``` + +- **Description:** Performs actions after establishing a connection, such as scanning for switches. + +- **Flowchart:** + +```mermaid +graph TD; + A[PostConnect] --> B[Call ScanForSwitches] + B --> C[End] +``` + +**Detailed Steps:** + +1. **Call ScanForSwitches:** Initiates the scanning process to discover switches. +2. **End:** Completes the connection post-setup. + +### `PostDisconnect` + +```csharp +protected override void PostDisconnect() +``` + +- **Description:** Performs actions after disconnecting, such as clearing the collection of switches. + +- **Flowchart:** + +```mermaid +graph TD; + A[PostDisconnect] --> B[Clear Switches Collection] + B --> C[End] +``` + +**Detailed Steps:** + +1. **Clear Switches Collection:** Resets the `Switches` collection to an empty state. +2. **End:** Completes the disconnection process. + +### `GetInstance` + +```csharp +protected override ISwitchV2 GetInstance() +``` + +- **Description:** Provides an instance of `ISwitchV2` for the switch hub. +- **Flowchart:** + +```mermaid +graph TD; + A[GetInstance] --> B[Check deviceMeta] + B -- Null --> C[Create Default Switch] + B -- Not Null --> D[Create AlpacaSwitch] + C --> E[Return Switch] + D --> E + E --> F[End] +``` + +**Detailed Steps:** + +1. **Check deviceMeta:** Determines whether `deviceMeta` is null. + - **Null:** Creates a default `Switch` instance. + - **Not Null:** Creates an `AlpacaSwitch` instance based on the metadata. +2. **Return Switch:** Returns the created switch instance. +3. **End:** Completes the instance retrieval process. diff --git a/doc/device/ascom_switch_v1.md b/doc/device/ascom_switch_v1.md new file mode 100644 index 00000000..1f82f94a --- /dev/null +++ b/doc/device/ascom_switch_v1.md @@ -0,0 +1,84 @@ +# AscomV1Switch + +The `AscomV1Switch` class is part of the `NINA.Equipment.Equipment.MySwitch.Ascom` namespace and implements the `ISwitch` interface. It provides functionality for handling ASCOM V1 switches, including retrieving their values and updating the state. + +## Namespace + +```csharp +namespace NINA.Equipment.Equipment.MySwitch.Ascom +``` + +## Class Declaration + +```csharp +internal class AscomV1Switch : BaseINPC, ISwitch +``` + +## Constructors + +### `AscomV1Switch(ISwitchV2 s, short id)` + +```csharp +public AscomV1Switch(ISwitchV2 s, short id) +``` + +- **Parameters:** + - `s`: The ASCOM switch hub instance. + - `id`: The identifier for the switch. +- **Description:** Initializes a new instance of the `AscomV1Switch` class with the specified switch hub and ID. Sets the initial properties of the switch. + +## Properties + +### `Id` + +- **Type:** `short` +- **Description:** The identifier for the switch. + +### `Name` + +- **Type:** `string` +- **Description:** The name of the switch. + +### `Description` + +- **Type:** `string` +- **Description:** The description of the switch. + +### `Value` + +- **Type:** `double` +- **Description:** The value of the switch (0 or 1). + +## Methods + +### `Poll` + +```csharp +public async Task Poll() +``` + +- **Description:** Retrieves the current value of the switch asynchronously and updates the `Value` property. Logs the operation and handles exceptions. + +- **Flowchart:** + +```mermaid +graph TD; + A[Poll] --> B[Run Task] + B --> C[Try to Retrieve Value] + C --> D[Update Value] + D --> E[Log Success] + C -- Exception --> F[Log Failure] + E --> G[Raise PropertyChanged] + F --> G + G --> H[Return Success] +``` + +**Detailed Steps:** + +1. **Run Task:** Executes the value retrieval in a separate task. +2. **Try to Retrieve Value:** Attempts to get the switch value from the ASCOM switch hub. + - **Update Value:** Sets the `Value` property based on the retrieved switch state. + - **Log Success:** Logs the successful retrieval of the switch value. + - **Exception Handling:** Logs the failure if an exception occurs. +3. **Raise PropertyChanged:** Notifies any listeners that the `Value` property has changed. +4. **Return Success:** Returns whether the operation was successful. diff --git a/doc/device/camera.drawio b/doc/device/camera.drawio new file mode 100644 index 00000000..59b22f54 --- /dev/null +++ b/doc/device/camera.drawio @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/device/connect.drawio b/doc/device/connect.drawio new file mode 100644 index 00000000..a6aabb32 --- /dev/null +++ b/doc/device/connect.drawio @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/device/connect.md b/doc/device/connect.md new file mode 100644 index 00000000..b98082ee --- /dev/null +++ b/doc/device/connect.md @@ -0,0 +1,53 @@ +# 连接流程 + +```mermaid +graph TD + A[第一次设备连接顺序] --> B[启动INDI服务器] + B --> C{检查端口是否被占用} + C -->|未被占用| D[获取所有可用设备大类,扫描指定目录下的xml文件] + D -->|成功| E[通过fifo通知INDI启动指定大类设备] + E --> F[通过indi_getprop获取所有可以使用的设备] + F --> G[通过indi_setprop设置所有选中设备,并且记录到设备列表中] + G --> H[启动成功] + + C -->|被占用| I{查找进程,判断是否为INDI服务器} + I -->|是| J[直接杀死] + J --> K[再次尝试] + K -->|成功| D + I -->|否| L[启动失败] + + K --> M[启动失败] + D -->|失败| N[启动失败] + F --> O[进程监视启动] + G --> P[记录用户选择具体设备] + P --> Q[推送前端,等待用户选择] + Q --> R[推送前端,等待用户选择] + + I -.-> S[启动失败] -->|包括但不限于找不到xml文件,解析错误,版本错误| N + C -.-> T[检查fifo管道文件是否创建,是否有缓存] + I -.-> U[此处如果为INDI服务器,可以放心杀死,INDI会处理设备断开连接问题] --> L + F -.-> V[后续步骤出错概率较小,可以暂时不做异常处理] --> R + Q -.-> W[这里直接在大类驱动中添加] --> P +``` + +```mermaid +graph TD + A[设备连接流程] --> B[启动INDI服务器] + B --> C{端口是否被占用?} + C -->|否| D[获取设备列表并扫描xml文件] + D -->|成功| E[推送设备类别至前端] + E --> F[通过fifo启动选定设备] + F --> G[获取所有可用设备并推送前端] + G --> H[通过indi_setprop配置设备并记录] + H --> I[启动成功] + + C -->|是| J{是否为INDI服务器?} + J -->|是| K[杀死进程并重试] + K --> L[获取设备列表并扫描xml文件] + L -->|失败| M[集中错误处理模块] + + J -->|否| M[集中错误处理模块] + + L -->|成功| E + M --> N[记录错误并通知用户] +``` diff --git a/doc/device/switch_chooser_vm.md b/doc/device/switch_chooser_vm.md new file mode 100644 index 00000000..29ab595b --- /dev/null +++ b/doc/device/switch_chooser_vm.md @@ -0,0 +1,127 @@ +# SwitchChooserVM + +## Overview + +The `SwitchChooserVM` class is responsible for choosing and managing switch devices within the N.I.N.A. (Nighttime Imaging 'N' Astronomy) application. It inherits from `DeviceChooserVM` and provides functionality to retrieve a list of switch hubs from various sources, including plugins, ASCOM, and Alpaca. + +### Overall Flowchart + +```mermaid +graph TD + A[Start] --> B[Get Equipment] + B --> C{Retrieve Devices} + C -->|Dummy Device| D[Add Dummy Device] + C -->|Plugin Providers| E[Retrieve Plugin Devices] + E --> F[Add Plugin Devices] + C -->|ASCOM| G[Retrieve ASCOM Devices] + G --> H[Add ASCOM Devices] + C -->|Alpaca| I[Retrieve Alpaca Devices] + I --> J[Add Alpaca Devices] + D --> K[Determine Selected Device] + F --> K + H --> K + J --> K + K --> L[End] +``` + +### Step-by-Step Flowcharts + +#### 1. Get Equipment Method + +The `GetEquipment` method retrieves the list of switch devices from multiple sources and determines the selected device. + +```mermaid +flowchart TD + A[Start] --> B[Acquire Lock] + B --> C[Initialize Device List] + C --> D[Add Dummy Device] + D --> E[Retrieve Plugin Devices] + E --> F[Add Plugin Devices] + F --> G[Retrieve ASCOM Devices] + G --> H[Add ASCOM Devices] + H --> I[Retrieve Alpaca Devices] + I --> J[Add Alpaca Devices] + J --> K[Determine Selected Device] + K --> L[Release Lock] + L --> M[End] + + E -->|For Each Provider| N[Try-Catch] + G --> O[Try-Catch] + I --> P[Try-Catch] +``` + +#### Detailed Steps for Get Equipment + +1. **Acquire Lock** + + ```mermaid + flowchart TD + A[Start] --> B[Lock Object] + B --> C[Lock Acquired] + C --> D[Proceed] + ``` + +2. **Initialize Device List** + + ```mermaid + flowchart TD + A[Start] --> B[Create List of Devices] + B --> C[Add Dummy Device] + C --> D[Proceed] + ``` + +3. **Retrieve Plugin Devices** + + ```mermaid + flowchart TD + A[Start] --> B[Get Providers] + B --> C[Retrieve Devices] + C --> D{Error Occurred?} + D -->|No| E[Add Devices to List] + D -->|Yes| F[Log Error] + E --> G[Proceed] + F --> G + ``` + +4. **Retrieve ASCOM Devices** + + ```mermaid + flowchart TD + A[Start] --> B[Create ASCOM Interaction] + B --> C[Retrieve Switches] + C --> D{Error Occurred?} + D -->|No| E[Add Devices to List] + D -->|Yes| F[Log Error] + E --> G[Proceed] + F --> G + ``` + +5. **Retrieve Alpaca Devices** + + ```mermaid + flowchart TD + A[Start] --> B[Create Alpaca Interaction] + B --> C[Retrieve Switches] + C --> D{Error Occurred?} + D -->|No| E[Add Devices to List] + D -->|Yes| F[Log Error] + E --> G[Proceed] + F --> G + ``` + +6. **Determine Selected Device** + + ```mermaid + flowchart TD + A[Start] --> B[Determine Device] + B --> C[Set Selected Device] + C --> D[End] + ``` + +7. **Release Lock** + + ```mermaid + flowchart TD + A[Start] --> B[Release Lock] + B --> C[End] + ``` diff --git a/doc/device/switch_vm.md b/doc/device/switch_vm.md new file mode 100644 index 00000000..0f284502 --- /dev/null +++ b/doc/device/switch_vm.md @@ -0,0 +1,107 @@ +# SwitchVM + +## Overview + +The `SwitchVM` class is a ViewModel for handling switch equipment in the N.I.N.A. (Nighttime Imaging 'N' Astronomy) application. It manages the connection to switch devices, provides commands for interaction, and updates the state of switches. + +### Overall Flowchart + +```mermaid +graph TD + A[User Interaction] --> B[Execute Command] + B --> C{Command Type} + C -->|Connect| D[Connect Method] + C -->|Disconnect| E[Disconnect Method] + C -->|SetSwitchValue| F[SetSwitchValue Method] + C -->|RescanDevices| G[RescanDevices Method] + C -->|Other| H[Other Methods] + + D --> I[Update Switch Info] + E --> J[Clear Switch Info] + F --> K[Update Switch Value] + G --> L[Update Device List] + I --> M[Broadcast Switch Info] + J --> M + K --> M + L --> M + M --> N[Notify Status] + H --> N + N --> O[Update UI] +``` + +### Step-by-Step Flowcharts + +#### 1. Connect Method + +The `Connect` method establishes a connection to the selected switch device. + +```mermaid +flowchart TD + A[Start] --> B[Acquire Semaphore] + B --> C[Disconnect Existing Connection] + C --> D{Check Device ID} + D -->|No_Device| E[Set Device ID] + D -->|Valid| F[Update Status to Connecting] + F --> G[Initialize Cancellation Token] + G --> H[Connect to Device] + H --> I{Check Connection} + I -->|Connected| J[Update Switch Hub] + I -->|Not Connected| K[Notify Error] + J --> L[Initialize Switch Collections] + L --> M[Update Switch Info] + M --> N[Start Update Timer] + N --> O[Notify Success] + O --> P[Release Semaphore] + P --> Q[End] + K --> P +``` + +#### 2. Disconnect Method + +The `Disconnect` method terminates the connection and cleans up resources. + +```mermaid +flowchart TD + A[Start] --> B{Check if Connected} + B -->|Yes| C[Stop Update Timer] + B -->|No| D[Skip Stop Timer] + C --> E[Disconnect Device] + E --> F[Clear Switch Collections] + F --> G[Reset Switch Info] + G --> H[Notify Disconnection] + H --> I[Release Semaphore] + I --> J[End] + D --> I +``` + +#### 3. SetSwitchValue Method + +The `SetSwitchValue` method sets the value of a writable switch and verifies the update. + +```mermaid +flowchart TD + A[Start] --> B[Set Switch Value] + B --> C[Poll Switch] + C --> D{Check Value Difference} + D -->|Within Tolerance| E[Success] + D -->|Not Within Tolerance| F[Wait and Retry] + F --> G{Check Timeout} + G -->|Timeout| H[Notify Error] + G -->|No Timeout| C + E --> I[Update UI] + H --> I + I --> J[End] +``` + +#### 4. RescanDevices Method + +The `RescanDevices` method refreshes the list of available devices. + +```mermaid +flowchart TD + A[Start] --> B[Get Equipment from Device Chooser] + B --> C[Update Device List] + C --> D[Return Device IDs] + D --> E[Update UI] + E --> F[End] +``` diff --git a/doc/loading.drawio b/doc/loading.drawio new file mode 100644 index 00000000..5bc0f8c9 --- /dev/null +++ b/doc/loading.drawio @@ -0,0 +1,698 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/task/autofocus/autofocus.md b/doc/task/autofocus/autofocus.md new file mode 100644 index 00000000..3fd8fc73 --- /dev/null +++ b/doc/task/autofocus/autofocus.md @@ -0,0 +1,114 @@ +# RunAutofocus + +The `RunAutofocus` class is part of the N.I.N.A. (Nighttime Imaging 'N' Astronomy) application. It manages the autofocus process for imaging sequences, ensuring the best focus is achieved for astronomical observations. + +## Class Overview + +### Namespace + +- **Namespace:** `NINA.Sequencer.SequenceItem.Autofocus` +- **Dependencies:** + - `NINA.Core.Model` + - `NINA.Profile.Interfaces` + - `NINA.Sequencer.Validations` + - `NINA.Equipment.Interfaces.Mediator` + - `NINA.Core.Utility.WindowService` + - `NINA.Core.Model.Equipment` + - `NINA.Core.Locale` + - `NINA.WPF.Base.Interfaces.ViewModel` + - `NINA.WPF.Base.Interfaces` + +### Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_Autofocus_RunAutofocus_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_Autofocus_RunAutofocus_Description")] +[ExportMetadata("Icon", "AutoFocusSVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_Focuser")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class RunAutofocus : SequenceItem, IValidatable +``` + +### Class Properties + +- **profileService**: Interface for accessing profile settings. +- **history**: Interface for managing image history. +- **cameraMediator**: Interface for interacting with the camera. +- **filterWheelMediator**: Interface for interacting with the filter wheel. +- **focuserMediator**: Interface for interacting with the focuser. +- **autoFocusVMFactory**: Factory for creating autofocus view models. +- **WindowServiceFactory**: Factory for creating window services. + +### Constructor + +The constructor initializes the `RunAutofocus` class with various services and mediators necessary for the autofocus process. + +```csharp +[ImportingConstructor] +public RunAutofocus( + IProfileService profileService, IImageHistoryVM history, ICameraMediator cameraMediator, IFilterWheelMediator filterWheelMediator, IFocuserMediator focuserMediator, IAutoFocusVMFactory autoFocusVMFactory) +``` + +### Key Methods + +- **Clone()**: Creates a copy of the `RunAutofocus` instance. +- **Execute(IProgress progress, CancellationToken token)**: Starts the autofocus process and handles the UI and autofocus execution. +- **Validate()**: Checks if the camera and focuser are connected and ready for autofocus. +- **GetEstimatedDuration()**: Estimates the duration of the autofocus process based on settings and hardware. +- **ToString()**: Provides a string representation of the `RunAutofocus` instance. + +### Flowchart: Execution Process + +Below is a flowchart illustrating the key steps in the `Execute` method of the `RunAutofocus` class. + +```mermaid +flowchart TD + A[Start Execute Method] --> B[Create Autofocus ViewModel] + B --> C[Show Autofocus Window] + C --> D[Get Selected Filter] + D --> E[Start Autofocus] + E --> F{Autofocus Successful?} + F -- No --> G[Handle Failure] + F -- Yes --> H[Append Autofocus Point] + H --> I[Close Autofocus Window] + G --> I +``` + +### Flowchart Explanation + +1. **Create Autofocus ViewModel**: Creates a new autofocus view model using `autoFocusVMFactory`. +2. **Show Autofocus Window**: Displays the autofocus UI window. +3. **Get Selected Filter**: Retrieves the selected filter from the filter wheel if available. +4. **Start Autofocus**: Initiates the autofocus process and obtains a report. +5. **Autofocus Successful?**: Checks if the autofocus was successful. If not, handles the failure. +6. **Append Autofocus Point**: Adds the result of the autofocus to the image history. +7. **Close Autofocus Window**: Closes the autofocus UI window after a delay. + +### Detailed Method Descriptions + +#### `Clone` + +Creates a new instance of `RunAutofocus` with the same configuration as the current instance. + +#### `Execute` + +1. **Create Autofocus ViewModel**: Initializes and displays the autofocus UI. +2. **Start Autofocus**: Executes the autofocus process and captures the result. +3. **Handle Failure**: Manages any errors during the autofocus process. +4. **Append Autofocus Point**: Updates the image history with the new autofocus point. +5. **Close Autofocus Window**: Closes the UI window after a delay. + +#### `Validate` + +1. **Camera Check**: Verifies that the camera is connected. +2. **Focuser Check**: Verifies that the focuser is connected. +3. **Update Issues**: Updates the list of issues based on connectivity checks. + +#### `GetEstimatedDuration` + +Calculates the estimated duration of the autofocus process based on settings and hardware characteristics. Considers exposure times, focuser settings, and number of attempts. + +#### `ToString` + +Provides a string representation of the `RunAutofocus` instance, including the category and item name. diff --git a/doc/task/camera/smart_epxosure.md b/doc/task/camera/smart_epxosure.md new file mode 100644 index 00000000..e974dfc8 --- /dev/null +++ b/doc/task/camera/smart_epxosure.md @@ -0,0 +1,71 @@ +# SmartExposure Class Overview + +The `SmartExposure` class is a key component within the N.I.N.A. (Nighttime Imaging 'N' Astronomy) software, designed to handle imaging sequences. Below is a detailed breakdown of its logic, event handling, validation, cloning, and more. + +## Key Functionalities + +- **Deserialization Initialization** + + - Clears all sequence items, conditions, and triggers during the deserialization process to ensure a clean start. + +- **Constructor** + + - Accepts dependencies like `profileService`, `cameraMediator`, and `filterWheelMediator`. + - Initializes core components including `SwitchFilter`, `TakeExposure`, `LoopCondition`, and `DitherAfterExposures`. + +- **Event Handling** + + - Monitors changes to the `SwitchFilter` property. If the filter changes during sequence creation or execution, the progress is reset. + +- **Error Handling** + + - Incorporates properties for managing error behavior (`ErrorBehavior`) and retry attempts (`Attempts`), ensuring robust error management during execution. + +- **Validation** + + - The `Validate` method checks the configuration of all internal components and returns a boolean indicating whether the sequence is valid for execution. + +- **Cloning** + + - Provides a `Clone` method to create a deep copy of the `SmartExposure` instance, including all its associated components. + +- **Duration Estimation** + + - Calculates the estimated duration for the sequence based on the exposure settings. + +- **Interrupt Handling** + - If an interruption occurs, it is rerouted to the parent sequence for consistent behavior. + +## Flowchart + +```mermaid +graph TD; + A[Start] --> B{OnDeserializing}; + B --> C(Clear Items); + B --> D(Clear Conditions); + B --> E(Clear Triggers); + C --> F[Constructor Called]; + D --> F; + E --> F; + F --> G[Initialize SwitchFilter]; + F --> H[Initialize TakeExposure]; + F --> I[Initialize LoopCondition]; + F --> J[Initialize DitherAfterExposures]; + G --> K[Event Handling]; + H --> K; + I --> K; + J --> K; + K --> L{Property Changed?}; + L --> M[Reset Progress]; + L --> N[Do Nothing]; + M --> O[Validate]; + N --> O; + O --> P{Valid Sequence?}; + P --> Q[Return True]; + P --> R[Return False]; + Q --> S[Cloning]; + R --> S; + S --> T[Estimate Duration]; + T --> U[Interrupt Handling]; + U --> V[End]; +``` diff --git a/doc/task/camera/take_many_exposure.md b/doc/task/camera/take_many_exposure.md new file mode 100644 index 00000000..e923cdce --- /dev/null +++ b/doc/task/camera/take_many_exposure.md @@ -0,0 +1,77 @@ +# TakeManyExposures Class Detailed Overview + +The `TakeManyExposures` class is part of the Nighttime Imaging 'N' Astronomy (N.I.N.A.) software, designed to manage multiple exposures in a sequence. This class builds upon the `SequentialContainer` class and integrates components like `TakeExposure` and `LoopCondition` to facilitate repeated image capturing operations. Below is a detailed breakdown of its functionalities, logic, and methods. + +## Key Functionalities + +1. **Initialization and Cloning**: + - The constructor initializes key components such as `TakeExposure` and `LoopCondition`. + - The `Clone` method creates a deep copy of the `TakeManyExposures` instance, ensuring that all settings and configurations are preserved. + +2. **Deserialization Handling (`OnDeserializing` Method)**: + - Clears the `Items`, `Conditions`, and `Triggers` collections to ensure a clean state when the object is deserialized. + +3. **Error Handling and Attempt Management**: + - The `ErrorBehavior` property controls how errors are handled during execution. It propagates the error behavior setting to all child items. + - The `Attempts` property determines the number of retry attempts for each operation and is also propagated to all child items. + +4. **Validation (`Validate` Method)**: + - Validates the internal `TakeExposure` component to ensure that all necessary settings are correct. + - If validation fails, the issues are collected and made available for further inspection. + +5. **Execution Flow**: + - The main execution logic is driven by the `TakeExposure` component and the `LoopCondition`, which controls the repetition of the exposure sequence. + - If an interruption occurs, the class routes the interrupt request to the parent sequence, ensuring consistent behavior. + +6. **Duration Estimation (`GetEstimatedDuration` Method)**: + - Calculates the estimated total duration of the exposure sequence based on the settings of the `TakeExposure` component. + +## Flowchart + +```mermaid +graph TD; + A[Start] --> B{OnDeserializing?}; + B --> |Yes| C[Clear Items]; + B --> |No| D[Initialize Components]; + C --> D; + D --> E[Set ErrorBehavior]; + E --> F[Set Attempts]; + F --> G[Validate]; + G --> H{Validation Successful?}; + H --> |Yes| I[Execute Loop Condition]; + H --> |No| J[Return Issues]; + I --> K[Take Exposure]; + K --> L{Interrupt?}; + L --> |Yes| M[Interrupt Parent]; + L --> |No| N[Repeat Based on Loop Condition]; + N --> O[End]; + M --> O; + J --> O; +``` + +## Detailed Methods and Properties + +1. **Constructor**: + - Initializes the `TakeExposure` component, which handles individual exposures, and the `LoopCondition`, which manages the repetition of exposures. + +2. **Clone Method**: + - Creates a copy of the `TakeManyExposures` object, duplicating all its settings and configurations for reuse. + +3. **OnDeserializing Method**: + - Ensures that the collections `Items`, `Conditions`, and `Triggers` are cleared during deserialization to avoid conflicts and ensure a fresh start. + +4. **ErrorBehavior Property**: + - Propagates the error behavior setting to all child items, ensuring that error handling is consistent across the entire sequence. + +5. **Attempts Property**: + - Propagates the retry attempts setting to all child items, ensuring that each operation has the same number of retry attempts. + +6. **Validate Method**: + - Validates the settings of the `TakeExposure` component, checking for any issues that might prevent the sequence from executing correctly. + - Returns `true` if validation is successful, otherwise collects and returns issues. + +7. **Interrupt Method**: + - Handles interrupt requests by forwarding them to the parent sequence, ensuring that any ongoing sequence can be halted appropriately. + +8. **GetEstimatedDuration Method**: + - Calculates the total estimated duration of the sequence based on the exposure settings and the number of repetitions defined by the `LoopCondition`. diff --git a/doc/task/camera/take_subframe_exposure.md b/doc/task/camera/take_subframe_exposure.md new file mode 100644 index 00000000..b9daf32c --- /dev/null +++ b/doc/task/camera/take_subframe_exposure.md @@ -0,0 +1,155 @@ +# `TakeSubframeExposure` Class Overview + +The `TakeSubframeExposure` class is part of the **N.I.N.A.** project and extends the `SequenceItem` class to manage subframe exposures in astrophotography sequences. + +## Key Responsibilities + +- Handles the capture of subframe exposures using a connected camera. +- Manages camera settings such as exposure time, gain, offset, and region of interest (ROI). +- Validates the camera configuration and the file paths for saving captured images. +- Supports cloning of its instances to create identical exposure sequences. +- Integrates with other components such as `CameraMediator`, `ImagingMediator`, and `ImageSaveMediator` for coordinated operation. + +## Constructor + +### Importing Constructor + +```csharp +[ImportingConstructor] +public TakeSubframeExposure( + IProfileService profileService, + ICameraMediator cameraMediator, + IImagingMediator imagingMediator, + IImageSaveMediator imageSaveMediator, + IImageHistoryVM imageHistoryVM) +``` + +- **Parameters:** + - `profileService`: Provides access to the current profile settings. + - `cameraMediator`: Facilitates interaction with the camera. + - `imagingMediator`: Manages imaging processes like capturing and preparing images. + - `imageSaveMediator`: Handles the saving of images. + - `imageHistoryVM`: Maintains the history of captured images. + +### Cloning Constructor + +```csharp +private TakeSubframeExposure(TakeSubframeExposure cloneMe) +``` + +- Creates a clone of an existing `TakeSubframeExposure` object. + +## Methods + +### `Execute` + +```csharp +public override async Task Execute(IProgress progress, CancellationToken token) +``` + +- **Purpose:** Executes the subframe exposure sequence. +- **Flow:** + 1. Retrieves target information if available. + 2. Determines the ROI for subframe capture if the camera supports subsampling. + 3. Initiates the capture sequence and processes the image. + 4. Increments the exposure count after a successful capture. + +**Flowchart:** + +```mermaid +graph TD + A[Start Execute] --> B[Retrieve Target] + B --> C{Can SubSample?} + C -->|Yes| D[Calculate ROI] + C -->|No| E[Log Warning] + D --> F[Initiate Capture Sequence] + E --> F + F --> G[Process Image Data] + G --> H{Is Light Sequence?} + H -->|Yes| I[Add to Image History] + H -->|No| J[Complete Execution] + I --> J + J --> K[Increment Exposure Count] + K --> L[End] +``` + +### `Validate` + +```csharp +public bool Validate() +``` + +- **Purpose:** Validates the camera configuration and file paths before execution. +- **Flow:** + 1. Checks camera connection status. + 2. Validates gain and offset values. + 3. Checks if the file path for saving images is valid. + 4. Returns a boolean indicating if the configuration is valid. + +**Flowchart:** + +```mermaid +graph TD + A[Start Validation] --> B{Camera Connected?} + B -->|No| C[Add Issue: Camera Not Connected] + B -->|Yes| D[Validate Gain and Offset] + D --> E{File Path Valid?} + E -->|No| F[Add Issue: Invalid File Path] + E -->|Yes| G[Validation Successful] + F --> G + G --> H[Return Validation Result] +``` + +### `ProcessImageData` + +```csharp +private async Task ProcessImageData(IDeepSkyObjectContainer dsoContainer, IExposureData exposureData, IProgress progress, CancellationToken token) +``` + +- **Purpose:** Processes the captured image data and saves it to the specified location. +- **Flow:** + 1. Prepares the image for saving based on the sequence type. + 2. Retrieves image statistics if it’s a light sequence. + 3. Adds target metadata if available. + 4. Saves the image through `imageSaveMediator`. + +**Flowchart:** + +```mermaid +graph TD + A[Start Process Image Data] --> B[Prepare Image] + B --> C{Is Light Sequence?} + C -->|Yes| D[Retrieve Image Statistics] + C -->|No| E[Skip Statistics] + D --> F[Add Target Metadata] + E --> F + F --> G[Save Image] + G --> H[End] +``` + +### `Clone` + +```csharp +public override object Clone() +``` + +- **Purpose:** Creates and returns a deep copy of the `TakeSubframeExposure` object. + +### `ToString` + +```csharp +public override string ToString() +``` + +- **Purpose:** Provides a string representation of the current state of the `TakeSubframeExposure` object. + +## Properties + +- **ROI (Region of Interest):** Defines the fraction of the sensor to be used for capturing. +- **ExposureTime:** The duration of each exposure. +- **Gain:** The gain setting of the camera. +- **Offset:** The offset setting of the camera. +- **Binning:** The binning mode used for capturing images. +- **ImageType:** The type of image being captured (e.g., LIGHT, DARK). +- **ExposureCount:** The number of exposures captured so far. +- **CameraInfo:** Holds information about the camera’s capabilities and settings. diff --git a/doc/task/camera/task_exposure.md b/doc/task/camera/task_exposure.md new file mode 100644 index 00000000..76bd0c1f --- /dev/null +++ b/doc/task/camera/task_exposure.md @@ -0,0 +1,82 @@ +# TakeExposure Class Detailed Overview + +The `TakeExposure` class is part of the Nighttime Imaging 'N' Astronomy (N.I.N.A.) software, specifically designed for managing the exposure process during an imaging sequence. Below is a detailed breakdown of its functionalities, logic, validation, and more. + +## Key Functionalities + +1. **Initialization and Cloning**: + + - The constructor initializes key components such as mediators (`cameraMediator`, `imagingMediator`, etc.) and sets default values for properties like `Gain`, `Offset`, and `ImageType`. + - The `Clone` method creates a deep copy of the `TakeExposure` instance, ensuring all settings are duplicated accurately. + +2. **Validation (`Validate` Method)**: + + - Ensures the camera is connected and the gain and offset values are within acceptable ranges. + - Checks that the image file path is set and valid. + +3. **Execution (`Execute` Method)**: + + - Handles the process of taking an exposure, including capturing the image, processing the data, and saving it. + - Integrates with the `imagingMediator` to perform the actual capture and image processing tasks. + - Updates the exposure count after each successful capture. + +4. **Image Processing (`ProcessImageData` Method)**: + + - Processes the captured image data, including preparing the image and updating metadata. + - If the sequence is for light frames, it populates statistics and associates the image with the current target. + +5. **Duration Estimation (`GetEstimatedDuration` Method)**: + + - Returns the estimated duration for the exposure based on the `ExposureTime` property. + +6. **Parent Validation (`AfterParentChanged` Method)**: + - Re-validates the sequence item when its parent is changed to ensure consistency. + +## Flowchart + +```mermaid +graph TD; + A[Start] --> B{Validate?}; + B --> |Valid| C[Initialize Components]; + B --> |Invalid| D[Return Issues]; + C --> E[Execute]; + E --> F[Capture Image]; + F --> G[Process Image Data]; + G --> H{Is Light Sequence?}; + H --> |Yes| I[Add to Image History]; + H --> |No| J[Skip Image History]; + I --> K[Save Image]; + J --> K; + K --> L[Increment Exposure Count]; + L --> M[End]; +``` + +## Detailed Methods and Properties + +1. **Constructor**: + + - Initializes properties such as `Gain`, `Offset`, `ImageType`, and sets up the necessary mediators for camera and image handling. + +2. **Clone Method**: + + - Creates a copy of the current `TakeExposure` object with all its properties, ensuring that the exposure settings are carried over. + +3. **Execute Method**: + + - Manages the main sequence of actions for taking an exposure. + - Integrates with the `imagingMediator` to capture the image. + - Handles special logic for deep sky object containers, adjusting the exposure count if necessary. + +4. **ProcessImageData Method**: + + - Prepares and processes the captured image data. + - Updates metadata, such as target name and coordinates, before saving the image. + - Handles additional tasks like calculating statistics if the sequence involves light frames. + +5. **Validation Method**: + + - Checks the camera's connection status and verifies that gain and offset values are within the acceptable range. + - Ensures the file path for saving images is valid. + +6. **GetEstimatedDuration Method**: + - Returns the estimated duration of the exposure based on the set `ExposureTime`. diff --git a/doc/task/filterwheel/switch.md b/doc/task/filterwheel/switch.md new file mode 100644 index 00000000..74df5b08 --- /dev/null +++ b/doc/task/filterwheel/switch.md @@ -0,0 +1,111 @@ +# SwitchFilter + +The `SwitchFilter` class manages the process of switching filters in a filter wheel. It interfaces with both profile services and filter wheel hardware to ensure that the correct filter is selected and applied. + +## Class Overview + +### Namespace + +- **Namespace:** `NINA.Sequencer.SequenceItem.FilterWheel` +- **Dependencies:** + - `NINA.Core.Model` + - `NINA.Profile.Interfaces` + - `NINA.Sequencer.Validations` + - `NINA.Equipment.Interfaces.Mediator` + - `NINA.Core.Locale` + - `NINA.Core.Utility` + +### Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_FilterWheel_SwitchFilter_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_FilterWheel_SwitchFilter_Description")] +[ExportMetadata("Icon", "FW_NoFill_SVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_FilterWheel")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class SwitchFilter : SequenceItem, IValidatable +``` + +### Class Properties + +- **profileService**: Provides access to profile services, including filter settings. +- **filterWheelMediator**: Handles communication with the filter wheel hardware. +- **issues**: A list to capture validation issues. +- **Filter**: The filter to be selected or applied. + +### Constructor + +The constructor initializes the `SwitchFilter` class with a `profileService` and `filterWheelMediator`. It also sets up an event handler to respond to profile changes. + +```csharp +[ImportingConstructor] +public SwitchFilter(IProfileService profileservice, IFilterWheelMediator filterWheelMediator) +``` + +### Key Methods + +- **OnDeserialized(StreamingContext context)**: Ensures the filter is correctly matched after deserialization. +- **MatchFilter()**: Attempts to match the selected filter with the current profile's filter settings. +- **ProfileService_ProfileChanged(object sender, EventArgs e)**: Updates the filter when the profile changes. +- **Execute(IProgress progress, CancellationToken token)**: Switches to the specified filter. +- **Validate()**: Checks if the filter wheel is connected and valid. +- **AfterParentChanged()**: Revalidates the state after the parent changes. +- **ToString()**: Returns a string representation of the class instance. + +### Flowchart: Execution Process + +Below is a flowchart illustrating the key steps in the `Execute` method of the `SwitchFilter` class. + +```mermaid +flowchart TD + A[Start Execute Method] --> B{Is Filter Selected?} + B -->|No| C[Throw Exception: No Filter Selected] + B -->|Yes| D{Is Filter Wheel Connected?} + D -->|No| E[Add Error: Filter Wheel Not Connected] + D -->|Yes| F[Change Filter Using Mediator] + F --> G[Await Filter Change Completion] + G --> H[End Execution] +``` + +### Flowchart Explanation + +1. **Is Filter Selected?**: Checks if a filter has been selected. If not, an exception is thrown. + - **No:** Throws an exception indicating that no filter was selected. + - **Yes:** Proceeds to check if the filter wheel is connected. +2. **Is Filter Wheel Connected?**: Checks if the filter wheel is connected. + - **No:** Adds an error indicating that the filter wheel is not connected. + - **Yes:** Commands the filter wheel to change to the selected filter. +3. **Change Filter Using Mediator**: Uses the filter wheel mediator to change the filter. +4. **Await Filter Change Completion**: Waits for the filter change to complete. +5. **End Execution**: Marks the end of the execution process. + +### Detailed Method Descriptions + +#### `OnDeserialized` + +This method ensures that the filter is correctly matched after deserialization by calling `MatchFilter`. + +#### `MatchFilter` + +Attempts to match the selected filter with the filter settings from the active profile. If the filter can't be found by name, it tries to match by position. + +#### `ProfileService_ProfileChanged` + +Updates the filter settings when the profile changes, ensuring that the `Filter` property is in sync with the active profile. + +#### `Execute` + +Switches to the specified filter. If no filter is selected or if the filter wheel is not connected, it handles these scenarios appropriately. + +#### `Validate` + +Checks if the filter wheel is connected and if the selected filter is valid. Updates the `Issues` property with any validation errors. + +#### `AfterParentChanged` + +Revalidates the state when the parent of this sequence item changes to ensure the filter settings are still valid. + +#### `ToString` + +Provides a string representation of the `SwitchFilter` instance, including the category, item name, and selected filter. diff --git a/doc/task/focuser/move_absolute.md b/doc/task/focuser/move_absolute.md new file mode 100644 index 00000000..447653cc --- /dev/null +++ b/doc/task/focuser/move_absolute.md @@ -0,0 +1,88 @@ +# MoveFocuserAbsolute + +The `MoveFocuserAbsolute` class in the N.I.N.A. application is responsible for controlling the focuser's movement to a specified absolute position during an astronomical imaging session. The focuser plays a crucial role in ensuring the telescope's optics are precisely aligned for capturing sharp images. This class ensures that the focuser moves to the exact position specified and validates that the focuser system is connected before attempting to move it. + +## Class Overview + +### Namespace + +- **Namespace:** `NINA.Sequencer.SequenceItem.Focuser` +- **Dependencies:** + - `NINA.Core.Model` + - `NINA.Sequencer.Validations` + - `NINA.Equipment.Interfaces.Mediator` + - `NINA.Core.Locale` + +### Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_Focuser_MoveFocuserAbsolute_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_Focuser_MoveFocuserAbsolute_Description")] +[ExportMetadata("Icon", "MoveFocuserSVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_Focuser")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class MoveFocuserAbsolute : SequenceItem, IValidatable +``` + +### Class Properties + +- **focuserMediator**: An interface that handles communication with the focuser hardware, specifically managing the movement to an absolute position. +- **Position**: The absolute position to which the focuser should move. +- **Issues**: A list of issues identified during the validation of the focuser's connection status. + +### Constructor + +The constructor initializes the `MoveFocuserAbsolute` class by setting up the connection with the focuser mediator, ensuring that the class can interact with the focuser system to move it to the specified position. + +```csharp +[ImportingConstructor] +public MoveFocuserAbsolute(IFocuserMediator focuserMediator) +``` + +### Key Methods + +- **Execute(IProgress progress, CancellationToken token)**: Moves the focuser to the specified absolute position using the `focuserMediator`. +- **Validate()**: Ensures that the focuser system is connected before attempting to move the focuser. Updates the `Issues` list if any problems are found. +- **AfterParentChanged()**: Re-validates the focuser connection whenever the parent sequence item changes. +- **Clone()**: Creates a new instance of the `MoveFocuserAbsolute` object, preserving its properties and metadata. + +### Flowchart: Execution Process + +Below is a flowchart that outlines the key steps in the `Execute` method of the `MoveFocuserAbsolute` class. + +```mermaid +flowchart TD + A[Start Execute Method] --> B{Is Focuser Connected?} + B -->|No| C[Throw Exception: Focuser Not Connected] + B -->|Yes| D[Move Focuser to Position: Position] + D --> E[Await Movement Completion] + E --> F[End Execution] +``` + +### Flowchart Explanation + +1. **Is Focuser Connected?**: The process begins by verifying that the focuser is connected and ready. + - **No:** If the focuser is not connected, an exception is thrown, aborting the process. + - **Yes:** If connected, the process continues to move the focuser to the specified position. +2. **Move Focuser to Position**: The focuser is instructed to move to the absolute position specified by the `Position` property. +3. **Await Movement Completion**: The system waits for the focuser to reach the desired position. +4. **End Execution**: The method completes execution after successfully moving the focuser. + +### Detailed Method Descriptions + +#### `Execute` Method + +The `Execute` method is the primary function of the `MoveFocuserAbsolute` class. It uses the `focuserMediator` to interact with the focuser hardware, sending the command to move the focuser to a specified absolute position. The method ensures that the focuser reaches the correct position, and if any issues arise, it handles them appropriately. + +#### `Validate` Method + +The `Validate` method checks that the focuser system is properly connected before allowing the movement to occur. It updates the `Issues` list with any problems that it encounters, such as the focuser being disconnected. This validation is crucial to prevent errors during execution. + +#### `AfterParentChanged` Method + +The `AfterParentChanged` method is called whenever the parent sequence item changes. This triggers a re-validation of the `MoveFocuserAbsolute` class to ensure that any contextual changes—such as different equipment or settings—are accounted for. This helps maintain the reliability of the sequence by confirming that moving the focuser is still appropriate in the new context. + +#### `Clone` Method + +The `Clone` method creates a new instance of the `MoveFocuserAbsolute` class, preserving all properties and metadata from the original instance. This is useful for repeating the focuser movement in different parts of a sequence without manually configuring each instance. diff --git a/doc/task/focuser/move_by_temperature.md b/doc/task/focuser/move_by_temperature.md new file mode 100644 index 00000000..bcae2fc9 --- /dev/null +++ b/doc/task/focuser/move_by_temperature.md @@ -0,0 +1,110 @@ +# MoveFocuserByTemperature + +The `MoveFocuserByTemperature` class in the N.I.N.A. application is designed to adjust the focuser position based on temperature changes. This is particularly useful in astrophotography, where temperature fluctuations can cause the telescope's focus to drift, affecting image sharpness. This class ensures that the focuser compensates for temperature changes, either by moving to an absolute position or by adjusting the position relative to the last known temperature. + +## Class Overview + +### Namespace + +- **Namespace:** `NINA.Sequencer.SequenceItem.Focuser` +- **Dependencies:** + - `NINA.Core.Model` + - `NINA.Sequencer.Validations` + - `NINA.Equipment.Interfaces.Mediator` + - `NINA.Core.Locale` + - `NINA.Core.Utility` + +### Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_Focuser_MoveFocuserByTemperature_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_Focuser_MoveFocuserByTemperature_Description")] +[ExportMetadata("Icon", "MoveFocuserByTemperatureSVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_Focuser")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class MoveFocuserByTemperature : SequenceItem, IValidatable, IFocuserConsumer +``` + +### Class Properties + +- **focuserMediator**: Interface that handles communication with the focuser hardware. +- **Slope**: The slope used to calculate the focuser position based on temperature. +- **Intercept**: The intercept used in the linear equation to determine the absolute focuser position. +- **Absolute**: Boolean value indicating whether the focuser should move to an absolute position (`true`) or adjust position relative to temperature changes (`false`). +- **Issues**: List of validation issues, such as connectivity problems or missing temperature data. +- **MiniDescription**: A brief description of the focusing logic used, displayed to the user. + +### Static Fields + +- **lastTemperature**: Stores the last known temperature from the autofocus or manual focus operations. +- **lastRoundoff**: Stores the last roundoff value used for precise adjustments. + +### Constructor + +The constructor initializes the `MoveFocuserByTemperature` class by associating it with a `focuserMediator`, ensuring it can interact with the focuser system to adjust the position based on temperature. + +```csharp +[ImportingConstructor] +public MoveFocuserByTemperature(IFocuserMediator focuserMediator) +``` + +### Key Methods + +- **Execute(IProgress progress, CancellationToken token)**: Adjusts the focuser position based on the current temperature and the provided slope and intercept. +- **Validate()**: Ensures that the focuser system is connected and that temperature data is available. +- **AfterParentChanged()**: Re-validates the focuser connection whenever the parent sequence item changes. +- **Clone()**: Creates a new instance of the `MoveFocuserByTemperature` object, preserving its properties and metadata. +- **UpdateDeviceInfo(Equipment.Equipment.MyFocuser.FocuserInfo deviceInfo)**: Updates the device information. +- **UpdateEndAutoFocusRun(AutoFocusInfo info)**: Logs the temperature after an autofocus run. +- **UpdateUserFocused(Equipment.Equipment.MyFocuser.FocuserInfo info)**: Logs the temperature after a user focuses the telescope. + +### Flowchart: Execution Process + +Below is a flowchart that outlines the key steps in the `Execute` method of the `MoveFocuserByTemperature` class. + +```mermaid +flowchart TD + A[Start Execute Method] --> B{Is Focuser Connected?} + B -->|No| C[Throw Exception: Focuser Not Connected] + B -->|Yes| D{Is Temperature Data Available?} + D -->|No| E[Log Warning: No Temperature Data] + D -->|Yes| F{Absolute Mode?} + F -->|Yes| G[Calculate Absolute Position: Slope * Temperature + Intercept] + G --> H[Move Focuser to Calculated Position] + F -->|No| I[Calculate Relative Position Based on Slope] + I --> J[Move Focuser by Relative Position] + H & J --> K[End Execution] +``` + +### Flowchart Explanation + +1. **Is Focuser Connected?**: The process begins by checking if the focuser is connected. + - **No:** If not connected, an exception is thrown, and the process stops. + - **Yes:** If connected, the process continues to check temperature data. +2. **Is Temperature Data Available?**: Ensures that valid temperature data is available. + - **No:** Logs a warning and stops further execution. + - **Yes:** Proceeds to adjust the focuser based on temperature. +3. **Absolute Mode?**: Checks if the adjustment is in absolute mode or relative mode. + - **Yes (Absolute Mode):** Calculates the absolute position using the formula `Slope * Temperature + Intercept`. + - **No (Relative Mode):** Calculates the position adjustment relative to temperature changes. +4. **Move Focuser**: Executes the calculated focuser movement, either to an absolute position or a relative adjustment. +5. **End Execution**: Completes the execution process. + +### Detailed Method Descriptions + +#### `Execute` Method + +The `Execute` method is the core function of the `MoveFocuserByTemperature` class. It adjusts the focuser's position based on the current temperature reading from the focuser hardware. Depending on whether the `Absolute` property is set to `true` or `false`, the method either calculates an absolute position using the provided slope and intercept or adjusts the position relative to the temperature change. + +#### `Validate` Method + +The `Validate` method checks the connectivity of the focuser system and ensures that temperature data is available. If the focuser is not connected or temperature data is missing, the method updates the `Issues` list with the relevant validation errors, preventing execution until the issues are resolved. + +#### `Clone` Method + +The `Clone` method creates a new instance of the `MoveFocuserByTemperature` class, preserving all the properties and metadata. This is useful for creating multiple instances of the same focuser adjustment logic within a sequence, ensuring consistency across different steps. + +#### `UpdateEndAutoFocusRun` and `UpdateUserFocused` Methods + +These methods log the temperature after an autofocus run or user focus operation. This data is stored in `lastTemperature` and can be used for further adjustments or logging purposes, ensuring that the focuser remains in optimal alignment throughout the imaging session. diff --git a/doc/task/focuser/move_relative.md b/doc/task/focuser/move_relative.md new file mode 100644 index 00000000..5fac6d43 --- /dev/null +++ b/doc/task/focuser/move_relative.md @@ -0,0 +1,88 @@ +# MoveFocuserRelative + +The `MoveFocuserRelative` class is part of the N.I.N.A. application, and it manages the movement of the focuser relative to its current position. This functionality is useful for fine-tuning the focus without needing to specify an absolute position. + +## Class Overview + +### Namespace + +- **Namespace:** `NINA.Sequencer.SequenceItem.Focuser` +- **Dependencies:** + - `NINA.Core.Model` + - `NINA.Sequencer.Validations` + - `NINA.Equipment.Interfaces.Mediator` + - `NINA.Core.Locale` + +### Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_Focuser_MoveFocuserRelative_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_Focuser_MoveFocuserRelative_Description")] +[ExportMetadata("Icon", "MoveFocuserRelativeSVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_Focuser")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class MoveFocuserRelative : SequenceItem, IValidatable +``` + +### Class Properties + +- **focuserMediator**: Manages communication with the focuser hardware. +- **RelativePosition**: The amount by which to move the focuser relative to its current position. +- **issues**: A list to capture validation issues. + +### Constructor + +The constructor initializes the `MoveFocuserRelative` class with a `focuserMediator`, allowing interaction with the focuser system. + +```csharp +[ImportingConstructor] +public MoveFocuserRelative(IFocuserMediator focuserMediator) +``` + +### Key Methods + +- **Execute(IProgress progress, CancellationToken token)**: Moves the focuser by a relative amount specified by the `RelativePosition` property. +- **Validate()**: Ensures that the focuser system is connected. +- **AfterParentChanged()**: Validates the state after the parent changes. +- **ToString()**: Returns a string representation of the class instance. + +### Flowchart: Execution Process + +Below is a flowchart illustrating the key steps in the `Execute` method of the `MoveFocuserRelative` class. + +```mermaid +flowchart TD + A[Start Execute Method] --> B{Is Focuser Connected?} + B -->|No| C[Throw Exception: Focuser Not Connected] + B -->|Yes| D[Move Focuser by Relative Position] + D --> E[Await Movement Completion] + E --> F[End Execution] +``` + +### Flowchart Explanation + +1. **Is Focuser Connected?**: Checks if the focuser is connected. If not, an exception is thrown. + - **No:** Throws an exception indicating that the focuser is not connected. + - **Yes:** Proceeds to move the focuser. +2. **Move Focuser by Relative Position**: Commands the focuser to move by the amount specified in `RelativePosition`. +3. **Await Movement Completion**: Waits for the focuser to complete its movement. +4. **End Execution**: Marks the end of the execution process. + +### Detailed Method Descriptions + +#### `Execute` Method + +The `Execute` method instructs the focuser to move by the amount specified in `RelativePosition`. This relative movement adjusts the focus based on the current position rather than an absolute position. + +#### `Validate` Method + +The `Validate` method checks if the focuser is connected. If not, it adds an appropriate validation error to the `Issues` list. + +#### `AfterParentChanged` Method + +The `AfterParentChanged` method is used to revalidate the state when the parent of this sequence item changes. + +#### `ToString` Method + +The `ToString` method provides a string representation of the `MoveFocuserRelative` instance, including the category, item name, and the relative position. diff --git a/doc/task/guider/dither.md b/doc/task/guider/dither.md new file mode 100644 index 00000000..278a14be --- /dev/null +++ b/doc/task/guider/dither.md @@ -0,0 +1,91 @@ +# Dither + +The `Dither` class in the N.I.N.A. application is designed to execute the dithering process during an astronomical imaging session. Dithering is a technique used to slightly move the telescope between exposures to reduce the impact of fixed pattern noise in the final stacked image. This class is essential for achieving higher-quality images by ensuring that noise patterns do not align across exposures. + +## Class Overview + +### Namespace + +- **Namespace:** `NINA.Sequencer.SequenceItem.Guider` +- **Dependencies:** + - `NINA.Core.Model` + - `NINA.Sequencer.Validations` + - `NINA.Equipment.Interfaces.Mediator` + - `NINA.Core.Locale` + - `NINA.Profile.Interfaces` + +### Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_Guider_Dither_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_Guider_Dither_Description")] +[ExportMetadata("Icon", "DitherSVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_Guider")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class Dither : SequenceItem, IValidatable +``` + +### Class Properties + +- **guiderMediator**: Handles communication with the guider hardware to execute the dithering commands. +- **profileService**: Provides access to the active profile settings, including guider configuration. +- **Issues**: A list of issues found during the validation of the class, particularly related to the connection status of the guider. + +### Constructor + +The constructor initializes the `Dither` class with dependencies on the guider mediator and profile service, ensuring the necessary components are available for executing the dithering process. + +```csharp +[ImportingConstructor] +public Dither(IGuiderMediator guiderMediator, IProfileService profileService) +``` + +### Key Methods + +- **Execute(IProgress progress, CancellationToken token)**: This method executes the dithering process by issuing the dither command to the guider. +- **Validate()**: Checks if the guider is connected and operational before attempting to dither. +- **AfterParentChanged()**: Validates the connection status of the guider when the parent sequence item changes. +- **GetEstimatedDuration()**: Returns an estimated duration for the dithering process, based on the profile's guider settings. +- **Clone()**: Creates a deep copy of the `Dither` object. + +### Flowchart: Execution Process + +Below is a flowchart that outlines the key steps in the `Execute` method of the `Dither` class. + +```mermaid +flowchart TD + A[Start Execute Method] --> B{Is Guider Connected?} + B -->|No| C[Add Issue: Guider Not Connected] + C --> D[Abort Execution] + B -->|Yes| E[Issue Dither Command to Guider] + E --> F[Await Dither Completion] + F --> G[End Execution] +``` + +### Flowchart Explanation + +1. **Is Guider Connected?:** The process begins by checking whether the guider is connected and ready to execute commands. + - **No:** If the guider is not connected, an issue is logged indicating the connection problem, and the execution is aborted. + - **Yes:** If the guider is connected, the process continues. +2. **Issue Dither Command to Guider:** The `Dither` class sends a command to the guider to perform the dithering operation. +3. **Await Dither Completion:** The system waits for the guider to complete the dithering process. +4. **End Execution:** The dithering process concludes, and control is returned to the sequence executor. + +### Detailed Method Descriptions + +#### `Execute` Method + +The `Execute` method is responsible for issuing the dither command to the guider. It relies on the `guiderMediator` to communicate with the guider hardware. The method ensures that the guider is connected and ready before attempting to dither, thus preventing runtime errors. + +#### `Validate` Method + +The `Validate` method checks the connection status of the guider. If the guider is not connected, the method adds an issue to the `Issues` list, which can be reviewed by the user to troubleshoot the problem. This validation step is critical for ensuring that the dithering process can be executed without errors. + +#### `AfterParentChanged` Method + +The `AfterParentChanged` method is called whenever the parent sequence item is changed. It triggers a re-validation of the `Dither` item to ensure that any changes in the sequence context are taken into account, particularly in terms of equipment connectivity. + +#### `GetEstimatedDuration` Method + +The `GetEstimatedDuration` method returns the estimated time required to complete the dithering process. This estimate is based on the `SettleTimeout` value specified in the active profile's guider settings, providing a rough timeline for how long the dithering will take. diff --git a/doc/task/guider/start_guiding.md b/doc/task/guider/start_guiding.md new file mode 100644 index 00000000..e404da0a --- /dev/null +++ b/doc/task/guider/start_guiding.md @@ -0,0 +1,103 @@ +# StartGuiding + +The `StartGuiding` class in the N.I.N.A. application is designed to initiate the guiding process during an astronomical imaging session. Guiding is a critical process in astrophotography, where a separate guider camera or system is used to keep the telescope precisely aligned with the target object. This class ensures that the guiding process starts correctly and optionally forces a new calibration. + +## Class Overview + +### Namespace + +- **Namespace:** `NINA.Sequencer.SequenceItem.Guider` +- **Dependencies:** + - `NINA.Core.Model` + - `NINA.Sequencer.Validations` + - `NINA.Equipment.Interfaces.Mediator` + - `NINA.Core.Locale` + +### Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_Guider_StartGuiding_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_Guider_StartGuiding_Description")] +[ExportMetadata("Icon", "GuiderSVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_Guider")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class StartGuiding : SequenceItem, IValidatable +``` + +### Class Properties + +- **guiderMediator**: Manages communication with the guider hardware, handling the start of the guiding process. +- **ForceCalibration**: A boolean flag indicating whether the guiding process should force a new calibration before starting. +- **Issues**: A list of issues identified during validation, particularly related to the guider’s connection status and calibration capability. + +### Constructor + +The constructor initializes the `StartGuiding` class by setting up the connection with the guider mediator. This ensures that the class can interact with the guider system when executing the guiding process. + +```csharp +[ImportingConstructor] +public StartGuiding(IGuiderMediator guiderMediator) +``` + +### Key Methods + +- **Execute(IProgress progress, CancellationToken token)**: Starts the guiding process, optionally forcing a new calibration. If the process fails, an exception is thrown. +- **Validate()**: Validates the connection to the guider and checks if forced calibration is possible, updating the `Issues` list if any problems are detected. +- **AfterParentChanged()**: Re-validates the guider connection and capabilities whenever the parent sequence item changes. +- **Clone()**: Creates a copy of the `StartGuiding` object, preserving its properties and metadata. + +### Flowchart: Execution Process + +Below is a flowchart that outlines the key steps in the `Execute` method of the `StartGuiding` class. + +```mermaid +flowchart TD + A[Start Execute Method] --> B{Is Guider Connected?} + B -->|No| C[Throw Exception: Guider Not Connected] + B -->|Yes| D{Force Calibration?} + D -->|Yes| E{Can Guider Clear Calibration?} + E -->|No| F[Throw Exception: Cannot Clear Calibration] + E -->|Yes| G[Start Guiding with Calibration] + D -->|No| H[Start Guiding without Calibration] + G --> I[Await Guiding Completion] + H --> I[Await Guiding Completion] + I --> J{Guiding Started Successfully?} + J -->|No| K[Throw Exception: Failed to Start Guiding] + J -->|Yes| L[End Execution] +``` + +### Flowchart Explanation + +1. **Is Guider Connected?**: The process begins by verifying that the guider is connected and ready. + - **No:** If the guider is not connected, an exception is thrown, aborting the process. + - **Yes:** If connected, the process continues to the next step. +2. **Force Calibration?**: Checks if the guiding process should force a new calibration. + - **Yes:** If calibration is required, the system checks if the guider can clear its current calibration. + - **No:** If no calibration is forced, guiding begins without recalibration. +3. **Can Guider Clear Calibration?**: If calibration is forced, this step checks whether the guider can clear its existing calibration. + - **No:** If the guider cannot clear calibration, an exception is thrown. + - **Yes:** If it can, the system proceeds to start guiding with calibration. +4. **Start Guiding**: The guiding process is initiated, either with or without calibration based on the previous steps. +5. **Await Guiding Completion**: The system waits for the guiding process to either succeed or fail. +6. **Guiding Started Successfully?**: A final check to confirm that guiding has started successfully. + - **No:** If guiding fails, an exception is thrown. + - **Yes:** If successful, the process completes. + +### Detailed Method Descriptions + +#### `Execute` Method + +The `Execute` method is the core of the `StartGuiding` class. It handles starting the guiding process, including the optional step of forcing a new calibration. The method uses the `guiderMediator` to interact with the guider hardware, ensuring the process is executed correctly. If any issues arise—such as a failure to start guiding or the inability to clear calibration—an exception is thrown to halt the sequence. + +#### `Validate` Method + +The `Validate` method checks the readiness of the guiding system. It ensures that the guider is connected and, if necessary, verifies that it can clear calibration data before starting a new calibration. The results of this validation are stored in the `Issues` list, which provides a way to identify and troubleshoot potential problems before executing the guiding process. + +#### `AfterParentChanged` Method + +The `AfterParentChanged` method is invoked whenever the parent sequence item changes. This triggers a re-validation of the `StartGuiding` class to ensure that any contextual changes—such as different equipment or settings—are taken into account. It helps maintain the integrity of the sequence by ensuring that guiding can be started successfully in the new context. + +#### `Clone` Method + +The `Clone` method creates a new instance of the `StartGuiding` class with the same properties and metadata as the original. This allows the guiding process to be reused or repeated in different parts of a sequence without needing to manually reconfigure each instance. diff --git a/doc/task/guider/stop_guiding.md b/doc/task/guider/stop_guiding.md new file mode 100644 index 00000000..5092c7bd --- /dev/null +++ b/doc/task/guider/stop_guiding.md @@ -0,0 +1,87 @@ +# StopGuiding + +The `StopGuiding` class in the N.I.N.A. application is responsible for halting the guiding process during an astronomical imaging session. Guiding is a critical aspect of astrophotography, where a guiding camera or system keeps the telescope aligned with the target object. This class ensures that the guiding process stops correctly and validates that the guiding system is properly connected before attempting to stop guiding. + +## Class Overview + +### Namespace + +- **Namespace:** `NINA.Sequencer.SequenceItem.Guider` +- **Dependencies:** + - `NINA.Core.Model` + - `NINA.Sequencer.Validations` + - `NINA.Equipment.Interfaces.Mediator` + - `NINA.Core.Locale` + +### Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_Guider_StopGuiding_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_Guider_StopGuiding_Description")] +[ExportMetadata("Icon", "StopGuiderSVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_Guider")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class StopGuiding : SequenceItem, IValidatable +``` + +### Class Properties + +- **guiderMediator**: An interface that handles communication with the guider hardware, specifically managing the stop guiding process. +- **Issues**: A list of issues that are identified during the validation of the guider's connection status. + +### Constructor + +The constructor initializes the `StopGuiding` class by setting up the connection with the guider mediator, ensuring that the class can interact with the guider system to stop the guiding process. + +```csharp +[ImportingConstructor] +public StopGuiding(IGuiderMediator guiderMediator) +``` + +### Key Methods + +- **Execute(IProgress progress, CancellationToken token)**: Stops the guiding process using the `guiderMediator`. If any issues occur, an exception may be thrown. +- **Validate()**: Ensures that the guiding system is connected before attempting to stop guiding. Updates the `Issues` list if any problems are found. +- **AfterParentChanged()**: Re-validates the guider connection whenever the parent sequence item changes. +- **Clone()**: Creates a new instance of the `StopGuiding` object, preserving its properties and metadata. + +### Flowchart: Execution Process + +Below is a flowchart that outlines the key steps in the `Execute` method of the `StopGuiding` class. + +```mermaid +flowchart TD + A[Start Execute Method] --> B{Is Guider Connected?} + B -->|No| C[Throw Exception: Guider Not Connected] + B -->|Yes| D[Stop Guiding Process] + D --> E[Await Stopping Completion] + E --> F[End Execution] +``` + +### Flowchart Explanation + +1. **Is Guider Connected?**: The process begins by verifying that the guider is connected and ready. + - **No:** If the guider is not connected, an exception is thrown, aborting the process. + - **Yes:** If connected, the process continues to stop the guiding process. +2. **Stop Guiding Process**: The guider is instructed to stop the guiding process. +3. **Await Stopping Completion**: The system waits for the guiding process to stop completely. +4. **End Execution**: The method completes execution after successfully stopping the guider. + +### Detailed Method Descriptions + +#### `Execute` Method + +The `Execute` method is the primary function of the `StopGuiding` class. It uses the `guiderMediator` to interact with the guider hardware, sending the command to stop guiding. The method ensures that the guiding process halts successfully, and if any issues arise, it handles them appropriately, potentially throwing an exception. + +#### `Validate` Method + +The `Validate` method checks that the guiding system is properly connected before allowing the guiding process to be stopped. It updates the `Issues` list with any problems that it encounters, such as the guider being disconnected. This validation is crucial to prevent errors during execution. + +#### `AfterParentChanged` Method + +The `AfterParentChanged` method is called whenever the parent sequence item changes. This triggers a re-validation of the `StopGuiding` class to ensure that any contextual changes—such as different equipment or settings—are accounted for. This helps maintain the reliability of the sequence by confirming that stopping guiding is still appropriate in the new context. + +#### `Clone` Method + +The `Clone` method creates a new instance of the `StopGuiding` class, preserving all properties and metadata from the original instance. This is useful for repeating the stop guiding process in different parts of a sequence without manually configuring each instance. diff --git a/doc/task/interpreter.md b/doc/task/interpreter.md new file mode 100644 index 00000000..5fff03d4 --- /dev/null +++ b/doc/task/interpreter.md @@ -0,0 +1,328 @@ +# TaskInterpreter Class Overview + +The TaskInterpreter class is part of the lithium namespace and is designed to load, manage, and execute scripts in JSON format. It provides a variety of methods to control script execution, manage variables, and handle exceptions. Below is an overview of the class and its key components. + +## Overall Flowchart + +```mermaid +flowchart TD + A[TaskInterpreter] + A1(TaskInterpreterImpl) + A2(Scripts Management) + A3(Variables Management) + A4(Functions Management) + A5(Execution Control) + A6(Event Queue) + + A --> A1 + A1 --> A2 + A1 --> A3 + A1 --> A4 + A1 --> A5 + A1 --> A6 + + A2 --> B1[loadScript] + A2 --> B2[unloadScript] + A2 --> B3[hasScript] + A2 --> B4[getScript] + A3 --> C1[setVariable] + A3 --> C2[getVariable] + A4 --> D1[registerFunction] + A4 --> D2[registerExceptionHandler] + A5 --> E1[execute] + A5 --> E2[stop] + A5 --> E3[pause] + A5 --> E4[resume] + A5 --> E5[executeStep] + A6 --> F1[queueEvent] +``` + +### Explanation + +TaskInterpreterImpl: This is a private implementation class that contains the actual data structures and methods used by TaskInterpreter. It includes: +Scripts Management: Manages loading, unloading, and checking of scripts. +Variables Management: Manages setting and getting of variables. +Functions Management: Handles function registration and invocation. +Execution Control: Handles the execution flow of the scripts, including pause, stop, and resume functionalities. +Event Queue: Manages the event queue for the interpreter. + +## Key Functions + +### `loadScript` Function + +```mermaid +flowchart TD + A[loadScript -name, script] + B[Lock Mutex] + C[Store script in scripts_] + D[Unlock Mutex] + E[Call prepareScript - script] + F[Call parseLabels - script] + G[Exception Handling] + + A --> B + B --> C + C --> D + D --> E + E --> F + E --> G[Exception?] + G -->|Yes| THROW_RUNTIME_ERROR + G -->|No| F +``` + +The loadScript function loads a script into the interpreter. It locks the mutex, stores the script in the scripts\_ map, prepares the script by processing it, and then parses any labels. If any errors occur during preparation, an exception is thrown. + +### `execute` Function + +```mermaid +flowchart TD + A[execute - scriptName] + B[Check stopRequested_ flag] + C[Set isRunning_ to true] + D[Join existing executionThread_] + E[Check if script exists] + F[Start executionThread_] + G[Execute steps sequentially] + H[Catch exceptions] + I[Set isRunning_ to false] + J[Notify all waiting threads] + + A --> B + B --> C + C --> D + D --> E + E -->|No| THROW_RUNTIME_ERROR + E -->|Yes| F + F --> G + G --> H[Exception?] + H -->|Yes| HandleException + H -->|No| I + I --> J +``` + +The execute function starts the execution of a specified script. It checks if the script exists, sets the isRunning\_ flag, and runs the script in a separate thread. If any exception occurs during execution, it is caught and handled appropriately. After the execution is complete, it notifies any waiting threads. + +### `executeStep` Function + +```mermaid +flowchart TD + A[executeStep - step, idx, script] + B[Check stopRequested_ flag] + C[Determine step type] + D[Call corresponding executeX function] + E[Return true] + F[Catch exceptions] + G[Log error and handle exception] + H[Return false] + + A --> B + B -->|Stopped| H + B -->|Not Stopped| C + C --> D + D --> E + E -->|Success| E + E -->|Failure| F + F --> G + G --> H +``` + +The executeStep function handles the execution of individual steps within a script. It checks if execution should stop, determines the step type, and calls the appropriate function to handle the step. If an exception occurs, it logs the error, handles the exception, and returns false to indicate failure. + +### `setVariable` Function + +```mermaid +flowchart TD + A[setVariable - name, value, type] + B[Lock Mutex] + C[Wait for isRunning_ to be false] + D[Determine the type of value] + E[Check if type matches] + F[Store variable in variables_] + G[Unlock Mutex] + H[Exception Handling] + + A --> B + B --> C + C --> D + D --> E[Type Mismatch?] + E -->|Yes| THROW_RUNTIME_ERROR + E -->|No| F + F --> G + G --> H[Exception?] + H -->|Yes| THROW_RUNTIME_ERROR + H -->|No| G +``` + +The setVariable function sets a variable's value and type in the interpreter. It first locks the mutex and waits until isRunning* is false. It then checks if the provided type matches the value's determined type. If the types match, it stores the variable in variables*. If any exception occurs during the process, it is thrown as a runtime error. + +### `executeCall` Function + +```mermaid +flowchart TD + A[executeCall - step] + B[Extract functionName and params] + C[Evaluate params] + D[Check if function exists] + E[Call function with params] + F[Store result in targetVariable if specified] + G[Handle exceptions] + + A --> B + B --> C + C --> D[Function exists?] + D -->|No| THROW_RUNTIME_ERROR + D -->|Yes| E + E --> F + F --> G +``` + +The executeCall function is used to execute a registered function. It extracts the function name and parameters from the step, looks up the corresponding function in the functions\_ map, and calls it. If a target variable is specified, the return value is stored in that variable. + +### `executeCondition` Function + +```mermaid +flowchart TD + A[executeCondition - step, idx, script] + B[Evaluate condition] + C[Condition is true?] + D[Execute true block] + E[Execute false block if exists] + + A --> B + B --> C + C -->|Yes| D + C -->|No| E +``` + +The executeCondition function evaluates a condition and executes the appropriate block of code. If the condition is true, the true block is executed; otherwise, the false block is executed (if it exists). + +### `executeGoto` Function + +```mermaid +flowchart TD + A[executeGoto - step, idx, script] + B[Extract label] + C[Find label in labels_ map] + D[Update idx to label position] + E[Handle label not found] + + A --> B + B --> C + C -->|Found| D + C -->|Not Found| E + E --> THROW_RUNTIME_ERROR +``` + +The executeGoto function allows jumping to a specific point in the script based on a label. It finds the label in the script and updates the execution index to that position. + +### `executeParallel` Function + +```mermaid +flowchart TD + A[executeParallel - step, idx, script] + B[Create task for each step] + C[Enqueue tasks in taskPool] + D[Wait for all tasks to complete] + E[Return to main execution flow] + + A --> B + B --> C + C --> D + D --> E +``` + +The executeParallel function executes multiple steps in parallel. It creates a task for each step, enqueues these tasks to the task pool, and waits for all tasks to complete before proceeding. + +### `executeTryCatch` Function + +```mermaid +flowchart TD + A[executeTryCatch - step, idx, script] + B[Execute try block] + C[Exception occurred?] + D[Execute catch block] + E[Continue execution] + + A --> B + B --> C + C -->|Yes| D + C -->|No| E + D --> E +``` + +The executeTryCatch function implements error handling. It executes the steps in the try block, and if an exception occurs, it handles the error by executing the steps in the catch block. + +### `executeAssign` Function + +```mermaid +flowchart TD + A[executeAssign - step] + B[Extract variable name] + C[Evaluate value] + D[Assign value to variable] + E[Update variables_ map] + + A --> B + B --> C + C --> D + D --> E +``` + +The executeAssign function assigns a value to a variable. It evaluates the value specified in the step and stores it in the designated variable. + +### `executeLoop` Function + +```mermaid +flowchart TD + A[executeLoop - step, idx, script] + B[Evaluate loop count] + C[Start loop iteration] + D[Execute steps in loop] + E[Increment iteration counter] + F[Check if loop is complete] + G[Return to main execution flow] + + A --> B + B --> C + C --> D + D --> E + E --> F + F -->|Complete| G + F -->|Not Complete| C +``` + +The executeLoop function handles looping in scripts. It repeats the execution of a block of steps for a specified number of iterations. + +### `executeSwitch` Function + +```mermaid +flowchart TD + A[executeSwitch - step, idx, script] + B[Evaluate switch variable] + C[Match variable with case blocks] + D[Execute matched case block] + E[Execute default block if no match] + + A --> B + B --> C + C -->|Matched| D + C -->|No Match| E +``` + +The executeSwitch function implements multi-branch conditional logic. It evaluates a variable and executes the corresponding case block or a default block if no cases match. + +### `handleException` Function + +```mermaid +flowchart TD + A[handleException - scriptName, e] + B[Check if exception handler exists for script] + C[Call exception handler] + D[Log unhandled exception] + + A --> B + B -->|Exists| C + B -->|Not Exists| D +``` + +The handleException function is called when an unhandled exception occurs during script execution. It checks for a registered exception handler and invokes it if available; otherwise, it logs the error. diff --git a/doc/task/solver/center.md b/doc/task/solver/center.md new file mode 100644 index 00000000..185a299f --- /dev/null +++ b/doc/task/solver/center.md @@ -0,0 +1,99 @@ +# Center + +## Overview + +The `Center` sequence item in N.I.N.A. is designed to accurately center a telescope on a specific set of celestial coordinates. This process uses plate solving, a method of determining the exact position of a telescope by analyzing an image of the night sky. The `Center` class is part of the N.I.N.A. sequencer, which allows users to automate various astrophotography tasks. + +## Class Diagram + +```mermaid +classDiagram + class Center { + - bool Inherited + - InputCoordinates Coordinates + - IList~string~ Issues + - IProfileService profileService + - ITelescopeMediator telescopeMediator + - IImagingMediator imagingMediator + - IFilterWheelMediator filterWheelMediator + - IGuiderMediator guiderMediator + - IDomeMediator domeMediator + - IDomeFollower domeFollower + - IPlateSolverFactory plateSolverFactory + - IWindowServiceFactory windowServiceFactory + + Center(profileService, telescopeMediator, imagingMediator, filterWheelMediator, guiderMediator, domeMediator, domeFollower, plateSolverFactory, windowServiceFactory) + + Task Execute(IProgress~ApplicationStatus~ progress, CancellationToken token) + + bool Validate() + + string ToString() + + object Clone() + } + Center --> IProfileService + Center --> ITelescopeMediator + Center --> IImagingMediator + Center --> IFilterWheelMediator + Center --> IGuiderMediator + Center --> IDomeMediator + Center --> IDomeFollower + Center --> IPlateSolverFactory + Center --> IWindowServiceFactory + Center --> InputCoordinates +``` + +## Key Components + +1. **Dependencies and Mediators** + + - `IProfileService`: Provides access to the user's profile settings, which are critical for the centering process. + - `ITelescopeMediator`: Manages telescope operations, including slewing to the target coordinates. + - `IImagingMediator`: Handles the camera operations needed to capture images for plate solving. + - `IFilterWheelMediator`, `IGuiderMediator`, `IDomeMediator`, `IDomeFollower`: Additional equipment mediators that may be involved depending on the setup. + - `IPlateSolverFactory`: Generates the plate solver used to determine the exact position of the telescope. + - `IWindowServiceFactory`: Manages UI windows, such as the one displaying the status of the plate-solving process. + +2. **Attributes** + + - `Coordinates`: Stores the target celestial coordinates for centering. + - `Issues`: A list of any issues or validation errors encountered during the centering process. + - `Inherited`: Indicates whether the coordinates were inherited from a parent sequence item. + +3. **Methods** + - `Execute`: The main method that performs the centering operation. It involves: + 1. Stopping guiding if it’s active. + 2. Slewing the telescope to the target coordinates. + 3. Initiating the plate-solving process. + 4. Synchronizing the dome position if necessary. + 5. Resuming guiding if it was stopped. + - `Validate`: Checks for any issues that might prevent the centering operation, such as a disconnected telescope. + - `Clone`: Creates a deep copy of the `Center` object. + - `ToString`: Provides a string representation of the sequence item, including its category and coordinates. + +## Process Flowchart + +```mermaid +flowchart TD + A[Start] --> B{Is Telescope Connected?} + B -- Yes --> C[Slew to Coordinates] + C --> D{Is Dome Following Enabled?} + D -- No --> E[Synchronize Dome with Telescope] + D -- Yes --> F[Proceed with Plate Solving] + E --> F + F --> G[Capture Image] + G --> H[Initiate Plate Solving] + H --> I{Plate Solving Success?} + I -- Yes --> J[Complete Centering Process] + I -- No --> K[Retry or Fail Sequence] + J --> L[Resume Guiding] + L --> M[End] + B -- No --> N[Error: Telescope Not Connected] + N --> M +``` + +## Execution Flow + +1. **Initialization**: The `Center` class is initialized with dependencies that manage various equipment and services required for the centering process. +2. **Validation**: Before executing the centering operation, the `Validate` method checks if the telescope is connected. If not, the process halts with an error. +3. **Slewing**: The telescope is slewed to the target coordinates provided by the user or inherited from a parent sequence item. +4. **Dome Synchronization**: If the observatory dome is not following the telescope, it is synchronized manually. +5. **Plate Solving**: The process captures an image of the sky and uses plate solving to determine the exact position of the telescope. +6. **Guiding**: If guiding was stopped earlier, it is resumed after successful centering. +7. **Completion**: The process concludes, and any issues are logged or displayed to the user. diff --git a/doc/task/solver/center_and_rotate.md b/doc/task/solver/center_and_rotate.md new file mode 100644 index 00000000..a4db5c1b --- /dev/null +++ b/doc/task/solver/center_and_rotate.md @@ -0,0 +1,78 @@ +# Center and Rotate + +This document provides an overview of the `CenterAndRotate` class from the N.I.N.A. software, which is responsible for controlling the telescope and camera to ensure accurate centering and rotation of the telescope's field of view. This functionality is crucial for astrophotography, where precise alignment and positioning are necessary. + +## Class Overview + +The `CenterAndRotate` class extends the `Center` class, adding functionality to rotate the telescope's field of view to match a desired position angle. This is particularly useful when capturing astronomical objects that require specific framing. + +### Key Components + +- **IProfileService**: Manages profile settings, such as plate solving settings and equipment configurations. +- **ITelescopeMediator**: Handles communication with the telescope, including slewing and retrieving its current position. +- **IImagingMediator**: Manages the imaging process, including camera control. +- **IRotatorMediator**: Manages the rotator, which adjusts the camera's rotation angle. +- **IFilterWheelMediator**: Controls the filter wheel for selecting the appropriate filter during imaging. +- **IGuiderMediator**: Handles the guiding process, which keeps the telescope locked on the target. +- **IDomeMediator & IDomeFollower**: Synchronizes the dome with the telescope’s movements. +- **IPlateSolverFactory**: Creates instances of plate solvers, which are used to determine the telescope's position based on star field images. +- **IWindowServiceFactory**: Provides a service to display windows for user interaction, such as progress tracking. + +### Properties + +- **PositionAngle**: The desired rotation angle of the telescope's field of view. +- **Coordinates**: The target celestial coordinates to which the telescope should point. + +### Methods + +- **Execute**: The main method that centers the telescope on the target and adjusts the field of view rotation. +- **Solve**: Performs a plate solve to determine the telescope's current position and orientation. +- **Validate**: Checks if all necessary equipment (e.g., telescope, rotator) is connected and ready. +- **AfterParentChanged**: Updates the target coordinates and position angle based on the parent sequence item. + +## Detailed Workflow with Flowchart + +Below is a detailed explanation of how the `CenterAndRotate` class operates, accompanied by a flowchart for visual reference. + +1. **Initialization** + + - The `CenterAndRotate` class is instantiated with various mediators and services that manage telescope, rotator, and other equipment. + +2. **Execution (`Execute` Method)** + + - The method begins by checking if the telescope is parked. If parked, an error is raised. + - The telescope slews to the specified coordinates. + - The dome is synchronized with the telescope if necessary. + - The `Solve` method is called to perform a plate solve and determine the telescope’s current orientation. + - The rotator is adjusted to match the desired position angle. If the difference between the current and desired angles is greater than a specified tolerance, the rotator moves accordingly. + - Once the rotation is within tolerance, the `DoCenter` method (inherited from `Center`) is called to fine-tune the telescope’s positioning. + +3. **Plate Solving (`Solve` Method)** + + - The method creates a plate solver instance using `IPlateSolverFactory`. + - A series of images are captured, and the solver compares them with star catalogs to determine the telescope’s exact position and orientation. + - The solver adjusts the telescope’s position until the target coordinates and rotation angle are achieved. + +4. **Validation (`Validate` Method)** + + - Checks if the telescope and rotator are connected. + - If any issues are detected, they are reported, and execution is halted. + +5. **Parent Change Handling (`AfterParentChanged` Method)** + - When the parent sequence item changes, the method updates the target coordinates and position angle based on the new parent’s context. + +```mermaid +flowchart TD + A[Initialize CenterAndRotate] --> B[Check Telescope Parked Status] + B -- Not Parked --> C[Slew to Target Coordinates] + C --> D[Synchronize Dome with Telescope] + D --> E[Perform Plate Solve] + E --> F[Determine Rotation Adjustment Needed] + F -- Rotation Needed --> G[Move Rotator to Target Angle] + G --> H[Is Rotation within Tolerance?] + H -- No --> F + H -- Yes --> I[Perform Final Centering DoCenter Method] + I --> J[Resume Guiding if stopped] + J --> K[Execution Complete] + B -- Parked --> L[Raise Error: Telescope is Parked] +``` diff --git a/doc/task/solver/solve_and_async.md b/doc/task/solver/solve_and_async.md new file mode 100644 index 00000000..2be9c353 --- /dev/null +++ b/doc/task/solver/solve_and_async.md @@ -0,0 +1,104 @@ +# SolveAndSync + +The `SolveAndSync` class in the N.I.N.A. application is designed to accurately synchronize the telescope's position with the actual celestial coordinates by performing a plate solving process. This class is essential for ensuring the telescope is correctly aligned with the intended target during an imaging session. + +## Class Overview + +### Namespace + +- **Namespace:** `NINA.Sequencer.SequenceItem.Platesolving` +- **Dependencies:** + - `NINA.Core.Model` + - `NINA.PlateSolving` + - `NINA.Profile.Interfaces` + - `NINA.Sequencer.Validations` + - `NINA.Equipment.Interfaces.Mediator` + - `NINA.Core.Utility.WindowService` + - `NINA.ViewModel` + - `NINA.Core.Locale` + - `NINA.Equipment.Model` + - `NINA.WPF.Base.ViewModel` + - `NINA.PlateSolving.Interfaces` + +### Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_Platesolving_SolveAndSync_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_Platesolving_SolveAndSync_Description")] +[ExportMetadata("Icon", "CrosshairSVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_Telescope")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class SolveAndSync : SequenceItem, IValidatable +``` + +### Class Properties + +- **PlateSolveStatusVM**: Manages the status of the plate-solving process, displaying progress and results. +- **Issues**: A list of issues found during the validation of the class, particularly related to equipment connectivity. + +### Constructor + +The constructor initializes the `SolveAndSync` class with dependencies on various services and mediators, including those for the telescope, rotator, imaging, filter wheel, plate solver, and window service. + +```csharp +[ImportingConstructor] +public SolveAndSync(IProfileService profileService, ...) +``` + +### Key Methods + +- **Execute(IProgress progress, CancellationToken token)**: This method executes the main logic of the `SolveAndSync` class. It performs plate solving and synchronizes the telescope’s position with the solved coordinates. +- **DoSolve(IProgress progress, CancellationToken token)**: This method performs the actual plate solving using the configured settings. +- **Validate()**: Checks if the telescope is connected and operational before attempting to solve and sync. +- **Clone()**: Creates a deep copy of the `SolveAndSync` object. + +### Flowchart: Execution Process + +Below is a flowchart that outlines the key steps in the `Execute` method of the `SolveAndSync` class. + +```mermaid +flowchart TD + A[Start Execute Method] --> B[Create Window Service] + B --> C[Show PlateSolveStatusVM Window] + C --> D[Start Plate Solving] + D --> E{Solving Successful?} + E -->|No| F[Throw Exception: Plate Solve Failed] + E -->|Yes| G[Sync Telescope with Coordinates] + G --> H[Sync Rotator with Position Angle] + H --> I{Sync Successful?} + I -->|No| J[Throw Exception: Sync Failed] + I -->|Yes| K[Proceed with Imaging Sequence] + K --> L[Close PlateSolveStatusVM Window] + L --> M[End] +``` + +### Flowchart Explanation + +1. **Create Window Service:** Initializes a window service to display the status of the plate-solving process. +2. **Show PlateSolveStatusVM Window:** Displays the `PlateSolveStatusVM` window to provide real-time feedback on the plate-solving progress. +3. **Start Plate Solving:** Begins the process of solving the telescope’s current position using the plate solver. +4. **Solving Successful?:** Checks if the plate-solving process was successful. + - **No:** Throws an exception indicating that the plate solve failed. + - **Yes:** Proceeds to sync the telescope’s coordinates. +5. **Sync Telescope with Coordinates:** Synchronizes the telescope's internal coordinates with the solved celestial coordinates. +6. **Sync Rotator with Position Angle:** Syncs the rotator's position angle with the orientation derived from the plate-solving result. +7. **Sync Successful?:** Checks if the telescope sync was successful. + - **No:** Throws an exception indicating that the sync operation failed. + - **Yes:** Proceeds with the imaging sequence. +8. **Close PlateSolveStatusVM Window:** Closes the status window after a short delay. +9. **End:** The method concludes. + +### Detailed Method Descriptions + +#### `Execute` Method + +The `Execute` method is the core of the `SolveAndSync` class, responsible for initiating and managing the entire plate-solving and synchronization process. It uses the `PlateSolveStatusVM` to provide real-time updates on the solving process and handles the synchronization of the telescope and rotator. + +#### `DoSolve` Method + +The `DoSolve` method encapsulates the logic for performing the actual plate-solving operation. It retrieves the appropriate plate solver and blind solver, configures the necessary parameters, and executes the solving sequence to determine the telescope's precise position and orientation. + +#### `Validate` Method + +The `Validate` method checks if the telescope is connected and functional before proceeding with the solve-and-sync operation. It ensures that the necessary equipment is ready, preventing runtime errors and providing feedback through the `Issues` list. diff --git a/doc/task/solver/solve_and_rotate.md b/doc/task/solver/solve_and_rotate.md new file mode 100644 index 00000000..c53d1b43 --- /dev/null +++ b/doc/task/solver/solve_and_rotate.md @@ -0,0 +1,95 @@ +# SolveAndRotate + +The `SolveAndRotate` class is part of the N.I.N.A. application, which is used for astrophotography and astronomical imaging. This class is responsible for solving the position of a telescope and rotating it to achieve a desired position angle. The class integrates various services and mediators to interact with the telescope, rotator, and other imaging equipment. + +## Class Overview + +### Namespace + +- **Namespace:** `NINA.Sequencer.SequenceItem.Platesolving` +- **Dependencies:** + - `NINA.Astrometry` + - `NINA.Core.Locale` + - `NINA.Core.Model` + - `NINA.Core.Utility` + - `NINA.Equipment.Interfaces.Mediator` + - `NINA.PlateSolving` + - `NINA.Profile.Interfaces` + - `NINA.Sequencer.Utility` + - `NINA.Sequencer.Validations` + - `NINA.WPF.Base.ViewModel` + +### Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_Platesolving_SolveAndRotate_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_Platesolving_SolveAndRotate_Description")] +[ExportMetadata("Icon", "PlatesolveAndRotateSVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_Rotator")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class SolveAndRotate : SequenceItem, IValidatable +``` + +### Class Properties + +- **PositionAngle**: This property holds the desired position angle for the telescope. It ensures the value is within a valid range (0-360 degrees). +- **Inherited**: Indicates whether the position angle is inherited from a parent sequence item. +- **Issues**: A list of validation issues that might arise during execution. +- **PlateSolveStatusVM**: Manages the status of the plate solving process. + +### Constructor + +The constructor initializes the `SolveAndRotate` class with dependencies on services and mediators for the telescope, imaging, rotator, filter wheel, guider, plate solver, and window service. + +```csharp +[ImportingConstructor] +public SolveAndRotate(IProfileService profileService, ...) +``` + +### Key Methods + +- **Execute(IProgress progress, CancellationToken token)**: This method executes the main logic of the `SolveAndRotate` class. It orchestrates the process of plate solving, rotating, and validating the rotation against the desired position angle. +- **Solve(IProgress progress, CancellationToken token)**: This method performs the plate solving using the configured settings. +- **AfterParentChanged()**: Updates the position angle and inheritance status based on the parent sequence item. +- **Validate()**: Checks if the necessary components (e.g., rotator) are connected and operational. + +### Flowchart: Execution Process + +Below is a flowchart that outlines the key steps in the `Execute` method of the `SolveAndRotate` class. + +```mermaid +flowchart TD + A[Start Execute Method] --> B[Stop Guiding] + B --> C[Initialize Rotation] + C --> D{Within Tolerance?} + D -->|Yes| E[Exit Loop] + D -->|No| F[Solve Position] + F --> G{Solve Success?} + G -->|Yes| H[Sync Rotator with Orientation] + G -->|No| I[Throw Exception] + H --> J[Calculate Rotation Distance] + J --> K[Move Rotator Relatively] + K --> D + E --> L[Resume Guiding] + L --> M[Close Service] + M --> N[End] +``` + +### Flowchart Explanation + +1. **Stop Guiding:** The guider is stopped to prepare for the rotation process. +2. **Initialize Rotation:** The initial rotation distance is set to a maximum value. +3. **Within Tolerance?:** Checks if the current rotation is within the acceptable tolerance. + - **Yes:** Exit the loop and proceed to resume guiding. + - **No:** Continue with the solving and rotation process. +4. **Solve Position:** The telescope’s current position is solved to determine the orientation. +5. **Solve Success?:** Checks if the solving process was successful. + - **Yes:** Sync the rotator with the current orientation. + - **No:** Throw an exception indicating the failure of the solving process. +6. **Calculate Rotation Distance:** The distance to the target rotation angle is calculated. +7. **Move Rotator Relatively:** The rotator is moved by the calculated rotation distance. +8. **Repeat:** The process loops until the rotation is within the desired tolerance. +9. **Resume Guiding:** Guiding is resumed after the rotation is completed. +10. **Close Service:** The window service is closed with a delay. +11. **End:** The method concludes. diff --git a/doc/task/switch/switch.md b/doc/task/switch/switch.md new file mode 100644 index 00000000..70399650 --- /dev/null +++ b/doc/task/switch/switch.md @@ -0,0 +1,97 @@ +# SetSwitchValue + +The `SetSwitchValue` class is designed to interact with switches within the N.I.N.A. (Nighttime Imaging 'N' Astronomy) application. It allows for setting values on writable switches and is used within sequence operations. + +## Class Overview + +### Namespace + +- **Namespace:** `NINA.Sequencer.SequenceItem.Switch` +- **Dependencies:** + - `NINA.Core.Model` + - `NINA.Sequencer.Validations` + - `NINA.Core.Utility` + - `NINA.Equipment.Interfaces.Mediator` + - `NINA.Core.Locale` + - `NINA.Equipment.Interfaces` + - `NINA.Equipment.Equipment.MySwitch` + +### Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_Switch_SetSwitchValue_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_Switch_SetSwitchValue_Description")] +[ExportMetadata("Icon", "ButtonSVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_Switch")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class SetSwitchValue : SequenceItem, IValidatable +``` + +### Class Properties + +- **switchMediator**: Interface for communicating with the switch hardware. +- **issues**: List of validation issues encountered. +- **Value**: The value to set on the switch. +- **SwitchIndex**: Index of the switch to be controlled. +- **SelectedSwitch**: The currently selected writable switch. +- **WritableSwitches**: List of writable switches available. + +### Constructor + +The constructor initializes the `SetSwitchValue` class with the provided `switchMediator` and sets up a dummy list of switches. + +```csharp +[ImportingConstructor] +public SetSwitchValue(ISwitchMediator switchMediator) +``` + +### Key Methods + +- **Clone()**: Creates a copy of the `SetSwitchValue` instance. +- **Execute(IProgress progress, CancellationToken token)**: Sets the switch value using `switchMediator`. +- **CreateDummyList()**: Creates a dummy list of switches for testing purposes. +- **Validate()**: Checks if the switch is connected and validates the switch value. +- **ToString()**: Provides a string representation of the `SetSwitchValue` instance. + +### Flowchart: Execution Process + +Below is a flowchart illustrating the key steps in the `Execute` method of the `SetSwitchValue` class. + +```mermaid +flowchart TD + A[Start Execute Method] --> B[Set Switch Value] + B --> C{Value Set Successfully?} + C -- No --> D[Handle Error] + C -- Yes --> E[End Execution] +``` + +### Flowchart Explanation + +1. **Set Switch Value**: Calls `switchMediator.SetSwitchValue()` to set the value on the switch. +2. **Value Set Successfully?**: Checks if the value was set successfully. If not, handles the error accordingly. +3. **End Execution**: If successful, the method completes without errors. + +### Detailed Method Descriptions + +#### `Clone` + +Creates a new instance of the `SetSwitchValue` class with the same configuration as the current instance, including the switch index and value. + +#### `Execute` + +Uses `switchMediator.SetSwitchValue()` to set the value on the switch at the specified index. + +#### `CreateDummyList` + +Generates a dummy list of switches, which is useful for scenarios where the actual switch hardware is not connected. + +#### `Validate` + +1. **Check Connection**: Verifies if the switch is connected. If not, replaces the real list with a dummy list. +2. **Update List**: If the switch is connected, updates the list of writable switches. +3. **Validation**: Ensures that the selected switch and value are valid. Adds errors to the issues list if validation fails. + +#### `ToString` + +Provides a string representation of the `SetSwitchValue` instance, including the category, item name, switch index, and value. diff --git a/doc/task/task_list.md b/doc/task/task_list.md new file mode 100644 index 00000000..fdc91695 --- /dev/null +++ b/doc/task/task_list.md @@ -0,0 +1,83 @@ +# NINA Task List + +This is the task list from NINA and we need to enhance it. + +## Camera + +### Cool Camera + + ++ Warm Camera [minimum duration] ++ Dew Heater [on/off] ++ Set Readout Mode ++ Take Exposure ++ Take Many Exposures ++ Take Subframe Exposure ++ Smart Exposure + +## Dome + ++ Close Dome Shutter ++ Enable Dome Sync ++ Disable Dome Sync ++ Open Dome Shutter ++ Park Dome ++ Slew Dome Azimuth ++ Synchronize Dome ++ Filter Wheel ++ Switch Filter ++ Flat Panel ++ Close Flat Panel Cover ++ Open Flat Panel Cover ++ Set Brightness ++ Toggle Light ++ Trained Flat Exposure ++ Trained Dark Exposure + +## Focuser + ++ Move Focuser ++ Move Focuser By Temp. ++ Move Focuser Relative ++ Run Autofocus + +## Guider + ++ Dither ++ Start Guiding ++ Stop Guiding + +## Rotator + ++ Rotate By Mechanical Angle ++ Solve and Rotate ++ Safety Monitor ++ Wait Until Safe + +## Switch + ++ Set Switch Value + +## Telescope + ++ Find Home ++ Park Scope ++ Set Tracking ++ Slew And Center ++ Slew To Alt/Az ++ Slew To Ra/Dec ++ Slew, Center And Rotate ++ Solve And Sync ++ Unpark Scope + +## Utility + ++ Annotation ++ External Script ++ Message Box ++ Wait For Altitude ++ Wait For Time ++ Wait For Time Span ++ Wait If Moon Altitude ++ Wait If Sun Altitude ++ Wait Until Above Horizon diff --git a/doc/task/telescope/find_home.md b/doc/task/telescope/find_home.md new file mode 100644 index 00000000..417f7be3 --- /dev/null +++ b/doc/task/telescope/find_home.md @@ -0,0 +1,96 @@ +# FindHome + +The `FindHome` class is used for directing a telescope to find its home position. It interacts with both the telescope and guider mediators to ensure the telescope returns to its home position while stopping any ongoing guiding operations. + +## Class Overview + +### Namespace + +- **Namespace:** `NINA.Sequencer.SequenceItem.Telescope` +- **Dependencies:** + - `NINA.Core.Model` + - `NINA.Sequencer.Validations` + - `NINA.Equipment.Interfaces.Mediator` + - `NINA.Core.Locale` + +### Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_Telescope_FindHome_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_Telescope_FindHome_Description")] +[ExportMetadata("Icon", "HomeSVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_Telescope")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class FindHome : SequenceItem, IValidatable +``` + +### Class Properties + +- **telescopeMediator**: Manages communication with the telescope hardware. +- **guiderMediator**: Manages communication with the guider hardware. +- **issues**: A list to capture any validation issues. + +### Constructor + +The constructor initializes the `FindHome` class with `telescopeMediator` and `guiderMediator`. + +```csharp +[ImportingConstructor] +public FindHome(ITelescopeMediator telescopeMediator, IGuiderMediator guiderMediator) +``` + +### Key Methods + +- **Clone()**: Creates a copy of the `FindHome` instance. +- **Execute(IProgress progress, CancellationToken token)**: Commands the telescope to find its home position while stopping guiding. +- **Validate()**: Checks if the telescope is connected and if it supports finding the home position. +- **AfterParentChanged()**: Revalidates the state when the parent changes. +- **ToString()**: Returns a string representation of the class instance. + +### Flowchart: Execution Process + +Below is a flowchart illustrating the key steps in the `Execute` method of the `FindHome` class. + +```mermaid +flowchart TD + A[Start Execute Method] --> B[Stop Guiding] + B --> C[Find Telescope Home] + C --> D[Await Home Finding Completion] + D --> E[End Execution] +``` + +### Flowchart Explanation + +1. **Stop Guiding**: Stops any ongoing guiding operations using `guiderMediator`. +2. **Find Telescope Home**: Commands the telescope to find its home position using `telescopeMediator`. +3. **Await Home Finding Completion**: Waits for the telescope to complete the home finding process. +4. **End Execution**: Marks the end of the execution process. + +### Detailed Method Descriptions + +#### `Clone` + +Creates a new instance of the `FindHome` class with the same configuration as the current instance. + +#### `Execute` + +1. **Stop Guiding**: Stops any guiding operation to avoid conflicts. +2. **Find Telescope Home**: Directs the telescope to locate its home position. This operation might involve moving the telescope to a known reference point where it can determine its home position. +3. **Await Completion**: Waits until the telescope completes the home finding process. + +#### `Validate` + +Checks the current state of the telescope: + +- If the telescope is not connected, adds an error to the issues list. +- If the telescope cannot find its home position, adds an error to the issues list. +- Updates the `Issues` property with any validation errors. + +#### `AfterParentChanged` + +Revalidates the state of the `FindHome` instance whenever its parent changes to ensure it remains valid. + +#### `ToString` + +Provides a string representation of the `FindHome` instance, including the category and item name. diff --git a/doc/task/telescope/park.md b/doc/task/telescope/park.md new file mode 100644 index 00000000..61803a97 --- /dev/null +++ b/doc/task/telescope/park.md @@ -0,0 +1,94 @@ +# ParkScope + +The `ParkScope` class is designed for parking a telescope and stopping any ongoing guiding operations. It integrates with both telescope and guider mediators to ensure the telescope is safely parked. + +## Class Overview + +### Namespace + +- **Namespace:** `NINA.Sequencer.SequenceItem.Telescope` +- **Dependencies:** + - `NINA.Core.Model` + - `NINA.Sequencer.Validations` + - `NINA.Equipment.Interfaces.Mediator` + - `NINA.Core.Locale` + +### Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_Telescope_ParkScope_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_Telescope_ParkScope_Description")] +[ExportMetadata("Icon", "ParkSVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_Telescope")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class ParkScope : SequenceItem, IValidatable +``` + +### Class Properties + +- **telescopeMediator**: Handles communication with the telescope hardware. +- **guiderMediator**: Manages communication with the guider hardware. +- **issues**: A list to record any validation issues. + +### Constructor + +The constructor initializes the `ParkScope` class with `telescopeMediator` and `guiderMediator`. + +```csharp +[ImportingConstructor] +public ParkScope(ITelescopeMediator telescopeMediator, IGuiderMediator guiderMediator) +``` + +### Key Methods + +- **Clone()**: Creates a copy of the `ParkScope` instance. +- **Execute(IProgress progress, CancellationToken token)**: Commands the telescope to park and stops guiding. +- **Validate()**: Checks if the telescope is connected. +- **AfterParentChanged()**: Revalidates the state when the parent changes. +- **ToString()**: Returns a string representation of the class instance. + +### Flowchart: Execution Process + +Below is a flowchart illustrating the key steps in the `Execute` method of the `ParkScope` class. + +```mermaid +flowchart TD + A[Start Execute Method] --> B[Stop Guiding] + B --> C[Park Telescope] + C --> D[Handle Failure] + D --> E[End Execution] +``` + +### Flowchart Explanation + +1. **Stop Guiding**: Stops any ongoing guiding operation using `guiderMediator`. +2. **Park Telescope**: Commands the telescope to park using `telescopeMediator`. If this operation fails, an exception is thrown. +3. **Handle Failure**: If the telescope fails to park, a `SequenceEntityFailedException` is thrown. +4. **End Execution**: Marks the end of the execution process. + +### Detailed Method Descriptions + +#### `Clone` + +Creates a new instance of the `ParkScope` class with the same configuration as the current instance. + +#### `Execute` + +1. **Stop Guiding**: Calls `guiderMediator.StopGuiding()` to halt any ongoing guiding operations. +2. **Park Telescope**: Uses `telescopeMediator.ParkTelescope()` to park the telescope. If the parking operation is unsuccessful, an exception is thrown. + +#### `Validate` + +Checks the current state of the telescope: + +- If the telescope is not connected, adds an error to the issues list. +- Updates the `Issues` property with any validation errors. + +#### `AfterParentChanged` + +Revalidates the state of the `ParkScope` instance whenever its parent changes to ensure it remains valid. + +#### `ToString` + +Provides a string representation of the `ParkScope` instance, including the category and item name. diff --git a/doc/task/telescope/set_tracking.md b/doc/task/telescope/set_tracking.md new file mode 100644 index 00000000..cfa96198 --- /dev/null +++ b/doc/task/telescope/set_tracking.md @@ -0,0 +1,104 @@ +# SetTracking + +The `SetTracking` class is responsible for setting the tracking mode of a telescope. It provides functionality to select and apply various tracking modes, ensuring that the telescope operates correctly according to the selected mode. + +## Class Overview + +### Namespace + +- **Namespace:** `NINA.Sequencer.SequenceItem.Telescope` +- **Dependencies:** + - `NINA.Equipment.Interfaces.Mediator` + - `NINA.Core.Model` + - `NINA.Sequencer.Validations` + - `NINA.Core.Locale` + - `NINA.Equipment.Interfaces` + +### Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_Telescope_SetTracking_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_Telescope_SetTracking_Description")] +[ExportMetadata("Icon", "SpeedometerSVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_Telescope")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class SetTracking : SequenceItem, IValidatable +``` + +### Class Properties + +- **telescopeMediator**: Interface for interacting with the telescope hardware. +- **issues**: List of validation issues. +- **trackingMode**: The current tracking mode selected for the telescope. + +### Static Properties + +- **trackingModeChoices**: A static list of available tracking modes. + +### Constructor + +The constructor initializes the `SetTracking` class with `telescopeMediator`. + +```csharp +[ImportingConstructor] +public SetTracking(ITelescopeMediator telescopeMediator) +``` + +### Key Methods + +- **Clone()**: Creates a copy of the `SetTracking` instance. +- **Execute(IProgress progress, CancellationToken token)**: Sets the tracking mode on the telescope. +- **Validate()**: Checks if the telescope is connected and if the selected tracking mode is supported. +- **AfterParentChanged()**: Revalidates the state when the parent changes. +- **ToString()**: Provides a string representation of the class instance. + +### Flowchart: Execution Process + +Below is a flowchart illustrating the key steps in the `Execute` method of the `SetTracking` class. + +```mermaid +flowchart TD + A[Start Execute Method] --> B[Set Tracking Mode] + B --> C[End Execution] +``` + +### Flowchart Explanation + +1. **Set Tracking Mode**: Uses `telescopeMediator.SetTrackingMode()` to apply the selected tracking mode. +2. **End Execution**: Marks the end of the execution process. + +### Detailed Method Descriptions + +#### `Clone` + +Creates a new instance of the `SetTracking` class with the same configuration as the current instance. + +#### `Execute` + +Sets the tracking mode on the telescope using `telescopeMediator.SetTrackingMode()`. This method completes immediately as it does not need to await any asynchronous operations. + +#### `Validate` + +Checks if the telescope is connected and if it supports the selected tracking mode: + +- Adds an error to the issues list if the telescope is not connected. +- Adds an error if the selected tracking mode is not supported by the telescope. + +#### `AfterParentChanged` + +Revalidates the state of the `SetTracking` instance whenever its parent changes to ensure it remains valid. + +#### `ToString` + +Provides a string representation of the `SetTracking` instance, including the category, item name, and the selected tracking mode. + +### Tracking Modes + +The `trackingModeChoices` static property provides a list of available tracking modes: + +- **Sidereal**: Follows the sidereal rate of the stars. +- **King**: A mode for specific astronomical calculations. +- **Solar**: Tracks the sun. +- **Lunar**: Tracks the moon. +- **Stopped**: Disables tracking. diff --git a/doc/task/telescope/slew_azalt.md b/doc/task/telescope/slew_azalt.md new file mode 100644 index 00000000..08b2c3b8 --- /dev/null +++ b/doc/task/telescope/slew_azalt.md @@ -0,0 +1,106 @@ +# SlewScopeToAltAz + +The `SlewScopeToAltAz` class is designed to move a telescope to a specific altitude and azimuth position. It handles the calculation of the telescope's new position based on input coordinates and interacts with both the telescope and guider systems. + +## Class Overview + +### Namespace + +- **Namespace:** `NINA.Sequencer.SequenceItem.Telescope` +- **Dependencies:** + - `NINA.Core.Model` + - `NINA.Profile.Interfaces` + - `NINA.Sequencer.Validations` + - `NINA.Astrometry` + - `NINA.Equipment.Interfaces.Mediator` + - `NINA.Core.Locale` + - `NINA.Core.Utility.Notification` + +### Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_Telescope_SlewScopeToAltAz_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_Telescope_SlewScopeToAltAz_Description")] +[ExportMetadata("Icon", "SlewToAltAzSVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_Telescope")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class SlewScopeToAltAz : SequenceItem, IValidatable +``` + +### Class Properties + +- **profileService**: Service for accessing and managing profile-related data. +- **telescopeMediator**: Interface for communicating with the telescope hardware. +- **guiderMediator**: Interface for managing the guiding system. +- **Coordinates**: The target altitude and azimuth coordinates for the telescope. +- **issues**: List of validation issues. + +### Constructor + +The constructor initializes the `SlewScopeToAltAz` class with the provided services and sets up an event handler for profile location changes. + +```csharp +[ImportingConstructor] +public SlewScopeToAltAz(IProfileService profileService, ITelescopeMediator telescopeMediator, IGuiderMediator guiderMediator) +``` + +### Key Methods + +- **Clone()**: Creates a copy of the `SlewScopeToAltAz` instance with the same configuration. +- **Execute(IProgress progress, CancellationToken token)**: Moves the telescope to the specified coordinates, handling any necessary guiding operations. +- **AfterParentChanged()**: Revalidates the state when the parent changes. +- **Validate()**: Checks if the telescope is connected and ready. +- **ToString()**: Provides a string representation of the class instance. + +### Flowchart: Execution Process + +Below is a flowchart illustrating the key steps in the `Execute` method of the `SlewScopeToAltAz` class. + +```mermaid +flowchart TD + A[Start Execute Method] --> B{Telescope Parked?} + B -- Yes --> C[Show Error and Abort] + B -- No --> D[Stop Guiding] + D --> E[Slew to Coordinates] + E --> F{Guiding Stopped?} + F -- Yes --> G[Start Guiding] + G --> H[End Execution] + F -- No --> H +``` + +### Flowchart Explanation + +1. **Telescope Parked?**: Checks if the telescope is parked. If it is, an error message is shown, and the operation is aborted. +2. **Stop Guiding**: Stops the guiding process to prepare for the slewing operation. +3. **Slew to Coordinates**: Moves the telescope to the specified altitude and azimuth coordinates. +4. **Guiding Stopped?**: Checks if guiding was stopped. If it was, guiding is restarted. + +### Detailed Method Descriptions + +#### `Clone` + +Creates a new instance of the `SlewScopeToAltAz` class with the same coordinates and configuration as the current instance. + +#### `Execute` + +1. **Check Telescope Status**: If the telescope is parked, an error is shown, and the method throws an exception. +2. **Stop Guiding**: Stops the guiding process to prevent conflicts during the slewing operation. +3. **Slew to Coordinates**: Uses `telescopeMediator.SlewToCoordinatesAsync()` to move the telescope to the target coordinates. +4. **Restart Guiding**: If guiding was stopped, it restarts guiding to resume normal operations. + +#### `AfterParentChanged` + +Revalidates the instance whenever its parent changes to ensure it remains in a valid state. + +#### `Validate` + +Checks if the telescope is connected. If it is not, an error is added to the issues list. + +#### `ToString` + +Provides a string representation of the `SlewScopeToAltAz` instance, including the category, item name, and the target coordinates. + +### Coordinates + +- **Coordinates**: Represents the altitude and azimuth coordinates to which the telescope should move. diff --git a/doc/task/telescope/slew_radec.md b/doc/task/telescope/slew_radec.md new file mode 100644 index 00000000..e7329411 --- /dev/null +++ b/doc/task/telescope/slew_radec.md @@ -0,0 +1,111 @@ +# SlewScopeToRaDec + +The `SlewScopeToRaDec` class is responsible for moving the telescope to a specified right ascension (RA) and declination (Dec) coordinate. It handles the slewing operation and interacts with both the telescope and guiding systems, and supports context-based coordinate retrieval from its parent container. + +## Class Overview + +### Namespace + +- **Namespace:** `NINA.Sequencer.SequenceItem.Telescope` +- **Dependencies:** + - `NINA.Core.Model` + - `NINA.Sequencer.Container` + - `NINA.Sequencer.Validations` + - `NINA.Astrometry` + - `NINA.Equipment.Interfaces.Mediator` + - `NINA.Core.Locale` + - `NINA.Core.Utility.Notification` + +### Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_Telescope_SlewScopeToRaDec_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_Telescope_SlewScopeToRaDec_Description")] +[ExportMetadata("Icon", "SlewToRaDecSVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_Telescope")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class SlewScopeToRaDec : SequenceItem, IValidatable +``` + +### Class Properties + +- **telescopeMediator**: Interface for communicating with the telescope hardware. +- **guiderMediator**: Interface for managing the guiding system. +- **inherited**: Indicates if coordinates are inherited from the parent context. +- **Coordinates**: The target right ascension and declination coordinates. +- **issues**: List of validation issues. + +### Constructor + +The constructor initializes the `SlewScopeToRaDec` class with the provided telescope and guider mediators. It also initializes the `Coordinates` property. + +```csharp +[ImportingConstructor] +public SlewScopeToRaDec(ITelescopeMediator telescopeMediator, IGuiderMediator guiderMediator) +``` + +### Key Methods + +- **Clone()**: Creates a copy of the `SlewScopeToRaDec` instance with the same coordinates and configuration. +- **Execute(IProgress progress, CancellationToken token)**: Moves the telescope to the specified RA and Dec coordinates, handling any necessary guiding operations. +- **AfterParentChanged()**: Updates the coordinates if inherited from the parent context and revalidates the state. +- **RetrieveContextCoordinates(ISequenceContainer parent)**: Recursively retrieves coordinates from the parent container if available. +- **Validate()**: Checks if the telescope is connected and ready. +- **ToString()**: Provides a string representation of the class instance. + +### Flowchart: Execution Process + +Below is a flowchart illustrating the key steps in the `Execute` method of the `SlewScopeToRaDec` class. + +```mermaid +flowchart TD + A[Start Execute Method] --> B{Telescope Parked?} + B -- Yes --> C[Show Error and Abort] + B -- No --> D[Stop Guiding] + D --> E[Slew to Coordinates] + E --> F{Guiding Stopped?} + F -- Yes --> G[Start Guiding] + G --> H[End Execution] + F -- No --> H +``` + +### Flowchart Explanation + +1. **Telescope Parked?**: Checks if the telescope is parked. If it is, an error message is shown, and the operation is aborted. +2. **Stop Guiding**: Stops the guiding process to prepare for the slewing operation. +3. **Slew to Coordinates**: Uses `telescopeMediator.SlewToCoordinatesAsync()` to move the telescope to the target RA and Dec coordinates. +4. **Guiding Stopped?**: Checks if guiding was stopped. If it was, guiding is restarted to resume normal operations. + +### Detailed Method Descriptions + +#### `Clone` + +Creates a new instance of the `SlewScopeToRaDec` class with the same coordinates and configuration as the current instance. + +#### `Execute` + +1. **Check Telescope Status**: If the telescope is parked, an error is shown, and the method throws an exception. +2. **Stop Guiding**: Stops the guiding process to avoid conflicts during the slewing operation. +3. **Slew to Coordinates**: Uses `telescopeMediator.SlewToCoordinatesAsync()` to move the telescope to the specified RA and Dec coordinates. +4. **Restart Guiding**: If guiding was stopped, it restarts guiding to ensure normal operations continue. + +#### `AfterParentChanged` + +Updates the `Coordinates` property if the coordinates are inherited from the parent container. It also sets the `Inherited` flag and revalidates the instance. + +#### `RetrieveContextCoordinates` + +Recursively searches for the coordinates in the parent container, allowing for context-based retrieval of coordinates if the current instance does not have them. + +#### `Validate` + +Checks if the telescope is connected. If it is not, an error is added to the issues list. + +#### `ToString` + +Provides a string representation of the `SlewScopeToRaDec` instance, including the category, item name, and target coordinates. + +### Coordinates + +- **Coordinates**: Represents the right ascension and declination coordinates to which the telescope should move. diff --git a/doc/task/telescope/unpark.md b/doc/task/telescope/unpark.md new file mode 100644 index 00000000..3df39cbd --- /dev/null +++ b/doc/task/telescope/unpark.md @@ -0,0 +1,89 @@ +# UnparkScope + +The `UnparkScope` class is designed to handle the process of unparking the telescope. This class interacts with the telescope system to bring the telescope out of the parked state and is used in sequence operations within the N.I.N.A. (Nighttime Imaging 'N' Astronomy) application. + +## Class Overview + +### Namespace + +- **Namespace:** `NINA.Sequencer.SequenceItem.Telescope` +- **Dependencies:** + - `NINA.Core.Model` + - `NINA.Sequencer.Validations` + - `NINA.Equipment.Interfaces.Mediator` + - `NINA.Core.Locale` + +### Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_Telescope_UnparkScope_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_Telescope_UnparkScope_Description")] +[ExportMetadata("Icon", "UnparkSVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_Telescope")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class UnparkScope : SequenceItem, IValidatable +``` + +### Class Properties + +- **telescopeMediator**: Interface for communicating with the telescope hardware. +- **issues**: List of validation issues encountered. + +### Constructor + +The constructor initializes the `UnparkScope` class with the provided `telescopeMediator`. + +```csharp +[ImportingConstructor] +public UnparkScope(ITelescopeMediator telescopeMediator) +``` + +### Key Methods + +- **Clone()**: Creates a copy of the `UnparkScope` instance. +- **Execute(IProgress progress, CancellationToken token)**: Unparks the telescope using `telescopeMediator`. +- **Validate()**: Checks if the telescope is connected and sets any validation issues. +- **AfterParentChanged()**: Revalidates the state when the parent changes. +- **ToString()**: Provides a string representation of the `UnparkScope` instance. + +### Flowchart: Execution Process + +Below is a flowchart illustrating the key steps in the `Execute` method of the `UnparkScope` class. + +```mermaid +flowchart TD + A[Start Execute Method] --> B[Unpark Telescope] + B --> C{Unparking Successful?} + C -- No --> D[Throw Exception] + C -- Yes --> E[End Execution] +``` + +### Flowchart Explanation + +1. **Unpark Telescope**: Calls `telescopeMediator.UnparkTelescope()` to unpark the telescope. +2. **Unparking Successful?**: Checks if the unparking was successful. If not, an exception is thrown. +3. **End Execution**: If successful, the method completes without errors. + +### Detailed Method Descriptions + +#### `Clone` + +Creates a new instance of the `UnparkScope` class with the same configuration as the current instance. + +#### `Execute` + +1. **Unpark Telescope**: Uses `telescopeMediator.UnparkTelescope()` to unpark the telescope. +2. **Check Success**: If the unparking operation fails, an exception (`SequenceEntityFailedException`) is thrown. + +#### `Validate` + +Checks if the telescope is connected by querying `telescopeMediator.GetInfo()`. If not connected, adds an error message to the issues list. + +#### `AfterParentChanged` + +Revalidates the `UnparkScope` instance when its parent changes, ensuring that the state is still valid. + +#### `ToString` + +Provides a string representation of the `UnparkScope` instance, including the category and item name. diff --git a/doc/task/utility/external_script.md b/doc/task/utility/external_script.md new file mode 100644 index 00000000..cec9938e --- /dev/null +++ b/doc/task/utility/external_script.md @@ -0,0 +1,148 @@ +# ExternalScrip + +The `ExternalScript` class is part of the `NINA.Sequencer.SequenceItem.Utility` namespace and is designed to execute an external script or command as part of a sequence item. + +## Namespace + +```csharp +namespace NINA.Sequencer.SequenceItem.Utility +``` + +## Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_Utility_ExternalScript_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_Utility_ExternalScript_Description")] +[ExportMetadata("Icon", "ScriptSVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_Utility")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class ExternalScript : SequenceItem, IValidatable +``` + +## Properties + +### `OpenDialogCommand` + +- **Type:** `System.Windows.Input.ICommand` +- **Description:** Command to open a file dialog for selecting the script to execute. + +### `Issues` + +- **Type:** `IList` +- **Description:** List of validation issues. +- **Json Property:** `[JsonProperty]` + +### `Script` + +- **Type:** `string` +- **Description:** Path to the external script or command to be executed. +- **Json Property:** `[JsonProperty]` + +## Constructors + +### Default Constructor + +```csharp +public ExternalScript() +``` + +- **Description:** Initializes the command to open a file dialog for selecting the script. + +### Clone Constructor + +```csharp +private ExternalScript(ExternalScript cloneMe) : this() +``` + +- **Parameters:** + - `cloneMe`: The instance to clone. + +## Methods + +### `Clone` + +```csharp +public override object Clone() +``` + +- **Description:** Creates a deep copy of the current `ExternalScript` instance. +- **Flowchart:** + +```mermaid +graph TD; + A[Clone Method] --> B[Create New ExternalScript] + B --> C[Copy Properties] + C --> D[Return New Instance] + D --> E[End] +``` + +### `Execute` + +```csharp +public override async Task Execute(IProgress progress, CancellationToken token) +``` + +- **Description:** Executes the external script or command and reports progress. +- **Flowchart:** + +```mermaid +graph TD; + A[Execute Method] --> B[Prepare External Command] + B --> C[Run External Command] + C --> D[Check Success] + D -- Success --> E[Complete] + D -- Failure --> F[Throw Exception] + E --> G[End] + F --> G +``` + +### `Validate` + +```csharp +public bool Validate() +``` + +- **Description:** Validates if the script or command exists and is executable. +- **Flowchart:** + +```mermaid +graph TD; + A[Validate Method] --> B[Check Command Exists] + B -- Exists --> C[Return True] + B -- Does Not Exist --> D[Add Error Message] + D --> E[Return False] + C --> E + E --> F[End] +``` + +### `AfterParentChanged` + +```csharp +public override void AfterParentChanged() +``` + +- **Description:** Validates the script after a parent change event. +- **Flowchart:** + +```mermaid +graph TD; + A[AfterParentChanged Method] --> B[Validate Script] + B --> C[End] +``` + +### `ToString` + +```csharp +public override string ToString() +``` + +- **Description:** Returns a string representation of the `ExternalScript` instance. +- **Flowchart:** + +```mermaid +graph TD; + A[ToString Method] --> B[Format String] + B --> C[Return String] + C --> D[End] +``` diff --git a/doc/task/utility/wait_altitude.md b/doc/task/utility/wait_altitude.md new file mode 100644 index 00000000..bba5b94b --- /dev/null +++ b/doc/task/utility/wait_altitude.md @@ -0,0 +1,166 @@ +# `WaitForAltitude` Class + +The `WaitForAltitude` class is part of the `NINA.Sequencer.SequenceItem.Utility` namespace and is used to wait until an astronomical object reaches a specified altitude. + +## Namespace + +```csharp +namespace NINA.Sequencer.SequenceItem.Utility +``` + +## Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_Utility_WaitForAltitude_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_Utility_WaitForAltitude_Description")] +[ExportMetadata("Icon", "WaitForAltitudeSVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_Utility")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class WaitForAltitude : WaitForAltitudeBase, IValidatable +``` + +## Properties + +### `AboveOrBelow` + +- **Type:** `string` +- **Description:** Defines whether the target altitude should be above (`>`) or below (`<`) a certain value. +- **Json Property:** `[JsonProperty]` + +### `HasDsoParent` + +- **Type:** `bool` +- **Description:** Indicates if this item has a Deep-Sky Object (DSO) parent. +- **Json Property:** `[JsonProperty]` + +## Constructors + +### Default Constructor + +```csharp +[ImportingConstructor] +public WaitForAltitude(IProfileService profileService) : base(profileService, useCustomHorizon: false) +``` + +- **Parameters:** + - `profileService`: Service providing profile information. + +### Clone Constructor + +```csharp +private WaitForAltitude(WaitForAltitude cloneMe) : this(cloneMe.ProfileService) +``` + +- **Parameters:** + - `cloneMe`: The instance to clone. + +## Methods + +### `Clone` + +```csharp +public override object Clone() +``` + +- **Description:** Creates a deep copy of the current `WaitForAltitude` instance. +- **Flowchart:** + +```mermaid +graph TD; + A[Clone Method] --> B[Create New WaitForAltitude] + B --> C[Copy Properties] + C --> D[Return New Instance] + D --> E[End] +``` + +### `Execute` + +```csharp +public override async Task Execute(IProgress progress, CancellationToken token) +``` + +- **Description:** Continuously checks if the current altitude is above or below the target altitude and waits for the specified interval if not. +- **Flowchart:** + +```mermaid +graph TD; + A[Execute Method] --> B[Transform Coordinates to AltAz] + B --> C[Check If Altitude Meets Criteria] + C -- Yes --> D[Break Loop] + C -- No --> E[Delay for 1 Second] + E --> B + D --> F[End] +``` + +### `GetCurrentAltitude` + +```csharp +public double GetCurrentAltitude(DateTime time, ObserverInfo observer) +``` + +- **Description:** Calculates the current altitude for a given time and observer. +- **Flowchart:** + +```mermaid +graph TD; + A[GetCurrentAltitude Method] --> B[Transform Coordinates to AltAz] + B --> C[Return Altitude] + C --> D[End] +``` + +### `CalculateExpectedTime` + +```csharp +public override void CalculateExpectedTime() +``` + +- **Description:** Calculates the expected time for the object to reach the target altitude. +- **Flowchart:** + +```mermaid +graph TD; + A[CalculateExpectedTime Method] --> B[Get Current Altitude] + B --> C[Calculate Expected Time Common] + C --> D[End] +``` + +### `AfterParentChanged` + +```csharp +public override void AfterParentChanged() +``` + +- **Description:** Updates coordinates based on the parent context and validates the data. +- **Flowchart:** + +```mermaid +graph TD; + A[AfterParentChanged Method] --> B[Retrieve Context Coordinates] + B --> C[Update Coordinates If Available] + C --> D[Set HasDsoParent] + D --> E[Validate Data] + E --> F[End] +``` + +### `Validate` + +```csharp +public bool Validate() +``` + +- **Description:** Validates if the target altitude can be reached based on the current conditions. +- **Flowchart:** + +```mermaid +graph TD; + A[Validate Method] --> B[Calculate Max Altitude] + B --> C[Calculate Min Altitude] + C --> D[Check Altitude Validity] + D -- Invalid --> E[Add Error Message] + D -- Valid --> F[Calculate Expected Time] + E --> G[Return False] + F --> H[Return True] + G --> H + H --> I[End] +``` diff --git a/doc/task/utility/wait_altitude_base.md b/doc/task/utility/wait_altitude_base.md new file mode 100644 index 00000000..75ce731b --- /dev/null +++ b/doc/task/utility/wait_altitude_base.md @@ -0,0 +1,116 @@ +# WaitForAltitudeBase + +The `WaitForAltitudeBase` class is an abstract class used in the N.I.N.A. (Nighttime Imaging 'N' Astronomy) application to handle operations related to waiting for a celestial body to reach a specified altitude. It includes properties and methods to manage altitude conditions and backward compatibility for deprecated fields. + +## Namespace + +```csharp +namespace NINA.Sequencer.SequenceItem.Utility +``` + +## Class Declaration + +```csharp +public abstract class WaitForAltitudeBase : SequenceItem +``` + +## Properties + +### `ProfileService` + +- **Type:** `IProfileService` +- **Description:** Provides access to profile data used in altitude calculations and waiting processes. + +### `Data` + +- **Type:** `WaitLoopData` +- **Description:** Contains configuration and data for the waiting loop, including altitude settings and comparison operators. + +### `Issues` + +- **Type:** `IList` +- **Description:** A list of issues encountered during the waiting process. + +## Constructor + +```csharp +public WaitForAltitudeBase(IProfileService profileService, bool useCustomHorizon) +``` + +- **Parameters:** + - `profileService`: Service for accessing profile data. + - `useCustomHorizon`: Boolean indicating whether to use custom horizon data. + +Initializes the `ProfileService` and `Data` properties. + +## Methods + +### `CalculateExpectedTime()` + +- **Description:** Abstract method that must be implemented by derived classes to calculate the expected time for the altitude condition to be met. + +## Obsolete Migration Properties + +These properties are retained for backward compatibility and are mapped to the `Data` property in the `WaitLoopData` class. + +### Deprecated Properties Flowchart + +```mermaid +graph TD; + A[Deprecated Properties] --> B[Comparator] + A --> C[UserMoonAltitude] + A --> D[UserSunAltitude] + A --> E[AltitudeOffset] + A --> F[Altitude] + A --> G[Coordinates] + + B --> H[DeprecatedComparator] + C --> I[DeprecatedUserMoonAltitude] + D --> J[DeprecatedUserSunAltitude] + E --> K[DeprecatedAltitudeOffset] + F --> L[DeprecatedAltitude] + G --> M[DeprecatedCoordinates] + + H --> N[Data.Comparator] + I --> O[Data.Offset] + J --> P[Data.Offset] + K --> Q[Data.Offset] + L --> R[Data.Offset] + M --> S[Data.Coordinates] +``` + +### `Comparator` + +- **Deprecated Name:** `DeprecatedComparator` +- **Type:** `ComparisonOperatorEnum` +- **Description:** Specifies the comparison operator for altitude conditions. The value is converted to the current enum format. + +### `UserMoonAltitude` + +- **Deprecated Name:** `DeprecatedUserMoonAltitude` +- **Type:** `double` +- **Description:** Altitude offset for the moon. + +### `UserSunAltitude` + +- **Deprecated Name:** `DeprecatedUserSunAltitude` +- **Type:** `double` +- **Description:** Altitude offset for the sun. + +### `AltitudeOffset` + +- **Deprecated Name:** `DeprecatedAltitudeOffset` +- **Type:** `double` +- **Description:** General altitude offset. + +### `Altitude` + +- **Deprecated Name:** `DeprecatedAltitude` +- **Type:** `double` +- **Description:** Specific altitude value. + +### `Coordinates` + +- **Deprecated Name:** `DeprecatedCoordinates` +- **Type:** `InputCoordinates` +- **Description:** Coordinates used for the altitude calculation. diff --git a/doc/task/utility/wait_horizon.md b/doc/task/utility/wait_horizon.md new file mode 100644 index 00000000..f095f3c3 --- /dev/null +++ b/doc/task/utility/wait_horizon.md @@ -0,0 +1,167 @@ +# WaitUntilAboveHorizon + +The `WaitUntilAboveHorizon` class is part of the `NINA.Sequencer.SequenceItem.Utility` namespace and is used to wait until an astronomical object is above a specified horizon level. + +## Namespace + +```csharp +namespace NINA.Sequencer.SequenceItem.Utility +``` + +## Class Declaration + +```csharp +[ExportMetadata("Name", "Lbl_SequenceItem_Utility_WaitUntilAboveHorizon_Name")] +[ExportMetadata("Description", "Lbl_SequenceItem_Utility_WaitUntilAboveHorizon_Description")] +[ExportMetadata("Icon", "WaitForAltitudeSVG")] +[ExportMetadata("Category", "Lbl_SequenceCategory_Utility")] +[Export(typeof(ISequenceItem))] +[JsonObject(MemberSerialization.OptIn)] +public class WaitUntilAboveHorizon : WaitForAltitudeBase, IValidatable +``` + +## Properties + +### `HasDsoParent` + +- **Type:** `bool` +- **Description:** Indicates if this item has a Deep-Sky Object (DSO) parent. +- **Json Property:** `[JsonProperty]` + +### `UpdateInterval` + +- **Type:** `int` +- **Description:** The interval, in seconds, for checking the altitude. +- **Default Value:** `1` + +## Constructors + +### Default Constructor + +```csharp +[ImportingConstructor] +public WaitUntilAboveHorizon(IProfileService profileService) : base(profileService, useCustomHorizon: true) +``` + +- **Parameters:** + - `profileService`: Service providing profile information. + +### Clone Constructor + +```csharp +private WaitUntilAboveHorizon(WaitUntilAboveHorizon cloneMe) : this(cloneMe.ProfileService) +``` + +- **Parameters:** + - `cloneMe`: The instance to clone. + +## Methods + +### `Clone` + +```csharp +public override object Clone() +``` + +- **Description:** Creates a deep copy of the current `WaitUntilAboveHorizon` instance. +- **Flowchart:** + +```mermaid +graph TD; + A[Clone Method] --> B[Create New WaitUntilAboveHorizon] + B --> C[Copy Properties] + C --> D[Return New Instance] + D --> E[End] +``` + +### `Execute` + +```csharp +public override async Task Execute(IProgress progress, CancellationToken token) +``` + +- **Description:** Continuously checks if the current altitude is above the target altitude and waits for the specified interval if not. +- **Flowchart:** + +```mermaid +graph TD; + A[Execute Method] --> B[Set Target Altitude with Horizon] + B --> C[Transform Coordinates to AltAz] + C --> D[Check If Current Altitude > Target Altitude] + D -- Yes --> E[Log Completion] + D -- No --> F[Delay for Update Interval] + F --> B + E --> G[End] +``` + +### `GetCurrentAltitude` + +```csharp +public double GetCurrentAltitude(DateTime time, ObserverInfo observer) +``` + +- **Description:** Calculates the current altitude for a given time and observer. +- **Flowchart:** + +```mermaid +graph TD; + A[GetCurrentAltitude Method] --> B[Transform Coordinates to AltAz] + B --> C[Return Altitude] + C --> D[End] +``` + +### `CalculateExpectedTime` + +```csharp +public override void CalculateExpectedTime() +``` + +- **Description:** Calculates the expected time for the object to reach the target altitude. +- **Flowchart:** + +```mermaid +graph TD; + A[CalculateExpectedTime Method] --> B[Get Current Altitude] + B --> C[Calculate Expected Time Common] + C --> D[End] +``` + +### `AfterParentChanged` + +```csharp +public override void AfterParentChanged() +``` + +- **Description:** Updates coordinates based on the parent context and validates the data. +- **Flowchart:** + +```mermaid +graph TD; + A[AfterParentChanged Method] --> B[Retrieve Context Coordinates] + B --> C[Update Coordinates If Available] + C --> D[Set HasDsoParent] + D --> E[Validate Data] + E --> F[End] +``` + +### `Validate` + +```csharp +public bool Validate() +``` + +- **Description:** Validates if the target altitude can be reached based on the current horizon and coordinates. +- **Flowchart:** + +```mermaid +graph TD; + A[Validate Method] --> B[Calculate Max Altitude] + B --> C[Calculate Minimum Horizon Altitude] + C --> D[Check If Max Altitude < Min Horizon Altitude] + D -- Yes --> E[Add Error Message] + D -- No --> F[Calculate Expected Time] + E --> G[Return False] + F --> H[Return True] + G --> H + H --> I[End] +``` diff --git a/doc/task/utility/wait_loop.md b/doc/task/utility/wait_loop.md new file mode 100644 index 00000000..47241fed --- /dev/null +++ b/doc/task/utility/wait_loop.md @@ -0,0 +1,205 @@ +# WaitLoopData + +The `WaitLoopData` class is part of the `NINA.Sequencer.SequenceItem.Utility` namespace and is used to manage and calculate waiting times based on astronomical parameters such as altitude and horizon conditions. + +## Namespace + +```csharp +namespace NINA.Sequencer.SequenceItem.Utility +``` + +## Class Declaration + +```csharp +[JsonObject(MemberSerialization.OptIn)] +public class WaitLoopData : BaseINPC +``` + +## Properties + +### `Coordinates` + +- **Type:** `InputCoordinates` +- **Description:** Represents the input coordinates for the astronomical calculations. +- **Json Property:** `[JsonProperty]` + +### `Offset` + +- **Type:** `double` +- **Description:** The user input for the desired result. For horizons, it is `[current horizon] + offset => target horizon`. For altitudes, it is `[0] + offset => target altitude`. +- **Json Property:** `[JsonProperty]` + +### `Comparator` + +- **Type:** `ComparisonOperatorEnum` +- **Description:** The comparison operator used for determining if the conditions are met. +- **Json Property:** `[JsonProperty]` + +### `UseCustomHorizon` + +- **Type:** `bool` +- **Description:** Indicates if a custom horizon is used. + +### `Name` + +- **Type:** `string` +- **Description:** The name associated with this `WaitLoopData` instance. + +### `Latitude`, `Longitude`, `Elevation` + +- **Type:** `double` +- **Description:** The latitude, longitude, and elevation of the observer. + +### `Horizon` + +- **Type:** `CustomHorizon` +- **Description:** Represents the custom horizon settings. + +### `Observer` + +- **Type:** `ObserverInfo` +- **Description:** Information about the observer. + +### `TargetAltitude` + +- **Type:** `double` +- **Description:** The target altitude to reach. + +### `RisingSettingDisplay` + +- **Type:** `string` +- **Description:** Displays whether the object is rising or setting. + +### `IsRising` + +- **Type:** `bool` +- **Description:** Indicates if the object is currently rising. + +### `CurrentAltitude` + +- **Type:** `double` +- **Description:** The current altitude of the object. + +### `Approximate` + +- **Type:** `string` +- **Description:** Indicates if the altitude is approximate. + +### `ExpectedDateTime` + +- **Type:** `DateTime` +- **Description:** The expected date and time for reaching the target altitude. + +### `ExpectedTime` + +- **Type:** `string` +- **Description:** The formatted string representing the expected time. + +## Constructors + +### Default Constructor + +```csharp +public WaitLoopData(IProfileService profileService, bool useCustomHorizon, Action calculateExpectedTime, string name) +``` + +- **Parameters:** + - `profileService`: Service providing profile information. + - `useCustomHorizon`: Indicates if a custom horizon is used. + - `calculateExpectedTime`: Action to calculate expected time. + - `name`: The name associated with this instance. + +### Clone Constructor + +```csharp +private WaitLoopData(WaitLoopData cloneMe) : this(cloneMe.profileService, cloneMe.UseCustomHorizon, cloneMe.calculateExpectedTime, cloneMe.Name) +``` + +- **Parameters:** + - `cloneMe`: The instance to clone. + +## Methods + +### `Clone` + +```csharp +public WaitLoopData Clone() +``` + +- **Description:** Creates a deep copy of the current `WaitLoopData` instance. +- **Flowchart:** + +```mermaid +graph TD; + A[Clone Method] --> B[Create New WaitLoopData] + B --> C[Copy Properties] + C --> D[Return New Instance] + D --> E[End] +``` + +### `SetCoordinates` + +```csharp +public void SetCoordinates(InputCoordinates coordinates) +``` + +- **Description:** Sets new coordinates and resets the expected time. +- **Flowchart:** + +```mermaid +graph TD; + A[SetCoordinates Method] --> B[Check If Coordinates Changed] + B --> C[Update Coordinates] + C --> D[Reset Expected DateTime] + D --> E[End] +``` + +### `SetApproximate` + +```csharp +public void SetApproximate(bool isApproximate) +``` + +- **Description:** Sets the approximate display value. +- **Flowchart:** + +```mermaid +graph TD; + A[SetApproximate Method] --> B[Check If Approximate] + B --> C[Update Approximate Property] + C --> D[End] +``` + +### `SetTargetAltitudeWithHorizon` + +```csharp +public void SetTargetAltitudeWithHorizon(DateTime when) +``` + +- **Description:** Sets the target altitude based on the horizon for a specified date and time. +- **Flowchart:** + +```mermaid +graph TD; + A[SetTargetAltitudeWithHorizon Method] --> B[Calculate Target Altitude] + B --> C[Update Target Altitude] + C --> D[End] +``` + +### `GetTargetAltitudeWithHorizon` + +```csharp +public double GetTargetAltitudeWithHorizon(DateTime when) +``` + +- **Description:** Calculates the target altitude based on the horizon for a specified date and time. +- **Flowchart:** + +```mermaid +graph TD; + A[GetTargetAltitudeWithHorizon Method] --> B[Transform Coordinates] + B --> C[Get Horizon Altitude] + C --> D[Calculate Target Altitude] + D --> E[Return Target Altitude] + E --> F[End] +``` diff --git a/doc/task/utility/wait_moon.md b/doc/task/utility/wait_moon.md new file mode 100644 index 00000000..83836873 --- /dev/null +++ b/doc/task/utility/wait_moon.md @@ -0,0 +1,118 @@ +# WaitForMoonAltitude + +The `WaitForMoonAltitude` class is a specific implementation of the `WaitForAltitudeBase` class used in the N.I.N.A. (Nighttime Imaging 'N' Astronomy) application. It manages waiting for the moon to reach a specified altitude based on the observer's location. + +## Namespace + +```csharp +namespace NINA.Sequencer.SequenceItem.Utility +``` + +## Class Declaration + +```csharp +public class WaitForMoonAltitude : WaitForAltitudeBase, IValidatable +``` + +## Properties + +### `ProfileService` + +- **Type:** `IProfileService` +- **Description:** Provides access to profile data used for altitude calculations and waiting conditions. + +### `Data` + +- **Type:** `WaitLoopData` +- **Description:** Holds configuration and data related to the waiting loop, including altitude settings and comparison operators. + +## Constructor + +```csharp +[ImportingConstructor] +public WaitForMoonAltitude(IProfileService profileService) : base(profileService, useCustomHorizon: false) +``` + +- **Parameters:** + - `profileService`: Service for accessing profile data. + +Initializes the `ProfileService` and `Data` properties. Sets the initial `Data.Offset` to `0d`. + +## Methods + +### `Execute` + +```csharp +public override async Task Execute(IProgress progress, CancellationToken token) +``` + +- **Description:** Executes the waiting process until the moon reaches the target altitude. Reports progress and handles cancellation. + +- **Flowchart:** + +```mermaid +graph TD; + A[Execute Method] --> B[CalculateExpectedTime] + B --> C[MustWait] + C -->|True| D[Report Progress] + D --> E[Delay] + E --> B + C -->|False| F[Exit Loop] + F --> G[End] +``` + +### `MustWait` + +```csharp +private bool MustWait() +``` + +- **Description:** Determines if the process should continue waiting based on the current altitude and comparator. + +- **Flowchart:** + +```mermaid +graph TD; + A[MustWait Method] --> B[CalculateExpectedTime] + B --> C[Check Comparator] + C -->|GREATER_THAN| D[CurrentAltitude > Offset] + C -->|Others| E[CurrentAltitude <= Offset] + D --> F[Return True] + E --> F + F --> G[End] +``` + +### `CalculateExpectedTime` + +```csharp +public override void CalculateExpectedTime() +``` + +- **Description:** Calculates the expected time for the moon to reach the target altitude. Updates `Data` with current moon altitude and coordinates. + +- **Flowchart:** + +```mermaid +graph TD; + A[CalculateExpectedTime Method] --> B[CalculateMoonRADec] + B --> C[GetMoonAltitude] + C --> D[CalculateExpectedTimeCommon] + D --> E[Update Data] + E --> F[End] +``` + +### `ToString` + +```csharp +public override string ToString() +``` + +- **Description:** Returns a string representation of the current state, including category, item name, target altitude, comparator, and current altitude. + +## Validate Method + +```csharp +public bool Validate() +``` + +- **Description:** Validates the current state of the instance. Calls `CalculateExpectedTime` and returns true. diff --git a/doc/task/utility/wait_sun.md b/doc/task/utility/wait_sun.md new file mode 100644 index 00000000..87bd8f44 --- /dev/null +++ b/doc/task/utility/wait_sun.md @@ -0,0 +1,117 @@ +# WaitForSunAltitude + +The `WaitForSunAltitude` class extends the `WaitForAltitudeBase` class and implements the `IValidatable` interface. It is used for waiting until the sun reaches a specified altitude, based on the observer's location. + +## Namespace + +```csharp +namespace NINA.Sequencer.SequenceItem.Utility +``` + +## Class Declaration + +```csharp +public class WaitForSunAltitude : WaitForAltitudeBase, IValidatable +``` + +## Properties + +### `ProfileService` + +- **Type:** `IProfileService` +- **Description:** Provides access to profile data used for altitude calculations and waiting conditions. + +### `Data` + +- **Type:** `WaitLoopData` +- **Description:** Holds configuration and data related to the waiting loop, including altitude settings and comparison operators. + +## Constructor + +```csharp +[ImportingConstructor] +public WaitForSunAltitude(IProfileService profileService) : base(profileService, useCustomHorizon: false) +``` + +- **Parameters:** + - `profileService`: Service for accessing profile data. + +Initializes the `ProfileService` and `Data` properties. + +## Methods + +### `Execute` + +```csharp +public override async Task Execute(IProgress progress, CancellationToken token) +``` + +- **Description:** Executes the waiting process until the sun reaches the target altitude. Reports progress and handles cancellation. + +- **Flowchart:** + +```mermaid +graph TD; + A[Execute Method] --> B[CalculateExpectedTime] + B --> C[MustWait] + C -->|True| D[Report Progress] + D --> E[Delay] + E --> B + C -->|False| F[Exit Loop] + F --> G[End] +``` + +### `MustWait` + +```csharp +private bool MustWait() +``` + +- **Description:** Determines if the process should continue waiting based on the current sun altitude and comparator. + +- **Flowchart:** + +```mermaid +graph TD; + A[MustWait Method] --> B[Check Comparator] + B -->|GREATER_THAN| C[CurrentAltitude > Offset] + B -->|Others| D[CurrentAltitude <= Offset] + C --> E[Return True] + D --> E + E --> F[End] +``` + +### `CalculateExpectedTime` + +```csharp +public override void CalculateExpectedTime() +``` + +- **Description:** Calculates the expected time for the sun to reach the target altitude. Updates `Data` with current sun altitude and coordinates. + +- **Flowchart:** + +```mermaid +graph TD; + A[CalculateExpectedTime Method] --> B[CalculateSunRADec] + B --> C[GetSunAltitude] + C --> D[CalculateExpectedTimeCommon] + D --> E[Update Data] + E --> F[End] +``` + +### `ToString` + +```csharp +public override string ToString() +``` + +- **Description:** Returns a string representation of the current state, including category, item name, target altitude, comparator, and current sun altitude. + +## Validate Method + +```csharp +public bool Validate() +``` + +- **Description:** Validates the current state of the instance. Calls `CalculateExpectedTime` and returns true. diff --git a/doc/task/utility/wait_time.md b/doc/task/utility/wait_time.md new file mode 100644 index 00000000..ece11b35 --- /dev/null +++ b/doc/task/utility/wait_time.md @@ -0,0 +1,197 @@ +# WaitForTime + +The `WaitForTime` class extends `SequenceItem` and implements the `IValidatable` interface. It is designed to wait until a specified time, considering optional time offsets and different date-time providers. + +## Namespace + +```csharp +namespace NINA.Sequencer.SequenceItem.Utility +``` + +## Class Declaration + +```csharp +public class WaitForTime : SequenceItem, IValidatable +``` + +## Properties + +### `DateTimeProviders` + +- **Type:** `IList` +- **Description:** List of available date-time providers. + +### `SelectedProvider` + +- **Type:** `IDateTimeProvider` +- **Description:** The currently selected date-time provider. + +### `Hours` + +- **Type:** `int` +- **Description:** The target hour for the wait time. + +### `Minutes` + +- **Type:** `int` +- **Description:** The target minute for the wait time. + +### `MinutesOffset` + +- **Type:** `int` +- **Description:** Offset in minutes to adjust the target time. + +### `Seconds` + +- **Type:** `int` +- **Description:** The target seconds for the wait time. + +### `Issues` + +- **Type:** `IList` +- **Description:** List of validation issues. + +## Constructor + +### Default Constructor + +```csharp +[ImportingConstructor] +public WaitForTime(IList dateTimeProviders) +``` + +- **Parameters:** + - `dateTimeProviders`: List of available date-time providers. + +### Overloaded Constructor + +```csharp +public WaitForTime(IList dateTimeProviders, IDateTimeProvider selectedProvider) +``` + +- **Parameters:** + - `dateTimeProviders`: List of available date-time providers. + - `selectedProvider`: The selected date-time provider. + +### Clone Constructor + +```csharp +private WaitForTime(WaitForTime cloneMe) : this(cloneMe.DateTimeProviders, cloneMe.SelectedProvider) +``` + +- **Parameters:** + - `cloneMe`: The instance to clone. + +## Methods + +### `Clone` + +```csharp +public override object Clone() +``` + +- **Description:** Creates a deep copy of the current `WaitForTime` instance. + +- **Flowchart:** + +```mermaid +graph TD; + A[Clone Method] --> B[Create New WaitForTime] + B --> C[Copy Properties] + C --> D[Return New Instance] + D --> E[End] +``` + +### `Validate` + +```csharp +public bool Validate() +``` + +- **Description:** Validates the current state. Checks if time determination was successful and updates issues list. + +- **Flowchart:** + +```mermaid +graph TD; + A[Validate Method] --> B[Check Time Provider] + B --> C[Update Time if Fixed Provider] + C --> D[Check Time Determination] + D --> E[Add Validation Issues if Any] + E --> F[Return Validation Result] + F --> G[End] +``` + +### `UpdateTime` + +```csharp +private void UpdateTime() +``` + +- **Description:** Updates the target time based on the selected provider and offset. + +- **Flowchart:** + +```mermaid +graph TD; + A[UpdateTime Method] --> B[Get Reference Date] + B --> C[Check Fixed Time Provider] + C --> D[Calculate New Time] + D --> E[Set Hours, Minutes, Seconds] + E --> F[Update Success Status] + F --> G[End] +``` + +### `AfterParentChanged` + +```csharp +public override void AfterParentChanged() +``` + +- **Description:** Updates the time when the parent changes. + +### `Execute` + +```csharp +public override Task Execute(IProgress progress, CancellationToken token) +``` + +- **Description:** Executes the waiting task until the specified time is reached. + +- **Flowchart:** + +```mermaid +graph TD; + A[Execute Method] --> B[Get Estimated Duration] + B --> C[Wait Until Duration Elapses] + C --> D[Report Progress] + D --> E[Handle Cancellation] + E --> F[End] +``` + +### `GetEstimatedDuration` + +```csharp +public override TimeSpan GetEstimatedDuration() +``` + +- **Description:** Calculates the duration until the target time is reached, considering rollovers. + +- **Flowchart:** + +```mermaid +graph TD; + A[GetEstimatedDuration Method] --> B[Calculate Target Time] + B --> C[Adjust for Rollover] + C --> D[Calculate Time Difference] + D --> E[Return Duration] + E --> F[End] +``` + +### `ToString` + +```csharp +public override string ToString() +``` + +- **Description:** Returns a string representation of the current state, including target time and offset. diff --git a/doc/task/utility/wait_timespan.md b/doc/task/utility/wait_timespan.md new file mode 100644 index 00000000..b6a99a9f --- /dev/null +++ b/doc/task/utility/wait_timespan.md @@ -0,0 +1,112 @@ +# WaitForTimeSpan + +The `WaitForTimeSpan` class extends `SequenceItem` and is designed to wait for a specified time span. It provides a simple way to introduce delays in a sequence based on a duration defined in seconds. + +## Namespace + +```csharp +namespace NINA.Sequencer.SequenceItem.Utility +``` + +## Class Declaration + +```csharp +public class WaitForTimeSpan : SequenceItem +``` + +## Properties + +### `Time` + +- **Type:** `double` +- **Description:** The duration to wait, in seconds. +- **Json Property:** `[JsonProperty]` + +## Constructor + +### Default Constructor + +```csharp +[ImportingConstructor] +public WaitForTimeSpan() +``` + +- **Description:** Initializes a new instance with a default time of 1 second. + +### Clone Constructor + +```csharp +private WaitForTimeSpan(WaitForTimeSpan cloneMe) : base(cloneMe) +``` + +- **Parameters:** + - `cloneMe`: The instance to clone. + +## Methods + +### `Clone` + +```csharp +public override object Clone() +``` + +- **Description:** Creates a deep copy of the current `WaitForTimeSpan` instance. +- **Flowchart:** + +```mermaid +graph TD; + A[Clone Method] --> B[Create New WaitForTimeSpan] + B --> C[Copy Properties] + C --> D[Return New Instance] + D --> E[End] +``` + +### `Execute` + +```csharp +public override Task Execute(IProgress progress, CancellationToken token) +``` + +- **Description:** Executes the waiting task for the duration specified by `Time`. +- **Flowchart:** + +```mermaid +graph TD; + A[Execute Method] --> B[Get Estimated Duration] + B --> C[Wait Until Duration Elapses] + C --> D[Report Progress] + D --> E[Handle Cancellation] + E --> F[End] +``` + +### `GetEstimatedDuration` + +```csharp +public override TimeSpan GetEstimatedDuration() +``` + +- **Description:** Returns the estimated duration to wait as a `TimeSpan`, based on the `Time` property. +- **Flowchart:** + +```mermaid +graph TD; + A[GetEstimatedDuration Method] --> B[Convert Time to TimeSpan] + B --> C[Return Duration] + C --> D[End] +``` + +### `ToString` + +```csharp +public override string ToString() +``` + +- **Description:** Returns a string representation of the current state, including the time span to wait. +- **Flowchart:** + +```mermaid +graph TD; + A[ToString Method] --> B[Format String] + B --> C[Return String] + C --> D[End] +``` diff --git a/doc/thirdparty.md b/doc/thirdparty.md new file mode 100644 index 00000000..c2f1efc7 --- /dev/null +++ b/doc/thirdparty.md @@ -0,0 +1,70 @@ +# 需要使用到的第三方软件 + +## 设备控制 + +### ASCOM:Windows下的设备控制 + +链接: [官网](https://www.ascom-standards.org/) + +由于ASCOM本体使用C#编写,因此不能直接通过C++控制,虽然可以模仿PHD2直接操作串口控制,但是这样的可用性很低。因此使用ASCOM Remote作为中转服务器。 + +链接: [ASCOM Remote](https://github.com/ASCOMInitiative/ASCOMRemote) + +ASCOM Remote的通信基于http协议,消息格式为json,在服务器启动后会暴露指定的端口。 + +可以模仿的实现:[Python下的ASCOM Remote客户端](https://github.com/ASCOMInitiative/alpyca) + + +#### 使用流程 + +用户启动ASCOMRemote服务器并选择需要使用的具体设备 -> 尝试扫描服务器,发现后建立连接 -> 根据API获取所有设备的信息 -> 连接完成 + +参考文档: +[ASCOM文档中心](https://www.ascom-standards.org/Documentation/Index.htm) +[Alpaca指令定义](https://download.ascom-standards.org/docs/AlpacaIntroduction.pdf) +[Alpyca文档](https://ascom-standards.org/alpyca/alpyca.pdf) +[PHD2中的ASCOM客户端](https://github.com/OpenPHDGuiding/phd2/blob/master/cam_ascom.cpp) + +### INDI: Linux下的设备控制 + +链接: [官网](https://github.com/indilib/) + +使用纯正C/C++编写,使用基于XML的tcp通信。__需要得到优先支持__ + +[核心库](https://github.com/indilib/indi) +[第三方驱动](https://github.com/indilib/indi-3rdparty) + +#### 使用流程 + +有用户选择对应的驱动大类 -> 启动INDI服务器 -> (INDI服务器会自动扫描属于已经选择的驱动大类下的具体设备) -> 连接服务器 -> (第一次连接INDI会返回所有已有设备的信息,此时设备仍然未连接) -> 用户选择具体的设备型号 -> 发送连接指令 -> INDI服务器与具体设备连接 -> 连接流程结束 + + +#### 参考资料 + +[Kstars中的INDI客户端](https://github.com/KDE/kstars/tree/master/kstars/indi) +[PHD2中的INDI客户端](https://github.com/OpenPHDGuiding/phd2/blob/master/cam_indi.cpp) +[Python下的INDI客户端](https://github.com/indilib/pyindi-client) + +## 导星 + +### PHD2 + +链接: [官网](https://github.com/OpenPHDGuiding/phd2) + +[服务器接口](https://github.com/OpenPHDGuiding/phd2/wiki/EventMonitoring) + +## 解析 + +### Astrometry.net + +链接: [官网](https://github.com/dstndstn/astrometry.net) + +文档: [官方文档](https://astrometry.net/doc/readme.html) + +命令行工具 + +### Astap + +[官网](https://www.hnsky.org/astap.htm) + +命令行工具 diff --git a/driver/CMakeLists.txt b/driver/CMakeLists.txt deleted file mode 100644 index 3bef5c55..00000000 --- a/driver/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# CMakeLists.txt for Atom-Driver -# This project is licensed under the terms of the GPL3 license. -# -# Project Name: Atom-Driver -# Description: A collection of drivers for Atom -# Author: Max Qian -# License: GPL3 - -cmake_minimum_required(VERSION 3.20) -project(atom-driver-interface C CXX) - -add_subdirectory(solver) -add_subdirectory(client) diff --git a/driver/camera/atom-asi/camera.cpp b/driver/camera/atom-asi/camera.cpp deleted file mode 100644 index bef30bea..00000000 --- a/driver/camera/atom-asi/camera.cpp +++ /dev/null @@ -1,474 +0,0 @@ -/* - * camera.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-3-29 - -Description: ASICamera Simulator and Basic Definition - -**************************************************/ - -#include "camera.hpp" - -#include "atom/driver/macro.hpp" -#include "atom/log/loguru.hpp" - -#include -#include - -#define ASI_CAMERA_CONNECTION_CHECK \ - if (is_connected.load()) { \ - LOG_F(WARNING, "Camera already connected"); \ - return true; \ - } - -#define ASI_CAMERA_CONNECT_CHECK \ - if (!is_connected.load()) { \ - LOG_F(ERROR, "Camera not connected"); \ - return false; \ - } - -#define ASI_CAMERA_EXPOSURE_CHECK \ - if (is_exposing.load()) { \ - LOG_F(ERROR, "Camera is exposing"); \ - return false; \ - } - -#define ASI_CAMERA_VIDEO_CHECK \ - if (is_videoing.load()) { \ - LOG_F(ERROR, "Camera is videoing"); \ - return false; \ - } - -using ImgBufferPtr = std::unique_ptr; - -ASICamera::ASICamera(const std::string &name) : AtomCamera(name) {} - -ASICamera::~ASICamera() {} - -bool ASICamera::initialize() { - AtomCamera::initialize(); - - registerVariable("CAMERA_COUNT", 0, "the number of connected cameras"); - return true; -} - -bool ASICamera::destroy() { - AtomCamera::destroy(); - return true; -} - -bool ASICamera::connect(const json ¶ms) { - ASI_CAMERA_CONNECTION_CHECK; - if (!params.contains("name")) { - LOG_F(ERROR, "No camera name provided"); - return false; - } - auto camera_name = params["name"].get(); - - auto camera_count = ASIGetNumOfConnectedCameras(); - if (camera_count <= 0) { - LOG_F(ERROR, - "ASI camera not found, please check the power supply or make " - "sure the camera is connected."); - return false; - } - for (int i = 0; i < camera_count; i++) { - /*获取相机信息*/ - if ((errCode = ASIGetCameraProperty(&ASICameraInfo, i)) != - ASI_SUCCESS) { - LOG_F(ERROR, - "Unable to get {} configuration information,the error " - "code is {},please check program permissions.\n", - ASICameraInfo.Name, errCode); - return false; - } - if (ASICameraInfo.Name == camera_name) { - LOG_F(INFO, "Find camera {}", ASICameraInfo.Name); - // Max: The member variable is faster than component variable - setVariable("DEVICE_ID", ASICameraInfo.CameraID); - setVariable("DEVICE_NAME", ASICameraInfo.Name); - m_camera_id = ASICameraInfo.CameraID; - m_camera_name = ASICameraInfo.Name; - /*打开相机*/ - if ((errCode = ASIOpenCamera(ASICameraInfo.CameraID)) != - ASI_SUCCESS) { - LOG_F(ERROR, "Unable to turn on the {}, error code: {}.", - ASICameraInfo.Name, errCode); - return false; - } - /*初始化相机*/ - if ((errCode = ASIInitCamera(ASICameraInfo.CameraID)) != - ASI_SUCCESS) { - LOG_F(ERROR, - "Unable to initialize connection to " - "camera,the error code is {}.", - errCode); - return false; - } - setVariable("DEVICE_CONNECTED", true); - is_connected.store(true); - LOG_F(INFO, "Camera connected successfully\n"); - return true; - } else { - LOG_F(ERROR, "This is not a designated camera"); - } - } - LOG_F(ERROR, "No camera found"); - return false; -} - -bool ASICamera::disconnect(const json ¶ms) { /*在关闭相机之前停止所有任务*/ - ASI_CAMERA_CONNECT_CHECK; - if (!params.empty()) { - LOG_F(ERROR, "No parameters are allowed"); - return false; - } - - if (is_videoing.load()) { - if ((errCode = ASIStopVideoCapture(m_camera_id)) != - ASI_SUCCESS) // 停止视频拍摄 - { - LOG_F(ERROR, - "Unable to stop video capture,error code is {},please try " - "again.", - errCode); - return false; - } - is_videoing.store(false); - setVariable("CCD_VIDEO_STATUS", false); - LOG_F(INFO, "Stop video capture"); - } - if (is_exposing.load()) { - if ((errCode = ASIStopExposure(m_camera_id)) != - ASI_SUCCESS) // 停止曝光 - { - LOG_F(ERROR, - "Unable to stop exposure,error code is {}, please try again.", - errCode); - return false; - } - is_exposing.store(false); - setVariable("CCD_EXPOSURE_STATUS", false); - LOG_F(INFO, "Stop exposure"); - } - /*关闭相机*/ - if ((errCode = ASICloseCamera(m_camera_id)) != ASI_SUCCESS) // 关闭相机 - { - LOG_F(ERROR, "Unable to turn off the camera,error code: {}", errCode); - return false; - } - setVariable("DEVICE_CONNECTED", false); - is_connected.store(false); - LOG_F(INFO, "Disconnect from camera"); - return true; -} - -bool ASICamera::reconnect(const json ¶ms) { - ASI_CAMERA_CONNECT_CHECK; - int timeout = 0; - if (params.contains("timeout")) { - timeout = params["timeout"].get(); - } - - if (!disconnect({})) { - LOG_F(ERROR, "Unable to disconnect from camera"); - return false; - } - if (!connect({})) { - LOG_F(ERROR, "Unable to connect to camera"); - return false; - } - LOG_F(INFO, "Reconnect to camera: {}", - getVariable("DEVICE_NAME")); - return true; -} - -bool ASICamera::isConnected() { return is_connected.load(); } - -bool ASICamera::startExposure(const double &duration) { - ASI_CAMERA_CONNECT_CHECK; - if (is_exposing.load()) { - LOG_F(ERROR, "Exposure is already in progress"); - return false; - } - - const long blink_duration = duration * 1000000; - LOG_F(INFO, "Blinking {} time(us) before exposure", blink_duration); - if ((errCode = ASISetControlValue(m_camera_id, ASI_EXPOSURE, blink_duration, - ASI_FALSE)) != ASI_SUCCESS) { - LOG_F(ERROR, "Failed to set blink exposure to {}us, error {}", - blink_duration, errCode); - return false; - } - if ((errCode = ASIStartExposure(m_camera_id, ASI_FALSE)) != ASI_SUCCESS) { - LOG_F(ERROR, "Failed to start blink exposure, error code: {}", errCode); - return false; - } - is_exposing.store(true); - setVariable("CCD_EXPOSURE_STATUS", true); - // Max: A timer is needed here - do { - usleep(10000); - errCode = ASIGetExpStatus(m_camera_id, &expStatus); - } while (errCode == ASI_SUCCESS && expStatus == ASI_EXP_WORKING); - if (errCode != ASI_SUCCESS) { - LOG_F(ERROR, "Blink exposure failed, error {}, error code: {}", errCode, - expStatus); - return false; - } - is_exposing.store(false); - setVariable("CCD_EXPOSURE_STATUS", false); - LOG_F(INFO, "Blink exposure completed"); - return true; -} - -bool ASICamera::abortExposure() { - ASI_CAMERA_CONNECT_CHECK; - if (!is_exposing.load()) { - LOG_F(ERROR, "No exposure is in progress"); - return false; - } - if ((errCode = ASIStopExposure(m_camera_id)) != ASI_SUCCESS) { - LOG_F(ERROR, "Unable to stop camera exposure, error code: {}", errCode); - return false; - } - setVariable("CCD_EXPOSURE_STATUS", false); - is_exposing.store(false); - LOG_F(INFO, "Abort exposure"); - return true; -} - -bool ASICamera::getExposureStatus() { - ASI_CAMERA_CONNECT_CHECK; - if ((errCode = ASIGetExpStatus(m_camera_id, &expStatus)) != ASI_SUCCESS) { - LOG_F(INFO, "Camera is busy, status code: {}", errCode); - setVariable("CCD_EXPOSURE_STATUS", true); - is_exposing.store(true); - return true; - } - LOG_F(INFO, "Camera is idle"); - return false; -} - -bool ASICamera::getExposureResult() { - ASI_CAMERA_CONNECT_CHECK; - ASI_CAMERA_EXPOSURE_CHECK; - - GET_INT_VARIABLE(width); - GET_INT_VARIABLE(height); - - long imgSize = width * height; - //* (1 + (ASICAMERA->ImageType == ASI_IMG_RAW16)); - - // 使用智能指针管理图像缓冲区内存 - ImgBufferPtr imgBuf(new unsigned char[imgSize]); - - /*曝光后获取图像信息*/ - int errCode = ASIGetDataAfterExp(m_camera_id, imgBuf.get(), imgSize); - if (errCode != ASI_SUCCESS) { - // 获取图像失败 - LOG_F(ERROR, "Unable to get image from camera, error code: {}", - errCode); - return; - } - - // 图像下载完成 - LOG_F(INFO, "Download from camera completely."); - - GET_STR_VARIABLE(upload_mode); - if (upload_mode == "LOCAL") [[likely]] { - // Max: image filename generation logic is needed - std::string FitsName = "test.fits"; - LOG_F(INFO, "Upload mode is LOCAL, save image to {}", FitsName); - /*将图像写入本地文件*/ - // auto res = getComponent("LITHIUM_IMAGE") - // ->runFunc("SaveImage", {{"filename", FitsName}, - // {"data", imgBuf}, - //{ "size", imgSize } - //}); - // if (res.contains("error")) { - // LOG_F(ERROR, "Unable to save image to {}, error: {}", FitsName, - // res["error"].get()); - // return false; - //} - } else if (upload_mode == "CLIENT") [[unlikely]] { - } else if (upload_mode == "None") [[unlikely]] { - LOG_F(INFO, "Upload mode is NONE, skip upload"); - } else { - LOG_F(ERROR, "Invalid upload mode: {}", upload_mode); - return false; - } - return true; -} - -bool ASICamera::saveExposureResult() { return true; } - -bool ASICamera::startVideo() { return true; } - -bool ASICamera::stopVideo() { return true; } - -bool ASICamera::getVideoStatus() { return true; } - -bool ASICamera::getVideoResult() { return true; } - -bool ASICamera::saveVideoResult() { return true; } - -bool ASICamera::startCooling() { return true; } - -bool ASICamera::stopCooling() { return true; } - -bool ASICamera::getCoolingStatus() { return true; } - -bool ASICamera::isCoolingAvailable() { return true; } - -bool ASICamera::getTemperature() { return true; } - -bool ASICamera::getCoolingPower() { return true; } - -bool ASICamera::setTemperature(const double &temperature) { - ASI_CAMERA_CONNECT_CHECK; - ASI_CAMERA_EXPOSURE_CHECK; - ASI_CAMERA_VIDEO_CHECK; - if (!is_cooling_available) { - LOG_F(ERROR, "Cooling is not available"); - return false; - } - /*转化温度参数*/ - long TargetTemp; - if (temperature > 0.5) - TargetTemp = static_cast(temperature + 0.49); - else if (temperature < 0.5) - TargetTemp = static_cast(temperature - 0.49); - else - TargetTemp = 0; - /*设置相机温度*/ - if ((errCode = ASISetControlValue(m_camera_id, ASI_TEMPERATURE, TargetTemp, - ASI_FALSE)) != ASI_SUCCESS) { - LOG_F(ERROR, "Unable to set camera temperature, error code: {}", - errCode); - return false; - } - setVariable("CCD_TEMPERATURE_VALUE", TargetTemp); - LOG_F(INFO, "Set camera cooling temperature to {}", TargetTemp); - return true; -} - -bool ASICamera::setCoolingPower(const double &power) { return true; } - -bool ASICamera::getGain() { - ASI_CAMERA_CONNECT_CHECK; - long gain; - if ((errCode = ASIGetControlValue(m_camera_id, ASI_GAIN, &gain, NULL)) != - ASI_SUCCESS) { - LOG_F(ERROR, "Unable to get camera gain, error code: {}", errCode); - return false; - } - setVariable("CCD_GAIN", static_cast(gain)); - m_gain.store(static_cast(gain)); - LOG_F(INFO, "Get camera gain: {}", gain); - return true; -} - -bool ASICamera::setGain(const int &gain) { - ASI_CAMERA_CONNECT_CHECK; - ASI_CAMERA_EXPOSURE_CHECK; - ASI_CAMERA_VIDEO_CHECK; - if ((errCode = ASISetControlValue(m_camera_id, ASI_GAIN, gain, - ASI_FALSE)) != ASI_SUCCESS) { - LOG_F(ERROR, "Unable to set camera gain,error code: {}", errCode); - return false; - } - setVariable("CCD_GAIN", gain); - m_gain.store(gain); - LOG_F(INFO, "Set camera gain to {}", gain); - return true; -} - -bool ASICamera::isGainAvailable() { - LOG_F(INFO, "Gain is available for {}", m_camera_name); - return true; -} - -bool ASICamera::getOffset() { - ASI_CAMERA_CONNECT_CHECK; - long offset; - if ((errCode = ASIGetControlValue(m_camera_id, ASI_BRIGHTNESS, &offset, - NULL)) != ASI_SUCCESS) { - LOG_F(ERROR, "Unable to get camera offset, error code: {}", errCode); - return false; - } - setVariable("CCD_OFFSET", static_cast(offset)); - m_offset.store(static_cast(offset)); - LOG_F(INFO, "Get camera offset: {}", offset); - return true; -} - -bool ASICamera::setOffset(const int &offset) { - ASI_CAMERA_CONNECT_CHECK; - ASI_CAMERA_EXPOSURE_CHECK; - ASI_CAMERA_VIDEO_CHECK; - if ((errCode = ASISetControlValue(m_camera_id, ASI_BRIGHTNESS, offset, - ASI_FALSE)) != ASI_SUCCESS) { - LOG_F(ERROR, "Unable to set camera offset, error code: {}", errCode); - return false; - } - setVariable("CCD_OFFSET", offset); - m_offset.store(offset); - LOG_F(INFO, "Set camera offset to {}", offset); - return true; -} - -bool ASICamera::isOffsetAvailable() { - LOG_F(INFO, "Offset is available for {}", m_camera_name); - return true; -} - -bool ASICamera::getISO() { - LOG_F(ERROR, "ISO is not available for {}", m_camera_name); - return false; -} - -bool ASICamera::setISO(const int &iso) { - LOG_F(ERROR, "ISO is not available for {}", m_camera_name); - return false; -} - -bool ASICamera::isISOAvailable() { - LOG_F(INFO, "ISO is not available for {}", m_camera_name); - return false; -} - -bool ASICamera::getFrame() { return true; } - -bool ASICamera::setFrame(const int &x, const int &y, const int &w, - const int &h) { - return true; -} - -bool ASICamera::isFrameSettingAvailable() { return true; } - -bool ASICamera::getBinning() { return true; } - -bool ASICamera::setBinning(const int &hor, const int &ver) { return true; } - -bool ASICamera::getFrameType() { return true; } - -bool ASICamera::setFrameType(FrameType type) { return true; } - -bool ASICamera::getUploadMode() { return true; } - -bool ASICamera::setUploadMode(UploadMode mode) { return true; } - -bool ASICamera::refreshCameraInfo() { - if ((errCode = ASIGetCameraProperty(&ASICameraInfo, i)) != ASI_SUCCESS) { - LOG_F(ERROR, "Unable to get camera information, error code: {}", - errCode); - return false; - } - return true; -} diff --git a/driver/camera/atom-asi/camera.hpp b/driver/camera/atom-asi/camera.hpp deleted file mode 100644 index ad3db211..00000000 --- a/driver/camera/atom-asi/camera.hpp +++ /dev/null @@ -1,120 +0,0 @@ -#ifndef ATOM_ASI_COMPONENT_HPP -#define ATOM_ASI_COMPONENT_HPP - -#include "atom/driver/camera.hpp" - -#include "driverlibs/libasi/ASICamera2.h" - -#include - -class ASICamera : public AtomCamera { -public: - explicit ASICamera(const std::string &name); - ~ASICamera(); - - bool initialize() final; - bool destroy() final; - - bool connect(const json ¶ms) final; - - bool disconnect(const json ¶ms) final; - - bool reconnect(const json ¶ms) final; - - bool isConnected() final; - - bool startExposure(const double &duration) final; - - bool abortExposure() final; - - bool getExposureStatus() final; - - bool getExposureResult() final; - - bool saveExposureResult() final; - - bool startVideo() final; - - bool stopVideo() final; - - bool getVideoStatus() final; - - bool getVideoResult() final; - - bool saveVideoResult() final; - - bool startCooling() final; - - bool stopCooling() final; - - bool getCoolingStatus() final; - - bool isCoolingAvailable() final; - - bool getTemperature() final; - - bool getCoolingPower() final; - - bool setTemperature(const double &temperature) final; - - bool setCoolingPower(const double &power) final; - - bool getGain() final; - - bool setGain(const int &gain) final; - - bool isGainAvailable() final; - - bool getOffset() final; - - bool setOffset(const int &offset) final; - - bool isOffsetAvailable() final; - - bool getISO() final; - - bool setISO(const int &iso) final; - - bool isISOAvailable() final; - - bool getFrame() final; - - bool setFrame(const int &x, const int &y, const int &w, const int &h) final; - - bool isFrameSettingAvailable() final; - - bool getBinning() final; - - bool setBinning(const int &hor, const int &ver) final; - - bool getFrameType() final; - - bool setFrameType(FrameType type) final; - - bool getUploadMode() final; - - bool setUploadMode(UploadMode mode) final; - -private: - bool refreshCameraInfo(); - - /*ASI相机参数*/ - ASI_CAMERA_INFO ASICameraInfo; - ASI_ERROR_CODE errCode; - ASI_EXPOSURE_STATUS expStatus; - - int m_camera_id; - std::string m_camera_name; - - std::atomic_bool is_connected; - std::atomic_bool is_exposing; - std::atomic_bool is_videoing; - std::atomic_bool is_cooling; - - bool is_cooling_available; - - std::atomic m_gain; - std::atomic m_offset; -}; - -#endif diff --git a/driver/camera/atom-asi/package.json b/driver/camera/atom-asi/package.json deleted file mode 100644 index 70ecb92d..00000000 --- a/driver/camera/atom-asi/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "Atom-Camera-ASI", - "version": "1.0.0", - "type": "shared", - "description": "Atom driver for ASI Camera", - "license": "LGPL-3.0-or-later", - "author": "Max Qian", - "repository": { - "type": "git", - "url": "https://github.com/ElementAstro/Atom-ASI" - }, - "bugs": { - "url": "https://github.com/ElementAstro/Atom-ASI/issues" - }, - "homepage": "https://github.com/ElementAstro/Atom-ASI", - "keywords": [ - "asi", - "camera", - "filter wheel" - ], - "scripts": { - "dev": "./atom-asi --standalone", - "build": "cmake --build-type=Release -- -j 4", - "lint": "clang-format -i src/*.cpp src/*.h" - }, - "dependencies": { - "asi-sdk" : "^1.34" - }, - "main": { - "ASICamera": { - "func": "getInstance", - "type" : "shared" - } - } -} diff --git a/driver/camera/atom-touptek/camera.cpp b/driver/camera/atom-touptek/camera.cpp deleted file mode 100644 index f0afffd1..00000000 --- a/driver/camera/atom-touptek/camera.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/* - * camera.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-3-29 - -Description: Touptek Camera - -**************************************************/ - -#include "camera.hpp" - -#include "atom/log/loguru.hpp" - -#include - -ToupCamera::ToupCamera(const std::string &name) - : ToupCamera(name) {} - -ToupCamera::~ToupCamera() {} - -bool ToupCamera::initialize() { return true; } - -bool ToupCamera::destroy() { return true; } - -bool ToupCamera::connect(const json ¶ms) { return true; } - -bool ToupCamera::disconnect(const json ¶ms) { return true; } - -bool ToupCamera::reconnect(const json ¶ms) { return true; } - -bool ToupCamera::isConnected() { return true; } - -bool ToupCamera::startExposure(const double &duration) { return true; } - -bool ToupCamera::abortExposure() { return true; } - -bool ToupCamera::getExposureStatus() { return true; } - -bool ToupCamera::getExposureResult() { return true; } - -bool ToupCamera::saveExposureResult() { return true; } - -bool ToupCamera::startVideo() { return true; } - -bool ToupCamera::stopVideo() { return true; } - -bool ToupCamera::getVideoStatus() { return true; } - -bool ToupCamera::getVideoResult() { return true; } - -bool ToupCamera::saveVideoResult() { return true; } - -bool ToupCamera::startCooling() { return true; } - -bool ToupCamera::stopCooling() { return true; } - -bool ToupCamera::getCoolingStatus() { return true; } - -bool ToupCamera::isCoolingAvailable() { return true; } - -bool ToupCamera::getTemperature() { return true; } - -bool ToupCamera::getCoolingPower() { return true; } - -bool ToupCamera::setTemperature(const double &temperature) { return true; } - -bool ToupCamera::setCoolingPower(const double &power) { return true; } - -bool ToupCamera::getGain() { return true; } - -bool ToupCamera::setGain(const int &gain) { return true; } - -bool ToupCamera::isGainAvailable() { return true; } - -bool ToupCamera::getOffset() { return true; } - -bool ToupCamera::setOffset(const int &offset) { return true; } - -bool ToupCamera::isOffsetAvailable() { return true; } - -bool ToupCamera::getISO() { return true; } - -bool ToupCamera::setISO(const int &iso) { return true; } - -bool ToupCamera::isISOAvailable() { return true; } - -bool ToupCamera::getFrame() { return true; } - -bool ToupCamera::setFrame(const int &x, const int &y, const int &w, - const int &h) { - return true; -} - -bool ToupCamera::isFrameSettingAvailable() { return true; } - -bool ToupCamera::getBinning() { return true; } - -bool ToupCamera::setBinning(const int &hor, const int &ver) { return true; } - -bool ToupCamera::getFrameType() { return true; } - -bool ToupCamera::setFrameType(FrameType type) { return true; } - -bool ToupCamera::getUploadMode() { return true; } - -bool ToupCamera::setUploadMode(UploadMode mode) { return true; } diff --git a/driver/camera/atom-touptek/camera.hpp b/driver/camera/atom-touptek/camera.hpp deleted file mode 100644 index 77ddd772..00000000 --- a/driver/camera/atom-touptek/camera.hpp +++ /dev/null @@ -1,101 +0,0 @@ -#ifndef ATOM_TOUPTEK_COMPONENT_HPP -#define ATOM_TOUPTEK_COMPONENT_HPP - -#include "atom/driver/camera.hpp" - -#include "libtouptek/toupcam.h" - -#include - -class ToupCamera : public AtomCamera { -public: - explicit ToupCamera(const std::string &name); - ~ToupCamera(); - - bool initialize() final; - bool destroy() final; - - bool connect(const json ¶ms) final; - - bool disconnect(const json ¶ms) final; - - bool reconnect(const json ¶ms) final; - - bool isConnected() final; - - bool startExposure(const double &duration) final; - - bool abortExposure() final; - - bool getExposureStatus() final; - - bool getExposureResult() final; - - bool saveExposureResult() final; - - bool startVideo() final; - - bool stopVideo() final; - - bool getVideoStatus() final; - - bool getVideoResult() final; - - bool saveVideoResult() final; - - bool startCooling() final; - - bool stopCooling() final; - - bool getCoolingStatus() final; - - bool isCoolingAvailable() final; - - bool getTemperature() final; - - bool getCoolingPower() final; - - bool setTemperature(const double &temperature) final; - - bool setCoolingPower(const double &power) final; - - bool getGain() final; - - bool setGain(const int &gain) final; - - bool isGainAvailable() final; - - bool getOffset() final; - - bool setOffset(const int &offset) final; - - bool isOffsetAvailable() final; - - bool getISO() final; - - bool setISO(const int &iso) final; - - bool isISOAvailable() final; - - bool getFrame() final; - - bool setFrame(const int &x, const int &y, const int &w, const int &h) final; - - bool isFrameSettingAvailable() final; - - bool getBinning() final; - - bool setBinning(const int &hor, const int &ver) final; - - bool getFrameType() final; - - bool setFrameType(FrameType type) final; - - bool getUploadMode() final; - - bool setUploadMode(UploadMode mode) final; - -private: -}; - -#endif diff --git a/driver/camera/atom-touptek/detail/libtoupbase.cpp b/driver/camera/atom-touptek/detail/libtoupbase.cpp deleted file mode 100644 index 52c6e077..00000000 --- a/driver/camera/atom-touptek/detail/libtoupbase.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - * libtoupbase.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 20234-3-1 - -Description: ToupBase Library - -**************************************************/ - -#include "libtoupbase.hpp" -#include - -std::string errorCodes(HRESULT rc) { - static std::unordered_map errCodes = { - {0x00000000, "Success"}, - {0x00000001, "Yet another success"}, - {0x8000ffff, "Catastrophic failure"}, - {0x80004001, "Not supported or not implemented"}, - {0x80070005, "Permission denied"}, - {0x8007000e, "Out of memory"}, - {0x80070057, "One or more arguments are not valid"}, - {0x80004003, "Pointer that is not valid"}, - {0x80004005, "Generic failure"}, - {0x8001010e, "Call function in the wrong thread"}, - {0x8007001f, "Device not functioning"}, - {0x800700aa, "The requested resource is in use"}, - {0x8000000a, - "The data necessary to complete this operation is not yet available"}, - {0x8001011f, - "This operation returned because the timeout period expired"}}; - - const std::unordered_map::iterator it = - errCodes.find(rc); - if (it != errCodes.end()) - return it->second; - else { - char str[256]; - snprintf(str, sizeof(str), "Unknown error: 0x%08x", rc); - return std::string(str); - } -} diff --git a/driver/camera/atom-touptek/detail/libtoupbase.hpp b/driver/camera/atom-touptek/detail/libtoupbase.hpp deleted file mode 100644 index ad4e4923..00000000 --- a/driver/camera/atom-touptek/detail/libtoupbase.hpp +++ /dev/null @@ -1,91 +0,0 @@ -/* - * libtoupbase.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 20234-3-1 - -Description: ToupBase Library - -**************************************************/ - -#pragma once - -#include - -#ifdef BUILD_TOUPCAM -#include -#define FP(x) Toupcam_##x -#define CP(x) TOUPCAM_##x -#define XP(x) Toupcam##x -#define THAND HToupcam -#define DNAME "ToupTek" -#elif BUILD_ALTAIRCAM -#include -#define FP(x) Altaircam_##x -#define CP(x) ALTAIRCAM_##x -#define XP(x) Altaircam##x -#define THAND HAltaircam -#define DNAME "Altair" -#elif BUILD_BRESSERCAM -#include -#define FP(x) Bressercam_##x -#define CP(x) BRESSERCAM_##x -#define XP(x) Bressercam##x -#define THAND HBressercam -#define DNAME "Bresser" -#elif BUILD_MALLINCAM -#include -#define FP(x) Mallincam_##x -#define CP(x) MALLINCAM_##x -#define XP(x) Mallincam##x -#define THAND HMallincam -#define DNAME "MALLINCAM" -#elif BUILD_NNCAM -#include -#define FP(x) Nncam_##x -#define CP(x) NNCAM_##x -#define XP(x) Nncam##x -#define THAND HNncam -#define DNAME "Nn" -#elif BUILD_OGMACAM -#include -#define FP(x) Ogmacam_##x -#define CP(x) OGMACAM_##x -#define XP(x) Ogmacam##x -#define THAND HOgmacam -#define DNAME "OGMAVision" -#elif BUILD_OMEGONPROCAM -#include -#define FP(x) Omegonprocam_##x -#define CP(x) OMEGONPROCAM_##x -#define XP(x) Omegonprocam##x -#define THAND HOmegonprocam -#define DNAME "Astroshop" -#elif BUILD_STARSHOOTG -#include -#define FP(x) Starshootg_##x -#define CP(x) STARSHOOTG_##x -#define XP(x) Starshootg##x -#define THAND HStarshootg -#define DNAME "Orion" -#elif BUILD_TSCAM -#include -#define FP(x) Tscam_##x -#define CP(x) TSCAM_##x -#define XP(x) Tscam##x -#define THAND HTscam -#define DNAME "Teleskop" -#elif BUILD_MEADECAM -#include -#define FP(x) Toupcam_##x -#define CP(x) TOUPCAM_##x -#define XP(x) Toupcam##x -#define THAND HToupcam -#define DNAME "Meade" -#endif - -extern std::string errorCodes(HRESULT rc); diff --git a/driver/camera/atom-touptek/package.json b/driver/camera/atom-touptek/package.json deleted file mode 100644 index f9ebf862..00000000 --- a/driver/camera/atom-touptek/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "Atom-Camera-Touptek", - "version": "1.0.0", - "type": "shared", - "description": "Atom driver for Touptek Camera", - "license": "LGPL-3.0-or-later", - "author": "Max Qian", - "repository": { - "type": "git", - "url": "https://github.com/ElementAstro/Atom-Touptek" - }, - "bugs": { - "url": "https://github.com/ElementAstro/Atom-Touptek/issues" - }, - "homepage": "https://github.com/ElementAstro/Atom-Touptek", - "keywords": [ - "asi", - "camera", - "filter wheel" - ], - "scripts": { - "dev": "./atom-touptek --standalone", - "build": "cmake --build-type=Release -- -j 4", - "lint": "clang-format -i src/*.cpp src/*.h" - }, - "dependencies": { - "asi-sdk" : "^1.34" - }, - "main": { - "TouptekCamera": { - "func": "getInstance", - "type" : "shared" - } - } -} diff --git a/driver/client/CMakeLists.txt b/driver/client/CMakeLists.txt deleted file mode 100644 index f331bc44..00000000 --- a/driver/client/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -# CMakeLists.txt for Atom-Client -# This project is licensed under the terms of the GPL3 license. -# -# Project Name: Atom-Client -# Description: A collection of clients for Atom. For cross-platform use. -# Author: Max Qian -# License: GPL3 - -cmake_minimum_required(VERSION 3.20) -project(atom-client C CXX) - -# add_subdirectory(atom-hydrogen) diff --git a/driver/client/README.md b/driver/client/README.md deleted file mode 100644 index 73c291d1..00000000 --- a/driver/client/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Atom Driver - Client - -Here are all of the official drivers which serve as bridges to overcome the differences between platform: - -+ Windows : ASCOM(com/direct) or Alpaca(remote/http) via ASCOMRemote -+ Linux: INDI(remote/tcp) or Hydrogen(remote/tcp) -+ All platform: INDIGo(remote/tcp) - -## Content - -+ atom-ascom -+ atom-alpaca -+ atom-hydrogen -+ atom-indigo diff --git a/driver/client/atom-alpaca/CMakeLists.txt b/driver/client/atom-alpaca/CMakeLists.txt deleted file mode 100644 index 56d36c9b..00000000 --- a/driver/client/atom-alpaca/CMakeLists.txt +++ /dev/null @@ -1,30 +0,0 @@ -add_library( - ascom_remote SHARED - ascom_camera.cpp - ascom_camera.hpp - - ascom_covercalibrator.cpp - ascom_covercalibrator.hpp - - ascom_device.cpp - ascom_device.hpp - - ascom_filterwheel.cpp - ascom_filterwheel.hpp - - ascom_focuser.cpp - ascom_focuser.hpp - - ascom_telescope.cpp - ascom_telescope.hpp -) - -target_link_libraries(ascom_remote PRIVATE LithiumDriver LithiumProperty loguru cpp_httplib) - -target_link_libraries(ascom_remote PRIVATE ${OPENSSL_LIBRARIES}) - -if(WIN32) -target_link_libraries(ascom_remote PRIVATE wsock32 ws2_32) -endif() - -include_directories(${CMAKE_SOURCE_DIR}/atom/lidrvier) diff --git a/driver/client/atom-alpaca/camera.hpp b/driver/client/atom-alpaca/camera.hpp deleted file mode 100644 index c43893a3..00000000 --- a/driver/client/atom-alpaca/camera.hpp +++ /dev/null @@ -1,74 +0,0 @@ -#include - -enum ImageArrayElementTypes { INT, FLOAT }; // 假设ImageArrayElementTypes是一个枚举类型 - -class ImageMetadata { -private: - int metavers; - ImageArrayElementTypes imgtype; - ImageArrayElementTypes xmtype; - int rank; - int x_size; - int y_size; - int z_size; - -public: - explicit ImageMetadata( - int metadata_version, - ImageArrayElementTypes image_element_type, - ImageArrayElementTypes transmission_element_type, - int rank, - int num_x, - int num_y, - int num_z - ) : - metavers(metadata_version), - imgtype(image_element_type), - xmtype(transmission_element_type), - rank(rank), - x_size(num_x), - y_size(num_y), - z_size(num_z) - {} - - int get_MetadataVersion() { - return metavers; - } - - ImageArrayElementTypes get_ImageElementType() { - return imgtype; - } - - ImageArrayElementTypes get_TransmissionElementType() { - return xmtype; - } - - int get_Rank() { - return rank; - } - - int get_Dimension1() { - return x_size; - } - - int get_Dimension2() { - return y_size; - } - - int get_Dimension3() { - return z_size; - } -}; - -int main() { - ImageMetadata metadata(1, INT, FLOAT, 2, 640, 480, 0); - std::cout << "Metadata version: " << metadata.get_MetadataVersion() << std::endl; - std::cout << "Image element type: " << metadata.get_ImageElementType() << std::endl; - std::cout << "Transmission element type: " << metadata.get_TransmissionElementType() << std::endl; - std::cout << "Rank: " << metadata.get_Rank() << std::endl; - std::cout << "Dimension 1: " << metadata.get_Dimension1() << std::endl; - std::cout << "Dimension 2: " << metadata.get_Dimension2() << std::endl; - std::cout << "Dimension 3: " << metadata.get_Dimension3() << std::endl; - - return 0; -} diff --git a/driver/client/atom-alpaca/device.cpp b/driver/client/atom-alpaca/device.cpp deleted file mode 100644 index 6c9894b1..00000000 --- a/driver/client/atom-alpaca/device.cpp +++ /dev/null @@ -1,212 +0,0 @@ -/* - * device.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 20234-3-1 - -Description: Basic Device Definition of Alpaca - -**************************************************/ - -#include "device.hpp" - -#include -#include -#include - - -#include "httplib.h" - -constexpr int API_VERSION = 1; - -Device::Device(const std::string &address, const std::string &device_type, - int device_number, const std::string &protocol) - : address(address), - device_type(device_type), - device_number(device_number), - protocol(protocol) { - api_version = API_VERSION; - base_url = protocol + "://" + address + "/api/v" + - std::to_string(api_version) + "/" + device_type + "/" + - std::to_string(device_number); -} - -std::string Device::Action(const std::string &ActionName, - const std::vector &Parameters) { - return _put("action", {{"Action", ActionName}, {"Parameters", Parameters}}) - .at("Value"); -} - -void Device::CommandBlind(const std::string &Command, bool Raw) { - _put("commandblind", {{"Command", Command}, {"Raw", Raw}}); -} - -bool Device::CommandBool(const std::string &Command, bool Raw) { - return _put("commandbool", {{"Command", Command}, {"Raw", Raw}}) - .at("Value"); -} - -std::string Device::CommandString(const std::string &Command, bool Raw) { - return _put("commandstring", {{"Command", Command}, {"Raw", Raw}}) - .at("Value"); -} - -bool Device::get_Connected() const { return _get("connected") == "true"; } - -void Device::set_Connected(bool ConnectedState) { - _put("connected", {{"Connected", ConnectedState}}); -} - -std::string Device::get_Description() const { return _get("description"); } - -std::vector Device::get_DriverInfo() const { - std::vector driver_info; - std::string driver_info_str = _get("driverinfo"); - size_t start_pos = 0; - size_t end_pos = driver_info_str.find(','); - while (end_pos != std::string::npos) { - driver_info.push_back( - driver_info_str.substr(start_pos, end_pos - start_pos)); - start_pos = end_pos + 1; - end_pos = driver_info_str.find(',', start_pos); - } - driver_info.push_back(driver_info_str.substr(start_pos)); - return driver_info; -} - -std::string Device::get_DriverVersion() const { return _get("driverversion"); } - -int Device::get_InterfaceVersion() const { - return std::stoi(_get("interfaceversion")); -} - -std::string Device::get_Name() const { return _get("name"); } - -std::vector Device::get_SupportedActions() const { - std::string supported_actions_str = _get("supportedactions"); - std::vector supported_actions; - size_t start_pos = 0; - size_t end_pos = supported_actions_str.find(','); - while (end_pos != std::string::npos) { - supported_actions.push_back( - supported_actions_str.substr(start_pos, end_pos - start_pos)); - start_pos = end_pos + 1; - end_pos = supported_actions_str.find(',', start_pos); - } - supported_actions.push_back(supported_actions_str.substr(start_pos)); - return supported_actions; -} - -json Device::_get(const std::string &attribute, - const std::map &data = {}, - double tmo = 5.0) const { - httplib::Client client(address.c_str()); - std::string path = base_url + "/" + attribute; - - std::lock_guard lock(_ctid_lock); - std::string client_trans_id = std::to_string(_client_trans_id++); - std::string client_id = std::to_string(_client_id); - - client.set_timeout(tmo); - - httplib::Headers headers = {{"ClientTransactionID", client_trans_id}, - {"ClientID", client_id}}; - - httplib::Result response = client.Get(path.c_str(), headers, data); - - if (response && response->status == 200) { - json j = json::parse(response->body); - int error_number = j["ErrorNumber"]; - std::string error_message = j["ErrorMessage"]; - if (error_number != 0) { - if (error_number == 0x0400) - throw NotImplementedException(error_message); - else if (error_number == 0x0401) - throw InvalidValueException(error_message); - else if (error_number == 0x0402) - throw ValueNotSetException(error_message); - else if (error_number == 0x0407) - throw NotConnectedException(error_message); - else if (error_number == 0x0408) - throw ParkedException(error_message); - else if (error_number == 0x0409) - throw SlavedException(error_message); - else if (error_number == 0x040B) - throw InvalidOperationException(error_message); - else if (error_number == 0x040C) - throw ActionNotImplementedException(error_message); - else if (error_number >= 0x500 && error_number <= 0xFFF) - throw DriverException(error_number, error_message); - else - throw DriverException(error_number, error_message); - } - return j; - } else { - throw AlpacaRequestException( - response ? response->status : -1, - response ? response->body : "Request failed"); - } -} - -json Device::_put(const std::string &attribute, - const std::map &data = {}, - double tmo = 5.0) const { - httplib::Client client(address.c_str()); - std::string path = base_url + "/" + attribute; - - std::lock_guard lock(_ctid_lock); - std::string client_trans_id = std::to_string(_client_trans_id++); - std::string client_id = std::to_string(_client_id); - - client.set_timeout(tmo); - - httplib::Headers headers = {{"ClientTransactionID", client_trans_id}, - {"ClientID", client_id}}; - - json json_data = data; - std::string body = json_data.dump(); - - httplib::Result response = - client.Put(path.c_str(), headers, body, "application/json"); - - if (response && response->status == 200) { - json j = json::parse(response->body); - int error_number = j["ErrorNumber"]; - std::string error_message = j["ErrorMessage"]; - if (error_number != 0) { - if (error_number == 0x0400) - throw NotImplementedException(error_message); - else if (error_number == 0x0401) - throw InvalidValueException(error_message); - else if (error_number == 0x0402) - throw ValueNotSetException(error_message); - else if (error_number == 0x0407) - throw NotConnectedException(error_message); - else if (error_number == 0x0408) - throw ParkedException(error_message); - else if (error_number == 0x0409) - throw SlavedException(error_message); - else if (error_number == 0x040B) - throw InvalidOperationException(error_message); - else if (error_number == 0x040C) - throw ActionNotImplementedException(error_message); - else if (error_number >= 0x500 && error_number <= 0xFFF) - throw DriverException(error_number, error_message); - else - throw DriverException(error_number, error_message); - } - return j; - } else { - throw AlpacaRequestException( - response ? response->status : -1, - response ? response->body : "Request failed"); - } -} - -int Device::_client_id = std::random_device()(); -int Device::_client_trans_id = 1; -std::mutex Device::_ctid_lock; diff --git a/driver/client/atom-alpaca/device.hpp b/driver/client/atom-alpaca/device.hpp deleted file mode 100644 index 6389a009..00000000 --- a/driver/client/atom-alpaca/device.hpp +++ /dev/null @@ -1,194 +0,0 @@ -/* - * device.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 20234-3-1 - -Description: Basic Device Definition of Alpaca - -**************************************************/ - -#ifndef ATOM_ALPACA_DEVICE_HPP -#define ATOM_ALPACA_DEVICE_HPP - -#include -#include -#include -#include - -#include "json.hpp" -using json = nlohmann::json; - -constexpr int API_VERSION = 1; - -/** - * @brief The Device class represents a device connected to a network. - * - * This class provides methods for interacting with the device, such as - * executing actions, sending commands, and retrieving information. - */ -class Device { -public: - /** - * @brief Constructs a Device object with the specified address, device - * type, device number, and protocol. - * - * @param address The address of the device. - * @param device_type The type of the device. - * @param device_number The number of the device. - * @param protocol The communication protocol used to connect to the device. - */ - explicit Device(const std::string &address, const std::string &device_type, - int device_number, const std::string &protocol); - - /** - * @brief Executes an action on the device with the specified name and - * parameters. - * - * @param ActionName The name of the action to execute. - * @param Parameters The parameters to pass to the action. - * @return The result of the action execution. - */ - std::string Action(const std::string &ActionName, - const std::vector &Parameters); - - /** - * @brief Sends a command to the device without waiting for a response. - * - * @param Command The command to send. - * @param Raw Indicates whether the command should be sent as raw text. - */ - void CommandBlind(const std::string &Command, bool Raw); - - /** - * @brief Sends a command to the device and retrieves a boolean response. - * - * @param Command The command to send. - * @param Raw Indicates whether the command should be sent as raw text. - * @return The boolean response from the device. - */ - bool CommandBool(const std::string &Command, bool Raw); - - /** - * @brief Sends a command to the device and retrieves a string response. - * - * @param Command The command to send. - * @param Raw Indicates whether the command should be sent as raw text. - * @return The string response from the device. - */ - std::string CommandString(const std::string &Command, bool Raw); - - /** - * @brief Returns the connection status of the device. - * - * @return True if the device is connected, false otherwise. - */ - bool get_Connected() const; - - /** - * @brief Sets the connection status of the device. - * - * @param ConnectedState The connection status to set. - */ - void set_Connected(bool ConnectedState); - - /** - * @brief Returns the description of the device. - * - * @return The description of the device. - */ - std::string get_Description() const; - - /** - * @brief Returns the driver information of the device. - * - * @return The driver information of the device. - */ - std::vector get_DriverInfo() const; - - /** - * @brief Returns the driver version of the device. - * - * @return The driver version of the device. - */ - std::string get_DriverVersion() const; - - /** - * @brief Returns the interface version of the device. - * - * @return The interface version of the device. - */ - int get_InterfaceVersion() const; - - /** - * @brief Returns the name of the device. - * - * @return The name of the device. - */ - std::string get_Name() const; - - /** - * @brief Returns the supported actions of the device. - * - * @return The supported actions of the device. - */ - std::vector get_SupportedActions() const; - - /** - * @brief Sends a GET request to the device with the specified attribute and - * data. - * - * @param attribute The attribute to retrieve. - * @param data The additional data to include in the request. - * @param tmo The timeout for the request. - * @return The JSON response from the device. - */ - json _get(const std::string &attribute, - const std::map &data, double tmo) const; - - /** - * @brief Sends a PUT request to the device with the specified attribute and - * data. - * - * @param attribute The attribute to modify. - * @param data The data to send in the request. - * @param tmo The timeout for the request. - * @return The JSON response from the device. - */ - json _put(const std::string &attribute, - const std::map &data, double tmo) const; - -private: - /** The address of the device. */ - std::string address; - - /** The type of the device. */ - std::string device_type; - - /** The number of the device. */ - int device_number; - - /** The communication protocol used to connect to the device. */ - std::string protocol; - - /** The API version used by the device. */ - int api_version; - - /** The base URL for accessing the device's API. */ - std::string base_url; - - /** The client ID used for communication with the device. */ - static int _client_id; - - /** The client transaction ID used for communication with the device. */ - static int _client_trans_id; - - /** The mutex used for thread-safe access to the client transaction ID. */ - static std::mutex _ctid_lock; -}; - -#endif diff --git a/driver/client/atom-alpaca/discovery.cpp b/driver/client/atom-alpaca/discovery.cpp deleted file mode 100644 index a41acb6e..00000000 --- a/driver/client/atom-alpaca/discovery.cpp +++ /dev/null @@ -1,263 +0,0 @@ -/* - * discovery.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 20234-3-1 - -Description: A simple way to discover HTTP server - -**************************************************/ - -#include "discovery.hpp" - -#include -#include -#include -#include - -#include "atom/log/loguru.hpp" - -#ifdef _WIN32 -#include -#include -#include - -#ifdef _MSVC -#pragma comment(lib, "iphlpapi.lib") -#pragma comment(lib, "Ws2_32.lib") -#endif -bool init_winsock() { - WSADATA wsaData; - int err = WSAStartup(MAKEWORD(2, 2), &wsaData); - if (err != 0) { - std::cerr << "WSAStartup failed with error: " << err << std::endl; - return false; - } - - return true; -} -#else -#include -#include -#include -#include - -#endif - -#include "atom/type/json.hpp" - -using json = nlohmann::json; - -const int PORT = 32227; -const std::string ALPACA_DISCOVERY = "alpacadiscovery1"; -const std::string ALPACA_RESPONSE = "AlpacaPort"; - -std::vector search_ipv4(int numquery = 2, int timeout = 2) { - std::vector addrs; -#ifdef _WIN32 - // Initialize Winsock - if (!init_winsock()) { - return addrs; - } -#endif - - int sock = socket(AF_INET, SOCK_DGRAM, 0); - if (sock < 0) { - std::cerr << "failed to create socket" << std::endl; - return addrs; - } - - // Enable broadcasting - int broadcast = 1; - if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&broadcast, - sizeof(broadcast)) < 0) { - std::cerr << "failed to enable broadcasting" << std::endl; - close(sock); - return addrs; - } - - struct sockaddr_in addr = {0}; - addr.sin_family = AF_INET; - addr.sin_port = htons(PORT); - - char buf[1024]; - for (int i = 0; i < numquery; ++i) { - struct ifaddrs *ifaddr, *ifa; - if (getifaddrs(&ifaddr) == -1) { - std::cerr << "failed to get interface addresses" << std::endl; - break; - } - - for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { - if (ifa->ifa_addr == NULL) - continue; - - if (ifa->ifa_addr->sa_family == AF_INET) { - auto sin = (struct sockaddr_in *)ifa->ifa_addr; - std::string ip = inet_ntoa(sin->sin_addr); - if (ip == "127.0.0.1") { - addr.sin_addr.s_addr = htonl(INADDR_BROADCAST); - } else { - auto netmask = (struct sockaddr_in *)ifa->ifa_netmask; - std::string netmask_str = inet_ntoa(netmask->sin_addr); - std::string broadcast_str = ""; - for (int i = 0; i < 4; ++i) { - unsigned char b1 = sin->sin_addr.S_un.S_un_b.s_b1; - unsigned char b2 = sin->sin_addr.S_un.S_un_b.s_b2; - unsigned char b3 = sin->sin_addr.S_un.S_un_b.s_b3; - unsigned char b4 = sin->sin_addr.S_un.S_un_b.s_b4; - unsigned char m1 = netmask->sin_addr.S_un.S_un_b.s_b1; - unsigned char m2 = netmask->sin_addr.S_un.S_un_b.s_b2; - unsigned char m3 = netmask->sin_addr.S_un.S_un_b.s_b3; - unsigned char m4 = netmask->sin_addr.S_un.S_un_b.s_b4; - - unsigned char b = ((b1 & m1) | (~m1 & 0xff)) << 24 | - ((b2 & m2) | (~m2 & 0xff)) << 16 | - ((b3 & m3) | (~m3 & 0xff)) << 8 | - ((b4 & m4) | (~m4 & 0xff)); - - std::stringstream ss; - ss << (int)(b >> (i * 8) & 0xff); - broadcast_str += ss.str(); - if (i < 3) - broadcast_str += "."; - } - addr.sin_addr.s_addr = inet_addr(broadcast_str.c_str()); - } - - // Send discovery message - if (sendto(sock, ALPACA_DISCOVERY.c_str(), - ALPACA_DISCOVERY.length(), 0, - (struct sockaddr *)&addr, sizeof(addr)) < 0) { - std::cerr << "failed to send discovery message" - << std::endl; - freeifaddrs(ifaddr); - close(sock); - return addrs; - } - - // Receive response - struct sockaddr_in remote_addr = {0}; - socklen_t addrlen = sizeof(remote_addr); - int len = recvfrom(sock, buf, sizeof(buf), 0, - (struct sockaddr *)&remote_addr, &addrlen); - if (len < 0) { - // Timeout or error occurred - continue; - } - - // Parse response as JSON - std::string data(buf, len); - try { - json j = json::parse(data); - int port = j[ALPACA_RESPONSE]; - std::string ip = inet_ntoa(remote_addr.sin_addr); - std::string addr_str = ip + ":" + std::to_string(port); - if (std::find(addrs.begin(), addrs.end(), addr_str) == - addrs.end()) { - addrs.push_back(addr_str); - } - } catch (std::exception &e) { - std::cerr << "failed to parse response: " << e.what() - << std::endl; - } - } - } - - freeifaddrs(ifaddr); - } - - close(sock); -#ifdef _WIN32 - WSACleanup(); -#endif - return addrs; -} - -std::vector search_ipv6(int numquery = 2, int timeout = 2) { - std::vector addrs; - - int sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); - if (sock < 0) { - std::cerr << "failed to create socket" << std::endl; - return addrs; - } - - struct sockaddr_in6 addr = {0}; - addr.sin6_family = AF_INET6; - addr.sin6_port = htons(PORT); - - char buf[1024]; - for (int i = 0; i < numquery; ++i) { - struct ifaddrs *ifaddr, *ifa; - if (getifaddrs(&ifaddr) == -1) { - std::cerr << "failed to get interface addresses" << std::endl; - break; - } - - for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { - if (ifa->ifa_addr == NULL) - continue; - - if (ifa->ifa_addr->sa_family == AF_INET6) { - auto sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; - if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr) || - IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { - // Skip loopback and link-local addresses - continue; - } - - addr.sin6_addr = sin6->sin6_addr; - - // Send discovery message - if (sendto(sock, ALPACA_DISCOVERY.c_str(), - ALPACA_DISCOVERY.length(), 0, - (struct sockaddr *)&addr, sizeof(addr)) < 0) { - std::cerr << "failed to send discovery message" - << std::endl; - freeifaddrs(ifaddr); - close(sock); - return addrs; - } - - // Receive response - struct sockaddr_in6 remote_addr = {0}; - socklen_t addrlen = sizeof(remote_addr); - int len = recvfrom(sock, buf, sizeof(buf), 0, - (struct sockaddr *)&remote_addr, &addrlen); - if (len < 0) { - // Timeout or error occurred - continue; - } - - // Parse response as JSON - std::string data(buf, len); - try { - json j = json::parse(data); - int port = j[ALPACA_RESPONSE]; - char ip_str[INET6_ADDRSTRLEN]; - inet_ntop(AF_INET6, &remote_addr.sin6_addr, ip_str, - INET6_ADDRSTRLEN); - std::string addr_str = std::string("[") + ip_str + "]" + - ":" + std::to_string(port); - if (std::find(addrs.begin(), addrs.end(), addr_str) == - addrs.end()) { - addrs.push_back(addr_str); - } - } catch (std::exception &e) { - std::cerr << "failed to parse response: " << e.what() - << std::endl; - } - } - } - - freeifaddrs(ifaddr); - } - - close(sock); - return addrs; -} diff --git a/driver/client/atom-alpaca/discovery.hpp b/driver/client/atom-alpaca/discovery.hpp deleted file mode 100644 index 68d7921c..00000000 --- a/driver/client/atom-alpaca/discovery.hpp +++ /dev/null @@ -1,24 +0,0 @@ -/* - * discovery.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 20234-3-1 - -Description: A simple way to discover HTTP server - -**************************************************/ - -#ifndef ATOM_ALPACA_DISCOVERY_HPP -#define ATOM_ALPACA_DISCOVERY_HPP - -#include -#include - -[[nodiscard]] std::vector search_ipv4(int numquery, int timeout); -[[nodiscard]] std::vector search_ipv6(int numquery, int timeout); - -#endif diff --git a/driver/client/atom-alpaca/docenum.cpp b/driver/client/atom-alpaca/docenum.cpp deleted file mode 100644 index 36c46323..00000000 --- a/driver/client/atom-alpaca/docenum.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* - * docenum.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 20234-3-1 - -Description: Enum with documents just like Python - -**************************************************/ - -#include "docenum.hpp" - -void DocIntEnum::addEnum(int value, std::string_view doc) { - enumMap[value] = doc; -} - -std::string_view DocIntEnum::getEnumDoc(int value) const { - const auto it = enumMap.find(value); - if (it != enumMap.end()) { - return it->second; - } - return "No documentation available for the enum value."; -} - -int DocIntEnum::getEnumValue(std::string_view doc) const { - for (const auto &[key, value] : enumMap) { - if (value == doc) { - return key; - } - } - return -1; // Return -1 if no enum value is found for the given - // documentation. -} - -void DocIntEnum::removeEnum(int value) { enumMap.erase(value); } - -void DocIntEnum::clearEnums() { enumMap.clear(); } diff --git a/driver/client/atom-alpaca/docenum.hpp b/driver/client/atom-alpaca/docenum.hpp deleted file mode 100644 index 001017b5..00000000 --- a/driver/client/atom-alpaca/docenum.hpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - * docenum.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 20234-3-1 - -Description: Enum with documents just like Python - -**************************************************/ - -#ifndef ATOM_ALPACA_DOCENUM_HPP -#define ATOM_ALPACA_DOCENUM_HPP - -#include -#include -#include - -class DocIntEnum { -private: - std::map enumMap; - -public: - DocIntEnum() = default; - - void addEnum(int value, std::string_view doc); - - [[nodiscard]] std::string_view getEnumDoc(int value) const; - - [[nodiscard]] int getEnumValue(std::string_view doc) const; - - void removeEnum(int value); - - void clearEnums(); -}; - -#endif diff --git a/driver/client/atom-alpaca/exception.hpp b/driver/client/atom-alpaca/exception.hpp deleted file mode 100644 index d59fbb0b..00000000 --- a/driver/client/atom-alpaca/exception.hpp +++ /dev/null @@ -1,135 +0,0 @@ -/* - * exception.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 20234-3-1 - -Description: the Same Expectations like Alpaca - -**************************************************/ - -#ifndef ATOM_ALPACA_EXCEPTION_HPP -#define ATOM_ALPACA_EXCEPTION_HPP - -#include -#include - -class ActionNotImplementedException : public std::exception { -public: - explicit ActionNotImplementedException(const std::string &message) - : message_(message) {} - - const char *what() const noexcept override { return message_.c_str(); } - -private: - std::string message_; -}; - -class AlpacaRequestException : public std::exception { -public: - explicit AlpacaRequestException(int number, const std::string &message) - : number_(number), message_(message) {} - - const char *what() const noexcept override { return message_.c_str(); } - - int number() const { return number_; } - -private: - int number_; - std::string message_; -}; - -class DriverException : public std::exception { -public: - explicit DriverException(int number, const std::string &message) - : number_(number), message_(message) {} - - const char *what() const noexcept override { return message_.c_str(); } - - int number() const { return number_; } - -private: - int number_; - std::string message_; -}; - -class InvalidOperationException : public std::exception { -public: - explicit InvalidOperationException(const std::string &message) - : message_(message) {} - - const char *what() const noexcept override { return message_.c_str(); } - -private: - std::string message_; -}; - -class InvalidValueException : public std::exception { -public: - explicit InvalidValueException(const std::string &message) - : message_(message) {} - - const char *what() const noexcept override { return message_.c_str(); } - -private: - std::string message_; -}; - -class NotConnectedException : public std::exception { -public: - explicit NotConnectedException(const std::string &message) - : message_(message) {} - - const char *what() const noexcept override { return message_.c_str(); } - -private: - std::string message_; -}; - -class NotImplementedException : public std::exception { -public: - explicit NotImplementedException(const std::string &message) - : message_(message) {} - - const char *what() const noexcept override { return message_.c_str(); } - -private: - std::string message_; -}; - -class ParkedException : public std::exception { -public: - explicit ParkedException(const std::string &message) : message_(message) {} - - const char *what() const noexcept override { return message_.c_str(); } - -private: - std::string message_; -}; - -class SlavedException : public std::exception { -public: - explicit SlavedException(const std::string &message) : message_(message) {} - - const char *what() const noexcept override { return message_.c_str(); } - -private: - std::string message_; -}; - -class ValueNotSetException : public std::exception { -public: - explicit ValueNotSetException(const std::string &message) - : message_(message) {} - - const char *what() const noexcept override { return message_.c_str(); } - -private: - std::string message_; -}; - -#endif diff --git a/driver/client/atom-alpaca/filterwheel.cpp b/driver/client/atom-alpaca/filterwheel.cpp deleted file mode 100644 index 3a3de13c..00000000 --- a/driver/client/atom-alpaca/filterwheel.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "filterwheel.hpp" -#include "exception.hpp" - -Filterwheel::Filterwheel(const std::string &address int device_number, - const std::string &protocol) - : Device(address, "filterwheel", device_number, protocol) {} - -std::vector Filterwheel::get_FocusOffsets() const { - std::vector focus_offsets; - json j = _get("focusoffsets"); - for (auto it = j.begin(); it != j.end(); ++it) { - focus_offsets.push_back(it.value()); - } - return focus_offsets; -} - -std::vector Filterwheel::get_Names() const { - std::vector names; - json j = _get("names"); - for (auto it = j.begin(); it != j.end(); ++it) { - names.push_back(it.value()); - } - return names; -} - -int Filterwheel::get_Positions() const { - int position = -1; - try { - position = _get("position").get(); - } catch (const std::exception &e) { - throw InvalidValueException("position") - } - return position; -} - -void Filterwheel::set_Position(int position) { _put("position", position); } diff --git a/driver/client/atom-alpaca/filterwheel.hpp b/driver/client/atom-alpaca/filterwheel.hpp deleted file mode 100644 index 977f7ea0..00000000 --- a/driver/client/atom-alpaca/filterwheel.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef ATOM_ALPACA_FILTERWHEEL_HPP -#define ATOM_ALPACA_FILTERWHEEL_HPP - -#include "device.hpp" - -class Filterwheel : public Device { -public: - explicit Filterwheel(const std::string &address, int device_number, - const std::string &protocol); - - std::vector get_FocusOffsets() const; - - std::vector get_Names() const; - - int get_Position() const; - - void set_Position(int position); -}; - -#endif diff --git a/driver/client/atom-ascom/CMakeLists.txt b/driver/client/atom-ascom/CMakeLists.txt deleted file mode 100644 index 6777ffd4..00000000 --- a/driver/client/atom-ascom/CMakeLists.txt +++ /dev/null @@ -1,49 +0,0 @@ -cmake_minimum_required(VERSION 3.13) -project(atom-ascom C CXX) - -list(APPEND ${PROJECT_NAME}_SOURCES - camera.cpp - covercalibrator.cpp - device.cpp - filterwheel.cpp - focuser.cpp - telescope.cpp -) - -# Headers -list(APPEND ${PROJECT_NAME}_HEADERS - camera.hpp - covercalibrator.hpp - device.hpp - filterwheel.hpp - focuser.hpp - telescope.hpp -) - -# Build Object Library -add_library(${PROJECT_NAME}_OBJECT OBJECT) -set_property(TARGET ${PROJECT_NAME}_OBJECT PROPERTY POSITION_INDEPENDENT_CODE 1) - -target_sources(${PROJECT_NAME}_OBJECT - PUBLIC - ${${PROJECT_NAME}_HEADERS} - PRIVATE - ${${PROJECT_NAME}_SOURCES} -) - -target_link_libraries(${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) -add_library(${PROJECT_NAME} STATIC) - -target_link_libraries(${PROJECT_NAME} ${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) -target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT}) -target_include_directories(${PROJECT_NAME} PUBLIC .) - -set_target_properties(${PROJECT_NAME} PROPERTIES - VERSION ${CMAKE_HYDROGEN_VERSION_STRING} - SOVERSION ${HYDROGEN_SOVERSION} - OUTPUT_NAME ${PROJECT_NAME} -) - -install(TARGETS ${PROJECT_NAME} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -) diff --git a/driver/client/atom-ascom/camera.cpp b/driver/client/atom-ascom/camera.cpp deleted file mode 100644 index 2edbb537..00000000 --- a/driver/client/atom-ascom/camera.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "ascom_camera.hpp" - -#include "atom/log/loguru.hpp" - -ASCOMCamera::ASCOMCamera(const std::string &name) - : Device(name), Camera(name), ASCOMDevice(name) { - DLOG_F(INFO, "ASCOMCamera Simulator Loaded : %s", name.c_str()); -} - -ASCOMCamera::~ASCOMCamera() { - DLOG_F(INFO, "ASCOMCamera Simulator Destructed"); -} - -bool ASCOMCamera::connect(const nlohmann::json ¶ms) { - return ASCOMDevice::connect(params); -} - -bool ASCOMCamera::disconnect(const nlohmann::json ¶ms) { - return ASCOMDevice::disconnect(params); -} - -bool ASCOMCamera::reconnect(const nlohmann::json ¶ms) { - return ASCOMDevice::reconnect(params); -} - -bool ASCOMCamera::startExposure(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::abortExposure(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::getExposureStatus(const nlohmann::json ¶ms) { - return true; -} - -bool ASCOMCamera::getExposureResult(const nlohmann::json ¶ms) { - return true; -} - -bool ASCOMCamera::saveExposureResult(const nlohmann::json ¶ms) { - return true; -} - -bool ASCOMCamera::startVideo(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::stopVideo(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::getVideoStatus(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::getVideoResult(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::saveVideoResult(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::startCooling(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::stopCooling(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::getTemperature(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::getCoolingPower(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::setTemperature(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::setCoolingPower(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::getGain(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::setGain(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::getOffset(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::setOffset(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::getISO(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::setISO(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::getFrame(const nlohmann::json ¶ms) { return true; } - -bool ASCOMCamera::setFrame(const nlohmann::json ¶ms) { return true; } diff --git a/driver/client/atom-ascom/camera.hpp b/driver/client/atom-ascom/camera.hpp deleted file mode 100644 index 6542ce65..00000000 --- a/driver/client/atom-ascom/camera.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include "ascom_device.hpp" -#include "core/camera.hpp" - -class ASCOMCamera : public Camera, public ASCOMDevice { -public: - explicit ASCOMCamera(const std::string &name); - ~ASCOMCamera(); - -public: - virtual bool connect(const nlohmann::json ¶ms) override; - virtual bool disconnect(const nlohmann::json ¶ms) override; - virtual bool reconnect(const nlohmann::json ¶ms) override; - -public: - virtual bool startExposure(const nlohmann::json ¶ms) override; - virtual bool abortExposure(const nlohmann::json ¶ms) override; - virtual bool getExposureStatus(const nlohmann::json ¶ms) override; - virtual bool getExposureResult(const nlohmann::json ¶ms) override; - virtual bool saveExposureResult(const nlohmann::json ¶ms) override; - virtual bool startVideo(const nlohmann::json ¶ms) override; - virtual bool stopVideo(const nlohmann::json ¶ms) override; - virtual bool getVideoStatus(const nlohmann::json ¶ms) override; - virtual bool getVideoResult(const nlohmann::json ¶ms) override; - virtual bool saveVideoResult(const nlohmann::json ¶ms) override; - virtual bool startCooling(const nlohmann::json ¶ms) override; - virtual bool stopCooling(const nlohmann::json ¶ms) override; - virtual bool getTemperature(const nlohmann::json ¶ms) override; - virtual bool getCoolingPower(const nlohmann::json ¶ms) override; - virtual bool setTemperature(const nlohmann::json ¶ms) override; - virtual bool setCoolingPower(const nlohmann::json ¶ms) override; - virtual bool getGain(const nlohmann::json ¶ms) override; - virtual bool setGain(const nlohmann::json ¶ms) override; - virtual bool getOffset(const nlohmann::json ¶ms) override; - virtual bool setOffset(const nlohmann::json ¶ms) override; - virtual bool getISO(const nlohmann::json ¶ms) override; - virtual bool setISO(const nlohmann::json ¶ms) override; - virtual bool getFrame(const nlohmann::json ¶ms) override; - virtual bool setFrame(const nlohmann::json ¶ms) override; - -public: -}; diff --git a/driver/client/atom-ascom/comdispatch.cpp b/driver/client/atom-ascom/comdispatch.cpp deleted file mode 100644 index 7168784f..00000000 --- a/driver/client/atom-ascom/comdispatch.cpp +++ /dev/null @@ -1,378 +0,0 @@ -/* - * comdispatch.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 20234-3-1 - -Description: Dispatch ASCOM command to COM - -**************************************************/ - -#include "comdispatch.h" -#include - -#include "atom/log/loguru.hpp" - -std::string ExcepMsg(const EXCEPINFO &excep) { - if (excep.bstrSource || excep.bstrDescription) - return std::string::Format("(%s) %s", excep.bstrSource, - excep.bstrDescription); - else - return _( - "A COM Error occurred. There may be more info in the Debug Log."); -} - -std::string ExcepMsg(const std::string &prefix, const EXCEPINFO &excep) { - return prefix + ":\n" + ExcepMsg(excep); -} - -ExcepInfo::ExcepInfo() { memset(this, 0, sizeof(*this)); } - -inline static void FreeExcep(EXCEPINFO &ex) { - if (ex.bstrSource) - SysFreeString(ex.bstrSource); - if (ex.bstrDescription) - SysFreeString(ex.bstrDescription); - if (ex.bstrHelpFile) - SysFreeString(ex.bstrHelpFile); -} - -void ExcepInfo::Assign(const _com_error &err, const std::string &source) { - FreeExcep(*this); - - wCode = 0; - wReserved = 0; - bstrSource = SysAllocString(source.wc_str()); - bstrDescription = SysAllocString(err.ErrorMessage()); - bstrHelpFile = nullptr; - dwHelpContext = 0; - pvReserved = nullptr; - pfnDeferredFillIn = nullptr; - scode = err.Error(); -} - -void ExcepInfo::Assign(HRESULT hr, const std::string &source) { - Assign(_com_error(hr), source); -} - -ExcepInfo::~ExcepInfo() { FreeExcep(*this); } - -bool DispatchClass::dispid(DISPID *ret, IDispatch *idisp, OLECHAR *wname, - ExcepInfo *excep) { - HRESULT hr = - idisp->GetIDsOfNames(IID_NULL, &wname, 1, LOCALE_USER_DEFAULT, ret); - if (FAILED(hr)) { - _com_error err(hr); - Debug.AddLine(std::string::Format("dispid(%s): [%x] %s", wname, hr, - err.ErrorMessage())); - excep->Assign(err, std::string::Format( - _("Driver error preparing to call %s"), wname)); - } - return SUCCEEDED(hr); -} - -bool DispatchClass::dispid_cached(DISPID *ret, IDispatch *idisp, OLECHAR *wname, - ExcepInfo *excep) { - std::string name(wname); - - idmap_t::const_iterator it = m_idmap.find(name); - if (it != m_idmap.end()) { - *ret = it->second; - return true; - } - - if (!dispid(ret, idisp, wname, excep)) - return false; - - m_idmap[name] = *ret; - return true; -} - -DispatchObj::DispatchObj() : m_class(0), m_idisp(0) {} - -DispatchObj::DispatchObj(DispatchClass *cls) : m_class(cls), m_idisp(0) {} - -DispatchObj::DispatchObj(IDispatch *idisp, DispatchClass *cls) - : m_class(cls), m_idisp(idisp) { - if (m_idisp) - m_idisp->AddRef(); -} - -DispatchObj::~DispatchObj() { - if (m_idisp) - m_idisp->Release(); -} - -void DispatchObj::Attach(IDispatch *idisp, DispatchClass *cls) { - m_class = cls; - if (m_idisp) - m_idisp->Release(); - m_idisp = idisp; -} - -bool DispatchObj::Create(OLECHAR *progid) { - CLSID clsid; - if (FAILED(CLSIDFromProgID(progid, &clsid))) - return false; - IDispatch *idisp; - HRESULT hr; - if (FAILED(hr = CoCreateInstance(clsid, NULL, CLSCTX_SERVER, IID_IDispatch, - (LPVOID *)&idisp))) { - Debug.AddLine(std::string::Format("CoCreateInstance: [%x] %s", hr, - _com_error(hr).ErrorMessage())); - return false; - } - m_idisp = idisp; - return true; -} - -bool DispatchObj::GetDispatchId(DISPID *ret, OLECHAR *name) { - if (m_class) - return m_class->dispid_cached(ret, m_idisp, name, &m_excep); - else - return DispatchClass::dispid(ret, m_idisp, name, &m_excep); -} - -bool DispatchObj::GetProp(Variant *res, DISPID dispid) { - DISPPARAMS dispParms; - dispParms.cArgs = 0; - dispParms.rgvarg = NULL; - dispParms.cNamedArgs = 0; - dispParms.rgdispidNamedArgs = NULL; - HRESULT hr = - m_idisp->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, - DISPATCH_PROPERTYGET, &dispParms, res, &m_excep, NULL); - if (FAILED(hr)) - Debug.AddLine(std::string::Format("invoke: [%x] %s", hr, - _com_error(hr).ErrorMessage())); - return SUCCEEDED(hr); -} - -bool DispatchObj::GetProp(Variant *res, OLECHAR *name) { - DISPID dispid; - if (!GetDispatchId(&dispid, name)) - return false; - - return GetProp(res, dispid); -} - -bool DispatchObj::GetProp(Variant *res, OLECHAR *name, int arg) { - DISPID dispid; - if (!GetDispatchId(&dispid, name)) - return false; - - VARIANTARG rgvarg[1]; - rgvarg[0].vt = VT_INT; - rgvarg[0].intVal = arg; - DISPPARAMS dispParms; - dispParms.cArgs = 1; - dispParms.rgvarg = rgvarg; - dispParms.cNamedArgs = 0; - dispParms.rgdispidNamedArgs = NULL; - HRESULT hr = - m_idisp->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, - DISPATCH_PROPERTYGET, &dispParms, res, &m_excep, NULL); - - if (FAILED(hr)) - Debug.AddLine(std::string::Format("getprop: [%x] %s", hr, - _com_error(hr).ErrorMessage())); - - return SUCCEEDED(hr); -} - -bool DispatchObj::PutProp(OLECHAR *name, OLECHAR *val) { - DISPID dispid; - if (!GetDispatchId(&dispid, name)) - return false; - - BSTR bs = SysAllocString(val); - VARIANTARG rgvarg[1]; - rgvarg[0].vt = VT_BSTR; - rgvarg[0].bstrVal = bs; - DISPID dispidNamed = DISPID_PROPERTYPUT; - DISPPARAMS dispParms; - dispParms.cArgs = 1; - dispParms.rgvarg = rgvarg; - dispParms.cNamedArgs = 1; - dispParms.rgdispidNamedArgs = &dispidNamed; - Variant res; - HRESULT hr = - m_idisp->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, - DISPATCH_PROPERTYPUT, &dispParms, &res, &m_excep, NULL); - SysFreeString(bs); - - if (FAILED(hr)) - Debug.AddLine(std::string::Format("putprop: [%x] %s", hr, - _com_error(hr).ErrorMessage())); - - return SUCCEEDED(hr); -} - -bool DispatchObj::PutProp(DISPID dispid, bool val) { - VARIANTARG rgvarg[1]; - rgvarg[0].vt = VT_BOOL; - rgvarg[0].boolVal = val ? VARIANT_TRUE : VARIANT_FALSE; - DISPID dispidNamed = DISPID_PROPERTYPUT; - DISPPARAMS dispParms; - dispParms.cArgs = 1; - dispParms.rgvarg = rgvarg; - dispParms.cNamedArgs = 1; - dispParms.rgdispidNamedArgs = &dispidNamed; - Variant res; - HRESULT hr = - m_idisp->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, - DISPATCH_PROPERTYPUT, &dispParms, &res, &m_excep, NULL); - if (FAILED(hr)) - Debug.AddLine(std::string::Format("putprop: [%x] %s", hr, - _com_error(hr).ErrorMessage())); - return SUCCEEDED(hr); -} - -bool DispatchObj::PutProp(DISPID dispid, double val) { - VARIANTARG rgvarg[1]; - rgvarg[0].vt = VT_R8; - rgvarg[0].dblVal = val; - DISPID dispidNamed = DISPID_PROPERTYPUT; - DISPPARAMS dispParms; - dispParms.cArgs = 1; - dispParms.rgvarg = rgvarg; - dispParms.cNamedArgs = 1; - dispParms.rgdispidNamedArgs = &dispidNamed; - Variant res; - HRESULT hr = - m_idisp->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, - DISPATCH_PROPERTYPUT, &dispParms, &res, &m_excep, NULL); - if (FAILED(hr)) - Debug.AddLine(std::string::Format("putprop: [%x] %s", hr, - _com_error(hr).ErrorMessage())); - return SUCCEEDED(hr); -} - -bool DispatchObj::PutProp(OLECHAR *name, bool val) { - DISPID dispid; - if (!GetDispatchId(&dispid, name)) - return false; - return PutProp(dispid, val); -} - -bool DispatchObj::InvokeMethod(Variant *res, OLECHAR *name, OLECHAR *arg) { - DISPID dispid; - if (!GetDispatchId(&dispid, name)) - return false; - - BSTR bs = SysAllocString(arg); - VARIANTARG rgvarg[1]; - rgvarg[0].vt = VT_BSTR; - rgvarg[0].bstrVal = bs; - DISPPARAMS dispParms; - dispParms.cArgs = 1; - dispParms.rgvarg = rgvarg; - dispParms.cNamedArgs = 0; - dispParms.rgdispidNamedArgs = NULL; - HRESULT hr = - m_idisp->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, - &dispParms, res, &m_excep, NULL); - SysFreeString(bs); - if (FAILED(hr)) - Debug.AddLine(std::string::Format("invoke(%s): [%x] %s", name, hr, - _com_error(hr).ErrorMessage())); - return SUCCEEDED(hr); -} - -bool DispatchObj::InvokeMethod(Variant *res, DISPID dispid, double arg1, - double arg2) { - VARIANTARG rgvarg[2]; - rgvarg[0].vt = VT_R8; - rgvarg[0].dblVal = arg2; - rgvarg[1].vt = VT_R8; - rgvarg[1].dblVal = arg1; - DISPPARAMS dispParms; - dispParms.cArgs = 2; - dispParms.rgvarg = rgvarg; - dispParms.cNamedArgs = 0; - dispParms.rgdispidNamedArgs = NULL; - HRESULT hr = - m_idisp->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, - &dispParms, res, &m_excep, NULL); - if (FAILED(hr)) - Debug.AddLine(std::string::Format("invoke: [%x] %s", hr, - _com_error(hr).ErrorMessage())); - return SUCCEEDED(hr); -} - -bool DispatchObj::InvokeMethod(Variant *res, OLECHAR *name, double arg1, - double arg2) { - DISPID dispid; - if (!GetDispatchId(&dispid, name)) - return false; - return InvokeMethod(res, dispid, arg1, arg2); -} - -bool DispatchObj::InvokeMethod(Variant *res, DISPID dispid) { - DISPPARAMS dispParms; - dispParms.cArgs = 0; - dispParms.rgvarg = NULL; - dispParms.cNamedArgs = 0; - dispParms.rgdispidNamedArgs = NULL; - HRESULT hr = - m_idisp->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, - &dispParms, res, &m_excep, NULL); - if (FAILED(hr)) - Debug.AddLine(std::string::Format("invoke: [%x] %s", hr, - _com_error(hr).ErrorMessage())); - return SUCCEEDED(hr); -} - -bool DispatchObj::InvokeMethod(Variant *res, OLECHAR *name) { - DISPID dispid; - if (!GetDispatchId(&dispid, name)) - return false; - return InvokeMethod(res, dispid); -} - -GITEntry::GITEntry() : m_pIGlobalInterfaceTable(0), m_dwCookie(0) {} - -GITEntry::~GITEntry() { Unregister(); } - -void GITEntry::Register(IDispatch *idisp) { - if (!m_pIGlobalInterfaceTable) { - // first find the global table - HRESULT hr; - if (FAILED(hr = ::CoCreateInstance( - CLSID_StdGlobalInterfaceTable, NULL, - CLSCTX_INPROC_SERVER, IID_IGlobalInterfaceTable, - (void **)&m_pIGlobalInterfaceTable))) { - Debug.AddLine( - std::string::Format("create global interface table: [%x] %s", - hr, _com_error(hr).ErrorMessage())); - throw ERROR_INFO( - "Cannot CoCreateInstance of Global Interface Table"); - } - } - - // add the Interface to the global table. Any errors past this point need to - // remove the interface from the global table. - HRESULT hr; - if (FAILED(hr = m_pIGlobalInterfaceTable->RegisterInterfaceInGlobal( - idisp, IID_IDispatch, &m_dwCookie))) { - Debug.AddLine( - std::string::Format("register in global interface table: [%x] %s", - hr, _com_error(hr).ErrorMessage())); - throw ERROR_INFO("Cannot register object in Global Interface Table"); - } -} - -void GITEntry::Unregister() { - if (m_pIGlobalInterfaceTable) { - if (m_dwCookie) { - m_pIGlobalInterfaceTable->RevokeInterfaceFromGlobal(m_dwCookie); - m_dwCookie = 0; - } - m_pIGlobalInterfaceTable->Release(); - m_pIGlobalInterfaceTable = 0; - } -} diff --git a/driver/client/atom-ascom/comdispatch.hpp b/driver/client/atom-ascom/comdispatch.hpp deleted file mode 100644 index e315a504..00000000 --- a/driver/client/atom-ascom/comdispatch.hpp +++ /dev/null @@ -1,109 +0,0 @@ -/* - * comdispatch.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 20234-3-1 - -Description: Dispatch ASCOM command to COM - -**************************************************/ - -#ifndef ATOM_ASCOM_COMDISPATCH_HPP -#define ATOM_ASCOM_COMDISPATCH_HPP - -#include -#include - -class _com_error; - -std::string ExcepMsg(const EXCEPINFO &excep); -std::string ExcepMsg(const std::string &prefix, const EXCEPINFO &excep); - -struct Variant : public VARIANT { - Variant() { VariantInit(this); } -}; - -class ExcepInfo : public EXCEPINFO { - ExcepInfo &operator=(const ExcepInfo &rhs) = delete; - -public: - ExcepInfo(); - ~ExcepInfo(); - void Assign(HRESULT hr, const std::string &source); - void Assign(const _com_error &err, const std::string &source); -}; - -typedef Variant VariantArg; - -class DispatchClass { - typedef std::map idmap_t; - idmap_t m_idmap; - -public: - DispatchClass() {} - ~DispatchClass() {} - static bool dispid(DISPID *ret, IDispatch *idisp, OLECHAR *name, - ExcepInfo *excep); - bool dispid_cached(DISPID *ret, IDispatch *idisp, OLECHAR *name, - ExcepInfo *excep); -}; - -class DispatchObj { - DispatchClass *m_class; - IDispatch *m_idisp; - ExcepInfo m_excep; - -public: - DispatchObj(); - DispatchObj(DispatchClass *cls); - DispatchObj(IDispatch *idisp, DispatchClass *cls); - ~DispatchObj(); - void Attach(IDispatch *idisp, DispatchClass *cls); - bool Create(OLECHAR *progid); - bool GetDispatchId(DISPID *ret, OLECHAR *name); - bool GetProp(Variant *res, DISPID dispid); - bool GetProp(Variant *res, OLECHAR *name); - bool GetProp(Variant *res, OLECHAR *name, int arg); - bool PutProp(OLECHAR *name, OLECHAR *val); - bool PutProp(DISPID dispid, bool val); - bool PutProp(DISPID dispid, double val); - bool PutProp(OLECHAR *name, bool val); - bool InvokeMethod(Variant *res, OLECHAR *name); - bool InvokeMethod(Variant *res, OLECHAR *name, OLECHAR *arg); - bool InvokeMethod(Variant *res, OLECHAR *name, double arg1, double arg2); - bool InvokeMethod(Variant *res, DISPID dispid, double arg1, double arg2); - bool InvokeMethod(Variant *res, DISPID dispid); - const EXCEPINFO &Excep() const { return m_excep; } - IDispatch *IDisp() const { return m_idisp; } -}; - -// IGlobalInterfaceTable wrapper -class GITEntry { - IGlobalInterfaceTable *m_pIGlobalInterfaceTable; - DWORD m_dwCookie; - -public: - GITEntry(); - ~GITEntry(); - void Register(IDispatch *idisp); - void Register(const DispatchObj &obj) { Register(obj.IDisp()); } - void Unregister(); - bool IsRegistered() const { return m_dwCookie != 0; } - IDispatch *Get() const { - IDispatch *idisp = 0; - if (m_dwCookie) - m_pIGlobalInterfaceTable->GetInterfaceFromGlobal( - m_dwCookie, IID_IDispatch, (LPVOID *)&idisp); - return idisp; - } -}; - -struct GITObjRef : public DispatchObj { - GITObjRef(const GITEntry &gitentry) { Attach(gitentry.Get(), 0); } -}; - -#endif diff --git a/driver/client/atom-ascom/telescope.cpp b/driver/client/atom-ascom/telescope.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/driver/client/atom-ascom/telescope.hpp b/driver/client/atom-ascom/telescope.hpp deleted file mode 100644 index e69de29b..00000000 diff --git a/driver/client/atom-hydrogen/CMakeLists.txt b/driver/client/atom-hydrogen/CMakeLists.txt deleted file mode 100644 index 767cb379..00000000 --- a/driver/client/atom-hydrogen/CMakeLists.txt +++ /dev/null @@ -1,54 +0,0 @@ -cmake_minimum_required(VERSION 3.13) -project(atom-hydrogen C CXX) - -include_directories(${CMAKE_SOURCE_DIR}/modules/hydrogen_core) -include_directories(${CMAKE_SOURCE_DIR}/modules/hydrogen_core/base) -include_directories(${CMAKE_SOURCE_DIR}/modules/hydrogen_core/property) -include_directories(${CMAKE_SOURCE_DIR}/modules/hydrogen_core/util) - -list(APPEND ${PROJECT_NAME}_SOURCES - hydrogencamera.cpp - hydrogendome.cpp - hydrogenfilterwheel.cpp - hydrogenfocuser.cpp - hydrogentelescope.cpp -) - -# Headers -list(APPEND ${PROJECT_NAME}_HEADERS - hydrogencamera.hpp - hydrogenbasic.hpp - hydrogendome.hpp - hydrogenfilterwheel.hpp - hydrogenfocuser.hpp - hydrogentelescope.hpp -) - -# Build Object Library -add_library(${PROJECT_NAME}_OBJECT OBJECT) -set_property(TARGET ${PROJECT_NAME}_OBJECT PROPERTY POSITION_INDEPENDENT_CODE 1) - -target_sources(${PROJECT_NAME}_OBJECT - PUBLIC - ${${PROJECT_NAME}_HEADERS} - PRIVATE - ${${PROJECT_NAME}_SOURCES} -) - -target_link_libraries(${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) -target_link_libraries(${PROJECT_NAME}_OBJECT atom-driver hydrogenclient) -add_library(${PROJECT_NAME} STATIC) - -target_link_libraries(${PROJECT_NAME} ${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) -target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT}) -target_include_directories(${PROJECT_NAME} PUBLIC .) - -set_target_properties(${PROJECT_NAME} PROPERTIES - VERSION ${CMAKE_HYDROGEN_VERSION_STRING} - SOVERSION ${HYDROGEN_SOVERSION} - OUTPUT_NAME ${PROJECT_NAME} -) - -install(TARGETS ${PROJECT_NAME} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -) diff --git a/driver/client/atom-hydrogen/hydrogenbasic.hpp b/driver/client/atom-hydrogen/hydrogenbasic.hpp deleted file mode 100644 index 1a8e2691..00000000 --- a/driver/client/atom-hydrogen/hydrogenbasic.hpp +++ /dev/null @@ -1,9 +0,0 @@ -#ifdef NATIVE_HYDROGEN -#include -#include -#else -#include "hydrogen_client/baseclient.h" -#include "hydrogen_core/base/basedevice.h" -#include "hydrogen_core/property/hydrogenproperty.h" -#include "hydrogen_core/property/hydrogenproperties.h" -#endif diff --git a/driver/client/atom-hydrogen/hydrogencamera.cpp b/driver/client/atom-hydrogen/hydrogencamera.cpp deleted file mode 100644 index a4603572..00000000 --- a/driver/client/atom-hydrogen/hydrogencamera.cpp +++ /dev/null @@ -1,559 +0,0 @@ -/* - * hydrogencamera.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-4-9 - -Description: Hydorgen Camera - -**************************************************/ - -#include "hydrogencamera.hpp" - -#include "config.h" - -#include "atom/log/loguru.hpp" - -HydrogenCamera::HydrogenCamera(const std::string &name) : Camera(name) { - DLOG_F(INFO, "Hydorgen camera {} init successfully", name); - m_number_switch = std::make_unique< - atom::utils::StringSwitch>(); - m_switch_switch = std::make_unique< - atom::utils::StringSwitch>(); - m_text_switch = std::make_unique< - atom::utils::StringSwitch>(); - - registerFunc("connect", &HydrogenCamera::connect, this); - registerFunc("disconnect", &HydrogenCamera::disconnect, this); - registerFunc("reconnect", &HydrogenCamera::reconnect, this); - registerFunc("isConnected", &HydrogenCamera::isConnected, this); - registerFunc("startExposure", &HydrogenCamera::startExposure, this); - registerFunc("abortExposure", &HydrogenCamera::abortExposure, this); - registerFunc("getExposureStatus", &HydrogenCamera::getExposureStatus, this); - registerFunc("getExposureResult", &HydrogenCamera::getExposureResult, this); - -} - -HydrogenCamera::~HydrogenCamera() {} - -bool HydrogenCamera::connect(const json ¶ms) { - std::string name = params["name"]; - std::string hostname = params["host"]; - int port = params["port"]; - DLOG_F(INFO, "Trying to connect to {}", name); - setServer(hostname.c_str(), port); - // Receive messages only for our camera. - watchDevice(name.c_str()); - // Connect to server. - if (connectServer()) { - DLOG_F(INFO, "{}: connectServer done ready", GetName()); - connectDevice(name.c_str()); - return !is_ready.load(); - } - return false; -} - -bool HydrogenCamera::disconnect(const json ¶ms) { - DLOG_F(INFO, "%s is disconnected", GetName()); - return true; -} - -bool HydrogenCamera::reconnect(const json ¶ms) { return true; } - -bool HydrogenCamera::isConnected() { return true; } - -bool HydrogenCamera::startExposure(const json ¶ms) { return true; } - -bool HydrogenCamera::abortExposure(const json ¶ms) { return true; } - -bool HydrogenCamera::getExposureStatus(const json ¶ms) { return true; } - -bool HydrogenCamera::getExposureResult(const json ¶ms) { return true; } - -bool HydrogenCamera::saveExposureResult(const json ¶ms) { return true; } - -bool HydrogenCamera::startVideo(const json ¶ms) { return true; } - -bool HydrogenCamera::stopVideo(const json ¶ms) { return true; } - -bool HydrogenCamera::getVideoStatus(const json ¶ms) { return true; } - -bool HydrogenCamera::getVideoResult(const json ¶ms) { return true; } - -bool HydrogenCamera::saveVideoResult(const json ¶ms) { return true; } - -bool HydrogenCamera::startCooling(const json ¶ms) { return true; } - -bool HydrogenCamera::stopCooling(const json ¶ms) { return true; } - -bool HydrogenCamera::isCoolingAvailable() { return true; } - -bool HydrogenCamera::getTemperature(const json ¶ms) { return true; } - -bool HydrogenCamera::getCoolingPower(const json ¶ms) { return true; } - -bool HydrogenCamera::setTemperature(const json ¶ms) { return true; } - -bool HydrogenCamera::setCoolingPower(const json ¶ms) { return true; } - -bool HydrogenCamera::getGain(const json ¶ms) { return true; } - -bool HydrogenCamera::setGain(const json ¶ms) { return true; } - -bool HydrogenCamera::isGainAvailable() { return true; } - -bool HydrogenCamera::getOffset(const json ¶ms) { return true; } - -bool HydrogenCamera::setOffset(const json ¶ms) { return true; } - -bool HydrogenCamera::isOffsetAvailable() { return true; } - -bool HydrogenCamera::getISO(const json ¶ms) { return true; } - -bool HydrogenCamera::setISO(const json ¶ms) { return true; } - -bool HydrogenCamera::isISOAvailable() { return true; } - -bool HydrogenCamera::getFrame(const json ¶ms) { return true; } - -bool HydrogenCamera::setFrame(const json ¶ms) { return true; } - -bool HydrogenCamera::isFrameSettingAvailable() { return true; } - -void HydrogenCamera::newDevice(HYDROGEN::BaseDevice dp) { - if (strcmp(dp.getDeviceName(), GetName().c_str()) == 0) { - camera_device = dp; - } -} - -void HydrogenCamera::removeDevice(HYDROGEN::BaseDevice dp) { - ClearStatus(); - DLOG_F(INFO, "{} disconnected", dp.getDeviceName()); -} - -void HydrogenCamera::newProperty(HYDROGEN::Property property) { - std::string PropName(property.getName()); - HYDROGEN_PROPERTY_TYPE Proptype = property.getType(); - - switch (Proptype) { - case HYDROGEN_SWITCH: - newSwitch(property.getSwitch()); - break; - case HYDROGEN_NUMBER: - newNumber(property.getNumber()); - break; - case HYDROGEN_TEXT: - newText(property.getText()); - break; - case HYDROGEN_BLOB: - newBLOB(property.getBLOB()); - break; - }; -} - -void HydrogenCamera::updateProperty(HYDROGEN::Property property) { - // we go here every time a Switch state change - - switch (property.getType()) { - case HYDROGEN_SWITCH: { - auto svp = property.getSwitch(); - DLOG_F(INFO, "{}: {}", GetName(), svp->name); - newSwitch(svp); - } break; - case HYDROGEN_NUMBER: { - auto nvp = property.getNumber(); - DLOG_F(INFO, "{}: {}", GetName(), nvp->name); - newNumber(nvp); - } break; - case HYDROGEN_TEXT: { - auto tvp = property.getText(); - DLOG_F(INFO, "{}: {}", GetName(), tvp->name); - newText(tvp); - } break; - case HYDROGEN_BLOB: { - // we go here every time a new blob is available - // this is normally the image from the camera - - auto bvp = property.getBLOB(); - auto bp = bvp->at(0); - newBLOB(bvp); - } break; - default: - break; - } -} - -void HydrogenCamera::newSwitch(HYDROGEN::PropertyViewSwitch *svp) { - std::string name = svp->name; - - DLOG_F(INFO, "{} Received Switch: {}", GetName(), name); - - if (name == "CONNECTION") { - m_connection_prop.reset(svp); - if (auto connectswitch = IUFindSwitch(svp, "CONNECT"); - connectswitch->s == ISS_ON) { - SetVariable("connect", true); - is_connected.store(true); - DLOG_F(INFO, "{} is connected", GetName()); - } else { - if (is_ready.load()) { - ClearStatus(); - SetVariable("connect", false); - is_connected.store(true); - DLOG_F(INFO, "{} is disconnected", GetName()); - } - } - } else if (name == "DEBUG") { - debug_prop.reset(svp); - if (auto debugswitch = IUFindSwitch(svp, "ENABLE"); - debugswitch->s == ISS_ON) { - SetVariable("debug", true); - is_debug.store(true); - DLOG_F(INFO, "DEBUG mode of {} is enabled", GetName()); - } else { - SetVariable("debug", false); - is_debug.store(false); - DLOG_F(INFO, "DEBUG mode of {} is disabled", GetName()); - } - } else if (name == "CCD_FRAME_TYPE") { - frame_type_prop.reset(svp); - std::string type; - if (auto lightswitch = IUFindSwitch(svp, "FRAME_LIGHT"); - lightswitch->s == ISS_ON) - type = "Light"; - else if (auto darkswitch = IUFindSwitch(svp, "FRAME_DARK"); - darkswitch->s == ISS_ON) - type = "Dark"; - else if (auto flatswitch = IUFindSwitch(svp, "FRAME_FLAT"); - flatswitch->s == ISS_ON) - type = "Flat"; - else if (auto biasswitch = IUFindSwitch(svp, "FRAME_BIAS"); - biasswitch->s == ISS_ON) - type = "Bias"; - SetVariable("frame_type", type); - frame.frame_type = type; - DLOG_F(INFO, "Current frame type of {} is {}", GetName(), - frame.frame_type); - } else if (name == "CCD_TRANSFER_FORMAT") { - frame_format_prop.reset(svp); - std::string format; - if (auto fitsswitch = IUFindSwitch(svp, "FORMAT_FITS"); - fitsswitch->s == ISS_ON) - format = "Fits"; - else if (auto natswitch = IUFindSwitch(svp, "FORMAT_NATIVE"); - natswitch->s == ISS_ON) - format = "Raw"; - else if (auto xisfswitch = IUFindSwitch(svp, "FORMAT_XISF"); - xisfswitch->s == ISS_ON) - format = "Xisf"; - SetVariable("frame_format", format); - frame.frame_format = format; - DLOG_F(INFO, "Current frame format of {} is {}", GetName(), - frame.frame_format); - } else if (name == "CCD_ABORT_EXPOSURE") { - abort_exposure_prop.reset(svp); - if (auto abortswitch = IUFindSwitch(svp, "ABORT_EXPOSURE"); - abortswitch->s == ISS_ON) { - SetVariable("is_exposure", false); - is_exposure.store(false); - DLOG_F(INFO, "{} is stopped", GetName()); - } - } else if (name == "UPLOAD_MODE") { - image_upload_mode_prop.reset(svp); - std::string mode; - if (auto clientswitch = IUFindSwitch(svp, "UPLOAD_CLIENT"); - clientswitch->s == ISS_ON) - mode = "Client"; - else if (auto localswitch = IUFindSwitch(svp, "UPLOAD_LOCAL"); - localswitch->s == ISS_ON) - mode = "Local"; - else if (auto bothswitch = IUFindSwitch(svp, "UPLOAD_BOTH"); - bothswitch->s == ISS_ON) - mode = "Both"; - frame.upload_mode = mode; - DLOG_F(INFO, "Current upload mode of {} is {}", GetName(), - frame.upload_mode); - } else if (name == "CCD_FAST_TOGGLE") { - fast_read_out_prop.reset(svp); - if (auto enabledswitch = IUFindSwitch(svp, "HYDROGEN_ENABLED"); - enabledswitch->s == ISS_ON) { - SetVariable("is_fastread", true); - frame.is_fastread.store(true); - DLOG_F(INFO, "Current fast readout mode of {} is enabled", - GetName()); - } else if (auto disabledswitch = IUFindSwitch(svp, "HYDROGEN_DISABLED"); - disabledswitch->s == ISS_ON) { - SetVariable("is_fastread", false); - frame.is_fastread.store(false); - DLOG_F(INFO, "Current fast readout mode of {} is disabled", - GetName()); - } - } else if (name == "CCD_VIDEO_STREAM") { - video_prop.reset(svp); - if (auto onswitch = IUFindSwitch(svp, "STREAM_ON"); - onswitch->s == ISS_ON) { - SetVariable("is_video", true); - is_video.store(true); - DLOG_F(INFO, "{} start video capture", GetName()); - } else if (auto offswitch = IUFindSwitch(svp, "STREAM_OFF"); - offswitch->s == ISS_ON) { - SetVariable("is_video", false); - is_video.store(false); - DLOG_F(INFO, "{} stop video capture", GetName()); - } - } else if (name == "FLIP") { - } else if (name == "CCD_COMPRESSION") { - } - - else if (name == "CCD_CONTROLS") { - } else if (name == "CCD_CONTROLS_MODE") { - } - // The following properties are for Toup Camera - else if (name == "TC_FAN_CONTROL") { - } else if (name == "TC_FAN_Speed") { - } else if (name == "TC_AUTO_WB") { - } else if (name == "TC_HEAT_CONTROL") { - } else if (name == "TC_HCG_CONTROL") { - } else if (name == "TC_LOW_NOISE_CONTROL") { - } - /* - else if (PropName == "CCD_BINNING_MODE" && Proptype == HYDROGEN_SWITCH) - { - } - */ -} - -void HydrogenCamera::newMessage(HYDROGEN::BaseDevice dp, int messageID) { - DLOG_F(INFO, "{} Received message: {}", GetName(), - dp.messageQueue(messageID)); -} - -void HydrogenCamera::serverConnected() { - DLOG_F(INFO, "{} Connected to server", GetName()); -} - -void HydrogenCamera::serverDisconnected(int exit_code) { - DLOG_F(INFO, "{} Disconnected from server", GetName()); - - ClearStatus(); -} - -inline static const char *StateStr(IPState st) { - switch (st) { - default: - case IPS_IDLE: - return "Idle"; - case IPS_OK: - return "Ok"; - case IPS_BUSY: - return "Busy"; - case IPS_ALERT: - return "Alert"; - } -} - -void HydrogenCamera::newNumber(HYDROGEN::PropertyViewNumber *nvp) { - using namespace std::string_literals; - const std::string name = nvp->name; - if (name == "CCD_EXPOSURE") { - const double exposure = nvp->np->value; - current_exposure.store(exposure); - DLOG_F(INFO, "Current CCD_EXPOSURE for {} is {}", GetName(), exposure); - } else if (name == "CCD_INFO") { - ccdinfo_prop.reset(nvp); - frame.pixel.store(IUFindNumber(nvp, "CCD_PIXEL_SIZE")->value); - frame.pixel_x.store(IUFindNumber(nvp, "CCD_PIXEL_SIZE_X")->value); - frame.pixel_y.store(IUFindNumber(nvp, "CCD_PIXEL_SIZE_Y")->value); - frame.max_frame_x.store(IUFindNumber(nvp, "CCD_MAX_X")->value); - frame.max_frame_y.store(IUFindNumber(nvp, "CCD_MAX_Y")->value); - frame.pixel_depth.store(IUFindNumber(nvp, "CCD_BITSPERPIXEL")->value); - - DLOG_F(INFO, - "{} pixel {} pixel_x {} pixel_y {} max_frame_x {} max_frame_y " - "{} pixel_depth {}", - GetName(), frame.pixel.load(), frame.pixel_x.load(), - frame.pixel_y.load(), frame.max_frame_x.load(), - frame.max_frame_y.load(), frame.pixel_depth.load()); - } else if (name == "CCD_BINNING") { - hydrogen_binning_x.reset(IUFindNumber(nvp, "HOR_BIN")); - hydrogen_binning_y.reset(IUFindNumber(nvp, "VER_BIN")); - frame.binning_x.store(hydrogen_binning_x->value); - frame.binning_y.store(hydrogen_binning_y->value); - DLOG_F(INFO, "Current binning_x and y of {} are {} {}", GetName(), - hydrogen_binning_x->value, hydrogen_binning_y->value); - } else if (name == "CCD_FRAME") { - hydrogen_frame_x.reset(IUFindNumber(nvp, "X")); - hydrogen_frame_y.reset(IUFindNumber(nvp, "Y")); - hydrogen_frame_width.reset(IUFindNumber(nvp, "WIDTH")); - hydrogen_frame_height.reset(IUFindNumber(nvp, "HEIGHT")); - - frame.frame_x.store(hydrogen_frame_x->value); - frame.frame_y.store(hydrogen_frame_y->value); - frame.frame_height.store(hydrogen_frame_height->value); - frame.frame_width.store(hydrogen_frame_width->value); - - DLOG_F(INFO, "Current frame of {} are {} {} {} {}", GetName(), - hydrogen_frame_width->value, hydrogen_frame_y->value, - hydrogen_frame_width->value, hydrogen_frame_height->value); - } else if (name == "CCD_TEMPERATURE") { - camera_temperature_prop.reset(nvp); - current_temperature.store( - IUFindNumber(nvp, "CCD_TEMPERATURE_VALUE")->value); - DLOG_F(INFO, "Current temperature of {} is {}", GetName(), - current_temperature.load()); - } else if (name == "CCD_GAIN") { - gain_prop.reset(nvp); - current_gain.store(IUFindNumber(nvp, "GAIN")->value); - SetVariable("gain", current_gain.load()); - DLOG_F(INFO, "Current camera gain of {} is {}", GetName(), - current_gain.load()); - } else if (name == "CCD_OFFSET") { - offset_prop.reset(nvp); - current_offset.store(IUFindNumber(nvp, "OFFSET")->value); - SetVariable("offset", current_offset.load()); - DLOG_F(INFO, "Current camera offset of {} is {}", GetName(), - current_offset.load()); - } - /* - else if (name == "POLLING_PERIOD") - { - device_info["network"]["period"] = IUFindNumber(nvp, - "PERIOD_MS")->value; DLOG_F(INFO, "Current period of {} is {}", GetName(), - device_info["network"]["period"].dump()); - } - else if (name == "LIMITS") - { - device_info["limits"]["maxbuffer"] = IUFindNumber(nvp, - "LIMITS_BUFFER_MAX")->value; DLOG_F(INFO, "Current max buffer of {} is {}", - GetName(), device_info["limits"]["maxbuffer"].dump()); - device_info["limits"]["maxfps"] = IUFindNumber(nvp, - "LIMITS_PREVIEW_FPS")->value; DLOG_F(INFO, "Current max fps of {} is {}", - GetName(), device_info["limits"]["maxfps"].dump()); - } - else if (name == "STREAM_DELAY") - { - device_info["video"]["delay"] = IUFindNumber(nvp, - "STREAM_DELAY_TIME")->value; DLOG_F(INFO, "Current stream delay of {} is - {}", GetName(), device_info["video"]["delay"].dump()); - } - else if (name == "STREAMING_EXPOSURE") - { - device_info["video"]["exposure"] = IUFindNumber(nvp, - "STREAMING_EXPOSURE_VALUE")->value; DLOG_F(INFO, "Current streaming exposure - of {} is {}", GetName(), device_info["video"]["exposure"].dump()); - device_info["video"]["division"] = IUFindNumber(nvp, - "STREAMING_DIVISOR_VALUE")->value; DLOG_F(INFO, "Current streaming division - of {} is {}", GetName(), device_info["video"]["division"].dump()); - } - else if (name == "FPS") - { - device_info["video"]["fps"] = IUFindNumber(nvp, "EST_FPS")->value; - DLOG_F(INFO, "Current fps of {} is {}", GetName(), - device_info["video"]["fps"].dump()); device_info["video"]["avgfps"] = - IUFindNumber(nvp, "AVG_FPS")->value; DLOG_F(INFO, "Current average fps of {} - is {}", GetName(), device_info["video"]["avgfps"].dump()); - } - else if (PropName == "TC_HGC_SET" && Proptype == HYDROGEN_NUMBER) - { - } - else if (PropName == "CCD_LEVEL_RANGE" && Proptype == HYDROGEN_NUMBER) - { - } - - else if (PropName == "CCD_BLACK_BALANCE" && Proptype == HYDROGEN_NUMBER) - { - } - else if (PropName == "Firmware" && Proptype == HYDROGEN_NUMBER) - { - } - */ -} - -void HydrogenCamera::newText(HYDROGEN::PropertyViewText *tvp) { - const std::string name = tvp->name; - DLOG_F(INFO, "{} Received Text: {} = {}", GetName(), name, tvp->tp->text); - - if (name == hydrogen_camera_cmd + "CFA") { - cfa_prop.reset(tvp); - cfa_type_prop.reset(IUFindText(tvp, "CFA_TYPE")); - if (cfa_type_prop && cfa_type_prop->text && *cfa_type_prop->text) { - DLOG_F(INFO, "{} CFA_TYPE is {}", GetName(), cfa_type_prop->text); - is_color = true; - SetVariable("is_color", true); - } else { - SetVariable("is_color", false); - } - } else if (name == "DEVICE_PORT") { - camera_prop.reset(tvp); - hydrogen_camera_port = tvp->tp->text; - SetVariable("port", hydrogen_camera_port); - DLOG_F(INFO, "Current device port of {} is {}", GetName(), - camera_prop->tp->text); - } else if (name == "DRIVER_INFO") { - hydrogen_camera_exec = IUFindText(tvp, "DRIVER_EXEC")->text; - hydrogen_camera_version = IUFindText(tvp, "DRIVER_VERSION")->text; - hydrogen_camera_interface = IUFindText(tvp, "DRIVER_INTERFACE")->text; - DLOG_F(INFO, "Camera Name : {} connected exec {}", GetName(), GetName(), - hydrogen_camera_exec); - } else if (name == "ACTIVE_DEVICES") { - active_device_prop.reset(tvp); - } -} - -void HydrogenCamera::newBLOB(HYDROGEN::PropertyViewBlob *bp) { - // we go here every time a new blob is available - // this is normally the image from the camera - - DLOG_F(INFO, "{} Received BLOB {} len = {} size = {}", GetName(), bp->name); - - if (exposure_prop) { - if (bp->name == hydrogen_blob_name.c_str()) { - has_blob = 1; - // set option to receive blob and messages for the selected CCD - setBLOBMode(B_ALSO, GetName().c_str(), hydrogen_blob_name.c_str()); - -#ifdef HYDROGEN_SHARED_BLOB_SUPPORT - // Allow faster mode provided we don't modify the blob content or - // free/realloc it - enableDirectBlobAccess(GetName().c_str(), - hydrogen_blob_name.c_str()); -#endif - } - } else if (video_prop) { - } -} - -void HydrogenCamera::ClearStatus() { - m_connection_prop = nullptr; - exposure_prop = nullptr; - frame_prop = nullptr; - frame_type_prop = nullptr; - ccdinfo_prop = nullptr; - binning_prop = nullptr; - video_prop = nullptr; - camera_prop = nullptr; - debug_prop = nullptr; - polling_prop = nullptr; - active_device_prop = nullptr; - compression_prop = nullptr; - image_upload_mode_prop = nullptr; - fast_read_out_prop = nullptr; - camera_limit_prop = nullptr; - - toupcam_fan_control_prop = nullptr; - toupcam_heat_control_prop = nullptr; - toupcam_hcg_control_prop = nullptr; - toupcam_low_noise_control_prop = nullptr; - toupcam_simulation_prop = nullptr; - toupcam_binning_mode_prop = nullptr; - - asi_image_flip_prop = nullptr; - asi_image_flip_hor_prop = nullptr; - asi_image_flip_ver_prop = nullptr; - asi_controls_prop = nullptr; - asi_controls_mode_prop = nullptr; -} diff --git a/driver/client/atom-hydrogen/hydrogencamera.hpp b/driver/client/atom-hydrogen/hydrogencamera.hpp deleted file mode 100644 index 77bec1b7..00000000 --- a/driver/client/atom-hydrogen/hydrogencamera.hpp +++ /dev/null @@ -1,422 +0,0 @@ -/* - * hydrogencamera.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-4-9 - -Description: Hydrogen Camera - -**************************************************/ - -#ifndef ATOM_HYDROGEN_CAMERA_HPP -#define ATOM_HYDROGEN_CAMERA_HPP - -#include "atom/driver/camera.hpp" -#include "atom/utils/switch.hpp" -#include "hydrogenbasic.hpp" - -class CapturedFrame { -public: - void *m_data; - size_t m_size; - char m_format[MAXHYDROGENBLOBFMT]; - - CapturedFrame() { - m_data = nullptr; - m_size = 0; - m_format[0] = 0; - } - - ~CapturedFrame() { -#ifdef HYDROGEN_SHARED_BLOB_SUPPORT - IDSharedBlobFree(m_data); -#else - free(m_data); -#endif - } - - // Take ownership of this blob's data, so HYDROGEN won't overwrite/free the - // memory - void steal(IBLOB *bp) { - m_data = bp->blob; - m_size = bp->size; - strncpy(m_format, bp->format, MAXHYDROGENBLOBFMT); - - bp->blob = nullptr; - bp->size = 0; - } -}; - -class HydrogenCamera : public Camera, public HYDROGEN::BaseClient { -public: - // 构造函数 - explicit HydrogenCamera(const std::string &name); - // 析构函数 - ~HydrogenCamera(); - - virtual bool connect(const json ¶ms) override; - - virtual bool disconnect(const json ¶ms) override; - - virtual bool reconnect(const json ¶ms) override; - - virtual bool isConnected() override; - -public: - /** - * @brief 启动曝光 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool startExposure(const json ¶ms); - - /** - * @brief 中止曝光 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool abortExposure(const json ¶ms); - - /** - * @brief 获取曝光状态 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool getExposureStatus(const json ¶ms); - - /** - * @brief 获取曝光结果 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool getExposureResult(const json ¶ms); - - /** - * @brief 保存曝光结果 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool saveExposureResult(const json ¶ms); - - /** - * @brief 启动视频 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool startVideo(const json ¶ms); - - /** - * @brief 停止视频 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool stopVideo(const json ¶ms); - - /** - * @brief 获取视频状态 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool getVideoStatus(const json ¶ms); - - /** - * @brief 获取视频结果 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool getVideoResult(const json ¶ms); - - /** - * @brief 保存视频结果 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool saveVideoResult(const json ¶ms); - - /** - * @brief 启动冷却 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool startCooling(const json ¶ms); - - /** - * @brief 停止冷却 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool stopCooling(const json ¶ms); - - bool isCoolingAvailable(); - - /** - * @brief 获取温度 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool getTemperature(const json ¶ms); - - /** - * @brief 获取冷却功率 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool getCoolingPower(const json ¶ms); - - /** - * @brief 设置温度 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool setTemperature(const json ¶ms); - - /** - * @brief 设置冷却功率 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool setCoolingPower(const json ¶ms); - - /** - * @brief 获取增益值 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool getGain(const json ¶ms); - - /** - * @brief 设置增益值 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool setGain(const json ¶ms); - - bool isGainAvailable(); - - /** - * @brief 获取偏移量 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool getOffset(const json ¶ms); - - /** - * @brief 设置偏移量 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool setOffset(const json ¶ms); - - bool isOffsetAvailable(); - - /** - * @brief 获取ISO值 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool getISO(const json ¶ms); - - /** - * @brief 设置ISO值 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool setISO(const json ¶ms); - - bool isISOAvailable(); - - /** - * @brief 获取帧数 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool getFrame(const json ¶ms); - - /** - * @brief 设置帧数 - * - * @param params 参数 - * @return 成功返回true,失败返回false - */ - bool setFrame(const json ¶ms); - - bool isFrameSettingAvailable(); - -protected: - // 清空状态 - void ClearStatus(); - - // Hydrogen Client API -protected: - void newDevice(HYDROGEN::BaseDevice dp) override; - void removeDevice(HYDROGEN::BaseDevice dp) override; - void newProperty(HYDROGEN::Property property) override; - void updateProperty(HYDROGEN::Property property) override; - void removeProperty(HYDROGEN::Property property) override {} - void newMessage(HYDROGEN::BaseDevice dp, int messageID) override; - void serverConnected() override; - void serverDisconnected(int exit_code) override; - - void newSwitch(HYDROGEN::PropertyViewSwitch *svp); - void newNumber(HYDROGEN::PropertyViewNumber *nvp); - void newText(HYDROGEN::PropertyViewText *tvp); - void newBLOB(HYDROGEN::PropertyViewBlob *bp); - - // Hydrogen Parameters -private: - // 连接属性 - std::shared_ptr m_connection_prop; - // 曝光属性 - std::shared_ptr exposure_prop; - // 停止曝光属性 - std::shared_ptr abort_exposure_prop; - // 帧属性 - std::shared_ptr frame_prop; - // 温度属性 - std::shared_ptr temperature_prop; - // 增益属性 - std::shared_ptr gain_prop; - // 偏移属性 - std::shared_ptr offset_prop; - // 帧区域参数 - std::shared_ptr hydrogen_frame_x; - std::shared_ptr hydrogen_frame_y; - std::shared_ptr hydrogen_frame_width; - std::shared_ptr hydrogen_frame_height; - // 帧类型 - std::shared_ptr frame_type_prop; - // 图像类型 - std::shared_ptr frame_format_prop; - // CCD 设备信息 - std::shared_ptr ccdinfo_prop; - // 二次取样属性 - std::shared_ptr binning_prop; - // 二次取样 X 轴 - std::shared_ptr hydrogen_binning_x; - // 二次取样 Y 轴 - std::shared_ptr hydrogen_binning_y; - // 视频属性 - std::shared_ptr video_prop; - // 视频延迟 - std::shared_ptr video_delay_prop; - // 视频曝光时间 - std::shared_ptr video_exposure_prop; - // 视频帧率 - std::shared_ptr video_fps_prop; - // 相机端口 - std::shared_ptr camera_prop; - // 相机设备 - HYDROGEN::BaseDevice camera_device; - // 调试模式 - std::shared_ptr debug_prop; - // 信息刷新间隔 - std::shared_ptr polling_prop; - // 已连接的辅助设备 - std::shared_ptr active_device_prop; - // 是否压缩 - std::shared_ptr compression_prop; - // 图像上传模式 - std::shared_ptr image_upload_mode_prop; - // 快速读出模式 - std::shared_ptr fast_read_out_prop; - // 相机限制 - std::shared_ptr camera_limit_prop; - // 相机温度 - std::shared_ptr camera_temperature_prop; - - std::shared_ptr cfa_prop; - - std::shared_ptr cfa_type_prop; - - // 标志位 - std::atomic_bool is_ready; // 是否就绪 - std::atomic_bool has_blob; // 是否有 BLOB 数据 - std::atomic_bool is_debug; - std::atomic_bool is_connected; - std::atomic_bool is_exposure; - std::atomic_bool is_video; - bool is_color; - - std::atomic_int current_gain; - std::atomic_int current_offset; - std::atomic_int current_exposure; - std::atomic current_temperature; - - // Hydrogen 指令 - std::string hydrogen_camera_cmd = "CCD_"; // Hydrogen 控制命令前缀 - std::string hydrogen_blob_name; // BLOB 文件名 - std::string hydrogen_camera_exec = ""; // Hydrogen 执行命令 - std::string hydrogen_camera_version; - std::string hydrogen_camera_interface; - std::string hydrogen_camera_port; - - CameraFrame frame; - - std::atomic polling_period; - - std::unique_ptr> - m_number_switch; - std::unique_ptr> - m_switch_switch; - std::unique_ptr> - m_text_switch; - -private: - // For Hydrogen Toupcamera - - std::shared_ptr toupcam_fan_control_prop; - - std::shared_ptr toupcam_heat_control_prop; - - std::shared_ptr toupcam_hcg_control_prop; - - std::shared_ptr - toupcam_low_noise_control_prop; - - std::shared_ptr toupcam_simulation_prop; - - std::shared_ptr toupcam_binning_mode_prop; - - // For Hydrogen ZWOASI - - // 图像翻转 - std::shared_ptr asi_image_flip_prop; - // 图像翻转 - std::shared_ptr asi_image_flip_hor_prop; - std::shared_ptr asi_image_flip_ver_prop; - // 控制模式 - std::shared_ptr asi_controls_prop; - // 控制模式 - std::shared_ptr asi_controls_mode_prop; - - // For Hydrogen QHYCCD -}; - -#endif diff --git a/driver/client/atom-hydrogen/hydrogendome.cpp b/driver/client/atom-hydrogen/hydrogendome.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/driver/client/atom-hydrogen/hydrogendome.hpp b/driver/client/atom-hydrogen/hydrogendome.hpp deleted file mode 100644 index e891e7de..00000000 --- a/driver/client/atom-hydrogen/hydrogendome.hpp +++ /dev/null @@ -1,4 +0,0 @@ -#ifndef ATOM_HYDROGEN_DOME_HPP -#define ATOM_HYDROGEN_DOME_HPP - -#endif diff --git a/driver/client/atom-hydrogen/hydrogenfilterwheel.cpp b/driver/client/atom-hydrogen/hydrogenfilterwheel.cpp deleted file mode 100644 index 8f1d7d69..00000000 --- a/driver/client/atom-hydrogen/hydrogenfilterwheel.cpp +++ /dev/null @@ -1,217 +0,0 @@ -/* - * hydrogenfilterwheel.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-4-10 - -Description: Hydrogen Filterwheel - -**************************************************/ - -#include "hydrogenfilterwheel.hpp" - -#include "config.h" - -#include "atom/log/loguru.hpp" - -HydrogenFilterwheel::HydrogenFilterwheel(const std::string &name) - : Filterwheel(name) { - DLOG_F(INFO, "Hydrogen filterwheel {} init successfully", name); - - m_number_switch = std::make_unique< - atom::utils::StringSwitch>(); - m_switch_switch = std::make_unique< - atom::utils::StringSwitch>(); - m_text_switch = std::make_unique< - atom::utils::StringSwitch>(); - - m_switch_switch->registerCase( - "CONNECTION", [this](HYDROGEN::PropertyViewSwitch *svp) { - m_connection_prop.reset(svp); - if (auto connectswitch = IUFindSwitch(svp, "CONNECT"); - connectswitch->s == ISS_ON) { - SetVariable("connect", true); - is_connected.store(true); - DLOG_F(INFO, "{} is connected", GetName()); - } else { - if (is_ready.load()) { - SetVariable("connect", false); - is_connected.store(true); - DLOG_F(INFO, "{} is disconnected", GetName()); - } - } - }); - - m_switch_switch->registerCase( - "DEVICE_BAUD_RATE", [this](HYDROGEN::PropertyViewSwitch *svp) { - std::string const baud_9600{"9600"}; - std::string const baud_19200{"19200"}; - std::string const baud_38400{"38400"}; - std::string const baud_57600{"57600"}; - std::string const baud_115200{"115200"}; - std::string const baud_230400{"230400"}; - - if (IUFindSwitch(svp, "9600")->s == ISS_ON) - hydrogen_filter_rate = baud_9600; - else if (IUFindSwitch(svp, "19200")->s == ISS_ON) - hydrogen_filter_rate = baud_19200; - else if (IUFindSwitch(svp, "38400")->s == ISS_ON) - hydrogen_filter_rate = baud_38400; - else if (IUFindSwitch(svp, "57600")->s == ISS_ON) - hydrogen_filter_rate = baud_57600; - else if (IUFindSwitch(svp, "115200")->s == ISS_ON) - hydrogen_filter_rate = baud_115200; - else if (IUFindSwitch(svp, "230400")->s == ISS_ON) - hydrogen_filter_rate = baud_230400; - - DLOG_F(INFO, "{} baud rate : {}", GetName(), hydrogen_filter_rate); - }); - - m_text_switch->registerCase( - "DEVICE_PORT", [this](HYDROGEN::PropertyViewText *tvp) { - filter_prop.reset(tvp); - hydrogen_filter_port = tvp->tp->text; - SetVariable("port", hydrogen_filter_port); - DLOG_F(INFO, "Current device port of {} is {}", GetName(), - filter_prop->tp->text); - }); - - m_text_switch->registerCase( - "DRIVER_INFO", [this](HYDROGEN::PropertyViewText *tvp) { - hydrogen_filter_exec = IUFindText(tvp, "DRIVER_EXEC")->text; - hydrogen_filter_version = IUFindText(tvp, "DRIVER_VERSION")->text; - hydrogen_filter_interface = - IUFindText(tvp, "DRIVER_INTERFACE")->text; - DLOG_F(INFO, "Filterwheel Name : {} connected exec {}", GetName(), - GetName(), hydrogen_filter_exec); - }); -} - -HydrogenFilterwheel::~HydrogenFilterwheel() {} - -bool HydrogenFilterwheel::connect(const json ¶ms) { - std::string name = params["name"]; - std::string hostname = params["host"]; - int port = params["port"]; - DLOG_F(INFO, "Trying to connect to {}", name); - setServer(hostname.c_str(), port); - // Receive messages only for our camera. - watchDevice(name.c_str()); - // Connect to server. - if (connectServer()) { - DLOG_F(INFO, "{}: connectServer done ready", GetName()); - connectDevice(name.c_str()); - return !is_ready.load(); - } - return false; -} - -bool HydrogenFilterwheel::disconnect(const json ¶ms) { - DLOG_F(INFO, "%s is disconnected", GetName()); - return true; -} - -bool HydrogenFilterwheel::reconnect(const json ¶ms) { return true; } - -bool HydrogenFilterwheel::isConnected() { return true; } - -bool HydrogenFilterwheel::moveTo(const json ¶ms) { return true; } - -bool HydrogenFilterwheel::getCurrentPosition(const json ¶ms) { - return true; -} - -void HydrogenFilterwheel::newDevice(HYDROGEN::BaseDevice dp) { - if (strcmp(dp.getDeviceName(), GetName().c_str()) == 0) { - filter_device = dp; - } -} - -void HydrogenFilterwheel::removeDevice(HYDROGEN::BaseDevice dp) { - ClearStatus(); - DLOG_F(INFO, "{} disconnected", GetName()); -} - -void HydrogenFilterwheel::newSwitch(HYDROGEN::PropertyViewSwitch *svp) { - m_switch_switch->match(svp->name, svp); -} - -void HydrogenFilterwheel::newMessage(HYDROGEN::BaseDevice dp, int messageID) { - DLOG_F(INFO, "{} Received message: {}", GetName(), - dp.messageQueue(messageID)); -} - -void HydrogenFilterwheel::serverConnected() { - DLOG_F(INFO, "{} Connected to server", GetName()); -} - -void HydrogenFilterwheel::serverDisconnected(int exit_code) { - DLOG_F(INFO, "{} Disconnected from server", GetName()); - - ClearStatus(); -} - -inline static const char *StateStr(IPState st) { - switch (st) { - default: - case IPS_IDLE: - return "Idle"; - case IPS_OK: - return "Ok"; - case IPS_BUSY: - return "Busy"; - case IPS_ALERT: - return "Alert"; - } -} - -void HydrogenFilterwheel::newNumber(HYDROGEN::PropertyViewNumber *nvp) { - m_number_switch->match(nvp->name, nvp); -} - -void HydrogenFilterwheel::newText(HYDROGEN::PropertyViewText *tvp) { - m_text_switch->match(tvp->name, tvp); -} - -void HydrogenFilterwheel::newBLOB(HYDROGEN::PropertyViewBlob *bp) { - DLOG_F(INFO, "{} Received BLOB {}", GetName(), bp->name); -} - -void HydrogenFilterwheel::newProperty(HYDROGEN::Property property) { - std::string PropName(property.getName()); - HYDROGEN_PROPERTY_TYPE Proptype = property.getType(); - - DLOG_F(INFO, "{} Property: {}", GetName(), property.getName()); - - switch (property.getType()) { - case HYDROGEN_SWITCH: { - auto svp = property.getSwitch(); - DLOG_F(INFO, "{}: {}", GetName(), svp->name); - newSwitch(svp); - } break; - case HYDROGEN_NUMBER: { - auto nvp = property.getNumber(); - DLOG_F(INFO, "{}: {}", GetName(), nvp->name); - newNumber(nvp); - } break; - case HYDROGEN_TEXT: { - auto tvp = property.getText(); - DLOG_F(INFO, "{}: {}", GetName(), tvp->name); - newText(tvp); - } break; - default: - break; - } -} - -void HydrogenFilterwheel::ClearStatus() { - m_connection_prop = nullptr; - filterinfo_prop = nullptr; - filter_port = nullptr; - rate_prop = nullptr; - filter_prop = nullptr; -} diff --git a/driver/client/atom-hydrogen/hydrogenfilterwheel.hpp b/driver/client/atom-hydrogen/hydrogenfilterwheel.hpp deleted file mode 100644 index 638fc4b9..00000000 --- a/driver/client/atom-hydrogen/hydrogenfilterwheel.hpp +++ /dev/null @@ -1,87 +0,0 @@ -/* - * hydrogenfilterwheel.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-4-10 - -Description: Hydrogen Filterwheel - -**************************************************/ - -#ifndef ATOM_HYDROGEN_FILTERWHEEL_HPP -#define ATOM_HYDROGEN_FILTERWHEEL_HPP - -#include "atom/driver/filterwheel.hpp" -#include "atom/utils/switch.hpp" -#include "hydrogenbasic.hpp" - -class HydrogenFilterwheel : public Filterwheel, public HYDROGEN::BaseClient { -public: - explicit HydrogenFilterwheel(const std::string &name); - ~HydrogenFilterwheel(); - - virtual bool connect(const json ¶ms) override; - - virtual bool disconnect(const json ¶ms) override; - - virtual bool reconnect(const json ¶ms) override; - - virtual bool isConnected() override; - - virtual bool moveTo(const json ¶ms) override; - - virtual bool getCurrentPosition(const json ¶ms) override; - -protected: - void newDevice(HYDROGEN::BaseDevice dp) override; - void removeDevice(HYDROGEN::BaseDevice dp) override; - void newProperty(HYDROGEN::Property property) override; - void updateProperty(HYDROGEN::Property property) override; - void removeProperty(HYDROGEN::Property property) override {} - void newMessage(HYDROGEN::BaseDevice dp, int messageID) override; - void serverConnected() override; - void serverDisconnected(int exit_code) override; - - void newSwitch(HYDROGEN::PropertyViewSwitch *svp); - void newNumber(HYDROGEN::PropertyViewNumber *nvp); - void newText(HYDROGEN::PropertyViewText *tvp); - void newBLOB(HYDROGEN::PropertyViewBlob *bp); - -protected: - void ClearStatus(); - - // Hydrogen Parameters -private: - std::shared_ptr m_connection_prop; - std::shared_ptr filterinfo_prop; - std::shared_ptr filter_port; - std::shared_ptr rate_prop; - std::shared_ptr filter_prop; - HYDROGEN::BaseDevice filter_device; - - std::atomic_bool is_ready; // 是否就绪 - std::atomic_bool has_blob; // 是否有 BLOB 数据 - std::atomic_bool is_debug; - std::atomic_bool is_connected; - - std::string hydrogen_filter_port = ""; - std::string hydrogen_filter_rate = ""; - - std::string hydrogen_filter_cmd; - std::string hydrogen_filter_exec = ""; - std::string hydrogen_filter_version = ""; - std::string hydrogen_filter_interface = ""; - - std::unique_ptr> - m_number_switch; - std::unique_ptr> - m_switch_switch; - std::unique_ptr> - m_text_switch; -}; - -#endif diff --git a/driver/client/atom-hydrogen/hydrogenfocuser.cpp b/driver/client/atom-hydrogen/hydrogenfocuser.cpp deleted file mode 100644 index b6521ee3..00000000 --- a/driver/client/atom-hydrogen/hydrogenfocuser.cpp +++ /dev/null @@ -1,326 +0,0 @@ -/* - * hydrogenfocuser.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-4-10 - -Description: Hydrogen Focuser - -**************************************************/ - -#include "hydrogenfocuser.hpp" - -#include "config.h" - -#include "atom/log/loguru.hpp" - -HydrogenFocuser::HydrogenFocuser(const std::string &name) : Focuser(name) { - DLOG_F(INFO, "Hydrogen Focuser {} init successfully", name); - - m_number_switch = std::make_unique< - atom::utils::StringSwitch>(); - m_switch_switch = std::make_unique< - atom::utils::StringSwitch>(); - m_text_switch = std::make_unique< - atom::utils::StringSwitch>(); - - m_switch_switch->registerCase( - "CONNECTION", [this](HYDROGEN::PropertyViewSwitch *svp) { - m_connection_prop.reset(svp); - if (auto connectswitch = IUFindSwitch(svp, "CONNECT"); - connectswitch->s == ISS_ON) { - SetVariable("connect", true); - is_connected.store(true); - DLOG_F(INFO, "{} is connected", GetName()); - } else { - if (is_ready.load()) { - SetVariable("connect", false); - is_connected.store(true); - DLOG_F(INFO, "{} is disconnected", GetName()); - } - } - }); - - m_switch_switch->registerCase( - "DEVICE_BAUD_RATE", [this](HYDROGEN::PropertyViewSwitch *svp) { - std::string const baud_9600{"9600"}; - std::string const baud_19200{"19200"}; - std::string const baud_38400{"38400"}; - std::string const baud_57600{"57600"}; - std::string const baud_115200{"115200"}; - std::string const baud_230400{"230400"}; - - if (IUFindSwitch(svp, "9600")->s == ISS_ON) - hydrogen_focuser_rate = baud_9600; - else if (IUFindSwitch(svp, "19200")->s == ISS_ON) - hydrogen_focuser_rate = baud_19200; - else if (IUFindSwitch(svp, "38400")->s == ISS_ON) - hydrogen_focuser_rate = baud_38400; - else if (IUFindSwitch(svp, "57600")->s == ISS_ON) - hydrogen_focuser_rate = baud_57600; - else if (IUFindSwitch(svp, "115200")->s == ISS_ON) - hydrogen_focuser_rate = baud_115200; - else if (IUFindSwitch(svp, "230400")->s == ISS_ON) - hydrogen_focuser_rate = baud_230400; - - DLOG_F(INFO, "{} baud rate: {}", GetName(), hydrogen_focuser_rate); - }); - - m_switch_switch->registerCase( - "Mode", [this](HYDROGEN::PropertyViewSwitch *svp) { - m_mode_prop.reset(svp); - ISwitch *modeswitch = IUFindSwitch(svp, "All"); - if (modeswitch->s == ISS_ON) { - can_absolute_move = true; - current_mode.store(0); - } else { - modeswitch = IUFindSwitch(svp, "Absolute"); - if (modeswitch->s == ISS_ON) { - can_absolute_move = true; - current_mode.store(1); - } else { - can_absolute_move = false; - current_mode.store(2); - } - } - }); - - m_switch_switch->registerCase( - hydrogen_focuser_cmd + "FOCUS_MOTION", - [this](HYDROGEN::PropertyViewSwitch *svp) { - m_motion_prop.reset(svp); - m_current_motion.store( - (IUFindSwitch(svp, "FOCUS_INWARD")->s == ISS_ON) ? 0 : 1); - }); - - m_switch_switch->registerCase( - hydrogen_focuser_cmd + "FOCUS_BACKLASH_TOGGLE", - [this](HYDROGEN::PropertyViewSwitch *svp) { - m_backlash_prop.reset(svp); - has_backlash = (IUFindSwitch(svp, "HYDROGEN_ENABLED")->s == ISS_ON); - }); - - m_number_switch->registerCase( - "FOCUS_ABSOLUTE_POSITION", [this](HYDROGEN::PropertyViewNumber *nvp) { - m_absolute_position_prop.reset(nvp); - INumber *num_value = IUFindNumber(nvp, "FOCUS_ABSOLUTE_POSITION"); - if (num_value) { - m_current_absolute_position.store(num_value->value); - DLOG_F(INFO, "{} Current Absolute Position: {}", GetName(), - m_current_absolute_position.load()); - } - }); - - m_number_switch->registerCase( - "FOCUS_SPEED", [this](HYDROGEN::PropertyViewNumber *nvp) { - m_speed_prop.reset(nvp); - INumber *num_value = IUFindNumber(nvp, "FOCUS_SPEED"); - if (num_value) { - m_current_speed.store(num_value->value); - DLOG_F(INFO, "{} Current Speed: {}", GetName(), - m_current_speed.load()); - } - }); - - m_number_switch->registerCase("ABS_FOCUS_POSITION", - [this](HYDROGEN::PropertyViewNumber *nvp) { - - }); - - m_number_switch->registerCase( - "DELAY", [this](HYDROGEN::PropertyViewNumber *nvp) { - m_delay_prop.reset(nvp); - INumber *num_value = IUFindNumber(nvp, "DELAY"); - if (num_value) { - m_delay = num_value->value; - DLOG_F(INFO, "{} Current Delay: {}", GetName(), m_delay); - } - }); - - m_number_switch->registerCase( - "FOCUS_TEMPERATURE", [this](HYDROGEN::PropertyViewNumber *nvp) { - m_temperature_prop.reset(nvp); - INumber *num_value = IUFindNumber(nvp, "FOCUS_TEMPERATURE"); - if (num_value) { - m_current_temperature.store(num_value->value); - DLOG_F(INFO, "{} Current Temperature: {}", GetName(), - m_current_temperature.load()); - } - }); - - m_number_switch->registerCase( - "FOCUS_MAX", [this](HYDROGEN::PropertyViewNumber *nvp) { - m_max_position_prop.reset(nvp); - INumber *num_value = IUFindNumber(nvp, "FOCUS_MAX"); - if (num_value) { - m_max_position = num_value->value; - DLOG_F(INFO, "{} Current Speed: {}", GetName(), m_max_position); - } - }); -} - -HydrogenFocuser::~HydrogenFocuser() {} - -bool HydrogenFocuser::connect(const json ¶ms) { - std::string name = params["name"]; - std::string hostname = params["host"]; - int port = params["port"]; - DLOG_F(INFO, "Trying to connect to {}", name); - setServer(hostname.c_str(), port); - // Receive messages only for our camera. - watchDevice(name.c_str()); - // Connect to server. - if (connectServer()) { - DLOG_F(INFO, "{}: connectServer done ready", GetName()); - connectDevice(name.c_str()); - return !is_ready.load(); - } - return false; -} - -bool HydrogenFocuser::disconnect(const json ¶ms) { - DLOG_F(INFO, "%s is disconnected", GetName()); - return true; -} - -bool HydrogenFocuser::reconnect(const json ¶ms) { return true; } - -bool HydrogenFocuser::isConnected() { return true; } - -bool HydrogenFocuser::moveTo(const json ¶ms) { return true; } - -bool HydrogenFocuser::moveToAbsolute(const json ¶ms) { return true; } - -bool HydrogenFocuser::moveStep(const json ¶ms) { return true; } - -bool HydrogenFocuser::moveStepAbsolute(const json ¶ms) { return true; } - -bool HydrogenFocuser::AbortMove(const json ¶ms) { return true; } - -int HydrogenFocuser::getMaxPosition(const json ¶ms) { return 0; } - -bool HydrogenFocuser::setMaxPosition(const json ¶ms) { return true; } - -bool HydrogenFocuser::isGetTemperatureAvailable(const json ¶ms) { - return true; -} - -double HydrogenFocuser::getTemperature(const json ¶ms) { return 0.0; } - -bool HydrogenFocuser::isAbsoluteMoveAvailable(const json ¶ms) { - return true; -} - -bool HydrogenFocuser::isManualMoveAvailable(const json ¶ms) { return true; } - -int HydrogenFocuser::getCurrentPosition(const json ¶ms) { return 0; } - -bool HydrogenFocuser::haveBacklash(const json ¶ms) { return true; } - -bool HydrogenFocuser::setBacklash(const json ¶ms) { return true; } - -void HydrogenFocuser::newDevice(HYDROGEN::BaseDevice dp) { - if (dp.getDeviceName() == GetName().c_str()) { - focuser_device = dp; - } -} - -void HydrogenFocuser::newSwitch(HYDROGEN::PropertyViewSwitch *svp) { - m_switch_switch->match(svp->name, svp); -} - -void HydrogenFocuser::newMessage(HYDROGEN::BaseDevice dp, int messageID) { - DLOG_F(INFO, "{} Received message: {}", GetName(), - dp.messageQueue(messageID)); -} - -void HydrogenFocuser::serverConnected() { - DLOG_F(INFO, "{} Connected to server", GetName()); -} - -void HydrogenFocuser::serverDisconnected(int exit_code) { - DLOG_F(INFO, "{} Disconnected from server", GetName()); - - ClearStatus(); -} - -inline static const char *StateStr(IPState st) { - switch (st) { - default: - case IPS_IDLE: - return "Idle"; - case IPS_OK: - return "Ok"; - case IPS_BUSY: - return "Busy"; - case IPS_ALERT: - return "Alert"; - } -} - -void HydrogenFocuser::newNumber(HYDROGEN::PropertyViewNumber *nvp) { - m_number_switch->match(nvp->name, nvp); -} - -void HydrogenFocuser::newText(HYDROGEN::PropertyViewText *tvp) { - m_text_switch->match(tvp->name, tvp); -} - -void HydrogenFocuser::newBLOB(HYDROGEN::PropertyViewBlob *bp) { - DLOG_F(INFO, "{} Received BLOB {}", GetName(), bp->name); -} - -void HydrogenFocuser::newProperty(HYDROGEN::Property property) { - std::string PropName(property.getName()); - HYDROGEN_PROPERTY_TYPE Proptype = property.getType(); - - DLOG_F(INFO, "{} Property: {}", GetName(), property.getName()); - - switch (property.getType()) { - case HYDROGEN_SWITCH: { - auto svp = property.getSwitch(); - DLOG_F(INFO, "{}: {}", GetName(), svp->name); - newSwitch(svp); - } break; - case HYDROGEN_NUMBER: { - auto nvp = property.getNumber(); - DLOG_F(INFO, "{}: {}", GetName(), nvp->name); - newNumber(nvp); - } break; - case HYDROGEN_TEXT: { - auto tvp = property.getText(); - DLOG_F(INFO, "{}: {}", GetName(), tvp->name); - newText(tvp); - } break; - default: - break; - }; -} - -void HydrogenFocuser::removeDevice(HYDROGEN::BaseDevice dp) { - ClearStatus(); - DLOG_F(INFO, "{} disconnected", GetName()); -} - -void HydrogenFocuser::ClearStatus() { - m_connection_prop = nullptr; - m_connection_prop = nullptr; - m_mode_prop = nullptr; // Focuser mode , absolute or relative - m_motion_prop = nullptr; // Focuser motion , inward or outward - m_speed_prop = nullptr; // Focuser speed , default is 1 - m_absolute_position_prop = nullptr; // Focuser absolute position - m_relative_position_prop = nullptr; // Focuser relative position - m_max_position_prop = nullptr; // Focuser max position - m_temperature_prop = nullptr; // Focuser temperature - m_rate_prop = nullptr; - m_delay_prop = nullptr; - m_backlash_prop = nullptr; - m_hydrogen_max_position = nullptr; - m_hydrogen_focuser_temperature = nullptr; - m_focuserinfo_prop = nullptr; - focuser_port = nullptr; -} diff --git a/driver/client/atom-hydrogen/hydrogenfocuser.hpp b/driver/client/atom-hydrogen/hydrogenfocuser.hpp deleted file mode 100644 index 352261f5..00000000 --- a/driver/client/atom-hydrogen/hydrogenfocuser.hpp +++ /dev/null @@ -1,236 +0,0 @@ -/* - * hydrogenfocuser.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-4-10 - -Description: Hydrogen Focuser - -**************************************************/ - -#ifndef ATOM_HYDROGEN_FOCUSER_HPP -#define ATOM_HYDROGEN_FOCUSER_HPP - -#include "atom/driver/focuser.hpp" -#include "atom/utils/switch.hpp" -#include "hydrogenbasic.hpp" - -class HydrogenFocuser : public Focuser, public HYDROGEN::BaseClient { -public: - /** - * @brief 构造函数,初始化 HydrogenFocuser 类 - * - * @param name 焦距器名字 - */ - explicit HydrogenFocuser(const std::string &name); - - /** - * @brief 析构函数,释放 HydrogenFocuser 类相关资源 - * - */ - ~HydrogenFocuser(); - - virtual bool connect(const json ¶ms) override; - - virtual bool disconnect(const json ¶ms) override; - - virtual bool reconnect(const json ¶ms) override; - - virtual bool isConnected() override; - - /** - * @brief 将电调移动到 position 位置 - * - * @param position 相对移动的步数 - * @return bool 移动是否成功 - */ - virtual bool moveTo(const nlohmann::json ¶ms); - - /** - * @brief 将电调移动到绝对位置 position - * - * @param position 绝对位置步数 - * @return bool 移动是否成功 - */ - virtual bool moveToAbsolute(const nlohmann::json ¶ms); - - /** - * @brief 移动电调 step 个步长 - * - * @param step 移动步数 - * @return bool 移动是否成功 - */ - virtual bool moveStep(const nlohmann::json ¶ms); - - /** - * @brief 移动电调至绝对步数位置 - * - * @param step 绝对步数位置 - * @return bool 移动是否成功 - */ - virtual bool moveStepAbsolute(const nlohmann::json ¶ms); - - /** - * @brief 中止电调移动 - * - * @return bool 操作是否成功 - */ - virtual bool AbortMove(const nlohmann::json ¶ms); - - /** - * @brief 获取电调最大位置 - * - * @return int 电调最大位置 - */ - virtual int getMaxPosition(const nlohmann::json ¶ms); - - /** - * @brief 设置电调最大位置 - * - * @param max_position 电调最大位置 - * @return bool 操作是否成功 - */ - virtual bool setMaxPosition(const nlohmann::json ¶ms); - - /** - * @brief 判断是否支持获取温度功能 - * - * @return bool 是否支持获取温度功能 - */ - virtual bool isGetTemperatureAvailable(const nlohmann::json ¶ms); - - /** - * @brief 获取电调当前温度 - * - * @return double 当前温度 - */ - virtual double getTemperature(const nlohmann::json ¶ms); - - /** - * @brief 判断是否支持绝对移动功能 - * - * @return bool 是否支持绝对移动功能 - */ - virtual bool isAbsoluteMoveAvailable(const nlohmann::json ¶ms); - - /** - * @brief 判断是否支持手动移动功能 - * - * @return bool 是否支持手动移动功能 - */ - virtual bool isManualMoveAvailable(const nlohmann::json ¶ms); - - /** - * @brief 获取电调当前位置 - * - * @return int 当前位置 - */ - virtual int getCurrentPosition(const nlohmann::json ¶ms); - - /** - * @brief 判断电调是否存在反向间隙 - * - * @return bool 是否存在反向间隙 - */ - virtual bool haveBacklash(const nlohmann::json ¶ms); - - /** - * @brief 设置电调反向间隙值 - * - * @param value 反向间隙值 - * @return bool 操作是否成功 - */ - virtual bool setBacklash(const nlohmann::json ¶ms); - -protected: - /** - * @brief 清除状态 - * - */ - void ClearStatus(); - -protected: - void newDevice(HYDROGEN::BaseDevice dp) override; - void removeDevice(HYDROGEN::BaseDevice dp) override; - void newProperty(HYDROGEN::Property property) override; - void updateProperty(HYDROGEN::Property property) override; - void removeProperty(HYDROGEN::Property property) override {} - void newMessage(HYDROGEN::BaseDevice dp, int messageID) override; - void serverConnected() override; - void serverDisconnected(int exit_code) override; - - void newSwitch(HYDROGEN::PropertyViewSwitch *svp); - void newNumber(HYDROGEN::PropertyViewNumber *nvp); - void newText(HYDROGEN::PropertyViewText *tvp); - void newBLOB(HYDROGEN::PropertyViewBlob *bp); - -private: - // Hydrogen 客户端参数 - std::shared_ptr - m_connection_prop; // 连接属性指针 - std::shared_ptr - m_mode_prop; // 焦距器模式(绝对或相对)属性指针 - std::shared_ptr - m_motion_prop; // 焦距器运动方向(向内或向外)属性指针 - std::shared_ptr - m_speed_prop; // 焦距器速度属性指针,默认为 1 - std::shared_ptr - m_absolute_position_prop; // 焦距器绝对位置属性指针 - std::shared_ptr - m_relative_position_prop; // 焦距器相对位置属性指针 - std::shared_ptr - m_max_position_prop; // 焦距器最大位置属性指针 - std::shared_ptr - m_temperature_prop; // 焦距器温度属性指针 - std::shared_ptr - m_rate_prop; // 焦距器速率属性指针 - std::shared_ptr - m_delay_prop; // 焦距器延迟属性指针 - std::shared_ptr - m_backlash_prop; // 焦距器反向间隙属性指针 - std::shared_ptr - m_focuserinfo_prop; // 焦距器用户信息属性指针 - INumber *m_hydrogen_max_position; // 焦距器 hydrogen 最大位置属性指针 - INumber *m_hydrogen_focuser_temperature; // 焦距器 hydrogen 温度属性指针 - std::shared_ptr - focuser_port; // 焦距器端口属性指针 - HYDROGEN::BaseDevice focuser_device; // 焦距器设备指针 - - std::atomic_bool is_ready; // 是否就绪 - std::atomic_bool has_blob; // 是否有 BLOB 数据 - std::atomic_bool is_debug; - std::atomic_bool is_connected; - - bool can_absolute_move = false; - bool has_backlash = false; - - std::atomic_int current_mode; - std::atomic_int m_current_absolute_position; - std::atomic_int m_current_motion; - std::atomic_int m_current_speed; - std::atomic m_current_temperature; - - int m_delay = 0; - int m_max_position = 0; - - std::string hydrogen_focuser_port = ""; // 焦距器所选端口 - std::string hydrogen_focuser_rate = ""; // 焦距器所选速率 - - std::string hydrogen_focuser_cmd; // Hydrogen 命令字符串 - std::string hydrogen_focuser_exec = ""; // Hydrogen 设备执行文件路径 - std::string hydrogen_focuser_version = ""; // Hydrogen 设备固件版本 - std::string hydrogen_focuser_interface = ""; // Hydrogen 接口版本 - - std::unique_ptr> - m_number_switch; - std::unique_ptr> - m_switch_switch; - std::unique_ptr> - m_text_switch; -}; - -#endif diff --git a/driver/client/atom-hydrogen/hydrogentelescope.cpp b/driver/client/atom-hydrogen/hydrogentelescope.cpp deleted file mode 100644 index 52b310b0..00000000 --- a/driver/client/atom-hydrogen/hydrogentelescope.cpp +++ /dev/null @@ -1,258 +0,0 @@ -/* - * hydrogentelescope.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-4-10 - -Description: Hydrogen Telescope - -**************************************************/ - -#include "hydrogentelescope.hpp" - -#include "config.h" - -#include "atom/log/loguru.hpp" - -HydrogenTelescope::HydrogenTelescope(const std::string &name) - : Telescope(name) { - DLOG_F(INFO, "Hydrogen telescope {} init successfully", name); - - m_number_switch = std::make_unique< - atom::utils::StringSwitch>(); - m_switch_switch = std::make_unique< - atom::utils::StringSwitch>(); - m_text_switch = std::make_unique< - atom::utils::StringSwitch>(); - - m_switch_switch->registerCase( - "CONNECTION", [this](HYDROGEN::PropertyViewSwitch *svp) { - m_connection_prop.reset(svp); - if (auto connectswitch = IUFindSwitch(svp, "CONNECT"); - connectswitch->s == ISS_ON) { - SetVariable("connect", true); - is_connected.store(true); - DLOG_F(INFO, "{} is connected", GetName()); - } else { - if (is_ready.load()) { - SetVariable("connect", false); - is_connected.store(true); - DLOG_F(INFO, "{} is disconnected", GetName()); - } - } - }); - - m_switch_switch->registerCase( - "DEVICE_BAUD_RATE", [this](HYDROGEN::PropertyViewSwitch *svp) { - std::string const baud_9600{"9600"}; - std::string const baud_19200{"19200"}; - std::string const baud_38400{"38400"}; - std::string const baud_57600{"57600"}; - std::string const baud_115200{"115200"}; - std::string const baud_230400{"230400"}; - - if (IUFindSwitch(svp, "9600")->s == ISS_ON) - hydrogen_telescope_rate = baud_9600; - else if (IUFindSwitch(svp, "19200")->s == ISS_ON) - hydrogen_telescope_rate = baud_19200; - else if (IUFindSwitch(svp, "38400")->s == ISS_ON) - hydrogen_telescope_rate = baud_38400; - else if (IUFindSwitch(svp, "57600")->s == ISS_ON) - hydrogen_telescope_rate = baud_57600; - else if (IUFindSwitch(svp, "115200")->s == ISS_ON) - hydrogen_telescope_rate = baud_115200; - else if (IUFindSwitch(svp, "230400")->s == ISS_ON) - hydrogen_telescope_rate = baud_230400; - - DLOG_F(INFO, "{} baud rate : {}", GetName(), - hydrogen_telescope_rate); - }); - - m_text_switch->registerCase( - "DEVICE_PORT", [this](HYDROGEN::PropertyViewText *tvp) { - telescope_prop.reset(tvp); - hydrogen_telescope_port = tvp->tp->text; - SetVariable("port", hydrogen_telescope_port); - DLOG_F(INFO, "Current device port of {} is {}", GetName(), - telescope_prop->tp->text); - }); - - m_text_switch->registerCase( - "DRIVER_INFO", [this](HYDROGEN::PropertyViewText *tvp) { - hydrogen_telescope_exec = IUFindText(tvp, "DRIVER_EXEC")->text; - hydrogen_telescope_version = - IUFindText(tvp, "DRIVER_VERSION")->text; - hydrogen_telescope_interface = - IUFindText(tvp, "DRIVER_INTERFACE")->text; - DLOG_F(INFO, "Telescope Name : {} connected exec {}", GetName(), - GetName(), hydrogen_telescope_exec); - }); -} - -HydrogenTelescope::~HydrogenTelescope() {} - -bool HydrogenTelescope::connect(const json ¶ms) { - std::string name = params["name"]; - std::string hostname = params["host"]; - int port = params["port"]; - DLOG_F(INFO, "Trying to connect to {}", name); - setServer(hostname.c_str(), port); - // Receive messages only for our camera. - watchDevice(name.c_str()); - // Connect to server. - if (connectServer()) { - DLOG_F(INFO, "{}: connectServer done ready", GetName()); - connectDevice(name.c_str()); - return !is_ready.load(); - } - return false; -} - -bool HydrogenTelescope::disconnect(const json ¶ms) { - DLOG_F(INFO, "%s is disconnected", GetName()); - return true; -} - -bool HydrogenTelescope::reconnect(const json ¶ms) { return true; } - -bool HydrogenTelescope::isConnected() { return true; } - -bool HydrogenTelescope::SlewTo(const json ¶ms) { return true; } - -bool HydrogenTelescope::Abort(const json ¶ms) { return true; } - -bool HydrogenTelescope::isSlewing(const json ¶ms) { return true; } - -std::string HydrogenTelescope::getCurrentRA(const json ¶ms) { return ""; } - -std::string HydrogenTelescope::getCurrentDec(const json ¶ms) { return ""; } - -bool HydrogenTelescope::StartTracking(const json ¶ms) { return true; } - -bool HydrogenTelescope::StopTracking(const json ¶ms) { return true; } - -bool HydrogenTelescope::setTrackingMode(const json ¶ms) { return true; } - -bool HydrogenTelescope::setTrackingSpeed(const json ¶ms) { return true; } - -std::string HydrogenTelescope::getTrackingMode(const json ¶ms) { - return ""; -} - -std::string HydrogenTelescope::getTrackingSpeed(const json ¶ms) { - return ""; -} - -bool HydrogenTelescope::Home(const json ¶ms) { return true; } - -bool HydrogenTelescope::isAtHome(const json ¶ms) { return true; } - -bool HydrogenTelescope::setHomePosition(const json ¶ms) { return true; } - -bool HydrogenTelescope::isHomeAvailable(const json ¶ms) { return true; } - -bool HydrogenTelescope::Park(const json ¶ms) { return true; } - -bool HydrogenTelescope::Unpark(const json ¶ms) { return true; } - -bool HydrogenTelescope::isAtPark(const json ¶ms) { return true; } - -bool HydrogenTelescope::setParkPosition(const json ¶ms) { return true; } - -bool HydrogenTelescope::isParkAvailable(const json ¶ms) { return true; } - -void HydrogenTelescope::newDevice(HYDROGEN::BaseDevice dp) { - if (strcmp(dp.getDeviceName(), GetName().c_str()) == 0) { - telescope_device = dp; - } -} - -void HydrogenTelescope::newSwitch(HYDROGEN::PropertyViewSwitch *svp) { - m_switch_switch->match(svp->name, svp); -} - -void HydrogenTelescope::newMessage(HYDROGEN::BaseDevice dp, int messageID) { - DLOG_F(INFO, "{} Received message: {}", GetName(), - dp.messageQueue(messageID)); -} - -void HydrogenTelescope::serverConnected() { - DLOG_F(INFO, "{} Connected to server", GetName()); -} - -void HydrogenTelescope::serverDisconnected(int exit_code) { - DLOG_F(INFO, "{} Disconnected from server", GetName()); - - ClearStatus(); -} - -inline static const char *StateStr(IPState st) { - switch (st) { - default: - case IPS_IDLE: - return "Idle"; - case IPS_OK: - return "Ok"; - case IPS_BUSY: - return "Busy"; - case IPS_ALERT: - return "Alert"; - } -} - -void HydrogenTelescope::newNumber(HYDROGEN::PropertyViewNumber *nvp) { - m_number_switch->match(nvp->name, nvp); -} - -void HydrogenTelescope::newText(HYDROGEN::PropertyViewText *tvp) { - m_text_switch->match(tvp->name, tvp); -} - -void HydrogenTelescope::newBLOB(HYDROGEN::PropertyViewBlob *bp) { - DLOG_F(INFO, "{} Received BLOB {}", GetName(), bp->name); -} - -void HydrogenTelescope::newProperty(HYDROGEN::Property property) { - std::string PropName(property.getName()); - HYDROGEN_PROPERTY_TYPE Proptype = property.getType(); - - DLOG_F(INFO, "{} Property: {}", GetName(), property.getName()); - - switch (property.getType()) { - case HYDROGEN_SWITCH: { - auto svp = property.getSwitch(); - DLOG_F(INFO, "{}: {}", GetName(), svp->name); - newSwitch(svp); - } break; - case HYDROGEN_NUMBER: { - auto nvp = property.getNumber(); - DLOG_F(INFO, "{}: {}", GetName(), nvp->name); - newNumber(nvp); - } break; - case HYDROGEN_TEXT: { - auto tvp = property.getText(); - DLOG_F(INFO, "{}: {}", GetName(), tvp->name); - newText(tvp); - } break; - default: - break; - }; -} - -void HydrogenTelescope::removeDevice(HYDROGEN::BaseDevice dp) { - ClearStatus(); - DLOG_F(INFO, "{} disconnected", GetName()); -} - -void HydrogenTelescope::ClearStatus() { - m_connection_prop = nullptr; - telescope_port = nullptr; - m_connection_prop = nullptr; - rate_prop = nullptr; - telescopeinfo_prop = nullptr; - telescope_port = nullptr; -} diff --git a/driver/client/atom-hydrogen/hydrogentelescope.hpp b/driver/client/atom-hydrogen/hydrogentelescope.hpp deleted file mode 100644 index 6e1057e5..00000000 --- a/driver/client/atom-hydrogen/hydrogentelescope.hpp +++ /dev/null @@ -1,226 +0,0 @@ -/* - * hydrogentelescope.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-4-10 - -Description: Hydrogen Telescope - -**************************************************/ - -#pragma once - -#include "atom/driver/telescope.hpp" -#include "atom/utils/switch.hpp" -#include "hydrogenbasic.hpp" - -class HydrogenTelescope : public Telescope, public HYDROGEN::BaseClient { -public: - /** - * @brief 构造函数 - * - * @param name 望远镜名字 - */ - explicit HydrogenTelescope(const std::string &name); - - /** - * @brief 析构函数 - * - */ - ~HydrogenTelescope(); - - virtual bool connect(const json ¶ms) override; - - virtual bool disconnect(const json ¶ms) override; - - virtual bool reconnect(const json ¶ms) override; - - virtual bool isConnected() override; - - /** - * @brief 指向新目标 - * @param ra 目标赤经 - * @param dec 目标赤纬 - * @param j2000 是否使用J2000坐标系,默认为false,表示使用本地坐标系 - * @return 是否成功指向新目标 - */ - virtual bool SlewTo(const json ¶ms); - - /** - * @brief 中止望远镜的指向 - * @return 是否成功中止指向 - */ - virtual bool Abort(const json ¶ms); - - /** - * @brief 获取望远镜是否在指向新目标 - * @return 返回 true 表示正在指向新目标,否则返回 false - */ - virtual bool isSlewing(const json ¶ms); - - /** - * @brief 获取当前赤经位置 - * @return 当前赤经位置 - */ - virtual std::string getCurrentRA(const json ¶ms); - - /** - * @brief 获取当前赤纬位置 - * @return 当前赤纬位置 - */ - virtual std::string getCurrentDec(const json ¶ms); - - /** - * @brief 开始跟踪运动目标 - * @param model 跟踪模式,包括恒星跟踪、太阳跟踪和月球跟踪 - * @param speed 跟踪速度,默认为1 - * @return 是否成功开始跟踪运动目标 - */ - virtual bool StartTracking(const json ¶ms); - - /** - * @brief 停止跟踪运动目标 - * @return 是否成功停止跟踪运动目标 - */ - virtual bool StopTracking(const json ¶ms); - - /** - * @brief 设置跟踪模式 - * @param mode 跟踪模式,包括恒星跟踪、太阳跟踪和月球跟踪 - * @return 是否成功设置跟踪模式 - */ - virtual bool setTrackingMode(const json ¶ms); - - /** - * @brief 设置跟踪速度 - * @param speed 跟踪速度 - * @return 是否成功设置跟踪速度 - */ - virtual bool setTrackingSpeed(const json ¶ms); - - /** - * @brief 获取当前跟踪模式 - * @return 当前跟踪模式,包括恒星跟踪、太阳跟踪和月球跟踪 - */ - virtual std::string getTrackingMode(const json ¶ms); - - /** - * @brief 获取当前跟踪速度 - * @return 当前跟踪速度 - */ - virtual std::string getTrackingSpeed(const json ¶ms); - - /** - * @brief 将望远镜回到家位置 - * @return 是否成功将望远镜回到家位置 - */ - virtual bool Home(const json ¶ms); - - /** - * @brief 判断望远镜是否在家位置 - * @return 返回 true 表示望远镜在家位置,否则返回 false - */ - virtual bool isAtHome(const json ¶ms); - - /** - * @brief 设置家位置 - * @return 是否成功设置家位置 - */ - virtual bool setHomePosition(const json ¶ms); - - /** - * @brief 获取望远镜是否可以回到家位置 - * @return 返回 true 表示望远镜可以回到家位置,否则返回 false - */ - virtual bool isHomeAvailable(const json ¶ms); - - /** - * @brief 停车 - * @return 是否成功停车 - */ - virtual bool Park(const json ¶ms); - - /** - * @brief 解除停车状态 - * @return 是否成功解除停车状态 - */ - virtual bool Unpark(const json ¶ms); - - /** - * @brief 判断望远镜是否在停车位置 - * @return 返回 true 表示位于停车位置,否则返回 false - */ - virtual bool isAtPark(const json ¶ms); - - /** - * @brief 设置停车位置 - * @return 是否成功设置停车位置 - */ - virtual bool setParkPosition(const json ¶ms); - - /** - * @brief 获取望远镜是否可以停车 - * @return 返回 true 表示望远镜可以停车,否则返回 false - */ - virtual bool isParkAvailable(const json ¶ms); - -protected: - /** - * @brief 清空状态 - * - */ - void ClearStatus(); - -protected: - // Hydrogen 回调函数 - void newDevice(HYDROGEN::BaseDevice dp) override; - void removeDevice(HYDROGEN::BaseDevice dp) override; - void newProperty(HYDROGEN::Property property) override; - void updateProperty(HYDROGEN::Property property) override; - void removeProperty(HYDROGEN::Property property) override {} - void newMessage(HYDROGEN::BaseDevice dp, int messageID) override; - void serverConnected() override; - void serverDisconnected(int exit_code) override; - - void newSwitch(HYDROGEN::PropertyViewSwitch *svp); - void newNumber(HYDROGEN::PropertyViewNumber *nvp); - void newText(HYDROGEN::PropertyViewText *tvp); - void newBLOB(HYDROGEN::PropertyViewBlob *bp); - -private: - // Hydrogen 客户端参数 - std::shared_ptr - m_connection_prop; // 连接属性指针 - std::shared_ptr - telescopeinfo_prop; // 望远镜信息属性指针 - std::shared_ptr - telescope_port; // 望远镜端口属性指针 - std::shared_ptr - rate_prop; // 望远镜速率属性指针 - std::shared_ptr telescope_prop; - HYDROGEN::BaseDevice telescope_device; // 望远镜设备指针 - - std::atomic_bool is_ready; // 是否就绪 - std::atomic_bool has_blob; // 是否有 BLOB 数据 - std::atomic_bool is_debug; - std::atomic_bool is_connected; - - std::string hydrogen_telescope_port = ""; // 望远镜所选端口 - std::string hydrogen_telescope_rate = ""; // 望远镜所选速率 - - std::string hydrogen_telescope_cmd; // Hydrogen 命令字符串 - std::string hydrogen_telescope_exec = ""; // Hydrogen 设备执行文件路径 - std::string hydrogen_telescope_version = ""; // Hydrogen 设备固件版本 - std::string hydrogen_telescope_interface = ""; // Hydrogen 接口版本 - - std::unique_ptr> - m_number_switch; - std::unique_ptr> - m_switch_switch; - std::unique_ptr> - m_text_switch; -}; diff --git a/driver/filterwheel/atom-touptek/_component.cpp b/driver/filterwheel/atom-touptek/_component.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/driver/filterwheel/atom-touptek/_component.hpp b/driver/filterwheel/atom-touptek/_component.hpp deleted file mode 100644 index e69de29b..00000000 diff --git a/driver/guider/atom-phd2/phd2client.cpp b/driver/guider/atom-phd2/phd2client.cpp deleted file mode 100644 index 987a7319..00000000 --- a/driver/guider/atom-phd2/phd2client.cpp +++ /dev/null @@ -1,674 +0,0 @@ -#include "phd2client.hpp" - -#include -#include -#include -#include "atom/log/loguru.hpp" - -#define CONNECT_CHECK(func) \ - if (!phd2_client->IsConnected()) \ - { \ - return false; \ - } \ - func - -using json = nlohmann::json; - -bool CommandDispatcher::HasHandler(const std::string &name) -{ - return handlers_.find(Djb2Hash(name.c_str())) != handlers_.end(); -} - -void CommandDispatcher::Dispatch(const std::string &name, const json &data) -{ - auto it = handlers_.find(Djb2Hash(name.c_str())); - if (it != handlers_.end()) - { - it->second(data); - } -} - -std::size_t CommandDispatcher::Djb2Hash(const char *str) -{ - std::size_t hash = 5381; - char c; - while ((c = *str++) != '\0') - { - hash = ((hash << 5) + hash) + static_cast(c); - } - return hash; -} - - -SocketClient::SocketClient() - : socket_(INVALID_SOCKET), isRunning_(false) -{ -#ifdef _WIN32 - WSADATA wsaData; - if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) - { - LOG_F(ERROR, "Failed to initialize Winsock"); - throw std::runtime_error("Failed to initialize Winsock"); - } -#endif -} - -SocketClient::~SocketClient() -{ - Disconnect(); -} - -bool SocketClient::Connect(const std::string &serverIP, int serverPort) -{ -#ifdef _WIN32 - socket_ = socket(AF_INET, SOCK_STREAM, 0); -#else - socket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); -#endif - if (socket_ == INVALID_SOCKET) - { - LOG_F(ERROR, "Failed to create socket"); -#ifdef _WIN32 - WSACleanup(); -#endif - throw std::runtime_error("Failed to create socket"); - } - - sockaddr_in serverAddress{}; - serverAddress.sin_family = AF_INET; - serverAddress.sin_port = htons(serverPort); - -#ifdef _WIN32 - if (InetPton(AF_INET, serverIP.c_str(), &(serverAddress.sin_addr)) <= 0) - { - LOG_F(ERROR, "Invalid server IP address"); - closesocket(socket_); - WSACleanup(); - throw std::runtime_error("Invalid server IP address"); - } -#else - if (inet_pton(AF_INET, serverIP.c_str(), &(serverAddress.sin_addr)) <= 0) - { - LOG_F(ERROR, "Invalid server IP address"); - throw std::runtime_error("Invalid server IP address"); - } -#endif - - if (connect(socket_, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) < 0) - { - LOG_F(ERROR, "Failed to connect to server"); - throw std::runtime_error("Failed to connect to server"); - } - - isRunning_ = true; - receiveThread_ = std::thread([&]() - { ReceiveThread(); }); - - return true; -} - -void SocketClient::Disconnect() -{ - if (socket_ != INVALID_SOCKET) - { -#ifdef _WIN32 - closesocket(socket_); -#else - close(socket_); -#endif - socket_ = INVALID_SOCKET; - } - - if (isRunning_) - { - isRunning_ = false; - if (receiveThread_.joinable()) - { - receiveThread_.join(); - } - } -} - -void SocketClient::Send(const std::string &message) -{ - if (socket_ == INVALID_SOCKET) - { - LOG_F(ERROR, "Not connected to server"); - return; - } - - if (send(socket_, message.c_str(), message.length(), 0) < 0) - { - LOG_F(ERROR, "Failed to send data"); - throw std::runtime_error("Failed to send data"); - } -} - -void SocketClient::SetMessageHandler(std::function handler) -{ - messageHandler_ = std::move(handler); -} - -bool SocketClient::IsConnected() const -{ - return socket_ != INVALID_SOCKET; -} - -void SocketClient::StopReceiveThread() -{ - if (isRunning_) - { - isRunning_ = false; - - // 等待接收线程退出 - std::unique_lock lock(mutex_); - cv_.wait(lock); - } -} - -void SocketClient::ReceiveThread() -{ - while (isRunning_) - { - char buffer[1024]; - memset(buffer, 0, sizeof(buffer)); - - int bytesRead = recv(socket_, buffer, sizeof(buffer) - 1, 0); - if (bytesRead <= 0) - { - if (bytesRead < 0) - { - LOG_F(ERROR, "Failed to receive data: %d", bytesRead); - } - else - { - DLOG_F(INFO, "Connection closed by server"); - } - break; - } - - std::string receivedData(buffer); - std::istringstream iss(receivedData); - std::string line; - - while (std::getline(iss, line)) - { - json jsonData; - try - { - jsonData = json::parse(line); - } - catch (const std::exception &e) - { - std::cerr << "Failed to parse JSON data: " << e.what() << std::endl; - continue; - } - - // 调用消息处理函数 - if (messageHandler_) - { - messageHandler_(jsonData); - } - } - } - - // 停止接收线程后通知等待的条件变量 - std::unique_lock lock(mutex_); - cv_.notify_all(); -} - -PHD2Client::PHD2Client() -{ - phd2_client = std::make_shared(); - m_CommandDispatcher = std::make_unique(); - - _is_star_locked = false; - _is_star_selected = false; - _is_calibrating = false; - _is_calibrated = false; - _is_selected = false; - - _is_guiding = false; - _is_looping = false; - _is_settling = false; - _is_settled = false; - _guiding_error = 0; - _dither_dx = 0; - _dither_dy = 0; - _last_error = ""; - - _is_calibration_flipped = false; - _calibrated_error = ""; - - _settle_error = ""; - _starlost_error = ""; - - RegisterFunc("Version", &PHD2Client::_version); - RegisterFunc("LockPositionSet", &PHD2Client::_lock_position_set); - RegisterFunc("Calibrating", &PHD2Client::_calibrating); - RegisterFunc("CalibrationComplete", &PHD2Client::_calibration_completed); - RegisterFunc("StarSelected", &PHD2Client::_star_selected); - RegisterFunc("StartGuiding", &PHD2Client::_start_guiding); - RegisterFunc("Paused", &PHD2Client::_paused); - RegisterFunc("StartCalibration", &PHD2Client::_start_calibration); - RegisterFunc("AppState", &PHD2Client::_app_state); - RegisterFunc("CalibrationFailed", &PHD2Client::_calibration_failed); - RegisterFunc("CalibrationDataFlipped", &PHD2Client::_calibration_data_flipped); - RegisterFunc("LockPositionShiftLimitReached", &PHD2Client::_lock_position_shift_limit_reached); - RegisterFunc("LoopingExposures", &PHD2Client::_looping_exposures); - RegisterFunc("LoopingExposuresStopped", &PHD2Client::_looping_exposures_stopped); - RegisterFunc("SettleBegin", &PHD2Client::_settle_begin); - RegisterFunc("Settling", &PHD2Client::_settling); - RegisterFunc("SettleDone", &PHD2Client::_settle_done); - RegisterFunc("StarLost", &PHD2Client::_star_lost); - RegisterFunc("GuidingStopped", &PHD2Client::_guiding_stopped); - RegisterFunc("Resumed", &PHD2Client::_resumed); - RegisterFunc("GuideStep", &PHD2Client::_guide_step); - RegisterFunc("GuidingDithered", &PHD2Client::_guiding_dithered); - RegisterFunc("LockPositionLost", &PHD2Client::_lock_position_lost); - RegisterFunc("Alert", &PHD2Client::_alert); - RegisterFunc("GuideParamChange", &PHD2Client::_guide_param_change); - RegisterFunc("ConfigurationChange", &PHD2Client::_configuration_change); - - phd2_client->SetMessageHandler(std::bind(&PHD2Client::parser_json, this, std::placeholders::_1)); -} - -PHD2Client::~PHD2Client() -{ -} - -bool PHD2Client::RunFunc(const std::string &name, const json ¶ms) -{ - if (m_CommandDispatcher->HasHandler(name)) - { - m_CommandDispatcher->Dispatch(name, params); - return true; - } - return false; -} - -bool PHD2Client::connect(const std::string &host, int port) -{ - return true; -} - -bool PHD2Client::disconnect() -{ - return true; -} - -bool PHD2Client::reconnect() -{ - return true; -} - -bool PHD2Client::is_connected() -{ - return _is_connected.load(); -} - -void PHD2Client::parser_json(const json &message) -{ - if (!message.empty()) - { - if (message.contains("Event")) - { - const std::string name = message["Event"].get(); - if (m_CommandDispatcher->HasHandler(name)) - { - m_CommandDispatcher->Dispatch(name, message); - } - } - } -} - -void PHD2Client::_version(const json &message) -{ - _host = message["Host"]; - _lightguiderversion = message["LGuiderVersion"]; - _subversion = message["LGuiderSubver"]; - _msgversion = message["MsgVersion"]; -} - -void PHD2Client::_lock_position_set(const json &message) -{ - _star_position["X"] = message["X"]; - _star_position["Y"] = message["Y"]; - _is_star_locked = true; -} - -void PHD2Client::_calibrating(const json &message) -{ - _calibrated_status["direction"] = message["dir"]; - _calibrated_status["distance"] = message["dist"]; - _calibrated_status["dx"] = message["dx"]; - _calibrated_status["dy"] = message["dy"]; - _calibrated_status["position"] = message["pos"]; - _calibrated_status["stop"] = message["step"]; - _calibrated_status["state"] = message["State"]; -} - -void PHD2Client::_calibration_completed(const json &message) -{ - _mount = message["Mount"]; -} - -void PHD2Client::_star_selected(const json &message) -{ - _star_position["X"] = message["X"]; - _star_position["Y"] = message["Y"]; - _is_star_selected = true; -} - -void PHD2Client::_start_guiding(const json &message) -{ - _is_guiding = true; -} - -void PHD2Client::_paused(const json &message) -{ - _is_guiding = false; - _is_calibrating = false; -} - -void PHD2Client::_start_calibration(const json &message) -{ - _mount = message["Mount"]; - _is_calibrating = true; - _is_guiding = false; -} - -void PHD2Client::_app_state(const json &message) -{ - std::string state = message["State"]; - - if (state == "Stopped") - { - _is_calibrating = false; - _is_looping = false; - _is_guiding = false; - _is_settling = false; - } - else if (state == "Selected") - { - _is_selected = true; - _is_looping = false; - _is_guiding = false; - _is_settling = false; - _is_calibrating = false; - } - else if (state == "Calibrating") - { - _is_calibrating = true; - _is_guiding = false; - } - else if (state == "Guiding") - { - _is_guiding = true; - _is_calibrating = false; - } - else if (state == "LostLock") - { - _is_guiding = true; - _is_star_locked = false; - } - else if (state == "Paused") - { - _is_guiding = false; - _is_calibrating = false; - } - else if (state == "Looping") - { - _is_looping = true; - } -} - -void PHD2Client::_calibration_failed(const json &message) -{ - _calibrated_error = message["Reason"]; - _is_calibrating = false; - _is_calibrated = false; -} - -void PHD2Client::_calibration_data_flipped(const json &message) -{ - _is_calibration_flipped = true; -} - -void PHD2Client::_lock_position_shift_limit_reached(const json &message) -{ - DLOG_F(WARNING, "Star locked position reached the edge of the camera frame"); -} - -void PHD2Client::_looping_exposures(const json &message) -{ - _is_looping = true; -} - -void PHD2Client::_looping_exposures_stopped(const json &message) -{ - _is_looping = false; -} - -void PHD2Client::_settle_begin(const json &message) -{ - _is_settling = true; -} - -void PHD2Client::_settling(const json &message) -{ - _settle_status["distance"] = message["Distance"]; - _settle_status["time"] = message["SettleTime"]; - _settle_status["locked"] = message["StarLocked"]; - _is_settling = true; -} - -void PHD2Client::_settle_done(const json &message) -{ - int status = message["Status"]; - - if (status == 0) - { - DLOG_F(INFO, "Settle succeeded"); - _is_settled = true; - } - else - { - _settle_error = message["Error"]; - DLOG_F(INFO, "Settle failed, error: {}", message["Error"].dump(4)); - _is_settled = false; - } - _is_settling = false; -} - -void PHD2Client::_star_lost(const json &message) -{ - _starlost_status["snr"] = message["SNR"]; - _starlost_status["star_mass"] = message["StarMass"]; - _starlost_status["avg_dist"] = message["AvgDist"]; - _starlost_error = message["Status"]; - - LOG_F(ERROR, "Star Lost, SNR: {}, StarMass: {}, AvgDist: {}", - _starlost_status["snr"], _starlost_status["star_mass"], _starlost_status["avg_dist"]); - - _is_guiding = false; - _is_calibrating = false; -} - -void PHD2Client::_guiding_stopped(const json &message) -{ - _is_guiding = false; - DLOG_F(INFO, "Guiding Stopped"); -} - -void PHD2Client::_resumed(const json &message) -{ - DLOG_F(INFO, "Guiding Resumed"); - _is_guiding = true; -} - -void PHD2Client::_guide_step(const json &message) -{ - _mount = message["Mount"]; - DLOG_F(INFO, "Guide step mount: %d", _mount); - _guiding_error = message["ErrorCode"]; - DLOG_F(INFO, "Guide step error: %d", _guiding_error); - - _guiding_status["avg_dist"] = message["AvgDist"]; - DLOG_F(INFO, "Guide step average distance: %f", _guiding_status["avg_dist"]); - - _guiding_status["dx"] = message["dx"]; - DLOG_F(INFO, "Guide step dx: %f", _guiding_status["dx"]); - _guiding_status["dy"] = message["dy"]; - DLOG_F(INFO, "Guide step dy: %f", _guiding_status["dy"]); - - _guiding_status["ra_raw_distance"] = message["RADistanceRaw"]; - DLOG_F(INFO, "Guide step RADistanceRaw: %f", _guiding_status["ra_raw_distance"]); - _guiding_status["dec_raw_distance"] = message["DECDistanceRaw"]; - DLOG_F(INFO, "Guide step DECDistanceRaw: %f", _guiding_status["dec_raw_distance"]); - - _guiding_status["ra_distance"] = message["RADistanceGuide"]; - DLOG_F(INFO, "Guide step RADistanceGuide: %f", _guiding_status["ra_distance"]); - _guiding_status["dec_distance"] = message["DECDistanceGuide"]; - DLOG_F(INFO, "Guide step DECDistanceGuide: %f", _guiding_status["dec_distance"]); - - _guiding_status["ra_duration"] = message["RADuration"]; - DLOG_F(INFO, "Guide step RADuration: %f", _guiding_status["ra_duration"]); - _guiding_status["dec_duration"] = message["DECDuration"]; - DLOG_F(INFO, "Guide step DECDuration: %f", _guiding_status["dec_duration"]); - - _guiding_status["ra_direction"] = message["RADirection"]; - DLOG_F(INFO, "Guide step RADirection: %f", _guiding_status["ra_direction"]); - _guiding_status["dec_direction"] = message["DECDirection"]; - DLOG_F(INFO, "Guide step DECDirection: %f", _guiding_status["dec_direction"]); - - _guiding_status["snr"] = message["SNR"]; - DLOG_F(INFO, "Guide step SNR: %f", _guiding_status["snr"]); - _guiding_status["starmass"] = message["StarMass"]; - DLOG_F(INFO, "Guide step StarMass: %f", _guiding_status["starmass"]); - _guiding_status["hfd"] = message["HFD"]; - DLOG_F(INFO, "Guide step HFD: %f", _guiding_status["hfd"]); -} - -void PHD2Client::_guiding_dithered(const json &message) -{ - _dither_dx = message["dx"]; - _dither_dy = message["dy"]; -} - -void PHD2Client::_lock_position_lost(const json &message) -{ - _is_star_locked = true; - LOG_F(ERROR, "Star Lock Position Lost"); -} - -void PHD2Client::_alert(const json &message) -{ - _last_error = message["Msg"]; - LOG_F(ERROR, "Alert: %s", _last_error.c_str()); -} - -void PHD2Client::_guide_param_change(const json &message) -{ -} - -void PHD2Client::_configuration_change(const json &message) -{ -} - -json PHD2Client::GenerateCommand(const std::string &command, const json ¶ms) -{ - json res = {{"method", command}, {"id", 1}}; - if (!params.empty()) - { - res["params"] = params; - } - return res; -} - -bool PHD2Client::SendCommand(const json &command) -{ - CONNECT_CHECK( - phd2_client->Send(command.dump()); - return true;) -} - -bool PHD2Client::GetProfiles() -{ - CONNECT_CHECK( - return SendCommand(GenerateCommand("get_profiles", {}));) -} - -bool PHD2Client::GetCurrentProfile() -{ - CONNECT_CHECK( - return SendCommand(GenerateCommand("get_profile", {}));) -} - -bool PHD2Client::SetProfile(int profileId) -{ - CONNECT_CHECK( - return SendCommand(GenerateCommand("set_profile", {{"profile_id", profileId}}));) -} - -bool PHD2Client::generateProfile(const json &profile) -{ - CONNECT_CHECK( - std::string name = profile["name"]; - int id = profile["id"]; - std::string camera = profile["camera"]; - std::string mount = profile["mount"]; - - if (name.empty() || id == 0 || camera.empty() || mount.empty()) { - return false; - } - - return true;) -} - -bool PHD2Client::exportProfile() -{ - CONNECT_CHECK( - return SendCommand(GenerateCommand("export_config_settings", {}));) -} - -bool PHD2Client::connectDevice() -{ - CONNECT_CHECK( - if (_current_profile.empty()) { - return false; - } - - json command; - command["command"] = "set_connected"; - command["params"] = true; - - return SendCommand(command);) -} - -bool PHD2Client::disconnectDevice() -{ - CONNECT_CHECK( - json command; - command["command"] = "set_connected"; - command["params"] = false; - return SendCommand(command);) -} - -bool PHD2Client::reconnectDevice() -{ - CONNECT_CHECK( - if (disconnectDevice()) { - std::this_thread::sleep_for(std::chrono::seconds(1)); - return connectDevice(); - } return false;) -} - -bool PHD2Client::checkConnected() -{ - CONNECT_CHECK( - json command; - command["command"] = "get_connected"; - command["params"] = json::object(); - return SendCommand(command);) -} diff --git a/driver/guider/atom-phd2/phd2client.hpp b/driver/guider/atom-phd2/phd2client.hpp deleted file mode 100644 index 529ca8dd..00000000 --- a/driver/guider/atom-phd2/phd2client.hpp +++ /dev/null @@ -1,271 +0,0 @@ -#ifndef SOCKETCLIENT_H_ -#define SOCKETCLIENT_H_ - -#include -#include -#include -#include -#include -#include - -#include "atom/type/json.hpp" -using json = nlohmann::json; - -/** - * @brief 类 VCommandDispatcher 负责命令的派发和处理。 - */ -class CommandDispatcher -{ -public: - /** - * @brief HandlerFunc 是用于处理命令的函数类型。 - * - * 该函数应该接受一个 `json` 类型的参数,表示命令所携带的数据。 - */ - using HandlerFunc = std::function; - - /** - * @brief RegisterHandler 函数用于将一个命令处理程序注册到 `CommandDispatcher` 中。 - * - * @tparam ClassType 命令处理程序所属的类类型。 - * @param name 命令的名称。 - * @param handler 处理命令的成员函数指针。 - * @param instance 处理命令的对象指针。 - */ - template - void RegisterHandler(const std::string &name, void (ClassType::*handler)(const json &), ClassType *instance) - { - auto hash_value = Djb2Hash(name.c_str()); - handlers_[hash_value] = std::bind(handler, instance, std::placeholders::_1); - } - - /** - * @brief HasHandler 函数用于检查是否有名为 `name` 的命令处理程序。 - * - * @param name 要检查的命令名称。 - * @return 如果存在名为 `name` 的命令处理程序,则返回 `true`;否则返回 `false`。 - */ - bool HasHandler(const std::string &name); - - /** - * @brief Dispatch 函数用于派发一个命令,并将它交给相应的处理程序处理。 - * - * @param name 要派发的命令的名称。 - * @param data 命令所携带的数据。 - */ - void Dispatch(const std::string &name, const json &data); - -private: - /** - * @brief handlers_ 是一个哈希表,存储了所有已注册的命令处理程序。 - * - * 键值为哈希值,值为命令处理程序本身。 - */ -#if ENABLE_FASTHASH - emhash8::HashMap handlers_; -#else - std::unordered_map handlers_; -#endif - - /** - * @brief Djb2Hash 函数是一个字符串哈希函数,用于将字符串转换成哈希值。 - * - * @param str 要转换的字符串。 - * @return 转换后的哈希值。 - */ - static std::size_t Djb2Hash(const char *str); -}; - -#ifdef _WIN32 -#include -#include -#else -#include -#include -#include -#define SOCKET int -#define INVALID_SOCKET -1 -#endif - -/** - * @brief Represents a client socket for connecting to a server. - */ -class SocketClient -{ -public: - /** - * @brief Default constructor. - */ - SocketClient(); - - /** - * @brief Destructor. - */ - ~SocketClient(); - - /** - * @brief Connects to the specified server. - * - * @param serverIP The IP address of the server. - * @param serverPort The port number of the server. - * @return `true` if the connection is successful, `false` otherwise. - */ - bool Connect(const std::string &serverIP, int serverPort); - - /** - * @brief Disconnects from the server. - */ - void Disconnect(); - - /** - * @brief Sends a message to the server. - * - * @param message The message to send. - */ - void Send(const std::string &message); - - /** - * @brief Sets the message handler function to process received messages. - * - * @param handler The message handler function. - */ - void SetMessageHandler(std::function handler); - - /** - * @brief Checks if the client is currently connected to the server. - * - * @return `true` if connected, `false` otherwise. - */ - bool IsConnected() const; - - /** - * @brief Stops the receive thread. - */ - void StopReceiveThread(); - -private: - SOCKET socket_; ///< The socket descriptor. - std::thread receiveThread_; ///< The receive thread. - std::function messageHandler_; ///< The message handler function. - bool isRunning_; ///< Flag to indicate if the client is running. - std::mutex mutex_; ///< Mutex for thread synchronization. - std::condition_variable cv_; ///< Conditional variable for thread synchronization. - - /** - * @brief The receive thread function. - */ - void ReceiveThread(); -}; - -class PHD2Client -{ -public: - PHD2Client(); - ~PHD2Client(); - - bool connect(const std::string &host = "127.0.0.1", int port = 4400); - bool disconnect(); - bool reconnect(); - bool is_connected(); - - bool start_guiding(); - - void parser_json(const json &message); - - void _version(const json &message); - void _lock_position_set(const json &message); - void _calibrating(const json &message); - void _calibration_completed(const json &message); - void _star_selected(const json &message); - void _start_guiding(const json &message); - void _paused(const json &message); - void _start_calibration(const json &message); - void _app_state(const json &message); - void _calibration_failed(const json &message); - void _calibration_data_flipped(const json &message); - void _lock_position_shift_limit_reached(const json &message); - void _looping_exposures(const json &message); - void _looping_exposures_stopped(const json &message); - void _settle_begin(const json &message); - void _settling(const json &message); - void _settle_done(const json &message); - void _star_lost(const json &message); - void _guiding_stopped(const json &message); - void _resumed(const json &message); - void _guide_step(const json &message); - void _guiding_dithered(const json &message); - void _lock_position_lost(const json &message); - void _alert(const json &message); - void _guide_param_change(const json &message); - void _configuration_change(const json &message); - - json GenerateCommand(const std::string &command, const json ¶ms); - bool SendCommand(const json &command); - - bool GetProfiles(); - bool GetCurrentProfile(); - bool SetProfile(int profileId); - bool generateProfile(const json &profile); - bool exportProfile(); - - bool connectDevice(); - bool disconnectDevice(); - bool reconnectDevice(); - bool checkConnected(); - -private: - std::shared_ptr phd2_client; - std::unique_ptr m_CommandDispatcher; - - template - void RegisterFunc(const std::string &name, void (ClassType::*handler)(const json &)) - { - m_CommandDispatcher->RegisterHandler(name, handler, this); - } - - bool RunFunc(const std::string &name, const json ¶ms); - - std::atomic_bool _is_connected; - - std::string _host; - std::string _lightguiderversion; - std::string _subversion; - std::string _msgversion; - - // 与选星和校准相关的变量 - std::map _star_position; - std::map _calibrated_status; - std::string _mount; - bool _is_star_locked; - bool _is_star_selected; - bool _is_calibrating; - bool _is_calibrated; - bool _is_selected; - - std::string _current_profile; - - // 与循迹、反漂和向导错误相关的变量 - bool _is_guiding; - bool _is_looping; - bool _is_settling; - bool _is_settled; - int _guiding_error; - std::map _guiding_status; - int _dither_dx; - int _dither_dy; - std::string _last_error; - - // 与标定过程相关的变量 - bool _is_calibration_flipped; - std::string _calibrated_error; - - // 与调焦状态相关的变量 - std::map _settle_status; - std::string _settle_error; - - // 与星体丢失相关的变量 - std::map _starlost_status; - std::string _starlost_error; -}; - -#endif /* SOCKETCLIENT_H_ */ diff --git a/driver/guider/atom-phd2/phd2client.py b/driver/guider/atom-phd2/phd2client.py deleted file mode 100644 index 7874c8bc..00000000 --- a/driver/guider/atom-phd2/phd2client.py +++ /dev/null @@ -1,2167 +0,0 @@ -# coding=utf-8 - -""" - -Copyright(c) 2022-2023 Max Qian - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License version 3 as published by the Free Software Foundation. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. -You should have received a copy of the GNU Library General Public License -along with this library; see the file COPYING.LIB. If not, write to -the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -Boston, MA 02110-1301, USA. - -""" - -# ################################################################# -# -# This file is part of LightAPT server , which contains the functionality -# of the communication between the server and the lightguider , via asynchronous -# socket communication -# -# ################################################################# - -import asyncio -import json -import os -import platform -import selectors -import socket -import threading - -from logger import lightguider_logger as logger - -class switch(object): - """switch function NOTE : must call break after all""" - def __init__(self, value): - self.value = value - self.fall = False - - def __iter__(self): - """Return the match method once, then stop""" - yield self.match - raise StopIteration - - def match(self, *args): - """Indicate whether or not to enter a case suite""" - if self.fall or not args: - return True - elif self.value in args: - self.fall = True - return True - else: - return False - - -def check_process_exist(process_name: str) -> bool: - """ - Check if process exists - Args: - process_name : str - Returns: bool - """ - #for proc in psutil.process_iter(): - # if proc.name() == process_name: - # return True - return True - - -class TcpSocket(object): - """ - TCP socket client interface - """ - - def __init__(self): - self.lines = [] - self.buf = b"" - self.sock = None - self.sel = None - self.terminate = False - - def __del__(self): - self.disconnect() - - def connect(self, hostname: str, port: int) -> bool: - self.sock = socket.socket() - try: - self.sock.connect((hostname, port)) - self.sock.setblocking(False) # non-blocking - self.sel = selectors.DefaultSelector() - self.sel.register(self.sock, selectors.EVENT_READ) - except Exception: - self.sel = None - self.sock = None - raise OSError - - def disconnect(self) -> None: - if self.sel is not None: - self.sel.unregister(self.sock) - self.sel = None - if self.sock is not None: - self.sock.close() - self.sock = None - - def is_connected(self) -> bool: - return self.sock is not None - - def terminate(self) -> None: - self.terminate = True - - def read(self): - while not self.lines: - while True: - if self.terminate: - return "" - events = self.sel.select(0.5) - if events: - break - s = self.sock.recv(4096) - i0 = 0 - i = i0 - while i < len(s): - if s[i] == b"\r"[0] or s[i] == b"\n"[0]: - self.buf += s[i0:i] - if self.buf: - self.lines.append(self.buf) - self.buf = b"" - i += 1 - i0 = i - else: - i += 1 - self.buf += s[i0:i] - return self.lines.pop(0) - - def send(self, s: str) -> bool: - b = s.encode() - totsent = 0 - while totsent < len(b): - sent = self.sock.send(b[totsent:]) - if sent == 0: - return False - totsent += sent - return True - - -class lightguiderClientWorker(object): - """ - lightguider client but alse worker , all of the functions should be asynchronous. - And do not exist any blocking operations , such as 'while True' - """ - - def __init__(self) -> None: - """ - Initialize the lightguider client instance and prepare the connection - Args : None - Returns : None - """ - self.client = TcpSocket() - self.conn = TcpSocket() - self._background_task = None - self.response = None - self.lock = threading.Lock() - self.cond = threading.Condition() - - self._host = None - self._lightguiderversion = None - self._subversion = None - self._msgversion = None - - self._is_server_connected = False - self._is_device_connected = False - self._terminated = False - - self._profiles = None - self._current_profile = {} - - self._mount = None - - self._is_calibrating = False - self._is_calibrated = False - self._is_calibration_flipped = False - self._calibrated_data = {} - self._calibrated_status = {} - self._calibrated_error = "" - - self._is_star_found = False - self._is_star_locked = False - self._is_star_selected = False - self._star_position = [0, 0] - - self._is_guiding = False - self._guiding_error = "" - self._guiding_status = {} - - self._is_settling = False - self._is_selected = False - self._settle_status = {} - self._settle_error = "" - - self._starlost_status = {} - self._starlost_error = "" - - self._is_looping = False - - self._dither_dx = None - self._dither_dy = None - - self._exposure = None - - self._is_cooling = False - self._target_temperature = None - self._current_temperature = None - self._coolig_power = None - - self._last_error = "" - - self.lightguider_instance = None - - def __del__(self) -> None: - """ - Destructor of the lightguider client instance - """ - - def __str__(self) -> str: - """ - Returns a string representation of the client instance - """ - return "LightAPT lightguider Client and Network Worker" - - # ################################################################# - # - # Http Request Handler Functions - # - # ################################################################# - - # ################################################################# - # Start or stop the lightguider server - # ################################################################# - - async def start_lightguider(self, path=None) -> dict: - """ - Start the lightguider server with the specified path - Args : - path : str # full path to the lightguider server executable - Returns : { - "message": str # None if the operation was successful - } - """ - res = {"message": None} - # Check if the instance had already been created - if self.lightguider_instance is not None: - logger.warning("lightguider instance had already been created") - res["message"] = "lightguider instance had already been created" - return res - lightguider_path = None - # judge the system type - if platform.platform() == "Windows": - if path is None: - lightguider_path = "C:\Program Files (x86)\LGuiderGuiding2\lightguider.exe" - else: - lightguider_path = path - elif platform.platform() == "Linux": - if path is None: - lightguider_path = "/usr/bin/lightguider" - else: - lightguider_path = path - logger.debug("lightguider executable path : {}".format(lightguider_path)) - - # Check whether the executable is existing - if not os.path.exists(lightguider_path): - logger.error("lightguider executable path does not exist: {}".format(lightguider_path)) - self.lightguider_instance = asyncio.subprocess.create_subprocess_exec( - program=lightguider_path, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - - logger.info("lightguider server started successfully") - res["message"] = "lightguider server started successfully" - return res - - async def stop_lightguider(self) -> dict: - """ - Stop the lightguider server - Args : None - Returns : { - "message" : str - } - """ - res = {"message": None} - if self.lightguider_instance is None: - logger.error("No lightguider instance running on this machine") - res["message"] = "No lightguider instance running on this machine" - return res - try: - self.lightguider_instance.close() - except Exception as e: - logger.error("Failed to close lightguider instance : {}".format(e)) - res["message"] = "Failed to close lightguider instance" - return res - logger.info("lightguider server stopped successfully") - res["message"] = "lightguider server stopped successfully" - return res - - async def check_started(self) -> dict: - """ - Check if the lightguider server had already started - Args : None - Returns : { - "status" : bool - } - """ - return {"status": check_process_exist("lightguider")} - - async def scan_server(self, start_port=4400, end_port=4405) -> dict: - """ - Scan the lightguider server available in the specified port - Args: - start_port : int - end_port : int - Returns:{ - "list" : [] # a list of the ports which servers are listening on - } - """ - res = {"list": []} - if ( - start_port > end_port - or not isinstance(start_port, int) - or not isinstance(end_port, int) - ): - logger.error("Invalid port was specified") - return res - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - for port in range(start_port, end_port + 1): - try: - sock.bind(("localhost", port)) - sock.shutdown(2) - res["list"].append(port) - except socket.error: - pass - logger.debug("Found {} servers".format(len(res["list"]))) - return res - - async def connect_server(self, host="localhost", port=4400) -> dict: - """ - Connect to the lightguider server on the specified port - Args : - host : str # hostname of the lightguider server , default is "localhost" - port : int # port number of the server , default is 4400 - Returns : { - "message": str # None if the operation is successful - } - """ - resp = {"message": None} - - if self.conn.is_connected(): - resp["message"] = "Server had already connected" - return resp - if not isinstance(host, str) or not isinstance(port, int): - resp["message"] = "Invalid host or port were specified" - return resp - try: - self._terminated = False - self.conn.connect(host, port) - self._is_server_connected = True - # Start a standalone thread to listen to the lightguider server - self._background_task = threading.Thread(target=self.background_task) - self._background_task.daemon = True - self._background_task.start() - except OSError: - resp["message"] = "Failed to connect to the lightguider server" - return resp - resp["message"] = "Connected to lightguider server successfully" - return resp - - async def disconnect_server(self) -> dict: - """ - Disconnects from the lightguider server - Args : None - Returns : { - "message": str # None if the operation is successful - } - """ - res = {"message": None} - if not self._is_server_connected: - res[ - "message" - ] = "Server is not connected , please do not execute this operation" - return res - try: - self.conn.disconnect() - self._terminated = True - except Exception: - res["message"] = "Failed to disconnect from the lightguider server" - return res - self._is_server_connected = False - res["message"] = "Disconnected from the lightguider server successfully" - return res - - async def reconnect_server(self) -> dict: - """ - Reconnects to the lightguider server - Args : None - Returns : { - "message": str # None if the operation is successful - } - """ - res = {"message": None} - if not self._is_server_connected and not self.conn.is_connected(): - res[ - "message" - ] = "Server is not connected , please connect before reconnecting" - return res - # Terminate the background thread first - self._terminated = True - # Close the socket connection - self.conn.disconnect() - # Then reconnect to the server - self.conn.connect() - # Restart the background thread - self._terminated = False - self._background_task = threading.Thread(target=self.background_task) - self._background_task.start() - - # ################################################################# - # lightguider Listener - # ################################################################# - - def background_task(self) -> None: - """ - Background task listen server message | 获取lightguider信息 - Args : None - Returns : None - """ - while not self._terminated: - line = self.conn.read() - if not line and not self.terminate: - break - try: - j = json.loads(line) - except json.JSONDecodeError: - continue - if "jsonrpc" in j: - with self.cond: - self.response = j - self.cond.notify() - else: - asyncio.run(self.parser_json(j)) - - async def generate_command(self, command: str, params: dict) -> dict: - """ - Generate command to send to the lightguider server - Args: - command : str - params : dict - Returns : dict - """ - res = {"method": command, "id": 1} - if params is not None: - if isinstance(params, (list, dict)): - res["params"] = params - else: - res["params"] = [params] - return res - - async def send_command(self, command: dict) -> dict: - """ - Send command to the lightguider server - Args: - command : dict - Returns : bool - """ - r = json.dumps(command, separators=(",", ":")) - self.conn.send(r + "\r\n") - # wait for response - with self.cond: - while not self.response: - self.cond.wait(timeout=30) - response = self.response - self.response = None - if "error" in response: - logger.error( - "Guiding Error : {})".format(response.get("error").get("message")) - ) - return response - - async def parser_json(self, message) -> None: - """ - Parser the JSON message received from the server - Args : message : JSON message - Returns : None - """ - if message is None: - return - event = message.get("Event") - - for case in switch(event): - if case("Version"): - await self.__version(message) - break - if case("LockPositionSet"): - await self.__lock_position_set(message) - break - if case("Calibrating"): - await self.__calibrating(message) - break - if case("CalibrationComplete"): - await self.__calibration_completed(message) - break - if case("StarSelected"): - await self.__star_selected(message) - break - if case("StartGuiding"): - await self.__start_guiding() - break - if case("Paused"): - await self.__paused() - break - if case("StartCalibration"): - await self.__start_calibration(message) - break - if case("AppState"): - await self.__app_state(message) - break - if case("CalibrationFailed"): - await self.__calibration_failed(message) - break - if case("CalibrationDataFlipped"): - await self.__calibration_data_flipped(message) - break - if case("LockPositionShiftLimitReached"): - await self.__lock_position_shift_limit_reached() - break - if case("LoopingExposures"): - await self.__looping_exposures(message) - break - if case("LoopingExposuresStopped"): - await self.__looping_exposures_stopped() - break - if case("SettleBegin"): - await self.__settle_begin() - break - if case("Settling"): - await self.__settling(message) - break - if case("SettleDone"): - await self.__settle_done(message) - break - if case("StarLost"): - await self.__star_lost(message) - break - if case("GuidingStopped"): - await self.__guiding_stopped() - break - if case("Resumed"): - await self.__resumed() - break - if case("GuideStep"): - await self.__guide_step(message) - break - if case("GuidingDithered"): - await self.__guiding_dithered(message) - break - if case("LockPositionLost"): - await self.__lock_position_lost() - break - if case("Alert"): - await self.__alert(message) - break - if case("GuideParamChange"): - await self.__guide_param_change(message) - break - if case("ConfigurationChange"): - await self.__configuration_change() - break - logger.error(f"Unknown event : {event}") - break - - # ################################################################# - # Profiles Manager - # ################################################################# - - async def get_profiles(self) -> dict: - """ - Get all profiles available on the lightguider server - Args : None - Returns : { - "list" : list # a list of profiles - "message" : str # None if succeeded - } - NOTE : If no profiles are available , just return an empty list - """ - resp = {"list": None, "message": None} - if not self._is_server_connected: - resp["message"] = "Server is not connected" - return resp - command = await self.generate_command("get_profiles", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to get profiles" - resp["error"] = res.get("error") - return resp - self._profiles = res.get("result") - logger.debug("All of the profile : {}".format(self._profiles)) - resp["list"] = self._profiles - return resp - - async def get_current_profile(self) -> dict: - """ - Get the current profile of the lightguider server - Args : None - Returns : { - "profile" : dict - } - """ - resp = {"message": None, "profile": None} - command = await self.generate_command("get_profile", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["error"] = res.get("error") - resp["message"] = "Failed to get current profile" - return resp - self._current_profile["id"] = res.get("result").get("id") - logger.debug("Current profile id : {}".format(self._current_profile["id"])) - # Check if the profile list is empty - if not self._profiles: - await self.get_profiles() - for itme in self._profiles: - if itme["id"] == self._current_profile["id"]: - self._current_profile["name"] = itme["name"] - # Get the devices settings in the profile - command = await self.generate_command("get_current_equipment", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["error"] = res.get("error") - resp["message"] = "Failed to get devices settings in the current profile" - return resp - try: - self._current_profile["camera"] = ( - res.get("result").get("camera").get("name") - ) - self._current_profile["mount"] = res.get("result").get("mount").get("name") - except KeyError: - pass - resp["profile"] = self._current_profile - return resp - - async def set_profile(self, profile_id: int) -> dict: - """ - Set the profile of the lightguider server - Args : - profile_id : int - Returns : { - "message" : str # None if succeeded - } - """ - resp = { - "message": None, - "error": None, - } - if not isinstance(profile_id, int): - resp["message"] = "Invalid profile_id was specified" - return resp - command = await self.generate_command("set_profile", [profile_id]) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to set the profile" - resp["error"] = res.get("error") - return resp - self._current_profile["id"] = profile_id - return resp - - async def generate_profile(self, profile={}) -> dict: - """ - Generates a profile for the given parameters - Args : - profile : { - "name" : str # Name of the profile - "id" : int # Id of the profile - "camera" : str # Camera type - "mount" : str # Mount type - } - Returns : { - "message" : str # None if succeeded - } - """ - resp = { - "message": None, - "error": None, - } - - _name = profile.get("name") - _id = profile.get("id") - _camera = profile.get("camera") - _mount = profile.get("mount") - - if not _name or not _id or not _camera or not _mount: - resp[ - "message" - ] = "Please provide all of the required parameters for a profile" - return resp - if ( - not isinstance(_name, str) - or not isinstance(_id, int) - or not isinstance(_camera, str) - or not isinstance(_mount, str) - ): - resp["message"] = "Invalid profile parameters were specified" - return resp - """" - LGuider Profile 1 - /auto_exp/exposure_max 1 5000 - /auto_exp/exposure_min 1 1000 - /auto_exp/target_snr 1 6 - /CalStepCalc/GuideSpeed 1 0.5 - /camera/AutoLoadDarks 1 1 - /camera/AutoLoadDefectMap 1 1 - /camera/binning 1 1 - /camera/gain 1 95 - /camera/LastMenuchoice 1 Simulator - /camera/pixelsize 1 9.8 - /camera/SaturationADU 1 0 - /camera/SaturationByADU 1 1 - /camera/TimeoutMs 1 15000 - /camera/UseSubframes 1 0 - /frame/var_delay/enabled 1 0 - /frame/var_delay/long_delay 1 10000 - /frame/var_delay/short_delay 1 1000 - /frame/focalLength 1 100 - /frame/timeLapse 1 0 - /guider/multistar/enabled 1 1 - /guider/onestar/MassChangeThreshold 1 0.5 - /guider/onestar/MassChangeThresholdEnabled 1 0 - /guider/onestar/SearchRegion 1 15 - /guider/onestar/TolerateJumpsEnabled 1 0 - /guider/onestar/TolerateJumpsThreshold 1 4 - /guider/AutoSelDownsample 1 0 - /guider/FastRecenter 1 1 - /guider/ScaleImage 1 1 - /guider/StarMinHFD 1 1.5 - /guider/StarMinSNR 1 6 - /ImageLogger/ErrorThreshPx 1 4 - /ImageLogger/ErrorThreshRel 1 4 - /ImageLogger/LogAutoSelectFrames 1 0 - /ImageLogger/LogFramesDropped 1 0 - /ImageLogger/LogFramesOverThreshPx 1 0 - /ImageLogger/LogFramesOverThreshRel 1 0 - /ImageLogger/LoggingEnabled 1 0 - /indi/INDIcam 1 CCD Simulator - /indi/INDIcam_ccd 1 1 - /indi/INDIcam_forceexposure 1 0 - /indi/INDIcam_forcevideo 1 0 - /indi/INDIhost 1 localhost - /indi/INDIport 1 7624 - /indi/VerboseLogging 1 1 - /overlay/slit/angle 1 0 - /overlay/slit/center.x 1 376 - /overlay/slit/center.y 1 290 - /overlay/slit/height 1 100 - /overlay/slit/width 1 8 - /rotator/LastMenuChoice 1 - /scope/GuideAlgorithm/X/Hysteresis/aggression 1 0.7 - /scope/GuideAlgorithm/X/Hysteresis/hysteresis 1 0.1 - /scope/GuideAlgorithm/X/Hysteresis/minMove 1 0.16 - /scope/GuideAlgorithm/Y/ResistSwitch/aggression 1 1 - /scope/GuideAlgorithm/Y/ResistSwitch/fastSwitch 1 1 - /scope/GuideAlgorithm/Y/ResistSwitch/minMove 1 0.16 - /scope/AssumeOrthogonal 1 0 - /scope/BacklashCompEnabled 1 0 - /scope/CalFlipRequiresDecFlip 1 0 - /scope/CalibrationDistance 1 49 - /scope/CalibrationDuration 1 10000 - /scope/DecBacklashCeiling 1 20 - /scope/DecBacklashFloor 1 20 - /scope/DecBacklashPulse 1 20 - /scope/DecGuideMode 1 1 - /scope/HiResEncoders 1 0 - /scope/LastAuxMenuChoice 1 - /scope/LastMenuChoice 1 On-camera - /scope/MaxDecDuration 1 2500 - /scope/MaxRaDuration 1 2500 - /scope/StopGuidingWhenSlewing 1 0 - /scope/UseDecComp 1 1 - /scope/XGuideAlgorithm 1 1 - /scope/YGuideAlgorithm 1 4 - /stepguider/LastMenuChoice 1 - /AutoLoadCalibration 1 0 - /DitherMode 1 0 - /DitherRaOnly 1 0 - /DitherScaleFactor 1 1 - /ExposureDurationMs 1 1000 - /name 1 aaa - /NoiseReductionMethod 1 0 - """ - - async def export_profile(self) -> dict: - """ - Export the profile - Args : None - Returns : { - "message" : str - } - """ - resp = {"message": None} - - if not self._is_server_connected: - resp["message"] = "Server is not connected" - return resp - command = await self.generate_command("export_config_settings", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to export the profile" - resp["error"] = res.get("error") - return resp - return resp - - async def delete_profile(self, name: str) -> dict: - """ - Delete the profile by name - Args : - name : str # the name of the profile - Returns : { - "message" : str # None if succeeded - } - """ - - async def delete_all_profiles(self) -> dict: - """ - Delete all of the profiles on lightguider server - Args : None - Returns : { - "message" : str - } - """ - - async def copy_profile(self, name: str, dest: str) -> dict: - """ - Copy a profile just like a backport - Args : - name : str # the name of the profile to copy - dest : str # the name of the copied file - Returns : { - "message" : str - } - """ - - async def rename_profile(self, name: str, newname: str) -> dict: - """ - Rename a profile - Args : - name : str # the old name of the profile - newname : str # the new name of the profile - Returns : { - "message" : str - } - """ - - # ################################################################# - # Device Connection - # ################################################################# - - async def connect_device(self) -> dict: - """ - Connect to the devices in the specified profile - Args : None - Returns : { - "message" : str # None if succeeded - } - """ - resp = {"message": None} - if self._current_profile is None: - resp["message"] = "no profile available" - return resp - if not self._is_server_connected: - resp["message"] = "Server is not connected" - return resp - command = await self.generate_command("set_connected", True) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to connect to all of the devices" - resp["error"] = res.get("error") - return resp - self._is_device_connected = True - return resp - - async def disconnect_device(self) -> dict: - """ - Disconnect the connected devices - Args : None - Returns : { - "message" : str # None if succeeded - } - """ - resp = {"message": None} - if not self._is_device_connected: - resp["message"] = "Device is not connected" - return resp - command = await self.generate_command("set_connected", False) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to connect to all of the devices" - resp["error"] = res.get("error") - return resp - self._is_device_connected = False - return resp - - async def reconnect_device(self) -> dict: - """ - Reconnect the connected devices , just an encapsulation of the connect and disconnect - Args : None - Returns : { - "message" : str # None if succeeded - } - """ - resp = {"message": None} - if not self._is_device_connected: - resp["message"] = "Device is not connected" - return resp - resp = await self.disconnect_device() - await asyncio.sleep(1) - resp = await self.connect_device() - return resp - - async def check_connected(self) -> dict: - """ - Check if all of the devices connected - Args : None - Returns : { - "message" : str # None if succeeded - "status" : bool # True if connected - } - """ - resp = {"message": None} - - command = await self.generate_command("get_connected", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to check the status of the connection" - resp["error"] = res.get("error") - return resp - self._is_device_connected = res.get("result") - resp["status"] = res.get("result") - - return resp - - # ################################################################# - # - # Websocket Handler Functions - # - # ################################################################# - - # ################################################################# - # Calibration - # ################################################################# - - async def start_calibration(self) -> dict: - """""" - - async def stop_calibration(self) -> dict: - """""" - - async def check_calibration(self) -> dict: - """ - Check if the calibration had been completed successfully - Args : None - Returns : { - "status" : bool # True if the calibrated - } - """ - resp = {"message": None, "status": None} - if not self._is_device_connected: - resp["message"] = "Device is not connected" - return resp - command = await self.generate_command("get_calibrated", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to get calibration data" - resp["error"] = res.get("error") - return resp - self._is_calibrated = res.get("result") - resp["status"] = self._is_calibrated - return resp - - async def get_calibration_data(self, device="Mount") -> dict: - """ - Get the calibration data and return it as a dictionary - Args : - device : str # device type , default is "Mount" - Returns : { - "data" : { - 'xAngle': self.xAngle, - 'xRate': self.xRate, - 'xParity': self.xParity, - 'yAngle': self.yAngle, - 'yRate': self.yRate, - 'yParity': self.yParity, - } - } - """ - resp = {"message": None, "data": None} - if not self._is_device_connected: - resp["message"] = "Device is not connected" - return resp - command = await self.generate_command("get_calibration_data", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to get calibration data" - resp["error"] = res.get("error") - return resp - self._calibrated_data = res.get("result") - resp["data"] = self._calibrated_data - return resp - - async def clear_calibration_data(self) -> dict: - """ - Clear calibration data and need a calibration before restart guiding - Args : None - Returns : { - "message" : str # None if succeeded - } - """ - resp = {"message": None} - if not self._is_device_connected: - resp["message"] = "Device is not connected" - return resp - command = await self.generate_command("clear_calibration", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to clear calibration data" - resp["error"] = res.get("error") - return resp - self._calibrated_data = {} - self._is_calibrated = False - return resp - - async def flip_calibration_data(self) -> dict: - """ - Flip the calibration data usually for transmit flipper - Args : None - Returns : { - "message" : str # None if succeeded - } - """ - resp = {"message": None} - if not self._is_device_connected: - resp["message"] = "Device is not connected" - return resp - command = await self.generate_command("clear_calibration", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to flip calibration data" - resp["error"] = res.get("error") - return resp - self._is_calibration_flipped = True - return resp - - # ################################################################# - # Guiding - # ################################################################# - - async def find_star(self, roi=[]) -> dict: - """ - Automatically find a star on the screen - Args : - roi : [] # camera frame list - Returns : { - "message" : str # None if succeeded - "x" : int # x position of the found star - "y" : int # y position of the found star - } - """ - resp = {"message": None, "x": None, "y": None} - if not self._is_device_connected: - resp["message"] = "Device is not connected" - return resp - command = await self.generate_command("find_star", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to find a star for guiding" - resp["error"] = res.get("error") - return resp - self._is_star_found = True - self._star_position = res.get("result") - resp["x"] = self._star_position[0] - resp["y"] = self._star_position[1] - return resp - - async def start_guiding( - self, pixel: float, time: int, timeout: int, recalibration: bool - ) -> dict: - """ - Start guiding with the given parameters - Args : - pixel : float # maximum guide distance for guiding to be considered stable or "in-range" - time : int # minimum time to be in-range before considering guiding to be stable - timeout : int # time limit before settling is considered to have failed - recalibration : bool # whether to restart the calibration before guiding - Returns : { - "message" : str # None if succeeded - } - """ - resp = {"message": None} - if not self._is_device_connected: - resp["message"] = "Device is not connected" - return resp - # Check if the parameters are valid - if ( - not isinstance(pixel, float) - or not isinstance(time, int) - or not isinstance(timeout, int) - or not isinstance(recalibration, bool) - ): - resp["message"] = "Invalid guiding parameters was specified" - return resp - settle = {"pixels": pixel, "time": time, "timeout": timeout} - - command = await self.generate_command( - "guide", {"settle": settle, "recalibrate": recalibration} - ) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to start guiding" - resp["error"] = res.get("error") - return resp - return resp - - async def stop_guiding(self) -> dict: - """ - Stop guiding operation - Args : None - Returns : { - "message" : str # None if succeeded - } - """ - resp = {"message": None} - if not self._is_device_connected: - resp["message"] = "Device is not connected" - return resp - command = await self.generate_command("set_paused", [True]) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to start guiding" - resp["error"] = res.get("error") - return resp - return resp - - async def pause_guiding(self) -> dict: - """ - Pause guiding operation but not stop looping - Args : None - Returns : { - "message" : str # None if succeeded - } - """ - - async def resume_guiding(self) -> dict: - """ - Resume the guiding operation just be symmetrical with the above function - Args : None - Returns : { - "message" : str # None if succeeded - } - """ - - async def get_guiding_algorithm(self, axis: str, name=None) -> dict: - """ - Get the algorithm of the specified axis , if the name is not None - Args : - axis : str # name of the axis , "ra","x","dec", or "y" - name : str # if not None , return a accurate value - Returns : { - "list" : list # a list of guide algorithm param names (strings) - "value" : float # the value of the named parameter - } - """ - - async def set_guiding_algorithm(self, axis: str, name: str, value: float) -> dict: - """ - Set the algorithm of the specified axis - URL : chat.forchange.cn - Args : - axis : str # "ra","x","dec", or "y" - name : str # the name of the parameter - value : float - Returns : { - "message" : str # None if succeeded - } - """ - resp = { - "message": None, - } - - if not self._is_device_connected: - resp["message"] = "Device is not connected" - return resp - if not isinstance(axis, str) or not axis in ["ra", "x", "dec", "y"]: - resp["message"] = "Invalid axis specified" - return resp - if not isinstance(name, str) or not name in ["ra", "x", "dec", "y"]: - resp["message"] = "Invalid axis specified" - return resp - command = await self.generate_command("set_dec_guide_mode", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to set the guiding mode of the DEC axis" - resp["error"] = res.get("error") - return resp - return resp - - async def get_dec_guiding_mode(self) -> dict: - """ - Get the guiding mode of the DEC axis - Args : None - Returns : { - "mode" : str # "Off"/"Auto"/"North"/"South" - } - """ - resp = {"message": None, "mode": None} - - if not self._is_device_connected: - resp["message"] = "get_dec_guide_mode" - return resp - command = await self.generate_command("get_dec_guide_mode", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to get the guiding mode of the DEC axis" - resp["error"] = res.get("error") - return resp - self._dec_guiding_mode = res.get("result") - resp["mode"] = self._dec_guiding_mode - return resp - - async def set_dec_guiding_mode(self, mode: str) -> dict: - """ - Set the guiding mode of the DEC axis - Args : - mode : str # "Off"/"Auto"/"North"/"South" - Returns : { - "message" : str # None if succeeded - } - """ - resp = { - "message": None, - } - - if not self._is_device_connected: - resp["message"] = "Device is not connected" - return resp - if not isinstance(mode, str) or not mode in ["Off", "Auto", "North", "South"]: - resp["message"] = "Invalid mode specified" - return resp - command = await self.generate_command("set_dec_guide_mode", {"mode": mode}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to set the guiding mode of the DEC axis" - resp["error"] = res.get("error") - return resp - return resp - - # ################################################################# - # Dither - # ################################################################# - - async def dither( - self, pixel: float, time: int, timeout: int, raonly: bool, amount: int - ) -> dict: - """ - Dither - Args : - pixel : float - time : int - timeout : int - raonly : bool - amount : int - Returns : { - "message" : str # None if succeeded - } - NOTE : After the jitter starts, it takes a certain time to reach stability, so the detection result needs to be returned to the client - """ - resp = {"message": None} - if not self._is_device_connected: - resp["message"] = "Device is not connected" - return resp - # Check if the parameters are valid - if ( - not isinstance(pixel, float) - or not isinstance(time, int) - or not isinstance(timeout, int) - or not isinstance(raonly, bool) - or not isinstance(amount, int) - ): - resp["message"] = "Invalid dithering parameters was specified" - return resp - settle = {"pixels": pixel, "time": time, "timeout": timeout} - - command = await self.generate_command( - "dither", {"settle": settle, "raOnly": raonly, "amount": amount} - ) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to start dithering" - resp["error"] = res.get("error") - return resp - return resp - - # ################################################################# - # Camera - # ################################################################# - - async def get_camera_frame_size(self) -> dict: - """ - Get the frame size of the camera - Args : None - Returns : { - "height" : int # height of the camera frame - "width" : int # width of the camera frame - } - """ - resp = {"message": None, "height": None, "width": None} - if not self._is_device_connected: - resp["message"] = "Device is not connected" - return resp - command = await self.generate_command("get_camera_frame_size", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to get frame size of the camemra" - resp["error"] = res.get("error") - return resp - self._frame_width, self._frame_height = res.get("result") - resp["height"] = self._frame_height - resp["width"] = self._frame_width - return resp - - async def get_cooler_status(self) -> dict: - """ - Get the cooler status of the camera - Args : None - Returns : { - "temperature": sensor temperature in degrees C (number), - "coolerOn": boolean, - "setpoint": cooler set-point temperature (number, degrees C), - "power": cooler power (number, percent) - } - NOTE : This function needs camera supported - """ - resp = { - "message": None, - "temperature": None, - "cooler_on": None, - "setpoint": None, - "power": None, - } - if not self._is_device_connected: - resp["message"] = "Device is not connected" - return resp - command = await self.generate_command("get_cooler_status", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to get cooling status of the camemra" - resp["error"] = res.get("error") - return resp - self._current_temperature = res.get("result").get("temperature") - self._is_cooling = res.get("result").get("coolerOn") - self._target_temperature = res.get("result").get("setpoint") - self._coolig_power = res.get("result").get("power") - resp["temperature"] = self._current_temperature - resp["cooler_on"] = self._is_cooling - resp["setpoint"] = self._target_temperature - resp["power"] = self._coolig_power - return resp - - async def get_camera_temperature(self) -> dict: - """ - Get the current temperature of the camera - Args : None - Returns : { - "temperature" : float # sensor temperature in degrees C - } - """ - resp = {"message": None, "temperature": None} - if not self._is_device_connected: - resp["message"] = "Device is not connected" - return resp - command = await self.generate_command("get_ccd_temperature", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to get temperature of the camemra" - resp["error"] = res.get("error") - return resp - temperature = res.get("result").get("temperature") - self._current_temperature = temperature - resp["temperature"] = temperature - return resp - - async def get_exposure(self) -> dict: - """ - Get the exposure of the camera - Args : None - Returns : { - "exposure" : float # in seconds - } - """ - resp = {"message": None, "exposure": None} - if not self._is_device_connected: - resp["message"] = "Device is not connected" - return resp - command = await self.generate_command("get_exposure", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to get exposure of the camemra" - resp["error"] = res.get("error") - return resp - exposure = res.get("result") / 1000 - self._exposure = exposure - resp["exposure"] = exposure - return resp - - async def set_exposure(self, exposure: float) -> dict: - """ - Set the exposure value of the camera - Args : - exposure : float # in seconds - Returns : { - "message" : str # None if succeeded - } - """ - resp = {"message": None} - if not self._is_device_connected: - resp["message"] = "Device is not connected" - return resp - if not isinstance(exposure, float) or not 0 < exposure < 30: - logger.error("Invalid exposure value specified") - resp["message"] = "Invalid exposure value specified" - return resp - command = await self.generate_command( - "set_exposure", {"exposure": int(exposure * 1000)} - ) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to set exposure of the camemra" - resp["error"] = res.get("error") - return resp - self._exposure = exposure - return resp - - # ################################################################# - # Image - # ################################################################# - - async def get_image(self) -> dict: - """ - Get the current image of the guiding - Args : None - Returns : { - "frame" : int # frame number - "width" : int # width in pixels - "height" : int # height in pixels - "image" : str # a base64 encoded image - "star_position" : list # The position of the star is locked , [x,y] - } - """ - resp = { - "message": None, - "image": None, - "width": None, - "height": None, - "star_position": None, - } - - if not self._is_device_connected: - resp["message"] = "Device is not connected" - return resp - command = await self.generate_command("get_star_image", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to get the current image" - resp["error"] = res.get("error") - return resp - resp["height"] = res.get("height") - resp["width"] = res.get("width") - resp["star_position"] = res.get("star_position") - return resp - - async def save_image(self) -> dict: - """ - Save the image to the specified folder - Args : None - Returns : { - "path" : str # the full path to the image - } - """ - - # ################################################################# - # Modified - # ################################################################# - - async def check_modified(self) -> dict: - """ - Check if the lightguider server been modified by LightAPT - Args : None - Returns : { - "status" : bool # true if the server was modified - } - """ - resp = {"message": None, "status": None} - if not self._is_server_connected: - resp["message"] = "Server is not connected" - return resp - command = await self.generate_command("lightapt_modify_response", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to check if the server is modified" - resp["error"] = res.get("error") - return resp - self._is_server_modified = res.get("result").get("modified") - resp["status"] = self._is_server_modified - return resp - - async def get_darklib_path(self) -> dict: - """ - Get the full path to the darklib directory - Args : None - Returns : { - "path" : str # the full path to the darklib directory - } - """ - resp = {"message": None, "path": None} - if not self._is_server_connected: - resp["message"] = "Server is not connected" - return resp - command = await self.generate_command("get_darklib_path", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to get the dark library path" - resp["error"] = res.get("error") - return resp - self._darklib_path = res.get("result").get("path") - resp["path"] = self._darklib_path - return resp - - async def get_darklib_name(self, profile_id: int) -> dict: - """ - Get the name of the darklib profile for the given profile_id - Args : - profile_id : int - Returns : { - "name" : str # the name of the darklib - } - """ - - async def check_darklib_loaded(self) -> dict: - """ - Check if the dark library is loaded - Args : None - Returns : { - "status" : bool # True if the dark library is loaded - } - """ - resp = {"message": None, "status": None} - - if not self._is_server_connected: - resp["message"] = "Server is not connected" - return resp - command = await self.generate_command("is_darklib_loaded", {}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to get the dark library path" - resp["error"] = res.get("error") - return resp - self._is_darklib_loaded = res.get("result").get("loaded") - resp["status"] = self._is_darklib_loaded - return resp - - async def create_darklib( - self, - name: str, - max_exposure: float, - min_exposure: float, - count: int, - rebuild: bool, - continued: bool, - ) -> dict: - """ - Create the dark frame library - Args : - name : str # the name of the darklib to save - max_exposure : float # the max value of the exposure time - min_exposure : float # the min value of the exposure time - count : int # the number of the images - rebuild : bool # whether to rebuild the dark lib - continued : bool # whether to continue to improve the old lib - Returns : { - "message" : str # None if succeeded - } - """ - resp = {"message": None} - if not self._is_device_connected: - resp["message"] = "Device is not connected" - return resp - command = await self.generate_command( - "create_darklib", - { - "name": name, - "max_exposure": max_exposure * 1000, - "min_exposure": min_exposure * 1000, - "count": count, - "rebuild": int(rebuild), - "continue": int(continued), - }, - ) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - - print(res) - - if "error" in res: - resp["message"] = "Failed to get the dark library path" - resp["error"] = res.get("error") - return resp - self._is_darklib_loaded = res.get("result").get("loaded") - resp["status"] = self._is_darklib_loaded - return resp - - async def create_profile(self, name: str) -> dict: - """ - Create a new profile - Args : - path : str # the full path to the profile - Returns : { - "message" : str # None if succeeded - } - """ - resp = {"message": None, "status": None} - - if not self._is_server_connected: - resp["message"] = "Server is not connected" - return resp - command = await self.generate_command("create_profile", {"name": name}) - try: - res = await self.send_command(command) - except socket.error as e: - resp["message"] = "Send command failed" - resp["error"] = e - return resp - if "error" in res: - resp["message"] = "Failed to get the dark library path" - resp["error"] = res.get("error") - return resp - resp["status"] = res.get("result").get("status") - - return resp - - # ################################################################# - # Server Listener Functions - # ################################################################# - - async def __version(self, message: dict) -> None: - """ - Get lightguider version - Args : None - Returns : None - """ - self._host = message.get("Host") - self._lightguiderversion = message.get("LGuiderVersion") - self._subversion = message.get("LGuiderSubver") - self._msgversion = message.get("MsgVersion") - - async def __lock_position_set(self, message: dict) -> None: - """ - Get lock position set - Args: - message : dict - Returns : None - """ - self._star_position[0] = message.get("X") - self._star_position[1] = message.get("Y") - self._is_star_locked = True - - async def __calibrating(self, message: dict) -> None: - """ - Get calibrating state - Args: - message : dict - Returns : None - """ - self._calibrated_status["direction"] = message.get("dir") - self._calibrated_status["distance"] = message.get("dist") - self._calibrated_status["dx"] = message.get("dx") - self._calibrated_status["dy"] = message.get("dy") - self._calibrated_status["position"] = message.get("pos") - self._calibrated_status["stop"] = message.get("step") - self._calibrated_status["state"] = message.get("State") - - async def __calibration_completed(self, message: dict) -> None: - """ - Get calibration completed state - Args: - message : dict - Returns : None - """ - self._mount = message.get("Mount") - - async def __star_selected(self, message: dict) -> None: - """ - Get star selected state - Args: - message : dict - Returns : None - """ - self._star_position[0] = message.get("X") - self._star_position[1] = message.get("Y") - self._is_star_selected = True - - async def __start_guiding(self) -> None: - """ - Get start guiding state - Args: - message : dict - Returns : None - """ - self._is_guiding = True - - async def __paused(self) -> None: - """ - Get paused state - Args : None - Returns : None - """ - self._is_guiding = False - self._is_calibrating = False - - async def __start_calibration(self, message: dict) -> None: - """ - Get start calibration state - Args: - message : dict - Returns : None - """ - self._mount = message.get("Mount") - self._is_calibrating = True - self._is_guiding = False - - async def __app_state(self, message: dict) -> None: - """ - Get app state - Args: - message : dict - Returns : None - """ - state = message.get("State") - for case in switch(state): - if case("Stopped"): - self._is_calibrating = False - self._is_looping = False - self._is_guiding = False - self._is_settling = False - break - if case("Selected"): - self._is_selected = True - self._is_looping = False - self._is_guiding = False - self._is_settling = False - self._is_calibrating = False - break - if case("Calibrating"): - self._is_calibrating = True - self._is_guiding = False - break - if case("Guiding"): - self._is_guiding = True - self._is_calibrating = False - break - if case("LostLock"): - self._is_guiding = True - self._is_star_locked = False - break - if case("Paused"): - self._is_guiding = False - self._is_calibrating = False - break - if case("Looping"): - self._is_looping = True - break - - async def __calibration_failed(self, message: dict) -> None: - """ - Get calibration failed state - Args: - message : dict - Returns : None - """ - self._calibrated_error = message.get("Reason") - self._is_calibrating = False - self._is_calibrated = False - - async def __calibration_data_flipped(self, message: dict) -> None: - """ - Get calibration data flipping state - Args: - message : dict - Returns : None - """ - self._is_calibration_flipped = True - - async def __lock_position_shift_limit_reached(self) -> None: - """ - Get lock position shift limit reached state - Args : None - Returns : None - """ - logger.warning("Star locked position reached the edge of the camera frame") - - async def __looping_exposures(self, message: dict) -> None: - """ - Get looping exposures state - Args: - message : dict - Returns : None - """ - self._is_looping = True - - async def __looping_exposures_stopped(self) -> None: - """ - Get looping exposures stopped state - Args : None - Returns : None - """ - self._is_looping = False - - async def __settle_begin(self) -> None: - """ - Get settle begin state - Args : None - Returns : None - """ - self._is_settling = True - - async def __settling(self, message: dict) -> None: - """ - Get settling state - Args: - message : dict - Returns : None - """ - self._settle_status["distance"] = message.get("Distance") - self._settle_status["time"] = message.get("SettleTime") - self._settle_status["locked"] = message.get("StarLocked") - self._is_settling = True - - async def __settle_done(self, message: dict) -> None: - """ - Get settle done state - Args: - message : dict - Returns : None - """ - status = message.get("Status") - if status == 0: - logger.info("Settle succeeded") - self._is_settled = True - else: - self._settle_error = message.get("Error") - logger.info(f"Settle failed , error : {message.get('Error')}") - self._is_settled = False - self._is_settling = False - - async def __star_lost(self, message: dict) -> None: - """ - Get star lost state - Args: - message : dict - Returns : None - """ - self._starlost_status["snr"] = message.get("SNR") - self._starlost_status["star_mass"] = message.get("StarMass") - self._starlost_status["avg_dist"] = message.get("AvgDist") - self._starlost_error = message.get("Status") - logger.error( - f"Star Lost , SNR : {self._starlost_status['snr']} , StarMass : {self._starlost_status['star_mass']} , AvgDist : {self._starlost_status['avg_dist']}" - ) - self._is_guiding = False - self._is_calibrating = False - - async def __guiding_stopped(self) -> None: - """ - Get guiding stopped state - Args : None - Returns : None - """ - self._is_guiding = False - logger.info("Guiding Stopped") - - async def __resumed(self) -> None: - """ - Get guiding resumed state - Args : None - Returns : None - """ - logger.info("Guiding Resumed") - self._is_guiding = True - - async def __guide_step(self, message: dict) -> None: - """ - Get guide step state - Args: - message : dict - Returns : None - """ - self._mount = message.get("Mount") - logger.debug("Guide step mount : {}".format(self._mount)) - self._guiding_error = message.get("ErrorCode") - logger.debug("Guide step error : {}".format(self._guiding_error)) - - self._guiding_status["avg_dist"] = message.get("AvgDist") - logger.debug( - "Guide step average distance : {}".format(self._guiding_status["avg_dist"]) - ) - - self._guiding_status["dx"] = message.get("dx") - logger.debug("Guide step dx : {}".format(self._guiding_status["dx"])) - self._guiding_status["dy"] = message.get("dy") - logger.debug("Guide step dy : {}".format(self._guiding_status["dy"])) - - self._guiding_status["ra_raw_distance"] = message.get("RADistanceRaw") - logger.debug( - "Guide step RADistanceRaw : {}".format( - self._guiding_status["ra_raw_distance"] - ) - ) - self._guiding_status["dec_raw_distance"] = message.get("DECDistanceRaw") - logger.debug( - "Guide step DECDistanceRaw : {}".format( - self._guiding_status["dec_raw_distance"] - ) - ) - - self._guiding_status["ra_distance"] = message.get("RADistanceGuide") - logger.debug( - "Guide step RADistanceGuide : {}".format( - self._guiding_status["ra_distance"] - ) - ) - self._guiding_status["dec_distance"] = message.get("DECDistanceGuide") - logger.debug( - "Guide step DECDistanceGuide : {}".format( - self._guiding_status["dec_distance"] - ) - ) - - self._guiding_status["ra_duration"] = message.get("RADuration") - logger.debug( - "Guide step RADuration : {}".format(self._guiding_status["ra_duration"]) - ) - self._guiding_status["dec_duration"] = message.get("DECDuration") - logger.debug( - "Guide step DECDuration : {}".format(self._guiding_status["dec_duration"]) - ) - - self._guiding_status["ra_direction"] = message.get("RADirection") - logger.debug( - "Guide step RADirection : {}".format(self._guiding_status["ra_direction"]) - ) - self._guiding_status["dec_direction"] = message.get("DECDirection") - logger.debug( - "Guide step DECDirection : {}".format(self._guiding_status["dec_direction"]) - ) - - self._guiding_status["snr"] = message.get("SNR") - logger.debug("Guide step SNR : {}".format(self._guiding_status["snr"])) - self._guiding_status["starmass"] = message.get("StarMass") - logger.debug( - "Guide step StarMass : {}".format(self._guiding_status["starmass"]) - ) - self._guiding_status["hfd"] = message.get("HFD") - logger.debug("Guide step HFD : {}".format(self._guiding_status["hfd"])) - - async def __guiding_dithered(self, message: dict) -> None: - """ - Get guiding dithered state - Args: - message : dict - Returns : None - """ - self._dither_dx = message.get("dx") - self._dither_dy = message.get("dy") - - async def __lock_position_lost(self) -> None: - """ - Get lock position lost state - Args : None - Returns : None - """ - self._is_star_locked = True - logger.error("Star Lock Position Lost") - - async def __alert(self, message: dict) -> None: - """ - Get alert state - Args: - message : dict - Returns : None - """ - self._last_error = message.get("Msg") - logger.error("Alert : {}".format(self._last_error)) - - async def __guide_param_change(self, message: dict) -> None: - """ - Get guide param change state - Args: - message : dict - Returns : None - """ - - async def __configuration_change(self) -> None: - """ - Get configuration change state - Args : None - Returns : None - """ diff --git a/driver/solver/CMakeLists.txt b/driver/solver/CMakeLists.txt deleted file mode 100644 index 8bdd085f..00000000 --- a/driver/solver/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# CMakeLists.txt for Atom-Solver -# This project is licensed under the terms of the GPL3 license. -# -# Project Name: Atom-Solver -# Description: A collection of solvers for Atom -# Author: Max Qian -# License: GPL3 - -cmake_minimum_required(VERSION 3.20) -project(atom-solver C CXX) - -add_subdirectory(atom-astrometry) -add_subdirectory(atom-astap) diff --git a/driver/solver/atom-astap/CMakeLists.txt b/driver/solver/atom-astap/CMakeLists.txt deleted file mode 100644 index c475dd91..00000000 --- a/driver/solver/atom-astap/CMakeLists.txt +++ /dev/null @@ -1,64 +0,0 @@ -# CMakeLists.txt for Atom-Astap -# This project is licensed under the terms of the GPL3 license. -# -# Project Name: Atom-Astap -# Description: Astap command line interface for astap -# Author: Max Qian -# License: GPL3 - -cmake_minimum_required(VERSION 3.20) -project(atom-astap C CXX) - -# Sources -set(${PROJECT_NAME}_SOURCES - astap.cpp -) - -# Headers -set(${PROJECT_NAME}_HEADERS - astap.hpp -) - -# Private Headers -set(${PROJECT_NAME}_PRIVATE_HEADERS -) - -set(${PROJECT_NAME}_LIBS - atom - atom-components - atom-driver - atom-type - atom-utils) - -# Build Object Library -add_library(${PROJECT_NAME}_OBJECT OBJECT) -set_property(TARGET ${PROJECT_NAME}_OBJECT PROPERTY POSITION_INDEPENDENT_CODE 1) - -target_link_libraries(${PROJECT_NAME}_OBJECT loguru) -target_link_libraries(${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) - -target_sources(${PROJECT_NAME}_OBJECT - PUBLIC - ${${PROJECT_NAME}_HEADERS} - PRIVATE - ${${PROJECT_NAME}_SOURCES} - ${${PROJECT_NAME}_PRIVATE_HEADERS} -) - -target_link_libraries(${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) - -add_library(${PROJECT_NAME} STATIC) - -target_link_libraries(${PROJECT_NAME} ${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) -target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT}) -target_include_directories(${PROJECT_NAME} PUBLIC .) - -set_target_properties(${PROJECT_NAME} PROPERTIES - VERSION ${CMAKE_HYDROGEN_VERSION_STRING} - SOVERSION ${HYDROGEN_SOVERSION} - OUTPUT_NAME ${PROJECT_NAME} -) - -install(TARGETS ${PROJECT_NAME} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -) diff --git a/driver/solver/atom-astap/astap.cpp b/driver/solver/atom-astap/astap.cpp deleted file mode 100644 index 3beee2b2..00000000 --- a/driver/solver/atom-astap/astap.cpp +++ /dev/null @@ -1,264 +0,0 @@ -#include "astap.hpp" - -#include "atom/async/async.hpp" -#include "atom/io/io.hpp" -#include "atom/log/loguru.hpp" -#include "atom/system/command.hpp" -#include "atom/utils/cmdline.hpp" - - -#include - -AstapSolver::AstapSolver(const std::string &name) : Solver(name) { - DLOG_F(INFO, "Initializing Astap Solver..."); -} - -AstapSolver::~AstapSolver() { DLOG_F(INFO, "Destroying Astap Solver..."); } - -bool AstapSolver::connect(const json ¶ms) { - DLOG_F(INFO, "Connecting to Astap Solver..."); - if (!params.contains("path") || !params["path"].is_string()) { - LOG_F(ERROR, "Failed to execute {}: Invalid Parameters", __func__); - return false; - } - std::string solverPath = params["path"].get(); - if (!Atom::IO::isFileNameValid(solverPath) || - !Atom::IO::isFileExists(solverPath)) { - LOG_F(ERROR, "Failed to execute {}: Invalid Parameters", __func__); - return false; - } - SetVariable("solverPath", params["path"].get()); - DLOG_F(INFO, "Connected to Astap Solver"); - return true; -} - -bool AstapSolver::disconnect(const json ¶ms) { - DLOG_F(INFO, "Disconnecting from Astap Solver..."); - SetVariable("solverPath", ""); - DLOG_F(INFO, "Disconnected from Astap Solver"); - return true; -} - -bool AstapSolver::reconnect(const json ¶ms) { - DLOG_F(INFO, "Reconnecting to Astap Solver..."); - if (!disconnect(params)) { - return false; - } - if (!connect(params)) { - return false; - } - DLOG_F(INFO, "Reconnected to Astap Solver"); - return true; -} - -bool AstapSolver::isConnected() { - return GetVariable("solverPath").has_value(); -} - -bool AstapSolver::solveImage(const std::string &image, const int &timeout, - const bool &debug) { - DLOG_F(INFO, "Solving Image {}...", image); - if (!isConnected()) { - LOG_F(ERROR, "Failed to execute {}: Not Connected", __func__); - return false; - } - if (!Atom::IO::isFileNameValid(image) || !Atom::IO::isFileExists(image)) { - LOG_F(ERROR, "Failed to execute {}: Invalid Parameters", __func__); - return false; - } - SolveResult resu; - t; - try { - std::string command = makeCommand(); - - auto ret = Atom::Async::asyncRetry( - [](const std::string &cmd) -> std::string { - return atom::utils::executeCommand(cmd, false); - }, - 3, std::chrono::seconds(5), cmd); - - // 等待命令执行完成,或者超时 - auto start_time = std::chrono::system_clock::now(); - while (ret.wait_for(std::chrono::seconds(1)) != - std::future_status::ready) { - auto elapsed_time = - std::chrono::duration_cast( - std::chrono::system_clock::now() - start_time) - .count(); - if (elapsed_time > timeout) { - LOG_F(ERROR, "Error: command timed out after {} seconds.", - std::to_string(timeout)); - return ""; - } - } - - // 返回命令执行结果,并输出调试信息 - auto output = ret.get(); - DLOG_F(INFO, "Command '{}' returned: {}", cmd, output); - - if (output.find("Solution found:") != std::string::npos) { - DLOG_F(INFO, "Solved successfully"); - ret_struct = readSolveResult(image); - } else { - LOG_F(ERROR, "Failed to solve the image"); - ret_struct.error = "Failed to solve the image"; - } - } catch (const std::exception &e) { - LOG_F(ERROR, "Failed to execute {}: {}", __func__, e.what()); - return false; - } - // 将解析结果写入JSON对象 - if (!result.ra.empty()) { - SetVariable("result.ra", result.ra); - } - if (!result.dec.empty()) { - SetVariable("result.dec", result.dec); - } - if (result.fov_x > 0) { - SetVariable("result.fov_x", result.fov_x); - } - if (result.fov_y > 0) { - SetVariable("result.fov_y", result.fov_y); - } - if (result.fov_avg > 0) { - SetVariable("result.fov_avg", result.fov_avg); - } - if (result.rotation != 0) { - SetVariable("result.rotation", result.rotation); - } - return true; -} - -bool AstapSolver::getSolveResult(const int &timeout, const bool &debug) { - DLOG_F(INFO, "Getting Solve Result..."); - return true; -} - -bool AstapSolver::getSolveStatus(const int &timeout, const bool &debug) { - DLOG_F(INFO, "Getting Solve Status..."); - return true; -} - -bool AstapSolver::setSolveParams(const json ¶ms) { - DLOG_F(INFO, "Setting Solve Parameters..."); - bool status = true; - if (params.contains("ra") && params["ra"].is_string()) { - DLOG_F(INFO, "Setting Target RA {}", params["ra"].get()); - status = SetVariable("target_ra", params["ra"].get()); - } - if (params.contains("dec") && params["dec"].is_string()) { - DLOG_F(INFO, "Setting Target Dec {}", params["dec"].get()); - status = SetVariable("target_dec", params["dec"].get()); - } - if (params.contains("fov") && params["fov"].is_number()) { - DLOG_F(INFO, "Setting Field of View {}", params["fov"].get()); - status = SetVariable("fov", params["fov"].get()); - } - if (params.contains("update") && params["update"].is_boolean()) { - DLOG_F(INFO, "Setting Update {}", params["update"].get()); - status = SetVariable("update", params["update"].get()); - } - return status; -} - -json AstapSolver::getSolveParams() { return json{}; } - -std::string AstapSolver::makeCommand() { - auto solverPath = GetVariable("solverPath").value(); - auto image = GetVariable("imagePath").value(); - auto ra = GetVariable("target_ra").value(); - auto dec = GetVariable("target_dec").value(); - auto fov = GetVariable("fov").value(); - auto update = GetVariable("update").value(); - - // Max: Here we should use cmdline.hpp to make the command - - std::stringstream ss; - ss << solverPath << " -f " << image << ""; - if (!ra.empty()) { - ss << " -ra " << ra << ""; - } - if (!dec.empty()) { - ss << " -dec " << dec << ""; - } - if (fov > 0) { - ss << " -fov " << fov; - } - if (update) { - ss << " -update"; - } - - std::string cmd = ss.str(); - DLOG_F(INFO, "Command: {}", cmd); - return cmd; -} - -SolveResult AstapSolver::readSolveResult(const std::string &image) { - SovleResult ret_struct; - - // 打开 FITS 文件并读取头信息 - fitsfile *fptr; - int status = 0; - fits_open_file(&fptr, image.c_str(), READONLY, &status); - if (status != 0) { - LOG_F(ERROR, "Failed to read FITS header: {}", image); - ret_struct.error = "Failed to read FITS header: " + image; - return ret_struct; - } - - double solved_ra, solved_dec, x_pixel_arcsec, y_pixel_arcsec, rotation, - x_pixel_size, y_pixel_size; - bool data_get_flag = false; - char comment[FLEN_COMMENT]; - - // 读取头信息中的关键字 - status = 0; - fits_read_key(fptr, TDOUBLE, "CRVAL1", &solved_ra, comment, &status); - - status = 0; - fits_read_key(fptr, TDOUBLE, "CRVAL2", &solved_dec, comment, &status); - - status = 0; - fits_read_key(fptr, TDOUBLE, "CDELT1", &x_pixel_arcsec, comment, &status); - - status = 0; - fits_read_key(fptr, TDOUBLE, "CDELT2", &y_pixel_arcsec, comment, &status); - - status = 0; - fits_read_key(fptr, TDOUBLE, "CROTA1", &rotation, comment, &status); - - status = 0; - fits_read_key(fptr, TDOUBLE, "XPIXSZ", &x_pixel_size, comment, &status); - - status = 0; - fits_read_key(fptr, TDOUBLE, "YPIXSZ", &y_pixel_size, comment, &status); - - // 关闭 FITS 文件 - fits_close_file(fptr, &status); - if (status != 0) { - LOG_F(ERROR, "Failed to close FITS file: {}", image); - ret_struct.error = "Failed to close FITS file: " + image; - return ret_struct; - } - - // 构造返回结果 - if (data_get_flag) { - ret_struct.ra = std::to_string(solved_ra); - ret_struct.dec = std::to_string(solved_dec); - ret_struct.rotation = std::to_string(rotation); - if (x_pixel_size > 0.0 && y_pixel_size > 0.0) { - double x_focal_length = x_pixel_size / x_pixel_arcsec * 206.625; - double y_focal_length = y_pixel_size / y_pixel_arcsec * 206.625; - double avg_focal_length = (x_focal_length + y_focal_length) / 2.0; - ret_struct.fov_x = x_focal_length; - ret_struct.fov_y = y_focal_length; - ret_struct.fov_avg = avg_focal_length; - // 调试输出 - DLOG_F(INFO, "avg_focal_length: {}", avg_focal_length); - } - } else { - LOG_F(ERROR, "Solve failed"); - ret_struct.error = "Solve failed"; - } - return ret_struct; -} diff --git a/driver/solver/atom-astap/astap.hpp b/driver/solver/atom-astap/astap.hpp deleted file mode 100644 index fa5d0af7..00000000 --- a/driver/solver/atom-astap/astap.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "atom/driver/solver.hpp" - -class AstapSolver : public Solver { -public: - explicit AstapSolver(const std::string &name); - virtual ~AstapSolver(); - - virtual bool connect(const json ¶ms) override; - - virtual bool disconnect(const json ¶ms) override; - - virtual bool reconnect(const json ¶ms) override; - - virtual bool isConnected() override; - - virtual bool solveImage(const std::string &image, const int &timeout, - const bool &debug); - - virtual bool getSolveResult(const int &timeout, const bool &debug); - - virtual bool getSolveStatus(const int &timeout, const bool &debug); - - virtual bool setSolveParams(const json ¶ms); - - virtual json getSolveParams(); - -private: - std::string makeCommand(); - - SolveResult readSolveResult(const std::string &image); -}; diff --git a/driver/solver/atom-astrometry/CMakeLists.txt b/driver/solver/atom-astrometry/CMakeLists.txt deleted file mode 100644 index 12e81e2d..00000000 --- a/driver/solver/atom-astrometry/CMakeLists.txt +++ /dev/null @@ -1,64 +0,0 @@ -# CMakeLists.txt for Atom-Astrometry -# This project is licensed under the terms of the GPL3 license. -# -# Project Name: Atom-Astrometry -# Description: Astrometry command line interface for astrometry.net -# Author: Max Qian -# License: GPL3 - -cmake_minimum_required(VERSION 3.20) -project(atom-astrometry C CXX) - -# Sources -set(${PROJECT_NAME}_SOURCES - astrometry.cpp -) - -# Headers -set(${PROJECT_NAME}_HEADERS - astrometry.hpp -) - -# Private Headers -set(${PROJECT_NAME}_PRIVATE_HEADERS -) - -set(${PROJECT_NAME}_LIBS - atom - atom-components - atom-driver - atom-type - atom-utils) - -# Build Object Library -add_library(${PROJECT_NAME}_OBJECT OBJECT) -set_property(TARGET ${PROJECT_NAME}_OBJECT PROPERTY POSITION_INDEPENDENT_CODE 1) - -target_link_libraries(${PROJECT_NAME}_OBJECT loguru) -target_link_libraries(${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) - -target_sources(${PROJECT_NAME}_OBJECT - PUBLIC - ${${PROJECT_NAME}_HEADERS} - PRIVATE - ${${PROJECT_NAME}_SOURCES} - ${${PROJECT_NAME}_PRIVATE_HEADERS} -) - -target_link_libraries(${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) - -add_library(${PROJECT_NAME} STATIC) - -target_link_libraries(${PROJECT_NAME} ${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) -target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT}) -target_include_directories(${PROJECT_NAME} PUBLIC .) - -set_target_properties(${PROJECT_NAME} PROPERTIES - VERSION ${CMAKE_HYDROGEN_VERSION_STRING} - SOVERSION ${HYDROGEN_SOVERSION} - OUTPUT_NAME ${PROJECT_NAME} -) - -install(TARGETS ${PROJECT_NAME} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -) diff --git a/driver/solver/atom-astrometry/astrometry.cpp b/driver/solver/atom-astrometry/astrometry.cpp deleted file mode 100644 index ce025ab1..00000000 --- a/driver/solver/atom-astrometry/astrometry.cpp +++ /dev/null @@ -1,302 +0,0 @@ -#include "astrometry.hpp" - -#include "atom/io/io.hpp" -#include "atom/log/loguru.hpp" -#include "atom/system/command.hpp" -#include "atom/utils/cmdline.hpp" - - -AstrometrySolver::AstrometrySolver(const std::string &name) : Solver(name) { - RegisterFunc("solveImage", &AstrometrySolver::_solveImage, this); - RegisterFunc("getSolveResult", &AstrometrySolver::_getSolveResult, this); - RegisterFunc("getSolveStatus", &AstrometrySolver::_getSolveStatus, this); - RegisterFunc("setSolveParams", &AstrometrySolver::_setSolveParams, this); - RegisterFunc("getSolveParams", &AstrometrySolver::_getSolveParams, this); - - RegisterFunc("connect", &AstrometrySolver::connect, this); - RegisterFunc("disconnect", &AstrometrySolver::disconnect, this); - RegisterFunc("reconnect", &AstrometrySolver::reconnect, this); - RegisterFunc("isConnected", &AstrometrySolver::isConnected, this); - - DLOG_F(INFO, "Initializing Astrometry Solver..."); -} - -AstrometrySolver::~AstrometrySolver() { - DLOG_F(INFO, "Destroying Astrometry Solver..."); -} - -bool AstrometrySolver::connect(const json ¶ms) { - DLOG_F(INFO, "Connecting to Astrometry Solver..."); - // Check the path - if (!params.contains("path") || !params["path"].is_string()) { - LOG_F(ERROR, "Failed to execute {}: Invalid Parameters", __func__); - return false; - } - std::string solverPath = params["path"].get(); - if (!Atom::IO::isFileNameValid(solverPath) || - !Atom::IO::isFileExists(solverPath)) { - LOG_F(ERROR, "Failed to execute {}: Invalid Parameters", __func__); - return false; - } - // Check whether the file is a executable file - - SetVariable("solverPath", params["path"].get()); - DLOG_F(INFO, "Connected to Astrometry Solver"); - return true; -} - -bool AstrometrySolver::disconnect(const json ¶ms) { - DLOG_F(INFO, "Disconnecting from Astrometry Solver..."); - SetVariable("solverPath", ""); - DLOG_F(INFO, "Disconnected from Astrometry Solver"); - return true; -} - -bool AstrometrySolver::reconnect(const json ¶ms) { - DLOG_F(INFO, "Reconnecting to Astrometry Solver..."); - if (!disconnect(params)) { - return false; - } - if (!connect(params)) { - return false; - } - DLOG_F(INFO, "Reconnected to Astrometry Solver"); - return true; -} - -bool AstrometrySolver::isConnected() { - return GetVariable("solverPath").has_value(); -} - -bool AstrometrySolver::solveImage(const std::string &image, const int &timeout, - const bool &debug) { - DLOG_F(INFO, "Solving Image {}...", image); - if (!isConnected()) { - LOG_F(ERROR, "Failed to execute {}: Not Connected", __func__); - return false; - } - if (!Atom::IO::isFileNameValid(image) || !Atom::IO::isFileExists(image)) { - LOG_F(ERROR, "Failed to execute {}: Invalid Parameters", __func__); - return false; - } - SolveResult result; - try { - std::string command = makeCommand(); - - std::string output = Atom::System::executeCommand(command, false); - - result = parseOutput(output); - } catch (const std::exception &e) { - LOG_F(ERROR, "Failed to execute {}: {}", __func__, e.what()); - return false; - } - // 将解析结果写入JSON对象 - if (!result.ra.empty()) { - SetVariable("result.ra", result.ra); - } - if (!result.dec.empty()) { - SetVariable("result.dec", result.dec); - } - if (result.fov_x > 0) { - SetVariable("result.fov_x", result.fov_x); - } - if (result.fov_y > 0) { - SetVariable("result.fov_y", result.fov_y); - } - if (result.rotation != 0) { - SetVariable("result.rotation", result.rotation); - } - return true; -} - -bool AstrometrySolver::getSolveResult(const int &timeout, const bool &debug) { - DLOG_F(INFO, "Getting Solve Result..."); - return true; -} - -bool AstrometrySolver::getSolveStatus(const int &timeout, const bool &debug) { - DLOG_F(INFO, "Getting Solve Status..."); - return true; -} - -bool AstrometrySolver::setSolveParams(const json ¶ms) { - DLOG_F(INFO, "Setting Solve Parameters..."); - bool status = true; - if (params.contains("ra") && params["ra"].is_string()) { - DLOG_F(INFO, "Setting Target RA {}", params["ra"].get()); - status = SetVariable("target_ra", params["ra"].get()); - } - if (params.contains("dec") && params["dec"].is_string()) { - DLOG_F(INFO, "Setting Target Dec {}", params["dec"].get()); - status = SetVariable("target_dec", params["dec"].get()); - } - if (params.contains("radius") && params["radius"].is_number()) { - DLOG_F(INFO, "Setting Search Radius {}", - params["radius"].get()); - status = SetVariable("radius", params["radius"].get()); - } - if (params.contains("downsample") && params["downsample"].is_number()) { - DLOG_F(INFO, "Setting Downsample {}", params["downsample"].get()); - status = SetVariable("downsample", params["downsample"].get()); - } - if (params.contains("depth") && params["depth"].is_array()) { - for (auto &i : params["depth"].get>()) { - DLOG_F(INFO, "Setting Depth {}", i); - /// status = SetVariable("depth", i); - } - // status = SetVariable("depth_x", - // params["depth"].get>()); - } - if (params.contains("scale_low") && params["scale_low"].is_number()) { - DLOG_F(INFO, "Setting Scale Low {}", params["scale_low"].get()); - status = SetVariable("scale_low", params["scale_low"].get()); - } - if (params.contains("scale_high") && params["scale_high"].is_number()) { - DLOG_F(INFO, "Setting Scale High {}", - params["scale_high"].get()); - status = SetVariable("scale_high", params["scale_high"].get()); - } - if (params.contains("width") && params["width"].is_number()) { - DLOG_F(INFO, "Setting Width {}", params["width"].get()); - status = SetVariable("width", params["width"].get()); - } - if (params.contains("height") && params["height"].is_number()) { - DLOG_F(INFO, "Setting Height {}", params["height"].get()); - status = SetVariable("height", params["height"].get()); - } - if (params.contains("scale_units") && params["scale_units"].is_string()) { - DLOG_F(INFO, "Setting Scale Units {}", - params["scale_units"].get()); - status = SetVariable("scale_units", - params["scale_units"].get()); - } - if (params.contains("overwrite") && params["overwrite"].is_boolean()) { - DLOG_F(INFO, "Setting Overwrite {}", params["overwrite"].get()); - status = SetVariable("overwrite", params["overwrite"].get()); - } - if (params.contains("no_plot") && params["no_plot"].is_boolean()) { - DLOG_F(INFO, "Setting No Plot {}", params["no_plot"].get()); - status = SetVariable("no_plot", params["no_plot"].get()); - } - if (params.contains("verify") && params["verify"].is_boolean()) { - DLOG_F(INFO, "Setting Verify {}", params["verify"].get()); - status = SetVariable("verify", params["verify"].get()); - } - return status; -} - -json AstrometrySolver::getSolveParams() { return json{}; } - -std::string AstrometrySolver::makeCommand() { - auto solverPath = GetVariable("solverPath").value(); - auto image = GetVariable("imagePath").value(); - auto ra = GetVariable("target_ra").value(); - auto dec = GetVariable("target_dec").value(); - auto radius = GetVariable("radius").value(); - auto downsample = GetVariable("downsample").value(); - auto depth = GetVariable>("depth").value(); - auto scale_low = GetVariable("scale_low").value(); - auto scale_high = GetVariable("scale_high").value(); - auto width = GetVariable("width").value(); - auto height = GetVariable("height").value(); - auto scale_units = GetVariable("scale_units").value(); - auto overwrite = GetVariable("overwrite").value(); - auto no_plot = GetVariable("no_plot").value(); - auto verify = GetVariable("verify").value(); - - // Max: Here we should use cmdline.hpp to make the command - - std::stringstream ss; - ss << solverPath << " \"" << image << "\""; - if (!ra.empty()) { - ss << " --ra \"" << ra << "\""; - } - if (!dec.empty()) { - ss << " --dec \"" << dec << "\""; - } - if (radius > 0) { - ss << " --radius " << radius; - } - if (downsample != 1) { - ss << " --downsample " << downsample; - } - if (!depth.empty()) { - ss << " --depth " << depth[0] << "," << depth[1]; - } - if (scale_low > 0) { - ss << " --scale-low " << scale_low; - } - if (scale_high > 0) { - ss << " --scale-high " << scale_high; - } - if (width > 0) { - ss << " --width " << width; - } - if (height > 0) { - ss << " --height " << height; - } - if (!scale_units.empty()) { - ss << " --scale-units \"" << scale_units << "\""; - } - if (overwrite) { - ss << " --overwrite"; - } - if (no_plot) { - ss << " --no-plot"; - } - if (verify) { - ss << " --verify"; - } - - std::string cmd = ss.str(); - DLOG_F(INFO, "Command: {}", cmd); - return cmd; -} - -SolveResult AstrometrySolver::parseOutput(const std::string &output) { - SolveResult result; - std::unordered_map tokens; - std::istringstream ss(output); - std::string line; - while (std::getline(ss, line)) { - // 解析命令行输出结果中的每一行 - std::string key, value; - auto pos = line.find(": "); - if (pos != std::string::npos) { - key = line.substr(0, pos); - value = line.substr(pos + 2); - } else { - key = line; - value = ""; - } - key.erase( - std::remove_if(key.begin(), key.end(), - [](unsigned char c) { return !std::isalnum(c); }), - key.end()); - tokens[key] = value; - } - - // 提取解析出的参数 - auto iter = tokens.find("FieldcenterRAHMSDecDMS"); - if (iter != tokens.end()) { - auto pos = iter->second.find(","); - if (pos != std::string::npos) { - result.ra = iter->second.substr(0, pos); - result.dec = iter->second.substr(pos + 1); - } - } - iter = tokens.find("Fieldsize"); - if (iter != tokens.end()) { - auto pos = iter->second.find("x"); - if (pos != std::string::npos) { - result.fov_x = std::stod(iter->second.substr(0, pos)); - result.fov_y = std::stod(iter->second.substr(pos + 1)); - } - } - iter = tokens.find("Fieldrotationangleupisdegrees"); - if (iter != tokens.end()) { - result.rotation = std::stod(iter->second); - } - - return result; -} diff --git a/driver/solver/atom-astrometry/astrometry.hpp b/driver/solver/atom-astrometry/astrometry.hpp deleted file mode 100644 index 5e0ab540..00000000 --- a/driver/solver/atom-astrometry/astrometry.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "atom/driver/solver.hpp" - -class AstrometrySolver : public Solver { -public: - explicit AstrometrySolver(const std::string &name); - virtual ~AstrometrySolver(); - - virtual bool connect(const json ¶ms) override; - - virtual bool disconnect(const json ¶ms) override; - - virtual bool reconnect(const json ¶ms) override; - - virtual bool isConnected() override; - - virtual bool solveImage(const std::string &image, const int &timeout, - const bool &debug); - - virtual bool getSolveResult(const int &timeout, const bool &debug); - - virtual bool getSolveStatus(const int &timeout, const bool &debug); - - virtual bool setSolveParams(const json ¶ms); - - virtual json getSolveParams(); - -private: - std::string makeCommand(); - - SolveResult parseOutput(const std::string &output); -}; diff --git a/driver/solver/atom-astrometry/main.cpp b/driver/solver/atom-astrometry/main.cpp deleted file mode 100644 index 1a926687..00000000 --- a/driver/solver/atom-astrometry/main.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "astrometry.hpp" - -static std::shared_ptr createShared(const std::string &name) { - return std::make_shared(name); -} - -int main(int argc, char *argv[]) { - std::shared_ptr solver = createShared("Astrometry"); - solver->solveImage("test.fits", 1000, false); - return 0; -} diff --git a/example/benchmark.cpp b/example/benchmark.cpp new file mode 100644 index 00000000..a6360559 --- /dev/null +++ b/example/benchmark.cpp @@ -0,0 +1,4 @@ +#include "benchmark.hpp" + +// Define the static member variable +std::vector Benchmark::results_; diff --git a/example/benchmark.hpp b/example/benchmark.hpp index 3e8dd87d..2c36df57 100644 --- a/example/benchmark.hpp +++ b/example/benchmark.hpp @@ -4,12 +4,14 @@ #include #include #include -#include +#include #include #include #include #include +#include #include +#include #include class Benchmark { @@ -18,18 +20,17 @@ class Benchmark { using TimePoint = Clock::time_point; using Duration = Clock::duration; - Benchmark(const std::string& name) : name_(name) {} + explicit Benchmark(std::string name) : name_(std::move(name)) {} template void Run(Func&& func, int iterations) { std::vector durations(iterations); - std::transform(std::execution::par, durations.begin(), durations.end(), - durations.begin(), [&func](const Duration&) { - TimePoint start = Clock::now(); - func(); - return Clock::now() - start; - }); + std::ranges::generate(durations, [&func]() { + TimePoint start = Clock::now(); + func(); + return Clock::now() - start; + }); Duration totalDuration = std::accumulate( durations.begin(), durations.end(), Duration::zero()); @@ -38,8 +39,7 @@ class Benchmark { double variance = std::transform_reduce( - std::execution::par, durations.begin(), durations.end(), 0.0, - std::plus<>(), + durations.begin(), durations.end(), 0.0, std::plus<>(), [&averageDuration](const Duration& d) { double durationInMicroseconds = static_cast(d.count()) / 1000.0; @@ -57,14 +57,15 @@ class Benchmark { static void PrintResults() { std::cout << "Benchmark Results:\n"; for (const auto& result : results_) { - std::cout << std::setw(20) << std::left << result.name << ": " - << std::chrono::duration_cast( - result.totalDuration) - .count() - << " us (avg: " << std::setprecision(4) - << result.averageDuration - << " us, std dev: " << result.standardDeviation << " us, " - << result.iterations << " iterations)\n"; + std::cout << std::format( + "{:<20}: {:>8} us (avg: {:>.4f} us, std dev: {:>.4f} us, {:>4} " + "iterations)\n", + result.name, + std::chrono::duration_cast( + result.totalDuration) + .count(), + result.averageDuration, result.standardDeviation, + result.iterations); } } @@ -77,12 +78,10 @@ class Benchmark { int iterations; }; - static inline std::vector results_; + static std::vector results_; std::string name_; }; #define BENCHMARK(name, func, iterations) Benchmark(name).Run(func, iterations) -std::vector Benchmark::results_; - #endif diff --git a/example/benchmark_test.cpp b/example/benchmark_test.cpp new file mode 100644 index 00000000..1eeb2f9d --- /dev/null +++ b/example/benchmark_test.cpp @@ -0,0 +1,31 @@ +#include +#include "benchmark.hpp" + +// Example function to benchmark +void ExampleFunction() { + volatile double result = 0.0; + for (int i = 0; i < 1000; ++i) { + result += std::sin(i); + } +} + +// Another example function to benchmark +void AnotherExampleFunction() { + volatile double result = 0.0; + for (int i = 0; i < 1000; ++i) { + result += std::cos(i); + } +} + +int main() { + // Benchmark ExampleFunction with 100 iterations + BENCHMARK("ExampleFunction", ExampleFunction, 100); + + // Benchmark AnotherExampleFunction with 100 iterations + BENCHMARK("AnotherExampleFunction", AnotherExampleFunction, 100); + + // Print the results of the benchmarks + Benchmark::PrintResults(); + + return 0; +} diff --git a/libs b/libs index 6b0fab03..f1082244 160000 --- a/libs +++ b/libs @@ -1 +1 @@ -Subproject commit 6b0fab0375a38e7a26bf02b70133024adf2d6936 +Subproject commit f1082244ea563500599fcc1726aadfb3e138c387 diff --git a/meson.build b/meson.build deleted file mode 100644 index 1c0c31d6..00000000 --- a/meson.build +++ /dev/null @@ -1,131 +0,0 @@ -project('Lithium', 'c', 'cpp', version: '1.0.0') - -# Project directories -src_dir = meson.source_root() / 'src' -module_dir = src_dir / 'atom' -client_dir = src_dir / 'client' -component_dir = src_dir / 'addon' -task_dir = src_dir / 'task' - -# Compiler options -add_project_arguments('-Wall', '-Wextra', '-Wpedantic', language: 'cpp') - -# Include directories -inc_dirs = include_directories([ - 'libs', - 'driverlibs', - src_dir, - module_dir, - 'libs/oatpp', - 'libs/oatpp-swagger', - 'libs/oatpp-websocket', - meson.current_source_dir / 'modules' -]) - -# Dependencies -openssl_dep = dependency('openssl', required: true) -cfitsio_dep = dependency('cfitsio', required: true) -zlib_dep = dependency('zlib', required: true) -sqlite3_dep = dependency('sqlite3', required: true) -fmt_dep = dependency('fmt', required: true) - -# Optional dependencies -loguru_lib = static_library('loguru', 'path/to/loguru.cpp') # Replace with actual path to loguru - -# Subdirectories -subdir('libs') -subdir('tools') -subdir('src/atom') -subdir('modules') -subdir('driver') -subdir('src/carbon') -subdir('src/config') -subdir('src/server') - -# Configuration options -enable_async = get_option('enable_async') -enable_debug = get_option('enable_debug') - -config_h = configure_file( - input: 'config.h.in', - output: 'config.h', - configuration: { - 'ENABLE_ASYNC_FLAG': enable_async ? 1 : 0, - 'ENABLE_DEBUG_FLAG': enable_debug ? 1 : 0, - } -) - -# Source files -component_module = [ - component_dir / 'addons.cpp', - component_dir / 'compiler.cpp', - component_dir / 'loader.cpp', - component_dir / 'manager.cpp', - component_dir / 'sandbox.cpp', - component_dir / 'sort.cpp' -] - -config_module = [src_dir / 'config/configor.cpp'] -debug_module = [src_dir / 'debug/terminal.cpp'] -script_module = [ - src_dir / 'script/manager.cpp', - src_dir / 'script/custom/sys.cpp', - src_dir / 'script/custom/config.cpp', - src_dir / 'script/sheller.cpp', - src_dir / 'script/carbon.cpp' -] - -task_module = [ - task_dir / 'manager.cpp', - task_dir / 'generator.cpp', - task_dir / 'container.cpp', - task_dir / 'tick.cpp', - task_dir / 'loader.cpp', - task_dir / 'list.cpp', - task_dir / 'pool.cpp' -] - -lithium_module = src_dir / 'LithiumApp.cpp' - -# Library -lithium_server_lib = static_library('lithium_server-library', [ - component_module, - config_module, - debug_module, - task_module, - script_module, - lithium_module -], include_directories: inc_dirs, dependencies: [loguru_lib]) - -# Executable -lithium_server = executable('lithium_server', src_dir / 'App.cpp', - include_directories: inc_dirs, - link_with: [ - lithium_server_lib, - # Add other libraries here - ], - dependencies: [ - openssl_dep, - cfitsio_dep, - zlib_dep, - sqlite3_dep, - fmt_dep - ] -) - -# Platform-specific settings -if host_machine.system() == 'windows' - lithium_server.link_with('pdh', 'iphlpapi', 'winmm', 'crypt32', 'wsock32', 'ws2_32') - dlfcn_dep = dependency('dlfcn-win32', required: true) - lithium_server.link_with(dlfcn_dep) -elif host_machine.system() == 'linux' - lithium_server.link_with('dl') -elif host_machine.system() == 'darwin' - lithium_server.link_with('dl') -else - error('Unsupported platform') -endif - -# Install -install_data('config.h', install_dir: get_option('includedir')) -install_data(lithium_server, install_dir: get_option('bindir')) diff --git a/modules/atom.error/CMakeLists.txt b/modules/atom.error/CMakeLists.txt new file mode 100644 index 00000000..5af0e2dd --- /dev/null +++ b/modules/atom.error/CMakeLists.txt @@ -0,0 +1,70 @@ +# CMakeLists.txt for Atom-Error-Builtin +# This project is licensed under the terms of the GPL3 license. +# +# Project Name: Atom-Error-Builtin +# Description: A builtin module for Atom-Error +# Author: Max Qian +# License: GPL3 + +cmake_minimum_required(VERSION 3.20) +project(atom.error C CXX) + +set(CMAKE_ATOM_ERROR_BUILTIN_VERSION_MAJOR 1) +set(CMAKE_ATOM_ERROR_BUILTIN_VERSION_MINOR 0) +set(CMAKE_ATOM_ERROR_BUILTIN_VERSION_RELEASE 0) + +set(ATOM_ERROR_BUILTIN_SOVERSION ${CMAKE_ATOM_ERROR_BUILTIN_VERSION_MAJOR}) +set(CMAKE_ATOM_ERROR_BUILTIN_VERSION_STRING "${CMAKE_ATOM_ERROR_BUILTIN_VERSION_MAJOR}.${CMAKE_ATOM_ERROR_BUILTIN_VERSION_MINOR}.${CMAKE_ATOM_ERROR_BUILTIN_VERSION_RELEASE}") +set(ATOM_ERROR_BUILTIN_VERSION ${CMAKE_ATOM_ERROR_BUILTIN_VERSION_MAJOR}.${CMAKE_ATOM_ERROR_BUILTIN_VERSION_MINOR}.${CMAKE_ATOM_ERROR_BUILTIN_VERSION_RELEASE}) + +# Sources +set(${PROJECT_NAME}_SOURCES + _main.cpp + _component.cpp +) + +# Headers +set(${PROJECT_NAME}_HEADERS +) + +# Private Headers +set(${PROJECT_NAME}_PRIVATE_HEADERS + _component.hpp +) + +set(${PROJECT_NAME}_LIBS + loguru + atom-component + atom-error + ${ZLIB_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} +) + +# Build Object Library +add_library(${PROJECT_NAME}_OBJECT OBJECT) +set_property(TARGET ${PROJECT_NAME}_OBJECT PROPERTY POSITION_INDEPENDENT_CODE 1) + +target_sources(${PROJECT_NAME}_OBJECT + PUBLIC + ${${PROJECT_NAME}_HEADERS} + PRIVATE + ${${PROJECT_NAME}_SOURCES} + ${${PROJECT_NAME}_PRIVATE_HEADERS} +) + +target_link_libraries(${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) + +add_library(${PROJECT_NAME} SHARED) + +target_link_libraries(${PROJECT_NAME} ${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) +target_include_directories(${PROJECT_NAME} PUBLIC .) + +set_target_properties(${PROJECT_NAME} PROPERTIES + VERSION ${CMAKE_ATOM_ERROR_BUILTIN_VERSION_STRING} + SOVERSION ${ATOM_ERROR_BUILTIN_SOVERSION} + OUTPUT_NAME error +) + +install(TARGETS ${PROJECT_NAME} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) diff --git a/modules/atom.error/_component.cpp b/modules/atom.error/_component.cpp new file mode 100644 index 00000000..9773cf33 --- /dev/null +++ b/modules/atom.error/_component.cpp @@ -0,0 +1,48 @@ +/* + * _component.cpp + * + * Copyright (C) 2023-2024 Max Qian + */ + +/************************************************* + +Date: 2024-4-13 + +Description: Component of Atom-System + +**************************************************/ + +#include "_component.hpp" + +#include "atom/log/loguru.hpp" + +#include "atom/error/error_stack.hpp" + +using namespace atom::error; + +ErrorComponent::ErrorComponent(const std::string &name) : Component(name) { + DLOG_F(INFO, "ErrorComponent::ErrorComponent"); + + def("insert_error", &ErrorStack::insertError, errorStack_, "error", + "Insert an error into the error stack."); + def("set_filters", &ErrorStack::setFilteredModules, errorStack_, "error", + "Set filters."); + def("clear_filters", &ErrorStack::clearFilteredModules, errorStack_, + "error", " Clear filters."); + def("get_filtered_errors", &ErrorStack::getFilteredErrorsByModule, + errorStack_, "error", "Get filtered errors by module."); + def("print_filtered_error_stack", &ErrorStack::printFilteredErrorStack, + errorStack_, "error", "Print filtered error stack."); + def("get_compressed_errors", &ErrorStack::getCompressedErrors, errorStack_, + "error", "Get compressed errors."); + + addVariable("error_stack.instance", errorStack_); +} + +ErrorComponent::~ErrorComponent() { + DLOG_F(INFO, "ErrorComponent::~ErrorComponent"); +} + +auto ErrorComponent::initialize() -> bool { return true; } + +auto ErrorComponent::destroy() -> bool { return true; } diff --git a/modules/atom.error/_component.hpp b/modules/atom.error/_component.hpp new file mode 100644 index 00000000..6b3e3e9f --- /dev/null +++ b/modules/atom.error/_component.hpp @@ -0,0 +1,40 @@ +/* + * _component.hpp + * + * Copyright (C) 2023-2024 Max Qian + */ + +/************************************************* + +Date: 2024-4-13 + +Description: Component of Atom-IO + +**************************************************/ + +#ifndef ATOM_IO_COMPONENT_HPP +#define ATOM_IO_COMPONENT_HPP + +#include "atom/components/component.hpp" + +namespace atom::error { +class ErrorStack; +} + +class ErrorComponent : public Component { +public: + explicit ErrorComponent(const std::string &name); + ~ErrorComponent() override; + + // ------------------------------------------------------------------- + // Common methods + // ------------------------------------------------------------------- + + auto initialize() -> bool override; + auto destroy() -> bool override; + +private: + std::shared_ptr errorStack_; +}; + +#endif diff --git a/modules/atom.error/_main.cpp b/modules/atom.error/_main.cpp new file mode 100644 index 00000000..dd493f67 --- /dev/null +++ b/modules/atom.error/_main.cpp @@ -0,0 +1,30 @@ +/* + * _main.cpp + * + * Copyright (C) 2023-2024 Max Qian + */ + +/************************************************* + +Date: 2024-4-13 + +Description: Main Entry + +**************************************************/ + +#include "_component.hpp" +#include "macro.hpp" + +#include "atom/type/json.hpp" +using json = nlohmann::json; + +ATOM_EXTERN_C { + auto getInstance( + [[maybe_unused]] const json ¶ms) -> std::shared_ptr { + if (params.contains("name") && params["name"].is_string()) { + return std::make_shared( + params["name"].get()); + } + return std::make_shared("atom.error"); + } +} diff --git a/modules/atom.error/package.json b/modules/atom.error/package.json new file mode 100644 index 00000000..a458b41c --- /dev/null +++ b/modules/atom.error/package.json @@ -0,0 +1,34 @@ +{ + "name": "atom.error", + "version": "1.0.0", + "type": "shared", + "description": "Atom Error Module", + "license": "GPL-3.0-or-later", + "author": "Max Qian", + "repository": { + "type": "git", + "url": "https://github.com/ElementAstro/Lithium" + }, + "bugs": { + "type": "git", + "url": "https://github.com/ElementAstro/Lithium/issues" + }, + "homepage": { + "type": "git", + "url": "https://github.com/ElementAstro/Lithium" + }, + "keywords": [ + "lithium", + "config" + ], + "scripts": { + "build": "cmake --build-type=Release -- -j 4", + "lint": "clang-format -i src/*.cpp src/*.h" + }, + "modules": [ + { + "name": "error", + "entry": "getInstance" + } + ] +} diff --git a/modules/atom.error/xmake.lua b/modules/atom.error/xmake.lua new file mode 100644 index 00000000..40559942 --- /dev/null +++ b/modules/atom.error/xmake.lua @@ -0,0 +1,44 @@ +set_project("atom-error") +set_version("1.0.0") +set_xmakever("2.5.1") + +-- Define libraries +local atom_error_libs = { + "loguru", + "atom-component", + "atom-error", + "zlib" +} + +-- Sources and Headers +local atom_error_sources = { + "_main.cpp", + "_component.cpp" +} + +local atom_error_private_headers = { + "_component.hpp" +} + +-- Object Library +target("atom-error_object") + set_kind("object") + add_files(table.unpack(atom_error_sources)) + add_headerfiles(table.unpack(atom_error_private_headers)) + add_packages(table.unpack(atom_error_libs)) +target_end() + +-- Shared Library +target("atom-error") + set_kind("shared") + add_deps("atom-error_object") + add_files(table.unpack(atom_error_sources)) + add_headerfiles(table.unpack(atom_error_private_headers)) + add_packages(table.unpack(atom_error_libs)) + add_syslinks("pthread") + set_version("1.0.0", {build = "%Y%m%d%H%M"}) + set_targetdir("$(buildir)/lib") + on_install(function (target) + os.cp(target:targetfile(), path.join(target:installdir(), "lib")) + end) +target_end() diff --git a/driver/camera/atom-asi/_component.cpp b/modules/atom.io/.disabled similarity index 100% rename from driver/camera/atom-asi/_component.cpp rename to modules/atom.io/.disabled diff --git a/modules/atom.io/CMakeLists.txt b/modules/atom.io/CMakeLists.txt index 293a827c..4178d918 100644 --- a/modules/atom.io/CMakeLists.txt +++ b/modules/atom.io/CMakeLists.txt @@ -36,7 +36,6 @@ set(${PROJECT_NAME}_LIBS loguru atom-component atom-io - libzippp ${ZLIB_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) diff --git a/modules/atom.io/_component.cpp b/modules/atom.io/_component.cpp index 2125948f..28227707 100644 --- a/modules/atom.io/_component.cpp +++ b/modules/atom.io/_component.cpp @@ -26,21 +26,21 @@ using namespace atom::io; IOComponent::IOComponent(const std::string &name) : Component(name) { DLOG_F(INFO, "IOComponent::IOComponent"); - def("compress", &compress_file, "Compress a file"); - def("decompress", &decompress_file, "Decompress a file"); - def("create_zip", &create_zip, "Create a zip file"); - def("extract_zip", &extract_zip, "Extract a zip file"); - def("compress_folder", &compress_folder, "Compress a folder"); + def("compress", &compressFile, "Compress a file"); + def("decompress", &decompressFile, "Decompress a file"); + def("create_zip", &createZip, "Create a zip file"); + def("extract_zip", &extractZip, "Extract a zip file"); + def("compress_folder", &compressFolder, "Compress a folder"); def("translate", &glob::translate, "Translate a pattern"); - def("compile_pattern", &glob::compile_pattern, "Compile a pattern"); + def("compile_pattern", &glob::compilePattern, "Compile a pattern"); def("fnmatch", &glob::fnmatch, "Check if a name matches a pattern"); def("filter", &glob::filter, "Filter a list of names"); - def("expand_tilde", &glob::expand_tilde, "Expand a tilde"); - def("has_magic", &glob::has_magic, "Check if a pattern has magic"); - def("is_hidden", &glob::is_hidden, "Check if a path is hidden"); - def("is_recursive", &glob::is_recursive, "Check if a pattern is recursive"); - def("iter_dir", &glob::iter_directory, "Iterate a directory"); + def("expand_tilde", &glob::expandTilde, "Expand a tilde"); + def("has_magic", &glob::hasMagic, "Check if a pattern has magic"); + def("is_hidden", &glob::isHidden, "Check if a path is hidden"); + def("is_recursive", &glob::isRecursive, "Check if a pattern is recursive"); + def("iter_dir", &glob::iterDirectory, "Iterate a directory"); def("rlistdir", &glob::rlistdir, "Recursively list a directory"); def("glob", &glob::glob, "Glob a list of files"); def("rglob", &glob::rglob, @@ -70,6 +70,6 @@ IOComponent::IOComponent(const std::string &name) : Component(name) { IOComponent::~IOComponent() { DLOG_F(INFO, "IOComponent::~IOComponent"); } -bool IOComponent::initialize() { return true; } +auto IOComponent::initialize() -> bool { return true; } -bool IOComponent::destroy() { return true; } +auto IOComponent::destroy() -> bool { return true; } diff --git a/modules/atom.io/_main.cpp b/modules/atom.io/_main.cpp index b32f36f4..ecec1b6e 100644 --- a/modules/atom.io/_main.cpp +++ b/modules/atom.io/_main.cpp @@ -13,17 +13,18 @@ Description: Main Entry **************************************************/ #include "_component.hpp" +#include "macro.hpp" #include "atom/type/json.hpp" using json = nlohmann::json; -extern "C" { - -std::shared_ptr getInstance([[maybe_unused]] const json ¶ms) { - if (params.contains("name") && params["name"].is_string()) { - return std::make_shared( - params["name"].get()); +ATOM_EXTERN_C { + auto getInstance( + [[maybe_unused]] const json ¶ms) -> std::shared_ptr { + if (params.contains("name") && params["name"].is_string()) { + return std::make_shared( + params["name"].get()); + } + return std::make_shared("atom.system"); } - return std::make_shared("atom.system"); -} } diff --git a/modules/atom.io/xmake.lua b/modules/atom.io/xmake.lua new file mode 100644 index 00000000..86fa4b22 --- /dev/null +++ b/modules/atom.io/xmake.lua @@ -0,0 +1,44 @@ +set_project("atom-io") +set_version("1.0.0") +set_xmakever("2.5.1") + +-- Define libraries +local atom_io_libs = { + "loguru", + "atom-component", + "atom-io", + "zlib" +} + +-- Sources and Headers +local atom_io_sources = { + "_main.cpp", + "_component.cpp" +} + +local atom_io_private_headers = { + "_component.hpp" +} + +-- Object Library +target("atom-io_object") + set_kind("object") + add_files(table.unpack(atom_io_sources)) + add_headerfiles(table.unpack(atom_io_private_headers)) + add_packages(table.unpack(atom_io_libs)) +target_end() + +-- Shared Library +target("atom-io") + set_kind("shared") + add_deps("atom-io_object") + add_files(table.unpack(atom_io_sources)) + add_headerfiles(table.unpack(atom_io_private_headers)) + add_packages(table.unpack(atom_io_libs)) + add_syslinks("pthread") + set_version("1.0.0", {build = "%Y%m%d%H%M"}) + set_targetdir("$(buildir)/lib") + on_install(function (target) + os.cp(target:targetfile(), path.join(target:installdir(), "lib")) + end) +target_end() diff --git a/modules/atom.sysinfo/CMakeLists.txt b/modules/atom.sysinfo/CMakeLists.txt index ef57e1e1..efabd6ae 100644 --- a/modules/atom.sysinfo/CMakeLists.txt +++ b/modules/atom.sysinfo/CMakeLists.txt @@ -12,14 +12,6 @@ set(CMAKE_CXX_STANDARD 20) # Add source files set(SOURCE_FILES - src/battery.cpp - src/cpu.cpp - src/disk.cpp - src/gpu.cpp - src/memory.cpp - src/os.cpp - src/wifi.cpp - _component.cpp _main.cpp ) diff --git a/modules/atom.sysinfo/_component.cpp b/modules/atom.sysinfo/_component.cpp index a56808b1..04a8c230 100644 --- a/modules/atom.sysinfo/_component.cpp +++ b/modules/atom.sysinfo/_component.cpp @@ -33,10 +33,10 @@ SysInfoComponent::SysInfoComponent(const std::string& name) : Component(name) { "Get current CPU temperature"); def("memory_usage", &atom::system::getMemoryUsage, "memory", "Get current memory usage percentage"); - def("is_charging", &isBatteryCharging, PointerSentinel(this), "battery", - "Check if the battery is charging"); - def("battery_level", &getCurrentBatteryLevel, PointerSentinel(this), - "battery", "Get current battery level"); + def("is_charging", &SysInfoComponent::isBatteryCharging, + PointerSentinel(this), "battery", "Check if the battery is charging"); + def("battery_level", &SysInfoComponent::getCurrentBatteryLevel, + PointerSentinel(this), "battery", "Get current battery level"); def("disk_usage", &atom::system::getDiskUsage, "disk", "Get current disk usage percentage"); def("is_hotspot_connected", &atom::system::isHotspotConnected, "wifi", @@ -48,9 +48,10 @@ SysInfoComponent::SysInfoComponent(const std::string& name) : Component(name) { def("current_ip", &atom::system::getHostIPs, "network", "Get current IP address"); def("gpu_info", &atom::system::getGPUInfo, "gpu", "Get GPU info"); - def("os_name", &getOSName, PointerSentinel(this), "os", "Get OS name"); - def("os_version", &getOSVersion, PointerSentinel(this), "os", - "Get OS version"); + def("os_name", &SysInfoComponent::getOSName, PointerSentinel(this), "os", + "Get OS name"); + def("os_version", &SysInfoComponent::getOSVersion, PointerSentinel(this), + "os", "Get OS version"); } SysInfoComponent::~SysInfoComponent() { diff --git a/modules/atom.sysinfo/_component.hpp b/modules/atom.sysinfo/_component.hpp index 52034d0f..9468983f 100644 --- a/modules/atom.sysinfo/_component.hpp +++ b/modules/atom.sysinfo/_component.hpp @@ -21,7 +21,7 @@ Description: A collector for system information, not the same as atom.system class SysInfoComponent : public Component { public: explicit SysInfoComponent(const std::string& name); - virtual ~SysInfoComponent(); + ~SysInfoComponent() override; bool initialize() override; bool destroy() override; diff --git a/modules/atom.sysinfo/_main.cpp b/modules/atom.sysinfo/_main.cpp index 5ca1efcf..79f7998b 100644 --- a/modules/atom.sysinfo/_main.cpp +++ b/modules/atom.sysinfo/_main.cpp @@ -13,16 +13,18 @@ Description: Main Entry **************************************************/ #include "_component.hpp" +#include "macro.hpp" #include "atom/type/json.hpp" using json = nlohmann::json; -extern "C" { -std::shared_ptr getInstance([[maybe_unused]] const json ¶ms) { - if (params.contains("name") && params["name"].is_string()) { - return std::make_shared( - params["name"].get()); +ATOM_EXTERN_C { + auto getInstance( + [[maybe_unused]] const json ¶ms) -> std::shared_ptr { + if (params.contains("name") && params["name"].is_string()) { + return std::make_shared( + params["name"].get()); + } + return std::make_shared("atom.sysinfo"); } - return std::make_shared("atom.sysinfo"); -} } diff --git a/modules/atom.sysinfo/include/atom/sysinfo/cpu.hpp b/modules/atom.sysinfo/include/atom/sysinfo/cpu.hpp deleted file mode 100644 index e4d14365..00000000 --- a/modules/atom.sysinfo/include/atom/sysinfo/cpu.hpp +++ /dev/null @@ -1,86 +0,0 @@ -/* - * cpu.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-2-21 - -Description: System Information Module - CPU - -**************************************************/ - -#ifndef ATOM_SYSTEM_MODULE_CPU_HPP -#define ATOM_SYSTEM_MODULE_CPU_HPP - -#include - -namespace atom::system { -/** - * @brief Get the CPU usage percentage. - * 获取 CPU 使用率百分比 - * - * @return The CPU usage percentage. - * CPU 使用率百分比 - */ -float getCurrentCpuUsage(); - -/** - * @brief Get the CPU temperature. - * 获取 CPU 温度 - * - * @return The CPU temperature. - * CPU 温度 - */ -float getCurrentCpuTemperature(); - -/** - * @brief Get the CPU model. - * 获取 CPU 型号 - * - * @return The CPU model. - * CPU 型号 - */ -std::string getCPUModel(); - -/** - * @brief Get the CPU identifier. - * 获取 CPU 标识 - * - * @return The CPU identifier. - * CPU 标识 - */ -std::string getProcessorIdentifier(); - -/** - * @brief Get the CPU frequency. - * 获取 CPU 频率 - * - * @return The CPU frequency. - * CPU 频率 - */ -double getProcessorFrequency(); - -/** - * @brief Get the number of physical CPUs. - * 获取物理 CPU 数量 - * - * @return The number of physical CPUs. - * 物理 CPU 数量 - */ -int getNumberOfPhysicalPackages(); - -/** - * @brief Get the number of logical CPUs. - * 获取逻辑 CPU 数量 - * - * @return The number of logical CPUs. - * 逻辑 CPU 数量 - */ -int getNumberOfPhysicalCPUs(); - -} // namespace atom::system - -#endif diff --git a/modules/atom.sysinfo/include/atom/sysinfo/memory.hpp b/modules/atom.sysinfo/include/atom/sysinfo/memory.hpp deleted file mode 100644 index 6e1ac387..00000000 --- a/modules/atom.sysinfo/include/atom/sysinfo/memory.hpp +++ /dev/null @@ -1,113 +0,0 @@ -/* - * memory.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-2-21 - -Description: System Information Module - Memory - -**************************************************/ - -#ifndef ATOM_SYSTEM_MODULE_MEMORY_HPP -#define ATOM_SYSTEM_MODULE_MEMORY_HPP - -#include -#include - -namespace atom::system { -struct MemoryInfo { - struct MemorySlot { - std::string capacity; - std::string clockSpeed; - std::string type; - - MemorySlot() = default; - MemorySlot(const std::string &capacity, const std::string &clockSpeed, const std::string &type) - : capacity(capacity), clockSpeed(clockSpeed), type(type) {} - }; - - std::vector slots; - unsigned long long virtualMemoryMax; - unsigned long long virtualMemoryUsed; - unsigned long long swapMemoryTotal; - unsigned long long swapMemoryUsed; -}; - -/** - * @brief Get the memory usage percentage. - * 获取内存使用率百分比 - * - * @return The memory usage percentage. - * 内存使用率百分比 - */ -float getMemoryUsage(); - -/** - * @brief Get the total memory size. - * 获取总内存大小 - * - * @return The total memory size. - * 总内存大小 - */ -unsigned long long getTotalMemorySize(); - -/** - * @brief Get the available memory size. - * 获取可用内存大小 - * - * @return The available memory size. - * 可用内存大小 - */ -unsigned long long getAvailableMemorySize(); - -/** - * @brief Get the physical memory slot info. - * 获取物理内存槽信息 - * - * @return The physical memory slot info. - * 物理内存槽信息 - */ -MemoryInfo::MemorySlot getPhysicalMemoryInfo(); - -/** - * @brief Get the virtual memory max size. - * 获取虚拟内存最大值 - * - * @return The virtual memory max size. - * 虚拟内存最大值 - */ -unsigned long long getVirtualMemoryMax(); - -/** - * @brief Get the virtual memory used size. - * 获取虚拟内存已用值 - * - * @return The virtual memory used size. - * 虚拟内存已用值 - */ -unsigned long long getVirtualMemoryUsed(); - -/** - * @brief Get the swap memory total size. - * 获取交换内存总值 - * - * @return The swap memory total size. - * 交换内存总值 - */ -unsigned long long getSwapMemoryTotal(); - -/** - * @brief Get the swap memory used size. - * 获取交换内存已用值 - * - * @return The swap memory used size. - * 交换内存已用值 - */ -unsigned long long getSwapMemoryUsed(); -} // namespace atom::system - -#endif diff --git a/modules/atom.sysinfo/include/atom/sysinfo/os.hpp b/modules/atom.sysinfo/include/atom/sysinfo/os.hpp deleted file mode 100644 index 5150d974..00000000 --- a/modules/atom.sysinfo/include/atom/sysinfo/os.hpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * os.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-2-21 - -Description: System Information Module - OS Information - -**************************************************/ - -#ifndef ATOM_SYSTEM_MODULE_OS_HPP -#define ATOM_SYSTEM_MODULE_OS_HPP - -#include - -namespace atom::system { -/** - * @brief Represents information about the operating system. - */ -struct OperatingSystemInfo { - std::string osName; /**< The name of the operating system. */ - std::string osVersion; /**< The version of the operating system. */ - std::string kernelVersion; /**< The version of the kernel. */ - std::string architecture; /**< The architecture of the operating system. */ - std::string compiler; /**< The compiler used to compile the operating system. */ - - OperatingSystemInfo() = default; - - std::string toJson() const; -}; - -/** - * @brief Retrieves the information about the operating system. - * @return The `OperatingSystemInfo` struct containing the operating system - * information. - */ -OperatingSystemInfo getOperatingSystemInfo(); - -} // namespace atom::system - -#endif diff --git a/modules/atom.sysinfo/include/atom/sysinfo/wifi.hpp b/modules/atom.sysinfo/include/atom/sysinfo/wifi.hpp deleted file mode 100644 index 062ed1f4..00000000 --- a/modules/atom.sysinfo/include/atom/sysinfo/wifi.hpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * wifi.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-2-21 - -Description: System Information Module - Wifi Information - -**************************************************/ - -#ifndef ATOM_SYSTEM_MODULE_WIFI_HPP -#define ATOM_SYSTEM_MODULE_WIFI_HPP - -#include -#include - -namespace atom::system { -/** - * @brief Get current wifi name - * @return Current wifi name - */ -[[nodiscard]] std::string getCurrentWifi(); - -/** - * @brief Get current wired network name - * @return Current wired network name - */ -[[nodiscard]] std::string getCurrentWiredNetwork(); - -/** - * @brief Check if hotspot is connected - * @return True if hotspot is connected - */ -[[nodiscard]] bool isHotspotConnected(); - -/* - * @brief Get host IP addresses - * @return Vector of host IP addresses - */ -std::vector getHostIPs(); -} // namespace atom::system - -#endif diff --git a/modules/atom.sysinfo/src/battery.cpp b/modules/atom.sysinfo/src/battery.cpp deleted file mode 100644 index 4f46334d..00000000 --- a/modules/atom.sysinfo/src/battery.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - * battery.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-2-21 - -Description: System Information Module - Battery - -**************************************************/ - -#include "atom/sysinfo/battery.hpp" - -#include -#include -#include -#include - -#ifdef _WIN32 -#include -#include -#elif defined(__APPLE__) -#include -#include -#elif defined(__linux__) -#include -#include -#endif - -namespace atom::system { -BatteryInfo getBatteryInfo() { - BatteryInfo info; - -#ifdef _WIN32 - SYSTEM_POWER_STATUS powerStatus; - if (GetSystemPowerStatus(&powerStatus)) { - info.isBatteryPresent = powerStatus.BatteryFlag != 128; - info.isCharging = - powerStatus.BatteryFlag == 8 || powerStatus.ACLineStatus == 1; - info.batteryLifePercent = - static_cast(powerStatus.BatteryLifePercent); - info.batteryLifeTime = - powerStatus.BatteryLifeTime == 0xFFFFFFFF - ? 0.0 - : static_cast(powerStatus.BatteryLifeTime); - info.batteryFullLifeTime = - powerStatus.BatteryFullLifeTime == 0xFFFFFFFF - ? 0.0 - : static_cast(powerStatus.BatteryFullLifeTime); - // 其他电池信息... - } -#elif defined(__APPLE__) - CFTypeRef powerSourcesInfo = IOPSCopyPowerSourcesInfo(); - CFArrayRef powerSources = IOPSCopyPowerSourcesList(powerSourcesInfo); - - CFIndex count = CFArrayGetCount(powerSources); - if (count > 0) { - CFDictionaryRef powerSource = CFArrayGetValueAtIndex(powerSources, 0); - - // 是否连接电源 - CFBooleanRef isCharging = - (CFBooleanRef)CFDictionaryGetValue(powerSource, kIOPSIsChargingKey); - if (isCharging != nullptr) { - info.isCharging = CFBooleanGetValue(isCharging); - } - - // 电池剩余容量百分比 - CFNumberRef capacity = (CFNumberRef)CFDictionaryGetValue( - powerSource, kIOPSCurrentCapacityKey); - if (capacity != nullptr) { - SInt32 value; - CFNumberGetValue(capacity, kCFNumberSInt32Type, &value); - info.batteryLifePercent = static_cast(value); - } - - // 电池剩余时间 - CFNumberRef timeToEmpty = - (CFNumberRef)CFDictionaryGetValue(powerSource, kIOPSTimeToEmptyKey); - if (timeToEmpty != nullptr) { - SInt32 value; - CFNumberGetValue(timeToEmpty, kCFNumberSInt32Type, &value); - info.batteryLifeTime = - static_cast(value) / 60.0f; // 转换为分钟 - } - - // 电池总容量 - CFNumberRef capacityMax = - (CFNumberRef)CFDictionaryGetValue(powerSource, kIOPSMaxCapacityKey); - if (capacityMax != nullptr) { - SInt32 value; - CFNumberGetValue(capacityMax, kCFNumberSInt32Type, &value); - info.batteryFullLifeTime = static_cast(value); - } - } - - CFRelease(powerSources); - CFRelease(powerSourcesInfo); -#elif defined(__linux__) - std::ifstream batteryInfo("/sys/class/power_supply/BAT0/uevent"); - if (batteryInfo.is_open()) { - std::string line; - while (std::getline(batteryInfo, line)) { - if (line.find("POWER_SUPPLY_PRESENT") != std::string::npos) { - info.isBatteryPresent = line.substr(line.find("=") + 1) == "1"; - } else if (line.find("POWER_SUPPLY_STATUS") != std::string::npos) { - std::string status = line.substr(line.find("=") + 1); - info.isCharging = status == "Charging" || status == "Full"; - } else if (line.find("POWER_SUPPLY_CAPACITY") != - std::string::npos) { - info.batteryLifePercent = - std::stof(line.substr(line.find("=") + 1)); - } else if (line.find("POWER_SUPPLY_TIME_TO_EMPTY_MIN") != - std::string::npos) { - info.batteryLifeTime = - std::stof(line.substr(line.find("=") + 1)); - } else if (line.find("POWER_SUPPLY_TIME_TO_FULL_NOW") != - std::string::npos) { - info.batteryFullLifeTime = - std::stof(line.substr(line.find("=") + 1)); - } else if (line.find("POWER_SUPPLY_ENERGY_NOW") != - std::string::npos) { - info.energyNow = std::stof(line.substr(line.find("=") + 1)); - } else if (line.find("POWER_SUPPLY_ENERGY_FULL_DESIGN") != - std::string::npos) { - info.energyDesign = std::stof(line.substr(line.find("=") + 1)); - } else if (line.find("POWER_SUPPLY_VOLTAGE_NOW") != - std::string::npos) { - info.voltageNow = - std::stof(line.substr(line.find("=") + 1)) / 1000000.0f; - } else if (line.find("POWER_SUPPLY_CURRENT_NOW") != - std::string::npos) { - info.currentNow = - std::stof(line.substr(line.find("=") + 1)) / 1000000.0f; - } - } - } -#endif - return info; -} -} // namespace atom::system diff --git a/modules/atom.sysinfo/src/cpu.cpp b/modules/atom.sysinfo/src/cpu.cpp deleted file mode 100644 index 2f53919c..00000000 --- a/modules/atom.sysinfo/src/cpu.cpp +++ /dev/null @@ -1,419 +0,0 @@ -/* - * cpu.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-2-21 - -Description: System Information Module - CPU - -**************************************************/ - -#include "atom/sysinfo/cpu.hpp" - -#include -#include -#include -#include -#include -namespace fs = std::filesystem; - -#ifdef _WIN32 -#include -#include -#include -#include -#include -#include -#include -#elif __linux__ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#elif __APPLE__ -#include -#include -#include -#include -#include -#include -#include -#endif - -#include "atom/log/loguru.hpp" - -namespace atom::system { -float getCurrentCpuUsage() { - float cpu_usage = 0.0; - -#ifdef _WIN32 - PDH_HQUERY query; - PdhOpenQuery(nullptr, 0, &query); - - PDH_HCOUNTER counter; - PdhAddCounter(query, "\\Processor(_Total)\\% Processor Time", 0, &counter); - PdhCollectQueryData(query); - - PDH_FMT_COUNTERVALUE counter_value; - PdhGetFormattedCounterValue(counter, PDH_FMT_DOUBLE, nullptr, - &counter_value); - - cpu_usage = static_cast(counter_value.doubleValue); - - PdhCloseQuery(query); -#elif __linux__ - std::ifstream file("/proc/stat"); - if (!file.is_open()) { - LOG_F(ERROR, "Failed to open /proc/stat"); - return cpu_usage; - } - std::string line; - std::getline(file, line); // 读取第一行 - - std::istringstream iss(line); - std::vector tokens(std::istream_iterator{iss}, - std::istream_iterator()); - - unsigned long total_time = 0; - for (size_t i = 1; i < tokens.size(); i++) { - total_time += std::stoul(tokens[i]); - } - - unsigned long idle_time = std::stoul(tokens[4]); - - float usage = static_cast(total_time - idle_time) / total_time; - cpu_usage = usage * 100.0; -#elif __APPLE__ - host_cpu_load_info_data_t cpu_load; - mach_msg_type_number_t count = HOST_CPU_LOAD_INFO_COUNT; - if (host_statistics64(mach_host_self(), HOST_CPU_LOAD_INFO, - reinterpret_cast(&cpu_load), - &count) == KERN_SUCCESS) { - uint64_t user_time = cpu_load.cpu_ticks[CPU_STATE_USER] - - cpu_load.cpu_ticks[CPU_STATE_NICE]; - uint64_t sys_time = cpu_load.cpu_ticks[CPU_STATE_SYSTEM] + - cpu_load.cpu_ticks[CPU_STATE_NICE]; - uint64_t idle_time = cpu_load.cpu_ticks[CPU_STATE_IDLE]; - uint64_t total_time = user_time + sys_time + idle_time; - - cpu_usage = static_cast(user_time + sys_time) / total_time; - cpu_usage *= 100.0; - } else { - LOG_F(ERROR, "Failed to get CPU temperature"); - } -#elif __ANDROID__ - // Android 实现 - android::sp battery_stat_service = - android::interface_cast( - android::defaultServiceManager()->getService( - android::String16("batterystats"))); - android::BatteryStats::Uid uid = - battery_stat_service->getUidStats(android::Process::myUid()); - int32_t user_time = uid.getUidCpuTime(android::BatteryStats::UID_TIME_USER); - int32_t system_time = - uid.getUidCpuTime(android::BatteryStats::UID_TIME_SYSTEM); - int32_t idle_time = uid.getUidCpuTime(android::BatteryStats::UID_TIME_IDLE); - int32_t total_time = user_time + system_time + idle_time; - - cpu_usage = static_cast(user_time + system_time) / total_time; - cpu_usage *= 100.0; -#endif - - return cpu_usage; -} - -float getCurrentCpuTemperature() { - float temperature = 0.0f; - -#ifdef _WIN32 - HKEY hKey; - DWORD temperatureValue = 0; - DWORD size = sizeof(DWORD); - - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, - "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, - KEY_READ, &hKey) == ERROR_SUCCESS) { - if (RegQueryValueEx(hKey, "~MHz", NULL, NULL, (LPBYTE)&temperatureValue, - &size) == ERROR_SUCCESS) { - temperature = static_cast(temperatureValue) / 10.0f; - } - RegCloseKey(hKey); - } -#elif defined(__APPLE__) - FILE *pipe = popen("sysctl -a | grep machdep.xcpm.cpu_thermal_level", "r"); - if (pipe != nullptr) { - char buffer[128]; - if (fgets(buffer, sizeof(buffer), pipe) != nullptr) { - std::string result(buffer); - size_t pos1 = result.find(": "); - size_t pos2 = result.find("\n"); - if (pos1 != std::string::npos && pos2 != std::string::npos) { - std::string tempStr = result.substr(pos1 + 2, pos2 - pos1 - 2); - try { - temperature = std::stof(tempStr); - } catch (const std::exception &e) { - LOG_F(ERROR, "GetCpuTemperature error: {}", e.what()); - } - } - } else { - LOG_F(ERROR, "GetCpuTemperature error: popen error"); - } - pclose(pipe); - } -#elif defined(__linux__) - std::ifstream tempFile("/sys/class/thermal/thermal_zone0/temp"); - if (tempFile.is_open()) { - int temp = 0; - tempFile >> temp; - tempFile.close(); - temperature = static_cast(temp) / 1000.0f; - } else { - LOG_F(ERROR, "GetMemoryUsage error: open /proc/meminfo error"); - } -#elif defined(__ANDROID__) - // Android 实现 - std::ifstream tempFile("/sys/class/thermal/thermal_zone0/temp"); - if (tempFile.is_open()) { - int temp = 0; - tempFile >> temp; - tempFile.close(); - temperature = static_cast(temp) / 1000.0f; - } else { - LOG_F(ERROR, - "GetCpuTemperature error: open " - "/sys/class/thermal/thermal_zone0/temp error"); - } -#endif - - return temperature; -} - -std::string getCPUModel() { - std::string cpuModel; -#ifdef _WIN32 - - HKEY hKey; - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, - "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, - KEY_READ, &hKey) == ERROR_SUCCESS) { - char cpuName[1024]; - DWORD size = sizeof(cpuName); - if (RegQueryValueEx(hKey, "ProcessorNameString", NULL, NULL, - (LPBYTE)cpuName, &size) == ERROR_SUCCESS) { - cpuModel = cpuName; - } - RegCloseKey(hKey); - } - -#elif __linux__ - - std::ifstream cpuinfo("/proc/cpuinfo"); - std::string line; - while (std::getline(cpuinfo, line)) { - if (line.substr(0, 10) == "model name") { - cpuModel = line.substr(line.find(":") + 2); - break; - } - } - cpuinfo.close(); -#elif defined(__APPLE__) - FILE *pipe = popen("sysctl -n machdep.cpu.brand_string", "r"); - if (pipe != nullptr) { - char buffer[128]; - if (fgets(buffer, sizeof(buffer), pipe) != nullptr) { - cpuModel = buffer; - cpuModel.erase(std::remove(cpuModel.begin(), cpuModel.end(), '\n'), - cpuModel.end()); - } else { - LOG_F(ERROR, "GetCPUModel error: popen error"); - } - pclose(pipe); - } -#elif defined(__ANDROID__) - FILE *pipe = popen("getprop ro.product.model", "r"); - if (pipe != nullptr) { - char buffer[128]; - if (fgets(buffer, sizeof(buffer), pipe) != nullptr) { - cpuModel = buffer; - cpuModel.erase(std::remove(cpuModel.begin(), cpuModel.end(), '\n'), - cpuModel.end()); - } else { - LOG_F(ERROR, "GetCPUModel error: popen error"); - } - pclose(pipe); - } -#endif - return cpuModel; -} - -std::string getProcessorIdentifier() { - std::string identifier; - -#ifdef _WIN32 - HKEY hKey; - char identifierValue[256]; - DWORD bufSize = sizeof(identifierValue); - - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, - "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, - KEY_READ, &hKey) == ERROR_SUCCESS) { - RegQueryValueEx(hKey, "Identifier", NULL, NULL, (LPBYTE)identifierValue, - &bufSize); - RegCloseKey(hKey); - - identifier = identifierValue; - } -#elif defined(__linux__) - std::ifstream cpuinfo("/proc/cpuinfo"); - std::string line; - while (std::getline(cpuinfo, line)) { - if (line.substr(0, 9) == "processor") { - identifier = line.substr(line.find(":") + 2); - break; - } - } -#elif defined(__APPLE__) - FILE *pipe = popen("sysctl -n machdep.cpu.brand_string", "r"); - if (pipe != nullptr) { - char buffer[128]; - if (fgets(buffer, sizeof(buffer), pipe) != nullptr) { - identifier = buffer; - identifier.erase( - std::remove(identifier.begin(), identifier.end(), '\n'), - identifier.end()); - } else { - LOG_F(ERROR, "GetProcessorIdentifier error: popen error"); - } - pclose(pipe); - } -#elif defined(__ANDROID__) - // Android 实现 - std::ifstream cpuinfo("/proc/cpuinfo"); - std::string line; - while (std::getline(cpuinfo, line)) { - if (line.substr(0, 9) == "processor") { - identifier = line.substr(line.find(":") + 2); - break; - } - } - cpuinfo.close(); -#endif - - return identifier; -} - -double getProcessorFrequency() { - double frequency = 0; - -#ifdef _WIN32 - HKEY hKey; - DWORD frequencyValue; - DWORD bufSize = sizeof(frequencyValue); - - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, - "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, - KEY_READ, &hKey) == ERROR_SUCCESS) { - RegQueryValueEx(hKey, "~MHz", NULL, NULL, (LPBYTE)&frequencyValue, - &bufSize); - RegCloseKey(hKey); - - frequency = static_cast(frequencyValue) / - 1000.0; // Convert frequency to GHz - } -#elif defined(__linux__) - // Linux 实现 - std::ifstream cpuinfo("/proc/cpuinfo"); - std::string line; - while (std::getline(cpuinfo, line)) { - if (line.substr(0, 7) == "cpu MHz") { - std::size_t pos = line.find(":") + 2; - frequency = std::stod(line.substr(pos)) / - 1000.0; // Convert frequency to GHz - break; - } - } - cpuinfo.close(); -#elif defined(__APPLE__) - // macOS 实现 - FILE *pipe = popen("sysctl -n hw.cpufrequency", "r"); - if (pipe != nullptr) { - char buffer[128]; - if (fgets(buffer, sizeof(buffer), pipe) != nullptr) { - frequency = std::stod(buffer) / 1e9; // Convert frequency to GHz - } else { - LOG_F(ERROR, "GetProcessorFrequency error: popen error"); - } - pclose(pipe); - } -#endif - - return frequency; -} - -int getNumberOfPhysicalPackages() { - int numberOfPackages = 0; - -#ifdef _WIN32 - SYSTEM_INFO systemInfo; - GetSystemInfo(&systemInfo); - numberOfPackages = systemInfo.dwNumberOfProcessors; -#elif defined(__APPLE__) - FILE *pipe = popen("sysctl -n hw.packages", "r"); - if (pipe != nullptr) { - char buffer[128]; - if (fgets(buffer, sizeof(buffer), pipe) != nullptr) { - numberOfPackages = std::stoi(buffer); - } else { - LOG_F(ERROR, "GetNumberOfPhysicalPackages error: popen error"); - } - pclose(pipe); - } -#elif defined(__linux__) - numberOfPackages = sysconf(_SC_PHYS_PAGES); -#endif - - return numberOfPackages; -} - -int getNumberOfPhysicalCPUs() { - int numberOfCPUs = 0; - -#ifdef _WIN32 - SYSTEM_INFO systemInfo; - GetSystemInfo(&systemInfo); - numberOfCPUs = systemInfo.dwNumberOfProcessors; -#elif defined(__APPLE__) - FILE *pipe = popen("sysctl -n hw.physicalcpu", "r"); - if (pipe != nullptr) { - char buffer[128]; - if (fgets(buffer, sizeof(buffer), pipe) != nullptr) { - numberOfCPUs = std::stoi(buffer); - } else { - LOG_F(ERROR, "GetNumberOfPhysicalCPUs error: popen error"); - } - pclose(pipe); - } -#elif defined(__linux__) - std::ifstream cpuinfo("/proc/cpuinfo"); - std::string line; - while (std::getline(cpuinfo, line)) { - if (line.substr(0, 7) == "physical") { - numberOfCPUs = std::stoi(line.substr(line.find(":") + 2)); - break; - } - } -#endif - - return numberOfCPUs; -} - -} // namespace atom::system diff --git a/modules/atom.sysinfo/src/disk.cpp b/modules/atom.sysinfo/src/disk.cpp deleted file mode 100644 index df427e04..00000000 --- a/modules/atom.sysinfo/src/disk.cpp +++ /dev/null @@ -1,243 +0,0 @@ -/* - * disk.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-2-21 - -Description: System Information Module - Disk - -**************************************************/ - -#include "atom/sysinfo/disk.hpp" - -#include "atom/log/loguru.hpp" - -#include -#include -#include -#include -#include - -#ifdef _WIN32 -#include -#elif __linux__ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#elif __APPLE__ -#include -#include -#include -#endif - -namespace atom::system { -std::vector> getDiskUsage() { - std::vector> disk_usage; - -#ifdef _WIN32 - DWORD drives = GetLogicalDrives(); - char drive_letter = 'A'; - - while (drives) { - if (drives & 1) { - std::string drive_path = std::string(1, drive_letter) + ":\\"; - ULARGE_INTEGER total_space, free_space; - - if (GetDiskFreeSpaceExA(drive_path.c_str(), nullptr, &total_space, - &free_space)) { - unsigned long long total = total_space.QuadPart / (1024 * 1024); - unsigned long long free = free_space.QuadPart / (1024 * 1024); - - float usage = 100.0 * static_cast(total - free) / total; - disk_usage.push_back(std::make_pair(drive_path, usage)); - } else { - LOG_F(ERROR, "GetDiskUsage error: GetDiskFreeSpaceExA error"); - } - } - - drives >>= 1; - drive_letter++; - } -#elif __linux__ || __APPLE__ - std::ifstream file("/proc/mounts"); - std::string line; - while (std::getline(file, line)) { - std::istringstream iss(line); - std::string device, path; - iss >> device >> path; - - struct statfs stats; - if (statfs(path.c_str(), &stats) == 0) { - unsigned long long totalSpace = - static_cast(stats.f_blocks) * stats.f_bsize; - unsigned long long freeSpace = - static_cast(stats.f_bfree) * stats.f_bsize; - - unsigned long long usedSpace = totalSpace - freeSpace; - float usage = static_cast(usedSpace) / totalSpace * 100.0; - disk_usage.push_back({path, usage}); - } else { - LOG_F(ERROR, "GetDiskUsage error: statfs error"); - } - } - -#endif - - return disk_usage; -} - -std::string getDriveModel(const std::string& drivePath) { - std::string model; - -#ifdef _WIN32 - HANDLE hDevice = - CreateFileA(drivePath.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, - nullptr, OPEN_EXISTING, 0, nullptr); - if (hDevice != INVALID_HANDLE_VALUE) { - STORAGE_PROPERTY_QUERY query = {}; - std::array buffer = {}; - query.PropertyId = StorageDeviceProperty; - query.QueryType = PropertyStandardQuery; - DWORD bytesReturned = 0; - if (DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY, &query, - sizeof(query), buffer.data(), buffer.size(), - &bytesReturned, nullptr)) { - auto desc = - reinterpret_cast(buffer.data()); - std::string_view vendorId(buffer.data() + desc->VendorIdOffset); - std::string_view productId(buffer.data() + desc->ProductIdOffset); - std::string_view productRevision(buffer.data() + - desc->ProductRevisionOffset); - model = std::string(vendorId) + " " + std::string(productId) + " " + - std::string(productRevision); - } - CloseHandle(hDevice); - } -#elif __APPLE__ - DASessionRef session = DASessionCreate(kCFAllocatorDefault); - if (session != nullptr) { - CFURLRef url = CFURLCreateWithFileSystemPath( - kCFAllocatorDefault, - CFStringCreateWithCString(kCFAllocatorDefault, drivePath.c_str(), - kCFStringEncodingUTF8), - kCFURLPOSIXPathStyle, false); - if (url != nullptr) { - DADiskRef disk = - DADiskCreateFromBSDName(kCFAllocatorDefault, session, - CFURLGetFileSystemRepresentation(url)); - if (disk != nullptr) { - CFDictionaryRef desc = DADiskCopyDescription(disk); - if (desc != nullptr) { - CFStringRef modelRef = - static_cast(CFDictionaryGetValue( - desc, kDADiskDescriptionDeviceModelKey)); - if (modelRef != nullptr) { - char buffer[256]; - CFStringGetCString(modelRef, buffer, 256, - kCFStringEncodingUTF8); - model = buffer; - } - CFRelease(desc); - } - CFRelease(disk); - } - CFRelease(url); - } - CFRelease(session); - } -#elif __linux__ - std::ifstream inFile("/sys/block/" + drivePath + "/device/model"); - if (inFile.is_open()) { - std::getline(inFile, model); - } -#endif - - return model; -} - -std::vector> getStorageDeviceModels() { - std::vector> storage_device_models; - -#ifdef _WIN32 - std::array driveStrings = {}; - DWORD length = - GetLogicalDriveStringsA(driveStrings.size(), driveStrings.data()); - if (length > 0 && length <= driveStrings.size()) { - std::span driveSpan(driveStrings.data(), length); - for (const auto& drive : - std::ranges::views::split(driveSpan, '\0') | - std::views::filter( - [](const std::span& s) { return !s.empty(); })) { - std::string drivePath(drive.data(), drive.size()); - UINT driveType = GetDriveTypeA(drivePath.c_str()); - if (driveType == DRIVE_FIXED) { - std::string model = getDriveModel(drivePath); - if (!model.empty()) { - storage_device_models.emplace_back(drivePath, - std::move(model)); - } - } - } - } -#else - fs::path sysBlockDir("/sys/block/"); - if (fs::exists(sysBlockDir) && fs::is_directory(sysBlockDir)) { - for (const auto& entry : fs::directory_iterator(sysBlockDir)) { - if (entry.is_directory() && entry.path().filename() != "." && - entry.path().filename() != "..") { - std::string devicePath = entry.path().filename().string(); - std::string model = getDriveModel(devicePath); - if (!model.empty()) { - storage_device_models.emplace_back(devicePath, - std::move(model)); - } - } - } - } -#endif - - return storage_device_models; -} - -std::vector getAvailableDrives() { - std::vector drives; - -#ifdef _WIN32 - DWORD drivesBitMask = GetLogicalDrives(); - for (char i = 'A'; i <= 'Z'; ++i) { - if (drivesBitMask & 1) { - std::string drive(1, i); - drives.push_back(drive + ":\\"); - } - drivesBitMask >>= 1; - } -#elif __linux__ - drives.push_back("/"); -#elif __APPLE__ - struct statfs* mounts; - int numMounts = getmntinfo(&mounts, MNT_NOWAIT); - for (int i = 0; i < numMounts; ++i) { - drives.push_back(mounts[i].f_mntonname); - } -#endif - - return drives; -} - -double calculateDiskUsagePercentage(unsigned long totalSpace, - unsigned long freeSpace) { - return ((static_cast(totalSpace) - static_cast(freeSpace)) / - totalSpace) * - 100.0; -} -} // namespace atom::system diff --git a/modules/atom.sysinfo/src/memory.cpp b/modules/atom.sysinfo/src/memory.cpp deleted file mode 100644 index 57cfbbca..00000000 --- a/modules/atom.sysinfo/src/memory.cpp +++ /dev/null @@ -1,339 +0,0 @@ -/* - * memory.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-2-21 - -Description: System Information Module - Memory - -**************************************************/ - -#include "atom/sysinfo/memory.hpp" - -#include -#include - -#include "atom/log/loguru.hpp" - -#ifdef _WIN32 -#include -#include -#include -#include -#include -#include -#include -#elif __linux__ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#elif __APPLE__ -#include -#include -#include -#include -#include -#include -#include -#endif - -namespace atom::system { -float getMemoryUsage() { - float memory_usage = 0.0; - -#ifdef _WIN32 - MEMORYSTATUSEX status; - status.dwLength = sizeof(status); - float total_memory = 0.0f; - float available_memory = 0.0f; - if (GlobalMemoryStatusEx(&status)) { - total_memory = static_cast(status.ullTotalPhys / 1024 / 1024); - available_memory = - static_cast(status.ullAvailPhys / 1024 / 1024); - memory_usage = (total_memory - available_memory) / total_memory * 100.0; - } else { - LOG_F(ERROR, "GetMemoryUsage error: GlobalMemoryStatusEx error"); - } -#elif __linux__ - std::ifstream file("/proc/meminfo"); - if (!file.is_open()) { - LOG_F(ERROR, "GetMemoryUsage error: open /proc/meminfo error"); - } - std::string line; - - unsigned long total_memory = 0; - unsigned long free_memory = 0; - unsigned long buffer_memory = 0; - unsigned long cache_memory = 0; - - while (std::getline(file, line)) { - std::istringstream iss(line); - std::string name; - unsigned long value; - - if (iss >> name >> value) { - if (name == "MemTotal:") { - total_memory = value; - } else if (name == "MemFree:") { - free_memory = value; - } else if (name == "Buffers:") { - buffer_memory = value; - } else if (name == "Cached:") { - cache_memory = value; - } - } - } - - unsigned long used_memory = - total_memory - free_memory - buffer_memory - cache_memory; - memory_usage = static_cast(used_memory) / total_memory * 100.0; -#elif __APPLE__ - struct statfs stats; - statfs("/", &stats); - - unsigned long long total_space = stats.f_blocks * stats.f_bsize; - unsigned long long free_space = stats.f_bfree * stats.f_bsize; - - unsigned long long used_space = total_space - free_space; - memory_usage = static_cast(used_space) / total_space * 100.0; -#elif defined(__ANDROID__) - LOG_F(ERROR, "GetTotalMemorySize error: not support"); -#endif - - return memory_usage; -} - -unsigned long long getTotalMemorySize() { - unsigned long long totalMemorySize = 0; - -#ifdef _WIN32 - MEMORYSTATUSEX status; - status.dwLength = sizeof(status); - GlobalMemoryStatusEx(&status); - totalMemorySize = status.ullTotalPhys; -#elif defined(__APPLE__) - FILE *pipe = popen("sysctl -n hw.memsize", "r"); - if (pipe != nullptr) { - char buffer[128]; - if (fgets(buffer, sizeof(buffer), pipe) != nullptr) { - totalMemorySize = std::stoull(buffer); - } else { - LOG_F(ERROR, "GetTotalMemorySize error: popen error"); - } - pclose(pipe); - } -#elif defined(__linux__) - long pages = sysconf(_SC_PHYS_PAGES); - long page_size = sysconf(_SC_PAGE_SIZE); - totalMemorySize = static_cast(pages) * - static_cast(page_size); -#endif - - return totalMemorySize; -} - -unsigned long long getAvailableMemorySize() { - unsigned long long availableMemorySize = 0; - -#ifdef _WIN32 - MEMORYSTATUSEX status; - status.dwLength = sizeof(status); - GlobalMemoryStatusEx(&status); - availableMemorySize = status.ullAvailPhys; -#elif defined(__APPLE__) - FILE *pipe = popen("vm_stat | grep 'Pages free:' | awk '{print $3}'", "r"); - if (pipe != nullptr) { - char buffer[128]; - if (fgets(buffer, sizeof(buffer), pipe) != nullptr) { - availableMemorySize = std::stoull(buffer) * getpagesize(); - } else { - LOG_F(ERROR, "GetAvailableMemorySize error: popen error"); - } - pclose(pipe); - } -#elif defined(__linux__) - std::ifstream meminfo("/proc/meminfo"); - std::string line; - while (std::getline(meminfo, line)) { - if (line.substr(0, 9) == "MemAvailable:") { - unsigned long long availableMemory; - std::sscanf(line.c_str(), "MemAvailable: %llu kB", - &availableMemory); - availableMemorySize = availableMemory * 1024; // 转换为字节 - break; - } - } - meminfo.close(); -#endif - - return availableMemorySize; -} - -MemoryInfo::MemorySlot getPhysicalMemoryInfo() { - MemoryInfo::MemorySlot slot; - -#ifdef _WIN32 - MEMORYSTATUSEX memoryStatus; - memoryStatus.dwLength = sizeof(memoryStatus); - GlobalMemoryStatusEx(&memoryStatus); - - slot.capacity = std::to_string(memoryStatus.ullTotalPhys / - (1024 * 1024)); // Convert bytes to MB -#elif defined(__APPLE__) - FILE *pipe = popen("sysctl hw.memsize | awk '{print $2}'", "r"); - if (pipe != nullptr) { - char buffer[128]; - if (fgets(buffer, sizeof(buffer), pipe) != nullptr) { - slot.capacity = std::string(buffer) / (1024 * 1024); - } else { - LOG_F(ERROR, "GetPhysicalMemoryInfo error: popen error"); - } - pclose(pipe); - } -#elif defined(__linux__) - std::ifstream meminfo("/proc/meminfo"); - std::string line; - while (std::getline(meminfo, line)) { - if (line.substr(0, 10) == "MemTotal: ") { - std::istringstream iss(line); - std::vector tokens{ - std::istream_iterator{iss}, - std::istream_iterator{}}; - slot.capacity = tokens[1]; - break; - } - } -#endif - - return slot; -} - -unsigned long long getVirtualMemoryMax() { - unsigned long long virtualMemoryMax; - -#ifdef _WIN32 - MEMORYSTATUSEX memoryStatus; - memoryStatus.dwLength = sizeof(memoryStatus); - GlobalMemoryStatusEx(&memoryStatus); - virtualMemoryMax = memoryStatus.ullTotalVirtual / (1024 * 1024); -#elif defined(__APPLE__) - FILE *pipe = popen("sysctl vm.swapusage | awk '{print $2}'", "r"); - if (pipe != nullptr) { - char buffer[128]; - if (fgets(buffer, sizeof(buffer), pipe) != nullptr) { - virtualMemoryMax = std::stoull(buffer) / (1024 * 1024); - } else { - LOG_F(ERROR, "GetVirtualMemoryMax error: popen error"); - } - pclose(pipe); - } -#elif defined(__linux__) - struct sysinfo si; - sysinfo(&si); - virtualMemoryMax = (si.totalram + si.totalswap) / 1024; -#endif - - return virtualMemoryMax; -} - -unsigned long long getVirtualMemoryUsed() { - unsigned long long virtualMemoryUsed; - -#ifdef _WIN32 - // Windows 实现 - MEMORYSTATUSEX memoryStatus; - memoryStatus.dwLength = sizeof(memoryStatus); - GlobalMemoryStatusEx(&memoryStatus); - virtualMemoryUsed = - (memoryStatus.ullTotalVirtual - memoryStatus.ullAvailVirtual) / - (1024 * 1024); -#elif defined(__APPLE__) - FILE *pipe = popen("sysctl vm.swapusage | awk '{print $6}'", "r"); - if (pipe != nullptr) { - char buffer[128]; - if (fgets(buffer, sizeof(buffer), pipe) != nullptr) { - virtualMemoryUsed = std::stoull(buffer) / (1024 * 1024); - } else { - LOG_F(ERROR, "GetVirtualMemoryUsed error: popen error"); - } - pclose(pipe); - } -#elif defined(__linux__) - struct sysinfo si; - sysinfo(&si); - virtualMemoryUsed = - (si.totalram - si.freeram + si.totalswap - si.freeswap) / 1024; -#endif - - return virtualMemoryUsed; -} - -unsigned long long getSwapMemoryTotal() { - unsigned long long swapMemoryTotal = 0; - -#ifdef _WIN32 - MEMORYSTATUSEX memoryStatus; - memoryStatus.dwLength = sizeof(memoryStatus); - GlobalMemoryStatusEx(&memoryStatus); - swapMemoryTotal = memoryStatus.ullTotalPageFile / (1024 * 1024); -#elif defined(__APPLE__) - FILE *pipe = popen("sysctl vm.swapusage | awk '{print $2}'", "r"); - if (pipe != nullptr) { - char buffer[128]; - if (fgets(buffer, sizeof(buffer), pipe) != nullptr) { - swapMemoryTotal = std::stoull(buffer) / (1024 * 1024); - } else { - LOG_F(ERROR, "GetSwapMemoryTotal error: popen error"); - } - pclose(pipe); - } -#elif defined(__linux__) - struct sysinfo si; - sysinfo(&si); - swapMemoryTotal = si.totalswap / 1024; -#endif - - return swapMemoryTotal; -} - -unsigned long long getSwapMemoryUsed() { - unsigned long long swapMemoryUsed = 0; - -#ifdef _WIN32 - MEMORYSTATUSEX memoryStatus; - memoryStatus.dwLength = sizeof(memoryStatus); - GlobalMemoryStatusEx(&memoryStatus); - swapMemoryUsed = - (memoryStatus.ullTotalPageFile - memoryStatus.ullAvailPageFile) / - (1024 * 1024); -#elif defined(__APPLE__) - FILE *pipe = popen("sysctl vm.swapusage | awk '{print $6}'", "r"); - if (pipe != nullptr) { - char buffer[128]; - if (fgets(buffer, sizeof(buffer), pipe) != nullptr) { - swapMemoryUsed = std::stoull(buffer) / (1024 * 1024); - } else { - LOG_F(ERROR, "GetSwapMemoryUsed error: popen error"); - } - pclose(pipe); - } -#elif defined(__linux__) - struct sysinfo si; - sysinfo(&si); - swapMemoryUsed = (si.totalswap - si.freeswap) / 1024; -#endif - - return swapMemoryUsed; -} - -} // namespace atom::system diff --git a/modules/atom.sysinfo/src/os.cpp b/modules/atom.sysinfo/src/os.cpp deleted file mode 100644 index e521f63d..00000000 --- a/modules/atom.sysinfo/src/os.cpp +++ /dev/null @@ -1,122 +0,0 @@ -/* - * os.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-2-21 - -Description: System Information Module - OS Information - -**************************************************/ - -#include "atom/sysinfo/os.hpp" - -#include - -#ifdef _WIN32 -#include -#elif __linux__ -#include -#elif __APPLE__ -#include -#endif - -#include "atom/log/loguru.hpp" - -namespace atom::system { -std::string OperatingSystemInfo::toJson() const { - std::stringstream ss; - ss << "{\n"; - ss << " \"osName\": \"" << osName << "\",\n"; - ss << " \"osVersion\": \"" << osVersion << "\",\n"; - ss << " \"kernelVersion\": \"" << kernelVersion << "\"\n"; - ss << " \"architecture\": \"" << architecture << "\"\n"; - ss << "}\n"; - return ss.str(); -} - -OperatingSystemInfo getOperatingSystemInfo() { - OperatingSystemInfo osInfo; - -#ifdef _WIN32 - OSVERSIONINFOEX osvi; - ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - if (GetVersionEx((LPOSVERSIONINFO)&osvi) != 0) { - osInfo.osName = "Windows"; - osInfo.osVersion = std::to_string(osvi.dwMajorVersion) + "." + - std::to_string(osvi.dwMinorVersion) + " (Build " + - std::to_string(osvi.dwBuildNumber) + ")"; - } else { - LOG_F(ERROR, "Failed to get OS version"); - } -#elif __linux__ - std::ifstream osReleaseFile("/etc/os-release"); - if (osReleaseFile.is_open()) { - std::string line; - while (std::getline(osReleaseFile, line)) { - if (line.find("PRETTY_NAME") != std::string::npos) { - osInfo.osName = line.substr(line.find("=") + 1); - break; - } - } - osReleaseFile.close(); - } - if (osInfo.osName.empty()) { - LOG_F(ERROR, "Failed to get OS name"); - } - - std::ifstream kernelVersionFile("/proc/version"); - if (kernelVersionFile.is_open()) { - std::string line; - std::getline(kernelVersionFile, line); - osInfo.kernelVersion = line.substr(0, line.find(" ")); - kernelVersionFile.close(); - } else { - LOG_F(ERROR, "Failed to open /proc/version"); - } -#elif __APPLE__ - struct utsname info; - if (uname(&info) == 0) { - osInfo.osName = info.sysname; - osInfo.osVersion = info.release; - osInfo.kernelVersion = info.version; - } -#endif - -// 获取系统架构 -#if defined(__i386__) || defined(__i386) - const std::string architecture = "x86"; -#elif defined(__x86_64__) - const std::string architecture = "x86_64"; -#elif defined(__arm__) - const std::string architecture = "ARM"; -#elif defined(__aarch64__) - const std::string architecture = "ARM64"; -#else - const std::string architecture = "Unknown architecture"; -#endif - osInfo.architecture = architecture; - - const std::string compiler = -#if defined(__clang__) - "Clang " + std::to_string(__clang_major__) + "." + - std::to_string(__clang_minor__) + "." + - std::to_string(__clang_patchlevel__); -#elif defined(__GNUC__) - "GCC " + std::to_string(__GNUC__) + "." + - std::to_string(__GNUC_MINOR__) + "." + - std::to_string(__GNUC_PATCHLEVEL__); -#elif defined(_MSC_VER) - "MSVC " + std::to_string(_MSC_FULL_VER); -#else - "Unknown compiler"; -#endif - osInfo.compiler = compiler; - - return osInfo; -} -} // namespace atom::system diff --git a/modules/atom.sysinfo/src/wifi.cpp b/modules/atom.sysinfo/src/wifi.cpp deleted file mode 100644 index 75b3ba90..00000000 --- a/modules/atom.sysinfo/src/wifi.cpp +++ /dev/null @@ -1,321 +0,0 @@ -/* - * wifi.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-2-21 - -Description: System Information Module - Wifi Information - -**************************************************/ - -#include "atom/sysinfo/wifi.hpp" - -#include -#include - -#ifdef _WIN32 -#include -#include -#include -#include -#include -#include -#if !defined(__MINGW32__) && !defined(__MINGW64__) -#pragma comment(lib, "wlanapi.lib") -#endif -#elif defined(__linux__) -#include -#include -#elif defined(__APPLE__) -#include -#include -#endif - -#include "atom/log/loguru.hpp" - -namespace atom::system -{ -// 获取当前连接的WIFI -std::string getCurrentWifi() { - std::string wifiName; - -#ifdef _WIN32 - DWORD negotiatedVersion; - HANDLE handle; - if (WlanOpenHandle(2, nullptr, &negotiatedVersion, &handle) == - ERROR_SUCCESS) { - WLAN_INTERFACE_INFO_LIST* interfaceInfoList; - if (WlanEnumInterfaces(handle, nullptr, &interfaceInfoList) == - ERROR_SUCCESS) { - for (DWORD i = 0; i < interfaceInfoList->dwNumberOfItems; ++i) { - WLAN_INTERFACE_INFO* interfaceInfo = - &interfaceInfoList->InterfaceInfo[i]; - if (interfaceInfo->isState == wlan_interface_state_connected) { - WLAN_CONNECTION_ATTRIBUTES* connectionAttributes; - DWORD dataSize = 0; - if (WlanQueryInterface( - handle, &interfaceInfo->InterfaceGuid, - wlan_intf_opcode_current_connection, nullptr, - &dataSize, - reinterpret_cast(&connectionAttributes), - nullptr) == ERROR_SUCCESS) { - wifiName = reinterpret_cast( - connectionAttributes->wlanAssociationAttributes - .dot11Ssid.ucSSID); - break; - } - } - } - } - else { - LOG_F(ERROR, "Error: WlanEnumInterfaces failed"); - } - WlanCloseHandle(handle, nullptr); - } - else { - LOG_F(ERROR, "Error: WlanOpenHandle failed"); - } -#elif defined(__linux__) - std::ifstream file("/proc/net/wireless"); - std::string line; - while (std::getline(file, line)) { - if (line.find(":") != std::string::npos) { - std::istringstream iss(line); - std::vector tokens( - std::istream_iterator{iss}, - std::istream_iterator()); - if (tokens.size() >= 2 && tokens[1] != "off/any" && - tokens[1] != "any") { - wifiName = tokens[0].substr(0, tokens[0].find(":")); - break; - } - } - } -#elif defined(__APPLE__) - CFArrayRef interfaces = CNCopySupportedInterfaces(); - if (interfaces != nullptr) { - CFDictionaryRef info = - CNCopyCurrentNetworkInfo(CFArrayGetValueAtIndex(interfaces, 0)); - if (info != nullptr) { - CFStringRef ssid = static_cast( - CFDictionaryGetValue(info, kCNNetworkInfoKeySSID)); - if (ssid != nullptr) { - char buffer[256]; - CFStringGetCString(ssid, buffer, sizeof(buffer), - kCFStringEncodingUTF8); - wifiName = buffer; - } - CFRelease(info); - } - CFRelease(interfaces); - } - else { - LOG_F(ERROR, "Error: CNCopySupportedInterfaces failed"); - } -#else - LOG_F(ERROR, "Unsupported operating system"); -#endif - - return wifiName; -} - -// 获取当前连接的有线网络 -std::string getCurrentWiredNetwork() { - std::string wiredNetworkName; - -#ifdef _WIN32 - PIP_ADAPTER_INFO adapterInfo = nullptr; - ULONG bufferLength = 0; - - if (GetAdaptersInfo(adapterInfo, &bufferLength) == ERROR_BUFFER_OVERFLOW) { - adapterInfo = - reinterpret_cast(new char[bufferLength]); - if (GetAdaptersInfo(adapterInfo, &bufferLength) == NO_ERROR) { - for (PIP_ADAPTER_INFO adapter = adapterInfo; adapter != nullptr; - adapter = adapter->Next) { - if (adapter->Type == MIB_IF_TYPE_ETHERNET ) { - wiredNetworkName = adapter->AdapterName; - break; - } - } - } - delete[] reinterpret_cast(adapterInfo); - } - else { - LOG_F(ERROR, "Error: GetAdaptersInfo failed"); - } -#elif defined(__linux__) - std::ifstream file("/sys/class/net"); - std::string line; - while (std::getline(file, line)) { - if (line != "." && line != "..") { - std::string path = "/sys/class/net/" + line + "/operstate"; - std::ifstream operStateFile(path); - if (operStateFile.is_open()) { - std::string operState; - std::getline(operStateFile, operState); - if (operState == "up") { - wiredNetworkName = line; - break; - } - } - } - } -#elif defined(__APPLE__) - // macOS下暂不支持获取当前连接的有线网络 -#else - LOG_F(ERROR, "Unsupported operating system"); -#endif - - return wiredNetworkName; -} - -// 检查是否连接到热点 -bool isHotspotConnected() { - bool isConnected = false; - -#ifdef _WIN32 - DWORD negotiatedVersion; - HANDLE handle; - if (WlanOpenHandle(2, nullptr, &negotiatedVersion, &handle) == - ERROR_SUCCESS) { - WLAN_INTERFACE_INFO_LIST* interfaceInfoList; - if (WlanEnumInterfaces(handle, nullptr, &interfaceInfoList) == - ERROR_SUCCESS) { - for (DWORD i = 0; i < interfaceInfoList->dwNumberOfItems; ++i) { - WLAN_INTERFACE_INFO* interfaceInfo = - &interfaceInfoList->InterfaceInfo[i]; - if (interfaceInfo->isState == wlan_interface_state_connected) { - WLAN_CONNECTION_ATTRIBUTES* connectionAttributes; - DWORD dataSize = 0; - if (WlanQueryInterface( - handle, &interfaceInfo->InterfaceGuid, - wlan_intf_opcode_current_connection, nullptr, - &dataSize, - reinterpret_cast(&connectionAttributes), - nullptr) == ERROR_SUCCESS) { - if (connectionAttributes->isState == - wlan_interface_state_connected && - connectionAttributes->wlanAssociationAttributes - .dot11BssType == - dot11_BSS_type_independent) { - isConnected = true; - break; - } - } - } - } - } - WlanCloseHandle(handle, nullptr); - } - else { - LOG_F(ERROR, "Error: WlanOpenHandle failed"); - } -#elif defined(__linux__) - std::ifstream file("/proc/net/dev"); - std::string line; - while (std::getline(file, line)) { - if (line.find(":") != std::string::npos) { - std::istringstream iss(line); - std::vector tokens( - std::istream_iterator{iss}, - std::istream_iterator()); - if (tokens.size() >= 17 && tokens[1].substr(0, 5) == "wlx00") { - isConnected = true; - break; - } - } - } -#elif defined(__APPLE__) - // macOS下暂不支持检查是否连接到热点 -#else - LOG_F(ERROR, "Unsupported operating system"); -#endif - - return isConnected; -} - -std::vector getHostIPs() { - std::vector hostIPs; - -#ifdef _WIN32 - WSADATA wsaData; - if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { - LOG_F(ERROR, "Error: WSAStartup failed"); - return hostIPs; - } - - char hostname[256]; - if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR) { - LOG_F(ERROR, "Error: gethostname failed"); - WSACleanup(); - return hostIPs; - } - - addrinfo hints, *res; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - if (getaddrinfo(hostname, NULL, &hints, &res) != 0) { - LOG_F(ERROR, "Error: getaddrinfo failed"); - WSACleanup(); - return hostIPs; - } - - for (addrinfo* p = res; p != NULL; p = p->ai_next) { - void* addr; - char ipstr[INET6_ADDRSTRLEN]; - if (p->ai_family == AF_INET) { - sockaddr_in* ipv4 = reinterpret_cast(p->ai_addr); - addr = &(ipv4->sin_addr); - } else { - sockaddr_in6* ipv6 = reinterpret_cast(p->ai_addr); - addr = &(ipv6->sin6_addr); - } - inet_ntop(p->ai_family, addr, ipstr, sizeof(ipstr)); - hostIPs.push_back(std::string(ipstr)); - } - - freeaddrinfo(res); - WSACleanup(); -#else - ifaddrs* ifaddr; - if (getifaddrs(&ifaddr) == -1) { - LOG_F(ERROR, "Error: getifaddrs failed"); - return hostIPs; - } - - for (ifaddrs* ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { - if (ifa->ifa_addr == NULL) - continue; - - if (ifa->ifa_addr->sa_family == AF_INET || - ifa->ifa_addr->sa_family == AF_INET6) { - char ipstr[INET6_ADDRSTRLEN]; - void* addr; - if (ifa->ifa_addr->sa_family == AF_INET) { - sockaddr_in* ipv4 = - reinterpret_cast(ifa->ifa_addr); - addr = &(ipv4->sin_addr); - } else { - sockaddr_in6* ipv6 = - reinterpret_cast(ifa->ifa_addr); - addr = &(ipv6->sin6_addr); - } - inet_ntop(ifa->ifa_addr->sa_family, addr, ipstr, sizeof(ipstr)); - hostIPs.push_back(std::string(ipstr)); - } - } - - freeifaddrs(ifaddr); -#endif - - return hostIPs; -} -} diff --git a/modules/atom.sysinfo/xmake.lua b/modules/atom.sysinfo/xmake.lua new file mode 100644 index 00000000..ce77f8fc --- /dev/null +++ b/modules/atom.sysinfo/xmake.lua @@ -0,0 +1,23 @@ +set_project("atom.sysinfo") +set_version("1.0.0") +set_xmakever("2.5.1") + +-- Set the C++ standard +set_languages("cxx20") + +-- Source files +local source_files = { + "_component.cpp", + "_main.cpp" +} + +-- Shared Library +target("atom.sysinfo") + set_kind("shared") + add_files(table.unpack(source_files)) + add_includedirs("include") + set_targetdir("$(buildir)/lib") + on_install(function (target) + os.cp(target:targetfile(), path.join(target:installdir(), "lib")) + end) +target_end() diff --git a/modules/atom.system/_component.cpp b/modules/atom.system/_component.cpp index 688c7f9b..ca864577 100644 --- a/modules/atom.system/_component.cpp +++ b/modules/atom.system/_component.cpp @@ -18,98 +18,72 @@ Description: Component of Atom-System #include "command.hpp" #include "crash.hpp" -#include "os.hpp" +#include "macro.hpp" #include "pidwatcher.hpp" #include "platform.hpp" -#include "register.hpp" #include "user.hpp" -#include "_constant.hpp" - using namespace atom::system; SystemComponent::SystemComponent(const std::string &name) : Component(name) { DLOG_F(INFO, "SystemComponent::SystemComponent"); def("run_commands", &executeCommands, "os", - "Run a list of system commands"); + "Run a list of system commands"); def("run_command_env", &executeCommandWithEnv, "os", - "Run a system command with environment variables"); + "Run a system command with environment variables"); def("run_command_status", &executeCommandWithStatus, "os", - "Run a system command and get its status"); - def("kill_process", &killProcess, "os", - "Kill a process by its PID"); - - def("walk", &walk, "os", "Walk a directory"); - def("fwalk", &fwalk, "os", "Walk a directory"); - def("uname", &uname, "os", "Get uname information"); - def("ctermid", &ctermid, "os", "Get current terminal ID"); - def("jwalk", &jwalk, "os", "Walk a directory"); - def("getpriority", &getpriority, "os", - "Get current process priority"); + "Run a system command and get its status"); def("getlogin", &getlogin, "os", "Get current user name"); - def("Environ", &Environ, "os", "Get environment variables"); - def("user_group", &getUserGroups, "user", - "Get current user groups"); + def("user_group", &getUserGroups, "user", "Get current user groups"); def("user_id", &getUserId, "user", "Get current user ID"); - def("user_host", &getHostname, "user", - "Get current user hostname"); + def("user_host", &getHostname, "user", "Get current user hostname"); def("user_name", &getUsername, "user", "Get current user name"); def("user_home", &getHomeDirectory, "user", - "Get current user home directory"); + "Get current user home directory"); - def("user_shell", &getLoginShell, "user", - "Get current user login shell"); + def("user_shell", &getLoginShell, "user", "Get current user login shell"); - def("user_groups", &getUserGroups, "user", - "Get current user groups"); + def("user_groups", &getUserGroups, "user", "Get current user groups"); addVariable("platform", platform, "Platform", "os_name", "os"); addVariable("architecture", architecture, "Architecture", "os_arch", "os"); addVariable("os_version", os_version, "OS Version", "kernel_version", "os"); addVariable("compiler", compiler, "Compiler", "builder", "os"); - def("make_pidwatcher", &SystemComponent::makePidWatcher, PointerSentinel(this), - "os", "Make a PID watcher"); - def("start_watcher", &SystemComponent::startPidWatcher, PointerSentinel(this), - "os", "Start a PID watcher"); + def("make_pidwatcher", &SystemComponent::makePidWatcher, + PointerSentinel(this), "os", "Make a PID watcher"); + def("start_watcher", &SystemComponent::startPidWatcher, + PointerSentinel(this), "os", "Start a PID watcher"); def("stop_watcher", &SystemComponent::stopPidWatcher, PointerSentinel(this), - "os", "Stop a PID watcher"); - def("switch_watcher", &SystemComponent::switchPidWatcher, PointerSentinel(this), - "os", "Switch a PID watcher"); + "os", "Stop a PID watcher"); + def("switch_watcher", &SystemComponent::switchPidWatcher, + PointerSentinel(this), "os", "Switch a PID watcher"); def("set_watcher_exit", &SystemComponent::setPidWatcherExitCallback, - PointerSentinel(this), "os", - "Set a PID watcher exit callback"); + PointerSentinel(this), "os", "Set a PID watcher exit callback"); def("set_watcher_monitor", &SystemComponent::setPidWatcherMonitorFunction, - PointerSentinel(this), "os", - "Set a PID watcher monitor callback"); + PointerSentinel(this), "os", "Set a PID watcher monitor callback"); #if ENABLE_REGISTRY_SUPPORT def("get_registry_subkeys", &getRegistrySubKeys, "os", - "Get registry subkeys"); - def("get_registry_values", &getRegistryValues, "os", - "Get registry values"); + "Get registry subkeys"); + def("get_registry_values", &getRegistryValues, "os", "Get registry values"); def("delete_registry_subkey", &deleteRegistrySubKey, "os", - "Delete registry subkey"); + "Delete registry subkey"); def("modify_registry_value", &modifyRegistryValue, "os", - "Modify registry value"); + "Modify registry value"); def("recursively_enumerate_registry_subkeys", - &recursivelyEnumerateRegistrySubKeys, "os", - "Recursively enumerate registry subkeys"); - def("find_registry_key", &findRegistryKey, "os", - "Find registry key"); - def("find_registry_value", &findRegistryValue, "os", - "Find registry value"); - def("backup_registry", &backupRegistry, "os", - "Backup registry"); - def("export_registry", &exportRegistry, "os", - "Export registry"); + &recursivelyEnumerateRegistrySubKeys, "os", + "Recursively enumerate registry subkeys"); + def("find_registry_key", &findRegistryKey, "os", "Find registry key"); + def("find_registry_value", &findRegistryValue, "os", "Find registry value"); + def("backup_registry", &backupRegistry, "os", "Backup registry"); + def("export_registry", &exportRegistry, "os", "Export registry"); def("delete_registry_value", &deleteRegistryValue, "os", - "Delete registry value"); + "Delete registry value"); #endif - def("save_crashreport", &saveCrashLog, "os", - "Save crash report"); + def("save_crashreport", &saveCrashLog, "os", "Save crash report"); } SystemComponent::~SystemComponent() { @@ -120,8 +94,6 @@ bool SystemComponent::initialize() { return true; } bool SystemComponent::destroy() { return true; } - - void SystemComponent::makePidWatcher(const std::string &name) { if (m_pidWatchers.find(name) != m_pidWatchers.end()) { return; @@ -129,13 +101,13 @@ void SystemComponent::makePidWatcher(const std::string &name) { m_pidWatchers[name] = std::make_shared(); } -bool SystemComponent::startPidWatcher(const std::string &name, - const std::string &pid) { - return m_pidWatchers[name]->Start(pid); +auto SystemComponent::startPidWatcher(const std::string &name, + const std::string &pid) -> bool { + return m_pidWatchers[name]->start(pid); } void SystemComponent::stopPidWatcher(const std::string &name) { - m_pidWatchers[name]->Stop(); + m_pidWatchers[name]->stop(); } bool SystemComponent::switchPidWatcher(const std::string &name, @@ -144,16 +116,16 @@ bool SystemComponent::switchPidWatcher(const std::string &name, } void SystemComponent::setPidWatcherExitCallback( const std::string &name, const std::function &callback) { - m_pidWatchers[name]->SetExitCallback(callback); + m_pidWatchers[name]->setExitCallback(callback); } void SystemComponent::setPidWatcherMonitorFunction( const std::string &name, const std::function &callback, std::chrono::milliseconds interval) { - m_pidWatchers[name]->SetMonitorFunction(callback, interval); + m_pidWatchers[name]->setMonitorFunction(callback, interval); } void SystemComponent::getPidByName(const std::string &name, const std::string &pid) { - m_pidWatchers[name]->GetPidByName(pid); + ATOM_UNUSED_RESULT(m_pidWatchers[name]->getPidByName(pid)); } diff --git a/modules/atom.system/_main.cpp b/modules/atom.system/_main.cpp index 22a43915..c57803f5 100644 --- a/modules/atom.system/_main.cpp +++ b/modules/atom.system/_main.cpp @@ -13,17 +13,18 @@ Description: Main Entry **************************************************/ #include "_component.hpp" +#include "macro.hpp" #include "atom/type/json.hpp" using json = nlohmann::json; -extern "C" { - -std::shared_ptr getInstance([[maybe_unused]] const json ¶ms) { - if (params.contains("name") && params["name"].is_string()) { - return std::make_shared( - params["name"].get()); +ATOM_EXTERN_C { + auto getInstance( + [[maybe_unused]] const json ¶ms) -> std::shared_ptr { + if (params.contains("name") && params["name"].is_string()) { + return std::make_shared( + params["name"].get()); + } + return std::make_shared("atom.sysinfo"); } - return std::make_shared("atom.system"); -} } diff --git a/modules/atom.system/xmake.lua b/modules/atom.system/xmake.lua new file mode 100644 index 00000000..8d665764 --- /dev/null +++ b/modules/atom.system/xmake.lua @@ -0,0 +1,47 @@ +set_project("atom-system") +set_version("1.0.0") + +-- Define libraries +local atom_system_libs = { + "loguru", + "atom-component", + "atom-system", + "pthread" +} + +if is_plat("windows") then + table.join2(atom_system_libs, {"pdh", "wlanapi", "ws2_32", "userenv", "setupapi", "iphlpapi"}) +end + +-- Sources and Headers +local atom_system_sources = { + "_main.cpp", + "_component.cpp" +} + +local atom_system_private_headers = { + "_component.hpp" +} + +-- Object Library +target("atom-system_object") + set_kind("object") + add_files(table.unpack(atom_system_sources)) + add_headerfiles(table.unpack(atom_system_private_headers)) + add_packages(table.unpack(atom_system_libs)) +target_end() + +-- Shared Library +target("atom-system") + set_kind("shared") + add_deps("atom-system_object") + add_files(table.unpack(atom_system_sources)) + add_headerfiles(table.unpack(atom_system_private_headers)) + add_packages(table.unpack(atom_system_libs)) + set_targetdir("$(buildir)/lib") + set_installdir("$(installdir)/lib") + set_version("1.0.0", {build = "%Y%m%d%H%M"}) + on_install(function (target) + os.cp(target:targetfile(), path.join(target:installdir(), "lib")) + end) +target_end() diff --git a/modules/atom.utils/xmake.lua b/modules/atom.utils/xmake.lua new file mode 100644 index 00000000..96c624d4 --- /dev/null +++ b/modules/atom.utils/xmake.lua @@ -0,0 +1,45 @@ +set_project("atom-utils") +set_version("1.0.0") +set_xmakever("2.5.1") + +-- Define libraries +local atom_utils_libs = { + "loguru", + "atom-component", + "atom-utils", + "openssl", + "pthread" +} + +-- Sources and Headers +local atom_utils_sources = { + "_main.cpp", + "_component.cpp" +} + +local atom_utils_private_headers = { + "_component.hpp" +} + +-- Object Library +target("atom-utils_object") + set_kind("object") + add_files(table.unpack(atom_utils_sources)) + add_headerfiles(table.unpack(atom_utils_private_headers)) + add_packages(table.unpack(atom_utils_libs)) +target_end() + +-- Shared Library +target("atom-utils") + set_kind("shared") + add_deps("atom-utils_object") + add_files(table.unpack(atom_utils_sources)) + add_headerfiles(table.unpack(atom_utils_private_headers)) + add_packages(table.unpack(atom_utils_libs)) + set_targetdir("$(buildir)/lib") + set_installdir("$(installdir)/lib") + set_version("1.0.0", {build = "%Y%m%d%H%M"}) + on_install(function (target) + os.cp(target:targetfile(), path.join(target:installdir(), "lib")) + end) +target_end() diff --git a/modules/lithium.config/_component.cpp b/modules/lithium.config/_component.cpp index afa81188..74120859 100644 --- a/modules/lithium.config/_component.cpp +++ b/modules/lithium.config/_component.cpp @@ -17,6 +17,7 @@ Description: Config Component for Atom Addon #include "config/configor.hpp" #include "atom/log/loguru.hpp" +#include "atom/type/json.hpp" ConfigComponent::ConfigComponent(const std::string& name) : Component(name), m_configManager(lithium::ConfigManager::createShared()) { diff --git a/modules/lithium.config/_test.cpp b/modules/lithium.config/_test.cpp index 91afe970..bebf504c 100644 --- a/modules/lithium.config/_test.cpp +++ b/modules/lithium.config/_test.cpp @@ -17,6 +17,8 @@ Description: Test Script #include "atom/type/json.hpp" using json = nlohmann::json; +#include + int main(int argc, char* argv[]) { auto config = std::make_shared("lithium.config"); json test_value = {{"key", "value"}}; diff --git a/modules/lithium.config/xmake.lua b/modules/lithium.config/xmake.lua new file mode 100644 index 00000000..a6e8421d --- /dev/null +++ b/modules/lithium.config/xmake.lua @@ -0,0 +1,56 @@ +set_project("lithium.config") +set_version("1.0.0") +set_xmakever("2.5.1") + +-- Define libraries +local lithium_config_libs = { + "atom-component", + "lithium-config", + "loguru", + "pthread" +} + +-- Sources and Headers +local lithium_config_sources = { + "_main.cpp", + "_component.cpp" +} + +local lithium_config_headers = { + "_component.hpp" +} + +-- Object Library +target("lithium.config_object") + set_kind("object") + add_files(table.unpack(lithium_config_sources)) + add_headerfiles(table.unpack(lithium_config_headers)) + add_packages(table.unpack(lithium_config_libs)) +target_end() + +-- Shared Library +target("lithium.config") + set_kind("shared") + add_deps("lithium.config_object") + add_files(table.unpack(lithium_config_sources)) + add_headerfiles(table.unpack(lithium_config_headers)) + add_packages(table.unpack(lithium_config_libs)) + set_targetdir("$(buildir)/lib") + set_installdir("$(installdir)/lib") + set_version("1.0.0", {build = "%Y%m%d%H%M"}) + on_install(function (target) + os.cp(target:targetfile(), path.join(target:installdir(), "lib")) + end) +target_end() + +-- Test Executable +target("lithium.config_test") + set_kind("binary") + add_files("_test.cpp") + add_deps("lithium.config") + add_packages(table.unpack(lithium_config_libs)) + set_targetdir("$(buildir)/bin") + if is_mode("debug") then + add_defines("_DEBUG") + end +target_end() diff --git a/modules/lithium.cxxtools/CMakeLists.txt b/modules/lithium.cxxtools/CMakeLists.txt index b2df9a2f..78a57337 100644 --- a/modules/lithium.cxxtools/CMakeLists.txt +++ b/modules/lithium.cxxtools/CMakeLists.txt @@ -25,17 +25,12 @@ set(SOURCE_FILES src/json2ini.cpp src/json2xml.cpp src/xml2json.cpp + src/pci_generator.cpp _component.cpp _main.cpp ) -find_package(tomlplusplus) - -if (tomlplusplus_FOUND) - list(APPEND SOURCE_FILES src/toml2json.cpp src/json2toml.cpp) -endif() - set(${PROJECT_NAME}_LIBS atom-component atom-error @@ -50,3 +45,5 @@ target_link_libraries(lithium.cxxtools ${${PROJECT_NAME}_LIBS}) # Include directories target_include_directories(lithium.cxxtools PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) + +add_subdirectory(tests) diff --git a/modules/lithium.cxxtools/_component.cpp b/modules/lithium.cxxtools/_component.cpp index d97678a6..1bdefd37 100644 --- a/modules/lithium.cxxtools/_component.cpp +++ b/modules/lithium.cxxtools/_component.cpp @@ -20,35 +20,31 @@ Description: Some useful tools written in c++ #include "ini2json.hpp" #include "json2ini.hpp" #include "json2xml.hpp" +#include "pci_generator.hpp" #include "xml2json.hpp" -#if ENABLE_TOML -#include "json2toml.hpp" -#include "toml2json.hpp" -#endif +using namespace lithium::cxxtools; ToolsComponent::ToolsComponent(const std::string& name) : Component(name) { LOG_F(INFO, "ToolsComponent Constructed"); - def("csv_to_json", &csv_to_json, "utils", "Convert csv to json"); - def("ini_to_json", &ini_to_json, "utils", "Convert ini to json"); - def("json_to_ini", &json_to_ini, "utils", "Convert json to ini"); - def("json_to_xml", &json_to_xml, "utils", "Convert json to xml"); - def("xml_to_json", &xml_to_json, "utils", "Convert xml to json"); -#if ENABLE_TOML - def("json_to_toml", &json_to_toml, "utils", "Convert json to toml"); - def("toml_to_json", &toml_to_json, "utils", "Convert toml to json"); -#endif + def("csv_to_json", &csvToJson, "lithium.cxxtools", "Convert csv to json"); + def("ini_to_json", &iniToJson, "lithium.cxxtools", "Convert ini to json"); + def("json_to_ini", &jsonToIni, "lithium.cxxtools", "Convert json to ini"); + def("json_to_xml", &jsonToXml, "lithium.cxxtools", "Convert json to xml"); + def("xml_to_json", &xmlToJson, "lithium.cxxtools", "Convert xml to json"); + def("pci_generator", &parseAndGeneratePCIInfo, "lithium.cxxtools", + "Generate pci id"); } ToolsComponent::~ToolsComponent() { LOG_F(INFO, "ToolsComponent Destructed"); } -bool ToolsComponent::initialize() { +auto ToolsComponent::initialize() -> bool { LOG_F(INFO, "ToolsComponent Initialized"); return true; } -bool ToolsComponent::destroy() { +auto ToolsComponent::destroy() -> bool { LOG_F(INFO, "ToolsComponent Destroyed"); return true; } diff --git a/modules/lithium.cxxtools/_component.hpp b/modules/lithium.cxxtools/_component.hpp index 3e5720f6..3b8ff34f 100644 --- a/modules/lithium.cxxtools/_component.hpp +++ b/modules/lithium.cxxtools/_component.hpp @@ -21,9 +21,9 @@ Description: Some useful tools written in c++ class ToolsComponent : public Component { public: explicit ToolsComponent(const std::string& name); - virtual ~ToolsComponent(); + ~ToolsComponent() override; - bool initialize() override; - bool destroy() override; + auto initialize() -> bool override; + auto destroy() -> bool override; }; #endif diff --git a/modules/lithium.cxxtools/_main.cpp b/modules/lithium.cxxtools/_main.cpp index 3a87e550..5e1bbd28 100644 --- a/modules/lithium.cxxtools/_main.cpp +++ b/modules/lithium.cxxtools/_main.cpp @@ -15,10 +15,11 @@ Description: Main Entry #include "_component.hpp" #include "atom/type/json.hpp" +#include "macro.hpp" using json = nlohmann::json; -extern "C" { -std::shared_ptr getInstance([[maybe_unused]] const json ¶ms) { +ATOM_C { +auto getInstance(ATOM_UNUSED const json ¶ms) -> std::shared_ptr { if (params.contains("name") && params["name"].is_string()) { return std::make_shared( params["name"].get()); diff --git a/modules/lithium.cxxtools/include/csv2json.hpp b/modules/lithium.cxxtools/include/csv2json.hpp index 4ea667d6..267e4720 100644 --- a/modules/lithium.cxxtools/include/csv2json.hpp +++ b/modules/lithium.cxxtools/include/csv2json.hpp @@ -1,8 +1,34 @@ #ifndef LITHIUM_CXXTOOLS_CSV2JSON_HPP #define LITHIUM_CXXTOOLS_CSV2JSON_HPP -#include +#include -bool csv_to_json(const std::string& csv_file, const std::string& json_file); +#include "atom/type/json_fwd.hpp" +using json = nlohmann::json; + +namespace lithium::cxxtools { +namespace detail { +/** + * @brief Convert a CSV file to a JSON object + * @param csvFilePath The path to the CSV file + * @return The JSON object + */ +auto csvToJson(std::string_view csvFilePath) -> json; + +/** + * @brief Save a JSON object to a file + * @param jsonData The JSON object + * @param jsonFilePath The path to the JSON file + */ +void saveJsonToFile(const json &jsonData, std::string_view jsonFilePath); +} // namespace detail +/** + * @brief Convert a CSV file to a JSON file + * @param csv_file The path to the CSV file + * @param json_file The path to the JSON file + * @return true if the conversion was successful + */ +auto csvToJson(std::string_view csv_file, std::string_view json_file) -> bool; +} // namespace lithium::cxxtools #endif diff --git a/modules/lithium.cxxtools/include/ini2json.hpp b/modules/lithium.cxxtools/include/ini2json.hpp index 3c797a9a..90b0b23a 100644 --- a/modules/lithium.cxxtools/include/ini2json.hpp +++ b/modules/lithium.cxxtools/include/ini2json.hpp @@ -1,8 +1,17 @@ +/* + * ini2json.hpp + * + * Copyright (C) 2023-2024 Max Qian + */ + #ifndef LITHIUM_CXXTOOLS_INI2JSON_HPP #define LITHIUM_CXXTOOLS_INI2JSON_HPP -#include +#include -bool ini_to_json(const std::string& ini_file, const std::string& json_file); +namespace lithium::cxxtools { +auto iniToJson(std::string_view iniFilePath, + std::string_view jsonFilePath) -> bool; +} -#endif +#endif // LITHIUM_CXXTOOLS_INI2JSON_HPP diff --git a/modules/lithium.cxxtools/include/json2ini.hpp b/modules/lithium.cxxtools/include/json2ini.hpp index 8383fc75..be244fde 100644 --- a/modules/lithium.cxxtools/include/json2ini.hpp +++ b/modules/lithium.cxxtools/include/json2ini.hpp @@ -1,8 +1,17 @@ +/* + * json2ini.hpp + * + * Copyright (C) 2023-2024 Max Qian + */ + #ifndef LITHIUM_CXXTOOLS_JSON2INI_HPP #define LITHIUM_CXXTOOLS_JSON2INI_HPP -#include +#include -bool json_to_ini(const std::string& json_file, const std::string& ini_file); +namespace lithium::cxxtools { +auto jsonToIni(std::string_view jsonFilePath, + std::string_view iniFilePath) -> bool; +} -#endif +#endif // LITHIUM_CXXTOOLS_JSON2INI_HPP diff --git a/modules/lithium.cxxtools/include/json2toml.hpp b/modules/lithium.cxxtools/include/json2toml.hpp deleted file mode 100644 index 32193fe7..00000000 --- a/modules/lithium.cxxtools/include/json2toml.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef LITHIUM_CXXTOOLS_JSON2TOML_HPP -#define LITHIUM_CXXTOOLS_JSON2TOML_HPP - -#include - -bool json_to_toml(const std::string& json_file, const std::string& toml_file); - -#endif diff --git a/modules/lithium.cxxtools/include/json2xml.hpp b/modules/lithium.cxxtools/include/json2xml.hpp index 52aa3af1..395f8b72 100644 --- a/modules/lithium.cxxtools/include/json2xml.hpp +++ b/modules/lithium.cxxtools/include/json2xml.hpp @@ -1,8 +1,20 @@ -#ifndef LITHIUM_CXXTOOLS_JSON2XML_HPP -#define LITHIUM_CXXTOOLS_JSON2XML_HPP +/* + * json2xml.hpp + * + * Copyright (C) 2023-2024 Max Qian + */ -#include +#ifndef JSON2XML_HPP +#define JSON2XML_HPP -bool json_to_xml(const std::string& json_file, const std::string& xml_file); +#include -#endif +namespace lithium::cxxtools { +namespace detail { +auto convertJsonToXml(std::string_view jsonFilePath, + std::string_view xmlFilePath) -> bool; +} // namespace detail +auto jsonToXml(std::string_view json_file, std::string_view xml_file) -> bool; +} // namespace lithium::cxxtools + +#endif // JSON2XML_HPP diff --git a/modules/lithium.cxxtools/include/pci_generator.hpp b/modules/lithium.cxxtools/include/pci_generator.hpp new file mode 100644 index 00000000..d1f43dd5 --- /dev/null +++ b/modules/lithium.cxxtools/include/pci_generator.hpp @@ -0,0 +1,9 @@ +#ifndef LITHIUM_CXXTOOLS_PCI_GENERATOR_HPP +#define LITHIUM_CXXTOOLS_PCI_GENERATOR_HPP + +#include + +// Parses the given PCI info file and generates the corresponding C++ code. +void parseAndGeneratePCIInfo(std::string_view filename); + +#endif // LITHIUM_CXXTOOLS_PCI_GENERATOR_HPP diff --git a/modules/lithium.cxxtools/include/toml2json.hpp b/modules/lithium.cxxtools/include/toml2json.hpp deleted file mode 100644 index 00b731c7..00000000 --- a/modules/lithium.cxxtools/include/toml2json.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef LITHIUM_CXXTOOLS_TOML2JSON_HPP -#define LITHIUM_CXXTOOLS_TOML2JSON_HPP - -#include - -bool toml_to_json(const std::string& toml_file, const std::string& json_file); - -#endif diff --git a/modules/lithium.cxxtools/include/xml2json.hpp b/modules/lithium.cxxtools/include/xml2json.hpp index 7eb78fb0..f83de86a 100644 --- a/modules/lithium.cxxtools/include/xml2json.hpp +++ b/modules/lithium.cxxtools/include/xml2json.hpp @@ -1,8 +1,36 @@ +/* + * xml2json.hpp + * + * Copyright (C) 2023-2024 Max Qian + */ + #ifndef LITHIUM_CXXTOOLS_XML2JSON_HPP #define LITHIUM_CXXTOOLS_XML2JSON_HPP -#include +#include -bool xml_to_json(const std::string& xml_file, const std::string& json_file); +namespace lithium::cxxtools { +namespace detail { +/** + * @brief Convert XML file to JSON file + * + * @param xmlFilePath Path to the XML file + * @param jsonFilePath Path to the JSON file + * @return true if conversion was successful + * @return false if conversion failed + */ +auto convertXmlToJson(std::string_view xmlFilePath, + std::string_view jsonFilePath) -> bool; +} // namespace detail +/** + * @brief Convert XML file to JSON file + * + * @param xml_file Path to the XML file + * @param json_file Path to the JSON file + * @return true if conversion was successful + * @return false if conversion failed + */ +auto xmlToJson(std::string_view xml_file, std::string_view json_file) -> bool; +} // namespace lithium::cxxtools -#endif +#endif // LITHIUM_CXXTOOLS_XML2JSON_HPP diff --git a/modules/lithium.cxxtools/src/csv2json.cpp b/modules/lithium.cxxtools/src/csv2json.cpp index cabf85bb..3381ede9 100644 --- a/modules/lithium.cxxtools/src/csv2json.cpp +++ b/modules/lithium.cxxtools/src/csv2json.cpp @@ -4,18 +4,9 @@ * Copyright (C) 2023-2024 Max Qian */ -/************************************************* - -Date: 2023-12-7 - -Description: CSV to JSON conversion - -**************************************************/ - #include "csv2json.hpp" #include -#include #include #include "atom/error/exception.hpp" @@ -23,23 +14,21 @@ Description: CSV to JSON conversion #include "atom/type/json.hpp" #include "atom/utils/string.hpp" -using json = nlohmann::json; - -json csvToJson(const std::string &csvFilePath) { +namespace lithium::cxxtools::detail { +auto csvToJson(std::string_view csvFilePath) -> json { LOG_F(INFO, "Converting CSV file to JSON: {}", csvFilePath); - std::ifstream csvFile(csvFilePath); + std::ifstream csvFile(csvFilePath.data()); if (!csvFile.is_open()) { - THROW_RUNTIME_ERROR("Failed to open CSV file: " + csvFilePath); + THROW_RUNTIME_ERROR("Failed to open CSV file: ", csvFilePath); } - std::vector headers; + std::vector headers; std::vector data; std::string line; bool isFirstLine = true; while (getline(csvFile, line)) { - std::vector fields = - atom::utils::splitString(line, ','); + auto fields = atom::utils::splitString(line, ','); if (isFirstLine) { headers = fields; @@ -49,78 +38,76 @@ json csvToJson(const std::string &csvFilePath) { for (size_t i = 0; i < fields.size(); ++i) { row[headers[i]] = fields[i]; } - data.push_back(row); + data.push_back(std::move(row)); } } - json jsonData; - for (const auto &row : data) { - jsonData.push_back(row); - } - LOG_F(INFO, "{}", jsonData.dump(4)); - - return jsonData; + return json{data}; } -void saveJsonToFile(const json &jsonData, const std::string &jsonFilePath) { +void saveJsonToFile(const json &jsonData, std::string_view jsonFilePath) { LOG_F(INFO, "Saving JSON data to file: {}", jsonFilePath); - std::ofstream jsonFile(jsonFilePath); - if (!jsonFile.is_open()) { - THROW_RUNTIME_ERROR("Failed to open JSON file: " + jsonFilePath); + std::ofstream jsonFile(jsonFilePath.data()); + if (!jsonFile.is_open() || !jsonFile.good()) { + THROW_RUNTIME_ERROR("Failed to open JSON file: ", jsonFilePath); } jsonFile << jsonData.dump(4); - jsonFile.close(); - LOG_F(INFO, "JSON data saved to file: {}", jsonFilePath); } +} // namespace lithium::cxxtools::detail #if ATOM_STANDALONE_COMPONENT_ENABLED #include "argparse/argparse.hpp" int main(int argc, char *argv[]) { loguru::init(argc, argv); - loguru::add_file("conversion_log.txt", loguru::Append, loguru::Verbosity_INFO); + loguru::add_file("conversion_log.txt", loguru::Append, + loguru::Verbosity_INFO); - // 设置命令行参数解析器 - argparse::ArgumentParser program; + argparse::ArgumentParser program("csv2json"); program.add_argument("-i", "--input") .required() .help("path to input CSV file"); - program.add_argument("-o", "--output") .required() .help("path to output JSON file"); - program.parse_args(argc, argv); - - // 获取命令行参数 - std::string csvFilePath = program.get("input"); - std::string jsonFilePath = program.get("output"); try { - DLOG_F(INFO, "Converting CSV to JSON..."); + program.parse_args(argc, argv); + } catch (const std::runtime_error &err) { + LOG_F(ERROR, "Error parsing arguments: {}", err.what()); + return 1; + } - json jsonData = csvToJson(csvFilePath); - saveJsonToFile(jsonData, jsonFilePath); + std::string csvFilePath = program.get("--input"); + std::string jsonFilePath = program.get("--output"); - DLOG_F(INFO, "CSV to JSON conversion succeeded."); + try { + LOG_F(INFO, "Converting CSV to JSON..."); + auto jsonData = csvToJson(csvFilePath); + saveJsonToFile(jsonData, jsonFilePath); + LOG_F(INFO, "CSV to JSON conversion succeeded."); } catch (const std::exception &ex) { - DLOG_F(ERROR, "CSV to JSON conversion failed: {}", ex.what()); + LOG_F(ERROR, "CSV to JSON conversion failed: {}", ex.what()); return 1; } return 0; } #else -bool csv_to_json(const std::string &csv_file, const std::string &json_file) { +namespace lithium::cxxtools { +auto csvToJson(std::string_view csv_file, std::string_view json_file) -> bool { if (csv_file.empty() || json_file.empty()) { LOG_F(ERROR, "CSV to JSON conversion failed: invalid input file path"); return false; } try { - auto csv_data = csvToJson(csv_file); - saveJsonToFile(csv_data, json_file); + auto csvData = detail::csvToJson(csv_file); + detail::saveJsonToFile(csvData, json_file); return true; } catch (const std::exception &e) { LOG_F(ERROR, "CSV to JSON conversion failed: {}", e.what()); } return false; } +} // namespace lithium::cxxtools + #endif diff --git a/modules/lithium.cxxtools/src/ini2json.cpp b/modules/lithium.cxxtools/src/ini2json.cpp index fcef64f1..1f85c609 100644 --- a/modules/lithium.cxxtools/src/ini2json.cpp +++ b/modules/lithium.cxxtools/src/ini2json.cpp @@ -4,19 +4,9 @@ * Copyright (C) 2023-2024 Max Qian */ -/************************************************* - -Date: 2023-7-29 - -Description: INI to JSON Converter - -**************************************************/ - #include "ini2json.hpp" -#include -#include -#include +#include #include #include #include @@ -26,92 +16,96 @@ Description: INI to JSON Converter #include "atom/log/loguru.hpp" #include "atom/utils/string.hpp" -using namespace std; -using namespace std::chrono; namespace fs = std::filesystem; -string tab(unsigned level) { return string(level * 4, ' '); } +namespace lithium::cxxtools::detail { +auto tab(unsigned level) -> std::string { + return std::string(static_cast(level * 4), ' '); +} -bool iniToJson(const string& iniFilePath, const string& jsonFilePath) { +auto iniToJson(std::string_view iniFilePath, + std::string_view jsonFilePath) -> bool { LOG_F(INFO, "Converting INI file to JSON: {}", iniFilePath); if (!fs::exists(iniFilePath) || !fs::is_regular_file(iniFilePath)) { - THROW_RUNTIME_ERROR("File not found: " + iniFilePath); + THROW_FILE_NOT_FOUND("File not found: ", iniFilePath); } - std::ifstream in(iniFilePath); - std::ofstream out(jsonFilePath); + std::ifstream in(iniFilePath.data()); + std::ofstream out(jsonFilePath.data()); if (!out.is_open()) { LOG_F(ERROR, "Can't create file: {}", jsonFilePath); return false; } - out << "{" << endl; + out << "{" << std::endl; - string line; + std::string line; bool sectionOpened = false; bool hasAttributes = false; - while (getline(in, line)) { - size_t commentPos = line.find(';'); - if (commentPos != string::npos) + while (std::getline(in, line)) { + auto commentPos = line.find(';'); + if (commentPos != std::string::npos) { line = line.substr(0, commentPos); + } line = atom::utils::trim(line); - if (line.empty()) + if (line.empty()) { continue; + } if (line.front() == '[') { line = atom::utils::trim(line, "[]"); if (hasAttributes) { hasAttributes = false; - out << endl; + out << std::endl; } if (sectionOpened) { - out << tab(1) << "}," << endl; + out << tab(1) << "}," << std::endl; } else { sectionOpened = true; } - out << tab(1) << "\"" << line << "\": {" << endl; + out << tab(1) << "\"" << line << "\": {" << std::endl; } else { auto pos = line.find('='); - if (pos == string::npos) + if (pos == std::string::npos) { continue; + } - string attribute = atom::utils::trim(line.substr(0, pos)); - string value = atom::utils::trim(line.substr(pos + 1)); + auto attribute = atom::utils::trim(line.substr(0, pos)); + auto value = atom::utils::trim(line.substr(pos + 1)); if (hasAttributes) { - out << "," << endl; + out << "," << std::endl; } else { hasAttributes = true; } - out << tab(3) << "\"" << attribute << "\": "; - if (value.find(':') != string::npos) { - out << "{" << endl; - auto items = atom::utils::explode(value, ','); - for (const auto& item : items) { + out << tab(2) << "\"" << attribute << "\": "; + if (value.find(':') != std::string::npos) { + out << "{" << std::endl; + for (const auto& item : atom::utils::explode(value, ',')) { auto kv = atom::utils::explode(item, ':'); - if (kv.size() == 2) - out << tab(4) << "\"" << atom::utils::trim(kv[0]) + if (kv.size() == 2) { + out << tab(3) << "\"" << atom::utils::trim(kv[0]) << "\": \"" << atom::utils::trim(kv[1]) << "\"," - << endl; + << std::endl; + } } - out.seekp(-2, out.cur); // Remove the last comma - out << endl << tab(3) << "}"; - } else if (value.find(',') != string::npos) { - out << "[" << endl; - auto items = atom::utils::explode(value, ','); - for (const auto& item : items) { - out << tab(4) << "\"" << atom::utils::trim(item) << "\"," - << endl; + out.seekp(-2, std::ofstream::cur); // Remove the last comma + out << std::endl << tab(2) << "}"; + } else if (value.find(',') != std::string::npos) { + out << "[" << std::endl; + for (const auto& item : atom::utils::explode(value, ',')) { + out << tab(3) << "\"" << atom::utils::trim(item) << "\"," + << std::endl; } - out.seekp(-2, out.cur); // Remove the last comma - out << endl << tab(3) << "]"; + out.seekp(-2, std::ofstream::cur); // Remove the last comma + out << std::endl << tab(2) << "]"; } else { out << "\"" << value << "\""; } @@ -119,48 +113,63 @@ bool iniToJson(const string& iniFilePath, const string& jsonFilePath) { } if (hasAttributes) { - out << endl; + out << std::endl; } if (sectionOpened) { - out << tab(1) << "}" << endl; + out << tab(1) << "}" << std::endl; } - out << "}" << endl; + out << "}" << std::endl; return true; } +} // namespace lithium::cxxtools::detail #if ATOM_STANDALONE_COMPONENT_ENABLED #include int main(int argc, char** argv) { loguru::init(argc, argv); - loguru::add_file("conversion_log.txt", loguru::Append, loguru::Verbosity_INFO); + loguru::add_file("conversion_log.txt", loguru::Append, + loguru::Verbosity_INFO); - argparse::ArgumentParser program; + argparse::ArgumentParser program("ini2json"); program.add_argument("-i", "--input") .required() .help("path to input INI file"); - program.add_argument("-o", "--output") .required() .help("path to output JSON file"); - program.parse_args(argc, argv); - - std::string iniFilePath = program.get("input"); - std::string jsonFilePath = program.get("output"); - if (!iniToJson(iniFilePath, jsonFilePath)) { - LOG_F(ERROR, "Conversion failed."); + try { + program.parse_args(argc, argv); + } catch (const std::runtime_error& err) { + LOG_F(ERROR, "Error parsing arguments: {}", err.what()); return 1; } - LOG_F(INFO, "Conversion completed. Result has been saved to {}", - jsonFilePath); + std::string iniFilePath = program.get("--input"); + std::string jsonFilePath = program.get("--output"); + + try { + LOG_F(INFO, "Converting INI to JSON..."); + if (!iniToJson(iniFilePath, jsonFilePath)) { + LOG_F(ERROR, "Conversion failed."); + return 1; + } + LOG_F(INFO, "Conversion completed. Result has been saved to {}", + jsonFilePath); + } catch (const std::exception& ex) { + LOG_F(ERROR, "Conversion failed: {}", ex.what()); + return 1; + } return 0; } #else -bool ini_to_json(const std::string& ini_file, const std::string& json_file) { - return iniToJson(ini_file, json_file); +namespace lithium::cxxtools { +auto iniToJson(std::string_view ini_file, std::string_view json_file) -> bool { + return detail::iniToJson(ini_file, json_file); } +} // namespace lithium::cxxtools + #endif diff --git a/modules/lithium.cxxtools/src/json2ini.cpp b/modules/lithium.cxxtools/src/json2ini.cpp index b54a051c..4e6e4734 100644 --- a/modules/lithium.cxxtools/src/json2ini.cpp +++ b/modules/lithium.cxxtools/src/json2ini.cpp @@ -14,14 +14,18 @@ Description: JSON to INI #include "json2ini.hpp" +#include #include #include "atom/log/loguru.hpp" #include "atom/type/json.hpp" +#include "exception.hpp" using json = nlohmann::json; +namespace fs = std::filesystem; -void writeIniSection(std::ofstream &iniFile, const std::string §ionName, +namespace lithium::cxxtools::detail { +void writeIniSection(std::ofstream &iniFile, std::string_view sectionName, const json &jsonObject) { iniFile << "[" << sectionName << "]" << std::endl; for (auto it = jsonObject.begin(); it != jsonObject.end(); ++it) { @@ -32,27 +36,27 @@ void writeIniSection(std::ofstream &iniFile, const std::string §ionName, iniFile << std::endl; } -void jsonToIni(const std::string &jsonFilePath, - const std::string &iniFilePath) { - std::ifstream jsonFile(jsonFilePath); +void jsonToIni(std::string_view jsonFilePath, std::string_view iniFilePath) { + if (!fs::exists(jsonFilePath) || !fs::is_regular_file(jsonFilePath)) { + THROW_FILE_NOT_FOUND("JSON file not found: ", jsonFilePath); + } + + std::ifstream jsonFile(jsonFilePath.data()); if (!jsonFile.is_open()) { - LOG_F(ERROR, "Failed to open JSON file: {}", jsonFilePath); - return; + THROW_FILE_NOT_READABLE("Failed to open JSON file: ", jsonFilePath); } json jsonData; try { jsonFile >> jsonData; } catch (const std::exception &e) { - LOG_F(ERROR, "Failed to parse JSON file: {}. Error: {}", jsonFilePath, - e.what()); - return; + THROW_RUNTIME_ERROR("Failed to parse JSON file: ", jsonFilePath, + ". Error: ", e.what()); } - std::ofstream iniFile(iniFilePath); + std::ofstream iniFile(iniFilePath.data()); if (!iniFile.is_open()) { - LOG_F(ERROR, "Failed to create INI file: {}", iniFilePath); - return; + THROW_RUNTIME_ERROR("Failed to create INI file: ", iniFilePath); } for (auto it = jsonData.begin(); it != jsonData.end(); ++it) { @@ -61,54 +65,63 @@ void jsonToIni(const std::string &jsonFilePath, } } - iniFile.close(); if (!iniFile) { - LOG_F(ERROR, "Failed to save INI file: {}", iniFilePath); - } else { - LOG_F(INFO, "INI file is saved: {}", iniFilePath); + THROW_FILE_NOT_WRITABLE("Failed to save INI file: ", iniFilePath); } + LOG_F(INFO, "INI file is saved: {}", iniFilePath); } +} // namespace lithium::cxxtools::detail #if ATOM_STANDALONE_COMPONENT_ENABLED #include int main(int argc, char *argv[]) { loguru::init(argc, argv); - loguru::add_file("conversion_log.txt", loguru::Append, loguru::Verbosity_INFO); + loguru::add_file("conversion_log.txt", loguru::Append, + loguru::Verbosity_INFO); - argparse::ArgumentParser program; + argparse::ArgumentParser program("json2ini"); program.add_argument("-i", "--input") .required() - .help("path to input CSV file"); - + .help("path to input JSON file"); program.add_argument("-o", "--output") .required() - .help("path to output JSON file"); - program.parse_args(argc, argv); + .help("path to output INI file"); + + try { + program.parse_args(argc, argv); + } catch (const std::runtime_error &err) { + LOG_F(ERROR, "Error parsing arguments: {}", err.what()); + return 1; + } - std::string jsonFilePath = program.get("input"); - std::string iniFilePath = program.get("output"); + std::string jsonFilePath = program.get("--input"); + std::string iniFilePath = program.get("--output"); - std::ifstream inputFile(jsonFilePath); - if (!inputFile.is_open()) { - LOG_F(ERROR, "JSON file not found: {}", jsonFilePath); + try { + LOG_F(INFO, "Converting JSON to INI..."); + jsonToIni(jsonFilePath, iniFilePath); + LOG_F(INFO, "JSON to INI conversion completed."); + } catch (const std::exception &ex) { + LOG_F(ERROR, "JSON to INI conversion failed: {}", ex.what()); return 1; } - inputFile.close(); - jsonToIni(jsonFilePath, iniFilePath); - LOG_F(INFO, "JSON to INI conversion is completed."); return 0; } #else -bool json_to_ini(const std::string &json_file, const std::string &ini_file) { +namespace lithium::cxxtools { +auto jsonToIni(std::string_view jsonFilePath, + std::string_view iniFilePath) -> bool { try { LOG_F(INFO, "Converting JSON to INI..."); - jsonToIni(json_file, ini_file); - LOG_F(INFO, "JSON to INI conversion is completed."); + detail::jsonToIni(jsonFilePath, iniFilePath); + LOG_F(INFO, "JSON to INI conversion completed."); return true; } catch (const std::exception &e) { LOG_F(ERROR, "JSON to INI conversion failed: {}", e.what()); } return false; } +} // namespace lithium::cxxtools + #endif diff --git a/modules/lithium.cxxtools/src/json2toml.cpp b/modules/lithium.cxxtools/src/json2toml.cpp deleted file mode 100644 index f4107a1e..00000000 --- a/modules/lithium.cxxtools/src/json2toml.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * json2toml.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-7-29 - -Description: JSON to TOML - -**************************************************/ - -#include "json2toml.hpp" - -#include -#include - -#include -#include "atom/log/loguru.hpp" -#include "atom/type/json.hpp" - -namespace fs = std::filesystem; -using json = nlohmann::json; - -void ConvertJsonToToml(const std::string &inputFile, - const std::string &outputFile) { - try { - const fs::path infile{inputFile}; - - if (!fs::exists(infile)) { - DLOG_F(ERROR, "Input file {} does not exist!", infile); - return; - } - - std::ifstream ifs{inputFile}; - json jsonData = json::parse(ifs); - - toml::value data = toml::from_json(jsonData); - - if (!outputFile.empty()) { - std::ofstream out{outputFile}; - if (!out) { - DLOG_F(ERROR, "Failed to open output file: {}", outputFile); - return; - } - out << data << std::endl; - DLOG_F(INFO, "Conversion completed. Output saved to {}", - outputFile); - } else { - std::cout << data << std::endl; - DLOG_F(INFO, "Conversion completed. Result printed to stdout"); - } - } catch (const std::exception &e) { - DLOG_F(ERROR, "An exception occurred during conversion: {}", e.what()); - } -} - -#if ATOM_STANDALONE_COMPONENT_ENABLED -#include -int main(int argc, char **argv) { - loguru::init(argc, argv); - loguru::add_file("conversion_log.txt", loguru::Append, loguru::Verbosity_INFO); - - argparse::ArgumentParser program("json2toml"); - program.add_argument("inputFile").help("Input JSON file"); - program.add_argument("--outputFile", "-o") - .nargs(1) - .help("Output TOML file"); - - try { - program.parse_args(argc, argv); - } catch (const std::runtime_error &err) { - DLOG_F(ERROR, "{}", err.what()); - std::cout << program; - return 1; - } - - const std::string inputFile = program.get("inputFile"); - const std::string outputFile = program.exist("outputFile") - ? program.get("outputFile") - : ""; - - ConvertJsonToToml(inputFile, outputFile); - - loguru::remove_all_callbacks(); - return 0; -} -#else -bool json_to_toml(const std::string &json_file, const std::string &toml_file) { - if (json_file.empty() || toml_file.empty()) { - LOG_F(ERROR, "json_file and toml_file must not be empty"); - return false; - } - if (!std::filesystem::exists(json_file) || - !std::filesystem::is_regular_file(json_file)) { - LOG_F(ERROR, "json_file does not exist"); - return false; - } - if (std::filesystem::exists(toml_file)) { - LOG_F(ERROR, "toml_file already exists"); - return false; - } - ConvertJsonToToml(json_file, toml_file); - return true; -} -#endif diff --git a/modules/lithium.cxxtools/src/json2xml.cpp b/modules/lithium.cxxtools/src/json2xml.cpp index 845a0221..860f9e3a 100644 --- a/modules/lithium.cxxtools/src/json2xml.cpp +++ b/modules/lithium.cxxtools/src/json2xml.cpp @@ -8,12 +8,13 @@ Date: 2023-12-7 -Description: Json to XML conversion +Description: JSON to XML conversion **************************************************/ #include "json2xml.hpp" +#include #include #include "atom/log/loguru.hpp" @@ -21,7 +22,9 @@ Description: Json to XML conversion #include "tinyxml2/tinyxml2.h" using json = nlohmann::json; +namespace fs = std::filesystem; +namespace lithium::cxxtools::detail { void jsonToXml(const json &jsonData, tinyxml2::XMLElement *xmlElement) { tinyxml2::XMLDocument *xmlDoc = xmlElement->GetDocument(); @@ -47,31 +50,38 @@ void jsonToXml(const json &jsonData, tinyxml2::XMLElement *xmlElement) { } } -bool convertJsonToXml(const std::string &jsonFilePath, - const std::string &xmlFilePath) { +auto convertJsonToXml(std::string_view jsonFilePath, + std::string_view xmlFilePath) -> bool { DLOG_F(INFO, "Reading JSON file: {}", jsonFilePath); - // 读取 JSON 文件 - std::ifstream jsonFile(jsonFilePath); + if (!fs::exists(jsonFilePath) || !fs::is_regular_file(jsonFilePath)) { + LOG_F(ERROR, "JSON file does not exist or is not a regular file: {}", + jsonFilePath); + return false; + } + + std::ifstream jsonFile(jsonFilePath.data()); if (!jsonFile.is_open()) { LOG_F(ERROR, "Failed to open JSON file: {}", jsonFilePath); return false; } - // 解析 JSON json jsonData; - jsonFile >> jsonData; + try { + jsonFile >> jsonData; + } catch (const std::exception &e) { + LOG_F(ERROR, "Failed to parse JSON file: {}. Error: {}", jsonFilePath, + e.what()); + return false; + } jsonFile.close(); - // 创建 XML 文档 tinyxml2::XMLDocument xmlDoc; tinyxml2::XMLElement *rootElement = xmlDoc.NewElement("root"); xmlDoc.InsertFirstChild(rootElement); - // 转换 JSON 到 XML jsonToXml(jsonData, rootElement); - // 保存 XML 文档到文件 - if (xmlDoc.SaveFile(xmlFilePath.c_str()) != tinyxml2::XML_SUCCESS) { + if (xmlDoc.SaveFile(xmlFilePath.data()) != tinyxml2::XML_SUCCESS) { LOG_F(ERROR, "Failed to save XML file: {}", xmlFilePath); return false; } @@ -79,6 +89,7 @@ bool convertJsonToXml(const std::string &jsonFilePath, DLOG_F(INFO, "JSON to XML conversion succeeded."); return true; } +} // namespace lithium::cxxtools::detail #if ATOM_STANDALONE_COMPONENT_ENABLED #include @@ -117,22 +128,18 @@ int main(int argc, const char **argv) { return 0; } #else -bool json_to_xml(const std::string &json_file, const std::string &xml_file) { +namespace lithium::cxxtools { +auto jsonToXml(std::string_view json_file, std::string_view xml_file) -> bool { if (json_file.empty() || xml_file.empty()) { DLOG_F(ERROR, "Invalid input file path."); return false; } - if (!std::filesystem::exists(json_file) || - !std::filesystem::is_regular_file(json_file)) { - DLOG_F(ERROR, "Json file does not exist or is not a regular file."); + if (!fs::exists(json_file) || !fs::is_regular_file(json_file)) { + DLOG_F(ERROR, "JSON file does not exist or is not a regular file."); return false; } - if (convertJsonToXml(json_file, xml_file)) { - DLOG_F(INFO, "JSON to XML conversion succeeded."); - return true; - } - DLOG_F(INFO, "JSON to XML conversion failed."); - - return false; + return detail::convertJsonToXml(json_file, xml_file); } +} // namespace lithium::cxxtools + #endif diff --git a/modules/lithium.cxxtools/src/pci_generator.cpp b/modules/lithium.cxxtools/src/pci_generator.cpp index eb05ea3f..2d1acefc 100644 --- a/modules/lithium.cxxtools/src/pci_generator.cpp +++ b/modules/lithium.cxxtools/src/pci_generator.cpp @@ -14,28 +14,33 @@ Description: PCI info generator #include #include +#include #include #include -#include #include #include +#include "atom/error/exception.hpp" +#include "atom/log/loguru.hpp" + +#include "macro.hpp" + struct Vendor { uint64_t pciId; size_t nameIndex; std::vector devices; -}; +} ATOM_ALIGNAS(64); struct Device { uint64_t pciId; size_t nameIndex; -}; +} ATOM_ALIGNAS(16); -void parseAndGeneratePCIInfo(const std::string& filename) { - std::ifstream in(filename); +void parseAndGeneratePCIInfo(std::string_view filename) { + std::ifstream in(filename.data()); if (!in.is_open()) { - std::cerr << "Couldn't open input file\n"; - return; + LOG_F(ERROR, "Couldn't open input file"); + THROW_FILE_NOT_READABLE("Couldn't open input file"); } std::vector vendors; @@ -43,76 +48,80 @@ void parseAndGeneratePCIInfo(const std::string& filename) { std::vector vendorDeviceNames; for (std::string line; std::getline(in, line);) { - if (line.empty()) - continue; - if (line[0] == 'C') // Got to device classes. which we don't want + if (line.empty() || line[0] == 'C') { break; + } - const auto tabcount = line.find_first_not_of('\t'); - if (!std::isxdigit(line[tabcount]) || tabcount >= 3) + const auto TABCOUNT = line.find_first_not_of('\t'); + if ((std::isxdigit(line[TABCOUNT]) == 0) || TABCOUNT >= 3) { continue; + } - if (*line.rbegin() == - '\r') // Remove carriage return if present for CRLF encoded files. + if (*line.rbegin() == '\r') { line.erase(line.length() - 1); + } - char* current_name{}; - auto current_number = - std::strtoull(line.c_str() + tabcount, ¤t_name, 16); - while (std::isspace(*current_name)) - ++current_name; + char* currentName{}; + auto currentNumber = + std::strtoull(line.c_str() + TABCOUNT, ¤tName, 16); + while (std::isspace(*currentName) != 0) { + ++currentName; + } - if (tabcount == 0) // Vendor - vendors.push_back({current_number, vendorDeviceNames.size(), {}}); - else if (tabcount == 1) { // Device + if (TABCOUNT == 0) { + vendors.emplace_back( + Vendor{currentNumber, vendorDeviceNames.size(), {}}); + } else if (TABCOUNT == 1) { vendors.back().devices.push_back(devices.size()); - devices.push_back({current_number, vendorDeviceNames.size()}); + devices.emplace_back( + Device{currentNumber, vendorDeviceNames.size()}); } - vendorDeviceNames.emplace_back(current_name); + vendorDeviceNames.emplace_back(currentName); } - // Sorting vendors by pciId - std::sort( - vendors.begin(), vendors.end(), - [](const Vendor& a, const Vendor& b) { return a.pciId < b.pciId; }); + std::ranges::sort(vendors, {}, &Vendor::pciId); auto& out = std::cout; out << std::hex << std::showbase; - // Output generated PCI indices out << "#define ATOM_SYSTEM_GENERATED_PCI_INDICES\n"; for (size_t idx = 0; const auto& vendor : vendors) { out << " \\\n\t{" << vendor.pciId << ", " << idx << "},"; ++idx; } - // Output generated PCI vendors out << "\n\n\n#define ATOM_SYSTEM_GENERATED_PCI_VENDORS"; for (const auto& vendor : vendors) { out << " \\\n\t{" << vendor.pciId << ", R\"(" << vendorDeviceNames[vendor.nameIndex] << ")\", {"; - for (auto i : vendor.devices) + for (auto i : vendor.devices) { out << i << ", "; + } out << "}},"; } - // Output generated PCI devices out << "\n\n\n#define ATOM_SYSTEM_GENERATED_PCI_DEVICES"; - for (const auto& device : devices) + for (const auto& device : devices) { out << " \\\n\t{" << device.pciId << ", R\"(" << vendorDeviceNames[device.nameIndex] << ")\"},"; + } out << "\n\n\nnamespace {}\n"; } -int main(int argc, const char** argv) { +auto main(int argc, const char** argv) -> int { if (argc < 2) { - std::cerr << "Input file missing\n"; + LOG_F(ERROR, "Input file missing"); return 1; } - parseAndGeneratePCIInfo(argv[1]); + try { + parseAndGeneratePCIInfo(argv[1]); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return 1; + } return 0; } diff --git a/modules/lithium.cxxtools/src/symbol.cpp b/modules/lithium.cxxtools/src/symbol.cpp new file mode 100644 index 00000000..d052e78f --- /dev/null +++ b/modules/lithium.cxxtools/src/symbol.cpp @@ -0,0 +1,204 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "atom/error/exception.hpp" +#include "atom/function/abi.hpp" +#include "atom/log/loguru.hpp" +#include "atom/type/json.hpp" +#include "macro.hpp" + +using json = nlohmann::json; + +// 执行系统命令并返回输出 +std::string exec(const char* cmd) { + std::array buffer; + std::string result; + std::unique_ptr pipe(popen(cmd, "r"), pclose); + if (!pipe) { + throw std::runtime_error("popen() failed!"); + } + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { + result += buffer.data(); + } + return result; +} + +// 符号结构体 +struct Symbol { + std::string address; + std::string type; + std::string bind; + std::string visibility; + std::string name; + std::string demangledName; +} ATOM_ALIGNAS(128); + +auto parseReadelfOutput(const std::string& output) -> std::vector { + std::vector symbols; + std::regex symbolRegex( + R"(\s*\d+:\s+(\S+)\s+\d+\s+(\S+)\s+(\S+)\s+(\S+)\s+\S+\s+(\S+))"); + std::smatch match; + std::string::const_iterator searchStart(output.cbegin()); + + while (std::regex_search(searchStart, output.cend(), match, symbolRegex)) { + symbols.push_back( + Symbol{match[1], match[2], match[3], match[4], match[5], ""}); + searchStart = match.suffix().first; + } + + return symbols; +} + +auto filterSymbolsByType(const std::vector& symbols, + const std::string& type) -> std::vector { + std::vector filtered; + for (const auto& symbol : symbols) { + if (symbol.type == type) { + filtered.push_back(symbol); + } + } + return filtered; +} + +auto filterSymbolsByVisibility(const std::vector& symbols, + const std::string& visibility) + -> std::vector { + std::vector filtered; + for (const auto& symbol : symbols) { + if (symbol.visibility == visibility) { + filtered.push_back(symbol); + } + } + return filtered; +} + +auto filterSymbolsByBind(const std::vector& symbols, + const std::string& bind) -> std::vector { + std::vector filtered; + for (const auto& symbol : symbols) { + if (symbol.bind == bind) { + filtered.push_back(symbol); + } + } + return filtered; +} + +void printSymbolStatistics(const std::vector& symbols) { + std::unordered_map typeCount; + for (const auto& symbol : symbols) { + typeCount[symbol.type]++; + } + LOG_F(INFO, "Symbol type statistics:"); + for (const auto& [type, count] : typeCount) { + LOG_F(INFO, "{} : {}", type, count); + } +} + +void exportSymbolsToFile(const std::vector& symbols, + const std::string& filename) { + std::ofstream file(filename); + if (!file) { + THROW_FAIL_TO_OPEN_FILE("Failed to open file for writing"); + } + + file << "Address,Type,Bind,Visibility,Name,Demangled Name\n"; + for (const auto& symbol : symbols) { + file << symbol.address << ',' << symbol.type << ',' << symbol.bind + << ',' << symbol.visibility << ',' << symbol.name << ',' + << symbol.demangledName << '\n'; + } +} + +void exportSymbolsToJson(const std::vector& symbols, + const std::string& filename) { + json j; + for (const auto& symbol : symbols) { + j.push_back({{"address", symbol.address}, + {"type", symbol.type}, + {"bind", symbol.bind}, + {"visibility", symbol.visibility}, + {"name", symbol.name}, + {"demangled_name", symbol.demangledName}}); + } + + std::ofstream file(filename); + if (!file) { + THROW_FAIL_TO_OPEN_FILE("Failed to open JSON file for writing"); + } + file << j.dump(4); // Pretty print with 4 spaces +} + +auto filterSymbolsByCondition(const std::vector& symbols, + const std::function& + condition) -> std::vector { + std::vector filtered; + for (const auto& symbol : symbols) { + if (condition(symbol)) { + filtered.push_back(symbol); + } + } + return filtered; +} + +void analyzeLibrary(const std::string& libraryPath, + const std::string& outputFormat) { + LOG_F(INFO, "Analyzing library: {}", libraryPath); + + // 使用 readelf 获取 ELF 特定信息 + std::string readelfCmd = "readelf -Ws " + libraryPath; + std::string readelfOutput = exec(readelfCmd.c_str()); + LOG_F(INFO, "Readelf output: {}", readelfOutput); + + // 解析 readelf 输出 + std::vector symbols = parseReadelfOutput(readelfOutput); + LOG_F(INFO, "Parsing readelf output..."); + + // 解码符号名 + for (auto& symbol : symbols) { + symbol.demangledName = + atom::meta::DemangleHelper::demangle(symbol.name); + } + LOG_F(INFO, "Decoding symbol names..."); + + // 符号统计 + printSymbolStatistics(symbols); + + // 导出符号到指定格式的文件 + if (outputFormat == "csv") { + exportSymbolsToFile(symbols, "symbols.csv"); + LOG_F(INFO, "Exported symbols to symbols.csv."); + } else if (outputFormat == "json") { + exportSymbolsToJson(symbols, "symbols.json"); + LOG_F(INFO, "Exported symbols to symbols.json."); + } else { + LOG_F(ERROR, "Unsupported output format: {}", outputFormat); + THROW_INVALID_ARGUMENT("Unsupported output format"); + } +} + +auto main(int argc, char* argv[]) -> int { + if (argc != 3) { + LOG_F(ERROR, "Invalid number of arguments"); + LOG_F(ERROR, "Usage: {} "); + return 1; + } + + std::string libraryPath = argv[1]; + std::string outputFormat = argv[2]; + + try { + analyzeLibrary(libraryPath, outputFormat); + } catch (const std::exception& ex) { + LOG_F(ERROR, "Error: {}", ex.what()); + return 1; + } + + return 0; +} diff --git a/modules/lithium.cxxtools/src/tcp_proxy.cpp b/modules/lithium.cxxtools/src/tcp_proxy.cpp index 6c7ca767..a7821cc8 100644 --- a/modules/lithium.cxxtools/src/tcp_proxy.cpp +++ b/modules/lithium.cxxtools/src/tcp_proxy.cpp @@ -13,6 +13,7 @@ Description: Tcp proxy server *************************************************/ #include +#include #include #include #include @@ -30,13 +31,12 @@ Description: Tcp proxy server #include #endif +#include "atom/error/exception.hpp" #include "atom/log/loguru.hpp" -const int BUFFER_SIZE = 4096; - +constexpr int BUFFER_SIZE = 4096; std::mutex mutex; -// 处理数据传输 void forwardData(int srcSockfd, int dstSockfd) { char buffer[BUFFER_SIZE]; int numBytes; @@ -50,16 +50,8 @@ void forwardData(int srcSockfd, int dstSockfd) { } } -// 启动代理服务器 -void startProxyServer(const std::string &srcIp, int srcPort, - const std::string &dstIp, int dstPort) { +void startProxyServer(const std::string &srcIp, int srcPort, const std::string &dstIp, int dstPort) { #ifdef _WIN32 - WSADATA wsData; - if (WSAStartup(MAKEWORD(2, 2), &wsData) != 0) { - LOG_F(ERROR, "Failed to initialize Winsock."); - return; - } - SOCKET srcSockfd = socket(AF_INET, SOCK_STREAM, 0); SOCKET dstSockfd = socket(AF_INET, SOCK_STREAM, 0); #else @@ -68,65 +60,52 @@ void startProxyServer(const std::string &srcIp, int srcPort, #endif if (srcSockfd == -1 || dstSockfd == -1) { - LOG_F(ERROR, "Failed to create socket."); -#ifdef _WIN32 - WSACleanup(); -#endif - return; + THROW_RUNTIME_ERROR("Failed to create socket."); } - // 绑定源地址和端口 sockaddr_in srcAddr{}; srcAddr.sin_family = AF_INET; srcAddr.sin_addr.s_addr = inet_addr(srcIp.c_str()); srcAddr.sin_port = htons(srcPort); - if (bind(srcSockfd, reinterpret_cast(&srcAddr), - sizeof(srcAddr)) == -1) { - LOG_F(ERROR, "Failed to bind source address."); -#ifdef _WIN32 - closesocket(srcSockfd); - WSACleanup(); -#else - close(srcSockfd); -#endif - return; + if (bind(srcSockfd, reinterpret_cast(&srcAddr), sizeof(srcAddr)) == -1) { + THROW_RUNTIME_ERROR("Failed to bind source address."); } - // 连接目标地址和端口 sockaddr_in dstAddr{}; dstAddr.sin_family = AF_INET; dstAddr.sin_addr.s_addr = inet_addr(dstIp.c_str()); dstAddr.sin_port = htons(dstPort); - if (connect(dstSockfd, reinterpret_cast(&dstAddr), - sizeof(dstAddr)) == -1) { - LOG_F(ERROR, "Failed to connect to destination address."); -#ifdef _WIN32 - closesocket(srcSockfd); - closesocket(dstSockfd); - WSACleanup(); -#else - close(srcSockfd); - close(dstSockfd); -#endif - return; + if (connect(dstSockfd, reinterpret_cast(&dstAddr), sizeof(dstAddr)) == -1) { + THROW_RUNTIME_ERROR("Failed to connect to destination address."); } - // 开始转发数据 forwardData(srcSockfd, dstSockfd); #ifdef _WIN32 closesocket(srcSockfd); closesocket(dstSockfd); - WSACleanup(); #else close(srcSockfd); close(dstSockfd); #endif } +void signalHandler(int signal) { + if (signal == SIGINT || signal == SIGTERM) { + LOG_F(INFO, "Interrupt signal received, shutting down..."); +#ifdef _WIN32 + WSACleanup(); +#endif + std::exit(0); + } +} + int main(int argc, char *argv[]) { + signal(SIGINT, signalHandler); + signal(SIGTERM, signalHandler); + #ifdef _WIN32 WSADATA wsData; if (WSAStartup(MAKEWORD(2, 2), &wsData) != 0) { @@ -135,11 +114,10 @@ int main(int argc, char *argv[]) { } #endif - std::string srcIp = "127.0.0.1"; // 源IP地址 - int srcPort = 12345; // 源端口 - - std::string dstIp = "127.0.0.1"; // 目标IP地址 - int dstPort = 54321; // 目标端口 + std::string srcIp = "127.0.0.1"; + int srcPort = 12345; + std::string dstIp = "127.0.0.1"; + int dstPort = 54321; int option; while ((option = getopt(argc, argv, "s:p:d:o:")) != -1) { @@ -157,28 +135,36 @@ int main(int argc, char *argv[]) { dstPort = std::stoi(optarg); break; default: - LOG_F(ERROR, - "Usage: {} -s -p -d -o " - "", - argv[0]); + LOG_F(ERROR, "Usage: {} -s -p -d -o ", argv[0]); return 1; } } + #if __cplusplus >= 202002L std::vector threads; #else std::vector threads; #endif - // 启动多个线程处理并发连接 - for (int i = 0; i < 5; ++i) { - threads.emplace_back(startProxyServer, srcIp, srcPort, dstIp, dstPort); + try { + for (int i = 0; i < 5; ++i) { + threads.emplace_back(startProxyServer, srcIp, srcPort, dstIp, dstPort); + } + } catch (const std::exception &e) { + LOG_F(ERROR, "Exception caught while starting proxy server: {}", e.what()); } - // 等待所有线程结束 +#if __cplusplus >= 202002L for (auto &thread : threads) { thread.join(); } +#else + for (auto &thread : threads) { + if (thread.joinable()) { + thread.join(); + } + } +#endif #ifdef _WIN32 WSACleanup(); diff --git a/modules/lithium.cxxtools/src/toml2json.cpp b/modules/lithium.cxxtools/src/toml2json.cpp deleted file mode 100644 index feda5d13..00000000 --- a/modules/lithium.cxxtools/src/toml2json.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* - * toml2json.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-7-29 - -Description: TOML to JSON - -**************************************************/ - -#include -#include -#include - -#include -#include "atom/log/loguru.hpp" - -namespace fs = std::filesystem; - -void ConvertTomlToJson(const std::string &inputFile, - const std::string &outputFile) { - try { - const fs::path infile{inputFile}; - - if (!fs::exists(infile)) { - DLOG_F(ERROR, "Input file {} does not exist!", infile); - return; - } - - auto data = toml::parse_file(infile); - - if (!outputFile.empty()) { - std::ofstream out{outputFile}; - if (!out) { - DLOG_F(ERROR, "Failed to open output file: {}", outputFile); - return; - } - out << toml::json_formatter(data) << std::endl; - DLOG_F(INFO, "Conversion completed. Output saved to {}", - outputFile); - } else { - std::cout << toml::json_formatter(data) << std::endl; - DLOG_F(INFO, "Conversion completed. Result printed to stdout"); - } - } catch (const std::exception &e) { - DLOG_F(ERROR, "An exception occurred during conversion: {}", e.what()); - } -} - -#if ATOM_STANDALONE_COMPONENT_ENABLED -#include "argparse/argparse.hpp" -int main(int argc, char *argv[]) { - loguru::init(argc, argv); - loguru::add_file("conversion_log.txt", loguru::Append, - loguru::Verbosity_INFO); - - argparse::ArgumentParser program; - program.add_argument("-i", "--input") - .required() - .help("path to input TOML file"); - - program.add_argument("-o", "--output") - .required() - .help("path to output JSON file"); - program.parse_args(argc, argv); - - std::string tomlFilePath = program.get("input"); - std::string jsonFilePath = program.get("output"); - - ConvertTomlToJson(tomlFilePath, jsonFilePath); - - return 0; -} -#else -bool toml_to_json(const std::string &toml_file, const std::string &json_file) { - ConvertTomlToJson(toml_file, json_file); - return true; -} - -#endif diff --git a/modules/lithium.cxxtools/src/xml2json.cpp b/modules/lithium.cxxtools/src/xml2json.cpp index 9edfc87c..734826b6 100644 --- a/modules/lithium.cxxtools/src/xml2json.cpp +++ b/modules/lithium.cxxtools/src/xml2json.cpp @@ -15,6 +15,7 @@ Description: XML to JSON conversion #include "xml2json.hpp" #include +#include #include "atom/log/loguru.hpp" #include "atom/type/json.hpp" @@ -22,16 +23,16 @@ Description: XML to JSON conversion using json = nlohmann::json; +namespace lithium::cxxtools::detail { void xmlToJson(const tinyxml2::XMLElement *xmlElement, json &jsonData) { for (const tinyxml2::XMLNode *childNode = xmlElement->FirstChild(); childNode != nullptr; childNode = childNode->NextSibling()) { - if (childNode->ToElement()) { - const std::string childNodeName = childNode->Value(); - json &jsonChildValue = jsonData[childNodeName]; + if (childNode->ToElement() != nullptr) { + const std::string CHILD_NODE_NAME = childNode->Value(); + json &jsonChildValue = jsonData[CHILD_NODE_NAME]; if (!jsonChildValue.is_null()) { if (!jsonChildValue.is_array()) { - jsonChildValue = json::array(); - jsonChildValue.push_back(jsonData[childNodeName]); + jsonChildValue = json::array({jsonChildValue}); } } else { jsonChildValue = json::array(); @@ -40,32 +41,27 @@ void xmlToJson(const tinyxml2::XMLElement *xmlElement, json &jsonData) { json jsonItemValue; xmlToJson(childNode->ToElement(), jsonItemValue); jsonChildValue.push_back(jsonItemValue); - } else if (childNode->ToText()) { + } else if (childNode->ToText() != nullptr) { jsonData = json(childNode->ToText()->Value()); } } } -bool convertXmlToJson(const std::string &xmlFilePath, - const std::string &jsonFilePath) { - // 读取 XML 文件 +auto convertXmlToJson(std::string_view xmlFilePath, + std::string_view jsonFilePath) -> bool { DLOG_F(INFO, "Reading XML file: {}", xmlFilePath); tinyxml2::XMLDocument xmlDoc; - if (xmlDoc.LoadFile(xmlFilePath.c_str()) != tinyxml2::XML_SUCCESS) { + if (xmlDoc.LoadFile(xmlFilePath.data()) != tinyxml2::XML_SUCCESS) { DLOG_F(ERROR, "Failed to load XML file: {}", xmlFilePath); return false; } - // 创建 JSON 对象 json jsonData; - - // 转换 XML 到 JSON DLOG_F(INFO, "Converting XML to JSON"); xmlToJson(xmlDoc.RootElement(), jsonData); - // 保存 JSON 对象到文件 DLOG_F(INFO, "Saving JSON file: {}", jsonFilePath); - std::ofstream jsonFile(jsonFilePath); + std::ofstream jsonFile(jsonFilePath.data()); if (!jsonFile.is_open()) { DLOG_F(ERROR, "Failed to open JSON file: {}", jsonFilePath); return false; @@ -77,9 +73,10 @@ bool convertXmlToJson(const std::string &xmlFilePath, DLOG_F(INFO, "XML to JSON conversion succeeded."); return true; } +} // namespace lithium::cxxtools::detail #if ATOM_STANDALONE_COMPONENT_ENABLED -#include "argparse/argparse.hpp" +#include int main(int argc, char *argv[]) { loguru::init(argc, argv); loguru::add_file("conversion_log.txt", loguru::Append, @@ -89,7 +86,6 @@ int main(int argc, char *argv[]) { program.add_argument("-i", "--input") .required() .help("path to input XML file"); - program.add_argument("-o", "--output") .required() .help("path to output JSON file"); @@ -114,12 +110,19 @@ int main(int argc, char *argv[]) { return 0; } #else -bool xml_to_json(const std::string &xml_file, const std::string &json_file) { - if (convertXmlToJson(xml_file, json_file)) { - DLOG_F(INFO, "XML to JSON conversion succeeded."); - return true; +namespace lithium::cxxtools { +auto xmlToJson(std::string_view xml_file, std::string_view json_file) -> bool { + try { + if (detail::convertXmlToJson(xml_file, json_file)) { + DLOG_F(INFO, "XML to JSON conversion succeeded."); + return true; + } + } catch (const std::exception &e) { + DLOG_F(ERROR, "Conversion failed: {}", e.what()); } DLOG_F(INFO, "XML to JSON conversion failed."); return false; } +} // namespace lithium::cxxtools + #endif diff --git a/modules/lithium.cxxtools/tests/CMakeLists.txt b/modules/lithium.cxxtools/tests/CMakeLists.txt new file mode 100644 index 00000000..4fbec3d8 --- /dev/null +++ b/modules/lithium.cxxtools/tests/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.20) + +project(lithium.cxxtools.test) + +file(GLOB_RECURSE TEST_SOURCES ${PROJECT_SOURCE_DIR}/*.cpp) + +add_executable(${PROJECT_NAME} ${TEST_SOURCES}) + +target_link_libraries(${PROJECT_NAME} gtest gtest_main lithium.cxxtools loguru) diff --git a/modules/lithium.cxxtools/tests/csv2json.cpp b/modules/lithium.cxxtools/tests/csv2json.cpp new file mode 100644 index 00000000..2d939b78 --- /dev/null +++ b/modules/lithium.cxxtools/tests/csv2json.cpp @@ -0,0 +1,50 @@ +#include "csv2json.hpp" + +#include +#include + +#include "atom/type/json.hpp" + +using namespace lithium::cxxtools; + +TEST(CSV2JSONTest, BasicConversion) { + std::string csvContent = + "name,age,city\nJohn,30,New York\nJane,25,Los Angeles\n"; + std::string csvFilePath = "test.csv"; + std::ofstream csvFile(csvFilePath); + csvFile << csvContent; + csvFile.close(); + + std::string jsonFilePath = "test.json"; + EXPECT_NO_THROW({ + auto jsonData = detail::csvToJson(csvFilePath); + detail::saveJsonToFile(jsonData, jsonFilePath); + }); + + std::ifstream jsonFile(jsonFilePath); + ASSERT_TRUE(jsonFile.is_open()); + + nlohmann::json expectedJson = R"([ + {"name": "John", "age": "30", "city": "New York"}, + {"name": "Jane", "age": "25", "city": "Los Angeles"} + ])"_json; + + nlohmann::json actualJson; + jsonFile >> actualJson; + EXPECT_EQ(expectedJson, actualJson); +} + +TEST(CSV2JSONTest, MissingCSVFile) { + EXPECT_THROW({ detail::csvToJson("nonexistent.csv"); }, std::runtime_error); +} + +TEST(CSV2JSONTest, InvalidCSVContent) { + std::string csvContent = "name,age,city\nJohn,30\nJane,25,Los Angeles\n"; + std::string csvFilePath = "invalid.csv"; + std::ofstream csvFile(csvFilePath); + csvFile << csvContent; + csvFile.close(); + + EXPECT_THROW( + { auto jsonData = detail::csvToJson(csvFilePath); }, std::exception); +} diff --git a/modules/lithium.cxxtools/tests/ini2json.cpp b/modules/lithium.cxxtools/tests/ini2json.cpp new file mode 100644 index 00000000..e67b7ac0 --- /dev/null +++ b/modules/lithium.cxxtools/tests/ini2json.cpp @@ -0,0 +1,60 @@ +#include "ini2json.hpp" + +#include +#include +#include "macro.hpp" + +using namespace lithium::cxxtools; + +class INI2JSONTest : public ::testing::Test { +protected: + void SetUp() override { + // Create a sample INI file + std::ofstream iniFile("test.ini"); + iniFile << "[section1]\nkey1=value1\nkey2=value2\n" + << "[section2]\nkey3=value3\nkey4=value4\n"; + iniFile.close(); + } + + void TearDown() override { + // Clean up the created files + ATOM_UNREF_PARAM(std::remove("test.ini")); + ATOM_UNREF_PARAM(std::remove("test.json")); + } +}; + +TEST_F(INI2JSONTest, BasicConversion) { + EXPECT_TRUE(iniToJson("test.ini", "test.json")); + + std::ifstream jsonFile("test.json"); + ASSERT_TRUE(jsonFile.is_open()); + + std::string jsonContent((std::istreambuf_iterator(jsonFile)), + std::istreambuf_iterator()); + std::string expectedJson = R"({ + "section1": { + "key1": "value1", + "key2": "value2" + }, + "section2": { + "key3": "value3", + "key4": "value4" + } +})"; + EXPECT_EQ(jsonContent, expectedJson); +} + +TEST_F(INI2JSONTest, MissingINIFile) { + EXPECT_THROW( + { iniToJson("nonexistent.ini", "test.json"); }, std::runtime_error); +} + +TEST_F(INI2JSONTest, InvalidINIContent) { + std::ofstream iniFile("invalid.ini"); + iniFile << "[section1\nkey1=value1\nkey2=value2\n"; + iniFile.close(); + + EXPECT_THROW({ iniToJson("invalid.ini", "test.json"); }, std::exception); + + ATOM_UNREF_PARAM(std::remove("invalid.ini")); +} diff --git a/modules/lithium.cxxtools/tests/json2ini.cpp b/modules/lithium.cxxtools/tests/json2ini.cpp new file mode 100644 index 00000000..8dd847e9 --- /dev/null +++ b/modules/lithium.cxxtools/tests/json2ini.cpp @@ -0,0 +1,66 @@ +#include "json2ini.hpp" + +#include +#include +#include "macro.hpp" + +using namespace lithium::cxxtools; + +class JSON2INITest : public ::testing::Test { +protected: + void SetUp() override { + // Create a sample JSON file + std::ofstream jsonFile("test.json"); + jsonFile << R"({ + "section1": { + "key1": "value1", + "key2": "value2" + }, + "section2": { + "key3": "value3", + "key4": "value4" + } + })"; + jsonFile.close(); + } + + void TearDown() override { + // Clean up the created files + ATOM_UNUSED_RESULT(std::remove("test.json")); + ATOM_UNUSED_RESULT(std::remove("test.ini")); + } +}; + +TEST_F(JSON2INITest, BasicConversion) { + EXPECT_NO_THROW(jsonToIni("test.json", "test.ini")); + + std::ifstream iniFile("test.ini"); + ASSERT_TRUE(iniFile.is_open()); + + std::string iniContent((std::istreambuf_iterator(iniFile)), + std::istreambuf_iterator()); + std::string expectedIni = R"([section1] +key1=value1 +key2=value2 + +[section2] +key3=value3 +key4=value4 + +)"; + EXPECT_EQ(iniContent, expectedIni); +} + +TEST_F(JSON2INITest, MissingJSONFile) { + EXPECT_THROW(jsonToIni("nonexistent.json", "test.ini"), std::runtime_error); +} + +TEST_F(JSON2INITest, InvalidJSONContent) { + std::ofstream jsonFile("invalid.json"); + jsonFile << R"({ "section1": { "key1": "value1", "key2": "value2", )"; + jsonFile.close(); + + EXPECT_THROW(jsonToIni("invalid.json", "test.ini"), std::runtime_error); + + ATOM_UNUSED_RESULT(std::remove("invalid.json")); +} diff --git a/modules/lithium.cxxtools/tests/json2xml.cpp b/modules/lithium.cxxtools/tests/json2xml.cpp new file mode 100644 index 00000000..ddb10ce7 --- /dev/null +++ b/modules/lithium.cxxtools/tests/json2xml.cpp @@ -0,0 +1,65 @@ +#include "json2xml.hpp" +#include +#include +#include +#include "tinyxml2/tinyxml2.h" + +namespace fs = std::filesystem; +using namespace lithium::cxxtools; + +class JSON2XMLTest : public ::testing::Test { +protected: + void SetUp() override { + // Create a sample JSON file + std::ofstream jsonFile("test.json"); + jsonFile << R"({ + "title": "Example Title", + "owner": { + "name": "Tom Preston-Werner", + "dob": "1979-05-27T07:32:00Z" + }, + "database": { + "server": "192.168.1.1", + "ports": [ 8001, 8001, 8002 ], + "connection_max": 5000, + "enabled": true + } + })"; + jsonFile.close(); + } + + void TearDown() override { + // Clean up the created files + fs::remove("test.json"); + fs::remove("test.xml"); + } +}; + +TEST_F(JSON2XMLTest, BasicConversion) { + EXPECT_TRUE(detail::convertJsonToXml("test.json", "test.xml")); + + tinyxml2::XMLDocument xmlDoc; + ASSERT_EQ(xmlDoc.LoadFile("test.xml"), tinyxml2::XML_SUCCESS); + + tinyxml2::XMLElement *root = xmlDoc.RootElement(); + ASSERT_NE(root, nullptr); + EXPECT_STREQ(root->Name(), "root"); + + tinyxml2::XMLElement *title = root->FirstChildElement("title"); + ASSERT_NE(title, nullptr); + EXPECT_STREQ(title->GetText(), "Example Title"); +} + +TEST_F(JSON2XMLTest, MissingJSONFile) { + EXPECT_FALSE(detail::convertJsonToXml("nonexistent.json", "test.xml")); +} + +TEST_F(JSON2XMLTest, InvalidJSONContent) { + std::ofstream jsonFile("invalid.json"); + jsonFile << R"({ "title": "Example Title", "owner": { "name": "Tom" )"; + jsonFile.close(); + + EXPECT_FALSE(detail::convertJsonToXml("invalid.json", "test.xml")); + + fs::remove("invalid.json"); +} diff --git a/modules/lithium.cxxtools/tests/pci_generator.cpp b/modules/lithium.cxxtools/tests/pci_generator.cpp new file mode 100644 index 00000000..c8ae38a1 --- /dev/null +++ b/modules/lithium.cxxtools/tests/pci_generator.cpp @@ -0,0 +1,58 @@ +#include "pci_generator.hpp" + +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +class PCIGeneratorTest : public ::testing::Test { +protected: + void SetUp() override { + std::ofstream outfile("test_pci_data.txt"); + outfile << "1234 Vendor A\n" + << "\t5678 Device A1\n" + << "\t5679 Device A2\n" + << "1235 Vendor B\n" + << "\t6789 Device B1\n"; + outfile.close(); + } + + void TearDown() override { fs::remove("test_pci_data.txt"); } +}; + +TEST_F(PCIGeneratorTest, ParseAndGeneratePCIInfo) { + std::stringstream output; + std::streambuf* oldCoutBuf = std::cout.rdbuf(output.rdbuf()); + + EXPECT_NO_THROW(parseAndGeneratePCIInfo("test_pci_data.txt")); + + std::cout.rdbuf(oldCoutBuf); + + std::string expectedOutput = + "#define ATOM_SYSTEM_GENERATED_PCI_INDICES\n" + " \\\n\t{0x1234, 0}, \\\n\t{0x1235, 1}," + "\n\n\n#define ATOM_SYSTEM_GENERATED_PCI_VENDORS" + " \\\n\t{0x1234, R\"(Vendor A)\", {0, 1, }}, \\\n\t{0x1235, R\"(Vendor " + "B)\", {2, }}," + "\n\n\n#define ATOM_SYSTEM_GENERATED_PCI_DEVICES" + " \\\n\t{0x5678, R\"(Device A1)\"}, \\\n\t{0x5679, R\"(Device A2)\"}, " + "\\\n\t{0x6789, R\"(Device B1)\"}," + "\n\n\nnamespace {}\n"; + + EXPECT_EQ(output.str(), expectedOutput); +} + +TEST_F(PCIGeneratorTest, FileNotFound) { + std::stringstream errorOutput; + std::streambuf* oldCerrBuf = std::cerr.rdbuf(errorOutput.rdbuf()); + + EXPECT_THROW(parseAndGeneratePCIInfo("nonexistent.txt"), + std::runtime_error); + + std::cerr.rdbuf(oldCerrBuf); + + EXPECT_EQ(errorOutput.str(), "Couldn't open input file\n"); +} diff --git a/modules/lithium.cxxtools/tests/xmake.lua b/modules/lithium.cxxtools/tests/xmake.lua new file mode 100644 index 00000000..0ccc0ba6 --- /dev/null +++ b/modules/lithium.cxxtools/tests/xmake.lua @@ -0,0 +1,14 @@ +set_project("lithium.cxxtools.test") +set_version("1.0.0") +set_xmakever("2.5.1") + +-- Add gtest dependency +add_requires("gtest") + +-- Test Executable +target("lithium.cxxtools.test") + set_kind("binary") + add_files("**.cpp") + add_packages("gtest", "lithium.cxxtools", "loguru") + set_targetdir("$(buildir)/bin") +target_end() diff --git a/modules/lithium.cxxtools/tests/xml2json.cpp b/modules/lithium.cxxtools/tests/xml2json.cpp new file mode 100644 index 00000000..1d955357 --- /dev/null +++ b/modules/lithium.cxxtools/tests/xml2json.cpp @@ -0,0 +1,70 @@ +#include "xml2json.hpp" + +#include +#include +#include + +namespace fs = std::filesystem; +using namespace lithium::cxxtools; + +class XML2JSONTest : public ::testing::Test { +protected: + void SetUp() override { + std::ofstream xmlFile("test.xml"); + xmlFile << R"( + + Example Title + + Tom Preston-Werner + 1979-05-27T07:32:00Z + + + 192.168.1.1 + 8001,8001,8002 + 5000 + true + + + )"; + xmlFile.close(); + } + + void TearDown() override { + fs::remove("test.xml"); + fs::remove("test.json"); + } +}; + +TEST_F(XML2JSONTest, BasicConversion) { + EXPECT_TRUE(detail::convertXmlToJson("test.xml", "test.json")); + + std::ifstream jsonFile("test.json"); + ASSERT_TRUE(jsonFile.is_open()); + + std::string jsonContent((std::istreambuf_iterator(jsonFile)), + std::istreambuf_iterator()); + ASSERT_FALSE(jsonContent.empty()); +} + +TEST_F(XML2JSONTest, MissingXMLFile) { + EXPECT_FALSE(detail::convertXmlToJson("nonexistent.xml", "test.json")); +} + +TEST_F(XML2JSONTest, InvalidXMLContent) { + std::ofstream xmlFile("invalid.xml"); + xmlFile << R"( + + Example Title + + Tom Preston-Werner + 1979-05-27T07:32:00Z + + + 192.168.1.1 + )"; + xmlFile.close(); + + EXPECT_FALSE(detail::convertXmlToJson("invalid.xml", "test.json")); + + fs::remove("invalid.xml"); +} diff --git a/modules/lithium.cxxtools/xmake.lua b/modules/lithium.cxxtools/xmake.lua new file mode 100644 index 00000000..953c07de --- /dev/null +++ b/modules/lithium.cxxtools/xmake.lua @@ -0,0 +1,44 @@ +set_project("lithium.cxxtools") +set_version("1.0.0") +set_xmakever("2.5.1") + +-- Set the C++ standard +set_languages("cxx20") + +-- Define libraries +local lithium_cxxtools_libs = { + "atom-component", + "atom-error", + "loguru", + "tinyxml2", + "pthread" +} + +-- Source files +local source_files = { + "src/csv2json.cpp", + "src/ini2json.cpp", + "src/json2ini.cpp", + "src/json2xml.cpp", + "src/xml2json.cpp", + "src/pci_generator.cpp", + "_component.cpp", + "_main.cpp" +} + +-- Shared Library +target("lithium.cxxtools") + set_kind("shared") + add_files(table.unpack(source_files)) + add_packages(table.unpack(lithium_cxxtools_libs)) + add_includedirs("include") + set_targetdir("$(buildir)/lib") + set_installdir("$(installdir)/lib") + set_version("1.0.0", {build = "%Y%m%d%H%M"}) + on_install(function (target) + os.cp(target:targetfile(), path.join(target:installdir(), "lib")) + end) +target_end() + +-- Add tests subdirectory +includes("tests") diff --git a/driver/camera/atom-touptek/_component.cpp b/modules/lithium.driver/solver/astap/_component.cpp similarity index 100% rename from driver/camera/atom-touptek/_component.cpp rename to modules/lithium.driver/solver/astap/_component.cpp diff --git a/driver/camera/atom-asi/_component.hpp b/modules/lithium.driver/solver/astap/_component.hpp similarity index 100% rename from driver/camera/atom-asi/_component.hpp rename to modules/lithium.driver/solver/astap/_component.hpp diff --git a/driver/camera/atom-touptek/_component.hpp b/modules/lithium.driver/solver/astap/_main.cpp similarity index 100% rename from driver/camera/atom-touptek/_component.hpp rename to modules/lithium.driver/solver/astap/_main.cpp diff --git a/driver/client/atom-alpaca/camera.cpp b/modules/lithium.driver/solver/astap/_pybind.cpp similarity index 100% rename from driver/client/atom-alpaca/camera.cpp rename to modules/lithium.driver/solver/astap/_pybind.cpp diff --git a/modules/lithium.image/include/fitsio.hpp b/modules/lithium.image/include/fitsio.hpp index fc2ccd74..bfc5f852 100644 --- a/modules/lithium.image/include/fitsio.hpp +++ b/modules/lithium.image/include/fitsio.hpp @@ -5,12 +5,16 @@ #include #include - void checkFitsStatus(int status, const std::string& errorMessage); -cv::Mat readFitsToMat(const std::filesystem::path& filepath); +auto readFitsToMat(const std::filesystem::path& filepath) -> cv::Mat; void writeMatToFits(const cv::Mat& image, const std::filesystem::path& filepath); -std::string matToBase64(const cv::Mat& image, const std::string& imgFormat); -std::string fitsToBase64(const std::filesystem::path& filepath); +auto matToBase64(const cv::Mat& image, + const std::string& imgFormat) -> std::string; +auto fitsToBase64(const std::filesystem::path& filepath) -> std::string; +auto readFitsHeadForDevName(const std::string& filename) + -> std::optional; +auto readFits(const std::string& fileName, cv::Mat& image) -> int; +auto readFits_(const std::string& fileName, cv::Mat& image) -> int; #endif diff --git a/modules/lithium.image/include/solver.hpp b/modules/lithium.image/include/solver.hpp new file mode 100644 index 00000000..a0926b77 --- /dev/null +++ b/modules/lithium.image/include/solver.hpp @@ -0,0 +1,15 @@ +#ifndef LITHIUM_IAMGE_SOLVER_HPP +#define LITHIUM_IAMGE_SOLVER_HPP + +#include +#include + +#include "macro.hpp" + +struct LoadFitsResult { + bool success{}; + FITSImage::Statistic imageStats{}; + uint8_t* imageBuffer{}; +} ATOM_ALIGNAS(128); + +#endif diff --git a/modules/lithium.image/src/convert.cpp b/modules/lithium.image/src/convert.cpp new file mode 100644 index 00000000..8dfa6c15 --- /dev/null +++ b/modules/lithium.image/src/convert.cpp @@ -0,0 +1,221 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fitsio.hpp" + +#include + +void saveFitsAsJPG(const std::string& filename) { + cv::Mat image; + cv::Mat image16; + cv::Mat SendImage; + readFits(filename.c_str(), image); + + std::vector stars = Tools::FindStarsByStellarSolver(true, true); + + if (stars.size() != 0) { + FWHM = stars[0].HFR; + } else { + FWHM = -1; + } + + if (image16.depth() == 8) + image.convertTo(image16, CV_16UC1, 256, 0); // x256 MSB alignment + else + image.convertTo(image16, CV_16UC1, 1, 0); + + if (FWHM != -1) { + // Draw detection results on the original image + cv::Point center(stars[0].x, stars[0].y); + cv::circle(image16, center, static_cast(FWHM), cv::Scalar(0, 0, 255), 1); // Draw HFR circle + cv::circle(image16, center, 1, cv::Scalar(0, 255, 0), -1); // Draw center point + // Display HFR value on the image + std::string hfrText = cv::format("%.2f", stars[0].HFR); + cv::putText(image16, hfrText, cv::Point(stars[0].x - FWHM, stars[0].y - FWHM - 5), cv::FONT_HERSHEY_SIMPLEX, 0.4, cv::Scalar(0, 0, 255), 1); + } + + cv::Mat NewImage = image16; + + FWHMCalOver = true; + + // Scale the image to the range of 0-255 + cv::normalize(NewImage, SendImage, 0, 255, cv::NORM_MINMAX, CV_8U); + + // Generate a unique ID + uuid_t uuid; + uuid_generate(uuid); + char uniqueId[37]; + uuid_unparse(uuid, uniqueId); + + // List all files with "CaptureImage" prefix + std::filesystem::path directory(vueDirectoryPath); + std::vector fileList; + for (const auto& entry : std::filesystem::directory_iterator(directory)) { + if (entry.path().extension() == ".jpg" && entry.path().stem().string().find("CaptureImage") == 0) { + fileList.push_back(entry.path()); + } + } + + // Remove all matching files + for (const auto& filePath : fileList) { + std::filesystem::remove(filePath); + } + + // Remove the previous image file + if (PriorROIImage != "NULL") { + std::filesystem::remove(PriorROIImage); + } + + // Save the new image with a unique ID in the filename + std::string fileName = "CaptureImage_" + std::string(uniqueId) + ".jpg"; + std::string filePath = vueDirectoryPath + fileName; + + bool saved = cv::imwrite(filePath, SendImage); + + std::string Command = "ln -sf " + filePath + " " + vueImagePath + fileName; + system(Command.c_str()); + + PriorROIImage = vueImagePath + fileName; + + if (saved) { + // emit wsThread->sendMessageToClient("SaveJpgSuccess:" + QString::fromStdString(fileName)); + + if (FWHM != -1) { + dataPoints.emplace_back(CurrentPosition, FWHM); + + qDebug() << "dataPoints:" << CurrentPosition << "," << FWHM; + + float a, b, c; + Tools::fitQuadraticCurve(dataPoints, a, b, c); + + if (dataPoints.size() >= 5) { + std::vector LineData; + + for (float x = CurrentPosition - 3000; x <= CurrentPosition + 3000; x += 10) { + float y = a * x * x + b * x + c; + LineData.emplace_back(x, y); + } + + // Calculate the x-coordinate where the derivative is zero + float x_min = -b / (2 * a); + minPoint_X = x_min; + // Calculate the y-coordinate of the minimum point + float y_min = a * x_min * x_min + b * x_min + c; + + std::ostringstream dataString; + for (const auto& point : LineData) { + dataString << point.x() << "|" << point.y() << ":"; + } + + R2 = Tools::calculateRSquared(dataPoints, a, b, c); + qDebug() << "RSquared: " << R2; + + // emit wsThread->sendMessageToClient("fitQuadraticCurve:" + QString::fromStdString(dataString.str())); + // emit wsThread->sendMessageToClient("fitQuadraticCurve_minPoint:" + QString::number(x_min) + ":" + QString::number(y_min)); + } + } + } else { + qDebug() << "Save Image Failed..."; + } +} + +int saveFitsAsPNG(const std::string& fitsFileName) { + CaptureTestTimer.start(); + qDebug() << "\033[32m" << "Save image data start." << "\033[0m"; + + cv::Mat image; + int status = Tools::readFits(fitsFileName.c_str(), image); + + // std::vector stars = Tools::FindStarsByStellarSolver(false, true); + + if (status != 0) { + qDebug() << "Failed to read FITS file: " << fitsFileName; + return status; + } + + int width = image.cols; + int height = image.rows; + + qDebug() << "image size:" << width << "," << height; + qDebug() << "image depth:" << image.depth(); + qDebug() << "image channels:" << image.channels(); + + std::vector imageData; + imageData.assign(image.data, image.data + image.total() * image.channels() * 2); + qDebug() << "imageData Size:" << imageData.size() << "," << image.data + image.total() * image.channels(); + + // Generate a unique ID + uuid_t uuid; + uuid_generate(uuid); + char uniqueId[37]; + uuid_unparse(uuid, uniqueId); + + // List all files with "CaptureImage" prefix + std::filesystem::path directory(vueDirectoryPath); + std::vector fileList; + for (const auto& entry : std::filesystem::directory_iterator(directory)) { + if (entry.path().extension() == ".bin" && entry.path().stem().string().find("CaptureImage") == 0) { + fileList.push_back(entry.path()); + } + } + + // Remove all matching files + for (const auto& filePath : fileList) { + std::filesystem::remove(filePath); + } + + // Remove the previous image file + if (PriorCaptureImage != "NULL") { + std::filesystem::remove(PriorCaptureImage); + } + + std::string fileName_ = "CaptureImage_" + std::string(uniqueId) + ".bin"; + std::string filePath_ = vueDirectoryPath + fileName_; + + std::ofstream outFile(filePath_, std::ios::binary); + if (!outFile) { + throw std::runtime_error("Failed to open file for writing."); + } + + outFile.write(reinterpret_cast(imageData.data()), imageData.size()); + if (!outFile) { + throw std::runtime_error("Failed to write data to file."); + } + + outFile.close(); + if (!outFile) { + throw std::runtime_error("Failed to close the file properly."); + } + + CaptureTestTime = CaptureTestTimer.elapsed(); + qDebug() << "\033[32m" << "Save image Data completed:" << CaptureTestTime << "milliseconds" << "\033[0m"; + CaptureTestTimer.invalidate(); + + std::string Command = "ln -sf " + filePath_ + " " + vueImagePath + fileName_; + system(Command.c_str()); + + PriorCaptureImage = vueImagePath + fileName_; + + // emit wsThread->sendMessageToClient("SaveBinSuccess:" + QString::fromStdString(fileName_)); + isStagingImage = true; + SavedImage = QString::fromStdString(fileName_); + + std::vector stars = Tools::FindStarsByStellarSolver(false, true); + + std::ostringstream dataString; + for (const auto& star : stars) { + dataString << star.x << "|" << star.y << "|" << star.HFR << ":"; + } + // emit wsThread->sendMessageToClient("DetectedStars:" + QString::fromStdString(dataString.str())); + + return 0; +} diff --git a/modules/lithium.image/src/fitsio.cpp b/modules/lithium.image/src/fitsio.cpp index e50ac328..f7fe4977 100644 --- a/modules/lithium.image/src/fitsio.cpp +++ b/modules/lithium.image/src/fitsio.cpp @@ -3,8 +3,9 @@ #include #include +#include +#include #include -#include #include #include #include @@ -13,7 +14,7 @@ // Helper function to handle FITS errors void checkFitsStatus(int status, const std::string& errorMessage) { - if (status) { + if (status != 0) { fits_report_error(stderr, status); throw std::runtime_error( errorMessage + " CFITSIO error code: " + std::to_string(status)); @@ -23,7 +24,8 @@ void checkFitsStatus(int status, const std::string& errorMessage) { cv::Mat readFitsToMat(const std::filesystem::path& filepath) { fitsfile* fptr; // FITS file pointer int status = 0; // CFITSIO status value MUST be initialized to zero - int bitpix, naxis; + int bitpix; + int naxis; long naxes[3] = {1, 1, 1}; // Open the FITS file @@ -43,7 +45,8 @@ cv::Mat readFitsToMat(const std::filesystem::path& filepath) { // Prepare to read the image data std::vector fpixel(naxis, 1); // First pixel to read - long nelements = width * height; // Number of pixels to read + long nelements = + static_cast(width * height); // Number of pixels to read // Prepare the cv::Mat container cv::Mat image; @@ -55,7 +58,7 @@ cv::Mat readFitsToMat(const std::filesystem::path& filepath) { // Read the image data if (fits_read_pix( fptr, CV_MAKETYPE(depth, 1) == CV_16U ? TUSHORT : TBYTE, - &fpixel[0], nelements, NULL, image.data, NULL, &status)) { + &fpixel[0], nelements, nullptr, image.data, nullptr, &status)) { checkFitsStatus(status, "Cannot read FITS image data"); } } else if (naxis == 3 && naxes[2] == 3) { // RGB image @@ -66,8 +69,8 @@ cv::Mat readFitsToMat(const std::filesystem::path& filepath) { fpixel[2] = i + 1; // Set the correct channel (plane) if (fits_read_pix(fptr, CV_MAKETYPE(depth, 1) == CV_16U ? TUSHORT : TBYTE, - &fpixel[0], nelements, NULL, channels[i].data, - NULL, &status)) { + &fpixel[0], nelements, nullptr, channels[i].data, + nullptr, &status)) { checkFitsStatus(status, "Cannot read FITS image data for channel " + std::to_string(i)); @@ -134,15 +137,123 @@ void writeMatToFits(const cv::Mat& image, } // Convert cv::Mat to Base64 string -std::string matToBase64(const cv::Mat& image, const std::string& imgFormat) { +auto matToBase64(const cv::Mat& image, + const std::string& imgFormat) -> std::string { std::vector buf; cv::imencode(imgFormat, image, buf); - auto base64_encoded = base64_encode(buf.data(), buf.size()); - return base64_encoded; + auto base64Encoded = base64_encode(buf.data(), buf.size()); + return base64Encoded; } // Convert FITS file to Base64 string -std::string fitsToBase64(const std::filesystem::path& filepath) { +auto fitsToBase64(const std::filesystem::path& filepath) -> std::string { cv::Mat image = readFitsToMat(filepath); return matToBase64(image, ".png"); } + +auto readFitsHeadForDevName(const std::string& filename) + -> std::optional { + fitsfile* fptr; + int status = 0; + char card[FLEN_CARD]; + + fits_open_file(&fptr, filename.c_str(), READONLY, &status); + if (status != 0) { + fits_report_error(stderr, status); + return std::nullopt; + } + + int nkeys; + fits_get_hdrspace(fptr, &nkeys, nullptr, &status); + std::optional devname; + + for (int i = 1; i <= nkeys; i++) { + fits_read_record(fptr, i, card, &status); + std::string s = card; + + if (s.find("INSTRUME") != std::string::npos) { + size_t a = s.find('\''); + size_t b = s.rfind('\''); + if (a != std::string::npos && b != std::string::npos && a != b) { + devname = s.substr(a + 1, b - a - 1); + } + } + } + + fits_close_file(fptr, &status); + if (status != 0) { + fits_report_error(stderr, status); + return std::nullopt; + } + + return devname; +} + +auto readFits(const std::string& fileName, cv::Mat& image) -> int { + fitsfile* fptr; + int status = 0; + int bitpix; + int naxis; + long naxes[2]; + std::unique_ptr array; + + if (fits_open_file(&fptr, fileName.c_str(), READONLY, &status)) { + return status; + } + + if (fits_get_img_param(fptr, 2, &bitpix, &naxis, naxes, &status)) { + return status; + } + + if (naxis != 2) { + return -1; + } + + long nelements = naxes[0] * naxes[1]; + array = std::make_unique(nelements); + if (fits_read_img(fptr, TUSHORT, 1, nelements, nullptr, array.get(), + nullptr, &status)) { + return status; + } + + if (bitpix == 16) { + image = cv::Mat(naxes[1], naxes[0], CV_16U, array.get()).clone(); + } else if (bitpix == 8) { + image = cv::Mat(naxes[1], naxes[0], CV_8U, array.get()).clone(); + } + + fits_close_file(fptr, &status); + return status; +} + +auto readFits_(const std::string& fileName, cv::Mat& image) -> int { + fitsfile* fptr; + int status = 0; + + fits_open_file(&fptr, fileName.c_str(), READONLY, &status); + if (status != 0) { + fits_report_error(stderr, status); + return status; + } + + int bitpix; + int naxis; + long naxes[2] = {1, 1}; + fits_get_img_param(fptr, 2, &bitpix, &naxis, naxes, &status); + if (status != 0) { + fits_report_error(stderr, status); + return status; + } + + long fpixel[2] = {1, 1}; + image.create(naxes[1], naxes[0], CV_32F); + fits_read_pix(fptr, TFLOAT, fpixel, naxes[0] * naxes[1], nullptr, + image.data, nullptr, &status); + if (status != 0) { + fits_report_error(stderr, status); + return status; + } + + fits_close_file(fptr, &status); + return status; +} diff --git a/modules/lithium.image/src/solver.cpp b/modules/lithium.image/src/solver.cpp new file mode 100644 index 00000000..3f602c48 --- /dev/null +++ b/modules/lithium.image/src/solver.cpp @@ -0,0 +1,107 @@ +#include +#include +#include +#include +#include +#include + +#include "solver.hpp" + +#include + +auto findStarsByStellarSolver(bool AllStars, bool runHFR) -> std::vector { + Tools tempTool; + + LoadFitsResult result; + + std::vector stars; + + result = loadFits("/dev/shm/ccd_simulator.fits"); + + if (!result.success) { + std::cerr << "Error in loading FITS file" << std::endl; + return stars; + } + + FITSImage::Statistic imageStats = result.imageStats; + uint8_t* imageBuffer = result.imageBuffer; + stars = tempTool.FindStarsByStellarSolver_(AllStars, imageStats, imageBuffer, runHFR); + return stars; +} + +std::vector FindStarsByStellarSolver_(bool AllStars, const FITSImage::Statistic& imagestats, const uint8_t* imageBuffer, bool runHFR) { + StellarSolver solver(imagestats, imageBuffer); + // 配置solver参数 + SSolver::Parameters parameters; + + // 设置参数 + parameters.apertureShape = SSolver::SHAPE_CIRCLE; + parameters.autoDownsample = true; + parameters.clean = 1; + parameters.clean_param = 1; + parameters.convFilterType = SSolver::CONV_GAUSSIAN; + parameters.deblend_contrast = 0.004999999888241291; + parameters.deblend_thresh = 32; + parameters.description = "Default focus star-extraction."; + parameters.downsample = 1; + parameters.fwhm = 1; + parameters.inParallel = true; + parameters.initialKeep = 250; + parameters.keepNum = 100; + parameters.kron_fact = 2.5; + parameters.listName = "1-Focus-Default"; + parameters.logratio_tokeep = 20.72326583694641; + parameters.logratio_tosolve = 20.72326583694641; + parameters.logratio_totune = 13.815510557964274; + parameters.magzero = 20; + parameters.maxEllipse = 1.5; + parameters.maxSize = 10; + parameters.maxwidth = 180; + parameters.minSize = 0; + parameters.minarea = 20; + parameters.minwidth = 0.1; + parameters.multiAlgorithm = SSolver::MULTI_AUTO; + parameters.partition = true; + parameters.r_min = 5; + parameters.removeBrightest = 10; + parameters.removeDimmest = 20; + parameters.resort = true; + parameters.saturationLimit = 90; + parameters.search_parity = 15; + parameters.solverTimeLimit = 600; + parameters.subpix = 5; + + solver.setLogLevel(SSolver::LOG_ALL); + solver.setSSLogLevel(SSolver::LOG_NORMAL); + + solver.setProperty("ExtractorType", SSolver::EXTRACTOR_INTERNAL); + solver.setProperty("ProcessType", SSolver::EXTRACT); + solver.setParameterProfile(SSolver::Parameters::DEFAULT); + + solver.setParameters(parameters); + + if (AllStars) { + solver.setParameterProfile(SSolver::Parameters::ALL_STARS); + } + + // 进行星点检测 + bool success = solver.extract(runHFR); + if (!success) { + std::cerr << "Star extraction failed." << std::endl; + } + std::cout << "Success extract: " << success << std::endl; + + std::vector stars = solver.getStarList(); + + // 输出检测到的星点信息 + std::cout << "Detected " << stars.size() << " stars." << std::endl; + for (const auto& star : stars) { + std::cout << "Star at (" << star.x << ", " << star.y << ") with HFR: " << star.HFR << std::endl; + } + + return stars; +} + +void StellarSolverLogOutput(const std::string& text) { + std::cout << "StellarSolver LogOutput: " << text << std::endl; +} diff --git a/modules/lithium.image/xmake.lua b/modules/lithium.image/xmake.lua new file mode 100644 index 00000000..133879a0 --- /dev/null +++ b/modules/lithium.image/xmake.lua @@ -0,0 +1,76 @@ +set_project("lithium.image") +set_version("1.0.0") +set_xmakever("2.5.1") + +-- Set the C++ standard +set_languages("cxx20") + +-- Add required packages +add_requires("opencv >=4.0", "cfitsio", "loguru", "pthread") + +-- Define libraries +local lithium_image_libs = { + "atom-component", + "atom-error", + "opencv", + "cfitsio", + "loguru", + "pthread" +} + +-- Sources and Headers +local lithium_image_sources = { + "_main.cpp", + "_component.cpp", + "src/base64.cpp", + "src/convolve.cpp", + "src/debayer.cpp", + "src/fitsio.cpp", + -- "src/fitskeyword.cpp", + "src/hfr.cpp", + "src/hist.cpp", + "src/stack.cpp", + "src/stretch.cpp", + "src/imgutils.cpp" +} + +local lithium_image_headers = { + "include/debayer.hpp", + "include/fitsio.hpp", + -- "include/fitskeyword.hpp", + "include/hfr.hpp", + "include/hist.hpp", + "include/stack.hpp", + "include/stretch.hpp", + "include/imgutils.hpp" +} + +local lithium_image_private_headers = { + "_component.hpp" +} + +-- Object Library +target("lithium.image_object") + set_kind("object") + add_files(table.unpack(lithium_image_sources)) + add_headerfiles(table.unpack(lithium_image_headers)) + add_headerfiles(table.unpack(lithium_image_private_headers)) + add_packages(table.unpack(lithium_image_libs)) +target_end() + +-- Shared Library +target("lithium.image") + set_kind("shared") + add_deps("lithium.image_object") + add_files(table.unpack(lithium_image_sources)) + add_headerfiles(table.unpack(lithium_image_headers)) + add_headerfiles(table.unpack(lithium_image_private_headers)) + add_packages(table.unpack(lithium_image_libs)) + add_includedirs(".", "include", "$(opencv includedirs)") + set_targetdir("$(buildir)/lib") + set_installdir("$(installdir)/lib") + set_version("1.0.0", {build = "%Y%m%d%H%M"}) + on_install(function (target) + os.cp(target:targetfile(), path.join(target:installdir(), "lib")) + end) +target_end() diff --git a/modules/lithium.indiserver/CMakeLists.txt b/modules/lithium.indiserver/CMakeLists.txt index 13dfd815..68ea8c4d 100644 --- a/modules/lithium.indiserver/CMakeLists.txt +++ b/modules/lithium.indiserver/CMakeLists.txt @@ -14,7 +14,7 @@ set(CMAKE_CXX_STANDARD 20) set(SOURCE_FILES src/indiserver.cpp src/collection.cpp - src/container.cpp + src/iconnector.cpp _component.cpp _main.cpp @@ -27,11 +27,16 @@ set(LIBS tinyxml2 atom-component atom-error + atom-utils ) +if (WIN32) + set(LIBS ${LIBS} ws2_32 version iphlpapi) +endif() + # Create the module library add_library(lithium.indiserver SHARED ${SOURCE_FILES} ${HEADER_FILES}) target_link_libraries(lithium.indiserver PUBLIC ${LIBS}) # Include directories -target_include_directories(lithium.indiserver PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_include_directories(lithium.indiserver PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src) diff --git a/modules/lithium.indiserver/_component.cpp b/modules/lithium.indiserver/_component.cpp index 72f68404..4d558a13 100644 --- a/modules/lithium.indiserver/_component.cpp +++ b/modules/lithium.indiserver/_component.cpp @@ -14,12 +14,14 @@ compatibility **************************************************/ #include "_component.hpp" +#include +#include "iconnector.hpp" #include "indiserver.hpp" #include "atom/log/loguru.hpp" INDIServerComponent::INDIServerComponent(const std::string& name) - : Component(name), m_manager(std::make_shared()) { + : Component(name), m_manager(std::make_shared(std::make_unique())) { LOG_F(INFO, "INDIServerComponent Constructed"); def("start", &INDIManager::startServer, m_manager, "astro", diff --git a/modules/lithium.indiserver/include/collection.hpp b/modules/lithium.indiserver/include/collection.hpp deleted file mode 100644 index bd634a52..00000000 --- a/modules/lithium.indiserver/include/collection.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef LITHIUM_INDISERVER_COLLECTION_HPP -#define LITHIUM_INDISERVER_COLLECTION_HPP - -#include "atom/type/json.hpp" -using json = nlohmann::json; - -#include "container.hpp" - -#include -#include -#include - -class INDIDriverCollection { -public: - /** - * @brief 解析所有INDI驱动程序 - */ - bool parseDrivers(const std::string &path); - - /** - * @brief 解析自定义的INDI驱动程序 - * @param drivers JSON格式的自定义驱动程序数据 - */ - bool parseCustomDrivers(const json &drivers); - - /** - * @brief 清除自定义的INDI驱动程序 - */ - void clearCustomDrivers(); - - /** - * @brief 根据标签获取INDI设备容器 - * @param label 设备的标签 - * @return 指向INDIDeviceContainer的shared_ptr - */ - std::shared_ptr getByLabel(const std::string &label); - - /** - * @brief 根据名称获取INDI设备容器 - * @param name 设备的名称 - * @return 指向INDIDeviceContainer的shared_ptr - */ - std::shared_ptr getByName(const std::string &name); - - /** - * @brief 根据二进制文件名获取INDI设备容器 - * @param binary 二进制文件名 - * @return 指向INDIDeviceContainer的shared_ptr - */ - std::shared_ptr getByBinary(const std::string &binary); - - /** - * @brief 获取所有INDI设备的家族关系 - * @return 包含家族关系的映射表,键为家族名称,值为设备名称的向量 - */ -#if ENABLE_FASTHASH - emhash8::HashMap> getFamilies(); -#else - std::unordered_map> getFamilies(); -#endif - -private: - std::string path; ///< INDI驱动程序的路径 - std::vector files; ///< INDI驱动程序文件列表 - std::vector> - drivers; ///< INDI驱动程序容器列表 -}; - -#endif diff --git a/modules/lithium.indiserver/include/container.hpp b/modules/lithium.indiserver/include/container.hpp deleted file mode 100644 index 561001bf..00000000 --- a/modules/lithium.indiserver/include/container.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef LITHIUM_INDISERVER_CONTAINER_HPP -#define LITHIUM_INDISERVER_CONTAINER_HPP - -#include - -class INDIDeviceContainer -{ -public: - std::string name; - std::string label; - std::string version; - std::string binary; - std::string family; - std::string skeleton; - bool custom; - - explicit INDIDeviceContainer(const std::string &name, const std::string &label, const std::string &version, - const std::string &binary, const std::string &family, - const std::string &skeleton = "", bool custom = false); -}; - -#endif diff --git a/modules/lithium.indiserver/include/indiserver.hpp b/modules/lithium.indiserver/include/indiserver.hpp deleted file mode 100644 index ee5929d7..00000000 --- a/modules/lithium.indiserver/include/indiserver.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef LITHIUM_INDISERVER_HPP -#define LITHIUM_INDISERVER_HPP - -#include "collection.hpp" - -class INDIManager { -public: - explicit INDIManager(const std::string &hst = "localhost", int prt = 7624, - const std::string &cfg = "", - const std::string &dta = "/usr/share/hydrogen", - const std::string &fif = "/tmp/hydrogenFIFO"); - - ~INDIManager(); - - static std::shared_ptr createShared( - const std::string &hst = "localhost", int prt = 7624, - const std::string &cfg = "", - const std::string &dta = "/usr/share/hydrogen", - const std::string &fif = "/tmp/hydrogenFIFO"); - - bool startServer(); - - bool stopServer(); - - bool isRunning(); - - bool isInstalled(); - - bool startDriver(std::shared_ptr driver); - - bool stopDriver(std::shared_ptr driver); - - bool setProp(const std::string &dev, const std::string &prop, - const std::string &element, const std::string &value); - - std::string getProp(const std::string &dev, const std::string &prop, - const std::string &element); - - std::string getState(const std::string &dev, const std::string &prop); - -#if ENABLE_FASTHASH - emhash8::HashMap> - getRunningDrivers(); -#else - std::unordered_map> - getRunningDrivers(); -#endif - -#if ENABLE_FASTHASH - static std::vector> getDevices(); -#else - static std::vector> - getDevices(); -#endif - -private: - std::string host; ///< INDI服务器的主机名 - int port; ///< INDI服务器的端口号 - std::string config_path; ///< INDI配置文件路径 - std::string data_path; ///< INDI驱动程序路径 - std::string fifo_path; ///< INDI FIFO路径 -#if ENABLE_FASTHASH - emhash8::HashMap> - running_drivers; -#else - std::unordered_map> - running_drivers; ///< 正在运行的驱动程序列表 -#endif -}; - -#endif // LITHIUM_INDISERVER_HPP diff --git a/modules/lithium.indiserver/src/collection.cpp b/modules/lithium.indiserver/src/collection.cpp index f617d2a1..91165400 100644 --- a/modules/lithium.indiserver/src/collection.cpp +++ b/modules/lithium.indiserver/src/collection.cpp @@ -1,109 +1,84 @@ -/* - * INDI_driver.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-3-29 - -Description: INDI Web Driver - -**************************************************/ - #include "collection.hpp" + +#include #include -#include -#include -#include +#include #include "atom/log/loguru.hpp" -#include "tinyxml2/tinyxml2.h" - - -#ifdef _WIN32 -#include -#endif +#include "atom/type/json.hpp" namespace fs = std::filesystem; -bool INDIDriverCollection::parseDrivers(const std::string &path) { +auto INDIDriverCollection::parseDrivers(const std::string& path) -> bool { if (!fs::exists(path) || !fs::is_directory(path)) { LOG_F(ERROR, "INDI driver path {} does not exist", path); return false; } - for (const auto &entry : fs::directory_iterator(path)) { - const std::string &fname = entry.path().filename().string(); + for (const auto& entry : fs::directory_iterator(path)) { + const auto& fname = entry.path().filename().string(); if (fname.ends_with(".xml") && fname.find("_sk") == std::string::npos) { - files.push_back(entry.path().string()); + files_.push_back(entry.path().string()); } } - for (const std::string &fname : files) { + for (const auto& fname : files_) { tinyxml2::XMLDocument doc; if (doc.LoadFile(fname.c_str()) != tinyxml2::XML_SUCCESS) { LOG_F(ERROR, "Error loading file {}", fname); continue; } - tinyxml2::XMLElement *root = doc.FirstChildElement("root"); - for (tinyxml2::XMLElement *group = root->FirstChildElement("devGroup"); - group; group = group->NextSiblingElement("devGroup")) { - const std::string &family = group->Attribute("group"); - for (tinyxml2::XMLElement *device = - group->FirstChildElement("device"); - device; device = device->NextSiblingElement("device")) { - const std::string &label = device->Attribute("label"); - const std::string &skel = device->Attribute("skel"); - const std::string &name = + auto* root = doc.FirstChildElement("root"); + for (auto* group = root->FirstChildElement("devGroup"); + group != nullptr; group = group->NextSiblingElement("devGroup")) { + const auto& family = group->Attribute("group"); + for (auto* device = group->FirstChildElement("device"); + device != nullptr; + device = device->NextSiblingElement("device")) { + const auto& label = device->Attribute("label"); + const auto& skel = device->Attribute("skel"); + const auto& name = device->FirstChildElement("driver")->Attribute("name"); - const std::string &binary = + const auto& binary = device->FirstChildElement("driver")->GetText(); - const std::string &version = + const auto& version = device->FirstChildElement("version")->GetText(); - drivers.push_back(std::make_shared( + drivers_.push_back(std::make_shared( name, label, version, binary, family, skel)); } } } - // Sort drivers by label - std::sort(drivers.begin(), drivers.end(), - [](const std::shared_ptr a, - const std::shared_ptr b) { - return a->label < b->label; - }); + std::sort(drivers_.begin(), drivers_.end(), + [](const auto& a, const auto& b) { return a->label < b->label; }); return true; } -bool INDIDriverCollection::parseCustomDrivers(const json &drivers) { - for (const auto &custom : drivers) { - const std::string &name = custom["name"].get(); - const std::string &label = custom["label"].get(); - const std::string &version = custom["version"].get(); - const std::string &binary = custom["exec"].get(); - const std::string &family = custom["family"].get(); - this->drivers.push_back(std::make_shared( +auto INDIDriverCollection::parseCustomDrivers(const json& drivers) -> bool { + for (const auto& custom : drivers) { + const auto& name = custom["name"].get(); + const auto& label = custom["label"].get(); + const auto& version = custom["version"].get(); + const auto& binary = custom["exec"].get(); + const auto& family = custom["family"].get(); + drivers_.push_back(std::make_shared( name, label, version, binary, family, "", true)); } return true; } void INDIDriverCollection::clearCustomDrivers() { - drivers.erase( - std::remove_if(drivers.begin(), drivers.end(), - [](const std::shared_ptr driver) { - return driver->custom == true; - }), - drivers.end()); + drivers_.erase( + std::remove_if(drivers_.begin(), drivers_.end(), + [](const auto& driver) { return driver->custom; }), + drivers_.end()); } -std::shared_ptr INDIDriverCollection::getByLabel( - const std::string &label) { - for (auto driver : drivers) { +auto INDIDriverCollection::getByLabel(const std::string& label) + -> std::shared_ptr { + for (const auto& driver : drivers_) { if (driver->label == label) { return driver; } @@ -112,9 +87,9 @@ std::shared_ptr INDIDriverCollection::getByLabel( return nullptr; } -std::shared_ptr INDIDriverCollection::getByName( - const std::string &name) { - for (auto driver : drivers) { +auto INDIDriverCollection::getByName(const std::string& name) + -> std::shared_ptr { + for (const auto& driver : drivers_) { if (driver->name == name) { return driver; } @@ -123,9 +98,9 @@ std::shared_ptr INDIDriverCollection::getByName( return nullptr; } -std::shared_ptr INDIDriverCollection::getByBinary( - const std::string &binary) { - for (auto driver : drivers) { +auto INDIDriverCollection::getByBinary(const std::string& binary) + -> std::shared_ptr { + for (const auto& driver : drivers_) { if (driver->binary == binary) { return driver; } @@ -134,20 +109,10 @@ std::shared_ptr INDIDriverCollection::getByBinary( return nullptr; } -#if ENABLE_FASTHASH -emhash8::HashMap> -INDIDriverCollection::getFamilies() -#else -std::unordered_map> -INDIDriverCollection::getFamilies() -#endif -{ -#if ENABLE_FASTHASH - emhash8::HashMap> families; -#else +auto INDIDriverCollection::getFamilies() + -> std::unordered_map> { std::unordered_map> families; -#endif - for (const auto driver : drivers) { + for (const auto& driver : drivers_) { families[driver->family].push_back(driver->label); DLOG_F(INFO, "Family {} contains devices {}", driver->family, driver->label); diff --git a/modules/lithium.indiserver/src/collection.hpp b/modules/lithium.indiserver/src/collection.hpp new file mode 100644 index 00000000..3b0a6ca8 --- /dev/null +++ b/modules/lithium.indiserver/src/collection.hpp @@ -0,0 +1,33 @@ +#ifndef LITHIUM_INDISERVER_COLLECTION_HPP +#define LITHIUM_INDISERVER_COLLECTION_HPP + +#include "container.hpp" + +#include +#include +#include + +#include "atom/type/json_fwd.hpp" +using json = nlohmann::json; + +class INDIDriverCollection { +public: + auto parseDrivers(const std::string& path) -> bool; + auto parseCustomDrivers(const json& drivers) -> bool; + void clearCustomDrivers(); + auto getByLabel(const std::string& label) + -> std::shared_ptr; + auto getByName(const std::string& name) + -> std::shared_ptr; + auto getByBinary(const std::string& binary) + -> std::shared_ptr; + auto getFamilies() + -> std::unordered_map>; + +private: + std::string path_; + std::vector files_; + std::vector> drivers_; +}; + +#endif // LITHIUM_INDISERVER_COLLECTION_HPP diff --git a/modules/lithium.indiserver/src/container.cpp b/modules/lithium.indiserver/src/container.cpp deleted file mode 100644 index 8e669f07..00000000 --- a/modules/lithium.indiserver/src/container.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "container.hpp" - -INDIDeviceContainer::INDIDeviceContainer(const std::string &name, const std::string &label, const std::string &version, - const std::string &binary, const std::string &family, - const std::string &skeleton, bool custom) - : name(name), label(label), version(version), binary(binary), - family(family), skeleton(skeleton), custom(custom) {} diff --git a/modules/lithium.indiserver/src/container.hpp b/modules/lithium.indiserver/src/container.hpp new file mode 100644 index 00000000..ebf37c51 --- /dev/null +++ b/modules/lithium.indiserver/src/container.hpp @@ -0,0 +1,30 @@ +#ifndef LITHIUM_INDISERVER_CONTAINER_HPP +#define LITHIUM_INDISERVER_CONTAINER_HPP + +#include +#include + +class INDIDeviceContainer { +public: + std::string name; + std::string label; + std::string version; + std::string binary; + std::string family; + std::string skeleton; + bool custom; + + INDIDeviceContainer(std::string name, std::string label, + std::string version, std::string binary, + std::string family, std::string skeleton = "", + bool custom = false) + : name(std::move(name)), + label(std::move(label)), + version(std::move(version)), + binary(std::move(binary)), + family(std::move(family)), + skeleton(std::move(skeleton)), + custom(custom) {} +}; + +#endif // LITHIUM_INDISERVER_CONTAINER_HPP diff --git a/modules/lithium.indiserver/src/driverlist.cpp b/modules/lithium.indiserver/src/driverlist.cpp new file mode 100644 index 00000000..7680322f --- /dev/null +++ b/modules/lithium.indiserver/src/driverlist.cpp @@ -0,0 +1,130 @@ +#include "driverlist.hpp" + +#include +#include + +#include "atom/log/loguru.hpp" + +using namespace tinyxml2; +using namespace std::filesystem; + +auto loadXMLFile(const std::string& filename, XMLDocument& doc) -> bool { + if (doc.LoadFile(filename.c_str()) != XML_SUCCESS) { + LOG_F(ERROR, "Unable to load XML file: {}", filename); + return false; + } + return true; +} + +auto parseDriversList(const std::string& filename) -> std::vector { + std::vector devGroups; + XMLDocument doc; + + if (!loadXMLFile(filename, doc)) { + return devGroups; + } + + XMLElement* root = doc.RootElement(); + for (XMLElement* devGroupElem = root->FirstChildElement("devGroup"); + devGroupElem != nullptr; + devGroupElem = devGroupElem->NextSiblingElement("devGroup")) { + DevGroup devGroup; + devGroup.group = devGroupElem->Attribute("group"); + devGroups.push_back(devGroup); + } + + return devGroups; +} + +auto parseDevicesFromPath(const std::string& path, + std::vector& devicesFrom) + -> std::vector { + std::vector devGroups; + + for (const auto& entry : directory_iterator(path)) { + if (entry.path().extension() == ".xml" && + entry.path().filename().string().substr( + entry.path().filename().string().size() - 6) != "sk.xml") { + XMLDocument doc; + + if (!loadXMLFile(entry.path().string(), doc)) { + LOG_F(ERROR, "Unable to load XML file: {}", entry.path()); + continue; + } + + XMLElement* root = doc.RootElement(); + for (XMLElement* devGroupElem = root->FirstChildElement("devGroup"); + devGroupElem != nullptr; + devGroupElem = devGroupElem->NextSiblingElement("devGroup")) { + DevGroup devGroup; + devGroup.group = devGroupElem->Attribute("group"); + + for (XMLElement* deviceElem = + devGroupElem->FirstChildElement("device"); + deviceElem != nullptr; + deviceElem = deviceElem->NextSiblingElement("device")) { + Device device; + device.label = deviceElem->Attribute("label"); + + if (deviceElem->FindAttribute("manufacturer") != nullptr) { + device.manufacturer = + deviceElem->Attribute("manufacturer"); + } + + for (XMLElement* driverElem = + deviceElem->FirstChildElement(); + driverElem != nullptr; + driverElem = driverElem->NextSiblingElement()) { + if (std::string(driverElem->Name()) == "driver") { + device.driverName = driverElem->GetText(); + } else if (std::string(driverElem->Name()) == + "version") { + device.version = driverElem->GetText(); + } + } + devGroup.devices.push_back(device); + devicesFrom.push_back(device); + } + devGroups.push_back(devGroup); + } + } + } + + return devGroups; +} + +auto mergeDeviceGroups(const DriversList& driversListFrom, + const std::vector& devGroupsFromPath) + -> DriversList { + DriversList mergedList = driversListFrom; + + for (auto& devGroupXml : devGroupsFromPath) { + for (auto& devGroupFrom : mergedList.devGroups) { + if (devGroupXml.group == devGroupFrom.group) { + devGroupFrom.devices.insert(devGroupFrom.devices.end(), + devGroupXml.devices.begin(), + devGroupXml.devices.end()); + } + } + } + + return mergedList; +} + +auto readDriversListFromFiles(std::string_view filename, std::string_view path) + -> std::tuple, std::vector> { + DriversList driversListFrom; + std::vector devGroupsFrom; + std::vector devicesFrom; + + if (!exists(path)) { + LOG_F(ERROR, "Folder not found: {}", path); + return {driversListFrom, devGroupsFrom, devicesFrom}; + } + + driversListFrom.devGroups = parseDriversList(filename.data()); + devGroupsFrom = parseDevicesFromPath(path.data(), devicesFrom); + driversListFrom = mergeDeviceGroups(driversListFrom, devGroupsFrom); + + return {driversListFrom, devGroupsFrom, devicesFrom}; +} diff --git a/modules/lithium.indiserver/src/driverlist.hpp b/modules/lithium.indiserver/src/driverlist.hpp new file mode 100644 index 00000000..d993b145 --- /dev/null +++ b/modules/lithium.indiserver/src/driverlist.hpp @@ -0,0 +1,28 @@ +#ifndef LITHIUM_INDISERVER_DRIVERLIST_HPP +#define LITHIUM_INDISERVER_DRIVERLIST_HPP + +#include +#include + +#include "macro.hpp" + +struct Device { + std::string label; + std::string manufacturer; + std::string driverName; + std::string version; +} ATOM_ALIGNAS(128); + +struct DevGroup { + std::string group; + std::vector devices; +} ATOM_ALIGNAS(64); + +struct DriversList { + std::vector devGroups; +} ATOM_ALIGNAS(32); + +auto readDriversListFromFiles(std::string_view filename, std::string_view path) + -> std::tuple, std::vector>; + +#endif diff --git a/modules/lithium.indiserver/src/iconnector.cpp b/modules/lithium.indiserver/src/iconnector.cpp new file mode 100644 index 00000000..45a529b4 --- /dev/null +++ b/modules/lithium.indiserver/src/iconnector.cpp @@ -0,0 +1,250 @@ +/* + * manager.cpp + * + * Copyright (C) 2023-2024 Max Qian + */ + +/************************************************* + +Date: 2023-3-29 + +Description: INDI Device Manager + +**************************************************/ + +#include "iconnector.hpp" +#include "container.hpp" + +#include + +#include "atom/error/exception.hpp" +#include "atom/io/io.hpp" +#include "atom/log/loguru.hpp" +#include "atom/system/command.hpp" +#include "atom/system/process.hpp" +#include "atom/system/software.hpp" + +INDIConnector::INDIConnector(const std::string &hst, int prt, + const std::string &cfg, const std::string &dta, + const std::string &fif) + : host_(hst) { + host_ = hst; + port_ = prt; + config_path_ = cfg; + data_path_ = dta; + fifo_path_ = fif; +} + +auto INDIConnector::startServer() -> bool { + // If there is an INDI server running, just kill it + // Surely, this is not the best way to do this, but it works. + // If there is no server running, start it. + if (isRunning()) { + stopServer(); + } + DLOG_F(INFO, "Deleting fifo pipe at: {}", fifo_path_); + if (!atom::io::removeFile(fifo_path_)) { + LOG_F(ERROR, "Failed to delete fifo pipe at: {}", fifo_path_); + return false; + } + std::string cmd = "indiserver -p " + std::to_string(port_) + + " -m 100 -v -f " + fifo_path_ + + " > /tmp/indiserver.log 2>&1"; + try { + if (atom::system::executeCommand(cmd, false) != "") { + LOG_F(ERROR, "Failed to execute command: {}", cmd); + return false; + } + } catch (const atom::error::RuntimeError &e) { + LOG_F(ERROR, "Failed to execute command: {} with {}", cmd, e.what()); + return false; + } + // Just start the server without driver + DLOG_F(INFO, "Started INDI server on port {}", port_); + return true; +} + +auto INDIConnector::stopServer() -> bool { + if (!isRunning()) { + DLOG_F(WARNING, "INDI server is not running"); + return true; + } + std::string cmd = "killall indiserver >/dev/null 2>&1"; + DLOG_F(INFO, "Terminating INDI server"); + try { + if (atom::system::executeCommand(cmd, false) != "") { + LOG_F(ERROR, "Failed to execute command: {}", cmd); + return false; + } + } catch (const std::exception &e) { + LOG_F(ERROR, "Failed to execute command: {} with {}", cmd, e.what()); + return false; + } + DLOG_F(INFO, "INDI server terminated successfully"); + return true; +} + +auto INDIConnector::isRunning() -> bool { + // A little bit hacky, but it works. We need a dynamic way to check if the + // server is running Not stupid like this :P + std::string processName = "indiserver"; + return atom::system::isProcessRunning(processName); +} + +auto INDIConnector::isInstalled() -> bool { + return atom::system::checkSoftwareInstalled("hydrogenserver"); +} + +auto INDIConnector::startDriver( + const std::shared_ptr &driver) -> bool { + std::string cmd = "start " + driver->label; + if (driver->skeleton != "") { + cmd += " -s \"" + driver->skeleton + "\""; + } + cmd = std::regex_replace(cmd, std::regex("\""), "\\\""); + std::string fullCmd = "echo \"" + cmd + "\" > " + fifo_path_; + DLOG_F(INFO, "Cmd: {}", fullCmd); + try { + if (atom::system::executeCommand(fullCmd, false) != "") { + LOG_F(ERROR, "Failed to execute command: {}", fullCmd); + return false; + } + } catch (const atom::error::RuntimeError &e) { + LOG_F(ERROR, "Failed to execute command: {} with {}", fullCmd, + e.what()); + return false; + } + running_drivers_.emplace(driver->label, driver); + return true; +} + +auto INDIConnector::stopDriver( + const std::shared_ptr &driver) -> bool { + std::string cmd = "stop " + driver->binary; + if (driver->binary.find('@') == std::string::npos) { + cmd += " -n \"" + driver->label + "\""; + } + cmd = std::regex_replace(cmd, std::regex("\""), "\\\""); + std::string fullCmd = "echo \"" + cmd + "\" > " + fifo_path_; + DLOG_F(INFO, "Cmd: {}", fullCmd); + try { + if (atom::system::executeCommand(fullCmd, false) != "") { + LOG_F(ERROR, "Failed to execute command: {}", fullCmd); + return false; + } + } catch (const atom::error::RuntimeError &e) { + LOG_F(ERROR, "Failed to execute command: {} with {}", fullCmd, + e.what()); + return false; + } + DLOG_F(INFO, "Stop running driver: {}", driver->label); + running_drivers_.erase(driver->label); + return true; +} + +auto INDIConnector::setProp(const std::string &dev, const std::string &prop, + const std::string &element, + const std::string &value) -> bool { + std::stringstream sss; + sss << "indi_setprop " << dev << "." << prop << "." << element << "=" + << value; + std::string cmd = sss.str(); + DLOG_F(INFO, "Cmd: {}", cmd); + try { + if (atom::system::executeCommand(cmd, false) != "") { + LOG_F(ERROR, "Failed to execute command: {}", cmd); + return false; + } + } catch (const atom::error::RuntimeError &e) { + LOG_F(ERROR, "Failed to execute command: {} with {}", cmd, e.what()); + return false; + } + DLOG_F(INFO, "Set property: {}.{} to {}", dev, prop, value); + return true; +} + +auto INDIConnector::getProp(const std::string &dev, const std::string &prop, + const std::string &element) -> std::string { + std::stringstream sss; +#if ENABLE_INDI + sss << "indi_getprop " << dev << "." << prop << "." << element; +#else + sss << "indi_getprop " << dev << "." << prop << "." << element; +#endif + std::string cmd = sss.str(); + try { + std::string output = atom::system::executeCommand(cmd, false); + size_t equalsPos = output.find('='); + if (equalsPos != std::string::npos && equalsPos + 1 < output.length()) { + return output.substr(equalsPos + 1, + output.length() - equalsPos - 2); + } + } catch (const atom::error::RuntimeError &e) { + LOG_F(ERROR, "Failed to execute command: {} with {}", cmd, e.what()); + } + return ""; +} + +auto INDIConnector::getState(const std::string &dev, + const std::string &prop) -> std::string { + return getProp(dev, prop, "_STATE"); +} + +#if ENABLE_FASTHASH +emhash8::HashMap> +INDIConnector::getRunningDrivers() +#else +auto INDIConnector::getRunningDrivers() + -> std::unordered_map> +#endif +{ + return running_drivers_; +} + +#if ENABLE_FASTHASH +std::vector> +INDIConnector::getDevices() +#else +auto INDIConnector::getDevices() + -> std::vector> +#endif +{ +#if ENABLE_FASTHASH + std::vector> devices; +#else + std::vector> devices; +#endif + std::string cmd = "indi_getprop *.CONNECTION.CONNECT"; + std::array buffer{}; + std::string result; + std::unique_ptr pipe(popen(cmd.c_str(), "r"), + pclose); + if (!pipe) { + LOG_F(ERROR, "Failed to execute command: {}", cmd); + THROW_RUNTIME_ERROR("popen() failed!"); + } + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { + result += buffer.data(); + } + std::vector lines = {"", ""}; + for (char token : result) { + if (token == '\n') { + std::unordered_map device; + std::stringstream sss(lines[0]); + std::string item; + while (getline(sss, item, '.')) { + device["device"] = item; + } + device["connected"] = (lines[1] == "On") ? "true" : "false"; + devices.push_back(device); + lines = {"", ""}; + } else if (token == '=') { + lines[1] = lines[1].substr(0, lines[1].length() - 1); + } else if (token == ' ') { + continue; + } else { + lines[(lines[0] == "") ? 0 : 1] += token; + } + } + return devices; +} diff --git a/modules/lithium.indiserver/src/iconnector.hpp b/modules/lithium.indiserver/src/iconnector.hpp new file mode 100644 index 00000000..dcc151b2 --- /dev/null +++ b/modules/lithium.indiserver/src/iconnector.hpp @@ -0,0 +1,49 @@ +#ifndef LITHIUM_INDISERVER_CONNECTOR_HPP +#define LITHIUM_INDISERVER_CONNECTOR_HPP + +#include +#include +#include + +#include "addon/template/connector.hpp" + +class INDIConnector : public Connector { +public: + INDIConnector(const std::string& hst = "localhost", int prt = 7624, const std::string& cfg = "", + const std::string& dta = "/usr/share/indi", const std::string& fif = "/tmp/indi.fifo"); + ~INDIConnector() override = default; + auto startServer() -> bool override; + auto stopServer() -> bool override; + auto isRunning() -> bool override; + auto isInstalled() -> bool; + auto startDriver(const std::shared_ptr& driver) + -> bool override; + auto stopDriver(const std::shared_ptr& driver) + -> bool override; + auto setProp(const std::string& dev, const std::string& prop, + const std::string& element, + const std::string& value) -> bool override; + auto getProp(const std::string& dev, const std::string& prop, + const std::string& element) -> std::string override; + auto getState(const std::string& dev, + const std::string& prop) -> std::string override; + auto getRunningDrivers() + -> std::unordered_map< + std::string, std::shared_ptr> override; + auto getDevices() -> std::vector> override; +private: + std::string host_; ///< INDI服务器的主机名 + int port_; ///< INDI服务器的端口号 + std::string config_path_; ///< INDI配置文件路径 + std::string data_path_; ///< INDI驱动程序路径 + std::string fifo_path_; ///< INDI FIFO路径 +#if ENABLE_FASTHASH + emhash8::HashMap> + running_drivers_; +#else + std::unordered_map> + running_drivers_; ///< 正在运行的驱动程序列表 +#endif +}; + +#endif // LITHIUM_INDISERVER_CONNECTOR_HPP diff --git a/modules/lithium.indiserver/src/indiserver.cpp b/modules/lithium.indiserver/src/indiserver.cpp index a73e4529..0c33b3eb 100644 --- a/modules/lithium.indiserver/src/indiserver.cpp +++ b/modules/lithium.indiserver/src/indiserver.cpp @@ -1,258 +1,50 @@ -/* - * manager.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-3-29 - -Description: INDI Device Manager - -**************************************************/ - #include "indiserver.hpp" -#include - -#include "atom/error/exception.hpp" -#include "atom/io/io.hpp" -#include "atom/log/loguru.hpp" -#include "atom/system/command.hpp" -#include "atom/system/system.hpp" - - -INDIManager::INDIManager(const std::string &hst, int prt, - const std::string &cfg, const std::string &dta, - const std::string &fif) { - host = hst; - port = prt; - config_path = cfg; - data_path = dta; - fifo_path = fif; -} +#include "atom/system/software.hpp" -INDIManager::~INDIManager() {} +INDIManager::INDIManager(std::unique_ptr conn) + : connector(std::move(conn)) {} -std::shared_ptr INDIManager::createShared(const std::string &hst, - int prt, - const std::string &cfg, - const std::string &dta, - const std::string &fif) { - return std::make_shared(hst, prt, cfg, dta, fif); -} +INDIManager::~INDIManager() = default; -bool INDIManager::startServer() { - // If there is an INDI server running, just kill it - // Surely, this is not the best way to do this, but it works. - // If there is no server running, start it. - if (isRunning()) { - stopServer(); - } - DLOG_F(INFO, "Deleting fifo pipe at: {}", fifo_path); - if (!atom::io::removeFile(fifo_path)) { - LOG_F(ERROR, "Failed to delete fifo pipe at: {}", fifo_path); - return false; - } - std::string cmd = "indiserver -p " + std::to_string(port) + - " -m 100 -v -f " + fifo_path + - " > C:\\tmp\\indiserver.log 2>&1"; - try { - if (atom::system::executeCommand(cmd, false) != "") { - LOG_F(ERROR, "Failed to execute command: {}", cmd); - return false; - } - } catch (const atom::error::RuntimeError &e) { - LOG_F(ERROR, "Failed to execute command: {} with {}", cmd, e.what()); - return false; - } - // Just start the server without driver - DLOG_F(INFO, "Started INDI server on port {}", port); - return true; -} +auto INDIManager::startServer() -> bool { return connector->startServer(); } -bool INDIManager::stopServer() { - if (!isRunning()) { - DLOG_F(WARNING, "INDI server is not running"); - return true; - } - std::string cmd = "killall indiserver >/dev/null 2>&1"; - DLOG_F(INFO, "Terminating INDI server"); - try { - if (atom::system::executeCommand(cmd, false) != "") { - LOG_F(ERROR, "Failed to execute command: {}", cmd); - return false; - } - } catch (const std::exception &e) { - LOG_F(ERROR, "Failed to execute command: {} with {}", cmd, e.what()); - return false; - } - DLOG_F(INFO, "INDI server terminated successfully"); - return true; -} +auto INDIManager::stopServer() -> bool { return connector->stopServer(); } -bool INDIManager::isRunning() { - // A little bit hacky, but it works. We need a dynamic way to check if the - // server is running Not stupid like this :P - std::string processName = "indiserver"; - return atom::system::isProcessRunning(processName); -} +auto INDIManager::isRunning() -> bool { return connector->isRunning(); } -bool INDIManager::isInstalled() { +auto INDIManager::isInstalled() -> bool { return atom::system::checkSoftwareInstalled("hydrogenserver"); } -bool INDIManager::startDriver(std::shared_ptr driver) { - std::string cmd = "start " + driver->binary; - if (driver->skeleton != "") { - cmd += " -s \"" + driver->skeleton + "\""; - } - cmd = std::regex_replace(cmd, std::regex("\""), "\\\""); - std::string full_cmd = "echo \"" + cmd + "\" > " + fifo_path; - DLOG_F(INFO, "Cmd: {}", full_cmd); - try { - if (atom::system::executeCommand(full_cmd, false) != "") { - LOG_F(ERROR, "Failed to execute command: {}", full_cmd); - return false; - } - } catch (const atom::error::RuntimeError &e) { - LOG_F(ERROR, "Failed to execute command: {} with {}", full_cmd, - e.what()); - return false; - } - running_drivers.emplace(driver->label, driver); - return true; +auto INDIManager::startDriver( + const std::shared_ptr& driver) -> bool { + return connector->startDriver(driver); } -bool INDIManager::stopDriver(std::shared_ptr driver) { - std::string cmd = "stop " + driver->binary; - if (driver->binary.find("@") == std::string::npos) { - cmd += " -n \"" + driver->label + "\""; - } - cmd = std::regex_replace(cmd, std::regex("\""), "\\\""); - std::string full_cmd = "echo \"" + cmd + "\" > " + fifo_path; - DLOG_F(INFO, "Cmd: {}", full_cmd); - try { - if (atom::system::executeCommand(full_cmd, false) != "") { - LOG_F(ERROR, "Failed to execute command: {}", full_cmd); - return false; - } - } catch (const atom::error::RuntimeError &e) { - LOG_F(ERROR, "Failed to execute command: {} with {}", full_cmd, - e.what()); - return false; - } - DLOG_F(INFO, "Stop running driver: {}", driver->label); - running_drivers.erase(driver->label); - return true; +auto INDIManager::stopDriver( + const std::shared_ptr& driver) -> bool { + return connector->stopDriver(driver); } -bool INDIManager::setProp(const std::string &dev, const std::string &prop, - const std::string &element, - const std::string &value) { - std::stringstream ss; - - ss << "indi_setprop " << dev << "." << prop << "." << element << "=" - << value; - std::string cmd = ss.str(); - DLOG_F(INFO, "Cmd: {}", cmd); - try { - if (atom::system::executeCommand(cmd, false) != "") { - LOG_F(ERROR, "Failed to execute command: {}", cmd); - return false; - } - } catch (const atom::error::RuntimeError &e) { - LOG_F(ERROR, "Failed to execute command: {} with {}", cmd, e.what()); - return false; - } - DLOG_F(INFO, "Set property: {}.{} to {}", dev, prop, value); - return true; +auto INDIManager::setProp(const std::string& dev, const std::string& prop, + const std::string& element, + const std::string& value) -> bool { + return connector->setProp(dev, prop, element, value); } -std::string INDIManager::getProp(const std::string &dev, - const std::string &prop, - const std::string &element) { - std::stringstream ss; -#if ENABLE_INDI - ss << "indi_getprop " << dev << "." << prop << "." << element; -#else - ss << "indi_getprop " << dev << "." << prop << "." << element; -#endif - std::string cmd = ss.str(); - try { - std::string output = atom::system::executeCommand(cmd, false); - size_t equalsPos = output.find('='); - if (equalsPos != std::string::npos && equalsPos + 1 < output.length()) { - return output.substr(equalsPos + 1, - output.length() - equalsPos - 2); - } - } catch (const atom::error::RuntimeError &e) { - LOG_F(ERROR, "Failed to execute command: {} with {}", cmd, e.what()); - } - return ""; +auto INDIManager::getProp(const std::string& dev, + const std::string& prop, + const std::string& element) -> std::string { + return connector->getProp(dev, prop, element); } -std::string INDIManager::getState(const std::string &dev, - const std::string &prop) { - return getProp(dev, prop, "_STATE"); +std::string INDIManager::getState(const std::string& dev, + const std::string& prop) { + return connector->getState(dev, prop); } -#if ENABLE_FASTHASH -emhash8::HashMap> -INDIManager::getRunningDrivers() -#else std::unordered_map> -INDIManager::getRunningDrivers() -#endif -{ - return running_drivers; -} - -#if ENABLE_FASTHASH -std::vector> -INDIManager::getDevices() -#else -std::vector> -INDIManager::getDevices() -#endif -{ -#if ENABLE_FASTHASH - std::vector> devices; -#else - std::vector> devices; -#endif - std::string cmd = "indi_getprop *.CONNECTION.CONNECT"; - std::array buffer; - std::string result = ""; - std::unique_ptr pipe(popen(cmd.c_str(), "r"), - pclose); - if (!pipe) { - LOG_F(ERROR, "Failed to execute command: {}", cmd); - THROW_RUNTIME_ERROR("popen() failed!"); - } - while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { - result += buffer.data(); - } - std::vector lines = {"", ""}; - for (char token : result) { - if (token == '\n') { - std::unordered_map device; - std::stringstream ss(lines[0]); - std::string item; - while (getline(ss, item, '.')) { - device["device"] = item; - } - device["connected"] = (lines[1] == "On") ? "true" : "false"; - devices.push_back(device); - lines = {"", ""}; - } else if (token == '=') { - lines[1] = lines[1].substr(0, lines[1].length() - 1); - } else if (token == ' ') { - continue; - } else { - lines[(lines[0] == "") ? 0 : 1] += token; - } - } - return devices; +INDIManager::getRunningDrivers() { + return connector->getRunningDrivers(); } diff --git a/modules/lithium.indiserver/src/indiserver.hpp b/modules/lithium.indiserver/src/indiserver.hpp new file mode 100644 index 00000000..8b6310d1 --- /dev/null +++ b/modules/lithium.indiserver/src/indiserver.hpp @@ -0,0 +1,27 @@ +#ifndef LITHIUM_INDISERVER_HPP +#define LITHIUM_INDISERVER_HPP + +#include "addon/template/connector.hpp" + +class INDIManager { +public: + explicit INDIManager(std::unique_ptr connector); + + ~INDIManager(); + + bool startServer(); + bool stopServer(); + bool isRunning(); + bool isInstalled(); + bool startDriver(const std::shared_ptr& driver); + bool stopDriver(const std::shared_ptr& driver); + bool setProp(const std::string& dev, const std::string& prop, const std::string& element, const std::string& value); + std::string getProp(const std::string& dev, const std::string& prop, const std::string& element); + std::string getState(const std::string& dev, const std::string& prop); + std::unordered_map> getRunningDrivers(); + +private: + std::unique_ptr connector; +}; + +#endif // LITHIUM_INDISERVER_HPP diff --git a/modules/lithium.indiserver/xmake.lua b/modules/lithium.indiserver/xmake.lua new file mode 100644 index 00000000..5471d070 --- /dev/null +++ b/modules/lithium.indiserver/xmake.lua @@ -0,0 +1,46 @@ +set_project("lithium.indiserver") +set_version("1.0.0") +set_xmakever("2.5.1") + +-- Set the C++ standard +set_languages("cxx20") + +-- Add required packages +add_requires("loguru", "tinyxml2") + +-- Define libraries +local packages = { + "loguru", + "tinyxml2", +} + +local libs = { + "atom-system", + "atom-io", + "atom-component", + "atom-error" +} + +-- Source files +local source_files = { + "src/indiserver.cpp", + "src/collection.cpp", + "src/connector.cpp", + "_component.cpp", + "_main.cpp" +} + +-- Shared Library +target("lithium.indiserver") + set_kind("shared") + add_files(table.unpack(source_files)) + add_deps(table.unpack(libs)) + add_packages(table.unpack(packages)) + add_includedirs("src") + set_targetdir("$(buildir)/lib") + set_installdir("$(installdir)/lib") + set_version("1.0.0", {build = "%Y%m%d%H%M"}) + on_install(function (target) + os.cp(target:targetfile(), path.join(target:installdir(), "lib")) + end) +target_end() diff --git a/modules/lithium.pytools/tools/compiler_parser.py b/modules/lithium.pytools/tools/compiler_parser.py new file mode 100644 index 00000000..de8efc53 --- /dev/null +++ b/modules/lithium.pytools/tools/compiler_parser.py @@ -0,0 +1,263 @@ +""" +This module contains functions for parsing compiler output and converting it to JSON, CSV, or XML format. +""" +import re +import json +import csv +import argparse +import xml.etree.ElementTree as ET +from concurrent.futures import ThreadPoolExecutor + + +def parse_gcc_clang_output(output): + """ + Parses GCC/Clang compiler output. + + Args: + output (str): The raw output from the GCC/Clang compiler. + + Returns: + dict: A dictionary containing the compiler version and categorized results. + """ + version_pattern = re.compile(r'(gcc|clang) version (\d+\.\d+\.\d+)') + error_pattern = re.compile( + r'(?P.*):(?P\d+):(?P\d+):\s*(?P\w+):\s*(?P.+)') + + version_match = version_pattern.search(output) + matches = error_pattern.findall(output) + + results = {"errors": [], "warnings": [], "info": []} + for match in matches: + entry = { + "file": match[0], + "line": int(match[1]), + "column": int(match[2]), + "message": match[4].strip() + } + if match[3].lower() == 'error': + results["errors"].append(entry) + elif match[3].lower() == 'warning': + results["warnings"].append(entry) + else: + results["info"].append(entry) + + return { + "version": version_match.group() if version_match else "unknown", + "results": results + } + + +def parse_msvc_output(output): + """ + Parses MSVC compiler output. + + Args: + output (str): The raw output from the MSVC compiler. + + Returns: + dict: A dictionary containing the compiler version and categorized results. + """ + version_pattern = re.compile(r'Compiler Version (\d+\.\d+\.\d+\.\d+)') + error_pattern = re.compile( + r'(?P.*)\((?P\d+)\):\s*(?P\w+)\s*(?P\w+\d+):\s*(?P.+)') + + version_match = version_pattern.search(output) + matches = error_pattern.findall(output) + + results = {"errors": [], "warnings": [], "info": []} + for match in matches: + entry = { + "file": match[0], + "line": int(match[1]), + "code": match[3], + "message": match[4].strip() + } + if match[2].lower() == 'error': + results["errors"].append(entry) + elif match[2].lower() == 'warning': + results["warnings"].append(entry) + else: + results["info"].append(entry) + + return { + "version": version_match.group() if version_match else "unknown", + "results": results + } + + +def parse_cmake_output(output): + """ + Parses CMake compiler output. + + Args: + output (str): The raw output from the CMake build system. + + Returns: + dict: A dictionary containing the CMake version and categorized results. + """ + version_pattern = re.compile(r'cmake version (\d+\.\d+\.\d+)') + error_pattern = re.compile( + r'(?P.*):(?P\d+):(?P\w+):\s*(?P.+)') + + version_match = version_pattern.search(output) + matches = error_pattern.findall(output) + + results = {"errors": [], "warnings": [], "info": []} + for match in matches: + entry = { + "file": match[0], + "line": int(match[1]), + "message": match[3].strip() + } + if match[2].lower() == 'error': + results["errors"].append(entry) + elif match[2].lower() == 'warning': + results["warnings"].append(entry) + else: + results["info"].append(entry) + + return { + "version": version_match.group() if version_match else "unknown", + "results": results + } + + +def parse_output(compiler, output): + """ + Parses the compiler output based on the specified compiler type. + + Args: + compiler (str): The compiler type (gcc, clang, msvc, cmake). + output (str): The raw output from the compiler. + + Returns: + dict: Parsed output containing the compiler version and categorized results. + """ + if compiler.lower() in ['gcc', 'clang']: + return parse_gcc_clang_output(output) + elif compiler.lower() == 'msvc': + return parse_msvc_output(output) + elif compiler.lower() == 'cmake': + return parse_cmake_output(output) + else: + raise ValueError("Unsupported compiler") + + +def write_to_csv(data, output_path): + """ + Writes parsed data to a CSV file. + + Args: + data (list): The parsed data to write. + output_path (str): The path to the output CSV file. + """ + with open(output_path, 'w', newline='', encoding="utf-8") as csvfile: + fieldnames = ['file', 'line', 'column', 'type', 'code', 'message'] + writer = csv.DictWriter(csvfile, fieldnames=fieldnames) + writer.writeheader() + for entry in data: + writer.writerow(entry) + + +def write_to_xml(data, output_path): + """ + Writes parsed data to an XML file. + + Args: + data (list): The parsed data to write. + output_path (str): The path to the output XML file. + """ + root = ET.Element("CompilerOutput") + for entry in data: + item = ET.SubElement(root, "Item") + for key, value in entry.items(): + child = ET.SubElement(item, key) + child.text = str(value) + tree = ET.ElementTree(root) + tree.write(output_path, encoding="utf-8", xml_declaration=True) + + +def process_file(compiler, file_path): + """ + Processes a file to parse the compiler output. + + Args: + compiler (str): The compiler type (gcc, clang, msvc, cmake). + file_path (str): The path to the file containing the compiler output. + + Returns: + dict: Parsed output containing the file path, compiler version, and categorized results. + """ + with open(file_path, 'r', encoding="utf-8") as file: + output = file.read() + parsed_output = parse_output(compiler, output) + return { + "file": file_path, + "version": parsed_output["version"], + "results": parsed_output["results"] + } + + +def main(): + """ + Main function to parse compiler output and convert to JSON, CSV, or XML format. + """ + parser = argparse.ArgumentParser( + description="Parse compiler output and convert to JSON, CSV, or XML format.") + parser.add_argument('compiler', choices=[ + 'gcc', 'clang', 'msvc', 'cmake'], help="The compiler used for the output.") + parser.add_argument('file_paths', nargs='+', + help="Paths to the compiler output files.") + parser.add_argument( + '--output-format', choices=['json', 'csv', 'xml'], default='json', help="Output format.") + parser.add_argument( + '--output-file', default='output.json', help="Output file name.") + parser.add_argument( + '--filter', choices=['error', 'warning', 'info'], help="Filter by message type.") + parser.add_argument( + '--stats', action='store_true', help="Include statistics in the output.") + + args = parser.parse_args() + + all_results = [] + with ThreadPoolExecutor() as executor: + futures = [executor.submit(process_file, args.compiler, file_path) + for file_path in args.file_paths] + for future in futures: + all_results.append(future.result()) + + flattened_results = [] + for result in all_results: + for key, entries in result['results'].items(): + for entry in entries: + entry['type'] = key + entry['file'] = result['file'] + flattened_results.append(entry) + + if args.filter: + flattened_results = [ + entry for entry in flattened_results if entry['type'] == args.filter] + + if args.stats: + stats = { + "errors": sum(1 for entry in flattened_results if entry['type'] == 'errors'), + "warnings": sum(1 for entry in flattened_results if entry['type'] == 'warnings'), + "info": sum(1 for entry in flattened_results if entry['type'] == 'info'), + } + print(f"Statistics: {json.dumps(stats, indent=4)}") + + if args.output_format == 'json': + json_output = json.dumps(flattened_results, indent=4) + with open(args.output_file, 'w', encoding="utf-8") as json_file: + json_file.write(json_output) + print(f"JSON output saved to {args.output_file}") + elif args.output_format == 'csv': + write_to_csv(flattened_results, args.output_file) + print(f"CSV output saved to {args.output_file}") + elif args.output_format == 'xml': + write_to_xml(flattened_results, args.output_file) + print(f"XML output saved to {args.output_file}") + + +if __name__ == "__main__": + main() diff --git a/modules/lithium.pytools/tools/net.py b/modules/lithium.pytools/tools/net.py new file mode 100644 index 00000000..8ae67b5d --- /dev/null +++ b/modules/lithium.pytools/tools/net.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +""" +@file net_framework_installer.py +@brief A command-line utility to check for installed .NET Framework versions on Windows, + download installers, and run them with multithreading and file verification capabilities. + +@details This script uses the Windows Registry to determine installed .NET Framework versions + and provides functionality to download and execute installer files for missing versions + using multithreaded downloads and checksum verification for file integrity. + + Usage: + python net_framework_installer.py --list + python net_framework_installer.py --check v4.0.30319 + python net_framework_installer.py --check v4.0.30319 --download [URL] --install [FILE_PATH] --threads 4 --checksum [SHA256] + +@requires - Python 3.x + - Windows operating system + - `requests` Python library + +@author Your Name +@date Date of creation or last modification +""" + +import argparse +import hashlib +import subprocess +import threading +import os +from sys import platform +import requests + +def verify_file_checksum(file_path, original_checksum, hash_algo='sha256'): + """ + Verify the file checksum. + + @param file_path The path to the file whose checksum is to be verified. + @param original_checksum The expected checksum to verify against. + @param hash_algo The hashing algorithm to use (default is SHA-256). + + @return True if the checksum matches, False otherwise. + """ + _hash = hashlib.new(hash_algo) + with open(file_path, 'rb') as f: + for chunk in iter(lambda: f.read(4096), b""): + _hash.update(chunk) + return _hash.hexdigest() == original_checksum + +def check_dotnet_installed(version): + """ + Checks if a specific version of the .NET Framework is installed by querying the Windows Registry. + + @param version: A string representing the .NET Framework version to check (e.g., 'v4\\Client'). + @return: True if the specified version is installed, False otherwise. + """ + try: + result = subprocess.run( + ["reg", "query", f"HKLM\\SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\{version}"], + capture_output=True, text=True, check=True + ) + return result.returncode == 0 and version in result.stdout + except subprocess.CalledProcessError: + return False + +def list_installed_dotnets(): + """ + Lists all installed .NET Framework versions by querying the Windows Registry under the NDP key. + + Prints each found version directly to the standard output. + """ + try: + result = subprocess.run( + ["reg", "query", "HKLM\\SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\"], + capture_output=True, text=True, check=True + ) + if result.returncode == 0: + print("Installed .NET Framework versions:") + for line in result.stdout.splitlines(): + if "v" in line: + print(line.strip()) + except subprocess.CalledProcessError: + print("Failed to query the registry for installed .NET Framework versions.") + +def download_file_part(url, start, end, filename, idx, results): + """ + Download a part of a file specified by byte range. + + @param url The URL from which to download the file. + @param start The starting byte of the file part. + @param end The ending byte of the file part. + @param filename The filename where the downloaded data will be temporarily stored. + @param idx The index of the thread (used for storing results in the correct order). + @param results A shared list to store results from each thread. + """ + headers = {'Range': f'bytes={start}-{end}'} + response = requests.get(url, headers=headers, stream=True, timeout=10) + response.raise_for_status() + results[idx] = response.content + +def download_file(url, filename, num_threads=4, expected_checksum=None): + """ + Download a file using multiple threads and optionally verify its checksum. + + @param url The URL from which to download the file. + @param filename The filename where the downloaded file will be saved. + @param num_threads The number of threads to use for downloading the file. + @param expected_checksum Optional; the expected checksum of the downloaded file for verification purposes. + + @return None + """ + response = requests.head(url, timeout=10) + total_size = int(response.headers.get('content-length', 0)) + part_size = total_size // num_threads + results = [None] * num_threads + + threads = [] + for i in range(num_threads): + start = i * part_size + end = start + part_size - 1 if i < num_threads - 1 else total_size - 1 + args = (url, start, end, filename, i, results) + thread = threading.Thread(target=download_file_part, args=args) + threads.append(thread) + thread.start() + + for thread in threads: + thread.join() + + with open(filename, 'wb') as f: + for content in results: + f.write(content) + + print(f"Downloaded {filename}") + if expected_checksum: + if verify_file_checksum(filename, expected_checksum): + print("File checksum verified successfully.") + else: + print("File checksum verification failed.") + os.remove(filename) + raise ValueError("Checksum verification failed") + +def install_software(installer_path : str): + """ + Executes a software installer from a specified path. + + @param installer_path: The path to the executable installer file. + """ + if platform == "win32": # Ensure this is run on Windows + subprocess.run(["start", installer_path], shell=True, check=True) + print(f"Installer {installer_path} started.") + else: + print("This script only supports Windows.") + +def main(): + """ + Main function to parse command-line arguments and invoke script functionality. + """ + parser = argparse.ArgumentParser(description="Check and install .NET Framework versions.") + parser.add_argument("--check", metavar="VERSION", help="Check if a specific .NET Framework version is installed.") + parser.add_argument("--list", action="store_true", help="List all installed .NET Framework versions.") + parser.add_argument("--download", metavar="URL", help="URL to download the .NET Framework installer from.") + parser.add_argument("--install", metavar="FILE", help="Path to the .NET Framework installer to run.") + + args = parser.parse_args() + + if args.list: + list_installed_dotnets() + + if args.check: + if check_dotnet_installed(args.check): + print(f".NET Framework {args.check} is already installed.") + else: + print(f".NET Framework {args.check} is not installed.") + if args.download and args.install: + download_file(args.download, args.install) + install_software(args.install) + +if __name__ == "__main__": + main() diff --git a/modules/lithium.pytools/tools/scaffold.py b/modules/lithium.pytools/tools/scaffold.py deleted file mode 100644 index cc54e01d..00000000 --- a/modules/lithium.pytools/tools/scaffold.py +++ /dev/null @@ -1,419 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import os -import json -from datetime import datetime - -def query_user(): - print("Please provide the following information for your new module:") - module_name = input("Module Name (e.g., atom.utils): ").strip() - component_name = input("Component Name (the name of the shared_ptr, should be considered seriously, e.g. SystemComponent): ").strip() - version = input("Version (e.g., 1.0.0): ").strip() - description = input("Description: ").strip() - author = input("Author: ").strip() - license = input("License (e.g., GPL-3.0-or-later): ").strip() - repo_url = input("Repository URL (e.g., https://github.com/ElementAstro/Lithium): ").strip() - cpp_standard = input("C++ Standard (e.g., 20): ").strip() or "20" - additional_sources = input("Additional source files (comma-separated, e.g., utils.cpp,math.cpp): ").strip() - additional_headers = input("Additional header files (comma-separated, e.g., utils.hpp,math.hpp): ").strip() - - return module_name, component_name, version, description, author, license, repo_url, cpp_standard, additional_sources, additional_headers - -def confirm_details(details): - print("\nPlease confirm the entered details:") - for key, value in details.items(): - print(f"{key}: {value}") - - confirm = input("\nIs this information correct? (yes/no/edit): ").strip().lower() - if confirm == 'edit': - return False, True - return confirm == 'yes', False - -def create_cmakelists(module_name, cpp_standard, additional_sources, additional_headers): - base_name = module_name.split('.')[-1] - all_sources = [f"{base_name}.cpp"] + additional_sources.split(',') - all_headers = [f"{base_name}.hpp"] + additional_headers.split(',') - - all_sources_list = "\n ".join(all_sources) - all_headers_list = "\n ".join(all_headers) - - content = f"""# CMakeLists.txt for {module_name} -# This project is licensed under the terms of the GPL3 license. -# -# Author: Max Qian -# License: GPL3 - -cmake_minimum_required(VERSION 3.20) -project({module_name}) - -# Set the C++ standard -set(CMAKE_CXX_STANDARD {cpp_standard}) - -# Add source files -set(SOURCE_FILES - {all_sources_list} - - _component.cpp - _main.cpp -) - -# Create the module library -add_library({module_name} SHARED ${{SOURCE_FILES}}) - -# Include directories -target_include_directories({module_name} PUBLIC ${{CMAKE_CURRENT_SOURCE_DIR}}/include) -""" - return content - -def create_header(module_name): - guard = module_name.upper().replace('.', '_') - content = f"""#ifndef {guard}_HPP -#define {guard}_HPP - -namespace {module_name.replace('.', '::')} {{ - - void say_hello(); - -}} // namespace {module_name.replace('.', '::')} - -#endif // {guard}_HPP -""" - return content - -def create_source(module_name): - content = f"""#include "{module_name.split('.')[-1]}.hpp" -#include - -namespace {module_name.replace('.', '::')} {{ - - void say_hello() {{ - std::cout << "Hello from the {module_name} module!" << std::endl; - }} - -}} // namespace {module_name.replace('.', '::')} -""" - return content - -def create_package_json(module_name, version, description, author, license, repo_url): - package = { - "name": module_name, - "version": version, - "type": "shared", - "description": description, - "license": license, - "author": author, - "repository": { - "type": "git", - "url": repo_url - }, - "bugs": { - "url": f"{repo_url}/issues" - }, - "homepage": repo_url, - "keywords": [ - "lithium", - *module_name.split('.') - ], - "scripts": { - "build": "cmake --build . --config Release -- -j 4", - "lint": "clang-format -i src/*.cpp include/*.h" - }, - "modules": [ - { - "name": module_name.split('.')[-1], - "entry": "getInstance" - } - ] - } - return json.dumps(package, indent=4) - -def create_readme(module_name, description, author, version): - content = f"""# {module_name} - -{description} - -## Installation - -```bash -mkdir build -cd build -cmake .. -make -``` - -## Usage - -Include the `{module_name.split('.')[-1]}.hpp` in your project and use the namespaces and functions provided by the module. - -## Contributing - -Contributions are welcome! Please read the contributing guidelines and code of conduct before making any changes. - -## License - -This project is licensed under the {version} License - see the LICENSE file for details. - -## Author - -{author} -""" - return content - -def create_gitignore(): - content = """# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - - -build -.vscode -node_modules - -test - -*.log -*.xml - -.xmake -""" - return content - -def get_current_date(): - # 获取当前日期时间对象 - current_date = datetime.now() - # 格式化日期为YYYY-MM-DD的形式 - formatted_date = current_date.strftime("%Y-%m-%d") - return formatted_date - -def convert_to_upper_snake_case(s: str) -> str: - """ - Convert a dot-separated string to UPPERCASE_SNAKE_CASE. - - Args: - s (str): The input string. - - Returns: - str: The converted string in UPPERCASE_SNAKE_CASE format. - """ - return s.replace('.', '_').upper() - -def create_component_main(author, module_name, component_name): - content = f"""/* - * _main.cpp - * - * Copyright (C) 2023-2024 {author} - */ - -/************************************************* - -Date: {get_current_date()} - -Description: Main Entry - -**************************************************/ - -#include "_component.hpp" - -#include "atom/type/json.hpp" -using json = nlohmann::json; - -extern "C" {{ -std::shared_ptr getInstance([[maybe_unused]] const json ¶ms) {{ - if (params.contains("name") && params["name"].is_string()) {{ - return std::make_shared<{component_name}>( - params["name"].get()); - }} - return std::make_shared<{component_name}>("{module_name}"); -}} -}} - """ - return content - -def create_component_hpp(author, description, module_name, component_name): - content = f""" -/* - * _component.hpp - * - * Copyright (C) 2023-2024 {author} - */ - -/************************************************* - -Date: {get_current_date()} - -Description: {description} - -**************************************************/ - -#ifndef {convert_to_upper_snake_case(module_name)}_COMPONENT_HPP -#define {convert_to_upper_snake_case(module_name)}_COMPONENT_HPP - -#include "atom/components/component.hpp" - -class {component_name} : public Component {{ -public: - explicit {component_name}(const std::string& name); - virtual ~{component_name}(); - - bool initialize() override; - bool destroy() override; -}}; -#endif -""" - return content - -def create_component_cpp(author, description,module_name, component_name): - content = f"""/* - * _component.cpp - * - * Copyright (C) 2023-2024 {author} - */ - -/************************************************* - -Date: {get_current_date()} - -Description: {description} - -**************************************************/ - -#include "_component.hpp" - -#include "atom/log/loguru.hpp" - -{component_name}::{component_name}(const std::string& name) - : Component(name) {{ - LOG_F(INFO, "{component_name} Constructed"); -}} - -{component_name}::~{component_name}() {{ - LOG_F(INFO, "{component_name} Destructed"); -}} - -bool {component_name}::initialize() {{ - LOG_F(INFO, "{component_name} Initialized"); - return true; -}} - -bool {component_name}::destroy() {{ - LOG_F(INFO, "{component_name} Destroyed"); - return true; -}} -""" - return content - -def main(): - while True: - details = query_user() - module_name, component_name, version, description, author, license, repo_url, cpp_standard, additional_sources, additional_headers = details - - details_dict = { - "Module Name": module_name, - "Version": version, - "Description": description, - "Author": author, - "License": license, - "Repository URL": repo_url, - "C++ Standard": cpp_standard, - "Additional Sources": additional_sources, - "Additional Headers": additional_headers - } - - confirmed, edit = confirm_details(details_dict) - if edit: - continue - if confirmed: - break - else: - print("Please re-enter the information...\n") - - module_dir = os.path.join(os.getcwd(), module_name) - - # Create the directory structure - os.makedirs(module_dir, exist_ok=True) - os.makedirs(os.path.join(module_dir, "src"), exist_ok=True) - os.makedirs(os.path.join(module_dir, "include"), exist_ok=True) - - base_name = module_name.split('.')[-1] - - # Create _main.cpp - main_cpp_path = os.path.join(module_dir, f"_main.cpp") - with open(main_cpp_path, "w") as f: - f.write(create_component_main(author, module_name, component_name)) - - # Create _component.cpp - component_cpp_path = os.path.join(module_dir, f"_component.cpp") - with open(component_cpp_path, "w") as f: - f.write(create_component_cpp(author,description, module_name, component_name)) - - # Create _component.hpp - component_hpp_path = os.path.join(module_dir, f"_component.hpp") - with open(component_hpp_path, "w") as f: - f.write(create_component_hpp(author, description, module_name, component_name)) - - # Create CMakeLists.txt - with open(os.path.join(module_dir, 'CMakeLists.txt'), 'w') as f: - f.write(create_cmakelists(module_name, cpp_standard, additional_sources, additional_headers)) - - # Create module_name.hpp - with open(os.path.join(module_dir, f'include/{base_name}.hpp'), 'w') as f: - f.write(create_header(module_name)) - - # Create module_name.cpp - with open(os.path.join(module_dir, f'src/{base_name}.cpp'), 'w') as f: - f.write(create_source(module_name)) - - # Create additional source and header files - for src in filter(None, additional_sources.split(',')): - with open(os.path.join(module_dir, f'src/{src}'), 'w') as f: - f.write(f"// {src}\n") - for hdr in filter(None, additional_headers.split(',')): - with open(os.path.join(module_dir, f'include/{hdr}'), 'w') as f: - f.write(f"// {hdr}\n") - - # Create package.json - with open(os.path.join(module_dir, 'package.json'), 'w') as f: - f.write(create_package_json(module_name, version, description, author, license, repo_url)) - - # Create README.md - with open(os.path.join(module_dir, 'README.md'), 'w') as f: - f.write(create_readme(module_name, description, author, version)) - - # Create .gitignore - with open(os.path.join(module_dir, '.gitignore'), 'w') as f: - f.write(create_gitignore()) - - print(f"Module {module_name} has been created successfully in {module_dir}") - -if __name__ == "__main__": - main() diff --git a/modules/lithium.pytools/tools/updater.py b/modules/lithium.pytools/tools/updater.py new file mode 100644 index 00000000..0de389ba --- /dev/null +++ b/modules/lithium.pytools/tools/updater.py @@ -0,0 +1,354 @@ +""" +Auto Updater Script + +This script is designed to automatically check for, download, verify, and install updates for a given application. +It supports multi-threaded downloads, file verification, backup of current files, and logging of update history. + +Author: Your Name +Date: 2024-06-20 +""" + +import os +import json +import zipfile +import shutil +import requests +import threading +import argparse +import logging +from tqdm import tqdm +from typing import Any, Dict, Optional +import hashlib +from concurrent.futures import ThreadPoolExecutor +from pathlib import Path + +logging.basicConfig(level=logging.INFO) + +class AutoUpdater: + """ + A class used to represent the AutoUpdater. + + Attributes + ---------- + url : str + The URL to check for updates + install_dir : pathlib.Path + The directory where the application is installed + num_threads : int + Number of threads to use for downloading + custom_params : dict + Custom parameters for post-download and post-install actions + temp_dir : pathlib.Path + Temporary directory for download and extraction + latest_version : str + The latest version available for download + download_url : str + The URL to download the latest version + + Methods + ------- + check_for_updates() + Checks for updates from the given URL + compare_versions(current_version: str) -> bool + Compares the current version with the latest version + download_file(url: str, dest: pathlib.Path) + Downloads a file from the given URL to the specified destination + verify_file(file_path: pathlib.Path, expected_hash: str) -> bool + Verifies the downloaded file's hash + extract_zip(zip_path: pathlib.Path, extract_to: pathlib.Path) + Extracts a zip file to the specified directory + move_files(src: pathlib.Path, dest: pathlib.Path) + Moves files from source to destination + backup_files(src: pathlib.Path, backup_dir: pathlib.Path) + Backs up current files to the specified backup directory + cleanup() + Cleans up temporary files and directories + custom_post_download() + Executes custom post-download actions + custom_post_install() + Executes custom post-install actions + log_update(current_version: str, new_version: str) + Logs the update history + update(current_version: str) + Orchestrates the update process + """ + + def __init__(self, config: Dict[str, Any]): + """ + Initializes the AutoUpdater with the given configuration. + + Parameters + ---------- + config : dict + Configuration parameters for the updater + """ + self.url = config['url'] + self.install_dir = Path(config['install_dir']) + self.num_threads = config.get('num_threads', 4) + self.custom_params = config.get('custom_params', {}) + self.temp_dir = self.install_dir / "temp" + self.temp_dir.mkdir(parents=True, exist_ok=True) + self.latest_version = None + self.download_url = None + + def check_for_updates(self) -> bool: + """ + Checks for updates from the given URL. + + Returns + ------- + bool + True if updates are available, False otherwise + """ + try: + response = requests.get(self.url) + response.raise_for_status() + data = response.json() + self.latest_version = data['version'] + self.download_url = data['download_url'] + logging.info(f"Found update: version {self.latest_version}") + return True + except requests.RequestException as e: + logging.error(f"Failed to check for updates: {e}") + return False + + def compare_versions(self, current_version: str) -> bool: + """ + Compares the current version with the latest version. + + Parameters + ---------- + current_version : str + The current version of the application + + Returns + ------- + bool + True if the latest version is newer than the current version, False otherwise + """ + return self.latest_version > current_version + + def download_file(self, url: str, dest: Path): + """ + Downloads a file from the given URL to the specified destination. + + Parameters + ---------- + url : str + The URL to download the file from + dest : pathlib.Path + The destination path to save the downloaded file + """ + try: + response = requests.get(url, stream=True) + response.raise_for_status() + total_size = int(response.headers.get('content-length', 0)) + chunk_size = 1024 + with open(dest, 'wb') as file, tqdm( + desc=dest.name, + total=total_size, + unit='B', + unit_scale=True, + unit_divisor=1024, + ) as bar: + for data in response.iter_content(chunk_size=chunk_size): + file.write(data) + bar.update(len(data)) + except requests.RequestException as e: + logging.error(f"Failed to download file: {e}") + + def verify_file(self, file_path: Path, expected_hash: str) -> bool: + """ + Verifies the downloaded file's hash. + + Parameters + ---------- + file_path : pathlib.Path + The path to the downloaded file + expected_hash : str + The expected SHA-256 hash of the file + + Returns + ------- + bool + True if the file's hash matches the expected hash, False otherwise + """ + sha256 = hashlib.sha256() + try: + with open(file_path, 'rb') as f: + for chunk in iter(lambda: f.read(4096), b""): + sha256.update(chunk) + file_hash = sha256.hexdigest() + return file_hash == expected_hash + except Exception as e: + logging.error(f"Failed to verify file: {e}") + return False + + def extract_zip(self, zip_path: Path, extract_to: Path): + """ + Extracts a zip file to the specified directory. + + Parameters + ---------- + zip_path : pathlib.Path + The path to the zip file + extract_to : pathlib.Path + The directory to extract the zip file to + """ + try: + with zipfile.ZipFile(zip_path, 'r') as zip_ref: + zip_ref.extractall(extract_to) + except zipfile.BadZipFile as e: + logging.error(f"Failed to extract zip file: {e}") + + def move_files(self, src: Path, dest: Path): + """ + Moves files from source to destination. + + Parameters + ---------- + src : pathlib.Path + The source directory + dest : pathlib.Path + The destination directory + """ + try: + for item in src.iterdir(): + s = src / item + d = dest / item + if s.is_dir(): + shutil.copytree(s, d, dirs_exist_ok=True) + else: + shutil.copy2(s, d) + except Exception as e: + logging.error(f"Failed to move files: {e}") + + def backup_files(self, src: Path, backup_dir: Path): + """ + Backs up current files to the specified backup directory. + + Parameters + ---------- + src : pathlib.Path + The source directory + backup_dir : pathlib.Path + The backup directory + """ + try: + backup_dir.mkdir(parents=True, exist_ok=True) + for item in src.iterdir(): + s = src / item + d = backup_dir / item + if s.is_dir(): + shutil.copytree(s, d, dirs_exist_ok=True) + else: + shutil.copy2(s, d) + logging.info("Backup completed successfully.") + except Exception as e: + logging.error(f"Failed to backup files: {e}") + + def cleanup(self): + """ + Cleans up temporary files and directories. + """ + try: + shutil.rmtree(self.temp_dir, ignore_errors=True) + except Exception as e: + logging.error(f"Failed to clean up: {e}") + + def custom_post_download(self): + """ + Executes custom post-download actions. + """ + logging.info("Running custom post-download actions") + if 'post_download' in self.custom_params: + self.custom_params['post_download']() + + def custom_post_install(self): + """ + Executes custom post-install actions. + """ + logging.info("Running custom post-install actions") + if 'post_install' in self.custom_params: + self.custom_params['post_install']() + + def log_update(self, current_version: str, new_version: str): + """ + Logs the update history. + + Parameters + ---------- + current_version : str + The current version of the application + new_version : str + The new version of the application + """ + try: + with open(self.install_dir / "update_log.txt", 'a') as log_file: + log_file.write(f"Updated from version {current_version} to {new_version}\n") + except Exception as e: + logging.error(f"Failed to log update: {e}") + + def update(self, current_version: str): + """ + Orchestrates the update process. + + Parameters + ---------- + current_version : str + The current version of the application + """ + if self.check_for_updates() and self.compare_versions(current_version): + logging.info("Update available. Downloading...") + zip_path = self.temp_dir / "update.zip" + self.download_file(self.download_url, zip_path) + if self.verify_file(zip_path, self.custom_params.get('expected_hash', '')): + self.custom_post_download() + logging.info("Download complete. Extracting...") + self.extract_zip(zip_path, self.temp_dir) + logging.info("Extraction complete. Backing up current files...") + backup_dir = self.install_dir / "backup" + self.backup_files(self.install_dir, backup_dir) + logging.info("Backup complete. Installing update...") + self.move_files(self.temp_dir, self.install_dir) + self.custom_post_install() + logging.info("Installation complete. Cleaning up...") + self.cleanup() + self.log_update(current_version, self.latest_version) + logging.info("Update installed successfully.") + else: + logging.error("File verification failed. Update aborted.") + else: + logging.info("No updates available or version is not newer.") + +def run_updater(config: Dict[str, Any]): + """ + Runs the updater with the provided configuration. + + Parameters + ---------- + config : dict + Configuration parameters for the updater + """ + updater = AutoUpdater(config) + current_version = config.get('current_version', '0.0.0') + updater.update(current_version) + +def main(): + """ + The main entry point for the script. Parses the configuration file and starts the updater. + """ + parser = argparse.ArgumentParser(description="Auto updater script") + parser.add_argument("--config", type=str, required=True, help="Path to the configuration file") + + args = parser.parse_args() + + with open(args.config, 'r') as f: + config = json.load(f) + + updater_thread = threading.Thread(target=run_updater, args=(config,)) + updater_thread.start() + updater_thread.join() + +if __name__ == "__main__": + main() diff --git a/modules/lithium.webserver/.gitignore b/modules/lithium.webserver/.gitignore deleted file mode 100644 index 03b499da..00000000 --- a/modules/lithium.webserver/.gitignore +++ /dev/null @@ -1,44 +0,0 @@ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - - -build -.vscode -node_modules - -test - -*.log -*.xml - -.xmake diff --git a/modules/lithium.webserver/CMakeLists.txt b/modules/lithium.webserver/CMakeLists.txt deleted file mode 100644 index a371453a..00000000 --- a/modules/lithium.webserver/CMakeLists.txt +++ /dev/null @@ -1,44 +0,0 @@ -# CMakeLists.txt for lithium.webserver -# This project is licensed under the terms of the GPL3 license. -# -# Author: Max Qian -# License: GPL3 - -cmake_minimum_required(VERSION 3.20) -project(lithium.webserver) - -# Set the C++ standard -set(CMAKE_CXX_STANDARD 20) - -set(server_websocket_module - src/websocket/Hub.cpp - src/websocket/Connection.cpp - src/websocket/Registry.cpp - src/websocket/Session.cpp -) - -set(server_module - src/App.cpp - src/ErrorHandler.cpp - src/Runner.cpp - - src/config/HubsConfig.cpp -) - -# Add source files -set(SOURCE_FILES - src/webserver.cpp - _component.cpp - _main.cpp -) -# Create the module library -add_library(lithium.webserver SHARED ${SOURCE_FILES} ${server_websocket_module} ${server_module}) - -target_link_directories(${PROJECT_NAME} PUBLIC ${CMAKE_BINARY_DIR}/libs) - -target_link_libraries(${PROJECT_NAME} PRIVATE oatpp-websocket oatpp-swagger oatpp-openssl oatpp-zlib oatpp) -target_link_libraries(${PROJECT_NAME} PRIVATE loguru fmt::fmt) -target_link_libraries(${PROJECT_NAME} PRIVATE atomstatic) - -# Include directories -target_include_directories(lithium.webserver PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/modules/lithium.webserver/README.md b/modules/lithium.webserver/README.md deleted file mode 100644 index 8f8d3df1..00000000 --- a/modules/lithium.webserver/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# lithium.webserver - -Lithium Web Interface based on oatpp - -## Installation - -```bash -mkdir build -cd build -cmake .. -make -``` - -## Usage - -Include the `webserver.hpp` in your project and use the namespaces and functions provided by the module. - -## Contributing - -Contributions are welcome! Please read the contributing guidelines and code of conduct before making any changes. - -## License - -This project is licensed under the 1.0.0 License - see the LICENSE file for details. - -## Author - -Max Qian diff --git a/modules/lithium.webserver/_component.cpp b/modules/lithium.webserver/_component.cpp deleted file mode 100644 index 9eeecef9..00000000 --- a/modules/lithium.webserver/_component.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* - * _component.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-05-18 - -Description: Lithium Web Interface based on oatpp - -**************************************************/ - -#include "_component.hpp" - -#include "atom/log/loguru.hpp" - -ServerComponent::ServerComponent(const std::string& name) - : Component(name) { - LOG_F(INFO, "ServerComponent Constructed"); -} - -ServerComponent::~ServerComponent() { - LOG_F(INFO, "ServerComponent Destructed"); -} - -bool ServerComponent::initialize() { - LOG_F(INFO, "ServerComponent Initialized"); - return true; -} - -bool ServerComponent::destroy() { - LOG_F(INFO, "ServerComponent Destroyed"); - return true; -} diff --git a/modules/lithium.webserver/_component.hpp b/modules/lithium.webserver/_component.hpp deleted file mode 100644 index f0b968b3..00000000 --- a/modules/lithium.webserver/_component.hpp +++ /dev/null @@ -1,29 +0,0 @@ - -/* - * _component.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-05-18 - -Description: Lithium Web Interface based on oatpp - -**************************************************/ - -#ifndef LITHIUM_WEBSERVER_COMPONENT_HPP -#define LITHIUM_WEBSERVER_COMPONENT_HPP - -#include "atom/components/component.hpp" - -class ServerComponent : public Component { -public: - explicit ServerComponent(const std::string& name); - virtual ~ServerComponent(); - - bool initialize() override; - bool destroy() override; -}; -#endif diff --git a/modules/lithium.webserver/_main.cpp b/modules/lithium.webserver/_main.cpp deleted file mode 100644 index 5e1a95c3..00000000 --- a/modules/lithium.webserver/_main.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/* - * _main.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-05-18 - -Description: Main Entry - -**************************************************/ - -#include "_component.hpp" - -#include "atom/type/json.hpp" -using json = nlohmann::json; - -extern "C" { -std::shared_ptr getInstance([[maybe_unused]] const json ¶ms) { - if (params.contains("name") && params["name"].is_string()) { - return std::make_shared( - params["name"].get()); - } - return std::make_shared("lithium.webserver"); -} -} diff --git a/modules/lithium.webserver/include/App.hpp b/modules/lithium.webserver/include/App.hpp deleted file mode 100644 index a2231778..00000000 --- a/modules/lithium.webserver/include/App.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef LITHIUM_SERVER_APP_HPP -#define LITHIUM_SERVER_APP_HPP - -int runServer(); - -#endif diff --git a/modules/lithium.webserver/include/AppComponent.hpp b/modules/lithium.webserver/include/AppComponent.hpp deleted file mode 100644 index f0e9b625..00000000 --- a/modules/lithium.webserver/include/AppComponent.hpp +++ /dev/null @@ -1,208 +0,0 @@ -/* - * AppComponent.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-7-13 - -Description: App Components - -**************************************************/ - -#ifndef LITHIUM_APP_COMPONENT_HPP -#define LITHIUM_APP_COMPONENT_HPP - -#include "config/Config.hpp" -#include "config/HubsConfig.hpp" - -#include "websocket/Registry.hpp" - -// Websocket -#include "oatpp-websocket/AsyncConnectionHandler.hpp" -#include "oatpp/web/server/AsyncHttpConnectionHandler.hpp" - -#include "ErrorHandler.hpp" - -#include "oatpp/core/macro/component.hpp" -#include "oatpp/core/utils/ConversionUtils.hpp" -#include "oatpp/network/monitor/ConnectionInactivityChecker.hpp" -#include "oatpp/network/monitor/ConnectionMaxAgeChecker.hpp" -#include "oatpp/network/monitor/ConnectionMonitor.hpp" -#include "oatpp/network/tcp/server/ConnectionProvider.hpp" -#include "oatpp/parser/json/mapping/ObjectMapper.hpp" -#include "oatpp/web/protocol/http/incoming/SimpleBodyDecoder.hpp" -#include "oatpp/web/server/HttpRouter.hpp" -#include "oatpp/web/server/interceptor/AllowCorsGlobal.hpp" - -#if ENABLE_DEBUG -#include "oatpp/network/virtual_/Interface.hpp" -#include "oatpp/network/virtual_/server/ConnectionProvider.hpp" -#endif - -#include "components/SwaggerComponent.hpp" - -// SSL -#include "oatpp-openssl/Config.hpp" -#include "oatpp-openssl/configurer/TrustStore.hpp" -#include "oatpp-openssl/server/ConnectionProvider.hpp" - -// GZip -#include "oatpp-zlib/EncoderProvider.hpp" - -#include -#include // for std::thread::hardware_concurrency - -// #include "data/SystemCustom.hpp" - -#include - -/** - * Class which creates and holds Application components and registers - * components in oatpp::base::Environment Order of components initialization is - * from top to bottom - */ -class AppComponent { -public: - AppComponent() = default; - -public: - /** - * Swagger component - */ - SwaggerComponent swaggerComponent; - - /** - * Create config component - */ - OATPP_CREATE_COMPONENT(oatpp::Object, appConfig) - ([this] { - auto config = ConfigDto::createShared(); - - auto hostServer = ServerConfigDto::createShared(); - hostServer->host = "0.0.0.0"; - hostServer->port = 8000; - - auto clientServer = ServerConfigDto::createShared(); - clientServer->host = "0.0.0.0"; - clientServer->port = 8001; - - config->hostAPIServer = hostServer; - config->clientAPIServer = clientServer; - - return config; - }()); - - /** - * Hub configs - */ - OATPP_CREATE_COMPONENT(std::shared_ptr, hubConfig) - ([] { - // We specify the default config here - auto config = std::make_shared(nullptr); - auto Hub1 = HubConfigDto::createShared(); - auto Hub2 = HubConfigDto::createShared(); - // Script and device are the default hubs - Hub1->hubId = "device"; - Hub2->hubId = "script"; - config->putHubConfig(Hub1); - config->putHubConfig(Hub2); - return config; - }()); - - /** - * Create Async Executor - */ - OATPP_CREATE_COMPONENT(std::shared_ptr, executor) - ([] { return std::make_shared(); }()); - - /** - * Create Router component - */ - OATPP_CREATE_COMPONENT(std::shared_ptr, - httpRouter) - ([] { return oatpp::web::server::HttpRouter::createShared(); }()); - - /** - * Create ObjectMapper component to serialize/deserialize DTOs in - * Contoller's API - */ - OATPP_CREATE_COMPONENT(std::shared_ptr, - apiObjectMapper) - (Constants::COMPONENT_REST_API, [] { - /* create serializer and deserializer configurations */ - auto serializeConfig = - oatpp::parser::json::mapping::Serializer::Config::createShared(); - auto deserializeConfig = - oatpp::parser::json::mapping::Deserializer::Config::createShared(); - - /* enable beautifier */ - serializeConfig->useBeautifier = true; - - auto objectMapper = - oatpp::parser::json::mapping::ObjectMapper::createShared( - serializeConfig, deserializeConfig); - objectMapper->getDeserializer()->getConfig()->allowUnknownFields = - false; - - // objectMapper->getSerializer()->getConfig()->enabledInterpretations = - // { - // "system::memory"}; - // objectMapper->getDeserializer()->getConfig()->enabledInterpretations - // = { - // "system::memory"}; - return objectMapper; - }()); - - /** - * Create ObjectMapper component to serialize/deserialize DTOs in WS - * communication - */ - /* - OATPP_CREATE_COMPONENT(std::shared_ptr, - wsApiObjectMapper) - (Constants::COMPONENT_WS_API, [] { - auto mapper = - oatpp::parser::json::mapping::ObjectMapper::createShared(); - mapper->getSerializer()->getConfig()->includeNullFields = false; - return mapper; - }()); - */ - - /** - * Create games sessions Registry component. - */ - OATPP_CREATE_COMPONENT(std::shared_ptr, gamesSessionsRegistry) - ([] { return std::make_shared(); }()); - - - - /** - * Create websocket connection handler - */ - OATPP_CREATE_COMPONENT(std::shared_ptr, - websocketConnectionHandler) - (Constants::COMPONENT_WS_API, [] { - OATPP_COMPONENT(std::shared_ptr, executor); - OATPP_COMPONENT(std::shared_ptr, registry); - auto connectionHandler = - oatpp::websocket::AsyncConnectionHandler::createShared(executor); - connectionHandler->setSocketInstanceListener(registry); - return connectionHandler; - }()); - - /** - * Create Debug virtual interface component - */ -#if ENABLE_DEBUG - OATPP_CREATE_COMPONENT(std::shared_ptr, - virtualInterface) - ([] { - return oatpp::network::virtual_::Interface::obtainShared("virtualhost"); - }()); -#endif -}; - -#endif /* LITHIUM_APP_COMPONENT_HPP */ diff --git a/modules/lithium.webserver/include/Constants.hpp b/modules/lithium.webserver/include/Constants.hpp deleted file mode 100644 index 963c0fb6..00000000 --- a/modules/lithium.webserver/include/Constants.hpp +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Constants.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-3-16 - -Description: Constants for Lithium - -**************************************************/ - -#ifndef LITHIUM_CONSTANTS_HPP -#define LITHIUM_CONSTANTS_HPP - -class Constants { -public: - static constexpr const char* COMPONENT_REST_API = "REST_API"; - static constexpr const char* COMPONENT_WS_API = "WS_API"; - -public: - static constexpr const char* PARAM_GAME_ID = "gameId"; - static constexpr const char* PARAM_GAME_SESSION_ID = "sessionId"; - static constexpr const char* PARAM_PEER_TYPE = "peerType"; - static constexpr const char* PARAM_PEER_TYPE_HOST = "host"; - static constexpr const char* PARAM_PEER_TYPE_CLIENT = "client"; -}; - -#endif // LITHIUM_CONSTANTS_HPP diff --git a/modules/lithium.webserver/include/ErrorHandler.hpp b/modules/lithium.webserver/include/ErrorHandler.hpp deleted file mode 100644 index b8c2ea4a..00000000 --- a/modules/lithium.webserver/include/ErrorHandler.hpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - * ErrorHandle.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-7-13 - -Description: Error Handle (404 or 500) - -**************************************************/ - -#ifndef ERRORHANDLER_HPP -#define ERRORHANDLER_HPP - -#include "data/StatusDto.hpp" - -#include "oatpp/web/protocol/http/outgoing/ResponseFactory.hpp" -#include "oatpp/web/server/handler/ErrorHandler.hpp" - -class ErrorHandler : public oatpp::web::server::handler::ErrorHandler { -private: - typedef oatpp::web::protocol::http::outgoing::Response OutgoingResponse; - typedef oatpp::web::protocol::http::Status Status; - typedef oatpp::web::protocol::http::outgoing::ResponseFactory - ResponseFactory; - -private: - std::shared_ptr m_objectMapper; - -public: - ErrorHandler(const std::shared_ptr - &objectMapper); - - std::shared_ptr handleError( - const Status &status, const oatpp::String &message, - const Headers &headers) override; -}; - -#endif // ERRORHANDLER_HPP diff --git a/modules/lithium.webserver/include/Runner.hpp b/modules/lithium.webserver/include/Runner.hpp deleted file mode 100644 index de791a5b..00000000 --- a/modules/lithium.webserver/include/Runner.hpp +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Runner.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-3-16 - -Description: Lithium Server Runner - -**************************************************/ - -#ifndef LITHIUM_RUNNER_HPP -#define LITHIUM_RUNNER_HPP - -#include "config/Config.hpp" - -#include "oatpp/web/server/HttpRouter.hpp" - -#include "oatpp/network/ConnectionHandler.hpp" -#include "oatpp/network/ConnectionProvider.hpp" - -#include "oatpp-websocket/AsyncConnectionHandler.hpp" -#include "oatpp/web/server/AsyncHttpConnectionHandler.hpp" - -#include "oatpp/core/async/Executor.hpp" - -class APIServer { -private: - std::shared_ptr m_router; - std::shared_ptr - m_connectionProvider; - std::shared_ptr - m_connectionHandler; - -private: -#if __cplusplus >= 202002L - std::jthread m_serverThread; -#else - std::thread m_serverThread; -#endif - -public: - APIServer(const oatpp::Object& config, - const std::shared_ptr& executor); - - std::shared_ptr getRouter(); - - void start(); - - void join(); -}; - -class Runner { -private: - std::list> m_servers; - -private: - void assertServerConfig(const oatpp::Object& config, - const oatpp::String& serverName, bool checkTls); - -public: - Runner(const oatpp::Object& config, - const std::shared_ptr& executor); - - void start(); - - void join(); -}; - -#endif // LITHIUM_RUNNER_HPP diff --git a/modules/lithium.webserver/include/components/SwaggerComponent.hpp b/modules/lithium.webserver/include/components/SwaggerComponent.hpp deleted file mode 100644 index f662e3d2..00000000 --- a/modules/lithium.webserver/include/components/SwaggerComponent.hpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * SwaggerComponent.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-11-11 - -Description: Oatpp Swagger component - -**************************************************/ - -#ifndef SwaggerComponent_hpp -#define SwaggerComponent_hpp - -#include "oatpp-swagger/Model.hpp" -#include "oatpp-swagger/Resources.hpp" -#include "oatpp/core/macro/component.hpp" - -/** - * Swagger ui is served at - * http://host:port/swagger/ui - */ -class SwaggerComponent { -public: - /** - * General API docs info - */ - OATPP_CREATE_COMPONENT(std::shared_ptr, - swaggerDocumentInfo) - ([] { - oatpp::swagger::DocumentInfo::Builder builder; - - builder.setTitle("Lithium API") - .setDescription("Lithium API") - .setVersion("1.0") - .setContactName("Max Qian") - .setContactUrl("https://lightapt.com/") - - .setLicenseName("GNU GENERAL PUBLIC LICENSE, Version 3") - .setLicenseUrl("https://www.gnu.org/licenses/gpl-3.0.en.html") - - .addServer("http://localhost:8000", "server on localhost"); - - return builder.build(); - }()); - - /** - * Swagger-Ui Resources (/lib/oatpp-swagger/res) - */ - OATPP_CREATE_COMPONENT(std::shared_ptr, - swaggerResources) - ([] { - // Make sure to specify correct full path to oatpp-swagger/res folder - // !!! - return oatpp::swagger::Resources::loadResources("websrc/swagger"); - }()); -}; - -#endif /* SwaggerComponent_hpp */ diff --git a/modules/lithium.webserver/include/config/Config.hpp b/modules/lithium.webserver/include/config/Config.hpp deleted file mode 100644 index e93ede76..00000000 --- a/modules/lithium.webserver/include/config/Config.hpp +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Config.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-3-16 - -Description: Config DTO - -**************************************************/ - -#ifndef LITHIUM_CONFIG_DTO_HPP -#define LITHIUM_CONFIG_DTO_HPP - -#include "oatpp/core/Types.hpp" -#include "oatpp/core/macro/codegen.hpp" - -#include "oatpp/core/data/stream/BufferStream.hpp" - -#include OATPP_CODEGEN_BEGIN(DTO) - -/** - * TLS config. - */ -class TLSConfigDto : public oatpp::DTO { - DTO_INIT(TLSConfigDto, DTO) - - /** - * Path to private key file. - */ - DTO_FIELD(String, pkFile); - - /** - * Path to full chain file. - */ - DTO_FIELD(String, chainFile); -}; - -/** - * Config where to serve controller's endpoints. - */ -class ServerConfigDto : public oatpp::DTO { - DTO_INIT(ServerConfigDto, DTO) - - /** - * Host - */ - DTO_FIELD(String, host); - - /** - * Port - */ - DTO_FIELD(UInt16, port); - - /** - * TLS config. If null - do not use TLS. - */ - DTO_FIELD(Object, tls); -}; - -class ConfigDto : public oatpp::DTO { -public: - DTO_INIT(ConfigDto, DTO) - - /** - * Config for Host API Server (create hub functionality). - */ - DTO_FIELD(Object, hostAPIServer); - - /** - * Config for Client API Server (join hub functionality). - */ - DTO_FIELD(Object, clientAPIServer); - - /** - * Path to hubs config file. - */ - DTO_FIELD(String, hubsConfigFile); -}; - -#include OATPP_CODEGEN_END(DTO) - -#endif // LITHIUM_CONFIG_DTO_HPP diff --git a/modules/lithium.webserver/include/config/HubsConfig.hpp b/modules/lithium.webserver/include/config/HubsConfig.hpp deleted file mode 100644 index ee5127b7..00000000 --- a/modules/lithium.webserver/include/config/HubsConfig.hpp +++ /dev/null @@ -1,109 +0,0 @@ -/* - * HubConfig.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-1-13 - -Description: Websocket Hub Config DTO - -**************************************************/ - -#ifndef LITHIUM_CONFIG_HUBSCONFIG_HPP -#define LITHIUM_CONFIG_HUBSCONFIG_HPP - -#include "oatpp/parser/json/mapping/ObjectMapper.hpp" -#include "oatpp/core/macro/codegen.hpp" - -#include - -#include OATPP_CODEGEN_BEGIN(DTO) - -/** - * Hub config - */ -class HubConfigDto : public oatpp::DTO { - - DTO_INIT(HubConfigDto, DTO) - - /** - * Hub ID. - */ - DTO_FIELD(String, hubId); - - /** - * Host peer can't change. - * If host peer disconnects the hub is over and all other peers are dropped. - */ - DTO_FIELD(Boolean, staticHost) = true; - - /** - * The maximum number of peers connected to hub (including host peer). - */ - DTO_FIELD(UInt32, maxPeers) = 10; - - /** - * Max size of the received bytes. (the whole MessageDto structure). - */ - DTO_FIELD(UInt64, maxMessageSizeBytes) = 4 * 1024; // Default - 4Kb - - /** - * Max number of messages queued for the peer. - * If exceeded messages are dropped. - */ - DTO_FIELD(UInt32, maxQueuedMessages) = 100; - - /** - * How often should server ping client. - */ - DTO_FIELD(UInt64, pingIntervalMillis) = 5 * 1000; // 5 seconds - - /** - * A failed ping is ping to which server receives no response in a 'pingIntervalMillis' interval. - * If number of failed pings for a peer reaches 'maxFailedPings' in a row then peer is dropped. - */ - DTO_FIELD(UInt64, maxFailedPings) = 100; - -}; - -class HubsConfig { -private: - oatpp::String m_configFile; - oatpp::parser::json::mapping::ObjectMapper m_mapper; - oatpp::UnorderedFields> m_hubs; - std::mutex m_mutex; -public: - - /** - * Path to config file containing configs for hubs. - * @param configFilename - */ - HubsConfig(const oatpp::String& configFilename); - - /** - * Put hub config - * @param hubId - * @param config - */ - void putHubConfig(const oatpp::Object& config); - - /** - * Get hub config - * @param hubId - * @return - */ - oatpp::Object getHubConfig(const oatpp::String& hubId); - - /** - * Save current state of hubs config to config file. - */ - bool save(); - -}; - -#include OATPP_CODEGEN_END(DTO) - -#endif //LITHIUM_CONFIG_HUBSCONFIG_HPP diff --git a/modules/lithium.webserver/include/controller/AsyncClientController.hpp b/modules/lithium.webserver/include/controller/AsyncClientController.hpp deleted file mode 100644 index 831277b3..00000000 --- a/modules/lithium.webserver/include/controller/AsyncClientController.hpp +++ /dev/null @@ -1,111 +0,0 @@ -/* - * AsyncClientController.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-3-16 - -Description: Async Client Controller - -**************************************************/ - -#ifndef LITHIUM_ASYNC_CLIENT_CONTROLLER_HPP -#define LITHIUM_ASYNC_CLIENT_CONTROLLER_HPP - -#include "Constants.hpp" - -#include "oatpp-websocket/Handshaker.hpp" - -#include "oatpp/network/ConnectionHandler.hpp" -#include "oatpp/web/server/api/ApiController.hpp" - -#include "oatpp/core/macro/codegen.hpp" -#include "oatpp/core/macro/component.hpp" - -#include OATPP_CODEGEN_BEGIN(ApiController) /// <-- Begin Code-Gen - -class ClientController : public oatpp::web::server::api::ApiController { -private: - typedef ClientController __ControllerType; - -private: - OATPP_COMPONENT(std::shared_ptr, - websocketConnectionHandler, Constants::COMPONENT_REST_API); - -public: - ClientController(OATPP_COMPONENT(std::shared_ptr, - objectMapper, - Constants::COMPONENT_REST_API)) - : oatpp::web::server::api::ApiController(objectMapper) {} - - // ---------------------------------------------------------------- - // Pointer creator - // ---------------------------------------------------------------- - - static std::shared_ptr createShared( - OATPP_COMPONENT(std::shared_ptr, objectMapper)) { - return std::make_shared(objectMapper); - } - -public: - /** - * Join existing game - */ - ENDPOINT_ASYNC("GET", "api/join-game/*", WS_CLIENT) { - ENDPOINT_ASYNC_INIT(WS_CLIENT); - - Action act() override { - /* Websocket handshake */ - auto response = oatpp::websocket::Handshaker::serversideHandshake( - request->getHeaders(), controller->websocketConnectionHandler); - auto parameters = std::make_shared< - oatpp::network::ConnectionHandler::ParameterMap>(); - - (*parameters)[Constants::PARAM_GAME_ID] = - request->getQueryParameter(Constants::PARAM_GAME_ID); - (*parameters)[Constants::PARAM_GAME_SESSION_ID] = - request->getQueryParameter(Constants::PARAM_GAME_SESSION_ID); - (*parameters)[Constants::PARAM_PEER_TYPE] = - Constants::PARAM_PEER_TYPE_CLIENT; - - /* Set connection upgrade params */ - response->setConnectionUpgradeParameters(parameters); - - return _return(response); - } - }; - - /** - * Create new host - */ - ENDPOINT_ASYNC("GET", "api/create-game/*", WS_HOST) { - ENDPOINT_ASYNC_INIT(WS_HOST); - - Action act() override { - /* Websocket handshake */ - auto response = oatpp::websocket::Handshaker::serversideHandshake( - request->getHeaders(), controller->websocketConnectionHandler); - auto parameters = std::make_shared< - oatpp::network::ConnectionHandler::ParameterMap>(); - - (*parameters)[Constants::PARAM_GAME_ID] = - request->getQueryParameter(Constants::PARAM_GAME_ID); - (*parameters)[Constants::PARAM_GAME_SESSION_ID] = - request->getQueryParameter(Constants::PARAM_GAME_SESSION_ID); - (*parameters)[Constants::PARAM_PEER_TYPE] = - Constants::PARAM_PEER_TYPE_HOST; - - /* Set connection upgrade params */ - response->setConnectionUpgradeParameters(parameters); - - return _return(response); - } - }; -}; - -#include OATPP_CODEGEN_END(ApiController) /// <-- End Code-Gen - -#endif /* LITHIUM_ASYNC_CLIENT_CONTROLLER_HPP */ diff --git a/modules/lithium.webserver/include/controller/AsyncConfigController.hpp b/modules/lithium.webserver/include/controller/AsyncConfigController.hpp deleted file mode 100644 index aa87605a..00000000 --- a/modules/lithium.webserver/include/controller/AsyncConfigController.hpp +++ /dev/null @@ -1,275 +0,0 @@ -/* - * AsyncConfigController.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-1-23 - -Description: Async Config Controller - -**************************************************/ - -#ifndef LITHIUM_ASYNC_SCRIPT_CONTROLLER_HPP -#define LITHIUM_ASYNC_SCRIPT_CONTROLLER_HPP - -#include "oatpp/core/macro/codegen.hpp" -#include "oatpp/core/macro/component.hpp" -#include "oatpp/parser/json/mapping/ObjectMapper.hpp" -#include "oatpp/web/server/api/ApiController.hpp" - -#include "data/ConfigDto.hpp" -#include "data/StatusDto.hpp" - -#include "atom/server/global_ptr.hpp" -#include "config/configor.hpp" - -#include OATPP_CODEGEN_BEGIN(ApiController) //<- Begin Codegen - -class ConfigController : public oatpp::web::server::api::ApiController { -public: - static std::weak_ptr m_configManager; - - ConfigController(const std::shared_ptr& objectMapper) - : oatpp::web::server::api::ApiController(objectMapper) { - m_configManager = GetWeakPtr("lithium.config"); - } - - // ---------------------------------------------------------------- - // Pointer creator - // ---------------------------------------------------------------- - - static std::shared_ptr createShared( - OATPP_COMPONENT(std::shared_ptr, objectMapper)) { - return std::make_shared(objectMapper); - } - - // ---------------------------------------------------------------- - // Config Http Handler - // ---------------------------------------------------------------- - - ENDPOINT_INFO(getUIGetConfig) { - info->summary = "Get config from global ConfigManager (thread safe)"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("POST", "/api/config/get", getUIGetConfig) { - ENDPOINT_ASYNC_INIT(getUIGetConfig); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIGetConfig::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - OATPP_ASSERT_HTTP(body->path.getValue("") != "", Status::CODE_400, - "Missing Parameters"); - - auto res = ReturnConfigDTO::createShared(); - res->status = "getConfig"; - if (m_configManager.expired()) { - res->status = "error"; - res->code = 500; - res->error = "ConfigManager is null"; - } else { - std::string path = body->path.getValue(""); - if (auto tmp = m_configManager.lock()->getValue(path); tmp) { - res->status = "success"; - res->code = 200; - res->value = tmp.value().dump(); - res->type = "string"; - } else { - res->status = "error"; - res->code = 404; - res->error = "ConfigManager can't find the path"; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUISetConfig) { - info->summary = "Set config to global ConfigManager (thread safe)"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("POST", "/api/config/set", getUISetConfig) { - ENDPOINT_ASYNC_INIT(getUISetConfig); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUISetConfig::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - OATPP_ASSERT_HTTP(body->path.getValue("") != "", Status::CODE_400, - "Missing Parameters"); - OATPP_ASSERT_HTTP(body->value.getValue("") != "", Status::CODE_400, - "Missing Parameters"); - - auto res = StatusDto::createShared(); - res->command = "setConfig"; - - if (m_configManager.expired()) { - res->status = "error"; - res->code = 500; - res->error = "ConfigManager is null"; - } else { - std::string path = body->path.getValue(""); - std::string value = body->value.getValue(""); - if (m_configManager.lock()->setValue(path, value)) { - res->status = "success"; - res->code = 200; - } else { - res->status = "error"; - res->code = 404; - res->error = "Failed to set the value"; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUIDeleteConfig) { - info->summary = "Delete config from global ConfigManager (thread safe)"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("POST", "/api/config/delete", getUIDeleteConfig) { - ENDPOINT_ASYNC_INIT(getUIDeleteConfig); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIDeleteConfig::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - OATPP_ASSERT_HTTP(body->path.getValue("") != "", Status::CODE_400, - "Missing Parameters"); - - auto res = StatusDto::createShared(); - res->command = "deleteConfig"; - - if (m_configManager.expired()) { - res->status = "error"; - res->code = 500; - res->error = "ConfigManager is null"; - } else { - std::string path = body->path.getValue(""); - if (m_configManager.lock()->deleteValue(path)) { - res->status = "success"; - res->code = 200; - } else { - res->status = "error"; - res->code = 404; - res->error = "ConfigManager can't find the path"; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUILoadConfig) { - info->summary = - "Load config from file, this will be merged into the main config"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("POST", "/api/config/load", getUILoadConfig) { - ENDPOINT_ASYNC_INIT(getUILoadConfig); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUILoadConfig::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - OATPP_ASSERT_HTTP(body->path.getValue("") != "", Status::CODE_400, - "Missing Parameters"); - - auto res = StatusDto::createShared(); - res->command = "loadConfig"; - - if (m_configManager.expired()) { - res->status = "error"; - res->code = 500; - res->error = "ConfigManager is null"; - } else { - std::string path = body->path.getValue(""); - if (m_configManager.lock()->loadFromFile(path)) { - res->status = "success"; - res->code = 200; - } else { - res->status = "error"; - res->code = 404; - res->error = "ConfigManager can't find the path"; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUISaveConfig) { - info->summary = - "Save config to file (this will auto load the config after saving)"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("POST", "/api/config/save", getUISaveConfig) { - ENDPOINT_ASYNC_INIT(getUISaveConfig); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUISaveConfig::returnResponse); - } - Action returnResponse(const oatpp::Object& body) { - OATPP_ASSERT_HTTP(body->path.getValue("") != "", Status::CODE_400, - "Missing Parameters"); - OATPP_ASSERT_HTTP(body->isAbsolute.getValue(true) != true, - Status::CODE_400, "Missing Parameters"); - - auto res = StatusDto::createShared(); - res->command = "saveConfig"; - if (m_configManager.expired()) { - res->status = "error"; - res->code = 500; - res->error = "ConfigManager is null"; - } else { - std::string path = body->path.getValue(""); - bool isAbsolute = body->isAbsolute.getValue(true); - if (m_configManager.lock()->saveToFile(path)) { - res->status = "success"; - res->code = 200; - } else { - res->status = "error"; - res->code = 404; - res->error = "Failed to save the config"; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; -}; - -std::weak_ptr ConfigController::m_configManager = {}; - -#include OATPP_CODEGEN_END(ApiController) //<- End Codegen - -#endif // LITHIUM_ASYNC_SCRIPT_CONTROLLER_HPP diff --git a/modules/lithium.webserver/include/controller/AsyncDeviceController.hpp b/modules/lithium.webserver/include/controller/AsyncDeviceController.hpp deleted file mode 100644 index 7c88b0ee..00000000 --- a/modules/lithium.webserver/include/controller/AsyncDeviceController.hpp +++ /dev/null @@ -1,541 +0,0 @@ -/* - * AsyncDeviceController.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-11-17 - -Description: Device Routes - -**************************************************/ - -#ifndef LITHIUM_ASYNC_DEVICE_CONTROLLER_HPP -#define LITHIUM_ASYNC_DEVICE_CONTROLLER_HPP - -#include "oatpp/core/macro/codegen.hpp" -#include "oatpp/core/macro/component.hpp" -#include "oatpp/parser/json/mapping/ObjectMapper.hpp" -#include "oatpp/web/server/api/ApiController.hpp" - -#include "data/DeviceDto.hpp" -#include "data/IODto.hpp" - -enum class DeviceType { - Camera, - Telescope, - Focuser, - FilterWheel, - Solver, - Guider, - NumDeviceTypes -}; - -inline const char* DeviceTypeToString(DeviceType type) { - switch (type) { - case DeviceType::Camera: - return "Camera"; - break; - case DeviceType::Telescope: - return "Telescope"; - break; - case DeviceType::Focuser: - return "Focuser"; - break; - case DeviceType::FilterWheel: - return "FilterWheel"; - break; - case DeviceType::Solver: - return "Solver"; - break; - case DeviceType::Guider: - return "Guider"; - break; - default: - return "Unknown"; - break; - } - return "Unknown"; -} - -inline DeviceType StringToDeviceType(const std::string& type) { - if (type == "Camera") - return DeviceType::Camera; - else if (type == "Telescope") - return DeviceType::Telescope; - else if (type == "Focuser") - return DeviceType::Focuser; - else if (type == "FilterWheel") - return DeviceType::FilterWheel; - else if (type == "Solver") - return DeviceType::Solver; - else if (type == "Guider") - return DeviceType::Guider; - else - return DeviceType::NumDeviceTypes; -} - -#include "magic_enum/magic_enum.hpp" - -#include "lithiumapp.hpp" - -#include OATPP_CODEGEN_BEGIN(ApiController) //<- Begin Codegen - -class DeviceController : public oatpp::web::server::api::ApiController { -public: - DeviceController(const std::shared_ptr& objectMapper) - : oatpp::web::server::api::ApiController(objectMapper) {} - -public: - static std::shared_ptr createShared( - OATPP_COMPONENT(std::shared_ptr, objectMapper)) { - return std::make_shared(objectMapper); - } - -public: - // ---------------------------------------------------------------- - // Device Library Http Handler - // ---------------------------------------------------------------- - - ENDPOINT_INFO(getUIAddDeviceLibrary) { - info->summary = - "Add device library with information into DeviceManager"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("GET", "/api/device/add_device_library", - getUIAddDeviceLibrary) { - ENDPOINT_ASYNC_INIT(getUIAddDeviceLibrary); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIAddDeviceLibrary::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - if (body->library_path.getValue("") == "" || - body->library_name.getValue("") == "") { - res->error = "Invalid Parameters"; - res->message = "Device library path and name is required"; - } else { - auto library_path = body->library_path.getValue(""); - auto library_name = body->library_name.getValue(""); - if (!lithium::MyApp->addDeviceLibrary( - {{"lib_path", library_path}, - {"lib_name", library_name}})) { - res->error = "DeviceError"; - res->message = "Failed to add device library"; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUIRemoveDeviceLibrary) { - info->summary = - "Remove device library with information from DeviceManager"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("GET", "/api/device/remove_device_library", - getUIRemoveDeviceLibrary) { - ENDPOINT_ASYNC_INIT(getUIRemoveDeviceLibrary); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIRemoveDeviceLibrary::returnResponse); - } - - Action returnResponse( - const oatpp::Object& body) { - auto res = StatusDto::createShared(); - if (body->library_name.getValue("") == "") { - res->error = "Invalid Parameters"; - res->message = "Device library path and name is required"; - } else { - auto library_name = body->library_name.getValue(""); - if (!lithium::MyApp->removeDeviceLibrary(library_name)) { - res->error = "DeviceError"; - res->message = "Failed to add device library"; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - // ---------------------------------------------------------------- - // Device Http Handler - // ---------------------------------------------------------------- - - ENDPOINT_INFO(getUIAddDevice) { - info->summary = "Add device from device library into DeviceManager"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("GET", "/api/device/add_device", getUIAddDevice) { - ENDPOINT_ASYNC_INIT(getUIAddDevice); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIAddDevice::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - if (body->device_name.getValue("") == "" || - body->device_type.getValue("") == "" || - body->library_name.getValue("") == "") { - res->error = "Invalid Parameters"; - res->message = - "Device library path and device name and type are required"; - } else { - auto device_name = body->device_name.getValue(""); - auto device_type = body->device_type.getValue(""); - auto library_name = body->library_name.getValue(""); - - DeviceType device_type_ = StringToDeviceType(device_type); - if (device_type_ == DeviceType::NumDeviceTypes) { - res->error = "Invalid Parameters"; - res->message = "Unsupported device type"; - } else { - if (!lithium::MyApp->addDevice( - {{"type", device_type}, - {"device_name", device_name}, - {"library_name", library_name}})) - ; - { - res->error = "DeviceError"; - res->message = "Failed to add device"; - } - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUIRemoveDevice) { - info->summary = "Remove device with information from DeviceManager"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("GET", "/api/device/remove_device", getUIRemoveDevice) { - ENDPOINT_ASYNC_INIT(getUIRemoveDevice); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIRemoveDevice::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - if (body->device_name.getValue("") == "") { - res->error = "Invalid Parameters"; - res->message = "Device name is required"; - } else { - auto device_name = body->device_name.getValue(""); - if (!lithium::MyApp->removeDeviceByName(device_name)) { - res->error = "DeviceError"; - res->message = "Failed to remove device"; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - // ---------------------------------------------------------------- - // Device Property Functions - // ---------------------------------------------------------------- - - ENDPOINT_INFO(getUIGetProperty) { - info->summary = "Get a specific property from the specified device"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("GET", "/api/device/get_property", getUIGetProperty) { - ENDPOINT_ASYNC_INIT(getUIGetProperty); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIGetProperty::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - if (body->device_name.getValue("") == "") { - res->error = "Invalid Parameters"; - res->message = "Device name is required"; - } else { - auto device_name = body->device_name.getValue(""); - if (!lithium::MyApp->removeDeviceByName(device_name)) { - res->error = "DeviceError"; - res->message = "Failed to remove device"; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUISetProperty) { - info->summary = - "Set a specific property from the specified device with new " - "property " - "value"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("GET", "/api/device/set_property", getUISetProperty) { - ENDPOINT_ASYNC_INIT(getUISetProperty); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUISetProperty::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - if (body->device_name.getValue("") == "") { - res->error = "Invalid Parameters"; - res->message = "Device name is required"; - } else { - auto device_name = body->device_name.getValue(""); - if (!lithium::MyApp->removeDeviceByName(device_name)) { - res->error = "DeviceError"; - res->message = "Failed to remove device"; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - // ---------------------------------------------------------------- - // Device Task Functions - // ---------------------------------------------------------------- - - ENDPOINT_INFO(getUIRunDeviceFunc) { - info->summary = "Run a specific task from device"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("GET", "/api/device/run_device_func", getUIRunDeviceFunc) { - ENDPOINT_ASYNC_INIT(getUIRunDeviceFunc); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIRunDeviceFunc::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - if (body->device_name.getValue("") == "") { - res->error = "Invalid Parameters"; - res->message = "Device name is required"; - } else { - auto device_name = body->device_name.getValue(""); - if (!lithium::MyApp->removeDeviceByName(device_name)) { - res->error = "DeviceError"; - res->message = "Failed to remove device"; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUIGetDeviceFunc) { - info->summary = "Get a specific task's infomation from device"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("GET", "/api/device/get_device_func", getUIGetDeviceFunc) { - ENDPOINT_ASYNC_INIT(getUIGetDeviceFunc); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIGetDeviceFunc::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - if (body->device_name.getValue("") == "") { - res->error = "Invalid Parameters"; - res->message = "Device name is required"; - } else { - auto device_name = body->device_name.getValue(""); - if (!lithium::MyApp->removeDeviceByName(device_name)) { - res->error = "DeviceError"; - res->message = "Failed to remove device"; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - // ---------------------------------------------------------------- - // Device Common Interface - // ---------------------------------------------------------------- - - ENDPOINT_INFO(getUIConnectDevice) { - info->summary = - "Connect to a specific device with name (must be unique)."; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("GET", "/api/device/connect", getUIConnectDevice) { - ENDPOINT_ASYNC_INIT(getUIConnectDevice); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIConnectDevice::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - if (body->device_name.getValue("") == "") { - res->error = "Invalid Parameters"; - res->message = "Device name is required"; - } else { - auto device_name = body->device_name.getValue(""); - if (!lithium::MyApp->removeDeviceByName(device_name)) { - res->error = "DeviceError"; - res->message = "Failed to remove device"; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUIDisconnectDevice) { - info->summary = - "Disconnect from a specific device with name (must be unique)."; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("GET", "/api/device/disconnect", getUIDisconnectDevice) { - ENDPOINT_ASYNC_INIT(getUIDisconnectDevice); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIDisconnectDevice::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - if (body->device_name.getValue("") == "") { - res->error = "Invalid Parameters"; - res->message = "Device name is required"; - } else { - auto device_name = body->device_name.getValue(""); - if (!lithium::MyApp->removeDeviceByName(device_name)) { - res->error = "DeviceError"; - res->message = "Failed to remove device"; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUIReconnectDevice) { - info->summary = - "Reconnect to a specific device with name (must be unique and the " - "device must already connect successfully)."; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("GET", "/api/device/reconnect", getUIReconnectDevice) { - ENDPOINT_ASYNC_INIT(getUIReconnectDevice); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIReconnectDevice::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - if (body->device_name.getValue("") == "") { - res->error = "Invalid Parameters"; - res->message = "Device name is required"; - } else { - auto device_name = body->device_name.getValue(""); - if (!lithium::MyApp->removeDeviceByName(device_name)) { - res->error = "DeviceError"; - res->message = "Failed to remove device"; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUIScanDevice) { - info->summary = - "Scan a specific type of the devices and return a list of " - "available " - "devices."; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("GET", "/api/device/scan", getUIScanDevice) { - ENDPOINT_ASYNC_INIT(getUIScanDevice); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIScanDevice::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - if (body->device_type.getValue("") == "") { - res->error = "Invalid Parameters"; - res->message = "Device name is required"; - } else { - auto device_name = body->device_type.getValue(""); - if (!lithium::MyApp->removeDeviceByName(device_name)) { - res->error = "DeviceError"; - res->message = "Failed to remove device"; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; -}; - -#include OATPP_CODEGEN_END(ApiController) //<- End Codegen - -#endif // LITHIUM_ASYNC_DEVICE_CONTROLLER_HPP diff --git a/modules/lithium.webserver/include/controller/AsyncIOController.hpp b/modules/lithium.webserver/include/controller/AsyncIOController.hpp deleted file mode 100644 index 0f3c2b91..00000000 --- a/modules/lithium.webserver/include/controller/AsyncIOController.hpp +++ /dev/null @@ -1,439 +0,0 @@ -/* - * AsyncIOController.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-7-13 - -Description: IO Route - -**************************************************/ - -#ifndef LITHIUM_ASYNC_IO_CONTROLLER_HPP -#define LITHIUM_ASYNC_IO_CONTROLLER_HPP - -#include "atom/io/compress.hpp" -#include "atom/io/file.hpp" -#include "atom/io/io.hpp" - -#include "oatpp/core/macro/codegen.hpp" -#include "oatpp/core/macro/component.hpp" -#include "oatpp/parser/json/mapping/ObjectMapper.hpp" -#include "oatpp/web/server/api/ApiController.hpp" - -#include "data/IODto.hpp" -#include "data/StatusDto.hpp" - -#include "atom/type/json.hpp" - -#include OATPP_CODEGEN_BEGIN(ApiController) //<- Begin Codegen - -class IOController : public oatpp::web::server::api::ApiController { -public: - IOController(const std::shared_ptr& objectMapper) - : oatpp::web::server::api::ApiController(objectMapper) {} - - // ---------------------------------------------------------------- - // Pointer creator - // ---------------------------------------------------------------- - - static std::shared_ptr createShared( - OATPP_COMPONENT(std::shared_ptr, objectMapper)) { - return std::make_shared(objectMapper); - } - - // ---------------------------------------------------------------- - // IO Http Handler - // ---------------------------------------------------------------- - - ENDPOINT_INFO(getUICreateDirectory) { - info->summary = "Create a directory with specific path"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("POST", "/api/io/directory/create", getUICreateDirectory) { - ENDPOINT_ASYNC_INIT(getUICreateDirectory); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUICreateDirectory::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - OATPP_ASSERT_HTTP(body->path.getValue("") != "", Status::CODE_400, - "Missing Parameters"); - auto res = StatusDto::createShared(); - res->command = "createDirectory"; - auto path = body->path.getValue(""); - auto isAbsolute = body->isAbsolute.getValue(false); - if (isAbsolute && !atom::io::isAbsolutePath(path)) { - res->status = "error"; - res->error = "Invalid Parameters"; - res->message = "Directory path must be a absolute path"; - } else { - if (!atom::io::createDirectory(path)) { - res->status = "error"; - res->error = "IO Failed"; - res->code = 500; - res->message = "Failed to create directory"; - } else { - res->status = "success"; - res->message = "Successfully created directory"; - res->code = 200; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUIRemoveDirectory) { - info->summary = "Remove a directory with specific path"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("POST", "/api/io/directory/remove", getUIRemoveDirectory) { - ENDPOINT_ASYNC_INIT(getUIRemoveDirectory); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIRemoveDirectory::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - OATPP_ASSERT_HTTP(body->path.getValue("") != "", Status::CODE_400, - "Missing Parameters"); - - auto res = StatusDto::createShared(); - res->command = "removeDirectory"; - auto path = body->path.getValue(""); - auto isAbsolute = body->isAbsolute.getValue(false); - - if (isAbsolute && !atom::io::isAbsolutePath(path)) { - res->status = "error"; - res->code = 500; - res->error = "Invalid Parameters"; - res->message = "Directory path must be a absolute path"; - } else { - if (!atom::io::removeDirectory(path)) { - res->status = "error"; - res->code = 500; - res->error = "IO Failed"; - res->message = "Failed to remove directory"; - } else { - res->status = "success"; - res->message = "Successfully removed directory"; - res->code = 200; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUIRenameDirectory) { - info->summary = "Rename a directory with specific path and new name"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("POST", "/api/io/directory/rename", getUIRenameDirectory) { - ENDPOINT_ASYNC_INIT(getUIRenameDirectory); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIRenameDirectory::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - OATPP_ASSERT_HTTP(body->path.getValue("") != "", Status::CODE_400, - "Missing Parameters"); - OATPP_ASSERT_HTTP(body->name.getValue("") != "", Status::CODE_400, - "Missing Parameters"); - - auto res = StatusDto::createShared(); - res->command = "renameDirectory"; - - auto path = body->path.getValue(""); - auto isAbsolute = body->isAbsolute.getValue(false); - auto name = body->name.getValue(""); - - if (!atom::io::isFolderNameValid(name)) { - res->status = "error"; - res->code = 500; - res->error = "Invalid Parameters"; - res->message = "New folder name must be valid"; - } - if (isAbsolute && !atom::io::isAbsolutePath(path)) { - res->status = "error"; - res->code = 500; - res->error = "Invalid Parameters"; - res->message = "Directory path must be a absolute path"; - } else { - if (!atom::io::renameDirectory(path, name)) { - res->status = "error"; - res->code = 500; - res->error = "IO Failed"; - res->message = "Failed to rename directory"; - } else { - res->status = "success"; - res->message = "Successfully renamed directory"; - res->code = 200; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUIMoveDirectory) { - info->summary = "Move a directory with specific path and new path"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("POST", "/api/io/directory/move", getUIMoveDirectory) { - ENDPOINT_ASYNC_INIT(getUIMoveDirectory); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIMoveDirectory::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - OATPP_ASSERT_HTTP(body->path.getValue("") != "", Status::CODE_400, - "Missing Parameters"); - OATPP_ASSERT_HTTP(body->new_path.getValue("") != "", - Status::CODE_400, "Missing Parameters"); - - auto res = StatusDto::createShared(); - res->command = "moveDirectory"; - auto old_path = body->path.getValue(""); - auto new_path = body->new_path.getValue(""); - if ((!atom::io::isAbsolutePath(old_path) || - !atom::io::isAbsolutePath(new_path))) { - res->status = "error"; - res->code = 500; - res->error = "Invalid Parameters"; - res->message = "Directory path must be a absolute path"; - } else { - if (!atom::io::moveDirectory(old_path, new_path)) { - res->status = "error"; - res->code = 500; - res->error = "IO Failed"; - res->message = "Failed to move directory"; - } else { - res->status = "success"; - res->message = "Successfully moved directory"; - res->code = 200; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUICopyFile) { - info->summary = "Copy a file to a new path"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("POST", "/api/io/file/copy", getUICopyFile) { - ENDPOINT_ASYNC_INIT(getUICopyFile); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUICopyFile::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - OATPP_ASSERT_HTTP(body->path.getValue("") != "", Status::CODE_400, - "Missing Parameters"); - OATPP_ASSERT_HTTP(body->new_path.getValue("") != "", - Status::CODE_400, "Missing Parameters"); - - auto res = StatusDto::createShared(); - res->command = "copyFile"; - auto old_path = body->path.getValue(""); - auto new_path = body->new_path.getValue(""); - auto isAbsolute = body->isAbsolute.getValue(false); - - if (isAbsolute && !atom::io::isAbsolutePath(old_path) || - !atom::io::isAbsolutePath(new_path)) { - res->status = "error"; - res->code = 500; - res->error = "Invalid Parameters"; - res->message = "Directory path must be a absolute path"; - } else { - if (!atom::io::copyFile(old_path, new_path)) { - res->status = "error"; - res->code = 500; - res->error = "IO Failed"; - res->message = "Failed to copy file"; - } else { - res->status = "success"; - res->message = "Successfully moved file"; - res->code = 200; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUIMoveFile) { - info->summary = "Move a file to a new path"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("POST", "/api/io/file/move", getUIMoveFile) { - ENDPOINT_ASYNC_INIT(getUIMoveFile); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIMoveFile::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - OATPP_ASSERT_HTTP(body->path.getValue("") != "", Status::CODE_400, - "Missing Parameters"); - OATPP_ASSERT_HTTP(body->new_path.getValue("") != "", - Status::CODE_400, "Missing Parameters"); - - auto res = StatusDto::createShared(); - res->command = "moveFile"; - - auto old_path = body->path.getValue(""); - auto new_path = body->new_path.getValue(""); - auto isAbsolute = body->isAbsolute.getValue(false); - - if (isAbsolute && !atom::io::isAbsolutePath(old_path) || - !atom::io::isAbsolutePath(new_path)) { - res->status = "error"; - res->code = 500; - res->error = "Invalid Parameters"; - res->message = "Directory path must be a absolute path"; - } else { - if (!atom::io::copyFile(old_path, new_path)) { - res->status = "error"; - res->code = 500; - res->error = "IO Failed"; - res->message = "Failed to move file"; - } else { - res->status = "success"; - res->message = "Successfully moved file"; - res->code = 200; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUIRenameFile) { - info->summary = "Move a file to a new path"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("POST", "/api/io/file/rename", getUIRenameFile) { - ENDPOINT_ASYNC_INIT(getUIRenameFile); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIRenameFile::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - OATPP_ASSERT_HTTP(body->path.getValue("") != "", Status::CODE_400, - "Missing Parameters"); - OATPP_ASSERT_HTTP(body->new_name.getValue("") != "", - Status::CODE_400, "Missing Parameters"); - - auto res = StatusDto::createShared(); - res->command = "renameFile"; - - auto old_name = body->path.getValue(""); - auto new_name = body->new_name.getValue(""); - auto isAbsolute = body->isAbsolute.getValue(false); - - if (isAbsolute && !atom::io::isAbsolutePath(old_name)) { - res->status = "error"; - res->code = 500; - res->error = "Invalid Parameters"; - res->message = "Directory path must be a absolute path"; - } else { - if (!atom::io::renameFile(old_name, new_name)) { - res->status = "error"; - res->code = 500; - res->error = "IO Failed"; - res->message = "Failed to rename file"; - } else { - res->status = "success"; - res->message = "Successfully renamed file"; - res->code = 200; - } - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUIRemoveFile) { - info->summary = "Remove a file with full path"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("POST", "/api/io/file/remove", getUIRemoveFile) { - ENDPOINT_ASYNC_INIT(getUIRemoveFile); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIRemoveFile::returnResponse); - } - - Action returnResponse(const oatpp::Object& body) { - OATPP_ASSERT_HTTP(body->path.getValue("") != "", Status::CODE_400, - "Missing Parameters"); - - auto res = StatusDto::createShared(); - res->command = "removeFile"; - - auto name = body->path.getValue(""); - auto isAbsolute = body->isAbsolute.getValue(false); - - if (isAbsolute && !atom::io::isAbsolutePath(name)) { - res->status = "error"; - res->code = 500; - res->error = "Invalid Parameters"; - res->message = "Directory path must be a absolute path"; - } - if (!atom::io::removeFile(name)) { - res->error = "IO Failed"; - res->message = "Failed to remove file"; - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; -}; - -#include OATPP_CODEGEN_END(ApiController) //<- End Codegen - -#endif // LITHIUM_ASYNC_IO_CONTROLLER_HPP diff --git a/modules/lithium.webserver/include/controller/AsyncModuleController.hpp b/modules/lithium.webserver/include/controller/AsyncModuleController.hpp deleted file mode 100644 index 264a2aa0..00000000 --- a/modules/lithium.webserver/include/controller/AsyncModuleController.hpp +++ /dev/null @@ -1,341 +0,0 @@ -/* - * AsyncModuleController.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-7-13 - -Description: Module Route - -**************************************************/ - -#ifndef ASYNC_MODULE_CONTROLLER_HPP -#define ASYNC_MODULE_CONTROLLER_HPP - -#include "oatpp/core/macro/codegen.hpp" -#include "oatpp/core/macro/component.hpp" -#include "oatpp/parser/json/mapping/ObjectMapper.hpp" -#include "oatpp/web/server/api/ApiController.hpp" - -#include "data/ModuleDto.hpp" -#include "data/StatusDto.hpp" - -#include "atom/plugin/module_loader.hpp" -#include "atom/server/global_ptr.hpp" - -#include "template/variable.hpp" - -#if __cplusplus >= 202002L -#include -#else -#include -#endif - -#include OATPP_CODEGEN_BEGIN(ApiController) //<- Begin Codegen - -class ModuleController : public oatpp::web::server::api::ApiController { -public: - static std::shared_ptr m_moduleLoader; - - ModuleController(const std::shared_ptr& objectMapper) - : oatpp::web::server::api::ApiController(objectMapper) {} - -public: - static std::shared_ptr createShared( - OATPP_COMPONENT(std::shared_ptr, objectMapper)) { - return std::make_shared(objectMapper); - } - -public: - ENDPOINT_INFO(getUILoadModule) { - info->summary = "Load a plugin module from the specified path"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - info->addResponse>(Status::CODE_400, - "application/json"); - } - ENDPOINT_ASYNC("GET", "/api/module/load", getUILoadModule){ - ENDPOINT_ASYNC_INIT(getUILoadModule) Action act() - override{return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUILoadModule::returnResponse); -} - -Action -returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - OATPP_ASSERT_HTTP(body->plugin_path.getValue("") != "", Status::CODE_400, - "Invalid Parameters"); - OATPP_ASSERT_HTTP(body->plugin_name.getValue("") != "", Status::CODE_400, - "Invalid Parameters"); - auto plugin_path = body->plugin_path.getValue(""); - auto plugin_name = body->plugin_name.getValue(""); - if (!m_moduleLoader->LoadModule(plugin_path, plugin_name)) { - res->error = "ModuleError"; -#if __cplusplus >= 202002L - res->message = std::format("Failed to load module: {}", plugin_name); -#else - res->message = fmt::format("Failed to load module: {}", plugin_name); -#endif - } - return _return(controller->createDtoResponse(Status::CODE_200, res)); -} -} -; - -ENDPOINT_INFO(getUIUnloadModule) { - info->summary = "Unload module by name"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, "application/json"); -} -ENDPOINT_ASYNC("GET", "/api/module/unload", getUIUnloadModule){ - ENDPOINT_ASYNC_INIT(getUIUnloadModule) Action act() - override{return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIUnloadModule::returnResponse); -} - -Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - OATPP_ASSERT_HTTP(body->plugin_name.getValue("") != "", Status::CODE_400, - "Invalid Parameters"); - auto plugin_name = body->plugin_name.getValue(""); - if (!lithium::MyApp->unloadModule(plugin_name)) { - res->error = "ModuleError"; - res->message = fmt::format("Failed to unload module: {}", plugin_name); - } - return _return(controller->createDtoResponse(Status::CODE_200, res)); -} -} -; - -ENDPOINT_INFO(getUIGetModuleList) { - info->summary = "Get module list"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, "application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); -} -ENDPOINT_ASYNC("GET", "/api/module/list", getUIGetModuleList){ - ENDPOINT_ASYNC_INIT(getUIGetModuleList) Action act() - override{return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIGetModuleList::returnResponse); -} - -Action returnResponse(const oatpp::Object& body) { - auto res = ReturnModuleListDto::createShared(); - OATPP_ASSERT_HTTP(body->plugin_path.getValue("") != "", Status::CODE_400, - "Invalid Parameters"); - auto plugin_path = body->plugin_path.getValue(""); - auto module_list = lithium::MyApp->getModuleList(); - for (auto module : module_list) { - res->module_list->push_back(module); - OATPP_LOGD("ModuleController", "Module: %s", module.c_str()); - } - return _return(controller->createDtoResponse(Status::CODE_200, res)); -} -} -; - -ENDPOINT_INFO(getUIRefreshModuleLists) { - info->summary = "Refresh module list"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, "application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); -} -ENDPOINT_ASYNC("GET", "/api/module/refresh", getUIRefreshModuleLists){ - ENDPOINT_ASYNC_INIT(getUIRefreshModuleLists) Action act() - override{return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIRefreshModuleLists::returnResponse); -} - -Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - return _return(controller->createDtoResponse(Status::CODE_200, res)); -} -} -; - -ENDPOINT_INFO(getUIEnableModule) { - info->summary = "Enable module by name"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, "application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); -} -ENDPOINT_ASYNC("GET", "/api/module/enable", getUIEnableModule){ - ENDPOINT_ASYNC_INIT(getUIEnableModule) Action act() - override{return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIEnableModule::returnResponse); -} - -Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - OATPP_ASSERT_HTTP(body->plugin_name.getValue("") != "", Status::CODE_400, - "Invalid Parameters"); - auto plugin_name = body->plugin_name.getValue(""); - if (!lithium::MyApp->enableModule(plugin_name)) { - res->error = "ModuleError"; - res->message = fmt::format("Failed to enable module: {}", plugin_name); - } - return _return(controller->createDtoResponse(Status::CODE_200, res)); -} -} -; - -ENDPOINT_INFO(getUIDisableModule) { - info->summary = "Disable module by name"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, "application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); -} -ENDPOINT_ASYNC("GET", "/api/module/disable", getUIDisableModule){ - ENDPOINT_ASYNC_INIT(getUIDisableModule) Action act() - override{return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIDisableModule::returnResponse); -} - -Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - OATPP_ASSERT_HTTP(body->plugin_name.getValue("") != "", Status::CODE_400, - "Invalid Parameters"); - auto plugin_name = body->plugin_name.getValue(""); - if (!lithium::MyApp->disableModule(plugin_name)) { - res->error = "ModuleError"; - res->message = fmt::format("Failed to disable module: {}", plugin_name); - } - return _return(controller->createDtoResponse(Status::CODE_200, res)); -} -} -; - -ENDPOINT_INFO(getUIGetModuleStatus) { - info->summary = "Get module status"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, "application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); -} -ENDPOINT_ASYNC("GET", "/api/module/status", getUIGetModuleStatus){ - ENDPOINT_ASYNC_INIT(getUIGetModuleStatus) Action act() - override{return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIGetModuleStatus::returnResponse); -} - -Action returnResponse(const oatpp::Object& body) { - auto res = ReturnModuleStatusDto::createShared(); - OATPP_ASSERT_HTTP(body->module_name.getValue("") != "", Status::CODE_400, - "Invalid Parameters"); - auto module_name = body->module_name.getValue(""); - auto module_status = lithium::MyApp->getModuleStatus(module_name); - if (module_status) { - res->module_status = module_status; - } else { - res->error = "ModuleError"; - res->message = - fmt::format("Failed to get module status: {}", module_name); - } - return _return(controller->createDtoResponse(Status::CODE_200, res)); -} -} -; - -ENDPOINT_INFO(getUIGetModuleConfig) { - info->summary = "Get module config"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, "application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); -} -ENDPOINT_ASYNC("GET", "/api/module/config", getUIGetModuleConfig){ - ENDPOINT_ASYNC_INIT(getUIGetModuleConfig) Action act() - override{return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIGetModuleConfig::returnResponse); -} - -Action returnResponse(const oatpp::Object& body) { - auto res = ReturnModuleConfigDto::createShared(); - OATPP_ASSERT_HTTP(body->module_name.getValue("") != "", Status::CODE_400, - "Invalid Parameters"); - auto module_name = body->module_name.getValue(""); - auto module_config = lithium::MyApp->getModuleConfig(module_name); - if (!module_config.empty()) { - res->module_config = module_config.dump(4); - } else { - res->error = "ModuleError"; - res->message = - fmt::format("Failed to get module config: {}", module_name); - } - return _return(controller->createDtoResponse(Status::CODE_200, res)); -} -} -; - -// This function is very dangerous, please use it carefully -// It will return the shared_ptr of the module, but we will do nothing with it -// So please make sure you know what you are doing -ENDPOINT_INFO(getUIGetInstance) { - info->summary = - "Get shared_ptr from a specific module and register it into global ptr " - "mamanger"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, "application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); -} -ENDPOINT_ASYNC("GET", "/api/module/get/instance", getUIGetInstance){ - ENDPOINT_ASYNC_INIT(getUIGetInstance) Action act() - override{return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIGetInstance::returnResponse); -} - -Action returnResponse(const oatpp::Object& body) { - auto res = ReturnModuleConfigDto::createShared(); - CHECK_VARIABLE(module_name, "Invalid Parameters") - CHECK_VARIABLE(instance_name, "Invalid Parameters") - CHECK_VARIABLE(instance_type, "Invalid Parameters") - CHECK_VARIABLE(get_func, "Invalid Parameters") - if (instance_type == "plugin" || instance_type == "module") { - AddPtr(instance_name, - GetPtr("ModuleLoader") - ->GetInstance(module_name, {}, instance_type)); - } else if (instance_type == "device") { - AddPtr(instance_name, - GetPtr("ModuleLoader") - ->GetInstance(module_name, {}, instance_type)); - } else { - res->error = "ModuleError"; - res->message = fmt::format("Failed to get instance: {}", instance_name); - } - return _return(controller->createDtoResponse(Status::CODE_200, res)); -} -} -; -} -; - -#include OATPP_CODEGEN_END(ApiController) //<- End Codegen - -#endif // ASYNC_MODULE_CONTROLLER_HPP diff --git a/modules/lithium.webserver/include/controller/AsyncPHD2Controller.hpp b/modules/lithium.webserver/include/controller/AsyncPHD2Controller.hpp deleted file mode 100644 index 60dd6150..00000000 --- a/modules/lithium.webserver/include/controller/AsyncPHD2Controller.hpp +++ /dev/null @@ -1,149 +0,0 @@ -/* - * AsyncPHD2Controller.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-7-13 - -Description: PHD2 Route - -**************************************************/ - -#ifndef ASYNC_PHD2_CONTROLLER_HPP -#define ASYNC_PHD2_CONTROLLER_HPP - -#include "lithiumapp.hpp" - -#include "data/PHD2Dto.hpp" -#include "data/StatusDto.hpp" - -#include "oatpp/core/macro/codegen.hpp" -#include "oatpp/core/macro/component.hpp" -#include "oatpp/parser/json/mapping/ObjectMapper.hpp" -#include "oatpp/web/server/api/ApiController.hpp" - -#include "atom/type/json.hpp" - -#include OATPP_CODEGEN_BEGIN(ApiController) //<- Begin Codegen - -class PHD2Controller : public oatpp::web::server::api::ApiController { -public: - PHD2Controller(const std::shared_ptr &objectMapper) - : oatpp::web::server::api::ApiController(objectMapper) {} - -public: - static std::shared_ptr createShared( - OATPP_COMPONENT(std::shared_ptr, objectMapper)) { - return std::make_shared(objectMapper); - } - - ENDPOINT_INFO(getUIStartPHD2API) { - info->summary = "Start PHD2 with some parameters"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, "text/json"); - info->addResponse>(Status::CODE_400, "text/json"); - } - ENDPOINT_ASYNC("GET", "/api/phd2/start", getUIStartPHD2API){ - - ENDPOINT_ASYNC_INIT(getUIStartPHD2API) - - Action act() - override{return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIStartPHD2API::returnResponse); -} - -Action -returnResponse(const oatpp::Object &body) { - auto res = StatusDto::createShared(); - res->command = "StartPHD2"; - res->code = 200; - auto params = body->phd2_params.getValue(""); - if (params != "") { - try { - nlohmann::json params_ = nlohmann::json::parse(params); - if (!lithium::MyApp->createProcess("phd2", "phd2")) { - res->error = "Process Failed"; - res->message = "Failed to start PHD2"; - } - } catch (const nlohmann::json::parse_error &e) { - res->error = "Invalid Parameters"; - res->message = "Failed to parse PHD2 parameters"; - } catch (const std::exception &e) { - res->error = "PHD2 Failed"; - res->message = "Unknown error happen when starting PHD2"; - } - } - return _return(controller->createDtoResponse(Status::CODE_200, res)); -} -} -; - -ENDPOINT_INFO(getUIStopPHD2ParamAPI) { - info->summary = "Stop PHD2"; - info->addResponse>(Status::CODE_200, "text/json"); -} -ENDPOINT_ASYNC("GET", "/api/phd2/stop", getUIStopPHD2ParamAPI){ - - ENDPOINT_ASYNC_INIT(getUIStopPHD2ParamAPI) - - Action act() override{auto res = StatusDto::createShared(); -res->command = "StopPHD2"; -if (!lithium::MyApp->terminateProcessByName("phd2")) { - res->error = "Process Failed"; - res->message = "Failed to stop PHD2"; -} -return _return(controller->createDtoResponse(Status::CODE_200, res)); -} -} -; - -ENDPOINT_INFO(getUIModifyPHD2ParamAPI) { - info->summary = "Modify PHD2 Parameter with name and value"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, "text/json"); -} -ENDPOINT_ASYNC("GET", "/api/phd2/modify", getUIModifyPHD2ParamAPI){ - - ENDPOINT_ASYNC_INIT(getUIModifyPHD2ParamAPI) - - Action act() override{ - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIModifyPHD2ParamAPI::returnResponse); -} - -Action returnResponse(const oatpp::Object &body) { - auto res = StatusDto::createShared(); - res->command = "ModifyPHD2Param"; - res->code = 200; - auto param_name = body->param_name.getValue(""); - auto param_value = body->param_value.getValue(""); - // OATPP_ASSERT_HTTP(param_name && param_value, Status::CODE_400, "parameter - // name and id should not be null"); - bool phd2_running = false; - for (auto process : lithium::MyApp->getRunningProcesses()) { - if (process.name == "phd2") { - phd2_running = true; - } - } - if (phd2_running) { - // PHD2参数热更新 - } else { - // PHD2参数冷更新,主要是修改配置文件 - } - return _return(controller->createDtoResponse(Status::CODE_200, res)); -} -} -; -} -; - -#include OATPP_CODEGEN_END(ApiController) //<- End Codegen - -#endif // ASYNC_PHD2_CONTROLLER_HPP diff --git a/modules/lithium.webserver/include/controller/AsyncProcessController.hpp b/modules/lithium.webserver/include/controller/AsyncProcessController.hpp deleted file mode 100644 index 6e72646a..00000000 --- a/modules/lithium.webserver/include/controller/AsyncProcessController.hpp +++ /dev/null @@ -1,243 +0,0 @@ -/* - * AsyncProcessController.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-7-13 - -Description: Process Route - -**************************************************/ - -#ifndef LITHIUM_ASYNC_PROCESS_CONTROLLER_HPP -#define LITHIUM_ASYNC_PROCESS_CONTROLLER_HPP - -#include "data/ProcessDto.hpp" -#include "data/StatusDto.hpp" - -#include "oatpp/core/macro/codegen.hpp" -#include "oatpp/core/macro/component.hpp" -#include "oatpp/parser/json/mapping/ObjectMapper.hpp" -#include "oatpp/web/server/api/ApiController.hpp" - -#include -#include - -#include "atom/server/global_ptr.hpp" -#include "atom/system/process.hpp" - -void replaceAll(std::string &str, auto &from, auto &to) { - size_t start_pos = 0; - while ((start_pos = str.find(from, start_pos)) != std::string::npos) { - str.replace(start_pos, from.length(), to); - start_pos += to.length(); - } -} - -#include OATPP_CODEGEN_BEGIN(ApiController) //<- Begin Codegen - -class ProcessController : public oatpp::web::server::api::ApiController { -private: - static std::shared_ptr m_processManager; - -public: - ProcessController(const std::shared_ptr &objectMapper) - : oatpp::web::server::api::ApiController(objectMapper) { - } - -public: - static std::shared_ptr createShared( - OATPP_COMPONENT(std::shared_ptr, objectMapper)) { - return std::make_shared(objectMapper); - } - - ENDPOINT_INFO(getUICreateProcess) { - info->summary = "Create Process with process name and id"; - info->addResponse>(Status::CODE_200, "text/json"); - info->addResponse>(Status::CODE_400, "text/text"); - info->pathParams.add("process-name").description = - "Name of the process want to start (must be available to execute)"; - info->pathParams.add("process-id").description = - "ID of the process , used to stop or get output"; - } - ENDPOINT_ASYNC("GET", "process/start/{process-name}/{process-id}", - getUICreateProcess) { - ENDPOINT_ASYNC_INIT(getUICreateProcess); - Action act() override { - auto res = StatusDto::createShared(); - res->command = "createProcess"; - auto processName = request->getPathVariable("process-name"); - auto processId = request->getPathVariable("process-id"); - OATPP_ASSERT_HTTP(processName != "" && processId != "", - Status::CODE_400, - "process name and id should not be null"); - if (!m_processManager->createProcess(processName, processId)) { - res->error = "Operate Error"; - res->message = "Failed to create process"; - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUICreateProcessAPI) { - info->summary = "Create Process with process name and id"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, "text/json"); - info->addResponse>(Status::CODE_400, "text/plain"); - } - ENDPOINT_ASYNC("GET", "/api/process/start", getUICreateProcessAPI) { - ENDPOINT_ASYNC_INIT(getUICreateProcessAPI); - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUICreateProcessAPI::returnResponse); - } - Action returnResponse(const oatpp::Object &body) { - auto res = StatusDto::createShared(); - res->command = "createProcess"; - auto processName = body->process_name.getValue(""); - auto processId = body->process_id.getValue(""); - OATPP_ASSERT_HTTP(processName != "" && processId != "", - Status::CODE_400, - "process name and id should not be null"); - if (!m_processManager->createProcess(processName, processId)) { - res->error = "Process Failed"; - res->message = "Failed to create process"; - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUIStopProcess) { - info->summary = "Stop Process with id"; - info->addResponse>(Status::CODE_200, "text/json"); - info->addResponse>(Status::CODE_400, "text/plain"); - info->pathParams.add("process-id").description = - "ID of the process"; - } - ENDPOINT_ASYNC("GET", "process/stop/{process-id}", getUIStopProcess) { - ENDPOINT_ASYNC_INIT(getUIStopProcess); - Action act() override { - auto res = StatusDto::createShared(); - res->command = "terminateProcess"; - auto processId = std::stoi(request->getPathVariable("process-id")); - OATPP_ASSERT_HTTP(processId, Status::CODE_400, - "process id should not be null"); - if (!m_processManager->terminateProcess(processId)) { - res->error = "Operate Error"; - res->message = "Failed to create process"; - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUITerminateProcessAPI) { - info->summary = "Terminate process with process and id"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, "text/json"); - info->addResponse>(Status::CODE_400, "text/plain"); - } - ENDPOINT_ASYNC("GET", "/api/process/stop", getUITerminateProcessAPI) { - ENDPOINT_ASYNC_INIT(getUITerminateProcessAPI) - - ; - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUITerminateProcessAPI::returnResponse); - } - - Action returnResponse(const oatpp::Object &body) { - auto res = StatusDto::createShared(); - res->command = "terminateProcess"; - auto processId = body->process_id.getValue(""); - OATPP_ASSERT_HTTP(processId != "", Status::CODE_400, - "process name and id should not be null"); - if (!m_processManager->terminateProcessByName(processId)) { - res->error = "Process Failed"; - res->message = "Failed to terminate process"; - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUIRunScript) { - info->summary = "Run script with script name and running id"; - info->addResponse>(Status::CODE_200, "text/json"); - info->addResponse>(Status::CODE_400, "text/plain"); - info->pathParams.add("script-name").description = - "Name of the script want to start (must be available to execute)"; - info->pathParams.add("script-id").description = - "ID of the script , used to stop or get output"; - } - ENDPOINT_ASYNC("GET", "process/start/{script-name}/{script-id}", - getUIRunScript) { - ENDPOINT_ASYNC_INIT(getUIRunScript) - - ; - Action act() override { - auto res = StatusDto::createShared(); - res->command = "runScript"; - auto scriptName = request->getPathVariable("script-name"); - auto scriptId = request->getPathVariable("script-id"); - OATPP_ASSERT_HTTP(scriptName && scriptId, Status::CODE_400, - "script name and id should not be null"); - if (!m_processManager->runScript(scriptName, scriptId)) { - res->error = "Operate Error"; - res->message = "Failed to run script"; - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUIRunScriptAPI) { - info->summary = "Run script with process name and id"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, "text/json"); - info->addResponse>(Status::CODE_400, "text/plain"); - } - ENDPOINT_ASYNC("GET", "/api/process/run", getUIRunScriptAPI) { - ENDPOINT_ASYNC_INIT(getUIRunScriptAPI) - - ; - Action act() override { - return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIRunScriptAPI::returnResponse); - } - - Action returnResponse(const oatpp::Object &body) { - auto res = StatusDto::createShared(); - res->command = "runScript"; - auto scriptId = body->script_id.getValue(""); - auto scriptName = body->script_name.getValue(""); - OATPP_ASSERT_HTTP(scriptId != "" && scriptName != "", - Status::CODE_400, - "script name and id should not be null"); - if (!m_processManager->runScript(scriptName, scriptId)) { - res->error = "Process Failed"; - res->message = "Failed to start script"; - } - return _return( - controller->createDtoResponse(Status::CODE_200, res)); - } - }; -}; - -std::shared_ptr ProcessController::m_processManager = - GetPtr("lithium.system.process"); - -#include OATPP_CODEGEN_END(ApiController) //<- End Codegen - -#endif // LITHIUM_ASYNC_PROCESS_CONTROLLER_HPP diff --git a/modules/lithium.webserver/include/controller/AsyncScriptController.hpp b/modules/lithium.webserver/include/controller/AsyncScriptController.hpp deleted file mode 100644 index b675996f..00000000 --- a/modules/lithium.webserver/include/controller/AsyncScriptController.hpp +++ /dev/null @@ -1,230 +0,0 @@ -/* - * AsyncScriptController.hpp - * - * Copyright (C) 2023-2024 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Date: 2023-11-18 - -Description: Async Script Controller - -**************************************************/ - -#ifndef Lithium_SCRIPTCONTROLLER_HPP -#define Lithium_SCRIPTCONTROLLER_HPP - -#include "oatpp/core/macro/codegen.hpp" -#include "oatpp/core/macro/component.hpp" -#include "oatpp/parser/json/mapping/ObjectMapper.hpp" -#include "oatpp/web/server/api/ApiController.hpp" - -#include "data/ScriptDto.hpp" -#include "data/StatusDto.hpp" - -#include "lithiumapp.hpp" - -#include OATPP_CODEGEN_BEGIN(ApiController) //<- Begin Codegen - -class ScriptController : public oatpp::web::server::api::ApiController { -public: - ScriptController(const std::shared_ptr& objectMapper) - : oatpp::web::server::api::ApiController(objectMapper) {} - -public: - // ---------------------------------------------------------------- - // Pointer creator - // ---------------------------------------------------------------- - /** - * Create shared pointer to &id:oatpp::web::server::api::ApiController;. - * @param objectMapper - &id:oatpp::data::mapping::ObjectMapper;. - */ - static std::shared_ptr createShared( - OATPP_COMPONENT(std::shared_ptr, objectMapper)) { - return std::make_shared(objectMapper); - } - - /** - * Create unique pointer to &id:oatpp::web::server::api::ApiController;. - * @param objectMapper - &id:oatpp::data::mapping::ObjectMapper;. - */ - static std::unique_ptr createUnique( - OATPP_COMPONENT(std::shared_ptr, objectMapper)) { - return std::make_unique(objectMapper); - } - -public: - // ---------------------------------------------------------------- - // Script Http Handler - // ---------------------------------------------------------------- - - ENDPOINT_INFO(getUIRunScript) { - info->summary = "Run a single line script and get the result"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, - "application/json"); - } - ENDPOINT_ASYNC("GET", "/api/script/run", getUIRunScript){ - ENDPOINT_ASYNC_INIT(getUIRunScript) Action act() - override{return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIRunScript::returnResponse); -} - -Action -returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - - auto script = body->script.getValue(""); - - return _return(controller->createDtoResponse(Status::CODE_200, res)); -} -} -; - -ENDPOINT_INFO(getUIRunCScriptFile) { - info->summary = "Run a script file and get the result"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, "application/json"); -} -ENDPOINT_ASYNC("GET", "/api/script/run", getUIRunCScriptFile){ - ENDPOINT_ASYNC_INIT(getUIRunCScriptFile) Action act() - override{return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIRunCScriptFile::returnResponse); -} - -Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - - return _return(controller->createDtoResponse(Status::CODE_200, res)); -} -} -; - -// ---------------------------------------------------------------- -// Some useful Functions about chaiscript -// ---------------------------------------------------------------- - -ENDPOINT_INFO(getUICheckScriptFile) { - info->summary = "Check script file"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, "application/json"); -} -ENDPOINT_ASYNC("GET", "/apt/script/check", getUICheckScriptFile){ - ENDPOINT_ASYNC_INIT(getUICheckScriptFile) Action act() - override{return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUICheckScriptFile::returnResponse); -} - -Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - - return _return(controller->createDtoResponse(Status::CODE_200, res)); -} -} -; - -ENDPOINT_INFO(getUIGetScriptFile) { - info->summary = "Get script file"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, "application/json"); -} -ENDPOINT_ASYNC("GET", "/api/script/get", getUIGetScriptFile){ - ENDPOINT_ASYNC_INIT(getUIGetScriptFile) Action act() - override{return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIGetScriptFile::returnResponse); -} - -Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - - return _return(controller->createDtoResponse(Status::CODE_200, res)); -} -} -; - -ENDPOINT_INFO(getUIListScriptFiles) { - info->summary = "List script files"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, "application/json"); -} -ENDPOINT_ASYNC("GET", "/api/script/list", getUIListScriptFiles){ - ENDPOINT_ASYNC_INIT(getUIListScriptFiles) Action act() - override{return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIListScriptFiles::returnResponse); -} - -Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - - return _return(controller->createDtoResponse(Status::CODE_200, res)); -} -} -; - -ENDPOINT_INFO(getUILoadScript) { - info->summary = "Load script into cache"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, "application/json"); -} -ENDPOINT_ASYNC("GET", "/api/script/load", getUILoadScript){ - ENDPOINT_ASYNC_INIT(getUILoadScript) Action act() - override{return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUILoadScript::returnResponse); -} -Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - - return _return(controller->createDtoResponse(Status::CODE_200, res)); -} -} -; - -ENDPOINT_INFO(getUIUnloadScriptFile) { - info->summary = "Unload script from cache"; - info->addConsumes>("application/json"); - info->addResponse>(Status::CODE_200, "application/json"); -} -ENDPOINT_ASYNC("GET", "/api/script/unload", getUIUnloadScriptFile){ - ENDPOINT_ASYNC_INIT(getUIUnloadScriptFile) Action act() - override{return request - ->readBodyToDtoAsync>( - controller->getDefaultObjectMapper()) - .callbackTo(&getUIUnloadScriptFile::returnResponse); -} -Action returnResponse(const oatpp::Object& body) { - auto res = StatusDto::createShared(); - - return _return(controller->createDtoResponse(Status::CODE_200, res)); -} -} -; -} -; - -#include OATPP_CODEGEN_END(ApiController) //<- End Codegen - -#endif // Lithium_SCRIPTCONTROLLER_HPP diff --git a/modules/lithium.webserver/include/controller/AsyncServerController.hpp b/modules/lithium.webserver/include/controller/AsyncServerController.hpp deleted file mode 100644 index 52ca7569..00000000 --- a/modules/lithium.webserver/include/controller/AsyncServerController.hpp +++ /dev/null @@ -1,53 +0,0 @@ -/* - * AsyncServerController.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-7-13 - -Description: Upload Route - -**************************************************/ - -// NOTE: 2024-3-9 Max: The 'ServerController' means INDI/Hydrogen and ASCOM -// Remote Server. All of the API is same as LightAPT Server - -#ifndef LITHIUM_ASYNC_SERVER_CONTROLLER_HPP -#define LITHIUM_ASYNC_SERVER_CONTROLLER_HPP - -#include "config.h" - -#include "oatpp/core/macro/codegen.hpp" -#include "oatpp/core/macro/component.hpp" -#include "oatpp/parser/json/mapping/ObjectMapper.hpp" -#include "oatpp/web/server/api/ApiController.hpp" - -namespace multipart = oatpp::web::mime::multipart; - -#include OATPP_CODEGEN_BEGIN(ApiController) //<- Begin Codegen - -class ServerController : public oatpp::web::server::api::ApiController { -public: - ServerController(const std::shared_ptr &objectMapper) - : oatpp::web::server::api::ApiController(objectMapper) {} - - // ---------------------------------------------------------------- - // Pointer creator - // ---------------------------------------------------------------- - - static std::shared_ptr createShared( - OATPP_COMPONENT(std::shared_ptr, objectMapper)) { - return std::make_shared(objectMapper); - } - - // ---------------------------------------------------------------- - // Server Http Handler - // ---------------------------------------------------------------- -}; - -#include OATPP_CODEGEN_END(ApiController) //<- End Codegen - -#endif // LITHIUM_ASYNC_SERVER_CONTROLLER_HPP diff --git a/modules/lithium.webserver/include/controller/AsyncStaticController.hpp b/modules/lithium.webserver/include/controller/AsyncStaticController.hpp deleted file mode 100644 index c56abdd0..00000000 --- a/modules/lithium.webserver/include/controller/AsyncStaticController.hpp +++ /dev/null @@ -1,348 +0,0 @@ -/* - * AsyncStaticController.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-7-13 - -Description: Static Route - -**************************************************/ - -#ifndef LITHIUM_ASYNC_STATIC_CONTROLLER_HPP -#define LITHIUM_ASYNC_STATIC_CONTROLLER_HPP - -#include "oatpp/core/macro/codegen.hpp" -#include "oatpp/core/macro/component.hpp" -#include "oatpp/parser/json/mapping/ObjectMapper.hpp" -#include "oatpp/web/server/api/ApiController.hpp" - -#include -#include -#include -#include -#include - -class StaticController : public oatpp::web::server::api::ApiController { -public: - StaticController(const std::shared_ptr &objectMapper) - : oatpp::web::server::api::ApiController(objectMapper) {} - - // ---------------------------------------------------------------- - // Pointer creator - // ---------------------------------------------------------------- - - static std::shared_ptr createShared( - OATPP_COMPONENT(std::shared_ptr, - objectMapper) // Inject objectMapper component here as - // default parameter - ) { - return std::make_shared(objectMapper); - } - - // ---------------------------------------------------------------- - // Static Loader - // ---------------------------------------------------------------- - - static std::string loadResource( - const std::string &path, - const std::unordered_set &allowedExtensions, - bool checkAllowed = true) { - std::string fullPath; - if (std::filesystem::path(path).is_absolute()) { - fullPath = path; - } else { - std::filesystem::path currentPath = std::filesystem::current_path(); - fullPath = (currentPath / path).string(); - } - - std::string fileExtension = - fullPath.substr(fullPath.find_last_of(".") + 1); - - if (checkAllowed && - allowedExtensions.find(fileExtension) == allowedExtensions.end()) { - throw std::runtime_error("File type not allowed: " + fileExtension); - } - - std::ifstream file(fullPath); - - if (!file.is_open()) { - throw std::runtime_error("Failed to open file: " + fullPath); - } - - try { - std::stringstream buffer; - buffer << file.rdbuf(); - return buffer.str(); - } catch (const std::exception &e) { - throw std::runtime_error("Failed to read file: " + fullPath + - ". Error: " + e.what()); - } - } - -#include OATPP_CODEGEN_BEGIN(ApiController) //<- Begin Codegen - - // ---------------------------------------------------------------- - // All of the Web page requests handled here - // ---------------------------------------------------------------- - - ENDPOINT_INFO(IndexRequestHandler) { info->summary = "'Root' endpoint"; } - ENDPOINT_ASYNC("GET", "/", IndexRequestHandler) { - ENDPOINT_ASYNC_INIT(IndexRequestHandler); - Action act() override { - auto response = controller->createResponse( - Status::CODE_200, loadResource("index.html", {"html"})); - response->putHeader(Header::CONTENT_TYPE, "text/html"); - return _return(response); - } - }; - - ENDPOINT_INFO(ClientRequestHandler) { info->summary = "'Client' endpoint"; } - ENDPOINT_ASYNC("GET", "/client", ClientRequestHandler) { - ENDPOINT_ASYNC_INIT(ClientRequestHandler); - Action act() override { - auto response = controller->createResponse( - Status::CODE_200, loadResource("client/index.html", {"html"})); - response->putHeader(Header::CONTENT_TYPE, "text/html"); - return _return(response); - } - }; - - ENDPOINT_INFO(NoVNCRequestHandler) { info->summary = "'NoVNC' endpoint"; } - ENDPOINT_ASYNC("GET", "/novnc", NoVNCRequestHandler) { - ENDPOINT_ASYNC_INIT(NoVNCRequestHandler); - Action act() override { - auto response = controller->createResponse( - Status::CODE_200, - loadResource("module/novnc/index.html", {"html"})); - response->putHeader(Header::CONTENT_TYPE, "text/html"); - return _return(response); - } - }; - - ENDPOINT_INFO(WebSSHRequestHandler) { info->summary = "'WebSSH' endpoint"; } - ENDPOINT_ASYNC("GET", "/webssh", WebSSHRequestHandler) { - ENDPOINT_ASYNC_INIT(WebSSHRequestHandler); - Action act() override { - auto response = controller->createResponse( - Status::CODE_200, - loadResource("module/webssh/index.html", {"html"})); - response->putHeader(Header::CONTENT_TYPE, "text/html"); - return _return(response); - } - }; - - ENDPOINT_INFO(Static) { info->summary = "'Static File' endpoint"; } - ENDPOINT_ASYNC("GET", "/static/*", Static) { - ENDPOINT_ASYNC_INIT(Static); - Action act() override { - auto tail = request->getPathTail(); - OATPP_ASSERT_HTTP(tail, Status::CODE_400, "Empty filename"); - oatpp::parser::Caret caret(tail); - auto pathLabel = caret.putLabel(); - caret.findChar('?'); - auto path = pathLabel.toString(); - auto buffer = loadResource( - path.getValue(""), - {"json", "js", "css", "html", "jpg", "png", "robot"}); - OATPP_ASSERT_HTTP(buffer != "", Status::CODE_500, - "Can't read file"); - return _return( - controller->createResponse(Status::CODE_200, buffer)); - } - }; - - // ---------------------------------------------------------------- - // Load Static Files (a little bit silly, but this is working) - // ---------------------------------------------------------------- - - ENDPOINT_INFO(AllStaticCSS) { info->summary = "All 'CSS File' endpoint"; } - ENDPOINT_ASYNC("GET", "/css/*", AllStaticCSS) { - ENDPOINT_ASYNC_INIT(AllStaticCSS); - Action act() override { - auto tail = request->getPathTail(); - OATPP_ASSERT_HTTP(tail, Status::CODE_400, "Empty filename"); - oatpp::parser::Caret caret(tail); - auto pathLabel = caret.putLabel(); - caret.findChar('?'); - auto path = "css/" + pathLabel.toString(); - auto buffer = loadResource( - path.getValue(""), - {"json", "js", "css", "html", "jpg", "png", "robot", "woff2", - "tff", "ico", "svg", "mp3", "oga", "woff", "ttf"}); - OATPP_ASSERT_HTTP(buffer != "", Status::CODE_500, - "Can't read file"); - return _return( - controller->createResponse(Status::CODE_200, buffer)); - } - }; - - ENDPOINT_INFO(AllStaticJS) { - info->summary = "All 'JavaScript File' endpoint"; - } - ENDPOINT_ASYNC("GET", "/js/*", AllStaticJS) { - ENDPOINT_ASYNC_INIT(AllStaticJS); - Action act() override { - auto tail = request->getPathTail(); - OATPP_ASSERT_HTTP(tail, Status::CODE_400, "Empty filename"); - oatpp::parser::Caret caret(tail); - auto pathLabel = caret.putLabel(); - caret.findChar('?'); - auto path = "js/" + pathLabel.toString(); - auto buffer = loadResource(path.getValue(""), {"js"}); - OATPP_ASSERT_HTTP(buffer != "", Status::CODE_500, - "Can't read file"); - return _return( - controller->createResponse(Status::CODE_200, buffer)); - } - }; - - ENDPOINT_INFO(AllStaticJSON) { info->summary = "All 'JSON File' endpoint"; } - ENDPOINT_ASYNC("GET", "/json/*", AllStaticJSON) { - ENDPOINT_ASYNC_INIT(AllStaticJSON); - Action act() override { - auto tail = request->getPathTail(); - OATPP_ASSERT_HTTP(tail, Status::CODE_400, "Empty filename"); - oatpp::parser::Caret caret(tail); - auto pathLabel = caret.putLabel(); - caret.findChar('?'); - auto path = "json/" + pathLabel.toString(); - auto buffer = loadResource(path.getValue(""), {"json"}); - OATPP_ASSERT_HTTP(buffer != "", Status::CODE_500, - "Can't read file"); - return _return( - controller->createResponse(Status::CODE_200, buffer)); - } - }; - - ENDPOINT_INFO(AllStaticFont) { info->summary = "All 'Font File' endpoint"; } - ENDPOINT_ASYNC("GET", "/font/*", AllStaticFont) { - ENDPOINT_ASYNC_INIT(AllStaticFont); - Action act() override { - auto tail = request->getPathTail(); - OATPP_ASSERT_HTTP(tail, Status::CODE_400, "Empty filename"); - oatpp::parser::Caret caret(tail); - auto pathLabel = caret.putLabel(); - caret.findChar('?'); - auto path = "font/" + pathLabel.toString(); - auto buffer = loadResource(path.getValue(""), - {"tff", "tff", "woff", "woff2", "eot"}); - OATPP_ASSERT_HTTP(buffer != "", Status::CODE_500, - "Can't read file"); - return _return( - controller->createResponse(Status::CODE_200, buffer)); - } - }; - - ENDPOINT_INFO(AllStaticNodeModules) { - info->summary = - "All 'Node Modules File' endpoint. This need to be changed to " - "normal " - "format"; - } - ENDPOINT_ASYNC("GET", "/node_modules/*", AllStaticNodeModules) { - ENDPOINT_ASYNC_INIT(AllStaticNodeModules); - Action act() override { - auto tail = request->getPathTail(); - OATPP_ASSERT_HTTP(tail, Status::CODE_400, "Empty filename"); - oatpp::parser::Caret caret(tail); - auto pathLabel = caret.putLabel(); - caret.findChar('?'); - auto path = "node_modules/" + pathLabel.toString(); - auto buffer = loadResource(path.getValue(""), {"css", "js"}); - OATPP_ASSERT_HTTP(buffer != "", Status::CODE_500, - "Can't read file"); - return _return( - controller->createResponse(Status::CODE_200, buffer)); - } - }; - - ENDPOINT_INFO(AllStaticSound) { - info->summary = "All 'Sound File' endpoint"; - } - ENDPOINT_ASYNC("GET", "/sounds/*", AllStaticSound) { - ENDPOINT_ASYNC_INIT(AllStaticSound); - Action act() override { - auto tail = request->getPathTail(); - OATPP_ASSERT_HTTP(tail, Status::CODE_400, "Empty filename"); - oatpp::parser::Caret caret(tail); - auto pathLabel = caret.putLabel(); - caret.findChar('?'); - auto path = "sounds/" + pathLabel.toString(); - auto buffer = loadResource(path.getValue(""), {"oga", "mp3"}); - OATPP_ASSERT_HTTP(buffer != "", Status::CODE_500, - "Can't read file"); - return _return( - controller->createResponse(Status::CODE_200, buffer)); - } - }; - - ENDPOINT_INFO(AllStaticTextures) { - info->summary = "All 'Textures File' endpoint"; - } - ENDPOINT_ASYNC("GET", "/textures/*", AllStaticTextures) { - ENDPOINT_ASYNC_INIT(AllStaticTextures); - Action act() override { - auto tail = request->getPathTail(); - OATPP_ASSERT_HTTP(tail, Status::CODE_400, "Empty filename"); - oatpp::parser::Caret caret(tail); - auto pathLabel = caret.putLabel(); - caret.findChar('?'); - auto path = "textures/" + pathLabel.toString(); - auto buffer = - loadResource(path.getValue(""), {"gif", "png", "svg", "jpg"}); - OATPP_ASSERT_HTTP(buffer != "", Status::CODE_500, - "Can't read file"); - return _return( - controller->createResponse(Status::CODE_200, buffer)); - } - }; - - ENDPOINT_INFO(AllStaticWebFonts) { - info->summary = "All 'WebFonts File' endpoint"; - } - ENDPOINT_ASYNC("GET", "/webfonts/*", AllStaticWebFonts) { - ENDPOINT_ASYNC_INIT(AllStaticWebFonts); - Action act() override { - auto tail = request->getPathTail(); - OATPP_ASSERT_HTTP(tail, Status::CODE_400, "Empty filename"); - oatpp::parser::Caret caret(tail); - auto pathLabel = caret.putLabel(); - caret.findChar('?'); - auto path = "webfonts/" + pathLabel.toString(); - auto buffer = loadResource(path.getValue(""), - {"eot", "svg", "ttf", "woff", "woff2"}); - OATPP_ASSERT_HTTP(buffer != "", Status::CODE_500, - "Can't read file"); - return _return( - controller->createResponse(Status::CODE_200, buffer)); - } - }; - - ENDPOINT_INFO(AllStaticWebAssets) { - info->summary = "All 'Assets File' endpoint"; - } - ENDPOINT_ASYNC("GET", "/assets/*", AllStaticWebAssets) { - ENDPOINT_ASYNC_INIT(AllStaticWebAssets); - Action act() override { - auto tail = request->getPathTail(); - OATPP_ASSERT_HTTP(tail, Status::CODE_400, "Empty filename"); - oatpp::parser::Caret caret(tail); - auto pathLabel = caret.putLabel(); - caret.findChar('?'); - auto path = "assets/" + pathLabel.toString(); - auto buffer = loadResource(path.getValue(""), {"css", "js"}); - OATPP_ASSERT_HTTP(buffer != "", Status::CODE_500, - "Can't read file"); - return _return( - controller->createResponse(Status::CODE_200, buffer)); - } - }; - -#include OATPP_CODEGEN_END(ApiController) //<- End Codegen -}; - -#endif // LITHIUM_ASYNC_STATIC_CONTROLLER_HPP diff --git a/modules/lithium.webserver/include/controller/AsyncSystemController.hpp b/modules/lithium.webserver/include/controller/AsyncSystemController.hpp deleted file mode 100644 index 344640ff..00000000 --- a/modules/lithium.webserver/include/controller/AsyncSystemController.hpp +++ /dev/null @@ -1,485 +0,0 @@ -/* - * SystemController.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-7-13 - -Description: System Route - -**************************************************/ - -#ifndef LITHIUM_ASYNC_SYSTEM_CONTROLLER_HPP -#define LITHIUM_ASYNC_SYSTEM_CONTROLLER_HPP - -#include "atom/system/module/battery.hpp" -#include "atom/system/module/cpu.hpp" -#include "atom/system/module/disk.hpp" -#include "atom/system/module/memory.hpp" -#include "atom/system/module/os.hpp" -#include "atom/system/module/wifi.hpp" -#include "atom/system/system.hpp" - -#include "oatpp/core/macro/codegen.hpp" -#include "oatpp/core/macro/component.hpp" -#include "oatpp/parser/json/mapping/ObjectMapper.hpp" -#include "oatpp/web/server/api/ApiController.hpp" - -#include "data/SystemDto.hpp" - -#include OATPP_CODEGEN_BEGIN(ApiController) //<- Begin Codegen - -class SystemController : public oatpp::web::server::api::ApiController { -public: - SystemController(const std::shared_ptr &objectMapper) - : oatpp::web::server::api::ApiController(objectMapper) {} - - // ---------------------------------------------------------------- - // Pointer creator - // ---------------------------------------------------------------- - - static std::shared_ptr createShared( - OATPP_COMPONENT(std::shared_ptr, objectMapper)) { - return std::make_shared(objectMapper); - } - - // ---------------------------------------------------------------- - // System Http Handler - // ---------------------------------------------------------------- - - // ---------------------------------------------------------------- - // CPU Methods - // ---------------------------------------------------------------- - - ENDPOINT_INFO(getUICpuUsage) { - info->summary = "Get current CPU usage"; - info->addResponse>( - Status::CODE_200, "application/json", "Usage of CPU"); - } - ENDPOINT_ASYNC("GET", "/api/system/cpu_usage", getUICpuUsage) { - ENDPOINT_ASYNC_INIT(getUICpuUsage); - Action act() override { - auto res = BaseReturnSystemDto::createShared(); - res->command = "getUICpuUsage"; - if (float cpu_usage = atom::system::getCurrentCpuUsage(); - cpu_usage <= 0.0f) { - res->status = "error"; - res->message = "Failed to get current CPU usage"; - res->error = "System Error"; - res->code = 500; - } else { - res->status = "success"; - res->code = 200; - res->value = cpu_usage; - res->message = "Success get current CPU usage"; - } - return _return(controller->createDtoResponse( - res->code == 500 ? Status::CODE_500 : Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUICpuTemperature) { - info->summary = "Get current CPU temperature"; - info->addResponse>( - Status::CODE_200, "application/json", "Temperature of CPU"); - } - ENDPOINT_ASYNC("GET", "/api/system/cpu_temp", getUICpuTemperature) { - ENDPOINT_ASYNC_INIT(getUICpuTemperature); - Action act() override { - auto res = BaseReturnSystemDto::createShared(); - res->command = "getUICpuTemperature"; - if (float cpu_temp = atom::system::getCurrentCpuTemperature(); - cpu_temp <= 0.0f) { - res->code = 500; - res->status = "error"; - res->message = "Failed to get current CPU temperature"; - res->error = "System Error"; - } else { - res->status = "success"; - res->code = 200; - res->value = cpu_temp; - res->message = "Success get current CPU temperature"; - } - return _return(controller->createDtoResponse( - res->code == 500 ? Status::CODE_500 : Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUICpuInfo) { - info->summary = "Get current CPU model"; - info->addResponse>( - Status::CODE_200, "application/json", "Model of CPU"); - } - ENDPOINT_ASYNC("GET", "/api/system/cpu_model", getUICpuInfo) { - ENDPOINT_ASYNC_INIT(getUICpuInfo); - Action act() override { - auto res = ReturnCpuInfoDto::createShared(); - res->command = "getUICpuInfo"; - - auto cpu_model = atom::system::getCPUModel(); - auto cpu_freq = atom::system::getProcessorFrequency(); - auto cpu_id = atom::system::getProcessorIdentifier(); - auto cpu_package = atom::system::getNumberOfPhysicalPackages(); - auto cpu_core = atom::system::getNumberOfPhysicalCPUs(); - - if (cpu_model.empty() || cpu_freq <= 0.0f || cpu_id.empty() || - cpu_package <= 0 || cpu_core <= 0) [[unlikely]] { - res->code = 500; - res->status = "error"; - res->error = "System Error"; - - if (cpu_model.empty()) { - res->message = "Failed to get current CPU model"; - } else if (cpu_freq <= 0.0f) { - res->message = "Failed to get current processor frequency"; - } else if (cpu_id.empty()) { - res->message = "Failed to get current processor identifier"; - } else if (cpu_package <= 0) { - res->message = "Failed to get current processor package"; - } else if (cpu_core <= 0) { - res->message = "Failed to get current processor core"; - } else { - res->message = - "Failed to get current processor information"; - } - } else [[likely]] { - res->status = "success"; - res->code = 200; - res->model = cpu_model; - res->frequency = cpu_freq; - res->identifier = cpu_id; - res->packages = cpu_package; - res->cpus = cpu_core; - res->message = "Success get current processor information"; - } - - return _return(controller->createDtoResponse( - res->code == 500 ? Status::CODE_500 : Status::CODE_200, res)); - } - }; - - // ---------------------------------------------------------------- - // Memory Methods - // ---------------------------------------------------------------- - - ENDPOINT_INFO(getUIMemoryUsage) { - info->summary = "Get current RAM usage"; - info->addResponse>( - Status::CODE_200, "application/json", "Usage of RAM"); - } - ENDPOINT_ASYNC("GET", "/api/system/memory_usage", getUIMemoryUsage) { - ENDPOINT_ASYNC_INIT(getUIMemoryUsage); - Action act() override { - auto res = BaseReturnSystemDto::createShared(); - res->command = "getUIMemoryUsage"; - - auto memory_usage = atom::system::getMemoryUsage(); - if (memory_usage <= 0.0f) { - res->code = 500; - res->status = "error"; - res->message = "Failed to get current RAM usage"; - res->error = "System Error"; - } else { - res->status = "success"; - res->code = 200; - res->value = memory_usage; - res->message = "Success get current RAM usage"; - } - return _return(controller->createDtoResponse( - res->code == 500 ? Status::CODE_500 : Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUIMemoryInfo) { - info->summary = "Get memory static info"; - info->addResponse>( - Status::CODE_200, "application/json", - "Info of memory (usually static infomation)"); - } - ENDPOINT_ASYNC("GET", "/api/system/memory_info", getUIMemoryInfo) { - ENDPOINT_ASYNC_INIT(getUIMemoryInfo); - Action act() override { - auto res = ReturnMemoryInfoDto::createShared(); - res->command = "getUIMemoryInfo"; - - auto total_memory = atom::system::getTotalMemorySize(); - auto available_memory = atom::system::getAvailableMemorySize(); - auto virtual_memory_max = atom::system::getVirtualMemoryMax(); - auto virtual_memory_used = atom::system::getVirtualMemoryUsed(); - auto swap_memory_total = atom::system::getSwapMemoryTotal(); - auto swap_memory_used = atom::system::getSwapMemoryUsed(); - - auto physical_memory = atom::system::getPhysicalMemoryInfo(); - - if (total_memory <= 0 || available_memory <= 0 || - virtual_memory_max <= 0 || virtual_memory_used <= 0 || - swap_memory_total <= 0 || swap_memory_used <= 0) [[unlikely]] { - res->code = 500; - res->status = "error"; - res->message = "Failed to get memory info"; - res->error = "System Error"; - } else [[likely]] { - res->status = "success"; - res->code = 200; - res->total_memory = total_memory; - res->available_memory = available_memory; - res->virtual_memory_max = virtual_memory_max; - res->virtual_memory_used = virtual_memory_used; - res->swap_memory_total = swap_memory_total; - res->swap_memory_used = swap_memory_used; - if (!physical_memory.capacity.empty() && - !physical_memory.clockSpeed.empty() && - !physical_memory.type.empty()) { - res->memory_slot["capacity"] = physical_memory.capacity; - res->memory_slot["clockSpeed"] = physical_memory.clockSpeed; - res->memory_slot["type"] = physical_memory.type; - } - res->message = "Success get memory info"; - } - - return _return(controller->createDtoResponse( - res->code == 500 ? Status::CODE_500 : Status::CODE_200, res)); - } - }; - - // ---------------------------------------------------------------- - // Disk Methods - // ---------------------------------------------------------------- - - ENDPOINT_INFO(getUIDiskUsage) { - info->summary = "Get current disks usage"; - info->addResponse>( - Status::CODE_200, "application/json", "Usage of disks"); - } - ENDPOINT_ASYNC("GET", "/api/system/disk_usage", getUIDiskUsage) { - ENDPOINT_ASYNC_INIT(getUIDiskUsage); - Action act() override { - auto res = ReturnDiskUsageDto::createShared(); - res->command = "getUIDiskUsage"; - - auto tmp = atom::system::getDiskUsage(); - if (tmp.empty()) { - res->code = 500; - res->status = "error"; - res->message = "Failed to get current disks usage"; - res->error = "System Error"; - } else { - for (auto &disk : tmp) { - res->value[disk.first] = disk.second; - } - res->status = "success"; - res->code = 200; - res->message = "Success get current disks usage"; - } - return _return(controller->createDtoResponse( - res->code == 500 ? Status::CODE_500 : Status::CODE_200, res)); - } - }; - - ENDPOINT_INFO(getUIAvailableDrives) { - info->summary = "Get available drives"; - info->addResponse>( - Status::CODE_200, "application/json", "Available drives"); - } - ENDPOINT_ASYNC("GET", "/api/system/available_drives", - getUIAvailableDrives) { - ENDPOINT_ASYNC_INIT(getUIAvailableDrives); - Action act() override { - auto res = ReturnAvailableDrivesDto::createShared(); - res->command = "getUIAvailableDrives"; - - auto tmp = atom::system::getAvailableDrives(); - - if (tmp.empty()) { - res->code = 500; - res->status = "error"; - res->message = "Failed to get available drives"; - res->error = "System Error"; - } else { - for (auto drive : tmp) { - res->value->push_back(drive); - } - - res->status = "success"; - res->code = 200; - res->message = "Success get available drives"; - } - return _return(controller->createDtoResponse( - res->code == 500 ? Status::CODE_500 : Status::CODE_200, res)); - } - }; - - // ---------------------------------------------------------------- - // Battery Methods - // ---------------------------------------------------------------- - - ENDPOINT_INFO(getUIBatteryInfo) { - info->summary = "Get battery info"; - info->addResponse>( - Status::CODE_200, "application/json", "Battery info"); - } - ENDPOINT_ASYNC("GET", "/api/system/battery", getUIBatteryInfo) { - ENDPOINT_ASYNC_INIT(getUIBatteryInfo); - Action act() override { - auto res = ReturnBatteryInfoDto::createShared(); - res->command = "getUIBatteryInfo"; - - const auto tmp = atom::system::getBatteryInfo(); - - res->isBatteryPresent = tmp.isBatteryPresent; - res->isCharging = tmp.isCharging; - res->batteryLifePercent = tmp.batteryLifePercent; - res->batteryLifeTime = tmp.batteryLifeTime; - res->batteryFullLifeTime = tmp.batteryFullLifeTime; - res->energyNow = tmp.energyNow; - res->energyFull = tmp.energyFull; - res->energyDesign = tmp.energyDesign; - res->voltageNow = tmp.voltageNow; - res->currentNow = tmp.currentNow; - - res->message = "Success get battery info"; - res->code = 200; - res->status = "success"; - - return _return(controller->createDtoResponse( - res->code == 500 ? Status::CODE_500 : Status::CODE_200, res)); - } - }; - - // ---------------------------------------------------------------- - // Network Methods - // ---------------------------------------------------------------- - - ENDPOINT_INFO(getUINetworkInfo) { - info->summary = "Get network info"; - info->addResponse>( - Status::CODE_200, "application/json", "Network info"); - info->addResponse>( - Status::CODE_500, "application/json", "Network info"); - } - ENDPOINT_ASYNC("GET", "/api/system/network", getUINetworkInfo) { - ENDPOINT_ASYNC_INIT(getUINetworkInfo); - Action act() override { - auto res = ReturnNetworkInfoDto::createShared(); - res->command = "getUINetworkInfo"; - - try { - auto isHotspotConnected = atom::system::isHotspotConnected(); - auto wifi = atom::system::getCurrentWifi(); - auto wired = atom::system::getCurrentWiredNetwork(); - - res->hotspot = isHotspotConnected; - res->wifi = wifi; - res->wired = wired; - - res->message = "Success get network info"; - res->code = 200; - res->status = "success"; - } catch (const std::exception &e) { - res->message = e.what(); - res->code = 500; - res->status = "error"; - } - - return _return(controller->createDtoResponse( - res->code == 500 ? Status::CODE_500 : Status::CODE_200, res)); - } - }; - - // ---------------------------------------------------------------- - // OS Infomation Methods - // ---------------------------------------------------------------- - - ENDPOINT_INFO(getUIOSInfo) { - info->summary = "Get OS info"; - info->addResponse>( - Status::CODE_200, "application/json", "OS info"); - } - ENDPOINT_ASYNC("GET", "/api/system/os", getUIOSInfo) { - ENDPOINT_ASYNC_INIT(getUIOSInfo); - Action act() override { - auto res = ReturnOSInfoDto::createShared(); - res->command = "getUIOSInfo"; - - auto tmp = atom::system::getOperatingSystemInfo(); - - if (tmp.osName.empty() || tmp.osVersion.empty() || - tmp.kernelVersion.empty()) { - res->code = 500; - res->status = "error"; - res->message = "Failed to get OS info"; - res->error = "System Error"; - } else { - res->code = 200; - res->status = "success"; - res->message = "Success get OS info"; - res->name = tmp.osName; - res->version = tmp.osVersion; - res->kernelVersion = tmp.kernelVersion; - res->architecture = tmp.architecture; - res->compiler = tmp.compiler; - } - return _return(controller->createDtoResponse( - res->code == 500 ? Status::CODE_500 : Status::CODE_200, res)); - } - }; - - // ---------------------------------------------------------------- - // Process Methods - // TODO: This is a little bit complicated. - // ---------------------------------------------------------------- - - ENDPOINT_INFO(getUIProcesses) { - info->summary = "Get all running processes"; - // info->addResponse(Status::CODE_200, "application/json", - // "Processes"); info->addResponse(Status::CODE_404, - // "text/plain"); - info->addResponse(Status::CODE_200, "text/plain"); - } - ENDPOINT_ASYNC("GET", "/api/system/process", getUIProcesses) { - ENDPOINT_ASYNC_INIT(getUIProcesses); - Action act() override { - nlohmann::json res; - for (const auto &process : atom::system::getProcessInfo()) { - OATPP_LOGD("System", "Process Name: %s File Address: %s", - process.first.c_str(), process.second.c_str()); - res["value"][process.first] = process.second; - } - auto response = - controller->createResponse(Status::CODE_200, res.dump()); - response->putHeader(Header::CONTENT_TYPE, "text/json"); - return _return(response); - } - }; - - // ---------------------------------------------------------------- - // System Operating Methods - // ---------------------------------------------------------------- - - ENDPOINT_INFO(getUIShutdown) { info->summary = "Shutdown system"; } - ENDPOINT_ASYNC("GET", "/api/system/shutdown", getUIShutdown) { - ENDPOINT_ASYNC_INIT(getUIShutdown); - Action act() override { - atom::system::shutdown(); - return _return(controller->createResponse( - Status::CODE_200, "Wtf, how can you do that?")); - } - }; - - ENDPOINT_INFO(getUIReboot) { info->summary = "Reboot system"; } - ENDPOINT_ASYNC("GET", "/api/system/reboot", getUIReboot) { - ENDPOINT_ASYNC_INIT(getUIReboot); - Action act() override { - atom::system::reboot(); - return _return(controller->createResponse( - Status::CODE_200, "Wtf, how can you do that?")); - } - }; -}; - -#include OATPP_CODEGEN_END(ApiController) //<- End Codegen - -#endif // LITHIUM_ASYNC_SYSTEM_CONTROLLER_HPP diff --git a/modules/lithium.webserver/include/controller/AsyncUploadController.hpp b/modules/lithium.webserver/include/controller/AsyncUploadController.hpp deleted file mode 100644 index 94d01b89..00000000 --- a/modules/lithium.webserver/include/controller/AsyncUploadController.hpp +++ /dev/null @@ -1,162 +0,0 @@ -/* - * AsyncUploadController.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-7-13 - -Description: Upload Route - -**************************************************/ - -// TODO #12 - -#ifndef LITHIUM_ASYNC_UPLOAD_CONTROLLER_HPP -#define LITHIUM_ASYNC_UPLOAD_CONTROLLER_HPP - -#include "config.h" - -#include "oatpp/web/mime/multipart/FileProvider.hpp" -#include "oatpp/web/mime/multipart/InMemoryDataProvider.hpp" -#include "oatpp/web/mime/multipart/Multipart.hpp" -#include "oatpp/web/mime/multipart/PartList.hpp" -#include "oatpp/web/mime/multipart/PartReader.hpp" -#include "oatpp/web/mime/multipart/Reader.hpp" -#include "oatpp/web/mime/multipart/TemporaryFileProvider.hpp" - -#include "oatpp/core/macro/codegen.hpp" -#include "oatpp/core/macro/component.hpp" -#include "oatpp/parser/json/mapping/ObjectMapper.hpp" -#include "oatpp/web/server/api/ApiController.hpp" - -namespace multipart = oatpp::web::mime::multipart; - -#include OATPP_CODEGEN_BEGIN(ApiController) //<- Begin Codegen - -class UploadController : public oatpp::web::server::api::ApiController { -public: - UploadController(const std::shared_ptr &objectMapper) - : oatpp::web::server::api::ApiController(objectMapper) {} - - // ---------------------------------------------------------------- - // Pointer creator - // ---------------------------------------------------------------- - - static std::shared_ptr createShared( - OATPP_COMPONENT(std::shared_ptr, objectMapper)) { - return std::make_shared(objectMapper); - } - - // ---------------------------------------------------------------- - // Upload Http Handler - // ---------------------------------------------------------------- - - ENDPOINT_INFO(MultipartUploadToFile) { - info->summary = "Upload File To File"; - } - ENDPOINT_ASYNC("POST", "/api/upload/file", MultipartUploadToFile) { - ENDPOINT_ASYNC_INIT(MultipartUploadToFile) - - /* Coroutine State */ - std::shared_ptr m_multipart; - std::shared_ptr - m_bufferStream = - std::make_shared(); - - Action act() override { - m_multipart = - std::make_shared(request->getHeaders()); - auto multipartReader = - std::make_shared(m_multipart); - - multipartReader->setPartReader( - "file", multipart::createAsyncFilePartReader("./tmp")); - - multipartReader->setDefaultPartReader( - multipart::createAsyncInMemoryPartReader( - 16 * 1024 /* max-data-size */)); - - return request->transferBodyAsync(multipartReader) - .next(yieldTo(&MultipartUploadToFile::onUploaded)); - } - - Action onUploaded() { - oatpp::String fileData; - - auto file = m_multipart->getNamedPart("file"); - if (file) { - return oatpp::data::stream::transferAsync( - file->getPayload()->openInputStream(), - m_bufferStream, 0, - oatpp::data::buffer::IOBuffer::createShared()) - .next(yieldTo( - &MultipartUploadToFile::sendResponseWithFileData)); - } - - return _return(controller->createDtoResponse( - Status::CODE_200, - oatpp::Fields( - {{"code", oatpp::Int32(200)}, - {"message", oatpp::String("OK")}, - {"parts-uploaded", oatpp::Int32(m_multipart->count())}, - {"file-data", nullptr /* no file data */}}))); - } - - Action sendResponseWithFileData() { - return _return(controller->createDtoResponse( - Status::CODE_200, - oatpp::Fields( - {{"code", oatpp::Int32(200)}, - {"message", oatpp::String("OK")}, - {"parts-uploaded", oatpp::Int32(m_multipart->count())}, - {"file-data", m_bufferStream->toString()}}))); - } - }; - - ENDPOINT_INFO(MultipartUploadToMemory) { - info->summary = "Upload File To Memory"; - } - ENDPOINT_ASYNC("POST", "test/multipart-all", MultipartUploadToMemory) { - ENDPOINT_ASYNC_INIT(MultipartUploadToMemory) - - /* Coroutine State */ - std::shared_ptr m_multipart; - std::shared_ptr - m_bufferStream = - std::make_shared(); - - Action act() override { - m_multipart = - std::make_shared(request->getHeaders()); - auto multipartReader = - std::make_shared(m_multipart); - - /* Configure to read part with name "part1" into memory */ - multipartReader->setPartReader( - "file", multipart::createAsyncInMemoryPartReader( - 256 /* max-data-size */)); - - multipartReader->setDefaultPartReader( - multipart::createAsyncInMemoryPartReader( - 16 * 1024 /* max-data-size */)); - /* Read multipart body */ - return request->transferBodyAsync(multipartReader) - .next(yieldTo(&MultipartUploadToMemory::onUploaded)); - } - - Action onUploaded() { - /* Get multipart by name */ - auto content = m_multipart->getNamedPart("file"); - /* Asser part not-null */ - OATPP_ASSERT_HTTP(content, Status::CODE_400, "file is null"); - return _return(controller->createResponse(Status::CODE_200, "OK")); - } - }; -}; - -#include OATPP_CODEGEN_END(ApiController) //<- End Codegen - -#endif // LITHIUM_ASYNC_UPLOAD_CONTROLLER_HPP diff --git a/modules/lithium.webserver/include/controller/AsyncWebSocketController.hpp b/modules/lithium.webserver/include/controller/AsyncWebSocketController.hpp deleted file mode 100644 index 684accb1..00000000 --- a/modules/lithium.webserver/include/controller/AsyncWebSocketController.hpp +++ /dev/null @@ -1,97 +0,0 @@ -/* - * AsyncWebSocketController.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-7-13 - -Description: Websocket Route - -**************************************************/ - -#ifndef LITHIUM_ASYNC_WEBSOCKET_CONTROLLER_HPP -#define LITHIUM_ASYNC_WEBSOCKET_CONTROLLER_HPP - -#include "config.h" - -#include "oatpp-websocket/Handshaker.hpp" - -#include "oatpp/core/macro/codegen.hpp" -#include "oatpp/core/macro/component.hpp" -#include "oatpp/network/ConnectionHandler.hpp" -#include "oatpp/web/server/api/ApiController.hpp" - -#include -#include - -#include OATPP_CODEGEN_BEGIN(ApiController) //<-- codegen begin - -/** - * Controller with WebSocket-connect endpoint. - */ -class WebSocketController : public oatpp::web::server::api::ApiController { -private: - typedef WebSocketController __ControllerType; - - OATPP_COMPONENT(std::shared_ptr, - websocketConnectionHandler, "websocket"); - -public: - WebSocketController(OATPP_COMPONENT(std::shared_ptr, - objectMapper)) - : oatpp::web::server::api::ApiController(objectMapper) {} - - // ---------------------------------------------------------------- - // Pointer creator - // ---------------------------------------------------------------- - - static std::shared_ptr createShared( - OATPP_COMPONENT(std::shared_ptr, objectMapper)) { - return std::make_shared(objectMapper); - } - - // ---------------------------------------------------------------- - // Websocket Handler - // ---------------------------------------------------------------- - - ENDPOINT_ASYNC("GET", "/ws/{hub-name}", wsConsole) { - ENDPOINT_ASYNC_INIT(wsConsole); - Action act() override { - auto hubType = request->getPathVariable("hub-type"); - auto response = oatpp::websocket::Handshaker::serversideHandshake( - request->getHeaders(), controller->websocketConnectionHandler); - auto parameters = std::make_shared< - oatpp::network::ConnectionHandler::ParameterMap>(); - (*parameters)["type"] = hubType; - response->setConnectionUpgradeParameters(parameters); - - if (const std::string hub_type = hubType.getValue(""); - hub_type == "device") { - std::vector available_device_types = { - "camera", "telescope", "focuser", - "filterwheel", "solver", "guider"}; - auto it = std::find(available_device_types.begin(), - available_device_types.end(), - hubType.getValue("")); - OATPP_ASSERT_HTTP(it != available_device_types.end(), - Status::CODE_500, "Invalid device type"); - } else if (hub_type == "plugin") { - std::vector available_plugins = {"script", "exe", - "liscript"}; - auto it = std::find(available_plugins.begin(), - available_plugins.end(), hubType->c_str()); - OATPP_ASSERT_HTTP(it != available_plugins.end(), - Status::CODE_500, "Invalid plugin type"); - } - - return _return(response); - } - }; -}; - -#include OATPP_CODEGEN_END(ApiController) //<-- codegen end - -#endif /* LITHIUM_ASYNC_WEBSOCKET_CONTROLLER_HPP */ diff --git a/modules/lithium.webserver/include/webserver.hpp b/modules/lithium.webserver/include/webserver.hpp deleted file mode 100644 index 10fbb6dd..00000000 --- a/modules/lithium.webserver/include/webserver.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef LITHIUM_WEBSERVER_HPP -#define LITHIUM_WEBSERVER_HPP - -namespace lithium::webserver { - - void say_hello(); - -} // namespace lithium::webserver - -#endif // LITHIUM_WEBSERVER_HPP diff --git a/modules/lithium.webserver/include/websocket/Connection.hpp b/modules/lithium.webserver/include/websocket/Connection.hpp deleted file mode 100644 index 5a6da9c6..00000000 --- a/modules/lithium.webserver/include/websocket/Connection.hpp +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Connection.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-1-13 - -Description: Websocket Connection - -**************************************************/ - -#ifndef LITHIUM_WEBSOCKET_CONNECTION_HPP -#define LITHIUM_WEBSOCKET_CONNECTION_HPP - -#include "Constants.hpp" - -#include "config/Config.hpp" -#include "config/HubsConfig.hpp" - -#include "data/DTOs.hpp" - -#include "oatpp-websocket/AsyncWebSocket.hpp" - -#include "oatpp/network/ConnectionProvider.hpp" - -#include "oatpp/core/async/Executor.hpp" -#include "oatpp/core/async/Lock.hpp" -#include "oatpp/core/data/mapping/ObjectMapper.hpp" -#include "oatpp/core/macro/component.hpp" - -class Session; // FWD - -class Connection : public oatpp::websocket::AsyncWebSocket::Listener { -private: - struct MessageQueue { - std::list> queue; - std::mutex mutex; - bool active = false; - }; - -private: - /** - * Buffer for messages. Needed for multi-frame messages. - */ - oatpp::data::stream::BufferOutputStream m_messageBuffer; - - /** - * Lock for synchronization of writes to the web socket. - */ - oatpp::async::Lock m_writeLock; - -private: - std::shared_ptr m_socket; - std::mutex m_socketMutex; - std::shared_ptr m_hubSession; - v_int64 m_connectionId; - std::shared_ptr m_messageQueue; - -private: - v_int64 m_pingTime; - v_int64 m_failedPings; - v_int64 m_lastPingTimestamp; - std::mutex m_pingMutex; - -private: - /* Inject application components */ - OATPP_COMPONENT(std::shared_ptr, m_asyncExecutor); - OATPP_COMPONENT(std::shared_ptr, - m_objectMapper, Constants::COMPONENT_REST_API); - -private: - CoroutineStarter handlePong(const oatpp::Object& message); - CoroutineStarter handleBroadcast(const oatpp::Object& message); - CoroutineStarter handleDirectMessage( - const oatpp::Object& message); - CoroutineStarter handleSynchronizedEvent( - const oatpp::Object& message); - CoroutineStarter handleKickMessage( - const oatpp::Object& message); - CoroutineStarter handleClientMessage( - const oatpp::Object& message); - CoroutineStarter handleMessage(const oatpp::Object& message); - -public: - Connection(const std::shared_ptr& socket, - const std::shared_ptr& hubSession, v_int64 connectionId); - - /** - * Send message to connection. - * @param message - */ - oatpp::async::CoroutineStarter sendMessageAsync( - const oatpp::Object& message); - - /** - * Send error message to connection. - * @param error - * @param fatal - */ - oatpp::async::CoroutineStarter sendErrorAsync( - const oatpp::Object& error, bool fatal = false); - - /** - * Queue message to send to connection. - * @param message - * @return - */ - bool queueMessage(const oatpp::Object& message); - - /** - * Ping connection. - */ - void ping(const v_int64 timestampMicroseconds); - - /** - * Kick this connection. - */ - void kick(); - - /** - * Check ping rules. - * @param currentPingSessionTimestamp - */ - void checkPingsRules(const v_int64 currentPingSessionTimestamp); - - /** - * Get the hub session the connection associated with. - * @return - */ - std::shared_ptr getHubSession(); - - /** - * Get connection connectionId. - * @return - */ - v_int64 getConnectionId(); - - /** - * Remove circle `std::shared_ptr` dependencies - */ - void invalidateSocket(); - -public: // WebSocket Listener methods - CoroutineStarter onPing(const std::shared_ptr& socket, - const oatpp::String& message) override; - CoroutineStarter onPong(const std::shared_ptr& socket, - const oatpp::String& message) override; - CoroutineStarter onClose(const std::shared_ptr& socket, - v_uint16 code, - const oatpp::String& message) override; - CoroutineStarter readMessage(const std::shared_ptr& socket, - v_uint8 opcode, p_char8 data, - oatpp::v_io_size size) override; -}; - -#endif // LITHIUM_WEBSOCKET_CONNECTION_HPP diff --git a/modules/lithium.webserver/include/websocket/Hub.hpp b/modules/lithium.webserver/include/websocket/Hub.hpp deleted file mode 100644 index 9d11b01f..00000000 --- a/modules/lithium.webserver/include/websocket/Hub.hpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Hub.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-1-13 - -Description: Websocket Connection Hub - -**************************************************/ - -#ifndef Helicopter_hub_Hub_hpp -#define Helicopter_hub_Hub_hpp - -#include "./Session.hpp" -#include "config/HubsConfig.hpp" - -class Hub { -private: - struct State { - oatpp::Object config; - std::unordered_map> sessions; - std::mutex mutex; - bool isPingerActive; - }; - -private: - std::shared_ptr m_state; - -private: - OATPP_COMPONENT(std::shared_ptr, m_asyncExecutor); - -private: - void startPinger(); - -public: - /** - * Constructor. - * @param config - */ - Hub(const oatpp::Object& config); - - /** - * Not thread safe. - * Create new hub session. - * @param sessionId - * @param config - * @return - `std::shared_ptr` to a new Session or `nullptr` if session with - * such ID already exists. - */ - std::shared_ptr createNewSession(const oatpp::String& sessionId); - - /** - * NOT thread-safe - * @param sessionId - * @return - */ - std::shared_ptr findSession(const oatpp::String& sessionId); - - /** - * NOT thread-safe - * @param sessionId - */ - void deleteSession(const oatpp::String& sessionId); -}; - -#endif // Helicopter_hub_Hub_hpp diff --git a/modules/lithium.webserver/include/websocket/Registry.hpp b/modules/lithium.webserver/include/websocket/Registry.hpp deleted file mode 100644 index a86dbc0a..00000000 --- a/modules/lithium.webserver/include/websocket/Registry.hpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Registry.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-1-13 - -Description: Websocket Hub Registry - -**************************************************/ - -#ifndef LITHIUM_WEBSOCKET_REGISTRY_HPP -#define LITHIUM_WEBSOCKET_REGISTRY_HPP - -#include "Hub.hpp" - -#include "oatpp-websocket/AsyncConnectionHandler.hpp" - -#include -#include - -class Registry - : public oatpp::websocket::AsyncConnectionHandler::SocketInstanceListener { -private: - struct SessionInfo { - std::shared_ptr session; - oatpp::Object error; - bool isHost; - }; - -private: - std::unordered_map> m_hubs; - std::mutex m_mutex; - -private: - /* Inject application components */ - OATPP_COMPONENT(oatpp::Object, m_config); - OATPP_COMPONENT(std::shared_ptr, m_hubConfig); - OATPP_COMPONENT(std::shared_ptr, m_asyncExecutor); - OATPP_COMPONENT(std::shared_ptr, - m_objectMapper, Constants::COMPONENT_REST_API); - -private: - oatpp::String getRequiredParameter( - const oatpp::String& name, - const std::shared_ptr& params, - SessionInfo& sessionInfo); - -private: - void sendSocketErrorAsync(const std::shared_ptr& socket, - const oatpp::Object& error, - bool fatal = false); - SessionInfo getSessionForConnection( - const std::shared_ptr& socket, - const std::shared_ptr& params); - -public: - Registry(); - - /** - * Get all sessions of the hub. - * @param hubId - * @return - */ - std::shared_ptr getHubById(const oatpp::String& hubId); - -public: - /** - * Called when socket is created - */ - void onAfterCreate_NonBlocking( - const std::shared_ptr& socket, - const std::shared_ptr& params) override; - - /** - * Called before socket instance is destroyed. - */ - void onBeforeDestroy_NonBlocking( - const std::shared_ptr& socket) override; -}; - -#endif // LITHIUM_WEBSOCKET_REGISTRY_HPP diff --git a/modules/lithium.webserver/include/websocket/Session.hpp b/modules/lithium.webserver/include/websocket/Session.hpp deleted file mode 100644 index 17b1a2d8..00000000 --- a/modules/lithium.webserver/include/websocket/Session.hpp +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Session.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-1-13 - -Description: Websocket Hub Connection Session - -**************************************************/ - -#ifndef LITHIUM_WEBSOCKET_SESSION_HPP -#define LITHIUM_WEBSOCKET_SESSION_HPP - -#include "Connection.hpp" -#include "config/HubsConfig.hpp" - -class Session { -private: - oatpp::String m_id; - oatpp::Object m_config; - std::atomic m_connectionIdCounter; - v_int64 m_synchronizedEventId; // synchronized by m_connectionsMutex - std::unordered_map> m_connections; - std::shared_ptr m_host; - std::mutex m_connectionsMutex; - -private: - v_int64 m_pingCurrentTimestamp; - v_int64 m_pingBestTime; - v_int64 m_pingBestConnectionId; - v_int64 m_pingBestConnectionSinceTimestamp; - std::mutex m_pingMutex; - -public: - Session(const oatpp::String& id, - const oatpp::Object& config); - - oatpp::String getId(); - oatpp::Object getConfig(); - - void addConnection(const std::shared_ptr& connection, bool isHost = false); - void setHost(const std::shared_ptr& connection); - std::shared_ptr getHost(); - - bool isHostConnection(v_int64 connectionId); - - void removeConnectionById(v_int64 connectionId, bool& isEmpty); - - std::vector> getAllConnections(); - std::vector> getConnections( - const oatpp::Vector& connectionIds); - - void broadcastSynchronizedEvent(v_int64 senderId, - const oatpp::String& eventData); - - v_int64 generateNewConnectionId(); - - void checkAllConnectionsPings(); - - void pingAllConnections(); - - /** - * Report pong from connection. - * @param connectionId - * @param timestamp - timestamp reported in the pong (payload). If timestamp - * doesn't equal to the latest ping timestamp - ping considered to be - * failed. - * @return - connection's ping in microseconds or `-1` if ping failed. - */ - v_int64 reportConnectionPong(v_int64 connectionId, v_int64 timestamp); -}; - -#endif // LITHIUM_WEBSOCKET_SESSION_HPP diff --git a/modules/lithium.webserver/include/websocket/data/DTOs.hpp b/modules/lithium.webserver/include/websocket/data/DTOs.hpp deleted file mode 100644 index 4eb85a97..00000000 --- a/modules/lithium.webserver/include/websocket/data/DTOs.hpp +++ /dev/null @@ -1,376 +0,0 @@ -/*************************************************************************** - * - * Project: _ _ _ - * /\ /\___| (_) ___ ___ _ __ | |_ ___ _ __ - * / /_/ / _ \ | |/ __/ _ \| '_ \| __/ _ \ '__| - * / __ / __/ | | (_| (_) | |_) | || __/ | - * \/ /_/ \___|_|_|\___\___/| .__/ \__\___|_| - * |_| - * - * - * Copyright 2022-present, Leonid Stryzhevskyi - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - ***************************************************************************/ - -#ifndef DTOs_hpp -#define DTOs_hpp - -#include "oatpp/core/Types.hpp" -#include "oatpp/core/macro/codegen.hpp" - -#include OATPP_CODEGEN_BEGIN(DTO) - -/** - * host <--> server <--> client - * message codes. - * - 0..99 general messages - * - 100 - 199 outgoing host messages - * - 200 - 299 incoming host messages - * - 300 - 399 outgoing client messages - * - 400 - 499 incoming client messages - */ -ENUM(MessageCodes, v_int32, - -/////////////////////////////////////////////////////////////////// -//// 0..99 general messages - - /** - * Sent to a connection once connected. - */ - VALUE(OUTGOING_HELLO, 0), - - /** - * Server sends ping message to connection. - */ - VALUE(OUTGOING_PING, 1), - - /** - * Connection responds to server ping with pong message. - */ - VALUE(INCOMING_PONG, 2), - - /** - * Sent to connection to indicate operation error. - */ - VALUE(OUTGOING_ERROR, 3), - - /** - * Server notifies connections that new host has been elected. - */ - VALUE(OUTGOING_NEW_HOST, 4), - - /** - * Server sends message to connection from other connection. - */ - VALUE(OUTGOING_MESSAGE, 5), - - /** - * Connection broadcasts message to all clients. - */ - VALUE(INCOMING_BROADCAST, 6), - - /** - * Connection sends message to a client or to a group of clients. - */ - VALUE(INCOMING_DIRECT_MESSAGE, 7), - - /** - * Connection sends synchronized event. Synchronized event will be broadcasted to ALL connections, including the sender of this event. - * All connections are guaranteed to receive synchronized events in the same order except for cases where connection's messages - * were discarded due to poor connection (message queue overflow). - */ - VALUE(INCOMING_SYNCHRONIZED_EVENT, 8), - - /** - * Server send synchronized event to connection. - */ - VALUE(OUTGOING_SYNCHRONIZED_EVENT, 9), - -/////////////////////////////////////////////////////////////////// -//// 100 - 199 outgoing host messages - - /** - * Sent to host when new client joined the hub. - */ - VALUE(OUTGOING_HOST_CLIENT_JOINED, 101), - - /** - * Sent to host when client left the hub. - */ - VALUE(OUTGOING_HOST_CLIENT_LEFT, 102), - -/////////////////////////////////////////////////////////////////// -//// 200 - 299 incoming host messages - - /** - * Host sends to server to kick client or a group of clients. - */ - VALUE(INCOMING_HOST_KICK_CLIENTS, 200), - -/////////////////////////////////////////////////////////////////// -//// 300 - 399 outgoing client messages - - /** - * Client was kicked by a host. - */ - VALUE(OUTGOING_CLIENT_KICKED, 300), - -/////////////////////////////////////////////////////////////////// -//// 400 - 499 incoming client messages - - /** - * Client sends direct message to host. - */ - VALUE(INCOMING_CLIENT_MESSAGE, 400) - -); - -/** - * Error codes. - */ -ENUM(ErrorCodes, v_int32, - - /** - * Request is malformed or it is missing required parameters. - */ - VALUE(BAD_REQUEST, 0), - - /** - * No hub config found on the server. - */ - VALUE(GAME_NOT_FOUND, 1), - - /** - * No hub session found for given sessionId. - */ - VALUE(SESSION_NOT_FOUND, 2), - - /** - * Operation not permitted. - */ - VALUE(OPERATION_NOT_PERMITTED, 3), - - /** - * Message is malformatted or violates configured restrictions. - */ - VALUE(BAD_MESSAGE, 4), - - /** - * Session is in an invalid state. - */ - VALUE(INVALID_STATE, 5) - -); - -/** - * Direct message - */ -class ErrorDto : public oatpp::DTO { - - DTO_INIT(ErrorDto, DTO) - - /** - * Error code - */ - DTO_FIELD(Enum::AsNumber::NotNull, code); - - /** - * Error text message - */ - DTO_FIELD(String, message); - -public: - - ErrorDto() = default; - - ErrorDto(const Enum::AsNumber& pCode, const String pMessage) - : code(pCode) - , message(pMessage) - {} - -}; - -/** - * Hello message. - */ -class HelloMessageDto : public oatpp::DTO { - - DTO_INIT(HelloMessageDto, DTO) - - /** - * ID assigned to this connection by server. - */ - DTO_FIELD(Int64, connectionId); - - /** - * Message data - */ - DTO_FIELD(Boolean, isHost); - -}; - -/** - * Direct message - */ -class DirectMessageDto : public oatpp::DTO { - - DTO_INIT(DirectMessageDto, DTO) - - /** - * ConnectionIds of recipients - */ - DTO_FIELD(Vector, connectionIds) = {}; - - /** - * Message data - */ - DTO_FIELD(String, data); - -}; - -/** - * Outgoing message. - */ -class OutgoingMessageDto : public oatpp::DTO { - - DTO_INIT(OutgoingMessageDto, DTO) - - /** - * connectionId of sender - */ - DTO_FIELD(Int64, connectionId); - - /** - * Message data - */ - DTO_FIELD(String, data); - -}; - -/** - * Outgoing synchronized message. - */ -class OutgoingSynchronizedMessageDto : public oatpp::DTO { - - DTO_INIT(OutgoingSynchronizedMessageDto, DTO) - - /** - * Event Id - event index in the sequence. - */ - DTO_FIELD(Int64, eventId); - - /** - * connectionId of sender - */ - DTO_FIELD(Int64, connectionId); - - /** - * Message data - */ - DTO_FIELD(String, data); - -}; - -/** - * Message - */ -class MessageDto : public oatpp::DTO { - - DTO_INIT(MessageDto, DTO) - - /** - * Message code - */ - DTO_FIELD(Enum::AsNumber::NotNull, code); - - /** - * Operation Correlation ID - */ - DTO_FIELD(oatpp::String, ocid); - - /** - * Message payload - */ - DTO_FIELD(oatpp::Any, payload); - - DTO_FIELD_TYPE_SELECTOR(payload) { - - if(!code) return Void::Class::getType(); - - switch (*code) { - - case MessageCodes::OUTGOING_HELLO: - return oatpp::Object::Class::getType(); - - case MessageCodes::OUTGOING_PING: - return oatpp::Int64::Class::getType(); - - case MessageCodes::INCOMING_PONG: - return oatpp::Int64::Class::getType(); - - case MessageCodes::OUTGOING_ERROR: - return oatpp::Object::Class::getType(); - - case MessageCodes::OUTGOING_MESSAGE: - return oatpp::Object::Class::getType(); - - case MessageCodes::INCOMING_BROADCAST: - return oatpp::String::Class::getType(); - - case MessageCodes::INCOMING_DIRECT_MESSAGE: - return oatpp::Object::Class::getType(); - - case MessageCodes::INCOMING_SYNCHRONIZED_EVENT: - return oatpp::String::Class::getType(); - - case MessageCodes::OUTGOING_SYNCHRONIZED_EVENT: - return oatpp::Object::Class::getType(); - - case MessageCodes::OUTGOING_HOST_CLIENT_JOINED: - case MessageCodes::OUTGOING_HOST_CLIENT_LEFT: - return oatpp::Int64::Class::getType(); - - case MessageCodes::INCOMING_HOST_KICK_CLIENTS: - return oatpp::Vector::Class::getType(); - - case MessageCodes::OUTGOING_CLIENT_KICKED: - return oatpp::String::Class::getType(); - - case MessageCodes::INCOMING_CLIENT_MESSAGE: - return oatpp::String::Class::getType(); - - default: - throw std::runtime_error("not implemented"); - - } - - } - -public: - - MessageDto() = default; - - MessageDto(const Enum::AsNumber& pCode, const oatpp::Any& pPayload, const oatpp::String& pOCID = nullptr) - : code(pCode) - , payload(pPayload) - , ocid(pOCID) - {} - -}; - - -#include OATPP_CODEGEN_END(DTO) - -#endif // DTOs_hpp diff --git a/modules/lithium.webserver/package.json b/modules/lithium.webserver/package.json deleted file mode 100644 index 782d7513..00000000 --- a/modules/lithium.webserver/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "lithium.webserver", - "version": "1.0.0", - "type": "shared", - "description": "Lithium Web Interface based on oatpp", - "license": "GPL-3.0-or-later", - "author": "Max Qian", - "repository": { - "type": "git", - "url": "https://github.com/ElementAstro/Lithium" - }, - "bugs": { - "url": "https://github.com/ElementAstro/Lithium/issues" - }, - "homepage": "https://github.com/ElementAstro/Lithium", - "keywords": [ - "lithium", - "lithium", - "webserver" - ], - "scripts": { - "build": "cmake --build . --config Release -- -j 4", - "lint": "clang-format -i src/*.cpp include/*.h" - }, - "modules": [ - { - "name": "webserver", - "entry": "getInstance" - } - ] -} diff --git a/modules/lithium.webserver/src/App.cpp b/modules/lithium.webserver/src/App.cpp deleted file mode 100644 index 571e6d80..00000000 --- a/modules/lithium.webserver/src/App.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * App.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-7-13 - -Description: Main - -**************************************************/ - -#include "App.hpp" - -#include "./Runner.hpp" - -#include "./AppComponent.hpp" - -#include "oatpp/network/Server.hpp" - -#include - -void run() { - /* Register Components in scope of run() method */ - AppComponent components; - - Runner runner(OATPP_GET_COMPONENT(oatpp::Object), - OATPP_GET_COMPONENT(std::shared_ptr)); - - runner.start(); - - runner.join(); -} - -int runServer() { - oatpp::base::Environment::init(); - - run(); - - oatpp::base::Environment::destroy(); - - return 0; -} diff --git a/modules/lithium.webserver/src/CMakeLists.txt b/modules/lithium.webserver/src/CMakeLists.txt deleted file mode 100644 index 22a4c79f..00000000 --- a/modules/lithium.webserver/src/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -# CMakeLists.txt for Lithium -# This project is licensed under the terms of the GPL3 license. -# -# Project Name: Lithium-WebServer -# Description: Oatpp http and websocket for Lithium -# Author: Max Qian -# License: GPL3 - -cmake_minimum_required(VERSION 3.20) - -project(lithium_webserver C CXX) - -set(server_websocket_module - websocket/Hub.cpp - websocket/Connection.cpp - websocket/Registry.cpp - websocket/Session.cpp -) - -set(server_module - App.cpp - AppComponent.hpp - ErrorHandler.cpp - Runner.cpp - - config/HubsConfig.cpp -) - -################################################################################# -# Main - -add_library(${PROJECT_NAME} STATIC ${server_websocket_module} ${server_module}) -target_link_directories(${PROJECT_NAME} PUBLIC ${CMAKE_BINARY_DIR}/libs) - -target_link_libraries(${PROJECT_NAME} PRIVATE oatpp-websocket oatpp-swagger oatpp-openssl oatpp-zlib oatpp) -target_link_libraries(${PROJECT_NAME} PRIVATE loguru fmt::fmt) -target_link_libraries(${PROJECT_NAME} PRIVATE atomstatic) diff --git a/modules/lithium.webserver/src/ErrorHandler.cpp b/modules/lithium.webserver/src/ErrorHandler.cpp deleted file mode 100644 index 0eec567a..00000000 --- a/modules/lithium.webserver/src/ErrorHandler.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/* - * ErrorHandle.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-7-13 - -Description: Error Handle (404 or 500) - -**************************************************/ - -#include "ErrorHandler.hpp" - -ErrorHandler::ErrorHandler( - const std::shared_ptr &objectMapper) - : m_objectMapper(objectMapper) {} - -std::shared_ptr ErrorHandler::handleError( - const Status &status, const oatpp::String &message, - const Headers &headers) { - auto error = StatusDto::createShared(); - error->status = "ERROR"; - error->code = status.code; - error->message = message; - error->command = ""; - - auto response = - ResponseFactory::createResponse(status, error, m_objectMapper); - - for (const auto &pair : headers.getAll()) { - response->putHeader(pair.first.toString(), pair.second.toString()); - } - return response; -} diff --git a/modules/lithium.webserver/src/Runner.cpp b/modules/lithium.webserver/src/Runner.cpp deleted file mode 100644 index 4eb67805..00000000 --- a/modules/lithium.webserver/src/Runner.cpp +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Runner.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-3-16 - -Description: Lithium Server Runner - -**************************************************/ - -#include "Runner.hpp" - -#include "ErrorHandler.hpp" - -#include "controller/AsyncClientController.hpp" -//#include "controller/AsyncConfigController.hpp" -//#include "controller/AsyncDeviceController.hpp" -#include "controller/AsyncIOController.hpp" -#include "controller/AsyncStaticController.hpp" -#include "controller/AsyncSystemController.hpp" - -#include "oatpp-swagger/AsyncController.hpp" - -#include "oatpp-openssl/server/ConnectionProvider.hpp" - -#include "oatpp/network/tcp/server/ConnectionProvider.hpp" -#include "oatpp/web/protocol/http/incoming/SimpleBodyDecoder.hpp" -#include "oatpp/web/server/AsyncHttpConnectionHandler.hpp" - -#include "oatpp/network/Server.hpp" - -#include "oatpp-zlib/EncoderProvider.hpp" - -#define ADD_CONTROLLER(controller, router) \ - auto controller##_ptr = controller::createShared(); \ - docEndpoints.append(controller##_ptr->getEndpoints()); \ - router->getRouter()->addController(controller##_ptr); - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// APIServer - -APIServer::APIServer(const oatpp::Object& config, - const std::shared_ptr& executor) - : m_router(oatpp::web::server::HttpRouter::createShared()) { - if (config->tls) { - OATPP_LOGD("APIServer", "key_path='%s'", config->tls->pkFile->c_str()); - OATPP_LOGD("APIServer", "chn_path='%s'", - config->tls->chainFile->c_str()); - - auto tlcConfig = - oatpp::openssl::Config::createDefaultServerConfigShared( - config->tls->pkFile->c_str(), config->tls->chainFile->c_str()); - - m_connectionProvider = - oatpp::openssl::server::ConnectionProvider::createShared( - tlcConfig, - {config->host, config->port, oatpp::network::Address::IP_4}); - - } else { - m_connectionProvider = - oatpp::network::tcp::server::ConnectionProvider::createShared( - {config->host, config->port, oatpp::network::Address::IP_4}); - } - - auto components = - std::make_shared( - m_router); - /* Add content encoders */ - auto encoders = std::make_shared< - oatpp::web::protocol::http::encoding::ProviderCollection>(); - encoders->add(std::make_shared()); - encoders->add(std::make_shared()); - /* Set content encoders */ - components->contentEncodingProviders = encoders; - - auto decoders = std::make_shared< - oatpp::web::protocol::http::encoding::ProviderCollection>(); - decoders->add(std::make_shared()); - decoders->add(std::make_shared()); - /* Set Body Decoder */ - components->bodyDecoder = std::make_shared< - oatpp::web::protocol::http::incoming::SimpleBodyDecoder>(decoders); - - m_connectionHandler = - oatpp::web::server::AsyncHttpConnectionHandler::createShared(components, - executor); - OATPP_COMPONENT( - std::shared_ptr, - - apiObjectMapper, - Constants::COMPONENT_REST_API); // get ObjectMapper component - m_connectionHandler->setErrorHandler( - std::make_shared(apiObjectMapper)); -} - -std::shared_ptr APIServer::getRouter() { - return m_router; -} - -void APIServer::start() { - m_serverThread = std::jthread([this] { - oatpp::network::Server server(m_connectionProvider, - m_connectionHandler); - server.run(); - }); -} - -void APIServer::join() { m_serverThread.join(); } - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Runner - -Runner::Runner(const oatpp::Object& config, - const std::shared_ptr& executor) { - /* host API server */ - assertServerConfig(config->hostAPIServer, "hostAPIServer", true); - - auto hostServer = - std::make_shared(config->hostAPIServer, executor); - - oatpp::web::server::api::Endpoints docEndpoints; - ADD_CONTROLLER(ClientController, hostServer); - //ADD_CONTROLLER(ConfigController, hostServer); - //ADD_CONTROLLER(DeviceController, hostServer); - ADD_CONTROLLER(IOController, hostServer); - ADD_CONTROLLER(StaticController, hostServer); - ADD_CONTROLLER(SystemController, hostServer); - - hostServer->getRouter()->addController( - oatpp::swagger::AsyncController::createShared(docEndpoints)); - - m_servers.push_back(hostServer); - - /* client API server */ - assertServerConfig(config->clientAPIServer, "clientAPIServer", false); - - if (config->clientAPIServer->host == config->hostAPIServer->host && - config->clientAPIServer->port == config->hostAPIServer->port) { - ADD_CONTROLLER(ClientController, hostServer); - - } else { - assertServerConfig(config->clientAPIServer, "clientAPIServer", true); - - auto clientServer = - std::make_shared(config->clientAPIServer, executor); - ADD_CONTROLLER(ClientController, clientServer); - m_servers.push_back(clientServer); - } -} - -void Runner::assertServerConfig(const oatpp::Object& config, - const oatpp::String& serverName, - bool checkTls) { - if (!config) { - OATPP_LOGE("Runner", "Error: Missing config value - '%s'", - serverName->c_str()) - throw std::runtime_error("Error: Missing config value - '" + - serverName + "'"); - } - - if (!config->host) { - OATPP_LOGE("Runner", "Error: Missing config value - '%s.host'", - serverName->c_str()) - throw std::runtime_error("Error: Missing config value - '" + - serverName + ".host'"); - } - - if (!config->port) { - OATPP_LOGE("Runner", "Error: Missing config value - '$s.port'", - serverName->c_str()) - throw std::runtime_error("Error: Missing config value - '" + - serverName + ".port'"); - } - - if (config->tls && checkTls) { - if (!config->tls->pkFile) { - OATPP_LOGE("Runner", - "Error: Missing config value - '$s.tls.pkFile'", - serverName->c_str()) - throw std::runtime_error("Error: Missing config value - '" + - serverName + ".tls.pkFile'"); - } - - if (!config->tls->chainFile) { - OATPP_LOGE("Runner", - "Error: Missing config value - '$s.tls.chainFile'", - serverName->c_str()) - throw std::runtime_error("Error: Missing config value - '" + - serverName + ".tls.chainFile'"); - } - } -} - -void Runner::start() { - for (auto& server : m_servers) { - server->start(); - } -} - -void Runner::join() { - for (auto& server : m_servers) { - server->join(); - } -} diff --git a/modules/lithium.webserver/src/config/HubsConfig.cpp b/modules/lithium.webserver/src/config/HubsConfig.cpp deleted file mode 100644 index ba1a1d8c..00000000 --- a/modules/lithium.webserver/src/config/HubsConfig.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * HubConfig.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-1-13 - -Description: Websocket Hub Config DTO - -**************************************************/ - -#include "HubsConfig.hpp" - -HubsConfig::HubsConfig(const oatpp::String& configFilename) - : m_configFile(configFilename) -{ - if(configFilename) { - auto json = oatpp::String::loadFromFile(configFilename->c_str()); - m_hubs = m_mapper.readFromString>>(json); - } -} - -void HubsConfig::putHubConfig(const oatpp::Object& config) { - std::lock_guard lock(m_mutex); - if(m_hubs == nullptr) { - m_hubs = oatpp::UnorderedFields>({}); - } - m_hubs->insert({config->hubId, config}); -} - -oatpp::Object HubsConfig::getHubConfig(const oatpp::String& hubId) { - std::lock_guard lock(m_mutex); - if(m_hubs) { - auto it = m_hubs->find(hubId); - if(it != m_hubs->end()) { - return it->second; - } - } - return nullptr; -} - -bool HubsConfig::save() { - oatpp::String json; - { - std::lock_guard lock(m_mutex); - json = m_mapper.writeToString(m_hubs); - } - if(m_configFile) { - json.saveToFile(m_configFile->c_str()); - return true; - } - return false; -} diff --git a/modules/lithium.webserver/src/data/AuthDto.hpp b/modules/lithium.webserver/src/data/AuthDto.hpp deleted file mode 100644 index 775c1f5c..00000000 --- a/modules/lithium.webserver/src/data/AuthDto.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef EXAMPLE_JWT_AUTHDTO_HPP -#define EXAMPLE_JWT_AUTHDTO_HPP - -#include "oatpp/core/Types.hpp" -#include "oatpp/core/macro/codegen.hpp" - -#include OATPP_CODEGEN_BEGIN(DTO) - -class AuthDto : public oatpp::DTO { - DTO_INIT(AuthDto, DTO) - - DTO_FIELD(String, token); -}; - -#include OATPP_CODEGEN_END(DTO) - -#endif /* EXAMPLE_JWT_AUTHDTO_HPP */ diff --git a/modules/lithium.webserver/src/data/ConfigDto.hpp b/modules/lithium.webserver/src/data/ConfigDto.hpp deleted file mode 100644 index 8efb4925..00000000 --- a/modules/lithium.webserver/src/data/ConfigDto.hpp +++ /dev/null @@ -1,126 +0,0 @@ -/* - * ConfigDto.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-11-17 - -Description: Data Transform Object for Config Controller - -**************************************************/ - -#ifndef CONFIGDTO_HPP -#define CONFIGDTO_HPP - -#include "oatpp/core/Types.hpp" -#include "oatpp/core/macro/codegen.hpp" - -#include "StatusDto.hpp" - -#include OATPP_CODEGEN_BEGIN(DTO) ///< Begin DTO codegen section - -class GetConfigDTO : public oatpp::DTO { - DTO_INIT(GetConfigDTO, DTO) - - DTO_FIELD_INFO(path) { - info->description = "The name of the config value to get, split by '/'"; - info->required = true; - } - DTO_FIELD(String, path); - - DTO_FIELD_INFO(type) { - info->description = "The type of the config value, not necessarily"; - } - DTO_FIELD(String, type); - - DTO_FIELD_INFO(defaultValue) { - info->description = - "Whether to get default value of the config value if the value is " - "empty!"; - } - DTO_FIELD(Boolean, defaultValue); -}; - -class SetConfigDTO : public oatpp::DTO { - DTO_INIT(SetConfigDTO, DTO) - - DTO_FIELD_INFO(path) { - info->description = "The name of the config value to set, split by '/'"; - info->required = true; - } - DTO_FIELD(String, path); - - DTO_FIELD_INFO(value) { - info->description = "The value of the config value"; - info->required = true; - } - DTO_FIELD(String, value); - - DTO_FIELD_INFO(type) { - info->description = "The type of the config value"; - info->required = true; - } - DTO_FIELD(String, type); -}; - -class DeleteConfigDTO : public oatpp::DTO { - DTO_INIT(DeleteConfigDTO, DTO) - - DTO_FIELD_INFO(path) { - info->description = - "The name of the config value to delete, split by '/'"; - info->required = true; - } - DTO_FIELD(String, path); -}; - -class LoadConfigDTO : public oatpp::DTO { - DTO_INIT(LoadConfigDTO, DTO) - - DTO_FIELD_INFO(path) { - info->description = "The path of the config value to load"; - info->required = true; - } - DTO_FIELD(String, path); - - DTO_FIELD_INFO(isAbsolute) { - info->description = "Whether the path is absolute or not"; - info->required = true; - } - DTO_FIELD(Boolean, isAbsolute); -}; - -class SaveConfigDTO : public oatpp::DTO { - DTO_INIT(SaveConfigDTO, DTO) - - DTO_FIELD_INFO(path) { - info->description = "The path of the config value to save"; - info->required = true; - } - DTO_FIELD(String, path); - - DTO_FIELD_INFO(isAbsolute) { - info->description = "Whether the path is absolute or not"; - info->required = true; - } - DTO_FIELD(Boolean, isAbsolute); -}; - -class ReturnConfigDTO : public StatusDto { - DTO_INIT(ReturnConfigDTO, DTO) - - DTO_FIELD_INFO(value) { - info->description = "The value of the config value"; - } - DTO_FIELD(String, value); - - DTO_FIELD_INFO(type) { info->description = "The type of the config value"; } - DTO_FIELD(String, type); -}; - -#include OATPP_CODEGEN_END(DTO) ///< End DTO codegen section - -#endif diff --git a/modules/lithium.webserver/src/data/DeviceDto.hpp b/modules/lithium.webserver/src/data/DeviceDto.hpp deleted file mode 100644 index 07627e4b..00000000 --- a/modules/lithium.webserver/src/data/DeviceDto.hpp +++ /dev/null @@ -1,244 +0,0 @@ -/* - * DeviceDto.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-11-17 - -Description: Data Transform Object for Device Controller - -**************************************************/ - -#ifndef DEVICEDTO_HPP -#define DEVICEDTO_HPP - -#include "oatpp/core/Types.hpp" -#include "oatpp/core/macro/codegen.hpp" - -#include OATPP_CODEGEN_BEGIN(DTO) ///< Begin DTO codegen section - -class AddDeviceLibraryDTO : public oatpp::DTO { - DTO_INIT(AddDeviceLibraryDTO, DTO) - - DTO_FIELD_INFO(library_path) { - info->description = "Path of the device library to add"; - info->required = true; - } - DTO_FIELD(String, library_path); - - DTO_FIELD_INFO(library_name) { - info->description = "Name of the device library to add"; - info->required = true; - } - DTO_FIELD(String, library_name); -}; - -class RemoveDeviceLibraryDTO : public oatpp::DTO { - DTO_INIT(RemoveDeviceLibraryDTO, DTO) - - DTO_FIELD_INFO(library_name) { - info->description = "Name of the device library to remove"; - info->required = true; - } - DTO_FIELD(String, library_name); -}; - -class AddDeviceDTO : public oatpp::DTO { - DTO_INIT(AddDeviceDTO, DTO) - - DTO_FIELD_INFO(library_name) { - info->description = "Name of the device library to add from"; - info->required = true; - } - DTO_FIELD(String, library_name); - - DTO_FIELD_INFO(device_name) { - info->description = "Name of the device to add"; - info->required = true; - } - DTO_FIELD(String, device_name); - - DTO_FIELD_INFO(device_type) { - info->description = "Type of device to add"; - info->required = true; - } - DTO_FIELD(String, device_type); -}; - -class RemoveDeviceDTO : public oatpp::DTO { - DTO_INIT(RemoveDeviceDTO, DTO) - - DTO_FIELD_INFO(device_name) { - info->description = "Name of the device to remove"; - info->required = true; - } - DTO_FIELD(String, device_name); -}; - -class GetPropertyDTO : public oatpp::DTO { - DTO_INIT(GetPropertyDTO, DTO) - - DTO_FIELD_INFO(device_name) { - info->description = "Name of the device to get property"; - info->required = true; - } - DTO_FIELD(String, device_name); - - DTO_FIELD_INFO(property_name) { - info->description = "Name of the property to get"; - info->required = true; - } - DTO_FIELD(String, property_name); - - DTO_FIELD_INFO(need_update) { - info->description = "Whether the property should be updated"; - info->required = false; - } - DTO_FIELD(String, need_update); -}; - -class SetPropertyDTO : public oatpp::DTO { - DTO_INIT(SetPropertyDTO, DTO) - - DTO_FIELD_INFO(device_name) { - info->description = "Name of the device to set property"; - info->required = true; - } - DTO_FIELD(String, device_name); - - DTO_FIELD_INFO(property_name) { - info->description = "Name of the property to set"; - info->required = true; - } - DTO_FIELD(String, property_name); - - DTO_FIELD_INFO(property_value) { - info->description = "Value of the property to set"; - info->required = true; - } - DTO_FIELD(String, property_value); - - DTO_FIELD_INFO(property_type) { - info->description = "Type of the property to set"; - info->required = true; - } - DTO_FIELD(String, property_type); -}; - -class RunDeviceFuncDTO : public oatpp::DTO { - DTO_INIT(RunDeviceFuncDTO, DTO) - - DTO_FIELD_INFO(device_name) { - info->description = "Name of the device to set property"; - info->required = true; - } - DTO_FIELD(String, device_name); - - DTO_FIELD_INFO(task_name) { - info->description = "Name of the task to run of the specified device"; - info->required = true; - } - DTO_FIELD(String, task_name); - - DTO_FIELD_INFO(task_params) { - info->description = - "Parameters for the task to run of the specified device.(in JSON " - "format)"; - info->required = true; - } - DTO_FIELD(String, task_params); - - DTO_FIELD_INFO(need_async) { - info->description = "Whether run task in async mode(default)"; - info->required = false; - } - DTO_FIELD(Boolean, need_async); -}; - -class GetDeviceFuncDTO : public oatpp::DTO { - DTO_INIT(GetDeviceFuncDTO, DTO) - - DTO_FIELD_INFO(device_name) { - info->description = "Name of the device to get function infomation"; - info->required = true; - } - DTO_FIELD(String, device_name); - - DTO_FIELD_INFO(func_name) { - info->description = "Name of the function to get information"; - info->required = true; - } - DTO_FIELD(String, func_name); -}; - -class ConnectDeviceDTO : public oatpp::DTO { - DTO_INIT(ConnectDeviceDTO, DTO) - - DTO_FIELD_INFO(device_name) { - info->description = "Name of the device to connect"; - info->required = true; - } - DTO_FIELD(String, device_name); - - DTO_FIELD_INFO(auto_reconnect) { - info->description = - "Whether to automatically reconnect the device (default: true)."; - info->required = false; - } - DTO_FIELD(Boolean, auto_reconnect); - - DTO_FIELD_INFO(times_to_connect) { - info->description = - "Times to connect the device (default: 3). Automatically try to " - "connect the device 3 times at a time"; - info->required = false; - } - DTO_FIELD(Int32, times_to_connect); -}; - -class DisconnectDeviceDTO : public oatpp::DTO { - DTO_INIT(DisconnectDeviceDTO, DTO) - - DTO_FIELD_INFO(device_name) { - info->description = - "Name of the device to disconnect(must match the unique device " - "name)"; - info->required = true; - } - DTO_FIELD(String, device_name); - - DTO_FIELD_INFO(must_success) { - info->description = - "If this option is true, the device will be erased from device " - "vector until it is disconnected successfully"; - info->required = false; - } - DTO_FIELD(Boolean, must_success); -}; - -class ReconnectDeviceDTO : public oatpp::DTO { - DTO_INIT(ReconnectDeviceDTO, DTO) - - DTO_FIELD_INFO(device_name) { - info->description = "Name of the device to reconnect"; - info->required = true; - } - DTO_FIELD(String, device_name); -}; - -class ScanDeviceDTO : public oatpp::DTO { - DTO_INIT(ScanDeviceDTO, DTO) - - DTO_FIELD_INFO(device_type) { - info->description = "Type of the device to scan, from device mamanger"; - info->required = true; - } - DTO_FIELD(String, device_type); -}; - -#include OATPP_CODEGEN_END(DTO) ///< End DTO codegen section - -#endif diff --git a/modules/lithium.webserver/src/data/IODto.hpp b/modules/lithium.webserver/src/data/IODto.hpp deleted file mode 100644 index e513c7f0..00000000 --- a/modules/lithium.webserver/src/data/IODto.hpp +++ /dev/null @@ -1,102 +0,0 @@ -/* - * IODto.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-7-25 - -Description: Data Transform Object for IO Controller - -**************************************************/ - -#ifndef IODTO_HPP -#define IODTO_HPP - -#include "oatpp/core/Types.hpp" -#include "oatpp/core/macro/codegen.hpp" - -#include OATPP_CODEGEN_BEGIN(DTO) ///< Begin DTO codegen section - -class BaseIODto : public oatpp::DTO { - DTO_INIT(BaseIODto, DTO) - DTO_FIELD_INFO(path) { - info->description = - "the path of the directory you want to create or remove, must be " - "full path"; - info->required = true; - } - DTO_FIELD(String, path); - - DTO_FIELD_INFO(isAbsolute) { - info->description = "whether the path is absolute or not"; - info->required = true; - } - DTO_FIELD(Boolean, isAbsolute); -}; - -class CreateDirectoryDTO : public BaseIODto { - DTO_INIT(CreateDirectoryDTO, DTO) -}; - -class RenameDirectoryDTO : public BaseIODto { - DTO_INIT(RenameDirectoryDTO, DTO) - - DTO_FIELD_INFO(name) { - info->description = - "the new name of the directory you want to rename, must be valid"; - info->required = true; - } - DTO_FIELD(String, name); -}; - -class MoveDirectoryDTO : public BaseIODto { - DTO_INIT(MoveDirectoryDTO, DTO) - - DTO_FIELD_INFO(new_path) { - info->description = - "the new path of the directory you want to move to, must be valid"; - info->required = true; - } - DTO_FIELD(String, new_path); -}; - -class CopyFileDTO : public BaseIODto { - DTO_INIT(CopyFileDTO, DTO) - - DTO_FIELD_INFO(new_path) { - info->description = "the new path of the file, must be valid"; - info->required = true; - } - DTO_FIELD(String, new_path); -}; - -class MoveFileDTO : public BaseIODto { - DTO_INIT(MoveFileDTO, DTO) - - DTO_FIELD_INFO(new_path) { - info->description = "the new path of the file, must be valid"; - info->required = true; - } - DTO_FIELD(String, new_path); -}; - -class RenameFileDTO : public BaseIODto { - DTO_INIT(RenameFileDTO, DTO) - - DTO_FIELD_INFO(new_name) { - info->description = "the new name of the file, must be valid"; - info->required = true; - } - DTO_FIELD(String, new_name); -}; - -class RemoveFileDTO : public BaseIODto { - DTO_INIT(RemoveFileDTO, DTO) -}; - -#include OATPP_CODEGEN_END(DTO) ///< End DTO codegen section - -#endif diff --git a/modules/lithium.webserver/src/data/ModuleDto.hpp b/modules/lithium.webserver/src/data/ModuleDto.hpp deleted file mode 100644 index 07356ca1..00000000 --- a/modules/lithium.webserver/src/data/ModuleDto.hpp +++ /dev/null @@ -1,175 +0,0 @@ -/* - * ModuleDto.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-12-1 - -Description: Data Transform Object for Module Controller - -**************************************************/ - -#ifndef MODULEDTO_HPP -#define MODULEDTO_HPP - -#include "oatpp/core/Types.hpp" -#include "oatpp/core/macro/codegen.hpp" - -#include "StatusDto.hpp" - -#include OATPP_CODEGEN_BEGIN(DTO) ///< Begin DTO codegen section - -class BaseModuleDto : public oatpp::DTO { - DTO_INIT(LoadPluginDto, DTO) - - DTO_FIELD_INFO(module_name) { - info->description = "Name of the module to modify"; - info->required = true; - } - DTO_FIELD(String, module_name); - - DTO_FIELD_INFO(need_check) { - info->description = "Whether to check the operation"; - info->required = false; - } - DTO_FIELD(Boolean, need_check); -}; - -class LoadModuleDto : public BaseModuleDto { - DTO_INIT(LoadModuleDto, BaseModuleDto) - - DTO_FIELD_INFO(module_path) { - info->description = "Path of the module to load"; - info->required = true; - } - DTO_FIELD(String, module_path); -}; - -class UnloadPluginDto : public BaseModuleDto { - DTO_INIT(UnloadPluginDto, BaseModuleDto) -}; - -class GetModuleListDto : public BaseModuleDto { - DTO_INIT(GetModuleListDto, BaseModuleDto) -}; - -class ReturnModuleListDto : public StatusDto { - DTO_INIT(ReturnModuleListDto, DTO) - - DTO_FIELD_INFO(module_list) { - info->description = "List of the modules"; - info->required = true; - } - DTO_FIELD(Vector, module_list); -}; - -class RefreshModuleListDto : public StatusDto { - DTO_INIT(RefreshModuleListDto, DTO) -}; - -class GetEnableModuleDto : public BaseModuleDto { - DTO_INIT(GetEnableModuleDto, BaseModuleDto) -}; - -class ReturnEnableModuleDto : public StatusDto { - DTO_INIT(ReturnEnableModuleDto, DTO) - - DTO_FIELD_INFO(status) { - info->description = "Enable status of the module"; - info->required = true; - } - DTO_FIELD(Boolean, status); -}; - -class GetDisableModuleDto : public BaseModuleDto { - DTO_INIT(GetDisableModuleDto, BaseModuleDto) -}; - -class ReturnDisableModuleDto : public StatusDto { - DTO_INIT(ReturnDisableModuleDto, DTO) - - DTO_FIELD_INFO(status) { - info->description = "Disable status of the module"; - info->required = true; - } - DTO_FIELD(Boolean, status); -}; - -class GetModuleStatusDto : public BaseModuleDto { - DTO_INIT(GetModuleStatusDto, BaseModuleDto) -}; - -class ReturnModuleStatusDto : public StatusDto { - DTO_INIT(ReturnModuleStatusDto, DTO) - - DTO_FIELD_INFO(status) { - info->description = "Status of the module"; - info->required = true; - } - DTO_FIELD(Boolean, status); -}; - -class GetModuleConfigDto : public oatpp::DTO { - DTO_INIT(GetModuleConfigDto, DTO) - - DTO_FIELD_INFO(module_name) { - info->description = "Name of the module to get"; - info->required = true; - } - DTO_FIELD(String, module_name); -}; - -class ReturnModuleConfigDto : public StatusDto { - DTO_INIT(ReturnModuleConfigDto, DTO) - - DTO_FIELD_INFO(module_config) { - info->description = "Config of the module"; - info->required = true; - } - DTO_FIELD(String, module_config); -}; - -class GetInstanceDto : public oatpp::DTO { - DTO_INIT(GetInstanceDto, DTO) - - DTO_FIELD_INFO(module_name) { - info->description = "Name of the module to get"; - info->required = true; - } - DTO_FIELD(String, module_name); - - DTO_FIELD_INFO(instance_name) { - info->description = "Name of the instance to get"; - info->required = true; - } - DTO_FIELD(String, instance_name); - - DTO_FIELD_INFO(instance_type) { - info->description = "Type of the instance to get"; - info->required = true; - } - DTO_FIELD(String, instance_type); - - DTO_FIELD_INFO(get_func) { - info->description = "Function to get the instance"; - info->required = true; - } - DTO_FIELD(String, get_func); -}; - -class ReturnInstanceDto : public StatusDto { - DTO_INIT(ReturnInstanceDto, DTO) - - DTO_FIELD_INFO(instance) { - info->description = "Instance of the module"; - info->required = true; - } - DTO_FIELD(String, instance); -}; - -#include OATPP_CODEGEN_END(DTO) ///< End DTO codegen section - -#endif diff --git a/modules/lithium.webserver/src/data/PageDto.hpp b/modules/lithium.webserver/src/data/PageDto.hpp deleted file mode 100644 index ad06d75b..00000000 --- a/modules/lithium.webserver/src/data/PageDto.hpp +++ /dev/null @@ -1,22 +0,0 @@ - -#ifndef EXAMPLE_JWT_PAGEDTO_HPP -#define EXAMPLE_JWT_PAGEDTO_HPP - -#include "oatpp/core/Types.hpp" -#include "oatpp/core/macro/codegen.hpp" - -#include OATPP_CODEGEN_BEGIN(DTO) - -template -class PageDto : public oatpp::DTO { - DTO_INIT(PageDto, DTO) - - DTO_FIELD(UInt32, offset); - DTO_FIELD(UInt32, limit); - DTO_FIELD(UInt32, count); - DTO_FIELD(Vector, items); -}; - -#include OATPP_CODEGEN_END(DTO) - -#endif // EXAMPLE_JWT_PAGEDTO_HPP diff --git a/modules/lithium.webserver/src/data/ProcessDto.hpp b/modules/lithium.webserver/src/data/ProcessDto.hpp deleted file mode 100644 index 41bd0b27..00000000 --- a/modules/lithium.webserver/src/data/ProcessDto.hpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * ProcessDto.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-7-25 - -Description: Data Transform Object for Process Controller - -**************************************************/ - -#ifndef PROCESSDTO_HPP -#define PROCESSDTO_HPP - -#include "oatpp/core/Types.hpp" -#include "oatpp/core/macro/codegen.hpp" - -#include OATPP_CODEGEN_BEGIN(DTO) ///< Begin DTO codegen section - -class CreateProcessDTO : public oatpp::DTO { - DTO_INIT(CreateProcessDTO, DTO) - DTO_FIELD_INFO(process_name) { info->description = "process name"; } - DTO_FIELD(String, process_name); - - DTO_FIELD_INFO(process_id) { - info->description = "process id (used for more operation)"; - } - DTO_FIELD(String, process_id); -}; - -class TerminateProcessDTO : public oatpp::DTO { - DTO_INIT(TerminateProcessDTO, DTO) - - DTO_FIELD_INFO(process_id) { - info->description = "process id to terminate"; - } - DTO_FIELD(String, process_id); -}; - -class RunScriptDTO : public oatpp::DTO { - DTO_INIT(RunScriptDTO, DTO) - DTO_FIELD_INFO(script_name) { info->description = "script name"; } - DTO_FIELD(String, script_name); - - DTO_FIELD_INFO(script_id) { - info->description = "script id (used for more operation)"; - } - DTO_FIELD(String, script_id); -}; - -#include OATPP_CODEGEN_END(DTO) ///< End DTO codegen section - -#endif diff --git a/modules/lithium.webserver/src/data/SignInDto.hpp b/modules/lithium.webserver/src/data/SignInDto.hpp deleted file mode 100644 index 798ad285..00000000 --- a/modules/lithium.webserver/src/data/SignInDto.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef EXAMPLE_JWT_SIGNINDTO_HPP -#define EXAMPLE_JWT_SIGNINDTO_HPP - -#include "oatpp/core/Types.hpp" -#include "oatpp/core/macro/codegen.hpp" - -#include OATPP_CODEGEN_BEGIN(DTO) - -class SignInDto : public oatpp::DTO { - DTO_INIT(SignInDto, DTO) - - DTO_FIELD(String, userName, "username"); - DTO_FIELD(String, password, "password"); -}; - -#include OATPP_CODEGEN_END(DTO) - -#endif /* EXAMPLE_JWT_SIGNINDTO_HPP */ diff --git a/modules/lithium.webserver/src/data/SignUpDto.hpp b/modules/lithium.webserver/src/data/SignUpDto.hpp deleted file mode 100644 index 774dfb15..00000000 --- a/modules/lithium.webserver/src/data/SignUpDto.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef EXAMPLE_JWT_SIGNUPDTO_HPP -#define EXAMPLE_JWT_SIGNUPDTO_HPP - -#include "oatpp/core/Types.hpp" -#include "oatpp/core/macro/codegen.hpp" - -#include OATPP_CODEGEN_BEGIN(DTO) - -class SignUpDto : public oatpp::DTO { - DTO_INIT(SignUpDto, DTO) - - DTO_FIELD(String, userName, "username"); - DTO_FIELD(String, email, "email"); - DTO_FIELD(String, password, "password"); -}; - -#include OATPP_CODEGEN_END(DTO) - -#endif /* EXAMPLE_JWT_SIGNUPDTO_HPP */ diff --git a/modules/lithium.webserver/src/data/StatusDto.hpp b/modules/lithium.webserver/src/data/StatusDto.hpp deleted file mode 100644 index eff535a0..00000000 --- a/modules/lithium.webserver/src/data/StatusDto.hpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * StatusDto.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-7-25 - -Description: Status Data Transform Object - -**************************************************/ - -#ifndef STATUSDTO_HPP -#define STATUSDTO_HPP - -#include "oatpp/core/Types.hpp" -#include "oatpp/core/macro/codegen.hpp" - -#include OATPP_CODEGEN_BEGIN(DTO) - -class StatusDto : public oatpp::DTO { - DTO_INIT(StatusDto, DTO) - - DTO_FIELD_INFO(status) { info->description = "Short status text"; } - DTO_FIELD(String, status); - - DTO_FIELD_INFO(code) { info->description = "Status code"; } - DTO_FIELD(Int32, code); - - DTO_FIELD_INFO(message) { info->description = "Verbose message"; } - DTO_FIELD(String, message); - - DTO_FIELD_INFO(error) { info->description = "Verbose error message"; } - DTO_FIELD(String, error); - - DTO_FIELD_INFO(warning) { info->description = "Verbose warning messsage"; } - DTO_FIELD(String, warning); - - DTO_FIELD_INFO(command) { - info->description = "Command"; - info->required = true; - } - DTO_FIELD(String, command); -}; - -#include OATPP_CODEGEN_END(DTO) - -#endif // STATUSDTO_HPP diff --git a/modules/lithium.webserver/src/data/StoryDto.hpp b/modules/lithium.webserver/src/data/StoryDto.hpp deleted file mode 100644 index 9dfd5bd7..00000000 --- a/modules/lithium.webserver/src/data/StoryDto.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef EXAMPLE_JWT_STORYDTO_HPP -#define EXAMPLE_JWT_STORYDTO_HPP - -#include "PageDto.hpp" - -#include OATPP_CODEGEN_BEGIN(DTO) - -class StoryDto : public oatpp::DTO { - DTO_INIT(StoryDto, DTO) - - DTO_FIELD(String, id); - DTO_FIELD(String, content); -}; - -class StoryPageDto : public PageDto> { - DTO_INIT(StoryPageDto, PageDto>) -}; - -#include OATPP_CODEGEN_END(DTO) - -#endif // EXAMPLE_JWT_STORYDTO_HPP diff --git a/modules/lithium.webserver/src/data/SystemCustom.hpp b/modules/lithium.webserver/src/data/SystemCustom.hpp deleted file mode 100644 index ccb2f218..00000000 --- a/modules/lithium.webserver/src/data/SystemCustom.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "oatpp/core/Types.hpp" -#include "oatpp/core/macro/codegen.hpp" - -#include "atom/system/module/memory.hpp" - -#include OATPP_CODEGEN_BEGIN(DTO) ///< Begin DTO codegen section - -namespace __class { -class MemorySlotClass; -} - -/* Declare ObjectWrapper for your type */ -/* Mapping-Enabled atom::system::MemoryInfo::MemorySlot */ -typedef oatpp::data::mapping::type::Primitive - atom::system::MemoryInfo::MemorySlot; - -namespace __class { - -/** - * Type info - */ -class MemorySlotClass { -private: - /** - * Type interpretation - */ - class Inter - : public oatpp::Type::Interpretation< - atom::system::MemoryInfo::MemorySlot, oatpp::UnorderedFields> { - public: - oatpp::UnorderedFields interpret( - const atom::system::MemoryInfo::MemorySlot& value) const override { - return {{"capacity", value.capacity}, - {"clockSpeed", value.clockSpeed}, - {"type", value.type}}; - } - - atom::system::MemoryInfo::MemorySlot reproduce( - const oatpp::UnorderedFields map) const override { - return atom::system::MemoryInfo::MemorySlot( - {map["capacity"], map["clockSpeed"], map["type"]}); - } - }; - -public: - static const oatpp::ClassId CLASS_ID; - - static oatpp::Type* getType() { - static oatpp::Type type(CLASS_ID, nullptr, nullptr, - {{"system::memory", - new Inter()}} /* <-- Add type interpretation */); - return &type; - } -}; - -const oatpp::ClassId MemorySlotClass::CLASS_ID( - "system::memory::atom::system::MemoryInfo::MemorySlot"); - -} // namespace __class - -#include OATPP_CODEGEN_END(DTO) ///< End DTO codegen section diff --git a/modules/lithium.webserver/src/data/SystemDto.hpp b/modules/lithium.webserver/src/data/SystemDto.hpp deleted file mode 100644 index ed6fd348..00000000 --- a/modules/lithium.webserver/src/data/SystemDto.hpp +++ /dev/null @@ -1,203 +0,0 @@ -/* - * SystemDto.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-7-25 - -Description: System Data Transform Object - -**************************************************/ - -#ifndef SYSTEMDTO_HPP -#define SYSTEMDTO_HPP - -#include "StatusDto.hpp" - -#include OATPP_CODEGEN_BEGIN(DTO) - -class BaseReturnSystemDto : public StatusDto { - DTO_INIT(BaseReturnSystemDto, StatusDto) - - DTO_FIELD_INFO(value) { - info->description = "The value of the system info"; - } - DTO_FIELD(Float32, value); -}; - -class ReturnCpuInfoDto : public StatusDto { - DTO_INIT(ReturnCpuInfoDto, StatusDto) - - DTO_FIELD_INFO(model) { info->description = "The value of the CPU model"; } - DTO_FIELD(String, model); - - DTO_FIELD_INFO(frequency) { - info->description = "The value of the CPU frequency"; - } - DTO_FIELD(Float32, frequency); - - DTO_FIELD_INFO(identifier) { - info->description = "The value of the CPU identifier"; - } - DTO_FIELD(String, identifier); - - DTO_FIELD_INFO(packages) { - info->description = "The value of the number of physical CPU packages"; - } - DTO_FIELD(Int32, packages); - - DTO_FIELD_INFO(cpus) { - info->description = "The value of the number of physical CPUs"; - } - DTO_FIELD(Int32, cpus); -}; - -class ReturnMemoryInfoDto : public StatusDto { - DTO_INIT(ReturnMemoryInfoDto, StatusDto) - - DTO_FIELD_INFO(memory_slot) { - info->description = "The value of the memory slot"; - } - DTO_FIELD(UnorderedFields, memory_slot); - DTO_FIELD_INFO(virtual_memory_max) { - info->description = "The value of the virtual memory max"; - } - DTO_FIELD(Float64, virtual_memory_max); - DTO_FIELD_INFO(virtual_memory_used) { - info->description = "The value of the virtual memory used"; - } - DTO_FIELD(Float64, virtual_memory_used); - DTO_FIELD_INFO(swap_memory_total) { - info->description = "The value of the swap memory total"; - } - DTO_FIELD(Float64, swap_memory_total); - DTO_FIELD_INFO(swap_memory_used) { - info->description = "The value of the swap memory used"; - } - DTO_FIELD(Float64, swap_memory_used); - - DTO_FIELD_INFO(total_memory) { - info->description = "The value of the total memory"; - } - DTO_FIELD(Float64, total_memory); - - DTO_FIELD_INFO(available_memory) { - info->description = "The value of the available memory"; - } - DTO_FIELD(Float64, available_memory); -}; - -class ReturnBatteryInfoDto : public StatusDto { - DTO_INIT(ReturnBatteryInfoDto, StatusDto) - - DTO_FIELD_INFO(isBatteryPresent) { - info->description = "The value of the battery present"; - } - DTO_FIELD(Boolean, isBatteryPresent); - - DTO_FIELD_INFO(isCharging) { - info->description = "The value of the battery charging"; - } - DTO_FIELD(Boolean, isCharging); - - DTO_FIELD_INFO(batteryLifePercent) { - info->description = "The value of the battery life percent"; - } - DTO_FIELD(Float32, batteryLifePercent); - - DTO_FIELD_INFO(batteryLifeTime) { - info->description = "The value of the battery life time"; - } - DTO_FIELD(Float32, batteryLifeTime); - - DTO_FIELD_INFO(batteryFullLifeTime) { - info->description = "The value of the battery full life time"; - } - DTO_FIELD(Float32, batteryFullLifeTime); - - DTO_FIELD_INFO(energyNow) { - info->description = "The value of the energy now"; - } - DTO_FIELD(Float32, energyNow); - - DTO_FIELD_INFO(energyFull) { - info->description = "The value of the energy full"; - } - DTO_FIELD(Float32, energyFull); - - DTO_FIELD_INFO(energyDesign) { - info->description = "The value of the energy design"; - } - DTO_FIELD(Float32, energyDesign); - - DTO_FIELD_INFO(voltageNow) { - info->description = "The value of the voltage now"; - } - DTO_FIELD(Float32, voltageNow); - - DTO_FIELD_INFO(currentNow) { - info->description = "The value of the current now"; - } - DTO_FIELD(Float32, currentNow); -}; - -class ReturnDiskUsageDto : public StatusDto { - DTO_INIT(ReturnDiskUsageDto, StatusDto) - - DTO_FIELD_INFO(value) { info->description = "The value of the disk usage"; } - DTO_FIELD(UnorderedFields, value); -}; - -class ReturnAvailableDrivesDto : public StatusDto { - DTO_INIT(ReturnAvailableDrivesDto, StatusDto) - - DTO_FIELD_INFO(value) { - info->description = "The value of the available drives"; - } - DTO_FIELD(Vector, value); -}; - -class ReturnNetworkInfoDto : public StatusDto { - DTO_INIT(ReturnNetworkInfoDto, StatusDto) - - DTO_FIELD_INFO(wifi) { info->description = "The value of the wifi"; } - DTO_FIELD(String, wifi); - DTO_FIELD_INFO(wired) { info->description = "The value of the wired"; } - DTO_FIELD(String, wired); - DTO_FIELD_INFO(hotspot) { info->description = "The value of the hotspot"; } - DTO_FIELD(Boolean, hotspot); -}; - -class ReturnOSInfoDto : public StatusDto { - DTO_INIT(ReturnOSInfoDto, StatusDto) - - DTO_FIELD_INFO(name) { info->description = "The value of the OS name"; } - DTO_FIELD(String, name); - - DTO_FIELD_INFO(version) { - info->description = "The value of the OS version"; - } - DTO_FIELD(String, version); - - DTO_FIELD_INFO(kernelVersion) { - info->description = "The value of the OS kernel version"; - } - DTO_FIELD(String, kernelVersion); - - DTO_FIELD_INFO(architecture) { - info->description = "The value of the OS architecture"; - } - DTO_FIELD(String, architecture); - - DTO_FIELD_INFO(compiler) { - info->description = "The value of the OS compiler"; - } - DTO_FIELD(String, compiler); -}; - -#include OATPP_CODEGEN_END(DTO) - -#endif // SYSTEMDTO_HPP diff --git a/modules/lithium.webserver/src/data/UserDto.hpp b/modules/lithium.webserver/src/data/UserDto.hpp deleted file mode 100644 index 72c3117d..00000000 --- a/modules/lithium.webserver/src/data/UserDto.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef UserDto_hpp -#define UserDto_hpp - -#include "oatpp/core/Types.hpp" -#include "oatpp/core/macro/codegen.hpp" - -#include OATPP_CODEGEN_BEGIN(DTO) - -ENUM(Role, v_int32, VALUE(GUEST, 0, "ROLE_GUEST"), - VALUE(ADMIN, 1, "ROLE_ADMIN")) - -class UserDto : public oatpp::DTO { - DTO_INIT(UserDto, DTO) - - DTO_FIELD(Int32, id); - DTO_FIELD(String, userName, "username"); - DTO_FIELD(String, email, "email"); - DTO_FIELD(String, password, "password"); - DTO_FIELD(Enum::AsString, role, "role"); -}; - -#include OATPP_CODEGEN_END(DTO) - -#endif /* UserDto_hpp */ diff --git a/modules/lithium.webserver/src/webserver.cpp b/modules/lithium.webserver/src/webserver.cpp deleted file mode 100644 index c394dfe7..00000000 --- a/modules/lithium.webserver/src/webserver.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "webserver.hpp" -#include - -namespace lithium::webserver { - - void say_hello() { - std::cout << "Hello from the lithium.webserver module!" << std::endl; - } - -} // namespace lithium::webserver diff --git a/modules/lithium.webserver/src/websocket/Connection.cpp b/modules/lithium.webserver/src/websocket/Connection.cpp deleted file mode 100644 index dd9019d4..00000000 --- a/modules/lithium.webserver/src/websocket/Connection.cpp +++ /dev/null @@ -1,494 +0,0 @@ -/* - * Connection.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-1-13 - -Description: Websocket Connection - -**************************************************/ - -#include "Connection.hpp" -#include "Session.hpp" - -#include "oatpp/core/utils/ConversionUtils.hpp" - -Connection::Connection(const std::shared_ptr& socket, - const std::shared_ptr& hubSession, v_int64 connectionId) - : m_socket(socket), - m_hubSession(hubSession), - m_connectionId(connectionId), - m_messageQueue(std::make_shared()), - m_pingTime(-1), - m_failedPings(0), - m_lastPingTimestamp(-1) {} - -oatpp::async::CoroutineStarter Connection::sendMessageAsync( - const oatpp::Object& message) { - class SendMessageCoroutine - : public oatpp::async::Coroutine { - private: - oatpp::async::Lock* m_lock; - std::shared_ptr m_websocket; - oatpp::String m_message; - - public: - SendMessageCoroutine(oatpp::async::Lock* lock, - const std::shared_ptr& websocket, - const oatpp::String& message) - : m_lock(lock), m_websocket(websocket), m_message(message) {} - - Action act() override { - return oatpp::async::synchronize( - m_lock, m_websocket->sendOneFrameTextAsync(m_message)) - .next(finish()); - } - }; - - std::lock_guard socketLock(m_socketMutex); - if (m_socket) { - return SendMessageCoroutine::start( - &m_writeLock, m_socket, m_objectMapper->writeToString(message)); - } - - return nullptr; -} - -oatpp::async::CoroutineStarter Connection::sendErrorAsync( - const oatpp::Object& error, bool fatal) { - class SendErrorCoroutine - : public oatpp::async::Coroutine { - private: - oatpp::async::Lock* m_lock; - std::shared_ptr m_websocket; - oatpp::String m_message; - bool m_fatal; - - public: - SendErrorCoroutine(oatpp::async::Lock* lock, - const std::shared_ptr& websocket, - const oatpp::String& message, bool fatal) - : m_lock(lock), - m_websocket(websocket), - m_message(message), - m_fatal(fatal) {} - - Action act() override { - /* synchronized async pipeline */ - auto call = oatpp::async::synchronize( - m_lock, m_websocket->sendOneFrameTextAsync(m_message)); - - if (m_fatal) { - return call.next(m_websocket->sendCloseAsync()).next(finish()); - //.next(new oatpp::async::Error("API Error")); - } - - return call.next(finish()); - } - }; - - auto message = MessageDto::createShared(); - message->code = MessageCodes::OUTGOING_ERROR; - message->payload = error; - - std::lock_guard socketLock(m_socketMutex); - if (m_socket) { - return SendErrorCoroutine::start(&m_writeLock, m_socket, - m_objectMapper->writeToString(message), - fatal); - } - - return nullptr; -} - -bool Connection::queueMessage(const oatpp::Object& message) { - class SendMessageCoroutine - : public oatpp::async::Coroutine { - private: - std::shared_ptr m_mapper; - oatpp::async::Lock* m_lock; - std::shared_ptr m_websocket; - std::shared_ptr m_queue; - - public: - SendMessageCoroutine( - const std::shared_ptr& mapper, - oatpp::async::Lock* lock, - const std::shared_ptr& websocket, - const std::shared_ptr& queue) - : m_mapper(mapper), - m_lock(lock), - m_websocket(websocket), - m_queue(queue) {} - - Action act() override { - std::unique_lock lock(m_queue->mutex); - if (m_queue->queue.empty()) { - m_queue->active = false; - return finish(); - } - auto msg = m_queue->queue.back(); - m_queue->queue.pop_back(); - lock.unlock(); - - auto json = m_mapper->writeToString(msg); - return oatpp::async::synchronize( - m_lock, m_websocket->sendOneFrameTextAsync(json)) - .next(repeat()); - } - - Action handleError(oatpp::async::Error* error) override { - return yieldTo(&SendMessageCoroutine::act); - } - }; - - if (message) { - std::lock_guard lock(m_messageQueue->mutex); - if (m_messageQueue->queue.size() < - m_hubSession->getConfig()->maxQueuedMessages) { - m_messageQueue->queue.push_front(message); - if (!m_messageQueue->active) { - m_messageQueue->active = true; - std::lock_guard socketLock(m_socketMutex); - if (m_socket) { - m_asyncExecutor->execute( - m_objectMapper, &m_writeLock, m_socket, m_messageQueue); - } - } - return true; - } - } - return false; -} - -void Connection::ping(v_int64 timestampMicroseconds) { - class PingCoroutine : public oatpp::async::Coroutine { - private: - oatpp::async::Lock* m_lock; - std::shared_ptr m_websocket; - oatpp::String m_message; - - public: - PingCoroutine(oatpp::async::Lock* lock, - const std::shared_ptr& websocket, - const oatpp::String& message) - : m_lock(lock), m_websocket(websocket), m_message(message) {} - - Action act() override { - return oatpp::async::synchronize( - m_lock, m_websocket->sendOneFrameTextAsync(m_message)) - .next(finish()); - } - }; - - auto message = MessageDto::createShared( - MessageCodes::OUTGOING_PING, oatpp::Int64(timestampMicroseconds)); - - std::lock_guard socketLock(m_socketMutex); - if (m_socket) { - m_asyncExecutor->execute( - &m_writeLock, m_socket, m_objectMapper->writeToString(message)); - } -} - -void Connection::kick() { - class KickCoroutine : public oatpp::async::Coroutine { - private: - oatpp::async::Lock* m_lock; - std::shared_ptr m_websocket; - oatpp::String m_message; - - public: - KickCoroutine(oatpp::async::Lock* lock, - const std::shared_ptr& websocket, - const oatpp::String& message) - : m_lock(lock), m_websocket(websocket), m_message(message) {} - - Action act() override { - return oatpp::async::synchronize( - m_lock, m_websocket->sendOneFrameTextAsync(m_message)) - .next(yieldTo(&KickCoroutine::onMessageSent)); - } - - Action onMessageSent() { - m_websocket->getConnection().invalidate(); - return finish(); - } - }; - - auto message = - MessageDto::createShared(MessageCodes::OUTGOING_CLIENT_KICKED, - oatpp::String("you were kicked.")); - - std::lock_guard socketLock(m_socketMutex); - if (m_socket) { - m_asyncExecutor->execute( - &m_writeLock, m_socket, m_objectMapper->writeToString(message)); - } -} - -std::shared_ptr Connection::getHubSession() { return m_hubSession; } - -v_int64 Connection::getConnectionId() { return m_connectionId; } - -void Connection::invalidateSocket() { - { - std::lock_guard socketLock(m_socketMutex); - if (m_socket) { - m_socket->getConnection().invalidate(); - m_socket.reset(); - } - } - - { - std::lock_guard lock(m_messageQueue->mutex); - m_messageQueue->queue.clear(); - } -} - -void Connection::checkPingsRules(const v_int64 currentPingSessionTimestamp) { - std::lock_guard pingLock(m_pingMutex); - - if (m_lastPingTimestamp != currentPingSessionTimestamp) { - m_failedPings++; - } - - OATPP_LOGD("Connection", "failed pings=%d", m_failedPings) - - if (m_failedPings >= m_hubSession->getConfig()->maxFailedPings) { - OATPP_LOGD("Connection", - "maxFailedPings exceeded. ConnectionId=%lld. Connection dropped.", - m_connectionId); - invalidateSocket(); - } -} - -oatpp::async::CoroutineStarter Connection::handlePong( - const oatpp::Object& message) { - auto timestamp = message->payload.retrieve(); - - if (!timestamp) { - return sendErrorAsync(ErrorDto::createShared( - ErrorCodes::BAD_MESSAGE, "Message MUST contain 'payload.'")); - } - - v_int64 pt = m_hubSession->reportConnectionPong(m_connectionId, timestamp); - - { - std::lock_guard pingLock(m_pingMutex); - m_pingTime = pt; - if (m_pingTime >= 0) { - m_failedPings = 0; - m_lastPingTimestamp = timestamp; - } - } - - return nullptr; -} - -oatpp::async::CoroutineStarter Connection::handleBroadcast( - const oatpp::Object& message) { - auto connections = m_hubSession->getAllConnections(); - - for (auto connection : connections) { - if (connection->getConnectionId() != m_connectionId) { - auto payload = OutgoingMessageDto::createShared(); - payload->connectionId = m_connectionId; - payload->data = message->payload.retrieve(); - - connection->queueMessage(MessageDto::createShared( - MessageCodes::OUTGOING_MESSAGE, payload)); - } - } - - return nullptr; -} - -oatpp::async::CoroutineStarter Connection::handleDirectMessage( - const oatpp::Object& message) { - auto dm = message->payload.retrieve>(); - - if (!dm) { - return sendErrorAsync(ErrorDto::createShared( - ErrorCodes::BAD_MESSAGE, "Message MUST contain 'payload.'")); - } - - if (!dm->connectionIds || dm->connectionIds->empty()) { - return sendErrorAsync(ErrorDto::createShared( - ErrorCodes::BAD_MESSAGE, - "Payload MUST contain array of connectionIds of recipients.")); - } - - auto connections = m_hubSession->getConnections(dm->connectionIds); - - for (auto connection : connections) { - if (connection->getConnectionId() != m_connectionId) { - auto payload = OutgoingMessageDto::createShared(); - payload->connectionId = m_connectionId; - payload->data = dm->data; - - connection->queueMessage(MessageDto::createShared( - MessageCodes::OUTGOING_MESSAGE, payload)); - } - } - - return nullptr; -} - -oatpp::async::CoroutineStarter Connection::handleSynchronizedEvent( - const oatpp::Object& message) { - m_hubSession->broadcastSynchronizedEvent( - m_connectionId, message->payload.retrieve()); - return nullptr; -} - -oatpp::async::CoroutineStarter Connection::handleKickMessage( - const oatpp::Object& message) { - auto host = m_hubSession->getHost(); - if (host == nullptr) { - return sendErrorAsync(ErrorDto::createShared(ErrorCodes::INVALID_STATE, - "There is no hub host.")); - } - - if (host->getConnectionId() != m_connectionId) { - return sendErrorAsync( - ErrorDto::createShared(ErrorCodes::OPERATION_NOT_PERMITTED, - "Only Host connection can kick others.")); - } - - auto ids = message->payload.retrieve>(); - - if (!ids || ids->empty()) { - return sendErrorAsync(ErrorDto::createShared( - ErrorCodes::BAD_MESSAGE, - "Payload MUST contain array of connectionIds to kick from session.'")); - } - - auto connections = m_hubSession->getConnections(ids); - - for (auto connection : connections) { - if (connection->getConnectionId() != m_connectionId) { - connection->kick(); - } - } - - return nullptr; -} - -oatpp::async::CoroutineStarter Connection::handleClientMessage( - const oatpp::Object& message) { - auto host = m_hubSession->getHost(); - if (host == nullptr) { - return sendErrorAsync(ErrorDto::createShared( - ErrorCodes::INVALID_STATE, - "There is no hub host. No one will receive this message.")); - } - - if (host->getConnectionId() == m_connectionId) { - return sendErrorAsync( - ErrorDto::createShared(ErrorCodes::OPERATION_NOT_PERMITTED, - "Host can't send message to itself.")); - } - - auto payload = OutgoingMessageDto::createShared(); - payload->connectionId = m_connectionId; - payload->data = message->payload.retrieve(); - - host->queueMessage( - MessageDto::createShared(MessageCodes::OUTGOING_MESSAGE, payload)); - - return nullptr; -} - -oatpp::async::CoroutineStarter Connection::handleMessage( - const oatpp::Object& message) { - if (!message->code) { - return sendErrorAsync(ErrorDto::createShared( - ErrorCodes::BAD_MESSAGE, "Message MUST contain 'code' field.")); - } - - switch (*message->code) { - case MessageCodes::INCOMING_PONG: - return handlePong(message); - case MessageCodes::INCOMING_BROADCAST: - return handleBroadcast(message); - case MessageCodes::INCOMING_DIRECT_MESSAGE: - return handleDirectMessage(message); - case MessageCodes::INCOMING_SYNCHRONIZED_EVENT: - return handleSynchronizedEvent(message); - case MessageCodes::INCOMING_HOST_KICK_CLIENTS: - return handleKickMessage(message); - case MessageCodes::INCOMING_CLIENT_MESSAGE: - return handleClientMessage(message); - - default: - return sendErrorAsync( - ErrorDto::createShared(ErrorCodes::OPERATION_NOT_PERMITTED, - "Invalid operation code.")); - } - - return nullptr; -} - -oatpp::async::CoroutineStarter Connection::onPing( - const std::shared_ptr& socket, - const oatpp::String& message) { - return oatpp::async::synchronize(&m_writeLock, - socket->sendPongAsync(message)); -} - -oatpp::async::CoroutineStarter Connection::onPong( - const std::shared_ptr& socket, - const oatpp::String& message) { - return nullptr; // do nothing -} - -oatpp::async::CoroutineStarter Connection::onClose( - const std::shared_ptr& socket, v_uint16 code, - const oatpp::String& message) { - OATPP_LOGD("Connection", "onClose received.") - return nullptr; // do nothing -} - -oatpp::async::CoroutineStarter Connection::readMessage( - const std::shared_ptr& socket, v_uint8 opcode, p_char8 data, - oatpp::v_io_size size) { - if (m_messageBuffer.getCurrentPosition() + size > - m_hubSession->getConfig()->maxMessageSizeBytes) { - auto err = ErrorDto::createShared( - ErrorCodes::BAD_MESSAGE, - "Fatal Error. Serialized message size shouldn't exceed " + - oatpp::utils::conversion::int64ToStdStr( - m_hubSession->getConfig()->maxMessageSizeBytes) + - " bytes."); - return sendErrorAsync(err, true); - } - - if (size == 0) { // message transfer finished - - auto wholeMessage = m_messageBuffer.toString(); - m_messageBuffer.setCurrentPosition(0); - - oatpp::Object message; - - try { - message = m_objectMapper->readFromString>( - wholeMessage); - } catch (const std::runtime_error& e) { - auto err = ErrorDto::createShared( - ErrorCodes::BAD_MESSAGE, "Fatal Error. Can't parse message."); - return sendErrorAsync(err, true); - } - - return handleMessage(message); - - } else if (size > 0) { // message frame received - m_messageBuffer.writeSimple(data, size); - } - - return nullptr; // do nothing -} diff --git a/modules/lithium.webserver/src/websocket/Hub.cpp b/modules/lithium.webserver/src/websocket/Hub.cpp deleted file mode 100644 index 4d304698..00000000 --- a/modules/lithium.webserver/src/websocket/Hub.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Hub.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-1-13 - -Description: Websocket Connection Hub - -**************************************************/ - -#include "Hub.hpp" - -Hub::Hub(const oatpp::Object& config) - : m_state(std::make_shared()) { - m_state->config = config; - m_state->isPingerActive = false; -} - -void Hub::startPinger() { - class Pinger : public oatpp::async::Coroutine { - private: - std::shared_ptr m_state; - - public: - Pinger(const std::shared_ptr& state) : m_state(state) {} - - Action act() override { - std::lock_guard lock(m_state->mutex); - - if (m_state->sessions.empty()) { - m_state->isPingerActive = false; - OATPP_LOGD("Pinger", "Stopped") - return finish(); - } - - for (auto& session : m_state->sessions) { - session.second->checkAllConnectionsPings(); - } - - for (auto& session : m_state->sessions) { - session.second->pingAllConnections(); - } - - return waitRepeat( - std::chrono::milliseconds(m_state->config->pingIntervalMillis)); - } - }; - - if (!m_state->isPingerActive) { - OATPP_LOGD("Pinger", "Started") - m_state->isPingerActive = true; - m_asyncExecutor->execute(m_state); - } -} - -std::shared_ptr Hub::createNewSession( - const oatpp::String& sessionId) { - std::lock_guard lock(m_state->mutex); - - auto it = m_state->sessions.find(sessionId); - if (it != m_state->sessions.end()) { - return nullptr; - } - - auto session = std::make_shared(sessionId, m_state->config); - m_state->sessions.insert({sessionId, session}); - - startPinger(); - - return session; -} - -std::shared_ptr Hub::findSession(const oatpp::String& sessionId) { - std::lock_guard lock(m_state->mutex); - auto it = m_state->sessions.find(sessionId); - if (it != m_state->sessions.end()) { - return it->second; - } - return nullptr; // Session not found. -} - -void Hub::deleteSession(const oatpp::String& sessionId) { - std::lock_guard lock(m_state->mutex); - m_state->sessions.erase(sessionId); -} diff --git a/modules/lithium.webserver/src/websocket/Registry.cpp b/modules/lithium.webserver/src/websocket/Registry.cpp deleted file mode 100644 index 73228da2..00000000 --- a/modules/lithium.webserver/src/websocket/Registry.cpp +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Registry.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-1-13 - -Description: Websocket Hub Registry - -**************************************************/ - -#include "Registry.hpp" - -#include "Constants.hpp" - -Registry::Registry() {} - -void Registry::sendSocketErrorAsync( - const std::shared_ptr& socket, - const oatpp::Object& error, bool fatal) { - class SendErrorCoroutine - : public oatpp::async::Coroutine { - private: - std::shared_ptr m_websocket; - oatpp::String m_message; - bool m_fatal; - - public: - SendErrorCoroutine(const std::shared_ptr& websocket, - const oatpp::String& message, bool fatal) - : m_websocket(websocket), m_message(message), m_fatal(fatal) {} - - Action act() override { - /* synchronized async pipeline */ - auto call = m_websocket->sendOneFrameTextAsync(m_message); - - if (m_fatal) { - return call.next(m_websocket->sendCloseAsync()).next(finish()); - } - - return call.next(finish()); - } - }; - - auto message = MessageDto::createShared(); - message->code = MessageCodes::OUTGOING_ERROR; - message->payload = error; - - m_asyncExecutor->execute( - socket, m_objectMapper->writeToString(message), fatal); -} - -oatpp::String Registry::getRequiredParameter( - const oatpp::String& name, - const std::shared_ptr& params, - SessionInfo& sessionInfo) { - auto it = params->find(name); - if (it != params->end() && it->second) { - return it->second; - } - sessionInfo.error = - ErrorDto::createShared(ErrorCodes::BAD_REQUEST, - "Missing required parameter - '" + name + "'."); - return nullptr; -} - -Registry::SessionInfo Registry::getSessionForConnection( - const std::shared_ptr& socket, - const std::shared_ptr& params) { - SessionInfo result; - - auto hubId = - getRequiredParameter(Constants::PARAM_GAME_ID, params, result); - if (result.error) - return result; - - auto sessionId = - getRequiredParameter(Constants::PARAM_GAME_SESSION_ID, params, result); - if (result.error) - return result; - - auto connectionType = - getRequiredParameter(Constants::PARAM_PEER_TYPE, params, result); - if (result.error) - return result; - - result.isHost = connectionType == Constants::PARAM_PEER_TYPE_HOST; - - auto hub = getHubById(hubId); - if (!hub) { - result.error = - ErrorDto::createShared(ErrorCodes::GAME_NOT_FOUND, - "Hub config not found. Hub config should " - "be present on the server."); - return result; - } - - if (result.isHost) { - result.session = hub->createNewSession(sessionId); - if (!result.session) { - result.error = - ErrorDto::createShared(ErrorCodes::OPERATION_NOT_PERMITTED, - "Session with such ID already exists. " - "Can't create new session session."); - return result; - } - } else { - result.session = hub->findSession(sessionId); - if (!result.session) { - result.error = ErrorDto::createShared( - ErrorCodes::SESSION_NOT_FOUND, - "No hub session found for given sessionId."); - return result; - } - } - - return result; -} - -std::shared_ptr Registry::getHubById(const oatpp::String& hubId) { - std::lock_guard lock(m_mutex); - - auto it = m_hubs.find(hubId); - if (it != m_hubs.end()) { - return it->second; - } - - auto config = m_hubConfig->getHubConfig(hubId); - if (config) { - auto hub = std::make_shared(config); - m_hubs.insert({config->hubId, hub}); - return hub; - } - - return nullptr; -} - -void Registry::onAfterCreate_NonBlocking( - const std::shared_ptr& socket, - const std::shared_ptr& params) { - OATPP_LOGD("Registry", "socket created - %d", socket.get()) - - auto sessionInfo = getSessionForConnection(socket, params); - - if (sessionInfo.error) { - sendSocketErrorAsync(socket, sessionInfo.error, true); - return; - } - - auto connection = std::make_shared( - socket, sessionInfo.session, sessionInfo.session->generateNewConnectionId()); - - socket->setListener(connection); - - OATPP_LOGD("Registry", "connection created for socket - %d", socket.get()) - - sessionInfo.session->addConnection(connection, sessionInfo.isHost); -} - -void Registry::onBeforeDestroy_NonBlocking( - const std::shared_ptr& socket) { - OATPP_LOGD("Registry", "destroying socket - %d", socket.get()) - - auto connection = std::static_pointer_cast(socket->getListener()); - if (connection) { - connection->invalidateSocket(); - - auto session = connection->getHubSession(); - - bool isEmptySession; - session->removeConnectionById(connection->getConnectionId(), isEmptySession); - - if (isEmptySession) { - auto hub = getHubById(session->getConfig()->hubId); - hub->deleteSession(session->getId()); - OATPP_LOGD("Registry", "Session deleted - %d", session.get()) - } - - } else { - socket->getConnection().invalidate(); - } -} diff --git a/modules/lithium.webserver/src/websocket/Session.cpp b/modules/lithium.webserver/src/websocket/Session.cpp deleted file mode 100644 index 1f1c3b69..00000000 --- a/modules/lithium.webserver/src/websocket/Session.cpp +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Session.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-1-13 - -Description: Websocket Hub Connection Session - -**************************************************/ - -#include "Session.hpp" - -#include "oatpp/core/utils/ConversionUtils.hpp" - -Session::Session(const oatpp::String& id, - const oatpp::Object& config) - : m_id(id), - m_config(config), - m_connectionIdCounter(0), - m_synchronizedEventId(0), - m_pingCurrentTimestamp(-1), - m_pingBestTime(-1), - m_pingBestConnectionId(-1), - m_pingBestConnectionSinceTimestamp(-1) {} - -oatpp::String Session::getId() { return m_id; } - -oatpp::Object Session::getConfig() { return m_config; } - -void Session::addConnection(const std::shared_ptr& connection, bool isHost) { - { - std::lock_guard lock(m_connectionsMutex); - m_connections.insert({connection->getConnectionId(), connection}); - if (isHost) { - m_host = connection; - } else { - if (m_host) { - m_host->queueMessage(MessageDto::createShared( - MessageCodes::OUTGOING_HOST_CLIENT_JOINED, - oatpp::Int64(connection->getConnectionId()))); - } - } - } - - auto hello = HelloMessageDto::createShared(); - hello->connectionId = connection->getConnectionId(); - hello->isHost = isHost; - - connection->queueMessage( - MessageDto::createShared(MessageCodes::OUTGOING_HELLO, hello)); -} - -void Session::setHost(const std::shared_ptr& connection) { - std::lock_guard lock(m_connectionsMutex); - m_host = connection; -} - -std::shared_ptr Session::getHost() { - std::lock_guard lock(m_connectionsMutex); - return m_host; -} - -bool Session::isHostConnection(v_int64 connectionId) { - std::lock_guard lock(m_connectionsMutex); - return m_host && m_host->getConnectionId() == connectionId; -} - -void Session::removeConnectionById(v_int64 connectionId, bool& isEmpty) { - std::lock_guard lock(m_connectionsMutex); - if (m_host && m_host->getConnectionId() == connectionId) { - m_host.reset(); - } - m_connections.erase(connectionId); - isEmpty = m_connections.empty(); - if (m_host) { - m_host->queueMessage(MessageDto::createShared( - MessageCodes::OUTGOING_HOST_CLIENT_LEFT, oatpp::Int64(connectionId))); - } -} - -std::vector> Session::getAllConnections() { - std::lock_guard lock(m_connectionsMutex); - std::vector> result; - for (auto& pair : m_connections) { - result.emplace_back(pair.second); - } - return result; -} - -std::vector> Session::getConnections( - const oatpp::Vector& connectionIds) { - if (!connectionIds) { - return {}; - } - - std::vector> result; - - std::lock_guard lock(m_connectionsMutex); - - for (auto& id : *connectionIds) { - if (id) { - auto it = m_connections.find(*id); - if (it != m_connections.end()) { - result.emplace_back(it->second); - } - } - } - - return result; -} - -void Session::broadcastSynchronizedEvent(v_int64 senderId, - const oatpp::String& eventData) { - std::lock_guard lock(m_connectionsMutex); - - auto event = OutgoingSynchronizedMessageDto::createShared(); - event->eventId = m_synchronizedEventId++; - event->connectionId = senderId; - event->data = eventData; - - auto message = MessageDto::createShared( - MessageCodes::OUTGOING_SYNCHRONIZED_EVENT, event); - for (auto& connection : m_connections) { - connection.second->queueMessage(message); - } -} - -v_int64 Session::generateNewConnectionId() { return m_connectionIdCounter++; } - -void Session::checkAllConnectionsPings() { - v_int64 currentTimestamp; - { - std::lock_guard lock(m_pingMutex); - currentTimestamp = m_pingCurrentTimestamp; - } - - std::lock_guard lock(m_connectionsMutex); - for (auto& connection : m_connections) { - connection.second->checkPingsRules(currentTimestamp); - } -} - -void Session::pingAllConnections() { - auto timestamp = oatpp::base::Environment::getMicroTickCount(); - - { - std::lock_guard lock(m_pingMutex); - m_pingCurrentTimestamp = timestamp; - } - - std::lock_guard lock(m_connectionsMutex); - for (auto& connection : m_connections) { - connection.second->ping(timestamp); - } -} - -v_int64 Session::reportConnectionPong(v_int64 connectionId, v_int64 timestamp) { - std::lock_guard lock(m_pingMutex); - if (timestamp != m_pingCurrentTimestamp) { - return -1; - } - - v_int64 pingTime = - oatpp::base::Environment::getMicroTickCount() - timestamp; - - if (m_pingBestTime < 0 || m_pingBestTime > pingTime) { - m_pingBestTime = pingTime; - if (m_pingBestConnectionId != connectionId) { - m_pingBestConnectionId = connectionId; - m_pingBestConnectionSinceTimestamp = timestamp; - OATPP_LOGD("Session", "new best connection=%lld, ping=%lld", connectionId, - pingTime) - } - } - - return pingTime; -} diff --git a/modules/lithium.webserver/src/websocket/template/error_message.hpp b/modules/lithium.webserver/src/websocket/template/error_message.hpp deleted file mode 100644 index 7e729a1a..00000000 --- a/modules/lithium.webserver/src/websocket/template/error_message.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#define RESPONSE_ERROR(res, code, msg) \ - do \ - { \ - LOG_F(ERROR, "{}: {}", __func__, msg); \ - res["error"] = magic_enum::enum_name(code); \ - res["message"] = msg; \ - res["timestamp"] = atom::utils::getChinaTimestampString(); \ - sendMessage(res.dump()); \ - return; \ - } while (0) - -#define RESPONSE_ERROR_C(res, code, msg) \ - do \ - { \ - LOG_F(ERROR, "{}: {}", __func__, msg); \ - res["error"] = magic_enum::enum_name(code); \ - res["message"] = msg; \ - res["timestamp"] = atom::utils::getChinaTimestampString(); \ - sendMessage(res.dump()); \ - return nullptr; \ - } while (0) - -#define RESPONSE_EXCEPTION(res, code, msg) \ - do \ - { \ - LOG_F(ERROR, "{}: {}", __func__, msg); \ - res["error"] = magic_enum::enum_name(code); \ - res["message"] = msg; \ - res["timestamp"] = getChinaTimestampString(); \ - sendMessage(res.dump()); \ - return; \ - } while (0) - -#define RESPONSE_EXCEPTION_C(res, code, msg) \ - do \ - { \ - LOG_F(ERROR, "{}: {}", __func__, msg); \ - res["error"] = magic_enum::enum_name(code); \ - res["message"] = msg; \ - res["timestamp"] = atom::utils::getChinaTimestampString(); \ - sendMessage(res.dump()); \ - return nullptr; \ - } while (0) diff --git a/modules/lithium.webserver/src/websocket/template/function.hpp b/modules/lithium.webserver/src/websocket/template/function.hpp deleted file mode 100644 index e28d89e3..00000000 --- a/modules/lithium.webserver/src/websocket/template/function.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#define FUNCTION_BEGIN \ - json res = {{"command", __func__}}; \ - try \ - { - -#define FUNCTION_END \ - } \ - catch (const json::exception &e) \ - { \ - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); \ - } \ - catch (const std::exception &e) \ - { \ - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); \ - } \ - catch (...) \ - { \ - RESPONSE_EXCEPTION(res, ServerError::UnknownError, "Unknown Error"); \ - } diff --git a/modules/lithium.webserver/src/websocket/template/message.hpp b/modules/lithium.webserver/src/websocket/template/message.hpp deleted file mode 100644 index 3a792caf..00000000 --- a/modules/lithium.webserver/src/websocket/template/message.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#define CHECK_STR_PARAM(_params) \ - if (!params->get("params", #_params).has_value()) \ - { \ - LOG_F(ERROR, "{}::{}: {} is needed!", getName(), __func__, #_params); \ - return MessageHelper::MakeTextMessage(__func__, #_params + "lib_name is needed!", "client", "websocket-checker"); \ - } \ - \ - -#define CHECK_INT_PARAM(_params) \ - if (!params->get("params", #_params).has_value()) \ - { \ - LOG_F(ERROR, "{}::{}: {} is needed!", getName(), __func__, #_params); \ - return MessageHelper::MakeTextMessage(__func__, #_params + "lib_name is needed!", "client", "websocket-checker"); \ - } \ - \ - -#define CHECK_BOOL_PARAM(_params) \ - if (!params->get("params", #_params).has_value()) \ - { \ - LOG_F(ERROR, "{}::{}: {} is needed!", getName(), __func__, #_params); \ - return MessageHelper::MakeTextMessage(__func__, #_params + "lib_name is needed!", "client", "websocket-checker"); \ - } \ - \ diff --git a/modules/lithium.webserver/src/websocket/template/variable.hpp b/modules/lithium.webserver/src/websocket/template/variable.hpp deleted file mode 100644 index b49744ae..00000000 --- a/modules/lithium.webserver/src/websocket/template/variable.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#define GET_PARAM_VALUE(param_name, param_type, var_name) \ - var_name = m_params[param_name].get(); - -#define GET_STRING_PARAM_VALUE(param_name, var_name) \ - std::string var_name = m_params[param_name].get(); - -#define SET_STRING_PARAM_VALUE(param_name, var_name) \ - var_name = m_params[param_name].get(); - -#define GET_INT_PARAM_VALUE(param_name, var_name) \ - int var_name = m_params[param_name].get(); - -#define SET_INT_PARAM_VALUE(param_name, var_name) \ - var_name = m_params[param_name].get(); - -#define GET_FLOAT_PARAM_VALUE(param_name, var_name) \ - float var_name = m_params[param_name].get(); - -#define SET_FLOAT_PARAM_VALUE(param_name, var_name) \ - var_name = m_params[param_name].get(); - -#define GET_DOUBLE_PARAM_VALUE(param_name, var_name) \ - double var_name = m_params[param_name].get(); - -#define SET_DOUBLE_PARAM_VALUE(param_name, var_name) \ - var_name = m_params[param_name].get(); - -#define GET_BOOL_PARAM_VALUE(param_name, var_name) \ - bool var_name = m_params[param_name].get(); - -#define SET_BOOL_PARAM_VALUE(param_name, var_name) \ - var_name = m_params[param_name].get(); - -#define CHECK_PARAM_EXISTS(param_name) \ - if (!m_params.contains(#param_name)) \ - { \ - RESPONSE_ERROR(res, ServerError::MissingParameters, #param_name " is required"); \ - } diff --git a/modules/meson.build b/modules/meson.build deleted file mode 100644 index 3385cb54..00000000 --- a/modules/meson.build +++ /dev/null @@ -1,35 +0,0 @@ -# Meson build file for Lithium Builtin Modules -# This project is licensed under the terms of the GPL3 license. -# -# Project Name: Lithium Builtin Modules -# Description: A collection of useful system functions -# Author: Max Qian -# License: GPL3 - -project('lithium.builtin', ['c', 'cpp'], license : 'GPL3') - -# Function to recursively add subdirectories that contain a meson.build file -subdirs = [] - -# Function to get all subdirectories that contain both meson.build and package.json -def add_subdirectories_recursively(start_dir) - entries = meson.get_directory_listing(start_dir, true) - - foreach entry : entries - if entry.is_dir() and - meson.is_directory(entry.path()) and - meson.is_file(entry.path(), 'meson.build') and - meson.is_file(entry.path(), 'package.json') - message('Adding module subdirectory: ' + entry.path()) - subdirs += entry.path() - endif - endforeach -endfunction - -# Use the function to find subdirectories -add_subdirectories_recursively(meson.current_source_dir()) - -# Add subdirectories to the build -foreach subdir : subdirs - subdir(subdir) -endforeach diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..cbbe8a88 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +numpy>=1.24.3 +pandas>=2.0.3 +requests>=2.31.0 +fastapi>=0.111.0 +pytest>=7.2.2 diff --git a/scripts/build_ci.sh b/scripts/build_ci.sh index 1b7eeb11..25860c95 100644 --- a/scripts/build_ci.sh +++ b/scripts/build_ci.sh @@ -1,68 +1,122 @@ -#!/bin/bash +#!/bin/sh set -e echo "Checking system environment..." -# Check if the script is run with sudo -if [[ $EUID -ne 0 ]]; then +# 检查是否以 sudo 运行脚本 +if [ "$(id -u)" -ne 0 ]; then echo "This script must be run with sudo." exit 1 fi -# Check if the system is Debian-based -if ! command -v apt-get &> /dev/null; then - echo "This script only supports Debian-based systems." +# 检查系统包管理器 +if command -v apt-get > /dev/null; then + PKG_MANAGER="apt-get" + PKG_INSTALL="$PKG_MANAGER install -y" + PKG_UPDATE="$PKG_MANAGER update" + PKG_UPGRADE="$PKG_MANAGER upgrade -y" + PKG_REMOVE="$PKG_MANAGER autoremove -y" + PKG_CLEAN="$PKG_MANAGER clean" + DEPENDENCIES="libcfitsio-dev zlib1g-dev libssl-dev libzip-dev libnova-dev libfmt-dev libopencv-dev build-essential software-properties-common uuid-dev" +elif command -v yum > /dev/null; then + PKG_MANAGER="yum" + PKG_INSTALL="$PKG_MANAGER install -y" + PKG_UPDATE="$PKG_MANAGER update -y" + PKG_UPGRADE="$PKG_MANAGER upgrade -y" + PKG_REMOVE="$PKG_MANAGER autoremove -y" + PKG_CLEAN="$PKG_MANAGER clean all" + DEPENDENCIES="cfitsio-devel zlib-devel openssl-devel libzip-devel nova-devel fmt-devel opencv-devel make automake gcc gcc-c++ kernel-devel uuid-devel" +else + echo "This script only supports Debian-based or RedHat-based systems." exit 1 fi -# Check if gcc and g++ are installed -if ! command -v gcc &> /dev/null || ! command -v g++ &> /dev/null; then +# 更新系统包 +echo "Updating system packages..." +$PKG_UPDATE +$PKG_UPGRADE + +# 安装或更新 gcc 和 g++ +if ! command -v gcc > /dev/null || ! command -v g++ > /dev/null; then echo "gcc and g++ are not installed. Installing..." - apt-get install -y gcc g++ + $PKG_INSTALL gcc g++ else echo "gcc and g++ are already installed." fi -# Check if g++ version is at least 10 -gpp_version=$(g++ --version | grep -oP '(?<=g\+\+ )[0-9]+') +# 检查 g++ 版本是否至少为 10 +gpp_version=$(g++ -dumpversion | cut -f1 -d.) if [ "$gpp_version" -lt "10" ]; then - sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y - sudo apt-get install gcc-13 g++-13 -y + if [ "$PKG_MANAGER" = "apt-get" ]; then + echo "Installing gcc-13 and g++-13..." + add-apt-repository ppa:ubuntu-toolchain-r/test -y + $PKG_UPDATE + $PKG_INSTALL gcc-13 g++-13 + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 100 + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 100 + elif [ "$PKG_MANAGER" = "yum" ]; then + echo "Installing devtoolset-10..." + $PKG_INSTALL centos-release-scl + $PKG_INSTALL devtoolset-10 + source /opt/rh/devtoolset-10/enable + fi fi -# Check if CMake is installed -if ! command -v cmake &> /dev/null; then +# 安装或更新 CMake +if ! command -v cmake > /dev/null; then echo "CMake is not installed. Installing..." - apt-get install -y cmake + $PKG_INSTALL cmake else echo "CMake is already installed." fi -# Check if CMake version is at least 3.20 -cmake_version=$(cmake --version | grep -oP '(?<=version )([0-9]+\.[0-9]+)') -if [ "$(printf "%s\n" "3.20" "$cmake_version" | sort -V | head -n1)" != "3.20" ]; then +# 检查 CMake 版本是否至少为 3.20 +cmake_version=$(cmake --version | awk -F " " '/version/ {print $3}') +if [ "$(printf '%s\n' "3.20" "$cmake_version" | sort -V | head -n1)" != "3.20" ]; then + echo "Updating CMake to version 3.28..." wget https://cmake.org/files/v3.28/cmake-3.28.0-rc5.tar.gz tar -zxvf cmake-3.28.0-rc5.tar.gz cd cmake-3.28.0-rc5 - ./bootstrap && make && sudo make install + ./bootstrap && make -j$(nproc) && make install cd .. + rm -rf cmake-3.28.0-rc5 cmake-3.28.0-rc5.tar.gz fi -echo "Updating system packages..." -apt-get update -apt-get upgrade -y - +# 安装依赖项 echo "Installing dependencies..." -apt-get install -y libcfitsio-dev zlib1g-dev libssl-dev libzip-dev libnova-dev libfmt-dev - -echo "Checking installed dependencies..." -for lib in cfitsio zlib ssl zip nova fmt; do - if ! ldconfig -p | grep -q "lib$lib"; then - echo "lib$lib is not properly installed. Please check the installation manually." - else - echo "lib$lib is properly installed." - fi -done +$PKG_INSTALL $DEPENDENCIES + +# 其他检查和优化选项 +echo "Performing additional checks and optimizations..." + +# 检查 make 是否安装 +if ! command -v make > /dev/null; then + echo "make is not installed. Installing..." + $PKG_INSTALL make +fi + +# 检查 git 是否安装 +if ! command -v git > /dev/null; then + echo "git is not installed. Installing..." + $PKG_INSTALL git +fi + +# 检查 curl 是否安装 +if ! command -v curl > /dev/null; then + echo "curl is not installed. Installing..." + $PKG_INSTALL curl +fi + +# 检查 wget 是否安装 +if ! command -v wget > /dev/null; then + echo "wget is not installed. Installing..." + $PKG_INSTALL wget +fi + +# 清理不必要的包和缓存 +echo "Cleaning up unnecessary packages and cache..." +$PKG_REMOVE +$PKG_CLEAN echo "Build environment setup completed." diff --git a/scripts/build_win.sh b/scripts/build_win.sh index 60379c5e..42bc6131 100644 --- a/scripts/build_win.sh +++ b/scripts/build_win.sh @@ -4,72 +4,101 @@ set -e echo "Checking system environment..." -# Check if pacman is available +# 检查是否在 MSYS2 环境中运行 if ! command -v pacman &> /dev/null; then echo "This script requires an MSYS2 environment with pacman." exit 1 fi +# 更新 MSYS2 镜像列表 echo "Updating MSYS2 mirror list..." sed -i "s#https\?://mirror.msys2.org/#https://mirrors.tuna.tsinghua.edu.cn/msys2/#g" /etc/pacman.d/mirrorlist* +# 更新系统包 echo "Updating system packages..." pacman -Syu --noconfirm -echo "Installing mingw-w64-x86_64-toolchain..." -if ! pacman -Q mingw-w64-x86_64-toolchain &> /dev/null; then - pacman -S --noconfirm mingw-w64-x86_64-toolchain -else - echo "mingw-w64-x86_64-toolchain is already installed." -fi +# 要安装的包列表 +packages=( + "mingw-w64-x86_64-toolchain" + "mingw-w64-x86_64-dlfcn" + "mingw-w64-x86_64-cfitsio" + "mingw-w64-x86_64-cmake" + "mingw-w64-x86_64-libzip" + "mingw-w64-x86_64-zlib" + "mingw-w64-x86_64-fmt" + "mingw-w64-x86_64-libnova" + "mingw-w64-x86_64-libusb" + "mingw-w64-x86_64-minizip-ng" + "mingw-w64-x86_64-make" + "mingw-w64-x86_64-openssl" + "mingw-w64-x86_64-python" +) -echo "Installing mingw-w64-x86_64-dlfcn..." -if ! pacman -Q mingw-w64-x86_64-dlfcn &> /dev/null; then - pacman -S --noconfirm mingw-w64-x86_64-dlfcn -else - echo "mingw-w64-x86_64-dlfcn is already installed." -fi +# 安装包的函数 +install_package() { + local package=$1 + echo "Installing $package..." + if ! pacman -Q $package &> /dev/null; then + pacman -S --noconfirm $package + else + echo "$package is already installed." + fi +} -echo "Installing mingw-w64-x86_64-cfitsio..." -if ! pacman -Q mingw-w64-x86_64-cfitsio &> /dev/null; then - pacman -S --noconfirm mingw-w64-x86_64-cfitsio -else - echo "mingw-w64-x86_64-cfitsio is already installed." -fi +# 安装所有包 +for package in "${packages[@]}"; do + install_package $package +done -echo "Installing mingw-w64-x86_64-cmake..." -if ! pacman -Q mingw-w64-x86_64-cmake &> /dev/null; then - pacman -S --noconfirm mingw-w64-x86_64-cmake -else - echo "mingw-w64-x86_64-cmake is already installed." -fi +# 检查安装的依赖项是否正确 +check_installed() { + local package=$1 + if pacman -Q $package &> /dev/null; then + echo "$package is properly installed." + else + echo "$package is not installed correctly. Please check the installation manually." + fi +} -echo "Installing mingw-w64-x86_64-libzip..." -if ! pacman -Q mingw-w64-x86_64-libzip &> /dev/null; then - pacman -S --noconfirm mingw-w64-x86_64-libzip -else - echo "mingw-w64-x86_64-libzip is already installed." +# 检查所有包 +for package in "${packages[@]}"; do + check_installed $package +done + +# 设置环境变量 +echo "Setting up environment variables..." +export PATH="/mingw64/bin:$PATH" +echo 'export PATH="/mingw64/bin:$PATH"' >> ~/.bashrc + +# 其他检查和优化选项 +echo "Performing additional checks and optimizations..." + +# 检查 make 是否安装 +if ! command -v make &> /dev/null; then + echo "make is not installed. Installing..." + pacman -S --noconfirm mingw-w64-x86_64-make fi -echo "Installing mingw-w64-x86_64-zlib..." -if ! pacman -Q mingw-w64-x86_64-zlib &> /dev/null; then - pacman -S --noconfirm mingw-w64-x86_64-zlib -else - echo "mingw-w64-x86_64-zlib is already installed." +# 检查 git 是否安装 +if ! command -v git &> /dev/null; then + echo "git is not installed. Installing..." + pacman -S --noconfirm mingw-w64-x86_64-git fi -echo "Installing mingw-w64-x86_64-fmt..." -if ! pacman -Q mingw-w64-x86_64-fmt &> /dev/null; then - pacman -S --noconfirm mingw-w64-x86_64-fmt -else - echo "mingw-w64-x86_64-fmt is already installed." +# 检查 curl 是否安装 +if ! command -v curl &> /dev/null; then + echo "curl is not installed. Installing..." + pacman -S --noconfirm mingw-w64-x86_64-curl fi -echo "Installing mingw-w64-x86_64-libnova..." -if ! pacman -Q mingw-w64-x86_64-libnova &> /dev/null; then - pacman -S --noconfirm mingw-w64-x86_64-libnova -else - echo "mingw-w64-x86_64-libnova is already installed." +# 检查 wget 是否安装 +if ! command -v wget &> /dev/null; then + echo "wget is not installed. Installing..." + pacman -S --noconfirm mingw-w64-x86_64-wget fi +echo "Cleaning up unnecessary packages and cache..." +pacman -Sc --noconfirm + echo "Environment setup completed." diff --git a/scripts/check-network.ps1 b/scripts/check-network.ps1 deleted file mode 100644 index c1b0f7eb..00000000 --- a/scripts/check-network.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -[CmdletBinding()] -Param( - [Parameter(Position = 0, Mandatory = $true)] - [string]$ComputerName -) - -# 获取网络连接状态 -$networkStatus = Test-NetConnection -ComputerName $ComputerName - -# 检查网络连接结果 -if ($networkStatus.PingSucceeded -eq $true) { - Write-Output "Internet Connection Success" -} else { - Write-Output "Failed to Connect to the Internet" -} diff --git a/scripts/ci_release.py b/scripts/ci_release.py new file mode 100644 index 00000000..b4586837 --- /dev/null +++ b/scripts/ci_release.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python +""" +GitHub Release Script + +This script automates the creation or update of a GitHub release +based on the current Travis CI build environment. It can also upload +assets to the release. + +Environment Variables: +- MAX_PR_WALKING: Maximum number of PRs to check when looking for a commit (default: 5) +- GITHUB_OAUTH_TOKEN: GitHub OAuth token for authentication +- TRAVIS_REPO_SLUG: Repository slug in the format "owner/repo" +- PROJECT_VERSION: Project version, used for tagging releases +- TRAVIS_COMMIT_MESSAGE: Commit message for the current build +- TRAVIS_COMMIT: Commit SHA for the current build +- GITHUB_DRAFT_RELEASE: Flag indicating if the release is a draft (default: 0) +- GITHUB_PRERELEASE: Flag indicating if the release is a pre-release (default: 1) +- TRAVIS_PULL_REQUEST: Pull request number if the build is for a PR (default: 'false') + +Usage: + python release_script.py [assets...] +""" + +import os +import sys +import time +import traceback +import threading +from github import Github +from github.GithubException import UnknownObjectException + +# Constants +MAX_PR_WALKING = int(os.getenv('MAX_PR_WALKING', '5')) +GITHUB_OAUTH_TOKEN = os.getenv('GITHUB_OAUTH_TOKEN') +TRAVIS_REPO_SLUG = os.getenv('TRAVIS_REPO_SLUG') +PROJECT_VERSION = os.getenv('PROJECT_VERSION') +TRAVIS_COMMIT_MESSAGE = os.getenv('TRAVIS_COMMIT_MESSAGE') +TRAVIS_COMMIT = os.getenv('TRAVIS_COMMIT') +GITHUB_DRAFT_RELEASE = os.getenv('GITHUB_DRAFT_RELEASE', '0') +GITHUB_PRERELEASE = os.getenv('GITHUB_PRERELEASE', '1') +TRAVIS_PULL_REQUEST = os.getenv('TRAVIS_PULL_REQUEST', 'false') + + +def get_github_repo(): + """ + Get the GitHub repository object. + + Returns: + github.Repository.Repository: The repository object. + + Raises: + ValueError: If the GitHub token or repository slug is not provided. + """ + if not GITHUB_OAUTH_TOKEN or not TRAVIS_REPO_SLUG: + raise ValueError("GitHub token or repository slug not provided") + github = Github(GITHUB_OAUTH_TOKEN) + return github.get_repo(TRAVIS_REPO_SLUG) + + +def is_true(env_var): + """ + Convert an environment variable to a boolean value. + + Args: + env_var (str): The environment variable value. + + Returns: + bool: True if the environment variable represents a true value, False otherwise. + """ + return env_var == '1' or env_var.lower() == 'true' + + +def find_pr(repo, commit_id): + """ + Find the pull request associated with a given commit ID. + + Args: + repo (github.Repository.Repository): The repository object. + commit_id (str): The commit SHA to search for. + + Returns: + github.PullRequest.PullRequest or None: The pull request object if found, None otherwise. + """ + all_pulls = list(repo.get_pulls(state='closed', sort='updated'))[::-1] + for index, merged_pr in enumerate(all_pulls): + if index >= MAX_PR_WALKING: + break + if merged_pr.merge_commit_sha == commit_id: + return merged_pr + return None + + +def get_pr(repo, pr_number, commit_id): + """ + Get the pull request object. + + Args: + repo (github.Repository.Repository): The repository object. + pr_number (str): The pull request number. + commit_id (str): The commit SHA to search for if the PR number is not valid. + + Returns: + github.PullRequest.PullRequest or None: The pull request object if found, None otherwise. + """ + pr = None + if pr_number and pr_number != 'false': + try: + pr = repo.get_pull(int(pr_number)) + except ValueError: + print("Invalid pull request number") + if not pr: + retry = 0 + while not pr and retry < 5: + time.sleep(retry * 10) + try: + pr = find_pr(repo, commit_id) + break + except Exception: + traceback.print_exc() + retry += 1 + return pr + + +def create_or_update_release(repo, tag, name, body, is_draft, is_prerelease, commit): + """ + Create or update a GitHub release. + + Args: + repo (github.Repository.Repository): The repository object. + tag (str): The tag for the release. + name (str): The name of the release. + body (str): The body description of the release. + is_draft (bool): Whether the release is a draft. + is_prerelease (bool): Whether the release is a pre-release. + commit (str): The commit SHA for the release. + + Returns: + github.GitRelease.GitRelease: The created or updated release object. + """ + try: + release = repo.get_release(tag) + release.update_release(name=name, message=body, draft=is_draft, + prerelease=is_prerelease, target_commitish=commit) + except UnknownObjectException: + release = repo.create_git_release( + tag, name, body, draft=is_draft, prerelease=is_prerelease, target_commitish=commit) + return release + + +def upload_asset(release, asset): + """ + Upload an asset to the GitHub release. + + Args: + release (github.GitRelease.GitRelease): The release object. + asset (str): The file path of the asset to upload. + """ + try: + release.upload_asset(asset) + print(f'Successfully uploaded asset: {asset}') + except Exception as e: + print(f'Failed to upload asset: {asset}, error: {e}') + + +def main(): + """ + Main script execution. + """ + try: + print(f'Running {" ".join(sys.argv)}') + + # Get the repository object + repo = get_github_repo() + release_tag = f'v{PROJECT_VERSION}' + commit_id = TRAVIS_COMMIT + + # Convert flags to boolean + is_draft_release = is_true(GITHUB_DRAFT_RELEASE) + is_prerelease = is_true(GITHUB_PRERELEASE) + + print(f'release_tag={release_tag}, commit_id={commit_id}') + + # Get the associated pull request + pr = get_pr(repo, TRAVIS_PULL_REQUEST, commit_id) + release_body = TRAVIS_COMMIT_MESSAGE + + # If a pull request is found, format the release body + if pr: + release_body = f'''## {pr.title}\n\n{ + pr.body}\n\n### Commit message:\n```\n{release_body}\n```\n''' + + # Create or update the release + release = create_or_update_release( + repo, release_tag, PROJECT_VERSION, release_body, is_draft_release, is_prerelease, commit_id) + + print(f'Release created/updated: {release_tag}') + + # Upload assets using multithreading for better performance + threads = [] + for asset in sys.argv[1:]: + print(f' - deploying asset: {asset}') + thread = threading.Thread( + target=upload_asset, args=(release, asset)) + threads.append(thread) + thread.start() + + for thread in threads: + thread.join() + + except Exception as e: + print(f'An error occurred: {e}') + traceback.print_exc() + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/scripts/hotspot.ps1 b/scripts/hotspot.ps1 new file mode 100644 index 00000000..120f2723 --- /dev/null +++ b/scripts/hotspot.ps1 @@ -0,0 +1,189 @@ +<# +使用说明 + +此脚本用于管理Windows上的Wi-Fi热点,包括启动、停止、查看状态和设置配置文件。以下是详细的使用说明: + +参数说明 +- Action: 指定要执行的操作。有效值包括 "Start", "Stop", "Status", "List", "Set"。这是一个必选参数。 +- Name: 热点的名称,默认为 "MyHotspot"。这是一个可选参数。 +- Password: 热点的密码,必须是一个SecureString。当启动或设置热点时,这是一个必需参数。 +- Authentication: 认证类型,有效值为 "WPA2PSK" 和 "WPA2",默认为 "WPA2PSK"。这是一个可选参数。 +- Encryption: 加密类型,有效值为 "AES" 和 "TKIP",默认为 "AES"。这是一个可选参数。 +- Channel: 无线信道,默认为 11。有效值范围为 1 到 11。 这是一个可选参数。 +- MaxClients: 最大连接客户端数量,默认为 10。 这是一个可选参数。 + +操作说明 + +启动热点 +启动热点并配置相关参数。 +powershell.exe -File "HotspotScript.ps1" -Action Start -Password (ConvertTo-SecureString -String 'YourSecurePasswordHere' -AsPlainText -Force) +注意:必须提供热点的密码。 + +停止热点 +停止当前运行的热点。 +powershell.exe -File "HotspotScript.ps1" -Action Stop + +查看热点状态 +查看当前热点的运行状态。 +powershell.exe -File "HotspotScript.ps1" -Action Status + +列出当前配置 +列出当前热点的所有配置信息。 +powershell.exe -File "HotspotScript.ps1" -Action List + +设置热点配置 +配置热点的详细信息。 +powershell.exe -File "HotspotScript.ps1" -Action Set -Password (ConvertTo-SecureString -String 'YourSecurePasswordHere' -AsPlainText -Force) +注意:必须提供热点的密码。 + +热点自动刷新 +为了确保热点的持续可用性,脚本会自动创建一个批处理文件并在后台运行。 + +创建批处理文件 +脚本会自动在桌面上创建一个名为 StartMobileHotspot.bat 的批处理文件。该文件包含启动热点的命令并自动刷新。 + +#> + +[CmdletBinding()] +Param( + [Parameter(Position = 0, Mandatory = $true)] + [ValidateSet("Start", "Stop", "Status", "List", "Set")] + [string]$Action, + + [Parameter(Position = 1, Mandatory = $false)] + [string]$Name = "MyHotspot", + + [Parameter(Position = 2, Mandatory = $false)] + [SecureString]$Password, + + [Parameter(Mandatory = $false)] + [ValidateSet("WPA2PSK", "WPA2")] + [string]$Authentication = "WPA2PSK", + + [Parameter(Mandatory = $false)] + [ValidateSet("AES", "TKIP")] + [string]$Encryption = "AES", + + [Parameter(Mandatory = $false)] + [int]$Channel = 11, + + [Parameter(Mandatory = $false)] + [int]$MaxClients = 10 +) + +# Define hotspot adapter name +$hotspotAdapterName = "Wi-Fi" + +function Start-Hotspot { + param ( + [string]$Name, + [string]$Password, + [string]$Authentication, + [string]$Encryption, + [int]$Channel, + [int]$MaxClients + ) + + netsh wlan set hostednetwork mode=allow ssid=$Name key=$Password keyUsage=persistent auth=$Authentication cipher=$Encryption channel=$Channel + netsh wlan start hostednetwork +} + +function Stop-Hotspot { + netsh wlan stop hostednetwork +} + +function Get-HotspotStatus { + netsh wlan show hostednetwork +} + +switch ($Action) { + "Start" { + if (-not $Password) { + Write-Error "Password is required when starting a hotspot" + exit 1 + } + + $plainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)) + + Start-Hotspot -Name $Name -Password $plainPassword -Authentication $Authentication -Encryption $Encryption -Channel $Channel -MaxClients $MaxClients + + Start-Sleep -Seconds 2 + $status = Get-HotspotStatus + if ($status -match "Status\s*:\s*Started") { + Write-Output "Hotspot $Name is now running with password $plainPassword" + } else { + Write-Error "Failed to start hotspot $Name" + exit 1 + } + } + + "Stop" { + Stop-Hotspot + + Start-Sleep -Seconds 2 + $status = Get-HotspotStatus + if ($status -match "Status\s*:\s*Not started") { + Write-Output "Hotspot has been stopped" + } else { + Write-Error "Failed to stop hotspot" + exit 1 + } + } + + "Status" { + $status = Get-HotspotStatus + if ($status -match "Status\s*:\s*Started") { + Write-Output "Hotspot is running" + Write-Output "Hotspot settings:" + Write-Output $status + } else { + Write-Output "Hotspot is not running" + } + } + + "List" { + Write-Output "Current hosted network configuration:" + $status = Get-HotspotStatus + Write-Output $status + } + + "Set" { + if (-not $Password) { + Write-Error "Password is required when setting a hotspot profile" + exit 1 + } + + $plainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)) + + netsh wlan set hostednetwork mode=allow ssid=$Name key=$plainPassword keyUsage=persistent auth=$Authentication cipher=$Encryption channel=$Channel + Write-Output "Hotspot profile '$Name' has been updated" + } +} + +# Script to auto-refresh and ensure hotspot availability +if ($MyInvocation.InvocationName -eq $PSCommandPath) { + start mshta vbscript:createobject("wscript.shell").run("""$PSCommandPath"" hide",0)(window.close)&&exit + goto :CmdBegin +} + +:CmdBegin + +$CurrentUserPath = [System.Environment]::GetFolderPath('Desktop') +$BatchFilePath = Join-Path $CurrentUserPath "StartMobileHotspot.bat" + +# Create or update the batch file +$batchContent = @" +@echo off +powershell.exe -File `"$PSCommandPath`" Start -Password (ConvertTo-SecureString -String 'YourSecurePasswordHere' -AsPlainText -Force) +choice /t 10 /d y /n >nul +goto autoRefreshWeb +:autoRefreshWeb +start "" `"$BatchFilePath`" +choice /t 10 /d y /n >nul +goto autoRefreshWeb +"@ + +Set-Content -Path $BatchFilePath -Value $batchContent + +# Start the batch file for auto-refresh +Start-Process -FilePath $BatchFilePath diff --git a/scripts/hotspot.sh b/scripts/hotspot.sh new file mode 100644 index 00000000..2b3072fc --- /dev/null +++ b/scripts/hotspot.sh @@ -0,0 +1,128 @@ +#!/bin/bash + +# hotspot.sh +# +# 用于在 Linux 上管理无线热点的 Shell 脚本 +# 使用 nmcli (NetworkManager 命令行接口) +# +# 使用方法: +# ./hotspot.sh {Start|Stop|Status|List|Set} [Name] [Password] [Authentication] [Encryption] [Channel] [MaxClients] +# +# 参数: +# - Action: 必需。操作类型。可选值为 Start、Stop、Status、List、Set。 +# - Name: 热点名称。默认值为 "MyHotspot"。 +# - Password: 热点的密码。在 Start 和 Set 操作中是必需的。 +# - Authentication: 认证类型。可选值为 wpa-psk 或 wpa2。默认值为 wpa-psk。 +# - Encryption: 加密类型。可选值为 aes 或 tkip。默认值为 aes。 +# - Channel: 热点使用的频道号。默认值为 11。 +# - MaxClients: 最大客户端数量。默认值为 10。 +# +# 使用示例: +# 启动热点: +# ./hotspot.sh Start MyHotspot MyPassword +# +# 停止热点: +# ./hotspot.sh Stop +# +# 查看热点状态: +# ./hotspot.sh Status +# +# 列出所有热点配置: +# ./hotspot.sh List +# +# 设置热点配置文件: +# ./hotspot.sh Set MyHotspot MyPassword wpa-psk aes 11 10 + +# 参数解析 +ACTION=$1 +NAME=${2:-MyHotspot} +PASSWORD=$3 +AUTHENTICATION=${4:-wpa-psk} +ENCRYPTION=${5:-aes} +CHANNEL=${6:-11} +MAX_CLIENTS=${7:-10} + +# 启动热点 +function start_hotspot { + if [ -z "$PASSWORD" ]; then + echo "Password is required when starting a hotspot" + exit 1 + fi + + # 创建热点连接 + nmcli dev wifi hotspot ifname wlan0 ssid "$NAME" password "$PASSWORD" + + # 设置其他参数 + nmcli connection modify Hotspot 802-11-wireless.security "$AUTHENTICATION" + nmcli connection modify Hotspot 802-11-wireless.band bg + nmcli connection modify Hotspot 802-11-wireless.channel "$CHANNEL" + nmcli connection modify Hotspot 802-11-wireless.cloned-mac-address stable + nmcli connection modify Hotspot 802-11-wireless.mac-address-randomization no + + echo "Hotspot $NAME is now running with password $PASSWORD" +} + +# 停止热点 +function stop_hotspot { + nmcli connection down Hotspot + echo "Hotspot has been stopped" +} + +# 查看热点状态 +function status_hotspot { + status=$(nmcli dev status | grep wlan0) + if echo "$status" | grep -q "connected"; then + echo "Hotspot is running" + nmcli connection show Hotspot + else + echo "Hotspot is not running" + fi +} + +# 列出所有热点配置 +function list_hotspot { + nmcli connection show --active | grep wifi +} + +# 设置热点配置文件 +function set_hotspot { + if [ -z "$PASSWORD" ]; then + echo "Password is required when setting a hotspot profile" + exit 1 + fi + + # 创建或修改热点连接 + nmcli connection modify Hotspot 802-11-wireless.ssid "$NAME" + nmcli connection modify Hotspot 802-11-wireless-security.key-mgmt "$AUTHENTICATION" + nmcli connection modify Hotspot 802-11-wireless-security.proto rsn + nmcli connection modify Hotspot 802-11-wireless-security.group "$ENCRYPTION" + nmcli connection modify Hotspot 802-11-wireless-security.pairwise "$ENCRYPTION" + nmcli connection modify Hotspot 802-11-wireless-security.psk "$PASSWORD" + nmcli connection modify Hotspot 802-11-wireless.band bg + nmcli connection modify Hotspot 802-11-wireless.channel "$CHANNEL" + nmcli connection modify Hotspot 802-11-wireless.mac-address-randomization no + + echo "Hotspot profile '$NAME' has been updated" +} + +# 主程序入口 +case $ACTION in + Start) + start_hotspot + ;; + Stop) + stop_hotspot + ;; + Status) + status_hotspot + ;; + List) + list_hotspot + ;; + Set) + set_hotspot + ;; + *) + echo "Usage: $0 {Start|Stop|Status|List|Set} [Name] [Password] [Authentication] [Encryption] [Channel] [MaxClients]" + ;; +esac diff --git a/scripts/indi_installer.sh b/scripts/indi_installer.sh index 5ed5151e..2655be45 100644 --- a/scripts/indi_installer.sh +++ b/scripts/indi_installer.sh @@ -19,7 +19,7 @@ apt-get install -y git cmake libcfitsio-dev libcfitsio8 zlib1g-dev libgsl-dev li # 克隆INDI库 echo -e "${BLUE}正在克隆INDI库...${NC}" -git clone https://github.com/indilib/indi.git +git clone https://github.com/indilib/indi # 进入INDI目录 cd indi diff --git a/scripts/install_msys2.ps1 b/scripts/install_msys2.ps1 index 47d4ef08..a9ba5d01 100644 --- a/scripts/install_msys2.ps1 +++ b/scripts/install_msys2.ps1 @@ -1,29 +1,63 @@ -# 设置MSYS2的下载URL和安装路径 -$msys2_url = "https://github.com/msys2/msys2-installer/releases/download/2022-06-03/msys2-x86_64-20220603.exe" -$msys2_installer = "msys2-x86_64-20220603.exe" -$install_dir = "C:\msys64" +param( + [Parameter(Mandatory=$false)] + [string]$InstallPath = "C:\msys64", + + [Parameter(Mandatory=$false)] + [string]$Msys2Url = "https://github.com/msys2/msys2-installer/releases/download/2022-06-03/msys2-x86_64-20220603.exe", + + [Parameter(Mandatory=$false)] + [string]$Mirror1 = "https://mirrors.tuna.tsinghua.edu.cn/msys2/mingw/x86_64/", + + [Parameter(Mandatory=$false)] + [string]$Mirror2 = "https://mirrors.tuna.tsinghua.edu.cn/msys2/mingw/i686/", + + [Parameter(Mandatory=$false)] + [string]$Mirror3 = "https://mirrors.tuna.tsinghua.edu.cn/msys2/msys/\$arch/", + + [Parameter(Mandatory=$false)] + [string[]]$Packages = @("mingw-w64-x86_64-toolchain", "mingw-w64-x86_64-dlfcn", "mingw-w64-x86_64-cfitsio", "mingw-w64-x86_64-cmake", "mingw-w64-x86_64-libzip", "mingw-w64-x86_64-zlib", "mingw-w64-x86_64-fmt", "mingw-w64-x86_64-libnova") +) + +# 设置MSYS2的安装路径和下载URL +$msys2_installer = [System.IO.Path]::GetFileName($Msys2Url) # 下载MSYS2安装程序 -Write-Output "Downloading MSYS2 installer..." -Invoke-WebRequest -Uri $msys2_url -OutFile $msys2_installer +Write-Output "Downloading MSYS2 installer from $Msys2Url..." +Invoke-WebRequest -Uri $Msys2Url -OutFile $msys2_installer # 安装MSYS2 -Write-Output "Installing MSYS2..." -Start-Process -FilePath .\$msys2_installer -ArgumentList "/S /D=$install_dir" -Wait +Write-Output "Installing MSYS2 to $InstallPath..." +Start-Process -FilePath .\$msys2_installer -ArgumentList "/S /D=$InstallPath" -Wait # 配置pacman -Write-Output "Configuring pacman..." -$pacman_conf = "C:\msys64\etc\pacman.conf" -Add-Content -Path $pacman_conf -Value 'Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/mingw/x86_64/' -Add-Content -Path $pacman_conf -Value 'Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/mingw/i686/' -Add-Content -Path $pacman_conf -Value 'Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/msys/$arch/' +Write-Output "Configuring pacman with custom mirrors..." +$pacman_conf = Join-Path -Path $InstallPath -ChildPath "etc\pacman.conf" +Add-Content -Path $pacman_conf -Value "Server = $Mirror1" +Add-Content -Path $pacman_conf -Value "Server = $Mirror2" +Add-Content -Path $pacman_conf -Value "Server = $Mirror3" # 更新系统包 Write-Output "Updating system packages..." -C:\msys64\usr\bin\bash.exe -lc "pacman -Syu --noconfirm" +& "$InstallPath\usr\bin\bash.exe" -lc "pacman -Syu --noconfirm" # 安装必要的开发工具和库 Write-Output "Installing development tools and libraries..." -C:\msys64\usr\bin\bash.exe -lc "pacman -S --noconfirm mingw-w64-x86_64-toolchain mingw-w64-x86_64-dlfcn mingw-w64-x86_64-cfitsio mingw-w64-x86_64-cmake mingw-w64-x86_64-libzip mingw-w64-x86_64-zlib mingw-w64-x86_64-fmt mingw-w64-x86_64-libnova" +$packageList = [string]::Join(" ", $Packages) +& "$InstallPath\usr\bin\bash.exe" -lc "pacman -S --noconfirm $packageList" + +# 添加 MSYS2 路径到系统环境变量 +Write-Output "Adding MSYS2 to system PATH..." +$envPath = [System.Environment]::GetEnvironmentVariable("Path", [System.EnvironmentVariableTarget]::Machine) +if (-not $envPath.Contains($InstallPath)) { + $newEnvPath = "$envPath;$InstallPath\usr\bin;$InstallPath\mingw64\bin;$InstallPath\mingw32\bin" + [System.Environment]::SetEnvironmentVariable("Path", $newEnvPath, [System.EnvironmentVariableTarget]::Machine) + Write-Output "MSYS2 paths added to system PATH." +} else { + Write-Output "MSYS2 paths already exist in system PATH." +} + +# 重新启动 MSYS2 +Write-Output "Restarting MSYS2..." +& "$InstallPath\usr\bin\bash.exe" -lc "exit" Write-Output "MSYS2 installation and setup completed." diff --git a/scripts/nginx.sh b/scripts/nginx.sh index 83781c1d..8e89d096 100644 --- a/scripts/nginx.sh +++ b/scripts/nginx.sh @@ -1,9 +1,37 @@ #!/bin/bash -# Define colors +# Nginx 管理脚本 +# 用途: 安装、启动、停止、重载、检查Nginx配置,并显示Nginx状态 +# +# 使用方法: +# ./nginx_manager.sh [start|stop|reload|check|status|help] +# +# 参数说明: +# start - 启动Nginx +# stop - 停止Nginx +# reload - 重载Nginx配置 +# check - 检查Nginx配置语法 +# status - 显示Nginx运行状态 +# help - 显示此帮助信息 +# +# 脚本功能: +# 1. 自动检测并安装Nginx(支持Debian和Red Hat系统) +# 2. 启动、停止和重载Nginx服务 +# 3. 检查Nginx配置文件的语法 +# 4. 显示Nginx的当前运行状态 +# +# 示例: +# ./nginx_manager.sh start +# ./nginx_manager.sh stop +# ./nginx_manager.sh reload +# ./nginx_manager.sh check +# ./nginx_manager.sh status +# ./nginx_manager.sh help + +# Define colors for output GREEN='\033[0;32m' RED='\033[0;31m' -NC='\033[0m' +NC='\033[0m' # No Color # Define Nginx paths NGINX_PATH="/etc/nginx" @@ -14,17 +42,25 @@ NGINX_BINARY="/usr/sbin/nginx" install_nginx() { if ! command -v nginx &>/dev/null; then echo "Installing Nginx..." - # Add installation commands according to the package manager used (apt, yum, etc.) - # Example for apt: - sudo apt-get update - sudo apt-get install nginx -y + if [ -f /etc/debian_version ]; then + # Debian-based system (e.g., Ubuntu) + sudo apt-get update + sudo apt-get install nginx -y + elif [ -f /etc/redhat-release ]; then + # Red Hat-based system (e.g., CentOS) + sudo yum update + sudo yum install nginx -y + else + echo -e "${RED}Unsupported platform. Please install Nginx manually.${NC}" + exit 1 + fi fi } # Function: Start Nginx start_nginx() { if [ -f "$NGINX_BINARY" ]; then - $NGINX_BINARY + sudo $NGINX_BINARY echo -e "${GREEN}Nginx has been started${NC}" else echo -e "${RED}Nginx binary not found${NC}" @@ -34,7 +70,7 @@ start_nginx() { # Function: Stop Nginx stop_nginx() { if [ -f "$NGINX_BINARY" ]; then - $NGINX_BINARY -s stop + sudo $NGINX_BINARY -s stop echo -e "${GREEN}Nginx has been stopped${NC}" else echo -e "${RED}Nginx binary not found${NC}" @@ -44,7 +80,7 @@ stop_nginx() { # Function: Reload Nginx configuration reload_nginx() { if [ -f "$NGINX_BINARY" ]; then - $NGINX_BINARY -s reload + sudo $NGINX_BINARY -s reload echo -e "${GREEN}Nginx configuration has been reloaded${NC}" else echo -e "${RED}Nginx binary not found${NC}" @@ -54,19 +90,29 @@ reload_nginx() { # Function: Check Nginx configuration syntax check_config() { if [ -f "$NGINX_CONF" ]; then - $NGINX_BINARY -t -c "$NGINX_CONF" + sudo $NGINX_BINARY -t -c "$NGINX_CONF" else echo -e "${RED}Nginx configuration file not found${NC}" fi } +# Function: Show Nginx status +status_nginx() { + if pgrep nginx &>/dev/null; then + echo -e "${GREEN}Nginx is running${NC}" + else + echo -e "${RED}Nginx is not running${NC}" + fi +} + # Function: Show help message show_help() { - echo "Usage: $0 [start|stop|reload|check|help]" + echo "Usage: $0 [start|stop|reload|check|status|help]" echo " start Start Nginx" echo " stop Stop Nginx" echo " reload Reload Nginx configuration" echo " check Check Nginx configuration syntax" + echo " status Show Nginx status" echo " help Show help message" } @@ -94,6 +140,9 @@ main() { check) check_config ;; + status) + status_nginx + ;; *) echo -e "${RED}Invalid command${NC}" show_help diff --git a/scripts/pip.ps1 b/scripts/pip.ps1 new file mode 100644 index 00000000..c40e53c8 --- /dev/null +++ b/scripts/pip.ps1 @@ -0,0 +1,96 @@ +[CmdletBinding()] +param ( + [string[]]$ExcludedPackages = @(), + [switch]$UpdateAll, + [switch]$GenerateReport +) + +function Update-PythonPackages { + [CmdletBinding()] + param ( + [string[]]$ExcludedPackages, + [switch]$UpdateAll + ) + + $outdatedPackages = if ($UpdateAll) { + pip list --format=json | ConvertFrom-Json | Select-Object -ExpandProperty name + } else { + pip list --outdated --format=json | ConvertFrom-Json | Select-Object -ExpandProperty name + } + + $updatedPackages = @() + $failedPackages = @() + + foreach ($package in $outdatedPackages) { + if ($ExcludedPackages -contains $package) { + Write-Verbose "Skipping excluded package: $package" + continue + } + + Write-Verbose "Updating package: $package" + try { + $output = pip install --upgrade $package 2>&1 + $updatedPackages += $package + Write-Verbose "Package updated successfully: $package" + } + catch { + Write-Warning "Failed to update package: $package" + $failedPackages += $package + } + } + + return @{ + Updated = $updatedPackages + Failed = $failedPackages + } +} + +function New-UpdateReport { + [CmdletBinding()] + param ( + [string[]]$ExcludedPackages, + [string[]]$UpdatedPackages, + [string[]]$FailedPackages + ) + + $reportContent = @" +Python Package Update Report +============================ + +Date: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss") + +Excluded Packages: +$($ExcludedPackages -join "`n") + +Updated Packages: +$($UpdatedPackages -join "`n") + +Failed Packages: +$($FailedPackages -join "`n") + +Summary: +-------- +Total packages processed: $($UpdatedPackages.Count + $FailedPackages.Count) +Successfully updated: $($UpdatedPackages.Count) +Failed to update: $($FailedPackages.Count) +"@ + + $reportFile = "PythonPackageUpdateReport_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt" + $reportContent | Out-File -FilePath $reportFile -Encoding UTF8 + Write-Host "Update report saved to: $reportFile" +} + +# Main execution +$updateResult = Update-PythonPackages -ExcludedPackages $ExcludedPackages -UpdateAll:$UpdateAll + +Write-Host "Update completed." +Write-Host "Total packages updated: $($updateResult.Updated.Count)" + +if ($updateResult.Failed.Count -gt 0) { + Write-Host "Failed to update the following packages:" + $updateResult.Failed | ForEach-Object { Write-Host "- $_" } +} + +if ($GenerateReport) { + New-UpdateReport -ExcludedPackages $ExcludedPackages -UpdatedPackages $updateResult.Updated -FailedPackages $updateResult.Failed +} diff --git a/scripts/pip.sh b/scripts/pip.sh new file mode 100644 index 00000000..dd905c45 --- /dev/null +++ b/scripts/pip.sh @@ -0,0 +1,111 @@ +#!/bin/bash + +# Function to update Python packages +update_python_packages() { + local excluded_packages=("$@") + local updated_packages=() + local failed_packages=() + + if [ "$UPDATE_ALL" = true ]; then + packages=$(pip list --format=json | jq -r '.[].name') + else + packages=$(pip list --outdated --format=json | jq -r '.[].name') + fi + + for package in $packages; do + if [[ " ${excluded_packages[@]} " =~ " ${package} " ]]; then + echo "Skipping excluded package: $package" + continue + fi + + echo "Updating package: $package" + if pip install --upgrade "$package" >/dev/null 2>&1; then + updated_packages+=("$package") + echo "Package updated successfully: $package" + else + failed_packages+=("$package") + echo "Failed to update package: $package" + fi + done + + echo "Updated packages: ${updated_packages[*]}" + echo "Failed packages: ${failed_packages[*]}" +} + +# Function to generate update report +generate_report() { + local excluded_packages=("$1") + local updated_packages=("$2") + local failed_packages=("$3") + + report_file="PythonPackageUpdateReport_$(date +%Y%m%d_%H%M%S).txt" + { + echo "Python Package Update Report" + echo "============================" + echo + echo "Date: $(date)" + echo + echo "Excluded Packages:" + printf '%s\n' "${excluded_packages[@]}" + echo + echo "Updated Packages:" + printf '%s\n' "${updated_packages[@]}" + echo + echo "Failed Packages:" + printf '%s\n' "${failed_packages[@]}" + echo + echo "Summary:" + echo "--------" + echo "Total packages processed: $((${#updated_packages[@]} + ${#failed_packages[@]}))" + echo "Successfully updated: ${#updated_packages[@]}" + echo "Failed to update: ${#failed_packages[@]}" + } > "$report_file" + + echo "Update report saved to: $report_file" +} + +# Main execution +EXCLUDED_PACKAGES=() +UPDATE_ALL=false +GENERATE_REPORT=false + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + --exclude) + shift + IFS=',' read -ra EXCLUDED_PACKAGES <<< "$1" + ;; + --update-all) + UPDATE_ALL=true + ;; + --generate-report) + GENERATE_REPORT=true + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac + shift +done + +# Run the update function +update_result=$(update_python_packages "${EXCLUDED_PACKAGES[@]}") + +# Extract updated and failed packages from the result +IFS=$'\n' read -rd '' -a lines <<< "$update_result" +updated_packages=($(echo "${lines[0]}" | cut -d ':' -f2)) +failed_packages=($(echo "${lines[1]}" | cut -d ':' -f2)) + +echo "Update completed." +echo "Total packages updated: ${#updated_packages[@]}" + +if [ ${#failed_packages[@]} -gt 0 ]; then + echo "Failed to update the following packages:" + printf '- %s\n' "${failed_packages[@]}" +fi + +if [ "$GENERATE_REPORT" = true ]; then + generate_report "${EXCLUDED_PACKAGES[*]}" "${updated_packages[*]}" "${failed_packages[*]}" +fi diff --git a/scripts/rename.ps1 b/scripts/rename.ps1 index 88451026..c7c6926c 100644 --- a/scripts/rename.ps1 +++ b/scripts/rename.ps1 @@ -1,12 +1,89 @@ +<# +详细的使用方法 + +此脚本用于在指定目录下对文件和目录执行多种操作,包括重命名、列出、复制、删除和移动。以下是详细的使用说明: + +参数说明: +- RootDirectory: 指定要操作的根目录路径(必需参数)。 +- OldElement: 在重命名操作中,要替换的旧元素字符串(必需参数)。 +- NewElement: 在重命名操作中,用于替换旧元素的新字符串(必需参数)。 +- Action: 要执行的操作类型,有效值包括 "Rename", "List", "Copy", "Delete", "Move"(必需参数)。 +- DestinationDirectory: 目标目录路径,仅在执行Copy和Move操作时必需。 + +操作说明: + +1. 重命名文件(Rename): + - 将指定目录及其子目录中的所有文件名中的旧元素字符串替换为新元素字符串。 + 示例: + ```powershell + ./YourScript.ps1 -RootDirectory "C:\YourDirectory" -OldElement "old" -NewElement "new" -Action "Rename" + ``` + +2. 列出文件(List): + - 列出指定目录及其子目录中的所有文件和目录。 + 示例: + ```powershell + ./YourScript.ps1 -RootDirectory "C:\YourDirectory" -Action "List" + ``` + +3. 复制文件(Copy): + - 递归复制指定目录及其子目录中的所有文件和目录到目标目录。 + 示例: + ```powershell + ./YourScript.ps1 -RootDirectory "C:\YourDirectory" -Action "Copy" -DestinationDirectory "D:\BackupDirectory" + ``` + +4. 删除文件(Delete): + - 递归删除指定目录及其子目录中的所有文件和目录。 + 示例: + ```powershell + ./YourScript.ps1 -RootDirectory "C:\YourDirectory" -Action "Delete" + ``` + +5. 移动文件(Move): + - 递归移动指定目录及其子目录中的所有文件和目录到目标目录。 + 示例: + ```powershell + ./YourScript.ps1 -RootDirectory "C:\YourDirectory" -Action "Move" -DestinationDirectory "D:\NewLocation" + ``` + +注意事项: +- 在执行Copy和Move操作时,必须提供DestinationDirectory参数。 +- 在执行重命名操作时,确保OldElement和NewElement参数正确,以免误操作。 + +示例命令: +```powershell +# 重命名操作 +./YourScript.ps1 -RootDirectory "C:\YourDirectory" -OldElement "old" -NewElement "new" -Action "Rename" + +# 列出文件 +./YourScript.ps1 -RootDirectory "C:\YourDirectory" -Action "List" + +# 复制文件 +./YourScript.ps1 -RootDirectory "C:\YourDirectory" -Action "Copy" -DestinationDirectory "D:\BackupDirectory" + +# 删除文件 +./YourScript.ps1 -RootDirectory "C:\YourDirectory" -Action "Delete" + +# 移动文件 +./YourScript.ps1 -RootDirectory "C:\YourDirectory" -Action "Move" -DestinationDirectory "D:\NewLocation" +``` +#> + param( [Parameter(Mandatory=$true)] - [string]$RootDirectory, + [string]$RootDirectory, # 根目录,必需参数 + [Parameter(Mandatory=$true)] + [string]$OldElement, # 要替换的旧元素字符串,必需参数 [Parameter(Mandatory=$true)] - [string]$OldElement, + [string]$NewElement, # 新元素字符串,必需参数 [Parameter(Mandatory=$true)] - [string]$NewElement + [ValidateSet("Rename", "List", "Copy", "Delete", "Move")] + [string]$Action, # 操作类型(Rename, List, Copy, Delete, Move),必需参数 + [string]$DestinationDirectory # 目标目录,Copy和Move操作时必需 ) +# 函数:重命名文件 function Rename-Files($Path) { $files = Get-ChildItem -Path $Path -File @@ -22,4 +99,53 @@ function Rename-Files($Path) { } } -Rename-Files -Path $RootDirectory +# 函数:列出文件 +function List-Files($Path) { + Get-ChildItem -Path $Path -Recurse +} + +# 函数:复制文件 +function Copy-Files($SourcePath, $DestinationPath) { + Copy-Item -Path $SourcePath -Destination $DestinationPath -Recurse -Force +} + +# 函数:删除文件 +function Delete-Files($Path) { + Remove-Item -Path $Path -Recurse -Force +} + +# 函数:移动文件 +function Move-Files($SourcePath, $DestinationPath) { + Move-Item -Path $SourcePath -Destination $DestinationPath -Recurse -Force +} + +# 主逻辑,根据Action参数执行相应操作 +switch ($Action) { + "Rename" { + Rename-Files -Path $RootDirectory + } + "List" { + List-Files -Path $RootDirectory + } + "Copy" { + if (-not $DestinationDirectory) { + Write-Error "DestinationDirectory is required for Copy action" + exit 1 + } + Copy-Files -SourcePath $RootDirectory -DestinationPath $DestinationDirectory + } + "Delete" { + Delete-Files -Path $RootDirectory + } + "Move" { + if (-not $DestinationDirectory) { + Write-Error "DestinationDirectory is required for Move action" + exit 1 + } + Move-Files -SourcePath $RootDirectory -DestinationPath $DestinationDirectory + } + default { + Write-Error "Invalid action specified" + exit 1 + } +} diff --git a/scripts/rename.sh b/scripts/rename.sh index 11b65e5a..f447227c 100644 --- a/scripts/rename.sh +++ b/scripts/rename.sh @@ -1,21 +1,116 @@ #!/bin/bash -root_directory=$1 -old_element=$2 -new_element=$3 - -rename_files() { - local path=$1 - - for file in "$path"/*; do - if [[ -f $file ]]; then - new_file_name="${file/$old_element/$new_element}" - new_file_path="$(dirname "$file")/$new_file_name" - mv -n "$file" "$new_file_path" - elif [[ -d $file ]]; then - rename_files "$file" - fi - done +# rename.sh +# +# 用于在 Linux 上管理文件和目录的 Shell 脚本 +# +# 使用方法: +# ./rename.sh {rename|list|copy|delete|move} [] +# +# 参数: +# - Action: 必需。操作类型。可选值为 rename、list、copy、delete、move。 +# - RootDirectory: 必需。操作的根目录。 +# - OldElement: 重命名操作中要被替换的字符串。 +# - NewElement: 重命名操作中替换后的新字符串。 +# - DestinationDirectory: 复制和移动操作的目标目录。 +# +# 使用示例: +# 重命名文件: +# ./rename.sh rename /path/to/directory old new +# +# 列出文件和目录: +# ./rename.sh list /path/to/directory +# +# 复制文件和目录: +# ./rename.sh copy /path/to/source old new /path/to/destination +# +# 删除文件和目录: +# ./rename.sh delete /path/to/directory +# +# 移动文件和目录: +# ./rename.sh move /path/to/source old new /path/to/destination + +# 参数解析 +ACTION=$1 +ROOT_DIRECTORY=$2 +OLD_ELEMENT=$3 +NEW_ELEMENT=$4 +DESTINATION_DIRECTORY=$5 + +# 检查必需参数 +if [ -z "$ACTION" ] || [ -z "$ROOT_DIRECTORY" ]; then + echo "Usage: $0 {rename|list|copy|delete|move} []" + exit 1 +fi + +# 重命名文件 +function rename_files { + local path=$1 + for file in $(find "$path" -type f); do + local dir=$(dirname "$file") + local filename=$(basename "$file") + local new_filename=$(echo "$filename" | sed "s/$OLD_ELEMENT/$NEW_ELEMENT/g") + local new_filepath="$dir/$new_filename" + mv "$file" "$new_filepath" 2>/dev/null + done + + for dir in $(find "$path" -type d); do + rename_files "$dir" + done +} + +# 列出文件和目录 +function list_files { + find "$1" -print +} + +# 复制文件和目录 +function copy_files { + cp -R "$1" "$2" +} + +# 删除文件和目录 +function delete_files { + rm -rf "$1" +} + +# 移动文件和目录 +function move_files { + mv "$1" "$2" } -rename_files "$root_directory" +# 主程序入口 +case $ACTION in + rename) + if [ -z "$OLD_ELEMENT" ] || [ -z "$NEW_ELEMENT" ]; then + echo "OldElement and NewElement are required for rename action" + exit 1 + fi + rename_files "$ROOT_DIRECTORY" + ;; + list) + list_files "$ROOT_DIRECTORY" + ;; + copy) + if [ -z "$DESTINATION_DIRECTORY" ]; then + echo "DestinationDirectory is required for copy action" + exit 1 + fi + copy_files "$ROOT_DIRECTORY" "$DESTINATION_DIRECTORY" + ;; + delete) + delete_files "$ROOT_DIRECTORY" + ;; + move) + if [ -z "$DESTINATION_DIRECTORY" ]; then + echo "DestinationDirectory is required for move action" + exit 1 + fi + move_files "$ROOT_DIRECTORY" "$DESTINATION_DIRECTORY" + ;; + *) + echo "Invalid action specified" + echo "Usage: $0 {rename|list|copy|delete|move} []" + exit 1 + ;; +esac diff --git a/scripts/rogue.ps1 b/scripts/rogue.ps1 new file mode 100644 index 00000000..d11b5d36 --- /dev/null +++ b/scripts/rogue.ps1 @@ -0,0 +1,199 @@ +# Function to lock the Administrator account +function Lock-AdministratorAccount { + Write-Host "Locking the Administrator account..." + Disable-LocalUser -Name "Administrator" + if ($?) { + Write-Host "Administrator account locked successfully." + } else { + Write-Host "Failed to lock Administrator account." + } +} + +# Function to unlock the Administrator account +function Unlock-AdministratorAccount { + Write-Host "Unlocking the Administrator account..." + Enable-LocalUser -Name "Administrator" + if ($?) { + Write-Host "Administrator account unlocked successfully." + } else { + Write-Host "Failed to unlock Administrator account." + } +} + +# Function to lock a specific drive (set it to read-only) +function Lock-Drive { + param ( + [string]$DriveLetter + ) + Write-Host "Locking the drive $DriveLetter..." + $disk = Get-WmiObject -Query "Select * from Win32_LogicalDisk Where DeviceID='$DriveLetter:'" + if ($disk) { + $disk.VolumeName = "Read-Only" + $disk.Put() + Write-Host "Drive $DriveLetter locked successfully." + } else { + Write-Host "Failed to lock drive $DriveLetter." + } +} + +# Function to unlock a specific drive (set it to read-write) +function Unlock-Drive { + param ( + [string]$DriveLetter + ) + Write-Host "Unlocking the drive $DriveLetter..." + $disk = Get-WmiObject -Query "Select * from Win32_LogicalDisk Where DeviceID='$DriveLetter:'" + if ($disk) { + $disk.VolumeName = "Read-Write" + $disk.Put() + Write-Host "Drive $DriveLetter unlocked successfully." + } else { + Write-Host "Failed to unlock drive $DriveLetter." + } +} + +# Function to setup firewall rules (e.g., block all incoming traffic) +function Setup-Firewall { + Write-Host "Setting up firewall to block all incoming traffic..." + New-NetFirewallRule -DisplayName "Block All Inbound" -Direction Inbound -Action Block + Write-Host "Firewall setup complete." +} + +# Function to disable USB ports (blocking new USB devices) +function Disable-USB { + Write-Host "Disabling USB ports..." + Get-PnpDevice | Where-Object { $_.Class -eq "USB" } | Disable-PnpDevice -Confirm:$false + if ($?) { + Write-Host "USB ports disabled successfully." + } else { + Write-Host "Failed to disable USB ports." + } +} + +# Function to enable USB ports (allowing new USB devices) +function Enable-USB { + Write-Host "Enabling USB ports..." + Get-PnpDevice | Where-Object { $_.Class -eq "USB" } | Enable-PnpDevice -Confirm:$false + if ($?) { + Write-Host "USB ports enabled successfully." + } else { + Write-Host "Failed to enable USB ports." + } +} + +# Function to close specific ports +function Close-Ports { + param ( + [string]$Ports + ) + Write-Host "Closing ports: $Ports..." + $portsArray = $Ports.Split(',') + foreach ($port in $portsArray) { + New-NetFirewallRule -DisplayName "Block Port $port" -Direction Inbound -LocalPort $port -Protocol TCP -Action Block + Write-Host "Port $port closed." + } +} + +# Function to setup RDP protection (similar to SSH protection) +function Setup-RDPProtection { + Write-Host "Setting up RDP protection..." + + # Allow RDP access only from specific IPs + $allowedIPs = "192.168.1.100" + Write-Host "Allowing RDP access only from: $allowedIPs" + Set-NetFirewallRule -DisplayName "Remote Desktop - User Mode (TCP-In)" -RemoteAddress $allowedIPs + + # Change RDP port (optional) + $newRDPPort = 2222 + Write-Host "Changing RDP port to $newRDPPort" + Set-ItemProperty -Path "HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" -Name "PortNumber" -Value $newRDPPort + + # Restart RDP service to apply changes + Restart-Service -Name "TermService" + Write-Host "RDP protection setup complete." +} + +# Function to monitor event logs for suspicious activity +function Monitor-EventLogs { + Write-Host "Monitoring Security event log for suspicious activity..." + Get-WinEvent -LogName "Security" -FilterXPath "*[System[(EventID=4625)]]" | Select-Object -First 10 | Format-List +} + +# Function to check system integrity using SFC and DISM +function Check-SystemIntegrity { + Write-Host "Checking system integrity..." + sfc /scannow + dism /online /cleanup-image /checkhealth + dism /online /cleanup-image /scanhealth + dism /online /cleanup-image /restorehealth +} + +# Function to setup automatic logout +function Setup-AutoLogout { + Write-Host "Setting up automatic logout for idle users..." + $timeout = 300 + Write-Host "Setting timeout to $timeout seconds..." + New-ItemProperty -Path 'HKCU:\Control Panel\Desktop' -Name ScreenSaveTimeOut -Value $timeout -PropertyType String -Force + New-ItemProperty -Path 'HKCU:\Control Panel\Desktop' -Name ScreenSaverIsSecure -Value 1 -PropertyType String -Force + Write-Host "Automatic logout setup complete." +} + +# Function to verify security settings +function Verify-Security { + Write-Host "Verifying security settings..." + + # Check if Administrator account is locked + $adminStatus = Get-LocalUser -Name "Administrator" | Select-Object -ExpandProperty Enabled + if (-not $adminStatus) { + Write-Host "Administrator account is locked." + } else { + Write-Host "Administrator account is not locked." + } + + # Check if firewall is configured + $firewallRules = Get-NetFirewallRule -DisplayName "Block All Inbound" + if ($firewallRules) { + Write-Host "Firewall is configured to block incoming traffic." + } else { + Write-Host "Firewall is not blocking incoming traffic." + } + + # Check if USB ports are disabled + $usbStatus = Get-PnpDevice | Where-Object { $_.Class -eq "USB" } | Select-Object -ExpandProperty Status + if ($usbStatus -contains "Disabled") { + Write-Host "USB ports are disabled." + } else { + Write-Host "USB ports are not disabled." + } + + # Check if RDP port is changed + $rdpPort = Get-ItemProperty -Path "HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" -Name "PortNumber" | Select-Object -ExpandProperty PortNumber + if ($rdpPort -eq 2222) { + Write-Host "RDP is running on custom port $rdpPort." + } else { + Write-Host "RDP is running on default port." + } +} + +# Main script +param ( + [string]$Action, + [string]$Parameter +) + +switch ($Action) { + "lock-admin" { Lock-AdministratorAccount } + "unlock-admin" { Unlock-AdministratorAccount } + "lock-drive" { Lock-Drive -DriveLetter $Parameter } + "unlock-drive" { Unlock-Drive -DriveLetter $Parameter } + "setup-firewall" { Setup-Firewall } + "close-ports" { Close-Ports -Ports $Parameter } + "disable-usb" { Disable-USB } + "enable-usb" { Enable-USB } + "setup-rdp-protection" { Setup-RDPProtection } + "monitor-logs" { Monitor-EventLogs } + "check-integrity" { Check-SystemIntegrity } + "setup-auto-logout" { Setup-AutoLogout } + "verify-security" { Verify-Security } + default { Write-Host "Usage: script.ps1 -Action {lock-admin|unlock-admin|lock-drive|unlock-drive|setup-firewall|close-ports|disable-usb|enable-usb|setup-rdp-protection|monitor-logs|check-integrity|setup-auto-logout|verify-security} -Parameter {optional}" } +} diff --git a/scripts/rogue.sh b/scripts/rogue.sh new file mode 100644 index 00000000..8c3d9024 --- /dev/null +++ b/scripts/rogue.sh @@ -0,0 +1,249 @@ +#!/bin/bash + +# Function to lock the root account +lock_root() { + echo "Locking the root account..." + passwd -l root + if [[ $? -eq 0 ]]; then + echo "Root account locked successfully." + else + echo "Failed to lock root account." + fi +} + +# Function to unlock the root account +unlock_root() { + echo "Unlocking the root account..." + passwd -u root + if [[ $? -eq 0 ]]; then + echo "Root account unlocked successfully." + else + echo "Failed to unlock root account." + fi +} + +# Function to lock a specific device (e.g., /dev/sda) +lock_device() { + DEVICE=$1 + if [ -z "$DEVICE" ]; then + echo "No device specified. Exiting." + exit 1 + fi + + echo "Locking the device $DEVICE..." + blockdev --setro $DEVICE + if [[ $? -eq 0 ]]; then + echo "Device $DEVICE locked successfully." + else + echo "Failed to lock device $DEVICE." + fi +} + +# Function to unlock a specific device +unlock_device() { + DEVICE=$1 + if [ -z "$DEVICE" ]; then + echo "No device specified. Exiting." + exit 1 + fi + + echo "Unlocking the device $DEVICE..." + blockdev --setrw $DEVICE + if [[ $? -eq 0 ]]; then + echo "Device $DEVICE unlocked successfully." + else + echo "Failed to unlock device $DEVICE." + fi +} + +# Function to set up a firewall rule (e.g., block all incoming traffic) +setup_firewall() { + echo "Setting up firewall to block all incoming traffic..." + iptables -P INPUT DROP + iptables -P FORWARD DROP + iptables -P OUTPUT ACCEPT + echo "Firewall setup complete." +} + +# Function to disable USB ports (blocking new USB devices) +disable_usb() { + echo "Disabling USB ports..." + echo "1" > /sys/bus/usb/devices/usb*/authorized + if [[ $? -eq 0 ]]; then + echo "USB ports disabled successfully." + else + echo "Failed to disable USB ports." + fi +} + +# Function to enable USB ports (allowing new USB devices) +enable_usb() { + echo "Enabling USB ports..." + echo "0" > /sys/bus/usb/devices/usb*/authorized + if [[ $? -eq 0 ]]; then + echo "USB ports enabled successfully." + else + echo "Failed to enable USB ports." + fi +} + +# Function to close specific ports +close_ports() { + PORTS=$1 + if [ -z "$PORTS" ]; then + echo "No ports specified. Exiting." + exit 1 + fi + + echo "Closing ports: $PORTS..." + for PORT in $(echo $PORTS | tr "," "\n"); do + iptables -A INPUT -p tcp --dport $PORT -j REJECT + echo "Port $PORT closed." + done +} + +# Function to setup SSH protection +setup_ssh_protection() { + SSH_CONFIG="/etc/ssh/sshd_config" + echo "Setting up SSH protection..." + + # Restrict SSH access to specific IPs (edit this list) + ALLOWED_IPS="192.168.1.100" + echo "Allowing SSH access only from: $ALLOWED_IPS" + echo "AllowUsers root@$ALLOWED_IPS" >> $SSH_CONFIG + + # Change SSH port (optional) + NEW_SSH_PORT=2222 + echo "Changing SSH port to $NEW_SSH_PORT" + sed -i "s/#Port 22/Port $NEW_SSH_PORT/" $SSH_CONFIG + + # Restart SSH service to apply changes + systemctl restart sshd + echo "SSH protection setup complete." +} + +# Function to monitor logs for suspicious activity +monitor_logs() { + LOG_FILE="/var/log/auth.log" + echo "Monitoring $LOG_FILE for suspicious activity..." + tail -f $LOG_FILE | grep --line-buffered "Failed password\|error" +} + +# Function to check system integrity using AIDE or Tripwire +check_integrity() { + echo "Checking system integrity..." + if command -v aide >/dev/null 2>&1; then + aide --check + elif command -v tripwire >/dev/null 2>&1; then + tripwire --check + else + echo "No integrity checking tool installed. Please install AIDE or Tripwire." + fi +} + +# Function to set up automatic logout +setup_auto_logout() { + echo "Setting up automatic logout for idle users..." + TMOUT=300 + echo "export TMOUT=$TMOUT" >> /etc/profile + echo "Automatic logout set for $TMOUT seconds of inactivity." +} + +# Function to verify system security settings +verify_security() { + echo "Verifying security settings..." + + # Verify root account lock + ROOT_LOCKED=$(passwd -S root | awk '{print $2}') + if [ "$ROOT_LOCKED" == "L" ]; then + echo "Root account is locked." + else + echo "Root account is not locked." + fi + + # Verify device lock + DEVICE=$1 + RO_STATUS=$(blockdev --getro $DEVICE) + if [ "$RO_STATUS" -eq 1 ]; then + echo "Device $DEVICE is read-only." + else + echo "Device $DEVICE is not read-only." + fi + + # Verify firewall setup + FW_POLICY=$(iptables -L INPUT -v -n | grep "policy DROP") + if [ ! -z "$FW_POLICY" ]; then + echo "Firewall is configured to block incoming traffic." + else + echo "Firewall is not blocking incoming traffic." + fi + + # Verify USB port status + USB_STATUS=$(cat /sys/bus/usb/devices/usb*/authorized) + if [ "$USB_STATUS" -eq 1 ]; then + echo "USB ports are disabled." + else + echo "USB ports are not disabled." + fi + + # Verify SSH protection + SSH_PORT=$(grep "^Port " /etc/ssh/sshd_config | awk '{print $2}') + if [ "$SSH_PORT" -eq 2222 ]; then + echo "SSH is running on custom port $SSH_PORT." + else + echo "SSH is running on default port 22." + fi +} + +# Main script +if [ "$EUID" -ne 0 ]; then + echo "Please run as root." + exit 1 +fi + +# Command line options +case $1 in + lock-root) + lock_root + ;; + unlock-root) + unlock_root + ;; + lock-device) + lock_device $2 + ;; + unlock-device) + unlock_device $2 + ;; + setup-firewall) + setup_firewall + ;; + close-ports) + close_ports $2 + ;; + disable-usb) + disable_usb + ;; + enable-usb) + enable_usb + ;; + setup-ssh-protection) + setup_ssh_protection + ;; + monitor-logs) + monitor_logs + ;; + check-integrity) + check_integrity + ;; + setup-auto-logout) + setup_auto_logout + ;; + verify-security) + verify_security $2 + ;; + *) + echo "Usage: $0 {lock-root|unlock-root|lock-device|unlock-device|setup-firewall|close-ports|disable-usb|enable-usb|setup-ssh-protection|monitor-logs|check-integrity|setup-auto-logout|verify-security} [device|ports]" + exit 1 + ;; +esac diff --git a/scripts/start-hotspot.ps1 b/scripts/start-hotspot.ps1 deleted file mode 100644 index 828d2c8c..00000000 --- a/scripts/start-hotspot.ps1 +++ /dev/null @@ -1,32 +0,0 @@ -[CmdletBinding()] -Param( - [Parameter(Position = 0, Mandatory = $true)] - [string]$Name, - - [Parameter(Position = 1, Mandatory = $true)] - [SecureString]$Password -) - -# 将SecureString类型的密码转换为明文字符串 -$plainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)) - -# 安装NetworkManager模块(如果未安装) -if (-not (Get-Module -ListAvailable NetworkManager)) { - Install-Module -Name NetworkManager -Scope CurrentUser -Force -} - -# 开启热点 -$hotspotSetting = @{ - Name = $Name - Passphrase = $plainPassword - InternetSharingEnabled = $true -} -New-NetHotspot @hotspotSetting - -# 验证热点是否已经开启 -$hotspot = Get-NetAdapter | Where-Object { $_.Name -eq "vEthernet (WLAN)" } -if ($hotspot.Status -eq "Up") { - Write-Output "Hotspot $Name is now running with password $plainPassword" -} else { - Write-Output "Failed to start hotspot $Name" -} diff --git a/scripts/start-hotspot.sh b/scripts/start-hotspot.sh deleted file mode 100644 index bbb50ddb..00000000 --- a/scripts/start-hotspot.sh +++ /dev/null @@ -1,109 +0,0 @@ -#!/bin/bash - -# Set default values -SSID="LithiumServer" -PASSWORD="lithiumserver" -PACKAGE_MANAGER="" - -# Function to detect package manager -detect_package_manager() { - if command -v apt-get &>/dev/null; then - PACKAGE_MANAGER="apt" - elif command -v yum &>/dev/null; then - PACKAGE_MANAGER="yum" - elif command -v dnf &>/dev/null; then - PACKAGE_MANAGER="dnf" - else - echo "Unsupported package manager. Please install 'hostapd' and 'dnsmasq' manually." - exit 1 - fi -} - -# Function to install packages -install_packages() { - case $PACKAGE_MANAGER in - apt) - sudo apt-get update - sudo apt-get install hostapd dnsmasq -y - ;; - yum|dnf) - sudo $PACKAGE_MANAGER install hostapd dnsmasq -y - ;; - *) - echo "Unsupported package manager." - exit 1 - ;; - esac -} - -# Function to start services -start_services() { - sudo systemctl start dnsmasq - sudo systemctl start hostapd -} - -# Function to check if services are running -check_services() { - if ! systemctl is-active --quiet dnsmasq || ! systemctl is-active --quiet hostapd; then - echo "Failed to start the hotspot." - exit 1 - fi -} - -# Main script - -# Process arguments -while [[ $# -gt 0 ]]; do - key="$1" - - case $key in - -s|--ssid) - SSID="$2" - shift 2 - ;; - -p|--password) - PASSWORD="$2" - shift 2 - ;; - *) - echo "Unknown option: $1" - exit 1 - ;; - esac -done - -# Detect package manager -detect_package_manager - -# Check if hostapd and dnsmasq are installed -if ! dpkg -s hostapd dnsmasq > /dev/null 2>&1; then - echo "Installing hostapd and dnsmasq..." - install_packages -fi - -# Configure dnsmasq -echo "interface=wlan0 -dhcp-range=192.168.4.2,192.168.4.20,255.255.255.0,24h" | sudo tee /etc/dnsmasq.conf > /dev/null - -# Configure hostapd -echo "interface=wlan0 -driver=nl80211 -ssid=$SSID -hw_mode=g -channel=6 -macaddr_acl=0 -auth_algs=1 -ignore_broadcast_ssid=0 -wpa=2 -wpa_passphrase=$PASSWORD -wpa_key_mgmt=WPA-PSK -wpa_pairwise=TKIP -rsn_pairwise=CCMP" | sudo tee /etc/hostapd/hostapd.conf > /dev/null - -# Start dnsmasq and hostapd -start_services - -# Check if the hotspot is started -check_services - -echo "Hotspot $SSID has been started with password $PASSWORD" diff --git a/scripts/stop-hotspot.ps1 b/scripts/stop-hotspot.ps1 deleted file mode 100644 index ae11d605..00000000 --- a/scripts/stop-hotspot.ps1 +++ /dev/null @@ -1,10 +0,0 @@ -# 停止热点 -Stop-NetAdapter -Name "vEthernet (WLAN)" - -# 验证热点是否已经停止 -$hotspot = Get-NetAdapter | Where-Object { $_.Name -eq "vEthernet (WLAN)" } -if ($hotspot.Status -ne "Up") { - Write-Output "Hotspot has been stopped" -} else { - Write-Output "Failed to stop hotspot" -} diff --git a/scripts/stop-hotspot.sh b/scripts/stop-hotspot.sh deleted file mode 100644 index 13ead45f..00000000 --- a/scripts/stop-hotspot.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -# Stop dnsmasq and hostapd -sudo systemctl stop dnsmasq -sudo systemctl stop hostapd - -# Check if the hotspot is stopped -ip addr show wlan0 | grep inet | awk '{print $2}' | cut -d/ -f1 | if grep -qE '^(192\.168\.4\.[0-9]{1,3})$'; then - echo "Failed to stop the hotspot" -else - echo "Hotspot has been stopped" -fi diff --git a/src/App.cpp b/src/App.cpp index 08dcd2f1..4a82fffe 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -12,22 +12,29 @@ Description: Main Entry **************************************************/ -#include "lithiumapp.hpp" +#include "LithiumApp.hpp" #include "preload.hpp" -#include "atom/log/loguru.hpp" #include "atom/function/global_ptr.hpp" +#include "atom/log/loguru.hpp" #include "atom/system/crash.hpp" #include "atom/web/utils.hpp" // TODO: This is for debug only, please remove it in production +#if !IN_PRODUCTION #define ENABLE_TERMINAL 1 +#endif #if ENABLE_TERMINAL #include "debug/terminal.hpp" using namespace lithium::debug; #endif +// In release mode, we will disable the debugger +#if IN_PRODUCTION +#include "atom/system/nodebugger.hpp" +#endif + #include "server/App.hpp" #include @@ -39,7 +46,9 @@ using namespace lithium::debug; #include #endif -#include "argparse/argparse.hpp" +#include "atom/utils/argsview.hpp" + +using namespace std::literals; /** * @brief setup log file @@ -51,10 +60,10 @@ void setupLogFile() { std::filesystem::create_directory(logsFolder); } auto now = std::chrono::system_clock::now(); - auto now_time_t = std::chrono::system_clock::to_time_t(now); - std::tm *local_time = std::localtime(&now_time_t); + auto nowTimeT = std::chrono::system_clock::to_time_t(now); + std::tm *localTime = std::localtime(&nowTimeT); char filename[100]; - std::strftime(filename, sizeof(filename), "%Y%m%d_%H%M%S.log", local_time); + std::strftime(filename, sizeof(filename), "%Y%m%d_%H%M%S.log", localTime); std::filesystem::path logFilePath = logsFolder / filename; loguru::add_file(logFilePath.string().c_str(), loguru::Append, loguru::Verbosity_MAX); @@ -71,7 +80,10 @@ void setupLogFile() { * @param argv arguments * @return 0 on success */ -int main(int argc, char *argv[]) { +auto main(int argc, char *argv[]) -> int { +#if ENABLE_CPPTRACE + cpptrace::init(); +#endif // NOTE: gettext is not supported yet, it will cause compilation error on // Mingw64 /* Add gettext */ @@ -88,45 +100,44 @@ int main(int argc, char *argv[]) { loguru::init(argc, argv); /* Parse arguments */ - argparse::ArgumentParser program("Lithium Server"); + atom::utils::ArgumentParser program("Lithium Server"s); // NOTE: The command arguments' priority is higher than the config file - program.add_argument("-P", "--port") - .help("port the server running on") - .default_value(8000); - program.add_argument("-H", "--host") - .help("host the server running on") - .default_value("0.0.0.0"); - program.add_argument("-C", "--config") - .help("path to the config file") - .default_value("config.json"); - program.add_argument("-M", "--module-path") - .help("path to the modules directory") - .default_value("./modules"); - program.add_argument("-W", "--web-panel") - .help("web panel") - .default_value(true); - program.add_argument("-D", "--debug") - .help("debug mode") - .default_value(false); - program.add_argument("-L", "--log-file").help("path to log file"); - - program.add_description("Lithium Command Line Interface:"); - program.add_epilog("End."); - - program.parse_args(argc, argv); - - lithium::InitLithiumApp(argc, argv); + program.addArgument("port", atom::utils::ArgumentParser::ArgType::INTEGER, + false, 8000, "Port of the server", {"p"}); + program.addArgument("host", atom::utils::ArgumentParser::ArgType::STRING, + false, "0.0.0.0"s, "Host of the server", {"h"}); + program.addArgument("config", atom::utils::ArgumentParser::ArgType::STRING, + false, "config.json"s, "Path to the config file", + {"c"}); + program.addArgument("module-path", + atom::utils::ArgumentParser::ArgType::STRING, false, + "modules"s, "Path to the modules directory", {"m"}); + program.addArgument("web-panel", + atom::utils::ArgumentParser::ArgType::BOOLEAN, false, + true, "Enable web panel", {"w"}); + program.addArgument("debug", atom::utils::ArgumentParser::ArgType::BOOLEAN, + false, false, "Enable debug mode", {"d"}); + program.addArgument("log-file", + atom::utils::ArgumentParser::ArgType::STRING, false, + ""s, "Path to the log file", {"l"}); + + program.addDescription("Lithium Command Line Interface:"); + program.addEpilog("End."); + + program.parse(argc, argv); + + lithium::initLithiumApp(argc, argv); // Create shared instance - lithium::MyApp = lithium::LithiumApp::createShared(); + lithium::myApp = lithium::LithiumApp::createShared(); // Parse arguments try { - auto cmd_host = program.get("--host"); - auto cmd_port = program.get("--port"); - auto cmd_config_path = program.get("--config"); - auto cmd_module_path = program.get("--module-path"); - auto cmd_web_panel = program.get("--web-panel"); - auto cmd_debug = program.get("--debug"); + auto cmdHost = program.get("host"); + auto cmdPort = program.get("port"); + auto cmdConfigPath = program.get("config"); + auto cmdModulePath = program.get("module-path"); + auto cmdWebPanel = program.get("web-panel"); + auto cmdDebug = program.get("debug"); // TODO: We need a new way to handle command line arguments. // Maybe we will generate a json object or a map and then given to the @@ -186,8 +197,9 @@ int main(int argc, char *argv[]) { } ConsoleTerminal terminal; + globalConsoleTerminal = &terminal; terminal.run(); - runServer(); + lithium::runServer({argc, const_cast(argv)}); return 0; } diff --git a/src/LithiumApp.cpp b/src/LithiumApp.cpp index 5cbbf486..2acf532e 100644 --- a/src/LithiumApp.cpp +++ b/src/LithiumApp.cpp @@ -12,8 +12,9 @@ Description: Lithium App Enter **************************************************/ -#include "lithiumapp.hpp" +#include "LithiumApp.hpp" +#include "components/component.hpp" #include "config.h" #include "addon/addons.hpp" @@ -22,20 +23,28 @@ Description: Lithium App Enter #include "config/configor.hpp" +#include "device/manager.hpp" + +#include "task/container.hpp" +#include "task/generator.hpp" +#include "task/loader.hpp" #include "task/manager.hpp" +#include "task/pool.hpp" +#include "task/tick.hpp" #include "script/manager.hpp" -#include "atom/error/exception.hpp" +#include "atom/components/dispatch.hpp" #include "atom/error/error_stack.hpp" -#include "atom/log/loguru.hpp" +#include "atom/error/exception.hpp" #include "atom/function/global_ptr.hpp" -#include "atom/components/dispatch.hpp" +#include "atom/log/loguru.hpp" +#include "atom/system/env.hpp" #include "atom/system/process.hpp" #include "atom/utils/time.hpp" -#include "utils/marco.hpp" -#include "magic_enum/magic_enum.hpp" +#include "utils/constant.hpp" +#include "utils/marco.hpp" using json = nlohmann::json; @@ -77,21 +86,24 @@ using json = nlohmann::json; #define INIT_FUNC() DLOG_F(INFO, "Call {} with: {}", __func__, params.dump()); #define GET_VALUE_D(type, key, defaultValue) \ - type key = (params.contains(#key) ? params[#key].get() : defaultValue) + type key = \ + (params.contains(#key) ? params[#key].get() : (defaultValue)) namespace lithium { -std::shared_ptr MyApp = nullptr; +std::shared_ptr myApp = nullptr; -LithiumApp::LithiumApp() { +LithiumApp::LithiumApp() : Component("lithium.main") { DLOG_F(INFO, "LithiumApp Constructor"); try { - m_MessageBus = GetWeakPtr("lithium.bus"); - CHECK_WEAK_PTR_EXPIRED(m_MessageBus, + m_messagebus_ = GetWeakPtr("lithium.bus"); + CHECK_WEAK_PTR_EXPIRED(m_messagebus_, "load message bus from gpm: lithium.bus"); - m_TaskManager = GetWeakPtr("lithium.task.manager"); + m_task_interpreter_ = + GetWeakPtr("lithium.task.manager"); CHECK_WEAK_PTR_EXPIRED( - m_TaskManager, "load task manager from gpm: lithium.task.manager"); + m_task_interpreter_, + "load task manager from gpm: lithium.task.manager"); // Common Message Processing Threads // Max : Maybe we only need one thread for Message, and dynamically cast @@ -99,26 +111,168 @@ LithiumApp::LithiumApp() { // to the right type to process. // All of the messages are based on the Message class. DLOG_F(INFO, "Start Message Processing Thread"); - // m_MessageBus.lock()->StartProcessingThread(); + // m_messagebus_.lock()->StartProcessingThread(); DLOG_F(INFO, "Register LithiumApp Member Functions"); } catch (const std::exception &e) { LOG_F(ERROR, "Failed to load Lithium App , error : {}", e.what()); THROW_OBJ_UNINITIALIZED("Failed to load Lithium App"); } + + doc("Lithium App"); + def("load_component", &LithiumApp::loadComponent, "addon", + "Load a component"); + def("unload_component", &LithiumApp::unloadComponent, "addon", + "Unload a component"); + def("reload_component", &LithiumApp::reloadComponent, "addon", + "Reload a component"); + def("reload_all_components", &LithiumApp::reloadAllComponents, + "Reload all components"); + def("unload_all_components", &LithiumApp::unloadAllComponents, + "Unload all components"); + def("get_component_info", &LithiumApp::getComponentInfo, "addon", + "Get info about a component"); + def("get_component_list", &LithiumApp::getComponentList, + "Get a list of all components"); + + def("load_script", &LithiumApp::loadScript, "script", "Load a script"); + def("unload_script", &LithiumApp::unloadScript, "script", + "Unload a script"); + def("has_script", &LithiumApp::hasScript, "script", + "Check if a script is "); + def("get_script", &LithiumApp::getScript, "script", "Get a script"); + DLOG_F(INFO, "Lithium App Initialized"); } LithiumApp::~LithiumApp() { - m_MessageBus.lock()->UnsubscribeAll(); - m_MessageBus.lock()->StopAllProcessingThreads(); + m_messagebus_.lock()->unsubscribeAll(); + m_messagebus_.lock()->stopAllProcessingThreads(); } -std::shared_ptr LithiumApp::createShared() { +auto LithiumApp::createShared() -> std::shared_ptr { return std::make_shared(); } -void InitLithiumApp(int argc, char **argv) { +auto LithiumApp::initialize() -> bool { return true; } + +auto LithiumApp::destroy() -> bool { return true; } + +auto LithiumApp::getComponentManager() -> std::weak_ptr { + return m_component_manager_; +} + +auto LithiumApp::loadComponent(const std::string &name) -> bool { + return m_component_manager_.lock()->loadComponent(name); +} + +auto LithiumApp::unloadComponent(const std::string &name) -> bool { + return m_component_manager_.lock()->unloadComponent(name); +} + +auto LithiumApp::unloadAllComponents() -> bool { + for (const auto &componentName : + m_component_manager_.lock()->getComponentList()) { + if (!unloadComponent(componentName)) { + return false; + } + } + return true; +} + +auto LithiumApp::reloadComponent(const std::string &name) -> bool { + return m_component_manager_.lock()->reloadComponent(name); +} + +auto LithiumApp::reloadAllComponents() -> bool { + return m_component_manager_.lock()->reloadAllComponents(); +} +auto LithiumApp::getComponent(const std::string &name) + -> std::weak_ptr { + return m_component_manager_.lock()->getComponent(name).value(); +} + +auto LithiumApp::getComponentInfo(const std::string &name) -> json { + return m_component_manager_.lock()->getComponentInfo(name).value(); +} + +auto LithiumApp::getComponentList() -> std::vector { + return m_component_manager_.lock()->getComponentList(); +} + +void LithiumApp::loadScript(const std::string &name, const json &script) { + m_task_interpreter_.lock()->loadScript(name, script); +} + +void LithiumApp::unloadScript(const std::string &name) { + m_task_interpreter_.lock()->unloadScript(name); +} + +auto LithiumApp::hasScript(const std::string &name) const -> bool { + return m_task_interpreter_.lock()->hasScript(name); +} + +auto LithiumApp::getScript(const std::string &name) const + -> std::optional { + return m_task_interpreter_.lock()->getScript(name); +} + +void LithiumApp::registerFunction(const std::string &name, + std::function func) { + m_task_interpreter_.lock()->registerFunction(name, func); +} + +void LithiumApp::registerExceptionHandler( + const std::string &name, + std::function handler) { + m_task_interpreter_.lock()->registerExceptionHandler(name, handler); +} + +void LithiumApp::setVariable(const std::string &name, const json &value) { + m_task_interpreter_.lock()->setVariable(name, value); +} + +auto LithiumApp::getVariable(const std::string &name) -> json { + return m_task_interpreter_.lock()->getVariable(name); +} + +void LithiumApp::parseLabels(const json &script) { + m_task_interpreter_.lock()->parseLabels(script); +} + +void LithiumApp::execute(const std::string &scriptName) { + m_task_interpreter_.lock()->execute(scriptName); +} + +void LithiumApp::stop() { m_task_interpreter_.lock()->stop(); } + +void LithiumApp::pause() { m_task_interpreter_.lock()->pause(); } + +void LithiumApp::resume() { m_task_interpreter_.lock()->resume(); } + +void LithiumApp::queueEvent(const std::string &eventName, + const json &eventData) { + m_task_interpreter_.lock()->queueEvent(eventName, eventData); +} + +auto LithiumApp::getValue(const std::string &key_path) const + -> std::optional {} +auto LithiumApp::setValue(const std::string &key_path, + const nlohmann::json &value) -> bool {} + +auto LithiumApp::appendValue(const std::string &key_path, + const nlohmann::json &value) -> bool {} +auto LithiumApp::deleteValue(const std::string &key_path) -> bool {} +auto LithiumApp::hasValue(const std::string &key_path) const -> bool {} +auto LithiumApp::loadFromFile(const fs::path &path) -> bool {} +auto LithiumApp::loadFromDir(const fs::path &dir_path, + bool recursive) -> bool {} +auto LithiumApp::saveToFile(const fs::path &file_path) const -> bool {} +void LithiumApp::tidyConfig() {} +void LithiumApp::clearConfig() {} +void LithiumApp::mergeConfig(const nlohmann::json &src) {} + +void initLithiumApp(int argc, char **argv) { LOG_F(INFO, "Init Lithium App"); // Message Bus AddPtr("lithium.bus", atom::async::MessageBus::createShared()); @@ -127,13 +281,17 @@ void InitLithiumApp(int argc, char **argv) { // Atom::Async::ThreadManager::createShared(GetIntConfig("config/server/maxthread"))); // AddPtr("PluginManager", // PluginManager::createShared(GetPtr("ProcessManager"))); - // AddPtr("TaskManager", std::make_shared("tasks.json")); + // AddPtr("TaskInterpreter", + // std::make_shared("tasks.json")); // AddPtr("TaskGenerator", // std::make_shared(GetPtr("DeviceManager"))); // AddPtr("TaskStack", std::make_shared()); // AddPtr("ScriptManager", // ScriptManager::createShared(GetPtr("MessageBus"))); + AddPtr(constants::LITHIUM_DEVICE_MANAGER, DeviceManager::createShared()); + AddPtr(constants::LITHIUM_DEVICE_LOADER, ModuleLoader::createShared("./drivers")); + AddPtr("lithium.error.stack", std::make_shared()); AddPtr("lithium.task.container", TaskContainer::createShared()); @@ -141,9 +299,8 @@ void InitLithiumApp(int argc, char **argv) { AddPtr("lithium.task.loader", TaskLoader::createShared()); AddPtr("lithium.task.pool", TaskPool::createShared(std::thread::hardware_concurrency())); - AddPtr("lithium.task.tick", - TickScheduler::createShared(std::thread::hardware_concurrency())); - AddPtr("lithium.task.manager", TaskManager::createShared()); + AddPtr("lithium.task.tick", TickScheduler::createShared()); + AddPtr("lithium.task.manager", TaskInterpreter::createShared()); AddPtr("lithium.utils.env", atom::utils::Env::createShared(argc, argv)); @@ -153,7 +310,8 @@ void InitLithiumApp(int argc, char **argv) { AddPtr("lithium.addon.manager", ComponentManager::createShared()); } -json createSuccessResponse(const std::string &command, const json &value) { +auto createSuccessResponse(const std::string &command, + const json &value) -> json { json res; res["command"] = command; res["value"] = value; @@ -167,8 +325,8 @@ json createSuccessResponse(const std::string &command, const json &value) { return res; } -json createErrorResponse(const std::string &command, const json &error, - const std::string &message = "") { +auto createErrorResponse(const std::string &command, const json &error, + const std::string &message = "") -> json { json res; res["command"] = command; res["status"] = "error"; diff --git a/src/LithiumApp.hpp b/src/LithiumApp.hpp index 14e9f9f9..237589c1 100644 --- a/src/LithiumApp.hpp +++ b/src/LithiumApp.hpp @@ -16,12 +16,18 @@ Description: Lithium App Enter #define LITHIUM_APP_MAIN +#include #include #include "atom/async/message_bus.hpp" +#include "atom/components/component.hpp" +#include "atom/type/json_fwd.hpp" #include "atom/type/message.hpp" -#include "atom/type/json.hpp" + +#include "macro.hpp" + using json = nlohmann::json; +namespace fs = std::filesystem; // ------------------------------------------------------------------- // About the LithiumApp @@ -34,53 +40,109 @@ using json = nlohmann::json; // parameters. However, It is more convenient to use json object. // ------------------------------------------------------------------- -namespace atom -{ - namespace error - { - class ErrorStack; - } - - namespace system - { - class ProcessManager; - } +namespace atom { +namespace error { +class ErrorStack; +} + +namespace system { +class ProcessManager; } +} // namespace atom namespace lithium { class PyScriptManager; // FWD -class CarbonScript; - -class ComponentManager; // FWD +class ComponentManager; // FWD class ConfigManager; class TaskPool; -class TaskManager; +class TaskInterpreter; -class LithiumApp { +class LithiumApp : public Component { public: - LithiumApp(); - ~LithiumApp(); + explicit LithiumApp(); + ~LithiumApp() override; // ------------------------------------------------------------------- // Common methods // ------------------------------------------------------------------- - static std::shared_ptr createShared(); + static auto createShared() -> std::shared_ptr; + auto initialize() -> bool override; + auto destroy() -> bool override; + + // ------------------------------------------------------------------- + // Component methods + // ------------------------------------------------------------------- + + auto getComponentManager() -> std::weak_ptr; + auto loadComponent(const std::string& name) -> bool; + auto unloadComponent(const std::string& name) -> bool; + auto unloadAllComponents() -> bool; + auto reloadComponent(const std::string& name) -> bool; + auto reloadAllComponents() -> bool; + auto getComponent(const std::string& name) -> std::weak_ptr; + auto getComponentInfo(const std::string& name) -> json; + auto getComponentList() -> std::vector; + + // ------------------------------------------------------------------- + // Config methods + // ------------------------------------------------------------------- + + ATOM_NODISCARD auto getValue(const std::string& key_path) const + -> std::optional; + auto setValue(const std::string& key_path, + const nlohmann::json& value) -> bool; + + auto appendValue(const std::string& key_path, const nlohmann::json& value) -> bool; + auto deleteValue(const std::string& key_path) -> bool; + ATOM_NODISCARD auto hasValue(const std::string& key_path) const -> bool; + auto loadFromFile(const fs::path& path) -> bool; + auto loadFromDir(const fs::path& dir_path, bool recursive = false) -> bool; + ATOM_NODISCARD auto saveToFile(const fs::path& file_path) const -> bool; + void tidyConfig(); + void clearConfig(); + void mergeConfig(const nlohmann::json& src); + + // ------------------------------------------------------------------- + // Task methods + // ------------------------------------------------------------------- + + void loadScript(const std::string& name, const json& script); + void unloadScript(const std::string& name); + + ATOM_NODISCARD auto hasScript(const std::string& name) const -> bool; + ATOM_NODISCARD auto getScript(const std::string& name) const + -> std::optional; + + void registerFunction(const std::string& name, + std::function func); + void registerExceptionHandler( + const std::string& name, + std::function handler); + + void setVariable(const std::string& name, const json& value); + auto getVariable(const std::string& name) -> json; + + void parseLabels(const json& script); + void execute(const std::string& scriptName); + void stop(); + void pause(); + void resume(); + void queueEvent(const std::string& eventName, const json& eventData); private: - std::weak_ptr m_TaskPool; - std::weak_ptr m_MessageBus; - std::weak_ptr m_ErrorStack; - std::weak_ptr m_ComponentManager; - std::weak_ptr m_TaskManager; - - std::weak_ptr m_PyScriptManager; - std::weak_ptr m_CarbonScript; + std::weak_ptr m_taskpool_; + std::weak_ptr m_messagebus_; + std::weak_ptr m_errorstack_; + std::weak_ptr m_component_manager_; + std::weak_ptr m_task_interpreter_; + + std::weak_ptr m_py_script_manager_; }; -extern std::shared_ptr MyApp; +extern std::shared_ptr myApp; -void InitLithiumApp(int argc, char **argv); +void initLithiumApp(int argc, char** argv); } // namespace lithium diff --git a/src/addon/CMakeLists.txt b/src/addon/CMakeLists.txt new file mode 100644 index 00000000..e2019ade --- /dev/null +++ b/src/addon/CMakeLists.txt @@ -0,0 +1,78 @@ +# Minimum required CMake version +cmake_minimum_required(VERSION 3.20) + +# Project name and version, using C and C++ languages +project(lithium-addons VERSION 1.0.0 LANGUAGES C CXX) + +# Project description and information +# This project is the official addonsuration module for the Lithium server. +# Author: Max Qian +# License: GPL3 +# Project Name: Lithium-Addons +# Description: The official addons module for lithium server +# Author: Max Qian +# License: GPL3 + +find_package(Seccomp REQUIRED) + +# Project sources +set(PROJECT_SOURCES + addons.cpp + compiler.cpp + dependency.cpp + loader.cpp + manager.cpp + sandbox.cpp + version.cpp + + template/standalone.cpp +) + +# Project headers +set(PROJECT_HEADERS + addons.hpp + compiler.hpp + dependency.hpp + loader.hpp + manager.hpp + sandbox.hpp +) + +# Required libraries for the project +set(PROJECT_LIBS + atom-io + atom-error + atom-function + atom-system + loguru + lithium-utils + ${CMAKE_THREAD_LIBS_INIT} + ${Seccomp_LIBRARIES} +) + +# Create object library +add_library(${PROJECT_NAME}_OBJECT OBJECT ${PROJECT_SOURCES} ${PROJECT_HEADERS}) + +# Set object library property to be position independent code +set_property(TARGET ${PROJECT_NAME}_OBJECT PROPERTY POSITION_INDEPENDENT_CODE ON) + +# Create static library +add_library(${PROJECT_NAME} STATIC $) + +# Set static library properties +set_target_properties(${PROJECT_NAME} PROPERTIES + VERSION ${PROJECT_VERSION} # Version number + SOVERSION 1 # Compatibility version + OUTPUT_NAME ${PROJECT_NAME} # Output name +) + +# Include directories so that project headers can be included +target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +# Link libraries required by the project +target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_LIBS}) + +# Install target to install the static library to a specified location +install(TARGETS ${PROJECT_NAME} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) diff --git a/src/addon/addons.cpp b/src/addon/addons.cpp index c4afd1ba..78073b0c 100644 --- a/src/addon/addons.cpp +++ b/src/addon/addons.cpp @@ -16,35 +16,36 @@ Description: Addon manager to solve the dependency problem. #include #include - - #include "atom/log/loguru.hpp" namespace lithium { -bool AddonManager::addModule(const std::filesystem::path &path, - const std::string &name) { - if (m_modules.find(name) != m_modules.end()) { + +auto AddonManager::addModule(const std::filesystem::path &path, + const std::string &name) -> bool { + if (modules_.contains(name)) { LOG_F(ERROR, "Addon {} has already been added.", name); return false; } - if (std::filesystem::exists(path)) { - std::filesystem::path new_path = path / "package.json"; - try { - m_modules[name] = json::parse(std::ifstream(new_path.string())); - } catch (const std::exception &e) { - LOG_F(ERROR, "Addon {} package.json file does not exist.", name); - return false; - } - } else { + + if (!std::filesystem::exists(path)) { LOG_F(ERROR, "Addon {} does not exist.", name); return false; } + + std::filesystem::path packagePath = path / "package.json"; + try { + modules_[name] = json::parse(std::ifstream(packagePath)); + } catch (const std::exception &e) { + LOG_F(ERROR, "Addon {} package.json file does not exist or is invalid.", + name); + return false; + } + return true; } -bool AddonManager::removeModule(const std::string &name) { - if (m_modules.find(name) != m_modules.end()) { - m_modules.erase(name); +auto AddonManager::removeModule(const std::string &name) -> bool { + if (modules_.erase(name) != 0U) { DLOG_F(INFO, "Addon {} has been removed.", name); return true; } @@ -52,95 +53,91 @@ bool AddonManager::removeModule(const std::string &name) { return false; } -json AddonManager::getModule(const std::string &name) { - if (m_modules.find(name) != m_modules.end()) { - return m_modules[name]; +auto AddonManager::getModule(const std::string &name) const -> json { + if (auto it = modules_.find(name); it != modules_.end()) { + return it->second; } LOG_F(ERROR, "Addon {} does not exist.", name); return nullptr; } -bool AddonManager::resolveDependencies(const std::string &modName, - std::vector &resolvedDeps, - std::vector &missingDeps) { - // 检查模组是否存在 - if (m_modules.find(modName) == m_modules.end()) { +auto AddonManager::resolveDependencies( + const std::string &modName, std::vector &resolvedDeps, + std::vector &missingDeps) -> bool { + if (!modules_.contains(modName)) { LOG_F(ERROR, "Addon {} does not exist.", modName); return false; } + std::unordered_map inDegree; std::queue q; - for (const auto &pair : m_modules) { - inDegree[pair.second["name"]] = 0; - for (auto &dep : pair.second["dependencies"]) { - inDegree[dep["name"]]++; + for (const auto &[name, module] : modules_) { + inDegree[name] = 0; + for (const auto &dep : module["dependencies"]) { + inDegree[dep]++; } } - // 将入度为0的模组加入队列 - const json &mod = m_modules[modName]; + const auto &mod = modules_.at(modName); q.push(mod); + while (!q.empty()) { - json currentMod = q.front(); + const auto CURRENT_MOD = q.front(); q.pop(); - resolvedDeps.push_back(currentMod["name"].get()); + resolvedDeps.push_back(CURRENT_MOD["name"]); - for (const json &dep : - currentMod["dependencies"].get>()) { - inDegree[dep.get()]--; - if (inDegree[dep.get()] == 0) { - q.push(dep); + for (const auto &dep : CURRENT_MOD["dependencies"]) { + if (--inDegree[dep] == 0) { + q.push(modules_.at(dep)); } } } - // 检查是否有循环依赖关系 - if (resolvedDeps.size() < m_modules.size() || - !checkMissingDependencies(modName, missingDeps)) { - return false; - } - return true; + + return !(resolvedDeps.size() < modules_.size() || + !checkMissingDependencies(modName, missingDeps)); } -bool AddonManager::checkMissingDependencies( - const std::string &modName, std::vector &missingDeps) { +auto AddonManager::checkMissingDependencies( + const std::string &modName, + std::vector &missingDeps) const -> bool { std::unordered_map expectedDeps; - for (const std::string &depName : - m_modules[modName]["dependencies"].get>()) { - expectedDeps[depName] = true; + for (const auto &dep : modules_.at(modName)["dependencies"]) { + expectedDeps[dep] = true; } - for (const std::string &dep : - m_modules[modName]["dependencies"].get>()) { - if (expectedDeps.find(dep) != expectedDeps.end()) { - expectedDeps.erase(dep); - } + + for (const auto &dep : modules_.at(modName)["dependencies"]) { + expectedDeps.erase(dep); } - for (const auto &pair : expectedDeps) { - missingDeps.push_back(pair.first); + + for (const auto &[dep, _] : expectedDeps) { + missingDeps.push_back(dep); } + return missingDeps.empty(); } -bool AddonManager::checkCircularDependencies( +auto AddonManager::checkCircularDependencies( const std::string &modName, std::unordered_map &visited, - std::unordered_map &recursionStack) { + std::unordered_map &recursionStack) const -> bool { if (!visited[modName]) { visited[modName] = true; recursionStack[modName] = true; - const json &mod = m_modules[modName]; - for (const json &dep : mod["dependencies"]) { - if (!visited[dep.get()] && - checkCircularDependencies(dep.get(), visited, - recursionStack)) { + for (const auto &dep : modules_.at(modName)["dependencies"]) { + if (!visited[dep] && + checkCircularDependencies(dep, visited, recursionStack)) { return true; - } else if (recursionStack[dep["name"]]) { + } + if (recursionStack[dep]) { return true; } } } + recursionStack[modName] = false; return false; } + } // namespace lithium diff --git a/src/addon/addons.hpp b/src/addon/addons.hpp index 34906304..43fe7a89 100644 --- a/src/addon/addons.hpp +++ b/src/addon/addons.hpp @@ -14,119 +14,70 @@ Description: Addon manager to solve the dependency problem. #pragma once +#include #include +#include #include +#include "atom/type/json.hpp" + #if ENABLE_FASTHASH #include "emhash/hash_table8.hpp" -#else -#include #endif -#include "atom/type/json.hpp" using json = nlohmann::json; namespace lithium { + /** - * @brief This is the class which contains the dependencies relationships of the - * modules. - * @note This class will not contains any module's shared_ptr or unique_ptr. - * @note So, the AddonManager is not same as the moduleManager. + * @brief This class manages the dependencies of modules. + * @note This class does not contain any module's shared_ptr or unique_ptr. */ class AddonManager { public: /** - * @brief Construct a new Module Manager object. + * @brief Construct a new Addon Manager object. */ explicit AddonManager() = default; /** - * @brief Destroy the Module Manager object. + * @brief Destroy the Addon Manager object. */ ~AddonManager() = default; // ------------------------------------------------------------------- // Common methods // ------------------------------------------------------------------- - - static std::shared_ptr createShared() { + static auto createShared() -> std::shared_ptr { return std::make_shared(); } // ------------------------------------------------------------------- // Module methods // ------------------------------------------------------------------- + auto addModule(const std::filesystem::path &path, + const std::string &name) -> bool; + auto removeModule(const std::string &name) -> bool; + auto getModule(const std::string &name) const -> json; - /** - * @brief Add a module to the Module manager. - * @param path The path of the addon. - * @note The name of the module must be unique. - * @note The name of the module must be the same as the name of the module - * in the Module.json. - */ - bool addModule(const std::filesystem::path &path, const std::string &name); - - /** - * @brief Remove a module from the Module manager. - * @param name The name of the module. - * @note If there is no module with the name, this function will do nothing. - */ - bool removeModule(const std::string &name); - - /** - * @brief Get a module from the Module manager. - * @param name The name of the module. - * @return The module. - * @note If there is no module with the name, this function will return - * nullptr. - */ - json getModule(const std::string &name); - - /** - * @brief Resolve the dependencies of the module. - * @param modName The name of the module. - * @param resolvedDeps The vector of resolved dependencies. - * @param missingDeps The vector of missing dependencies. - * @return True if the dependencies are resolved successfully, otherwise - * false. - * @note If there is no module with the name, this function will return - * false. - */ - bool resolveDependencies(const std::string &modName, + auto resolveDependencies(const std::string &modName, std::vector &resolvedDeps, - std::vector &missingDeps); + std::vector &missingDeps) -> bool; private: #if ENABLE_FASTHASH - emhash8::HashMap m_modules; + emhash8::HashMap modules_; #else - std::unordered_map m_modules; + std::unordered_map modules_; #endif - /** - * @brief Check the circular dependencies of the module. - * @param modName The name of the module. - * @param visited The map of visited modules. - * @param recursionStack The map of recursion stack. - * @return True if the dependencies are resolved successfully, otherwise - * false. - * @note If there is no module with the name, this function will return - * false. - */ - bool checkCircularDependencies( + auto checkCircularDependencies( const std::string &modName, std::unordered_map &visited, - std::unordered_map &recursionStack); + std::unordered_map &recursionStack) const -> bool; - /** - * @brief Check the missing dependencies of the module. - * @param modName The name of the module. - * @param missingDeps The vector of missing dependencies. - * @return True if the dependencies are resolved successfully, otherwise - * false. - * @note If there is no module with the name, this function will return - * false. - */ - bool checkMissingDependencies(const std::string &modName, - std::vector &missingDeps); + auto checkMissingDependencies(const std::string &modName, + std::vector &missingDeps) const + -> bool; }; + } // namespace lithium diff --git a/src/addon/analysts.cpp b/src/addon/analysts.cpp new file mode 100644 index 00000000..2f3bcc9a --- /dev/null +++ b/src/addon/analysts.cpp @@ -0,0 +1,235 @@ +#include "analysts.hpp" + +#include +#include + +#include "atom/error/exception.hpp" +#include "atom/type/json.hpp" + +namespace lithium { +void CompilerOutputParser::parseLine(const std::string& line) { + if (std::regex_search(line, includeMatch_, includePattern_)) { + currentContext_ = includeMatch_.str(1); + return; + } + + for (const auto& [compiler, pattern] : regexPatterns_) { + std::smatch match; + if (std::regex_search(line, match, pattern)) { + MessageType type = determineType(match.str(4)); + std::string file = match.str(1); + int lineNum = std::stoi(match.str(2)); + int column = match.str(3).empty() ? 0 : std::stoi(match.str(3)); + std::string errorCode = match.size() > 5 ? match.str(5) : ""; + std::string functionName = match.size() > 6 ? match.str(6) : ""; + std::string message = + match.size() > 7 ? match.str(7) : match.str(5); + + std::lock_guard lock(mutex_); + messages_.emplace_back(type, file, lineNum, column, errorCode, + functionName, message, currentContext_); + counts_[type]++; + return; + } + } + + std::lock_guard lock(mutex_); + messages_.emplace_back(MessageType::UNKNOWN, "", 0, 0, "", "", line, + currentContext_); + counts_[MessageType::UNKNOWN]++; +} + +void CompilerOutputParser::parseFile(const std::string& filename) { + std::ifstream inputFile(filename); + if (!inputFile.is_open()) { + THROW_FAIL_TO_OPEN_FILE("Failed to open file: " + filename); + } + + std::string line; + while (std::getline(inputFile, line)) { + parseLine(line); + } +} + +void CompilerOutputParser::parseFileMultiThreaded(const std::string& filename, + int numThreads) { + std::ifstream inputFile(filename); + if (!inputFile.is_open()) { + THROW_FAIL_TO_OPEN_FILE("Failed to open file: " + filename); + } + + std::vector lines; + std::string line; + while (std::getline(inputFile, line)) { + lines.push_back(line); + } + + std::vector threads; + auto worker = [this](const std::vector& lines, int start, + int end) { + for (int i = start; i < end; ++i) { + parseLine(lines[i]); + } + }; + + int blockSize = lines.size() / numThreads; + for (int i = 0; i < numThreads; ++i) { + int start = i * blockSize; + int end = (i == numThreads - 1) ? lines.size() : (i + 1) * blockSize; + threads.emplace_back(worker, std::cref(lines), start, end); + } + + for (auto& thread : threads) { + thread.join(); + } +} + +auto CompilerOutputParser::getReport(bool detailed) const -> std::string { + std::ostringstream report; + report << "Compiler Messages Report:\n"; + report << "Errors: " << counts_.at(MessageType::ERROR) << "\n"; + report << "Warnings: " << counts_.at(MessageType::WARNING) << "\n"; + report << "Notes: " << counts_.at(MessageType::NOTE) << "\n"; + report << "Unknown: " << counts_.at(MessageType::UNKNOWN) << "\n"; + + if (detailed) { + report << "\nDetails:\n"; + for (const auto& msg : messages_) { + report << "[" << toString(msg.type) << "] "; + if (!msg.file.empty()) { + report << msg.file << ":" << msg.line << ":" << msg.column + << ": "; + } + if (!msg.errorCode.empty()) { + report << msg.errorCode << " "; + } + if (!msg.functionName.empty()) { + report << msg.functionName << " "; + } + report << msg.message << "\n"; + if (!msg.context.empty()) { + report << " Context: " << msg.context << "\n"; + } + for (const auto& note : msg.relatedNotes) { + report << " Note: " << note << "\n"; + } + } + } + + return report.str(); +} + +void CompilerOutputParser::generateHtmlReport( + const std::string& outputFilename) const { + std::ofstream outputFile(outputFilename); + if (!outputFile.is_open()) { + THROW_FAIL_TO_OPEN_FILE("Failed to open output file: " + + outputFilename); + } + + outputFile << "\n"; + outputFile << "

Compiler Messages Report

\n"; + outputFile << "\n"; + + outputFile << "

Details

\n"; + outputFile << "\n"; + outputFile << "\n"; +} + +auto CompilerOutputParser::generateJsonReport() -> json { + json root; + root["Errors"] = counts_[MessageType::ERROR]; + root["Warnings"] = counts_[MessageType::WARNING]; + root["Notes"] = counts_[MessageType::NOTE]; + root["Unknown"] = counts_[MessageType::UNKNOWN]; + + for (const auto& msg : messages_) { + json entry; + entry["Type"] = toString(msg.type); + entry["File"] = msg.file; + entry["Line"] = msg.line; + entry["Column"] = msg.column; + entry["ErrorCode"] = msg.errorCode; + entry["FunctionName"] = msg.functionName; + entry["Message"] = msg.message; + entry["Context"] = msg.context; + for (const auto& note : msg.relatedNotes) { + entry["RelatedNotes"].push_back(note); + } + root["Details"].push_back(entry); + } + + return root; +} + +void CompilerOutputParser::setCustomRegexPattern(const std::string& compiler, + const std::string& pattern) { + std::lock_guard lock(mutex_); + regexPatterns_[compiler] = std::regex(pattern); +} + +void CompilerOutputParser::initRegexPatterns() { + regexPatterns_["gcc_clang"] = + std::regex(R"((.*):(\d+):(\d+): (error|warning|note): (.*))"); + regexPatterns_["msvc"] = + std::regex(R"((.*)\((\d+),(\d+)\): (error|warning|note) (C\d+): (.*))"); + regexPatterns_["icc"] = + std::regex(R"((.*)\((\d+)\): (error|remark|warning|note): (.*))"); +} + +auto CompilerOutputParser::determineType(const std::string& typeStr) const + -> MessageType { + if (typeStr == "error") { + return MessageType::ERROR; + } + if (typeStr == "warning") { + return MessageType::WARNING; + } + if (typeStr == "note" || typeStr == "remark") { + return MessageType::NOTE; + } + return MessageType::UNKNOWN; +} + +auto CompilerOutputParser::toString(MessageType type) const -> std::string { + switch (type) { + case MessageType::ERROR: + return "Error"; + case MessageType::WARNING: + return "Warning"; + case MessageType::NOTE: + return "Note"; + default: + return "Unknown"; + } +} + +} // namespace lithium diff --git a/src/addon/analysts.hpp b/src/addon/analysts.hpp new file mode 100644 index 00000000..3cfb0304 --- /dev/null +++ b/src/addon/analysts.hpp @@ -0,0 +1,62 @@ +#ifndef LITHIUM_ADDON_COMPILER_ANALYSIS_HPP +#define LITHIUM_ADDON_COMPILER_ANALYSIS_HPP + +#include +#include +#include +#include +#include + +#include "atom/type/json_fwd.hpp" + +namespace lithium { + +using json = nlohmann::json; + +enum class MessageType { ERROR, WARNING, NOTE, UNKNOWN }; + +struct Message { + MessageType type; + std::string file; + int line; + int column; + std::string errorCode; + std::string functionName; + std::string message; + std::string context; + std::vector relatedNotes; + + Message(MessageType t, std::string f, int l, int c, std::string code, + std::string func, std::string msg, std::string ctx); +}; + +class CompilerOutputParser { +public: + CompilerOutputParser(); + + void parseLine(const std::string& line); + void parseFile(const std::string& filename); + void parseFileMultiThreaded(const std::string& filename, int numThreads); + auto getReport(bool detailed = true) const -> std::string; + void generateHtmlReport(const std::string& outputFilename) const; + auto generateJsonReport() -> json; + void setCustomRegexPattern(const std::string& compiler, + const std::string& pattern); + +private: + std::vector messages_; + std::unordered_map counts_; + mutable std::unordered_map regexPatterns_; + mutable std::mutex mutex_; + std::string currentContext_; + std::regex includePattern_; + std::smatch includeMatch_; + + void initRegexPatterns(); + MessageType determineType(const std::string& typeStr) const; + std::string toString(MessageType type) const; +}; + +} // namespace lithium + +#endif diff --git a/src/addon/builder.cpp b/src/addon/builder.cpp new file mode 100644 index 00000000..47568a37 --- /dev/null +++ b/src/addon/builder.cpp @@ -0,0 +1,54 @@ +#include "builder.hpp" + +#include "platform/cmake.hpp" +#include "platform/meson.hpp" + +#include "atom/error/exception.hpp" + +namespace lithium { +BuildManager::BuildManager(BuildSystemType type) { + switch (type) { + case BuildSystemType::CMake: + builder_ = std::make_unique(); + break; + case BuildSystemType::Meson: + builder_ = std::make_unique(); + break; + default: + THROW_INVALID_ARGUMENT("Unsupported build system type"); + } +} + +auto BuildManager::configureProject( + const std::string &sourceDir, const std::string &buildDir, + const std::string &buildType, + const std::vector &options) -> bool { + return builder_->configureProject(sourceDir, buildDir, buildType, options); +} + +auto BuildManager::buildProject(const std::string &buildDir, int jobs) -> bool { + return builder_->buildProject(buildDir, jobs); +} + +auto BuildManager::cleanProject(const std::string &buildDir) -> bool { + return builder_->cleanProject(buildDir); +} + +auto BuildManager::installProject(const std::string &buildDir, + const std::string &installDir) -> bool { + return builder_->installProject(buildDir, installDir); +} + +auto BuildManager::runTests(const std::string &buildDir) -> bool { + return builder_->runTests(buildDir); +} + +auto BuildManager::generateDocs(const std::string &buildDir) -> bool { + return builder_->generateDocs(buildDir); +} + +auto BuildManager::loadConfig(const std::string &configPath) -> bool { + return builder_->loadConfig(configPath); +} + +} // namespace lithium diff --git a/src/addon/builder.hpp b/src/addon/builder.hpp new file mode 100644 index 00000000..844162a0 --- /dev/null +++ b/src/addon/builder.hpp @@ -0,0 +1,31 @@ +#ifndef LITHIUM_ADDON_BUILDER_HPP +#define LITHIUM_ADDON_BUILDER_HPP + +#include + +#include "platform/base.hpp" + +namespace lithium { +class BuildManager { +public: + enum class BuildSystemType { CMake, Meson }; + + BuildManager(BuildSystemType type); + auto configureProject(const std::string &sourceDir, + const std::string &buildDir, + const std::string &buildType, + const std::vector &options) -> bool; + auto buildProject(const std::string &buildDir, int jobs) -> bool; + auto cleanProject(const std::string &buildDir) -> bool; + auto installProject(const std::string &buildDir, + const std::string &installDir) -> bool; + auto runTests(const std::string &buildDir) -> bool; + auto generateDocs(const std::string &buildDir) -> bool; + auto loadConfig(const std::string &configPath) -> bool; + +private: + std::unique_ptr builder_; +}; +} // namespace lithium + +#endif diff --git a/src/addon/compiler.cpp b/src/addon/compiler.cpp index d5db6774..8cc3ac80 100644 --- a/src/addon/compiler.cpp +++ b/src/addon/compiler.cpp @@ -1,36 +1,72 @@ -/* - * compiler.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-3-29 - -Description: Compiler - -**************************************************/ - #include "compiler.hpp" +#include "toolchain.hpp" #include "utils/constant.hpp" #include -#include - -#include +#include +#include #include "atom/log/loguru.hpp" +#include "atom/system/command.hpp" #include "atom/type/json.hpp" +#include "atom/utils/to_string.hpp" using json = nlohmann::json; namespace fs = std::filesystem; namespace lithium { -bool Compiler::compileToSharedLibrary(std::string_view code, + +class CompilerImpl { +public: + CompilerImpl(); + auto compileToSharedLibrary(std::string_view code, + std::string_view moduleName, + std::string_view functionName, + std::string_view optionsFile) -> bool; + + void addCompileOptions(const std::string &options); + + auto getAvailableCompilers() const -> std::vector; + +private: + void createOutputDirectory(const fs::path &outputDir); + auto syntaxCheck(std::string_view code, std::string_view compiler) -> bool; + auto compileCode(std::string_view code, std::string_view compiler, + std::string_view compileOptions, + const fs::path &output) -> bool; + + auto findAvailableCompilers() -> std::vector; + + std::unordered_map cache_; + std::string customCompileOptions_; + ToolchainManager toolchainManager_; +}; + +Compiler::Compiler() : impl_(std::make_unique()) {} + +Compiler::~Compiler() = default; + +auto Compiler::compileToSharedLibrary(std::string_view code, std::string_view moduleName, std::string_view functionName, - std::string_view optionsFile) { + std::string_view optionsFile) -> bool { + return impl_->compileToSharedLibrary(code, moduleName, functionName, + optionsFile); +} + +void Compiler::addCompileOptions(const std::string &options) { + impl_->addCompileOptions(options); +} + +auto Compiler::getAvailableCompilers() const -> std::vector { + return impl_->getAvailableCompilers(); +} + +CompilerImpl::CompilerImpl() { toolchainManager_.scanForToolchains(); } + +auto CompilerImpl::compileToSharedLibrary( + std::string_view code, std::string_view moduleName, + std::string_view functionName, std::string_view optionsFile) -> bool { LOG_F(INFO, "Compiling module {}::{}...", moduleName, functionName); if (code.empty() || moduleName.empty() || functionName.empty()) { @@ -39,48 +75,65 @@ bool Compiler::compileToSharedLibrary(std::string_view code, } // 检查模块是否已编译并缓存 - auto cachedModule = - cache_.find(fmt::format("{}::{}", moduleName, functionName)); - if (cachedModule != cache_.end()) { - LOG_F(WARNING, - "Module {}::{} is already compiled, using cached result.", - moduleName, functionName); + std::string cacheKey = std::format("{}::{}", moduleName, functionName); + if (cache_.find(cacheKey) != cache_.end()) { + LOG_F(WARNING, "Module {} is already compiled, using cached result.", + cacheKey); return true; } // 创建输出目录 - const fs::path outputDir = "atom/global"; - createOutputDirectory(outputDir); + const fs::path OUTPUT_DIR = "atom/global"; + createOutputDirectory(OUTPUT_DIR); - const auto availableCompilers = findAvailableCompilers(); + // Max: 检查可用的编译器,然后再和指定的进行比对 + auto availableCompilers = findAvailableCompilers(); if (availableCompilers.empty()) { LOG_F(ERROR, "No available compilers found."); return false; } - LOG_F(INFO, "Available compilers: {}", fmt::join(availableCompilers, ", ")); + LOG_F(INFO, "Available compilers: {}", + atom::utils::toString(availableCompilers)); // 读取编译选项 std::ifstream optionsStream(optionsFile.data()); - const auto compileOptions = [&optionsStream] { + std::string compileOptions = [&] -> std::string { if (!optionsStream) { LOG_F( WARNING, "Failed to open compile options file, using default options."); - return std::string{"-O2 -std=c++20 -Wall -shared -fPIC"}; + return "-O2 -std=c++20 -Wall -shared -fPIC"; } try { json optionsJson; optionsStream >> optionsJson; - return fmt::format( - "{} {} {}", - optionsJson["optimization_level"].get(), - optionsJson["cplus_version"].get(), - optionsJson["warnings"].get()); - } catch (const std::exception& e) { + + auto compiler = optionsJson.value("compiler", constants::COMPILER); + if (std::find(availableCompilers.begin(), availableCompilers.end(), + compiler) == availableCompilers.end()) { + LOG_F(WARNING, "Compiler {} is not available, using default.", + compiler); + compiler = constants::COMPILER; + } + + auto cmd = std::format( + "{} {} {} {} {}", compiler, + optionsJson.value("optimization_level", "-O2"), + optionsJson.value("cplus_version", "-std=c++20"), + optionsJson.value("warnings", "-Wall"), customCompileOptions_); + + LOG_F(INFO, "Compile options: {}", cmd); + return cmd; + + } catch (const json::parse_error &e) { + LOG_F(ERROR, "Failed to parse compile options file: {}", e.what()); + } catch (const std::exception &e) { LOG_F(ERROR, "Failed to parse compile options file: {}", e.what()); - return std::string{"-O2 -std=c++20 -Wall -shared -fPIC"}; } + + return constants::COMPILER + + std::string{"-O2 -std=c++20 -Wall -shared -fPIC"}; }(); // 语法检查 @@ -89,19 +142,19 @@ bool Compiler::compileToSharedLibrary(std::string_view code, } // 编译代码 - const auto outputPath = - outputDir / fmt::format("{}{}{}", constants::LIB_EXTENSION, moduleName, - constants::LIB_EXTENSION); + fs::path outputPath = + OUTPUT_DIR / std::format("{}{}{}", constants::LIB_EXTENSION, moduleName, + constants::LIB_EXTENSION); if (!compileCode(code, constants::COMPILER, compileOptions, outputPath)) { return false; } // 缓存编译结果 - cache_[fmt::format("{}::{}", moduleName, functionName)] = outputPath; + cache_[cacheKey] = outputPath; return true; } -void Compiler::createOutputDirectory(const fs::path& outputDir) { +void CompilerImpl::createOutputDirectory(const fs::path &outputDir) { if (!fs::exists(outputDir)) { LOG_F(WARNING, "Output directory {} does not exist, creating it.", outputDir.string()); @@ -109,65 +162,230 @@ void Compiler::createOutputDirectory(const fs::path& outputDir) { } } -bool Compiler::syntaxCheck(std::string_view code, std::string_view compiler) { - const auto command = fmt::format("{} -fsyntax-only -xc++ -", compiler); +auto CompilerImpl::syntaxCheck(std::string_view code, + std::string_view compiler) -> bool { + std::string command = std::format("{} -fsyntax-only -xc++ -", compiler); std::string output; - const auto exitCode = runCommand(command, code, output); - if (exitCode != 0) { + output = atom::system::executeCommand( + command, false, + [&](const std::string &line) { output += line + "\n"; }); + if (!output.empty()) { LOG_F(ERROR, "Syntax check failed:\n{}", output); return false; } return true; } -bool Compiler::compileCode(std::string_view code, std::string_view compiler, - std::string_view compileOptions, - const fs::path& output) { - const auto command = fmt::format("{} {} -xc++ - -o {}", compiler, - compileOptions, output.string()); +auto CompilerImpl::compileCode(std::string_view code, std::string_view compiler, + std::string_view compileOptions, + const fs::path &output) -> bool { + std::string command = std::format("{} {} -xc++ - -o {}", compiler, + compileOptions, output.string()); std::string compilationOutput; - const auto exitCode = runCommand(command, code, compilationOutput); - if (exitCode != 0) { + compilationOutput = atom::system::executeCommand( + command, false, + [&](const std::string &line) { compilationOutput += line + "\n"; }); + if (!compilationOutput.empty()) { LOG_F(ERROR, "Compilation failed:\n{}", compilationOutput); return false; } return true; } -int Compiler::runCommand(std::string_view command, std::string_view input, - std::string& output) { - std::array buffer; - output.clear(); +auto CompilerImpl::findAvailableCompilers() -> std::vector { + return toolchainManager_.getAvailableCompilers(); +} - auto pipe = popen(command.data(), "r+"); - if (!pipe) { - LOG_F(ERROR, "Failed to run command: {}", command); - return -1; +void CompilerImpl::addCompileOptions(const std::string &options) { + customCompileOptions_ = options; +} + +auto CompilerImpl::getAvailableCompilers() const -> std::vector { + return toolchainManager_.getAvailableCompilers(); +} + +void CppMemberGenerator::generate(const json &j, std::ostream &os) { + for (const auto &member : j) { + os << " " << member["type"].get() << " " + << member["name"].get() << ";\n"; } +} - fwrite(input.data(), sizeof(char), input.size(), pipe); - fclose(pipe); +void CppConstructorGenerator::generate(const std::string &className, + const json &j, std::ostream &os) { + for (const auto &constructor : j) { + os << " " << className << "("; + bool first = true; + for (const auto ¶m : constructor["parameters"]) { + if (!first) + os << ", "; + os << param["type"].get() << " " + << param["name"].get(); + first = false; + } + os << ")"; + if (!constructor["initializer_list"].empty()) { + os << " : "; + bool first_init = true; + for (const auto &init : constructor["initializer_list"]) { + if (!first_init) + os << ", "; + os << init["member"].get() << "(" + << init["value"].get() << ")"; + first_init = false; + } + } + os << " {\n"; + for (const auto ¶m : constructor["parameters"]) { + os << " this->" << param["name"].get() << " = " + << param["name"].get() << ";\n"; + } + os << " }\n"; + } + if (j.empty()) { + os << " " << className << "() = default;\n"; + } +} + +void CppMethodGenerator::generate(const json &j, std::ostream &os) { + for (const auto &method : j) { + os << " "; + if (method.value("is_virtual", false)) { + os << "virtual "; + } + os << method["return_type"].get() << " " + << method["name"].get() << "("; + bool first = true; + for (const auto ¶m : method["parameters"]) { + if (!first) + os << ", "; + os << param["type"].get() << " " + << param["name"].get(); + first = false; + } + os << ")"; + if (method.value("is_const", false)) { + os << " const"; + } + os << " {\n"; + os << " " << method["body"].get() << "\n"; + os << " }\n"; + } +} + +void CppAccessorGenerator::generate(const json &j, std::ostream &os) { + for (const auto &accessor : j) { + os << " " << accessor["type"].get() << " " + << accessor["name"].get() << "() const {\n"; + os << " return " << accessor["member"].get() + << ";\n"; + os << " }\n"; + } +} + +void CppMutatorGenerator::generate(const json &j, std::ostream &os) { + for (const auto &mutator : j) { + os << " void " << mutator["name"].get() << "(" + << mutator["parameter_type"].get() << " value) {\n"; + os << " " << mutator["member"].get() + << " = value;\n"; + os << " }\n"; + } +} + +void CppFriendFunctionGenerator::generate(const json &j, std::ostream &os) { + for (const auto &friendFunction : j) { + os << " friend " << friendFunction["return_type"].get() + << " " << friendFunction["name"].get() << "("; + bool first = true; + for (const auto ¶m : friendFunction["parameters"]) { + if (!first) { + os << ", "; + } + os << param["type"].get() << " " + << param["name"].get(); + first = false; + } + os << ");\n"; + } +} - pipe = popen(command.data(), "r"); - while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) { - output += buffer.data(); +void CppOperatorOverloadGenerator::generate(const json &j, std::ostream &os) { + for (const auto &opOverload : j) { + os << " " << opOverload["return_type"].get() + << " operator" << opOverload["operator"].get() << "("; + bool first = true; + for (const auto ¶m : opOverload["parameters"]) { + if (!first) { + os << ", "; + } + os << param["type"].get() << " " + << param["name"].get(); + first = false; + } + os << ") {\n"; + os << " " << opOverload["body"].get() << "\n"; + os << " }\n"; } - return pclose(pipe); } -std::vector Compiler::findAvailableCompilers() { - std::vector availableCompilers; +void CppClassGenerator::generate(const json &j, std::ostream &os) { + os << "// Auto-generated C++ class\n"; + os << "// Generated by CppClassGenerator\n\n"; + + if (j.contains("namespace")) { + os << "namespace " << j["namespace"].get() << " {\n\n"; + } + + if (j.contains("template_parameters")) { + os << "template <"; + bool first = true; + for (const auto ¶m : j["template_parameters"]) { + if (!first) { + os << ", "; + } + os << "typename " << param.get(); + first = false; + } + os << ">\n"; + } - for (const auto& path : constants::COMPILER_PATHS) { - for (const auto& compiler : constants::COMMON_COMPILERS) { - std::filesystem::path compilerPath = - std::filesystem::path(path) / compiler; - if (std::filesystem::exists(compilerPath)) { - availableCompilers.push_back(compilerPath.string()); + std::string className = j["class_name"]; + os << "class " << className; + + if (j.contains("base_classes")) { + os << " : "; + bool first = true; + for (const auto &baseClass : j["base_classes"]) { + if (!first) { + os << ", "; } + os << "public " << baseClass.get(); + first = false; } } - return availableCompilers; + os << " {\npublic:\n"; + + CppMemberGenerator::generate(j["members"], os); + os << "\n"; + CppConstructorGenerator::generate(className, j["constructors"], os); + os << "\n"; + CppMethodGenerator::generate(j["methods"], os); + os << "\n"; + CppAccessorGenerator::generate(j["accessors"], os); + os << "\n"; + CppMutatorGenerator::generate(j["mutators"], os); + os << "\n"; + CppFriendFunctionGenerator::generate(j["friend_functions"], os); + os << "\n"; + CppOperatorOverloadGenerator::generate(j["operator_overloads"], os); + + os << "};\n"; + + if (j.contains("namespace")) { + os << "\n} // namespace " << j["namespace"].get() << "\n"; + } } + } // namespace lithium diff --git a/src/addon/compiler.hpp b/src/addon/compiler.hpp index 77d22ac4..62d1c809 100644 --- a/src/addon/compiler.hpp +++ b/src/addon/compiler.hpp @@ -1,29 +1,36 @@ -/* - * compiler.hpp +/** + * @file compiler.hpp * - * Copyright (C) 2023-2024 Max Qian + * @brief Contains the compiler definitions and declarations. + * + * This file includes all necessary definitions and declarations related to the + * compiler. It is part of the codebase developed by Max Qian. + * + * @copyright Copyright (C) 2023-2024 Max Qian + * @date 2023-03-29 */ -/************************************************* - -Date: 2023-3-29 - -Description: Compiler - -**************************************************/ - #ifndef LITHIUM_ADDON_COMPILER_HPP #define LITHIUM_ADDON_COMPILER_HPP -#include +#include #include #include -#include #include +#include "macro.hpp" + +#include "atom/type/json_fwd.hpp" +using json = nlohmann::json; namespace lithium { + +class CompilerImpl; + class Compiler { public: + Compiler(); + ~Compiler(); + /** * 编译 C++ 代码为共享库,并加载到内存中 * @param code 要编译的代码 @@ -32,57 +39,69 @@ class Compiler { * @param optionsFile 编译选项文件路径,默认为 "compile_options.json" * @return 编译是否成功 */ - [[nodiscard]] bool compileToSharedLibrary( + ATOM_NODISCARD auto compileToSharedLibrary( std::string_view code, std::string_view moduleName, std::string_view functionName, - std::string_view optionsFile = "compile_options.json"); + std::string_view optionsFile = "compile_options.json") -> bool; -private: /** - * 创建输出目录 - * @param outputDir 输出目录路径 + * 添加自定义编译选项 + * @param options 编译选项 */ - void createOutputDirectory(const std::filesystem::path& outputDir); + void addCompileOptions(const std::string &options); /** - * 语法检查 - * @param code 要编译的代码 - * @param compiler 编译器路径 - * @return 语法检查是否成功 + * 获取可用编译器列表 + * @return 编译器列表 */ - [[nodiscard]] bool syntaxCheck(std::string_view code, - std::string_view compiler); + ATOM_NODISCARD auto getAvailableCompilers() const + -> std::vector; - /** - * 编译代码 - * @param code 要编译的代码 - * @param compiler 编译器路径 - * @param compileOptions 编译选项 - * @param output 编译输出路径 - * @return 编译是否成功 - */ - [[nodiscard]] bool compileCode(std::string_view code, - std::string_view compiler, - std::string_view compileOptions, - const std::filesystem::path& output); +private: + std::unique_ptr impl_; +}; - /** - * 查找可用的编译器 - * @return 可用的编译器列表 - */ - [[nodiscard]] std::vector findAvailableCompilers(); +class CppMemberGenerator { +public: + static void generate(const json &j, std::ostream &os); +}; - /** - * 运行外部 shell 命令,并将标准输入输出流转发到命令的标准输入输出流中 - * @param command 要运行的命令 - * @param input 标准输入 - * @param output 标准输出 - * @return 命令运行的返回值 - */ - int runCommand(std::string_view command, std::string_view input, - std::string& output); +class CppConstructorGenerator { +public: + static void generate(const std::string &className, const json &j, + std::ostream &os); +}; + +class CppMethodGenerator { +public: + static void generate(const json &j, std::ostream &os); +}; + +class CppAccessorGenerator { +public: + static void generate(const json &j, std::ostream &os); +}; + +class CppMutatorGenerator { +public: + static void generate(const json &j, std::ostream &os); +}; + +class CppFriendFunctionGenerator { +public: + static void generate(const json &j, std::ostream &os); +}; - std::unordered_map cache_; +class CppOperatorOverloadGenerator { +public: + static void generate(const json &j, std::ostream &os); }; + +class CppClassGenerator { +public: + static void generate(const json &j, std::ostream &os); +}; + } // namespace lithium + #endif diff --git a/src/addon/component.hpp b/src/addon/component.hpp index 2b6f9d7c..60603b86 100644 --- a/src/addon/component.hpp +++ b/src/addon/component.hpp @@ -16,26 +16,22 @@ Description: Component Entry, which is used to describe the component. #define LITIHUM_ADDON_COMPONENT_HPP #include +#include namespace lithium { -class ComponentEntry { -public: - std::string m_name; - std::string m_main_entry; - std::string m_component_type; - std::string m_module_name; - std::string m_project_name; - - explicit ComponentEntry(const std::string &name, - const std::string &func_name, - const std::string &component_type, - const std::string &module_name, - const std::string &project_name) - : m_name(name), - m_main_entry(func_name), - m_component_type(component_type), - m_module_name(module_name), - m_project_name(project_name) {} +struct ComponentEntry { + std::string name; + std::string func_name; + std::string component_type; + std::string module_name; + std::vector dependencies; + + ComponentEntry(std::string name, std::string func_name, + std::string component_type, std::string module_name) + : name(std::move(name)), + func_name(std::move(func_name)), + component_type(std::move(component_type)), + module_name(std::move(module_name)) {} }; } // namespace lithium diff --git a/src/addon/dependency.cpp b/src/addon/dependency.cpp new file mode 100644 index 00000000..3a9aea5a --- /dev/null +++ b/src/addon/dependency.cpp @@ -0,0 +1,301 @@ +#include "dependency.hpp" +#include "version.hpp" + +#include +#include +#include +#include +#include + +#include "atom/error/exception.hpp" +#include "atom/log/loguru.hpp" +#include "atom/type/json.hpp" + +namespace lithium { +void DependencyGraph::addNode(const Node& node, const Version& version) { + adjList_.try_emplace(node); + incomingEdges_.try_emplace(node); + nodeVersions_[node] = version; +} + +void DependencyGraph::addDependency(const Node& from, const Node& to, + const Version& requiredVersion) { + if (nodeVersions_.find(to) != nodeVersions_.end() && + nodeVersions_[to] < requiredVersion) { + THROW_INVALID_ARGUMENT( + "Version requirement not satisfied for dependency " + from + + " -> " + to); + } + adjList_[from].insert(to); + incomingEdges_[to].insert(from); +} + +void DependencyGraph::removeNode(const Node& node) { + adjList_.erase(node); + incomingEdges_.erase(node); + for (auto& [key, neighbors] : adjList_) { + neighbors.erase(node); + } + for (auto& [key, sources] : incomingEdges_) { + sources.erase(node); + } +} + +void DependencyGraph::removeDependency(const Node& from, const Node& to) { + if (adjList_.find(from) != adjList_.end()) { + adjList_[from].erase(to); + } + if (incomingEdges_.find(to) != incomingEdges_.end()) { + incomingEdges_[to].erase(from); + } +} + +auto DependencyGraph::getDependencies(const Node& node) const + -> std::vector { + if (adjList_.find(node) != adjList_.end()) { + return {adjList_.at(node).begin(), adjList_.at(node).end()}; + } + return {}; +} + +auto DependencyGraph::getDependents(const Node& node) const + -> std::vector { + std::vector dependents; + for (const auto& [key, neighbors] : adjList_) { + if (neighbors.contains(node)) { + dependents.push_back(key); + } + } + return dependents; +} + +auto DependencyGraph::hasCycle() const -> bool { + std::unordered_set visited; + std::unordered_set recStack; + + for (const auto& [node, _] : adjList_) { + if (hasCycleUtil(node, visited, recStack)) { + return true; + } + } + return false; +} + +auto DependencyGraph::topologicalSort() const + -> std::optional> { + std::unordered_set visited; + std::stack stack; + for (const auto& [node, _] : adjList_) { + if (!visited.contains(node)) { + if (!topologicalSortUtil(node, visited, stack)) { + return std::nullopt; // Cycle detected + } + } + } + + std::vector sortedNodes; + while (!stack.empty()) { + sortedNodes.push_back(stack.top()); + stack.pop(); + } + return sortedNodes; +} + +auto DependencyGraph::getAllDependencies(const Node& node) const + -> std::unordered_set { + std::unordered_set allDependencies; + getAllDependenciesUtil(node, allDependencies); + return allDependencies; +} + +void DependencyGraph::loadNodesInParallel( + std::function loadFunction) const { + std::queue readyQueue; + std::mutex mtx; + std::condition_variable cv; + std::unordered_map inDegree; + std::unordered_set loadedNodes; + std::vector threads; + bool done = false; + + // Initialize in-degree and ready queue + for (const auto& [node, deps] : adjList_) { + inDegree[node] = + incomingEdges_.contains(node) ? incomingEdges_.at(node).size() : 0; + if (inDegree[node] == 0) { + readyQueue.push(node); + } + } + + auto worker = [&]() { + while (true) { + Node node; + { + std::unique_lock lock(mtx); + cv.wait(lock, [&] { return !readyQueue.empty() || done; }); + + if (done && readyQueue.empty()) { + return; + } + + node = readyQueue.front(); + readyQueue.pop(); + } + + loadFunction(node); + + { + std::unique_lock lock(mtx); + loadedNodes.insert(node); + + for (const auto& dep : adjList_.at(node)) { + inDegree[dep]--; + if (inDegree[dep] == 0) { + readyQueue.push(dep); + } + } + + if (readyQueue.empty() && + loadedNodes.size() == adjList_.size()) { + done = true; + cv.notify_all(); + } else { + cv.notify_all(); + } + } + } + }; + + int numThreads = std::thread::hardware_concurrency(); + threads.reserve(numThreads); + for (int i = 0; i < numThreads; ++i) { + threads.emplace_back(worker); + } +} + +auto DependencyGraph::hasCycleUtil( + const Node& node, std::unordered_set& visited, + std::unordered_set& recStack) const -> bool { + if (!visited.contains(node)) { + visited.insert(node); + recStack.insert(node); + + for (const auto& neighbour : adjList_.at(node)) { + if (!visited.contains(neighbour) && + hasCycleUtil(neighbour, visited, recStack)) { + return true; + } + if (recStack.contains(neighbour)) { + return true; + } + } + } + recStack.erase(node); + return false; +} + +auto DependencyGraph::topologicalSortUtil( + const Node& node, std::unordered_set& visited, + std::stack& stack) const -> bool { + visited.insert(node); + for (const auto& neighbour : adjList_.at(node)) { + if (!visited.contains(neighbour)) { + if (!topologicalSortUtil(neighbour, visited, stack)) { + return false; // Cycle detected + } + } + } + stack.push(node); + return true; +} + +void DependencyGraph::getAllDependenciesUtil( + const Node& node, std::unordered_set& allDependencies) const { + if (adjList_.contains(node)) { + for (const auto& neighbour : adjList_.at(node)) { + if (!allDependencies.contains(neighbour)) { + allDependencies.insert(neighbour); + getAllDependenciesUtil(neighbour, allDependencies); + } + } + } +} + +auto DependencyGraph::removeDuplicates(const std::vector& input) + -> std::vector { + std::unordered_set seen; + std::vector result; + + for (const auto& element : input) { + if (seen.insert(element).second) { + result.push_back(element); + } + } + + return result; +} + +auto DependencyGraph::resolveDependencies( + const std::vector& directories) -> std::vector { + DependencyGraph graph; + + for (const auto& dir : directories) { + std::string packagePath = dir + "/package.json"; + auto [package_name, deps] = parsePackageJson(packagePath); + + 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 (graph.hasCycle()) { + LOG_F(ERROR, "Circular dependency detected."); + return {}; + } + + auto sortedPackagesOpt = graph.topologicalSort(); + if (!sortedPackagesOpt) { + LOG_F(ERROR, "Failed to sort packages."); + return {}; + } + + return removeDuplicates(sortedPackagesOpt.value()); +} + +auto DependencyGraph::parsePackageJson(const std::string& path) + -> std::pair> { + std::ifstream file(path); + if (!file.is_open()) { + THROW_FAIL_TO_OPEN_FILE("Failed to open " + path); + } + + json packageJson; + try { + file >> packageJson; + } catch (const json::exception& e) { + THROW_JSON_PARSE_ERROR("Error parsing JSON in " + path + ": " + + e.what()); + } + + if (!packageJson.contains("name")) { + THROW_MISSING_ARGUMENT("Missing package name in " + path); + } + + std::string packageName = packageJson["name"]; + std::unordered_map deps; + + if (packageJson.contains("dependencies")) { + for (const auto& dep : packageJson["dependencies"].items()) { + deps[dep.key()] = Version::parse(dep.value().get()); + } + } + + file.close(); + return {packageName, deps}; +} +} // namespace lithium diff --git a/src/addon/dependency.hpp b/src/addon/dependency.hpp new file mode 100644 index 00000000..48b6cfdb --- /dev/null +++ b/src/addon/dependency.hpp @@ -0,0 +1,188 @@ +#ifndef LITHIUM_ADDON_DEPENDENCY_HPP +#define LITHIUM_ADDON_DEPENDENCY_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include "version.hpp" + +#include "atom/type/json_fwd.hpp" +using json = nlohmann::json; + +namespace lithium { +/** + * @brief A class that represents a directed dependency graph. + * + * This class allows for managing nodes and their dependencies, detecting + * cycles, performing topological sorting, and resolving dependencies for a + * given set of directories. + */ +class DependencyGraph { +public: + using Node = std::string; + + /** + * @brief Adds a node to the dependency graph. + * + * @param node The name of the node to be added. + */ + void addNode(const Node& node, const Version& version); + + /** + * @brief Adds a directed dependency from one node to another. + * + * This establishes a relationship where 'from' depends on 'to'. + * + * @param from The node that has a dependency. + * @param to The node that is being depended upon. + */ + void addDependency(const Node& from, const Node& to, + const Version& requiredVersion); + + /** + * @brief Removes a node from the dependency graph. + * + * This will also remove any dependencies associated with the node. + * + * @param node The name of the node to be removed. + */ + void removeNode(const Node& node); + + /** + * @brief Removes a directed dependency from one node to another. + * + * @param from The node that previously had a dependency. + * @param to The node that is being removed from the dependency list. + */ + void removeDependency(const Node& from, const Node& to); + + /** + * @brief Retrieves the direct dependencies of a node. + * + * @param node The node for which to get dependencies. + * @return A vector containing the names of the dependent nodes. + */ + auto getDependencies(const Node& node) const -> std::vector; + + /** + * @brief Retrieves the direct dependents of a node. + * + * @param node The node for which to get dependents. + * @return A vector containing the names of the dependents. + */ + auto getDependents(const Node& node) const -> std::vector; + + /** + * @brief Checks if the dependency graph contains a cycle. + * + * @return True if there is a cycle in the graph, false otherwise. + */ + auto hasCycle() const -> bool; + + /** + * @brief Performs a topological sort of the nodes in the dependency graph. + * + * @return An optional vector containing the nodes in topological order, or + * std::nullopt if a cycle is detected. + */ + auto topologicalSort() const -> std::optional>; + + /** + * @brief Retrieves all dependencies of a node, including transitive + * dependencies. + * + * @param node The node for which to get all dependencies. + * @return A set containing all dependency node names. + */ + auto getAllDependencies(const Node& node) const -> std::unordered_set; + + /** + * @brief Loads nodes in parallel using a specified loading function. + * + * This function applies a provided function to each node concurrently. + * + * @param loadFunction The function to apply to each node. + */ + void loadNodesInParallel( + std::function loadFunction) const; + + /** + * @brief Resolves dependencies for a given list of directories. + * + * This function analyzes the specified directories and determines their + * dependencies. + * + * @param directories A vector containing the paths of directories to + * resolve. + * @return A vector containing resolved dependency paths. + */ + auto resolveDependencies(const std::vector& directories) + -> std::vector; + +private: + std::unordered_map> + adjList_; ///< Adjacency list representation of the graph. + std::unordered_map> + incomingEdges_; ///< Map to track incoming edges for each node. + std::unordered_map + nodeVersions_; ///< Map to track node versions. + + /** + * @brief Utility function to check for cycles in the graph using DFS. + * + * @param node The current node being visited. + * @param visited Set of visited nodes. + * @param recStack Set of nodes currently in the recursion stack. + * @return True if a cycle is detected, false otherwise. + */ + auto hasCycleUtil(const Node& node, std::unordered_set& visited, + std::unordered_set& recStack) const -> bool; + + /** + * @brief Utility function to perform DFS for topological sorting. + * + * @param node The current node being visited. + * @param visited Set of visited nodes. + * @param stack The stack to hold the topological order. + * @return True if successful, false otherwise. + */ + auto topologicalSortUtil(const Node& node, + std::unordered_set& visited, + std::stack& stack) const -> bool; + + /** + * @brief Utility function to gather all dependencies of a node. + * + * @param node The node for which to collect dependencies. + * @param allDependencies Set to hold all found dependencies. + */ + void getAllDependenciesUtil( + const Node& node, std::unordered_set& allDependencies) const; + + /** + * @brief Removes duplicate entries from a vector of strings. + * + * @param input The input vector potentially containing duplicates. + * @return A vector containing unique entries from the input. + */ + static auto removeDuplicates(const std::vector& input) + -> std::vector; + + /** + * @brief Parses a package.json file to extract package name and + * dependencies. + * + * @param path The path to the package.json file. + * @return A pair containing the package name and its dependencies. + */ + static auto parsePackageJson(const Node& path) + -> std::pair>; +}; + +} // namespace lithium +#endif // LITHIUM_ADDON_DEPENDENCY_HPP diff --git a/src/addon/loader.cpp b/src/addon/loader.cpp index 2aae444d..a5a93a54 100644 --- a/src/addon/loader.cpp +++ b/src/addon/loader.cpp @@ -14,444 +14,412 @@ Description: C++ and Modules Loader #include "loader.hpp" -#include +#include #include -#include -#include #include -#include -#include -#include - -#include "atom/io/io.hpp" +#include "function/ffi.hpp" +#include "macro.hpp" #ifdef _WIN32 -#include -#include +// clang-format off +#include +#include +// clang-format on #else -#include #include +#include +#include +#include #endif +#include "atom/io/io.hpp" -namespace fs = std::filesystem; - -#ifdef _WIN32 -#define PATH_SEPARATOR "\\" -#else -#define PATH_SEPARATOR "/" -#endif +namespace lithium { -#define SET_CONFIG_VALUE(key) \ - config[dir.path().string()][#key] = module_config.value(#key, ""); +namespace internal { +auto replaceFilename(const std::string& path, + const std::string& newFilename) -> std::string { + size_t pos = path.find_last_of("/\\"); + if (pos == std::string::npos) { + return newFilename; + } + return path.substr(0, pos + 1) + newFilename; +} +} // namespace internal -namespace lithium { -ModuleLoader::ModuleLoader(const std::string &dir_name = "modules") { - DLOG_F(INFO, "C++ module manager loaded successfully."); +ModuleLoader::ModuleLoader(std::string dirName) { + DLOG_F(INFO, "Module manager {} loaded successfully.", dirName); } ModuleLoader::~ModuleLoader() { if (!modules_.empty()) { - if (!UnloadAllModules()) { + if (!unloadAllModules()) { LOG_F(ERROR, "Failed to unload all modules"); } modules_.clear(); } } -std::shared_ptr ModuleLoader::createShared() { +auto ModuleLoader::createShared() -> std::shared_ptr { return std::make_shared("modules"); } -std::shared_ptr ModuleLoader::createShared( - const std::string &dir_name = "modules") { - return std::make_shared(dir_name); +auto ModuleLoader::createShared(std::string dirName) + -> std::shared_ptr { + return std::make_shared(std::move(dirName)); } -bool ModuleLoader::LoadModule(const std::string &path, - const std::string &name) { - // dead lock if use std::lock_guard - // std::unique_lock lock(m_SharedMutex); - try { - // Max : The mod's name should be unique, so we check if it already - // exists - if (HasModule(name)) { - LOG_F(ERROR, "Module {} already loaded", name); - return false; - } - if (!atom::io::isFileExists(path)) { - LOG_F(ERROR, "Module {} does not exist", name); - return false; - } - // Create module info - std::shared_ptr mod_info = std::make_shared(); - // Load the library file - void *handle = LOAD_LIBRARY(path.c_str()); - if (!handle) { - LOG_F(ERROR, "Failed to load library {}: {}", path, LOAD_ERROR()); - return false; - } - mod_info->handle = handle; - mod_info->functions = loadModuleFunctions(name); - // Load infomation from the library - - // Store the library handle in handles_ map with the module name as key - // handles_[name] = handle; - modules_[name] = mod_info; - DLOG_F(INFO, "Loaded module : {}", name); - return true; - } catch (const std::exception &e) { - LOG_F(ERROR, "Failed to load library {}: {}", path, e.what()); +auto ModuleLoader::loadModule(const std::string& path, + const std::string& name) -> bool { + std::unique_lock lock(sharedMutex_); + if (hasModule(name)) { + LOG_F(ERROR, "Module {} already loaded", name); return false; } + + if (!std::filesystem::exists(path)) { + LOG_F(ERROR, "Module {} does not exist", name); + return false; + } + LOG_F(INFO, "Loading module: {} from {}", name, path); + auto modInfo = std::make_shared(); + modInfo->mLibrary = std::make_shared(path); + modules_[name] = modInfo; + + // TODO: Fix me + // modInfo->functions = loadModuleFunctions(name); + auto moduleDumpPath = internal::replaceFilename(path, "module_dump.json"); + if (!atom::io::isFileExists(moduleDumpPath)) { + std::ofstream out(moduleDumpPath); + json dump; + for (auto& func : modInfo->functions) { + json j; + j["name"] = func->name; + j["address"] = (uintptr_t)func->address; + j["parameters"] = func->parameters; + dump.push_back(j); + } + dump.dump(4); + LOG_F(INFO, "Dumped module functions to {}", moduleDumpPath); + } else { + LOG_F(WARNING, "Module dump file already exists, skipping"); + } + DLOG_F(INFO, "Loaded module: {}", name); + return true; } -std::vector> ModuleLoader::loadModuleFunctions( - const std::string &name) { +auto ModuleLoader::loadModuleFunctions(const std::string& name) + -> std::vector> { std::vector> funcs; + + // std::shared_lock lock(sharedMutex_); auto it = modules_.find(name); if (it == modules_.end()) { - LOG_F(ERROR, "Module {} not found", name); + LOG_F(ERROR, "Module not found: {}", name); + return funcs; + } + + void* handle = it->second->mLibrary->getHandle(); + if (handle == nullptr) { + LOG_F(ERROR, "Failed to get handle for module: {}", name); return funcs; } #ifdef _WIN32 - HMODULE handle = reinterpret_cast(it->second->handle); - - PIMAGE_DOS_HEADER dosHeader = reinterpret_cast(handle); - PIMAGE_NT_HEADERS ntHeader = reinterpret_cast( - reinterpret_cast(handle) + dosHeader->e_lfanew); - PIMAGE_EXPORT_DIRECTORY exportDir = - reinterpret_cast( - reinterpret_cast(handle) + - ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT] - .VirtualAddress); - - DWORD *nameTable = reinterpret_cast( - reinterpret_cast(handle) + exportDir->AddressOfNames); - WORD *ordinalTable = reinterpret_cast( - reinterpret_cast(handle) + exportDir->AddressOfNameOrdinals); - DWORD *functionTable = reinterpret_cast( - reinterpret_cast(handle) + exportDir->AddressOfFunctions); - - for (DWORD i = 0; i < exportDir->NumberOfNames; ++i) { - std::unique_ptr func = std::make_unique(); - func->name = reinterpret_cast( - reinterpret_cast(handle) + nameTable[i]); - func->address = reinterpret_cast( - reinterpret_cast(handle) + functionTable[ordinalTable[i]]); - // TODO funcs.push_back(std::construct_at(func.get())); - DLOG_F(INFO, "Function: {}", func->name); - - // Trying to get function's parameters, but there are some issues with - // it - DWORD functionRva = functionTable[ordinalTable[i]]; - PIMAGE_FUNCTION_ENTRY functionEntry = - reinterpret_cast( - reinterpret_cast(handle) + functionRva); - ULONG_PTR dwFunctionLength = - functionEntry->EndOfPrologue - functionEntry->StartingAddress; - - DWORD64 dwImageBase = reinterpret_cast(handle); - DWORD64 dwFunctionAddress = - dwImageBase + functionEntry->StartingAddress; - - const int maxParameters = 16; - BYTE parameters[maxParameters * sizeof(DWORD64)]; - DWORD cbParameterSize = sizeof(DWORD64) * maxParameters; - DWORD cbBytesRead; - if (ReadProcessMemory(GetCurrentProcess(), - reinterpret_cast(dwFunctionAddress), - parameters, cbParameterSize, - reinterpret_cast(&cbBytesRead))) { - BYTE *dwParameterPtr = parameters; - DWORD64 dwParameter = *reinterpret_cast(dwParameterPtr); - for (int j = 0; j < maxParameters; ++j) { - DWORD64 dwParameter = *dwParameterPtr; - - if (dwParameter == 0) { - break; - } + loadFunctionsWindows(handle, funcs); +#else + loadFunctionsUnix(funcs); +#endif - char parameterBuffer[256]; - sprintf_s(parameterBuffer, "%#010I64x", dwParameter); - funcs[i].get()->parameters.push_back(parameterBuffer); + return funcs; +} - ++dwParameterPtr; - } +#ifdef _WIN32 +void ModuleLoader::loadFunctionsWindows( + void* handle, std::vector>& funcs) { + HMODULE hModule = static_cast(handle); + ULONG size; + auto* exports = (PIMAGE_EXPORT_DIRECTORY)ImageDirectoryEntryToDataEx( + hModule, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &size, NULL); + + if (exports == nullptr) { + logError("No export directory found in module."); + return; + } + + auto* names = (DWORD*)((BYTE*)hModule + exports->AddressOfNames); + for (DWORD i = 0; i < exports->NumberOfNames; ++i) { + char* funcName = (char*)hModule + names[i]; + void* funcAddress = (void*)GetProcAddress(hModule, funcName); + if (funcAddress) { + auto func = std::make_unique(); + func->name = funcName; + func->address = funcAddress; + funcs.push_back(std::move(func)); + logInfo("Loaded function: " + func->name); + } else { + logError("Failed to load function: " + std::string(funcName)); } } +} #else - void *handle = it->second->handle; - +// TODO: Max: 现在加载函数信息还是有问题,主要集中在段错误的问题 +void ModuleLoader::loadFunctionsUnix( + std::vector>& funcs) { dl_iterate_phdr( - [](struct dl_phdr_info *info, size_t size, void *data) { - auto funcs = - static_cast> *>(data); - if (info->dlpi_name && strcmp(info->dlpi_name, "") != 0) { - DLOG_F(INFO, "Module: %s", info->dlpi_name); - - const ElfW(Addr) base_address = info->dlpi_addr; - const ElfW(Phdr) *phdr = info->dlpi_phdr; - const auto phnum = info->dlpi_phnum; - - for (int j = 0; j < phnum; ++j) { - if (phdr[j].p_type == PT_DYNAMIC) { - auto *dyn = reinterpret_cast( - base_address + phdr[j].p_vaddr); - ElfW(Sym) *symtab = nullptr; - char *strtab = nullptr; - - for (; dyn->d_tag != DT_NULL; ++dyn) { - if (dyn->d_tag == DT_SYMTAB) { - symtab = reinterpret_cast( - base_address + dyn->d_un.d_ptr); - } else if (dyn->d_tag == DT_STRTAB) { - strtab = reinterpret_cast( - base_address + dyn->d_un.d_ptr); - } - } + [](struct dl_phdr_info* info, size_t, void* data) { + auto* funcs = + static_cast>*>(data); + + for (int i = 0; i < info->dlpi_phnum; ++i) { + const ElfW(Phdr)* phdr = &info->dlpi_phdr[i]; + if (phdr->p_type != PT_DYNAMIC) { + continue; + } - if (symtab && strtab) { - for (ElfW(Sym) *sym = symtab; sym->st_name != 0; - ++sym) { - if (ELF32_ST_TYPE(sym->st_info) == STT_FUNC) { - const char *name = &strtab[sym->st_name]; - std::unique_ptr func = - std::make_unique(); - func->name = name; - func->address = reinterpret_cast( - base_address + sym->st_value); - funcs->push_back(std::move(func)); - DLOG_F(INFO, "Function: %s", name); + auto* dyn = reinterpret_cast(info->dlpi_addr + + phdr->p_vaddr); + ElfW(Sym)* symtab = nullptr; + char* strtab = nullptr; + size_t numSymbols = 0; + size_t symEntrySize = 0; + uint32_t* gnuHash = nullptr; + + // Parse the dynamic section to locate symbol and string tables + for (; dyn->d_tag != DT_NULL; ++dyn) { + switch (dyn->d_tag) { + case DT_SYMTAB: + symtab = reinterpret_cast( + info->dlpi_addr + dyn->d_un.d_ptr); + break; + case DT_STRTAB: + strtab = reinterpret_cast(info->dlpi_addr + + dyn->d_un.d_ptr); + break; + case DT_HASH: + if (dyn->d_un.d_ptr) { + auto* hash = reinterpret_cast( + info->dlpi_addr + dyn->d_un.d_ptr); + if (hash) { + numSymbols = + hash[1]; // The second entry is + // the number of symbols } } + break; + case DT_GNU_HASH: + if (dyn->d_un.d_ptr) { + gnuHash = reinterpret_cast( + info->dlpi_addr + dyn->d_un.d_ptr); + } + break; + case DT_SYMENT: + symEntrySize = dyn->d_un.d_val; + break; + } + } + + // Process GNU hash if available + /* + if (gnuHash) { + const uint32_t nbuckets = gnuHash[0]; + const uint32_t* buckets = gnuHash + 4 + gnuHash[2]; + const uint32_t* chains = buckets + nbuckets; + + // Calculate the number of symbols based on the GNU hash + // table + for (size_t b = 0; b < nbuckets; ++b) { + if (buckets[b] != 0) { + uint32_t chainIndex = buckets[b]; + while (!(chains[chainIndex] & 1)) { + ++chainIndex; + } + numSymbols = + std::max(numSymbols, + static_cast(chainIndex + 1)); } } } - } - return 0; - }, - funcs); + */ -#endif - return funcs; + // If hash-based methods fail, estimate from symbol table size + if (!numSymbols && symEntrySize && symtab) { + // Assume the table ends with DT_NULL or similar entry + for (ElfW(Sym)* sym = symtab; + sym->st_name != 0 || + ELF32_ST_TYPE(sym->st_info) != STT_NOTYPE; + ++sym) { + ++numSymbols; + } + } + + // Ensure valid symbol table and string table pointers + if (!symtab || !strtab || numSymbols == 0 || + symEntrySize == 0) { + continue; + } + + // Iterate over the symbol table + for (size_t j = 0; j < numSymbols; ++j) { + auto* sym = reinterpret_cast( + reinterpret_cast(symtab) + j * symEntrySize); + + // Check for valid function symbols + if (ELF32_ST_TYPE(sym->st_info) == STT_FUNC && + sym->st_name != 0) { + auto func = std::make_unique(); + func->name = &strtab[sym->st_name]; + func->address = reinterpret_cast( + info->dlpi_addr + sym->st_value); + funcs->push_back(std::move(func)); + } + } + } + return 0; // Continue iteration + }, + &funcs); } +#endif -bool ModuleLoader::UnloadModule(const std::string &name) { - // Check if the module is loaded and has a valid handle - // Max: Check before loading to avaid dead lock - if (!HasModule(name)) { - LOG_F(ERROR, "Module {} is not loaded", name); - return false; - }; - // TODO: Fix this, maybe use a lock - // std::unique_lock lock(m_SharedMutex); - try { - // Unload the library and remove its handle from handles_ map - int result = UNLOAD_LIBRARY(GetModule(name)->handle); - if (result != 0) { - LOG_F(ERROR, "Failed to unload module {}", name); - return false; - } +auto ModuleLoader::unloadModule(const std::string& name) -> bool { + std::unique_lock lock(sharedMutex_); + if (hasModule(name)) { modules_.erase(name); return true; - } catch (const std::exception &e) { - LOG_F(ERROR, "{}", e.what()); - return false; } + LOG_F(ERROR, "Module {} is not loaded", name); + return false; } -bool ModuleLoader::UnloadAllModules() { - std::unique_lock lock(m_SharedMutex); - for (auto entry : modules_) { - int result = UNLOAD_LIBRARY(entry.second->handle); - if (result != 0) { - LOG_F(ERROR, "Failed to unload module {}", entry.first); - return false; +auto ModuleLoader::unloadAllModules() -> bool { + std::unique_lock lock(sharedMutex_); + for (auto& [name, module] : modules_) { + if (!unloadModule(name)) { + LOG_F(ERROR, "Failed to unload module {}", name); } + LOG_F(INFO, "Unloaded module {}", name); } modules_.clear(); return true; } -bool ModuleLoader::CheckModuleExists(const std::string &name) const { - std::shared_lock lock(m_SharedMutex); - // Max : Directly check if the library exists seems to be a litle bit slow. - // May we use filesystem instead? - void *handle = LOAD_LIBRARY(name.c_str()); - if (handle == nullptr) { - LOG_F(ERROR, "Module {} does not exist.", name); - return false; +auto ModuleLoader::checkModuleExists(const std::string& name) const -> bool { + if (void* handle = LOAD_LIBRARY(name.c_str()); handle) { + UNLOAD_LIBRARY(handle); + return true; } - DLOG_F(INFO, "Module {} is existing.", name); - UNLOAD_LIBRARY(handle); - return true; + return false; } -std::shared_ptr ModuleLoader::GetModule( - const std::string &name) const { - std::shared_lock lock(m_SharedMutex); - auto it = modules_.find(name); - if (it == modules_.end()) { - return nullptr; +auto ModuleLoader::getModule(const std::string& name) const + -> std::shared_ptr { + std::shared_lock lock(sharedMutex_); + if (auto it = modules_.find(name); it != modules_.end()) { + return it->second; } - return it->second; + return nullptr; } -void *ModuleLoader::GetHandle(const std::string &name) const { - std::shared_lock lock(m_SharedMutex); - auto it = modules_.find(name); - if (it == modules_.end()) { - return nullptr; +auto ModuleLoader::getHandle(const std::string& name) const + -> std::shared_ptr { + std::shared_lock lock(sharedMutex_); + if (auto it = modules_.find(name); it != modules_.end()) { + return it->second->mLibrary; } - return it->second->handle; + return nullptr; } -bool ModuleLoader::HasModule(const std::string &name) const { - std::shared_lock lock(m_SharedMutex); - return modules_.count(name) > 0; +auto ModuleLoader::hasModule(const std::string& name) const -> bool { + // std::shared_lock lock(sharedMutex_); + return modules_.contains(name); } -bool ModuleLoader::EnableModule(const std::string &name) { - std::unique_lock lock(m_SharedMutex); - // Check if the module is loaded - if (!HasModule(name)) { - LOG_F(ERROR, "Module {} is not loaded", name); - return false; - } - std::shared_ptr mod = GetModule(name); - if (!mod->m_enabled.load()) { +auto ModuleLoader::enableModule(const std::string& name) -> bool { + std::unique_lock lock(sharedMutex_); + if (auto mod = getModule(name); mod && !mod->m_enabled.load()) { mod->m_enabled.store(true); - std::string disabled_file = mod->m_path; - std::string enabled_file = - disabled_file.substr(0, disabled_file.size() - 8); - if (CheckModuleExists(enabled_file)) { - if (UnloadModule(enabled_file)) { - std::rename(disabled_file.c_str(), enabled_file.c_str()); - return true; - } else { - return false; - } - } else { - LOG_F(ERROR, "Enabled file not found for module {}", name); - return false; - } + return true; } - return true; + return false; } -bool ModuleLoader::DisableModule(const std::string &name) { - std::unique_lock lock(m_SharedMutex); - // Check if the module is loaded - if (!HasModule(name)) { - LOG_F(ERROR, "Module {} is not loaded", name); - return false; - } - std::shared_ptr mod = GetModule(name); - if (mod->m_enabled.load()) { +auto ModuleLoader::disableModule(const std::string& name) -> bool { + std::unique_lock lock(sharedMutex_); + if (auto mod = getModule(name); mod && mod->m_enabled.load()) { mod->m_enabled.store(false); - std::string module_path = GetModulePath(name); - if (module_path.empty()) { - LOG_F(ERROR, "Module path not found for module {}", name); - return false; - } - std::string disabled_file = module_path + ".disabled"; - if (std::rename(module_path.c_str(), disabled_file.c_str()) == 0) { - modules_.erase(name); - return true; - } else { - LOG_F(ERROR, "Failed to disable module {}", name); - return false; - } - } - return true; -} - -bool ModuleLoader::IsModuleEnabled(const std::string &name) const { - std::shared_lock lock(m_SharedMutex); - if (!HasModule(name)) { - LOG_F(ERROR, "Module {} is not loaded", name); - return false; - } - if (GetModule(name)->m_enabled.load()) { return true; } return false; } -std::string ModuleLoader::GetModuleVersion(const std::string &name) { - std::shared_lock lock(m_SharedMutex); - if (HasModule(name)) { - return GetFunction(name, "GetVersion")(); +auto ModuleLoader::isModuleEnabled(const std::string& name) const -> bool { + std::shared_lock lock(sharedMutex_); + if (auto mod = getModule(name); mod) { + return mod->m_enabled.load(); } - return ""; + return false; } -std::string ModuleLoader::GetModuleDescription(const std::string &name) { - std::shared_lock lock(m_SharedMutex); - if (HasModule(name)) { - return GetFunction(name, "GetDescription")(); +auto ModuleLoader::getModuleVersion(const std::string& name) -> std::string { + if (auto getVersionFunc = getFunction(name, "getVersion"); + getVersionFunc) { + return getVersionFunc(); } return ""; } -std::string ModuleLoader::GetModuleAuthor(const std::string &name) { - std::shared_lock lock(m_SharedMutex); - if (HasModule(name)) { - return GetFunction(name, "GetAuthor")(); +auto ModuleLoader::getModuleDescription(const std::string& name) + -> std::string { + if (auto getDescriptionFunc = + getFunction(name, "getDescription"); + getDescriptionFunc) { + return getDescriptionFunc(); } return ""; } -std::string ModuleLoader::GetModuleLicense(const std::string &name) { - std::shared_lock lock(m_SharedMutex); - if (HasModule(name)) { - return GetFunction(name, "GetLicense")(); +auto ModuleLoader::getModuleAuthor(const std::string& name) -> std::string { + if (auto getAuthorFunc = getFunction(name, "getAuthor"); + getAuthorFunc) { + return getAuthorFunc(); } return ""; } -std::string ModuleLoader::GetModulePath(const std::string &name) { - std::shared_lock lock(m_SharedMutex); - auto it = modules_.find(name); - if (it != modules_.end()) { - Dl_info dl_info; - if (dladdr(it->second->handle, &dl_info) != 0) { - return dl_info.dli_fname; - } +auto ModuleLoader::getModuleLicense(const std::string& name) -> std::string { + if (auto getLicenseFunc = getFunction(name, "getLicense"); + getLicenseFunc) { + return getLicenseFunc(); } return ""; } -json ModuleLoader::GetModuleConfig(const std::string &name) { - if (HasModule(name)) { - return GetFunction(name, "GetConfig")(); +auto ModuleLoader::getModuleConfig(const std::string& name) -> json { + if (auto getConfigFunc = getFunction(name, "getConfig"); + getConfigFunc) { + return getConfigFunc(); } return {}; } -const std::vector ModuleLoader::GetAllExistedModules() const { - std::shared_lock lock(m_SharedMutex); - std::vector modules_name; - if (modules_.empty()) { - return modules_name; - } - for (auto module_ : modules_) { - modules_name.push_back(module_.first); +auto ModuleLoader::getAllExistedModules() const -> std::vector { + std::shared_lock lock(sharedMutex_); + std::vector moduleNames; + moduleNames.reserve(modules_.size()); + for (const auto& [name, _] : modules_) { + moduleNames.push_back(name); } - return modules_name; + return moduleNames; } -bool ModuleLoader::HasFunction(const std::string &name, - const std::string &function_name) { - std::shared_lock lock(m_SharedMutex); - auto handle_it = modules_.find(name); - if (handle_it == modules_.end()) { - LOG_F(ERROR, "Failed to find module {}", name); - return false; +auto ModuleLoader::hasFunction(const std::string& name, + const std::string& functionName) -> bool { + std::shared_lock lock(sharedMutex_); + if (auto it = modules_.find(name); it != modules_.end()) { + return it->second->mLibrary->hasFunction(functionName); } - return (LOAD_FUNCTION(handle_it->second->handle, function_name.c_str()) != - nullptr); + LOG_F(ERROR, "Failed to find module {}", name); + return false; } + } // namespace lithium diff --git a/src/addon/loader.hpp b/src/addon/loader.hpp index a9efd25b..0a19120d 100644 --- a/src/addon/loader.hpp +++ b/src/addon/loader.hpp @@ -15,346 +15,149 @@ Description: C++ and Modules Loader #ifndef LITHIUM_ADDON_LOADER_HPP #define LITHIUM_ADDON_LOADER_HPP -#include +#include #include #include +#include #include +#include +#include #include +#include "atom/log/loguru.hpp" +#include "atom/type/json.hpp" #include "module.hpp" -#if ENABLE_FASTHASH -#include "emhash/hash_table8.hpp" -#else -#include -#endif +using json = nlohmann::json; -// Max: There we use dlfcn-win to load shared library on windows.So we don't -// need to use Windows Native API. -/* -#ifdef _WIN32 -#include -#define MODULE_HANDLE HMODULE -#define LOAD_LIBRARY(p) LoadLibrary(p) -#define LOAD_SHARED_LIBRARY(file, size) LoadLibraryA(NULL) -#define UNLOAD_LIBRARY(p) FreeLibrary(p) -#define LOAD_ERROR() GetLastError() -#define LOAD_FUNCTION(handle, name) GetProcAddress(handle, name) -#elif defined(__APPLE__) || defined(__linux__) -*/ -#include -#include -#include -#define MODULE_HANDLE void * -#define LOAD_LIBRARY(p) dlopen(p, RTLD_LAZY | RTLD_GLOBAL) -#define LOAD_SHARED_LIBRARY(file, size) dlopen(nullptr, RTLD_LAZY | RTLD_GLOBAL) +#define MODULE_HANDLE void* +// #define LOAD_LIBRARY(p) dlopen(p, RTLD_LAZY | RTLD_GLOBAL) +#define LOAD_LIBRARY(p) dlopen(p, RTLD_LAZY) #define UNLOAD_LIBRARY(p) dlclose(p) #define LOAD_ERROR() dlerror() #define LOAD_FUNCTION(handle, name) dlsym(handle, name) -/* -#endif -*/ - -#include "atom/log/loguru.hpp" -#include "atom/type/json.hpp" -#include "error/error_code.hpp" - -using json = nlohmann::json; namespace lithium { -/** - * @brief 模块加载器类。 - * - * @details 模块加载器类,用于加载模块,提供模块的注册和卸载功能。 - */ class ModuleLoader { public: - /** - * @brief 使用给定的目录名称和线程管理器参数构造 ModuleLoader 对象。 - * - * @param dir_name 模块所在的目录名称。 - * @param threadManager 线程管理器的共享指针。 - */ - explicit ModuleLoader(const std::string &dir_name); - - /** - * @brief 析构函数,释放 ModuleLoader 对象。 - */ + explicit ModuleLoader(std::string dirName); ~ModuleLoader(); - // ------------------------------------------------------------------- - // Common methods - // ------------------------------------------------------------------- - - /** - * @brief 使用默认参数创建一个共享的 ModuleLoader 对象。 - * @return 新创建的共享 ModuleLoader 对象。 - */ - static std::shared_ptr createShared(); - - /** - * @brief 使用给定的目录名称和线程管理器参数创建一个共享的 ModuleLoader - * 指针对象。 - * - * @param dir_name 模块所在的目录名称。 - * @param threadManager 线程管理器的共享指针。 - * @return 新创建的共享 ModuleLoader 指针对象。 - */ - static std::shared_ptr createShared( - const std::string &dir_name); - - // ------------------------------------------------------------------- - // Module methods - // ------------------------------------------------------------------- - - /** - * @brief Loads a dynamic module from the given path. - * - * This function loads a dynamic module from the given path. If the loading - * is successful, it returns true and saves the handle to the module in the - * modules_ map. If the loading fails, it returns false and logs an error - * message. - * - * @param[in] path The path of the dynamic module to load. - * @param[in] name The name of the dynamic module. - * @return true if the loading is successful, false otherwise. - */ - bool LoadModule(const std::string &path, const std::string &name); - - /** - * @brief Loads all functions from the given module. - * - * This function loads all functions from the given module. If the loading - * is successful, it returns a vector of FunctionInfo objects. If the - * loading fails, it returns an empty vector and logs an error message. - * - * @param[in] name The name of the dynamic module. - * @return A vector of FunctionInfo objects. - */ - std::vector> loadModuleFunctions( - const std::string &name); + static auto createShared() -> std::shared_ptr; + static auto createShared(std::string dirName) + -> std::shared_ptr; + + auto loadModule(const std::string& path, const std::string& name) -> bool; + auto unloadModule(const std::string& name) -> bool; + auto unloadAllModules() -> bool; + auto hasModule(const std::string& name) const -> bool; + auto getModule(const std::string& name) const + -> std::shared_ptr; + auto enableModule(const std::string& name) -> bool; + auto disableModule(const std::string& name) -> bool; + auto isModuleEnabled(const std::string& name) const -> bool; + auto getModuleVersion(const std::string& name) -> std::string; + auto getModuleDescription(const std::string& name) -> std::string; + auto getModuleAuthor(const std::string& name) -> std::string; + auto getModuleLicense(const std::string& name) -> std::string; + auto getModuleConfig(const std::string& name) -> json; + auto getAllExistedModules() const -> std::vector; - /** - * @brief 卸载指定名称的动态库 - * - * @param filename [in] 要卸载的动态库的文件名(包括扩展名) - * @return true 动态库卸载成功 - * @return false 动态库卸载失败 - */ - bool UnloadModule(const std::string &name); - - /** - * @brief 卸载所有动态库 - * - * @return true 所有动态库卸载成功 - * @return false 所有动态库卸载失败 - */ - bool UnloadAllModules(); - - /** - * @brief 判断指定名称的模块是否存在 - * - * @param name 模块名称 - * @return true 模块存在 - * @return false 模块不存在 - */ - bool HasModule(const std::string &name) const; - - /** - * @brief 获取指定名称的模块 - * - * @param name 模块名称 - * @return std::shared_ptr 模块指针 - */ - std::shared_ptr GetModule(const std::string &name) const; - - /** - * @brief 检查指定名称的模块是否存在 - * - * @param name 模块名称 - * @return true 模块存在 - * @return false 模块不存在 - */ - bool CheckModuleExists(const std::string &name) const; - - /** - * @brief 允许指定模块 - * - * @param name 模块名称 - * @return true 成功允许模块 - * @return false 允许模块失败 - */ - bool EnableModule(const std::string &name); - - /** - * @brief 禁用指定模块 - * - * @param name 模块名称 - * @return true 成功禁用模块 - * @return false 禁用模块失败 - */ - bool DisableModule(const std::string &name); - - /* - * @brief 判断指定模块是否被允许 - * @param name 模块名称 - * @return true 指定模块被允许 - * @return false 指定模块未被允许 - */ - bool IsModuleEnabled(const std::string &name) const; - - // ------------------------------------------------------------------- - // Functions methods - // ------------------------------------------------------------------- - - /** - * @brief 获取指定模块中的函数指针 - * - * @tparam T 函数指针类型 - * @param name 模块名称 - * @param function_name 函数名称 - * @return T 返回函数指针,如果获取失败则返回nullptr - */ template - T GetFunction(const std::string &name, const std::string &function_name); - - /** - * @brief 判断指定模块中的函数是否存在 - * - * @param name 模块名称 - * @param function_name 函数名称 - * @return true 函数存在 - * @return false 函数不存在 - */ - bool HasFunction(const std::string &name, const std::string &function_name); + auto getFunction(const std::string& name, + const std::string& functionName) -> std::function; - /** - * @brief 从指定模块中获取实例对象 - * - * @tparam T 实例对象类型 - * @param name 模块名称 - * @param config 实例对象的配置参数 - * @param symbol_name 获取实例对象的符号名称 - * @return std::shared_ptr - * 返回实例对象的智能指针,如果获取失败则返回nullptr - */ template - std::shared_ptr GetInstance(const std::string &name, const json &config, - const std::string &symbol_name); + auto getInstance(const std::string& name, const json& config, + const std::string& symbolName) -> std::shared_ptr; - /** - * @brief 从指定模块中获取实例对象 - * - * @tparam T 实例对象类型 - * @param name 模块名称 - * @param config 实例对象的配置参数 - * @param instance_function_name 获取实例对象的函数名称 - * @return std::unique_ptr - * 返回实例对象的智能指针,如果获取失败则返回nullptr - */ template - std::unique_ptr GetUniqueInstance( - const std::string &name, const json &config, - const std::string &instance_function_name); + auto getUniqueInstance(const std::string& name, const json& config, + const std::string& instanceFunctionName) + -> std::unique_ptr; - /** - * @brief 获取指定模块的任务实例指针 - * - * @tparam T 任务类型 - * @param name 模块名称 - * @param config 配置信息 - * @param instance_function_name 实例化函数名称 - * @return std::shared_ptr 任务实例指针 - * std::shared_ptr task = GetInstancePointer(name, - * config, "GetTaskInstance"); std::shared_ptr device = - * GetInstancePointer(name, config, "GetDeviceInstance"); - * std::shared_ptr plugin = GetInstancePointer(name, config, - * "GetPluginInstance"); - */ template - std::shared_ptr GetInstancePointer( - const std::string &name, const json &config, - const std::string &instance_function_name); - -public: - /** - * @brief 获取给定名称的句柄。 - * - * @param name 句柄名称。 - * @return 对应名称的句柄指针,如果未找到则返回空指针。 - * @note - * 该函数不检查模块是否被允许。这个函数的使用其实是很危险的,不建议暴露到模组或者脚本中被随意调用。 - */ - void *GetHandle(const std::string &name) const; - - // --------------------------------------------------------------------- - // Get Module Info - // --------------------------------------------------------------------- + auto getInstancePointer(const std::string& name, const json& config, + const std::string& instanceFunctionName) + -> std::shared_ptr; - /** - * @brief 获取指定模块的版本号 - * @param name 模块名称 - * @return 模块版本号 - */ - std::string GetModuleVersion(const std::string &name); + auto hasFunction(const std::string& name, + const std::string& functionName) -> bool; - /** - * @brief 获取指定模块的描述信息 - * @param name 模块名称 - * @return 模块描述信息 - */ - std::string GetModuleDescription(const std::string &name); - - /** - * @brief 获取指定模块的作者信息 - * @param name 模块名称 - * @return 模块作者信息 - */ - std::string GetModuleAuthor(const std::string &name); - - /** - * @brief 获取指定模块的许可证信息 - * @param name 模块名称 - * @return 模块许可证信息 - */ - std::string GetModuleLicense(const std::string &name); - - /** - * @brief 获取给定模块名称的模块路径。 - * - * @param name 模块名称。 - * @return 对应模块名称的模块路径。 - */ - std::string GetModulePath(const std::string &name); - - /** - * @brief 获取给定模块名称的模块配置。 - * - * @param name 模块名称。 - * @return 对应模块名称的模块配置。 - */ - json GetModuleConfig(const std::string &name); +private: + std::unordered_map> modules_; + mutable std::shared_mutex sharedMutex_; - /** - * @brief 获取所有存在的模块名称。 - * - * @return 存在的模块名称的向量。 - */ - const std::vector GetAllExistedModules() const; + auto loadModuleFunctions(const std::string& name) + -> std::vector>; + auto getHandle(const std::string& name) const + -> std::shared_ptr; + auto checkModuleExists(const std::string& name) const -> bool; -private: -#if ENABLE_FASTHASH - emhash8::HashMap> - modules_; // 模块哈希表 +#ifdef _WIN32 + void loadFunctionsWindows( + void* handle, std::vector>& funcs); #else - std::unordered_map> - modules_; // 模块哈希表 + void loadFunctionsUnix(std::vector>& funcs); #endif - - mutable std::shared_mutex m_SharedMutex; }; -} // namespace lithium -#include "loader.inl" +template +auto ModuleLoader::getFunction(const std::string& name, + const std::string& functionName) + -> std::function { + std::shared_lock lock(sharedMutex_); + if (auto it = modules_.find(name); it != modules_.end()) { + if (auto funcPtr = + it->second->mLibrary->getFunction(functionName.c_str()); + funcPtr) { + return funcPtr; + } + LOG_F(ERROR, "Failed to get symbol {} from module {}: {}", functionName, + name, LOAD_ERROR()); + } else { + LOG_F(ERROR, "Failed to find module {}", name); + } + return nullptr; +} + +template +auto ModuleLoader::getInstance(const std::string& name, const json& config, + const std::string& symbolName) + -> std::shared_ptr { + if (auto getInstanceFunc = + getFunction(const json&)>(name, symbolName); + getInstanceFunc) { + return getInstanceFunc(config); + } + if (auto getInstanceFunc = + getFunction()>(name, symbolName); + getInstanceFunc) { + return getInstanceFunc(); + } + + return nullptr; +} + +template +auto ModuleLoader::getUniqueInstance( + const std::string& name, const json& config, + const std::string& instanceFunctionName) -> std::unique_ptr { + if (auto getInstanceFunc = getFunction(const json&)>( + name, instanceFunctionName); + getInstanceFunc) { + return getInstanceFunc(config); + } + return nullptr; +} + +template +auto ModuleLoader::getInstancePointer( + const std::string& name, const json& config, + const std::string& instanceFunctionName) -> std::shared_ptr { + return getInstance(name, config, instanceFunctionName); +} -#endif +} // namespace lithium + +#endif // LITHIUM_ADDON_LOADER_HPP diff --git a/src/addon/loader.inl b/src/addon/loader.inl deleted file mode 100644 index 1f11786f..00000000 --- a/src/addon/loader.inl +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef LITHIUM_ADDON_LOADER_INL -#define LITHIUM_ADDON_LOADER_INL - -#include "loader.hpp" - -namespace lithium { - -template -T ModuleLoader::GetFunction(const std::string &name, - const std::string &function_name) { - std::shared_lock lock(m_SharedMutex); - auto handle_it = modules_.find(name); - if (handle_it == modules_.end()) { - LOG_F(ERROR, "Failed to find module {}", name); - return nullptr; - } - - auto func_ptr = reinterpret_cast( - LOAD_FUNCTION(handle_it->second->handle, function_name.c_str())); - - if (!func_ptr) { - LOG_F(ERROR, "Failed to get symbol {} from module {}: {}", - function_name, name, dlerror()); - return nullptr; - } - - return func_ptr; -} - -template -std::shared_ptr ModuleLoader::GetInstance(const std::string &name, - const json &config, - const std::string &symbol_name) { - std::shared_lock lock(m_SharedMutex); - if (!HasModule(name)) { - LOG_F(ERROR, "Failed to find module {}", name); - return nullptr; - } - auto get_instance_func = - GetFunction (*)(const json &)>(name, symbol_name); - if (!get_instance_func) { - LOG_F(ERROR, "Failed to get symbol {} from module {}: {}", symbol_name, - name, dlerror()); - return nullptr; - } - - return get_instance_func(config); -} - -template -std::unique_ptr ModuleLoader::GetUniqueInstance( - const std::string &name, const json &config, - const std::string &instance_function_name) { - std::shared_lock lock(m_SharedMutex); - if (!HasModule(name)) { - LOG_F(ERROR, "Failed to find module {}", name); - return nullptr; - } - auto get_instance_func = GetFunction (*)(const json &)>( - name, instance_function_name); - if (!get_instance_func) { - LOG_F(ERROR, "Failed to get symbol {} from module {}: {}", - instance_function_name, name, dlerror()); - return nullptr; - } - - return get_instance_func(config); -} - -template -std::shared_ptr ModuleLoader::GetInstancePointer( - const std::string &name, const json &config, - const std::string &instance_function_name) { - return GetInstance(name, config, instance_function_name); -} - -} // namespace lithium - -#endif diff --git a/src/addon/manager.cpp b/src/addon/manager.cpp index d439ce4d..b228bcbf 100644 --- a/src/addon/manager.cpp +++ b/src/addon/manager.cpp @@ -4,64 +4,90 @@ * Copyright (C) 2023-2024 Max Qian */ -/************************************************* - -Date: 2024-1-4 - -Description: Component Manager (the core of the plugin system) - -**************************************************/ - #include "manager.hpp" +#include +#include +#include +#include +#include +#include +#include + #include "addons.hpp" #include "compiler.hpp" -// #include "finder.hpp" +#include "component.hpp" +#include "components/registry.hpp" #include "loader.hpp" +#include "macro.hpp" #include "sandbox.hpp" -#include "sort.hpp" +#include "template/standalone.hpp" #include "atom/error/exception.hpp" +#include "atom/function/global_ptr.hpp" #include "atom/io/io.hpp" #include "atom/log/loguru.hpp" -#include "atom/function/global_ptr.hpp" -#include "atom/utils/ranges.hpp" +#include "atom/system/env.hpp" +#include "atom/system/process.hpp" +#include "atom/type/json.hpp" #include "atom/utils/string.hpp" +#include "system/command.hpp" #include "utils/constant.hpp" #include "utils/marco.hpp" -#include - -#define IS_ARGUMENT_EMPTY() \ - if (params.is_null()) { \ - return false; \ - } - -#define GET_ARGUMENT_C(name, type) \ - if (!params.contains(#name)) { \ - LOG_F(ERROR, "{}: Missing arguments: {}", __func__, #name); \ - return false; \ - } \ - type name = params[#name].get(); +#if defined(_WIN32) || defined(_WIN64) +// clang-format off +#include +#include +#include +#define pipe _pipe +#define popen _popen +#define pclose _pclose +// clang-format on +#else +#include +#include +#include +#include +#include +#endif namespace lithium { -ComponentManager::ComponentManager() : m_Sandbox(nullptr), m_Compiler(nullptr) { - m_ModuleLoader = - GetWeakPtr(constants::LITHIUM_MODULE_LOADER); - CHECK_WEAK_PTR_EXPIRED(m_ModuleLoader, - "load module loader from gpm: lithium.addon.loader"); - m_Env = GetWeakPtr(constants::LITHIUM_UTILS_ENV); - CHECK_WEAK_PTR_EXPIRED(m_Env, "load env from gpm: lithium.utils.env"); - m_AddonManager = - GetWeakPtr(constants::LITHIUM_ADDON_MANAGER); - CHECK_WEAK_PTR_EXPIRED(m_AddonManager, - "load addon manager from gpm: lithium.addon.addon"); - // Initialize sandbox and compiler, these are not shared objects - m_Sandbox = std::make_unique(); - m_Compiler = std::make_unique(); - - if (!Initialize()) { + +class ComponentManagerImpl { +public: + std::weak_ptr moduleLoader; + std::weak_ptr env; + std::unique_ptr sandbox; + std::unique_ptr compiler; + std::weak_ptr addonManager; + std::unordered_map> + componentEntries; + std::weak_ptr processManager; + std::unordered_map componentInfos; + std::unordered_map> components; + std::string modulePath; + DependencyGraph dependencyGraph; + std::mutex mutex; + + ComponentManagerImpl() = default; + ~ComponentManagerImpl() = default; +}; + +ComponentManager::ComponentManager() + : impl_(std::make_unique()) { + impl_->moduleLoader = + GetWeakPtr(constants::LITHIUM_MODULE_LOADER); + impl_->env = GetWeakPtr(constants::LITHIUM_UTILS_ENV); + impl_->addonManager = + GetWeakPtr(constants::LITHIUM_ADDON_MANAGER); + impl_->processManager = GetWeakPtr( + constants::LITHIUM_PROCESS_MANAGER); + impl_->sandbox = std::make_unique(); + impl_->compiler = std::make_unique(); + + if (!initialize()) { LOG_F(ERROR, "Failed to initialize component manager"); THROW_EXCEPTION("Failed to initialize component manager"); } @@ -70,295 +96,354 @@ ComponentManager::ComponentManager() : m_Sandbox(nullptr), m_Compiler(nullptr) { ComponentManager::~ComponentManager() {} -bool ComponentManager::Initialize() { - // Check if the module path is valid or reset by the user - // Default path is ./modules - const std::string &module_path = m_Env.lock()->getEnv( - constants::ENV_VAR_MODULE_PATH, constants::MODULE_FOLDER); - m_module_path = m_Env.lock()->getEnv(constants::ENV_VAR_MODULE_PATH, - constants::MODULE_FOLDER); - - // make a loading list of modules - const std::vector &qualified_subdirs = - resolveDependencies(getQualifiedSubDirs(module_path)); - if (qualified_subdirs.empty()) { +auto ComponentManager::initialize() -> bool { + // std::lock_guard lock(impl_->mutex); + // Max: 优先加载内置模组 + Registry::instance().initializeAll(); + for (const auto& name : Registry::instance().getAllComponentNames()) { + LOG_F(INFO, "Registering component: {}", name); + } + for (const auto& component : Registry::instance().getAllComponents()) { + impl_->components[component->getName()] = component; + impl_->componentInfos[component->getName()] = json(); + impl_->componentEntries[component->getName()] = + std::make_shared(component->getName(), "builtin", + "embed", "main"); + } + + auto envLock = impl_->env.lock(); + if (!envLock) { + LOG_F(ERROR, "Failed to lock environment"); + return false; + } + + impl_->modulePath = envLock->getEnv(constants::ENV_VAR_MODULE_PATH, + constants::MODULE_FOLDER); + + for (const auto& dir : getQualifiedSubDirs(impl_->modulePath)) { + LOG_F(INFO, "Found module: {}", dir); + } + auto qualifiedSubdirs = impl_->dependencyGraph.resolveDependencies( + getQualifiedSubDirs(impl_->modulePath)); + if (qualifiedSubdirs.empty()) { LOG_F(INFO, "No modules found, just skip loading modules"); return true; } - LOG_F(INFO, "Loading modules from: {}", m_module_path); - // List all of the available modules folders -#if ENABLE_DEBUG - LOG_F(INFO, "Available modules:"); - for (const auto &dir : qualified_subdirs) { - LOG_F(INFO, "{}", dir); + + LOG_F(INFO, "Loading modules from: {}", impl_->modulePath); + + auto addonManagerLock = impl_->addonManager.lock(); + if (!addonManagerLock) { + LOG_F(ERROR, "Failed to lock addon manager"); + return false; } -#endif - for (const auto &dir : qualified_subdirs) { - std::filesystem::path path = std::filesystem::path(module_path) / dir; + for (const auto& dir : qualifiedSubdirs) { + std::filesystem::path path = + std::filesystem::path(impl_->modulePath) / dir; - if (!m_AddonManager.lock()->addModule(path, dir)) { + if (!addonManagerLock->addModule(path, dir)) { LOG_F(ERROR, "Failed to load module: {}", path.string()); continue; } - // Get addon's package.json - const json &addon_info = m_AddonManager.lock()->getModule(dir); - if (!addon_info.is_object() || !addon_info.contains("name") || - !addon_info["name"].is_string()) { + + const auto& addonInfo = addonManagerLock->getModule(dir); + if (!addonInfo.is_object() || !addonInfo.contains("name") || + !addonInfo["name"].is_string()) { LOG_F(ERROR, "Invalid module name: {}", path.string()); continue; } - auto addon_name = addon_info["name"].get(); - LOG_F(INFO, "Start loading addon: {}", addon_name); - // Check if the addon info is valid - if (!addon_info.contains("modules")) { + + auto addonName = addonInfo["name"].get(); + LOG_F(INFO, "Start loading addon: {}", addonName); + + if (!addonInfo.contains("modules") || + !addonInfo["modules"].is_array()) { LOG_F(ERROR, - "Failed to load module {}: Missing modules field in module " - "info", - path.string()); - m_AddonManager.lock()->removeModule(dir); - continue; - } - if (addon_info["modules"].is_null() || - !addon_info["modules"].is_array()) { - LOG_F(ERROR, "Failed to load module {}: Modules field is null", + "Failed to load module {}: Missing or invalid modules field", path.string()); - m_AddonManager.lock()->removeModule(dir); + addonManagerLock->removeModule(dir); continue; } - // loading - for (const auto &component_info : - addon_info["modules"].get()) { - if (component_info.is_null() || !component_info.contains("name") || - !component_info.contains("entry") || - !component_info["name"].is_string() || - !component_info["entry"].is_string()) { + + for (const auto& componentInfo : addonInfo["modules"]) { + if (!componentInfo.is_object() || !componentInfo.contains("name") || + !componentInfo.contains("entry") || + !componentInfo["name"].is_string() || + !componentInfo["entry"].is_string()) { LOG_F(ERROR, "Failed to load module {}/{}: Invalid component info", - path.string(), component_info.dump()); + path.string(), componentInfo.dump()); continue; } - auto component_name = component_info["name"].get(); - auto entry = component_info["entry"].get(); - auto dependencies = component_info.contains("dependencies") - ? component_info["dependencies"] - .get>() - : std::vector(); - auto module_path = - path / (component_name + std::string(constants::LIB_EXTENSION)); - auto component_full_name = addon_name + "." + component_name; - if (!loadComponentInfo(path.string(), component_full_name)) { + + auto componentName = componentInfo["name"].get(); + auto entry = componentInfo["entry"].get(); + auto dependencies = + componentInfo.value("dependencies", std::vector{}); + auto modulePath = + path / (componentName + std::string(constants::LIB_EXTENSION)); + std::string componentFullName; + componentFullName.reserve(addonName.length() + + componentName.length() + + 1); // 预留足够空间 + componentFullName.append(addonName).append(".").append( + componentName); + + if (!loadComponentInfo(path.string(), componentFullName)) { LOG_F(ERROR, "Failed to load addon package.json {}/{}", - path.string(), component_full_name); + path.string(), componentFullName); return false; } - if (!loadSharedComponent(component_name, addon_name, - module_path.string(), entry, - dependencies)) { + + if (!loadSharedComponent(componentName, addonName, path.string(), + entry, dependencies)) { LOG_F(ERROR, "Failed to load module {}/{}", path.string(), - component_name); - // Max: We will directly throw a exception here, just like what - // Minecraft does - THROW_EXCEPTION("Failed to load module", component_name); + componentName); + THROW_RUNTIME_ERROR("Failed to load module: " + componentName); } } } return true; } -bool ComponentManager::Destroy() { return true; } +auto ComponentManager::destroy() -> bool { + std::lock_guard lock(impl_->mutex); + // 销毁组件管理器的逻辑 + impl_->components.clear(); + impl_->componentInfos.clear(); + impl_->componentEntries.clear(); + return true; +} -std::shared_ptr ComponentManager::createShared() { +auto ComponentManager::createShared() -> std::shared_ptr { return std::make_shared(); } -std::vector ComponentManager::getFilesInDir( - const std::string &path) { +auto ComponentManager::scanComponents(const std::string& path) + -> std::vector { + std::vector foundComponents; + try { + auto subDirs = getQualifiedSubDirs(path); + for (const auto& subDir : subDirs) { + auto componentFiles = getFilesInDir(subDir); + foundComponents.insert(foundComponents.end(), + componentFiles.begin(), + componentFiles.end()); + } + } catch (const std::exception& e) { + LOG_F(ERROR, "Failed to scan components: {}", e.what()); + } + return foundComponents; +} + +auto ComponentManager::getFilesInDir(const std::string& path) + -> std::vector { std::vector files; - for (const auto &entry : std::filesystem::directory_iterator(path)) { - if (!entry.is_directory()) { - files.push_back(entry.path().filename().string()); + try { + for (const auto& entry : std::filesystem::directory_iterator(path)) { + if (!entry.is_directory()) { + files.push_back(entry.path().filename().string()); + } } + } catch (const std::filesystem::filesystem_error& e) { + LOG_F(ERROR, "Error accessing directory {}: {}", path, e.what()); } return files; } -std::vector ComponentManager::getQualifiedSubDirs( - const std::string &path) { +auto ComponentManager::getQualifiedSubDirs(const std::string& path) + -> std::vector { std::vector qualifiedSubDirs; - for (const auto &entry : std::filesystem::directory_iterator(path)) { - if (entry.is_directory()) { - bool hasJson = false, hasLib = false; - std::vector files = - getFilesInDir(entry.path().string()); - for (const auto &fileName : files) { - if (fileName == constants::PACKAGE_NAME) { - hasJson = true; - } else if (fileName.size() > 4 && - fileName.substr(fileName.size() - 4) == - constants::LIB_EXTENSION) { - hasLib = true; + try { + for (const auto& entry : std::filesystem::directory_iterator(path)) { + if (entry.is_directory()) { + bool hasJson = false; + bool hasLib = false; + LOG_F(INFO, "Checking directory: {}", entry.path().string()); + auto files = getFilesInDir(entry.path().string()); + for (const auto& fileName : files) { + if (fileName == constants::PACKAGE_NAME) { + hasJson = true; + } else if (fileName.size() > 4 && +#ifdef _WIN32 + fileName.substr(fileName.size() - 4) == + constants::LIB_EXTENSION +#else + fileName.substr(fileName.size() - 3) == + constants::LIB_EXTENSION +#endif + ) { + hasLib = true; + } + LOG_F(INFO, "Dir {} has json: {}, lib: {}", + entry.path().string(), hasJson, hasLib); + if (hasJson && hasLib) { + break; + } } if (hasJson && hasLib) { - break; + qualifiedSubDirs.push_back(entry.path().string()); } } - if (hasJson && hasLib) { - qualifiedSubDirs.push_back(entry.path().string()); - } } + } catch (const std::filesystem::filesystem_error& e) { + LOG_F(ERROR, "Error accessing directory {}: {}", path, e.what()); } return qualifiedSubDirs; } -bool ComponentManager::loadComponent(ComponentType component_type, - const json ¶ms) { - IS_ARGUMENT_EMPTY(); - // Args: - // module_name: the name of the module(.dll or.so) - // module_path: the path of the module(.dll or.so) - // component_name: the name of the component, the name of the ptr will be - // gotten from the module Others will be load from the package.json - GET_ARGUMENT_C(module_name, std::string); - GET_ARGUMENT_C(module_path, std::string); - GET_ARGUMENT_C(component_name, std::string); +auto ComponentManager::loadComponent(const json& params) -> bool { + std::lock_guard lock(impl_->mutex); + if (params.is_null()) { + return false; + } - if (!checkComponent(module_name, module_path)) { - LOG_F(ERROR, "Failed to load component library: {}", module_name); + if (!params.contains("module_name") || !params.contains("module_path") || + !params.contains("component_name")) { + LOG_F(ERROR, "{}: Missing arguments", __func__); return false; } - if (!loadComponentInfo(module_path, component_name)) { - LOG_F(ERROR, "Failed to load component info: {}", module_path); + + auto moduleName = params["module_name"].get(); + auto modulePath = params["module_path"].get(); + auto componentName = params["component_name"].get(); + + if (!checkComponent(moduleName, modulePath)) { + LOG_F(ERROR, "Failed to load component library: {}", moduleName); return false; } - if (!checkComponentInfo(module_name, component_name)) { - LOG_F(ERROR, "Failed to load component info: {}", module_path); + if (!loadComponentInfo(modulePath, componentName)) { + LOG_F(ERROR, "Failed to load component info: {}", modulePath); return false; } - - auto it = m_ComponentEntries.find(module_name + "." + component_name); - if (it == m_ComponentEntries.end()) { - LOG_F(ERROR, "Failed to load component entry: {}", component_name); + if (!checkComponentInfo(moduleName, componentName)) { + LOG_F(ERROR, "Failed to load component info: {}", modulePath); return false; } - if (it->second->m_component_type == "shared") { - /* - if (!loadSharedComponent(component_name)) { - LOG_F(ERROR, "Failed to load shared component: {}", component_name); - return false; - } - */ - } else if (it->second->m_component_type == "alone") { - } else if (it->second->m_component_type == "executable") { + auto it = impl_->componentEntries.find(moduleName + "." + componentName); + if (it == impl_->componentEntries.end()) { + LOG_F(ERROR, "Failed to load component entry: {}", componentName); + return false; } - return true; + if (it->second->component_type == "shared") { + return loadSharedComponent(componentName, moduleName, modulePath, + it->second->func_name, + it->second->dependencies); + } + if (it->second->component_type == "standalone") { + return loadStandaloneComponent(componentName, moduleName, modulePath, + it->second->func_name, + it->second->dependencies); + } + LOG_F(ERROR, "Unknown component type: {}", componentName); + return false; } -bool ComponentManager::checkComponent(const std::string &module_name, - const std::string &module_path) { - // Check if the module has been loaded - if (m_ModuleLoader.lock()->HasModule(module_name)) { +auto ComponentManager::checkComponent(const std::string& module_name, + const std::string& module_path) -> bool { + auto moduleLoader = impl_->moduleLoader.lock(); + if (moduleLoader && moduleLoader->hasModule(module_name)) { LOG_F(WARNING, "Module {} has been loaded, please do not load again", module_name); return true; } - // If not, load the module - // Check component path - if (!atom::io::isFolderExists(module_path)) { + + if (!std::filesystem::exists(module_path)) { LOG_F(ERROR, "Component path {} does not exist", module_path); return false; } - // Check component package.json file, this is for the first time loading - // And we need to know how to load component's ptr from this file - if (!atom::io::isFileExists(module_path + constants::PATH_SEPARATOR + - constants::PACKAGE_NAME)) { + + if (!std::filesystem::exists(module_path + constants::PATH_SEPARATOR + + constants::PACKAGE_NAME)) { LOG_F(ERROR, "Component path {} does not contain package.json", module_path); return false; } - // Check component library files - std::vector files = atom::io::checkFileTypeInFolder( - module_path, constants::LIB_EXTENSION, atom::io::FileOption::Name); - if (files.empty()) { - LOG_F(ERROR, "Component path {} does not contain dll or so file", + auto files = getFilesInDir(module_path); + auto it = std::find_if(files.begin(), files.end(), + [&](const std::string& fileName) { + return fileName.size() > 4 && + fileName.substr(fileName.size() - 4) == + constants::LIB_EXTENSION; + }); + + if (it == files.end()) { + LOG_F(ERROR, + "Component path {} does not contain specified dll or so file", module_path); return false; } - auto it = std::find(files.begin(), files.end(), - module_name + constants::LIB_EXTENSION); - if (it != files.end()) { - if (!m_ModuleLoader.lock()->LoadModule( - module_path + constants::PATH_SEPARATOR + module_name + - constants::LIB_EXTENSION, - module_name)) { - LOG_F(ERROR, "Failed to load module: {}'s library {}", module_name, - module_path); - return false; - } - } else { - LOG_F(ERROR, - "Component path {} does not contain specified dll or so file", + + if (moduleLoader && + !moduleLoader->loadModule(module_path + constants::PATH_SEPARATOR + *it, + module_name)) { + LOG_F(ERROR, "Failed to load module: {}'s library {}", module_name, module_path); return false; } return true; } -bool ComponentManager::loadComponentInfo(const std::string &module_path, - const std::string &component_name) { - // Load the Package.json - // Max: We will only load the root package.json - std::string file_path = +auto ComponentManager::loadComponentInfo( + const std::string& module_path, const std::string& component_name) -> bool { + std::string filePath = module_path + constants::PATH_SEPARATOR + constants::PACKAGE_NAME; - if (!fs::exists(file_path)) { + if (!std::filesystem::exists(filePath)) { LOG_F(ERROR, "Component path {} does not contain package.json", module_path); return false; } try { - m_ComponentInfos[component_name] = - json::parse(std::ifstream(file_path)); - } catch (const json::parse_error &e) { + std::ifstream file(filePath); + impl_->componentInfos[component_name] = json::parse(file); + } catch (const json::parse_error& e) { LOG_F(ERROR, "Failed to load package.json file: {}", e.what()); return false; } return true; } -bool ComponentManager::checkComponentInfo(const std::string &module_name, - const std::string &component_name) { - auto it = m_ComponentInfos.find(module_name); - if (it == m_ComponentInfos.end()) { +auto ComponentManager::checkComponentInfo( + const std::string& module_name, const std::string& component_name) -> bool { + auto it = impl_->componentInfos.find(module_name); + if (it == impl_->componentInfos.end()) { LOG_F(ERROR, "Component {} does not contain package.json file", module_name); return false; } - const auto component_info = m_ComponentInfos[module_name]; + const auto& componentInfo = it->second; - if (!component_info.contains("modules") || - !component_info["modules"].is_array()) { - LOG_F(ERROR, "Component {} does not contain mmodules", module_name); + if (!componentInfo.contains("modules") || + !componentInfo["modules"].is_array()) { + LOG_F(ERROR, "Component {} does not contain modules", module_name); return false; } - for (const auto &module : component_info["modules"]) { + + for (const auto& module : componentInfo["modules"]) { if (!module.contains("name") || !module["name"].is_string() || !module.contains("entry") || !module["entry"].is_string()) { LOG_F(ERROR, "Component {} does not contain name or entry", module_name); return false; } + if (module["name"] == component_name) { - auto func_name = module["entry"].get(); - if (!m_ModuleLoader.lock()->HasFunction(module_name, func_name)) { + auto funcName = module["entry"].get(); + + auto moduleLoaderLock = impl_->moduleLoader.lock(); + if (moduleLoaderLock && + !moduleLoaderLock->hasFunction(module_name, funcName)) { LOG_F(ERROR, "Failed to load module: {}'s function {}", - component_name, func_name); + component_name, funcName); return false; } - m_ComponentEntries[component_name] = - std::make_shared(component_name, func_name, + + impl_->componentEntries[component_name] = + std::make_shared(component_name, funcName, "shared", module_name); return true; } @@ -366,59 +451,78 @@ bool ComponentManager::checkComponentInfo(const std::string &module_name, return false; } -bool ComponentManager::unloadComponent(ComponentType component_type, - const json ¶ms) { - IS_ARGUMENT_EMPTY(); - // Args: - // component_name: the name of the component - GET_ARGUMENT_C(component_name, std::string); - GET_ARGUMENT_C(forced, bool) +auto ComponentManager::unloadComponent(const json& params) -> bool { + std::lock_guard lock(impl_->mutex); + if (params.is_null()) { + return false; + } + + if (!params.contains("component_name") || !params.contains("forced")) { + LOG_F(ERROR, "{}: Missing arguments", __func__); + return false; + } - auto it = m_ComponentEntries.find(component_name); - if (it == m_ComponentEntries.end()) { - LOG_F(ERROR, "Failed to load component entry: {}", component_name); + auto componentName = params["component_name"].get(); + auto forced = params["forced"].get(); + + auto it = impl_->componentEntries.find(componentName); + if (it == impl_->componentEntries.end()) { + LOG_F(ERROR, "Failed to load component entry: {}", componentName); return false; } - if (it->second->m_component_type == "shared") [[likely]] { - if (!unloadSharedComponent(component_name, forced)) { - LOG_F(ERROR, "Failed to unload component: {}", component_name); + if (it->second->component_type == "shared") { + if (!unloadSharedComponent(componentName, forced)) { + LOG_F(ERROR, "Failed to unload component: {}", componentName); + return false; + } + } else if (it->second->component_type == "standalone") { + if (!unloadStandaloneComponent(componentName, forced)) { + LOG_F(ERROR, "Failed to unload standalone component: {}", + componentName); return false; } - } else if (it->second->m_component_type == "alone") { - } else if (it->second->m_component_type == "executable") { } return true; } -bool ComponentManager::reloadComponent(ComponentType component_type, - const json ¶ms) { - IS_ARGUMENT_EMPTY(); - // Args: - // component_name: the name of the component - GET_ARGUMENT_C(component_name, std::string); +auto ComponentManager::reloadComponent(const json& params) -> bool { + std::lock_guard lock(impl_->mutex); + if (params.is_null()) { + return false; + } - auto it = m_ComponentEntries.find(component_name); - if (it == m_ComponentEntries.end()) { - LOG_F(ERROR, "Failed to load component entry: {}", component_name); + if (!params.contains("component_name")) { + LOG_F(ERROR, "{}: Missing arguments", __func__); return false; } - if (it->second->m_component_type == "shared") [[likely]] { - if (!reloadSharedComponent(component_name)) { - LOG_F(ERROR, "Failed to unload component: {}", component_name); + + auto componentName = params["component_name"].get(); + + auto it = impl_->componentEntries.find(componentName); + if (it == impl_->componentEntries.end()) { + LOG_F(ERROR, "Failed to load component entry: {}", componentName); + return false; + } + if (it->second->component_type == "shared") { + if (!reloadSharedComponent(componentName)) { + LOG_F(ERROR, "Failed to reload component: {}", componentName); + return false; + } + } else if (it->second->component_type == "standalone") { + if (!reloadStandaloneComponent(componentName)) { + LOG_F(ERROR, "Failed to reload standalone component: {}", + componentName); return false; } - } else if (it->second->m_component_type == "alone") { - } else if (it->second->m_component_type == "executable") { } return true; - return true; } -bool ComponentManager::reloadAllComponents() { +auto ComponentManager::reloadAllComponents() -> bool { + std::lock_guard lock(impl_->mutex); LOG_F(INFO, "Reloading all components"); - for (auto &[name, component] : m_Components) { - if (!reloadComponent(ComponentType::SHREAD_INJECTED, - json::object({{"component_name", name}}))) { + for (const auto& [name, component] : impl_->components) { + if (!reloadComponent(json::object({{"component_name", name}}))) { LOG_F(ERROR, "Failed to reload component: {}", name); return false; } @@ -426,141 +530,166 @@ bool ComponentManager::reloadAllComponents() { return true; } -std::optional> ComponentManager::getComponent( - const std::string &component_name) { - if (!m_ComponentEntries.contains(component_name)) { - LOG_F(ERROR, "Could not found the component: {}", component_name); +auto ComponentManager::getComponent(const std::string& component_name) + -> std::optional> { + std::lock_guard lock(impl_->mutex); + if (!impl_->componentEntries.contains(component_name)) { + LOG_F(ERROR, "Could not find the component: {}", component_name); return std::nullopt; } - return std::optional(m_Components[component_name]); + return impl_->components[component_name]; } -std::optional ComponentManager::getComponentInfo( - const std::string &component_name) { - if (!m_ComponentEntries.contains(component_name)) { - LOG_F(ERROR, "Could not found the component: {}", component_name); +auto ComponentManager::getComponentInfo(const std::string& component_name) + -> std::optional { + std::lock_guard lock(impl_->mutex); + if (!impl_->componentEntries.contains(component_name)) { + LOG_F(ERROR, "Could not find the component: {}", component_name); return std::nullopt; } - return std::optional(m_ComponentInfos[component_name]); + return impl_->componentInfos[component_name]; } -std::vector ComponentManager::getComponentList() { +auto ComponentManager::getComponentList() -> std::vector { + std::lock_guard lock(impl_->mutex); std::vector list; - for (const auto &[name, component] : m_Components) { + for (const auto& [name, component] : impl_->components) { list.push_back(name); } std::sort(list.begin(), list.end()); return list; } -bool ComponentManager::loadSharedComponent( - const std::string &component_name, const std::string &addon_name, - const std::string &module_path, const std::string &entry, - const std::vector &dependencies) { - auto component_full_name = addon_name + "." + component_name; +auto ComponentManager::hasComponent(const std::string& component_name) -> bool { + std::lock_guard lock(impl_->mutex); + return impl_->componentEntries.contains(component_name); +} - DLOG_F(INFO, "Loading module: {}", component_full_name); +auto ComponentManager::loadSharedComponent( + 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); + auto componentFullName = addon_name + "." + component_name; + DLOG_F(INFO, "Loading module: {}", componentFullName); + auto modulePathStr = #ifdef _WIN32 - // This is to pass file name check - auto module_path_str = atom::utils::replaceString(module_path, "/", "\\"); + atom::utils::replaceString(module_path, "/", "\\") #else - auto module_path_str = atom::utils::replaceString(module_path, "\\", "/"); + atom::utils::replaceString(module_path, "\\", "/") #endif + + constants::PATH_SEPARATOR + component_name + constants::LIB_EXTENSION; + + auto moduleLoader = impl_->moduleLoader.lock(); + if (!moduleLoader) { + LOG_F(ERROR, "Failed to lock module loader"); + return false; + } - // This step is to load the dynamic library - if (!m_ModuleLoader.lock()->LoadModule(module_path_str, - component_full_name)) { - LOG_F(ERROR, "Failed to load module: {}", module_path_str); + if (!moduleLoader->loadModule(modulePathStr, componentFullName)) { + LOG_F(ERROR, "Failed to load module: {}", modulePathStr); return false; } + if (entry.empty()) { LOG_F(ERROR, "Failed to load module: {}/{}", module_path, component_name); return false; } - // get the component shared_ptr from dynamic library - if (auto component = m_ModuleLoader.lock()->GetInstance( - component_full_name, {}, entry); + + // Max: 对组件进行初始化,如果存在可以使用的初始化函数 + auto moduleInitFunc = moduleLoader->getFunction( + componentFullName, "initialize_registry"); + if (moduleInitFunc != nullptr) { + LOG_F(INFO, "Initializing registry for shared component: {}", componentFullName); + moduleInitFunc(); + LOG_F(INFO, "Initialized registry for shared component: {}", componentFullName); + } + + if (auto component = + moduleLoader->getInstance(componentFullName, {}, entry); component) { - LOG_F(INFO, "Loaded shared component: {}", component_full_name); - // Inject all of the component dependencies - // Remember that the dependencies must be injected before - // initialization + LOG_F(INFO, "Loaded shared component: {}", componentFullName); + try { - for (const auto &dependency : dependencies) { - // Check if the dependency is a string + for (const auto& dependency : dependencies) { if (!dependency.empty()) { component->addOtherComponent( dependency, GetWeakPtr(dependency)); + } else { + LOG_F(WARNING, "Empty dependency detected"); } - { LOG_F(WARNING, "Empty dependency detected"); } } - } catch (const json::exception &e) { + } catch (const std::exception& e) { LOG_F(ERROR, "Failed to load shared component: {} {}", - component_full_name, e.what()); + componentFullName, e.what()); return false; } try { - // Initialize the component if (component->initialize()) { - m_Components[component_full_name] = component; - // Inject it into GSPM - AddPtr(component_full_name, component); - LOG_F(INFO, "Loaded shared component: {}", component_full_name); - auto component_entry = std::make_shared( - component_name, entry, "shared", module_path); - m_ComponentEntries[component_full_name] = component_entry; + impl_->components[componentFullName] = component; + AddPtr(componentFullName, component); + LOG_F(INFO, "Loaded shared component: {}", componentFullName); + + impl_->componentEntries[componentFullName] = + std::make_shared(component_name, entry, + "shared", module_path); return true; } - } catch (const std::exception &e) { + } catch (const std::exception& e) { + LOG_F(ERROR, "Failed to initialize shared component: {} {}", + componentFullName, e.what()); } - LOG_F(ERROR, "Failed to initialize shared component: {}", - component_full_name); + } else { + LOG_F(ERROR, "Failed to load shared component: {}", componentFullName); } - LOG_F(ERROR, "Failed to load shared component: {}", component_full_name); + return false; } -bool ComponentManager::unloadSharedComponent(const std::string &component_name, - bool forced) { +auto ComponentManager::unloadSharedComponent(const std::string& component_name, + bool forced) -> bool { LOG_F(WARNING, - "Unload a component is very dangerous, you should make sure " - "everything proper"); - // CHeck if the compoent is loaded - if (!m_Components.contains(component_name)) { + "Unloading a component is very dangerous, you should make sure " + "everything is proper"); + + if (!impl_->components.contains(component_name)) { LOG_F(ERROR, "Component {} is not loaded", component_name); return false; } - std::vector dependencies; // record all of the components - // which depend on this component - for (const auto &entry : m_ComponentEntries) { - if (find_element(entry.second->m_dependencies, component_name)) { + + std::vector dependencies; + for (const auto& entry : impl_->componentEntries) { + if (std::find(entry.second->dependencies.begin(), + entry.second->dependencies.end(), + component_name) != entry.second->dependencies.end()) { dependencies.push_back(entry.first); } } + if (!dependencies.empty()) { if (!forced) { return false; } - for (const auto &dependency : dependencies) { + for (const auto& dependency : dependencies) { unloadSharedComponent(dependency, forced); } } - // explicit destroy the component - if (!m_Components[component_name].lock()->destroy()) { + + if (!impl_->components[component_name].lock()->destroy()) { LOG_F(ERROR, "Failed to destroy component: {}", component_name); return false; } - m_Components.erase(component_name); + impl_->components.erase(component_name); RemovePtr(component_name); LOG_F(INFO, "Unloaded shared component: {}", component_name); return true; } -bool ComponentManager::reloadSharedComponent( - const std::string &component_name) { - if (!m_Components.contains(component_name)) { +auto ComponentManager::reloadSharedComponent(const std::string& component_name) + -> bool { + if (!impl_->components.contains(component_name)) { LOG_F(ERROR, "Component {} is not loaded", component_name); return false; } @@ -569,14 +698,117 @@ bool ComponentManager::reloadSharedComponent( return false; } if (!loadSharedComponent( - component_name, m_ComponentEntries[component_name]->m_module_name, - m_ComponentEntries[component_name]->m_module_name, - m_ComponentEntries[component_name]->m_func_name, - m_ComponentEntries[component_name]->m_dependencies)) { + component_name, + impl_->componentEntries[component_name]->module_name, + impl_->componentEntries[component_name]->module_name, + impl_->componentEntries[component_name]->func_name, + impl_->componentEntries[component_name]->dependencies)) { LOG_F(ERROR, "Failed to reload component: {}", component_name); return false; } return true; } +auto ComponentManager::loadStandaloneComponent( + const std::string& component_name, + [[maybe_unused]] 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 standaloneComponent = + std::make_shared(component_name); + standaloneComponent->startLocalDriver(componentFullPath); + if (!standaloneComponent->initialize()) { + LOG_F(ERROR, "Failed to initialize component {}", component_name); + return false; + } + standaloneComponent->toggleDriverListening(); + LOG_F(INFO, "Start listening to driver for component {}", component_name); + standaloneComponent->monitorDrivers(); + LOG_F(INFO, "Start monitoring drivers for component {}", component_name); + + impl_->components[component_name] = standaloneComponent; + impl_->componentEntries[component_name] = std::make_shared( + component_name, entry, "standalone", module_path); + LOG_F(INFO, "Successfully loaded standalone component {}", component_name); + return true; +} + +auto ComponentManager::unloadStandaloneComponent( + const std::string& component_name, bool forced) -> bool { + std::lock_guard lock(impl_->mutex); + auto it = impl_->components.find(component_name); + if (it == impl_->components.end()) { + LOG_F(WARNING, "Component {} is not loaded", component_name); + return true; + } + auto component = it->second.lock(); + if (!component) { + LOG_F(ERROR, "Component {} is expired", component_name); + impl_->components.erase(component_name); + return true; + } + auto standaloneComponent = + std::dynamic_pointer_cast(component); + if (forced) { + LOG_F(INFO, "Forcefully unloading component {}", component_name); + if (!standaloneComponent->destroy()) { + LOG_F(ERROR, "Failed to destroy component {}", component_name); + } + impl_->components.erase(component_name); + return true; + } + LOG_F(INFO, "Unloaded standalone component {}", component_name); + return true; +} + +auto ComponentManager::reloadStandaloneComponent( + const std::string& component_name) -> bool { + std::lock_guard lock(impl_->mutex); + if (!impl_->components.contains(component_name)) { + LOG_F(ERROR, "Component {} not found", component_name); + return false; + } + if (!unloadStandaloneComponent(component_name, false)) { + LOG_F(ERROR, "Failed to unload standalone component: {}", + component_name); + return false; + } + if (!loadStandaloneComponent( + component_name, + impl_->componentEntries[component_name]->module_name, + impl_->componentEntries[component_name]->module_name, + impl_->componentEntries[component_name]->func_name, + impl_->componentEntries[component_name]->dependencies)) { + LOG_F(ERROR, "Failed to reload standalone component: {}", + component_name); + return false; + } + return true; +} + } // namespace lithium diff --git a/src/addon/manager.hpp b/src/addon/manager.hpp index 91b3c023..b4be5689 100644 --- a/src/addon/manager.hpp +++ b/src/addon/manager.hpp @@ -14,263 +14,78 @@ Description: Component Manager (the core of the plugin system) #pragma once +#include +#include +#include +#include + #include "atom/components/component.hpp" -#include "atom/components/types.hpp" -#include "atom/type/args.hpp" -#include "atom/system/env.hpp" +#include "dependency.hpp" -#include "atom/type/json.hpp" +#include "atom/type/json_fwd.hpp" using json = nlohmann::json; namespace lithium { + class AddonManager; class Compiler; class ModuleLoader; class Sandbox; -class ComponentEntry { -public: - std::string m_name; - std::string m_func_name; - std::string m_component_type; - std::string m_module_name; - - std::vector m_dependencies; - - ComponentEntry(const std::string& name, const std::string& func_name, - const std::string& component_type, - const std::string& module_name) - : m_name(name), - m_func_name(func_name), - m_component_type(component_type), - m_module_name(module_name) {} -}; +class ComponentManagerImpl; class ComponentManager { public: explicit ComponentManager(); ~ComponentManager(); - // ------------------------------------------------------------------- - // Common methods - // ------------------------------------------------------------------- - - /** - * @brief Initializes the component manager - * @return true if the component manager was initialized successfully, false - * otherwise - * @note We will try to load all of the components in the components - * directory. - * @note And the directory structure should be like this: - * - components / modules - * - component1 - * - package.json - * - component.dll - * - component2 - * - package.json - * - component.dll - * -... - * @warning This method will not load the components in the debug mode. - * @note The `addon` is the highest level of the component system. - */ - bool Initialize(); - - /** - * @brief Destroys the component manager - * @return true if the component manager was destroyed successfully, false - * otherwise - */ - bool Destroy(); - - /** - * @brief Creates a shared pointer to the component manager - * @return A shared pointer to the component manager - */ - static std::shared_ptr createShared(); - - // ------------------------------------------------------------------- - // Components methods (main entry) - // ------------------------------------------------------------------- - - /* - Max: Though we provide the ways to load and unload components, - we will only used load method in the release mode. - It seems that the logic of loading and unloading components is not - as simple as we thought. Only the developer can use unload and reload - methods for debugging. - - Also, the module means the dynamic library, which is a kind of - shared library. The components means the shared_ptr involved by the - module. So do not confuse the module and the component. - */ - - /** - * @brief Load a component - * @param component_type The type of the component to load - * @param args The arguments to pass to the component - * @return true if the component was loaded successfully, false otherwise - * @note The component will be loaded in the main thread - */ - bool loadComponent(ComponentType component_type, const json& params); - - /** - * @brief Unload a component - * @param component_type The type of the component to unload - * @param args The arguments to pass to the component - * @return true if the component was unloaded successfully, false otherwise - * @note The component will be unloaded in the main thread - * @note This method is not supposed to be called in release mode - * @warning This method will alse unload the component if it is still in use - * @warning Also, will cause Segmentation fault - */ - bool unloadComponent(ComponentType component_type, const json& params); - - /** - * @brief Reload a component - * @param component_type The type of the component to reload - * @param args The arguments to pass to the component - * @return true if the component was reloaded successfully, false otherwise - * @note The component will be reloaded in the main thread - * @note This method is not supposed to be called in release mode - * @warning This method will alse reload the component if it is still in use - * @warning Also, will cause Segmentation fault - */ - bool reloadComponent(ComponentType component_type, const json& params); - - /** - * @brief Reload all components - * @return true if the components were reloaded successfully, false - * otherwise - * @note The components will be reloaded in the main thread - * @note This method is not supposed to be called in release mode - * @warning This method will alse reload the components if they are still in - * use - * @warning Also, will cause Segmentation fault - */ - bool reloadAllComponents(); - - // ------------------------------------------------------------------- - // Components methods (getters) - // ------------------------------------------------------------------- + auto initialize() -> bool; + auto destroy() -> bool; - /** - * @brief Get a component - * @param component_type The type of the component to get - * @param component_name The name of the component to get - * @return The component if it exists, nullptr otherwise - */ - std::optional> getComponent( - const std::string& component_name); + static auto createShared() -> std::shared_ptr; - std::optional getComponentInfo(const std::string& component_name); + auto loadComponent(const json& params) -> bool; + auto unloadComponent(const json& params) -> bool; + auto reloadComponent(const json& params) -> bool; + auto reloadAllComponents() -> bool; - std::vector getComponentList(); + auto scanComponents(const std::string& path) -> std::vector; - // ------------------------------------------------------------------- - // Load Components Steppers methods - // ------------------------------------------------------------------- - - /** - * @brief Check if a component is loaded - * @param module_path The path of the module - * @param module_name The name of the module - * @return true if the component is loaded, false otherwise - * @note We will check if the module is loaded, if not we will load it - */ - bool checkComponent(const std::string& module_path, - const std::string& module_name); - - /** - * @brief Load the component info - * @param module_path The path of the module - * @return true if the component info was loaded successfully, false - * otherwise - * @note This method is used to load the component info. - * Just to check if the component info was loaded, if not we will load - * it - */ - bool loadComponentInfo(const std::string& module_path, - const std::string& name); - - /** - * @brief Check if a component info is loaded - * @param module_name The name of the module - * @param component_name The name of the component - * @return true if the component info is loaded, false otherwise - * @note We will check if the component info is loaded, if not we will load - * it - */ - bool checkComponentInfo(const std::string& module_name, - const std::string& component_name); - - // ------------------------------------------------------------------- - // Components methods (for shared components) - // ------------------------------------------------------------------- - - /** - * @brief Load a shared component - * @param component_name The name of the component to load - * @return true if the component was loaded successfully, false otherwise - * @note The component will be loaded in the main thread - * @note This method is not supposed to be called in release mode - * @warning This method will alse load the component if it is still in use - * @warning Also, will cause Segmentation fault - */ - bool loadSharedComponent(const std::string& component_name, - const std::string& addon_name, - const std::string& module_path, - const std::string& entry, - const std::vector& dependencies); - bool unloadSharedComponent(const std::string& component_name, bool forced); - bool reloadSharedComponent(const std::string& component_name); - -private: - /** - * @brief Get all files in a directory - * @param path The path of the directory - * @return The files in the directory - */ - std::vector getFilesInDir(const std::string& path); - - /** - * @brief Get all sub directories in a directory - * @param path The path of the directory - * @return The sub directories in the directory - */ - std::vector getQualifiedSubDirs(const std::string& path); + auto getComponent(const std::string& component_name) + -> std::optional>; + auto getComponentInfo(const std::string& component_name) + -> std::optional; + auto getComponentList() -> std::vector; + auto hasComponent(const std::string& component_name) -> bool; private: - std::weak_ptr m_ModuleLoader; - std::weak_ptr m_Env; - - // The finder used to find the components - // std::unique_ptr m_ComponentFinder; - - // The sandbox used to run the components in a safe way - std::unique_ptr m_Sandbox; - - // The compiler used to compile the shared components just in time - std::unique_ptr m_Compiler; - - // This is used to solve the circular dependency problem - // And make sure we can unload the components in the correct order - std::weak_ptr m_AddonManager; - - std::unordered_map> - m_ComponentEntries; - std::unordered_map m_ComponentInfos; - - // Components map of different types - // Max: Why not just use a single map of std::shared_ptr? - // Maybe it is because the dynamic_cast will be slow - // And we are surely about what the component is -#if ENABLE_FASTHASH - emhash8::HashMap> m_Components; -#else - std::unordered_map> m_Components; -#endif - - std::string m_module_path; + auto getFilesInDir(const std::string& path) -> std::vector; + auto getQualifiedSubDirs(const std::string& path) + -> std::vector; + auto checkComponent(const std::string& module_name, + const std::string& module_path) -> bool; + auto loadComponentInfo(const std::string& module_path, + const std::string& component_name) -> bool; + auto checkComponentInfo(const std::string& module_name, + const std::string& component_name) -> bool; + auto loadSharedComponent( + 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 unloadSharedComponent(const std::string& component_name, + bool forced) -> bool; + auto reloadSharedComponent(const std::string& component_name) -> bool; + + auto loadStandaloneComponent( + 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 unloadStandaloneComponent(const std::string& component_name, + bool forced) -> bool; + auto reloadStandaloneComponent(const std::string& component_name) -> bool; + + std::unique_ptr impl_; }; + } // namespace lithium diff --git a/src/addon/module.hpp b/src/addon/module.hpp index 3f61b00e..961beab2 100644 --- a/src/addon/module.hpp +++ b/src/addon/module.hpp @@ -15,27 +15,12 @@ Description: Module Information #pragma once #include -#include -#include #include -#include -#include #include -#include #include -// Determine the platform -#if defined(_WIN32) || defined(_WIN64) -#include -#elif defined(__linux__) -#define PLATFORM_LINUX -#include -#elif defined(__APPLE__) -#define PLATFORM_MACOS -#include -#endif - -#include "atom/error/exception.hpp" +#include "atom/function/ffi.hpp" +#include "macro.hpp" namespace lithium { struct FunctionInfo { @@ -46,9 +31,7 @@ struct FunctionInfo { FunctionInfo() : name(""), address(nullptr) {} }; -class ModuleInfo { - // All of the module information -public: +struct ModuleInfo { std::string m_name; std::string m_description; std::string m_version; @@ -66,87 +49,7 @@ class ModuleInfo { // All of the functions in the module(dynamic loaded) std::vector> functions; - // Module handle pointer - void* handle; -}; - -class DynamicLibrary { -public: - explicit DynamicLibrary(const std::string& dllName) { - loadLibrary(dllName); - } - - ~DynamicLibrary() { unloadLibrary(); } - - // Function to get a function pointer of any type using template and - // variadic arguments - template - std::function getFunction(const std::string& funcName) { - std::lock_guard lock(mutex); // Ensure thread-safety - if (hModule == nullptr) { - THROW_NOT_FOUND("Module not loaded"); - } - -#ifdef _WIN32 - FARPROC proc = - GetProcAddress(static_cast(hModule), funcName.c_str()); -#else - void* proc = dlsym(hModule, funcName.c_str()); -#endif - if (!proc) { - THROW_FAIL_TO_LOAD_SYMBOL("Failed to load symbol: " + funcName); - } - // We use std::function to wrap the raw function pointer. - return std::function(reinterpret_cast(proc)); - } - - // Reload or load another library - void reload(const std::string& dllName) { - std::lock_guard lock(mutex); // Ensure thread-safety - unloadLibrary(); - loadLibrary(dllName); - } - -private: - void* hModule = nullptr; - std::mutex mutex; + std::shared_ptr mLibrary; +} ATOM_ALIGNAS(128); - void loadLibrary(const std::string& dllName) { -#ifdef _WIN32 - hModule = LoadLibraryA(dllName.c_str()); -#else - hModule = dlopen(dllName.c_str(), RTLD_LAZY); -#endif - if (!hModule) { - THROW_FAIL_TO_LOAD_DLL("Failed to load " + dllName); - } - } - - void unloadLibrary() { - if (hModule) { -#ifdef _WIN32 - FreeLibrary(static_cast(hModule)); -#else - dlclose(hModule); -#endif - hModule = nullptr; - } - } -}; - -template -class LibraryObject { -public: - LibraryObject(DynamicLibrary& library, const std::string& factoryFuncName) { - auto factory = library.getFunction(factoryFuncName); - object.reset(factory()); - } - - T* operator->() const { return object.get(); } - - T& operator*() const { return *object; } - -private: - std::unique_ptr object; -}; } // namespace lithium diff --git a/src/addon/platform/base.hpp b/src/addon/platform/base.hpp new file mode 100644 index 00000000..04cf9ef3 --- /dev/null +++ b/src/addon/platform/base.hpp @@ -0,0 +1,27 @@ +#ifndef LITHIUM_ADDON_BUILDBASE_HPP +#define LITHIUM_ADDON_BUILDBASE_HPP + +#include +#include + +namespace lithium { +class BuildSystem { +public: + virtual ~BuildSystem() = default; + + virtual auto configureProject( + const std::string &sourceDir, const std::string &buildDir, + const std::string &buildType, + const std::vector &options) -> bool = 0; + virtual auto buildProject(const std::string &buildDir, + int jobs) -> bool = 0; + virtual auto cleanProject(const std::string &buildDir) -> bool = 0; + virtual auto installProject(const std::string &buildDir, + const std::string &installDir) -> bool = 0; + virtual auto runTests(const std::string &buildDir) -> bool = 0; + virtual auto generateDocs(const std::string &buildDir) -> bool = 0; + virtual auto loadConfig(const std::string &configPath) -> bool = 0; +}; +} // namespace lithium + +#endif // LITHIUM_ADDON_BUILDBASE_HPP diff --git a/src/addon/platform/cmake.cpp b/src/addon/platform/cmake.cpp new file mode 100644 index 00000000..39eea089 --- /dev/null +++ b/src/addon/platform/cmake.cpp @@ -0,0 +1,123 @@ +#include "cmake.hpp" + +#include +#include +#include +#include + +#include "atom/type/json.hpp" + +namespace fs = std::filesystem; +using json = nlohmann::json; + +#include "atom/log/loguru.hpp" +#include "atom/system/command.hpp" + +namespace lithium { +class CMakeBuilderImpl { +public: + std::unique_ptr configOptions = std::make_unique(); + std::vector dependencies; +}; + +CMakeBuilder::CMakeBuilder() : pImpl_(std::make_unique()) {} +CMakeBuilder::~CMakeBuilder() = default; + +bool CMakeBuilder::checkAndInstallDependencies() { + for (const auto &dep : pImpl_->dependencies) { + std::string checkCommand = "pkg-config --exists " + dep; + if (!atom::system::executeCommandSimple(checkCommand)) { + LOG_F(INFO, "Dependency {} not found, attempting to install...", + dep); + std::string installCommand = "sudo apt-get install -y " + dep; + if (!atom::system::executeCommandSimple(installCommand)) { + LOG_F(ERROR, "Failed to install dependency: {}", dep); + return false; + } + } + } + return true; +} + +auto CMakeBuilder::configureProject( + const std::string &sourceDir, const std::string &buildDir, + const std::string &buildType, + const std::vector &options) -> bool { + if (!fs::exists(buildDir)) { + fs::create_directories(buildDir); + } + + if (!checkAndInstallDependencies()) { + return false; + } + + std::string opts; + for (const auto &opt : options) { + opts += " " + opt; + } + + std::string command = "cmake -S " + sourceDir + " -B " + buildDir + + " -DCMAKE_BUILD_TYPE=" + buildType + opts; + return atom::system::executeCommandSimple(command); +} + +auto CMakeBuilder::buildProject(const std::string &buildDir, int jobs) -> bool { + std::string command = "cmake --build " + buildDir; + if (jobs > 0) { + command += " -j" + std::to_string(jobs); + } + return atom::system::executeCommandSimple(command); +} + +auto CMakeBuilder::cleanProject(const std::string &buildDir) -> bool { + if (!fs::exists(buildDir)) { + LOG_F(ERROR, "Build directory does not exist: {}", buildDir); + return false; + } + std::string command = "cmake --build " + buildDir + " --target clean"; + return atom::system::executeCommandSimple(command); +} + +auto CMakeBuilder::installProject(const std::string &buildDir, + const std::string &installDir) -> bool { + std::string command = "cmake --build " + buildDir + + " --target install --prefix " + installDir; + return atom::system::executeCommandSimple(command); +} + +auto CMakeBuilder::runTests(const std::string &buildDir) -> bool { + std::string command = "ctest --test-dir " + buildDir; + return atom::system::executeCommandSimple(command); +} + +auto CMakeBuilder::generateDocs(const std::string &buildDir) -> bool { + std::string command = "cmake --build " + buildDir + " --target docs"; + return atom::system::executeCommandSimple(command); +} + +auto CMakeBuilder::loadConfig(const std::string &configPath) -> bool { + std::ifstream configFile(configPath); + if (!configFile.is_open()) { + LOG_F(ERROR, "Failed to open config file: {}", configPath); + return false; + } + + try { + configFile >> *(pImpl_->configOptions); + pImpl_->dependencies = pImpl_->configOptions->at("dependencies") + .get>(); + } catch (const json::parse_error &e) { + LOG_F(ERROR, "Failed to parse config file: {} with {}", configPath, + e.what()); + return false; + } catch (const json::type_error &e) { + LOG_F(ERROR, "Failed to parse config file: {} with {}", configPath, + e.what()); + return false; + } + + configFile.close(); + return true; +} + +} // namespace lithium diff --git a/src/addon/platform/cmake.hpp b/src/addon/platform/cmake.hpp new file mode 100644 index 00000000..fd5fae3e --- /dev/null +++ b/src/addon/platform/cmake.hpp @@ -0,0 +1,36 @@ +#ifndef LITHIUM_ADDON_CMAKEBUILDER_HPP +#define LITHIUM_ADDON_CMAKEBUILDER_HPP + +#include +#include +#include + +#include "base.hpp" + +namespace lithium { +class CMakeBuilderImpl; +class CMakeBuilder : public BuildSystem { +public: + CMakeBuilder(); + ~CMakeBuilder() override; + + auto configureProject( + const std::string &sourceDir, const std::string &buildDir, + const std::string &buildType, + const std::vector &options) -> bool override; + auto buildProject(const std::string &buildDir, int jobs) -> bool override; + auto cleanProject(const std::string &buildDir) -> bool override; + auto installProject(const std::string &buildDir, + const std::string &installDir) -> bool override; + auto runTests(const std::string &buildDir) -> bool override; + auto generateDocs(const std::string &buildDir) -> bool override; + auto loadConfig(const std::string &configPath) -> bool override; + +private: + std::unique_ptr pImpl_; + + auto checkAndInstallDependencies() -> bool; +}; +} // namespace lithium + +#endif // LITHIUM_ADDON_CMAKEBUILDER_HPP diff --git a/src/addon/platform/meson.cpp b/src/addon/platform/meson.cpp new file mode 100644 index 00000000..768843fa --- /dev/null +++ b/src/addon/platform/meson.cpp @@ -0,0 +1,130 @@ +#include "meson.hpp" + +#include +#include +#include + +#include "atom/log/loguru.hpp" +#include "atom/system/command.hpp" +#include "atom/type/json.hpp" +namespace fs = std::filesystem; +using json = nlohmann::json; + +namespace lithium { +class MesonBuilderImpl { +public: + std::unique_ptr configOptions = std::make_unique(); + std::vector preBuildScripts; + std::vector postBuildScripts; + std::vector environmentVariables; + std::vector dependencies; +}; + +MesonBuilder::MesonBuilder() : pImpl_(std::make_unique()) {} +MesonBuilder::~MesonBuilder() = default; + +auto MesonBuilder::checkAndInstallDependencies() -> bool { + for (const auto &dep : pImpl_->dependencies) { + std::string checkCommand = "pkg-config --exists " + dep; + if (!atom::system::executeCommandSimple(checkCommand)) { + LOG_F(INFO, "Dependency {} not found, attempting to install...", + dep); + std::string installCommand = "sudo apt-get install -y " + dep; + if (!atom::system::executeCommandSimple(installCommand)) { + LOG_F(ERROR, "Failed to install dependency: {}", dep); + return false; + } + } + } + return true; +} + +auto MesonBuilder::configureProject( + const std::string &sourceDir, const std::string &buildDir, + const std::string &buildType, + const std::vector &options) -> bool { + if (!fs::exists(buildDir)) { + fs::create_directories(buildDir); + } + + if (!checkAndInstallDependencies()) { + return false; + } + + std::string opts; + for (const auto &opt : options) { + opts += " " + opt; + } + + std::string command = "meson setup " + buildDir + " " + sourceDir + + " --buildtype=" + buildType + opts; + return atom::system::executeCommandSimple(command); +} + +auto MesonBuilder::buildProject(const std::string &buildDir, int jobs) -> bool { + std::string command = "ninja -C " + buildDir; + if (jobs > 0) { + command += " -j" + std::to_string(jobs); + } + return atom::system::executeCommandSimple(command); +} + +auto MesonBuilder::cleanProject(const std::string &buildDir) -> bool { + if (!fs::exists(buildDir)) { + LOG_F(ERROR, "Build directory does not exist: {}", buildDir); + return false; + } + std::string command = "ninja -C " + buildDir + " clean"; + return atom::system::executeCommandSimple(command); +} + +auto MesonBuilder::installProject(const std::string &buildDir, + const std::string &installDir) -> bool { + std::string command = + "ninja -C " + buildDir + " install --prefix " + installDir; + return atom::system::executeCommandSimple(command); +} + +auto MesonBuilder::runTests(const std::string &buildDir) -> bool { + std::string command = "meson test -C " + buildDir; + return atom::system::executeCommandSimple(command); +} + +auto MesonBuilder::generateDocs(const std::string &buildDir) -> bool { + std::string command = "ninja -C " + buildDir + " docs"; + return atom::system::executeCommandSimple(command); +} + +auto MesonBuilder::loadConfig(const std::string &configPath) -> bool { + std::ifstream configFile(configPath); + if (!configFile.is_open()) { + LOG_F(ERROR, "Failed to open config file: {}", configPath); + return false; + } + + try { + configFile >> *(pImpl_->configOptions); + pImpl_->preBuildScripts = pImpl_->configOptions->at("preBuildScripts") + .get>(); + pImpl_->postBuildScripts = pImpl_->configOptions->at("postBuildScripts") + .get>(); + pImpl_->environmentVariables = + pImpl_->configOptions->at("environmentVariables") + .get>(); + pImpl_->dependencies = pImpl_->configOptions->at("dependencies") + .get>(); + } catch (const json::parse_error &e) { + LOG_F(ERROR, "Failed to parse config file: {} with {}", configPath, + e.what()); + return false; + } catch (const json::type_error &e) { + LOG_F(ERROR, "Failed to parse config file: {} with {}", configPath, + e.what()); + return false; + } + + configFile.close(); + return true; +} + +} // namespace lithium diff --git a/src/addon/platform/meson.hpp b/src/addon/platform/meson.hpp new file mode 100644 index 00000000..3b7f0247 --- /dev/null +++ b/src/addon/platform/meson.hpp @@ -0,0 +1,36 @@ +#ifndef LITHIUM_ADDON_MESONBUILDER_HPP +#define LITHIUM_ADDON_MESONBUILDER_HPP + +#include +#include +#include + +#include "base.hpp" + +namespace lithium { +class MesonBuilderImpl; +class MesonBuilder : public BuildSystem { +public: + MesonBuilder(); + ~MesonBuilder() override; + + auto configureProject( + const std::string &sourceDir, const std::string &buildDir, + const std::string &buildType, + const std::vector &options) -> bool override; + auto buildProject(const std::string &buildDir, int jobs) -> bool override; + auto cleanProject(const std::string &buildDir) -> bool override; + auto installProject(const std::string &buildDir, + const std::string &installDir) -> bool override; + auto runTests(const std::string &buildDir) -> bool override; + auto generateDocs(const std::string &buildDir) -> bool override; + auto loadConfig(const std::string &configPath) -> bool override; + +private: + std::unique_ptr pImpl_; + + auto checkAndInstallDependencies() -> bool; +}; +} // namespace lithium + +#endif // LITHIUM_ADDON_MESONBUILDER_HPP diff --git a/src/addon/platform/xmake.cpp b/src/addon/platform/xmake.cpp new file mode 100644 index 00000000..8e776a6c --- /dev/null +++ b/src/addon/platform/xmake.cpp @@ -0,0 +1,121 @@ +#include "xmake.hpp" + +#include +#include +#include + +#include "atom/type/json.hpp" + +namespace fs = std::filesystem; +using json = nlohmann::json; + +#include "atom/log/loguru.hpp" +#include "atom/system/command.hpp" + +namespace lithium { +class XMakeBuilderImpl { +public: + std::unique_ptr configOptions = std::make_unique(); + std::vector dependencies; +}; + +XMakeBuilder::XMakeBuilder() : pImpl_(std::make_unique()) {} +XMakeBuilder::~XMakeBuilder() = default; + +auto XMakeBuilder::checkAndInstallDependencies() -> bool { + for (const auto &dep : pImpl_->dependencies) { + std::string checkCommand = "pkg-config --exists " + dep; + if (!atom::system::executeCommandSimple(checkCommand)) { + LOG_F(INFO, "Dependency {} not found, attempting to install...", + dep); + std::string installCommand = "sudo apt-get install -y " + dep; + if (!atom::system::executeCommandSimple(installCommand)) { + LOG_F(ERROR, "Failed to install dependency: {}", dep); + return false; + } + } + } + return true; +} + +auto XMakeBuilder::configureProject( + const std::string &sourceDir, const std::string &buildDir, + const std::string &buildType, + const std::vector &options) -> bool { + if (!fs::exists(buildDir)) { + fs::create_directories(buildDir); + } + + if (!checkAndInstallDependencies()) { + return false; + } + + std::string opts; + for (const auto &opt : options) { + opts += " " + opt; + } + + std::string command = + "xmake f -p " + buildType + " -o " + buildDir + " " + opts; + return atom::system::executeCommandSimple(command); +} + +auto XMakeBuilder::buildProject(const std::string &buildDir, int jobs) -> bool { + std::string command = "xmake -C " + buildDir; + if (jobs > 0) { + command += " -j" + std::to_string(jobs); + } + return atom::system::executeCommandSimple(command); +} + +auto XMakeBuilder::cleanProject(const std::string &buildDir) -> bool { + if (!fs::exists(buildDir)) { + LOG_F(ERROR, "Build directory does not exist: {}", buildDir); + return false; + } + std::string command = "xmake clean -C " + buildDir; + return atom::system::executeCommandSimple(command); +} + +auto XMakeBuilder::installProject(const std::string &buildDir, + const std::string &installDir) -> bool { + std::string command = "xmake install -o " + installDir + " -C " + buildDir; + return atom::system::executeCommandSimple(command); +} + +auto XMakeBuilder::runTests(const std::string &buildDir) -> bool { + std::string command = "xmake run test -C " + buildDir; + return atom::system::executeCommandSimple(command); +} + +auto XMakeBuilder::generateDocs(const std::string &buildDir) -> bool { + std::string command = "xmake doc -C " + buildDir; + return atom::system::executeCommandSimple(command); +} + +auto XMakeBuilder::loadConfig(const std::string &configPath) -> bool { + std::ifstream configFile(configPath); + if (!configFile.is_open()) { + LOG_F(ERROR, "Failed to open config file: {}", configPath); + return false; + } + + try { + configFile >> *(pImpl_->configOptions); + pImpl_->dependencies = pImpl_->configOptions->at("dependencies") + .get>(); + } catch (const json::parse_error &e) { + LOG_F(ERROR, "Failed to parse config file: {} with {}", configPath, + e.what()); + return false; + } catch (const json::type_error &e) { + LOG_F(ERROR, "Failed to parse config file: {} with {}", configPath, + e.what()); + return false; + } + + configFile.close(); + return true; +} + +} // namespace lithium diff --git a/src/addon/platform/xmake.hpp b/src/addon/platform/xmake.hpp new file mode 100644 index 00000000..3d2170e0 --- /dev/null +++ b/src/addon/platform/xmake.hpp @@ -0,0 +1,36 @@ +#ifndef LITHIUM_ADDON_XMAKEBUILDER_HPP +#define LITHIUM_ADDON_XMAKEBUILDER_HPP + +#include +#include +#include + +#include "base.hpp" + +namespace lithium { +class XMakeBuilderImpl; +class XMakeBuilder : public BuildSystem { +public: + XMakeBuilder(); + ~XMakeBuilder() override; + + auto configureProject( + const std::string &sourceDir, const std::string &buildDir, + const std::string &buildType, + const std::vector &options) -> bool override; + auto buildProject(const std::string &buildDir, int jobs) -> bool override; + auto cleanProject(const std::string &buildDir) -> bool override; + auto installProject(const std::string &buildDir, + const std::string &installDir) -> bool override; + auto runTests(const std::string &buildDir) -> bool override; + auto generateDocs(const std::string &buildDir) -> bool override; + auto loadConfig(const std::string &configPath) -> bool override; + +private: + std::unique_ptr pImpl_; + + auto checkAndInstallDependencies() -> bool; +}; +} // namespace lithium + +#endif // LITHIUM_ADDON_XMAKEBUILDER_HPP diff --git a/driver/client/atom-ascom/covercalibrator.cpp b/src/addon/project.cpp similarity index 100% rename from driver/client/atom-ascom/covercalibrator.cpp rename to src/addon/project.cpp diff --git a/src/addon/project.hpp b/src/addon/project.hpp new file mode 100644 index 00000000..3abacebb --- /dev/null +++ b/src/addon/project.hpp @@ -0,0 +1,36 @@ +#ifndef LITHIUM_ADDON_PROJECT_HPP +#define LITHIUM_ADDON_PROJECT_HPP + +#include +#include +#include + +#include "project/base.hpp" + +namespace lithium { +class ProjectManager { +public: + enum class VcsType { Git, Svn }; + + ProjectManager(VcsType type, const std::string& repoPath); + ~ProjectManager(); + + auto initRepository() -> bool; + auto cloneRepository(const std::string& url) -> bool; + auto createBranch(const std::string& branchName) -> bool; + auto checkoutBranch(const std::string& branchName) -> bool; + auto mergeBranch(const std::string& branchName) -> bool; + auto addFile(const std::string& filePath) -> bool; + auto commitChanges(const std::string& message) -> bool; + auto pull(const std::string& remoteName, const std::string& branchName) -> bool; + auto push(const std::string& remoteName, const std::string& branchName) -> bool; + auto getLog(int limit = 10) -> std::vector; + auto update() -> bool; + +private: + std::unique_ptr vcsManager; +}; + +} // namespace lithium + +#endif diff --git a/src/addon/project/base.hpp b/src/addon/project/base.hpp new file mode 100644 index 00000000..8dc55595 --- /dev/null +++ b/src/addon/project/base.hpp @@ -0,0 +1,28 @@ +#ifndef LITHIUM_ADDON_VCSMANAGER_HPP +#define LITHIUM_ADDON_VCSMANAGER_HPP + +#include +#include + +namespace lithium { +class VcsManager { +public: + virtual ~VcsManager() = default; + + virtual auto initRepository() -> bool = 0; + virtual auto cloneRepository(const std::string& url) -> bool = 0; + virtual auto createBranch(const std::string& branchName) -> bool = 0; + virtual auto checkoutBranch(const std::string& branchName) -> bool = 0; + virtual auto mergeBranch(const std::string& branchName) -> bool = 0; + virtual auto addFile(const std::string& filePath) -> bool = 0; + virtual auto commitChanges(const std::string& message) -> bool = 0; + virtual auto pull(const std::string& remoteName, + const std::string& branchName) -> bool = 0; + virtual auto push(const std::string& remoteName, + const std::string& branchName) -> bool = 0; + virtual auto getLog(int limit = 10) -> std::vector = 0; +}; + +} // namespace lithium + +#endif // LITHIUM_ADDON_VCSMANAGER_HPP diff --git a/src/addon/project/git.cpp b/src/addon/project/git.cpp new file mode 100644 index 00000000..7bd0a462 --- /dev/null +++ b/src/addon/project/git.cpp @@ -0,0 +1,45 @@ +#include "git.hpp" +#include "git_impl.hpp" + +namespace lithium { +GitManager::GitManager(const std::string& repoPath) + : impl(new Impl(repoPath)) {} +GitManager::~GitManager() = default; + +auto GitManager::initRepository() -> bool { return impl->initRepository(); } + +auto GitManager::cloneRepository(const std::string& url) -> bool { + return impl->cloneRepository(url); +} + +auto GitManager::createBranch(const std::string& branchName) -> bool { + return impl->createBranch(branchName); +} + +auto GitManager::checkoutBranch(const std::string& branchName) -> bool { + return impl->checkoutBranch(branchName); +} + +auto GitManager::mergeBranch(const std::string& branchName) -> bool { + return impl->mergeBranch(branchName); +} + +auto GitManager::addFile(const std::string& filePath) -> bool { + return impl->addFile(filePath); +} + +auto GitManager::commitChanges(const std::string& message) -> bool { + return impl->commitChanges(message); +} + +auto GitManager::pull(const std::string& remoteName, + const std::string& branchName) -> bool { + return impl->pull(remoteName, branchName); +} + +auto GitManager::push(const std::string& remoteName, + const std::string& branchName) -> bool { + return impl->push(remoteName, branchName); +} + +} // namespace lithium diff --git a/src/addon/project/git.hpp b/src/addon/project/git.hpp new file mode 100644 index 00000000..8aa2148d --- /dev/null +++ b/src/addon/project/git.hpp @@ -0,0 +1,29 @@ +#ifndef LITHIUM_ADDON_GITMANAGER_HPP +#define LITHIUM_ADDON_GITMANAGER_HPP + +#include +#include + +namespace lithium { +class GitManager { +public: + explicit GitManager(const std::string& repoPath); + ~GitManager(); + + bool initRepository(); + bool cloneRepository(const std::string& url); + bool createBranch(const std::string& branchName); + bool checkoutBranch(const std::string& branchName); + bool mergeBranch(const std::string& branchName); + bool addFile(const std::string& filePath); + bool commitChanges(const std::string& message); + bool pull(const std::string& remoteName, const std::string& branchName); + bool push(const std::string& remoteName, const std::string& branchName); + +private: + class Impl; + std::unique_ptr impl; +}; +} // namespace lithium + +#endif // LITHIUM_ADDON_GITMANAGER_HPP diff --git a/src/addon/project/git_impl.cpp b/src/addon/project/git_impl.cpp new file mode 100644 index 00000000..45a57bd0 --- /dev/null +++ b/src/addon/project/git_impl.cpp @@ -0,0 +1,533 @@ +#include "git_impl.hpp" + +#include + +#include "atom/log/loguru.hpp" + +namespace lithium { +GitManager::Impl::Impl(const std::string& repoPath) + : repoPath(repoPath), repo(nullptr) { + git_libgit2_init(); +} + +GitManager::Impl::~Impl() { + if (repo != nullptr) { + git_repository_free(repo); + } + git_libgit2_shutdown(); +} + +void GitManager::Impl::printError(int error) { + const git_error* err = git_error_last(); + if (err != nullptr) { + LOG_F(ERROR, "Error {}: {}", error, err->message); + } else { + LOG_F(ERROR, "Unknown error: {}", error); + } +} + +auto GitManager::Impl::initRepository() -> bool { + int error = git_repository_init(&repo, repoPath.c_str(), 0); + if (error < 0) { + printError(error); + return false; + } + return true; +} + +auto GitManager::Impl::cloneRepository(const std::string& url) -> bool { + int error = git_clone(&repo, url.c_str(), repoPath.c_str(), nullptr); + if (error < 0) { + printError(error); + return false; + } + return true; +} + +auto GitManager::Impl::createBranch(const std::string& branchName) -> bool { + git_reference* newBranchRef = nullptr; + git_oid commitOid; + + int error = git_reference_name_to_id(&commitOid, repo, "HEAD"); + if (error < 0) { + printError(error); + return false; + } + + git_commit* targetCommit = nullptr; + error = git_commit_lookup(&targetCommit, repo, &commitOid); + if (error < 0) { + printError(error); + return false; + } + + error = git_branch_create(&newBranchRef, repo, branchName.c_str(), + targetCommit, 0); + git_commit_free(targetCommit); + if (error < 0) { + printError(error); + return false; + } + return true; +} + +auto GitManager::Impl::checkoutBranch(const std::string& branchName) -> bool { + git_object* treeish = nullptr; + int error = git_revparse_single(&treeish, repo, + ("refs/heads/" + branchName).c_str()); + if (error < 0) { + printError(error); + return false; + } + + error = git_checkout_tree(repo, treeish, nullptr); + git_object_free(treeish); + if (error < 0) { + printError(error); + return false; + } + + error = git_repository_set_head(repo, ("refs/heads/" + branchName).c_str()); + if (error < 0) { + printError(error); + return false; + } + return true; +} + +auto GitManager::Impl::mergeBranch(const std::string& branchName) -> bool { + git_reference* branchRef; + int error = git_branch_lookup(&branchRef, repo, branchName.c_str(), + GIT_BRANCH_LOCAL); + if (error < 0) { + printError(error); + return false; + } + + git_commit* branchCommit; + error = + git_commit_lookup(&branchCommit, repo, git_reference_target(branchRef)); + if (error < 0) { + git_reference_free(branchRef); + printError(error); + return false; + } + + git_merge_analysis_t analysis; + git_merge_preference_t preference; + error = git_merge_analysis(&analysis, &preference, repo, + (const git_annotated_commit**)&branchCommit, 1); + if (error < 0) { + git_commit_free(branchCommit); + git_reference_free(branchRef); + printError(error); + return false; + } + + if ((analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) != 0) { + LOG_F(INFO, "Already up-to-date."); + git_commit_free(branchCommit); + git_reference_free(branchRef); + return true; + } + + if ((analysis & GIT_MERGE_ANALYSIS_FASTFORWARD) != 0) { + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + error = git_checkout_tree(repo, (const git_object*)branchCommit, &opts); + if (error < 0) { + git_commit_free(branchCommit); + git_reference_free(branchRef); + printError(error); + return false; + } + + error = git_repository_set_head(repo, git_reference_name(branchRef)); + if (error < 0) { + git_commit_free(branchCommit); + git_reference_free(branchRef); + printError(error); + return false; + } + } else { + // Perform a non-fast-forward merge + git_index* index; + error = git_merge(repo, (const git_annotated_commit**)&branchCommit, 1, + nullptr, nullptr); + if (error < 0) { + git_commit_free(branchCommit); + git_reference_free(branchRef); + printError(error); + return false; + } + + error = git_repository_index(&index, repo); + if (error < 0) { + git_commit_free(branchCommit); + git_reference_free(branchRef); + printError(error); + return false; + } + + if (git_index_has_conflicts(index) != 0) { + LOG_F(ERROR, "Merge conflicts detected. Please resolve them."); + git_index_free(index); + git_commit_free(branchCommit); + git_reference_free(branchRef); + return false; + } + + git_oid treeOid; + error = git_index_write_tree(&treeOid, index); + git_index_free(index); + if (error < 0) { + git_commit_free(branchCommit); + git_reference_free(branchRef); + printError(error); + return false; + } + + git_tree* tree; + error = git_tree_lookup(&tree, repo, &treeOid); + if (error < 0) { + git_commit_free(branchCommit); + git_reference_free(branchRef); + printError(error); + return false; + } + + git_commit* headCommit; + error = git_reference_name_to_id(&treeOid, repo, "HEAD"); + if (error < 0) { + git_tree_free(tree); + git_commit_free(branchCommit); + git_reference_free(branchRef); + printError(error); + return false; + } + + error = git_commit_lookup(&headCommit, repo, &treeOid); + if (error < 0) { + git_tree_free(tree); + git_commit_free(branchCommit); + git_reference_free(branchRef); + printError(error); + return false; + } + + const git_commit* parents[] = {headCommit, branchCommit}; + git_signature* signature; + error = + git_signature_now(&signature, "Author Name", "email@example.com"); + if (error < 0) { + git_tree_free(tree); + git_commit_free(branchCommit); + git_commit_free(headCommit); + git_reference_free(branchRef); + printError(error); + return false; + } + + git_oid commitOid; + error = + git_commit_create_v(&commitOid, repo, "HEAD", signature, signature, + nullptr, "Merge commit", tree, 2, parents); + git_signature_free(signature); + git_tree_free(tree); + git_commit_free(branchCommit); + git_commit_free(headCommit); + git_reference_free(branchRef); + if (error < 0) { + printError(error); + return false; + } + } + + return true; +} + +auto GitManager::Impl::addFile(const std::string& filePath) -> bool { + git_index* index; + int error = git_repository_index(&index, repo); + if (error < 0) { + printError(error); + return false; + } + + error = git_index_add_bypath(index, filePath.c_str()); + if (error < 0) { + printError(error); + git_index_free(index); + return false; + } + + error = git_index_write(index); + if (error < 0) { + printError(error); + git_index_free(index); + return false; + } + + git_index_free(index); + return true; +} + +auto GitManager::Impl::commitChanges(const std::string& message) -> bool { + git_oid treeId; + git_oid commitId; + git_index* index; + git_tree* tree; + git_signature* sig; + + int error = git_repository_index(&index, repo); + if (error < 0) { + printError(error); + return false; + } + + error = git_index_write_tree(&treeId, index); + if (error < 0) { + printError(error); + git_index_free(index); + return false; + } + + error = git_tree_lookup(&tree, repo, &treeId); + if (error < 0) { + printError(error); + git_index_free(index); + return false; + } + + error = git_signature_now(&sig, "Author Name", "email@example.com"); + if (error < 0) { + printError(error); + git_tree_free(tree); + git_index_free(index); + return false; + } + + error = git_commit_create_v(&commitId, repo, "HEAD", sig, sig, nullptr, + message.c_str(), tree, 0, nullptr); + if (error < 0) { + printError(error); + git_signature_free(sig); + git_tree_free(tree); + git_index_free(index); + return false; + } + + git_signature_free(sig); + git_tree_free(tree); + git_index_free(index); + return true; +} + +auto GitManager::Impl::pull(const std::string& remoteName, + const std::string& branchName) -> bool { + git_remote* remote = nullptr; + int error = git_remote_lookup(&remote, repo, remoteName.c_str()); + if (error < 0) { + printError(error); + return false; + } + + error = git_remote_fetch(remote, nullptr, nullptr, nullptr); + if (error < 0) { + printError(error); + git_remote_free(remote); + return false; + } + + git_reference* ref = nullptr; + error = git_reference_lookup( + &ref, repo, ("refs/remotes/" + remoteName + "/" + branchName).c_str()); + if (error < 0) { + printError(error); + git_remote_free(remote); + return false; + } + + git_merge_analysis_t analysis; + git_merge_preference_t preference; + git_annotated_commit* commit; + error = git_annotated_commit_from_ref(&commit, repo, ref); + if (error < 0) { + printError(error); + git_remote_free(remote); + git_reference_free(ref); + return false; + } + + error = + git_merge_analysis_for_ref(&analysis, &preference, repo, ref, + (const git_annotated_commit**)&commit, 1); + if (error < 0) { + printError(error); + git_annotated_commit_free(commit); + git_remote_free(remote); + git_reference_free(ref); + return false; + } + + if ((analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) != 0) { + LOG_F(INFO, "Already up-to-date."); + git_annotated_commit_free(commit); + git_remote_free(remote); + git_reference_free(ref); + return true; + } + + if ((analysis & GIT_MERGE_ANALYSIS_FASTFORWARD) != 0) { + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + error = git_checkout_tree(repo, (const git_object*)commit, &opts); + if (error < 0) { + printError(error); + git_annotated_commit_free(commit); + git_remote_free(remote); + git_reference_free(ref); + return false; + } + + error = git_repository_set_head(repo, git_reference_name(ref)); + if (error < 0) { + printError(error); + git_annotated_commit_free(commit); + git_remote_free(remote); + git_reference_free(ref); + return false; + } + } else { + // Perform a non-fast-forward merge + git_index* index; + error = git_merge(repo, (const git_annotated_commit**)&commit, 1, + nullptr, nullptr); + if (error < 0) { + printError(error); + git_annotated_commit_free(commit); + git_remote_free(remote); + git_reference_free(ref); + return false; + } + + error = git_repository_index(&index, repo); + if (error < 0) { + printError(error); + git_annotated_commit_free(commit); + git_remote_free(remote); + git_reference_free(ref); + return false; + } + + if (git_index_has_conflicts(index) != 0) { + LOG_F(ERROR, "Merge conflicts detected."); + git_index_free(index); + git_annotated_commit_free(commit); + git_remote_free(remote); + git_reference_free(ref); + return false; + } + + git_oid treeOid; + error = git_index_write_tree(&treeOid, index); + git_index_free(index); + if (error < 0) { + printError(error); + git_annotated_commit_free(commit); + git_remote_free(remote); + git_reference_free(ref); + return false; + } + + git_tree* tree; + error = git_tree_lookup(&tree, repo, &treeOid); + if (error < 0) { + printError(error); + git_annotated_commit_free(commit); + git_remote_free(remote); + git_reference_free(ref); + return false; + } + + git_commit* headCommit; + error = git_reference_name_to_id(&treeOid, repo, "HEAD"); + if (error < 0) { + git_tree_free(tree); + git_annotated_commit_free(commit); + git_remote_free(remote); + git_reference_free(ref); + printError(error); + return false; + } + + error = git_commit_lookup(&headCommit, repo, &treeOid); + if (error < 0) { + git_tree_free(tree); + git_annotated_commit_free(commit); + git_remote_free(remote); + git_reference_free(ref); + printError(error); + return false; + } + + const git_commit* parents[] = {headCommit, (const git_commit*)commit}; + git_signature* signature; + error = + git_signature_now(&signature, "Author Name", "email@example.com"); + if (error < 0) { + git_tree_free(tree); + git_commit_free(headCommit); + git_annotated_commit_free(commit); + git_remote_free(remote); + git_reference_free(ref); + printError(error); + return false; + } + + git_oid commitOid; + error = + git_commit_create_v(&commitOid, repo, "HEAD", signature, signature, + nullptr, "Merge commit", tree, 2, parents); + git_signature_free(signature); + git_tree_free(tree); + git_commit_free(headCommit); + git_annotated_commit_free(commit); + git_remote_free(remote); + git_reference_free(ref); + if (error < 0) { + printError(error); + return false; + } + } + + return true; +} + +auto GitManager::Impl::push(const std::string& remoteName, + const std::string& branchName) -> bool { + git_remote* remote = nullptr; + int error = git_remote_lookup(&remote, repo, remoteName.c_str()); + if (error < 0) { + printError(error); + return false; + } + + std::string refspec = + "refs/heads/" + branchName + ":refs/heads/" + branchName; + const char* refspecs[] = {refspec.c_str()}; + git_strarray refspecArray = {const_cast(refspecs), 1}; + error = git_remote_push(remote, &refspecArray, nullptr); + if (error < 0) { + printError(error); + git_remote_free(remote); + return false; + } + + git_remote_free(remote); + return true; +} + +} // namespace lithium diff --git a/src/addon/project/git_impl.hpp b/src/addon/project/git_impl.hpp new file mode 100644 index 00000000..1592bdf0 --- /dev/null +++ b/src/addon/project/git_impl.hpp @@ -0,0 +1,35 @@ +#ifndef LITHIUM_ADDON_GITMANAGERIMPL_HPP +#define LITHIUM_ADDON_GITMANAGERIMPL_HPP + +#include +#include + +#include "git.hpp" + +namespace lithium { +class GitManager::Impl { +public: + explicit Impl(const std::string& repoPath); + ~Impl(); + + auto initRepository() -> bool; + auto cloneRepository(const std::string& url) -> bool; + auto createBranch(const std::string& branchName) -> bool; + auto checkoutBranch(const std::string& branchName) -> bool; + auto mergeBranch(const std::string& branchName) -> bool; + auto addFile(const std::string& filePath) -> bool; + auto commitChanges(const std::string& message) -> bool; + auto pull(const std::string& remoteName, + const std::string& branchName) -> bool; + auto push(const std::string& remoteName, + const std::string& branchName) -> bool; + +private: + std::string repoPath; + git_repository* repo; + + void printError(int error); +}; +} // namespace lithium + +#endif // LITHIUM_ADDON_GITMANAGERIMPL_HPP diff --git a/src/addon/project/svn.cpp b/src/addon/project/svn.cpp new file mode 100644 index 00000000..9ded56c3 --- /dev/null +++ b/src/addon/project/svn.cpp @@ -0,0 +1,36 @@ +#include "svn.hpp" +#include +#include "svn_impl.hpp" + +namespace lithium { +SvnManager::SvnManager(const std::string& repoPath) + : impl_(std::make_unique(repoPath)) {} +SvnManager::~SvnManager() = default; + +bool SvnManager::checkout(const std::string& url, const std::string& revision) { + return impl_->checkout(url, revision); +} + +bool SvnManager::addFile(const std::string& filePath) { + return impl_->addFile(filePath); +} + +bool SvnManager::commitChanges(const std::string& message) { + return impl_->commitChanges(message); +} + +bool SvnManager::update() { return impl_->update(); } + +bool SvnManager::createBranch(const std::string& branchName) { + return impl_->createBranch(branchName); +} + +bool SvnManager::mergeBranch(const std::string& branchName) { + return impl_->mergeBranch(branchName); +} + +std::vector SvnManager::getLog(int limit) { + return impl_->getLog(limit); +} + +} // namespace lithium diff --git a/src/addon/project/svn.hpp b/src/addon/project/svn.hpp new file mode 100644 index 00000000..83dc6140 --- /dev/null +++ b/src/addon/project/svn.hpp @@ -0,0 +1,28 @@ +#ifndef LITHIUM_ADDON_SVNMANAGER_HPP +#define LITHIUM_ADDON_SVNMANAGER_HPP + +#include +#include +#include + +namespace lithium { +class SvnManager { +public: + SvnManager(const std::string& repoPath); + ~SvnManager(); + + auto checkout(const std::string& url, const std::string& revision) -> bool; + auto addFile(const std::string& filePath) -> bool; + auto commitChanges(const std::string& message) -> bool; + auto update() -> bool; + auto createBranch(const std::string& branchName) -> bool; + auto mergeBranch(const std::string& branchName) -> bool; + auto getLog(int limit = 10) -> std::vector; + +private: + class Impl; + std::unique_ptr impl_; +}; +} // namespace lithium + +#endif // LITHIUM_ADDON_SVNMANAGER_HPP diff --git a/src/addon/project/svn_impl.cpp b/src/addon/project/svn_impl.cpp new file mode 100644 index 00000000..a31fd6d6 --- /dev/null +++ b/src/addon/project/svn_impl.cpp @@ -0,0 +1,157 @@ +#include "svn_impl.hpp" + +#include +#include +#include +#include + +namespace lithium { +SvnManager::Impl::Impl(const std::string& repoPath) + : repoPath(repoPath), ctx(nullptr), pool(nullptr) { + apr_initialize(); + pool = svn_pool_create(nullptr); + + svn_client_create_context2(&ctx, nullptr, pool); +} + +SvnManager::Impl::~Impl() { + if (pool) { + svn_pool_destroy(pool); + } + apr_terminate(); +} + +void SvnManager::Impl::printError(svn_error_t* err) { + if (err) { + char buf[1024]; + svn_error_clear(svn_cmdline_cstring_from_utf8_fuzzy(buf, sizeof(buf), + err->message)); + std::cerr << "SVN Error: " << buf << std::endl; + svn_error_clear(err); + } +} + +bool SvnManager::Impl::checkout(const std::string& url, + const std::string& revision) { + svn_revnum_t rev; + svn_opt_revision_t peg_revision = {svn_opt_revision_unspecified}; + svn_opt_revision_t opt_revision = {svn_opt_revision_unspecified}; + + opt_revision.kind = svn_opt_revision_head; + svn_error_t* err = svn_client_checkout3( + &rev, url.c_str(), repoPath.c_str(), &peg_revision, &opt_revision, + svn_depth_infinity, FALSE, FALSE, ctx, pool); + if (err) { + printError(err); + return false; + } + return true; +} + +bool SvnManager::Impl::addFile(const std::string& filePath) { + svn_error_t* err = svn_client_add4(filePath.c_str(), svn_depth_infinity, + FALSE, FALSE, FALSE, ctx, pool); + if (err) { + printError(err); + return false; + } + return true; +} + +bool SvnManager::Impl::commitChanges(const std::string& message) { + apr_array_header_t* targets = apr_array_make(pool, 1, sizeof(const char*)); + APR_ARRAY_PUSH(targets, const char*) = repoPath.c_str(); + + svn_commit_info_t* commit_info = nullptr; + svn_error_t* err = + svn_client_commit6(targets, svn_depth_infinity, FALSE, FALSE, FALSE, + nullptr, message.c_str(), nullptr, ctx, pool); + if (err) { + printError(err); + return false; + } + return true; +} + +bool SvnManager::Impl::update() { + apr_array_header_t* targets = apr_array_make(pool, 1, sizeof(const char*)); + APR_ARRAY_PUSH(targets, const char*) = repoPath.c_str(); + + svn_error_t* err = + svn_client_update3(nullptr, targets, svn_depth_infinity, FALSE, FALSE, + FALSE, FALSE, ctx, pool); + if (err) { + printError(err); + return false; + } + return true; +} + +bool SvnManager::Impl::createBranch(const std::string& branchName) { + std::string branchUrl = repoPath + "/branches/" + branchName; + svn_opt_revision_t revision = {svn_opt_revision_head}; + + svn_error_t* err = svn_client_copy4(nullptr, repoPath.c_str(), &revision, + branchUrl.c_str(), svn_depth_infinity, + FALSE, FALSE, nullptr, ctx, pool); + if (err) { + printError(err); + return false; + } + return true; +} + +bool SvnManager::Impl::mergeBranch(const std::string& branchName) { + std::string branchUrl = repoPath + "/branches/" + branchName; + svn_opt_revision_t peg_revision = {svn_opt_revision_head}; + svn_opt_revision_t revision = {svn_opt_revision_head}; + + svn_error_t* err = svn_client_merge_peg5( + branchUrl.c_str(), nullptr, &peg_revision, &revision, repoPath.c_str(), + svn_depth_infinity, FALSE, FALSE, FALSE, FALSE, FALSE, nullptr, ctx, + pool); + if (err) { + printError(err); + return false; + } + return true; +} + +std::vector SvnManager::Impl::getLog(int limit) { + std::vector logMessages; + + struct LogReceiverBaton { + std::vector* logMessages; + int remaining; + }; + + LogReceiverBaton baton = {&logMessages, limit}; + + auto log_receiver = [](void* baton, svn_log_entry_t* log_entry, + apr_pool_t* pool) -> svn_error_t* { + auto* b = static_cast(baton); + if (b->remaining-- <= 0) + return SVN_NO_ERROR; + b->logMessages->emplace_back( + log_entry->revprops->nelts > 0 + ? svn_string_value(log_entry->revprops->nelts) + : "No message"); + return SVN_NO_ERROR; + }; + + svn_opt_revision_t start = {svn_opt_revision_head}; + svn_opt_revision_t end = {svn_opt_revision_number, 0}; + + svn_error_t* err = + svn_client_log5(repoPath.c_str(), nullptr, &start, &end, 0, FALSE, TRUE, + FALSE, apr_array_make(pool, 1, sizeof(const char*)), + log_receiver, &baton, ctx, pool); + if (err) { + printError(err); + return {}; + } + + return logMessages; +} + +} // namespace lithium diff --git a/src/addon/project/svn_impl.hpp b/src/addon/project/svn_impl.hpp new file mode 100644 index 00000000..36bbdd91 --- /dev/null +++ b/src/addon/project/svn_impl.hpp @@ -0,0 +1,35 @@ +#ifndef LITHIUM_ADDON_SVNMANAGERIMPL_HPP +#define LITHIUM_ADDON_SVNMANAGERIMPL_HPP + +#include +#include + +#include "svn.hpp" + +#include + +namespace lithium { +class SvnManager::Impl { +public: + Impl(const std::string& repoPath); + ~Impl(); + + bool checkout(const std::string& url, const std::string& revision); + bool addFile(const std::string& filePath); + bool commitChanges(const std::string& message); + bool update(); + bool createBranch(const std::string& branchName); + bool mergeBranch(const std::string& branchName); + std::vector getLog(int limit); + +private: + std::string repoPath; + svn_client_ctx_t* ctx; + apr_pool_t* pool; + + void printError(svn_error_t* err); +}; + +} // namespace lithium + +#endif // LITHIUM_ADDON_SVNMANAGERIMPL_HPP diff --git a/src/addon/sandbox.cpp b/src/addon/sandbox.cpp index 37598a70..2f632780 100644 --- a/src/addon/sandbox.cpp +++ b/src/addon/sandbox.cpp @@ -4,22 +4,16 @@ * Copyright (C) 2023-2024 Max Qian */ -/************************************************* - -Date: 2024-1-4 - -Description: A sandbox for alone componnents, such as executables. - -**************************************************/ - #include "sandbox.hpp" -#include "atom/utils/convert.hpp" - #ifdef _WIN32 +// clang-format off #include #include +// clang-format on #else +#include +#include #include #include #include @@ -28,8 +22,69 @@ Description: A sandbox for alone componnents, such as executables. #endif namespace lithium { -bool Sandbox::setTimeLimit(int timeLimitMs) { - m_timeLimit = timeLimitMs; + +class SandboxImpl { +public: + int mTimeLimit{0}; + long mMemoryLimit{0}; + std::string mRootDirectory; + int mUserId{0}; + std::string mProgramPath; + std::vector mProgramArgs; + int mTimeUsed{0}; + long mMemoryUsed{0}; + + auto setTimeLimit(int timeLimitMs) -> bool; + auto setMemoryLimit(long memoryLimitKb) -> bool; + auto setRootDirectory(const std::string& rootDirectory) -> bool; + auto setUserId(int userId) -> bool; + auto setProgramPath(const std::string& programPath) -> bool; + auto setProgramArgs(const std::vector& programArgs) -> bool; + auto run() -> bool; + +#ifdef _WIN32 + auto setWindowsLimits(PROCESS_INFORMATION& processInfo) const -> bool; +#else + bool setUnixLimits(); + bool applySeccomp(); +#endif +}; + +Sandbox::Sandbox() : pimpl(std::make_unique()) {} + +Sandbox::~Sandbox() = default; + +auto Sandbox::setTimeLimit(int timeLimitMs) -> bool { + return pimpl->setTimeLimit(timeLimitMs); +} + +auto Sandbox::setMemoryLimit(long memoryLimitKb) -> bool { + return pimpl->setMemoryLimit(memoryLimitKb); +} + +auto Sandbox::setRootDirectory(const std::string& rootDirectory) -> bool { + return pimpl->setRootDirectory(rootDirectory); +} + +auto Sandbox::setUserId(int userId) -> bool { return pimpl->setUserId(userId); } + +auto Sandbox::setProgramPath(const std::string& programPath) -> bool { + return pimpl->setProgramPath(programPath); +} + +auto Sandbox::setProgramArgs(const std::vector& programArgs) + -> bool { + return pimpl->setProgramArgs(programArgs); +} + +auto Sandbox::run() -> bool { return pimpl->run(); } + +auto Sandbox::getTimeUsed() const -> int { return pimpl->mTimeUsed; } + +auto Sandbox::getMemoryUsed() const -> long { return pimpl->mMemoryUsed; } + +auto SandboxImpl::setTimeLimit(int timeLimitMs) -> bool { + mTimeLimit = timeLimitMs; #ifndef _WIN32 rlimit limit{.rlim_cur = static_cast(timeLimitMs) / 1000, .rlim_max = static_cast(timeLimitMs) / 1000}; @@ -38,8 +93,8 @@ bool Sandbox::setTimeLimit(int timeLimitMs) { return true; } -bool Sandbox::setMemoryLimit(long memoryLimitKb) { - m_memoryLimit = memoryLimitKb; +auto SandboxImpl::setMemoryLimit(long memoryLimitKb) -> bool { + mMemoryLimit = memoryLimitKb; #ifndef _WIN32 rlimit limit{.rlim_cur = static_cast(memoryLimitKb) * 1024, .rlim_max = static_cast(memoryLimitKb) * 1024}; @@ -48,107 +103,179 @@ bool Sandbox::setMemoryLimit(long memoryLimitKb) { return true; } -bool Sandbox::setRootDirectory(const std::string& rootDirectory) { - m_rootDirectory = rootDirectory; +auto SandboxImpl::setRootDirectory(const std::string& rootDirectory) -> bool { + mRootDirectory = rootDirectory; #ifndef _WIN32 return (chdir(rootDirectory.c_str()) == 0) && (chroot(rootDirectory.c_str()) == 0); #endif - return false; + return true; } -bool Sandbox::setUserId(int userId) { - m_userId = userId; +auto SandboxImpl::setUserId(int userId) -> bool { + mUserId = userId; #ifndef _WIN32 return (setuid(userId) == 0) && (setgid(userId) == 0); #endif - return false; + return true; } -bool Sandbox::setProgramPath(const std::string& programPath) { - m_programPath = programPath; +auto SandboxImpl::setProgramPath(const std::string& programPath) -> bool { + mProgramPath = programPath; return true; } -bool Sandbox::setProgramArgs(const std::vector& programArgs) { - m_programArgs = programArgs; +auto SandboxImpl::setProgramArgs(const std::vector& programArgs) + -> bool { + mProgramArgs = programArgs; return true; } -bool Sandbox::run() { +#ifdef _WIN32 +auto SandboxImpl::setWindowsLimits(PROCESS_INFORMATION& processInfo) const + -> bool { + const HANDLE PROCESS_HANDLE = processInfo.hProcess; + + if (mTimeLimit > 0) { + SetProcessAffinityMask(PROCESS_HANDLE, + static_cast(mTimeLimit)); + } + + if (mMemoryLimit > 0) { + SetProcessWorkingSetSize(PROCESS_HANDLE, + static_cast(mMemoryLimit), + static_cast(mMemoryLimit)); + } + + return true; +} +#else +auto SandboxImpl::setUnixLimits() -> bool { + if (mTimeLimit > 0) { + rlimit limit{.rlim_cur = static_cast(mTimeLimit) / 1000, + .rlim_max = static_cast(mTimeLimit) / 1000}; + if (setrlimit(RLIMIT_CPU, &limit) != 0) { + return false; + } + } + + if (mMemoryLimit > 0) { + rlimit limit{.rlim_cur = static_cast(mMemoryLimit) * 1024, + .rlim_max = static_cast(mMemoryLimit) * 1024}; + if (setrlimit(RLIMIT_AS, &limit) != 0) { + return false; + } + } + + return true; +} + +auto SandboxImpl::applySeccomp() -> bool { + scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); // Default action: kill + if (ctx == nullptr) { + return false; + } + + // Allow necessary syscalls + seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(execve), 0); + seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0); + seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); + seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0); + seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0); + seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(munmap), 0); + seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 1, + SCMP_A1(SCMP_CMP_MASKED_EQ, O_WRONLY, O_WRONLY)); + seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0); + seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); + seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0); + + // Load the filter + if (seccomp_load(ctx) != 0) { + seccomp_release(ctx); + return false; + } + + seccomp_release(ctx); + return true; +} +#endif + +auto SandboxImpl::run() -> bool { #ifdef _WIN32 STARTUPINFO startupInfo{}; startupInfo.cb = sizeof(startupInfo); PROCESS_INFORMATION processInfo{}; - std::string commandLine = m_programPath; - for (const auto& arg : m_programArgs) { + std::string commandLine = mProgramPath; + for (const auto& arg : mProgramArgs) { commandLine += ' ' + arg; } - if (!CreateProcess(nullptr, commandLine.data(), - nullptr, nullptr, FALSE, CREATE_SUSPENDED, nullptr, - nullptr, &startupInfo, &processInfo)) { + if (!CreateProcess(nullptr, commandLine.data(), nullptr, nullptr, FALSE, + CREATE_SUSPENDED, nullptr, nullptr, &startupInfo, + &processInfo)) { return false; } - const HANDLE processHandle = processInfo.hProcess; - - if (m_timeLimit > 0) { - SetProcessAffinityMask(processHandle, - static_cast(m_timeLimit)); - } - - if (m_memoryLimit > 0) { - SetProcessWorkingSetSize(processHandle, - static_cast(m_memoryLimit), - static_cast(m_memoryLimit)); + if (!setWindowsLimits(processInfo)) { + return; } ResumeThread(processInfo.hThread); - WaitForSingleObject(processHandle, INFINITE); + WaitForSingleObject(processInfo.hProcess, INFINITE); DWORD exitCode = 0; - GetExitCodeProcess(processHandle, &exitCode); + GetExitCodeProcess(processInfo.hProcess, &exitCode); PROCESS_MEMORY_COUNTERS memoryCounters{}; - GetProcessMemoryInfo(processHandle, &memoryCounters, + GetProcessMemoryInfo(processInfo.hProcess, &memoryCounters, sizeof(memoryCounters)); - m_timeUsed = GetTickCount(); - m_memoryUsed = static_cast(memoryCounters.WorkingSetSize / 1024); + mTimeUsed = GetTickCount(); + mMemoryUsed = static_cast(memoryCounters.WorkingSetSize / 1024); - CloseHandle(processHandle); + CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); return exitCode == 0; #else - const pid_t pid = fork(); - if (pid < 0) { + const pid_t PID = fork(); + if (PID < 0) { return false; - } else if (pid == 0) { + } + if (PID == 0) { ptrace(PTRACE_TRACEME, 0, nullptr, nullptr); std::vector args; - args.reserve(m_programArgs.size() + 2); - args.emplace_back(m_programPath.data()); - for (const auto& arg : m_programArgs) { + args.reserve(mProgramArgs.size() + 2); + args.emplace_back(mProgramPath.data()); + for (const auto& arg : mProgramArgs) { args.emplace_back(const_cast(arg.c_str())); } args.emplace_back(nullptr); - execvp(m_programPath.c_str(), args.data()); + if (!setUnixLimits()) { + exit(1); + } + + if (!applySeccomp()) { + exit(1); + } + + execvp(mProgramPath.c_str(), args.data()); exit(0); } else { int status = 0; rusage usage{}; - while (wait4(pid, &status, 0, &usage) != pid) + while (wait4(PID, &status, 0, &usage) != PID) { ; - m_timeUsed = static_cast(usage.ru_utime.tv_sec * 1000 + - usage.ru_utime.tv_usec / 1000); - m_memoryUsed = static_cast(usage.ru_maxrss); + } + mTimeUsed = static_cast(usage.ru_utime.tv_sec * 1000 + + usage.ru_utime.tv_usec / 1000); + mMemoryUsed = static_cast(usage.ru_maxrss); return WIFEXITED(status) && WEXITSTATUS(status) == 0; } #endif } + } // namespace lithium diff --git a/src/addon/sandbox.hpp b/src/addon/sandbox.hpp index d4ab9498..33129c8e 100644 --- a/src/addon/sandbox.hpp +++ b/src/addon/sandbox.hpp @@ -8,110 +8,45 @@ Date: 2024-1-4 -Description: A sandbox for alone componnents, such as executables. +Description: A sandbox for isolated components, such as executables. **************************************************/ #ifndef LITHIUM_ADDON_SANDBOX_HPP #define LITHIUM_ADDON_SANDBOX_HPP +#include #include #include namespace lithium { + +class SandboxImpl; // Forward declaration of the implementation class + /** * @brief Sandbox class for running programs with time and memory limits in a * restricted environment. */ class Sandbox { public: - /** - * @brief Default constructor for Sandbox class. - */ - Sandbox() = default; - - /** - * @brief Default destructor for Sandbox class. - */ - ~Sandbox() = default; - - /** - * @brief Sets the time limit for program execution. - * @param timeLimitMs The time limit in milliseconds. - * @return True if the time limit is set successfully, false otherwise. - */ - bool setTimeLimit(int timeLimitMs); - - /** - * @brief Sets the memory limit for program execution. - * @param memoryLimitKb The memory limit in kilobytes. - * @return True if the memory limit is set successfully, false otherwise. - */ - bool setMemoryLimit(long memoryLimitKb); - - /** - * @brief Sets the root directory for the sandbox environment. - * @param rootDirectory The root directory path. - * @return True if the root directory is set successfully, false otherwise. - */ - bool setRootDirectory(const std::string& rootDirectory); + Sandbox(); + ~Sandbox(); - /** - * @brief Sets the user ID for running the program in the sandbox. - * @param userId The user ID. - * @return True if the user ID is set successfully, false otherwise. - */ - bool setUserId(int userId); + auto setTimeLimit(int timeLimitMs) -> bool; + auto setMemoryLimit(long memoryLimitKb) -> bool; + auto setRootDirectory(const std::string& rootDirectory) -> bool; + auto setUserId(int userId) -> bool; + auto setProgramPath(const std::string& programPath) -> bool; + auto setProgramArgs(const std::vector& programArgs) -> bool; + auto run() -> bool; - /** - * @brief Sets the path to the program to be executed in the sandbox. - * @param programPath The path to the program. - * @return True if the program path is set successfully, false otherwise. - */ - bool setProgramPath(const std::string& programPath); - - /** - * @brief Sets the arguments for the program to be executed in the sandbox. - * @param programArgs The vector of program arguments. - * @return True if the program arguments are set successfully, false - * otherwise. - */ - bool setProgramArgs(const std::vector& programArgs); - - /** - * @brief Runs the program in the sandbox environment. - * @return True if the program runs successfully within the time and memory - * limits, false otherwise. - */ - bool run(); - - /** - * @brief Retrieves the actual time used by the program during execution. - * @return The time used in milliseconds. - */ - [[nodiscard]] int getTimeUsed() const { return m_timeUsed; } - - /** - * @brief Retrieves the actual memory used by the program during execution. - * @return The memory used in kilobytes. - */ - [[nodiscard]] long getMemoryUsed() const { return m_memoryUsed; } + [[nodiscard]] auto getTimeUsed() const -> int; + [[nodiscard]] auto getMemoryUsed() const -> long; private: - int m_timeLimit = - 0; /**< Time limit for program execution in milliseconds. */ - long m_memoryLimit = - 0; /**< Memory limit for program execution in kilobytes. */ - std::string - m_rootDirectory; /**< Root directory for the sandbox environment. */ - int m_userId = 0; /**< User ID for running the program in the sandbox. */ - std::string m_programPath; /**< Path to the program to be executed. */ - std::vector m_programArgs; /**< Program arguments. */ - int m_timeUsed = - 0; /**< Actual time used by the program during execution. */ - long m_memoryUsed = - 0; /**< Actual memory used by the program during execution. */ + std::unique_ptr pimpl; // Pointer to the implementation class }; +} // namespace lithium + #endif -} diff --git a/src/addon/sort.cpp b/src/addon/sort.cpp deleted file mode 100644 index 6d17e105..00000000 --- a/src/addon/sort.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include "sort.hpp" - -#include -#include -#include -#include - -#include "atom/error/exception.hpp" -#include "atom/log/loguru.hpp" -#include "atom/type/json.hpp" -using json = nlohmann::json; - -namespace lithium { -std::vector removeDuplicates( - const std::vector& input) { - std::unordered_set seen; - std::vector result; - - for (const auto& element : input) { - if (seen.insert(element).second) { - result.push_back(element); - } - } - - return result; -} - -std::pair> parsePackageJson( - const std::string& path) { - std::ifstream file(path); - if (!file.is_open()) { - THROW_EXCEPTION("Failed to open " + path); - } - - json package_json; - try { - file >> package_json; - } catch (const json::exception& e) { - THROW_EXCEPTION("Error parsing JSON in " + path + ": " + e.what()); - } - - if (!package_json.contains("name")) { - THROW_EXCEPTION("Missing package name in " + path); - } - - std::string package_name = package_json["name"]; - std::vector deps; - - if (package_json.contains("dependencies")) { - for (auto& dep : package_json["dependencies"].items()) { - deps.push_back(dep.key()); - } - } - - file.close(); - return {package_name, deps}; -} - -std::vector resolveDependencies( - const std::vector& directories) { - std::unordered_map> dependency_graph; - std::unordered_map indegree; - std::vector sorted_packages; - std::unordered_set visited; - - for (const auto& dir : directories) { - std::string package_path = dir + "/package.json"; - auto [package_name, deps] = parsePackageJson(package_path); - - // 构建依赖图和入度表 - if (!dependency_graph.count(package_name)) { - dependency_graph[package_name] = {}; - indegree[package_name] = 0; - } - - for (const auto& dep : deps) { - dependency_graph[dep].push_back(package_name); - indegree[package_name]++; - } - } - - if (dependency_graph.empty()) { - LOG_F(ERROR, "No packages found."); - return {}; - } - - std::queue q; - for (const auto& [node, _] : dependency_graph) { - q.push(node); - } - - while (!q.empty()) { - std::string current = q.front(); - q.pop(); - sorted_packages.push_back(current); - visited.insert(current); - - for (const auto& neighbor : dependency_graph[current]) { - if (visited.count(neighbor)) { - LOG_F(WARNING, - "Circular dependency detected. Ignoring dependency from " - "{} to {}", - current, neighbor); - continue; - } - indegree[neighbor]--; - if (indegree[neighbor] == 0) { - q.push(neighbor); - } - } - } - - for (const auto& node : indegree) { - if (node.second > 0) { - LOG_F(WARNING, "Unresolved dependency for {}", node.first); - } - } - - if (sorted_packages.size() != dependency_graph.size()) { - LOG_F(WARNING, "Some packages were not included in the load order."); - } - - return removeDuplicates(sorted_packages); -} - -} // namespace lithium diff --git a/src/addon/sort.hpp b/src/addon/sort.hpp deleted file mode 100644 index 2d1c7149..00000000 --- a/src/addon/sort.hpp +++ /dev/null @@ -1,26 +0,0 @@ -/* - * sort.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-1-4 - -Description: A sandbox for alone componnents, such as executables. - -**************************************************/ - -#ifndef LITHIUM_COMPONENTS_SORT_HPP -#define LITHIUM_COMPONENTS_SORT_HPP - -#include -#include - -namespace lithium { -[[nodiscard("result is discarded")]] std::vector -resolveDependencies(const std::vector& directories); -} - -#endif diff --git a/src/addon/template/connector.hpp b/src/addon/template/connector.hpp new file mode 100644 index 00000000..0dd469db --- /dev/null +++ b/src/addon/template/connector.hpp @@ -0,0 +1,33 @@ +#ifndef LITHIUM_ADDON_TEMPLATE_CONNECTOR_HPP +#define LITHIUM_ADDON_TEMPLATE_CONNECTOR_HPP + +#include +#include +#include +#include + +class Connector { +public: + virtual ~Connector() = default; + virtual auto startServer() -> bool = 0; + virtual auto stopServer() -> bool = 0; + virtual auto isRunning() -> bool = 0; + virtual auto startDriver( + const std::shared_ptr& driver) -> bool = 0; + virtual auto stopDriver( + const std::shared_ptr& driver) -> bool = 0; + virtual auto setProp(const std::string& dev, const std::string& prop, + const std::string& element, + const std::string& value) -> bool = 0; + virtual auto getProp(const std::string& dev, const std::string& prop, + const std::string& element) -> std::string = 0; + virtual auto getState(const std::string& dev, + const std::string& prop) -> std::string = 0; + virtual auto getRunningDrivers() + -> std::unordered_map> = 0; + virtual auto getDevices() + -> std::vector> = 0; +}; + +#endif // LITHIUM_ADDON_TEMPLATE_CONNECTOR_HPP diff --git a/src/addon/template/standalone.cpp b/src/addon/template/standalone.cpp new file mode 100644 index 00000000..11c52a52 --- /dev/null +++ b/src/addon/template/standalone.cpp @@ -0,0 +1,389 @@ +#include "standalone.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include "macro.hpp" + +#if defined(_WIN32) || defined(_WIN64) +// clang-format off +#include +#include +#include +#include +#define pipe _pipe +#define popen _popen +#define pclose _pclose +// clang-format on +#else +#include +#include +#include +#include +#include +#endif + +struct LocalDriver { + int processHandle{}; + int stdinFd{}; + int stdoutFd{}; + std::string name; + bool isListening{}; +} ATOM_ALIGNAS(64); + +#if defined(_WIN32) || defined(_WIN64) +constexpr char SEM_NAME[] = "driver_semaphore"; +constexpr char SHM_NAME[] = "driver_shm"; +#else +constexpr char SEM_NAME[] = "/driver_semaphore"; +constexpr char SHM_NAME[] = "/driver_shm"; +#endif + +class StandAloneComponentImpl { +public: + LocalDriver driver; + std::atomic shouldExit{false}; + std::jthread driverThread; + + void handleDriverOutput(std::string_view driver_name, const char* buffer, + int length) { + LOG_F(INFO, "Output from driver {}: {}", driver_name, + std::string_view(buffer, length)); + } + + void closePipes(int stdinPipe[2], int stdoutPipe[2]) { +#if defined(_WIN32) || defined(_WIN64) + _close(stdinPipe[0]); + _close(stdinPipe[1]); + _close(stdoutPipe[0]); + _close(stdoutPipe[1]); +#else + close(stdinPipe[0]); + close(stdinPipe[1]); + close(stdoutPipe[0]); + close(stdoutPipe[1]); +#endif + } +}; + +StandAloneComponent::StandAloneComponent(std::string name) + : Component(std::move(name)), + impl_(std::make_unique()) { + doc("A standalone component that can be used to run a local driver"); + def("start", &StandAloneComponent::startLocalDriver); + def("stop", &StandAloneComponent::stopLocalDriver); + def("listen", &StandAloneComponent::toggleDriverListening); + def("send", &StandAloneComponent::sendMessageToDriver); + def("print", &StandAloneComponent::printDriver); + def("monitor", &StandAloneComponent::monitorDrivers); +} + +StandAloneComponent::~StandAloneComponent() { + LOG_F(INFO, "Component {} destroyed", getName()); +} + +void StandAloneComponent::startLocalDriver(const std::string& driver_name) { + int stdinPipe[2]; + int stdoutPipe[2]; + + if (!createPipes(stdinPipe, stdoutPipe)) { + return; + } + +#if defined(_WIN32) || defined(_WIN64) + startWindowsProcess(driver_name, stdinPipe, stdoutPipe); +#else + startUnixProcess(driver_name, stdinPipe, stdoutPipe); +#endif + impl_->driverThread = + std::jthread(&StandAloneComponent::backgroundProcessing, this); +} + +void StandAloneComponent::backgroundProcessing() { + while (!impl_->shouldExit) { + monitorDrivers(); + processMessages(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } +} + +auto StandAloneComponent::createPipes(int stdinPipe[2], + int stdoutPipe[2]) -> bool { +#if defined(_WIN32) || defined(_WIN64) + return _pipe(stdinPipe, 4096, _O_BINARY | _O_NOINHERIT) == 0 && + _pipe(stdoutPipe, 4096, _O_BINARY | _O_NOINHERIT) == 0; +#else + return pipe(stdinPipe) == 0 && pipe(stdoutPipe) == 0; +#endif +} + +#if defined(_WIN32) || defined(_WIN64) +void StandAloneComponent::startWindowsProcess(const std::string& driver_name, + int stdinPipe[2], + int stdoutPipe[2]) { + SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE}; + HANDLE hStdinRead, hStdinWrite, hStdoutRead, hStdoutWrite; + + if (!CreatePipe(&hStdinRead, &hStdinWrite, &sa, 0) || + !CreatePipe(&hStdoutRead, &hStdoutWrite, &sa, 0)) { + LOG_F(ERROR, "Failed to create pipes"); + return; + } + + SetHandleInformation(hStdoutRead, HANDLE_FLAG_INHERIT, 0); + SetHandleInformation(hStdinWrite, HANDLE_FLAG_INHERIT, 0); + + STARTUPINFO si = {sizeof(STARTUPINFO)}; + si.hStdError = hStdoutWrite; + si.hStdOutput = hStdoutWrite; + si.hStdInput = hStdinRead; + si.dwFlags |= STARTF_USESTDHANDLES; + + PROCESS_INFORMATION pi; + std::string cmd = driver_name; + + if (!CreateProcess(NULL, cmd.data(), NULL, NULL, TRUE, CREATE_NO_WINDOW, + NULL, NULL, &si, &pi)) { + LOG_F(ERROR, "Failed to start process"); + impl_->closePipes(stdinPipe, stdoutPipe); + return; + } + + CloseHandle(hStdoutWrite); + CloseHandle(hStdinRead); + + // Cast HANDLE to int (not recommended) + impl_->driver.processHandle = static_cast(reinterpret_cast(pi.hProcess)); + + impl_->driver.stdinFd = + _open_osfhandle(reinterpret_cast(hStdinWrite), 0); + impl_->driver.stdoutFd = + _open_osfhandle(reinterpret_cast(hStdoutRead), 0); + impl_->driver.name = driver_name; +} +#else +void StandAloneComponent::startUnixProcess(const std::string& driver_name, + int stdinPipe[2], + int stdoutPipe[2]) { + int shmFd; + int* shmPtr; + sem_t* sem; + + if (!createSharedMemory(shmFd, shmPtr)) { + impl_->closePipes(stdinPipe, stdoutPipe); + return; + } + + if (!createSemaphore(sem)) { + impl_->closePipes(stdinPipe, stdoutPipe); + closeSharedMemory(shmFd, shmPtr); + return; + } + + pid_t pid = fork(); + if (pid == 0) { + handleChildProcess(driver_name, stdinPipe, stdoutPipe, shmPtr, sem, + shmFd); + } else if (pid > 0) { + handleParentProcess(pid, stdinPipe, stdoutPipe, shmPtr, sem, shmFd); + } else { + LOG_F(ERROR, "Failed to fork driver process"); + impl_->closePipes(stdinPipe, stdoutPipe); + closeSharedMemory(shmFd, shmPtr); + sem_close(sem); + } +} + +auto StandAloneComponent::createSharedMemory(int& shm_fd, + int*& shm_ptr) -> bool { + shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666); + if (shm_fd == -1) { + LOG_F(ERROR, "Failed to create shared memory"); + return false; + } + + if (ftruncate(shm_fd, sizeof(int)) == -1) { + LOG_F(ERROR, "Failed to set size of shared memory"); + close(shm_fd); + shm_unlink(SHM_NAME); + return false; + } + + shm_ptr = static_cast(mmap( + nullptr, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0)); + if (shm_ptr == MAP_FAILED) { + LOG_F(ERROR, "Failed to map shared memory"); + close(shm_fd); + shm_unlink(SHM_NAME); + return false; + } + + *shm_ptr = 0; // Initialize shared memory to 0 + return true; +} + +void StandAloneComponent::closeSharedMemory(int shm_fd, int* shm_ptr) { + munmap(shm_ptr, sizeof(int)); + close(shm_fd); + shm_unlink(SHM_NAME); +} + +auto StandAloneComponent::createSemaphore(sem_t*& sem) -> bool { + sem = sem_open(SEM_NAME, O_CREAT | O_EXCL, 0644, 0); + if (sem == SEM_FAILED) { + LOG_F(ERROR, "Failed to create semaphore"); + return false; + } + sem_unlink(SEM_NAME); // Ensure the semaphore is removed once it's no + // longer needed + return true; +} + +void StandAloneComponent::handleChildProcess(const std::string& driver_name, + int stdinPipe[2], + int stdoutPipe[2], int* shm_ptr, + sem_t* sem, int shm_fd) { + close(stdinPipe[1]); + close(stdoutPipe[0]); + + dup2(stdinPipe[0], STDIN_FILENO); + dup2(stdoutPipe[1], STDOUT_FILENO); + + // Try to execute the driver + execlp(driver_name.data(), driver_name.data(), nullptr); + impl_->driver.name = driver_name; + + // If exec fails, set shared memory and post semaphore + *shm_ptr = -1; + sem_post(sem); + LOG_F(ERROR, "Failed to exec driver process"); + close(shm_fd); + munmap(shm_ptr, sizeof(int)); + sem_close(sem); + exit(1); +} + +void StandAloneComponent::handleParentProcess(pid_t pid, int stdinPipe[2], + int stdoutPipe[2], int* shm_ptr, + sem_t* sem, int shm_fd) { + close(stdinPipe[0]); + close(stdoutPipe[1]); + + // Wait for the semaphore to be posted + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += 1; // 1 second timeout + + int s = sem_timedwait(sem, &ts); + if (s == -1) { + if (errno == ETIMEDOUT) { + LOG_F(ERROR, "Driver process start timed out"); + } else { + LOG_F(ERROR, "Failed to wait on semaphore"); + } + close(stdinPipe[1]); + close(stdoutPipe[0]); + kill(pid, SIGKILL); // Kill the process if it didn't start correctly + waitpid(pid, nullptr, 0); // Wait for the process to terminate + } else { + if (*shm_ptr == -1) { + LOG_F(ERROR, "Driver process failed to start"); + close(stdinPipe[1]); + close(stdoutPipe[0]); + waitpid(pid, nullptr, 0); // Ensure child process is cleaned up + } else { + impl_->driver.processHandle = pid; + impl_->driver.stdinFd = stdinPipe[1]; + impl_->driver.stdoutFd = stdoutPipe[0]; + //impl_->driver.name = driver_name; + } + } + closeSharedMemory(shm_fd, shm_ptr); + sem_close(sem); +} +#endif + +void StandAloneComponent::stopLocalDriver() { + close(impl_->driver.stdinFd); + close(impl_->driver.stdoutFd); +#if defined(_WIN32) || defined(_WIN64) + TerminateProcess(reinterpret_cast(impl_->driver.processHandle), 0); + CloseHandle(reinterpret_cast(impl_->driver.processHandle)); +#else + kill(impl_->driver.processHandle, SIGTERM); + waitpid(impl_->driver.processHandle, nullptr, 0); +#endif + impl_->shouldExit = true; + if (impl_->driverThread.joinable()) { + impl_->driverThread.join(); + } +} + +void StandAloneComponent::monitorDrivers() { +#if defined(_WIN32) || defined(_WIN64) + DWORD exitCode; + if (GetExitCodeProcess( + reinterpret_cast(impl_->driver.processHandle), &exitCode) && + exitCode == STILL_ACTIVE) { + return; + } + LOG_F(INFO, "Driver {} exited, restarting...", impl_->driver.name); + startLocalDriver(impl_->driver.name); +#else + int status; + pid_t result = waitpid(impl_->driver.processHandle, &status, WNOHANG); + if (result == 0) { + return; + } + if (result == -1) { + LOG_F(ERROR, "Failed to wait for driver process"); + return; + } + LOG_F(INFO, "Driver {} exited, restarting...", impl_->driver.name); + startLocalDriver(impl_->driver.name); +#endif +} + +void StandAloneComponent::processMessages() { + std::array buffer; + if (impl_->driver.isListening) { +#if defined(_WIN32) || defined(_WIN64) + int bytesRead = + _read(impl_->driver.stdoutFd, buffer.data(), buffer.size()); +#else + int flags = fcntl(impl_->driver.stdoutFd, F_GETFL, 0); + fcntl(impl_->driver.stdoutFd, F_SETFL, flags | O_NONBLOCK); + int bytesRead = + read(impl_->driver.stdoutFd, buffer.data(), buffer.size()); +#endif + if (bytesRead > 0) { + impl_->handleDriverOutput(impl_->driver.name, buffer.data(), + bytesRead); + } + } +} + +void StandAloneComponent::sendMessageToDriver(const std::string_view message) { +#if defined(_WIN32) || defined(_WIN64) + _write(impl_->driver.stdinFd, message.data(), + static_cast(message.size())); +#else + write(impl_->driver.stdinFd, message.data(), message.size()); +#endif +} + +void StandAloneComponent::printDriver() { + LOG_F(INFO, "{} (PID: {}) {}", impl_->driver.name, + impl_->driver.processHandle, + impl_->driver.isListening ? "[Listening]" : ""); +} + +void StandAloneComponent::toggleDriverListening() { + impl_->driver.isListening = !impl_->driver.isListening; + LOG_F(INFO, "Driver {} listening status: {}", impl_->driver.name, + impl_->driver.isListening ? "ON" : "OFF"); +} diff --git a/src/addon/template/standalone.hpp b/src/addon/template/standalone.hpp new file mode 100644 index 00000000..fd624f42 --- /dev/null +++ b/src/addon/template/standalone.hpp @@ -0,0 +1,58 @@ +#ifndef LITHIUM_ADDON_TEMPLATE_STANDALONE_HPP +#define LITHIUM_ADDON_TEMPLATE_STANDALONE_HPP + +#include +#include +#include +#include +#include +#include +#include "atom/components/component.hpp" + +class StandAloneComponentImpl; + +class StandAloneComponent : public Component { +public: + explicit StandAloneComponent(std::string name); + ~StandAloneComponent() override; + + void startLocalDriver(const std::string& driver_name); + + void stopLocalDriver(); + + void monitorDrivers(); + + void processMessages(); + + void sendMessageToDriver(std::string_view message); + + void printDriver(); + + void toggleDriverListening(); + +private: + auto createPipes(int stdinPipe[2], int stdoutPipe[2]) -> bool; + + void backgroundProcessing(); + +#if defined(_WIN32) || defined(_WIN64) + void startWindowsProcess(const std::string& driver_name, int stdinPipe[2], + int stdoutPipe[2]); +#else + void startUnixProcess(const std::string& driver_name, int stdinPipe[2], + int stdoutPipe[2]); + + auto createSharedMemory(int& shm_fd, int*& shm_ptr) -> bool; + void closeSharedMemory(int shm_fd, int* shm_ptr); + auto createSemaphore(sem_t*& sem) -> bool; + void handleChildProcess(const std::string& driver_name, int stdinPipe[2], + int stdoutPipe[2], int* shm_ptr, sem_t* sem, + int shm_fd); + void handleParentProcess(pid_t pid, int stdinPipe[2], int stdoutPipe[2], + int* shm_ptr, sem_t* sem, int shm_fd); +#endif + + std::unique_ptr impl_; +}; + +#endif diff --git a/src/addon/toolchain.cpp b/src/addon/toolchain.cpp new file mode 100644 index 00000000..1d4ba60e --- /dev/null +++ b/src/addon/toolchain.cpp @@ -0,0 +1,140 @@ +#include "toolchain.hpp" + +#include +#include +#include +#include + +#include "atom/log/loguru.hpp" + +Toolchain::Toolchain(std::string name, std::string compiler, + std::string buildTool, std::string version, + std::string path) + : name_(std::move(name)), + compiler_(std::move(compiler)), + buildTool_(std::move(buildTool)), + version_(std::move(version)), + path_(std::move(path)) {} + +void Toolchain::displayInfo() const { + LOG_F(INFO, "Toolchain Information for {}", name_); + LOG_F(INFO, "Compiler: {}", compiler_); + LOG_F(INFO, "Build Tool: {}", buildTool_); + LOG_F(INFO, "Version: {}", version_); + LOG_F(INFO, "Path: {}", path_); +} + +auto Toolchain::getName() const -> const std::string& { return name_; } + +void ToolchainManager::scanForToolchains() { + std::vector searchPaths; + +#if defined(_WIN32) || defined(_WIN64) + searchPaths = {"C:\\Program Files", "C:\\Program Files (x86)", + "C:\\MinGW\\bin", "C:\\LLVM\\bin", + "C:\\msys64\\mingw64\\bin", "C:\\msys64\\mingw32\\bin", + "C:\\msys64\\clang64\\bin", "C:\\msys64\\clang32\\bin"}; +#else + searchPaths = {"/usr/bin", "/usr/local/bin"}; +#endif + + for (const auto& path : searchPaths) { + if (std::filesystem::exists(path)) { + for (const auto& entry : + std::filesystem::directory_iterator(path)) { + if (entry.is_regular_file()) { + std::string filename = entry.path().filename().string(); + if (filename.starts_with("gcc") || + filename.starts_with("g++") || + filename.starts_with("clang") || + filename.starts_with("clang++")) { + std::string version = + getCompilerVersion(entry.path().string()); + toolchains_.emplace_back(filename, filename, filename, + version, + entry.path().string()); + } + } + } + } + } + + scanBuildTools(); +} + +void ToolchainManager::scanBuildTools() { + std::vector buildTools = {"make", "ninja", "cmake", "gmake", + "msbuild"}; + + for (const auto& tool : buildTools) { + if (std::filesystem::exists(tool)) { + std::string version = getCompilerVersion(tool); + toolchains_.emplace_back(tool, tool, tool, version, tool); + } + } +} + +void ToolchainManager::listToolchains() const { + LOG_F(INFO, "Available Toolchains:"); + for (const auto& tc : toolchains_) { + LOG_F(INFO, "- {}", tc.getName()); + } +} + +bool ToolchainManager::selectToolchain(const std::string& name) const { + for (const auto& tc : toolchains_) { + if (tc.getName() == name) { + tc.displayInfo(); + return true; + } + } + return false; +} + +void ToolchainManager::saveConfig(const std::string& filename) { + std::ofstream file(filename); + for (const auto& tc : toolchains_) { + file << tc.getName() << "\n"; + } + file.close(); + LOG_F(INFO, "Configuration saved to {}", filename); +} + +void ToolchainManager::loadConfig(const std::string& filename) { + std::ifstream file(filename); + std::string toolchainName; + while (std::getline(file, toolchainName)) { + selectToolchain(toolchainName); + } + file.close(); +} + +std::string ToolchainManager::getCompilerVersion(const std::string& path) { + std::string command = path + " --version"; +#if defined(_WIN32) || defined(_WIN64) + command = "\"" + path + "\"" + " --version"; +#endif + + std::array buffer; + std::string result; + + std::unique_ptr pipe(popen(command.c_str(), "r"), + pclose); + if (!pipe) { + return "Unknown version"; + } + + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { + result += buffer.data(); + } + + return result.empty() ? "Unknown version" : result; +} + +std::vector ToolchainManager::getAvailableCompilers() const { + std::vector compilers; + for (const auto& toolchain : toolchains_) { + compilers.push_back(toolchain.getName()); // 收集每个可用工具链的名称 + } + return compilers; +} diff --git a/src/addon/toolchain.hpp b/src/addon/toolchain.hpp new file mode 100644 index 00000000..0e90c009 --- /dev/null +++ b/src/addon/toolchain.hpp @@ -0,0 +1,53 @@ +#ifndef LITHIUM_ADDON_TOOLCHAIN_HPP +#define LITHIUM_ADDON_TOOLCHAIN_HPP + +#include +#include +#include + +#if defined(_WIN32) || defined(_WIN64) +#include +#define CMD_EXTENSION ".exe" +#else +#define CMD_EXTENSION "" +#endif + +class Toolchain { +public: + Toolchain(std::string name, std::string compiler, std::string buildTool, + std::string version, std::string path); + + void displayInfo() const; + + [[nodiscard]] auto getName() const -> const std::string&; + +private: + std::string name_; + std::string compiler_; + std::string buildTool_; + std::string version_; + std::string path_; +}; + +class ToolchainManager { +public: + void scanForToolchains(); + void listToolchains() const; + [[nodiscard]] auto selectToolchain(const std::string& name) const -> bool; + void saveConfig(const std::string& filename); + void loadConfig(const std::string& filename); + + [[nodiscard]] auto getToolchains() const -> const std::vector& { + return toolchains_; + } + + auto getAvailableCompilers() const -> std::vector; + +private: + std::vector toolchains_; + + auto getCompilerVersion(const std::string& path) -> std::string; + void scanBuildTools(); +}; + +#endif // LITHIUM_ADDON_TOOLCHAIN_HPP diff --git a/src/addon/version.cpp b/src/addon/version.cpp new file mode 100644 index 00000000..2ae3e969 --- /dev/null +++ b/src/addon/version.cpp @@ -0,0 +1,205 @@ +#include "version.hpp" + +#include +#include +#include +#include + +namespace lithium { +Version::Version() : major(0), minor(0), patch(0) {} + +Version::Version(int maj, int min, int pat, std::string pre, std::string bld) + : major(maj), + minor(min), + patch(pat), + prerelease(std::move(pre)), + build(std::move(bld)) {} + +auto Version::parse(const std::string& versionStr) -> Version { + std::regex versionPattern( + R"((\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?)"); + std::smatch match; + if (std::regex_match(versionStr, match, versionPattern)) { + int major = std::stoi(match[1].str()); + int minor = std::stoi(match[2].str()); + int patch = std::stoi(match[3].str()); + std::string prerelease = match[4].matched ? match[4].str() : ""; + std::string build = match[5].matched ? match[5].str() : ""; + return {major, minor, patch, prerelease, build}; + } + throw std::invalid_argument("Invalid version format"); +} + +auto Version::operator<(const Version& other) const -> bool { + if (major != other.major) { + return major < other.major; + } + if (minor != other.minor) { + return minor < other.minor; + } + if (patch != other.patch) { + return patch < other.patch; + } + if (prerelease.empty() && other.prerelease.empty()) { + return false; + } + if (prerelease.empty()) { + return false; + } + if (other.prerelease.empty()) { + return true; + } + return prerelease < other.prerelease; +} + +auto Version::operator>(const Version& other) const -> bool { + return other < *this; +} + +auto Version::operator==(const Version& other) const -> bool { + return major == other.major && minor == other.minor && + patch == other.patch && prerelease == other.prerelease; +} + +auto Version::operator<=(const Version& other) const -> bool { + return *this < other || *this == other; +} + +auto Version::operator>=(const Version& other) const -> bool { + return *this > other || *this == other; +} + +auto operator<<(std::ostream& os, const Version& version) -> std::ostream& { + os << version.major << '.' << version.minor << '.' << version.patch; + if (!version.prerelease.empty()) { + os << '-' << version.prerelease; + } + if (!version.build.empty()) { + os << '+' << version.build; + } + return os; +} + +DateVersion::DateVersion(int y, int m, int d) : year(y), month(m), day(d) {} + +auto DateVersion::parse(const std::string& dateStr) -> DateVersion { + std::regex datePattern(R"((\d{4})-(\d{2})-(\d{2}))"); + std::smatch match; + if (std::regex_match(dateStr, match, datePattern)) { + int year = std::stoi(match[1].str()); + int month = std::stoi(match[2].str()); + int day = std::stoi(match[3].str()); + // Check if month and day are valid + if (month < 1 || month > 12 || day < 1 || day > 31) { + throw std::invalid_argument("Invalid date format"); + } + return {year, month, day}; + } + throw std::invalid_argument("Invalid date format"); +} + +auto DateVersion::operator<(const DateVersion& other) const -> bool { + if (year != other.year) { + return year < other.year; + } + if (month != other.month) { + return month < other.month; + } + return day < other.day; +} + +auto DateVersion::operator>(const DateVersion& other) const -> bool { + return other < *this; +} + +auto DateVersion::operator==(const DateVersion& other) const -> bool { + return year == other.year && month == other.month && day == other.day; +} + +auto DateVersion::operator<=(const DateVersion& other) const -> bool { + return *this < other || *this == other; +} + +auto DateVersion::operator>=(const DateVersion& other) const -> bool { + return *this > other || *this == other; +} + +auto operator<<(std::ostream& os, const DateVersion& version) -> std::ostream& { + os << version.year << '-' << version.month << '-' << version.day; + return os; +} + +auto checkVersion(const Version& actualVersion, + const std::string& requiredVersionStr) -> bool { + size_t opLength = 1; + if (requiredVersionStr[1] == '=' || requiredVersionStr[1] == '>') { + opLength = 2; + } + + std::string op = requiredVersionStr.substr(0, opLength); + std::string versionPart = requiredVersionStr.substr(opLength); + Version requiredVersion; + + try { + requiredVersion = Version::parse(versionPart); + } catch (const std::invalid_argument& e) { + std::cerr << "Invalid version format: " << versionPart << std::endl; + throw; // Rethrow or handle the error appropriately + } + + if (op == "^") { + return actualVersion.major == requiredVersion.major && + actualVersion >= requiredVersion; + } + if (op == "~") { + return actualVersion.major == requiredVersion.major && + actualVersion.minor == requiredVersion.minor && + actualVersion >= requiredVersion; + } + if (op == ">") { + return actualVersion > requiredVersion; + } + if (op == "<") { + return actualVersion < requiredVersion; + } + if (op == ">=") { + return actualVersion >= requiredVersion; + } + if (op == "<=") { + return actualVersion <= requiredVersion; + } + if (op == "=") { + return actualVersion == requiredVersion; + } + return actualVersion == requiredVersion; +} + +auto checkDateVersion(const DateVersion& actualVersion, + const std::string& requiredVersionStr) -> bool { + size_t opLength = 1; + if (requiredVersionStr[1] == '=') { + opLength = 2; + } + + std::string op = requiredVersionStr.substr(0, opLength); + DateVersion requiredVersion = + DateVersion::parse(requiredVersionStr.substr(opLength)); + + if (op == ">") { + return actualVersion > requiredVersion; + } + if (op == "<") { + return actualVersion < requiredVersion; + } + if (op == ">=") { + return actualVersion >= requiredVersion; + } + if (op == "<=") { + return actualVersion <= requiredVersion; + } + if (op == "=") { + return actualVersion == requiredVersion; + } + throw std::invalid_argument("Invalid comparison operator"); +} +} // namespace lithium diff --git a/src/addon/version.hpp b/src/addon/version.hpp new file mode 100644 index 00000000..1cd64cd4 --- /dev/null +++ b/src/addon/version.hpp @@ -0,0 +1,67 @@ +#ifndef LITHIUM_ADDON_VERSION_HPP +#define LITHIUM_ADDON_VERSION_HPP + +#include + +#include "macro.hpp" + +namespace lithium { +struct Version { + int major; + int minor; + int patch; + std::string prerelease; // alpha, beta, rc等 + std::string build; // build metadata + + Version(); + + Version(int maj, int min, int pat, std::string pre = "", + std::string bld = ""); + + static auto parse(const std::string& versionStr) -> Version; + + auto operator<(const Version& other) const -> bool; + + auto operator>(const Version& other) const -> bool; + + auto operator==(const Version& other) const -> bool; + + auto operator<=(const Version& other) const -> bool; + + auto operator>=(const Version& other) const -> bool; +} ATOM_ALIGNAS(128); + +auto operator<<(std::ostream& os, const Version& version) -> std::ostream&; + +// 日期版本解析 +struct DateVersion { + int year; + int month; + int day; + + DateVersion(int y, int m, int d); + + static auto parse(const std::string& dateStr) -> DateVersion; + + auto operator<(const DateVersion& other) const -> bool; + + auto operator>(const DateVersion& other) const -> bool; + + auto operator==(const DateVersion& other) const -> bool; + + auto operator<=(const DateVersion& other) const -> bool; + + auto operator>=(const DateVersion& other) const -> bool; +} ATOM_ALIGNAS(16); + +auto operator<<(std::ostream& os, const DateVersion& version) -> std::ostream&; + +auto checkVersion(const Version& actualVersion, + const std::string& requiredVersionStr) -> bool; + +auto checkDateVersion(const DateVersion& actualVersion, + const std::string& requiredVersionStr) -> bool; + +} // namespace lithium + +#endif diff --git a/src/addon/xmake.lua b/src/addon/xmake.lua new file mode 100644 index 00000000..84269a02 --- /dev/null +++ b/src/addon/xmake.lua @@ -0,0 +1,69 @@ + +set_project("lithium-addons") +set_version("1.0.0") +set_xmakever("2.5.1") + +-- Set the C++ standard +set_languages("cxx20") + +-- Add required packages +add_requires("loguru", "pthread") + +local project_packages = { + "loguru", + "pthread" +} + +-- Define libraries +local project_libs = { + "atom-io", + "atom-error", + "atom-function", + "atom-system", + "lithium-utils" +} + +-- Source files +local source_files = { + "addons.cpp", + "compiler.cpp", + "dependency.cpp", + "loader.cpp", + "manager.cpp", + "sandbox.cpp" +} + +-- Header files +local header_files = { + "addons.hpp", + "compiler.hpp", + "dependency.hpp", + "loader.hpp", + "manager.hpp", + "sandbox.hpp" +} + +-- Object Library +target("lithium-addons_object") + set_kind("object") + add_files(table.unpack(source_files)) + add_headerfiles(table.unpack(header_files)) + add_deps(table.unpack(project_libs)) + add_packages(table.unpack(project_packages)) +target_end() + +-- Static Library +target("lithium-addons") + set_kind("static") + add_deps("lithium-addons_object") + add_files(table.unpack(source_files)) + add_headerfiles(table.unpack(header_files)) + add_packages(table.unpack(project_libs)) + add_includedirs(".") + set_targetdir("$(buildir)/lib") + set_installdir("$(installdir)/lib") + set_version("1.0.0", {build = "%Y%m%d%H%M"}) + on_install(function (target) + os.cp(target:targetfile(), path.join(target:installdir(), "lib")) + end) +target_end() diff --git a/src/atom/CMakeLists.txt b/src/atom/CMakeLists.txt index b00a1f9d..5efff376 100644 --- a/src/atom/CMakeLists.txt +++ b/src/atom/CMakeLists.txt @@ -9,74 +9,64 @@ cmake_minimum_required(VERSION 3.20) project(atom C CXX) -CHECK_INCLUDE_FILE(format HAS_STD_FORMAT) - -set(CMAKE_ATOM_VERSION_MAJOR 1) -set(CMAKE_ATOM_VERSION_MINOR 0) -set(CMAKE_ATOM_VERSION_RELEASE 0) - -set(ATOM_SOVERSION ${CMAKE_ATOM_VERSION_MAJOR}) -set(CMAKE_ATOM_VERSION_STRING "${CMAKE_ATOM_VERSION_MAJOR}.${CMAKE_ATOM_VERSION_MINOR}.${CMAKE_ATOM_VERSION_RELEASE}") -set(ATOM_VERSION ${CMAKE_ATOM_VERSION_MAJOR}.${CMAKE_ATOM_VERSION_MINOR}.${CMAKE_ATOM_VERSION_RELEASE}) - -option(ATOM_BUILD_PYTHON " Build Atom with Python support" OFF) -find_package(Python COMPONENTS Interpreter Development REQUIRED) -if(${PYTHON_FOUND}) - message("-- Found Python ${PYTHON_VERSION_STRING}: ${PYTHON_EXECUTABLE}") - find_package(pybind11) - if (pybind11_FOUND) - message(STATUS "Found pybind11: ${pybind11_INCLUDE_DIRS}") - set(ATOM_BUILD_PYTHON ON) +# Versioning +set(ATOM_VERSION_MAJOR 1) +set(ATOM_VERSION_MINOR 0) +set(ATOM_VERSION_PATCH 0) +set(ATOM_SOVERSION ${ATOM_VERSION_MAJOR}) +set(ATOM_VERSION_STRING "${ATOM_VERSION_MAJOR}.${ATOM_VERSION_MINOR}.${ATOM_VERSION_PATCH}") + +# Python Support +option(ATOM_BUILD_PYTHON "Build Atom with Python support" OFF) +if(ATOM_BUILD_PYTHON) + find_package(Python COMPONENTS Interpreter Development REQUIRED) + if(PYTHON_FOUND) + message("-- Found Python ${PYTHON_VERSION_STRING}: ${PYTHON_EXECUTABLE}") + find_package(pybind11 QUIET) + if(pybind11_FOUND) + message(STATUS "Found pybind11: ${pybind11_INCLUDE_DIRS}") + else() + message(FATAL_ERROR "pybind11 not found") + endif() + else() + message(FATAL_ERROR "Python not found") endif() endif() + +# Subdirectories add_subdirectory(algorithm) add_subdirectory(async) add_subdirectory(components) add_subdirectory(connection) add_subdirectory(error) +add_subdirectory(function) add_subdirectory(io) add_subdirectory(log) add_subdirectory(search) +add_subdirectory(sysinfo) add_subdirectory(system) -add_subdirectory(task) add_subdirectory(type) add_subdirectory(utils) add_subdirectory(web) -if(NOT HAS_STD_FORMAT) - find_package(fmt REQUIRED) -endif() -# Sources -list(APPEND ${PROJECT_NAME}_SOURCES +# Sources and Headers +set(ATOM_SOURCES log/logger.cpp - log/global_logger.cpp log/syslog.cpp - - function/global_ptr.cpp ) -# Headers -list(APPEND ${PROJECT_NAME}_HEADERS +set(ATOM_HEADERS log/logger.hpp - log/global_logger.hpp log/syslog.hpp - - function/global_ptr.hpp ) -# Private Headers -list(APPEND ${PROJECT_NAME}_PRIVATE_HEADERS - -) - -list(APPEND ${PROJECT_NAME}_LIBS +# Libraries +set(ATOM_LIBS loguru cpp_httplib - libzippp + atom-function atom-algorithm - #atom-connection atom-async - atom-task atom-io atom-component atom-type @@ -84,41 +74,30 @@ list(APPEND ${PROJECT_NAME}_LIBS atom-search atom-web atom-system - ) - -# Build Object Library -add_library(${PROJECT_NAME}_OBJECT OBJECT) -set_property(TARGET ${PROJECT_NAME}_OBJECT PROPERTY POSITION_INDEPENDENT_CODE 1) + atom-sysinfo +) -target_compile_definitions(${PROJECT_NAME}_OBJECT PRIVATE "-DHAVE_LIBNOVA") +# Object Library +add_library(atom_object OBJECT ${ATOM_SOURCES} ${ATOM_HEADERS}) if(WIN32) -target_link_libraries(${PROJECT_NAME}_OBJECT setupapi wsock32 ws2_32 shlwapi iphlpapi) + target_link_libraries(atom_object setupapi wsock32 ws2_32 shlwapi iphlpapi) endif() -target_link_libraries(${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) - -target_sources(${PROJECT_NAME}_OBJECT - PUBLIC - ${${PROJECT_NAME}_HEADERS} - PRIVATE - ${${PROJECT_NAME}_SOURCES} - ${${PROJECT_NAME}_PRIVATE_HEADERS} -) - -target_link_libraries(${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) -add_library(${PROJECT_NAME}static STATIC) +target_link_libraries(atom_object ${ATOM_LIBS}) -target_link_libraries(${PROJECT_NAME}static ${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) -target_link_libraries(${PROJECT_NAME}static ${CMAKE_THREAD_LIBS_INIT}) -target_include_directories(${PROJECT_NAME}static PUBLIC .) - -set_target_properties(${PROJECT_NAME}static PROPERTIES - VERSION ${CMAKE_HYDROGEN_VERSION_STRING} - SOVERSION ${HYDROGEN_SOVERSION} - OUTPUT_NAME ${PROJECT_NAME} # this same name like shared library - backwards compatibility +# Static Library +add_library(atom STATIC) +set_target_properties(atom PROPERTIES + IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}atom${CMAKE_STATIC_LIBRARY_SUFFIX}" + VERSION ${ATOM_VERSION_STRING} + SOVERSION ${ATOM_SOVERSION} ) -install(TARGETS ${PROJECT_NAME}static - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +target_link_libraries(atom atom_object ${CMAKE_THREAD_LIBS_INIT} ${ATOM_LIBS}) + +# Install +install(TARGETS atom + DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT library ) diff --git a/src/atom/README.md b/src/atom/README.md deleted file mode 100644 index e739da89..00000000 --- a/src/atom/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Atom - -## Introduction - -Atom is a universal accessory library for all ElementAstro projects, mainly providing encapsulation of different functions, usually based on the latest C++features, with good performance and readability. - -## Features - -- **High performance** -- **Easy to use** -- **Easy to extend** -- **Support for multiple platforms** -- **Newest C++ features** - -## Classification - -Each of the following modules are available to use as static or dynamic libraries: - -- [Atom-Algorithm](algorithm/README.md) -- [Atom-Async](async/README.md) -- [Atom-Components](components/README.md) -- [Atom-Connection](connection/README.md) -- [Atom-Driver](driver/README.md) -- [Atom-Error](error/README.md) -- [Atom-Event](event/README.md) -- [Atom-Experiment](experiment/README.md) -- [Atom-IO](io/README.md) -- [Atom-Log](log/README.md) -- [Atom-Search](search/README.md) -- [Atom-Serial](serial/README.md) -- [Atom-Server](server/README.md) -- [Atom-System](system/README.md) -- [Atom-Task](task/README.md) -- [Atom-Type](type/README.md) -- [Atom-Utility](utils/README.md) -- [Atom-Web](web/README.md) diff --git a/src/atom/algorithm/CMakeLists.txt b/src/atom/algorithm/CMakeLists.txt index 2788beb3..294674ff 100644 --- a/src/atom/algorithm/CMakeLists.txt +++ b/src/atom/algorithm/CMakeLists.txt @@ -13,25 +13,22 @@ project(atom-algorithm C CXX) set(${PROJECT_NAME}_SOURCES algorithm.cpp base.cpp + bignumber.cpp convolve.cpp - fbase.cpp fnmatch.cpp fraction.cpp huffman.cpp math.cpp md5.cpp mhash.cpp - pid.cpp ) # Headers set(${PROJECT_NAME}_HEADERS algorithm.hpp - algorithm.inl base.hpp - calculator.hpp + bignumber.hpp convolve.hpp - fbase.hpp fnmatch.hpp fraction.hpp hash.hpp @@ -39,7 +36,6 @@ set(${PROJECT_NAME}_HEADERS math.hpp md5.hpp mhash.hpp - pid.hpp ) # Build Object Library diff --git a/src/atom/algorithm/_pybind.cpp b/src/atom/algorithm/_pybind.cpp deleted file mode 100644 index acc41066..00000000 --- a/src/atom/algorithm/_pybind.cpp +++ /dev/null @@ -1,127 +0,0 @@ -/* - * _pybind.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-4-13 - -Description: Python Binding of Atom-Algorithm - -**************************************************/ - -#include -#include -#include - -#include "algorithm.hpp" -#include "base.hpp" -#include "convolve.hpp" -#include "fraction.hpp" -#include "hash.hpp" -#include "huffman.hpp" -#include "math.hpp" -#include "md5.hpp" -#include "mhash.hpp" - -namespace py = pybind11; - -using namespace atom::algorithm; - -PYBIND11_MODULE(atom_algorithm, m) { - m.doc() = "Atom Algorithm Python Binding"; - - m.def("base16encode", &base16Encode, "Base16 Encode"); - m.def("base16decode", &base16Decode, "Base16 Decode"); - m.def("base32encode", &base32Encode, "Base32 Encode"); - m.def("base32decode", &base32Decode, "Base32 Decode"); - m.def("base64encode", &base64Encode, "Base64 Encode"); - m.def("base64decode", &base64Decode, "Base64 Decode"); - m.def("base85encode", &base85Encode, "Base85 Encode"); - m.def("base85decode", &base85Decode, "Base85 Decode"); - m.def("base91encode", &base91Encode, "Base91 Encode"); - m.def("base91decode", &base91Decode, "Base91 Decode"); - m.def("base128encode", &base128Encode, "Base128 Encode"); - m.def("base128decode", &base128Decode, "Base128 Decode"); - - py::class_(m, "KMP") - .def(py::init()) - .def("search", &KMP::Search) - .def("set_pattern", &KMP::SetPattern); - - py::class_(m, "MinHash") - .def(py::init()) - .def("compute_signature", &MinHash::compute_signature) - .def("estimate_similarity", &MinHash::estimate_similarity); - - m.def("convolve", &convolve, "Perform one-dimensional convolution"); - m.def("deconvolve", &deconvolve, "Perform one-dimensional deconvolution"); - m.def("convolve2D", &convolve2D, "Perform two-dimensional convolution", - py::arg("input"), py::arg("kernel"), py::arg("numThreads") = 1); - m.def("deconvolve2D", &deconvolve2D, - "Perform two-dimensional deconvolution"); - m.def("DFT2D", &DFT2D, "Perform two-dimensional discrete Fourier transform", - py::arg("signal"), py::arg("numThreads") = 1); - m.def("IDFT2D", &IDFT2D, - "Perform two-dimensional inverse discrete Fourier transform", - py::arg("spectrum"), py::arg("numThreads") = 1); - m.def("generate_gaussian_kernel", &generateGaussianKernel, - "Generate a Gaussian kernel for convolution"); - m.def("apply_gaussian_filter", &applyGaussianFilter, - "Apply a Gaussian filter to an image"); - - py::class_(m, "Fraction") - .def(py::init()) - .def_readwrite("numerator", &Fraction::numerator) - .def_readwrite("denominator", &Fraction::denominator) - .def("__add__", &Fraction::operator+, py::is_operator()) - .def("__sub__", &Fraction::operator-, py::is_operator()) - .def("__mul__", &Fraction::operator*, py::is_operator()) - .def("__truediv__", &Fraction::operator/, py::is_operator()) - .def("__eq__", &Fraction::operator==, py::is_operator()) - .def("__neg__", &Fraction::operator-) - .def("__str__", &Fraction::to_string) - .def("__float__", &Fraction::to_double) - .def("__int__", &Fraction::operator int) - .def("__repr__", [](const Fraction &f) { return f.to_string(); }); - - // Bindings for createHuffmanTree function - m.def("create_huffman_tree", &createHuffmanTree, - "Create a Huffman tree from character frequencies"); - - // Bindings for generateHuffmanCodes function - m.def("generate_huffman_codes", &generateHuffmanCodes, - "Generate Huffman codes for characters in the Huffman tree"); - - // Bindings for compressText function - m.def("compress_text", &compressText, - "Compress text using Huffman encoding"); - - // Bindings for decompressText function - m.def("decompress_text", &decompressText, - "Decompress text using Huffman decoding"); - - m.def("mul_div_64", &mulDiv64, - "Perform 64-bit multiplication and division"); - m.def("safe_add", &safeAdd, "Safe addition"); - m.def("safe_sub", &safeSub, "Safe subtraction"); - m.def("safe_mul", &safeMul, "Safe multiplication"); - m.def("safe_div", &safeDiv, "Safe division"); - m.def("normalize", &normalize, "Normalize a 64-bit number"); - m.def("rotl64", &rotl64, "Rotate left"); - m.def("rotr64", &rotr64, "Rotate right"); - m.def("clz64", &clz64, "Count leading zeros"); - - py::class_(m, "MD5") - .def(py::init<>()) - .def_static("encrypt", &MD5::encrypt); - - m.def("murmur3_hash", &murmur3Hash, "Murmur3 Hash"); - m.def("murmur3_hash64", &murmur3Hash64, "Murmur3 Hash64"); - m.def("hexstring_from_data", - py::overload_cast(&hexstringFromData), - "Hexstring from Data"); - m.def("data_from_hexstring", &dataFromHexstring, "Data from Hexstring"); -} diff --git a/src/atom/algorithm/algorithm.cpp b/src/atom/algorithm/algorithm.cpp index c65fe338..6e886870 100644 --- a/src/atom/algorithm/algorithm.cpp +++ b/src/atom/algorithm/algorithm.cpp @@ -1,17 +1,46 @@ #include "algorithm.hpp" - -#include +#ifdef USE_OPENMP +#include +#endif namespace atom::algorithm { -KMP::KMP(std::string_view pattern) : pattern_(pattern) { - failure_ = ComputeFailureFunction(pattern_); -} +KMP::KMP(std::string_view pattern) { setPattern(pattern); } -std::vector KMP::Search(std::string_view text) { +auto KMP::search(std::string_view text) const -> std::vector { std::vector occurrences; auto n = static_cast(text.length()); auto m = static_cast(pattern_.length()); + if (m == 0) { + return occurrences; + } + +#ifdef USE_OPENMP + std::vector local_occurrences[omp_get_max_threads()]; +#pragma omp parallel + { + int i = omp_get_thread_num(); + int j = 0; + while (i < n) { + if (text[i] == pattern_[j]) { + ++i; + ++j; + if (j == m) { + local_occurrences[omp_get_thread_num()].push_back(i - m); + j = failure_[j - 1]; + } + } else if (j > 0) { + j = failure_[j - 1]; + } else { + ++i; + } + } + } + for (int t = 0; t < omp_get_max_threads(); ++t) { + occurrences.insert(occurrences.end(), local_occurrences[t].begin(), + local_occurrences[t].end()); + } +#else int i = 0; int j = 0; while (i < n) { @@ -28,92 +57,68 @@ std::vector KMP::Search(std::string_view text) { ++i; } } +#endif return occurrences; } -void KMP::SetPattern(std::string_view pattern) { +void KMP::setPattern(std::string_view pattern) { pattern_ = pattern; - failure_ = ComputeFailureFunction(pattern_); + failure_ = computeFailureFunction(pattern_); } -std::vector KMP::ComputeFailureFunction(std::string_view pattern) { +auto KMP::computeFailureFunction(std::string_view pattern) -> std::vector { auto m = static_cast(pattern.length()); std::vector failure(m, 0); - int i = 1; int j = 0; - while (i < m) { + for (int i = 1; i < m; ++i) { if (pattern[i] == pattern[j]) { - failure[i] = j + 1; - ++i; - ++j; + failure[i] = ++j; } else if (j > 0) { j = failure[j - 1]; - } else { - failure[i] = 0; - ++i; + --i; // stay in the same position } } return failure; } -MinHash::MinHash(int num_hash_functions) - : m_num_hash_functions(num_hash_functions) { - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution dis; - - for (int i = 0; i < num_hash_functions; ++i) { - m_coefficients_a.push_back(dis(gen)); - m_coefficients_b.push_back(dis(gen)); - } -} - -std::vector MinHash::compute_signature( - const std::unordered_set& set) { - std::vector signature( - m_num_hash_functions, std::numeric_limits::max()); +BoyerMoore::BoyerMoore(std::string_view pattern) { setPattern(pattern); } - for (const auto& element : set) { - for (int i = 0; i < m_num_hash_functions; ++i) { - unsigned long long hash_value = hash(element, i); - signature[i] = std::min(signature[i], hash_value); - } +auto BoyerMoore::search(std::string_view text) const -> std::vector { + std::vector occurrences; + auto n = static_cast(text.length()); + auto m = static_cast(pattern_.length()); + if (m == 0) { + return occurrences; } - return signature; -} - -double MinHash::estimate_similarity( - const std::vector& signature1, - const std::vector& signature2) const { - int num_matches = 0; - for (int i = 0; i < m_num_hash_functions; ++i) { - if (signature1[i] == signature2[i]) { - ++num_matches; +#ifdef USE_OPENMP + std::vector local_occurrences[omp_get_max_threads()]; +#pragma omp parallel + { + int i = omp_get_thread_num(); + while (i <= n - m) { + int j = m - 1; + while (j >= 0 && pattern_[j] == text[i + j]) { + --j; + } + if (j < 0) { + local_occurrences[omp_get_thread_num()].push_back(i); + i += good_suffix_shift_[0]; + } else { + int badCharShift = + bad_char_shift_.find(text[i + j]) != bad_char_shift_.end() + ? bad_char_shift_.at(text[i + j]) + : m; + i += std::max(good_suffix_shift_[j + 1], + badCharShift - m + 1 + j); + } } } - return static_cast(num_matches) / m_num_hash_functions; -} - -unsigned long long MinHash::hash(const std::string& element, int index) { - unsigned long long hash_value = 0; - for (char c : element) { - hash_value += - (m_coefficients_a[index] * static_cast(c) + - m_coefficients_b[index]); + for (int t = 0; t < omp_get_max_threads(); ++t) { + occurrences.insert(occurrences.end(), local_occurrences[t].begin(), + local_occurrences[t].end()); } - return hash_value; -} - -BoyerMoore::BoyerMoore(std::string_view pattern) : pattern_(pattern) { - ComputeBadCharacterShift(); - ComputeGoodSuffixShift(); -} - -std::vector BoyerMoore::Search(std::string_view text) { - std::vector occurrences; - auto n = static_cast(text.length()); - auto m = static_cast(pattern_.length()); +#else int i = 0; while (i <= n - m) { int j = m - 1; @@ -124,20 +129,24 @@ std::vector BoyerMoore::Search(std::string_view text) { occurrences.push_back(i); i += good_suffix_shift_[0]; } else { - i += std::max(good_suffix_shift_[j + 1], - bad_char_shift_[text[i + j]] - m + 1 + j); + int badCharShift = + bad_char_shift_.find(text[i + j]) != bad_char_shift_.end() + ? bad_char_shift_.at(text[i + j]) + : m; + i += std::max(good_suffix_shift_[j + 1], badCharShift - m + 1 + j); } } +#endif return occurrences; } -void BoyerMoore::SetPattern(std::string_view pattern) { - pattern_ = pattern; - ComputeBadCharacterShift(); - ComputeGoodSuffixShift(); +void BoyerMoore::setPattern(std::string_view pattern) { + pattern_ = std::string(pattern); + computeBadCharacterShift(); + computeGoodSuffixShift(); } -void BoyerMoore::ComputeBadCharacterShift() { +void BoyerMoore::computeBadCharacterShift() { bad_char_shift_.clear(); for (int i = 0; i < static_cast(pattern_.length()) - 1; ++i) { bad_char_shift_[pattern_[i]] = @@ -145,22 +154,36 @@ void BoyerMoore::ComputeBadCharacterShift() { } } -void BoyerMoore::ComputeGoodSuffixShift() { +void BoyerMoore::computeGoodSuffixShift() { auto m = static_cast(pattern_.length()); - good_suffix_shift_.resize(m, m); - std::vector suffix(m, 0); - int j = 0; - for (int i = m - 1; i >= 0; --i) { - if (pattern_.substr(i) == pattern_.substr(m - j - 1, j + 1)) { - suffix[i] = j + 1; + good_suffix_shift_.resize(m + 1, m); + std::vector suffix(m + 1, 0); + suffix[m] = m + 1; + + for (int i = m; i > 0; --i) { + int j = i - 1; + while (j >= 0 && pattern_[j] != pattern_[m - 1 - (i - 1 - j)]) { + --j; } - if (i > 0) { - good_suffix_shift_[m - suffix[i]] = m - i; + suffix[i - 1] = j + 1; + } + + for (int i = 0; i <= m; ++i) { + good_suffix_shift_[i] = m; + } + + for (int i = m; i > 0; --i) { + if (suffix[i - 1] == i) { + for (int j = 0; j < m - i; ++j) { + if (good_suffix_shift_[j] == m) { + good_suffix_shift_[j] = m - i; + } + } } } + for (int i = 0; i < m - 1; ++i) { - good_suffix_shift_[m - suffix[i]] = m - i; + good_suffix_shift_[m - suffix[i]] = m - 1 - i; } } - } // namespace atom::algorithm diff --git a/src/atom/algorithm/algorithm.hpp b/src/atom/algorithm/algorithm.hpp index 1216b4b2..e00bb234 100644 --- a/src/atom/algorithm/algorithm.hpp +++ b/src/atom/algorithm/algorithm.hpp @@ -19,98 +19,59 @@ Description: A collection of algorithms for C++ #include #include #include -#include #include namespace atom::algorithm { /** - * @brief The KMP class implements the Knuth-Morris-Pratt string searching - * algorithm. + * @brief Implements the Knuth-Morris-Pratt (KMP) string searching algorithm. + * + * This class provides methods to search for occurrences of a pattern within a + * text using the KMP algorithm, which preprocesses the pattern to achieve + * efficient string searching. */ class KMP { public: /** - * @brief Constructs a new KMP object with the specified pattern. - * @param pattern The pattern to search for. + * @brief Constructs a KMP object with the given pattern. + * + * @param pattern The pattern to search for in text. */ explicit KMP(std::string_view pattern); /** * @brief Searches for occurrences of the pattern in the given text. - * @param text The text to search in. - * @return A vector containing the starting positions of all occurrences of - * the pattern in the text. + * + * @param text The text to search within. + * @return std::vector Vector containing positions where the pattern + * starts in the text. */ - std::vector Search(std::string_view text); + [[nodiscard]] auto search(std::string_view text) const -> std::vector; /** - * @brief Sets a new pattern to search for. - * @param pattern The new pattern to set. + * @brief Sets a new pattern for searching. + * + * @param pattern The new pattern to search for. */ - void SetPattern(std::string_view pattern); + void setPattern(std::string_view pattern); private: /** - * @brief Computes the failure function for the specified pattern. + * @brief Computes the failure function (partial match table) for the given + * pattern. + * + * This function preprocesses the pattern to determine the length of the + * longest proper prefix which is also a suffix at each position in the + * pattern. + * * @param pattern The pattern for which to compute the failure function. - * @return A vector containing the failure function values. + * @return std::vector The computed failure function (partial match + * table). */ - std::vector ComputeFailureFunction(std::string_view pattern); + auto computeFailureFunction(std::string_view pattern) -> std::vector; - std::string pattern_; /**< The pattern to search for. */ + std::string pattern_; ///< The pattern to search for. std::vector - failure_; /**< The failure function for the current pattern. */ -}; - -/** - * @brief The MinHash class implements the MinHash algorithm for estimating the - * Jaccard similarity between sets. - */ -class MinHash { -public: - /** - * @brief Constructs a new MinHash object with the specified number of hash - * functions. - * @param num_hash_functions The number of hash functions to use for - * computing MinHash signatures. - */ - explicit MinHash(int num_hash_functions); - - /** - * @brief Computes the MinHash signature for the given set. - * @param set The set for which to compute the MinHash signature. - * @return A vector containing the MinHash signature of the set. - */ - std::vector compute_signature( - const std::unordered_set& set); - - /** - * @brief Estimates the Jaccard similarity between two sets using their - * MinHash signatures. - * @param signature1 The MinHash signature of the first set. - * @param signature2 The MinHash signature of the second set. - * @return The estimated Jaccard similarity between the two sets. - */ - double estimate_similarity( - const std::vector& signature1, - const std::vector& signature2) const; - -private: - /** - * @brief Computes the hash value of an element using a specific hash - * function. - * @param element The element to hash. - * @param index The index of the hash function to use. - * @return The hash value of the element. - */ - unsigned long long hash(const std::string& element, int index); - - int m_num_hash_functions; /**< The number of hash functions used for - MinHash. */ - std::vector - m_coefficients_a; /**< Coefficients 'a' for hash functions. */ - std::vector - m_coefficients_b; /**< Coefficients 'b' for hash functions. */ + failure_; ///< Failure function (partial match table) for the pattern. }; /** @@ -139,11 +100,12 @@ class BloomFilter { * @param element The element to check. * @return True if the element might be present, false otherwise. */ - bool contains(std::string_view element) const; + [[nodiscard]] auto contains(std::string_view element) const -> bool; private: - std::bitset m_bits; /**< The bitset representing the Bloom filter. */ - std::size_t m_num_hash_functions; /**< The number of hash functions used. */ + std::bitset m_bits_; /**< The bitset representing the Bloom filter. */ + std::size_t + m_num_hash_functions_; /**< The number of hash functions used. */ /** * @brief Computes the hash value of an element using a specific seed. @@ -151,51 +113,97 @@ class BloomFilter { * @param seed The seed value for the hash function. * @return The hash value of the element. */ - std::size_t hash(std::string_view element, std::size_t seed) const; + auto hash(std::string_view element, std::size_t seed) const -> std::size_t; }; /** - * @brief The BoyerMoore class implements the Boyer-Moore algorithm for string - * searching. + * @brief Implements the Boyer-Moore string searching algorithm. + * + * This class provides methods to search for occurrences of a pattern within a + * text using the Boyer-Moore algorithm, which preprocesses the pattern to + * achieve efficient string searching. */ class BoyerMoore { public: /** - * @brief Constructs a new BoyerMoore object with the specified pattern. - * @param pattern The pattern to search for. + * @brief Constructs a BoyerMoore object with the given pattern. + * + * @param pattern The pattern to search for in text. */ explicit BoyerMoore(std::string_view pattern); /** * @brief Searches for occurrences of the pattern in the given text. - * @param text The text in which to search for the pattern. - * @return A vector containing the starting positions of all occurrences of - * the pattern in the text. + * + * @param text The text to search within. + * @return std::vector Vector containing positions where the pattern + * starts in the text. */ - std::vector Search(std::string_view text); + auto search(std::string_view text) const -> std::vector; /** - * @brief Sets a new pattern for the BoyerMoore object. - * @param pattern The new pattern to set. + * @brief Sets a new pattern for searching. + * + * @param pattern The new pattern to search for. */ - void SetPattern(std::string_view pattern); + void setPattern(std::string_view pattern); private: /** - * @brief Computes the bad character shift table for the pattern. + * @brief Computes the bad character shift table for the current pattern. + * + * This table determines how far to shift the pattern relative to the text + * based on the last occurrence of a mismatched character. */ - void ComputeBadCharacterShift(); + void computeBadCharacterShift(); /** - * @brief Computes the good suffix shift table for the pattern. + * @brief Computes the good suffix shift table for the current pattern. + * + * This table helps determine how far to shift the pattern when a mismatch + * occurs based on the occurrence of a partial match (suffix of the + * pattern). */ - void ComputeGoodSuffixShift(); + void computeGoodSuffixShift(); - std::string pattern_; /**< The pattern to search for. */ + std::string pattern_; ///< The pattern to search for. std::unordered_map - bad_char_shift_; /**< The bad character shift table. */ - std::vector good_suffix_shift_; /**< The good suffix shift table. */ + bad_char_shift_; ///< Bad character shift table. + std::vector good_suffix_shift_; ///< Good suffix shift table. }; + +template +BloomFilter::BloomFilter(std::size_t num_hash_functions) + : m_num_hash_functions_(num_hash_functions) {} + +template +void BloomFilter::insert(std::string_view element) { + for (std::size_t i = 0; i < m_num_hash_functions_; ++i) { + std::size_t hashValue = hash(element, i); + m_bits_.set(hashValue % N); + } +} + +template +auto BloomFilter::contains(std::string_view element) const -> bool { + for (std::size_t i = 0; i < m_num_hash_functions_; ++i) { + std::size_t hashValue = hash(element, i); + if (!m_bits_.test(hashValue % N)) { + return false; + } + } + return true; +} + +template +auto BloomFilter::hash(std::string_view element, + std::size_t seed) const -> std::size_t { + std::size_t hashValue = seed; + for (char c : element) { + hashValue = hashValue * 31 + static_cast(c); + } + return hashValue; +} } // namespace atom::algorithm #include "algorithm.inl" diff --git a/src/atom/algorithm/algorithm.inl b/src/atom/algorithm/algorithm.inl deleted file mode 100644 index 42f7bfd0..00000000 --- a/src/atom/algorithm/algorithm.inl +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef ATOM_ALGORITHM_ALGORITHM_INL -#define ATOM_ALGORITHM_ALGORITHM_INL - -#include "algorithm.hpp" - -namespace atom::algorithm { -template -BloomFilter::BloomFilter(std::size_t num_hash_functions) - : m_num_hash_functions(num_hash_functions) {} - -template -void BloomFilter::insert(std::string_view element) { - for (std::size_t i = 0; i < m_num_hash_functions; ++i) { - std::size_t hash_value = hash(element, i); - m_bits.set(hash_value % N); - } -} - -template -bool BloomFilter::contains(std::string_view element) const { - for (std::size_t i = 0; i < m_num_hash_functions; ++i) { - std::size_t hash_value = hash(element, i); - if (!m_bits.test(hash_value % N)) { - return false; - } - } - return true; -} - -template -std::size_t BloomFilter::hash(std::string_view element, std::size_t seed) const { - std::size_t hash_value = seed; - for (char c : element) { - hash_value = hash_value * 31 + static_cast(c); - } - return hash_value; -} -} // namespace atom::algorithm - -#endif diff --git a/src/atom/algorithm/base.cpp b/src/atom/algorithm/base.cpp index 02e850f0..b131351a 100644 --- a/src/atom/algorithm/base.cpp +++ b/src/atom/algorithm/base.cpp @@ -14,370 +14,341 @@ Description: A collection of algorithms for C++ #include "base.hpp" -#include #include -#include -#include -#include -#include +#include + +#include "atom/error/exception.hpp" #ifdef _WIN32 -#include +#include #else #include #endif -namespace atom::algorithm { -std::string base16Encode(const std::vector &data) { - std::stringstream ss; - ss << std::hex << std::uppercase << std::setfill('0'); - - for (unsigned char byte : data) { - ss << std::setw(2) << static_cast(byte); - } +#if USE_OPENCL +#include +constexpr bool HAS_OPEN_CL = true; +#else +constexpr bool HAS_OPEN_CL = false; +#endif - return ss.str(); +namespace atom::algorithm { +namespace detail { +#if USE_OPENCL +const char* base64EncodeKernelSource = R"( + __kernel void base64EncodeKernel(__global const uchar* input, __global char* output, int size) { + int i = get_global_id(0); + if (i < size / 3) { + uchar3 in = vload3(i, input); + output[i * 4 + 0] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(in.s0 >> 2) & 0x3F]; + output[i * 4 + 1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[((in.s0 & 0x03) << 4) | ((in.s1 >> 4) & 0x0F)]; + output[i * 4 + 2] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[((in.s1 & 0x0F) << 2) | ((in.s2 >> 6) & 0x03)]; + output[i * 4 + 3] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[in.s2 & 0x3F]; + } + } + )"; + +const char* base64DecodeKernelSource = R"( + __kernel void base64DecodeKernel(__global const char* input, __global uchar* output, int size) { + int i = get_global_id(0); + if (i < size / 4) { + char4 in = vload4(i, input); + output[i * 3 + 0] = (uchar)((in.s0 << 2) | ((in.s1 >> 4) & 0x03)); + output[i * 3 + 1] = (uchar)(((in.s1 & 0x0F) << 4) | ((in.s2 >> 2) & 0x0F)); + output[i * 3 + 2] = (uchar)(((in.s2 & 0x03) << 6) | (in.s3 & 0x3F)); + } + } + )"; + +// OpenCL kernel for XOR encryption/decryption +const char* xorKernelSource = R"( + __kernel void xorKernel(__global const char* input, __global char* output, uchar key, int size) { + int i = get_global_id(0); + if (i < size) { + output[i] = input[i] ^ key; + } + } + )"; + +// OpenCL setup and context management +cl_context context; +cl_command_queue queue; +cl_program program; +cl_kernel base64EncodeKernel, base64DecodeKernel, xorKernel; + +void initializeOpenCL() { + // Initialize OpenCL context, compile the kernels, etc. + // Error handling omitted for brevity + cl_platform_id platform; + cl_device_id device; + clGetPlatformIDs(1, &platform, nullptr); + clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, nullptr); + context = clCreateContext(nullptr, 1, &device, nullptr, nullptr, nullptr); + queue = clCreateCommandQueue(context, device, 0, nullptr); + + // Compile the kernels + const char* sources[] = {base64EncodeKernelSource, base64DecodeKernelSource, + xorKernelSource}; + program = clCreateProgramWithSource(context, 3, sources, nullptr, nullptr); + clBuildProgram(program, 1, &device, nullptr, nullptr, nullptr); + + base64EncodeKernel = clCreateKernel(program, "base64EncodeKernel", nullptr); + base64DecodeKernel = clCreateKernel(program, "base64DecodeKernel", nullptr); + xorKernel = clCreateKernel(program, "xorKernel", nullptr); } -std::vector base16Decode(const std::string &data) { - std::vector result; - - for (size_t i = 0; i < data.length(); i += 2) { - std::string byteStr = data.substr(i, 2); - unsigned char byte = - static_cast(std::stoi(byteStr, nullptr, 16)); - result.push_back(byte); - } - - return result; +void cleanupOpenCL() { + // Cleanup OpenCL resources + clReleaseKernel(base64EncodeKernel); + clReleaseKernel(base64DecodeKernel); + clReleaseKernel(xorKernel); + clReleaseProgram(program); + clReleaseCommandQueue(queue); + clReleaseContext(context); } -constexpr std::string_view base32_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; +void base64EncodeOpenCL(const unsigned char* input, char* output, size_t size) { + cl_mem inputBuffer = + clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, size, + (void*)input, nullptr); + cl_mem outputBuffer = clCreateBuffer(context, CL_MEM_WRITE_ONLY, + (size + 2) / 3 * 4, nullptr, nullptr); -std::string base32Encode(const uint8_t *data, size_t length) { - std::string result; - result.reserve((length + 4) / 5 * 8); + clSetKernelArg(base64EncodeKernel, 0, sizeof(cl_mem), &inputBuffer); + clSetKernelArg(base64EncodeKernel, 1, sizeof(cl_mem), &outputBuffer); + clSetKernelArg(base64EncodeKernel, 2, sizeof(int), &size); - size_t bits = 0; - int num_bits = 0; - for (size_t i = 0; i < length; ++i) { - bits = (bits << 8) | data[i]; - num_bits += 8; - while (num_bits >= 5) { - result.push_back(base32_chars[(bits >> (num_bits - 5)) & 0x1F]); - num_bits -= 5; - } - } - - if (num_bits > 0) { - bits <<= (5 - num_bits); - result.push_back(base32_chars[bits & 0x1F]); - } + size_t globalWorkSize = (size + 2) / 3; + clEnqueueNDRangeKernel(queue, base64EncodeKernel, 1, nullptr, + &globalWorkSize, nullptr, 0, nullptr, nullptr); - int padding_chars = (8 - result.size() % 8) % 8; - result.append(padding_chars, '='); + clEnqueueReadBuffer(queue, outputBuffer, CL_TRUE, 0, (size + 2) / 3 * 4, + output, 0, nullptr, nullptr); - return result; + clReleaseMemObject(inputBuffer); + clReleaseMemObject(outputBuffer); } -std::string base32Decode(std::string_view encoded) { - std::string result; - result.reserve(encoded.size() * 5 / 8); - - size_t bits = 0; - int num_bits = 0; - for (char c : encoded) { - if (c == '=') { - break; - } - auto pos = base32_chars.find(c); - if (pos == std::string_view::npos) { - throw std::invalid_argument( - "Invalid character in Base32 encoded string"); - } - bits = (bits << 5) | pos; - num_bits += 5; - if (num_bits >= 8) { - result.push_back(static_cast(bits >> (num_bits - 8))); - num_bits -= 8; - } - } +void base64DecodeOpenCL(const char* input, unsigned char* output, size_t size) { + cl_mem inputBuffer = + clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, size, + (void*)input, nullptr); + cl_mem outputBuffer = clCreateBuffer(context, CL_MEM_WRITE_ONLY, + size / 4 * 3, nullptr, nullptr); - return result; -} + clSetKernelArg(base64DecodeKernel, 0, sizeof(cl_mem), &inputBuffer); + clSetKernelArg(base64DecodeKernel, 1, sizeof(cl_mem), &outputBuffer); + clSetKernelArg(base64DecodeKernel, 2, sizeof(int), &size); -std::string base64Encode(std::string_view bytes_to_encode) { - static constexpr std::string_view kBase64Chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; + size_t globalWorkSize = size / 4; + clEnqueueNDRangeKernel(queue, base64DecodeKernel, 1, nullptr, + &globalWorkSize, nullptr, 0, nullptr, nullptr); - std::string ret; - ret.reserve((bytes_to_encode.size() + 2) / 3 * 4); + clEnqueueReadBuffer(queue, outputBuffer, CL_TRUE, 0, size / 4 * 3, output, + 0, nullptr, nullptr); - std::array char_array_3{}; - std::array char_array_4{}; + clReleaseMemObject(inputBuffer); + clReleaseMemObject(outputBuffer); +} - auto it = bytes_to_encode.begin(); - auto end = bytes_to_encode.end(); +void xorEncryptOpenCL(const char* input, char* output, uint8_t key, + size_t size) { + cl_mem inputBuffer = + clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, size, + (void*)input, nullptr); + cl_mem outputBuffer = + clCreateBuffer(context, CL_MEM_WRITE_ONLY, size, nullptr, nullptr); - while (it != end) { - int i = 0; - for (; i < 3 && it != end; ++i, ++it) { - char_array_3[i] = static_cast(*it); - } + clSetKernelArg(xorKernel, 0, sizeof(cl_mem), &inputBuffer); + clSetKernelArg(xorKernel, 1, sizeof(cl_mem), &outputBuffer); + clSetKernelArg(xorKernel, 2, sizeof(uint8_t), &key); + clSetKernelArg(xorKernel, 3, sizeof(int), &size); - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = - ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = - ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; + size_t globalWorkSize = size; + clEnqueueNDRangeKernel(queue, xorKernel, 1, nullptr, &globalWorkSize, + nullptr, 0, nullptr, nullptr); - for (int j = 0; j < i + 1; ++j) { - ret.push_back(kBase64Chars[char_array_4[j]]); - } + clEnqueueReadBuffer(queue, outputBuffer, CL_TRUE, 0, size, output, 0, + nullptr, nullptr); - if (i < 3) { - for (int j = i; j < 3; ++j) { - ret.push_back('='); + clReleaseMemObject(inputBuffer); + clReleaseMemObject(outputBuffer); +} +#endif +template +void base64Encode(InputIt begin, InputIt end, OutputIt dest) { + std::array charArray3{}; + std::array charArray4{}; + + size_t i = 0; + for (auto it = begin; it != end; ++it, ++i) { + charArray3[i % 3] = static_cast(*it); + if (i % 3 == 2) { + charArray4[0] = (charArray3[0] & 0xfc) >> 2; + charArray4[1] = + ((charArray3[0] & 0x03) << 4) + ((charArray3[1] & 0xf0) >> 4); + charArray4[2] = + ((charArray3[1] & 0x0f) << 2) + ((charArray3[2] & 0xc0) >> 6); + charArray4[3] = charArray3[2] & 0x3f; + + for (int j = 0; j < 4; ++j) { + *dest++ = BASE64_CHARS[charArray4[j]]; } - break; } } - return ret; -} - -std::string base64Decode(std::string_view encoded_string) { - static constexpr std::string_view kBase64Chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - - std::string ret; - ret.reserve(encoded_string.size() / 4 * 3); - - std::array char_array_4{}; - std::array char_array_3{}; + if (i % 3 != 0) { + for (size_t j = i % 3; j < 3; ++j) { + charArray3[j] = '\0'; + } - auto it = encoded_string.begin(); - auto end = encoded_string.end(); + charArray4[0] = (charArray3[0] & 0xfc) >> 2; + charArray4[1] = + ((charArray3[0] & 0x03) << 4) + ((charArray3[1] & 0xf0) >> 4); + charArray4[2] = + ((charArray3[1] & 0x0f) << 2) + ((charArray3[2] & 0xc0) >> 6); + charArray4[3] = charArray3[2] & 0x3f; - while (it != end) { - int i = 0; - for (; i < 4 && it != end && *it != '='; ++i, ++it) { - char_array_4[i] = - static_cast(kBase64Chars.find(*it)); + for (size_t j = 0; j < i % 3 + 1; ++j) { + *dest++ = BASE64_CHARS[charArray4[j]]; } - char_array_3[0] = - (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = - ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - - for (int j = 0; j < i - 1; ++j) { - ret.push_back(static_cast(char_array_3[j])); + while (i++ % 3 != 0) { + *dest++ = '='; } } - - return ret; } -const std::string base85_chars = - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<" - "=>?@^_`{|}~"; - -std::string base85Encode(const std::vector &data) { - std::string result; - - unsigned int value = 0; - int count = 0; - - for (unsigned char byte : data) { - value = value * 256 + byte; - count += 8; - - while (count >= 5) { - int index = (value >> (count - 5)) & 0x1F; - result += base85_chars[index]; - count -= 5; +template +void base64Decode(InputIt begin, InputIt end, OutputIt dest) { + std::array charArray4{}; + std::array charArray3{}; + + size_t i = 0; + for (auto it = begin; it != end && *it != '='; ++it) { + charArray4[i++] = static_cast(BASE64_CHARS.find(*it)); + if (i == 4) { + charArray3[0] = + (charArray4[0] << 2) + ((charArray4[1] & 0x30) >> 4); + charArray3[1] = + ((charArray4[1] & 0xf) << 4) + ((charArray4[2] & 0x3c) >> 2); + charArray3[2] = ((charArray4[2] & 0x3) << 6) + charArray4[3]; + + for (i = 0; i < 3; ++i) { + *dest++ = charArray3[i]; + } + i = 0; } } - if (count > 0) { - value <<= (5 - count); - int index = value & 0x1F; - result += base85_chars[index]; - } - - return result; -} - -std::vector base85Decode(const std::string &data) { - std::vector result; - - unsigned int value = 0; - int count = 0; + if (i != 0) { + for (size_t j = i; j < 4; ++j) { + charArray4[j] = 0; + } - for (char c : data) { - if (c >= '!' && c <= 'u') { - value = value * 85 + (c - '!'); - count += 5; + charArray3[0] = (charArray4[0] << 2) + ((charArray4[1] & 0x30) >> 4); + charArray3[1] = + ((charArray4[1] & 0xf) << 4) + ((charArray4[2] & 0x3c) >> 2); - if (count >= 8) { - result.push_back((value >> (count - 8)) & 0xFF); - count -= 8; - } + for (size_t j = 0; j < i - 1; ++j) { + *dest++ = charArray3[j]; } } - - return result; } +} // namespace detail -constexpr std::array kEncodeTable = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '#', '$', - '%', '&', '(', ')', '*', '+', ',', '.', '/', ':', ';', '<', '=', - '>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~', '"'}; - -constexpr std::array kDecodeTable = []() { - std::array table{}; - table.fill(-1); - for (int i = 0; i < kEncodeTable.size(); ++i) { - table[kEncodeTable[i]] = i; - } - return table; -}(); - -std::string base91Encode(std::string_view data) { - std::string result; - result.reserve(data.size() * 2); - - int ebq = 0; - int en = 0; - for (char c : data) { - ebq |= static_cast(c) << en; - en += 8; - if (en > 13) { - int ev = ebq & 8191; - if (ev > 88) { - ebq >>= 13; - en -= 13; - } else { - ev = ebq & 16383; - ebq >>= 14; - en -= 14; - } - result += kEncodeTable[ev % 91]; - result += kEncodeTable[ev / 91]; - } - } +auto base64Encode(std::string_view bytes_to_encode) -> std::string { + std::string ret; + ret.reserve((bytes_to_encode.size() + 2) / 3 * 4); - if (en > 0) { - result += kEncodeTable[ebq % 91]; - if (en > 7 || ebq > 90) { - result += kEncodeTable[ebq / 91]; - } + if (HAS_OPEN_CL) { +#if USE_OPENCL + detail::base64EncodeOpenCL( + reinterpret_cast(bytes_to_encode.data()), + ret.data(), bytes_to_encode.size()); +#endif + } else { + detail::base64Encode(bytes_to_encode.begin(), bytes_to_encode.end(), + std::back_inserter(ret)); } - return result; + return ret; } -std::string base91Decode(std::string_view data) { - std::string result; - result.reserve(data.size()); - - int dbq = 0; - int dn = 0; - int dv = -1; - - for (char c : data) { - if (c == '"') { - continue; - } - if (dv == -1) { - dv = kDecodeTable[c]; - } else { - dv += kDecodeTable[c] * 91; - dbq |= dv << dn; - dn += (dv & 8191) > 88 ? 13 : 14; - do { - result += static_cast(dbq & 0xFF); - dbq >>= 8; - dn -= 8; - } while (dn > 7); - dv = -1; - } - } +auto base64Decode(std::string_view encoded_string) -> std::string { + std::string ret; + ret.reserve(encoded_string.size() / 4 * 3); - if (dv != -1) { - result += static_cast((dbq | dv << dn) & 0xFF); + if (HAS_OPEN_CL) { +#if USE_OPENCL + detail::base64DecodeOpenCL(encoded_string.data(), + reinterpret_cast(ret.data()), + encoded_string.size()); +#endif + } else { + detail::base64Decode(encoded_string.begin(), encoded_string.end(), + std::back_inserter(ret)); } - return result; + return ret; } -std::string base128Encode(const uint8_t *data, size_t length) { - std::string result; - result.reserve((length * 8 + 6) / 7); - - size_t bits = 0; - int num_bits = 0; - for (size_t i = 0; i < length; ++i) { - bits = (bits << 8) | data[i]; - num_bits += 8; - while (num_bits >= 7) { - result.push_back( - static_cast((bits >> (num_bits - 7)) & 0x7F)); - num_bits -= 7; - } - } +auto fbase64Encode(std::span input) -> std::string { + std::string output; + output.reserve((input.size() + 2) / 3 * 4); - if (num_bits > 0) { - bits <<= (7 - num_bits); - result.push_back(static_cast(bits & 0x7F)); + if (HAS_OPEN_CL) { +#if USE_OPENCL + detail::base64EncodeOpenCL(input.data(), output.data(), input.size()); +#endif + } else { + detail::base64Encode(input.begin(), input.end(), + std::back_inserter(output)); } - return result; + return output; } -std::string base128Decode(std::string_view encoded) { - std::string result; - result.reserve(encoded.size() * 7 / 8); +auto fbase64Decode(std::span input) -> std::vector { + if (input.size() % 4 != 0) { + THROW_INVALID_ARGUMENT("Invalid base64 input length"); + } - size_t bits = 0; - int num_bits = 0; - for (char c : encoded) { - if (static_cast(c) > 127) { - throw std::invalid_argument( - "Invalid character in Base128 encoded string"); - } - bits = (bits << 7) | static_cast(c); - num_bits += 7; - if (num_bits >= 8) { - result.push_back(static_cast(bits >> (num_bits - 8))); - num_bits -= 8; - } + std::vector output; + output.reserve(input.size() / 4 * 3); + + if (HAS_OPEN_CL) { +#if USE_OPENCL + detail::base64DecodeOpenCL(input.data(), output.data(), input.size()); +#endif + } else { + detail::base64Decode(input.begin(), input.end(), + std::back_inserter(output)); } - return result; + return output; } -std::string xorEncrypt(std::string_view plaintext, uint8_t key) { +auto xorEncrypt(std::string_view plaintext, uint8_t key) -> std::string { std::string ciphertext; ciphertext.reserve(plaintext.size()); - for (char c : plaintext) { - ciphertext.push_back(static_cast(static_cast(c) ^ key)); + + if (HAS_OPEN_CL) { +#if USE_OPENCL + detail::xorEncryptOpenCL(plaintext.data(), ciphertext.data(), key, + plaintext.size()); +#endif + } else { + for (char c : plaintext) { + ciphertext.push_back( + static_cast(static_cast(c) ^ key)); + } } + return ciphertext; } -std::string xorDecrypt(std::string_view ciphertext, uint8_t key) { +auto xorDecrypt(std::string_view ciphertext, uint8_t key) -> std::string { return xorEncrypt(ciphertext, key); } } // namespace atom::algorithm diff --git a/src/atom/algorithm/base.hpp b/src/atom/algorithm/base.hpp index ae8feb57..006394cd 100644 --- a/src/atom/algorithm/base.hpp +++ b/src/atom/algorithm/base.hpp @@ -16,63 +16,27 @@ Description: A collection of algorithms for C++ #define ATOM_ALGORITHM_BASE16_HPP #include +#include #include #include -namespace atom::algorithm { -/** - * @brief Encodes a vector of unsigned characters into a Base16 string. - * - * This function takes a vector of unsigned characters and encodes it into a - * Base16 string representation. Base16 encoding, also known as hexadecimal - * encoding, represents each byte in the data data as a pair of hexadecimal - * digits (0-9, A-F). - * - * @param data The vector of unsigned characters to be encoded. - * @return The Base16 encoded string. - */ -[[nodiscard("The result of base16Encode is not used.")]] std::string -base16Encode(const std::vector &data); - -/** - * @brief Decodes a Base16 string into a vector of unsigned characters. - * - * This function takes a Base16 encoded string and decodes it into a vector of - * unsigned characters. Base16 encoding, also known as hexadecimal encoding, - * represents each byte in the data data as a pair of hexadecimal digits (0-9, - * A-F). - * - * @param data The Base16 encoded string to be decoded. - * @return The decoded vector of unsigned characters. - */ -[[nodiscard( - "The result of base16Decode is not used.")]] std::vector -base16Decode(const std::string &data); - -/** - * @brief Encodes a string to Base32 - * @param data The string to encode - * @return The encoded string - */ -[[nodiscard("The result of base32Encode is not used.")]] std::string -base32Encode(const uint8_t *data, size_t length); - -/** - * @brief Decodes a Base32 string - * @param data The string to decode - * @return The decoded string - */ -[[nodiscard("The result of base32Decode is not used.")]] std::string -base32Decode(std::string_view encoded); +#include "atom/type/static_string.hpp" +namespace atom::algorithm { +namespace detail { +constexpr std::string_view BASE64_CHARS = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; +} /** * @brief Base64编码函数 * * @param bytes_to_encode 待编码数据 * @return std::string 编码后的字符串 */ -[[nodiscard("The result of base64Encode is not used.")]] std::string -base64Encode(std::string_view bytes_to_encode); +[[nodiscard("The result of base64Encode is not used.")]] auto base64Encode( + std::string_view bytes_to_encode) -> std::string; /** * @brief Base64解码函数 @@ -80,83 +44,148 @@ base64Encode(std::string_view bytes_to_encode); * @param encoded_string 待解码字符串 * @return std::vector 解码后的数据 */ -[[nodiscard("The result of base64Decode is not used.")]] std::string -base64Decode(std::string_view encoded_string); +[[nodiscard("The result of base64Decode is not used.")]] auto base64Decode( + std::string_view encoded_string) -> std::string; /** - * @brief Encodes a vector of unsigned characters into a Base85 string. + * @brief Faster Base64 Encode * - * This function takes a vector of unsigned characters and encodes it into a - * Base85 string representation. Base85 encoding is a binary-to-text encoding - * scheme that encodes 4 bytes into 5 ASCII characters. - * - * @param data The vector of unsigned characters to be encoded. - * @return The Base85 encoded string. + * @param input + * @return std::string */ -[[nodiscard("The result of base85Encode is not used.")]] std::string -base85Encode(const std::vector &data); +auto fbase64Encode(std::span input) -> std::string; /** - * @brief Decodes a Base85 string into a vector of unsigned characters. - * - * This function takes a Base85 encoded string and decodes it into a vector of - * unsigned characters. Base85 encoding is a binary-to-text encoding scheme that - * encodes 4 bytes into 5 ASCII characters. + * @brief Faster Base64 Decode * - * @param data The Base85 encoded string to be decoded. - * @return The decoded vector of unsigned characters. - */ -[[nodiscard( - "The result of base85Decode is not used.")]] std::vector -base85Decode(const std::string &data); - -/** - * @brief Encodes a string to Base91 - * @param data The string to encode - * @return The encoded string + * @param input + * @return std::vector */ -[[nodiscard("The result of base91Encode is not used.")]] -std::string base91Encode(std::string_view data); +auto fbase64Decode(std::span input) -> std::vector; /** - * @brief Decodes a Base91 string - * @param data The string to decode - * @return The decoded string - */ -[[nodiscard("The result of base91Decode is not used.")]] std::string -base91Decode(std::string_view data); - -/** - * @brief Encodes a vector of unsigned characters into a Base128 string. - * - * This function takes a vector of unsigned characters and encodes it into a - * Base128 string representation. Base128 encoding is a binary-to-text encoding - * scheme that encodes 1 byte into 1 ASCII character. + * @brief Encrypts a string using the XOR algorithm. * - * @param data The vector of unsigned characters to be encoded. - * @return The Base128 encoded string. + * @param data The string to be encrypted. + * @param key The encryption key. + * @return The encrypted string. */ -[[nodiscard("The result of encodeBase128 is not used.")]] std::string -base128Encode(const uint8_t *data, size_t length); +[[nodiscard("The result of xorEncrypt is not used.")]] auto xorEncrypt( + std::string_view plaintext, uint8_t key) -> std::string; /** - * @brief Decodes a Base128 string into a vector of unsigned characters. + * @brief Decrypts a string using the XOR algorithm. * - * This function takes a Base128 encoded string and decodes it into a vector of - * unsigned characters. Base128 encoding is a binary-to-text encoding scheme - * that encodes 1 byte into 1 ASCII character. - * - * @param data The Base128 encoded string to be decoded. - * @return The decoded vector of unsigned characters. + * @param data The string to be decrypted. + * @param key The decryption key. + * @return The decrypted string. */ -[[nodiscard("The result of decodeBase128 is not used.")]] std::string -base128Decode(std::string_view encoded); - -[[nodiscard("The result of xorEncrypt is not used.")]] std::string xorEncrypt( - std::string_view plaintext, uint8_t key); - -[[nodiscard("The result of xorDecrypt is not used.")]] std::string xorDecrypt( - std::string_view ciphertext, uint8_t key); +[[nodiscard("The result of xorDecrypt is not used.")]] auto xorDecrypt( + std::string_view ciphertext, uint8_t key) -> std::string; + +ATOM_INLINE constexpr auto findBase64Char(char c) -> size_t { + for (size_t i = 0; i < 64; ++i) { + if (detail::BASE64_CHARS[i] == c) { + return i; + } + } + return 64; // Indicates not found, should not happen with valid input +} + +template +constexpr auto cbase64Encode(const StaticString &input) { + constexpr size_t ENCODED_SIZE = ((N + 2) / 3) * 4; + StaticString ret; + + auto addChar = [&](char c) constexpr { ret += c; }; + + std::array charArray3{}; + std::array charArray4{}; + + size_t i = 0; + for (auto it = input.begin(); it != input.end(); ++it, ++i) { + charArray3[i % 3] = static_cast(*it); + if (i % 3 == 2) { + charArray4[0] = (charArray3[0] & 0xfc) >> 2; + charArray4[1] = + ((charArray3[0] & 0x03) << 4) + ((charArray3[1] & 0xf0) >> 4); + charArray4[2] = + ((charArray3[1] & 0x0f) << 2) + ((charArray3[2] & 0xc0) >> 6); + charArray4[3] = charArray3[2] & 0x3f; + + for (int j = 0; j < 4; ++j) { + addChar(detail::BASE64_CHARS[charArray4[j]]); + } + } + } + + if (i % 3 != 0) { + for (size_t j = i % 3; j < 3; ++j) { + charArray3[j] = '\0'; + } + + charArray4[0] = (charArray3[0] & 0xfc) >> 2; + charArray4[1] = + ((charArray3[0] & 0x03) << 4) + ((charArray3[1] & 0xf0) >> 4); + charArray4[2] = + ((charArray3[1] & 0x0f) << 2) + ((charArray3[2] & 0xc0) >> 6); + charArray4[3] = charArray3[2] & 0x3f; + + for (size_t j = 0; j < i % 3 + 1; ++j) { + addChar(detail::BASE64_CHARS[charArray4[j]]); + } + + while (i++ % 3 != 0) { + addChar('='); + } + } + + return ret; +} + +template +constexpr auto cbase64Decode(const StaticString &input) { + constexpr size_t DECODED_SIZE = (N / 4) * 3; + StaticString ret; + + auto addChar = [&](char c) constexpr { ret += c; }; + + std::array charArray4{}; + std::array charArray3{}; + + size_t i = 0; + for (auto it = input.begin(); it != input.end() && *it != '='; ++it) { + charArray4[i++] = static_cast(findBase64Char(*it)); + if (i == 4) { + charArray3[0] = + (charArray4[0] << 2) + ((charArray4[1] & 0x30) >> 4); + charArray3[1] = + ((charArray4[1] & 0xf) << 4) + ((charArray4[2] & 0x3c) >> 2); + charArray3[2] = ((charArray4[2] & 0x3) << 6) + charArray4[3]; + + for (i = 0; i < 3; ++i) { + addChar(static_cast(charArray3[i])); + } + i = 0; + } + } + + if (i != 0) { + for (size_t j = i; j < 4; ++j) { + charArray4[j] = 0; + } + + charArray3[0] = (charArray4[0] << 2) + ((charArray4[1] & 0x30) >> 4); + charArray3[1] = + ((charArray4[1] & 0xf) << 4) + ((charArray4[2] & 0x3c) >> 2); + + for (size_t j = 0; j < i - 1; ++j) { + addChar(static_cast(charArray3[j])); + } + } + + return ret; +} } // namespace atom::algorithm #endif diff --git a/src/atom/algorithm/bignumber.cpp b/src/atom/algorithm/bignumber.cpp new file mode 100644 index 00000000..2cd2dd51 --- /dev/null +++ b/src/atom/algorithm/bignumber.cpp @@ -0,0 +1,194 @@ +#include "bignumber.hpp" + +#include + +#include "atom/log/loguru.hpp" + +namespace atom::algorithm { +auto BigNumber::add(const BigNumber& other) const -> BigNumber { + if (isNegative() && other.isNegative()) { + return negate().add(other.negate()).negate(); + } + if (isNegative()) { + return other.subtract(abs()); + } + if (other.isNegative()) { + return subtract(other.abs()); + } + + std::string result; + int carry = 0; + int i = numberString_.length() - 1; + int j = other.numberString_.length() - 1; + + while (i >= 0 || j >= 0 || (carry != 0)) { + int digit1 = (i >= 0) ? numberString_[i--] - '0' : 0; + int digit2 = (j >= 0) ? other.numberString_[j--] - '0' : 0; + int sum = digit1 + digit2 + carry; + result.insert(result.begin(), '0' + (sum % 10)); + carry = sum / 10; + } + + return {result}; +} + +auto BigNumber::subtract(const BigNumber& other) const -> BigNumber { + if (isNegative() && other.isNegative()) { + return other.negate().subtract(negate()); + } + if (isNegative()) { + return negate().add(other).negate(); + } + if (other.isNegative()) { + return add(other.negate()); + } + if (*this < other) { + return other.subtract(*this).negate(); + } + + std::string result; + int carry = 0; + int i = numberString_.length() - 1; + int j = other.numberString_.length() - 1; + + while (i >= 0 || j >= 0) { + int digit1 = (i >= 0) ? numberString_[i--] - '0' : 0; + int digit2 = (j >= 0) ? other.numberString_[j--] - '0' : 0; + int diff = digit1 - digit2 - carry; + if (diff < 0) { + diff += 10; + carry = 1; + } else { + carry = 0; + } + result.insert(result.begin(), '0' + diff); + } + + return BigNumber(result).trimLeadingZeros(); +} + +auto BigNumber::multiply(const BigNumber& other) const -> BigNumber { + if (*this == 0 || other == 0) { + return {"0"}; + } + + bool resultNegative = isNegative() != other.isNegative(); + BigNumber b1 = abs(); + BigNumber b2 = other.abs(); + + std::vector result(b1.numberString_.size() + b2.numberString_.size(), + 0); + + for (int i = b1.numberString_.size() - 1; i >= 0; --i) { + for (int j = b2.numberString_.size() - 1; j >= 0; --j) { + int mul = (b1.numberString_[i] - '0') * (b2.numberString_[j] - '0'); + int sum = mul + result[i + j + 1]; + + result[i + j + 1] = sum % 10; + result[i + j] += sum / 10; + } + } + + std::string resultStr; + for (int num : result) { + if (!resultStr.empty() || num != 0) { + resultStr.push_back(num + '0'); + } + } + + if (resultStr.empty()) { + resultStr = "0"; + } + + if (resultNegative && resultStr != "0") { + resultStr.insert(resultStr.begin(), '-'); + } + + return {resultStr}; +} + +auto BigNumber::divide(const BigNumber& other) const -> BigNumber { + if (other == 0) { + throw std::invalid_argument("Division by zero"); + } + + bool resultNegative = isNegative() != other.isNegative(); + BigNumber dividend = abs(); + BigNumber divisor = other.abs(); + BigNumber quotient("0"); + BigNumber current("0"); + + for (char i : dividend.numberString_) { + current = current * 10 + BigNumber(std::string(1, i)); + int count = 0; + while (current >= divisor) { + current = current - divisor; + ++count; + } + quotient = quotient * 10 + BigNumber(std::to_string(count)); + } + + quotient = quotient.trimLeadingZeros(); + if (resultNegative && quotient != 0) { + quotient = quotient.negate(); + } + + return quotient; +} + +auto BigNumber::pow(int exponent) const -> BigNumber { + if (exponent < 0) { + LOG_F(ERROR, "Powers less than 0 are not supported"); + return {"0"}; + } + if (exponent == 0) { + return {"1"}; + } + if (exponent == 1) { + return *this; + } + BigNumber result = std::string("1"); + BigNumber base = *this; + while (exponent != 0) { + if ((exponent & 1) != 0) { + result = result.multiply(base); + } + exponent >>= 1; + base = base.multiply(base); + } + return result; +} + +auto BigNumber::trimLeadingZeros() const -> BigNumber { + BigNumber b = *this; + bool negative = b.isNegative(); + if (negative) { + b.numberString_.erase(0, 1); // Remove the negative sign temporarily + } + size_t nonZeroPos = b.numberString_.find_first_not_of('0'); + if (nonZeroPos != std::string::npos) { + b.numberString_ = b.numberString_.substr(nonZeroPos); + } else { + b.numberString_ = "0"; + } + if (negative && b.numberString_ != "0") { + b.numberString_.insert(b.numberString_.begin(), '-'); + } + return b; +} + +auto operator>(const BigNumber& b1, const BigNumber& b2) -> bool { + if (b1.isNegative() || b2.isNegative()) { + if (b1.isNegative() && b2.isNegative()) { + return b2.abs() > b1.abs(); + } + return !b1.isNegative(); + } + BigNumber b1Trimmed = b1.trimLeadingZeros(); + BigNumber b2Trimmed = b2.trimLeadingZeros(); + if (b1Trimmed.numberString_.size() != b2Trimmed.numberString_.size()) { + return b1Trimmed.numberString_.size() > b2Trimmed.numberString_.size(); + } + return b1Trimmed.numberString_ > b2Trimmed.numberString_; +} +} // namespace atom::algorithm diff --git a/src/atom/algorithm/bignumber.hpp b/src/atom/algorithm/bignumber.hpp new file mode 100644 index 00000000..08bc4f77 --- /dev/null +++ b/src/atom/algorithm/bignumber.hpp @@ -0,0 +1,142 @@ +#ifndef ATOM_ALGORITHM_BIGNUMBER_HPP +#define ATOM_ALGORITHM_BIGNUMBER_HPP + +#include + +#include "macro.hpp" + +namespace atom::algorithm { +class BigNumber { +public: + BigNumber(std::string number) : numberString_(std::move(number)) { + numberString_ = trimLeadingZeros().numberString_; + } + BigNumber(long long number) : numberString_(std::to_string(number)) {} + + ATOM_NODISCARD auto add(const BigNumber& other) const -> BigNumber; + ATOM_NODISCARD auto subtract(const BigNumber& other) const -> BigNumber; + ATOM_NODISCARD auto multiply(const BigNumber& other) const -> BigNumber; + ATOM_NODISCARD auto divide(const BigNumber& other) const -> BigNumber; + ATOM_NODISCARD auto pow(int exponent) const -> BigNumber; + + ATOM_NODISCARD auto getString() const -> std::string { + return numberString_; + } + auto setString(const std::string& newStr) -> BigNumber { + numberString_ = newStr; + return *this; + } + + ATOM_NODISCARD auto negate() const -> BigNumber { + return numberString_[0] == '-' ? BigNumber(numberString_.substr(1)) + : BigNumber("-" + numberString_); + } + ATOM_NODISCARD auto trimLeadingZeros() const -> BigNumber; + + ATOM_NODISCARD auto equals(const BigNumber& other) const -> bool { + return numberString_ == other.numberString_; + } + ATOM_NODISCARD auto equals(const long long& other) const -> bool { + return numberString_ == std::to_string(other); + } + ATOM_NODISCARD auto equals(const std::string& other) const -> bool { + return numberString_ == other; + } + + ATOM_NODISCARD auto digits() const -> unsigned int { + return numberString_.length() - static_cast(isNegative()); + } + ATOM_NODISCARD auto isNegative() const -> bool { + return numberString_[0] == '-'; + } + ATOM_NODISCARD auto isPositive() const -> bool { return !isNegative(); } + ATOM_NODISCARD auto isEven() const -> bool { + return (numberString_.back() - '0') % 2 == 0; + } + ATOM_NODISCARD auto isOdd() const -> bool { return !isEven(); } + ATOM_NODISCARD auto abs() const -> BigNumber { + return isNegative() ? BigNumber(numberString_.substr(1)) : *this; + } + + friend auto operator<<(std::ostream& os, + const BigNumber& num) -> std::ostream& { + os << num.numberString_; + return os; + } + friend auto operator+(const BigNumber& b1, + const BigNumber& b2) -> BigNumber { + return b1.add(b2); + } + friend auto operator-(const BigNumber& b1, + const BigNumber& b2) -> BigNumber { + return b1.subtract(b2); + } + friend auto operator*(const BigNumber& b1, + const BigNumber& b2) -> BigNumber { + return b1.multiply(b2); + } + friend auto operator/(const BigNumber& b1, + const BigNumber& b2) -> BigNumber { + return b1.divide(b2); + } + friend auto operator^(const BigNumber& b1, int b2) -> BigNumber { + return b1.pow(b2); + } + friend auto operator==(const BigNumber& b1, const BigNumber& b2) -> bool { + return b1.equals(b2); + } + friend auto operator>(const BigNumber& b1, const BigNumber& b2) -> bool; + friend auto operator<(const BigNumber& b1, const BigNumber& b2) -> bool { + return !(b1 == b2) && !(b1 > b2); + } + friend auto operator>=(const BigNumber& b1, const BigNumber& b2) -> bool { + return b1 > b2 || b1 == b2; + } + friend auto operator<=(const BigNumber& b1, const BigNumber& b2) -> bool { + return b1 < b2 || b1 == b2; + } + + auto operator+=(const BigNumber& other) -> BigNumber& { + *this = *this + other; + return *this; + } + auto operator-=(const BigNumber& other) -> BigNumber& { + *this = *this - other; + return *this; + } + auto operator*=(const BigNumber& other) -> BigNumber& { + *this = *this * other; + return *this; + } + auto operator/=(const BigNumber& other) -> BigNumber& { + *this = *this / other; + return *this; + } + auto operator++() -> BigNumber& { + *this += BigNumber("1"); + return *this; + } + auto operator--() -> BigNumber& { + *this -= BigNumber("1"); + return *this; + } + auto operator++(int) -> BigNumber { + BigNumber t(*this); + ++(*this); + return t; + } + auto operator--(int) -> BigNumber { + BigNumber t(*this); + --(*this); + return t; + } + auto operator[](int index) const -> unsigned int { + return static_cast(numberString_[index] - '0'); + } + +private: + std::string numberString_; +}; +} // namespace atom::algorithm + +#endif // ATOM_ALGORITHM_BIGNUMBER_HPP diff --git a/src/atom/algorithm/calculator.hpp b/src/atom/algorithm/calculator.hpp deleted file mode 100644 index 944df60c..00000000 --- a/src/atom/algorithm/calculator.hpp +++ /dev/null @@ -1,464 +0,0 @@ -/* - * calculator.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-4-5 - -Description: A calculator implementation under C++20 - -**************************************************/ - -#ifndef ATOM_ALGORITHM_CALCULATOR_HPP -#define ATOM_ALGORITHM_CALCULATOR_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace atom::algorithm { -class error : public std::runtime_error { -public: - error(std::string_view expr, std::string_view message) - : std::runtime_error(std::string(message)), expr_(expr) {} - std::string_view expression() const noexcept { return expr_; } - -private: - std::string expr_; -}; - -template -class ExpressionParser { -public: - T eval(std::string_view expr) { - T result = 0; - index_ = 0; - expr_ = expr; - try { - result = parseExpr(); - if (!isEnd()) - unexpected(); - } catch (const error &) { - stack_ = {}; - throw; - } - return result; - } - - T eval(char c) { return eval(std::string_view(&c, 1)); } - - void set(const std::string &name, T value) { variables_[name] = value; } - - void set(const std::string &name, std::function func) { - functions_[name] = func; - } - -private: - std::unordered_map variables_; - std::unordered_map> functions_; - - enum { - OPERATOR_NULL, - OPERATOR_BITWISE_OR, /// | - OPERATOR_BITWISE_XOR, /// ^ - OPERATOR_BITWISE_AND, /// & - OPERATOR_BITWISE_SHL, /// << - OPERATOR_BITWISE_SHR, /// >> - OPERATOR_ADDITION, /// + - OPERATOR_SUBTRACTION, /// - - OPERATOR_MULTIPLICATION, /// * - OPERATOR_DIVISION, /// / - OPERATOR_MODULO, /// % - OPERATOR_POWER, /// ** - OPERATOR_EXPONENT /// e, E - }; - - struct Operator { - int op; - int precedence; - char associativity; - constexpr Operator(int opr, int prec, char assoc) noexcept - : op(opr), precedence(prec), associativity(assoc) {} - }; - - struct OperatorValue { - Operator op; - T value; - constexpr OperatorValue(const Operator &opr, T val) noexcept - : op(opr), value(val) {} - constexpr int getPrecedence() const noexcept { return op.precedence; } - constexpr bool isNull() const noexcept { - return op.op == OPERATOR_NULL; - } - }; - - static T pow(T x, T n) { - T res = 1; - - while (n > 0) { - if (n & 1) { - res *= x; - } - - x *= x; - n >>= 1; - } - - return res; - } - - [[nodiscard]] T checkZero(T value) const { - if (value == 0) { - auto divOperators = std::string_view("/%"); - auto division = expr_.find_last_of(divOperators, index_ - 2); - std::ostringstream msg; - msg << "Parser error: division by 0"; - if (division != std::string_view::npos) - msg << " (error token is \"" - << expr_.substr(division, expr_.size() - division) << "\")"; - throw error(expr_, msg.str()); - } - return value; - } - - [[nodiscard]] constexpr T calculate(T v1, T v2, - const Operator &op) const noexcept { - if constexpr (std::is_same_v || std::is_same_v) { - switch (op.op) { - case OPERATOR_ADDITION: - return v1 + v2; - case OPERATOR_SUBTRACTION: - return v1 - v2; - case OPERATOR_MULTIPLICATION: - return v1 * v2; - case OPERATOR_DIVISION: - return v1 / checkZero(v2); - case OPERATOR_POWER: - return std::pow(v1, v2); - case OPERATOR_EXPONENT: - return v1 * std::pow(10, v2); - default: - return 0; - } - } else { - switch (op.op) { - case OPERATOR_BITWISE_OR: - return v1 | v2; - case OPERATOR_BITWISE_XOR: - return v1 ^ v2; - case OPERATOR_BITWISE_AND: - return v1 & v2; - case OPERATOR_BITWISE_SHL: - return v1 << v2; - case OPERATOR_BITWISE_SHR: - return v1 >> v2; - case OPERATOR_ADDITION: - return v1 + v2; - case OPERATOR_SUBTRACTION: - return v1 - v2; - case OPERATOR_MULTIPLICATION: - return v1 * v2; - case OPERATOR_DIVISION: - return v1 / checkZero(v2); - case OPERATOR_MODULO: - return v1 % checkZero(v2); - case OPERATOR_POWER: - return pow(v1, v2); - case OPERATOR_EXPONENT: - return v1 * pow(10, v2); - default: - return 0; - } - } - } - - [[nodiscard]] constexpr bool isEnd() const noexcept { - return index_ >= expr_.size(); - } - - [[nodiscard]] constexpr char getCharacter() const noexcept { - if (!isEnd()) - return expr_[index_]; - return 0; - } - - void expect(std::string_view str) { - if (expr_.substr(index_, str.size()) != str) - unexpected(); - index_ += str.size(); - } - - [[noreturn]] void unexpected() const { - std::ostringstream msg; - msg << "Syntax error: unexpected token \"" << expr_.substr(index_) - << "\" at index " << index_; - throw error(expr_, msg.str()); - } - - constexpr void eatSpaces() noexcept { - while (std::isspace(getCharacter()) != 0) - index_++; - } - - [[nodiscard]] Operator parseOp() { - eatSpaces(); - switch (std::tolower(getCharacter())) { - case '|': - index_++; - return Operator(OPERATOR_BITWISE_OR, 4, 'L'); - case '^': - index_++; - return Operator(OPERATOR_BITWISE_XOR, 5, 'L'); - case '&': - index_++; - return Operator(OPERATOR_BITWISE_AND, 6, 'L'); - case '<': - expect("<<"); - return Operator(OPERATOR_BITWISE_SHL, 9, 'L'); - case '>': - expect(">>"); - return Operator(OPERATOR_BITWISE_SHR, 9, 'L'); - case '+': - index_++; - return Operator(OPERATOR_ADDITION, 10, 'L'); - case '-': - index_++; - return Operator(OPERATOR_SUBTRACTION, 10, 'L'); - case '/': - index_++; - return Operator(OPERATOR_DIVISION, 20, 'L'); - case '%': - index_++; - return Operator(OPERATOR_MODULO, 20, 'L'); - case '*': - index_++; - if (getCharacter() != '*') - return Operator(OPERATOR_MULTIPLICATION, 20, 'L'); - index_++; - return Operator(OPERATOR_POWER, 30, 'R'); - case 'e': - index_++; - return Operator(OPERATOR_EXPONENT, 40, 'R'); - default: - return Operator(OPERATOR_NULL, 0, 'L'); - } - } - - [[nodiscard]] static constexpr T toInteger(char c) noexcept { - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'a' && c <= 'f') - return c - 'a' + 0xa; - if (c >= 'A' && c <= 'F') - return c - 'A' + 0xa; - return 0xf + 1; - } - - [[nodiscard]] constexpr T getInteger() const noexcept { - return toInteger(getCharacter()); - } - - [[nodiscard]] T parseDecimal() { - T value = 0; - T fraction = 1; - bool decimal_point = false; - std::size_t length = 0; - std::size_t max_length = std::numeric_limits::digits10 + 1; - - while (index_ < expr_.size()) { - char c = expr_[index_]; - if (std::isdigit(c)) { - if (length >= max_length) { - throw error(expr_, "Number too large"); - } - value = value * 10 + (c - '0'); - if (decimal_point) - fraction *= 10; - length++; - } else if (c == '.' && !decimal_point) { - if (std::is_integral::value) { - throw error( - expr_, - "Decimal numbers are not allowed in integer mode"); - } - decimal_point = true; - } else if (c == '.' && decimal_point) { - throw error(expr_, "Multiple decimal points in number"); - } else { - break; - } - index_++; - } - return decimal_point ? value / fraction : value; - } - - [[nodiscard]] T parseHex() { - index_ += 2; - T value = 0; - std::size_t length = 0; - std::size_t max_length = std::numeric_limits::digits / 4; - - while (index_ < expr_.size()) { - T h = getInteger(); - if (h <= 0xf) { - if (length >= max_length) { - throw error(expr_, "Number too large"); - } - value = value * 0x10 + h; - length++; - index_++; - } else { - break; - } - } - return value; - } - - [[nodiscard]] constexpr bool isHex() const noexcept { - if (index_ + 2 < expr_.size()) { - char x = expr_[index_ + 1]; - char h = expr_[index_ + 2]; - return (std::tolower(x) == 'x' && toInteger(h) <= 0xf); - } - return false; - } - - [[nodiscard]] T parseValue() { - T val = 0; - eatSpaces(); - if (std::isalpha(getCharacter())) { - std::string name; - while (std::isalnum(getCharacter()) || getCharacter() == '_') { - name += getCharacter(); - index_++; - } - if (functions_.count(name) > 0) { - eatSpaces(); - if (getCharacter() != '(') { - throw error(expr_, "Expected '(' after function name"); - } - index_++; - T arg = parseExpr(); - if (getCharacter() != ')') { - throw error(expr_, "Expected ')' after function argument"); - } - index_++; - val = functions_[name](arg); - } else if (variables_.count(name) > 0) { - val = variables_[name]; - } else { - throw error(expr_, "Undefined function or variable: " + name); - } - } else { - switch (getCharacter()) { - case '0': - if (isHex()) - val = parseHex(); - else - val = parseDecimal(); - break; - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - val = parseDecimal(); - break; - case '(': - index_++; - val = parseExpr(); - eatSpaces(); - if (getCharacter() != ')') { - if (!isEnd()) - unexpected(); - throw error( - expr_, - "Syntax error: `)' expected at end of expression"); - } - index_++; - break; - case '~': - if constexpr (std::is_same_v) { - index_++; - val = ~parseValue(); - } else { - throw error(expr_, - "Syntax error: `~' not supported for " - "non-int types"); - } - break; - case '+': - index_++; - val = parseValue(); - break; - case '-': - index_++; - val = -parseValue(); - break; - default: - if (!isEnd()) - unexpected(); - throw error( - expr_, - "Syntax error: value expected at end of expression"); - } - } - - return val; - } - - T parseExpr() { - stack_.emplace(Operator(OPERATOR_NULL, 0, 'L'), 0); - T value = parseValue(); - - while (!stack_.empty()) { - Operator op(parseOp()); - while (op.precedence < stack_.top().getPrecedence() || - (op.precedence == stack_.top().getPrecedence() && - op.associativity == 'L')) { - if (stack_.top().isNull()) { - stack_.pop(); - return value; - } - value = calculate(stack_.top().value, value, stack_.top().op); - stack_.pop(); - } - - stack_.emplace(op, value); - value = parseValue(); - } - return 0; - } - - std::string_view expr_; - std::size_t index_ = 0; - std::stack stack_; -}; - -template -inline T eval(std::string_view expression) { - ExpressionParser parser; - return parser.eval(expression); -} - -inline double eval(const std::string &expression) { - return eval(expression); -} - -} // namespace atom::algorithm - -#endif diff --git a/src/atom/algorithm/convolve.cpp b/src/atom/algorithm/convolve.cpp index 11d9626b..de8b3f02 100644 --- a/src/atom/algorithm/convolve.cpp +++ b/src/atom/algorithm/convolve.cpp @@ -9,29 +9,40 @@ Date: 2023-11-10 Description: Implementation of one-dimensional and two-dimensional convolution -and deconvolution. +and deconvolution with optional OpenCL support. **************************************************/ #include "convolve.hpp" #include +#include #include +#include +#include +#include #include +#include + +#if USE_OPENCL +#include +#endif #include "atom/error/exception.hpp" namespace atom::algorithm { -std::vector convolve(const std::vector &input, - const std::vector &kernel) { - auto input_size = input.size(); - auto kernel_size = kernel.size(); - auto output_size = input_size + kernel_size - 1; - std::vector output(output_size, 0.0); - - for (std::size_t i = 0; i < output_size; ++i) { - for (std::size_t j = 0; j < kernel_size; ++j) { - if (i >= j && (i - j) < input_size) { + +// Function to convolve a 1D input with a kernel +auto convolve(const std::vector &input, + const std::vector &kernel) -> std::vector { + auto inputSize = input.size(); + auto kernelSize = kernel.size(); + auto outputSize = inputSize + kernelSize - 1; + std::vector output(outputSize, 0.0); + + for (std::size_t i = 0; i < outputSize; ++i) { + for (std::size_t j = 0; j < kernelSize; ++j) { + if (i >= j && (i - j) < inputSize) { output[i] += input[i - j] * kernel[j]; } } @@ -40,19 +51,20 @@ std::vector convolve(const std::vector &input, return output; } -std::vector deconvolve(const std::vector &input, - const std::vector &kernel) { - auto input_size = input.size(); - auto kernel_size = kernel.size(); - if (kernel_size > input_size) { - THROW_EXCEPTION("Kernel size cannot be larger than input size."); +// Function to deconvolve a 1D input with a kernel +auto deconvolve(const std::vector &input, + const std::vector &kernel) -> std::vector { + auto inputSize = input.size(); + auto kernelSize = kernel.size(); + if (kernelSize > inputSize) { + THROW_INVALID_ARGUMENT("Kernel size cannot be larger than input size."); } - auto output_size = input_size - kernel_size + 1; - std::vector output(output_size, 0.0); + auto outputSize = inputSize - kernelSize + 1; + std::vector output(outputSize, 0.0); - for (std::size_t i = 0; i < output_size; ++i) { - for (std::size_t j = 0; j < kernel_size; ++j) { + for (std::size_t i = 0; i < outputSize; ++i) { + for (std::size_t j = 0; j < kernelSize; ++j) { output[i] += input[i + j] * kernel[j]; } } @@ -60,37 +72,206 @@ std::vector deconvolve(const std::vector &input, return output; } -std::vector> convolve2D( - const std::vector> &input, - const std::vector> &kernel, int numThreads) { +// Helper function to extend 2D vectors +template +auto extend2D(const std::vector> &input, std::size_t newRows, + std::size_t newCols) -> std::vector> { + std::vector> extended(newRows, std::vector(newCols, 0.0)); auto inputRows = input.size(); auto inputCols = input[0].size(); - auto kernelRows = kernel.size(); - auto kernelCols = kernel[0].size(); - - // Extend input and kernel matrices with zeros - std::vector> extendedInput( - inputRows + kernelRows - 1, - std::vector(inputCols + kernelCols - 1, 0.0)); - std::vector> extendedKernel( - inputRows + kernelRows - 1, - std::vector(inputCols + kernelCols - 1, 0.0)); - - // Center the input in the extended input matrix for (std::size_t i = 0; i < inputRows; ++i) { for (std::size_t j = 0; j < inputCols; ++j) { - extendedInput[i + kernelRows / 2][j + kernelCols / 2] = input[i][j]; + extended[i + newRows / 2][j + newCols / 2] = input[i][j]; } } + return extended; +} + +#if USE_OPENCL +// OpenCL initialization and helper functions +auto initializeOpenCL() -> cl_context { + cl_uint numPlatforms; + cl_platform_id platform = nullptr; + clGetPlatformIDs(1, &platform, &numPlatforms); + + cl_context_properties properties[] = {CL_CONTEXT_PLATFORM, + (cl_context_properties)platform, 0}; + + cl_int err; + cl_context context = clCreateContextFromType(properties, CL_DEVICE_TYPE_GPU, + nullptr, nullptr, &err); + if (err != CL_SUCCESS) { + THROW_RUNTIME_ERROR("Failed to create OpenCL context."); + } + return context; +} + +auto createCommandQueue(cl_context context) -> cl_command_queue { + cl_device_id device_id; + clGetDeviceIDs(nullptr, CL_DEVICE_TYPE_GPU, 1, &device_id, nullptr); + cl_int err; + cl_command_queue commandQueue = + clCreateCommandQueue(context, device_id, 0, &err); + if (err != CL_SUCCESS) { + THROW_RUNTIME_ERROR("Failed to create OpenCL command queue."); + } + return commandQueue; +} + +auto createProgram(const std::string &source, + cl_context context) -> cl_program { + const char *sourceStr = source.c_str(); + cl_int err; + cl_program program = + clCreateProgramWithSource(context, 1, &sourceStr, nullptr, &err); + if (err != CL_SUCCESS) { + THROW_RUNTIME_ERROR("Failed to create OpenCL program."); + } + return program; +} + +void checkErr(cl_int err, const char *operation) { + if (err != CL_SUCCESS) { + std::string errMsg = "OpenCL Error during operation: "; + errMsg += operation; + THROW_RUNTIME_ERROR(errMsg.c_str()); + } +} - // Center the kernel in the extended kernel matrix - for (std::size_t i = 0; i < kernelRows; ++i) { - for (std::size_t j = 0; j < kernelCols; ++j) { - extendedKernel[i][j] = kernel[i][j]; +// OpenCL kernel code for 2D convolution +const std::string convolve2DKernelSrc = R"CLC( +__kernel void convolve2D(__global const float* input, + __global const float* kernel, + __global float* output, + const int inputRows, + const int inputCols, + const int kernelRows, + const int kernelCols) { + int row = get_global_id(0); + int col = get_global_id(1); + + int halfKernelRows = kernelRows / 2; + int halfKernelCols = kernelCols / 2; + + float sum = 0.0; + for (int i = -halfKernelRows; i <= halfKernelRows; ++i) { + for (int j = -halfKernelCols; j <= halfKernelCols; ++j) { + int x = clamp(row + i, 0, inputRows - 1); + int y = clamp(col + j, 0, inputCols - 1); + sum += input[x * inputCols + y] * kernel[(i + halfKernelRows) * kernelCols + (j + halfKernelCols)]; } } + output[row * inputCols + col] = sum; +} +)CLC"; + +// Function to convolve a 2D input with a 2D kernel using OpenCL +auto convolve2DOpenCL(const std::vector> &input, + const std::vector> &kernel, + int numThreads) -> std::vector> { + auto context = initializeOpenCL(); + auto queue = createCommandQueue(context); + + auto inputRows = input.size(); + auto inputCols = input[0].size(); + auto kernelRows = kernel.size(); + auto kernelCols = kernel[0].size(); + + std::vector inputFlattened(inputRows * inputCols); + std::vector kernelFlattened(kernelRows * kernelCols); + std::vector outputFlattened(inputRows * inputCols, 0.0); + + for (size_t i = 0; i < inputRows; ++i) + for (size_t j = 0; j < inputCols; ++j) + inputFlattened[i * inputCols + j] = static_cast(input[i][j]); + + for (size_t i = 0; i < kernelRows; ++i) + for (size_t j = 0; j < kernelCols; ++j) + kernelFlattened[i * kernelCols + j] = + static_cast(kernel[i][j]); + + cl_int err; + cl_mem inputBuffer = clCreateBuffer( + context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, + sizeof(float) * inputFlattened.size(), inputFlattened.data(), &err); + checkErr(err, "Creating input buffer"); + + cl_mem kernelBuffer = clCreateBuffer( + context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, + sizeof(float) * kernelFlattened.size(), kernelFlattened.data(), &err); + checkErr(err, "Creating kernel buffer"); + + cl_mem outputBuffer = + clCreateBuffer(context, CL_MEM_WRITE_ONLY, + sizeof(float) * outputFlattened.size(), nullptr, &err); + checkErr(err, "Creating output buffer"); + + cl_program program = createProgram(convolve2DKernelSrc, context); + err = clBuildProgram(program, 0, nullptr, nullptr, nullptr, nullptr); + checkErr(err, "Building program"); + + cl_kernel kernel = clCreateKernel(program, "convolve2D", &err); + checkErr(err, "Creating kernel"); + + err = clSetKernelArg(kernel, 0, sizeof(cl_mem), &inputBuffer); + err |= clSetKernelArg(kernel, 1, sizeof(cl_mem), &kernelBuffer); + err |= clSetKernelArg(kernel, 2, sizeof(cl_mem), &outputBuffer); + err |= clSetKernelArg(kernel, 3, sizeof(int), &inputRows); + err |= clSetKernelArg(kernel, 4, sizeof(int), &inputCols); + err |= clSetKernelArg(kernel, 5, sizeof(int), &kernelRows); + err |= clSetKernelArg(kernel, 6, sizeof(int), &kernelCols); + checkErr(err, "Setting kernel arguments"); + + size_t globalWorkSize[2] = {static_cast(inputRows), + static_cast(inputCols)}; + err = clEnqueueNDRangeKernel(queue, kernel, 2, nullptr, globalWorkSize, + nullptr, 0, nullptr, nullptr); + checkErr(err, "Enqueueing kernel"); + + err = clEnqueueReadBuffer(queue, outputBuffer, CL_TRUE, 0, + sizeof(float) * outputFlattened.size(), + outputFlattened.data(), 0, nullptr, nullptr); + checkErr(err, "Reading back output buffer"); + + // Convert output back to 2D vector + std::vector> output( + inputRows, std::vector(inputCols, 0.0)); + for (size_t i = 0; i < inputRows; ++i) + for (size_t j = 0; j < inputCols; ++j) + output[i][j] = + static_cast(outputFlattened[i * inputCols + j]); + + // Clean up OpenCL resources + clReleaseMemObject(inputBuffer); + clReleaseMemObject(kernelBuffer); + clReleaseMemObject(outputBuffer); + clReleaseKernel(kernel); + clReleaseProgram(program); + clReleaseCommandQueue(queue); + clReleaseContext(context); + + return output; +} +#endif + +// Function to convolve a 2D input with a 2D kernel using multithreading or +// OpenCL +auto convolve2D(const std::vector> &input, + const std::vector> &kernel, + int numThreads) -> std::vector> { +#if USE_OPENCL + return convolve2DOpenCL(input, kernel, numThreads); +#else + auto inputRows = input.size(); + auto inputCols = input[0].size(); + auto kernelRows = kernel.size(); + auto kernelCols = kernel[0].size(); + + auto extendedInput = + extend2D(input, inputRows + kernelRows - 1, inputCols + kernelCols - 1); + auto extendedKernel = extend2D(kernel, inputRows + kernelRows - 1, + inputCols + kernelCols - 1); - // Prepare output matrix std::vector> output( inputRows, std::vector(inputCols, 0.0)); @@ -134,70 +315,55 @@ std::vector> convolve2D( } return output; +#endif } -std::vector> deconvolve2D( - const std::vector> &signal, - const std::vector> &kernel, int numThreads) { +// Function to deconvolve a 2D input with a 2D kernel using multithreading or +// OpenCL +auto deconvolve2D(const std::vector> &signal, + const std::vector> &kernel, + int numThreads) -> std::vector> { + // Implement OpenCL support similarly if necessary int M = signal.size(); int N = signal[0].size(); int K = kernel.size(); int L = kernel[0].size(); - // 扩展信号和卷积核到相同的大小 - std::vector> extendedSignal( - M + K - 1, std::vector(N + L - 1, 0)); - std::vector> extendedKernel( - M + K - 1, std::vector(N + L - 1, 0)); - - // 复制原始信号和卷积核到扩展矩阵中 - for (int i = 0; i < M; ++i) { - for (int j = 0; j < N; ++j) { - extendedSignal[i][j] = signal[i][j]; - } - } - for (int i = 0; i < K; ++i) { - for (int j = 0; j < L; ++j) { - extendedKernel[i][j] = kernel[i][j]; - } - } + auto extendedSignal = extend2D(signal, M + K - 1, N + L - 1); + auto extendedKernel = extend2D(kernel, M + K - 1, N + L - 1); - // 计算信号和卷积核的二维DFT - auto DFT2DWrapper = [&](const std::vector> &input) { - return DFT2D(input, + auto dfT2DWrapper = [&](const std::vector> &input) { + return dfT2D(input, numThreads); // Assume DFT2D supports multithreading }; - auto X = DFT2DWrapper(extendedSignal); - auto H = DFT2DWrapper(extendedKernel); + auto x = dfT2DWrapper(extendedSignal); + auto h = dfT2DWrapper(extendedKernel); - // 对卷积核的频谱进行修正 - std::vector>> G( + std::vector>> g( M + K - 1, std::vector>(N + L - 1)); - double alpha = 0.1; // 防止分母为0 + double alpha = 0.1; // Prevent division by zero for (int u = 0; u < M + K - 1; ++u) { for (int v = 0; v < N + L - 1; ++v) { - if (std::abs(H[u][v]) > alpha) { - G[u][v] = std::conj(H[u][v]) / (std::norm(H[u][v]) + alpha); + if (std::abs(h[u][v]) > alpha) { + g[u][v] = std::conj(h[u][v]) / (std::norm(h[u][v]) + alpha); } else { - G[u][v] = std::conj(H[u][v]); + g[u][v] = std::conj(h[u][v]); } } } - // 计算反卷积结果 std::vector>> Y( M + K - 1, std::vector>(N + L - 1)); for (int u = 0; u < M + K - 1; ++u) { for (int v = 0; v < N + L - 1; ++v) { - Y[u][v] = G[u][v] * X[u][v]; + Y[u][v] = g[u][v] * x[u][v]; } } - auto y = IDFT2D(Y, numThreads); + auto y = idfT2D(Y, numThreads); - // 提取有效结果 - std::vector> result(M, std::vector(N, 0)); + std::vector> result(M, std::vector(N, 0.0)); for (int i = 0; i < M; ++i) { for (int j = 0; j < N; ++j) { result[i][j] = y[i][j]; @@ -207,9 +373,9 @@ std::vector> deconvolve2D( return result; } -// 二维离散傅里叶变换(2D DFT) -std::vector>> DFT2D( - const std::vector> &signal, int numThreads) { +// 2D Discrete Fourier Transform (2D DFT) +auto dfT2D(const std::vector> &signal, + int numThreads) -> std::vector>> { const auto M = signal.size(); const auto N = signal[0].size(); std::vector>> X( @@ -254,10 +420,9 @@ std::vector>> DFT2D( return X; } -// 二维离散傅里叶逆变换(2D IDFT) -std::vector> IDFT2D( - const std::vector>> &spectrum, - int numThreads) { +// 2D Inverse Discrete Fourier Transform (2D IDFT) +auto idfT2D(const std::vector>> &spectrum, + int numThreads) -> std::vector> { const auto M = spectrum.size(); const auto N = spectrum[0].size(); std::vector> x(M, std::vector(N, 0.0)); @@ -276,7 +441,8 @@ std::vector> IDFT2D( sum += spectrum[u][v] * w; } } - x[m][n] = real(sum) / (M * N); // Normalize by dividing by M*N + x[m][n] = + std::real(sum) / (M * N); // Normalize by dividing by M*N } } }; @@ -301,8 +467,8 @@ std::vector> IDFT2D( return x; } -std::vector> generateGaussianKernel(int size, - double sigma) { +auto generateGaussianKernel(int size, + double sigma) -> std::vector> { std::vector> kernel(size, std::vector(size)); double sum = 0.0; int center = size / 2; @@ -316,7 +482,7 @@ std::vector> generateGaussianKernel(int size, } } - // 归一化,确保权重和为1 + // Normalize to ensure the sum of the weights is 1 for (int i = 0; i < size; ++i) { for (int j = 0; j < size; ++j) { kernel[i][j] /= sum; @@ -326,9 +492,9 @@ std::vector> generateGaussianKernel(int size, return kernel; } -std::vector> applyGaussianFilter( - const std::vector> &image, - const std::vector> &kernel) { +auto applyGaussianFilter(const std::vector> &image, + const std::vector> &kernel) + -> std::vector> { auto imageHeight = image.size(); auto imageWidth = image[0].size(); auto kernelSize = kernel.size(); diff --git a/src/atom/algorithm/convolve.hpp b/src/atom/algorithm/convolve.hpp index 739467d9..4b8d7592 100644 --- a/src/atom/algorithm/convolve.hpp +++ b/src/atom/algorithm/convolve.hpp @@ -29,8 +29,9 @@ namespace atom::algorithm { * @param kernel The convolution kernel. * @return The convolved signal. */ -[[nodiscard("The result of convolve is not used.")]] std::vector -convolve(const std::vector &input, const std::vector &kernel); +[[nodiscard("The result of convolve is not used.")]] auto convolve( + const std::vector &input, + const std::vector &kernel) -> std::vector; /** * @brief Performs 1D deconvolution operation. @@ -41,8 +42,9 @@ convolve(const std::vector &input, const std::vector &kernel); * @param kernel The deconvolution kernel. * @return The deconvolved signal. */ -[[nodiscard("The result of deconvolve is not used.")]] std::vector -deconvolve(const std::vector &input, const std::vector &kernel); +[[nodiscard("The result of deconvolve is not used.")]] auto deconvolve( + const std::vector &input, + const std::vector &kernel) -> std::vector; /** * @brief Performs 2D convolution operation. @@ -54,10 +56,10 @@ deconvolve(const std::vector &input, const std::vector &kernel); * @param numThreads Number of threads for parallel execution (default: 1). * @return The convolved image. */ -[[nodiscard( - "The result of convolve2D is not used.")]] std::vector> -convolve2D(const std::vector> &input, - const std::vector> &kernel, int numThreads = 1); +[[nodiscard("The result of convolve2D is not used.")]] auto convolve2D( + const std::vector> &input, + const std::vector> &kernel, + int numThreads = 1) -> std::vector>; /** * @brief Performs 2D deconvolution operation. @@ -69,11 +71,10 @@ convolve2D(const std::vector> &input, * @param numThreads Number of threads for parallel execution (default: 1). * @return The deconvolved image. */ -[[nodiscard("The result of deconvolve2D is not used.")]] std::vector< - std::vector> -deconvolve2D(const std::vector> &signal, - const std::vector> &kernel, - int numThreads = 1); +[[nodiscard("The result of deconvolve2D is not used.")]] auto deconvolve2D( + const std::vector> &signal, + const std::vector> &kernel, + int numThreads = 1) -> std::vector>; /** * @brief Performs 2D Discrete Fourier Transform (DFT). @@ -84,9 +85,9 @@ deconvolve2D(const std::vector> &signal, * @param numThreads Number of threads for parallel execution (default: 1). * @return The 2D DFT spectrum. */ -[[nodiscard("The result of DFT2D is not used.")]] std::vector< - std::vector>> -DFT2D(const std::vector> &signal, int numThreads = 1); +[[nodiscard("The result of DFT2D is not used.")]] auto dfT2D( + const std::vector> &signal, + int numThreads = 1) -> std::vector>>; /** * @brief Performs 2D Inverse Discrete Fourier Transform (IDFT). @@ -97,10 +98,9 @@ DFT2D(const std::vector> &signal, int numThreads = 1); * @param numThreads Number of threads for parallel execution (default: 1). * @return The 2D IDFT image. */ -[[nodiscard( - "The result of IDFT2D is not used.")]] std::vector> -IDFT2D(const std::vector>> &spectrum, - int numThreads = 1); +[[nodiscard("The result of IDFT2D is not used.")]] auto idfT2D( + const std::vector>> &spectrum, + int numThreads = 1) -> std::vector>; /** * @brief Generates a Gaussian kernel for 2D convolution. @@ -111,9 +111,9 @@ IDFT2D(const std::vector>> &spectrum, * @param sigma The standard deviation of the Gaussian distribution. * @return The generated Gaussian kernel. */ -[[nodiscard("The result of generateGaussianKernel is not used.")]] std::vector< - std::vector> -generateGaussianKernel(int size, double sigma); +[[nodiscard("The result of generateGaussianKernel is not used.")]] auto +generateGaussianKernel(int size, + double sigma) -> std::vector>; /** * @brief Applies a Gaussian filter to an image. @@ -124,10 +124,10 @@ generateGaussianKernel(int size, double sigma); * @param kernel The Gaussian kernel. * @return The filtered image. */ -[[nodiscard("The result of applyGaussianFilter is not used.")]] std::vector< - std::vector> +[[nodiscard("The result of applyGaussianFilter is not used.")]] auto applyGaussianFilter(const std::vector> &image, - const std::vector> &kernel); + const std::vector> &kernel) + -> std::vector>; } // namespace atom::algorithm #endif diff --git a/src/atom/algorithm/fbase.cpp b/src/atom/algorithm/fbase.cpp deleted file mode 100644 index e250b260..00000000 --- a/src/atom/algorithm/fbase.cpp +++ /dev/null @@ -1,154 +0,0 @@ -/* - * fbase.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-11-10 - -Description: Faster Base64 Encode & Decode from pocketpy - -**************************************************/ - -#include -#include -#include - -#include "fbase.hpp" - -#include "atom/error/exception.hpp" - -namespace atom::algorithm { - -constexpr char BASE64_PAD = '='; -constexpr char BASE64DE_FIRST = '+'; -constexpr char BASE64DE_LAST = 'z'; - -constexpr std::array base64en = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; - -constexpr std::array base64de = [] { - std::array arr; - arr.fill(255); - for (unsigned char i = 0; i < 64; ++i) { - arr[static_cast(base64en[i])] = i; - } - arr['+'] = 62; - arr['/'] = 63; - for (unsigned char i = '0'; i <= '9'; ++i) { - arr[i] = i - '0' + 52; - } - for (unsigned char i = 'A'; i <= 'Z'; ++i) { - arr[i] = i - 'A'; - } - for (unsigned char i = 'a'; i <= 'z'; ++i) { - arr[i] = i - 'a' + 26; - } - return arr; -}(); - -std::string fbase64Encode(std::span input) { - std::string output; - output.reserve((input.size() + 2) / 3 * 4); - - unsigned int s = 0; - unsigned char l = 0; - - for (unsigned char c : input) { - switch (s) { - case 0: - output.push_back(base64en[(c >> 2) & 0x3F]); - s = 1; - break; - case 1: - output.push_back(base64en[((l & 0x3) << 4) | ((c >> 4) & 0xF)]); - s = 2; - break; - case 2: - output.push_back(base64en[((l & 0xF) << 2) | ((c >> 6) & 0x3)]); - output.push_back(base64en[c & 0x3F]); - s = 0; - break; - } - l = c; - } - - switch (s) { - case 1: - output.push_back(base64en[(l & 0x3) << 4]); - output.push_back(BASE64_PAD); - output.push_back(BASE64_PAD); - break; - case 2: - output.push_back(base64en[(l & 0xF) << 2]); - output.push_back(BASE64_PAD); - break; - } - - return output; -} - -std::vector fbase64Decode(std::span input) { - if (input.size() % 4 != 0) { - THROW_INVALID_ARGUMENT("Invalid base64 input length"); - } - - std::vector output; - output.reserve(input.size() / 4 * 3); - - unsigned int s = 0; - unsigned char l = 0; - - for (size_t i = 0; i < input.size(); ++i) { - if (input[i] == BASE64_PAD) { - break; - } - - if (input[i] < BASE64DE_FIRST || input[i] > BASE64DE_LAST) { - THROW_INVALID_ARGUMENT("Invalid base64 character"); - } - - unsigned char c = base64de[static_cast(input[i])]; - if (c == 255) { - THROW_INVALID_ARGUMENT("Invalid base64 character"); - } - - switch (s) { - case 0: - output.push_back((c << 2) & 0xFF); - s = 1; - break; - case 1: - output.back() |= (c >> 4) & 0x3; - output.push_back((c & 0xF) << 4); - s = 2; - break; - case 2: - output.back() |= (c >> 2) & 0xF; - output.push_back((c & 0x3) << 6); - s = 3; - break; - case 3: - output.back() |= c; - s = 0; - break; - } - } - - if (s == 1 || s == 2) { - output.pop_back(); - } - if (s == 2) { - output.pop_back(); - } - - return output; -} - -} // namespace atom::algorithm diff --git a/src/atom/algorithm/fbase.hpp b/src/atom/algorithm/fbase.hpp deleted file mode 100644 index 53cda3f0..00000000 --- a/src/atom/algorithm/fbase.hpp +++ /dev/null @@ -1,27 +0,0 @@ -/* - * fbase.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-11-10 - -Description: Faster Base64 Encode & Decode from pocketpy - -**************************************************/ - -#ifndef ATOM_ALGORITHM_FBASE_HPP -#define ATOM_ALGORITHM_FBASE_HPP - -#include -#include -#include - -namespace atom::algorithm { -std::string fbase64Encode(std::span input); -std::vector fbase64Decode(std::span input); -} // namespace atom::algorithm - -#endif diff --git a/src/atom/algorithm/fnmatch.cpp b/src/atom/algorithm/fnmatch.cpp index be6b5efb..9e537a76 100644 --- a/src/atom/algorithm/fnmatch.cpp +++ b/src/atom/algorithm/fnmatch.cpp @@ -16,6 +16,8 @@ Description: Python Like fnmatch for C++ #include #include +#include +#include #ifdef _WIN32 #include @@ -25,15 +27,16 @@ Description: Python Like fnmatch for C++ namespace atom::algorithm { +#ifdef _WIN32 constexpr int FNM_NOESCAPE = 0x01; constexpr int FNM_PATHNAME = 0x02; constexpr int FNM_PERIOD = 0x04; constexpr int FNM_CASEFOLD = 0x08; +#endif -bool fnmatch(std::string_view pattern, std::string_view string, int flags) { +auto fnmatch(std::string_view pattern, std::string_view string, + int flags) -> bool { #ifdef _WIN32 - // Windows doesn't have a built-in fnmatch function, so we need to implement - // it ourselves auto p = pattern.begin(); auto s = string.begin(); @@ -115,34 +118,33 @@ bool fnmatch(std::string_view pattern, std::string_view string, int flags) { } return p == pattern.end() && s == string.end(); #else - // On POSIX systems, we can use the built-in fnmatch function return ::fnmatch(pattern.data(), string.data(), flags) == 0; #endif } -bool filter(const std::vector& names, std::string_view pattern, - int flags) { - return std::any_of( - names.begin(), names.end(), - [&](const std::string& name) { return fnmatch(pattern, name, flags); }); +auto filter(const std::vector& names, std::string_view pattern, + int flags) -> bool { + return std::ranges::any_of(names, [&](const std::string& name) { + return fnmatch(pattern, name, flags); + }); } -std::vector filter(const std::vector& names, - const std::vector& patterns, - int flags) { +auto filter(const std::vector& names, + const std::vector& patterns, + int flags) -> std::vector { std::vector result; for (const auto& name : names) { - if (std::any_of(patterns.begin(), patterns.end(), - [&](std::string_view pattern) { - return fnmatch(pattern, name, flags); - })) { + if (std::ranges::any_of(patterns, [&](std::string_view pattern) { + return fnmatch(pattern, name, flags); + })) { result.push_back(name); } } return result; } -bool translate(std::string_view pattern, std::string& result, int flags) { +auto translate(std::string_view pattern, std::string& result, + int flags) -> bool { result.clear(); for (auto it = pattern.begin(); it != pattern.end(); ++it) { switch (*it) { @@ -161,25 +163,21 @@ bool translate(std::string_view pattern, std::string& result, int flags) { result += '^'; ++it; } - bool first = true; - char last_char = 0; - while (it != pattern.end() && *it != ']') { - if (!first && *it == '-' && last_char != 0 && - it + 1 != pattern.end() && *(it + 1) != ']') { - result += last_char; - result += '-'; - ++it; + if (it == pattern.end()) { + return false; + } + char lastChar = *it; + result += *it; + while (++it != pattern.end() && *it != ']') { + if (*it == '-' && it + 1 != pattern.end() && + *(it + 1) != ']') { result += *it; + result += *(++it); + lastChar = *it; } else { - if (flags & FNM_NOESCAPE && *it == '\\' && - ++it == pattern.end()) { - return false; - } result += *it; - last_char = *it; + lastChar = *it; } - first = false; - ++it; } result += ']'; break; @@ -190,7 +188,7 @@ bool translate(std::string_view pattern, std::string& result, int flags) { } [[fallthrough]]; default: - if (flags & FNM_CASEFOLD && std::isalpha(*it)) { + if ((flags & FNM_CASEFOLD) && std::isalpha(*it)) { result += '['; result += std::tolower(*it); result += std::toupper(*it); diff --git a/src/atom/algorithm/fnmatch.hpp b/src/atom/algorithm/fnmatch.hpp index 6f775351..56cab250 100644 --- a/src/atom/algorithm/fnmatch.hpp +++ b/src/atom/algorithm/fnmatch.hpp @@ -32,7 +32,7 @@ namespace atom::algorithm { * @param flags Optional flags to modify the matching behavior (default is 0). * @return True if the `string` matches the `pattern`, false otherwise. */ -bool fnmatch(std::string_view pattern, std::string_view string, int flags = 0); +auto fnmatch(std::string_view pattern, std::string_view string, int flags = 0) -> bool; /** * @brief Filters a vector of strings based on a specified pattern. @@ -47,8 +47,8 @@ bool fnmatch(std::string_view pattern, std::string_view string, int flags = 0); * @return True if any element of `names` matches the `pattern`, false * otherwise. */ -bool filter(const std::vector& names, std::string_view pattern, - int flags = 0); +auto filter(const std::vector& names, std::string_view pattern, + int flags = 0) -> bool; /** * @brief Filters a vector of strings based on multiple patterns. @@ -63,9 +63,9 @@ bool filter(const std::vector& names, std::string_view pattern, * @return A vector containing strings from `names` that match any pattern in * `patterns`. */ -std::vector filter(const std::vector& names, +auto filter(const std::vector& names, const std::vector& patterns, - int flags = 0); + int flags = 0) -> std::vector; /** * @brief Translates a pattern into a different representation. @@ -81,7 +81,7 @@ std::vector filter(const std::vector& names, * 0). * @return True if the translation was successful, false otherwise. */ -bool translate(std::string_view pattern, std::string& result, int flags = 0); +auto translate(std::string_view pattern, std::string& result, int flags = 0) -> bool; } // namespace atom::algorithm diff --git a/src/atom/algorithm/fraction.cpp b/src/atom/algorithm/fraction.cpp index ba9594bf..acb6d16b 100644 --- a/src/atom/algorithm/fraction.cpp +++ b/src/atom/algorithm/fraction.cpp @@ -1,10 +1,11 @@ #include "fraction.hpp" #include -#include #include #include +#include "atom/error/exception.hpp" + namespace atom::algorithm { int Fraction::gcd(int a, int b) { return std::gcd(a, b); } @@ -21,35 +22,35 @@ void Fraction::reduce() { Fraction::Fraction(int n, int d) : numerator(n), denominator(d) { if (denominator == 0) { - throw std::invalid_argument("Denominator cannot be zero."); + THROW_INVALID_ARGUMENT("Denominator cannot be zero."); } reduce(); } -Fraction &Fraction::operator+=(const Fraction &other) { +auto Fraction::operator+=(const Fraction &other) -> Fraction & { numerator = numerator * other.denominator + other.numerator * denominator; denominator *= other.denominator; reduce(); return *this; } -Fraction &Fraction::operator-=(const Fraction &other) { +auto Fraction::operator-=(const Fraction &other) -> Fraction & { numerator = numerator * other.denominator - other.numerator * denominator; denominator *= other.denominator; reduce(); return *this; } -Fraction &Fraction::operator*=(const Fraction &other) { +auto Fraction::operator*=(const Fraction &other) -> Fraction & { numerator *= other.numerator; denominator *= other.denominator; reduce(); return *this; } -Fraction &Fraction::operator/=(const Fraction &other) { +auto Fraction::operator/=(const Fraction &other) -> Fraction & { if (other.numerator == 0) { - throw std::invalid_argument("Division by zero."); + THROW_INVALID_ARGUMENT("Division by zero."); } numerator *= other.denominator; denominator *= other.numerator; @@ -57,57 +58,57 @@ Fraction &Fraction::operator/=(const Fraction &other) { return *this; } -Fraction Fraction::operator+(const Fraction &other) const { +auto Fraction::operator+(const Fraction &other) const -> Fraction { Fraction result = *this; result += other; return result; } -Fraction Fraction::operator-(const Fraction &other) const { +auto Fraction::operator-(const Fraction &other) const -> Fraction { Fraction result = *this; result -= other; return result; } -Fraction Fraction::operator*(const Fraction &other) const { +auto Fraction::operator*(const Fraction &other) const -> Fraction { Fraction result = *this; result *= other; return result; } -Fraction Fraction::operator/(const Fraction &other) const { +auto Fraction::operator/(const Fraction &other) const -> Fraction { Fraction result = *this; result /= other; return result; } -bool Fraction::operator==(const Fraction &other) const { +auto Fraction::operator==(const Fraction &other) const -> bool { return (numerator == other.numerator) && (denominator == other.denominator); } -Fraction::operator double() const { return to_double(); } +Fraction::operator double() const { return toDouble(); } -Fraction::operator float() const { return static_cast(to_double()); } +Fraction::operator float() const { return static_cast(toDouble()); } Fraction::operator int() const { return numerator / denominator; } -std::string Fraction::to_string() const { +auto Fraction::toString() const -> std::string { if (denominator == 1) { return std::to_string(numerator); } return std::to_string(numerator) + "/" + std::to_string(denominator); } -double Fraction::to_double() const { +auto Fraction::toDouble() const -> double { return static_cast(numerator) / denominator; } -std::ostream &operator<<(std::ostream &os, const Fraction &f) { - os << f.to_string(); +auto operator<<(std::ostream &os, const Fraction &f) -> std::ostream & { + os << f.toString(); return os; } -std::istream &operator>>(std::istream &is, Fraction &f) { +auto operator>>(std::istream &is, Fraction &f) -> std::istream & { std::string input; is >> input; std::istringstream iss(input); diff --git a/src/atom/algorithm/fraction.hpp b/src/atom/algorithm/fraction.hpp index 2a8b581d..2e0a2262 100644 --- a/src/atom/algorithm/fraction.hpp +++ b/src/atom/algorithm/fraction.hpp @@ -42,7 +42,6 @@ class Fraction { int numerator; /**< The numerator of the fraction. */ int denominator; /**< The denominator of the fraction. */ -public: /** * @brief Constructs a new Fraction object with the given numerator and * denominator. @@ -56,56 +55,56 @@ class Fraction { * @param other The fraction to add. * @return Reference to the modified fraction. */ - Fraction& operator+=(const Fraction& other); + auto operator+=(const Fraction& other) -> Fraction&; /** * @brief Subtracts another fraction from this fraction. * @param other The fraction to subtract. * @return Reference to the modified fraction. */ - Fraction& operator-=(const Fraction& other); + auto operator-=(const Fraction& other) -> Fraction&; /** * @brief Multiplies this fraction by another fraction. * @param other The fraction to multiply by. * @return Reference to the modified fraction. */ - Fraction& operator*=(const Fraction& other); + auto operator*=(const Fraction& other) -> Fraction&; /** * @brief Divides this fraction by another fraction. * @param other The fraction to divide by. * @return Reference to the modified fraction. */ - Fraction& operator/=(const Fraction& other); + auto operator/=(const Fraction& other) -> Fraction&; /** * @brief Adds another fraction to this fraction. * @param other The fraction to add. * @return The result of addition. */ - Fraction operator+(const Fraction& other) const; + auto operator+(const Fraction& other) const -> Fraction; /** * @brief Subtracts another fraction from this fraction. * @param other The fraction to subtract. * @return The result of subtraction. */ - Fraction operator-(const Fraction& other) const; + auto operator-(const Fraction& other) const -> Fraction; /** * @brief Multiplies this fraction by another fraction. * @param other The fraction to multiply by. * @return The result of multiplication. */ - Fraction operator*(const Fraction& other) const; + auto operator*(const Fraction& other) const -> Fraction; /** * @brief Divides this fraction by another fraction. * @param other The fraction to divide by. * @return The result of division. */ - Fraction operator/(const Fraction& other) const; + auto operator/(const Fraction& other) const -> Fraction; #if __cplusplus >= 202002L /** @@ -114,13 +113,14 @@ class Fraction { * @return An integer indicating the comparison result. */ auto operator<=>(const Fraction& other) const { - double diff = this->to_double() - other.to_double(); - if (diff > 0) + double diff = this->toDouble() - other.toDouble(); + if (diff > 0) { return std::strong_ordering::greater; - else if (diff < 0) + } + if (diff < 0) { return std::strong_ordering::less; - else - return std::strong_ordering::equal; + } + return std::strong_ordering::equal; } #endif @@ -129,7 +129,7 @@ class Fraction { * @param other The fraction to compare with. * @return True if fractions are equal, false otherwise. */ - bool operator==(const Fraction& other) const; + auto operator==(const Fraction& other) const -> bool; /** * @brief Converts the fraction to a double value. @@ -153,13 +153,13 @@ class Fraction { * @brief Converts the fraction to a string representation. * @return The string representation of the fraction. */ - std::string to_string() const; + [[nodiscard]] auto toString() const -> std::string; /** * @brief Converts the fraction to a double value. * @return The fraction as a double. */ - double to_double() const; + [[nodiscard]] auto toDouble() const -> double; /** * @brief Outputs the fraction to the output stream. @@ -167,7 +167,8 @@ class Fraction { * @param f The fraction to output. * @return Reference to the output stream. */ - friend std::ostream& operator<<(std::ostream& os, const Fraction& f); + friend auto operator<<(std::ostream& os, + const Fraction& f) -> std::ostream&; /** * @brief Inputs the fraction from the input stream. @@ -175,7 +176,7 @@ class Fraction { * @param f The fraction to input. * @return Reference to the input stream. */ - friend std::istream& operator>>(std::istream& is, Fraction& f); + friend auto operator>>(std::istream& is, Fraction& f) -> std::istream&; }; } // namespace atom::algorithm diff --git a/src/atom/algorithm/hash.hpp b/src/atom/algorithm/hash.hpp index 89881441..ce7822cd 100644 --- a/src/atom/algorithm/hash.hpp +++ b/src/atom/algorithm/hash.hpp @@ -16,20 +16,17 @@ Description: A collection of hash algorithms #define ATOM_ALGORITHM_HASH_HPP #include -#include #include -#include -#include #include -#include #include namespace atom::algorithm { + /** - * @brief Concept to check if a type is hashable. + * @brief Concept for types that can be hashed. * - * A type is considered hashable if it can be used as a key in hash-based - * containers. + * A type is Hashable if it supports hashing via std::hash and the result is + * convertible to std::size_t. */ template concept Hashable = requires(T a) { @@ -37,30 +34,26 @@ concept Hashable = requires(T a) { }; /** - * @brief Computes the hash value of a single value. - * - * This function computes the hash value of a single value using std::hash. + * @brief Computes the hash value for a single Hashable value. * - * @param value The value for which to compute the hash. - * @return The hash value of the input value. + * @tparam T Type of the value to hash, must satisfy Hashable concept. + * @param value The value to hash. + * @return std::size_t Hash value of the input value. */ template -std::size_t computeHash(const T& value) { +auto computeHash(const T& value) -> std::size_t { return std::hash{}(value); } /** - * @brief Computes the hash value of a vector of hashable values. - * - * This function computes the hash value of a vector of hashable values by - * combining the hash values of individual elements using a bitwise XOR - * operation. + * @brief Computes the hash value for a vector of Hashable values. * - * @param values The vector of hashable values. - * @return The hash value of the vector. + * @tparam T Type of the elements in the vector, must satisfy Hashable concept. + * @param values The vector of values to hash. + * @return std::size_t Hash value of the vector of values. */ template -std::size_t computeHash(const std::vector& values) { +auto computeHash(const std::vector& values) -> std::size_t { std::size_t result = 0; for (const auto& value : values) { result ^= @@ -70,17 +63,15 @@ std::size_t computeHash(const std::vector& values) { } /** - * @brief Computes the hash value of a tuple of hashable values. + * @brief Computes the hash value for a tuple of Hashable values. * - * This function computes the hash value of a tuple of hashable values by - * applying the computeHash function to each element of the tuple and combining - * the hash values using a bitwise XOR operation. - * - * @param tuple The tuple of hashable values. - * @return The hash value of the tuple. + * @tparam Ts Types of the elements in the tuple, all must satisfy Hashable + * concept. + * @param tuple The tuple of values to hash. + * @return std::size_t Hash value of the tuple of values. */ template -std::size_t computeHash(const std::tuple& tuple) { +auto computeHash(const std::tuple& tuple) -> std::size_t { std::size_t result = 0; std::apply( [&result](const Ts&... values) { @@ -93,17 +84,15 @@ std::size_t computeHash(const std::tuple& tuple) { } /** - * @brief Computes the hash value of an array of hashable values. - * - * This function computes the hash value of an array of hashable values by - * combining the hash values of individual elements using a bitwise XOR - * operation. + * @brief Computes the hash value for an array of Hashable values. * - * @param array The array of hashable values. - * @return The hash value of the array. + * @tparam T Type of the elements in the array, must satisfy Hashable concept. + * @tparam N Size of the array. + * @param array The array of values to hash. + * @return std::size_t Hash value of the array of values. */ template -std::size_t computeHash(const std::array& array) { +auto computeHash(const std::array& array) -> std::size_t { std::size_t result = 0; for (const auto& value : array) { result ^= @@ -112,179 +101,39 @@ std::size_t computeHash(const std::array& array) { return result; } -/** - * @brief Computes the FNV-1a hash value of a range. - * - * This function computes the FNV-1a hash value of a range defined by iterators. - * - * @param begin Iterator to the beginning of the range. - * @param end Iterator to the end of the range. - * @return The FNV-1a hash value of the range. - */ -template -constexpr std::uint32_t fnv1a_hash(Itr begin, Itr end) noexcept { - std::uint32_t h = 0x811c9dc5; - - while (begin != end) { - h = (h ^ static_cast(*begin)) * 0x01000193; - ++begin; - } - return h; -} - -/** - * @brief Computes the FNV-1a hash value of a null-terminated string literal. - * - * This function computes the FNV-1a hash value of a null-terminated string - * literal. - * - * @param str The null-terminated string literal. - * @return The FNV-1a hash value of the string. - */ -template -constexpr std::uint32_t fnv1a_hash(const char (&str)[N]) noexcept { - return fnv1a_hash(std::begin(str), std::end(str) - 1); -} - -/** - * @brief Computes the FNV-1a hash value of a string view. - * - * This function computes the FNV-1a hash value of a string view. - * - * @param sv The string view. - * @return The FNV-1a hash value of the string view. - */ -constexpr std::uint32_t fnv1a_hash(std::string_view sv) noexcept { - return fnv1a_hash(sv.begin(), sv.end()); -} - -/** - * @brief Computes the FNV-1a hash value of a string. - * - * This function computes the FNV-1a hash value of a string. - * - * @param s The string. - * @return The FNV-1a hash value of the string. - */ -inline std::uint32_t fnv1a_hash(const std::string& s) noexcept { - return fnv1a_hash(std::string_view{s}); -} - -/** - * @brief Computes the Jenkins One-at-a-Time hash value of a range. - * - * This function computes the Jenkins One-at-a-Time hash value of a range - * defined by iterators. - * - * @param begin Iterator to the beginning of the range. - * @param end Iterator to the end of the range. - * @return The Jenkins One-at-a-Time hash value of the range. - */ -template -constexpr std::uint32_t jenkins_one_at_a_time_hash(Itr begin, - Itr end) noexcept { - std::uint32_t hash = 0; - - while (begin != end) { - hash += static_cast(*begin); - hash += hash << 10; - hash ^= hash >> 6; - ++begin; - } - - hash += hash << 3; - hash ^= hash >> 11; - hash += hash << 15; - return hash; -} - -/** - * @brief Computes the Jenkins One-at-a-Time hash value of a null-terminated - * string literal. - * - * This function computes the Jenkins One-at-a-Time hash value of a - * null-terminated string literal. - * - * @param str The null-terminated string literal. - * @return The Jenkins One-at-a-Time hash value of the string. - */ -template -constexpr std::uint32_t jenkins_one_at_a_time_hash( - const char (&str)[N]) noexcept { - return jenkins_one_at_a_time_hash(std::begin(str), std::end(str) - 1); -} - -/** - * @brief Computes the Jenkins One-at-a-Time hash value of a string view. - * - * This function computes the Jenkins One-at-a-Time hash value of a string view. - * - * @param sv The string view. - * @return The Jenkins One-at-a-Time hash value of the string view. - */ -constexpr std::uint32_t jenkins_one_at_a_time_hash( - std::string_view sv) noexcept { - return jenkins_one_at_a_time_hash(sv.begin(), sv.end()); -} - -/** - * @brief Computes the Jenkins One-at-a-Time hash value of a string. - * - * This function computes the Jenkins One-at-a-Time hash value of a string. - * - * @param s The string. - * @return The Jenkins One-at-a-Time hash value of the string. - */ -inline std::uint32_t jenkins_one_at_a_time_hash(const std::string& s) noexcept { - return jenkins_one_at_a_time_hash(std::string_view{s}); -} - -inline uint32_t quickHash(std::string_view str) { - uint32_t h = 0; - for (char c : str) { - h = 31 * h + static_cast(c); - } - return h; -} - -inline uint32_t quickHash(const void* data, size_t size) { - if (data == nullptr || size == 0) - return 0; - - const auto* str = static_cast(data); - uint32_t h = 0; - for (size_t i = 0; i < size; ++i) { - h = 31 * h + str[i]; - } - return h; -} - } // namespace atom::algorithm /** - * @brief Computes the hash value of a string during compile time. - * - * This function computes the hash value of a string using the FNV-1a algorithm. - * - * @param str The string. - * @return The hash value of the string. - */ -constexpr unsigned int hash(const char* str, unsigned int basis = 2166136261u) { - return *str ? hash(str + 1, - (basis ^ static_cast(*str)) * 16777619u) - : basis; + * @brief Computes a hash value for a null-terminated string using FNV-1a + * algorithm. + * + * @param str Pointer to the null-terminated string to hash. + * @param basis Initial basis value for hashing. + * @return constexpr unsigned int Hash value of the string. + */ +constexpr auto hash(const char* str, + unsigned int basis = 2166136261U) -> unsigned int { + while (*str != 0) { + basis = (basis ^ static_cast(*str)) * 16777619u; + ++str; + } + return basis; } /** - * @brief Computes the hash value of a string during compile time. + * @brief User-defined literal for computing hash values of string literals. * - * This function computes the hash value of a string using the FNV-1a algorithm. + * Example usage: "example"_hash * - * @param str The string. - * @return The hash value of the string. + * @param str Pointer to the string literal to hash. + * @param size Size of the string literal (unused). + * @return constexpr unsigned int Hash value of the string literal. */ -constexpr unsigned int operator""_hash(const char* str, std::size_t) { +constexpr auto operator""_hash(const char* str, + std::size_t size) -> unsigned int { + // The size parameter is not used in this implementation + static_cast(size); return hash(str); } -#endif +#endif // ATOM_ALGORITHM_HASH_HPP diff --git a/src/atom/algorithm/huffman.cpp b/src/atom/algorithm/huffman.cpp index 65df7af1..ffbc1375 100644 --- a/src/atom/algorithm/huffman.cpp +++ b/src/atom/algorithm/huffman.cpp @@ -16,12 +16,16 @@ Description: Simple implementation of Huffman encoding #include +#ifdef USE_OPENMP +#include +#endif + namespace atom::algorithm { HuffmanNode::HuffmanNode(char data, int frequency) : data(data), frequency(frequency), left(nullptr), right(nullptr) {} -std::shared_ptr createHuffmanTree( - const std::unordered_map& frequencies) { +auto createHuffmanTree(const std::unordered_map& frequencies) + -> std::shared_ptr { auto compare = [](const std::shared_ptr& a, const std::shared_ptr& b) { return a->frequency > b->frequency; @@ -50,12 +54,12 @@ std::shared_ptr createHuffmanTree( minHeap.push(std::move(newNode)); } - return minHeap.empty() ? nullptr : std::move(minHeap.top()); + return minHeap.empty() ? nullptr : minHeap.top(); } void generateHuffmanCodes(const HuffmanNode* root, const std::string& code, std::unordered_map& huffmanCodes) { - if (!root) + if (root == nullptr) { return; if (!root->left && !root->right) { huffmanCodes[root->data] = code; @@ -63,26 +67,47 @@ void generateHuffmanCodes(const HuffmanNode* root, const std::string& code, generateHuffmanCodes(root->left.get(), code + "0", huffmanCodes); generateHuffmanCodes(root->right.get(), code + "1", huffmanCodes); } + if (!root->left && !root->right) { + huffmanCodes[root->data] = code; + } else { + generateHuffmanCodes(root->left.get(), code + "0", huffmanCodes); + generateHuffmanCodes(root->right.get(), code + "1", huffmanCodes); + } } -std::string compressText( - const std::string_view text, - const std::unordered_map& huffmanCodes) { +auto compressText(std::string_view TEXT, + const std::unordered_map& huffmanCodes) + -> std::string { std::string compressedText; - for (char c : text) { + +#ifdef USE_OPENMP +#pragma omp parallel + { + std::string local_compressed; +#pragma omp for nowait schedule(static) + for (std::size_t i = 0; i < TEXT.size(); ++i) { + local_compressed += huffmanCodes.at(TEXT[i]); + } +#pragma omp critical + compressedText += local_compressed; + } +#else + for (char c : TEXT) { compressedText += huffmanCodes.at(c); } +#endif + return compressedText; } -std::string decompressText(const std::string_view compressedText, - const HuffmanNode* root) { +auto decompressText(std::string_view COMPRESSED_TEXT, + const HuffmanNode* root) -> std::string { std::string decompressedText; const HuffmanNode* current = root; - for (char bit : compressedText) { + for (char bit : COMPRESSED_TEXT) { current = (bit == '0') ? current->left.get() : current->right.get(); - if (current && !current->left && !current->right) { + if ((current != nullptr) && !current->left && !current->right) { decompressedText += current->data; current = root; } diff --git a/src/atom/algorithm/huffman.hpp b/src/atom/algorithm/huffman.hpp index 481151d1..73973d39 100644 --- a/src/atom/algorithm/huffman.hpp +++ b/src/atom/algorithm/huffman.hpp @@ -54,8 +54,9 @@ struct HuffmanNode { * @param frequencies A map of characters and their corresponding frequencies. * @return A shared pointer to the root of the Huffman tree. */ -[[nodiscard]] std::shared_ptr createHuffmanTree( - const std::unordered_map& frequencies); +[[nodiscard]] auto createHuffmanTree( + const std::unordered_map& frequencies) + -> std::shared_ptr; /** * @brief Generates Huffman codes for each character from the Huffman tree. @@ -84,9 +85,9 @@ void generateHuffmanCodes(const HuffmanNode* root, const std::string& code, * codes. * @return A string representing the compressed text. */ -[[nodiscard]] std::string compressText( - const std::string_view text, - const std::unordered_map& huffmanCodes); +[[nodiscard]] auto compressText( + std::string_view TEXT, + const std::unordered_map& huffmanCodes) -> std::string; /** * @brief Decompresses Huffman encoded text back to its original form. @@ -100,8 +101,8 @@ void generateHuffmanCodes(const HuffmanNode* root, const std::string& code, * @param root Pointer to the root of the Huffman tree. * @return The original decompressed text. */ -[[nodiscard]] std::string decompressText(const std::string_view compressedText, - const HuffmanNode* root); +[[nodiscard]] auto decompressText(std::string_view COMPRESSED_TEXT, + const HuffmanNode* root) -> std::string; } // namespace atom::algorithm diff --git a/src/atom/algorithm/math.cpp b/src/atom/algorithm/math.cpp index 4b5c594f..145c5e1b 100644 --- a/src/atom/algorithm/math.cpp +++ b/src/atom/algorithm/math.cpp @@ -19,12 +19,15 @@ Description: Extra Math Library #include // For std::gcd #include // For std::runtime_error +#include "atom/error/exception.hpp" + namespace atom::algorithm { #if defined(__GNUC__) && defined(__SIZEOF_INT128__) -uint64_t mulDiv64(uint64_t operand, uint64_t multiplier, uint64_t divider) { +auto mulDiv64(uint64_t operand, uint64_t multiplier, + uint64_t divider) -> uint64_t { if (divider == 0) { - throw std::runtime_error("Division by zero"); + THROW_INVALID_ARGUMENT("Division by zero"); } __uint128_t a = operand; @@ -38,7 +41,7 @@ uint64_t mulDiv64(uint64_t operand, uint64_t multiplier, uint64_t divider) { uint64_t mulDiv64(uint64_t operand, uint64_t multiplier, uint64_t divider) { if (divider == 0) { - throw std::runtime_error("Division by zero"); + THROW_INVALID_ARGUMENT("Division by zero"); } uint64_t highProd; @@ -59,10 +62,10 @@ uint64_t mulDiv64(uint64_t operand, uint64_t multiplier, uint64_t divider) { #error "Platform not supported for mulDiv64 function!" #endif -uint64_t safeAdd(uint64_t a, uint64_t b) { +auto safeAdd(uint64_t a, uint64_t b) -> uint64_t { uint64_t result; if (__builtin_add_overflow(a, b, &result)) { - throw std::overflow_error("Overflow in addition"); + THROW_OVERFLOW("Overflow in addition"); } return result; } @@ -70,44 +73,46 @@ uint64_t safeAdd(uint64_t a, uint64_t b) { uint64_t safeMul(uint64_t a, uint64_t b) { uint64_t result; if (__builtin_mul_overflow(a, b, &result)) { - throw std::overflow_error("Overflow in multiplication"); + THROW_OVERFLOW("Overflow in multiplication"); } return result; } -uint64_t rotl64(uint64_t n, unsigned int c) { return std::rotl(n, c); } +auto rotl64(uint64_t n, unsigned int c) -> uint64_t { return std::rotl(n, c); } -uint64_t rotr64(uint64_t n, unsigned int c) { return std::rotr(n, c); } +auto rotr64(uint64_t n, unsigned int c) -> uint64_t { return std::rotr(n, c); } -int clz64(uint64_t x) { - if (x == 0) +auto clz64(uint64_t x) -> int { + if (x == 0) { return 64; + } return __builtin_clzll(x); } -uint64_t normalize(uint64_t x) { - if (x == 0) +auto normalize(uint64_t x) -> uint64_t { + if (x == 0) { return 0; + } int n = clz64(x); return x << n; } -uint64_t safeSub(uint64_t a, uint64_t b) { +auto safeSub(uint64_t a, uint64_t b) -> uint64_t { uint64_t result; if (__builtin_sub_overflow(a, b, &result)) { - throw std::underflow_error("Underflow in subtraction"); + THROW_UNDERFLOW("Underflow in subtraction"); } return result; } -uint64_t safeDiv(uint64_t a, uint64_t b) { +auto safeDiv(uint64_t a, uint64_t b) -> uint64_t { if (b == 0) { - throw std::runtime_error("Division by zero"); + THROW_INVALID_ARGUMENT("Division by zero"); } return a / b; } -uint64_t bitReverse64(uint64_t n) { +auto bitReverse64(uint64_t n) -> uint64_t { n = ((n & 0xAAAAAAAAAAAAAAAA) >> 1) | ((n & 0x5555555555555555) << 1); n = ((n & 0xCCCCCCCCCCCCCCCC) >> 2) | ((n & 0x3333333333333333) << 2); n = ((n & 0xF0F0F0F0F0F0F0F0) >> 4) | ((n & 0x0F0F0F0F0F0F0F0F) << 4); @@ -117,7 +122,7 @@ uint64_t bitReverse64(uint64_t n) { return n; } -uint64_t approximateSqrt(uint64_t n) { +auto approximateSqrt(uint64_t n) -> uint64_t { if (n == 0 || n == 1) { return n; } @@ -131,13 +136,13 @@ uint64_t approximateSqrt(uint64_t n) { return static_cast(x); } -uint64_t gcd64(uint64_t a, uint64_t b) { return std::gcd(a, b); } +auto gcd64(uint64_t a, uint64_t b) -> uint64_t { return std::gcd(a, b); } -uint64_t lcm64(uint64_t a, uint64_t b) { return a / gcd64(a, b) * b; } +auto lcm64(uint64_t a, uint64_t b) -> uint64_t { return a / gcd64(a, b) * b; } -bool isPowerOfTwo(uint64_t n) { return n != 0 && (n & (n - 1)) == 0; } +auto isPowerOfTwo(uint64_t n) -> bool { return n != 0 && (n & (n - 1)) == 0; } -uint64_t nextPowerOfTwo(uint64_t n) { +auto nextPowerOfTwo(uint64_t n) -> uint64_t { if (n == 0) { return 1; } diff --git a/src/atom/algorithm/math.hpp b/src/atom/algorithm/math.hpp index f3823f07..92a17f2f 100644 --- a/src/atom/algorithm/math.hpp +++ b/src/atom/algorithm/math.hpp @@ -28,7 +28,8 @@ namespace atom::algorithm { * @param divider The divisor for the division operation. * @return The result of (operant * multiplier) / divider. */ -uint64_t mulDiv64(uint64_t operant, uint64_t multiplier, uint64_t divider); +auto mulDiv64(uint64_t operant, uint64_t multiplier, + uint64_t divider) -> uint64_t; /** * @brief Performs a safe addition operation. @@ -39,7 +40,7 @@ uint64_t mulDiv64(uint64_t operant, uint64_t multiplier, uint64_t divider); * @param b The second operand for addition. * @return The result of a + b, or 0 if there is an overflow. */ -uint64_t safeAdd(uint64_t a, uint64_t b); +auto safeAdd(uint64_t a, uint64_t b) -> uint64_t; /** * @brief Performs a safe multiplication operation. @@ -51,7 +52,7 @@ uint64_t safeAdd(uint64_t a, uint64_t b); * @param b The second operand for multiplication. * @return The result of a * b, or 0 if there is an overflow. */ -uint64_t safeMul(uint64_t a, uint64_t b); +auto safeMul(uint64_t a, uint64_t b) -> uint64_t; /** * @brief Rotates a 64-bit integer to the left. @@ -63,7 +64,7 @@ uint64_t safeMul(uint64_t a, uint64_t b); * @param c The number of bits to rotate. * @return The rotated 64-bit integer. */ -uint64_t rotl64(uint64_t n, unsigned int c); +auto rotl64(uint64_t n, unsigned int c) -> uint64_t; /** * @brief Rotates a 64-bit integer to the right. @@ -75,7 +76,7 @@ uint64_t rotl64(uint64_t n, unsigned int c); * @param c The number of bits to rotate. * @return The rotated 64-bit integer. */ -uint64_t rotr64(uint64_t n, unsigned int c); +auto rotr64(uint64_t n, unsigned int c) -> uint64_t; /** * @brief Counts the leading zeros in a 64-bit integer. @@ -85,7 +86,7 @@ uint64_t rotr64(uint64_t n, unsigned int c); * @param x The 64-bit integer to count leading zeros in. * @return The number of leading zeros in the 64-bit integer. */ -int clz64(uint64_t x); +auto clz64(uint64_t x) -> int; /** * @brief Normalizes a 64-bit integer. @@ -96,7 +97,7 @@ int clz64(uint64_t x); * @param x The 64-bit integer to normalize. * @return The normalized 64-bit integer. */ -uint64_t normalize(uint64_t x); +auto normalize(uint64_t x) -> uint64_t; /** * @brief Performs a safe subtraction operation. @@ -108,7 +109,7 @@ uint64_t normalize(uint64_t x); * @param b The second operand for subtraction. * @return The result of a - b, or 0 if there is an underflow. */ -uint64_t safeSub(uint64_t a, uint64_t b); +auto safeSub(uint64_t a, uint64_t b) -> uint64_t; /** * @brief Performs a safe division operation. @@ -120,7 +121,7 @@ uint64_t safeSub(uint64_t a, uint64_t b); * @param b The denominator for division. * @return The result of a / b, or 0 if there is a division by zero. */ -uint64_t safeDiv(uint64_t a, uint64_t b); +auto safeDiv(uint64_t a, uint64_t b) -> uint64_t; /** * @brief Calculates the bitwise reverse of a 64-bit integer. @@ -130,7 +131,7 @@ uint64_t safeDiv(uint64_t a, uint64_t b); * @param n The 64-bit integer to reverse. * @return The bitwise reverse of the 64-bit integer. */ -uint64_t bitReverse64(uint64_t n); +auto bitReverse64(uint64_t n) -> uint64_t; /** * @brief Approximates the square root of a 64-bit integer. @@ -141,7 +142,7 @@ uint64_t bitReverse64(uint64_t n); * @param n The 64-bit integer for which to approximate the square root. * @return The approximate square root of the 64-bit integer. */ -uint64_t approximateSqrt(uint64_t n); +auto approximateSqrt(uint64_t n) -> uint64_t; /** * @brief Calculates the greatest common divisor (GCD) of two 64-bit integers. @@ -153,7 +154,7 @@ uint64_t approximateSqrt(uint64_t n); * @param b The second 64-bit integer. * @return The greatest common divisor of the two 64-bit integers. */ -uint64_t gcd64(uint64_t a, uint64_t b); +auto gcd64(uint64_t a, uint64_t b) -> uint64_t; /** * @brief Calculates the least common multiple (LCM) of two 64-bit integers. @@ -165,7 +166,7 @@ uint64_t gcd64(uint64_t a, uint64_t b); * @param b The second 64-bit integer. * @return The least common multiple of the two 64-bit integers. */ -uint64_t lcm64(uint64_t a, uint64_t b); +auto lcm64(uint64_t a, uint64_t b) -> uint64_t; /** * @brief Checks if a 64-bit integer is a power of two. @@ -175,7 +176,7 @@ uint64_t lcm64(uint64_t a, uint64_t b); * @param n The 64-bit integer to check. * @return True if the 64-bit integer is a power of two, false otherwise. */ -bool isPowerOfTwo(uint64_t n); +auto isPowerOfTwo(uint64_t n) -> bool; /** * @brief Calculates the next power of two for a 64-bit integer. @@ -185,7 +186,7 @@ bool isPowerOfTwo(uint64_t n); * @param n The 64-bit integer for which to calculate the next power of two. * @return The next power of two for the 64-bit integer. */ -uint64_t nextPowerOfTwo(uint64_t n); +auto nextPowerOfTwo(uint64_t n) -> uint64_t; } // namespace atom::algorithm #endif diff --git a/src/atom/algorithm/md5.cpp b/src/atom/algorithm/md5.cpp index b155de2a..cbe7741d 100644 --- a/src/atom/algorithm/md5.cpp +++ b/src/atom/algorithm/md5.cpp @@ -14,12 +14,20 @@ Description: Self implemented MD5 algorithm. #include "md5.hpp" +#include +#include +#include #include #include #include +#ifdef USE_OPENMP +#include +#endif + namespace atom::algorithm { -constexpr uint32_t T[64] = { + +constexpr std::array T{ 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, @@ -32,69 +40,73 @@ constexpr uint32_t T[64] = { 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391}; -constexpr uint32_t s[64] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, - 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, - 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, - 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, - 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; +constexpr std::array s{ + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; void MD5::init() { - _a = 0x67452301; - _b = 0xefcdab89; - _c = 0x98badcfe; - _d = 0x10325476; - _count = 0; - _buffer.clear(); + a_ = 0x67452301; + b_ = 0xefcdab89; + c_ = 0x98badcfe; + d_ = 0x10325476; + count_ = 0; + buffer_.clear(); } void MD5::update(const std::string &input) { + auto update_length = [this](size_t length) { count_ += length * 8; }; + + update_length(input.size()); + for (char ch : input) { - _buffer.push_back(static_cast(ch)); - if (_buffer.size() == 64) { - processBlock(_buffer.data()); - _count += 512; - _buffer.clear(); + buffer_.push_back(static_cast(ch)); + if (buffer_.size() == 64) { + processBlock(buffer_.data()); + buffer_.clear(); } } // Padding - size_t bitLen = _count + _buffer.size() * 8; - _buffer.push_back(0x80); - while (_buffer.size() < 56) - _buffer.push_back(0x00); + buffer_.push_back(0x80); + while (buffer_.size() < 56) { + buffer_.push_back(0x00); + } for (int i = 0; i < 8; ++i) { - _buffer.push_back(static_cast((bitLen >> (i * 8)) & 0xff)); + buffer_.push_back(static_cast((count_ >> (i * 8)) & 0xff)); } - processBlock(_buffer.data()); + processBlock(buffer_.data()); } -std::string MD5::finalize() { +auto MD5::finalize() -> std::string { std::stringstream ss; ss << std::hex << std::setfill('0'); - ss << std::setw(8) << reverseBytes(_a); - ss << std::setw(8) << reverseBytes(_b); - ss << std::setw(8) << reverseBytes(_c); - ss << std::setw(8) << reverseBytes(_d); + ss << std::setw(8) << std::byteswap(a_); + ss << std::setw(8) << std::byteswap(b_); + ss << std::setw(8) << std::byteswap(c_); + ss << std::setw(8) << std::byteswap(d_); return ss.str(); } void MD5::processBlock(const uint8_t *block) { - uint32_t M[16]; - for (int i = 0; i < 16; ++i) { - // 将block的每四个字节转换为32位整数(考虑到小端字节序) - M[i] = (uint32_t)block[i * 4] | ((uint32_t)block[i * 4 + 1] << 8) | - ((uint32_t)block[i * 4 + 2] << 16) | - ((uint32_t)block[i * 4 + 3] << 24); + std::array M; + for (size_t i = 0; i < 16; ++i) { + M[i] = std::bit_cast( + std::array{block[i * 4], block[i * 4 + 1], + block[i * 4 + 2], block[i * 4 + 3]}); } - uint32_t a = _a; - uint32_t b = _b; - uint32_t c = _c; - uint32_t d = _d; + uint32_t a = a_; + uint32_t b = b_; + uint32_t c = c_; + uint32_t d = d_; - // 主循环 +#ifdef USE_OPENMP +#pragma omp parallel for +#endif for (uint32_t i = 0; i < 64; ++i) { uint32_t f, g; if (i < 16) { @@ -114,41 +126,46 @@ void MD5::processBlock(const uint8_t *block) { uint32_t temp = d; d = c; c = b; - b = b + leftRotate((a + f + T[i] + M[g]), s[i]); + b += leftRotate(a + f + T[i] + M[g], s[i]); a = temp; } - _a += a; - _b += b; - _c += c; - _d += d; +#ifdef USE_OPENMP +#pragma omp critical +#endif + { + a_ += a; + b_ += b; + c_ += c; + d_ += d; + } } -uint32_t MD5::F(uint32_t x, uint32_t y, uint32_t z) { +auto MD5::F(uint32_t x, uint32_t y, uint32_t z) -> uint32_t { return (x & y) | (~x & z); } -uint32_t MD5::G(uint32_t x, uint32_t y, uint32_t z) { +auto MD5::G(uint32_t x, uint32_t y, uint32_t z) -> uint32_t { return (x & z) | (y & ~z); } -uint32_t MD5::H(uint32_t x, uint32_t y, uint32_t z) { return x ^ y ^ z; } - -uint32_t MD5::I(uint32_t x, uint32_t y, uint32_t z) { return y ^ (x | ~z); } +auto MD5::H(uint32_t x, uint32_t y, uint32_t z) -> uint32_t { + return x ^ y ^ z; +} -uint32_t MD5::leftRotate(uint32_t x, uint32_t n) { - return (x << n) | (x >> (32 - n)); +auto MD5::I(uint32_t x, uint32_t y, uint32_t z) -> uint32_t { + return y ^ (x | ~z); } -uint32_t MD5::reverseBytes(uint32_t x) { - return ((x & 0x000000FF) << 24) | ((x & 0x0000FF00) << 8) | - ((x & 0x00FF0000) >> 8) | ((x & 0xFF000000) >> 24); +auto MD5::leftRotate(uint32_t x, uint32_t n) -> uint32_t { + return std::rotl(x, n); } -std::string MD5::encrypt(const std::string &input) { +auto MD5::encrypt(const std::string &input) -> std::string { MD5 md5; md5.init(); md5.update(input); return md5.finalize(); } + } // namespace atom::algorithm diff --git a/src/atom/algorithm/md5.hpp b/src/atom/algorithm/md5.hpp index ad3a0e0c..f49daea3 100644 --- a/src/atom/algorithm/md5.hpp +++ b/src/atom/algorithm/md5.hpp @@ -19,84 +19,30 @@ Description: Self implemented MD5 algorithm. #include #include - namespace atom::algorithm { -/** - * @brief The MD5 class for calculating MD5 hash of input data. - */ + class MD5 { public: - /** - * @brief Encrypts the input string using MD5 algorithm. - * - * @param input The input string to be encrypted. - * @return The MD5 hash of the input string. - */ - static std::string encrypt(const std::string &input); + static auto encrypt(const std::string &input) -> std::string; private: - /** - * @brief Initializes internal variables and buffer for MD5 computation. - */ void init(); - - /** - * @brief Updates the MD5 computation with additional input data. - * - * @param input The input string to be added to the MD5 computation. - */ void update(const std::string &input); - - /** - * @brief Finalizes the MD5 computation and returns the resulting hash. - * - * @return The MD5 hash of all the input data provided so far. - */ - std::string finalize(); - - /** - * @brief Processes a 64-byte block of input data. - * - * @param block Pointer to the 64-byte block of input data. - */ + auto finalize() -> std::string; void processBlock(const uint8_t *block); - /** - * @brief The F function for MD5 algorithm. - */ - static uint32_t F(uint32_t x, uint32_t y, uint32_t z); - - /** - * @brief The G function for MD5 algorithm. - */ - static uint32_t G(uint32_t x, uint32_t y, uint32_t z); - - /** - * @brief The H function for MD5 algorithm. - */ - static uint32_t H(uint32_t x, uint32_t y, uint32_t z); - - /** - * @brief The I function for MD5 algorithm. - */ - static uint32_t I(uint32_t x, uint32_t y, uint32_t z); - - /** - * @brief Performs a left rotate operation on the input. - */ - static uint32_t leftRotate(uint32_t x, uint32_t n); - - /** - * @brief Reverses the bytes in the input. - */ - static uint32_t reverseBytes(uint32_t x); + static auto F(uint32_t x, uint32_t y, uint32_t z) -> uint32_t; + static auto G(uint32_t x, uint32_t y, uint32_t z) -> uint32_t; + static auto H(uint32_t x, uint32_t y, uint32_t z) -> uint32_t; + static auto I(uint32_t x, uint32_t y, uint32_t z) -> uint32_t; + static auto leftRotate(uint32_t x, uint32_t n) -> uint32_t; + static auto reverseBytes(uint32_t x) -> uint32_t; - uint32_t _a, _b, _c, - _d; /**< Internal state variables for MD5 computation. */ - uint64_t _count; /**< Total count of input bits. */ - std::vector _buffer; /**< Buffer for input data. */ + uint32_t a_, b_, c_, d_; + uint64_t count_; + std::vector buffer_; }; } // namespace atom::algorithm -#endif // MD5_H +#endif // ATOM_UTILS_MD5_HPP diff --git a/src/atom/algorithm/meson.build b/src/atom/algorithm/meson.build deleted file mode 100644 index 02f3d0cd..00000000 --- a/src/atom/algorithm/meson.build +++ /dev/null @@ -1,66 +0,0 @@ -project('atom-algorithm', 'c', 'cpp', - version: '1.0.0', - license: 'GPL3', - default_options: ['cpp_std=c++20'] -) - -# 源文件和头文件 -atom_algorithm_sources = [ - 'algorithm.cpp', - 'base.cpp', - 'convolve.cpp', - 'fraction.cpp', - 'huffman.cpp', - 'math.cpp', - 'md5.cpp', - 'mhash.cpp', - 'pid.cpp' -] - -atom_algorithm_headers = [ - 'algorithm.hpp', - 'algorithm.inl', - 'base.hpp', - 'convolve.hpp', - 'fraction.hpp', - 'hash.hpp', - 'huffman.hpp', - 'math.hpp', - 'md5.hpp', - 'mhash.hpp', - 'pid.hpp' -] - -# 对象库 -atom_algorithm_object = static_library('atom_algorithm_object', - sources: atom_algorithm_sources, - include_directories: include_directories('.'), - install: false -) - -# 静态库 -atom_algorithm_lib = static_library('atom-algorithm', - sources: atom_algorithm_object.extract_all_objects(), - include_directories: include_directories('.'), - install: true -) - -# 安装头文件 -install_headers(atom_algorithm_headers, subdir: 'atom-algorithm') - -# Python 绑定(如果需要) -pybind_enabled = get_option('build_python_binding') - -if pybind_enabled - pybind11_dep = dependency('pybind11', required: true) - python_binding = import('python') - atom_algorithm_py = python_binding.extension_module('atom-algorithm-py', - sources: '_pybind.cpp', - dependencies: [pybind11_dep], - include_directories: include_directories('.') - ) - atom_algorithm_py.link_with(atom_algorithm_lib) -endif - -# 选项:是否构建 Python 绑定 -option('build_python_binding', type: 'boolean', value: false, description: 'Build Python bindings using pybind11') diff --git a/src/atom/algorithm/mhash.cpp b/src/atom/algorithm/mhash.cpp index 5797899b..bf7dce38 100644 --- a/src/atom/algorithm/mhash.cpp +++ b/src/atom/algorithm/mhash.cpp @@ -14,15 +14,14 @@ Description: Implementation of murmur3 hash and quick hash #include "mhash.hpp" -#include -#include #include #include #include -#include -#include -#include -#include +#include +#include + +#include "atom/error/exception.hpp" +#include "atom/utils/random.hpp" #include #include @@ -30,125 +29,157 @@ Description: Implementation of murmur3 hash and quick hash #include namespace atom::algorithm { -uint32_t fmix32(uint32_t h) noexcept { - h ^= h >> 16; - h *= 0x85ebca6b; - h ^= h >> 13; - h *= 0xc2b2ae35; - h ^= h >> 16; - return h; +namespace { +#if USE_OPENCL +const char* minhashKernelSource = R"CLC( +__kernel void minhash_kernel(__global const size_t* hashes, __global size_t* signature, __global const size_t* a_values, __global const size_t* b_values, const size_t p, const size_t num_hashes, const size_t num_elements) { + int gid = get_global_id(0); + if (gid < num_hashes) { + size_t min_hash = SIZE_MAX; + size_t a = a_values[gid]; + size_t b = b_values[gid]; + for (size_t i = 0; i < num_elements; ++i) { + size_t h = (a * hashes[i] + b) % p; + if (h < min_hash) { + min_hash = h; + } + } + signature[gid] = min_hash; + } +} +)CLC"; +#endif +} // anonymous namespace + +MinHash::MinHash(size_t num_hashes) +#if USE_OPENCL + : opencl_available_(false) +#endif +{ + hash_functions_.reserve(num_hashes); + for (size_t i = 0; i < num_hashes; ++i) { + hash_functions_.emplace_back(generateHashFunction()); + } +#if USE_OPENCL + initializeOpenCL(); +#endif } -uint32_t ROTL(uint32_t x, int8_t r) noexcept { - return (x << r) | (x >> (32 - r)); +MinHash::~MinHash() { +#if USE_OPENCL + cleanupOpenCL(); +#endif } -uint32_t murmur3Hash(std::string_view data, uint32_t seed) noexcept { - uint32_t hash = seed; - const uint32_t seed1 = 0xcc9e2d51; - const uint32_t seed2 = 0x1b873593; - - // Process 4-byte chunks - const uint32_t *blocks = reinterpret_cast(data.data()); - size_t nblocks = data.size() / 4; - for (size_t i = 0; i < nblocks; i++) { - uint32_t k = blocks[i]; - k *= seed1; - k = ROTL(k, 15); - k *= seed2; - - hash ^= k; - hash = ROTL(hash, 13); - hash = hash * 5 + 0xe6546b64; +#if USE_OPENCL +void MinHash::initializeOpenCL() { + cl_int err; + cl_platform_id platform; + cl_device_id device; + + err = clGetPlatformIDs(1, &platform, nullptr); + if (err != CL_SUCCESS) { + return; } - // Handle the tail - const uint8_t *tail = - reinterpret_cast(data.data() + nblocks * 4); - uint32_t tail_val = 0; - switch (data.size() & 3) { - case 3: - tail_val |= tail[2] << 16; - [[fallthrough]]; - case 2: - tail_val |= tail[1] << 8; - [[fallthrough]]; - case 1: - tail_val |= tail[0]; - tail_val *= seed1; - tail_val = ROTL(tail_val, 15); - tail_val *= seed2; - hash ^= tail_val; + err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, nullptr); + if (err != CL_SUCCESS) { + return; } - return fmix32(hash ^ static_cast(data.size())); -} + context_ = clCreateContext(nullptr, 1, &device, nullptr, nullptr, &err); + if (err != CL_SUCCESS) { + return; + } -uint64_t murmur3Hash64(std::string_view str, uint32_t seed, uint32_t seed2) { - return (static_cast(murmur3Hash(str, seed)) << 32) | - murmur3Hash(str, seed2); -} + queue_ = clCreateCommandQueue(context_, device, 0, &err); + if (err != CL_SUCCESS) { + return; + } -void hexstringFromData(const void *data, size_t len, char *output) { - const unsigned char *buf = static_cast(data); - std::span bytes(buf, len); - std::ostringstream stream; + program_ = clCreateProgramWithSource(context_, 1, &minhashKernelSource, + nullptr, &err); + if (err != CL_SUCCESS) { + return; + } - // Use iomanip to format output - stream << std::hex << std::setfill('0'); - for (unsigned char byte : bytes) { - stream << std::setw(2) << static_cast(byte); + err = clBuildProgram(program_, 1, &device, nullptr, nullptr, nullptr); + if (err != CL_SUCCESS) { + return; } - std::string hexstr = stream.str(); - std::copy(hexstr.begin(), hexstr.end(), output); - output[hexstr.size()] = '\0'; // Null-terminate the output string + minhash_kernel_ = clCreateKernel(program_, "minhash_kernel", &err); + if (err == CL_SUCCESS) { + opencl_available_ = true; + } } -std::string hexstringFromData(const std::string &data) { - if (data.empty()) { - return {}; +void MinHash::cleanupOpenCL() { + if (opencl_available_) { + clReleaseKernel(minhash_kernel_); + clReleaseProgram(program_); + clReleaseCommandQueue(queue_); + clReleaseContext(context_); } +} +#endif - std::string result; - result.reserve(data.size() * 2); +auto MinHash::generateHashFunction() -> HashFunction { + utils::Random> rand( + 0, std::numeric_limits::max()); - for (unsigned char c : data) { - char buf[3]; // buffer for two hex chars and null terminator - std::to_chars_result conv_result = - std::to_chars(buf, buf + sizeof(buf), c, 16); + size_t a = rand(); + size_t b = rand(); + size_t p = std::numeric_limits::max(); - if (conv_result.ec == std::errc{}) { - if (buf[1] == '\0') { - result += '0'; // pad single digit hex numbers - } - result.append(buf, conv_result.ptr); + return [a, b, p](size_t x) -> size_t { return (a * x + b) % p; }; +} + +auto MinHash::jaccardIndex(const std::vector& sig1, + const std::vector& sig2) -> double { + size_t equalCount = 0; + + for (size_t i = 0; i < sig1.size(); ++i) { + if (sig1[i] == sig2[i]) { + ++equalCount; } } - return result; + return static_cast(equalCount) / sig1.size(); +} + +auto hexstringFromData(const std::string& data) -> std::string { + const char* hexChars = "0123456789ABCDEF"; + std::string output; + output.reserve(data.size() * 2); // Reserve space for the hex string + + for (unsigned char byte : data) { + output.push_back(hexChars[(byte >> 4) & 0x0F]); + output.push_back(hexChars[byte & 0x0F]); + } + + return output; } -std::string dataFromHexstring(const std::string &hexstring) { - if (hexstring.size() % 2 != 0) { - throw std::invalid_argument("Hex string length must be even"); +auto dataFromHexstring(const std::string& data) -> std::string { + if (data.size() % 2 != 0) { + THROW_INVALID_ARGUMENT("Hex string length must be even"); } std::string result; - result.resize(hexstring.size() / 2); + result.resize(data.size() / 2); - size_t output_index = 0; - for (size_t i = 0; i < hexstring.size(); i += 2) { + size_t outputIndex = 0; + for (size_t i = 0; i < data.size(); i += 2) { int byte = 0; - auto [ptr, ec] = std::from_chars(hexstring.data() + i, - hexstring.data() + i + 2, byte, 16); + auto [ptr, ec] = + std::from_chars(data.data() + i, data.data() + i + 2, byte, 16); - if (ec == std::errc::invalid_argument || - ptr != hexstring.data() + i + 2) { - throw std::invalid_argument("Invalid hex character"); + if (ec == std::errc::invalid_argument || ptr != data.data() + i + 2) { + THROW_INVALID_ARGUMENT("Invalid hex character"); } - result[output_index++] = static_cast(byte); + result[outputIndex++] = static_cast(byte); } return result; diff --git a/src/atom/algorithm/mhash.hpp b/src/atom/algorithm/mhash.hpp index fb089fd9..deca72e8 100644 --- a/src/atom/algorithm/mhash.hpp +++ b/src/atom/algorithm/mhash.hpp @@ -15,51 +15,26 @@ Description: Implementation of murmur3 hash and quick hash #ifndef ATOM_ALGORITHM_MHASH_HPP #define ATOM_ALGORITHM_MHASH_HPP -#include -#include +#include +#include +#include #include #include -namespace atom::algorithm { -/** - * @brief Calculates the MurmurHash3 hash value for a given string. - * - * @param str The input string. - * @param seed The seed value (optional, default is 1060627423). - * @return uint32_t The calculated hash value. - */ -[[nodiscard]] uint32_t murmur3Hash(std::string_view data, - uint32_t seed = 1060627423) noexcept; - -/** - * @brief Calculates the 64-bit MurmurHash3 hash value for a given string. - * - * @param str The input string. - * @param seed The first seed value (optional, default is 1060627423). - * @param seed2 The second seed value (optional, default is 1050126127). - * @return uint64_t The calculated hash value. - */ -[[nodiscard]] uint64_t murmur3Hash64(std::string_view str, - uint32_t seed = 1060627423, - uint32_t seed2 = 1050126127); +#if USE_OPENCL +#include +#endif -/** - * @brief Converts binary data to a hexadecimal string representation. - * - * @param data The input data buffer. - * @param len The length of the data buffer. - * @param output The output buffer to store the hexadecimal string (length must - * be len * 2). - */ -void hexstringFromData(const void *data, size_t len, char *output); +#include "macro.hpp" +namespace atom::algorithm { /** * @brief Converts a string to a hexadecimal string representation. * * @param data The input string. * @return std::string The hexadecimal string representation. */ -[[nodiscard]] std::string hexstringFromData(const std::string &data); +ATOM_NODISCARD auto hexstringFromData(const std::string& data) -> std::string; /** * @brief Converts a hexadecimal string representation to binary data. @@ -69,7 +44,174 @@ void hexstringFromData(const void *data, size_t len, char *output); * @throw std::invalid_argument If the input hexstring is not a valid * hexadecimal string. */ -[[nodiscard]] std::string dataFromHexstring(const std::string &data); -} // namespace atom::algorithm +ATOM_NODISCARD auto dataFromHexstring(const std::string& data) -> std::string; + +/** + * @brief Implements the MinHash algorithm for estimating Jaccard similarity. + * + * The MinHash algorithm generates hash signatures for sets and estimates the + * Jaccard index between sets based on these signatures. + */ +class MinHash { +public: + /** + * @brief Type definition for a hash function used in MinHash. + */ + using HashFunction = std::function; + + /** + * @brief Constructs a MinHash object with a specified number of hash + * functions. + * + * @param num_hashes The number of hash functions to use for MinHash. + */ + explicit MinHash(size_t num_hashes); + + /** + * @brief Destructor to clean up OpenCL resources. + */ + ~MinHash(); + /** + * @brief Computes the MinHash signature (hash values) for a given set. + * + * @tparam Range Type of the range representing the set elements. + * @param set The set for which to compute the MinHash signature. + * @return std::vector MinHash signature (hash values) for the set. + */ + template + auto computeSignature(const Range& set) const -> std::vector { + std::vector signature(hash_functions_.size(), + std::numeric_limits::max()); +#if USE_OPENCL + if (opencl_available_) { + computeSignatureOpenCL(set, signature); + } else { #endif + for (const auto& element : set) { + size_t elementHash = + std::hash{}(element); + for (size_t i = 0; i < hash_functions_.size(); ++i) { + signature[i] = + std::min(signature[i], hash_functions_[i](elementHash)); + } + } +#if USE_OPENCL + } +#endif + return signature; + } + + /** + * @brief Computes the Jaccard index between two sets based on their MinHash + * signatures. + * + * @param sig1 MinHash signature of the first set. + * @param sig2 MinHash signature of the second set. + * @return double Estimated Jaccard index between the two sets. + */ + static auto jaccardIndex(const std::vector& sig1, + const std::vector& sig2) -> double; + +private: + /** + * @brief Vector of hash functions used for MinHash. + */ + std::vector hash_functions_; + + /** + * @brief Generates a hash function suitable for MinHash. + * + * @return HashFunction Generated hash function. + */ + static auto generateHashFunction() -> HashFunction; + +#if USE_OPENCL + /** + * @brief OpenCL resources and state. + */ + cl_context context_; + cl_command_queue queue_; + cl_program program_; + cl_kernel minhash_kernel_; + bool opencl_available_; + + /** + * @brief Initializes OpenCL context and resources. + */ + void initializeOpenCL(); + + /** + * @brief Cleans up OpenCL resources. + */ + void cleanupOpenCL(); + + /** + * @brief Computes the MinHash signature using OpenCL. + * + * @tparam Range Type of the range representing the set elements. + * @param set The set for which to compute the MinHash signature. + * @param signature The vector to store the computed signature. + */ + template + void computeSignatureOpenCL(const Range& set, + std::vector& signature) const { + cl_int err; + size_t numHashes = hash_functions_.size(); + size_t numElements = set.size(); + + std::vector hashes; + hashes.reserve(numElements); + for (const auto& element : set) { + hashes.push_back(std::hash{}(element)); + } + + std::vector aValues(numHashes); + std::vector bValues(numHashes); + for (size_t i = 0; i < numHashes; ++i) { + aValues; // Use the generated hash function's "a" value + bValues; // Use the generated hash function's "b" value + } + + cl_mem hashesBuffer = + clCreateBuffer(context_, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, + numElements * sizeof(size_t), hashes.data(), &err); + cl_mem signatureBuffer = + clCreateBuffer(context_, CL_MEM_WRITE_ONLY, + numHashes * sizeof(size_t), nullptr, &err); + cl_mem aValuesBuffer = + clCreateBuffer(context_, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, + numHashes * sizeof(size_t), aValues.data(), &err); + cl_mem bValuesBuffer = + clCreateBuffer(context_, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, + numHashes * sizeof(size_t), bValues.data(), &err); + + size_t p = std::numeric_limits::max(); + + clSetKernelArg(minhash_kernel_, 0, sizeof(cl_mem), &hashesBuffer); + clSetKernelArg(minhash_kernel_, 1, sizeof(cl_mem), &signatureBuffer); + clSetKernelArg(minhash_kernel_, 2, sizeof(cl_mem), &aValuesBuffer); + clSetKernelArg(minhash_kernel_, 3, sizeof(cl_mem), &bValuesBuffer); + clSetKernelArg(minhash_kernel_, 4, sizeof(size_t), &p); + clSetKernelArg(minhash_kernel_, 5, sizeof(size_t), &numHashes); + clSetKernelArg(minhash_kernel_, 6, sizeof(size_t), &numElements); + + size_t globalWorkSize = numHashes; + clEnqueueNDRangeKernel(queue_, minhash_kernel_, 1, nullptr, + &globalWorkSize, nullptr, 0, nullptr, nullptr); + + clEnqueueReadBuffer(queue_, signatureBuffer, CL_TRUE, 0, + numHashes * sizeof(size_t), signature.data(), 0, + nullptr, nullptr); + + clReleaseMemObject(hashesBuffer); + clReleaseMemObject(signatureBuffer); + clReleaseMemObject(aValuesBuffer); + clReleaseMemObject(bValuesBuffer); + } +#endif +}; + +} // namespace atom::algorithm + +#endif // ATOM_ALGORITHM_MHASH_HPP diff --git a/src/atom/algorithm/perlin.hpp b/src/atom/algorithm/perlin.hpp new file mode 100644 index 00000000..b4bca416 --- /dev/null +++ b/src/atom/algorithm/perlin.hpp @@ -0,0 +1,311 @@ +#ifndef ATOM_ALGORITHM_PERLIN_HPP +#define ATOM_ALGORITHM_PERLIN_HPP + +#include +#include +#include +#include +#include +#include +#include + +#ifdef USE_OPENCL // 宏定义:是否启用OpenCL +#include +#endif + +namespace atom::algorithm { +class PerlinNoise { +public: + explicit PerlinNoise( + unsigned int seed = std::default_random_engine::default_seed) { + p.resize(512); + std::iota(p.begin(), p.begin() + 256, 0); + + std::default_random_engine engine(seed); + std::ranges::shuffle(std::span(p.begin(), p.begin() + 256), engine); + + std::ranges::copy(std::span(p.begin(), p.begin() + 256), + p.begin() + 256); + +#ifdef USE_OPENCL + initializeOpenCL(); +#endif + } + + ~PerlinNoise() { +#ifdef USE_OPENCL + cleanupOpenCL(); +#endif + } + + template + [[nodiscard]] auto noise(T x, T y, T z) const -> T { +#ifdef USE_OPENCL + if (opencl_available_) { + return noiseOpenCL(x, y, z); + } +#endif + return noiseCPU(x, y, z); + } + + template + [[nodiscard]] auto octaveNoise(T x, T y, T z, int octaves, + T persistence) const -> T { + T total = 0; + T frequency = 1; + T amplitude = 1; + T maxValue = 0; + + for (int i = 0; i < octaves; ++i) { + total += + noise(x * frequency, y * frequency, z * frequency) * amplitude; + maxValue += amplitude; + amplitude *= persistence; + frequency *= 2; + } + + return total / maxValue; + } + + [[nodiscard]] auto generateNoiseMap( + int width, int height, double scale, int octaves, double persistence, + double /*lacunarity*/, + int seed = std::default_random_engine::default_seed) const + -> std::vector> { + std::vector> noiseMap(height, + std::vector(width)); + std::default_random_engine prng(seed); + std::uniform_real_distribution dist(-10000, 10000); + double offsetX = dist(prng); + double offsetY = dist(prng); + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + double sampleX = (x - width / 2.0 + offsetX) / scale; + double sampleY = (y - height / 2.0 + offsetY) / scale; + noiseMap[y][x] = + octaveNoise(sampleX, sampleY, 0.0, octaves, persistence); + } + } + + return noiseMap; + } + +private: + std::vector p; + +#ifdef USE_OPENCL + cl_context context_; + cl_command_queue queue_; + cl_program program_; + cl_kernel noise_kernel_; + bool opencl_available_; + + void initializeOpenCL() { + cl_int err; + cl_platform_id platform; + cl_device_id device; + + err = clGetPlatformIDs(1, &platform, nullptr); + if (err != CL_SUCCESS) { + opencl_available_ = false; + return; + } + + err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, nullptr); + if (err != CL_SUCCESS) { + opencl_available_ = false; + return; + } + + context_ = clCreateContext(nullptr, 1, &device, nullptr, nullptr, &err); + if (err != CL_SUCCESS) { + opencl_available_ = false; + return; + } + + queue_ = clCreateCommandQueue(context_, device, 0, &err); + if (err != CL_SUCCESS) { + opencl_available_ = false; + return; + } + + const char* kernel_source = R"CLC( + __kernel void noise_kernel(__global const float* coords, + __global float* result, + __constant int* p) { + int gid = get_global_id(0); + + float x = coords[gid * 3]; + float y = coords[gid * 3 + 1]; + float z = coords[gid * 3 + 2]; + + int X = ((int)floor(x)) & 255; + int Y = ((int)floor(y)) & 255; + int Z = ((int)floor(z)) & 255; + + x -= floor(x); + y -= floor(y); + z -= floor(z); + + float u = x * x * x * (x * (x * 6 - 15) + 10); + float v = y * y * y * (y * (y * 6 - 15) + 10); + float w = z * z * z * (z * (z * 6 - 15) + 10); + + int A = p[X] + Y; + int AA = p[A] + Z; + int AB = p[A + 1] + Z; + int B = p[X + 1] + Y; + int BA = p[B] + Z; + int BB = p[B + 1] + Z; + + float res = lerp(w, + lerp(v, lerp(u, grad(p[AA], x, y, z), + grad(p[BA], x - 1, y, z)), + lerp(u, grad(p[AB], x, y - 1, z), + grad(p[BB], x - 1, y - 1, z))), + lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1), + grad(p[BA + 1], x - 1, y, z - 1)), + lerp(u, grad(p[AB + 1], x, y - 1, z - 1), + grad(p[BB + 1], x - 1, y - 1, + z - 1)))); + result[gid] = (res + 1) / 2; + } + + float lerp(float t, float a, float b) { + return a + t * (b - a); + } + + float grad(int hash, float x, float y, float z) { + int h = hash & 15; + float u = h < 8 ? x : y; + float v = h < 4 ? y : (h == 12 || h == 14 ? x : z); + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); + } + )CLC"; + + program_ = clCreateProgramWithSource(context_, 1, &kernel_source, + nullptr, &err); + if (err != CL_SUCCESS) { + opencl_available_ = false; + return; + } + + err = clBuildProgram(program_, 1, &device, nullptr, nullptr, nullptr); + if (err != CL_SUCCESS) { + opencl_available_ = false; + return; + } + + noise_kernel_ = clCreateKernel(program_, "noise_kernel", &err); + if (err != CL_SUCCESS) { + opencl_available_ = false; + return; + } + + opencl_available_ = true; + } + + void cleanupOpenCL() { + if (opencl_available_) { + clReleaseKernel(noise_kernel_); + clReleaseProgram(program_); + clReleaseCommandQueue(queue_); + clReleaseContext(context_); + } + } + + template + auto noiseOpenCL(T x, T y, T z) const -> T { + float coords[] = {static_cast(x), static_cast(y), + static_cast(z)}; + float result; + + cl_mem coords_buffer = + clCreateBuffer(context_, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, + sizeof(coords), coords, nullptr); + cl_mem result_buffer = clCreateBuffer(context_, CL_MEM_WRITE_ONLY, + sizeof(float), nullptr, nullptr); + cl_mem p_buffer = + clCreateBuffer(context_, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, + p.size() * sizeof(int), p.data(), nullptr); + + clSetKernelArg(noise_kernel_, 0, sizeof(cl_mem), &coords_buffer); + clSetKernelArg(noise_kernel_, 1, sizeof(cl_mem), &result_buffer); + clSetKernelArg(noise_kernel_, 2, sizeof(cl_mem), &p_buffer); + + size_t global_work_size = 1; + clEnqueueNDRangeKernel(queue_, noise_kernel_, 1, nullptr, + &global_work_size, nullptr, 0, nullptr, nullptr); + + clEnqueueReadBuffer(queue_, result_buffer, CL_TRUE, 0, sizeof(float), + &result, 0, nullptr, nullptr); + + clReleaseMemObject(coords_buffer); + clReleaseMemObject(result_buffer); + clReleaseMemObject(p_buffer); + + return static_cast(result); + } +#endif // USE_OPENCL + + template + [[nodiscard]] auto noiseCPU(T x, T y, T z) const -> T { + // Find unit cube containing point + int X = static_cast(std::floor(x)) & 255; + int Y = static_cast(std::floor(y)) & 255; + int Z = static_cast(std::floor(z)) & 255; + + // Find relative x, y, z of point in cube + x -= std::floor(x); + y -= std::floor(y); + z -= std::floor(z); + + // Compute fade curves for each of x, y, z + T u = fade(x); + T v = fade(y); + T w = fade(z); + + // Hash coordinates of the 8 cube corners + int A = p[X] + Y; + int AA = p[A] + Z; + int AB = p[A + 1] + Z; + int B = p[X + 1] + Y; + int BA = p[B] + Z; + int BB = p[B + 1] + Z; + + // Add blended results from 8 corners of cube + T res = lerp( + w, + lerp(v, lerp(u, grad(p[AA], x, y, z), grad(p[BA], x - 1, y, z)), + lerp(u, grad(p[AB], x, y - 1, z), + grad(p[BB], x - 1, y - 1, z))), + lerp(v, + lerp(u, grad(p[AA + 1], x, y, z - 1), + grad(p[BA + 1], x - 1, y, z - 1)), + lerp(u, grad(p[AB + 1], x, y - 1, z - 1), + grad(p[BB + 1], x - 1, y - 1, z - 1)))); + return (res + 1) / 2; // Normalize to [0,1] + } + + static constexpr auto fade(double t) noexcept -> double { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + static constexpr auto lerp(double t, double a, + double b) noexcept -> double { + return a + t * (b - a); + } + + static constexpr auto grad(int hash, double x, double y, + double z) noexcept -> double { + int h = hash & 15; + double u = h < 8 ? x : y; + double v = h < 4 ? y : (h == 12 || h == 14 ? x : z); + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); + } +}; + +} // namespace atom::algorithm + +#endif // ATOM_ALGORITHM_PERLIN_HPP diff --git a/src/atom/algorithm/pid.cpp b/src/atom/algorithm/pid.cpp deleted file mode 100644 index f0dd43e4..00000000 --- a/src/atom/algorithm/pid.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "pid.hpp" - -#include - -namespace atom::algorithm { -PID::PID(double dt, double max, double min, double Kp, double Kd, double Ki) - : pimpl(std::make_unique(dt, max, min, Kp, Kd, Ki)) {} - -void PID::setIntegratorLimits(double min, double max) { - pimpl->setIntegratorLimits(min, max); -} - -void PID::setTau(double value) { pimpl->setTau(value); } - -double PID::calculate(double setpoint, double pv) { - return pimpl->calculate(setpoint, pv); -} - -double PID::propotionalTerm() const { return pimpl->propotionalTerm(); } - -double PID::integralTerm() const { return pimpl->integralTerm(); } - -double PID::derivativeTerm() const { return pimpl->derivativeTerm(); } - -PIDImpl::PIDImpl(double dt, double max, double min, double Kp, double Kd, - double Ki) - : m_T(dt), m_Max(max), m_Min(min), m_Kp(Kp), m_Kd(Kd), m_Ki(Ki) {} - -PIDImpl::~PIDImpl() {} - -void PIDImpl::setIntegratorLimits(double min, double max) { - m_IntegratorMin = min; - m_IntegratorMax = max; -} - -void PIDImpl::setTau(double value) { m_Tau = value; } - -double PIDImpl::calculate(double setpoint, double measurement) { - double error = setpoint - measurement; - m_PropotionalTerm = m_Kp * error; - - m_IntegralTerm = - m_IntegralTerm + 0.5 * m_Ki * m_T * (error + m_PreviousError); - - if (m_IntegratorMin || m_IntegratorMax) - m_IntegralTerm = std::min(m_IntegratorMax, - std::max(m_IntegratorMin, m_IntegralTerm)); - - m_DerivativeTerm = -(2.0f * m_Kd * (measurement - m_PreviousMeasurement) + - (2.0f * m_Tau - m_T) * m_DerivativeTerm) / - (2.0f * m_Tau + m_T); - - double output = m_PropotionalTerm + m_IntegralTerm + m_DerivativeTerm; - output = std::min(m_Max, std::max(m_Min, output)); - - m_PreviousError = error; - m_PreviousMeasurement = measurement; - - return output; -} - -double PIDImpl::propotionalTerm() const { return m_PropotionalTerm; } -double PIDImpl::integralTerm() const { return m_IntegralTerm; } -double PIDImpl::derivativeTerm() const { return m_DerivativeTerm; } - -} // namespace atom::algorithm diff --git a/src/atom/algorithm/pid.hpp b/src/atom/algorithm/pid.hpp deleted file mode 100644 index 4feda18f..00000000 --- a/src/atom/algorithm/pid.hpp +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef ATOM_ALGORITHM_PID_HPP -#define ATOM_ALGORITHM_PID_HPP - -#include - -namespace atom::algorithm { - -class PIDImpl { -public: - PIDImpl(double dt, double max, double min, double Kp, double Kd, double Ki); - ~PIDImpl(); - void setIntegratorLimits(double min, double max); - void setTau(double value); - double calculate(double setpoint, double measurement); - double propotionalTerm() const; - double integralTerm() const; - double derivativeTerm() const; - -private: - double m_T{1}; - double m_Tau{2}; - double m_Max{0}; - double m_Min{0}; - double m_IntegratorMin{0}; - double m_IntegratorMax{0}; - double m_Kp{0}; - double m_Kd{0}; - double m_Ki{0}; - double m_PreviousError{0}; - double m_PreviousMeasurement{0}; - double m_PropotionalTerm{0}; - double m_IntegralTerm{0}; - double m_DerivativeTerm{0}; -}; - -/** - * @brief The PID class implements a Proportional-Integral-Derivative - * controller. - */ -class PID { -public: - /** - * @brief Constructor for the PID controller. - * @param dt The time step for the controller. - * @param max The maximum output value of the controller. - * @param min The minimum output value of the controller. - * @param Kp The proportional gain. - * @param Kd The derivative gain. - * @param Ki The integral gain. - */ - PID(double dt, double max, double min, double Kp, double Kd, double Ki); - - /** - * @brief Set the limits for the integrator term. - * @param min The minimum value of the integrator term. - * @param max The maximum value of the integrator term. - */ - void setIntegratorLimits(double min, double max); - - /** - * @brief Set the time constant (Tau) for the derivative component. - * @param value The value of the time constant. - */ - void setTau(double value); - - /** - * @brief Calculate the control output for the given setpoint and process - * variable. - * @param setpoint The desired setpoint value. - * @param pv The process variable (current measurement). - * @return The calculated control output. - */ - double calculate(double setpoint, double pv); - - /** - * @brief Get the proportional term of the controller. - * @return The proportional term value. - */ - double propotionalTerm() const; - - /** - * @brief Get the integral term of the controller. - * @return The integral term value. - */ - double integralTerm() const; - - /** - * @brief Get the derivative term of the controller. - * @return The derivative term value. - */ - double derivativeTerm() const; - -private: - std::unique_ptr - pimpl; /**< Pointer to the implementation of the PID controller. */ -}; -} // namespace atom::algorithm - -#endif diff --git a/src/atom/algorithm/weight.hpp b/src/atom/algorithm/weight.hpp new file mode 100644 index 00000000..678881c2 --- /dev/null +++ b/src/atom/algorithm/weight.hpp @@ -0,0 +1,179 @@ +#ifndef ATOM_ALGORITHM_WEIGHT_HPP +#define ATOM_ALGORITHM_WEIGHT_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "atom/utils/random.hpp" +#include "error/exception.hpp" +#include "function/concept.hpp" + +namespace atom::algorithm { +template +class WeightSelector { +public: + class SelectionStrategy { + public: + virtual ~SelectionStrategy() = default; + virtual auto select(std::span cumulative_weights, + T total_weight) -> size_t = 0; + }; + + class DefaultSelectionStrategy : public SelectionStrategy { + private: + utils::Random> random_; + + public: + DefaultSelectionStrategy() : random_(0.0, 1.0) {} + + auto select(std::span cumulative_weights, + T total_weight) -> size_t override { + T randomValue = random_() * total_weight; + auto it = std::ranges::upper_bound(cumulative_weights, randomValue); + return std::distance(cumulative_weights.begin(), it); + } + }; + +private: + std::vector weights_; + std::vector cumulative_weights_; + std::unique_ptr strategy_; + + void updateCumulativeWeights() { + cumulative_weights_.resize(weights_.size()); + std::exclusive_scan(weights_.begin(), weights_.end(), + cumulative_weights_.begin(), T{0}); + } + +public: + explicit WeightSelector(std::span input_weights, + std::unique_ptr custom_strategy = + std::make_unique()) + : weights_(input_weights.begin(), input_weights.end()), + strategy_(std::move(custom_strategy)) { + updateCumulativeWeights(); + } + + void setSelectionStrategy(std::unique_ptr new_strategy) { + strategy_ = std::move(new_strategy); + } + + auto select() -> size_t { + T totalWeight = std::reduce(weights_.begin(), weights_.end()); + return strategy_->select(cumulative_weights_, totalWeight); + } + + auto selectMultiple(size_t n) -> std::vector { + std::vector results; + results.reserve(n); + for (size_t i = 0; i < n; ++i) { + results.push_back(select()); + } + return results; + } + + void updateWeight(size_t index, T new_weight) { + if (index >= weights_.size()) { + THROW_OUT_OF_RANGE("Index out of range"); + } + weights_[index] = new_weight; + updateCumulativeWeights(); + } + + void addWeight(T new_weight) { + weights_.push_back(new_weight); + updateCumulativeWeights(); + } + + void removeWeight(size_t index) { + if (index >= weights_.size()) { + THROW_OUT_OF_RANGE("Index out of range"); + } + weights_.erase(weights_.begin() + index); + updateCumulativeWeights(); + } + + void normalizeWeights() { + T sum = std::reduce(weights_.begin(), weights_.end()); + std::ranges::transform(weights_, weights_.begin(), + [sum](T w) { return w / sum; }); + updateCumulativeWeights(); + } + + void applyFunctionToWeights(std::invocable auto&& func) { + std::ranges::transform(weights_, weights_.begin(), + std::forward(func)); + updateCumulativeWeights(); + } + + void batchUpdateWeights(const std::vector>& updates) { + for (const auto& [index, new_weight] : updates) { + if (index >= weights_.size()) { + THROW_OUT_OF_RANGE("Index out of range"); + } + weights_[index] = new_weight; + } + updateCumulativeWeights(); + } + + [[nodiscard]] auto getWeight(size_t index) const -> std::optional { + if (index >= weights_.size()) { + return std::nullopt; + } + return weights_[index]; + } + + [[nodiscard]] auto getMaxWeightIndex() const -> size_t { + return std::distance(weights_.begin(), + std::ranges::max_element(weights_)); + } + + [[nodiscard]] auto getMinWeightIndex() const -> size_t { + return std::distance(weights_.begin(), + std::ranges::min_element(weights_)); + } + + [[nodiscard]] auto size() const -> size_t { return weights_.size(); } + + [[nodiscard]] auto getWeights() const -> std::span { + return weights_; + } + + [[nodiscard]] auto getTotalWeight() const -> T { + return std::reduce(weights_.begin(), weights_.end()); + } + + void printWeights(std::ostream& oss) const { + oss << std::format("[{:.2f}", weights_.front()); + for (auto it = weights_.begin() + 1; it != weights_.end(); ++it) { + oss << std::format(", {:.2f}", *it); + } + oss << "]\n"; + } +}; + +template +class TopHeavySelectionStrategy : public WeightSelector::SelectionStrategy { +private: + utils::Random> random_; + +public: + TopHeavySelectionStrategy() : random_(0.0, 1.0) {} + + size_t select(std::span cumulative_weights, + T total_weight) override { + T randomValue = std::pow(random_(), 2) * total_weight; + auto it = std::ranges::upper_bound(cumulative_weights, randomValue); + return std::distance(cumulative_weights.begin(), it); + } +}; +} // namespace atom::algorithm + +#endif diff --git a/src/atom/algorithm/xmake.lua b/src/atom/algorithm/xmake.lua index 73de7bc9..45f56012 100644 --- a/src/atom/algorithm/xmake.lua +++ b/src/atom/algorithm/xmake.lua @@ -6,50 +6,16 @@ -- Author: Max Qian -- License: GPL3 -set_project("atom-algorithm") -set_version("1.0.0") -- Adjust version accordingly - --- Set minimum xmake version -set_xmakever("2.5.1") - --- Define target for object library -target("atom-algorithm_object") - set_kind("object") - set_languages("c99", "cxx20") - set_values("xmake.build.cc.flags", "-fPIC") - add_files("algorithm.cpp", "base.cpp", "convolve.cpp", "fbase.cpp", "fnmatch.cpp", "fraction.cpp", "huffman.cpp", "math.cpp", "md5.cpp", "mhash.cpp", "pid.cpp") - add_headerfiles("algorithm.hpp", "algorithm.inl", "base.hpp", "calculator.hpp", "convolve.hpp", "fbase.hpp", "fnmatch.hpp", "fraction.hpp", "hash.hpp", "huffman.hpp", "math.hpp", "md5.hpp", "mhash.hpp", "pid.hpp") - add_includedirs(".") - --- Define target for static library -target("atom-algorithm") - set_kind("static") - set_languages("c99", "cxx20") - add_deps("atom-algorithm_object") - add_includedirs(".") - add_files("algorithm.cpp", "base.cpp", "convolve.cpp", "fbase.cpp", "fnmatch.cpp", "fraction.cpp", "huffman.cpp", "math.cpp", "md5.cpp", "mhash.cpp", "pid.cpp") - add_headerfiles("algorithm.hpp", "algorithm.inl", "base.hpp", "calculator.hpp", "convolve.hpp", "fbase.hpp", "fnmatch.hpp", "fraction.hpp", "hash.hpp", "huffman.hpp", "math.hpp", "md5.hpp", "mhash.hpp", "pid.hpp") - add_packages("threads") -- Assuming threads package is needed - set_values("VERSION", "$(CMAKE_HYDROGEN_VERSION_STRING)") - set_values("SOVERSION", "$(HYDROGEN_SOVERSION)") - - -- Add installation directory - on_install(function (target) - os.cp(target:targetfile(), path.join(target:installdir(), "lib")) +package("foo") + add_deps("cmake") + set_sourcedir(path.join(os.scriptdir(), "foo")) + on_install(function (package) + local configs = {} + table.insert(configs, "-DCMAKE_BUILD_TYPE=" .. (package:debug() and "Debug" or "Release")) + table.insert(configs, "-DBUILD_SHARED_LIBS=" .. (package:config("shared") and "ON" or "OFF")) + import("package.tools.cmake").install(package, configs) end) - --- Optionally build Python module if ATOM_BUILD_PYTHON is set -option("ATOM_BUILD_PYTHON") - set_default(false) - set_showmenu(true) - set_description("Build Python bindings") - -if has_config("ATOM_BUILD_PYTHON") then - target("atom-algorithm-py") - set_kind("shared") - set_languages("c99", "cxx20") - add_files("_pybind.cpp") - add_includedirs(".") - add_deps("atom-algorithm") - add_packages("pybind11") -end + on_test(function (package) + assert(package:has_cfuncs("add", {includes = "foo.h"})) + end) +package_end() diff --git a/src/atom/async/CMakeLists.txt b/src/atom/async/CMakeLists.txt index abe35349..58a470f8 100644 --- a/src/atom/async/CMakeLists.txt +++ b/src/atom/async/CMakeLists.txt @@ -11,6 +11,8 @@ project(atom-async C CXX) # Sources set(${PROJECT_NAME}_SOURCES + daemon.cpp + limiter.cpp lock.cpp timer.cpp ) @@ -18,27 +20,29 @@ set(${PROJECT_NAME}_SOURCES # Headers set(${PROJECT_NAME}_HEADERS async.hpp - async.inl + daemon.hpp + eventstack.hpp + limiter.hpp lock.hpp + message_bus.hpp + message_queue.hpp pool.hpp queue.hpp - queue.inl + safetype.hpp thread_wrapper.hpp timer.hpp trigger.hpp - trigger.inl ) set(${PROJECT_NAME}_LIBS loguru + ${CMAKE_THREAD_LIBS_INIT} ) # Build Object Library add_library(${PROJECT_NAME}_OBJECT OBJECT) set_property(TARGET ${PROJECT_NAME}_OBJECT PROPERTY POSITION_INDEPENDENT_CODE 1) -target_link_libraries(${PROJECT_NAME}_OBJECT loguru) - target_sources(${PROJECT_NAME}_OBJECT PUBLIC ${${PROJECT_NAME}_HEADERS} @@ -51,7 +55,6 @@ target_link_libraries(${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) add_library(${PROJECT_NAME} STATIC) target_link_libraries(${PROJECT_NAME} ${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) -target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT}) target_include_directories(${PROJECT_NAME} PUBLIC .) set_target_properties(${PROJECT_NAME} PROPERTIES diff --git a/src/atom/async/_pybind.cpp b/src/atom/async/_pybind.cpp deleted file mode 100644 index b98adf74..00000000 --- a/src/atom/async/_pybind.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * _pybind.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-4-13 - -Description: Python Binding of Atom-Async - -**************************************************/ - -#include -#include -#include -#include - -#include "async.hpp" -#include "thread_wrapper.hpp" -#include "timer.hpp" -#include "trigger.hpp" - -namespace py = pybind11; - -using namespace atom::async; - -template -void start_wrapper(Thread& thread, Callable&& func, Args&&... args) { - thread.start(std::forward(func), std::forward(args)...); -} - -void bind_thread(py::module& m) { - py::class_(m, "Thread") - .def(py::init<>()) - .def("start", &start_wrapper, - "Starts a new thread with the specified callable object and " - "arguments.") - .def("request_stop", &Thread::request_stop, - "Requests the thread to stop execution.") - .def("join", &Thread::join, "Waits for the thread to finish execution.") - .def("running", &Thread::running, - "Checks if the thread is currently running.") - .def("swap", &Thread::swap, - "Swaps the content of this Thread object with another Thread " - "object.") - .def("get_id", &Thread::get_id, "Gets the ID of the thread.") - .def("get_stop_source", &Thread::get_stop_source, - "Gets the underlying std::stop_source object.") - .def("get_stop_token", &Thread::get_stop_token, - "Gets the underlying std::stop_token object.") - .def("__enter__", [](Thread& self) -> Thread& { return self; }) - .def("__exit__", [](Thread& self, py::object, py::object, - py::object) { self.join(); }) - .def("__enter__", [](Thread& self) -> Thread& { return self; }) - .def("__exit__", [](Thread& self, py::object, py::object, py::object) { - self.join(); - }); -} - -// Bind TimerTask class -void bind_timer_task(py::module& m) { - py::class_(m, "TimerTask") - .def(py::init, unsigned int, int, int>()) - .def("__lt__", &TimerTask::operator<) - .def("run", &TimerTask::run) - .def("getNextExecutionTime", &TimerTask::getNextExecutionTime); -} - -// Bind Timer class -void bind_timer(py::module& m) { - py::class_(m, "Timer") - .def(py::init<>()) - .def("setTimeout", &Timer::setTimeout, - "Schedules a task to be executed once after a specified delay.") - .def("setInterval", &Timer::setInterval, - "Schedules a task to be executed repeatedly at a specified " - "interval.") - .def("now", &Timer::now, "Returns the current time.") - .def("cancelAllTasks", &Timer::cancelAllTasks, - "Cancels all scheduled tasks.") - .def("pause", &Timer::pause, "Pauses the execution of scheduled tasks.") - .def("resume", &Timer::resume, - "Resumes the execution of scheduled tasks after pausing.") - .def("stop", &Timer::stop, "Stops the timer and cancels all tasks.") - .def("setCallback", &Timer::setCallback, - "Sets a callback function to be called when a task is executed.") - .def("getTaskCount", &Timer::getTaskCount, - "Gets the number of scheduled tasks."); -} - -PYBIND11_MODULE(atom_async, m) { - m.doc() = "Atom Async Python Binding"; - - // Define the Trigger class and its methods - py::class_>(m, "Trigger") - .def(py::init<>()) - .def("register_callback", &Trigger::registerCallback) - .def("unregister_callback", &Trigger::unregisterCallback) - .def("trigger", &Trigger::trigger) - .def("schedule_trigger", &Trigger::scheduleTrigger) - .def("schedule_async_trigger", &Trigger::scheduleAsyncTrigger) - .def("cancel_trigger", &Trigger::cancelTrigger) - .def("cancel_all_triggers", &Trigger::cancelAllTriggers); - - bind_thread(m); - bind_timer_task(m); - bind_timer(m); -} diff --git a/src/atom/async/async.hpp b/src/atom/async/async.hpp index 2cb62790..786829af 100644 --- a/src/atom/async/async.hpp +++ b/src/atom/async/async.hpp @@ -15,18 +15,23 @@ Description: A simple but useful async worker manager #ifndef ATOM_ASYNC_ASYNC_HPP #define ATOM_ASYNC_ASYNC_HPP -#include #include #include #include #include -#include -#include -#include #include #include "atom/error/exception.hpp" +class TimeoutException : public atom::error::RuntimeError { +public: + using atom::error::RuntimeError::RuntimeError; +}; + +#define THROW_TIMEOUT_EXCEPTION(...) \ + throw TimeoutException(ATOM_FILE_NAME, ATOM_FILE_LINE, ATOM_FUNC_NAME, \ + __VA_ARGS__); + namespace atom::async { /** * @brief Class for performing asynchronous tasks. @@ -50,7 +55,7 @@ class AsyncWorker { * @param args The arguments to be passed to the function. */ template - void StartAsync(Func &&func, Args &&...args); + void startAsync(Func &&func, Args &&...args); /** * @brief Gets the result of the task. @@ -58,28 +63,28 @@ class AsyncWorker { * @throw std::runtime_error if the task is not valid. * @return The result of the task. */ - ResultType GetResult(); + auto getResult() -> ResultType; /** * @brief Cancels the task. * * If the task is valid, this function waits for the task to complete. */ - void Cancel(); + void cancel(); /** * @brief Checks if the task is done. * * @return True if the task is done, false otherwise. */ - bool IsDone() const; + [[nodiscard]] auto isDone() const -> bool; /** * @brief Checks if the task is active. * * @return True if the task is active, false otherwise. */ - bool IsActive() const; + [[nodiscard]] auto isActive() const -> bool; /** * @brief Validates the result of the task using a validator function. @@ -87,21 +92,21 @@ class AsyncWorker { * @param validator The function used to validate the result. * @return True if the result is valid, false otherwise. */ - bool Validate(std::function validator); + auto validate(std::function validator) -> bool; /** * @brief Sets a callback function to be called when the task is done. * * @param callback The callback function to be set. */ - void SetCallback(std::function callback); + void setCallback(std::function callback); /** * @brief Sets a timeout for the task. * * @param timeout The timeout duration. */ - void SetTimeout(std::chrono::seconds timeout); + void setTimeout(std::chrono::seconds timeout); /** * @brief Waits for the task to complete. @@ -110,7 +115,7 @@ class AsyncWorker { * timeout is reached. If a callback function is set and the task is done, * the callback function is called with the result. */ - void WaitForCompletion(); + void waitForCompletion(); private: std::future @@ -149,25 +154,25 @@ class AsyncWorkerManager { * @return A shared pointer to the created AsyncWorker instance. */ template - std::shared_ptr> CreateWorker(Func &&func, - Args &&...args); + auto createWorker(Func &&func, Args &&...args) + -> std::shared_ptr>; /** * @brief Cancels all the managed tasks. */ - void CancelAll(); + void cancelAll(); /** * @brief Checks if all the managed tasks are done. * * @return True if all tasks are done, false otherwise. */ - bool AllDone() const; + auto allDone() const -> bool; /** * @brief Waits for all the managed tasks to complete. */ - void WaitForAll(); + void waitForAll(); /** * @brief Checks if a specific task is done. @@ -175,14 +180,14 @@ class AsyncWorkerManager { * @param worker The AsyncWorker instance to check. * @return True if the task is done, false otherwise. */ - bool IsDone(std::shared_ptr> worker) const; + bool isDone(std::shared_ptr> worker) const; /** * @brief Cancels a specific task. * * @param worker The AsyncWorker instance to cancel. */ - void Cancel(std::shared_ptr> worker); + void cancel(std::shared_ptr> worker); private: std::vector>> @@ -199,9 +204,9 @@ class AsyncWorkerManager { * @return A shared pointer to the created AsyncWorker instance. */ template -std::future()(std::declval()...))> asyncRetry( +auto asyncRetry( Func &&func, int attemptsLeft, std::chrono::milliseconds delay, - Args &&...args); + Args &&...args) -> std::future()(std::declval()...))>; /** * @brief Gets the result of the task with a timeout. @@ -211,10 +216,188 @@ std::future()(std::declval()...))> asyncRetry( * @return The result of the task. */ template -ReturnType getWithTimeout(std::future &future, - std::chrono::milliseconds timeout); -} // namespace atom::async +auto getWithTimeout(std::future &future, + std::chrono::milliseconds timeout) -> ReturnType; + +template +template +void AsyncWorker::startAsync(Func &&func, Args &&...args) { + static_assert(std::is_invocable_r_v, + "Function must return a result"); + task_ = std::async(std::launch::async, std::forward(func), + std::forward(args)...); +} + +template +[[nodiscard]] auto AsyncWorker::getResult() -> ResultType { + if (!task_.valid()) { + throw std::invalid_argument("Task is not valid"); + } + return task_.get(); +} + +template +void AsyncWorker::cancel() { + if (task_.valid()) { + task_.wait(); // 等待任务完成 + } +} + +template +auto AsyncWorker::isDone() const -> bool { + return task_.valid() && (task_.wait_for(std::chrono::seconds(0)) == + std::future_status::ready); +} + +template +auto AsyncWorker::isActive() const -> bool { + return task_.valid() && (task_.wait_for(std::chrono::seconds(0)) == + std::future_status::timeout); +} + +template +auto AsyncWorker::validate( + std::function validator) -> bool { + if (!isDone()) { + } + ResultType result = getResult(); + return validator(result); +} + +template +void AsyncWorker::setCallback( + std::function callback) { + callback_ = callback; +} + +template +void AsyncWorker::setTimeout(std::chrono::seconds timeout) { + timeout_ = timeout; +} + +template +void AsyncWorker::waitForCompletion() { + if (timeout_ != std::chrono::seconds(0)) { + auto startTime = std::chrono::steady_clock::now(); + while (!isDone()) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + if (std::chrono::steady_clock::now() - startTime > timeout_) { + cancel(); + break; + } + } + } else { + while (!isDone()) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + + if (callback_ && isDone()) { + callback_(getResult()); + } +} + +template +template +[[nodiscard]] auto AsyncWorkerManager::createWorker( + Func &&func, Args &&...args) -> std::shared_ptr> { + auto worker = std::make_shared>(); + workers_.push_back(worker); + worker->StartAsync(std::forward(func), std::forward(args)...); + return worker; +} + +template +void AsyncWorkerManager::cancelAll() { + for (auto &worker : workers_) { + worker->Cancel(); + } +} + +template +auto AsyncWorkerManager::allDone() const -> bool { + return std::all_of(workers_.begin(), workers_.end(), + [](const auto &worker) { return worker->IsDone(); }); +} -#include "async.inl" +template +void AsyncWorkerManager::waitForAll() { + while (!allDone()) { + } +} + +template +auto AsyncWorkerManager::isDone( + std::shared_ptr> worker) const -> bool { + return worker->IsDone(); +} +template +void AsyncWorkerManager::cancel( + std::shared_ptr> worker) { + worker->Cancel(); +} + +template + requires std::invocable +[[nodiscard]] auto asyncRetry(Func &&func, int attemptsLeft, + std::chrono::milliseconds delay, Args &&...args) { + using ReturnType = std::invoke_result_t; + static_assert(!std::is_void_v, "Func must return a value"); + + if (attemptsLeft <= 1) { + // 最后一次尝试,直接执行 + return std::async(std::launch::deferred, std::forward(func), + std::forward(args)...); + } + + // 尝试执行函数 + auto attempt = std::async(std::launch::async, std::forward(func), + std::forward(args)...); + + try { + // 立即获取结果,如果有异常会在这里抛出 + if constexpr (std::is_same_v) { + attempt.get(); + return std::async(std::launch::deferred, []() {}); + } else { + auto result = attempt.get(); + return std::async(std::launch::deferred, + [result = std::move(result)]() mutable { + return std::move(result); + }); + } + } catch (...) { + if (attemptsLeft <= 1) { + // 所有尝试都失败,重新抛出最后一次的异常 + throw; + } // 等待一段时间后重试 + std::this_thread::sleep_for(delay); + return asyncRetry(std::forward(func), attemptsLeft - 1, delay, + std::forward(args)...); + } +} + +template +[[nodiscard]] auto getWithTimeout(std::future &future, + std::chrono::milliseconds timeout) -> T { + static_assert(!std::is_void_v, "T must not be void"); + + if (future.wait_for(timeout) == std::future_status::ready) { + return future.get(); + } + THROW_TIMEOUT_EXCEPTION("Timeout occurred while waiting for future result"); +} + +template +[[nodiscard]] auto getWithTimeout( + std::future &future, std::chrono::duration timeout) -> T { + static_assert(!std::is_void_v, "T must not be void"); + + if (future.wait_for(timeout) == std::future_status::ready) { + return future.get(); + } + THROW_TIMEOUT_EXCEPTION("Timeout occurred while waiting for future result"); +} +} // namespace atom::async #endif diff --git a/src/atom/async/async.inl b/src/atom/async/async.inl deleted file mode 100644 index d2efeb1d..00000000 --- a/src/atom/async/async.inl +++ /dev/null @@ -1,210 +0,0 @@ -/* - * async.inl - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-11-10 - -Description: A simple but useful async worker manager - -**************************************************/ - -#ifndef ATOM_ASYNC_ASYNC_INL -#define ATOM_ASYNC_ASYNC_INL - -#include "async.hpp" - -namespace atom::async { -template -template -void AsyncWorker::StartAsync(Func &&func, Args &&...args) { - static_assert(std::is_invocable_r_v, - "Function must return a result"); - task_ = std::async(std::launch::async, std::forward(func), - std::forward(args)...); -} - -template -[[nodiscard]] ResultType AsyncWorker::GetResult() { - if (!task_.valid()) { - throw std::invalid_argument("Task is not valid"); - } - return task_.get(); -} - -template -void AsyncWorker::Cancel() { - if (task_.valid()) { - task_.wait(); // 等待任务完成 - } -} - -template -bool AsyncWorker::IsDone() const { - return task_.valid() && (task_.wait_for(std::chrono::seconds(0)) == - std::future_status::ready); -} - -template -bool AsyncWorker::IsActive() const { - return task_.valid() && (task_.wait_for(std::chrono::seconds(0)) == - std::future_status::timeout); -} - -template -bool AsyncWorker::Validate( - std::function validator) { - if (!IsDone()) { - } - ResultType result = GetResult(); - return validator(result); -} - -template -void AsyncWorker::SetCallback( - std::function callback) { - callback_ = callback; -} - -template -void AsyncWorker::SetTimeout(std::chrono::seconds timeout) { - timeout_ = timeout; -} - -template -void AsyncWorker::WaitForCompletion() { - if (timeout_ != std::chrono::seconds(0)) { - auto start_time = std::chrono::steady_clock::now(); - while (!IsDone()) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - if (std::chrono::steady_clock::now() - start_time > timeout_) { - Cancel(); - break; - } - } - } else { - while (!IsDone()) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - } - - if (callback_ && IsDone()) { - callback_(GetResult()); - } -} - -template -template -[[nodiscard]] std::shared_ptr> -AsyncWorkerManager::CreateWorker(Func &&func, Args &&...args) { - auto worker = std::make_shared>(); - workers_.push_back(worker); - worker->StartAsync(std::forward(func), std::forward(args)...); - return worker; -} - -template -void AsyncWorkerManager::CancelAll() { - for (auto &worker : workers_) { - worker->Cancel(); - } -} - -template -bool AsyncWorkerManager::AllDone() const { - return std::all_of(workers_.begin(), workers_.end(), - [](const auto &worker) { return worker->IsDone(); }); -} - -template -void AsyncWorkerManager::WaitForAll() { - while (!AllDone()) { - // 可以加入一些逻辑来检查任务完成情况 - } -} - -template -bool AsyncWorkerManager::IsDone( - std::shared_ptr> worker) const { - return worker->IsDone(); -} - -template -void AsyncWorkerManager::Cancel( - std::shared_ptr> worker) { - worker->Cancel(); -} - -template - requires std::invocable -[[nodiscard]] auto asyncRetry(Func &&func, int attemptsLeft, - std::chrono::milliseconds delay, Args &&...args) { - using ReturnType = std::invoke_result_t; - static_assert(!std::is_void_v, "Func must return a value"); - - if (attemptsLeft <= 1) { - // 最后一次尝试,直接执行 - return std::async(std::launch::deferred, std::forward(func), - std::forward(args)...); - } - - // 尝试执行函数 - auto attempt = std::async(std::launch::async, std::forward(func), - std::forward(args)...); - - try { - // 立即获取结果,如果有异常会在这里抛出 - if constexpr (std::is_same_v) { - attempt.get(); - return std::async(std::launch::deferred, []() {}); - } else { - auto result = attempt.get(); - return std::async(std::launch::deferred, - [result = std::move(result)]() mutable { - return std::move(result); - }); - } - } catch (...) { - if (attemptsLeft <= 1) { - // 所有尝试都失败,重新抛出最后一次的异常 - throw; - } else { - // 等待一段时间后重试 - std::this_thread::sleep_for(delay); - return asyncRetry(std::forward(func), attemptsLeft - 1, delay, - std::forward(args)...); - } - } -} - -template -[[nodiscard]] T getWithTimeout(std::future &future, - std::chrono::milliseconds timeout) { - static_assert(!std::is_void_v, "T must not be void"); - - if (future.wait_for(timeout) == std::future_status::ready) { - return future.get(); - } else { - throw std::runtime_error( - "Timeout occurred while waiting for future result"); - } -} - -template -[[nodiscard]] T getWithTimeout(std::future &future, - std::chrono::duration timeout) { - static_assert(!std::is_void_v, "T must not be void"); - - if (future.wait_for(timeout) == std::future_status::ready) { - return future.get(); - } else { - throw std::runtime_error( - "Timeout occurred while waiting for future result"); - } -} -} // namespace atom::async - -#endif diff --git a/src/atom/async/daemon.cpp b/src/atom/async/daemon.cpp index ba09787b..ebe20a19 100644 --- a/src/atom/async/daemon.cpp +++ b/src/atom/async/daemon.cpp @@ -19,6 +19,7 @@ still some problems on Windows, especially the console. #include #include #include +#include "macro.hpp" #ifndef _WIN32 #include @@ -28,16 +29,16 @@ still some problems on Windows, especially the console. #include "atom/utils/time.hpp" // 定义 g_daemonRestartInterval 变量 -int g_daemonRestartInterval = 10; +int gDaemonRestartInterval = 10; // 定义 g_pidFilePath 变量 -std::string g_pidFilePath = "lithium-daemon"; +std::string gPidFilePath = "lithium-daemon"; // 定义 g_isDaemon 变量 -bool g_isDaemon = false; +bool gIsDaemon = false; namespace atom::async { -std::string DaemonGuard::ToString() const { +auto DaemonGuard::toString() const -> std::string { std::stringstream ss; ss << "[DaemonGuard parentId=" << m_parentId << " mainId=" << m_mainId << " parentStartTime=" << utils::timeStampToString(m_parentStartTime) @@ -46,8 +47,9 @@ std::string DaemonGuard::ToString() const { return ss.str(); } -int DaemonGuard::RealStart(int argc, char **argv, - std::function mainCb) { +auto DaemonGuard::realStart( + int argc, char **argv, + const std::function &mainCb) -> int { #ifdef _WIN32 m_mainId = reinterpret_cast(getpid()); #else @@ -57,8 +59,9 @@ int DaemonGuard::RealStart(int argc, char **argv, return mainCb(argc, argv); } -int DaemonGuard::RealDaemon(int argc, char **argv, - std::function mainCb) { +auto DaemonGuard::realDaemon( + int argc, char **argv, + const std::function &mainCb) -> int { #ifdef _WIN32 // 在 Windows 平台下模拟守护进程 FreeConsole(); @@ -83,7 +86,7 @@ int DaemonGuard::RealDaemon(int argc, char **argv, // 等待一段时间后重新启动子进程 m_restartCount++; - Sleep(g_daemonRestartInterval * 1000); + Sleep(gDaemonRestartInterval * 1000); } #else if (daemon(1, 0) == -1) { @@ -100,41 +103,42 @@ int DaemonGuard::RealDaemon(int argc, char **argv, m_mainStartTime = time(0); LOG_F(INFO, "daemon process start pid={}", reinterpret_cast(getpid())); - return RealStart(argc, argv, mainCb); - } else if (pid < 0) { // 创建子进程失败 + return realStart(argc, argv, mainCb); + } + if (pid < 0) { // 创建子进程失败 LOG_F(ERROR, "fork fail return={} errno={} errstr={}", pid, errno, strerror(errno)); return -1; - } else { // 父进程 - int status = 0; - waitpid(pid, &status, 0); // 等待子进程退出 - - // 子进程异常退出 - if (status) { - if (status == 9) { // SIGKILL 信号杀死子进程,不需要重新启动 - LOG_F(INFO, "daemon process killed pid={}", getpid()); - break; - } else { // 记录日志并重新启动子进程 - LOG_F(ERROR, "child crash pid={} status={}", pid, status); - } - } else { // 正常退出,直接退出程序 - LOG_F(INFO, "daemon process exit pid={}", getpid()); + } // 父进程 + int status = 0; + waitpid(pid, &status, 0); // 等待子进程退出 + + // 子进程异常退出 + if (status != 0) { + if (status == 9) { // SIGKILL 信号杀死子进程,不需要重新启动 + LOG_F(INFO, "daemon process killed pid={}", getpid()); break; - } + } // 记录日志并重新启动子进程 + LOG_F(ERROR, "child crash pid={} status={}", pid, status); - // 等待一段时间后重新启动子进程 - m_restartCount++; - sleep(g_daemonRestartInterval); + } else { // 正常退出,直接退出程序 + LOG_F(INFO, "daemon process exit pid={}", getpid()); + break; } + + // 等待一段时间后重新启动子进程 + m_restartCount++; + sleep(gDaemonRestartInterval); } #endif return 0; } // 启动进程,如果需要创建守护进程,则先创建守护进程 -int DaemonGuard::StartDaemon(int argc, char **argv, - std::function mainCb, - bool isDaemon) { +auto DaemonGuard::startDaemon( + int argc, char **argv, + const std::function &mainCb, + bool isDaemon) -> int { #ifdef _WIN32 if (isDaemon) { AllocConsole(); @@ -150,32 +154,30 @@ int DaemonGuard::StartDaemon(int argc, char **argv, m_parentId = getpid(); #endif m_parentStartTime = time(0); - return RealStart(argc, argv, mainCb); - } else { // 创建守护进程 - return RealDaemon(argc, argv, mainCb); + return realStart(argc, argv, mainCb); } + // 创建守护进程 + return realDaemon(argc, argv, mainCb); } -// 信号处理函数,用于在程序退出时删除 PID 文件 -void SignalHandler(int signum) { +void signalHandler(int signum) { #ifdef _WIN32 if (signum == SIGTERM || signum == SIGINT) { - remove(g_pidFilePath.c_str()); + remove(gPidFilePath.c_str()); exit(0); } #else if (signum == SIGTERM || signum == SIGINT) { - remove(g_pidFilePath.c_str()); + ATOM_UNREF_PARAM(remove(gPidFilePath.c_str())); exit(0); } #endif } -// 写入 PID 文件 -void WritePidFile() { - std::ofstream ofs(g_pidFilePath); +void writePidFile() { + std::ofstream ofs(gPidFilePath); if (!ofs) { - // LOG_F(ERROR, "open pid file {} failed", g_pidFilePath); + LOG_F(ERROR, "open pid file {} failed", gPidFilePath); exit(-1); } ofs << getpid(); @@ -183,26 +185,23 @@ void WritePidFile() { } // 检查 PID 文件是否存在,并检查文件中的 PID 是否有效 -bool CheckPidFile() { +auto checkPidFile() -> bool { #ifdef _WIN32 // Windows 平台下不检查 PID 文件是否存在以及文件中的 PID 是否有效 return false; #else - struct stat st; - if (stat(g_pidFilePath.c_str(), &st) != 0) { + struct stat st {}; + if (stat(gPidFilePath.c_str(), &st) != 0) { return false; } - std::ifstream ifs(g_pidFilePath); + std::ifstream ifs(gPidFilePath); if (!ifs) { return false; } pid_t pid = -1; ifs >> pid; ifs.close(); - if (kill(pid, 0) == -1 && errno == ESRCH) { - return false; - } - return true; + return kill(pid, 0) != -1 || errno != ESRCH; #endif } } // namespace atom::async diff --git a/src/atom/async/daemon.hpp b/src/atom/async/daemon.hpp index 8c0b0937..fda4c2f8 100644 --- a/src/atom/async/daemon.hpp +++ b/src/atom/async/daemon.hpp @@ -19,14 +19,11 @@ Description: Daemon process implementation #include #include #include -#include #include -#include #ifdef _WIN32 #include #else -#include #include #include #include @@ -39,14 +36,14 @@ class DaemonGuard { /** * @brief Default constructor. */ - DaemonGuard() {} + DaemonGuard() = default; /** * @brief Converts process information to a string. * * @return The process information as a string. */ - std::string ToString() const; + [[nodiscard]] auto toString() const -> std::string; /** * @brief Starts a child process to execute the actual task. @@ -57,8 +54,9 @@ class DaemonGuard { * process. * @return The return value of the main callback function. */ - int RealStart(int argc, char **argv, - std::function mainCb); + auto realStart(int argc, char **argv, + const std::function &mainCb) + -> int; /** * @brief Starts a child process to execute the actual task. @@ -69,8 +67,9 @@ class DaemonGuard { * process. * @return The return value of the main callback function. */ - int RealDaemon(int argc, char **argv, - std::function mainCb); + auto realDaemon(int argc, char **argv, + const std::function &mainCb) + -> int; /** * @brief Starts the process. If a daemon process needs to be created, it @@ -82,9 +81,9 @@ class DaemonGuard { * @param isDaemon Determines if a daemon process should be created. * @return The return value of the main callback function. */ - int StartDaemon(int argc, char **argv, - std::function mainCb, - bool isDaemon); + auto startDaemon(int argc, char **argv, + const std::function &mainCb, + bool isDaemon) -> int; private: #ifdef _WIN32 @@ -104,19 +103,19 @@ class DaemonGuard { * * @param signum The signal number. */ -void SignalHandler(int signum); +void signalHandler(int signum); /** * @brief Writes the process ID to a file. */ -void WritePidFile(); +void writePidFile(); /** * @brief Checks if the process ID file exists. * * @return True if the process ID file exists, false otherwise. */ -bool CheckPidFile(); +auto checkPidFile() -> bool; } // namespace atom::async diff --git a/src/atom/async/eventloop.c b/src/atom/async/eventloop.c new file mode 100644 index 00000000..b63fd7a5 --- /dev/null +++ b/src/atom/async/eventloop.c @@ -0,0 +1,498 @@ +#include "eventloop.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Generic functions for timing and high-resolution timing +static inline int64_t get_time_in_ns(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec * 1000000000L + ts.tv_nsec; +} + +static inline double get_time_in_ms(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (double)ts.tv_sec * 1000.0 + (double)ts.tv_nsec / 1000000.0; +} + +// Thread-safe atomic counters +static atomic_int callback_counter = 0; +static atomic_int timer_counter = 0; +static atomic_int workproc_counter = 0; + +/* Structures to hold callback, timer, and work procedure information */ +typedef struct Callback { + atomic_bool in_use; /* flag to mark this record is active */ + int fd; /* file descriptor to watch for read */ + void *ud; /* user's data handle */ + CBF *fp; /* callback function */ +} Callback; + +static Callback *callbacks = NULL; +static int ncback = 0; /* number of entries in callbacks[] */ +static int ncbinuse = 0; /* number of entries in callbacks[] marked in_use */ + +/* Timers and work procedures handled similarly */ +typedef struct TimerFunction { + double trigger_time; /* trigger time, ms from epoch */ + int interval; /* repeat timer if interval > 0, ms */ + void *ud; /* user's data handle */ + TCF *fp; /* timer function */ + int tid; /* unique id for this timer */ + struct TimerFunction *next; +} TimerFunction; + +static TimerFunction *timers = NULL; /* linked list of timer functions */ +static int tid_counter = 0; /* unique timer ID source */ + +typedef struct WorkProcedure { + atomic_bool in_use; /* flag to mark this record is active */ + void *ud; /* user's data handle */ + WPF *fp; /* work procedure function */ +} WorkProcedure; + +static WorkProcedure *work_procs = NULL; +static int nwproc = 0; +static int nwpinuse = 0; + +/* Function prototypes */ +static void run_work_procs(void); +static void call_callback(fd_set *rfdp); +static void check_timers(void); +static void event_loop_iteration(void); +static void run_immediates(void); +static void insert_timer(TimerFunction *node); +static TimerFunction *find_timer(int tid); + +/* Infinite loop to dispatch callbacks, work procs, and timers as necessary. */ +void eventLoop(void) { + while (true) { + event_loop_iteration(); + } +} + +/* Timer callback used to implement deferLoop and deferLoop0. + * The argument is a pointer to an int which we set to 1 when the timer fires. + */ +static void deferTO(void *p) { *(int *)p = 1; } + +/* Allow other timers/callbacks/workprocs to run until time out in maxms + * or *flagp becomes non-zero. Wait forever if maxms is 0. + * Return 0 if flag flipped, else -1 if it timed out without flipping. + */ +int deferLoop(int maxms, int *flagp) { + int timeout_flag = 0; + int timer_id = maxms > 0 ? addTimer(maxms, deferTO, &timeout_flag) : 0; + + while (!*flagp) { + event_loop_iteration(); + if (timeout_flag) { + return -1; // Timer expired + } + } + + if (timer_id) { + rmTimer( + timer_id); // Cancel the timer if the flag was set before timeout + } + return 0; +} + +/* Allow other timers/callbacks/workprocs to run until time out in maxms + * or *flagp becomes zero. Wait forever if maxms is 0. + * Return 0 if flag flipped, else -1 if it timed out without flipping. + */ +int deferLoop0(int maxms, int *flagp) { + int timeout_flag = 0; + int timer_id = maxms > 0 ? addTimer(maxms, deferTO, &timeout_flag) : 0; + + while (*flagp) { + event_loop_iteration(); + if (timeout_flag) { + return -1; // Timer expired + } + } + + if (timer_id) { + rmTimer( + timer_id); // Cancel the timer if the flag was set before timeout + } + return 0; +} + +/* Add a callback */ +int addCallback(int fd, CBF *fp, void *ud) { + atomic_fetch_add_explicit(&callback_counter, 1, memory_order_relaxed); + callbacks = realloc(callbacks, callback_counter * sizeof(Callback)); + if (!callbacks) { + perror(realloc); + exit(EXIT_FAILURE); + } + + Callback *cb = &callbacks[callback_counter - 1]; + atomic_store(&cb->in_use, true); + cb->fd = fd; + cb->fp = fp; + cb->ud = ud; + + atomic_fetch_add_explicit(&ncbinuse, 1, memory_order_relaxed); + return callback_counter - 1; +} + +/* Remove a callback */ +void rmCallback(int cid) { + if (cid < 0 || cid >= callback_counter) { + return; + } + + Callback *cb = &callbacks[cid]; + if (atomic_load(&cb->in_use)) { + atomic_store(&cb->in_use, false); + atomic_fetch_sub_explicit(&ncbinuse, 1, memory_order_relaxed); + } +} + +/* Insert timer in sorted order */ +static void insert_timer(TimerFunction *node) { + TimerFunction **it = &timers; + while (*it && (*it)->trigger_time < node->trigger_time) { + it = &(*it)->next; + } + node->next = *it; + *it = node; +} + +/* Add a new timer */ +static int add_timer_impl(int delay, int interval, TCF *fp, void *ud) { + TimerFunction *node = malloc(sizeof(TimerFunction)); + if (!node) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + node->trigger_time = get_time_in_ms() + delay; + node->interval = interval; + node->fp = fp; + node->ud = ud; + node->tid = ++tid_counter; + + insert_timer(node); + + return node->tid; +} + +int addTimer(int ms, TCF *fp, void *ud) { + return add_timer_impl(ms, 0, fp, ud); +} + +int addPeriodicTimer(int ms, TCF *fp, void *ud) { + return add_timer_impl(ms, ms, fp, ud); +} + +/* Remove a timer */ +void rmTimer(int tid) { + TimerFunction **it = &timers; + while (*it && (*it)->tid != tid) { + it = &(*it)->next; + } + if (*it) { + TimerFunction *node = *it; + *it = node->next; + free(node); + } +} + +/* Return remaining time in ms for a timer */ +int remainingTimer(int tid) { + TimerFunction *it = find_timer(tid); + if (!it) { + return -1; + } + return (int)(it->trigger_time - get_time_in_ms()); +} + +/* Return remaining time in ns for a timer */ +int64_t nsecRemainingTimer(int tid) { + TimerFunction *it = find_timer(tid); + if (!it) { + return -1; + } + return (int64_t)((it->trigger_time - get_time_in_ms()) * 1000000); +} + +/* Add a new work procedure */ +int addWorkProc(WPF *fp, void *ud) { + atomic_fetch_add_explicit(&workproc_counter, 1, memory_order_relaxed); + work_procs = realloc(work_procs, workproc_counter * sizeof(WorkProcedure)); + if (!work_procs) { + perror("realloc"); + exit(EXIT_FAILURE); + } + + WorkProcedure *wp = &work_procs[workproc_counter - 1]; + atomic_store(&wp->in_use, true); + wp->fp = fp; + wp->ud = ud; + + atomic_fetch_add_explicit(&nwpinuse, 1, memory_order_relaxed); + return workproc_counter - 1; +} + +/* Remove a work procedure */ +void rmWorkProc(int wid) { + if (wid < 0 || wid >= workproc_counter) { + return; + } + + WorkProcedure *wp = &work_procs[wid]; + if (atomic_load(&wp->in_use)) { + atomic_store(&wp->in_use, false); + atomic_fetch_sub_explicit(&nwpinuse, 1, memory_order_relaxed); + } +} + +/* Execute next work procedure */ +static void run_work_procs(void) { + for (int i = 0; i < workproc_counter; i++) { + WorkProcedure *wp = &work_procs[i]; + if (atomic_load(&wp->in_use)) { + wp->fp(wp->ud); + } + } +} + +/* Call callback functions whose file descriptors are ready */ +static void call_callback(fd_set *rfdp) { + for (int i = 0; i < callback_counter; i++) { + Callback *cb = &callbacks[i]; + if (atomic_load(&cb->in_use) && FD_ISSET(cb->fd, rfdp)) { + cb->fp(cb->fd, cb->ud); + } + } +} + +/* Check and trigger timers */ +static void check_timers(void) { + double now = get_time_in_ms(); + while (timers && timers->trigger_time <= now) { + TimerFunction *node = timers; + node->fp(node->ud); + + if (node->interval > 0) { + node->trigger_time += node->interval; + TimerFunction *next_node = node->next; + insert_timer(node); + timers = next_node; + } else { + timers = node->next; + free(node); + } + } +} + +/* Main event loop iteration */ +static void event_loop_iteration(void) { + struct timeval tv; + fd_set rfd; + int maxfd = -1; + + FD_ZERO(&rfd); + for (int i = 0; i < callback_counter; i++) { + Callback *cb = &callbacks[i]; + if (atomic_load(&cb->in_use)) { + FD_SET(cb->fd, &rfd); + if (cb->fd > maxfd) { + maxfd = cb->fd; + } + } + } + + if (timers) { + double next_timer_ms = timers->trigger_time - get_time_in_ms(); + if (next_timer_ms < 0) + next_timer_ms = 0; + tv.tv_sec = (time_t)(next_timer_ms / 1000); + tv.tv_usec = (long)((next_timer_ms - tv.tv_sec * 1000) * 1000); + } else { + tv.tv_sec = 1; + tv.tv_usec = 0; + } + + int ns = select(maxfd + 1, &rfd, NULL, NULL, timers ? &tv : NULL); + if (ns < 0) { + perror("select"); + return; + } + + check_timers(); + if (ns > 0) { + call_callback(&rfd); + } + + run_work_procs(); + run_immediates(); +} + +/* Immediate work management */ +typedef struct ImmediateWork { + void *ud; + TCF *fp; + struct ImmediateWork *next; +} ImmediateWork; + +static ImmediateWork *immediates = NULL; + +void addImmediateWork(TCF *fp, void *ud) { + ImmediateWork *work = malloc(sizeof(ImmediateWork)); + if (!work) { + perror("malloc"); + exit(EXIT_FAILURE); + } + work->fp = fp; + work->ud = ud; + work->next = immediates; + immediates = work; +} + +static void run_immediates(void) { + while (immediates) { + ImmediateWork *work = immediates; + immediates = work->next; + work->fp(work->ud); + free(work); + } +} + +static TimerFunction *find_timer(int tid) { + TimerFunction *node = timers; + while (node && node->tid != tid) { + node = node->next; + } + return node; +} + +/* "INDI" wrappers for eventloop functions */ +typedef void(IE_CBF)(int readfiledes, void *userpointer); +typedef void(IE_TCF)(void *userpointer); +typedef void(IE_WPF)(void *userpointer); + +int IEAddCallback(int readfiledes, IE_CBF *fp, void *p) { + return addCallback(readfiledes, (CBF *)fp, p); +} + +void IERmCallback(int callbackid) { rmCallback(callbackid); } + +int IEAddTimer(int millisecs, IE_TCF *fp, void *p) { + return addTimer(millisecs, (TCF *)fp, p); +} + +int IEAddPeriodicTimer(int millisecs, IE_TCF *fp, void *p) { + return addPeriodicTimer(millisecs, (TCF *)fp, p); +} + +int IERemainingTimer(int timerid) { return remainingTimer(timerid); } + +int64_t IENSecsRemainingTimer(int timerid) { + return nsecRemainingTimer(timerid); +} + +void IERmTimer(int timerid) { rmTimer(timerid); } + +int IEAddWorkProc(IE_WPF *fp, void *p) { return addWorkProc((WPF *)fp, p); } + +void IERmWorkProc(int workprocid) { rmWorkProc(workprocid); } + +int IEDeferLoop(int maxms, int *flagp) { return deferLoop(maxms, flagp); } + +int IEDeferLoop0(int maxms, int *flagp) { return deferLoop0(maxms, flagp); } + +#if ENABLE_DEBUG +#include +#include + +int mycid; +int mywid; +int mytid; + +int user_a; +int user_b; +int counter; + +void wp(void *ud) { + struct timeval tv; + + gettimeofday(&tv, NULL); + printf("workproc @ %ld.%03ld %d %d\n", (long)tv.tv_sec, + (long)tv.tv_usec / 1000, counter, ++(*(int *)ud)); +} + +void to(void *ud) { printf("timeout %d\n", (int)ud); } + +void stdinCB(int fd, void *ud) { + char c; + + if (read(fd, &c, 1) != 1) { + perror("read"); + return; + } + + switch (c) { + case '+': + counter++; + break; + case '-': + counter--; + break; + + case 'W': + mywid = addWorkProc(wp, &user_b); + break; + case 'w': + rmWorkProc(mywid); + break; + + case 'c': + rmCallback(mycid); + break; + + case 't': + rmTimer(mytid); + break; + case '1': + mytid = addTimer(1000, to, (void *)1); + break; + case '2': + mytid = addTimer(2000, to, (void *)2); + break; + case '3': + mytid = addTimer(3000, to, (void *)3); + break; + case '4': + mytid = addTimer(4000, to, (void *)4); + break; + case '5': + mytid = addTimer(5000, to, (void *)5); + break; + default: + return; /* silently absorb other chars like \n */ + } + + printf("callback: %d\n", ++(*(int *)ud)); +} + +int main(int ac, char *av[]) { + (void)addCallback(0, stdinCB, &user_a); + eventLoop(); + exit(0); +} +#endif diff --git a/src/atom/async/eventloop.h b/src/atom/async/eventloop.h new file mode 100644 index 00000000..93809f5a --- /dev/null +++ b/src/atom/async/eventloop.h @@ -0,0 +1,127 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// Platform-specific includes for Windows +#ifdef _WIN32 +#include +#endif + +/** \typedef CBF + \brief Signature of a callback function. +*/ +typedef void(CBF)(int fd, void *); + +/** \typedef WPF + \brief Signature of a work procedure function. +*/ +typedef void(WPF)(void *); + +/** \typedef TCF + \brief Signature of a timer function. +*/ +typedef void(TCF)(void *); + +#ifdef __cplusplus +extern "C" { +#endif + +/** \fn void eventLoop() + \brief Main calls this when ready to hand over control. +*/ +extern void eventLoop(); + +/** Register a new callback, \e fp, to be called with \e ud as argument when \e + * fd is ready. + * + * \param fd file descriptor. + * \param fp a pointer to the callback function. + * \param ud a pointer to be passed to the callback function when called. + * \return a unique callback id for use with rmCallback(). + */ +extern int addCallback(int fd, CBF *fp, void *ud); + +/** Remove a callback function. + * + * \param cid the callback ID returned from addCallback(). + */ +extern void rmCallback(int cid); + +/** Add a new work procedure, fp, to be called with ud when nothing else to do. + * + * \param fp a pointer to the work procedure callback function. + * \param ud a pointer to be passed to the callback function when called. + * \return a unique id for use with rmWorkProc(). + */ +extern int addWorkProc(WPF *fp, void *ud); + +/** Remove the work procedure with the given \e id, as returned from + * addWorkProc(). + * + * \param wid the work procedure callback ID returned from addWorkProc(). + */ +extern void rmWorkProc(int wid); + +/** Register a new single-shot timer function, \e fp, to be called with \e ud as + * argument after \e ms. + * + * \param ms timer period in milliseconds. + * \param fp a pointer to the callback function. + * \param ud a pointer to be passed to the callback function when called. + * \return a unique id for use with rmTimer(). + */ +extern int addTimer(int ms, TCF *fp, void *ud); + +/** Register a new periodic timer function, \e fp, to be called with \e ud as + * argument after \e ms. + * + * \param ms timer period in milliseconds. + * \param fp a pointer to the callback function. + * \param ud a pointer to be passed to the callback function when called. + * \return a unique id for use with rmTimer(). + */ +extern int addPeriodicTimer(int ms, TCF *fp, void *ud); + +/** Returns the timer's remaining value in milliseconds left until the timeout. + * + * \param tid the timer callback ID returned from addTimer() or + * addPeriodicTimer() \return If the timer not exists, the returned value will + * be -1. + */ +extern int remainingTimer(int tid); + +/** Returns the timer's remaining value in nanoseconds left until the timeout. + * + * \param tid the timer callback ID returned from addTimer() or + * addPeriodicTimer() \return If the timer not exists, the returned value will + * be -1. + */ +extern int64_t nsecRemainingTimer(int tid); + +/** Remove the timer with the given \e id, as returned from addTimer() or + * addPeriodicTimer(). + * + * \param tid the timer callback ID returned from addTimer() or + * addPeriodicTimer。 + */ +extern void rmTimer(int tid); + +/** Register a given function to be called once after the current loop + * \param fp a pointer to the callback function. + * \param ud a pointer to be passed to the callback function when called. + */ +extern void addImmediateWork(TCF *fp, void *ud); + +/* utility functions */ +extern int deferLoop(int maxms, int *flagp); +extern int deferLoop0(int maxms, int *flagp); + +#ifdef __cplusplus +} +#endif diff --git a/src/atom/async/eventstack.hpp b/src/atom/async/eventstack.hpp index 885eeef3..710f901f 100644 --- a/src/atom/async/eventstack.hpp +++ b/src/atom/async/eventstack.hpp @@ -12,19 +12,19 @@ Description: A thread-safe stack data structure for managing events. **************************************************/ -#ifndef ATOM_SERVER_EVENTSTACK_HPP -#define ATOM_SERVER_EVENTSTACK_HPP +#ifndef ATOM_ASYNC_EVENTSTACK_HPP +#define ATOM_ASYNC_EVENTSTACK_HPP #include #include #include -#include #include #include #include #include #include +namespace atom::async { /** * @brief A thread-safe stack data structure for managing events. * @@ -33,8 +33,8 @@ Description: A thread-safe stack data structure for managing events. template class EventStack { public: - EventStack(); /**< Constructor. */ - ~EventStack(); /**< Destructor. */ + EventStack() = default; + ~EventStack() = default; /** * @brief Pushes an event onto the stack. @@ -48,26 +48,28 @@ class EventStack { * * @return The popped event, or std::nullopt if the stack is empty. */ - std::optional popEvent(); + auto popEvent() -> std::optional; +#if ENABLE_DEBUG /** * @brief Prints all events in the stack. */ void printEvents() const; +#endif /** * @brief Checks if the stack is empty. * * @return true if the stack is empty, false otherwise. */ - bool isEmpty() const; + auto isEmpty() const -> bool; /** * @brief Returns the number of events in the stack. * * @return The number of events. */ - size_t size() const; + auto size() const -> size_t; /** * @brief Clears all events from the stack. @@ -79,14 +81,14 @@ class EventStack { * * @return The top event, or std::nullopt if the stack is empty. */ - std::optional peekTopEvent() const; + auto peekTopEvent() const -> std::optional; /** * @brief Copies the current stack. * * @return A copy of the stack. */ - EventStack copyStack() const; + auto copyStack() const -> EventStack; /** * @brief Filters events based on a custom filter function. @@ -100,7 +102,7 @@ class EventStack { * * @return The serialized stack. */ - std::string serializeStack() const; + auto serializeStack() const -> std::string; /** * @brief Deserializes a string into the stack. @@ -133,7 +135,7 @@ class EventStack { * @param predicate The predicate function. * @return The count of events satisfying the predicate. */ - size_t countEvents(std::function predicate) const; + auto countEvents(std::function predicate) const -> size_t; /** * @brief Finds the first event that satisfies a predicate. @@ -142,7 +144,8 @@ class EventStack { * @return The first event satisfying the predicate, or std::nullopt if not * found. */ - std::optional findEvent(std::function predicate) const; + auto findEvent(std::function predicate) const + -> std::optional; /** * @brief Checks if any event in the stack satisfies a predicate. @@ -150,7 +153,7 @@ class EventStack { * @param predicate The predicate function. * @return true if any event satisfies the predicate, false otherwise. */ - bool anyEvent(std::function predicate) const; + auto anyEvent(std::function predicate) const -> bool; /** * @brief Checks if all events in the stack satisfy a predicate. @@ -158,14 +161,167 @@ class EventStack { * @param predicate The predicate function. * @return true if all events satisfy the predicate, false otherwise. */ - bool allEvents(std::function predicate) const; + auto allEvents(std::function predicate) const -> bool; private: - std::vector events; /**< Vector to store events. */ - mutable std::shared_mutex mtx; /**< Mutex for thread safety. */ - std::atomic eventCount{0}; /**< Atomic counter for event count. */ + std::vector events_; /**< Vector to store events. */ + mutable std::shared_mutex mtx_; /**< Mutex for thread safety. */ + std::atomic eventCount_{0}; /**< Atomic counter for event count. */ }; -#include "eventstack.inl" +template +void EventStack::pushEvent(T event) { + std::unique_lock lock(mtx_); + events_.push_back(std::move(event)); + ++eventCount_; +} + +template +auto EventStack::popEvent() -> std::optional { + std::unique_lock lock(mtx_); + if (!events_.empty()) { + T event = std::move(events_.back()); + events_.pop_back(); + --eventCount_; + return event; + } + return std::nullopt; +} + +#if ENABLE_DEBUG +template +void EventStack::printEvents() const { + std::shared_lock lock(mtx_); + std::cout << "Events in stack:" << std::endl; + for (const T& event : events) { + std::cout << event << std::endl; + } +} +#endif + +template +auto EventStack::isEmpty() const -> bool { + std::shared_lock lock(mtx_); + return events_.empty(); +} + +template +auto EventStack::size() const -> size_t { + return eventCount_.load(); +} + +template +void EventStack::clearEvents() { + std::unique_lock lock(mtx_); + events_.clear(); + eventCount_.store(0); +} + +template +auto EventStack::peekTopEvent() const -> std::optional { + std::shared_lock lock(mtx_); + if (!events_.empty()) { + return events_.back(); + } + return std::nullopt; +} + +template +auto EventStack::copyStack() const -> EventStack { + std::shared_lock lock(mtx_); + EventStack newStack; + newStack.events = events_; + newStack.eventCount_.store(eventCount_.load()); + return newStack; +} + +template +void EventStack::filterEvents(std::function filterFunc) { + std::unique_lock lock(mtx_); + events_.erase( + std::remove_if(events_.begin(), events_.end(), + [&](const T& event) { return !filterFunc(event); }), + events_.end()); + eventCount_.store(events_.size()); +} + +template +auto EventStack::serializeStack() const -> std::string { + std::shared_lock lock(mtx_); + std::string serializedStack; + for (const T& event : events_) { + serializedStack += event + ";"; + } + return serializedStack; +} + +template +void EventStack::deserializeStack(std::string_view serializedData) { + std::unique_lock lock(mtx_); + events_.clear(); + size_t pos = 0; + size_t nextPos = 0; + while ((nextPos = serializedData.find(';', pos)) != + std::string_view::npos) { + T event = serializedData.substr(pos, nextPos - pos); + events_.push_back(std::move(event)); + pos = nextPos + 1; + } + eventCount_.store(events_.size()); +} + +template +void EventStack::removeDuplicates() { + std::unique_lock lock(mtx_); + std::sort(events_.begin(), events_.end()); + events_.erase(std::unique(events_.begin(), events_.end()), events_.end()); + eventCount_.store(events_.size()); +} + +template +void EventStack::sortEvents( + std::function compareFunc) { + std::unique_lock lock(mtx_); + std::sort(events_.begin(), events_.end(), compareFunc); +} + +template +void EventStack::reverseEvents() { + std::unique_lock lock(mtx_); + std::reverse(events_.begin(), events_.end()); +} + +template +auto EventStack::countEvents(std::function predicate) const + -> size_t { + std::shared_lock lock(mtx_); + return std::count_if(events_.begin(), events_.end(), predicate); +} + +template +auto EventStack::findEvent(std::function predicate) const + -> std::optional { + std::shared_lock lock(mtx_); + auto it = std::find_if(events_.begin(), events_.end(), predicate); + if (it != events_.end()) { + return *it; + } + return std::nullopt; +} + +template +auto EventStack::anyEvent(std::function predicate) const + -> bool { + std::shared_lock lock(mtx_); + return std::any_of(events_.begin(), events_.end(), predicate); +} + +template +auto EventStack::allEvents(std::function predicate) const + -> bool { + std::shared_lock lock(mtx_); + return std::all_of(events_.begin(), events_.end(), predicate); +} +} // namespace atom::async -#endif // ATOM_SERVER_EVENTSTACK_HPP +#endif // ATOM_ASYNC_EVENTSTACK_HPP diff --git a/src/atom/async/eventstack.inl b/src/atom/async/eventstack.inl deleted file mode 100644 index 5485c469..00000000 --- a/src/atom/async/eventstack.inl +++ /dev/null @@ -1,176 +0,0 @@ -/* - * eventstack_impl.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-3-26 - -Description: A thread-safe stack data structure for managing events. - -**************************************************/ - -#ifndef ATOM_SERVER_EVENTSTACK_INL -#define ATOM_SERVER_EVENTSTACK_INL - -#include "eventstack.hpp" - -template -EventStack::EventStack() {} - -template -EventStack::~EventStack() {} - -template -void EventStack::pushEvent(T event) { - std::unique_lock lock(mtx); - events.push_back(std::move(event)); - ++eventCount; -} - -template -std::optional EventStack::popEvent() { - std::unique_lock lock(mtx); - if (!events.empty()) { - T event = std::move(events.back()); - events.pop_back(); - --eventCount; - return event; - } - return std::nullopt; -} - -template -void EventStack::printEvents() const { - std::shared_lock lock(mtx); - std::cout << "Events in stack:" << std::endl; - for (const T& event : events) { - std::cout << event << std::endl; - } -} - -template -bool EventStack::isEmpty() const { - std::shared_lock lock(mtx); - return events.empty(); -} - -template -size_t EventStack::size() const { - return eventCount.load(); -} - -template -void EventStack::clearEvents() { - std::unique_lock lock(mtx); - events.clear(); - eventCount.store(0); -} - -template -std::optional EventStack::peekTopEvent() const { - std::shared_lock lock(mtx); - if (!events.empty()) { - return events.back(); - } - return std::nullopt; -} - -template -EventStack EventStack::copyStack() const { - std::shared_lock lock(mtx); - EventStack newStack; - newStack.events = events; - newStack.eventCount.store(eventCount.load()); - return newStack; -} - -template -void EventStack::filterEvents(std::function filterFunc) { - std::unique_lock lock(mtx); - events.erase( - std::remove_if(events.begin(), events.end(), - [&](const T& event) { return !filterFunc(event); }), - events.end()); - eventCount.store(events.size()); -} - -template -std::string EventStack::serializeStack() const { - std::shared_lock lock(mtx); - std::string serializedStack; - for (const T& event : events) { - serializedStack += event + ";"; - } - return serializedStack; -} - -template -void EventStack::deserializeStack(std::string_view serializedData) { - std::unique_lock lock(mtx); - events.clear(); - size_t pos = 0; - size_t nextPos = 0; - while ((nextPos = serializedData.find(";", pos)) != - std::string_view::npos) { - T event = serializedData.substr(pos, nextPos - pos); - events.push_back(std::move(event)); - pos = nextPos + 1; - } - eventCount.store(events.size()); -} - -template -void EventStack::removeDuplicates() { - std::unique_lock lock(mtx); - std::sort(events.begin(), events.end()); - events.erase(std::unique(events.begin(), events.end()), events.end()); - eventCount.store(events.size()); -} - -template -void EventStack::sortEvents( - std::function compareFunc) { - std::unique_lock lock(mtx); - std::sort(events.begin(), events.end(), compareFunc); -} - -template -void EventStack::reverseEvents() { - std::unique_lock lock(mtx); - std::reverse(events.begin(), events.end()); -} - -template -size_t EventStack::countEvents( - std::function predicate) const { - std::shared_lock lock(mtx); - return std::count_if(events.begin(), events.end(), predicate); -} - -template -std::optional EventStack::findEvent( - std::function predicate) const { - std::shared_lock lock(mtx); - auto it = std::find_if(events.begin(), events.end(), predicate); - if (it != events.end()) { - return *it; - } - return std::nullopt; -} - -template -bool EventStack::anyEvent(std::function predicate) const { - std::shared_lock lock(mtx); - return std::any_of(events.begin(), events.end(), predicate); -} - -template -bool EventStack::allEvents(std::function predicate) const { - std::shared_lock lock(mtx); - return std::all_of(events.begin(), events.end(), predicate); -} - -#endif diff --git a/src/atom/async/limiter.cpp b/src/atom/async/limiter.cpp new file mode 100644 index 00000000..d269ffc2 --- /dev/null +++ b/src/atom/async/limiter.cpp @@ -0,0 +1,234 @@ +#include "limiter.hpp" + +namespace atom::async { +RateLimiter::Settings::Settings(size_t max_requests, + std::chrono::seconds time_window) + : maxRequests(max_requests), timeWindow(time_window) {} + +// Implementation of RateLimiter constructor +RateLimiter::RateLimiter() {} + +// Implementation of Awaiter constructor +RateLimiter::Awaiter::Awaiter(RateLimiter& limiter, + const std::string& function_name) + : limiter_(limiter), function_name_(function_name) {} + +// Implementation of Awaiter::await_ready +auto RateLimiter::Awaiter::await_ready() -> bool { return false; } + +// Implementation of Awaiter::await_suspend +void RateLimiter::Awaiter::await_suspend(std::coroutine_handle<> handle) { + std::unique_lock lock(limiter_.mutex_); + auto& settings = limiter_.settings_[function_name_]; + limiter_.cleanup(function_name_, settings.timeWindow); + if (limiter_.paused_ || + limiter_.requests_[function_name_].size() >= settings.maxRequests) { + limiter_.waiters_[function_name_].emplace_back(handle); + limiter_.rejected_requests_[function_name_]++; + } else { + limiter_.requests_[function_name_].emplace_back( + std::chrono::steady_clock::now()); + lock.unlock(); + handle.resume(); + } +} + +// Implementation of Awaiter::await_resume +void RateLimiter::Awaiter::await_resume() {} + +// Implementation of RateLimiter::acquire +RateLimiter::Awaiter RateLimiter::acquire(const std::string& function_name) { + return Awaiter(*this, function_name); +} + +// Implementation of RateLimiter::setFunctionLimit +void RateLimiter::setFunctionLimit(const std::string& function_name, + size_t max_requests, + std::chrono::seconds time_window) { + std::unique_lock lock(mutex_); + settings_[function_name] = Settings(max_requests, time_window); +} + +// Implementation of RateLimiter::pause +void RateLimiter::pause() { + std::unique_lock lock(mutex_); + paused_ = true; +} + +// Implementation of RateLimiter::resume +void RateLimiter::resume() { + std::unique_lock lock(mutex_); + paused_ = false; + process_waiters(); +} + +// Implementation of RateLimiter::printLog +void RateLimiter::printLog() { +#if ENABLE_DEBUG + std::unique_lock lock(mutex_); + for (const auto& [function_name, timestamps] : log_) { + std::cout << "Request log for " << function_name << ":\n"; + for (const auto& timestamp : timestamps) { + std::cout << "Request at " << timestamp.time_since_epoch().count() + << std::endl; + } + } +#endif +} + +// Implementation of RateLimiter::get_rejected_requests +size_t RateLimiter::get_rejected_requests(const std::string& function_name) { + std::unique_lock lock(mutex_); + return rejected_requests_[function_name]; +} + +// Implementation of RateLimiter::cleanup +void RateLimiter::cleanup(const std::string& function_name, + const std::chrono::seconds& time_window) { + auto now = std::chrono::steady_clock::now(); + auto& reqs = requests_[function_name]; + while (!reqs.empty() && now - reqs.front() > time_window) { + reqs.pop_front(); + } +} + +// Implementation of RateLimiter::process_waiters +void RateLimiter::process_waiters() { + for (auto& [function_name, wait_queue] : waiters_) { + auto& settings = settings_[function_name]; + while (!wait_queue.empty() && + requests_[function_name].size() < settings.maxRequests) { + auto waiter = wait_queue.front(); + wait_queue.pop_front(); + requests_[function_name].emplace_back( + std::chrono::steady_clock::now()); + mutex_.unlock(); + waiter.resume(); + mutex_.lock(); + } + } +} + +Debounce::Debounce(std::function func, std::chrono::milliseconds delay, + bool leading, + std::optional maxWait) + : func_(std::move(func)), + delay_(delay), + leading_(leading), + maxWait_(maxWait) {} + +void Debounce::operator()() { + auto now = std::chrono::steady_clock::now(); + std::unique_lock lock(mutex_); + + if (leading_ && !scheduled_) { + scheduled_ = true; + func_(); + ++call_count_; + } + + last_call_ = now; + if (!thread_.joinable()) { + thread_ = std::jthread([this]() { this->run(); }); + } +} + +void Debounce::cancel() { + std::unique_lock lock(mutex_); + scheduled_ = false; + last_call_.reset(); +} + +void Debounce::flush() { + std::unique_lock lock(mutex_); + if (scheduled_) { + func_(); + ++call_count_; + scheduled_ = false; + } +} + +void Debounce::reset() { + std::unique_lock lock(mutex_); + last_call_.reset(); + scheduled_ = false; +} + +size_t Debounce::callCount() const { + std::unique_lock lock(mutex_); + return call_count_; +} + +void Debounce::run() { + while (true) { + std::this_thread::sleep_for(delay_); + std::unique_lock lock(mutex_); + auto now = std::chrono::steady_clock::now(); + if (last_call_ && now - last_call_.value() >= delay_) { + if (scheduled_) { + func_(); + ++call_count_; + scheduled_ = false; + } + return; + } + if (maxWait_ && now - last_call_.value() >= maxWait_) { + if (scheduled_) { + func_(); + ++call_count_; + scheduled_ = false; + } + return; + } + } +} + +Throttle::Throttle(std::function func, + std::chrono::milliseconds interval, bool leading, + std::optional maxWait) + : func_(std::move(func)), + interval_(interval), + last_call_(std::chrono::steady_clock::now() - interval), + leading_(leading), + maxWait_(maxWait) {} + +void Throttle::operator()() { + auto now = std::chrono::steady_clock::now(); + std::unique_lock lock(mutex_); + + if (leading_ && !called_) { + called_ = true; + func_(); + last_call_ = now; + ++call_count_; + return; + } + + if (now - last_call_ >= interval_) { + last_call_ = now; + func_(); + ++call_count_; + } else if (maxWait_ && (now - last_call_ >= maxWait_)) { + last_call_ = now; + func_(); + ++call_count_; + } +} + +void Throttle::cancel() { + std::unique_lock lock(mutex_); + called_ = false; +} + +void Throttle::reset() { + std::unique_lock lock(mutex_); + last_call_ = std::chrono::steady_clock::now() - interval_; + called_ = false; +} + +auto Throttle::callCount() const -> size_t { + std::unique_lock lock(mutex_); + return call_count_; +} + +} // namespace atom::async diff --git a/src/atom/async/limiter.hpp b/src/atom/async/limiter.hpp new file mode 100644 index 00000000..08481fb4 --- /dev/null +++ b/src/atom/async/limiter.hpp @@ -0,0 +1,329 @@ +#ifndef ATOM_ASYNC_LIMITER_HPP +#define ATOM_ASYNC_LIMITER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace atom::async { +/** + * @brief A rate limiter class to control the rate of function executions. + */ +class RateLimiter { +public: + /** + * @brief Settings for the rate limiter. + */ + struct Settings { + size_t maxRequests; ///< Maximum number of requests allowed in the time + ///< window. + std::chrono::seconds + timeWindow; ///< The time window in which maxRequests are allowed. + + /** + * @brief Constructor for Settings. + * @param max_requests Maximum number of requests. + * @param time_window Duration of the time window. + */ + explicit Settings( + size_t max_requests = 5, + std::chrono::seconds time_window = std::chrono::seconds(1)); + }; + + /** + * @brief Constructor for RateLimiter. + */ + RateLimiter(); + + /** + * @brief Awaiter class for handling coroutines. + */ + class Awaiter { + public: + /** + * @brief Constructor for Awaiter. + * @param limiter Reference to the rate limiter. + * @param function_name Name of the function to be rate-limited. + */ + Awaiter(RateLimiter& limiter, const std::string& function_name); + + /** + * @brief Checks if the awaiter is ready. + * @return Always returns false. + */ + auto await_ready() -> bool; + + /** + * @brief Suspends the coroutine. + * @param handle Coroutine handle. + */ + void await_suspend(std::coroutine_handle<> handle); + + /** + * @brief Resumes the coroutine. + */ + void await_resume(); + + private: + RateLimiter& limiter_; + std::string function_name_; + }; + + /** + * @brief Acquires the rate limiter for a specific function. + * @param function_name Name of the function to be rate-limited. + * @return An Awaiter object. + */ + Awaiter acquire(const std::string& function_name); + + /** + * @brief Sets the rate limit for a specific function. + * @param function_name Name of the function to be rate-limited. + * @param max_requests Maximum number of requests allowed. + * @param time_window Duration of the time window. + */ + void setFunctionLimit(const std::string& function_name, size_t max_requests, + std::chrono::seconds time_window); + + /** + * @brief Pauses the rate limiter. + */ + void pause(); + + /** + * @brief Resumes the rate limiter. + */ + void resume(); + + /** + * @brief Prints the log of requests. + */ + void printLog(); + + /** + * @brief Gets the number of rejected requests for a specific function. + * @param function_name Name of the function. + * @return Number of rejected requests. + */ + auto get_rejected_requests(const std::string& function_name) -> size_t; + +#if !defined(TEST_F) && !defined(TEST) +private: +#endif + /** + * @brief Cleans up old requests outside the time window. + * @param function_name Name of the function. + * @param time_window Duration of the time window. + */ + void cleanup(const std::string& function_name, + const std::chrono::seconds& time_window); + + /** + * @brief Processes waiting coroutines. + */ + void process_waiters(); + + std::unordered_map settings_; + std::unordered_map> + requests_; + std::unordered_map>> + waiters_; + std::unordered_map> + log_; + std::unordered_map rejected_requests_; + bool paused_ = false; + std::mutex mutex_; +}; + +/** + * @class Debounce + * @brief A class that implements a debouncing mechanism for function calls. + * + * The `Debounce` class ensures that the given function is not invoked more + * frequently than a specified delay interval. It postpones the function call + * until the delay has elapsed since the last call. If a new call occurs before + * the delay expires, the previous call is canceled and the delay starts over. + * This is useful for situations where you want to limit the rate of function + * invocations, such as handling user input events. + */ +class Debounce { +public: + /** + * @brief Constructs a Debounce object. + * + * @param func The function to be debounced. + * @param delay The time delay to wait before invoking the function. + * @param leading If true, the function will be invoked immediately on the + * first call and then debounced for subsequent calls. If false, the + * function will be debounced and invoked only after the delay has passed + * since the last call. + * @param maxWait Optional maximum wait time before invoking the function if + * it has been called frequently. If not provided, there is no maximum wait + * time. + */ + Debounce(std::function func, std::chrono::milliseconds delay, + bool leading = false, + std::optional maxWait = std::nullopt); + + /** + * @brief Invokes the debounced function if the delay has elapsed since the + * last call. + * + * This method schedules the function call if the delay period has passed + * since the last call. If the leading flag is set, the function will be + * called immediately on the first call. Subsequent calls will reset the + * delay timer. + */ + void operator()(); + + /** + * @brief Cancels any pending function calls. + * + * This method cancels any pending invocation of the function that is + * scheduled to occur based on the debouncing mechanism. + */ + void cancel(); + + /** + * @brief Immediately invokes the function if it is scheduled to be called. + * + * This method flushes any pending function calls, ensuring the function is + * called immediately. + */ + void flush(); + + /** + * @brief Resets the debouncer, clearing any pending function call and + * timer. + * + * This method resets the internal state of the debouncer, allowing it to + * start fresh and schedule new function calls based on the debounce delay. + */ + void reset(); + + /** + * @brief Returns the number of times the function has been invoked. + * + * @return The count of function invocations. + */ + size_t callCount() const; + +private: + /** + * @brief Runs the function in a separate thread after the debounce delay. + * + * This method is used internally to handle the scheduling and execution of + * the function after the specified delay. + */ + void run(); + + std::function func_; ///< The function to be debounced. + std::chrono::milliseconds + delay_; ///< The time delay before invoking the function. + std::optional + last_call_; ///< The timestamp of the last call. + std::jthread thread_; ///< A thread used to handle delayed function calls. + mutable std::mutex + mutex_; ///< Mutex to protect concurrent access to internal state. + bool leading_; ///< Indicates if the function should be called immediately + ///< upon the first call. + bool scheduled_ = + false; ///< Flag to track if the function is scheduled for execution. + std::optional + maxWait_; ///< Optional maximum wait time before invocation. + size_t + call_count_{}; ///< Counter to keep track of function call invocations. +}; + +/** + * @class Throttle + * @brief A class that provides throttling for function calls, ensuring they are + * not invoked more frequently than a specified interval. + * + * This class is useful for rate-limiting function calls. It ensures that the + * given function is not called more frequently than the specified interval. + * Additionally, it can be configured to either throttle function calls to be + * executed at most once per interval or to execute the function immediately + * upon the first call and then throttle subsequent calls. + */ +class Throttle { +public: + /** + * @brief Constructs a Throttle object. + * + * @param func The function to be throttled. + * @param interval The minimum time interval between calls to the function. + * @param leading If true, the function will be called immediately upon the + * first call, then throttled. If false, the function will be throttled and + * called at most once per interval. + * @param maxWait Optional maximum wait time before invoking the function if + * it has been called frequently. If not provided, there is no maximum wait + * time. + */ + Throttle(std::function func, std::chrono::milliseconds interval, + bool leading = false, + std::optional maxWait = std::nullopt); + + /** + * @brief Invokes the throttled function if the interval has elapsed. + * + * This method will check if enough time has passed since the last function + * call. If so, it will invoke the function and update the last call + * timestamp. If the function is being invoked immediately as per the + * leading configuration, it will be executed at once, and subsequent calls + * will be throttled. + */ + void operator()(); + + /** + * @brief Cancels any pending function calls. + * + * This method cancels any pending function invocations that are scheduled + * to occur based on the throttling mechanism. + */ + void cancel(); + + /** + * @brief Resets the throttle, clearing the last call timestamp and allowing + * the function to be invoked immediately if required. + * + * This method can be used to reset the throttle state, allowing the + * function to be called immediately if the leading flag is set or to reset + * the interval for subsequent function calls. + */ + void reset(); + + /** + * @brief Returns the number of times the function has been called. + * + * @return The count of function invocations. + */ + auto callCount() const -> size_t; + +private: + std::function func_; ///< The function to be throttled. + std::chrono::milliseconds + interval_; ///< The time interval between allowed function calls. + std::chrono::steady_clock::time_point + last_call_; ///< The timestamp of the last function call. + mutable std::mutex + mutex_; ///< Mutex to protect concurrent access to internal state. + bool leading_; ///< Indicates if the function should be called immediately + ///< upon first call. + bool called_ = false; ///< Flag to track if the function has been called. + std::optional + maxWait_; ///< Optional maximum wait time before invocation. + size_t + call_count_{}; ///< Counter to keep track of function call invocations. +}; + +} // namespace atom::async + +#endif diff --git a/src/atom/async/lock.cpp b/src/atom/async/lock.cpp index 7028c7a0..f03056ef 100644 --- a/src/atom/async/lock.cpp +++ b/src/atom/async/lock.cpp @@ -21,18 +21,22 @@ void Spinlock::lock() { } } +auto Spinlock::tryLock() -> bool { + return !flag_.test_and_set(std::memory_order_acquire); +} + void Spinlock::unlock() { flag_.clear(std::memory_order_release); } -uint64_t TicketSpinlock::lock() { - const auto ticket = ticket_.fetch_add(1, std::memory_order_acq_rel); - while (serving_.load(std::memory_order_acquire) != ticket) { +auto TicketSpinlock::lock() -> uint64_t { + const auto TICKET = ticket_.fetch_add(1, std::memory_order_acq_rel); + while (serving_.load(std::memory_order_acquire) != TICKET) { cpu_relax(); } - return ticket; + return TICKET; } -void TicketSpinlock::unlock(const uint64_t ticket) { - serving_.store(ticket + 1, std::memory_order_release); +void TicketSpinlock::unlock(uint64_t TICKET) { + serving_.store(TICKET + 1, std::memory_order_release); } void UnfairSpinlock::lock() { diff --git a/src/atom/async/lock.hpp b/src/atom/async/lock.hpp index 9647097f..a6073fb2 100644 --- a/src/atom/async/lock.hpp +++ b/src/atom/async/lock.hpp @@ -16,7 +16,8 @@ Description: Some useful spinlock implementations #define ATOM_ASYNC_LOCK_HPP #include -#include + +#include "atom/type/noncopyable.hpp" namespace atom::async { @@ -36,7 +37,7 @@ namespace atom::async { /** * @brief A simple spinlock implementation using atomic_flag. */ -class Spinlock { +class Spinlock : public NonCopyable { std::atomic_flag flag_ = ATOMIC_FLAG_INIT; public: @@ -45,16 +46,6 @@ class Spinlock { */ Spinlock() = default; - /** - * @brief Disables copy. - */ - Spinlock(const Spinlock &) = delete; - - /** - * @brief Disables copy. - */ - Spinlock &operator=(const Spinlock &) = delete; - /** * @brief Acquires the lock. */ @@ -64,25 +55,30 @@ class Spinlock { * @brief Releases the lock. */ void unlock(); + + /** + * @brief Tries to acquire the lock. + * + * @return true if the lock was acquired, false otherwise. + */ + auto tryLock() -> bool; }; /** * @brief A ticket spinlock implementation using atomic operations. */ -class TicketSpinlock { +class TicketSpinlock : public NonCopyable { std::atomic ticket_{0}; std::atomic serving_{0}; public: TicketSpinlock() = default; - TicketSpinlock(const TicketSpinlock &) = delete; - TicketSpinlock &operator=(const TicketSpinlock &) = delete; /** * @brief Lock guard for TicketSpinlock. */ class LockGuard { TicketSpinlock &spinlock_; - const uint64_t ticket_; + const uint64_t TICKET; public: /** @@ -91,12 +87,12 @@ class TicketSpinlock { * @param spinlock The TicketSpinlock to guard. */ explicit LockGuard(TicketSpinlock &spinlock) - : spinlock_(spinlock), ticket_(spinlock_.lock()) {} + : spinlock_(spinlock), TICKET(spinlock_.lock()) {} /** * @brief Destructs the lock guard and releases the lock. */ - ~LockGuard() { spinlock_.unlock(ticket_); } + ~LockGuard() { spinlock_.unlock(TICKET); } }; using scoped_lock = LockGuard; @@ -106,27 +102,24 @@ class TicketSpinlock { * * @return The acquired ticket number. */ - uint64_t lock(); + auto lock() -> uint64_t; /** * @brief Releases the lock given a specific ticket number. * * @param ticket The ticket number to release. */ - void unlock(const uint64_t ticket); + void unlock(uint64_t TICKET); }; /** * @brief An unfair spinlock implementation using atomic_flag. */ -class UnfairSpinlock { +class UnfairSpinlock : public NonCopyable { std::atomic_flag flag_ = ATOMIC_FLAG_INIT; public: UnfairSpinlock() = default; - UnfairSpinlock(const UnfairSpinlock &) = delete; - UnfairSpinlock &operator=(const UnfairSpinlock &) = delete; - /** * @brief Acquires the lock. */ @@ -150,7 +143,8 @@ class ScopedLock { public: /** - * @brief Constructs the scoped lock and acquires the lock on the provided mutex. + * @brief Constructs the scoped lock and acquires the lock on the provided + * mutex. * * @param mutex The mutex to lock. */ @@ -171,26 +165,24 @@ class ScopedLock { * @tparam Mutex Type of the spinlock (i.e., TicketSpinlock). */ template -class ScopedTicketLock { +class ScopedTicketLock : public NonCopyable { Mutex &mutex_; - const uint64_t ticket_; + const uint64_t TICKET; public: /** - * @brief Constructs the scoped lock and acquires the lock on the provided mutex. + * @brief Constructs the scoped lock and acquires the lock on the provided + * mutex. * * @param mutex The mutex to lock. */ explicit ScopedTicketLock(Mutex &mutex) - : mutex_(mutex), ticket_(mutex_.lock()) {} + : mutex_(mutex), TICKET(mutex_.lock()) {} /** * @brief Destructs the scoped lock and releases the lock. */ - ~ScopedTicketLock() { mutex_.unlock(ticket_); } - - ScopedTicketLock(const ScopedTicketLock &) = delete; - ScopedTicketLock &operator=(const ScopedTicketLock &) = delete; + ~ScopedTicketLock() { mutex_.unlock(TICKET); } }; /** @@ -199,12 +191,13 @@ class ScopedTicketLock { * @tparam Mutex Type of the spinlock (i.e., UnfairSpinlock). */ template -class ScopedUnfairLock { +class ScopedUnfairLock : public NonCopyable { Mutex &mutex_; public: /** - * @brief Constructs the scoped lock and acquires the lock on the provided mutex. + * @brief Constructs the scoped lock and acquires the lock on the provided + * mutex. * * @param mutex The mutex to lock. */ @@ -214,9 +207,6 @@ class ScopedUnfairLock { * @brief Destructs the scoped lock and releases the lock. */ ~ScopedUnfairLock() { mutex_.unlock(); } - - ScopedUnfairLock(const ScopedUnfairLock &) = delete; - ScopedUnfairLock &operator=(const ScopedUnfairLock &) = delete; }; } // namespace atom::async diff --git a/src/atom/async/meson.build b/src/atom/async/meson.build deleted file mode 100644 index 7c95951a..00000000 --- a/src/atom/async/meson.build +++ /dev/null @@ -1,63 +0,0 @@ -project('atom-async', 'c', 'cpp', - version: '1.0.0', - license: 'GPL3', - default_options: ['cpp_std=c++20'] -) - -# 源文件和头文件 -atom_async_sources = [ - 'lock.cpp', - 'timer.cpp' -] - -atom_async_headers = [ - 'async.hpp', - 'async.inl', - 'lock.hpp', - 'pool.hpp', - 'queue.hpp', - 'queue.inl', - 'thread_wrapper.hpp', - 'timer.hpp', - 'trigger.hpp', - 'trigger.inl' -] - -# 依赖 -loguru_dep = dependency('loguru') - -# 对象库 -atom_async_object = static_library('atom_async_object', - sources: atom_async_sources, - dependencies: [loguru_dep], - include_directories: include_directories('.'), - install: false -) - -# 静态库 -atom_async_lib = static_library('atom-async', - sources: atom_async_object.extract_all_objects(), - dependencies: [loguru_dep], - include_directories: include_directories('.'), - install: true -) - -# 安装头文件 -install_headers(atom_async_headers, subdir: 'atom-async') - -# Python 绑定(如果需要) -pybind_enabled = get_option('build_python_binding') - -if pybind_enabled - pybind11_dep = dependency('pybind11', required: true) - python_binding = import('python') - atom_async_py = python_binding.extension_module('atom-async-py', - sources: '_pybind.cpp', - dependencies: [pybind11_dep], - include_directories: include_directories('.') - ) - atom_async_py.link_with(atom_async_lib) -endif - -# 选项:是否构建 Python 绑定 -option('build_python_binding', type: 'boolean', value: false, description: 'Build Python bindings using pybind11') diff --git a/src/atom/async/message_bus.hpp b/src/atom/async/message_bus.hpp index 942f911d..d48968fb 100644 --- a/src/atom/async/message_bus.hpp +++ b/src/atom/async/message_bus.hpp @@ -22,89 +22,78 @@ Description: Main Message Bus #include #include #include +#include #include #include #include #include +#include #include #if ENABLE_FASTHASH #include "emhash/hash_table8.hpp" -#else -#include #endif #include "atom/log/loguru.hpp" namespace atom::async { + class MessageBus { public: - MessageBus() {} + MessageBus() = default; - MessageBus(const int &max_queue_size) { + explicit MessageBus(const int &max_queue_size) { maxMessageBusSize_.store(max_queue_size); } - ~MessageBus() { StopAllProcessingThreads(); } + ~MessageBus() { stopAllProcessingThreads(); } - // ------------------------------------------------------------------- - // Common methods - // ------------------------------------------------------------------- - - static std::shared_ptr createShared() { + static auto createShared() -> std::shared_ptr { return std::make_shared(); } - static std::unique_ptr createUnique() { + static auto createUnique() -> std::unique_ptr { return std::make_unique(); } -public: - // ------------------------------------------------------------------- - // MessageBus methods - // ------------------------------------------------------------------- - template - void Subscribe(const std::string &topic, + void subscribe(const std::string &topic, std::function callback, int priority = 0, const std::string &namespace_ = "") { - std::string fullTopic = - namespace_.empty() ? topic : (namespace_ + "::" + topic); - + std::string fullTopic = getFullTopic(topic, namespace_); std::scoped_lock lock(subscribersLock_); - subscribers_[fullTopic].emplace_back(priority, std::move(callback)); - std::sort( - subscribers_[fullTopic].begin(), subscribers_[fullTopic].end(), - [](const auto &a, const auto &b) { return a.first > b.first; }); + auto &topicSubscribers = subscribers_[fullTopic]; + topicSubscribers.emplace_back(priority, std::move(callback)); + std::ranges::sort(topicSubscribers, [](const auto &a, const auto &b) { + return a.first > b.first; + }); DLOG_F(INFO, "Subscribed to topic: {}", fullTopic); } template - void SubscribeToNamespace(const std::string &namespaceName, + void subscribeToNamespace(const std::string &namespaceName, std::function callback, int priority = 0) { std::string topic = namespaceName + ".*"; - Subscribe(topic, callback, priority, namespaceName); + subscribe(topic, callback, priority, namespaceName); } template - void Unsubscribe(const std::string &topic, + void unsubscribe(const std::string &topic, std::function callback, const std::string &namespace_ = "") { - std::string fullTopic = - namespace_.empty() ? topic : (namespace_ + "::" + topic); - + std::string fullTopic = getFullTopic(topic, namespace_); std::scoped_lock lock(subscribersLock_); auto it = subscribers_.find(fullTopic); if (it != subscribers_.end()) { auto &topicSubscribers = it->second; topicSubscribers.erase( - std::remove_if(topicSubscribers.begin(), topicSubscribers.end(), - [&](const auto &subscriber) { - return subscriber.second.type() == - typeid(callback); - }), + std::ranges::remove_if(topicSubscribers, + [&](const auto &subscriber) { + return subscriber.second.type() == + typeid(callback); + }), topicSubscribers.end()); DLOG_F(INFO, "Unsubscribed from topic: {}", fullTopic); @@ -112,30 +101,27 @@ class MessageBus { } template - void UnsubscribeFromNamespace(const std::string &namespaceName, + void unsubscribeFromNamespace(const std::string &namespaceName, std::function callback) { std::string topic = namespaceName + ".*"; - Unsubscribe(topic, callback, namespaceName); + unsubscribe(topic, callback, namespaceName); } - void UnsubscribeAll(const std::string &namespace_ = "") { + void unsubscribeAll(const std::string &namespace_ = "") { std::string fullTopic = namespace_.empty() ? "*" : (namespace_ + "::*"); - std::scoped_lock lock(subscribersLock_); subscribers_.erase(fullTopic); - DLOG_F(INFO, "Unsubscribed from all topics"); } template - void Publish(const std::string &topic, const T &message, + void publish(const std::string &topic, const T &message, const std::string &namespace_ = "") { - std::string fullTopic = - namespace_.empty() ? topic : (namespace_ + "::" + topic); - + std::string fullTopic = getFullTopic(topic, namespace_); { std::scoped_lock lock(messageQueueLock_); - if (messageQueue_.size() >= maxMessageBusSize_) { + if (messageQueue_.size() >= + static_cast(maxMessageBusSize_)) { LOG_F(WARNING, "Message queue is full. Discarding oldest message."); messageQueue_.pop(); @@ -143,39 +129,36 @@ class MessageBus { messageQueue_.emplace(fullTopic, message); } messageAvailableFlag_.notify_one(); - DLOG_F(INFO, "Published message to topic: {}", fullTopic); } template - bool TryPublish( - const std::string &topic, const T &message, - const std::string &namespace_ = "", - std::chrono::milliseconds timeout = std::chrono::milliseconds(100)) { - std::string fullTopic = - namespace_.empty() ? topic : (namespace_ + "::" + topic); - + auto tryPublish(const std::string &topic, const T &message, + const std::string &namespace_ = "", + std::chrono::milliseconds timeout = + std::chrono::milliseconds(100)) -> bool { + std::string fullTopic = getFullTopic(topic, namespace_); { std::unique_lock lock(messageQueueLock_); if (messageQueueCondition_.wait_for(lock, timeout, [this] { - return messageQueue_.size() < maxMessageBusSize_; + return messageQueue_.size() < + static_cast(maxMessageBusSize_); })) { messageQueue_.emplace(fullTopic, message); messageAvailableFlag_.notify_one(); DLOG_F(INFO, "Published message to topic: {}", fullTopic); return true; - } else { - LOG_F(WARNING, - "Failed to publish message to topic: {} due to timeout", - fullTopic); - return false; } + LOG_F(WARNING, + "Failed to publish message to topic: {} due to timeout", + fullTopic); + return false; } } template - bool TryReceive(T &outMessage, std::chrono::milliseconds timeout = - std::chrono::milliseconds(100)) { + auto tryReceive(T &outMessage, std::chrono::milliseconds timeout = + std::chrono::milliseconds(100)) -> bool { std::unique_lock lock(waitingMutex_); if (messageAvailableFlag_.wait_for( lock, timeout, [this] { return !messageQueue_.empty(); })) { @@ -184,116 +167,43 @@ class MessageBus { messageQueue_.pop(); outMessage = std::any_cast(message.second); return true; - } else { - LOG_F(WARNING, "Failed to receive message due to timeout"); - return false; } + LOG_F(WARNING, "Failed to receive message due to timeout"); + return false; } template - void GlobalSubscribe(std::function callback) { + void globalSubscribe(std::function callback) { std::scoped_lock lock(globalSubscribersLock_); globalSubscribers_.emplace_back(std::move(callback)); } template - void GlobalUnsubscribe(std::function callback) { + void globalUnsubscribe(std::function callback) { std::scoped_lock lock(globalSubscribersLock_); globalSubscribers_.erase( - std::remove_if(globalSubscribers_.begin(), globalSubscribers_.end(), - [&](const auto &subscriber) { - return subscriber.type() == typeid(callback); - }), + std::ranges::remove_if(globalSubscribers_, + [&](const auto &subscriber) { + return subscriber.type() == + typeid(callback); + }), globalSubscribers_.end()); } template - void StartProcessingThread() { + void startProcessingThread() { std::type_index typeIndex = typeid(T); if (processingThreads_.find(typeIndex) == processingThreads_.end()) { processingThreads_.emplace( - typeIndex, - std::jthread([this, typeIndex](std::stop_token stopToken) { - while (!stopToken.stop_requested()) { - std::pair message; - bool hasMessage = false; - - { - std::unique_lock lock(waitingMutex_); - messageAvailableFlag_.wait(lock, [this] { - return !messageQueue_.empty(); - }); - if (stopToken.stop_requested()) { - break; - } - std::scoped_lock messageLock(messageQueueLock_); - if (!messageQueue_.empty()) { - message = std::move(messageQueue_.front()); - messageQueue_.pop(); - hasMessage = true; - } - } - - if (hasMessage) { - const std::string &topic = message.first; - const std::any &data = message.second; - - std::shared_lock subscribersLock(subscribersLock_); - auto it = subscribers_.find(topic); - if (it != subscribers_.end()) { - try { - for (const auto &subscriber : it->second) { - if (subscriber.second.type() == - typeid(std::function)) { - std::any_cast< - std::function>( - subscriber.second)( - std::any_cast(data)); - } - } - } catch (const std::bad_any_cast &e) { - LOG_F(ERROR, "Message type mismatch: {}", - e.what()); - } catch (...) { - LOG_F(ERROR, - "Unknown error occurred during " - "message processing"); - } - } - - std::shared_lock globalLock(globalSubscribersLock_); - try { - for (const auto &subscriber : - globalSubscribers_) { - if (subscriber.type() == - typeid( - std::function)) { - std::any_cast< - std::function>( - subscriber)( - std::any_cast(data)); - } - } - } catch (const std::bad_any_cast &e) { - LOG_F(ERROR, "Global message type mismatch: {}", - e.what()); - } catch (...) { - LOG_F(ERROR, - "Unknown error occurred during global " - "message processing"); - } - - DLOG_F(INFO, "Processed message on topic: {}", - topic); - } - } + typeIndex, std::jthread([this, typeIndex]( + const std::stop_token &stopToken) { + processMessages(stopToken); })); } } template - void StopProcessingThread() { + void stopProcessingThread() { std::type_index typeIndex = typeid(T); auto it = processingThreads_.find(typeIndex); if (it != processingThreads_.end()) { @@ -305,7 +215,7 @@ class MessageBus { } } - void StopAllProcessingThreads() { + void stopAllProcessingThreads() { for (auto &thread : processingThreads_) { thread.second.request_stop(); } @@ -317,7 +227,6 @@ class MessageBus { } private: - // using SubscriberCallback = std::function; using SubscriberCallback = std::any; using SubscriberList = std::vector>; using SubscriberMap = std::unordered_map; @@ -342,7 +251,89 @@ class MessageBus { using GlobalSubscriberList = std::vector; GlobalSubscriberList globalSubscribers_; std::shared_mutex globalSubscribersLock_; + + auto getFullTopic(const std::string &topic, + const std::string &namespace_) const -> std::string { + return namespace_.empty() ? topic : (namespace_ + "::" + topic); + } + + template + void processMessages(const std::stop_token &stopToken) { + while (!stopToken.stop_requested()) { + std::pair message; + bool hasMessage = false; + + { + std::unique_lock lock(waitingMutex_); + messageAvailableFlag_.wait( + lock, [this] { return !messageQueue_.empty(); }); + if (stopToken.stop_requested()) { + break; + } + std::scoped_lock messageLock(messageQueueLock_); + if (!messageQueue_.empty()) { + message = std::move(messageQueue_.front()); + messageQueue_.pop(); + hasMessage = true; + } + } + + if (hasMessage) { + const std::string &topic = message.first; + const std::any &data = message.second; + + // Process local subscribers + { + std::shared_lock subscribersLock(subscribersLock_); + auto it = subscribers_.find(topic); + if (it != subscribers_.end()) { + try { + for (const auto &subscriber : it->second) { + if (subscriber.second.type() == + typeid(std::function)) { + std::any_cast< + std::function>( + subscriber.second)( + std::any_cast(data)); + } + } + } catch (const std::bad_any_cast &e) { + LOG_F(ERROR, "Message type mismatch: {}", e.what()); + } catch (...) { + LOG_F(ERROR, + "Unknown error occurred during message " + "processing"); + } + } + } + + // Process global subscribers + { + std::shared_lock globalLock(globalSubscribersLock_); + try { + for (const auto &subscriber : globalSubscribers_) { + if (subscriber.type() == + typeid(std::function)) { + std::any_cast>( + subscriber)(std::any_cast(data)); + } + } + } catch (const std::bad_any_cast &e) { + LOG_F(ERROR, "Global message type mismatch: {}", + e.what()); + } catch (...) { + LOG_F(ERROR, + "Unknown error occurred during global message " + "processing"); + } + } + + DLOG_F(INFO, "Processed message on topic: {}", topic); + } + } + } }; + } // namespace atom::async -#endif +#endif // ATOM_ASYNC_MESSAGE_BUS_HPP diff --git a/src/atom/async/message_queue.hpp b/src/atom/async/message_queue.hpp index 192937c3..bd4295ca 100644 --- a/src/atom/async/message_queue.hpp +++ b/src/atom/async/message_queue.hpp @@ -15,24 +15,20 @@ Description: A simple message queue (just learn something) #ifndef ATOM_ASYNC_MESSAGE_QUEUE_HPP #define ATOM_ASYNC_MESSAGE_QUEUE_HPP -#include +#include #include #include +#include +#include #include #include -#include #include #include -#include +#include #include -#if ENABLE_FASTHASH -#include "emhash/hash_table8.hpp" -#else -#include -#endif - namespace atom::async { + /** * @brief A message queue that allows subscribers to receive messages of type T. * @@ -45,7 +41,7 @@ class MessageQueue { * @brief The callback function type that will be called when a new message * is received. */ - using CallbackType = std::function; + using CallbackType = std::function; /** * @brief Subscribe a callback function to receive messages. @@ -56,7 +52,7 @@ class MessageQueue { * @param priority The priority of the subscriber. Higher priority * subscribers will receive messages before lower priority subscribers. */ - void subscribe(CallbackType callback, const std::string &subscriberName, + void subscribe(CallbackType callback, const std::string& subscriberName, int priority = 0); /** @@ -71,7 +67,7 @@ class MessageQueue { * * @param message The message to be published. */ - void publish(const T &message); + void publish(const T& message); /** * @brief Start the processing thread(s) to receive and handle messages. @@ -80,7 +76,7 @@ class MessageQueue { * specified, the number of hardware threads will be used. */ void startProcessingThread( - int numThreads = std::thread::hardware_concurrency()); + size_t numThreads = std::thread::hardware_concurrency()); /** * @brief Stop the processing thread(s) from receiving and handling @@ -93,14 +89,14 @@ class MessageQueue { * * @return The number of messages in the queue. */ - size_t getMessageCount() const; + auto getMessageCount() const -> size_t; /** * @brief Get the number of subscribers currently registered. * * @return The number of subscribers. */ - size_t getSubscriberCount() const; + auto getSubscriberCount() const -> size_t; private: /** @@ -121,9 +117,8 @@ class MessageQueue { * is received. * @param priority The priority of the subscriber. */ - Subscriber(const std::string &name, const CallbackType &callback, - int priority) - : name(name), callback(callback), priority(priority) {} + Subscriber(std::string name, const CallbackType& callback, int priority) + : name(std::move(name)), callback(callback), priority(priority) {} /** * @brief Compare two Subscriber objects based on their priority @@ -132,120 +127,126 @@ class MessageQueue { * @return True if this Subscriber has a higher priority than the other * Subscriber, false otherwise. */ - bool operator<(const Subscriber &other) const { + auto operator<(const Subscriber& other) const -> bool { return priority > other.priority; } }; std::deque - m_messages; /**< The queue containing all published messages. */ - std::vector m_subscribers; /**< The vector containing all + m_messages_; /**< The queue containing all published messages. */ + std::vector m_subscribers_; /**< The vector containing all subscribed callback functions. */ - mutable std::mutex m_mutex; /**< The mutex used to protect access to the + mutable std::mutex m_mutex_; /**< The mutex used to protect access to the message queue and subscriber vector. */ std::condition_variable - m_condition; /**< The condition variable used to notify processing + m_condition_; /**< The condition variable used to notify processing threads of new messages. */ - std::atomic m_isRunning{ + std::atomic m_isRunning_{ true}; /**< The flag used to indicate whether the processing thread(s) - should continue running. */ - std::vector m_processingThreads; /**< The vector containing all - processing threads. */ + should continue running. */ + std::vector m_processingThreads_; /**< The vector containing + all processing threads. */ + + void processMessages(); }; template void MessageQueue::subscribe(CallbackType callback, - const std::string &subscriberName, + const std::string& subscriberName, int priority) { - std::lock_guard lock(m_mutex); - m_subscribers.emplace_back(subscriberName, callback, priority); - std::sort(m_subscribers.begin(), m_subscribers.end()); + std::lock_guard lock(m_mutex_); + m_subscribers_.emplace_back(subscriberName, callback, priority); + std::sort(m_subscribers_.begin(), m_subscribers_.end()); } template void MessageQueue::unsubscribe(CallbackType callback) { - std::lock_guard lock(m_mutex); - auto it = std::remove_if(m_subscribers.begin(), m_subscribers.end(), - [&callback](const auto &subscriber) { + std::lock_guard lock(m_mutex_); + auto it = std::remove_if(m_subscribers_.begin(), m_subscribers_.end(), + [&callback](const auto& subscriber) { return subscriber.callback.target_type() == callback.target_type(); }); - m_subscribers.erase(it, m_subscribers.end()); + m_subscribers_.erase(it, m_subscribers_.end()); } template -void MessageQueue::publish(const T &message) { +void MessageQueue::publish(const T& message) { { - std::lock_guard lock(m_mutex); - m_messages.emplace_back(message); + std::lock_guard lock(m_mutex_); + m_messages_.emplace_back(message); } - m_condition.notify_one(); + m_condition_.notify_one(); } template -void MessageQueue::startProcessingThread(int numThreads) { - for (int i = 0; i < numThreads; ++i) { - m_processingThreads.emplace_back([this]() { - while (m_isRunning.load()) { - std::optional message; - - { - std::unique_lock lock(m_mutex); - m_condition.wait(lock, [this]() { - return !m_messages.empty() || !m_isRunning.load(); - }); - - if (!m_isRunning.load()) - return; - - if (!m_messages.empty()) { - message = std::move(m_messages.front()); - m_messages.pop_front(); - } - } - - if (message) { - std::vector subscribersCopy; - - { - std::lock_guard lock(m_mutex); - subscribersCopy.reserve(m_subscribers.size()); - for (const auto &subscriber : m_subscribers) { - subscribersCopy.emplace_back(subscriber.callback); - } - } - - for (const auto &subscriber : subscribersCopy) { - subscriber(*message); - } - } - } - }); +void MessageQueue::startProcessingThread(size_t numThreads) { + for (size_t i = 0; i < numThreads; ++i) { + m_processingThreads_.emplace_back(&MessageQueue::processMessages, this); } } template void MessageQueue::stopProcessingThread() { - m_isRunning.store(false); - m_condition.notify_all(); - for (auto &thread : m_processingThreads) { + m_isRunning_.store(false); + m_condition_.notify_all(); + for (auto& thread : m_processingThreads_) { if (thread.joinable()) { thread.join(); } } - m_processingThreads.clear(); + m_processingThreads_.clear(); +} + +template +auto MessageQueue::getMessageCount() const -> size_t { + std::lock_guard lock(m_mutex_); + return m_messages_.size(); } template -size_t MessageQueue::getMessageCount() const { - std::lock_guard lock(m_mutex); - return m_messages.size(); +auto MessageQueue::getSubscriberCount() const -> size_t { + std::lock_guard lock(m_mutex_); + return m_subscribers_.size(); } template -size_t MessageQueue::getSubscriberCount() const { - std::lock_guard lock(m_mutex); - return m_subscribers.size(); +void MessageQueue::processMessages() { + while (m_isRunning_.load()) { + std::optional message; + + { + std::unique_lock lock(m_mutex_); + m_condition_.wait(lock, [this]() { + return !m_messages_.empty() || !m_isRunning_.load(); + }); + + if (!m_isRunning_.load() && m_messages_.empty()) { + return; + } + + if (!m_messages_.empty()) { + message = std::move(m_messages_.front()); + m_messages_.pop_front(); + } + } + + if (message) { + std::vector subscribersCopy; + + { + std::lock_guard lock(m_mutex_); + subscribersCopy.reserve(m_subscribers_.size()); + for (const auto& subscriber : m_subscribers_) { + subscribersCopy.emplace_back(subscriber.callback); + } + } + + for (const auto& subscriber : subscribersCopy) { + subscriber(*message); + } + } + } } } // namespace atom::async diff --git a/src/atom/async/pool.hpp b/src/atom/async/pool.hpp index 8376259b..7ef74ff3 100644 --- a/src/atom/async/pool.hpp +++ b/src/atom/async/pool.hpp @@ -15,173 +15,373 @@ Description: A very simple thread pool for preload #ifndef ATOM_ASYNC_POOL_HPP #define ATOM_ASYNC_POOL_HPP -#include -#include +#include +#include +#include +#include #include #include -#include #include -#include +#include #include -#include #include -#include - -#include "atom/error/exception.hpp" +#include +#include +#include "macro.hpp" +#ifdef __has_include +#if __has_include() +#include +#endif +#endif namespace atom::async { +/** + * @brief Simple concept for the Lockable and Basic Lockable types as defined by + * the C++ standard. + * @details See https://en.cppreference.com/w/cpp/named_req/Lockable and + * https://en.cppreference.com/w/cpp/named_req/BasicLockable for details. + */ +template +concept is_lockable = requires(Lock&& lock) { + lock.lock(); + lock.unlock(); + { lock.try_lock() } -> std::convertible_to; +}; + +template + requires is_lockable +class ThreadSafeQueue { +public: + using value_type = T; + using size_type = typename std::deque::size_type; + + ThreadSafeQueue() = default; + + // Copy constructor + ThreadSafeQueue(const ThreadSafeQueue& other) { + std::scoped_lock lock(other.mutex_); + data_ = other.data_; + } + + // Copy assignment operator + ThreadSafeQueue& operator=(const ThreadSafeQueue& other) { + if (this != &other) { + std::scoped_lock lockThis(mutex_, std::defer_lock); + std::scoped_lock lockOther(other.mutex_, std::defer_lock); + std::lock(lockThis, lockOther); + data_ = other.data_; + } + return *this; + } + + // Move constructor + ThreadSafeQueue(ThreadSafeQueue&& other) noexcept { + std::scoped_lock lock(other.mutex_); + data_ = std::move(other.data_); + } + + // Move assignment operator + ThreadSafeQueue& operator=(ThreadSafeQueue&& other) noexcept { + if (this != &other) { + std::scoped_lock lockThis(mutex_, std::defer_lock); + std::scoped_lock lockOther(other.mutex_, std::defer_lock); + std::lock(lockThis, lockOther); + data_ = std::move(other.data_); + } + return *this; + } + + void pushBack(T&& value) { + std::scoped_lock lock(mutex_); + data_.push_back(std::forward(value)); + } + + void pushFront(T&& value) { + std::scoped_lock lock(mutex_); + data_.push_front(std::forward(value)); + } + + [[nodiscard]] auto empty() const -> bool { + std::scoped_lock lock(mutex_); + return data_.empty(); + } + + [[nodiscard]] auto size() const -> size_type { + std::scoped_lock lock(mutex_); + return data_.size(); + } + + [[nodiscard]] auto popFront() -> std::optional { + std::scoped_lock lock(mutex_); + if (data_.empty()) { + return std::nullopt; +} + + auto front = std::move(data_.front()); + data_.pop_front(); + return front; + } + + [[nodiscard]] auto popBack() -> std::optional { + std::scoped_lock lock(mutex_); + if (data_.empty()) + return std::nullopt; + + auto back = std::move(data_.back()); + data_.pop_back(); + return back; + } + + [[nodiscard]] auto steal() -> std::optional { + std::scoped_lock lock(mutex_); + if (data_.empty()) + return std::nullopt; + + auto back = std::move(data_.back()); + data_.pop_back(); + return back; + } + + void rotateToFront(const T& item) { + std::scoped_lock lock(mutex_); + auto iter = std::find(data_.begin(), data_.end(), item); + + if (iter != data_.end()) { + std::ignore = data_.erase(iter); + } + + data_.push_front(item); + } + + [[nodiscard]] auto copyFrontAndRotateToBack() -> std::optional { + std::scoped_lock lock(mutex_); + + if (data_.empty()) { + return std::nullopt; +} + + auto front = data_.front(); + data_.pop_front(); + + data_.push_back(front); + + return front; + } + + void clear() { + std::scoped_lock lock(mutex_); + data_.clear(); + } + +private: + std::deque data_; + mutable Lock mutex_; +}; + +namespace details { +#ifdef __cpp_lib_move_only_function +using default_function_type = std::move_only_function; +#else +using default_function_type = std::function; +#endif +} // namespace details + +template + requires std::invocable && + std::is_same_v> class ThreadPool { public: - /** - * @brief Construct a new Thread Pool object - * - * @param n_threads The number of threads in the pool - */ - explicit ThreadPool(std::size_t n_threads) : stop(false), active_count(0) { - startThreads(n_threads); - } - - /** - * @brief Destroy the Thread Pool object - */ - ~ThreadPool() { stopPool(); } - - /** - * @brief Enqueue a task to the pool - * - * @tparam F The type of the function - * @tparam Args The type of the arguments - * @param f The function - * @param args The arguments - * @return std::future> The future of the - * task - */ - template - auto enqueue(F&& f, Args&&... args) { - using return_type = std::invoke_result_t; - - auto task = std::make_shared>( - std::bind(std::forward(f), std::forward(args)...)); - - auto res = task->get_future(); - { - std::scoped_lock lock(queue_mutex); - if (stop) { - THROW_UNLAWFUL_OPERATION("enqueue on stopped ThreadPool"); + template < + typename InitializationFunction = std::function> + requires std::invocable && + std::is_same_v> + explicit ThreadPool( + const unsigned int& number_of_threads = + std::thread::hardware_concurrency(), + InitializationFunction init = [](std::size_t) {}) + : tasks_(number_of_threads) { + std::size_t currentId = 0; + for (std::size_t i = 0; i < number_of_threads; ++i) { + priority_queue_.pushBack(std::move(currentId)); + try { + threads_.emplace_back([&, id = currentId, + init](const std::stop_token& stop_tok) { + try { + std::invoke(init, id); + } catch (...) { + } + + do { + tasks_[id].signal.acquire(); + + do { + while (auto task = tasks_[id].tasks.pop_front()) { + unassigned_tasks_.fetch_sub( + 1, std::memory_order_release); + std::invoke(std::move(task.value())); + in_flight_tasks_.fetch_sub( + 1, std::memory_order_release); + } + + for (std::size_t j = 1; j < tasks_.size(); ++j) { + const std::size_t INDEX = + (id + j) % tasks_.size(); + if (auto task = tasks_[INDEX].tasks.steal()) { + unassigned_tasks_.fetch_sub( + 1, std::memory_order_release); + std::invoke(std::move(task.value())); + in_flight_tasks_.fetch_sub( + 1, std::memory_order_release); + break; + } + } + } while (unassigned_tasks_.load( + std::memory_order_acquire) > 0); + + priority_queue_.rotateToFront(id); + + if (in_flight_tasks_.load(std::memory_order_acquire) == + 0) { + threads_complete_signal_.store( + true, std::memory_order_release); + threads_complete_signal_.notify_one(); + } + + } while (!stop_tok.stop_requested()); + }); + ++currentId; + + } catch (...) { + tasks_.pop_back(); + std::ignore = priority_queue_.popBack(); } - tasks.emplace([task = std::move(task)]() { (*task)(); }); } - condition.notify_one(); - return res; - } - - /** - * @brief Wait for all tasks to finish - */ - void wait() { - std::unique_lock lock(queue_mutex); - condition.wait(lock, - [this] { return tasks.empty() && active_count == 0; }); - } - - /** - * @brief Get the number of threads in the pool - * - * @return std::size_t The number of threads in the pool - */ - std::size_t size() const { return threads.size(); } - - /** - * @brief Get the number of tasks in the pool - * - * @return std::size_t The number of tasks in the pool - */ - std::size_t taskCount() const { - std::scoped_lock lock(queue_mutex); - return tasks.size(); - } - - /** - * @brief Resize the number of threads in the pool - * - * @param n_threads The number of threads in the pool - */ - void resize(std::size_t n_threads) { - { - std::scoped_lock lock(queue_mutex); - stop = true; + } + + ~ThreadPool() { + waitForTasks(); + + for (auto& thread : threads_) { + thread.request_stop(); } - condition.notify_all(); - for (auto& thread : threads) { - thread.join(); + for (auto& task : tasks_) { + task.signal.release(); } - threads.clear(); - { - std::scoped_lock lock(queue_mutex); - stop = false; + for (auto& thread : threads_) { + thread.join(); } - startThreads(n_threads); } -private: - std::vector threads; - std::queue> tasks; - - mutable std::mutex queue_mutex; - std::condition_variable condition; - std::atomic stop; - std::atomic active_count; - - /** - * @brief Start the threads in the pool - * - * @param n_threads The number of threads in the pool - */ - void startThreads(std::size_t n_threads) { - threads.reserve(n_threads); - for (std::size_t i = 0; i < n_threads; ++i) { - threads.emplace_back([this] { - while (true) { - std::function task; - { - std::unique_lock lock(queue_mutex); - condition.wait( - lock, [this] { return stop || !tasks.empty(); }); - - if (stop && tasks.empty()) { - return; - } + ThreadPool(const ThreadPool&) = delete; + auto operator=(const ThreadPool&) -> ThreadPool& = delete; - task = std::move(tasks.front()); - tasks.pop(); - ++active_count; - } + template > + requires std::invocable + [[nodiscard]] auto enqueue(Function f, + Args... args) -> std::future { +#ifdef __cpp_lib_move_only_function + std::promise promise; + auto future = promise.get_future(); + auto task = [func = std::move(f), ... largs = std::move(args), + promise = std::move(promise)]() mutable { + try { + if constexpr (std::is_same_v) { + func(largs...); + promise.set_value(); + } else { + promise.set_value(func(largs...)); + } + } catch (...) { + promise.set_exception(std::current_exception()); + } + }; + enqueue_task(std::move(task)); + return future; +#else + auto shared_promise = std::make_shared>(); + auto task = [func = std::move(f), ... largs = std::move(args), + promise = shared_promise]() { + try { + if constexpr (std::is_same_v) { + func(largs...); + promise->set_value(); + } else { + promise->set_value(func(largs...)); + } + } catch (...) { + promise->set_exception(std::current_exception()); + } + }; + + auto future = shared_promise->get_future(); + enqueue_task(std::move(task)); + return future; +#endif + } - task(); - --active_count; - condition.notify_one(); + template + requires std::invocable + void enqueueDetach(Function&& func, Args&&... args) { + enqueueTask([f = std::forward(func), + ... largs = std::forward(args)]() mutable { + try { + if constexpr (std::is_same_v>) { + std::invoke(f, largs...); + } else { + std::ignore = std::invoke(f, largs...); } - }); + } catch (...) { + } + }); + } + + [[nodiscard]] auto size() const { return threads_.size(); } + + void waitForTasks() { + if (in_flight_tasks_.load(std::memory_order_acquire) > 0) { + threads_complete_signal_.wait(false); } } - /** - * @brief Stop the pool - */ - void stopPool() { - { - std::scoped_lock lock(queue_mutex); - stop = true; +private: + template + void enqueueTask(Function&& f) { + auto iOpt = priority_queue_.copyFrontAndRotateToBack(); + if (!iOpt.has_value()) { + return; } - condition.notify_all(); - for (auto& thread : threads) { - thread.join(); + auto i = *(iOpt); + + unassigned_tasks_.fetch_add(1, std::memory_order_release); + const auto PREV_IN_FLIGHT = + in_flight_tasks_.fetch_add(1, std::memory_order_release); + + if (PREV_IN_FLIGHT == 0) { + threads_complete_signal_.store(false, std::memory_order_release); } + + tasks_[i].tasks.push_back(std::forward(f)); + tasks_[i].signal.release(); } -}; + struct TaskItem { + atom::async::ThreadSafeQueue tasks{}; + std::binary_semaphore signal{0}; + } ATOM_ALIGNAS(128); + + std::vector threads_; + std::deque tasks_; + atom::async::ThreadSafeQueue priority_queue_; + std::atomic_int_fast64_t unassigned_tasks_{0}, in_flight_tasks_{0}; + std::atomic_bool threads_complete_signal_{false}; +}; } // namespace atom::async #endif // ATOM_ASYNC_POOL_HPP diff --git a/src/atom/async/queue.hpp b/src/atom/async/queue.hpp index e182c81e..247ffc37 100644 --- a/src/atom/async/queue.hpp +++ b/src/atom/async/queue.hpp @@ -15,144 +15,303 @@ Description: A simple thread safe queue #ifndef ATOM_ASYNC_QUEUE_HPP #define ATOM_ASYNC_QUEUE_HPP +#include #include +#include #include #include +#include #include #include #include -#include +#include +#include #include -#include "atom/type/noncopyable.hpp" - namespace atom::async { -/** - * @brief A thread-safe queue data structure that supports concurrent access - * from multiple threads. - * - * This class provides a thread-safe implementation of a queue, allowing - * elements to be added and removed concurrently from multiple threads without - * causing data races or undefined behavior. - * - * @tparam T The type of elements stored in the queue. - */ template -class ThreadSafeQueue : public NonCopyable { +class ThreadSafeQueue { public: - /** - * @brief Default constructor. - */ ThreadSafeQueue() = default; - /** - * @brief Adds an element to the end of the queue. - * @param element The element to be added to the queue. - */ - void put(T element); - - /** - * @brief Removes and returns an element from the front of the queue. - * @return An optional containing the removed element, or empty if the queue - * is empty. - */ - std::optional take(); - - /** - * @brief Removes all elements from the queue and returns them in a - * std::queue. - * @return A std::queue containing all the elements removed from the queue. - */ - std::queue destroy(); - - /** - * @brief Returns the number of elements currently in the queue. - * @return The number of elements in the queue. - */ - [[nodiscard]] size_t size() const; - - /** - * @brief Checks if the queue is empty. - * @return True if the queue is empty, false otherwise. - */ - [[nodiscard]] bool empty() const; - - /** - * @brief Removes all elements from the queue. - */ - void clear(); - - /** - * @brief Returns the element at the front of the queue without removing it. - * @return An optional containing the element at the front of the queue, or - * empty if the queue is empty. - */ - std::optional front(); - - /** - * @brief Returns the element at the back of the queue without removing it. - * @return An optional containing the element at the back of the queue, or - * empty if the queue is empty. - */ - std::optional back(); - - /** - * @brief Constructs and adds an element to the end of the queue. - * @tparam Args The types of arguments used to construct the element. - * @param args The arguments used to construct the element. - */ + void put(T element) { + { + std::lock_guard lock(m_mutex_); + m_queue_.push(std::move(element)); + } + m_conditionVariable_.notify_one(); + } + + auto take() -> std::optional { + std::unique_lock lock(m_mutex_); + m_conditionVariable_.wait( + lock, [this] { return m_mustReturnNullptr_ || !m_queue_.empty(); }); + + if (m_mustReturnNullptr_) { + return std::nullopt; + } + + T ret = std::move(m_queue_.front()); + m_queue_.pop(); + + return ret; + } + + auto destroy() -> std::queue { + { + std::lock_guard lock(m_mutex_); + m_mustReturnNullptr_ = true; + } + m_conditionVariable_.notify_all(); + + std::queue result; + { + std::lock_guard lock(m_mutex_); + std::swap(result, m_queue_); + } + return result; + } + + [[nodiscard]] auto size() const -> size_t { + std::lock_guard lock(m_mutex_); + return m_queue_.size(); + } + + [[nodiscard]] auto empty() const -> bool { + std::lock_guard lock(m_mutex_); + return m_queue_.empty(); + } + + void clear() { + std::lock_guard lock(m_mutex_); + std::queue empty; + std::swap(m_queue_, empty); + } + + auto front() -> std::optional { + std::lock_guard lock(m_mutex_); + if (m_queue_.empty()) { + return std::nullopt; + } + return m_queue_.front(); + } + + auto back() -> std::optional { + std::lock_guard lock(m_mutex_); + if (m_queue_.empty()) { + return std::nullopt; + } + return m_queue_.back(); + } + template - void emplace(Args&&... args); - - /** - * @brief Waits until a predicate becomes true for an element in the queue, - * then removes and returns that element. - * @tparam Predicate The type of predicate function or functor. - * @param predicate The predicate to wait for. - * @return An optional containing the element that satisfied the predicate, - * or empty if the queue is destroyed or the timeout expires. - */ - template - std::optional waitFor(Predicate predicate); - - /** - * @brief Blocks until the queue becomes empty. - */ - void waitUntilEmpty(); - - /** - * @brief Removes and returns all elements from the queue that satisfy a - * given unary predicate. - * @tparam UnaryPredicate The type of unary predicate function or functor. - * @param pred The unary predicate used to test elements. - * @return A vector containing all elements removed from the queue that - * satisfy the predicate. - */ - template - std::vector extractIf(UnaryPredicate pred); - - /** - * @brief Sorts the elements in the queue using a custom comparison - * function. - * @tparam Compare The type of comparison function or functor. - * @param comp The comparison function used to sort elements. - */ + void emplace(Args&&... args) { + { + std::lock_guard lock(m_mutex_); + m_queue_.emplace(std::forward(args)...); + } + m_conditionVariable_.notify_one(); + } + + template Predicate> + auto waitFor(Predicate predicate) -> std::optional { + std::unique_lock lock(m_mutex_); + m_conditionVariable_.wait(lock, [this, &predicate] { + return m_mustReturnNullptr_ || + (!m_queue_.empty() && predicate(m_queue_.front())); + }); + + if (m_mustReturnNullptr_) + return std::nullopt; + + T ret = std::move(m_queue_.front()); + m_queue_.pop(); + + return ret; + } + + void waitUntilEmpty() { + std::unique_lock lock(m_mutex_); + m_conditionVariable_.wait( + lock, [this] { return m_mustReturnNullptr_ || m_queue_.empty(); }); + } + + template UnaryPredicate> + auto extractIf(UnaryPredicate pred) -> std::vector { + std::vector result; + { + std::lock_guard lock(m_mutex_); + std::queue remaining; + while (!m_queue_.empty()) { + T& item = m_queue_.front(); + if (pred(item)) { + result.push_back(std::move(item)); + } else { + remaining.push(std::move(item)); + } + m_queue_.pop(); + } + std::swap(m_queue_, remaining); + } + return result; + } + template - void sort(Compare comp); + requires std::is_invocable_r_v + void sort(Compare comp) { + std::lock_guard lock(m_mutex_); + std::vector temp; + temp.reserve(m_queue_.size()); + while (!m_queue_.empty()) { + temp.push_back(std::move(m_queue_.front())); + m_queue_.pop(); + } + std::sort(temp.begin(), temp.end(), comp); + for (auto& elem : temp) { + m_queue_.push(std::move(elem)); + } + } + + template + auto transform(std::function func) + -> std::shared_ptr> { + std::shared_ptr> resultQueue; + { + std::lock_guard lock(m_mutex_); + std::vector original; + original.reserve(m_queue_.size()); + + while (!m_queue_.empty()) { + original.push_back(std::move(m_queue_.front())); + m_queue_.pop(); + } + + std::vector transformed(original.size()); + std::transform(original.begin(), original.end(), + transformed.begin(), func); + + for (auto& item : transformed) { + resultQueue->put(std::move(item)); + } + } + return resultQueue; + } + + template + auto groupBy(std::function func) + -> std::vector>> { + std::unordered_map>> + resultMap; + { + std::lock_guard lock(m_mutex_); + while (!m_queue_.empty()) { + T item = std::move(m_queue_.front()); + m_queue_.pop(); + GroupKey key = func(item); + if (!resultMap.contains(key)) { + resultMap[key] = std::make_shared>(); + } + resultMap[key]->put(std::move(item)); + } + } + + std::vector>> resultQueues; + resultQueues.reserve(resultMap.size()); + for (auto& [_, queue_ptr] : resultMap) { + resultQueues.push_back(queue_ptr); + } + + return resultQueues; + } + + auto toVector() const -> std::vector { + std::lock_guard lock(m_mutex_); + return std::vector(m_queue_.front(), m_queue_.back()); + } + + template + requires std::is_invocable_r_v + void forEach(Func func, bool parallel = false) { + std::lock_guard lock(m_mutex_); + if (parallel) { + std::vector vec; + vec.reserve(m_queue_.size()); + while (!m_queue_.empty()) { + vec.push_back(std::move(m_queue_.front())); + m_queue_.pop(); + } + +#pragma omp parallel for + for (size_t i = 0; i < vec.size(); ++i) { + func(vec[i]); + } + + for (auto& item : vec) { + m_queue_.push(std::move(item)); + } + } else { + std::queue tempQueue; + while (!m_queue_.empty()) { + T& item = m_queue_.front(); + func(item); + tempQueue.push(std::move(item)); + m_queue_.pop(); + } + m_queue_ = std::move(tempQueue); + } + } + + auto tryTake() -> std::optional { + std::lock_guard lock(m_mutex_); + if (m_queue_.empty()) { + return std::nullopt; + } + T ret = std::move(m_queue_.front()); + m_queue_.pop(); + return ret; + } + + template + auto takeFor(const std::chrono::duration& timeout) + -> std::optional { + std::unique_lock lock(m_mutex_); + if (m_conditionVariable_.wait_for(lock, timeout, [this] { + return !m_queue_.empty() || m_mustReturnNullptr_; + })) { + if (m_mustReturnNullptr_) { + return std::nullopt; + } + T ret = std::move(m_queue_.front()); + m_queue_.pop(); + return ret; + } + return std::nullopt; + } + + template + auto takeUntil(const std::chrono::time_point& timeout_time) + -> std::optional { + std::unique_lock lock(m_mutex_); + if (m_conditionVariable_.wait_until(lock, timeout_time, [this] { + return !m_queue_.empty() || m_mustReturnNullptr_; + })) { + if (m_mustReturnNullptr_) { + return std::nullopt; + } + T ret = std::move(m_queue_.front()); + m_queue_.pop(); + return ret; + } + return std::nullopt; + } private: - std::queue m_queue; ///< The underlying queue. - mutable std::mutex m_mutex; ///< Mutex for ensuring thread safety. - std::condition_variable - m_conditionVariable; ///< Condition variable for blocking and waking - ///< threads. - std::atomic m_mustReturnNullptr{ - false}; ///< Atomic flag indicating whether the queue should return - ///< nullptr on take() when empty. + std::queue m_queue_; + mutable std::mutex m_mutex_; + std::condition_variable m_conditionVariable_; + std::atomic m_mustReturnNullptr_{false}; }; } // namespace atom::async -#include "queue.inl" - -#endif +#endif // ATOM_ASYNC_QUEUE_HPP diff --git a/src/atom/async/queue.inl b/src/atom/async/queue.inl deleted file mode 100644 index 9432d74b..00000000 --- a/src/atom/async/queue.inl +++ /dev/null @@ -1,206 +0,0 @@ -#ifndef ATOM_ASYNC_QUEUE_INL -#define ATOM_ASYNC_QUEUE_INL - -#include "queue.hpp" - -#include - -namespace atom::async { -template -void ThreadSafeQueue::put(T element) { - { - std::lock_guard lock(m_mutex); - m_queue.push(std::move(element)); - } - m_conditionVariable.notify_one(); -} - -template -std::optional ThreadSafeQueue::take() { - std::unique_lock lock(m_mutex); - m_conditionVariable.wait( - lock, [this] { return m_mustReturnNullptr || !m_queue.empty(); }); - - if (m_mustReturnNullptr) - return std::nullopt; - - T ret = std::move(m_queue.front()); - m_queue.pop(); - - return ret; -} - -template -std::queue ThreadSafeQueue::destroy() { - { - std::lock_guard lock(m_mutex); - m_mustReturnNullptr = true; - } - m_conditionVariable.notify_all(); - - std::queue result; - { - std::lock_guard lock(m_mutex); - std::swap(result, m_queue); - } - return result; -} - -template -size_t ThreadSafeQueue::size() const { - std::lock_guard lock(m_mutex); - return m_queue.size(); -} - -template -bool ThreadSafeQueue::empty() const { - std::lock_guard lock(m_mutex); - return m_queue.empty(); -} - -template -void ThreadSafeQueue::clear() { - std::lock_guard lock(m_mutex); - std::queue empty; - std::swap(m_queue, empty); -} - -template -std::optional ThreadSafeQueue::front() { - std::lock_guard lock(m_mutex); - if (m_queue.empty()) { - return std::nullopt; - } - return m_queue.front(); -} - -template -std::optional ThreadSafeQueue::back() { - std::lock_guard lock(m_mutex); - if (m_queue.empty()) { - return std::nullopt; - } - return m_queue.back(); -} - -template -template -void ThreadSafeQueue::emplace(Args&&... args) { - { - std::lock_guard lock(m_mutex); - m_queue.emplace(std::forward(args)...); - } - m_conditionVariable.notify_one(); -} - -template -template -std::optional ThreadSafeQueue::waitFor(Predicate predicate) { - std::unique_lock lock(m_mutex); - m_conditionVariable.wait(lock, [this, &predicate] { - return m_mustReturnNullptr || predicate(m_queue); - }); - - if (m_mustReturnNullptr) - return std::nullopt; - - T ret = std::move(m_queue.front()); - m_queue.pop(); - - return ret; -} - -template -void ThreadSafeQueue::waitUntilEmpty() { - std::unique_lock lock(m_mutex); - m_conditionVariable.wait( - lock, [this] { return m_mustReturnNullptr || m_queue.empty(); }); -} - -template -template -std::vector ThreadSafeQueue::extractIf(UnaryPredicate pred) { - std::vector result; - { - std::lock_guard lock(m_mutex); - auto it = - std::remove_if(m_queue.front(), m_queue.back(), [&](const T& item) { - if (pred(item)) { - result.push_back(std::move(const_cast(item))); - return true; - } - return false; - }); - m_queue.pop(); - } - return result; -} - -template -template -void ThreadSafeQueue::sort(Compare comp) { - std::lock_guard lock(m_mutex); - - // 移动元素到临时向量并排序 - std::vector temp; - while (!m_queue.empty()) { - temp.push_back(std::move(m_queue.front())); - m_queue.pop(); - } - std::sort(temp.begin(), temp.end(), comp); - - // 将排序后的元素移动到新的队列中 - std::queue newQueue; - for (auto& elem : temp) { - newQueue.push(std::move(elem)); - } - - // 交换新旧队列 - std::swap(m_queue, newQueue); -} - -/* -template -template -ThreadSafeQueue ThreadSafeQueue::transform( - std::function func) { - ThreadSafeQueue resultQueue; - { - std::lock_guard lock(m_mutex); - std::transform(std::make_move_iterator(m_queue.front()), - std::make_move_iterator(m_queue.back()), - std::back_inserter(resultQueue.m_queue), func); - std::queue empty; - std::swap(m_queue, empty); - } - // return resultQueue; -} - -template -template -std::vector> ThreadSafeQueue::groupBy( - std::function func) { - std::unordered_map> resultMap; - { - std::lock_guard lock(m_mutex); - while (!m_queue.empty()) { - T item = std::move(m_queue.front()); - m_queue.pop(); - GroupKey key = func(item); - resultMap[key].put(std::move(item)); - } - } - - std::vector> resultQueues; - resultQueues.reserve(resultMap.size()); - for (auto& pair : resultMap) { - resultQueues.push_back(std::move(pair.second)); - } - - return resultQueues; -} -*/ - -} // namespace atom::async - -#endif diff --git a/src/atom/async/safetype.hpp b/src/atom/async/safetype.hpp new file mode 100644 index 00000000..e7a16d61 --- /dev/null +++ b/src/atom/async/safetype.hpp @@ -0,0 +1,680 @@ +#ifndef ATOM_ASYNC_SAFETYPE_HPP +#define ATOM_ASYNC_SAFETYPE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "atom/error/exception.hpp" + +namespace atom::async { +/** + * @brief A lock-free stack implementation suitable for concurrent use. + * + * @tparam T Type of elements stored in the stack. + */ +template +class LockFreeStack { +private: + struct Node { + T value; ///< The stored value of type T. + std::atomic next = + nullptr; ///< Pointer to the next node in the stack. + + /** + * @brief Construct a new Node object. + * + * @param value_ The value to store in the node. + */ + explicit Node(T value_); + }; + + std::atomic head_; ///< Atomic pointer to the top of the stack. + std::atomic approximateSize_ = + 0; ///< An approximate count of the stack's elements. + +public: + /** + * @brief Construct a new Lock Free Stack object. + */ + LockFreeStack(); + + /** + * @brief Destroy the Lock Free Stack object. + */ + ~LockFreeStack(); + + /** + * @brief Pushes a value onto the stack. Thread-safe. + * + * @param value The value to push onto the stack. + */ + void push(const T& value); + + /** + * @brief Pushes a value onto the stack using move semantics. Thread-safe. + * + * @param value The value to move onto the stack. + */ + void push(T&& value); + + /** + * @brief Attempts to pop the top value off the stack. Thread-safe. + * + * @return std::optional The popped value if stack is not empty, + * otherwise nullopt. + */ + auto pop() -> std::optional; + + /** + * @brief Get the top value of the stack without removing it. Thread-safe. + * + * @return std::optional The top value if stack is not empty, otherwise + * nullopt. + */ + auto top() const -> std::optional; + + /** + * @brief Check if the stack is empty. Thread-safe. + * + * @return true If the stack is empty. + * @return false If the stack has one or more elements. + */ + [[nodiscard]] auto empty() const -> bool; + + /** + * @brief Get the approximate size of the stack. Thread-safe. + * + * @return int The approximate number of elements in the stack. + */ + [[nodiscard]] auto size() const -> int; +}; + +template +class LockFreeHashTable { +private: + struct Node { + Key key; + Value value; + std::atomic next; + + Node(Key k, Value v) : key(k), value(v), next(nullptr) {} + }; + + struct Bucket { + std::atomic head; + + Bucket() : head(nullptr) {} + + ~Bucket() { + Node* node = head.load(); + while (node) { + Node* next = node->next.load(); + delete node; + node = next; + } + } + + auto find(const Key& key) const -> std::optional { + Node* node = head.load(); + while (node) { + if (node->key == key) { + return node->value; + } + node = node->next.load(); + } + return std::nullopt; + } + + void insert(const Key& key, const Value& value) { + Node* newNode = new Node(key, value); + newNode->next = head.load(); + Node* expected = newNode->next.load(); + while (!head.compare_exchange_weak(expected, newNode)) { + newNode->next = expected; + } + } + + void erase(const Key& key) { + Node* node = head.load(); + Node* prev = nullptr; + while (node) { + if (node->key == key) { + Node* next = node->next.load(); + if (prev) { + prev->next.compare_exchange_strong(node, next); + } else { + head.compare_exchange_strong(node, next); + } + delete node; + return; + } + prev = node; + node = node->next.load(); + } + } + }; + + std::vector> buckets_; + std::hash hasher_; + + auto getBucket(const Key& key) const -> Bucket& { + auto bucketIndex = hasher_(key) % buckets_.size(); + return *buckets_[bucketIndex]; + } + +public: + explicit LockFreeHashTable(size_t num_buckets = 16) + : buckets_(num_buckets) { + for (size_t i = 0; i < num_buckets; ++i) { + buckets_[i] = std::make_unique(); + } + } + + auto find(const Key& key) const -> std::optional { + return getBucket(key).find(key); + } + + void insert(const Key& key, const Value& value) { + getBucket(key).insert(key, value); + } + + void erase(const Key& key) { getBucket(key).erase(key); } + + [[nodiscard]] auto empty() const -> bool { + for (const auto& bucket : buckets_) { + if (bucket->head.load() != nullptr) { + return false; + } + } + return true; + } + + [[nodiscard]] auto size() const -> size_t { + size_t totalSize = 0; + for (const auto& bucket : buckets_) { + Node* node = bucket->head.load(); + while (node) { + ++totalSize; + node = node->next.load(); + } + } + return totalSize; + } + + void clear() { + for (const auto& bucket : buckets_) { + Node* node = bucket->head.load(); + while (node) { + Node* next = node->next.load(); + delete node; + node = next; + } + bucket->head.store(nullptr); + } + } + + // 迭代器类 + class Iterator { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = std::pair; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + Iterator( + typename std::vector>::iterator bucket_iter, + typename std::vector>::iterator bucket_end, + Node* node) + : bucket_iter_(bucket_iter), bucket_end_(bucket_end), node_(node) { + advancePastEmptyBuckets(); + } + + auto operator++() -> Iterator& { + if (node_) { + node_ = node_->next.load(); + if (!node_) { + ++bucket_iter_; + advancePastEmptyBuckets(); + } + } + return *this; + } + + auto operator++(int) -> Iterator { + Iterator tmp = *this; + ++(*this); + return tmp; + } + + auto operator==(const Iterator& other) const -> bool { + return bucket_iter_ == other.bucket_iter_ && node_ == other.node_; + } + + auto operator!=(const Iterator& other) const -> bool { + return !(*this == other); + } + + auto operator*() const -> reference { + return *reinterpret_cast(node_); + } + + auto operator->() const -> pointer { + return reinterpret_cast(node_); + } + + private: + void advancePastEmptyBuckets() { + while (bucket_iter_ != bucket_end_ && !node_) { + node_ = (*bucket_iter_)->head.load(); + if (!node_) { + ++bucket_iter_; + } + } + } + + typename std::vector>::iterator bucket_iter_; + typename std::vector>::iterator bucket_end_; + Node* node_; + }; + + auto begin() -> Iterator { + auto bucketIter = buckets_.begin(); + auto bucketEnd = buckets_.end(); + Node* node = + bucketIter != bucketEnd ? (*bucketIter)->head.load() : nullptr; + return Iterator(bucketIter, bucketEnd, node); + } + + auto end() -> Iterator { + return Iterator(buckets_.end(), buckets_.end(), nullptr); + } +}; + +template +class ThreadSafeVector { + std::atomic data_; + std::atomic capacity_; + std::atomic size_; + mutable std::shared_mutex resize_mutex_; + + void resize() { + std::unique_lock lock(resize_mutex_); + + size_t oldCapacity = capacity_.load(std::memory_order_relaxed); + size_t newCapacity = oldCapacity * 2; + T* newData = new T[newCapacity]; + + for (size_t i = 0; i < size_.load(std::memory_order_relaxed); ++i) { + newData[i] = std::move(data_.load(std::memory_order_relaxed)[i]); + } + + T* oldData = data_.exchange(newData, std::memory_order_acq_rel); + capacity_.store(newCapacity, std::memory_order_release); + + delete[] oldData; + } + +public: + explicit ThreadSafeVector(size_t initial_capacity = 16) + : data_(new T[initial_capacity]), + capacity_(initial_capacity), + size_(0) {} + + ~ThreadSafeVector() { delete[] data_.load(std::memory_order_relaxed); } + + void pushBack(const T& value) { + size_t currentSize = size_.load(std::memory_order_relaxed); + while (true) { + if (currentSize < capacity_.load(std::memory_order_relaxed)) { + if (size_.compare_exchange_weak(currentSize, currentSize + 1, + std::memory_order_acq_rel)) { + data_.load(std::memory_order_relaxed)[currentSize] = value; + return; + } + } else { + resize(); + } + currentSize = size_.load(std::memory_order_relaxed); + } + } + + void pushBack(T&& value) { + size_t currentSize = size_.load(std::memory_order_relaxed); + while (true) { + if (currentSize < capacity_.load(std::memory_order_relaxed)) { + if (size_.compare_exchange_weak(currentSize, currentSize + 1, + std::memory_order_acq_rel)) { + data_.load(std::memory_order_relaxed)[currentSize] = + std::move(value); + return; + } + } else { + resize(); + } + currentSize = size_.load(std::memory_order_relaxed); + } + } + + auto popBack() -> std::optional { + size_t currentSize = size_.load(std::memory_order_relaxed); + while (currentSize > 0) { + if (size_.compare_exchange_weak(currentSize, currentSize - 1, + std::memory_order_acq_rel)) { + return data_.load(std::memory_order_relaxed)[currentSize - 1]; + } + currentSize = size_.load(std::memory_order_relaxed); + } + return std::nullopt; + } + + auto at(size_t index) const -> std::optional { + if (index >= size_.load(std::memory_order_relaxed)) { + return std::nullopt; + } + return data_.load(std::memory_order_relaxed)[index]; + } + + auto empty() const -> bool { + return size_.load(std::memory_order_relaxed) == 0; + } + + auto getSize() const -> size_t { + return size_.load(std::memory_order_relaxed); + } + + auto getCapacity() const -> size_t { + return capacity_.load(std::memory_order_relaxed); + } + + void clear() { size_.store(0, std::memory_order_relaxed); } + + void shrinkToFit() { + std::unique_lock lock(resize_mutex_); + + size_t currentSize = size_.load(std::memory_order_relaxed); + T* newData = new T[currentSize]; + + for (size_t i = 0; i < currentSize; ++i) { + newData[i] = std::move(data_.load(std::memory_order_relaxed)[i]); + } + + T* oldData = data_.exchange(newData, std::memory_order_acq_rel); + capacity_.store(currentSize, std::memory_order_release); + + delete[] oldData; + } + + auto front() const -> T { + if (empty()) { + THROW_OUT_OF_RANGE("Vector is empty"); + } + return data_.load(std::memory_order_relaxed)[0]; + } + + auto back() const -> T { + if (empty()) { + THROW_OUT_OF_RANGE("Vector is empty"); + } + return data_.load( + std::memory_order_relaxed)[size_.load(std::memory_order_relaxed) - + 1]; + } + + auto operator[](size_t index) const -> T { + if (index >= size_.load(std::memory_order_relaxed)) { + THROW_OUT_OF_RANGE("Index out of range"); + } + return data_.load(std::memory_order_relaxed)[index]; + } +}; + +template +class LockFreeList { +private: + struct Node { + std::shared_ptr value; + std::atomic next; + explicit Node(T val) : value(std::make_shared(val)), next(nullptr) {} + }; + + std::atomic head_; + + // Hazard pointers structure + struct HazardPointer { + std::atomic id; + std::atomic pointer; + }; + + static const int MAX_HAZARD_POINTERS = 100; + HazardPointer hazard_pointers_[MAX_HAZARD_POINTERS]; + + // Get hazard pointer for current thread + auto getHazardPointerForCurrentThread() -> std::atomic& { + std::thread::id thisId = std::this_thread::get_id(); + for (auto& hazardPointer : hazard_pointers_) { + std::thread::id oldId; + if (hazardPointer.id.compare_exchange_strong(oldId, thisId)) { + return hazardPointer.pointer; + } + if (hazardPointer.id == thisId) { + return hazardPointer.pointer; + } + } + THROW_RUNTIME_ERROR("No hazard pointers available"); + } + + // Reclaim list + void reclaimLater(Node* node) { + retired_nodes_.push_back(node); + if (retired_nodes_.size() >= MAX_HAZARD_POINTERS) { + doReclamation(); + } + } + + // Reclaim retired nodes + void doReclamation() { + std::vector toReclaim; + for (Node* node : retired_nodes_) { + if (!isHazard(node)) { + toReclaim.push_back(node); + } + } + retired_nodes_.clear(); + for (Node* node : toReclaim) { + delete node; + } + } + + // Check if node is a hazard + auto isHazard(Node* node) -> bool { + for (auto& hazardPointer : hazard_pointers_) { + if (hazardPointer.pointer.load() == node) { + return true; + } + } + return false; + } + + std::vector retired_nodes_; + +public: + LockFreeList() : head_(nullptr) {} + + ~LockFreeList() { + while (head_.load()) { + Node* oldHead = head_.load(); + head_.store(oldHead->next); + delete oldHead; + } + } + + void pushFront(T value) { + Node* newNode = new Node(value); + newNode->next = head_.load(); + while (!head_.compare_exchange_weak(newNode->next, newNode)) { + } + } + + auto popFront() -> std::optional { + std::atomic& hazardPointer = getHazardPointerForCurrentThread(); + Node* oldHead = head_.load(); + do { + Node* temp; + do { + temp = oldHead; + hazardPointer.store(oldHead); + oldHead = head_.load(); + } while (oldHead != temp); + if (!oldHead) { + hazardPointer.store(nullptr); + return std::nullopt; + } + } while (!head_.compare_exchange_strong(oldHead, oldHead->next)); + hazardPointer.store(nullptr); + std::shared_ptr res = oldHead->value; + if (res.use_count() == 1) { + reclaimLater(oldHead); + } + return *res; + } + + [[nodiscard]] auto empty() const -> bool { return head_.load() == nullptr; } + + class Iterator { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = T*; + using reference = T&; + + Iterator(Node* node, LockFreeList* list) : node_(node), list_(list) {} + + auto operator++() -> Iterator& { + if (node_) { + node_ = node_->next.load(); + } + return *this; + } + + auto operator++(int) -> Iterator { + Iterator tmp = *this; + ++(*this); + return tmp; + } + + auto operator==(const Iterator& other) const -> bool { + return node_ == other.node_; + } + + auto operator!=(const Iterator& other) const -> bool { + return node_ != other.node_; + } + + auto operator*() const -> reference { return *(node_->value); } + + auto operator->() const -> pointer { return node_->value.get(); } + + private: + Node* node_; + LockFreeList* list_; + }; + + auto begin() -> Iterator { return Iterator(head_.load(), this); } + + auto end() -> Iterator { return Iterator(nullptr, this); } +}; + +template +LockFreeStack::Node::Node(T value_) : value(std::move(value_)) {} + +// 构造函数 +template +LockFreeStack::LockFreeStack() : head_(nullptr) {} + +// 析构函数 +template +LockFreeStack::~LockFreeStack() { + while (auto node = head_.load(std::memory_order_relaxed)) { + head_.store(node->next.load(std::memory_order_relaxed), + std::memory_order_relaxed); + delete node; + } +} + +// push 常量左值引用 +template +void LockFreeStack::push(const T& value) { + auto newNode = new Node(value); + newNode->next = head_.load(std::memory_order_relaxed); + Node* expected = newNode->next.load(std::memory_order_relaxed); + while (!head_.compare_exchange_weak(expected, newNode, + std::memory_order_release, + std::memory_order_relaxed)) { + newNode->next = expected; + } + approximateSize_.fetch_add(1, std::memory_order_relaxed); +} + +// push 右值引用 +template +void LockFreeStack::push(T&& value) { + auto newNode = new Node(std::move(value)); + newNode->next = head_.load(std::memory_order_relaxed); + Node* expected = newNode->next.load(std::memory_order_relaxed); + while (!head_.compare_exchange_weak(expected, newNode, + std::memory_order_release, + std::memory_order_relaxed)) { + newNode->next = expected; + } + approximateSize_.fetch_add(1, std::memory_order_relaxed); +} + +// pop +template +auto LockFreeStack::pop() -> std::optional { + Node* oldHead = head_.load(std::memory_order_relaxed); + while (oldHead && !head_.compare_exchange_weak(oldHead, oldHead->next, + std::memory_order_acquire, + std::memory_order_relaxed)) { + } + if (oldHead) { + T value = std::move(oldHead->value); + delete oldHead; + approximateSize_.fetch_sub(1, std::memory_order_relaxed); + return value; + } + return std::nullopt; +} + +// top +template +auto LockFreeStack::top() const -> std::optional { + Node* topNode = head_.load(std::memory_order_relaxed); + if (head_.load(std::memory_order_relaxed)) { + return std::optional(topNode->value); + } + return std::nullopt; +} + +// empty +template +auto LockFreeStack::empty() const -> bool { + return head_.load(std::memory_order_relaxed) == nullptr; +} + +// size +template +auto LockFreeStack::size() const -> int { + return approximateSize_.load(std::memory_order_relaxed); +} +} // namespace atom::async + +#endif // ATOM_ASYNC_SAFETYPE_HPP diff --git a/src/atom/async/slot.hpp b/src/atom/async/slot.hpp new file mode 100644 index 00000000..c9c12aac --- /dev/null +++ b/src/atom/async/slot.hpp @@ -0,0 +1,358 @@ +#ifndef ATOM_ASYNC_SIGNAL_HPP +#define ATOM_ASYNC_SIGNAL_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace atom::async { +template +class Signal { +public: + using SlotType = std::function; + + void connect(SlotType slot) { + std::lock_guard lock(mutex_); + slots_.push_back(std::move(slot)); + } + + void disconnect(const SlotType& slot) { + std::lock_guard lock(mutex_); + slots_.erase(std::remove_if(slots_.begin(), slots_.end(), + [&](const SlotType& s) { + return s.target_type() == + slot.target_type(); + }), + slots_.end()); + } + + void emit(Args... args) { + std::lock_guard lock(mutex_); + for (const auto& slot : slots_) { + slot(args...); + } + } + +private: + std::vector slots_; + std::mutex mutex_; +}; + +template +class AsyncSignal { +public: + using SlotType = std::function; + + void connect(SlotType slot) { + std::lock_guard lock(mutex_); + slots_.push_back(std::move(slot)); + } + + void disconnect(const SlotType& slot) { + std::lock_guard lock(mutex_); + slots_.erase(std::remove_if(slots_.begin(), slots_.end(), + [&](const SlotType& s) { + return s.target_type() == + slot.target_type(); + }), + slots_.end()); + } + + void emit(Args... args) { + std::vector> futures; + { + std::lock_guard lock(mutex_); + for (const auto& slot : slots_) { + futures.push_back( + std::async(std::launch::async, slot, args...)); + } + } + for (auto& future : futures) { + future.get(); + } + } + +private: + std::vector slots_; + std::mutex mutex_; +}; + +template +class AutoDisconnectSignal { +public: + using SlotType = std::function; + + auto connect(SlotType slot) -> int { + std::lock_guard lock(mutex_); + auto id = nextId_++; + slots_.emplace(id, std::move(slot)); + return id; + } + + void disconnect(int id) { + std::lock_guard lock(mutex_); + slots_.erase(id); + } + + void emit(Args... args) { + std::lock_guard lock(mutex_); + for (const auto& [id, slot] : slots_) { + slot(args...); + } + } + +private: + std::map slots_; + std::mutex mutex_; + int nextId_ = 0; +}; + +template +class ChainedSignal { +public: + using SlotType = std::function; + + void connect(SlotType slot) { + std::lock_guard lock(mutex_); + slots_.push_back(std::move(slot)); + } + + void addChain(ChainedSignal& nextSignal) { + std::lock_guard lock(mutex_); + chains_.push_back(&nextSignal); + } + + void emit(Args... args) { + std::lock_guard lock(mutex_); + for (const auto& slot : slots_) { + slot(args...); + } + for (auto& chain : chains_) { + chain->emit(args...); + } + } + +private: + std::vector slots_; + std::vector*> chains_; + std::mutex mutex_; +}; + +template +class TemplateSignal { +public: + using SlotType = std::function; + + void connect(SlotType slot) { + std::lock_guard lock(mutex_); + slots_.push_back(std::move(slot)); + } + + void disconnect(const SlotType& slot) { + std::lock_guard lock(mutex_); + slots_.erase(std::remove_if(slots_.begin(), slots_.end(), + [&](const SlotType& s) { + return s.target_type() == + slot.target_type(); + }), + slots_.end()); + } + + void emit(Args... args) { + std::lock_guard lock(mutex_); + for (const auto& slot : slots_) { + slot(args...); + } + } + +private: + std::vector slots_; + std::mutex mutex_; +}; + +template +class ThreadSafeSignal { +public: + using SlotType = std::function; + + void connect(SlotType slot) { + std::lock_guard lock(mutex_); + slots_.push_back(std::move(slot)); + } + + void disconnect(const SlotType& slot) { + std::lock_guard lock(mutex_); + slots_.erase(std::remove_if(slots_.begin(), slots_.end(), + [&](const SlotType& s) { + return s.target_type() == + slot.target_type(); + }), + slots_.end()); + } + + void emit(Args... args) { + std::vector> tasks; + { + std::lock_guard lock(mutex_); + for (const auto& slot : slots_) { + tasks.emplace_back([slot, args...]() { slot(args...); }); + } + } + for (auto& task : tasks) { + std::async(std::launch::async, task).get(); + } + } + +private: + std::vector slots_; + std::mutex mutex_; +}; + +template +class BroadcastSignal { +public: + using SlotType = std::function; + + void connect(SlotType slot) { + std::lock_guard lock(mutex_); + slots_.push_back(std::move(slot)); + } + + void disconnect(const SlotType& slot) { + std::lock_guard lock(mutex_); + slots_.erase(std::remove_if(slots_.begin(), slots_.end(), + [&](const SlotType& s) { + return s.target_type() == + slot.target_type(); + }), + slots_.end()); + } + + void emit(Args... args) { + std::lock_guard lock(mutex_); + for (const auto& slot : slots_) { + slot(args...); + } + for (const auto& signal : chainedSignals_) { + signal->emit(args...); + } + } + + void addChain(BroadcastSignal& signal) { + std::lock_guard lock(mutex_); + chainedSignals_.push_back(&signal); + } + +private: + std::vector slots_; + std::vector*> chainedSignals_; + std::mutex mutex_; +}; + +template +class LimitedSignal { +public: + using SlotType = std::function; + + explicit LimitedSignal(size_t maxCalls) : maxCalls_(maxCalls) {} + + void connect(SlotType slot) { + std::lock_guard lock(mutex_); + slots_.push_back(std::move(slot)); + } + + void disconnect(const SlotType& slot) { + std::lock_guard lock(mutex_); + slots_.erase(std::remove_if(slots_.begin(), slots_.end(), + [&](const SlotType& s) { + return s.target_type() == + slot.target_type(); + }), + slots_.end()); + } + + void emit(Args... args) { + std::lock_guard lock(mutex_); + if (callCount_ >= maxCalls_) { + return; + } + for (const auto& slot : slots_) { + slot(args...); + } + ++callCount_; + } + +private: + std::vector slots_; + size_t maxCalls_; + size_t callCount_{}; + std::mutex mutex_; +}; + +template +class DynamicSignal { +public: + using SlotType = std::function; + + void connect(SlotType slot) { + std::lock_guard lock(mutex_); + slots_.push_back(std::move(slot)); + } + + void disconnect(const SlotType& slot) { + std::lock_guard lock(mutex_); + slots_.erase(std::remove_if(slots_.begin(), slots_.end(), + [&](const SlotType& s) { + return s.target_type() == + slot.target_type(); + }), + slots_.end()); + } + + void emit(Args... args) { + std::lock_guard lock(mutex_); + for (const auto& slot : slots_) { + slot(args...); + } + } + +private: + std::vector slots_; + std::mutex mutex_; +}; + +template +class ScopedSignal { +public: + using SlotType = std::function; + + void connect(std::shared_ptr slotPtr) { + std::lock_guard lock(mutex_); + slots_.push_back(slotPtr); + } + + void emit(Args... args) { + std::lock_guard lock(mutex_); + auto it = slots_.begin(); + while (it != slots_.end()) { + if (auto slot = *it; slot) { + (*slot)(args...); + ++it; + } else { + it = slots_.erase(it); + } + } + } + +private: + std::vector> slots_; + std::mutex mutex_; +}; + +} // namespace atom::async + +#endif diff --git a/src/atom/async/thread_wrapper.hpp b/src/atom/async/thread_wrapper.hpp index cfb6a51f..3c3fd8c9 100644 --- a/src/atom/async/thread_wrapper.hpp +++ b/src/atom/async/thread_wrapper.hpp @@ -15,15 +15,11 @@ Description: A simple wrapper of std::jthread #ifndef ATOM_ASYNC_THREAD_WRAPPER_HPP #define ATOM_ASYNC_THREAD_WRAPPER_HPP -#include -#include -#include -#include -#include #include #include #include #include +#include "type/noncopyable.hpp" namespace atom::async { /** @@ -32,36 +28,13 @@ namespace atom::async { * This class provides a convenient interface for managing a C++20 jthread, * allowing for starting, stopping, and joining threads easily. */ -class Thread { +class Thread : public NonCopyable { public: /** * @brief Default constructor. */ Thread() = default; - /** - * @brief Move constructor. - * @param other The Thread object to move from. - */ - Thread(Thread&&) noexcept = default; - - /** - * @brief Move assignment operator. - * @param other The Thread object to move from. - * @return Reference to the assigned Thread object. - */ - Thread& operator=(Thread&&) noexcept = default; - - /** - * @brief Deleted copy constructor. - */ - Thread(const Thread&) = delete; - - /** - * @brief Deleted copy assignment operator. - */ - Thread& operator=(const Thread&) = delete; - /** * @brief Starts a new thread with the specified callable object and * arguments. @@ -94,7 +67,7 @@ class Thread { /** * @brief Requests the thread to stop execution. */ - void request_stop() { thread_.request_stop(); } + void requestStop() { thread_.request_stop(); } /** * @brief Waits for the thread to finish execution. @@ -105,7 +78,9 @@ class Thread { * @brief Checks if the thread is currently running. * @return True if the thread is running, false otherwise. */ - [[nodiscard]] bool running() const noexcept { return thread_.joinable(); } + [[nodiscard]] auto running() const noexcept -> bool { + return thread_.joinable(); + } /** * @brief Swaps the content of this Thread object with another Thread @@ -118,13 +93,13 @@ class Thread { * @brief Gets the underlying std::jthread object. * @return Reference to the underlying std::jthread object. */ - [[nodiscard]] std::jthread& get_thread() noexcept { return thread_; } + [[nodiscard]] auto getThread() noexcept -> std::jthread& { return thread_; } /** * @brief Gets the underlying std::jthread object (const version). * @return Constant reference to the underlying std::jthread object. */ - [[nodiscard]] const std::jthread& get_thread() const noexcept { + [[nodiscard]] auto getThread() const noexcept -> const std::jthread& { return thread_; } @@ -132,7 +107,7 @@ class Thread { * @brief Gets the ID of the thread. * @return The ID of the thread. */ - [[nodiscard]] std::thread::id get_id() const noexcept { + [[nodiscard]] auto getId() const noexcept -> std::thread::id { return thread_.get_id(); } @@ -140,7 +115,7 @@ class Thread { * @brief Gets the underlying std::stop_source object. * @return The underlying std::stop_source object. */ - [[nodiscard]] std::stop_source get_stop_source() noexcept { + [[nodiscard]] auto getStopSource() noexcept -> std::stop_source { return thread_.get_stop_source(); } @@ -148,7 +123,7 @@ class Thread { * @brief Gets the underlying std::stop_token object. * @return The underlying std::stop_token object. */ - [[nodiscard]] std::stop_token get_stop_token() const noexcept { + [[nodiscard]] auto getStopToken() const noexcept -> std::stop_token { return thread_.get_stop_token(); } diff --git a/src/atom/async/threadlocal.hpp b/src/atom/async/threadlocal.hpp index 7cf2a762..d69add87 100644 --- a/src/atom/async/threadlocal.hpp +++ b/src/atom/async/threadlocal.hpp @@ -16,15 +16,15 @@ Description: ThreadLocal #define ATOM_ASYNC_THREADLOCAL_HPP #include -#include #include #include #include #include #include +#include "type/noncopyable.hpp" template -class ThreadLocal { +class ThreadLocal : public NonCopyable { public: using InitializerFn = std::function; @@ -33,13 +33,10 @@ class ThreadLocal { explicit ThreadLocal(InitializerFn initializer) : initializer_(std::move(initializer)) {} - ThreadLocal(const ThreadLocal&) = delete; - ThreadLocal& operator=(const ThreadLocal&) = delete; - ThreadLocal(ThreadLocal&&) = default; - ThreadLocal& operator=(ThreadLocal&&) = default; + auto operator=(ThreadLocal&&) -> ThreadLocal& = default; - T& get() { + auto get() -> T& { auto tid = std::this_thread::get_id(); std::unique_lock lock(mutex_); auto [it, inserted] = values_.try_emplace(tid); @@ -51,11 +48,11 @@ class ThreadLocal { return it->second.value(); } - T* operator->() { return &get(); } - const T* operator->() const { return &get(); } + auto operator->() -> T* { return &get(); } + auto operator->() const -> const T* { return &get(); } - T& operator*() { return get(); } - const T& operator*() const { return get(); } + auto operator*() -> T& { return get(); } + auto operator*() const -> const T& { return get(); } void reset(T value = T()) { auto tid = std::this_thread::get_id(); @@ -63,14 +60,14 @@ class ThreadLocal { values_[tid] = std::make_optional(std::move(value)); } - bool has_value() const { + auto hasValue() const -> bool { auto tid = std::this_thread::get_id(); std::lock_guard lock(mutex_); auto it = values_.find(tid); return it != values_.end() && it->second.has_value(); } - T* get_pointer() { + auto getPointer() -> T* { auto tid = std::this_thread::get_id(); std::lock_guard lock(mutex_); auto it = values_.find(tid); @@ -79,7 +76,7 @@ class ThreadLocal { : nullptr; } - const T* get_pointer() const { + auto getPointer() const -> const T* { auto tid = std::this_thread::get_id(); std::lock_guard lock(mutex_); auto it = values_.find(tid); @@ -89,7 +86,7 @@ class ThreadLocal { } template - void for_each(Func&& func) { + void forEach(Func&& func) { std::lock_guard lock(mutex_); for (auto& [tid, value_opt] : values_) { if (value_opt.has_value()) { diff --git a/src/atom/async/timer.cpp b/src/atom/async/timer.cpp index e2904d88..d9b66bff 100644 --- a/src/atom/async/timer.cpp +++ b/src/atom/async/timer.cpp @@ -13,6 +13,8 @@ Description: Timer class for C++ **************************************************/ #include "timer.hpp" +#include +#include "error/exception.hpp" namespace atom::async { TimerTask::TimerTask(std::function func, unsigned int delay, @@ -25,19 +27,18 @@ TimerTask::TimerTask(std::function func, unsigned int delay, std::chrono::steady_clock::now() + std::chrono::milliseconds(m_delay); } -bool TimerTask::operator<(const TimerTask &other) const { +auto TimerTask::operator<(const TimerTask &other) const -> bool { if (m_priority != other.m_priority) { return m_priority > other.m_priority; - } else { - return m_nextExecutionTime > other.m_nextExecutionTime; } + return m_nextExecutionTime > other.m_nextExecutionTime; } void TimerTask::run() { try { m_func(); } catch (const std::exception &e) { - std::throw_with_nested(std::runtime_error("Failed to run timer task")); + THROW_RUNTIME_ERROR("Failed to run timer task: ", e.what()); } if (m_repeatCount > 0) { --m_repeatCount; @@ -81,7 +82,7 @@ void Timer::stop() { m_cond.notify_all(); } -std::chrono::steady_clock::time_point Timer::now() const { +auto Timer::now() const -> std::chrono::steady_clock::time_point { return std::chrono::steady_clock::now(); } @@ -118,7 +119,7 @@ void Timer::run() { } } -int Timer::getTaskCount() const { +auto Timer::getTaskCount() const -> size_t { std::unique_lock lock(m_mutex); return m_taskQueue.size(); } diff --git a/src/atom/async/timer.hpp b/src/atom/async/timer.hpp index e99386db..fbf6a637 100644 --- a/src/atom/async/timer.hpp +++ b/src/atom/async/timer.hpp @@ -17,14 +17,12 @@ Description: Timer class for C++ #include #include -#include +#include #include #include -#include #include #include #include -#include namespace atom::async { /** @@ -41,8 +39,8 @@ class TimerTask { * for infinite repetition. * @param priority The priority of the task. */ - explicit TimerTask(std::function func, unsigned int delay, int repeatCount, - int priority); + explicit TimerTask(std::function func, unsigned int delay, + int repeatCount, int priority); /** * @brief Comparison operator for comparing two TimerTask objects based on @@ -52,7 +50,7 @@ class TimerTask { * @return True if this task's next execution time is earlier than the other * task's next execution time. */ - bool operator<(const TimerTask &other) const; + auto operator<(const TimerTask &other) const -> bool; /** * @brief Executes the task's associated function. @@ -64,7 +62,7 @@ class TimerTask { * * @return The steady clock time point representing the next execution time. */ - std::chrono::steady_clock::time_point getNextExecutionTime() const; + auto getNextExecutionTime() const -> std::chrono::steady_clock::time_point; std::function m_func; ///< The function to be executed. unsigned int m_delay; ///< The delay before the first execution. @@ -100,8 +98,9 @@ class Timer { * @return A future representing the result of the function execution. */ template - [[nodiscard]] std::future::type> - setTimeout(Function &&func, unsigned int delay, Args &&...args); + [[nodiscard]] auto setTimeout(Function &&func, unsigned int delay, + Args &&...args) + -> std::future::type>; /** * @brief Schedules a task to be executed repeatedly at a specified @@ -120,7 +119,7 @@ class Timer { void setInterval(Function &&func, unsigned int interval, int repeatCount, int priority, Args &&...args); - [[nodiscard]] std::chrono::steady_clock::time_point now() const; + [[nodiscard]] auto now() const -> std::chrono::steady_clock::time_point; /** * @brief Cancels all scheduled tasks. @@ -151,7 +150,7 @@ class Timer { template void setCallback(Function &&func); - [[nodiscard]] int getTaskCount() const; + [[nodiscard]] auto getTaskCount() const -> size_t; private: /** @@ -167,16 +166,15 @@ class Timer { * @return A future representing the result of the function execution. */ template - std::future::type> addTask( - Function &&func, unsigned int delay, int repeatCount, int priority, - Args &&...args); + auto addTask(Function &&func, unsigned int delay, int repeatCount, + int priority, Args &&...args) + -> std::future::type>; /** * @brief Main execution loop for processing and running tasks. */ void run(); -private: #if _cplusplus >= 202203L std::jthread m_thread; ///< The thread for running the timer loop (C++20 or later). @@ -195,8 +193,8 @@ class Timer { }; template -std::future::type> Timer::setTimeout( - Function &&func, unsigned int delay, Args &&...args) { +auto Timer::setTimeout(Function &&func, unsigned int delay, Args &&...args) + -> std::future::type> { using ReturnType = typename std::result_of::type; auto task = std::make_shared>( std::bind(std::forward(func), std::forward(args)...)); diff --git a/src/atom/async/trigger.hpp b/src/atom/async/trigger.hpp index 569f615c..4f1813ca 100644 --- a/src/atom/async/trigger.hpp +++ b/src/atom/async/trigger.hpp @@ -99,8 +99,8 @@ class Trigger { * @return A future object that can be used to track the completion of the * asynchronous trigger. */ - std::future scheduleAsyncTrigger(const std::string &event, - const ParamType ¶m); + auto scheduleAsyncTrigger(const std::string &event, + const ParamType ¶m) -> std::future; /** * @brief Cancel the scheduled triggering of a specific event. @@ -115,21 +115,112 @@ class Trigger { void cancelAllTriggers(); private: - std::mutex m_mutex; /**< Mutex used to synchronize access to the callback + std::mutex m_mutex_; /**< Mutex used to synchronize access to the callback data structure. */ #if ENABLE_FASTHASH emhash8::HashMap>> - m_callbacks; /**< Hash map to store registered callbacks for events. */ + m_callbacks_; /**< Hash map to store registered callbacks for events. */ #else std::unordered_map>> - m_callbacks; /**< Hash map to store registered callbacks for events. */ + m_callbacks_; /**< Hash map to store registered callbacks for events. */ #endif }; -} // namespace atom::async -#include "trigger.inl" +template +void Trigger::registerCallback(const std::string &event, + const Callback &callback, + CallbackPriority priority) { + std::lock_guard lock(m_mutex_); + auto &callbacks = m_callbacks_[event]; + auto pos = std::find_if( + callbacks.begin(), callbacks.end(), + [&](const std::pair &cb) { + return cb.second.target_type() == callback.target_type() && + cb.second.template target() == + callback.template target(); + }); + if (pos != callbacks.end()) { + pos->first = priority; + } else { + callbacks.emplace_back(priority, callback); + } +} + +template +void Trigger::unregisterCallback(const std::string &event, + const Callback &callback) { + std::lock_guard lock(m_mutex_); + auto &callbacks = m_callbacks_[event]; + callbacks.erase( + std::remove_if( + callbacks.begin(), callbacks.end(), + [&](const std::pair &cb) { + return cb.second.target_type() == callback.target_type() && + cb.second.template target() == + callback.template target(); + }), + callbacks.end()); +} + +template +void Trigger::trigger(const std::string &event, + const ParamType ¶m) { + std::lock_guard lock(m_mutex_); + auto &callbacks = m_callbacks_[event]; + std::sort(callbacks.begin(), callbacks.end(), + [](const std::pair &cb1, + const std::pair &cb2) { + return static_cast(cb1.first) > + static_cast(cb2.first); + }); + for (auto &callback : callbacks) { + try { + callback.second(param); + } catch (std::exception &e) { + } + } +} + +template +void Trigger::scheduleTrigger(const std::string &event, + const ParamType ¶m, + std::chrono::milliseconds delay) { + std::thread([this, event, param, delay]() { + std::this_thread::sleep_for(delay); + trigger(event, param); + }).detach(); +} + +template +auto Trigger::scheduleAsyncTrigger( + const std::string &event, const ParamType ¶m) -> std::future { + auto promise = std::make_shared>(); + auto future = promise->get_future(); + std::thread([this, event, param, promise]() mutable { + try { + trigger(event, param); + promise->set_value(); + } catch (...) { + promise->set_exception(std::current_exception()); + } + }).detach(); + return future; +} + +template +void Trigger::cancelTrigger(const std::string &event) { + std::lock_guard lock(m_mutex_); + m_callbacks_.erase(event); +} + +template +void Trigger::cancelAllTriggers() { + std::lock_guard lock(m_mutex_); + m_callbacks_.clear(); +} +} // namespace atom::async #endif diff --git a/src/atom/async/trigger.inl b/src/atom/async/trigger.inl deleted file mode 100644 index 576efef1..00000000 --- a/src/atom/async/trigger.inl +++ /dev/null @@ -1,115 +0,0 @@ -/* - * trigger_impl.hpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-12-14 - -Description: Trigger class for C++ - -**************************************************/ - -#ifndef ATOM_ASYNC_TRIGGER_IMPL_HPP -#define ATOM_ASYNC_TRIGGER_IMPL_HPP - -#include "trigger.hpp" - -namespace atom::async { -template -void Trigger::registerCallback(const std::string &event, - const Callback &callback, - CallbackPriority priority) { - std::lock_guard lock(m_mutex); - auto &callbacks = m_callbacks[event]; - auto pos = std::find_if( - callbacks.begin(), callbacks.end(), - [&](const std::pair &cb) { - return cb.second.target_type() == callback.target_type() && - cb.second.template target() == - callback.template target(); - }); - if (pos != callbacks.end()) { - pos->first = priority; - } else { - callbacks.emplace_back(priority, callback); - } -} - -template -void Trigger::unregisterCallback(const std::string &event, - const Callback &callback) { - std::lock_guard lock(m_mutex); - auto &callbacks = m_callbacks[event]; - callbacks.erase( - std::remove_if( - callbacks.begin(), callbacks.end(), - [&](const std::pair &cb) { - return cb.second.target_type() == callback.target_type() && - cb.second.template target() == - callback.template target(); - }), - callbacks.end()); -} - -template -void Trigger::trigger(const std::string &event, - const ParamType ¶m) { - std::lock_guard lock(m_mutex); - auto &callbacks = m_callbacks[event]; - std::sort(callbacks.begin(), callbacks.end(), - [](const std::pair &cb1, - const std::pair &cb2) { - return static_cast(cb1.first) > - static_cast(cb2.first); - }); - for (auto &callback : callbacks) { - try { - callback.second(param); - } catch (std::exception &e) { - } - } -} - -template -void Trigger::scheduleTrigger(const std::string &event, - const ParamType ¶m, - std::chrono::milliseconds delay) { - std::thread([this, event, param, delay]() { - std::this_thread::sleep_for(delay); - trigger(event, param); - }).detach(); -} - -template -std::future Trigger::scheduleAsyncTrigger( - const std::string &event, const ParamType ¶m) { - auto promise = std::make_shared>(); - auto future = promise->get_future(); - std::thread([this, event, param, promise]() mutable { - try { - trigger(event, param); - promise->set_value(); - } catch (...) { - promise->set_exception(std::current_exception()); - } - }).detach(); - return future; -} - -template -void Trigger::cancelTrigger(const std::string &event) { - std::lock_guard lock(m_mutex); - m_callbacks.erase(event); -} - -template -void Trigger::cancelAllTriggers() { - std::lock_guard lock(m_mutex); - m_callbacks.clear(); -} -} // namespace atom::async - -#endif diff --git a/src/atom/components/CMakeLists.txt b/src/atom/components/CMakeLists.txt index e92824f4..245c77ed 100644 --- a/src/atom/components/CMakeLists.txt +++ b/src/atom/components/CMakeLists.txt @@ -1,62 +1,50 @@ # CMakeLists.txt for Atom-Component -# This project is licensed under the terms of the GPL3 license. +# This project adheres to the GPL3 license. # -# Project Name: Atom-Component -# Description: The core component library of Atom -# Author: Max Qian -# License: GPL3 +# Project Details: +# Name: Atom-Component +# Description: Central component library for the Atom framework +# Author: Max Qian +# License: GPL3 cmake_minimum_required(VERSION 3.20) -project(atom-component C CXX) - - -list(APPEND ${PROJECT_NAME}_SOURCES +project(atom-component LANGUAGES C CXX) +# Source files with project-specific prefix +set(${PROJECT_NAME}_SOURCES ) -# Headers -list(APPEND ${PROJECT_NAME}_HEADERS +set(${PROJECT_NAME}_HEADERS component.hpp dispatch.hpp - dispatch.inl types.hpp var.hpp - var.inl ) -list(APPEND ${PROJECT_NAME}_LIBS +# Dependencies +set(${PROJECT_NAME}_LIBS loguru atom-error atom-type atom-utils - ) - -# Build Object Library -add_library(${PROJECT_NAME}_OBJECT OBJECT) -set_property(TARGET ${PROJECT_NAME}_OBJECT PROPERTY POSITION_INDEPENDENT_CODE 1) - -target_sources(${PROJECT_NAME}_OBJECT - PUBLIC - ${${PROJECT_NAME}_HEADERS} - PRIVATE - ${${PROJECT_NAME}_SOURCES} ) -target_link_libraries(${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) +# Include directories +include_directories(.) -add_library(${PROJECT_NAME} STATIC) +# Object library for headers and sources with project prefix +add_library(${PROJECT_NAME}_OBJECT OBJECT ${${PROJECT_NAME}_HEADERS} ${${PROJECT_NAME}_SOURCES}) +# set_target_properties(${PROJECT_NAME}_OBJECT PROPERTIES LINKER_LANGUAGE CXX) -target_link_libraries(${PROJECT_NAME} ${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) -target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT}) -target_link_libraries(${PROJECT_NAME} atom-utils) -target_link_libraries(${PROJECT_NAME} atom-type) -target_include_directories(${PROJECT_NAME} PUBLIC .) +# Static library target +add_library(${PROJECT_NAME} SHARED $) -set_target_properties(${PROJECT_NAME} PROPERTIES - VERSION ${CMAKE_HYDROGEN_VERSION_STRING} - SOVERSION ${HYDROGEN_SOVERSION} - OUTPUT_NAME ${PROJECT_NAME} -) +# Set project properties and definitions +# set_property(TARGET ${PROJECT_NAME} PROPERTY POSITION_INDEPENDENT_CODE ON) + +# Link dependencies to the main target +target_link_libraries(${PROJECT_NAME} PRIVATE ${${PROJECT_NAME}_LIBS} ${CMAKE_THREAD_LIBS_INIT}) +# Install rules install(TARGETS ${PROJECT_NAME} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ) diff --git a/src/atom/components/component.hpp b/src/atom/components/component.hpp index 1780ade1..19078dca 100644 --- a/src/atom/components/component.hpp +++ b/src/atom/components/component.hpp @@ -15,12 +15,15 @@ Description: Basic Component Definition #ifndef ATOM_COMPONENT_HPP #define ATOM_COMPONENT_HPP +#include #include +#include #include #include "dispatch.hpp" +#include "error/exception.hpp" #include "macro.hpp" -#include "registry.hpp" +#include "module_macro.hpp" #include "types.hpp" #include "var.hpp" @@ -28,19 +31,29 @@ Description: Basic Component Definition #include "atom/function/conversion.hpp" #include "atom/function/type_caster.hpp" #include "atom/function/type_info.hpp" -#include "atom/type/noncopyable.hpp" +#include "atom/log/loguru.hpp" class Component : public std::enable_shared_from_this { public: + /** + * @brief Type definition for initialization function. + */ + using InitFunc = std::function; + + /** + * @brief Type definition for cleanup function. + */ + using CleanupFunc = std::function; + /** * @brief Constructs a new Component object. */ - explicit Component(const std::string& name); + explicit Component(std::string name); /** * @brief Destroys the Component object. */ - virtual ~Component(); + virtual ~Component() = default; // ------------------------------------------------------------------- // Inject methods @@ -59,7 +72,7 @@ class Component : public std::enable_shared_from_this { * @note This function is called by the server when the plugin is loaded. * @note This function should be overridden by the plugin. */ - virtual bool initialize(); + virtual auto initialize() -> bool; /** * @brief Destroys the plugin. @@ -70,7 +83,7 @@ class Component : public std::enable_shared_from_this { * @note The plugin should not be used after this function is called. * @note This is for the plugin to release any resources it has allocated. */ - virtual bool destroy(); + virtual auto destroy() -> bool; /** * @brief Gets the name of the plugin. @@ -84,7 +97,9 @@ class Component : public std::enable_shared_from_this { * * @return The type information of the plugin. */ - atom::meta::Type_Info getTypeInfo() const; + atom::meta::TypeInfo getTypeInfo() const; + + void setTypeInfo(atom::meta::TypeInfo typeInfo); void setTypeInfo(atom::meta::Type_Info typeInfo); @@ -97,18 +112,18 @@ class Component : public std::enable_shared_from_this { const std::string& description = "", const std::string& alias = "", const std::string& group = "") { - m_VariableManager->addVariable(name, initialValue, description, alias, - group); + m_VariableManager_->addVariable(name, initialValue, description, alias, + group); } template void setRange(const std::string& name, T min, T max) { - m_VariableManager->setRange(name, min, max); + m_VariableManager_->setRange(name, min, max); } void setStringOptions(const std::string& name, - std::vector options) { - m_VariableManager->setStringOptions(name, options); + const std::vector& options) { + m_VariableManager_->setStringOptions(name, options); } /** @@ -118,7 +133,7 @@ class Component : public std::enable_shared_from_this { */ template std::shared_ptr> getVariable(const std::string& name) { - return m_VariableManager->getVariable(name); + return m_VariableManager_->getVariable(name); } [[nodiscard]] bool hasVariable(const std::string& name) const; @@ -132,7 +147,7 @@ class Component : public std::enable_shared_from_this { */ template void setValue(const std::string& name, T newValue) { - m_VariableManager->setValue(name, newValue); + m_VariableManager_->setValue(name, newValue); } std::vector getVariableNames() const; @@ -262,16 +277,16 @@ class Component : public std::enable_shared_from_this { const std::string& description = ""); template - void def_m(const std::string& name, MemberType ClassType::*member_var, - std::shared_ptr instance, - const std::string& group = "", - const std::string& description = ""); + void defM(const std::string& name, MemberType ClassType::*member_var, + std::shared_ptr instance, + const std::string& group = "", + const std::string& description = ""); template - void def_m(const std::string& name, MemberType ClassType::*member_var, - PointerSentinel instance, - const std::string& group = "", - const std::string& description = ""); + void defM(const std::string& name, MemberType ClassType::*member_var, + PointerSentinel instance, + const std::string& group = "", + const std::string& description = ""); template void def(const std::string& name, const std::string& group = "", @@ -282,66 +297,70 @@ class Component : public std::enable_shared_from_this { const std::string& description = ""); template - void def_constructor(const std::string& name, const std::string& group = "", - const std::string& description = ""); + void defConstructor(const std::string& name, const std::string& group = "", + const std::string& description = ""); template - void def_default_constructor(const std::string& name, - const std::string& group = "", - const std::string& description = ""); + void defDefaultConstructor(const std::string& name, + const std::string& group = "", + const std::string& description = ""); template - void def_type(std::string_view name, const atom::meta::Type_Info& ti, - const std::string& group = "", - const std::string& description = ""); + void defType(std::string_view name, const std::string& group = "", + const std::string& description = ""); template - void def_conversion(std::function func); + void defConversion(std::function func); template - void def_base_class(); + void defBaseClass(); - void def_class_conversion( - const std::shared_ptr& conversion); + void defClassConversion( + const std::shared_ptr& conversion); - void addAlias(const std::string& name, const std::string& alias); + void addAlias(const std::string& name, const std::string& alias) const; - void addGroup(const std::string& name, const std::string& group); + void addGroup(const std::string& name, const std::string& group) const; - void setTimeout(const std::string& name, std::chrono::milliseconds timeout); + void setTimeout(const std::string& name, + std::chrono::milliseconds timeout) const; template - std::any dispatch(const std::string& name, Args&&... args) { - return m_CommandDispatcher->dispatch(name, std::forward(args)...); + auto dispatch(const std::string& name, Args&&... args) -> std::any { + return m_CommandDispatcher_->dispatch(name, + std::forward(args)...); } - std::any dispatch(const std::string& name, - const std::vector& args) { - return m_CommandDispatcher->dispatch(name, args); + auto dispatch(const std::string& name, + const std::vector& args) const -> std::any { + return m_CommandDispatcher_->dispatch(name, args); } - [[nodiscard]] bool has(const std::string& name) const; + [[nodiscard]] auto has(const std::string& name) const -> bool; - [[nodiscard]] bool has_type(std::string_view name) const; + [[nodiscard]] auto hasType(std::string_view name) const -> bool; template - [[nodiscard]] bool has_conversion() const; + [[nodiscard]] auto hasConversion() const -> bool; - void removeCommand(const std::string& name); + void removeCommand(const std::string& name) const; - std::vector getCommandsInGroup(const std::string& group) const; + auto getCommandsInGroup(const std::string& group) const + -> std::vector; - std::string getCommandDescription(const std::string& name) const; + auto getCommandDescription(const std::string& name) const -> std::string; #if ENABLE_FASTHASH emhash::HashSet getCommandAliases( const std::string& name) const; #else - std::unordered_set getCommandAliases( - const std::string& name) const; + auto getCommandAliases(const std::string& name) const + -> std::unordered_set; #endif - std::vector getAllCommands() const; + auto getAllCommands() const -> std::vector; + + auto getRegisteredTypes() const -> std::vector; std::vector getRegisteredTypes() const; @@ -358,7 +377,7 @@ class Component : public std::enable_shared_from_this { * @return The names of the components that are needed by this component. * @note This will be called when the component is initialized. */ - std::vector getNeededComponents() const; + static auto getNeededComponents() -> std::vector; void addOtherComponent(const std::string& name, const std::weak_ptr& component); @@ -367,37 +386,532 @@ class Component : public std::enable_shared_from_this { void clearOtherComponents(); - std::weak_ptr getOtherComponent(const std::string& name); + auto getOtherComponent(const std::string& name) -> std::weak_ptr; + + auto runCommand(const std::string& name, + const std::vector& args) -> std::any; - std::any runCommand(const std::string& name, - const std::vector& args); + InitFunc initFunc; /**< The initialization function for the component. */ + CleanupFunc cleanupFunc; /**< The cleanup function for the component. */ private: template - void define_accessors(const std::string& name, - MemberType ClassType::*member_var, - InstanceType instance, const std::string& group = "", - const std::string& description = ""); + void defineAccessors(const std::string& name, + MemberType ClassType::*member_var, + InstanceType instance, const std::string& group = "", + const std::string& description = ""); -private: - std::string m_name; - std::string m_doc; - std::string m_configPath; - std::string m_infoPath; - atom::meta::Type_Info m_typeInfo; - std::unordered_map m_classes; - - std::shared_ptr - m_CommandDispatcher; ///< The command dispatcher for managing commands. - std::shared_ptr - m_VariableManager; ///< The variable registry for managing variables. - - std::unordered_map> m_OtherComponents; - - std::shared_ptr m_TypeCaster; - std::shared_ptr m_TypeConverter; + std::string m_name_; + std::string m_doc_; + std::string m_configPath_; + std::string m_infoPath_; + atom::meta::TypeInfo m_typeInfo_{atom::meta::userType()}; + std::unordered_map m_classes_; + + ///< managing commands. + std::shared_ptr m_VariableManager_{ + std::make_shared()}; ///< The variable registry for + ///< managing variables. + + std::unordered_map> + m_OtherComponents_; + + std::shared_ptr m_TypeCaster_{ + atom::meta::TypeCaster::createShared()}; + std::shared_ptr m_TypeConverter_{ + atom::meta::TypeConversions::createShared()}; + + std::shared_ptr m_CommandDispatcher_{ + std::make_shared( + m_TypeCaster_)}; ///< The command dispatcher for }; -#include "component.inl" +ATOM_INLINE Component::Component(std::string name) : m_name_(std::move(name)) {} + +ATOM_INLINE auto Component::getInstance() const + -> std::weak_ptr { + return shared_from_this(); +} + +ATOM_INLINE auto Component::initialize() -> bool { + LOG_F(INFO, "Initializing component: {}", m_name_); + return true; +} + +ATOM_INLINE auto Component::destroy() -> bool { + LOG_F(INFO, "Destroying component: {}", m_name_); + return true; +} + +ATOM_INLINE auto Component::getName() const -> std::string { return m_name_; } + +ATOM_INLINE auto Component::getTypeInfo() const -> atom::meta::TypeInfo { + return m_typeInfo_; +} + +ATOM_INLINE void Component::setTypeInfo(atom::meta::TypeInfo typeInfo) { + m_typeInfo_ = typeInfo; +} + +ATOM_INLINE void Component::addAlias(const std::string& name, + const std::string& alias) const { + m_CommandDispatcher_->addAlias(name, alias); +} + +ATOM_INLINE void Component::addGroup(const std::string& name, + const std::string& group) const { + m_CommandDispatcher_->addGroup(name, group); +} + +ATOM_INLINE void Component::setTimeout( + const std::string& name, std::chrono::milliseconds timeout) const { + m_CommandDispatcher_->setTimeout(name, timeout); +} + +ATOM_INLINE void Component::removeCommand(const std::string& name) const { + m_CommandDispatcher_->removeCommand(name); +} + +ATOM_INLINE auto Component::getCommandsInGroup(const std::string& group) const + -> std::vector { + return m_CommandDispatcher_->getCommandsInGroup(group); +} + +ATOM_INLINE auto Component::getCommandDescription(const std::string& name) const + -> std::string { + return m_CommandDispatcher_->getCommandDescription(name); +} + +#if ENABLE_FASTHASH +ATOM_INLINE emhash::HashSet Component::getCommandAliases( + const std::string& name) const +#else +ATOM_INLINE auto Component::getCommandAliases(const std::string& name) const + -> std::unordered_set +#endif +{ + return m_CommandDispatcher_->getCommandAliases(name); +} + +ATOM_INLINE auto Component::getNeededComponents() -> std::vector { + return {}; +} + +ATOM_INLINE void Component::addOtherComponent( + const std::string& name, const std::weak_ptr& component) { + if (m_OtherComponents_.contains(name)) { + THROW_OBJ_ALREADY_EXIST(name); + } + m_OtherComponents_[name] = component; +} + +ATOM_INLINE void Component::removeOtherComponent(const std::string& name) { + m_OtherComponents_.erase(name); +} + +ATOM_INLINE void Component::clearOtherComponents() { + m_OtherComponents_.clear(); +} + +ATOM_INLINE auto Component::getOtherComponent(const std::string& name) + -> std::weak_ptr { + if (m_OtherComponents_.contains(name)) { + return m_OtherComponents_[name]; + } + return {}; +} + +ATOM_INLINE bool Component::has(const std::string& name) const { + return m_CommandDispatcher_->has(name); +} + +ATOM_INLINE bool Component::hasType(std::string_view name) const { + if (auto it = m_classes_.find(name); it != m_classes_.end()) { + return true; + } + return false; +} + +template +auto Component::hasConversion() const -> bool { + if constexpr (std::is_same_v) { + return true; + } + return m_TypeConverter_->canConvert( + atom::meta::userType(), + atom::meta::userType()); +} + +ATOM_INLINE auto Component::getAllCommands() const -> std::vector { + if (m_CommandDispatcher_ == nullptr) { + THROW_OBJ_UNINITIALIZED( + "Component command dispatch is not initialized"); + } + return m_CommandDispatcher_->getAllCommands(); +} + +ATOM_INLINE auto Component::getRegisteredTypes() const + -> std::vector { + return m_TypeCaster_->getRegisteredTypes(); +} + +ATOM_INLINE auto Component::runCommand( + const std::string& name, const std::vector& args) -> std::any { + auto cmd = getAllCommands(); + + if (auto it = std::ranges::find(cmd, name); it != cmd.end()) { + return m_CommandDispatcher_->dispatch(name, args); + } + for (const auto& [key, value] : m_OtherComponents_) { + if (!value.expired() && value.lock()->has(name)) { + return value.lock()->dispatch(name, args); + } + LOG_F(ERROR, "Component {} has expired", key); + m_OtherComponents_.erase(key); + } + + THROW_EXCEPTION("Component ", name, " not found"); +} + +ATOM_INLINE void Component::doc(const std::string& description) { + m_doc_ = description; +} + +template +void Component::defType(std::string_view name, + [[maybe_unused]] const std::string& group, + [[maybe_unused]] const std::string& description) { + m_classes_[name] = atom::meta::userType(); + m_TypeCaster_->registerType(std::string(name)); +} + +template +void Component::defConversion(std::function func) { + static_assert(!std::is_same_v, + "SourceType and DestinationType must be not the same"); + m_TypeCaster_->registerConversion(func); +} + +ATOM_INLINE void Component::defClassConversion( + const std::shared_ptr& conversion) { + m_TypeConverter_->addConversion(conversion); +} + +template +void Component::defBaseClass() { + static_assert(std::is_base_of_v, + "Derived must be derived from Base"); + m_TypeConverter_->addBaseClass(); +} + +ATOM_INLINE auto Component::hasVariable(const std::string& name) const -> bool { + return m_VariableManager_->has(name); +} + +ATOM_INLINE auto Component::getVariableDescription( + const std::string& name) const -> std::string { + return m_VariableManager_->getDescription(name); +} + +ATOM_INLINE auto Component::getVariableAlias(const std::string& name) const + -> std::string { + return m_VariableManager_->getAlias(name); +} + +ATOM_INLINE auto Component::getVariableGroup(const std::string& name) const + -> std::string { + return m_VariableManager_->getGroup(name); +} + +template +void Component::def(const std::string& name, Callable&& func, + const std::string& group, const std::string& description) { + m_CommandDispatcher_->def(name, group, description, + std::function(std::forward(func))); +} + +template +void Component::def(const std::string& name, Ret (*func)(), + const std::string& group, const std::string& description) { + m_CommandDispatcher_->def(name, group, description, + std::function(func)); +} + +template +void Component::def(const std::string& name, Ret (*func)(Args...), + const std::string& group, const std::string& description) { + m_CommandDispatcher_->def(name, group, description, + std::function([func](Args... args) { + return func(std::forward(args)...); + })); +} + +template +void Component::def(const std::string& name, Ret (Class::*func)(), + std::shared_ptr instance, const std::string& group, + const std::string& description) { + m_CommandDispatcher_->def(name, group, description, + std::function([instance, func]() { + return std::invoke(func, instance.get()); + })); +} + +template +void Component::def(const std::string& name, Ret (Class::*func)(Args...), + std::shared_ptr instance, const std::string& group, + const std::string& description) { + m_CommandDispatcher_->def( + name, group, description, + std::function([instance, func](Args... args) { + return std::invoke(func, instance.get(), + std::forward(args)...); + })); +} + +template +void Component::def(const std::string& name, Ret (Class::*func)(Args...) const, + std::shared_ptr instance, const std::string& group, + const std::string& description) { + m_CommandDispatcher_->def( + name, group, description, + std::function([instance, func](Args... args) { + return std::invoke(func, instance.get(), + std::forward(args)...); + })); +} + +template +void Component::def(const std::string& name, Ret (Class::*func)(Args...), + const std::string& group, const std::string& description) { + auto boundFunc = atom::meta::bindMemberFunction(func); + m_CommandDispatcher_->def( + name, group, description, + std::function( + [boundFunc](Class& instance, Args... args) { + return boundFunc(instance, std::forward(args)...); + })); +} + +template +void Component::def(const std::string& name, Ret (Class::*func)(Args...) const, + const std::string& group, const std::string& description) { + auto boundFunc = atom::meta::bindMemberFunction(func); + m_CommandDispatcher_->def( + name, group, description, + std::function( + [boundFunc](Class& instance, Args... args) -> Ret { + return boundFunc(instance, std::forward(args)...); + })); +} + +template +void Component::def_v(const std::string& name, VarType Class::*var, + const std::string& group, + const std::string& description) { + auto boundVar = atom::meta::bindMemberVariable(var); + m_CommandDispatcher_->def( + name, group, description, + std::function( + [boundVar](Class& instance) { return boundVar(instance); })); +} + +template +void Component::def(const std::string& name, Ret (Class::*func)(), + const PointerSentinel& instance, + const std::string& group, const std::string& description) { + m_CommandDispatcher_->def(name, group, description, + std::function([instance, func]() { + return std::invoke(func, instance.get()); + })); +} + +template +void Component::def(const std::string& name, Ret (Class::*func)(Args...), + const PointerSentinel& instance, + const std::string& group, const std::string& description) { + m_CommandDispatcher_->def( + name, group, description, + std::function([instance, func](Args... args) { + return std::invoke(func, instance.get(), + std::forward(args)...); + })); +} + +template +void Component::def(const std::string& name, Ret (Class::*func)(Args...) const, + const PointerSentinel& instance, + const std::string& group, const std::string& description) { + m_CommandDispatcher_->def( + name, group, description, + std::function([instance, func](Args... args) { + return std::invoke(func, instance.get(), + std::forward(args)...); + })); +} + +template +void Component::def(const std::string& name, + Ret (Class::*func)(Args...) noexcept, + const PointerSentinel& instance, + const std::string& group, const std::string& description) { + m_CommandDispatcher_->def( + name, group, description, + std::function([instance, func](Args... args) { + return std::invoke(func, instance.get(), + std::forward(args)...); + })); +} + +template +void Component::def(const std::string& name, MemberType ClassType::*member_var, + std::shared_ptr instance, + const std::string& group, const std::string& description) { + m_CommandDispatcher_->def( + "get_" + name, group, "Get " + description, + std::function([instance, member_var]() { + return atom::meta::bindMemberVariable(member_var)(*instance); + })); + m_CommandDispatcher_->def( + "set_" + name, group, "Set " + description, + std::function( + [instance, member_var](MemberType value) { + atom::meta::bindMemberVariable(member_var)(*instance) = value; + })); +} + +template +void Component::def(const std::string& name, Ret (Class::*getter)() const, + void (Class::*setter)(Ret), std::shared_ptr instance, + const std::string& group, const std::string& description) { + m_CommandDispatcher_->def("get_" + name, group, "Get " + description, + std::function([instance, getter]() { + return std::invoke(getter, instance.get()); + })); + m_CommandDispatcher_->def( + "set_" + name, group, "Set " + description, + std::function([instance, setter](Ret value) { + std::invoke(setter, instance.get(), value); + })); +} + +template +void Component::def(const std::string& name, MemberType ClassType::*member_var, + const PointerSentinel& instance, + const std::string& group, const std::string& description) { + auto callable = bind_member_variable(member_var); + m_CommandDispatcher_->def( + name, group, description, + std::function( + [instance, callable]() { return callable(*instance.get()); })); +} + +template +void Component::def(const std::string& name, + const MemberType ClassType::*member_var, + std::shared_ptr instance, + const std::string& group, const std::string& description) { + auto callable = bind_member_variable(member_var); + m_CommandDispatcher_->def( + name, group, description, + std::function( + [instance, callable]() { return callable(*instance); })); +} + +template +void Component::def(const std::string& name, + const MemberType ClassType::*member_var, + const PointerSentinel& instance, + const std::string& group, const std::string& description) { + auto callable = bind_member_variable(member_var); + m_CommandDispatcher_->def( + name, group, description, + std::function( + [instance, callable]() { return callable(*instance.get()); })); +} + +template +void Component::def(const std::string& name, MemberType* member_var, + const std::string& group, const std::string& description) { + m_CommandDispatcher_->def( + name, group, description, + std::function( + [member_var]() -> MemberType& { return *member_var; })); +} + +template +void Component::def(const std::string& name, const MemberType* member_var, + const std::string& group, const std::string& description) { + m_CommandDispatcher_->def( + name, group, description, + std::function( + [member_var]() -> const MemberType& { return *member_var; })); +} + +template +void Component::defConstructor(const std::string& name, + const std::string& group, + const std::string& description) { + m_CommandDispatcher_->def(name, group, description, + std::function(Args...)>( + atom::meta::constructor())); +} + +template +void Component::defDefaultConstructor(const std::string& name, + const std::string& group, + const std::string& description) { + m_CommandDispatcher_->def( + name, group, description, + std::function()>([]() -> std::shared_ptr { + return std::make_shared(); + })); +} + +template +void Component::defM(const std::string& name, MemberType ClassType::*member_var, + std::shared_ptr instance, + const std::string& group, const std::string& description) { + define_accessors(name, member_var, instance, group, description); +} + +template +void Component::defM(const std::string& name, MemberType ClassType::*member_var, + PointerSentinel instance, + const std::string& group, const std::string& description) { + define_accessors(name, member_var, instance, group, description); +} + +template +void Component::def(const std::string& name, const std::string& group, + const std::string& description) { + auto constructor = atom::meta::defaultConstructor(); + def(name, constructor, group, description); +} + +template +void Component::def(const std::string& name, const std::string& group, + const std::string& description) { + auto constructor_ = atom::meta::constructor(); + def(name, constructor_, group, description); +} + +template +void Component::defineAccessors(const std::string& name, + MemberType ClassType::*member_var, + InstanceType instance, const std::string& group, + const std::string& description) { + auto getter = [instance, member_var]() -> MemberType& { + return instance->*member_var; + }; + + auto setter = [instance, member_var](const MemberType& value) { + instance->*member_var = value; + }; + + m_CommandDispatcher_->def("get_" + name, group, description, + std::function(getter)); + m_CommandDispatcher_->def("set_" + name, group, description, + std::function(setter)); +} #endif diff --git a/src/atom/components/component.inl b/src/atom/components/component.inl deleted file mode 100644 index acdcf979..00000000 --- a/src/atom/components/component.inl +++ /dev/null @@ -1,519 +0,0 @@ -/* - * component.inl - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2023-12-26 - -Description: Basic Component Definition - -**************************************************/ - -#ifndef ATOM_COMPONENT_COMPONENT_INL -#define ATOM_COMPONENT_COMPONENT_INL - -#include "component.hpp" - -#include "atom/log/loguru.hpp" - -inline Component::Component(const std::string& name) - : m_name(name), - m_CommandDispatcher(std::make_shared()), - m_VariableManager(std::make_shared()), - m_typeInfo(atom::meta::user_type()), - m_TypeCaster(atom::meta::TypeCaster::createShared()), - m_TypeConverter(atom::meta::TypeConversions::createShared()) { - // Empty -} - -inline Component::~Component() { - // Empty -} - -inline std::weak_ptr Component::getInstance() const { - return shared_from_this(); -} - -inline bool Component::initialize() { - LOG_F(INFO, "Initializing component: {}", m_name); - return true; -} - -inline bool Component::destroy() { - LOG_F(INFO, "Destroying component: {}", m_name); - return true; -} - -inline std::string Component::getName() const { return m_name; } - -inline atom::meta::Type_Info Component::getTypeInfo() const { - return m_typeInfo; -} - -inline void Component::setTypeInfo(atom::meta::Type_Info typeInfo) { - m_typeInfo = typeInfo; -} - -inline void Component::addAlias(const std::string& name, - const std::string& alias) { - m_CommandDispatcher->addAlias(name, alias); -} - -inline void Component::addGroup(const std::string& name, - const std::string& group) { - m_CommandDispatcher->addGroup(name, group); -} - -inline void Component::setTimeout(const std::string& name, - std::chrono::milliseconds timeout) { - m_CommandDispatcher->setTimeout(name, timeout); -} - -inline void Component::removeCommand(const std::string& name) { - m_CommandDispatcher->removeCommand(name); -} - -inline std::vector Component::getCommandsInGroup( - const std::string& group) const { - return m_CommandDispatcher->getCommandsInGroup(group); -} - -inline std::string Component::getCommandDescription( - const std::string& name) const { - return m_CommandDispatcher->getCommandDescription(name); -} - -#if ENABLE_FASTHASH -inline emhash::HashSet Component::getCommandAliases( - const std::string& name) const -#else -inline std::unordered_set Component::getCommandAliases( - const std::string& name) const -#endif -{ - return m_CommandDispatcher->getCommandAliases(name); -} - -inline std::vector Component::getNeededComponents() const { - return {}; -} - -inline void Component::addOtherComponent( - const std::string& name, const std::weak_ptr& component) { - if (m_OtherComponents.contains(name)) { - THROW_OBJ_ALREADY_EXIST(name); - } - m_OtherComponents[name] = std::move(component); -} - -inline void Component::removeOtherComponent(const std::string& name) { - m_OtherComponents.erase(name); -} - -inline void Component::clearOtherComponents() { m_OtherComponents.clear(); } - -inline std::weak_ptr Component::getOtherComponent( - const std::string& name) { - if (m_OtherComponents.contains(name)) { - return m_OtherComponents[name]; - } - return {}; -} - -inline bool Component::has(const std::string& name) const { - return m_CommandDispatcher->has(name); -} - -inline bool Component::has_type(std::string_view name) const { - if (auto it = m_classes.find(name); it != m_classes.end()) { - return true; - } - return false; -} - -template -bool Component::has_conversion() const { - if constexpr (std::is_same_v) { - return true; - } - return m_TypeConverter->can_convert( - atom::meta::user_type(), - atom::meta::user_type()); -} - -inline std::vector Component::getAllCommands() const { - return m_CommandDispatcher->getAllCommands(); -} - -std::vector Component::getRegisteredTypes() const { - return m_TypeCaster->get_registered_types(); -} - -inline std::any Component::runCommand(const std::string& name, - const std::vector& args) { - auto _cmd = getAllCommands(); - auto it = std::find(_cmd.begin(), _cmd.end(), name); - - if (it != _cmd.end()) { - return m_CommandDispatcher->dispatch(name, args); - } else { - for (auto& [key, value] : m_OtherComponents) { - if (!value.expired()) { - if (value.lock()->has(name)) { - return value.lock()->dispatch(name, args); - } - } else { - LOG_F(ERROR, "Component {} has expired", key); - m_OtherComponents.erase(key); - } - } - } - THROW_EXCEPTION("Coomponent ", name, " not found"); -} - -inline void Component::doc(const std::string& description) { m_doc = description; } - -template -void Component::def_type(std::string_view name, const atom::meta::Type_Info& ti, - [[maybe_unused]] const std::string& group, - [[maybe_unused]] const std::string& description) { - m_classes[name] = ti; - m_TypeCaster->register_type(std::string(name)); -} - -template -void Component::def_conversion(std::function func) { - static_assert(!std::is_same_v, - "SourceType and DestinationType must be not the same"); - m_TypeCaster->register_conversion(func); -} - -inline void Component::def_class_conversion( - const std::shared_ptr& conversion) { - m_TypeConverter->add_conversion(conversion); -} - -template -void Component::def_base_class() { - static_assert(std::is_base_of_v, - "Derived must be derived from Base"); - m_TypeConverter->add_base_class(); -} - -inline bool Component::hasVariable(const std::string& name) const { - return m_VariableManager->has(name); -} - -inline std::string Component::getVariableDescription( - const std::string& name) const { - return m_VariableManager->getDescription(name); -} - -inline std::string Component::getVariableAlias(const std::string& name) const { - return m_VariableManager->getAlias(name); -} - -inline std::string Component::getVariableGroup(const std::string& name) const { - return m_VariableManager->getGroup(name); -} - -template -void Component::def(const std::string& name, Callable&& func, - const std::string& group, const std::string& description) { - m_CommandDispatcher->def(name, group, description, - std::function(std::forward(func))); -} - -template -void Component::def(const std::string& name, Ret (*func)(), - const std::string& group, const std::string& description) { - m_CommandDispatcher->def(name, group, description, - std::function(func)); -} - -template -void Component::def(const std::string& name, Ret (*func)(Args...), - const std::string& group, const std::string& description) { - m_CommandDispatcher->def(name, group, description, - std::function([func](Args... args) { - return func(std::forward(args)...); - })); -} - -template -void Component::def(const std::string& name, Ret (Class::*func)(), - std::shared_ptr instance, const std::string& group, - const std::string& description) { - m_CommandDispatcher->def(name, group, description, - std::function([instance, func]() { - return std::invoke(func, instance.get()); - })); -} - -template -void Component::def(const std::string& name, Ret (Class::*func)(Args...), - std::shared_ptr instance, const std::string& group, - const std::string& description) { - m_CommandDispatcher->def( - name, group, description, - std::function([instance, func](Args... args) { - return std::invoke(func, instance.get(), - std::forward(args)...); - })); -} - -template -void Component::def(const std::string& name, Ret (Class::*func)(Args...) const, - std::shared_ptr instance, const std::string& group, - const std::string& description) { - m_CommandDispatcher->def( - name, group, description, - std::function([instance, func](Args... args) { - return std::invoke(func, instance.get(), - std::forward(args)...); - })); -} - -template -void Component::def(const std::string& name, Ret (Class::*func)(Args...), - const std::string& group, const std::string& description) { - auto bound_func = atom::meta::bind_member_function(func); - m_CommandDispatcher->def(name, group, description, - std::function( - [bound_func](Class& instance, Args... args) { - return bound_func( - instance, std::forward(args)...); - })); -} - -template -void Component::def(const std::string& name, Ret (Class::*func)(Args...) const, - const std::string& group, const std::string& description) { - auto bound_func = atom::meta::bind_member_function(func); - m_CommandDispatcher->def( - name, group, description, - std::function( - [bound_func](Class& instance, Args... args) -> Ret { - return bound_func(instance, std::forward(args)...); - })); -} - -template -void Component::def_v(const std::string& name, VarType Class::*var, - const std::string& group, - const std::string& description) { - auto bound_var = atom::meta::bind_member_variable(var); - m_CommandDispatcher->def( - name, group, description, - std::function( - [bound_var](Class& instance) { return bound_var(instance); })); -} - -template -void Component::def(const std::string& name, Ret (Class::*func)(), - const PointerSentinel& instance, - const std::string& group, const std::string& description) { - m_CommandDispatcher->def(name, group, description, - std::function([instance, func]() { - return std::invoke(func, instance.get()); - })); -} - -template -void Component::def(const std::string& name, Ret (Class::*func)(Args...), - const PointerSentinel& instance, - const std::string& group, const std::string& description) { - m_CommandDispatcher->def( - name, group, description, - std::function([instance, func](Args... args) { - return std::invoke(func, instance.get(), - std::forward(args)...); - })); -} - -template -void Component::def(const std::string& name, Ret (Class::*func)(Args...) const, - const PointerSentinel& instance, - const std::string& group, const std::string& description) { - m_CommandDispatcher->def( - name, group, description, - std::function([instance, func](Args... args) { - return std::invoke(func, instance.get(), - std::forward(args)...); - })); -} - -template -void Component::def(const std::string& name, - Ret (Class::*func)(Args...) noexcept, - const PointerSentinel& instance, - const std::string& group, const std::string& description) { - m_CommandDispatcher->def( - name, group, description, - std::function([instance, func](Args... args) { - return std::invoke(func, instance.get(), - std::forward(args)...); - })); -} - -template -void Component::def(const std::string& name, MemberType ClassType::*member_var, - std::shared_ptr instance, - const std::string& group, const std::string& description) { - m_CommandDispatcher->def( - "get_" + name, group, "Get " + description, - std::function([instance, member_var]() { - return atom::meta::bind_member_variable(member_var)(*instance); - })); - m_CommandDispatcher->def( - "set_" + name, group, "Set " + description, - std::function( - [instance, member_var](MemberType value) { - atom::meta::bind_member_variable(member_var)(*instance) = value; - })); -} - -template -void Component::def(const std::string& name, Ret (Class::*getter)() const, - void (Class::*setter)(Ret), std::shared_ptr instance, - const std::string& group, const std::string& description) { - m_CommandDispatcher->def("get_" + name, group, "Get " + description, - std::function([instance, getter]() { - return std::invoke(getter, instance.get()); - })); - m_CommandDispatcher->def( - "set_" + name, group, "Set " + description, - std::function([instance, setter](Ret value) { - std::invoke(setter, instance.get(), value); - })); -} - -template -void Component::def(const std::string& name, MemberType ClassType::*member_var, - const PointerSentinel& instance, - const std::string& group, const std::string& description) { - auto callable = bind_member_variable(member_var); - m_CommandDispatcher->def( - name, group, description, - std::function( - [instance, callable]() { return callable(*instance.get()); })); -} - -template -void Component::def(const std::string& name, - const MemberType ClassType::*member_var, - std::shared_ptr instance, - const std::string& group, const std::string& description) { - auto callable = bind_member_variable(member_var); - m_CommandDispatcher->def( - name, group, description, - std::function( - [instance, callable]() { return callable(*instance); })); -} - -template -void Component::def(const std::string& name, - const MemberType ClassType::*member_var, - const PointerSentinel& instance, - const std::string& group, const std::string& description) { - auto callable = bind_member_variable(member_var); - m_CommandDispatcher->def( - name, group, description, - std::function( - [instance, callable]() { return callable(*instance.get()); })); -} - -template -void Component::def(const std::string& name, MemberType* member_var, - const std::string& group, const std::string& description) { - m_CommandDispatcher->def( - name, group, description, - std::function( - [member_var]() -> MemberType& { return *member_var; })); -} - -template -void Component::def(const std::string& name, const MemberType* member_var, - const std::string& group, const std::string& description) { - m_CommandDispatcher->def( - name, group, description, - std::function( - [member_var]() -> const MemberType& { return *member_var; })); -} - -template -void Component::def_constructor(const std::string& name, - const std::string& group, - const std::string& description) { - m_CommandDispatcher->def(name, group, description, - std::function(Args...)>( - atom::meta::constructor())); -} - -template -void Component::def_default_constructor(const std::string& name, - const std::string& group, - const std::string& description) { - m_CommandDispatcher->def( - name, group, description, - std::function()>([]() -> std::shared_ptr { - return std::make_shared(); - })); -} - -template -void Component::def_m(const std::string& name, - MemberType ClassType::*member_var, - std::shared_ptr instance, - const std::string& group, - const std::string& description) { - define_accessors(name, member_var, instance, group, description); -} - -template -void Component::def_m(const std::string& name, - MemberType ClassType::*member_var, - PointerSentinel instance, - const std::string& group, - const std::string& description) { - define_accessors(name, member_var, instance, group, description); -} - -template -void Component::def(const std::string& name, const std::string& group, - const std::string& description) { - auto constructor = atom::meta::default_constructor(); - def(name, constructor, group, description); -} - -template -void Component::def(const std::string& name, const std::string& group, - const std::string& description) { - auto constructor_ = atom::meta::constructor(); - def(name, constructor_, group, description); -} - -template -void Component::define_accessors(const std::string& name, - MemberType ClassType::*member_var, - InstanceType instance, - const std::string& group, - const std::string& description) { - auto getter = [instance, member_var]() -> MemberType& { - return instance->*member_var; - }; - - auto setter = [instance, member_var](const MemberType& value) { - instance->*member_var = value; - }; - - m_CommandDispatcher->def("get_" + name, group, description, - std::function(getter)); - m_CommandDispatcher->def("set_" + name, group, description, - std::function(setter)); -} - -#endif diff --git a/src/atom/components/constants.hpp b/src/atom/components/constants.hpp deleted file mode 100644 index e69de29b..00000000 diff --git a/src/atom/components/dispatch.hpp b/src/atom/components/dispatch.hpp index 32c6603a..7ad6aa70 100644 --- a/src/atom/components/dispatch.hpp +++ b/src/atom/components/dispatch.hpp @@ -3,16 +3,17 @@ #include #include -#include #include #include -#include #include #include -#include #include #include #include +#include +#include +#include "function/type_caster.hpp" + #if ENABLE_FASTHASH #include "emhash/hash_set8.hpp" #include "emhash/hash_table8.hpp" @@ -20,14 +21,54 @@ #include #include #endif -#include -#include #include "atom/type/noncopyable.hpp" #include "atom/type/pointer.hpp" #include "atom/function/proxy.hpp" #include "atom/function/type_info.hpp" +#include "macro.hpp" + +#include "atom/error/exception.hpp" +#include "atom/function/abi.hpp" +#include "atom/function/func_traits.hpp" + +#include "atom/utils/to_string.hpp" + +class Arg { +public: + explicit Arg(std::string name) : name_(std::move(name)) {} + Arg(std::string name, std::any default_value) + : name_(std::move(name)), default_value_(default_value) {} + + [[nodiscard]] auto getName() const -> const std::string& { return name_; } + [[nodiscard]] auto getDefaultValue() const + -> const std::optional& { + return default_value_; + } + +private: + std::string name_; + std::optional default_value_; +}; + +class DispatchException : public atom::error::Exception { +public: + using atom::error::Exception::Exception; +}; + +#define THROW_DISPATCH_EXCEPTION(...) \ + throw DispatchException(ATOM_FILE_NAME, ATOM_FILE_LINE, ATOM_FUNC_NAME, \ + __VA_ARGS__); + +class DispatchTimeout : public atom::error::Exception { +public: + using atom::error::Exception::Exception; +}; + +#define THROW_DISPATCH_TIMEOUT(...) \ + throw DispatchTimeout(ATOM_FILE_NAME, ATOM_FILE_LINE, ATOM_FUNC_NAME, \ + __VA_ARGS__); class Arg { public: @@ -47,6 +88,9 @@ class Arg { class CommandDispatcher { public: + explicit CommandDispatcher(std::weak_ptr typeCaster) + : typeCaster_(std::move(typeCaster)) {} + template void def(const std::string& name, const std::string& group, const std::string& description, std::function func, @@ -55,14 +99,13 @@ class CommandDispatcher { std::vector arg_info = {}); template - void def_t( - const std::string& name, const std::string& group, - const std::string& description, std::function func, - std::optional> precondition = std::nullopt, - std::optional> postcondition = std::nullopt, - std::vector arg_info = {}); + void defT(const std::string& name, const std::string& group, + const std::string& description, std::function func, + std::optional> precondition = std::nullopt, + std::optional> postcondition = std::nullopt, + std::vector arg_info = {}); - [[nodiscard]] bool has(const std::string& name) const; + [[nodiscard]] auto has(const std::string& name) const -> bool; void addAlias(const std::string& name, const std::string& alias); @@ -71,42 +114,71 @@ class CommandDispatcher { void setTimeout(const std::string& name, std::chrono::milliseconds timeout); template - std::any dispatch(const std::string& name, Args&&... args); + auto dispatch(const std::string& name, Args&&... args) -> std::any; - std::any dispatch(const std::string& name, - const std::vector& args); + auto dispatch(const std::string& name, + const std::vector& args) -> std::any; - std::any dispatch(const std::string& name, const FunctionParams& params); + auto dispatch(const std::string& name, + const FunctionParams& params) -> std::any; void removeCommand(const std::string& name); - std::vector getCommandsInGroup(const std::string& group) const; + auto getCommandsInGroup(const std::string& group) const + -> std::vector; - std::string getCommandDescription(const std::string& name) const; + auto getCommandDescription(const std::string& name) const -> std::string; #if ENABLE_FASTHASH emhash::HashSet getCommandAliases( const std::string& name) const; #else - std::unordered_set getCommandAliases( - const std::string& name) const; + auto getCommandAliases(const std::string& name) const + -> std::unordered_set; #endif - std::vector getAllCommands() const; + auto getAllCommands() const -> std::vector; private: + struct Command; + template - std::any dispatchHelper(const std::string& name, const ArgsType& args); + auto dispatchHelper(const std::string& name, + const ArgsType& args) -> std::any; template - std::vector convertToArgsVector(std::tuple&& tuple); + auto convertToArgsVector(std::tuple&& tuple) + -> std::vector; + + auto findCommand(const std::string& name); + + template + auto completeArgs(const Command& cmd, + const ArgsType& args) -> std::vector; + + void checkPrecondition(const Command& cmd, const std::string& name); + + auto executeCommand(const Command& cmd, const std::string& name, + const std::vector& args) -> std::any; + + auto executeWithTimeout(const Command& cmd, const std::string& name, + const std::vector& args, + const std::chrono::duration& timeout) + -> std::any; + + auto executeWithoutTimeout(const Command& cmd, const std::string& name, + const std::vector& args) -> std::any; + + auto executeFunctions(const Command& cmd, + const std::vector& args) -> std::any; + + auto computeFunctionHash(const std::vector& args) -> std::string; -private: struct Command { std::vector&)>> funcs; - std::vector return_type; - std::vector> arg_types; + std::vector returnType; + std::vector> argTypes; std::vector hash; std::string description; #if ENABLE_FASTHASH @@ -116,20 +188,349 @@ class CommandDispatcher { #endif std::optional> precondition; std::optional> postcondition; - std::vector arg_info; - }; + std::vector argInfo; + } ATOM_ALIGNAS(128); #if ENABLE_FASTHASH emhash8::HashMap commands; emhash8::HashMap groupMap; emhash8::HashMap timeoutMap; #else - std::unordered_map commands; - std::unordered_map groupMap; - std::unordered_map timeoutMap; + std::unordered_map commands_; + std::unordered_map groupMap_; + std::unordered_map timeoutMap_; #endif + + std::weak_ptr typeCaster_; }; -#include "dispatch.inl" +template +void CommandDispatcher::def(const std::string& name, const std::string& group, + const std::string& description, + std::function func, + std::optional> precondition, + std::optional> postcondition, + std::vector arg_info) { + auto _func = atom::meta::ProxyFunction(std::move(func)); + auto info = _func.getFunctionInfo(); + auto it = commands_.find(name); + if (it == commands_.end()) { + Command cmd{{std::move(_func)}, + {info.returnType}, + {info.argumentTypes}, + {info.hash}, + description, + {}, + std::move(precondition), + std::move(postcondition), + std::move(arg_info)}; + commands_[name] = std::move(cmd); + groupMap_[name] = group; + } else { + it->second.funcs.emplace_back(std::move(_func)); + it->second.returnType.emplace_back(info.returnType); + it->second.argTypes.emplace_back(info.argumentTypes); + it->second.hash.emplace_back(info.hash); + it->second.argInfo = std::move(arg_info); + } +} + +template +void CommandDispatcher::defT(const std::string& name, const std::string& group, + const std::string& description, + std::function func, + std::optional> precondition, + std::optional> postcondition, + std::vector arg_info) { + auto _func = atom::meta::TimerProxyFunction(std::move(func)); + std::function&)> wrappedFunc = + [_func](const std::vector& args) mutable -> std::any { + std::chrono::milliseconds defaultTimeout(1000); + return _func(args, defaultTimeout); + }; + + auto info = _func.getFunctionInfo(); + auto it = commands_.find(name); + if (it == commands_.end()) { + Command cmd{{std::move(wrappedFunc)}, + {info.returnType}, + {info.argumentTypes}, + {info.hash}, + description, + {}, + std::move(precondition), + std::move(postcondition), + std::move(arg_info)}; + commands_[name] = std::move(cmd); + groupMap_[name] = group; + } else { + it->second.funcs.emplace_back(std::move(wrappedFunc)); + it->second.returnType.emplace_back(info.returnType); + it->second.argTypes.emplace_back(info.argumentTypes); + it->second.hash.emplace_back(info.hash); + it->second.argInfo = std::move(arg_info); + } +} + +template +auto CommandDispatcher::dispatch(const std::string& name, + Args&&... args) -> std::any { + auto argsTuple = std::make_tuple(std::forward(args)...); + auto argsVec = convertToArgsVector(std::move(argsTuple)); + return dispatchHelper(name, argsVec); +} + +template +auto CommandDispatcher::convertToArgsVector(std::tuple&& tuple) + -> std::vector { + std::vector argsVec; + argsVec.reserve(sizeof...(Args)); + std::apply( + [&argsVec](auto&&... args) { + ((argsVec.emplace_back(std::forward(args))), ...); + }, + std::move(tuple)); + return argsVec; +} + +ATOM_INLINE auto CommandDispatcher::findCommand(const std::string& name) { + auto it = commands_.find(name); + if (it == commands_.end()) { + for (const auto& [cmdName, cmd] : commands_) { + if (std::find(cmd.aliases.begin(), cmd.aliases.end(), name) != + cmd.aliases.end()) { +#if ENABLE_DEBUG + std::cout << "Command '" << name + << "' not found, did you mean '" << cmdName << "'?\n"; +#endif + return commands_.find(cmdName); + } + } + } + return it; +} + +template +auto CommandDispatcher::dispatchHelper(const std::string& name, + const ArgsType& args) -> std::any { + auto it = findCommand(name); + if (it == commands_.end()) { + THROW_INVALID_ARGUMENT("Unknown command: " + name); + } + + const auto& cmd = it->second; + std::vector fullArgs; + // if constexpr (std::is_same_v>) { + // fullArgs = args; + // } else { + fullArgs = completeArgs(cmd, args); + //} + + if constexpr (std::is_same_v>) { + auto it1 = args.begin(); + auto it2 = cmd.argTypes.begin(); + // Max: 这里需要自动处理类型差异 + for (; it1 != args.end() && it2 != cmd.argTypes.end(); ++it1, ++it2) { + } + } + + checkPrecondition(cmd, name); + + auto result = executeCommand(cmd, name, fullArgs); + + if (cmd.postcondition) { + cmd.postcondition.value()(); + } + + return result; +} + +template +auto CommandDispatcher::completeArgs(const Command& cmd, const ArgsType& args) + -> std::vector { + std::vector fullArgs(args.begin(), args.end()); + for (size_t i = args.size(); i < cmd.argInfo.size(); ++i) { + if (cmd.argInfo[i].getDefaultValue()) { + fullArgs.push_back(cmd.argInfo[i].getDefaultValue().value()); + } else { + THROW_INVALID_ARGUMENT("Missing argument: " + + cmd.argInfo[i].getName()); + } + } + return fullArgs; +} + +ATOM_INLINE void CommandDispatcher::checkPrecondition(const Command& cmd, + const std::string& name) { + if (cmd.precondition && !cmd.precondition.value()()) { + THROW_DISPATCH_EXCEPTION("Precondition failed for command: " + name); + } +} + +ATOM_INLINE auto CommandDispatcher::executeCommand( + const Command& cmd, const std::string& name, + const std::vector& args) -> std::any { + auto timeoutIt = timeoutMap_.find(name); + if (timeoutIt != timeoutMap_.end()) { + return executeWithTimeout(cmd, name, args, timeoutIt->second); + } + return executeWithoutTimeout(cmd, name, args); +} + +ATOM_INLINE auto CommandDispatcher::executeWithTimeout( + const Command& cmd, const std::string& name, + const std::vector& args, + const std::chrono::duration& timeout) -> std::any { + auto future = std::async(std::launch::async, + [&]() { return executeFunctions(cmd, args); }); + + if (future.wait_for(timeout) == std::future_status::timeout) { + THROW_DISPATCH_TIMEOUT("Command timed out: " + name); + } + + return future.get(); +} + +ATOM_INLINE auto CommandDispatcher::executeWithoutTimeout( + const Command& cmd, [[maybe_unused]] const std::string& name, + const std::vector& args) -> std::any { + if (!args.empty()) { + if (args.size() == 1 && + args[0].type() == typeid(std::vector)) { + return executeFunctions( + cmd, std::any_cast>(args[0])); + } + } + + return executeFunctions(cmd, args); +} + +ATOM_INLINE auto CommandDispatcher::executeFunctions( + const Command& cmd, const std::vector& args) -> std::any { + // TODO: FIX ME - Overload resolution + if (cmd.funcs.size() == 1) { + return cmd.funcs[0](args); + } + + std::string funcHash = computeFunctionHash(args); + for (size_t i = 0; i < cmd.funcs.size(); ++i) { + if (cmd.hash[i] == funcHash) { + try { + return cmd.funcs[i](args); + } catch (const std::bad_any_cast&) { + THROW_DISPATCH_EXCEPTION("Failed to call function with hash " + + funcHash); + } + } + } + + THROW_INVALID_ARGUMENT("No matching overload found"); +} + +ATOM_INLINE auto CommandDispatcher::computeFunctionHash( + const std::vector& args) -> std::string { + std::vector argTypes; + argTypes.reserve(args.size()); + for (const auto& arg : args) { + argTypes.emplace_back( + atom::meta::DemangleHelper::demangle(arg.type().name())); + } + return atom::utils::toString(atom::algorithm::computeHash(argTypes)); +} + +ATOM_INLINE auto CommandDispatcher::has(const std::string& name) const -> bool { + if (commands_.find(name) != commands_.end()) { + return true; + } + for (const auto& command : commands_) { + if (command.second.aliases.find(name) != command.second.aliases.end()) { + return true; + } + } + return false; +} + +ATOM_INLINE void CommandDispatcher::addAlias(const std::string& name, + const std::string& alias) { + auto it = commands_.find(name); + if (it != commands_.end()) { + it->second.aliases.insert(alias); + commands_[alias] = it->second; + groupMap_[alias] = groupMap_[name]; + } +} + +ATOM_INLINE void CommandDispatcher::addGroup(const std::string& name, + const std::string& group) { + groupMap_[name] = group; +} + +ATOM_INLINE void CommandDispatcher::setTimeout( + const std::string& name, std::chrono::milliseconds timeout) { + timeoutMap_[name] = timeout; +} + +ATOM_INLINE void CommandDispatcher::removeCommand(const std::string& name) { + commands_.erase(name); + groupMap_.erase(name); + timeoutMap_.erase(name); +} + +ATOM_INLINE auto CommandDispatcher::getCommandsInGroup( + const std::string& group) const -> std::vector { + std::vector result; + for (const auto& pair : groupMap_) { + if (pair.second == group) { + result.push_back(pair.first); + } + } + return result; +} + +ATOM_INLINE auto CommandDispatcher::getCommandDescription( + const std::string& name) const -> std::string { + auto it = commands_.find(name); + if (it != commands_.end()) { + return it->second.description; + } + return ""; +} + +ATOM_INLINE auto CommandDispatcher::getCommandAliases( + const std::string& name) const -> std::unordered_set { + auto it = commands_.find(name); + if (it != commands_.end()) { + return it->second.aliases; + } + return {}; +} + +ATOM_INLINE auto CommandDispatcher::dispatch( + const std::string& name, const std::vector& args) -> std::any { + return dispatchHelper(name, args); +} + +ATOM_INLINE auto CommandDispatcher::dispatch( + const std::string& name, const FunctionParams& params) -> std::any { + return dispatchHelper(name, params.toVector()); +} + +ATOM_INLINE auto CommandDispatcher::getAllCommands() const + -> std::vector { + std::vector result; + result.reserve(commands_.size()); + for (const auto& pair : commands_) { + result.push_back(pair.first); + } + // Max: Add aliases to the result vector + for (const auto& command : commands_) { + for (const auto& alias : command.second.aliases) { + result.push_back(alias); + } + } + auto it = std::unique(result.begin(), result.end()); + result.erase(it, result.end()); + return result; +} #endif diff --git a/src/atom/components/dispatch.inl b/src/atom/components/dispatch.inl deleted file mode 100644 index 75054f51..00000000 --- a/src/atom/components/dispatch.inl +++ /dev/null @@ -1,315 +0,0 @@ -#ifndef ATOM_COMPONENT_DISPATCH_INL -#define ATOM_COMPONENT_DISPATCH_INL - -#include "dispatch.hpp" - -#include "atom/error/exception.hpp" -#include "atom/function/abi.hpp" -#include "atom/function/func_traits.hpp" - -#include "atom/utils/to_string.hpp" - -class DispatchException : public atom::error::Exception { -public: - using atom::error::Exception::Exception; -}; - -#define THROW_DISPATCH_EXCEPTION(...) \ - throw DispatchException(__FILE__, __LINE__, __func__, __VA_ARGS__); - -class DispatchTimeout : public atom::error::Exception { -public: - using atom::error::Exception::Exception; -}; - -#define THROW_DISPATCH_TIMEOUT(...) \ - throw DispatchTimeout(__FILE__, __LINE__, __func__, __VA_ARGS__); - -template -void CommandDispatcher::def(const std::string& name, const std::string& group, - const std::string& description, - std::function func, - std::optional> precondition, - std::optional> postcondition, - std::vector arg_info) { - auto _func = atom::meta::ProxyFunction(std::move(func)); - auto info = _func.getFunctionInfo(); - auto it = commands.find(name); - if (it == commands.end()) { - Command cmd{{std::move(_func)}, - {info.returnType}, - {info.argumentTypes}, - {info.hash}, - description, - {}, - std::move(precondition), - std::move(postcondition), - std::move(arg_info)}; - commands[name] = std::move(cmd); - groupMap[name] = group; - } else { - it->second.funcs.emplace_back(std::move(_func)); - it->second.return_type.emplace_back(info.returnType); - it->second.arg_types.emplace_back(info.argumentTypes); - it->second.hash.emplace_back(info.hash); - it->second.arg_info = std::move(arg_info); - } -} - -template -void CommandDispatcher::def_t( - const std::string& name, const std::string& group, - const std::string& description, std::function func, - std::optional> precondition, - std::optional> postcondition, - std::vector arg_info) { - auto _func = atom::meta::TimerProxyFunction(std::move(func)); - auto info = _func.getFunctionInfo(); - auto it = commands.find(name); - if (it == commands.end()) { - Command cmd{{std::move(_func)}, - {info.returnType}, - {info.argumentTypes}, - {info.hash}, - description, - {}, - std::move(precondition), - std::move(postcondition), - std::move(arg_info)}; - commands[name] = std::move(cmd); - groupMap[name] = group; - } else { - it->second.funcs.emplace_back(std::move(_func)); - it->second.return_type.emplace_back(info.returnType); - it->second.arg_types.emplace_back(info.argumentTypes); - it->second.hash.emplace_back(info.hash); - it->second.arg_info = std::move(arg_info); - } -} - -template -std::any CommandDispatcher::dispatch(const std::string& name, Args&&... args) { - auto argsTuple = std::make_tuple(std::forward(args)...); - auto argsVec = convertToArgsVector(std::move(argsTuple)); - return dispatchHelper(name, argsVec); -} - -template -std::vector CommandDispatcher::convertToArgsVector( - std::tuple&& tuple) { - std::vector argsVec; - argsVec.reserve(sizeof...(Args)); - std::apply( - [&argsVec](auto&&... args) { - ((argsVec.emplace_back(std::forward(args))), ...); - }, - std::move(tuple)); - return argsVec; -} - -template -std::any CommandDispatcher::dispatchHelper(const std::string& name, - const ArgsType& args) { - auto it = commands.find(name); - if (it == commands.end()) { - for (const auto& cmd : commands) { - for (const auto& alias : cmd.second.aliases) { - if (alias == name) { -#if ENABLE_DEBUG - std::cout << "Command '" << name - << "' not found, did you mean '" << cmd.first - << "'?\n"; -#endif - it = commands.find(cmd.first); - break; - } - } - if (it != commands.end()) - break; - } - if (it == commands.end()) { - THROW_INVALID_ARGUMENT("Unknown command: " + name); - } - } - - const auto& cmd = it->second; - - if (args.size() < cmd.arg_info.size()) { - std::vector full_args = args; - for (size_t i = args.size(); i < cmd.arg_info.size(); ++i) { - if (cmd.arg_info[i].getDefaultValue().has_value()) { - full_args.push_back(cmd.arg_info[i].getDefaultValue().value()); - } else { - THROW_INVALID_ARGUMENT("Missing argument: " + - cmd.arg_info[i].getName()); - } - } - return dispatchHelper(name, full_args); - } - - if (cmd.precondition.has_value() && !cmd.precondition.value()()) { - THROW_DISPATCH_EXCEPTION("Precondition failed for command: " + name); - } - - auto timeoutIt = timeoutMap.find(name); - // Max: 超时函数的需求没有这么迫切,具体应该会有统一的线程池,现在仅作保留 - if (timeoutIt != timeoutMap.end()) { - auto future = std::async(std::launch::async, [&]() { - if (cmd.funcs.size() == 1) { - return cmd.funcs[0](args); - } - for (const auto& func : cmd.funcs) { - try { - return func(args); - } catch (const std::bad_any_cast&) { - // 参数类型不匹配, 尝试下一个重载函数 - } - } - return std::any{}; - }); - if (future.wait_for(timeoutIt->second) == std::future_status::timeout) { - THROW_DISPATCH_TIMEOUT("Command timed out: " + name); - } - auto result = future.get(); - if (cmd.postcondition.has_value()) - cmd.postcondition.value()(); - return result; - } else { - // Max: 如果只有一个重载函数,直接调用 - if (cmd.funcs.size() == 1) { - try { - auto result = cmd.funcs[0](args); - if (cmd.postcondition.has_value()) - cmd.postcondition.value()(); - return result; - } catch (const std::bad_any_cast&) { - // 这里不需要重载函数,因此重新抛出异常 - THROW_DISPATCH_EXCEPTION("Bad command invoke: " + name); - } - } else if (cmd.funcs.size() > 1) { - if constexpr (std::is_same_v>) { - std::string func_hash; - std::vector arg_types; - for (const auto& arg : args) { - arg_types.emplace_back(atom::meta::DemangleHelper::Demangle( - arg.type().name())); - } - func_hash = atom::algorithm::computeHash(arg_types); - int i = 0; - for (const auto& func : cmd.funcs) { - if (cmd.hash[i] == func_hash) { - try { - // Max: 这里用函数的hash来匹配,是根据注册时计算得到 - auto result = func(args); - if (cmd.postcondition.has_value()) - cmd.postcondition.value()(); - return result; - } catch (const std::bad_any_cast&) { - // Max: 这里应该是不会出现异常的 - THROW_DISPATCH_EXCEPTION( - "Failed to call function ", name, - "with function hash ", func_hash); - } - } - } - } - - THROW_INVALID_ARGUMENT("No matching overload found for command: " + - name); - } - THROW_INVALID_ARGUMENT("No overload found for command: " + name); - } -} - -inline bool CommandDispatcher::has(const std::string& name) const { - if (commands.find(name) != commands.end()) - return true; - for (const auto& command : commands) { - if (command.second.aliases.find(name) != command.second.aliases.end()) - return true; - } - return false; -} - -inline void CommandDispatcher::addAlias(const std::string& name, - const std::string& alias) { - auto it = commands.find(name); - if (it != commands.end()) { - it->second.aliases.insert(alias); - commands[alias] = it->second; - groupMap[alias] = groupMap[name]; - } -} - -inline void CommandDispatcher::addGroup(const std::string& name, - const std::string& group) { - groupMap[name] = group; -} - -inline void CommandDispatcher::setTimeout(const std::string& name, - std::chrono::milliseconds timeout) { - timeoutMap[name] = timeout; -} - -inline void CommandDispatcher::removeCommand(const std::string& name) { - commands.erase(name); - groupMap.erase(name); - timeoutMap.erase(name); -} - -inline std::vector CommandDispatcher::getCommandsInGroup( - const std::string& group) const { - std::vector result; - for (const auto& pair : groupMap) { - if (pair.second == group) { - result.push_back(pair.first); - } - } - return result; -} - -inline std::string CommandDispatcher::getCommandDescription( - const std::string& name) const { - auto it = commands.find(name); - if (it != commands.end()) { - return it->second.description; - } - return ""; -} - -inline std::unordered_set CommandDispatcher::getCommandAliases( - const std::string& name) const { - auto it = commands.find(name); - if (it != commands.end()) { - return it->second.aliases; - } - return {}; -} - -inline std::any CommandDispatcher::dispatch(const std::string& name, - const std::vector& args) { - return dispatchHelper(name, args); -} - -inline std::any CommandDispatcher::dispatch(const std::string& name, - const FunctionParams& params) { - return dispatchHelper(name, params.to_vector()); -} - -inline std::vector CommandDispatcher::getAllCommands() const { - std::vector result; - for (const auto& pair : commands) { - result.push_back(pair.first); - } - // Max: Add aliases to the result vector - for (const auto& command : commands) { - for (const auto& alias : command.second.aliases) { - result.push_back(alias); - } - } - auto it = std::unique(result.begin(), result.end()); - result.erase(it, result.end()); - return result; -} - -#endif diff --git a/src/atom/components/macro.hpp b/src/atom/components/macro.hpp deleted file mode 100644 index 0e3d6345..00000000 --- a/src/atom/components/macro.hpp +++ /dev/null @@ -1,48 +0,0 @@ -// Helper macro to register initializers with dependencies and cleanup -#define REGISTER_INITIALIZER(name, init_func, cleanup_func) \ - namespace { \ - struct Initializer { \ - Initializer() { \ - Registry::instance().add_initializer(name, init_func, \ - cleanup_func); \ - } \ - }; \ - static Initializer initializer; \ - } - -#define REGISTER_DEPENDENCY(name, dependency) \ - namespace { \ - struct Dependency { \ - Dependency() { \ - Registry::instance().add_dependency(name, dependency); \ - } \ - }; \ - static Dependency dependency; \ - } - -// Macro for dynamic library module -#define ATOM_MODULE(module_name, init_func) \ - extern "C" void initialize_registry() { \ - init_func(); \ - Registry::instance().initialize_all(); \ - } \ - extern "C" void cleanup_registry() { Registry::instance().cleanup_all(); } \ - namespace module_name { \ - struct ModuleInitializer { \ - ModuleInitializer() { init_func(); } \ - }; \ - static ModuleInitializer module_initializer; \ - } - -// Macro for embedded module -#define ATOM_EMBED_MODULE(module_name, init_func) \ - namespace module_name { \ - struct ModuleInitializer { \ - ModuleInitializer() { \ - init_func(); \ - Registry::instance().initialize_all(); \ - } \ - ~ModuleInitializer() { Registry::instance().cleanup_all(); } \ - }; \ - static ModuleInitializer module_initializer; \ - } diff --git a/src/atom/components/meson.build b/src/atom/components/meson.build deleted file mode 100644 index c92f22b4..00000000 --- a/src/atom/components/meson.build +++ /dev/null @@ -1,60 +0,0 @@ -project('atom-component', 'c', 'cpp', - version: '1.0.0', - license: 'GPL3', - default_options: ['cpp_std=c++17'] -) - -# 源文件和头文件 -atom_component_sources = [ - 'component.cpp', - 'dispatch.cpp' -] - -atom_component_headers = [ - 'component.hpp', - 'dispatch.hpp', - 'dispatch.inl', - 'types.hpp', - 'var.hpp', - 'var.inl' -] - -# 依赖 -loguru_dep = dependency('loguru') -atom_error_dep = dependency('atom-error', required: true) -atom_type_dep = dependency('atom-type', required: true) -atom_utils_dep = dependency('atom-utils', required: true) - -atom_component_deps = [ - loguru_dep, - atom_error_dep, - atom_type_dep, - atom_utils_dep -] - -# 对象库 -atom_component_object = static_library('atom_component_object', - sources: atom_component_sources, - dependencies: atom_component_deps, - include_directories: include_directories('.'), - install: false -) - -# 静态库 -atom_component_lib = static_library('atom-component', - sources: atom_component_object.extract_all_objects(), - dependencies: atom_component_deps, - include_directories: include_directories('.'), - install: true -) - -# 安装头文件 -install_headers(atom_component_headers, subdir: 'atom-component') - -# 设置目标属性 -atom_hydrogen_version_string = '1.0.0' -atom_hydrogen_soversion = '1' - -atom_component_lib.set_version(atom_hydrogen_version_string) -atom_component_lib.set_soversion(atom_hydrogen_soversion) -atom_component_lib.set_output_name('atom-component') diff --git a/src/atom/components/module_macro.hpp b/src/atom/components/module_macro.hpp new file mode 100644 index 00000000..e8535c71 --- /dev/null +++ b/src/atom/components/module_macro.hpp @@ -0,0 +1,82 @@ +// Helper macros for registering initializers, dependencies, and modules +#ifndef REGISTER_INITIALIZER +#define REGISTER_INITIALIZER(name, init_func, cleanup_func) \ + namespace { \ + struct Initializer_##name { \ + Initializer_##name() { \ + Registry::instance().addInitializer(#name, init_func, \ + cleanup_func); \ + } \ + }; \ + static Initializer_##name initializer_##name; \ + } +#endif + +#ifndef REGISTER_DEPENDENCY +#define REGISTER_DEPENDENCY(name, dependency) \ + namespace { \ + struct Dependency_##name { \ + Dependency_##name() { \ + Registry::instance().addDependency(#name, #dependency); \ + } \ + }; \ + static Dependency_##name dependency_##name; \ + } +#endif + +// Nested macro for module initialization +#ifndef ATOM_MODULE_INIT +#define ATOM_MODULE_INIT(module_name, init_func) \ + namespace module_name { \ + struct ModuleManager { \ + static void init() { \ + Registry::instance().registerModule(#module_name, init_func); \ + Registry::instance().addInitializer(#module_name, init_func); \ + Registry::instance().initializeAll(); \ + } \ + static void cleanup() { \ + static std::once_flag flag; \ + std::call_once(flag, []() { Registry::instance().cleanupAll(); }); \ + } \ + }; \ + } +#endif + +// Macro for dynamic library module +#ifndef ATOM_MODULE +#define ATOM_MODULE(module_name, init_func) \ + ATOM_MODULE_INIT(module_name, init_func) \ + extern "C" void initialize_registry() { \ + module_name::ModuleManager::init(); \ + } \ + extern "C" void cleanup_registry() { \ + module_name::ModuleManager::cleanup(); \ + } \ + extern "C" auto getInstance() -> std::shared_ptr { \ + return Registry::instance().getComponent(#module_name); \ + } +#endif + +// Macro for embedded module +#ifndef ATOM_EMBED_MODULE +#define ATOM_EMBED_MODULE(module_name, init_func) \ + ATOM_MODULE_INIT(module_name, init_func) \ + namespace module_name { \ + inline std::optional init_flag; \ + struct ModuleInitializer { \ + ModuleInitializer() { \ + if (!init_flag.has_value()) { \ + init_flag.emplace(); \ + Registry::instance().registerModule(#module_name, init_func); \ + Registry::instance().addInitializer(#module_name, init_func); \ + } \ + } \ + ~ModuleInitializer() { \ + if (init_flag.has_value()) { \ + init_flag.reset(); \ + } \ + } \ + }; \ + static ModuleInitializer module_initializer; \ + } +#endif diff --git a/src/atom/components/registry.cpp b/src/atom/components/registry.cpp deleted file mode 100644 index 2151e050..00000000 --- a/src/atom/components/registry.cpp +++ /dev/null @@ -1,107 +0,0 @@ -#include "registry.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include "atom/error/exception.hpp" - -Registry& Registry::instance() { - static Registry instance; - return instance; -} - -void Registry::add_initializer(const std::string& name, InitFunc init_func, - CleanupFunc cleanup_func = nullptr) { - std::unique_lock lock(mutex_); - if (initializers.find(name) != initializers.end()) { - THROW_OBJ_ALREADY_INITIALIZED("Initializer already registered: " + - name); - } - initializers[name] = {std::move(init_func), std::move(cleanup_func)}; - initialized[name] = false; -} - -void Registry::add_dependency(const std::string& name, - const std::string& dependency) { - std::unique_lock lock(mutex_); - if (has_circular_dependency(name, dependency)) { - THROW_RUNTIME_ERROR("Circular dependency detected: " + name + " -> " + - dependency); - } - dependencies[name].insert(dependency); -} - -void Registry::initialize_all() { - std::unique_lock lock(mutex_); - std::unordered_set init_stack; - for (const auto& pair : initializers) { - initialize_component(pair.first, init_stack); - } -} - -void Registry::cleanup_all() { - std::unique_lock lock(mutex_); - std::vector keys; - for (const auto& pair : initializers) { - keys.push_back(pair.first); - } - std::reverse(keys.begin(), keys.end()); - - for (const auto& key : keys) { - if (initializers[key].cleanup_func && initialized[key]) { - initializers[key].cleanup_func(); - initialized[key] = false; - } - } -} - -bool Registry::is_initialized(const std::string& name) const { - std::shared_lock lock(mutex_); - auto it = initialized.find(name); - return it != initialized.end() && it->second; -} - -void Registry::reinitialize_component(const std::string& name) { - std::unique_lock lock(mutex_); - if (initializers.find(name) == initializers.end()) { - THROW_OBJ_NOT_EXIST("Component not registered: " + name); - } - std::unordered_set init_stack; - initialize_component(name, init_stack); -} - -bool Registry::has_circular_dependency(const std::string& name, - const std::string& dependency) { - if (dependencies[dependency].count(name)) { - return true; - } - for (const auto& dep : dependencies[dependency]) { - if (has_circular_dependency(name, dep)) { - return true; - } - } - return false; -} - -void Registry::initialize_component( - const std::string& name, std::unordered_set& init_stack) { - if (initialized[name]) { - return; - } - if (init_stack.count(name)) { - THROW_RUNTIME_ERROR( - "Circular dependency detected while initializing: " + name); - } - init_stack.insert(name); - for (const auto& dep : dependencies[name]) { - initialize_component(dep, init_stack); - } - initializers[name].init_func(); - initialized[name] = true; - init_stack.erase(name); -} diff --git a/src/atom/components/registry.hpp b/src/atom/components/registry.hpp index 55ef741f..49292823 100644 --- a/src/atom/components/registry.hpp +++ b/src/atom/components/registry.hpp @@ -15,33 +15,44 @@ Description: Registry Pattern #ifndef ATOM_COMPONENT_REGISTRY_HPP #define ATOM_COMPONENT_REGISTRY_HPP -#include // for std::function -#include // for std::string -#include // for std::unordered_map -#include // for std::unordered_set -#include // for std::shared_mutex +#include // for std::reverse +#include // for std::shared_ptr +#include +#include // for std::scoped_lock +#include // for std::ranges +#include // for std::shared_mutex +#include // for std::string +#include // for std::unordered_map +#include // for std::unordered_set +#include +#include // for std::vector + +#include "atom/error/exception.hpp" +#include "component.hpp" +#include "macro.hpp" /** * @class Registry - * @brief Manages initialization and cleanup of components in a registry pattern. + * @brief Manages initialization and cleanup of components in a registry + * pattern. */ class Registry { public: /** - * @brief Type definition for initialization function. - */ - using InitFunc = std::function; - - /** - * @brief Type definition for cleanup function. + * @brief Gets the singleton instance of the Registry. + * @return Reference to the singleton instance of the Registry. */ - using CleanupFunc = std::function; + static inline auto instance() -> Registry& { + static Registry instance; + return instance; + } /** - * @brief Gets the singleton instance of the Registry. - * @return Reference to the singleton instance of the Registry. + * @brief Registers a module's initialization function. + * @param name The name of the module. + * @param init_func The initialization function for the module. */ - static Registry& instance(); + void registerModule(const std::string& name, Component::InitFunc init_func); /** * @brief Adds an initializer function for a component to the registry. @@ -49,38 +60,45 @@ class Registry { * @param init_func The initialization function for the component. * @param cleanup_func The cleanup function for the component (optional). */ - void add_initializer(const std::string& name, InitFunc init_func, - CleanupFunc cleanup_func = nullptr); + void addInitializer(const std::string& name, Component::InitFunc init_func, + Component::CleanupFunc cleanup_func = nullptr); /** * @brief Adds a dependency between two components. * @param name The name of the component. * @param dependency The name of the component's dependency. */ - void add_dependency(const std::string& name, const std::string& dependency); + void addDependency(const std::string& name, const std::string& dependency); /** * @brief Initializes all components in the registry. */ - void initialize_all(); + void initializeAll(); /** * @brief Cleans up all components in the registry. */ - void cleanup_all(); + void cleanupAll(); /** * @brief Checks if a component is initialized. * @param name The name of the component to check. * @return True if the component is initialized, false otherwise. */ - bool is_initialized(const std::string& name) const; + auto isInitialized(const std::string& name) const -> bool; /** * @brief Reinitializes a component in the registry. * @param name The name of the component to reinitialize. */ - void reinitialize_component(const std::string& name); + void reinitializeComponent(const std::string& name); + + auto getComponent(const std::string& name) const + -> std::shared_ptr; + + auto getAllComponents() const -> std::vector>; + + auto getAllComponentNames() const -> std::vector; private: /** @@ -88,33 +106,209 @@ class Registry { */ Registry() = default; - /** - * @brief Represents a component with its initialization and cleanup functions. - */ - struct Component { - InitFunc init_func; /**< The initialization function for the component. */ - CleanupFunc cleanup_func; /**< The cleanup function for the component. */ - }; - - std::unordered_map initializers; /**< Map of component names to their initialization and cleanup functions. */ - std::unordered_map> dependencies; /**< Map of component names to their dependencies. */ - std::unordered_map initialized; /**< Map of component names to their initialization status. */ - mutable std::shared_mutex mutex_; /**< Mutex for thread-safe access to the registry. */ + std::unordered_map> + initializers_; /**< Map of component names to their initialization and + cleanup functions. */ + std::unordered_map> + dependencies_; /**< Map of component names to their dependencies. */ + std::unordered_map + initialized_; /**< Map of component names to their initialization + status. */ + std::vector + initializationOrder_; /**< List of component names in initialization + order. */ + std::unordered_map + module_initializers_; /**< Map of module names to their initialization + functions. */ + mutable std::shared_mutex + mutex_; /**< Mutex for thread-safe access to the registry. */ /** * @brief Checks if adding a dependency creates a circular dependency. * @param name The name of the component. * @param dependency The name of the dependency. - * @return True if adding the dependency creates a circular dependency, false otherwise. + * @return True if adding the dependency creates a circular dependency, + * false otherwise. */ - bool has_circular_dependency(const std::string& name, const std::string& dependency); + bool hasCircularDependency(const std::string& name, + const std::string& dependency); /** * @brief Initializes a component and its dependencies recursively. * @param name The name of the component to initialize. - * @param init_stack Stack to keep track of components being initialized to detect circular dependencies. + * @param init_stack Stack to keep track of components being initialized to + * detect circular dependencies. + */ + void initializeComponent(const std::string& name, + std::unordered_set& init_stack); + + /** + * @brief Determines the order of initialization based on dependencies. */ - void initialize_component(const std::string& name, std::unordered_set& init_stack); + void determineInitializationOrder(); }; -#endif // ATOM_COMPONENT_REGISTRY_HPP +ATOM_INLINE void Registry::registerModule(const std::string& name, + Component::InitFunc init_func) { + std::scoped_lock lock(mutex_); + LOG_F(INFO, "Registering module: {}", name); + module_initializers_[name] = std::move(init_func); +} + +ATOM_INLINE void Registry::addInitializer(const std::string& name, + Component::InitFunc init_func, + Component::CleanupFunc cleanup_func) { + std::scoped_lock lock(mutex_); + if (initializers_.contains(name)) { + return; + } + initializers_[name] = std::make_shared(name); + initializers_[name]->initFunc = std::move(init_func); + initializers_[name]->cleanupFunc = std::move(cleanup_func); + initialized_[name] = false; +} + +ATOM_INLINE void Registry::addDependency(const std::string& name, + const std::string& dependency) { + std::unique_lock lock(mutex_); + if (hasCircularDependency(name, dependency)) { + THROW_RUNTIME_ERROR("Circular dependency detected: " + name + " -> " + + dependency); + } + dependencies_[name].insert(dependency); +} + +ATOM_INLINE void Registry::initializeAll() { + std::unique_lock lock(mutex_); + LOG_F(INFO, "Initializing all components"); + determineInitializationOrder(); + for (const auto& name : initializationOrder_) { + std::unordered_set initStack; + LOG_F(INFO, "Initializing component: {}", name); + initializeComponent(name, initStack); + } +} + +ATOM_INLINE void Registry::cleanupAll() { + std::unique_lock lock(mutex_); + for (const auto& name : std::ranges::reverse_view(initializationOrder_)) { + if (initializers_[name]->cleanupFunc && initialized_[name]) { + initializers_[name]->cleanupFunc(); + initialized_[name] = false; + } + } +} + +ATOM_INLINE auto Registry::isInitialized(const std::string& name) const + -> bool { + std::shared_lock lock(mutex_); + auto it = initialized_.find(name); + return it != initialized_.end() && it->second; +} + +ATOM_INLINE void Registry::reinitializeComponent(const std::string& name) { + std::scoped_lock lock(mutex_); + if (initialized_[name]) { + if (auto it = initializers_.find(name); + it != initializers_.end() && it->second->cleanupFunc) { + it->second->cleanupFunc(); + } + } + auto it = module_initializers_.find(name); + if (it != module_initializers_.end()) { + auto component = std::make_shared(name); + it->second(*component); + initializers_[name] = component; + initialized_[name] = true; + } +} + +ATOM_INLINE auto Registry::getComponent(const std::string& name) const + -> std::shared_ptr { + std::shared_lock lock(mutex_); + if (!initializers_.contains(name)) { + THROW_OBJ_NOT_EXIST("Component not registered: " + name); + } + return initializers_.at(name); +} + +ATOM_INLINE auto Registry::getAllComponents() const + -> std::vector> { + std::shared_lock lock(mutex_); + std::vector> components; + for (const auto& pair : initializers_) { + if (pair.second) { + components.push_back(pair.second); + } + } + return components; +} + +ATOM_INLINE auto Registry::getAllComponentNames() const + -> std::vector { + std::shared_lock lock(mutex_); + std::vector names; + names.reserve(initializers_.size()); + for (const auto& pair : initializers_) { + names.push_back(pair.first); + } + return names; +} + +ATOM_INLINE auto Registry::hasCircularDependency( + const std::string& name, const std::string& dependency) -> bool { + if (dependencies_[dependency].contains(name)) { + return true; + } + for (const auto& dep : dependencies_[dependency]) { + if (hasCircularDependency(name, dep)) { + return true; + } + } + return false; +} + +ATOM_INLINE void Registry::initializeComponent( + const std::string& name, std::unordered_set& init_stack) { + if (initialized_[name]) { + if (init_stack.contains(name)) { + THROW_RUNTIME_ERROR( + "Circular dependency detected while initializing component " + "'{}'", + name); + } + return; + } + if (init_stack.contains(name)) { + THROW_RUNTIME_ERROR( + "Circular dependency detected while initializing: " + name); + } + init_stack.insert(name); + for (const auto& dep : dependencies_[name]) { + initializeComponent(dep, init_stack); + } + if (initializers_[name]->initFunc) { + initializers_[name]->initFunc(*initializers_[name]); + } + initialized_[name] = true; + init_stack.erase(name); +} + +ATOM_INLINE void Registry::determineInitializationOrder() { + std::unordered_set visited; + std::function visit = + [&](const std::string& name) { + if (!visited.contains(name)) { + visited.insert(name); + for (const auto& dep : dependencies_[name]) { + visit(dep); + } + initializationOrder_.push_back(name); + } + }; + for (const auto& pair : initializers_) { + visit(pair.first); + } +} + +#endif // ATOM_COMPONENT_REGISTRY_HPP diff --git a/src/atom/components/types.hpp b/src/atom/components/types.hpp index d98447ce..7e1e6f19 100644 --- a/src/atom/components/types.hpp +++ b/src/atom/components/types.hpp @@ -26,8 +26,8 @@ Description: Basic Component Types Definition and Some Utilities // Helper to get the number of enum entries using constexpr reflection template constexpr auto enumSize() { - if constexpr (requires { Enum::LastEnumValue; }) { - return static_cast(Enum::LastEnumValue); + if constexpr (requires { Enum::LAST_ENUM_VALUE; }) { + return static_cast(Enum::LAST_ENUM_VALUE); } else { return 0; // Handle the error or throw a static_assert } @@ -35,14 +35,14 @@ constexpr auto enumSize() { template struct EnumReflection { - std::array, N> data; + std::array, N> data{}; - constexpr EnumReflection(const std::pair (&arr)[N]) - : data{} { + constexpr explicit EnumReflection( + const std::pair (&arr)[N]) { std::copy(std::begin(arr), std::end(arr), std::begin(data)); } - constexpr std::string_view toString(Enum e) const { + [[nodiscard]] constexpr auto toString(Enum e) const -> std::string_view { auto it = std::find_if(data.begin(), data.end(), [e](const auto& pair) { return pair.first == e; }); @@ -52,7 +52,8 @@ struct EnumReflection { return "Undefined"; } - constexpr std::optional fromString(std::string_view str) const { + [[nodiscard]] constexpr auto fromString(std::string_view str) const + -> std::optional { auto it = std::find_if( data.begin(), data.end(), [str](const auto& pair) { return pair.second == str; }); @@ -65,21 +66,21 @@ struct EnumReflection { enum class ComponentType { NONE, - SHREAD, - SHREAD_INJECTED, - Script, - Executable, - Task, - LastEnumValue + SHARED, + SHARED_INJECTED, + SCRIPT, + EXECUTABLE, + TASK, + LAST_ENUM_VALUE }; -constexpr auto componentTypeReflection = +constexpr auto COMPONENT_TYPE_REFLECTION = EnumReflection()>( {{ComponentType::NONE, "none"}, - {ComponentType::SHREAD, "shared"}, - {ComponentType::SHREAD_INJECTED, "injected"}, - {ComponentType::Script, "script"}, - {ComponentType::Executable, "executable"}, - {ComponentType::Task, "task"}}); + {ComponentType::SHARED, "shared"}, + {ComponentType::SHARED_INJECTED, "injected"}, + {ComponentType::SCRIPT, "script"}, + {ComponentType::EXECUTABLE, "executable"}, + {ComponentType::TASK, "task"}}); #endif diff --git a/src/atom/components/var.hpp b/src/atom/components/var.hpp index 3b0490e5..e08819e2 100644 --- a/src/atom/components/var.hpp +++ b/src/atom/components/var.hpp @@ -15,15 +15,13 @@ Description: Variable Manager #ifndef ATOM_COMPONENT_VAR_HPP #define ATOM_COMPONENT_VAR_HPP +#include #include -#include -#include #include -#include -#include #include #include #include + #if ENABLE_FASTHASH #include "emhash/hash_table8.hpp" #else @@ -31,9 +29,8 @@ Description: Variable Manager #endif #include "atom/error/exception.hpp" -#include "atom/type/noncopyable.hpp" #include "atom/type/trackable.hpp" -#include "atom/utils/cstring.hpp" +#include "macro.hpp" class VariableManager { public: @@ -43,6 +40,12 @@ class VariableManager { const std::string& alias = "", const std::string& group = ""); + template + void addVariable(const std::string& name, T C::*memberPointer, C& instance, + const std::string& description = "", + const std::string& alias = "", + const std::string& group = ""); + template void setRange(const std::string& name, T min, T max); @@ -50,20 +53,20 @@ class VariableManager { std::vector options); template - std::shared_ptr> getVariable(const std::string& name); + auto getVariable(const std::string& name) -> std::shared_ptr>; void setValue(const std::string& name, const char* newValue); template void setValue(const std::string& name, T newValue); - bool has(const std::string& name) const; + auto has(const std::string& name) const -> bool; - std::string getDescription(const std::string& name) const; + auto getDescription(const std::string& name) const -> std::string; - std::string getAlias(const std::string& name) const; + auto getAlias(const std::string& name) const -> std::string; - std::string getGroup(const std::string& name) const; + auto getGroup(const std::string& name) const -> std::string; private: struct VariableInfo { @@ -71,7 +74,7 @@ class VariableManager { std::string description; std::string alias; std::string group; - }; + } ATOM_ALIGNAS(128); #if ENABLE_FASTHASH emhash8::HashMap variables_; @@ -84,6 +87,133 @@ class VariableManager { #endif }; -#include "var.inl" +template +ATOM_INLINE void VariableManager::addVariable(const std::string& name, + T initialValue, + const std::string& description, + const std::string& alias, + const std::string& group) { + auto variable = std::make_shared>(std::move(initialValue)); + variables_[name] = {std::move(variable), description, alias, group}; +} + +template +ATOM_INLINE void VariableManager::addVariable(const std::string& name, + T C::*memberPointer, C& instance, + const std::string& description, + const std::string& alias, + const std::string& group) { + auto variable = std::make_shared>(instance.*memberPointer); + variable->setOnChangeCallback( + [&instance, memberPointer](const T& newValue) { + instance.*memberPointer = newValue; + }); + variables_[name] = {std::move(variable), description, alias, group}; +} + +template +ATOM_INLINE void VariableManager::setRange(const std::string& name, T min, + T max) { + if (auto variable = getVariable(name)) { + ranges_[name] = std::make_pair(std::move(min), std::move(max)); + } +} + +ATOM_INLINE void VariableManager::setStringOptions( + const std::string& name, std::vector options) { + if (auto variable = getVariable(name)) { + stringOptions_[name] = std::move(options); + } +} + +template +ATOM_INLINE auto VariableManager::getVariable(const std::string& name) + -> std::shared_ptr> { + auto it = variables_.find(name); + if (it != variables_.end()) { + try { + return std::any_cast>>( + it->second.variable); + } catch (const std::bad_any_cast& e) { + THROW_INVALID_ARGUMENT("Type mismatch: ", name); + } + } + return nullptr; +} + +ATOM_INLINE auto VariableManager::has(const std::string& name) const -> bool { + return variables_.find(name) != variables_.end(); +} + +ATOM_INLINE void VariableManager::setValue(const std::string& name, + const char* newValue) { + setValue(name, std::string(newValue)); +} + +template +ATOM_INLINE void VariableManager::setValue(const std::string& name, + T newValue) { + if (auto variable = getVariable(name)) { + if constexpr (std::is_arithmetic_v) { + if (ranges_.contains(name)) { + auto [min, max] = std::any_cast>(ranges_[name]); + if (newValue < min || newValue > max) { + THROW_OUT_OF_RANGE("Value out of range"); + } + } + } else if constexpr (std::is_same_v || + std::is_same_v) { + if (stringOptions_.contains(name)) { + auto& options = stringOptions_[name]; + if (std::find(options.begin(), options.end(), newValue) == + options.end()) { + THROW_INVALID_ARGUMENT("Invalid string option"); + } + } + } + *variable = std::move(newValue); + } else { + THROW_OBJ_NOT_EXIST("Variable not found"); + } +} + +ATOM_INLINE auto VariableManager::getDescription(const std::string& name) const + -> std::string { + if (auto it = variables_.find(name); it != variables_.end()) { + return it->second.description; + } + for (const auto& [key, value] : variables_) { + if (value.alias == name) { + return value.description; + } + } + return ""; +} + +ATOM_INLINE auto VariableManager::getAlias(const std::string& name) const + -> std::string { + if (auto it = variables_.find(name); it != variables_.end()) { + return it->second.alias; + } + for (const auto& [key, value] : variables_) { + if (value.alias == name) { + return key; + } + } + return ""; +} + +ATOM_INLINE auto VariableManager::getGroup(const std::string& name) const + -> std::string { + if (auto it = variables_.find(name); it != variables_.end()) { + return it->second.group; + } + for (const auto& [key, value] : variables_) { + if (value.alias == name) { + return value.group; + } + } + return ""; +} #endif diff --git a/src/atom/components/var.inl b/src/atom/components/var.inl deleted file mode 100644 index 77dbcdf9..00000000 --- a/src/atom/components/var.inl +++ /dev/null @@ -1,131 +0,0 @@ -/* - * var.inl - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-3-1 - -Description: Variable Manager - -**************************************************/ - -#ifndef ATOM_COMPONENT_VAR_INL -#define ATOM_COMPONENT_VAR_INL - -#include "var.hpp" - -template -inline void VariableManager::addVariable(const std::string& name, - T initialValue, - const std::string& description, - const std::string& alias, - const std::string& group) { - auto variable = std::make_shared>(std::move(initialValue)); - variables_[name] = {std::move(variable), description, alias, group}; -} - -template -inline void VariableManager::setRange(const std::string& name, T min, T max) { - if (auto variable = getVariable(name)) { - ranges_[name] = std::make_pair(std::move(min), std::move(max)); - } -} - -inline void VariableManager::setStringOptions( - const std::string& name, std::vector options) { - if (auto variable = getVariable(name)) { - stringOptions_[name] = std::move(options); - } -} - -template -inline std::shared_ptr> VariableManager::getVariable( - const std::string& name) { - auto it = variables_.find(name); - if (it != variables_.end()) { - try { - return std::any_cast>>( - it->second.variable); - } catch (const std::bad_any_cast& e) { - // THROW_EXCEPTION(concat("Type mismatch: ", name)); - } - } - return nullptr; -} - -inline bool VariableManager::has(const std::string& name) const { - return variables_.find(name) != variables_.end(); -} - -inline void VariableManager::setValue(const std::string& name, - const char* newValue) { - setValue(name, std::string(newValue)); -} - -template -inline void VariableManager::setValue(const std::string& name, T newValue) { - if (auto variable = getVariable(name)) { - if constexpr (std::is_arithmetic_v) { - if (ranges_.count(name)) { - auto [min, max] = std::any_cast>(ranges_[name]); - if (newValue < min || newValue > max) { - THROW_EXCEPTION("Value out of range"); - } - } - } else if constexpr (std::is_same_v || - std::is_same_v) { - if (stringOptions_.count(name)) { - auto& options = stringOptions_[name]; - if (std::find(options.begin(), options.end(), newValue) == - options.end()) { - THROW_EXCEPTION("Invalid string option"); - } - } - } - *variable = std::move(newValue); - } else { - THROW_EXCEPTION("Variable not found"); - } -} - -inline std::string VariableManager::getDescription( - const std::string& name) const { - if (auto it = variables_.find(name); it != variables_.end()) { - return it->second.description; - } - for (const auto& [key, value] : variables_) { - if (value.alias == name) { - return value.description; - } - } - return ""; -} - -inline std::string VariableManager::getAlias(const std::string& name) const { - if (auto it = variables_.find(name); it != variables_.end()) { - return it->second.alias; - } - for (const auto& [key, value] : variables_) { - if (value.alias == name) { - return key; - } - } - return ""; -} - -inline std::string VariableManager::getGroup(const std::string& name) const { - if (auto it = variables_.find(name); it != variables_.end()) { - return it->second.group; - } - for (const auto& [key, value] : variables_) { - if (value.alias == name) { - return value.group; - } - } - return ""; -} - -#endif diff --git a/src/atom/components/xmake.lua b/src/atom/components/xmake.lua new file mode 100644 index 00000000..e18b8b9e --- /dev/null +++ b/src/atom/components/xmake.lua @@ -0,0 +1,58 @@ +set_project("atom-component") +set_version("1.0.0") + +-- Set the C++ standard +set_languages("cxx20") + +-- Add required packages +add_requires("loguru") + +-- Define libraries +local atom_component_libs = { + "atom-error", + "atom-type", + "atom-utils" +} + +local atom_component_packages = { + "loguru", + "pthread" +} + +-- Source files +local source_files = { + "registry.cpp" +} + +-- Header files +local header_files = { + "component.hpp", + "dispatch.hpp", + "types.hpp", + "var.hpp" +} + +-- Object Library +target("atom-component_object") + set_kind("object") + add_files(table.unpack(source_files)) + add_headerfiles(table.unpack(header_files)) + add_deps(table.unpack(atom_component_libs)) + add_packages(table.unpack(atom_component_packages)) +target_end() + +-- Static Library +target("atom-component") + set_kind("static") + add_deps("atom-component_object") + add_files(table.unpack(source_files)) + add_headerfiles(table.unpack(header_files)) + add_packages(table.unpack(atom_component_libs)) + add_includedirs(".") + set_targetdir("$(buildir)/lib") + set_installdir("$(installdir)/lib") + set_version("1.0.0", {build = "%Y%m%d%H%M"}) + on_install(function (target) + os.cp(target:targetfile(), path.join(target:installdir(), "lib")) + end) +target_end() diff --git a/src/atom/connection/_pybind.cpp b/src/atom/connection/_pybind.cpp deleted file mode 100644 index 44e74fb2..00000000 --- a/src/atom/connection/_pybind.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* - * _pybind.cpp - * - * Copyright (C) 2023-2024 Max Qian - */ - -/************************************************* - -Date: 2024-4-13 - -Description: Python Binding of Atom-Connection - -**************************************************/ - -#include - -#include "fifoclient.hpp" -#include "fifoserver.hpp" -#include "shared_memory.hpp" -#include "sockethub.hpp" -#include "udp_server.hpp" - -#if ENABLE_SSHCLIENT -#include "sshclient.hpp" -#endif -namespace py = pybind11; - -using namespace atom::connection; - -template -void bind_shared_memory(py::module &m, const std::string &type_name) { - py::class_>(m, ("shared_memory_" + type_name).c_str()) - .def(py::init()) - .def("write", &SharedMemory::write, py::arg("data"), - py::arg("timeout") = std::chrono::milliseconds(0)) - .def("read", &SharedMemory::read, - py::arg("timeout") = std::chrono::milliseconds(0)) - .def("clear", &SharedMemory::clear) - .def("isOccupied", &SharedMemory::isOccupied); -} - -PYBIND11_MODULE(atom_connection, m) { - m.doc() = "Atom Connection Python Binding"; - - py::class_(m, "FifoClient") - .def(py::init()) - .def("write", &FifoClient::write) - .def("read", &FifoClient::read); - - py::class_(m, "FIFOServer") - .def(py::init()) - .def("sendMessage", &FIFOServer::sendMessage); - - bind_shared_memory(m, "int"); - bind_shared_memory(m, "float"); - bind_shared_memory(m, "double"); - - py::class_(m, "UdpSocketHub") - .def(py::init<>()) - .def("start", &UdpSocketHub::start, py::arg("port")) - .def("stop", &UdpSocketHub::stop) - .def("addHandler", &UdpSocketHub::addHandler) - .def("sendTo", &UdpSocketHub::sendTo, py::arg("message"), py::arg("ip"), - py::arg("port")); - -#if ENABLE_SSHCLIENT - py::class_(m, "SSHClient") - .def(py::init()) - .def("Connect", &SSHClient::Connect, py::arg("username"), - py::arg("password"), py::arg("timeout") = 10) - .def("IsConnected", &SSHClient::IsConnected) - .def("Disconnect", &SSHClient::Disconnect) - .def("ExecuteCommand", &SSHClient::ExecuteCommand) - .def("ExecuteCommands", &SSHClient::ExecuteCommands) - .def("FileExists", &SSHClient::FileExists) - .def("CreateDirectory", &SSHClient::CreateDirectory, - py::arg("remote_path"), py::arg("mode") = S_NORMAL) - .def("RemoveFile", &SSHClient::RemoveFile) - .def("RemoveDirectory", &SSHClient::RemoveDirectory) - .def("ListDirectory", &SSHClient::ListDirectory) - .def("Rename", &SSHClient::Rename) - .def("GetFileInfo", &SSHClient::GetFileInfo) - .def("DownloadFile", &SSHClient::DownloadFile) - .def("UploadFile", &SSHClient::UploadFile); -#endif - - py::class_(m, "SocketHub") - .def(py::init<>()) - .def("start", &SocketHub::start) - .def("stop", &SocketHub::stop) - .def("addHandler", &SocketHub::addHandler); -} diff --git a/src/atom/connection/fifoclient.cpp b/src/atom/connection/fifoclient.cpp index b347759c..2861437c 100644 --- a/src/atom/connection/fifoclient.cpp +++ b/src/atom/connection/fifoclient.cpp @@ -62,7 +62,7 @@ bool FifoClient::write(std::string_view data, } #else if (!timeout.has_value()) { - return write(m_fifo, buffer.data(), buffer.size()) != -1; + return ::write(m_fifo, buffer.data(), buffer.size()) != -1; } else { fd_set writeFds; FD_ZERO(&writeFds); @@ -76,7 +76,7 @@ bool FifoClient::write(std::string_view data, } else if (selectResult == 0) { return false; // Timeout occurred } else { - return write(m_fifo, buffer.data(), buffer.size()) != -1; + return ::write(m_fifo, buffer.data(), buffer.size()) != -1; } } #endif @@ -113,7 +113,7 @@ std::optional FifoClient::read( #else if (!timeout.has_value()) { ssize_t bytesRead; - while ((bytesRead = read(m_fifo, buffer, sizeof(buffer) - 1)) > 0) { + while ((bytesRead = ::read(m_fifo, buffer, sizeof(buffer) - 1)) > 0) { buffer[bytesRead] = '\0'; data += buffer; } @@ -130,7 +130,7 @@ std::optional FifoClient::read( } else if (selectResult == 0) { // Timeout occurred } else { - ssize_t bytesRead = read(m_fifo, buffer, sizeof(buffer) - 1); + ssize_t bytesRead = ::read(m_fifo, buffer, sizeof(buffer) - 1); if (bytesRead > 0) { buffer[bytesRead] = '\0'; data += buffer; diff --git a/src/atom/connection/fifoserver.cpp b/src/atom/connection/fifoserver.cpp index 7b60f3d5..cbcd3927 100644 --- a/src/atom/connection/fifoserver.cpp +++ b/src/atom/connection/fifoserver.cpp @@ -14,6 +14,7 @@ Description: FIFO Server #include "fifoserver.hpp" +#include #include #include #include @@ -34,7 +35,7 @@ namespace atom::connection { class FIFOServer::Impl { public: explicit Impl(std::string_view fifo_path) - : fifo_path_(std::move(fifo_path)) { + : fifo_path_(fifo_path), stop_server_(false) { // 创建 FIFO 文件 #ifdef _WIN32 CreateNamedPipeA(fifo_path_.c_str(), PIPE_ACCESS_DUPLEX, @@ -58,7 +59,7 @@ class FIFOServer::Impl { void sendMessage(std::string message) { { std::scoped_lock lock(queue_mutex_); - message_queue_.push(std::move(message)); + message_queue_.emplace(std::move(message)); } message_cv_.notify_one(); } @@ -66,7 +67,7 @@ class FIFOServer::Impl { void start() { if (!server_thread_.joinable()) { stop_server_ = false; - server_thread_ = std::thread([this] { serverLoop(); }); + server_thread_ = std::jthread([this] { serverLoop(); }); } } @@ -89,39 +90,45 @@ class FIFOServer::Impl { message_cv_.wait(lock, [this] { return stop_server_ || !message_queue_.empty(); }); - if (stop_server_) { + if (stop_server_ && message_queue_.empty()) { break; } - message = std::move(message_queue_.front()); - message_queue_.pop(); + if (!message_queue_.empty()) { + message = std::move(message_queue_.front()); + message_queue_.pop(); + } } #ifdef _WIN32 HANDLE pipe = CreateFileA(fifo_path_.c_str(), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); - DWORD bytes_written; - WriteFile(pipe, message.c_str(), - static_cast(message.length()), &bytes_written, - NULL); - CloseHandle(pipe); + if (pipe != INVALID_HANDLE_VALUE) { + DWORD bytes_written; + WriteFile(pipe, message.c_str(), + static_cast(message.length()), &bytes_written, + NULL); + CloseHandle(pipe); + } #elif __APPLE__ || __linux__ int fd = open(fifo_path_.c_str(), O_WRONLY); - write(fd, message.c_str(), message.length()); - close(fd); + if (fd != -1) { + write(fd, message.c_str(), message.length()); + close(fd); + } #endif } } std::string fifo_path_; - std::thread server_thread_; - std::atomic_bool stop_server_ = true; + std::jthread server_thread_; + std::atomic_bool stop_server_; std::queue message_queue_; std::mutex queue_mutex_; std::condition_variable message_cv_; }; FIFOServer::FIFOServer(std::string_view fifo_path) - : impl_(std::make_unique(std::move(fifo_path))) {} + : impl_(std::make_unique(fifo_path)) {} FIFOServer::~FIFOServer() = default; diff --git a/src/atom/connection/meson.build b/src/atom/connection/meson.build deleted file mode 100644 index 86100c66..00000000 --- a/src/atom/connection/meson.build +++ /dev/null @@ -1,90 +0,0 @@ -project('atom-connection', 'c', 'cpp', - version : '1.0.0', - license : 'GPL3' -) - -# C++ standard -cpp_std = 'c++11' - -# Sources -sources = [ - 'fifoclient.cpp', - 'fifoserver.cpp', - 'sockethub.cpp', - 'tcpclient.cpp', - 'udp_server.cpp', - 'udpclient.cpp' -] - -# Headers -headers = [ - 'fifoclient.hpp', - 'fifoserver.hpp', - 'sockethub.hpp', - 'tcpclient.hpp', - 'udp_server.hpp', - 'udpclient.hpp' -] - -if get_option('enable_libssh') - sources += [ - 'sshclient.cpp', - 'sshserver.cpp' - ] - headers += [ - 'sshclient.hpp', - 'sshserver.hpp' - ] -endif - -# Dependencies -loguru_dep = dependency('loguru') -threads_dep = dependency('threads') - -libs = [ - loguru_dep, - threads_dep -] - -if get_option('enable_ssh') - libssh_dep = dependency('libssh') - libs += [libssh_dep] -endif - -# Build Object Library -atom_connection_obj = static_library('atom-connection-object', - sources, - cpp_args : ['-fPIC'], - dependencies : libs -) - -# Build Static Library -atom_connection = static_library('atom-connection', - [], - link_with : atom_connection_obj, - dependencies : libs, - include_directories : include_directories('.') -) - -# Set version properties -atom_connection.soversion = '1' -atom_connection.version = '0.1' - -# Install -install_headers(headers, subdir : 'atom-connection') -install_library(atom_connection, subdir : get_option('libdir')) - -# Python binding -if get_option('atom_build_python') - pybind11_dep = dependency('pybind11') - py_module = import('python') - pybind_module = py_module.extension_module('atom-connection-py', - 'pybind.cpp', - link_with : atom_connection, - dependencies : [pybind11_dep] - ) - - if host_machine.system() == 'windows' - pybind_module.link_with : 'ws2_32' - endif -endif diff --git a/src/atom/connection/sockethub.cpp b/src/atom/connection/sockethub.cpp index d9e01f81..0c942b47 100644 --- a/src/atom/connection/sockethub.cpp +++ b/src/atom/connection/sockethub.cpp @@ -17,21 +17,98 @@ Description: SocketHub类用于管理socket连接的类。 #include #include #include +#include +#include #include +#include #include "atom/log/loguru.hpp" -#ifndef _WIN32 -typedef int SOCKET; +#ifdef _WIN32 +#include +#include +#pragma comment(lib, "ws2_32.lib") +#else +#include +#include +#include +#include #endif namespace atom::connection { -SocketHub::SocketHub() : running(false) {} -SocketHub::~SocketHub() { stop(); } +class SocketHubImpl { +public: + SocketHubImpl() + : running_(false), + serverSocket(-1) +#ifdef __linux__ + , + epoll_fd(-1) +#endif + { + } + + ~SocketHubImpl() { stop(); } + + void start(int port); + void stop(); + void addHandler(std::function handler); + [[nodiscard]] auto isRunning() const -> bool; + +private: + static const int maxConnections = 10; + std::atomic running_; +#ifdef _WIN32 + SOCKET serverSocket; + std::vector clients; +#else + int serverSocket; + std::vector clients; + int epoll_fd; +#endif + std::map clientThreads_; + std::mutex clientMutex; +#if __cplusplus >= 202002L + std::jthread acceptThread; +#else + std::unique_ptr acceptThread; +#endif + + std::function handler; + + bool initWinsock(); + void cleanupWinsock(); +#ifdef _WIN32 + void closeSocket(SOCKET socket); +#else + void closeSocket(int socket); +#endif + void acceptConnections(); +#ifdef _WIN32 + void handleClientMessages(SOCKET clientSocket); +#else + void handleClientMessages(int clientSocket); +#endif + void cleanupSocket(); +}; + +SocketHub::SocketHub() : impl_(std::make_unique()) {} + +SocketHub::~SocketHub() = default; + +void SocketHub::start(int port) { impl_->start(port); } + +void SocketHub::stop() { impl_->stop(); } -void SocketHub::start(int port) { - if (running.load()) { +void SocketHub::addHandler(std::function handler) { + impl_->addHandler(std::move(handler)); +} + +auto SocketHub::isRunning() const -> bool { return impl_->isRunning(); } + +void SocketHubImpl::start(int port) { + if (running_.load()) { LOG_F(WARNING, "SocketHub is already running."); return; } @@ -81,28 +158,45 @@ void SocketHub::start(int port) { return; } - running.store(true); +#ifdef __linux__ + epoll_fd = epoll_create1(0); + if (epoll_fd == -1) { + LOG_F(ERROR, "Failed to create epoll file descriptor."); + cleanupSocket(); + return; + } + + struct epoll_event event; + event.events = EPOLLIN; + event.data.fd = serverSocket; + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, serverSocket, &event) == -1) { + LOG_F(ERROR, "Failed to add server socket to epoll."); + cleanupSocket(); + return; + } +#endif + + running_.store(true); DLOG_F(INFO, "SocketHub started on port {}", port); #if __cplusplus >= 202002L - acceptThread = - std::make_unique(&SocketHub::acceptConnections, this); + acceptThread = std::jthread(&SocketHubImpl::acceptConnections, this); #else acceptThread = - std::make_unique(&SocketHub::acceptConnections, this); + std::make_unique(&SocketHubImpl::acceptConnections, this); #endif } -void SocketHub::stop() { - if (!running.load()) { +void SocketHubImpl::stop() { + if (!running_.load()) { LOG_F(WARNING, "SocketHub is not running."); return; } - running.store(false); + running_.store(false); - if (acceptThread && acceptThread->joinable()) { - acceptThread->join(); + if (acceptThread.joinable()) { + acceptThread.join(); } cleanupSocket(); @@ -110,11 +204,11 @@ void SocketHub::stop() { DLOG_F(INFO, "SocketHub stopped."); } -void SocketHub::addHandler(std::function handler) { +void SocketHubImpl::addHandler(std::function handler) { this->handler = std::move(handler); } -bool SocketHub::initWinsock() { +bool SocketHubImpl::initWinsock() { #ifdef _WIN32 WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { @@ -125,92 +219,138 @@ bool SocketHub::initWinsock() { return true; } -void SocketHub::cleanupWinsock() { +void SocketHubImpl::cleanupWinsock() { #ifdef _WIN32 WSACleanup(); #endif } #ifdef _WIN32 -void SocketHub::closeSocket(SOCKET socket) +void SocketHubImpl::closeSocket(SOCKET socket) { closesocket(socket); } #else -void SocketHub::closeSocket(int socket) +void SocketHubImpl::closeSocket(int socket) { close(socket); } #endif -{ -#ifdef _WIN32 - closesocket(socket); -#else - close(socket); -#endif -} -void SocketHub::acceptConnections() { - while (running.load()) { +void SocketHubImpl::acceptConnections() { +#ifdef __linux__ + struct epoll_event events[maxConnections]; + while (running_.load()) { + int n = epoll_wait(epoll_fd, events, maxConnections, -1); + for (int i = 0; i < n; i++) { + if (events[i].data.fd == serverSocket) { + sockaddr_in clientAddress{}; + socklen_t clientAddressLength = sizeof(clientAddress); + int clientSocket = accept( + serverSocket, reinterpret_cast(&clientAddress), + &clientAddressLength); + + if (clientSocket < 0) { + if (running_.load()) { + LOG_F(ERROR, "Failed to accept client connection."); + } + continue; + } + + struct epoll_event event; + event.events = EPOLLIN | EPOLLET; + event.data.fd = clientSocket; + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, clientSocket, &event) == + -1) { + LOG_F(ERROR, "Failed to add client socket to epoll."); + closeSocket(clientSocket); + continue; + } + + std::scoped_lock lock(clientMutex); + clients.push_back(clientSocket); + + clientThreads_[clientSocket] = std::jthread( + &SocketHubImpl::handleClientMessages, this, clientSocket); + } else { + handleClientMessages(events[i].data.fd); + } + } + } +#else + while (running_.load()) { sockaddr_in clientAddress{}; socklen_t clientAddressLength = sizeof(clientAddress); SOCKET clientSocket = accept(serverSocket, reinterpret_cast(&clientAddress), &clientAddressLength); -#ifdef _WIN32 - if (clientSocket == INVALID_SOCKET) -#else - if (clientSocket < 0) -#endif - { - if (running.load()) { + if (clientSocket == INVALID_SOCKET) { + if (running_.load()) { LOG_F(ERROR, "Failed to accept client connection."); } continue; } + std::scoped_lock lock(clientMutex); clients.push_back(clientSocket); -#if __cplusplus >= 202002L - clientThreads.push_back(std::make_unique( - [this, clientSocket]() { handleClientMessages(clientSocket); })); -#else - clientThreads.push_back(std::make_unique( - [this, clientSocket]() { handleClientMessages(clientSocket); })); -#endif + std::jthread(&SocketHubImpl::handleClientMessages, this, clientSocket) + .detach(); } +#endif } -void SocketHub::handleClientMessages(SOCKET clientSocket) { +#ifdef _WIN32 +void SocketHubImpl::handleClientMessages(SOCKET clientSocket) { +#else +void SocketHubImpl::handleClientMessages(int clientSocket) { +#endif char buffer[1024]; - while (running.load()) { + while (running_.load()) { memset(buffer, 0, sizeof(buffer)); int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0); if (bytesRead <= 0) { - closeSocket(clientSocket); - clients.erase( - std::remove(clients.begin(), clients.end(), clientSocket), - clients.end()); + { + std::scoped_lock lock(clientMutex); + closeSocket(clientSocket); + clients.erase( + std::remove(clients.begin(), clients.end(), clientSocket), + clients.end()); + } +#ifdef __linux__ + clientThreads_.erase(clientSocket); +#endif break; } std::string message(buffer, bytesRead); - if (handler) { handler(message); } } } -void SocketHub::cleanupSocket() { - for (const auto &client : clients) { - closeSocket(client); +void SocketHubImpl::cleanupSocket() { + { + std::scoped_lock lock(clientMutex); + for (const auto &client : clients) { + closeSocket(client); + } + clients.clear(); } - clients.clear(); closeSocket(serverSocket); - for (auto &thread : clientThreads) { - if (thread->joinable()) { - thread->join(); +#ifdef __linux__ + if (epoll_fd != -1) { + close(epoll_fd); + epoll_fd = -1; + } +#endif + + for (auto &pair : clientThreads_) { + if (pair.second.joinable()) { + pair.second.join(); } } - clientThreads.clear(); + clientThreads_.clear(); } +auto SocketHubImpl::isRunning() const -> bool { return running_.load(); } + } // namespace atom::connection diff --git a/src/atom/connection/sockethub.hpp b/src/atom/connection/sockethub.hpp index 5f898bce..1f3b2f32 100644 --- a/src/atom/connection/sockethub.hpp +++ b/src/atom/connection/sockethub.hpp @@ -8,173 +8,89 @@ Date: 2023-6-1 -Description: SocketHub类用于管理socket连接的类。 +Description: SocketHub class for managing socket connections. *************************************************/ #ifndef ATOM_CONNECTION_SOCKETHUB_HPP #define ATOM_CONNECTION_SOCKETHUB_HPP -#include #include #include -#include -#include - -#ifdef _WIN32 -#include -#include -#pragma comment(lib, "ws2_32.lib") -#else -#include -#include -#include -#endif +#include namespace atom::connection { + +class SocketHubImpl; + /** * @class SocketHub - * @brief 用于管理socket连接的类。 - * - * SocketHub类提供了启动和停止socket服务的功能,同时管理多个客户端连接。 - * 它支持接收来自客户端的连接,并为每个客户端启动一个线程处理消息。 - * - * @class SocketHub - * @brief A class for managing socket connections. + * @brief Manages socket connections. * - * The SocketHub class offers functionalities to start and stop a socket - * service, while managing multiple client connections. It supports accepting - * connections from clients and starts a thread for each client to handle - * messages. + * The SocketHub class is responsible for managing socket connections. + * It provides functionality to start and stop the socket service, and + * handles multiple client connections. For each client, it spawns a + * thread to handle incoming messages. The class allows for adding + * custom message handlers that are called when a message is received + * from a client. */ class SocketHub { public: /** - * @brief 构造函数。 - * @brief Constructor. + * @brief Constructs a SocketHub instance. */ SocketHub(); /** - * @brief 析构函数,负责资源的清理。 - * @brief Destructor, responsible for cleaning up resources. + * @brief Destroys the SocketHub instance. + * + * Cleans up resources and stops any ongoing socket operations. */ ~SocketHub(); /** - * @brief 启动socket服务并监听指定端口。 + * @brief Starts the socket service. + * @param port The port number on which the socket service will listen. * - * @param port 要监听的端口号。 - * @brief Starts the socket service and listens on the specified port. - * - * @param port The port number to listen on. + * Initializes the socket service and starts listening for incoming + * connections on the specified port. It spawns threads to handle + * each connected client. */ void start(int port); /** - * @brief 停止socket服务并关闭所有连接。 - * @brief Stops the socket service and closes all connections. + * @brief Stops the socket service. + * + * Shuts down the socket service, closes all client connections, + * and stops any running threads associated with handling client + * messages. */ void stop(); /** - * @brief 添加消息处理函数。 - * - * @param handler 消息处理函数。 * @brief Adds a message handler. + * @param handler A function to handle incoming messages from clients. * - * @param handler The message handler. + * The provided handler function will be called with the received + * message as a string parameter. Multiple handlers can be added + * and will be called in the order they are added. */ void addHandler(std::function handler); -private: - static const int maxConnections = 10; ///< 最大连接数。 - ///< Maximum number of connections. - std::atomic - running; ///< 指示服务是否正在运行的标志。 - ///< Flag indicating whether the service is running. -#ifdef _WIN32 - SOCKET serverSocket; ///< 服务器socket。 - ///< Server socket. - std::vector clients; ///< 客户端sockets列表。 - ///< List of client sockets. -#else - int serverSocket; - std::vector clients; -#endif -#if __cplusplus >= 202002L - std::unique_ptr - acceptThread; ///< 用于接受连接的线程。 - ///< Thread for accepting connections. - std::vector> - clientThreads; ///< 客户端处理线程列表。 - ///< List of threads for handling clients. -#else - std::unique_ptr - acceptThread; ///< 用于接受连接的线程。 - ///< Thread for accepting connections. - std::vector> - clientThreads; ///< 客户端处理线程列表。 - ///< List of threads for handling clients. -#endif - - std::function handler; ///< 消息处理函数。 - /** - * @brief 初始化Winsock。 - * - * @return 成功返回true,失败返回false。 - * @brief Initializes Winsock. + * @brief Checks if the socket service is currently running. + * @return True if the socket service is running, false otherwise. * - * @return true on success, false on failure. + * This method returns the status of the socket service, indicating + * whether it is currently active and listening for connections. */ - bool initWinsock(); + [[nodiscard]] auto isRunning() const -> bool; - /** - * @brief 清理Winsock资源。 - * @brief Cleans up Winsock resources. - */ - void cleanupWinsock(); - - /** - * @brief 关闭指定的socket。 - * - * @param socket 要关闭的socket。 - * @brief Closes the specified socket. - * - * @param socket The socket to close. - */ -#ifdef _WIN32 - void closeSocket(SOCKET socket); -#else - void closeSocket(int socket); -#endif - - /** - * @brief 接受客户端连接并将它们添加到clients列表。 - * @brief Accepts client connections and adds them to the clients list. - */ - void acceptConnections(); - - /** - * @brief 处理客户端消息。 - * - * @param clientSocket 客户端socket。 - * @brief Handles messages from a client. - * - * @param clientSocket The client socket. - */ -#ifdef _WIN32 - void handleClientMessages(SOCKET clientSocket); -#else - void handleClientMessages(int clientSocket); -#endif - /** - * @brief 清理sockets资源,关闭所有客户端连接。 - * @brief Cleans up socket resources, closing all client connections. - */ - void cleanupSocket(); +private: + std::unique_ptr + impl_; ///< Pointer to the implementation details of SocketHub. }; + } // namespace atom::connection -#endif +#endif // ATOM_CONNECTION_SOCKETHUB_HPP diff --git a/src/atom/connection/sshclient.hpp b/src/atom/connection/sshclient.hpp index b3caaa8c..54c95096 100644 --- a/src/atom/connection/sshclient.hpp +++ b/src/atom/connection/sshclient.hpp @@ -22,6 +22,7 @@ Description: SSH Client #include #include +#if __has_include() #include #include @@ -161,5 +162,6 @@ class SSHClient { sftp_session m_sftp_session; }; } // namespace atom::connection +#endif #endif diff --git a/src/atom/connection/tcpclient.cpp b/src/atom/connection/tcpclient.cpp index bf9aa882..da33e119 100644 --- a/src/atom/connection/tcpclient.cpp +++ b/src/atom/connection/tcpclient.cpp @@ -15,6 +15,7 @@ Description: TCP Client Class #include "tcpclient.hpp" #include +#include #include #include @@ -25,6 +26,7 @@ Description: TCP Client Class #else #include #include +#include #include #include #endif @@ -46,12 +48,22 @@ class TcpClient::Impl { if (socket_ < 0) { THROW_RUNTIME_ERROR("Socket creation failed"); } + +#ifdef __linux__ + epoll_fd_ = epoll_create1(0); + if (epoll_fd_ == -1) { + THROW_RUNTIME_ERROR("Failed to create epoll file descriptor"); + } +#endif } ~Impl() { disconnect(); #ifdef _WIN32 WSACleanup(); +#endif +#ifdef __linux__ + close(epoll_fd_); #endif } @@ -70,13 +82,19 @@ class TcpClient::Impl { serverAddress.sin_port = htons(port); if (timeout > std::chrono::milliseconds::zero()) { - struct timeval tv; - tv.tv_sec = timeout.count() / 1000; - tv.tv_usec = (timeout.count() % 1000) * 1000; +#ifdef _WIN32 + DWORD tv = timeout.count(); setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(tv)); setsockopt(socket_, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&tv), sizeof(tv)); +#else + struct timeval tv; + tv.tv_sec = timeout.count() / 1000; + tv.tv_usec = (timeout.count() % 1000) * 1000; + setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + setsockopt(socket_, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); +#endif } if (::connect(socket_, @@ -87,6 +105,17 @@ class TcpClient::Impl { } connected_ = true; + +#ifdef __linux__ + struct epoll_event event; + event.events = EPOLLIN | EPOLLOUT; + event.data.fd = socket_; + if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, socket_, &event) == -1) { + errorMessage_ = "Failed to add file descriptor to epoll"; + return false; + } +#endif + return true; } @@ -115,25 +144,32 @@ class TcpClient::Impl { return true; } - std::vector receive( + std::future> receive( size_t size, std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()) { - if (timeout > std::chrono::milliseconds::zero()) { - struct timeval tv; - tv.tv_sec = timeout.count() / 1000; - tv.tv_usec = (timeout.count() % 1000) * 1000; - setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, - reinterpret_cast(&tv), sizeof(tv)); - } + return std::async(std::launch::async, [this, size, timeout] { + if (timeout > std::chrono::milliseconds::zero()) { +#ifdef _WIN32 + DWORD tv = timeout.count(); + setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#else + struct timeval tv; + tv.tv_sec = timeout.count() / 1000; + tv.tv_usec = (timeout.count() % 1000) * 1000; + setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); +#endif + } - std::vector data(size); - ssize_t bytesRead = ::recv(socket_, data.data(), size, 0); - if (bytesRead < 0) { - errorMessage_ = "Receive failed"; - return {}; - } - data.resize(bytesRead); - return data; + std::vector data(size); + ssize_t bytesRead = ::recv(socket_, data.data(), size, 0); + if (bytesRead < 0) { + errorMessage_ = "Receive failed"; + return std::vector{}; + } + data.resize(bytesRead); + return data; + }); } [[nodiscard]] bool isConnected() const { return connected_; } @@ -171,11 +207,26 @@ class TcpClient::Impl { private: void receivingLoop(size_t bufferSize) { +#ifdef __linux__ + struct epoll_event events[10]; +#endif while (!receivingStopped_) { - std::vector data = receive(bufferSize); +#ifdef __linux__ + int n = epoll_wait(epoll_fd_, events, 10, -1); + for (int i = 0; i < n; i++) { + if (events[i].events & EPOLLIN) { + std::vector data = receive(bufferSize).get(); + if (!data.empty() && onDataReceivedCallback_) { + onDataReceivedCallback_(data); + } + } + } +#else + std::vector data = receive(bufferSize).get(); if (!data.empty() && onDataReceivedCallback_) { onDataReceivedCallback_(data); } +#endif } } @@ -183,6 +234,7 @@ class TcpClient::Impl { SOCKET socket_; #else int socket_; + int epoll_fd_; #endif bool connected_ = false; std::string errorMessage_; @@ -211,8 +263,8 @@ bool TcpClient::send(const std::vector& data) { return impl_->send(data); } -std::vector TcpClient::receive(size_t size, - std::chrono::milliseconds timeout) { +std::future> TcpClient::receive( + size_t size, std::chrono::milliseconds timeout) { return impl_->receive(size, timeout); } diff --git a/src/atom/connection/tcpclient.hpp b/src/atom/connection/tcpclient.hpp index f3135ed1..580705b9 100644 --- a/src/atom/connection/tcpclient.hpp +++ b/src/atom/connection/tcpclient.hpp @@ -17,6 +17,7 @@ Description: TCP Client Class #include #include +#include #include #include #include @@ -52,7 +53,7 @@ class TcpClient : public NonCopyable { /** * @brief Destructor. */ - ~TcpClient(); + ~TcpClient() override; /** * @brief Connects to a TCP server. @@ -61,9 +62,9 @@ class TcpClient : public NonCopyable { * @param timeout The connection timeout duration. * @return True if the connection is successful, false otherwise. */ - bool connect( - const std::string& host, int port, - std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()); + auto connect(const std::string& host, int port, + std::chrono::milliseconds timeout = + std::chrono::milliseconds::zero()) -> bool; /** * @brief Disconnects from the server. @@ -75,7 +76,7 @@ class TcpClient : public NonCopyable { * @param data The data to be sent. * @return True if the data is sent successfully, false otherwise. */ - bool send(const std::vector& data); + auto send(const std::vector& data) -> bool; /** * @brief Receives data from the server. @@ -83,21 +84,21 @@ class TcpClient : public NonCopyable { * @param timeout The receive timeout duration. * @return The received data. */ - std::vector receive( - size_t size, - std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()); + auto receive(size_t size, std::chrono::milliseconds timeout = + std::chrono::milliseconds::zero()) + -> std::future>; /** * @brief Checks if the client is connected to the server. * @return True if connected, false otherwise. */ - bool isConnected() const; + [[nodiscard]] auto isConnected() const -> bool; /** * @brief Gets the error message in case of any error. * @return The error message. */ - std::string getErrorMessage() const; + [[nodiscard]] auto getErrorMessage() const -> std::string; /** * @brief Sets the callback function to be called when connected to the diff --git a/src/atom/connection/ttybase.cpp b/src/atom/connection/ttybase.cpp new file mode 100644 index 00000000..0f5924a7 --- /dev/null +++ b/src/atom/connection/ttybase.cpp @@ -0,0 +1,696 @@ +#include "ttybase.hpp" + +#if defined(_WIN32) || defined(_WIN64) +#include +#else +#include +#include +#include +#include +#endif + +#include "atom/log/loguru.hpp" + +TTYBase::~TTYBase() { + if (m_PortFD != -1) { + disconnect(); + } +} + +TTYBase::TTYResponse TTYBase::checkTimeout(uint8_t timeout) { +#ifdef _WIN32 + // Windows specific implementation + COMMTIMEOUTS timeouts = {0}; + timeouts.ReadIntervalTimeout = timeout; + timeouts.ReadTotalTimeoutConstant = timeout * 1000; + timeouts.ReadTotalTimeoutMultiplier = 0; + timeouts.WriteTotalTimeoutConstant = timeout * 1000; + timeouts.WriteTotalTimeoutMultiplier = 0; + + if (!SetCommTimeouts(reinterpret_cast(m_PortFD), &timeouts)) + return TTYResponse::Errno; + + return TTYResponse::OK; +#else + if (m_PortFD == -1) { + return TTYResponse::Errno; + } + + struct timeval tv; + fd_set readout; + int retval; + + FD_ZERO(&readout); + FD_SET(m_PortFD, &readout); + + tv.tv_sec = timeout; + tv.tv_usec = 0; + + retval = select(m_PortFD + 1, &readout, nullptr, nullptr, &tv); + + if (retval > 0) { + return TTYResponse::OK; + } + if (retval == -1) { + return TTYResponse::SelectError; + } + return TTYResponse::Timeout; +#endif +} + +TTYBase::TTYResponse TTYBase::write(const uint8_t* buffer, uint32_t nbytes, + uint32_t& nbytesWritten) { + if (m_PortFD == -1) + return TTYResponse::Errno; + +#ifdef _WIN32 + // Windows specific write implementation + DWORD bytesWritten; + if (!WriteFile(reinterpret_cast(m_PortFD), buffer, nbytes, + &bytesWritten, nullptr)) + return TTYResponse::WriteError; + + nbytesWritten = bytesWritten; + return TTYResponse::OK; +#else + int bytesW = 0; + nbytesWritten = 0; + + while (nbytes > 0) { + bytesW = ::write(m_PortFD, buffer + nbytesWritten, nbytes); + + if (bytesW < 0) { + return TTYResponse::WriteError; + } + + nbytesWritten += bytesW; + nbytes -= bytesW; + } + + return TTYResponse::OK; +#endif +} + +TTYBase::TTYResponse TTYBase::writeString(std::string_view string, + uint32_t& nbytesWritten) { + return write(reinterpret_cast(string.data()), string.size(), + nbytesWritten); +} + +TTYBase::TTYResponse TTYBase::read(uint8_t* buffer, uint32_t nbytes, + uint8_t timeout, uint32_t& nbytesRead) { + if (m_PortFD == -1) { + return TTYResponse::Errno; + } + +#ifdef _WIN32 + // Windows specific read implementation + DWORD bytesRead; + if (!ReadFile(reinterpret_cast(m_PortFD), buffer, nbytes, + &bytesRead, nullptr)) + return TTYResponse::ReadError; + + nbytesRead = bytesRead; + return TTYResponse::OK; +#else + uint32_t numBytesToRead = nbytes; + int bytesRead = 0; + TTYResponse timeoutResponse = TTYResponse::OK; + nbytesRead = 0; + + while (numBytesToRead > 0) { + if ((timeoutResponse = checkTimeout(timeout)) != TTYResponse::OK) { + return timeoutResponse; + } + + bytesRead = ::read(m_PortFD, buffer + nbytesRead, numBytesToRead); + + if (bytesRead < 0) { + return TTYResponse::ReadError; + } + + nbytesRead += bytesRead; + numBytesToRead -= bytesRead; + } + + return TTYResponse::OK; +#endif +} + +TTYBase::TTYResponse TTYBase::readSection(uint8_t* buffer, uint32_t nsize, + uint8_t stopByte, uint8_t timeout, + uint32_t& nbytesRead) { + if (m_PortFD == -1) { + return TTYResponse::Errno; + } + + nbytesRead = 0; + memset(buffer, 0, nsize); + + while (nbytesRead < nsize) { + if (auto timeoutResponse = checkTimeout(timeout); + timeoutResponse != TTYResponse::OK) { + return timeoutResponse; + } + + uint8_t readChar; + int bytesRead = ::read(m_PortFD, &readChar, 1); + + if (bytesRead < 0) { + return TTYResponse::ReadError; + } + + buffer[nbytesRead++] = readChar; + + if (readChar == stopByte) { + return TTYResponse::OK; + } + } + + return TTYResponse::Overflow; +} + +TTYBase::TTYResponse TTYBase::connect(std::string_view device, uint32_t bitRate, + uint8_t wordSize, uint8_t parity, + uint8_t stopBits) { +#ifdef _WIN32 + // Windows specific implementation + HANDLE hSerial = + CreateFile(device.data(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hSerial == INVALID_HANDLE_VALUE) + return TTYResponse::PortFailure; + + DCB dcbSerialParams = {0}; + dcbSerialParams.DCBlength = sizeof(dcbSerialParams); + + if (!GetCommState(hSerial, &dcbSerialParams)) { + CloseHandle(hSerial); + return TTYResponse::PortFailure; + } + + dcbSerialParams.BaudRate = bitRate; + dcbSerialParams.ByteSize = wordSize; + dcbSerialParams.StopBits = (stopBits == 1) ? ONESTOPBIT : TWOSTOPBITS; + dcbSerialParams.Parity = parity; + + if (!SetCommState(hSerial, &dcbSerialParams)) { + CloseHandle(hSerial); + return TTYResponse::PortFailure; + } + + m_PortFD = reinterpret_cast(hSerial); + return TTYResponse::OK; +#elif defined(BSD) && !defined(__GNU__) + int t_fd = -1; + int bps; + int handshake; + struct termios tty_setting; + + // Open the serial port read/write, with no controlling terminal, and don't + // wait for a connection. The O_NONBLOCK flag also causes subsequent I/O on + // the device to be non-blocking. See open(2) ("man 2 open") for details. + + t_fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK); + if (t_fd == -1) { + DEBUGFDEVICE(m_DriverName, m_DebugChannel, + "Error opening serial port (%s) - %s(%d).", device, + strerror(errno), errno); + goto error; + } + + // Note that open() follows POSIX semantics: multiple open() calls to the + // same file will succeed unless the TIOCEXCL ioctl is issued. This will + // prevent additional opens except by root-owned processes. See tty(4) ("man + // 4 tty") and ioctl(2) ("man 2 ioctl") for details. + + if (ioctl(t_fd, TIOCEXCL) == -1) { + DEBUGFDEVICE(m_DriverName, m_DebugChannel, + "Error setting TIOCEXCL on %s - %s(%d).", device, + strerror(errno), errno); + goto error; + } + + // Now that the device is open, clear the O_NONBLOCK flag so subsequent I/O + // will block. See fcntl(2) ("man 2 fcntl") for details. + + if (fcntl(t_fd, F_SETFL, 0) == -1) { + DEBUGFDEVICE(m_DriverName, m_DebugChannel, + "Error clearing O_NONBLOCK %s - %s(%d).", device, + strerror(errno), errno); + goto error; + } + + // Get the current options and save them so we can restore the default + // settings later. + if (tcgetattr(t_fd, &tty_setting) == -1) { + DEBUGFDEVICE(m_DriverName, m_DebugChannel, + "Error getting tty attributes %s - %s(%d).", device, + strerror(errno), errno); + goto error; + } + + // Set raw input (non-canonical) mode, with reads blocking until either a + // single character has been received or a one second timeout expires. See + // tcsetattr(4) ("man 4 tcsetattr") and termios(4) ("man 4 termios") for + // details. + + cfmakeraw(&tty_setting); + tty_setting.c_cc[VMIN] = 1; + tty_setting.c_cc[VTIME] = 10; + + // The baud rate, word length, and handshake options can be set as follows: + switch (bit_rate) { + case 0: + bps = B0; + break; + case 50: + bps = B50; + break; + case 75: + bps = B75; + break; + case 110: + bps = B110; + break; + case 134: + bps = B134; + break; + case 150: + bps = B150; + break; + case 200: + bps = B200; + break; + case 300: + bps = B300; + break; + case 600: + bps = B600; + break; + case 1200: + bps = B1200; + break; + case 1800: + bps = B1800; + break; + case 2400: + bps = B2400; + break; + case 4800: + bps = B4800; + break; + case 9600: + bps = B9600; + break; + case 19200: + bps = B19200; + break; + case 38400: + bps = B38400; + break; + case 57600: + bps = B57600; + break; + case 115200: + bps = B115200; + break; + case 230400: + bps = B230400; + break; + default: + DEBUGFDEVICE(m_DriverName, m_DebugChannel, + "connect: %d is not a valid bit rate.", bit_rate); + return TTY_PARAM_ERROR; + } + + cfsetspeed(&tty_setting, bps); // Set baud rate + /* word size */ + switch (word_size) { + case 5: + tty_setting.c_cflag |= CS5; + break; + case 6: + tty_setting.c_cflag |= CS6; + break; + case 7: + tty_setting.c_cflag |= CS7; + break; + case 8: + tty_setting.c_cflag |= CS8; + break; + default: + DEBUGFDEVICE(m_DriverName, m_DebugChannel, + "connect: %d is not a valid data bit count.", + word_size); + return TTY_PARAM_ERROR; + } + + /* parity */ + switch (parity) { + case PARITY_NONE: + break; + case PARITY_EVEN: + tty_setting.c_cflag |= PARENB; + break; + case PARITY_ODD: + tty_setting.c_cflag |= PARENB | PARODD; + break; + default: + DEBUGFDEVICE(m_DriverName, m_DebugChannel, + "connect: %d is not a valid parity selection value.", + parity); + return TTY_PARAM_ERROR; + } + + /* stop_bits */ + switch (stop_bits) { + case 1: + break; + case 2: + tty_setting.c_cflag |= CSTOPB; + break; + default: + DEBUGFDEVICE(m_DriverName, m_DebugChannel, + "connect: %d is not a valid stop bit count.", + stop_bits); + return TTY_PARAM_ERROR; + } + +#if defined(MAC_OS_X_VERSION_10_4) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) + // Starting with Tiger, the IOSSIOSPEED ioctl can be used to set arbitrary + // baud rates other than those specified by POSIX. The driver for the + // underlying serial hardware ultimately determines which baud rates can be + // used. This ioctl sets both the input and output speed. + + speed_t speed = 14400; // Set 14400 baud + if (ioctl(t_fd, IOSSIOSPEED, &speed) == -1) { + IDLog("Error calling ioctl(..., IOSSIOSPEED, ...) - %s(%d).\n", + strerror(errno), errno); + } +#endif + + // Cause the new options to take effect immediately. + if (tcsetattr(t_fd, TCSANOW, &tty_setting) == -1) { + DEBUGFDEVICE(m_DriverName, m_DebugChannel, + "Error setting tty attributes %s - %s(%d).", device, + strerror(errno), errno); + goto error; + } + + // To set the modem handshake lines, use the following ioctls. + // See tty(4) ("man 4 tty") and ioctl(2) ("man 2 ioctl") for details. + + if (ioctl(t_fd, TIOCSDTR) == -1) // Assert Data Terminal Ready (DTR) + { + DEBUGFDEVICE(m_DriverName, m_DebugChannel, + "Error asserting DTR %s - %s(%d).", device, + strerror(errno), errno); + } + + if (ioctl(t_fd, TIOCCDTR) == -1) // Clear Data Terminal Ready (DTR) + { + DEBUGFDEVICE(m_DriverName, m_DebugChannel, + "Error clearing DTR %s - %s(%d).", device, strerror(errno), + errno); + } + + handshake = TIOCM_DTR | TIOCM_RTS | TIOCM_CTS | TIOCM_DSR; + if (ioctl(t_fd, TIOCMSET, &handshake) == -1) + // Set the modem lines depending on the bits set in handshake + { + DEBUGFDEVICE(m_DriverName, m_DebugChannel, + "Error setting handshake lines %s - %s(%d).", device, + strerror(errno), errno); + } + + // To read the state of the modem lines, use the following ioctl. + // See tty(4) ("man 4 tty") and ioctl(2) ("man 2 ioctl") for details. + + if (ioctl(t_fd, TIOCMGET, &handshake) == -1) + // Store the state of the modem lines in handshake + { + DEBUGFDEVICE(m_DriverName, m_DebugChannel, + "Error getting handshake lines %s - %s(%d).", device, + strerror(errno), errno); + } + + DEBUGFDEVICE(m_DriverName, m_DebugChannel, + "Handshake lines currently set to %d", handshake); + +#if defined(MAC_OS_X_VERSION_10_3) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_3) + unsigned long mics = 1UL; + + // Set the receive latency in microseconds. Serial drivers use this value to + // determine how often to dequeue characters received by the hardware. Most + // applications don't need to set this value: if an app reads lines of + // characters, the app can't do anything until the line termination + // character has been received anyway. The most common applications which + // are sensitive to read latency are MIDI and IrDA applications. + + if (ioctl(t_fd, IOSSDATALAT, &mics) == -1) { + // set latency to 1 microsecond + DEBUGFDEVICE(m_DriverName, m_DebugChannel, + "Error setting read latency %s - %s(%d).\n", device, + strerror(errno), errno); + goto error; + } +#endif + + m_PortFD = t_fd; + /* return success */ + return TTY_OK; + + // Failure path +error: + if (t_fd != -1) { + close(t_fd); + m_PortFD = -1; + } + + return TTY_PORT_FAILURE; +#else + int tFd = open(device.data(), O_RDWR | O_NOCTTY); + if (tFd == -1) { + LOG_F(ERROR, "Error opening {}: {}", device.data(), strerror(errno)); + m_PortFD = -1; + return TTYResponse::PortFailure; + } + + termios ttySetting{}; + if (tcgetattr(tFd, &ttySetting) == -1) { + LOG_F(ERROR, "Error getting {} tty attributes: {}", device.data(), + strerror(errno)); + return TTYResponse::PortFailure; + } + + int bps; + switch (bitRate) { + case 0: + bps = B0; + break; + case 50: + bps = B50; + break; + case 75: + bps = B75; + break; + case 110: + bps = B110; + break; + case 134: + bps = B134; + break; + case 150: + bps = B150; + break; + case 200: + bps = B200; + break; + case 300: + bps = B300; + break; + case 600: + bps = B600; + break; + case 1200: + bps = B1200; + break; + case 1800: + bps = B1800; + break; + case 2400: + bps = B2400; + break; + case 4800: + bps = B4800; + break; + case 9600: + bps = B9600; + break; + case 19200: + bps = B19200; + break; + case 38400: + bps = B38400; + break; + case 57600: + bps = B57600; + break; + case 115200: + bps = B115200; + break; + case 230400: + bps = B230400; + break; + default: + LOG_F(ERROR, "connect: {} is not a valid bit rate.", bitRate); + return TTYResponse::ParamError; + } + + // Set baud rate + if ((cfsetispeed(&ttySetting, bps) < 0) || + (cfsetospeed(&ttySetting, bps) < 0)) { + LOG_F(ERROR, "connect: failed setting bit rate."); + return TTYResponse::PortFailure; + } + + ttySetting.c_cflag &= ~(CSIZE | CSTOPB | PARENB | PARODD | HUPCL | CRTSCTS); + ttySetting.c_cflag |= (CLOCAL | CREAD); + + // Set word size + switch (wordSize) { + case 5: + ttySetting.c_cflag |= CS5; + break; + case 6: + ttySetting.c_cflag |= CS6; + break; + case 7: + ttySetting.c_cflag |= CS7; + break; + case 8: + ttySetting.c_cflag |= CS8; + break; + default: + LOG_F(ERROR, "connect: {} is not a valid data bit count.", + wordSize); + return TTYResponse::ParamError; + } + + // Set parity + if (parity == 1) { + ttySetting.c_cflag |= PARENB; + } else if (parity == 2) { + ttySetting.c_cflag |= PARENB | PARODD; + } else { + LOG_F(ERROR, "connect: {} is not a valid parity setting.", parity); + return TTYResponse::ParamError; + } + + // Set stop bits + if (stopBits == 2) { + ttySetting.c_cflag |= CSTOPB; + } else if (stopBits != 1) { + LOG_F(ERROR, "connect: {} is not a valid stop bit count.", stopBits); + return TTYResponse::ParamError; + } + + /* Ignore bytes with parity errors and make terminal raw and dumb.*/ + ttySetting.c_iflag &= + ~(PARMRK | ISTRIP | IGNCR | ICRNL | INLCR | IXOFF | IXON | IXANY); + ttySetting.c_iflag |= INPCK | IGNPAR | IGNBRK; + + /* Raw output.*/ + ttySetting.c_oflag &= ~(OPOST | ONLCR); + + /* Local Modes + Don't echo characters. Don't generate signals. + Don't process any characters.*/ + ttySetting.c_lflag &= + ~(ICANON | ECHO | ECHOE | ISIG | IEXTEN | NOFLSH | TOSTOP); + ttySetting.c_lflag |= NOFLSH; + + /* blocking read until 1 char arrives */ + ttySetting.c_cc[VMIN] = 1; + ttySetting.c_cc[VTIME] = 0; + + tcflush(tFd, TCIOFLUSH); + + // Set raw input mode (non-canonical) + cfmakeraw(&ttySetting); + + // Set the new attributes for the port + if (tcsetattr(tFd, TCSANOW, &ttySetting) != 0) { + close(tFd); + return TTYResponse::PortFailure; + } + + m_PortFD = tFd; + return TTYResponse::OK; +#endif +} + +TTYBase::TTYResponse TTYBase::disconnect() { + if (m_PortFD == -1) { + return TTYResponse::Errno; + } + +#ifdef _WIN32 + // Windows specific disconnection + if (!CloseHandle(reinterpret_cast(m_PortFD))) + return TTYResponse::Errno; + + m_PortFD = -1; + return TTYResponse::OK; +#else + if (tcflush(m_PortFD, TCIOFLUSH) != 0 || close(m_PortFD) != 0) { + return TTYResponse::Errno; + } + + m_PortFD = -1; + return TTYResponse::OK; +#endif +} + +void TTYBase::setDebug(bool enabled) { + m_Debug = enabled; + if (m_Debug) + LOG_F(INFO, "Debugging enabled."); + else + LOG_F(INFO, "Debugging disabled."); +} + +std::string TTYBase::getErrorMessage(TTYResponse code) const { + switch (code) { + case TTYResponse::OK: + return "No Error"; + case TTYResponse::ReadError: + return "Read Error: " + std::string(strerror(errno)); + case TTYResponse::WriteError: + return "Write Error: " + std::string(strerror(errno)); + case TTYResponse::SelectError: + return "Select Error: " + std::string(strerror(errno)); + case TTYResponse::Timeout: + return "Timeout Error"; + case TTYResponse::PortFailure: + if (errno == EACCES) { + return "Port failure: Access denied. Try adding your user to " + "the dialout group and restart (sudo adduser $USER " + "dialout)"; + } else { + return "Port failure: " + std::string(strerror(errno)) + + ". Check if the device is connected to this port."; + } + case TTYResponse::ParamError: + return "Parameter Error"; + case TTYResponse::Errno: + return "Error: " + std::string(strerror(errno)); + case TTYResponse::Overflow: + return "Read Overflow Error"; + default: + return "Unknown Error"; + } +} diff --git a/src/atom/connection/ttybase.hpp b/src/atom/connection/ttybase.hpp new file mode 100644 index 00000000..479632d9 --- /dev/null +++ b/src/atom/connection/ttybase.hpp @@ -0,0 +1,60 @@ +#ifndef ATOM_CONNECTION_TTYBASE_HPP +#define ATOM_CONNECTION_TTYBASE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +// Windows specific includes +#ifdef _WIN32 +#include +#undef min +#undef max +#endif + +class TTYBase { +public: + enum class TTYResponse { + OK = 0, + ReadError = -1, + WriteError = -2, + SelectError = -3, + Timeout = -4, + PortFailure = -5, + ParamError = -6, + Errno = -7, + Overflow = -8 + }; + + explicit TTYBase(std::string_view driverName) : m_DriverName(driverName) {} + virtual ~TTYBase(); + + TTYResponse read(uint8_t* buffer, uint32_t nbytes, uint8_t timeout, + uint32_t& nbytesRead); + TTYResponse readSection(uint8_t* buffer, uint32_t nsize, uint8_t stopByte, + uint8_t timeout, uint32_t& nbytesRead); + TTYResponse write(const uint8_t* buffer, uint32_t nbytes, + uint32_t& nbytesWritten); + TTYResponse writeString(std::string_view string, uint32_t& nbytesWritten); + TTYResponse connect(std::string_view device, uint32_t bitRate, + uint8_t wordSize, uint8_t parity, uint8_t stopBits); + TTYResponse disconnect(); + void setDebug(bool enabled); + std::string getErrorMessage(TTYResponse code) const; + + int getPortFD() const { return m_PortFD; } + +private: + TTYResponse checkTimeout(uint8_t timeout); + + int m_PortFD{-1}; + bool m_Debug{false}; + std::string_view m_DriverName; +}; + +#endif diff --git a/src/atom/connection/udpclient.cpp b/src/atom/connection/udpclient.cpp index d159be64..17b1f62a 100644 --- a/src/atom/connection/udpclient.cpp +++ b/src/atom/connection/udpclient.cpp @@ -21,9 +21,11 @@ Description: UDP Client Class #include #include #pragma comment(lib, "ws2_32.lib") +#include #else #include #include +#include #include #include #endif @@ -45,14 +47,22 @@ class UdpClient::Impl { if (socket_ < 0) { THROW_RUNTIME_ERROR("Socket creation failed"); } +#ifdef __linux__ + epoll_fd_ = epoll_create1(0); + if (epoll_fd_ == -1) { + THROW_RUNTIME_ERROR("Epoll creation failed"); + } +#endif } ~Impl() { + stopReceiving(); #ifdef _WIN32 closesocket(socket_); WSACleanup(); #else close(socket_); + close(epoll_fd_); #endif } @@ -98,11 +108,30 @@ class UdpClient::Impl { size_t size, std::string& remoteHost, int& remotePort, std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()) { if (timeout > std::chrono::milliseconds::zero()) { - struct timeval tv; - tv.tv_sec = timeout.count() / 1000; - tv.tv_usec = (timeout.count() % 1000) * 1000; +#ifdef _WIN32 + DWORD timeout_ms = static_cast(timeout.count()); setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, - reinterpret_cast(&tv), sizeof(tv)); + reinterpret_cast(&timeout_ms), + sizeof(timeout_ms)); +#else + struct epoll_event event; + event.events = EPOLLIN; + event.data.fd = socket_; + if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, socket_, &event) == -1) { + errorMessage_ = "Epoll control failed"; + return {}; + } + + struct epoll_event events[1]; + int nfds = epoll_wait(epoll_fd_, events, 1, timeout.count()); + if (nfds == 0) { + errorMessage_ = "Receive timeout"; + return {}; + } else if (nfds == -1) { + errorMessage_ = "Epoll wait failed"; + return {}; + } +#endif } std::vector data(size); @@ -135,7 +164,7 @@ class UdpClient::Impl { void startReceiving(size_t bufferSize) { stopReceiving(); - receivingThread_ = std::thread(&Impl::receivingLoop, this, bufferSize); + receivingThread_ = std::jthread(&Impl::receivingLoop, this, bufferSize); } void stopReceiving() { @@ -163,14 +192,15 @@ class UdpClient::Impl { SOCKET socket_; #else int socket_; + int epoll_fd_; #endif std::string errorMessage_; OnDataReceivedCallback onDataReceivedCallback_; OnErrorCallback onErrorCallback_; - std::thread receivingThread_; - bool receivingStopped_ = false; + std::jthread receivingThread_; + std::atomic receivingStopped_ = false; }; UdpClient::UdpClient() : impl_(std::make_unique()) {} diff --git a/src/atom/error/CMakeLists.txt b/src/atom/error/CMakeLists.txt index 9f40952f..f42880e5 100644 --- a/src/atom/error/CMakeLists.txt +++ b/src/atom/error/CMakeLists.txt @@ -24,6 +24,7 @@ list(APPEND ${PROJECT_NAME}_HEADERS list(APPEND ${PROJECT_NAME}_LIBS loguru + atom-utils ) if (LINUX) diff --git a/src/atom/error/_pybind.cpp b/src/atom/error/_pybind.cpp deleted file mode 100644 index f574a5f7..00000000 --- a/src/atom/error/_pybind.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include - -#include "error_code.hpp" -#include "error_stack.hpp" -#include "exception.hpp" - -namespace py = pybind11; - -using namespace atom::error; - -PYBIND11_MODULE(atom_error, m) { - py::enum_(m, "LIError") - .value("None", LIError::None) - .value("NotFound", LIError::NotFound) - .value("OpenError", LIError::OpenError) - .value("AccessDenied", LIError::AccessDenied) - .value("ReadError", LIError::ReadError) - .value("WriteError", LIError::WriteError) - .value("PermissionDenied", LIError::PermissionDenied) - .value("ParseError", LIError::ParseError) - .value("InvalidPath", LIError::InvalidPath) - .value("FileExists", LIError::FileExists) - .value("DirectoryNotEmpty", LIError::DirectoryNotEmpty) - .value("TooManyOpenFiles", LIError::TooManyOpenFiles) - .value("DiskFull", LIError::DiskFull) - .value("LoadError", LIError::LoadError) - .value("UnLoadError", LIError::UnLoadError); - - py::enum_(m, "DeviceError") - .value("None", DeviceError::None) - .value("NotSpecific", DeviceError::NotSpecific) - .value("NotFound", DeviceError::NotFound) - .value("NotSupported", DeviceError::NotSupported) - .value("NotConnected", DeviceError::NotConnected) - .value("MissingValue", DeviceError::MissingValue) - .value("InvalidValue", DeviceError::InvalidValue) - .value("Busy", DeviceError::Busy) - .value("ExposureError", DeviceError::ExposureError) - .value("GainError", DeviceError::GainError) - .value("OffsetError", DeviceError::OffsetError) - .value("ISOError", DeviceError::ISOError) - .value("CoolingError", DeviceError::CoolingError) - .value("GotoError", DeviceError::GotoError) - .value("ParkError", DeviceError::ParkError) - .value("UnParkError", DeviceError::UnParkError) - .value("ParkedError", DeviceError::ParkedError) - .value("HomeError", DeviceError::HomeError); - - py::enum_(m, "DeviceWarning") - .value("ExposureWarning", DeviceWarning::ExposureWarning) - .value("GainWarning", DeviceWarning::GainWarning) - .value("OffsetWarning", DeviceWarning::OffsetWarning) - .value("ISOWarning", DeviceWarning::ISOWarning) - .value("CoolingWarning", DeviceWarning::CoolingWarning) - .value("GotoWarning", DeviceWarning::GotoWarning) - .value("ParkWarning", DeviceWarning::ParkWarning) - .value("UnParkWarning", DeviceWarning::UnParkWarning) - .value("ParkedWarning", DeviceWarning::ParkedWarning) - .value("HomeWarning", DeviceWarning::HomeWarning); - - py::enum_(m, "ServerError") - .value("None", ServerError::None) - .value("InvalidParameters", ServerError::InvalidParameters) - .value("InvalidFormat", ServerError::InvalidFormat) - .value("MissingParameters", ServerError::MissingParameters) - .value("RunFailed", ServerError::RunFailed) - .value("UnknownError", ServerError::UnknownError) - .value("UnknownCommand", ServerError::UnknownCommand) - .value("UnknownDevice", ServerError::UnknownDevice) - .value("UnknownDeviceType", ServerError::UnknownDeviceType) - .value("UnknownDeviceName", ServerError::UnknownDeviceName) - .value("UnknownDeviceID", ServerError::UnknownDeviceID); - - py::class_(m, "ErrorInfo") - .def(py::init<>()) - .def_readwrite("errorMessage", &ErrorInfo::errorMessage) - .def_readwrite("moduleName", &ErrorInfo::moduleName) - .def_readwrite("functionName", &ErrorInfo::functionName) - .def_readwrite("line", &ErrorInfo::line) - .def_readwrite("fileName", &ErrorInfo::fileName) - .def_readwrite("timestamp", &ErrorInfo::timestamp) - .def_readwrite("uuid", &ErrorInfo::uuid); - - py::class_(m, "ErrorStack") - .def(py::init<>()) - .def("createShared", &ErrorStack::createShared) - .def("createUnique", &ErrorStack::createUnique) - .def("insertError", &ErrorStack::insertError) - .def("setFilteredModules", &ErrorStack::setFilteredModules) - .def("clearFilteredModules", &ErrorStack::clearFilteredModules) - .def("printFilteredErrorStack", &ErrorStack::printFilteredErrorStack) - .def("getFilteredErrorsByModule", - &ErrorStack::getFilteredErrorsByModule) - .def("getCompressedErrors", &ErrorStack::getCompressedErrors); -} diff --git a/src/atom/error/error_code.hpp b/src/atom/error/error_code.hpp index 8f8751a0..ba3190d4 100644 --- a/src/atom/error/error_code.hpp +++ b/src/atom/error/error_code.hpp @@ -15,79 +15,85 @@ Description: All of the error code #ifndef ATOM_ERROR_CODE_HPP #define ATOM_ERROR_CODE_HPP -enum class LIError { - None, // 无错误 - NotFound, // 文件未找到 - OpenError, // 无法打开 - AccessDenied, // 访问被拒绝 - ReadError, // 读取错误 - WriteError, // 写入错误 - PermissionDenied, // 权限被拒绝 - ParseError, // 解析错误,通常由json.hpp抛出异常然后捕获 - InvalidPath, // 无效路径 - FileExists, // 文件已存在 - DirectoryNotEmpty, // 目录非空 - TooManyOpenFiles, // 打开的文件过多 - DiskFull, // 磁盘已满 - LoadError, // 动态库加载错误 - UnLoadError // 动态卸载错误 +// 基础错误码(可选) +enum class ErrorCodeBase { + Success = 0, // 成功 + Failed = 1, // 失败 + Cancelled = 2, // 操作被取消 }; -enum class DeviceError { - None, - NotSpecific, - NotFound, - NotSupported, - NotConnected, - MissingValue, - InvalidValue, - Busy, - - // For Camera - ExposureError, - GainError, - OffsetError, - ISOError, - CoolingError, - - // For Telescope - GotoError, - ParkError, - UnParkError, - ParkedError, - HomeError +// 文件操作错误 +enum class FileError : int { + None = static_cast(ErrorCodeBase::Success), + NotFound = 100, // 文件未找到 + OpenError = 101, // 无法打开 + AccessDenied = 102, // 访问被拒绝 + ReadError = 103, // 读取错误 + WriteError = 104, // 写入错误 + PermissionDenied = 105, // 权限被拒绝 + ParseError = 106, // 解析错误 + InvalidPath = 107, // 无效路径 + FileExists = 108, // 文件已存在 + DirectoryNotEmpty = 109, // 目录非空 + TooManyOpenFiles = 110, // 打开的文件过多 + DiskFull = 111, // 磁盘已满 + LoadError = 112, // 动态库加载错误 + UnLoadError = 113, // 动态卸载错误 + LockError = 114, // 文件锁错误 + FormatError = 115, // 文件格式错误 }; -enum class DeviceWarning { - // For Camera - ExposureWarning, - GainWarning, - OffsetWarning, - ISOWarning, - CoolingWarning, - - // For Telescope - GotoWarning, - ParkWarning, - UnParkWarning, - ParkedWarning, - HomeWarning +// 设备错误 +enum class DeviceError : int { + None = static_cast(ErrorCodeBase::Success), + NotSpecific = 200, + NotFound = 201, // 设备未找到 + NotSupported = 202, // 不支持的设备 + NotConnected = 203, // 设备未连接 + MissingValue = 204, // 缺少必要的值 + InvalidValue = 205, // 无效的值 + Busy = 206, // 设备忙 + + // 相机特有错误 + ExposureError = 210, + GainError = 211, + OffsetError = 212, + ISOError = 213, + CoolingError = 214, + + // 望远镜特有错误 + GotoError = 220, + ParkError = 221, + UnParkError = 222, + ParkedError = 223, + HomeError = 224, + + InitializationError = 230, // 初始化错误 + ResourceExhausted = 231, // 资源耗尽 }; -enum class ServerError { - None, - InvalidParameters, - InvalidFormat, - MissingParameters, +// 设备警告 +// (保持现有结构,根据需要添加更多警告类型) + +// 服务器错误 +enum class ServerError : int { + None = static_cast(ErrorCodeBase::Success), + InvalidParameters = 300, + InvalidFormat = 301, + MissingParameters = 302, + + RunFailed = 303, - RunFailed, + UnknownError = 310, + UnknownCommand = 311, + UnknownDevice = 312, + UnknownDeviceType = 313, + UnknownDeviceName = 314, + UnknownDeviceID = 315, - UnknownError, - UnknownCommand, - UnknownDevice, - UnknownDeviceType, - UnknownDeviceName, - UnknownDeviceID + NetworkError = 320, // 网络错误 + TimeoutError = 321, // 请求超时 + AuthenticationError = 322, // 认证失败 }; -#endif +#endif // ATOM_ERROR_CODE_HPP diff --git a/src/atom/error/error_stack.cpp b/src/atom/error/error_stack.cpp index d24755a3..3f1acd47 100644 --- a/src/atom/error/error_stack.cpp +++ b/src/atom/error/error_stack.cpp @@ -14,6 +14,7 @@ Description: Error Stack #include "error_stack.hpp" +#include #include #include @@ -22,43 +23,36 @@ Description: Error Stack namespace atom::error { std::ostream &operator<<(std::ostream &os, const ErrorInfo &error) { - os << "{" - << "\"errorMessage\": \"" << error.errorMessage << "\"," - << "\"moduleName\": \"" << error.moduleName << "\"," - << "\"functionName\": \"" << error.functionName << "\"," - << "\"line\": " << error.line << "," - << "\"fileName\": \"" << error.fileName << "\"," - << "\"timestamp\": \"" + os << "{" << R"("errorMessage": ")" << error.errorMessage << "\"," + << R"("moduleName": ")" << error.moduleName << "\"," + << R"("functionName": ")" << error.functionName << "\"," + << "\"line\": " << error.line << "," << R"("fileName": ")" + << error.fileName << "\"," << "\"timestamp\": \"" << atom::utils::timeStampToString(error.timestamp) << "\"," - << "\"uuid\": \"" << error.uuid << "\"" - << "}"; + << "\"uuid\": \"" << error.uuid << "\"" << "}"; return os; } -std::string operator<<(const std::string &str, const ErrorInfo &error) { +std::string operator<<([[maybe_unused]] const std::string &str, + const ErrorInfo &error) { std::stringstream ss; - ss << "{" - << "\"errorMessage\": \"" << error.errorMessage << "\"," - << "\"moduleName\": \"" << error.moduleName << "\"," - << "\"functionName\": \"" << error.functionName << "\"," - << "\"line\": " << error.line << "," - << "\"fileName\": \"" << error.fileName << "\"," - << "\"timestamp\": \"" + ss << "{" << R"("errorMessage": ")" << error.errorMessage << "\"," + << R"("moduleName": ")" << error.moduleName << "\"," + << R"("functionName": ")" << error.functionName << "\"," + << "\"line\": " << error.line << "," << R"("fileName": ")" + << error.fileName << "\"," << R"("timestamp": ")" << atom::utils::timeStampToString(error.timestamp) << "\"," - << "\"uuid\": \"" << error.uuid << "\"" - << "}"; + << R"("uuid": ")" << error.uuid << "\"" << "}"; return ss.str(); } -ErrorStack::ErrorStack() {} - -std::shared_ptr ErrorStack::createShared() { +auto ErrorStack::createShared() -> std::shared_ptr { return std::make_shared(); } -std::unique_ptr ErrorStack::createUnique() { +auto ErrorStack::createUnique() -> std::unique_ptr { return std::make_unique(); } @@ -69,57 +63,57 @@ void ErrorStack::insertError(const std::string &errorMessage, auto currentTime = std::time(nullptr); auto iter = - std::find_if(errorStack.begin(), errorStack.end(), + std::find_if(errorStack_.begin(), errorStack_.end(), [&errorMessage, &moduleName](const ErrorInfo &error) { return error.errorMessage == errorMessage && error.moduleName == moduleName; }); - if (iter != errorStack.end()) { + if (iter != errorStack_.end()) { iter->timestamp = currentTime; } else { - errorStack.emplace_back(ErrorInfo{errorMessage, moduleName, - functionName, line, fileName, - currentTime, ""}); + errorStack_.emplace_back(ErrorInfo{errorMessage, moduleName, + functionName, line, fileName, + currentTime, ""}); } updateCompressedErrors(); } void ErrorStack::setFilteredModules(const std::vector &modules) { - filteredModules = modules; + filteredModules_ = modules; } -void ErrorStack::clearFilteredModules() { filteredModules.clear(); } +void ErrorStack::clearFilteredModules() { filteredModules_.clear(); } void ErrorStack::printFilteredErrorStack() const { - for (const auto &error : errorStack) { - if (std::find(filteredModules.begin(), filteredModules.end(), - error.moduleName) == filteredModules.end()) { + for (const auto &error : errorStack_) { + if (std::find(filteredModules_.begin(), filteredModules_.end(), + error.moduleName) == filteredModules_.end()) { LOG_F(ERROR, "{}", error.errorMessage); } } } -std::vector ErrorStack::getFilteredErrorsByModule( - const std::string &moduleName) const { +auto ErrorStack::getFilteredErrorsByModule(const std::string &moduleName) const + -> std::vector { std::vector errors; std::copy_if( - errorStack.begin(), errorStack.end(), std::back_inserter(errors), + errorStack_.begin(), errorStack_.end(), std::back_inserter(errors), [&moduleName, this](const ErrorInfo &error) { return error.moduleName == moduleName && - std::find(filteredModules.begin(), filteredModules.end(), - error.moduleName) == filteredModules.end(); + std::find(filteredModules_.begin(), filteredModules_.end(), + error.moduleName) == filteredModules_.end(); }); return errors; } -std::string ErrorStack::getCompressedErrors() const { +auto ErrorStack::getCompressedErrors() const -> std::string { std::stringstream compressedErrors; - for (const auto &error : compressedErrorStack) { + for (const auto &error : compressedErrorStack_) { compressedErrors << error.errorMessage << " "; } @@ -127,20 +121,20 @@ std::string ErrorStack::getCompressedErrors() const { } void ErrorStack::updateCompressedErrors() { - compressedErrorStack.clear(); + compressedErrorStack_.clear(); - for (const auto &error : errorStack) { + for (const auto &error : errorStack_) { auto iter = std::find_if( - compressedErrorStack.begin(), compressedErrorStack.end(), + compressedErrorStack_.begin(), compressedErrorStack_.end(), [&error](const ErrorInfo &compressedError) { return compressedError.errorMessage == error.errorMessage && compressedError.moduleName == error.moduleName; }); - if (iter != compressedErrorStack.end()) { + if (iter != compressedErrorStack_.end()) { iter->timestamp = error.timestamp; } else { - compressedErrorStack.push_back(error); + compressedErrorStack_.push_back(error); } } @@ -148,7 +142,7 @@ void ErrorStack::updateCompressedErrors() { } void ErrorStack::sortCompressedErrorStack() { - std::sort(compressedErrorStack.begin(), compressedErrorStack.end(), + std::sort(compressedErrorStack_.begin(), compressedErrorStack_.end(), [](const ErrorInfo &error1, const ErrorInfo &error2) { return error1.timestamp > error2.timestamp; }); diff --git a/src/atom/error/error_stack.hpp b/src/atom/error/error_stack.hpp index 1fb39578..280986b7 100644 --- a/src/atom/error/error_stack.hpp +++ b/src/atom/error/error_stack.hpp @@ -15,12 +15,11 @@ Description: Error Stack #ifndef ATOM_ERROR_STACK_HPP #define ATOM_ERROR_STACK_HPP -#include #include #include #include -#include "atom//type/string.hpp" +#include "atom/macro.hpp" namespace atom::error { /** @@ -34,7 +33,7 @@ struct ErrorInfo { std::string fileName; /**< File name where the error occurred. */ time_t timestamp; /**< Timestamp of the error. */ std::string uuid; /**< UUID of the error. */ -}; +} ATOM_ALIGNAS(128); /** * @brief Overloaded stream insertion operator to print ErrorInfo object. @@ -42,7 +41,7 @@ struct ErrorInfo { * @param error ErrorInfo object to be printed. * @return Reference to the output stream. */ -std::ostream &operator<<(std::ostream &os, const ErrorInfo &error); +auto operator<<(std::ostream &os, const ErrorInfo &error) -> std::ostream &; /** * @brief Overloaded string concatenation operator to concatenate ErrorInfo @@ -51,29 +50,28 @@ std::ostream &operator<<(std::ostream &os, const ErrorInfo &error); * @param error ErrorInfo object to be concatenated. * @return Concatenated string. */ -std::string operator<<(const std::string &str, const ErrorInfo &error); +auto operator<<(const std::string &str, const ErrorInfo &error) -> std::string; /// Represents a stack of errors and provides operations to manage and retrieve /// them. class ErrorStack { -private: - std::vector errorStack; ///< The stack of all errors. + std::vector errorStack_; ///< The stack of all errors. std::vector - compressedErrorStack; ///< The compressed stack of unique errors. - std::vector - filteredModules; ///< Modules to be filtered out while printing errors. + compressedErrorStack_; ///< The compressed stack of unique errors. + std::vector filteredModules_; ///< Modules to be filtered out + ///< while printing errors. public: /// Default constructor. - ErrorStack(); + ErrorStack() = default; /// Create a shared pointer to an ErrorStack object. /// \return A shared pointer to the ErrorStack object. - [[nodiscard]] std::shared_ptr createShared(); + [[nodiscard]] static auto createShared() -> std::shared_ptr; /// Create a unique pointer to an ErrorStack object. /// \return A unique pointer to the ErrorStack object. - [[nodiscard]] std::unique_ptr createUnique(); + [[nodiscard]] static auto createUnique() -> std::unique_ptr; /// Insert a new error into the error stack. /// \param errorMessage The error message. @@ -99,12 +97,12 @@ class ErrorStack { /// Get a vector of errors filtered by a specific module. /// \param moduleName The module name for which errors are to be retrieved. /// \return A vector of errors filtered by the given module. - [[nodiscard]] std::vector getFilteredErrorsByModule( - const std::string &moduleName) const; + [[nodiscard]] auto getFilteredErrorsByModule( + const std::string &moduleName) const -> std::vector; /// Get a string containing the compressed errors in the stack. /// \return A string containing the compressed errors. - [[nodiscard]] std::string getCompressedErrors() const; + [[nodiscard]] auto getCompressedErrors() const -> std::string; private: /// Update the compressed error stack based on the current error stack. diff --git a/src/atom/error/exception.cpp b/src/atom/error/exception.cpp index 046b49e2..c407504a 100644 --- a/src/atom/error/exception.cpp +++ b/src/atom/error/exception.cpp @@ -13,42 +13,40 @@ Description: Better Exception Library **************************************************/ #include "exception.hpp" +#include "atom/utils/time.hpp" -#include #include #include #include #include -#include -#include + +#if ENABLE_CPPTRACE +#include +#endif namespace atom::error { -const char* Exception::what() const noexcept { +auto Exception::what() const noexcept -> const char* { if (full_message_.empty()) { std::ostringstream oss; - oss << "[" << getCurrentTime() << "] "; + oss << "[" << utils::getChinaTimestampString() << "] "; oss << "Exception at " << file_ << ":" << line_ << " in " << func_ << "()"; oss << " (thread " << thread_id_ << ")"; oss << "\n\tMessage: " << message_; +#if ENABLE_CPPTRACE + oss << "\n\tStack trace:\n" + << cpptrace::generate() +#else oss << "\n\tStack trace:\n" << stack_trace_.toString(); - full_message_ = oss.str(); +#endif + full_message_ = oss.str(); } return full_message_.c_str(); } -const std::string& Exception::getFile() const { return file_; } -int Exception::getLine() const { return line_; } -const std::string& Exception::getFunction() const { return func_; } -const std::string& Exception::getMessage() const { return message_; } -std::thread::id Exception::getThreadId() const { return thread_id_; } - -std::string Exception::getCurrentTime() const { - auto now = std::chrono::system_clock::now(); - auto time = std::chrono::system_clock::to_time_t(now); - char buffer[80]; - std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", - std::localtime(&time)); - return buffer; -} +auto Exception::getFile() const -> std::string { return file_; } +auto Exception::getLine() const -> int { return line_; } +auto Exception::getFunction() const -> std::string { return func_; } +auto Exception::getMessage() const -> std::string { return message_; } +auto Exception::getThreadId() const -> std::thread::id { return thread_id_; } } // namespace atom::error diff --git a/src/atom/error/exception.hpp b/src/atom/error/exception.hpp index 1d959317..2a795198 100644 --- a/src/atom/error/exception.hpp +++ b/src/atom/error/exception.hpp @@ -16,12 +16,11 @@ Description: Better Exception Library #define ATOM_ERROR_EXCEPTION_HPP #include -#include #include -#include #include #include +#include "macro.hpp" #include "stacktrace.hpp" namespace atom::error { @@ -40,10 +39,7 @@ class Exception : public std::exception { */ template Exception(const char *file, int line, const char *func, Args &&...args) - : file_(file), - line_(line), - func_(func), - thread_id_(std::this_thread::get_id()) { + : file_(file), line_(line), func_(func) { std::ostringstream oss; ((oss << std::forward(args)), ...); message_ = oss.str(); @@ -53,58 +49,54 @@ class Exception : public std::exception { * @brief Returns a C-style string describing the exception. * @return A pointer to a string describing the exception. */ - const char *what() const noexcept override; + auto what() const ATOM_NOEXCEPT -> const char * override; /** * @brief Gets the file where the exception occurred. * @return The file where the exception occurred. */ - const std::string &getFile() const; + auto getFile() const -> std::string; /** * @brief Gets the line number where the exception occurred. * @return The line number where the exception occurred. */ - int getLine() const; + auto getLine() const -> int; /** * @brief Gets the function where the exception occurred. * @return The function where the exception occurred. */ - const std::string &getFunction() const; + auto getFunction() const -> std::string; /** * @brief Gets the message associated with the exception. * @return The message associated with the exception. */ - const std::string &getMessage() const; + auto getMessage() const -> std::string; /** * @brief Gets the ID of the thread where the exception occurred. * @return The ID of the thread where the exception occurred. */ - std::thread::id getThreadId() const; + auto getThreadId() const -> std::thread::id; private: - /** - * @brief Gets the current time as a formatted string. - * @return The current time as a formatted string. - */ - std::string getCurrentTime() const; - std::string file_; /**< The file where the exception occurred. */ int line_; /**< The line number in the file where the exception occurred. */ std::string func_; /**< The function where the exception occurred. */ std::string message_; /**< The message associated with the exception. */ mutable std::string full_message_; /**< The full message including additional context. */ - std::thread::id - thread_id_; /**< The ID of the thread where the exception occurred. */ + std::thread::id thread_id_ = + std::this_thread::get_id(); /**< The ID of the thread where the + exception occurred. */ StackTrace stack_trace_; }; -#define THROW_EXCEPTION(...) \ - throw atom::error::Exception(__FILE__, __LINE__, __func__, __VA_ARGS__) +#define THROW_EXCEPTION(...) \ + throw atom::error::Exception(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) // Special Exception @@ -117,23 +109,55 @@ class RuntimeError : public Exception { using Exception::Exception; }; -#define THROW_RUNTIME_ERROR(...) \ - throw atom::error::RuntimeError(__FILE__, __LINE__, __func__, __VA_ARGS__) +#define THROW_RUNTIME_ERROR(...) \ + throw atom::error::RuntimeError(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) class UnlawfulOperation : public Exception { public: using Exception::Exception; }; -#define THROW_UNLAWFUL_OPERATION(...) \ - throw atom::error::UnlawfulOperation(__FILE__, __LINE__, __func__, \ - __VA_ARGS__) +#define THROW_UNLAWFUL_OPERATION(...) \ + throw atom::error::UnlawfulOperation(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) + +class OutOfRange : public Exception { +public: + using Exception::Exception; +}; + +#define THROW_OUT_OF_RANGE(...) \ + throw atom::error::OutOfRange(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__); + +class OverflowException : public Exception { +public: + using Exception::Exception; +}; + +#define THROW_OVERFLOW(...) \ + throw atom::error::OverflowException(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__); + +class UnderflowException : public Exception { +public: + using Exception::Exception; +}; + +#define THROW_UNDERFLOW(...) \ + throw atom::error::UnderflowException(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__); class Unkown : public Exception { public: using Exception::Exception; }; +#define THROW_UNKOWN(...) \ + throw atom::error::Unkown(ATOM_FILE_NAME, ATOM_FILE_LINE, ATOM_FUNC_NAME, \ + __VA_ARGS__); + // ------------------------------------------------------------------- // Object // ------------------------------------------------------------------- @@ -143,92 +167,94 @@ class ObjectAlreadyExist : public Exception { using Exception::Exception; }; -#define THROW_OBJ_ALREADY_EXIST(...) \ - throw atom::error::ObjectAlreadyExist(__FILE__, __LINE__, __func__, \ - __VA_ARGS__) +#define THROW_OBJ_ALREADY_EXIST(...) \ + throw atom::error::ObjectAlreadyExist(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) class ObjectAlreadyInitialized : public Exception { public: using Exception::Exception; }; -#define THROW_OBJ_ALREADY_INITIALIZED(...) \ - throw atom::error::ObjectAlreadyInitialized(__FILE__, __LINE__, __func__, \ - __VA_ARGS__) +#define THROW_OBJ_ALREADY_INITIALIZED(...) \ + throw atom::error::ObjectAlreadyInitialized( \ + ATOM_FILE_NAME, ATOM_FILE_LINE, ATOM_FUNC_NAME, __VA_ARGS__) class ObjectNotExist : public Exception { public: using Exception::Exception; }; -#define THROW_OBJ_NOT_EXIST(...) \ - throw atom::error::ObjectNotExist(__FILE__, __LINE__, __func__, __VA_ARGS__) +#define THROW_OBJ_NOT_EXIST(...) \ + throw atom::error::ObjectNotExist(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) class ObjectUninitialized : public Exception { public: using Exception::Exception; }; -#define THROW_UNKOWN(...) \ - throw atom::error::Unkown(__FILE__, __LINE__, __func__, __VA_ARGS__) - class SystemCollapse : public Exception { public: using Exception::Exception; }; -#define THROW_SYSTEM_COLLAPSE(...) \ - throw atom::error::SystemCollapse(__FILE__, __LINE__, __func__, __VA_ARGS__) +#define THROW_SYSTEM_COLLAPSE(...) \ + throw atom::error::SystemCollapse(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) class NullPointer : public Exception { public: using Exception::Exception; }; -#define THROW_NULL_POINTER(...) \ - throw atom::error::NullPointer(__FILE__, __LINE__, __func__, __VA_ARGS__) +#define THROW_NULL_POINTER(...) \ + throw atom::error::NullPointer(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) class NotFound : public Exception { public: using Exception::Exception; }; -#define THROW_NOT_FOUND(...) \ - throw atom::error::NotFound(__FILE__, __LINE__, __func__, __VA_ARGS__) +#define THROW_NOT_FOUND(...) \ + throw atom::error::NotFound(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) // ------------------------------------------------------------------- // Argument // ------------------------------------------------------------------- -#define THROW_OBJ_UNINITIALIZED(...) \ - throw atom::error::ObjectUninitialized(__FILE__, __LINE__, __func__, \ - __VA_ARGS__) +#define THROW_OBJ_UNINITIALIZED(...) \ + throw atom::error::ObjectUninitialized(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) class WrongArgument : public Exception { public: using Exception::Exception; }; -#define THROW_WRONG_ARGUMENT(...) \ - throw atom::error::WrongArgument(__FILE__, __LINE__, __func__, __VA_ARGS__) +#define THROW_WRONG_ARGUMENT(...) \ + throw atom::error::WrongArgument(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) class InvalidArgument : public Exception { public: using Exception::Exception; }; -#define THROW_INVALID_ARGUMENT(...) \ - throw atom::error::InvalidArgument(__FILE__, __LINE__, __func__, \ - __VA_ARGS__) +#define THROW_INVALID_ARGUMENT(...) \ + throw atom::error::InvalidArgument(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) class MissingArgument : public Exception { public: using Exception::Exception; }; -#define THROW_MISSING_ARGUMENT(...) \ - throw atom::error::MissingArgument(__FILE__, __LINE__, __func__, \ - __VA_ARGS__) +#define THROW_MISSING_ARGUMENT(...) \ + throw atom::error::MissingArgument(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) // ------------------------------------------------------------------- // File @@ -239,43 +265,45 @@ class FileNotFound : public Exception { using Exception::Exception; }; -#define THROW_FILE_NOT_FOUND(...) \ - throw atom::error::FileNotFound(__FILE__, __LINE__, __func__, __VA_ARGS__) +#define THROW_FILE_NOT_FOUND(...) \ + throw atom::error::FileNotFound(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) class FileNotReadable : public Exception { public: using Exception::Exception; }; -#define THROW_FILE_NOT_READABLE(...) \ - throw atom::error::FileNotReadable(__FILE__, __LINE__, __func__, \ - __VA_ARGS__) +#define THROW_FILE_NOT_READABLE(...) \ + throw atom::error::FileNotReadable(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) class FileNotWritable : public Exception { public: using Exception::Exception; }; -#define THROW_FILE_NOT_WRITABLE(...) \ - throw atom::error::FileNotWritable(__FILE__, __LINE__, __func__, \ - __VA_ARGS__) +#define THROW_FILE_NOT_WRITABLE(...) \ + throw atom::error::FileNotWritable(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) class FailToOpenFile : public Exception { public: using Exception::Exception; }; -#define THROW_FAIL_TO_OPEN_FILE(...) \ - throw atom::error::FailToOpenFile(__FILE__, __LINE__, __func__, __VA_ARGS__) +#define THROW_FAIL_TO_OPEN_FILE(...) \ + throw atom::error::FailToOpenFile(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) class FailToCloseFile : public Exception { public: using Exception::Exception; }; -#define THROW_FAIL_TO_CLOSE_FILE(...) \ - throw atom::error::FailToCloseFile(__FILE__, __LINE__, __func__, \ - __VA_ARGS__) +#define THROW_FAIL_TO_CLOSE_FILE(...) \ + throw atom::error::FailToCloseFile(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) class FailToLoadDll : public Exception { public: @@ -286,26 +314,93 @@ class FailToLoadDll : public Exception { // Dynamic Library // ------------------------------------------------------------------- -#define THROW_FAIL_TO_LOAD_DLL(...) \ - throw atom::error::FailToLoadDll(__FILE__, __LINE__, __func__, __VA_ARGS__) +#define THROW_FAIL_TO_LOAD_DLL(...) \ + throw atom::error::FailToLoadDll(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) class FailToUnloadDll : public Exception { public: using Exception::Exception; }; -#define THROW_FAIL_TO_UNLOAD_DLL(...) \ - throw atom::error::FailToUnloadDll(__FILE__, __LINE__, __func__, \ - __VA_ARGS__) +#define THROW_FAIL_TO_UNLOAD_DLL(...) \ + throw atom::error::FailToUnloadDll(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) class FailToLoadSymbol : public Exception { public: using Exception::Exception; }; -#define THROW_FAIL_TO_LOAD_SYMBOL(...) \ - throw atom::error::FailToLoadSymbol(__FILE__, __LINE__, __func__, \ - __VA_ARGS__) +#define THROW_FAIL_TO_LOAD_SYMBOL(...) \ + throw atom::error::FailToLoadSymbol(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) + +// ------------------------------------------------------------------- +// Proccess Library +// ------------------------------------------------------------------- + +class FailToCreateProcess : public Exception { +public: + using Exception::Exception; +}; + +#define THROW_FAIL_TO_CREATE_PROCESS(...) \ + throw atom::error::FailToCreateProcess(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) + +class FailToTerminateProcess : public Exception { +public: + using Exception::Exception; +}; + +#define THROW_FAIL_TO_TERMINATE_PROCESS(...) \ + throw atom::error::FailToTerminateProcess(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) + +// ------------------------------------------------------------------- +// JSON Error +// ------------------------------------------------------------------- + +class JsonParseError : public Exception { +public: + using Exception::Exception; +}; + +#define THROW_JSON_PARSE_ERROR(...) \ + throw atom::error::JsonParseError(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) + +class JsonValueError : public Exception { +public: + using Exception::Exception; +}; + +#define THROW_JSON_VALUE_ERROR(...) \ + throw atom::error::JsonValueError(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) + +// ------------------------------------------------------------------- +// Network Error +// ------------------------------------------------------------------- + +class CurlInitializationError : public Exception { +public: + using Exception::Exception; +}; + +#define THROW_CURL_INITIALIZATION_ERROR(...) \ + throw atom::error::CurlInitializationError(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) + +class CurlRuntimeError : public Exception { +public: + using Exception::Exception; +}; + +#define THROW_CURL_RUNTIME_ERROR(...) \ + throw atom::error::CurlRuntimeError(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) } // namespace atom::error #endif diff --git a/src/atom/error/meson.build b/src/atom/error/meson.build deleted file mode 100644 index be669471..00000000 --- a/src/atom/error/meson.build +++ /dev/null @@ -1,49 +0,0 @@ -project('atom-error', 'c', 'cpp', - version: '1.0.0', - license: 'GPL3', - default_options: ['cpp_std=c++17'] -) - -# 源文件和头文件 -atom_error_sources = [ - 'error_stack.cpp', - 'exception.cpp', - 'stacktrace.cpp' -] - -atom_error_headers = [ - 'error_code.hpp', - 'error_stack.hpp', - 'stacktrace.hpp' -] - -# 依赖 -loguru_dep = dependency('loguru') -dl_dep = cc.find_library('dl', required: false) - -atom_error_deps = [loguru_dep] -if dl_dep.found() - atom_error_deps += [dl_dep] -endif - -# 对象库 -atom_error_object = static_library('atom_error_object', - sources: atom_error_sources, - dependencies: atom_error_deps, - include_directories: include_directories('.'), - install: false -) - -# 静态库 -atom_error_lib = static_library('atom-error', - sources: atom_error_object.extract_all_objects(), - dependencies: atom_error_deps, - include_directories: include_directories('.'), - install: true -) - -# 安装头文件 -install_headers(atom_error_headers, subdir: 'atom-error') - -# 设置目标属性 -atom_error_lib.set_output_name('atom-error') diff --git a/src/atom/error/stacktrace.cpp b/src/atom/error/stacktrace.cpp index 2cc42f14..25b23a14 100644 --- a/src/atom/error/stacktrace.cpp +++ b/src/atom/error/stacktrace.cpp @@ -13,15 +13,17 @@ Description: StackTrace **************************************************/ #include "stacktrace.hpp" +#include "atom/function/abi.hpp" -#include -#include #include -#include +#include +#include #ifdef _WIN32 -#include -#include +// clang-format off +#include +#include +// clang-format on #if !defined(__MINGW32__) && !defined(__MINGW64__) #pragma comment(lib, "dbghelp.lib") #endif @@ -31,81 +33,86 @@ Description: StackTrace #endif namespace atom::error { + +#if defined(linux) || defined(__APPLE__) +std::string processString(const std::string& input) { + // Find the position of "_Z" in the string + size_t startIndex = input.find("_Z"); + if (startIndex == std::string::npos) { + return input; + } + size_t endIndex = input.find('+', startIndex); + if (endIndex == std::string::npos) { + return input; + } + std::string abiName = input.substr(startIndex, endIndex - startIndex); + abiName = meta::DemangleHelper::demangle(abiName); + std::string result = input; + result.replace(startIndex, endIndex - startIndex, abiName); + return result; +} +#endif + StackTrace::StackTrace() { capture(); } -std::string StackTrace::toString() const { +auto StackTrace::toString() const -> std::string { std::ostringstream oss; + #ifdef _WIN32 - SYMBOL_INFO* symbol = - (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1); + SYMBOL_INFO* symbol = reinterpret_cast( + calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1)); symbol->MaxNameLen = 255; symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - for (void* frame : frames) { - SymFromAddr(GetCurrentProcess(), (DWORD64)frame, 0, symbol); - oss << "\t\t" << symbol->Name << " - 0x" << std::hex << symbol->Address - << "\n"; + for (void* frame : frames_) { + SymFromAddr(GetCurrentProcess(), reinterpret_cast(frame), 0, + symbol); + std::string symbol_name = symbol->Name; + if (!symbol_name.empty()) { + oss << "\t\t" + << atom::meta::DemangleHelper::demangle("_" + symbol_name) + << " - 0x" << std::hex << symbol->Address << "\n"; + } } free(symbol); -#elif defined(__APPLE__) || defined(__linux__) - for (int i = 0; i < num_frames; ++i) { - char* symbol_name = nullptr; - char* offset_begin = nullptr; - char* offset_end = nullptr; - - for (char* p = symbols.get()[i]; *p; ++p) { - if (*p == '(') - symbol_name = p; - else if (*p == '+') - offset_begin = p; - else if (*p == ')') { - offset_end = p; - break; - } - } - if (symbol_name && offset_begin && offset_end && - symbol_name < offset_begin) { - *symbol_name++ = '\0'; - *offset_begin++ = '\0'; - *offset_end = '\0'; - - int status = 0; - char* demangled_name = - abi::__cxa_demangle(symbol_name, nullptr, 0, &status); - if (status == 0) { - oss << "\t\t" << demangled_name << " +" << offset_begin - << offset_end << "\n"; - free(demangled_name); - } else { - oss << "\t\t" << symbol_name << " +" << offset_begin - << offset_end << "\n"; - } - } else { - oss << "\t\t" << symbols.get()[i] << "\n"; - } +#elif defined(__APPLE__) || defined(__linux__) + for (int i = 0; i < num_frames_; ++i) { + std::string_view symbol(symbols_.get()[i]); + auto demangledName = processString(std::string(symbol)); + oss << "\t\t" << demangledName << "\n"; } + #else oss << "\t\tStack trace not available on this platform.\n"; #endif + return oss.str(); } void StackTrace::capture() { #ifdef _WIN32 - const int max_frames = 64; - frames.resize(max_frames); - SymInitialize(GetCurrentProcess(), NULL, TRUE); + constexpr int max_frames = 64; + frames_.resize(max_frames); + SymInitialize(GetCurrentProcess(), nullptr, TRUE); + + std::array frame_ptrs; WORD captured_frames = - CaptureStackBackTrace(0, max_frames, frames.data(), NULL); - frames.resize(captured_frames); + CaptureStackBackTrace(0, max_frames, frame_ptrs.data(), nullptr); + + frames_.resize(captured_frames); + std::copy_n(frame_ptrs.begin(), captured_frames, frames_.begin()); + #elif defined(__APPLE__) || defined(__linux__) - const int max_frames = 64; - void* frame_pointers[max_frames]; - num_frames = backtrace(frame_pointers, max_frames); - symbols.reset(backtrace_symbols(frame_pointers, num_frames)); + constexpr int MAX_FRAMES = 64; + void* framePtrs[MAX_FRAMES]; + + num_frames_ = backtrace(framePtrs, MAX_FRAMES); + symbols_.reset(backtrace_symbols(framePtrs, num_frames_)); + #else - num_frames = 0; + num_frames_ = 0; #endif } + } // namespace atom::error diff --git a/src/atom/error/stacktrace.hpp b/src/atom/error/stacktrace.hpp index a0b34e23..21a83dab 100644 --- a/src/atom/error/stacktrace.hpp +++ b/src/atom/error/stacktrace.hpp @@ -15,11 +15,12 @@ Description: StackTrace #ifndef ATOM_ERROR_STACKTRACE_HPP #define ATOM_ERROR_STACKTRACE_HPP -#include #include #include +#include namespace atom::error { + /** * @brief Class for capturing and representing a stack trace. * @@ -41,7 +42,7 @@ class StackTrace { * * @return A string representing the captured stack trace. */ - std::string toString() const; + [[nodiscard]] auto toString() const -> std::string; private: /** @@ -53,14 +54,13 @@ class StackTrace { void capture(); #ifdef _WIN32 - std::vector frames; /**< Vector to store stack frames on Windows. */ + std::vector frames_; /**< Vector to store stack frames on Windows. */ #elif defined(__APPLE__) || defined(__linux__) - std::unique_ptr symbols{ - nullptr, - &free}; /**< Pointer to store stack symbols on macOS or Linux. */ - int num_frames = 0; /**< Number of stack frames captured. */ + std::unique_ptr symbols_{nullptr, &free}; /**< Pointer to store stack symbols on macOS or Linux. */ + int num_frames_ = 0; /**< Number of stack frames captured. */ #endif }; + } // namespace atom::error #endif diff --git a/src/atom/error/xmake.lua b/src/atom/error/xmake.lua new file mode 100644 index 00000000..36ae0f56 --- /dev/null +++ b/src/atom/error/xmake.lua @@ -0,0 +1,64 @@ +set_project("atom-error") +set_version("1.0.0") +set_xmakever("2.5.1") + +-- Set the C++ standard +set_languages("cxx20") + +-- Add required packages +add_requires("loguru") + +-- Define libraries +local atom_error_libs = { + "atom-utils" +} + +local project_packages = { + "loguru", + "dl" +} + +-- Source files +local source_files = { + "error_stack.cpp", + "exception.cpp", + "stacktrace.cpp" +} + +-- Header files +local header_files = { + "error_code.hpp", + "error_stack.hpp", + "stacktrace.hpp" +} + +-- Object Library +target("atom-error_object") + set_kind("object") + add_files(table.unpack(source_files)) + add_headerfiles(table.unpack(header_files)) + add_packages("loguru") + if is_plat("linux") then + add_syslinks("dl") + end +target_end() + +-- Static Library +target("atom-error") + set_kind("static") + add_deps("atom-error_object") + add_files(table.unpack(source_files)) + add_headerfiles(table.unpack(header_files)) + add_packages("loguru") + add_deps("atom-utils") + if is_plat("linux") then + add_syslinks("dl") + end + add_includedirs(".") + set_targetdir("$(buildir)/lib") + set_installdir("$(installdir)/lib") + set_version("1.0.0", {build = "%Y%m%d%H%M"}) + on_install(function (target) + os.cp(target:targetfile(), path.join(target:installdir(), "lib")) + end) +target_end() diff --git a/src/atom/function/CMakeLists.txt b/src/atom/function/CMakeLists.txt new file mode 100644 index 00000000..f9d558dc --- /dev/null +++ b/src/atom/function/CMakeLists.txt @@ -0,0 +1,46 @@ +# CMakeLists.txt for Atom-Function +# This project is licensed under the terms of the GPL3 license. +# +# Project Name: Atom-Function +# Description: a library for meta programming in C++ +# Author: Max Qian +# License: GPL3 + +cmake_minimum_required(VERSION 3.20) +project(atom-function C CXX) + +list(APPEND ${PROJECT_NAME}_SOURCES + global_ptr.cpp +) + +# Headers +list(APPEND ${PROJECT_NAME}_HEADERS + global_ptr.hpp +) + +list(APPEND ${PROJECT_NAME}_LIBS +) + +# Build Object Library +add_library(${PROJECT_NAME}_OBJECT OBJECT) +set_property(TARGET ${PROJECT_NAME}_OBJECT PROPERTY POSITION_INDEPENDENT_CODE 1) + +target_link_libraries(${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) + +target_sources(${PROJECT_NAME}_OBJECT + PUBLIC + ${${PROJECT_NAME}_HEADERS} + PRIVATE + ${${PROJECT_NAME}_SOURCES} +) + +target_link_libraries(${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) + +add_library(${PROJECT_NAME} STATIC) + +target_link_libraries(${PROJECT_NAME} ${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) +target_include_directories(${PROJECT_NAME} PUBLIC .) + +install(TARGETS ${PROJECT_NAME} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) diff --git a/src/atom/function/abi.hpp b/src/atom/function/abi.hpp index 337f0cab..e721e9f1 100644 --- a/src/atom/function/abi.hpp +++ b/src/atom/function/abi.hpp @@ -37,19 +37,19 @@ namespace atom::meta { class DemangleHelper { public: template - static std::string DemangleType() { - return DemangleInternal(typeid(T).name()); + static auto demangleType() -> std::string { + return demangleInternal(typeid(T).name()); } template - static std::string DemangleType(const T& instance) { - return DemangleInternal(typeid(instance).name()); + static auto demangleType(const T& instance) -> std::string { + return demangleInternal(typeid(instance).name()); } - static std::string Demangle( - std::string_view mangled_name, - const std::optional& location = std::nullopt) { - std::string demangled = DemangleInternal(mangled_name); + static auto demangle(std::string_view mangled_name, + const std::optional& location = + std::nullopt) -> std::string { + std::string demangled = demangleInternal(mangled_name); if (location) { demangled += " ("; @@ -62,27 +62,28 @@ class DemangleHelper { return demangled; } - static std::vector DemangleMany( + static auto demangleMany( const std::vector& mangled_names, - const std::optional& location = std::nullopt) { - std::vector demangled_names; - demangled_names.reserve(mangled_names.size()); + const std::optional& location = std::nullopt) + -> std::vector { + std::vector demangledNames; + demangledNames.reserve(mangled_names.size()); for (const auto& name : mangled_names) { - demangled_names.push_back(Demangle(name, location)); + demangledNames.push_back(demangle(name, location)); } - return demangled_names; + return demangledNames; } #if ENABLE_DEBUG - static std::string Visualize(const std::string& demangled_name) { - return VisualizeType(demangled_name); + static auto visualize(const std::string& demangled_name) -> std::string { + return visualizeType(demangled_name); } #endif private: - static std::string DemangleInternal(std::string_view mangled_name) { + static auto demangleInternal(std::string_view mangled_name) -> std::string { #ifdef _MSC_VER char buffer[1024]; DWORD length = UnDecorateSymbolName(mangled_name.data(), buffer, @@ -92,50 +93,51 @@ class DemangleHelper { : std::string(mangled_name); #else int status = -1; - std::unique_ptr demangled_name( + std::unique_ptr demangledName( abi::__cxa_demangle(mangled_name.data(), nullptr, nullptr, &status), std::free); - return (status == 0) ? std::string(demangled_name.get()) + return (status == 0) ? std::string(demangledName.get()) : std::string(mangled_name); #endif } #if ENABLE_DEBUG - static std::string VisualizeType(const std::string& type_name, - int indent_level = 0) { - std::string indent(indent_level * 4, ' '); // 4 spaces per indent level + static auto visualizeType(const std::string& type_name, + int indent_level = 0) -> std::string { + std::string indent(static_cast(indent_level) * 4, + ' '); // 4 spaces per indent level std::string result; // Regular expressions for parsing - std::regex template_regex(R"((\w+)<(.*)>)"); - std::regex function_regex(R"(\((.*)\)\s*->\s*(.*))"); - std::regex ptr_regex(R"((.+)\s*\*\s*)"); - std::regex array_regex(R"((.+)\s*\[(\d+)\])"); + std::regex templateRegex(R"((\w+)<(.*)>)"); + std::regex functionRegex(R"(\((.*)\)\s*->\s*(.*))"); + std::regex ptrRegex(R"((.+)\s*\*\s*)"); + std::regex arrayRegex(R"((.+)\s*\[(\d+)\])"); std::smatch match; - if (std::regex_match(type_name, match, template_regex)) { + if (std::regex_match(type_name, match, templateRegex)) { // Template type result += indent + "`-- " + match[1].str() + " [template]\n"; std::string params = match[2].str(); - result += VisualizeTemplateParams(params, indent_level + 1); - } else if (std::regex_match(type_name, match, function_regex)) { + result += visualizeTemplateParams(params, indent_level + 1); + } else if (std::regex_match(type_name, match, functionRegex)) { // Function type result += indent + "`-- function\n"; std::string params = match[1].str(); - std::string return_type = match[2].str(); - result += VisualizeFunctionParams(params, indent_level + 1); + std::string returnType = match[2].str(); + result += visualizeFunctionParams(params, indent_level + 1); result += indent + " `-- R: " + - VisualizeType(return_type, indent_level + 1) + visualizeType(returnType, indent_level + 1) .substr(indent.size() + 4); - } else if (std::regex_match(type_name, match, ptr_regex)) { + } else if (std::regex_match(type_name, match, ptrRegex)) { // Pointer type result += indent + "`-- ptr\n"; - result += VisualizeType(match[1].str(), indent_level + 1); - } else if (std::regex_match(type_name, match, array_regex)) { + result += visualizeType(match[1].str(), indent_level + 1); + } else if (std::regex_match(type_name, match, arrayRegex)) { // Array type result += indent + "`-- array [N = " + match[2].str() + "]\n"; - result += VisualizeType(match[1].str(), indent_level + 1); + result += visualizeType(match[1].str(), indent_level + 1); } else { // Simple type result += indent + "`-- " + type_name + "\n"; @@ -144,67 +146,67 @@ class DemangleHelper { return result; } - static std::string VisualizeTemplateParams(const std::string& params, - int indent_level) { - std::string indent(indent_level * 4, ' '); + static auto visualizeTemplateParams(const std::string& params, + int indent_level) -> std::string { + std::string indent(static_cast(indent_level) * 4, ' '); std::string result; - int param_index = 0; + int paramIndex = 0; size_t start = 0; size_t end = 0; - int angle_brackets = 0; + int angleBrackets = 0; for (size_t i = 0; i < params.size(); ++i) { if (params[i] == '<') { - ++angle_brackets; + ++angleBrackets; } else if (params[i] == '>') { - --angle_brackets; - } else if (params[i] == ',' && angle_brackets == 0) { + --angleBrackets; + } else if (params[i] == ',' && angleBrackets == 0) { end = i; - result += indent + "|-- " + std::to_string(param_index++) + + result += indent + "|-- " + std::to_string(paramIndex++) + ": " + - VisualizeType(params.substr(start, end - start), + visualizeType(params.substr(start, end - start), indent_level + 1) .substr(indent.size() + 4); start = i + 1; } } - result += indent + "|-- " + std::to_string(param_index++) + ": " + - VisualizeType(params.substr(start), indent_level + 1) + result += indent + "|-- " + std::to_string(paramIndex++) + ": " + + visualizeType(params.substr(start), indent_level + 1) .substr(indent.size() + 4); return result; } - static std::string VisualizeFunctionParams(const std::string& params, - int indent_level) { - std::string indent(indent_level * 4, ' '); + static auto visualizeFunctionParams(const std::string& params, + int indent_level) -> std::string { + std::string indent(static_cast(indent_level) * 4, ' '); std::string result; - int param_index = 0; + int paramIndex = 0; size_t start = 0; size_t end = 0; - int angle_brackets = 0; + int angleBrackets = 0; for (size_t i = 0; i < params.size(); ++i) { if (params[i] == '<') { - ++angle_brackets; + ++angleBrackets; } else if (params[i] == '>') { - --angle_brackets; - } else if (params[i] == ',' && angle_brackets == 0) { + --angleBrackets; + } else if (params[i] == ',' && angleBrackets == 0) { end = i; - result += indent + "|-- " + std::to_string(param_index++) + + result += indent + "|-- " + std::to_string(paramIndex++) + ": " + - VisualizeType(params.substr(start, end - start), + visualizeType(params.substr(start, end - start), indent_level + 1) .substr(indent.size() + 4); start = i + 1; } } - result += indent + "|-- " + std::to_string(param_index++) + ": " + - VisualizeType(params.substr(start), indent_level + 1) + result += indent + "|-- " + std::to_string(paramIndex++) + ": " + + visualizeType(params.substr(start), indent_level + 1) .substr(indent.size() + 4); return result; diff --git a/src/atom/function/any.hpp b/src/atom/function/any.hpp index 7ddc5743..5254026a 100644 --- a/src/atom/function/any.hpp +++ b/src/atom/function/any.hpp @@ -20,190 +20,191 @@ #include #include #include -#include #include #include +#include "macro.hpp" #include "type_info.hpp" namespace atom::meta { class BoxedValue { public: - struct Void_Type {}; + struct VoidType {}; private: struct Data { - std::any m_obj; - Type_Info m_type_info; - std::shared_ptr>> m_attrs; - bool m_is_ref = false; - bool m_return_value = false; - bool m_readonly = false; - const void* m_const_data_ptr = nullptr; + std::any mObj; + TypeInfo mTypeInfo; + std::shared_ptr>> mAttrs; + bool mIsRef = false; + bool mReturnValue = false; + bool mReadonly = false; + const void* mConstDataPtr = nullptr; template Data(T&& obj, bool is_ref, bool return_value, bool readonly) - : m_obj(std::forward(obj)), - m_type_info(user_type>()), - m_attrs(nullptr), - m_is_ref(is_ref), - m_return_value(return_value), - m_readonly(readonly), - m_const_data_ptr((const void*)std::addressof(obj)) {} - }; - - std::shared_ptr m_data; - mutable std::shared_mutex m_mutex; + : mObj(std::forward(obj)), + mTypeInfo(userType>()), + mAttrs(nullptr), + mIsRef(is_ref), + mReturnValue(return_value), + mReadonly(readonly), + mConstDataPtr((const void*)std::addressof(obj)) {} + } ATOM_ALIGNAS(128); + + std::shared_ptr m_data_; + mutable std::shared_mutex m_mutex_; public: template >>> explicit BoxedValue(T&& t, bool t_return_value = false, bool readonly = false) - : m_data(std::make_shared(std::forward(t), - std::is_reference_v, t_return_value, - readonly)) {} + : m_data_(std::make_shared(std::forward(t), + std::is_reference_v, t_return_value, + readonly)) {} BoxedValue() - : m_data(std::make_shared(Void_Type{}, false, false, false)) {} + : m_data_(std::make_shared(VoidType{}, false, false, false)) {} BoxedValue(const BoxedValue& other) { - std::shared_lock lock(other.m_mutex); - m_data = other.m_data; + std::shared_lock lock(other.m_mutex_); + m_data_ = other.m_data_; } BoxedValue(BoxedValue&& other) noexcept { - std::unique_lock lock(other.m_mutex); - m_data = std::move(other.m_data); + std::unique_lock lock(other.m_mutex_); + m_data_ = std::move(other.m_data_); } - BoxedValue& operator=(const BoxedValue& other) { + auto operator=(const BoxedValue& other) -> BoxedValue& { if (this != &other) { - std::unique_lock lock(m_mutex); - std::shared_lock other_lock(other.m_mutex); - m_data = other.m_data; + std::unique_lock lock(m_mutex_); + std::shared_lock otherLock(other.m_mutex_); + m_data_ = other.m_data_; } return *this; } - BoxedValue& operator=(BoxedValue&& other) noexcept { + auto operator=(BoxedValue&& other) noexcept -> BoxedValue& { if (this != &other) { - std::unique_lock lock(m_mutex); - std::unique_lock other_lock(other.m_mutex); - m_data = std::move(other.m_data); + std::unique_lock lock(m_mutex_); + std::unique_lock otherLock(other.m_mutex_); + m_data_ = std::move(other.m_data_); } return *this; } void swap(BoxedValue& rhs) noexcept { if (this != &rhs) { - std::unique_lock lock1(m_mutex, std::defer_lock); - std::unique_lock lock2(rhs.m_mutex, std::defer_lock); + std::unique_lock lock1(m_mutex_, std::defer_lock); + std::unique_lock lock2(rhs.m_mutex_, std::defer_lock); std::lock(lock1, lock2); - std::swap(m_data, rhs.m_data); + std::swap(m_data_, rhs.m_data_); } } - bool is_undef() const noexcept { - std::shared_lock lock(m_mutex); - return m_data->m_obj.type() == typeid(Void_Type); + auto isUndef() const noexcept -> bool { + std::shared_lock lock(m_mutex_); + return m_data_->mObj.type() == typeid(VoidType); } - bool is_const() const noexcept { - std::shared_lock lock(m_mutex); - return m_data->m_type_info.is_const(); + auto isConst() const noexcept -> bool { + std::shared_lock lock(m_mutex_); + return m_data_->mTypeInfo.isConst(); } - bool is_type(const Type_Info& ti) const noexcept { - std::shared_lock lock(m_mutex); - return m_data->m_type_info == ti; + auto isType(const TypeInfo& ti) const noexcept -> bool { + std::shared_lock lock(m_mutex_); + return m_data_->mTypeInfo == ti; } - bool is_ref() const noexcept { - std::shared_lock lock(m_mutex); - return m_data->m_is_ref; + auto isRef() const noexcept -> bool { + std::shared_lock lock(m_mutex_); + return m_data_->mIsRef; } - bool is_return_value() const noexcept { - std::shared_lock lock(m_mutex); - return m_data->m_return_value; + auto isReturnValue() const noexcept -> bool { + std::shared_lock lock(m_mutex_); + return m_data_->mReturnValue; } - void reset_return_value() noexcept { - std::unique_lock lock(m_mutex); - m_data->m_return_value = false; + void resetReturnValue() noexcept { + std::unique_lock lock(m_mutex_); + m_data_->mReturnValue = false; } - bool is_readonly() const noexcept { - std::shared_lock lock(m_mutex); - return m_data->m_readonly; + auto isReadonly() const noexcept -> bool { + std::shared_lock lock(m_mutex_); + return m_data_->mReadonly; } - bool is_const_data_ptr() const noexcept { - std::shared_lock lock(m_mutex); - return m_data->m_const_data_ptr != nullptr; + auto isConstDataPtr() const noexcept -> bool { + std::shared_lock lock(m_mutex_); + return m_data_->mConstDataPtr != nullptr; } - const std::any& get() const noexcept { - std::shared_lock lock(m_mutex); - return m_data->m_obj; + auto get() const noexcept -> const std::any& { + std::shared_lock lock(m_mutex_); + return m_data_->mObj; } - const Type_Info& get_type_info() const noexcept { - std::shared_lock lock(m_mutex); - return m_data->m_type_info; + auto getTypeInfo() const noexcept -> const TypeInfo& { + std::shared_lock lock(m_mutex_); + return m_data_->mTypeInfo; } - BoxedValue& set_attr(const std::string& name, const BoxedValue& value) { - std::unique_lock lock(m_mutex); - if (!m_data->m_attrs) { - m_data->m_attrs = std::make_shared< + auto setAttr(const std::string& name, + const BoxedValue& value) -> BoxedValue& { + std::unique_lock lock(m_mutex_); + if (!m_data_->mAttrs) { + m_data_->mAttrs = std::make_shared< std::map>>(); } - (*m_data->m_attrs)[name] = value.m_data; + (*m_data_->mAttrs)[name] = value.m_data_; return *this; } - BoxedValue get_attr(const std::string& name) const { - std::shared_lock lock(m_mutex); - if (m_data->m_attrs) { - auto it = m_data->m_attrs->find(name); - if (it != m_data->m_attrs->end()) { + auto getAttr(const std::string& name) const -> BoxedValue { + std::shared_lock lock(m_mutex_); + if (m_data_->mAttrs) { + auto it = m_data_->mAttrs->find(name); + if (it != m_data_->mAttrs->end()) { return BoxedValue(it->second); } } - return BoxedValue(); // Return undefined BoxedValue + return {}; // Return undefined BoxedValue } - bool has_attr(const std::string& name) const { - std::shared_lock lock(m_mutex); - return m_data->m_attrs && - (m_data->m_attrs->find(name) != m_data->m_attrs->end()); + auto hasAttr(const std::string& name) const -> bool { + std::shared_lock lock(m_mutex_); + return m_data_->mAttrs && + (m_data_->mAttrs->find(name) != m_data_->mAttrs->end()); } - void remove_attr(const std::string& name) { - std::unique_lock lock(m_mutex); - if (m_data->m_attrs) { - m_data->m_attrs->erase(name); + void removeAttr(const std::string& name) { + std::unique_lock lock(m_mutex_); + if (m_data_->mAttrs) { + m_data_->mAttrs->erase(name); } } - std::vector list_attrs() const { - std::shared_lock lock(m_mutex); + auto listAttrs() const -> std::vector { + std::shared_lock lock(m_mutex_); std::vector attrs; - if (m_data->m_attrs) { - for (const auto& entry : *m_data->m_attrs) { + if (m_data_->mAttrs) { + for (const auto& entry : *m_data_->mAttrs) { attrs.push_back(entry.first); } } return attrs; } - bool is_null() const noexcept { - std::shared_lock lock(m_mutex); - if (m_data->m_obj.has_value()) { + auto isNull() const noexcept -> bool { + std::shared_lock lock(m_mutex_); + if (m_data_->mObj.has_value()) { try { - return std::any_cast(m_data->m_obj) != nullptr; + return std::any_cast(m_data_->mObj) != nullptr; } catch (const std::bad_any_cast&) { return false; } @@ -211,41 +212,41 @@ class BoxedValue { return true; } - void* get_ptr() const noexcept { - std::shared_lock lock(m_mutex); - return const_cast(m_data->m_const_data_ptr); + auto getPtr() const noexcept -> void* { + std::shared_lock lock(m_mutex_); + return const_cast(m_data_->mConstDataPtr); } template - std::optional try_cast() const noexcept { - std::shared_lock lock(m_mutex); + auto tryCast() const noexcept -> std::optional { + std::shared_lock lock(m_mutex_); try { if constexpr (std::is_reference_v) { - if (m_data->m_obj.type() == + if (m_data_->mObj.type() == typeid( std::reference_wrapper>)) { auto& ref = std::any_cast< std::reference_wrapper>>( - m_data->m_obj); + m_data_->mObj); return ref.get(); } } - return std::any_cast(m_data->m_obj); + return std::any_cast(m_data_->mObj); } catch (const std::bad_any_cast&) { return std::nullopt; } } template - bool can_cast() const noexcept { - std::shared_lock lock(m_mutex); + auto canCast() const noexcept -> bool { + std::shared_lock lock(m_mutex_); try { if constexpr (std::is_reference_v) { - if (m_data->m_obj.type() == typeid(std::reference_wrapper)) { + if (m_data_->mObj.type() == typeid(std::reference_wrapper)) { return true; } } else { - std::any_cast(m_data->m_obj); + std::any_cast(m_data_->mObj); return true; } } catch (const std::bad_any_cast&) { @@ -255,65 +256,66 @@ class BoxedValue { } /// Debug string representation of the contained object - std::string debug_string() const { + auto debugString() const -> std::string { std::ostringstream oss; - oss << "BoxedValue<" << m_data->m_type_info.name() << ">: "; - if (m_data->m_obj.type() == typeid(int)) - oss << std::any_cast(m_data->m_obj); - else if (m_data->m_obj.type() == typeid(double)) - oss << std::any_cast(m_data->m_obj); - else if (m_data->m_obj.type() == typeid(std::string)) - oss << std::any_cast(m_data->m_obj); - else if (m_data->m_obj.type() == typeid(bool)) - oss << std::any_cast(m_data->m_obj); - else if (m_data->m_obj.type() == typeid(std::vector)) + oss << "BoxedValue<" << m_data_->mTypeInfo.name() << ">: "; + if (m_data_->mObj.type() == typeid(int)) { + oss << std::any_cast(m_data_->mObj); + } else if (m_data_->mObj.type() == typeid(double)) { + oss << std::any_cast(m_data_->mObj); + } else if (m_data_->mObj.type() == typeid(std::string)) { + oss << std::any_cast(m_data_->mObj); + } else if (m_data_->mObj.type() == typeid(bool)) { + oss << std::any_cast(m_data_->mObj); + } else if (m_data_->mObj.type() == typeid(std::vector)) { oss << "vector"; - else if (m_data->m_obj.type() == typeid(std::vector)) + } else if (m_data_->mObj.type() == typeid(std::vector)) { oss << "vector"; - else if (m_data->m_obj.type() == typeid(std::vector)) + } else if (m_data_->mObj.type() == typeid(std::vector)) { oss << "vector"; - else if (m_data->m_obj.type() == typeid(std::vector)) + } else if (m_data_->mObj.type() == typeid(std::vector)) { oss << "vector"; - else + } else { oss << "unknown type"; + } return oss.str(); } /// Visitor pattern implementation template auto visit(Visitor&& visitor) { - std::shared_lock lock(m_mutex); - return std::visit(std::forward(visitor), m_data->m_obj); + std::shared_lock lock(m_mutex_); + return std::visit(std::forward(visitor), m_data_->mObj); } }; // Helper function to create a non-constant BoxedValue template -BoxedValue var(T&& t) { +auto var(T&& t) -> BoxedValue { using DecayedType = std::decay_t; - constexpr bool is_ref_wrapper = + constexpr bool IS_REF_WRAPPER = std::is_same_v>>; - return BoxedValue(std::forward(t), is_ref_wrapper, false); + return BoxedValue(std::forward(t), IS_REF_WRAPPER, false); } // Helper function to create a constant BoxedValue template -BoxedValue const_var(const T& t) { +auto constVar(const T& t) -> BoxedValue { using DecayedType = std::decay_t; - constexpr bool is_ref_wrapper = + constexpr bool IS_REF_WRAPPER = std::is_same_v>>; - return BoxedValue(std::cref(t), is_ref_wrapper, true); + return BoxedValue(std::cref(t), IS_REF_WRAPPER, true); } // Helper function to create a void BoxedValue -inline BoxedValue void_var() { return BoxedValue(); } +inline auto voidVar() -> BoxedValue { return {}; } // Factory functions template -BoxedValue make_boxed_value(T&& t, bool is_return_value = false, - bool readonly = false) { +auto makeBoxedValue(T&& t, bool is_return_value = false, + bool readonly = false) -> BoxedValue { if constexpr (std::is_reference_v) { return BoxedValue(std::ref(t), is_return_value, readonly); } else { diff --git a/src/atom/function/anymeta.hpp b/src/atom/function/anymeta.hpp index d266c95e..8e217246 100644 --- a/src/atom/function/anymeta.hpp +++ b/src/atom/function/anymeta.hpp @@ -11,6 +11,7 @@ #include "any.hpp" #include "atom/error/exception.hpp" +#include "macro.hpp" #include "type_info.hpp" namespace atom::meta { @@ -25,44 +26,46 @@ class TypeMetadata { struct Property { GetterFunction getter; SetterFunction setter; - }; + } ATOM_ALIGNAS(64); private: - std::unordered_map m_methods; - std::unordered_map m_properties; - std::vector m_constructors; + std::unordered_map m_methods_; + std::unordered_map m_properties_; + std::vector m_constructors_; public: - void add_method(const std::string& name, MethodFunction method) { - m_methods[name] = std::move(method); + void addMethod(const std::string& name, MethodFunction method) { + m_methods_[name] = std::move(method); } - void add_property(const std::string& name, GetterFunction getter, - SetterFunction setter) { - m_properties[name] = {std::move(getter), std::move(setter)}; + void addProperty(const std::string& name, GetterFunction getter, + SetterFunction setter) { + m_properties_[name] = {std::move(getter), std::move(setter)}; } - void add_constructor(ConstructorFunction constructor) { - m_constructors.push_back(std::move(constructor)); + void addConstructor(ConstructorFunction constructor) { + m_constructors_.push_back(std::move(constructor)); } - std::optional get_method(const std::string& name) const { - if (auto it = m_methods.find(name); it != m_methods.end()) { + auto getMethod(const std::string& name) const + -> std::optional { + if (auto it = m_methods_.find(name); it != m_methods_.end()) { return it->second; } return std::nullopt; } - std::optional get_property(const std::string& name) const { - if (auto it = m_properties.find(name); it != m_properties.end()) { + auto getProperty(const std::string& name) const -> std::optional { + if (auto it = m_properties_.find(name); it != m_properties_.end()) { return it->second; } return std::nullopt; } - std::optional get_constructor(size_t index = 0) const { - if (index < m_constructors.size()) { - return m_constructors[index]; + auto getConstructor(size_t index = 0) const + -> std::optional { + if (index < m_constructors_.size()) { + return m_constructors_[index]; } return std::nullopt; } @@ -70,24 +73,25 @@ class TypeMetadata { class TypeRegistry { private: - std::unordered_map m_registry; - mutable std::shared_mutex m_mutex; + std::unordered_map m_registry_; + mutable std::shared_mutex m_mutex_; public: - static TypeRegistry& instance() { + static auto instance() -> TypeRegistry& { static TypeRegistry registry; return registry; } - void register_type(const std::string& name, TypeMetadata metadata) { - std::unique_lock lock(m_mutex); - m_registry[name] = std::move(metadata); + void registerType(const std::string& name, TypeMetadata metadata) { + std::unique_lock lock(m_mutex_); + m_registry_[name] = std::move(metadata); } - std::optional get_metadata(const std::string& name) const { - std::shared_lock lock(m_mutex); - auto it = m_registry.find(name); - if (it != m_registry.end()) { + auto getMetadata(const std::string& name) const + -> std::optional { + std::shared_lock lock(m_mutex_); + auto it = m_registry_.find(name); + if (it != m_registry_.end()) { return it->second; } return std::nullopt; @@ -95,12 +99,12 @@ class TypeRegistry { }; // Helper function to call methods dynamically -inline BoxedValue call_method(BoxedValue& obj, const std::string& method_name, - std::vector args) { +inline auto callMethod(BoxedValue& obj, const std::string& method_name, + std::vector args) -> BoxedValue { auto metadata = - TypeRegistry::instance().get_metadata(obj.get_type_info().name()); + TypeRegistry::instance().getMetadata(obj.getTypeInfo().name()); if (metadata) { - auto method = metadata->get_method(method_name); + auto method = metadata->getMethod(method_name); if (method) { args.insert(args.begin(), obj); return (*method)(args); @@ -110,12 +114,12 @@ inline BoxedValue call_method(BoxedValue& obj, const std::string& method_name, } // Helper function to get/set properties dynamically -inline BoxedValue get_property(BoxedValue& obj, - const std::string& property_name) { +inline auto getProperty(BoxedValue& obj, + const std::string& property_name) -> BoxedValue { auto metadata = - TypeRegistry::instance().get_metadata(obj.get_type_info().name()); + TypeRegistry::instance().getMetadata(obj.getTypeInfo().name()); if (metadata) { - auto property = metadata->get_property(property_name); + auto property = metadata->getProperty(property_name); if (property) { return property->getter(obj); } @@ -123,12 +127,12 @@ inline BoxedValue get_property(BoxedValue& obj, THROW_NOT_FOUND("Property not found"); } -inline void set_property(BoxedValue& obj, const std::string& property_name, - const BoxedValue& value) { +inline void setProperty(BoxedValue& obj, const std::string& property_name, + const BoxedValue& value) { auto metadata = - TypeRegistry::instance().get_metadata(obj.get_type_info().name()); + TypeRegistry::instance().getMetadata(obj.getTypeInfo().name()); if (metadata) { - auto property = metadata->get_property(property_name); + auto property = metadata->getProperty(property_name); if (property) { property->setter(obj, value); return; diff --git a/src/atom/function/bind_first.hpp b/src/atom/function/bind_first.hpp index 49700289..fcc74b58 100644 --- a/src/atom/function/bind_first.hpp +++ b/src/atom/function/bind_first.hpp @@ -15,22 +15,22 @@ namespace atom::meta { template -constexpr T *get_pointer(T *t) noexcept { +constexpr auto getPointer(T *t) noexcept -> T * { return t; } template -T *get_pointer(const std::reference_wrapper &t) noexcept { +auto getPointer(const std::reference_wrapper &t) noexcept -> T * { return &t.get(); } template -constexpr const T *get_pointer(const T &t) noexcept { +constexpr auto getPointer(const T &t) noexcept -> const T * { return &t; } template -constexpr T *remove_const_pointer(const T *t) noexcept { +constexpr auto removeConstPointer(const T *t) noexcept -> T * { return const_cast(t); } @@ -47,67 +47,68 @@ template constexpr bool is_nothrow_invocable_v = std::is_nothrow_invocable_v; template -constexpr auto bind_first(Ret (*f)(P1, Param...), O &&o) +constexpr auto bindFirst(Ret (*func)(P1, Param...), O &&object) requires invocable { - return [f, o = std::forward(o)](Param... param) -> Ret { - return f(o, std::forward(param)...); + return [func, object = std::forward(object)](Param... param) -> Ret { + return func(object, std::forward(param)...); }; } template -constexpr auto bind_first(Ret (Class::*f)(Param...), O &&o) +constexpr auto bindFirst(Ret (Class::*func)(Param...), O &&object) requires invocable { - return [f, o = std::forward(o)](Param... param) -> Ret { - return (remove_const_pointer(get_pointer(o))->*f)( + return [func, object = std::forward(object)](Param... param) -> Ret { + return (removeConstPointer(getPointer(object))->*func)( std::forward(param)...); }; } template -constexpr auto bind_first(Ret (Class::*f)(Param...) const, O &&o) +constexpr auto bindFirst(Ret (Class::*func)(Param...) const, O &&object) requires invocable { - return [f, o = std::forward(o)](Param... param) -> Ret { - return (get_pointer(o)->*f)(std::forward(param)...); + return [func, object = std::forward(object)](Param... param) -> Ret { + return (getPointer(object)->*func)(std::forward(param)...); }; } template -auto bind_first(const std::function &f, O &&o) +auto bindFirst(const std::function &func, O &&object) requires invocable, O, Param...> { - return [f, o = std::forward(o)](Param... param) -> Ret { - return f(o, std::forward(param)...); + return [func, object = std::forward(object)](Param... param) -> Ret { + return func(object, std::forward(param)...); }; } template -constexpr auto bind_first(const F &fo, O &&o, - Ret (Class::*f)(P1, Param...) const) +constexpr auto bindFirst(const F &fo, O &&object, + Ret (Class::*func)(P1, Param...) const) requires invocable { - return [fo, o = std::forward(o), f](Param... param) -> Ret { - return (fo.*f)(o, std::forward(param)...); + return [fo, object = std::forward(object), func](Param... param) -> Ret { + return (fo.*func)(object, std::forward(param)...); }; } template -constexpr auto bind_first(const F &f, O &&o) +constexpr auto bindFirst(const F &func, O &&object) requires invocable { - return bind_first(f, std::forward(o), &F::operator()); + return bindFirst(func, std::forward(object), &F::operator()); } template -constexpr auto bind_first(F &&f, O &&o) +constexpr auto bindFirst(F &&func, O &&object) requires std::invocable { - return [f = std::forward(f), - o = std::forward(o)](auto &&...param) -> decltype(auto) { - return std::invoke(f, o, std::forward(param)...); + return [func = std::forward(func), object = std::forward(object)]( + auto &&...param) -> decltype(auto) { + return std::invoke(func, object, + std::forward(param)...); }; } } // namespace atom::meta diff --git a/src/atom/function/concept.hpp b/src/atom/function/concept.hpp index 6624acf5..749e1f9d 100644 --- a/src/atom/function/concept.hpp +++ b/src/atom/function/concept.hpp @@ -126,6 +126,10 @@ concept Hashable = requires(T t) { template concept Swappable = requires(T t) { std::swap(t, t); }; +template +concept Copyable = + std::is_copy_constructible_v && std::is_copy_assignable_v; + template concept Destructible = requires(T t) { { t.~T() } -> std::same_as; @@ -235,6 +239,10 @@ concept Trivial = std::is_trivial_v; template concept TriviallyConstructible = std::is_trivially_constructible_v; +template +concept TriviallyCopyable = + std::is_trivially_copyable_v && std::is_standard_layout_v; + #if __has_include() #include diff --git a/src/atom/function/constructor.hpp b/src/atom/function/constructor.hpp index 284b8dd6..140870e4 100644 --- a/src/atom/function/constructor.hpp +++ b/src/atom/function/constructor.hpp @@ -11,11 +11,13 @@ #include +#include "func_traits.hpp" + #include "atom/error/exception.hpp" namespace atom::meta { template -auto bind_member_function(MemberFunc ClassType::*member_func) { +auto bindMemberFunction(MemberFunc ClassType::*member_func) { return [member_func](ClassType &obj, auto &&...params) { if constexpr (FunctionTraits::is_const_member_function) { return (std::as_const(obj).* @@ -28,19 +30,19 @@ auto bind_member_function(MemberFunc ClassType::*member_func) { } template -auto bind_static_function(Func func) { +auto bindStaticFunction(Func func) { return func; } template -auto bind_member_variable(MemberType ClassType::*member_var) { +auto bindMemberVariable(MemberType ClassType::*member_var) { return [member_var](ClassType &instance) -> MemberType & { return instance.*member_var; }; } template -auto build_shared_constructor_(Class (*)(Params...)) { +auto buildSharedConstructor(Class (* /*unused*/)(Params...)) { return [](auto &&...params) { return std::make_shared( std::forward(params)...); @@ -48,54 +50,54 @@ auto build_shared_constructor_(Class (*)(Params...)) { } template -auto build_copy_constructor_(Class (*)(Params...)) { +auto buildCopyConstructor(Class (* /*unused*/)(Params...)) { return [](auto &&...params) { return Class(std::forward(params)...); }; } template -auto build_plain_constructor_(Class (*)(Params...)) { +auto buildPlainConstructor(Class (* /*unused*/)(Params...)) { return [](auto &&...params) { return Class(std::forward(params)...); }; } template -auto build_constructor_() { +auto buildConstructor() { return [](Args... args) -> std::shared_ptr { return std::make_shared(std::forward(args)...); }; } template -auto build_default_constructor_() { +auto buildDefaultConstructor() { return []() { return Class(); }; } template auto constructor() { - T *f = nullptr; + T *func = nullptr; using ClassType = typename FunctionTraits::class_type; if constexpr (!std::is_copy_constructible_v) { - return build_shared_constructor_(f); + return build_shared_constructor_(func); } else { - return build_copy_constructor_(f); + return build_copy_constructor_(func); } } template auto constructor() { - return build_constructor_(); + return buildConstructor(); } template -auto default_constructor() { +auto defaultConstructor() { if constexpr (std::is_default_constructible_v) { - return build_default_constructor_(); + return buildDefaultConstructor(); } else { - THROW_EXCEPTION("Class is not default constructible"); + THROW_NOT_FOUND("Class is not default constructible"); } } } // namespace atom::meta diff --git a/src/atom/function/conversion.hpp b/src/atom/function/conversion.hpp index 058b7dbe..d9ba705d 100644 --- a/src/atom/function/conversion.hpp +++ b/src/atom/function/conversion.hpp @@ -10,14 +10,11 @@ #define ATOM_META_CONVERSION_HPP #include -#include -#include #include -#include -#include #include #include #include +#include "macro.hpp" #if ENABLE_FASTHASH #include "emhash/hash_table8.hpp" @@ -29,106 +26,112 @@ #include "type_info.hpp" namespace atom::meta { -class bad_conversion : public std::bad_cast { -public: - bad_conversion(const Type_Info& from_type, const Type_Info& to_type) - : message("Failed to convert from " + std::string(from_type.name()) + - " to " + std::string(to_type.name())) {} - - const char* what() const noexcept override { return message.c_str(); } - -private: - std::string message; -}; -class ConversionError : atom::error::Exception { -public: - using atom::error::Exception::Exception; +class BadConversionException : public error::RuntimeError { + using atom::error::RuntimeError::RuntimeError; }; -#define THROW_CONVERSION_ERROR(...) \ - throw ConversionError(__FILE__, __LINE__, __func__, __VA_ARGS__) +#define THROW_CONVERSION_ERROR(...) \ + throw BadConversionException(ATOM_FILE_NAME, ATOM_FILE_LINE, \ + ATOM_FUNC_NAME, __VA_ARGS__) -class Type_Conversion_Base { +class TypeConversionBase { public: - virtual std::any convert(const std::any& from) const = 0; - virtual std::any convert_down(const std::any& to) const = 0; + ATOM_NODISCARD virtual auto convert(const std::any& from) const + -> std::any = 0; + ATOM_NODISCARD virtual auto convertDown(const std::any& to) const + -> std::any = 0; - const Type_Info& to() const noexcept { return to_type; } - const Type_Info& from() const noexcept { return from_type; } + ATOM_NODISCARD auto to() const ATOM_NOEXCEPT -> const TypeInfo& { + return toType; + } + ATOM_NODISCARD auto from() const ATOM_NOEXCEPT -> const TypeInfo& { + return fromType; + } - virtual bool bidir() const noexcept { return true; } + ATOM_NODISCARD virtual auto bidir() const ATOM_NOEXCEPT -> bool { + return true; + } - virtual ~Type_Conversion_Base() = default; + virtual ~TypeConversionBase() = default; - Type_Info to_type; - Type_Info from_type; + TypeInfo toType; + TypeInfo fromType; protected: - Type_Conversion_Base(const Type_Info& to, const Type_Info& from) - : to_type(to), from_type(from) {} + TypeConversionBase(const TypeInfo& to, const TypeInfo& from) + : toType(to), fromType(from) {} }; template -class Static_Conversion : public Type_Conversion_Base { +class StaticConversion : public TypeConversionBase { public: - Static_Conversion() - : Type_Conversion_Base(user_type(), user_type()) {} + StaticConversion() : TypeConversionBase(userType(), userType()) {} - std::any convert(const std::any& from) const override { + ATOM_NODISCARD auto convert(const std::any& from) const + -> std::any override { // Pointer types static conversion (upcasting) - if constexpr (std::is_pointer_v && std::is_pointer_v) { - auto fromPtr = std::any_cast(from); - return std::any(static_cast(fromPtr)); - } - // Reference types static conversion (upcasting) - else if constexpr (std::is_reference_v && - std::is_reference_v) { - try { + try { + if constexpr (std::is_pointer_v && std::is_pointer_v) { + auto fromPtr = std::any_cast(from); + return std::any(static_cast(fromPtr)); + } + // Reference types static conversion (upcasting) + else if constexpr (std::is_reference_v && + std::is_reference_v) { auto& fromRef = std::any_cast(from); return std::any(static_cast(fromRef)); - } catch (const std::bad_cast&) { - throw bad_conversion(from_type, to_type); + + } else { + THROW_CONVERSION_ERROR("Failed to convert ", fromType.name(), + " to ", toType.name()); } - } else { - throw bad_conversion(from_type, to_type); + } catch (const std::bad_cast&) { + THROW_CONVERSION_ERROR("Failed to convert ", fromType.name(), + " to ", toType.name()); } } - std::any convert_down(const std::any& to) const override { + ATOM_NODISCARD auto convertDown(const std::any& to) const + -> std::any override { // Pointer types static conversion (downcasting) - if constexpr (std::is_pointer_v && std::is_pointer_v) { - auto toPtr = std::any_cast(to); - return std::any(static_cast(toPtr)); - } - // Reference types static conversion (downcasting) - else if constexpr (std::is_reference_v && - std::is_reference_v) { - try { + try { + if constexpr (std::is_pointer_v && std::is_pointer_v) { + auto toPtr = std::any_cast(to); + return std::any(static_cast(toPtr)); + } + // Reference types static conversion (downcasting) + else if constexpr (std::is_reference_v && + std::is_reference_v) { auto& toRef = std::any_cast(to); return std::any(static_cast(toRef)); - } catch (const std::bad_cast&) { - throw bad_conversion(to_type, from_type); + + } else { + THROW_CONVERSION_ERROR("Failed to convert ", toType.name(), + " to ", fromType.name()); } - } else { - throw bad_conversion(to_type, from_type); + } catch (const std::bad_cast&) { + THROW_CONVERSION_ERROR("Failed to convert ", toType.name(), " to ", + fromType.name()); } } }; template -class Dynamic_Conversion : public Type_Conversion_Base { +class DynamicConversion : public TypeConversionBase { public: - Dynamic_Conversion() - : Type_Conversion_Base(user_type(), user_type()) {} + DynamicConversion() + : TypeConversionBase(userType(), userType()) {} - std::any convert(const std::any& from) const override { + ATOM_NODISCARD auto convert(const std::any& from) const + -> std::any override { // Pointer types dynamic conversion if constexpr (std::is_pointer_v && std::is_pointer_v) { auto fromPtr = std::any_cast(from); auto convertedPtr = dynamic_cast(fromPtr); - if (!convertedPtr && fromPtr != nullptr) + if (!convertedPtr && fromPtr != nullptr) { throw std::bad_cast(); + } return std::any(convertedPtr); } // Reference types dynamic conversion @@ -138,20 +141,24 @@ class Dynamic_Conversion : public Type_Conversion_Base { auto& fromRef = std::any_cast(from); return std::any(dynamic_cast(fromRef)); } catch (const std::bad_cast&) { - throw bad_conversion(from_type, to_type); + THROW_CONVERSION_ERROR("Failed to convert ", fromType.name(), + " to ", toType.name()); } } else { - throw bad_conversion(from_type, to_type); + THROW_CONVERSION_ERROR("Failed to convert ", fromType.name(), + " to ", toType.name()); } } - std::any convert_down(const std::any& to) const override { + ATOM_NODISCARD auto convertDown(const std::any& to) const + -> std::any override { // Pointer types dynamic conversion if constexpr (std::is_pointer_v && std::is_pointer_v) { auto toPtr = std::any_cast(to); auto convertedPtr = dynamic_cast(toPtr); - if (!convertedPtr && toPtr != nullptr) + if (!convertedPtr && toPtr != nullptr) { throw std::bad_cast(); + } return std::any(convertedPtr); } // Reference types dynamic conversion @@ -161,31 +168,33 @@ class Dynamic_Conversion : public Type_Conversion_Base { auto& toRef = std::any_cast(to); return std::any(dynamic_cast(toRef)); } catch (const std::bad_cast&) { - throw bad_conversion(to_type, from_type); + THROW_CONVERSION_ERROR("Failed to convert ", toType.name(), + " to ", fromType.name()); } } else { - throw bad_conversion(to_type, from_type); + THROW_CONVERSION_ERROR("Failed to convert ", toType.name(), " to ", + fromType.name()); } } }; template -std::shared_ptr base_class() { - if constexpr (std::is_polymorphic::value && - std::is_polymorphic::value) { - return std::make_shared>(); +auto baseClass() -> std::shared_ptr { + if constexpr (std::is_polymorphic_v && + std::is_polymorphic_v) { + return std::make_shared>(); } else { - return std::make_shared>(); + return std::make_shared>(); } } // Specialized conversion for std::vector template -class Vector_Conversion : public Type_Conversion_Base { +class VectorConversion : public TypeConversionBase { public: - Vector_Conversion() - : Type_Conversion_Base(user_type>(), - user_type>()) {} + VectorConversion() + : TypeConversionBase(userType>(), + userType>()) {} std::any convert(const std::any& from) const override { try { @@ -197,14 +206,41 @@ class Vector_Conversion : public Type_Conversion_Base { // Convert each element using dynamic cast auto convertedElem = std::dynamic_pointer_cast(elem); - if (!convertedElem) + if (!convertedElem) { throw std::bad_cast(); + } toVec.push_back(convertedElem); } return std::any(toVec); } catch (const std::bad_any_cast&) { - throw bad_conversion(from_type, to_type); + THROW_CONVERSION_ERROR("Failed to convert ", fromType.name(), + " to ", toType.name()); + } + } + + ATOM_NODISCARD auto convertDown(const std::any& to) const + -> std::any override { + try { + const auto& toVec = std::any_cast&>(to); + std::vector fromVec; + fromVec.reserve(toVec.size()); + + for (const auto& elem : toVec) { + // Convert each element using dynamic cast + auto convertedElem = + std::dynamic_pointer_cast( + elem); + if (!convertedElem) { + throw std::bad_cast(); + } + fromVec.push_back(convertedElem); + } + + return std::any(fromVec); + } catch (const std::bad_any_cast&) { + THROW_CONVERSION_ERROR("Failed to convert ", toType.name(), " to ", + fromType.name()); } } @@ -234,27 +270,55 @@ class Vector_Conversion : public Type_Conversion_Base { template