UNPKG

ns2-front-module-common

Version:
316 lines 15.4 kB
import { KeyValueCacheStructure } from "../models/key-value-cache-structure.model"; import { Injectable } from "@angular/core"; import { Md5 } from 'ts-md5/dist/md5'; import { STORAGE_TOKEN_KEY } from "./current-user.service"; /** * Базовый сервис для key-value хранилищ */ var AbstractKeyValueStorage = (function () { function AbstractKeyValueStorage() { // Флаг, обозначающий что процесс очистки выполняется this.maintenanceProcessing = false; // Список ключей которые нельзя удалять при процедуре очистки this.maintenanceExceptKeys = [STORAGE_TOKEN_KEY]; } /** * Сохранение данных по ключу на указанное время, с учетом переданных тегов. * При ошибке сохранения запускается метод очистки хранилища (т.к. ошибка могла произойти из-за нехватки места) * и после очистки сохранение пытается выполниться еще раз. * * @param {string} key Ключ, по которому необходимо сохранить данные * @param {any} value Данные для сохранения * @param {number} expired Количество секунд в течение которых данные будут храниться * @param {string[]} tags Массив тегов, которыми необходимо пометить набор данных * @returns {Promise<T>} */ AbstractKeyValueStorage.prototype.set = function (key, value, expired, tags) { var _this = this; var item = new KeyValueCacheStructure(expired, tags, value); return new Promise(function (resolve, reject) { var res = function () { resolve(value); }; _this.setInternal(key, item) .then(res) .catch(function () { _this.maintenance() .then(function () { _this.setInternal(key, item).then(res).catch(reject); }) .catch(reject); }); }); }; /** * Получение значения по ключу. * Если значение существует, но истек срок его хранения - удаляем эту запись и все с ней связанное из хранилища. * * @param {string} key Ключ, по которому необходимо получить значение * @returns {Promise<T>} */ AbstractKeyValueStorage.prototype.get = function (key) { var _this = this; return new Promise(function (resolve, reject) { _this._get(_this.prepareKey(key)).then(function (value) { try { var item_1 = KeyValueCacheStructure.unserialize(value); if (item_1.isExpired()) { _this.removeInternal(key, item_1.tags).then(reject).catch(reject); } else { item_1.hits = item_1.hits + 1; _this.setInternal(key, item_1).then(function () { resolve(item_1.value || null); }).catch(function () { resolve(item_1.value || null); }); } } catch (err) { reject(err); } }).catch(reject); }); }; /** * Удаление данных из хранилища по указанному тегу. * * @param {string} tag Имя тега по которому необходимо удалить данные * @returns {Promise<T>} */ AbstractKeyValueStorage.prototype.removeByTag = function (tag) { var _this = this; return new Promise(function (resolve, reject) { var depends = []; var realTagName = _this.prepareTag(tag); _this._get(realTagName).then(function (keysListJson) { var keysList = JSON.parse(keysListJson); depends = keysList.map(function (keyName) { return new Promise(function (res, rej) { var realKeyName = _this.prepareKey(keyName); _this._get(realKeyName).then(function (value) { var item = KeyValueCacheStructure.unserialize(value); _this.removeInternal(keyName, item.tags).then(res).catch(rej); }).catch(rej); }); }); var rmDepends = function () { Promise.all(depends).then(resolve).catch(resolve); }; _this._remove(realTagName).then(rmDepends).catch(rmDepends); }).catch(reject); }); }; /** * Удаление данных из хранилища по ключу. * * @param {string} key Ключ по которым необходимо удалить данные * @returns {Promise<T>} */ AbstractKeyValueStorage.prototype.remove = function (key) { var _this = this; return new Promise(function (resolve, reject) { var realKeyName = _this.prepareKey(key); _this._get(realKeyName).then(function (jsonItem) { var item = KeyValueCacheStructure.unserialize(jsonItem); _this.removeInternal(key, item.tags).then(resolve).catch(reject); }).catch(reject); }); }; /** * Очистка всего хранилища. * * @returns {Promise<any>} */ AbstractKeyValueStorage.prototype.clear = function () { return this._clear(); }; /** * Очистка неиспользуемых/редко используемых данных, освобождение места. * * @returns {Promise<any>} */ AbstractKeyValueStorage.prototype.maintenance = function () { var _this = this; return new Promise(function (resolve, reject) { if (!_this.maintenanceProcessing) { _this._getStorageInstance().then(function (storage) { _this.maintenanceProcessing = true; var keys = Object.keys(storage); var CLEAN_RECORDS = Math.floor(keys.length * AbstractKeyValueStorage.CLEAN_RECORDS_PERCENT / 100); var list2Clean = {}; var toCleanLength = 0; var minKeyName = ''; var minHits = 0; var cleanOnlyEmpty = true; for (var i = 0; i < keys.length; i++) { var keyName = keys[i]; if (keyName.indexOf(AbstractKeyValueStorage.DATA_PREFIX) === 0) { var originalName = keyName.substring(AbstractKeyValueStorage.DATA_PREFIX.length), skipKey = (_this.maintenanceExceptKeys.indexOf(originalName) > -1); var jsonInfo = storage.getItem(keyName); if ((jsonInfo !== null) && !skipKey) { var item = KeyValueCacheStructure.unserialize(jsonInfo); if (toCleanLength < CLEAN_RECORDS) { list2Clean[keyName] = item.hits; toCleanLength++; if ((minKeyName == '') || (minHits > item.hits)) { minKeyName = keyName; minHits = item.hits; } if (item.hits > 0) { cleanOnlyEmpty = false; } } else if (cleanOnlyEmpty && (toCleanLength >= CLEAN_RECORDS)) { break; } else if (item.hits < minHits) { delete list2Clean[minKeyName]; list2Clean[keyName] = item.hits; if (item.hits > 0) { cleanOnlyEmpty = false; } } } } } var depends = []; for (var kName in list2Clean) { if (list2Clean.hasOwnProperty(kName)) { var name_1 = kName.substring(AbstractKeyValueStorage.DATA_PREFIX.length); depends.push(_this.remove(name_1)); } } Promise.all(depends).then(function () { _this.maintenanceProcessing = false; resolve(); }).catch(function () { _this.maintenanceProcessing = false; resolve(); }); }).catch(reject); } else { resolve(); } }); }; /** * Генерирует имя для использования в качестве ключа * * @param args Аргументы участвующие в формировании ключа * @returns {string} */ AbstractKeyValueStorage.prototype.getCacheKey = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } var result = JSON.stringify(args); return String(Md5.hashStr(result)); }; /** * Установка значения в хранилище и помечание его тегами * * @param {string} key Ключ по которому надо сохранить значение * @param {KeyValueCacheStructure} item Структура элемента который надо сохранить * @returns {Promise<T>} */ AbstractKeyValueStorage.prototype.setInternal = function (key, item) { var _this = this; return new Promise(function (resolve, reject) { var depends = item.tags.map(function (tagKey) { return new Promise(function (res, rej) { var realTagKey = _this.prepareTag(tagKey); _this._get(realTagKey).then(function (tagValue) { var iTags = JSON.parse(tagValue); var sIndex = iTags.indexOf(key); if (sIndex === -1) { iTags.push(key); _this._set(realTagKey, JSON.stringify(iTags)).then(res).catch(rej); } else { res(); } }).catch(function () { _this._set(realTagKey, JSON.stringify([key])).then(res).catch(rej); }); }); }); depends.push(_this._set(_this.prepareKey(key), item.serialize())); Promise.all(depends).then(resolve).catch(reject); }); }; /** * Удаление значения по указанному ключу, помеченное переданными тегами. * Удаляется ключ с его значением, из тегов которыми он был помечен удаляется этот ключ * (если тег после удаления ключа становится пустым - он удаляется также) * * @param {string} key Ключ по которому надо удалить данные * @param {string[]} tags Массив тегов которыми помечена удаляемая запись * @returns {Promise<T>} */ AbstractKeyValueStorage.prototype.removeInternal = function (key, tags) { var _this = this; return new Promise(function (resolve, reject) { var depends = tags.map(function (tagKey) { return new Promise(function (res, rej) { tagKey = _this.prepareTag(tagKey); _this._get(tagKey) .then(function (tagValue) { var iTags = JSON.parse(tagValue); var sIndex = iTags.indexOf(key); if (sIndex > -1) { iTags.splice(sIndex, 1); if (iTags.length > 0) { _this._set(tagKey, JSON.stringify(iTags)).then(res).catch(rej); } else { _this._remove(tagKey).then(res).catch(rej); } } else { rej(); } }) .catch(rej); }); }); depends.push(_this._remove(_this.prepareKey(key))); Promise.all(depends).then(resolve).catch(reject); }); }; /** * Формирует имя тега для сохранения в хранилище * * @param {string} tag Оригинальное имя тега (которое указал программист) * @returns {string} */ AbstractKeyValueStorage.prototype.prepareTag = function (tag) { return AbstractKeyValueStorage.TAGS_PREFIX + tag.trim().toLowerCase(); }; ; /** * Формирует имя ключа для сохранения в хранилище * * @param {string} key Оригинальное имя ключа (которое указал программист) * @returns {string} */ AbstractKeyValueStorage.prototype.prepareKey = function (key) { return AbstractKeyValueStorage.DATA_PREFIX + key.trim().toLowerCase(); }; return AbstractKeyValueStorage; }()); export { AbstractKeyValueStorage }; // Количество наименее редко используемых записей (в процентах) которые будут удалены при очередной очистке AbstractKeyValueStorage.CLEAN_RECORDS_PERCENT = 30; // Префикс ключей при хранении тегов AbstractKeyValueStorage.TAGS_PREFIX = 'T_'; // Префикс ключей при хранении данных AbstractKeyValueStorage.DATA_PREFIX = 'D_'; AbstractKeyValueStorage.decorators = [ { type: Injectable }, ]; /** @nocollapse */ AbstractKeyValueStorage.ctorParameters = function () { return []; }; //# sourceMappingURL=abstract-key-value-storage.service.js.map