From bbd913b87d99a3678b2146b4a100f9955b2bb3c5 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Mon, 2 Nov 2026 15:18:31 +0100 Subject: [PATCH] fix(cli): show the last sync as a time in sync list and devices list `pdfs sync list` and `pdfs devices list` printed the last sync as epoch seconds, such as `synced: 1793570850` in issue #46. Both columns now go through `last_synced`, which formats it like `pdfs activity` and prints `never` for a folder that never synced. `--json` output is unchanged (B278). --- crates/pdfs-cli/src/main.rs | 28 +++++++++++++++++++--------- docs/BUGS.md | 25 ++++++++++++++++++++++++- docs/CHANGELOG.md | 3 +++ 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/crates/pdfs-cli/src/main.rs b/crates/pdfs-cli/src/main.rs index 8109bf2..90cd4ce 100644 --- a/crates/pdfs-cli/src/main.rs +++ b/crates/pdfs-cli/src/main.rs @@ -1036,10 +1036,7 @@ fn cmd_devices(action: DeviceCmd) -> Result<()> { CtlResponse::Devices { items } if items.is_empty() => println!("No devices."), CtlResponse::Devices { items } => { for d in items { - let sync = d - .last_sync - .map(|t| t.to_string()) - .unwrap_or_else(|| "never".to_string()); + let sync = last_synced(d.last_sync); let tag = match (d.adopted, d.this_device) { (true, _) => " *adopted*", (false, true) => " *this machine*", @@ -1130,11 +1127,7 @@ fn cmd_sync(action: SyncCmd) -> Result<()> { } CtlResponse::SyncFolders { items } => { for f in items { - let sync = if f.last_sync == 0 { - "never".to_string() - } else { - f.last_sync.to_string() - }; + let sync = last_synced(Some(f.last_sync)); // A queued switch is reported as the mode the folder is moving // to, so `sync list` never reads as if the request was dropped. let mode = match &f.pending_mode { @@ -1915,6 +1908,15 @@ fn format_epoch(secs: i64) -> String { format!("{m:02}-{d:02} {h:02}:{mi:02}") } +/// The `synced:` column of `sync list` and `devices list`: when the last pass +/// finished, or `never`. The daemon reports 0 for a folder that never synced. +fn last_synced(secs: Option) -> String { + match secs { + Some(secs) if secs > 0 => format_epoch(secs), + _ => "never".to_string(), + } +} + fn cmd_leave(uid: String) -> Result<()> { ok_or_bail(control_request(CtlRequest::LeaveShared { uid })?) } @@ -4452,6 +4454,14 @@ mod login_wait_tests { assert_eq!(format_epoch_utc(-86_400), "1969-12-31"); } + #[test] + fn last_synced_shows_a_time_not_epoch_seconds() { + // B278: issue #46 showed `synced: 1793570850`. + assert_eq!(last_synced(Some(1_793_570_850)), "11-01 22:07"); + assert_eq!(last_synced(Some(0)), "never"); + assert_eq!(last_synced(None), "never"); + } + #[test] fn a_forged_looking_invitation_is_flagged_not_stated() { let mut entry = pdfs_core::control::DirEntry { diff --git a/docs/BUGS.md b/docs/BUGS.md index fac94e1..4356d24 100644 --- a/docs/BUGS.md +++ b/docs/BUGS.md @@ -109,6 +109,7 @@ a live account since. | Entry | Title | |---|---| +| [B278](#b278--sync-list-and-devices-list-show-the-last-sync-as-epoch-seconds) | `sync list` and `devices list` show the last sync as epoch seconds | | [B277](#b277--my-files-mounted-over-a-folder-with-files-in-it-and-hid-them) | My files mounted over a folder with files in it, and hid them | | [B274](#b274--opening-a-file-for-writing-fails-with-eio-while-its-queued-revision-lands) | Opening a file for writing fails with EIO while its queued revision lands | | [B272](#b272--after-a-folder-is-removed-and-added-again-its-file-events-go-to-the-old-id) | After a folder is removed and added again, its file events go to the old id | @@ -380,6 +381,26 @@ a live account since. Newest first. +### B278 — `sync list` and `devices list` show the last sync as epoch seconds + +**Status:** Fixed, not verified live +**Found:** 2026-11-02, issue #46. The reporter's `pdfs sync list` on 3.6.0 read +`synced: 1793570850`, a pass that finished at 2026-11-01 22:07 UTC. +**Where:** `cmd_sync` and `cmd_devices` in `crates/pdfs-cli/src/main.rs` + +**Repro:** Let a synced folder finish one pass and run `pdfs sync list`, or `pdfs devices list` +on an account with a device that synced. + +**Cause.** Both commands printed `last_sync` with `to_string()`. The activity feed already +formats the same epoch seconds with `format_epoch`. + +**Fix.** Both columns go through `last_synced`, which prints `MM-DD HH:MM` in UTC like +`pdfs activity`, and `never` for 0 or no value. `--json` output is unchanged. + +**Test:** `last_synced_shows_a_time_not_epoch_seconds` in `crates/pdfs-cli/src/main.rs`. + +--- + ### B277 — My files mounted over a folder with files in it, and hid them **Status:** Fixed, not verified live @@ -575,7 +596,9 @@ folder list has now. **Status:** Fixed, not verified live **Found:** 2026-10-31, code audit after issue #46: a folder of about 19,900 items restored on a second computer sat at `scanning…` with nothing downloaded, and the journal showed -`RequestTimeout (http 408)`. +`RequestTimeout (http 408)`. On 2026-11-01 the reporter's 3.6.0 journal showed the same pass +failing four times in four minutes with `reconcile failed id=1 error=enumerate …: HTTP transport +error`, after the laptop woke from sleep; a later pass finished. **Where:** `walk_remote` in `crates/pdfs-fuse/src/sync.rs` **Repro:** Restore or add a mirror folder with tens of thousands of items on Drive, on a link diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index da3abf4..a13d02f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -78,6 +78,9 @@ scratch (user data in `staging/` and `recovery/` is never touched by this). - **A restore that skipped folders is reported as failed**, with the reason for each, instead of "restored 0 folder(s)" as a success (B269). +- **`pdfs sync list` and `pdfs devices list` show when a folder last synced as a time**, such as + `synced: 11-01 22:07` in UTC like `pdfs activity`, instead of seconds since 1970 (B278, #46). + - **"You're offline" no longer appears for a cache or database error** when pinning, unpinning, searching or loading a folder (B266, #38). The app shows the actual error.