Compare commits

...

9 Commits

Author SHA1 Message Date
vorotamoroz
420c3b94df bump 2022-11-23 10:34:04 +09:00
vorotamoroz
239c087132 framework and dependency upgraded. 2022-11-23 10:27:12 +09:00
vorotamoroz
d1a633c799 bump 2022-11-07 17:26:40 +09:00
vorotamoroz
1c07cd92fc - Fixed
- Automatic (temporary) batch size adjustment has been restored to work correctly.
  - Chunk splitting has been backed to the previous behaviour for saving them correctly.
- Improved
  - Corrupted chunks will be detected automatically.
  - Now on the case-insensitive system, `aaa.md` and `AAA.md` will be treated as the same file or path at applying changesets.
2022-11-07 17:26:33 +09:00
vorotamoroz
adc84d53b1 bump again 2022-10-27 17:43:00 +09:00
vorotamoroz
c3a762ceed bump 2022-10-27 17:42:15 +09:00
vorotamoroz
5945638633 Fixed:
- Conflict detection and merging of deleted files.
- Fixed wrong logs.
- Fix redundant logs.

Implemented
- Automatically deletion of old metadata.
2022-10-27 17:41:26 +09:00
vorotamoroz
331acd463d bump 2022-10-25 11:48:21 +09:00
vorotamoroz
9d4f41bbf9 Fixed failure of detection 2022-10-25 11:47:02 +09:00
10 changed files with 1308 additions and 1715 deletions

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-livesync",
"name": "Self-hosted LiveSync",
"version": "0.16.3",
"version": "0.16.7",
"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.",
"author": "vorotamoroz",

2725
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "obsidian-livesync",
"version": "0.16.3",
"version": "0.16.7",
"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",
"type": "module",
@@ -13,34 +13,30 @@
"author": "vorotamoroz",
"license": "MIT",
"devDependencies": {
"@rollup/plugin-commonjs": "^18.0.0",
"@rollup/plugin-node-resolve": "^11.2.1",
"@rollup/plugin-typescript": "^8.2.1",
"@types/diff-match-patch": "^1.0.32",
"@types/pouchdb": "^6.4.0",
"@types/pouchdb-browser": "^6.1.3",
"@typescript-eslint/eslint-plugin": "^5.7.0",
"@typescript-eslint/parser": "^5.0.0",
"builtin-modules": "^3.2.0",
"esbuild": "0.13.12",
"esbuild-svelte": "^0.7.0",
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.25.2",
"@typescript-eslint/eslint-plugin": "^5.44.0",
"@typescript-eslint/parser": "^5.44.0",
"builtin-modules": "^3.3.0",
"esbuild": "0.15.15",
"esbuild-svelte": "^0.7.3",
"eslint": "^8.28.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.26.0",
"obsidian": "^0.16.3",
"postcss": "^8.4.14",
"postcss-load-config": "^3.1.4",
"rollup": "^2.32.1",
"svelte": "^3.49.0",
"postcss": "^8.4.19",
"postcss-load-config": "^4.0.1",
"svelte": "^3.53.1",
"svelte-preprocess": "^4.10.7",
"tslib": "^2.2.0",
"typescript": "^4.2.4"
"tslib": "^2.4.1",
"typescript": "^4.9.3"
},
"dependencies": {
"diff-match-patch": "^1.0.5",
"esbuild": "0.13.12",
"esbuild-svelte": "^0.7.0",
"idb": "^7.0.2",
"esbuild": "0.15.15",
"esbuild-svelte": "^0.7.3",
"idb": "^7.1.1",
"xxhash-wasm": "^0.4.2"
}
}

View File

@@ -1,31 +0,0 @@
import typescript from "@rollup/plugin-typescript";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
const isProd = process.env.BUILD === "production";
const banner = `/*
THIS IS A GENERATED/BUNDLED FILE BY ROLLUP
if you want to view the source visit the plugins github repository
*/
`;
export default {
input: "./src/main.ts",
output: {
dir: ".",
sourcemap: "inline",
sourcemapExcludeSources: isProd,
format: "cjs",
exports: "default",
banner,
},
external: ["obsidian"],
plugins: [
typescript({ exclude: ["pouchdb-browser.js", "pouchdb-browser-webpack"] }),
nodeResolve({
browser: true,
}),
commonjs(),
],
};

View File

@@ -38,8 +38,8 @@ export class ConflictResolveModal extends Modal {
diff = diff.replace(/\n/g, "<br>");
div.innerHTML = diff;
const div2 = contentEl.createDiv("");
const date1 = new Date(this.result.left.mtime).toLocaleString();
const date2 = new Date(this.result.right.mtime).toLocaleString();
const date1 = new Date(this.result.left.mtime).toLocaleString() + (this.result.left.deleted ? " (Deleted)" : "");
const date2 = new Date(this.result.right.mtime).toLocaleString() + (this.result.right.deleted ? " (Deleted)" : "");
div2.innerHTML = `
<span class='deleted'>A:${date1}</span><br /><span class='added'>B:${date2}</span><br>
`;

View File

@@ -35,7 +35,7 @@ export class LocalPouchDB extends LocalPouchDBBase {
last_successful_post = false;
getLastPostFailedBySize() {
return this.last_successful_post;
return !this.last_successful_post;
}
async fetchByAPI(request: RequestUrlParam): Promise<RequestUrlResponse> {
const ret = await requestUrl(request);
@@ -75,7 +75,7 @@ export class LocalPouchDB extends LocalPouchDBBase {
const method = opts.method ?? "GET";
if (opts.body) {
const opts_length = opts.body.toString().length;
if (opts_length > 1024 * 1024 * 10) {
if (opts_length > 1000 * 1000 * 10) {
// over 10MB
if (isCloudantURI(uri)) {
this.last_successful_post = false;

View File

@@ -813,6 +813,24 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
})
}
);
new Setting(containerGeneralSettingsEl)
.setName("Delete old metadata of deleted files on start-up")
.setClass("wizardHidden")
.setDesc("(Days passed, 0 to disable automatic-deletion)")
.addText((text) => {
text.setPlaceholder("")
.setValue(this.plugin.settings.automaticallyDeleteMetadataOfDeletedFiles + "")
.onChange(async (value) => {
let v = Number(value);
if (isNaN(v)) {
v = 0;
}
this.plugin.settings.automaticallyDeleteMetadataOfDeletedFiles = v;
await this.plugin.saveSettings();
});
text.inputEl.setAttribute("type", "number");
});
addScreenElement("20", containerGeneralSettingsEl);
const containerSyncSettingEl = containerEl.createDiv();

Submodule src/lib updated: d73b96ba2a...b848f9cc72

View File

@@ -44,6 +44,16 @@ const ICHeaderEnd = "i;";
const ICHeaderLength = ICHeader.length;
const FileWatchEventQueueMax = 10;
function getAbstractFileByPath(path: string): TAbstractFile | null {
// Hidden API but so useful.
if ("getAbstractFileByPathInsensitive" in app.vault && (app.vault.adapter?.insensitive ?? false)) {
// @ts-ignore
return app.vault.getAbstractFileByPathInsensitive(path);
} else {
return app.vault.getAbstractFileByPath(path);
}
}
/**
* returns is internal chunk of file
* @param str ID
@@ -97,7 +107,7 @@ const askString = (app: App, title: string, key: string, placeholder: string): P
};
let touchedFiles: string[] = [];
function touch(file: TFile | string) {
const f = file instanceof TFile ? file : app.vault.getAbstractFileByPath(file) as TFile;
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);
@@ -147,7 +157,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
isRedFlagRaised(): boolean {
const redflag = this.app.vault.getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG));
const redflag = getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG));
if (redflag != null) {
return true;
}
@@ -174,7 +184,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
nextKey = `${row.id}\u{10ffff}`;
if (!("type" in doc)) continue;
if (doc.type == "newnote" || doc.type == "plain") {
// const docId = doc._id.startsWith("i:") ? doc._id.substring("i:".length) : doc._id;
notes.push({ path: id2path(doc._id), mtime: doc.mtime });
}
if (isChunk(nextKey)) {
@@ -203,8 +212,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
nextKey = `${row.id}\u{10ffff}`;
if (!("_conflicts" in doc)) continue;
if (isInternalChunk(row.id)) continue;
if (doc._deleted) continue;
if ("deleted" in doc && doc.deleted) continue;
// We have to check also deleted files.
// if (doc._deleted) continue;
// if ("deleted" in doc && doc.deleted) continue;
if (doc.type == "newnote" || doc.type == "plain") {
// const docId = doc._id.startsWith("i:") ? doc._id.substring("i:".length) : doc._id;
notes.push({ path: id2path(doc._id), mtime: doc.mtime });
@@ -226,11 +236,52 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (isInternalChunk(target)) {
//NOP
} else {
await this.showIfConflicted(this.app.vault.getAbstractFileByPath(target) as TFile);
await this.showIfConflicted(target);
}
}
}
async collectDeletedFiles() {
const pageLimit = 1000;
let nextKey = "";
const limitDays = this.settings.automaticallyDeleteMetadataOfDeletedFiles;
if (limitDays <= 0) return;
Logger(`Checking expired file history`);
const limit = Date.now() - (86400 * 1000 * limitDays);
const notes: { path: string, mtime: number, ttl: number, doc: PouchDB.Core.ExistingDocument<EntryDoc & PouchDB.Core.AllDocsMeta> }[] = [];
do {
const docs = await this.localDatabase.localDatabase.allDocs({ limit: pageLimit, startkey: nextKey, conflicts: true, include_docs: true });
nextKey = "";
for (const row of docs.rows) {
const doc = row.doc;
nextKey = `${row.id}\u{10ffff}`;
if (doc.type == "newnote" || doc.type == "plain") {
if (doc.deleted && (doc.mtime - limit) < 0) {
notes.push({ path: id2path(doc._id), mtime: doc.mtime, ttl: (doc.mtime - limit) / 1000 / 86400, doc: doc });
}
}
if (isChunk(nextKey)) {
// skip the chunk zone.
nextKey = CHeaderEnd;
}
}
} while (nextKey != "");
if (notes.length == 0) {
Logger("There are no old documents");
Logger(`Checking expired file history done`);
return;
}
for (const v of notes) {
Logger(`Deletion history expired: ${v.path}`);
const delDoc = v.doc;
delDoc._deleted = true;
// console.dir(delDoc);
await this.localDatabase.localDatabase.put(delDoc);
}
Logger(`Checking expired file history done`);
}
async onload() {
setLogger(this.addLog.bind(this)); // Logger moved to global.
Logger("loading plugin");
@@ -528,7 +579,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
id: "livesync-checkdoc-conflicted",
name: "Resolve if conflicted.",
editorCallback: async (editor: Editor, view: MarkdownView) => {
await this.showIfConflicted(view.file);
await this.showIfConflicted(view.file.path);
},
});
@@ -921,7 +972,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (this.settings.syncOnFileOpen && !this.suspended) {
await this.replicate();
}
await this.showIfConflicted(file);
await this.showIfConflicted(file.path);
}
async applyBatchChange() {
@@ -1009,7 +1060,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
for (const i of newFiles) {
try {
const newFilePath = normalizePath(this.getFilePath(i));
const newFile = this.app.vault.getAbstractFileByPath(newFilePath);
const newFile = getAbstractFileByPath(newFilePath);
if (newFile instanceof TFile) {
Logger(`save ${newFile.path} into db`);
await this.updateIntoDB(newFile);
@@ -1170,7 +1221,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
touch(newFile);
this.app.vault.trigger("create", newFile);
} catch (ex) {
Logger(msg + "ERROR, Could not parse: " + path + "(" + doc.datatype + ")", LOG_LEVEL.NOTICE);
Logger(msg + "ERROR, Could not create: " + path + "(" + doc.datatype + ")", LOG_LEVEL.NOTICE);
Logger(ex, LOG_LEVEL.VERBOSE);
}
} else {
@@ -1237,7 +1288,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
await this.app.vault.modifyBinary(file, bin, { ctime: doc.ctime, mtime: doc.mtime });
// this.batchFileChange = this.batchFileChange.filter((e) => e != file.path);
Logger(msg + path);
const xf = this.app.vault.getAbstractFileByPath(file.path) as TFile;
const xf = getAbstractFileByPath(file.path) as TFile;
touch(xf);
this.app.vault.trigger("modify", xf);
} catch (ex) {
@@ -1254,7 +1305,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
await this.app.vault.modify(file, doc.data, { ctime: doc.ctime, mtime: doc.mtime });
Logger(msg + path);
// this.batchFileChange = this.batchFileChange.filter((e) => e != file.path);
const xf = this.app.vault.getAbstractFileByPath(file.path) as TFile;
const xf = getAbstractFileByPath(file.path) as TFile;
touch(xf);
this.app.vault.trigger("modify", xf);
} catch (ex) {
@@ -1304,7 +1355,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
async handleDBChangedAsync(change: EntryBody) {
const targetFile = this.app.vault.getAbstractFileByPath(id2path(change._id));
const targetFile = getAbstractFileByPath(id2path(change._id));
if (targetFile == null) {
if (change._deleted || change.deleted) {
return;
@@ -1420,7 +1471,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (!this.isTargetFile(id2path(doc._id))) return;
const skipOldFile = this.settings.skipOlderFilesOnSync && false; //patched temporary.
if ((!isInternalChunk(doc._id)) && skipOldFile) {
const info = this.app.vault.getAbstractFileByPath(id2path(doc._id));
const info = getAbstractFileByPath(id2path(doc._id));
if (info && info instanceof TFile) {
const localMtime = ~~((info as TFile).stat.mtime / 1000);
@@ -1727,10 +1778,18 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
Logger("Initializing", LOG_LEVEL.NOTICE, "syncAll");
}
await this.collectDeletedFiles();
const filesStorage = this.app.vault.getFiles().filter(e => this.isTargetFile(e));
const filesStorageName = filesStorage.map((e) => e.path);
const wf = await this.localDatabase.localDatabase.allDocs();
const filesDatabase = wf.rows.filter((e) => !isChunk(e.id) && !isPluginChunk(e.id) && e.id != "obsydian_livesync_version").filter(e => isValidPath(e.id)).map((e) => id2path(e.id)).filter(e => this.isTargetFile(e));
const filesDatabase = wf.rows.filter((e) =>
!isChunk(e.id) &&
!isPluginChunk(e.id) &&
e.id != "obsydian_livesync_version" &&
e.id != "_design/replicate"
)
.filter(e => isValidPath(e.id)).map((e) => id2path(e.id)).filter(e => this.isTargetFile(e));
const isInitialized = await (this.localDatabase.kvDB.get<boolean>("initialized")) || false;
// Make chunk bigger if it is the initial scan. There must be non-active docs.
if (filesDatabase.length == 0 && !isInitialized) {
@@ -1748,28 +1807,28 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.setStatusBarText(`UPDATE DATABASE`);
const runAll = async<T>(procedureName: string, objects: T[], callback: (arg: T) => Promise<void>) => {
const count = objects.length;
// const count = objects.length;
Logger(procedureName);
let i = 0;
// let i = 0;
const semaphore = Semaphore(10);
Logger(`${procedureName} exec.`);
// Logger(`${procedureName} exec.`);
if (!this.localDatabase.isReady) throw Error("Database is not ready!");
const processes = objects.map(e => (async (v) => {
const releaser = await semaphore.acquire(1, procedureName);
try {
await callback(v);
i++;
if (i % 50 == 0) {
const notify = `${procedureName} : ${i}/${count}`;
if (showingNotice) {
Logger(notify, LOG_LEVEL.NOTICE, "syncAll");
} else {
Logger(notify);
}
this.setStatusBarText(notify);
}
// i++;
// if (i % 50 == 0) {
// const notify = `${procedureName} : ${i}/${count}`;
// if (showingNotice) {
// Logger(notify, LOG_LEVEL.NOTICE, "syncAll");
// } else {
// Logger(notify);
// }
// this.setStatusBarText(notify);
// }
} catch (ex) {
Logger(`Error while ${procedureName}`, LOG_LEVEL.NOTICE);
Logger(ex);
@@ -1785,18 +1844,19 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
await runAll("UPDATE DATABASE", onlyInStorage, async (e) => {
Logger(`Update into ${e.path}`);
await this.updateIntoDB(e, initialScan);
});
if (!initialScan) {
await runAll("UPDATE STORAGE", onlyInDatabase, async (e) => {
const w = await this.localDatabase.getDBEntryMeta(e);
if (w) {
const w = await this.localDatabase.getDBEntryMeta(e, {}, true);
if (w && !(w.deleted || w._deleted)) {
Logger(`Check or pull from db:${e}`);
await this.pullFile(e, filesStorage, false, null, false);
Logger(`Check or pull from db:${e} OK`);
} else if (w) {
Logger(`Deletion history skipped: ${e}`, LOG_LEVEL.VERBOSE);
} else {
Logger(`entry not found, maybe deleted (it is normal behavior):${e}`);
Logger(`entry not found: ${e}`);
}
});
}
@@ -1872,7 +1932,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
// --> conflict resolving
async getConflictedDoc(path: string, rev: string): Promise<false | diff_result_leaf> {
try {
const doc = await this.localDatabase.getDBEntry(path, { rev: rev }, false, false);
const doc = await this.localDatabase.getDBEntry(path, { rev: rev }, false, false, true);
if (doc === false) return false;
let data = doc.data;
if (doc.datatype == "newnote") {
@@ -1881,6 +1941,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
data = doc.data;
}
return {
deleted: doc.deleted || doc._deleted,
ctime: doc.ctime,
mtime: doc.mtime,
rev: rev,
@@ -1900,7 +1961,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
* @returns true -> resolved, false -> nothing to do, or check result.
*/
async getConflictedStatus(path: string): Promise<diff_check_result> {
const test = await this.localDatabase.getDBEntry(path, { conflicts: true }, false, false);
const test = await this.localDatabase.getDBEntry(path, { conflicts: true }, false, false, true);
if (test === false) return false;
if (test == null) return false;
if (!test._conflicts) return false;
@@ -1920,8 +1981,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
Logger(`could not get old revisions, automatically used newer one:${path}`, LOG_LEVEL.NOTICE);
return true;
}
// first,check for same contents
if (leftLeaf.data == rightLeaf.data) {
// first, check for same contents and deletion status.
if (leftLeaf.data == rightLeaf.data && leftLeaf.deleted == rightLeaf.deleted) {
let leaf = leftLeaf;
if (leftLeaf.mtime > rightLeaf.mtime) {
leaf = rightLeaf;
@@ -1955,11 +2016,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
};
}
showMergeDialog(file: TFile, conflictCheckResult: diff_result): Promise<boolean> {
showMergeDialog(filename: string, conflictCheckResult: diff_result): Promise<boolean> {
return new Promise((res, rej) => {
Logger("open conflict dialog", LOG_LEVEL.VERBOSE);
new ConflictResolveModal(this.app, conflictCheckResult, async (selected) => {
const testDoc = await this.localDatabase.getDBEntry(file.path, { conflicts: true });
const testDoc = await this.localDatabase.getDBEntry(filename, { conflicts: true }, false, false, true);
if (testDoc === false) {
Logger("Missing file..", LOG_LEVEL.VERBOSE);
return res(true);
@@ -1974,25 +2035,31 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
//concat both,
// write data,and delete both old rev.
const p = conflictCheckResult.diff.map((e) => e[1]).join("");
await this.localDatabase.deleteDBEntry(file.path, { rev: conflictCheckResult.left.rev });
await this.localDatabase.deleteDBEntry(file.path, { rev: conflictCheckResult.right.rev });
await this.app.vault.modify(file, p);
await this.updateIntoDB(file);
await this.pullFile(file.path);
await this.localDatabase.deleteDBEntry(filename, { rev: conflictCheckResult.left.rev });
await this.localDatabase.deleteDBEntry(filename, { rev: conflictCheckResult.right.rev });
const file = getAbstractFileByPath(filename) as TFile;
if (file) {
await this.app.vault.modify(file, p);
await this.updateIntoDB(file);
} else {
const newFile = await this.app.vault.create(filename, p);
await this.updateIntoDB(newFile);
}
await this.pullFile(filename);
Logger("concat both file");
setTimeout(() => {
//resolved, check again.
this.showIfConflicted(file);
this.showIfConflicted(filename);
}, 500);
} else if (toDelete == null) {
Logger("Leave it still conflicted");
} else {
Logger(`Conflict resolved:${file.path}`);
await this.localDatabase.deleteDBEntry(file.path, { rev: toDelete });
await this.pullFile(file.path, null, true, toKeep);
Logger(`Conflict resolved:${filename}`);
await this.localDatabase.deleteDBEntry(filename, { rev: toDelete });
await this.pullFile(filename, null, true, toKeep);
setTimeout(() => {
//resolved, check again.
this.showIfConflicted(file);
this.showIfConflicted(filename);
}, 500);
}
@@ -2017,9 +2084,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
const checkFiles = JSON.parse(JSON.stringify(this.conflictedCheckFiles)) as string[];
for (const filename of checkFiles) {
try {
const file = this.app.vault.getAbstractFileByPath(filename);
const file = getAbstractFileByPath(filename);
if (file != null && file instanceof TFile) {
await this.showIfConflicted(file);
await this.showIfConflicted(file.path);
}
} catch (ex) {
Logger(ex);
@@ -2028,9 +2095,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}, 1000);
}
async showIfConflicted(file: TFile) {
async showIfConflicted(filename: string) {
await runWithLock("conflicted", false, async () => {
const conflictCheckResult = await this.getConflictedStatus(file.path);
const conflictCheckResult = await this.getConflictedStatus(filename);
if (conflictCheckResult === false) {
//nothing to do.
return;
@@ -2039,17 +2106,17 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
//auto resolved, but need check again;
Logger("conflict:Automatically merged, but we have to check it again");
setTimeout(() => {
this.showIfConflicted(file);
this.showIfConflicted(filename);
}, 500);
return;
}
//there conflicts, and have to resolve ;
await this.showMergeDialog(file, conflictCheckResult);
await this.showMergeDialog(filename, conflictCheckResult);
});
}
async pullFile(filename: string, fileList?: TFile[], force?: boolean, rev?: string, waitForReady = true) {
const targetFile = this.app.vault.getAbstractFileByPath(id2path(filename));
const targetFile = getAbstractFileByPath(id2path(filename));
if (!this.isTargetFile(id2path(filename))) return;
if (targetFile == null) {
//have to create;
@@ -2080,7 +2147,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
throw new Error(`Missing doc:${(file as any).path}`)
}
if (!(file instanceof TFile) && "path" in file) {
const w = this.app.vault.getAbstractFileByPath((file as any).path);
const w = getAbstractFileByPath((file as any).path);
if (w instanceof TFile) {
file = w;
} else {

View File

@@ -10,6 +10,26 @@
- 0.16.3
- Fixed detection of IBM Cloudant (And if there are some issues, be fixed automatically).
- A configuration information reporting tool has been implemented.
- 0.16.4 Fixed detection failure. Please set the `Chunk size` again when using a self-hosted database.
- 0.16.5
- Fixed
- Conflict detection and merging now be able to treat deleted files.
- Logs while the boot-up sequence has been tidied up.
- Fixed incorrect log entries.
- New Feature
- The feature of automatically deleting old expired metadata has been implemented.
We can configure it in `Delete old metadata of deleted files on start-up` in the `General Settings` pane.
- 0.16.6
- Fixed
- Automatic (temporary) batch size adjustment has been restored to work correctly.
- Chunk splitting has been backed to the previous behaviour for saving them correctly.
- Improved
- Corrupted chunks will be detected automatically.
- Now on the case-insensitive system, `aaa.md` and `AAA.md` will be treated as the same file or path at applying changesets.
- 0.16.7 Nothing has been changed except toolsets, framework library, and as like them. Please inform me if something had been getting strange!
Note:
Before 0.16.5, LiveSync had some issues making chunks. In this case, synchronisation had became been always failing after a corrupted one should be made. After 0.16.6, the corrupted chunk is automatically detected. Sorry for troubling you but please do `rebuild everything` when this plug-in notified so.
### 0.15.0
- Outdated configuration items have been removed.