[ui] Add auto-mount on first launch functionality #52
This commit is contained in:
		| @@ -43,6 +43,17 @@ enum class launchctl_type : std::uint8_t { | ||||
|   bootstrap, | ||||
|   kickstart, | ||||
| }; | ||||
|  | ||||
| struct plist_cfg final { | ||||
|   std::vector<std::string> 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<int()> 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<std::string> &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; | ||||
|   | ||||
| @@ -448,13 +448,19 @@ auto fuse_base::mount([[maybe_unused]] std::vector<std::string> 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; | ||||
|   | ||||
| @@ -308,12 +308,13 @@ auto create_daemon(std::function<int()> main_func) -> int { | ||||
| } | ||||
|  | ||||
| #if defined(__APPLE__) | ||||
| auto generate_launchd_plist(const std::string &label, std::string plist_path, | ||||
|                             const std::vector<std::string> &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 { | ||||
|   | ||||
| @@ -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<std::string> 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 { | ||||
|   | ||||
| @@ -51,7 +51,9 @@ template <typename thread_t> | ||||
| #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; | ||||
|   | ||||
| @@ -42,12 +42,16 @@ void free_console(); | ||||
|  | ||||
| [[nodiscard]] auto run_process_elevated(std::vector<const char *> 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 | ||||
|   | ||||
| @@ -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__) | ||||
|  | ||||
|   | ||||
| @@ -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}; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user