Merge PR #3176: LR-IMP-ADD-DIALOG: an import's probe opens each file once

docs/whats-new.md: the PR's entry added under the 2026-10-27 heading after main's entry.
This commit is contained in:
aiongg
2026-10-31 02:10:38 +00:00
3 changed files with 34 additions and 6 deletions
+12 -6
View File
@@ -76,11 +76,17 @@ pub fn fs_pair_loader() -> PairLoader {
pub fn fs_hooks() -> (FileLoader, FileProbe) {
let loader: FileLoader = Arc::new(|path: &str, max_edge: usize| fs_load(path, max_edge, |bytes| load_vec(bytes, max_edge)));
let probe: FileProbe = Arc::new(|path: &str| {
let len = std::fs::metadata(path).map(|m| usize::try_from(m.len()).unwrap_or(usize::MAX)).unwrap_or(0);
use std::io::Read;
// one open, then everything from the open file: its size and modification time are not
// round trips of their own (on a network share each of them is one)
let mut file = std::fs::File::open(path).map_err(|e| format!("{path}: {e}"))?;
let meta = file.metadata().ok();
let len = meta.as_ref().map_or(0, |m| usize::try_from(m.len()).unwrap_or(usize::MAX));
let _permit = crate::memory::work_gate().acquire(len);
let bytes = std::fs::read(path).map_err(|e| format!("{path}: {e}"))?;
let mut bytes = Vec::new();
file.read_to_end(&mut bytes).map_err(|e| format!("{path}: {e}"))?;
let mut info = probe_bytes(path, &bytes)?;
info.file_modified = file_modified(path);
info.file_modified = meta.as_ref().and_then(modified_iso);
Ok(info)
});
(loader, probe)
@@ -104,10 +110,10 @@ fn fs_load<T>(path: &str, max_edge: usize, load: impl FnOnce(Vec<u8>) -> Result<
r
}
/// `path`'s modification time as ISO 8601 (see [`ProbeInfo::file_modified`]); `None` when the
/// The modification time in `meta` as ISO 8601 (see [`ProbeInfo::file_modified`]); `None` when the
/// filesystem doesn't say or the time is before 1970.
fn file_modified(path: &str) -> Option<String> {
let t = std::fs::metadata(path).ok()?.modified().ok()?;
fn modified_iso(meta: &std::fs::Metadata) -> Option<String> {
let t = meta.modified().ok()?;
let secs = i64::try_from(t.duration_since(std::time::UNIX_EPOCH).ok()?.as_secs()).ok()?;
Some(crate::import::civil(secs))
}
+15
View File
@@ -427,6 +427,21 @@ fn undated_files_take_the_file_date() {
let _ = std::fs::remove_dir_all(&dir);
}
/// The probe opens the file once and says the same thing as before about one that can't be read:
/// its path, then the system's words (a missing file, a folder).
#[test]
fn a_probe_of_a_file_that_cannot_be_read_names_the_path_and_the_cause() {
let dir = std::env::temp_dir().join(format!("lightcraft-probe-errors-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let (_, probe) = fs_hooks();
for path in [dir.join("no-such-file.jpg"), dir.clone()] {
let path = path.to_string_lossy().to_string();
let e = probe(&path).unwrap_err();
assert_eq!(e, format!("{path}: {}", std::fs::read(&path).unwrap_err()));
}
let _ = std::fs::remove_dir_all(&dir);
}
/// Issue #367: probes read headers, not pixels, and report what the decode-based probe did:
/// oriented dimensions (EXIF orientation 6 swaps them), metadata, the error for a truncated
/// file or one that isn't an image.
+7
View File
@@ -5,6 +5,13 @@
### The Edit menu groups Select Only Active Photo and Paste Settings from Previous with their kin
- Select Only Active Photo now sits under Deselect All, and Paste Settings from Previous under Paste Selected Settings…, instead of both after Settings…, near the end of the Edit menu. On macOS, where Settings… lives in the application menu, the Edit menu no longer shows two separator lines in a row.
### Reading a photo from a network share asks the server less
- Checking a photo (before an import, and for Reload, Recheck, Relink and Convert) asked for the file's size, then
opened it, then asked for its modification time as a question of its own. It now opens the file once and reads
both from the open file, so each photo costs the server fewer round trips (about a quarter less time to read a
set of photos from an SMB share, in one measurement on one share). What is imported and the messages for a file
that can't be read are the same.
## 2026-10-25
### `>=` and `<=` work in the search box