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 { invoke } from '@tauri-apps/api/core'
|
||||||
import type { StateCreator } from 'zustand'
|
import type { StateCreator } from 'zustand'
|
||||||
import { notifyTaskComplete } from '../notifications'
|
import { notifyTaskComplete } from '../notifications'
|
||||||
|
import { invalidateDuplicateScanCaches } from './helpers'
|
||||||
import type { GalleryStore } from './index'
|
import type { GalleryStore } from './index'
|
||||||
import type { DuplicateGroup, DuplicateScanProgress, DuplicateScanResult } from './types'
|
import type { DuplicateGroup, DuplicateScanProgress, DuplicateScanResult } from './types'
|
||||||
|
|
||||||
@@ -146,10 +147,7 @@ export const createDuplicateSlice: StateCreator<GalleryStore, [], [], DuplicateS
|
|||||||
.filter((img) => succeededSet.has(img.id))
|
.filter((img) => succeededSet.has(img.id))
|
||||||
.map((img) => img.folder_id)
|
.map((img) => img.folder_id)
|
||||||
)
|
)
|
||||||
await invoke('invalidate_duplicate_scan_cache', { folderId: null }) // global
|
await invalidateDuplicateScanCaches(affectedFolderIds)
|
||||||
for (const folderId of affectedFolderIds) {
|
|
||||||
await invoke('invalidate_duplicate_scan_cache', { folderId })
|
|
||||||
}
|
|
||||||
return succeededIds.length
|
return succeededIds.length
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { StateCreator } from 'zustand'
|
|||||||
import {
|
import {
|
||||||
PAGE_SIZE,
|
PAGE_SIZE,
|
||||||
TIMELINE_PAGE_SIZE,
|
TIMELINE_PAGE_SIZE,
|
||||||
|
invalidateDuplicateScanCaches,
|
||||||
isCurrentGalleryRequest,
|
isCurrentGalleryRequest,
|
||||||
isDerivedCollectionTitle,
|
isDerivedCollectionTitle,
|
||||||
mergeImages,
|
mergeImages,
|
||||||
@@ -608,10 +609,7 @@ export const createGallerySlice: StateCreator<GalleryStore, [], [], GallerySlice
|
|||||||
}))
|
}))
|
||||||
// The DB cascade already removed these from album_images; refresh counts/covers.
|
// The DB cascade already removed these from album_images; refresh counts/covers.
|
||||||
void get().loadAlbums()
|
void get().loadAlbums()
|
||||||
await invoke('invalidate_duplicate_scan_cache', { folderId: null })
|
await invalidateDuplicateScanCaches(affectedFolderIds)
|
||||||
for (const folderId of affectedFolderIds) {
|
|
||||||
await invoke('invalidate_duplicate_scan_cache', { folderId })
|
|
||||||
}
|
|
||||||
return succeededIds.length
|
return succeededIds.length
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
import type {
|
import type {
|
||||||
ImageRecord,
|
ImageRecord,
|
||||||
MediaFilter,
|
MediaFilter,
|
||||||
@@ -268,6 +269,26 @@ export function scopeHasTaggingPending(
|
|||||||
return (progressByFolder[folderId]?.tagging_pending ?? 0) > 0
|
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(
|
export function taggingProgressAffectsScope(
|
||||||
progressFolderId: number,
|
progressFolderId: number,
|
||||||
scopeFolderId: number | null
|
scopeFolderId: number | null
|
||||||
|
|||||||
Reference in New Issue
Block a user