mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-26 02:59:17 -06:00
catalog: slice captured dates on char boundaries so non-ASCII dates don't panic
This commit is contained in:
@@ -397,10 +397,13 @@ impl Catalog {
|
||||
let mut years: BTreeMap<&str, (BTreeMap<&str, usize>, BTreeMap<&str, usize>)> = BTreeMap::new();
|
||||
for p in self.photos().filter(|p| p.in_library()) {
|
||||
let Some(d) = p.captured.as_deref() else { continue };
|
||||
if d.len() >= 10 {
|
||||
let (months, days) = years.entry(&d[..4]).or_default();
|
||||
*months.entry(&d[..7]).or_default() += 1;
|
||||
*days.entry(&d[..10]).or_default() += 1;
|
||||
// `get`, not `[..]`: a corrupt or foreign `captured` (a Lightroom catalog's
|
||||
// captureTime is copied verbatim) may not be ASCII, and slicing it on a byte index
|
||||
// would panic — the same rule as `dates::group_label`.
|
||||
if let (Some(year), Some(month), Some(day)) = (d.get(..4), d.get(..7), d.get(..10)) {
|
||||
let (months, days) = years.entry(year).or_default();
|
||||
*months.entry(month).or_default() += 1;
|
||||
*days.entry(day).or_default() += 1;
|
||||
}
|
||||
}
|
||||
let newest_first = |m: BTreeMap<&str, usize>| m.into_iter().rev().map(|(k, n)| (k.to_string(), n)).collect::<Vec<_>>();
|
||||
|
||||
@@ -123,6 +123,23 @@ fn filter_search_sort() {
|
||||
assert_eq!(c.keywords(), vec![("ocean".to_string(), 1), ("summer".to_string(), 1)]);
|
||||
}
|
||||
|
||||
/// A `captured` string that is not ASCII (the Lightroom importer copies `captureTime` into an
|
||||
/// `AddPhoto` op verbatim, and a JSON snapshot is valid UTF-8 whatever it holds) must not be
|
||||
/// sliced on a byte index in `date_groups`: grouping counts the well-formed dates and ignores
|
||||
/// the rest instead of panicking on a char boundary.
|
||||
#[test]
|
||||
fn date_groups_ignores_a_non_ascii_captured() {
|
||||
let mut c = Catalog::new();
|
||||
let ok = photo(&mut c, "ok.jpg", "2026-05-01T10:00:00");
|
||||
// `AddPhoto` takes the field as it is, like `library.importLightroom` building the op
|
||||
let bad = photo(&mut c, "bad.jpg", "日本語日本語");
|
||||
assert_ne!(ok, bad);
|
||||
let g = c.date_groups();
|
||||
assert_eq!(g.len(), 1, "the one well-formed date still groups");
|
||||
assert_eq!((g[0].year.as_str(), g[0].count), ("2026", 1));
|
||||
assert_eq!(g[0].days.iter().map(|d| d.1).sum::<usize>(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_and_log_replay() {
|
||||
let mut c = Catalog::new();
|
||||
|
||||
Reference in New Issue
Block a user