From 8e49b9273d988eb62d32b3f237d449bca316db52 Mon Sep 17 00:00:00 2001 From: Tomas Dvorak Date: Sun, 20 Sep 2026 11:59:45 +0200 Subject: [PATCH] fix(desktop): tolerate missing `obfuscated` in thumb response (#167) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CE server's /file/thumb payload carries only `url`/`expires`, but the cloudreve-api model required `obfuscated`, so every thumbnail request failed deserialization and Explorer got E_FAIL for online-only placeholders. Hydrated files were unaffected — Windows thumbs them locally without the handler. `obfuscated` is now #[serde(default)]: missing → false → the time-flow decode path only runs when a server actually emits the flag. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- ROADMAP.md | 1 + .../cloudreve-api/src/models/explorer.rs | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/ROADMAP.md b/ROADMAP.md index 0a8812bd..a83e1e27 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -231,6 +231,7 @@ Goal: Windows + macOS + Linux from the `desktop/` tree in this repo. - Status: (1) notifications already per-OS (`win32_notif` / `notify_rust` / `mac_notification_sys`) — no abstraction needed; (2) hydration abstracted via `drive/placeholder` cfg swap — `cfapi` on Windows, `placeholder_non_windows` full-sync adapter elsewhere (FUSE / File Provider still open); (3) CI matrix builds + tests all 3 OSes; (4) packaging: `desktop-release.yml` on `desktop-v*` tags ships .msi/.exe (Windows), .dmg (macOS), .deb/.AppImage (Linux) — MSIX deferred (needs store signing). - Verified on Linux: `cargo test --workspace` green (49 tests), `cargo tauri build` produces working .deb + .AppImage. - Feature fallback on Linux/macOS until providers land: full sync without placeholders (download-on-access still works via sync engine). +- [x] #167 (upstream desktop#49) — online-only thumbnails missing in Explorer: root cause was a client/server contract mismatch — the CE `/file/thumb` response carries only `url`/`expires` while the `cloudreve-api` model required `obfuscated`, failing deserialization on every thumbnail request (`E_FAIL` to Explorer; hydrated files were unaffected since Windows thumbs them locally). `obfuscated` is now `#[serde(default)]`; the decode path still runs when a server emits the flag ## 7. Phase E — Android app (native, no iOS) diff --git a/desktop/crates/cloudreve-api/src/models/explorer.rs b/desktop/crates/cloudreve-api/src/models/explorer.rs index 285922bd..f64141c5 100644 --- a/desktop/crates/cloudreve-api/src/models/explorer.rs +++ b/desktop/crates/cloudreve-api/src/models/explorer.rs @@ -254,6 +254,7 @@ pub struct FileThumbResponse { pub url: String, #[serde(skip_serializing_if = "Option::is_none")] pub expires: Option, + #[serde(default)] pub obfuscated: bool, } @@ -575,3 +576,32 @@ pub enum FileEvent { /// Batch of file events with data Event(Vec), } + +#[cfg(test)] +mod tests { + use super::FileThumbResponse; + + #[test] + fn thumb_response_without_obfuscated_field_deserializes() { + // The CE server's /file/thumb response carries only `url`/`expires`; + // a missing `obfuscated` flag must not fail deserialization — otherwise + // every thumbnail request errors out and Explorer shows no thumbnails + // for online-only placeholders. + let res: FileThumbResponse = + serde_json::from_str(r#"{"url":"https://example.com/t.jpg","expires":null}"#) + .expect("response without `obfuscated` must deserialize"); + assert!(!res.obfuscated); + assert_eq!(res.url, "https://example.com/t.jpg"); + assert!(res.expires.is_none()); + } + + #[test] + fn thumb_response_with_obfuscated_field_deserializes() { + let res: FileThumbResponse = serde_json::from_str( + r#"{"url":"abc","expires":"2026-01-01T00:00:00Z","obfuscated":true}"#, + ) + .expect("response with `obfuscated` must deserialize"); + assert!(res.obfuscated); + assert_eq!(res.expires.as_deref(), Some("2026-01-01T00:00:00Z")); + } +}