From f722dd4b84b9662c7892a4508ec514dc9ba15c04 Mon Sep 17 00:00:00 2001 From: "Scott E. Graves" Date: Mon, 15 Sep 2025 10:48:25 -0500 Subject: [PATCH] broken build - [refactor ui start/stop][prefer REPERTORY/REPERTORY_W] --- .../src/drives/fuse/fuse_base.cpp | 2 +- .../repertory/include/ui/mgmt_app_config.hpp | 4 +- .../ui/{handlers.hpp => ui_server.hpp} | 33 +- repertory/repertory/main.cpp | 80 ++- .../repertory/src/ui/mgmt_app_config.cpp | 16 +- .../src/ui/{handlers.cpp => ui_server.cpp} | 502 +++++++++--------- .../repertory_test/src/curl_comm_test.cpp | 8 +- 7 files changed, 355 insertions(+), 290 deletions(-) rename repertory/repertory/include/ui/{handlers.hpp => ui_server.hpp} (87%) rename repertory/repertory/src/ui/{handlers.cpp => ui_server.cpp} (71%) diff --git a/repertory/librepertory/src/drives/fuse/fuse_base.cpp b/repertory/librepertory/src/drives/fuse/fuse_base.cpp index 326b1e63..ccda30cb 100644 --- a/repertory/librepertory/src/drives/fuse/fuse_base.cpp +++ b/repertory/librepertory/src/drives/fuse/fuse_base.cpp @@ -447,7 +447,7 @@ auto fuse_base::mount([[maybe_unused]] std::vector orig_args, return -1; } - orig_args[0U] = utils::path::combine(".", {"repertory"}); + orig_args[0U] = utils::path::combine(".", {REPERTORY}); orig_args.insert(std::next(orig_args.begin()), "-f"); utils::plist_cfg cfg{}; diff --git a/repertory/repertory/include/ui/mgmt_app_config.hpp b/repertory/repertory/include/ui/mgmt_app_config.hpp index b6656f1e..3e7b4114 100644 --- a/repertory/repertory/include/ui/mgmt_app_config.hpp +++ b/repertory/repertory/include/ui/mgmt_app_config.hpp @@ -36,9 +36,9 @@ private: private: std::atomic animations_{true}; - utils::atomic api_password_{"repertory"}; + utils::atomic api_password_{REPERTORY}; std::atomic api_port_{default_ui_mgmt_port}; - utils::atomic api_user_{"repertory"}; + utils::atomic api_user_{REPERTORY}; std::atomic auto_start_{true}; std::unordered_map> diff --git a/repertory/repertory/include/ui/handlers.hpp b/repertory/repertory/include/ui/ui_server.hpp similarity index 87% rename from repertory/repertory/include/ui/handlers.hpp rename to repertory/repertory/include/ui/ui_server.hpp index 810028aa..0f11cd05 100644 --- a/repertory/repertory/include/ui/handlers.hpp +++ b/repertory/repertory/include/ui/ui_server.hpp @@ -19,16 +19,17 @@ 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_ +#ifndef REPERTORY_INCLUDE_UI_SERVER_HPP_ +#define REPERTORY_INCLUDE_UI_SERVER_HPP_ #include "events/consumers/console_consumer.hpp" +#include "events/consumers/logging_consumer.hpp" #include "utils/common.hpp" namespace repertory::ui { class mgmt_app_config; -class handlers final { +class ui_server final { private: static constexpr auto nonce_length{128U}; static constexpr auto nonce_timeout{15U}; @@ -44,21 +45,21 @@ private: }; public: - handlers(mgmt_app_config *config, httplib::Server *server); + ui_server(mgmt_app_config *config); - handlers() = delete; - handlers(const handlers &) = delete; - handlers(handlers &&) = delete; + ui_server() = delete; + ui_server(const ui_server &) = delete; + ui_server(ui_server &&) = delete; - ~handlers(); + ~ui_server(); - auto operator=(const handlers &) -> handlers & = delete; - auto operator=(handlers &&) -> handlers & = delete; + auto operator=(const ui_server &) -> ui_server & = delete; + auto operator=(ui_server &&) -> ui_server & = delete; private: mgmt_app_config *config_; + logging_consumer logging; std::string repertory_binary_; - httplib::Server *server_; private: console_consumer console; @@ -68,6 +69,7 @@ private: std::unordered_map nonce_lookup_; std::condition_variable nonce_notify_; std::unique_ptr nonce_thread_; + httplib::Server server_; stop_type stop_requested{false}; mutable std::mutex test_mtx_; @@ -127,12 +129,19 @@ private: [[nodiscard]] auto mount(provider_type prov, std::string_view name, const std::string &location) -> bool; + void notify_and_unlock(unique_mutex_lock &nonce_lock); + void removed_expired_nonces(); void set_key_value(provider_type prov, std::string_view name, std::string_view key, std::string_view value, std::optional data_dir = std::nullopt) const; + +public: + void start(); + + void stop(); }; } // namespace repertory::ui -#endif // REPERTORY_INCLUDE_UI_HANDLERS_HPP_ +#endif // REPERTORY_INCLUDE_UI_SERVER_HPP_ diff --git a/repertory/repertory/main.cpp b/repertory/repertory/main.cpp index 0459e846..5648ba94 100644 --- a/repertory/repertory/main.cpp +++ b/repertory/repertory/main.cpp @@ -26,8 +26,8 @@ #include "cli/actions.hpp" #include "initialize.hpp" #include "types/repertory.hpp" -#include "ui/handlers.hpp" #include "ui/mgmt_app_config.hpp" +#include "ui/ui_server.hpp" #include "utils/cli_utils.hpp" #include "utils/polling.hpp" @@ -80,35 +80,67 @@ auto main(int argc, char **argv) -> int { if (not utils::file::change_to_process_directory()) { ret = static_cast(exit_code::ui_failed); } else { - httplib::Server server; -#if defined(__APPLE__) - if (not server.set_mount_point("/ui", "../Resources/web")) { -#else // !defined(__APPLE__) - if (not server.set_mount_point("/ui", "./web")) { -#endif // defined(__APPLE__) - ret = static_cast(exit_code::ui_failed); - } else { + const auto run_ui = [](auto *server) { + REPERTORY_USES_FUNCTION_NAME(); + + static std::atomic active_server{server}; + static const auto quit_handler = [](int /* sig */) { + REPERTORY_USES_FUNCTION_NAME(); + + auto *ptr = active_server.exchange(nullptr); + if (ptr == nullptr) { + return; + } + + try { + ptr->stop(); + } catch (const std::exception &ex) { + utils::error::raise_error(function_name, ex, "failed to stop ui"); + } + }; + + std::signal(SIGINT, quit_handler); +#if !defined(_WIN32) + std::signal(SIGQUIT, quit_handler); +#endif // !defined(_WIN32) + std::signal(SIGTERM, quit_handler); + + try { + ptr->start(); + } catch (const std::exception &ex) { + utils::error::raise_error(function_name, ex, "failed to start ui"); + } + + try { + ptr->stop(); + } catch (const std::exception &ex) { + utils::error::raise_error(function_name, ex, "failed to stop ui"); + } + + repertory::project_cleanup(); + }; + #if defined(_WIN32) - ui::handlers handlers(&config, &server); + ui::server server(&config); + run_ui(&server); #else // !defined(_WIN32) - project_cleanup(); + repertory::project_cleanup(); - ret = utils::create_daemon([&]() -> int { - if (not repertory::project_initialize()) { - repertory::project_cleanup(); - return -1; - } + ret = utils::create_daemon([&]() -> int { + if (not repertory::project_initialize()) { + repertory::project_cleanup(); + return -1; + } - if (not utils::file::change_to_process_directory()) { - return -1; - } + if (not utils::file::change_to_process_directory()) { + return -1; + } - ui::handlers handlers(&config, &server); - project_cleanup(); - return 0; - }); + ui::server server(&config); + run_ui(&server); + return 0; + }); #endif // defined(_WIN32) - } } } else { auto prov = utils::cli::get_provider_type_from_args(args); diff --git a/repertory/repertory/src/ui/mgmt_app_config.cpp b/repertory/repertory/src/ui/mgmt_app_config.cpp index c292878f..1cf3a300 100644 --- a/repertory/repertory/src/ui/mgmt_app_config.cpp +++ b/repertory/repertory/src/ui/mgmt_app_config.cpp @@ -249,11 +249,11 @@ void mgmt_app_config::set_auto_start(bool auto_start) { #if defined(__linux__) if (auto_start) { utils::autostart_cfg cfg{}; - cfg.app_name = "repertory"; + cfg.app_name = REPERTORY; cfg.comment = "Mount utility for AWS S3 and Sia"; cfg.exec_args = {"-ui", "-lo"}; - cfg.exec_path = utils::path::combine(".", {"repertory"}); - cfg.icon_path = utils::path::combine(".", {"repertory.png"}); + cfg.exec_path = utils::path::combine(".", {REPERTORY}); + cfg.icon_path = utils::path::combine(".", {REPERTORY ".png"}); cfg.terminal = true; if (utils::create_autostart_entry(cfg, false)) { @@ -264,7 +264,7 @@ void mgmt_app_config::set_auto_start(bool auto_start) { function_name, utils::get_last_error_code(), "failed to create auto-start entry|name|repertory"); } - } else if (utils::remove_autostart_entry("repertory")) { + } else if (utils::remove_autostart_entry(REPERTORY)) { utils::error::handle_info(function_name, "removed auto-start entry|name|repertory"); } else { @@ -278,9 +278,9 @@ void mgmt_app_config::set_auto_start(bool auto_start) { if (auto_start) { utils::shortcut_cfg cfg{}; cfg.arguments = L"-ui -lo --hidden"; - cfg.exe_path = utils::path::combine(L".", {L"repertory"}); + cfg.exe_path = utils::path::combine(L".", {REPERTORY_W}); cfg.icon_path = utils::path::combine(L".", {L"icon.ico"}); - cfg.shortcut_name = L"repertory"; + cfg.shortcut_name = REPERTORY_W; cfg.working_directory = utils::path::absolute(L"."); if (utils::create_shortcut(cfg, false)) { @@ -291,7 +291,7 @@ void mgmt_app_config::set_auto_start(bool auto_start) { function_name, utils::get_last_error_code(), "failed to create auto-start entry|name|repertory"); } - } else if (utils::remove_shortcut(L"repertory")) { + } else if (utils::remove_shortcut(REPERTORY_W)) { utils::error::handle_info(function_name, "removed auto-start entry|name|repertory"); } else { @@ -307,7 +307,7 @@ void mgmt_app_config::set_auto_start(bool auto_start) { if (auto_start) { utils::plist_cfg cfg{}; cfg.args = { - utils::path::combine(".", {"repertory"}), + utils::path::combine(".", {REPERTORY}), "-ui", "-lo", }; diff --git a/repertory/repertory/src/ui/handlers.cpp b/repertory/repertory/src/ui/ui_server.cpp similarity index 71% rename from repertory/repertory/src/ui/handlers.cpp rename to repertory/repertory/src/ui/ui_server.cpp index 96afe3b5..11e871ea 100644 --- a/repertory/repertory/src/ui/handlers.cpp +++ b/repertory/repertory/src/ui/ui_server.cpp @@ -19,9 +19,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "ui/handlers.hpp" +#include "ui/ui_server.hpp" #include "app_config.hpp" +#include "events/consumers/console_consumer.hpp" +#include "events/consumers/logging_consumer.hpp" #include "events/event_system.hpp" #include "platform/platform.hpp" #include "types/repertory.hpp" @@ -120,43 +122,27 @@ namespace { } // namespace namespace repertory::ui { -handlers::handlers(mgmt_app_config *config, httplib::Server *server) +ui_server::ui_server(mgmt_app_config *config) : config_(config), + logging(config->get_event_level(), + utils::path::combine(app_config::get_root_data_directory(), + { + "ui", + "logs", + })), #if defined(_WIN32) - repertory_binary_(utils::path::combine(".", {"repertory.exe"})), + repertory_binary_(utils::path::combine(".", {REPERTORY ".exe"})) #else // !defined(_WIN32) - repertory_binary_(utils::path::combine(".", {"repertory"})), + repertory_binary_(utils::path::combine(".", {REPERTORY})) #endif // defined(_WIN32) - server_(server) { - REPERTORY_USES_FUNCTION_NAME(); +{ +#if defined(__APPLE__) + server_.set_mount_point("/ui", "../Resources/web"); +#else // !defined(__APPLE__) + server_.set_mount_point("/ui", "./web"); +#endif // defined(__APPLE__) -#if defined(_WIN32) - if (config_->get_hidden()) { - ::ShowWindow(::GetConsoleWindow(), SW_HIDE); - } -#endif // defined(_WIN32) - - if (not config_->get_launch_only()) { -#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()); -#elif defined(__APPLE__) - system( - fmt::format(R"(open "http://127.0.0.1:{}/ui")", config_->get_api_port()) - .c_str()); -#else - build fails here -#endif - } - - server_->set_socket_options([](auto &&sock) { + server_.set_socket_options([](auto &&sock) { #if defined(_WIN32) BOOL enable = TRUE; ::setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, @@ -176,7 +162,7 @@ handlers::handlers(mgmt_app_config *config, httplib::Server *server) #endif }); - server_->set_pre_routing_handler( + server_.set_pre_routing_handler( [this](const httplib::Request &req, auto &&res) -> httplib::Server::HandlerResponse { if (req.path == "/api/v1/nonce" || req.path == "/ui" || @@ -190,7 +176,7 @@ handlers::handlers(mgmt_app_config *config, httplib::Server *server) auth, fmt::format("{}_", config_->get_api_user()))) { auto nonce = auth.substr(config_->get_api_user().length() + 1U); - mutex_lock lock(nonce_mtx_); + mutex_lock nonce_lock(nonce_mtx_); if (nonce_lookup_.contains(nonce)) { nonce_lookup_.erase(nonce); return httplib::Server::HandlerResponse::Unhandled; @@ -201,9 +187,11 @@ handlers::handlers(mgmt_app_config *config, httplib::Server *server) return httplib::Server::HandlerResponse::Handled; }); - server_->set_exception_handler([](const httplib::Request &req, - httplib::Response &res, - std::exception_ptr ptr) { + server_.set_exception_handler([](const httplib::Request &req, + httplib::Response &res, + std::exception_ptr ptr) { + REPERTORY_USES_FUNCTION_NAME(); + json data{ {"path", req.path}, }; @@ -227,197 +215,72 @@ handlers::handlers(mgmt_app_config *config, httplib::Server *server) : http_error_codes::internal_error; }); - server->Get("/api/v1/locations", [](auto && /* req */, auto &&res) { + server_.Get("/api/v1/locations", [](auto && /* req */, auto &&res) { handle_get_available_locations(res); }); - server->Get("/api/v1/mount", + 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) { + server_.Get("/api/v1/mount_location", [this](auto &&req, auto &&res) { handle_get_mount_location(req, res); }); - server->Get("/api/v1/mount_list", [](auto && /* req */, auto &&res) { + server_.Get("/api/v1/mount_list", [](auto && /* req */, auto &&res) { handle_get_mount_list(res); }); - server->Get("/api/v1/mount_status", [this](auto &&req, auto &&res) { + server_.Get("/api/v1/mount_status", [this](auto &&req, auto &&res) { handle_get_mount_status(req, res); }); - server->Get("/api/v1/nonce", + server_.Get("/api/v1/nonce", [this](auto && /* req */, auto &&res) { handle_get_nonce(res); }); - server->Get("/api/v1/settings", [this](auto && /* req */, auto &&res) { + server_.Get("/api/v1/settings", [this](auto && /* req */, auto &&res) { handle_get_settings(res); }); - server->Get("/api/v1/test", + server_.Get("/api/v1/test", [this](auto &&req, auto &&res) { handle_get_test(req, res); }); - server->Post("/api/v1/add_mount", [this](auto &&req, auto &&res) { + server_.Post("/api/v1/add_mount", [this](auto &&req, auto &&res) { handle_post_add_mount(req, res); }); - server->Post("/api/v1/mount", + server_.Post("/api/v1/mount", [this](auto &&req, auto &&res) { handle_post_mount(req, res); }); - server->Put("/api/v1/mount_auto_start", [this](auto &&req, auto &&res) { + server_.Put("/api/v1/mount_auto_start", [this](auto &&req, auto &&res) { handle_put_mount_auto_start(req, res); }); - server->Put("/api/v1/mount_location", [this](auto &&req, auto &&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) { + 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/setting", + server_.Put("/api/v1/setting", [this](auto &&req, auto &&res) { handle_put_setting(req, res); }); - server->Put("/api/v1/settings", [this](auto &&req, auto &&res) { + server_.Put("/api/v1/settings", [this](auto &&req, auto &&res) { handle_put_settings(req, res); }); - static std::atomic this_server{server_}; - static const auto quit_handler = [](int /* sig */) { - auto *ptr = this_server.exchange(nullptr); - if (ptr == nullptr) { - return; - } - #if defined(_WIN32) - std::jthread([ptr]() { ptr->stop(); }); -#else // !defined(_WIN32) - ptr->stop(); + if (config_->get_hidden()) { + ::ShowWindow(::GetConsoleWindow(), SW_HIDE); + } #endif // defined(_WIN32) - }; - - std::signal(SIGINT, quit_handler); -#if !defined(_WIN32) - std::signal(SIGQUIT, quit_handler); -#endif // !defined(_WIN32) - std::signal(SIGTERM, quit_handler); - - event_system::instance().start(); - - { - auto deadline = std::chrono::steady_clock::now() + 30s; - std::string host{"127.0.0.1"}; - auto desired_port = config_->get_api_port(); - for (;;) { - if (server_->bind_to_port(host, desired_port)) { - break; // bound successfully; proceed to route registration - } - - const auto err = get_last_net_error(); - if (!is_addr_in_use(err)) { - utils::error::raise_error(function_name, - utils::error::create_error_message({ - "failed to bind", - "host", - host, - "port", - std::to_string(desired_port), - "errno", - std::to_string(err), - })); - return; // abort constructor on non-EADDRINUSE errors - } - - if (std::chrono::steady_clock::now() >= deadline) { - utils::error::raise_error(function_name, - utils::error::create_error_message({ - "bind timeout (port in use)", - "host", - host, - "port", - std::to_string(desired_port), - })); - return; // abort constructor on timeout - } - - std::this_thread::sleep_for(250ms); - } - - std::thread([this]() { - for (const auto &[prov, names] : config_->get_auto_start_list()) { - for (const auto &name : names) { - try { - auto location = config_->get_mount_location(prov, name); - if (location.empty()) { - utils::error::raise_error(function_name, - utils::error::create_error_message({ - "failed to auto-mount", - "provider", - provider_type_to_string(prov), - "name", - name, - "location is empty", - })); - } else if (not mount(prov, name, location)) { - utils::error::raise_error(function_name, - utils::error::create_error_message({ - "failed to auto-mount", - "provider", - provider_type_to_string(prov), - "name", - name, - "mount failed", - })); - } - } catch (const std::exception &e) { - utils::error::raise_error(function_name, e, - utils::error::create_error_message({ - "failed to auto-mount", - "provider", - provider_type_to_string(prov), - "name", - name, - })); - } catch (...) { - utils::error::raise_error(function_name, "unknown error", - utils::error::create_error_message({ - "failed to auto-mount", - "provider", - provider_type_to_string(prov), - "name", - name, - })); - } - } - } - }).join(); - - nonce_thread_ = - std::make_unique([this]() { removed_expired_nonces(); }); - - server_->listen_after_bind(); - } - - quit_handler(SIGTERM); } -handlers::~handlers() { - if (nonce_thread_) { - stop_requested = true; +ui_server::~ui_server() { stop(); } - 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 ui_server::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), @@ -428,16 +291,16 @@ auto handlers::data_directory_exists(provider_type prov, return ret; } - unique_mutex_lock lock(mtx_); + unique_mutex_lock nonce_lock(mtx_); mtx_lookup_.erase( fmt::format("{}-{}", name, app_config::get_provider_name(prov))); - lock.unlock(); + nonce_lock.unlock(); return ret; } -void handlers::generate_config(provider_type prov, std::string_view name, - const json &cfg, - std::optional data_dir) const { +void ui_server::generate_config(provider_type prov, std::string_view name, + const json &cfg, + std::optional data_dir) const { REPERTORY_USES_FUNCTION_NAME(); std::map values{}; @@ -484,8 +347,8 @@ void handlers::generate_config(provider_type prov, std::string_view name, } } -void handlers::handle_put_mount_auto_start(const httplib::Request &req, - httplib::Response &res) const { +void ui_server::handle_put_mount_auto_start(const httplib::Request &req, + httplib::Response &res) const { auto prov = provider_type_from_string(req.get_param_value("type")); auto name = req.get_param_value("name"); @@ -499,8 +362,8 @@ void handlers::handle_put_mount_auto_start(const httplib::Request &req, res.status = http_error_codes::ok; } -void handlers::handle_put_mount_location(const httplib::Request &req, - httplib::Response &res) const { +void ui_server::handle_put_mount_location(const httplib::Request &req, + httplib::Response &res) const { 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"); @@ -514,7 +377,7 @@ void handlers::handle_put_mount_location(const httplib::Request &req, res.status = http_error_codes::ok; } -void handlers::handle_get_available_locations(httplib::Response &res) { +void ui_server::handle_get_available_locations(httplib::Response &res) { #if defined(_WIN32) constexpr std::array letters{ "a:", "b:", "c:", "d:", "e:", "f:", "g:", "h:", "i:", @@ -543,8 +406,8 @@ void handlers::handle_get_available_locations(httplib::Response &res) { res.status = http_error_codes::ok; } -void handlers::handle_get_mount(const httplib::Request &req, - httplib::Response &res) const { +void ui_server::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")); @@ -572,7 +435,7 @@ void handlers::handle_get_mount(const httplib::Request &req, res.status = http_error_codes::ok; } -void handlers::handle_get_mount_list(httplib::Response &res) { +void ui_server::handle_get_mount_list(httplib::Response &res) { auto data_dir = utils::file::directory{app_config::get_root_data_directory()}; nlohmann::json result; @@ -601,8 +464,8 @@ void handlers::handle_get_mount_list(httplib::Response &res) { res.status = http_error_codes::ok; } -void handlers::handle_get_mount_location(const httplib::Request &req, - httplib::Response &res) const { +void ui_server::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")); @@ -620,8 +483,8 @@ void handlers::handle_get_mount_location(const httplib::Request &req, res.status = http_error_codes::ok; } -void handlers::handle_get_mount_status(const httplib::Request &req, - httplib::Response &res) const { +void ui_server::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")); @@ -646,8 +509,8 @@ void handlers::handle_get_mount_status(const httplib::Request &req, res.status = http_error_codes::ok; } -void handlers::handle_get_nonce(httplib::Response &res) { - mutex_lock lock(nonce_mtx_); +void ui_server::handle_get_nonce(httplib::Response &res) { + mutex_lock nonce_lock(nonce_mtx_); nonce_data nonce{}; nonce_lookup_[nonce.nonce] = nonce; @@ -657,7 +520,7 @@ void handlers::handle_get_nonce(httplib::Response &res) { res.status = http_error_codes::ok; } -void handlers::handle_get_settings(httplib::Response &res) const { +void ui_server::handle_get_settings(httplib::Response &res) const { auto settings = config_->to_json(); settings[JSON_API_PASSWORD] = ""; settings.erase(JSON_MOUNT_LOCATIONS); @@ -665,18 +528,18 @@ void handlers::handle_get_settings(httplib::Response &res) const { res.status = http_error_codes::ok; } -void handlers::handle_get_test(const httplib::Request &req, - httplib::Response &res) const { +void ui_server::handle_get_test(const httplib::Request &req, + httplib::Response &res) const { REPERTORY_USES_FUNCTION_NAME(); - unique_mutex_lock lock(test_mtx_); + unique_mutex_lock nonce_lock(test_mtx_); auto name = req.get_param_value("name"); auto prov = provider_type_from_string(req.get_param_value("type")); auto cfg = nlohmann::json::parse(req.get_param_value("config")); auto data_dir = utils::path::combine( - utils::directory::temp(), {utils::file::create_temp_name("repertory")}); + utils::directory::temp(), {utils::file::create_temp_name(REPERTORY)}); try { generate_config(prov, name, cfg, data_dir); @@ -698,8 +561,8 @@ void handlers::handle_get_test(const httplib::Request &req, fmt::format("failed to remove data directory|{}", data_dir)); } -void handlers::handle_post_add_mount(const httplib::Request &req, - httplib::Response &res) const { +void ui_server::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)) { @@ -713,8 +576,8 @@ void handlers::handle_post_add_mount(const httplib::Request &req, res.status = http_error_codes::ok; } -void handlers::handle_post_mount(const httplib::Request &req, - httplib::Response &res) { +void ui_server::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")); @@ -740,8 +603,8 @@ void handlers::handle_post_mount(const httplib::Request &req, res.status = http_error_codes::internal_error; } -void handlers::handle_put_set_value_by_name(const httplib::Request &req, - httplib::Response &res) const { +void ui_server::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")); @@ -762,8 +625,8 @@ void handlers::handle_put_set_value_by_name(const httplib::Request &req, res.status = http_error_codes::ok; } -void handlers::handle_put_setting(const httplib::Request &req, - httplib::Response &res) const { +void ui_server::handle_put_setting(const httplib::Request &req, + httplib::Response &res) const { auto name = req.get_param_value("name"); auto value = req.get_param_value("value"); @@ -776,8 +639,8 @@ void handlers::handle_put_setting(const httplib::Request &req, res.status = http_error_codes::ok; } -void handlers::handle_put_settings(const httplib::Request &req, - httplib::Response &res) const { +void ui_server::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)) { @@ -800,9 +663,9 @@ void handlers::handle_put_settings(const httplib::Request &req, res.status = http_error_codes::ok; } -auto handlers::launch_process(provider_type prov, std::string_view name, - std::vector args, - bool background) const +auto ui_server::launch_process(provider_type prov, std::string_view name, + std::vector args, + bool background) const -> std::vector { REPERTORY_USES_FUNCTION_NAME(); @@ -840,10 +703,10 @@ auto handlers::launch_process(provider_type prov, std::string_view name, }); } - unique_mutex_lock lock(mtx_); + unique_mutex_lock nonce_lock(mtx_); auto &inst_mtx = mtx_lookup_[fmt::format( "{}-{}", name, app_config::get_provider_name(prov))]; - lock.unlock(); + nonce_lock.unlock(); recur_mutex_lock inst_lock(inst_mtx); if (background) { @@ -912,8 +775,8 @@ auto handlers::launch_process(provider_type prov, std::string_view name, false); } -auto handlers::mount(provider_type prov, std::string_view name, - const std::string &location) -> bool { +auto ui_server::mount(provider_type prov, std::string_view name, + const std::string &location) -> bool { #if defined(_WIN32) if (utils::file::directory{location}.exists()) { #else // !defined(_WIN32) @@ -927,28 +790,33 @@ auto handlers::mount(provider_type prov, std::string_view name, config_->set_mount_location(prov, name, location); static std::mutex mount_mtx; - mutex_lock lock(mount_mtx); + mutex_lock nonce_lock(mount_mtx); launch_process(prov, name, {location}, true); return true; } -void handlers::removed_expired_nonces() { - unique_mutex_lock lock(nonce_mtx_); - lock.unlock(); +void ui_server::notify_and_unlock(unique_mutex_lock &nonce_lock) { + nonce_notify_.notify_all(); + nonce_lock.unlock(); +} + +void ui_server::removed_expired_nonces() { + unique_mutex_lock nonce_lock(nonce_mtx_); + notify_and_unlock(nonce_lock); while (not stop_requested) { - lock.lock(); + nonce_lock.lock(); auto nonces = nonce_lookup_; - lock.unlock(); + notify_and_unlock(nonce_lock); for (const auto &[key, value] : nonces) { if (std::chrono::duration_cast( std::chrono::system_clock::now() - value.creation) .count() >= nonce_timeout) { - lock.lock(); + nonce_lock.lock(); nonce_lookup_.erase(key); - lock.unlock(); + notify_and_unlock(nonce_lock); } } @@ -956,18 +824,18 @@ void handlers::removed_expired_nonces() { break; } - lock.lock(); + nonce_lock.lock(); if (stop_requested) { break; } - nonce_notify_.wait_for(lock, std::chrono::seconds(1U)); - lock.unlock(); + nonce_notify_.wait_for(nonce_lock, std::chrono::seconds(1U)); + notify_and_unlock(nonce_lock); } } -void handlers::set_key_value(provider_type prov, std::string_view name, - std::string_view key, std::string_view value, - std::optional data_dir) const { +void ui_server::set_key_value(provider_type prov, std::string_view name, + std::string_view key, std::string_view value, + std::optional data_dir) const { std::vector args; if (data_dir.has_value()) { args.emplace_back("-dd"); @@ -978,4 +846,160 @@ void handlers::set_key_value(provider_type prov, std::string_view name, args.emplace_back(value); launch_process(prov, name, args, false); } + +void ui_server::start() { + REPERTORY_USES_FUNCTION_NAME(); + + unique_mutex_lock nonce_lock(nonce_mtx_); + if (nonce_thread_) { + return; + } + + stop_requested = false; + event_system::instance().start(); + + nonce_thread_ = + std::make_unique([this]() { removed_expired_nonces(); }); + + const auto launch_ui = [this]() { + if (not config_->get_launch_only()) { +#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()); +#elif defined(__APPLE__) + system(fmt::format(R"(open "http://127.0.0.1:{}/ui")", + config_->get_api_port()) + .c_str()); +#else + build fails here +#endif + } + }; + + auto should_launch{true}; + + lock_data ui_lock(provider_type::unknown, "ui"); + auto res = ui_lock.grab_lock(1U); + if (res == lock_result::locked) { + auto deadline{std::chrono::steady_clock::now() + 30s}; + std::string host{"127.0.0.1"}; + auto desired_port{config_->get_api_port()}; + + auto success{false}; + while (not success && std::chrono::steady_clock::now() >= deadline) { + success = server_.bind_to_port(host, desired_port); + if (success) { + break; + } + + utils::error::raise_error(function_name, + utils::error::create_error_message({ + "failed to bind", + "host", + host, + "port", + std::to_string(desired_port), + "error", + std::to_string(get_last_net_error()), + })); + std::this_thread::sleep_for(250ms); + } + + if (success) { + std::thread([this]() { + for (const auto &[prov, names] : config_->get_auto_start_list()) { + for (const auto &name : names) { + try { + auto location = config_->get_mount_location(prov, name); + if (location.empty()) { + utils::error::raise_error(function_name, + utils::error::create_error_message({ + "failed to auto-mount", + "provider", + provider_type_to_string(prov), + "name", + name, + "location is empty", + })); + } else if (not mount(prov, name, location)) { + utils::error::raise_error(function_name, + utils::error::create_error_message({ + "failed to auto-mount", + "provider", + provider_type_to_string(prov), + "name", + name, + "mount failed", + })); + } + } catch (const std::exception &e) { + utils::error::raise_error(function_name, e, + utils::error::create_error_message({ + "failed to auto-mount", + "provider", + provider_type_to_string(prov), + "name", + name, + })); + } catch (...) { + utils::error::raise_error(function_name, "unknown error", + utils::error::create_error_message({ + "failed to auto-mount", + "provider", + provider_type_to_string(prov), + "name", + name, + })); + } + } + } + }).join(); + + server_.listen_after_bind(); + } else { + utils::error::raise_error(function_name, + utils::error::create_error_message({ + "bind timeout (port in use)", + "host", + host, + "port", + std::to_string(desired_port), + })); + should_launch = false; + } + } + + notify_and_unlock(nonce_lock); + if (not should_launch) { + return; + } + + launch_ui(); +} + +void ui_server::stop() { + unique_mutex_lock nonce_lock(nonce_mtx_); + if (not nonce_thread_) { + notify_and_unlock(nonce_lock); + return; + } + + stop_requested = true; + notify_and_unlock(nonce_lock); + + nonce_thread_->join(); + + nonce_lock.lock(); + nonce_thread_.reset(); + notify_and_unlock(nonce_lock); + + event_system::instance().stop(); +} } // namespace repertory::ui diff --git a/repertory/repertory_test/src/curl_comm_test.cpp b/repertory/repertory_test/src/curl_comm_test.cpp index 3d4af959..2ab8ba1e 100644 --- a/repertory/repertory_test/src/curl_comm_test.cpp +++ b/repertory/repertory_test/src/curl_comm_test.cpp @@ -27,7 +27,7 @@ namespace repertory { TEST(curl_comm_test, can_create_s3_host_config) { s3_config config{}; - config.bucket = "repertory"; + config.bucket = REPERTORY; config.url = "https://s3.test.com"; config.region = "any"; config.use_path_style = false; @@ -40,7 +40,7 @@ TEST(curl_comm_test, can_create_s3_host_config) { TEST(curl_comm_test, can_create_s3_host_config_with_path_style) { s3_config config{}; - config.bucket = "repertory"; + config.bucket = REPERTORY; config.url = "https://s3.test.com"; config.region = "any"; config.use_path_style = true; @@ -53,7 +53,7 @@ TEST(curl_comm_test, can_create_s3_host_config_with_path_style) { TEST(curl_comm_test, can_create_s3_host_config_with_region) { s3_config config{}; - config.bucket = "repertory"; + config.bucket = REPERTORY; config.url = "https://s3.test.com"; config.region = "any"; config.use_region_in_url = true; @@ -67,7 +67,7 @@ TEST(curl_comm_test, can_create_s3_host_config_with_region) { TEST(curl_comm_test, can_create_s3_host_config_with_region_and_path_style) { s3_config config{}; - config.bucket = "repertory"; + config.bucket = REPERTORY; config.url = "https://s3.test.com"; config.region = "any"; config.use_region_in_url = true;