mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2025-12-13 17:55:56 +00:00
Implemented:
- Exporting settings and setup from uri. Fixed: - Change "Leaf" into "Chunk" - Reduced meaninglessly verbose logging - Trimmed deadcode.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-livesync",
|
||||
"name": "Self-hosted LiveSync",
|
||||
"version": "0.10.1",
|
||||
"version": "0.11.0",
|
||||
"minAppVersion": "0.9.12",
|
||||
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"author": "vorotamoroz",
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.10.1",
|
||||
"version": "0.11.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.10.1",
|
||||
"version": "0.11.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"diff-match-patch": "^1.0.5",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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.",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
MAX_DOC_SIZE,
|
||||
MAX_DOC_SIZE_BIN,
|
||||
NODEINFO_DOCID,
|
||||
RECENT_MOFIDIED_DOCS_QTY,
|
||||
VER,
|
||||
MILSTONE_DOCID,
|
||||
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() {
|
||||
const db = new PouchDB<EntryDoc>(this.dbname + "-livesync", {
|
||||
auto_compaction: this.settings.useHistory ? false : true,
|
||||
@@ -305,7 +291,7 @@ export class LocalPouchDB {
|
||||
waitForLeafReady(id: string): Promise<boolean> {
|
||||
return new Promise((res, rej) => {
|
||||
// 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") {
|
||||
this.leafArrivedCallbacks[id] = [];
|
||||
}
|
||||
@@ -329,21 +315,21 @@ export class LocalPouchDB {
|
||||
this.hashCaches.set(id, w.data);
|
||||
return w.data;
|
||||
}
|
||||
throw new Error(`retrive leaf, but it was not leaf.`);
|
||||
throw new Error(`Corrupted chunk detected.`);
|
||||
} catch (ex) {
|
||||
if (ex.status && ex.status == 404) {
|
||||
if (waitForReady) {
|
||||
// just leaf is not ready.
|
||||
// wait for on
|
||||
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);
|
||||
} else {
|
||||
throw new Error("Leaf was not found");
|
||||
throw new Error("Chunk was not found");
|
||||
}
|
||||
} else {
|
||||
Logger(`Something went wrong on retriving leaf`);
|
||||
Logger(`Something went wrong on retriving chunk`);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
@@ -447,11 +433,11 @@ export class LocalPouchDB {
|
||||
try {
|
||||
childrens = await Promise.all(obj.children.map((e) => this.getDBLeaf(e, waitForReady)));
|
||||
if (dump) {
|
||||
Logger(`childrens:`);
|
||||
Logger(`Chunks:`);
|
||||
Logger(childrens);
|
||||
}
|
||||
} 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);
|
||||
this.corruptedEntries[obj._id] = obj;
|
||||
return false;
|
||||
@@ -515,7 +501,7 @@ export class LocalPouchDB {
|
||||
if (!obj.type || (obj.type && obj.type == "notes")) {
|
||||
obj._deleted = true;
|
||||
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") {
|
||||
delete this.corruptedEntries[obj._id];
|
||||
}
|
||||
@@ -526,7 +512,6 @@ export class LocalPouchDB {
|
||||
obj._deleted = true;
|
||||
const r = await this.localDatabase.put(obj);
|
||||
Logger(`entry removed:${obj._id}-${r.rev}`);
|
||||
this.updateRecentModifiedDocs(r.id, r.rev, true);
|
||||
if (typeof this.corruptedEntries[obj._id] != "undefined") {
|
||||
delete this.corruptedEntries[obj._id];
|
||||
}
|
||||
@@ -579,7 +564,6 @@ export class LocalPouchDB {
|
||||
const item = await this.localDatabase.get(v);
|
||||
item._deleted = true;
|
||||
await this.localDatabase.put(item);
|
||||
this.updateRecentModifiedDocs(item._id, item._rev, true);
|
||||
});
|
||||
|
||||
deleteCount++;
|
||||
@@ -702,21 +686,21 @@ export class LocalPouchDB {
|
||||
try {
|
||||
const result = await this.localDatabase.bulkDocs(newLeafs);
|
||||
for (const item of result) {
|
||||
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).ok) {
|
||||
if ((item as any).status && (item as any).status == 409) {
|
||||
// conflicted, but it would be ok in childrens.
|
||||
} 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);
|
||||
saved = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (saved) {
|
||||
Logger(`Chunk saved:${newLeafs.length} chunks`);
|
||||
}
|
||||
} catch (ex) {
|
||||
Logger("ERROR ON SAVING LEAVES:", LOG_LEVEL.NOTICE);
|
||||
Logger("Chunk save failed:", LOG_LEVEL.NOTICE);
|
||||
Logger(ex, LOG_LEVEL.NOTICE);
|
||||
saved = false;
|
||||
}
|
||||
@@ -748,7 +732,6 @@ export class LocalPouchDB {
|
||||
}
|
||||
}
|
||||
const r = await this.localDatabase.put(newDoc, { force: true });
|
||||
this.updateRecentModifiedDocs(r.id, r.rev, newDoc._deleted);
|
||||
if (typeof this.corruptedEntries[note._id] != "undefined") {
|
||||
delete this.corruptedEntries[note._id];
|
||||
}
|
||||
|
||||
119
src/main.ts
119
src/main.ts
@@ -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 { 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 { id2path, path2id } from "./utils";
|
||||
import { decrypt, encrypt } from "./lib/src/e2ee";
|
||||
const isDebug = false;
|
||||
setNoticeClass(Notice);
|
||||
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 {
|
||||
settings: ObsidianLiveSyncSettings;
|
||||
@@ -200,6 +240,81 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
||||
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({
|
||||
id: "livesync-replicate",
|
||||
name: "Replicate now",
|
||||
@@ -587,8 +702,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
||||
//--> Basic document Functions
|
||||
notifies: { [key: string]: { notice: Notice; timer: NodeJS.Timeout; count: number } } = {};
|
||||
|
||||
// eslint-disable-next-line require-await
|
||||
lastLog = "";
|
||||
// eslint-disable-next-line require-await
|
||||
async addLog(message: any, level: LOG_LEVEL = LOG_LEVEL.INFO) {
|
||||
if (level == LOG_LEVEL.DEBUG && !isDebug) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user