[ui] Add auto-mount on first launch functionality #52
This commit is contained in:
		| @@ -28,6 +28,17 @@ | ||||
| namespace repertory { | ||||
| class i_provider; | ||||
|  | ||||
| struct create_autostart_opts final { | ||||
|   std::string app_name; | ||||
|   std::optional<std::string> comment; | ||||
|   bool enabled{true}; | ||||
|   std::vector<std::string> exec_args; | ||||
|   std::string exec_path; | ||||
|   std::optional<std::string> icon_path; | ||||
|   std::vector<std::string> only_show_in; | ||||
|   bool terminal{false}; | ||||
| }; | ||||
|  | ||||
| class lock_data final { | ||||
| public: | ||||
|   lock_data(provider_type prov, std::string_view unique_id); | ||||
| @@ -71,6 +82,8 @@ public: | ||||
|       -> bool; | ||||
| }; | ||||
|  | ||||
| [[nodiscard]] auto create_autostart_entry(create_autostart_opts opts) -> bool; | ||||
|  | ||||
| [[nodiscard]] auto create_meta_attributes( | ||||
|     std::uint64_t accessed_date, std::uint32_t attributes, | ||||
|     std::uint64_t changed_date, std::uint64_t creation_date, bool directory, | ||||
| @@ -81,6 +94,8 @@ public: | ||||
|  | ||||
| [[nodiscard]] auto provider_meta_handler(i_provider &provider, bool directory, | ||||
|                                          const api_file &file) -> api_error; | ||||
|  | ||||
| [[nodiscard]] auto remove_autostart_entry(std::string_view name) -> bool; | ||||
| } // namespace repertory | ||||
|  | ||||
| #endif // !defined(_WIN32) | ||||
|   | ||||
| @@ -19,6 +19,7 @@ | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #include <utils/config.hpp> | ||||
| #if !defined(_WIN32) | ||||
|  | ||||
| #include "platform/platform.hpp" | ||||
| @@ -34,6 +35,79 @@ | ||||
| #include "utils/string.hpp" | ||||
| #include "utils/unix.hpp" | ||||
|  | ||||
| namespace { | ||||
| [[nodiscard]] auto get_autostart_dir() -> std::string { | ||||
|   auto config = repertory::utils::get_environment_variable("XDG_CONFIG_HOME"); | ||||
|   if (config.empty()) { | ||||
|     config = repertory::utils::path::combine( | ||||
|         repertory::utils::get_environment_variable("HOME"), {".config"}); | ||||
|   } | ||||
|  | ||||
|   return repertory::utils::path::combine(config, {"autostart"}); | ||||
| } | ||||
|  | ||||
| [[nodiscard]] auto join_args_for_exec(const std::vector<std::string> &args) | ||||
|     -> std::string { | ||||
|   std::string str; | ||||
|   for (const auto &arg : args) { | ||||
|     if (not str.empty()) { | ||||
|       str += ' '; | ||||
|     } | ||||
|  | ||||
|     auto needs_quotes = arg.find_first_of(" \t\"'\\$`") != std::string::npos; | ||||
|     if (needs_quotes) { | ||||
|       str += '"'; | ||||
|       for (const auto &cur_ch : arg) { | ||||
|         if (cur_ch == '"' || cur_ch == '\\') { | ||||
|           str += '\\'; | ||||
|         } | ||||
|         str += cur_ch; | ||||
|       } | ||||
|       str += '"'; | ||||
|     } else { | ||||
|       str += arg; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return str; | ||||
| } | ||||
|  | ||||
| [[nodiscard]] auto sanitize_basename(std::string_view app_name) -> std::string { | ||||
|   std::string out; | ||||
|   out.reserve(app_name.size()); | ||||
|   for (const auto &cur_ch : app_name) { | ||||
|     if ((cur_ch >= 'a' && cur_ch <= 'z') || (cur_ch >= '0' && cur_ch <= '9') || | ||||
|         (cur_ch == '-' || cur_ch == '_')) { | ||||
|       out.push_back(cur_ch); | ||||
|     } else if (cur_ch >= 'A' && cur_ch <= 'Z') { | ||||
|       out.push_back(static_cast<char>(cur_ch - 'A' + 'a')); | ||||
|     } else { | ||||
|       out.push_back('-'); // replace spaces/symbols | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   std::string collapsed; | ||||
|   collapsed.reserve(out.size()); | ||||
|   bool prev_dash = false; | ||||
|   for (const auto &cur_ch : out) { | ||||
|     if (cur_ch == '-') { | ||||
|       if (not prev_dash) { | ||||
|         collapsed.push_back(cur_ch); | ||||
|       } | ||||
|       prev_dash = true; | ||||
|     } else { | ||||
|       collapsed.push_back(cur_ch); | ||||
|       prev_dash = false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (collapsed.empty()) { | ||||
|     collapsed = "app"; | ||||
|   } | ||||
|   return collapsed; | ||||
| } | ||||
| } // namespace | ||||
|  | ||||
| namespace repertory { | ||||
| lock_data::lock_data(provider_type prov, std::string_view unique_id) | ||||
|     : mutex_id_(create_lock_id(prov, unique_id)) { | ||||
| @@ -254,6 +328,102 @@ auto provider_meta_handler(i_provider &provider, bool directory, | ||||
|  | ||||
|   return res; | ||||
| } | ||||
|  | ||||
| [[nodiscard]] static auto desktop_file_path_for(std::string_view app_name) | ||||
|     -> std::string { | ||||
|   return utils::path::combine(get_autostart_dir(), | ||||
|                               {sanitize_basename(app_name) + ".desktop"}); | ||||
| } | ||||
|  | ||||
| auto create_autostart_entry(create_autostart_opts opts) -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   auto file = desktop_file_path_for(opts.app_name); | ||||
|   if (utils::file::file{file}.exists()) { | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   auto dir = get_autostart_dir(); | ||||
|   if (dir.empty()) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   if (not utils::file::directory{dir}.create_directory()) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   auto exec_line = opts.exec_path; | ||||
|   if (not opts.exec_args.empty()) { | ||||
|     exec_line += ' '; | ||||
|     exec_line += join_args_for_exec(opts.exec_args); | ||||
|   } | ||||
|  | ||||
|   std::ofstream out(file, std::ios::binary | std::ios::trunc); | ||||
|   if (not out) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   out << "[Desktop Entry]\n"; | ||||
|   out << "Type=Application\n"; | ||||
|   out << "Version=1.0\n"; | ||||
|   out << "Name=" << opts.app_name << "\n"; | ||||
|   out << "Exec=" << exec_line << "\n"; | ||||
|   out << "Terminal=" << (opts.terminal ? "true" : "false") << "\n"; | ||||
|  | ||||
|   if (opts.comment && !opts.comment->empty()) { | ||||
|     out << "Comment=" << *opts.comment << "\n"; | ||||
|   } | ||||
|  | ||||
|   if (opts.icon_path && !opts.icon_path->empty()) { | ||||
|     out << "Icon=" << *opts.icon_path << "\n"; | ||||
|   } | ||||
|  | ||||
|   if (!opts.only_show_in.empty()) { | ||||
|     out << "OnlyShowIn="; | ||||
|     for (std::size_t idx = 0U; idx < opts.only_show_in.size(); ++idx) { | ||||
|       if (idx != 0U) { | ||||
|         out << ';'; | ||||
|       } | ||||
|       out << opts.only_show_in[idx]; | ||||
|     } | ||||
|     out << ";\n"; | ||||
|   } | ||||
|  | ||||
|   if (not opts.enabled) { | ||||
|     out << "X-GNOME-Autostart-enabled=false\n"; | ||||
|     out << "Hidden=true\n"; | ||||
|   } | ||||
|  | ||||
|   out.flush(); | ||||
|   if (not out) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
| #if defined(__linux__) || defined(__APPLE__) | ||||
|   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; | ||||
| } | ||||
|  | ||||
| auto remove_autostart_entry(std::string_view name) -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   auto file = desktop_file_path_for(name); | ||||
|   if (not utils::file::file{file}.exists()) { | ||||
|     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; | ||||
| } | ||||
| } // namespace repertory | ||||
|  | ||||
| #endif //_WIN32 | ||||
|   | ||||
| @@ -22,6 +22,7 @@ | ||||
| #include "ui/mgmt_app_config.hpp" | ||||
|  | ||||
| #include "app_config.hpp" | ||||
| #include "platform/platform.hpp" | ||||
| #include "utils/error_utils.hpp" | ||||
| #include "utils/file.hpp" | ||||
| #include "utils/path.hpp" | ||||
| @@ -103,6 +104,7 @@ mgmt_app_config::mgmt_app_config(bool hidden, bool launch_only) | ||||
|         save(); | ||||
|       } | ||||
|  | ||||
|       set_auto_start(get_auto_start()); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
| @@ -110,6 +112,8 @@ mgmt_app_config::mgmt_app_config(bool hidden, bool launch_only) | ||||
|         function_name, utils::get_last_error_code(), | ||||
|         fmt::format("failed to read file|{}", config_file)); | ||||
|     save(); | ||||
|  | ||||
|     set_auto_start(get_auto_start()); | ||||
|   } catch (const std::exception &ex) { | ||||
|     utils::error::raise_error( | ||||
|         function_name, ex, fmt::format("failed to read file|{}", config_file)); | ||||
| @@ -207,6 +211,37 @@ void mgmt_app_config::set_mount_location(provider_type prov, | ||||
| } | ||||
|  | ||||
| void mgmt_app_config::set_auto_start(bool auto_start) { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   auto current_directory = std::filesystem::current_path(); | ||||
|   if (utils::file::change_to_process_directory()) { | ||||
| #if defined(__linux__) | ||||
|     if (auto_start) { | ||||
|       create_autostart_opts opts{}; | ||||
|       opts.app_name = "repertory"; | ||||
|       opts.comment = "Mount utility for AWS S3 and Sia"; | ||||
|       opts.exec_args = {"-ui -lo"}; | ||||
|       opts.exec_path = utils::path::combine(".", {"repertory"}); | ||||
|       opts.icon_path = utils::path::combine(".", {"repertory.png"}); | ||||
|       opts.terminal = true; | ||||
|  | ||||
|       if (not create_autostart_entry(opts)) { | ||||
|         utils::error::raise_error( | ||||
|             function_name, utils::get_last_error_code(), | ||||
|             fmt::format("failed to create auto-start entry")); | ||||
|       } | ||||
|     } else if (not remove_autostart_entry("repertory")) { | ||||
|       utils::error::raise_error( | ||||
|           function_name, utils::get_last_error_code(), | ||||
|           fmt::format("failed to remove auto-start entry")); | ||||
|     } | ||||
| #endif // defined(__linux__) | ||||
|   } else { | ||||
|     utils::error::raise_error(function_name, utils::get_last_error_code(), | ||||
|                               fmt::format("failed to change directory")); | ||||
|   } | ||||
|  | ||||
|   std::filesystem::current_path(current_directory); | ||||
|   if (auto_start_ == auto_start) { | ||||
|     return; | ||||
|   } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user