From ceb217cda87169e5bd3907187e274f7ac5333e9f Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Thu, 22 May 2025 21:41:59 +0100 Subject: [PATCH] Add simple glob ignore patterns --- .../obsidian-plugin/src/vault-link-plugin.ts | 4 +- .../src/views/settings/settings-tab.ts | 23 +++++++++++ frontend/package-lock.json | 25 ++++++++++++ frontend/sync-client/package.json | 1 + frontend/sync-client/src/index.ts | 2 +- .../sync-client/src/persistence/settings.ts | 6 ++- .../sync-operations/unrestricted-syncer.ts | 39 +++++++++++++++++-- .../sync-client/src/tracing/sync-history.ts | 3 +- 8 files changed, 95 insertions(+), 8 deletions(-) diff --git a/frontend/obsidian-plugin/src/vault-link-plugin.ts b/frontend/obsidian-plugin/src/vault-link-plugin.ts index f675d4dd..68be059f 100644 --- a/frontend/obsidian-plugin/src/vault-link-plugin.ts +++ b/frontend/obsidian-plugin/src/vault-link-plugin.ts @@ -11,7 +11,7 @@ import { HistoryView } from "./views/history/history-view"; import { StatusBar } from "./views/status-bar/status-bar"; import { LogsView } from "./views/logs/logs-view"; import { StatusDescription } from "./views/status-description/status-description"; -import { SyncClient, rateLimit } from "sync-client"; +import { SyncClient, rateLimit, DEFAULT_SETTINGS } from "sync-client"; import { ObsidianFileSystemOperations } from "./obsidian-file-system"; import { SyncSettingsTab } from "./views/settings/settings-tab"; import { registerConsoleForLogging } from "./utils/register-console-for-logging"; @@ -27,6 +27,8 @@ export default class VaultLinkPlugin extends Plugin { >(); public async onload(): Promise { + DEFAULT_SETTINGS.ignorePatterns.push(".obsidian", ".git"); + this.client = await SyncClient.create({ fs: new ObsidianFileSystemOperations( this.app.vault, diff --git a/frontend/obsidian-plugin/src/views/settings/settings-tab.ts b/frontend/obsidian-plugin/src/views/settings/settings-tab.ts index c5f0c214..31e9182b 100644 --- a/frontend/obsidian-plugin/src/views/settings/settings-tab.ts +++ b/frontend/obsidian-plugin/src/views/settings/settings-tab.ts @@ -253,6 +253,29 @@ export class SyncSettingsTab extends PluginSettingTab { ) ); + new Setting(containerEl) + .setName("Ignore patterns") + .setDesc( + "Patterns to ignore when syncing. Each line is a separate glob pattern. Patterns are matched against the relative path of the file. For example, to ignore all files in a folder named 'ignore', enter 'ignore/*'. To ignore all files with the extension '.log', enter '*.log'." + ) + .addTextArea((text) => + text + .setValue( + this.syncClient.getSettings().ignorePatterns.join("\n") + ) + .setPlaceholder("Enter patterns to ignore, one per line") + .onChange(async (value) => { + const patterns = value + .split("\n") + .map((pattern) => pattern.trim()) + .filter((pattern) => pattern.length > 0); + return this.syncClient.setSetting( + "ignorePatterns", + patterns + ); + }) + ); + new Setting(containerEl) .setName("Sync concurrency") .setDesc( diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1f030d69..045c5e9f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -7884,6 +7884,7 @@ "version": "0.3.13", "dependencies": { "byte-base64": "^1.1.0", + "minimatch": "^10.0.1", "openapi-fetch": "0.13.5", "openapi-typescript": "7.6.1", "p-queue": "^8.1.0", @@ -7904,6 +7905,30 @@ "ws": "^8.18.1" } }, + "sync-client/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "sync-client/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "test-client": { "version": "0.3.13", "bin": { diff --git a/frontend/sync-client/package.json b/frontend/sync-client/package.json index 8236c3ae..c38fcd01 100644 --- a/frontend/sync-client/package.json +++ b/frontend/sync-client/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "byte-base64": "^1.1.0", + "minimatch": "^10.0.1", "openapi-fetch": "0.13.5", "openapi-typescript": "7.6.1", "p-queue": "^8.1.0", diff --git a/frontend/sync-client/src/index.ts b/frontend/sync-client/src/index.ts index 38d11ec7..324312b5 100644 --- a/frontend/sync-client/src/index.ts +++ b/frontend/sync-client/src/index.ts @@ -5,7 +5,7 @@ export { type HistoryEntry } from "./tracing/sync-history"; export { Logger, LogLevel, LogLine } from "./tracing/logger"; -export { type SyncSettings } from "./persistence/settings"; +export { type SyncSettings, DEFAULT_SETTINGS } from "./persistence/settings"; export { rateLimit } from "./utils/rate-limit"; export type { RelativePath, StoredDatabase } from "./persistence/database"; export type { diff --git a/frontend/sync-client/src/persistence/settings.ts b/frontend/sync-client/src/persistence/settings.ts index aecfafc9..a62e4f0c 100644 --- a/frontend/sync-client/src/persistence/settings.ts +++ b/frontend/sync-client/src/persistence/settings.ts @@ -7,15 +7,17 @@ export interface SyncSettings { syncConcurrency: number; isSyncEnabled: boolean; maxFileSizeMB: number; + ignorePatterns: string[]; } -const DEFAULT_SETTINGS: SyncSettings = { +export const DEFAULT_SETTINGS: SyncSettings = { remoteUri: "", token: "", vaultName: "default", syncConcurrency: 1, isSyncEnabled: false, - maxFileSizeMB: 10 + maxFileSizeMB: 10, + ignorePatterns: [] }; export class Settings { diff --git a/frontend/sync-client/src/sync-operations/unrestricted-syncer.ts b/frontend/sync-client/src/sync-operations/unrestricted-syncer.ts index e62ea173..f6b2c8f8 100644 --- a/frontend/sync-client/src/sync-operations/unrestricted-syncer.ts +++ b/frontend/sync-client/src/sync-operations/unrestricted-syncer.ts @@ -16,8 +16,11 @@ import type { FileOperations } from "../file-operations/file-operations"; import { createPromise } from "../utils/create-promise"; import { FileNotFoundError } from "../file-operations/file-not-found-error"; import { SyncResetError } from "../services/sync-reset-error"; +import { makeRe } from "minimatch"; export class UnrestrictedSyncer { + private ignorePatterns: RegExp[]; + public constructor( private readonly logger: Logger, private readonly database: Database, @@ -25,7 +28,16 @@ export class UnrestrictedSyncer { private readonly syncService: SyncService, private readonly operations: FileOperations, private readonly history: SyncHistory - ) {} + ) { + this.ignorePatterns = this.globsToRegex( + this.settings.getSettings().ignorePatterns + ); + + this.settings.addOnSettingsChangeListener((newSettings) => { + this.ignorePatterns = this.globsToRegex(newSettings.ignorePatterns); + }); + } + public async unrestrictedSyncLocallyCreatedFile( document: DocumentRecord ): Promise { @@ -373,7 +385,14 @@ export class UnrestrictedSyncer { syncType: SyncType, fn: () => Promise ): Promise { - this.logger.debug(`Syncing ${relativePath} (${syncType})`); + for (const pattern of this.ignorePatterns) { + if (pattern.test(relativePath)) { + this.logger.debug( + `File '${relativePath}' is ignored by the ignore pattern: ${pattern}` + ); + return; // bail without SKIPPED status because we were told to ignore this file and we shouldn't clutter up the history + } + } try { if (await this.operations.exists(relativePath)) { @@ -385,7 +404,7 @@ export class UnrestrictedSyncer { if (sizeInMB > this.settings.getSettings().maxFileSizeMB) { this.history.addHistoryEntry({ - status: SyncStatus.ERROR, + status: SyncStatus.SKIPPED, relativePath, message: `File size of ${sizeInMB} MB exceeds the maximum file size limit of ${ this.settings.getSettings().maxFileSizeMB @@ -422,4 +441,18 @@ export class UnrestrictedSyncer { } } } + + private globsToRegex(globs: string[]): RegExp[] { + return globs + .map((pattern) => { + const result = makeRe(pattern); + if (result === false) { + this.logger.warn( + `Failed to parse ${pattern}' as a glob pattern, skipping it` + ); + } + return result; + }) + .filter((pattern) => pattern !== false); + } } diff --git a/frontend/sync-client/src/tracing/sync-history.ts b/frontend/sync-client/src/tracing/sync-history.ts index fe782e9b..38f8320c 100644 --- a/frontend/sync-client/src/tracing/sync-history.ts +++ b/frontend/sync-client/src/tracing/sync-history.ts @@ -16,7 +16,8 @@ export enum SyncType { export enum SyncStatus { SUCCESS = "SUCCESS", - ERROR = "ERROR" + ERROR = "ERROR", + SKIPPED = "SKIPPED" } export type HistoryEntry = CommonHistoryEntry & { timestamp: Date };