Compare commits

...

2 Commits

Author SHA1 Message Date
vorotamoroz
b337a05b5a bump 2023-11-27 07:13:15 +00:00
vorotamoroz
9ea6bee9d1 - Fixed:
- No longer files are broken while rebuilding.
    - Now, Large binary files can be written correctly on a mobile platform.
    - Any decoding errors now make zero-byte files.
  - Modified:
    - All files are processed sequentially for each.
2023-11-27 06:55:55 +00:00
15 changed files with 222 additions and 135 deletions

View File

@@ -1,7 +1,7 @@
{ {
"id": "obsidian-livesync", "id": "obsidian-livesync",
"name": "Self-hosted LiveSync", "name": "Self-hosted LiveSync",
"version": "0.21.1", "version": "0.21.2",
"minAppVersion": "0.9.12", "minAppVersion": "0.9.12",
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
"author": "vorotamoroz", "author": "vorotamoroz",

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.21.1", "version": "0.21.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.21.1", "version": "0.21.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"diff-match-patch": "^1.0.5", "diff-match-patch": "^1.0.5",

View File

@@ -1,6 +1,6 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.21.1", "version": "0.21.2",
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
"main": "main.js", "main": "main.js",
"type": "module", "type": "module",

View File

@@ -418,9 +418,9 @@ export class ConfigSync extends LiveSyncCommands {
await this.ensureDirectoryEx(path); await this.ensureDirectoryEx(path);
if (!content) { if (!content) {
const dt = decodeBinary(f.data); const dt = decodeBinary(f.data);
await this.app.vault.adapter.writeBinary(path, dt); await this.vaultAccess.adapterWrite(path, dt);
} else { } 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`); Logger(`Applying ${f.filename} of ${data.displayName || data.name}.. Done`);
@@ -540,13 +540,13 @@ export class ConfigSync extends LiveSyncCommands {
recentProcessedInternalFiles = [] as string[]; recentProcessedInternalFiles = [] as string[];
async makeEntryFromFile(path: FilePath): Promise<false | PluginDataExFile> { async makeEntryFromFile(path: FilePath): Promise<false | PluginDataExFile> {
const stat = await this.app.vault.adapter.stat(path); const stat = await this.vaultAccess.adapterStat(path);
let version: string | undefined; let version: string | undefined;
let displayName: string | undefined; let displayName: string | undefined;
if (!stat) { if (!stat) {
return false; return false;
} }
const contentBin = await this.app.vault.adapter.readBinary(path); const contentBin = await this.vaultAccess.adapterReadBinary(path);
let content: string[]; let content: string[];
try { try {
content = await arrayBufferToBase64(contentBin); content = await arrayBufferToBase64(contentBin);
@@ -701,7 +701,7 @@ export class ConfigSync extends LiveSyncCommands {
async watchVaultRawEventsAsync(path: FilePath) { async watchVaultRawEventsAsync(path: FilePath) {
if (!this.settings.usePluginSync) return false; if (!this.settings.usePluginSync) return false;
if (!this.isTargetPath(path)) 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. // Make sure that target is a file.
if (stat && stat.type != "file") if (stat && stat.type != "file")
return false; return false;

View File

@@ -103,7 +103,7 @@ export class HiddenFileSync extends LiveSyncCommands {
Logger(`Hidden file skipped: ${path} is synchronized in customization sync.`, LOG_LEVEL_VERBOSE); Logger(`Hidden file skipped: ${path} is synchronized in customization sync.`, LOG_LEVEL_VERBOSE);
return; return;
} }
const stat = await this.app.vault.adapter.stat(path); const stat = await this.vaultAccess.adapterStat(path);
// sometimes folder is coming. // sometimes folder is coming.
if (stat && stat.type != "file") if (stat && stat.type != "file")
return; return;
@@ -171,12 +171,12 @@ export class HiddenFileSync extends LiveSyncCommands {
if (result) { if (result) {
Logger(`Object merge:${path}`, LOG_LEVEL_INFO); Logger(`Object merge:${path}`, LOG_LEVEL_INFO);
const filename = stripAllPrefixes(path); const filename = stripAllPrefixes(path);
const isExists = await this.app.vault.adapter.exists(filename); const isExists = await this.plugin.vaultAccess.adapterExists(filename);
if (!isExists) { if (!isExists) {
await this.ensureDirectoryEx(filename); await this.ensureDirectoryEx(filename);
} }
await this.app.vault.adapter.write(filename, result); await this.plugin.vaultAccess.adapterWrite(filename, result);
const stat = await this.app.vault.adapter.stat(filename); const stat = await this.vaultAccess.adapterStat(filename);
await this.storeInternalFileToDatabase({ path: filename, ...stat }); await this.storeInternalFileToDatabase({ path: filename, ...stat });
await this.extractInternalFileFromDatabase(filename); await this.extractInternalFileFromDatabase(filename);
await this.localDatabase.removeRaw(id, revB); await this.localDatabase.removeRaw(id, revB);
@@ -408,7 +408,7 @@ export class HiddenFileSync extends LiveSyncCommands {
const id = await this.path2id(file.path, ICHeader); const id = await this.path2id(file.path, ICHeader);
const prefixedFileName = addPrefix(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; let content: Blob;
try { try {
content = createBinaryBlob(contentBin); content = createBinaryBlob(contentBin);
@@ -511,7 +511,7 @@ export class HiddenFileSync extends LiveSyncCommands {
} }
async extractInternalFileFromDatabase(filename: FilePath, force = false) { 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); const prefixedFileName = addPrefix(filename, ICHeader);
if (await this.plugin.isIgnoredByIgnoreFiles(filename)) { if (await this.plugin.isIgnoredByIgnoreFiles(filename)) {
return; return;
@@ -534,7 +534,7 @@ export class HiddenFileSync extends LiveSyncCommands {
Logger(`STORAGE <x- DB:${filename}: deleted (hidden) Deleted on DB, but the file is already not found on storage.`); Logger(`STORAGE <x- DB:${filename}: deleted (hidden) Deleted on DB, but the file is already not found on storage.`);
} else { } else {
Logger(`STORAGE <x- DB:${filename}: deleted (hidden).`); Logger(`STORAGE <x- DB:${filename}: deleted (hidden).`);
await this.app.vault.adapter.remove(filename); await this.plugin.vaultAccess.adapterRemove(filename);
try { try {
//@ts-ignore internalAPI //@ts-ignore internalAPI
await this.app.vault.adapter.reconcileInternalFile(filename); await this.app.vault.adapter.reconcileInternalFile(filename);
@@ -547,7 +547,7 @@ export class HiddenFileSync extends LiveSyncCommands {
} }
if (!isExists) { if (!isExists) {
await this.ensureDirectoryEx(filename); await this.ensureDirectoryEx(filename);
await this.app.vault.adapter.writeBinary(filename, decodeBinary(fileOnDB.data), { mtime: fileOnDB.mtime, ctime: fileOnDB.ctime }); await this.plugin.vaultAccess.adapterWrite(filename, decodeBinary(fileOnDB.data), { mtime: fileOnDB.mtime, ctime: fileOnDB.ctime });
try { try {
//@ts-ignore internalAPI //@ts-ignore internalAPI
await this.app.vault.adapter.reconcileInternalFile(filename); await this.app.vault.adapter.reconcileInternalFile(filename);
@@ -558,13 +558,13 @@ export class HiddenFileSync extends LiveSyncCommands {
Logger(`STORAGE <-- DB:${filename}: written (hidden,new${force ? ", force" : ""})`); Logger(`STORAGE <-- DB:${filename}: written (hidden,new${force ? ", force" : ""})`);
return true; return true;
} else { } else {
const contentBin = await this.app.vault.adapter.readBinary(filename); const contentBin = await this.plugin.vaultAccess.adapterReadBinary(filename);
const content = await encodeBinary(contentBin); const content = await encodeBinary(contentBin);
if (isDocContentSame(content, fileOnDB.data) && !force) { if (isDocContentSame(content, fileOnDB.data) && !force) {
// Logger(`STORAGE <-- DB:${filename}: skipped (hidden) Not changed`, LOG_LEVEL_VERBOSE); // Logger(`STORAGE <-- DB:${filename}: skipped (hidden) Not changed`, LOG_LEVEL_VERBOSE);
return true; return true;
} }
await this.app.vault.adapter.writeBinary(filename, decodeBinary(fileOnDB.data), { mtime: fileOnDB.mtime, ctime: fileOnDB.ctime }); await this.plugin.vaultAccess.adapterWrite(filename, decodeBinary(fileOnDB.data), { mtime: fileOnDB.mtime, ctime: fileOnDB.ctime });
try { try {
//@ts-ignore internalAPI //@ts-ignore internalAPI
await this.app.vault.adapter.reconcileInternalFile(filename); await this.app.vault.adapter.reconcileInternalFile(filename);
@@ -613,12 +613,12 @@ export class HiddenFileSync extends LiveSyncCommands {
} }
} }
if (!keep && result) { if (!keep && result) {
const isExists = await this.app.vault.adapter.exists(filename); const isExists = await this.plugin.vaultAccess.adapterExists(filename);
if (!isExists) { if (!isExists) {
await this.ensureDirectoryEx(filename); await this.ensureDirectoryEx(filename);
} }
await this.app.vault.adapter.write(filename, result); await this.plugin.vaultAccess.adapterWrite(filename, result);
const stat = await this.app.vault.adapter.stat(filename); const stat = await this.plugin.vaultAccess.adapterStat(filename);
await this.storeInternalFileToDatabase({ path: filename, ...stat }, true); await this.storeInternalFileToDatabase({ path: filename, ...stat }, true);
try { try {
//@ts-ignore internalAPI //@ts-ignore internalAPI
@@ -657,7 +657,7 @@ export class HiddenFileSync extends LiveSyncCommands {
const files = filenames.filter(path => synchronisedInConfigSync.every(filterFile => !path.toLowerCase().startsWith(filterFile))).map(async (e) => { const files = filenames.filter(path => synchronisedInConfigSync.every(filterFile => !path.toLowerCase().startsWith(filterFile))).map(async (e) => {
return { return {
path: e as FilePath, path: e as FilePath,
stat: await this.app.vault.adapter.stat(e) stat: await this.plugin.vaultAccess.adapterStat(e)
}; };
}); });
const result: InternalFileInfo[] = []; const result: InternalFileInfo[] = [];

View File

@@ -186,18 +186,17 @@ export class PluginAndTheirSettings extends LiveSyncCommands {
} }
Logger(`Reading plugin:${m.name}(${m.id})`, LOG_LEVEL_VERBOSE); Logger(`Reading plugin:${m.name}(${m.id})`, LOG_LEVEL_VERBOSE);
const path = normalizePath(m.dir) + "/"; const path = normalizePath(m.dir) + "/";
const adapter = this.app.vault.adapter;
const files = ["manifest.json", "main.js", "styles.css", "data.json"]; const files = ["manifest.json", "main.js", "styles.css", "data.json"];
const pluginData: { [key: string]: string; } = {}; const pluginData: { [key: string]: string; } = {};
for (const file of files) { for (const file of files) {
const thePath = path + file; const thePath = path + file;
if (await adapter.exists(thePath)) { if (await this.plugin.vaultAccess.adapterExists(thePath)) {
pluginData[file] = await adapter.read(thePath); pluginData[file] = await this.plugin.vaultAccess.adapterRead(thePath);
} }
} }
let mtime = 0; let mtime = 0;
if (await adapter.exists(path + "/data.json")) { if (await this.plugin.vaultAccess.adapterExists(path + "/data.json")) {
mtime = (await adapter.stat(path + "/data.json")).mtime; mtime = (await this.plugin.vaultAccess.adapterStat(path + "/data.json")).mtime;
} }
const p: PluginDataEntry = { const p: PluginDataEntry = {
@@ -269,7 +268,6 @@ export class PluginAndTheirSettings extends LiveSyncCommands {
async applyPluginData(plugin: PluginDataEntry) { async applyPluginData(plugin: PluginDataEntry) {
await serialized("plugin-" + plugin.manifest.id, async () => { await serialized("plugin-" + plugin.manifest.id, async () => {
const pluginTargetFolderPath = normalizePath(plugin.manifest.dir) + "/"; const pluginTargetFolderPath = normalizePath(plugin.manifest.dir) + "/";
const adapter = this.app.vault.adapter;
// @ts-ignore // @ts-ignore
const stat = this.app.plugins.enabledPlugins.has(plugin.manifest.id) == true; const stat = this.app.plugins.enabledPlugins.has(plugin.manifest.id) == true;
if (stat) { if (stat) {
@@ -278,7 +276,7 @@ export class PluginAndTheirSettings extends LiveSyncCommands {
Logger(`Unload plugin:${plugin.manifest.id}`, LOG_LEVEL_NOTICE); Logger(`Unload plugin:${plugin.manifest.id}`, LOG_LEVEL_NOTICE);
} }
if (plugin.dataJson) 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); Logger("wrote:" + pluginTargetFolderPath + "data.json", LOG_LEVEL_NOTICE);
if (stat) { if (stat) {
// @ts-ignore // @ts-ignore
@@ -299,14 +297,13 @@ export class PluginAndTheirSettings extends LiveSyncCommands {
} }
const pluginTargetFolderPath = normalizePath(plugin.manifest.dir) + "/"; const pluginTargetFolderPath = normalizePath(plugin.manifest.dir) + "/";
const adapter = this.app.vault.adapter; if ((await this.plugin.vaultAccess.adapterExists(pluginTargetFolderPath)) === false) {
if ((await adapter.exists(pluginTargetFolderPath)) === false) { await this.app.vault.adapter.mkdir(pluginTargetFolderPath);
await adapter.mkdir(pluginTargetFolderPath);
} }
await adapter.write(pluginTargetFolderPath + "main.js", plugin.mainJs); await this.plugin.vaultAccess.adapterWrite(pluginTargetFolderPath + "main.js", plugin.mainJs);
await adapter.write(pluginTargetFolderPath + "manifest.json", plugin.manifestJson); await this.plugin.vaultAccess.adapterWrite(pluginTargetFolderPath + "manifest.json", plugin.manifestJson);
if (plugin.styleCss) if (plugin.styleCss)
await adapter.write(pluginTargetFolderPath + "styles.css", plugin.styleCss); await this.plugin.vaultAccess.adapterWrite(pluginTargetFolderPath + "styles.css", plugin.styleCss);
if (stat) { if (stat) {
// @ts-ignore // @ts-ignore
await this.app.plugins.loadPlugin(plugin.manifest.id); await this.app.plugins.loadPlugin(plugin.manifest.id);

View File

@@ -200,11 +200,11 @@ export class DocumentHistoryModal extends Modal {
Logger("Path is not valid to write content.", LOG_LEVEL_INFO); Logger("Path is not valid to write content.", LOG_LEVEL_INFO);
} }
if (this.currentDoc?.datatype == "plain") { 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); await focusFile(pathToWrite);
this.close(); this.close();
} else if (this.currentDoc?.datatype == "newnote") { } 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); await focusFile(pathToWrite);
this.close(); this.close();
} else { } else {

View File

@@ -2,12 +2,11 @@
import ObsidianLiveSyncPlugin from "./main"; import ObsidianLiveSyncPlugin from "./main";
import { onDestroy, onMount } from "svelte"; import { onDestroy, onMount } from "svelte";
import type { AnyEntry, FilePathWithPrefix } from "./lib/src/types"; 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 { diff_match_patch } from "./deps";
import { DocumentHistoryModal } from "./DocumentHistoryModal"; import { DocumentHistoryModal } from "./DocumentHistoryModal";
import { isPlainText, stripAllPrefixes } from "./lib/src/path"; import { isPlainText, stripAllPrefixes } from "./lib/src/path";
import { TFile } from "./deps"; import { TFile } from "./deps";
import { encodeBinary } from "./lib/src/strbin";
export let plugin: ObsidianLiveSyncPlugin; export let plugin: ObsidianLiveSyncPlugin;
let showDiffInfo = false; let showDiffInfo = false;
@@ -108,15 +107,15 @@
} }
if (rev == docA._rev) { if (rev == docA._rev) {
if (checkStorageDiff) { 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) { if (abs instanceof TFile) {
let result = false; let result = false;
if (isPlainText(docA.path)) { if (isPlainText(docA.path)) {
const data = await plugin.app.vault.read(abs); const data = await plugin.vaultAccess.adapterRead(abs);
result = isDocContentSame(data, doc.data); result = isDocContentSame(data, doc.data);
} else { } else {
const data = await plugin.app.vault.readBinary(abs); const data = await plugin.vaultAccess.adapterReadBinary(abs);
const dataEEncoded = await encodeBinary(data, plugin.settings.useV1); const dataEEncoded = createBinaryBlob(data);
result = isDocContentSame(dataEEncoded, doc.data); result = isDocContentSame(dataEEncoded, doc.data);
} }
if (result) { if (result) {

View File

@@ -14,6 +14,9 @@ export abstract class LiveSyncCommands {
get localDatabase() { get localDatabase() {
return this.plugin.localDatabase; return this.plugin.localDatabase;
} }
get vaultAccess() {
return this.plugin.vaultAccess;
}
id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix { id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
return this.plugin.id2path(id, entry, stripPrefix); return this.plugin.id2path(id, entry, stripPrefix);
} }

118
src/SerializedFileAccess.ts Normal file
View File

@@ -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<TFile> {
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 = [];
}
}

View File

@@ -1,9 +1,9 @@
import type { SerializedFileAccess } from "./SerializedFileAccess";
import { Plugin, TAbstractFile, TFile, TFolder } from "./deps"; import { Plugin, TAbstractFile, TFile, TFolder } from "./deps";
import { isPlainText, shouldBeIgnored } from "./lib/src/path"; import { isPlainText, shouldBeIgnored } from "./lib/src/path";
import { getGlobalStore } from "./lib/src/store"; import { getGlobalStore } from "./lib/src/store";
import { type FilePath, type ObsidianLiveSyncSettings } from "./lib/src/types"; import { type FilePath, type ObsidianLiveSyncSettings } from "./lib/src/types";
import { type FileEventItem, type FileEventType, type FileInfo, type InternalFileInfo, type queueItem } from "./types"; import { type FileEventItem, type FileEventType, type FileInfo, type InternalFileInfo, type queueItem } from "./types";
import { recentlyTouched } from "./utils";
export abstract class StorageEventManager { export abstract class StorageEventManager {
@@ -16,6 +16,7 @@ type LiveSyncForStorageEventManager = Plugin &
{ {
settings: ObsidianLiveSyncSettings settings: ObsidianLiveSyncSettings
ignoreFiles: string[], ignoreFiles: string[],
vaultAccess: SerializedFileAccess
} & { } & {
isTargetFile: (file: string | TAbstractFile) => Promise<boolean>, isTargetFile: (file: string | TAbstractFile) => Promise<boolean>,
procFileEvent: (applyBatch?: boolean) => Promise<any>, procFileEvent: (applyBatch?: boolean) => Promise<any>,
@@ -105,15 +106,14 @@ export class StorageEventManagerObsidian extends StorageEventManager {
let cache: null | string | ArrayBuffer; let cache: null | string | ArrayBuffer;
// new file or something changed, cache the changes. // new file or something changed, cache the changes.
if (file instanceof TFile && (type == "CREATE" || type == "CHANGED")) { if (file instanceof TFile && (type == "CREATE" || type == "CHANGED")) {
if (recentlyTouched(file)) { if (this.plugin.vaultAccess.recentlyTouched(file)) {
continue; continue;
} }
if (!isPlainText(file.name)) { if (!isPlainText(file.name)) {
cache = await this.plugin.app.vault.readBinary(file); cache = await this.plugin.vaultAccess.vaultReadBinary(file);
} else { } else {
// cache = await this.app.vault.read(file); cache = await this.plugin.vaultAccess.vaultCacheRead(file);
cache = await this.plugin.app.vault.cachedRead(file); if (!cache) cache = await this.plugin.vaultAccess.vaultRead(file);
if (!cache) cache = await this.plugin.app.vault.read(file);
} }
} }
if (type == "DELETE" || type == "RENAME") { if (type == "DELETE" || type == "RENAME") {

Submodule src/lib updated: f9aeaf6a2d...7e79c27035

View File

@@ -10,7 +10,7 @@ import { PouchDB } from "./lib/src/pouchdb-browser.js";
import { ConflictResolveModal } from "./ConflictResolveModal"; import { ConflictResolveModal } from "./ConflictResolveModal";
import { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab"; import { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab";
import { DocumentHistoryModal } from "./DocumentHistoryModal"; 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 { encrypt, tryDecrypt } from "./lib/src/e2ee_v2";
import { balanceChunkPurgedDBs, enableEncryption, isCloudantURI, isErrorOfMissingDoc, isValidRemoteCouchDBURI, purgeUnreferencedChunks } from "./lib/src/utils_couchdb"; import { balanceChunkPurgedDBs, enableEncryption, isCloudantURI, isErrorOfMissingDoc, isValidRemoteCouchDBURI, purgeUnreferencedChunks } from "./lib/src/utils_couchdb";
import { getGlobalStore, ObservableStore, observeStores } from "./lib/src/store"; 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 { LogPaneView, VIEW_TYPE_LOG } from "./LogPaneView";
import { mapAllTasksWithConcurrencyLimit, processAllTasksWithConcurrencyLimit } from "./lib/src/task"; import { mapAllTasksWithConcurrencyLimit, processAllTasksWithConcurrencyLimit } from "./lib/src/task";
import { LRUCache } from "./lib/src/LRUCache"; import { LRUCache } from "./lib/src/LRUCache";
import { SerializedFileAccess } from "./SerializedFileAccess";
setNoticeClass(Notice); setNoticeClass(Notice);
@@ -86,6 +87,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin
return !this.last_successful_post; return !this.last_successful_post;
} }
vaultAccess: SerializedFileAccess = new SerializedFileAccess(this.app);
_unloaded = false; _unloaded = false;
@@ -293,36 +295,36 @@ export default class ObsidianLiveSyncPlugin extends Plugin
} }
isRedFlagRaised(): boolean { isRedFlagRaised(): boolean {
const redflag = getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG)); const redflag = this.vaultAccess.getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG));
if (redflag != null) { if (redflag != null) {
return true; return true;
} }
return false; return false;
} }
isRedFlag2Raised(): boolean { isRedFlag2Raised(): boolean {
const redflag = getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG2)); const redflag = this.vaultAccess.getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG2));
if (redflag != null) { if (redflag != null) {
return true; return true;
} }
return false; return false;
} }
async deleteRedFlag2() { async deleteRedFlag2() {
const redflag = getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG2)); const redflag = this.vaultAccess.getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG2));
if (redflag != null) { if (redflag != null && redflag instanceof TFile) {
await app.vault.delete(redflag, true); await this.vaultAccess.delete(redflag, true);
} }
} }
isRedFlag3Raised(): boolean { isRedFlag3Raised(): boolean {
const redflag = getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG3)); const redflag = this.vaultAccess.getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG3));
if (redflag != null) { if (redflag != null) {
return true; return true;
} }
return false; return false;
} }
async deleteRedFlag3() { async deleteRedFlag3() {
const redflag = getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG3)); const redflag = this.vaultAccess.getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG3));
if (redflag != null) { if (redflag != null && redflag instanceof TFile) {
await app.vault.delete(redflag, true); 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() { async onload() {
logStore.subscribe(e => this.addLog(e.message, e.level, e.key)); logStore.subscribe(e => this.addLog(e.message, e.level, e.key));
Logger("loading plugin"); Logger("loading plugin");
this.addSettingTab(new ObsidianLiveSyncSettingTab(this.app, this)); 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.addOnHiddenFileSync.watchVaultRawEventsAsync(file.path);
await this.addOnConfigSync.watchVaultRawEventsAsync(file.path); await this.addOnConfigSync.watchVaultRawEventsAsync(file.path);
} else { } else {
const targetFile = this.app.vault.getAbstractFileByPath(file.path); const targetFile = this.vaultAccess.getAbstractFileByPath(file.path);
if (!(targetFile instanceof TFile)) { if (!(targetFile instanceof TFile)) {
Logger(`Target file was not found: ${file.path}`, LOG_LEVEL_INFO); Logger(`Target file was not found: ${file.path}`, LOG_LEVEL_INFO);
continue; continue;
@@ -1184,7 +1185,7 @@ Note: We can always able to read V1 format. It will be progressively converted.
if (this.settings?.writeLogToTheFile) { if (this.settings?.writeLogToTheFile) {
const time = now.toISOString().split("T")[0]; const time = now.toISOString().split("T")[0];
const logDate = `${PREFIXMD_LOGFILE}${time}.md`; const logDate = `${PREFIXMD_LOGFILE}${time}.md`;
const file = this.app.vault.getAbstractFileByPath(normalizePath(logDate)); const file = this.vaultAccess.getAbstractFileByPath(normalizePath(logDate));
if (!file) { if (!file) {
this.app.vault.adapter.append(normalizePath(logDate), "```\n"); 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 { try {
let outFile; let outFile;
if (mode == "create") { 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 { } else {
await modifyFile(this.app,file, writeData, { ctime: doc.ctime, mtime: doc.mtime }); await this.vaultAccess.vaultModify(file, writeData, { ctime: doc.ctime, mtime: doc.mtime });
outFile = getAbstractFileByPath(getPathFromTFile(file)) as TFile; outFile = this.vaultAccess.getAbstractFileByPath(getPathFromTFile(file)) as TFile;
} }
Logger(msg + path); Logger(msg + path);
touch(outFile); this.vaultAccess.touch(outFile);
this.app.vault.trigger(mode, outFile); this.app.vault.trigger(mode, outFile);
} catch (ex) { } catch (ex) {
@@ -1327,9 +1330,9 @@ Note: We can always able to read V1 format. It will be progressively converted.
} }
const dir = file.parent; const dir = file.parent;
if (this.settings.trashInsteadDelete) { if (this.settings.trashInsteadDelete) {
await this.app.vault.trash(file, false); await this.vaultAccess.trash(file, false);
} else { } else {
await this.app.vault.delete(file, true); await this.vaultAccess.delete(file, true);
} }
Logger(`xxx <- STORAGE (deleted) ${file.path}`); Logger(`xxx <- STORAGE (deleted) ${file.path}`);
Logger(`files: ${dir.children.length}`); 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) { async handleDBChangedAsync(change: EntryBody) {
const targetFile = getAbstractFileByPath(this.getPathWithoutPrefix(change)); const targetFile = this.vaultAccess.getAbstractFileByPath(this.getPathWithoutPrefix(change));
if (targetFile == null) { if (targetFile == null) {
if (change._deleted || change.deleted) { if (change._deleted || change.deleted) {
return; return;
@@ -1518,7 +1521,7 @@ Note: We can always able to read V1 format. It will be progressively converted.
} }
if ((!isInternalMetadata(doc._id)) && skipOldFile) { if ((!isInternalMetadata(doc._id)) && skipOldFile) {
const info = getAbstractFileByPath(stripAllPrefixes(path)); const info = this.vaultAccess.getAbstractFileByPath(stripAllPrefixes(path));
if (info && info instanceof TFile) { if (info && info instanceof TFile) {
const localMtime = ~~(info.stat.mtime / 1000); 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. // remove conflicted revision.
await this.localDatabase.deleteDBEntry(path, { rev: conflictedRev }); 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) { if (file) {
await this.app.vault.modify(file, p); await this.vaultAccess.vaultModify(file, p);
await this.updateIntoDB(file); await this.updateIntoDB(file);
} else { } else {
const newFile = await this.app.vault.create(path, p); const newFile = await this.vaultAccess.vaultCreate(path, p);
await this.updateIntoDB(newFile); await this.updateIntoDB(newFile);
} }
await this.pullFile(path); 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. // delete conflicted revision and write a new file, store it again.
const p = conflictCheckResult.diff.map((e) => e[1]).join(""); const p = conflictCheckResult.diff.map((e) => e[1]).join("");
await this.localDatabase.deleteDBEntry(filename, { rev: testDoc._conflicts[0] }); 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) { if (file) {
await this.app.vault.modify(file, p); await this.vaultAccess.vaultModify(file, p);
await this.updateIntoDB(file); await this.updateIntoDB(file);
} else { } else {
const newFile = await this.app.vault.create(filename, p); const newFile = await this.vaultAccess.vaultCreate(filename, p);
await this.updateIntoDB(newFile); await this.updateIntoDB(newFile);
} }
await this.pullFile(filename); 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[]; const checkFiles = JSON.parse(JSON.stringify(this.conflictedCheckFiles)) as FilePath[];
for (const filename of checkFiles) { for (const filename of checkFiles) {
try { try {
const file = getAbstractFileByPath(filename); const file = this.vaultAccess.getAbstractFileByPath(filename);
if (file != null && file instanceof TFile) { if (file != null && file instanceof TFile) {
await this.showIfConflicted(getPathFromTFile(file)); 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) { 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 (!await this.isTargetFile(filename)) return;
if (targetFile == null) { if (targetFile == null) {
//have to create; //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}`) throw new Error(`Missing doc:${(file as any).path}`)
} }
if (!(file instanceof TFile) && "path" in file) { 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) { if (w instanceof TFile) {
file = w; file = w;
} else { } else {
@@ -2504,7 +2507,7 @@ Or if you are sure know what had been happened, we can unlock the database from
if (!cache) { if (!cache) {
if (!isPlainText(file.name)) { if (!isPlainText(file.name)) {
Logger(`Reading : ${file.path}`, LOG_LEVEL_VERBOSE); 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); Logger(`Processing: ${file.path}`, LOG_LEVEL_VERBOSE);
try { try {
content = createBinaryBlob(contentBin); 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"; datatype = "newnote";
} else { } else {
content = createTextBlob(await this.app.vault.read(file)); content = createTextBlob(await this.vaultAccess.vaultRead(file));
datatype = "plain"; datatype = "plain";
} }
} else { } else {
if (cache instanceof ArrayBuffer) { if (cache instanceof ArrayBuffer) {
Logger(`Processing: ${file.path}`, LOG_LEVEL_VERBOSE); Logger(`Cache Processing: ${file.path}`, LOG_LEVEL_VERBOSE);
try { try {
content = createBinaryBlob(cache); content = createBinaryBlob(cache);
} catch (ex) { } 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 //upsert should locked
const msg = `DB <- STORAGE (${datatype}) `; const msg = `DB <- STORAGE (${datatype}) `;
const isNotChanged = await serialized("file-" + fullPath, async () => { const isNotChanged = await serialized("file-" + fullPath, async () => {
if (recentlyTouched(file)) { if (this.vaultAccess.recentlyTouched(file)) {
return true; return true;
} }
try { try {
@@ -2574,7 +2577,11 @@ Or if you are sure know what had been happened, we can unlock the database from
} }
return false; 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); const ret = await this.localDatabase.putDBEntry(d, initialScan);
this.queuedFiles = this.queuedFiles.map((e) => ({ ...e, ...(e.entry._id == d._id ? { done: true } : {}) })); 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() { async resetLocalDatabase() {
clearTouched(); this.vaultAccess.clearTouched();
await this.localDatabase.resetDatabase(); 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)) 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) { async resolveConflictByNewerEntry(path: FilePathWithPrefix) {
const id = await this.path2id(path); const id = await this.path2id(path);
const doc = await this.localDatabase.getRaw<AnyEntry>(id, { conflicts: true }); const doc = await this.localDatabase.getRaw<AnyEntry>(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[] ignoreFiles = [] as string[]
async readIgnoreFile(path: string) { async readIgnoreFile(path: string) {
try { try {
const file = await this.app.vault.adapter.read(path); const file = await this.vaultAccess.adapterRead(path);
const gitignore = file.split(/\r?\n/g); const gitignore = file.split(/\r?\n/g);
this.ignoreFileCache.set(path, gitignore); this.ignoreFileCache.set(path, gitignore);
return gitignore; return gitignore;

View File

@@ -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 { path2id_base, id2path_base, isValidFilenameInLinux, isValidFilenameInDarwin, isValidFilenameInWidows, isValidFilenameInAndroid, stripAllPrefixes } from "./lib/src/path";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
@@ -303,20 +303,6 @@ export function flattenObject(obj: Record<string | number | symbol, any>, path:
return ret; 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<TFile> {
if (typeof (data) === "string") {
return app.vault.create(path, data, options);
} else {
return app.vault.createBinary(path, data, options);
}
}
export function isValidPath(filename: string) { export function isValidPath(filename: string) {
if (Platform.isDesktop) { if (Platform.isDesktop) {
@@ -332,38 +318,10 @@ export function isValidPath(filename: string) {
return isValidFilenameInWidows(filename); 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) { export function trimPrefix(target: string, prefix: string) {
return target.startsWith(prefix) ? target.substring(prefix.length) : target; 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 * returns is internal chunk of file

View File

@@ -6,6 +6,15 @@ It will be addressed soon. Please be patient if you are using filesystem-livesyn
#### Version history #### Version history
- 0.21.2
- IMPORTANT NOTICE: **0.21.1 CONTAINS A BUG WHILE REBUILDING THE DATABASE. IF YOU HAVE BEEN REBUILT, PLEASE MAKE SURE THAT ALL FILES ARE SANE.**
- This has been fixed in this version.
- Fixed:
- No longer files are broken while rebuilding.
- Now, Large binary files can be written correctly on a mobile platform.
- Any decoding errors now make zero-byte files.
- Modified:
- All files are processed sequentially for each.
- 0.21.1 - 0.21.1
- Fixed: - Fixed:
- No more infinity loops on larger files. - No more infinity loops on larger files.