feat: prune orphaned photo rows + retry-pending action
Adds /api/v1/library/maintenance/{missing-stats,prune-missing} backed
by a new cleanup helper that deletes Photo rows whose files no longer
exist on disk under a *mounted* source root. Skips photos under
unmounted roots so a temporarily-disconnected drive doesn't get
silently nuked.
Settings panel surfaces the orphan count with a destructive Prune
button, plus a "Kick pending" action that re-queues photos stuck in
processing_status='pending' (typically left behind when the scanner
created the row but the worker never picked up the thumbnail task).
Common trigger: PHOTO_DIRS in .env was repointed at a different
library root, leaving every old row dangling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -230,6 +230,18 @@ export interface RegenerateResult {
|
||||
}
|
||||
}
|
||||
|
||||
export interface MissingStats {
|
||||
would_delete?: number
|
||||
deleted?: number
|
||||
skipped_unmounted: number
|
||||
dry_run: boolean
|
||||
}
|
||||
|
||||
export interface PruneResult extends MissingStats {
|
||||
status?: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
export interface WorkerInfo {
|
||||
name: string
|
||||
status: 'online' | 'unreachable'
|
||||
@@ -295,7 +307,11 @@ export const library = {
|
||||
/** Reset on-disk thumbs and re-queue Celery generation. With no
|
||||
* filters, every photo in the library is re-queued. */
|
||||
regenerateThumbnails: async (
|
||||
body: { media_types?: MediaType[]; only_failed?: boolean } = {}
|
||||
body: {
|
||||
media_types?: MediaType[]
|
||||
only_failed?: boolean
|
||||
only_pending?: boolean
|
||||
} = {}
|
||||
): Promise<RegenerateResult> => {
|
||||
const response = await api.post(
|
||||
'/library/maintenance/regenerate-thumbnails',
|
||||
@@ -312,6 +328,19 @@ export const library = {
|
||||
return response.data
|
||||
},
|
||||
|
||||
/** Dry-run count of photo rows whose files are no longer on disk
|
||||
* (under a mounted source root). */
|
||||
missingStats: async (): Promise<MissingStats> => {
|
||||
const response = await api.get('/library/maintenance/missing-stats')
|
||||
return response.data
|
||||
},
|
||||
|
||||
/** Actually delete the orphaned photo rows. */
|
||||
pruneMissing: async (): Promise<PruneResult> => {
|
||||
const response = await api.post('/library/maintenance/prune-missing')
|
||||
return response.data
|
||||
},
|
||||
|
||||
/** Re-run the source-roots / folders / photos integrity cleanup that
|
||||
* normally runs on backend startup. */
|
||||
cleanup: async (): Promise<{ status: string; message?: string }> => {
|
||||
|
||||
Reference in New Issue
Block a user