mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2025-12-13 17:55:56 +00:00
Bug fixed and new feature implemented
- Synchronization Timing problem fixed - Performance improvement of handling large files - Timeout for collecting leaves extended - Periodic synchronization implemented - Dumping document information implemented. - Folder watching problem fixed. - Delay vault watching for database ready.
This commit is contained in:
288
main.ts
288
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 { PouchDB } from "./pouchdb-browser-webpack/dist/pouchdb-browser";
|
||||||
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 xxhash from "xxhash-wasm";
|
import xxhash from "xxhash-wasm";
|
||||||
@@ -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 LEAF_WAIT_TIMEOUT = 90000; // in synchronization, waiting missing leaf time out.
|
||||||
const LOG_LEVEL = {
|
const LOG_LEVEL = {
|
||||||
VERBOSE: 1,
|
VERBOSE: 1,
|
||||||
INFO: 10,
|
INFO: 10,
|
||||||
@@ -31,6 +31,7 @@ interface ObsidianLiveSyncSettings {
|
|||||||
liveSync: boolean;
|
liveSync: boolean;
|
||||||
syncOnSave: boolean;
|
syncOnSave: boolean;
|
||||||
syncOnStart: boolean;
|
syncOnStart: boolean;
|
||||||
|
syncOnFileOpen: boolean;
|
||||||
savingDelay: number;
|
savingDelay: number;
|
||||||
lessInformationInLog: boolean;
|
lessInformationInLog: boolean;
|
||||||
gcDelay: number;
|
gcDelay: number;
|
||||||
@@ -40,6 +41,8 @@ interface ObsidianLiveSyncSettings {
|
|||||||
showVerboseLog: boolean;
|
showVerboseLog: boolean;
|
||||||
suspendFileWatching: boolean;
|
suspendFileWatching: boolean;
|
||||||
trashInsteadDelete: boolean;
|
trashInsteadDelete: boolean;
|
||||||
|
periodicReplication: boolean;
|
||||||
|
periodicReplicationInterval: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = {
|
const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = {
|
||||||
@@ -58,6 +61,9 @@ const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = {
|
|||||||
showVerboseLog: false,
|
showVerboseLog: false,
|
||||||
suspendFileWatching: false,
|
suspendFileWatching: false,
|
||||||
trashInsteadDelete: false,
|
trashInsteadDelete: false,
|
||||||
|
periodicReplication: false,
|
||||||
|
periodicReplicationInterval: 60,
|
||||||
|
syncOnFileOpen: false,
|
||||||
};
|
};
|
||||||
interface Entry {
|
interface Entry {
|
||||||
_id: string;
|
_id: string;
|
||||||
@@ -159,7 +165,7 @@ type Credential = {
|
|||||||
type EntryDocResponse = EntryDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta;
|
type EntryDocResponse = EntryDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta;
|
||||||
|
|
||||||
//-->Functions.
|
//-->Functions.
|
||||||
function arrayBufferToBase64(buffer: ArrayBuffer) {
|
function arrayBufferToBase64Old(buffer: ArrayBuffer) {
|
||||||
var binary = "";
|
var binary = "";
|
||||||
var bytes = new Uint8Array(buffer);
|
var bytes = new Uint8Array(buffer);
|
||||||
var len = bytes.byteLength;
|
var len = bytes.byteLength;
|
||||||
@@ -168,6 +174,18 @@ function arrayBufferToBase64(buffer: ArrayBuffer) {
|
|||||||
}
|
}
|
||||||
return window.btoa(binary);
|
return window.btoa(binary);
|
||||||
}
|
}
|
||||||
|
// Ten times faster.
|
||||||
|
function arrayBufferToBase64(buffer: ArrayBuffer): Promise<string> {
|
||||||
|
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 {
|
function base64ToArrayBuffer(base64: string): ArrayBuffer {
|
||||||
try {
|
try {
|
||||||
@@ -315,6 +333,7 @@ class LocalPouchDB {
|
|||||||
settings: ObsidianLiveSyncSettings;
|
settings: ObsidianLiveSyncSettings;
|
||||||
localDatabase: PouchDB.Database<EntryDoc>;
|
localDatabase: PouchDB.Database<EntryDoc>;
|
||||||
nodeid: string = "";
|
nodeid: string = "";
|
||||||
|
isReady: boolean = false;
|
||||||
|
|
||||||
recentModifiedDocs: string[] = [];
|
recentModifiedDocs: string[] = [];
|
||||||
h32: (input: string, seed?: number) => string;
|
h32: (input: string, seed?: number) => string;
|
||||||
@@ -338,11 +357,18 @@ class LocalPouchDB {
|
|||||||
this.dbname = dbname;
|
this.dbname = dbname;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
|
|
||||||
this.initializeDatabase();
|
// this.initializeDatabase();
|
||||||
}
|
}
|
||||||
close() {
|
close() {
|
||||||
|
this.isReady = false;
|
||||||
|
if (this.changeHandler != null) {
|
||||||
|
this.changeHandler.cancel();
|
||||||
|
this.changeHandler.removeAllListeners();
|
||||||
|
}
|
||||||
|
if (this.localDatabase != null) {
|
||||||
this.localDatabase.close();
|
this.localDatabase.close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
status() {
|
status() {
|
||||||
if (this.syncHandler == null) {
|
if (this.syncHandler == null) {
|
||||||
return "connected";
|
return "connected";
|
||||||
@@ -370,6 +396,7 @@ class LocalPouchDB {
|
|||||||
|
|
||||||
changeHandler: PouchDB.Core.Changes<{}> = null;
|
changeHandler: PouchDB.Core.Changes<{}> = null;
|
||||||
async initializeDatabase() {
|
async initializeDatabase() {
|
||||||
|
await this.prepareHashFunctions();
|
||||||
if (this.localDatabase != null) this.localDatabase.close();
|
if (this.localDatabase != null) this.localDatabase.close();
|
||||||
if (this.changeHandler != null) {
|
if (this.changeHandler != null) {
|
||||||
this.changeHandler.cancel();
|
this.changeHandler.cancel();
|
||||||
@@ -380,6 +407,9 @@ class LocalPouchDB {
|
|||||||
revs_limit: 100,
|
revs_limit: 100,
|
||||||
deterministic_revs: true,
|
deterministic_revs: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Logger("Database Info");
|
||||||
|
Logger(await this.localDatabase.info(), LOG_LEVEL.VERBOSE);
|
||||||
// initialize local node information.
|
// initialize local node information.
|
||||||
let nodeinfo: EntryNodeInfo = await resolveWithIgnoreKnownError<EntryNodeInfo>(this.localDatabase.get(NODEINFO_DOCID), {
|
let nodeinfo: EntryNodeInfo = await resolveWithIgnoreKnownError<EntryNodeInfo>(this.localDatabase.get(NODEINFO_DOCID), {
|
||||||
_id: NODEINFO_DOCID,
|
_id: NODEINFO_DOCID,
|
||||||
@@ -390,6 +420,9 @@ class LocalPouchDB {
|
|||||||
nodeinfo.nodeid = Math.random().toString(36).slice(-10);
|
nodeinfo.nodeid = Math.random().toString(36).slice(-10);
|
||||||
await this.localDatabase.put(nodeinfo);
|
await this.localDatabase.put(nodeinfo);
|
||||||
}
|
}
|
||||||
|
this.localDatabase.on("close", () => {
|
||||||
|
this.isReady = false;
|
||||||
|
});
|
||||||
this.nodeid = nodeinfo.nodeid;
|
this.nodeid = nodeinfo.nodeid;
|
||||||
|
|
||||||
// Traceing the leaf id
|
// Traceing the leaf id
|
||||||
@@ -405,7 +438,7 @@ class LocalPouchDB {
|
|||||||
this.docSeq = `${e.seq}`;
|
this.docSeq = `${e.seq}`;
|
||||||
});
|
});
|
||||||
this.changeHandler = changes;
|
this.changeHandler = changes;
|
||||||
await this.prepareHashFunctions();
|
this.isReady = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async prepareHashFunctions() {
|
async prepareHashFunctions() {
|
||||||
@@ -514,6 +547,7 @@ class LocalPouchDB {
|
|||||||
children: [],
|
children: [],
|
||||||
datatype: "newnote",
|
datatype: "newnote",
|
||||||
};
|
};
|
||||||
|
return doc;
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
if (ex.status && ex.status == 404) {
|
if (ex.status && ex.status == 404) {
|
||||||
@@ -523,7 +557,7 @@ class LocalPouchDB {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
async getDBEntry(id: string, opt?: PouchDB.Core.GetOptions, retryCount = 5): Promise<false | LoadedEntry> {
|
async getDBEntry(id: string, opt?: PouchDB.Core.GetOptions, dump = false): Promise<false | LoadedEntry> {
|
||||||
try {
|
try {
|
||||||
let obj: EntryDocResponse = null;
|
let obj: EntryDocResponse = null;
|
||||||
if (opt) {
|
if (opt) {
|
||||||
@@ -555,15 +589,28 @@ class LocalPouchDB {
|
|||||||
if (typeof this.corruptedEntries[doc._id] != "undefined") {
|
if (typeof this.corruptedEntries[doc._id] != "undefined") {
|
||||||
delete this.corruptedEntries[doc._id];
|
delete this.corruptedEntries[doc._id];
|
||||||
}
|
}
|
||||||
|
if (dump) {
|
||||||
|
Logger(`Simple doc`);
|
||||||
|
Logger(doc);
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
if (dump) {
|
||||||
|
Logger(`Enhanced doc`);
|
||||||
|
Logger(obj);
|
||||||
|
}
|
||||||
let childrens;
|
let childrens;
|
||||||
try {
|
try {
|
||||||
childrens = await Promise.all(obj.children.map((e) => this.getDBLeaf(e)));
|
childrens = await Promise.all(obj.children.map((e) => this.getDBLeaf(e)));
|
||||||
|
if (dump) {
|
||||||
|
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 elements of ${obj._id} from database.`, LOG_LEVEL.NOTICE);
|
||||||
this.corruptedEntries[obj._id] = obj;
|
this.corruptedEntries[obj._id] = obj;
|
||||||
@@ -583,6 +630,10 @@ class LocalPouchDB {
|
|||||||
datatype: obj.type,
|
datatype: obj.type,
|
||||||
_conflicts: obj._conflicts,
|
_conflicts: obj._conflicts,
|
||||||
};
|
};
|
||||||
|
if (dump) {
|
||||||
|
Logger(`therefore:`);
|
||||||
|
Logger(doc);
|
||||||
|
}
|
||||||
if (typeof this.corruptedEntries[doc._id] != "undefined") {
|
if (typeof this.corruptedEntries[doc._id] != "undefined") {
|
||||||
delete this.corruptedEntries[doc._id];
|
delete this.corruptedEntries[doc._id];
|
||||||
}
|
}
|
||||||
@@ -693,6 +744,18 @@ class LocalPouchDB {
|
|||||||
Logger(`deleteDBEntryPrefix:deleted ${deleteCount} items, skipped ${notfound}`);
|
Logger(`deleteDBEntryPrefix:deleted ${deleteCount} items, skipped ${notfound}`);
|
||||||
return true;
|
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) {
|
async putDBEntry(note: LoadedEntry) {
|
||||||
let leftData = note.data;
|
let leftData = note.data;
|
||||||
let savenNotes = [];
|
let savenNotes = [];
|
||||||
@@ -702,10 +765,11 @@ class LocalPouchDB {
|
|||||||
let pieceSize = MAX_DOC_SIZE_BIN;
|
let pieceSize = MAX_DOC_SIZE_BIN;
|
||||||
let plainSplit = false;
|
let plainSplit = false;
|
||||||
let cacheUsed = 0;
|
let cacheUsed = 0;
|
||||||
if (note._id.endsWith(".md")) {
|
if (this.isPlainText(note._id)) {
|
||||||
pieceSize = MAX_DOC_SIZE;
|
pieceSize = MAX_DOC_SIZE;
|
||||||
plainSplit = true;
|
plainSplit = true;
|
||||||
}
|
}
|
||||||
|
let newLeafs: EntryLeaf[] = [];
|
||||||
do {
|
do {
|
||||||
// To keep low bandwith and database size,
|
// To keep low bandwith and database size,
|
||||||
// Dedup pieces on database.
|
// Dedup pieces on database.
|
||||||
@@ -715,11 +779,11 @@ class LocalPouchDB {
|
|||||||
// 3. \r\n\r\n should break
|
// 3. \r\n\r\n should break
|
||||||
// 4. \n# should break.
|
// 4. \n# should break.
|
||||||
let cPieceSize = pieceSize;
|
let cPieceSize = pieceSize;
|
||||||
|
if (plainSplit) {
|
||||||
let minimumChunkSize = this.settings.minimumChunkSize;
|
let minimumChunkSize = this.settings.minimumChunkSize;
|
||||||
if (minimumChunkSize < 10) minimumChunkSize = 10;
|
if (minimumChunkSize < 10) minimumChunkSize = 10;
|
||||||
let longLineThreshold = this.settings.longLineThreshold;
|
let longLineThreshold = this.settings.longLineThreshold;
|
||||||
if (longLineThreshold < 100) longLineThreshold = 100;
|
if (longLineThreshold < 100) longLineThreshold = 100;
|
||||||
if (plainSplit) {
|
|
||||||
cPieceSize = 0;
|
cPieceSize = 0;
|
||||||
// lookup for next splittion .
|
// lookup for next splittion .
|
||||||
// we're standing on "\n"
|
// we're standing on "\n"
|
||||||
@@ -748,11 +812,13 @@ class LocalPouchDB {
|
|||||||
} while (cPieceSize < minimumChunkSize);
|
} while (cPieceSize < minimumChunkSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// piece size determined.
|
||||||
|
|
||||||
let piece = leftData.substring(0, cPieceSize);
|
let piece = leftData.substring(0, cPieceSize);
|
||||||
leftData = leftData.substring(cPieceSize);
|
leftData = leftData.substring(cPieceSize);
|
||||||
processed++;
|
processed++;
|
||||||
let leafid = "";
|
let leafid = "";
|
||||||
// Get has of piece.
|
// Get hash of piece.
|
||||||
let hashedPiece: string = "";
|
let hashedPiece: string = "";
|
||||||
let hashQ: number = 0; // if hash collided, **IF**, count it up.
|
let hashQ: number = 0; // if hash collided, **IF**, count it up.
|
||||||
let tryNextHash = false;
|
let tryNextHash = false;
|
||||||
@@ -803,23 +869,39 @@ class LocalPouchDB {
|
|||||||
data: piece,
|
data: piece,
|
||||||
type: "leaf",
|
type: "leaf",
|
||||||
};
|
};
|
||||||
let result = await this.localDatabase.put(d);
|
newLeafs.push(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.hashCache[piece] = leafid;
|
||||||
this.hashCacheRev[leafid] = piece;
|
this.hashCacheRev[leafid] = piece;
|
||||||
made++;
|
made++;
|
||||||
} else {
|
|
||||||
Logger("save faild");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
skiped++;
|
skiped++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
savenNotes.push(leafid);
|
savenNotes.push(leafid);
|
||||||
} while (leftData != "");
|
} while (leftData != "");
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (saved) {
|
||||||
Logger(`note content saven, pieces:${processed} new:${made}, skip:${skiped}, cache:${cacheUsed}`);
|
Logger(`note content saven, pieces:${processed} new:${made}, skip:${skiped}, cache:${cacheUsed}`);
|
||||||
let newDoc: PlainEntry | NewEntry = {
|
let newDoc: PlainEntry | NewEntry = {
|
||||||
NewNote: true,
|
NewNote: true,
|
||||||
@@ -850,6 +932,9 @@ class LocalPouchDB {
|
|||||||
delete this.corruptedEntries[note._id];
|
delete this.corruptedEntries[note._id];
|
||||||
}
|
}
|
||||||
Logger(`note saven:${newDoc._id}:${r.rev}`);
|
Logger(`note saven:${newDoc._id}:${r.rev}`);
|
||||||
|
} else {
|
||||||
|
Logger(`note coud not saved:${note._id}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
syncHandler: PouchDB.Replication.Sync<{}> = null;
|
syncHandler: PouchDB.Replication.Sync<{}> = null;
|
||||||
@@ -900,12 +985,12 @@ class LocalPouchDB {
|
|||||||
this.docSent += e.docs_written;
|
this.docSent += e.docs_written;
|
||||||
this.docArrived += e.docs_read;
|
this.docArrived += e.docs_read;
|
||||||
this.updateInfo();
|
this.updateInfo();
|
||||||
Logger(`sending..:${e.docs.length}`);
|
Logger(`replicateAllToServer: sending..:${e.docs.length}`);
|
||||||
})
|
})
|
||||||
.on("complete", async (info) => {
|
.on("complete", async (info) => {
|
||||||
this.syncStatus = "COMPLETED";
|
this.syncStatus = "COMPLETED";
|
||||||
this.updateInfo();
|
this.updateInfo();
|
||||||
Logger("Completed", LOG_LEVEL.NOTICE);
|
Logger("replicateAllToServer: Completed", LOG_LEVEL.NOTICE);
|
||||||
replicate.cancel();
|
replicate.cancel();
|
||||||
replicate.removeAllListeners();
|
replicate.removeAllListeners();
|
||||||
res(true);
|
res(true);
|
||||||
@@ -913,13 +998,21 @@ class LocalPouchDB {
|
|||||||
.on("error", (e) => {
|
.on("error", (e) => {
|
||||||
this.syncStatus = "ERRORED";
|
this.syncStatus = "ERRORED";
|
||||||
this.updateInfo();
|
this.updateInfo();
|
||||||
Logger("Pulling Replication error", LOG_LEVEL.NOTICE);
|
Logger("replicateAllToServer: Pulling Replication error", LOG_LEVEL.INFO);
|
||||||
Logger(e);
|
Logger(e);
|
||||||
|
replicate.cancel();
|
||||||
|
replicate.removeAllListeners();
|
||||||
rej(e);
|
rej(e);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async openReplication(setting: ObsidianLiveSyncSettings, keepAlive: boolean, showResult: boolean, callback: (e: PouchDB.Core.ExistingDocument<{}>[]) => Promise<void>) {
|
async openReplication(setting: ObsidianLiveSyncSettings, keepAlive: boolean, showResult: boolean, callback: (e: PouchDB.Core.ExistingDocument<{}>[]) => Promise<void>) {
|
||||||
|
if (!this.isReady) {
|
||||||
|
Logger("Database is not ready.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (setting.versionUpFlash != "") {
|
if (setting.versionUpFlash != "") {
|
||||||
new Notice("Open settings and check message, please.");
|
new Notice("Open settings and check message, please.");
|
||||||
return;
|
return;
|
||||||
@@ -973,11 +1066,15 @@ class LocalPouchDB {
|
|||||||
let db = dbret.db;
|
let db = dbret.db;
|
||||||
//replicate once
|
//replicate once
|
||||||
this.syncStatus = "CONNECTED";
|
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);
|
let replicate = this.localDatabase.replicate.from(db, syncOptionBase);
|
||||||
replicate
|
replicate
|
||||||
.on("active", () => {
|
.on("active", () => {
|
||||||
this.syncStatus = "CONNECTED";
|
this.syncStatus = "CONNECTED";
|
||||||
this.updateInfo();
|
this.updateInfo();
|
||||||
|
Logger("Replication pull activated.");
|
||||||
})
|
})
|
||||||
.on("change", async (e) => {
|
.on("change", async (e) => {
|
||||||
// when in first run, replication will send us tombstone data
|
// when in first run, replication will send us tombstone data
|
||||||
@@ -1004,6 +1101,7 @@ class LocalPouchDB {
|
|||||||
this.syncHandler.cancel();
|
this.syncHandler.cancel();
|
||||||
this.syncHandler.removeAllListeners();
|
this.syncHandler.removeAllListeners();
|
||||||
}
|
}
|
||||||
|
Logger("Replication pull completed.");
|
||||||
this.syncHandler = this.localDatabase.sync(db, syncOption);
|
this.syncHandler = this.localDatabase.sync(db, syncOption);
|
||||||
this.syncHandler
|
this.syncHandler
|
||||||
.on("active", () => {
|
.on("active", () => {
|
||||||
@@ -1051,7 +1149,13 @@ class LocalPouchDB {
|
|||||||
.on("error", (e) => {
|
.on("error", (e) => {
|
||||||
this.syncStatus = "ERRORED";
|
this.syncStatus = "ERRORED";
|
||||||
this.updateInfo();
|
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);
|
Logger(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1070,9 +1174,11 @@ class LocalPouchDB {
|
|||||||
|
|
||||||
async resetDatabase() {
|
async resetDatabase() {
|
||||||
if (this.changeHandler != null) {
|
if (this.changeHandler != null) {
|
||||||
|
this.changeHandler.removeAllListeners();
|
||||||
this.changeHandler.cancel();
|
this.changeHandler.cancel();
|
||||||
}
|
}
|
||||||
await this.closeReplication();
|
await this.closeReplication();
|
||||||
|
this.isReady = false;
|
||||||
await this.localDatabase.destroy();
|
await this.localDatabase.destroy();
|
||||||
this.localDatabase = null;
|
this.localDatabase = null;
|
||||||
await this.initializeDatabase();
|
await this.initializeDatabase();
|
||||||
@@ -1080,7 +1186,6 @@ class LocalPouchDB {
|
|||||||
Logger("Local Database Reset", LOG_LEVEL.NOTICE);
|
Logger("Local Database Reset", LOG_LEVEL.NOTICE);
|
||||||
}
|
}
|
||||||
async tryResetRemoteDatabase(setting: ObsidianLiveSyncSettings) {
|
async tryResetRemoteDatabase(setting: ObsidianLiveSyncSettings) {
|
||||||
await this.closeReplication();
|
|
||||||
await this.closeReplication();
|
await this.closeReplication();
|
||||||
let uri = setting.couchDB_URI;
|
let uri = setting.couchDB_URI;
|
||||||
let auth: Credential = {
|
let auth: Credential = {
|
||||||
@@ -1181,9 +1286,11 @@ class LocalPouchDB {
|
|||||||
let readCount = 0;
|
let readCount = 0;
|
||||||
let hashPieces: string[] = [];
|
let hashPieces: string[] = [];
|
||||||
let usedPieces: string[] = [];
|
let usedPieces: string[] = [];
|
||||||
|
Logger("Collecting Garbage");
|
||||||
do {
|
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;
|
readCount = result.rows.length;
|
||||||
|
Logger("checked:" + readCount);
|
||||||
if (readCount > 0) {
|
if (readCount > 0) {
|
||||||
//there are some result
|
//there are some result
|
||||||
for (let v of result.rows) {
|
for (let v of result.rows) {
|
||||||
@@ -1209,13 +1316,21 @@ class LocalPouchDB {
|
|||||||
c += readCount;
|
c += readCount;
|
||||||
} while (readCount != 0);
|
} while (readCount != 0);
|
||||||
// items collected.
|
// items collected.
|
||||||
|
Logger("Finding unused pieces");
|
||||||
const garbages = hashPieces.filter((e) => usedPieces.indexOf(e) == -1);
|
const garbages = hashPieces.filter((e) => usedPieces.indexOf(e) == -1);
|
||||||
let deleteCount = 0;
|
let deleteCount = 0;
|
||||||
|
Logger("we have to delete:" + garbages.length);
|
||||||
|
let deleteDoc: EntryDoc[] = [];
|
||||||
for (let v of garbages) {
|
for (let v of garbages) {
|
||||||
try {
|
try {
|
||||||
let item = await this.localDatabase.get(v);
|
let item = await this.localDatabase.get(v);
|
||||||
item._deleted = true;
|
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++;
|
deleteCount++;
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
if (ex.status && ex.status == 404) {
|
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.`);
|
Logger(`GC:deleted ${deleteCount} items.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1246,6 +1364,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
this.settings.liveSync = false;
|
this.settings.liveSync = false;
|
||||||
this.settings.syncOnSave = false;
|
this.settings.syncOnSave = false;
|
||||||
this.settings.syncOnStart = 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.settings.versionUpFlash = "I changed specifications incompatiblly, so when you enable sync again, be sure to made version up all nother devides.";
|
||||||
this.saveSettings();
|
this.saveSettings();
|
||||||
}
|
}
|
||||||
@@ -1295,30 +1414,19 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
this.watchWorkspaceOpen = debounce(this.watchWorkspaceOpen.bind(this), delay, false);
|
this.watchWorkspaceOpen = debounce(this.watchWorkspaceOpen.bind(this), delay, false);
|
||||||
this.watchWindowVisiblity = debounce(this.watchWindowVisiblity.bind(this), delay, false);
|
this.watchWindowVisiblity = debounce(this.watchWindowVisiblity.bind(this), delay, false);
|
||||||
|
|
||||||
this.registerWatchEvents();
|
|
||||||
this.parseReplicationResult = this.parseReplicationResult.bind(this);
|
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.addSettingTab(new ObsidianLiveSyncSettingTab(this.app, this));
|
||||||
|
|
||||||
this.app.workspace.onLayoutReady(async () => {
|
this.app.workspace.onLayoutReady(async () => {
|
||||||
await this.initializeDatabase();
|
await this.initializeDatabase();
|
||||||
this.realizeSettingSyncMode();
|
this.realizeSettingSyncMode();
|
||||||
if (this.settings.syncOnStart) {
|
this.registerWatchEvents();
|
||||||
await this.replicate(false);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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({
|
this.addCommand({
|
||||||
id: "livesync-replicate",
|
id: "livesync-replicate",
|
||||||
name: "Replicate now",
|
name: "Replicate now",
|
||||||
@@ -1326,6 +1434,14 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
this.replicate();
|
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({
|
// this.addCommand({
|
||||||
// id: "livesync-test",
|
// id: "livesync-test",
|
||||||
// name: "test reset db and replicate",
|
// name: "test reset db and replicate",
|
||||||
@@ -1356,14 +1472,13 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
this.saveSettings();
|
this.saveSettings();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.watchWindowVisiblity = this.watchWindowVisiblity.bind(this);
|
|
||||||
window.addEventListener("visibilitychange", this.watchWindowVisiblity);
|
|
||||||
}
|
}
|
||||||
onunload() {
|
onunload() {
|
||||||
if (this.gcTimerHandler != null) {
|
if (this.gcTimerHandler != null) {
|
||||||
clearTimeout(this.gcTimerHandler);
|
clearTimeout(this.gcTimerHandler);
|
||||||
this.gcTimerHandler = null;
|
this.gcTimerHandler = null;
|
||||||
}
|
}
|
||||||
|
this.clearPeriodicSync();
|
||||||
this.localDatabase.closeReplication();
|
this.localDatabase.closeReplication();
|
||||||
this.localDatabase.close();
|
this.localDatabase.close();
|
||||||
window.removeEventListener("visibilitychange", this.watchWindowVisiblity);
|
window.removeEventListener("visibilitychange", this.watchWindowVisiblity);
|
||||||
@@ -1375,6 +1490,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
this.localDatabase.close();
|
this.localDatabase.close();
|
||||||
}
|
}
|
||||||
let vaultName = this.app.vault.getName();
|
let vaultName = this.app.vault.getName();
|
||||||
|
Logger("Open Database...");
|
||||||
this.localDatabase = new LocalPouchDB(this.settings, vaultName);
|
this.localDatabase = new LocalPouchDB(this.settings, vaultName);
|
||||||
this.localDatabase.updateInfo = () => {
|
this.localDatabase.updateInfo = () => {
|
||||||
this.refreshStatusText();
|
this.refreshStatusText();
|
||||||
@@ -1392,6 +1508,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
async saveSettings() {
|
async saveSettings() {
|
||||||
await this.saveData(this.settings);
|
await this.saveData(this.settings);
|
||||||
this.localDatabase.settings = this.settings;
|
this.localDatabase.settings = this.settings;
|
||||||
|
this.realizeSettingSyncMode();
|
||||||
}
|
}
|
||||||
gcTimerHandler: any = null;
|
gcTimerHandler: any = null;
|
||||||
gcHook() {
|
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("rename", this.watchVaultRename));
|
||||||
this.registerEvent(this.app.vault.on("create", this.watchVaultChange));
|
this.registerEvent(this.app.vault.on("create", this.watchVaultChange));
|
||||||
this.registerEvent(this.app.workspace.on("file-open", this.watchWorkspaceOpen));
|
this.registerEvent(this.app.workspace.on("file-open", this.watchWorkspaceOpen));
|
||||||
|
window.addEventListener("visibilitychange", this.watchWindowVisiblity);
|
||||||
}
|
}
|
||||||
|
|
||||||
watchWindowVisiblity() {
|
watchWindowVisiblity() {
|
||||||
@@ -1422,6 +1540,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
let isHidden = document.hidden;
|
let isHidden = document.hidden;
|
||||||
if (isHidden) {
|
if (isHidden) {
|
||||||
this.localDatabase.closeReplication();
|
this.localDatabase.closeReplication();
|
||||||
|
this.clearPeriodicSync();
|
||||||
} else {
|
} else {
|
||||||
if (this.settings.liveSync) {
|
if (this.settings.liveSync) {
|
||||||
await this.localDatabase.openReplication(this.settings, true, false, this.parseReplicationResult);
|
await this.localDatabase.openReplication(this.settings, true, false, this.parseReplicationResult);
|
||||||
@@ -1429,6 +1548,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
if (this.settings.syncOnStart) {
|
if (this.settings.syncOnStart) {
|
||||||
await this.localDatabase.openReplication(this.settings, false, false, this.parseReplicationResult);
|
await this.localDatabase.openReplication(this.settings, false, false, this.parseReplicationResult);
|
||||||
}
|
}
|
||||||
|
if (this.settings.periodicReplication) {
|
||||||
|
this.setPeriodicSync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.gcHook();
|
this.gcHook();
|
||||||
}
|
}
|
||||||
@@ -1439,6 +1561,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
async watchWorkspaceOpenAsync(file: TFile) {
|
async watchWorkspaceOpenAsync(file: TFile) {
|
||||||
if (file == null) return;
|
if (file == null) return;
|
||||||
|
if (this.settings.syncOnFileOpen) {
|
||||||
|
await this.replicate();
|
||||||
|
}
|
||||||
this.localDatabase.disposeHashCache();
|
this.localDatabase.disposeHashCache();
|
||||||
await this.showIfConflicted(file);
|
await this.showIfConflicted(file);
|
||||||
this.gcHook();
|
this.gcHook();
|
||||||
@@ -1453,10 +1578,13 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
batchFileChange: string[] = [];
|
batchFileChange: string[] = [];
|
||||||
async watchVaultChangeAsync(file: TFile, ...args: any[]) {
|
async watchVaultChangeAsync(file: TFile, ...args: any[]) {
|
||||||
|
if (file instanceof TFile) {
|
||||||
await this.updateIntoDB(file);
|
await this.updateIntoDB(file);
|
||||||
this.gcHook();
|
this.gcHook();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
watchVaultDelete(file: TFile | TFolder) {
|
watchVaultDelete(file: TFile | TFolder) {
|
||||||
|
console.log(`${file.path} delete`);
|
||||||
if (this.settings.suspendFileWatching) return;
|
if (this.settings.suspendFileWatching) return;
|
||||||
this.watchVaultDeleteAsync(file);
|
this.watchVaultDeleteAsync(file);
|
||||||
}
|
}
|
||||||
@@ -1518,7 +1646,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
this.gcHook();
|
this.gcHook();
|
||||||
}
|
}
|
||||||
|
addLogHook: () => void = null;
|
||||||
//--> Basic document Functions
|
//--> Basic document Functions
|
||||||
async addLog(message: any, level: LOG_LEVEL = LOG_LEVEL.INFO) {
|
async addLog(message: any, level: LOG_LEVEL = LOG_LEVEL.INFO) {
|
||||||
if (level < LOG_LEVEL.INFO && this.settings && this.settings.lessInformationInLog) {
|
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) {
|
if (level >= LOG_LEVEL.NOTICE) {
|
||||||
new Notice(messagecontent);
|
new Notice(messagecontent);
|
||||||
}
|
}
|
||||||
|
if (this.addLogHook != null) this.addLogHook();
|
||||||
}
|
}
|
||||||
|
|
||||||
async ensureDirectory(fullpath: string) {
|
async ensureDirectory(fullpath: string) {
|
||||||
@@ -1694,6 +1823,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
periodicSyncHandler: NodeJS.Timer = null;
|
||||||
//---> Sync
|
//---> Sync
|
||||||
async parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): Promise<void> {
|
async parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): Promise<void> {
|
||||||
this.refreshStatusText();
|
this.refreshStatusText();
|
||||||
@@ -1714,12 +1844,29 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
this.gcHook();
|
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() {
|
realizeSettingSyncMode() {
|
||||||
this.localDatabase.closeReplication();
|
this.localDatabase.closeReplication();
|
||||||
if (this.settings.liveSync) {
|
if (this.settings.liveSync) {
|
||||||
this.localDatabase.openReplication(this.settings, true, false, this.parseReplicationResult);
|
this.localDatabase.openReplication(this.settings, true, false, this.parseReplicationResult);
|
||||||
this.refreshStatusText();
|
this.refreshStatusText();
|
||||||
}
|
}
|
||||||
|
this.clearPeriodicSync();
|
||||||
|
this.setPeriodicSync();
|
||||||
}
|
}
|
||||||
refreshStatusText() {
|
refreshStatusText() {
|
||||||
let sent = this.localDatabase.docSent;
|
let sent = this.localDatabase.docSent;
|
||||||
@@ -1777,7 +1924,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
const filesStorage = this.app.vault.getFiles();
|
const filesStorage = this.app.vault.getFiles();
|
||||||
const filesStorageName = filesStorage.map((e) => e.path);
|
const filesStorageName = filesStorage.map((e) => e.path);
|
||||||
const wf = await this.localDatabase.localDatabase.allDocs();
|
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 onlyInStorage = filesStorage.filter((e) => filesDatabase.indexOf(e.path) == -1);
|
||||||
const onlyInDatabase = filesDatabase.filter((e) => filesStorageName.indexOf(e) == -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`);
|
this.statusBar.setText(`UPDATE DATABASE`);
|
||||||
// just write to DB from storage.
|
// just write to DB from storage.
|
||||||
for (let v of onlyInStorage) {
|
for (let v of onlyInStorage) {
|
||||||
|
Logger(`Update into ${v.path}`);
|
||||||
await this.updateIntoDB(v);
|
await this.updateIntoDB(v);
|
||||||
}
|
}
|
||||||
// simply realize it
|
// simply realize it
|
||||||
this.statusBar.setText(`UPDATE STORAGE`);
|
this.statusBar.setText(`UPDATE STORAGE`);
|
||||||
Logger("Writing files that only in database");
|
Logger("Writing files that only in database");
|
||||||
for (let v of onlyInDatabase) {
|
for (let v of onlyInDatabase) {
|
||||||
|
Logger(`Pull from db:${v}`);
|
||||||
await this.pullFile(v, filesStorage);
|
await this.pullFile(v, filesStorage);
|
||||||
}
|
}
|
||||||
// have to sync below..
|
// have to sync below..
|
||||||
@@ -1803,6 +1952,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
for (let v of syncFiles) {
|
for (let v of syncFiles) {
|
||||||
await this.syncFileBetweenDBandStorage(v, filesStorage);
|
await this.syncFileBetweenDBandStorage(v, filesStorage);
|
||||||
}
|
}
|
||||||
|
this.statusBar.setText(`NOW TRACKING!`);
|
||||||
Logger("Initialized");
|
Logger("Initialized");
|
||||||
}
|
}
|
||||||
async deleteFolderOnDB(folder: TFolder) {
|
async deleteFolderOnDB(folder: TFolder) {
|
||||||
@@ -2028,7 +2178,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
let datatype: "plain" | "newnote" = "newnote";
|
let datatype: "plain" | "newnote" = "newnote";
|
||||||
if (file.extension != "md") {
|
if (file.extension != "md") {
|
||||||
let contentBin = await this.app.vault.readBinary(file);
|
let contentBin = await this.app.vault.readBinary(file);
|
||||||
content = arrayBufferToBase64(contentBin);
|
content = await arrayBufferToBase64(contentBin);
|
||||||
datatype = "newnote";
|
datatype = "newnote";
|
||||||
} else {
|
} else {
|
||||||
content = await this.app.vault.read(file);
|
content = await this.app.vault.read(file);
|
||||||
@@ -2111,13 +2261,13 @@ class LogDisplayModal extends Modal {
|
|||||||
div.addClass("op-pre");
|
div.addClass("op-pre");
|
||||||
this.logEl = div;
|
this.logEl = div;
|
||||||
this.updateLog = this.updateLog.bind(this);
|
this.updateLog = this.updateLog.bind(this);
|
||||||
// this.plugin.onLogChanged = this.updateLog;
|
this.plugin.addLogHook = this.updateLog;
|
||||||
this.updateLog();
|
this.updateLog();
|
||||||
}
|
}
|
||||||
onClose() {
|
onClose() {
|
||||||
let { contentEl } = this;
|
let { contentEl } = this;
|
||||||
contentEl.empty();
|
contentEl.empty();
|
||||||
// this.plugin.onLogChanged = null;
|
this.plugin.addLogHook = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class ConflictResolveModal extends Modal {
|
class ConflictResolveModal extends Modal {
|
||||||
@@ -2342,6 +2492,33 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
this.plugin.realizeSettingSyncMode();
|
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)
|
new Setting(containerEl)
|
||||||
.setName("Sync on Save")
|
.setName("Sync on Save")
|
||||||
.setDesc("When you save file, sync automatically")
|
.setDesc("When you save file, sync automatically")
|
||||||
@@ -2351,6 +2528,15 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
await this.plugin.saveSettings();
|
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)
|
new Setting(containerEl)
|
||||||
.setName("Sync on Start")
|
.setName("Sync on Start")
|
||||||
.setDesc("Start synchronization on Obsidian started.")
|
.setDesc("Start synchronization on Obsidian started.")
|
||||||
@@ -2451,6 +2637,12 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
.setWarning()
|
.setWarning()
|
||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.onClick(async () => {
|
.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.resetLocalDatabase();
|
||||||
await this.plugin.initializeDatabase();
|
await this.plugin.initializeDatabase();
|
||||||
await this.plugin.tryResetRemoteDatabase();
|
await this.plugin.tryResetRemoteDatabase();
|
||||||
@@ -2529,6 +2721,8 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
let cx = containerEl.createEl("div", { text: "There's no collupted data." });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-livesync",
|
"id": "obsidian-livesync",
|
||||||
"name": "Self-hosted LiveSync",
|
"name": "Self-hosted LiveSync",
|
||||||
"version": "0.1.13",
|
"version": "0.1.14",
|
||||||
"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
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.1.13",
|
"version": "0.1.14",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.1.13",
|
"version": "0.1.14",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"diff-match-patch": "^1.0.5",
|
"diff-match-patch": "^1.0.5",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-livesync",
|
"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.",
|
"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": {
|
||||||
|
|||||||
Reference in New Issue
Block a user