diff --git a/src/CmdConfigSync.ts b/src/CmdConfigSync.ts index d2fb1dc..ff10398 100644 --- a/src/CmdConfigSync.ts +++ b/src/CmdConfigSync.ts @@ -418,9 +418,9 @@ export class ConfigSync extends LiveSyncCommands { await this.ensureDirectoryEx(path); if (!content) { const dt = decodeBinary(f.data); - await this.app.vault.adapter.writeBinary(path, dt); + await this.vaultAccess.adapterWrite(path, dt); } else { - await this.app.vault.adapter.write(path, content); + await this.vaultAccess.adapterWrite(path, content); } Logger(`Applying ${f.filename} of ${data.displayName || data.name}.. Done`); @@ -540,13 +540,13 @@ export class ConfigSync extends LiveSyncCommands { recentProcessedInternalFiles = [] as string[]; async makeEntryFromFile(path: FilePath): Promise { - const stat = await this.app.vault.adapter.stat(path); + const stat = await this.vaultAccess.adapterStat(path); let version: string | undefined; let displayName: string | undefined; if (!stat) { return false; } - const contentBin = await this.app.vault.adapter.readBinary(path); + const contentBin = await this.vaultAccess.adapterReadBinary(path); let content: string[]; try { content = await arrayBufferToBase64(contentBin); @@ -701,7 +701,7 @@ export class ConfigSync extends LiveSyncCommands { async watchVaultRawEventsAsync(path: FilePath) { if (!this.settings.usePluginSync) return false; if (!this.isTargetPath(path)) return false; - const stat = await this.app.vault.adapter.stat(path); + const stat = await this.vaultAccess.adapterStat(path); // Make sure that target is a file. if (stat && stat.type != "file") return false; diff --git a/src/CmdHiddenFileSync.ts b/src/CmdHiddenFileSync.ts index b276a9c..844fdb6 100644 --- a/src/CmdHiddenFileSync.ts +++ b/src/CmdHiddenFileSync.ts @@ -103,7 +103,7 @@ export class HiddenFileSync extends LiveSyncCommands { Logger(`Hidden file skipped: ${path} is synchronized in customization sync.`, LOG_LEVEL_VERBOSE); return; } - const stat = await this.app.vault.adapter.stat(path); + const stat = await this.vaultAccess.adapterStat(path); // sometimes folder is coming. if (stat && stat.type != "file") return; @@ -171,12 +171,12 @@ export class HiddenFileSync extends LiveSyncCommands { if (result) { Logger(`Object merge:${path}`, LOG_LEVEL_INFO); const filename = stripAllPrefixes(path); - const isExists = await this.app.vault.adapter.exists(filename); + const isExists = await this.plugin.vaultAccess.adapterExists(filename); if (!isExists) { await this.ensureDirectoryEx(filename); } - await this.app.vault.adapter.write(filename, result); - const stat = await this.app.vault.adapter.stat(filename); + await this.plugin.vaultAccess.adapterWrite(filename, result); + const stat = await this.vaultAccess.adapterStat(filename); await this.storeInternalFileToDatabase({ path: filename, ...stat }); await this.extractInternalFileFromDatabase(filename); await this.localDatabase.removeRaw(id, revB); @@ -408,7 +408,7 @@ export class HiddenFileSync extends LiveSyncCommands { const id = await this.path2id(file.path, ICHeader); const prefixedFileName = addPrefix(file.path, ICHeader); - const contentBin = await this.app.vault.adapter.readBinary(file.path); + const contentBin = await this.plugin.vaultAccess.adapterReadBinary(file.path); let content: Blob; try { content = createBinaryBlob(contentBin); @@ -511,7 +511,7 @@ export class HiddenFileSync extends LiveSyncCommands { } async extractInternalFileFromDatabase(filename: FilePath, force = false) { - const isExists = await this.app.vault.adapter.exists(filename); + const isExists = await this.plugin.vaultAccess.adapterExists(filename); const prefixedFileName = addPrefix(filename, ICHeader); if (await this.plugin.isIgnoredByIgnoreFiles(filename)) { return; @@ -534,7 +534,7 @@ export class HiddenFileSync extends LiveSyncCommands { Logger(`STORAGE synchronisedInConfigSync.every(filterFile => !path.toLowerCase().startsWith(filterFile))).map(async (e) => { return { path: e as FilePath, - stat: await this.app.vault.adapter.stat(e) + stat: await this.plugin.vaultAccess.adapterStat(e) }; }); const result: InternalFileInfo[] = []; diff --git a/src/CmdPluginAndTheirSettings.ts b/src/CmdPluginAndTheirSettings.ts index 814ca37..f863e47 100644 --- a/src/CmdPluginAndTheirSettings.ts +++ b/src/CmdPluginAndTheirSettings.ts @@ -186,18 +186,17 @@ export class PluginAndTheirSettings extends LiveSyncCommands { } Logger(`Reading plugin:${m.name}(${m.id})`, LOG_LEVEL_VERBOSE); const path = normalizePath(m.dir) + "/"; - const adapter = this.app.vault.adapter; const files = ["manifest.json", "main.js", "styles.css", "data.json"]; const pluginData: { [key: string]: string; } = {}; for (const file of files) { const thePath = path + file; - if (await adapter.exists(thePath)) { - pluginData[file] = await adapter.read(thePath); + if (await this.plugin.vaultAccess.adapterExists(thePath)) { + pluginData[file] = await this.plugin.vaultAccess.adapterRead(thePath); } } let mtime = 0; - if (await adapter.exists(path + "/data.json")) { - mtime = (await adapter.stat(path + "/data.json")).mtime; + if (await this.plugin.vaultAccess.adapterExists(path + "/data.json")) { + mtime = (await this.plugin.vaultAccess.adapterStat(path + "/data.json")).mtime; } const p: PluginDataEntry = { @@ -269,7 +268,6 @@ export class PluginAndTheirSettings extends LiveSyncCommands { async applyPluginData(plugin: PluginDataEntry) { await serialized("plugin-" + plugin.manifest.id, async () => { const pluginTargetFolderPath = normalizePath(plugin.manifest.dir) + "/"; - const adapter = this.app.vault.adapter; // @ts-ignore const stat = this.app.plugins.enabledPlugins.has(plugin.manifest.id) == true; if (stat) { @@ -278,7 +276,7 @@ export class PluginAndTheirSettings extends LiveSyncCommands { Logger(`Unload plugin:${plugin.manifest.id}`, LOG_LEVEL_NOTICE); } if (plugin.dataJson) - await adapter.write(pluginTargetFolderPath + "data.json", plugin.dataJson); + await this.plugin.vaultAccess.adapterWrite(pluginTargetFolderPath + "data.json", plugin.dataJson); Logger("wrote:" + pluginTargetFolderPath + "data.json", LOG_LEVEL_NOTICE); if (stat) { // @ts-ignore @@ -299,14 +297,13 @@ export class PluginAndTheirSettings extends LiveSyncCommands { } const pluginTargetFolderPath = normalizePath(plugin.manifest.dir) + "/"; - const adapter = this.app.vault.adapter; - if ((await adapter.exists(pluginTargetFolderPath)) === false) { - await adapter.mkdir(pluginTargetFolderPath); + if ((await this.plugin.vaultAccess.adapterExists(pluginTargetFolderPath)) === false) { + await this.app.vault.adapter.mkdir(pluginTargetFolderPath); } - await adapter.write(pluginTargetFolderPath + "main.js", plugin.mainJs); - await adapter.write(pluginTargetFolderPath + "manifest.json", plugin.manifestJson); + await this.plugin.vaultAccess.adapterWrite(pluginTargetFolderPath + "main.js", plugin.mainJs); + await this.plugin.vaultAccess.adapterWrite(pluginTargetFolderPath + "manifest.json", plugin.manifestJson); if (plugin.styleCss) - await adapter.write(pluginTargetFolderPath + "styles.css", plugin.styleCss); + await this.plugin.vaultAccess.adapterWrite(pluginTargetFolderPath + "styles.css", plugin.styleCss); if (stat) { // @ts-ignore await this.app.plugins.loadPlugin(plugin.manifest.id); diff --git a/src/DocumentHistoryModal.ts b/src/DocumentHistoryModal.ts index 71f58e5..2880d01 100644 --- a/src/DocumentHistoryModal.ts +++ b/src/DocumentHistoryModal.ts @@ -200,11 +200,11 @@ export class DocumentHistoryModal extends Modal { Logger("Path is not valid to write content.", LOG_LEVEL_INFO); } if (this.currentDoc?.datatype == "plain") { - await this.app.vault.adapter.write(pathToWrite, getDocData(this.currentDoc.data)); + await this.plugin.vaultAccess.adapterWrite(pathToWrite, getDocData(this.currentDoc.data)); await focusFile(pathToWrite); this.close(); } else if (this.currentDoc?.datatype == "newnote") { - await this.app.vault.adapter.writeBinary(pathToWrite, decodeBinary(this.currentDoc.data)); + await this.plugin.vaultAccess.adapterWrite(pathToWrite, decodeBinary(this.currentDoc.data)); await focusFile(pathToWrite); this.close(); } else { diff --git a/src/GlobalHistory.svelte b/src/GlobalHistory.svelte index 9463383..07f56b1 100644 --- a/src/GlobalHistory.svelte +++ b/src/GlobalHistory.svelte @@ -2,12 +2,11 @@ import ObsidianLiveSyncPlugin from "./main"; import { onDestroy, onMount } from "svelte"; import type { AnyEntry, FilePathWithPrefix } from "./lib/src/types"; - import { getDocData, isDocContentSame } from "./lib/src/utils"; + import { createBinaryBlob, getDocData, isDocContentSame } from "./lib/src/utils"; import { diff_match_patch } from "./deps"; import { DocumentHistoryModal } from "./DocumentHistoryModal"; import { isPlainText, stripAllPrefixes } from "./lib/src/path"; import { TFile } from "./deps"; - import { encodeBinary } from "./lib/src/strbin"; export let plugin: ObsidianLiveSyncPlugin; let showDiffInfo = false; @@ -108,15 +107,15 @@ } if (rev == docA._rev) { if (checkStorageDiff) { - const abs = plugin.app.vault.getAbstractFileByPath(stripAllPrefixes(plugin.getPath(docA))); + const abs = plugin.vaultAccess.getAbstractFileByPath(stripAllPrefixes(plugin.getPath(docA))); if (abs instanceof TFile) { let result = false; if (isPlainText(docA.path)) { - const data = await plugin.app.vault.read(abs); + const data = await plugin.vaultAccess.adapterRead(abs); result = isDocContentSame(data, doc.data); } else { - const data = await plugin.app.vault.readBinary(abs); - const dataEEncoded = await encodeBinary(data, plugin.settings.useV1); + const data = await plugin.vaultAccess.adapterReadBinary(abs); + const dataEEncoded = createBinaryBlob(data); result = isDocContentSame(dataEEncoded, doc.data); } if (result) { diff --git a/src/LiveSyncCommands.ts b/src/LiveSyncCommands.ts index 3b07b5d..99eb7fa 100644 --- a/src/LiveSyncCommands.ts +++ b/src/LiveSyncCommands.ts @@ -14,6 +14,9 @@ export abstract class LiveSyncCommands { get localDatabase() { return this.plugin.localDatabase; } + get vaultAccess() { + return this.plugin.vaultAccess; + } id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix { return this.plugin.id2path(id, entry, stripPrefix); } diff --git a/src/SerializedFileAccess.ts b/src/SerializedFileAccess.ts new file mode 100644 index 0000000..f7078eb --- /dev/null +++ b/src/SerializedFileAccess.ts @@ -0,0 +1,118 @@ +import { type App, TFile, type DataWriteOptions, TFolder, TAbstractFile } from "./deps"; +import { serialized } from "./lib/src/lock"; +import type { FilePath } from "./lib/src/types"; +function getFileLockKey(file: TFile | TFolder | string) { + return `fl:${typeof (file) == "string" ? file : file.path}`; +} +function toArrayBuffer(arr: Uint8Array | ArrayBuffer | DataView): ArrayBufferLike { + if (arr instanceof Uint8Array) { + return arr.buffer; + } + if (arr instanceof DataView) { + return arr.buffer; + } + return arr; +} + +export class SerializedFileAccess { + app: App + constructor(app: App) { + this.app = app; + } + + async adapterStat(file: TFile | string) { + const path = file instanceof TFile ? file.path : file; + return await serialized(getFileLockKey(path), () => this.app.vault.adapter.stat(path)); + } + async adapterExists(file: TFile | string) { + const path = file instanceof TFile ? file.path : file; + return await serialized(getFileLockKey(path), () => this.app.vault.adapter.exists(path)); + } + async adapterRemove(file: TFile | string) { + const path = file instanceof TFile ? file.path : file; + return await serialized(getFileLockKey(path), () => this.app.vault.adapter.remove(path)); + } + + async adapterRead(file: TFile | string) { + const path = file instanceof TFile ? file.path : file; + return await serialized(getFileLockKey(path), () => this.app.vault.adapter.read(path)); + } + async adapterReadBinary(file: TFile | string) { + const path = file instanceof TFile ? file.path : file; + return await serialized(getFileLockKey(path), () => this.app.vault.adapter.readBinary(path)); + } + + async adapterWrite(file: TFile | string, data: string | ArrayBuffer | Uint8Array, options?: DataWriteOptions) { + const path = file instanceof TFile ? file.path : file; + if (typeof (data) === "string") { + return await serialized(getFileLockKey(path), () => this.app.vault.adapter.write(path, data, options)); + } else { + return await serialized(getFileLockKey(path), () => this.app.vault.adapter.writeBinary(path, toArrayBuffer(data), options)); + } + } + + async vaultCacheRead(file: TFile) { + return await serialized(getFileLockKey(file), () => this.app.vault.cachedRead(file)); + } + + async vaultRead(file: TFile) { + return await serialized(getFileLockKey(file), () => this.app.vault.read(file)); + } + + async vaultReadBinary(file: TFile) { + return await serialized(getFileLockKey(file), () => this.app.vault.readBinary(file)); + } + + async vaultModify(file: TFile, data: string | ArrayBuffer | Uint8Array, options?: DataWriteOptions) { + if (typeof (data) === "string") { + return await serialized(getFileLockKey(file), () => this.app.vault.modify(file, data, options)); + } else { + return await serialized(getFileLockKey(file), () => this.app.vault.modifyBinary(file, toArrayBuffer(data), options)); + } + } + async vaultCreate(path: string, data: string | ArrayBuffer | Uint8Array, options?: DataWriteOptions): Promise { + if (typeof (data) === "string") { + return await serialized(getFileLockKey(path), () => this.app.vault.create(path, data, options)); + } else { + return await serialized(getFileLockKey(path), () => this.app.vault.createBinary(path, toArrayBuffer(data), options)); + } + } + async delete(file: TFile | TFolder, force = false) { + return await serialized(getFileLockKey(file), () => this.app.vault.delete(file, force)); + } + async trash(file: TFile | TFolder, force = false) { + return await serialized(getFileLockKey(file), () => this.app.vault.trash(file, force)); + } + + getAbstractFileByPath(path: FilePath | string): TAbstractFile | null { + // Disabled temporary. + return this.app.vault.getAbstractFileByPath(path); + // // Hidden API but so useful. + // // @ts-ignore + // if ("getAbstractFileByPathInsensitive" in app.vault && (app.vault.adapter?.insensitive ?? false)) { + // // @ts-ignore + // return app.vault.getAbstractFileByPathInsensitive(path); + // } else { + // return app.vault.getAbstractFileByPath(path); + // } + } + + + touchedFiles: string[] = []; + + + touch(file: TFile | FilePath) { + const f = file instanceof TFile ? file : this.getAbstractFileByPath(file) as TFile; + const key = `${f.path}-${f.stat.mtime}-${f.stat.size}`; + this.touchedFiles.unshift(key); + this.touchedFiles = this.touchedFiles.slice(0, 100); + } + recentlyTouched(file: TFile) { + const key = `${file.path}-${file.stat.mtime}-${file.stat.size}`; + if (this.touchedFiles.indexOf(key) == -1) return false; + return true; + } + clearTouched() { + this.touchedFiles = []; + } +} \ No newline at end of file diff --git a/src/StorageEventManager.ts b/src/StorageEventManager.ts index 06a87f1..cdf4194 100644 --- a/src/StorageEventManager.ts +++ b/src/StorageEventManager.ts @@ -1,9 +1,9 @@ +import type { SerializedFileAccess } from "./SerializedFileAccess"; import { Plugin, TAbstractFile, TFile, TFolder } from "./deps"; import { isPlainText, shouldBeIgnored } from "./lib/src/path"; import { getGlobalStore } from "./lib/src/store"; import { type FilePath, type ObsidianLiveSyncSettings } from "./lib/src/types"; import { type FileEventItem, type FileEventType, type FileInfo, type InternalFileInfo, type queueItem } from "./types"; -import { recentlyTouched } from "./utils"; export abstract class StorageEventManager { @@ -16,6 +16,7 @@ type LiveSyncForStorageEventManager = Plugin & { settings: ObsidianLiveSyncSettings ignoreFiles: string[], + vaultAccess: SerializedFileAccess } & { isTargetFile: (file: string | TAbstractFile) => Promise, procFileEvent: (applyBatch?: boolean) => Promise, @@ -105,15 +106,14 @@ export class StorageEventManagerObsidian extends StorageEventManager { let cache: null | string | ArrayBuffer; // new file or something changed, cache the changes. if (file instanceof TFile && (type == "CREATE" || type == "CHANGED")) { - if (recentlyTouched(file)) { + if (this.plugin.vaultAccess.recentlyTouched(file)) { continue; } if (!isPlainText(file.name)) { - cache = await this.plugin.app.vault.readBinary(file); + cache = await this.plugin.vaultAccess.vaultReadBinary(file); } else { - // cache = await this.app.vault.read(file); - cache = await this.plugin.app.vault.cachedRead(file); - if (!cache) cache = await this.plugin.app.vault.read(file); + cache = await this.plugin.vaultAccess.vaultCacheRead(file); + if (!cache) cache = await this.plugin.vaultAccess.vaultRead(file); } } if (type == "DELETE" || type == "RENAME") { diff --git a/src/lib b/src/lib index f9aeaf6..7e79c27 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit f9aeaf6a2d8cb42deafc0bd89d6a43b4654d18fd +Subproject commit 7e79c2703520b6a9e31e50c94f12ce0d948fd745 diff --git a/src/main.ts b/src/main.ts index 2cec12e..97188bd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -10,7 +10,7 @@ import { PouchDB } from "./lib/src/pouchdb-browser.js"; import { ConflictResolveModal } from "./ConflictResolveModal"; import { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab"; import { DocumentHistoryModal } from "./DocumentHistoryModal"; -import { applyPatch, cancelAllPeriodicTask, cancelAllTasks, cancelTask, generatePatchObj, id2path, isObjectMargeApplicable, isSensibleMargeApplicable, flattenObject, path2id, scheduleTask, tryParseJSON, createFile, modifyFile, isValidPath, getAbstractFileByPath, touch, recentlyTouched, isInternalMetadata, isPluginMetadata, stripInternalMetadataPrefix, isChunk, askSelectString, askYesNo, askString, PeriodicProcessor, clearTouched, getPath, getPathWithoutPrefix, getPathFromTFile, performRebuildDB, memoIfNotExist, memoObject, retrieveMemoObject, disposeMemoObject, isCustomisationSyncMetadata } from "./utils"; +import { applyPatch, cancelAllPeriodicTask, cancelAllTasks, cancelTask, generatePatchObj, id2path, isObjectMargeApplicable, isSensibleMargeApplicable, flattenObject, path2id, scheduleTask, tryParseJSON, isValidPath, isInternalMetadata, isPluginMetadata, stripInternalMetadataPrefix, isChunk, askSelectString, askYesNo, askString, PeriodicProcessor, getPath, getPathWithoutPrefix, getPathFromTFile, performRebuildDB, memoIfNotExist, memoObject, retrieveMemoObject, disposeMemoObject, isCustomisationSyncMetadata } from "./utils"; import { encrypt, tryDecrypt } from "./lib/src/e2ee_v2"; import { balanceChunkPurgedDBs, enableEncryption, isCloudantURI, isErrorOfMissingDoc, isValidRemoteCouchDBURI, purgeUnreferencedChunks } from "./lib/src/utils_couchdb"; import { getGlobalStore, ObservableStore, observeStores } from "./lib/src/store"; @@ -33,6 +33,7 @@ import { GlobalHistoryView, VIEW_TYPE_GLOBAL_HISTORY } from "./GlobalHistoryView import { LogPaneView, VIEW_TYPE_LOG } from "./LogPaneView"; import { mapAllTasksWithConcurrencyLimit, processAllTasksWithConcurrencyLimit } from "./lib/src/task"; import { LRUCache } from "./lib/src/LRUCache"; +import { SerializedFileAccess } from "./SerializedFileAccess"; setNoticeClass(Notice); @@ -86,6 +87,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin return !this.last_successful_post; } + vaultAccess: SerializedFileAccess = new SerializedFileAccess(this.app); _unloaded = false; @@ -293,36 +295,36 @@ export default class ObsidianLiveSyncPlugin extends Plugin } isRedFlagRaised(): boolean { - const redflag = getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG)); + const redflag = this.vaultAccess.getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG)); if (redflag != null) { return true; } return false; } isRedFlag2Raised(): boolean { - const redflag = getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG2)); + const redflag = this.vaultAccess.getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG2)); if (redflag != null) { return true; } return false; } async deleteRedFlag2() { - const redflag = getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG2)); - if (redflag != null) { - await app.vault.delete(redflag, true); + const redflag = this.vaultAccess.getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG2)); + if (redflag != null && redflag instanceof TFile) { + await this.vaultAccess.delete(redflag, true); } } isRedFlag3Raised(): boolean { - const redflag = getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG3)); + const redflag = this.vaultAccess.getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG3)); if (redflag != null) { return true; } return false; } async deleteRedFlag3() { - const redflag = getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG3)); - if (redflag != null) { - await app.vault.delete(redflag, true); + const redflag = this.vaultAccess.getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG3)); + if (redflag != null && redflag instanceof TFile) { + await this.vaultAccess.delete(redflag, true); } } @@ -683,7 +685,6 @@ Note: We can always able to read V1 format. It will be progressively converted. } async onload() { - logStore.subscribe(e => this.addLog(e.message, e.level, e.key)); Logger("loading plugin"); this.addSettingTab(new ObsidianLiveSyncSettingTab(this.app, this)); @@ -1065,7 +1066,7 @@ Note: We can always able to read V1 format. It will be progressively converted. await this.addOnHiddenFileSync.watchVaultRawEventsAsync(file.path); await this.addOnConfigSync.watchVaultRawEventsAsync(file.path); } else { - const targetFile = this.app.vault.getAbstractFileByPath(file.path); + const targetFile = this.vaultAccess.getAbstractFileByPath(file.path); if (!(targetFile instanceof TFile)) { Logger(`Target file was not found: ${file.path}`, LOG_LEVEL_INFO); continue; @@ -1184,7 +1185,7 @@ Note: We can always able to read V1 format. It will be progressively converted. if (this.settings?.writeLogToTheFile) { const time = now.toISOString().split("T")[0]; const logDate = `${PREFIXMD_LOGFILE}${time}.md`; - const file = this.app.vault.getAbstractFileByPath(normalizePath(logDate)); + const file = this.vaultAccess.getAbstractFileByPath(normalizePath(logDate)); if (!file) { this.app.vault.adapter.append(normalizePath(logDate), "```\n"); } @@ -1306,13 +1307,15 @@ Note: We can always able to read V1 format. It will be progressively converted. try { let outFile; if (mode == "create") { - outFile = await createFile(this.app,normalizePath(path), writeData, { ctime: doc.ctime, mtime: doc.mtime, }); + const normalizedPath = normalizePath(path); + await this.vaultAccess.vaultCreate(normalizedPath, writeData, { ctime: doc.ctime, mtime: doc.mtime, }); + outFile = this.vaultAccess.getAbstractFileByPath(normalizedPath) as TFile; } else { - await modifyFile(this.app,file, writeData, { ctime: doc.ctime, mtime: doc.mtime }); - outFile = getAbstractFileByPath(getPathFromTFile(file)) as TFile; + await this.vaultAccess.vaultModify(file, writeData, { ctime: doc.ctime, mtime: doc.mtime }); + outFile = this.vaultAccess.getAbstractFileByPath(getPathFromTFile(file)) as TFile; } Logger(msg + path); - touch(outFile); + this.vaultAccess.touch(outFile); this.app.vault.trigger(mode, outFile); } catch (ex) { @@ -1327,9 +1330,9 @@ Note: We can always able to read V1 format. It will be progressively converted. } const dir = file.parent; if (this.settings.trashInsteadDelete) { - await this.app.vault.trash(file, false); + await this.vaultAccess.trash(file, false); } else { - await this.app.vault.delete(file, true); + await this.vaultAccess.delete(file, true); } Logger(`xxx <- STORAGE (deleted) ${file.path}`); Logger(`files: ${dir.children.length}`); @@ -1394,7 +1397,7 @@ Note: We can always able to read V1 format. It will be progressively converted. } async handleDBChangedAsync(change: EntryBody) { - const targetFile = getAbstractFileByPath(this.getPathWithoutPrefix(change)); + const targetFile = this.vaultAccess.getAbstractFileByPath(this.getPathWithoutPrefix(change)); if (targetFile == null) { if (change._deleted || change.deleted) { return; @@ -1518,7 +1521,7 @@ Note: We can always able to read V1 format. It will be progressively converted. } if ((!isInternalMetadata(doc._id)) && skipOldFile) { - const info = getAbstractFileByPath(stripAllPrefixes(path)); + const info = this.vaultAccess.getAbstractFileByPath(stripAllPrefixes(path)); if (info && info instanceof TFile) { const localMtime = ~~(info.stat.mtime / 1000); @@ -2260,12 +2263,12 @@ Or if you are sure know what had been happened, we can unlock the database from // remove conflicted revision. await this.localDatabase.deleteDBEntry(path, { rev: conflictedRev }); - const file = getAbstractFileByPath(stripAllPrefixes(path)) as TFile; + const file = this.vaultAccess.getAbstractFileByPath(stripAllPrefixes(path)) as TFile; if (file) { - await this.app.vault.modify(file, p); + await this.vaultAccess.vaultModify(file, p); await this.updateIntoDB(file); } else { - const newFile = await this.app.vault.create(path, p); + const newFile = await this.vaultAccess.vaultCreate(path, p); await this.updateIntoDB(newFile); } await this.pullFile(path); @@ -2338,12 +2341,12 @@ Or if you are sure know what had been happened, we can unlock the database from // delete conflicted revision and write a new file, store it again. const p = conflictCheckResult.diff.map((e) => e[1]).join(""); await this.localDatabase.deleteDBEntry(filename, { rev: testDoc._conflicts[0] }); - const file = getAbstractFileByPath(stripAllPrefixes(filename)) as TFile; + const file = this.vaultAccess.getAbstractFileByPath(stripAllPrefixes(filename)) as TFile; if (file) { - await this.app.vault.modify(file, p); + await this.vaultAccess.vaultModify(file, p); await this.updateIntoDB(file); } else { - const newFile = await this.app.vault.create(filename, p); + const newFile = await this.vaultAccess.vaultCreate(filename, p); await this.updateIntoDB(newFile); } await this.pullFile(filename); @@ -2385,7 +2388,7 @@ Or if you are sure know what had been happened, we can unlock the database from const checkFiles = JSON.parse(JSON.stringify(this.conflictedCheckFiles)) as FilePath[]; for (const filename of checkFiles) { try { - const file = getAbstractFileByPath(filename); + const file = this.vaultAccess.getAbstractFileByPath(filename); if (file != null && file instanceof TFile) { await this.showIfConflicted(getPathFromTFile(file)); } @@ -2420,7 +2423,7 @@ Or if you are sure know what had been happened, we can unlock the database from } async pullFile(filename: FilePathWithPrefix, fileList?: TFile[], force?: boolean, rev?: string, waitForReady = true) { - const targetFile = getAbstractFileByPath(stripAllPrefixes(filename)); + const targetFile = this.vaultAccess.getAbstractFileByPath(stripAllPrefixes(filename)); if (!await this.isTargetFile(filename)) return; if (targetFile == null) { //have to create; @@ -2451,7 +2454,7 @@ Or if you are sure know what had been happened, we can unlock the database from throw new Error(`Missing doc:${(file as any).path}`) } if (!(file instanceof TFile) && "path" in file) { - const w = getAbstractFileByPath((file as any).path); + const w = this.vaultAccess.getAbstractFileByPath((file as any).path); if (w instanceof TFile) { file = w; } else { @@ -2504,7 +2507,7 @@ Or if you are sure know what had been happened, we can unlock the database from if (!cache) { if (!isPlainText(file.name)) { Logger(`Reading : ${file.path}`, LOG_LEVEL_VERBOSE); - const contentBin = await this.app.vault.readBinary(file); + const contentBin = await this.vaultAccess.vaultReadBinary(file); Logger(`Processing: ${file.path}`, LOG_LEVEL_VERBOSE); try { content = createBinaryBlob(contentBin); @@ -2515,12 +2518,12 @@ Or if you are sure know what had been happened, we can unlock the database from } datatype = "newnote"; } else { - content = createTextBlob(await this.app.vault.read(file)); + content = createTextBlob(await this.vaultAccess.vaultRead(file)); datatype = "plain"; } } else { if (cache instanceof ArrayBuffer) { - Logger(`Processing: ${file.path}`, LOG_LEVEL_VERBOSE); + Logger(`Cache Processing: ${file.path}`, LOG_LEVEL_VERBOSE); try { content = createBinaryBlob(cache); } catch (ex) { @@ -2550,7 +2553,7 @@ Or if you are sure know what had been happened, we can unlock the database from //upsert should locked const msg = `DB <- STORAGE (${datatype}) `; const isNotChanged = await serialized("file-" + fullPath, async () => { - if (recentlyTouched(file)) { + if (this.vaultAccess.recentlyTouched(file)) { return true; } try { @@ -2574,7 +2577,11 @@ Or if you are sure know what had been happened, we can unlock the database from } return false; }); - if (isNotChanged) return true; + if (isNotChanged) { + this.queuedFiles = this.queuedFiles.map((e) => ({ ...e, ...(e.entry._id == d._id ? { done: true } : {}) })); + Logger(msg + " Skip " + fullPath, LOG_LEVEL_VERBOSE); + return true; + } const ret = await this.localDatabase.putDBEntry(d, initialScan); this.queuedFiles = this.queuedFiles.map((e) => ({ ...e, ...(e.entry._id == d._id ? { done: true } : {}) })); @@ -2603,7 +2610,7 @@ Or if you are sure know what had been happened, we can unlock the database from } async resetLocalDatabase() { - clearTouched(); + this.vaultAccess.clearTouched(); await this.localDatabase.resetDatabase(); } @@ -2643,10 +2650,6 @@ Or if you are sure know what had been happened, we can unlock the database from return files.filter(file => !ignorePatterns.some(e => file.path.match(e))).filter(file => !targetFiles || (targetFiles && targetFiles.indexOf(file.path) !== -1)) } - async applyMTimeToFile(file: InternalFileInfo) { - await this.app.vault.adapter.append(file.path, "", { ctime: file.ctime, mtime: file.mtime }); - } - async resolveConflictByNewerEntry(path: FilePathWithPrefix) { const id = await this.path2id(path); const doc = await this.localDatabase.getRaw(id, { conflicts: true }); @@ -2673,7 +2676,7 @@ Or if you are sure know what had been happened, we can unlock the database from ignoreFiles = [] as string[] async readIgnoreFile(path: string) { try { - const file = await this.app.vault.adapter.read(path); + const file = await this.vaultAccess.adapterRead(path); const gitignore = file.split(/\r?\n/g); this.ignoreFileCache.set(path, gitignore); return gitignore; diff --git a/src/utils.ts b/src/utils.ts index da95aa5..077fbb4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,4 @@ -import { type DataWriteOptions, normalizePath, TFile, Platform, TAbstractFile, App, Plugin, type RequestUrlParam, requestUrl } from "./deps"; +import { normalizePath, TFile, Platform, TAbstractFile, App, Plugin, type RequestUrlParam, requestUrl } from "./deps"; import { path2id_base, id2path_base, isValidFilenameInLinux, isValidFilenameInDarwin, isValidFilenameInWidows, isValidFilenameInAndroid, stripAllPrefixes } from "./lib/src/path"; import { Logger } from "./lib/src/logger"; @@ -303,20 +303,6 @@ export function flattenObject(obj: Record, path: return ret; } -export function modifyFile(app: App, file: TFile, data: string | ArrayBuffer, options?: DataWriteOptions) { - if (typeof (data) === "string") { - return app.vault.modify(file, data, options); - } else { - return app.vault.modifyBinary(file, data, options); - } -} -export function createFile(app: App, path: string, data: string | ArrayBuffer, options?: DataWriteOptions): Promise { - if (typeof (data) === "string") { - return app.vault.create(path, data, options); - } else { - return app.vault.createBinary(path, data, options); - } -} export function isValidPath(filename: string) { if (Platform.isDesktop) { @@ -332,38 +318,10 @@ export function isValidPath(filename: string) { return isValidFilenameInWidows(filename); } -let touchedFiles: string[] = []; - -export function getAbstractFileByPath(path: FilePath): TAbstractFile | null { - // Disabled temporary. - return app.vault.getAbstractFileByPath(path); - // // Hidden API but so useful. - // // @ts-ignore - // if ("getAbstractFileByPathInsensitive" in app.vault && (app.vault.adapter?.insensitive ?? false)) { - // // @ts-ignore - // return app.vault.getAbstractFileByPathInsensitive(path); - // } else { - // return app.vault.getAbstractFileByPath(path); - // } -} export function trimPrefix(target: string, prefix: string) { return target.startsWith(prefix) ? target.substring(prefix.length) : target; } -export function touch(file: TFile | FilePath) { - const f = file instanceof TFile ? file : getAbstractFileByPath(file) as TFile; - const key = `${f.path}-${f.stat.mtime}-${f.stat.size}`; - touchedFiles.unshift(key); - touchedFiles = touchedFiles.slice(0, 100); -} -export function recentlyTouched(file: TFile) { - const key = `${file.path}-${file.stat.mtime}-${file.stat.size}`; - if (touchedFiles.indexOf(key) == -1) return false; - return true; -} -export function clearTouched() { - touchedFiles = []; -} /** * returns is internal chunk of file