Merge branch 'v2.1.0-rc-develop' of https://git.fifthgrid.com/blockstorage/repertory into v2.1.0-rc-develop

This commit is contained in:
2025-09-15 12:20:06 -05:00
8 changed files with 431 additions and 308 deletions

View File

@@ -134,6 +134,19 @@ unmount_existing_repertory_volumes() {
# ----- user LaunchAgents (by LABEL_PREFIX only) ----- # ----- user LaunchAgents (by LABEL_PREFIX only) -----
get_plist_label() { /usr/bin/defaults read "$1" Label 2>/dev/null || /usr/libexec/PlistBuddy -c "Print :Label" "$1" 2>/dev/null || basename "$1" .plist; } get_plist_label() { /usr/bin/defaults read "$1" Label 2>/dev/null || /usr/libexec/PlistBuddy -c "Print :Label" "$1" 2>/dev/null || basename "$1" .plist; }
# Return the executable path a LaunchAgent runs (ProgramArguments[0] or Program).
# Echoes empty string if neither is present.
get_plist_exec_path() {
local plist="$1" arg0=""
# Prefer ProgramArguments[0]
arg0="$(/usr/libexec/PlistBuddy -c 'Print :ProgramArguments:0' "$plist" 2>/dev/null || true)"
if [[ -z "$arg0" ]]; then
# Fallback to Program (older style)
arg0="$(/usr/libexec/PlistBuddy -c 'Print :Program' "$plist" 2>/dev/null || true)"
fi
printf '%s\n' "$arg0"
}
snapshot_launchagents_user() { snapshot_launchagents_user() {
local user_agents="$HOME/Library/LaunchAgents" local user_agents="$HOME/Library/LaunchAgents"
snapfile="$(/usr/bin/mktemp "/tmp/repertory_launchagents.XXXXXX")" || snapfile="" snapfile="$(/usr/bin/mktemp "/tmp/repertory_launchagents.XXXXXX")" || snapfile=""
@@ -141,16 +154,48 @@ snapshot_launchagents_user() {
warn "Could not create temporary snapshot file; skipping LaunchAgent restart snapshot." warn "Could not create temporary snapshot file; skipping LaunchAgent restart snapshot."
return 0 return 0
fi fi
if [[ -d "$user_agents" ]]; then [[ -d "$user_agents" ]] || return 0
/usr/bin/find "$user_agents" -maxdepth 1 -type f -name "${LABEL_PREFIX}"'*.plist' -print 2>/dev/null |
while IFS= read -r plist; do # We collect non-UI first, then UI last, to preserve restart order later.
[[ -z "$plist" ]] && continue local tmp_nonui tmp_ui
local label tmp_nonui="$(/usr/bin/mktemp "/tmp/repertory_launchagents.nonui.XXXXXX")" || tmp_nonui=""
label="$(get_plist_label "$plist")" tmp_ui="$(/usr/bin/mktemp "/tmp/repertory_launchagents.ui.XXXXXX")" || tmp_ui=""
[[ -n "$label" && "$label" == "${LABEL_PREFIX}"* ]] || continue
printf "%s\t%s\n" "$plist" "$label" >>"$snapfile" /usr/bin/find "$user_agents" -maxdepth 1 -type f -name "${LABEL_PREFIX}"'*.plist' -print 2>/dev/null |
done while IFS= read -r plist; do
fi [[ -z "$plist" ]] && continue
# Label must match the prefix
local label
label="$(get_plist_label "$plist")"
[[ -n "$label" && "$label" == "${LABEL_PREFIX}"* ]] || continue
# Executable must point into the *installed* app path
local exec_path
exec_path="$(get_plist_exec_path "$plist")"
[[ -n "$exec_path" ]] || continue
# Normalize: only accept absolute paths under $dest_app (e.g. .../repertory.app/Contents/...)
if [[ "$exec_path" != "$dest_app/"* ]]; then
# Not one of ours; skip
continue
fi
# Defer UI label to the end
if [[ "$label" == "$UI_LABEL" ]]; then
[[ -n "$tmp_ui" ]] && printf "%s\t%s\n" "$plist" "$label" >>"$tmp_ui"
else
[[ -n "$tmp_nonui" ]] && printf "%s\t%s\n" "$plist" "$label" >>"$tmp_nonui"
fi
done
# Stitch together: non-UI first, then UI
[[ -s "$tmp_nonui" ]] && /bin/cat "$tmp_nonui" >>"$snapfile"
[[ -s "$tmp_ui" ]] && /bin/cat "$tmp_ui" >>"$snapfile"
[[ -n "$tmp_nonui" ]] && /bin/rm -f "$tmp_nonui" 2>/dev/null || true
[[ -n "$tmp_ui" ]] && /bin/rm -f "$tmp_ui" 2>/dev/null || true
log "Snapshot contains $(/usr/bin/wc -l <"$snapfile" 2>/dev/null || echo 0) LaunchAgent(s)."
} }
unload_launchd_helpers_user() { unload_launchd_helpers_user() {

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,10 +36,11 @@ 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_{std::string{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_{std::string{REPERTORY}};
std::atomic<bool> auto_start_{true}; std::atomic<bool> auto_start_{true};
std::atomic<event_level> event_level_{event_level::info};
std::unordered_map<provider_type, std::unordered_map<provider_type,
std::unordered_map<std::string, std::string>> std::unordered_map<std::string, std::string>>
locations_; locations_;
@@ -71,6 +72,10 @@ public:
[[nodiscard]] auto get_auto_start_list() const [[nodiscard]] auto get_auto_start_list() const
-> std::unordered_map<provider_type, std::vector<std::string>>; -> std::unordered_map<provider_type, std::vector<std::string>>;
[[nodiscard]] auto get_event_level() const -> event_level {
return event_level_;
}
[[nodiscard]] auto get_hidden() const -> bool { return hidden_; } [[nodiscard]] auto get_hidden() const -> bool { return hidden_; }
[[nodiscard]] auto get_launch_only() const -> bool { return launch_only_; } [[nodiscard]] auto get_launch_only() const -> bool { return launch_only_; }
@@ -92,6 +97,8 @@ public:
void set_auto_start(provider_type prov, std::string_view name, void set_auto_start(provider_type prov, std::string_view name,
bool auto_start); bool auto_start);
void set_event_level(event_level level);
void set_hidden(bool hidden); void set_hidden(bool hidden);
void set_launch_only(bool launch_only); void set_launch_only(bool launch_only);

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,9 +26,10 @@
#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/error_utils.hpp"
#include "utils/polling.hpp" #include "utils/polling.hpp"
using namespace repertory; using namespace repertory;
@@ -80,35 +81,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::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 {
server->start();
} catch (const std::exception &ex) {
utils::error::raise_error(function_name, ex, "failed to start ui");
}
try {
server->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::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

@@ -112,6 +112,13 @@ mgmt_app_config::mgmt_app_config(bool hidden, bool launch_only)
should_save = true; should_save = true;
} }
if (data.contains(JSON_EVENT_LEVEL)) {
event_level_ = event_level_from_string(
data.at(JSON_EVENT_LEVEL).get<std::string>());
} else {
should_save = true;
}
if (data.contains(JSON_MOUNT_LOCATIONS)) { if (data.contains(JSON_MOUNT_LOCATIONS)) {
locations_ = from_json<std::string>(data.at(JSON_MOUNT_LOCATIONS)); locations_ = from_json<std::string>(data.at(JSON_MOUNT_LOCATIONS));
} else { } else {
@@ -249,11 +256,12 @@ 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(".", {std::string{REPERTORY} + ".png"});
cfg.terminal = true; cfg.terminal = true;
if (utils::create_autostart_entry(cfg, false)) { if (utils::create_autostart_entry(cfg, false)) {
@@ -264,7 +272,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 +286,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 +299,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 +315,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",
}; };
@@ -365,6 +373,10 @@ void mgmt_app_config::set_auto_start(provider_type prov, std::string_view name,
save(); save();
} }
void mgmt_app_config::set_event_level(event_level level) {
event_level_ = level;
}
void mgmt_app_config::set_hidden(bool hidden) { hidden_ = hidden; } void mgmt_app_config::set_hidden(bool hidden) { hidden_ = hidden; }
void mgmt_app_config::set_launch_only(bool launch_only) { void mgmt_app_config::set_launch_only(bool launch_only) {
@@ -395,6 +407,7 @@ auto mgmt_app_config::to_json() const -> nlohmann::json {
data[JSON_API_PORT] = api_port_; data[JSON_API_PORT] = api_port_;
data[JSON_API_USER] = api_user_; data[JSON_API_USER] = api_user_;
data[JSON_AUTO_START] = auto_start_; data[JSON_AUTO_START] = auto_start_;
data[JSON_EVENT_LEVEL] = event_level_to_string(event_level_);
data[JSON_MOUNT_AUTO_START] = map_to_json(mount_auto_start_); data[JSON_MOUNT_AUTO_START] = map_to_json(mount_auto_start_);
data[JSON_MOUNT_LOCATIONS] = map_to_json(locations_); data[JSON_MOUNT_LOCATIONS] = map_to_json(locations_);
return data; return data;

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"
@@ -109,54 +111,30 @@ namespace {
return errno; return errno;
#endif // defined(_WIN32) #endif // defined(_WIN32)
} }
[[nodiscard]] auto is_addr_in_use(int err) -> bool {
#if defined(_WIN32)
return err == WSAEADDRINUSE;
#else // !defined(_WIN32)
return err == EADDRINUSE;
#endif // defined(_WIN32)
}
} // 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 +154,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 +168,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 +179,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 +207,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 +283,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 +339,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 +354,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 +369,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 +398,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 +427,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 +456,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 +475,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 +501,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 +512,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 +520,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 +553,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 +568,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 +595,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 +617,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 +631,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 +655,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 +695,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 +767,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 +782,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 +816,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 +838,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;