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