mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-05-09 01:01:51 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15b580aa9a | ||
|
|
ebcb059d99 | ||
|
|
5bb8b2567b |
@@ -75,6 +75,8 @@ Synchronization status is shown in statusbar.
|
|||||||
- ⚠ Error occurred.
|
- ⚠ Error occurred.
|
||||||
- ↑ Uploaded pieces
|
- ↑ Uploaded pieces
|
||||||
- ↓ Downloaded pieces
|
- ↓ Downloaded pieces
|
||||||
|
- ⏳ Count of the pending process
|
||||||
|
If you have deleted or renamed files, please wait until this disappears.
|
||||||
|
|
||||||
# More supplements
|
# More supplements
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ Self-hosted LiveSync用にWebClipperも作りました。Chrome Web Storeから
|
|||||||
- ⚠ エラーが発生しています
|
- ⚠ エラーが発生しています
|
||||||
- ↑ 送信したデータ数
|
- ↑ 送信したデータ数
|
||||||
- ↓ 受信したデータ数
|
- ↓ 受信したデータ数
|
||||||
|
- ⏳ 保留している処理の数です
|
||||||
|
ファイルを削除したりリネームした場合、この表示が消えるまでお待ちください。
|
||||||
|
|
||||||
# さらなる補足
|
# さらなる補足
|
||||||
- ファイルは同期された後、タイムスタンプを比較して新しければいったん新しい方で上書きされます。その後、衝突が発生したかによって、マージが行われます。
|
- ファイルは同期された後、タイムスタンプを比較して新しければいったん新しい方で上書きされます。その後、衝突が発生したかによって、マージが行われます。
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-livesync",
|
"id": "obsidian-livesync",
|
||||||
"name": "Self-hosted LiveSync",
|
"name": "Self-hosted LiveSync",
|
||||||
"version": "0.4.0",
|
"version": "0.6.0",
|
||||||
"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.4.0",
|
"version": "0.6.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.4.0",
|
"version": "0.6.0",
|
||||||
"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.4.0",
|
"version": "0.6.0",
|
||||||
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
"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": {
|
||||||
|
|||||||
132
src/DocumentHistoryModal.ts
Normal file
132
src/DocumentHistoryModal.ts
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
import { TFile, Modal, App } from "obsidian";
|
||||||
|
import { path2id, escapeStringToHTML } from "./utils";
|
||||||
|
import ObsidianLiveSyncPlugin from "./main";
|
||||||
|
import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch";
|
||||||
|
|
||||||
|
export class DocumentHistoryModal extends Modal {
|
||||||
|
plugin: ObsidianLiveSyncPlugin;
|
||||||
|
range: HTMLInputElement;
|
||||||
|
contentView: HTMLDivElement;
|
||||||
|
info: HTMLDivElement;
|
||||||
|
fileInfo: HTMLDivElement;
|
||||||
|
showDiff = false;
|
||||||
|
|
||||||
|
file: string;
|
||||||
|
|
||||||
|
revs_info: PouchDB.Core.RevisionInfo[] = [];
|
||||||
|
|
||||||
|
constructor(app: App, plugin: ObsidianLiveSyncPlugin, file: TFile) {
|
||||||
|
super(app);
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.file = file.path;
|
||||||
|
if (localStorage.getItem("ols-history-highlightdiff") == "1") {
|
||||||
|
this.showDiff = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async loadFile() {
|
||||||
|
const db = this.plugin.localDatabase;
|
||||||
|
const w = await db.localDatabase.get(path2id(this.file), { revs_info: true });
|
||||||
|
this.revs_info = w._revs_info.filter((e) => e.status == "available");
|
||||||
|
this.range.max = `${this.revs_info.length - 1}`;
|
||||||
|
this.range.value = this.range.max;
|
||||||
|
this.fileInfo.setText(`${this.file} / ${this.revs_info.length} revisions`);
|
||||||
|
await this.loadRevs();
|
||||||
|
}
|
||||||
|
async loadRevs() {
|
||||||
|
const db = this.plugin.localDatabase;
|
||||||
|
const index = this.revs_info.length - 1 - (this.range.value as any) / 1;
|
||||||
|
const rev = this.revs_info[index];
|
||||||
|
const w = await db.getDBEntry(path2id(this.file), { rev: rev.rev }, false, false);
|
||||||
|
|
||||||
|
if (w === false) {
|
||||||
|
this.info.innerHTML = "";
|
||||||
|
this.contentView.innerHTML = `Could not read this revision<br>(${rev.rev})`;
|
||||||
|
} else {
|
||||||
|
this.info.innerHTML = `Modified:${new Date(w.mtime).toLocaleString()}`;
|
||||||
|
let result = "";
|
||||||
|
if (this.showDiff) {
|
||||||
|
const prevRevIdx = this.revs_info.length - 1 - ((this.range.value as any) / 1 - 1);
|
||||||
|
if (prevRevIdx >= 0 && prevRevIdx < this.revs_info.length) {
|
||||||
|
const oldRev = this.revs_info[prevRevIdx].rev;
|
||||||
|
const w2 = await db.getDBEntry(path2id(this.file), { rev: oldRev }, false, false);
|
||||||
|
if (w2 != false) {
|
||||||
|
const dmp = new diff_match_patch();
|
||||||
|
const diff = dmp.diff_main(w2.data, w.data);
|
||||||
|
dmp.diff_cleanupSemantic(diff);
|
||||||
|
for (const v of diff) {
|
||||||
|
const x1 = v[0];
|
||||||
|
const x2 = v[1];
|
||||||
|
if (x1 == DIFF_DELETE) {
|
||||||
|
result += "<span class='history-deleted'>" + escapeStringToHTML(x2) + "</span>";
|
||||||
|
} else if (x1 == DIFF_EQUAL) {
|
||||||
|
result += "<span class='history-normal'>" + escapeStringToHTML(x2) + "</span>";
|
||||||
|
} else if (x1 == DIFF_INSERT) {
|
||||||
|
result += "<span class='history-added'>" + escapeStringToHTML(x2) + "</span>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = result.replace(/\n/g, "<br>");
|
||||||
|
} else {
|
||||||
|
result = escapeStringToHTML(w.data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = escapeStringToHTML(w.data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = escapeStringToHTML(w.data);
|
||||||
|
}
|
||||||
|
this.contentView.innerHTML = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpen() {
|
||||||
|
const { contentEl } = this;
|
||||||
|
|
||||||
|
contentEl.empty();
|
||||||
|
contentEl.createEl("h2", { text: "Document History" });
|
||||||
|
this.fileInfo = contentEl.createDiv("");
|
||||||
|
this.fileInfo.addClass("op-info");
|
||||||
|
const divView = contentEl.createDiv("");
|
||||||
|
divView.addClass("op-flex");
|
||||||
|
|
||||||
|
divView.createEl("input", { type: "range" }, (e) => {
|
||||||
|
this.range = e;
|
||||||
|
e.addEventListener("change", (e) => {
|
||||||
|
this.loadRevs();
|
||||||
|
});
|
||||||
|
e.addEventListener("input", (e) => {
|
||||||
|
this.loadRevs();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
contentEl
|
||||||
|
.createDiv("", (e) => {
|
||||||
|
e.createEl("label", {}, (label) => {
|
||||||
|
label.appendChild(
|
||||||
|
createEl("input", { type: "checkbox" }, (checkbox) => {
|
||||||
|
if (this.showDiff) {
|
||||||
|
checkbox.checked = true;
|
||||||
|
}
|
||||||
|
checkbox.addEventListener("input", (evt: any) => {
|
||||||
|
this.showDiff = checkbox.checked;
|
||||||
|
localStorage.setItem("ols-history-highlightdiff", this.showDiff == true ? "1" : "");
|
||||||
|
this.loadRevs();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
label.appendText("Highlight diff");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.addClass("op-info");
|
||||||
|
this.info = contentEl.createDiv("");
|
||||||
|
this.info.addClass("op-info");
|
||||||
|
this.loadFile();
|
||||||
|
const div = contentEl.createDiv({ text: "Loading old revisions..." });
|
||||||
|
this.contentView = div;
|
||||||
|
div.addClass("op-scrollable");
|
||||||
|
div.addClass("op-pre");
|
||||||
|
}
|
||||||
|
onClose() {
|
||||||
|
const { contentEl } = this;
|
||||||
|
contentEl.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
} from "./types";
|
} from "./types";
|
||||||
import { resolveWithIgnoreKnownError, delay, path2id, runWithLock } from "./utils";
|
import { resolveWithIgnoreKnownError, delay, path2id, runWithLock } from "./utils";
|
||||||
import { Logger } from "./logger";
|
import { Logger } from "./logger";
|
||||||
import { checkRemoteVersion, connectRemoteCouchDB } from "./utils_couchdb";
|
import { checkRemoteVersion, connectRemoteCouchDB, getLastPostFailedBySize } from "./utils_couchdb";
|
||||||
import { decrypt, encrypt } from "./e2ee";
|
import { decrypt, encrypt } from "./e2ee";
|
||||||
|
|
||||||
export class LocalPouchDB {
|
export class LocalPouchDB {
|
||||||
@@ -121,7 +121,7 @@ export class LocalPouchDB {
|
|||||||
this.changeHandler = this.cancelHandler(this.changeHandler);
|
this.changeHandler = this.cancelHandler(this.changeHandler);
|
||||||
this.localDatabase = null;
|
this.localDatabase = null;
|
||||||
this.localDatabase = new PouchDB<EntryDoc>(this.dbname + "-livesync", {
|
this.localDatabase = new PouchDB<EntryDoc>(this.dbname + "-livesync", {
|
||||||
auto_compaction: true,
|
auto_compaction: this.settings.useHistory ? false : true,
|
||||||
revs_limit: 100,
|
revs_limit: 100,
|
||||||
deterministic_revs: true,
|
deterministic_revs: true,
|
||||||
});
|
});
|
||||||
@@ -763,6 +763,12 @@ export class LocalPouchDB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const syncOptionBase: PouchDB.Replication.SyncOptions = {
|
const syncOptionBase: PouchDB.Replication.SyncOptions = {
|
||||||
|
pull: {
|
||||||
|
checkpoint: "target",
|
||||||
|
},
|
||||||
|
push: {
|
||||||
|
checkpoint: "source",
|
||||||
|
},
|
||||||
batches_limit: setting.batches_limit,
|
batches_limit: setting.batches_limit,
|
||||||
batch_size: setting.batch_size,
|
batch_size: setting.batch_size,
|
||||||
};
|
};
|
||||||
@@ -770,7 +776,7 @@ export class LocalPouchDB {
|
|||||||
const db = dbret.db;
|
const db = dbret.db;
|
||||||
const totalCount = (await this.localDatabase.info()).doc_count;
|
const totalCount = (await this.localDatabase.info()).doc_count;
|
||||||
//replicate once
|
//replicate once
|
||||||
const replicate = this.localDatabase.replicate.to(db, syncOptionBase);
|
const replicate = this.localDatabase.replicate.to(db, { checkpoint: "source", ...syncOptionBase });
|
||||||
replicate
|
replicate
|
||||||
.on("active", () => {
|
.on("active", () => {
|
||||||
this.syncStatus = "CONNECTED";
|
this.syncStatus = "CONNECTED";
|
||||||
@@ -806,7 +812,7 @@ export class LocalPouchDB {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkReplicationConnectivity(setting: ObsidianLiveSyncSettings, keepAlive: boolean) {
|
async checkReplicationConnectivity(setting: ObsidianLiveSyncSettings, keepAlive: boolean, skipCheck: boolean) {
|
||||||
if (!this.isReady) {
|
if (!this.isReady) {
|
||||||
Logger("Database is not ready.");
|
Logger("Database is not ready.");
|
||||||
return false;
|
return false;
|
||||||
@@ -832,40 +838,41 @@ export class LocalPouchDB {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await checkRemoteVersion(dbret.db, this.migrate.bind(this), VER))) {
|
if (!skipCheck) {
|
||||||
Logger("Remote database is newer or corrupted, make sure to latest version of self-hosted-livesync installed", LOG_LEVEL.NOTICE);
|
if (!(await checkRemoteVersion(dbret.db, this.migrate.bind(this), VER))) {
|
||||||
return false;
|
Logger("Remote database is newer or corrupted, make sure to latest version of self-hosted-livesync installed", LOG_LEVEL.NOTICE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defMilestonePoint: EntryMilestoneInfo = {
|
||||||
|
_id: MILSTONE_DOCID,
|
||||||
|
type: "milestoneinfo",
|
||||||
|
created: (new Date() as any) / 1,
|
||||||
|
locked: false,
|
||||||
|
accepted_nodes: [this.nodeid],
|
||||||
|
};
|
||||||
|
// const remoteInfo = dbret.info;
|
||||||
|
// const localInfo = await this.localDatabase.info();
|
||||||
|
// const remoteDocsCount = remoteInfo.doc_count;
|
||||||
|
// const localDocsCount = localInfo.doc_count;
|
||||||
|
// const remoteUpdSeq = typeof remoteInfo.update_seq == "string" ? Number(remoteInfo.update_seq.split("-")[0]) : remoteInfo.update_seq;
|
||||||
|
// const localUpdSeq = typeof localInfo.update_seq == "string" ? Number(localInfo.update_seq.split("-")[0]) : localInfo.update_seq;
|
||||||
|
|
||||||
|
// Logger(`Database diffences: remote:${remoteDocsCount} docs / last update ${remoteUpdSeq}`);
|
||||||
|
// Logger(`Database diffences: local :${localDocsCount} docs / last update ${localUpdSeq}`);
|
||||||
|
|
||||||
|
const remoteMilestone: EntryMilestoneInfo = await resolveWithIgnoreKnownError(dbret.db.get(MILSTONE_DOCID), defMilestonePoint);
|
||||||
|
this.remoteLocked = remoteMilestone.locked;
|
||||||
|
this.remoteLockedAndDeviceNotAccepted = remoteMilestone.locked && remoteMilestone.accepted_nodes.indexOf(this.nodeid) == -1;
|
||||||
|
|
||||||
|
if (remoteMilestone.locked && remoteMilestone.accepted_nodes.indexOf(this.nodeid) == -1) {
|
||||||
|
Logger("Remote database marked as 'Auto Sync Locked'. And this devide does not marked as resolved device. see settings dialog.", LOG_LEVEL.NOTICE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (typeof remoteMilestone._rev == "undefined") {
|
||||||
|
await dbret.db.put(remoteMilestone);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const defMilestonePoint: EntryMilestoneInfo = {
|
|
||||||
_id: MILSTONE_DOCID,
|
|
||||||
type: "milestoneinfo",
|
|
||||||
created: (new Date() as any) / 1,
|
|
||||||
locked: false,
|
|
||||||
accepted_nodes: [this.nodeid],
|
|
||||||
};
|
|
||||||
// const remoteInfo = dbret.info;
|
|
||||||
// const localInfo = await this.localDatabase.info();
|
|
||||||
// const remoteDocsCount = remoteInfo.doc_count;
|
|
||||||
// const localDocsCount = localInfo.doc_count;
|
|
||||||
// const remoteUpdSeq = typeof remoteInfo.update_seq == "string" ? Number(remoteInfo.update_seq.split("-")[0]) : remoteInfo.update_seq;
|
|
||||||
// const localUpdSeq = typeof localInfo.update_seq == "string" ? Number(localInfo.update_seq.split("-")[0]) : localInfo.update_seq;
|
|
||||||
|
|
||||||
// Logger(`Database diffences: remote:${remoteDocsCount} docs / last update ${remoteUpdSeq}`);
|
|
||||||
// Logger(`Database diffences: local :${localDocsCount} docs / last update ${localUpdSeq}`);
|
|
||||||
|
|
||||||
const remoteMilestone: EntryMilestoneInfo = await resolveWithIgnoreKnownError(dbret.db.get(MILSTONE_DOCID), defMilestonePoint);
|
|
||||||
this.remoteLocked = remoteMilestone.locked;
|
|
||||||
this.remoteLockedAndDeviceNotAccepted = remoteMilestone.locked && remoteMilestone.accepted_nodes.indexOf(this.nodeid) == -1;
|
|
||||||
|
|
||||||
if (remoteMilestone.locked && remoteMilestone.accepted_nodes.indexOf(this.nodeid) == -1) {
|
|
||||||
Logger("Remote database marked as 'Auto Sync Locked'. And this devide does not marked as resolved device. see settings dialog.", LOG_LEVEL.NOTICE);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (typeof remoteMilestone._rev == "undefined") {
|
|
||||||
await dbret.db.put(remoteMilestone);
|
|
||||||
}
|
|
||||||
|
|
||||||
const syncOptionBase: PouchDB.Replication.SyncOptions = {
|
const syncOptionBase: PouchDB.Replication.SyncOptions = {
|
||||||
batches_limit: setting.batches_limit,
|
batches_limit: setting.batches_limit,
|
||||||
batch_size: setting.batch_size,
|
batch_size: setting.batch_size,
|
||||||
@@ -877,33 +884,49 @@ export class LocalPouchDB {
|
|||||||
|
|
||||||
async openReplication(setting: ObsidianLiveSyncSettings, keepAlive: boolean, showResult: boolean, callback: (e: PouchDB.Core.ExistingDocument<EntryDoc>[]) => Promise<void>): Promise<boolean> {
|
async openReplication(setting: ObsidianLiveSyncSettings, keepAlive: boolean, showResult: boolean, callback: (e: PouchDB.Core.ExistingDocument<EntryDoc>[]) => Promise<void>): Promise<boolean> {
|
||||||
return await runWithLock("replicate", false, () => {
|
return await runWithLock("replicate", false, () => {
|
||||||
return this._openReplication(setting, keepAlive, showResult, callback);
|
return this._openReplication(setting, keepAlive, showResult, callback, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async _openReplication(setting: ObsidianLiveSyncSettings, keepAlive: boolean, showResult: boolean, callback: (e: PouchDB.Core.ExistingDocument<EntryDoc>[]) => Promise<void>): Promise<boolean> {
|
originalSetting: ObsidianLiveSyncSettings = null;
|
||||||
const ret = await this.checkReplicationConnectivity(setting, keepAlive);
|
// last_seq: number = 200;
|
||||||
|
async _openReplication(setting: ObsidianLiveSyncSettings, keepAlive: boolean, showResult: boolean, callback: (e: PouchDB.Core.ExistingDocument<EntryDoc>[]) => Promise<void>, retrying: boolean): Promise<boolean> {
|
||||||
|
const ret = await this.checkReplicationConnectivity(setting, keepAlive, retrying);
|
||||||
if (ret === false) return false;
|
if (ret === false) return false;
|
||||||
let notice: Notice = null;
|
let notice: Notice = null;
|
||||||
if (showResult) {
|
if (showResult) {
|
||||||
notice = new Notice("Replicating", 0);
|
notice = new Notice("Looking for the point last synchronized point.", 0);
|
||||||
}
|
}
|
||||||
const { db, syncOptionBase, syncOption } = ret;
|
const { db, syncOptionBase, syncOption } = ret;
|
||||||
//replicate once
|
//replicate once
|
||||||
this.syncStatus = "STARTED";
|
this.syncStatus = "STARTED";
|
||||||
|
this.updateInfo();
|
||||||
|
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
const docArrivedOnStart = this.docArrived;
|
const docArrivedOnStart = this.docArrived;
|
||||||
const docSentOnStart = this.docSent;
|
const docSentOnStart = this.docSent;
|
||||||
|
|
||||||
const _openReplicationSync = () => {
|
const _openReplicationSync = () => {
|
||||||
Logger("Sync Main Started");
|
Logger("Sync Main Started");
|
||||||
|
if (!retrying) {
|
||||||
|
this.originalSetting = setting;
|
||||||
|
}
|
||||||
this.syncHandler = this.cancelHandler(this.syncHandler);
|
this.syncHandler = this.cancelHandler(this.syncHandler);
|
||||||
this.syncHandler = this.localDatabase.sync<EntryDoc>(db, syncOption);
|
this.syncHandler = this.localDatabase.sync<EntryDoc>(db, {
|
||||||
|
...syncOption,
|
||||||
|
pull: {
|
||||||
|
checkpoint: "target",
|
||||||
|
},
|
||||||
|
push: {
|
||||||
|
checkpoint: "source",
|
||||||
|
},
|
||||||
|
});
|
||||||
this.syncHandler
|
this.syncHandler
|
||||||
.on("active", () => {
|
.on("active", () => {
|
||||||
this.syncStatus = "CONNECTED";
|
this.syncStatus = "CONNECTED";
|
||||||
this.updateInfo();
|
this.updateInfo();
|
||||||
Logger("Replication activated");
|
Logger("Replication activated");
|
||||||
|
if (notice != null) notice.setMessage(`Activated..`);
|
||||||
})
|
})
|
||||||
.on("change", async (e) => {
|
.on("change", async (e) => {
|
||||||
try {
|
try {
|
||||||
@@ -924,6 +947,16 @@ export class LocalPouchDB {
|
|||||||
Logger("Replication callback error");
|
Logger("Replication callback error");
|
||||||
Logger(ex);
|
Logger(ex);
|
||||||
}
|
}
|
||||||
|
// re-connect to retry with original setting
|
||||||
|
if (retrying) {
|
||||||
|
if (this.docSent - docSentOnStart + (this.docArrived - docArrivedOnStart) > this.originalSetting.batch_size * 2) {
|
||||||
|
// restore sync values
|
||||||
|
Logger("Back into original settings once.");
|
||||||
|
if (notice != null) notice.hide();
|
||||||
|
this.syncHandler = this.cancelHandler(this.syncHandler);
|
||||||
|
this._openReplication(this.originalSetting, keepAlive, showResult, callback, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.on("complete", (e) => {
|
.on("complete", (e) => {
|
||||||
this.syncStatus = "COMPLETED";
|
this.syncStatus = "COMPLETED";
|
||||||
@@ -948,8 +981,25 @@ export class LocalPouchDB {
|
|||||||
this.syncHandler = this.cancelHandler(this.syncHandler);
|
this.syncHandler = this.cancelHandler(this.syncHandler);
|
||||||
this.updateInfo();
|
this.updateInfo();
|
||||||
if (notice != null) notice.hide();
|
if (notice != null) notice.hide();
|
||||||
Logger("Replication error", LOG_LEVEL.NOTICE);
|
if (getLastPostFailedBySize()) {
|
||||||
Logger(e);
|
if (keepAlive) {
|
||||||
|
Logger("Replication stopped.", LOG_LEVEL.NOTICE);
|
||||||
|
} else {
|
||||||
|
// Duplicate settings for smaller batch.
|
||||||
|
const xsetting: ObsidianLiveSyncSettings = JSON.parse(JSON.stringify(setting));
|
||||||
|
xsetting.batch_size = Math.ceil(xsetting.batch_size / 2);
|
||||||
|
xsetting.batches_limit = Math.ceil(xsetting.batches_limit / 2);
|
||||||
|
if (xsetting.batch_size <= 3 || xsetting.batches_limit <= 3) {
|
||||||
|
Logger("We can't replicate more lower value.", showResult ? LOG_LEVEL.NOTICE : LOG_LEVEL.INFO);
|
||||||
|
} else {
|
||||||
|
Logger(`Retry with lower batch size:${xsetting.batch_size}/${xsetting.batches_limit}`, showResult ? LOG_LEVEL.NOTICE : LOG_LEVEL.INFO);
|
||||||
|
this._openReplication(xsetting, keepAlive, showResult, callback, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger("Replication error", LOG_LEVEL.NOTICE);
|
||||||
|
Logger(e);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.on("paused", (e) => {
|
.on("paused", (e) => {
|
||||||
this.syncStatus = "PAUSED";
|
this.syncStatus = "PAUSED";
|
||||||
@@ -974,7 +1024,7 @@ export class LocalPouchDB {
|
|||||||
Logger(await db.info(), LOG_LEVEL.VERBOSE);
|
Logger(await db.info(), LOG_LEVEL.VERBOSE);
|
||||||
let replicate: PouchDB.Replication.Replication<EntryDoc>;
|
let replicate: PouchDB.Replication.Replication<EntryDoc>;
|
||||||
try {
|
try {
|
||||||
replicate = this.localDatabase.replicate.from(db, syncOptionBase);
|
replicate = this.localDatabase.replicate.from(db, { checkpoint: "target", ...syncOptionBase });
|
||||||
replicate
|
replicate
|
||||||
.on("active", () => {
|
.on("active", () => {
|
||||||
this.syncStatus = "CONNECTED";
|
this.syncStatus = "CONNECTED";
|
||||||
@@ -1154,7 +1204,13 @@ export class LocalPouchDB {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async garbageCollect() {
|
async garbageCollect() {
|
||||||
|
// if (this.settings.useHistory) {
|
||||||
|
// Logger("GC skipped for using history", LOG_LEVEL.VERBOSE);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// NOTE:Garbage collection could break old revisions.
|
||||||
await runWithLock("replicate", true, async () => {
|
await runWithLock("replicate", true, async () => {
|
||||||
if (this.gcRunning) return;
|
if (this.gcRunning) return;
|
||||||
this.gcRunning = true;
|
this.gcRunning = true;
|
||||||
@@ -1168,29 +1224,36 @@ export class LocalPouchDB {
|
|||||||
let usedPieces: string[] = [];
|
let usedPieces: string[] = [];
|
||||||
Logger("Collecting Garbage");
|
Logger("Collecting Garbage");
|
||||||
do {
|
do {
|
||||||
const result = await this.localDatabase.allDocs({ include_docs: true, skip: c, limit: 500, conflicts: true });
|
const result = await this.localDatabase.allDocs({ include_docs: false, skip: c, limit: 2000, conflicts: true });
|
||||||
readCount = result.rows.length;
|
readCount = result.rows.length;
|
||||||
Logger("checked:" + readCount);
|
Logger("checked:" + readCount);
|
||||||
if (readCount > 0) {
|
if (readCount > 0) {
|
||||||
//there are some result
|
//there are some result
|
||||||
for (const v of result.rows) {
|
for (const v of result.rows) {
|
||||||
const doc = v.doc;
|
if (v.id.startsWith("h:")) {
|
||||||
if (doc.type == "newnote" || doc.type == "plain") {
|
hashPieces = Array.from(new Set([...hashPieces, v.id]));
|
||||||
// used pieces memo.
|
} else {
|
||||||
usedPieces = Array.from(new Set([...usedPieces, ...doc.children]));
|
const docT = await this.localDatabase.get(v.id, { revs_info: true });
|
||||||
if (doc._conflicts) {
|
const revs = docT._revs_info;
|
||||||
for (const cid of doc._conflicts) {
|
// console.log(`revs:${revs.length}`)
|
||||||
const p = await this.localDatabase.get<EntryDoc>(doc._id, { rev: cid });
|
for (const rev of revs) {
|
||||||
if (p.type == "newnote" || p.type == "plain") {
|
if (rev.status != "available") continue;
|
||||||
usedPieces = Array.from(new Set([...usedPieces, ...p.children]));
|
// console.log(`id:${docT._id},rev:${rev.rev}`);
|
||||||
|
const doc = await this.localDatabase.get(v.id, { rev: rev.rev });
|
||||||
|
if ("children" in doc) {
|
||||||
|
// used pieces memo.
|
||||||
|
usedPieces = Array.from(new Set([...usedPieces, ...doc.children]));
|
||||||
|
if (doc._conflicts) {
|
||||||
|
for (const cid of doc._conflicts) {
|
||||||
|
const 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]));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c += readCount;
|
c += readCount;
|
||||||
|
|||||||
@@ -602,6 +602,15 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
new Setting(containerMiscellaneousEl)
|
||||||
|
.setName("Use history (beta)")
|
||||||
|
.setDesc("Use history dialog (Restart required, auto compaction would be disabled, and more storage will be consumed)")
|
||||||
|
.addToggle((toggle) =>
|
||||||
|
toggle.setValue(this.plugin.settings.useHistory).onChange(async (value) => {
|
||||||
|
this.plugin.settings.useHistory = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
})
|
||||||
|
);
|
||||||
addScreenElement("40", containerMiscellaneousEl);
|
addScreenElement("40", containerMiscellaneousEl);
|
||||||
|
|
||||||
const containerHatchEl = containerEl.createDiv();
|
const containerHatchEl = containerEl.createDiv();
|
||||||
@@ -811,7 +820,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
// With great respect, thank you TfTHacker!
|
// With great respect, thank you TfTHacker!
|
||||||
// refered: https://github.com/TfTHacker/obsidian42-brat/blob/main/src/features/BetaPlugins.ts
|
// refered: https://github.com/TfTHacker/obsidian42-brat/blob/main/src/features/BetaPlugins.ts
|
||||||
const containerPluginSettings = containerEl.createDiv();
|
const containerPluginSettings = containerEl.createDiv();
|
||||||
containerPluginSettings.createEl("h3", { text: "Plugins and settings (bleeding edge)" });
|
containerPluginSettings.createEl("h3", { text: "Plugins and settings (beta)" });
|
||||||
|
|
||||||
const updateDisabledOfDeviceAndVaultName = () => {
|
const updateDisabledOfDeviceAndVaultName = () => {
|
||||||
vaultName.setDisabled(this.plugin.settings.autoSweepPlugins || this.plugin.settings.autoSweepPluginsPeriodic);
|
vaultName.setDisabled(this.plugin.settings.autoSweepPlugins || this.plugin.settings.autoSweepPluginsPeriodic);
|
||||||
|
|||||||
25
src/main.ts
25
src/main.ts
@@ -18,12 +18,13 @@ import {
|
|||||||
diff_result,
|
diff_result,
|
||||||
FLAGMD_REDFLAG,
|
FLAGMD_REDFLAG,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { base64ToString, arrayBufferToBase64, base64ToArrayBuffer, isValidPath, versionNumberString2Number, id2path, path2id, runWithLock, shouldBeIgnored } from "./utils";
|
import { base64ToString, arrayBufferToBase64, base64ToArrayBuffer, isValidPath, versionNumberString2Number, id2path, path2id, runWithLock, shouldBeIgnored, getProcessingCounts, setLockNotifier } from "./utils";
|
||||||
import { Logger, setLogger } from "./logger";
|
import { Logger, setLogger } from "./logger";
|
||||||
import { LocalPouchDB } from "./LocalPouchDB";
|
import { LocalPouchDB } from "./LocalPouchDB";
|
||||||
import { LogDisplayModal } from "./LogDisplayModal";
|
import { LogDisplayModal } from "./LogDisplayModal";
|
||||||
import { ConflictResolveModal } from "./ConflictResolveModal";
|
import { ConflictResolveModal } from "./ConflictResolveModal";
|
||||||
import { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab";
|
import { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab";
|
||||||
|
import { DocumentHistoryModal } from "./DocumentHistoryModal";
|
||||||
|
|
||||||
export default class ObsidianLiveSyncPlugin extends Plugin {
|
export default class ObsidianLiveSyncPlugin extends Plugin {
|
||||||
settings: ObsidianLiveSyncSettings;
|
settings: ObsidianLiveSyncSettings;
|
||||||
@@ -45,6 +46,13 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
showHistory(file: TFile) {
|
||||||
|
if (!this.settings.useHistory) {
|
||||||
|
Logger("You have to enable Use history in misc.", LOG_LEVEL.NOTICE);
|
||||||
|
} else {
|
||||||
|
new DocumentHistoryModal(this.app, this, file).open();
|
||||||
|
}
|
||||||
|
}
|
||||||
async onload() {
|
async onload() {
|
||||||
setLogger(this.addLog.bind(this)); // Logger moved to global.
|
setLogger(this.addLog.bind(this)); // Logger moved to global.
|
||||||
Logger("loading plugin");
|
Logger("loading plugin");
|
||||||
@@ -203,8 +211,18 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
this.saveSettings();
|
this.saveSettings();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
this.addCommand({
|
||||||
|
id: "livesync-history",
|
||||||
|
name: "Show history",
|
||||||
|
editorCallback: (editor: Editor, view: MarkdownView) => {
|
||||||
|
this.showHistory(view.file);
|
||||||
|
},
|
||||||
|
});
|
||||||
this.triggerRealizeSettingSyncMode = debounce(this.triggerRealizeSettingSyncMode.bind(this), 1000);
|
this.triggerRealizeSettingSyncMode = debounce(this.triggerRealizeSettingSyncMode.bind(this), 1000);
|
||||||
this.triggerCheckPluginUpdate = debounce(this.triggerCheckPluginUpdate.bind(this), 3000);
|
this.triggerCheckPluginUpdate = debounce(this.triggerCheckPluginUpdate.bind(this), 3000);
|
||||||
|
setLockNotifier(() => {
|
||||||
|
this.refreshStatusText();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
onunload() {
|
onunload() {
|
||||||
this.localDatabase.onunload();
|
this.localDatabase.onunload();
|
||||||
@@ -253,6 +271,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
gcTimerHandler: any = null;
|
gcTimerHandler: any = null;
|
||||||
gcHook() {
|
gcHook() {
|
||||||
if (this.settings.gcDelay == 0) return;
|
if (this.settings.gcDelay == 0) return;
|
||||||
|
if (this.settings.useHistory) return;
|
||||||
const GC_DELAY = this.settings.gcDelay * 1000; // if leaving opening window, try GC,
|
const GC_DELAY = this.settings.gcDelay * 1000; // if leaving opening window, try GC,
|
||||||
if (this.gcTimerHandler != null) {
|
if (this.gcTimerHandler != null) {
|
||||||
clearTimeout(this.gcTimerHandler);
|
clearTimeout(this.gcTimerHandler);
|
||||||
@@ -787,7 +806,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
waiting = " " + this.batchFileChange.map((e) => "🛫").join("");
|
waiting = " " + this.batchFileChange.map((e) => "🛫").join("");
|
||||||
waiting = waiting.replace(/(🛫){10}/g, "🚀");
|
waiting = waiting.replace(/(🛫){10}/g, "🚀");
|
||||||
}
|
}
|
||||||
const message = `Sync:${w} ↑${sent} ↓${arrived}${waiting}`;
|
const procs = getProcessingCounts();
|
||||||
|
const procsDisp = procs == 0 ? "" : ` ⏳${procs}`;
|
||||||
|
const message = `Sync:${w} ↑${sent} ↓${arrived}${waiting}${procsDisp}`;
|
||||||
this.setStatusBarText(message);
|
this.setStatusBarText(message);
|
||||||
}
|
}
|
||||||
setStatusBarText(message: string) {
|
setStatusBarText(message: string) {
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export interface ObsidianLiveSyncSettings {
|
|||||||
checkIntegrityOnSave: boolean;
|
checkIntegrityOnSave: boolean;
|
||||||
batch_size: number;
|
batch_size: number;
|
||||||
batches_limit: number;
|
batches_limit: number;
|
||||||
|
useHistory:boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = {
|
export const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = {
|
||||||
@@ -98,6 +99,7 @@ export const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = {
|
|||||||
checkIntegrityOnSave: false,
|
checkIntegrityOnSave: false,
|
||||||
batch_size: 250,
|
batch_size: 250,
|
||||||
batches_limit: 40,
|
batches_limit: 40,
|
||||||
|
useHistory:false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PERIODIC_PLUGIN_SWEEP = 60;
|
export const PERIODIC_PLUGIN_SWEEP = 60;
|
||||||
|
|||||||
27
src/utils.ts
27
src/utils.ts
@@ -121,7 +121,28 @@ function objectToKey(key: any): string {
|
|||||||
const keys = Object.keys(key).sort((a, b) => a.localeCompare(b));
|
const keys = Object.keys(key).sort((a, b) => a.localeCompare(b));
|
||||||
return keys.map((e) => e + objectToKey(key[e])).join(":");
|
return keys.map((e) => e + objectToKey(key[e])).join(":");
|
||||||
}
|
}
|
||||||
|
export function getProcessingCounts() {
|
||||||
|
let count = 0;
|
||||||
|
for (const v in pendingProcs) {
|
||||||
|
count += pendingProcs[v].length;
|
||||||
|
}
|
||||||
|
count += runningProcs.length;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
let externalNotifier: () => void = () => {};
|
||||||
|
let notifyTimer: number = null;
|
||||||
|
export function setLockNotifier(fn: () => void) {
|
||||||
|
externalNotifier = fn;
|
||||||
|
}
|
||||||
|
function notifyLock() {
|
||||||
|
if (notifyTimer != null) {
|
||||||
|
window.clearTimeout(notifyTimer);
|
||||||
|
}
|
||||||
|
notifyTimer = window.setTimeout(() => {
|
||||||
|
externalNotifier();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
// Just run async/await as like transacion ISOLATION SERIALIZABLE
|
// Just run async/await as like transacion ISOLATION SERIALIZABLE
|
||||||
export function runWithLock<T>(key: unknown, ignoreWhenRunning: boolean, proc: () => Promise<T>): Promise<T> {
|
export function runWithLock<T>(key: unknown, ignoreWhenRunning: boolean, proc: () => Promise<T>): Promise<T> {
|
||||||
// Logger(`Lock:${key}:enter`, LOG_LEVEL.VERBOSE);
|
// Logger(`Lock:${key}:enter`, LOG_LEVEL.VERBOSE);
|
||||||
@@ -130,11 +151,13 @@ export function runWithLock<T>(key: unknown, ignoreWhenRunning: boolean, proc: (
|
|||||||
if (typeof pendingProcs[lockKey] === "undefined") {
|
if (typeof pendingProcs[lockKey] === "undefined") {
|
||||||
//simply unlock
|
//simply unlock
|
||||||
runningProcs.remove(lockKey);
|
runningProcs.remove(lockKey);
|
||||||
|
notifyLock();
|
||||||
// Logger(`Lock:${lockKey}:released`, LOG_LEVEL.VERBOSE);
|
// Logger(`Lock:${lockKey}:released`, LOG_LEVEL.VERBOSE);
|
||||||
} else {
|
} else {
|
||||||
Logger(`Lock:${lockKey}:left ${pendingProcs[lockKey].length}`, LOG_LEVEL.VERBOSE);
|
Logger(`Lock:${lockKey}:left ${pendingProcs[lockKey].length}`, LOG_LEVEL.VERBOSE);
|
||||||
let nextProc = null;
|
let nextProc = null;
|
||||||
nextProc = pendingProcs[lockKey].shift();
|
nextProc = pendingProcs[lockKey].shift();
|
||||||
|
notifyLock();
|
||||||
if (nextProc) {
|
if (nextProc) {
|
||||||
// left some
|
// left some
|
||||||
nextProc()
|
nextProc()
|
||||||
@@ -145,6 +168,7 @@ export function runWithLock<T>(key: unknown, ignoreWhenRunning: boolean, proc: (
|
|||||||
.finally(() => {
|
.finally(() => {
|
||||||
if (pendingProcs && lockKey in pendingProcs && pendingProcs[lockKey].length == 0) {
|
if (pendingProcs && lockKey in pendingProcs && pendingProcs[lockKey].length == 0) {
|
||||||
delete pendingProcs[lockKey];
|
delete pendingProcs[lockKey];
|
||||||
|
notifyLock();
|
||||||
}
|
}
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
handleNextProcs();
|
handleNextProcs();
|
||||||
@@ -153,6 +177,7 @@ export function runWithLock<T>(key: unknown, ignoreWhenRunning: boolean, proc: (
|
|||||||
} else {
|
} else {
|
||||||
if (pendingProcs && lockKey in pendingProcs && pendingProcs[lockKey].length == 0) {
|
if (pendingProcs && lockKey in pendingProcs && pendingProcs[lockKey].length == 0) {
|
||||||
delete pendingProcs[lockKey];
|
delete pendingProcs[lockKey];
|
||||||
|
notifyLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,10 +214,12 @@ export function runWithLock<T>(key: unknown, ignoreWhenRunning: boolean, proc: (
|
|||||||
});
|
});
|
||||||
|
|
||||||
pendingProcs[lockKey].push(subproc);
|
pendingProcs[lockKey].push(subproc);
|
||||||
|
notifyLock();
|
||||||
// Logger(`Lock:${lockKey}:queud:left${pendingProcs[lockKey].length}`, LOG_LEVEL.VERBOSE);
|
// Logger(`Lock:${lockKey}:queud:left${pendingProcs[lockKey].length}`, LOG_LEVEL.VERBOSE);
|
||||||
return responder;
|
return responder;
|
||||||
} else {
|
} else {
|
||||||
runningProcs.push(lockKey);
|
runningProcs.push(lockKey);
|
||||||
|
notifyLock();
|
||||||
// Logger(`Lock:${lockKey}:aqquired`, LOG_LEVEL.VERBOSE);
|
// Logger(`Lock:${lockKey}:aqquired`, LOG_LEVEL.VERBOSE);
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
proc()
|
proc()
|
||||||
|
|||||||
@@ -8,11 +8,54 @@ export const isValidRemoteCouchDBURI = (uri: string): boolean => {
|
|||||||
if (uri.startsWith("http://")) return true;
|
if (uri.startsWith("http://")) return true;
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
let last_post_successed = false;
|
||||||
|
export const getLastPostFailedBySize = () => {
|
||||||
|
return !last_post_successed;
|
||||||
|
};
|
||||||
export const connectRemoteCouchDB = async (uri: string, auth: { username: string; password: string }): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> => {
|
export const connectRemoteCouchDB = async (uri: string, auth: { username: string; password: string }): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> => {
|
||||||
if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid";
|
if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid";
|
||||||
const db: PouchDB.Database<EntryDoc> = new PouchDB<EntryDoc>(uri, {
|
const conf: PouchDB.HttpAdapter.HttpAdapterConfiguration = {
|
||||||
|
adapter: "http",
|
||||||
auth,
|
auth,
|
||||||
});
|
fetch: async function (url: string | Request, opts: RequestInit) {
|
||||||
|
let size_ok = true;
|
||||||
|
let size = "";
|
||||||
|
const localURL = url.toString().substring(uri.length);
|
||||||
|
const method = opts.method ?? "GET";
|
||||||
|
if (opts.body) {
|
||||||
|
const opts_length = opts.body.toString().length;
|
||||||
|
if (opts_length > 1024 * 1024 * 10) {
|
||||||
|
// over 10MB
|
||||||
|
size_ok = false;
|
||||||
|
if (uri.contains(".cloudantnosqldb.")) {
|
||||||
|
last_post_successed = false;
|
||||||
|
Logger("This request should fail on IBM Cloudant.", LOG_LEVEL.VERBOSE);
|
||||||
|
throw new Error("This request should fail on IBM Cloudant.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size = ` (${opts_length})`;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const responce: Response = await fetch(url, opts);
|
||||||
|
if (method == "POST" || method == "PUT") {
|
||||||
|
last_post_successed = responce.ok;
|
||||||
|
} else {
|
||||||
|
last_post_successed = true;
|
||||||
|
}
|
||||||
|
Logger(`HTTP:${method}${size} to:${localURL} -> ${responce.status}`, LOG_LEVEL.VERBOSE);
|
||||||
|
return responce;
|
||||||
|
} catch (ex) {
|
||||||
|
Logger(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL.VERBOSE);
|
||||||
|
if (!size_ok && (method == "POST" || method == "PUT")) {
|
||||||
|
last_post_successed = false;
|
||||||
|
}
|
||||||
|
Logger(ex);
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
// return await fetch(url, opts);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const db: PouchDB.Database<EntryDoc> = new PouchDB<EntryDoc>(uri, conf);
|
||||||
try {
|
try {
|
||||||
const info = await db.info();
|
const info = await db.info();
|
||||||
return { db: db, info: info };
|
return { db: db, info: info };
|
||||||
|
|||||||
30
styles.css
30
styles.css
@@ -140,3 +140,33 @@ div.sls-setting-menu-btn {
|
|||||||
background-color: var(--background-secondary-alt);
|
background-color: var(--background-secondary-alt);
|
||||||
color: var(--text-accent);
|
color: var(--text-accent);
|
||||||
}
|
}
|
||||||
|
.op-flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.op-flex input {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.op-info {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
border-bottom: 1px solid var(--background-modifier-border);
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-added {
|
||||||
|
color: var(--text-on-accent);
|
||||||
|
background-color: var(--text-accent);
|
||||||
|
}
|
||||||
|
.history-normal {
|
||||||
|
color: var(--text-normal);
|
||||||
|
}
|
||||||
|
.history-deleted {
|
||||||
|
color: var(--text-on-accent);
|
||||||
|
background-color: var(--text-muted);
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user