2026-04-02 11:40:04 +05:30
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useEffect, useRef } from "react";
|
2026-04-02 22:21:16 +05:30
|
|
|
import { documentsApiService } from "@/lib/apis/documents-api.service";
|
|
|
|
|
|
|
|
|
|
interface FileChangedEvent {
|
2026-04-03 00:40:49 +05:30
|
|
|
id: string;
|
2026-04-02 22:21:16 +05:30
|
|
|
rootFolderId: number | null;
|
|
|
|
|
searchSpaceId: number;
|
|
|
|
|
folderPath: string;
|
|
|
|
|
folderName: string;
|
|
|
|
|
relativePath: string;
|
|
|
|
|
fullPath: string;
|
|
|
|
|
action: string;
|
|
|
|
|
timestamp: number;
|
|
|
|
|
}
|
2026-04-02 11:40:04 +05:30
|
|
|
|
|
|
|
|
const DEBOUNCE_MS = 2000;
|
2026-04-03 00:40:49 +05:30
|
|
|
interface QueueItem {
|
|
|
|
|
event: FileChangedEvent;
|
|
|
|
|
ackIds: string[];
|
|
|
|
|
}
|
2026-04-02 11:40:04 +05:30
|
|
|
|
|
|
|
|
export function useFolderSync() {
|
2026-04-03 00:40:49 +05:30
|
|
|
const queueRef = useRef<QueueItem[]>([]);
|
2026-04-02 22:21:16 +05:30
|
|
|
const processingRef = useRef(false);
|
|
|
|
|
const debounceTimers = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
|
2026-04-03 00:40:49 +05:30
|
|
|
const pendingByKey = useRef<Map<string, QueueItem>>(new Map());
|
|
|
|
|
const isMountedRef = useRef(false);
|
2026-04-02 22:21:16 +05:30
|
|
|
|
|
|
|
|
async function processQueue() {
|
|
|
|
|
if (processingRef.current) return;
|
|
|
|
|
processingRef.current = true;
|
|
|
|
|
while (queueRef.current.length > 0) {
|
2026-04-03 00:40:49 +05:30
|
|
|
const item = queueRef.current.shift()!;
|
2026-04-02 22:21:16 +05:30
|
|
|
try {
|
2026-04-03 00:40:49 +05:30
|
|
|
await documentsApiService.folderIndexFile(item.event.searchSpaceId, {
|
|
|
|
|
folder_path: item.event.folderPath,
|
|
|
|
|
folder_name: item.event.folderName,
|
|
|
|
|
search_space_id: item.event.searchSpaceId,
|
|
|
|
|
target_file_path: item.event.fullPath,
|
|
|
|
|
root_folder_id: item.event.rootFolderId,
|
2026-04-02 22:21:16 +05:30
|
|
|
});
|
2026-04-03 00:40:49 +05:30
|
|
|
const api = typeof window !== "undefined" ? window.electronAPI : null;
|
|
|
|
|
if (api?.acknowledgeFileEvents && item.ackIds.length > 0) {
|
|
|
|
|
await api.acknowledgeFileEvents(item.ackIds);
|
|
|
|
|
}
|
2026-04-02 22:21:16 +05:30
|
|
|
} catch (err) {
|
|
|
|
|
console.error("[FolderSync] Failed to trigger re-index:", err);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
processingRef.current = false;
|
|
|
|
|
}
|
2026-04-02 11:40:04 +05:30
|
|
|
|
2026-04-03 00:40:49 +05:30
|
|
|
function enqueueWithDebounce(event: FileChangedEvent) {
|
|
|
|
|
const key = `${event.folderPath}:${event.relativePath}`;
|
|
|
|
|
const existing = pendingByKey.current.get(key);
|
|
|
|
|
const ackSet = new Set(existing?.ackIds ?? []);
|
|
|
|
|
ackSet.add(event.id);
|
|
|
|
|
pendingByKey.current.set(key, {
|
|
|
|
|
event,
|
|
|
|
|
ackIds: Array.from(ackSet),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const existingTimeout = debounceTimers.current.get(key);
|
|
|
|
|
if (existingTimeout) clearTimeout(existingTimeout);
|
|
|
|
|
|
|
|
|
|
const timeout = setTimeout(() => {
|
|
|
|
|
debounceTimers.current.delete(key);
|
|
|
|
|
const pending = pendingByKey.current.get(key);
|
|
|
|
|
if (!pending) return;
|
|
|
|
|
pendingByKey.current.delete(key);
|
|
|
|
|
queueRef.current.push(pending);
|
|
|
|
|
processQueue();
|
|
|
|
|
}, DEBOUNCE_MS);
|
|
|
|
|
|
|
|
|
|
debounceTimers.current.set(key, timeout);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 11:40:04 +05:30
|
|
|
useEffect(() => {
|
2026-04-03 00:40:49 +05:30
|
|
|
isMountedRef.current = true;
|
2026-04-02 11:40:04 +05:30
|
|
|
const api = typeof window !== "undefined" ? window.electronAPI : null;
|
2026-04-03 00:40:49 +05:30
|
|
|
if (!api?.onFileChanged) {
|
|
|
|
|
return () => {
|
|
|
|
|
isMountedRef.current = false;
|
|
|
|
|
};
|
|
|
|
|
}
|
2026-04-02 11:40:04 +05:30
|
|
|
|
2026-04-02 22:21:16 +05:30
|
|
|
// Signal to main process that the renderer is ready to receive events
|
|
|
|
|
api.signalRendererReady?.();
|
|
|
|
|
|
2026-04-03 00:40:49 +05:30
|
|
|
// Drain durable outbox first so events survive renderer startup gaps and restarts
|
|
|
|
|
void api.getPendingFileEvents?.().then((pendingEvents) => {
|
|
|
|
|
if (!isMountedRef.current || !pendingEvents?.length) return;
|
|
|
|
|
for (const event of pendingEvents) {
|
|
|
|
|
enqueueWithDebounce(event);
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-04-02 11:40:04 +05:30
|
|
|
|
2026-04-03 00:40:49 +05:30
|
|
|
const cleanup = api.onFileChanged((event: FileChangedEvent) => {
|
|
|
|
|
enqueueWithDebounce(event);
|
2026-04-02 11:40:04 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return () => {
|
2026-04-03 00:40:49 +05:30
|
|
|
isMountedRef.current = false;
|
2026-04-02 11:40:04 +05:30
|
|
|
cleanup();
|
2026-04-02 22:21:16 +05:30
|
|
|
for (const timeout of debounceTimers.current.values()) {
|
2026-04-02 11:40:04 +05:30
|
|
|
clearTimeout(timeout);
|
|
|
|
|
}
|
2026-04-02 22:21:16 +05:30
|
|
|
debounceTimers.current.clear();
|
2026-04-03 00:40:49 +05:30
|
|
|
pendingByKey.current.clear();
|
2026-04-02 11:40:04 +05:30
|
|
|
};
|
|
|
|
|
}, []);
|
|
|
|
|
}
|