import PouchDB from 'pouchdb'; import {DiagramEntity, DiagramEventType} from "../diagram/types/diagramEntity"; import {Color3, Observable} from "@babylonjs/core"; import {AppConfigType} from "../util/appConfigType"; import {v4 as uuidv4} from 'uuid'; import axios from "axios"; import {DiagramManager} from "../diagram/diagramManager"; import log, {Logger} from "loglevel"; import {DiagramListing, DiagramListingEventType} from "../diagram/types/diagramListing"; export class PouchdbPersistenceManager { configObserver: Observable = new Observable(); updateObserver: Observable = new Observable(); removeObserver: Observable = new Observable(); //implement IPersistenceManager interface with pouchdb apis private db: PouchDB; private remote: PouchDB; private config: PouchDB; private diagramListings: PouchDB; private readonly logger: Logger = log.getLogger('PouchdbPersistenceManager'); constructor() { this.config = new PouchDB("config"); this.diagramListings = new PouchDB("diagramListings"); } public async getDiagramListings() { return this.diagramListings.allDocs({include_docs: true}); } public setDiagramManager(diagramManager: DiagramManager) { diagramManager.onDiagramEventListingObservable.add((evt) => { if (evt.type == DiagramListingEventType.GETALL) { this.diagramListings.allDocs({include_docs: true}).then((all) => { for (const entity of all.rows) { diagramManager.onDiagramEventListingObservable.notifyObservers({ type: DiagramListingEventType.ADD, listing: {id: entity.doc._id, name: entity.doc.name} }, -1, false, this); } }); } }, -1, false, this); diagramManager.onDiagramEventObservable.add((evt) => { switch (evt.type) { case DiagramEventType.CHANGECOLOR: this.changeColor(evt.oldColor, evt.newColor); break; case DiagramEventType.ADD: this.add(evt.entity); break; case DiagramEventType.REMOVE: this.remove(evt.entity.id); break; case DiagramEventType.MODIFY: case DiagramEventType.DROP: this.modify(evt.entity); break; default: //this.logger.warn('App', 'unknown diagram event type', evt); } }, 2); this.updateObserver.add((evt) => { diagramManager.onDiagramEventObservable.notifyObservers({ type: DiagramEventType.ADD, entity: evt }, 1); }); this.removeObserver.add((entity) => { diagramManager.onDiagramEventObservable.notifyObservers( {type: DiagramEventType.REMOVE, entity: entity}, 1); }); } private _currentDiagramId: string; public get currentDiagramId(): string { return this._currentDiagramId; } public set currentDiagramId(value: string) { this._currentDiagramId = value; try { const listing = this.diagramListings.get(value); } catch (err) { this.diagramListings.put({_id: value, name: "New Diagram"}); } this.db = new PouchDB(value); this.db.sync(this.remote, {live: true}); } public async add(entity: DiagramEntity) { if (!entity) { return; } const newEntity = {...entity, _id: entity.id}; try { this.db.put(newEntity); } catch (err) { this.logger.error(err); } } public async remove(id: string) { if (!id) { return; } try { const doc = await this.db.get(id); this.db.remove(doc); } catch (err) { this.logger.error(err); } } public async modify(entity: DiagramEntity) { if (!entity) { return; } try { const doc = await this.db.get(entity.id); const newDoc = {...doc, ...entity}; this.db.put(newDoc); } catch (err) { this.logger.error(err); } } public async addDiagram(diagram: DiagramListing) { try { const doc = await this.diagramListings.get(diagram.id); } catch (err) { this.diagramListings.put({...diagram, _id: diagram.id}); } } public async removeDiagram(diagram: DiagramListing) { try { this.diagramListings.delete(diagram.id); } catch (err) { this.logger.error(err); } } public async modifyDiagram(diagram: DiagramListing) { try { const doc = await this.db.get(diagram.id); this.db.put({...doc, ...diagram}); } catch (err) { this.logger.error(err); } } public async getNewRelicData(): Promise { return []; } public async setNewRelicData(data: any[]): Promise { return data; } public async setConfig(config: any, initial: boolean = false): Promise { if (!initial) { const doc = await this.config.get('1'); const newConf = {...config, _id: '1', _rev: doc._rev}; return this.config.put(newConf); } else { const newConf = {...config, _id: '1'}; return this.config.put(newConf); } } public async getConfig(): Promise { return this.config.get('1'); } public async initialize() { try { let current = this.getPath(); const config = await this.config.get('1'); if (!current && config.currentDiagramId) { this.db = new PouchDB(config.currentDiagramId); await this.beginSync(config.currentDiagramId); } else { if (current) { config.currentDiagramId = current; } else { config.currentDiagramId = uuidv4(); } this.db = new PouchDB(config.currentDiagramId); await this.beginSync(config.currentDiagramId); await this.config.put(config); } this.configObserver.notifyObservers(config); } catch (err) { const defaultConfig = { _id: '1', demoCompleted: false, gridSnap: 1, rotateSnap: 0, createSnap: 0, turnSnap: 0, flyMode: true, currentDiagramId: uuidv4() } try { await this.setConfig(defaultConfig, true); } catch (err) { this.logger.error(err); } this.diagramListings.put({_id: defaultConfig.currentDiagramId, name: "New Diagram"}); this.db = new PouchDB(defaultConfig.currentDiagramId); await this.beginSync(defaultConfig.currentDiagramId); this.configObserver.notifyObservers(defaultConfig); } try { const all = await this.db.allDocs({include_docs: true}); for (const entity of all.rows) { this.updateObserver.notifyObservers(entity.doc, 1); } } catch (err) { this.logger.error(err); } } private getPath(): string { const path = window.location.pathname.split('/'); if (path.length == 3 && path[1]) { return path[2]; } else { return null; } } syncDoc = function (info) { this.logger.info(info); if (info.direction == 'pull') { const docs = info.change.docs; for (const doc of docs) { if (doc._deleted) { this.removeObserver.notifyObservers({id: doc._id, template: doc.template}, 1); } else { this.updateObserver.notifyObservers(doc, 1); } } } } async changeColor(oldColor: Color3, newColor: Color3) { const all = await this.db.allDocs({include_docs: true}); for (const entity of all.rows) { this.logger.debug(`comparing ${entity.doc.color} to ${oldColor.toHexString()}`); if (entity.doc.color == oldColor.toHexString()) { entity.doc.color = newColor.toHexString(); this.db.put({...entity.doc, _rev: entity.doc._rev}); } } } setCurrentDiagram(diagram: DiagramListing) { this.currentDiagramId = diagram.id; } sync() { } private async beginSync(remoteDbName: string) { try { //const remoteDbName = "db1"; const remoteUserName = remoteDbName; const password = "password"; const dbs = await axios.get(import.meta.env.VITE_SYNCDB_ENDPOINT + '_all_dbs'); if (dbs.data.indexOf(remoteDbName) == -1) { this.logger.warn('sync target missing'); const userEndpoint: string = import.meta.env.VITE_USER_ENDPOINT console.log(userEndpoint); console.log(remoteDbName); const buildTarget = await axios.post(userEndpoint, {username: remoteUserName, password: password, db: remoteDbName}); if (buildTarget.status != 200) { this.logger.info(buildTarget.statusText); return; } } this.logger.debug(dbs); const remoteEndpoint: string = import.meta.env.VITE_SYNCDB_ENDPOINT; console.log(remoteEndpoint); this.remote = new PouchDB(remoteEndpoint + remoteDbName, {auth: {username: remoteUserName, password: password}}); this.syncDoc = this.syncDoc.bind(this); this.db.sync(this.remote, {live: true, retry: true}) .on('change', this.syncDoc); } catch (err) { this.logger.error(err); } } }