Stop the scan queueing a thumbnail job per photograph

The reference catalog held 23,582 Thumbnail jobs, one per image, and
every scan re-coalesced all of them. Nothing has ever claimed that kind:
no JobHandler is registered for it on desktop or Android, and
dr_catalog::sync never merges another device's jobs in.

Thumbnails are owed by the store, not the queue. The grid's worker and
the thumbnail sweep both find their work by asking ThumbStore what it
lacks, and the store is shared between devices, so it is the only record
that knows another device already made one. A queue row was a second,
staler copy of that debt that grew with the library and was read by
nothing.

persist still writes the images and their remote identities in the one
transaction; it just no longer adds a row to jobs for each of them. The
two tests that asserted the rows existed become one that asserts a
repeated scan queues nothing.

Refs #73
This commit is contained in:
2026-09-26 13:09:44 -04:00
parent 8a1d9c8642
commit 5da28584a4
2 changed files with 24 additions and 41 deletions
File diff suppressed because one or more lines are too long
+21 -38
View File
@@ -1,7 +1,7 @@
//! Walking a remote library into the catalog, and pulling other devices' //! Walking a remote library into the catalog, and pulling other devices'
//! judgements out of the sidecars the walk finds along the way. //! judgements out of the sidecars the walk finds along the way.
use dr_catalog::{Catalog, JobKind, Priority}; use dr_catalog::Catalog;
use dr_sync::{Connection, RemoteBackend, RemoteId, RemotePath}; use dr_sync::{Connection, RemoteBackend, RemoteId, RemotePath};
use dr_types::FormatFilter; use dr_types::FormatFilter;
use std::path::PathBuf; use std::path::PathBuf;
@@ -580,8 +580,14 @@ pub(super) fn load_folder_etags(
/// ///
/// Images insert at `metadata_state = 1` (stat-only): the scan knows name and /// Images insert at `metadata_state = 1` (stat-only): the scan knows name and
/// size but has read no EXIF, and pretending otherwise would make a date /// size but has read no EXIF, and pretending otherwise would make a date
/// filter silently wrong. A `Thumbnail` job is enqueued per image, coalescing /// filter silently wrong.
/// with anything already pending. ///
/// No job is queued. This used to enqueue a `Thumbnail` job per photograph,
/// and nothing has ever claimed that kind: the grid's worker and the thumbnail
/// sweep both find their work by asking the store what it lacks, which is the
/// only place that knows another device already made one. The reference
/// catalog held 23,582 of them, one per image, re-coalesced on every scan
/// (#73; catalog.md §6.1).
pub(super) fn persist( pub(super) fn persist(
catalog: &Catalog, catalog: &Catalog,
root: &str, root: &str,
@@ -700,20 +706,6 @@ pub(super) fn persist(
entry.path.as_str() entry.path.as_str()
])?; ])?;
} }
// Its thumbnail job, in the same transaction as the row it points at,
// so a failure mid-insert cannot leave one pointing at a row that
// never landed. It used to follow the commit, one autocommitting
// statement per photograph, each finding its image again by path: a
// relisted folder of 1,600 paid 1,600 commits for rows that are
// almost all already queued.
let _ = dr_catalog::jobs::enqueue(
&tx,
JobKind::Thumbnail,
Some(image_id),
Priority::Background,
None,
);
} }
drop((folder_of, upsert, image_of, remote)); drop((folder_of, upsert, image_of, remote));
@@ -819,7 +811,11 @@ mod tests {
} }
#[test] #[test]
fn a_thumbnail_job_is_queued_per_image() { fn a_scan_queues_no_work_nothing_would_claim() {
// Every photograph used to leave a `Thumbnail` job behind, and no
// handler for that kind exists: thumbnails are owed by the store, not
// the queue. Scanned twice, because the second scan is the one that
// used to re-coalesce every row.
let catalog = Catalog::in_memory().unwrap(); let catalog = Catalog::in_memory().unwrap();
let result = dr_sync::ScanResult { let result = dr_sync::ScanResult {
images: vec![ images: vec![
@@ -831,31 +827,18 @@ mod tests {
sidecars: Vec::new(), sidecars: Vec::new(),
}; };
persist(&catalog, "PhotosRaw", &result).unwrap(); persist(&catalog, "PhotosRaw", &result).unwrap();
let jobs: i64 = catalog
.connection()
.query_row("SELECT count(*) FROM jobs WHERE kind = 2", [], |r| r.get(0))
.unwrap();
assert_eq!(jobs, 2);
}
#[test]
fn a_second_scan_does_not_multiply_jobs() {
let catalog = Catalog::in_memory().unwrap();
let result = dr_sync::ScanResult {
images: vec![entry("PhotosRaw/a.CR2", 1, 30_000_000)],
directories: vec![(RemotePath::new("PhotosRaw"), dr_sync::Validator::new("e1"))],
progress: Default::default(),
sidecars: Vec::new(),
};
persist(&catalog, "PhotosRaw", &result).unwrap();
persist(&catalog, "PhotosRaw", &result).unwrap(); persist(&catalog, "PhotosRaw", &result).unwrap();
let jobs: i64 = catalog let jobs: i64 = catalog
.connection() .connection()
.query_row("SELECT count(*) FROM jobs WHERE kind = 2", [], |r| r.get(0)) .query_row("SELECT count(*) FROM jobs", [], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(jobs, 1, "coalesced, not queued twice"); assert_eq!(jobs, 0);
let images: i64 = catalog
.connection()
.query_row("SELECT count(*) FROM images", [], |r| r.get(0))
.unwrap();
assert_eq!(images, 2, "the scan still records what it found");
} }
#[test] #[test]