mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-04 16:56:59 +01:00
feat(core,fuse,gui,cli): upload and download bandwidth limits (phase 6)
Every transfer charges the bytes it moves to its direction's rate limit, a virtual clock shared by all transfers with a one-second burst, and sleeps off any overshoot. On runtime workers the sleep goes through block_in_place so FUSE reads keep their threads. The caps are set live with SetBandwidthLimits, persisted to config and applied at mount start. Preferences gains a Network group with debounced MiB/s editors, and the CLI gains `pdfs sync limit --up/--down`.
This commit is contained in:
1 parent
b58b60010e
commit
8351382ef2
9 files changed
+306
-2
No files matched your search
@@ -376,10 +376,13 @@ fn build_window(app: &adw::Application) {
|
||||
quota_checked_at: Cell::new(None),
|
||||
autostart_row: main_widgets.autostart_row.clone(),
|
||||
budget_row: main_widgets.budget_row.clone(),
|
||||
upload_limit_row: main_widgets.upload_limit_row.clone(),
|
||||
download_limit_row: main_widgets.download_limit_row.clone(),
|
||||
mountpoint_row: main_widgets.mountpoint_row.clone(),
|
||||
accent_row: main_widgets.accent_row.clone(),
|
||||
settings_suppress: Cell::new(false),
|
||||
budget_source: RefCell::new(None),
|
||||
limit_source: RefCell::new(None),
|
||||
pins_expanded: Cell::new(false),
|
||||
pins_group: main_widgets.pins_group.clone(),
|
||||
pin_rows: RefCell::new(Vec::new()),
|
||||
|
||||
@@ -37,6 +37,11 @@ pub(crate) struct StatusState {
|
||||
/// Cache-budget editor (GiB). Populated once from config; user edits drive a
|
||||
/// `SetCacheBudget` round-trip. Guarded by [`Self::settings_suppress`].
|
||||
pub(crate) budget_row: adw::SpinRow,
|
||||
/// Bandwidth caps (MiB/s, `0` = none). Populated once from config; an edit
|
||||
/// to either sends both in one `SetBandwidthLimits`. Guarded by
|
||||
/// [`Self::settings_suppress`].
|
||||
pub(crate) upload_limit_row: adw::SpinRow,
|
||||
pub(crate) download_limit_row: adw::SpinRow,
|
||||
/// Shows where the primary mount lives; its Change button picks a new one.
|
||||
pub(crate) mountpoint_row: adw::ActionRow,
|
||||
/// "Proton purple accent" toggle. Guarded by [`Self::settings_suppress`].
|
||||
@@ -49,6 +54,8 @@ pub(crate) struct StatusState {
|
||||
/// stack of toasts and a series of caps the user never asked to apply — so
|
||||
/// only the value they settle on is sent.
|
||||
pub(crate) budget_source: RefCell<Option<glib::SourceId>>,
|
||||
/// Pending debounce for the bandwidth editors, as for the cache budget.
|
||||
pub(crate) limit_source: RefCell<Option<glib::SourceId>>,
|
||||
pub(crate) pins_group: adw::PreferencesGroup,
|
||||
/// Whether the pin list is showing every pin or only the first
|
||||
/// [`PINS_COLLAPSED`]. A long pin list would otherwise push everything below
|
||||
@@ -119,6 +126,9 @@ pub(crate) struct MainWidgets {
|
||||
pub(crate) autostart_row: adw::SwitchRow,
|
||||
/// Cache soft-cap editor, in GiB; `0` = unlimited.
|
||||
pub(crate) budget_row: adw::SpinRow,
|
||||
/// Upload and download caps, in MiB/s; `0` = unlimited.
|
||||
pub(crate) upload_limit_row: adw::SpinRow,
|
||||
pub(crate) download_limit_row: adw::SpinRow,
|
||||
/// Purges all unpinned cached content.
|
||||
pub(crate) purge_button: gtk4::Button,
|
||||
/// Shows the active mountpoint; its suffix button picks a new one.
|
||||
@@ -159,6 +169,24 @@ pub(crate) fn build_main_page() -> MainWidgets {
|
||||
mountpoint_row.add_suffix(&mountpoint_button);
|
||||
location_group.add(&mountpoint_row);
|
||||
|
||||
// Bandwidth caps in MiB/s. 0 = unlimited; the smallest cap is one 0.5 step,
|
||||
// since anything slower would stall a single block for most of a minute.
|
||||
let network_group = adw::PreferencesGroup::builder()
|
||||
.title("Network")
|
||||
.description("Limit how fast files move, shared by every transfer. 0 means no limit.")
|
||||
.build();
|
||||
let limit_row = |title: &str| {
|
||||
adw::SpinRow::builder()
|
||||
.title(title)
|
||||
.adjustment(>k4::Adjustment::new(0.0, 0.0, 1000.0, 0.5, 5.0, 0.0))
|
||||
.digits(1)
|
||||
.build()
|
||||
};
|
||||
let upload_limit_row = limit_row("Upload limit (MiB/s)");
|
||||
let download_limit_row = limit_row("Download limit (MiB/s)");
|
||||
network_group.add(&upload_limit_row);
|
||||
network_group.add(&download_limit_row);
|
||||
|
||||
let appearance_group = adw::PreferencesGroup::builder().title("Appearance").build();
|
||||
let accent_row = adw::SwitchRow::builder()
|
||||
.title("Proton purple accent")
|
||||
@@ -172,6 +200,7 @@ pub(crate) fn build_main_page() -> MainWidgets {
|
||||
.build();
|
||||
general.add(&startup_group);
|
||||
general.add(&location_group);
|
||||
general.add(&network_group);
|
||||
general.add(&appearance_group);
|
||||
|
||||
// ---- Preferences: Storage
|
||||
@@ -327,6 +356,8 @@ pub(crate) fn build_main_page() -> MainWidgets {
|
||||
pins_group,
|
||||
autostart_row,
|
||||
budget_row,
|
||||
upload_limit_row,
|
||||
download_limit_row,
|
||||
purge_button,
|
||||
mountpoint_row,
|
||||
mountpoint_button,
|
||||
@@ -356,6 +387,9 @@ pub(crate) const BUDGET_DEBOUNCE: Duration = Duration::from_millis(600);
|
||||
/// Bytes per GiB, for the cache-budget editor's unit conversion.
|
||||
pub(crate) const GIB: f64 = 1024.0 * 1024.0 * 1024.0;
|
||||
|
||||
/// Bytes per MiB, for the bandwidth editors' unit conversion.
|
||||
pub(crate) const MIB: f64 = 1024.0 * 1024.0;
|
||||
|
||||
/// Wire the Preferences controls: the cache-budget editor, the purge button, the
|
||||
/// start-on-login switch, the mountpoint chooser and the accent toggle. Initial widget state is
|
||||
/// read once from config / systemd here (the refresh loop owns only the live
|
||||
@@ -373,6 +407,12 @@ pub(crate) fn wire_settings(
|
||||
ui.status
|
||||
.budget_row
|
||||
.set_value(config.resolved_cache_budget() as f64 / GIB);
|
||||
ui.status
|
||||
.upload_limit_row
|
||||
.set_value(config.upload_limit.unwrap_or(0) as f64 / MIB);
|
||||
ui.status
|
||||
.download_limit_row
|
||||
.set_value(config.download_limit.unwrap_or(0) as f64 / MIB);
|
||||
ui.status
|
||||
.mountpoint_row
|
||||
.set_subtitle(&ui.dirs.resolved_mountpoint(&config).display().to_string());
|
||||
@@ -407,6 +447,33 @@ pub(crate) fn wire_settings(
|
||||
*ui_budget.status.budget_source.borrow_mut() = Some(src);
|
||||
});
|
||||
|
||||
// Bandwidth: either editor sends both caps, since the daemon sets them as a
|
||||
// pair. Debounced like the budget.
|
||||
for row in [&ui.status.upload_limit_row, &ui.status.download_limit_row] {
|
||||
let ui_limit = ui.clone();
|
||||
row.connect_value_notify(move |_| {
|
||||
if ui_limit.status.settings_suppress.get() {
|
||||
return;
|
||||
}
|
||||
if let Some(src) = ui_limit.status.limit_source.borrow_mut().take() {
|
||||
src.remove();
|
||||
}
|
||||
let ui_fire = ui_limit.clone();
|
||||
let src = glib::timeout_add_local_once(BUDGET_DEBOUNCE, move || {
|
||||
ui_fire.status.limit_source.borrow_mut().take();
|
||||
let upload = (ui_fire.status.upload_limit_row.value() * MIB).round() as u64;
|
||||
let download = (ui_fire.status.download_limit_row.value() * MIB).round() as u64;
|
||||
settings_request(
|
||||
&ui_fire,
|
||||
Request::SetBandwidthLimits { upload, download },
|
||||
"Bandwidth limits updated",
|
||||
"Couldn't set bandwidth limits",
|
||||
);
|
||||
});
|
||||
*ui_limit.status.limit_source.borrow_mut() = Some(src);
|
||||
});
|
||||
}
|
||||
|
||||
// Purge: confirm, then drop all unpinned cached content via the daemon.
|
||||
let ui_purge = ui.clone();
|
||||
purge_button.connect_clicked(move |_| {
|
||||
|
||||
Reference in new issue
Block a user