[ui] Add auto-mount on first launch functionality #52

This commit is contained in:
2025-09-06 08:08:26 -05:00
parent 78abe1ed8f
commit 13fbcbab9a
7 changed files with 238 additions and 188 deletions

View File

@@ -28,17 +28,6 @@
namespace repertory { namespace repertory {
class i_provider; 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 { class lock_data final {
public: public:
lock_data(provider_type prov, std::string_view unique_id); lock_data(provider_type prov, std::string_view unique_id);
@@ -82,8 +71,6 @@ public:
-> bool; -> bool;
}; };
[[nodiscard]] auto create_autostart_entry(create_autostart_opts opts) -> bool;
[[nodiscard]] auto create_meta_attributes( [[nodiscard]] auto create_meta_attributes(
std::uint64_t accessed_date, std::uint32_t attributes, std::uint64_t accessed_date, std::uint32_t attributes,
std::uint64_t changed_date, std::uint64_t creation_date, bool directory, std::uint64_t changed_date, std::uint64_t creation_date, bool directory,
@@ -94,8 +81,6 @@ public:
[[nodiscard]] auto provider_meta_handler(i_provider &provider, bool directory, [[nodiscard]] auto provider_meta_handler(i_provider &provider, bool directory,
const api_file &file) -> api_error; const api_file &file) -> api_error;
[[nodiscard]] auto remove_autostart_entry(std::string_view name) -> bool;
} // namespace repertory } // namespace repertory
#endif // !defined(_WIN32) #endif // !defined(_WIN32)

View File

@@ -19,7 +19,6 @@
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 <utils/config.hpp>
#if !defined(_WIN32) #if !defined(_WIN32)
#include "platform/platform.hpp" #include "platform/platform.hpp"
@@ -29,85 +28,13 @@
#include "providers/i_provider.hpp" #include "providers/i_provider.hpp"
#include "types/startup_exception.hpp" #include "types/startup_exception.hpp"
#include "utils/common.hpp" #include "utils/common.hpp"
#include "utils/config.hpp"
#include "utils/error_utils.hpp" #include "utils/error_utils.hpp"
#include "utils/file_utils.hpp" #include "utils/file_utils.hpp"
#include "utils/path.hpp" #include "utils/path.hpp"
#include "utils/string.hpp" #include "utils/string.hpp"
#include "utils/unix.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 { namespace repertory {
lock_data::lock_data(provider_type prov, std::string_view unique_id) lock_data::lock_data(provider_type prov, std::string_view unique_id)
: mutex_id_(create_lock_id(prov, unique_id)) { : mutex_id_(create_lock_id(prov, unique_id)) {
@@ -328,102 +255,6 @@ auto provider_meta_handler(i_provider &provider, bool directory,
return res; 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 } // namespace repertory
#endif //_WIN32 #endif //_WIN32

View File

@@ -217,7 +217,7 @@ void mgmt_app_config::set_auto_start(bool auto_start) {
if (utils::file::change_to_process_directory()) { if (utils::file::change_to_process_directory()) {
#if defined(__linux__) #if defined(__linux__)
if (auto_start) { if (auto_start) {
create_autostart_opts opts{}; utils::create_autostart_opts opts{};
opts.app_name = "repertory"; opts.app_name = "repertory";
opts.comment = "Mount utility for AWS S3 and Sia"; opts.comment = "Mount utility for AWS S3 and Sia";
opts.exec_args = {"-ui", "-lo"}; opts.exec_args = {"-ui", "-lo"};
@@ -225,17 +225,33 @@ void mgmt_app_config::set_auto_start(bool auto_start) {
opts.icon_path = utils::path::combine(".", {"repertory.png"}); opts.icon_path = utils::path::combine(".", {"repertory.png"});
opts.terminal = true; opts.terminal = true;
if (not create_autostart_entry(opts)) { if (not utils::create_autostart_entry(opts)) {
utils::error::raise_error( utils::error::raise_error(
function_name, utils::get_last_error_code(), function_name, utils::get_last_error_code(),
fmt::format("failed to create auto-start entry")); fmt::format("failed to create auto-start entry"));
} }
} else if (not remove_autostart_entry("repertory")) { } else if (not utils::remove_autostart_entry("repertory")) {
utils::error::raise_error( utils::error::raise_error(
function_name, utils::get_last_error_code(), function_name, utils::get_last_error_code(),
fmt::format("failed to remove auto-start entry")); fmt::format("failed to remove auto-start entry"));
} }
#endif // defined(__linux__) #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")) {
utils::error::raise_error(
function_name, utils::get_last_error_code(),
fmt::format("failed to create auto-start entry"));
}
} else if (not utils::remove_shortcut(L"repertory")) {
utils::error::raise_error(
function_name, utils::get_last_error_code(),
fmt::format("failed to remove auto-start entry"));
}
#endif // defined(_WIN32)
} else { } else {
utils::error::raise_error(function_name, utils::get_last_error_code(), utils::error::raise_error(function_name, utils::get_last_error_code(),
fmt::format("failed to change directory")); fmt::format("failed to change directory"));

View File

@@ -27,6 +27,19 @@
#include "utils/config.hpp" #include "utils/config.hpp"
namespace repertory::utils { namespace repertory::utils {
#if defined(__linux__)
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};
};
#endif // defined(__linux__)
using passwd_callback_t = std::function<void(struct passwd *pass)>; using passwd_callback_t = std::function<void(struct passwd *pass)>;
#if defined(__APPLE__) #if defined(__APPLE__)
@@ -37,6 +50,10 @@ template <typename thread_t>
[[nodiscard]] auto convert_to_uint64(const pthread_t &thread) -> std::uint64_t; [[nodiscard]] auto convert_to_uint64(const pthread_t &thread) -> std::uint64_t;
#endif // defined(__APPLE__) #endif // defined(__APPLE__)
#if defined(__linux__)
[[nodiscard]] auto create_autostart_entry(create_autostart_opts opts) -> bool;
#endif // defined(__linux__)
[[nodiscard]] auto get_last_error_code() -> int; [[nodiscard]] auto get_last_error_code() -> int;
[[nodiscard]] auto get_thread_id() -> std::uint64_t; [[nodiscard]] auto get_thread_id() -> std::uint64_t;
@@ -48,6 +65,10 @@ void set_last_error_code(int error_code);
[[nodiscard]] auto use_getpwuid(uid_t uid, passwd_callback_t callback) [[nodiscard]] auto use_getpwuid(uid_t uid, passwd_callback_t callback)
-> utils::result; -> utils::result;
#if defined(__linux__)
[[nodiscard]] auto remove_autostart_entry(std::string_view name) -> bool;
#endif // defined(__linux__)
// template implementations // template implementations
#if defined(__APPLE__) #if defined(__APPLE__)
template <typename thread_t> template <typename thread_t>

View File

@@ -50,6 +50,10 @@ auto create_shortcut(const std::wstring &exe_path,
const std::wstring &location = get_startup_folder()) const std::wstring &location = get_startup_folder())
-> bool; -> bool;
[[nodiscard]] auto
remove_shortcut(std::wstring shortcut_name,
const std::wstring &location = get_startup_folder()) -> bool;
void set_last_error_code(DWORD error_code); void set_last_error_code(DWORD error_code);
} // namespace repertory::utils } // namespace repertory::utils

View File

@@ -24,7 +24,10 @@
#include "utils/unix.hpp" #include "utils/unix.hpp"
#include "utils/collection.hpp" #include "utils/collection.hpp"
#include "utils/config.hpp"
#include "utils/error.hpp" #include "utils/error.hpp"
#include "utils/file.hpp"
#include "utils/path.hpp"
namespace { namespace {
[[nodiscard]] auto get_group_list(auto *pass) -> std::vector<gid_t> { [[nodiscard]] auto get_group_list(auto *pass) -> std::vector<gid_t> {
@@ -86,6 +89,87 @@ namespace {
return groups; return groups;
} }
#if defined(__linux__)
[[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;
}
[[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 desktop_file_path_for(std::string_view app_name)
-> std::string {
return repertory::utils::path::combine(
get_autostart_dir(), {
sanitize_basename(app_name) + ".desktop",
});
}
[[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;
}
#endif // defined(__linux__)
} // namespace } // namespace
namespace repertory::utils { namespace repertory::utils {
@@ -95,6 +179,81 @@ auto convert_to_uint64(const pthread_t &thread) -> std::uint64_t {
} }
#endif // !defined(__APPLE__) #endif // !defined(__APPLE__)
#if defined(__linux__)
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;
}
#endif // defined(__linux__)
auto get_last_error_code() -> int { return errno; } auto get_last_error_code() -> int { return errno; }
auto get_thread_id() -> std::uint64_t { auto get_thread_id() -> std::uint64_t {
@@ -115,6 +274,25 @@ auto is_uid_member_of_group(uid_t uid, gid_t gid) -> bool {
return collection::includes(groups, gid); return collection::includes(groups, gid);
} }
#if defined(__linux__)
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;
}
#endif // defined(__linux__)
auto use_getpwuid(uid_t uid, passwd_callback_t callback) -> result { auto use_getpwuid(uid_t uid, passwd_callback_t callback) -> result {
REPERTORY_USES_FUNCTION_NAME(); REPERTORY_USES_FUNCTION_NAME();

View File

@@ -24,6 +24,8 @@
#include "utils/windows.hpp" #include "utils/windows.hpp"
#include "utils/error.hpp" #include "utils/error.hpp"
#include "utils/file.hpp"
#include "utils/path.hpp"
#include "utils/string.hpp" #include "utils/string.hpp"
namespace repertory::utils { namespace repertory::utils {
@@ -273,6 +275,19 @@ auto create_shortcut(const std::wstring &exe_path,
return true; return true;
} }
auto remove_shortcut(std::wstring shortcut_name, const std::wstring &location)
-> bool {
if (not shortcut_name.ends_with(L".lnk")) {
shortcut_name += L".lnk";
}
auto file = utils::path::combine(location, {shortcut_name});
if (not utils::file::file{file}.exists()) {
return true;
}
return utils::file::file{file}.remove();
}
} // namespace repertory::utils } // namespace repertory::utils
#endif // defined(_WIN32) #endif // defined(_WIN32)