Compare commits

...

22 Commits

Author SHA1 Message Date
vorotamoroz
aec0b2986b bump 2023-01-28 21:24:03 +09:00
vorotamoroz
e6025b92d8 Ensure logging. 2023-01-28 21:20:26 +09:00
vorotamoroz
fad9fed5ca bump 2023-01-27 21:47:04 +09:00
vorotamoroz
e46246cd63 Fixed:
- Fixed lack of error handling.
2023-01-27 17:49:53 +09:00
vorotamoroz
0f3be19dd7 bump 2023-01-25 22:39:50 +09:00
vorotamoroz
bc568ff479 Fixed:
- Now we can merge JSON files even if they have entries which cannot be compared.
2023-01-25 22:37:55 +09:00
vorotamoroz
2fdc7669f3 bump 2023-01-25 20:54:20 +09:00
vorotamoroz
ec8d9785ed - Improved:
- Plugins and their settings no longer need scanning if changes are monitored.
  - Now synchronising plugins and their settings are performed parallelly and faster.
  - We can place `redflag2.md` to rebuild the database automatically while the boot sequence.
- Experimental:
  - We can use a new adapter on PouchDB. This will make us smoother.
    - Note: Not compatible with the older version.
- Fixed:
  - The default batch size is smaller again.
  - Plugins and their setting can be synchronised again.
  - Hidden files and plugins are correctly scanned while rebuilding.
  - Files with the name started `_` are also being performed conflict-checking.
2023-01-25 20:53:20 +09:00
vorotamoroz
71a80cacc3 bump again 2023-01-19 19:05:16 +09:00
vorotamoroz
38daeca89f fixed leaked logging 2023-01-19 19:03:57 +09:00
vorotamoroz
7ec64a6a93 bump 2023-01-19 18:55:18 +09:00
vorotamoroz
c5c6deb742 Improved:
- Confidential information has no longer stored in data.json as is.
- Synchronising progress has been shown in the notification.
- We can commit passphrases with a keyboard.
- Configuration which had not been saved yet is marked now.

Fixed:
- Hidden files have been synchronised again.

And, minor changes have been included.
2023-01-19 18:50:06 +09:00
vorotamoroz
ef57fbfdda Fixed:
- Now the filename is shown on the Conflict resolving dialog
- Rename of files has been improved again.
2023-01-19 13:11:30 +09:00
vorotamoroz
bc158e9f2b bump 2023-01-17 17:46:06 +09:00
vorotamoroz
6513c53c7e Fixed:
- Document history is now displayed again.

Reorganised:
- Many files have been refactored.
2023-01-17 17:39:26 +09:00
vorotamoroz
5d1074065c bump 2023-01-16 17:33:31 +09:00
vorotamoroz
b444082b0c Fixed:
- Performance improvement
- Now `Chunk size` can be set to under one hundred.

New feature:
- The number of transfers required before replication stabilises is now displayed.
2023-01-16 17:31:37 +09:00
vorotamoroz
d5e6419504 bump 2023-01-15 11:17:08 +09:00
vorotamoroz
1bf1e1540d Fix diff check. 2023-01-15 11:09:23 +09:00
vorotamoroz
be1e6b11ac Fixed
- Large files addressed.
2023-01-13 19:43:39 +09:00
vorotamoroz
a486788572 bump 2023-01-06 16:33:54 +09:00
vorotamoroz
e5784a1da6 Fixed:
- Conflict merge of internal files is no longer broken.
Improved:
- Smoother status display inside the editor.
2023-01-06 16:27:39 +09:00
17 changed files with 1029 additions and 606 deletions

View File

@@ -1,7 +1,7 @@
{ {
"id": "obsidian-livesync", "id": "obsidian-livesync",
"name": "Self-hosted LiveSync", "name": "Self-hosted LiveSync",
"version": "0.17.8", "version": "0.17.19",
"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",

45
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.17.8", "version": "0.17.19",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.17.8", "version": "0.17.19",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"diff-match-patch": "^1.0.5", "diff-match-patch": "^1.0.5",
@@ -33,6 +33,7 @@
"postcss-load-config": "^4.0.1", "postcss-load-config": "^4.0.1",
"pouchdb-adapter-http": "^8.0.0", "pouchdb-adapter-http": "^8.0.0",
"pouchdb-adapter-idb": "^8.0.0", "pouchdb-adapter-idb": "^8.0.0",
"pouchdb-adapter-indexeddb": "^8.0.0",
"pouchdb-core": "^8.0.0", "pouchdb-core": "^8.0.0",
"pouchdb-find": "^8.0.0", "pouchdb-find": "^8.0.0",
"pouchdb-mapreduce": "^8.0.0", "pouchdb-mapreduce": "^8.0.0",
@@ -2389,9 +2390,9 @@
"dev": true "dev": true
}, },
"node_modules/json5": { "node_modules/json5": {
"version": "1.0.1", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"minimist": "^1.2.0" "minimist": "^1.2.0"
@@ -2889,6 +2890,20 @@
"pouchdb-utils": "8.0.0" "pouchdb-utils": "8.0.0"
} }
}, },
"node_modules/pouchdb-adapter-indexeddb": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/pouchdb-adapter-indexeddb/-/pouchdb-adapter-indexeddb-8.0.0.tgz",
"integrity": "sha512-h+vMPspVF6s4IKzLSys7iGDlANWkow77hJV/MX6JIftrjj/QS5jShSzhGCAR9HpLtuAVwQQM+k4hQodGnoAGWw==",
"dev": true,
"dependencies": {
"pouchdb-adapter-utils": "8.0.0",
"pouchdb-binary-utils": "8.0.0",
"pouchdb-errors": "8.0.0",
"pouchdb-md5": "8.0.0",
"pouchdb-merge": "8.0.0",
"pouchdb-utils": "8.0.0"
}
},
"node_modules/pouchdb-adapter-utils": { "node_modules/pouchdb-adapter-utils": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/pouchdb-adapter-utils/-/pouchdb-adapter-utils-8.0.0.tgz", "resolved": "https://registry.npmjs.org/pouchdb-adapter-utils/-/pouchdb-adapter-utils-8.0.0.tgz",
@@ -5487,9 +5502,9 @@
"dev": true "dev": true
}, },
"json5": { "json5": {
"version": "1.0.1", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true, "dev": true,
"requires": { "requires": {
"minimist": "^1.2.0" "minimist": "^1.2.0"
@@ -5841,6 +5856,20 @@
"pouchdb-utils": "8.0.0" "pouchdb-utils": "8.0.0"
} }
}, },
"pouchdb-adapter-indexeddb": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/pouchdb-adapter-indexeddb/-/pouchdb-adapter-indexeddb-8.0.0.tgz",
"integrity": "sha512-h+vMPspVF6s4IKzLSys7iGDlANWkow77hJV/MX6JIftrjj/QS5jShSzhGCAR9HpLtuAVwQQM+k4hQodGnoAGWw==",
"dev": true,
"requires": {
"pouchdb-adapter-utils": "8.0.0",
"pouchdb-binary-utils": "8.0.0",
"pouchdb-errors": "8.0.0",
"pouchdb-md5": "8.0.0",
"pouchdb-merge": "8.0.0",
"pouchdb-utils": "8.0.0"
}
},
"pouchdb-adapter-utils": { "pouchdb-adapter-utils": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/pouchdb-adapter-utils/-/pouchdb-adapter-utils-8.0.0.tgz", "resolved": "https://registry.npmjs.org/pouchdb-adapter-utils/-/pouchdb-adapter-utils-8.0.0.tgz",

View File

@@ -1,6 +1,6 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.17.8", "version": "0.17.19",
"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",
@@ -30,6 +30,7 @@
"postcss-load-config": "^4.0.1", "postcss-load-config": "^4.0.1",
"pouchdb-adapter-http": "^8.0.0", "pouchdb-adapter-http": "^8.0.0",
"pouchdb-adapter-idb": "^8.0.0", "pouchdb-adapter-idb": "^8.0.0",
"pouchdb-adapter-indexeddb": "^8.0.0",
"pouchdb-core": "^8.0.0", "pouchdb-core": "^8.0.0",
"pouchdb-find": "^8.0.0", "pouchdb-find": "^8.0.0",
"pouchdb-mapreduce": "^8.0.0", "pouchdb-mapreduce": "^8.0.0",

View File

@@ -1,17 +1,19 @@
import { App, Modal } from "obsidian"; import { App, Modal } from "obsidian";
import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT } from "diff-match-patch"; import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT } from "diff-match-patch";
import { diff_result } from "./lib/src/types"; import { diff_result } from "./lib/src/types";
import { escapeStringToHTML } from "./lib/src/utils"; import { escapeStringToHTML } from "./lib/src/strbin";
export class ConflictResolveModal extends Modal { export class ConflictResolveModal extends Modal {
// result: Array<[number, string]>; // result: Array<[number, string]>;
result: diff_result; result: diff_result;
filename: string;
callback: (remove_rev: string) => Promise<void>; callback: (remove_rev: string) => Promise<void>;
constructor(app: App, diff: diff_result, callback: (remove_rev: string) => Promise<void>) { constructor(app: App, filename: string, diff: diff_result, callback: (remove_rev: string) => Promise<void>) {
super(app); super(app);
this.result = diff; this.result = diff;
this.callback = callback; this.callback = callback;
this.filename = filename;
} }
onOpen() { onOpen() {
@@ -20,6 +22,7 @@ export class ConflictResolveModal extends Modal {
contentEl.empty(); contentEl.empty();
contentEl.createEl("h2", { text: "This document has conflicted changes." }); contentEl.createEl("h2", { text: "This document has conflicted changes." });
contentEl.createEl("span", this.filename);
const div = contentEl.createDiv(""); const div = contentEl.createDiv("");
div.addClass("op-scrollable"); div.addClass("op-scrollable");
let diff = ""; let diff = "";

View File

@@ -1,10 +1,13 @@
import { TFile, Modal, App } from "obsidian"; import { TFile, Modal, App } from "obsidian";
import { path2id } from "./utils"; import { path2id } from "./utils";
import { base64ToArrayBuffer, base64ToString, escapeStringToHTML, isValidPath } from "./lib/src/utils"; import { base64ToArrayBuffer, base64ToString, escapeStringToHTML } from "./lib/src/strbin";
import { isValidPath } from "./lib/src/path";
import ObsidianLiveSyncPlugin from "./main"; import ObsidianLiveSyncPlugin from "./main";
import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch"; import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch";
import { LoadedEntry, LOG_LEVEL } from "./lib/src/types"; import { LoadedEntry, LOG_LEVEL } from "./lib/src/types";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
import { isErrorOfMissingDoc } from "./lib/src/utils_couchdb";
import { getDocData } from "./lib/src/utils";
export class DocumentHistoryModal extends Modal { export class DocumentHistoryModal extends Modal {
plugin: ObsidianLiveSyncPlugin; plugin: ObsidianLiveSyncPlugin;
@@ -33,13 +36,13 @@ export class DocumentHistoryModal extends Modal {
const db = this.plugin.localDatabase; const db = this.plugin.localDatabase;
try { try {
const w = await db.localDatabase.get(path2id(this.file), { revs_info: true }); const w = await db.localDatabase.get(path2id(this.file), { revs_info: true });
this.revs_info = w._revs_info.filter((e) => e.status == "available"); this.revs_info = w._revs_info.filter((e) => e?.status == "available");
this.range.max = `${this.revs_info.length - 1}`; this.range.max = `${this.revs_info.length - 1}`;
this.range.value = this.range.max; this.range.value = this.range.max;
this.fileInfo.setText(`${this.file} / ${this.revs_info.length} revisions`); this.fileInfo.setText(`${this.file} / ${this.revs_info.length} revisions`);
await this.loadRevs(); await this.loadRevs();
} catch (ex) { } catch (ex) {
if (ex.status && ex.status == 404) { if (isErrorOfMissingDoc(ex)) {
this.range.max = "0"; this.range.max = "0";
this.range.value = ""; this.range.value = "";
this.range.disabled = true; this.range.disabled = true;
@@ -64,7 +67,7 @@ export class DocumentHistoryModal extends Modal {
this.currentDoc = w; this.currentDoc = w;
this.info.innerHTML = `Modified:${new Date(w.mtime).toLocaleString()}`; this.info.innerHTML = `Modified:${new Date(w.mtime).toLocaleString()}`;
let result = ""; let result = "";
const w1data = w.datatype == "plain" ? w.data : base64ToString(w.data); const w1data = w.datatype == "plain" ? getDocData(w.data) : base64ToString(w.data);
this.currentDeleted = w.deleted; this.currentDeleted = w.deleted;
this.currentText = w1data; this.currentText = w1data;
if (this.showDiff) { if (this.showDiff) {
@@ -74,7 +77,7 @@ export class DocumentHistoryModal extends Modal {
const w2 = await db.getDBEntry(path2id(this.file), { rev: oldRev }, false, false, true); const w2 = await db.getDBEntry(path2id(this.file), { rev: oldRev }, false, false, true);
if (w2 != false) { if (w2 != false) {
const dmp = new diff_match_patch(); const dmp = new diff_match_patch();
const w2data = w2.datatype == "plain" ? w2.data : base64ToString(w2.data); const w2data = w2.datatype == "plain" ? getDocData(w2.data) : base64ToString(w2.data);
const diff = dmp.diff_main(w2data, w1data); const diff = dmp.diff_main(w2data, w1data);
dmp.diff_cleanupSemantic(diff); dmp.diff_cleanupSemantic(diff);
for (const v of diff) { for (const v of diff) {
@@ -176,7 +179,7 @@ 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, this.currentDoc.data); await this.app.vault.adapter.write(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") {

View File

@@ -3,14 +3,15 @@ import { KeyValueDatabase, OpenKeyValueDatabase } from "./KeyValueDB.js";
import { LocalPouchDBBase } from "./lib/src/LocalPouchDBBase.js"; import { LocalPouchDBBase } from "./lib/src/LocalPouchDBBase.js";
import { Logger } from "./lib/src/logger.js"; import { Logger } from "./lib/src/logger.js";
import { PouchDB } from "./lib/src/pouchdb-browser.js"; import { PouchDB } from "./lib/src/pouchdb-browser.js";
import { EntryDoc, LOG_LEVEL } from "./lib/src/types.js"; import { EntryDoc, LOG_LEVEL, ObsidianLiveSyncSettings } from "./lib/src/types.js";
import { enableEncryption } from "./lib/src/utils.js"; import { enableEncryption } from "./lib/src/utils_couchdb.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";
export class LocalPouchDB extends LocalPouchDBBase { export class LocalPouchDB extends LocalPouchDBBase {
kvDB: KeyValueDatabase; kvDB: KeyValueDatabase;
settings: ObsidianLiveSyncSettings;
id2path(filename: string): string { id2path(filename: string): string {
return id2path(filename); return id2path(filename);
} }
@@ -18,6 +19,10 @@ export class LocalPouchDB extends LocalPouchDBBase {
return path2id(filename); return path2id(filename);
} }
CreatePouchDBInstance<T>(name?: string, options?: PouchDB.Configuration.DatabaseConfiguration): PouchDB.Database<T> { CreatePouchDBInstance<T>(name?: string, options?: PouchDB.Configuration.DatabaseConfiguration): PouchDB.Database<T> {
if (this.settings.useIndexedDBAdapter) {
options.adapter = "indexeddb";
return new PouchDB(name + "-indexeddb", options);
}
return new PouchDB(name, options); return new PouchDB(name, options);
} }
beforeOnUnload(): void { beforeOnUnload(): void {
@@ -52,7 +57,7 @@ export class LocalPouchDB extends LocalPouchDBBase {
} }
async connectRemoteCouchDB(uri: string, auth: { username: string; password: string }, disableRequestURI: boolean, passphrase: string | boolean, useDynamicIterationCount: boolean): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> { async connectRemoteCouchDB(uri: string, auth: { username: string; password: string }, disableRequestURI: boolean, passphrase: string | false, useDynamicIterationCount: boolean): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> {
if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid"; if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid";
if (uri.toLowerCase() != uri) return "Remote URI and database name could not contain capital letters."; if (uri.toLowerCase() != uri) return "Remote URI and database name could not contain capital letters.";
if (uri.indexOf(" ") !== -1) return "Remote URI and database name could not contain spaces."; if (uri.indexOf(" ") !== -1) return "Remote URI and database name could not contain spaces.";
@@ -154,7 +159,7 @@ export class LocalPouchDB extends LocalPouchDBBase {
}; };
const db: PouchDB.Database<EntryDoc> = new PouchDB<EntryDoc>(uri, conf); const db: PouchDB.Database<EntryDoc> = new PouchDB<EntryDoc>(uri, conf);
if (passphrase && typeof passphrase === "string") { if (passphrase !== "false" && typeof passphrase === "string") {
enableEncryption(db, passphrase, useDynamicIterationCount); enableEncryption(db, passphrase, useDynamicIterationCount);
} }
try { try {

View File

@@ -1,21 +1,17 @@
import { App, Modal } from "obsidian"; import { App, Modal } from "obsidian";
import { escapeStringToHTML } from "./lib/src/utils"; import { logMessageStore } from "./lib/src/stores";
import { escapeStringToHTML } from "./lib/src/strbin";
import ObsidianLiveSyncPlugin from "./main"; import ObsidianLiveSyncPlugin from "./main";
export class LogDisplayModal extends Modal { export class LogDisplayModal extends Modal {
plugin: ObsidianLiveSyncPlugin; plugin: ObsidianLiveSyncPlugin;
logEl: HTMLDivElement; logEl: HTMLDivElement;
unsubscribe: () => void;
constructor(app: App, plugin: ObsidianLiveSyncPlugin) { constructor(app: App, plugin: ObsidianLiveSyncPlugin) {
super(app); super(app);
this.plugin = plugin; this.plugin = plugin;
} }
updateLog() {
let msg = "";
for (const v of this.plugin.logMessage) {
msg += escapeStringToHTML(v) + "<br>";
}
this.logEl.innerHTML = msg;
}
onOpen() { onOpen() {
const { contentEl } = this; const { contentEl } = this;
@@ -25,13 +21,18 @@ export class LogDisplayModal extends Modal {
div.addClass("op-scrollable"); div.addClass("op-scrollable");
div.addClass("op-pre"); div.addClass("op-pre");
this.logEl = div; this.logEl = div;
this.updateLog = this.updateLog.bind(this); this.unsubscribe = logMessageStore.observe((e) => {
this.plugin.addLogHook = this.updateLog; let msg = "";
this.updateLog(); for (const v of e) {
msg += escapeStringToHTML(v) + "<br>";
}
this.logEl.innerHTML = msg;
})
logMessageStore.invalidate();
} }
onClose() { onClose() {
const { contentEl } = this; const { contentEl } = this;
contentEl.empty(); contentEl.empty();
this.plugin.addLogHook = null; if (this.unsubscribe) this.unsubscribe();
} }
} }

View File

@@ -1,7 +1,9 @@
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, ConfigPassphraseStore, 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 } from "./lib/src/utils";
import { Semaphore } from "./lib/src/semaphore";
import { versionNumberString2Number } from "./lib/src/strbin";
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";
@@ -41,6 +43,9 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
} }
display(): void { display(): void {
const { containerEl } = this; const { containerEl } = this;
let encrypt = this.plugin.settings.encrypt;
let passphrase = this.plugin.settings.passphrase;
let useDynamicIterationCount = this.plugin.settings.useDynamicIterationCount;
containerEl.empty(); containerEl.empty();
@@ -291,68 +296,78 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
) )
); );
new Setting(containerRemoteDatabaseEl) const e2e = new Setting(containerRemoteDatabaseEl)
.setName("End to End Encryption") .setName("End to End Encryption")
.setDesc("Encrypt contents on the remote database. If you use the plugin's synchronization feature, enabling this is recommend.") .setDesc("Encrypt contents on the remote database. If you use the plugin's synchronization feature, enabling this is recommend.")
.addToggle((toggle) => .addToggle((toggle) =>
toggle.setValue(this.plugin.settings.workingEncrypt).onChange(async (value) => { toggle.setValue(encrypt).onChange(async (value) => {
if (inWizard) { if (inWizard) {
this.plugin.settings.encrypt = value; this.plugin.settings.encrypt = value;
passphrase.setDisabled(!value); passphraseSetting.setDisabled(!value);
dynamicIteration.setDisabled(!value); dynamicIteration.setDisabled(!value);
await this.plugin.saveSettings(); await this.plugin.saveSettings();
} else { } else {
this.plugin.settings.workingEncrypt = value; encrypt = value;
passphrase.setDisabled(!value); passphraseSetting.setDisabled(!value);
dynamicIteration.setDisabled(!value); dynamicIteration.setDisabled(!value);
await this.plugin.saveSettings(); await this.plugin.saveSettings();
markDirtyControl();
} }
}) })
); );
const passphrase = new Setting(containerRemoteDatabaseEl)
const markDirtyControl = () => {
passphraseSetting.controlEl.toggleClass("sls-item-dirty", passphrase != this.plugin.settings.passphrase);
e2e.controlEl.toggleClass("sls-item-dirty", encrypt != this.plugin.settings.encrypt);
dynamicIteration.controlEl.toggleClass("sls-item-dirty", useDynamicIterationCount != this.plugin.settings.useDynamicIterationCount)
}
const passphraseSetting = new Setting(containerRemoteDatabaseEl)
.setName("Passphrase") .setName("Passphrase")
.setDesc("Encrypting passphrase. If you change the passphrase of a existing database, overwriting the remote database is strongly recommended.") .setDesc("Encrypting passphrase. If you change the passphrase of a existing database, overwriting the remote database is strongly recommended.")
.addText((text) => { .addText((text) => {
text.setPlaceholder("") text.setPlaceholder("")
.setValue(this.plugin.settings.workingPassphrase) .setValue(passphrase)
.onChange(async (value) => { .onChange(async (value) => {
if (inWizard) { if (inWizard) {
this.plugin.settings.passphrase = value; this.plugin.settings.passphrase = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
} else { } else {
this.plugin.settings.workingPassphrase = value; passphrase = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
markDirtyControl();
} }
}); });
text.inputEl.setAttribute("type", "password"); text.inputEl.setAttribute("type", "password");
}); });
passphrase.setDisabled(!this.plugin.settings.workingEncrypt); passphraseSetting.setDisabled(!encrypt);
const dynamicIteration = new Setting(containerRemoteDatabaseEl) const dynamicIteration = new Setting(containerRemoteDatabaseEl)
.setName("Use dynamic iteration count (experimental)") .setName("Use dynamic iteration count (experimental)")
.setDesc("Balancing the encryption/decryption load against the length of the passphrase if toggled. (v0.17.5 or higher required)") .setDesc("Balancing the encryption/decryption load against the length of the passphrase if toggled. (v0.17.5 or higher required)")
.addToggle((toggle) => { .addToggle((toggle) => {
toggle.setValue(this.plugin.settings.workingUseDynamicIterationCount) toggle.setValue(useDynamicIterationCount)
.onChange(async (value) => { .onChange(async (value) => {
if (inWizard) { if (inWizard) {
this.plugin.settings.useDynamicIterationCount = value; this.plugin.settings.useDynamicIterationCount = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
} else { } else {
this.plugin.settings.workingUseDynamicIterationCount = value; useDynamicIterationCount = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
markDirtyControl();
} }
}); });
}) })
.setClass("wizardHidden"); .setClass("wizardHidden");
dynamicIteration.setDisabled(!this.plugin.settings.workingEncrypt); dynamicIteration.setDisabled(!encrypt);
const checkWorkingPassphrase = async (): Promise<boolean> => { const checkWorkingPassphrase = async (): Promise<boolean> => {
const settingForCheck: RemoteDBSettings = { const settingForCheck: RemoteDBSettings = {
...this.plugin.settings, ...this.plugin.settings,
encrypt: this.plugin.settings.workingEncrypt, encrypt: encrypt,
passphrase: this.plugin.settings.workingPassphrase, passphrase: passphrase,
useDynamicIterationCount: this.plugin.settings.workingUseDynamicIterationCount, useDynamicIterationCount: useDynamicIterationCount,
}; };
console.dir(settingForCheck); console.dir(settingForCheck);
const db = await this.plugin.localDatabase.connectRemoteCouchDBWithSetting(settingForCheck, this.plugin.localDatabase.isMobile); const db = await this.plugin.localDatabase.connectRemoteCouchDBWithSetting(settingForCheck, this.plugin.localDatabase.isMobile);
@@ -370,19 +385,19 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
} }
}; };
const applyEncryption = async (sendToServer: boolean) => { const applyEncryption = async (sendToServer: boolean) => {
if (this.plugin.settings.workingEncrypt && this.plugin.settings.workingPassphrase == "") { if (encrypt && passphrase == "") {
Logger("If you enable encryption, you have to set the passphrase", LOG_LEVEL.NOTICE); Logger("If you enable encryption, you have to set the passphrase", LOG_LEVEL.NOTICE);
return; return;
} }
if (this.plugin.settings.workingEncrypt && !(await testCrypt())) { if (encrypt && !(await testCrypt())) {
Logger("WARNING! Your device would not support encryption.", LOG_LEVEL.NOTICE); Logger("WARNING! Your device would not support encryption.", LOG_LEVEL.NOTICE);
return; return;
} }
if (!(await checkWorkingPassphrase()) && !sendToServer) { if (!(await checkWorkingPassphrase()) && !sendToServer) {
return; return;
} }
if (!this.plugin.settings.workingEncrypt) { if (!encrypt) {
this.plugin.settings.workingPassphrase = ""; passphrase = "";
} }
this.plugin.settings.liveSync = false; this.plugin.settings.liveSync = false;
this.plugin.settings.periodicReplication = false; this.plugin.settings.periodicReplication = false;
@@ -390,11 +405,12 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin.settings.syncOnStart = false; this.plugin.settings.syncOnStart = false;
this.plugin.settings.syncOnFileOpen = false; this.plugin.settings.syncOnFileOpen = false;
this.plugin.settings.syncAfterMerge = false; this.plugin.settings.syncAfterMerge = false;
this.plugin.settings.encrypt = this.plugin.settings.workingEncrypt; this.plugin.settings.encrypt = encrypt;
this.plugin.settings.passphrase = this.plugin.settings.workingPassphrase; this.plugin.settings.passphrase = passphrase;
this.plugin.settings.useDynamicIterationCount = this.plugin.settings.workingUseDynamicIterationCount; this.plugin.settings.useDynamicIterationCount = useDynamicIterationCount;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
markDirtyControl();
if (sendToServer) { if (sendToServer) {
await this.plugin.initializeDatabase(true); await this.plugin.initializeDatabase(true);
await this.plugin.markRemoteLocked(); await this.plugin.markRemoteLocked();
@@ -801,6 +817,23 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}) })
); );
containerLocalDatabaseEl.createEl("h3", {
text: sanitizeHTMLToDom(`Experimental`),
cls: "wizardHidden"
});
new Setting(containerLocalDatabaseEl)
.setName("Use new adapter")
.setDesc("This option is not compatible with a database made by older versions. Changing this configuration will fetch the remote database again.")
.setClass("wizardHidden")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.useIndexedDBAdapter).onChange(async (value) => {
this.plugin.settings.useIndexedDBAdapter = value;
await this.plugin.saveSettings();
await rebuildDB("localOnly");
})
);
addScreenElement("10", containerLocalDatabaseEl); addScreenElement("10", containerLocalDatabaseEl);
const containerGeneralSettingsEl = containerEl.createDiv(); const containerGeneralSettingsEl = containerEl.createDiv();
containerGeneralSettingsEl.createEl("h3", { text: "General Settings" }); containerGeneralSettingsEl.createEl("h3", { text: "General Settings" });
@@ -850,6 +883,14 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}); });
text.inputEl.setAttribute("type", "number"); text.inputEl.setAttribute("type", "number");
}); });
new Setting(containerGeneralSettingsEl)
.setName("Monitor changes to hidden files and plugin")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.watchInternalFileChanges).onChange(async (value) => {
this.plugin.settings.watchInternalFileChanges = value;
await this.plugin.saveSettings();
})
);
addScreenElement("20", containerGeneralSettingsEl); addScreenElement("20", containerGeneralSettingsEl);
@@ -1023,7 +1064,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}) })
); );
new Setting(containerSyncSettingEl) new Setting(containerSyncSettingEl)
.setName("Sync hidden files") .setName("Sync hidden files")
.addToggle((toggle) => .addToggle((toggle) =>
@@ -1032,14 +1072,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
await this.plugin.saveSettings(); await this.plugin.saveSettings();
}) })
); );
new Setting(containerSyncSettingEl)
.setName("Monitor changes to internal files")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.watchInternalFileChanges).onChange(async (value) => {
this.plugin.settings.watchInternalFileChanges = value;
await this.plugin.saveSettings();
})
);
new Setting(containerSyncSettingEl) new Setting(containerSyncSettingEl)
.setName("Scan for hidden files before replication") .setName("Scan for hidden files before replication")
.setDesc("This configuration will be ignored if monitoring changes is enabled.") .setDesc("This configuration will be ignored if monitoring changes is enabled.")
@@ -1184,8 +1217,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setValue(this.plugin.settings.customChunkSize + "") .setValue(this.plugin.settings.customChunkSize + "")
.onChange(async (value) => { .onChange(async (value) => {
let v = Number(value); let v = Number(value);
if (isNaN(v) || v < 100) { if (isNaN(v) || v < 1) {
v = 100; v = 1;
} }
this.plugin.settings.customChunkSize = v; this.plugin.settings.customChunkSize = v;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
@@ -1324,6 +1357,45 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}) })
); );
const passphrase_options: Record<ConfigPassphraseStore, string> = {
"": "Default",
LOCALSTORAGE: "Use a custom passphrase",
ASK_AT_LAUNCH: "Ask an passphrase at every launch",
}
new Setting(containerMiscellaneousEl)
.setName("Encrypting sensitive configuration items")
.addDropdown((dropdown) =>
dropdown
.addOptions(passphrase_options)
.setValue(this.plugin.settings.configPassphraseStore)
.onChange(async (value) => {
this.plugin.settings.configPassphraseStore = value as ConfigPassphraseStore;
this.plugin.usedPassphrase = "";
confPassphraseSetting.setDisabled(this.plugin.settings.configPassphraseStore != "LOCALSTORAGE");
await this.plugin.saveSettings();
})
)
.setClass("wizardHidden");
const confPassphrase = localStorage.getItem("ls-setting-passphrase") || "";
const confPassphraseSetting = new Setting(containerMiscellaneousEl)
.setName("Passphrase of sensitive configuration items")
.setDesc("This passphrase will not be copied to another device. It will be set to `Default` until you configure it again.")
.addText((text) => {
text.setPlaceholder("")
.setValue(confPassphrase)
.onChange(async (value) => {
this.plugin.usedPassphrase = "";
localStorage.setItem("ls-setting-passphrase", value);
await this.plugin.saveSettings();
markDirtyControl();
});
text.inputEl.setAttribute("type", "password");
})
.setClass("wizardHidden");
confPassphraseSetting.setDisabled(this.plugin.settings.configPassphraseStore != "LOCALSTORAGE");
const infoApply = containerMiscellaneousEl.createEl("div", { text: `To finish setup, please select one of the presets` }); const infoApply = containerMiscellaneousEl.createEl("div", { text: `To finish setup, please select one of the presets` });
infoApply.addClass("op-warn-info"); infoApply.addClass("op-warn-info");
infoApply.addClass("wizardOnly") infoApply.addClass("wizardOnly")
@@ -1365,7 +1437,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
pluginConfig.couchDB_URI = isCloudantURI(pluginConfig.couchDB_URI) ? "cloudant" : "self-hosted"; pluginConfig.couchDB_URI = isCloudantURI(pluginConfig.couchDB_URI) ? "cloudant" : "self-hosted";
pluginConfig.couchDB_USER = REDACTED; pluginConfig.couchDB_USER = REDACTED;
pluginConfig.passphrase = REDACTED; pluginConfig.passphrase = REDACTED;
pluginConfig.workingPassphrase = REDACTED; pluginConfig.encryptedPassphrase = REDACTED;
pluginConfig.encryptedCouchDBConnection = REDACTED;
const msgConfig = `----remote config---- const msgConfig = `----remote config----
${stringifyYaml(responseConfig)} ${stringifyYaml(responseConfig)}
@@ -1423,7 +1496,7 @@ ${stringifyYaml(pluginConfig)}`;
const releaser = await semaphore.acquire(1, "verifyAndRepair"); const releaser = await semaphore.acquire(1, "verifyAndRepair");
try { try {
Logger(`Update into ${file.path}`); Logger(`UPDATE DATABASE ${file.path}`);
await this.plugin.updateIntoDB(file, false, null, true); await this.plugin.updateIntoDB(file, false, null, true);
i++; i++;
Logger(`${i}/${files.length}\n${file.path}`, LOG_LEVEL.NOTICE, "verify"); Logger(`${i}/${files.length}\n${file.path}`, LOG_LEVEL.NOTICE, "verify");
@@ -1495,7 +1568,7 @@ ${stringifyYaml(pluginConfig)}`;
new Setting(containerPluginSettings) new Setting(containerPluginSettings)
.setName("Scan plugins periodically") .setName("Scan plugins periodically")
.setDesc("Scan plugins every 1 minute.") .setDesc("Scan plugins every 1 minute. This configuration will be ignored if monitoring changes of hidden files has been enabled.")
.addToggle((toggle) => .addToggle((toggle) =>
toggle.setValue(this.plugin.settings.autoSweepPluginsPeriodic).onChange(async (value) => { toggle.setValue(this.plugin.settings.autoSweepPluginsPeriodic).onChange(async (value) => {
this.plugin.settings.autoSweepPluginsPeriodic = value; this.plugin.settings.autoSweepPluginsPeriodic = value;

View File

@@ -2,7 +2,7 @@
import ObsidianLiveSyncPlugin from "./main"; import ObsidianLiveSyncPlugin from "./main";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { DevicePluginList, PluginDataEntry } from "./types"; import { DevicePluginList, PluginDataEntry } from "./types";
import { versionNumberString2Number } from "./lib/src/utils"; import { versionNumberString2Number } from "./lib/src/strbin";
type JudgeResult = "" | "NEWER" | "EVEN" | "EVEN_BUT_DIFFERENT" | "OLDER" | "REMOTE_ONLY"; type JudgeResult = "" | "NEWER" | "EVEN" | "EVEN_BUT_DIFFERENT" | "OLDER" | "REMOTE_ONLY";

View File

@@ -52,14 +52,14 @@ export class InputStringDialog extends Modal {
const { contentEl } = this; const { contentEl } = this;
contentEl.createEl("h1", { text: this.title }); contentEl.createEl("h1", { text: this.title });
// For enter to submit
new Setting(contentEl).setName(this.key).addText((text) => const formEl = contentEl.createEl("form");
new Setting(formEl).setName(this.key).addText((text) =>
text.onChange((value) => { text.onChange((value) => {
this.result = value; this.result = value;
}) })
); );
new Setting(formEl).addButton((btn) =>
new Setting(contentEl).addButton((btn) =>
btn btn
.setButtonText("Ok") .setButtonText("Ok")
.setCta() .setCta()

Submodule src/lib updated: 4ef5986b4d...9e993fd984

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
import { PluginManifest } from "obsidian"; import { PluginManifest, TFile } from "obsidian";
import { DatabaseEntry } from "./lib/src/types"; import { DatabaseEntry, EntryBody } from "./lib/src/types";
export interface PluginDataEntry extends DatabaseEntry { export interface PluginDataEntry extends DatabaseEntry {
deviceVaultName: string; deviceVaultName: string;
@@ -30,3 +30,20 @@ export interface InternalFileInfo {
size: number; size: number;
deleted?: boolean; deleted?: boolean;
} }
export interface FileInfo {
path: string;
mtime: number;
ctime: number;
size: number;
deleted?: boolean;
file: TFile;
}
export type queueItem = {
entry: EntryBody;
missingChildren: string[];
timeout?: number;
done?: boolean;
warned?: boolean;
};

View File

@@ -1,6 +1,6 @@
import { normalizePath } from "obsidian"; import { normalizePath } from "obsidian";
import { path2id_base, id2path_base } from "./lib/src/utils"; import { path2id_base, id2path_base } from "./lib/src/path";
// For backward compatibility, using the path for determining id. // For backward compatibility, using the path for determining id.
// Only CouchDB unacceptable ID (that starts with an underscore) has been prefixed with "/". // Only CouchDB unacceptable ID (that starts with an underscore) has been prefixed with "/".

View File

@@ -101,7 +101,7 @@
.CodeMirror-wrap::before, .CodeMirror-wrap::before,
.cm-s-obsidian>.cm-editor::before, .cm-s-obsidian>.cm-editor::before,
.canvas-wrapper::before { .canvas-wrapper::before {
content: var(--slsmessage); content: attr(data-log);
text-align: right; text-align: right;
white-space: pre-wrap; white-space: pre-wrap;
position: absolute; position: absolute;
@@ -247,4 +247,8 @@ div.sls-setting-menu-btn {
.sls-setting:not(.isWizard) .wizardOnly { .sls-setting:not(.isWizard) .wizardOnly {
display: none; display: none;
}
.sls-item-dirty::before {
content: "✏";
} }

View File

@@ -35,42 +35,57 @@
- Fixed button styling. - Fixed button styling.
- Changed: - Changed:
- Conflict checking on synchronising has been enabled for every note in default. - Conflict checking on synchronising has been enabled for every note in default.
- 0.17.8 - 0.17.8
- Improved: Performance improved. Prebuilt PouchDB is no longer used. - Improved: Performance improved. Prebuilt PouchDB is no longer used.
- Fixed: Merging hidden files is also fixed. - Fixed: Merging hidden files is also fixed.
- New Feature: Now we can synchronise automatically after merging conflicts. - New Feature: Now we can synchronise automatically after merging conflicts.
- 0.17.9
- Fixed: Conflict merge of internal files is no longer broken.
- Improved: Smoother status display inside the editor.
- 0.17.10
- Fixed: Large file synchronising has been now addressed!
Note: When synchronising large files, we have to set `Chunk size` to lower than 50, disable `Read chunks online`, `Batch size` should be set 50-100, and `Batch limit` could be around 20.
- 0.17.11
- Fixed:
- Performance improvement
- Now `Chunk size` can be set to under one hundred.
- New feature:
- The number of transfers required before replication stabilises is now displayed.
- 0.17.12: Skipped.
- 0.17.13
- Fixed: Document history is now displayed again.
- Reorganised: Many files have been refactored.
- 0.17.14: Skipped.
- 0.17.15
- Improved:
- Confidential information has no longer stored in data.json as is.
- Synchronising progress has been shown in the notification.
- We can commit passphrases with a keyboard.
- Configuration which had not been saved yet is marked now.
- Now the filename is shown on the Conflict resolving dialog
- Fixed:
- Hidden files have been synchronised again.
- Rename of files has been fixed again.
And, minor changes have been included.
### 0.16.0 - 0.17.16:
- Now hidden files need not be scanned. Changes will be detected automatically. - Improved:
- If you want it to back to its previous behaviour, please disable `Monitor changes to internal files`. - Plugins and their settings no longer need scanning if changes are monitored.
- Due to using an internal API, this feature may become unusable with a major update. If this happens, please disable this once. - Now synchronising plugins and their settings are performed parallelly and faster.
- We can place `redflag2.md` to rebuild the database automatically while the boot sequence.
- Experimental:
- We can use a new adapter on PouchDB. This will make us smoother.
- Note: Not compatible with the older version.
- Fixed:
- The default batch size is smaller again.
- Plugins and their setting can be synchronised again.
- Hidden files and plugins are correctly scanned while rebuilding.
- Files with the name started `_` are also being performed conflict-checking.
- 0.17.17
- Fixed: Now we can merge JSON files even if we failed to compare items like null.
- 0.17.18
- Fixed: Fixed lack of error handling.
- 0.17.19
- Fixed: Error reporting has been ensured.
#### Minors ... To continue on to `updates_old.md`.
- 0.16.1 Added missing log updates.
- 0.16.2 Fixed many problems caused by combinations of `Sync On Save` and the tracking logic that changed at 0.15.6.
- 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!
- 0.16.8 Now we can synchronise without `bad_request:invalid UTF-8 JSON` even while end-to-end encryption has been disabled.
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.

View File

@@ -1,3 +1,58 @@
### 0.16.0
- 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`.
- Due to using an internal API, this feature may become unusable with a major update. If this happens, please disable this once.
#### Minors
- 0.16.1 Added missing log updates.
- 0.16.2 Fixed many problems caused by combinations of `Sync On Save` and the tracking logic that changed at 0.15.6.
- 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!
- 0.16.8 Now we can synchronise without `bad_request:invalid UTF-8 JSON` even while end-to-end encryption has been disabled.
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.
- 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.
### 0.14.1 ### 0.14.1
- The target selecting filter was implemented. - The target selecting filter was implemented.
Now we can set what files are synchronised by regular expression. Now we can set what files are synchronised by regular expression.