[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 {
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);
@@ -82,8 +71,6 @@ 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,
@@ -94,8 +81,6 @@ 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)

View File

@@ -19,7 +19,6 @@
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"
@@ -29,85 +28,13 @@
#include "providers/i_provider.hpp"
#include "types/startup_exception.hpp"
#include "utils/common.hpp"
#include "utils/config.hpp"
#include "utils/error_utils.hpp"
#include "utils/file_utils.hpp"
#include "utils/path.hpp"
#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)) {
@@ -328,102 +255,6 @@ 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

View File

@@ -217,7 +217,7 @@ void mgmt_app_config::set_auto_start(bool auto_start) {
if (utils::file::change_to_process_directory()) {
#if defined(__linux__)
if (auto_start) {
create_autostart_opts opts{};
utils::create_autostart_opts opts{};
opts.app_name = "repertory";
opts.comment = "Mount utility for AWS S3 and Sia";
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.terminal = true;
if (not create_autostart_entry(opts)) {
if (not utils::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")) {
} else if (not utils::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__)
#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 {
utils::error::raise_error(function_name, utils::get_last_error_code(),
fmt::format("failed to change directory"));

View File

@@ -27,6 +27,19 @@
#include "utils/config.hpp"
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)>;
#if defined(__APPLE__)
@@ -37,6 +50,10 @@ template <typename thread_t>
[[nodiscard]] auto convert_to_uint64(const pthread_t &thread) -> std::uint64_t;
#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_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)
-> utils::result;
#if defined(__linux__)
[[nodiscard]] auto remove_autostart_entry(std::string_view name) -> bool;
#endif // defined(__linux__)
// template implementations
#if defined(__APPLE__)
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())
-> 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);
} // namespace repertory::utils

View File

@@ -24,7 +24,10 @@
#include "utils/unix.hpp"
#include "utils/collection.hpp"
#include "utils/config.hpp"
#include "utils/error.hpp"
#include "utils/file.hpp"
#include "utils/path.hpp"
namespace {
[[nodiscard]] auto get_group_list(auto *pass) -> std::vector<gid_t> {
@@ -86,6 +89,87 @@ namespace {
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 repertory::utils {
@@ -95,6 +179,81 @@ 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 {
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_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);
}
#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 {
REPERTORY_USES_FUNCTION_NAME();

View File

@@ -24,6 +24,8 @@
#include "utils/windows.hpp"
#include "utils/error.hpp"
#include "utils/file.hpp"
#include "utils/path.hpp"
#include "utils/string.hpp"
namespace repertory::utils {
@@ -273,6 +275,19 @@ auto create_shortcut(const std::wstring &exe_path,
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
#endif // defined(_WIN32)