UNPKG

@iobroker/db-objects-file

Version:

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

811 lines (810 loc) 34.1 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var objectsInMemServerRedis_exports = {}; __export(objectsInMemServerRedis_exports, { ObjectsInMemoryServer: () => ObjectsInMemoryServer }); module.exports = __toCommonJS(objectsInMemServerRedis_exports); var import_node_net = __toESM(require("node:net"), 1); var import_fs_extra = __toESM(require("fs-extra"), 1); var import_node_path = __toESM(require("node:path"), 1); var import_node_crypto = __toESM(require("node:crypto"), 1); var import_db_objects_redis = require("@iobroker/db-objects-redis"); var import_db_base = require("@iobroker/db-base"); var import_tools = require("@iobroker/js-controller-common-db/tools"); var import_js_controller_common_db = require("@iobroker/js-controller-common-db"); var import_db_base2 = require("@iobroker/db-base"); var import_objectsInMemFileDB = require("./objectsInMemFileDB.js"); class ObjectsInMemoryServer extends import_objectsInMemFileDB.ObjectsInMemoryFileDB { /** * Constructor * * @param settings State and InMem-DB settings */ constructor(settings) { super(settings); this.serverConnections = {}; this.namespaceObjects = `${this.settings.redisNamespace || settings.connection && settings.connection.redisNamespace || "cfg"}.`; this.namespaceFile = `${this.namespaceObjects}f.`; this.namespaceObj = `${this.namespaceObjects}o.`; this.namespaceSet = `${this.namespaceObjects}s.`; this.namespaceSetLen = this.namespaceSet.length; this.namespaceFileLen = this.namespaceFile.length; this.namespaceObjLen = this.namespaceObj.length; this.namespaceMeta = `${this.settings.namespaceMeta || "meta"}.`; this.namespaceMetaLen = this.namespaceMeta.length; this.knownScripts = {}; this.normalizeFileRegex1 = new RegExp("^(.*)\\$%\\$(.*)\\$%\\$(meta|data)$"); this.normalizeFileRegex2 = new RegExp("^(.*)\\$%\\$(.*)\\/?\\*$"); this.open().then(() => { return this._initRedisServer(this.settings.connection); }).then(() => { this.log.debug(`${this.namespace} ${settings.secure ? "Secure " : ""} Redis inMem-objects listening on port ${this.settings.connection.port || 9001}`); if (typeof this.settings.connected === "function") { setImmediate(() => this.settings.connected()); } }).catch((e) => { this.log.error(`${this.namespace} Cannot start inMem-objects on port ${this.settings.connection.port || 9001}: ${e.message}`); process.exit(import_js_controller_common_db.EXIT_CODES.NO_CONNECTION_TO_OBJ_DB); }); } /** * Separate Namespace from ID and return both * * @param idWithNamespace ID or Array of IDs containing a redis namespace and the real ID * @returns Object with namespace and the * ID/Array of IDs without the namespace */ _normalizeId(idWithNamespace) { let ns = this.namespaceObjects; let id = null; let name = ""; let isMeta; if (Array.isArray(idWithNamespace)) { const ids = []; idWithNamespace.forEach((el) => { const { id: id2, namespace } = this._normalizeId(el); ids.push(id2); ns = namespace; }); id = ids; } else if (typeof idWithNamespace === "string") { id = idWithNamespace; if (idWithNamespace.startsWith(this.namespaceObjects)) { let idx = -1; if (idWithNamespace.startsWith(this.namespaceObj)) { idx = this.namespaceObjLen; } else if (idWithNamespace.startsWith(this.namespaceFile)) { idx = this.namespaceFileLen; } else if (idWithNamespace.startsWith(this.namespaceSet)) { idx = this.namespaceSetLen; } if (idx !== -1) { ns = idWithNamespace.substr(0, idx); id = idWithNamespace.substr(idx); } if (ns === this.namespaceFile) { let fileIdDetails = id.match(this.normalizeFileRegex1); if (fileIdDetails) { id = fileIdDetails[1]; name = fileIdDetails[2] || ""; isMeta = fileIdDetails[3] === "meta"; } else { fileIdDetails = id.match(this.normalizeFileRegex2); if (fileIdDetails) { id = fileIdDetails[1]; name = fileIdDetails[2] || ""; isMeta = void 0; } else { name = ""; isMeta = void 0; } } } } else if (idWithNamespace.startsWith(this.namespaceMeta)) { const idx = this.namespaceMetaLen; if (idx !== -1) { ns = idWithNamespace.substr(0, idx); id = idWithNamespace.substr(idx); } } } return { id, namespace: ns, name, isMeta }; } /** * Publish a subscribed value to one of the redis connections in redis format * * @param client Instance of RedisHandler * @param type Type of subscribed key * @param id Subscribed ID * @param obj Object to publish * @returns Publish counter 0 or 1 depending on if send out or not */ publishToClients(client, type, id, obj) { if (!client._subscribe || !client._subscribe[type]) { return 0; } const s = client._subscribe[type]; const found = s.find((sub) => sub.regex.test(id)); if (found) { if (type === "meta") { this.log.silly(`${this.namespace} Redis Publish Meta ${id}=${obj}`); const sendPattern = this.namespaceMeta + found.pattern; const sendId = this.namespaceMeta + id; client.sendArray(null, ["pmessage", sendPattern, sendId, obj]); } else if (type === "files") { const objString = JSON.stringify(obj); this.log.silly(`${this.namespace} Redis Publish File ${id}=${objString}`); const sendPattern = this.namespaceFile + found.pattern; const sendId = this.namespaceFile + id; client.sendArray(null, ["pmessage", sendPattern, sendId, objString]); } else { const objString = JSON.stringify(obj); this.log.silly(`${this.namespace} Redis Publish Object ${id}=${objString}`); const sendPattern = (type === "objects" ? "" : this.namespaceObjects) + found.pattern; const sendId = (type === "objects" ? this.namespaceObj : this.namespaceObjects) + id; client.sendArray(null, ["pmessage", sendPattern, sendId, objString]); } return 1; } return 0; } /** * Generate ID for a File * * @param id ID of the File * @param name Name of the file * @param isMeta generate a META ID or a Data ID? * @returns File-ID */ getFileId(id, name, isMeta) { if (id.endsWith(".admin")) { if (name.startsWith("admin/")) { name = name.replace(/^admin\//, ""); } else if (name.match(/^iobroker.[-\d\w]\/admin\//i)) { name = name.replace(/^iobroker.[-\d\w]\/admin\//i, ""); } } return `${this.namespaceFile + id}$%$${name}${isMeta !== void 0 ? isMeta ? "$%$meta" : "$%$data" : ""}`; } /** * Register all event listeners for Handler and implement the relevant logic * * @param handler RedisHandler instance */ _socketEvents(handler) { let connectionName = null; let namespaceLog = this.namespace; handler.on("info", (_data, responseId) => { let infoString = "# Server\r\n"; infoString += "redis_version:3.0.0-iobroker\r\n"; infoString += "# Clients\r\n"; infoString += "# Memory\r\n"; infoString += "# Persistence\r\n"; infoString += "# Stats\r\n"; infoString += "# Replication\r\n"; infoString += "# CPU\r\n"; infoString += "# Cluster\r\n"; infoString += "# Keyspace\r\n"; infoString += `db0:keys=${Object.keys(this.dataset).length},expires=0,avg_ttl=98633637897`; handler.sendBulk(responseId, infoString); }); handler.on("quit", (_data, responseId) => { this.log.silly(`${namespaceLog} Redis QUIT received, close connection`); handler.sendString(responseId, "OK"); handler.close(); }); handler.on("script", (data, responseId) => { data[0] = data[0].toLowerCase(); if (data[0] === "exists") { data.shift(); const scripts = []; data.forEach((checksum) => scripts.push(this.knownScripts[checksum] ? 1 : 0)); handler.sendArray(responseId, scripts); } else if (data[0] === "load") { const shasum = import_node_crypto.default.createHash("sha1"); const buf = Buffer.from(data[1]); shasum.update(buf); const scriptChecksum = shasum.digest("hex"); const scriptDesign = data[1].match(/^-- design: ([a-z0-9A-Z-.]+)\s/m); const scriptFunc = data[1].match(/^-- func: (.+)$/m); if (scriptDesign && scriptDesign[1]) { const design = scriptDesign[1]; let search = null; const scriptSearch = data[1].match(/^-- search: ([a-z0-9A-Z-.]*)\s/m); if (scriptSearch && scriptSearch[1]) { search = scriptSearch[1]; } this.knownScripts[scriptChecksum] = { design, search }; if (this.settings.connection.enhancedLogging) { this.log.silly(`${namespaceLog} Register View LUA Script: ${scriptChecksum} = ${JSON.stringify(this.knownScripts[scriptChecksum])}`); } handler.sendBulk(responseId, scriptChecksum); } else if (scriptFunc && scriptFunc[1]) { this.knownScripts[scriptChecksum] = { func: scriptFunc[1] }; if (this.settings.connection.enhancedLogging) { this.log.silly(`${namespaceLog} Register Func LUA Script: ${scriptChecksum} = ${JSON.stringify(this.knownScripts[scriptChecksum])}`); } handler.sendBulk(responseId, scriptChecksum); } else if (data[1].includes("-- REDLOCK SCRIPT")) { this.knownScripts[scriptChecksum] = { redlock: true }; if (this.settings.connection.enhancedLogging) { this.log.silly(`${namespaceLog} Register Func LUA Script: ${scriptChecksum} = ${JSON.stringify(this.knownScripts[scriptChecksum])}`); } handler.sendBulk(responseId, scriptChecksum); } else { handler.sendError(responseId, new Error(`Unknown LUA script ${data[1]}`)); } } else { handler.sendError(responseId, new Error(`Unsupported Script command ${data[0]}`)); } }); handler.on("evalsha", (data, responseId) => { if (!this.knownScripts[data[0]]) { return void handler.sendError(responseId, new Error(`Unknown Script ${data[0]}`)); } if (this.knownScripts[data[0]].design) { const scriptDesign = this.knownScripts[data[0]].design; if (typeof data[2] === "string" && data[2].startsWith(this.namespaceObj) && data.length > 4) { let scriptSearch = this.knownScripts[data[0]].search; if (scriptDesign === "system" && !scriptSearch && data[5]) { scriptSearch = data[5]; } if (!scriptSearch) { scriptSearch = "state"; } if (this.settings.connection.enhancedLogging) { this.log.silly(`${namespaceLog} Script transformed into getObjectView: design=${scriptDesign}, search=${scriptSearch}`); } let objs; try { objs = this._getObjectView(scriptDesign, scriptSearch, { startkey: data[3], endkey: data[4], include_docs: true }); } catch (err) { return void handler.sendError(responseId, new Error(`_getObjectView Error for ${scriptDesign}/${scriptSearch}: ${err.message}`)); } const res = objs.rows.map((obj) => JSON.stringify(this.dataset[obj.value._id || obj.id])); handler.sendArray(responseId, res); } } else if (this.knownScripts[data[0]].func && data.length > 4) { const scriptFunc = { map: this.knownScripts[data[0]].func.replace("%1", data[5]) }; if (this.settings.connection.enhancedLogging) { this.log.silly(`${namespaceLog} Script transformed into _applyView: func=${scriptFunc.map}`); } const objs = this._applyView(scriptFunc, { startkey: data[3], endkey: data[4], include_docs: true }); const res = objs.rows.map((obj) => JSON.stringify(this.dataset[obj.value._id || obj.id])); return void handler.sendArray(responseId, res); } else if (this.knownScripts[data[0]].redlock) { return void handler.sendArray(responseId, [0]); } else { handler.sendError(responseId, new Error(`Unknown LUA script eval call ${JSON.stringify(data)}`)); } }); handler.on("publish", (data, responseId) => { const { id, namespace } = this._normalizeId(data[0]); if (namespace === this.namespaceObj || namespace === this.namespaceMeta || namespace === this.namespaceFile) { return void handler.sendInteger(responseId, 0); } const publishCount = this.publishAll(namespace.substr(0, namespace.length - 1), id, JSON.parse(data[1])); handler.sendInteger(responseId, publishCount); }); handler.on("mget", (data, responseId) => { if (!data || !data.length) { return void handler.sendArray(responseId, []); } const { namespace, isMeta } = this._normalizeId(data[0]); if (namespace === this.namespaceObj) { const keys = []; data.forEach((dataId) => { const { id, namespace: namespace2 } = this._normalizeId(dataId); if (namespace2 !== this.namespaceObj) { keys.push(null); this.log.warn(`${namespaceLog} Got MGET request for non Object-ID in Objects-ID chunk for ${namespace2} / ${dataId}`); return; } keys.push(id); }); let result; try { result = this._getObjects(keys); } catch (err) { return void handler.sendError(responseId, new Error(`ERROR _getObjects: ${err.message}`)); } result = result.map((el) => el ? JSON.stringify(el) : null); handler.sendArray(responseId, result); } else if (namespace === this.namespaceFile) { if (isMeta) { const response = []; data.forEach((dataId) => { const { id, namespace: namespace2, name } = this._normalizeId(dataId); if (namespace2 !== this.namespaceFile) { response.push(null); this.log.warn(`${namespaceLog} Got MGET request for non File ID in File-ID chunk for ${dataId}`); return; } this._loadFileSettings(id); if (!this.fileOptions[id] || !this.fileOptions[id][name]) { response.push(null); return; } const obj = this._clone(this.fileOptions[id][name]); try { obj.stats = import_fs_extra.default.statSync(import_node_path.default.join(this.objectsDir, id, name)); } catch (err) { if (!name.endsWith("/_data.json")) { this.log.warn(`${namespaceLog} Got MGET request for non existing file ${dataId}, err: ${err.message}`); } response.push(null); return; } response.push(JSON.stringify(obj)); }); handler.sendArray(responseId, response); } else { handler.sendError(responseId, new Error("MGET-UNSUPPORTED for file data")); } } else { handler.sendError(responseId, new Error(`MGET-UNSUPPORTED for namespace ${namespace}: Data=${JSON.stringify(data)}`)); } }); handler.on("get", (data, responseId) => { const { id, namespace, name, isMeta } = this._normalizeId(data[0]); if (namespace === this.namespaceObj) { const result = this._getObject(id); if (!result) { handler.sendNull(responseId); } else { handler.sendBulk(responseId, JSON.stringify(result)); } } else if (namespace === this.namespaceFile) { if (isMeta) { let stats; try { stats = import_fs_extra.default.statSync(import_node_path.default.join(this.objectsDir, id, name)); } catch { return void handler.sendNull(responseId); } if (stats.isDirectory()) { return void handler.sendBulk(responseId, JSON.stringify({ file: name, stats: {}, isDir: true })); } this._loadFileSettings(id); if (!this.fileOptions[id] || !this.fileOptions[id][name]) { return void handler.sendNull(responseId); } let obj = this._clone(this.fileOptions[id][name]); if (typeof obj !== "object") { obj = { mimeType: obj, acl: { owner: this.defaultNewAcl && this.defaultNewAcl.owner || import_db_objects_redis.objectsUtils.CONSTS.SYSTEM_ADMIN_USER, ownerGroup: this.defaultNewAcl && this.defaultNewAcl.ownerGroup || import_db_objects_redis.objectsUtils.CONSTS.SYSTEM_ADMIN_GROUP, permissions: this.defaultNewAcl && this.defaultNewAcl.file.permissions || import_db_objects_redis.objectsUtils.CONSTS.ACCESS_USER_ALL | import_db_objects_redis.objectsUtils.CONSTS.ACCESS_GROUP_ALL | import_db_objects_redis.objectsUtils.CONSTS.ACCESS_EVERY_ALL // 777 } }; } obj.stats = stats; handler.sendBulk(responseId, JSON.stringify(obj)); } else { let data2; try { data2 = this._readFile(id, name); } catch { return void handler.sendNull(responseId); } if (data2.fileContent === void 0 || data2.fileContent === null) { return void handler.sendNull(responseId); } let fileData = data2.fileContent; if (!Buffer.isBuffer(fileData) && import_db_base.tools.isObject(fileData)) { fileData = JSON.stringify(fileData); this.log.warn(`${namespaceLog} Data of "${id}/${name}" has invalid structure at file data request: ${fileData}`); } handler.sendBufBulk(responseId, Buffer.from(fileData)); } } else if (namespace === this.namespaceMeta) { if (id === "objects.primaryHost") { handler.sendString(this.settings.hostname); } else { const result = this.getMeta(id); if (result === void 0 || result === null) { handler.sendNull(responseId); } else { handler.sendBulk(responseId, result); } } } else { handler.sendError(responseId, new Error(`GET-UNSUPPORTED for namespace ${namespace}: Data=${JSON.stringify(data)}`)); } }); handler.on("set", (data, responseId) => { const { id, namespace, name, isMeta } = this._normalizeId(data[0]); if (namespace === this.namespaceObj) { try { const obj = JSON.parse(data[1].toString("utf-8")); this._setObjectDirect(id, obj); } catch (err) { return void handler.sendError(responseId, new Error(`ERROR setObject id=${id}: ${err.message}`)); } handler.sendString(responseId, "OK"); } else if (namespace === this.namespaceFile) { if (isMeta) { this._loadFileSettings(id); try { import_fs_extra.default.ensureDirSync(import_node_path.default.join(this.objectsDir, id, import_node_path.default.dirname(name))); if (this.fileOptions[id]) { this.fileOptions[id][name] = JSON.parse(data[1].toString("utf-8")); import_fs_extra.default.writeFileSync(import_node_path.default.join(this.objectsDir, id, "_data.json"), JSON.stringify(this.fileOptions[id])); } } catch (err) { return void handler.sendError(responseId, new Error(`ERROR writeFile-Meta id=${id}: ${err.message}`)); } handler.sendString(responseId, "OK"); } else { try { this._writeFile(id, name, data[1]); } catch (err) { return void handler.sendError(responseId, new Error(`ERROR writeFile id=${id}: ${err.message}`)); } handler.sendString(responseId, "OK"); } } else if (namespace === this.namespaceMeta) { this.setMeta(id, data[1].toString("utf-8")); handler.sendString(responseId, "OK"); } else { handler.sendError(responseId, new Error(`SET-UNSUPPORTED for namespace ${namespace}: Data=${JSON.stringify(data)}`)); } }); handler.on("rename", (data, responseId) => { const oldDetails = this._normalizeId(data[0]); const newDetails = this._normalizeId(data[1]); if (oldDetails.namespace === this.namespaceFile) { if (oldDetails.id !== newDetails.id) { return void handler.sendError(responseId, new Error("ERROR renameObject: id needs to stay the same")); } if (oldDetails.isMeta) { handler.sendString(responseId, "OK"); } else { try { this._rename(oldDetails.id, oldDetails.name, newDetails.name); } catch { return void handler.sendNull(responseId); } handler.sendString(responseId, "OK"); } } else { handler.sendError(responseId, new Error(`RENAME-UNSUPPORTED for namespace ${oldDetails.namespace}: Data=${JSON.stringify(data)}`)); } }); handler.on("del", (data, responseId) => { const { id, namespace, name, isMeta } = this._normalizeId(data[0]); if (namespace === this.namespaceObj) { try { this._delObject(id); } catch (err) { return void handler.sendError(responseId, err); } handler.sendInteger(responseId, 1); } else if (namespace === this.namespaceFile) { if (isMeta) { handler.sendString(responseId, "OK"); } else { try { this._unlink(id, name); } catch (err) { return void handler.sendError(responseId, err); } handler.sendString(responseId, "OK"); } } else { handler.sendError(responseId, new Error(`DEL-UNSUPPORTED for namespace ${namespace}: Data=${JSON.stringify(data)}`)); } }); handler.on("exists", (data, responseId) => { if (!data || !data.length) { return void handler.sendInteger(responseId, 0); } const { id, namespace, name } = this._normalizeId(data[0]); if (namespace === this.namespaceObj) { let exists; try { exists = this._objectExists(id); } catch (e) { return void handler.sendError(responseId, e); } handler.sendInteger(responseId, exists ? 1 : 0); } else if (namespace === this.namespaceFile) { let exists; try { exists = this._fileExists(id, name); } catch (e) { return void handler.sendError(responseId, e); } handler.sendInteger(responseId, exists ? 1 : 0); } else if (namespace === this.namespaceSet) { return void handler.sendInteger(responseId, 1); } else { handler.sendError(responseId, new Error(`EXISTS-UNSUPPORTED for namespace ${namespace}`)); } }); handler.on("scan", (data, responseId) => { if (!data || data.length < 3) { return void handler.sendArray(responseId, ["0", []]); } return this._handleScanOrKeys(handler, data[2], responseId, true); }); handler.on("keys", (data, responseId) => { if (!data || !data.length) { return void handler.sendArray(responseId, []); } return this._handleScanOrKeys(handler, data[0], responseId); }); handler.on("sadd", (data, responseId) => { return void handler.sendInteger(responseId, 1); }); handler.on("srem", (data, responseId) => { return void handler.sendInteger(responseId, 1); }); handler.on("eval", (data, responseId) => { return void handler.sendNull(responseId); }); handler.on("sscan", (data, responseId) => { if (!data || data.length < 4) { return void handler.sendArray(responseId, ["0", []]); } return this._handleScanOrKeys(handler, data[3], responseId, true); }); handler.on("psubscribe", (data, responseId) => { const { id, namespace, name } = this._normalizeId(data[0]); if (namespace === this.namespaceObj) { this._subscribeConfigForClient(handler, id); handler.sendArray(responseId, ["psubscribe", data[0], 1]); } else if (namespace === this.namespaceMeta) { this._subscribeMeta(handler, id); handler.sendArray(responseId, ["psubscribe", data[0], 1]); } else if (namespace === this.namespaceFile) { this._subscribeFileForClient(handler, id, name); handler.sendArray(responseId, ["psubscribe", data[0], 1]); } else { handler.sendError(responseId, new Error(`PSUBSCRIBE-UNSUPPORTED for namespace ${namespace}: Data=${JSON.stringify(data)}`)); } }); handler.on("punsubscribe", (data, responseId) => { const { id, namespace, name } = this._normalizeId(data[0]); if (namespace === this.namespaceObj) { this._unsubscribeConfigForClient(handler, id); handler.sendArray(responseId, ["punsubscribe", data[0], 1]); } else if (namespace === this.namespaceFile) { this._unsubscribeFileForClient(handler, id, name); handler.sendArray(responseId, ["punsubscribe", data[0], 1]); } else { handler.sendError(responseId, new Error(`PUNSUBSCRIBE-UNSUPPORTED for namespace ${namespace}: Data=${JSON.stringify(data)}`)); } }); handler.on("subscribe", (data, responseId) => { if (data[0].startsWith("__keyevent@")) { handler.sendArray(responseId, ["subscribe", data[0], 1]); } else { handler.sendError(responseId, new Error(`SUBSCRIBE-UNSUPPORTED for ${data[0]}`)); } }); handler.on("config", (data, responseId) => { const command = typeof data[0] === "string" ? data[0].toLowerCase() : data[0].toString().toLowerCase(); if (command === "set" && data[1] === "notify-keyspace-events") { handler.sendString(responseId, "OK"); } else if (command === "set" && data[1] === "lua-time-limit") { handler.sendString(responseId, "OK"); } else { handler.sendError(responseId, new Error(`CONFIG-UNSUPPORTED for ${JSON.stringify(data)}`)); } }); handler.on("client", (data, responseId) => { if (data[0] === "setname" && typeof data[1] === "string") { if (data[1] === "") { connectionName = null; } else { connectionName = data[1]; namespaceLog = connectionName; } handler.sendString(responseId, "OK"); } else if (data[0] === "getname") { if (typeof connectionName === "string" && connectionName !== "") { handler.sendString(responseId, connectionName); } else { handler.sendNull(responseId); } } else { handler.sendError(responseId, new Error(`CLIENT-UNSUPPORTED for ${JSON.stringify(data)}`)); } }); handler.on("error", (err) => this.log.warn(`${namespaceLog} Redis objects: ${err}`)); } /** * Return connected RedisHandlers/Connections * * @returns */ getClients() { return this.serverConnections; } /** * Destructor of the class. Called by shutting down. */ async destroy() { if (this.server) { Object.keys(this.serverConnections).forEach((s) => { this.serverConnections[s].close(); delete this.serverConnections[s]; }); await new Promise((resolve) => { if (!this.server) { return void resolve(); } try { this.server.close(() => resolve()); } catch (e) { console.log(e.message); resolve(); } }); } await super.destroy(); } /** * Get keys matching pattern and send it to given responseId, for "SCAN" and "KEYS" - Objects and files supported * * @param handler RedisHandler instance * @param pattern - pattern without namespace prefix * @param responseId - Id where response will be sent to * @param isScan - if used by "SCAN" this flag should be true */ _handleScanOrKeys(handler, pattern, responseId, isScan = false) { const { id, namespace, name, isMeta } = this._normalizeId(pattern); let response = []; if (namespace === this.namespaceObj || namespace === this.namespaceObjects) { try { response = this._getKeys(id).map((val) => this.namespaceObj + val); } catch (e) { return void handler.sendError(responseId, e); } if (namespace !== this.namespaceObjects) { return void handler.sendArray(responseId, isScan ? ["0", response] : response); } } if (namespace === this.namespaceFile || namespace === this.namespaceObjects) { if (isMeta === void 0) { let res; try { res = this._readDir(id, name); if (!res || !res.length) { res = [ { file: "_data.json", stats: {}, isDir: false, virtualFile: true, notExists: true } ]; } } catch (e) { if (!e.message.endsWith(import_db_objects_redis.objectsUtils.ERRORS.ERROR_NOT_FOUND)) { return void handler.sendError(responseId, new Error(`ERROR readDir id=${id}: ${e.message}`)); } res = []; } let baseName = name || ""; if (baseName.length && !baseName.endsWith("/")) { baseName += "/"; } res.forEach((arr) => { let entryId = id; if (arr.isDir) { if (entryId === "" || entryId === "*") { entryId = arr.file; arr.file = "_data.json"; } else { arr.file += "/_data.json"; } } response.push(this.getFileId(entryId, baseName + arr.file, true)); response.push(this.getFileId(entryId, baseName + arr.file, false)); }); handler.sendArray(responseId, isScan ? ["0", response] : response); } else { handler.sendArray(responseId, isScan ? ["0", []] : []); } } else if (namespace === this.namespaceSet) { handler.sendArray(responseId, isScan ? ["0", []] : []); } else { handler.sendError(responseId, new Error(`${isScan ? "SCAN" : "KEYS"}-UNSUPPORTED for namespace ${namespace}: Pattern=${pattern}`)); } } /** * Initialize RedisHandler for a new network connection * * @param socket Network socket */ _initSocket(socket) { if (this.settings.connection.enhancedLogging) { this.log.silly(`${this.namespace} Handling new Redis Objects connection`); } const options = { log: this.log, logScope: `${this.settings.namespace || ""} Objects`, handleAsBuffers: true, enhancedLogging: this.settings.connection.enhancedLogging }; const handler = new import_db_base2.RedisHandler(socket, options); this._socketEvents(handler); this.serverConnections[`${socket.remoteAddress}:${socket.remotePort}`] = handler; socket.on("close", () => { if (this.serverConnections[`${socket.remoteAddress}:${socket.remotePort}`]) { delete this.serverConnections[`${socket.remoteAddress}:${socket.remotePort}`]; } }); } /** * Initialize Redis Server * * @param settings Settings object * @returns */ _initRedisServer(settings) { return new Promise((resolve, reject) => { if (settings.secure) { reject(new Error("Secure Redis unsupported for File-DB")); } try { this.server = import_node_net.default.createServer(); this.server.on("error", (err) => this.log.info(`${this.namespace} ${settings.secure ? "Secure " : ""} Error inMem-objects listening on port ${settings.port || 9001}: ${err}`)); this.server.on("connection", (socket) => this._initSocket(socket)); this.server.listen(settings.port || 9001, settings.host === "localhost" ? (0, import_tools.getLocalAddress)() : settings.host ? settings.host : void 0, () => resolve()); } catch (err) { reject(err); } }); } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ObjectsInMemoryServer }); //# sourceMappingURL=objectsInMemServerRedis.js.map