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