Borrow the library to index it, and give it back
The passes that need every photograph's bytes — thumbnails, face indexing — now borrow each one and release it at the end. On a placeholder library that is the difference between peak disk being the working set and being the whole library. Including on cancellation, which was nearly missed: the face sweep returns mid-loop when the user presses Stop, and without releasing there the disk is spent and nothing is delivered for it. `materialise` now answers whether *it* fetched the content. The pool used to work that out by listing a file's parent directory — one listing per file across a library — when the backend already had to `stat` it to decide whether to ask. One syscall instead of a directory walk, and it removes the bug class the tests found earlier: a file at the library root has no `parent()`, so every one of them read as already-downloaded. **Pinning is the retention control**, and it drives the model the catalog already had rather than a second one. `tier_desired` is what the user asked to keep hydrated, `pending_pins` is the resumable work list, and a pinned collection is never dehydrated for the same reason it was never evicted. It was in fact *broken* here before: `get` on a stub failed, and the pin worker logged "one unreadable file must not abandon the whole pin" and silently did nothing. Pinned originals on such a library are recorded with `path = NULL` (`Cache::record_in_place`) rather than copied under `originals/`. Two reasons, and the second is the important one. A copy would hold every pinned photograph twice, with the budget able to evict the half that was not costing the disk. And `release` deletes the file a row names — so a row that names none cannot delete anything, which puts the one catastrophic operation out of reach by construction rather than by remembering not to call it. Deleting a materialised file inside a synced tree removes the photograph from the server and every other device. Handing disk back is `spawn_dehydrate`, which asks the client. Two gaps written down rather than papered over (docs/storage.md §7): a hydrating pass cannot yet quote its cost, because a stub reports no size; and the two sweeps hold separate pools, so a library indexed for both fetches twice.
This commit is contained in:
@@ -1326,13 +1326,30 @@ fn release_collection_offline(
|
||||
}
|
||||
};
|
||||
let images = collection_images(catalog, &ids);
|
||||
match cache.release(catalog.connection(), &images) {
|
||||
let outcome = match cache.release(catalog.connection(), &images) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
window.set_library_error(format!("removing local copies: {e}").into());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// TRACES: FR-NC-6c
|
||||
// On a placeholder library the bookkeeping above owns no files, so it
|
||||
// freed nothing — the originals are materialised in the library folder
|
||||
// and only the sync client may take them back. Asking it to is what
|
||||
// makes unpinning actually return the disk, and it must be a
|
||||
// dehydration rather than a delete: removing a file inside a synced
|
||||
// tree propagates to the server (ARCH §9.0a).
|
||||
//
|
||||
// Fire and forget: it is per-file work over a socket, the user has
|
||||
// already been told the pin is withdrawn, and a client that refuses
|
||||
// leaves the content where it is at no cost but disk.
|
||||
if let Some(conn) = ctl.session() {
|
||||
let path = library::catalog_path(&conn.account);
|
||||
std::mem::drop(library::spawn_dehydrate(conn, path, images));
|
||||
}
|
||||
outcome
|
||||
};
|
||||
|
||||
let (count, freed) = released;
|
||||
|
||||
Reference in New Issue
Block a user