@iobroker/db-base 7.2.3 → 8.0.0-alpha.1-20260921-e5941ca8a
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/build/cjs/index.d.ts +1 -1
- package/build/cjs/index.js +3 -3
- package/build/cjs/index.js.map +2 -2
- package/build/cjs/lib/inMemFileDB.d.ts +155 -44
- package/build/cjs/lib/inMemFileDB.js +108 -46
- package/build/cjs/lib/inMemFileDB.js.map +2 -2
- package/build/cjs/lib/redisHandler.d.ts +1 -1
- package/build/cjs/lib/redisHandler.js +11 -11
- package/build/cjs/lib/redisHandler.js.map +2 -2
- package/build/esm/index.d.ts +1 -1
- package/build/esm/index.d.ts.map +1 -1
- package/build/esm/index.js +1 -1
- package/build/esm/index.js.map +1 -1
- package/build/esm/lib/inMemFileDB.d.ts +155 -44
- package/build/esm/lib/inMemFileDB.d.ts.map +1 -1
- package/build/esm/lib/inMemFileDB.js +107 -49
- package/build/esm/lib/inMemFileDB.js.map +1 -1
- package/build/esm/lib/redisHandler.d.ts +1 -1
- package/build/esm/lib/redisHandler.d.ts.map +1 -1
- package/build/esm/lib/redisHandler.js +7 -11
- package/build/esm/lib/redisHandler.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +6 -6
|
@@ -1,66 +1,120 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* States DB in memory - Server
|
|
3
3
|
*
|
|
4
|
-
* Copyright 2013-
|
|
4
|
+
* Copyright 2013-2026 bluefox <dogafox@gmail.com>
|
|
5
5
|
*
|
|
6
6
|
* MIT License
|
|
7
7
|
*
|
|
8
8
|
*/
|
|
9
9
|
import type { InternalLogger } from '@iobroker/js-controller-common-db/tools';
|
|
10
|
+
interface BackupOptions {
|
|
11
|
+
/** deactivates backup if true */
|
|
12
|
+
disabled: boolean;
|
|
13
|
+
/** minimum number of files */
|
|
14
|
+
files: number;
|
|
15
|
+
hours: number;
|
|
16
|
+
/** minutes */
|
|
17
|
+
period: number;
|
|
18
|
+
path: string;
|
|
19
|
+
}
|
|
20
|
+
export type MetaObject = Record<string, string>;
|
|
21
|
+
export type FileSize = number;
|
|
22
|
+
/** Options describing how to connect to the database server */
|
|
10
23
|
export interface ConnectionOptions {
|
|
24
|
+
/** Password for authentication, if required */
|
|
11
25
|
pass?: string;
|
|
26
|
+
/** Name of the sentinel master to connect to */
|
|
12
27
|
sentinelName?: string;
|
|
13
28
|
/** array on sentinel */
|
|
14
29
|
host: string | string[];
|
|
15
30
|
/** array on sentinel */
|
|
16
31
|
port: number | number[];
|
|
17
|
-
options
|
|
32
|
+
/** Additional connection options passed to the database driver */
|
|
33
|
+
options: ioBroker.ObjectsDatabaseOptions['options'];
|
|
34
|
+
/** Options forwarded to the JSONL database backend */
|
|
35
|
+
jsonlOptions?: ioBroker.ObjectsDatabaseOptions['jsonlOptions'];
|
|
36
|
+
/** Redis key prefix used by the in-memory server */
|
|
37
|
+
redisNamespace?: string;
|
|
38
|
+
/** Enable more verbose connection logging */
|
|
18
39
|
enhancedLogging?: boolean;
|
|
40
|
+
/** Disable the in-memory file cache and always read files from disk */
|
|
41
|
+
noFileCache?: boolean;
|
|
42
|
+
/** Whether the connection should be secured via TLS */
|
|
43
|
+
secure?: boolean;
|
|
44
|
+
/** Backup configuration */
|
|
19
45
|
backup?: BackupOptions;
|
|
20
46
|
/** relative path to the data dir */
|
|
21
47
|
dataDir?: string;
|
|
48
|
+
/** Interval in milliseconds in which the in-memory states are persisted to disk */
|
|
49
|
+
writeFileInterval?: number;
|
|
22
50
|
}
|
|
23
|
-
type ChangeFunction = (id: string,
|
|
51
|
+
type ChangeFunction<TObject> = (id: string, obj: TObject) => void;
|
|
52
|
+
/** Status information about the database */
|
|
24
53
|
export interface DbStatus {
|
|
25
|
-
|
|
54
|
+
/** Type of the database backend (e.g. file, jsonl, redis) */
|
|
55
|
+
type: 'file' | 'redis' | 'jsonl';
|
|
56
|
+
/** Whether this process runs the database server */
|
|
26
57
|
server: boolean;
|
|
27
58
|
}
|
|
28
|
-
|
|
29
|
-
/** deactivates backup if true */
|
|
30
|
-
disabled: boolean;
|
|
31
|
-
/** minimum number of files */
|
|
32
|
-
files: number;
|
|
33
|
-
hours: number;
|
|
34
|
-
/** minutes */
|
|
35
|
-
period: number;
|
|
36
|
-
path: string;
|
|
37
|
-
}
|
|
59
|
+
/** Options describing where the database stores its files */
|
|
38
60
|
export interface DbOptions {
|
|
61
|
+
/** Name of the directory used for backups */
|
|
39
62
|
backupDirName: string;
|
|
63
|
+
/** Name of the database file */
|
|
40
64
|
fileName: string;
|
|
41
65
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
66
|
+
/** Settings accepted by the in-memory database classes and their subclasses */
|
|
67
|
+
export interface FileDbSettings<TObject> {
|
|
68
|
+
/** File DB storage options (filled in by the subclass constructor before `super()`) */
|
|
69
|
+
fileDB?: DbOptions;
|
|
70
|
+
/** JSONL DB storage options (only set by the JSONL backend; only the file name is used) */
|
|
71
|
+
jsonlDB?: Pick<DbOptions, 'fileName'>;
|
|
72
|
+
/** Backup options (derived from `connection.backup` by the base constructor) */
|
|
73
|
+
backup?: BackupOptions;
|
|
74
|
+
/** Callback invoked when a value in the database changes */
|
|
75
|
+
change?: ChangeFunction<TObject>;
|
|
76
|
+
/** Callback invoked once the database server is ready */
|
|
77
|
+
connected: (nameOfServer?: string) => void;
|
|
78
|
+
/** Logger instance (or logger configuration) used for all database logging */
|
|
79
|
+
logger?: InternalLogger;
|
|
80
|
+
/** Options describing how to connect to the database server */
|
|
49
81
|
connection: ConnectionOptions;
|
|
50
82
|
/** unused */
|
|
51
83
|
auth?: null;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
port
|
|
56
|
-
|
|
84
|
+
/** Whether the server connection should be secured via TLS */
|
|
85
|
+
secure?: boolean;
|
|
86
|
+
/** Port the in-memory server listens on */
|
|
87
|
+
port?: number;
|
|
88
|
+
/** Host the in-memory server binds to */
|
|
89
|
+
host?: string;
|
|
57
90
|
/** logging namespace */
|
|
58
91
|
namespace?: string;
|
|
92
|
+
/** Host name used as a fallback for the logging namespace */
|
|
93
|
+
hostname?: string;
|
|
94
|
+
/** Default ACL applied to newly created objects */
|
|
95
|
+
defaultNewAcl?: {
|
|
96
|
+
object: number;
|
|
97
|
+
state: number;
|
|
98
|
+
file: number;
|
|
99
|
+
owner: ioBroker.ObjectIDs.User;
|
|
100
|
+
ownerGroup: ioBroker.ObjectIDs.Group;
|
|
101
|
+
};
|
|
102
|
+
/** Redis key prefix used by the in-memory server (defaults to "io") */
|
|
103
|
+
redisNamespace?: string;
|
|
104
|
+
/** Namespace used for the message box */
|
|
105
|
+
namespaceMsg?: string;
|
|
106
|
+
/** Namespace used for log messages */
|
|
107
|
+
namespaceLog?: string;
|
|
108
|
+
/** Namespace used for sessions */
|
|
109
|
+
namespaceSession?: string;
|
|
110
|
+
/** Namespace used for meta information */
|
|
111
|
+
metaNamespace?: string;
|
|
112
|
+
/** Redis key prefix used for object meta information (defaults to "meta") */
|
|
113
|
+
namespaceMeta?: string;
|
|
59
114
|
}
|
|
60
115
|
interface Subscription {
|
|
61
116
|
pattern: string;
|
|
62
117
|
regex: RegExp;
|
|
63
|
-
options: any;
|
|
64
118
|
}
|
|
65
119
|
interface SubscriptionClient {
|
|
66
120
|
_subscribe?: Record<string, Subscription[]>;
|
|
@@ -69,19 +123,25 @@ interface SubscriptionClient {
|
|
|
69
123
|
* The parent of the class structure, which provides basic JSON storage
|
|
70
124
|
* and general subscription and publish functionality
|
|
71
125
|
*/
|
|
72
|
-
export declare class InMemoryFileDB {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
protected dataset: Record<string,
|
|
76
|
-
|
|
126
|
+
export declare class InMemoryFileDB<TObject, THandler extends SubscriptionClient = SubscriptionClient> {
|
|
127
|
+
protected settings: FileDbSettings<TObject>;
|
|
128
|
+
protected change: ChangeFunction<TObject> | undefined;
|
|
129
|
+
protected dataset: Record<string, TObject | MetaObject>;
|
|
130
|
+
protected namespace: string;
|
|
77
131
|
private lastSave;
|
|
78
|
-
|
|
132
|
+
protected stateTimer: NodeJS.Timeout | null;
|
|
79
133
|
private callbackSubscriptionClient;
|
|
80
|
-
|
|
134
|
+
protected readonly dataDir: string;
|
|
81
135
|
private readonly datasetName;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
136
|
+
protected log: InternalLogger;
|
|
137
|
+
protected readonly backupDir: string;
|
|
138
|
+
/**
|
|
139
|
+
* @param settings Settings for the database, the connection and backups
|
|
140
|
+
*/
|
|
141
|
+
constructor(settings: FileDbSettings<TObject>);
|
|
142
|
+
/**
|
|
143
|
+
* Open the database by loading the dataset from disk
|
|
144
|
+
*/
|
|
85
145
|
open(): Promise<void>;
|
|
86
146
|
/**
|
|
87
147
|
* Loads a dataset file
|
|
@@ -89,20 +149,55 @@ export declare class InMemoryFileDB {
|
|
|
89
149
|
* @param datasetName Filename of the file to load
|
|
90
150
|
* @returns obj read data, normally as object
|
|
91
151
|
*/
|
|
92
|
-
loadDatasetFile(datasetName: string): Promise<Record<string,
|
|
152
|
+
loadDatasetFile(datasetName: string): Promise<Record<string, TObject>>;
|
|
93
153
|
/**
|
|
94
154
|
* Loads the dataset including pot. Fallback handling
|
|
95
155
|
*
|
|
96
156
|
* @param datasetName Filename of the file to load
|
|
97
157
|
* @returns obj dataset read as object
|
|
98
158
|
*/
|
|
99
|
-
loadDataset(datasetName: string): Promise<Record<string,
|
|
159
|
+
loadDataset(datasetName: string): Promise<Record<string, TObject>>;
|
|
160
|
+
/**
|
|
161
|
+
* Normalize the backup settings and create the backup directory
|
|
162
|
+
*/
|
|
100
163
|
initBackupDir(): void;
|
|
101
|
-
|
|
102
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Subscribe a client to changes matching the given pattern
|
|
166
|
+
*
|
|
167
|
+
* @param client The client to register the subscription for
|
|
168
|
+
* @param type The subscription type (e.g. objects or states)
|
|
169
|
+
* @param pattern One or more patterns to subscribe to
|
|
170
|
+
*/
|
|
171
|
+
handleSubscribe(client: SubscriptionClient, type: string, pattern: string | string[]): void;
|
|
172
|
+
/**
|
|
173
|
+
* Remove a client's subscription for the given pattern
|
|
174
|
+
*
|
|
175
|
+
* @param client The client whose subscription should be removed
|
|
176
|
+
* @param type The subscription type (e.g. objects or states)
|
|
177
|
+
* @param pattern One or more patterns to unsubscribe from
|
|
178
|
+
* @param cb Called once the subscription has been removed
|
|
179
|
+
*/
|
|
103
180
|
handleUnsubscribe(client: SubscriptionClient, type: string, pattern: string | string[], cb?: () => void): void | Promise<void>;
|
|
104
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Publish a change to a single client. Must be implemented by a subclass that handles client communication.
|
|
183
|
+
*
|
|
184
|
+
* @param _client The client to publish to
|
|
185
|
+
* @param _type The change type (e.g. objects or states)
|
|
186
|
+
* @param _id The ID of the changed object or state
|
|
187
|
+
* @param _obj The new value of the object or state
|
|
188
|
+
*/
|
|
189
|
+
publishToClients(_client: SubscriptionClient, _type: string, _id: string, _obj: TObject | string | null | FileSize | MetaObject): number;
|
|
190
|
+
/**
|
|
191
|
+
* Delete outdated backup files according to the configured retention settings
|
|
192
|
+
*
|
|
193
|
+
* @param baseFilename Base file name of the backups that should be pruned
|
|
194
|
+
*/
|
|
105
195
|
deleteOldBackupFiles(baseFilename: string): void;
|
|
196
|
+
/**
|
|
197
|
+
* Format a timestamp into a "YYYY-MM-DD_HH-MM-SS" string used for backup file names
|
|
198
|
+
*
|
|
199
|
+
* @param date Timestamp in milliseconds
|
|
200
|
+
*/
|
|
106
201
|
getTimeStr(date: number): string;
|
|
107
202
|
/**
|
|
108
203
|
* Handle saving the dataset incl. backups
|
|
@@ -120,9 +215,25 @@ export declare class InMemoryFileDB {
|
|
|
120
215
|
* @param jsonString JSON string of the complete dataset to also be stored into a compressed backup file
|
|
121
216
|
*/
|
|
122
217
|
saveBackup(jsonString: string): void;
|
|
218
|
+
/**
|
|
219
|
+
* Get the current status of the database
|
|
220
|
+
*/
|
|
123
221
|
getStatus(): DbStatus;
|
|
124
|
-
|
|
125
|
-
|
|
222
|
+
/**
|
|
223
|
+
* Get the currently connected clients. Subclasses override this to return the real clients.
|
|
224
|
+
*/
|
|
225
|
+
getClients(): Record<string, THandler>;
|
|
226
|
+
/**
|
|
227
|
+
* Publish a change to all connected clients and local subscribers
|
|
228
|
+
*
|
|
229
|
+
* @param type The change type (e.g. objects or states)
|
|
230
|
+
* @param id The ID of the changed object or state
|
|
231
|
+
* @param obj The new value of the object or state
|
|
232
|
+
*/
|
|
233
|
+
publishAll(type: string, id: string, obj: TObject | string | null | FileSize | MetaObject): number;
|
|
234
|
+
/**
|
|
235
|
+
* Destructor of the class. Called when shutting down to persist state and clear timers.
|
|
236
|
+
*/
|
|
126
237
|
destroy(): Promise<void>;
|
|
127
238
|
}
|
|
128
239
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inMemFileDB.d.ts","sourceRoot":"","sources":["../../../src/lib/inMemFileDB.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"inMemFileDB.d.ts","sourceRoot":"","sources":["../../../src/lib/inMemFileDB.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* States DB in memory - Server
|
|
3
3
|
*
|
|
4
|
-
* Copyright 2013-
|
|
4
|
+
* Copyright 2013-2026 bluefox <dogafox@gmail.com>
|
|
5
5
|
*
|
|
6
6
|
* MIT License
|
|
7
7
|
*
|
|
8
8
|
*/
|
|
9
|
+
/// <reference types="@iobroker/types-dev" />
|
|
9
10
|
import fs from 'fs-extra';
|
|
10
11
|
import path from 'node:path';
|
|
11
12
|
import { tools } from '@iobroker/js-controller-common-db';
|
|
@@ -17,22 +18,22 @@ import { createGzip } from 'node:zlib';
|
|
|
17
18
|
export class InMemoryFileDB {
|
|
18
19
|
settings;
|
|
19
20
|
change;
|
|
20
|
-
dataset;
|
|
21
|
+
dataset = {};
|
|
21
22
|
namespace;
|
|
22
|
-
lastSave;
|
|
23
|
-
stateTimer;
|
|
24
|
-
callbackSubscriptionClient;
|
|
23
|
+
lastSave = null;
|
|
24
|
+
stateTimer = null;
|
|
25
|
+
callbackSubscriptionClient = {};
|
|
25
26
|
dataDir;
|
|
26
27
|
datasetName;
|
|
27
28
|
log;
|
|
28
29
|
backupDir;
|
|
30
|
+
/**
|
|
31
|
+
* @param settings Settings for the database, the connection and backups
|
|
32
|
+
*/
|
|
29
33
|
constructor(settings) {
|
|
30
|
-
this.settings = settings
|
|
34
|
+
this.settings = settings;
|
|
31
35
|
this.change = this.settings.change;
|
|
32
|
-
this.dataset = {};
|
|
33
36
|
this.namespace = this.settings.namespace || '';
|
|
34
|
-
this.lastSave = null;
|
|
35
|
-
this.callbackSubscriptionClient = {};
|
|
36
37
|
this.settings.backup = this.settings.connection.backup || {
|
|
37
38
|
disabled: false, // deactivates
|
|
38
39
|
files: 24, // minimum number of files
|
|
@@ -40,30 +41,37 @@ export class InMemoryFileDB {
|
|
|
40
41
|
period: 120, // minutes
|
|
41
42
|
path: '', // use default path
|
|
42
43
|
};
|
|
44
|
+
const backup = this.settings.backup;
|
|
43
45
|
this.dataDir = this.settings.connection.dataDir || tools.getDefaultDataDir();
|
|
44
46
|
if (!path.isAbsolute(this.dataDir)) {
|
|
45
47
|
this.dataDir = path.normalize(path.join(tools.getControllerDir(), this.dataDir));
|
|
46
48
|
}
|
|
47
49
|
this.dataDir = this.dataDir.replace(/\\/g, '/');
|
|
48
|
-
const
|
|
50
|
+
const fileDbOptions = this.settings.fileDB;
|
|
51
|
+
if (!fileDbOptions) {
|
|
52
|
+
throw new Error(`${this.namespace} No fileDB storage options were provided`);
|
|
53
|
+
}
|
|
54
|
+
const fileName = this.settings.jsonlDB ? this.settings.jsonlDB.fileName : fileDbOptions.fileName;
|
|
49
55
|
this.datasetName = path.join(this.dataDir, fileName);
|
|
50
56
|
const parts = path.dirname(this.datasetName);
|
|
51
57
|
fs.ensureDirSync(parts);
|
|
52
|
-
this.
|
|
53
|
-
this.backupDir = this.settings.backup.path || path.join(this.dataDir, this.settings.fileDB.backupDirName);
|
|
58
|
+
this.backupDir = backup.path || path.join(this.dataDir, fileDbOptions.backupDirName);
|
|
54
59
|
this.log = tools.getLogger(this.settings.logger);
|
|
55
|
-
if (!
|
|
60
|
+
if (!backup.disabled) {
|
|
56
61
|
try {
|
|
57
62
|
this.initBackupDir();
|
|
58
63
|
}
|
|
59
64
|
catch (e) {
|
|
60
65
|
this.log.error(`Database backups are disabled, because backup directory could not be initialized: ${e.message}`);
|
|
61
66
|
this.log.error('This leads to an increased risk of data loss, please check that the configured backup directory is available and restart the controller');
|
|
62
|
-
|
|
67
|
+
backup.disabled = true;
|
|
63
68
|
}
|
|
64
69
|
}
|
|
65
70
|
this.log.debug(`${this.namespace} Data File: ${this.datasetName}`);
|
|
66
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Open the database by loading the dataset from disk
|
|
74
|
+
*/
|
|
67
75
|
async open() {
|
|
68
76
|
// load values from file
|
|
69
77
|
this.dataset = await this.loadDataset(this.datasetName);
|
|
@@ -137,57 +145,71 @@ export class InMemoryFileDB {
|
|
|
137
145
|
}
|
|
138
146
|
return ret;
|
|
139
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Normalize the backup settings and create the backup directory
|
|
150
|
+
*/
|
|
140
151
|
initBackupDir() {
|
|
152
|
+
// Create backup directory
|
|
153
|
+
fs.ensureDirSync(this.backupDir);
|
|
154
|
+
const backup = this.settings.backup;
|
|
155
|
+
if (!backup) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
141
158
|
// Interval in minutes => to milliseconds
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
this.settings.backup.period = 120;
|
|
159
|
+
backup.period = backup.period === undefined ? 120 : parseInt(String(backup.period));
|
|
160
|
+
if (isNaN(backup.period)) {
|
|
161
|
+
backup.period = 120;
|
|
146
162
|
}
|
|
147
163
|
// Node.js timeouts overflow after roughly 24 days, defaulting to 1 millisecond, which causes chaos.
|
|
148
164
|
// If a user configured the backup this way, we use our default of 120 minutes instead.
|
|
149
165
|
const maxTimeoutMinutes = Math.floor((2 ** 31 - 1) / 60000);
|
|
150
|
-
if (
|
|
151
|
-
this.log.warn(`${this.namespace} Configured backup period ${
|
|
152
|
-
|
|
166
|
+
if (backup.period > maxTimeoutMinutes) {
|
|
167
|
+
this.log.warn(`${this.namespace} Configured backup period ${backup.period} is larger than the supported maximum of ${maxTimeoutMinutes} minutes. Defaulting to 120 minutes.`);
|
|
168
|
+
backup.period = 120;
|
|
153
169
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
this.settings.backup.files = 24;
|
|
170
|
+
backup.period *= 60_000;
|
|
171
|
+
backup.files = backup.files === undefined ? 24 : parseInt(String(backup.files));
|
|
172
|
+
if (isNaN(backup.files)) {
|
|
173
|
+
backup.files = 24;
|
|
159
174
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
this.settings.backup.hours = 48;
|
|
175
|
+
backup.hours = backup.hours === undefined ? 48 : parseInt(String(backup.hours));
|
|
176
|
+
if (isNaN(backup.hours)) {
|
|
177
|
+
backup.hours = 48;
|
|
164
178
|
}
|
|
165
|
-
// Create backup directory
|
|
166
|
-
fs.ensureDirSync(this.backupDir);
|
|
167
179
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Subscribe a client to changes matching the given pattern
|
|
182
|
+
*
|
|
183
|
+
* @param client The client to register the subscription for
|
|
184
|
+
* @param type The subscription type (e.g. objects or states)
|
|
185
|
+
* @param pattern One or more patterns to subscribe to
|
|
186
|
+
*/
|
|
187
|
+
handleSubscribe(client, type, pattern) {
|
|
188
|
+
client._subscribe ||= {};
|
|
189
|
+
client._subscribe[type] ||= [];
|
|
175
190
|
const s = client._subscribe[type];
|
|
176
191
|
if (pattern instanceof Array) {
|
|
177
192
|
pattern.forEach(pattern => {
|
|
178
193
|
if (s.find(sub => sub.pattern === pattern)) {
|
|
179
194
|
return;
|
|
180
195
|
}
|
|
181
|
-
s.push({ pattern, regex: new RegExp(tools.pattern2RegEx(pattern))
|
|
196
|
+
s.push({ pattern, regex: new RegExp(tools.pattern2RegEx(pattern)) });
|
|
182
197
|
});
|
|
183
198
|
}
|
|
184
199
|
else {
|
|
185
200
|
if (!s.find(sub => sub.pattern === pattern)) {
|
|
186
|
-
s.push({ pattern, regex: new RegExp(tools.pattern2RegEx(pattern))
|
|
201
|
+
s.push({ pattern, regex: new RegExp(tools.pattern2RegEx(pattern)) });
|
|
187
202
|
}
|
|
188
203
|
}
|
|
189
|
-
typeof cb === 'function' && cb();
|
|
190
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Remove a client's subscription for the given pattern
|
|
207
|
+
*
|
|
208
|
+
* @param client The client whose subscription should be removed
|
|
209
|
+
* @param type The subscription type (e.g. objects or states)
|
|
210
|
+
* @param pattern One or more patterns to unsubscribe from
|
|
211
|
+
* @param cb Called once the subscription has been removed
|
|
212
|
+
*/
|
|
191
213
|
handleUnsubscribe(client, type, pattern, cb) {
|
|
192
214
|
const s = client?._subscribe?.[type];
|
|
193
215
|
if (s) {
|
|
@@ -208,16 +230,29 @@ export class InMemoryFileDB {
|
|
|
208
230
|
}
|
|
209
231
|
return tools.maybeCallback(cb);
|
|
210
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Publish a change to a single client. Must be implemented by a subclass that handles client communication.
|
|
235
|
+
*
|
|
236
|
+
* @param _client The client to publish to
|
|
237
|
+
* @param _type The change type (e.g. objects or states)
|
|
238
|
+
* @param _id The ID of the changed object or state
|
|
239
|
+
* @param _obj The new value of the object or state
|
|
240
|
+
*/
|
|
211
241
|
publishToClients(_client, _type, _id, _obj) {
|
|
212
242
|
throw new Error('no communication handling implemented');
|
|
213
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Delete outdated backup files according to the configured retention settings
|
|
246
|
+
*
|
|
247
|
+
* @param baseFilename Base file name of the backups that should be pruned
|
|
248
|
+
*/
|
|
214
249
|
deleteOldBackupFiles(baseFilename) {
|
|
215
250
|
// delete files only if settings.backupNumber is not 0
|
|
216
251
|
let files = fs.readdirSync(this.backupDir);
|
|
217
252
|
files.sort();
|
|
218
|
-
const limit = Date.now() - this.settings.backup
|
|
253
|
+
const limit = Date.now() - (this.settings.backup?.hours ?? 48) * 3600000;
|
|
219
254
|
files = files.filter(f => f.endsWith(`${baseFilename}.gz`));
|
|
220
|
-
while (files.length > this.settings.backup
|
|
255
|
+
while (files.length > (this.settings.backup?.files ?? 24)) {
|
|
221
256
|
const file = files.shift();
|
|
222
257
|
if (!file) {
|
|
223
258
|
continue;
|
|
@@ -234,6 +269,11 @@ export class InMemoryFileDB {
|
|
|
234
269
|
}
|
|
235
270
|
}
|
|
236
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* Format a timestamp into a "YYYY-MM-DD_HH-MM-SS" string used for backup file names
|
|
274
|
+
*
|
|
275
|
+
* @param date Timestamp in milliseconds
|
|
276
|
+
*/
|
|
237
277
|
getTimeStr(date) {
|
|
238
278
|
const dateObj = new Date(date);
|
|
239
279
|
let text = `${dateObj.getFullYear().toString()}-`;
|
|
@@ -265,7 +305,7 @@ export class InMemoryFileDB {
|
|
|
265
305
|
async saveState() {
|
|
266
306
|
try {
|
|
267
307
|
const jsonString = await this.saveDataset();
|
|
268
|
-
if (!this.settings.backup
|
|
308
|
+
if (!this.settings.backup?.disabled && jsonString) {
|
|
269
309
|
this.saveBackup(jsonString);
|
|
270
310
|
}
|
|
271
311
|
}
|
|
@@ -342,9 +382,12 @@ export class InMemoryFileDB {
|
|
|
342
382
|
// save files for the last x hours
|
|
343
383
|
const now = Date.now();
|
|
344
384
|
// makes backups only if settings.backupInterval is not 0
|
|
345
|
-
|
|
385
|
+
const fileName = this.settings.fileDB?.fileName;
|
|
386
|
+
if (fileName &&
|
|
387
|
+
this.settings.backup?.period &&
|
|
388
|
+
(!this.lastSave || now - this.lastSave > this.settings.backup.period)) {
|
|
346
389
|
this.lastSave = now;
|
|
347
|
-
const backFileName = path.join(this.backupDir, `${this.getTimeStr(now)}_${
|
|
390
|
+
const backFileName = path.join(this.backupDir, `${this.getTimeStr(now)}_${fileName}.gz`);
|
|
348
391
|
try {
|
|
349
392
|
if (!fs.existsSync(backFileName)) {
|
|
350
393
|
const output = fs.createWriteStream(backFileName);
|
|
@@ -359,7 +402,7 @@ export class InMemoryFileDB {
|
|
|
359
402
|
compress.write(jsonString);
|
|
360
403
|
compress.end();
|
|
361
404
|
// analyse older files
|
|
362
|
-
this.deleteOldBackupFiles(
|
|
405
|
+
this.deleteOldBackupFiles(fileName);
|
|
363
406
|
}
|
|
364
407
|
}
|
|
365
408
|
catch (e) {
|
|
@@ -367,12 +410,25 @@ export class InMemoryFileDB {
|
|
|
367
410
|
}
|
|
368
411
|
}
|
|
369
412
|
}
|
|
413
|
+
/**
|
|
414
|
+
* Get the current status of the database
|
|
415
|
+
*/
|
|
370
416
|
getStatus() {
|
|
371
417
|
return { type: 'file', server: true };
|
|
372
418
|
}
|
|
419
|
+
/**
|
|
420
|
+
* Get the currently connected clients. Subclasses override this to return the real clients.
|
|
421
|
+
*/
|
|
373
422
|
getClients() {
|
|
374
423
|
return {};
|
|
375
424
|
}
|
|
425
|
+
/**
|
|
426
|
+
* Publish a change to all connected clients and local subscribers
|
|
427
|
+
*
|
|
428
|
+
* @param type The change type (e.g. objects or states)
|
|
429
|
+
* @param id The ID of the changed object or state
|
|
430
|
+
* @param obj The new value of the object or state
|
|
431
|
+
*/
|
|
376
432
|
publishAll(type, id, obj) {
|
|
377
433
|
if (id === undefined) {
|
|
378
434
|
this.log.error(`${this.namespace} Can not publish empty ID`);
|
|
@@ -399,7 +455,9 @@ export class InMemoryFileDB {
|
|
|
399
455
|
}
|
|
400
456
|
return publishCount;
|
|
401
457
|
}
|
|
402
|
-
|
|
458
|
+
/**
|
|
459
|
+
* Destructor of the class. Called when shutting down to persist state and clear timers.
|
|
460
|
+
*/
|
|
403
461
|
async destroy() {
|
|
404
462
|
if (this.stateTimer) {
|
|
405
463
|
clearTimeout(this.stateTimer);
|