Move file handling logic inside of client
This commit is contained in:
parent
db8e4bc2e7
commit
fde1fecbb6
7 changed files with 151 additions and 137 deletions
|
|
@ -1,168 +0,0 @@
|
|||
import type { Stat, Vault } from "obsidian";
|
||||
import { normalizePath } from "obsidian";
|
||||
import { Platform } from "obsidian";
|
||||
import type { FileOperations, RelativePath } from "sync-client";
|
||||
import { Logger, isFileTypeMergable, mergeText } from "sync-client";
|
||||
|
||||
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> {
|
||||
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))
|
||||
);
|
||||
}
|
||||
|
||||
public async getFileSize(path: RelativePath): Promise<number> {
|
||||
Logger.getInstance().debug(`Getting file size: ${path}`);
|
||||
return (await this.statFile(path)).size;
|
||||
}
|
||||
|
||||
public async getModificationTime(path: RelativePath): Promise<Date> {
|
||||
Logger.getInstance().debug(`Getting modification time: ${path}`);
|
||||
return new Date((await this.statFile(path)).mtime);
|
||||
}
|
||||
|
||||
public async exists(path: RelativePath): Promise<boolean> {
|
||||
Logger.getInstance().debug(`Checking existance of ${path}`);
|
||||
return this.vault.adapter.exists(normalizePath(path));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
await this.createParentDirectories(normalizePath(path));
|
||||
await this.vault.adapter.writeBinary(
|
||||
normalizePath(path),
|
||||
newContent.buffer as ArrayBuffer
|
||||
);
|
||||
}
|
||||
|
||||
public async write(
|
||||
path: RelativePath,
|
||||
expectedContent: Uint8Array,
|
||||
newContent: Uint8Array
|
||||
): Promise<Uint8Array> {
|
||||
Logger.getInstance().debug(`Writing file: ${path}`);
|
||||
if (!(await this.vault.adapter.exists(normalizePath(path)))) {
|
||||
Logger.getInstance().debug(
|
||||
`The caller assumed ${path} exists, but it no longer, so we wont recreate it`
|
||||
);
|
||||
return new Uint8Array(0);
|
||||
}
|
||||
|
||||
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.buffer as ArrayBuffer
|
||||
);
|
||||
return newContent;
|
||||
}
|
||||
|
||||
const expetedText = new TextDecoder().decode(expectedContent);
|
||||
const newText = new TextDecoder().decode(newContent);
|
||||
|
||||
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;
|
||||
}
|
||||
);
|
||||
return new TextEncoder().encode(resultText);
|
||||
}
|
||||
|
||||
public async remove(path: RelativePath): Promise<void> {
|
||||
Logger.getInstance().debug(`Removing file: ${path}`);
|
||||
if (await this.vault.adapter.exists(normalizePath(path))) {
|
||||
await this.vault.adapter.trashSystem(normalizePath(path));
|
||||
}
|
||||
}
|
||||
|
||||
public async move(
|
||||
oldPath: RelativePath,
|
||||
newPath: RelativePath
|
||||
): Promise<void> {
|
||||
oldPath = normalizePath(oldPath);
|
||||
newPath = normalizePath(newPath);
|
||||
|
||||
Logger.getInstance().debug(`Moving file: ${oldPath} -> ${newPath}`);
|
||||
|
||||
if (oldPath === newPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.createParentDirectories(newPath);
|
||||
await this.vault.adapter.rename(oldPath, newPath);
|
||||
}
|
||||
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
for (let i = 1; i < components.length; i++) {
|
||||
const parentDir = components.slice(0, i).join("/");
|
||||
if (!(await this.vault.adapter.exists(parentDir))) {
|
||||
await this.vault.adapter.mkdir(parentDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
69
frontend/obsidian-plugin/src/obsidian-file-system.ts
Normal file
69
frontend/obsidian-plugin/src/obsidian-file-system.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import { normalizePath, Stat, Vault } from "obsidian";
|
||||
import { FileSystemOperations, RelativePath } from "sync-client";
|
||||
|
||||
export class ObsidianFileSystemOperations implements FileSystemOperations {
|
||||
public constructor(private readonly vault: Vault) {}
|
||||
|
||||
public async listAllFiles(): Promise<RelativePath[]> {
|
||||
return this.vault.getFiles().map((file) => file.path);
|
||||
}
|
||||
|
||||
public async read(path: RelativePath): Promise<Uint8Array> {
|
||||
return new Uint8Array(
|
||||
await this.vault.adapter.readBinary(normalizePath(path))
|
||||
);
|
||||
}
|
||||
|
||||
public async write(path: RelativePath, content: Uint8Array): Promise<void> {
|
||||
return this.vault.adapter.writeBinary(
|
||||
normalizePath(path),
|
||||
content.buffer as ArrayBuffer
|
||||
);
|
||||
}
|
||||
|
||||
public async atomicUpdateText(
|
||||
path: RelativePath,
|
||||
updater: (currentContent: string) => string
|
||||
): Promise<string> {
|
||||
return this.vault.adapter.process(normalizePath(path), updater);
|
||||
}
|
||||
|
||||
public async getFileSize(path: RelativePath): Promise<number> {
|
||||
return (await this.statFile(path)).size;
|
||||
}
|
||||
|
||||
public async getModificationTime(path: RelativePath): Promise<Date> {
|
||||
return new Date((await this.statFile(path)).mtime);
|
||||
}
|
||||
|
||||
public async exists(path: RelativePath): Promise<boolean> {
|
||||
return this.vault.adapter.exists(normalizePath(path));
|
||||
}
|
||||
|
||||
public async createDirectory(path: RelativePath): Promise<void> {
|
||||
return this.vault.adapter.mkdir(normalizePath(path));
|
||||
}
|
||||
|
||||
public async delete(path: RelativePath): Promise<void> {
|
||||
if (!(await this.vault.adapter.trashSystem(normalizePath(path)))) {
|
||||
return this.vault.adapter.remove(normalizePath(path));
|
||||
}
|
||||
}
|
||||
|
||||
public async rename(
|
||||
oldPath: RelativePath,
|
||||
newPath: RelativePath
|
||||
): Promise<void> {
|
||||
return this.vault.adapter.rename(oldPath, newPath);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,12 +6,12 @@ import "../manifest.json";
|
|||
import { SyncSettingsTab } from "./views/settings-tab";
|
||||
import { HistoryView } from "./views/history-view";
|
||||
import { ObsidianFileEventHandler } from "./obisidan-event-handler";
|
||||
import { ObsidianFileOperations } from "./obsidian-file-operations";
|
||||
import { StatusBar } from "./views/status-bar";
|
||||
|
||||
import { LogsView } from "./views/logs-view";
|
||||
import { StatusDescription } from "./views/status-description";
|
||||
import { Logger, SyncClient } from "sync-client";
|
||||
import { ObsidianFileSystemOperations } from "./obsidian-file-system";
|
||||
|
||||
export default class VaultLinkPlugin extends Plugin {
|
||||
private settingsTab: SyncSettingsTab | undefined;
|
||||
|
|
@ -21,7 +21,7 @@ export default class VaultLinkPlugin extends Plugin {
|
|||
Logger.getInstance().info("Starting plugin");
|
||||
|
||||
this.client = await SyncClient.create(
|
||||
new ObsidianFileOperations(this.app.vault),
|
||||
new ObsidianFileSystemOperations(this.app.vault),
|
||||
{
|
||||
load: this.loadData.bind(this),
|
||||
save: this.saveData.bind(this)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue