UNPKG

c8osdkjscore

Version:
503 lines (470 loc) 22.7 kB
import {C8oCore} from "./c8oCore"; import {C8oProgress} from "./c8oProgress"; import {C8oResponseListener, C8oResponseProgressListener} from "./c8oResponse"; import {FullSyncReplication} from "./fullSyncReplication"; import PouchDB from "pouchdb-browser"; import * as PouchDBLoad from "pouchdb-load"; /** * Created by charlesg on 10/01/2017. */ export class C8oFullSyncDatabase { /** * Used to log. */ private c8o: C8oCore; /** TAG Attributes **/ /** * The fullSync database name. */ private databaseName: string; private c8oFullSyncDatabaseUrl: string; /** * The fullSync Database instance. */ private database = null; /** * Used to make pull replication (uploads changes from the local database to the remote one). */ private pullFullSyncReplication: FullSyncReplication = new FullSyncReplication(true); /** * Used to make push replication (downloads changes from the remote database to the local one). */ private pushFullSyncReplication: FullSyncReplication = new FullSyncReplication(false); /** * Used to make pull replication (uploads changes from the local database to the remote one). */ private syncFullSyncReplication: FullSyncReplication = new FullSyncReplication(); private remotePouchHeader; /** * Creates a fullSync database with the specified name and its location. * * @param c8o * @param databaseName * @param fullSyncDatabases * @param localSuffix * @throws C8oException Failed to get the fullSync database. */ constructor(c8o: C8oCore, databaseName: string, fullSyncDatabases: string, localSuffix: string) { this.c8o = c8o; const header = { "x-convertigo-sdk": this.c8o.sdkVersion, }; Object.assign(header, this.c8o.headers); this.remotePouchHeader = { ajax: { headers: header, }, }; this.c8oFullSyncDatabaseUrl = fullSyncDatabases + databaseName; this.databaseName = databaseName + localSuffix; try { if (c8o.couchUrl != null) { this.database = new PouchDB(c8o.couchUrl + "/" + databaseName); this.c8o.log.debug("PouchDb launched on couchbaselite"); } else { PouchDB.plugin(PouchDBLoad) this.database = new PouchDB(databaseName); this.c8o.log.debug("PouchDb launched normally"); } } catch (error) { throw error; } } /** * Start pull and push replications. * @returns Promise<any> */ public startAllReplications(parameters: Object, c8oResponseListener: C8oResponseListener): Promise<any> { return this.startSync(this.syncFullSyncReplication, parameters, c8oResponseListener); } /** * Start pull replication. * @returns Promise<any> */ public startPullReplication(parameters: Object, c8oResponseListener: C8oResponseListener): Promise<any> { return this.startReplication(this.pullFullSyncReplication, parameters, c8oResponseListener); } /** * Start push replication. * @returns Promise<any> */ public startPushReplication(parameters: Object, c8oResponseListener: C8oResponseListener): Promise<any> { return this.startReplication(this.pushFullSyncReplication, parameters, c8oResponseListener); } private startSync(fullSyncReplication: FullSyncReplication, parameters: Object, c8oResponseListener: C8oResponseListener): Promise<any> { let continuous: boolean = false; let cancel: boolean = false; const parametersObj: Object = {}; //stop replication if exists if (fullSyncReplication.replication != null) { fullSyncReplication.replication.cancel(); } //check continuous flag if (parameters["continuous"] != null) { if (parameters["continuous"] as boolean === true) { continuous = true; } else { continuous = false; } } //check cancel flag if (parameters["cancel"] != null) { //noinspection RedundantIfStatementJS if (parameters["cancel"] as boolean === true) { cancel = true; } else { cancel = false; } } // Set retry true by default... parametersObj["retry"] = true; //check parameters to throw to pouchDB if (parameters["retry"] != null) { parametersObj["retry"] = parameters["retry"]; } if (parameters["filter"] != null) { parametersObj["filter"] = parameters["filter"]; } if (parameters["doc_ids"] != null) { parametersObj["doc_ids"] = parameters["doc_ids"]; } if (parameters["query_params"] != null) { parametersObj["query_params"] = parameters["query_params"]; } if (parameters["view"] != null) { parametersObj["view"] = parameters["view"]; } if (parameters["since"] != null) { parametersObj["since"] = parameters["since"]; } if (parameters["heartbeat"] != null) { parametersObj["heartbeat"] = parameters["heartbeat"]; } if (parameters["timeout"] != null) { parametersObj["timeout"] = parameters["timeout"]; } if (parameters["batch_size"] != null) { parametersObj["batch_size"] = parameters["batch_size"]; } if (parameters["batches_limit"] != null) { parametersObj["batches_limit"] = parameters["batches_limit"]; } if (parameters["back_off_function"] != null) { parametersObj["back_off_function"] = parameters["back_off_function"]; } if (parameters["checkpoint"] != null) { parametersObj["checkpoint"] = parameters["checkpoint"]; } if (parameters["seq_interval"] != null) { parametersObj["seq_interval"] = parameters["seq_interval"]; } const remoteDB = new PouchDB(this.c8oFullSyncDatabaseUrl, this.remotePouchHeader); let rep = fullSyncReplication.replication = this.database.sync(remoteDB, parametersObj); const param = parameters; const progress: C8oProgress = new C8oProgress(); progress.raw = rep; progress.continuous = false; return new Promise((resolve, reject) => { rep.on("change", (info) => { progress.finished = false; if (info.direction === "pull") { progress.pull = true; progress.status = rep.pull.state; progress.finished = rep.pull.state !== "active"; } else if (info.direction === "push") { progress.pull = false; progress.status = rep.push.state; progress.finished = rep.push.state !== "active"; } progress.total = info.change.docs_read; progress.current = info.change.docs_written; param[C8oCore.ENGINE_PARAMETER_PROGRESS] = progress; (c8oResponseListener as C8oResponseProgressListener).onProgressResponse(progress, param); }).on("complete", (info) => { progress.finished = true; progress.pull = false; progress.total = info.push.docs_read; progress.current = info.push.docs_written; progress.status = info.status; progress.finished = true; param[C8oCore.ENGINE_PARAMETER_PROGRESS] = progress; (c8oResponseListener as C8oResponseProgressListener).onProgressResponse(progress, param); progress.pull = true; progress.total = info.pull.docs_read; progress.current = info.pull.docs_written; (c8oResponseListener as C8oResponseProgressListener).onProgressResponse(progress, param); rep.cancel(); if (continuous) { parametersObj["live"] = true; rep = fullSyncReplication.replication = this.database.sync(remoteDB, parametersObj); progress.continuous = true; progress.raw = rep; progress.taskInfo = "n/a"; progress.pull = true; progress.status = "live"; progress.finished = false; progress.pull = true; progress.total = 0; progress.current = 0; (c8oResponseListener as C8oResponseProgressListener).onProgressResponse(progress, param); progress.pull = false; (c8oResponseListener as C8oResponseProgressListener).onProgressResponse(progress, param); rep.on("change", (info) => { progress.finished = false; if (info.direction === "pull") { progress.pull = true; progress.status = rep.pull.state; } else if (info.direction === "push") { progress.pull = false; progress.status = rep.push.state; } progress.total = info.change.docs_read; progress.current = info.change.docs_written; param[C8oCore.ENGINE_PARAMETER_PROGRESS] = progress; (c8oResponseListener as C8oResponseProgressListener).onProgressResponse(progress, param); }) .on("paused", function() { progress.finished = true; (c8oResponseListener as C8oResponseProgressListener).onProgressResponse(progress, param); if (progress.total === 0 && progress.current === 0) { progress.pull = !progress.pull; (c8oResponseListener as C8oResponseProgressListener).onProgressResponse(progress, param); } }) .on("error", (err) => { if (err.message === "Unexpected end of JSON input") { progress.finished = true; progress.status = "live"; (c8oResponseListener as C8oResponseProgressListener).onProgressResponse(progress, parameters); } else { rep.cancel(); if (err.code === "ETIMEDOUT" && err.status === 0) { if (parameters["force_retry"] == true) { this.c8o.log.warn("C80=>FullSyncDatabase: Timeout handle during fullsync replication (fs://.sync) \n Forcing Restarting replication"); this.database.sync(remoteDB, {timeout: 600000, retry: true}); } else { this.c8o.log.warn("C80=>FullSyncDatabase: Timeout handle during fullsync replication (fs://.sync) \n Restarting automatically replication"); } } else if (err.name === "unknown" && err.status === 0 && err.message === "getCheckpoint rejected with ") { reject("NO_NETWORK"); } else { reject(err); } } }); } }).on("error", (err) => { rep.cancel(); if (err.message === "Unexpected end of JSON input") { progress.finished = true; progress.status = "Complete"; (c8oResponseListener as C8oResponseProgressListener).onProgressResponse(progress, parameters); rep.cancel(); } else if (err.code === "ETIMEDOUT" && err.status === 0) { if (parameters["force_retry"] == true) { this.c8o.log.warn("C80=>FullSyncDatabase: Timeout handle during fullsync replication (fs://.sync) \n Forcing Restarting replication"); this.database.sync(remoteDB, {timeout: 600000, retry: true}); } else { this.c8o.log.warn("C80=>FullSyncDatabase: Timeout handle during fullsync replication (fs://.sync) \n Restarting automatically replication"); } } else if (err.name === "unknown" && err.status === 0 && err.message === "getCheckpoint rejected with ") { reject("NO_NETWORK"); } else { reject(err); } }); if (cancel) { if (rep != null) { rep.cancel(); progress.finished = true; if (c8oResponseListener != null && c8oResponseListener instanceof C8oResponseProgressListener) { c8oResponseListener.onProgressResponse(progress, parameters); } } } }).catch((error) => { throw error.toString(); }); } /** * Starts a replication taking into account parameters.<br/> * This action does not directly return something but setup a callback raised when the replication raises change events. * * @param fullSyncReplication * @param c8oResponseListener * @param parameters */ private startReplication(fullSyncReplication: FullSyncReplication, parameters: Object, c8oResponseListener: C8oResponseListener): Promise<any> { let continuous: boolean = false; let cancel: boolean = false; const parametersObj: Object = {}; //stop replication if exists if (fullSyncReplication.replication != null) { fullSyncReplication.replication.cancel(); } //check continuous flag if (parameters["continuous"] != null) { if (parameters["continuous"] as boolean == true) { continuous = true; } else { continuous = false; } } //check cancel flag if (parameters["cancel"] != null) { //noinspection RedundantIfStatementJS if (parameters["cancel"] as boolean == true) { cancel = true; } else { cancel = false; } } //check parameters to throw to pouchDB // Set retry true by default... parametersObj["retry"] = true; if (parameters["retry"] != null) { parametersObj["retry"] = parameters["retry"]; } if (parameters["filter"] != null) { parametersObj["filter"] = parameters["filter"]; } if (parameters["doc_ids"] != null) { parametersObj["doc_ids"] = parameters["doc_ids"]; } if (parameters["query_params"] != null) { parametersObj["query_params"] = parameters["query_params"]; } if (parameters["view"] != null) { parametersObj["view"] = parameters["view"]; } if (parameters["since"] != null) { parametersObj["since"] = parameters["since"]; } if (parameters["heartbeat"] != null) { parametersObj["heartbeat"] = parameters["heartbeat"]; } if (parameters["timeout"] != null) { parametersObj["timeout"] = parameters["timeout"]; } if (parameters["batch_size"] != null) { parametersObj["batch_size"] = parameters["batch_size"]; } if (parameters["batches_limit"] != null) { parametersObj["batches_limit"] = parameters["batches_limit"]; } if (parameters["back_off_function"] != null) { parametersObj["back_off_function"] = parameters["back_off_function"]; } if (parameters["checkpoint"] != null) { parametersObj["checkpoint"] = parameters["checkpoint"]; } if (parameters["seq_interval"] != null) { parametersObj["seq_interval"] = parameters["seq_interval"]; } const remoteDB = new PouchDB(this.c8oFullSyncDatabaseUrl, this.remotePouchHeader); let rep = fullSyncReplication.replication = fullSyncReplication.pull ? this.database.replicate.from(remoteDB, parametersObj) : this.database.replicate.to(remoteDB, parametersObj); const progress: C8oProgress = new C8oProgress(); progress.raw = rep; progress.pull = fullSyncReplication.pull; progress.continuous = false; return new Promise((resolve, reject) => { rep.on("change", (info) => { progress.total = info.docs_read; progress.current = info.docs_written; progress.status = "change"; parameters[C8oCore.ENGINE_PARAMETER_PROGRESS] = progress; (c8oResponseListener as C8oResponseProgressListener).onProgressResponse(progress, parameters); }).on("complete", (info) => { progress.finished = true; progress.total = info.docs_read; progress.current = info.docs_written; progress.status = "complete"; parameters[C8oCore.ENGINE_PARAMETER_PROGRESS] = progress; (c8oResponseListener as C8oResponseProgressListener).onProgressResponse(progress, parameters); rep.cancel(); if (continuous) { parametersObj["live"] = true; rep = fullSyncReplication.replication = fullSyncReplication.pull ? this.database.replicate.from(remoteDB, parametersObj) : this.database.replicate.to(remoteDB, parametersObj); progress.continuous = true; progress.raw = rep; progress.taskInfo = "n/a"; rep.on("change", (info) => { progress.finished = false; progress.total = info.docs_read; progress.current = info.docs_written; progress.status = "change"; parameters[C8oCore.ENGINE_PARAMETER_PROGRESS] = progress; (c8oResponseListener as C8oResponseProgressListener).onProgressResponse(progress, parameters); }) .on("error", (err) => { if (err.message === "Unexpected end of JSON input") { progress.finished = true; progress.status = "live"; (c8oResponseListener as C8oResponseProgressListener).onProgressResponse(progress, parameters); } else { rep.cancel(); if (err.code === "ETIMEDOUT" && err.status === 0) { reject("TIMEOUT"); } else if (err.name === "unknown" && err.status === 0 && err.message === "getCheckpoint rejected with ") { reject("NO_NETWORK"); } else { reject(err); } } }); } }).on("error", (err) => { if (err.message === "Unexpected end of JSON input") { progress.finished = true; progress.status = "complete"; parameters[C8oCore.ENGINE_PARAMETER_PROGRESS] = progress; (c8oResponseListener as C8oResponseProgressListener).onProgressResponse(progress, parameters); rep.cancel(); } else if (err.code === "ETIMEDOUT" && err.status === 0) { reject("TIMEOUT"); } else if (err.name === "unknown" && err.status === 0 && err.message === "getCheckpoint rejected with ") { reject("NO_NETWORK"); } else { reject(err); } }); if (cancel) { if (rep != null) { rep.cancel(); progress.finished = true; if (c8oResponseListener != null && c8oResponseListener instanceof C8oResponseProgressListener) { c8oResponseListener.onProgressResponse(progress, parameters); } } } }).catch((error) => { throw error.toString(); }); } //noinspection JSUnusedGlobalSymbols public get getdatabseName(): string { return this.databaseName; } public get getdatabase(): any { return this.database; } public deleteDB(): Promise<any> { return new Promise((resolve, reject) => { if (this.database != null) { this.database.destroy().then((response) => { this.database = null; resolve(response); }).catch((error) => { this.c8o.log.debug("Failed to close DB, will retry: ", error.message); this.database.destroy().then((response) => { this.database = null; resolve(response); }).catch((error) => { this.c8o.log.debug("Failed to close DB, second attempt has failed ", error.message); reject(error); }); }); } }); } }