Files
proton-drive-linux/crates/pdfs-fuse/src/queue.rs
T
nvrl 067561564e feat(core,fuse,gui,cli): pause/resume sync and a visible upload queue (phase 2)
Backend (docs/UI_UX_PLAN.md B-1, B-2):
- Request::SetSyncPaused { paused, until } pauses the drain worker and the
  mirror reconcile. FUSE reads, write staging, the watcher and the poll keep
  running. The pause is persisted in the `state` table (`sync_paused_until`),
  so it survives a restart; a timed pause ends by itself through a timer
  thread, re-armed on mount. Resuming wakes the drain and runs a full
  reconcile. Response::Status gains `paused` and `paused_until`.
- Request::ListPendingOps lists the queue with paths, attempts, last error,
  next attempt and parked/failing flags. Request::RetryPendingOp { id } makes
  one backed-off op (or every failed one) due now. Parked ops are never
  un-parked and nothing is dropped from the queue.

Front ends:
- Sync page: a status card with Pause/Resume (split button with 1 h, 8 h and
  24 h pauses) and a "Waiting to Upload" list with Retry now and Retry All.
- The sidebar strip and the tray show the paused state; the tray gains Pause
  Sync / Resume Sync and names failing changes.
- CLI: `pdfs sync pause [--for 30m|1h|2d]`, `pdfs sync resume`,
  `pdfs sync queue`, `pdfs sync retry [id]`; `pdfs status` reports a pause.

Not in this phase: merging Activity into the Sync page, discarding a queued
op (needs a recovery copy first), and per-folder pause.
2026-09-23 02:06:41 +02:00

61 lines
2.2 KiB
Rust

//! The user's view of the pending-op queue: what has not reached the remote
//! yet, why, and a way to stop waiting out a backoff.
//!
//! Read-only apart from "retry now", which only moves a row's next-attempt time.
//! Nothing here drops a queued op: its staged blob is the only copy of the
//! user's write (see `docs/RECOVERY.md`).
use super::*;
use pdfs_core::control::PendingOpInfo;
use pdfs_core::db::{FAILING_ATTEMPTS, PARK_UNTIL};
impl Core {
/// Every queued op, oldest first, with a path a person can read.
pub(crate) fn pending_op_infos(&self) -> CoreResult<Vec<PendingOpInfo>> {
let ops = self
.db
.pending_ops()
.map_err(|e| CoreError::internal(format!("listing the queue: {e}")))?;
Ok(ops
.into_iter()
.map(|op| {
let parked = op.next_attempt_at >= PARK_UNTIL;
let path = self
.db
.node_path(&op.uid)
.ok()
.flatten()
.filter(|path| !path.is_empty())
.or_else(|| op.name.clone())
.unwrap_or_else(|| op.uid.clone());
PendingOpInfo {
id: op.id,
kind: op.kind,
path,
attempts: op.attempts,
last_error: op.last_error,
queued_at: op.created_at / 1000,
next_attempt_at: (!parked).then_some(op.next_attempt_at / 1000),
parked,
failing: op.attempts >= FAILING_ATTEMPTS,
}
})
.collect())
}
/// Retry one backed-off op now, or every failed one when `id` is `None`.
/// Returns how many ops were moved up.
pub(crate) fn retry_pending_ops(&self, id: Option<i64>) -> CoreResult<usize> {
let now = now_millis();
let moved = match id {
Some(id) => self.db.retry_op_now(id, now).map(usize::from),
None => self.db.retry_failed_ops_now(now),
}
.map_err(|e| CoreError::internal(format!("retrying the queue: {e}")))?;
if moved > 0 {
self.wake_drain();
}
Ok(moved)
}
}