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:
@@ -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() {
|
||||||
|
@@ -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{};
|
||||||
|
@@ -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);
|
||||||
|
@@ -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_
|
@@ -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);
|
||||||
|
@@ -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;
|
||||||
|
@@ -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
|
@@ -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;
|
||||||
|
Reference in New Issue
Block a user