diff --git a/main.ts b/main.ts index 4745a64..a99b5a5 100644 --- a/main.ts +++ b/main.ts @@ -1,4 +1,4 @@ -import { App, debounce, Modal, Notice, Plugin, PluginSettingTab, Setting, TFile, addIcon, TFolder, normalizePath, TAbstractFile } from "obsidian"; +import { App, debounce, Modal, Notice, Plugin, PluginSettingTab, Setting, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView } from "obsidian"; import { PouchDB } from "./pouchdb-browser-webpack/dist/pouchdb-browser"; import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch"; import xxhash from "xxhash-wasm"; @@ -11,7 +11,7 @@ const MAX_DOC_SIZE_BIN = 102400; // 100kb const VER = 10; const RECENT_MOFIDIED_DOCS_QTY = 30; -const LEAF_WAIT_TIMEOUT = 30000; // in synchronization, waiting missing leaf time out. +const LEAF_WAIT_TIMEOUT = 90000; // in synchronization, waiting missing leaf time out. const LOG_LEVEL = { VERBOSE: 1, INFO: 10, @@ -31,6 +31,7 @@ interface ObsidianLiveSyncSettings { liveSync: boolean; syncOnSave: boolean; syncOnStart: boolean; + syncOnFileOpen: boolean; savingDelay: number; lessInformationInLog: boolean; gcDelay: number; @@ -40,6 +41,8 @@ interface ObsidianLiveSyncSettings { showVerboseLog: boolean; suspendFileWatching: boolean; trashInsteadDelete: boolean; + periodicReplication: boolean; + periodicReplicationInterval: number; } const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = { @@ -58,6 +61,9 @@ const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = { showVerboseLog: false, suspendFileWatching: false, trashInsteadDelete: false, + periodicReplication: false, + periodicReplicationInterval: 60, + syncOnFileOpen: false, }; interface Entry { _id: string; @@ -159,7 +165,7 @@ type Credential = { type EntryDocResponse = EntryDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta; //-->Functions. -function arrayBufferToBase64(buffer: ArrayBuffer) { +function arrayBufferToBase64Old(buffer: ArrayBuffer) { var binary = ""; var bytes = new Uint8Array(buffer); var len = bytes.byteLength; @@ -168,6 +174,18 @@ function arrayBufferToBase64(buffer: ArrayBuffer) { } return window.btoa(binary); } +// Ten times faster. +function arrayBufferToBase64(buffer: ArrayBuffer): Promise { + return new Promise((res) => { + var blob = new Blob([buffer], { type: "application/octet-binary" }); + var reader = new FileReader(); + reader.onload = function (evt) { + var dataurl = evt.target.result.toString(); + res(dataurl.substr(dataurl.indexOf(",") + 1)); + }; + reader.readAsDataURL(blob); + }); +} function base64ToArrayBuffer(base64: string): ArrayBuffer { try { @@ -315,6 +333,7 @@ class LocalPouchDB { settings: ObsidianLiveSyncSettings; localDatabase: PouchDB.Database; nodeid: string = ""; + isReady: boolean = false; recentModifiedDocs: string[] = []; h32: (input: string, seed?: number) => string; @@ -338,10 +357,17 @@ class LocalPouchDB { this.dbname = dbname; this.settings = settings; - this.initializeDatabase(); + // this.initializeDatabase(); } close() { - this.localDatabase.close(); + this.isReady = false; + if (this.changeHandler != null) { + this.changeHandler.cancel(); + this.changeHandler.removeAllListeners(); + } + if (this.localDatabase != null) { + this.localDatabase.close(); + } } status() { if (this.syncHandler == null) { @@ -370,6 +396,7 @@ class LocalPouchDB { changeHandler: PouchDB.Core.Changes<{}> = null; async initializeDatabase() { + await this.prepareHashFunctions(); if (this.localDatabase != null) this.localDatabase.close(); if (this.changeHandler != null) { this.changeHandler.cancel(); @@ -380,6 +407,9 @@ class LocalPouchDB { revs_limit: 100, deterministic_revs: true, }); + + Logger("Database Info"); + Logger(await this.localDatabase.info(), LOG_LEVEL.VERBOSE); // initialize local node information. let nodeinfo: EntryNodeInfo = await resolveWithIgnoreKnownError(this.localDatabase.get(NODEINFO_DOCID), { _id: NODEINFO_DOCID, @@ -390,6 +420,9 @@ class LocalPouchDB { nodeinfo.nodeid = Math.random().toString(36).slice(-10); await this.localDatabase.put(nodeinfo); } + this.localDatabase.on("close", () => { + this.isReady = false; + }); this.nodeid = nodeinfo.nodeid; // Traceing the leaf id @@ -405,7 +438,7 @@ class LocalPouchDB { this.docSeq = `${e.seq}`; }); this.changeHandler = changes; - await this.prepareHashFunctions(); + this.isReady = true; } async prepareHashFunctions() { @@ -514,6 +547,7 @@ class LocalPouchDB { children: [], datatype: "newnote", }; + return doc; } } catch (ex) { if (ex.status && ex.status == 404) { @@ -523,7 +557,7 @@ class LocalPouchDB { } return false; } - async getDBEntry(id: string, opt?: PouchDB.Core.GetOptions, retryCount = 5): Promise { + async getDBEntry(id: string, opt?: PouchDB.Core.GetOptions, dump = false): Promise { try { let obj: EntryDocResponse = null; if (opt) { @@ -555,15 +589,28 @@ class LocalPouchDB { if (typeof this.corruptedEntries[doc._id] != "undefined") { delete this.corruptedEntries[doc._id]; } + if (dump) { + Logger(`Simple doc`); + Logger(doc); + } + return doc; // simple note } if (obj.type == "newnote" || obj.type == "plain") { // search childrens try { + if (dump) { + Logger(`Enhanced doc`); + Logger(obj); + } let childrens; try { childrens = await Promise.all(obj.children.map((e) => this.getDBLeaf(e))); + if (dump) { + Logger(`childrens:`); + Logger(childrens); + } } catch (ex) { Logger(`Something went wrong on reading elements of ${obj._id} from database.`, LOG_LEVEL.NOTICE); this.corruptedEntries[obj._id] = obj; @@ -583,6 +630,10 @@ class LocalPouchDB { datatype: obj.type, _conflicts: obj._conflicts, }; + if (dump) { + Logger(`therefore:`); + Logger(doc); + } if (typeof this.corruptedEntries[doc._id] != "undefined") { delete this.corruptedEntries[doc._id]; } @@ -693,6 +744,18 @@ class LocalPouchDB { Logger(`deleteDBEntryPrefix:deleted ${deleteCount} items, skipped ${notfound}`); return true; } + isPlainText(filename: string): boolean { + if (filename.endsWith(".md")) return true; + if (filename.endsWith(".txt")) return true; + if (filename.endsWith(".svg")) return true; + if (filename.endsWith(".html")) return true; + if (filename.endsWith(".csv")) return true; + if (filename.endsWith(".css")) return true; + if (filename.endsWith(".js")) return true; + if (filename.endsWith(".xml")) return true; + + return false; + } async putDBEntry(note: LoadedEntry) { let leftData = note.data; let savenNotes = []; @@ -702,10 +765,11 @@ class LocalPouchDB { let pieceSize = MAX_DOC_SIZE_BIN; let plainSplit = false; let cacheUsed = 0; - if (note._id.endsWith(".md")) { + if (this.isPlainText(note._id)) { pieceSize = MAX_DOC_SIZE; plainSplit = true; } + let newLeafs: EntryLeaf[] = []; do { // To keep low bandwith and database size, // Dedup pieces on database. @@ -715,11 +779,11 @@ class LocalPouchDB { // 3. \r\n\r\n should break // 4. \n# should break. let cPieceSize = pieceSize; - let minimumChunkSize = this.settings.minimumChunkSize; - if (minimumChunkSize < 10) minimumChunkSize = 10; - let longLineThreshold = this.settings.longLineThreshold; - if (longLineThreshold < 100) longLineThreshold = 100; if (plainSplit) { + let minimumChunkSize = this.settings.minimumChunkSize; + if (minimumChunkSize < 10) minimumChunkSize = 10; + let longLineThreshold = this.settings.longLineThreshold; + if (longLineThreshold < 100) longLineThreshold = 100; cPieceSize = 0; // lookup for next splittion . // we're standing on "\n" @@ -748,11 +812,13 @@ class LocalPouchDB { } while (cPieceSize < minimumChunkSize); } + // piece size determined. + let piece = leftData.substring(0, cPieceSize); leftData = leftData.substring(cPieceSize); processed++; let leafid = ""; - // Get has of piece. + // Get hash of piece. let hashedPiece: string = ""; let hashQ: number = 0; // if hash collided, **IF**, count it up. let tryNextHash = false; @@ -803,53 +869,72 @@ class LocalPouchDB { data: piece, type: "leaf", }; - let result = await this.localDatabase.put(d); - this.updateRecentModifiedDocs(result.id, result.rev, d._deleted); - if (result.ok) { - Logger(`save ok:id:${result.id} rev:${result.rev}`, LOG_LEVEL.VERBOSE); - this.hashCache[piece] = leafid; - this.hashCacheRev[leafid] = piece; - made++; - } else { - Logger("save faild"); - } + newLeafs.push(d); + this.hashCache[piece] = leafid; + this.hashCacheRev[leafid] = piece; + made++; } else { skiped++; } } - savenNotes.push(leafid); } while (leftData != ""); - Logger(`note content saven, pieces:${processed} new:${made}, skip:${skiped}, cache:${cacheUsed}`); - let newDoc: PlainEntry | NewEntry = { - NewNote: true, - children: savenNotes, - _id: note._id, - ctime: note.ctime, - mtime: note.mtime, - size: note.size, - type: plainSplit ? "plain" : "newnote", - }; - // Here for upsert logic, - try { - let old = await this.localDatabase.get(newDoc._id); - if (!old.type || old.type == "notes" || old.type == "newnote" || old.type == "plain") { - // simple use rev for new doc - newDoc._rev = old._rev; - } - } catch (ex) { - if (ex.status && ex.status == 404) { - // NO OP/ - } else { - throw ex; + let saved = true; + if (newLeafs.length > 0) { + try { + let result = await this.localDatabase.bulkDocs(newLeafs); + for (let 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 { + Logger(`save failed:id:${item.id} rev:${item.rev}`, LOG_LEVEL.NOTICE); + Logger(item); + this.disposeHashCache(); + saved = false; + } + } + } catch (ex) { + Logger("ERROR ON SAVING LEAVES "); + Logger(ex); + saved = false; } } - let r = await this.localDatabase.put(newDoc); - this.updateRecentModifiedDocs(r.id, r.rev, newDoc._deleted); - if (typeof this.corruptedEntries[note._id] != "undefined") { - delete this.corruptedEntries[note._id]; + if (saved) { + Logger(`note content saven, pieces:${processed} new:${made}, skip:${skiped}, cache:${cacheUsed}`); + let newDoc: PlainEntry | NewEntry = { + NewNote: true, + children: savenNotes, + _id: note._id, + ctime: note.ctime, + mtime: note.mtime, + size: note.size, + type: plainSplit ? "plain" : "newnote", + }; + // Here for upsert logic, + try { + let old = await this.localDatabase.get(newDoc._id); + if (!old.type || old.type == "notes" || old.type == "newnote" || old.type == "plain") { + // simple use rev for new doc + newDoc._rev = old._rev; + } + } catch (ex) { + if (ex.status && ex.status == 404) { + // NO OP/ + } else { + throw ex; + } + } + let r = await this.localDatabase.put(newDoc); + this.updateRecentModifiedDocs(r.id, r.rev, newDoc._deleted); + if (typeof this.corruptedEntries[note._id] != "undefined") { + delete this.corruptedEntries[note._id]; + } + Logger(`note saven:${newDoc._id}:${r.rev}`); + } else { + Logger(`note coud not saved:${note._id}`); } - Logger(`note saven:${newDoc._id}:${r.rev}`); } syncHandler: PouchDB.Replication.Sync<{}> = null; @@ -900,12 +985,12 @@ class LocalPouchDB { this.docSent += e.docs_written; this.docArrived += e.docs_read; this.updateInfo(); - Logger(`sending..:${e.docs.length}`); + Logger(`replicateAllToServer: sending..:${e.docs.length}`); }) .on("complete", async (info) => { this.syncStatus = "COMPLETED"; this.updateInfo(); - Logger("Completed", LOG_LEVEL.NOTICE); + Logger("replicateAllToServer: Completed", LOG_LEVEL.NOTICE); replicate.cancel(); replicate.removeAllListeners(); res(true); @@ -913,13 +998,21 @@ class LocalPouchDB { .on("error", (e) => { this.syncStatus = "ERRORED"; this.updateInfo(); - Logger("Pulling Replication error", LOG_LEVEL.NOTICE); + Logger("replicateAllToServer: Pulling Replication error", LOG_LEVEL.INFO); Logger(e); + replicate.cancel(); + replicate.removeAllListeners(); rej(e); }); }); } + async openReplication(setting: ObsidianLiveSyncSettings, keepAlive: boolean, showResult: boolean, callback: (e: PouchDB.Core.ExistingDocument<{}>[]) => Promise) { + if (!this.isReady) { + Logger("Database is not ready."); + return false; + } + if (setting.versionUpFlash != "") { new Notice("Open settings and check message, please."); return; @@ -973,11 +1066,15 @@ class LocalPouchDB { let db = dbret.db; //replicate once this.syncStatus = "CONNECTED"; + Logger("Pull before replicate."); + Logger(await this.localDatabase.info(), LOG_LEVEL.VERBOSE); + Logger(await db.info(), LOG_LEVEL.VERBOSE); let replicate = this.localDatabase.replicate.from(db, syncOptionBase); replicate .on("active", () => { this.syncStatus = "CONNECTED"; this.updateInfo(); + Logger("Replication pull activated."); }) .on("change", async (e) => { // when in first run, replication will send us tombstone data @@ -1004,6 +1101,7 @@ class LocalPouchDB { this.syncHandler.cancel(); this.syncHandler.removeAllListeners(); } + Logger("Replication pull completed."); this.syncHandler = this.localDatabase.sync(db, syncOption); this.syncHandler .on("active", () => { @@ -1051,7 +1149,13 @@ class LocalPouchDB { .on("error", (e) => { this.syncStatus = "ERRORED"; this.updateInfo(); - Logger("Pulling Replication error", LOG_LEVEL.NOTICE); + Logger("Pulling Replication error", LOG_LEVEL.INFO); + replicate.cancel(); + replicate.removeAllListeners(); + this.syncHandler.cancel(); + this.syncHandler.removeAllListeners(); + this.syncHandler = null; + // debugger; Logger(e); }); } @@ -1070,9 +1174,11 @@ class LocalPouchDB { async resetDatabase() { if (this.changeHandler != null) { + this.changeHandler.removeAllListeners(); this.changeHandler.cancel(); } await this.closeReplication(); + this.isReady = false; await this.localDatabase.destroy(); this.localDatabase = null; await this.initializeDatabase(); @@ -1080,7 +1186,6 @@ class LocalPouchDB { Logger("Local Database Reset", LOG_LEVEL.NOTICE); } async tryResetRemoteDatabase(setting: ObsidianLiveSyncSettings) { - await this.closeReplication(); await this.closeReplication(); let uri = setting.couchDB_URI; let auth: Credential = { @@ -1181,9 +1286,11 @@ class LocalPouchDB { let readCount = 0; let hashPieces: string[] = []; let usedPieces: string[] = []; + Logger("Collecting Garbage"); do { - let result = await this.localDatabase.allDocs({ include_docs: true, skip: c, limit: 100, conflicts: true }); + let result = await this.localDatabase.allDocs({ include_docs: true, skip: c, limit: 500, conflicts: true }); readCount = result.rows.length; + Logger("checked:" + readCount); if (readCount > 0) { //there are some result for (let v of result.rows) { @@ -1209,13 +1316,21 @@ class LocalPouchDB { c += readCount; } while (readCount != 0); // items collected. + Logger("Finding unused pieces"); const garbages = hashPieces.filter((e) => usedPieces.indexOf(e) == -1); let deleteCount = 0; + Logger("we have to delete:" + garbages.length); + let deleteDoc: EntryDoc[] = []; for (let v of garbages) { try { let item = await this.localDatabase.get(v); item._deleted = true; - await this.localDatabase.put(item); + deleteDoc.push(item); + if (deleteDoc.length > 50) { + await this.localDatabase.bulkDocs(deleteDoc); + deleteDoc = []; + Logger("delete:" + deleteCount); + } deleteCount++; } catch (ex) { if (ex.status && ex.status == 404) { @@ -1225,6 +1340,9 @@ class LocalPouchDB { } } } + if (deleteDoc.length > 0) { + await this.localDatabase.bulkDocs(deleteDoc); + } Logger(`GC:deleted ${deleteCount} items.`); } } @@ -1246,6 +1364,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin { this.settings.liveSync = false; this.settings.syncOnSave = false; this.settings.syncOnStart = false; + this.settings.periodicReplication = false; this.settings.versionUpFlash = "I changed specifications incompatiblly, so when you enable sync again, be sure to made version up all nother devides."; this.saveSettings(); } @@ -1295,30 +1414,19 @@ export default class ObsidianLiveSyncPlugin extends Plugin { this.watchWorkspaceOpen = debounce(this.watchWorkspaceOpen.bind(this), delay, false); this.watchWindowVisiblity = debounce(this.watchWindowVisiblity.bind(this), delay, false); - this.registerWatchEvents(); this.parseReplicationResult = this.parseReplicationResult.bind(this); + this.periodicSync = this.periodicSync.bind(this); + this.setPeriodicSync = this.setPeriodicSync.bind(this); + + // this.registerWatchEvents(); this.addSettingTab(new ObsidianLiveSyncSettingTab(this.app, this)); this.app.workspace.onLayoutReady(async () => { await this.initializeDatabase(); this.realizeSettingSyncMode(); - if (this.settings.syncOnStart) { - await this.replicate(false); - } + this.registerWatchEvents(); }); - - // when in mobile, too long suspended , connection won't back if setting retry:true - this.registerInterval( - window.setInterval(async () => { - if (this.settings.liveSync) { - await this.localDatabase.closeReplication(); - if (this.settings.liveSync) { - this.localDatabase.openReplication(this.settings, true, false, this.parseReplicationResult); - } - } - }, 60 * 1000) - ); this.addCommand({ id: "livesync-replicate", name: "Replicate now", @@ -1326,6 +1434,14 @@ export default class ObsidianLiveSyncPlugin extends Plugin { this.replicate(); }, }); + this.addCommand({ + id: "livesync-dump", + name: "Dump informations of this doc ", + editorCallback: (editor: Editor, view: MarkdownView) => { + //this.replicate(); + this.localDatabase.getDBEntry(view.file.path, {}, true); + }, + }); // this.addCommand({ // id: "livesync-test", // name: "test reset db and replicate", @@ -1356,14 +1472,13 @@ export default class ObsidianLiveSyncPlugin extends Plugin { this.saveSettings(); }, }); - this.watchWindowVisiblity = this.watchWindowVisiblity.bind(this); - window.addEventListener("visibilitychange", this.watchWindowVisiblity); } onunload() { if (this.gcTimerHandler != null) { clearTimeout(this.gcTimerHandler); this.gcTimerHandler = null; } + this.clearPeriodicSync(); this.localDatabase.closeReplication(); this.localDatabase.close(); window.removeEventListener("visibilitychange", this.watchWindowVisiblity); @@ -1375,6 +1490,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin { this.localDatabase.close(); } let vaultName = this.app.vault.getName(); + Logger("Open Database..."); this.localDatabase = new LocalPouchDB(this.settings, vaultName); this.localDatabase.updateInfo = () => { this.refreshStatusText(); @@ -1392,6 +1508,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin { async saveSettings() { await this.saveData(this.settings); this.localDatabase.settings = this.settings; + this.realizeSettingSyncMode(); } gcTimerHandler: any = null; gcHook() { @@ -1412,6 +1529,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin { this.registerEvent(this.app.vault.on("rename", this.watchVaultRename)); this.registerEvent(this.app.vault.on("create", this.watchVaultChange)); this.registerEvent(this.app.workspace.on("file-open", this.watchWorkspaceOpen)); + window.addEventListener("visibilitychange", this.watchWindowVisiblity); } watchWindowVisiblity() { @@ -1422,6 +1540,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin { let isHidden = document.hidden; if (isHidden) { this.localDatabase.closeReplication(); + this.clearPeriodicSync(); } else { if (this.settings.liveSync) { await this.localDatabase.openReplication(this.settings, true, false, this.parseReplicationResult); @@ -1429,6 +1548,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin { if (this.settings.syncOnStart) { await this.localDatabase.openReplication(this.settings, false, false, this.parseReplicationResult); } + if (this.settings.periodicReplication) { + this.setPeriodicSync(); + } } this.gcHook(); } @@ -1439,6 +1561,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin { } async watchWorkspaceOpenAsync(file: TFile) { if (file == null) return; + if (this.settings.syncOnFileOpen) { + await this.replicate(); + } this.localDatabase.disposeHashCache(); await this.showIfConflicted(file); this.gcHook(); @@ -1453,10 +1578,13 @@ export default class ObsidianLiveSyncPlugin extends Plugin { } batchFileChange: string[] = []; async watchVaultChangeAsync(file: TFile, ...args: any[]) { - await this.updateIntoDB(file); - this.gcHook(); + if (file instanceof TFile) { + await this.updateIntoDB(file); + this.gcHook(); + } } watchVaultDelete(file: TFile | TFolder) { + console.log(`${file.path} delete`); if (this.settings.suspendFileWatching) return; this.watchVaultDeleteAsync(file); } @@ -1518,7 +1646,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin { } this.gcHook(); } - + addLogHook: () => void = null; //--> Basic document Functions async addLog(message: any, level: LOG_LEVEL = LOG_LEVEL.INFO) { if (level < LOG_LEVEL.INFO && this.settings && this.settings.lessInformationInLog) { @@ -1540,6 +1668,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin { if (level >= LOG_LEVEL.NOTICE) { new Notice(messagecontent); } + if (this.addLogHook != null) this.addLogHook(); } async ensureDirectory(fullpath: string) { @@ -1694,6 +1823,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin { } } + periodicSyncHandler: NodeJS.Timer = null; //---> Sync async parseReplicationResult(docs: Array>): Promise { this.refreshStatusText(); @@ -1714,12 +1844,29 @@ export default class ObsidianLiveSyncPlugin extends Plugin { this.gcHook(); } } + clearPeriodicSync() { + if (this.periodicSyncHandler != null) { + clearInterval(this.periodicSyncHandler); + this.periodicSyncHandler = null; + } + } + setPeriodicSync() { + if (this.settings.periodicReplication && this.settings.periodicReplicationInterval > 0) { + this.clearPeriodicSync(); + this.periodicSyncHandler = setInterval(() => this.periodicSync, Math.max(this.settings.periodicReplicationInterval, 30) * 1000); + } + } + async periodicSync() { + await this.replicate(); + } realizeSettingSyncMode() { this.localDatabase.closeReplication(); if (this.settings.liveSync) { this.localDatabase.openReplication(this.settings, true, false, this.parseReplicationResult); this.refreshStatusText(); } + this.clearPeriodicSync(); + this.setPeriodicSync(); } refreshStatusText() { let sent = this.localDatabase.docSent; @@ -1777,7 +1924,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin { const filesStorage = this.app.vault.getFiles(); const filesStorageName = filesStorage.map((e) => e.path); const wf = await this.localDatabase.localDatabase.allDocs(); - const filesDatabase = wf.rows.filter((e) => !e.id.startsWith("h:")).map((e) => normalizePath(e.id)); + const filesDatabase = wf.rows.filter((e) => !e.id.startsWith("h:") && e.id != "obsydian_livesync_version").map((e) => normalizePath(e.id)); const onlyInStorage = filesStorage.filter((e) => filesDatabase.indexOf(e.path) == -1); const onlyInDatabase = filesDatabase.filter((e) => filesStorageName.indexOf(e) == -1); @@ -1790,12 +1937,14 @@ export default class ObsidianLiveSyncPlugin extends Plugin { this.statusBar.setText(`UPDATE DATABASE`); // just write to DB from storage. for (let v of onlyInStorage) { + Logger(`Update into ${v.path}`); await this.updateIntoDB(v); } // simply realize it this.statusBar.setText(`UPDATE STORAGE`); Logger("Writing files that only in database"); for (let v of onlyInDatabase) { + Logger(`Pull from db:${v}`); await this.pullFile(v, filesStorage); } // have to sync below.. @@ -1803,6 +1952,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin { for (let v of syncFiles) { await this.syncFileBetweenDBandStorage(v, filesStorage); } + this.statusBar.setText(`NOW TRACKING!`); Logger("Initialized"); } async deleteFolderOnDB(folder: TFolder) { @@ -2028,7 +2178,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin { let datatype: "plain" | "newnote" = "newnote"; if (file.extension != "md") { let contentBin = await this.app.vault.readBinary(file); - content = arrayBufferToBase64(contentBin); + content = await arrayBufferToBase64(contentBin); datatype = "newnote"; } else { content = await this.app.vault.read(file); @@ -2111,13 +2261,13 @@ class LogDisplayModal extends Modal { div.addClass("op-pre"); this.logEl = div; this.updateLog = this.updateLog.bind(this); - // this.plugin.onLogChanged = this.updateLog; + this.plugin.addLogHook = this.updateLog; this.updateLog(); } onClose() { let { contentEl } = this; contentEl.empty(); - // this.plugin.onLogChanged = null; + this.plugin.addLogHook = null; } } class ConflictResolveModal extends Modal { @@ -2342,6 +2492,33 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab { this.plugin.realizeSettingSyncMode(); }) ); + + new Setting(containerEl) + .setName("Periodic Sync") + .setDesc("Sync periodically") + .addToggle((toggle) => + toggle.setValue(this.plugin.settings.periodicReplication).onChange(async (value) => { + this.plugin.settings.periodicReplication = value; + await this.plugin.saveSettings(); + }) + ); + new Setting(containerEl) + .setName("Periodic sync intreval") + .setDesc("Interval (sec)") + .addText((text) => { + text.setPlaceholder("") + .setValue(this.plugin.settings.periodicReplicationInterval + "") + .onChange(async (value) => { + let v = Number(value); + if (isNaN(v) || v > 5000) { + return 0; + } + this.plugin.settings.periodicReplicationInterval = v; + await this.plugin.saveSettings(); + }); + text.inputEl.setAttribute("type", "number"); + }); + new Setting(containerEl) .setName("Sync on Save") .setDesc("When you save file, sync automatically") @@ -2351,6 +2528,15 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab { await this.plugin.saveSettings(); }) ); + new Setting(containerEl) + .setName("Sync on File Open") + .setDesc("When you open file, sync automatically") + .addToggle((toggle) => + toggle.setValue(this.plugin.settings.syncOnFileOpen).onChange(async (value) => { + this.plugin.settings.syncOnFileOpen = value; + await this.plugin.saveSettings(); + }) + ); new Setting(containerEl) .setName("Sync on Start") .setDesc("Start synchronization on Obsidian started.") @@ -2451,6 +2637,12 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab { .setWarning() .setDisabled(false) .onClick(async () => { + this.plugin.settings.liveSync = false; + this.plugin.settings.periodicReplication = false; + this.plugin.settings.syncOnSave = false; + this.plugin.settings.syncOnStart = false; + await this.plugin.saveSettings(); + await this.plugin.saveSettings(); await this.plugin.resetLocalDatabase(); await this.plugin.initializeDatabase(); await this.plugin.tryResetRemoteDatabase(); @@ -2529,6 +2721,8 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab { }); }); } + } else { + let cx = containerEl.createEl("div", { text: "There's no collupted data." }); } } } diff --git a/manifest.json b/manifest.json index adc3921..978e992 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.1.13", + "version": "0.1.14", "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", diff --git a/package-lock.json b/package-lock.json index ce36ffd..70b3aad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.1.13", + "version": "0.1.14", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.1.13", + "version": "0.1.14", "license": "MIT", "dependencies": { "diff-match-patch": "^1.0.5", diff --git a/package.json b/package.json index e42eea7..1dfeac6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.1.13", + "version": "0.1.14", "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", "scripts": {