From 49cced2de62176c2ba5cb8bb64e8984959073b3c Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Sat, 11 Jan 2025 10:53:50 +0000 Subject: [PATCH] Refactor, normalize reads, and add isFileEligibleForSync --- plugin/src/file-operations/file-operations.ts | 2 + .../obsidian-file-operations.ts | 78 ++++++++++++++----- 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/plugin/src/file-operations/file-operations.ts b/plugin/src/file-operations/file-operations.ts index aaeee87..6632799 100644 --- a/plugin/src/file-operations/file-operations.ts +++ b/plugin/src/file-operations/file-operations.ts @@ -25,4 +25,6 @@ export interface FileOperations { remove: (path: RelativePath) => Promise; move: (oldPath: RelativePath, newPath: RelativePath) => Promise; + + isFileEligibleForSync: (path: RelativePath) => boolean; } diff --git a/plugin/src/file-operations/obsidian-file-operations.ts b/plugin/src/file-operations/obsidian-file-operations.ts index 3a45d36..128d5ea 100644 --- a/plugin/src/file-operations/obsidian-file-operations.ts +++ b/plugin/src/file-operations/obsidian-file-operations.ts @@ -1,48 +1,53 @@ -import type { Vault } from "obsidian"; +import type { Stat, Vault } from "obsidian"; import { normalizePath } from "obsidian"; import type { FileOperations } from "./file-operations"; import type { RelativePath } from "src/database/document-metadata"; -import { isBinary, mergeText } from "sync_lib"; +import { isFileTypeMergable, mergeText } from "sync_lib"; +import { Platform } from "obsidian"; +import { Logger } from "src/tracing/logger"; export class ObsidianFileOperations implements FileOperations { public constructor(private readonly vault: Vault) {} public async listAllFiles(): Promise { const files = this.vault.getFiles(); + Logger.getInstance().debug(`Listing all files, found ${files.length}`); return files.map((file) => file.path); } public async read(path: RelativePath): Promise { - const result = new Uint8Array( + Logger.getInstance().debug(`Reading file: ${path}`); + if (isFileTypeMergable(path)) { + let text = await this.vault.adapter.read(normalizePath(path)); + + text = text.replace(/\r\n/g, "\n"); + + return new TextEncoder().encode(text); + } + return new Uint8Array( await this.vault.adapter.readBinary(normalizePath(path)) ); - - return result; } public async getFileSize(path: RelativePath): Promise { - const file = await this.vault.adapter.stat(normalizePath(path)); - if (!file) { - throw new Error(`File not found: ${path}`); - } - - return file.size; + Logger.getInstance().debug(`Getting file size: ${path}`); + return (await this.statFile(path)).size; } public async getModificationTime(path: RelativePath): Promise { - const file = await this.vault.adapter.stat(normalizePath(path)); - if (!file) { - throw new Error(`File not found: ${path}`); - } - - return new Date(file.mtime); + Logger.getInstance().debug(`Getting modification time: ${path}`); + return new Date((await this.statFile(path)).mtime); } public async create( path: RelativePath, newContent: Uint8Array ): Promise { + Logger.getInstance().debug(`Creating file: ${path}`); if (await this.vault.adapter.exists(normalizePath(path))) { + Logger.getInstance().debug( + `Didn't expect ${path} to exist, when trying to create it, merging instead` + ); await this.write(path, new Uint8Array(0), newContent); return; } @@ -56,12 +61,18 @@ export class ObsidianFileOperations implements FileOperations { expectedContent: Uint8Array, newContent: Uint8Array ): Promise { + Logger.getInstance().debug(`Writing file: ${path}`); if (!(await this.vault.adapter.exists(normalizePath(path)))) { - // The caller assumed the file exists, but it doesn't, let's not recreate it + Logger.getInstance().debug( + `The caller assumed ${path} exists, but it no longer, so we wont recreate it` + ); return new Uint8Array(0); } - if (isBinary(expectedContent)) { + if (isFileTypeMergable(path)) { + Logger.getInstance().debug( + `The expected content is not mergable, so we won't perform a 3-way merge, just overwrite it` + ); await this.vault.adapter.writeBinary( normalizePath(path), newContent @@ -75,10 +86,19 @@ export class ObsidianFileOperations implements FileOperations { const resultText = await this.vault.adapter.process( normalizePath(path), (currentText) => { + currentText = currentText.replace(/\r\n/g, "\n"); if (currentText !== expetedText) { + Logger.getInstance().debug( + `Performing a 3-way merge for ${path} with the expected content` + ); + return mergeText(expetedText, currentText, newText); } + Logger.getInstance().debug( + `The current content of ${path} is the same as the expected content, so we will just write the new content` + ); + return newText; } ); @@ -86,6 +106,7 @@ export class ObsidianFileOperations implements FileOperations { } public async remove(path: RelativePath): Promise { + Logger.getInstance().debug(`Removing file: ${path}`); if (await this.vault.adapter.exists(normalizePath(path))) { return this.vault.adapter.remove(normalizePath(path)); } @@ -95,6 +116,7 @@ export class ObsidianFileOperations implements FileOperations { oldPath: RelativePath, newPath: RelativePath ): Promise { + Logger.getInstance().debug(`Moving file: ${oldPath} -> ${newPath}`); if (oldPath === newPath) { return; } @@ -105,6 +127,24 @@ export class ObsidianFileOperations implements FileOperations { ); } + public isFileEligibleForSync(path: RelativePath): boolean { + if (Platform.isDesktopApp) { + return true; + } + + return isFileTypeMergable(path); + } + + private async statFile(path: string): Promise { + const file = await this.vault.adapter.stat(normalizePath(path)); + + if (!file) { + throw new Error(`File not found: ${path}`); + } + + return file; + } + private async createParentDirectories(path: string): Promise { const components = path.split("/"); if (components.length === 1) {