fix(thumbnails): neither import job may tear down the shared directory
Found on a sandbox restore. `thumb_derived_import` ran first, imported and deleted its own hash-named sidecars, then found `remove_dir` refused because the `ext-*.jpg` previews were still there — those belong to `thumb_attached_import`. The rename fallback fired, moving the tree to `.thumbnails.migrated`; the attached job then looked in `.thumbnails/`, found nothing, and reported zeros. That stranded the user-uploaded previews, which are the one class of file here with no render path to rebuild them. The rename exists for files NEITHER job claims — a `.DS_Store` blocking removal forever — and it fired for the sibling's work in progress instead. Inverting the job order does not help: once the tree is renamed, both jobs look at `.thumbnails/` and find nothing, whatever order they run in. Teardown is now shared and refuses to act while anything remains that either job would claim. Both jobs call it, so whichever finishes last removes the tree in the same boot rather than leaving an empty directory until the next one. The rename survives for its original purpose, and now only fires when the remaining files are genuinely nobody's. Also drops the daily tick on both imports — they are on-demand now. The boot run in repair mode IS the migration: nothing has written a sidecar since step 10d2, so the tail cannot grow afterwards, and a tick could not finish the job anyway because ticks never pass `repair`. Once drained it was a `read_dir` returning nothing, every day, forever. UX: the "at boot" badge moves from beside the job name into the cadence column. It answers WHEN a job runs, which is what that column is for — next to the name it read as a property of the job, and the row could show "on-demand" beside a badge saying otherwise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -89,15 +89,11 @@ impl ThumbAttachedImport {
|
||||
registry: &JobRegistry,
|
||||
provider: &Arc<dyn JobStoreProvider>,
|
||||
) -> Arc<Self> {
|
||||
// Daily, matching `thumb_derived_import` — and it does not delete on
|
||||
// the tick either, since `repair` defaults false. See that job for
|
||||
// the reasoning.
|
||||
// On-demand, matching `thumb_derived_import` — the boot run in repair
|
||||
// mode is the migration, and a tick could not finish it anyway
|
||||
// because ticks never pass `repair`. See that job for the reasoning.
|
||||
registry
|
||||
.register_recoverable_job(
|
||||
self.clone(),
|
||||
provider.clone(),
|
||||
Some(std::time::Duration::from_secs(24 * 3600)),
|
||||
)
|
||||
.register_recoverable_job(self.clone(), provider.clone(), None)
|
||||
.await;
|
||||
self
|
||||
}
|
||||
@@ -480,6 +476,20 @@ impl RecoverableJobHandler for ThumbAttachedImport {
|
||||
}
|
||||
}
|
||||
|
||||
// Both jobs attempt the teardown, and it no-ops unless the tree is
|
||||
// drained of files EITHER of them claims. Without this, whichever
|
||||
// job runs last leaves an empty `.thumbnails/` behind until the
|
||||
// next boot; with it, the tree disappears in the same run that
|
||||
// empties it, whatever order the two ran in.
|
||||
if delete_imported {
|
||||
crate::infrastructure::services::thumb_derived_import_service::teardown_if_drained(
|
||||
&self.thumbnails_root,
|
||||
THUMB_ATTACHED_IMPORT_JOB_NAME,
|
||||
&store.run_id().to_string(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
target: "oxicloud::dedup",
|
||||
event = "thumb_attached_import.completed",
|
||||
|
||||
Reference in New Issue
Block a user