Refactor, normalize reads, and add isFileEligibleForSync

This commit is contained in:
Andras Schmelczer 2025-01-11 10:53:50 +00:00
parent eb6f7d5a58
commit 49cced2de6
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C
2 changed files with 61 additions and 19 deletions

View file

@ -25,4 +25,6 @@ export interface FileOperations {
remove: (path: RelativePath) => Promise<void>;
move: (oldPath: RelativePath, newPath: RelativePath) => Promise<void>;
isFileEligibleForSync: (path: RelativePath) => boolean;
}

View file

@ -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<RelativePath[]> {
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<Uint8Array> {
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<number> {
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<Date> {
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<void> {
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<Uint8Array> {
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<void> {
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<void> {
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<Stat> {
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<void> {
const components = path.split("/");
if (components.length === 1) {