fix: ensure the ATG will start from its saved status
[e-mobility-charging-stations-simulator.git] / src / utils / Configuration.ts
CommitLineData
d972af76
JB
1import { type FSWatcher, readFileSync, watch } from 'node:fs';
2import { dirname, join, resolve } from 'node:path';
10687422 3import { env } from 'node:process';
130783a7 4import { fileURLToPath } from 'node:url';
8114d10e
JB
5
6import chalk from 'chalk';
088ee3c1 7import merge from 'just-merge';
8114d10e 8
878e026c 9import { Constants } from './Constants';
a418c77b
JB
10import {
11 hasOwnProp,
12 isCFEnvironment,
13 isNotEmptyString,
14 isUndefined,
15 logPrefix,
16 once,
17} from './Utils';
83e00df1 18import {
268a74bb 19 ApplicationProtocol,
83e00df1 20 type ConfigurationData,
5d049829 21 ConfigurationSection,
268a74bb 22 FileType,
3d48c1c1 23 type LogConfiguration,
83e00df1
JB
24 type StationTemplateUrl,
25 type StorageConfiguration,
268a74bb 26 StorageType,
e7aeea18 27 SupervisionUrlDistribution,
83e00df1
JB
28 type UIServerConfiguration,
29 type WorkerConfiguration,
268a74bb 30} from '../types';
769d3b10
JB
31import {
32 DEFAULT_ELEMENT_START_DELAY,
33 DEFAULT_POOL_MAX_SIZE,
34 DEFAULT_POOL_MIN_SIZE,
35 DEFAULT_WORKER_START_DELAY,
36 WorkerProcessType,
37} from '../worker';
7dde0b73 38
e7c0fce0
JB
39type ConfigurationSectionType =
40 | LogConfiguration
41 | StorageConfiguration
42 | WorkerConfiguration
43 | UIServerConfiguration;
44
268a74bb 45export class Configuration {
6501eda9
JB
46 public static configurationChangeCallback: () => Promise<void>;
47
d972af76
JB
48 private static configurationFile = join(
49 dirname(fileURLToPath(import.meta.url)),
e7aeea18 50 'assets',
5edd8ba0 51 'config.json',
e7aeea18 52 );
10068088 53
c7db8ecb 54 private static configurationData?: ConfigurationData;
3602e107 55 private static configurationFileWatcher?: FSWatcher;
974efe6c
JB
56 private static configurationSectionCache = new Map<
57 ConfigurationSection,
e7c0fce0 58 ConfigurationSectionType
5d049829
JB
59 >([
60 [ConfigurationSection.log, Configuration.buildLogSection()],
61 [ConfigurationSection.performanceStorage, Configuration.buildPerformanceStorageSection()],
62 [ConfigurationSection.worker, Configuration.buildWorkerSection()],
63 [ConfigurationSection.uiServer, Configuration.buildUIServerSection()],
64 ]);
974efe6c 65
d5bd1c00
JB
66 private constructor() {
67 // This is intentional
68 }
69
e7c0fce0
JB
70 public static getConfigurationSection<T extends ConfigurationSectionType>(
71 sectionName: ConfigurationSection,
72 ): T {
81b9a105
JB
73 if (!Configuration.isConfigurationSectionCached(sectionName)) {
74 Configuration.cacheConfigurationSection(sectionName);
c1c97db8 75 }
5d049829
JB
76 return Configuration.configurationSectionCache.get(sectionName) as T;
77 }
78
5d049829 79 public static getStationTemplateUrls(): StationTemplateUrl[] | undefined {
5f742aac
JB
80 const checkDeprecatedConfigurationKeysOnce = once(
81 Configuration.checkDeprecatedConfigurationKeys.bind(Configuration),
0898a067 82 Configuration,
5f742aac
JB
83 );
84 checkDeprecatedConfigurationKeysOnce();
5d049829
JB
85 return Configuration.getConfigurationData()?.stationTemplateUrls;
86 }
87
5d049829 88 public static getSupervisionUrls(): string | string[] | undefined {
a37fc6dc
JB
89 if (
90 !isUndefined(
1c9de2b9 91 Configuration.getConfigurationData()?.['supervisionURLs' as keyof ConfigurationData],
a37fc6dc
JB
92 )
93 ) {
5d049829 94 Configuration.getConfigurationData()!.supervisionUrls = Configuration.getConfigurationData()![
a37fc6dc 95 'supervisionURLs' as keyof ConfigurationData
5d049829
JB
96 ] as string | string[];
97 }
98 return Configuration.getConfigurationData()?.supervisionUrls;
99 }
100
101 public static getSupervisionUrlDistribution(): SupervisionUrlDistribution | undefined {
5d049829
JB
102 return hasOwnProp(Configuration.getConfigurationData(), 'supervisionUrlDistribution')
103 ? Configuration.getConfigurationData()?.supervisionUrlDistribution
104 : SupervisionUrlDistribution.ROUND_ROBIN;
105 }
106
c831d2bc 107 public static workerPoolInUse(): boolean {
1d8f226b 108 return [WorkerProcessType.dynamicPool, WorkerProcessType.fixedPool].includes(
eb979012
JB
109 Configuration.getConfigurationSection<WorkerConfiguration>(ConfigurationSection.worker)
110 .processType!,
c831d2bc
JB
111 );
112 }
113
114 public static workerDynamicPoolInUse(): boolean {
eb979012
JB
115 return (
116 Configuration.getConfigurationSection<WorkerConfiguration>(ConfigurationSection.worker)
117 .processType === WorkerProcessType.dynamicPool
118 );
c831d2bc
JB
119 }
120
a418c77b
JB
121 private static logPrefix = (): string => {
122 return logPrefix(' Simulator configuration |');
123 };
124
81b9a105
JB
125 private static isConfigurationSectionCached(sectionName: ConfigurationSection): boolean {
126 return Configuration.configurationSectionCache.has(sectionName);
127 }
128
129 private static cacheConfigurationSection(sectionName: ConfigurationSection): void {
130 switch (sectionName) {
131 case ConfigurationSection.log:
132 Configuration.configurationSectionCache.set(sectionName, Configuration.buildLogSection());
133 break;
134 case ConfigurationSection.performanceStorage:
135 Configuration.configurationSectionCache.set(
136 sectionName,
137 Configuration.buildPerformanceStorageSection(),
138 );
139 break;
140 case ConfigurationSection.worker:
141 Configuration.configurationSectionCache.set(
142 sectionName,
143 Configuration.buildWorkerSection(),
144 );
145 break;
146 case ConfigurationSection.uiServer:
147 Configuration.configurationSectionCache.set(
148 sectionName,
149 Configuration.buildUIServerSection(),
150 );
151 break;
152 default:
153 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
154 throw new Error(`Unknown configuration section '${sectionName}'`);
155 }
156 }
157
5d049829 158 private static buildUIServerSection(): UIServerConfiguration {
675fa8e3 159 let uiServerConfiguration: UIServerConfiguration = {
b803eefa 160 enabled: false,
1f7fa4de 161 type: ApplicationProtocol.WS,
c127bd64 162 options: {
adbddcb4
JB
163 host: Constants.DEFAULT_UI_SERVER_HOST,
164 port: Constants.DEFAULT_UI_SERVER_PORT,
c127bd64 165 },
6a49ad23 166 };
f74e97ac 167 if (hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.uiServer)) {
598c886d
JB
168 uiServerConfiguration = merge<UIServerConfiguration>(
169 uiServerConfiguration,
f74e97ac 170 Configuration.getConfigurationData()!.uiServer!,
598c886d 171 );
6a49ad23 172 }
9bf0ef23 173 if (isCFEnvironment() === true) {
72092cfc 174 delete uiServerConfiguration.options?.host;
10687422 175 uiServerConfiguration.options!.port = parseInt(env.PORT!);
b803eefa 176 }
5d049829 177 return uiServerConfiguration;
6a49ad23
JB
178 }
179
5d049829 180 private static buildPerformanceStorageSection(): StorageConfiguration {
6a49ad23
JB
181 let storageConfiguration: StorageConfiguration = {
182 enabled: false,
183 type: StorageType.JSON_FILE,
f0855167 184 uri: Configuration.getDefaultPerformanceStorageUri(StorageType.JSON_FILE),
6a49ad23 185 };
f74e97ac 186 if (hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.performanceStorage)) {
e7aeea18 187 storageConfiguration = {
1ba1e8fb 188 ...storageConfiguration,
f74e97ac
JB
189 ...Configuration.getConfigurationData()?.performanceStorage,
190 ...(Configuration.getConfigurationData()?.performanceStorage?.type ===
191 StorageType.JSON_FILE &&
192 Configuration.getConfigurationData()?.performanceStorage?.uri && {
e8044a69 193 uri: Configuration.buildPerformanceUriFilePath(
f74e97ac 194 new URL(Configuration.getConfigurationData()!.performanceStorage!.uri!).pathname,
e8044a69 195 ),
f682b2dc 196 }),
72f041bd 197 };
72f041bd 198 }
5d049829 199 return storageConfiguration;
7dde0b73
JB
200 }
201
5d049829 202 private static buildLogSection(): LogConfiguration {
3d48c1c1
JB
203 const defaultLogConfiguration: LogConfiguration = {
204 enabled: true,
205 file: 'logs/combined.log',
206 errorFile: 'logs/error.log',
207 statisticsInterval: Constants.DEFAULT_LOG_STATISTICS_INTERVAL,
208 level: 'info',
209 format: 'simple',
210 rotate: true,
211 };
212 const deprecatedLogConfiguration: LogConfiguration = {
f74e97ac
JB
213 ...(hasOwnProp(Configuration.getConfigurationData(), 'logEnabled') && {
214 enabled: Configuration.getConfigurationData()?.logEnabled,
3d48c1c1 215 }),
f74e97ac
JB
216 ...(hasOwnProp(Configuration.getConfigurationData(), 'logFile') && {
217 file: Configuration.getConfigurationData()?.logFile,
3d48c1c1 218 }),
f74e97ac
JB
219 ...(hasOwnProp(Configuration.getConfigurationData(), 'logErrorFile') && {
220 errorFile: Configuration.getConfigurationData()?.logErrorFile,
3d48c1c1 221 }),
f74e97ac
JB
222 ...(hasOwnProp(Configuration.getConfigurationData(), 'logStatisticsInterval') && {
223 statisticsInterval: Configuration.getConfigurationData()?.logStatisticsInterval,
3d48c1c1 224 }),
f74e97ac
JB
225 ...(hasOwnProp(Configuration.getConfigurationData(), 'logLevel') && {
226 level: Configuration.getConfigurationData()?.logLevel,
3d48c1c1 227 }),
f74e97ac
JB
228 ...(hasOwnProp(Configuration.getConfigurationData(), 'logConsole') && {
229 console: Configuration.getConfigurationData()?.logConsole,
3d48c1c1 230 }),
f74e97ac
JB
231 ...(hasOwnProp(Configuration.getConfigurationData(), 'logFormat') && {
232 format: Configuration.getConfigurationData()?.logFormat,
3d48c1c1 233 }),
f74e97ac
JB
234 ...(hasOwnProp(Configuration.getConfigurationData(), 'logRotate') && {
235 rotate: Configuration.getConfigurationData()?.logRotate,
3d48c1c1 236 }),
f74e97ac
JB
237 ...(hasOwnProp(Configuration.getConfigurationData(), 'logMaxFiles') && {
238 maxFiles: Configuration.getConfigurationData()?.logMaxFiles,
3d48c1c1 239 }),
f74e97ac
JB
240 ...(hasOwnProp(Configuration.getConfigurationData(), 'logMaxSize') && {
241 maxSize: Configuration.getConfigurationData()?.logMaxSize,
3d48c1c1
JB
242 }),
243 };
244 const logConfiguration: LogConfiguration = {
245 ...defaultLogConfiguration,
246 ...deprecatedLogConfiguration,
f74e97ac
JB
247 ...(hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.log) &&
248 Configuration.getConfigurationData()?.log),
3d48c1c1 249 };
5d049829 250 return logConfiguration;
3d48c1c1
JB
251 }
252
5d049829 253 private static buildWorkerSection(): WorkerConfiguration {
3602e107
JB
254 const defaultWorkerConfiguration: WorkerConfiguration = {
255 processType: WorkerProcessType.workerSet,
256 startDelay: DEFAULT_WORKER_START_DELAY,
257 elementsPerWorker: 'auto',
258 elementStartDelay: DEFAULT_ELEMENT_START_DELAY,
259 poolMinSize: DEFAULT_POOL_MIN_SIZE,
260 poolMaxSize: DEFAULT_POOL_MAX_SIZE,
261 };
262 const deprecatedWorkerConfiguration: WorkerConfiguration = {
263 ...(hasOwnProp(Configuration.getConfigurationData(), 'workerProcess') && {
264 processType: Configuration.getConfigurationData()?.workerProcess,
265 }),
266 ...(hasOwnProp(Configuration.getConfigurationData(), 'workerStartDelay') && {
267 startDelay: Configuration.getConfigurationData()?.workerStartDelay,
268 }),
269 ...(hasOwnProp(Configuration.getConfigurationData(), 'chargingStationsPerWorker') && {
270 elementsPerWorker: Configuration.getConfigurationData()?.chargingStationsPerWorker,
271 }),
272 ...(hasOwnProp(Configuration.getConfigurationData(), 'elementStartDelay') && {
273 elementStartDelay: Configuration.getConfigurationData()?.elementStartDelay,
274 }),
275 ...(hasOwnProp(Configuration.getConfigurationData(), 'workerPoolMinSize') && {
276 poolMinSize: Configuration.getConfigurationData()?.workerPoolMinSize,
277 }),
278 ...(hasOwnProp(Configuration.getConfigurationData(), 'workerPoolMaxSize') && {
279 poolMaxSize: Configuration.getConfigurationData()?.workerPoolMaxSize,
280 }),
281 };
282 hasOwnProp(Configuration.getConfigurationData(), 'workerPoolStrategy') &&
283 delete Configuration.getConfigurationData()?.workerPoolStrategy;
284 const workerConfiguration: WorkerConfiguration = {
285 ...defaultWorkerConfiguration,
286 ...deprecatedWorkerConfiguration,
287 ...(hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.worker) &&
288 Configuration.getConfigurationData()?.worker),
289 };
290 if (!Object.values(WorkerProcessType).includes(workerConfiguration.processType!)) {
291 throw new SyntaxError(
292 `Invalid worker process type '${workerConfiguration.processType}' defined in configuration`,
293 );
294 }
295 return workerConfiguration;
296 }
297
3602e107 298 private static checkDeprecatedConfigurationKeys() {
3602e107
JB
299 // connection timeout
300 Configuration.warnDeprecatedConfigurationKey(
301 'autoReconnectTimeout',
302 undefined,
303 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead",
304 );
305 Configuration.warnDeprecatedConfigurationKey(
306 'connectionTimeout',
307 undefined,
308 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead",
309 );
310 // connection retries
311 Configuration.warnDeprecatedConfigurationKey(
312 'autoReconnectMaxRetries',
313 undefined,
314 'Use it in charging station template instead',
315 );
316 // station template url(s)
317 Configuration.warnDeprecatedConfigurationKey(
318 'stationTemplateURLs',
319 undefined,
320 "Use 'stationTemplateUrls' instead",
321 );
322 !isUndefined(
1c9de2b9 323 Configuration.getConfigurationData()?.['stationTemplateURLs' as keyof ConfigurationData],
3602e107
JB
324 ) &&
325 (Configuration.getConfigurationData()!.stationTemplateUrls =
326 Configuration.getConfigurationData()![
327 'stationTemplateURLs' as keyof ConfigurationData
328 ] as StationTemplateUrl[]);
1c9de2b9 329 Configuration.getConfigurationData()?.stationTemplateUrls.forEach(
3602e107 330 (stationTemplateUrl: StationTemplateUrl) => {
1c9de2b9 331 if (!isUndefined(stationTemplateUrl?.['numberOfStation' as keyof StationTemplateUrl])) {
3602e107
JB
332 console.error(
333 `${chalk.green(Configuration.logPrefix())} ${chalk.red(
334 `Deprecated configuration key 'numberOfStation' usage for template file '${stationTemplateUrl.file}' in 'stationTemplateUrls'. Use 'numberOfStations' instead`,
335 )}`,
336 );
337 }
338 },
339 );
340 // supervision url(s)
341 Configuration.warnDeprecatedConfigurationKey(
342 'supervisionURLs',
343 undefined,
344 "Use 'supervisionUrls' instead",
345 );
346 // supervision urls distribution
347 Configuration.warnDeprecatedConfigurationKey(
348 'distributeStationToTenantEqually',
349 undefined,
350 "Use 'supervisionUrlDistribution' instead",
351 );
352 Configuration.warnDeprecatedConfigurationKey(
353 'distributeStationsToTenantsEqually',
354 undefined,
355 "Use 'supervisionUrlDistribution' instead",
356 );
357 // worker section
e7aeea18 358 Configuration.warnDeprecatedConfigurationKey(
e80bc579 359 'useWorkerPool',
1895299d 360 undefined,
974efe6c 361 `Use '${ConfigurationSection.worker}' section to define the type of worker process model instead`,
cf2a5d9b
JB
362 );
363 Configuration.warnDeprecatedConfigurationKey(
364 'workerProcess',
1895299d 365 undefined,
974efe6c 366 `Use '${ConfigurationSection.worker}' section to define the type of worker process model instead`,
cf2a5d9b
JB
367 );
368 Configuration.warnDeprecatedConfigurationKey(
369 'workerStartDelay',
1895299d 370 undefined,
974efe6c 371 `Use '${ConfigurationSection.worker}' section to define the worker start delay instead`,
cf2a5d9b
JB
372 );
373 Configuration.warnDeprecatedConfigurationKey(
374 'chargingStationsPerWorker',
1895299d 375 undefined,
974efe6c 376 `Use '${ConfigurationSection.worker}' section to define the number of element(s) per worker instead`,
cf2a5d9b
JB
377 );
378 Configuration.warnDeprecatedConfigurationKey(
379 'elementStartDelay',
1895299d 380 undefined,
974efe6c 381 `Use '${ConfigurationSection.worker}' section to define the worker's element start delay instead`,
cf2a5d9b
JB
382 );
383 Configuration.warnDeprecatedConfigurationKey(
384 'workerPoolMinSize',
1895299d 385 undefined,
974efe6c 386 `Use '${ConfigurationSection.worker}' section to define the worker pool minimum size instead`,
e7aeea18 387 );
e7aeea18 388 Configuration.warnDeprecatedConfigurationKey(
1d8f226b 389 'workerPoolSize',
1895299d 390 undefined,
974efe6c 391 `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`,
e7aeea18 392 );
cf2a5d9b 393 Configuration.warnDeprecatedConfigurationKey(
1d8f226b 394 'workerPoolMaxSize',
1895299d 395 undefined,
974efe6c 396 `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`,
cf2a5d9b
JB
397 );
398 Configuration.warnDeprecatedConfigurationKey(
1d8f226b 399 'workerPoolStrategy',
1895299d 400 undefined,
974efe6c 401 `Use '${ConfigurationSection.worker}' section to define the worker pool strategy instead`,
cf2a5d9b 402 );
eda9c451
JB
403 Configuration.warnDeprecatedConfigurationKey(
404 'poolStrategy',
974efe6c 405 ConfigurationSection.worker,
eda9c451
JB
406 'Not publicly exposed to end users',
407 );
1d8f226b
JB
408 if (
409 Configuration.getConfigurationData()?.worker?.processType ===
410 ('staticPool' as WorkerProcessType)
411 ) {
412 console.error(
413 `${chalk.green(Configuration.logPrefix())} ${chalk.red(
414 `Deprecated configuration 'staticPool' value usage in worker section 'processType' field. Use '${WorkerProcessType.fixedPool}' value instead`,
415 )}`,
416 );
417 }
3602e107
JB
418 // log section
419 Configuration.warnDeprecatedConfigurationKey(
420 'logEnabled',
421 undefined,
422 `Use '${ConfigurationSection.log}' section to define the logging enablement instead`,
423 );
424 Configuration.warnDeprecatedConfigurationKey(
425 'logFile',
426 undefined,
427 `Use '${ConfigurationSection.log}' section to define the log file instead`,
428 );
429 Configuration.warnDeprecatedConfigurationKey(
430 'logErrorFile',
431 undefined,
432 `Use '${ConfigurationSection.log}' section to define the log error file instead`,
433 );
434 Configuration.warnDeprecatedConfigurationKey(
435 'logConsole',
436 undefined,
437 `Use '${ConfigurationSection.log}' section to define the console logging enablement instead`,
438 );
439 Configuration.warnDeprecatedConfigurationKey(
440 'logStatisticsInterval',
441 undefined,
442 `Use '${ConfigurationSection.log}' section to define the log statistics interval instead`,
443 );
444 Configuration.warnDeprecatedConfigurationKey(
445 'logLevel',
446 undefined,
447 `Use '${ConfigurationSection.log}' section to define the log level instead`,
448 );
449 Configuration.warnDeprecatedConfigurationKey(
450 'logFormat',
451 undefined,
452 `Use '${ConfigurationSection.log}' section to define the log format instead`,
453 );
454 Configuration.warnDeprecatedConfigurationKey(
455 'logRotate',
456 undefined,
457 `Use '${ConfigurationSection.log}' section to define the log rotation enablement instead`,
458 );
459 Configuration.warnDeprecatedConfigurationKey(
460 'logMaxFiles',
461 undefined,
462 `Use '${ConfigurationSection.log}' section to define the log maximum files instead`,
463 );
464 Configuration.warnDeprecatedConfigurationKey(
465 'logMaxSize',
466 undefined,
467 `Use '${ConfigurationSection.log}' section to define the log maximum size instead`,
468 );
469 // performanceStorage section
470 Configuration.warnDeprecatedConfigurationKey(
471 'URI',
472 ConfigurationSection.performanceStorage,
473 "Use 'uri' instead",
474 );
475 // uiServer section
476 if (hasOwnProp(Configuration.getConfigurationData(), 'uiWebSocketServer')) {
477 console.error(
478 `${chalk.green(Configuration.logPrefix())} ${chalk.red(
479 `Deprecated configuration section 'uiWebSocketServer' usage. Use '${ConfigurationSection.uiServer}' instead`,
480 )}`,
b5b2c3e8
JB
481 );
482 }
7dde0b73 483 }
eb3937cb 484
e7aeea18
JB
485 private static warnDeprecatedConfigurationKey(
486 key: string,
487 sectionName?: string,
5edd8ba0 488 logMsgToAppend = '',
e7aeea18 489 ) {
e7aeea18
JB
490 if (
491 sectionName &&
1c9de2b9
JB
492 !isUndefined(
493 Configuration.getConfigurationData()?.[sectionName as keyof ConfigurationData],
494 ) &&
f74e97ac 495 !isUndefined(
a37fc6dc 496 (
1c9de2b9 497 Configuration.getConfigurationData()?.[sectionName as keyof ConfigurationData] as Record<
a37fc6dc
JB
498 string,
499 unknown
500 >
1c9de2b9 501 )?.[key],
f74e97ac 502 )
e7aeea18
JB
503 ) {
504 console.error(
c5e52a07
JB
505 `${chalk.green(Configuration.logPrefix())} ${chalk.red(
506 `Deprecated configuration key '${key}' usage in section '${sectionName}'${
507 logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
5edd8ba0
JB
508 }`,
509 )}`,
e7aeea18 510 );
a37fc6dc 511 } else if (
1c9de2b9 512 !isUndefined(Configuration.getConfigurationData()?.[key as keyof ConfigurationData])
a37fc6dc 513 ) {
e7aeea18 514 console.error(
c5e52a07
JB
515 `${chalk.green(Configuration.logPrefix())} ${chalk.red(
516 `Deprecated configuration key '${key}' usage${
517 logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
5edd8ba0
JB
518 }`,
519 )}`,
e7aeea18 520 );
eb3937cb
JB
521 }
522 }
523
c7db8ecb 524 private static getConfigurationData(): ConfigurationData | undefined {
f74e97ac 525 if (!Configuration.configurationData) {
23132a44 526 try {
f74e97ac 527 Configuration.configurationData = JSON.parse(
5edd8ba0 528 readFileSync(Configuration.configurationFile, 'utf8'),
e7aeea18 529 ) as ConfigurationData;
c96b764e
JB
530 if (!Configuration.configurationFileWatcher) {
531 Configuration.configurationFileWatcher = Configuration.getConfigurationFileWatcher();
532 }
23132a44 533 } catch (error) {
69074173 534 Configuration.handleFileException(
a95873d8 535 Configuration.configurationFile,
7164966d
JB
536 FileType.Configuration,
537 error as NodeJS.ErrnoException,
5edd8ba0 538 Configuration.logPrefix(),
e7aeea18 539 );
23132a44 540 }
eb3937cb 541 }
f74e97ac 542 return Configuration.configurationData;
eb3937cb 543 }
963ee397 544
d972af76 545 private static getConfigurationFileWatcher(): FSWatcher | undefined {
23132a44 546 try {
d972af76 547 return watch(Configuration.configurationFile, (event, filename): void => {
e1d9a0f4 548 if (filename!.trim()!.length > 0 && event === 'change') {
c7db8ecb 549 delete Configuration.configurationData;
974efe6c 550 Configuration.configurationSectionCache.clear();
9bf0ef23 551 if (!isUndefined(Configuration.configurationChangeCallback)) {
9f139e99 552 Configuration.configurationChangeCallback().catch((error) => {
dcaf96dc
JB
553 throw typeof error === 'string' ? new Error(error) : error;
554 });
3ec10737 555 }
23132a44
JB
556 }
557 });
558 } catch (error) {
69074173 559 Configuration.handleFileException(
a95873d8 560 Configuration.configurationFile,
7164966d
JB
561 FileType.Configuration,
562 error as NodeJS.ErrnoException,
5edd8ba0 563 Configuration.logPrefix(),
e7aeea18 564 );
23132a44 565 }
ded13d97
JB
566 }
567
69074173
JB
568 private static handleFileException(
569 file: string,
570 fileType: FileType,
571 error: NodeJS.ErrnoException,
a418c77b 572 logPfx: string,
69074173 573 ): void {
a418c77b 574 const prefix = isNotEmptyString(logPfx) ? `${logPfx} ` : '';
69074173
JB
575 let logMsg: string;
576 switch (error.code) {
577 case 'ENOENT':
5587f482 578 logMsg = `${fileType} file ${file} not found: `;
69074173
JB
579 break;
580 case 'EEXIST':
5587f482 581 logMsg = `${fileType} file ${file} already exists: `;
69074173
JB
582 break;
583 case 'EACCES':
5587f482 584 logMsg = `${fileType} file ${file} access denied: `;
69074173 585 break;
7b5dbe91 586 case 'EPERM':
5587f482 587 logMsg = `${fileType} file ${file} permission denied: `;
7b5dbe91 588 break;
69074173 589 default:
5587f482 590 logMsg = `${fileType} file ${file} error: `;
69074173 591 }
5587f482 592 console.error(`${chalk.green(prefix)}${chalk.red(logMsg)}`, error);
7b5dbe91 593 throw error;
69074173
JB
594 }
595
1f5df42a 596 private static getDefaultPerformanceStorageUri(storageType: StorageType) {
d5603918
JB
597 switch (storageType) {
598 case StorageType.JSON_FILE:
e8044a69 599 return Configuration.buildPerformanceUriFilePath(
5edd8ba0 600 `${Constants.DEFAULT_PERFORMANCE_DIRECTORY}/${Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME}`,
e8044a69 601 );
d5603918 602 case StorageType.SQLITE:
e8044a69 603 return Configuration.buildPerformanceUriFilePath(
5edd8ba0 604 `${Constants.DEFAULT_PERFORMANCE_DIRECTORY}/${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`,
e8044a69 605 );
d5603918 606 default:
f39ae767 607 throw new Error(`Unsupported storage type '${storageType}'`);
d5603918
JB
608 }
609 }
e8044a69
JB
610
611 private static buildPerformanceUriFilePath(file: string) {
d972af76 612 return `file://${join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), file)}`;
e8044a69 613 }
7dde0b73 614}