From: Bill Erickson Date: Mon, 2 Dec 2019 17:26:08 +0000 (-0500) Subject: LPXXX Angular IndexedDB pass-through service X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=5b4022a179378a3370cd71a6f3c8652ee53e58fb;p=working%2FEvergreen.git LPXXX Angular IndexedDB pass-through service Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/src/eg2/src/app/core/db.service.ts b/Open-ILS/src/eg2/src/app/core/db.service.ts new file mode 100644 index 0000000000..53238809f1 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/core/db.service.ts @@ -0,0 +1,169 @@ +import {Injectable} from '@angular/core'; + +// TODO: move to a more generic location. +const WORKER_URL = '/js/ui/default/staff/offline-db-worker.js'; + +// Tell TS about SharedWorkers +// https://stackoverflow.com/questions/13296549/typescript-enhanced-sharedworker-portmessage-channel-contracts +interface SharedWorker extends AbstractWorker { + port: MessagePort; +}; + +declare var SharedWorker: { + prototype: SharedWorker; + new (scriptURL: any, name: any): SharedWorker; + new (scriptURL: any): SharedWorker; +} + +interface PendingRequest { + id: number; + resolve(response: any): any; + reject(error: any): any; +} + +interface DbRequest { + schema: string; + action: string; + table?: string; + id?: number; +} + +@Injectable({providedIn: 'root'}) +export class DbService { + + autoId = 0; // each request gets a unique id. + cannotConnect: boolean; + pendingRequests: {[id: number]: PendingRequest} = {}; + + activeSchemas: string[] = ['cache']; // add 'offline' in the offline UI + + schemasInProgress: {[schema: string]: Promise} = {}; + schemasConnected: {[schema: string]: boolean} = {}; + + worker: SharedWorker = null; + + constructor() {} + + connectToWorker() { + if (this.worker || this.cannotConnect) { return; } + + try { + this.worker = new SharedWorker(WORKER_URL); + } catch (E) { + console.warn('SharedWorker() not supported', E); + this.cannotConnect = true; + return; + } + + this.worker.onerror = err => { + this.cannotConnect = true; + console.error('Cannot connect to DB shared worker', err); + }; + + // List for responses and resolve the matching pending request. + this.worker.port.addEventListener('message', evt => { + const response = evt.data; + const reqId = response.id; + const req = this.pendingRequests[reqId]; + + if (!req) { + console.error('Recieved response for unknown request ' + reqId); + return; + } + + // Request is no longer pending. + delete this.pendingRequests[reqId]; + + if (response.status === 'OK') { + req.resolve(response.result); + } else { + console.error('worker request failed with ' + response.error); + req.reject(response.error); + } + }); + + this.worker.port.start(); + console.log('PORT OPENED'); + } + + // Send a request to the web worker and register the request for + // future resolution. + // Store the request ID in the request arguments, so it's included + // in the response, and in the pendingRequests list for linking. + relayRequest(req: DbRequest): Promise { + + return new Promise((resolve, reject) => { + const id = this.autoId++; + req.id = id; + + this.pendingRequests[id] = + {id : id, resolve: resolve, reject: reject}; + + this.worker.port.postMessage(req); + }); + } + + request(req: DbRequest): Promise { + + this.connectToWorker(); + + if (this.cannotConnect) { return Promise.resolve(); } + + return this.connectToSchemas().then(_ => this.relayRequest(req)); + } + + connectToSchemas(): Promise { + const promises = []; + + this.activeSchemas.forEach(schema => + promises.push(this.connectToOneSchema(schema)) + ); + + return Promise.all(promises).then( + _ => {}, + err => this.cannotConnect = true + ); + } + + connectToOneSchema(schema: string): Promise { + + console.log('connecting to a schema', schema); + + if (this.schemasConnected[schema]) { + return Promise.resolve(); + } + + if (this.schemasInProgress[schema]) { + return this.schemasInProgress[schema]; + } + + const promise = new Promise((resolve, reject) => { + + this.relayRequest({schema: schema, action: 'createSchema'} + + ).then(_ => + this.relayRequest({schema: schema, action: 'connect'}) + + ).then( + _ => { + this.schemasConnected[schema] = true; + delete this.schemasInProgress[schema]; + resolve(); + }, + err => reject(err) + ); + }); + + return this.schemasInProgress[schema] = promise; + } + + clearSettingsCache() { + return this.request({ + schema: 'cache', + table: 'Setting', + action: 'deleteAll' + }); + } +} + +