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

This commit is contained in:
2025-09-06 07:33:14 -05:00
parent 9de4d3172f
commit a1d77faa8d
4 changed files with 222 additions and 0 deletions

View File

@@ -10,3 +10,5 @@ rsync -av --progress ${CURRENT_DIR}/${PROJECT_NAME}/${PROJECT_NAME}_test/test_in
${PROJECT_DIST_DIR}/test_input/ ${PROJECT_DIST_DIR}/test_input/
rsync -av --progress ${CURRENT_DIR}/icon.ico ${PROJECT_DIST_DIR}/icon.ico rsync -av --progress ${CURRENT_DIR}/icon.ico ${PROJECT_DIST_DIR}/icon.ico
rsync -av --progress ${CURRENT_DIR}/assets/blue/logo.iconset/icon_128x128.png ${PROJECT_DIST_DIR}/repertory.png

View File

@@ -28,6 +28,17 @@
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);
@@ -71,6 +82,8 @@ 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,
@@ -81,6 +94,8 @@ 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,6 +19,7 @@
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"
@@ -34,6 +35,79 @@
#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)) {
@@ -254,6 +328,102 @@ 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

@@ -22,6 +22,7 @@
#include "ui/mgmt_app_config.hpp" #include "ui/mgmt_app_config.hpp"
#include "app_config.hpp" #include "app_config.hpp"
#include "platform/platform.hpp"
#include "utils/error_utils.hpp" #include "utils/error_utils.hpp"
#include "utils/file.hpp" #include "utils/file.hpp"
#include "utils/path.hpp" #include "utils/path.hpp"
@@ -103,6 +104,7 @@ mgmt_app_config::mgmt_app_config(bool hidden, bool launch_only)
save(); save();
} }
set_auto_start(get_auto_start());
return; return;
} }
@@ -110,6 +112,8 @@ mgmt_app_config::mgmt_app_config(bool hidden, bool launch_only)
function_name, utils::get_last_error_code(), function_name, utils::get_last_error_code(),
fmt::format("failed to read file|{}", config_file)); fmt::format("failed to read file|{}", config_file));
save(); save();
set_auto_start(get_auto_start());
} catch (const std::exception &ex) { } catch (const std::exception &ex) {
utils::error::raise_error( utils::error::raise_error(
function_name, ex, fmt::format("failed to read file|{}", config_file)); 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) { 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) { if (auto_start_ == auto_start) {
return; return;
} }