mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2025-01-05 12:50:41 +02:00
Fixed:
- Freezing LiveSync on mobile devices.
This commit is contained in:
parent
6f76f90075
commit
5db3a374a9
@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-livesync",
|
||||
"name": "Self-hosted LiveSync",
|
||||
"version": "0.8.5",
|
||||
"version": "0.8.6",
|
||||
"minAppVersion": "0.9.12",
|
||||
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"author": "vorotamoroz",
|
||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.8.5",
|
||||
"version": "0.8.6",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.8.5",
|
||||
"version": "0.8.6",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"diff-match-patch": "^1.0.5",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.8.5",
|
||||
"version": "0.8.6",
|
||||
"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",
|
||||
"type": "module",
|
||||
|
@ -28,6 +28,8 @@ import { path2id } from "./utils";
|
||||
import { Logger } from "./lib/src/logger";
|
||||
import { checkRemoteVersion, connectRemoteCouchDB, getLastPostFailedBySize } from "./utils_couchdb";
|
||||
|
||||
type ReplicationCallback = (e: PouchDB.Core.ExistingDocument<EntryDoc>[]) => Promise<void>;
|
||||
|
||||
export class LocalPouchDB {
|
||||
auth: Credential;
|
||||
dbname: string;
|
||||
@ -52,7 +54,7 @@ export class LocalPouchDB {
|
||||
remoteLockedAndDeviceNotAccepted = false;
|
||||
|
||||
changeHandler: PouchDB.Core.Changes<EntryDoc> = null;
|
||||
syncHandler: PouchDB.Replication.Sync<EntryDoc> = null;
|
||||
syncHandler: PouchDB.Replication.Sync<EntryDoc> | PouchDB.Replication.Replication<EntryDoc> = null;
|
||||
|
||||
leafArrivedCallbacks: { [key: string]: (() => void)[] } = {};
|
||||
|
||||
@ -61,6 +63,8 @@ export class LocalPouchDB {
|
||||
docSent = 0;
|
||||
docSeq = "";
|
||||
|
||||
isMobile = false;
|
||||
|
||||
cancelHandler<T extends PouchDB.Core.Changes<EntryDoc> | PouchDB.Replication.Sync<EntryDoc> | PouchDB.Replication.Replication<EntryDoc>>(handler: T): T {
|
||||
if (handler != null) {
|
||||
handler.removeAllListeners();
|
||||
@ -77,7 +81,7 @@ export class LocalPouchDB {
|
||||
this.localDatabase.removeAllListeners();
|
||||
}
|
||||
|
||||
constructor(settings: RemoteDBSettings, dbname: string) {
|
||||
constructor(settings: RemoteDBSettings, dbname: string, isMobile: boolean) {
|
||||
this.auth = {
|
||||
username: "",
|
||||
password: "",
|
||||
@ -85,6 +89,7 @@ export class LocalPouchDB {
|
||||
this.dbname = dbname;
|
||||
this.settings = settings;
|
||||
this.cancelHandler = this.cancelHandler.bind(this);
|
||||
this.isMobile = isMobile;
|
||||
|
||||
// this.initializeDatabase();
|
||||
}
|
||||
@ -699,73 +704,18 @@ export class LocalPouchDB {
|
||||
replicateAllToServer(setting: RemoteDBSettings, showingNotice?: boolean) {
|
||||
return new Promise(async (res, rej) => {
|
||||
await this.waitForGCComplete();
|
||||
this.closeReplication();
|
||||
Logger("send all data to server", LOG_LEVEL.NOTICE);
|
||||
let notice: WrappedNotice = null;
|
||||
if (showingNotice) {
|
||||
notice = NewNotice("Initializing", 0);
|
||||
}
|
||||
this.syncStatus = "STARTED";
|
||||
this.updateInfo();
|
||||
const uri = setting.couchDB_URI + (setting.couchDB_DBNAME == "" ? "" : "/" + setting.couchDB_DBNAME);
|
||||
const auth: Credential = {
|
||||
username: setting.couchDB_USER,
|
||||
password: setting.couchDB_PASSWORD,
|
||||
};
|
||||
const dbret = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI);
|
||||
if (typeof dbret === "string") {
|
||||
Logger(`could not connect to ${uri}:${dbret}`, LOG_LEVEL.NOTICE);
|
||||
if (notice != null) notice.hide();
|
||||
return rej(`could not connect to ${uri}:${dbret}`);
|
||||
}
|
||||
|
||||
const syncOptionBase: PouchDB.Replication.SyncOptions = {
|
||||
pull: {
|
||||
checkpoint: "target",
|
||||
},
|
||||
push: {
|
||||
checkpoint: "source",
|
||||
},
|
||||
batches_limit: setting.batches_limit,
|
||||
batch_size: setting.batch_size,
|
||||
};
|
||||
|
||||
const db = dbret.db;
|
||||
const totalCount = (await this.localDatabase.info()).doc_count;
|
||||
//replicate once
|
||||
const replicate = this.localDatabase.replicate.to(db, { checkpoint: "source", ...syncOptionBase });
|
||||
replicate
|
||||
.on("active", () => {
|
||||
this.syncStatus = "CONNECTED";
|
||||
this.updateInfo();
|
||||
if (notice) {
|
||||
notice.setMessage("CONNECTED");
|
||||
}
|
||||
})
|
||||
.on("change", (e) => {
|
||||
// no op.
|
||||
this.docSent += e.docs.length;
|
||||
this.updateInfo();
|
||||
notice.setMessage(`SENDING:${e.docs_written}/${totalCount}`);
|
||||
Logger(`replicateAllToServer: sending..:${e.docs.length}`);
|
||||
})
|
||||
.on("complete", (info) => {
|
||||
this.syncStatus = "COMPLETED";
|
||||
this.updateInfo();
|
||||
Logger("replicateAllToServer: Completed", LOG_LEVEL.NOTICE);
|
||||
this.cancelHandler(replicate);
|
||||
if (notice != null) notice.hide();
|
||||
res(true);
|
||||
})
|
||||
.on("error", (e) => {
|
||||
this.syncStatus = "ERRORED";
|
||||
this.updateInfo();
|
||||
Logger("replicateAllToServer: Pulling Replication error", LOG_LEVEL.INFO);
|
||||
Logger(e);
|
||||
this.cancelHandler(replicate);
|
||||
if (notice != null) notice.hide();
|
||||
this.openOneshotReplication(
|
||||
setting,
|
||||
showingNotice,
|
||||
async (e) => {},
|
||||
false,
|
||||
(e) => {
|
||||
if (e === true) res(e);
|
||||
rej(e);
|
||||
});
|
||||
},
|
||||
true,
|
||||
false
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@ -789,7 +739,8 @@ export class LocalPouchDB {
|
||||
Logger("Another replication running.");
|
||||
return false;
|
||||
}
|
||||
const dbret = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI);
|
||||
|
||||
const dbret = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI || this.isMobile);
|
||||
if (typeof dbret === "string") {
|
||||
Logger(`could not connect to ${uri}: ${dbret}`, LOG_LEVEL.NOTICE);
|
||||
return false;
|
||||
@ -830,189 +781,256 @@ export class LocalPouchDB {
|
||||
return { db: dbret.db, info: dbret.info, syncOptionBase, syncOption };
|
||||
}
|
||||
|
||||
async openReplication(setting: RemoteDBSettings, keepAlive: boolean, showResult: boolean, callback: (e: PouchDB.Core.ExistingDocument<EntryDoc>[]) => Promise<void>): Promise<boolean> {
|
||||
return await runWithLock("replicate", false, () => {
|
||||
return this._openReplication(setting, keepAlive, showResult, callback, false);
|
||||
});
|
||||
openReplication(setting: RemoteDBSettings, keepAlive: boolean, showResult: boolean, callback: (e: PouchDB.Core.ExistingDocument<EntryDoc>[]) => Promise<void>) {
|
||||
if (keepAlive) {
|
||||
this.openContinuousReplication(setting, showResult, callback, false);
|
||||
} else {
|
||||
this.openOneshotReplication(setting, showResult, callback, false, null, false, false);
|
||||
}
|
||||
}
|
||||
replicationActivated(notice: WrappedNotice) {
|
||||
this.syncStatus = "CONNECTED";
|
||||
this.updateInfo();
|
||||
Logger("Replication activated");
|
||||
if (notice != null) notice.setMessage(`Activated..`);
|
||||
}
|
||||
async replicationChangeDetected(e: PouchDB.Replication.SyncResult<EntryDoc>, notice: WrappedNotice, docSentOnStart: number, docArrivedOnStart: number, callback: ReplicationCallback) {
|
||||
try {
|
||||
if (e.direction == "pull") {
|
||||
await callback(e.change.docs);
|
||||
Logger(`replicated ${e.change.docs_read} doc(s)`);
|
||||
this.docArrived += e.change.docs.length;
|
||||
} else {
|
||||
this.docSent += e.change.docs.length;
|
||||
}
|
||||
if (notice != null) {
|
||||
notice.setMessage(`↑${this.docSent - docSentOnStart} ↓${this.docArrived - docArrivedOnStart}`);
|
||||
}
|
||||
this.updateInfo();
|
||||
} catch (ex) {
|
||||
Logger("Replication callback error", LOG_LEVEL.NOTICE);
|
||||
Logger(ex, LOG_LEVEL.NOTICE);
|
||||
//
|
||||
}
|
||||
}
|
||||
replicationCompleted(notice: WrappedNotice, showResult: boolean) {
|
||||
this.syncStatus = "COMPLETED";
|
||||
this.updateInfo();
|
||||
Logger("Replication completed", showResult ? LOG_LEVEL.NOTICE : LOG_LEVEL.INFO);
|
||||
if (notice != null) notice.hide();
|
||||
this.syncHandler = this.cancelHandler(this.syncHandler);
|
||||
}
|
||||
replicationDeniend(notice: WrappedNotice, e: any) {
|
||||
this.syncStatus = "ERRORED";
|
||||
this.updateInfo();
|
||||
this.syncHandler = this.cancelHandler(this.syncHandler);
|
||||
if (notice != null) notice.hide();
|
||||
Logger("Replication denied", LOG_LEVEL.NOTICE);
|
||||
Logger(e);
|
||||
}
|
||||
replicationErrored(notice: WrappedNotice, e: any) {
|
||||
this.syncStatus = "ERRORED";
|
||||
this.syncHandler = this.cancelHandler(this.syncHandler);
|
||||
this.updateInfo();
|
||||
}
|
||||
replicationPaused(notice: WrappedNotice) {
|
||||
this.syncStatus = "PAUSED";
|
||||
this.updateInfo();
|
||||
if (notice != null) notice.hide();
|
||||
Logger("replication paused", LOG_LEVEL.VERBOSE);
|
||||
}
|
||||
|
||||
originalSetting: RemoteDBSettings = null;
|
||||
// last_seq: number = 200;
|
||||
async _openReplication(setting: RemoteDBSettings, 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;
|
||||
async openOneshotReplication(
|
||||
setting: RemoteDBSettings,
|
||||
showResult: boolean,
|
||||
callback: (e: PouchDB.Core.ExistingDocument<EntryDoc>[]) => Promise<void>,
|
||||
retrying: boolean,
|
||||
callbackDone: (e: boolean | any) => void,
|
||||
pushOnly: boolean,
|
||||
pullOnly: boolean
|
||||
): Promise<boolean> {
|
||||
if (this.syncHandler != null) {
|
||||
Logger("Replication is already in progress.", showResult ? LOG_LEVEL.NOTICE : LOG_LEVEL.INFO);
|
||||
return;
|
||||
}
|
||||
Logger("Oneshot Sync begin...");
|
||||
let thisCallback = callbackDone;
|
||||
const ret = await this.checkReplicationConnectivity(setting, true, retrying);
|
||||
let notice: WrappedNotice = null;
|
||||
if (ret === false) {
|
||||
Logger("Could not connect to server.", LOG_LEVEL.NOTICE);
|
||||
return;
|
||||
}
|
||||
if (showResult) {
|
||||
notice = NewNotice("Looking for the point last synchronized point.", 0);
|
||||
}
|
||||
const { db, syncOptionBase, syncOption } = ret;
|
||||
//replicate once
|
||||
const { db, syncOptionBase } = ret;
|
||||
this.syncStatus = "STARTED";
|
||||
this.updateInfo();
|
||||
|
||||
let resolved = false;
|
||||
const docArrivedOnStart = this.docArrived;
|
||||
const docSentOnStart = this.docSent;
|
||||
|
||||
const _openReplicationSync = () => {
|
||||
Logger("Sync Main Started");
|
||||
if (!retrying) {
|
||||
this.originalSetting = setting;
|
||||
}
|
||||
this.syncHandler = this.cancelHandler(this.syncHandler);
|
||||
this.syncHandler = this.localDatabase.sync<EntryDoc>(db, {
|
||||
...syncOption,
|
||||
pull: {
|
||||
checkpoint: "target",
|
||||
},
|
||||
push: {
|
||||
checkpoint: "source",
|
||||
},
|
||||
});
|
||||
if (!retrying) {
|
||||
// If initial replication, save setting to rollback
|
||||
this.originalSetting = setting;
|
||||
}
|
||||
this.syncHandler = this.cancelHandler(this.syncHandler);
|
||||
if (!pushOnly && !pullOnly) {
|
||||
this.syncHandler = this.localDatabase.sync(db, { checkpoint: "target", ...syncOptionBase });
|
||||
this.syncHandler
|
||||
.on("active", () => {
|
||||
this.syncStatus = "CONNECTED";
|
||||
this.updateInfo();
|
||||
Logger("Replication activated");
|
||||
if (notice != null) notice.setMessage(`Activated..`);
|
||||
})
|
||||
.on("change", async (e) => {
|
||||
try {
|
||||
if (e.direction == "pull") {
|
||||
await callback(e.change.docs);
|
||||
Logger(`replicated ${e.change.docs_read} doc(s)`);
|
||||
this.docArrived += e.change.docs.length;
|
||||
} else {
|
||||
this.docSent += e.change.docs.length;
|
||||
}
|
||||
if (notice != null) {
|
||||
notice.setMessage(`↑${this.docSent - docSentOnStart} ↓${this.docArrived - docArrivedOnStart}`);
|
||||
}
|
||||
this.updateInfo();
|
||||
} catch (ex) {
|
||||
Logger("Replication callback error", LOG_LEVEL.NOTICE);
|
||||
Logger(ex, LOG_LEVEL.NOTICE);
|
||||
}
|
||||
// re-connect to retry with original setting
|
||||
await this.replicationChangeDetected(e, notice, docSentOnStart, docArrivedOnStart, callback);
|
||||
if (retrying) {
|
||||
if (this.docSent - docSentOnStart + (this.docArrived - docArrivedOnStart) > this.originalSetting.batch_size * 2) {
|
||||
// restore sync values
|
||||
// restore configration.
|
||||
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);
|
||||
this.openOneshotReplication(this.originalSetting, showResult, callback, false, callbackDone, pushOnly, pullOnly);
|
||||
}
|
||||
}
|
||||
})
|
||||
.on("complete", (e) => {
|
||||
this.syncStatus = "COMPLETED";
|
||||
this.updateInfo();
|
||||
Logger("Replication completed", showResult ? LOG_LEVEL.NOTICE : LOG_LEVEL.INFO);
|
||||
if (notice != null) notice.hide();
|
||||
if (!keepAlive) {
|
||||
this.syncHandler = this.cancelHandler(this.syncHandler);
|
||||
// if keep alive runnning, resolve here,
|
||||
this.replicationCompleted(notice, showResult);
|
||||
if (thisCallback != null) {
|
||||
thisCallback(true);
|
||||
}
|
||||
});
|
||||
} else if (pullOnly) {
|
||||
this.syncHandler = this.localDatabase.replicate.to(db, { checkpoint: "target", ...syncOptionBase });
|
||||
this.syncHandler
|
||||
.on("change", async (e) => {
|
||||
await this.replicationChangeDetected({ direction: "pull", change: e }, notice, docSentOnStart, docArrivedOnStart, callback);
|
||||
if (retrying) {
|
||||
if (this.docSent - docSentOnStart + (this.docArrived - docArrivedOnStart) > this.originalSetting.batch_size * 2) {
|
||||
// restore configration.
|
||||
Logger("Back into original settings once.");
|
||||
if (notice != null) notice.hide();
|
||||
this.syncHandler = this.cancelHandler(this.syncHandler);
|
||||
this.openOneshotReplication(this.originalSetting, showResult, callback, false, callbackDone, pushOnly, pullOnly);
|
||||
}
|
||||
}
|
||||
})
|
||||
.on("denied", (e) => {
|
||||
this.syncStatus = "ERRORED";
|
||||
this.updateInfo();
|
||||
this.syncHandler = this.cancelHandler(this.syncHandler);
|
||||
if (notice != null) notice.hide();
|
||||
Logger("Replication denied", LOG_LEVEL.NOTICE);
|
||||
.on("complete", (e) => {
|
||||
this.replicationCompleted(notice, showResult);
|
||||
if (thisCallback != null) {
|
||||
thisCallback(true);
|
||||
}
|
||||
});
|
||||
} else if (pushOnly) {
|
||||
this.syncHandler = this.localDatabase.replicate.to(db, { checkpoint: "target", ...syncOptionBase });
|
||||
this.syncHandler.on("complete", (e) => {
|
||||
this.replicationCompleted(notice, showResult);
|
||||
if (thisCallback != null) {
|
||||
thisCallback(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.syncHandler
|
||||
.on("active", () => this.replicationActivated(notice))
|
||||
.on("denied", (e) => {
|
||||
this.replicationDeniend(notice, e);
|
||||
if (thisCallback != null) {
|
||||
thisCallback(e);
|
||||
}
|
||||
})
|
||||
.on("error", (e) => {
|
||||
this.replicationErrored(notice, e);
|
||||
Logger("Replication stopped.", showResult ? LOG_LEVEL.NOTICE : LOG_LEVEL.INFO);
|
||||
if (notice != null) notice.hide();
|
||||
if (getLastPostFailedBySize()) {
|
||||
// Duplicate settings for smaller batch.
|
||||
const xsetting: RemoteDBSettings = JSON.parse(JSON.stringify(setting));
|
||||
xsetting.batch_size = Math.ceil(xsetting.batch_size / 2) + 2;
|
||||
xsetting.batches_limit = Math.ceil(xsetting.batches_limit / 2) + 2;
|
||||
if (xsetting.batch_size <= 5 && xsetting.batches_limit <= 5) {
|
||||
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);
|
||||
thisCallback = null;
|
||||
this.openOneshotReplication(xsetting, showResult, callback, true, callbackDone, pushOnly, pullOnly);
|
||||
}
|
||||
} else {
|
||||
Logger("Replication error", LOG_LEVEL.NOTICE);
|
||||
Logger(e);
|
||||
})
|
||||
.on("error", (e) => {
|
||||
this.syncStatus = "ERRORED";
|
||||
this.syncHandler = this.cancelHandler(this.syncHandler);
|
||||
this.updateInfo();
|
||||
if (notice != null) notice.hide();
|
||||
if (getLastPostFailedBySize()) {
|
||||
if (keepAlive) {
|
||||
Logger("Replication stopped.", LOG_LEVEL.NOTICE);
|
||||
} else {
|
||||
// Duplicate settings for smaller batch.
|
||||
const xsetting: RemoteDBSettings = 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);
|
||||
}
|
||||
if (thisCallback != null) {
|
||||
thisCallback(e);
|
||||
}
|
||||
})
|
||||
.on("paused", (e) => this.replicationPaused(notice));
|
||||
}
|
||||
|
||||
openContinuousReplication(setting: RemoteDBSettings, showResult: boolean, callback: (e: PouchDB.Core.ExistingDocument<EntryDoc>[]) => Promise<void>, retrying: boolean) {
|
||||
if (this.syncHandler != null) {
|
||||
Logger("Replication is already in progress.", showResult ? LOG_LEVEL.NOTICE : LOG_LEVEL.INFO);
|
||||
return;
|
||||
}
|
||||
Logger("Before LiveSync, start OneShot once...");
|
||||
this.openOneshotReplication(
|
||||
setting,
|
||||
showResult,
|
||||
callback,
|
||||
false,
|
||||
async () => {
|
||||
Logger("LiveSync begin...");
|
||||
const ret = await this.checkReplicationConnectivity(setting, true, true);
|
||||
let notice: WrappedNotice = null;
|
||||
if (ret === false) {
|
||||
Logger("Could not connect to server.", showResult ? LOG_LEVEL.NOTICE : LOG_LEVEL.INFO);
|
||||
return;
|
||||
}
|
||||
if (showResult) {
|
||||
notice = NewNotice("Looking for the point last synchronized point.", 0);
|
||||
}
|
||||
const { db, syncOption } = ret;
|
||||
this.syncStatus = "STARTED";
|
||||
this.updateInfo();
|
||||
const docArrivedOnStart = this.docArrived;
|
||||
const docSentOnStart = this.docSent;
|
||||
if (!retrying) {
|
||||
//TODO if successfly saven, roll back org setting.
|
||||
this.originalSetting = setting;
|
||||
}
|
||||
this.syncHandler = this.cancelHandler(this.syncHandler);
|
||||
this.syncHandler = this.localDatabase.sync<EntryDoc>(db, {
|
||||
...syncOption,
|
||||
pull: {
|
||||
checkpoint: "target",
|
||||
},
|
||||
push: {
|
||||
checkpoint: "source",
|
||||
},
|
||||
});
|
||||
this.syncHandler
|
||||
.on("active", () => this.replicationActivated(notice))
|
||||
.on("change", async (e) => {
|
||||
await this.replicationChangeDetected(e, notice, docSentOnStart, docArrivedOnStart, callback);
|
||||
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.openContinuousReplication(this.originalSetting, showResult, callback, false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Logger("Replication error", LOG_LEVEL.NOTICE);
|
||||
Logger(e);
|
||||
}
|
||||
})
|
||||
.on("paused", (e) => {
|
||||
this.syncStatus = "PAUSED";
|
||||
this.updateInfo();
|
||||
if (notice != null) notice.hide();
|
||||
Logger("replication paused", LOG_LEVEL.VERBOSE);
|
||||
if (keepAlive && !resolved) {
|
||||
// if keep alive runnning, resolve here,
|
||||
resolved = true;
|
||||
}
|
||||
// Logger(e);
|
||||
});
|
||||
return this.syncHandler;
|
||||
};
|
||||
if (!keepAlive) {
|
||||
await _openReplicationSync();
|
||||
return true;
|
||||
}
|
||||
this.syncHandler = this.cancelHandler(this.syncHandler);
|
||||
Logger("Pull before replicate.");
|
||||
Logger(await this.localDatabase.info(), LOG_LEVEL.VERBOSE);
|
||||
Logger(await db.info(), LOG_LEVEL.VERBOSE);
|
||||
let replicate: PouchDB.Replication.Replication<EntryDoc>;
|
||||
try {
|
||||
replicate = this.localDatabase.replicate.from(db, { checkpoint: "target", ...syncOptionBase });
|
||||
replicate
|
||||
.on("active", () => {
|
||||
this.syncStatus = "CONNECTED";
|
||||
this.updateInfo();
|
||||
Logger("Replication pull activated.");
|
||||
})
|
||||
.on("change", async (e) => {
|
||||
// when in first run, replication will send us tombstone data
|
||||
// and in normal cases, all leavs should sent before the entry that contains these item.
|
||||
// so skip to completed all, we should treat all changes.
|
||||
try {
|
||||
await callback(e.docs);
|
||||
this.docArrived += e.docs.length;
|
||||
this.updateInfo();
|
||||
Logger(`pulled ${e.docs.length} doc(s)`);
|
||||
if (notice != null) {
|
||||
notice.setMessage(`Replication pulled:${e.docs_read}`);
|
||||
}
|
||||
} catch (ex) {
|
||||
Logger("Replication callback error", LOG_LEVEL.NOTICE);
|
||||
Logger(ex, LOG_LEVEL.NOTICE);
|
||||
}
|
||||
});
|
||||
this.syncStatus = "COMPLETED";
|
||||
this.updateInfo();
|
||||
this.cancelHandler(replicate);
|
||||
this.syncHandler = this.cancelHandler(this.syncHandler);
|
||||
Logger("Replication pull completed.");
|
||||
_openReplicationSync();
|
||||
return true;
|
||||
} catch (ex) {
|
||||
this.syncStatus = "ERRORED";
|
||||
this.updateInfo();
|
||||
Logger("Pulling Replication error:", LOG_LEVEL.NOTICE);
|
||||
Logger(ex, LOG_LEVEL.NOTICE);
|
||||
this.cancelHandler(replicate);
|
||||
this.syncHandler = this.cancelHandler(this.syncHandler);
|
||||
if (notice != null) notice.hide();
|
||||
throw ex;
|
||||
}
|
||||
})
|
||||
.on("complete", (e) => this.replicationCompleted(notice, showResult))
|
||||
.on("denied", (e) => this.replicationDeniend(notice, e))
|
||||
.on("error", (e) => {
|
||||
this.replicationErrored(notice, e);
|
||||
Logger("Replication stopped.", LOG_LEVEL.NOTICE);
|
||||
})
|
||||
.on("paused", (e) => this.replicationPaused(notice));
|
||||
},
|
||||
false,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
originalSetting: RemoteDBSettings = null;
|
||||
|
||||
closeReplication() {
|
||||
this.syncStatus = "CLOSED";
|
||||
this.updateInfo();
|
||||
@ -1039,7 +1057,7 @@ export class LocalPouchDB {
|
||||
username: setting.couchDB_USER,
|
||||
password: setting.couchDB_PASSWORD,
|
||||
};
|
||||
const con = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI);
|
||||
const con = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI || this.isMobile);
|
||||
if (typeof con == "string") return;
|
||||
try {
|
||||
await con.db.destroy();
|
||||
@ -1057,7 +1075,7 @@ export class LocalPouchDB {
|
||||
username: setting.couchDB_USER,
|
||||
password: setting.couchDB_PASSWORD,
|
||||
};
|
||||
const con2 = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI);
|
||||
const con2 = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI || this.isMobile);
|
||||
if (typeof con2 === "string") return;
|
||||
Logger("Remote Database Created or Connected", LOG_LEVEL.NOTICE);
|
||||
}
|
||||
@ -1067,7 +1085,7 @@ export class LocalPouchDB {
|
||||
username: setting.couchDB_USER,
|
||||
password: setting.couchDB_PASSWORD,
|
||||
};
|
||||
const dbret = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI);
|
||||
const dbret = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI || this.isMobile);
|
||||
if (typeof dbret === "string") {
|
||||
Logger(`could not connect to ${uri}:${dbret}`, LOG_LEVEL.NOTICE);
|
||||
return;
|
||||
@ -1101,7 +1119,7 @@ export class LocalPouchDB {
|
||||
username: setting.couchDB_USER,
|
||||
password: setting.couchDB_PASSWORD,
|
||||
};
|
||||
const dbret = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI);
|
||||
const dbret = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI || this.isMobile);
|
||||
if (typeof dbret === "string") {
|
||||
Logger(`could not connect to ${uri}:${dbret}`, LOG_LEVEL.NOTICE);
|
||||
return;
|
||||
|
@ -171,12 +171,18 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
),
|
||||
new Setting(containerRemoteDatabaseEl).setName("Use the old connecting method").addToggle((toggle) =>
|
||||
toggle.setValue(this.plugin.settings.disableRequestURI).onChange(async (value) => {
|
||||
this.plugin.settings.disableRequestURI = value;
|
||||
await this.plugin.saveSettings();
|
||||
|
||||
new Setting(containerRemoteDatabaseEl)
|
||||
.setDesc("This feature is locked in mobile")
|
||||
.setName("Use the old connecting method")
|
||||
.addToggle((toggle) => {
|
||||
toggle.setValue(this.plugin.settings.disableRequestURI).onChange(async (value) => {
|
||||
this.plugin.settings.disableRequestURI = value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
toggle.setDisabled(this.plugin.isMobile);
|
||||
return toggle;
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
new Setting(containerRemoteDatabaseEl)
|
||||
|
33
src/main.ts
33
src/main.ts
@ -64,6 +64,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
||||
statusBar2: HTMLElement;
|
||||
suspended: boolean;
|
||||
deviceAndVaultName: string;
|
||||
isMobile = false;
|
||||
|
||||
setInterval(handler: () => any, timeout?: number): number {
|
||||
const timer = window.setInterval(handler, timeout);
|
||||
@ -93,6 +94,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
||||
const lsname = "obsidian-live-sync-ver" + this.app.vault.getName();
|
||||
const last_version = localStorage.getItem(lsname);
|
||||
await this.loadSettings();
|
||||
//@ts-ignore
|
||||
if (this.app.isMobile) {
|
||||
this.isMobile = true;
|
||||
this.settings.disableRequestURI = true;
|
||||
}
|
||||
if (last_version && Number(last_version) < VER) {
|
||||
this.settings.liveSync = false;
|
||||
this.settings.syncOnSave = false;
|
||||
@ -180,7 +186,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
||||
await this.realizeSettingSyncMode();
|
||||
this.registerWatchEvents();
|
||||
if (this.settings.syncOnStart) {
|
||||
await this.localDatabase.openReplication(this.settings, false, false, this.parseReplicationResult);
|
||||
this.localDatabase.openReplication(this.settings, false, false, this.parseReplicationResult);
|
||||
}
|
||||
} catch (ex) {
|
||||
Logger("Error while loading Self-hosted LiveSync", LOG_LEVEL.NOTICE);
|
||||
@ -190,8 +196,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
||||
this.addCommand({
|
||||
id: "livesync-replicate",
|
||||
name: "Replicate now",
|
||||
callback: () => {
|
||||
this.replicate();
|
||||
callback: async () => {
|
||||
await this.replicate();
|
||||
},
|
||||
});
|
||||
this.addCommand({
|
||||
@ -306,7 +312,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
||||
}
|
||||
const vaultName = this.app.vault.getName();
|
||||
Logger("Open Database...");
|
||||
this.localDatabase = new LocalPouchDB(this.settings, vaultName);
|
||||
//@ts-ignore
|
||||
const isMobile = this.app.isMobile;
|
||||
this.localDatabase = new LocalPouchDB(this.settings, vaultName, isMobile);
|
||||
this.localDatabase.updateInfo = () => {
|
||||
this.refreshStatusText();
|
||||
};
|
||||
@ -389,10 +397,10 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
||||
await this.sweepPlugin(false);
|
||||
}
|
||||
if (this.settings.liveSync) {
|
||||
await this.localDatabase.openReplication(this.settings, true, false, this.parseReplicationResult);
|
||||
this.localDatabase.openReplication(this.settings, true, false, this.parseReplicationResult);
|
||||
}
|
||||
if (this.settings.syncOnStart) {
|
||||
await this.localDatabase.openReplication(this.settings, false, false, this.parseReplicationResult);
|
||||
this.localDatabase.openReplication(this.settings, false, false, this.parseReplicationResult);
|
||||
}
|
||||
if (this.settings.periodicReplication) {
|
||||
this.setPeriodicSync();
|
||||
@ -408,7 +416,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
||||
|
||||
async watchWorkspaceOpenAsync(file: TFile) {
|
||||
await this.applyBatchChange();
|
||||
if (file == null) return;
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
if (this.settings.syncOnFileOpen && !this.suspended) {
|
||||
await this.replicate();
|
||||
}
|
||||
@ -449,7 +459,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
||||
|
||||
async applyBatchChange() {
|
||||
if (!this.settings.batchSave || this.batchFileChange.length == 0) {
|
||||
return [];
|
||||
return;
|
||||
}
|
||||
return await runWithLock("batchSave", false, async () => {
|
||||
const batchItems = JSON.parse(JSON.stringify(this.batchFileChange)) as string[];
|
||||
@ -467,7 +477,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
||||
}
|
||||
});
|
||||
this.refreshStatusText();
|
||||
return await Promise.all(promises);
|
||||
await allSettledWithConcurrencyLimit(promises, 3);
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
@ -902,7 +913,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
||||
await this.sweepPlugin(false);
|
||||
}
|
||||
if (this.settings.liveSync) {
|
||||
await this.localDatabase.openReplication(this.settings, true, false, this.parseReplicationResult);
|
||||
this.localDatabase.openReplication(this.settings, true, false, this.parseReplicationResult);
|
||||
this.refreshStatusText();
|
||||
}
|
||||
this.setPeriodicSync();
|
||||
@ -971,7 +982,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
||||
if (this.settings.autoSweepPlugins) {
|
||||
await this.sweepPlugin(false);
|
||||
}
|
||||
await this.localDatabase.openReplication(this.settings, false, showMessage, this.parseReplicationResult);
|
||||
this.localDatabase.openReplication(this.settings, false, showMessage, this.parseReplicationResult);
|
||||
}
|
||||
|
||||
async initializeDatabase(showingNotice?: boolean) {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { PouchDB } from "../pouchdb-browser-webpack/dist/pouchdb-browser.js";
|
||||
console.dir(PouchDB)
|
||||
export { PouchDB };
|
||||
import { PouchDB as PouchDB_ } from "../pouchdb-browser-webpack/dist/pouchdb-browser.js";
|
||||
|
||||
const Pouch: PouchDB.Static = PouchDB_;
|
||||
export { Pouch as PouchDB };
|
||||
|
@ -41,7 +41,6 @@ export const connectRemoteCouchDB = async (uri: string, auth: { username: string
|
||||
adapter: "http",
|
||||
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";
|
||||
@ -49,7 +48,6 @@ export const connectRemoteCouchDB = async (uri: string, auth: { username: string
|
||||
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);
|
||||
@ -93,7 +91,8 @@ export const connectRemoteCouchDB = async (uri: string, auth: { username: string
|
||||
});
|
||||
} catch (ex) {
|
||||
Logger(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL.VERBOSE);
|
||||
if (!size_ok && (method == "POST" || method == "PUT")) {
|
||||
// limit only in bulk_docs.
|
||||
if (url.toString().indexOf("_bulk_docs") !== -1) {
|
||||
last_post_successed = false;
|
||||
}
|
||||
Logger(ex);
|
||||
@ -114,7 +113,8 @@ export const connectRemoteCouchDB = async (uri: string, auth: { username: string
|
||||
return responce;
|
||||
} catch (ex) {
|
||||
Logger(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL.VERBOSE);
|
||||
if (!size_ok && (method == "POST" || method == "PUT")) {
|
||||
// limit only in bulk_docs.
|
||||
if (url.toString().indexOf("_bulk_docs") !== -1) {
|
||||
last_post_successed = false;
|
||||
}
|
||||
Logger(ex);
|
||||
|
@ -6,7 +6,6 @@
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"types": ["svelte", "node"],
|
||||
// "importsNotUsedAsValues": "error",
|
||||
"importHelpers": true,
|
||||
"alwaysStrict": true,
|
||||
|
Loading…
Reference in New Issue
Block a user