Implemented:

- Exporting settings and setup from uri.

Fixed:
- Change "Leaf" into "Chunk"
- Reduced meaninglessly verbose logging
- Trimmed deadcode.
This commit is contained in:
vorotamoroz
2022-06-10 18:48:04 +09:00
parent 236f2293ce
commit 3783fc6926
5 changed files with 135 additions and 37 deletions

View File

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

4
package-lock.json generated
View File

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

View File

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

View File

@@ -16,7 +16,6 @@ import {
MAX_DOC_SIZE, MAX_DOC_SIZE,
MAX_DOC_SIZE_BIN, MAX_DOC_SIZE_BIN,
NODEINFO_DOCID, NODEINFO_DOCID,
RECENT_MOFIDIED_DOCS_QTY,
VER, VER,
MILSTONE_DOCID, MILSTONE_DOCID,
DatabaseConnectingStatus, DatabaseConnectingStatus,
@@ -142,19 +141,6 @@ export class LocalPouchDB {
} }
} }
updateRecentModifiedDocs(id: string, rev: string, deleted: boolean) {
const idrev = id + rev;
if (deleted) {
this.recentModifiedDocs = this.recentModifiedDocs.filter((e) => e != idrev);
} else {
this.recentModifiedDocs.push(idrev);
this.recentModifiedDocs = this.recentModifiedDocs.slice(0 - RECENT_MOFIDIED_DOCS_QTY);
}
}
isSelfModified(id: string, rev: string): boolean {
const idrev = id + rev;
return this.recentModifiedDocs.indexOf(idrev) !== -1;
}
async isOldDatabaseExists() { async isOldDatabaseExists() {
const db = new PouchDB<EntryDoc>(this.dbname + "-livesync", { const db = new PouchDB<EntryDoc>(this.dbname + "-livesync", {
auto_compaction: this.settings.useHistory ? false : true, auto_compaction: this.settings.useHistory ? false : true,
@@ -305,7 +291,7 @@ export class LocalPouchDB {
waitForLeafReady(id: string): Promise<boolean> { waitForLeafReady(id: string): Promise<boolean> {
return new Promise((res, rej) => { return new Promise((res, rej) => {
// Set timeout. // Set timeout.
const timer = setTimeout(() => rej(new Error(`Leaf timed out:${id}`)), LEAF_WAIT_TIMEOUT); const timer = setTimeout(() => rej(new Error(`Chunk reading timed out:${id}`)), LEAF_WAIT_TIMEOUT);
if (typeof this.leafArrivedCallbacks[id] == "undefined") { if (typeof this.leafArrivedCallbacks[id] == "undefined") {
this.leafArrivedCallbacks[id] = []; this.leafArrivedCallbacks[id] = [];
} }
@@ -329,21 +315,21 @@ export class LocalPouchDB {
this.hashCaches.set(id, w.data); this.hashCaches.set(id, w.data);
return w.data; return w.data;
} }
throw new Error(`retrive leaf, but it was not leaf.`); throw new Error(`Corrupted chunk detected.`);
} catch (ex) { } catch (ex) {
if (ex.status && ex.status == 404) { if (ex.status && ex.status == 404) {
if (waitForReady) { if (waitForReady) {
// just leaf is not ready. // just leaf is not ready.
// wait for on // wait for on
if ((await this.waitForLeafReady(id)) === false) { if ((await this.waitForLeafReady(id)) === false) {
throw new Error(`time out (waiting leaf)`); throw new Error(`time out (waiting chunk)`);
} }
return this.getDBLeaf(id, false); return this.getDBLeaf(id, false);
} else { } else {
throw new Error("Leaf was not found"); throw new Error("Chunk was not found");
} }
} else { } else {
Logger(`Something went wrong on retriving leaf`); Logger(`Something went wrong on retriving chunk`);
throw ex; throw ex;
} }
} }
@@ -447,11 +433,11 @@ export class LocalPouchDB {
try { try {
childrens = await Promise.all(obj.children.map((e) => this.getDBLeaf(e, waitForReady))); childrens = await Promise.all(obj.children.map((e) => this.getDBLeaf(e, waitForReady)));
if (dump) { if (dump) {
Logger(`childrens:`); Logger(`Chunks:`);
Logger(childrens); Logger(childrens);
} }
} catch (ex) { } catch (ex) {
Logger(`Something went wrong on reading elements of ${obj._id} from database:`, LOG_LEVEL.NOTICE); Logger(`Something went wrong on reading chunks of ${obj._id} from database:`, LOG_LEVEL.NOTICE);
Logger(ex, LOG_LEVEL.VERBOSE); Logger(ex, LOG_LEVEL.VERBOSE);
this.corruptedEntries[obj._id] = obj; this.corruptedEntries[obj._id] = obj;
return false; return false;
@@ -515,7 +501,7 @@ export class LocalPouchDB {
if (!obj.type || (obj.type && obj.type == "notes")) { if (!obj.type || (obj.type && obj.type == "notes")) {
obj._deleted = true; obj._deleted = true;
const r = await this.localDatabase.put(obj); const r = await this.localDatabase.put(obj);
this.updateRecentModifiedDocs(r.id, r.rev, true); Logger(`entry removed:${obj._id}-${r.rev}`);
if (typeof this.corruptedEntries[obj._id] != "undefined") { if (typeof this.corruptedEntries[obj._id] != "undefined") {
delete this.corruptedEntries[obj._id]; delete this.corruptedEntries[obj._id];
} }
@@ -526,7 +512,6 @@ export class LocalPouchDB {
obj._deleted = true; obj._deleted = true;
const r = await this.localDatabase.put(obj); const r = await this.localDatabase.put(obj);
Logger(`entry removed:${obj._id}-${r.rev}`); Logger(`entry removed:${obj._id}-${r.rev}`);
this.updateRecentModifiedDocs(r.id, r.rev, true);
if (typeof this.corruptedEntries[obj._id] != "undefined") { if (typeof this.corruptedEntries[obj._id] != "undefined") {
delete this.corruptedEntries[obj._id]; delete this.corruptedEntries[obj._id];
} }
@@ -579,7 +564,6 @@ export class LocalPouchDB {
const item = await this.localDatabase.get(v); const item = await this.localDatabase.get(v);
item._deleted = true; item._deleted = true;
await this.localDatabase.put(item); await this.localDatabase.put(item);
this.updateRecentModifiedDocs(item._id, item._rev, true);
}); });
deleteCount++; deleteCount++;
@@ -702,21 +686,21 @@ export class LocalPouchDB {
try { try {
const result = await this.localDatabase.bulkDocs(newLeafs); const result = await this.localDatabase.bulkDocs(newLeafs);
for (const item of result) { for (const item of result) {
if ((item as any).ok) { if (!(item as any).ok) {
this.updateRecentModifiedDocs(item.id, item.rev, false);
Logger(`save ok:id:${item.id} rev:${item.rev}`, LOG_LEVEL.VERBOSE);
} else {
if ((item as any).status && (item as any).status == 409) { if ((item as any).status && (item as any).status == 409) {
// conflicted, but it would be ok in childrens. // conflicted, but it would be ok in childrens.
} else { } else {
Logger(`save failed:id:${item.id} rev:${item.rev}`, LOG_LEVEL.NOTICE); Logger(`Save failed:id:${item.id} rev:${item.rev}`, LOG_LEVEL.NOTICE);
Logger(item); Logger(item);
saved = false; saved = false;
} }
} }
} }
if (saved) {
Logger(`Chunk saved:${newLeafs.length} chunks`);
}
} catch (ex) { } catch (ex) {
Logger("ERROR ON SAVING LEAVES:", LOG_LEVEL.NOTICE); Logger("Chunk save failed:", LOG_LEVEL.NOTICE);
Logger(ex, LOG_LEVEL.NOTICE); Logger(ex, LOG_LEVEL.NOTICE);
saved = false; saved = false;
} }
@@ -748,7 +732,6 @@ export class LocalPouchDB {
} }
} }
const r = await this.localDatabase.put(newDoc, { force: true }); const r = await this.localDatabase.put(newDoc, { force: true });
this.updateRecentModifiedDocs(r.id, r.rev, newDoc._deleted);
if (typeof this.corruptedEntries[note._id] != "undefined") { if (typeof this.corruptedEntries[note._id] != "undefined") {
delete this.corruptedEntries[note._id]; delete this.corruptedEntries[note._id];
} }

View File

@@ -1,4 +1,4 @@
import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, PluginManifest, Modal, App } from "obsidian"; import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, PluginManifest, Modal, App, FuzzySuggestModal } from "obsidian";
import { 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 } 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 } from "./lib/src/types";
@@ -28,6 +28,7 @@ import { DocumentHistoryModal } from "./DocumentHistoryModal";
import PluginPane from "./PluginPane.svelte"; import PluginPane from "./PluginPane.svelte";
import { id2path, path2id } from "./utils"; import { id2path, path2id } from "./utils";
import { decrypt, encrypt } from "./lib/src/e2ee";
const isDebug = false; const isDebug = false;
setNoticeClass(Notice); setNoticeClass(Notice);
class PluginDialogModal extends Modal { class PluginDialogModal extends Modal {
@@ -57,6 +58,45 @@ class PluginDialogModal extends Modal {
} }
} }
} }
class PopoverYesNo extends FuzzySuggestModal<string> {
app: App;
callback: (e: string) => void = () => {};
constructor(app: App, note: string, callback: (e: string) => void) {
super(app);
this.app = app;
this.setPlaceholder("y/n) " + note);
this.callback = callback;
}
getItems(): string[] {
return ["yes", "no"];
}
getItemText(item: string): string {
return item;
}
onChooseItem(item: string, evt: MouseEvent | KeyboardEvent): void {
// debugger;
this.callback(item);
this.callback = null;
}
onClose(): void {
setTimeout(() => {
if (this.callback != null) {
this.callback("");
}
}, 100);
}
}
const askYesNo = (app: App, message: string): Promise<"yes" | "no"> => {
return new Promise((res) => {
const popover = new PopoverYesNo(app, message, (result) => res(result as "yes" | "no"));
popover.open();
});
};
export default class ObsidianLiveSyncPlugin extends Plugin { export default class ObsidianLiveSyncPlugin extends Plugin {
settings: ObsidianLiveSyncSettings; settings: ObsidianLiveSyncSettings;
@@ -200,6 +240,81 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
Logger(ex, LOG_LEVEL.VERBOSE); Logger(ex, LOG_LEVEL.VERBOSE);
} }
}); });
this.addCommand({
id: "livesync-exportconfig",
name: "Copy setup uri (beta)",
callback: async () => {
const encryptedSetting = encodeURIComponent(await encrypt(JSON.stringify(this.settings), "---"));
const uri = `obsidian://setuplivesync?settings=${encryptedSetting}`;
await navigator.clipboard.writeText(uri);
Logger("Setup uri copied to clipboard", LOG_LEVEL.NOTICE);
},
});
this.registerObsidianProtocolHandler("setuplivesync", async (conf: any) => {
try {
const oldConf = JSON.parse(JSON.stringify(this.settings));
const newconf = await JSON.parse(await decrypt(conf.settings, "---"));
if (newconf) {
const result = await askYesNo(this.app, "Importing LiveSync's conf, OK?");
if (result == "yes") {
const newSettingW = Object.assign({}, this.settings, newconf);
// stopping once.
this.localDatabase.closeReplication();
this.settings.suspendFileWatching = true;
console.dir(newSettingW);
const keepLocalDB = await askYesNo(this.app, "Keep local DB?");
const keepRemoteDB = await askYesNo(this.app, "Keep remote DB?");
if (keepLocalDB == "yes" && keepRemoteDB == "yes") {
// nothing to do. so peaceful.
this.settings = newSettingW;
await this.saveSettings();
Logger("Configuration loaded.", LOG_LEVEL.NOTICE);
return;
}
if (keepLocalDB == "no" && keepRemoteDB == "no") {
const reset = await askYesNo(this.app, "Drop everything?");
if (reset != "yes") {
Logger("Cancelled", LOG_LEVEL.NOTICE);
this.settings = oldConf;
return;
}
}
let initDB;
await this.saveSettings();
if (keepLocalDB == "no") {
this.resetLocalOldDatabase();
this.resetLocalDatabase();
this.localDatabase.initializeDatabase();
const rebuild = await askYesNo(this.app, "Rebuild the database?");
if (rebuild == "yes") {
initDB = this.initializeDatabase(true);
} else {
this.markRemoteResolved();
}
}
if (keepRemoteDB == "no") {
await this.tryResetRemoteDatabase();
await this.markRemoteLocked();
}
if (keepLocalDB == "no" || keepRemoteDB == "no") {
const replicate = await askYesNo(this.app, "Replicate once?");
if (replicate == "yes") {
if (initDB != null) {
await initDB;
}
await this.replicate(true);
}
}
}
Logger("Configuration loaded.", LOG_LEVEL.NOTICE);
} else {
Logger("Cancelled.", LOG_LEVEL.NOTICE);
}
} catch (ex) {
Logger("Couldn't parse configuration uri.", LOG_LEVEL.NOTICE);
}
});
this.addCommand({ this.addCommand({
id: "livesync-replicate", id: "livesync-replicate",
name: "Replicate now", name: "Replicate now",
@@ -587,8 +702,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
//--> Basic document Functions //--> Basic document Functions
notifies: { [key: string]: { notice: Notice; timer: NodeJS.Timeout; count: number } } = {}; notifies: { [key: string]: { notice: Notice; timer: NodeJS.Timeout; count: number } } = {};
// eslint-disable-next-line require-await
lastLog = ""; lastLog = "";
// eslint-disable-next-line require-await
async addLog(message: any, level: LOG_LEVEL = LOG_LEVEL.INFO) { async addLog(message: any, level: LOG_LEVEL = LOG_LEVEL.INFO) {
if (level == LOG_LEVEL.DEBUG && !isDebug) { if (level == LOG_LEVEL.DEBUG && !isDebug) {
return; return;