diff --git a/surfsense_desktop/src/ipc/channels.ts b/surfsense_desktop/src/ipc/channels.ts index 1921dcda2..61213eb46 100644 --- a/surfsense_desktop/src/ipc/channels.ts +++ b/surfsense_desktop/src/ipc/channels.ts @@ -31,6 +31,7 @@ export const IPC_CHANNELS = { FOLDER_SYNC_GET_PENDING_EVENTS: 'folder-sync:get-pending-events', FOLDER_SYNC_ACK_EVENTS: 'folder-sync:ack-events', FOLDER_SYNC_LIST_FILES: 'folder-sync:list-files', + FOLDER_SYNC_SEED_MTIMES: 'folder-sync:seed-mtimes', BROWSE_FILES: 'browse:files', READ_LOCAL_FILES: 'browse:read-local-files', // Auth token sync across windows diff --git a/surfsense_desktop/src/ipc/handlers.ts b/surfsense_desktop/src/ipc/handlers.ts index a1d5552c9..afb2ba038 100644 --- a/surfsense_desktop/src/ipc/handlers.ts +++ b/surfsense_desktop/src/ipc/handlers.ts @@ -20,6 +20,7 @@ import { browseFiles, readLocalFiles, listFolderFiles, + seedFolderMtimes, type WatchedFolderConfig, } from '../modules/folder-watcher'; import { getShortcuts, setShortcuts, type ShortcutConfig } from '../modules/shortcuts'; @@ -97,6 +98,12 @@ export function registerIpcHandlers(): void { listFolderFiles(config) ); + ipcMain.handle( + IPC_CHANNELS.FOLDER_SYNC_SEED_MTIMES, + (_event, folderPath: string, mtimes: Record) => + seedFolderMtimes(folderPath, mtimes), + ); + ipcMain.handle(IPC_CHANNELS.BROWSE_FILES, () => browseFiles()); ipcMain.handle(IPC_CHANNELS.READ_LOCAL_FILES, (_event, paths: string[]) => diff --git a/surfsense_desktop/src/modules/folder-watcher.ts b/surfsense_desktop/src/modules/folder-watcher.ts index a39d7855a..96b490d7b 100644 --- a/surfsense_desktop/src/modules/folder-watcher.ts +++ b/surfsense_desktop/src/modules/folder-watcher.ts @@ -449,14 +449,30 @@ export async function acknowledgeFileEvents(eventIds: string[]): Promise<{ ackno const ackSet = new Set(eventIds); let acknowledged = 0; + const foldersToUpdate = new Set(); for (const [key, event] of outboxEvents.entries()) { if (ackSet.has(event.id)) { + if (event.action !== 'unlink') { + const map = mtimeMaps.get(event.folderPath); + if (map) { + try { + map[event.relativePath] = fs.statSync(event.fullPath).mtimeMs; + foldersToUpdate.add(event.folderPath); + } catch { + // File may have been removed + } + } + } outboxEvents.delete(key); acknowledged += 1; } } + for (const fp of foldersToUpdate) { + persistMtimeMap(fp); + } + if (acknowledged > 0) { persistOutbox(); } @@ -464,6 +480,17 @@ export async function acknowledgeFileEvents(eventIds: string[]): Promise<{ ackno return { acknowledged }; } +export async function seedFolderMtimes( + folderPath: string, + mtimes: Record, +): Promise { + const ms = await getMtimeStore(); + const existing: MtimeMap = ms.get(folderPath) ?? {}; + const merged = { ...existing, ...mtimes }; + mtimeMaps.set(folderPath, merged); + ms.set(folderPath, merged); +} + export async function pauseWatcher(): Promise { for (const [, entry] of watchers) { if (entry.watcher) { diff --git a/surfsense_desktop/src/preload.ts b/surfsense_desktop/src/preload.ts index 7cc63aea1..e3d12c5e6 100644 --- a/surfsense_desktop/src/preload.ts +++ b/surfsense_desktop/src/preload.ts @@ -65,6 +65,8 @@ contextBridge.exposeInMainWorld('electronAPI', { getPendingFileEvents: () => ipcRenderer.invoke(IPC_CHANNELS.FOLDER_SYNC_GET_PENDING_EVENTS), acknowledgeFileEvents: (eventIds: string[]) => ipcRenderer.invoke(IPC_CHANNELS.FOLDER_SYNC_ACK_EVENTS, eventIds), listFolderFiles: (config: any) => ipcRenderer.invoke(IPC_CHANNELS.FOLDER_SYNC_LIST_FILES, config), + seedFolderMtimes: (folderPath: string, mtimes: Record) => + ipcRenderer.invoke(IPC_CHANNELS.FOLDER_SYNC_SEED_MTIMES, folderPath, mtimes), // Browse files via native dialog browseFiles: () => ipcRenderer.invoke(IPC_CHANNELS.BROWSE_FILES), diff --git a/surfsense_web/lib/folder-sync-upload.ts b/surfsense_web/lib/folder-sync-upload.ts index 28f38ced4..ef01d52bd 100644 --- a/surfsense_web/lib/folder-sync-upload.ts +++ b/surfsense_web/lib/folder-sync-upload.ts @@ -212,5 +212,15 @@ export async function uploadFolderScan(params: FolderSyncParams): Promise = {}; + for (const f of allFiles) { + mtimes[f.relativePath] = f.mtimeMs; + } + await api.seedFolderMtimes(folderPath, mtimes); + } + return rootFolderId; } diff --git a/surfsense_web/types/window.d.ts b/surfsense_web/types/window.d.ts index 4373cdaac..004aefcd5 100644 --- a/surfsense_web/types/window.d.ts +++ b/surfsense_web/types/window.d.ts @@ -90,6 +90,7 @@ interface ElectronAPI { getPendingFileEvents: () => Promise; acknowledgeFileEvents: (eventIds: string[]) => Promise<{ acknowledged: number }>; listFolderFiles: (config: WatchedFolderConfig) => Promise; + seedFolderMtimes: (folderPath: string, mtimes: Record) => Promise; // Browse files/folders via native dialogs browseFiles: () => Promise; readLocalFiles: (paths: string[]) => Promise;