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

This commit is contained in:
2025-09-06 12:07:07 -05:00
parent 30633aa4ff
commit 1904bd868d
8 changed files with 142 additions and 3 deletions

View File

@@ -426,6 +426,7 @@ inline constexpr auto JSON_MAX_CONNECTIONS{"MaxConnections"};
inline constexpr auto JSON_MAX_UPLOAD_COUNT{"MaxUploadCount"}; inline constexpr auto JSON_MAX_UPLOAD_COUNT{"MaxUploadCount"};
inline constexpr auto JSON_MED_FREQ_INTERVAL_SECS{"MedFreqIntervalSeconds"}; inline constexpr auto JSON_MED_FREQ_INTERVAL_SECS{"MedFreqIntervalSeconds"};
inline constexpr auto JSON_META{"Meta"}; inline constexpr auto JSON_META{"Meta"};
inline constexpr auto JSON_MOUNT_AUTO_START{"MountAutoStart"};
inline constexpr auto JSON_MOUNT_LOCATIONS{"MountLocations"}; inline constexpr auto JSON_MOUNT_LOCATIONS{"MountLocations"};
inline constexpr auto JSON_ONLINE_CHECK_RETRY_SECS{"OnlineCheckRetrySeconds"}; inline constexpr auto JSON_ONLINE_CHECK_RETRY_SECS{"OnlineCheckRetrySeconds"};
inline constexpr auto JSON_PATH{"Path"}; inline constexpr auto JSON_PATH{"Path"};

View File

@@ -104,6 +104,9 @@ private:
void handle_post_mount(const httplib::Request &req, httplib::Response &res); void handle_post_mount(const httplib::Request &req, httplib::Response &res);
void handle_put_mount_auto_start(const httplib::Request &req,
httplib::Response &res) const;
void handle_put_mount_location(const httplib::Request &req, void handle_put_mount_location(const httplib::Request &req,
httplib::Response &res) const; httplib::Response &res) const;

View File

@@ -42,6 +42,8 @@ private:
std::unordered_map<provider_type, std::unordered_map<provider_type,
std::unordered_map<std::string, std::string>> std::unordered_map<std::string, std::string>>
locations_; locations_;
std::unordered_map<provider_type, std::unordered_map<std::string, bool>>
mount_auto_start_;
mutable std::recursive_mutex mtx_; mutable std::recursive_mutex mtx_;
private: private:
@@ -60,6 +62,9 @@ public:
[[nodiscard]] auto get_auto_start() const -> bool { return auto_start_; } [[nodiscard]] auto get_auto_start() const -> bool { return auto_start_; }
[[nodiscard]] auto get_auto_start(provider_type prov,
std::string_view name) const -> bool;
[[nodiscard]] auto get_hidden() const -> bool { return hidden_; } [[nodiscard]] auto get_hidden() const -> bool { return hidden_; }
[[nodiscard]] auto get_launch_only() const -> bool { return launch_only_; } [[nodiscard]] auto get_launch_only() const -> bool { return launch_only_; }
@@ -76,6 +81,9 @@ public:
void set_auto_start(bool auto_start); void set_auto_start(bool auto_start);
void set_auto_start(provider_type prov, std::string_view name,
bool auto_start);
void set_hidden(bool hidden); void set_hidden(bool hidden);
void set_launch_only(bool launch_only); void set_launch_only(bool launch_only);

View File

@@ -255,6 +255,10 @@ handlers::handlers(mgmt_app_config *config, httplib::Server *server)
server->Post("/api/v1/mount", server->Post("/api/v1/mount",
[this](auto &&req, auto &&res) { handle_post_mount(req, res); }); [this](auto &&req, auto &&res) { handle_post_mount(req, res); });
server->Put("/api/v1/mount_auto_start", [this](auto &&req, auto &&res) {
handle_put_mount_auto_start(req, res);
});
server->Put("/api/v1/mount_location", [this](auto &&req, auto &&res) { server->Put("/api/v1/mount_location", [this](auto &&req, auto &&res) {
handle_put_mount_location(req, res); handle_put_mount_location(req, res);
}); });
@@ -382,6 +386,21 @@ void handlers::generate_config(provider_type prov, std::string_view name,
} }
} }
void handlers::handle_put_mount_auto_start(const httplib::Request &req,
httplib::Response &res) const {
auto prov = provider_type_from_string(req.get_param_value("type"));
auto name = req.get_param_value("name");
if (not data_directory_exists(prov, name)) {
res.status = http_error_codes::not_found;
return;
}
auto auto_start = utils::string::to_bool(req.get_param_value("auto_start"));
config_->set_auto_start(prov, name, auto_start);
res.status = http_error_codes::ok;
}
void handlers::handle_put_mount_location(const httplib::Request &req, void handlers::handle_put_mount_location(const httplib::Request &req,
httplib::Response &res) const { httplib::Response &res) const {
auto prov = provider_type_from_string(req.get_param_value("type")); auto prov = provider_type_from_string(req.get_param_value("type"));
@@ -523,6 +542,8 @@ void handlers::handle_get_mount_status(const httplib::Request &req,
result.at("Location").get<std::string>()); result.at("Location").get<std::string>());
} }
result["AutoStart"] = config_->get_auto_start(prov, name);
res.set_content(result.dump(), "application/json"); res.set_content(result.dump(), "application/json");
res.status = http_error_codes::ok; res.status = http_error_codes::ok;
} }

View File

@@ -29,11 +29,12 @@
#include "utils/unix.hpp" #include "utils/unix.hpp"
namespace { namespace {
template <typename data_t>
[[nodiscard]] auto from_json(const nlohmann::json &json) [[nodiscard]] auto from_json(const nlohmann::json &json)
-> std::unordered_map<repertory::provider_type, -> std::unordered_map<repertory::provider_type,
std::unordered_map<std::string, std::string>> { std::unordered_map<std::string, data_t>> {
std::unordered_map<repertory::provider_type, std::unordered_map<repertory::provider_type,
std::unordered_map<std::string, std::string>> std::unordered_map<std::string, data_t>>
map_of_maps{ map_of_maps{
{repertory::provider_type::encrypt, nlohmann::json::object()}, {repertory::provider_type::encrypt, nlohmann::json::object()},
{repertory::provider_type::remote, nlohmann::json::object()}, {repertory::provider_type::remote, nlohmann::json::object()},
@@ -97,7 +98,8 @@ mgmt_app_config::mgmt_app_config(bool hidden, bool launch_only)
auto_start_ = data.contains(JSON_AUTO_START) auto_start_ = data.contains(JSON_AUTO_START)
? data.at(JSON_AUTO_START).get<bool>() ? data.at(JSON_AUTO_START).get<bool>()
: false; : false;
locations_ = from_json(data.at(JSON_MOUNT_LOCATIONS)); mount_auto_start_ = from_json<bool>(data.at(JSON_MOUNT_AUTO_START));
locations_ = from_json<std::string>(data.at(JSON_MOUNT_LOCATIONS));
if (not data.contains(JSON_AUTO_START)) { if (not data.contains(JSON_AUTO_START)) {
save(); save();
@@ -119,6 +121,17 @@ mgmt_app_config::mgmt_app_config(bool hidden, bool launch_only)
} }
} }
auto mgmt_app_config::get_auto_start(provider_type prov,
std::string_view name) const -> bool {
recur_mutex_lock lock(mtx_);
if (mount_auto_start_.contains(prov) &&
mount_auto_start_.at(prov).contains(std::string{name})) {
return mount_auto_start_.at(prov).at(std::string{name});
}
return false;
}
auto mgmt_app_config::get_mount_location(provider_type prov, auto mgmt_app_config::get_mount_location(provider_type prov,
std::string_view name) const std::string_view name) const
-> std::string { -> std::string {
@@ -294,6 +307,22 @@ void mgmt_app_config::set_auto_start(bool auto_start) {
save(); save();
} }
void mgmt_app_config::set_auto_start(provider_type prov, std::string_view name,
bool auto_start) {
if (name.empty()) {
return;
}
recur_mutex_lock lock(mtx_);
if (mount_auto_start_[prov][std::string{name}] == auto_start) {
return;
}
mount_auto_start_[prov][std::string{name}] = auto_start;
save();
}
auto mgmt_app_config::to_json() const -> nlohmann::json { auto mgmt_app_config::to_json() const -> nlohmann::json {
nlohmann::json data; nlohmann::json data;
data[JSON_AUTO_START] = auto_start_; data[JSON_AUTO_START] = auto_start_;
@@ -301,6 +330,7 @@ auto mgmt_app_config::to_json() const -> nlohmann::json {
data[JSON_API_PORT] = api_port_; data[JSON_API_PORT] = api_port_;
data[JSON_API_USER] = api_user_; data[JSON_API_USER] = api_user_;
data[JSON_MOUNT_LOCATIONS] = map_to_json(locations_); data[JSON_MOUNT_LOCATIONS] = map_to_json(locations_);
data[JSON_MOUNT_AUTO_START] = map_to_json(mount_auto_start_);
return data; return data;
} }
} // namespace repertory::ui } // namespace repertory::ui

View File

@@ -21,6 +21,7 @@ class Mount with ChangeNotifier {
refresh(); refresh();
} }
bool get autoStart => mountConfig.autoStart;
String? get bucket => mountConfig.bucket; String? get bucket => mountConfig.bucket;
String get id => '${type}_$name'; String get id => '${type}_$name';
bool? get mounted => mountConfig.mounted; bool? get mounted => mountConfig.mounted;
@@ -101,6 +102,35 @@ class Mount with ChangeNotifier {
} }
} }
Future<void> setMountAutoStart(bool autoStart) async {
try {
mountConfig.autoStart = autoStart;
final auth = await _auth.createAuth();
final response = await http.put(
Uri.parse(
Uri.encodeFull(
'${getBaseUri()}/api/v1/mount_location?auth=$auth&name=$name&type=$type&auto_start=$autoStart',
),
),
);
if (response.statusCode == 401) {
_auth.logoff();
return;
}
if (response.statusCode == 404) {
_mountList?.reset();
return;
}
return refresh();
} catch (e) {
debugPrint('$e');
}
}
Future<void> setMountLocation(String location) async { Future<void> setMountLocation(String location) async {
try { try {
mountConfig.path = location; mountConfig.path = location;

View File

@@ -3,6 +3,7 @@ import 'package:repertory/helpers.dart' show initialCaps;
class MountConfig { class MountConfig {
bool? mounted; bool? mounted;
bool autoStart = false;
final String _name; final String _name;
String path = ''; String path = '';
Map<String, dynamic> _settings = {}; Map<String, dynamic> _settings = {};
@@ -30,6 +31,9 @@ class MountConfig {
} }
void updateStatus(Map<String, dynamic> status) { void updateStatus(Map<String, dynamic> status) {
autoStart = status.containsKey('AutoStart')
? status['AutoStart'] as bool
: false;
path = status['Location'] as String; path = status['Location'] as String;
mounted = status['Active'] as bool; mounted = status['Active'] as bool;
} }

View File

@@ -35,6 +35,11 @@ class _MountWidgetState extends State<MountWidget>
); );
final subStyle = textTheme.bodyMedium?.copyWith(color: scheme.onSurface); final subStyle = textTheme.bodyMedium?.copyWith(color: scheme.onSurface);
final visualDensity = VisualDensity(
horizontal: -VisualDensity.maximumDensity,
vertical: -(VisualDensity.maximumDensity * 2.0),
);
return ConstrainedBox( return ConstrainedBox(
constraints: const BoxConstraints(minHeight: 120), constraints: const BoxConstraints(minHeight: 120),
child: Card( child: Card(
@@ -141,6 +146,43 @@ class _MountWidgetState extends State<MountWidget>
style: subStyle, style: subStyle,
), ),
), ),
IntrinsicWidth(
child: Theme(
data: Theme.of(context).copyWith(
listTileTheme: const ListTileThemeData(
contentPadding: EdgeInsets.zero,
horizontalTitleGap: 0,
minLeadingWidth: 0,
minVerticalPadding: 0,
),
checkboxTheme: CheckboxThemeData(
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
visualDensity: visualDensity,
),
),
child: CheckboxListTile(
contentPadding: EdgeInsets.zero,
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
onChanged: (_) {},
title: Row(
mainAxisSize: MainAxisSize.min,
children: const [
Icon(
Icons.auto_mode,
size: constants.smallIconSize,
),
SizedBox(width: constants.paddingSmall),
Text('Auto-mount'),
SizedBox(width: constants.paddingSmall),
],
),
value: mount.autoStart,
visualDensity: visualDensity,
),
),
),
], ],
), ),
], ],