From f473d5c85589a9a09b78e575c22e5da5cf24377a Mon Sep 17 00:00:00 2001 From: "Scott E. Graves" Date: Sat, 6 Sep 2025 18:19:41 -0500 Subject: [PATCH] [ui] Add auto-mount on first launch functionality #52 --- .../include/utils/unix/unix_utils.hpp | 20 +++-- .../src/drives/fuse/fuse_base.cpp | 20 +++-- .../src/utils/unix/unix_utils.cpp | 32 ++++---- .../repertory/src/ui/mgmt_app_config.cpp | 77 ++++++++++++------- support/include/utils/unix.hpp | 4 +- support/include/utils/windows.hpp | 14 ++-- support/src/utils/unix.cpp | 15 +--- support/src/utils/windows.cpp | 49 ++++++------ 8 files changed, 131 insertions(+), 100 deletions(-) diff --git a/repertory/librepertory/include/utils/unix/unix_utils.hpp b/repertory/librepertory/include/utils/unix/unix_utils.hpp index 3da6cdc3..fdbe7c2e 100644 --- a/repertory/librepertory/include/utils/unix/unix_utils.hpp +++ b/repertory/librepertory/include/utils/unix/unix_utils.hpp @@ -43,6 +43,17 @@ enum class launchctl_type : std::uint8_t { bootstrap, kickstart, }; + +struct plist_cfg final { + std::vector args; + bool keep_alive{false}; + std::string label; + std::string plist_path; + bool run_at_load{false}; + std::string stderr_log{"/tmp/stderr.log"}; + std::string stdout_log{"/tmp/stdout.log"}; + std::string working_dir{"/tmp"}; +}; #endif // defined(__APPLE__) [[nodiscard]] auto create_daemon(std::function main_func) -> int; @@ -57,12 +68,9 @@ void windows_create_to_unix(const UINT32 &create_options, const UINT32 &granted_access, std::uint32_t &flags, remote::file_mode &mode); #if defined(__APPLE__) -[[nodiscard]] auto generate_launchd_plist( - const std::string &label, std::string plist_path, - const std::vector &args, bool run_at_load = false, - bool keep_alive = false, const std::string &working_dir = "/tmp", - const std::string &stdout_log = "/tmp/stdout.log", - const std::string &stderr_log = "/tmp/stderr.log") -> bool; +[[nodiscard]] auto generate_launchd_plist(const plist_cfg &cfg, + bool overwrite_existing = true) + -> bool; [[nodiscard]] auto launchctl_command(std::string_view label, launchctl_type type) -> int; diff --git a/repertory/librepertory/src/drives/fuse/fuse_base.cpp b/repertory/librepertory/src/drives/fuse/fuse_base.cpp index 91aa2e5a..4095719a 100644 --- a/repertory/librepertory/src/drives/fuse/fuse_base.cpp +++ b/repertory/librepertory/src/drives/fuse/fuse_base.cpp @@ -448,13 +448,19 @@ auto fuse_base::mount([[maybe_unused]] std::vector orig_args, orig_args[0U] = utils::path::combine(".", {"repertory"}); orig_args.insert(std::next(orig_args.begin()), "-f"); - if (not utils::generate_launchd_plist( - label_, utils::path::combine("~", {"/Library/LaunchAgents"}), - orig_args, false, false, utils::path::absolute("."), - fmt::format("/tmp/repertory_{}_{}.out", - provider_type_to_string(prov), unique_id), - fmt::format("/tmp/repertory_{}_{}.err", - provider_type_to_string(prov), unique_id))) { + utils::plist_cfg cfg{}; + cfg.args = orig_args; + cfg.keep_alive = false; + cfg.label = label_; + cfg.plist_path = utils::path::combine("~", {"/Library/LaunchAgents"}); + cfg.run_at_load = false; + cfg.stderr_log = fmt::format("/tmp/repertory_{}_{}.err", + provider_type_to_string(prov), unique_id); + cfg.stdout_log = fmt::format("/tmp/repertory_{}_{}.out", + provider_type_to_string(prov), unique_id); + cfg.working_dir = utils::path::absolute("."); + + if (not utils::generate_launchd_plist(cfg, true)) { std::cerr << fmt::format("FATAL: Failed to generate plist|{}", label_) << std::endl; return -1; diff --git a/repertory/librepertory/src/utils/unix/unix_utils.cpp b/repertory/librepertory/src/utils/unix/unix_utils.cpp index 36fd4a82..452ffcc6 100644 --- a/repertory/librepertory/src/utils/unix/unix_utils.cpp +++ b/repertory/librepertory/src/utils/unix/unix_utils.cpp @@ -308,12 +308,13 @@ auto create_daemon(std::function main_func) -> int { } #if defined(__APPLE__) -auto generate_launchd_plist(const std::string &label, std::string plist_path, - const std::vector &args, - bool run_at_load, bool keep_alive, - const std::string &working_dir, - const std::string &stdout_log, - const std::string &stderr_log) -> bool { +auto generate_launchd_plist(const plist_cfg &cfg, bool overwrite_existing) + -> bool { + auto file = utils::path::combine(cfg.plist_path, {cfg.label + ".plist"}); + if (utils::file::file{file}.exists() && not overwrite_existing) { + return true; + } + pugi::xml_document doc; auto decl = doc.append_child(pugi::node_declaration); decl.append_attribute("version") = "1.0"; @@ -325,11 +326,11 @@ auto generate_launchd_plist(const std::string &label, std::string plist_path, auto dict = plist.append_child("dict"); dict.append_child("key").text().set("Label"); - dict.append_child("string").text().set(label.c_str()); + dict.append_child("string").text().set(cfg.label.c_str()); dict.append_child("key").text().set("ProgramArguments"); auto array = dict.append_child("array"); - for (const auto &arg : args) { + for (const auto &arg : cfg.args) { array.append_child("string").text().set(arg.c_str()); } @@ -347,23 +348,22 @@ auto generate_launchd_plist(const std::string &label, std::string plist_path, } dict.append_child("key").text().set("WorkingDirectory"); - dict.append_child("string").text().set(working_dir.c_str()); + dict.append_child("string").text().set(cfg.working_dir.c_str()); dict.append_child("key").text().set("KeepAlive"); - dict.append_child(keep_alive ? "true" : "false"); + dict.append_child(cfg.keep_alive ? "true" : "false"); dict.append_child("key").text().set("RunAtLoad"); - dict.append_child(run_at_load ? "true" : "false"); + dict.append_child(cfg.run_at_load ? "true" : "false"); dict.append_child("key").text().set("StandardOutPath"); - dict.append_child("string").text().set(stdout_log.c_str()); + dict.append_child("string").text().set(cfg.stdout_log.c_str()); dict.append_child("key").text().set("StandardErrorPath"); - dict.append_child("string").text().set(stderr_log.c_str()); + dict.append_child("string").text().set(cfg.stderr_log.c_str()); - return doc.save_file( - utils::path::combine(plist_path, {label + ".plist"}).c_str(), " ", - pugi::format_indent | pugi::format_write_bom); + return doc.save_file(file.c_str(), " ", + pugi::format_indent | pugi::format_write_bom); } auto launchctl_command(std::string_view label, launchctl_type type) -> int { diff --git a/repertory/repertory/src/ui/mgmt_app_config.cpp b/repertory/repertory/src/ui/mgmt_app_config.cpp index 519913c7..f823ac5a 100644 --- a/repertory/repertory/src/ui/mgmt_app_config.cpp +++ b/repertory/repertory/src/ui/mgmt_app_config.cpp @@ -224,60 +224,79 @@ void mgmt_app_config::set_auto_start(bool auto_start) { opts.icon_path = utils::path::combine(".", {"repertory.png"}); opts.terminal = true; - if (not utils::create_autostart_entry(opts)) { + if (utils::create_autostart_entry(opts, false)) { + utils::error::handle_info(function_name, + "created auto-start entry|name|repertory"); + } else { utils::error::raise_error( function_name, utils::get_last_error_code(), - fmt::format("failed to create auto-start entry")); + "failed to create auto-start entry|name|repertory"); } - } else if (not utils::remove_autostart_entry("repertory")) { + } else if (utils::remove_autostart_entry("repertory")) { + utils::error::handle_info(function_name, + "removed auto-start entry|name|repertory"); + } else { utils::error::raise_error( function_name, utils::get_last_error_code(), - fmt::format("failed to remove auto-start entry")); + "failed to remove auto-start entry|name|repertory"); } #endif // defined(__linux__) #if defined(_WIN32) if (auto_start) { - if (not utils::create_shortcut( - utils::path::combine(L".", {L"repertory"}), {L"-ui", L"-lo"}, - utils::path::absolute(L"."), L"repertory")) { + if (utils::create_shortcut(utils::path::combine(L".", {L"repertory"}), + {L"-ui", L"-lo"}, utils::path::absolute(L"."), + L"repertory", false)) { + utils::error::handle_info(function_name, + "created auto-start entry|name|repertory"); + } else { utils::error::raise_error( function_name, utils::get_last_error_code(), - fmt::format("failed to create auto-start entry")); + "failed to create auto-start entry|name|repertory"); } - } else if (not utils::remove_shortcut(L"repertory")) { + } else if (utils::remove_shortcut(L"repertory")) { + utils::error::handle_info(function_name, + "removed auto-start entry|name|repertory"); + } else { utils::error::raise_error( function_name, utils::get_last_error_code(), - fmt::format("failed to remove auto-start entry")); + "failed to remove auto-start entry|name|repertory"); } #endif // defined(_WIN32) #if defined(__APPLE__) - auto label = "com.fifthgrid.blockstorage.repertory.ui"; - auto plist_file = utils::file::file{ + auto plist_path = utils::file::file{ utils::path::combine("~", {"/Library/LaunchAgents", label}), }; if (auto_start) { - if (not plist_file.exists()) { - std::vector args = { - utils::path::combine(".", {"repertory"}), - "-ui", - "-lo", - }; - - if (not utils::generate_launchd_plist( - label, utils::path::combine("~", {"/Library/LaunchAgents"}), - args, true, false, utils::path::absolute("."), - "/tmp/repertory_ui.out", "/tmp/repertory_ui.err")) { - utils::error::raise_error( - function_name, utils::get_last_error_code(), - fmt::format("failed to create auto-start entry")); - } + utils::plist_cfg cfg{}; + cfg.args = { + utils::path::combine(".", {"repertory"}), + "-ui", + "-lo", + }; + cfg.keep_alive = false; + cfg.label = "com.fifthgrid.blockstorage.repertory.ui"; + cfg.plist_path = plist_path; + cfg.run_at_load = true; + cfg.stderr_log = "/tmp/repertory_ui.err"; + cfg.stdout_log = "/tmp/repertory_ui.out"; + cfg.working_dir = utils::path::absolute("."); + if (utils::generate_launchd_plist(cfg, false)) { + utils::error::handle_info(function_name, + "created auto-start entry|name|repertory"); + } else { + utils::error::raise_error( + function_name, utils::get_last_error_code(), + "failed to create auto-start entry|name|repertory"); } - } else if (not plist_file.remove()) { + } else if (plist_path.remove()) { + utils::error::handle_info(function_name, + "removed auto-start entry|name|repertory"); + } else { utils::error::raise_error( function_name, utils::get_last_error_code(), - fmt::format("failed to remove auto-start entry")); + "failed to remove auto-start entry|name|repertory"); } #endif // defined(__APPLE__) } else { diff --git a/support/include/utils/unix.hpp b/support/include/utils/unix.hpp index 273eff6f..d73f1875 100644 --- a/support/include/utils/unix.hpp +++ b/support/include/utils/unix.hpp @@ -51,7 +51,9 @@ template #endif // defined(__APPLE__) #if defined(__linux__) -[[nodiscard]] auto create_autostart_entry(create_autostart_opts opts) -> bool; +[[nodiscard]] auto create_autostart_entry(create_autostart_opts opts, + bool overwrite_existing = true) + -> bool; #endif // defined(__linux__) [[nodiscard]] auto get_last_error_code() -> int; diff --git a/support/include/utils/windows.hpp b/support/include/utils/windows.hpp index 1b97db22..86e43463 100644 --- a/support/include/utils/windows.hpp +++ b/support/include/utils/windows.hpp @@ -42,12 +42,16 @@ void free_console(); [[nodiscard]] auto run_process_elevated(std::vector args) -> int; +struct shortcut_cfg final { + std::wstring arguments; + std::wstring exe_path; + std::wstring location{get_startup_folder()}; + std::wstring shortcut_name; + std::wstring working_directory; +}; + [[nodiscard]] -auto create_shortcut(const std::wstring &exe_path, - const std::wstring &arguments, - const std::wstring &working_directory, - const std::wstring &shortcut_name = L"", - const std::wstring &location = get_startup_folder()) +auto create_shortcut(const shortcut_cfg &cfg, bool overwrite_existing = true) -> bool; [[nodiscard]] auto diff --git a/support/src/utils/unix.cpp b/support/src/utils/unix.cpp index f004dd03..67c91599 100644 --- a/support/src/utils/unix.cpp +++ b/support/src/utils/unix.cpp @@ -180,11 +180,12 @@ auto convert_to_uint64(const pthread_t &thread) -> std::uint64_t { #endif // !defined(__APPLE__) #if defined(__linux__) -auto create_autostart_entry(create_autostart_opts opts) -> bool { +auto create_autostart_entry(create_autostart_opts opts, bool overwrite_existing) + -> bool { REPERTORY_USES_FUNCTION_NAME(); auto file = desktop_file_path_for(opts.app_name); - if (utils::file::file{file}.exists()) { + if (utils::file::file{file}.exists() && not overwrite_existing) { return true; } @@ -248,8 +249,6 @@ auto create_autostart_entry(create_autostart_opts opts) -> bool { chmod(file.c_str(), 0644); #endif // defined(__linux__) || defined(__APPLE__) - utils::error::handle_info( - function_name, fmt::format("created auto-start entry|path|{}", file)); return true; } #endif // defined(__linux__) @@ -283,13 +282,7 @@ auto remove_autostart_entry(std::string_view name) -> bool { return true; } - auto ret = utils::file::file{file}.remove(); - if (ret) { - utils::error::handle_info( - function_name, fmt::format("removed auto-start entry|path|{}", file)); - } - - return ret; + return utils::file::file{file}.remove(); } #endif // defined(__linux__) diff --git a/support/src/utils/windows.cpp b/support/src/utils/windows.cpp index efa605d1..d3c7f73a 100644 --- a/support/src/utils/windows.cpp +++ b/support/src/utils/windows.cpp @@ -156,11 +156,7 @@ auto get_startup_folder() -> std::wstring { return str; } -auto create_shortcut(const std::wstring &exe_path, - const std::wstring &arguments, - const std::wstring &working_directory, - const std::wstring &shortcut_name, - const std::wstring &location) -> bool { +auto create_shortcut(const shortcut_cfg &cfg, bool overwrite_existing) -> bool { REPERTORY_USES_FUNCTION_NAME(); const auto hr_hex = [](HRESULT hr) -> std::string { @@ -170,26 +166,29 @@ auto create_shortcut(const std::wstring &exe_path, return oss.str(); }; - if (location.empty()) { - utils::error::handle_error(function_name, "Shortcut location was empty."); + if (cfg.location.empty()) { + utils::error::handle_error(function_name, "shortcut location was empty"); return false; } - { - std::error_code ec_mk; - std::filesystem::create_directories(std::filesystem::path{location}, ec_mk); + if (not utils::file::directory{cfg.location}.create_directory()) { + utils::error::handle_error(function_name, + "failed to create shortcut directory|path|" + + utils::string::to_utf8(cfg.location)); + return false; } - std::filesystem::path exe_p{exe_path}; - std::wstring final_name = shortcut_name.empty() - ? (exe_p.stem().wstring() + L".lnk") - : shortcut_name; + auto final_name = cfg.shortcut_name.empty() + ? utils::path::strip_to_file_name(cfg.exe_path) + : cfg.shortcut_name; if (not final_name.ends_with(L".lnk")) { final_name += L".lnk"; } - const std::filesystem::path lnk_path = - std::filesystem::path{location} / final_name; + auto lnk_path = utils::path::combine(cfg.location, {final_name}); + if (utils::file::file{lnk_path}.exists() && not overwrite_existing) { + return true; + } IShellLinkW *psl{nullptr}; HRESULT result = ::CoCreateInstance(CLSID_ShellLink, nullptr, @@ -202,7 +201,7 @@ auto create_shortcut(const std::wstring &exe_path, return false; } - result = psl->SetPath(exe_path.c_str()); + result = psl->SetPath(cfg.exe_path.c_str()); if (FAILED(result)) { utils::error::handle_error(function_name, std::string("IShellLink::SetPath failed: ") + @@ -211,8 +210,8 @@ auto create_shortcut(const std::wstring &exe_path, return false; } - if (not arguments.empty()) { - result = psl->SetArguments(arguments.c_str()); + if (not cfg.arguments.empty()) { + result = psl->SetArguments(cfg.arguments.c_str()); if (FAILED(result)) { utils::error::handle_error( function_name, @@ -222,8 +221,8 @@ auto create_shortcut(const std::wstring &exe_path, } } - if (not working_directory.empty()) { - result = psl->SetWorkingDirectory(working_directory.c_str()); + if (not cfg.working_directory.empty()) { + result = psl->SetWorkingDirectory(cfg.working_directory.c_str()); if (FAILED(result)) { utils::error::handle_error( function_name, @@ -243,10 +242,10 @@ auto create_shortcut(const std::wstring &exe_path, return false; } - // Best-effort overwrite - { - std::error_code ec; - std::filesystem::remove(lnk_path, ec); + if (not utils::file::file{lnk_path}.remove()) { + utils::error::handle_error( + function_name, "failed to remove existing shortcut|path|" + lnk_path); + return false; } IPersistFile *ppf{nullptr};