Skip to main content

mimick/library/masonry/
load.rs

1//! Async load plumbing for the masonry grid.
2//!
3//! `load_with_fallback` consults the shared `ThumbnailCache`, walking the
4//! `quality::fallback_bucket` chain on 404 so opt-in server features (e.g.
5//! fullsize generation) degrade gracefully to whatever the server has.
6//! `collect_dims` / `propagate_dimensions` keep the layout's `(w, h)` view
7//! of each asset in sync with what eventually decodes.
8
9use gdk4::Texture;
10use gtk::prelude::*;
11
12use crate::api_client::ThumbnailSize;
13use crate::library::asset_model::LibraryAssetModel;
14use crate::library::asset_object::AssetObject;
15use crate::library::masonry::quality::fallback_bucket;
16use crate::library::thumbnail_cache::ThumbnailCache;
17
18pub(crate) fn collect_dims(model: &LibraryAssetModel) -> Vec<(u32, u32)> {
19    let n = model.n_items();
20    let mut out = Vec::with_capacity(n as usize);
21    for i in 0..n {
22        if let Some(obj) = model.item(i).and_downcast::<AssetObject>() {
23            out.push((obj.property::<u32>("width"), obj.property::<u32>("height")));
24        } else {
25            out.push((0, 0));
26        }
27    }
28    out
29}
30
31/// Try requested bucket, then walk `fallback_bucket` chain on 404. Only the
32/// 404 path is retried — auth, network, etc. surface unchanged.
33pub(crate) async fn load_with_fallback<F: Fn() -> bool>(
34    cache: &ThumbnailCache,
35    asset_id: &str,
36    requested: ThumbnailSize,
37    is_cancelled: &F,
38) -> Result<Texture, String> {
39    let mut current = requested;
40    loop {
41        match cache
42            .load_thumbnail_cancellable(asset_id, current, is_cancelled)
43            .await
44        {
45            Ok(tex) => return Ok(tex),
46            Err(e) if e.contains("404") => match fallback_bucket(current) {
47                Some(next) => {
48                    log::debug!(
49                        "masonry fallback id={} {:?} -> {:?} ({})",
50                        asset_id,
51                        current,
52                        next,
53                        e
54                    );
55                    current = next;
56                }
57                None => return Err(e),
58            },
59            Err(e) => return Err(e),
60        }
61    }
62}
63
64/// Returns true if the AssetObject dimensions were filled in (relayout needed).
65pub(crate) fn propagate_dimensions(
66    model: &LibraryAssetModel,
67    asset_id: &str,
68    tex: &Texture,
69) -> bool {
70    let n = model.n_items();
71    for i in 0..n {
72        if let Some(obj) = model.item(i).and_downcast::<AssetObject>()
73            && obj.property::<String>("id") == asset_id
74        {
75            let w = obj.property::<u32>("width");
76            let h = obj.property::<u32>("height");
77            if w == 0 || h == 0 {
78                obj.set_property("width", tex.width() as u32);
79                obj.set_property("height", tex.height() as u32);
80                return true;
81            }
82            return false;
83        }
84    }
85    false
86}