broken build - [refactor ui start/stop][prefer REPERTORY/REPERTORY_W]
Some checks failed
Blockstorage/repertory/pipeline/head There was a failure building this commit
BlockStorage/repertory/pipeline/head There was a failure building this commit

This commit is contained in:
2025-09-15 10:48:25 -05:00
parent 53b8c70c80
commit f722dd4b84
7 changed files with 355 additions and 290 deletions

View File

@@ -447,7 +447,7 @@ auto fuse_base::mount([[maybe_unused]] std::vector<std::string> orig_args,
return -1; 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"); orig_args.insert(std::next(orig_args.begin()), "-f");
utils::plist_cfg cfg{}; utils::plist_cfg cfg{};

View File

@@ -36,9 +36,9 @@ private:
private: private:
std::atomic<bool> animations_{true}; std::atomic<bool> animations_{true};
utils::atomic<std::string> api_password_{"repertory"}; utils::atomic<std::string> api_password_{REPERTORY};
std::atomic<std::uint16_t> api_port_{default_ui_mgmt_port}; std::atomic<std::uint16_t> api_port_{default_ui_mgmt_port};
utils::atomic<std::string> api_user_{"repertory"}; utils::atomic<std::string> api_user_{REPERTORY};
std::atomic<bool> auto_start_{true}; std::atomic<bool> auto_start_{true};
std::unordered_map<provider_type, std::unordered_map<provider_type,
std::unordered_map<std::string, std::string>> std::unordered_map<std::string, std::string>>

View File

@@ -19,16 +19,17 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#ifndef REPERTORY_INCLUDE_UI_HANDLERS_HPP_ #ifndef REPERTORY_INCLUDE_UI_SERVER_HPP_
#define REPERTORY_INCLUDE_UI_HANDLERS_HPP_ #define REPERTORY_INCLUDE_UI_SERVER_HPP_
#include "events/consumers/console_consumer.hpp" #include "events/consumers/console_consumer.hpp"
#include "events/consumers/logging_consumer.hpp"
#include "utils/common.hpp" #include "utils/common.hpp"
namespace repertory::ui { namespace repertory::ui {
class mgmt_app_config; class mgmt_app_config;
class handlers final { class ui_server final {
private: private:
static constexpr auto nonce_length{128U}; static constexpr auto nonce_length{128U};
static constexpr auto nonce_timeout{15U}; static constexpr auto nonce_timeout{15U};
@@ -44,21 +45,21 @@ private:
}; };
public: public:
handlers(mgmt_app_config *config, httplib::Server *server); ui_server(mgmt_app_config *config);
handlers() = delete; ui_server() = delete;
handlers(const handlers &) = delete; ui_server(const ui_server &) = delete;
handlers(handlers &&) = delete; ui_server(ui_server &&) = delete;
~handlers(); ~ui_server();
auto operator=(const handlers &) -> handlers & = delete; auto operator=(const ui_server &) -> ui_server & = delete;
auto operator=(handlers &&) -> handlers & = delete; auto operator=(ui_server &&) -> ui_server & = delete;
private: private:
mgmt_app_config *config_; mgmt_app_config *config_;
logging_consumer logging;
std::string repertory_binary_; std::string repertory_binary_;
httplib::Server *server_;
private: private:
console_consumer console; console_consumer console;
@@ -68,6 +69,7 @@ private:
std::unordered_map<std::string, nonce_data> nonce_lookup_; std::unordered_map<std::string, nonce_data> nonce_lookup_;
std::condition_variable nonce_notify_; std::condition_variable nonce_notify_;
std::unique_ptr<std::thread> nonce_thread_; std::unique_ptr<std::thread> nonce_thread_;
httplib::Server server_;
stop_type stop_requested{false}; stop_type stop_requested{false};
mutable std::mutex test_mtx_; mutable std::mutex test_mtx_;
@@ -127,12 +129,19 @@ private:
[[nodiscard]] auto mount(provider_type prov, std::string_view name, [[nodiscard]] auto mount(provider_type prov, std::string_view name,
const std::string &location) -> bool; const std::string &location) -> bool;
void notify_and_unlock(unique_mutex_lock &nonce_lock);
void removed_expired_nonces(); void removed_expired_nonces();
void set_key_value(provider_type prov, std::string_view name, void set_key_value(provider_type prov, std::string_view name,
std::string_view key, std::string_view value, std::string_view key, std::string_view value,
std::optional<std::string> data_dir = std::nullopt) const; std::optional<std::string> data_dir = std::nullopt) const;
public:
void start();
void stop();
}; };
} // namespace repertory::ui } // namespace repertory::ui
#endif // REPERTORY_INCLUDE_UI_HANDLERS_HPP_ #endif // REPERTORY_INCLUDE_UI_SERVER_HPP_

View File

@@ -26,8 +26,8 @@
#include "cli/actions.hpp" #include "cli/actions.hpp"
#include "initialize.hpp" #include "initialize.hpp"
#include "types/repertory.hpp" #include "types/repertory.hpp"
#include "ui/handlers.hpp"
#include "ui/mgmt_app_config.hpp" #include "ui/mgmt_app_config.hpp"
#include "ui/ui_server.hpp"
#include "utils/cli_utils.hpp" #include "utils/cli_utils.hpp"
#include "utils/polling.hpp" #include "utils/polling.hpp"
@@ -80,35 +80,67 @@ auto main(int argc, char **argv) -> int {
if (not utils::file::change_to_process_directory()) { if (not utils::file::change_to_process_directory()) {
ret = static_cast<std::int32_t>(exit_code::ui_failed); ret = static_cast<std::int32_t>(exit_code::ui_failed);
} else { } else {
httplib::Server server; const auto run_ui = [](auto *server) {
#if defined(__APPLE__) REPERTORY_USES_FUNCTION_NAME();
if (not server.set_mount_point("/ui", "../Resources/web")) {
#else // !defined(__APPLE__) static std::atomic<ui::server *> active_server{server};
if (not server.set_mount_point("/ui", "./web")) { static const auto quit_handler = [](int /* sig */) {
#endif // defined(__APPLE__) REPERTORY_USES_FUNCTION_NAME();
ret = static_cast<std::int32_t>(exit_code::ui_failed);
} else { 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) #if defined(_WIN32)
ui::handlers handlers(&config, &server); ui::server server(&config);
run_ui(&server);
#else // !defined(_WIN32) #else // !defined(_WIN32)
project_cleanup(); repertory::project_cleanup();
ret = utils::create_daemon([&]() -> int { ret = utils::create_daemon([&]() -> int {
if (not repertory::project_initialize()) { if (not repertory::project_initialize()) {
repertory::project_cleanup(); repertory::project_cleanup();
return -1; return -1;
} }
if (not utils::file::change_to_process_directory()) { if (not utils::file::change_to_process_directory()) {
return -1; return -1;
} }
ui::handlers handlers(&config, &server); ui::server server(&config);
project_cleanup(); run_ui(&server);
return 0; return 0;
}); });
#endif // defined(_WIN32) #endif // defined(_WIN32)
}
} }
} else { } else {
auto prov = utils::cli::get_provider_type_from_args(args); auto prov = utils::cli::get_provider_type_from_args(args);

View File

@@ -249,11 +249,11 @@ void mgmt_app_config::set_auto_start(bool auto_start) {
#if defined(__linux__) #if defined(__linux__)
if (auto_start) { if (auto_start) {
utils::autostart_cfg cfg{}; utils::autostart_cfg cfg{};
cfg.app_name = "repertory"; cfg.app_name = REPERTORY;
cfg.comment = "Mount utility for AWS S3 and Sia"; cfg.comment = "Mount utility for AWS S3 and Sia";
cfg.exec_args = {"-ui", "-lo"}; cfg.exec_args = {"-ui", "-lo"};
cfg.exec_path = utils::path::combine(".", {"repertory"}); cfg.exec_path = utils::path::combine(".", {REPERTORY});
cfg.icon_path = utils::path::combine(".", {"repertory.png"}); cfg.icon_path = utils::path::combine(".", {REPERTORY ".png"});
cfg.terminal = true; cfg.terminal = true;
if (utils::create_autostart_entry(cfg, false)) { 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(), function_name, utils::get_last_error_code(),
"failed to create auto-start entry|name|repertory"); "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, utils::error::handle_info(function_name,
"removed auto-start entry|name|repertory"); "removed auto-start entry|name|repertory");
} else { } else {
@@ -278,9 +278,9 @@ void mgmt_app_config::set_auto_start(bool auto_start) {
if (auto_start) { if (auto_start) {
utils::shortcut_cfg cfg{}; utils::shortcut_cfg cfg{};
cfg.arguments = L"-ui -lo --hidden"; 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.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"."); cfg.working_directory = utils::path::absolute(L".");
if (utils::create_shortcut(cfg, false)) { 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(), function_name, utils::get_last_error_code(),
"failed to create auto-start entry|name|repertory"); "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, utils::error::handle_info(function_name,
"removed auto-start entry|name|repertory"); "removed auto-start entry|name|repertory");
} else { } else {
@@ -307,7 +307,7 @@ void mgmt_app_config::set_auto_start(bool auto_start) {
if (auto_start) { if (auto_start) {
utils::plist_cfg cfg{}; utils::plist_cfg cfg{};
cfg.args = { cfg.args = {
utils::path::combine(".", {"repertory"}), utils::path::combine(".", {REPERTORY}),
"-ui", "-ui",
"-lo", "-lo",
}; };

View File

@@ -19,9 +19,11 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#include "ui/handlers.hpp" #include "ui/ui_server.hpp"
#include "app_config.hpp" #include "app_config.hpp"
#include "events/consumers/console_consumer.hpp"
#include "events/consumers/logging_consumer.hpp"
#include "events/event_system.hpp" #include "events/event_system.hpp"
#include "platform/platform.hpp" #include "platform/platform.hpp"
#include "types/repertory.hpp" #include "types/repertory.hpp"
@@ -120,43 +122,27 @@ namespace {
} // namespace } // namespace
namespace repertory::ui { namespace repertory::ui {
handlers::handlers(mgmt_app_config *config, httplib::Server *server) ui_server::ui_server(mgmt_app_config *config)
: config_(config), : config_(config),
logging(config->get_event_level(),
utils::path::combine(app_config::get_root_data_directory(),
{
"ui",
"logs",
})),
#if defined(_WIN32) #if defined(_WIN32)
repertory_binary_(utils::path::combine(".", {"repertory.exe"})), repertory_binary_(utils::path::combine(".", {REPERTORY ".exe"}))
#else // !defined(_WIN32) #else // !defined(_WIN32)
repertory_binary_(utils::path::combine(".", {"repertory"})), repertory_binary_(utils::path::combine(".", {REPERTORY}))
#endif // defined(_WIN32) #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) server_.set_socket_options([](auto &&sock) {
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) {
#if defined(_WIN32) #if defined(_WIN32)
BOOL enable = TRUE; BOOL enable = TRUE;
::setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, ::setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
@@ -176,7 +162,7 @@ handlers::handlers(mgmt_app_config *config, httplib::Server *server)
#endif #endif
}); });
server_->set_pre_routing_handler( server_.set_pre_routing_handler(
[this](const httplib::Request &req, [this](const httplib::Request &req,
auto &&res) -> httplib::Server::HandlerResponse { auto &&res) -> httplib::Server::HandlerResponse {
if (req.path == "/api/v1/nonce" || req.path == "/ui" || 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()))) { auth, fmt::format("{}_", config_->get_api_user()))) {
auto nonce = auth.substr(config_->get_api_user().length() + 1U); 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)) { if (nonce_lookup_.contains(nonce)) {
nonce_lookup_.erase(nonce); nonce_lookup_.erase(nonce);
return httplib::Server::HandlerResponse::Unhandled; return httplib::Server::HandlerResponse::Unhandled;
@@ -201,9 +187,11 @@ handlers::handlers(mgmt_app_config *config, httplib::Server *server)
return httplib::Server::HandlerResponse::Handled; return httplib::Server::HandlerResponse::Handled;
}); });
server_->set_exception_handler([](const httplib::Request &req, server_.set_exception_handler([](const httplib::Request &req,
httplib::Response &res, httplib::Response &res,
std::exception_ptr ptr) { std::exception_ptr ptr) {
REPERTORY_USES_FUNCTION_NAME();
json data{ json data{
{"path", req.path}, {"path", req.path},
}; };
@@ -227,197 +215,72 @@ handlers::handlers(mgmt_app_config *config, httplib::Server *server)
: http_error_codes::internal_error; : 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); 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); }); [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); 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); 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); 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); }); [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); handle_get_settings(res);
}); });
server->Get("/api/v1/test", server_.Get("/api/v1/test",
[this](auto &&req, auto &&res) { handle_get_test(req, res); }); [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); 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); }); [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); 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); 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); 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); }); [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); handle_put_settings(req, res);
}); });
static std::atomic<httplib::Server *> this_server{server_};
static const auto quit_handler = [](int /* sig */) {
auto *ptr = this_server.exchange(nullptr);
if (ptr == nullptr) {
return;
}
#if defined(_WIN32) #if defined(_WIN32)
std::jthread([ptr]() { ptr->stop(); }); if (config_->get_hidden()) {
#else // !defined(_WIN32) ::ShowWindow(::GetConsoleWindow(), SW_HIDE);
ptr->stop(); }
#endif // defined(_WIN32) #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<std::thread>([this]() { removed_expired_nonces(); });
server_->listen_after_bind();
}
quit_handler(SIGTERM);
} }
handlers::~handlers() { ui_server::~ui_server() { stop(); }
if (nonce_thread_) {
stop_requested = true;
unique_mutex_lock lock(nonce_mtx_); auto ui_server::data_directory_exists(provider_type prov,
nonce_notify_.notify_all(); std::string_view name) const -> bool {
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(), auto data_dir = utils::path::combine(app_config::get_root_data_directory(),
{ {
app_config::get_provider_name(prov), app_config::get_provider_name(prov),
@@ -428,16 +291,16 @@ auto handlers::data_directory_exists(provider_type prov,
return ret; return ret;
} }
unique_mutex_lock lock(mtx_); unique_mutex_lock nonce_lock(mtx_);
mtx_lookup_.erase( mtx_lookup_.erase(
fmt::format("{}-{}", name, app_config::get_provider_name(prov))); fmt::format("{}-{}", name, app_config::get_provider_name(prov)));
lock.unlock(); nonce_lock.unlock();
return ret; return ret;
} }
void handlers::generate_config(provider_type prov, std::string_view name, void ui_server::generate_config(provider_type prov, std::string_view name,
const json &cfg, const json &cfg,
std::optional<std::string> data_dir) const { std::optional<std::string> data_dir) const {
REPERTORY_USES_FUNCTION_NAME(); REPERTORY_USES_FUNCTION_NAME();
std::map<std::string, std::string> values{}; std::map<std::string, std::string> 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, void ui_server::handle_put_mount_auto_start(const httplib::Request &req,
httplib::Response &res) const { httplib::Response &res) const {
auto prov = provider_type_from_string(req.get_param_value("type")); auto prov = provider_type_from_string(req.get_param_value("type"));
auto name = req.get_param_value("name"); 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; res.status = http_error_codes::ok;
} }
void handlers::handle_put_mount_location(const httplib::Request &req, void ui_server::handle_put_mount_location(const httplib::Request &req,
httplib::Response &res) const { httplib::Response &res) const {
auto prov = provider_type_from_string(req.get_param_value("type")); auto prov = provider_type_from_string(req.get_param_value("type"));
auto name = req.get_param_value("name"); auto name = req.get_param_value("name");
auto location = req.get_param_value("location"); 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; 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) #if defined(_WIN32)
constexpr std::array<std::string_view, 26U> letters{ constexpr std::array<std::string_view, 26U> letters{
"a:", "b:", "c:", "d:", "e:", "f:", "g:", "h:", "i:", "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; res.status = http_error_codes::ok;
} }
void handlers::handle_get_mount(const httplib::Request &req, void ui_server::handle_get_mount(const httplib::Request &req,
httplib::Response &res) const { httplib::Response &res) const {
REPERTORY_USES_FUNCTION_NAME(); REPERTORY_USES_FUNCTION_NAME();
auto prov = provider_type_from_string(req.get_param_value("type")); 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; 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()}; auto data_dir = utils::file::directory{app_config::get_root_data_directory()};
nlohmann::json result; nlohmann::json result;
@@ -601,8 +464,8 @@ void handlers::handle_get_mount_list(httplib::Response &res) {
res.status = http_error_codes::ok; res.status = http_error_codes::ok;
} }
void handlers::handle_get_mount_location(const httplib::Request &req, void ui_server::handle_get_mount_location(const httplib::Request &req,
httplib::Response &res) const { httplib::Response &res) const {
auto name = req.get_param_value("name"); auto name = req.get_param_value("name");
auto prov = provider_type_from_string(req.get_param_value("type")); 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; res.status = http_error_codes::ok;
} }
void handlers::handle_get_mount_status(const httplib::Request &req, void ui_server::handle_get_mount_status(const httplib::Request &req,
httplib::Response &res) const { httplib::Response &res) const {
auto name = req.get_param_value("name"); auto name = req.get_param_value("name");
auto prov = provider_type_from_string(req.get_param_value("type")); 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; res.status = http_error_codes::ok;
} }
void handlers::handle_get_nonce(httplib::Response &res) { void ui_server::handle_get_nonce(httplib::Response &res) {
mutex_lock lock(nonce_mtx_); mutex_lock nonce_lock(nonce_mtx_);
nonce_data nonce{}; nonce_data nonce{};
nonce_lookup_[nonce.nonce] = nonce; nonce_lookup_[nonce.nonce] = nonce;
@@ -657,7 +520,7 @@ void handlers::handle_get_nonce(httplib::Response &res) {
res.status = http_error_codes::ok; 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(); auto settings = config_->to_json();
settings[JSON_API_PASSWORD] = ""; settings[JSON_API_PASSWORD] = "";
settings.erase(JSON_MOUNT_LOCATIONS); settings.erase(JSON_MOUNT_LOCATIONS);
@@ -665,18 +528,18 @@ void handlers::handle_get_settings(httplib::Response &res) const {
res.status = http_error_codes::ok; res.status = http_error_codes::ok;
} }
void handlers::handle_get_test(const httplib::Request &req, void ui_server::handle_get_test(const httplib::Request &req,
httplib::Response &res) const { httplib::Response &res) const {
REPERTORY_USES_FUNCTION_NAME(); 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 name = req.get_param_value("name");
auto prov = provider_type_from_string(req.get_param_value("type")); auto prov = provider_type_from_string(req.get_param_value("type"));
auto cfg = nlohmann::json::parse(req.get_param_value("config")); auto cfg = nlohmann::json::parse(req.get_param_value("config"));
auto data_dir = utils::path::combine( 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 { try {
generate_config(prov, name, cfg, data_dir); 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)); fmt::format("failed to remove data directory|{}", data_dir));
} }
void handlers::handle_post_add_mount(const httplib::Request &req, void ui_server::handle_post_add_mount(const httplib::Request &req,
httplib::Response &res) const { httplib::Response &res) const {
auto name = req.get_param_value("name"); auto name = req.get_param_value("name");
auto prov = provider_type_from_string(req.get_param_value("type")); auto prov = provider_type_from_string(req.get_param_value("type"));
if (data_directory_exists(prov, name)) { 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; res.status = http_error_codes::ok;
} }
void handlers::handle_post_mount(const httplib::Request &req, void ui_server::handle_post_mount(const httplib::Request &req,
httplib::Response &res) { httplib::Response &res) {
auto name = req.get_param_value("name"); auto name = req.get_param_value("name");
auto prov = provider_type_from_string(req.get_param_value("type")); 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; res.status = http_error_codes::internal_error;
} }
void handlers::handle_put_set_value_by_name(const httplib::Request &req, void ui_server::handle_put_set_value_by_name(const httplib::Request &req,
httplib::Response &res) const { httplib::Response &res) const {
auto name = req.get_param_value("name"); auto name = req.get_param_value("name");
auto prov = provider_type_from_string(req.get_param_value("type")); 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; res.status = http_error_codes::ok;
} }
void handlers::handle_put_setting(const httplib::Request &req, void ui_server::handle_put_setting(const httplib::Request &req,
httplib::Response &res) const { httplib::Response &res) const {
auto name = req.get_param_value("name"); auto name = req.get_param_value("name");
auto value = req.get_param_value("value"); 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; res.status = http_error_codes::ok;
} }
void handlers::handle_put_settings(const httplib::Request &req, void ui_server::handle_put_settings(const httplib::Request &req,
httplib::Response &res) const { httplib::Response &res) const {
auto data = nlohmann::json::parse(req.get_param_value("data")); auto data = nlohmann::json::parse(req.get_param_value("data"));
if (data.contains(JSON_API_PASSWORD)) { 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; res.status = http_error_codes::ok;
} }
auto handlers::launch_process(provider_type prov, std::string_view name, auto ui_server::launch_process(provider_type prov, std::string_view name,
std::vector<std::string> args, std::vector<std::string> args,
bool background) const bool background) const
-> std::vector<std::string> { -> std::vector<std::string> {
REPERTORY_USES_FUNCTION_NAME(); 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( auto &inst_mtx = mtx_lookup_[fmt::format(
"{}-{}", name, app_config::get_provider_name(prov))]; "{}-{}", name, app_config::get_provider_name(prov))];
lock.unlock(); nonce_lock.unlock();
recur_mutex_lock inst_lock(inst_mtx); recur_mutex_lock inst_lock(inst_mtx);
if (background) { if (background) {
@@ -912,8 +775,8 @@ auto handlers::launch_process(provider_type prov, std::string_view name,
false); false);
} }
auto handlers::mount(provider_type prov, std::string_view name, auto ui_server::mount(provider_type prov, std::string_view name,
const std::string &location) -> bool { const std::string &location) -> bool {
#if defined(_WIN32) #if defined(_WIN32)
if (utils::file::directory{location}.exists()) { if (utils::file::directory{location}.exists()) {
#else // !defined(_WIN32) #else // !defined(_WIN32)
@@ -927,28 +790,33 @@ auto handlers::mount(provider_type prov, std::string_view name,
config_->set_mount_location(prov, name, location); config_->set_mount_location(prov, name, location);
static std::mutex mount_mtx; static std::mutex mount_mtx;
mutex_lock lock(mount_mtx); mutex_lock nonce_lock(mount_mtx);
launch_process(prov, name, {location}, true); launch_process(prov, name, {location}, true);
return true; return true;
} }
void handlers::removed_expired_nonces() { void ui_server::notify_and_unlock(unique_mutex_lock &nonce_lock) {
unique_mutex_lock lock(nonce_mtx_); nonce_notify_.notify_all();
lock.unlock(); 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) { while (not stop_requested) {
lock.lock(); nonce_lock.lock();
auto nonces = nonce_lookup_; auto nonces = nonce_lookup_;
lock.unlock(); notify_and_unlock(nonce_lock);
for (const auto &[key, value] : nonces) { for (const auto &[key, value] : nonces) {
if (std::chrono::duration_cast<std::chrono::seconds>( if (std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now() - value.creation) std::chrono::system_clock::now() - value.creation)
.count() >= nonce_timeout) { .count() >= nonce_timeout) {
lock.lock(); nonce_lock.lock();
nonce_lookup_.erase(key); nonce_lookup_.erase(key);
lock.unlock(); notify_and_unlock(nonce_lock);
} }
} }
@@ -956,18 +824,18 @@ void handlers::removed_expired_nonces() {
break; break;
} }
lock.lock(); nonce_lock.lock();
if (stop_requested) { if (stop_requested) {
break; break;
} }
nonce_notify_.wait_for(lock, std::chrono::seconds(1U)); nonce_notify_.wait_for(nonce_lock, std::chrono::seconds(1U));
lock.unlock(); notify_and_unlock(nonce_lock);
} }
} }
void handlers::set_key_value(provider_type prov, std::string_view name, void ui_server::set_key_value(provider_type prov, std::string_view name,
std::string_view key, std::string_view value, std::string_view key, std::string_view value,
std::optional<std::string> data_dir) const { std::optional<std::string> data_dir) const {
std::vector<std::string> args; std::vector<std::string> args;
if (data_dir.has_value()) { if (data_dir.has_value()) {
args.emplace_back("-dd"); args.emplace_back("-dd");
@@ -978,4 +846,160 @@ void handlers::set_key_value(provider_type prov, std::string_view name,
args.emplace_back(value); args.emplace_back(value);
launch_process(prov, name, args, false); 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<std::thread>([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 } // namespace repertory::ui

View File

@@ -27,7 +27,7 @@
namespace repertory { namespace repertory {
TEST(curl_comm_test, can_create_s3_host_config) { TEST(curl_comm_test, can_create_s3_host_config) {
s3_config config{}; s3_config config{};
config.bucket = "repertory"; config.bucket = REPERTORY;
config.url = "https://s3.test.com"; config.url = "https://s3.test.com";
config.region = "any"; config.region = "any";
config.use_path_style = false; 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) { TEST(curl_comm_test, can_create_s3_host_config_with_path_style) {
s3_config config{}; s3_config config{};
config.bucket = "repertory"; config.bucket = REPERTORY;
config.url = "https://s3.test.com"; config.url = "https://s3.test.com";
config.region = "any"; config.region = "any";
config.use_path_style = true; 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) { TEST(curl_comm_test, can_create_s3_host_config_with_region) {
s3_config config{}; s3_config config{};
config.bucket = "repertory"; config.bucket = REPERTORY;
config.url = "https://s3.test.com"; config.url = "https://s3.test.com";
config.region = "any"; config.region = "any";
config.use_region_in_url = true; 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) { TEST(curl_comm_test, can_create_s3_host_config_with_region_and_path_style) {
s3_config config{}; s3_config config{};
config.bucket = "repertory"; config.bucket = REPERTORY;
config.url = "https://s3.test.com"; config.url = "https://s3.test.com";
config.region = "any"; config.region = "any";
config.use_region_in_url = true; config.use_region_in_url = true;