@iobroker/db-objects-file 4.0.0-alpha.9-20211115-5dac659e → 4.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Objects DB in memory - Server with Redis protocol
3
3
  *
4
- * Copyright 2013-2021 bluefox <dogafox@gmail.com>
4
+ * Copyright 2013-2022 bluefox <dogafox@gmail.com>
5
5
  *
6
6
  * MIT License
7
7
  *
@@ -13,14 +13,14 @@
13
13
  /* jshint strict:false */
14
14
  /* jslint node: true */
15
15
  'use strict';
16
- const net = require('net');
17
- const fs = require('fs-extra');
18
- const path = require('path');
16
+ const net = require('net');
17
+ const fs = require('fs-extra');
18
+ const path = require('path');
19
19
  const crypto = require('crypto');
20
- const utils = require('@iobroker/db-objects-redis').objectsUtils;
21
- const tools = require('@iobroker/db-base').tools;
20
+ const utils = require('@iobroker/db-objects-redis').objectsUtils;
21
+ const tools = require('@iobroker/db-base').tools;
22
22
 
23
- const RedisHandler = require('@iobroker/db-base').redisHandler;
23
+ const RedisHandler = require('@iobroker/db-base').redisHandler;
24
24
  const ObjectsInMemoryFileDB = require('./objectsInMemFileDB');
25
25
 
26
26
  // settings = {
@@ -57,30 +57,47 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
57
57
  super(settings);
58
58
 
59
59
  this.serverConnections = {};
60
- this.namespaceObjects = (this.settings.redisNamespace || (settings.connection && settings.connection.redisNamespace) || 'cfg') + '.';
61
- this.namespaceFile = this.namespaceObjects + 'f.';
62
- this.namespaceObj = this.namespaceObjects + 'o.';
60
+ this.namespaceObjects =
61
+ (this.settings.redisNamespace || (settings.connection && settings.connection.redisNamespace) || 'cfg') +
62
+ '.';
63
+ this.namespaceFile = this.namespaceObjects + 'f.';
64
+ this.namespaceObj = this.namespaceObjects + 'o.';
65
+ this.namespaceSet = this.namespaceObjects + 's.';
66
+ this.namespaceSetLen = this.namespaceSet.length;
63
67
  // this.namespaceObjectsLen = this.namespaceObjects.length;
64
- this.namespaceFileLen = this.namespaceFile.length;
65
- this.namespaceObjLen = this.namespaceObj.length;
68
+ this.namespaceFileLen = this.namespaceFile.length;
69
+ this.namespaceObjLen = this.namespaceObj.length;
70
+ this.metaNamespace = (this.settings.metaNamespace || 'meta') + '.';
71
+ this.metaNamespaceLen = this.metaNamespace.length;
66
72
 
67
73
  this.knownScripts = {};
68
74
 
69
75
  this.normalizeFileRegex1 = new RegExp('^(.*)\\$%\\$(.*)\\$%\\$(meta|data)$');
70
76
  this.normalizeFileRegex2 = new RegExp('^(.*)\\$%\\$(.*)\\/?\\*$');
71
77
 
72
- this.open().then(() => {
73
- return this._initRedisServer(this.settings.connection);
74
- }).then(() => {
75
- this.log.debug(this.namespace + ' ' + (settings.secure ? 'Secure ' : '') + ' Redis inMem-objects listening on port ' + (settings.port || 9001));
78
+ this.open()
79
+ .then(() => {
80
+ return this._initRedisServer(this.settings.connection);
81
+ })
82
+ .then(() => {
83
+ this.log.debug(
84
+ this.namespace +
85
+ ' ' +
86
+ (settings.secure ? 'Secure ' : '') +
87
+ ' Redis inMem-objects listening on port ' +
88
+ (settings.port || 9001)
89
+ );
76
90
 
77
- if (typeof this.settings.connected === 'function') {
78
- setImmediate(() => this.settings.connected());
79
- }
80
- }).catch(e => {
81
- this.log.error(this.namespace + ' Cannot start inMem-objects on port ' + (settings.port || 9001) + ': ' + e.message);
82
- process.exit(24); // todo: replace it with exitcode
83
- });
91
+ if (typeof this.settings.connected === 'function') {
92
+ setImmediate(() => this.settings.connected());
93
+ }
94
+ })
95
+ .catch(e => {
96
+ this.log.error(
97
+ this.namespace + ' Cannot start inMem-objects on port ' + (settings.port || 9001) + ': ' + e.message
98
+ );
99
+ process.exit(24); // todo: replace it with exitcode
100
+ });
84
101
  }
85
102
 
86
103
  /**
@@ -98,7 +115,7 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
98
115
  if (Array.isArray(idWithNamespace)) {
99
116
  const ids = [];
100
117
  idWithNamespace.forEach(el => {
101
- const {id, namespace} = this._normalizeId(el);
118
+ const { id, namespace } = this._normalizeId(el);
102
119
  ids.push(id);
103
120
  ns = namespace; // we ignore the pot. case from arrays with different namespaces
104
121
  });
@@ -111,7 +128,10 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
111
128
  idx = this.namespaceObjLen;
112
129
  } else if (idWithNamespace.startsWith(this.namespaceFile)) {
113
130
  idx = this.namespaceFileLen;
131
+ } else if (idWithNamespace.startsWith(this.namespaceSet)) {
132
+ idx = this.namespaceSetLen;
114
133
  }
134
+
115
135
  if (idx !== -1) {
116
136
  ns = idWithNamespace.substr(0, idx);
117
137
  id = idWithNamespace.substr(idx);
@@ -121,7 +141,7 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
121
141
  if (fileIdDetails) {
122
142
  id = fileIdDetails[1];
123
143
  name = fileIdDetails[2] || '';
124
- isMeta = (fileIdDetails[3] === 'meta');
144
+ isMeta = fileIdDetails[3] === 'meta';
125
145
  } else {
126
146
  fileIdDetails = id.match(this.normalizeFileRegex2);
127
147
  if (fileIdDetails) {
@@ -134,9 +154,15 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
134
154
  }
135
155
  }
136
156
  }
157
+ } else if (idWithNamespace.startsWith(this.metaNamespace)) {
158
+ const idx = this.metaNamespaceLen;
159
+ if (idx !== -1) {
160
+ ns = idWithNamespace.substr(0, idx);
161
+ id = idWithNamespace.substr(idx);
162
+ }
137
163
  }
138
164
  }
139
- return {id, namespace: ns, name, isMeta};
165
+ return { id, namespace: ns, name, isMeta };
140
166
  }
141
167
 
142
168
  /**
@@ -154,12 +180,20 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
154
180
  const s = client._subscribe[type];
155
181
 
156
182
  const found = s.find(sub => sub.regex.test(id));
183
+
157
184
  if (found) {
158
- const objString = JSON.stringify(obj);
159
- this.log.silly(this.namespace + ' Redis Publish Object ' + id + '=' + objString);
160
- const sendPattern = (type === 'objects' ? '' : this.namespaceObjects) + found.pattern;
161
- const sendId = (type === 'objects' ? this.namespaceObj : this.namespaceObjects) + id;
162
- client.sendArray(null,['pmessage', sendPattern, sendId, objString]);
185
+ if (type === 'meta') {
186
+ this.log.silly(`${this.namespace} Redis Publish Meta ${id}=${obj}`);
187
+ const sendPattern = this.metaNamespace + found.pattern;
188
+ const sendId = this.metaNamespace + id;
189
+ client.sendArray(null, ['pmessage', sendPattern, sendId, obj]);
190
+ } else {
191
+ const objString = JSON.stringify(obj);
192
+ this.log.silly(this.namespace + ' Redis Publish Object ' + id + '=' + objString);
193
+ const sendPattern = (type === 'objects' ? '' : this.namespaceObjects) + found.pattern;
194
+ const sendId = (type === 'objects' ? this.namespaceObj : this.namespaceObjects) + id;
195
+ client.sendArray(null, ['pmessage', sendPattern, sendId, objString]);
196
+ }
163
197
  return 1;
164
198
  }
165
199
  return 0;
@@ -174,12 +208,11 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
174
208
  */
175
209
  getFileId(id, name, isMeta) {
176
210
  // e.g. ekey.admin and admin/ekey.png
177
- if (id.match(/\.admin$/)) {
178
- if (name.match(/^admin\//)) {
211
+ if (id.endsWith('.admin')) {
212
+ if (name.startsWith('admin/')) {
179
213
  name = name.replace(/^admin\//, '');
180
- } else
181
- // e.g. ekey.admin and iobroker.ekey/admin/ekey.png
182
- if (name.match(/^iobroker.[-\d\w]\/admin\//i)) {
214
+ } else if (name.match(/^iobroker.[-\d\w]\/admin\//i)) {
215
+ // e.g. ekey.admin and iobroker.ekey/admin/ekey.png
183
216
  name = name.replace(/^iobroker.[-\d\w]\/admin\//i, '');
184
217
  }
185
218
  }
@@ -243,33 +276,52 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
243
276
  search = scriptSearch[1];
244
277
  }
245
278
 
246
- this.knownScripts[scriptChecksum] = {design: design, search: search};
279
+ this.knownScripts[scriptChecksum] = { design: design, search: search };
247
280
  if (this.settings.connection.enhancedLogging) {
248
- this.log.silly(`${namespaceLog} Register View LUA Script: ${scriptChecksum} = ${JSON.stringify(this.knownScripts[scriptChecksum])}`);
281
+ this.log.silly(
282
+ `${namespaceLog} Register View LUA Script: ${scriptChecksum} = ${JSON.stringify(
283
+ this.knownScripts[scriptChecksum]
284
+ )}`
285
+ );
249
286
  }
250
287
  handler.sendBulk(responseId, scriptChecksum);
251
288
  } else if (scriptFunc && scriptFunc[1]) {
252
- this.knownScripts[scriptChecksum] = {func: scriptFunc[1]};
289
+ this.knownScripts[scriptChecksum] = { func: scriptFunc[1] };
253
290
  if (this.settings.connection.enhancedLogging) {
254
- this.log.silly(`${namespaceLog} Register Func LUA Script: ${scriptChecksum} = ${JSON.stringify(this.knownScripts[scriptChecksum])}`);
291
+ this.log.silly(
292
+ `${namespaceLog} Register Func LUA Script: ${scriptChecksum} = ${JSON.stringify(
293
+ this.knownScripts[scriptChecksum]
294
+ )}`
295
+ );
296
+ }
297
+ handler.sendBulk(responseId, scriptChecksum);
298
+ } else if (data[1].includes('-- REDLOCK SCRIPT')) {
299
+ // redlock scripts are currently not needed for Simulator
300
+ this.knownScripts[scriptChecksum] = { redlock: true };
301
+ if (this.settings.connection.enhancedLogging) {
302
+ this.log.silly(
303
+ `${namespaceLog} Register Func LUA Script: ${scriptChecksum} = ${JSON.stringify(
304
+ this.knownScripts[scriptChecksum]
305
+ )}`
306
+ );
255
307
  }
256
308
  handler.sendBulk(responseId, scriptChecksum);
257
309
  } else {
258
- handler.sendError(responseId, new Error('Unknown LUA script ' + data[0]));
310
+ handler.sendError(responseId, new Error(`Unknown LUA script ${data[1]}`));
259
311
  }
260
312
  } else {
261
- handler.sendError(responseId, new Error('Unsupported Script command ' + data[0]));
313
+ handler.sendError(responseId, new Error(`Unsupported Script command ${data[0]}`));
262
314
  }
263
315
  });
264
316
 
265
317
  // Handle Redis "EVALSHA" request
266
318
  handler.on('evalsha', (data, responseId) => {
267
319
  if (!this.knownScripts[data[0]]) {
268
- return void handler.sendError(responseId, new Error('Unknown Script ' + data[0]));
320
+ return void handler.sendError(responseId, new Error(`Unknown Script ${data[0]}`));
269
321
  }
270
322
  if (this.knownScripts[data[0]].design) {
271
323
  const scriptDesign = this.knownScripts[data[0]].design;
272
- if (data[2] === this.namespaceObj && data.length > 4) {
324
+ if (typeof data[2] === 'string' && data[2].startsWith(this.namespaceObj) && data.length > 4) {
273
325
  let scriptSearch = this.knownScripts[data[0]].search;
274
326
  if (scriptDesign === 'system' && !scriptSearch && data[5]) {
275
327
  scriptSearch = data[5];
@@ -278,7 +330,9 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
278
330
  scriptSearch = 'state';
279
331
  }
280
332
  if (this.settings.connection.enhancedLogging) {
281
- this.log.silly(`${namespaceLog} Script transformed into getObjectView: design=${scriptDesign}, search=${scriptSearch}`);
333
+ this.log.silly(
334
+ `${namespaceLog} Script transformed into getObjectView: design=${scriptDesign}, search=${scriptSearch}`
335
+ );
282
336
  }
283
337
  let objs;
284
338
  try {
@@ -288,13 +342,18 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
288
342
  include_docs: true
289
343
  });
290
344
  } catch (err) {
291
- return void handler.sendError(responseId, new Error('_getObjectView Error for ' + scriptDesign + '/' + scriptSearch + ': ' + err.message));
345
+ return void handler.sendError(
346
+ responseId,
347
+ new Error(
348
+ '_getObjectView Error for ' + scriptDesign + '/' + scriptSearch + ': ' + err.message
349
+ )
350
+ );
292
351
  }
293
352
  const res = objs.rows.map(obj => JSON.stringify(this.dataset[obj.value._id || obj.id]));
294
353
  handler.sendArray(responseId, res);
295
354
  }
296
355
  } else if (this.knownScripts[data[0]].func && data.length > 4) {
297
- const scriptFunc = {map: this.knownScripts[data[0]].func.replace('%1', data[5])};
356
+ const scriptFunc = { map: this.knownScripts[data[0]].func.replace('%1', data[5]) };
298
357
  if (this.settings.connection.enhancedLogging) {
299
358
  this.log.silly(`${namespaceLog} Script transformed into _applyView: func=${scriptFunc.map}`);
300
359
  }
@@ -306,16 +365,20 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
306
365
  const res = objs.rows.map(obj => JSON.stringify(this.dataset[obj.value._id || obj.id]));
307
366
 
308
367
  return void handler.sendArray(responseId, res);
368
+ } else if (this.knownScripts[data[0]].redlock) {
369
+ // just return a dummy
370
+ return void handler.sendArray(responseId, [0]);
309
371
  } else {
310
- handler.sendError(responseId, new Error('Unknown LUA script eval call ' + JSON.stringify(data)));
372
+ handler.sendError(responseId, new Error(`Unknown LUA script eval call ${JSON.stringify(data)}`));
311
373
  }
312
374
  });
313
375
 
314
376
  // Handle Redis "PUBLISH" request
315
377
  handler.on('publish', (data, responseId) => {
316
- const {id, namespace} = this._normalizeId(data[0]);
378
+ const { id, namespace } = this._normalizeId(data[0]);
317
379
 
318
- if (namespace === this.namespaceObj) { // a "set" always comes afterwards, so do not publish
380
+ if (namespace === this.namespaceObj || namespace === this.metaNamespace) {
381
+ // a "set" always comes afterwards, so do not publish
319
382
  return void handler.sendInteger(responseId, 0); // do not publish for now
320
383
  }
321
384
  const publishCount = this.publishAll(namespace.substr(0, namespace.length - 1), id, JSON.parse(data[1]));
@@ -327,15 +390,17 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
327
390
  if (!data || !data.length) {
328
391
  return void handler.sendArray(responseId, []);
329
392
  }
330
- const {namespace, isMeta} = this._normalizeId(data[0]);
393
+ const { namespace, isMeta } = this._normalizeId(data[0]);
331
394
 
332
395
  if (namespace === this.namespaceObj) {
333
396
  const keys = [];
334
397
  data.forEach(dataId => {
335
- const {id, namespace} = this._normalizeId(dataId);
398
+ const { id, namespace } = this._normalizeId(dataId);
336
399
  if (namespace !== this.namespaceObj) {
337
400
  keys.push(null);
338
- this.log.warn(`${namespaceLog} Got MGET request for non Object-ID in Objects-ID chunk for ${namespace} / ${dataId}`);
401
+ this.log.warn(
402
+ `${namespaceLog} Got MGET request for non Object-ID in Objects-ID chunk for ${namespace} / ${dataId}`
403
+ );
339
404
  return;
340
405
  }
341
406
  keys.push(id);
@@ -346,17 +411,19 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
346
411
  } catch (err) {
347
412
  return void handler.sendError(responseId, new Error('ERROR _getObjects: ' + err.message));
348
413
  }
349
- result = result.map(el => el ? JSON.stringify(el) : null);
414
+ result = result.map(el => (el ? JSON.stringify(el) : null));
350
415
  handler.sendArray(responseId, result);
351
416
  } else if (namespace === this.namespaceFile) {
352
417
  // Handle request for Meta data for files
353
418
  if (isMeta) {
354
419
  const response = [];
355
420
  data.forEach(dataId => {
356
- const {id, namespace, name} = this._normalizeId(dataId);
421
+ const { id, namespace, name } = this._normalizeId(dataId);
357
422
  if (namespace !== this.namespaceFile) {
358
423
  response.push(null);
359
- this.log.warn(`${namespaceLog} Got MGET request for non File ID in File-ID chunk for ${dataId}`);
424
+ this.log.warn(
425
+ `${namespaceLog} Got MGET request for non File ID in File-ID chunk for ${dataId}`
426
+ );
360
427
  return;
361
428
  }
362
429
  this._loadFileSettings(id);
@@ -370,7 +437,9 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
370
437
  obj.stats = fs.statSync(path.join(this.objectsDir, id, name));
371
438
  } catch (err) {
372
439
  if (!name.endsWith('/_data.json')) {
373
- this.log.warn(`${namespaceLog} Got MGET request for non existing file ${dataId}, err: ${err.message}`);
440
+ this.log.warn(
441
+ `${namespaceLog} Got MGET request for non existing file ${dataId}, err: ${err.message}`
442
+ );
374
443
  }
375
444
  response.push(null);
376
445
  return;
@@ -383,13 +452,16 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
383
452
  handler.sendError(responseId, new Error('MGET-UNSUPPORTED for file data'));
384
453
  }
385
454
  } else {
386
- handler.sendError(responseId, new Error('MGET-UNSUPPORTED for namespace ' + namespace + ': Data=' + JSON.stringify(data)));
455
+ handler.sendError(
456
+ responseId,
457
+ new Error('MGET-UNSUPPORTED for namespace ' + namespace + ': Data=' + JSON.stringify(data))
458
+ );
387
459
  }
388
460
  });
389
461
 
390
462
  // Handle Redis "GET" requests
391
463
  handler.on('get', (data, responseId) => {
392
- const {id, namespace, name, isMeta} = this._normalizeId(data[0]);
464
+ const { id, namespace, name, isMeta } = this._normalizeId(data[0]);
393
465
 
394
466
  if (namespace === this.namespaceObj) {
395
467
  const result = this._getObject(id);
@@ -408,11 +480,14 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
408
480
  return void handler.sendNull(responseId);
409
481
  }
410
482
  if (stats.isDirectory()) {
411
- return void handler.sendBulk(responseId, JSON.stringify({
412
- file: name,
413
- stats: {},
414
- isDir: true
415
- }));
483
+ return void handler.sendBulk(
484
+ responseId,
485
+ JSON.stringify({
486
+ file: name,
487
+ stats: {},
488
+ isDir: true
489
+ })
490
+ );
416
491
  }
417
492
  this._loadFileSettings(id);
418
493
  if (!this.fileOptions[id] || !this.fileOptions[id][name]) {
@@ -424,9 +499,16 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
424
499
  obj = {
425
500
  mimeType: obj,
426
501
  acl: {
427
- owner: (this.defaultNewAcl && this.defaultNewAcl.owner) || utils.CONSTS.SYSTEM_ADMIN_USER,
428
- ownerGroup: (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) || utils.CONSTS.SYSTEM_ADMIN_GROUP,
429
- permissions: (this.defaultNewAcl && this.defaultNewAcl.file.permissions) || (utils.CONSTS.ACCESS_USER_ALL | utils.CONSTS.ACCESS_GROUP_ALL | utils.CONSTS.ACCESS_EVERY_ALL) // 777
502
+ owner:
503
+ (this.defaultNewAcl && this.defaultNewAcl.owner) || utils.CONSTS.SYSTEM_ADMIN_USER,
504
+ ownerGroup:
505
+ (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) ||
506
+ utils.CONSTS.SYSTEM_ADMIN_GROUP,
507
+ permissions:
508
+ (this.defaultNewAcl && this.defaultNewAcl.file.permissions) ||
509
+ utils.CONSTS.ACCESS_USER_ALL |
510
+ utils.CONSTS.ACCESS_GROUP_ALL |
511
+ utils.CONSTS.ACCESS_EVERY_ALL // 777
430
512
  }
431
513
  };
432
514
  }
@@ -447,18 +529,36 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
447
529
  if (!Buffer.isBuffer(fileData) && tools.isObject(fileData)) {
448
530
  // if its an invalid object, stringify it and log warning
449
531
  fileData = JSON.stringify(fileData);
450
- this.log.warn(`${namespaceLog} Data of "${id}/${name}" has invalid structure at file data request: ${fileData}`);
532
+ this.log.warn(
533
+ `${namespaceLog} Data of "${id}/${name}" has invalid structure at file data request: ${fileData}`
534
+ );
451
535
  }
452
536
  handler.sendBufBulk(responseId, Buffer.from(fileData));
453
537
  }
538
+ } else if (namespace === this.metaNamespace) {
539
+ // special handling for the primaryHost
540
+ if (id === 'objects.primaryHost') {
541
+ // we are the server -> we are primary
542
+ handler.sendString(this.settings.hostname);
543
+ } else {
544
+ const result = this.getMeta(id);
545
+ if (result === undefined || result === null) {
546
+ handler.sendNull(responseId);
547
+ } else {
548
+ handler.sendBulk(responseId, result);
549
+ }
550
+ }
454
551
  } else {
455
- handler.sendError(responseId, new Error('GET-UNSUPPORTED for namespace ' + namespace + ': Data=' + JSON.stringify(data)));
552
+ handler.sendError(
553
+ responseId,
554
+ new Error('GET-UNSUPPORTED for namespace ' + namespace + ': Data=' + JSON.stringify(data))
555
+ );
456
556
  }
457
557
  });
458
558
 
459
559
  // Handle Redis "SET" requests
460
560
  handler.on('set', (data, responseId) => {
461
- const {id, namespace, name, isMeta} = this._normalizeId(data[0]);
561
+ const { id, namespace, name, isMeta } = this._normalizeId(data[0]);
462
562
 
463
563
  if (namespace === this.namespaceObj) {
464
564
  try {
@@ -480,10 +580,16 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
480
580
  // only set if the meta object is already/still existing
481
581
  if (this.fileOptions[id]) {
482
582
  this.fileOptions[id][name] = JSON.parse(data[1].toString('utf-8'));
483
- fs.writeFileSync(path.join(this.objectsDir, id, '_data.json'), JSON.stringify(this.fileOptions[id]));
583
+ fs.writeFileSync(
584
+ path.join(this.objectsDir, id, '_data.json'),
585
+ JSON.stringify(this.fileOptions[id])
586
+ );
484
587
  }
485
588
  } catch (err) {
486
- return void handler.sendError(responseId, new Error(`ERROR writeFile-Meta id=${id}: ${err.message}`));
589
+ return void handler.sendError(
590
+ responseId,
591
+ new Error(`ERROR writeFile-Meta id=${id}: ${err.message}`)
592
+ );
487
593
  }
488
594
  handler.sendString(responseId, 'OK');
489
595
  } else {
@@ -491,12 +597,21 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
491
597
  try {
492
598
  this._writeFile(id, name, data[1]);
493
599
  } catch (err) {
494
- return void handler.sendError(responseId, new Error(`ERROR writeFile id=${id}: ${err.message}`));
600
+ return void handler.sendError(
601
+ responseId,
602
+ new Error(`ERROR writeFile id=${id}: ${err.message}`)
603
+ );
495
604
  }
496
605
  handler.sendString(responseId, 'OK');
497
606
  }
607
+ } else if (namespace === this.metaNamespace) {
608
+ this.setMeta(id, data[1].toString('utf-8'));
609
+ handler.sendString(responseId, 'OK');
498
610
  } else {
499
- handler.sendError(responseId, new Error(`SET-UNSUPPORTED for namespace ${namespace}: Data=${JSON.stringify(data)}`));
611
+ handler.sendError(
612
+ responseId,
613
+ new Error(`SET-UNSUPPORTED for namespace ${namespace}: Data=${JSON.stringify(data)}`)
614
+ );
500
615
  }
501
616
  });
502
617
 
@@ -507,7 +622,10 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
507
622
 
508
623
  if (oldDetails.namespace === this.namespaceFile) {
509
624
  if (oldDetails.id !== newDetails.id) {
510
- return void handler.sendError(responseId, new Error('ERROR renameObject: id needs to stay the same'));
625
+ return void handler.sendError(
626
+ responseId,
627
+ new Error('ERROR renameObject: id needs to stay the same')
628
+ );
511
629
  }
512
630
 
513
631
  // Handle request for Meta data for files
@@ -523,13 +641,16 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
523
641
  handler.sendString(responseId, 'OK');
524
642
  }
525
643
  } else {
526
- handler.sendError(responseId, new Error(`RENAME-UNSUPPORTED for namespace ${oldDetails.namespace}: Data=${JSON.stringify(data)}`));
644
+ handler.sendError(
645
+ responseId,
646
+ new Error(`RENAME-UNSUPPORTED for namespace ${oldDetails.namespace}: Data=${JSON.stringify(data)}`)
647
+ );
527
648
  }
528
649
  });
529
650
 
530
651
  // Handle Redis "DEL" request for state and session namespace
531
652
  handler.on('del', (data, responseId) => {
532
- const {id, namespace, name, isMeta} = this._normalizeId(data[0]);
653
+ const { id, namespace, name, isMeta } = this._normalizeId(data[0]);
533
654
 
534
655
  if (namespace === this.namespaceObj) {
535
656
  try {
@@ -553,7 +674,10 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
553
674
  handler.sendString(responseId, 'OK');
554
675
  }
555
676
  } else {
556
- handler.sendError(responseId, new Error(`DEL-UNSUPPORTED for namespace ${namespace}: Data=${JSON.stringify(data)}`));
677
+ handler.sendError(
678
+ responseId,
679
+ new Error(`DEL-UNSUPPORTED for namespace ${namespace}: Data=${JSON.stringify(data)}`)
680
+ );
557
681
  }
558
682
  });
559
683
 
@@ -563,7 +687,7 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
563
687
  }
564
688
 
565
689
  // Note: we only simulate single key existence check
566
- const {id, namespace, name} = this._normalizeId(data[0]);
690
+ const { id, namespace, name } = this._normalizeId(data[0]);
567
691
 
568
692
  if (namespace === this.namespaceObj) {
569
693
  let exists;
@@ -581,6 +705,9 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
581
705
  return void handler.sendError(responseId, e);
582
706
  }
583
707
  handler.sendInteger(responseId, exists ? 1 : 0);
708
+ } else if (namespace === this.namespaceSet) {
709
+ // we are not using sets in simulator, so just say it exists
710
+ return void handler.sendInteger(responseId, 1);
584
711
  } else {
585
712
  handler.sendError(responseId, new Error(`EXISTS-UNSUPPORTED for namespace ${namespace}`));
586
713
  }
@@ -604,33 +731,73 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
604
731
  return this._handleScanOrKeys(handler, data[0], responseId);
605
732
  });
606
733
 
734
+ // commands for redis SETS, just dummies
735
+ handler.on('sadd', (data, responseId) => {
736
+ return void handler.sendInteger(responseId, 1);
737
+ });
738
+
739
+ handler.on('srem', (data, responseId) => {
740
+ return void handler.sendInteger(responseId, 1);
741
+ });
742
+
743
+ handler.on('sscan', (data, responseId) => {
744
+ // for file DB it does the same as scan but data looks different
745
+ if (!data || data.length < 4) {
746
+ return void handler.sendArray(responseId, ['0', []]);
747
+ }
748
+
749
+ return this._handleScanOrKeys(handler, data[3], responseId, true);
750
+ });
751
+
607
752
  // Handle Redis "PSUBSCRIBE" request for state, log and session namespace
608
753
  handler.on('psubscribe', (data, responseId) => {
609
- const {id, namespace} = this._normalizeId(data[0]);
754
+ const { id, namespace } = this._normalizeId(data[0]);
610
755
 
611
756
  if (namespace === this.namespaceObj) {
612
757
  this._subscribeConfigForClient(handler, id);
613
758
  handler.sendArray(responseId, ['psubscribe', data[0], 1]);
759
+ } else if (namespace === this.metaNamespace) {
760
+ this._subscribeMeta(handler, id);
761
+ handler.sendArray(responseId, ['psubscribe', data[0], 1]);
614
762
  } else {
615
- handler.sendError(responseId, new Error('PSUBSCRIBE-UNSUPPORTED for namespace ' + namespace + ': Data=' + JSON.stringify(data)));
763
+ handler.sendError(
764
+ responseId,
765
+ new Error('PSUBSCRIBE-UNSUPPORTED for namespace ' + namespace + ': Data=' + JSON.stringify(data))
766
+ );
616
767
  }
617
768
  });
618
769
 
619
770
  // Handle Redis "UNSUBSCRIBE" request for state, log and session namespace
620
771
  handler.on('punsubscribe', (data, responseId) => {
621
- const {id, namespace} = this._normalizeId(data[0]);
772
+ const { id, namespace } = this._normalizeId(data[0]);
622
773
 
623
774
  if (namespace === this.namespaceObj) {
624
775
  this._unsubscribeConfigForClient(handler, id);
625
776
  handler.sendArray(responseId, ['punsubscribe', data[0], 1]);
626
777
  } else {
627
- handler.sendError(responseId, new Error('PUNSUBSCRIBE-UNSUPPORTED for namespace ' + namespace + ': Data=' + JSON.stringify(data)));
778
+ handler.sendError(
779
+ responseId,
780
+ new Error('PUNSUBSCRIBE-UNSUPPORTED for namespace ' + namespace + ': Data=' + JSON.stringify(data))
781
+ );
782
+ }
783
+ });
784
+
785
+ // Handle Redis "SUBSCRIBE" ... currently mainly ignored
786
+ handler.on('subscribe', (data, responseId) => {
787
+ if (data[0].startsWith('__keyevent@')) {
788
+ // we ignore these type of events because we publish expires anyway directly
789
+ handler.sendArray(responseId, ['subscribe', data[0], 1]);
790
+ } else {
791
+ handler.sendError(responseId, new Error(`SUBSCRIBE-UNSUPPORTED for ${data[0]}`));
628
792
  }
629
793
  });
630
794
 
631
795
  // Handle Redis "CONFIG" ... currently mainly ignored
632
796
  handler.on('config', (data, responseId) => {
633
- if (data[0] === 'set' && data[1] === 'lua-time-limit') {
797
+ if (data[0] === 'set' && data[1] === 'notify-keyspace-events') {
798
+ // we ignore these type of commands for now, should only be to subscribe to keyspace events
799
+ handler.sendString(responseId, 'OK');
800
+ } else if (data[0] === 'set' && data[1] === 'lua-time-limit') {
634
801
  // we ignore these type of commands for now, irrelevant
635
802
  handler.sendString(responseId, 'OK');
636
803
  } else {
@@ -661,7 +828,7 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
661
828
  }
662
829
  });
663
830
 
664
- handler.on('error', err => this.log.warn(`${namespaceLog} Redis states: ${err}`));
831
+ handler.on('error', err => this.log.warn(`${namespaceLog} Redis objects: ${err}`));
665
832
  }
666
833
 
667
834
  /**
@@ -684,17 +851,19 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
684
851
  delete this.serverConnections[s];
685
852
  });
686
853
 
687
- return /** @type {Promise<void>} */ (new Promise(resolve => {
688
- if (!this.server) {
689
- return void resolve();
690
- }
691
- try {
692
- this.server.close(() => resolve());
693
- } catch (e) {
694
- console.log(e.message);
695
- resolve();
696
- }
697
- }));
854
+ return /** @type {Promise<void>} */ (
855
+ new Promise(resolve => {
856
+ if (!this.server) {
857
+ return void resolve();
858
+ }
859
+ try {
860
+ this.server.close(() => resolve());
861
+ } catch (e) {
862
+ console.log(e.message);
863
+ resolve();
864
+ }
865
+ })
866
+ );
698
867
  }
699
868
  }
700
869
 
@@ -708,13 +877,14 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
708
877
  * @private
709
878
  */
710
879
  _handleScanOrKeys(handler, pattern, responseId, isScan = false) {
711
- const {id, namespace, name, isMeta} = this._normalizeId(pattern);
880
+ const { id, namespace, name, isMeta } = this._normalizeId(pattern);
712
881
 
713
882
  let response = [];
714
883
  if (namespace === this.namespaceObj || namespace === this.namespaceObjects) {
715
- response = this._getKeys(id).map(val => this.namespaceObj + val);
884
+ response = this._getKeys(id).map(val => this.namespaceObj + val);
716
885
  // if scan, we send the cursor as first argument
717
- if (namespace !== this.namespaceObjects) { // When it was not the full DB namespace send out response
886
+ if (namespace !== this.namespaceObjects) {
887
+ // When it was not the full DB namespace send out response
718
888
  return void handler.sendArray(responseId, isScan ? ['0', response] : response);
719
889
  }
720
890
  }
@@ -725,13 +895,15 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
725
895
  try {
726
896
  res = this._readDir(id, name);
727
897
  if (!res || !res.length) {
728
- res = [{
729
- file: '_data.json',
730
- stats: {},
731
- isDir: false,
732
- virtualFile: true,
733
- notExists: true
734
- }];
898
+ res = [
899
+ {
900
+ file: '_data.json',
901
+ stats: {},
902
+ isDir: false,
903
+ virtualFile: true,
904
+ notExists: true
905
+ }
906
+ ];
735
907
  }
736
908
  } catch (err) {
737
909
  if (!err.message.endsWith(utils.ERRORS.ERROR_NOT_FOUND)) {
@@ -758,11 +930,17 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
758
930
  response.push(this.getFileId(entryId, baseName + arr.file, false));
759
931
  });
760
932
  handler.sendArray(responseId, isScan ? ['0', response] : response); // send out file or full db response
761
- } else { // such a request should never happen
933
+ } else {
934
+ // such a request should never happen
762
935
  handler.sendArray(responseId, isScan ? ['0', []] : []); // send out file or full db response
763
936
  }
937
+ } else if (namespace === this.namespaceSet) {
938
+ handler.sendArray(responseId, isScan ? ['0', []] : []); // send out empty array, we have no sets
764
939
  } else {
765
- handler.sendError(responseId, new Error(`${isScan ? 'SCAN' : 'KEYS'}-UNSUPPORTED for namespace ${namespace}: Pattern=${pattern}`));
940
+ handler.sendError(
941
+ responseId,
942
+ new Error(`${isScan ? 'SCAN' : 'KEYS'}-UNSUPPORTED for namespace ${namespace}: Pattern=${pattern}`)
943
+ );
766
944
  }
767
945
  }
768
946
 
@@ -806,7 +984,12 @@ class ObjectsInMemoryServer extends ObjectsInMemoryFileDB {
806
984
  try {
807
985
  this.server = net.createServer();
808
986
  this.server.on('error', err =>
809
- this.log.info(`${this.namespace} ${settings.secure ? 'Secure ' : ''} Error inMem-objects listening on port ${settings.port || 9001}: ${err}`));
987
+ this.log.info(
988
+ `${this.namespace} ${settings.secure ? 'Secure ' : ''} Error inMem-objects listening on port ${
989
+ settings.port || 9001
990
+ }: ${err}`
991
+ )
992
+ );
810
993
  this.server.on('connection', socket => this._initSocket(socket));
811
994
 
812
995
  this.server.listen(