mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-11-14 20:20:12 +00:00
Use the shared Node adapter and retain one comparison policy throughout each CLI scan. Propagate permission and I/O failures instead of reporting absence or a successful partial scan. Use shared namespace and comparison helpers in Obsidian while preserving its Vault APIs and alias policy. Cover native spelling and host filename compatibility, and document the dependency and integration boundaries.
99 lines
3.7 KiB
TypeScript
99 lines
3.7 KiB
TypeScript
import type { FilePath, FilePathWithPrefix, UXFileInfo } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
|
import { readAsBlob } from "@vrtmrz/livesync-commonlib/compat/common/utils";
|
|
import type { DatabaseFileAccess } from "@vrtmrz/livesync-commonlib/compat/interfaces/DatabaseFileAccess";
|
|
import type { IFileHandler } from "@vrtmrz/livesync-commonlib/compat/interfaces/FileHandler";
|
|
import { parseDocumentPath, splitDocumentPath } from "@vrtmrz/livesync-commonlib/storage";
|
|
|
|
export type DocumentHistoryRestorationCore = {
|
|
databaseFileAccess: Pick<
|
|
DatabaseFileAccess,
|
|
"fetchEntry" | "fetchEntryMeta" | "getConflictedRevs" | "storeWithLiveBaseRevision"
|
|
>;
|
|
fileHandler: Pick<IFileHandler, "dbToStorageWithSpecificRev">;
|
|
};
|
|
|
|
export type DocumentHistoryRestorationResult =
|
|
| {
|
|
status: "restored";
|
|
path: FilePath;
|
|
revision: string;
|
|
conflictsRemain: boolean | undefined;
|
|
conflictCheckError?: unknown;
|
|
}
|
|
| { status: "source-unavailable" }
|
|
| { status: "current-unavailable" }
|
|
| { status: "unsupported-path" }
|
|
| { status: "database-write-refused" }
|
|
| { status: "stored-not-reflected"; path: FilePath; revision: string; cause?: unknown };
|
|
|
|
export type DocumentHistoryRestorationOptions = {
|
|
now?: () => number;
|
|
isPathValid?: (path: FilePath) => boolean;
|
|
};
|
|
|
|
/**
|
|
* Restore historical content as a new child of the current database winner, then reflect
|
|
* that exact new revision to the Vault. The historical revision supplies content, not ancestry.
|
|
*/
|
|
export async function restoreDocumentHistoryRevision(
|
|
core: DocumentHistoryRestorationCore,
|
|
path: FilePathWithPrefix,
|
|
sourceRevision: string,
|
|
options: DocumentHistoryRestorationOptions = {}
|
|
): Promise<DocumentHistoryRestorationResult> {
|
|
const now = options.now ?? Date.now;
|
|
const isPathValid = options.isPathValid ?? (() => true);
|
|
const source = await core.databaseFileAccess.fetchEntry(path, sourceRevision, true, true);
|
|
if (source === false) {
|
|
return { status: "source-unavailable" };
|
|
}
|
|
|
|
const current = await core.databaseFileAccess.fetchEntryMeta(path, undefined, true);
|
|
if (current === false || !current._rev) {
|
|
return { status: "current-unavailable" };
|
|
}
|
|
|
|
const body = readAsBlob(source);
|
|
const { prefix, path: storagePath } = splitDocumentPath(parseDocumentPath(current.path));
|
|
if (prefix !== "" || !isPathValid(storagePath)) {
|
|
return { status: "unsupported-path" };
|
|
}
|
|
const file: UXFileInfo = {
|
|
name: storagePath.split("/").pop() ?? storagePath,
|
|
path: storagePath,
|
|
stat: {
|
|
ctime: current.ctime,
|
|
mtime: now(),
|
|
size: body.size,
|
|
type: "file",
|
|
},
|
|
body,
|
|
};
|
|
const revision = await core.databaseFileAccess.storeWithLiveBaseRevision(file, current._rev, true);
|
|
if (revision === false) {
|
|
return { status: "database-write-refused" };
|
|
}
|
|
|
|
try {
|
|
const reflected = await core.fileHandler.dbToStorageWithSpecificRev(storagePath, revision, true);
|
|
if (!reflected) {
|
|
return { status: "stored-not-reflected", path: storagePath, revision };
|
|
}
|
|
} catch (cause) {
|
|
return { status: "stored-not-reflected", path: storagePath, revision, cause };
|
|
}
|
|
|
|
try {
|
|
const conflictsRemain = (await core.databaseFileAccess.getConflictedRevs(storagePath)).length > 0;
|
|
return { status: "restored", path: storagePath, revision, conflictsRemain };
|
|
} catch (conflictCheckError) {
|
|
return {
|
|
status: "restored",
|
|
path: storagePath,
|
|
revision,
|
|
conflictsRemain: undefined,
|
|
conflictCheckError,
|
|
};
|
|
}
|
|
}
|