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