|
@@ -0,0 +1,122 @@
|
|
|
|
+const _ = require('lodash');
|
|
|
|
+
|
|
|
|
+const utils = require('../utils');
|
|
|
|
+const JembaConnManager = require('../../db/JembaConnManager');//singleton
|
|
|
|
+
|
|
|
|
+let instance = null;
|
|
|
|
+
|
|
|
|
+//singleton
|
|
|
|
+class JembaReaderStorage {
|
|
|
|
+ constructor() {
|
|
|
|
+ if (!instance) {
|
|
|
|
+ this.connManager = new JembaConnManager();
|
|
|
|
+ this.db = this.connManager.db['reader-storage'];
|
|
|
|
+ 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 db = this.db;
|
|
|
|
+
|
|
|
|
+ for (const id of Object.keys(items)) {
|
|
|
|
+ if (this.cache[id]) {
|
|
|
|
+ result.items[id] = this.cache[id];
|
|
|
|
+ } else {
|
|
|
|
+ const rows = await db.select({//SQL`SELECT rev FROM storage WHERE id = ${id}`
|
|
|
|
+ table: 'storage',
|
|
|
|
+ map: '(r) => ({rev: r.rev})',
|
|
|
|
+ where: `@@id(${db.esc(id)})`
|
|
|
|
+ });
|
|
|
|
+ const rev = (rows.length && rows[0].rev ? rows[0].rev : 0);
|
|
|
|
+ result.items[id] = {rev};
|
|
|
|
+ this.cache[id] = result.items[id];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ async getItems(items) {
|
|
|
|
+ let result = {state: 'success', items: {}};
|
|
|
|
+
|
|
|
|
+ const db = this.db;
|
|
|
|
+
|
|
|
|
+ for (const id of Object.keys(items)) {
|
|
|
|
+ const rows = await db.select({//SQL`SELECT rev, data FROM storage WHERE id = ${id}`);
|
|
|
|
+ table: 'storage',
|
|
|
|
+ where: `@@id(${db.esc(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};
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ 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 db = this.db;
|
|
|
|
+ const newRev = {};
|
|
|
|
+ for (const id of Object.keys(items)) {
|
|
|
|
+ await db.insert({//SQL`INSERT OR REPLACE INTO storage (id, rev, time, data) VALUES (${id}, ${items[id].rev}, strftime('%s','now'), ${items[id].data})`);
|
|
|
|
+ table: 'storage',
|
|
|
|
+ replace: true,
|
|
|
|
+ rows: [{id, rev: items[id].rev, time: utils.toUnixTime(Date.now()), data: items[id].data}],
|
|
|
|
+ });
|
|
|
|
+ newRev[id] = {rev: items[id].rev};
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Object.assign(this.cache, newRev);
|
|
|
|
+
|
|
|
|
+ return {state: 'success'};
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ periodicCleanCache(timeout) {
|
|
|
|
+ this.cache = {};
|
|
|
|
+
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ this.periodicCleanCache(timeout);
|
|
|
|
+ }, timeout);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+module.exports = JembaReaderStorage;
|