mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-05-03 14:21:52 +00:00
Compare commits
1 Commits
0.17.2
...
forprivacy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db68bc8e30 |
@@ -13,7 +13,7 @@ if you want to view the source, please visit the github repository of this plugi
|
|||||||
const prod = process.argv[2] === "production";
|
const prod = process.argv[2] === "production";
|
||||||
const manifestJson = JSON.parse(fs.readFileSync("./manifest.json"));
|
const manifestJson = JSON.parse(fs.readFileSync("./manifest.json"));
|
||||||
const packageJson = JSON.parse(fs.readFileSync("./package.json"));
|
const packageJson = JSON.parse(fs.readFileSync("./package.json"));
|
||||||
const updateInfo = JSON.stringify(fs.readFileSync("./updates.md") + "");
|
const updateInfo = JSON.stringify("PATCHED-"+fs.readFileSync("./updates.md") + "");
|
||||||
esbuild
|
esbuild
|
||||||
.build({
|
.build({
|
||||||
banner: {
|
banner: {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-livesync",
|
"id": "obsidian-livesync",
|
||||||
"name": "Self-hosted LiveSync",
|
"name": "Self-hosted LiveSync",
|
||||||
"version": "0.17.2",
|
"version": "0.16.8",
|
||||||
"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",
|
||||||
|
|||||||
52
package-lock.json
generated
52
package-lock.json
generated
@@ -1,24 +1,26 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.17.2",
|
"version": "0.16.8",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.17.2",
|
"version": "0.16.8",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"diff-match-patch": "^1.0.5",
|
"diff-match-patch": "^1.0.5",
|
||||||
"esbuild": "0.15.15",
|
"esbuild": "0.15.15",
|
||||||
"esbuild-svelte": "^0.7.3",
|
"esbuild-svelte": "^0.7.3",
|
||||||
"idb": "^7.1.1",
|
"idb": "^7.1.1",
|
||||||
"xxhash-wasm": "^0.4.2"
|
"xxhash-wasm": "^0.4.2",
|
||||||
|
"xxhashjs": "^0.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/diff-match-patch": "^1.0.32",
|
"@types/diff-match-patch": "^1.0.32",
|
||||||
"@types/pouchdb": "^6.4.0",
|
"@types/pouchdb": "^6.4.0",
|
||||||
"@types/pouchdb-browser": "^6.1.3",
|
"@types/pouchdb-browser": "^6.1.3",
|
||||||
|
"@types/xxhashjs": "^0.2.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.44.0",
|
"@typescript-eslint/eslint-plugin": "^5.44.0",
|
||||||
"@typescript-eslint/parser": "^5.44.0",
|
"@typescript-eslint/parser": "^5.44.0",
|
||||||
"builtin-modules": "^3.3.0",
|
"builtin-modules": "^3.3.0",
|
||||||
@@ -442,6 +444,15 @@
|
|||||||
"@types/estree": "*"
|
"@types/estree": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/xxhashjs": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/xxhashjs/-/xxhashjs-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-+hlk/W1kgnZn0vR22XNhxHk/qIRQYF54i0UTF2MwBAPd0e7xSy+jKOJwSwTdRQrNnOMRVv+vsh8ITV0uyhp2yg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "5.44.0",
|
"version": "5.44.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.44.0.tgz",
|
||||||
@@ -872,6 +883,11 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cuint": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw=="
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
@@ -3344,6 +3360,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-0.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-0.4.2.tgz",
|
||||||
"integrity": "sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA=="
|
"integrity": "sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/xxhashjs": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==",
|
||||||
|
"dependencies": {
|
||||||
|
"cuint": "^0.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
@@ -3736,6 +3760,15 @@
|
|||||||
"@types/estree": "*"
|
"@types/estree": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/xxhashjs": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/xxhashjs/-/xxhashjs-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-+hlk/W1kgnZn0vR22XNhxHk/qIRQYF54i0UTF2MwBAPd0e7xSy+jKOJwSwTdRQrNnOMRVv+vsh8ITV0uyhp2yg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@typescript-eslint/eslint-plugin": {
|
"@typescript-eslint/eslint-plugin": {
|
||||||
"version": "5.44.0",
|
"version": "5.44.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.44.0.tgz",
|
||||||
@@ -4011,6 +4044,11 @@
|
|||||||
"which": "^2.0.1"
|
"which": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cuint": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw=="
|
||||||
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
@@ -5678,6 +5716,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-0.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-0.4.2.tgz",
|
||||||
"integrity": "sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA=="
|
"integrity": "sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA=="
|
||||||
},
|
},
|
||||||
|
"xxhashjs": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==",
|
||||||
|
"requires": {
|
||||||
|
"cuint": "^0.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"yallist": {
|
"yallist": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.17.2",
|
"version": "0.16.8",
|
||||||
"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",
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
"@types/diff-match-patch": "^1.0.32",
|
"@types/diff-match-patch": "^1.0.32",
|
||||||
"@types/pouchdb": "^6.4.0",
|
"@types/pouchdb": "^6.4.0",
|
||||||
"@types/pouchdb-browser": "^6.1.3",
|
"@types/pouchdb-browser": "^6.1.3",
|
||||||
|
"@types/xxhashjs": "^0.2.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.44.0",
|
"@typescript-eslint/eslint-plugin": "^5.44.0",
|
||||||
"@typescript-eslint/parser": "^5.44.0",
|
"@typescript-eslint/parser": "^5.44.0",
|
||||||
"builtin-modules": "^3.3.0",
|
"builtin-modules": "^3.3.0",
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
"esbuild": "0.15.15",
|
"esbuild": "0.15.15",
|
||||||
"esbuild-svelte": "^0.7.3",
|
"esbuild-svelte": "^0.7.3",
|
||||||
"idb": "^7.1.1",
|
"idb": "^7.1.1",
|
||||||
"xxhash-wasm": "^0.4.2"
|
"xxhash-wasm": "^0.4.2",
|
||||||
|
"xxhashjs": "^0.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { EntryDoc, LOG_LEVEL } from "./lib/src/types.js";
|
|||||||
import { enableEncryption } from "./lib/src/utils.js";
|
import { enableEncryption } from "./lib/src/utils.js";
|
||||||
import { isCloudantURI, isValidRemoteCouchDBURI } from "./lib/src/utils_couchdb.js";
|
import { isCloudantURI, isValidRemoteCouchDBURI } from "./lib/src/utils_couchdb.js";
|
||||||
import { id2path, path2id } from "./utils.js";
|
import { id2path, path2id } from "./utils.js";
|
||||||
|
import XXH from "xxhashjs";
|
||||||
|
|
||||||
export class LocalPouchDB extends LocalPouchDBBase {
|
export class LocalPouchDB extends LocalPouchDBBase {
|
||||||
|
|
||||||
@@ -33,6 +34,13 @@ export class LocalPouchDB extends LocalPouchDBBase {
|
|||||||
await this.kvDB.destroy();
|
await this.kvDB.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async prepareHashFunctions() {
|
||||||
|
if (this.h32 != null) return;
|
||||||
|
// const { h32, h32Raw } = await xxhash();
|
||||||
|
this.h32 = (input: string, seed: number) => (XXH.h32(input, seed).toString(16))// h32;
|
||||||
|
this.h32Raw = (input: Uint8Array, seed: number) => (XXH.h32(input.buffer, seed).toNumber())// h32;
|
||||||
|
}
|
||||||
|
|
||||||
last_successful_post = false;
|
last_successful_post = false;
|
||||||
getLastPostFailedBySize() {
|
getLastPostFailedBySize() {
|
||||||
return !this.last_successful_post;
|
return !this.last_successful_post;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, RequestUrlParam, requestUrl, TextAreaComponent, MarkdownRenderer, stringifyYaml } from "obsidian";
|
import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, RequestUrlParam, requestUrl, TextAreaComponent, MarkdownRenderer, stringifyYaml } from "obsidian";
|
||||||
import { DEFAULT_SETTINGS, LOG_LEVEL, ObsidianLiveSyncSettings, RemoteDBSettings } from "./lib/src/types";
|
import { DEFAULT_SETTINGS, LOG_LEVEL, ObsidianLiveSyncSettings, RemoteDBSettings } from "./lib/src/types";
|
||||||
import { path2id, id2path } from "./utils";
|
import { path2id, id2path } from "./utils";
|
||||||
import { delay, Semaphore, versionNumberString2Number } from "./lib/src/utils";
|
import { delay, versionNumberString2Number } from "./lib/src/utils";
|
||||||
import { Logger } from "./lib/src/logger";
|
import { Logger } from "./lib/src/logger";
|
||||||
import { checkSyncInfo, isCloudantURI } from "./lib/src/utils_couchdb.js";
|
import { checkSyncInfo, isCloudantURI } from "./lib/src/utils_couchdb.js";
|
||||||
import { testCrypt } from "./lib/src/e2ee_v2";
|
import { testCrypt } from "./lib/src/e2ee_v2";
|
||||||
@@ -974,24 +974,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
new Setting(containerSyncSettingEl)
|
|
||||||
.setName("Disable sensible auto merging on markdown files")
|
|
||||||
.setDesc("If this switch is turned on, a merge dialog will be displayed, even if the sensible-merge is possible automatically. (Turn on to previous behavior)")
|
|
||||||
.addToggle((toggle) =>
|
|
||||||
toggle.setValue(this.plugin.settings.disableMarkdownAutoMerge).onChange(async (value) => {
|
|
||||||
this.plugin.settings.disableMarkdownAutoMerge = value;
|
|
||||||
await this.plugin.saveSettings();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
new Setting(containerSyncSettingEl)
|
|
||||||
.setName("Write documents after synchronization even if they have conflict")
|
|
||||||
.setDesc("Turn on to previous behavior")
|
|
||||||
.addToggle((toggle) =>
|
|
||||||
toggle.setValue(this.plugin.settings.writeDocumentsIfConflicted).onChange(async (value) => {
|
|
||||||
this.plugin.settings.writeDocumentsIfConflicted = value;
|
|
||||||
await this.plugin.saveSettings();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
new Setting(containerSyncSettingEl)
|
new Setting(containerSyncSettingEl)
|
||||||
@@ -1306,6 +1288,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
|
|
||||||
new Setting(containerHatchEl)
|
new Setting(containerHatchEl)
|
||||||
.setName("Make report to inform the issue")
|
.setName("Make report to inform the issue")
|
||||||
|
.setDesc("Verify and repair all files and update database without restoring")
|
||||||
.addButton((button) =>
|
.addButton((button) =>
|
||||||
button
|
button
|
||||||
.setButtonText("Make report")
|
.setButtonText("Make report")
|
||||||
@@ -1385,28 +1368,21 @@ ${stringifyYaml(pluginConfig)}`;
|
|||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.setWarning()
|
.setWarning()
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
const semaphore = Semaphore(10);
|
|
||||||
const files = this.app.vault.getFiles();
|
const files = this.app.vault.getFiles();
|
||||||
|
Logger("Verify and repair all files started", LOG_LEVEL.NOTICE, "verify");
|
||||||
|
// const notice = NewNotice("", 0);
|
||||||
let i = 0;
|
let i = 0;
|
||||||
const processes = files.map(e => (async (file) => {
|
for (const file of files) {
|
||||||
const releaser = await semaphore.acquire(1, "verifyAndRepair");
|
i++;
|
||||||
|
Logger(`Update into ${file.path}`);
|
||||||
|
Logger(`${i}/${files.length}\n${file.path}`, LOG_LEVEL.NOTICE, "verify");
|
||||||
try {
|
try {
|
||||||
Logger(`Update into ${file.path}`);
|
await this.plugin.updateIntoDB(file);
|
||||||
await this.plugin.updateIntoDB(file, false, null, true);
|
|
||||||
i++;
|
|
||||||
Logger(`${i}/${files.length}\n${file.path}`, LOG_LEVEL.NOTICE, "verify");
|
|
||||||
|
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
i++;
|
Logger("could not update:");
|
||||||
Logger(`Error while verifyAndRepair`, LOG_LEVEL.NOTICE);
|
|
||||||
Logger(ex);
|
Logger(ex);
|
||||||
} finally {
|
|
||||||
releaser();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)(e));
|
|
||||||
await Promise.all(processes);
|
|
||||||
Logger("done", LOG_LEVEL.NOTICE, "verify");
|
Logger("done", LOG_LEVEL.NOTICE, "verify");
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
2
src/lib
2
src/lib
Submodule src/lib updated: bf8ab8883d...85bb3556ba
260
src/main.ts
260
src/main.ts
@@ -1,5 +1,5 @@
|
|||||||
import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, PluginManifest, App, } from "obsidian";
|
import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, PluginManifest, App, } from "obsidian";
|
||||||
import { Diff, DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch";
|
import { diff_match_patch } from "diff-match-patch";
|
||||||
|
|
||||||
import { EntryDoc, LoadedEntry, ObsidianLiveSyncSettings, diff_check_result, diff_result_leaf, EntryBody, LOG_LEVEL, VER, DEFAULT_SETTINGS, diff_result, FLAGMD_REDFLAG, SYNCINFO_ID, InternalFileEntry } from "./lib/src/types";
|
import { EntryDoc, LoadedEntry, ObsidianLiveSyncSettings, diff_check_result, diff_result_leaf, EntryBody, LOG_LEVEL, VER, DEFAULT_SETTINGS, diff_result, FLAGMD_REDFLAG, SYNCINFO_ID, InternalFileEntry } from "./lib/src/types";
|
||||||
import { PluginDataEntry, PERIODIC_PLUGIN_SWEEP, PluginList, DevicePluginList, InternalFileInfo } from "./types";
|
import { PluginDataEntry, PERIODIC_PLUGIN_SWEEP, PluginList, DevicePluginList, InternalFileInfo } from "./types";
|
||||||
@@ -709,7 +709,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
clearAllPeriodic();
|
clearAllPeriodic();
|
||||||
clearAllTriggers();
|
clearAllTriggers();
|
||||||
window.removeEventListener("visibilitychange", this.watchWindowVisibility);
|
window.removeEventListener("visibilitychange", this.watchWindowVisibility);
|
||||||
window.removeEventListener("online", this.watchOnline);
|
window.removeEventListener("online", this.watchOnline)
|
||||||
Logger("unloading plugin");
|
Logger("unloading plugin");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1365,30 +1365,13 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
} else if (targetFile instanceof TFile) {
|
} else if (targetFile instanceof TFile) {
|
||||||
const doc = change;
|
const doc = change;
|
||||||
const file = targetFile;
|
const file = targetFile;
|
||||||
const queueConflictCheck = () => {
|
await this.doc2storage_modify(doc, file);
|
||||||
if (!this.settings.checkConflictOnlyOnOpen) {
|
if (!this.settings.checkConflictOnlyOnOpen) {
|
||||||
this.queueConflictedCheck(file);
|
this.queueConflictedCheck(file);
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
const af = app.workspace.getActiveFile();
|
|
||||||
if (af && af.path == file.path) {
|
|
||||||
this.queueConflictedCheck(file);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this.settings.writeDocumentsIfConflicted) {
|
|
||||||
await this.doc2storage_modify(doc, file);
|
|
||||||
queueConflictCheck();
|
|
||||||
} else {
|
} else {
|
||||||
const d = await this.localDatabase.getDBEntryMeta(id2path(change._id), { conflicts: true })
|
const af = app.workspace.getActiveFile();
|
||||||
if (d && !d._conflicts) {
|
if (af && af.path == file.path) {
|
||||||
await this.doc2storage_modify(doc, file);
|
this.queueConflictedCheck(file);
|
||||||
} else {
|
|
||||||
if (!queueConflictCheck()) {
|
|
||||||
Logger(`${id2path(change._id)} is conflicted, write to the storage has been pended.`, LOG_LEVEL.NOTICE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1971,163 +1954,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//TODO: TIDY UP
|
|
||||||
async mergeSensibly(path: string, baseRev: string, currentRev: string, conflictedRev: string): Promise<Diff[] | false> {
|
|
||||||
const baseLeaf = await this.getConflictedDoc(path, baseRev);
|
|
||||||
const leftLeaf = await this.getConflictedDoc(path, currentRev);
|
|
||||||
const rightLeaf = await this.getConflictedDoc(path, conflictedRev);
|
|
||||||
let autoMerge = false;
|
|
||||||
if (baseLeaf == false || leftLeaf == false || rightLeaf == false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// diff between base and each revision
|
|
||||||
const dmp = new diff_match_patch();
|
|
||||||
const mapLeft = dmp.diff_linesToChars_(baseLeaf.data, leftLeaf.data);
|
|
||||||
const diffLeftSrc = dmp.diff_main(mapLeft.chars1, mapLeft.chars2, false);
|
|
||||||
dmp.diff_charsToLines_(diffLeftSrc, mapLeft.lineArray);
|
|
||||||
const mapRight = dmp.diff_linesToChars_(baseLeaf.data, rightLeaf.data);
|
|
||||||
const diffRightSrc = dmp.diff_main(mapRight.chars1, mapRight.chars2, false);
|
|
||||||
dmp.diff_charsToLines_(diffRightSrc, mapRight.lineArray);
|
|
||||||
function splitDiffPiece(src: Diff[]): Diff[] {
|
|
||||||
const ret = [] as Diff[];
|
|
||||||
do {
|
|
||||||
const d = src.shift();
|
|
||||||
const pieces = d[1].split(/([^\n]*\n)/).filter(f => f != "");
|
|
||||||
if (typeof (d) == "undefined") {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (d[0] != DIFF_DELETE) {
|
|
||||||
ret.push(...(pieces.map(e => [d[0], e] as Diff)));
|
|
||||||
}
|
|
||||||
if (d[0] == DIFF_DELETE) {
|
|
||||||
const nd = src.shift();
|
|
||||||
|
|
||||||
if (typeof (nd) != "undefined") {
|
|
||||||
const piecesPair = nd[1].split(/([^\n]*\n)/).filter(f => f != "");
|
|
||||||
if (nd[0] == DIFF_INSERT) {
|
|
||||||
// it might be pair
|
|
||||||
for (const pt of pieces) {
|
|
||||||
ret.push([d[0], pt]);
|
|
||||||
const pairP = piecesPair.shift();
|
|
||||||
if (typeof (pairP) != "undefined") ret.push([DIFF_INSERT, pairP]);
|
|
||||||
}
|
|
||||||
ret.push(...(piecesPair.map(e => [nd[0], e] as Diff)));
|
|
||||||
} else {
|
|
||||||
ret.push(...(pieces.map(e => [d[0], e] as Diff)));
|
|
||||||
ret.push(...(piecesPair.map(e => [nd[0], e] as Diff)));
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ret.push(...(pieces.map(e => [0, e] as Diff)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (src.length > 0);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
const diffLeft = splitDiffPiece(diffLeftSrc);
|
|
||||||
const diffRight = splitDiffPiece(diffRightSrc);
|
|
||||||
|
|
||||||
let rightIdx = 0;
|
|
||||||
let leftIdx = 0;
|
|
||||||
const merged = [] as Diff[];
|
|
||||||
autoMerge = true;
|
|
||||||
LOOP_MERGE:
|
|
||||||
do {
|
|
||||||
if (leftIdx >= diffLeft.length && rightIdx >= diffRight.length) {
|
|
||||||
break LOOP_MERGE;
|
|
||||||
}
|
|
||||||
const leftItem = diffLeft[leftIdx] ?? [0, ""];
|
|
||||||
const rightItem = diffRight[rightIdx] ?? [0, ""];
|
|
||||||
leftIdx++;
|
|
||||||
rightIdx++;
|
|
||||||
// when completely same, leave it .
|
|
||||||
if (leftItem[0] == DIFF_EQUAL && rightItem[0] == DIFF_EQUAL && leftItem[1] == rightItem[1]) {
|
|
||||||
merged.push(leftItem);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (leftItem[0] == DIFF_DELETE && rightItem[0] == DIFF_DELETE && leftItem[1] == rightItem[1]) {
|
|
||||||
// when deleted evenly,
|
|
||||||
const nextLeftIdx = leftIdx;
|
|
||||||
const nextRightIdx = rightIdx;
|
|
||||||
const [nextLeftItem, nextRightItem] = [diffLeft[nextLeftIdx] ?? [0, ""], diffRight[nextRightIdx] ?? [0, ""]];
|
|
||||||
if ((nextLeftItem[0] == DIFF_INSERT && nextRightItem[0] == DIFF_INSERT) && nextLeftItem[1] != nextRightItem[1]) {
|
|
||||||
//but next line looks like different
|
|
||||||
autoMerge = false;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
merged.push(leftItem);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// when inserted evenly
|
|
||||||
if (leftItem[0] == DIFF_INSERT && rightItem[0] == DIFF_INSERT) {
|
|
||||||
if (leftItem[1] == rightItem[1]) {
|
|
||||||
merged.push(leftItem);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
// sort by file date.
|
|
||||||
if (leftLeaf.mtime <= rightLeaf.mtime) {
|
|
||||||
merged.push(leftItem);
|
|
||||||
merged.push(rightItem);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
merged.push(rightItem);
|
|
||||||
merged.push(leftItem);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
// when on inserting, index should be fixed again.
|
|
||||||
if (leftItem[0] == DIFF_INSERT) {
|
|
||||||
rightIdx--;
|
|
||||||
merged.push(leftItem);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (rightItem[0] == DIFF_INSERT) {
|
|
||||||
leftIdx--;
|
|
||||||
merged.push(rightItem);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// except insertion, the line should not be different.
|
|
||||||
if (rightItem[1] != leftItem[1]) {
|
|
||||||
//TODO: SHOULD BE PANIC.
|
|
||||||
Logger(`MERGING PANIC:${leftItem[0]},${leftItem[1]} == ${rightItem[0]},${rightItem[1]}`, LOG_LEVEL.VERBOSE);
|
|
||||||
autoMerge = false;
|
|
||||||
break LOOP_MERGE;
|
|
||||||
}
|
|
||||||
if (leftItem[0] == DIFF_DELETE) {
|
|
||||||
if (rightItem[0] == DIFF_EQUAL) {
|
|
||||||
merged.push(leftItem);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
//we cannot perform auto merge.
|
|
||||||
autoMerge = false;
|
|
||||||
break LOOP_MERGE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rightItem[0] == DIFF_DELETE) {
|
|
||||||
if (leftItem[0] == DIFF_EQUAL) {
|
|
||||||
merged.push(rightItem);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
//we cannot perform auto merge.
|
|
||||||
autoMerge = false;
|
|
||||||
break LOOP_MERGE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Logger(`Weird condition:${leftItem[0]},${leftItem[1]} == ${rightItem[0]},${rightItem[1]}`, LOG_LEVEL.VERBOSE);
|
|
||||||
// here is the exception
|
|
||||||
break LOOP_MERGE;
|
|
||||||
} while (leftIdx < diffLeft.length || rightIdx < diffRight.length);
|
|
||||||
if (autoMerge) {
|
|
||||||
Logger(`Sensibly merge available`, LOG_LEVEL.VERBOSE);
|
|
||||||
return merged;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Getting file conflicted status.
|
* Getting file conflicted status.
|
||||||
@@ -2140,39 +1966,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
if (test == null) return false;
|
if (test == null) return false;
|
||||||
if (!test._conflicts) return false;
|
if (!test._conflicts) return false;
|
||||||
if (test._conflicts.length == 0) return false;
|
if (test._conflicts.length == 0) return false;
|
||||||
const conflicts = test._conflicts.sort((a, b) => Number(a.split("-")[0]) - Number(b.split("-")[0]));
|
|
||||||
|
|
||||||
if (path.endsWith(".md") && !this.settings.disableMarkdownAutoMerge) {
|
|
||||||
const conflictedRev = conflicts[0];
|
|
||||||
const conflictedRevNo = Number(conflictedRev.split("-")[0]);
|
|
||||||
//Search
|
|
||||||
const revFrom = (await this.localDatabase.localDatabase.get(id2path(path), { revs_info: true })) as unknown as LoadedEntry & PouchDB.Core.GetMeta;
|
|
||||||
const commonBase = revFrom._revs_info.filter(e => e.status == "available" && Number(e.rev.split("-")[0]) < conflictedRevNo).first().rev ?? "";
|
|
||||||
if (commonBase) {
|
|
||||||
const result = await this.mergeSensibly(path, commonBase, test._rev, conflictedRev);
|
|
||||||
if (result) {
|
|
||||||
// can be merged.
|
|
||||||
Logger(`Sensible merge:${path}`, LOG_LEVEL.INFO);
|
|
||||||
// remove conflicted revision.
|
|
||||||
await this.localDatabase.deleteDBEntry(path, { rev: conflictedRev });
|
|
||||||
const p = result.filter(e => e[0] != DIFF_DELETE).map((e) => e[1]).join("");
|
|
||||||
const file = getAbstractFileByPath(path) as TFile;
|
|
||||||
if (file) {
|
|
||||||
await this.app.vault.modify(file, p);
|
|
||||||
await this.updateIntoDB(file);
|
|
||||||
} else {
|
|
||||||
const newFile = await this.app.vault.create(path, p);
|
|
||||||
await this.updateIntoDB(newFile);
|
|
||||||
}
|
|
||||||
await this.pullFile(path);
|
|
||||||
Logger(`Automatically merged (sensible) :${path}`, LOG_LEVEL.INFO);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// should be one or more conflicts;
|
// should be one or more conflicts;
|
||||||
const leftLeaf = await this.getConflictedDoc(path, test._rev);
|
const leftLeaf = await this.getConflictedDoc(path, test._rev);
|
||||||
const rightLeaf = await this.getConflictedDoc(path, conflicts[0]);
|
const rightLeaf = await this.getConflictedDoc(path, test._conflicts[0]);
|
||||||
if (leftLeaf == false) {
|
if (leftLeaf == false) {
|
||||||
// what's going on..
|
// what's going on..
|
||||||
Logger(`could not get current revisions:${path}`, LOG_LEVEL.NOTICE);
|
Logger(`could not get current revisions:${path}`, LOG_LEVEL.NOTICE);
|
||||||
@@ -2180,7 +1976,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
if (rightLeaf == false) {
|
if (rightLeaf == false) {
|
||||||
// Conflicted item could not load, delete this.
|
// Conflicted item could not load, delete this.
|
||||||
await this.localDatabase.deleteDBEntry(path, { rev: conflicts[0] });
|
await this.localDatabase.deleteDBEntry(path, { rev: test._conflicts[0] });
|
||||||
await this.pullFile(path, null, true);
|
await this.pullFile(path, null, true);
|
||||||
Logger(`could not get old revisions, automatically used newer one:${path}`, LOG_LEVEL.NOTICE);
|
Logger(`could not get old revisions, automatically used newer one:${path}`, LOG_LEVEL.NOTICE);
|
||||||
return true;
|
return true;
|
||||||
@@ -2236,10 +2032,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
const toDelete = selected;
|
const toDelete = selected;
|
||||||
const toKeep = conflictCheckResult.left.rev != toDelete ? conflictCheckResult.left.rev : conflictCheckResult.right.rev;
|
const toKeep = conflictCheckResult.left.rev != toDelete ? conflictCheckResult.left.rev : conflictCheckResult.right.rev;
|
||||||
if (toDelete == "") {
|
if (toDelete == "") {
|
||||||
// concat both,
|
//concat both,
|
||||||
// delete conflicted revision and write a new file, store it again.
|
// write data,and delete both old rev.
|
||||||
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: conflictCheckResult.left.rev });
|
||||||
|
await this.localDatabase.deleteDBEntry(filename, { rev: conflictCheckResult.right.rev });
|
||||||
const file = getAbstractFileByPath(filename) as TFile;
|
const file = getAbstractFileByPath(filename) as TFile;
|
||||||
if (file) {
|
if (file) {
|
||||||
await this.app.vault.modify(file, p);
|
await this.app.vault.modify(file, p);
|
||||||
@@ -2295,7 +2092,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
Logger(ex);
|
Logger(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
async showIfConflicted(filename: string) {
|
async showIfConflicted(filename: string) {
|
||||||
@@ -2395,7 +2192,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateIntoDB(file: TFile, initialScan?: boolean, cache?: CacheData, force?: boolean) {
|
async updateIntoDB(file: TFile, initialScan?: boolean, cache?: CacheData) {
|
||||||
if (!this.isTargetFile(file)) return;
|
if (!this.isTargetFile(file)) return;
|
||||||
if (shouldBeIgnored(file.path)) {
|
if (shouldBeIgnored(file.path)) {
|
||||||
return;
|
return;
|
||||||
@@ -2437,24 +2234,15 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
if (recentlyTouched(file)) {
|
if (recentlyTouched(file)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
try {
|
const old = await this.localDatabase.getDBEntry(fullPath, null, false, false);
|
||||||
const old = await this.localDatabase.getDBEntry(fullPath, null, false, false);
|
if (old !== false) {
|
||||||
if (old !== false) {
|
const oldData = { data: old.data, deleted: old._deleted || old.deleted, };
|
||||||
const oldData = { data: old.data, deleted: old._deleted || old.deleted, };
|
const newData = { data: d.data, deleted: d._deleted || d.deleted };
|
||||||
const newData = { data: d.data, deleted: d._deleted || d.deleted };
|
if (JSON.stringify(oldData) == JSON.stringify(newData)) {
|
||||||
if (JSON.stringify(oldData) == JSON.stringify(newData)) {
|
Logger(msg + "Skipped (not changed) " + fullPath + ((d._deleted || d.deleted) ? " (deleted)" : ""), LOG_LEVEL.VERBOSE);
|
||||||
Logger(msg + "Skipped (not changed) " + fullPath + ((d._deleted || d.deleted) ? " (deleted)" : ""), LOG_LEVEL.VERBOSE);
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// d._rev = old._rev;
|
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
// d._rev = old._rev;
|
||||||
if (force) {
|
|
||||||
Logger(msg + "Error, Could not check the diff for the old one." + (force ? "force writing." : "") + fullPath + ((d._deleted || d.deleted) ? " (deleted)" : ""), LOG_LEVEL.VERBOSE);
|
|
||||||
} else {
|
|
||||||
Logger(msg + "Error, Could not check the diff for the old one." + fullPath + ((d._deleted || d.deleted) ? " (deleted)" : ""), LOG_LEVEL.VERBOSE);
|
|
||||||
}
|
|
||||||
return !force;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|||||||
44
updates.md
44
updates.md
@@ -1,25 +1,3 @@
|
|||||||
### 0.17.0
|
|
||||||
- 0.17.0 has no surfaced changes but the design of saving chunks has been changed. They have compatibility but changing files after upgrading makes different chunks than before 0.16.x.
|
|
||||||
Please rebuild databases once if you have been worried about storage usage.
|
|
||||||
|
|
||||||
- Improved:
|
|
||||||
- Splitting markdown
|
|
||||||
- Saving chunks
|
|
||||||
|
|
||||||
- Changed:
|
|
||||||
- Chunk ID numbering rules
|
|
||||||
|
|
||||||
#### Minors
|
|
||||||
- 0.17.1
|
|
||||||
- Fixed: Now we can verify and repair the database.
|
|
||||||
- Refactored inside.
|
|
||||||
|
|
||||||
- 0.17.2
|
|
||||||
- New feature
|
|
||||||
- We can merge conflicted documents automatically if sensible.
|
|
||||||
- Fixed
|
|
||||||
- Writing to the storage will be pended while they have conflicts after replication.
|
|
||||||
|
|
||||||
### 0.16.0
|
### 0.16.0
|
||||||
- Now hidden files need not be scanned. Changes will be detected automatically.
|
- Now hidden files need not be scanned. Changes will be detected automatically.
|
||||||
- If you want it to back to its previous behaviour, please disable `Monitor changes to internal files`.
|
- If you want it to back to its previous behaviour, please disable `Monitor changes to internal files`.
|
||||||
@@ -53,3 +31,25 @@
|
|||||||
|
|
||||||
Note:
|
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.
|
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.
|
||||||
|
- Setup wizard has been implemented!
|
||||||
|
|
||||||
|
I appreciate for reviewing and giving me advice @Pouhon158!
|
||||||
|
|
||||||
|
#### Minors
|
||||||
|
- 0.15.1 Missed the stylesheet.
|
||||||
|
- 0.15.2 The wizard has been improved and documented!
|
||||||
|
- 0.15.3 Fixed the issue about locking/unlocking remote database while rebuilding in the wizard.
|
||||||
|
- 0.15.4 Fixed issues about asynchronous processing (e.g., Conflict check or hidden file detection)
|
||||||
|
- 0.15.5 Add new features for setting Self-hosted LiveSync up more easier.
|
||||||
|
- 0.15.6 File tracking logic has been refined.
|
||||||
|
- 0.15.7 Fixed bug about renaming file.
|
||||||
|
- 0.15.8 Fixed bug about deleting empty directory, weird behaviour on boot-sequence on mobile devices.
|
||||||
|
- 0.15.9 Improved chunk retrieving, now chunks are retrieved in batch on continuous requests.
|
||||||
|
- 0.15.10 Fixed:
|
||||||
|
- The boot sequence has been corrected and now boots smoothly.
|
||||||
|
- Auto applying of batch save will be processed earlier than before.
|
||||||
|
|
||||||
|
... To continue on to `updates_old.md`.
|
||||||
Reference in New Issue
Block a user