1
0
mirror of https://github.com/vrtmrz/obsidian-livesync.git synced 2024-12-12 09:04:06 +02:00

Fixing issues and tidy up the setting dialog.

Fixed timing problem of synchronization note and contents.
Tidy up the setting dialog.
Add Escape hatch
This commit is contained in:
vorotamoroz 2021-10-19 17:53:54 +09:00
parent 20bdf057fe
commit 39e2eab023
3 changed files with 200 additions and 44 deletions

238
main.ts
View File

@ -11,7 +11,7 @@ const MAX_DOC_SIZE_BIN = 102400; // 100kb
const VER = 10; const VER = 10;
const RECENT_MOFIDIED_DOCS_QTY = 30; const RECENT_MOFIDIED_DOCS_QTY = 30;
const LEAF_WAIT_TIMEOUT = 30000; // in synchronization, waiting missing leaf time out.
const LOG_LEVEL = { const LOG_LEVEL = {
VERBOSE: 1, VERBOSE: 1,
INFO: 10, INFO: 10,
@ -34,6 +34,7 @@ interface ObsidianLiveSyncSettings {
minimumChunkSize: number; minimumChunkSize: number;
longLineThreshold: number; longLineThreshold: number;
showVerboseLog: boolean; showVerboseLog: boolean;
suspendFileWatching: boolean;
} }
const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = { const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = {
@ -45,11 +46,12 @@ const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = {
syncOnStart: false, syncOnStart: false,
savingDelay: 200, savingDelay: 200,
lessInformationInLog: false, lessInformationInLog: false,
gcDelay: 30, gcDelay: 300,
versionUpFlash: "", versionUpFlash: "",
minimumChunkSize: 20, minimumChunkSize: 20,
longLineThreshold: 250, longLineThreshold: 250,
showVerboseLog: false, showVerboseLog: false,
suspendFileWatching: false,
}; };
interface Entry { interface Entry {
_id: string; _id: string;
@ -99,7 +101,9 @@ interface EntryLeaf {
_rev?: string; _rev?: string;
} }
type EntryDoc = Entry | NewEntry | PlainEntry | LoadedEntry | EntryLeaf; type EntryBody = Entry | NewEntry | PlainEntry;
type EntryDoc = EntryBody | LoadedEntry | EntryLeaf;
type diff_result_leaf = { type diff_result_leaf = {
rev: string; rev: string;
data: string; data: string;
@ -188,7 +192,7 @@ const connectRemoteCouchDB = async (uri: string, auth: { username: string; passw
let info = await db.info(); let info = await db.info();
return { db: db, info: info }; return { db: db, info: info };
} catch (ex) { } catch (ex) {
return; return false;
} }
}; };
@ -212,6 +216,8 @@ class LocalPouchDB {
[key: string]: string; [key: string]: string;
} = {}; } = {};
corruptedEntries: { [key: string]: EntryDoc } = {};
constructor(app: App, plugin: ObsidianLiveSyncPlugin, dbname: string) { constructor(app: App, plugin: ObsidianLiveSyncPlugin, dbname: string) {
this.plugin = plugin; this.plugin = plugin;
this.app = app; this.app = app;
@ -251,23 +257,112 @@ class LocalPouchDB {
let idrev = id + rev; let idrev = id + rev;
return this.recentModifiedDocs.indexOf(idrev) !== -1; return this.recentModifiedDocs.indexOf(idrev) !== -1;
} }
changeHandler: PouchDB.Core.Changes<{}> = null;
async initializeDatabase() { async initializeDatabase() {
if (this.localDatabase != null) this.localDatabase.close(); if (this.localDatabase != null) this.localDatabase.close();
if (this.changeHandler != null) {
this.changeHandler.cancel();
}
this.localDatabase = null; this.localDatabase = null;
this.localDatabase = new PouchDB<EntryDoc>(this.dbname + "-livesync", { this.localDatabase = new PouchDB<EntryDoc>(this.dbname + "-livesync", {
auto_compaction: true, auto_compaction: true,
revs_limit: 100, revs_limit: 100,
deterministic_revs: true, deterministic_revs: true,
}); });
// Traceing the leaf id
let changes = this.localDatabase
.changes({
since: "now",
live: true,
filter: (doc) => doc.type == "leaf",
})
.on("change", (e) => {
if (e.deleted) return;
this.leafArrived(e.id);
});
this.changeHandler = changes;
await this.prepareHashFunctions(); await this.prepareHashFunctions();
} }
async prepareHashFunctions() { async prepareHashFunctions() {
if (this.h32 != null) return; if (this.h32 != null) return;
const { h32, h64 } = await xxhash(); const { h32, h64 } = await xxhash();
this.h32 = h32; this.h32 = h32;
this.h64 = h64; this.h64 = h64;
} }
async getDBEntry(id: string, opt?: PouchDB.Core.GetOptions): Promise<false | LoadedEntry> {
// leaf waiting
leafArrivedCallbacks: { [key: string]: (() => void)[] } = {};
leafArrived(id: string) {
if (typeof this.leafArrivedCallbacks[id] !== "undefined") {
for (let func of this.leafArrivedCallbacks[id]) {
func();
}
delete this.leafArrivedCallbacks[id];
}
}
// wait
waitForLeafReady(id: string): Promise<boolean> {
return new Promise((_, res) => {
// Set timeout.
let timer = setTimeout(() => res(false), LEAF_WAIT_TIMEOUT);
if (typeof this.leafArrivedCallbacks[id] == "undefined") {
this.leafArrivedCallbacks[id] = [];
}
this.leafArrivedCallbacks[id].push(() => {
clearTimeout(timer);
res(true);
});
});
}
async getDBLeaf(id: string): Promise<string> {
// when in cache, use that.
if (this.hashCacheRev[id]) {
return this.hashCacheRev[id];
}
try {
let w = await this.localDatabase.get(id);
if (w.type == "leaf") {
this.hashCache[w.data] = id;
this.hashCacheRev[id] = w.data;
return w.data;
}
throw new Error(`retrive leaf, but it was not leaf.`);
} catch (ex) {
if (ex.status && ex.status == 404) {
// just leaf is not ready.
// wait for on
if ((await this.waitForLeafReady(id)) === false) {
throw new Error(`time out (waiting leaf)`);
}
try {
// retrive again.
let w = await this.localDatabase.get(id);
if (w.type == "leaf") {
this.hashCache[w.data] = id;
this.hashCacheRev[id] = w.data;
return w.data;
}
throw new Error(`retrive leaf, but it was not leaf.`);
} catch (ex) {
if (ex.status && ex.status == 404) {
throw new Error("leaf is not found");
}
this.addLog(`Something went wrong on retriving leaf`);
throw ex;
}
} else {
this.addLog(`Something went wrong on retriving leaf`);
throw ex;
}
}
}
async getDBEntry(id: string, opt?: PouchDB.Core.GetOptions, retryCount = 5): Promise<false | LoadedEntry> {
try { try {
let obj: EntryDocResponse = null; let obj: EntryDocResponse = null;
if (opt) { if (opt) {
@ -296,33 +391,24 @@ class LocalPouchDB {
children: [], children: [],
datatype: "newnote", datatype: "newnote",
}; };
if (typeof this.corruptedEntries[doc._id] != "undefined") {
delete this.corruptedEntries[doc._id];
}
return doc; return doc;
// simple note // simple note
} }
if (obj.type == "newnote" || obj.type == "plain") { if (obj.type == "newnote" || obj.type == "plain") {
// search childrens // search childrens
try { try {
let childrens = []; let childrens;
for (var v of obj.children) { try {
if (typeof this.hashCacheRev[v] !== "undefined") { childrens = await Promise.all(obj.children.map((e) => this.getDBLeaf(e)));
childrens.push(this.hashCacheRev[v]); } catch (ex) {
} else { this.addLog(`Something went wrong on reading elements of ${obj._id} from database.`, LOG_LEVEL.NOTICE);
try { this.corruptedEntries[obj._id] = obj;
let elem = await this.localDatabase.get(v); return false;
if (elem.type && elem.type == "leaf") {
childrens.push(elem.data);
} else {
throw new Error("linked document is not leaf");
}
} catch (ex) {
if (ex.status && ex.status == 404) {
this.addLog(`Missing document content!, could not read ${v} of ${obj._id}(${obj._rev}) from database.`, LOG_LEVEL.NOTICE);
return false;
}
throw ex;
}
}
} }
let data = childrens.join(""); let data = childrens.join("");
let doc: LoadedEntry & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta = { let doc: LoadedEntry & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta = {
data: data, data: data,
@ -336,7 +422,9 @@ class LocalPouchDB {
datatype: obj.type, datatype: obj.type,
_conflicts: obj._conflicts, _conflicts: obj._conflicts,
}; };
if (typeof this.corruptedEntries[doc._id] != "undefined") {
delete this.corruptedEntries[doc._id];
}
return doc; return doc;
} catch (ex) { } catch (ex) {
if (ex.status && ex.status == 404) { if (ex.status && ex.status == 404) {
@ -373,6 +461,9 @@ class LocalPouchDB {
obj._deleted = true; obj._deleted = true;
let r = await this.localDatabase.put(obj); let r = await this.localDatabase.put(obj);
this.updateRecentModifiedDocs(r.id, r.rev, true); this.updateRecentModifiedDocs(r.id, r.rev, true);
if (typeof this.corruptedEntries[obj._id] != "undefined") {
delete this.corruptedEntries[obj._id];
}
return true; return true;
// simple note // simple note
} }
@ -381,6 +472,9 @@ class LocalPouchDB {
let r = await this.localDatabase.put(obj); let r = await this.localDatabase.put(obj);
this.addLog(`entry removed:${obj._id}-${r.rev}`); this.addLog(`entry removed:${obj._id}-${r.rev}`);
this.updateRecentModifiedDocs(r.id, r.rev, true); this.updateRecentModifiedDocs(r.id, r.rev, true);
if (typeof this.corruptedEntries[obj._id] != "undefined") {
delete this.corruptedEntries[obj._id];
}
return true; return true;
} }
} catch (ex) { } catch (ex) {
@ -528,7 +622,6 @@ class LocalPouchDB {
size: note.size, size: note.size,
type: plainSplit ? "plain" : "newnote", type: plainSplit ? "plain" : "newnote",
}; };
// Here for upsert logic, // Here for upsert logic,
try { try {
let old = await this.localDatabase.get(newDoc._id); let old = await this.localDatabase.get(newDoc._id);
@ -545,6 +638,9 @@ class LocalPouchDB {
} }
let r = await this.localDatabase.put(newDoc); let r = await this.localDatabase.put(newDoc);
this.updateRecentModifiedDocs(r.id, r.rev, newDoc._deleted); this.updateRecentModifiedDocs(r.id, r.rev, newDoc._deleted);
if (typeof this.corruptedEntries[note._id] != "undefined") {
delete this.corruptedEntries[note._id];
}
this.addLog(`note saven:${newDoc._id}:${r.rev}`); this.addLog(`note saven:${newDoc._id}:${r.rev}`);
} }
@ -576,11 +672,13 @@ class LocalPouchDB {
let syncOption: PouchDB.Replication.SyncOptions = keepAlive ? { live: true, retry: true, heartbeat: 30000, ...syncOptionBase } : { ...syncOptionBase }; let syncOption: PouchDB.Replication.SyncOptions = keepAlive ? { live: true, retry: true, heartbeat: 30000, ...syncOptionBase } : { ...syncOptionBase };
let db = dbret.db; let db = dbret.db;
//replicate once //replicate once
let replicate = this.localDatabase.replicate.from(db, syncOptionBase); let replicate = this.localDatabase.replicate.from(db, syncOptionBase);
replicate replicate
.on("change", async (e) => { .on("change", async (e) => {
// when in first run, replication will send us tombstone data
// and in normal cases, all leavs should sent before the entry that contains these item.
// so skip to completed all, we should treat all changes.
try { try {
callback(e.docs); callback(e.docs);
this.addLog(`pulled ${e.docs.length} doc(s)`); this.addLog(`pulled ${e.docs.length} doc(s)`);
@ -590,10 +688,11 @@ class LocalPouchDB {
} }
}) })
.on("complete", async (info) => { .on("complete", async (info) => {
replicate.removeAllListeners();
replicate.cancel(); replicate.cancel();
replicate.removeAllListeners();
this.syncHandler = null; this.syncHandler = null;
if (this.syncHandler != null) { if (this.syncHandler != null) {
this.syncHandler.cancel();
this.syncHandler.removeAllListeners(); this.syncHandler.removeAllListeners();
} }
this.syncHandler = this.localDatabase.sync(db, syncOption); this.syncHandler = this.localDatabase.sync(db, syncOption);
@ -644,10 +743,14 @@ class LocalPouchDB {
} }
async resetDatabase() { async resetDatabase() {
if (this.changeHandler != null) {
this.changeHandler.cancel();
}
await this.closeReplication(); await this.closeReplication();
await this.localDatabase.destroy(); await this.localDatabase.destroy();
this.localDatabase = null; this.localDatabase = null;
await this.initializeDatabase(); await this.initializeDatabase();
this.disposeHashCache();
this.addLog("Local Database Reset", LOG_LEVEL.NOTICE); this.addLog("Local Database Reset", LOG_LEVEL.NOTICE);
} }
async tryResetRemoteDatabase(setting: ObsidianLiveSyncSettings) { async tryResetRemoteDatabase(setting: ObsidianLiveSyncSettings) {
@ -741,7 +844,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
//localDatabase: PouchDB.Database<EntryDoc>; //localDatabase: PouchDB.Database<EntryDoc>;
localDatabase: LocalPouchDB; localDatabase: LocalPouchDB;
logMessage: string[] = []; logMessage: string[] = [];
// onLogChanged: () => void;
statusBar: HTMLElement; statusBar: HTMLElement;
statusBar2: HTMLElement; statusBar2: HTMLElement;
@ -827,6 +929,14 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.replicate(); this.replicate();
}, },
}); });
// this.addCommand({
// id: "livesync-test",
// name: "test reset db and replicate",
// callback: async () => {
// await this.resetLocalDatabase();
// await this.replicate();
// },
// });
this.addCommand({ this.addCommand({
id: "livesync-gc", id: "livesync-gc",
name: "garbage collect now", name: "garbage collect now",
@ -904,6 +1014,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
} }
watchWindowVisiblity() { watchWindowVisiblity() {
if (this.settings.suspendFileWatching) return;
let isHidden = document.hidden; let isHidden = document.hidden;
if (isHidden) { if (isHidden) {
this.localDatabase.closeReplication(); this.localDatabase.closeReplication();
@ -919,16 +1030,19 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
} }
watchWorkspaceOpen(file: TFile) { watchWorkspaceOpen(file: TFile) {
if (this.settings.suspendFileWatching) return;
if (file == null) return; if (file == null) return;
this.localDatabase.disposeHashCache(); this.localDatabase.disposeHashCache();
this.showIfConflicted(file); this.showIfConflicted(file);
this.gcHook(); this.gcHook();
} }
watchVaultChange(file: TFile, ...args: any[]) { watchVaultChange(file: TFile, ...args: any[]) {
if (this.settings.suspendFileWatching) return;
this.updateIntoDB(file); this.updateIntoDB(file);
this.gcHook(); this.gcHook();
} }
watchVaultDelete(file: TFile & TFolder) { watchVaultDelete(file: TFile & TFolder) {
if (this.settings.suspendFileWatching) return;
if (file.children) { if (file.children) {
//folder //folder
this.deleteFolderOnDB(file); this.deleteFolderOnDB(file);
@ -939,6 +1053,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.gcHook(); this.gcHook();
} }
watchVaultRename(file: TFile & TFolder, oldFile: any) { watchVaultRename(file: TFile & TFolder, oldFile: any) {
if (this.settings.suspendFileWatching) return;
if (file.children) { if (file.children) {
// this.renameFolder(file,oldFile); // this.renameFolder(file,oldFile);
this.addLog(`folder name changed:(this operation is not supported) ${file.path}`); this.addLog(`folder name changed:(this operation is not supported) ${file.path}`);
@ -995,7 +1110,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
} }
} }
async doc2storage_create(docEntry: Entry, force?: boolean) { async doc2storage_create(docEntry: EntryBody, force?: boolean) {
let doc = await this.localDatabase.getDBEntry(docEntry._id, { rev: docEntry._rev }); let doc = await this.localDatabase.getDBEntry(docEntry._id, { rev: docEntry._rev });
if (doc === false) return; if (doc === false) return;
if (doc.datatype == "newnote") { if (doc.datatype == "newnote") {
@ -1026,7 +1141,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
await this.deleteVaultItem(dir); await this.deleteVaultItem(dir);
} }
} }
async doc2storate_modify(docEntry: Entry, file: TFile, force?: boolean) { async doc2storate_modify(docEntry: EntryBody, file: TFile, force?: boolean) {
if (docEntry._deleted) { if (docEntry._deleted) {
//basically pass. //basically pass.
//but if there're no docs left, delete file. //but if there're no docs left, delete file.
@ -1072,7 +1187,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
//eq.case //eq.case
} }
} }
async handleDBChanged(change: Entry) { async handleDBChanged(change: EntryBody) {
let allfiles = this.app.vault.getFiles(); let allfiles = this.app.vault.getFiles();
let targetFiles = allfiles.filter((e) => e.path == change._id); let targetFiles = allfiles.filter((e) => e.path == change._id);
if (targetFiles.length == 0) { if (targetFiles.length == 0) {
@ -1091,13 +1206,15 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
} }
//---> Sync //---> Sync
async parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<Entry>>): Promise<void> { async parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): Promise<void> {
for (var change of docs) { for (var change of docs) {
if (this.localDatabase.isSelfModified(change._id, change._rev)) { if (this.localDatabase.isSelfModified(change._id, change._rev)) {
return; return;
} }
this.addLog("replication change arrived", LOG_LEVEL.VERBOSE); this.addLog("replication change arrived", LOG_LEVEL.VERBOSE);
await this.handleDBChanged(change); if (change.type != "leaf") {
await this.handleDBChanged(change);
}
this.gcHook(); this.gcHook();
} }
} }
@ -1588,6 +1705,8 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}) })
); );
containerEl.createEl("h3", { text: "Database configuration" });
new Setting(containerEl) new Setting(containerEl)
.setName("File to Database saving delay") .setName("File to Database saving delay")
.setDesc("ms, between 200 and 5000, restart required.") .setDesc("ms, between 200 and 5000, restart required.")
@ -1615,15 +1734,17 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
let v = Number(value); let v = Number(value);
if (isNaN(v) || v > 5000) { if (isNaN(v) || v > 5000) {
return 0; return 0;
//text.inputEl.va;
} }
this.plugin.settings.gcDelay = v; this.plugin.settings.gcDelay = v;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
}); });
text.inputEl.setAttribute("type", "number"); text.inputEl.setAttribute("type", "number");
}); });
containerEl.createEl("h3", { text: "Log Setting" });
new Setting(containerEl) new Setting(containerEl)
.setName("Log") .setName("Do not show low-priority Log")
.setDesc("Reduce log infomations") .setDesc("Reduce log infomations")
.addToggle((toggle) => .addToggle((toggle) =>
toggle.setValue(this.plugin.settings.lessInformationInLog).onChange(async (value) => { toggle.setValue(this.plugin.settings.lessInformationInLog).onChange(async (value) => {
@ -1640,18 +1761,21 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
await this.plugin.saveSettings(); await this.plugin.saveSettings();
}) })
); );
containerEl.createEl("h3", { text: "Sync setting" });
if (this.plugin.settings.versionUpFlash != "") { if (this.plugin.settings.versionUpFlash != "") {
let c = containerEl.createEl("div", { text: this.plugin.settings.versionUpFlash }); let c = containerEl.createEl("div", { text: this.plugin.settings.versionUpFlash });
c.createEl("button", { text: "I got it and updated." }, (e) => { c.createEl("button", { text: "I got it and updated." }, (e) => {
e.addEventListener("click", async () => { e.addEventListener("click", async () => {
this.plugin.settings.versionUpFlash = ""; this.plugin.settings.versionUpFlash = "";
this.plugin.saveSettings(); await this.plugin.saveSettings();
c.remove(); c.remove();
}); });
}); });
c.addClass("op-warn"); c.addClass("op-warn");
} }
// containerEl.createDiv(this.plugin.settings.versionUpFlash);
new Setting(containerEl) new Setting(containerEl)
.setName("LiveSync") .setName("LiveSync")
.setDesc("Sync realtime") .setDesc("Sync realtime")
@ -1664,7 +1788,7 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
); );
new Setting(containerEl) new Setting(containerEl)
.setName("Sync on Save") .setName("Sync on Save")
.setDesc("Sync on Save") .setDesc("When you save file, sync automatically")
.addToggle((toggle) => .addToggle((toggle) =>
toggle.setValue(this.plugin.settings.syncOnSave).onChange(async (value) => { toggle.setValue(this.plugin.settings.syncOnSave).onChange(async (value) => {
this.plugin.settings.syncOnSave = value; this.plugin.settings.syncOnSave = value;
@ -1673,13 +1797,14 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
); );
new Setting(containerEl) new Setting(containerEl)
.setName("Sync on Start") .setName("Sync on Start")
.setDesc("Sync on Start") .setDesc("Start synchronization on Obsidian started.")
.addToggle((toggle) => .addToggle((toggle) =>
toggle.setValue(this.plugin.settings.syncOnStart).onChange(async (value) => { toggle.setValue(this.plugin.settings.syncOnStart).onChange(async (value) => {
this.plugin.settings.syncOnStart = value; this.plugin.settings.syncOnStart = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
}) })
); );
new Setting(containerEl) new Setting(containerEl)
.setName("Minimum chunk size") .setName("Minimum chunk size")
.setDesc("(letters), minimum chunk size.") .setDesc("(letters), minimum chunk size.")
@ -1696,6 +1821,7 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}); });
text.inputEl.setAttribute("type", "number"); text.inputEl.setAttribute("type", "number");
}); });
new Setting(containerEl) new Setting(containerEl)
.setName("LongLine Threshold") .setName("LongLine Threshold")
.setDesc("(letters), If the line is longer than this, make the line to chunk") .setDesc("(letters), If the line is longer than this, make the line to chunk")
@ -1712,6 +1838,7 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}); });
text.inputEl.setAttribute("type", "number"); text.inputEl.setAttribute("type", "number");
}); });
new Setting(containerEl).setName("Local Database Operations").addButton((button) => new Setting(containerEl).setName("Local Database Operations").addButton((button) =>
button button
.setButtonText("Reset local database") .setButtonText("Reset local database")
@ -1738,6 +1865,19 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
await this.plugin.garbageCollect(); await this.plugin.garbageCollect();
}) })
); );
containerEl.createEl("h3", { text: "Hatch" });
new Setting(containerEl)
.setName("Suspend file watching")
.setDesc("if enables it, all file operations are ignored.")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.suspendFileWatching).onChange(async (value) => {
this.plugin.settings.suspendFileWatching = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl).setName("Remote Database Operations").addButton((button) => new Setting(containerEl).setName("Remote Database Operations").addButton((button) =>
button button
.setButtonText("Reset remote database") .setButtonText("Reset remote database")
@ -1754,5 +1894,21 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
await this.plugin.tryResetRemoteDatabase(); await this.plugin.tryResetRemoteDatabase();
}) })
); );
containerEl.createEl("h3", { text: "Corrupted data" });
if (Object.keys(this.plugin.localDatabase.corruptedEntries).length > 0) {
let cx = containerEl.createEl("div", { text: "If you have copy of these items on any device, simply edit once or twice. Or not, delete this. sorry.." });
for (let k in this.plugin.localDatabase.corruptedEntries) {
let xx = cx.createEl("div", { text: `${k}` });
let ba = xx.createEl("button", { text: `Delete this` }, (e) => {
e.addEventListener("click", async () => {
await this.plugin.localDatabase.deleteDBEntry(k);
xx.remove();
});
});
}
}
} }
} }

View File

@ -1,7 +1,7 @@
{ {
"id": "obsidian-livesync", "id": "obsidian-livesync",
"name": "Obsidian Live sync", "name": "Obsidian Live sync",
"version": "0.1.2", "version": "0.1.3",
"minAppVersion": "0.9.12", "minAppVersion": "0.9.12",
"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.",
"author": "vorotamoroz", "author": "vorotamoroz",

View File

@ -1,7 +1,7 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.1.1", "version": "0.1.3",
"description": "obsidian Live synchronization plugin.", "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",
"scripts": { "scripts": {
"dev": "rollup --config rollup.config.js -w", "dev": "rollup --config rollup.config.js -w",