const SQL = require('sql-template-strings'); const _ = require('lodash'); const ConnManager = require('../db/ConnManager');//singleton let instance = null; //singleton class ReaderStorage { constructor() { if (!instance) { this.connManager = new ConnManager(); this.storagePool = this.connManager.pool.readerStorage; this.periodicCleanCache(3*3600*1000);//1 раз в 3 часа instance = this; } return instance; } async doAction(act) { if (!_.isObject(act.items)) throw new Error('items is not an object'); let result = {}; switch (act.action) { case 'check': result = await this.checkItems(act.items); break; case 'get': result = await this.getItems(act.items); break; case 'set': result = await this.setItems(act.items, act.force); break; default: throw new Error('Unknown action'); } return result; } async checkItems(items) { let result = {state: 'success', items: {}}; const dbh = await this.storagePool.get(); try { for (const id of Object.keys(items)) { if (this.cache[id]) { result.items[id] = this.cache[id]; } else { const rows = await dbh.all(SQL`SELECT rev FROM storage WHERE id = ${id}`); const rev = (rows.length && rows[0].rev ? rows[0].rev : 0); result.items[id] = {rev}; this.cache[id] = result.items[id]; } } } finally { dbh.ret(); } return result; } async getItems(items) { let result = {state: 'success', items: {}}; const dbh = await this.storagePool.get(); try { for (const id of Object.keys(items)) { const rows = await dbh.all(SQL`SELECT rev, data FROM storage WHERE id = ${id}`); const rev = (rows.length && rows[0].rev ? rows[0].rev : 0); const data = (rows.length && rows[0].data ? rows[0].data : ''); result.items[id] = {rev, data}; } } finally { dbh.ret(); } return result; } async setItems(items, force) { let check = await this.checkItems(items); //сначала проверим совпадение ревизий for (const id of Object.keys(items)) { if (!_.isString(items[id].data)) throw new Error('items.data is not a string'); if (!force && check.items[id].rev + 1 !== items[id].rev) return {state: 'reject', items: check.items}; } const dbh = await this.storagePool.get(); await dbh.run('BEGIN'); try { const newRev = {}; for (const id of Object.keys(items)) { await dbh.run(SQL`INSERT OR REPLACE INTO storage (id, rev, time, data) VALUES (${id}, ${items[id].rev}, strftime('%s','now'), ${items[id].data})`); newRev[id] = {rev: items[id].rev}; } await dbh.run('COMMIT'); Object.assign(this.cache, newRev); } catch (e) { await dbh.run('ROLLBACK'); throw e; } finally { dbh.ret(); } return {state: 'success'}; } periodicCleanCache(timeout) { this.cache = {}; setTimeout(() => { this.periodicCleanCache(timeout); }, timeout); } } module.exports = ReaderStorage;