diff --git a/desktop/src/main/ipc.ts b/desktop/src/main/ipc.ts index 10b5b9bd9..429582d99 100644 --- a/desktop/src/main/ipc.ts +++ b/desktop/src/main/ipc.ts @@ -224,7 +224,7 @@ export const attachIPCHandlers = () => { export const attachFSWatchIPCHandlers = (watcher: FSWatcher) => { // - Watch - ipcMain.handle("watchGet", () => watchGet()); + ipcMain.handle("watchGet", () => watchGet(watcher)); ipcMain.handle( "watchAdd", diff --git a/desktop/src/main/services/watch.ts b/desktop/src/main/services/watch.ts index d0c554ec7..3a71c28af 100644 --- a/desktop/src/main/services/watch.ts +++ b/desktop/src/main/services/watch.ts @@ -3,6 +3,7 @@ import { BrowserWindow } from "electron/main"; import fs from "node:fs/promises"; import path from "node:path"; import { FolderWatch, type CollectionMapping } from "../../types/ipc"; +import { fsIsDir } from "../fs"; import log from "../log"; import { watchStore } from "../stores/watch"; @@ -52,77 +53,65 @@ const eventData = (path: string): [string, FolderWatch] => { const posixPath = (filePath: string) => filePath.split(path.sep).join(path.posix.sep); -export const watchGet = () => { - return folderWatches(); +export const watchGet = (watcher: FSWatcher) => { + const [valid, deleted] = folderWatches().reduce( + ([valid, deleted], watch) => { + (fsIsDir(watch.folderPath) ? valid : deleted).push(watch); + return [valid, deleted]; + }, + [[], []], + ); + if (deleted.length) { + for (const watch of deleted) watchRemove(watcher, watch.folderPath); + setFolderWatches(valid); + } + return valid; }; -const folderWatches = () => { - let watches = watchStore.get("mappings") ?? []; - - // Previous versions of the store used to store an integer to indicate the - // collection mapping, migrate these to the new schema if we see them still. - let needsUpdate = false; - watches = watches.map((watch) => { - const cm = watch.collectionMapping; - if (cm != "root" && cm != "parent") { - const uploadStrategy = watch.uploadStrategy; - const collectionMapping = uploadStrategy == 1 ? "parent" : "root"; - needsUpdate = true; - return { ...watch, collectionMapping } - } - }) - if (watches.length && watches) - return mappings; -}; +const folderWatches = (): FolderWatch[] => watchStore.get("mappings") ?? []; +const setFolderWatches = (watches: FolderWatch[]) => + watchStore.set("mappings", watches); export const watchAdd = async ( watcher: FSWatcher, folderPath: string, collectionMapping: CollectionMapping, ) => { - const watchMappings = getWatchMappings(); - if (isMappingPresent(watchMappings, folderPath)) { - throw new Error(`Watch mapping already exists`); - } + const watches = folderWatches(); - watcher.add(folderPath); + if (!fsIsDir(folderPath)) + throw new Error( + `Attempting to add a folder watch for a folder path ${folderPath} that is not an existing directory`, + ); - watchMappings.push({ - rootFolderName, - uploadStrategy, + if (watches.find((watch) => watch.folderPath == folderPath)) + throw new Error( + `A folder watch with the given folder path ${folderPath} already exists`, + ); + + watches.push({ folderPath, + collectionMapping, syncedFiles: [], ignoredFiles: [], }); - setWatchMappings(watchMappings); + setFolderWatches(watches); + + watcher.add(folderPath); }; -function isMappingPresent(watchMappings: FolderWatch[], folderPath: string) { - const watchMapping = watchMappings?.find( - (mapping) => mapping.folderPath === folderPath, - ); - return !!watchMapping; -} - export const watchRemove = async (watcher: FSWatcher, folderPath: string) => { - let watchMappings = getWatchMappings(); - const watchMapping = watchMappings.find( - (mapping) => mapping.folderPath === folderPath, - ); - - if (!watchMapping) { - throw new Error(`Watch mapping does not exist`); - } - - watcher.unwatch(watchMapping.folderPath); - - watchMappings = watchMappings.filter( - (mapping) => mapping.folderPath !== watchMapping.folderPath, - ); - - setWatchMappings(watchMappings); + const watches = folderWatches(); + const filtered = watches.filter((watch) => watch.folderPath != folderPath); + if (watches.length == filtered.length) + throw new Error( + `Attempting to remove a non-existing folder watch for folder path ${folderPath}`, + ); + setFolderWatches(filtered); + watcher.unwatch(folderPath); + return filtered; }; export function updateWatchMappingSyncedFiles( @@ -159,11 +148,6 @@ export function updateWatchMappingIgnoredFiles( setWatchMappings(watchMappings); } - -function setWatchMappings(watchMappings: WatchStoreType["mappings"]) { - watchStore.set("mappings", watchMappings); -} - export const watchFindFiles = async (dirPath: string) => { const items = await fs.readdir(dirPath, { withFileTypes: true }); let paths: string[] = []; diff --git a/desktop/src/main/stores/watch.ts b/desktop/src/main/stores/watch.ts index df4ad4b7b..e0e061422 100644 --- a/desktop/src/main/stores/watch.ts +++ b/desktop/src/main/stores/watch.ts @@ -8,7 +8,9 @@ interface WatchStore { type FolderWatchWithLegacyFields = FolderWatch & { /** @deprecated Only retained for migration, do not use in other code */ - uploadStrategy: number; + rootFolderName?: string; + /** @deprecated Only retained for migration, do not use in other code */ + uploadStrategy?: number; }; const watchStoreSchema: Schema = { @@ -58,6 +60,10 @@ export const migrateLegacyWatchStoreIfNeeded = () => { collectionMapping = watch.uploadStrategy == 1 ? "parent" : "root"; needsUpdate = true; } + if (watch.rootFolderName) { + delete watch.rootFolderName; + needsUpdate = true; + } return { ...watch, collectionMapping }; }); if (needsUpdate) { diff --git a/desktop/src/types/ipc.ts b/desktop/src/types/ipc.ts index 25e98ceff..d96341982 100644 --- a/desktop/src/types/ipc.ts +++ b/desktop/src/types/ipc.ts @@ -11,8 +11,6 @@ export interface AppUpdate { } export interface FolderWatch { - // TODO(MR): Is this needed? - rootFolderName: string; collectionMapping: CollectionMapping; folderPath: string; syncedFiles: FolderWatchSyncedFile[]; diff --git a/web/packages/next/types/ipc.ts b/web/packages/next/types/ipc.ts index 3a7aff3e8..098263e2d 100644 --- a/web/packages/next/types/ipc.ts +++ b/web/packages/next/types/ipc.ts @@ -290,12 +290,12 @@ export interface Electron { */ watch: { /** - * Return the list of folder watches. + * Return the list of folder watches, pruning non-existing directories. * * The list of folder paths (and auxillary details) is persisted in the - * Node.js layer. When we invoke this method, the Node.js goes through - * the list, permanently removes any watches whose on-disk directory has - * is no longer present, and returns this pruned list of watches. + * Node.js layer. The implementation of this function goes through the + * list, permanently removes any watches whose on-disk directory is no + * longer present, and returns this pruned list of watches. */ get: () => Promise;