fix: pre-empt review findings before next push
- hnsw_index: fetch folder embedding IDs before acquiring the read lock in find_similar_image_matches; holding the lock across a SQLite query was delaying concurrent ensure_index write-lock requests - store/DuplicateFinder: add duplicateScanError state; scanDuplicates now catches errors and stores them rather than silently leaving the user with empty results and no feedback
This commit is contained in:
@@ -103,16 +103,29 @@ pub fn find_similar_image_matches(
|
||||
None => return Ok(Vec::new()),
|
||||
};
|
||||
|
||||
// Fetch folder image IDs *before* acquiring the read lock so we don't hold
|
||||
// the lock across a potentially slow SQLite query, which would delay any
|
||||
// concurrent ensure_index call waiting for a write lock.
|
||||
let folder_image_ids: Option<Vec<i64>> = if let Some(folder_id) = folder_id {
|
||||
let ids = vector::get_all_image_embeddings_with_ids(conn, Some(folder_id))?
|
||||
.into_iter()
|
||||
.map(|(id, _)| id)
|
||||
.collect();
|
||||
Some(ids)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let guard = cache().read().expect("hnsw cache poisoned");
|
||||
let Some(cached) = guard.as_ref() else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
|
||||
let knbn = (offset + limit).max(limit).saturating_add(32);
|
||||
let neighbours: Vec<Neighbour> = if let Some(folder_id) = folder_id {
|
||||
let mut allowed_ids = vector::get_all_image_embeddings_with_ids(conn, Some(folder_id))?
|
||||
let neighbours: Vec<Neighbour> = if let Some(image_ids) = folder_image_ids {
|
||||
let mut allowed_ids = image_ids
|
||||
.into_iter()
|
||||
.filter_map(|(allowed_image_id, _)| {
|
||||
.filter_map(|allowed_image_id| {
|
||||
cached.external_by_image_id.get(&allowed_image_id).copied()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -116,6 +116,7 @@ export function DuplicateFinder() {
|
||||
const duplicateGroups = useGalleryStore((state) => state.duplicateGroups);
|
||||
const duplicateScanning = useGalleryStore((state) => state.duplicateScanning);
|
||||
const duplicateScanProgress = useGalleryStore((state) => state.duplicateScanProgress);
|
||||
const duplicateScanError = useGalleryStore((state) => state.duplicateScanError);
|
||||
const duplicateSelectedIds = useGalleryStore((state) => state.duplicateSelectedIds);
|
||||
const duplicateLastScanned = useGalleryStore((state) => state.duplicateLastScanned);
|
||||
const selectedFolderId = useGalleryStore((state) => state.selectedFolderId);
|
||||
@@ -228,6 +229,9 @@ export function DuplicateFinder() {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{duplicateScanError ? (
|
||||
<p className="mt-2 text-[11px] text-red-400/80">{duplicateScanError}</p>
|
||||
) : null}
|
||||
{deleteResult ? (
|
||||
<p className="mt-2 text-[11px] text-white/40">{deleteResult}</p>
|
||||
) : null}
|
||||
|
||||
+5
-1
@@ -278,6 +278,7 @@ interface GalleryState {
|
||||
duplicateGroups: DuplicateGroup[];
|
||||
duplicateScanning: boolean;
|
||||
duplicateScanProgress: { scanned: number; total: number } | null;
|
||||
duplicateScanError: string | null;
|
||||
duplicateSelectedIds: Set<number>;
|
||||
duplicateLastScanned: number | null; // Unix timestamp (seconds)
|
||||
duplicateScanFolderId: number | null | undefined; // undefined = never scanned
|
||||
@@ -613,6 +614,7 @@ export const useGalleryStore = create<GalleryState>((set, get) => ({
|
||||
duplicateGroups: [],
|
||||
duplicateScanning: false,
|
||||
duplicateScanProgress: null,
|
||||
duplicateScanError: null,
|
||||
duplicateSelectedIds: new Set(),
|
||||
duplicateLastScanned: null,
|
||||
duplicateScanFolderId: undefined,
|
||||
@@ -1466,7 +1468,7 @@ export const useGalleryStore = create<GalleryState>((set, get) => ({
|
||||
|
||||
scanDuplicates: async (folderId = null) => {
|
||||
const { listen } = await import("@tauri-apps/api/event");
|
||||
set({ duplicateScanning: true, duplicateGroups: [], duplicateScanProgress: null, duplicateSelectedIds: new Set() });
|
||||
set({ duplicateScanning: true, duplicateGroups: [], duplicateScanProgress: null, duplicateScanError: null, duplicateSelectedIds: new Set() });
|
||||
const unlisten = await listen<[number, number]>("duplicate_scan_progress", (event) => {
|
||||
const [scanned, total] = event.payload;
|
||||
set({ duplicateScanProgress: { scanned, total } });
|
||||
@@ -1478,6 +1480,8 @@ export const useGalleryStore = create<GalleryState>((set, get) => ({
|
||||
"Duplicate scan complete",
|
||||
groups.length === 1 ? "Found 1 duplicate group." : `Found ${groups.length.toLocaleString()} duplicate groups.`,
|
||||
);
|
||||
} catch (e) {
|
||||
set({ duplicateScanError: String(e) });
|
||||
} finally {
|
||||
unlisten();
|
||||
set({ duplicateScanning: false });
|
||||
|
||||
Reference in New Issue
Block a user