|
|
|
|
@@ -53,6 +53,8 @@ interface ObsidianLiveSyncSettings {
|
|
|
|
|
batchSave: boolean;
|
|
|
|
|
deviceAndVaultName: string;
|
|
|
|
|
usePluginSettings: boolean;
|
|
|
|
|
showOwnPlugins: boolean;
|
|
|
|
|
showStatusOnEditor: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = {
|
|
|
|
|
@@ -84,6 +86,8 @@ const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = {
|
|
|
|
|
batchSave: false,
|
|
|
|
|
deviceAndVaultName: "",
|
|
|
|
|
usePluginSettings: false,
|
|
|
|
|
showOwnPlugins: false,
|
|
|
|
|
showStatusOnEditor: false,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
interface Entry {
|
|
|
|
|
@@ -351,7 +355,7 @@ const bumpRemoteVersion = async (db: PouchDB.Database, barrier: number = VER): P
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function isValidPath(filename: string): boolean {
|
|
|
|
|
let regex = /[\u0000-\u001f]|[\\"':?<>|*$]/g;
|
|
|
|
|
let regex = /[\u0000-\u001f]|[\\"':?<>|*]/g;
|
|
|
|
|
let x = filename.replace(regex, "_");
|
|
|
|
|
let win = /(\\|\/)(COM\d|LPT\d|CON|PRN|AUX|NUL|CLOCK$)($|\.)/gi;
|
|
|
|
|
let sx = (x = x.replace(win, "/_"));
|
|
|
|
|
@@ -549,6 +553,13 @@ async function testCrypt() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// <-- Encryption
|
|
|
|
|
const delay = (ms: number): Promise<void> => {
|
|
|
|
|
return new Promise((res) => {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
res();
|
|
|
|
|
}, ms);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
//<--Functions
|
|
|
|
|
class LocalPouchDB {
|
|
|
|
|
auth: Credential;
|
|
|
|
|
@@ -584,6 +595,7 @@ class LocalPouchDB {
|
|
|
|
|
// this.initializeDatabase();
|
|
|
|
|
}
|
|
|
|
|
close() {
|
|
|
|
|
Logger("Database closed (by close)");
|
|
|
|
|
this.isReady = false;
|
|
|
|
|
if (this.changeHandler != null) {
|
|
|
|
|
this.changeHandler.cancel();
|
|
|
|
|
@@ -645,6 +657,7 @@ class LocalPouchDB {
|
|
|
|
|
await this.localDatabase.put(nodeinfo);
|
|
|
|
|
}
|
|
|
|
|
this.localDatabase.on("close", () => {
|
|
|
|
|
Logger("Database closed.");
|
|
|
|
|
this.isReady = false;
|
|
|
|
|
});
|
|
|
|
|
this.nodeid = nodeinfo.nodeid;
|
|
|
|
|
@@ -663,6 +676,7 @@ class LocalPouchDB {
|
|
|
|
|
});
|
|
|
|
|
this.changeHandler = changes;
|
|
|
|
|
this.isReady = true;
|
|
|
|
|
Logger("Database is now ready.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async prepareHashFunctions() {
|
|
|
|
|
@@ -688,7 +702,7 @@ class LocalPouchDB {
|
|
|
|
|
waitForLeafReady(id: string): Promise<boolean> {
|
|
|
|
|
return new Promise((res, rej) => {
|
|
|
|
|
// Set timeout.
|
|
|
|
|
let timer = setTimeout(() => rej(false), LEAF_WAIT_TIMEOUT);
|
|
|
|
|
let timer = setTimeout(() => rej(new Error(`Leaf timed out:${id}`)), LEAF_WAIT_TIMEOUT);
|
|
|
|
|
if (typeof this.leafArrivedCallbacks[id] == "undefined") {
|
|
|
|
|
this.leafArrivedCallbacks[id] = [];
|
|
|
|
|
}
|
|
|
|
|
@@ -699,7 +713,8 @@ class LocalPouchDB {
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async getDBLeaf(id: string): Promise<string> {
|
|
|
|
|
async getDBLeaf(id: string, waitForReady: boolean): Promise<string> {
|
|
|
|
|
await this.waitForGCComplete();
|
|
|
|
|
// when in cache, use that.
|
|
|
|
|
if (this.hashCacheRev[id]) {
|
|
|
|
|
return this.hashCacheRev[id];
|
|
|
|
|
@@ -721,7 +736,7 @@ class LocalPouchDB {
|
|
|
|
|
}
|
|
|
|
|
throw new Error(`retrive leaf, but it was not leaf.`);
|
|
|
|
|
} catch (ex) {
|
|
|
|
|
if (ex.status && ex.status == 404) {
|
|
|
|
|
if (ex.status && ex.status == 404 && waitForReady) {
|
|
|
|
|
// just leaf is not ready.
|
|
|
|
|
// wait for on
|
|
|
|
|
if ((await this.waitForLeafReady(id)) === false) {
|
|
|
|
|
@@ -759,6 +774,7 @@ class LocalPouchDB {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async getDBEntryMeta(path: string, opt?: PouchDB.Core.GetOptions): Promise<false | LoadedEntry> {
|
|
|
|
|
await this.waitForGCComplete();
|
|
|
|
|
let id = path2id(path);
|
|
|
|
|
try {
|
|
|
|
|
let obj: EntryDocResponse = null;
|
|
|
|
|
@@ -776,6 +792,10 @@ class LocalPouchDB {
|
|
|
|
|
// retrieve metadata only
|
|
|
|
|
if (!obj.type || (obj.type && obj.type == "notes") || obj.type == "newnote" || obj.type == "plain") {
|
|
|
|
|
let note = obj as Entry;
|
|
|
|
|
let children: string[] = [];
|
|
|
|
|
if (obj.type == "newnote" || obj.type == "plain") {
|
|
|
|
|
children = obj.children;
|
|
|
|
|
}
|
|
|
|
|
let doc: LoadedEntry & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta = {
|
|
|
|
|
data: "",
|
|
|
|
|
_id: note._id,
|
|
|
|
|
@@ -785,7 +805,7 @@ class LocalPouchDB {
|
|
|
|
|
_deleted: obj._deleted,
|
|
|
|
|
_rev: obj._rev,
|
|
|
|
|
_conflicts: obj._conflicts,
|
|
|
|
|
children: [],
|
|
|
|
|
children: children,
|
|
|
|
|
datatype: "newnote",
|
|
|
|
|
};
|
|
|
|
|
return doc;
|
|
|
|
|
@@ -798,7 +818,8 @@ class LocalPouchDB {
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
async getDBEntry(path: string, opt?: PouchDB.Core.GetOptions, dump = false): Promise<false | LoadedEntry> {
|
|
|
|
|
async getDBEntry(path: string, opt?: PouchDB.Core.GetOptions, dump = false, waitForReady = true): Promise<false | LoadedEntry> {
|
|
|
|
|
await this.waitForGCComplete();
|
|
|
|
|
let id = path2id(path);
|
|
|
|
|
try {
|
|
|
|
|
let obj: EntryDocResponse = null;
|
|
|
|
|
@@ -848,13 +869,14 @@ class LocalPouchDB {
|
|
|
|
|
}
|
|
|
|
|
let childrens: string[];
|
|
|
|
|
try {
|
|
|
|
|
childrens = await Promise.all(obj.children.map((e) => this.getDBLeaf(e)));
|
|
|
|
|
childrens = await Promise.all(obj.children.map((e) => this.getDBLeaf(e, waitForReady)));
|
|
|
|
|
if (dump) {
|
|
|
|
|
Logger(`childrens:`);
|
|
|
|
|
Logger(childrens);
|
|
|
|
|
}
|
|
|
|
|
} catch (ex) {
|
|
|
|
|
Logger(`Something went wrong on reading elements of ${obj._id} from database.`, LOG_LEVEL.NOTICE);
|
|
|
|
|
Logger(ex, LOG_LEVEL.VERBOSE);
|
|
|
|
|
this.corruptedEntries[obj._id] = obj;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
@@ -897,6 +919,7 @@ class LocalPouchDB {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
async deleteDBEntry(path: string, opt?: PouchDB.Core.GetOptions): Promise<boolean> {
|
|
|
|
|
await this.waitForGCComplete();
|
|
|
|
|
let id = path2id(path);
|
|
|
|
|
try {
|
|
|
|
|
let obj: EntryDocResponse = null;
|
|
|
|
|
@@ -939,6 +962,7 @@ class LocalPouchDB {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
async deleteDBEntryPrefix(prefixSrc: string): Promise<boolean> {
|
|
|
|
|
await this.waitForGCComplete();
|
|
|
|
|
// delete database entries by prefix.
|
|
|
|
|
// it called from folder deletion.
|
|
|
|
|
let c = 0;
|
|
|
|
|
@@ -1000,6 +1024,7 @@ class LocalPouchDB {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
async putDBEntry(note: LoadedEntry) {
|
|
|
|
|
await this.waitForGCComplete();
|
|
|
|
|
let leftData = note.data;
|
|
|
|
|
let savenNotes = [];
|
|
|
|
|
let processed = 0;
|
|
|
|
|
@@ -1161,7 +1186,7 @@ class LocalPouchDB {
|
|
|
|
|
} else {
|
|
|
|
|
Logger(`save failed:id:${item.id} rev:${item.rev}`, LOG_LEVEL.NOTICE);
|
|
|
|
|
Logger(item);
|
|
|
|
|
this.disposeHashCache();
|
|
|
|
|
// this.disposeHashCache();
|
|
|
|
|
saved = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -1197,7 +1222,7 @@ class LocalPouchDB {
|
|
|
|
|
throw ex;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let r = await this.localDatabase.put(newDoc);
|
|
|
|
|
let r = await this.localDatabase.put(newDoc, { force: true });
|
|
|
|
|
this.updateRecentModifiedDocs(r.id, r.rev, newDoc._deleted);
|
|
|
|
|
if (typeof this.corruptedEntries[note._id] != "undefined") {
|
|
|
|
|
delete this.corruptedEntries[note._id];
|
|
|
|
|
@@ -1223,6 +1248,7 @@ class LocalPouchDB {
|
|
|
|
|
}
|
|
|
|
|
replicateAllToServer(setting: ObsidianLiveSyncSettings, showingNotice?: boolean) {
|
|
|
|
|
return new Promise(async (res, rej) => {
|
|
|
|
|
await this.waitForGCComplete();
|
|
|
|
|
this.closeReplication();
|
|
|
|
|
Logger("send all data to server", LOG_LEVEL.NOTICE);
|
|
|
|
|
let notice: Notice = null;
|
|
|
|
|
@@ -1295,6 +1321,7 @@ class LocalPouchDB {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await this.waitForGCComplete();
|
|
|
|
|
if (setting.versionUpFlash != "") {
|
|
|
|
|
new Notice("Open settings and check message, please.");
|
|
|
|
|
return;
|
|
|
|
|
@@ -1471,11 +1498,13 @@ class LocalPouchDB {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async resetDatabase() {
|
|
|
|
|
await this.waitForGCComplete();
|
|
|
|
|
if (this.changeHandler != null) {
|
|
|
|
|
this.changeHandler.removeAllListeners();
|
|
|
|
|
this.changeHandler.cancel();
|
|
|
|
|
}
|
|
|
|
|
await this.closeReplication();
|
|
|
|
|
Logger("Database closed for reset Database.");
|
|
|
|
|
this.isReady = false;
|
|
|
|
|
await this.localDatabase.destroy();
|
|
|
|
|
this.localDatabase = null;
|
|
|
|
|
@@ -1576,72 +1605,87 @@ class LocalPouchDB {
|
|
|
|
|
Logger("Mark this device as 'resolved'.", LOG_LEVEL.NOTICE);
|
|
|
|
|
await dbret.db.put(remoteMilestone);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gcRunning = false;
|
|
|
|
|
async waitForGCComplete() {
|
|
|
|
|
while (this.gcRunning) {
|
|
|
|
|
Logger("Waiting for Garbage Collection completed.");
|
|
|
|
|
await delay(1000);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
async garbageCollect() {
|
|
|
|
|
// get all documents of NewEntry2
|
|
|
|
|
// we don't use queries , just use allDocs();
|
|
|
|
|
let c = 0;
|
|
|
|
|
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: 500, conflicts: true });
|
|
|
|
|
readCount = result.rows.length;
|
|
|
|
|
Logger("checked:" + readCount);
|
|
|
|
|
if (readCount > 0) {
|
|
|
|
|
//there are some result
|
|
|
|
|
for (let v of result.rows) {
|
|
|
|
|
let doc = v.doc;
|
|
|
|
|
if (doc.type == "newnote" || doc.type == "plain") {
|
|
|
|
|
// used pieces memo.
|
|
|
|
|
usedPieces = Array.from(new Set([...usedPieces, ...doc.children]));
|
|
|
|
|
if (doc._conflicts) {
|
|
|
|
|
for (let cid of doc._conflicts) {
|
|
|
|
|
let p = await this.localDatabase.get<EntryDoc>(doc._id, { rev: cid });
|
|
|
|
|
if (p.type == "newnote" || p.type == "plain") {
|
|
|
|
|
usedPieces = Array.from(new Set([...usedPieces, ...p.children]));
|
|
|
|
|
if (this.gcRunning) return;
|
|
|
|
|
this.gcRunning = true;
|
|
|
|
|
try {
|
|
|
|
|
// get all documents of NewEntry2
|
|
|
|
|
// we don't use queries , just use allDocs();
|
|
|
|
|
this.disposeHashCache();
|
|
|
|
|
let c = 0;
|
|
|
|
|
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: 500, conflicts: true });
|
|
|
|
|
readCount = result.rows.length;
|
|
|
|
|
Logger("checked:" + readCount);
|
|
|
|
|
if (readCount > 0) {
|
|
|
|
|
//there are some result
|
|
|
|
|
for (let v of result.rows) {
|
|
|
|
|
let doc = v.doc;
|
|
|
|
|
if (doc.type == "newnote" || doc.type == "plain") {
|
|
|
|
|
// used pieces memo.
|
|
|
|
|
usedPieces = Array.from(new Set([...usedPieces, ...doc.children]));
|
|
|
|
|
if (doc._conflicts) {
|
|
|
|
|
for (let cid of doc._conflicts) {
|
|
|
|
|
let p = await this.localDatabase.get<EntryDoc>(doc._id, { rev: cid });
|
|
|
|
|
if (p.type == "newnote" || p.type == "plain") {
|
|
|
|
|
usedPieces = Array.from(new Set([...usedPieces, ...p.children]));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (doc.type == "leaf") {
|
|
|
|
|
// all pieces.
|
|
|
|
|
hashPieces = Array.from(new Set([...hashPieces, doc._id]));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (doc.type == "leaf") {
|
|
|
|
|
// all pieces.
|
|
|
|
|
hashPieces = Array.from(new Set([...hashPieces, doc._id]));
|
|
|
|
|
}
|
|
|
|
|
c += readCount;
|
|
|
|
|
} while (readCount != 0);
|
|
|
|
|
// items collected.
|
|
|
|
|
Logger("Finding unused pieces");
|
|
|
|
|
this.disposeHashCache();
|
|
|
|
|
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;
|
|
|
|
|
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) {
|
|
|
|
|
// NO OP. It should be timing problem.
|
|
|
|
|
} else {
|
|
|
|
|
throw ex;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
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) {
|
|
|
|
|
// NO OP. It should be timing problem.
|
|
|
|
|
} else {
|
|
|
|
|
throw ex;
|
|
|
|
|
}
|
|
|
|
|
if (deleteDoc.length > 0) {
|
|
|
|
|
await this.localDatabase.bulkDocs(deleteDoc);
|
|
|
|
|
}
|
|
|
|
|
Logger(`GC:deleted ${deleteCount} items.`);
|
|
|
|
|
} finally {
|
|
|
|
|
this.gcRunning = false;
|
|
|
|
|
}
|
|
|
|
|
if (deleteDoc.length > 0) {
|
|
|
|
|
await this.localDatabase.bulkDocs(deleteDoc);
|
|
|
|
|
}
|
|
|
|
|
Logger(`GC:deleted ${deleteCount} items.`);
|
|
|
|
|
this.disposeHashCache();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1741,7 +1785,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|
|
|
|
name: "Dump informations of this doc ",
|
|
|
|
|
editorCallback: (editor: Editor, view: MarkdownView) => {
|
|
|
|
|
//this.replicate();
|
|
|
|
|
this.localDatabase.getDBEntry(view.file.path, {}, true);
|
|
|
|
|
this.localDatabase.getDBEntry(view.file.path, {}, true, false);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
this.addCommand({
|
|
|
|
|
@@ -1993,6 +2037,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|
|
|
|
}
|
|
|
|
|
addLogHook: () => void = null;
|
|
|
|
|
//--> Basic document Functions
|
|
|
|
|
notifies: { [key: string]: { notice: Notice; timer: NodeJS.Timeout; count: number } } = {};
|
|
|
|
|
async addLog(message: any, level: LOG_LEVEL = LOG_LEVEL.INFO) {
|
|
|
|
|
if (level < LOG_LEVEL.INFO && this.settings && this.settings.lessInformationInLog) {
|
|
|
|
|
return;
|
|
|
|
|
@@ -2010,8 +2055,32 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|
|
|
|
// if (this.statusBar2 != null) {
|
|
|
|
|
// this.statusBar2.setText(newmessage.substring(0, 60));
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
if (level >= LOG_LEVEL.NOTICE) {
|
|
|
|
|
new Notice(messagecontent);
|
|
|
|
|
if (messagecontent in this.notifies) {
|
|
|
|
|
clearTimeout(this.notifies[messagecontent].timer);
|
|
|
|
|
this.notifies[messagecontent].count++;
|
|
|
|
|
this.notifies[messagecontent].notice.setMessage(`(${this.notifies[messagecontent].count}):${messagecontent}`);
|
|
|
|
|
this.notifies[messagecontent].timer = setTimeout(() => {
|
|
|
|
|
const notify = this.notifies[messagecontent].notice;
|
|
|
|
|
delete this.notifies[messagecontent];
|
|
|
|
|
try {
|
|
|
|
|
notify.hide();
|
|
|
|
|
} catch (ex) {
|
|
|
|
|
// NO OP
|
|
|
|
|
}
|
|
|
|
|
}, 5000);
|
|
|
|
|
} else {
|
|
|
|
|
let notify = new Notice(messagecontent, 0);
|
|
|
|
|
this.notifies[messagecontent] = {
|
|
|
|
|
count: 0,
|
|
|
|
|
notice: notify,
|
|
|
|
|
timer: setTimeout(() => {
|
|
|
|
|
delete this.notifies[messagecontent];
|
|
|
|
|
notify.hide();
|
|
|
|
|
}, 5000),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (this.addLogHook != null) this.addLogHook();
|
|
|
|
|
}
|
|
|
|
|
@@ -2182,10 +2251,16 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|
|
|
|
this.refreshStatusText();
|
|
|
|
|
for (var change of docs) {
|
|
|
|
|
if (this.localDatabase.isSelfModified(change._id, change._rev)) {
|
|
|
|
|
return;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (change._id.startsWith("ps:")) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (change._id.startsWith("h:")) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
Logger("replication change arrived", LOG_LEVEL.VERBOSE);
|
|
|
|
|
if (change.type != "leaf" && change.type != "versioninfo" && change.type != "milestoneinfo" && change.type != "nodeinfo" && change.type != "plugin") {
|
|
|
|
|
Logger("replication change arrived", LOG_LEVEL.VERBOSE);
|
|
|
|
|
await this.handleDBChanged(change);
|
|
|
|
|
}
|
|
|
|
|
if (change.type == "versioninfo") {
|
|
|
|
|
@@ -2224,6 +2299,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|
|
|
|
}
|
|
|
|
|
this.setPeriodicSync();
|
|
|
|
|
}
|
|
|
|
|
lastMessage = "";
|
|
|
|
|
refreshStatusText() {
|
|
|
|
|
let sent = this.localDatabase.docSent;
|
|
|
|
|
let arrived = this.localDatabase.docArrived;
|
|
|
|
|
@@ -2250,9 +2326,24 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|
|
|
|
this.statusBar.title = this.localDatabase.syncStatus;
|
|
|
|
|
let waiting = "";
|
|
|
|
|
if (this.settings.batchSave) {
|
|
|
|
|
waiting = " " + this.batchFileChange.map((e) => "🚀").join("");
|
|
|
|
|
waiting = " " + this.batchFileChange.map((e) => "🛫").join("");
|
|
|
|
|
waiting = waiting.replace(/🛫{10}/g, "🚀");
|
|
|
|
|
}
|
|
|
|
|
const message = `Sync:${w} ↑${sent} ↓${arrived}${waiting}`;
|
|
|
|
|
this.setStatusBarText(message);
|
|
|
|
|
}
|
|
|
|
|
setStatusBarText(message: string) {
|
|
|
|
|
if (this.lastMessage != message) {
|
|
|
|
|
this.statusBar.setText(message);
|
|
|
|
|
if (this.settings.showStatusOnEditor) {
|
|
|
|
|
const root = document.documentElement;
|
|
|
|
|
root.style.setProperty("--slsmessage", '"' + message + '"');
|
|
|
|
|
} else {
|
|
|
|
|
const root = document.documentElement;
|
|
|
|
|
root.style.setProperty("--slsmessage", '""');
|
|
|
|
|
}
|
|
|
|
|
this.lastMessage = message;
|
|
|
|
|
}
|
|
|
|
|
this.statusBar.setText(`Sync:${w} ↑${sent} ↓${arrived}${waiting}`);
|
|
|
|
|
}
|
|
|
|
|
async replicate(showMessage?: boolean) {
|
|
|
|
|
if (this.settings.versionUpFlash != "") {
|
|
|
|
|
@@ -2298,7 +2389,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|
|
|
|
const syncFiles = filesStorage.filter((e) => onlyInStorageNames.indexOf(e.path) == -1);
|
|
|
|
|
Logger("Initialize and checking database files");
|
|
|
|
|
Logger("Updating database by new files");
|
|
|
|
|
this.statusBar.setText(`UPDATE DATABASE`);
|
|
|
|
|
this.setStatusBarText(`UPDATE DATABASE`);
|
|
|
|
|
let _this = this;
|
|
|
|
|
async function runAll<T>(procedurename: string, objects: T[], callback: (arg: T) => Promise<void>) {
|
|
|
|
|
const count = objects.length;
|
|
|
|
|
Logger(procedurename);
|
|
|
|
|
@@ -2315,7 +2407,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|
|
|
|
if (notice != null) notice.setMessage(notify);
|
|
|
|
|
Logger(notify);
|
|
|
|
|
// lastTicks = performance.now() + 2000;
|
|
|
|
|
// this.statusBar.setText(notify);
|
|
|
|
|
_this.setStatusBarText(notify);
|
|
|
|
|
}
|
|
|
|
|
} catch (ex) {
|
|
|
|
|
Logger(`Error while ${procedurename}`, LOG_LEVEL.NOTICE);
|
|
|
|
|
@@ -2346,12 +2438,12 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|
|
|
|
});
|
|
|
|
|
await runAll("UPDATE STORAGE", onlyInDatabase, async (e) => {
|
|
|
|
|
Logger(`Pull from db:${e}`);
|
|
|
|
|
await this.pullFile(e, filesStorage);
|
|
|
|
|
await this.pullFile(e, filesStorage, false, null, false);
|
|
|
|
|
});
|
|
|
|
|
await runAll("CHECK FILE STATUS", syncFiles, async (e) => {
|
|
|
|
|
await this.syncFileBetweenDBandStorage(e, filesStorage);
|
|
|
|
|
});
|
|
|
|
|
this.statusBar.setText(`NOW TRACKING!`);
|
|
|
|
|
this.setStatusBarText(`NOW TRACKING!`);
|
|
|
|
|
Logger("Initialized,NOW TRACKING!");
|
|
|
|
|
if (showingNotice) {
|
|
|
|
|
notice.hide();
|
|
|
|
|
@@ -2547,20 +2639,20 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|
|
|
|
}).open();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
async pullFile(filename: string, fileList?: TFile[], force?: boolean, rev?: string) {
|
|
|
|
|
async pullFile(filename: string, fileList?: TFile[], force?: boolean, rev?: string, waitForReady: boolean = true) {
|
|
|
|
|
if (!fileList) {
|
|
|
|
|
fileList = this.app.vault.getFiles();
|
|
|
|
|
}
|
|
|
|
|
let targetFiles = fileList.filter((e) => e.path == id2path(filename));
|
|
|
|
|
if (targetFiles.length == 0) {
|
|
|
|
|
//have to create;
|
|
|
|
|
let doc = await this.localDatabase.getDBEntry(filename, rev ? { rev: rev } : null);
|
|
|
|
|
let doc = await this.localDatabase.getDBEntry(filename, rev ? { rev: rev } : null, false, waitForReady);
|
|
|
|
|
if (doc === false) return;
|
|
|
|
|
await this.doc2storage_create(doc, force);
|
|
|
|
|
} else if (targetFiles.length == 1) {
|
|
|
|
|
//normal case
|
|
|
|
|
let file = targetFiles[0];
|
|
|
|
|
let doc = await this.localDatabase.getDBEntry(filename, rev ? { rev: rev } : null);
|
|
|
|
|
let doc = await this.localDatabase.getDBEntry(filename, rev ? { rev: rev } : null, false, waitForReady);
|
|
|
|
|
if (doc === false) return;
|
|
|
|
|
await this.doc2storate_modify(doc, file, force);
|
|
|
|
|
} else {
|
|
|
|
|
@@ -2572,18 +2664,19 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|
|
|
|
async syncFileBetweenDBandStorage(file: TFile, fileList?: TFile[]) {
|
|
|
|
|
let doc = await this.localDatabase.getDBEntryMeta(file.path);
|
|
|
|
|
if (doc === false) return;
|
|
|
|
|
|
|
|
|
|
let storageMtime = ~~(file.stat.mtime / 1000);
|
|
|
|
|
let docMtime = ~~(doc.mtime / 1000);
|
|
|
|
|
if (storageMtime > docMtime) {
|
|
|
|
|
//newer local file.
|
|
|
|
|
Logger("DB -> STORAGE :" + file.path);
|
|
|
|
|
Logger("STORAGE -> DB :" + file.path);
|
|
|
|
|
Logger(`${storageMtime} > ${docMtime}`);
|
|
|
|
|
await this.updateIntoDB(file);
|
|
|
|
|
} else if (storageMtime < docMtime) {
|
|
|
|
|
//newer database file.
|
|
|
|
|
Logger("STORAGE <- DB :" + file.path);
|
|
|
|
|
Logger(`${storageMtime} < ${docMtime}`);
|
|
|
|
|
let docx = await this.localDatabase.getDBEntry(file.path);
|
|
|
|
|
let docx = await this.localDatabase.getDBEntry(file.path, null, false, false);
|
|
|
|
|
if (docx != false) {
|
|
|
|
|
await this.doc2storate_modify(docx, file);
|
|
|
|
|
}
|
|
|
|
|
@@ -2595,6 +2688,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async updateIntoDB(file: TFile) {
|
|
|
|
|
await this.localDatabase.waitForGCComplete();
|
|
|
|
|
let content = "";
|
|
|
|
|
let datatype: "plain" | "newnote" = "newnote";
|
|
|
|
|
if (file.extension != "md") {
|
|
|
|
|
@@ -2616,7 +2710,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|
|
|
|
datatype: datatype,
|
|
|
|
|
};
|
|
|
|
|
//From here
|
|
|
|
|
let old = await this.localDatabase.getDBEntry(fullpath);
|
|
|
|
|
let old = await this.localDatabase.getDBEntry(fullpath, null, false, false);
|
|
|
|
|
if (old !== false) {
|
|
|
|
|
let oldData = { data: old.data, deleted: old._deleted };
|
|
|
|
|
let newData = { data: d.data, deleted: d._deleted };
|
|
|
|
|
@@ -2788,6 +2882,9 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
containerEl.createEl("h2", { text: "Settings for Self-hosted LiveSync." });
|
|
|
|
|
|
|
|
|
|
containerEl.createEl("h3", { text: "Remote Database configuration" });
|
|
|
|
|
let syncWarn = containerEl.createEl("div", { text: "The remote configuration is locked while any synchronization is enabled." });
|
|
|
|
|
syncWarn.addClass("op-warn");
|
|
|
|
|
syncWarn.addClass("sls-hidden");
|
|
|
|
|
|
|
|
|
|
const isAnySyncEnabled = (): boolean => {
|
|
|
|
|
if (this.plugin.settings.liveSync) return true;
|
|
|
|
|
@@ -2801,10 +2898,12 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
dbsettings.forEach((e) => {
|
|
|
|
|
e.setDisabled(true).setTooltip("When any sync is enabled, It cound't be changed.");
|
|
|
|
|
});
|
|
|
|
|
syncWarn.removeClass("sls-hidden");
|
|
|
|
|
} else {
|
|
|
|
|
dbsettings.forEach((e) => {
|
|
|
|
|
e.setDisabled(false).setTooltip("");
|
|
|
|
|
});
|
|
|
|
|
syncWarn.addClass("sls-hidden");
|
|
|
|
|
}
|
|
|
|
|
if (this.plugin.settings.liveSync) {
|
|
|
|
|
syncNonLive.forEach((e) => {
|
|
|
|
|
@@ -2996,6 +3095,7 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
.setButtonText("Apply and send")
|
|
|
|
|
.setWarning()
|
|
|
|
|
.setDisabled(false)
|
|
|
|
|
.setClass("sls-btn-left")
|
|
|
|
|
.onClick(async () => {
|
|
|
|
|
await applyEncryption(true);
|
|
|
|
|
})
|
|
|
|
|
@@ -3005,6 +3105,7 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
.setButtonText("Apply and receive")
|
|
|
|
|
.setWarning()
|
|
|
|
|
.setDisabled(false)
|
|
|
|
|
.setClass("sls-btn-right")
|
|
|
|
|
.onClick(async () => {
|
|
|
|
|
await applyEncryption(false);
|
|
|
|
|
})
|
|
|
|
|
@@ -3036,6 +3137,7 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
if (this.plugin.settings.versionUpFlash != "") {
|
|
|
|
|
let c = containerEl.createEl("div", { text: this.plugin.settings.versionUpFlash });
|
|
|
|
|
c.createEl("button", { text: "I got it and updated." }, (e) => {
|
|
|
|
|
e.addClass("mod-cta");
|
|
|
|
|
e.addEventListener("click", async () => {
|
|
|
|
|
this.plugin.settings.versionUpFlash = "";
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
@@ -3193,6 +3295,17 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
text.inputEl.setAttribute("type", "number");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
containerEl.createEl("h3", { text: "Miscellaneous" });
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Show status inside editor")
|
|
|
|
|
.setDesc("")
|
|
|
|
|
.addToggle((toggle) =>
|
|
|
|
|
toggle.setValue(this.plugin.settings.showStatusOnEditor).onChange(async (value) => {
|
|
|
|
|
this.plugin.settings.showStatusOnEditor = value;
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
containerEl.createEl("h3", { text: "Hatch" });
|
|
|
|
|
|
|
|
|
|
if (this.plugin.localDatabase.remoteLockedAndDeviceNotAccepted) {
|
|
|
|
|
@@ -3200,6 +3313,7 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
text: "To prevent unwanted vault corruption, the remote database has been locked for synchronization, and this device was not marked as 'resolved'. it caused by some operations like this. re-initialized. Local database initialization should be required. please back your vault up, reset local database, and press 'Mark this device as resolved'. ",
|
|
|
|
|
});
|
|
|
|
|
c.createEl("button", { text: "I'm ready, mark this device 'resolved'" }, (e) => {
|
|
|
|
|
e.addClass("mod-warning");
|
|
|
|
|
e.addEventListener("click", async () => {
|
|
|
|
|
await this.plugin.markRemoteResolved();
|
|
|
|
|
c.remove();
|
|
|
|
|
@@ -3212,6 +3326,7 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
text: "To prevent unwanted vault corruption, the remote database has been locked for synchronization. (This device is marked 'resolved') When all your devices are marked 'resolved', unlock the database.",
|
|
|
|
|
});
|
|
|
|
|
c.createEl("button", { text: "I'm ready, unlock the database" }, (e) => {
|
|
|
|
|
e.addClass("mod-warning");
|
|
|
|
|
e.addEventListener("click", async () => {
|
|
|
|
|
await this.plugin.markRemoteUnlocked();
|
|
|
|
|
c.remove();
|
|
|
|
|
@@ -3241,6 +3356,35 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
await this.plugin.replicate(true);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Reread all files")
|
|
|
|
|
.setDesc("Reread all files and update the database without dropping history")
|
|
|
|
|
.addButton((button) =>
|
|
|
|
|
button
|
|
|
|
|
.setButtonText("Reread")
|
|
|
|
|
.setDisabled(false)
|
|
|
|
|
.setWarning()
|
|
|
|
|
.onClick(async () => {
|
|
|
|
|
const files = this.app.vault.getFiles();
|
|
|
|
|
Logger("Reread all files started", LOG_LEVEL.NOTICE);
|
|
|
|
|
let notice = new Notice("", 0);
|
|
|
|
|
let i = 0;
|
|
|
|
|
for (const file of files) {
|
|
|
|
|
i++;
|
|
|
|
|
Logger(`Update into ${file.path}`);
|
|
|
|
|
notice.setMessage(`${i}/${files.length}\n${file.path}`);
|
|
|
|
|
try {
|
|
|
|
|
await this.plugin.updateIntoDB(file);
|
|
|
|
|
} catch (ex) {
|
|
|
|
|
Logger("could not update:");
|
|
|
|
|
Logger(ex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
notice.hide();
|
|
|
|
|
Logger("done", LOG_LEVEL.NOTICE);
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Drop History")
|
|
|
|
|
.setDesc("Initialize local and remote database, and send all or retrieve all again.")
|
|
|
|
|
@@ -3249,6 +3393,7 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
.setButtonText("Drop and send")
|
|
|
|
|
.setWarning()
|
|
|
|
|
.setDisabled(false)
|
|
|
|
|
.setClass("sls-btn-left")
|
|
|
|
|
.onClick(async () => {
|
|
|
|
|
await dropHistory(true);
|
|
|
|
|
})
|
|
|
|
|
@@ -3258,6 +3403,7 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
.setButtonText("Drop and receive")
|
|
|
|
|
.setWarning()
|
|
|
|
|
.setDisabled(false)
|
|
|
|
|
.setClass("sls-btn-right")
|
|
|
|
|
.onClick(async () => {
|
|
|
|
|
await dropHistory(false);
|
|
|
|
|
})
|
|
|
|
|
@@ -3270,6 +3416,7 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
button
|
|
|
|
|
.setButtonText("Lock")
|
|
|
|
|
.setDisabled(false)
|
|
|
|
|
.setWarning()
|
|
|
|
|
.onClick(async () => {
|
|
|
|
|
await this.plugin.markRemoteLocked();
|
|
|
|
|
})
|
|
|
|
|
@@ -3292,6 +3439,7 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
button
|
|
|
|
|
.setButtonText("Reset")
|
|
|
|
|
.setDisabled(false)
|
|
|
|
|
.setWarning()
|
|
|
|
|
.onClick(async () => {
|
|
|
|
|
await this.plugin.tryResetRemoteDatabase();
|
|
|
|
|
})
|
|
|
|
|
@@ -3303,6 +3451,7 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
button
|
|
|
|
|
.setButtonText("Reset")
|
|
|
|
|
.setDisabled(false)
|
|
|
|
|
.setWarning()
|
|
|
|
|
.onClick(async () => {
|
|
|
|
|
await this.plugin.resetLocalDatabase();
|
|
|
|
|
})
|
|
|
|
|
@@ -3335,6 +3484,17 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
// })
|
|
|
|
|
// );
|
|
|
|
|
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Show own plugins and settings")
|
|
|
|
|
.setDesc("Show ")
|
|
|
|
|
.addToggle((toggle) =>
|
|
|
|
|
toggle.setValue(this.plugin.settings.showOwnPlugins).onChange(async (value) => {
|
|
|
|
|
this.plugin.settings.showOwnPlugins = value;
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
updatePluginPane();
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Device and Vault name")
|
|
|
|
|
.setDesc("")
|
|
|
|
|
@@ -3363,7 +3523,6 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
const pl = this.plugin.app.plugins;
|
|
|
|
|
const manifests: PluginManifest[] = Object.values(pl.manifests);
|
|
|
|
|
console.dir(manifests);
|
|
|
|
|
for (let m of manifests) {
|
|
|
|
|
let path = normalizePath(m.dir) + "/";
|
|
|
|
|
const adapter = this.plugin.app.vault.adapter;
|
|
|
|
|
@@ -3372,19 +3531,16 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
for (let file of files) {
|
|
|
|
|
let thePath = path + file;
|
|
|
|
|
if (await adapter.exists(thePath)) {
|
|
|
|
|
// pluginData[file] = await arrayBufferToBase64(await adapter.readBinary(thePath));
|
|
|
|
|
pluginData[file] = await adapter.read(thePath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
console.dir(m.id);
|
|
|
|
|
console.dir(pluginData);
|
|
|
|
|
let mtime = 0;
|
|
|
|
|
if (await adapter.exists(path + "/data.json")) {
|
|
|
|
|
mtime = (await adapter.stat(path + "/data.json")).mtime;
|
|
|
|
|
}
|
|
|
|
|
let p: PluginDataEntry = {
|
|
|
|
|
_id: `ps:${this.plugin.settings.deviceAndVaultName}-${m.id}`,
|
|
|
|
|
dataJson: pluginData["data.json"] ? await encrypt(pluginData["data.json"], this.plugin.settings.passphrase) : undefined,
|
|
|
|
|
dataJson: pluginData["data.json"],
|
|
|
|
|
deviceVaultName: this.plugin.settings.deviceAndVaultName,
|
|
|
|
|
mainJs: pluginData["main.js"],
|
|
|
|
|
styleCss: pluginData["style.css"],
|
|
|
|
|
@@ -3393,55 +3549,67 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
mtime: mtime,
|
|
|
|
|
type: "plugin",
|
|
|
|
|
};
|
|
|
|
|
await db.put(p);
|
|
|
|
|
let d: LoadedEntry = {
|
|
|
|
|
_id: p._id,
|
|
|
|
|
data: JSON.stringify(p),
|
|
|
|
|
ctime: mtime,
|
|
|
|
|
mtime: mtime,
|
|
|
|
|
size: 0,
|
|
|
|
|
children: [],
|
|
|
|
|
datatype: "plain",
|
|
|
|
|
};
|
|
|
|
|
await this.plugin.localDatabase.putDBEntry(d);
|
|
|
|
|
}
|
|
|
|
|
await this.plugin.replicate(true);
|
|
|
|
|
updatePluginPane();
|
|
|
|
|
};
|
|
|
|
|
const updatePluginPane = async () => {
|
|
|
|
|
const db = this.plugin.localDatabase.localDatabase;
|
|
|
|
|
let oldDocs = await db.allDocs<PluginDataEntry>({ startkey: `ps:`, endkey: `ps;`, include_docs: true });
|
|
|
|
|
let docList = await db.allDocs<PluginDataEntry>({ startkey: `ps:`, endkey: `ps;`, include_docs: false });
|
|
|
|
|
let oldDocs: PluginDataEntry[] = ((await Promise.all(docList.rows.map(async (e) => await this.plugin.localDatabase.getDBEntry(e.id)))).filter((e) => e !== false) as LoadedEntry[]).map((e) => JSON.parse(e.data));
|
|
|
|
|
let plugins: { [key: string]: PluginDataEntry[] } = {};
|
|
|
|
|
let allPlugins: { [key: string]: PluginDataEntry } = {};
|
|
|
|
|
let thisDevicePlugins: { [key: string]: PluginDataEntry } = {};
|
|
|
|
|
for (let v of oldDocs.rows) {
|
|
|
|
|
if (typeof plugins[v.doc.deviceVaultName] === "undefined") {
|
|
|
|
|
plugins[v.doc.deviceVaultName] = [];
|
|
|
|
|
for (let v of oldDocs) {
|
|
|
|
|
if (typeof plugins[v.deviceVaultName] === "undefined") {
|
|
|
|
|
plugins[v.deviceVaultName] = [];
|
|
|
|
|
}
|
|
|
|
|
plugins[v.doc.deviceVaultName].push(v.doc);
|
|
|
|
|
allPlugins[v.doc._id] = v.doc;
|
|
|
|
|
if (v.doc.deviceVaultName == this.plugin.settings.deviceAndVaultName) {
|
|
|
|
|
thisDevicePlugins[v.doc.manifest.id] = v.doc;
|
|
|
|
|
plugins[v.deviceVaultName].push(v);
|
|
|
|
|
allPlugins[v._id] = v;
|
|
|
|
|
if (v.deviceVaultName == this.plugin.settings.deviceAndVaultName) {
|
|
|
|
|
thisDevicePlugins[v.manifest.id] = v;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let html = `
|
|
|
|
|
<table>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>vault</th>
|
|
|
|
|
<th>plugin</th>
|
|
|
|
|
<th>version</th>
|
|
|
|
|
<th>modified</th>
|
|
|
|
|
<th>plugin</th>
|
|
|
|
|
<th>setting</th>
|
|
|
|
|
</tr>`;
|
|
|
|
|
<div class='sls-plugins-wrap'>
|
|
|
|
|
<table class='sls-plugins-tbl'>
|
|
|
|
|
`;
|
|
|
|
|
for (let vaults in plugins) {
|
|
|
|
|
if (vaults == this.plugin.settings.deviceAndVaultName) continue;
|
|
|
|
|
if (!this.plugin.settings.showOwnPlugins && vaults == this.plugin.settings.deviceAndVaultName) continue;
|
|
|
|
|
html += `
|
|
|
|
|
<tr>
|
|
|
|
|
<th colspan=2>${escapeStringToHTML(vaults)}</th>
|
|
|
|
|
</tr>`;
|
|
|
|
|
for (let v of plugins[vaults]) {
|
|
|
|
|
let mtime = v.mtime == 0 ? "-" : new Date(v.mtime).toLocaleString();
|
|
|
|
|
let settingApplyable: boolean | string = "-";
|
|
|
|
|
let settingFleshness: string = "";
|
|
|
|
|
let isSameVersion = false;
|
|
|
|
|
let isSameContents = false;
|
|
|
|
|
if (thisDevicePlugins[v.manifest.id]) {
|
|
|
|
|
if (thisDevicePlugins[v.manifest.id].manifest.version == v.manifest.version) {
|
|
|
|
|
isSameVersion = true;
|
|
|
|
|
}
|
|
|
|
|
if (thisDevicePlugins[v.manifest.id].styleCss == v.styleCss && thisDevicePlugins[v.manifest.id].mainJs == v.mainJs && thisDevicePlugins[v.manifest.id].manifestJson == v.manifestJson) {
|
|
|
|
|
isSameContents = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (thisDevicePlugins[v.manifest.id] && thisDevicePlugins[v.manifest.id].dataJson && v.dataJson) {
|
|
|
|
|
// have this plugin.
|
|
|
|
|
let localSetting = await decrypt(thisDevicePlugins[v.manifest.id].dataJson, this.plugin.settings.passphrase);
|
|
|
|
|
let localSetting = thisDevicePlugins[v.manifest.id].dataJson;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
let remoteSetting = await decrypt(v.dataJson, this.plugin.settings.passphrase);
|
|
|
|
|
let remoteSetting = v.dataJson;
|
|
|
|
|
if (localSetting == remoteSetting) {
|
|
|
|
|
settingApplyable = "even";
|
|
|
|
|
} else {
|
|
|
|
|
@@ -3459,18 +3627,28 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
settingApplyable = "N/A";
|
|
|
|
|
}
|
|
|
|
|
// very ugly way.
|
|
|
|
|
let piece = `<tr>
|
|
|
|
|
<th>${escapeStringToHTML(v.deviceVaultName)}</th>
|
|
|
|
|
<td>${escapeStringToHTML(v.manifest.name)}</td>
|
|
|
|
|
<td class="tcenter">${escapeStringToHTML(v.manifest.version)}</td>
|
|
|
|
|
<td class="tcenter">${escapeStringToHTML(mtime)}</td>
|
|
|
|
|
<td class="tcenter">${isSameVersion ? "even" : "<button data-key='" + v._id + "' class='apply-plugin-version'>Use</button>"}</td>
|
|
|
|
|
<td class="tcenter">${settingApplyable === true ? "<button data-key='" + v._id + "' class='apply-plugin-data'>Apply (" + settingFleshness + ")</button>" : settingApplyable}</td>
|
|
|
|
|
</tr>`;
|
|
|
|
|
let piece = `
|
|
|
|
|
<tr class='divider'>
|
|
|
|
|
<th colspan=2></th>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<th class='sls-table-head'>${escapeStringToHTML(v.manifest.name)}</th>
|
|
|
|
|
<td class="sls-table-tail tcenter">${isSameContents ? "even" : `<button data-key='${v._id}' class='apply-plugin-version mod-cta'>Use (${isSameVersion ? "=" : ""}${v.manifest.version}) </button>`}</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td class="sls-table-head tcenter">${escapeStringToHTML(mtime)}</td>
|
|
|
|
|
<td class="sls-table-tail tcenter">${settingApplyable === true ? "<button data-key='" + v._id + "' class='apply-plugin-data mod-cta'>Apply (" + settingFleshness + ")</button>" : settingApplyable}</td>
|
|
|
|
|
</tr>
|
|
|
|
|
`;
|
|
|
|
|
html += piece;
|
|
|
|
|
}
|
|
|
|
|
html += `
|
|
|
|
|
<tr class='divider'>
|
|
|
|
|
<th colspan=2></th>
|
|
|
|
|
</tr>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
html += "</table>";
|
|
|
|
|
html += "</table></div>";
|
|
|
|
|
pluginConfig.innerHTML = html;
|
|
|
|
|
pluginConfig.querySelectorAll(".apply-plugin-data").forEach((e) =>
|
|
|
|
|
e.addEventListener("click", async (evt) => {
|
|
|
|
|
@@ -3485,7 +3663,7 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
await this.plugin.app.plugins.unloadPlugin(plugin.manifest.id);
|
|
|
|
|
Logger(`Unload plugin:${plugin.manifest.id}`, LOG_LEVEL.NOTICE);
|
|
|
|
|
}
|
|
|
|
|
if (plugin.dataJson) await adapter.write(pluginTargetFolderPath + "data.json", await decrypt(plugin.dataJson, this.plugin.settings.passphrase));
|
|
|
|
|
if (plugin.dataJson) await adapter.write(pluginTargetFolderPath + "data.json", plugin.dataJson);
|
|
|
|
|
Logger("wrote:" + pluginTargetFolderPath + "data.json", LOG_LEVEL.NOTICE);
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
if (stat) {
|
|
|
|
|
@@ -3518,7 +3696,7 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
await adapter.write(pluginTargetFolderPath + "main.js", plugin.mainJs);
|
|
|
|
|
await adapter.write(pluginTargetFolderPath + "manifest.json", plugin.manifestJson);
|
|
|
|
|
if (plugin.styleCss) await adapter.write(pluginTargetFolderPath + "styles.css", plugin.styleCss);
|
|
|
|
|
if (plugin.dataJson) await adapter.write(pluginTargetFolderPath + "data.json", await decrypt(plugin.dataJson, this.plugin.settings.passphrase));
|
|
|
|
|
if (plugin.dataJson) await adapter.write(pluginTargetFolderPath + "data.json", plugin.dataJson);
|
|
|
|
|
if (stat) {
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
await this.plugin.app.plugins.loadPlugin(plugin.manifest.id);
|
|
|
|
|
@@ -3550,6 +3728,14 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
.setButtonText("Save plugins")
|
|
|
|
|
.setDisabled(false)
|
|
|
|
|
.onClick(async () => {
|
|
|
|
|
if (!this.plugin.settings.encrypt) {
|
|
|
|
|
Logger("You have to encrypt the database to use plugin setting sync.", LOG_LEVEL.NOTICE);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!this.plugin.settings.deviceAndVaultName) {
|
|
|
|
|
Logger("You have to set your device and vault name.", LOG_LEVEL.NOTICE);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
await sweepPlugin();
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
@@ -3567,6 +3753,19 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|
|
|
|
xx.remove();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
ba.addClass("mod-warning");
|
|
|
|
|
xx.createEl("button", { text: `Restore from file` }, (e) => {
|
|
|
|
|
e.addEventListener("click", async () => {
|
|
|
|
|
let f = await this.app.vault.getFiles().filter((e) => path2id(e.path) == k);
|
|
|
|
|
if (f.length == 0) {
|
|
|
|
|
Logger("Not found in vault", LOG_LEVEL.NOTICE);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
await this.plugin.updateIntoDB(f[0]);
|
|
|
|
|
xx.remove();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
xx.addClass("mod-warning");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
let cx = containerEl.createEl("div", { text: "There's no collupted data." });
|
|
|
|
|
|