mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-05-14 03:21:17 +00:00
126 lines
4.8 KiB
TypeScript
126 lines
4.8 KiB
TypeScript
import * as fs from "fs/promises";
|
|
import * as path from "path";
|
|
import type { UXDataWriteOptions } from "@lib/common/types";
|
|
import type { IVaultAdapter } from "@lib/serviceModules/adapters";
|
|
import type { NodeFile, NodeFolder, NodeStat } from "./NodeTypes";
|
|
|
|
/**
|
|
* Vault adapter implementation for Node.js
|
|
*/
|
|
export class NodeVaultAdapter implements IVaultAdapter<NodeFile> {
|
|
constructor(private basePath: string) {}
|
|
|
|
private resolvePath(p: string): string {
|
|
return path.join(this.basePath, p);
|
|
}
|
|
|
|
async read(file: NodeFile): Promise<string> {
|
|
const content = await fs.readFile(this.resolvePath(file.path), "utf-8");
|
|
// Correct stale stat.size — chokidar stats may be from a poll before the final write.
|
|
// The downstream document integrity check compares stat.size to content length, so
|
|
// they must agree or other clients reject the file as corrupted.
|
|
file.stat.size = Buffer.byteLength(content, "utf-8");
|
|
return content;
|
|
}
|
|
|
|
async cachedRead(file: NodeFile): Promise<string> {
|
|
// No caching in CLI version, just read directly
|
|
return await this.read(file);
|
|
}
|
|
|
|
async readBinary(file: NodeFile): Promise<ArrayBuffer> {
|
|
const buffer = await fs.readFile(this.resolvePath(file.path));
|
|
// Same correction as read() — ensure stat.size matches actual byte length.
|
|
file.stat.size = buffer.length;
|
|
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) as ArrayBuffer;
|
|
}
|
|
|
|
async modify(file: NodeFile, data: string, options?: UXDataWriteOptions): Promise<void> {
|
|
const fullPath = this.resolvePath(file.path);
|
|
await fs.writeFile(fullPath, data, "utf-8");
|
|
|
|
if (options?.mtime || options?.ctime) {
|
|
const atime = options.mtime ? new Date(options.mtime) : new Date();
|
|
const mtime = options.mtime ? new Date(options.mtime) : new Date();
|
|
await fs.utimes(fullPath, atime, mtime);
|
|
}
|
|
}
|
|
|
|
async modifyBinary(file: NodeFile, data: ArrayBuffer, options?: UXDataWriteOptions): Promise<void> {
|
|
const fullPath = this.resolvePath(file.path);
|
|
await fs.writeFile(fullPath, new Uint8Array(data));
|
|
|
|
if (options?.mtime || options?.ctime) {
|
|
const atime = options.mtime ? new Date(options.mtime) : new Date();
|
|
const mtime = options.mtime ? new Date(options.mtime) : new Date();
|
|
await fs.utimes(fullPath, atime, mtime);
|
|
}
|
|
}
|
|
|
|
async create(p: string, data: string, options?: UXDataWriteOptions): Promise<NodeFile> {
|
|
const fullPath = this.resolvePath(p);
|
|
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
await fs.writeFile(fullPath, data, "utf-8");
|
|
|
|
if (options?.mtime || options?.ctime) {
|
|
const atime = options.mtime ? new Date(options.mtime) : new Date();
|
|
const mtime = options.mtime ? new Date(options.mtime) : new Date();
|
|
await fs.utimes(fullPath, atime, mtime);
|
|
}
|
|
|
|
const stat = await fs.stat(fullPath);
|
|
return {
|
|
path: p as any,
|
|
stat: {
|
|
size: stat.size,
|
|
mtime: Math.floor(stat.mtimeMs),
|
|
ctime: Math.floor(stat.ctimeMs),
|
|
type: "file",
|
|
},
|
|
};
|
|
}
|
|
|
|
async createBinary(p: string, data: ArrayBuffer, options?: UXDataWriteOptions): Promise<NodeFile> {
|
|
const fullPath = this.resolvePath(p);
|
|
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
await fs.writeFile(fullPath, new Uint8Array(data));
|
|
|
|
if (options?.mtime || options?.ctime) {
|
|
const atime = options.mtime ? new Date(options.mtime) : new Date();
|
|
const mtime = options.mtime ? new Date(options.mtime) : new Date();
|
|
await fs.utimes(fullPath, atime, mtime);
|
|
}
|
|
|
|
const stat = await fs.stat(fullPath);
|
|
return {
|
|
path: p as any,
|
|
stat: {
|
|
size: stat.size,
|
|
mtime: Math.floor(stat.mtimeMs),
|
|
ctime: Math.floor(stat.ctimeMs),
|
|
type: "file",
|
|
},
|
|
};
|
|
}
|
|
|
|
async delete(file: NodeFile | NodeFolder, force = false): Promise<void> {
|
|
const fullPath = this.resolvePath(file.path);
|
|
const stat = await fs.stat(fullPath);
|
|
if (stat.isDirectory()) {
|
|
await fs.rm(fullPath, { recursive: true, force });
|
|
} else {
|
|
await fs.unlink(fullPath);
|
|
}
|
|
}
|
|
|
|
async trash(file: NodeFile | NodeFolder, force = false): Promise<void> {
|
|
// In CLI, trash is the same as delete (no recycle bin)
|
|
await this.delete(file, force);
|
|
}
|
|
|
|
trigger(name: string, ...data: any[]): any {
|
|
// No-op in CLI version (no event system)
|
|
return undefined;
|
|
}
|
|
}
|