diff --git a/frontend/sync-client/src/persistence/settings.test.ts b/frontend/sync-client/src/persistence/settings.test.ts index b17df261..cff24895 100644 --- a/frontend/sync-client/src/persistence/settings.test.ts +++ b/frontend/sync-client/src/persistence/settings.test.ts @@ -57,3 +57,33 @@ it("owns settings arrays instead of sharing them with callers and listeners", as await settings.setSettings({ maxFileSizeMB: 12 }); assert.deepEqual(settings.getSettings().ignorePatterns, ["updated"]); }); + +it("file eligibility follows successfully saved settings", async () => { + let fail = false; + const settings = new Settings( + new Logger(), + { + ignorePatterns: ["private/**"], + maxFileSizeMB: 1 + }, + async () => { + if (fail) throw new Error("save failed"); + } + ); + assert(settings.isIgnored("private/note.md")); + assert(settings.isIgnored(".vault-link-sync/state.json")); + assert(!settings.isIgnored("public/note.md")); + assert(!settings.isOversized(1024 * 1024)); + assert(settings.isOversized(1024 * 1024 + 1)); + fail = true; + await assert.rejects( + settings.setSettings({ ignorePatterns: [], maxFileSizeMB: 2 }), + /save failed/ + ); + assert(settings.isIgnored("private/note.md")); + assert(settings.isOversized(1024 * 1024 + 1)); + fail = false; + await settings.setSettings({ ignorePatterns: [], maxFileSizeMB: 2 }); + assert(!settings.isIgnored("private/note.md")); + assert(!settings.isOversized(1024 * 1024 + 1)); +}); diff --git a/frontend/sync-client/src/persistence/settings.ts b/frontend/sync-client/src/persistence/settings.ts index 9cc3bbe3..1b592d65 100644 --- a/frontend/sync-client/src/persistence/settings.ts +++ b/frontend/sync-client/src/persistence/settings.ts @@ -1,4 +1,6 @@ import type { Logger } from "../tracing/logger"; +import { globsToRegexes } from "../utils/globs-to-regexes"; +import { isInternalPath } from "../utils/portable-path"; import { Lock } from "../utils/data-structures/locks"; import { EventListeners } from "../utils/data-structures/event-listeners"; @@ -43,6 +45,7 @@ export class Settings { >(); private settings: SyncSettings; + private ignorePatterns: RegExp[]; private readonly lock: Lock = new Lock(); public constructor( @@ -55,11 +58,27 @@ export class Settings { ...structuredClone(initialState ?? {}) }; + this.ignorePatterns = globsToRegexes( + this.settings.ignorePatterns, + logger + ); + this.logger.debug( `Loaded settings: ${JSON.stringify(redactSettings(this.settings), null, 2)}` ); } + public isIgnored(path: string): boolean { + return ( + isInternalPath(path) || + this.ignorePatterns.some((pattern) => pattern.test(path)) + ); + } + + public isOversized(size: number): boolean { + return size > this.settings.maxFileSizeMB * 1024 * 1024; + } + public getSettings(): SyncSettings { return { ...this.settings, @@ -92,6 +111,12 @@ export class Settings { await this.saveData(next); this.settings = next; + if (update.ignorePatterns !== undefined) + this.ignorePatterns = globsToRegexes( + next.ignorePatterns, + this.logger + ); + return [this.getSettings(), oldSettings] as const; });