v2.0.5-rc (#41)
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				BlockStorage/repertory/pipeline/head This commit looks good
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	BlockStorage/repertory/pipeline/head This commit looks good
				
			Reviewed-on: #41
This commit is contained in:
		| @@ -43,8 +43,7 @@ public: | ||||
|   [[nodiscard]] static auto default_remote_api_port(const provider_type &prov) | ||||
|       -> std::uint16_t; | ||||
|  | ||||
|   [[nodiscard]] static auto default_rpc_port(const provider_type &prov) | ||||
|       -> std::uint16_t; | ||||
|   [[nodiscard]] static auto default_rpc_port() -> std::uint16_t; | ||||
|  | ||||
|   [[nodiscard]] static auto get_provider_display_name(const provider_type &prov) | ||||
|       -> std::string; | ||||
| @@ -52,6 +51,8 @@ public: | ||||
|   [[nodiscard]] static auto get_provider_name(const provider_type &prov) | ||||
|       -> std::string; | ||||
|  | ||||
|   [[nodiscard]] static auto get_root_data_directory() -> std::string; | ||||
|  | ||||
| public: | ||||
|   [[nodiscard]] static auto get_stop_requested() -> bool; | ||||
|  | ||||
| @@ -71,7 +72,7 @@ public: | ||||
|  | ||||
| private: | ||||
|   provider_type prov_; | ||||
|   atomic<std::string> api_auth_; | ||||
|   atomic<std::string> api_password_; | ||||
|   std::atomic<std::uint16_t> api_port_; | ||||
|   atomic<std::string> api_user_; | ||||
|   std::atomic<bool> config_changed_; | ||||
| @@ -121,7 +122,7 @@ private: | ||||
|   auto set_value(dest &dst, const source &src) -> bool; | ||||
|  | ||||
| public: | ||||
|   [[nodiscard]] auto get_api_auth() const -> std::string; | ||||
|   [[nodiscard]] auto get_api_password() const -> std::string; | ||||
|  | ||||
|   [[nodiscard]] auto get_api_port() const -> std::uint16_t; | ||||
|  | ||||
| @@ -199,7 +200,7 @@ public: | ||||
|  | ||||
|   void save(); | ||||
|  | ||||
|   void set_api_auth(const std::string &value); | ||||
|   void set_api_password(const std::string &value); | ||||
|  | ||||
|   void set_api_port(std::uint16_t value); | ||||
|  | ||||
|   | ||||
| @@ -57,7 +57,7 @@ using json = nlohmann::json; | ||||
| inline constexpr const std::string_view REPERTORY = "repertory"; | ||||
| inline constexpr const std::wstring_view REPERTORY_W = L"repertory"; | ||||
|  | ||||
| inline constexpr const std::uint64_t REPERTORY_CONFIG_VERSION = 1ULL; | ||||
| inline constexpr const std::uint64_t REPERTORY_CONFIG_VERSION = 2ULL; | ||||
| inline constexpr const std::string_view REPERTORY_DATA_NAME = "repertory2"; | ||||
| inline constexpr const std::string_view REPERTORY_MIN_REMOTE_VERSION = "2.0.0"; | ||||
|  | ||||
|   | ||||
| @@ -22,6 +22,13 @@ | ||||
| #ifndef REPERTORY_INCLUDE_PLATFORM_PLATFORM_HPP_ | ||||
| #define REPERTORY_INCLUDE_PLATFORM_PLATFORM_HPP_ | ||||
|  | ||||
| #include "types/repertory.hpp" | ||||
|  | ||||
| namespace repertory { | ||||
| [[nodiscard]] auto create_lock_id(provider_type prov, | ||||
|                                   std::string_view unique_id)->std::string; | ||||
| } | ||||
|  | ||||
| #if defined(_WIN32) | ||||
| #include "platform/win32_platform.hpp" | ||||
| #include "utils/windows.hpp" | ||||
|   | ||||
| @@ -30,38 +30,45 @@ class i_provider; | ||||
|  | ||||
| class lock_data final { | ||||
| public: | ||||
|   explicit lock_data(const provider_type &pt, std::string unique_id /*= ""*/); | ||||
|   lock_data(provider_type prov, std::string_view unique_id); | ||||
|  | ||||
|   lock_data(); | ||||
|   lock_data(const lock_data &) = delete; | ||||
|   lock_data(lock_data &&) = delete; | ||||
|  | ||||
|   auto operator=(const lock_data &) -> lock_data & = delete; | ||||
|   auto operator=(lock_data &&) -> lock_data & = delete; | ||||
|  | ||||
|   ~lock_data(); | ||||
|  | ||||
| private: | ||||
|   const provider_type pt_; | ||||
|   const std::string unique_id_; | ||||
|   const std::string mutex_id_; | ||||
|   int lock_fd_; | ||||
|   int lock_status_ = EWOULDBLOCK; | ||||
|   std::string mutex_id_; | ||||
|  | ||||
| private: | ||||
|   int handle_{}; | ||||
|   int lock_status_{EWOULDBLOCK}; | ||||
|  | ||||
| private: | ||||
|   [[nodiscard]] static auto get_state_directory() -> std::string; | ||||
|  | ||||
|   [[nodiscard]] static auto get_lock_data_file() -> std::string; | ||||
|   [[nodiscard]] auto get_lock_data_file() const -> std::string; | ||||
|  | ||||
|   [[nodiscard]] auto get_lock_file() -> std::string; | ||||
|   [[nodiscard]] auto get_lock_file() const -> std::string; | ||||
|  | ||||
| private: | ||||
|   [[nodiscard]] static auto | ||||
|   wait_for_lock(int fd, std::uint8_t retry_count = 30u) -> int; | ||||
|   [[nodiscard]] static auto wait_for_lock(int handle, | ||||
|                                           std::uint8_t retry_count = 30U) | ||||
|       -> int; | ||||
|  | ||||
| public: | ||||
|   [[nodiscard]] auto get_mount_state(json &mount_state) -> bool; | ||||
|  | ||||
|   [[nodiscard]] auto grab_lock(std::uint8_t retry_count = 30u) -> lock_result; | ||||
|   [[nodiscard]] auto grab_lock(std::uint8_t retry_count = 30U) -> lock_result; | ||||
|  | ||||
|   void release(); | ||||
|  | ||||
|   [[nodiscard]] auto set_mount_state(bool active, | ||||
|                                      const std::string &mount_location, | ||||
|                                      int pid) -> bool; | ||||
|                                      std::string_view mount_location, int pid) | ||||
|       -> bool; | ||||
| }; | ||||
|  | ||||
| [[nodiscard]] auto create_meta_attributes( | ||||
| @@ -76,5 +83,5 @@ public: | ||||
|                                          const api_file &file) -> api_error; | ||||
| } // namespace repertory | ||||
|  | ||||
| #endif // _WIN32 | ||||
| #endif // !defined(_WIN32) | ||||
| #endif // REPERTORY_INCLUDE_PLATFORM_UNIXPLATFORM_HPP_ | ||||
|   | ||||
| @@ -23,7 +23,6 @@ | ||||
| #define REPERTORY_INCLUDE_PLATFORM_WINPLATFORM_HPP_ | ||||
| #if defined(_WIN32) | ||||
|  | ||||
| #include "app_config.hpp" | ||||
| #include "types/repertory.hpp" | ||||
|  | ||||
| namespace repertory { | ||||
| @@ -31,43 +30,32 @@ class i_provider; | ||||
|  | ||||
| class lock_data final { | ||||
| public: | ||||
|   explicit lock_data(const provider_type &pt, std::string unique_id /*= ""*/) | ||||
|       : pt_(pt), | ||||
|         unique_id_(std::move(unique_id)), | ||||
|         mutex_id_("repertory_" + app_config::get_provider_name(pt) + "_" + | ||||
|                   unique_id_), | ||||
|         mutex_handle_(::CreateMutex(nullptr, FALSE, &mutex_id_[0u])) {} | ||||
|   explicit lock_data(provider_type prov, std::string unique_id); | ||||
|   lock_data(const lock_data &) = delete; | ||||
|   lock_data(lock_data &&) = delete; | ||||
|  | ||||
|   lock_data() | ||||
|       : pt_(provider_type::sia), | ||||
|         unique_id_(""), | ||||
|         mutex_id_(""), | ||||
|         mutex_handle_(INVALID_HANDLE_VALUE) {} | ||||
|   ~lock_data(); | ||||
|  | ||||
|   ~lock_data() { release(); } | ||||
|   auto operator=(const lock_data &) -> lock_data & = delete; | ||||
|   auto operator=(lock_data &&) -> lock_data & = delete; | ||||
|  | ||||
| private: | ||||
|   const provider_type pt_; | ||||
|   const std::string unique_id_; | ||||
|   const std::string mutex_id_; | ||||
|   HANDLE mutex_handle_; | ||||
|   DWORD mutex_state_ = WAIT_FAILED; | ||||
|   std::string mutex_id_; | ||||
|   HANDLE mutex_handle_{INVALID_HANDLE_VALUE}; | ||||
|   DWORD mutex_state_{WAIT_FAILED}; | ||||
|  | ||||
|   [[nodiscard]] auto get_current_mount_state(json &mount_state) -> bool; | ||||
|  | ||||
| public: | ||||
|   [[nodiscard]] auto get_mount_state(const provider_type &pt, | ||||
|                                      json &mount_state) -> bool; | ||||
|  | ||||
|   [[nodiscard]] auto get_mount_state(json &mount_state) -> bool; | ||||
|  | ||||
|   [[nodiscard]] auto get_unique_id() const -> std::string { return unique_id_; } | ||||
|  | ||||
|   [[nodiscard]] auto grab_lock(std::uint8_t retry_count = 30) -> lock_result; | ||||
|   [[nodiscard]] auto grab_lock(std::uint8_t retry_count = 30U) -> lock_result; | ||||
|  | ||||
|   void release(); | ||||
|  | ||||
|   [[nodiscard]] auto set_mount_state(bool active, | ||||
|                                      const std::string &mount_location, | ||||
|                                      const std::int64_t &pid) -> bool; | ||||
|                                      std::string_view mount_location, | ||||
|                                      std::int64_t pid) -> bool; | ||||
| }; | ||||
|  | ||||
| [[nodiscard]] auto create_meta_attributes( | ||||
|   | ||||
| @@ -48,9 +48,9 @@ public: | ||||
|  | ||||
|   [[nodiscard]] auto get_pinned_files() -> rpc_response; | ||||
|  | ||||
|   [[nodiscard]] auto pin_file(const std::string &api_file) -> rpc_response; | ||||
|   [[nodiscard]] auto pin_file(const std::string &api_path) -> rpc_response; | ||||
|  | ||||
|   [[nodiscard]] auto pinned_status(const std::string &api_file) -> rpc_response; | ||||
|   [[nodiscard]] auto pinned_status(const std::string &api_path) -> rpc_response; | ||||
|  | ||||
|   [[nodiscard]] auto set_config_value_by_name(const std::string &name, | ||||
|                                               const std::string &value) | ||||
| @@ -58,7 +58,7 @@ public: | ||||
|  | ||||
|   [[nodiscard]] auto unmount() -> rpc_response; | ||||
|  | ||||
|   [[nodiscard]] auto unpin_file(const std::string &api_file) -> rpc_response; | ||||
|   [[nodiscard]] auto unpin_file(const std::string &api_path) -> rpc_response; | ||||
| }; | ||||
| } // namespace repertory | ||||
|  | ||||
|   | ||||
							
								
								
									
										82
									
								
								repertory/librepertory/include/rpc/common.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								repertory/librepertory/include/rpc/common.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| /* | ||||
|   Copyright <2018-2025> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #ifndef REPERTORY_INCLUDE_RPC_COMMON_HPP_ | ||||
| #define REPERTORY_INCLUDE_RPC_COMMON_HPP_ | ||||
|  | ||||
| #include "utils/base64.hpp" | ||||
| #include "utils/error_utils.hpp" | ||||
| #include "utils/string.hpp" | ||||
|  | ||||
| namespace repertory::rpc { | ||||
| [[nodiscard]] auto check_authorization(const auto &cfg, | ||||
|                                        const httplib::Request &req) -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (cfg.get_api_password().empty() || cfg.get_api_user().empty()) { | ||||
|     utils::error::raise_error(function_name, | ||||
|                               "authorization user or password is not set"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   auto authorization = req.get_header_value("Authorization"); | ||||
|   if (authorization.empty()) { | ||||
|     utils::error::raise_error(function_name, | ||||
|                               "'Authorization' header is not set"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   auto auth_parts = utils::string::split(authorization, ' ', true); | ||||
|   if (auth_parts.empty()) { | ||||
|     utils::error::raise_error(function_name, "'Authorization' header is empty"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   auto auth_type = auth_parts[0U]; | ||||
|   if (auth_type != "Basic") { | ||||
|     utils::error::raise_error(function_name, | ||||
|                               "authorization type is not 'Basic'"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   auto data = macaron::Base64::Decode(authorization.substr(6U)); | ||||
|   auto auth_str = std::string(data.begin(), data.end()); | ||||
|  | ||||
|   auto auth = utils::string::split(auth_str, ':', false); | ||||
|   if (auth.size() < 2U) { | ||||
|     utils::error::raise_error(function_name, "authorization data is not valid"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   auto user = auth.at(0U); | ||||
|   auth.erase(auth.begin()); | ||||
|  | ||||
|   auto pwd = utils::string::join(auth, ':'); | ||||
|   if ((user != cfg.get_api_user()) || (pwd != cfg.get_api_password())) { | ||||
|     utils::error::raise_error(function_name, "authorization failed"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
| } // namespace repertory::rpc | ||||
|  | ||||
| #endif // REPERTORY_INCLUDE_RPC_COMMON_HPP_ | ||||
| @@ -40,8 +40,6 @@ private: | ||||
|   std::mutex start_stop_mutex_; | ||||
|  | ||||
| private: | ||||
|   [[nodiscard]] auto check_authorization(const httplib::Request &req) -> bool; | ||||
|  | ||||
|   void handle_get_config(const httplib::Request &req, httplib::Response &res); | ||||
|  | ||||
|   void handle_get_config_value_by_name(const httplib::Request &req, | ||||
|   | ||||
| @@ -23,7 +23,7 @@ | ||||
| #define REPERTORY_INCLUDE_TYPES_REPERTORY_HPP_ | ||||
|  | ||||
| namespace repertory { | ||||
| constexpr const auto default_api_auth_size{48U}; | ||||
| constexpr const auto default_api_password_size{48U}; | ||||
| constexpr const auto default_download_timeout_secs{30U}; | ||||
| constexpr const auto default_eviction_delay_mins{1U}; | ||||
| constexpr const auto default_high_freq_interval_secs{std::uint16_t{30U}}; | ||||
| @@ -38,6 +38,7 @@ constexpr const auto default_retry_read_count{6U}; | ||||
| constexpr const auto default_ring_buffer_file_size{512U}; | ||||
| constexpr const auto default_task_wait_ms{100U}; | ||||
| constexpr const auto default_timeout_ms{60000U}; | ||||
| constexpr const auto default_ui_mgmt_port{std::uint16_t{30000U}}; | ||||
| constexpr const auto max_ring_buffer_file_size{std::uint16_t(1024U)}; | ||||
| constexpr const auto max_s3_object_name_length{1024U}; | ||||
| constexpr const auto min_cache_size_bytes{ | ||||
| @@ -280,6 +281,8 @@ enum class exit_code : std::int32_t { | ||||
|   pin_failed = -16, | ||||
|   unpin_failed = -17, | ||||
|   init_failed = -18, | ||||
|   ui_mount_failed = -19, | ||||
|   exception = -20, | ||||
| }; | ||||
|  | ||||
| enum http_error_codes : std::int32_t { | ||||
| @@ -304,6 +307,18 @@ enum class provider_type : std::size_t { | ||||
|   unknown, | ||||
| }; | ||||
|  | ||||
| [[nodiscard]] auto | ||||
| provider_type_from_string(std::string_view type, | ||||
|                           provider_type default_type = provider_type::unknown) | ||||
|     -> provider_type; | ||||
|  | ||||
| [[nodiscard]] auto provider_type_to_string(provider_type type) -> std::string; | ||||
|  | ||||
| void clean_json_config(provider_type prov, nlohmann::json &data); | ||||
|  | ||||
| [[nodiscard]] auto clean_json_value(std::string_view name, | ||||
|                                     std::string_view data) -> std::string; | ||||
|  | ||||
| #if defined(_WIN32) | ||||
| struct open_file_data final { | ||||
|   PVOID directory_buffer{nullptr}; | ||||
| @@ -452,7 +467,6 @@ using meta_provider_callback = std::function<void(directory_item &)>; | ||||
|  | ||||
| inline constexpr const auto JSON_ACCESS_KEY{"AccessKey"}; | ||||
| inline constexpr const auto JSON_AGENT_STRING{"AgentString"}; | ||||
| inline constexpr const auto JSON_API_AUTH{"ApiAuth"}; | ||||
| inline constexpr const auto JSON_API_PARENT{"ApiParent"}; | ||||
| inline constexpr const auto JSON_API_PASSWORD{"ApiPassword"}; | ||||
| inline constexpr const auto JSON_API_PATH{"ApiPath"}; | ||||
| @@ -487,6 +501,7 @@ inline constexpr const auto JSON_MAX_UPLOAD_COUNT{"MaxUploadCount"}; | ||||
| inline constexpr const auto JSON_MED_FREQ_INTERVAL_SECS{ | ||||
|     "MedFreqIntervalSeconds"}; | ||||
| inline constexpr const auto JSON_META{"Meta"}; | ||||
| inline constexpr const auto JSON_MOUNT_LOCATIONS{"MountLocations"}; | ||||
| inline constexpr const auto JSON_ONLINE_CHECK_RETRY_SECS{ | ||||
|     "OnlineCheckRetrySeconds"}; | ||||
| inline constexpr const auto JSON_PATH{"Path"}; | ||||
|   | ||||
| @@ -49,6 +49,8 @@ static const option password_option = {"-pw", "--password"}; | ||||
| static const option remote_mount_option = {"-rm", "--remote_mount"}; | ||||
| static const option set_option = {"-set", "--set"}; | ||||
| static const option status_option = {"-status", "--status"}; | ||||
| static const option ui_option = {"-ui", "--ui"}; | ||||
| static const option ui_port_option = {"-up", "--ui_port"}; | ||||
| static const option unmount_option = {"-unmount", "--unmount"}; | ||||
| static const option unpin_file_option = {"-uf", "--unpin_file"}; | ||||
| static const option user_option = {"-us", "--user"}; | ||||
| @@ -75,6 +77,8 @@ static const std::vector<option> option_list = { | ||||
|     remote_mount_option, | ||||
|     set_option, | ||||
|     status_option, | ||||
|     ui_option, | ||||
|     ui_port_option, | ||||
|     unmount_option, | ||||
|     unpin_file_option, | ||||
|     user_option, | ||||
| @@ -87,26 +91,27 @@ void get_api_authentication_data(std::string &user, std::string &password, | ||||
|                                  std::uint16_t &port, const provider_type &prov, | ||||
|                                  const std::string &data_directory); | ||||
|  | ||||
| [[nodiscard]] auto | ||||
| get_provider_type_from_args(std::vector<const char *> args) -> provider_type; | ||||
| [[nodiscard]] auto get_provider_type_from_args(std::vector<const char *> args) | ||||
|     -> provider_type; | ||||
|  | ||||
| [[nodiscard]] auto has_option(std::vector<const char *> args, | ||||
|                               const std::string &option_name) -> bool; | ||||
|  | ||||
| [[nodiscard]] auto has_option(std::vector<const char *> args, | ||||
|                               const option &opt) -> bool; | ||||
| [[nodiscard]] auto has_option(std::vector<const char *> args, const option &opt) | ||||
|     -> bool; | ||||
|  | ||||
| [[nodiscard]] auto parse_option(std::vector<const char *> args, | ||||
|                                 const std::string &option_name, | ||||
|                                 std::uint8_t count) -> std::vector<std::string>; | ||||
|  | ||||
| [[nodiscard]] auto parse_string_option(std::vector<const char *> args, | ||||
|                                        const option &opt, | ||||
|                                        std::string &value) -> exit_code; | ||||
|                                        const option &opt, std::string &value) | ||||
|     -> exit_code; | ||||
|  | ||||
| [[nodiscard]] auto | ||||
| parse_drive_options(std::vector<const char *> args, provider_type &prov, | ||||
|                     std::string &data_directory) -> std::vector<std::string>; | ||||
| [[nodiscard]] auto parse_drive_options(std::vector<const char *> args, | ||||
|                                        provider_type &prov, | ||||
|                                        std::string &data_directory) | ||||
|     -> std::vector<std::string>; | ||||
| } // namespace repertory::utils::cli | ||||
|  | ||||
| #endif // REPERTORY_INCLUDE_UTILS_CLI_UTILS_HPP_ | ||||
|   | ||||
| @@ -66,8 +66,8 @@ void app_config::set_stop_requested() { stop_requested.store(true); } | ||||
| app_config::app_config(const provider_type &prov, | ||||
|                        std::string_view data_directory) | ||||
|     : prov_(prov), | ||||
|       api_auth_(utils::generate_random_string(default_api_auth_size)), | ||||
|       api_port_(default_rpc_port(prov)), | ||||
|       api_password_(utils::generate_random_string(default_api_password_size)), | ||||
|       api_port_(default_rpc_port()), | ||||
|       api_user_(std::string{REPERTORY}), | ||||
|       config_changed_(false), | ||||
|       download_timeout_secs_(default_download_timeout_secs), | ||||
| @@ -124,7 +124,7 @@ app_config::app_config(const provider_type &prov, | ||||
|   } | ||||
|  | ||||
|   value_get_lookup_ = { | ||||
|       {JSON_API_AUTH, [this]() { return get_api_auth(); }}, | ||||
|       {JSON_API_PASSWORD, [this]() { return get_api_password(); }}, | ||||
|       {JSON_API_PORT, [this]() { return std::to_string(get_api_port()); }}, | ||||
|       {JSON_API_USER, [this]() { return get_api_user(); }}, | ||||
|       {JSON_DATABASE_TYPE, | ||||
| @@ -253,10 +253,10 @@ app_config::app_config(const provider_type &prov, | ||||
|  | ||||
|   value_set_lookup_ = { | ||||
|       { | ||||
|           JSON_API_AUTH, | ||||
|           JSON_API_PASSWORD, | ||||
|           [this](const std::string &value) { | ||||
|             set_api_auth(value); | ||||
|             return get_api_auth(); | ||||
|             set_api_password(value); | ||||
|             return get_api_password(); | ||||
|           }, | ||||
|       }, | ||||
|       { | ||||
| @@ -699,36 +699,37 @@ auto app_config::default_api_port(const provider_type &prov) -> std::uint16_t { | ||||
|   return PROVIDER_API_PORTS.at(static_cast<std::size_t>(prov)); | ||||
| } | ||||
|  | ||||
| auto app_config::default_data_directory(const provider_type &prov) | ||||
|     -> std::string { | ||||
| auto app_config::get_root_data_directory() -> std::string { | ||||
| #if defined(_WIN32) | ||||
|   auto data_directory = | ||||
|       utils::path::combine(utils::get_local_app_data_directory(), | ||||
|                            { | ||||
|                                REPERTORY_DATA_NAME, | ||||
|                                app_config::get_provider_name(prov), | ||||
|                            }); | ||||
|   auto data_directory = utils::path::combine( | ||||
|       utils::get_local_app_data_directory(), { | ||||
|                                                  REPERTORY_DATA_NAME, | ||||
|                                              }); | ||||
| #else // !defined(_WIN32) | ||||
| #if defined(__APPLE__) | ||||
|   auto data_directory = | ||||
|       utils::path::combine("~", { | ||||
|                                     "Library", | ||||
|                                     "Application Support", | ||||
|                                     REPERTORY_DATA_NAME, | ||||
|                                     app_config::get_provider_name(prov), | ||||
|                                 }); | ||||
|   auto data_directory = utils::path::combine("~", { | ||||
|                                                       "Library", | ||||
|                                                       "Application Support", | ||||
|                                                       REPERTORY_DATA_NAME, | ||||
|                                                   }); | ||||
| #else  // !defined(__APPLE__) | ||||
|   auto data_directory = | ||||
|       utils::path::combine("~", { | ||||
|                                     ".local", | ||||
|                                     REPERTORY_DATA_NAME, | ||||
|                                     app_config::get_provider_name(prov), | ||||
|                                 }); | ||||
|   auto data_directory = utils::path::combine("~", { | ||||
|                                                       ".local", | ||||
|                                                       REPERTORY_DATA_NAME, | ||||
|                                                   }); | ||||
| #endif // defined(__APPLE__) | ||||
| #endif // defined(_WIN32) | ||||
|   return data_directory; | ||||
| } | ||||
|  | ||||
| auto app_config::default_data_directory(const provider_type &prov) | ||||
|     -> std::string { | ||||
|   return utils::path::combine(app_config::get_root_data_directory(), | ||||
|                               { | ||||
|                                   app_config::get_provider_name(prov), | ||||
|                               }); | ||||
| } | ||||
|  | ||||
| auto app_config::default_remote_api_port(const provider_type &prov) | ||||
|     -> std::uint16_t { | ||||
|   static const std::array<std::uint16_t, | ||||
| @@ -741,19 +742,12 @@ auto app_config::default_remote_api_port(const provider_type &prov) | ||||
|       }; | ||||
|   return PROVIDER_REMOTE_PORTS.at(static_cast<std::size_t>(prov)); | ||||
| } | ||||
| auto app_config::default_rpc_port(const provider_type &prov) -> std::uint16_t { | ||||
|   static const std::array<std::uint16_t, | ||||
|                           static_cast<std::size_t>(provider_type::unknown)> | ||||
|       PROVIDER_RPC_PORTS = { | ||||
|           10000U, | ||||
|           10010U, | ||||
|           10100U, | ||||
|           10002U, | ||||
|       }; | ||||
|   return PROVIDER_RPC_PORTS.at(static_cast<std::size_t>(prov)); | ||||
| } | ||||
|  | ||||
| auto app_config::get_api_auth() const -> std::string { return api_auth_; } | ||||
| auto app_config::default_rpc_port() -> std::uint16_t { return 10000U; } | ||||
|  | ||||
| auto app_config::get_api_password() const -> std::string { | ||||
|   return api_password_; | ||||
| } | ||||
|  | ||||
| auto app_config::get_api_port() const -> std::uint16_t { return api_port_; } | ||||
|  | ||||
| @@ -814,7 +808,7 @@ auto app_config::get_host_config() const -> host_config { return host_config_; } | ||||
|  | ||||
| auto app_config::get_json() const -> json { | ||||
|   json ret = { | ||||
|       {JSON_API_AUTH, api_auth_}, | ||||
|       {JSON_API_PASSWORD, api_password_}, | ||||
|       {JSON_API_PORT, api_port_}, | ||||
|       {JSON_API_USER, api_user_}, | ||||
|       {JSON_DOWNLOAD_TIMEOUT_SECS, download_timeout_secs_}, | ||||
| @@ -939,24 +933,18 @@ auto app_config::get_preferred_download_type() const -> download_type { | ||||
| auto app_config::get_provider_display_name(const provider_type &prov) | ||||
|     -> std::string { | ||||
|   static const std::array<std::string, | ||||
|                           static_cast<std::size_t>(provider_type::unknown)> | ||||
|                           static_cast<std::size_t>(provider_type::unknown) + 1U> | ||||
|       PROVIDER_DISPLAY_NAMES = { | ||||
|           "Sia", | ||||
|           "Remote", | ||||
|           "S3", | ||||
|           "Encrypt", | ||||
|           "Sia", "Remote", "S3", "Encrypt", "Unknown", | ||||
|       }; | ||||
|   return PROVIDER_DISPLAY_NAMES.at(static_cast<std::size_t>(prov)); | ||||
| } | ||||
|  | ||||
| auto app_config::get_provider_name(const provider_type &prov) -> std::string { | ||||
|   static const std::array<std::string, | ||||
|                           static_cast<std::size_t>(provider_type::unknown)> | ||||
|                           static_cast<std::size_t>(provider_type::unknown) + 1U> | ||||
|       PROVIDER_NAMES = { | ||||
|           "sia", | ||||
|           "remote", | ||||
|           "s3", | ||||
|           "encrypt", | ||||
|           "sia", "remote", "s3", "encrypt", "unknown", | ||||
|       }; | ||||
|   return PROVIDER_NAMES.at(static_cast<std::size_t>(prov)); | ||||
| } | ||||
| @@ -1035,7 +1023,7 @@ auto app_config::load() -> bool { | ||||
|     auto found{true}; | ||||
|     auto json_document = json::parse(json_text); | ||||
|  | ||||
|     get_value(json_document, JSON_API_AUTH, api_auth_, found); | ||||
|     get_value(json_document, JSON_API_PASSWORD, api_password_, found); | ||||
|     get_value(json_document, JSON_API_PORT, api_port_, found); | ||||
|     get_value(json_document, JSON_API_USER, api_user_, found); | ||||
|     get_value(json_document, JSON_DATABASE_TYPE, db_type_, found); | ||||
| @@ -1092,6 +1080,13 @@ auto app_config::load() -> bool { | ||||
|           set_value(max_cache_size_bytes_, default_max_cache_size_bytes); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       if (version_ == 2U) { | ||||
|         if (json_document.contains("ApiAuth")) { | ||||
|           api_password_ = json_document.at("ApiAuth").get<std::string>(); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       found = false; | ||||
|     } | ||||
|  | ||||
| @@ -1130,8 +1125,8 @@ void app_config::save() { | ||||
|   }); | ||||
| } | ||||
|  | ||||
| void app_config::set_api_auth(const std::string &value) { | ||||
|   set_value(api_auth_, value); | ||||
| void app_config::set_api_password(const std::string &value) { | ||||
|   set_value(api_password_, value); | ||||
| } | ||||
|  | ||||
| void app_config::set_api_port(std::uint16_t value) { | ||||
|   | ||||
| @@ -21,9 +21,8 @@ | ||||
| */ | ||||
| #if !defined(_WIN32) | ||||
|  | ||||
| #include "platform/unix_platform.hpp" | ||||
| #include "platform/platform.hpp" | ||||
|  | ||||
| #include "app_config.hpp" | ||||
| #include "events/event_system.hpp" | ||||
| #include "events/types/filesystem_item_added.hpp" | ||||
| #include "providers/i_provider.hpp" | ||||
| @@ -36,61 +35,65 @@ | ||||
| #include "utils/unix.hpp" | ||||
|  | ||||
| namespace repertory { | ||||
| lock_data::lock_data(const provider_type &pt, std::string unique_id /*= ""*/) | ||||
|     : pt_(pt), | ||||
|       unique_id_(std::move(unique_id)), | ||||
|       mutex_id_("repertory_" + app_config::get_provider_name(pt) + "_" + | ||||
|                 unique_id_) { | ||||
|   lock_fd_ = open(get_lock_file().c_str(), O_CREAT | O_RDWR, S_IWUSR | S_IRUSR); | ||||
| lock_data::lock_data(provider_type prov, std::string_view unique_id) | ||||
|     : mutex_id_(create_lock_id(prov, unique_id)) { | ||||
|   handle_ = open(get_lock_file().c_str(), O_CREAT | O_RDWR, S_IWUSR | S_IRUSR); | ||||
| } | ||||
|  | ||||
| lock_data::lock_data() | ||||
|     : pt_(provider_type::sia), unique_id_(""), mutex_id_(""), lock_fd_(-1) {} | ||||
| lock_data::~lock_data() { release(); } | ||||
|  | ||||
| lock_data::~lock_data() { | ||||
|   if (lock_fd_ != -1) { | ||||
|     if (lock_status_ == 0) { | ||||
|       unlink(get_lock_file().c_str()); | ||||
|       flock(lock_fd_, LOCK_UN); | ||||
|     } | ||||
|  | ||||
|     close(lock_fd_); | ||||
|   } | ||||
| } | ||||
|  | ||||
| auto lock_data::get_lock_data_file() -> std::string { | ||||
|   const auto dir = get_state_directory(); | ||||
| auto lock_data::get_lock_data_file() const -> std::string { | ||||
|   auto dir = get_state_directory(); | ||||
|   if (not utils::file::directory(dir).create_directory()) { | ||||
|     throw startup_exception("failed to create directory|sp|" + dir + "|err|" + | ||||
|                             std::to_string(utils::get_last_error_code())); | ||||
|   } | ||||
|  | ||||
|   return utils::path::combine( | ||||
|       dir, {"mountstate_" + std::to_string(getuid()) + ".json"}); | ||||
|       dir, { | ||||
|                fmt::format("{}_{}.json", mutex_id_, getuid()), | ||||
|            }); | ||||
| } | ||||
|  | ||||
| auto lock_data::get_lock_file() -> std::string { | ||||
|   const auto dir = get_state_directory(); | ||||
| auto lock_data::get_lock_file() const -> std::string { | ||||
|   auto dir = get_state_directory(); | ||||
|   if (not utils::file::directory(dir).create_directory()) { | ||||
|     throw startup_exception("failed to create directory|sp|" + dir + "|err|" + | ||||
|                             std::to_string(utils::get_last_error_code())); | ||||
|   } | ||||
|  | ||||
|   return utils::path::combine(dir, | ||||
|                               {mutex_id_ + "_" + std::to_string(getuid())}); | ||||
|   return utils::path::combine( | ||||
|       dir, { | ||||
|                fmt::format("{}_{}.lock", mutex_id_, getuid()), | ||||
|            }); | ||||
| } | ||||
|  | ||||
| auto lock_data::get_mount_state(json &mount_state) -> bool { | ||||
|   auto ret = false; | ||||
|   auto fd = | ||||
|       open(get_lock_data_file().c_str(), O_CREAT | O_RDWR, S_IWUSR | S_IRUSR); | ||||
|   if (fd != -1) { | ||||
|     if (wait_for_lock(fd) == 0) { | ||||
|       ret = utils::file::read_json_file(get_lock_data_file(), mount_state); | ||||
|       flock(fd, LOCK_UN); | ||||
|     } | ||||
|   auto handle = open(get_lock_data_file().c_str(), O_RDWR, S_IWUSR | S_IRUSR); | ||||
|   if (handle == -1) { | ||||
|     mount_state = { | ||||
|         {"Active", false}, | ||||
|         {"Location", ""}, | ||||
|         {"PID", -1}, | ||||
|     }; | ||||
|  | ||||
|     close(fd); | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   auto ret{false}; | ||||
|   if (wait_for_lock(handle) == 0) { | ||||
|     ret = utils::file::read_json_file(get_lock_data_file(), mount_state); | ||||
|     if (ret && mount_state.empty()) { | ||||
|       mount_state = { | ||||
|           {"Active", false}, | ||||
|           {"Location", ""}, | ||||
|           {"PID", -1}, | ||||
|       }; | ||||
|     } | ||||
|     flock(handle, LOCK_UN); | ||||
|   } | ||||
|  | ||||
|   close(handle); | ||||
|   return ret; | ||||
| } | ||||
|  | ||||
| @@ -98,25 +101,20 @@ auto lock_data::get_state_directory() -> std::string { | ||||
| #if defined(__APPLE__) | ||||
|   return utils::path::absolute("~/Library/Application Support/" + | ||||
|                                std::string{REPERTORY_DATA_NAME} + "/state"); | ||||
| #else | ||||
| #else  // !defined(__APPLE__) | ||||
|   return utils::path::absolute("~/.local/" + std::string{REPERTORY_DATA_NAME} + | ||||
|                                "/state"); | ||||
| #endif | ||||
| #endif // defined(__APPLE__) | ||||
| } | ||||
|  | ||||
| auto lock_data::grab_lock(std::uint8_t retry_count) -> lock_result { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (lock_fd_ == -1) { | ||||
|   if (handle_ == -1) { | ||||
|     return lock_result::failure; | ||||
|   } | ||||
|  | ||||
|   lock_status_ = wait_for_lock(lock_fd_, retry_count); | ||||
|   lock_status_ = wait_for_lock(handle_, retry_count); | ||||
|   switch (lock_status_) { | ||||
|   case 0: | ||||
|     if (not set_mount_state(false, "", -1)) { | ||||
|       utils::error::raise_error(function_name, "failed to set mount state"); | ||||
|     } | ||||
|     return lock_result::success; | ||||
|   case EWOULDBLOCK: | ||||
|     return lock_result::locked; | ||||
| @@ -125,61 +123,72 @@ auto lock_data::grab_lock(std::uint8_t retry_count) -> lock_result { | ||||
|   } | ||||
| } | ||||
|  | ||||
| auto lock_data::set_mount_state(bool active, const std::string &mount_location, | ||||
| void lock_data::release() { | ||||
|   if (handle_ == -1) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (lock_status_ == 0) { | ||||
|     [[maybe_unused]] auto success{utils::file::file{get_lock_file()}.remove()}; | ||||
|     flock(handle_, LOCK_UN); | ||||
|   } | ||||
|  | ||||
|   close(handle_); | ||||
|   handle_ = -1; | ||||
| } | ||||
|  | ||||
| auto lock_data::set_mount_state(bool active, std::string_view mount_location, | ||||
|                                 int pid) -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   auto ret = false; | ||||
|   auto handle = | ||||
|       open(get_lock_data_file().c_str(), O_CREAT | O_RDWR, S_IWUSR | S_IRUSR); | ||||
|   if (handle != -1) { | ||||
|     if (wait_for_lock(handle) == 0) { | ||||
|       const auto mount_id = | ||||
|           app_config::get_provider_display_name(pt_) + unique_id_; | ||||
|       json mount_state; | ||||
|       if (not utils::file::read_json_file(get_lock_data_file(), mount_state)) { | ||||
|         utils::error::raise_error(function_name, | ||||
|                                   "failed to read mount state file|sp|" + | ||||
|                                       get_lock_file()); | ||||
|       } | ||||
|       if ((mount_state.find(mount_id) == mount_state.end()) || | ||||
|           (mount_state[mount_id].find("Active") == | ||||
|            mount_state[mount_id].end()) || | ||||
|           (mount_state[mount_id]["Active"].get<bool>() != active) || | ||||
|           (active && ((mount_state[mount_id].find("Location") == | ||||
|                        mount_state[mount_id].end()) || | ||||
|                       (mount_state[mount_id]["Location"].get<std::string>() != | ||||
|                        mount_location)))) { | ||||
|         const auto lines = utils::file::read_file_lines(get_lock_data_file()); | ||||
|         const auto txt = std::accumulate( | ||||
|             lines.begin(), lines.end(), std::string(), | ||||
|             [](auto &&val, auto &&line) -> auto { return val + line; }); | ||||
|         auto json_data = json::parse(txt.empty() ? "{}" : txt); | ||||
|         json_data[mount_id] = { | ||||
|             {"Active", active}, | ||||
|             {"Location", active ? mount_location : ""}, | ||||
|             {"PID", active ? pid : -1}, | ||||
|         }; | ||||
|         ret = utils::file::write_json_file(get_lock_data_file(), json_data); | ||||
|       } else { | ||||
|         ret = true; | ||||
|       } | ||||
|   if (handle == -1) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|       flock(handle, LOCK_UN); | ||||
|   auto ret{false}; | ||||
|   if (wait_for_lock(handle) == 0) { | ||||
|     json mount_state; | ||||
|     if (not utils::file::read_json_file(get_lock_data_file(), mount_state)) { | ||||
|       utils::error::raise_error(function_name, | ||||
|                                 "failed to read mount state file|sp|" + | ||||
|                                     get_lock_file()); | ||||
|     } | ||||
|     if ((mount_state.find("Active") == mount_state.end()) || | ||||
|         (mount_state["Active"].get<bool>() != active) || | ||||
|         (active && | ||||
|          ((mount_state.find("Location") == mount_state.end()) || | ||||
|           (mount_state["Location"].get<std::string>() != mount_location)))) { | ||||
|       if (mount_location.empty() && not active) { | ||||
|         ret = utils::file::file{get_lock_data_file()}.remove(); | ||||
|       } else { | ||||
|         ret = utils::file::write_json_file( | ||||
|             get_lock_data_file(), | ||||
|             { | ||||
|                 {"Active", active}, | ||||
|                 {"Location", active ? mount_location : ""}, | ||||
|                 {"PID", active ? pid : -1}, | ||||
|             }); | ||||
|       } | ||||
|     } else { | ||||
|       ret = true; | ||||
|     } | ||||
|  | ||||
|     close(handle); | ||||
|     flock(handle, LOCK_UN); | ||||
|   } | ||||
|  | ||||
|   close(handle); | ||||
|   return ret; | ||||
| } | ||||
|  | ||||
| auto lock_data::wait_for_lock(int fd, std::uint8_t retry_count) -> int { | ||||
|   static constexpr const std::uint32_t max_sleep = 100U; | ||||
| auto lock_data::wait_for_lock(int handle, std::uint8_t retry_count) -> int { | ||||
|   static constexpr const std::uint32_t max_sleep{100U}; | ||||
|  | ||||
|   auto lock_status = EWOULDBLOCK; | ||||
|   auto remain = static_cast<std::uint32_t>(retry_count * max_sleep); | ||||
|   auto lock_status{EWOULDBLOCK}; | ||||
|   auto remain{static_cast<std::uint32_t>(retry_count * max_sleep)}; | ||||
|   while ((remain > 0) && (lock_status == EWOULDBLOCK)) { | ||||
|     lock_status = flock(fd, LOCK_EX | LOCK_NB); | ||||
|     lock_status = flock(handle, LOCK_EX | LOCK_NB); | ||||
|     if (lock_status == -1) { | ||||
|       lock_status = errno; | ||||
|       if (lock_status == EWOULDBLOCK) { | ||||
| @@ -228,13 +237,13 @@ auto provider_meta_handler(i_provider &provider, bool directory, | ||||
|                            const api_file &file) -> api_error { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   const auto meta = create_meta_attributes( | ||||
|   auto meta = create_meta_attributes( | ||||
|       file.accessed_date, | ||||
|       directory ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_ARCHIVE, | ||||
|       file.changed_date, file.creation_date, directory, getgid(), file.key, | ||||
|       directory ? S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR | ||||
|                 : S_IFREG | S_IRUSR | S_IWUSR, | ||||
|       file.modified_date, 0u, 0u, file.file_size, file.source_path, getuid(), | ||||
|       file.modified_date, 0U, 0U, file.file_size, file.source_path, getuid(), | ||||
|       file.modified_date); | ||||
|   auto res = provider.set_item_meta(file.api_path, meta); | ||||
|   if (res == api_error::success) { | ||||
|   | ||||
| @@ -21,150 +21,171 @@ | ||||
| */ | ||||
| #if defined(_WIN32) | ||||
|  | ||||
| #include "platform/win32_platform.hpp" | ||||
| #include "platform/platform.hpp" | ||||
|  | ||||
| #include "events/event_system.hpp" | ||||
| #include "events/types/filesystem_item_added.hpp" | ||||
| #include "providers/i_provider.hpp" | ||||
| #include "utils/config.hpp" | ||||
| #include "utils/error_utils.hpp" | ||||
| #include "utils/string.hpp" | ||||
|  | ||||
| namespace repertory { | ||||
| auto lock_data::get_mount_state(const provider_type & /*pt*/, json &mount_state) | ||||
|     -> bool { | ||||
|   const auto ret = get_mount_state(mount_state); | ||||
|   if (ret) { | ||||
|     const auto mount_id = | ||||
|         app_config::get_provider_display_name(pt_) + unique_id_; | ||||
|     mount_state = mount_state[mount_id].empty() | ||||
|                       ? json({{"Active", false}, {"Location", ""}, {"PID", -1}}) | ||||
|                       : mount_state[mount_id]; | ||||
| lock_data::lock_data(provider_type prov, std::string unique_id) | ||||
|     : mutex_id_(create_lock_id(prov, unique_id)), | ||||
|       mutex_handle_(::CreateMutex(nullptr, FALSE, | ||||
|                                   create_lock_id(prov, unique_id).c_str())) {} | ||||
|  | ||||
| lock_data::~lock_data() { release(); } | ||||
|  | ||||
| auto lock_data::get_current_mount_state(json &mount_state) -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   HKEY key{}; | ||||
|   if (::RegOpenKeyEx(HKEY_CURRENT_USER, | ||||
|                      fmt::format(R"(SOFTWARE\{}\Mounts\{})", | ||||
|                                  REPERTORY_DATA_NAME, mutex_id_) | ||||
|                          .c_str(), | ||||
|                      0, KEY_ALL_ACCESS, &key) != ERROR_SUCCESS) { | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   std::string data; | ||||
|   DWORD data_size{}; | ||||
|  | ||||
|   DWORD type{REG_SZ}; | ||||
|   ::RegGetValueA(key, nullptr, nullptr, RRF_RT_REG_SZ, &type, nullptr, | ||||
|                  &data_size); | ||||
|  | ||||
|   data.resize(data_size); | ||||
|   auto res = ::RegGetValueA(key, nullptr, nullptr, RRF_RT_REG_SZ, &type, | ||||
|                             data.data(), &data_size); | ||||
|   auto ret = res == ERROR_SUCCESS || res == ERROR_FILE_NOT_FOUND; | ||||
|   if (ret && data_size != 0U) { | ||||
|     try { | ||||
|       mount_state = json::parse(data); | ||||
|     } catch (const std::exception &e) { | ||||
|       utils::error::raise_error(function_name, e, "failed to read mount state"); | ||||
|       ret = false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   ::RegCloseKey(key); | ||||
|   return ret; | ||||
| } | ||||
|  | ||||
| auto lock_data::get_mount_state(json &mount_state) -> bool { | ||||
|   HKEY key; | ||||
|   auto ret = !::RegCreateKeyEx( | ||||
|       HKEY_CURRENT_USER, | ||||
|       ("SOFTWARE\\" + std::string{REPERTORY_DATA_NAME} + "\\Mounts").c_str(), 0, | ||||
|       nullptr, 0, KEY_ALL_ACCESS, nullptr, &key, nullptr); | ||||
|   if (ret) { | ||||
|     DWORD i = 0u; | ||||
|     DWORD data_size = 0u; | ||||
|     std::string name; | ||||
|     name.resize(32767u); | ||||
|     auto name_size = static_cast<DWORD>(name.size()); | ||||
|     while (ret && | ||||
|            (::RegEnumValue(key, i, &name[0], &name_size, nullptr, nullptr, | ||||
|                            nullptr, &data_size) == ERROR_SUCCESS)) { | ||||
|       std::string data; | ||||
|       data.resize(data_size); | ||||
|       name_size++; | ||||
|       if ((ret = !::RegEnumValue(key, i++, &name[0], &name_size, nullptr, | ||||
|                                  nullptr, reinterpret_cast<LPBYTE>(&data[0]), | ||||
|                                  &data_size))) { | ||||
|         mount_state[name.c_str()] = json::parse(data); | ||||
|         name_size = static_cast<DWORD>(name.size()); | ||||
|         data_size = 0u; | ||||
|       } | ||||
|     } | ||||
|     ::RegCloseKey(key); | ||||
|   if (not get_current_mount_state(mount_state)) { | ||||
|     return false; | ||||
|   } | ||||
|   return ret; | ||||
|  | ||||
|   mount_state = mount_state.empty() ? json({ | ||||
|                                           {"Active", false}, | ||||
|                                           {"Location", ""}, | ||||
|                                           {"PID", -1}, | ||||
|                                       }) | ||||
|                                     : mount_state; | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| auto lock_data::grab_lock(std::uint8_t retry_count) -> lock_result { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|   static constexpr const std::uint32_t max_sleep{100U}; | ||||
|  | ||||
|   auto ret = lock_result::success; | ||||
|   if (mutex_handle_ == INVALID_HANDLE_VALUE) { | ||||
|     ret = lock_result::failure; | ||||
|   } else { | ||||
|     for (auto i = 0; | ||||
|          (i <= retry_count) && ((mutex_state_ = ::WaitForSingleObject( | ||||
|                                      mutex_handle_, 100)) == WAIT_TIMEOUT); | ||||
|          i++) { | ||||
|     } | ||||
|  | ||||
|     switch (mutex_state_) { | ||||
|     case WAIT_OBJECT_0: { | ||||
|       ret = lock_result::success; | ||||
|       auto should_reset = true; | ||||
|       json mount_state; | ||||
|       if (get_mount_state(pt_, mount_state)) { | ||||
|         if (mount_state["Active"].get<bool>() && | ||||
|             mount_state["Location"] == "elevating") { | ||||
|           should_reset = false; | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       if (should_reset) { | ||||
|         if (not set_mount_state(false, "", -1)) { | ||||
|           utils::error::raise_error(function_name, "failed to set mount state"); | ||||
|         } | ||||
|       } | ||||
|     } break; | ||||
|  | ||||
|     case WAIT_TIMEOUT: | ||||
|       ret = lock_result::locked; | ||||
|       break; | ||||
|  | ||||
|     default: | ||||
|       ret = lock_result::failure; | ||||
|       break; | ||||
|     } | ||||
|     return lock_result::failure; | ||||
|   } | ||||
|  | ||||
|   return ret; | ||||
|   for (std::uint8_t idx = 0U; | ||||
|        (idx <= retry_count) && | ||||
|        ((mutex_state_ = ::WaitForSingleObject(mutex_handle_, max_sleep)) == | ||||
|         WAIT_TIMEOUT); | ||||
|        ++idx) { | ||||
|   } | ||||
|  | ||||
|   switch (mutex_state_) { | ||||
|   case WAIT_OBJECT_0: | ||||
|     return lock_result::success; | ||||
|  | ||||
|   case WAIT_TIMEOUT: | ||||
|     return lock_result::locked; | ||||
|  | ||||
|   default: | ||||
|     return lock_result::failure; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void lock_data::release() { | ||||
|   if (mutex_handle_ != INVALID_HANDLE_VALUE) { | ||||
|     if ((mutex_state_ == WAIT_OBJECT_0) || (mutex_state_ == WAIT_ABANDONED)) { | ||||
|       ::ReleaseMutex(mutex_handle_); | ||||
|     } | ||||
|     ::CloseHandle(mutex_handle_); | ||||
|     mutex_handle_ = INVALID_HANDLE_VALUE; | ||||
|   if (mutex_handle_ == INVALID_HANDLE_VALUE) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if ((mutex_state_ == WAIT_OBJECT_0) || (mutex_state_ == WAIT_ABANDONED)) { | ||||
|     if (mutex_state_ == WAIT_OBJECT_0) { | ||||
|       [[maybe_unused]] auto success{set_mount_state(false, "", -1)}; | ||||
|     } | ||||
|  | ||||
|     ::ReleaseMutex(mutex_handle_); | ||||
|   } | ||||
|  | ||||
|   ::CloseHandle(mutex_handle_); | ||||
|   mutex_handle_ = INVALID_HANDLE_VALUE; | ||||
| } | ||||
|  | ||||
| auto lock_data::set_mount_state(bool active, const std::string &mount_location, | ||||
|                                 const std::int64_t &pid) -> bool { | ||||
|   auto ret = false; | ||||
|   if (mutex_handle_ != INVALID_HANDLE_VALUE) { | ||||
|     const auto mount_id = | ||||
|         app_config::get_provider_display_name(pt_) + unique_id_; | ||||
|     json mount_state; | ||||
|     [[maybe_unused]] auto success = get_mount_state(mount_state); | ||||
|     if ((mount_state.find(mount_id) == mount_state.end()) || | ||||
|         (mount_state[mount_id].find("Active") == mount_state[mount_id].end()) || | ||||
|         (mount_state[mount_id]["Active"].get<bool>() != active) || | ||||
|         (active && ((mount_state[mount_id].find("Location") == | ||||
|                      mount_state[mount_id].end()) || | ||||
|                     (mount_state[mount_id]["Location"].get<std::string>() != | ||||
|                      mount_location)))) { | ||||
|       HKEY key; | ||||
|       if ((ret = !::RegCreateKeyEx( | ||||
|                HKEY_CURRENT_USER, | ||||
|                ("SOFTWARE\\" + std::string{REPERTORY_DATA_NAME} + "\\Mounts") | ||||
|                    .c_str(), | ||||
|                0, nullptr, 0, KEY_ALL_ACCESS, nullptr, &key, nullptr))) { | ||||
|         const auto str = json({{"Active", active}, | ||||
|                                {"Location", active ? mount_location : ""}, | ||||
|                                {"PID", active ? pid : -1}}) | ||||
|                              .dump(0); | ||||
|         ret = !::RegSetValueEx(key, &mount_id[0], 0, REG_SZ, | ||||
|                                reinterpret_cast<const BYTE *>(&str[0]), | ||||
|                                static_cast<DWORD>(str.size())); | ||||
|         ::RegCloseKey(key); | ||||
|       } | ||||
|     } else { | ||||
|       ret = true; | ||||
|     } | ||||
| auto lock_data::set_mount_state(bool active, std::string_view mount_location, | ||||
|                                 std::int64_t pid) -> bool { | ||||
|   if (mutex_handle_ == INVALID_HANDLE_VALUE) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   json mount_state; | ||||
|   [[maybe_unused]] auto success{get_mount_state(mount_state)}; | ||||
|   if (not((mount_state.find("Active") == mount_state.end()) || | ||||
|           (mount_state["Active"].get<bool>() != active) || | ||||
|           (active && | ||||
|            ((mount_state.find("Location") == mount_state.end()) || | ||||
|             (mount_state["Location"].get<std::string>() != mount_location))))) { | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   HKEY key{}; | ||||
|   if (::RegCreateKeyExA(HKEY_CURRENT_USER, | ||||
|                         fmt::format(R"(SOFTWARE\{}\Mounts\{})", | ||||
|                                     REPERTORY_DATA_NAME, mutex_id_) | ||||
|                             .c_str(), | ||||
|                         0, nullptr, 0, KEY_ALL_ACCESS, nullptr, &key, | ||||
|                         nullptr) != ERROR_SUCCESS) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   auto ret{false}; | ||||
|   if (mount_location.empty() && not active) { | ||||
|     ::RegCloseKey(key); | ||||
|  | ||||
|     if (::RegCreateKeyExA( | ||||
|             HKEY_CURRENT_USER, | ||||
|             fmt::format(R"(SOFTWARE\{}\Mounts)", REPERTORY_DATA_NAME).c_str(), | ||||
|             0, nullptr, 0, KEY_ALL_ACCESS, nullptr, &key, | ||||
|             nullptr) != ERROR_SUCCESS) { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     ret = (::RegDeleteKeyA(key, mutex_id_.c_str()) == ERROR_SUCCESS); | ||||
|   } else { | ||||
|     auto data{ | ||||
|         json({ | ||||
|                  {"Active", active}, | ||||
|                  {"Location", active ? mount_location : ""}, | ||||
|                  {"PID", active ? pid : -1}, | ||||
|              }) | ||||
|             .dump(), | ||||
|     }; | ||||
|     ret = (::RegSetValueEx(key, nullptr, 0, REG_SZ, | ||||
|                            reinterpret_cast<const BYTE *>(data.c_str()), | ||||
|                            static_cast<DWORD>(data.size())) == ERROR_SUCCESS); | ||||
|   } | ||||
|  | ||||
|   ::RegCloseKey(key); | ||||
|   return ret; | ||||
| } | ||||
|  | ||||
| @@ -215,4 +236,4 @@ auto provider_meta_handler(i_provider &provider, bool directory, | ||||
| } | ||||
| } // namespace repertory | ||||
|  | ||||
| #endif //_WIN32 | ||||
| #endif // defined(_WIN32) | ||||
|   | ||||
| @@ -22,8 +22,6 @@ | ||||
| #include "rpc/client/client.hpp" | ||||
|  | ||||
| #include "types/repertory.hpp" | ||||
| #include "utils/base64.hpp" | ||||
| #include "utils/utils.hpp" | ||||
|  | ||||
| namespace repertory { | ||||
| client::client(rpc_host_info host_info) : host_info_(std::move(host_info)) {} | ||||
|   | ||||
| @@ -28,64 +28,16 @@ | ||||
| #include "events/types/service_stop_begin.hpp" | ||||
| #include "events/types/service_stop_end.hpp" | ||||
| #include "events/types/unmount_requested.hpp" | ||||
| #include "utils/base64.hpp" | ||||
| #include "rpc/common.hpp" | ||||
| #include "utils/error_utils.hpp" | ||||
| #include "utils/string.hpp" | ||||
|  | ||||
| namespace repertory { | ||||
| server::server(app_config &config) : config_(config) {} | ||||
|  | ||||
| auto server::check_authorization(const httplib::Request &req) -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (config_.get_api_auth().empty() || config_.get_api_user().empty()) { | ||||
|     utils::error::raise_error(function_name, | ||||
|                               "authorization user or password is not set"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   auto authorization = req.get_header_value("Authorization"); | ||||
|   if (authorization.empty()) { | ||||
|     utils::error::raise_error(function_name, "Authorization header is not set"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   auto auth_parts = utils::string::split(authorization, ' ', true); | ||||
|   if (auth_parts.empty()) { | ||||
|     utils::error::raise_error(function_name, "Authorization header is empty"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   auto auth_type = auth_parts[0U]; | ||||
|   if (auth_type != "Basic") { | ||||
|     utils::error::raise_error(function_name, "Authorization is not Basic"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   auto data = macaron::Base64::Decode(authorization.substr(6U)); | ||||
|   auto auth_str = std::string(data.begin(), data.end()); | ||||
|  | ||||
|   auto auth = utils::string::split(auth_str, ':', false); | ||||
|   if (auth.size() < 2U) { | ||||
|     utils::error::raise_error(function_name, "Authorization is not valid"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   auto user = auth.at(0U); | ||||
|   auth.erase(auth.begin()); | ||||
|  | ||||
|   auto pwd = utils::string::join(auth, ':'); | ||||
|   if ((user != config_.get_api_user()) || (pwd != config_.get_api_auth())) { | ||||
|     utils::error::raise_error(function_name, "Authorization failed"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void server::handle_get_config(const httplib::Request & /*req*/, | ||||
|                                httplib::Response &res) { | ||||
|   auto data = config_.get_json(); | ||||
|   clean_json_config(config_.get_provider_type(), data); | ||||
|   res.set_content(data.dump(), "application/json"); | ||||
|   res.status = http_error_codes::ok; | ||||
| } | ||||
| @@ -93,7 +45,10 @@ void server::handle_get_config(const httplib::Request & /*req*/, | ||||
| void server::handle_get_config_value_by_name(const httplib::Request &req, | ||||
|                                              httplib::Response &res) { | ||||
|   auto name = req.get_param_value("name"); | ||||
|   auto data = json({{"value", config_.get_value_by_name(name)}}); | ||||
|   auto data = json({{ | ||||
|       "value", | ||||
|       clean_json_value(name, config_.get_value_by_name(name)), | ||||
|   }}); | ||||
|   res.set_content(data.dump(), "application/json"); | ||||
|   res.status = http_error_codes::ok; | ||||
| } | ||||
| @@ -103,7 +58,10 @@ void server::handle_set_config_value_by_name(const httplib::Request &req, | ||||
|   auto name = req.get_param_value("name"); | ||||
|   auto value = req.get_param_value("value"); | ||||
|  | ||||
|   json data = {{"value", config_.set_value_by_name(name, value)}}; | ||||
|   json data = {{ | ||||
|       "value", | ||||
|       clean_json_value(name, config_.set_value_by_name(name, value)), | ||||
|   }}; | ||||
|   res.set_content(data.dump(), "application/json"); | ||||
|   res.status = http_error_codes::ok; | ||||
| } | ||||
| @@ -173,7 +131,7 @@ void server::start() { | ||||
|  | ||||
|   server_->set_pre_routing_handler( | ||||
|       [this](auto &&req, auto &&res) -> httplib::Server::HandlerResponse { | ||||
|         if (check_authorization(req)) { | ||||
|         if (rpc::check_authorization(config_, req)) { | ||||
|           return httplib::Server::HandlerResponse::Unhandled; | ||||
|         } | ||||
|  | ||||
| @@ -183,8 +141,21 @@ void server::start() { | ||||
|  | ||||
|   initialize(*server_); | ||||
|  | ||||
|   server_thread_ = std::make_unique<std::thread>( | ||||
|       [this]() { server_->listen("127.0.0.1", config_.get_api_port()); }); | ||||
|   server_thread_ = std::make_unique<std::thread>([this]() { | ||||
|     server_->set_socket_options([](auto &&sock) { | ||||
| #if defined(_WIN32) | ||||
|       int enable{1}; | ||||
|       setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, | ||||
|                  reinterpret_cast<const char *>(&enable), sizeof(enable)); | ||||
| #else  //  !defined(_WIN32) | ||||
|       linger opt{1, 0}; | ||||
|       setsockopt(sock, SOL_SOCKET, SO_LINGER, | ||||
|                  reinterpret_cast<const char *>(&opt), sizeof(opt)); | ||||
| #endif // defined(_WIN32) | ||||
|     }); | ||||
|  | ||||
|     server_->listen("127.0.0.1", config_.get_api_port()); | ||||
|   }); | ||||
|   event_system::instance().raise<service_start_end>(function_name, "server"); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -21,10 +21,56 @@ | ||||
| */ | ||||
| #include "types/repertory.hpp" | ||||
|  | ||||
| #include "app_config.hpp" | ||||
| #include "types/startup_exception.hpp" | ||||
| #include "utils/string.hpp" | ||||
|  | ||||
| namespace repertory { | ||||
| void clean_json_config(provider_type prov, nlohmann::json &data) { | ||||
|   data[JSON_API_PASSWORD] = ""; | ||||
|  | ||||
|   switch (prov) { | ||||
|   case provider_type::encrypt: | ||||
|     data[JSON_ENCRYPT_CONFIG][JSON_ENCRYPTION_TOKEN] = ""; | ||||
|     data[JSON_REMOTE_MOUNT][JSON_ENCRYPTION_TOKEN] = ""; | ||||
|     break; | ||||
|  | ||||
|   case provider_type::remote: | ||||
|     data[JSON_REMOTE_CONFIG][JSON_ENCRYPTION_TOKEN] = ""; | ||||
|     break; | ||||
|  | ||||
|   case provider_type::s3: | ||||
|     data[JSON_REMOTE_MOUNT][JSON_ENCRYPTION_TOKEN] = ""; | ||||
|     data[JSON_S3_CONFIG][JSON_ENCRYPTION_TOKEN] = ""; | ||||
|     data[JSON_S3_CONFIG][JSON_SECRET_KEY] = ""; | ||||
|     break; | ||||
|  | ||||
|   case provider_type::sia: | ||||
|     data[JSON_REMOTE_MOUNT][JSON_ENCRYPTION_TOKEN] = ""; | ||||
|     data[JSON_HOST_CONFIG][JSON_API_PASSWORD] = ""; | ||||
|  | ||||
|     break; | ||||
|   default: | ||||
|     return; | ||||
|   } | ||||
| } | ||||
|  | ||||
| auto clean_json_value(std::string_view name, std::string_view data) | ||||
|     -> std::string { | ||||
|   if (name == | ||||
|           fmt::format("{}.{}", JSON_ENCRYPT_CONFIG, JSON_ENCRYPTION_TOKEN) || | ||||
|       name == fmt::format("{}.{}", JSON_HOST_CONFIG, JSON_API_PASSWORD) || | ||||
|       name == fmt::format("{}.{}", JSON_REMOTE_CONFIG, JSON_ENCRYPTION_TOKEN) || | ||||
|       name == fmt::format("{}.{}", JSON_REMOTE_MOUNT, JSON_ENCRYPTION_TOKEN) || | ||||
|       name == fmt::format("{}.{}", JSON_S3_CONFIG, JSON_ENCRYPTION_TOKEN) || | ||||
|       name == fmt::format("{}.{}", JSON_S3_CONFIG, JSON_SECRET_KEY) || | ||||
|       name == JSON_API_PASSWORD) { | ||||
|     return ""; | ||||
|   } | ||||
|  | ||||
|   return std::string{data}; | ||||
| } | ||||
|  | ||||
| auto database_type_from_string(std::string type, database_type default_type) | ||||
|     -> database_type { | ||||
|   type = utils::string::to_lower(utils::string::trim(type)); | ||||
| @@ -191,4 +237,34 @@ auto api_error_to_string(const api_error &error) -> const std::string & { | ||||
|  | ||||
|   return LOOKUP.at(error); | ||||
| } | ||||
|  | ||||
| auto provider_type_from_string(std::string_view type, | ||||
|                                provider_type default_type) -> provider_type { | ||||
|   auto type_lower = utils::string::to_lower(std::string{type}); | ||||
|   if (type_lower == "encrypt") { | ||||
|     return provider_type::encrypt; | ||||
|   } | ||||
|  | ||||
|   if (type_lower == "remote") { | ||||
|     return provider_type::remote; | ||||
|   } | ||||
|  | ||||
|   if (type_lower == "s3") { | ||||
|     return provider_type::s3; | ||||
|   } | ||||
|  | ||||
|   if (type_lower == "sia") { | ||||
|     return provider_type::sia; | ||||
|   } | ||||
|  | ||||
|   if (type_lower == "unknown") { | ||||
|     return provider_type::unknown; | ||||
|   } | ||||
|  | ||||
|   return default_type; | ||||
| } | ||||
|  | ||||
| auto provider_type_to_string(provider_type type) -> std::string { | ||||
|   return app_config::get_provider_name(type); | ||||
| } | ||||
| } // namespace repertory | ||||
|   | ||||
| @@ -45,7 +45,7 @@ void get_api_authentication_data(std::string &user, std::string &password, | ||||
|  | ||||
|   if (success) { | ||||
|     if (user.empty() && password.empty()) { | ||||
|       password = data[JSON_API_AUTH].get<std::string>(); | ||||
|       password = data[JSON_API_PASSWORD].get<std::string>(); | ||||
|       user = data[JSON_API_USER].get<std::string>(); | ||||
|     } | ||||
|     port = data[JSON_API_PORT].get<std::uint16_t>(); | ||||
|   | ||||
| @@ -44,11 +44,13 @@ struct repertory_exception_handler final | ||||
|   } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<repertory_exception_handler> handler{([]() -> auto * { | ||||
|   auto *ptr = new repertory_exception_handler{}; | ||||
|   repertory::utils::error::set_exception_handler(ptr); | ||||
|   return ptr; | ||||
| })()}; | ||||
| const auto repertory_handler{ | ||||
|     ([]() -> auto { | ||||
|       auto ptr = std::make_unique<repertory_exception_handler>(); | ||||
|       repertory::utils::error::set_exception_handler(ptr.get()); | ||||
|       return ptr; | ||||
|     })(), | ||||
| }; | ||||
| } // namespace | ||||
|  | ||||
| namespace repertory::utils::error { | ||||
|   | ||||
							
								
								
									
										31
									
								
								repertory/librepertory/src/utils/platform.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								repertory/librepertory/src/utils/platform.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| /* | ||||
|   Copyright <2018-2025> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #include "platform/platform.hpp" | ||||
|  | ||||
| #include "app_config.hpp" | ||||
|  | ||||
| namespace repertory { | ||||
| auto create_lock_id(provider_type prov, std::string_view unique_id)->std::string { | ||||
|   return fmt::format("{}_{}_{}", REPERTORY_DATA_NAME, | ||||
|                      app_config::get_provider_name(prov), unique_id); | ||||
| } | ||||
| } // namespace repertory | ||||
| @@ -36,6 +36,7 @@ | ||||
| #include "cli/pinned_status.hpp" | ||||
| #include "cli/set.hpp" | ||||
| #include "cli/status.hpp" | ||||
| #include "cli/ui.hpp" | ||||
| #include "cli/unmount.hpp" | ||||
| #include "cli/unpin_file.hpp" | ||||
| #include "utils/cli_utils.hpp" | ||||
| @@ -70,6 +71,7 @@ static const std::unordered_map<utils::cli::option, action, option_hasher> | ||||
|          cli::actions::pinned_status}, | ||||
|         {utils::cli::options::set_option, cli::actions::set}, | ||||
|         {utils::cli::options::status_option, cli::actions::status}, | ||||
|         {utils::cli::options::ui_option, cli::actions::ui}, | ||||
|         {utils::cli::options::unmount_option, cli::actions::unmount}, | ||||
|         {utils::cli::options::unpin_file_option, cli::actions::unpin_file}, | ||||
| }; | ||||
|   | ||||
| @@ -36,10 +36,9 @@ template <typename drive> inline void help(std::vector<const char *> args) { | ||||
|   std::cout << "    -di,--drive_information           Display mounted drive " | ||||
|                "information" | ||||
|             << std::endl; | ||||
|   std::cout | ||||
|       << "    -na,--name                        Unique name for S3 or Sia " | ||||
|          "instance [Required]" | ||||
|       << std::endl; | ||||
|   std::cout << "    -na,--name                        Unique configuration " | ||||
|                "name [Required for Encrypt, S3 and Sia]" | ||||
|             << std::endl; | ||||
|   std::cout << "    -s3,--s3                          Enables S3 mode" | ||||
|             << std::endl; | ||||
|   std::cout | ||||
| @@ -79,6 +78,12 @@ template <typename drive> inline void help(std::vector<const char *> args) { | ||||
|             << std::endl; | ||||
|   std::cout << "    -status                           Display mount status" | ||||
|             << std::endl; | ||||
|   std::cout | ||||
|       << "    -ui,--ui                          Run embedded management UI" | ||||
|       << std::endl; | ||||
|   std::cout << "    -up,--ui_port                     Custom port for embedded " | ||||
|                "management UI" | ||||
|             << std::endl; | ||||
|   std::cout << "    -unmount,--unmount                Unmount and shutdown" | ||||
|             << std::endl; | ||||
|   std::cout << "    -uf,--unpin_file [API path]       Unpin a file from cache " | ||||
|   | ||||
| @@ -28,14 +28,13 @@ | ||||
| #include "providers/provider.hpp" | ||||
| #include "types/repertory.hpp" | ||||
| #include "utils/cli_utils.hpp" | ||||
| #include "utils/com_init_wrapper.hpp" | ||||
| #include "utils/file_utils.hpp" | ||||
| #include "utils/string.hpp" | ||||
| #include "utils/file.hpp" | ||||
|  | ||||
| #if defined(_WIN32) | ||||
| #include "drives/winfsp/remotewinfsp/remote_client.hpp" | ||||
| #include "drives/winfsp/remotewinfsp/remote_winfsp_drive.hpp" | ||||
| #include "drives/winfsp/winfsp_drive.hpp" | ||||
| #include "utils/com_init_wrapper.hpp" | ||||
|  | ||||
| using repertory_drive = repertory::winfsp_drive; | ||||
| using remote_client = repertory::remote_winfsp::remote_client; | ||||
| @@ -57,130 +56,143 @@ namespace repertory::cli::actions { | ||||
| mount(std::vector<const char *> args, std::string data_directory, | ||||
|       int &mount_result, provider_type prov, const std::string &remote_host, | ||||
|       std::uint16_t remote_port, const std::string &unique_id) -> exit_code { | ||||
|   auto ret = exit_code::success; | ||||
|  | ||||
|   lock_data lock(prov, unique_id); | ||||
|   const auto res = lock.grab_lock(); | ||||
|   if (res == lock_result::locked) { | ||||
|     ret = exit_code::mount_active; | ||||
|     std::cerr << app_config::get_provider_display_name(prov) | ||||
|               << " mount is already active" << std::endl; | ||||
|   } else if (res == lock_result::success) { | ||||
|     const auto generate_config = utils::cli::has_option( | ||||
|         args, utils::cli::options::generate_config_option); | ||||
|     if (generate_config) { | ||||
|       app_config config(prov, data_directory); | ||||
|       if (prov == provider_type::remote) { | ||||
|         auto cfg = config.get_remote_config(); | ||||
|         cfg.host_name_or_ip = remote_host; | ||||
|         cfg.api_port = remote_port; | ||||
|         config.set_remote_config(cfg); | ||||
|       } else if (prov == provider_type::sia && | ||||
|                  config.get_sia_config().bucket.empty()) { | ||||
|         [[maybe_unused]] auto bucket = | ||||
|             config.set_value_by_name("SiaConfig.Bucket", unique_id); | ||||
|       } | ||||
|  | ||||
|       std::cout << "Generated " << app_config::get_provider_display_name(prov) | ||||
|                 << " Configuration" << std::endl; | ||||
|       std::cout << config.get_config_file_path() << std::endl; | ||||
|       ret = utils::file::file(config.get_config_file_path()).exists() | ||||
|                 ? exit_code::success | ||||
|                 : exit_code::file_creation_failed; | ||||
|     } else { | ||||
| #if defined(_WIN32) | ||||
|       if (utils::cli::has_option(args, utils::cli::options::hidden_option)) { | ||||
|         ::ShowWindow(::GetConsoleWindow(), SW_HIDE); | ||||
|       } | ||||
| #endif // defined(_WIN32) | ||||
|       auto drive_args = | ||||
|           utils::cli::parse_drive_options(args, prov, data_directory); | ||||
|       app_config config(prov, data_directory); | ||||
| #if defined(_WIN32) | ||||
|       if (config.get_enable_mount_manager() && | ||||
|           not utils::is_process_elevated()) { | ||||
|         utils::com_init_wrapper cw; | ||||
|         if (not lock.set_mount_state(true, "elevating", -1)) { | ||||
|           std::cerr << "failed to set mount state" << std::endl; | ||||
|         } | ||||
|         lock.release(); | ||||
|  | ||||
|         mount_result = utils::run_process_elevated(args); | ||||
|         lock_data lock2(prov, unique_id); | ||||
|         if (lock2.grab_lock() == lock_result::success) { | ||||
|           if (not lock2.set_mount_state(false, "", -1)) { | ||||
|             std::cerr << "failed to set mount state" << std::endl; | ||||
|           } | ||||
|           lock2.release(); | ||||
|         } | ||||
|  | ||||
|         return exit_code::mount_result; | ||||
|       } | ||||
| #endif // defined(_WIN32) | ||||
|       std::cout << "Initializing " | ||||
|                 << app_config::get_provider_display_name(prov) | ||||
|                 << (unique_id.empty() ? "" | ||||
|                     : (prov == provider_type::s3 || prov == provider_type::sia) | ||||
|                         ? " [" + unique_id + ']' | ||||
|                         : " [" + remote_host + ':' + | ||||
|                               std::to_string(remote_port) + ']') | ||||
|                 << " Drive" << std::endl; | ||||
|       if (prov == provider_type::remote) { | ||||
|         std::uint16_t port{0U}; | ||||
|         if (utils::get_next_available_port(config.get_api_port(), port)) { | ||||
|           auto cfg = config.get_remote_config(); | ||||
|           cfg.host_name_or_ip = remote_host; | ||||
|           cfg.api_port = remote_port; | ||||
|           config.set_remote_config(cfg); | ||||
|           config.set_api_port(port); | ||||
|  | ||||
|           try { | ||||
|             remote_drive drive( | ||||
|                 config, | ||||
|                 [&config]() -> std::unique_ptr<remote_instance> { | ||||
|                   return std::unique_ptr<remote_instance>( | ||||
|                       new remote_client(config)); | ||||
|                 }, | ||||
|                 lock); | ||||
|             if (not lock.set_mount_state(true, "", -1)) { | ||||
|               std::cerr << "failed to set mount state" << std::endl; | ||||
|             } | ||||
|             mount_result = drive.mount(drive_args); | ||||
|             ret = exit_code::mount_result; | ||||
|           } catch (const std::exception &e) { | ||||
|             std::cerr << "FATAL: " << e.what() << std::endl; | ||||
|             ret = exit_code::startup_exception; | ||||
|           } | ||||
|         } else { | ||||
|           std::cerr << "FATAL: Unable to get available port" << std::endl; | ||||
|           ret = exit_code::startup_exception; | ||||
|         } | ||||
|       } else { | ||||
|         if (prov == provider_type::sia && | ||||
|             config.get_sia_config().bucket.empty()) { | ||||
|           [[maybe_unused]] auto bucket = | ||||
|               config.set_value_by_name("SiaConfig.Bucket", unique_id); | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|           auto provider = create_provider(prov, config); | ||||
|           repertory_drive drive(config, lock, *provider); | ||||
|           if (not lock.set_mount_state(true, "", -1)) { | ||||
|             std::cerr << "failed to set mount state" << std::endl; | ||||
|           } | ||||
|           mount_result = drive.mount(drive_args); | ||||
|           ret = exit_code::mount_result; | ||||
|         } catch (const std::exception &e) { | ||||
|           std::cerr << "FATAL: " << e.what() << std::endl; | ||||
|           ret = exit_code::startup_exception; | ||||
|         } | ||||
|       } | ||||
|   lock_data global_lock(provider_type::unknown, "global"); | ||||
|   { | ||||
|     auto lock_result = global_lock.grab_lock(100U); | ||||
|     if (lock_result != lock_result::success) { | ||||
|       std::cerr << "FATAL: Unable to get global lock" << std::endl; | ||||
|       return exit_code::lock_failed; | ||||
|     } | ||||
|   } else { | ||||
|     ret = exit_code::lock_failed; | ||||
|   } | ||||
|  | ||||
|   return ret; | ||||
|   lock_data lock(prov, unique_id); | ||||
|   auto lock_result = lock.grab_lock(); | ||||
|   if (lock_result == lock_result::locked) { | ||||
|     std::cerr << app_config::get_provider_display_name(prov) | ||||
|               << " mount is already active" << std::endl; | ||||
|     return exit_code::mount_active; | ||||
|   } | ||||
|  | ||||
|   if (lock_result != lock_result::success) { | ||||
|     std::cerr << "FATAL: Unable to get provider lock" << std::endl; | ||||
|     return exit_code::lock_failed; | ||||
|   } | ||||
|  | ||||
|   if (utils::cli::has_option(args, | ||||
|                              utils::cli::options::generate_config_option)) { | ||||
|     app_config config(prov, data_directory); | ||||
|     if (prov == provider_type::remote) { | ||||
|       auto remote_config = config.get_remote_config(); | ||||
|       remote_config.host_name_or_ip = remote_host; | ||||
|       remote_config.api_port = remote_port; | ||||
|       config.set_remote_config(remote_config); | ||||
|     } else if (prov == provider_type::sia && | ||||
|                config.get_sia_config().bucket.empty()) { | ||||
|       [[maybe_unused]] auto bucket = | ||||
|           config.set_value_by_name("SiaConfig.Bucket", unique_id); | ||||
|     } | ||||
|  | ||||
|     std::cout << "Generated " << app_config::get_provider_display_name(prov) | ||||
|               << " Configuration" << std::endl; | ||||
|     std::cout << config.get_config_file_path() << std::endl; | ||||
|     return utils::file::file(config.get_config_file_path()).exists() | ||||
|                ? exit_code::success | ||||
|                : exit_code::file_creation_failed; | ||||
|   } | ||||
|  | ||||
| #if defined(_WIN32) | ||||
|   if (utils::cli::has_option(args, utils::cli::options::hidden_option)) { | ||||
|     ::ShowWindow(::GetConsoleWindow(), SW_HIDE); | ||||
|   } | ||||
| #endif // defined(_WIN32) | ||||
|  | ||||
|   auto drive_args = utils::cli::parse_drive_options(args, prov, data_directory); | ||||
|   app_config config(prov, data_directory); | ||||
|   { | ||||
|     std::uint16_t port{}; | ||||
|     if (not utils::get_next_available_port(config.get_api_port(), port)) { | ||||
|       std::cerr << "FATAL: Unable to get available port" << std::endl; | ||||
|       return exit_code::startup_exception; | ||||
|     } | ||||
|     config.set_api_port(port); | ||||
|   } | ||||
|  | ||||
| #if defined(_WIN32) | ||||
|   if (config.get_enable_mount_manager() && not utils::is_process_elevated()) { | ||||
|     utils::com_init_wrapper wrapper; | ||||
|     if (not lock.set_mount_state(true, "elevating", -1)) { | ||||
|       std::cerr << "failed to set mount state" << std::endl; | ||||
|     } | ||||
|     lock.release(); | ||||
|     global_lock.release(); | ||||
|  | ||||
|     mount_result = utils::run_process_elevated(args); | ||||
|     lock_data prov_lock(prov, unique_id); | ||||
|     if (prov_lock.grab_lock() == lock_result::success) { | ||||
|       if (not prov_lock.set_mount_state(false, "", -1)) { | ||||
|         std::cerr << "failed to set mount state" << std::endl; | ||||
|       } | ||||
|       prov_lock.release(); | ||||
|     } | ||||
|  | ||||
|     return exit_code::mount_result; | ||||
|   } | ||||
| #endif // defined(_WIN32) | ||||
|  | ||||
|   std::cout << "Initializing " << app_config::get_provider_display_name(prov) | ||||
|             << (unique_id.empty() ? "" | ||||
|                 : (prov == provider_type::remote) | ||||
|                     ? " [" + remote_host + ':' + std::to_string(remote_port) + | ||||
|                           ']' | ||||
|                     : " [" + unique_id + ']') | ||||
|             << " Drive" << std::endl; | ||||
|   if (prov == provider_type::remote) { | ||||
|     try { | ||||
|       auto remote_cfg = config.get_remote_config(); | ||||
|       remote_cfg.host_name_or_ip = remote_host; | ||||
|       remote_cfg.api_port = remote_port; | ||||
|       config.set_remote_config(remote_cfg); | ||||
|  | ||||
|       remote_drive drive( | ||||
|           config, | ||||
|           [&config]() -> std::unique_ptr<remote_instance> { | ||||
|             return std::unique_ptr<remote_instance>(new remote_client(config)); | ||||
|           }, | ||||
|           lock); | ||||
|       if (not lock.set_mount_state(true, "", -1)) { | ||||
|         std::cerr << "failed to set mount state" << std::endl; | ||||
|       } | ||||
|  | ||||
|       global_lock.release(); | ||||
|       mount_result = drive.mount(drive_args); | ||||
|       return exit_code::mount_result; | ||||
|     } catch (const std::exception &e) { | ||||
|       std::cerr << "FATAL: " << e.what() << std::endl; | ||||
|     } | ||||
|  | ||||
|     return exit_code::startup_exception; | ||||
|   } | ||||
|  | ||||
|   try { | ||||
|     if (prov == provider_type::sia && config.get_sia_config().bucket.empty()) { | ||||
|       [[maybe_unused]] auto bucket = | ||||
|           config.set_value_by_name("SiaConfig.Bucket", unique_id); | ||||
|     } | ||||
|  | ||||
|     auto provider = create_provider(prov, config); | ||||
|     repertory_drive drive(config, lock, *provider); | ||||
|     if (not lock.set_mount_state(true, "", -1)) { | ||||
|       std::cerr << "failed to set mount state" << std::endl; | ||||
|     } | ||||
|  | ||||
|     global_lock.release(); | ||||
|     mount_result = drive.mount(drive_args); | ||||
|     return exit_code::mount_result; | ||||
|   } catch (const std::exception &e) { | ||||
|     std::cerr << "FATAL: " << e.what() << std::endl; | ||||
|   } | ||||
|  | ||||
|   return exit_code::startup_exception; | ||||
| } | ||||
| } // namespace repertory::cli::actions | ||||
|  | ||||
|   | ||||
							
								
								
									
										61
									
								
								repertory/repertory/include/cli/ui.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								repertory/repertory/include/cli/ui.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| /* | ||||
|   Copyright <2018-2025> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #ifndef REPERTORY_INCLUDE_CLI_UI_HPP_ | ||||
| #define REPERTORY_INCLUDE_CLI_UI_HPP_ | ||||
|  | ||||
| #include "types/repertory.hpp" | ||||
| #include "ui/handlers.hpp" | ||||
| #include "ui/mgmt_app_config.hpp" | ||||
| #include "utils/cli_utils.hpp" | ||||
| #include "utils/file.hpp" | ||||
| #include "utils/string.hpp" | ||||
|  | ||||
| namespace repertory::cli::actions { | ||||
| [[nodiscard]] inline auto | ||||
| ui(std::vector<const char *> args, const std::string & /*data_directory*/, | ||||
|    const provider_type & /* prov */, const std::string & /* unique_id */, | ||||
|    std::string /* user */, std::string /* password */) -> exit_code { | ||||
|  | ||||
|   ui::mgmt_app_config config{}; | ||||
|  | ||||
|   std::string data; | ||||
|   auto res = utils::cli::parse_string_option( | ||||
|       args, utils::cli::options::ui_port_option, data); | ||||
|   if (res == exit_code::success && not data.empty()) { | ||||
|     config.set_api_port(utils::string::to_uint16(data)); | ||||
|   } | ||||
|  | ||||
|   if (not utils::file::change_to_process_directory()) { | ||||
|     return exit_code::ui_mount_failed; | ||||
|   } | ||||
|  | ||||
|   httplib::Server server; | ||||
|   if (not server.set_mount_point("/ui", "./web")) { | ||||
|     return exit_code::ui_mount_failed; | ||||
|   } | ||||
|  | ||||
|   ui::handlers handlers(&config, &server); | ||||
|   return exit_code::success; | ||||
| } | ||||
| } // namespace repertory::cli::actions | ||||
|  | ||||
| #endif // REPERTORY_INCLUDE_CLI_UI_HPP_ | ||||
							
								
								
									
										120
									
								
								repertory/repertory/include/ui/handlers.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								repertory/repertory/include/ui/handlers.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| /* | ||||
|   Copyright <2018-2025> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #ifndef REPERTORY_INCLUDE_UI_HANDLERS_HPP_ | ||||
| #define REPERTORY_INCLUDE_UI_HANDLERS_HPP_ | ||||
|  | ||||
| #include "events/consumers/console_consumer.hpp" | ||||
| #include "utils/common.hpp" | ||||
|  | ||||
| namespace repertory::ui { | ||||
| class mgmt_app_config; | ||||
|  | ||||
| class handlers final { | ||||
| private: | ||||
|   static constexpr const auto nonce_length{128U}; | ||||
|   static constexpr const auto nonce_timeout{15U}; | ||||
|  | ||||
|   struct nonce_data final { | ||||
|     std::chrono::system_clock::time_point creation{ | ||||
|         std::chrono::system_clock::now(), | ||||
|     }; | ||||
|  | ||||
|     std::string nonce{ | ||||
|         utils::generate_random_string(nonce_length), | ||||
|     }; | ||||
|   }; | ||||
|  | ||||
| public: | ||||
|   handlers(mgmt_app_config *config, httplib::Server *server); | ||||
|  | ||||
|   handlers() = delete; | ||||
|   handlers(const handlers &) = delete; | ||||
|   handlers(handlers &&) = delete; | ||||
|  | ||||
|   ~handlers(); | ||||
|  | ||||
|   auto operator=(const handlers &) -> handlers & = delete; | ||||
|   auto operator=(handlers &&) -> handlers & = delete; | ||||
|  | ||||
| private: | ||||
|   mgmt_app_config *config_; | ||||
|   std::string repertory_binary_; | ||||
|   httplib::Server *server_; | ||||
|  | ||||
| private: | ||||
|   console_consumer console; | ||||
|   mutable std::mutex mtx_; | ||||
|   mutable std::unordered_map<std::string, std::recursive_mutex> mtx_lookup_; | ||||
|   std::mutex nonce_mtx_; | ||||
|   std::unordered_map<std::string, nonce_data> nonce_lookup_; | ||||
|   std::condition_variable nonce_notify_; | ||||
|   std::unique_ptr<std::thread> nonce_thread_; | ||||
|   stop_type stop_requested{false}; | ||||
|  | ||||
| private: | ||||
|   [[nodiscard]] auto data_directory_exists(provider_type prov, | ||||
|                                            std::string_view name) const -> bool; | ||||
|  | ||||
|   static void handle_get_available_locations(httplib::Response &res); | ||||
|  | ||||
|   void handle_get_mount(const httplib::Request &req, | ||||
|                         httplib::Response &res) const; | ||||
|  | ||||
|   void handle_get_mount_list(httplib::Response &res) const; | ||||
|  | ||||
|   void handle_get_mount_location(const httplib::Request &req, | ||||
|                                  httplib::Response &res) const; | ||||
|  | ||||
|   void handle_get_mount_status(const httplib::Request &req, | ||||
|                                httplib::Response &res) const; | ||||
|  | ||||
|   void handle_get_nonce(httplib::Response &res); | ||||
|  | ||||
|   void handle_get_settings(httplib::Response &res) const; | ||||
|  | ||||
|   void handle_post_add_mount(const httplib::Request &req, | ||||
|                              httplib::Response &res) const; | ||||
|  | ||||
|   void handle_post_mount(const httplib::Request &req, httplib::Response &res); | ||||
|  | ||||
|   void handle_put_mount_location(const httplib::Request &req, | ||||
|                                  httplib::Response &res) const; | ||||
|  | ||||
|   void handle_put_set_value_by_name(const httplib::Request &req, | ||||
|                                     httplib::Response &res) const; | ||||
|  | ||||
|   void handle_put_settings(const httplib::Request &req, | ||||
|                            httplib::Response &res) const; | ||||
|  | ||||
|   auto launch_process(provider_type prov, std::string_view name, | ||||
|                       std::vector<std::string> args, | ||||
|                       bool background = false) const | ||||
|       -> std::vector<std::string>; | ||||
|  | ||||
|   void removed_expired_nonces(); | ||||
|  | ||||
|   void set_key_value(provider_type prov, std::string_view name, | ||||
|                      std::string_view key, std::string_view value) const; | ||||
| }; | ||||
| } // namespace repertory::ui | ||||
|  | ||||
| #endif // REPERTORY_INCLUDE_UI_HANDLERS_HPP_ | ||||
							
								
								
									
										70
									
								
								repertory/repertory/include/ui/mgmt_app_config.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								repertory/repertory/include/ui/mgmt_app_config.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| /* | ||||
|   Copyright <2018-2025> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #ifndef REPERTORY_INCLUDE_UI_MGMT_APP_CONFIG_HPP_ | ||||
| #define REPERTORY_INCLUDE_UI_MGMT_APP_CONFIG_HPP_ | ||||
|  | ||||
| #include "types/repertory.hpp" | ||||
|  | ||||
| namespace repertory::ui { | ||||
| class mgmt_app_config final { | ||||
| public: | ||||
|   mgmt_app_config(); | ||||
|  | ||||
| private: | ||||
|   atomic<std::string> api_password_{"repertory"}; | ||||
|   std::atomic<std::uint16_t> api_port_{default_ui_mgmt_port}; | ||||
|   atomic<std::string> api_user_{"repertory"}; | ||||
|   std::unordered_map<provider_type, | ||||
|                      std::unordered_map<std::string, std::string>> | ||||
|       locations_; | ||||
|   mutable std::recursive_mutex mtx_; | ||||
|  | ||||
| private: | ||||
|   void save() const; | ||||
|  | ||||
| public: | ||||
|   [[nodiscard]] auto to_json() const -> nlohmann::json; | ||||
|  | ||||
|   [[nodiscard]] auto get_api_password() const -> std::string { | ||||
|     return api_password_; | ||||
|   } | ||||
|  | ||||
|   [[nodiscard]] auto get_api_port() const -> std::uint16_t { return api_port_; } | ||||
|  | ||||
|   [[nodiscard]] auto get_api_user() const -> std::string { return api_user_; } | ||||
|  | ||||
|   [[nodiscard]] auto get_mount_location(provider_type prov, | ||||
|                                         std::string_view name) const | ||||
|       -> std::string; | ||||
|  | ||||
|   void set_api_password(std::string_view api_password); | ||||
|  | ||||
|   void set_api_port(std::uint16_t api_port); | ||||
|  | ||||
|   void set_api_user(std::string_view api_user); | ||||
|  | ||||
|   void set_mount_location(provider_type prov, std::string_view name, | ||||
|                           std::string_view location); | ||||
| }; | ||||
| } // namespace repertory::ui | ||||
|  | ||||
| #endif // REPERTORY_INCLUDE_UI_MGMT_APP_CONFIG_HPP_ | ||||
| @@ -105,7 +105,8 @@ auto main(int argc, char **argv) -> int { | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } else if ((prov == provider_type::s3) || (prov == provider_type::sia)) { | ||||
|     } else if ((prov == provider_type::s3) || (prov == provider_type::sia) || | ||||
|                (prov == provider_type::encrypt)) { | ||||
|       std::string data; | ||||
|       res = utils::cli::parse_string_option( | ||||
|           args, utils::cli::options::name_option, data); | ||||
| @@ -115,9 +116,9 @@ auto main(int argc, char **argv) -> int { | ||||
|           if (prov == provider_type::sia) { | ||||
|             unique_id = "default"; | ||||
|           } else { | ||||
|             std::cerr << "Name of " | ||||
|             std::cerr << "Configuration name for '" | ||||
|                       << app_config::get_provider_display_name(prov) | ||||
|                       << " instance not provided" << std::endl; | ||||
|                       << "' was not provided" << std::endl; | ||||
|             res = exit_code::invalid_syntax; | ||||
|           } | ||||
|         } | ||||
| @@ -146,10 +147,17 @@ auto main(int argc, char **argv) -> int { | ||||
|            (res == exit_code::option_not_found) && | ||||
|            (idx < utils::cli::options::option_list.size()); | ||||
|            idx++) { | ||||
|         res = cli::actions::perform_action( | ||||
|             utils::cli::options::option_list[idx], args, data_directory, prov, | ||||
|             unique_id, user, password); | ||||
|         try { | ||||
|           res = cli::actions::perform_action( | ||||
|               utils::cli::options::option_list[idx], args, data_directory, prov, | ||||
|               unique_id, user, password); | ||||
|         } catch (const std::exception &ex) { | ||||
|           res = exit_code::exception; | ||||
|         } catch (...) { | ||||
|           res = exit_code::exception; | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       if (res == exit_code::option_not_found) { | ||||
|         res = cli::actions::mount(args, data_directory, mount_result, prov, | ||||
|                                   remote_host, remote_port, unique_id); | ||||
|   | ||||
							
								
								
									
										751
									
								
								repertory/repertory/src/ui/handlers.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										751
									
								
								repertory/repertory/src/ui/handlers.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,751 @@ | ||||
| /* | ||||
|   Copyright <2018-2025> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #include "ui/handlers.hpp" | ||||
|  | ||||
| #include "app_config.hpp" | ||||
| #include "events/event_system.hpp" | ||||
| #include "types/repertory.hpp" | ||||
| #include "ui/mgmt_app_config.hpp" | ||||
| #include "utils/collection.hpp" | ||||
| #include "utils/common.hpp" | ||||
| #include "utils/config.hpp" | ||||
| #include "utils/error_utils.hpp" | ||||
| #include "utils/file.hpp" | ||||
| #include "utils/hash.hpp" | ||||
| #include "utils/path.hpp" | ||||
| #include "utils/string.hpp" | ||||
|  | ||||
| #include <boost/process.hpp> | ||||
|  | ||||
| namespace { | ||||
| [[nodiscard]] auto decrypt(std::string_view data, std::string_view password) | ||||
|     -> std::string { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|   if (data.empty()) { | ||||
|     return std::string{data}; | ||||
|   } | ||||
|  | ||||
|   repertory::data_buffer decoded; | ||||
|   if (not repertory::utils::collection::from_hex_string(data, decoded)) { | ||||
|     throw repertory::utils::error::create_exception(function_name, | ||||
|                                                     {"decryption failed"}); | ||||
|   } | ||||
|   repertory::data_buffer buffer(decoded.size()); | ||||
|  | ||||
|   auto key = repertory::utils::encryption::create_hash_blake2b_256(password); | ||||
|  | ||||
|   unsigned long long size{}; | ||||
|   auto res = crypto_aead_xchacha20poly1305_ietf_decrypt( | ||||
|       reinterpret_cast<unsigned char *>(buffer.data()), &size, nullptr, | ||||
|       reinterpret_cast<const unsigned char *>( | ||||
|           &decoded.at(crypto_aead_xchacha20poly1305_IETF_NPUBBYTES)), | ||||
|       decoded.size() - crypto_aead_xchacha20poly1305_IETF_NPUBBYTES, | ||||
|       reinterpret_cast<const unsigned char *>(REPERTORY.data()), | ||||
|       REPERTORY.length(), | ||||
|       reinterpret_cast<const unsigned char *>(decoded.data()), | ||||
|       reinterpret_cast<const unsigned char *>(key.data())); | ||||
|   if (res != 0) { | ||||
|     throw repertory::utils::error::create_exception(function_name, | ||||
|                                                     {"decryption failed"}); | ||||
|   } | ||||
|  | ||||
|   return { | ||||
|       buffer.begin(), | ||||
|       std::next(buffer.begin(), static_cast<std::int64_t>(size)), | ||||
|   }; | ||||
| } | ||||
|  | ||||
| [[nodiscard]] auto decrypt_value(const repertory::ui::mgmt_app_config *config, | ||||
|                                  std::string_view key, std::string_view value, | ||||
|                                  bool &skip) -> std::string { | ||||
|   auto last_key{key}; | ||||
|   auto parts = repertory::utils::string::split(key, '.', false); | ||||
|   if (parts.size() > 1U) { | ||||
|     last_key = parts.at(parts.size() - 1U); | ||||
|   } | ||||
|  | ||||
|   if (last_key == repertory::JSON_API_PASSWORD || | ||||
|       last_key == repertory::JSON_ENCRYPTION_TOKEN || | ||||
|       last_key == repertory::JSON_SECRET_KEY) { | ||||
|     auto decrypted = decrypt(value, config->get_api_password()); | ||||
|     if (decrypted.empty()) { | ||||
|       skip = true; | ||||
|     } | ||||
|  | ||||
|     return decrypted; | ||||
|   } | ||||
|  | ||||
|   return std::string{value}; | ||||
| } | ||||
| } // namespace | ||||
|  | ||||
| namespace repertory::ui { | ||||
| handlers::handlers(mgmt_app_config *config, httplib::Server *server) | ||||
|     : config_(config), | ||||
| #if defined(_WIN32) | ||||
|       repertory_binary_(utils::path::combine(".", {"repertory.exe"})), | ||||
| #else  // !defined(_WIN32) | ||||
|       repertory_binary_(utils::path::combine(".", {"repertory"})), | ||||
| #endif // defined(_WIN32) | ||||
|       server_(server) { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   server_->set_socket_options([](auto &&sock) { | ||||
| #if defined(_WIN32) | ||||
|     int enable{1}; | ||||
|     setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, | ||||
|                reinterpret_cast<const char *>(&enable), sizeof(enable)); | ||||
| #else  //  !defined(_WIN32) | ||||
|     linger opt{1, 0}; | ||||
|     setsockopt(sock, SOL_SOCKET, SO_LINGER, | ||||
|                reinterpret_cast<const char *>(&opt), sizeof(opt)); | ||||
| #endif // defined(_WIN32) | ||||
|   }); | ||||
|  | ||||
|   server_->set_pre_routing_handler( | ||||
|       [this](const httplib::Request &req, | ||||
|              auto &&res) -> httplib::Server::HandlerResponse { | ||||
|         if (req.path == "/api/v1/nonce" || req.path == "/ui" || | ||||
|             req.path.starts_with("/ui/")) { | ||||
|           return httplib::Server::HandlerResponse::Unhandled; | ||||
|         } | ||||
|  | ||||
|         auto auth = | ||||
|             decrypt(req.get_param_value("auth"), config_->get_api_password()); | ||||
|         if (utils::string::begins_with( | ||||
|                 auth, fmt::format("{}_", config_->get_api_user()))) { | ||||
|           auto nonce = auth.substr(config_->get_api_user().length() + 1U); | ||||
|  | ||||
|           mutex_lock lock(nonce_mtx_); | ||||
|           if (nonce_lookup_.contains(nonce)) { | ||||
|             nonce_lookup_.erase(nonce); | ||||
|             return httplib::Server::HandlerResponse::Unhandled; | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         res.status = http_error_codes::unauthorized; | ||||
|         return httplib::Server::HandlerResponse::Handled; | ||||
|       }); | ||||
|  | ||||
|   server_->set_exception_handler([](const httplib::Request &req, | ||||
|                                     httplib::Response &res, | ||||
|                                     std::exception_ptr ptr) { | ||||
|     json data{ | ||||
|         {"path", req.path}, | ||||
|     }; | ||||
|  | ||||
|     try { | ||||
|       std::rethrow_exception(ptr); | ||||
|     } catch (const std::exception &e) { | ||||
|       data["error"] = (e.what() == nullptr) ? "unknown error" : e.what(); | ||||
|       utils::error::raise_error(function_name, e, | ||||
|                                 "failed request: " + req.path); | ||||
|     } catch (...) { | ||||
|       data["error"] = "unknown error"; | ||||
|       utils::error::raise_error(function_name, "unknown error", | ||||
|                                 "failed request: " + req.path); | ||||
|     } | ||||
|  | ||||
|     res.set_content(data.dump(), "application/json"); | ||||
|     res.status = utils::string::ends_with(data["error"].get<std::string>(), | ||||
|                                           "|decryption failed") | ||||
|                      ? http_error_codes::unauthorized | ||||
|                      : http_error_codes::internal_error; | ||||
|   }); | ||||
|  | ||||
|   server->Get("/api/v1/locations", [](auto && /* req */, auto &&res) { | ||||
|     handle_get_available_locations(res); | ||||
|   }); | ||||
|  | ||||
|   server->Get("/api/v1/mount", | ||||
|               [this](auto &&req, auto &&res) { handle_get_mount(req, res); }); | ||||
|  | ||||
|   server->Get("/api/v1/mount_location", [this](auto &&req, auto &&res) { | ||||
|     handle_get_mount_location(req, res); | ||||
|   }); | ||||
|  | ||||
|   server->Get("/api/v1/mount_list", [this](auto && /* req */, auto &&res) { | ||||
|     handle_get_mount_list(res); | ||||
|   }); | ||||
|  | ||||
|   server->Get("/api/v1/mount_status", [this](auto &&req, auto &&res) { | ||||
|     handle_get_mount_status(req, res); | ||||
|   }); | ||||
|  | ||||
|   server->Get("/api/v1/nonce", | ||||
|               [this](auto && /* req */, auto &&res) { handle_get_nonce(res); }); | ||||
|  | ||||
|   server->Get("/api/v1/settings", [this](auto && /* req */, auto &&res) { | ||||
|     handle_get_settings(res); | ||||
|   }); | ||||
|  | ||||
|   server->Post("/api/v1/add_mount", [this](auto &&req, auto &&res) { | ||||
|     handle_post_add_mount(req, res); | ||||
|   }); | ||||
|  | ||||
|   server->Post("/api/v1/mount", | ||||
|                [this](auto &&req, auto &&res) { handle_post_mount(req, res); }); | ||||
|  | ||||
|   server->Put("/api/v1/mount_location", [this](auto &&req, auto &&res) { | ||||
|     handle_put_mount_location(req, res); | ||||
|   }); | ||||
|  | ||||
|   server->Put("/api/v1/set_value_by_name", [this](auto &&req, auto &&res) { | ||||
|     handle_put_set_value_by_name(req, res); | ||||
|   }); | ||||
|  | ||||
|   server->Put("/api/v1/settings", [this](auto &&req, auto &&res) { | ||||
|     handle_put_settings(req, res); | ||||
|   }); | ||||
|  | ||||
|   static std::atomic<httplib::Server *> this_server{server_}; | ||||
|   static const auto quit_handler = [](int /* sig */) { | ||||
|     auto *ptr = this_server.load(); | ||||
|     if (ptr == nullptr) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     this_server = nullptr; | ||||
|     ptr->stop(); | ||||
|   }; | ||||
|  | ||||
|   std::signal(SIGINT, quit_handler); | ||||
| #if !defined(_WIN32) | ||||
|   std::signal(SIGQUIT, quit_handler); | ||||
| #endif // !defined(_WIN32) | ||||
|   std::signal(SIGTERM, quit_handler); | ||||
|  | ||||
| #if defined(_WIN32) | ||||
|   system(fmt::format( | ||||
|              R"(start "Repertory Management Portal" "http://127.0.0.1:{}/ui")", | ||||
|              config_->get_api_port()) | ||||
|              .c_str()); | ||||
| #elif defined(__linux__) | ||||
|   system(fmt::format(R"(xdg-open "http://127.0.0.1:{}/ui")", | ||||
|                      config_->get_api_port()) | ||||
|              .c_str()); | ||||
| #else // error | ||||
|   build fails here | ||||
| #endif | ||||
|  | ||||
|   std::uint16_t port{}; | ||||
|   if (not utils::get_next_available_port(config_->get_api_port(), port)) { | ||||
|     fmt::println("failed to detect if port is available|{}", | ||||
|                  config_->get_api_port()); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (port != config_->get_api_port()) { | ||||
|     fmt::println("failed to listen on port|{}|next available|{}", | ||||
|                  config_->get_api_port(), port); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   event_system::instance().start(); | ||||
|  | ||||
|   nonce_thread_ = | ||||
|       std::make_unique<std::thread>([this]() { removed_expired_nonces(); }); | ||||
|  | ||||
|   server_->listen("127.0.0.1", config_->get_api_port()); | ||||
|   if (this_server != nullptr) { | ||||
|     this_server = nullptr; | ||||
|     server_->stop(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| handlers::~handlers() { | ||||
|   if (nonce_thread_) { | ||||
|     stop_requested = true; | ||||
|  | ||||
|     unique_mutex_lock lock(nonce_mtx_); | ||||
|     nonce_notify_.notify_all(); | ||||
|     lock.unlock(); | ||||
|  | ||||
|     nonce_thread_->join(); | ||||
|     nonce_thread_.reset(); | ||||
|   } | ||||
|  | ||||
|   event_system::instance().stop(); | ||||
| } | ||||
|  | ||||
| auto handlers::data_directory_exists(provider_type prov, | ||||
|                                      std::string_view name) const -> bool { | ||||
|   auto data_dir = utils::path::combine(app_config::get_root_data_directory(), | ||||
|                                        { | ||||
|                                            app_config::get_provider_name(prov), | ||||
|                                            name, | ||||
|                                        }); | ||||
|   auto ret = utils::file::directory{data_dir}.exists(); | ||||
|   if (ret) { | ||||
|     return ret; | ||||
|   } | ||||
|  | ||||
|   unique_mutex_lock lock(mtx_); | ||||
|   mtx_lookup_.erase( | ||||
|       fmt::format("{}-{}", name, app_config::get_provider_name(prov))); | ||||
|   lock.unlock(); | ||||
|   return ret; | ||||
| } | ||||
|  | ||||
| void handlers::handle_put_mount_location(const httplib::Request &req, | ||||
|                                          httplib::Response &res) const { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   auto prov = provider_type_from_string(req.get_param_value("type")); | ||||
|   auto name = req.get_param_value("name"); | ||||
|   auto location = req.get_param_value("location"); | ||||
|  | ||||
|   if (not data_directory_exists(prov, name)) { | ||||
|     res.status = http_error_codes::not_found; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   config_->set_mount_location(prov, name, location); | ||||
|   res.status = http_error_codes::ok; | ||||
| } | ||||
|  | ||||
| void handlers::handle_get_available_locations(httplib::Response &res) { | ||||
| #if defined(_WIN32) | ||||
|   constexpr const std::array<std::string_view, 26U> letters{ | ||||
|       "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:", | ||||
|   }; | ||||
|  | ||||
|   auto available = std::accumulate( | ||||
|       letters.begin(), letters.end(), std::vector<std::string_view>(), | ||||
|       [](auto &&vec, auto &&letter) -> std::vector<std::string_view> { | ||||
|         if (utils::file::directory{utils::path::combine(letter, {"\\"})} | ||||
|                 .exists()) { | ||||
|           return vec; | ||||
|         } | ||||
|  | ||||
|         vec.emplace_back(letter); | ||||
|         return vec; | ||||
|       }); | ||||
|  | ||||
|   res.set_content(nlohmann::json(available).dump(), "application/json"); | ||||
| #else  // !defined(_WIN32) | ||||
|   res.set_content(nlohmann::json(std::vector<std::string_view>()).dump(), | ||||
|                   "application/json"); | ||||
| #endif // defined(_WIN32) | ||||
|  | ||||
|   res.status = http_error_codes::ok; | ||||
| } | ||||
|  | ||||
| void handlers::handle_get_mount(const httplib::Request &req, | ||||
|                                 httplib::Response &res) const { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   auto prov = provider_type_from_string(req.get_param_value("type")); | ||||
|   auto name = req.get_param_value("name"); | ||||
|  | ||||
|   if (not data_directory_exists(prov, name)) { | ||||
|     res.status = http_error_codes::not_found; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   auto lines = launch_process(prov, name, {"-dc"}); | ||||
|  | ||||
|   if (lines.at(0U) != "0") { | ||||
|     throw utils::error::create_exception(function_name, { | ||||
|                                                             "command failed", | ||||
|                                                             lines.at(0U), | ||||
|                                                         }); | ||||
|   } | ||||
|  | ||||
|   lines.erase(lines.begin()); | ||||
|  | ||||
|   auto result = nlohmann::json::parse(utils::string::join(lines, '\n')); | ||||
|   clean_json_config(prov, result); | ||||
|  | ||||
|   res.set_content(result.dump(), "application/json"); | ||||
|   res.status = http_error_codes::ok; | ||||
| } | ||||
|  | ||||
| void handlers::handle_get_mount_list(httplib::Response &res) const { | ||||
|   auto data_dir = utils::file::directory{app_config::get_root_data_directory()}; | ||||
|  | ||||
|   nlohmann::json result; | ||||
|   const auto process_dir = [&data_dir, &result](std::string_view name) { | ||||
|     auto name_dir = data_dir.get_directory(name); | ||||
|     if (not name_dir) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     for (const auto &dir : name_dir->get_directories()) { | ||||
|       if (not dir->get_file("config.json")) { | ||||
|         continue; | ||||
|       } | ||||
|  | ||||
|       result[name].emplace_back( | ||||
|           utils::path::strip_to_file_name(dir->get_path())); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   process_dir("encrypt"); | ||||
|   process_dir("remote"); | ||||
|   process_dir("s3"); | ||||
|   process_dir("sia"); | ||||
|  | ||||
|   res.set_content(result.dump(), "application/json"); | ||||
|   res.status = http_error_codes::ok; | ||||
| } | ||||
|  | ||||
| void handlers::handle_get_mount_location(const httplib::Request &req, | ||||
|                                          httplib::Response &res) const { | ||||
|   auto name = req.get_param_value("name"); | ||||
|   auto prov = provider_type_from_string(req.get_param_value("type")); | ||||
|  | ||||
|   if (not data_directory_exists(prov, name)) { | ||||
|     res.status = http_error_codes::not_found; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   res.set_content( | ||||
|       nlohmann::json({ | ||||
|                          {"Location", config_->get_mount_location(prov, name)}, | ||||
|                      }) | ||||
|           .dump(), | ||||
|       "application/json"); | ||||
|   res.status = http_error_codes::ok; | ||||
| } | ||||
|  | ||||
| void handlers::handle_get_mount_status(const httplib::Request &req, | ||||
|                                        httplib::Response &res) const { | ||||
|   auto name = req.get_param_value("name"); | ||||
|   auto prov = provider_type_from_string(req.get_param_value("type")); | ||||
|  | ||||
|   if (not data_directory_exists(prov, name)) { | ||||
|     res.status = http_error_codes::not_found; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   auto lines = launch_process(prov, name, {"-status"}); | ||||
|  | ||||
|   auto result = nlohmann::json::parse(utils::string::join(lines, '\n')); | ||||
|   if (result.at("Location").get<std::string>().empty()) { | ||||
|     result.at("Location") = config_->get_mount_location(prov, name); | ||||
|   } else if (result.at("Active").get<bool>()) { | ||||
|     config_->set_mount_location(prov, name, | ||||
|                                 result.at("Location").get<std::string>()); | ||||
|   } | ||||
|  | ||||
|   res.set_content(result.dump(), "application/json"); | ||||
|   res.status = http_error_codes::ok; | ||||
| } | ||||
|  | ||||
| void handlers::handle_get_nonce(httplib::Response &res) { | ||||
|   mutex_lock lock(nonce_mtx_); | ||||
|  | ||||
|   nonce_data nonce{}; | ||||
|   nonce_lookup_[nonce.nonce] = nonce; | ||||
|  | ||||
|   nlohmann::json data({{"nonce", nonce.nonce}}); | ||||
|   res.set_content(data.dump(), "application/json"); | ||||
|   res.status = http_error_codes::ok; | ||||
| } | ||||
|  | ||||
| void handlers::handle_get_settings(httplib::Response &res) const { | ||||
|   auto settings = config_->to_json(); | ||||
|   settings[JSON_API_PASSWORD] = ""; | ||||
|   settings.erase(JSON_MOUNT_LOCATIONS); | ||||
|   res.set_content(settings.dump(), "application/json"); | ||||
|   res.status = http_error_codes::ok; | ||||
| } | ||||
|  | ||||
| void handlers::handle_post_add_mount(const httplib::Request &req, | ||||
|                                      httplib::Response &res) const { | ||||
|   auto name = req.get_param_value("name"); | ||||
|   auto prov = provider_type_from_string(req.get_param_value("type")); | ||||
|   if (data_directory_exists(prov, name)) { | ||||
|     res.status = http_error_codes::ok; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   auto cfg = nlohmann::json::parse(req.get_param_value("config")); | ||||
|  | ||||
|   std::map<std::string, std::string> values{}; | ||||
|   for (const auto &[key, value] : cfg.items()) { | ||||
|     if (value.is_object()) { | ||||
|       for (const auto &[key2, value2] : value.items()) { | ||||
|         auto sub_key = fmt::format("{}.{}", key, key2); | ||||
|         auto skip{false}; | ||||
|         auto decrypted = decrypt_value( | ||||
|             config_, sub_key, value2.template get<std::string>(), skip); | ||||
|         if (skip) { | ||||
|           continue; | ||||
|         } | ||||
|         values[sub_key] = decrypted; | ||||
|       } | ||||
|  | ||||
|       continue; | ||||
|     } | ||||
|  | ||||
|     auto skip{false}; | ||||
|     auto decrypted = | ||||
|         decrypt_value(config_, key, value.template get<std::string>(), skip); | ||||
|     if (skip) { | ||||
|       continue; | ||||
|     } | ||||
|     values[key] = decrypted; | ||||
|   } | ||||
|  | ||||
|   launch_process(prov, name, {"-gc"}); | ||||
|   for (auto &[key, value] : values) { | ||||
|     set_key_value(prov, name, key, value); | ||||
|   } | ||||
|  | ||||
|   res.status = http_error_codes::ok; | ||||
| } | ||||
|  | ||||
| void handlers::handle_post_mount(const httplib::Request &req, | ||||
|                                  httplib::Response &res) { | ||||
|   auto name = req.get_param_value("name"); | ||||
|   auto prov = provider_type_from_string(req.get_param_value("type")); | ||||
|  | ||||
|   if (not data_directory_exists(prov, name)) { | ||||
|     res.status = http_error_codes::not_found; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   auto location = utils::path::absolute(req.get_param_value("location")); | ||||
|   auto unmount = utils::string::to_bool(req.get_param_value("unmount")); | ||||
|  | ||||
|   if (unmount) { | ||||
|     launch_process(prov, name, {"-unmount"}); | ||||
|   } else { | ||||
| #if defined(_WIN32) | ||||
|     if (utils::file::directory{location}.exists()) { | ||||
| #else  // !defined(_WIN32) | ||||
|     if (not utils::file::directory{location}.exists()) { | ||||
| #endif // defined(_WIN32) | ||||
|       config_->set_mount_location(prov, name, ""); | ||||
|       res.status = http_error_codes::internal_error; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     config_->set_mount_location(prov, name, location); | ||||
|  | ||||
|     static std::mutex mount_mtx; | ||||
|     mutex_lock lock(mount_mtx); | ||||
|  | ||||
|     launch_process(prov, name, {location}, true); | ||||
|     launch_process(prov, name, {"-status"}); | ||||
|   } | ||||
|  | ||||
|   res.status = http_error_codes::ok; | ||||
| } | ||||
|  | ||||
| void handlers::handle_put_set_value_by_name(const httplib::Request &req, | ||||
|                                             httplib::Response &res) const { | ||||
|   auto name = req.get_param_value("name"); | ||||
|   auto prov = provider_type_from_string(req.get_param_value("type")); | ||||
|  | ||||
|   if (not data_directory_exists(prov, name)) { | ||||
|     res.status = http_error_codes::not_found; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   auto key = req.get_param_value("key"); | ||||
|   auto value = req.get_param_value("value"); | ||||
|  | ||||
|   auto skip{false}; | ||||
|   value = decrypt_value(config_, key, value, skip); | ||||
|   if (not skip) { | ||||
|     set_key_value(prov, name, key, value); | ||||
|   } | ||||
|  | ||||
|   res.status = http_error_codes::ok; | ||||
| } | ||||
|  | ||||
| void handlers::handle_put_settings(const httplib::Request &req, | ||||
|                                    httplib::Response &res) const { | ||||
|   auto data = nlohmann::json::parse(req.get_param_value("data")); | ||||
|  | ||||
|   if (data.contains(JSON_API_PASSWORD)) { | ||||
|     auto password = decrypt(data.at(JSON_API_PASSWORD).get<std::string>(), | ||||
|                             config_->get_api_password()); | ||||
|     if (not password.empty()) { | ||||
|       config_->set_api_password(password); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (data.contains(JSON_API_PORT)) { | ||||
|     config_->set_api_port( | ||||
|         utils::string::to_uint16(data.at(JSON_API_PORT).get<std::string>())); | ||||
|   } | ||||
|  | ||||
|   if (data.contains(JSON_API_USER)) { | ||||
|     config_->set_api_user(data.at(JSON_API_USER).get<std::string>()); | ||||
|   } | ||||
|  | ||||
|   res.status = http_error_codes::ok; | ||||
| } | ||||
|  | ||||
| auto handlers::launch_process(provider_type prov, std::string_view name, | ||||
|                               std::vector<std::string> args, | ||||
|                               bool background) const | ||||
|     -> std::vector<std::string> { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   switch (prov) { | ||||
|   case provider_type::encrypt: | ||||
|     args.insert(args.begin(), "-en"); | ||||
|     args.insert(std::next(args.begin()), "-na"); | ||||
|     args.insert(std::next(args.begin(), 2U), std::string{name}); | ||||
|     break; | ||||
|  | ||||
|   case provider_type::remote: { | ||||
|     auto parts = utils::string::split(name, '_', false); | ||||
|     args.insert(args.begin(), "-rm"); | ||||
|     args.insert(std::next(args.begin()), | ||||
|                 fmt::format("{}:{}", parts.at(0U), parts.at(1U))); | ||||
|   } break; | ||||
|  | ||||
|   case provider_type::s3: | ||||
|     args.insert(args.begin(), "-s3"); | ||||
|     args.insert(std::next(args.begin()), "-na"); | ||||
|     args.insert(std::next(args.begin(), 2U), std::string{name}); | ||||
|     break; | ||||
|  | ||||
|   case provider_type::sia: | ||||
|     args.insert(args.begin(), "-na"); | ||||
|     args.insert(std::next(args.begin()), std::string{name}); | ||||
|     break; | ||||
|  | ||||
|   default: | ||||
|     throw utils::error::create_exception(function_name, | ||||
|                                          { | ||||
|                                              "provider is not supported", | ||||
|                                              provider_type_to_string(prov), | ||||
|                                              name, | ||||
|                                          }); | ||||
|   } | ||||
|  | ||||
|   unique_mutex_lock lock(mtx_); | ||||
|   auto &inst_mtx = mtx_lookup_[fmt::format( | ||||
|       "{}-{}", name, app_config::get_provider_name(prov))]; | ||||
|   lock.unlock(); | ||||
|  | ||||
|   recur_mutex_lock inst_lock(inst_mtx); | ||||
|   if (background) { | ||||
| #if defined(_WIN32) | ||||
|     std::array<char, MAX_PATH + 1U> path{}; | ||||
|     ::GetSystemDirectoryA(path.data(), path.size()); | ||||
|  | ||||
|     args.insert(args.begin(), utils::path::combine(path.data(), {"cmd.exe"})); | ||||
|     args.insert(std::next(args.begin()), "/c"); | ||||
|     args.insert(std::next(args.begin(), 2U), "start"); | ||||
|     args.insert(std::next(args.begin(), 3U), ""); | ||||
|     args.insert(std::next(args.begin(), 4U), "/MIN"); | ||||
|     args.insert(std::next(args.begin(), 5U), repertory_binary_); | ||||
| #else  // !defined(_WIN32) | ||||
|     args.insert(args.begin(), repertory_binary_); | ||||
| #endif // defined(_WIN32) | ||||
|  | ||||
|     std::vector<const char *> exec_args; | ||||
|     exec_args.reserve(args.size() + 1U); | ||||
|     for (const auto &arg : args) { | ||||
|       exec_args.push_back(arg.c_str()); | ||||
|     } | ||||
|     exec_args.push_back(nullptr); | ||||
|  | ||||
| #if defined(_WIN32) | ||||
|     _spawnv(_P_DETACH, exec_args.at(0U), | ||||
|             const_cast<char *const *>(exec_args.data())); | ||||
| #else  // !defined(_WIN32) | ||||
|     auto pid = fork(); | ||||
|     if (pid == 0) { | ||||
|       setsid(); | ||||
|       chdir("/"); | ||||
|       close(STDIN_FILENO); | ||||
|       close(STDOUT_FILENO); | ||||
|       close(STDERR_FILENO); | ||||
|       open("/dev/null", O_RDONLY); | ||||
|       open("/dev/null", O_WRONLY); | ||||
|       open("/dev/null", O_WRONLY); | ||||
|  | ||||
|       execvp(exec_args.at(0U), const_cast<char *const *>(exec_args.data())); | ||||
|     } else { | ||||
|       signal(SIGCHLD, SIG_IGN); | ||||
|     } | ||||
| #endif // defined(_WIN32) | ||||
|     return {}; | ||||
|   } | ||||
|  | ||||
|   boost::process::ipstream out; | ||||
|   boost::process::child proc(repertory_binary_, boost::process::args(args), | ||||
|                              boost::process::std_out > out); | ||||
|  | ||||
|   std::string data; | ||||
|   std::string line; | ||||
|   while (out && std::getline(out, line)) { | ||||
|     data += line + "\n"; | ||||
|   } | ||||
|  | ||||
|   return utils::string::split(utils::string::replace(data, "\r", ""), '\n', | ||||
|                               false); | ||||
| } | ||||
|  | ||||
| void handlers::removed_expired_nonces() { | ||||
|   unique_mutex_lock lock(nonce_mtx_); | ||||
|   lock.unlock(); | ||||
|  | ||||
|   while (not stop_requested) { | ||||
|     lock.lock(); | ||||
|     auto nonces = nonce_lookup_; | ||||
|     lock.unlock(); | ||||
|  | ||||
|     for (const auto &[key, value] : nonces) { | ||||
|       if (std::chrono::duration_cast<std::chrono::seconds>( | ||||
|               std::chrono::system_clock::now() - value.creation) | ||||
|               .count() >= nonce_timeout) { | ||||
|         lock.lock(); | ||||
|         nonce_lookup_.erase(key); | ||||
|         lock.unlock(); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     if (stop_requested) { | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     lock.lock(); | ||||
|     if (stop_requested) { | ||||
|       break; | ||||
|     } | ||||
|     nonce_notify_.wait_for(lock, std::chrono::seconds(1U)); | ||||
|     lock.unlock(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void handlers::set_key_value(provider_type prov, std::string_view name, | ||||
|                              std::string_view key, | ||||
|                              std::string_view value) const { | ||||
|   std::vector<std::string> args; | ||||
|   args.emplace_back("-set"); | ||||
|   args.emplace_back(key); | ||||
|   args.emplace_back(value); | ||||
|   launch_process(prov, name, args, false); | ||||
| } | ||||
| } // namespace repertory::ui | ||||
							
								
								
									
										202
									
								
								repertory/repertory/src/ui/mgmt_app_config.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								repertory/repertory/src/ui/mgmt_app_config.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
| /* | ||||
|   Copyright <2018-2025> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #include "ui/mgmt_app_config.hpp" | ||||
|  | ||||
| #include "app_config.hpp" | ||||
| #include "utils/error_utils.hpp" | ||||
| #include "utils/file.hpp" | ||||
| #include "utils/path.hpp" | ||||
| #include "utils/unix.hpp" | ||||
| #include "utils/windows.hpp" | ||||
|  | ||||
| namespace { | ||||
| [[nodiscard]] auto from_json(const nlohmann::json &json) | ||||
|     -> std::unordered_map<repertory::provider_type, | ||||
|                           std::unordered_map<std::string, std::string>> { | ||||
|   std::unordered_map<repertory::provider_type, | ||||
|                      std::unordered_map<std::string, std::string>> | ||||
|       map_of_maps{ | ||||
|           {repertory::provider_type::encrypt, nlohmann::json::object()}, | ||||
|           {repertory::provider_type::remote, nlohmann::json::object()}, | ||||
|           {repertory::provider_type::s3, nlohmann::json::object()}, | ||||
|           {repertory::provider_type::sia, nlohmann::json::object()}, | ||||
|       }; | ||||
|  | ||||
|   if (json.is_null() || json.empty()) { | ||||
|     return map_of_maps; | ||||
|   } | ||||
|  | ||||
|   for (auto &[prov, map] : map_of_maps) { | ||||
|     auto prov_str = repertory::provider_type_to_string(prov); | ||||
|     if (!json.contains(prov_str)) { | ||||
|       continue; | ||||
|     } | ||||
|  | ||||
|     for (const auto &[key, value] : json.at(prov_str).items()) { | ||||
|       map[key] = value; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return map_of_maps; | ||||
| } | ||||
|  | ||||
| [[nodiscard]] auto map_to_json(const auto &map_of_maps) -> nlohmann::json { | ||||
|   auto json = nlohmann::json::object(); | ||||
|   for (const auto &[prov, map] : map_of_maps) { | ||||
|     for (const auto &[key, value] : map) { | ||||
|       json[repertory::provider_type_to_string(prov)][key] = value; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return json; | ||||
| } | ||||
| } // namespace | ||||
|  | ||||
| namespace repertory::ui { | ||||
| mgmt_app_config::mgmt_app_config() { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   auto config_file = | ||||
|       utils::path::combine(app_config::get_root_data_directory(), {"ui.json"}); | ||||
|  | ||||
|   try { | ||||
|     if (not utils::file::directory{app_config::get_root_data_directory()} | ||||
|                 .create_directory()) { | ||||
|       throw utils::error::create_exception( | ||||
|           function_name, { | ||||
|                              "failed to create directory", | ||||
|                              app_config::get_root_data_directory(), | ||||
|                          }); | ||||
|     } | ||||
|  | ||||
|     nlohmann::json data; | ||||
|     if (utils::file::read_json_file(config_file, data)) { | ||||
|       api_password_ = data.at(JSON_API_PASSWORD).get<std::string>(); | ||||
|       api_port_ = data.at(JSON_API_PORT).get<std::uint16_t>(); | ||||
|       api_user_ = data.at(JSON_API_USER).get<std::string>(); | ||||
|       locations_ = from_json(data.at(JSON_MOUNT_LOCATIONS)); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     utils::error::raise_error( | ||||
|         function_name, utils::get_last_error_code(), | ||||
|         fmt::format("failed to read file|{}", config_file)); | ||||
|     save(); | ||||
|   } catch (const std::exception &ex) { | ||||
|     utils::error::raise_error( | ||||
|         function_name, ex, fmt::format("failed to read file|{}", config_file)); | ||||
|   } | ||||
| } | ||||
|  | ||||
| auto mgmt_app_config::get_mount_location(provider_type prov, | ||||
|                                          std::string_view name) const | ||||
|     -> std::string { | ||||
|   recur_mutex_lock lock(mtx_); | ||||
|   if (locations_.contains(prov) && | ||||
|       locations_.at(prov).contains(std::string{name})) { | ||||
|     return locations_.at(prov).at(std::string{name}); | ||||
|   } | ||||
|  | ||||
|   return ""; | ||||
| } | ||||
|  | ||||
| void mgmt_app_config::save() const { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   auto config_file = | ||||
|       utils::path::combine(app_config::get_root_data_directory(), {"ui.json"}); | ||||
|  | ||||
|   try { | ||||
|     if (not utils::file::directory{app_config::get_root_data_directory()} | ||||
|                 .create_directory()) { | ||||
|       utils::error::raise_error( | ||||
|           function_name, fmt::format("failed to create directory|{}", | ||||
|                                      app_config::get_root_data_directory())); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (utils::file::write_json_file(config_file, to_json())) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     utils::error::raise_error( | ||||
|         function_name, utils::get_last_error_code(), | ||||
|         fmt::format("failed to save file|{}", config_file)); | ||||
|   } catch (const std::exception &ex) { | ||||
|     utils::error::raise_error( | ||||
|         function_name, ex, fmt::format("failed to save file|{}", config_file)); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void mgmt_app_config::set_api_password(std::string_view api_password) { | ||||
|   if (api_password_ == std::string{api_password}) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   api_password_ = std::string{api_password}; | ||||
|   save(); | ||||
| } | ||||
|  | ||||
| void mgmt_app_config::set_api_port(std::uint16_t api_port) { | ||||
|   if (api_port_ == api_port) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   api_port_ = api_port; | ||||
|   save(); | ||||
| } | ||||
|  | ||||
| void mgmt_app_config::set_api_user(std::string_view api_user) { | ||||
|   if (api_user_ == std::string{api_user}) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   api_user_ = std::string{api_user}; | ||||
|   save(); | ||||
| } | ||||
|  | ||||
| void mgmt_app_config::set_mount_location(provider_type prov, | ||||
|                                          std::string_view name, | ||||
|                                          std::string_view location) { | ||||
|   if (name.empty()) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   recur_mutex_lock lock(mtx_); | ||||
|   if (locations_[prov][std::string{name}] == std::string{location}) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   locations_[prov][std::string{name}] = std::string{location}; | ||||
|  | ||||
|   save(); | ||||
| } | ||||
|  | ||||
| auto mgmt_app_config::to_json() const -> nlohmann::json { | ||||
|   nlohmann::json data; | ||||
|   data[JSON_API_PASSWORD] = api_password_; | ||||
|   data[JSON_API_PORT] = api_port_; | ||||
|   data[JSON_API_USER] = api_user_; | ||||
|   data[JSON_MOUNT_LOCATIONS] = map_to_json(locations_); | ||||
|   return data; | ||||
| } | ||||
| } // namespace repertory::ui | ||||
| @@ -128,7 +128,7 @@ std::atomic<std::uint64_t> app_config_test::idx{0U}; | ||||
|  | ||||
| static void defaults_tests(const json &json_data, provider_type prov) { | ||||
|   json json_defaults = { | ||||
|       {JSON_API_PORT, app_config::default_rpc_port(prov)}, | ||||
|       {JSON_API_PORT, app_config::default_rpc_port()}, | ||||
|       {JSON_API_USER, std::string{REPERTORY}}, | ||||
|       {JSON_DOWNLOAD_TIMEOUT_SECS, default_download_timeout_secs}, | ||||
|       {JSON_DATABASE_TYPE, database_type::rocksdb}, | ||||
| @@ -185,9 +185,9 @@ static void defaults_tests(const json &json_data, provider_type prov) { | ||||
|   } | ||||
|  | ||||
|   fmt::println("testing default|{}-{}", app_config::get_provider_name(prov), | ||||
|                JSON_API_AUTH); | ||||
|   ASSERT_EQ(std::size_t(default_api_auth_size), | ||||
|             json_data.at(JSON_API_AUTH).get<std::string>().size()); | ||||
|                JSON_API_PASSWORD); | ||||
|   ASSERT_EQ(std::size_t(default_api_password_size), | ||||
|             json_data.at(JSON_API_PASSWORD).get<std::string>().size()); | ||||
|   for (const auto &[key, value] : json_defaults.items()) { | ||||
|     fmt::println("testing default|{}-{}", app_config::get_provider_name(prov), | ||||
|                  key); | ||||
| @@ -216,11 +216,11 @@ static void common_tests(app_config &config, provider_type prov) { | ||||
|   ASSERT_EQ(config.get_provider_type(), prov); | ||||
|  | ||||
|   std::map<std::string_view, std::function<void(app_config &)>> methods{ | ||||
|       {JSON_API_AUTH, | ||||
|       {JSON_API_PASSWORD, | ||||
|        [](app_config &cfg) { | ||||
|          test_getter_setter(cfg, &app_config::get_api_auth, | ||||
|                             &app_config::set_api_auth, "", "auth", | ||||
|                             JSON_API_AUTH, "auth2"); | ||||
|          test_getter_setter(cfg, &app_config::get_api_password, | ||||
|                             &app_config::set_api_password, "", "auth", | ||||
|                             JSON_API_PASSWORD, "auth2"); | ||||
|        }}, | ||||
|       {JSON_API_PORT, | ||||
|        [](app_config &cfg) { | ||||
|   | ||||
							
								
								
									
										198
									
								
								repertory/repertory_test/src/clean_json_test.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								repertory/repertory_test/src/clean_json_test.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,198 @@ | ||||
| /* | ||||
|   Copyright <2018-2025> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #include "test_common.hpp" | ||||
|  | ||||
| #include "app_config.hpp" | ||||
| #include "types/repertory.hpp" | ||||
|  | ||||
| namespace repertory { | ||||
| TEST(clean_json_test, can_clean_values) { | ||||
|   auto result = clean_json_value(JSON_API_PASSWORD, "moose"); | ||||
|   EXPECT_TRUE(result.empty()); | ||||
|  | ||||
|   result = clean_json_value( | ||||
|       fmt::format("{}.{}", JSON_ENCRYPT_CONFIG, JSON_ENCRYPTION_TOKEN), | ||||
|       "moose"); | ||||
|   EXPECT_TRUE(result.empty()); | ||||
|  | ||||
|   result = clean_json_value( | ||||
|       fmt::format("{}.{}", JSON_HOST_CONFIG, JSON_API_PASSWORD), "moose"); | ||||
|   EXPECT_TRUE(result.empty()); | ||||
|  | ||||
|   result = clean_json_value( | ||||
|       fmt::format("{}.{}", JSON_REMOTE_CONFIG, JSON_ENCRYPTION_TOKEN), "moose"); | ||||
|   EXPECT_TRUE(result.empty()); | ||||
|  | ||||
|   result = clean_json_value( | ||||
|       fmt::format("{}.{}", JSON_REMOTE_MOUNT, JSON_ENCRYPTION_TOKEN), "moose"); | ||||
|   EXPECT_TRUE(result.empty()); | ||||
|  | ||||
|   result = clean_json_value( | ||||
|       fmt::format("{}.{}", JSON_S3_CONFIG, JSON_ENCRYPTION_TOKEN), "moose"); | ||||
|   EXPECT_TRUE(result.empty()); | ||||
|  | ||||
|   result = clean_json_value( | ||||
|       fmt::format("{}.{}", JSON_S3_CONFIG, JSON_SECRET_KEY), "moose"); | ||||
|   EXPECT_TRUE(result.empty()); | ||||
| } | ||||
|  | ||||
| TEST(clean_json_test, can_clean_encrypt_config) { | ||||
|   auto dir = | ||||
|       utils::path::combine(test::get_test_output_dir(), { | ||||
|                                                             "clean_json_test", | ||||
|                                                             "encrypt", | ||||
|                                                         }); | ||||
|   app_config cfg(provider_type::encrypt, dir); | ||||
|   cfg.set_value_by_name(JSON_API_PASSWORD, "moose"); | ||||
|   cfg.set_value_by_name( | ||||
|       fmt::format("{}.{}", JSON_ENCRYPT_CONFIG, JSON_ENCRYPTION_TOKEN), | ||||
|       "moose"); | ||||
|   cfg.set_value_by_name( | ||||
|       fmt::format("{}.{}", JSON_REMOTE_MOUNT, JSON_ENCRYPTION_TOKEN), "moose"); | ||||
|  | ||||
|   auto data = cfg.get_json(); | ||||
|   EXPECT_FALSE(data.at(JSON_API_PASSWORD).get<std::string>().empty()); | ||||
|   EXPECT_FALSE(data.at(JSON_ENCRYPT_CONFIG) | ||||
|                    .at(JSON_ENCRYPTION_TOKEN) | ||||
|                    .get<std::string>() | ||||
|                    .empty()); | ||||
|   EXPECT_FALSE(data.at(JSON_REMOTE_MOUNT) | ||||
|                    .at(JSON_ENCRYPTION_TOKEN) | ||||
|                    .get<std::string>() | ||||
|                    .empty()); | ||||
|  | ||||
|   clean_json_config(cfg.get_provider_type(), data); | ||||
|   EXPECT_TRUE(data.at(JSON_API_PASSWORD).get<std::string>().empty()); | ||||
|   EXPECT_TRUE(data.at(JSON_ENCRYPT_CONFIG) | ||||
|                   .at(JSON_ENCRYPTION_TOKEN) | ||||
|                   .get<std::string>() | ||||
|                   .empty()); | ||||
|   EXPECT_TRUE(data.at(JSON_REMOTE_MOUNT) | ||||
|                   .at(JSON_ENCRYPTION_TOKEN) | ||||
|                   .get<std::string>() | ||||
|                   .empty()); | ||||
| } | ||||
|  | ||||
| TEST(clean_json_test, can_clean_remote_config) { | ||||
|   auto dir = | ||||
|       utils::path::combine(test::get_test_output_dir(), { | ||||
|                                                             "clean_json_test", | ||||
|                                                             "remote", | ||||
|                                                         }); | ||||
|   app_config cfg(provider_type::remote, dir); | ||||
|   cfg.set_value_by_name(JSON_API_PASSWORD, "moose"); | ||||
|   cfg.set_value_by_name( | ||||
|       fmt::format("{}.{}", JSON_REMOTE_CONFIG, JSON_ENCRYPTION_TOKEN), "moose"); | ||||
|  | ||||
|   auto data = cfg.get_json(); | ||||
|   EXPECT_FALSE(data.at(JSON_API_PASSWORD).get<std::string>().empty()); | ||||
|   EXPECT_FALSE(data.at(JSON_REMOTE_CONFIG) | ||||
|                    .at(JSON_ENCRYPTION_TOKEN) | ||||
|                    .get<std::string>() | ||||
|                    .empty()); | ||||
|  | ||||
|   clean_json_config(cfg.get_provider_type(), data); | ||||
|   EXPECT_TRUE(data.at(JSON_API_PASSWORD).get<std::string>().empty()); | ||||
|   EXPECT_TRUE(data.at(JSON_REMOTE_CONFIG) | ||||
|                   .at(JSON_ENCRYPTION_TOKEN) | ||||
|                   .get<std::string>() | ||||
|                   .empty()); | ||||
| } | ||||
|  | ||||
| TEST(clean_json_test, can_clean_s3_config) { | ||||
|   auto dir = | ||||
|       utils::path::combine(test::get_test_output_dir(), { | ||||
|                                                             "clean_json_test", | ||||
|                                                             "s3", | ||||
|                                                         }); | ||||
|   app_config cfg(provider_type::s3, dir); | ||||
|   cfg.set_value_by_name(JSON_API_PASSWORD, "moose"); | ||||
|   cfg.set_value_by_name( | ||||
|       fmt::format("{}.{}", JSON_REMOTE_MOUNT, JSON_ENCRYPTION_TOKEN), "moose"); | ||||
|   cfg.set_value_by_name( | ||||
|       fmt::format("{}.{}", JSON_S3_CONFIG, JSON_ENCRYPTION_TOKEN), "moose"); | ||||
|   cfg.set_value_by_name(fmt::format("{}.{}", JSON_S3_CONFIG, JSON_SECRET_KEY), | ||||
|                         "moose"); | ||||
|  | ||||
|   auto data = cfg.get_json(); | ||||
|   EXPECT_FALSE(data.at(JSON_API_PASSWORD).get<std::string>().empty()); | ||||
|   EXPECT_FALSE(data.at(JSON_REMOTE_MOUNT) | ||||
|                    .at(JSON_ENCRYPTION_TOKEN) | ||||
|                    .get<std::string>() | ||||
|                    .empty()); | ||||
|   EXPECT_FALSE(data.at(JSON_S3_CONFIG) | ||||
|                    .at(JSON_ENCRYPTION_TOKEN) | ||||
|                    .get<std::string>() | ||||
|                    .empty()); | ||||
|   EXPECT_FALSE( | ||||
|       data.at(JSON_S3_CONFIG).at(JSON_SECRET_KEY).get<std::string>().empty()); | ||||
|  | ||||
|   clean_json_config(cfg.get_provider_type(), data); | ||||
|   EXPECT_TRUE(data.at(JSON_API_PASSWORD).get<std::string>().empty()); | ||||
|   EXPECT_TRUE(data.at(JSON_REMOTE_MOUNT) | ||||
|                   .at(JSON_ENCRYPTION_TOKEN) | ||||
|                   .get<std::string>() | ||||
|                   .empty()); | ||||
|   EXPECT_TRUE(data.at(JSON_S3_CONFIG) | ||||
|                   .at(JSON_ENCRYPTION_TOKEN) | ||||
|                   .get<std::string>() | ||||
|                   .empty()); | ||||
|   EXPECT_TRUE( | ||||
|       data.at(JSON_S3_CONFIG).at(JSON_SECRET_KEY).get<std::string>().empty()); | ||||
| } | ||||
|  | ||||
| TEST(clean_json_test, can_clean_sia_config) { | ||||
|   auto dir = | ||||
|       utils::path::combine(test::get_test_output_dir(), { | ||||
|                                                             "clean_json_test", | ||||
|                                                             "sia", | ||||
|                                                         }); | ||||
|   app_config cfg(provider_type::sia, dir); | ||||
|   cfg.set_value_by_name(JSON_API_PASSWORD, "moose"); | ||||
|   cfg.set_value_by_name( | ||||
|       fmt::format("{}.{}", JSON_HOST_CONFIG, JSON_API_PASSWORD), "moose"); | ||||
|   cfg.set_value_by_name( | ||||
|       fmt::format("{}.{}", JSON_REMOTE_MOUNT, JSON_ENCRYPTION_TOKEN), "moose"); | ||||
|  | ||||
|   auto data = cfg.get_json(); | ||||
|   EXPECT_FALSE(data.at(JSON_API_PASSWORD).get<std::string>().empty()); | ||||
|   EXPECT_FALSE(data.at(JSON_HOST_CONFIG) | ||||
|                    .at(JSON_API_PASSWORD) | ||||
|                    .get<std::string>() | ||||
|                    .empty()); | ||||
|   EXPECT_FALSE(data.at(JSON_REMOTE_MOUNT) | ||||
|                    .at(JSON_ENCRYPTION_TOKEN) | ||||
|                    .get<std::string>() | ||||
|                    .empty()); | ||||
|  | ||||
|   clean_json_config(cfg.get_provider_type(), data); | ||||
|   EXPECT_TRUE(data.at(JSON_API_PASSWORD).get<std::string>().empty()); | ||||
|   EXPECT_TRUE(data.at(JSON_HOST_CONFIG) | ||||
|                   .at(JSON_API_PASSWORD) | ||||
|                   .get<std::string>() | ||||
|                   .empty()); | ||||
|   EXPECT_TRUE(data.at(JSON_REMOTE_MOUNT) | ||||
|                   .at(JSON_ENCRYPTION_TOKEN) | ||||
|                   .get<std::string>() | ||||
|                   .empty()); | ||||
| } | ||||
| } // namespace repertory | ||||
| @@ -62,13 +62,16 @@ TEST(lock_data_test, set_and_unset_mount_state) { | ||||
|  | ||||
|   json mount_state; | ||||
|   EXPECT_TRUE(l.get_mount_state(mount_state)); | ||||
|  | ||||
|   EXPECT_STREQ(R"({"Active":true,"Location":"C:","PID":99})", | ||||
|                mount_state["Sia1"].dump().c_str()); | ||||
|                mount_state.dump().c_str()); | ||||
|  | ||||
|   EXPECT_TRUE(l2.get_mount_state(mount_state)); | ||||
|   EXPECT_STREQ(R"({"Active":true,"Location":"D:","PID":97})", | ||||
|                mount_state["Remote1"].dump().c_str()); | ||||
|                mount_state.dump().c_str()); | ||||
|  | ||||
|   EXPECT_TRUE(l3.get_mount_state(mount_state)); | ||||
|   EXPECT_STREQ(R"({"Active":true,"Location":"E:","PID":96})", | ||||
|                mount_state["Remote2"].dump().c_str()); | ||||
|                mount_state.dump().c_str()); | ||||
|  | ||||
|   EXPECT_TRUE(l.set_mount_state(false, "C:", 99)); | ||||
|   EXPECT_TRUE(l2.set_mount_state(false, "D:", 98)); | ||||
| @@ -76,11 +79,15 @@ TEST(lock_data_test, set_and_unset_mount_state) { | ||||
|  | ||||
|   EXPECT_TRUE(l.get_mount_state(mount_state)); | ||||
|   EXPECT_STREQ(R"({"Active":false,"Location":"","PID":-1})", | ||||
|                mount_state["Sia1"].dump().c_str()); | ||||
|                mount_state.dump().c_str()); | ||||
|  | ||||
|   EXPECT_TRUE(l2.get_mount_state(mount_state)); | ||||
|   EXPECT_STREQ(R"({"Active":false,"Location":"","PID":-1})", | ||||
|                mount_state["Remote1"].dump().c_str()); | ||||
|                mount_state.dump().c_str()); | ||||
|  | ||||
|   EXPECT_TRUE(l3.get_mount_state(mount_state)); | ||||
|   EXPECT_STREQ(R"({"Active":false,"Location":"","PID":-1})", | ||||
|                mount_state["Remote2"].dump().c_str()); | ||||
|                mount_state.dump().c_str()); | ||||
| } | ||||
| #else | ||||
| TEST(lock_data_test, set_and_unset_mount_state) { | ||||
| @@ -91,14 +98,13 @@ TEST(lock_data_test, set_and_unset_mount_state) { | ||||
|   EXPECT_TRUE(l.get_mount_state(mount_state)); | ||||
|  | ||||
|   EXPECT_STREQ(R"({"Active":true,"Location":"/mnt/1","PID":99})", | ||||
|                mount_state["Sia1"].dump().c_str()); | ||||
|                mount_state.dump().c_str()); | ||||
|  | ||||
|   EXPECT_TRUE(l.set_mount_state(false, "/mnt/1", 99)); | ||||
|  | ||||
|   EXPECT_TRUE(l.get_mount_state(mount_state)); | ||||
|  | ||||
|   EXPECT_STREQ(R"({"Active":false,"Location":"","PID":-1})", | ||||
|                mount_state["Sia1"].dump().c_str()); | ||||
|                mount_state.dump().c_str()); | ||||
| } | ||||
| #endif | ||||
| } // namespace repertory | ||||
|   | ||||
		Reference in New Issue
	
	Block a user