mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-11-15 12:40:12 +00:00
Maintain watched file names for CLI collision checks
Share the CLI watcher with a reconciled name inventory, observe local mutations, and retain newer notifications across overlapping reads. Adopt the shared index for Obsidian and preserve fresh bulk operations.
This commit is contained in:
1 parent
78ebc2d298
commit
2a65fe0117
35 files changed
+5440
-28
No files matched your search
@@ -0,0 +1,87 @@
|
||||
import { UnicodeResolvingNodeStorageAdapter } from "@vrtmrz/livesync-commonlib/node";
|
||||
import type { UXDataWriteOptions } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
|
||||
/** Ends the invalidation fence even if the filesystem operation fails partially. */
|
||||
export type StorageMutationCompletion = () => void;
|
||||
/** Begin observation before I/O and optionally return its matching completion callback. */
|
||||
export type StorageMutationInvalidator = (paths: readonly string[]) => StorageMutationCompletion | void;
|
||||
|
||||
/** Adds inventory invalidation around the shared CLI storage mutation boundary. */
|
||||
export class ObservedNodeStorageAdapter extends UnicodeResolvingNodeStorageAdapter {
|
||||
constructor(
|
||||
basePath: string,
|
||||
getPathNormalizationVersion: () => number = () => 0,
|
||||
isCaseInsensitive: () => boolean = () => false,
|
||||
private readonly onMutation?: StorageMutationInvalidator
|
||||
) {
|
||||
super(basePath, getPathNormalizationVersion, isCaseInsensitive);
|
||||
}
|
||||
|
||||
private async runMutation<T>(paths: readonly string[], operation: () => Promise<T>): Promise<T> {
|
||||
// Capture one completion for this operation. Nested storage methods can
|
||||
// create their own tokens; the source waits for all tokens in its boundary.
|
||||
let complete: StorageMutationCompletion | void;
|
||||
try {
|
||||
complete = this.onMutation?.(paths);
|
||||
} catch {
|
||||
// A failed observation hook must not suppress the requested filesystem I/O.
|
||||
complete = undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
return await operation();
|
||||
} finally {
|
||||
// Failed I/O may still have changed names, so completion always runs.
|
||||
try {
|
||||
complete?.();
|
||||
} catch {
|
||||
// Inventory bookkeeping must not change the storage operation's result.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override async write(
|
||||
path: string,
|
||||
data: string,
|
||||
options?: UXDataWriteOptions,
|
||||
caseInsensitive?: boolean
|
||||
): Promise<void> {
|
||||
return await this.runMutation([path], async () => await super.write(path, data, options, caseInsensitive));
|
||||
}
|
||||
|
||||
override async writeBinary(
|
||||
path: string,
|
||||
data: ArrayBuffer,
|
||||
options?: UXDataWriteOptions,
|
||||
caseInsensitive?: boolean
|
||||
): Promise<void> {
|
||||
return await this.runMutation(
|
||||
[path],
|
||||
async () => await super.writeBinary(path, data, options, caseInsensitive)
|
||||
);
|
||||
}
|
||||
|
||||
override async append(
|
||||
path: string,
|
||||
data: string,
|
||||
options?: UXDataWriteOptions,
|
||||
caseInsensitive?: boolean
|
||||
): Promise<void> {
|
||||
return await this.runMutation([path], async () => await super.append(path, data, options, caseInsensitive));
|
||||
}
|
||||
|
||||
override async mkdir(path: string, caseInsensitive?: boolean): Promise<void> {
|
||||
return await this.runMutation([path], async () => await super.mkdir(path, caseInsensitive));
|
||||
}
|
||||
|
||||
override async remove(path: string, caseInsensitive?: boolean): Promise<void> {
|
||||
return await this.runMutation([path], async () => await super.remove(path, caseInsensitive));
|
||||
}
|
||||
|
||||
override async rename(sourcePath: string, targetPath: string, caseInsensitive?: boolean): Promise<void> {
|
||||
return await this.runMutation(
|
||||
[sourcePath, targetPath],
|
||||
async () => await super.rename(sourcePath, targetPath, caseInsensitive)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user