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