perf(frontend): invalidate duplicate-scan cache concurrently and safely
Cache invalidation across affected folders after a bulk delete ran sequentially through awaited invoke calls, duplicated between gallerySlice and duplicateSlice. Extract a shared invalidateDuplicateScanCaches helper that batches the calls with Promise.allSettled, logging failures instead of throwing — a best-effort cache refresh should never turn an already-successful delete into a rejected promise for the caller.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import type { StateCreator } from 'zustand'
|
||||
import { notifyTaskComplete } from '../notifications'
|
||||
import { invalidateDuplicateScanCaches } from './helpers'
|
||||
import type { GalleryStore } from './index'
|
||||
import type { DuplicateGroup, DuplicateScanProgress, DuplicateScanResult } from './types'
|
||||
|
||||
@@ -146,10 +147,7 @@ export const createDuplicateSlice: StateCreator<GalleryStore, [], [], DuplicateS
|
||||
.filter((img) => succeededSet.has(img.id))
|
||||
.map((img) => img.folder_id)
|
||||
)
|
||||
await invoke('invalidate_duplicate_scan_cache', { folderId: null }) // global
|
||||
for (const folderId of affectedFolderIds) {
|
||||
await invoke('invalidate_duplicate_scan_cache', { folderId })
|
||||
}
|
||||
await invalidateDuplicateScanCaches(affectedFolderIds)
|
||||
return succeededIds.length
|
||||
},
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { StateCreator } from 'zustand'
|
||||
import {
|
||||
PAGE_SIZE,
|
||||
TIMELINE_PAGE_SIZE,
|
||||
invalidateDuplicateScanCaches,
|
||||
isCurrentGalleryRequest,
|
||||
isDerivedCollectionTitle,
|
||||
mergeImages,
|
||||
@@ -608,10 +609,7 @@ export const createGallerySlice: StateCreator<GalleryStore, [], [], GallerySlice
|
||||
}))
|
||||
// The DB cascade already removed these from album_images; refresh counts/covers.
|
||||
void get().loadAlbums()
|
||||
await invoke('invalidate_duplicate_scan_cache', { folderId: null })
|
||||
for (const folderId of affectedFolderIds) {
|
||||
await invoke('invalidate_duplicate_scan_cache', { folderId })
|
||||
}
|
||||
await invalidateDuplicateScanCaches(affectedFolderIds)
|
||||
return succeededIds.length
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import type {
|
||||
ImageRecord,
|
||||
MediaFilter,
|
||||
@@ -268,6 +269,26 @@ export function scopeHasTaggingPending(
|
||||
return (progressByFolder[folderId]?.tagging_pending ?? 0) > 0
|
||||
}
|
||||
|
||||
// Invalidates the persisted duplicate-scan cache for every scope affected by a
|
||||
// deletion: the global "all" cache (always, since a folder-scoped deletion
|
||||
// still makes the global result stale) and each folder that contained a
|
||||
// deleted image. This is a best-effort background refresh — a failure here
|
||||
// must not turn an already-successful delete into a rejected promise for the
|
||||
// caller, so failures are logged rather than thrown.
|
||||
export async function invalidateDuplicateScanCaches(affectedFolderIds: Set<number>): Promise<void> {
|
||||
const results = await Promise.allSettled([
|
||||
invoke('invalidate_duplicate_scan_cache', { folderId: null }),
|
||||
...[...affectedFolderIds].map((folderId) =>
|
||||
invoke('invalidate_duplicate_scan_cache', { folderId })
|
||||
),
|
||||
])
|
||||
for (const result of results) {
|
||||
if (result.status === 'rejected') {
|
||||
console.error('Failed to invalidate duplicate-scan cache:', result.reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function taggingProgressAffectsScope(
|
||||
progressFolderId: number,
|
||||
scopeFolderId: number | null
|
||||
|
||||
Reference in New Issue
Block a user