UNPKG

@iobroker/db-objects-file

Version:

The Library contains the Database classes for File based objects database client and server.

898 lines 37.1 kB
/** * Object DB in memory - Server * * Copyright 2013-2024 bluefox <dogafox@gmail.com> * * MIT License * */ import fs from 'fs-extra'; import path from 'node:path'; import { InMemoryFileDB } from '@iobroker/db-base'; import { tools } from '@iobroker/db-base'; import { objectsUtils as utils } from '@iobroker/db-objects-redis'; import deepClone from 'deep-clone'; /** * This class inherits InMemoryFileDB class and adds all relevant logic for objects * including the available methods for use by js-controller directly */ export class ObjectsInMemoryFileDB extends InMemoryFileDB { constructor(settings) { settings = settings || {}; settings.fileDB = settings.fileDB || { fileName: 'objects.json', backupDirName: 'backup-objects', }; super(settings); if (!this.change) { this.change = id => { this.log.silly(`${this.namespace} objects change: ${id} ${JSON.stringify(this.change)}`); }; } this.META_ID = '**META**'; this.fileOptions = {}; this.files = {}; this.writeTimer = null; this.writeIds = []; this.preserveSettings = ['custom']; this.defaultNewAcl = this.settings.defaultNewAcl || null; this.namespace = this.settings.namespace || this.settings.hostname || ''; this.writeFileInterval = this.settings.connection && typeof this.settings.connection.writeFileInterval === 'number' ? parseInt(this.settings.connection.writeFileInterval) : 5_000; if (!settings.jsonlDB) { this.log.silly(`${this.namespace} Objects DB uses file write interval of ${this.writeFileInterval} ms`); } this.objectsDir = path.join(this.dataDir, 'files'); // cached meta information for file operations this.existingMetaObjects = {}; // Handle some < js-controller 2.0 broken objects and correct them for (const obj of Object.values(this.dataset)) { if (tools.isObject(obj) && obj.acl && obj.acl.permissions && !obj.acl.object) { obj.acl.object = obj.acl.permissions; delete obj.acl.permissions; } } // init default new acl const configObj = this.dataset['system.config']; if (configObj && configObj.common && configObj.common.defaultNewAcl) { this.defaultNewAcl = deepClone(configObj.common.defaultNewAcl); } } // internal functionality _normalizeFilename(name) { return name ? name.replace(/[/\\]+/g, '/') : name; } // -------------- FILE FUNCTIONS ------------------------------------------- // internal functionality _saveFileSettings(id, force) { if (typeof id === 'boolean') { force = id; id = undefined; } id !== undefined && !this.writeIds.includes(id) && this.writeIds.push(id); this.writeTimer && clearTimeout(this.writeTimer); // if store immediately if (force) { this.writeTimer = null; // Store dirs description for (const writeId of this.writeIds) { const location = path.join(this.objectsDir, writeId, '_data.json'); try { if (fs.existsSync(path.join(this.objectsDir, writeId))) { fs.writeFileSync(location, JSON.stringify(this.fileOptions[writeId])); } } catch (e) { this.log.error(`${this.namespace} Cannot write files: ${location}: ${e.message}`); } } this.writeIds = []; } else { this.writeTimer = setTimeout(() => { // Store dirs description for (const writeId of this.writeIds) { const location = path.join(this.objectsDir, writeId, '_data.json'); try { fs.writeFileSync(location, JSON.stringify(this.fileOptions[writeId])); } catch (e) { this.log.error(`${this.namespace} Cannot write files: ${location}: ${e.message}`); } } this.writeIds = []; }, 1_000); } } // internal functionality _loadFileSettings(id) { if (!this.fileOptions[id]) { const location = path.join(this.objectsDir, id, '_data.json'); if (fs.existsSync(location)) { try { this.fileOptions[id] = fs.readJSONSync(location); } catch (e) { this.log.error(`${this.namespace} Cannot parse ${location}: ${e.message}`); this.fileOptions[id] = {}; } let corrected = false; Object.keys(this.fileOptions[id]).forEach(filename => { const normalized = this._normalizeFilename(filename); if (normalized !== filename) { const options = this.fileOptions[id][filename]; delete this.fileOptions[id][filename]; this.fileOptions[id][normalized] = options; corrected = true; } if (corrected) { try { fs.writeFileSync(location, JSON.stringify(this.fileOptions[id])); } catch (e) { this.log.error(`${this.namespace} Cannot write files: ${location}: ${e.message}`); } } }); } else { this.fileOptions[id] = {}; } } } // server only functionality syncFileDirectory(limitId) { const resNotifies = []; let resSynced = 0; function getAllFiles(dir) { let results = []; const list = fs.readdirSync(dir); list.forEach(file => { file = `${dir}/${file}`; const stat = fs.statSync(file); if (stat && stat.isDirectory()) { /* Recurse into a subdirectory */ results = results.concat(getAllFiles(file)); } else { /* Is a file */ results.push(file); } }); return results; } const res = this._getObjectView('system', 'meta', null); // collect meta ids to generate warning if non existing const metaIds = res.rows.map(obj => obj.id).filter(id => !limitId || limitId === id); if (!fs.existsSync(this.objectsDir)) { return { numberSuccess: resSynced, notifications: resNotifies, }; } const baseDirs = fs.readdirSync(this.objectsDir); baseDirs.forEach(dir => { let dirSynced = 0; if (dir === '..' || dir === '.') { return; } const dirPath = path.join(this.objectsDir, dir); const stat = fs.statSync(dirPath); if (!stat.isDirectory()) { return; } if (limitId && dir !== limitId) { return; } if (!metaIds.includes(dir)) { resNotifies.push(`Ignoring Directory "${dir}" because officially not created as meta object. Please remove directory!`); return; } this._loadFileSettings(dir); const files = getAllFiles(dirPath); files.forEach(file => { const localFile = file.substr(dirPath.length + 1); if (localFile === '_data.json') { return; } if (!this.fileOptions[dir][localFile]) { const fileStat = fs.statSync(file); const ext = path.extname(localFile); const mime = utils.getMimeType(ext); const _mimeType = mime.mimeType; const isBinary = mime.isBinary; this.fileOptions[dir][localFile] = { createdAt: fileStat.ctimeMs, acl: { owner: (this.defaultNewAcl && this.defaultNewAcl.owner) || utils.CONSTS.SYSTEM_ADMIN_USER, ownerGroup: (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) || utils.CONSTS.SYSTEM_ADMIN_GROUP, permissions: (this.defaultNewAcl && this.defaultNewAcl.file) || utils.CONSTS.ACCESS_USER_RW | utils.CONSTS.ACCESS_GROUP_READ | utils.CONSTS.ACCESS_EVERY_READ, // 0x644 }, mimeType: _mimeType, binary: isBinary, modifiedAt: fileStat.mtimeMs, }; dirSynced++; } }); this._saveFileSettings(dir); resSynced += dirSynced; dirSynced && resNotifies.push(`Added ${dirSynced} Files in Directory "${dir}"`); }); return { numberSuccess: resSynced, notifications: resNotifies, }; } // needed by server _writeFile(id, name, data, options) { if (typeof options === 'string') { options = { mimeType: options }; } if (options && options.acl) { options.acl = null; } const _path = utils.sanitizePath(id, name); id = _path.id; name = _path.name; options = options || {}; this._loadFileSettings(id); this.files[id] = this.files[id] || {}; try { if (!fs.existsSync(this.objectsDir)) { fs.mkdirSync(this.objectsDir); } if (!fs.existsSync(path.join(this.objectsDir, id))) { fs.mkdirSync(path.join(this.objectsDir, id)); } } catch (e) { this.log.error(`${this.namespace} Cannot create directories: ${path.join(this.objectsDir, id)}: ${e.message}`); this.log.error(`${this.namespace} Check the permissions! Or run installation fixer or "iobroker fix" command!`); throw e; } const ext = path.extname(name); const mime = utils.getMimeType(ext); const _mimeType = mime.mimeType; const isBinary = mime.isBinary; this.fileOptions[id][name] = this.fileOptions[id][name] || { createdAt: Date.now() }; this.fileOptions[id][name].acl = this.fileOptions[id][name].acl || { owner: options.user || (this.defaultNewAcl && this.defaultNewAcl.owner) || utils.CONSTS.SYSTEM_ADMIN_USER, ownerGroup: options.group || (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) || utils.CONSTS.SYSTEM_ADMIN_GROUP, permissions: options.mode || (this.defaultNewAcl && this.defaultNewAcl.file) || utils.CONSTS.ACCESS_USER_RW | utils.CONSTS.ACCESS_GROUP_READ | utils.CONSTS.ACCESS_EVERY_READ, // 0x644 }; this.fileOptions[id][name].mimeType = options.mimeType || _mimeType; this.fileOptions[id][name].binary = isBinary; this.fileOptions[id][name].acl.ownerGroup = this.fileOptions[id][name].acl.ownerGroup || (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) || utils.CONSTS.SYSTEM_ADMIN_GROUP; this.fileOptions[id][name].modifiedAt = Date.now(); try { // Create directories if complex structure fs.ensureDirSync(path.join(this.objectsDir, id, path.dirname(name))); // Store file fs.writeFileSync(path.join(this.objectsDir, id, name), data, { flag: 'w', encoding: isBinary ? 'binary' : 'utf8', }); if (isBinary) { // Reload by read delete this.files[id][name]; } else { this.files[id][name] = data; } // Store dir description this._saveFileSettings(id); } catch (e) { this.log.error(`${this.namespace} Cannot write files: ${path.join(this.objectsDir, id, name)}: ${e.message}`); throw e; } setImmediate((name, size) => { // publish event in states this.log.silly(`${this.namespace} memory publish ${id} ${JSON.stringify({ name, file: true, size })}`); this.publishAll('files', `${id}$%$${name}`, size); }, name, data.byteLength); } // needed by server _readFile(id, name, options) { if (options && options.acl) { options.acl = null; } const _path = utils.sanitizePath(id, name); id = _path.id; name = _path.name; options = options || {}; try { this._loadFileSettings(id); this.files[id] = this.files[id] || {}; if (!this.files[id][name] || this.settings.connection.noFileCache || options.noFileCache) { const location = path.join(this.objectsDir, id, name); if (fs.existsSync(location)) { // Create description object if not exists this.fileOptions[id][name] = this.fileOptions[id][name] || { acl: { owner: (this.defaultNewAcl && this.defaultNewAcl.owner) || utils.CONSTS.SYSTEM_ADMIN_USER, ownerGroup: (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) || utils.CONSTS.SYSTEM_ADMIN_GROUP, permissions: (this.defaultNewAcl && this.defaultNewAcl.file.permissions) || utils.CONSTS.ACCESS_USER_ALL | utils.CONSTS.ACCESS_GROUP_ALL | utils.CONSTS.ACCESS_EVERY_ALL, // 777 }, }; if (typeof this.fileOptions[id][name] !== 'object') { this.fileOptions[id][name] = { mimeType: this.fileOptions[id][name], acl: { owner: (this.defaultNewAcl && this.defaultNewAcl.owner) || utils.CONSTS.SYSTEM_ADMIN_USER, ownerGroup: (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) || utils.CONSTS.SYSTEM_ADMIN_GROUP, permissions: (this.defaultNewAcl && this.defaultNewAcl.file.permissions) || utils.CONSTS.ACCESS_USER_ALL | utils.CONSTS.ACCESS_GROUP_ALL | utils.CONSTS.ACCESS_EVERY_ALL, // 777 }, }; } this.files[id][name] = fs.readFileSync(location); if (this.fileOptions[id][name].binary === undefined) { const ext = path.extname(name); const mimeType = utils.getMimeType(ext); this.fileOptions[id][name].binary = mimeType.isBinary; this.fileOptions[id][name].mimeType = mimeType.mimeType; } if (!this.fileOptions[id][name].binary) { if (this.files[id][name]) { this.files[id][name] = this.files[id][name].toString(); } } } else { if (this.fileOptions[id][name] !== undefined) { delete this.fileOptions[id][name]; } if (this.files[id][name] !== undefined) { delete this.files[id][name]; } } } if (this.fileOptions[id][name] && !this.fileOptions[id][name].acl) { // all files belong to admin by default, but everyone can edit it this.fileOptions[id][name].acl = { owner: (this.defaultNewAcl && this.defaultNewAcl.owner) || utils.CONSTS.SYSTEM_ADMIN_USER, ownerGroup: (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) || utils.CONSTS.SYSTEM_ADMIN_GROUP, permissions: (this.defaultNewAcl && this.defaultNewAcl.file.permissions) || utils.CONSTS.ACCESS_USER_ALL | utils.CONSTS.ACCESS_GROUP_ALL | utils.CONSTS.ACCESS_EVERY_RW, // 776 }; } if (this.fileOptions[id][name] !== null && this.fileOptions[id][name] !== undefined) { if (!this.fileOptions[id][name].mimeType) { const _ext = path.extname(name); const _mimeType = utils.getMimeType(_ext); this.fileOptions[id][name].mimeType = _mimeType.mimeType; } return { fileContent: this.files[id][name], fileMime: this.fileOptions[id][name].mimeType, }; } } catch (e) { this.log.warn(`${this.namespace} Cannot read file ${id} / ${name}: ${e.message}`); throw e; } throw new Error(utils.ERRORS.ERROR_NOT_FOUND); } /** * Check if given object exists * * @param id id of the object * @returns if the object exists */ // needed by server _objectExists(id) { if (!id || typeof id !== 'string') { throw new Error(`invalid id ${JSON.stringify(id)}`); } try { // check if the id exists return Object.prototype.hasOwnProperty.call(this.dataset, id); } catch (e) { this.log.error(`${this.namespace} Cannot check object existence of "${id}": ${e.message}`); throw new Error(`Cannot check object existence of "${id}": ${e.message}`); } } /** * Check if given file exists * * @param id id of the namespace * @param [name] name of the file * @returns */ // needed by server _fileExists(id, name) { if (typeof name !== 'string') { name = ''; } const location = path.join(this.objectsDir, id, name); try { const stat = fs.statSync(location); return stat.isFile(); } catch (e) { if (e.code !== 'ENOENT') { this.log.error(`${this.namespace} Cannot check file existence of "${location}": ${e.message}`); throw new Error(`Cannot check file existence of "${location}": ${e.message}`); } return false; } } /** * Check if given directory exists * * @param id id of the namespace * @param [name] name of the directory * @returns */ // special functionality only for Server (used together with SyncFileDirectory) dirExists(id, name) { if (typeof name !== 'string') { name = ''; } const location = path.join(this.objectsDir, id, name); try { const stat = fs.statSync(location); return stat.isDirectory(); } catch (e) { if (e.code !== 'ENOENT') { this.log.error(`${this.namespace} Cannot check directory existence of "${location}": ${e.message}`); throw new Error(`Cannot check directory existence of "${location}": ${e.message}`); } return false; } } // needed by server _unlink(id, name) { const _path = utils.sanitizePath(id, name); id = _path.id; name = _path.name; this._loadFileSettings(id); const location = path.join(this.objectsDir, id, name); if (fs.existsSync(location)) { const stat = fs.statSync(location); if (stat.isDirectory()) { // read all entries and delete every one fs.readdirSync(location).forEach(dir => this._unlink(id, `${name}/${dir}`)); this.log.debug(`Delete directory ${path.join(id, name)}`); try { fs.removeSync(location); } catch (e) { this.log.error(`${this.namespace} Cannot delete directory "${path.join(id, name)}": ${e.message}`); throw e; } if (this.fileOptions[id]) { delete this.fileOptions[id]; } if (this.files[id] && this.files[id]) { delete this.files[id]; } } else { this.log.debug(`Delete file ${path.join(id, name)}`); try { fs.removeSync(location); } catch (e) { this.log.error(`${this.namespace} Cannot delete file "${path.join(id, name)}": ${e.message}`); throw e; } if (this.fileOptions[id][name]) { delete this.fileOptions[id][name]; } if (this.files[id] && this.files[id][name]) { delete this.files[id][name]; } // Store dir description this._saveFileSettings(id, true); } setImmediate((id, name) => { // publish event in states this.log.silly(`${this.namespace} memory publish ${id} ${JSON.stringify({ name, file: true, size: null })}`); this.publishAll('files', `${id}$%$${name}`, null); }, id, name); } } // needed by server _readDir(id, name, options) { if (options && options.acl) { options.acl = null; } if ((id === '' || id === '/' || id === '*') && (name === '' || name === '*')) { // read root of xxx-data/files } else { const _path = utils.sanitizePath(id, name); id = _path.id; name = _path.name; } options = options || {}; // Find all files and directories starts with name const _files = []; if (id && id === '*') { id = ''; } if (name && name[name.length - 1] !== '/') { name += '/'; } this._loadFileSettings(id); const len = name ? name.length : 0; for (const f of Object.keys(this.fileOptions[id])) { if (!name || f.substring(0, len) === name) { let rest = f.substring(len); rest = rest.split('/', 2); if (rest[0] && _files.indexOf(rest[0]) === -1) { _files.push(rest[0]); } } } const location = path.join(this.objectsDir, id, name); if (fs.existsSync(location) && fs.statSync(location).isDirectory()) { const dirFiles = fs.readdirSync(location); for (let i = 0; i < dirFiles.length; i++) { if (dirFiles[i] === '..' || dirFiles[i] === '.') { continue; } if (dirFiles[i] !== '_data.json' && _files.indexOf(dirFiles[i]) === -1) { _files.push(dirFiles[i]); } } } else { throw new Error(utils.ERRORS.ERROR_NOT_FOUND); } _files.sort(); const res = []; for (const file of _files) { if (file === '..' || file === '.') { continue; } if (fs.existsSync(path.join(location, file))) { try { const stats = fs.statSync(path.join(location, file)); const acl = this.fileOptions[id][name + file] && this.fileOptions[id][name + file].acl ? deepClone(this.fileOptions[id][name + file].acl) // copy settings : { read: true, write: true, owner: (this.defaultNewAcl && this.defaultNewAcl.owner) || utils.CONSTS.SYSTEM_ADMIN_USER, ownerGroup: (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) || utils.CONSTS.SYSTEM_ADMIN_GROUP, permissions: (this.defaultNewAcl && this.defaultNewAcl.file.permissions) || utils.CONSTS.ACCESS_USER_RW | utils.CONSTS.ACCESS_GROUP_READ | utils.CONSTS.ACCESS_EVERY_READ, }; // if filter for user if (options.filter && acl) { // If user may not write if (!options.acl.file.write) { // write acl.permissions &= ~(utils.CONSTS.ACCESS_USER_WRITE | utils.CONSTS.ACCESS_GROUP_WRITE | utils.CONSTS.ACCESS_EVERY_WRITE); } // If user may not read if (!options.acl.file.read) { // read acl.permissions &= ~(utils.CONSTS.ACCESS_USER_READ | utils.CONSTS.ACCESS_GROUP_READ | utils.CONSTS.ACCESS_EVERY_READ); } if (options.user !== utils.CONSTS.SYSTEM_ADMIN_USER && options.groups.includes(utils.CONSTS.SYSTEM_ADMIN_GROUP)) { if (acl.owner !== options.user) { // Check if the user is in the group if (options.groups.includes(acl.ownerGroup)) { // Check group rights if (!(acl.permissions & utils.CONSTS.ACCESS_GROUP_RW)) { continue; } acl.read = !!(acl.permissions & utils.CONSTS.ACCESS_GROUP_READ); acl.write = !!(acl.permissions & utils.CONSTS.ACCESS_GROUP_WRITE); } else { // everybody if (!(acl.permissions & utils.CONSTS.ACCESS_EVERY_RW)) { continue; } acl.read = !!(acl.permissions & utils.CONSTS.ACCESS_EVERY_READ); acl.write = !!(acl.permissions & utils.CONSTS.ACCESS_EVERY_WRITE); } } else { // Check user rights if (!(acl.permissions & utils.CONSTS.ACCESS_USER_RW)) { continue; } acl.read = !!(acl.permissions & utils.CONSTS.ACCESS_USER_READ); acl.write = !!(acl.permissions & utils.CONSTS.ACCESS_USER_WRITE); } } else { acl.read = true; acl.write = true; } } res.push({ file, stats: stats, isDir: stats.isDirectory(), acl: acl, modifiedAt: this.fileOptions[id][name + file] ? this.fileOptions[id][name + file].modifiedAt : undefined, createdAt: this.fileOptions[id][name + file] ? this.fileOptions[id][name + file].createdAt : undefined, }); } catch (e) { this.log.error(`${this.namespace} Cannot read permissions of ${path.join(this.objectsDir, id, name, file)}: ${e.message}`); } } } return res; } // needed by server _rename(id, oldName, newName) { const _path = utils.sanitizePath(id, oldName); id = _path.id; oldName = _path.name; if (newName[0] === '/') { newName = newName.substring(1); } this._loadFileSettings(id); if (fs.existsSync(path.join(this.objectsDir, id, oldName))) { fs.renameSync(path.join(this.objectsDir, id, oldName), path.join(this.objectsDir, id, newName)); } else { throw new Error(utils.ERRORS.ERROR_NOT_FOUND); } Object.keys(this.fileOptions[id]).forEach(name => { const type = this.fileOptions[id][name]; if (name.startsWith(oldName)) { delete this.fileOptions[id][name]; this.fileOptions[id][name.replace(oldName, newName)] = type; } }); Object.keys(this.files[id]).forEach(name => { const data = this.files[id][name]; if (name.startsWith(oldName)) { delete this.files[id][name]; this.files[id][name.replace(oldName, newName)] = data; } }); this._saveFileSettings(id, true); } // internal functionality _clone(obj) { if (obj === null || obj === undefined || !tools.isObject(obj)) { return obj; } const temp = obj.constructor(); // changed for (const key of Object.keys(obj)) { temp[key] = this._clone(obj[key]); } return temp; } _subscribeMeta(client, pattern) { this.handleSubscribe(client, 'meta', pattern); } // needed by server _subscribeConfigForClient(client, pattern) { this.handleSubscribe(client, 'objects', pattern); } // needed by server _unsubscribeConfigForClient(client, pattern) { this.handleUnsubscribe(client, 'objects', pattern); // ignore options => unsubscribe may everyone } // needed by server _subscribeFileForClient(client, id, pattern) { if (Array.isArray(pattern)) { pattern.forEach(pattern => this.handleSubscribe(client, 'files', `${id}$%$${pattern}`)); } else { this.handleSubscribe(client, 'files', `${id}$%$${pattern}`); } } // needed by server _unsubscribeFileForClient(client, id, pattern) { if (Array.isArray(pattern)) { pattern.forEach(pattern => this.handleUnsubscribe(client, 'files', `${id}$%$${pattern}`)); } else { this.handleUnsubscribe(client, 'files', `${id}$%$${pattern}`); } } // needed by server _getObject(id) { return this.dataset[id]; } // needed by server _getKeys(pattern) { const r = new RegExp(tools.pattern2RegEx(pattern)); const result = Object.keys(this.dataset).filter(id => r.test(id) && id !== this.META_ID); result.sort(); return result; } // needed by server _getObjects(keys) { if (!keys) { throw new Error('no keys'); } return keys.map(id => this.dataset[id]); } _ensureMetaDict() { let meta = this.dataset[this.META_ID]; if (!meta) { meta = {}; this.dataset[this.META_ID] = meta; } return meta; } /** * Get value of given meta id * * @param id * @returns */ getMeta(id) { const meta = this._ensureMetaDict(); return meta[id]; } /** * Sets given value to id in metaNamespace * * @param id * @param value */ setMeta(id, value) { const meta = this._ensureMetaDict(); meta[id] = value; // Make sure the object gets re-written, especially when using an external DB this.dataset[this.META_ID] = meta; setImmediate(() => { // publish event in states this.log.silly(`${this.namespace} memory publish meta ${id} ${value}`); this.publishAll('meta', id, value); }); if (!this.stateTimer) { this.stateTimer = setTimeout(() => this.saveState(), this.writeFileInterval); } } // needed by server _setObjectDirect(id, obj) { this.dataset[id] = obj; // object updated -> if type changed to meta -> cache if (obj.type === 'meta' && this.existingMetaObjects[id] === false) { this.existingMetaObjects[id] = true; } setImmediate(() => this.publishAll('objects', id, obj)); this.stateTimer = this.stateTimer || setTimeout(() => this.saveState(), this.writeFileInterval); } /** * Delete the given object from the dataset * * @param id unique id of the object */ _delObject(id) { const obj = this.dataset[id]; if (!obj) { // Not existent, so goal reached :-) return; } if (obj.common?.dontDelete) { throw new Error('Object is marked as non deletable'); } delete this.dataset[id]; // object has been deleted -> remove from cached meta if there if (this.existingMetaObjects[id]) { this.existingMetaObjects[id] = false; } setImmediate(() => this.publishAll('objects', id, null)); if (!this.stateTimer) { this.stateTimer = setTimeout(() => this.saveState(), this.writeFileInterval); } } // internal functionality _applyView(func, params) { const result = { rows: [], }; // eslint-disable-next-line @typescript-eslint/no-unused-vars function _emit_(id, obj) { result.rows.push({ id: id, value: obj }); } const f = eval(`(${func.map.replace(/emit/g, '_emit_')})`); for (const [id, obj] of Object.entries(this.dataset)) { if (params) { if (params.startkey && id < params.startkey) { continue; } if (params.endkey && id > params.endkey) { continue; } } if (obj) { try { f(obj); } catch (e) { this.log.warn(`${this.namespace} Cannot execute map: ${e.message}`); } } } // Calculate max if (func.reduce === '_stats') { let max = null; for (const row of result.rows) { if (max === null || row.value > max) { max = row.value; } } if (max !== null) { result.rows = [{ id: '_stats', value: { max: max } }]; } else { result.rows = []; } } return result; } // needed by server _getObjectView(design, search, params) { const designObj = this.dataset[`_design/${design}`]; if (!designObj) { this.log.error(`${this.namespace} Cannot find view "${design}"`); throw new Error(`Cannot find view "${design}"`); } if (!(designObj.views && designObj.views[search])) { this.log.warn(`${this.namespace} Cannot find search "${search}" in "${design}"`); throw new Error(`Cannot find search "${search}" in "${design}"`); } return this._applyView(designObj.views[search], params); } /** * Destructor of the class. Called by shutting down. */ async destroy() { await super.destroy(); this._saveFileSettings(true); if (this.stateTimer) { clearTimeout(this.stateTimer); this.stateTimer = null; } if (this.writeTimer) { clearTimeout(this.writeTimer); this.writeTimer = null; } } } //# sourceMappingURL=objectsInMemFileDB.js.map