mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-04 16:56:59 +01:00
fix(core,fuse,gui,cli): pair a Pixel raw with its JPEG, and stop refresh timing out
Three things the 12,445-photo library showed that the tests did not. A Pixel raw capture is two files named after the role each plays — `PXL_….RAW-01.COVER.jpg` and `PXL_….RAW-02.ORIGINAL.dng` — so their stems differ and the pairing rule never saw one shot. The marker is now cut off before names are compared, narrowly: digits, then one role word, so an ordinary name that happens to contain `.raw-` keeps its stem. Fed the real library's first 40 rows through the grouping, they collapse to 21 tiles. A raw that has a JPEG beside it is now painted with that JPEG's thumbnail, which the server usually already holds. The Raw tab costs no raw download and no raw decode for those tiles. Raws are found in one batched read, so an ordinary page pays nothing for the lookup. Photos could not be moved to Trash at all: they live on the photo volume, which the mount never holds, so no `nodes` row exists, every authority answered `Unknown`, and the write check refused the whole library with "trash access: not allowed". Membership of our own timeline is the authority that does exist for a photo, and it now answers. An album photo on someone else's volume still fails closed. Refreshing the gallery then reported `io: Resource temporarily unavailable (os error 11)` (B92). The daemon held the control reply until the whole timeline had been re-read — 63 batches of 200 nodes for this library, minutes of work against a 120 s read bound. The daemon now answers as soon as the refresh is running, `PhotosRefreshStatus` reports when it is done, and both front ends follow it: the gallery reloads the page when it clears, and `pdfs refresh photos` blocks so the command still means what it says. Each refresh logs its photo count, batch count and duration. Released as 1.11.0.
This commit is contained in:
1 parent
09c0684a5e
commit
376f3edb28
12 files changed
+378
-30
No files matched your search
@@ -97,14 +97,39 @@ fn capture_day(conn: &rusqlite::Connection, capture_time: i64) -> Option<String>
|
||||
}
|
||||
|
||||
/// The file name without its extension, lowercased — `IMG_1234.CR2` and
|
||||
/// `IMG_1234.JPG` share `img_1234`.
|
||||
/// `IMG_1234.JPG` share `img_1234` — with the camera's own part-of-a-shot
|
||||
/// marker removed, so the two files a raw capture writes share a stem too.
|
||||
fn name_stem(name: Option<&str>) -> Option<String> {
|
||||
let name = name?;
|
||||
let stem = match name.rsplit_once('.') {
|
||||
Some((stem, _)) if !stem.is_empty() => stem,
|
||||
_ => name,
|
||||
};
|
||||
Some(stem.to_ascii_lowercase())
|
||||
let stem = stem.to_ascii_lowercase();
|
||||
Some(strip_shot_marker(&stem).to_string())
|
||||
}
|
||||
|
||||
/// Drop a `.RAW-NN.ROLE` marker from a stem.
|
||||
///
|
||||
/// Pixel stores one raw capture as `PXL_20260919_000625670.RAW-01.COVER.jpg`
|
||||
/// and `PXL_20260919_000625670.RAW-02.ORIGINAL.dng`: one shot, two files, two
|
||||
/// stems that differ in exactly the part naming which file of the shot this is.
|
||||
/// Cutting the marker off is what lets rule 2 see the pair. The check is narrow
|
||||
/// on purpose — digits, then a single role word — so an ordinary name that
|
||||
/// happens to contain `.raw-` keeps its stem.
|
||||
fn strip_shot_marker(stem: &str) -> &str {
|
||||
let Some(at) = stem.rfind(".raw-") else {
|
||||
return stem;
|
||||
};
|
||||
let rest = &stem[at + ".raw-".len()..];
|
||||
let Some((index, role)) = rest.split_once('.') else {
|
||||
return stem;
|
||||
};
|
||||
let marked = !index.is_empty()
|
||||
&& index.bytes().all(|b| b.is_ascii_digit())
|
||||
&& !role.is_empty()
|
||||
&& role.bytes().all(|b| b.is_ascii_alphabetic());
|
||||
if marked { &stem[..at] } else { stem }
|
||||
}
|
||||
|
||||
/// One entry of the grouping pass: what rule 2 needs, plus what choosing a
|
||||
|
||||
@@ -4040,6 +4040,51 @@ fn a_raw_and_its_jpeg_are_one_entry_in_the_grid() {
|
||||
assert_eq!(db.photos_group("alone").unwrap().len(), 1);
|
||||
}
|
||||
|
||||
/// Pixel names the two files of one raw capture after the role each plays, so
|
||||
/// their stems differ. The shot is still one shot, and the grid shows the cover
|
||||
/// JPEG.
|
||||
#[test]
|
||||
fn a_pixel_raw_capture_groups_with_its_cover_jpeg() {
|
||||
let db = Db::open_in_memory().unwrap();
|
||||
db.photos_replace(&[
|
||||
TimelineRow {
|
||||
name: Some("PXL_20260919_000625670.RAW-01.COVER.jpg".into()),
|
||||
..TimelineRow::new("cover", 1_789_776_385)
|
||||
},
|
||||
TimelineRow {
|
||||
name: Some("PXL_20260919_000625670.RAW-02.ORIGINAL.dng".into()),
|
||||
..TimelineRow::new("original", 1_789_776_385)
|
||||
},
|
||||
// A name that merely contains the marker's opening is not a marker.
|
||||
TimelineRow {
|
||||
name: Some("scan.raw-notes.jpg".into()),
|
||||
..TimelineRow::new("notes", 1_789_776_385)
|
||||
},
|
||||
TimelineRow {
|
||||
name: Some("scan.raw-other.dng".into()),
|
||||
..TimelineRow::new("other", 1_789_776_385)
|
||||
},
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let page = db.photos_page(0, 10, None, None, false).unwrap();
|
||||
assert_eq!(
|
||||
page.iter().map(|p| p.uid.as_str()).collect::<Vec<_>>(),
|
||||
["cover", "notes", "other"],
|
||||
"the capture shows once, as the cover JPEG"
|
||||
);
|
||||
assert_eq!(page[0].group_size, 2);
|
||||
assert!(page[0].has_raw);
|
||||
assert_eq!(
|
||||
db.photos_group("original")
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|p| p.uid.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
["cover", "original"]
|
||||
);
|
||||
}
|
||||
|
||||
/// Two files that merely share a name are not one shot: the same stem on
|
||||
/// different days, or two files of the same kind, stay apart.
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user