@iobroker/db-base 4.0.0-alpha.8-20210909-001a711c → 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.
- package/LICENSE +1 -1
- package/README.md +1 -1
- package/lib/constants.js +6 -0
- package/lib/inMemFileDB.js +73 -38
- package/lib/redisHandler.js +189 -14
- package/lib/tools.js +1 -1
- package/package.json +3 -4
package/LICENSE
CHANGED
package/README.md
CHANGED
package/lib/constants.js
ADDED
package/lib/inMemFileDB.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* States DB in memory - Server
|
|
3
3
|
*
|
|
4
|
-
* Copyright 2013-
|
|
4
|
+
* Copyright 2013-2022 bluefox <dogafox@gmail.com>
|
|
5
5
|
*
|
|
6
6
|
* MIT License
|
|
7
7
|
*
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
/* jslint node: true */
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
-
const fs
|
|
18
|
-
const path
|
|
19
|
-
const tools
|
|
17
|
+
const fs = require('fs-extra');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const tools = require('./tools.js');
|
|
20
20
|
|
|
21
21
|
// settings = {
|
|
22
22
|
// change: function (id, state) {},
|
|
@@ -44,7 +44,6 @@ const tools = require('./tools.js');
|
|
|
44
44
|
* and general subscription and publish functionality
|
|
45
45
|
**/
|
|
46
46
|
class InMemoryFileDB {
|
|
47
|
-
|
|
48
47
|
constructor(settings) {
|
|
49
48
|
this.settings = settings || {};
|
|
50
49
|
|
|
@@ -57,15 +56,15 @@ class InMemoryFileDB {
|
|
|
57
56
|
this.zlib = null;
|
|
58
57
|
this.callbackSubscriptionClient = {};
|
|
59
58
|
|
|
60
|
-
this.settings.backup = this.settings.backup || {
|
|
61
|
-
disabled: false,
|
|
62
|
-
files: 24,
|
|
63
|
-
hours: 48,
|
|
64
|
-
period: 120,
|
|
65
|
-
path: ''
|
|
59
|
+
this.settings.backup = this.settings.connection.backup || {
|
|
60
|
+
disabled: false, // deactivates
|
|
61
|
+
files: 24, // minimum number of files
|
|
62
|
+
hours: 48, // hours
|
|
63
|
+
period: 120, // minutes
|
|
64
|
+
path: '' // use default path
|
|
66
65
|
};
|
|
67
66
|
|
|
68
|
-
this.dataDir =
|
|
67
|
+
this.dataDir = this.settings.connection.dataDir || tools.getDefaultDataDir();
|
|
69
68
|
if (!path.isAbsolute(this.dataDir)) {
|
|
70
69
|
this.dataDir = path.normalize(path.join(tools.getControllerDir(), this.dataDir));
|
|
71
70
|
}
|
|
@@ -77,7 +76,7 @@ class InMemoryFileDB {
|
|
|
77
76
|
|
|
78
77
|
this.stateTimer = null;
|
|
79
78
|
|
|
80
|
-
this.backupDir = this.settings.backup.path ||
|
|
79
|
+
this.backupDir = this.settings.backup.path || path.join(this.dataDir, this.settings.fileDB.backupDirName);
|
|
81
80
|
|
|
82
81
|
if (!this.settings.backup.disabled) {
|
|
83
82
|
this.initBackupDir();
|
|
@@ -117,32 +116,55 @@ class InMemoryFileDB {
|
|
|
117
116
|
let ret = {};
|
|
118
117
|
try {
|
|
119
118
|
ret = await this.loadDatasetFile(datasetName);
|
|
119
|
+
|
|
120
|
+
// loading worked, make sure that "bak" File is not broken
|
|
121
|
+
try {
|
|
122
|
+
await fs.readJSON(`${datasetName}.bak`);
|
|
123
|
+
} catch (e) {
|
|
124
|
+
this.log.info(
|
|
125
|
+
`${this.namespace} Rewrite bak file, because error on verify ${datasetName}.bak: ${e.message}`
|
|
126
|
+
);
|
|
127
|
+
try {
|
|
128
|
+
const jsonString = JSON.stringify(ret);
|
|
129
|
+
await fs.writeFile(`${datasetName}.bak`, jsonString);
|
|
130
|
+
} catch (e) {
|
|
131
|
+
this.log.error(`${this.namespace} Cannot save ${datasetName}.bak: ${e.message}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
120
134
|
} catch (err) {
|
|
121
135
|
this.log.error(`${this.namespace} Cannot load ${datasetName}: ${err.message}. We try last Backup!`);
|
|
122
136
|
|
|
123
137
|
try {
|
|
124
|
-
ret = await this.loadDatasetFile(datasetName
|
|
138
|
+
ret = await this.loadDatasetFile(`${datasetName}.bak`);
|
|
125
139
|
|
|
126
140
|
// it worked, lets overwrite old file and store the broken one for pot. forensic check
|
|
127
141
|
try {
|
|
128
142
|
if (await fs.pathExists(datasetName)) {
|
|
129
143
|
try {
|
|
130
|
-
await fs.move(datasetName, `${datasetName}.broken`, {overwrite: true});
|
|
144
|
+
await fs.move(datasetName, `${datasetName}.broken`, { overwrite: true });
|
|
131
145
|
} catch (e) {
|
|
132
|
-
this.log.error(
|
|
146
|
+
this.log.error(
|
|
147
|
+
`${this.namespace} Cannot copy the broken file ${datasetName} to ${datasetName}.broken ${e.message}`
|
|
148
|
+
);
|
|
133
149
|
}
|
|
134
150
|
try {
|
|
135
151
|
await fs.writeFile(datasetName, JSON.stringify(ret));
|
|
136
152
|
} catch (e) {
|
|
137
|
-
this.log.error(
|
|
153
|
+
this.log.error(
|
|
154
|
+
`${this.namespace} Cannot restore backup file as new main ${datasetName}: ${e.message}`
|
|
155
|
+
);
|
|
138
156
|
}
|
|
139
157
|
}
|
|
140
158
|
} catch {
|
|
141
159
|
// ignore, file does not exist
|
|
142
160
|
}
|
|
143
161
|
} catch (err) {
|
|
144
|
-
this.log.error(
|
|
145
|
-
|
|
162
|
+
this.log.error(
|
|
163
|
+
`${this.namespace} Cannot load ${datasetName}.bak: ${err.message}. Continue with empty dataset!`
|
|
164
|
+
);
|
|
165
|
+
this.log.error(
|
|
166
|
+
`${this.namespace} If this is no Migration or initial start please restore the last backup from ${this.backupDir}`
|
|
167
|
+
);
|
|
146
168
|
}
|
|
147
169
|
}
|
|
148
170
|
return ret;
|
|
@@ -151,18 +173,21 @@ class InMemoryFileDB {
|
|
|
151
173
|
initBackupDir() {
|
|
152
174
|
this.zlib = this.zlib || require('zlib');
|
|
153
175
|
// Interval in minutes => to milliseconds
|
|
154
|
-
this.settings.backup.period =
|
|
176
|
+
this.settings.backup.period =
|
|
177
|
+
this.settings.backup.period === undefined ? 120 : parseInt(this.settings.backup.period);
|
|
155
178
|
if (isNaN(this.settings.backup.period)) {
|
|
156
179
|
this.settings.backup.period = 120;
|
|
157
180
|
}
|
|
158
181
|
this.settings.backup.period *= 60000;
|
|
159
182
|
|
|
160
|
-
this.settings.backup.files =
|
|
183
|
+
this.settings.backup.files =
|
|
184
|
+
this.settings.backup.files === undefined ? 24 : parseInt(this.settings.backup.files);
|
|
161
185
|
if (isNaN(this.settings.backup.files)) {
|
|
162
186
|
this.settings.backup.files = 24;
|
|
163
187
|
}
|
|
164
188
|
|
|
165
|
-
this.settings.backup.hours =
|
|
189
|
+
this.settings.backup.hours =
|
|
190
|
+
this.settings.backup.hours === undefined ? 48 : parseInt(this.settings.backup.hours);
|
|
166
191
|
if (isNaN(this.settings.backup.hours)) {
|
|
167
192
|
this.settings.backup.hours = 48;
|
|
168
193
|
}
|
|
@@ -186,11 +211,11 @@ class InMemoryFileDB {
|
|
|
186
211
|
return;
|
|
187
212
|
}
|
|
188
213
|
|
|
189
|
-
s.push({pattern: pattern, regex: new RegExp(tools.pattern2RegEx(pattern)), options: options});
|
|
214
|
+
s.push({ pattern: pattern, regex: new RegExp(tools.pattern2RegEx(pattern)), options: options });
|
|
190
215
|
});
|
|
191
216
|
} else {
|
|
192
217
|
if (!s.find(sub => sub.pattern === pattern)) {
|
|
193
|
-
s.push({pattern: pattern, regex: new RegExp(tools.pattern2RegEx(pattern)), options: options});
|
|
218
|
+
s.push({ pattern: pattern, regex: new RegExp(tools.pattern2RegEx(pattern)), options: options });
|
|
194
219
|
}
|
|
195
220
|
}
|
|
196
221
|
typeof cb === 'function' && cb();
|
|
@@ -226,13 +251,13 @@ class InMemoryFileDB {
|
|
|
226
251
|
throw new Error('no communication handling implemented');
|
|
227
252
|
}
|
|
228
253
|
|
|
229
|
-
deleteOldBackupFiles() {
|
|
254
|
+
deleteOldBackupFiles(baseFilename) {
|
|
230
255
|
// delete files only if settings.backupNumber is not 0
|
|
231
256
|
let files = fs.readdirSync(this.backupDir);
|
|
232
257
|
files.sort();
|
|
233
258
|
const limit = Date.now() - this.settings.backup.hours * 3600000;
|
|
234
259
|
|
|
235
|
-
files = files.filter(f => f.endsWith(
|
|
260
|
+
files = files.filter(f => f.endsWith(baseFilename + '.gz'));
|
|
236
261
|
|
|
237
262
|
while (files.length > this.settings.backup.files) {
|
|
238
263
|
const file = files.shift();
|
|
@@ -245,7 +270,9 @@ class InMemoryFileDB {
|
|
|
245
270
|
try {
|
|
246
271
|
fs.unlinkSync(path.join(this.backupDir, file));
|
|
247
272
|
} catch (e) {
|
|
248
|
-
this.log.error(
|
|
273
|
+
this.log.error(
|
|
274
|
+
`${this.namespace} Cannot delete file "${path.join(this.backupDir, file)}: ${e.message}`
|
|
275
|
+
);
|
|
249
276
|
}
|
|
250
277
|
}
|
|
251
278
|
}
|
|
@@ -319,7 +346,7 @@ class InMemoryFileDB {
|
|
|
319
346
|
try {
|
|
320
347
|
if (await fs.pathExists(this.datasetName)) {
|
|
321
348
|
try {
|
|
322
|
-
await fs.move(this.datasetName, `${this.datasetName}.bak`, {overwrite: true});
|
|
349
|
+
await fs.move(this.datasetName, `${this.datasetName}.bak`, { overwrite: true });
|
|
323
350
|
} catch (e) {
|
|
324
351
|
bakOk = false;
|
|
325
352
|
this.log.error(`${this.namespace} Cannot backup file ${this.datasetName}.bak: ${e.message}`);
|
|
@@ -333,19 +360,21 @@ class InMemoryFileDB {
|
|
|
333
360
|
}
|
|
334
361
|
|
|
335
362
|
try {
|
|
336
|
-
await fs.move(`${this.datasetName}.new`, this.datasetName, {overwrite: true});
|
|
363
|
+
await fs.move(`${this.datasetName}.new`, this.datasetName, { overwrite: true });
|
|
337
364
|
} catch (e) {
|
|
338
|
-
this.log.error(
|
|
365
|
+
this.log.error(
|
|
366
|
+
`${this.namespace} Cannot move ${this.datasetName}.new to ${this.datasetName}: ${e.message}. Try direct write as fallback`
|
|
367
|
+
);
|
|
339
368
|
try {
|
|
340
369
|
await fs.writeFile(this.datasetName, jsonString);
|
|
341
370
|
} catch (e) {
|
|
342
371
|
this.log.error(`${this.namespace} Cannot directly write Dataset to ${this.datasetName}: ${e.message}`);
|
|
343
372
|
return jsonString;
|
|
344
373
|
}
|
|
345
|
-
|
|
346
374
|
}
|
|
347
375
|
|
|
348
|
-
if (!bakOk) {
|
|
376
|
+
if (!bakOk) {
|
|
377
|
+
// it seems the bak File is not successfully there, write current content again
|
|
349
378
|
try {
|
|
350
379
|
await fs.writeFile(`${this.datasetName}.bak`, jsonString);
|
|
351
380
|
} catch (e) {
|
|
@@ -368,7 +397,10 @@ class InMemoryFileDB {
|
|
|
368
397
|
// makes backups only if settings.backupInterval is not 0
|
|
369
398
|
if (this.settings.backup.period && (!this.lastSave || now - this.lastSave > this.settings.backup.period)) {
|
|
370
399
|
this.lastSave = now;
|
|
371
|
-
const backFileName = path.join(
|
|
400
|
+
const backFileName = path.join(
|
|
401
|
+
this.backupDir,
|
|
402
|
+
this.getTimeStr(now) + '_' + this.settings.fileDB.fileName + '.gz'
|
|
403
|
+
);
|
|
372
404
|
|
|
373
405
|
try {
|
|
374
406
|
if (!fs.existsSync(backFileName)) {
|
|
@@ -386,7 +418,7 @@ class InMemoryFileDB {
|
|
|
386
418
|
compress.end();
|
|
387
419
|
|
|
388
420
|
// analyse older files
|
|
389
|
-
this.deleteOldBackupFiles();
|
|
421
|
+
this.deleteOldBackupFiles(this.settings.fileDB.fileName);
|
|
390
422
|
}
|
|
391
423
|
} catch (e) {
|
|
392
424
|
this.log.error(`${this.namespace} Cannot save backup ${backFileName}: ${e.message}`);
|
|
@@ -395,7 +427,7 @@ class InMemoryFileDB {
|
|
|
395
427
|
}
|
|
396
428
|
|
|
397
429
|
getStatus() {
|
|
398
|
-
return {type: 'file', server: true};
|
|
430
|
+
return { type: 'file', server: true };
|
|
399
431
|
}
|
|
400
432
|
|
|
401
433
|
/** @returns {object} */
|
|
@@ -419,11 +451,14 @@ class InMemoryFileDB {
|
|
|
419
451
|
}
|
|
420
452
|
|
|
421
453
|
// local subscriptions
|
|
422
|
-
if (
|
|
454
|
+
if (
|
|
455
|
+
this.change &&
|
|
456
|
+
this.callbackSubscriptionClient._subscribe &&
|
|
457
|
+
this.callbackSubscriptionClient._subscribe[type]
|
|
458
|
+
) {
|
|
423
459
|
for (let j = 0; j < this.callbackSubscriptionClient._subscribe[type].length; j++) {
|
|
424
460
|
if (this.callbackSubscriptionClient._subscribe[type][j].regex.test(id)) {
|
|
425
|
-
setImmediate(() =>
|
|
426
|
-
this.change(id, obj));
|
|
461
|
+
setImmediate(() => this.change(id, obj));
|
|
427
462
|
break;
|
|
428
463
|
}
|
|
429
464
|
}
|
package/lib/redisHandler.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const Resp = require('respjs');
|
|
2
2
|
const { EventEmitter } = require('events');
|
|
3
|
+
const { QUEUED_STR_BUF, OK_STR_BUF } = require('./constants');
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Class to handle a redis connection and provide events to react on for
|
|
@@ -23,10 +24,11 @@ class RedisHandler extends EventEmitter {
|
|
|
23
24
|
}
|
|
24
25
|
this.socket = socket;
|
|
25
26
|
|
|
26
|
-
this.socketId = this.logScope + socket.remoteAddress
|
|
27
|
+
this.socketId = `${this.logScope + socket.remoteAddress}:${socket.remotePort}`;
|
|
27
28
|
this.initialized = false;
|
|
28
29
|
this.stop = false;
|
|
29
30
|
|
|
31
|
+
this.activeMultiCalls = [];
|
|
30
32
|
this.writeQueue = [];
|
|
31
33
|
|
|
32
34
|
this.handleBuffers = false;
|
|
@@ -40,7 +42,7 @@ class RedisHandler extends EventEmitter {
|
|
|
40
42
|
this.resp.on('error', err => {
|
|
41
43
|
this.log.error(`${this.socketId} (Init=${this.initialized}) Redis error:${err}`);
|
|
42
44
|
if (this.initialized) {
|
|
43
|
-
this.sendError(null,new Error(
|
|
45
|
+
this.sendError(null, new Error(`PARSER ERROR ${err}`)); // TODO
|
|
44
46
|
} else {
|
|
45
47
|
this.close();
|
|
46
48
|
}
|
|
@@ -50,7 +52,16 @@ class RedisHandler extends EventEmitter {
|
|
|
50
52
|
|
|
51
53
|
socket.on('data', data => {
|
|
52
54
|
if (this.options.enhancedLogging) {
|
|
53
|
-
this.log.silly(
|
|
55
|
+
this.log.silly(
|
|
56
|
+
`${this.socketId} New Redis request: ${
|
|
57
|
+
data.length > 1024
|
|
58
|
+
? `${data
|
|
59
|
+
.toString()
|
|
60
|
+
.replace(/[\r\n]+/g, '')
|
|
61
|
+
.substring(0, 100)} -- ${data.length} bytes`
|
|
62
|
+
: data.toString().replace(/[\r\n]+/g, '')
|
|
63
|
+
}`
|
|
64
|
+
);
|
|
54
65
|
}
|
|
55
66
|
this.resp.write(data);
|
|
56
67
|
});
|
|
@@ -87,19 +98,53 @@ class RedisHandler extends EventEmitter {
|
|
|
87
98
|
}
|
|
88
99
|
}
|
|
89
100
|
}
|
|
90
|
-
|
|
91
|
-
this.initialized = true;
|
|
92
|
-
}
|
|
101
|
+
|
|
93
102
|
const t = process.hrtime();
|
|
94
|
-
const responseId =
|
|
103
|
+
const responseId = t[0] * 1e3 + t[1] / 1e6;
|
|
104
|
+
|
|
95
105
|
if (this.options.enhancedLogging) {
|
|
96
|
-
this.log.silly(
|
|
106
|
+
this.log.silly(
|
|
107
|
+
`${this.socketId} Parser result: id=${responseId}, command=${command}, data=${
|
|
108
|
+
JSON.stringify(data).length > 1024
|
|
109
|
+
? `${JSON.stringify(data).substring(0, 100)} -- ${JSON.stringify(data).length} bytes`
|
|
110
|
+
: JSON.stringify(data)
|
|
111
|
+
}`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (command === 'multi') {
|
|
116
|
+
if (this.activeMultiCalls.length && !this.activeMultiCalls[0].execCalled) {
|
|
117
|
+
// should never happen
|
|
118
|
+
this.log.warn(`${this.socketId} Conflicting multi call`);
|
|
119
|
+
}
|
|
120
|
+
this._handleMulti();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// multi active and exec not called yet
|
|
125
|
+
if (this.activeMultiCalls.length && !this.activeMultiCalls[0].execCalled && command !== 'exec') {
|
|
126
|
+
// store all response ids so we know which need to be in the multi call
|
|
127
|
+
this.activeMultiCalls[0].responseIds.push(responseId);
|
|
128
|
+
// add it for the correct order will be overwritten with correct response
|
|
129
|
+
this.activeMultiCalls[0].responseMap.set(responseId, null);
|
|
130
|
+
} else {
|
|
131
|
+
// multi response ids should not be pushed - we will answer combined
|
|
132
|
+
this.writeQueue.push({ id: responseId, data: false });
|
|
97
133
|
}
|
|
98
|
-
|
|
134
|
+
|
|
135
|
+
if (command === 'exec') {
|
|
136
|
+
this._handleExec(responseId);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (command === 'info') {
|
|
141
|
+
this.initialized = true;
|
|
142
|
+
}
|
|
143
|
+
|
|
99
144
|
if (this.listenerCount(command) !== 0) {
|
|
100
145
|
setImmediate(() => this.emit(command, data, responseId));
|
|
101
146
|
} else {
|
|
102
|
-
this.sendError(responseId, new Error(command
|
|
147
|
+
this.sendError(responseId, new Error(`${command} NOT SUPPORTED`));
|
|
103
148
|
}
|
|
104
149
|
}
|
|
105
150
|
|
|
@@ -112,6 +157,7 @@ class RedisHandler extends EventEmitter {
|
|
|
112
157
|
*/
|
|
113
158
|
_sendQueued(responseId, data) {
|
|
114
159
|
let idx = 0;
|
|
160
|
+
|
|
115
161
|
while (this.writeQueue.length && idx < this.writeQueue.length) {
|
|
116
162
|
// we found the queue entry that matches with the responseId, so store the data so be sent out
|
|
117
163
|
if (this.writeQueue[idx].id === responseId) {
|
|
@@ -127,8 +173,15 @@ class RedisHandler extends EventEmitter {
|
|
|
127
173
|
if (idx === 0 && this.writeQueue[idx].data !== false) {
|
|
128
174
|
const response = this.writeQueue.shift();
|
|
129
175
|
if (this.options.enhancedLogging) {
|
|
130
|
-
this.log.silly(
|
|
176
|
+
this.log.silly(
|
|
177
|
+
`${this.socketId} Redis response (${response.id}): ${
|
|
178
|
+
response.data.length > 1024
|
|
179
|
+
? `${data.length} bytes`
|
|
180
|
+
: response.data.toString().replace(/[\r\n]+/g, '')
|
|
181
|
+
}`
|
|
182
|
+
);
|
|
131
183
|
}
|
|
184
|
+
|
|
132
185
|
this._write(response.data);
|
|
133
186
|
// We sended out first queue entry but no further response is ready
|
|
134
187
|
// and we do not need to check the whole queue, so we are done here
|
|
@@ -175,8 +228,9 @@ class RedisHandler extends EventEmitter {
|
|
|
175
228
|
}
|
|
176
229
|
if (!data) {
|
|
177
230
|
this.log.warn(`${this.socketId} Not able to write ${JSON.stringify(data)}`);
|
|
178
|
-
data = Resp.encodeError(new Error(
|
|
231
|
+
data = Resp.encodeError(new Error(`INVALID RESPONSE: ${JSON.stringify(data)}`));
|
|
179
232
|
}
|
|
233
|
+
|
|
180
234
|
setImmediate(() => this._sendQueued(responseId, data));
|
|
181
235
|
}
|
|
182
236
|
|
|
@@ -202,6 +256,13 @@ class RedisHandler extends EventEmitter {
|
|
|
202
256
|
* @param responseId ID of the response
|
|
203
257
|
*/
|
|
204
258
|
sendNull(responseId) {
|
|
259
|
+
for (const i in this.activeMultiCalls) {
|
|
260
|
+
if (this.activeMultiCalls[i].responseIds.includes(responseId)) {
|
|
261
|
+
this._handleMultiResponse(responseId, i, Resp.encodeNull());
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
205
266
|
this.sendResponse(responseId, Resp.encodeNull());
|
|
206
267
|
}
|
|
207
268
|
|
|
@@ -210,6 +271,13 @@ class RedisHandler extends EventEmitter {
|
|
|
210
271
|
* @param responseId ID of the response
|
|
211
272
|
*/
|
|
212
273
|
sendNullArray(responseId) {
|
|
274
|
+
for (const i in this.activeMultiCalls) {
|
|
275
|
+
if (this.activeMultiCalls[i].responseIds.includes(responseId)) {
|
|
276
|
+
this._handleMultiResponse(responseId, i, Resp.encodeNullArray());
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
213
281
|
this.sendResponse(responseId, Resp.encodeNullArray());
|
|
214
282
|
}
|
|
215
283
|
|
|
@@ -219,6 +287,13 @@ class RedisHandler extends EventEmitter {
|
|
|
219
287
|
* @param str String to encode
|
|
220
288
|
*/
|
|
221
289
|
sendString(responseId, str) {
|
|
290
|
+
for (const i in this.activeMultiCalls) {
|
|
291
|
+
if (this.activeMultiCalls[i].responseIds.includes(responseId)) {
|
|
292
|
+
this._handleMultiResponse(responseId, i, Resp.encodeString(str));
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
222
297
|
this.sendResponse(responseId, Resp.encodeString(str));
|
|
223
298
|
}
|
|
224
299
|
|
|
@@ -229,6 +304,14 @@ class RedisHandler extends EventEmitter {
|
|
|
229
304
|
*/
|
|
230
305
|
sendError(responseId, error) {
|
|
231
306
|
this.log.warn(`${this.socketId} Error from InMemDB: ${error}`);
|
|
307
|
+
|
|
308
|
+
for (const i in this.activeMultiCalls) {
|
|
309
|
+
if (this.activeMultiCalls[i].responseIds.includes(responseId)) {
|
|
310
|
+
this._handleMultiResponse(responseId, i, Resp.encodeError(error));
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
232
315
|
this.sendResponse(responseId, Resp.encodeError(error));
|
|
233
316
|
}
|
|
234
317
|
|
|
@@ -238,6 +321,13 @@ class RedisHandler extends EventEmitter {
|
|
|
238
321
|
* @param num Integer to send out
|
|
239
322
|
*/
|
|
240
323
|
sendInteger(responseId, num) {
|
|
324
|
+
for (const i in this.activeMultiCalls) {
|
|
325
|
+
if (this.activeMultiCalls[i].responseIds.includes(responseId)) {
|
|
326
|
+
this._handleMultiResponse(responseId, i, Resp.encodeInteger(num));
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
241
331
|
this.sendResponse(responseId, Resp.encodeInteger(num));
|
|
242
332
|
}
|
|
243
333
|
|
|
@@ -247,6 +337,13 @@ class RedisHandler extends EventEmitter {
|
|
|
247
337
|
* @param str String to send out
|
|
248
338
|
*/
|
|
249
339
|
sendBulk(responseId, str) {
|
|
340
|
+
for (const i in this.activeMultiCalls) {
|
|
341
|
+
if (this.activeMultiCalls[i].responseIds.includes(responseId)) {
|
|
342
|
+
this._handleMultiResponse(responseId, i, Resp.encodeBulk(str));
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
250
347
|
this.sendResponse(responseId, Resp.encodeBulk(str));
|
|
251
348
|
}
|
|
252
349
|
|
|
@@ -256,6 +353,13 @@ class RedisHandler extends EventEmitter {
|
|
|
256
353
|
* @param buf Buffer to send out
|
|
257
354
|
*/
|
|
258
355
|
sendBufBulk(responseId, buf) {
|
|
356
|
+
for (const i in this.activeMultiCalls) {
|
|
357
|
+
if (this.activeMultiCalls[i].responseIds.includes(responseId)) {
|
|
358
|
+
this._handleMultiResponse(responseId, i, Resp.encodeBufBulk(buf));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
259
363
|
this.sendResponse(responseId, Resp.encodeBufBulk(buf));
|
|
260
364
|
}
|
|
261
365
|
|
|
@@ -283,12 +387,83 @@ class RedisHandler extends EventEmitter {
|
|
|
283
387
|
|
|
284
388
|
/**
|
|
285
389
|
* Encode a array values to buffers and send out
|
|
286
|
-
* @param responseId ID of the response
|
|
287
|
-
* @param arr Array to send out
|
|
390
|
+
* @param {number} responseId ID of the response
|
|
391
|
+
* @param {any[]} arr Array to send out
|
|
288
392
|
*/
|
|
289
393
|
sendArray(responseId, arr) {
|
|
394
|
+
for (const i in this.activeMultiCalls) {
|
|
395
|
+
if (this.activeMultiCalls[i].responseIds.includes(responseId)) {
|
|
396
|
+
this._handleMultiResponse(responseId, i, Resp.encodeArray(this.encodeRespArray(arr)));
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
290
401
|
this.sendResponse(responseId, Resp.encodeArray(this.encodeRespArray(arr)));
|
|
291
402
|
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Handles a 'multi' command
|
|
406
|
+
*
|
|
407
|
+
* @private
|
|
408
|
+
*/
|
|
409
|
+
_handleMulti() {
|
|
410
|
+
this.activeMultiCalls.unshift({
|
|
411
|
+
responseIds: [],
|
|
412
|
+
execCalled: false,
|
|
413
|
+
responseCount: 0,
|
|
414
|
+
responseMap: new Map()
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Handles an 'exec' command
|
|
420
|
+
*
|
|
421
|
+
* @param {number} responseId ID of the response
|
|
422
|
+
* @private
|
|
423
|
+
*/
|
|
424
|
+
_handleExec(responseId) {
|
|
425
|
+
this.activeMultiCalls[0].execId = responseId;
|
|
426
|
+
this.activeMultiCalls[0].execCalled = true;
|
|
427
|
+
|
|
428
|
+
// maybe we have all fullfilled yet
|
|
429
|
+
if (this.activeMultiCalls[0].responseCount === this.activeMultiCalls[0].responseIds.length) {
|
|
430
|
+
const multiRespObj = this.activeMultiCalls.shift();
|
|
431
|
+
this._sendExecResponse(multiRespObj);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Builds up the exec response and sends it
|
|
437
|
+
* @param {Record<string, any>} multiObj the multi object to send out
|
|
438
|
+
*
|
|
439
|
+
* @private
|
|
440
|
+
*/
|
|
441
|
+
_sendExecResponse(multiObj) {
|
|
442
|
+
// collect all 'QUEUED' answers
|
|
443
|
+
const queuedStrArr = new Array(multiObj.responseCount).fill(QUEUED_STR_BUF);
|
|
444
|
+
|
|
445
|
+
this._sendQueued(
|
|
446
|
+
multiObj.execId,
|
|
447
|
+
Buffer.concat([OK_STR_BUF, ...queuedStrArr, Resp.encodeArray(Array.from(multiObj.responseMap.values()))])
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Handles a multi response
|
|
453
|
+
*
|
|
454
|
+
* @param {number} responseId ID of the response
|
|
455
|
+
* @param {number} index index of the multi call
|
|
456
|
+
* @param {Buffer} buf buffer to include in response
|
|
457
|
+
* @private
|
|
458
|
+
*/
|
|
459
|
+
_handleMultiResponse(responseId, index, buf) {
|
|
460
|
+
this.activeMultiCalls[index].responseMap.set(responseId, buf);
|
|
461
|
+
this.activeMultiCalls[index].responseCount++;
|
|
462
|
+
if (this.activeMultiCalls[index].responseCount === this.activeMultiCalls[index].responseIds.length) {
|
|
463
|
+
const multiRespObj = this.activeMultiCalls.splice(index, 1)[0];
|
|
464
|
+
this._sendExecResponse(multiRespObj);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
292
467
|
}
|
|
293
468
|
|
|
294
469
|
module.exports = RedisHandler;
|
package/lib/tools.js
CHANGED
package/package.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iobroker/db-base",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.3",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=12.0.0"
|
|
6
6
|
},
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@iobroker/js-controller-common": "4.0.
|
|
8
|
+
"@iobroker/js-controller-common": "4.0.3",
|
|
9
9
|
"deep-clone": "^3.0.3",
|
|
10
10
|
"fs-extra": "^10.0.0",
|
|
11
|
-
"node.extend": "^2.0.2",
|
|
12
11
|
"respjs": "^4.2.0"
|
|
13
12
|
},
|
|
14
13
|
"keywords": [
|
|
@@ -36,5 +35,5 @@
|
|
|
36
35
|
"lib/",
|
|
37
36
|
"index.js"
|
|
38
37
|
],
|
|
39
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "79a76a082db91399d0e432ef02bf2981a6739107"
|
|
40
39
|
}
|