@iobroker/db-objects-redis 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
  * Object DB in REDIS - Client
3
3
  *
4
- * Copyright (c) 2018-2021 ioBroker GmbH - All rights reserved.
4
+ * Copyright (c) 2018-2022 ioBroker GmbH - All rights reserved.
5
5
  *
6
6
  * You may not to use, modify or distribute this package in any form without explicit agreement from ioBroker GmbH.
7
7
  *
@@ -16,37 +16,31 @@
16
16
  /* jshint -W061 */
17
17
  'use strict';
18
18
 
19
- const extend = require('node.extend');
20
- const Redis = require('ioredis');
21
- const tools = require('@iobroker/db-base').tools;
22
- const fs = require('fs');
23
- const path = require('path');
24
- const crypto = require('crypto');
19
+ const extend = require('node.extend');
20
+ const Redis = require('ioredis');
21
+ const { tools } = require('@iobroker/db-base');
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+ const crypto = require('crypto');
25
25
  const { isDeepStrictEqual } = require('util');
26
- const deepClone = require('deep-clone');
27
- const utils = require('./objectsUtils.js');
28
-
29
- function initScriptFiles() {
30
- const scripts = {};
31
- try {
32
- fs.readdirSync(__dirname + '/lib/objects/lua')
33
- .forEach(name => scripts[name.replace(/.lua$/, '')] = fs.readFileSync(path.join(__dirname, 'lua', name)).toString('utf8'));
34
- } catch {
35
- // TODO
36
- }
37
- return scripts;
38
- }
39
- const scriptFiles = initScriptFiles();
26
+ const deepClone = require('deep-clone');
27
+ const utils = require('./objectsUtils.js');
28
+ const semver = require('semver');
40
29
 
41
30
  class ObjectsInRedisClient {
42
-
43
31
  constructor(settings) {
44
32
  this.settings = settings || {};
45
- this.redisNamespace = (this.settings.redisNamespace || (this.settings.connection && this.settings.connection.redisNamespace) || 'cfg') + '.';
33
+ this.redisNamespace =
34
+ (this.settings.redisNamespace ||
35
+ (this.settings.connection && this.settings.connection.redisNamespace) ||
36
+ 'cfg') + '.';
46
37
  this.fileNamespace = this.redisNamespace + 'f.';
47
38
  this.fileNamespaceL = this.fileNamespace.length;
48
39
  this.objNamespace = this.redisNamespace + 'o.';
40
+ this.setNamespace = this.redisNamespace + 's.';
41
+ this.metaNamespace = (this.settings.metaNamespace || 'meta') + '.';
49
42
  this.objNamespaceL = this.objNamespace.length;
43
+ this.supportedProtocolVersions = ['4'];
50
44
 
51
45
  this.stop = false;
52
46
  this.client = null;
@@ -55,6 +49,8 @@ class ObjectsInRedisClient {
55
49
  this.preserveSettings = ['custom', 'smartName', 'material', 'habpanel', 'mobile'];
56
50
  this.defaultNewAcl = this.settings.defaultNewAcl || null;
57
51
  this.namespace = this.settings.namespace || this.settings.hostname || '';
52
+ /** @type {string} */
53
+ this.hostname = this.settings.hostname || tools.getHostName();
58
54
  this.scripts = {};
59
55
 
60
56
  // cached meta objects for file operations
@@ -67,11 +63,42 @@ class ObjectsInRedisClient {
67
63
  }
68
64
  }
69
65
 
66
+ /**
67
+ * Checks if we are allowed to start and sets the protocol version accordingly
68
+ *
69
+ * @returns {Promise<void>}
70
+ * @private
71
+ */
72
+ async _determineProtocolVersion() {
73
+ let protoVersion;
74
+ try {
75
+ protoVersion = await this.client.get(`${this.metaNamespace}objects.protocolVersion`);
76
+ } catch (e) {
77
+ if (e.message.includes('GET-UNSUPPORTED')) {
78
+ // secondary updated and primary < 4.0
79
+ return;
80
+ }
81
+ }
82
+
83
+ if (!protoVersion) {
84
+ // if no proto version existent yet, we set ours
85
+ const highestVersion = Math.max(...this.supportedProtocolVersions);
86
+ await this.setProtocolVersion(highestVersion);
87
+ this.activeProtocolVersion = highestVersion.toString();
88
+ return;
89
+ }
90
+
91
+ // check if we can support this version
92
+ if (this.supportedProtocolVersions.includes(protoVersion)) {
93
+ this.activeProtocolVersion = protoVersion;
94
+ } else {
95
+ throw new Error(`This host does not support protocol version "${protoVersion}"`);
96
+ }
97
+ }
98
+
70
99
  connectDb() {
71
100
  this.settings.connection = this.settings.connection || {};
72
101
 
73
- const ioRegExp = new RegExp('^' + this.objNamespace.replace(/\./g, '\\.') + '[_A-Za-z0-9ÄÖÜäöüа-яА-Я]+'); // cfg.o.[_A-Za-z0-9]+
74
-
75
102
  const onChange = this.settings.change; // on change handler
76
103
  const onChangeUser = this.settings.changeUser; // on change handler for User events
77
104
 
@@ -131,11 +158,17 @@ class ObjectsInRedisClient {
131
158
  delete this.settings.connection.options.retry_max_delay;
132
159
  this.settings.connection.options.enableReadyCheck = true;
133
160
 
134
- if (this.settings.connection.port === 0) { // Port = 0 means unix socket
161
+ if (this.settings.connection.port === 0) {
162
+ // Port = 0 means unix socket
135
163
  // initiate a unix socket connection
136
164
  this.settings.connection.options.path = this.settings.connection.host;
137
- this.log.debug(this.namespace + ' Redis Objects: Use File Socket for connection: ' + this.settings.connection.options.path);
138
- } else if (Array.isArray(this.settings.connection.host)) { // Host is an array means we use a sentinel
165
+ this.log.debug(
166
+ this.namespace +
167
+ ' Redis Objects: Use File Socket for connection: ' +
168
+ this.settings.connection.options.path
169
+ );
170
+ } else if (Array.isArray(this.settings.connection.host)) {
171
+ // Host is an array means we use a sentinel
139
172
  const defaultPort = Array.isArray(this.settings.connection.port) ? null : this.settings.connection.port;
140
173
 
141
174
  this.settings.connection.options.sentinels = this.settings.connection.host.map((redisNode, idx) => ({
@@ -143,16 +176,31 @@ class ObjectsInRedisClient {
143
176
  port: defaultPort || this.settings.connection.port[idx]
144
177
  }));
145
178
 
146
- this.settings.connection.options.name = this.settings.connection.sentinelName ? this.settings.connection.sentinelName : 'mymaster';
147
- this.log.debug(this.namespace + ' Redis Objects: Use Sentinel for connection: ' + this.settings.connection.options.name + ', ' + JSON.stringify(this.settings.connection.options.sentinels));
179
+ this.settings.connection.options.name = this.settings.connection.sentinelName
180
+ ? this.settings.connection.sentinelName
181
+ : 'mymaster';
182
+ this.log.debug(
183
+ this.namespace +
184
+ ' Redis Objects: Use Sentinel for connection: ' +
185
+ this.settings.connection.options.name +
186
+ ', ' +
187
+ JSON.stringify(this.settings.connection.options.sentinels)
188
+ );
148
189
  } else {
149
190
  this.settings.connection.options.host = this.settings.connection.host;
150
191
  this.settings.connection.options.port = this.settings.connection.port;
151
- this.log.debug(this.namespace + ' Redis Objects: Use Redis connection: ' + this.settings.connection.options.host + ':' + this.settings.connection.options.port);
192
+ this.log.debug(
193
+ this.namespace +
194
+ ' Redis Objects: Use Redis connection: ' +
195
+ this.settings.connection.options.host +
196
+ ':' +
197
+ this.settings.connection.options.port
198
+ );
152
199
  }
153
200
  this.settings.connection.options.db = this.settings.connection.options.db || 0;
154
201
  this.settings.connection.options.family = this.settings.connection.options.family || 0;
155
- this.settings.connection.options.password = this.settings.connection.options.auth_pass || this.settings.connection.pass || null;
202
+ this.settings.connection.options.password =
203
+ this.settings.connection.options.auth_pass || this.settings.connection.pass || null;
156
204
 
157
205
  this.settings.connection.options.autoResubscribe = false; // We do our own resubscribe because other sometimes not work
158
206
  // REDIS does not allow whitespaces, we have some because of pid
@@ -161,7 +209,18 @@ class ObjectsInRedisClient {
161
209
  this.client = new Redis(this.settings.connection.options);
162
210
 
163
211
  this.client.on('error', error => {
164
- this.settings.connection.enhancedLogging && this.log.silly(this.namespace + ' Redis ERROR Objects: (' + ignoreErrors + '/' + this.stop + ') ' + error.message + ' / ' + error.stack);
212
+ this.settings.connection.enhancedLogging &&
213
+ this.log.silly(
214
+ this.namespace +
215
+ ' Redis ERROR Objects: (' +
216
+ ignoreErrors +
217
+ '/' +
218
+ this.stop +
219
+ ') ' +
220
+ error.message +
221
+ ' / ' +
222
+ error.stack
223
+ );
165
224
  if (this.stop) {
166
225
  return;
167
226
  }
@@ -169,30 +228,40 @@ class ObjectsInRedisClient {
169
228
  initError = true;
170
229
  // Seems we have a socket.io server
171
230
  if (!ignoreErrors && error.message.startsWith('Protocol error, got "H" as reply type byte.')) {
172
- this.log.error(this.namespace + ' Could not connect to objects database at ' + this.settings.connection.options.host + ':' + this.settings.connection.options.port + ' (invalid protocol). Please make sure the configured IP and port points to a host running JS-Controller >= 2.0. and that the port is not occupied by other software!');
231
+ this.log.error(
232
+ this.namespace +
233
+ ' Could not connect to objects database at ' +
234
+ this.settings.connection.options.host +
235
+ ':' +
236
+ this.settings.connection.options.port +
237
+ ' (invalid protocol). Please make sure the configured IP and port points to a host running JS-Controller >= 2.0. and that the port is not occupied by other software!'
238
+ );
173
239
  }
174
240
  return;
175
241
  }
176
- this.log.error(this.namespace + ' Objects database error: ' + error.message);
242
+ this.log.error(`${this.namespace} Objects database error: ${error.message}`);
177
243
  errorLogged = true;
178
244
  });
179
245
 
180
246
  this.client.on('end', () => {
181
- this.settings.connection.enhancedLogging && this.log.silly(this.namespace + ' Objects-Redis Event end (stop=' + this.stop + ')');
247
+ this.settings.connection.enhancedLogging &&
248
+ this.log.silly(`${this.namespace} Objects-Redis Event end (stop=${this.stop})`);
182
249
  ready && typeof this.settings.disconnected === 'function' && this.settings.disconnected();
183
250
  });
184
251
 
185
252
  this.client.on('connect', () => {
186
- this.settings.connection.enhancedLogging && this.log.silly(this.namespace + ' Objects-Redis Event connect (stop=' + this.stop + ')');
253
+ this.settings.connection.enhancedLogging &&
254
+ this.log.silly(`${this.namespace} Objects-Redis Event connect (stop=${this.stop})`);
187
255
  connected = true;
188
256
  if (errorLogged) {
189
- this.log.info(this.namespace + ' Objects database successfully reconnected');
257
+ this.log.info(`${this.namespace} Objects database successfully reconnected`);
190
258
  errorLogged = false;
191
259
  }
192
260
  });
193
261
 
194
262
  this.client.on('close', () => {
195
- this.settings.connection.enhancedLogging && this.log.silly(this.namespace + ' Objects-Redis Event close (stop=' + this.stop + ')');
263
+ this.settings.connection.enhancedLogging &&
264
+ this.log.silly(`${this.namespace} Objects-Redis Event close (stop=${this.stop})`);
196
265
  //if (ready && typeof this.settings.disconnected === 'function') this.settings.disconnected();
197
266
  });
198
267
 
@@ -201,10 +270,19 @@ class ObjectsInRedisClient {
201
270
  reconnectCounter++;
202
271
  }
203
272
 
204
- this.settings.connection.enhancedLogging && this.log.silly(this.namespace + ' Objects-Redis Event reconnect (reconnectCounter=' + reconnectCounter + ', stop=' + this.stop + ')');
273
+ this.settings.connection.enhancedLogging &&
274
+ this.log.silly(
275
+ `${this.namespace} Objects-Redis Event reconnect (reconnectCounter=${reconnectCounter}, stop=${this.stop})`
276
+ );
205
277
 
206
- if (reconnectCounter > 2) { // fallback logic for nodejs <10
207
- this.log.error(this.namespace + ' The DB port ' + this.settings.connection.options.port +' is occupied by something that is not a Redis protocol server. Please check other software running on this port or, if you use iobroker, make sure to update to js-controller 2.0 or higher!');
278
+ if (reconnectCounter > 2) {
279
+ // fallback logic for nodejs <10
280
+ this.log.error(
281
+ this.namespace +
282
+ ' The DB port ' +
283
+ this.settings.connection.options.port +
284
+ ' is occupied by something that is not a Redis protocol server. Please check other software running on this port or, if you use iobroker, make sure to update to js-controller 2.0 or higher!'
285
+ );
208
286
  return;
209
287
  }
210
288
  connected = false;
@@ -218,7 +296,7 @@ class ObjectsInRedisClient {
218
296
  initError = false;
219
297
  ignoreErrors = false;
220
298
 
221
- this.log.debug(this.namespace + ' Objects client ready ... initialize now');
299
+ this.log.debug(`${this.namespace} Objects client ready ... initialize now`);
222
300
  try {
223
301
  await this.client.config('set', ['lua-time-limit', 10000]); // increase LUA timeout TODO needs better fix
224
302
  } catch (e) {
@@ -228,46 +306,119 @@ class ObjectsInRedisClient {
228
306
  let initCounter = 0;
229
307
  if (!this.subSystem && typeof onChange === 'function') {
230
308
  initCounter++;
231
- this.log.debug(this.namespace + ' Objects create System PubSub Client');
309
+ this.log.debug(`${this.namespace} Objects create System PubSub Client`);
232
310
  this.subSystem = new Redis(this.settings.connection.options);
233
311
  this.subSystem.ioBrokerSubscriptions = {};
234
312
 
313
+ if (typeof this.settings.primaryHostLost === 'function') {
314
+ try {
315
+ // enable Expiry/Evicted events in server - same as states (could be same db)
316
+ await this.client.config('set', ['notify-keyspace-events', 'Exe']);
317
+ } catch (e) {
318
+ this.log.warn(
319
+ `${this.namespace} Unable to enable Expiry Keyspace events from Redis Server: ${e.message}`
320
+ );
321
+ }
322
+
323
+ this.subSystem.on('message', (channel, message) => {
324
+ if (
325
+ channel === `__keyevent@${this.settings.connection.options.db}__:expired` ||
326
+ channel === `__keyevent@${this.settings.connection.options.db}__:evicted`
327
+ ) {
328
+ this.log.silly(`${this.namespace} redis message expired/evicted ${channel}:${message}`);
329
+
330
+ if (message === `${this.metaNamespace}objects.primaryHost`) {
331
+ this.settings.primaryHostLost();
332
+ }
333
+ }
334
+ });
335
+ }
336
+
235
337
  if (typeof onChange === 'function') {
236
338
  this.subSystem.on('pmessage', (pattern, channel, message) =>
237
339
  setImmediate(() => {
238
- this.log.silly(`${this.namespace} Objects system redis pmessage ${pattern}/${channel}:${message}`);
340
+ this.log.silly(
341
+ `${this.namespace} Objects system redis pmessage ${pattern}/${channel}:${message}`
342
+ );
343
+
344
+ if (channel.startsWith(this.metaNamespace)) {
345
+ if (
346
+ channel === `${this.metaNamespace}objects.protocolVersion` &&
347
+ message !== this.activeProtocolVersion
348
+ ) {
349
+ if (typeof this.settings.disconnected === 'function') {
350
+ // protocol version has changed, restart controller
351
+ this.log.info(
352
+ `${this.namespace} Objects protocol version has changed, disconnecting!`
353
+ );
354
+ this.settings.disconnected();
355
+ }
356
+ } else if (channel === `${this.metaNamespace}objects.features.useSets`) {
357
+ const newUseSets = !!parseInt(message);
358
+ if (newUseSets !== this.useSets) {
359
+ this.log.info(
360
+ `${this.namespace} Sets ${
361
+ newUseSets ? 'activated' : 'deactivated'
362
+ }: restarting ...`
363
+ );
364
+ this.useSets = newUseSets;
365
+ // luas are no longer up to date, lets restart
366
+ if (typeof this.settings.disconnected === 'function') {
367
+ this.settings.disconnected();
368
+ }
369
+ }
370
+ }
371
+ return;
372
+ }
373
+
239
374
  try {
240
- if (ioRegExp.test(channel)) {
375
+ if (channel.startsWith(this.objNamespace) && channel.length > this.objNamespaceL) {
241
376
  const id = channel.substring(this.objNamespaceL);
242
377
  try {
243
378
  const obj = message ? JSON.parse(message) : null;
244
379
 
245
- if (id === 'system.config' &&
380
+ if (
381
+ id === 'system.config' &&
246
382
  obj &&
247
383
  obj.common &&
248
384
  obj.common.defaultNewAcl &&
249
- !isDeepStrictEqual(obj.common.defaultNewAcl, this.defaultNewAcl)) {
385
+ !isDeepStrictEqual(obj.common.defaultNewAcl, this.defaultNewAcl)
386
+ ) {
250
387
  this.defaultNewAcl = JSON.parse(JSON.stringify(obj.common.defaultNewAcl));
251
388
  this.settings.controller && this.setDefaultAcl(this.defaultNewAcl);
252
389
  }
253
390
 
254
391
  onChange(id, obj);
255
392
  } catch (e) {
256
- this.log.warn(`${this.namespace} Objects Cannot process system pmessage ${id} - ${message}: ${e.message}`);
393
+ this.log.warn(
394
+ `${this.namespace} Objects Cannot process system pmessage ${id} - ${message}: ${e.message}`
395
+ );
257
396
  this.log.warn(`${this.namespace} ${e.stack}`);
258
397
  }
259
398
  } else {
260
- this.log.warn(`${this.namespace} Objects Received unexpected system pmessage: ${channel}`);
399
+ this.log.warn(
400
+ `${this.namespace} Objects Received unexpected system pmessage: ${channel}`
401
+ );
261
402
  }
262
403
  } catch (e) {
263
- this.log.warn(this.namespace + ' Objects system pmessage ' + channel + ' ' + JSON.stringify(message) + ' ' + e.message);
264
- this.log.warn(this.namespace + ' ' + e.stack);
404
+ this.log.warn(
405
+ this.namespace +
406
+ ' Objects system pmessage ' +
407
+ channel +
408
+ ' ' +
409
+ JSON.stringify(message) +
410
+ ' ' +
411
+ e.message
412
+ );
413
+ this.log.warn(`${this.namespace} ${e.stack}`);
265
414
  }
266
- }));
415
+ })
416
+ );
267
417
  }
268
418
 
269
419
  this.subSystem.on('end', () => {
270
- this.settings.connection.enhancedLogging && this.log.silly(this.namespace + ' Objects-Redis System Event end sub (stop=' + this.stop + ')');
420
+ this.settings.connection.enhancedLogging &&
421
+ this.log.silly(`${this.namespace} Objects-Redis System Event end sub (stop=${this.stop})`);
271
422
  ready && typeof this.settings.disconnected === 'function' && this.settings.disconnected();
272
423
  });
273
424
 
@@ -275,37 +426,68 @@ class ObjectsInRedisClient {
275
426
  if (this.stop) {
276
427
  return;
277
428
  }
278
- this.settings.connection.enhancedLogging && this.log.silly(this.namespace + ' PubSub System client Objects No redis connection: ' + JSON.stringify(error));
429
+ this.settings.connection.enhancedLogging &&
430
+ this.log.silly(
431
+ this.namespace +
432
+ ' PubSub System client Objects No redis connection: ' +
433
+ JSON.stringify(error)
434
+ );
279
435
  });
280
436
 
281
437
  if (this.settings.connection.enhancedLogging) {
282
438
  this.subSystem.on('connect', () =>
283
- this.log.silly(this.namespace + ' PubSub System client Objects-Redis Event connect (stop=' + this.stop + ')'));
439
+ this.log.silly(
440
+ `${this.namespace} PubSub System client Objects-Redis Event connect (stop=${this.stop})`
441
+ )
442
+ );
284
443
 
285
444
  this.subSystem.on('close', () =>
286
- this.log.silly(this.namespace + ' PubSub System client Objects-Redis Event close (stop=' + this.stop + ')'));
445
+ this.log.silly(
446
+ `${this.namespace} PubSub System client Objects-Redis Event close (stop=${this.stop})`
447
+ )
448
+ );
287
449
 
288
450
  this.subSystem.on('reconnecting', reconnectCounter =>
289
- this.log.silly(this.namespace + ' PubSub System client Objects-Redis Event reconnect (reconnectCounter=' + reconnectCounter + ', stop=' + this.stop + ')'));
451
+ this.log.silly(
452
+ `${this.namespace} PubSub System client Objects-Redis Event reconnect (reconnectCounter=${reconnectCounter}, stop=${this.stop})`
453
+ )
454
+ );
290
455
  }
291
456
 
292
457
  this.subSystem.on('ready', async () => {
293
458
  if (--initCounter < 1) {
294
459
  if (this.settings.connection.port === 0) {
295
- this.log.debug(this.namespace + ' Objects ' + (ready ? 'system re' : '') + 'connected to redis: ' + this.settings.connection.host);
460
+ this.log.debug(
461
+ `${this.namespace} Objects ${ready ? 'system re' : ''}connected to redis: ${
462
+ this.settings.connection.host
463
+ }`
464
+ );
296
465
  } else {
297
- this.log.debug(this.namespace + ' Objects ' + (ready ? 'system re' : '') + 'connected to redis: ' + this.settings.connection.host + ':' + this.settings.connection.port);
466
+ this.log.debug(
467
+ `${this.namespace} Objects ${ready ? 'system re' : ''}connected to redis: ${
468
+ this.settings.connection.host
469
+ }:${this.settings.connection.port}`
470
+ );
298
471
  }
299
472
  !ready && typeof this.settings.connected === 'function' && this.settings.connected();
300
473
  ready = true;
301
474
  }
302
475
  // subscribe on system.config anytime because also adapters need stuff like defaultNewAcl (especially admin)
303
476
  try {
304
- this.subSystem && await this.subSystem.psubscribe(`${this.objNamespace}system.config`);
477
+ this.subSystem && (await this.subSystem.psubscribe(`${this.objNamespace}system.config`));
305
478
  } catch {
306
479
  // ignore
307
480
  }
308
481
 
482
+ // subscribe to meta changes
483
+ try {
484
+ this.subSystem && (await this.subSystem.psubscribe(`${this.metaNamespace}*`));
485
+ } catch (e) {
486
+ this.log.warn(
487
+ `${this.namespace} Unable to subscribe to meta namespace "${this.metaNamespace}" changes: ${e.message}`
488
+ );
489
+ }
490
+
309
491
  if (this.subSystem) {
310
492
  for (const sub of Object.keys(this.subSystem.ioBrokerSubscriptions)) {
311
493
  try {
@@ -320,36 +502,47 @@ class ObjectsInRedisClient {
320
502
 
321
503
  if (!this.sub && typeof onChangeUser === 'function') {
322
504
  initCounter++;
323
- this.log.debug(this.namespace + ' Objects create User PubSub Client');
505
+ this.log.debug(`${this.namespace} Objects create User PubSub Client`);
324
506
  this.sub = new Redis(this.settings.connection.options);
325
507
  this.sub.ioBrokerSubscriptions = {};
326
508
 
327
509
  this.sub.on('pmessage', (pattern, channel, message) => {
328
510
  setImmediate(() => {
329
- this.log.silly(this.namespace + ' Objects user redis pmessage ' + pattern + '/' + channel + ':' + message);
511
+ this.log.silly(
512
+ `${this.namespace} Objects user redis pmessage ${pattern}/${channel}:${message}`
513
+ );
330
514
  try {
331
- if (ioRegExp.test(channel)) {
515
+ if (channel.startsWith(this.objNamespace) && channel.length > this.objNamespaceL) {
332
516
  const id = channel.substring(this.objNamespaceL);
333
517
  try {
334
518
  const obj = message ? JSON.parse(message) : null;
335
519
 
336
520
  onChangeUser(id, obj);
337
521
  } catch (e) {
338
- this.log.warn(`${this.namespace} Objects user cannot process pmessage ${id} - ${message}: ${e.message}`);
522
+ this.log.warn(
523
+ `${this.namespace} Objects user cannot process pmessage ${id} - ${message}: ${e.message}`
524
+ );
339
525
  this.log.warn(`${this.namespace} ${e.stack}`);
340
526
  }
341
527
  } else {
342
- this.log.warn(`${this.namespace} Objects user received unexpected pmessage: ${channel}`);
528
+ this.log.warn(
529
+ `${this.namespace} Objects user received unexpected pmessage: ${channel}`
530
+ );
343
531
  }
344
532
  } catch (e) {
345
- this.log.warn(this.namespace + ' Objects user pmessage ' + channel + ' ' + JSON.stringify(message) + ' ' + e.message);
346
- this.log.warn(this.namespace + ' ' + e.stack);
533
+ this.log.warn(
534
+ `${this.namespace} Objects user pmessage ${channel} ${JSON.stringify(message)} ${
535
+ e.message
536
+ }`
537
+ );
538
+ this.log.warn(`${this.namespace} ${e.stack}`);
347
539
  }
348
540
  });
349
541
  });
350
542
 
351
543
  this.sub.on('end', () => {
352
- this.settings.connection.enhancedLogging && this.log.silly(this.namespace + ' Objects-Redis Event end user sub (stop=' + this.stop + ')');
544
+ this.settings.connection.enhancedLogging &&
545
+ this.log.silly(`${this.namespace} Objects-Redis Event end user sub (stop=${this.stop})`);
353
546
  ready && typeof this.settings.disconnected === 'function' && this.settings.disconnected();
354
547
  });
355
548
 
@@ -357,26 +550,46 @@ class ObjectsInRedisClient {
357
550
  if (this.stop) {
358
551
  return;
359
552
  }
360
- this.settings.connection.enhancedLogging && this.log.silly(this.namespace + ' PubSub user client Objects No redis connection: ' + JSON.stringify(error));
553
+ this.settings.connection.enhancedLogging &&
554
+ this.log.silly(
555
+ `${this.namespace} PubSub user client Objects No redis connection: ${JSON.stringify(error)}`
556
+ );
361
557
  });
362
558
 
363
559
  if (this.settings.connection.enhancedLogging) {
364
560
  this.sub.on('connect', () =>
365
- this.log.silly(this.namespace + ' PubSub user client Objects-Redis Event connect (stop=' + this.stop + ')'));
561
+ this.log.silly(
562
+ `${this.namespace} PubSub user client Objects-Redis Event connect (stop=${this.stop})`
563
+ )
564
+ );
366
565
 
367
566
  this.sub.on('close', () =>
368
- this.log.silly(this.namespace + ' PubSub user client Objects-Redis Event close (stop=' + this.stop + ')'));
567
+ this.log.silly(
568
+ `${this.namespace} PubSub user client Objects-Redis Event close (stop=${this.stop})`
569
+ )
570
+ );
369
571
 
370
572
  this.sub.on('reconnecting', reconnectCounter =>
371
- this.log.silly(this.namespace + ' PubSub user client Objects-Redis Event reconnect (reconnectCounter=' + reconnectCounter + ', stop=' + this.stop + ')'));
573
+ this.log.silly(
574
+ `${this.namespace} PubSub user client Objects-Redis Event reconnect (reconnectCounter=${reconnectCounter}, stop=${this.stop})`
575
+ )
576
+ );
372
577
  }
373
578
 
374
579
  this.sub.on('ready', async () => {
375
580
  if (--initCounter < 1) {
376
581
  if (this.settings.connection.port === 0) {
377
- this.log.debug(this.namespace + ' Objects ' + (ready ? 'user re' : '') + 'connected to redis: ' + this.settings.connection.host);
582
+ this.log.debug(
583
+ `${this.namespace} Objects ${ready ? 'user re' : ''}connected to redis: ${
584
+ this.settings.connection.host
585
+ }`
586
+ );
378
587
  } else {
379
- this.log.debug(this.namespace + ' Objects ' + (ready ? 'user re' : '') + 'connected to redis: ' + this.settings.connection.host + ':' + this.settings.connection.port);
588
+ this.log.debug(
589
+ `${this.namespace} Objects ${ready ? 'user re' : ''}connected to redis: ${
590
+ this.settings.connection.host
591
+ }:${this.settings.connection.port}`
592
+ );
380
593
  }
381
594
  !ready && typeof this.settings.connected === 'function' && this.settings.connected();
382
595
  ready = true;
@@ -392,21 +605,82 @@ class ObjectsInRedisClient {
392
605
  });
393
606
  }
394
607
 
395
- this.log.debug(this.namespace + ' Objects client initialize lua scripts');
608
+ if (!this.client) {
609
+ return;
610
+ }
611
+
612
+ // do this before starting with async calls ;-)
396
613
  initCounter++;
614
+
615
+ try {
616
+ // check if we are allowed to use sets
617
+ this.useSets = !!parseInt(await this.client.get(`${this.metaNamespace}objects.features.useSets`));
618
+ } catch (e) {
619
+ // if unsupported we have a legacy host
620
+ if (!e.message.includes('UNSUPPORTED')) {
621
+ throw e;
622
+ } else {
623
+ this.useSets = false;
624
+ }
625
+ }
626
+
627
+ try {
628
+ await this._determineProtocolVersion();
629
+ } catch (e) {
630
+ this.log.error(`${this.namespace} ${e.message}`);
631
+ throw new Error('Objects DB is not allowed to start in the current Multihost environment');
632
+ }
633
+
634
+ // for controller v4 we have to check if we can use the new lua scripts and set logic
635
+ // TODO: remove this backward shim if controller v4.0 is old enough
636
+ let keys = await this._getKeysViaScan(`${this.objNamespace}system.host.*`);
637
+
638
+ // filter out obvious non-host objects
639
+ const hostRegex = new RegExp(`^${this.objNamespace.replace(/\./g, '\\.')}system\\.host\\.[^.]+$`);
640
+ keys = keys.filter(id => hostRegex.test(id));
641
+ /** if false we have a host smaller 4 (no proto version for this existing) */
642
+ this.noLegacyMultihost = true;
643
+
644
+ try {
645
+ if (keys.length) {
646
+ // else no host known yet - so we are single host
647
+ const objs = await this.client.mget(keys);
648
+ for (const strObj of objs) {
649
+ const obj = JSON.parse(strObj);
650
+ if (
651
+ obj &&
652
+ obj.type === 'host' &&
653
+ obj._id !== `system.host.${this.hostname}` &&
654
+ obj.common &&
655
+ obj.common.installedVersion &&
656
+ semver.lt(obj.common.installedVersion, '4.0.0')
657
+ ) {
658
+ // one of the host has a version smaller 4, we have to use legacy db
659
+ this.noLegacyMultihost = false;
660
+ this.log.info(`${this.namespace} Sets unsupported`);
661
+ }
662
+ }
663
+ }
664
+ } catch (e) {
665
+ this.log.error(
666
+ `${this.namespace} Cannot determine Lua scripts strategy: ${e.message} ${JSON.stringify(keys)}`
667
+ );
668
+ return;
669
+ }
670
+
671
+ this.log.debug(`${this.namespace} Objects client initialize lua scripts`);
672
+
397
673
  try {
398
674
  await this.loadLuaScripts();
399
675
  } catch (err) {
400
676
  this.log.error(`${this.namespace} Cannot initialize database scripts: ${err.message}`);
401
677
  return;
402
678
  }
403
- if (!this.client) {
404
- return;
405
- }
679
+
406
680
  // init default new acl
407
681
  let obj;
408
682
  try {
409
- obj = await this.client.get(this.objNamespace + 'system.config');
683
+ obj = await this.client.get(`${this.objNamespace}system.config`);
410
684
  } catch {
411
685
  // ignore
412
686
  }
@@ -426,9 +700,23 @@ class ObjectsInRedisClient {
426
700
 
427
701
  if (--initCounter < 1) {
428
702
  if (this.settings.connection.port === 0) {
429
- this.log.debug(this.namespace + ' Objects ' + (ready ? 'client re' : '') + 'connected to redis: ' + this.settings.connection.host);
703
+ this.log.debug(
704
+ this.namespace +
705
+ ' Objects ' +
706
+ (ready ? 'client re' : '') +
707
+ 'connected to redis: ' +
708
+ this.settings.connection.host
709
+ );
430
710
  } else {
431
- this.log.debug(this.namespace + ' Objects ' + (ready ? 'client re' : '') + 'connected to redis: ' + this.settings.connection.host + ':' + this.settings.connection.port);
711
+ this.log.debug(
712
+ this.namespace +
713
+ ' Objects ' +
714
+ (ready ? 'client re' : '') +
715
+ 'connected to redis: ' +
716
+ this.settings.connection.host +
717
+ ':' +
718
+ this.settings.connection.port
719
+ );
432
720
  }
433
721
  !ready && typeof this.settings.connected === 'function' && this.settings.connected();
434
722
  ready = true;
@@ -437,7 +725,7 @@ class ObjectsInRedisClient {
437
725
  }
438
726
 
439
727
  getStatus() {
440
- return {type: 'redis', server: false};
728
+ return { type: 'redis', server: false };
441
729
  }
442
730
 
443
731
  /**
@@ -468,7 +756,7 @@ class ObjectsInRedisClient {
468
756
  // -------------- FILE FUNCTIONS -------------------------------------------
469
757
  async _setBinaryState(id, data, callback) {
470
758
  if (!this.client) {
471
- return typeof callback === 'function' && setImmediate(() => callback(utils.ERRORS.ERROR_DB_CLOSED));
759
+ return tools.maybeCallbackWithRedisError(callback, utils.ERRORS.ERROR_DB_CLOSED);
472
760
  }
473
761
  if (!Buffer.isBuffer(data)) {
474
762
  data = Buffer.from(data);
@@ -526,12 +814,11 @@ class ObjectsInRedisClient {
526
814
  getFileId(id, name, isMeta) {
527
815
  name = this.normalizeFilename(name);
528
816
  // e.g. ekey.admin and admin/ekey.png
529
- if (id.match(/\.admin$/)) {
530
- if (name.match(/^admin\//)) {
817
+ if (id.endsWith('.admin')) {
818
+ if (name.startsWith('admin/')) {
531
819
  name = name.replace(/^admin\//, '');
532
- } else
533
- // e.g. ekey.admin and iobroker.ekey/admin/ekey.png
534
- if (name.match(/^iobroker.[-\d\w]\/admin\//i)) {
820
+ } else if (name.match(/^iobroker.[-\d\w]\/admin\//i)) {
821
+ // e.g. ekey.admin and iobroker.ekey/admin/ekey.png
535
822
  name = name.replace(/^iobroker.[-\d\w]\/admin\//i, '');
536
823
  }
537
824
  }
@@ -554,15 +841,15 @@ class ObjectsInRedisClient {
554
841
  // read file settings from redis
555
842
  const fileId = this.getFileId(id, name, true);
556
843
  if (!fileId) {
557
- const fileOptions = {'notExists': true};
844
+ const fileOptions = { notExists: true };
558
845
  if (utils.checkFile(fileOptions, options, flag, this.defaultNewAcl)) {
559
- return typeof callback === 'function' && setImmediate(() => callback(false, options, fileOptions)); // NO error
846
+ return tools.maybeCallback(callback, false, options, fileOptions); // NO error
560
847
  } else {
561
- return typeof callback === 'function' && setImmediate(() => callback(true, options)); // error
848
+ return tools.maybeCallback(callback, true, options); // error
562
849
  }
563
850
  }
564
851
  if (!this.client) {
565
- return typeof callback === 'function' && setImmediate(() => callback(utils.ERRORS.ERROR_DB_CLOSED, options));
852
+ return tools.maybeCallbackWithRedisError(callback, utils.ERRORS.ERROR_DB_CLOSED, options);
566
853
  }
567
854
  let fileOptions;
568
855
  try {
@@ -575,7 +862,7 @@ class ObjectsInRedisClient {
575
862
  fileOptions = JSON.parse(fileOptions);
576
863
  } catch {
577
864
  this.log.error(`${this.namespace} Cannot parse JSON ${id}: ${fileOptions}`);
578
- fileOptions = {notExists: true};
865
+ fileOptions = { notExists: true };
579
866
  }
580
867
  if (utils.checkFile(fileOptions, options, flag, this.defaultNewAcl)) {
581
868
  return typeof callback === 'function' && callback(false, options, fileOptions); // NO error
@@ -594,12 +881,9 @@ class ObjectsInRedisClient {
594
881
  this.getObject(id, (err, obj) => {
595
882
  if (obj && !obj.acl) {
596
883
  obj.acl = defaultAcl;
597
- this.setObject(id, obj, null, () =>
598
- setImmediate(() =>
599
- this._setDefaultAcl(ids, defaultAcl)));
884
+ this.setObject(id, obj, null, () => setImmediate(() => this._setDefaultAcl(ids, defaultAcl)));
600
885
  } else {
601
- setImmediate(() =>
602
- this._setDefaultAcl(ids, defaultAcl));
886
+ setImmediate(() => this._setDefaultAcl(ids, defaultAcl));
603
887
  }
604
888
  });
605
889
  }
@@ -622,7 +906,7 @@ class ObjectsInRedisClient {
622
906
  if (error) {
623
907
  this.log.error(`${this.namespace} ${error}`);
624
908
  }
625
- return tools.maybeCallbackWithError(callback, user, userGroups, userAcl);
909
+ return tools.maybeCallback(callback, user, userGroups, userAcl);
626
910
  });
627
911
  }
628
912
 
@@ -631,10 +915,10 @@ class ObjectsInRedisClient {
631
915
  }
632
916
 
633
917
  async _writeFile(id, name, data, options, callback, meta) {
634
- const ext = name.match(/\.[^.]+$/);
635
- const mime = utils.getMimeType(ext);
636
- const _mimeType = mime.mimeType;
637
- const isBinary = mime.isBinary;
918
+ const ext = name.match(/\.[^.]+$/);
919
+ const mime = utils.getMimeType(ext);
920
+ const _mimeType = mime.mimeType;
921
+ const isBinary = mime.isBinary;
638
922
 
639
923
  const metaID = this.getFileId(id, name, true);
640
924
  if (!this.client) {
@@ -654,12 +938,18 @@ class ObjectsInRedisClient {
654
938
  }
655
939
  } else {
656
940
  if (!meta) {
657
- meta = {createdAt: Date.now()};
941
+ meta = { createdAt: Date.now() };
658
942
  }
659
943
  if (!meta.acl) {
660
944
  meta.acl = {
661
- owner: options.user || (this.defaultNewAcl && this.defaultNewAcl.owner) || utils.CONSTS.SYSTEM_ADMIN_USER,
662
- ownerGroup: options.group || (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) || utils.CONSTS.SYSTEM_ADMIN_GROUP,
945
+ owner:
946
+ options.user ||
947
+ (this.defaultNewAcl && this.defaultNewAcl.owner) ||
948
+ utils.CONSTS.SYSTEM_ADMIN_USER,
949
+ ownerGroup:
950
+ options.group ||
951
+ (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) ||
952
+ utils.CONSTS.SYSTEM_ADMIN_GROUP,
663
953
  permissions: options.mode || (this.defaultNewAcl && this.defaultNewAcl.file) || 0x644
664
954
  };
665
955
  }
@@ -672,7 +962,10 @@ class ObjectsInRedisClient {
672
962
 
673
963
  meta.mimeType = options.mimeType || _mimeType;
674
964
  meta.binary = isBinary;
675
- meta.acl.ownerGroup = meta.acl.ownerGroup || (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) || utils.CONSTS.SYSTEM_ADMIN_GROUP;
965
+ meta.acl.ownerGroup =
966
+ meta.acl.ownerGroup ||
967
+ (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) ||
968
+ utils.CONSTS.SYSTEM_ADMIN_GROUP;
676
969
  meta.modifiedAt = Date.now();
677
970
 
678
971
  this._setBinaryState(this.getFileId(id, name, false), data, async err => {
@@ -692,7 +985,7 @@ class ObjectsInRedisClient {
692
985
  options = null;
693
986
  }
694
987
  if (typeof options === 'string') {
695
- options = {mimeType: options};
988
+ options = { mimeType: options };
696
989
  }
697
990
 
698
991
  if (options && options.acl) {
@@ -700,9 +993,11 @@ class ObjectsInRedisClient {
700
993
  }
701
994
 
702
995
  if (!callback) {
703
- return /** @type {Promise<void>} */ (new Promise((resolve, reject) =>
704
- this.writeFile(id, name, data, options, err =>
705
- err ? reject(err) : resolve())));
996
+ return /** @type {Promise<void>} */ (
997
+ new Promise((resolve, reject) =>
998
+ this.writeFile(id, name, data, options, err => (err ? reject(err) : resolve()))
999
+ )
1000
+ );
706
1001
  }
707
1002
 
708
1003
  try {
@@ -735,9 +1030,11 @@ class ObjectsInRedisClient {
735
1030
  }
736
1031
 
737
1032
  writeFileAsync(id, name, data, options) {
738
- return /** @type {Promise<void>} */ (new Promise((resolve, reject) =>
739
- this.writeFile(id, name, data, options, err =>
740
- err ? reject(err) : resolve())));
1033
+ return /** @type {Promise<void>} */ (
1034
+ new Promise((resolve, reject) =>
1035
+ this.writeFile(id, name, data, options, err => (err ? reject(err) : resolve()))
1036
+ )
1037
+ );
741
1038
  }
742
1039
 
743
1040
  async _readFile(id, name, options, callback, meta) {
@@ -764,7 +1061,7 @@ class ObjectsInRedisClient {
764
1061
  readFile(id, name, options, callback) {
765
1062
  if (typeof options === 'function') {
766
1063
  callback = options;
767
- options = null;
1064
+ options = null;
768
1065
  }
769
1066
  if (options && options.acl) {
770
1067
  options.acl = null;
@@ -773,7 +1070,9 @@ class ObjectsInRedisClient {
773
1070
  if (!callback) {
774
1071
  return new Promise((resolve, reject) =>
775
1072
  this.readFile(id, name, options, (err, res, mimeType) =>
776
- err ? reject(err) : resolve({data: res, mimeType: mimeType})));
1073
+ err ? reject(err) : resolve({ data: res, mimeType: mimeType })
1074
+ )
1075
+ );
777
1076
  }
778
1077
 
779
1078
  if (typeof name !== 'string' || !name.length || name === '/') {
@@ -797,7 +1096,9 @@ class ObjectsInRedisClient {
797
1096
  readFileAsync(id, name, options) {
798
1097
  return new Promise((resolve, reject) =>
799
1098
  this.readFile(id, name, options, (err, res, mimeType) =>
800
- err ? reject(err) : resolve({data: res, mimeType: mimeType})));
1099
+ err ? reject(err) : resolve({ data: res, mimeType: mimeType })
1100
+ )
1101
+ );
801
1102
  }
802
1103
 
803
1104
  /**
@@ -809,22 +1110,24 @@ class ObjectsInRedisClient {
809
1110
  */
810
1111
  async objectExists(id, options) {
811
1112
  if (!this.client) {
812
- return Promise.reject(new Error(utils.ERRORS.ERROR_DB_CLOSED));
1113
+ throw new Error(utils.ERRORS.ERROR_DB_CLOSED);
813
1114
  }
814
1115
  if (!id || typeof id !== 'string') {
815
- return Promise.reject(new Error(`invalid id ${JSON.stringify(id)}`));
1116
+ throw new Error(`invalid id ${JSON.stringify(id)}`);
816
1117
  }
817
1118
 
818
1119
  try {
819
- await /** @type {Promise<void>} */ (new Promise((resolve, reject) => {
820
- utils.checkObjectRights(this, null, null, options, utils.CONSTS.ACCESS_LIST, err => {
821
- if (err) {
822
- reject(err);
823
- } else {
824
- resolve();
825
- }
826
- });
827
- }));
1120
+ await /** @type {Promise<void>} */ (
1121
+ new Promise((resolve, reject) => {
1122
+ utils.checkObjectRights(this, null, null, options, utils.CONSTS.ACCESS_LIST, err => {
1123
+ if (err) {
1124
+ reject(err);
1125
+ } else {
1126
+ resolve();
1127
+ }
1128
+ });
1129
+ })
1130
+ );
828
1131
  const exists = await this.client.exists(this.objNamespace + id);
829
1132
  return !!exists;
830
1133
  } catch (e) {
@@ -851,15 +1154,17 @@ class ObjectsInRedisClient {
851
1154
  }
852
1155
 
853
1156
  try {
854
- await /** @type {Promise<void>} */ (new Promise((resolve, reject) => {
855
- this.checkFileRights(id, name, options, utils.CONSTS.ACCESS_READ, err => {
856
- if (err) {
857
- reject(err);
858
- } else {
859
- resolve();
860
- }
861
- });
862
- }));
1157
+ await /** @type {Promise<void>} */ (
1158
+ new Promise((resolve, reject) => {
1159
+ this.checkFileRights(id, name, options, utils.CONSTS.ACCESS_READ, err => {
1160
+ if (err) {
1161
+ reject(err);
1162
+ } else {
1163
+ resolve();
1164
+ }
1165
+ });
1166
+ })
1167
+ );
863
1168
  id = this.getFileId(id, name, false);
864
1169
  const exists = await this.client.exists(id);
865
1170
  return !!exists;
@@ -892,7 +1197,7 @@ class ObjectsInRedisClient {
892
1197
  unlink(id, name, options, callback) {
893
1198
  if (typeof options === 'function') {
894
1199
  callback = options;
895
- options = null;
1200
+ options = null;
896
1201
  }
897
1202
  if (options && options.acl) {
898
1203
  options.acl = null;
@@ -920,9 +1225,9 @@ class ObjectsInRedisClient {
920
1225
  }
921
1226
 
922
1227
  unlinkAsync(id, name, options) {
923
- return /** @type {Promise<void>} */ (new Promise((resolve, reject) =>
924
- this.unlink(id, name, options, err =>
925
- err ? reject(err) : resolve())));
1228
+ return /** @type {Promise<void>} */ (
1229
+ new Promise((resolve, reject) => this.unlink(id, name, options, err => (err ? reject(err) : resolve())))
1230
+ );
926
1231
  }
927
1232
 
928
1233
  delFile(id, name, options, callback) {
@@ -938,7 +1243,8 @@ class ObjectsInRedisClient {
938
1243
  if (!this.client) {
939
1244
  return tools.maybeCallbackWithError(callback, utils.ERRORS.ERROR_DB_CLOSED);
940
1245
  }
941
- if (id === '') { // special case for "root"
1246
+ if (id === '') {
1247
+ // special case for "root"
942
1248
  const dirID = this.getFileId('*', '*');
943
1249
 
944
1250
  let keys;
@@ -993,21 +1299,19 @@ class ObjectsInRedisClient {
993
1299
  if (!keys || !keys.length) {
994
1300
  return tools.maybeCallbackWithError(callback, utils.ERRORS.ERROR_NOT_FOUND, []);
995
1301
  }
996
- keys = keys
997
- .sort()
998
- .filter(key => {
999
- if (key.match(/\$%\$meta$/)) {
1000
- const parts = key.substr(start, key.length - end).split('/');
1001
- if (parts.length === deepLevel) {
1002
- return !key.includes('/_data.json$%$') && key !== '_data.json'; // sort out "virtual" files that are used to mark directories
1003
- } else {
1004
- const dir = parts[deepLevel - 1];
1005
- if (dirs.indexOf(dir) === -1) {
1006
- dirs.push(dir);
1007
- }
1302
+ keys = keys.sort().filter(key => {
1303
+ if (key.endsWith('$%$meta')) {
1304
+ const parts = key.substr(start, key.length - end).split('/');
1305
+ if (parts.length === deepLevel) {
1306
+ return !key.includes('/_data.json$%$') && key !== '_data.json'; // sort out "virtual" files that are used to mark directories
1307
+ } else {
1308
+ const dir = parts[deepLevel - 1];
1309
+ if (dirs.indexOf(dir) === -1) {
1310
+ dirs.push(dir);
1008
1311
  }
1009
1312
  }
1010
- });
1313
+ }
1314
+ });
1011
1315
  if (!keys.length) {
1012
1316
  const result = [];
1013
1317
  while (dirs.length) {
@@ -1030,9 +1334,9 @@ class ObjectsInRedisClient {
1030
1334
 
1031
1335
  const result = [];
1032
1336
  const dontCheck =
1033
- options.user === utils.CONSTS.SYSTEM_ADMIN_USER ||
1034
- options.group !== utils.CONSTS.SYSTEM_ADMIN_GROUP ||
1035
- (options.groups && options.groups.indexOf(utils.CONSTS.SYSTEM_ADMIN_GROUP) !== -1);
1337
+ options.user === utils.CONSTS.SYSTEM_ADMIN_USER ||
1338
+ options.group !== utils.CONSTS.SYSTEM_ADMIN_GROUP ||
1339
+ (options.groups && options.groups.indexOf(utils.CONSTS.SYSTEM_ADMIN_GROUP) !== -1);
1036
1340
 
1037
1341
  objs = objs || [];
1038
1342
  for (let i = 0; i < keys.length; i++) {
@@ -1056,11 +1360,14 @@ class ObjectsInRedisClient {
1056
1360
  continue;
1057
1361
  } // virtual file, ignore
1058
1362
  objs[i].acl = objs[i].acl || {};
1059
- if (options.user !== utils.CONSTS.SYSTEM_ADMIN_USER && options.groups.indexOf(utils.CONSTS.SYSTEM_ADMIN_GROUP) === -1) {
1060
- objs[i].acl.read = !!(objs[i].acl.permissions & utils.CONSTS.ACCESS_EVERY_READ);
1363
+ if (
1364
+ options.user !== utils.CONSTS.SYSTEM_ADMIN_USER &&
1365
+ options.groups.indexOf(utils.CONSTS.SYSTEM_ADMIN_GROUP) === -1
1366
+ ) {
1367
+ objs[i].acl.read = !!(objs[i].acl.permissions & utils.CONSTS.ACCESS_EVERY_READ);
1061
1368
  objs[i].acl.write = !!(objs[i].acl.permissions & utils.CONSTS.ACCESS_EVERY_WRITE);
1062
1369
  } else {
1063
- objs[i].acl.read = true;
1370
+ objs[i].acl.read = true;
1064
1371
  objs[i].acl.write = true;
1065
1372
  }
1066
1373
  result.push({
@@ -1069,7 +1376,7 @@ class ObjectsInRedisClient {
1069
1376
  isDir: false,
1070
1377
  acl: objs[i].acl,
1071
1378
  modifiedAt: objs[i].modifiedAt,
1072
- createdAt: objs[i].createdAt
1379
+ createdAt: objs[i].createdAt
1073
1380
  });
1074
1381
  }
1075
1382
  }
@@ -1119,8 +1426,8 @@ class ObjectsInRedisClient {
1119
1426
 
1120
1427
  readDirAsync(id, name, options) {
1121
1428
  return new Promise((resolve, reject) =>
1122
- this.readDir(id, name, options, (err, res) =>
1123
- err ? reject(err) : resolve(res)));
1429
+ this.readDir(id, name, options, (err, res) => (err ? reject(err) : resolve(res)))
1430
+ );
1124
1431
  }
1125
1432
 
1126
1433
  async _renameHelper(keys, oldBase, newBase, callback) {
@@ -1133,7 +1440,10 @@ class ObjectsInRedisClient {
1133
1440
  for (const id of keys) {
1134
1441
  try {
1135
1442
  try {
1136
- await this.client.rename(id.replace(/\$%\$meta$/, '$%$data'), id.replace(oldBase, newBase).replace(/\$%\$meta$/, '$%$data'));
1443
+ await this.client.rename(
1444
+ id.replace(/\$%\$meta$/, '$%$data'),
1445
+ id.replace(oldBase, newBase).replace(/\$%\$meta$/, '$%$data')
1446
+ );
1137
1447
  } catch (e) {
1138
1448
  // _data.json is not having a data key, so ignore error
1139
1449
  if (!(id.endsWith('/_data.json$%$meta') && e.message.includes('no such key'))) {
@@ -1190,9 +1500,7 @@ class ObjectsInRedisClient {
1190
1500
  return tools.maybeCallbackWithError(callback, utils.ERRORS.ERROR_NOT_FOUND);
1191
1501
  }
1192
1502
 
1193
- keys = keys
1194
- .sort()
1195
- .filter(key => key.match(/\$%\$meta$/));
1503
+ keys = keys.sort().filter(key => key.endsWith('$%$meta'));
1196
1504
 
1197
1505
  if (!keys.length) {
1198
1506
  return tools.maybeCallbackWithError(callback, utils.ERRORS.ERROR_NOT_FOUND);
@@ -1206,9 +1514,9 @@ class ObjectsInRedisClient {
1206
1514
  }
1207
1515
  let result;
1208
1516
  const dontCheck =
1209
- options.user === utils.CONSTS.SYSTEM_ADMIN_USER ||
1210
- options.group !== utils.CONSTS.SYSTEM_ADMIN_GROUP ||
1211
- (options.groups && options.groups.indexOf(utils.CONSTS.SYSTEM_ADMIN_GROUP) !== -1);
1517
+ options.user === utils.CONSTS.SYSTEM_ADMIN_USER ||
1518
+ options.group !== utils.CONSTS.SYSTEM_ADMIN_GROUP ||
1519
+ (options.groups && options.groups.indexOf(utils.CONSTS.SYSTEM_ADMIN_GROUP) !== -1);
1212
1520
 
1213
1521
  objs = objs || [];
1214
1522
  if (!dontCheck) {
@@ -1247,8 +1555,16 @@ class ObjectsInRedisClient {
1247
1555
  if (options && options.acl) {
1248
1556
  options.acl = null;
1249
1557
  }
1250
- if (typeof oldName !== 'string' || !oldName.length || oldName === '/' || oldName === '//' ||
1251
- typeof newName !== 'string' || !newName.length || newName === '/' || newName === '//') {
1558
+ if (
1559
+ typeof oldName !== 'string' ||
1560
+ !oldName.length ||
1561
+ oldName === '/' ||
1562
+ oldName === '//' ||
1563
+ typeof newName !== 'string' ||
1564
+ !newName.length ||
1565
+ newName === '/' ||
1566
+ newName === '//'
1567
+ ) {
1252
1568
  return tools.maybeCallbackWithError(callback, utils.ERRORS.ERROR_NOT_FOUND);
1253
1569
  }
1254
1570
  if (oldName.startsWith('/')) {
@@ -1278,9 +1594,11 @@ class ObjectsInRedisClient {
1278
1594
  }
1279
1595
 
1280
1596
  renameAsync(id, oldName, newName, options) {
1281
- return /** @type {Promise<void>} */ (new Promise((resolve, reject) =>
1282
- this.rename(id, oldName, newName, options, err =>
1283
- err ? reject(err) : resolve())));
1597
+ return /** @type {Promise<void>} */ (
1598
+ new Promise((resolve, reject) =>
1599
+ this.rename(id, oldName, newName, options, err => (err ? reject(err) : resolve()))
1600
+ )
1601
+ );
1284
1602
  }
1285
1603
 
1286
1604
  async _touch(id, name, options, callback, meta) {
@@ -1327,9 +1645,9 @@ class ObjectsInRedisClient {
1327
1645
  }
1328
1646
 
1329
1647
  touchAsync(id, name, options) {
1330
- return /** @type {Promise<void>} */ (new Promise((resolve, reject) =>
1331
- this.touch(id, name, options, err =>
1332
- err ? reject(err) : resolve())));
1648
+ return /** @type {Promise<void>} */ (
1649
+ new Promise((resolve, reject) => this.touch(id, name, options, err => (err ? reject(err) : resolve())))
1650
+ );
1333
1651
  }
1334
1652
 
1335
1653
  async _rmHelper(keys, callback) {
@@ -1364,7 +1682,7 @@ class ObjectsInRedisClient {
1364
1682
  }
1365
1683
  name = this.normalizeFilename(name);
1366
1684
  // it could be dir
1367
- if (! name.endsWith('/*')) {
1685
+ if (!name.endsWith('/*')) {
1368
1686
  name += '/*';
1369
1687
  } else if (name.endsWith('/')) {
1370
1688
  name += '*';
@@ -1383,9 +1701,7 @@ class ObjectsInRedisClient {
1383
1701
  return tools.maybeCallbackWithError(callback, utils.ERRORS.ERROR_NOT_FOUND);
1384
1702
  }
1385
1703
 
1386
- keys = keys
1387
- .sort()
1388
- .filter(key => key.match(/\$%\$meta$/));
1704
+ keys = keys.sort().filter(key => key.endsWith('$%$meta'));
1389
1705
 
1390
1706
  if (!keys.length) {
1391
1707
  return tools.maybeCallbackWithError(callback, utils.ERRORS.ERROR_NOT_FOUND);
@@ -1399,9 +1715,9 @@ class ObjectsInRedisClient {
1399
1715
  }
1400
1716
  let result;
1401
1717
  const dontCheck =
1402
- options.user === utils.CONSTS.SYSTEM_ADMIN_USER ||
1403
- options.group !== utils.CONSTS.SYSTEM_ADMIN_GROUP ||
1404
- (options.groups && options.groups.indexOf(utils.CONSTS.SYSTEM_ADMIN_GROUP) !== -1);
1718
+ options.user === utils.CONSTS.SYSTEM_ADMIN_USER ||
1719
+ options.group !== utils.CONSTS.SYSTEM_ADMIN_GROUP ||
1720
+ (options.groups && options.groups.indexOf(utils.CONSTS.SYSTEM_ADMIN_GROUP) !== -1);
1405
1721
 
1406
1722
  objs = objs || [];
1407
1723
  if (!dontCheck) {
@@ -1424,9 +1740,9 @@ class ObjectsInRedisClient {
1424
1740
  const name = key.substring(this.fileNamespaceL + id.length + 3, key.length - 7);
1425
1741
  const pos = name.lastIndexOf('/');
1426
1742
  if (pos !== -1) {
1427
- return {file: name.substring(pos + 1), path: name.substring(0, pos)};
1743
+ return { file: name.substring(pos + 1), path: name.substring(0, pos) };
1428
1744
  } else {
1429
- return {file: id, path: ''};
1745
+ return { file: id, path: '' };
1430
1746
  }
1431
1747
  });
1432
1748
  this._rmHelper(result, () => {
@@ -1463,8 +1779,8 @@ class ObjectsInRedisClient {
1463
1779
 
1464
1780
  rmAsync(id, name, options) {
1465
1781
  return new Promise((resolve, reject) =>
1466
- this.rm(id, name, options, (err, files) =>
1467
- err ? reject(err) : resolve(files)));
1782
+ this.rm(id, name, options, (err, files) => (err ? reject(err) : resolve(files)))
1783
+ );
1468
1784
  }
1469
1785
 
1470
1786
  // simulate. redis has no dirs
@@ -1499,9 +1815,9 @@ class ObjectsInRedisClient {
1499
1815
  }
1500
1816
 
1501
1817
  mkdirAsync(id, dirName, options) {
1502
- return /** @type {Promise<void>} */ (new Promise((resolve, reject) =>
1503
- this.mkdir(id, dirName, options, err =>
1504
- err ? reject(err) : resolve())));
1818
+ return /** @type {Promise<void>} */ (
1819
+ new Promise((resolve, reject) => this.mkdir(id, dirName, options, err => (err ? reject(err) : resolve())))
1820
+ );
1505
1821
  }
1506
1822
 
1507
1823
  async _chownFileHelper(keys, metas, options, callback) {
@@ -1546,20 +1862,22 @@ class ObjectsInRedisClient {
1546
1862
  }
1547
1863
  const nameArr = name.split('/');
1548
1864
  const file = nameArr.pop();
1549
- const res = [{
1550
- path: nameArr.join('/'),
1551
- file: file,
1552
- stats: meta.stats,
1553
- isDir: false,
1554
- acl: meta.acl || {},
1555
- modifiedAt: meta.modifiedAt,
1556
- createdAt: meta.createdAt
1557
- }];
1865
+ const res = [
1866
+ {
1867
+ path: nameArr.join('/'),
1868
+ file: file,
1869
+ stats: meta.stats,
1870
+ isDir: false,
1871
+ acl: meta.acl || {},
1872
+ modifiedAt: meta.modifiedAt,
1873
+ createdAt: meta.createdAt
1874
+ }
1875
+ ];
1558
1876
  return tools.maybeCallbackWithError(callback, null, res);
1559
1877
  }
1560
1878
 
1561
1879
  // it could be dir
1562
- if (! name.endsWith('/*')) {
1880
+ if (!name.endsWith('/*')) {
1563
1881
  name += '/*';
1564
1882
  } else if (name.endsWith('/')) {
1565
1883
  name += '*';
@@ -1580,9 +1898,7 @@ class ObjectsInRedisClient {
1580
1898
  return tools.maybeCallbackWithError(callback, utils.ERRORS.ERROR_NOT_FOUND);
1581
1899
  }
1582
1900
 
1583
- keys = keys
1584
- .sort()
1585
- .filter(key => key.match(/\$%\$meta$/));
1901
+ keys = keys.sort().filter(key => key.endsWith('$%$meta'));
1586
1902
 
1587
1903
  // Check permissions
1588
1904
  let metas;
@@ -1591,9 +1907,10 @@ class ObjectsInRedisClient {
1591
1907
  } catch (e) {
1592
1908
  return tools.maybeCallbackWithRedisError(callback, e);
1593
1909
  }
1594
- const dontCheck = options.user === utils.CONSTS.SYSTEM_ADMIN_USER ||
1595
- options.group !== utils.CONSTS.SYSTEM_ADMIN_GROUP ||
1596
- (options.groups && options.groups.indexOf(utils.CONSTS.SYSTEM_ADMIN_GROUP) !== -1);
1910
+ const dontCheck =
1911
+ options.user === utils.CONSTS.SYSTEM_ADMIN_USER ||
1912
+ options.group !== utils.CONSTS.SYSTEM_ADMIN_GROUP ||
1913
+ (options.groups && options.groups.indexOf(utils.CONSTS.SYSTEM_ADMIN_GROUP) !== -1);
1597
1914
  const keysFiltered = [];
1598
1915
  const objsFiltered = [];
1599
1916
  const processed = [];
@@ -1619,13 +1936,13 @@ class ObjectsInRedisClient {
1619
1936
  const nameArr = name.split('/');
1620
1937
  const file = nameArr.pop();
1621
1938
  processed.push({
1622
- path: nameArr.join('/'),
1623
- file: file,
1624
- stats: metas[i].stats || {},
1625
- isDir: false,
1626
- acl: metas[i].acl || {},
1939
+ path: nameArr.join('/'),
1940
+ file: file,
1941
+ stats: metas[i].stats || {},
1942
+ isDir: false,
1943
+ acl: metas[i].acl || {},
1627
1944
  modifiedAt: metas[i].modifiedAt,
1628
- createdAt: metas[i].createdAt
1945
+ createdAt: metas[i].createdAt
1629
1946
  });
1630
1947
  }
1631
1948
  }
@@ -1641,7 +1958,7 @@ class ObjectsInRedisClient {
1641
1958
  }
1642
1959
  options = options || {};
1643
1960
  if (typeof options !== 'object') {
1644
- options = {owner: options};
1961
+ options = { owner: options };
1645
1962
  }
1646
1963
 
1647
1964
  if (typeof name !== 'string' || !name.length || name === '/') {
@@ -1655,8 +1972,8 @@ class ObjectsInRedisClient {
1655
1972
  if (!options.ownerGroup && options.group) {
1656
1973
  options.ownerGroup = options.group;
1657
1974
  }
1658
- if (!options.owner && options.user) {
1659
- options.owner = options.user;
1975
+ if (!options.owner && options.user) {
1976
+ options.owner = options.user;
1660
1977
  }
1661
1978
 
1662
1979
  if (!options.owner) {
@@ -1691,9 +2008,11 @@ class ObjectsInRedisClient {
1691
2008
  }
1692
2009
 
1693
2010
  chownFileAsync(id, name, options) {
1694
- return /** @type {Promise<void>} */ (new Promise((resolve, reject) =>
1695
- this.chownFile(id, name, options, (err, processed) =>
1696
- err ? reject(err) : resolve(processed))));
2011
+ return /** @type {Promise<void>} */ (
2012
+ new Promise((resolve, reject) =>
2013
+ this.chownFile(id, name, options, (err, processed) => (err ? reject(err) : resolve(processed)))
2014
+ )
2015
+ );
1697
2016
  }
1698
2017
 
1699
2018
  /**
@@ -1746,15 +2065,17 @@ class ObjectsInRedisClient {
1746
2065
 
1747
2066
  const nameArr = name.split('/');
1748
2067
  const file = nameArr.pop();
1749
- const res = [{
1750
- path: nameArr.join('/'),
1751
- file: file,
1752
- stats: meta.stats,
1753
- isDir: false,
1754
- acl: meta.acl || {},
1755
- modifiedAt: meta.modifiedAt,
1756
- createdAt: meta.createdAt
1757
- }];
2068
+ const res = [
2069
+ {
2070
+ path: nameArr.join('/'),
2071
+ file: file,
2072
+ stats: meta.stats,
2073
+ isDir: false,
2074
+ acl: meta.acl || {},
2075
+ modifiedAt: meta.modifiedAt,
2076
+ createdAt: meta.createdAt
2077
+ }
2078
+ ];
1758
2079
  return tools.maybeCallbackWithError(callback, null, res);
1759
2080
  }
1760
2081
 
@@ -1780,9 +2101,7 @@ class ObjectsInRedisClient {
1780
2101
  return tools.maybeCallbackWithError(callback, utils.ERRORS.ERROR_NOT_FOUND);
1781
2102
  }
1782
2103
 
1783
- keys = keys
1784
- .sort()
1785
- .filter(key => key.match(/\$%\$meta$/));
2104
+ keys = keys.sort().filter(key => key.endsWith('$%$meta'));
1786
2105
 
1787
2106
  // Check permissions
1788
2107
  let objs;
@@ -1793,9 +2112,9 @@ class ObjectsInRedisClient {
1793
2112
  }
1794
2113
 
1795
2114
  const dontCheck =
1796
- options.user === utils.CONSTS.SYSTEM_ADMIN_USER ||
1797
- options.group !== utils.CONSTS.SYSTEM_ADMIN_GROUP ||
1798
- (options.groups && options.groups.indexOf(utils.CONSTS.SYSTEM_ADMIN_GROUP) !== -1);
2115
+ options.user === utils.CONSTS.SYSTEM_ADMIN_USER ||
2116
+ options.group !== utils.CONSTS.SYSTEM_ADMIN_GROUP ||
2117
+ (options.groups && options.groups.indexOf(utils.CONSTS.SYSTEM_ADMIN_GROUP) !== -1);
1799
2118
 
1800
2119
  const keysFiltered = [];
1801
2120
  const objsFiltered = [];
@@ -1822,18 +2141,19 @@ class ObjectsInRedisClient {
1822
2141
  const nameArr = name.split('/');
1823
2142
  const file = nameArr.pop();
1824
2143
  processed.push({
1825
- path: nameArr.join('/'),
1826
- file: file,
1827
- stats: objs[i].stats,
1828
- isDir: false,
1829
- acl: objs[i].acl || {},
2144
+ path: nameArr.join('/'),
2145
+ file: file,
2146
+ stats: objs[i].stats,
2147
+ isDir: false,
2148
+ acl: objs[i].acl || {},
1830
2149
  modifiedAt: objs[i].modifiedAt,
1831
- createdAt: objs[i].createdAt
2150
+ createdAt: objs[i].createdAt
1832
2151
  });
1833
2152
  }
1834
2153
  }
1835
2154
  this._chmodFileHelper(keysFiltered, objsFiltered, options, err =>
1836
- tools.maybeCallbackWithError(callback, err, processed));
2155
+ tools.maybeCallbackWithError(callback, err, processed)
2156
+ );
1837
2157
  }
1838
2158
 
1839
2159
  chmodFile(id, name, options, callback) {
@@ -1852,7 +2172,7 @@ class ObjectsInRedisClient {
1852
2172
  }
1853
2173
 
1854
2174
  if (typeof options !== 'object') {
1855
- options = {mode: options};
2175
+ options = { mode: options };
1856
2176
  }
1857
2177
 
1858
2178
  if (options.mode === undefined) {
@@ -1876,9 +2196,11 @@ class ObjectsInRedisClient {
1876
2196
  }
1877
2197
 
1878
2198
  chmodFileAsync(id, name, options) {
1879
- return /** @type {Promise<void>} */ (new Promise((resolve, reject) =>
1880
- this.chmodFile(id, name, options, (err, processed) =>
1881
- err ? reject(err) : resolve(processed))));
2199
+ return /** @type {Promise<void>} */ (
2200
+ new Promise((resolve, reject) =>
2201
+ this.chmodFile(id, name, options, (err, processed) => (err ? reject(err) : resolve(processed)))
2202
+ )
2203
+ );
1882
2204
  }
1883
2205
 
1884
2206
  enableFileCache(enabled, options, callback) {
@@ -1903,8 +2225,8 @@ class ObjectsInRedisClient {
1903
2225
 
1904
2226
  enableFileCacheAsync(enabled, options) {
1905
2227
  return new Promise((resolve, reject) =>
1906
- this.enableFileCache(enabled, options, (err, res) =>
1907
- err ? reject(err) : resolve(res)));
2228
+ this.enableFileCache(enabled, options, (err, res) => (err ? reject(err) : resolve(res)))
2229
+ );
1908
2230
  }
1909
2231
 
1910
2232
  // -------------- OBJECT FUNCTIONS -------------------------------------------
@@ -1924,7 +2246,6 @@ class ObjectsInRedisClient {
1924
2246
  return tools.maybeCallbackWithError(callback, err);
1925
2247
  }
1926
2248
  });
1927
-
1928
2249
  });
1929
2250
  } else {
1930
2251
  this.log.silly(this.namespace + ' redis psubscribe ' + this.objNamespace + pattern);
@@ -1944,7 +2265,7 @@ class ObjectsInRedisClient {
1944
2265
  }
1945
2266
  utils.checkObjectRights(this, null, null, options, 'list', (err, options) => {
1946
2267
  if (err) {
1947
- typeof callback === 'function' && setImmediate(() => callback(err));
2268
+ return tools.maybeCallbackWithRedisError(callback, err);
1948
2269
  } else {
1949
2270
  return this._subscribe(pattern, options, this.subSystem, callback);
1950
2271
  }
@@ -1956,9 +2277,9 @@ class ObjectsInRedisClient {
1956
2277
  }
1957
2278
 
1958
2279
  subscribeAsync(pattern, options) {
1959
- return /** @type {Promise<void>} */ (new Promise((resolve, reject) =>
1960
- this.subscribe(pattern, options, err =>
1961
- err ? reject(err) : resolve())));
2280
+ return /** @type {Promise<void>} */ (
2281
+ new Promise((resolve, reject) => this.subscribe(pattern, options, err => (err ? reject(err) : resolve())))
2282
+ );
1962
2283
  }
1963
2284
 
1964
2285
  subscribeUser(pattern, options, callback) {
@@ -1968,7 +2289,7 @@ class ObjectsInRedisClient {
1968
2289
  }
1969
2290
  utils.checkObjectRights(this, null, null, options, 'list', (err, options) => {
1970
2291
  if (err) {
1971
- typeof callback === 'function' && setImmediate(() => callback(err));
2292
+ return tools.maybeCallbackWithRedisError(callback, err);
1972
2293
  } else {
1973
2294
  return this._subscribe(pattern, options, this.sub, callback);
1974
2295
  }
@@ -1976,14 +2297,16 @@ class ObjectsInRedisClient {
1976
2297
  }
1977
2298
 
1978
2299
  subscribeUserAsync(pattern, options) {
1979
- return /** @type {Promise<void>} */ (new Promise((resolve, reject) =>
1980
- this.subscribeUser(pattern, options, err =>
1981
- err ? reject(err) : resolve())));
2300
+ return /** @type {Promise<void>} */ (
2301
+ new Promise((resolve, reject) =>
2302
+ this.subscribeUser(pattern, options, err => (err ? reject(err) : resolve()))
2303
+ )
2304
+ );
1982
2305
  }
1983
2306
 
1984
2307
  _unsubscribe(pattern, options, subClient, callback) {
1985
2308
  if (!subClient) {
1986
- return typeof callback === 'function' && setImmediate(() => callback(utils.ERRORS.ERROR_DB_CLOSED));
2309
+ return tools.maybeCallbackWithRedisError(callback, utils.ERRORS.ERROR_DB_CLOSED);
1987
2310
  }
1988
2311
  if (Array.isArray(pattern)) {
1989
2312
  let count = pattern.length;
@@ -2014,7 +2337,7 @@ class ObjectsInRedisClient {
2014
2337
  }
2015
2338
  utils.checkObjectRights(this, null, null, options, 'list', (err, options) => {
2016
2339
  if (err) {
2017
- typeof callback === 'function' && setImmediate(() => callback(err));
2340
+ return tools.maybeCallbackWithRedisError(callback, err);
2018
2341
  } else {
2019
2342
  return this._unsubscribe(pattern, options, this.subSystem, callback);
2020
2343
  }
@@ -2026,9 +2349,9 @@ class ObjectsInRedisClient {
2026
2349
  }
2027
2350
 
2028
2351
  unsubscribeAsync(pattern, options) {
2029
- return /** @type {Promise<void>} */ (new Promise((resolve, reject) =>
2030
- this.unsubscribe(pattern, options, err =>
2031
- err ? reject(err) : resolve())));
2352
+ return /** @type {Promise<void>} */ (
2353
+ new Promise((resolve, reject) => this.unsubscribe(pattern, options, err => (err ? reject(err) : resolve())))
2354
+ );
2032
2355
  }
2033
2356
 
2034
2357
  unsubscribeUser(pattern, options, callback) {
@@ -2038,7 +2361,7 @@ class ObjectsInRedisClient {
2038
2361
  }
2039
2362
  utils.checkObjectRights(this, null, null, options, 'list', (err, options) => {
2040
2363
  if (err) {
2041
- typeof callback === 'function' && setImmediate(() => callback(err));
2364
+ return tools.maybeCallbackWithError(callback, err);
2042
2365
  } else {
2043
2366
  return this._unsubscribe(pattern, options, this.sub, callback);
2044
2367
  }
@@ -2046,14 +2369,16 @@ class ObjectsInRedisClient {
2046
2369
  }
2047
2370
 
2048
2371
  unsubscribeUserAsync(pattern, options) {
2049
- return /** @type {Promise<void>} */ (new Promise((resolve, reject) =>
2050
- this.unsubscribeUser(pattern, options, err =>
2051
- err ? reject(err) : resolve())));
2372
+ return /** @type {Promise<void>} */ (
2373
+ new Promise((resolve, reject) =>
2374
+ this.unsubscribeUser(pattern, options, err => (err ? reject(err) : resolve()))
2375
+ )
2376
+ );
2052
2377
  }
2053
2378
 
2054
2379
  async _objectHelper(keys, objs, callback) {
2055
2380
  if (!keys || !keys.length) {
2056
- typeof callback === 'function' && callback();
2381
+ return tools.maybeCallback(callback);
2057
2382
  } else {
2058
2383
  if (!this.client) {
2059
2384
  return typeof callback === 'function' && callback(utils.ERRORS.ERROR_DB_CLOSED);
@@ -2062,7 +2387,28 @@ class ObjectsInRedisClient {
2062
2387
  const obj = objs.shift();
2063
2388
  const message = JSON.stringify(obj);
2064
2389
  try {
2065
- await this.client.set(id, message);
2390
+ const commands = [];
2391
+ if (this.useSets) {
2392
+ if (obj.type) {
2393
+ // e.g. _design/ has no type
2394
+ // add the object to the set + set object atomic
2395
+ commands.push(['sadd', `${this.setNamespace}object.type.${obj.type}`, id]);
2396
+ }
2397
+
2398
+ if (obj.common && obj.common.custom) {
2399
+ // add to "common" set
2400
+ commands.push(['sadd', `${this.setNamespace}object.common.custom`, id]);
2401
+ }
2402
+ }
2403
+
2404
+ if (!commands.length) {
2405
+ // only set
2406
+ await this.client.set(id, message);
2407
+ } else {
2408
+ // set all commands atomic
2409
+ commands.push(['set', id, message]);
2410
+ await this.client.multi(commands).exec();
2411
+ }
2066
2412
  await this.client.publish(id, message);
2067
2413
  } catch (e) {
2068
2414
  return tools.maybeCallbackWithRedisError(callback, e);
@@ -2073,52 +2419,67 @@ class ObjectsInRedisClient {
2073
2419
  }
2074
2420
 
2075
2421
  _chownObject(pattern, options, callback) {
2076
- this.getConfigKeys(pattern, options, async (err, keys) => {
2077
- if (err) {
2078
- return tools.maybeCallbackWithError(callback, err);
2079
- }
2080
- if (!this.client) {
2081
- return tools.maybeCallbackWithError(callback, utils.ERRORS.ERROR_DB_CLOSED);
2082
- }
2422
+ this.getConfigKeys(
2423
+ pattern,
2424
+ options,
2425
+ async (err, keys) => {
2426
+ if (err) {
2427
+ return tools.maybeCallbackWithError(callback, err);
2428
+ }
2429
+ if (!this.client) {
2430
+ return tools.maybeCallbackWithError(callback, utils.ERRORS.ERROR_DB_CLOSED);
2431
+ }
2083
2432
 
2084
- let objects;
2085
- try {
2086
- objects = await this.client.mget(keys);
2087
- } catch (e) {
2088
- return tools.maybeCallbackWithRedisError(callback, e);
2089
- }
2090
- const filteredKeys = [];
2091
- const filteredObjs = [];
2092
- objects = objects || [];
2093
- for (let k = 0; k < keys.length; k++) {
2433
+ let objects;
2094
2434
  try {
2095
- objects[k] = JSON.parse(objects[k]);
2096
- } catch {
2097
- this.log.error(`${this.namespace} Cannot parse JSON ${keys[k]}: ${objects[k]}`);
2098
- continue;
2099
- }
2100
- if (!utils.checkObject(objects[k], options, utils.CONSTS.ACCESS_WRITE)) {
2101
- continue;
2435
+ objects = await this.client.mget(keys);
2436
+ } catch (e) {
2437
+ return tools.maybeCallbackWithRedisError(callback, e);
2102
2438
  }
2103
- if (!objects[k].acl) {
2104
- objects[k].acl = {
2105
- owner: (this.defaultNewAcl && this.defaultNewAcl.owner) || utils.CONSTS.SYSTEM_ADMIN_USER,
2106
- ownerGroup: (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) || utils.CONSTS.SYSTEM_ADMIN_GROUP,
2107
- object: (this.defaultNewAcl && this.defaultNewAcl.object) || (utils.CONSTS.ACCESS_USER_RW | utils.CONSTS.ACCESS_GROUP_READ | utils.CONSTS.ACCESS_EVERY_READ) // '0644'
2108
- };
2109
- if (objects[k].type === 'state') {
2110
- objects[k].acl.state = (this.defaultNewAcl && this.defaultNewAcl.state) || (utils.CONSTS.ACCESS_USER_RW | utils.CONSTS.ACCESS_GROUP_READ | utils.CONSTS.ACCESS_EVERY_READ); // '0644'
2439
+ const filteredKeys = [];
2440
+ const filteredObjs = [];
2441
+ objects = objects || [];
2442
+ for (let k = 0; k < keys.length; k++) {
2443
+ try {
2444
+ objects[k] = JSON.parse(objects[k]);
2445
+ } catch {
2446
+ this.log.error(`${this.namespace} Cannot parse JSON ${keys[k]}: ${objects[k]}`);
2447
+ continue;
2448
+ }
2449
+ if (!utils.checkObject(objects[k], options, utils.CONSTS.ACCESS_WRITE)) {
2450
+ continue;
2451
+ }
2452
+ if (!objects[k].acl) {
2453
+ objects[k].acl = {
2454
+ owner: (this.defaultNewAcl && this.defaultNewAcl.owner) || utils.CONSTS.SYSTEM_ADMIN_USER,
2455
+ ownerGroup:
2456
+ (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) ||
2457
+ utils.CONSTS.SYSTEM_ADMIN_GROUP,
2458
+ object:
2459
+ (this.defaultNewAcl && this.defaultNewAcl.object) ||
2460
+ utils.CONSTS.ACCESS_USER_RW |
2461
+ utils.CONSTS.ACCESS_GROUP_READ |
2462
+ utils.CONSTS.ACCESS_EVERY_READ // '0644'
2463
+ };
2464
+ if (objects[k].type === 'state') {
2465
+ objects[k].acl.state =
2466
+ (this.defaultNewAcl && this.defaultNewAcl.state) ||
2467
+ utils.CONSTS.ACCESS_USER_RW |
2468
+ utils.CONSTS.ACCESS_GROUP_READ |
2469
+ utils.CONSTS.ACCESS_EVERY_READ; // '0644'
2470
+ }
2111
2471
  }
2472
+ objects[k].acl.owner = options.owner;
2473
+ objects[k].acl.ownerGroup = options.ownerGroup;
2474
+ filteredKeys.push(keys[k]);
2475
+ filteredObjs.push(objects[k]);
2112
2476
  }
2113
- objects[k].acl.owner = options.owner;
2114
- objects[k].acl.ownerGroup = options.ownerGroup;
2115
- filteredKeys.push(keys[k]);
2116
- filteredObjs.push(objects[k]);
2117
- }
2118
- this._objectHelper(filteredKeys, filteredObjs, () => {
2119
- return tools.maybeCallbackWithError(callback, null, filteredObjs);
2120
- });
2121
- }, true);
2477
+ this._objectHelper(filteredKeys, filteredObjs, () => {
2478
+ return tools.maybeCallbackWithError(callback, null, filteredObjs);
2479
+ });
2480
+ },
2481
+ true
2482
+ );
2122
2483
  }
2123
2484
 
2124
2485
  chownObject(pattern, options, callback) {
@@ -2130,13 +2491,13 @@ class ObjectsInRedisClient {
2130
2491
  options.acl = null;
2131
2492
 
2132
2493
  if (typeof options !== 'object') {
2133
- options = {owner: options};
2494
+ options = { owner: options };
2134
2495
  }
2135
2496
 
2136
2497
  if (!options.ownerGroup && options.group) {
2137
2498
  options.ownerGroup = options.group;
2138
2499
  }
2139
- if (!options.owner && options.user) {
2500
+ if (!options.owner && options.user) {
2140
2501
  options.owner = options.user;
2141
2502
  }
2142
2503
 
@@ -2160,7 +2521,7 @@ class ObjectsInRedisClient {
2160
2521
 
2161
2522
  utils.checkObjectRights(this, null, null, options, utils.CONSTS.ACCESS_WRITE, (err, options) => {
2162
2523
  if (err) {
2163
- return tools.maybeCallbackWithError(callback, err);
2524
+ return tools.maybeCallbackWithRedisError(callback, err);
2164
2525
  } else {
2165
2526
  if (!options.acl.object || !options.acl.object.write) {
2166
2527
  return tools.maybeCallbackWithError(callback, utils.ERRORS.ERROR_PERMISSION);
@@ -2173,62 +2534,77 @@ class ObjectsInRedisClient {
2173
2534
 
2174
2535
  chownObjectAsync(pattern, options) {
2175
2536
  return new Promise((resolve, reject) =>
2176
- this.chownObject(pattern, options, (err, list) =>
2177
- err ? reject(err) : resolve(list)));
2537
+ this.chownObject(pattern, options, (err, list) => (err ? reject(err) : resolve(list)))
2538
+ );
2178
2539
  }
2179
2540
 
2180
2541
  _chmodObject(pattern, options, callback) {
2181
- this.getConfigKeys(pattern, options, async (err, keys) => {
2182
- if (err) {
2183
- return tools.maybeCallbackWithError(callback, err);
2184
- }
2185
- if (!this.client) {
2186
- return tools.maybeCallbackWithError(callback, utils.ERRORS.ERROR_DB_CLOSED);
2187
- }
2188
-
2189
- let objects;
2190
- try {
2191
- objects = await this.client.mget(keys);
2192
- } catch (e) {
2193
- return tools.maybeCallbackWithRedisError(callback, e);
2194
- }
2542
+ this.getConfigKeys(
2543
+ pattern,
2544
+ options,
2545
+ async (err, keys) => {
2546
+ if (err) {
2547
+ return tools.maybeCallbackWithRedisError(callback, err);
2548
+ }
2549
+ if (!this.client) {
2550
+ return tools.maybeCallbackWithRedisError(callback, utils.ERRORS.ERROR_DB_CLOSED);
2551
+ }
2195
2552
 
2196
- const filteredKeys = [];
2197
- const filteredObjs = [];
2198
- objects = objects || [];
2199
- for (let k = 0; k < keys.length; k++) {
2553
+ let objects;
2200
2554
  try {
2201
- objects[k] = JSON.parse(objects[k]);
2202
- } catch {
2203
- this.log.error(`${this.namespace} Cannot parse JSON ${keys[k]}: ${objects[k]}`);
2204
- continue;
2205
- }
2206
- if (!utils.checkObject(objects[k], options, utils.CONSTS.ACCESS_WRITE)) {
2207
- continue;
2555
+ objects = await this.client.mget(keys);
2556
+ } catch (e) {
2557
+ return tools.maybeCallbackWithRedisError(callback, e);
2208
2558
  }
2209
- if (!objects[k].acl) {
2210
- objects[k].acl = {
2211
- owner: (this.defaultNewAcl && this.defaultNewAcl.owner) || utils.CONSTS.SYSTEM_ADMIN_USER,
2212
- ownerGroup: (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) || utils.CONSTS.SYSTEM_ADMIN_GROUP,
2213
- object: (this.defaultNewAcl && this.defaultNewAcl.object) || (utils.CONSTS.ACCESS_USER_RW | utils.CONSTS.ACCESS_GROUP_READ | utils.CONSTS.ACCESS_EVERY_READ) // '0644'
2214
- };
2215
- if (objects[k].type === 'state') {
2216
- objects[k].acl.state = (this.defaultNewAcl && this.defaultNewAcl.state) || (utils.CONSTS.ACCESS_USER_RW | utils.CONSTS.ACCESS_GROUP_READ | utils.CONSTS.ACCESS_EVERY_READ); // '0644'
2559
+
2560
+ const filteredKeys = [];
2561
+ const filteredObjs = [];
2562
+ objects = objects || [];
2563
+ for (let k = 0; k < keys.length; k++) {
2564
+ try {
2565
+ objects[k] = JSON.parse(objects[k]);
2566
+ } catch {
2567
+ this.log.error(`${this.namespace} Cannot parse JSON ${keys[k]}: ${objects[k]}`);
2568
+ continue;
2217
2569
  }
2570
+ if (!utils.checkObject(objects[k], options, utils.CONSTS.ACCESS_WRITE)) {
2571
+ continue;
2572
+ }
2573
+ if (!objects[k].acl) {
2574
+ objects[k].acl = {
2575
+ owner: (this.defaultNewAcl && this.defaultNewAcl.owner) || utils.CONSTS.SYSTEM_ADMIN_USER,
2576
+ ownerGroup:
2577
+ (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) ||
2578
+ utils.CONSTS.SYSTEM_ADMIN_GROUP,
2579
+ object:
2580
+ (this.defaultNewAcl && this.defaultNewAcl.object) ||
2581
+ utils.CONSTS.ACCESS_USER_RW |
2582
+ utils.CONSTS.ACCESS_GROUP_READ |
2583
+ utils.CONSTS.ACCESS_EVERY_READ // '0644'
2584
+ };
2585
+ if (objects[k].type === 'state') {
2586
+ objects[k].acl.state =
2587
+ (this.defaultNewAcl && this.defaultNewAcl.state) ||
2588
+ utils.CONSTS.ACCESS_USER_RW |
2589
+ utils.CONSTS.ACCESS_GROUP_READ |
2590
+ utils.CONSTS.ACCESS_EVERY_READ; // '0644'
2591
+ }
2592
+ }
2593
+ if (options.object !== undefined) {
2594
+ objects[k].acl.object = options.object;
2595
+ }
2596
+ if (options.state !== undefined) {
2597
+ objects[k].acl.state = options.state;
2598
+ }
2599
+ filteredKeys.push(keys[k]);
2600
+ filteredObjs.push(objects[k]);
2218
2601
  }
2219
- if (options.object !== undefined) {
2220
- objects[k].acl.object = options.object;
2221
- }
2222
- if (options.state !== undefined) {
2223
- objects[k].acl.state = options.state;
2224
- }
2225
- filteredKeys.push(keys[k]);
2226
- filteredObjs.push(objects[k]);
2227
- }
2228
- this._objectHelper(filteredKeys, filteredObjs, () => {
2229
- return tools.maybeCallbackWithError(callback, null, filteredObjs);
2230
- });
2231
- }, true);
2602
+ this._objectHelper(filteredKeys, filteredObjs, () => {
2603
+ return tools.maybeCallbackWithError(callback, null, filteredObjs);
2604
+ });
2605
+ },
2606
+ true
2607
+ );
2232
2608
  }
2233
2609
 
2234
2610
  chmodObject(pattern, options, callback) {
@@ -2240,7 +2616,7 @@ class ObjectsInRedisClient {
2240
2616
  options.acl = null;
2241
2617
 
2242
2618
  if (typeof options !== 'object') {
2243
- options = {object: options};
2619
+ options = { object: options };
2244
2620
  }
2245
2621
 
2246
2622
  if (options.mode && !options.object) {
@@ -2248,18 +2624,18 @@ class ObjectsInRedisClient {
2248
2624
  }
2249
2625
 
2250
2626
  if (options.object === undefined) {
2251
- this.log.error(this.namespace + ' mode is not defined');
2252
- return typeof callback === 'function' && setImmediate(() => callback('invalid parameter'));
2627
+ this.log.error(`${this.namespace} mode is not defined`);
2628
+ return tools.maybeCallbackWithError(callback, 'invalid parameter');
2253
2629
  } else if (typeof options.mode === 'string') {
2254
2630
  options.mode = parseInt(options.mode, 16);
2255
2631
  }
2256
2632
 
2257
2633
  utils.checkObjectRights(this, null, null, options, utils.CONSTS.ACCESS_WRITE, (err, options) => {
2258
2634
  if (err) {
2259
- typeof callback === 'function' && setImmediate(() => callback(err));
2635
+ return tools.maybeCallbackWithRedisError(callback, err);
2260
2636
  } else {
2261
2637
  if (!options.acl.file.write) {
2262
- typeof callback === 'function' && setImmediate(() => callback(utils.ERRORS.ERROR_PERMISSION));
2638
+ return tools.maybeCallbackWithError(callback, utils.ERRORS.ERROR_PERMISSION);
2263
2639
  } else {
2264
2640
  return this._chmodObject(pattern, options, callback);
2265
2641
  }
@@ -2269,8 +2645,8 @@ class ObjectsInRedisClient {
2269
2645
 
2270
2646
  chmodObjectAsync(pattern, options) {
2271
2647
  return new Promise((resolve, reject) =>
2272
- this.chmodObject(pattern, options, (err, list) =>
2273
- err ? reject(err) : resolve(list)));
2648
+ this.chmodObject(pattern, options, (err, list) => (err ? reject(err) : resolve(list)))
2649
+ );
2274
2650
  }
2275
2651
 
2276
2652
  async _getObject(id, options, callback) {
@@ -2317,8 +2693,8 @@ class ObjectsInRedisClient {
2317
2693
  }
2318
2694
  if (!callback) {
2319
2695
  return new Promise((resolve, reject) =>
2320
- this.getObject(id, options, (err, obj) =>
2321
- err ? reject(err) : resolve(obj)));
2696
+ this.getObject(id, options, (err, obj) => (err ? reject(err) : resolve(obj)))
2697
+ );
2322
2698
  }
2323
2699
 
2324
2700
  if (typeof callback === 'function') {
@@ -2337,8 +2713,8 @@ class ObjectsInRedisClient {
2337
2713
 
2338
2714
  getObjectAsync(id, options) {
2339
2715
  return new Promise((resolve, reject) =>
2340
- this.getObject(id, options, (err, obj) =>
2341
- err ? reject(err) : resolve(obj)));
2716
+ this.getObject(id, options, (err, obj) => (err ? reject(err) : resolve(obj)))
2717
+ );
2342
2718
  }
2343
2719
 
2344
2720
  async _getKeys(pattern, options, callback, dontModify) {
@@ -2367,9 +2743,9 @@ class ObjectsInRedisClient {
2367
2743
  keys.sort();
2368
2744
  const result = [];
2369
2745
  const dontCheck =
2370
- options.user === utils.CONSTS.SYSTEM_ADMIN_USER ||
2371
- options.group !== utils.CONSTS.SYSTEM_ADMIN_GROUP ||
2372
- (options.groups && options.groups.indexOf(utils.CONSTS.SYSTEM_ADMIN_GROUP) !== -1);
2746
+ options.user === utils.CONSTS.SYSTEM_ADMIN_USER ||
2747
+ options.group !== utils.CONSTS.SYSTEM_ADMIN_GROUP ||
2748
+ (options.groups && options.groups.indexOf(utils.CONSTS.SYSTEM_ADMIN_GROUP) !== -1);
2373
2749
 
2374
2750
  if (dontCheck) {
2375
2751
  for (let i = 0; i < keys.length; i++) {
@@ -2422,13 +2798,13 @@ class ObjectsInRedisClient {
2422
2798
  }
2423
2799
  if (!callback) {
2424
2800
  return new Promise((resolve, reject) =>
2425
- this.getKeys(pattern, options, (err, obj) =>
2426
- err ? reject(err) : resolve(obj), dontModify));
2801
+ this.getKeys(pattern, options, (err, obj) => (err ? reject(err) : resolve(obj)), dontModify)
2802
+ );
2427
2803
  }
2428
2804
  if (typeof callback === 'function') {
2429
2805
  utils.checkObjectRights(this, null, null, options, 'list', (err, options) => {
2430
2806
  if (err) {
2431
- typeof callback === 'function' && setImmediate(() => callback(err));
2807
+ return tools.maybeCallbackWithRedisError(callback, err);
2432
2808
  } else {
2433
2809
  return this._getKeys(pattern, options, callback, dontModify);
2434
2810
  }
@@ -2438,8 +2814,8 @@ class ObjectsInRedisClient {
2438
2814
 
2439
2815
  getKeysAsync(id, options) {
2440
2816
  return new Promise((resolve, reject) =>
2441
- this.getKeys(id, options, (err, keys) =>
2442
- err ? reject(err) : resolve(keys)));
2817
+ this.getKeys(id, options, (err, keys) => (err ? reject(err) : resolve(keys)))
2818
+ );
2443
2819
  }
2444
2820
 
2445
2821
  getConfigKeys(pattern, options, callback, dontModify) {
@@ -2471,7 +2847,8 @@ class ObjectsInRedisClient {
2471
2847
  let objs;
2472
2848
  try {
2473
2849
  objs = await this.client.mget(_keys);
2474
- this.settings.connection.enhancedLogging && this.log.silly(this.namespace + ' redis mget ' + (!objs ? 0 : objs.length) + ' ' + _keys.length);
2850
+ this.settings.connection.enhancedLogging &&
2851
+ this.log.silly(this.namespace + ' redis mget ' + (!objs ? 0 : objs.length) + ' ' + _keys.length);
2475
2852
  } catch (e) {
2476
2853
  this.log.warn(`${this.namespace} redis mget ${!objs ? 0 : objs.length} ${_keys.length}, err: ${e.message}`);
2477
2854
  }
@@ -2479,9 +2856,9 @@ class ObjectsInRedisClient {
2479
2856
 
2480
2857
  if (objs) {
2481
2858
  const dontCheck =
2482
- options.user === utils.CONSTS.SYSTEM_ADMIN_USER ||
2483
- options.group !== utils.CONSTS.SYSTEM_ADMIN_GROUP ||
2484
- (options.groups && options.groups.indexOf(utils.CONSTS.SYSTEM_ADMIN_GROUP) !== -1);
2859
+ options.user === utils.CONSTS.SYSTEM_ADMIN_USER ||
2860
+ options.group !== utils.CONSTS.SYSTEM_ADMIN_GROUP ||
2861
+ (options.groups && options.groups.indexOf(utils.CONSTS.SYSTEM_ADMIN_GROUP) !== -1);
2485
2862
 
2486
2863
  if (!dontCheck) {
2487
2864
  for (let i = 0; i < objs.length; i++) {
@@ -2489,13 +2866,13 @@ class ObjectsInRedisClient {
2489
2866
  objs[i] = JSON.parse(objs[i]);
2490
2867
  } catch {
2491
2868
  this.log.error(`${this.namespace} Cannot parse JSON ${_keys[i]}: ${objs[i]}`);
2492
- result.push({error: utils.ERRORS.ERROR_PERMISSION});
2869
+ result.push({ error: utils.ERRORS.ERROR_PERMISSION });
2493
2870
  continue;
2494
2871
  }
2495
2872
  if (utils.checkObject(objs[i], options, utils.CONSTS.ACCESS_READ)) {
2496
2873
  result.push(objs[i]);
2497
2874
  } else {
2498
- result.push({error: utils.ERRORS.ERROR_PERMISSION});
2875
+ result.push({ error: utils.ERRORS.ERROR_PERMISSION });
2499
2876
  }
2500
2877
  }
2501
2878
  } else {
@@ -2519,8 +2896,8 @@ class ObjectsInRedisClient {
2519
2896
  }
2520
2897
  if (!callback) {
2521
2898
  return new Promise((resolve, reject) =>
2522
- this.getObjects(keys, options, (err, objs) =>
2523
- err ? reject(err) : resolve(objs), dontModify));
2899
+ this.getObjects(keys, options, (err, objs) => (err ? reject(err) : resolve(objs)), dontModify)
2900
+ );
2524
2901
  }
2525
2902
 
2526
2903
  if (options && options.acl) {
@@ -2529,7 +2906,7 @@ class ObjectsInRedisClient {
2529
2906
  if (typeof callback === 'function') {
2530
2907
  utils.checkObjectRights(this, null, null, options, utils.CONSTS.ACCESS_READ, (err, options) => {
2531
2908
  if (err) {
2532
- typeof callback === 'function' && setImmediate(() => callback(err));
2909
+ return tools.maybeCallbackWithRedisError(callback, err);
2533
2910
  } else {
2534
2911
  return this._getObjects(keys, options, callback, dontModify);
2535
2912
  }
@@ -2539,18 +2916,17 @@ class ObjectsInRedisClient {
2539
2916
 
2540
2917
  getObjectsAsync(id, options) {
2541
2918
  return new Promise((resolve, reject) =>
2542
- this.getObjects(id, options, (err, objs) =>
2543
- err ? reject(err) : resolve(objs)));
2919
+ this.getObjects(id, options, (err, objs) => (err ? reject(err) : resolve(objs)))
2920
+ );
2544
2921
  }
2545
2922
 
2546
2923
  async _getObjectsByPattern(pattern, options, callback) {
2547
2924
  if (!pattern || typeof pattern !== 'string') {
2548
- typeof callback === 'function' && setImmediate(() => callback('invalid pattern ' + JSON.stringify(pattern)));
2549
- return;
2925
+ return tools.maybeCallbackWithError(callback, `invalid pattern ${JSON.stringify(pattern)}`);
2550
2926
  }
2551
2927
 
2552
2928
  if (!this.client) {
2553
- return typeof callback === 'function' && setImmediate(() => callback(utils.ERRORS.ERROR_DB_CLOSED));
2929
+ return tools.maybeCallbackWithRedisError(callback, utils.ERRORS.ERROR_DB_CLOSED);
2554
2930
  }
2555
2931
 
2556
2932
  let keys;
@@ -2563,7 +2939,8 @@ class ObjectsInRedisClient {
2563
2939
  return tools.maybeCallbackWithError(callback, utils.ERRORS.ERROR_DB_CLOSED);
2564
2940
  }
2565
2941
 
2566
- this.settings.connection.enhancedLogging && this.log.silly(`${this.namespace} redis keys ${keys.length} ${pattern}`);
2942
+ this.settings.connection.enhancedLogging &&
2943
+ this.log.silly(`${this.namespace} redis keys ${keys.length} ${pattern}`);
2567
2944
  this._getObjects(keys, options, callback, true);
2568
2945
  }
2569
2946
 
@@ -2574,8 +2951,8 @@ class ObjectsInRedisClient {
2574
2951
  }
2575
2952
  if (!callback) {
2576
2953
  return new Promise((resolve, reject) =>
2577
- this.getObjectsByPattern(pattern, options, (err, obj) =>
2578
- err ? reject(err) : resolve(obj)));
2954
+ this.getObjectsByPattern(pattern, options, (err, obj) => (err ? reject(err) : resolve(obj)))
2955
+ );
2579
2956
  }
2580
2957
  if (options && options.acl) {
2581
2958
  options.acl = null;
@@ -2583,7 +2960,7 @@ class ObjectsInRedisClient {
2583
2960
  if (typeof callback === 'function') {
2584
2961
  utils.checkObjectRights(this, null, null, options, utils.CONSTS.ACCESS_READ, (err, options) => {
2585
2962
  if (err) {
2586
- typeof callback === 'function' && setImmediate(() => callback(err));
2963
+ return tools.maybeCallbackWithRedisError(callback, err);
2587
2964
  } else {
2588
2965
  return this._getObjectsByPattern(pattern, options, callback);
2589
2966
  }
@@ -2593,25 +2970,25 @@ class ObjectsInRedisClient {
2593
2970
 
2594
2971
  getObjectsByPatternAsync(pattern, options) {
2595
2972
  return new Promise((resolve, reject) =>
2596
- this.getObjectsByPattern(pattern, options, (err, objs) =>
2597
- err ? reject(err) : resolve(objs)));
2973
+ this.getObjectsByPattern(pattern, options, (err, objs) => (err ? reject(err) : resolve(objs)))
2974
+ );
2598
2975
  }
2599
2976
 
2600
2977
  async _setObject(id, obj, options, callback) {
2601
2978
  if (!id || typeof id !== 'string' || utils.regCheckId.test(id)) {
2602
- return typeof callback === 'function' && setImmediate(() => callback(`Invalid ID: ${id}`));
2979
+ return tools.maybeCallbackWithError(callback, `Invalid ID: ${id}`);
2603
2980
  }
2604
2981
 
2605
2982
  if (!obj) {
2606
2983
  this.log.warn(this.namespace + ' setObject: Argument object is null');
2607
- return typeof callback === 'function' && setImmediate(() => callback('obj is null'));
2984
+ return tools.maybeCallbackWithError(callback, 'obj is null');
2608
2985
  }
2609
2986
  if (!tools.isObject(obj)) {
2610
2987
  this.log.warn(this.namespace + ' setObject: Argument object is no object: ' + obj);
2611
- return typeof callback === 'function' && setImmediate(() => callback('obj is no object'));
2988
+ return tools.maybeCallbackWithError(callback, 'obj is no object');
2612
2989
  }
2613
2990
  if (!this.client) {
2614
- return typeof callback === 'function' && setImmediate(() => callback(utils.ERRORS.ERROR_DB_CLOSED));
2991
+ return tools.maybeCallbackWithRedisError(callback, utils.ERRORS.ERROR_DB_CLOSED);
2615
2992
  }
2616
2993
  // make a copy of object, because we will modify it
2617
2994
  obj = deepClone(obj);
@@ -2636,17 +3013,29 @@ class ObjectsInRedisClient {
2636
3013
  return tools.maybeCallbackWithError(callback, 'Invalid password for update of vendor information');
2637
3014
  }
2638
3015
 
3016
+ // we need to know if custom has been added/deleted
3017
+ const oldObjHasCustom = oldObj && oldObj.common && oldObj.common && oldObj.common.custom;
3018
+
2639
3019
  // do not delete common settings, like "history" or "mobile". It can be erased only with "null"
2640
3020
  if (oldObj && oldObj.common) {
2641
3021
  this.preserveSettings.forEach(commonSetting => {
2642
3022
  // special case if "custom"
2643
3023
  if (commonSetting === 'custom') {
2644
3024
  // we had broken objects where common.custom was a "non-object" ... check and fix them here, no warning because users will most likely have no idea how to deal with it
2645
- if (oldObj.common.custom !== undefined && oldObj.common.custom !== null && !tools.isObject(oldObj.common.custom)) {
3025
+ if (
3026
+ oldObj.common.custom !== undefined &&
3027
+ oldObj.common.custom !== null &&
3028
+ !tools.isObject(oldObj.common.custom)
3029
+ ) {
2646
3030
  delete oldObj.common.custom;
2647
3031
  }
2648
3032
  // also remove invalid data from new objects ... should not happen because adapter.js checks too
2649
- if (obj.common && obj.common.custom !== undefined && obj.common.custom !== null && !tools.isObject(obj.common.custom)) {
3033
+ if (
3034
+ obj.common &&
3035
+ obj.common.custom !== undefined &&
3036
+ obj.common.custom !== null &&
3037
+ !tools.isObject(obj.common.custom)
3038
+ ) {
2650
3039
  delete obj.common.custom;
2651
3040
  }
2652
3041
 
@@ -2680,9 +3069,11 @@ class ObjectsInRedisClient {
2680
3069
  // remove settings if desired
2681
3070
  if (obj.common && obj.common[commonSetting] === null) {
2682
3071
  delete obj.common[commonSetting];
2683
- } else
2684
- // if old setting present and new setting is absent
2685
- if (oldObj.common[commonSetting] !== undefined && (!obj.common || obj.common[commonSetting] === undefined)) {
3072
+ } else if (
3073
+ // if old setting present and new setting is absent
3074
+ oldObj.common[commonSetting] !== undefined &&
3075
+ (!obj.common || obj.common[commonSetting] === undefined)
3076
+ ) {
2686
3077
  obj.common = obj.common || {};
2687
3078
  obj.common[commonSetting] = oldObj.common[commonSetting];
2688
3079
  }
@@ -2693,19 +3084,19 @@ class ObjectsInRedisClient {
2693
3084
  if (obj.common && obj.common.alias && obj.common.alias.id) {
2694
3085
  if (typeof obj.common.alias.id === 'object') {
2695
3086
  if (typeof obj.common.alias.id.write !== 'string' || typeof obj.common.alias.id.read !== 'string') {
2696
- return typeof callback === 'function' && callback('Invalid alias ID');
3087
+ return tools.maybeCallbackWithError(callback, 'Invalid alias ID');
2697
3088
  }
2698
3089
 
2699
3090
  if (obj.common.alias.id.write.startsWith('alias.') || obj.common.alias.id.read.startsWith('alias.')) {
2700
- return typeof callback === 'function' && callback('Cannot make alias on alias');
3091
+ return tools.maybeCallbackWithError(callback, 'Cannot make alias on alias');
2701
3092
  }
2702
3093
  } else {
2703
3094
  if (typeof obj.common.alias.id !== 'string') {
2704
- return typeof callback === 'function' && callback('Invalid alias ID');
3095
+ return tools.maybeCallbackWithError(callback, 'Invalid alias ID');
2705
3096
  }
2706
3097
 
2707
3098
  if (obj.common.alias.id.startsWith('alias.')) {
2708
- return typeof callback === 'function' && callback('Cannot make alias on alias');
3099
+ return tools.maybeCallbackWithError(callback, 'Cannot make alias on alias');
2709
3100
  }
2710
3101
  }
2711
3102
  }
@@ -2738,7 +3129,39 @@ class ObjectsInRedisClient {
2738
3129
  try {
2739
3130
  const message = JSON.stringify(obj);
2740
3131
 
2741
- await this.client.set(this.objNamespace + id, message);
3132
+ const commands = [];
3133
+ if (this.useSets) {
3134
+ if (obj.type && (!oldObj || !oldObj.type)) {
3135
+ // new object or oldObj had no type -> add to set + set object
3136
+ commands.push(['sadd', `${this.setNamespace}object.type.${obj.type}`, this.objNamespace + id]);
3137
+ } else if (obj.type && oldObj && oldObj.type && oldObj.type !== obj.type) {
3138
+ // the old obj had a type which differs from the new type -> rem old, add new
3139
+ commands.push(
3140
+ ['sadd', `${this.setNamespace}object.type.${obj.type}`, this.objNamespace + id],
3141
+ ['srem', `${this.setNamespace}object.type.${oldObj.type}`, this.objNamespace + id]
3142
+ );
3143
+ } else if (oldObj && oldObj.type && !obj.type) {
3144
+ // the oldObj had a type, the new one has no -> rem
3145
+ commands.push(['srem', `${this.setNamespace}object.type.${obj.type}`, this.objNamespace + id]);
3146
+ }
3147
+
3148
+ if (obj.common && obj.common.custom && !oldObjHasCustom) {
3149
+ // we now have custom, old object had no custom
3150
+ commands.push(['sadd', `${this.setNamespace}object.common.custom`, this.objNamespace + id]);
3151
+ } else if (oldObjHasCustom && (!obj.common || !obj.common.custom)) {
3152
+ // we no longer have custom
3153
+ commands.push(['srem', `${this.setNamespace}object.common.custom`, this.objNamespace + id]);
3154
+ }
3155
+ }
3156
+
3157
+ if (!commands.length) {
3158
+ await this.client.set(this.objNamespace + id, message);
3159
+ } else {
3160
+ // set all commands atomic
3161
+ commands.push(['set', this.objNamespace + id, message]);
3162
+ await this.client.multi(commands).exec();
3163
+ }
3164
+
2742
3165
  //this.settings.connection.enhancedLogging && this.log.silly(this.namespace + ' redis publish ' + this.objNamespace + id + ' ' + message);
2743
3166
  // object updated -> if type changed to meta -> cache
2744
3167
  if (oldObj && oldObj.type === 'meta' && this.existingMetaObjects[id] === false) {
@@ -2747,9 +3170,9 @@ class ObjectsInRedisClient {
2747
3170
 
2748
3171
  await this.client.publish(this.objNamespace + id, message);
2749
3172
 
2750
- return tools.maybeCallbackWithError(callback, null, {id});
3173
+ return tools.maybeCallbackWithError(callback, null, { id });
2751
3174
  } catch (e) {
2752
- return tools.maybeCallbackWithRedisError(callback, e, {id});
3175
+ return tools.maybeCallbackWithRedisError(callback, e, { id });
2753
3176
  }
2754
3177
  }
2755
3178
 
@@ -2772,8 +3195,8 @@ class ObjectsInRedisClient {
2772
3195
  }
2773
3196
  if (!callback) {
2774
3197
  return new Promise((resolve, reject) =>
2775
- this.setObject(id, obj, options, (err, res) =>
2776
- err ? reject(err) : resolve(res)));
3198
+ this.setObject(id, obj, options, (err, res) => (err ? reject(err) : resolve(res)))
3199
+ );
2777
3200
  }
2778
3201
  if (options && options.acl) {
2779
3202
  options.acl = null;
@@ -2782,7 +3205,7 @@ class ObjectsInRedisClient {
2782
3205
  utils.checkObjectRights(this, null, null, options, utils.CONSTS.ACCESS_WRITE, err => {
2783
3206
  // do not use options from checkObjectRights because this will mess up configured default acl
2784
3207
  if (err) {
2785
- typeof callback === 'function' && setImmediate(() => callback(err));
3208
+ return tools.maybeCallbackWithRedisError(callback, err);
2786
3209
  } else {
2787
3210
  return this._setObject(id, obj, options || {}, callback);
2788
3211
  }
@@ -2791,8 +3214,8 @@ class ObjectsInRedisClient {
2791
3214
 
2792
3215
  setObjectAsync(id, obj, options) {
2793
3216
  return new Promise((resolve, reject) =>
2794
- this.setObject(id, obj, options, (err, res) =>
2795
- err ? reject(err) : resolve(res)));
3217
+ this.setObject(id, obj, options, (err, res) => (err ? reject(err) : resolve(res)))
3218
+ );
2796
3219
  }
2797
3220
 
2798
3221
  async _delObject(id, options, callback) {
@@ -2811,7 +3234,8 @@ class ObjectsInRedisClient {
2811
3234
  this.log.warn(`${this.namespace} redis get ${id}, error - ${e.message}`);
2812
3235
  }
2813
3236
 
2814
- if (!oldObj) { // Not existent, so goal reached :-)
3237
+ if (!oldObj) {
3238
+ // Not existent, so goal reached :-)
2815
3239
  return tools.maybeCallback(callback);
2816
3240
  }
2817
3241
 
@@ -2826,7 +3250,34 @@ class ObjectsInRedisClient {
2826
3250
  return tools.maybeCallbackWithError(callback, utils.ERRORS.ERROR_PERMISSION);
2827
3251
  } else {
2828
3252
  try {
2829
- await this.client.del(this.objNamespace + id);
3253
+ const commands = [];
3254
+
3255
+ if (this.useSets) {
3256
+ if (oldObj.type) {
3257
+ // e.g. _design/ has no type
3258
+ // del the object from the set + del object atomic
3259
+ commands.push([
3260
+ 'srem',
3261
+ `${this.setNamespace}object.type.${oldObj.type}`,
3262
+ this.objNamespace + id
3263
+ ]);
3264
+ }
3265
+
3266
+ if (oldObj.common && oldObj.common.custom) {
3267
+ // del the object from "custom" set
3268
+ commands.push(['srem', `${this.setNamespace}object.common.custom`, this.objNamespace + id]);
3269
+ }
3270
+ }
3271
+
3272
+ if (!commands.length) {
3273
+ // only del
3274
+ await this.client.del(this.objNamespace + id);
3275
+ } else {
3276
+ // set all commands atomic
3277
+ commands.push(['del', this.objNamespace + id]);
3278
+ await this.client.multi(commands).exec();
3279
+ }
3280
+
2830
3281
  // object has been deleted -> remove from cached meta if there
2831
3282
  if (this.existingMetaObjects[id]) {
2832
3283
  this.existingMetaObjects[id] = false;
@@ -2847,8 +3298,8 @@ class ObjectsInRedisClient {
2847
3298
  }
2848
3299
  if (!callback) {
2849
3300
  return new Promise((resolve, reject) =>
2850
- this.delObject(id, options, (err, obj) =>
2851
- err ? reject(err) : resolve(obj)));
3301
+ this.delObject(id, options, (err, obj) => (err ? reject(err) : resolve(obj)))
3302
+ );
2852
3303
  }
2853
3304
 
2854
3305
  if (options && options.acl) {
@@ -2864,9 +3315,9 @@ class ObjectsInRedisClient {
2864
3315
  }
2865
3316
 
2866
3317
  delObjectAsync(id, options) {
2867
- return /** @type {Promise<void>} */ (new Promise((resolve, reject) =>
2868
- this.delObject(id, options, err =>
2869
- err ? reject(err) : resolve())));
3318
+ return /** @type {Promise<void>} */ (
3319
+ new Promise((resolve, reject) => this.delObject(id, options, err => (err ? reject(err) : resolve())))
3320
+ );
2870
3321
  }
2871
3322
 
2872
3323
  // this function is very ineffective. Because reads all objects and then process them
@@ -2906,7 +3357,7 @@ class ObjectsInRedisClient {
2906
3357
 
2907
3358
  params = params || {};
2908
3359
  params.startkey = params.startkey || '';
2909
- params.endkey = params.endkey || '\u9999';
3360
+ params.endkey = params.endkey || '\u9999';
2910
3361
  const wildcardPos = params.endkey.indexOf('\u9999');
2911
3362
  let wildCardLastPos = true;
2912
3363
  if (wildcardPos !== -1 && wildcardPos !== params.endkey.length - 1) {
@@ -2920,7 +3371,13 @@ class ObjectsInRedisClient {
2920
3371
  }
2921
3372
 
2922
3373
  // filter by type
2923
- if (wildCardLastPos && func && func.map && this.scripts.filter && (m = func.map.match(/if\s\(doc\.type\s?===?\s?'(\w+)'\)\semit\(([^,]+),\s?doc\s?\)/))) {
3374
+ if (
3375
+ wildCardLastPos &&
3376
+ func &&
3377
+ func.map &&
3378
+ this.scripts.filter &&
3379
+ (m = func.map.match(/if\s\(doc\.type\s?===?\s?'(\w+)'\)\semit\(([^,]+),\s?doc\s?\)/))
3380
+ ) {
2924
3381
  let cursor = '0';
2925
3382
  let filterRequired = true;
2926
3383
  do {
@@ -2930,7 +3387,16 @@ class ObjectsInRedisClient {
2930
3387
 
2931
3388
  let objs;
2932
3389
  try {
2933
- objs = await this.client.evalsha([this.scripts.filter, 5, this.objNamespace, params.startkey, params.endkey, m[1], cursor]);
3390
+ objs = await this.client.evalsha([
3391
+ this.scripts.filter,
3392
+ 6,
3393
+ this.objNamespace,
3394
+ params.startkey,
3395
+ params.endkey,
3396
+ m[1],
3397
+ cursor,
3398
+ `${this.setNamespace}object.type.${m[1]}`
3399
+ ]);
2934
3400
  } catch (e) {
2935
3401
  this.log.warn(`${this.namespace} Cannot get view: ${e.message}`);
2936
3402
  }
@@ -2949,23 +3415,23 @@ class ObjectsInRedisClient {
2949
3415
  obj = JSON.parse(obj);
2950
3416
  } catch {
2951
3417
  this.log.error(`${this.namespace} Cannot parse JSON: ${obj}`);
2952
- return {id: 'parseError', value: null};
3418
+ return { id: 'parseError', value: null };
2953
3419
  }
2954
3420
  if (m[2] && m[2].trim() === 'doc._id') {
2955
- return {id: obj._id, value: obj};
3421
+ return { id: obj._id, value: obj };
2956
3422
  } else if (m[2] && m[2].trim() === 'doc.common.name' && obj.common) {
2957
3423
  if (typeof obj.common.name === 'object') {
2958
3424
  if (obj.common.name.en) {
2959
- return {id: obj.common.name.en, value: obj};
3425
+ return { id: obj.common.name.en, value: obj };
2960
3426
  } else {
2961
- return {id: JSON.stringify(obj.common.name), value: obj};
3427
+ return { id: JSON.stringify(obj.common.name), value: obj };
2962
3428
  }
2963
3429
  } else {
2964
- return {id: obj.common.name, value: obj};
3430
+ return { id: obj.common.name, value: obj };
2965
3431
  }
2966
3432
  } else {
2967
3433
  this.log.error(`${this.namespace} Cannot filter "${m[2]}": ${JSON.stringify(obj)}`);
2968
- return {id: 'parseError', value: null};
3434
+ return { id: 'parseError', value: null };
2969
3435
  }
2970
3436
  });
2971
3437
  if (currRows.length) {
@@ -2982,7 +3448,7 @@ class ObjectsInRedisClient {
2982
3448
  }
2983
3449
  }
2984
3450
  if (max !== null) {
2985
- result.rows = [{id: '_stats', value: {max: max}}];
3451
+ result.rows = [{ id: '_stats', value: { max: max } }];
2986
3452
  } else {
2987
3453
  result.rows = [];
2988
3454
  }
@@ -2991,9 +3457,14 @@ class ObjectsInRedisClient {
2991
3457
  // apply filter if needed
2992
3458
  result.rows = filterEntries(result.rows, filterRequired);
2993
3459
  return tools.maybeCallbackWithError(callback, null, result);
2994
- } else
2995
- // filter by script
2996
- if (wildCardLastPos && func && func.map && this.scripts.script && func.map.indexOf('doc.common.engineType') !== -1) {
3460
+ } else if (
3461
+ // filter by script
3462
+ wildCardLastPos &&
3463
+ func &&
3464
+ func.map &&
3465
+ this.scripts.script &&
3466
+ func.map.indexOf('doc.common.engineType') !== -1
3467
+ ) {
2997
3468
  let cursor = '0';
2998
3469
  let filterRequired = true;
2999
3470
  do {
@@ -3002,9 +3473,17 @@ class ObjectsInRedisClient {
3002
3473
  }
3003
3474
  let objs;
3004
3475
  try {
3005
- objs = await this.client.evalsha([this.scripts.script, 4, this.objNamespace, params.startkey, params.endkey, cursor]);
3476
+ objs = await this.client.evalsha([
3477
+ this.scripts.script,
3478
+ 5,
3479
+ this.objNamespace,
3480
+ params.startkey,
3481
+ params.endkey,
3482
+ cursor,
3483
+ `${this.setNamespace}object.type.script`
3484
+ ]);
3006
3485
  } catch (e) {
3007
- this.log.warn(`${this.namespace} Cannot get view: ${e.message}`);
3486
+ this.log.warn(`${this.namespace} Cannot get "scripts" view: ${e.message}`);
3008
3487
  }
3009
3488
  // if real redis we will have e.g. [[objs..], '0'], else [{}, .., {}]
3010
3489
  if (Array.isArray(objs[0])) {
@@ -3020,9 +3499,9 @@ class ObjectsInRedisClient {
3020
3499
  obj = JSON.parse(obj);
3021
3500
  } catch {
3022
3501
  this.log.error(`${this.namespace} Cannot parse JSON: ${obj}`);
3023
- return {id: 'parseError', value: null};
3502
+ return { id: 'parseError', value: null };
3024
3503
  }
3025
- return {id: obj._id, value: obj};
3504
+ return { id: obj._id, value: obj };
3026
3505
  });
3027
3506
  if (currRows.length) {
3028
3507
  result.rows = [...result.rows, ...currRows];
@@ -3032,9 +3511,15 @@ class ObjectsInRedisClient {
3032
3511
  // apply filter if needed
3033
3512
  result.rows = filterEntries(result.rows, filterRequired);
3034
3513
  return tools.maybeCallbackWithError(callback, null, result);
3035
- } else
3036
- // filter by hm-rega programs
3037
- if (wildCardLastPos && func && func.map && this.scripts.programs && func.map.indexOf('doc.native.TypeName === \'PROGRAM\'') !== -1) {
3514
+ } else if (
3515
+ // filter by hm-rega programs
3516
+
3517
+ wildCardLastPos &&
3518
+ func &&
3519
+ func.map &&
3520
+ this.scripts.programs &&
3521
+ func.map.indexOf("doc.native.TypeName === 'PROGRAM'") !== -1
3522
+ ) {
3038
3523
  let cursor = '0';
3039
3524
  let filterRequired = true;
3040
3525
  do {
@@ -3044,7 +3529,15 @@ class ObjectsInRedisClient {
3044
3529
 
3045
3530
  let objs;
3046
3531
  try {
3047
- objs = await this.client.evalsha([this.scripts.programs, 4, this.objNamespace, params.startkey, params.endkey, cursor]);
3532
+ objs = await this.client.evalsha([
3533
+ this.scripts.programs,
3534
+ 5,
3535
+ `${this.objNamespace}hm-rega.`,
3536
+ params.startkey,
3537
+ params.endkey,
3538
+ cursor,
3539
+ `${this.setNamespace}object.type.channel`
3540
+ ]);
3048
3541
  } catch (e) {
3049
3542
  this.log.warn(`${this.namespace} Cannot get view: ${e.message}`);
3050
3543
  }
@@ -3062,21 +3555,26 @@ class ObjectsInRedisClient {
3062
3555
  obj = JSON.parse(obj);
3063
3556
  } catch {
3064
3557
  this.log.error(`${this.namespace} Cannot parse JSON: ${obj}`);
3065
- return {id: 'parseError', value: null};
3558
+ return { id: 'parseError', value: null };
3066
3559
  }
3067
- return {id: obj._id, value: obj};
3560
+ return { id: obj._id, value: obj };
3068
3561
  });
3069
3562
  if (currRows.length) {
3070
3563
  result.rows = [...result.rows, ...currRows];
3071
3564
  } // endIf
3072
- } while(cursor !== '0');
3565
+ } while (cursor !== '0');
3073
3566
 
3074
3567
  // apply filter if needed
3075
3568
  result.rows = filterEntries(result.rows, filterRequired);
3076
3569
  return tools.maybeCallbackWithError(callback, null, result);
3077
- } else
3078
- // filter by hm-rega variables
3079
- if (wildCardLastPos && func && func.map && this.scripts.variables && func.map.indexOf('doc.native.TypeName === \'ALARMDP\'') !== -1) {
3570
+ } else if (
3571
+ // filter by hm-rega variables
3572
+ wildCardLastPos &&
3573
+ func &&
3574
+ func.map &&
3575
+ this.scripts.variables &&
3576
+ func.map.indexOf("doc.native.TypeName === 'ALARMDP'") !== -1
3577
+ ) {
3080
3578
  let cursor = '0';
3081
3579
  let filterRequired = true;
3082
3580
  do {
@@ -3086,7 +3584,15 @@ class ObjectsInRedisClient {
3086
3584
 
3087
3585
  let objs;
3088
3586
  try {
3089
- objs = await this.client.evalsha([this.scripts.variables, 4, this.objNamespace, params.startkey, params.endkey, cursor]);
3587
+ objs = await this.client.evalsha([
3588
+ this.scripts.variables,
3589
+ 5,
3590
+ `${this.objNamespace}hm-rega.`,
3591
+ params.startkey,
3592
+ params.endkey,
3593
+ cursor,
3594
+ `${this.setNamespace}object.type.state`
3595
+ ]);
3090
3596
  } catch (e) {
3091
3597
  this.log.warn(`${this.namespace} Cannot get view ${e.message}`);
3092
3598
  }
@@ -3104,9 +3610,9 @@ class ObjectsInRedisClient {
3104
3610
  obj = JSON.parse(obj);
3105
3611
  } catch {
3106
3612
  this.log.error(`${this.namespace} Cannot parse JSON: ${obj}`);
3107
- return {id: 'parseError', value: null};
3613
+ return { id: 'parseError', value: null };
3108
3614
  }
3109
- return {id: obj._id, value: obj};
3615
+ return { id: obj._id, value: obj };
3110
3616
  });
3111
3617
  if (currRows.length) {
3112
3618
  result.rows = [...result.rows, ...currRows];
@@ -3116,9 +3622,14 @@ class ObjectsInRedisClient {
3116
3622
  // apply filter if needed
3117
3623
  result.rows = filterEntries(result.rows, filterRequired);
3118
3624
  typeof callback === 'function' && callback(null, result);
3119
- } else
3120
- // filter by custom, redis also returns if common.custom is not present
3121
- if (wildCardLastPos && func && func.map && this.scripts.custom && func.map.indexOf('doc.common.custom') !== -1) {
3625
+ } else if (
3626
+ // filter by custom, redis also returns if common.custom is not present
3627
+ wildCardLastPos &&
3628
+ func &&
3629
+ func.map &&
3630
+ this.scripts.custom &&
3631
+ func.map.indexOf('doc.common.custom') !== -1
3632
+ ) {
3122
3633
  let cursor = '0';
3123
3634
  let filterRequired = true;
3124
3635
  do {
@@ -3127,7 +3638,15 @@ class ObjectsInRedisClient {
3127
3638
  }
3128
3639
  let objs;
3129
3640
  try {
3130
- objs = await this.client.evalsha([this.scripts.custom, 4, this.objNamespace, params.startkey, params.endkey, cursor]);
3641
+ objs = await this.client.evalsha([
3642
+ this.scripts.custom,
3643
+ 5,
3644
+ this.objNamespace,
3645
+ params.startkey,
3646
+ params.endkey,
3647
+ cursor,
3648
+ `${this.setNamespace}object.common.custom`
3649
+ ]);
3131
3650
  } catch (e) {
3132
3651
  this.log.warn(`${this.namespace} Cannot get view: ${e.message}`);
3133
3652
  }
@@ -3148,7 +3667,7 @@ class ObjectsInRedisClient {
3148
3667
  obj = null;
3149
3668
  }
3150
3669
  if (obj && obj.common && obj.common.custom) {
3151
- result.rows.push({id: obj._id, value: obj.common.custom});
3670
+ result.rows.push({ id: obj._id, value: obj.common.custom });
3152
3671
  }
3153
3672
  });
3154
3673
  } while (cursor !== '0');
@@ -3158,13 +3677,16 @@ class ObjectsInRedisClient {
3158
3677
  return tools.maybeCallbackWithError(callback, null, result);
3159
3678
  } else {
3160
3679
  if (!wildCardLastPos) {
3161
- this.log.debug(`${this.namespace} Search can't be optimized because wildcard not at the end, fallback to keys!: ${func.map}`);
3680
+ this.log.debug(
3681
+ `${this.namespace} Search can't be optimized because wildcard not at the end, fallback to keys!: ${func.map}`
3682
+ );
3162
3683
  } else {
3163
3684
  this.log.debug(`${this.namespace} No suitable Lua script, fallback to keys!: ${func.map}`);
3164
3685
  }
3165
3686
 
3166
3687
  let searchKeys = this.objNamespace + '*';
3167
- if (wildcardPos !== -1) { // Wildcard included
3688
+ if (wildcardPos !== -1) {
3689
+ // Wildcard included
3168
3690
  searchKeys = this.objNamespace + params.endkey.replace(/\u9999/g, '*');
3169
3691
  }
3170
3692
 
@@ -3181,7 +3703,7 @@ class ObjectsInRedisClient {
3181
3703
 
3182
3704
  const endAfterWildcard = params.endkey.substr(wildcardPos + 1);
3183
3705
  params.startkey = this.objNamespace + params.startkey;
3184
- params.endkey = this.objNamespace + params.endkey;
3706
+ params.endkey = this.objNamespace + params.endkey;
3185
3707
 
3186
3708
  keys = keys.sort().filter(key => {
3187
3709
  if (key && !utils.regCheckId.test(key)) {
@@ -3189,11 +3711,10 @@ class ObjectsInRedisClient {
3189
3711
  if (params.startkey && key < params.startkey) {
3190
3712
  return false;
3191
3713
  }
3192
- if (params.endkey && key > params.endkey) {
3714
+ if (params.endkey && key > params.endkey) {
3193
3715
  return false;
3194
3716
  }
3195
- } else
3196
- if (params && wildcardPos === 0) {
3717
+ } else if (params && wildcardPos === 0) {
3197
3718
  if (!key.endsWith(endAfterWildcard)) {
3198
3719
  return false;
3199
3720
  }
@@ -3212,7 +3733,7 @@ class ObjectsInRedisClient {
3212
3733
  }
3213
3734
 
3214
3735
  const _emit_ = (id, obj) => {
3215
- result.rows.push({id: id, value: obj});
3736
+ result.rows.push({ id: id, value: obj });
3216
3737
  };
3217
3738
 
3218
3739
  const f = eval('(' + func.map.replace(/^function\(([a-z0-9A-Z_]+)\)/g, 'function($1, emit)') + ')');
@@ -3246,7 +3767,7 @@ class ObjectsInRedisClient {
3246
3767
  }
3247
3768
  }
3248
3769
  if (max !== null) {
3249
- result.rows = [{id: '_stats', value: {max: max}}];
3770
+ result.rows = [{ id: '_stats', value: { max: max } }];
3250
3771
  } else {
3251
3772
  result.rows = [];
3252
3773
  }
@@ -3262,8 +3783,8 @@ class ObjectsInRedisClient {
3262
3783
  }
3263
3784
  if (!callback) {
3264
3785
  return new Promise((resolve, reject) =>
3265
- this._applyView(func, params, options, (err, obj) =>
3266
- err ? reject(err) : resolve(obj)));
3786
+ this._applyView(func, params, options, (err, obj) => (err ? reject(err) : resolve(obj)))
3787
+ );
3267
3788
  }
3268
3789
 
3269
3790
  if (options && options.acl) {
@@ -3283,7 +3804,7 @@ class ObjectsInRedisClient {
3283
3804
 
3284
3805
  async _getObjectView(design, search, params, options, callback) {
3285
3806
  if (!this.client) {
3286
- return typeof callback === 'function' && setImmediate(() => callback(utils.ERRORS.ERROR_DB_CLOSED));
3807
+ return tools.maybeCallbackWithRedisError(callback, utils.ERRORS.ERROR_DB_CLOSED);
3287
3808
  }
3288
3809
 
3289
3810
  let obj;
@@ -3291,7 +3812,7 @@ class ObjectsInRedisClient {
3291
3812
  obj = await this.client.get(this.objNamespace + '_design/' + design);
3292
3813
  } catch (e) {
3293
3814
  this.log.error(`${this.namespace} Cannot find view "${design}" for search "${search}" : ${e.message}`);
3294
- return tools.maybeCallbackWithRedisError(callback, new Error(`Cannot find view "${design}"`));
3815
+ return tools.maybeCallbackWithError(callback, new Error(`Cannot find view "${design}"`));
3295
3816
  }
3296
3817
 
3297
3818
  if (obj) {
@@ -3299,14 +3820,20 @@ class ObjectsInRedisClient {
3299
3820
  obj = JSON.parse(obj);
3300
3821
  } catch {
3301
3822
  this.log.error(`${this.namespace} Cannot parse JSON: ${obj}`);
3302
- return tools.maybeCallbackWithError(callback, new Error(`Cannot parse JSON: "_design/${design}" / "${obj}"`));
3823
+ return tools.maybeCallbackWithError(
3824
+ callback,
3825
+ new Error(`Cannot parse JSON: "_design/${design}" / "${obj}"`)
3826
+ );
3303
3827
  }
3304
3828
 
3305
3829
  if (obj.views && obj.views[search]) {
3306
3830
  return this._applyViewFunc(obj.views[search], params, options, callback);
3307
3831
  } else {
3308
3832
  this.log.error(`${this.namespace} Cannot find search "${search}" in "${design}"`);
3309
- return tools.maybeCallbackWithError(callback, new Error(`Cannot find search "${search}" in "${design}"`));
3833
+ return tools.maybeCallbackWithError(
3834
+ callback,
3835
+ new Error(`Cannot find search "${search}" in "${design}"`)
3836
+ );
3310
3837
  }
3311
3838
  } else {
3312
3839
  this.log.error(`${this.namespace} Cannot find view "${design}" for search "${search}"`);
@@ -3321,8 +3848,8 @@ class ObjectsInRedisClient {
3321
3848
  }
3322
3849
  if (!callback) {
3323
3850
  return new Promise((resolve, reject) =>
3324
- this.getObjectView(design, search, params, options, (err, obj) =>
3325
- err ? reject(err) : resolve(obj)));
3851
+ this.getObjectView(design, search, params, options, (err, obj) => (err ? reject(err) : resolve(obj)))
3852
+ );
3326
3853
  }
3327
3854
 
3328
3855
  if (options && options.acl) {
@@ -3332,7 +3859,7 @@ class ObjectsInRedisClient {
3332
3859
  if (typeof callback === 'function') {
3333
3860
  utils.checkObjectRights(this, null, null, options, 'list', (err, options) => {
3334
3861
  if (err) {
3335
- typeof callback === 'function' && setImmediate(() => callback(err));
3862
+ return tools.maybeCallbackWithRedisError(callback, err);
3336
3863
  } else {
3337
3864
  return this._getObjectView(design, search, params, options, callback);
3338
3865
  }
@@ -3342,8 +3869,8 @@ class ObjectsInRedisClient {
3342
3869
 
3343
3870
  getObjectViewAsync(design, search, params, options) {
3344
3871
  return new Promise((resolve, reject) =>
3345
- this.getObjectView(design, search, params, options, (err, arr) =>
3346
- err ? reject(err) : resolve(arr)));
3872
+ this.getObjectView(design, search, params, options, (err, arr) => (err ? reject(err) : resolve(arr)))
3873
+ );
3347
3874
  }
3348
3875
 
3349
3876
  async _getObjectList(params, options, callback) {
@@ -3354,7 +3881,10 @@ class ObjectsInRedisClient {
3354
3881
  params = params || {};
3355
3882
  params.startkey = params.startkey || '';
3356
3883
  params.endkey = params.endkey || '\u9999';
3357
- const pattern = (params.endkey.substring(0, params.startkey.length) === params.startkey) ? this.objNamespace + params.startkey + '*' : this.objNamespace + '*';
3884
+ const pattern =
3885
+ params.endkey.substring(0, params.startkey.length) === params.startkey
3886
+ ? this.objNamespace + params.startkey + '*'
3887
+ : this.objNamespace + '*';
3358
3888
 
3359
3889
  // todo: use lua script for this
3360
3890
  let keys;
@@ -3408,7 +3938,7 @@ class ObjectsInRedisClient {
3408
3938
  if (!objs[r] || !utils.checkObject(objs[r], options, utils.CONSTS.ACCESS_READ)) {
3409
3939
  continue;
3410
3940
  }
3411
- result.rows.push({id: objs[r]._id, value: objs[r], doc: objs[r]});
3941
+ result.rows.push({ id: objs[r]._id, value: objs[r], doc: objs[r] });
3412
3942
  }
3413
3943
  }
3414
3944
  return tools.maybeCallbackWithError(callback, null, result);
@@ -3421,8 +3951,8 @@ class ObjectsInRedisClient {
3421
3951
  }
3422
3952
  if (!callback) {
3423
3953
  return new Promise((resolve, reject) =>
3424
- this.getObjectList(params, options, (err, obj) =>
3425
- err ? reject(err) : resolve(obj)));
3954
+ this.getObjectList(params, options, (err, obj) => (err ? reject(err) : resolve(obj)))
3955
+ );
3426
3956
  }
3427
3957
 
3428
3958
  if (options && options.acl) {
@@ -3432,7 +3962,7 @@ class ObjectsInRedisClient {
3432
3962
  if (typeof callback === 'function') {
3433
3963
  utils.checkObjectRights(this, null, null, options, 'list', (err, options) => {
3434
3964
  if (err) {
3435
- typeof callback === 'function' && setImmediate(() => callback(err));
3965
+ return tools.maybeCallbackWithRedisError(callback, err);
3436
3966
  } else {
3437
3967
  return this._getObjectList(params, options, callback);
3438
3968
  }
@@ -3442,17 +3972,17 @@ class ObjectsInRedisClient {
3442
3972
 
3443
3973
  getObjectListAsync(params, options) {
3444
3974
  return new Promise((resolve, reject) =>
3445
- this.getObjectList(params, options, (err, arr) =>
3446
- err ? reject(err) : resolve(arr)));
3975
+ this.getObjectList(params, options, (err, arr) => (err ? reject(err) : resolve(arr)))
3976
+ );
3447
3977
  }
3448
3978
 
3449
3979
  // could be optimised, to read object only once. Now it will read 3 times
3450
3980
  async _extendObject(id, obj, options, callback, _iteration) {
3451
3981
  if (!id || typeof id !== 'string' || utils.regCheckId.test(id)) {
3452
- return typeof callback === 'function' && setImmediate(() => callback(`Invalid ID: ${id}`));
3982
+ return tools.maybeCallbackWithError(callback, `Invalid ID: ${id}`);
3453
3983
  }
3454
3984
  if (!this.client) {
3455
- return typeof callback === 'function' && setImmediate (() => callback(utils.ERRORS.ERROR_DB_CLOSED));
3985
+ return tools.maybeCallbackWithRedisError(callback, utils.ERRORS.ERROR_DB_CLOSED);
3456
3986
  }
3457
3987
 
3458
3988
  let oldObj;
@@ -3479,12 +4009,24 @@ class ObjectsInRedisClient {
3479
4009
  _oldObj = deepClone(oldObj);
3480
4010
  }
3481
4011
 
4012
+ // we need to know if custom has been added/deleted
4013
+ const oldObjHasCustom = oldObj && oldObj.common && oldObj.common && oldObj.common.custom;
4014
+
3482
4015
  oldObj = oldObj || {};
3483
4016
  obj = deepClone(obj); // copy here to prevent "sandboxed" objects from JavaScript adapter
3484
- if (oldObj.common && oldObj.common.custom !== undefined && oldObj.common.custom !== null && !tools.isObject(oldObj.common.custom)) {
4017
+ if (
4018
+ oldObj.common &&
4019
+ oldObj.common.custom !== undefined &&
4020
+ oldObj.common.custom !== null &&
4021
+ !tools.isObject(oldObj.common.custom)
4022
+ ) {
4023
+ // custom has to be an object, else clean up
3485
4024
  delete oldObj.common.custom;
3486
4025
  }
3487
4026
 
4027
+ // we need to check if type has changed
4028
+ const oldType = oldObj.type;
4029
+
3488
4030
  oldObj = extend(true, oldObj, obj);
3489
4031
  oldObj._id = id;
3490
4032
 
@@ -3503,7 +4045,9 @@ class ObjectsInRedisClient {
3503
4045
  oldObj.acl.ownerGroup = null;
3504
4046
  return this.getUserGroup(options.owner, (user, groups /*, permissions */) => {
3505
4047
  if (!groups || !groups[0]) {
3506
- options.ownerGroup = (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) || utils.CONSTS.SYSTEM_ADMIN_GROUP;
4048
+ options.ownerGroup =
4049
+ (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) ||
4050
+ utils.CONSTS.SYSTEM_ADMIN_GROUP;
3507
4051
  } else {
3508
4052
  options.ownerGroup = groups[0];
3509
4053
  }
@@ -3520,30 +4064,62 @@ class ObjectsInRedisClient {
3520
4064
  if (obj.common && obj.common.alias && obj.common.alias.id) {
3521
4065
  if (typeof obj.common.alias.id === 'object') {
3522
4066
  if (typeof obj.common.alias.id.write !== 'string' || typeof obj.common.alias.id.read !== 'string') {
3523
- return typeof callback === 'function' && callback('Invalid alias ID');
4067
+ return tools.maybeCallbackWithError(callback, 'Invalid alias ID');
3524
4068
  }
3525
4069
 
3526
4070
  if (obj.common.alias.id.write.startsWith('alias.') || obj.common.alias.id.read.startsWith('alias.')) {
3527
- return typeof callback === 'function' && callback('Cannot make alias on alias');
4071
+ return tools.maybeCallbackWithError(callback, 'Cannot make alias on alias');
3528
4072
  }
3529
4073
  } else {
3530
4074
  if (typeof obj.common.alias.id !== 'string') {
3531
- return typeof callback === 'function' && callback('Invalid alias ID');
4075
+ return tools.maybeCallbackWithError(callback, 'Invalid alias ID');
3532
4076
  }
3533
4077
 
3534
4078
  if (obj.common.alias.id.startsWith('alias.')) {
3535
- return typeof callback === 'function' && callback('Cannot make alias on alias');
4079
+ return tools.maybeCallbackWithError(callback, 'Cannot make alias on alias');
3536
4080
  }
3537
4081
  }
3538
4082
  }
3539
4083
 
3540
4084
  if (_oldObj && !tools.checkNonEditable(_oldObj, oldObj)) {
3541
- return typeof callback === 'function' && callback('Invalid password for update of vendor information');
4085
+ return tools.maybeCallbackWithError(callback, 'Invalid password for update of vendor information');
3542
4086
  }
3543
4087
  const message = JSON.stringify(oldObj);
3544
4088
 
3545
4089
  try {
3546
- await this.client.set(this.objNamespace + id, message);
4090
+ const commands = [];
4091
+ if (this.useSets) {
4092
+ // what is called oldObj is acutally the obj we set, because it has been extended
4093
+ if (oldObj.type && !oldType) {
4094
+ // new object or oldObj had no type -> add to set + set object
4095
+ commands.push(['sadd', `${this.setNamespace}object.type.${obj.type}`, this.objNamespace + id]);
4096
+ } else if (oldObj.type && oldType && oldObj.type !== oldType) {
4097
+ // the old obj had a type which differs from the new type -> rem old, add new
4098
+ commands.push(
4099
+ ['sadd', `${this.setNamespace}object.type.${obj.type}`, this.objNamespace + id],
4100
+ ['srem', `${this.setNamespace}object.type.${oldObj.type}`, this.objNamespace + id]
4101
+ );
4102
+ } else if (oldType && !oldObj.type) {
4103
+ // the oldObj had a type, the new one has no -> rem
4104
+ commands.push(['srem', `${this.setNamespace}object.type.${obj.type}`, this.objNamespace + id]);
4105
+ }
4106
+ }
4107
+
4108
+ if (oldObj.common && oldObj.common.custom && !oldObjHasCustom) {
4109
+ // we now have custom, old object had no custom
4110
+ commands.push(['sadd', `${this.setNamespace}object.common.custom`, this.objNamespace + id]);
4111
+ } else if (oldObjHasCustom && (!oldObj.common || !oldObj.common.custom)) {
4112
+ // we no longer have custom
4113
+ commands.push(['srem', `${this.setNamespace}object.common.custom`, this.objNamespace + id]);
4114
+ }
4115
+
4116
+ if (!commands.length) {
4117
+ await this.client.set(this.objNamespace + id, message);
4118
+ } else {
4119
+ // set all commands atomic
4120
+ commands.push(['set', this.objNamespace + id, message]);
4121
+ await this.client.multi(commands).exec();
4122
+ }
3547
4123
 
3548
4124
  // extended -> if its now type meta and currently marked as not -> cache
3549
4125
  if (this.existingMetaObjects[id] === false && oldObj && oldObj.type === 'meta') {
@@ -3551,7 +4127,7 @@ class ObjectsInRedisClient {
3551
4127
  }
3552
4128
  //this.settings.connection.enhancedLogging && this.log.silly(this.namespace + ' redis publish ' + this.objNamespace + id + ' ' + message);
3553
4129
  await this.client.publish(this.objNamespace + id, message);
3554
- return tools.maybeCallbackWithError(callback, null, {id: id, value: oldObj}, id);
4130
+ return tools.maybeCallbackWithError(callback, null, { id: id, value: oldObj }, id);
3555
4131
  } catch (e) {
3556
4132
  return tools.maybeCallbackWithRedisError(callback, e);
3557
4133
  }
@@ -3564,8 +4140,8 @@ class ObjectsInRedisClient {
3564
4140
  }
3565
4141
  if (!callback) {
3566
4142
  return new Promise((resolve, reject) =>
3567
- this.extendObject(id, obj, options, (err, res) =>
3568
- err ? reject(err) : resolve(res)));
4143
+ this.extendObject(id, obj, options, (err, res) => (err ? reject(err) : resolve(res)))
4144
+ );
3569
4145
  }
3570
4146
 
3571
4147
  if (options && options.acl) {
@@ -3574,7 +4150,7 @@ class ObjectsInRedisClient {
3574
4150
 
3575
4151
  utils.checkObjectRights(this, null, null, options, utils.CONSTS.ACCESS_WRITE, (err, options) => {
3576
4152
  if (err) {
3577
- typeof callback === 'function' && setImmediate(() => callback(err));
4153
+ return tools.maybeCallbackWithRedisError(callback, err);
3578
4154
  } else {
3579
4155
  return this._extendObject(id, obj, options, callback);
3580
4156
  }
@@ -3583,8 +4159,8 @@ class ObjectsInRedisClient {
3583
4159
 
3584
4160
  extendObjectAsync(id, obj, options) {
3585
4161
  return new Promise((resolve, reject) =>
3586
- this.extendObject(id, obj, options, (err, res) =>
3587
- err ? reject(err) : resolve(res)));
4162
+ this.extendObject(id, obj, options, (err, res) => (err ? reject(err) : resolve(res)))
4163
+ );
3588
4164
  }
3589
4165
 
3590
4166
  setConfig(id, obj, options, callback) {
@@ -3606,33 +4182,45 @@ class ObjectsInRedisClient {
3606
4182
  _findObject(idOrName, type, options, callback) {
3607
4183
  this._getObject(idOrName, options, (err, obj) => {
3608
4184
  // Assume it is ID
3609
- if (obj && utils.checkObject(obj, options, utils.CONSTS.ACCESS_READ) && (!type || (obj.common && obj.common.type === type))) {
4185
+ if (
4186
+ obj &&
4187
+ utils.checkObject(obj, options, utils.CONSTS.ACCESS_READ) &&
4188
+ (!type || (obj.common && obj.common.type === type))
4189
+ ) {
3610
4190
  return tools.maybeCallbackWithError(callback, null, idOrName, obj.common.name);
3611
4191
  } else {
3612
- this._getKeys('*', options, async (err, keys) => {
3613
- let objs;
3614
- try {
3615
- objs = await this.client.mget(keys);
3616
- } catch (e) {
3617
- return tools.maybeCallbackWithRedisError(callback, e);
3618
- }
3619
- objs = objs || [];
3620
- // Assume it is name
3621
- for (let i = 0; i < keys.length; i++) {
4192
+ this._getKeys(
4193
+ '*',
4194
+ options,
4195
+ async (err, keys) => {
4196
+ let objs;
3622
4197
  try {
3623
- objs[i] = JSON.parse(objs[i]);
3624
- } catch {
3625
- this.log.error(`${this.namespace} Cannot parse JSON ${keys[i]}: ${objs[i]}`);
3626
- continue;
4198
+ objs = await this.client.mget(keys);
4199
+ } catch (e) {
4200
+ return tools.maybeCallbackWithRedisError(callback, e);
3627
4201
  }
3628
- if (objs[i] && objs[i].common &&
4202
+ objs = objs || [];
4203
+ // Assume it is name
4204
+ for (let i = 0; i < keys.length; i++) {
4205
+ try {
4206
+ objs[i] = JSON.parse(objs[i]);
4207
+ } catch {
4208
+ this.log.error(`${this.namespace} Cannot parse JSON ${keys[i]}: ${objs[i]}`);
4209
+ continue;
4210
+ }
4211
+ if (
4212
+ objs[i] &&
4213
+ objs[i].common &&
3629
4214
  objs[i].common.name === idOrName &&
3630
- (!type || objs[i].common.type === type)) {
3631
- return tools.maybeCallbackWithError(callback, null, objs[i]._id, idOrName);
4215
+ (!type || objs[i].common.type === type)
4216
+ ) {
4217
+ return tools.maybeCallbackWithError(callback, null, objs[i]._id, idOrName);
4218
+ }
3632
4219
  }
3633
- }
3634
- return tools.maybeCallbackWithError(callback, null, null, idOrName);
3635
- }, true);
4220
+ return tools.maybeCallbackWithError(callback, null, null, idOrName);
4221
+ },
4222
+ true
4223
+ );
3636
4224
  }
3637
4225
  });
3638
4226
  }
@@ -3649,8 +4237,8 @@ class ObjectsInRedisClient {
3649
4237
  }
3650
4238
  if (!callback) {
3651
4239
  return new Promise((resolve, reject) =>
3652
- this.findObject(idOrName, type, options, (err, id, _idOrName) =>
3653
- err ? reject(err) : resolve(id)));
4240
+ this.findObject(idOrName, type, options, (err, id, _idOrName) => (err ? reject(err) : resolve(id)))
4241
+ );
3654
4242
  }
3655
4243
 
3656
4244
  if (options && options.acl) {
@@ -3670,8 +4258,8 @@ class ObjectsInRedisClient {
3670
4258
 
3671
4259
  findObjectAsync(idOrName, type, options) {
3672
4260
  return new Promise((resolve, reject) =>
3673
- this.findObject(idOrName, type, options, (err, id, _idOrName) =>
3674
- err ? reject(err) : resolve(id)));
4261
+ this.findObject(idOrName, type, options, (err, id, _idOrName) => (err ? reject(err) : resolve(id)))
4262
+ );
3675
4263
  }
3676
4264
 
3677
4265
  // can be called only from js-controller
@@ -3729,10 +4317,10 @@ class ObjectsInRedisClient {
3729
4317
 
3730
4318
  utils.checkObjectRights(this, null, null, options, utils.CONSTS.ACCESS_WRITE, (err, options) => {
3731
4319
  if (err) {
3732
- typeof callback === 'function' && setImmediate(() => callback(err));
4320
+ return tools.maybeCallbackWithRedisError(callback, err);
3733
4321
  } else {
3734
4322
  if (!options.acl.file.write || options.user !== utils.CONSTS.SYSTEM_ADMIN_USER) {
3735
- typeof callback === 'function' && setImmediate(() => callback(utils.ERRORS.ERROR_PERMISSION));
4323
+ return tools.maybeCallbackWithError(callback, utils.ERRORS.ERROR_PERMISSION);
3736
4324
  } else {
3737
4325
  return this._destroyDB(options, callback);
3738
4326
  }
@@ -3741,9 +4329,9 @@ class ObjectsInRedisClient {
3741
4329
  }
3742
4330
 
3743
4331
  destroyDBAsync(options) {
3744
- return /** @type {Promise<void>} */ (new Promise((resolve, reject) =>
3745
- this.destroyDB(options, err =>
3746
- err ? reject(err) : resolve())));
4332
+ return /** @type {Promise<void>} */ (
4333
+ new Promise((resolve, reject) => this.destroyDB(options, err => (err ? reject(err) : resolve())))
4334
+ );
3747
4335
  }
3748
4336
 
3749
4337
  // Destructor of the class. Called by shutting down.
@@ -3756,7 +4344,6 @@ class ObjectsInRedisClient {
3756
4344
  } catch {
3757
4345
  // ignore error
3758
4346
  }
3759
-
3760
4347
  }
3761
4348
  if (this.sub) {
3762
4349
  try {
@@ -3777,28 +4364,26 @@ class ObjectsInRedisClient {
3777
4364
  }
3778
4365
 
3779
4366
  async loadLuaScripts() {
3780
- let _scripts = [];
3781
- if (scriptFiles && scriptFiles.filter) {
3782
- for (const name of Object.keys(scriptFiles)) {
3783
- const shasum = crypto.createHash('sha1');
3784
- const buf = Buffer.from(scriptFiles[name]);
3785
- shasum.update(buf);
3786
- _scripts.push({
3787
- name,
3788
- text: buf,
3789
- hash: shasum.digest('hex')
3790
- });
3791
- }
4367
+ let luaDirName;
4368
+
4369
+ if (this.noLegacyMultihost && this.useSets) {
4370
+ luaDirName = 'lua-v4';
4371
+ } else if (this.noLegacyMultihost) {
4372
+ luaDirName = 'lua-v4-no-sets';
3792
4373
  } else {
3793
- _scripts = fs.readdirSync(__dirname + '/lua/').map(name => {
3794
- const shasum = crypto.createHash('sha1');
3795
- const script = fs.readFileSync(__dirname + '/lua/' + name);
3796
- shasum.update(script);
3797
- const hash = shasum.digest('hex');
3798
- return {name: name.replace(/\.lua$/, ''), text: script, hash};
3799
- });
4374
+ luaDirName = 'lua-v3';
3800
4375
  }
3801
- const hashes = _scripts.map(e => e.hash);
4376
+
4377
+ const luaPath = path.join(__dirname, luaDirName);
4378
+ const scripts = fs.readdirSync(luaPath).map(name => {
4379
+ const shasum = crypto.createHash('sha1');
4380
+ const script = fs.readFileSync(path.join(luaPath, name));
4381
+ shasum.update(script);
4382
+ const hash = shasum.digest('hex');
4383
+ return { name: name.replace(/\.lua$/, ''), text: script, hash };
4384
+ });
4385
+
4386
+ const hashes = scripts.map(e => e.hash);
3802
4387
  hashes.unshift('EXISTS');
3803
4388
 
3804
4389
  if (!this.client) {
@@ -3812,15 +4397,15 @@ class ObjectsInRedisClient {
3812
4397
  // ignore
3813
4398
  }
3814
4399
 
3815
- arr && _scripts.forEach((e, i) => _scripts[i].loaded = !!arr[i]);
4400
+ arr && scripts.forEach((e, i) => (scripts[i].loaded = !!arr[i]));
3816
4401
 
3817
4402
  if (!this.client) {
3818
4403
  throw new Error(tools.ERRORS.ERROR_DB_CLOSED);
3819
4404
  }
3820
4405
 
3821
- for (let i = 0; i < _scripts.length; i++) {
3822
- if (!_scripts[i].loaded) {
3823
- const script = _scripts[i];
4406
+ for (let i = 0; i < scripts.length; i++) {
4407
+ if (!scripts[i].loaded) {
4408
+ const script = scripts[i];
3824
4409
  let hash;
3825
4410
  try {
3826
4411
  hash = await this.client.script(['LOAD', script.text]);
@@ -3834,20 +4419,20 @@ class ObjectsInRedisClient {
3834
4419
  }
3835
4420
  }
3836
4421
  this.scripts = {};
3837
- _scripts.forEach(e => this.scripts[e.name] = e.hash);
4422
+ scripts.forEach(e => (this.scripts[e.name] = e.hash));
3838
4423
  }
3839
4424
 
3840
4425
  /**
3841
4426
  * Get all keys matching a pattern using redis SCAN command, duplicates are filtered out
3842
4427
  *
3843
4428
  * @param {string} pattern - pattern to match, e. g. io.hm-rpc.0*
3844
- * @param {number} count - count argument used by redis SCAN, default is 500
4429
+ * @param {number} count - count argument used by redis SCAN, default is 250
3845
4430
  * @return {Promise<string[]>}
3846
4431
  * @private
3847
4432
  */
3848
4433
  _getKeysViaScan(pattern, count = 250) {
3849
4434
  return new Promise(resolve => {
3850
- const stream = this.client.scanStream({match: pattern, count: count});
4435
+ const stream = this.client.scanStream({ match: pattern, count: count });
3851
4436
  let uniqueKeys = [];
3852
4437
 
3853
4438
  stream.on('data', resultKeys => {
@@ -3861,6 +4446,239 @@ class ObjectsInRedisClient {
3861
4446
  });
3862
4447
  });
3863
4448
  }
4449
+
4450
+ /**
4451
+ * Checks if a given set exists
4452
+ * @param {string} id - id of the set
4453
+ * @private
4454
+ * @return {Promise<boolean>}
4455
+ */
4456
+ async setExists(id) {
4457
+ if (!this.client) {
4458
+ throw new Error(utils.ERRORS.ERROR_DB_CLOSED);
4459
+ }
4460
+
4461
+ const exists = await this.client.exists(this.setNamespace + id);
4462
+ return !!exists;
4463
+ }
4464
+
4465
+ /**
4466
+ * Migrate all objects to sets
4467
+ * @return {Promise<number>}
4468
+ */
4469
+ async migrateToSets() {
4470
+ if (!this.useSets) {
4471
+ return 0;
4472
+ }
4473
+
4474
+ if (!this.client) {
4475
+ throw new Error(utils.ERRORS.ERROR_DB_CLOSED);
4476
+ }
4477
+
4478
+ // to be safe we remove all sets before migration
4479
+ const keys = await this._getKeysViaScan(`${this.setNamespace}object.type.*`);
4480
+ for (const key of keys) {
4481
+ await this.client.del(key);
4482
+ }
4483
+
4484
+ let noMigrated = 0;
4485
+ // get all objs without using view
4486
+ const objs = await this.getObjectList({ startkey: '', endkey: '\u9999' });
4487
+ for (const obj of objs.rows) {
4488
+ if (obj.value.type) {
4489
+ // e.g. _design/.. has no type
4490
+ // 1 if added else 0 (mostly always part of the set)
4491
+ const migrated = await this.client.sadd(
4492
+ `${this.setNamespace}object.type.${obj.value.type}`,
4493
+ this.objNamespace + obj.id
4494
+ );
4495
+ noMigrated += migrated;
4496
+ }
4497
+
4498
+ // check for custom
4499
+ if (obj.value.common && obj.value.common.custom) {
4500
+ const migrated = await this.client.sadd(
4501
+ `${this.setNamespace}object.common.custom`,
4502
+ this.objNamespace + obj.id
4503
+ );
4504
+ noMigrated += migrated;
4505
+ }
4506
+ }
4507
+ return noMigrated;
4508
+ }
4509
+
4510
+ /**
4511
+ * Returns the protocol version from DB
4512
+ *
4513
+ * @returns {Promise<string>}
4514
+ */
4515
+ getProtocolVersion() {
4516
+ if (!this.client) {
4517
+ throw new Error(utils.ERRORS.ERROR_DB_CLOSED);
4518
+ }
4519
+
4520
+ return this.client.get(`${this.metaNamespace}objects.protocolVersion`);
4521
+ }
4522
+
4523
+ /**
4524
+ * Extend the primary host lock time
4525
+ * Value will expire after ms milliseconds
4526
+ *
4527
+ * {number} ms - ms until value expires
4528
+ * @return {Promise<number>} 1 if extended else 0
4529
+ */
4530
+ extendPrimaryHostLock(ms) {
4531
+ if (!this.client) {
4532
+ throw new Error(utils.ERRORS.ERROR_DB_CLOSED);
4533
+ }
4534
+
4535
+ // we have a host version smaller 3 no one can be primary
4536
+ if (!this.noLegacyMultihost) {
4537
+ return Promise.resolve(0);
4538
+ }
4539
+
4540
+ // try to extend lock
4541
+ return this.client.evalsha([
4542
+ this.scripts['redlock_extend'],
4543
+ 3,
4544
+ `${this.metaNamespace}objects.primaryHost`,
4545
+ this.hostname,
4546
+ ms
4547
+ ]);
4548
+ }
4549
+
4550
+ /**
4551
+ * Sets current host as primary if no primary host active
4552
+ * Value will expire after ms milliseconds
4553
+ *
4554
+ * {number} ms - ms until value expires
4555
+ * @return {Promise<number>} 1 if lock acquired else 0
4556
+ */
4557
+ setPrimaryHost(ms) {
4558
+ if (!this.client) {
4559
+ throw new Error(utils.ERRORS.ERROR_DB_CLOSED);
4560
+ }
4561
+
4562
+ // we have a host version smaller 3 no one can be primary
4563
+ if (!this.noLegacyMultihost) {
4564
+ return Promise.resolve(0);
4565
+ }
4566
+
4567
+ // try to acquire lock
4568
+ return this.client.evalsha([
4569
+ this.scripts['redlock_acquire'],
4570
+ 3,
4571
+ `${this.metaNamespace}objects.primaryHost`,
4572
+ this.hostname,
4573
+ ms
4574
+ ]);
4575
+ }
4576
+
4577
+ /**
4578
+ * Get name of the primary host
4579
+ *
4580
+ * @return {Promise<string>}
4581
+ */
4582
+ getPrimaryHost() {
4583
+ if (!this.client) {
4584
+ throw new Error(utils.ERRORS.ERROR_DB_CLOSED);
4585
+ }
4586
+
4587
+ // we have a host version smaller 3 no one can be primary
4588
+ if (!this.noLegacyMultihost) {
4589
+ return new Promise.resolve('');
4590
+ }
4591
+
4592
+ return this.client.get(`${this.metaNamespace}objects.primaryHost`);
4593
+ }
4594
+
4595
+ /**
4596
+ * Ensure we are no longer the primary host
4597
+ *
4598
+ * @return Promise<void>
4599
+ */
4600
+ releasePrimaryHost() {
4601
+ if (!this.client) {
4602
+ throw new Error(utils.ERRORS.ERROR_DB_CLOSED);
4603
+ }
4604
+
4605
+ if (!this.noLegacyMultihost) {
4606
+ return new Promise.resolve();
4607
+ }
4608
+
4609
+ // try to release lock
4610
+ return this.client.evalsha([
4611
+ this.scripts['redlock_release'],
4612
+ 4,
4613
+ `${this.metaNamespace}objects.primaryHost`,
4614
+ this.hostname,
4615
+ this.settings.connection.options.db,
4616
+ `${this.metaNamespace}objects.primaryHost`
4617
+ ]);
4618
+ }
4619
+
4620
+ /**
4621
+ * Sets the protocol version to the DB
4622
+ * @param {number|string} version - protocol version
4623
+ * @returns {Promise<void>}
4624
+ */
4625
+ async setProtocolVersion(version) {
4626
+ if (!this.client) {
4627
+ throw new Error(utils.ERRORS.ERROR_DB_CLOSED);
4628
+ }
4629
+
4630
+ version = version.toString();
4631
+ // we can only set a protocol if we actually support it
4632
+ if (this.supportedProtocolVersions.includes(version)) {
4633
+ await this.client.set(`${this.metaNamespace}objects.protocolVersion`, version);
4634
+ await this.client.publish(`${this.metaNamespace}objects.protocolVersion`, version);
4635
+ } else {
4636
+ throw new Error('Cannot set an unsupported protocol version on the current host');
4637
+ }
4638
+ }
4639
+
4640
+ /**
4641
+ * Subscribe to expired events to get expiration of primary host
4642
+ * @return {Promise<void>}
4643
+ */
4644
+ async subscribePrimaryHost() {
4645
+ if (this.subSystem) {
4646
+ await this.subSystem.subscribe(`__keyevent@${this.settings.connection.options.db}__:expired`);
4647
+ await this.subSystem.subscribe(`__keyevent@${this.settings.connection.options.db}__:evicted`);
4648
+ }
4649
+ }
4650
+
4651
+ /**
4652
+ * Activates the usage of sets
4653
+ * @return {Promise<void>}
4654
+ */
4655
+ async activateSets() {
4656
+ this.useSets = true;
4657
+ await this.client.set(`${this.metaNamespace}objects.features.useSets`, '1');
4658
+ }
4659
+
4660
+ /**
4661
+ * Deactivates the usage of sets
4662
+ * @return {Promise<void>}
4663
+ */
4664
+ async deactivateSets() {
4665
+ this.useSets = false;
4666
+ await this.client.set(`${this.metaNamespace}objects.features.useSets`, '0');
4667
+ }
4668
+
4669
+ /**
4670
+ * Get value from meta namespace
4671
+ *
4672
+ * @param {string} id redis key
4673
+ * @return {Promise<string>}
4674
+ */
4675
+ getMeta(id) {
4676
+ if (!this.client) {
4677
+ throw new Error(utils.ERRORS.ERROR_DB_CLOSED);
4678
+ }
4679
+
4680
+ return this.client.get(this.metaNamespace + id);
4681
+ }
3864
4682
  }
3865
4683
 
3866
4684
  module.exports = ObjectsInRedisClient;