diff --git a/Cargo.lock b/Cargo.lock index a74d717..85b2820 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,6 +127,12 @@ dependencies = [ "equator", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -1685,6 +1691,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -2207,6 +2219,17 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hashbrown" version = "0.17.1" @@ -2954,6 +2977,15 @@ dependencies = [ "imgref", ] +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + [[package]] name = "lru-slab" version = "0.1.2" @@ -3780,6 +3812,7 @@ dependencies = [ "getrandom 0.4.3", "hex", "hmac 0.13.0", + "lru", "pgp", "proton-sdk", "reqwest", @@ -3911,7 +3944,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] diff --git a/crates/pdfs-fuse/src/lib.rs b/crates/pdfs-fuse/src/lib.rs index c0e6437..24ae6bf 100644 --- a/crates/pdfs-fuse/src/lib.rs +++ b/crates/pdfs-fuse/src/lib.rs @@ -745,6 +745,21 @@ impl Core { if stored.is_empty() { return; } + // A write queued by a previous run carries the optimistic size the file + // grew to, which the sealed remote revision does not yet reflect. The DB + // node still holds the pre-write `claimed_size` (often 0 for a fresh + // file), so without this a queued 500 MB file `stat`s as 0 bytes until + // the drain lands — reads still serve from the staged blob, but `ls` + // lying about the size reads as data loss. `hydrate_pending` ran before + // us, so the pending map is populated; snapshot it here rather than hold + // both locks at once. + let pending_sizes: HashMap = { + let pending = self.pending.lock().unwrap(); + pending + .iter() + .map(|(uid, pr)| (uid.clone(), pr.meta.len)) + .collect() + }; let mut st = self.state.lock().unwrap(); // Pass 1: assign a stable inode to every uid (root is already mapped). @@ -761,10 +776,17 @@ impl Core { // Track folders flagged complete so their listings rebuild in pass 3. let mut listed_dirs: Vec = Vec::new(); for sn in stored { - let StoredNode { node, listed } = sn; + let StoredNode { mut node, listed } = sn; let Some(&ino) = st.by_uid.get(&node.uid) else { continue; }; + // Re-apply a queued write's optimistic size so `stat` matches what + // reads (served from the staged blob) already return. + if let Some(&len) = pending_sizes.get(&node.uid) + && let NodeKind::File { claimed_size, .. } = &mut node.kind + { + *claimed_size = Some(len as i64); + } if listed && node.is_folder() { listed_dirs.push(ino); } @@ -6490,6 +6512,14 @@ async fn run_event_sync( } debug!(count = events.len(), "applying remote events"); for event in &events { + // Converge the SDK's own caches (folder keys, entity cache) on the + // server before applying the event to our tree. Without this, a node + // re-keyed/moved by another client keeps a stale key in the SDK for + // the life of the daemon (SDK plan #9). `apply_event` only touches + // our FUSE state, so nothing else does this. + if let Err(e) = client.invalidate_caches_for_event(event).await { + warn!(error = %e, "sdk cache invalidation for event failed"); + } apply_event(&state, &content, &pending, ¬ifier, event); } cursor = events.last().map(|e| e.id().clone());