//! TRACES: FR-CAT-1 | FR-CAT-4 | FR-NC-3 | NFR-P9 //! Opening a remote library: scan → catalog → grid. //! //! This is the wire between three pieces that already worked separately — //! `dr_sync::scan` walks the tree, `dr_catalog` indexes it, and //! `dr_decode::preview` turns bytes into pixels. Until now the "Open library" //! button logged its intent and stopped. //! //! # Threading //! //! Slint's event loop is single-threaded and must never block (NFR-P9), so //! every network and decode operation runs on a worker thread and results //! return through an mpsc channel drained by a Slint timer. That is the same //! shape the login flow uses; it is repeated rather than shared because the //! message types differ and a generic version would obscure both. //! //! # Why thumbnails are fetched, not derived from the scan //! //! A scan yields paths and sizes, nothing visual. Each thumbnail costs its own //! range request, so they are fetched **only for cells the grid actually //! wants** — never for the whole library up front. On the reference library //! that is the difference between a few MB and ~370 GB (ARCH §6.7). //! //! Split into one module per area (docs/dev/code-health.md CH-1): filtering, //! sidecar writes, on-disk paths, scanning, XMP, thumbnail fetch and //! generation, the background sweeps, and reading the grid's cells and spans. //! `mod.rs` re-exports every module's public items so nothing outside //! `library` needs to change. mod cells; mod filters; mod paths; mod scan; mod sidecar; mod spans; mod sweep; mod thumbnails_fetch; mod thumbnails_gen; mod xmp; pub use cells::*; pub use filters::*; pub use paths::*; pub use scan::*; pub use sidecar::*; pub use spans::*; pub use sweep::*; pub use thumbnails_fetch::*; pub use thumbnails_gen::*; pub use xmp::*; /// What narrows the grid, when something does. /// /// A collection is a set of originals the photographer files; an album is the /// photographs whose exports went into one folder. The grid reads either the /// same way — see `spans::scope_clause`, the one place the difference is /// spelled — and the whole library is `None`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Scope { Collection(dr_types::CollectionId), Album(dr_catalog::AlbumId), } #[cfg(test)] pub(crate) mod test_support { //! Fixtures shared with the modules that build on this one's catalog //! layout -- `repairs`, whose work lists are read over the same `images` //! and `remote` rows a scan writes. use dr_catalog::Catalog; use dr_sync::{RemoteEntry, RemoteId, RemotePath}; use super::scan::persist; pub fn entry(path: &str, file_id: u64, size: u64) -> RemoteEntry { RemoteEntry { id: RemoteId::Stable(file_id), path: RemotePath::new(path), kind: dr_sync::EntryKind::File, validator: dr_sync::Validator::new("v"), size, modified: None, has_preview: false, materialised: true, } } /// A catalog with `n` images, ready to file into collections. pub fn with_images(n: usize) -> Catalog { let catalog = Catalog::in_memory().unwrap(); let images: Vec = (0..n) .map(|i| entry(&format!("PhotosRaw/img{i:03}.CR2"), i as u64, 1000)) .collect(); let result = dr_sync::ScanResult { images, directories: vec![(RemotePath::new("PhotosRaw"), dr_sync::Validator::new("e"))], progress: Default::default(), sidecars: Vec::new(), }; persist(&catalog, "PhotosRaw", &result).unwrap(); catalog } /// A catalog with `n` scanned images, none trashed. /// /// Shared with `spans`, whose collection-membership tests build on the /// same "n images, no folders beyond the root" catalog `cells`'s trash /// tests do. pub fn scanned(n: u64) -> Catalog { let catalog = Catalog::in_memory().unwrap(); let images = (1..=n) .map(|i| entry(&format!("PhotosRaw/IMG_{i:04}.CR2"), 1000 + i, 30_000_000)) .collect(); persist( &catalog, "PhotosRaw", &dr_sync::ScanResult { images, directories: vec![(RemotePath::new("PhotosRaw"), dr_sync::Validator::new("e1"))], progress: Default::default(), sidecars: Vec::new(), }, ) .unwrap(); catalog } pub fn image_ids(catalog: &Catalog) -> Vec { let mut stmt = catalog .connection() .prepare("SELECT id FROM images ORDER BY source_ref") .unwrap(); stmt.query_map([], |r| Ok(dr_types::ImageId(r.get::<_, i64>(0)? as u64))) .unwrap() .map(Result::unwrap) .collect() } }