test(previews): cover legacy smart preview discovery and nonblocking look refresh

This commit is contained in:
Nigel Ihza Firdausy 2026-10-11 04:54:04 +07:00
parent e90d83ecc3
commit a66a133041
3 changed files with 27 additions and 10 deletions

View File

@ -194,10 +194,7 @@ fn smart_run(
let _one = SMART_WRITE.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
// Read either the new shard or a pre-upgrade flat file, without making new work
// depend on having the original online.
let path = if path.exists() { path } else {
let legacy = path.file_name().map(|name| dir.join(name));
legacy.filter(|old| old.exists()).unwrap_or(path)
};
let path = crate::smart::existing_file_path(dir, path);
if discard {
if std::fs::remove_file(&path).is_ok() {
n.removed += 1;
@ -329,7 +326,7 @@ pub(crate) fn refresh_stale_smart_previews(s: &mut Session) -> Option<std::threa
let max_edge = crate::media::SourceLevel::Preview.max_edge();
let photos: Vec<_> = s.catalog.photos().filter(|p| matches!(p.source, lightcraft_catalog::Source::File { .. }) && p.relative_wb()).collect();
let jobs: Vec<SmartJob> =
photos.iter().map(|p| (p.id, crate::smart::file_path(&dir, p), Some(s.media.origin_ref(&p.source, max_edge)), true)).collect();
photos.iter().map(|p| (p.id, crate::smart::sharded_file_path(&dir, p), Some(s.media.origin_ref(&p.source, max_edge)), true)).collect();
std::thread::Builder::new()
.name("lc-smart-look".into())
.spawn(move || refresh_stale(&dir, jobs))
@ -340,7 +337,11 @@ pub(crate) fn refresh_stale_smart_previews(s: &mut Session) -> Option<std::threa
/// The body of [`refresh_stale_smart_previews`]: rebuild the stale proxies among `jobs`.
#[cfg(not(target_arch = "wasm32"))]
fn refresh_stale(dir: &std::path::Path, jobs: Vec<SmartJob>) {
let stale: Vec<SmartJob> = jobs.into_iter().filter(|(_, path, _, _)| crate::smart::is_stale(path)).collect();
let stale: Vec<SmartJob> = jobs
.into_iter()
.map(|(id, path, source, look)| (id, crate::smart::existing_file_path(dir, path), source, look))
.filter(|(_, path, _, _)| crate::smart::is_stale(path))
.collect();
if !stale.is_empty() {
// `custom`: the folder is there already, and must not be recreated if it went away since
match smart_run(dir, true, false, stale, None) {

View File

@ -31,9 +31,18 @@ pub fn sharded_file_path(dir: &Path, p: &Photo) -> PathBuf {
/// Read an existing proxy in either layout. New proxies use the sharded location; previews
/// made by older releases still load and can be moved with smartPreviewsLocation.
pub fn file_path(dir: &Path, p: &Photo) -> PathBuf {
let sharded = sharded_file_path(dir, p);
let legacy = dir.join(file_name(p));
if sharded.exists() || !legacy.exists() { sharded } else { legacy }
existing_file_path(dir, sharded_file_path(dir, p))
}
/// Resolve a sharded path to an older flat file when present. Call only off the UI thread:
/// checking a removable drive synchronously could stall drawing.
pub fn existing_file_path(dir: &Path, sharded: PathBuf) -> PathBuf {
if sharded.exists() {
return sharded;
}
let Some(name) = sharded.file_name() else { return sharded };
let legacy = dir.join(name);
if legacy.exists() { legacy } else { sharded }
}
/// The most header [`is_valid`] reads (the JSON line before the JPEG).
@ -189,7 +198,7 @@ pub fn migrate(from: &Path, to: &Path, what: Existing) -> (usize, Vec<String>) {
return (done, failed);
}
for src in files(from) {
let name = src.file_name().unwrap_or_default();
let Some(name) = src.file_name() else { continue };
let dst = name.to_str().and_then(|n| n.strip_suffix(".lcsp")).filter(|stem| {
stem.len() == 32 && stem.bytes().all(|b| b.is_ascii_hexdigit() && !b.is_ascii_uppercase())
}).map_or_else(|| to.join(name), |stem| to.join(&stem[..2]).join(name));

View File

@ -795,6 +795,13 @@ fn smart_previews_folder_is_chosen_per_library() {
assert_eq!(loc["path"], lib.join("Smart Previews").to_string_lossy().as_ref());
s.execute("library.smartPreviews", &json!({})).unwrap();
assert_eq!(crate::smart::stats(&lib.join("Smart Previews")).0, 1);
// A flat proxy from older builds remains discoverable and migrates to the new sharded layout.
let dir = lib.join("Smart Previews");
let photo = s.catalog.photo(id).unwrap();
let shard = crate::smart::sharded_file_path(&dir, photo);
let flat = dir.join(crate::smart::file_name(photo));
std::fs::rename(&shard, &flat).unwrap();
assert_eq!(s.execute("photo.smartPreview", &json!({})).unwrap()["smartPreview"], true);
// relative paths and missing drives are refused, and nothing is created on this drive
assert!(s.execute("library.smartPreviewsLocation", &json!({"path": "rel/dir"})).is_err());