fix: various fixes to files handling and their content caching
authorJérôme Benoit <jerome.benoit@sap.com>
Mon, 22 May 2023 08:48:13 +0000 (10:48 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Mon, 22 May 2023 08:48:13 +0000 (10:48 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
14 files changed:
.vscode/settings.json
src/charging-station/ChargingStation.ts
src/charging-station/ChargingStationConfigurationUtils.ts
src/charging-station/IdTagsCache.ts
src/charging-station/ocpp/OCPPIncomingRequestService.ts
src/charging-station/ocpp/OCPPRequestService.ts
src/charging-station/ui-server/AbstractUIServer.ts
src/performance/storage/JsonFileStorage.ts
src/performance/storage/MongoDBStorage.ts
src/performance/storage/Storage.ts
src/performance/storage/StorageFactory.ts
src/types/Error.ts
src/utils/Configuration.ts
src/utils/ErrorUtils.ts

index b8f730caa6ca2df877f8029e5833c8bc6b86fb4b..47a0799f031eb80b48e03dbc8107012e7780044d 100644 (file)
@@ -21,6 +21,7 @@
     "evses",
     "iccid",
     "idtag",
+    "idtags",
     "imsi",
     "lcov",
     "logform",
index 1e95df4da9b5918a8f30c10386f4109156f0328d..e7bf50596acd04966c91ca7f97032d73189da6d2 100644 (file)
@@ -1493,7 +1493,7 @@ export class ChargingStation {
 
   private getConfigurationFromFile(): ChargingStationConfiguration | undefined {
     let configuration: ChargingStationConfiguration | undefined;
-    if (this.configurationFile && fs.existsSync(this.configurationFile)) {
+    if (Utils.isNotEmptyString(this.configurationFile) && fs.existsSync(this.configurationFile)) {
       try {
         if (this.sharedLRUCache.hasChargingStationConfiguration(this.configurationFileHash)) {
           configuration = this.sharedLRUCache.getChargingStationConfiguration(
@@ -1546,7 +1546,7 @@ export class ChargingStation {
   private saveConfiguration(
     chargingStationAutomaticTransactionGeneratorConfiguration?: ChargingStationAutomaticTransactionGeneratorConfiguration
   ): void {
-    if (this.configurationFile) {
+    if (Utils.isNotEmptyString(this.configurationFile)) {
       try {
         if (!fs.existsSync(path.dirname(this.configurationFile))) {
           fs.mkdirSync(path.dirname(this.configurationFile), { recursive: true });
index e529aa7fc29a90a014a3d3370eed029602b182a1..7ffb4e216fbdb26b8becc72b3c4d19cddec6d2a0 100644 (file)
@@ -97,6 +97,7 @@ export class ChargingStationConfigurationUtils {
     key: ConfigurationKeyType,
     params: DeleteConfigurationKeyParams = { save: true, caseInsensitive: false }
   ): ConfigurationKey[] | undefined {
+    params = { ...{ save: true, caseInsensitive: false }, ...params };
     const keyFound = ChargingStationConfigurationUtils.getConfigurationKey(
       chargingStation,
       key,
index 7d766a40d93d6fb1eebf39bfdb873542a7cd7dba..a4ce9a100608d2959529b46995e38ed13788b0a0 100644 (file)
@@ -27,6 +27,15 @@ export class IdTagsCache {
     return IdTagsCache.instance;
   }
 
+  /**
+   * Get one idtag from the cache given the distribution
+   * Must be called after checking the cache is not an empty array
+   *
+   * @param distribution
+   * @param chargingStation
+   * @param connectorId
+   * @returns
+   */
   public getIdTag(
     distribution: IdTagDistribution,
     chargingStation: ChargingStation,
@@ -46,9 +55,19 @@ export class IdTagsCache {
     }
   }
 
+  /**
+   * Get all idtags from the cache
+   * Must be called after checking the cache is not an empty array
+   *
+   * @param file
+   * @returns
+   */
   public getIdTags(file: string): string[] | undefined {
     if (this.hasIdTagsCache(file) === false) {
-      this.setIdTagsCache(file, this.getIdTagsFromFile(file));
+      this.setIdTagsCache(
+        Utils.isNotEmptyString(file) ? file : 'empty',
+        this.getIdTagsFromFile(file)
+      );
     }
     return this.getIdTagsCache(file);
   }
@@ -152,11 +171,9 @@ export class IdTagsCache {
   }
 
   private getIdTagsFromFile(file: string): string[] {
-    let idTags: string[] = [];
-    if (file) {
+    if (Utils.isNotEmptyString(file)) {
       try {
-        // Load id tags file
-        idTags = JSON.parse(fs.readFileSync(file, 'utf8')) as string[];
+        return JSON.parse(fs.readFileSync(file, 'utf8')) as string[];
       } catch (error) {
         ErrorUtils.handleFileException(
           file,
@@ -165,10 +182,9 @@ export class IdTagsCache {
           this.logPrefix(file)
         );
       }
-    } else {
-      logger.info(`${this.logPrefix(file)} No id tags file given`);
     }
-    return idTags;
+    logger.info(`${this.logPrefix(file)} No id tags file given in configuration`);
+    return [];
   }
 
   private logPrefix = (file: string): string => {
index c98c28b8186a480ba630d3e2c052ff2ba965f37b..1206cede00d0493df941f54e73dc03cffedfc28c 100644 (file)
@@ -15,7 +15,7 @@ import type {
   JsonType,
   OCPPVersion,
 } from '../../types';
-import { logger } from '../../utils';
+import { ErrorUtils, logger } from '../../utils';
 
 const moduleName = 'OCPPIncomingRequestService';
 
@@ -56,12 +56,13 @@ export abstract class OCPPIncomingRequestService extends AsyncResource {
     return OCPPIncomingRequestService.instance as T;
   }
 
-  protected handleIncomingRequestError<T>(
+  protected handleIncomingRequestError<T extends JsonType>(
     chargingStation: ChargingStation,
     commandName: IncomingRequestCommand,
     error: Error,
-    params: HandleErrorParams<T> = { throwError: true }
+    params: HandleErrorParams<T> = { throwError: true, consoleOut: false }
   ): T | undefined {
+    ErrorUtils.handleErrorParams(params);
     logger.error(
       `${chargingStation.logPrefix()} ${moduleName}.handleIncomingRequestError: Incoming request command '${commandName}' error:`,
       error
index 06258fe4a1e071f7f1581420116329a80906dcd7..d2122a65ad2f281c7a9de9cd6b9afcbbeeb87a9d 100644 (file)
@@ -164,6 +164,10 @@ export abstract class OCPPRequestService {
       throwError: false,
     }
   ): Promise<ResponseType> {
+    params = {
+      ...{ skipBufferingOnError: false, triggerMessage: false, throwError: false },
+      ...params,
+    };
     try {
       return await this.internalSendMessage(
         chargingStation,
@@ -263,8 +267,13 @@ export abstract class OCPPRequestService {
     params: RequestParams = {
       skipBufferingOnError: false,
       triggerMessage: false,
+      throwError: false,
     }
   ): Promise<ResponseType> {
+    params = {
+      ...{ skipBufferingOnError: false, triggerMessage: false, throwError: false },
+      ...params,
+    };
     if (
       (chargingStation.inUnknownState() === true &&
         commandName === RequestCommand.BOOT_NOTIFICATION) ||
index f2429ae1746b739f3429963686ef3059bfd2f453..643b18831f2010c525844ccd5085f1a1a3a20ca8 100644 (file)
@@ -4,6 +4,7 @@ import type { WebSocket } from 'ws';
 
 import type { AbstractUIService } from './ui-services/AbstractUIService';
 import { UIServiceFactory } from './ui-services/UIServiceFactory';
+import { BaseError } from '../../exception';
 import {
   AuthenticationType,
   type ChargingStationData,
@@ -60,7 +61,7 @@ export abstract class AbstractUIServer {
   protected authenticate(req: IncomingMessage, next: (err?: Error) => void): void {
     if (this.isBasicAuthEnabled() === true) {
       if (this.isValidBasicAuth(req) === false) {
-        next(new Error('Unauthorized'));
+        next(new BaseError('Unauthorized'));
       }
       next();
     }
index 50d1a8b925e0b46862c22c41d77b3fc1f9e5d078..2a712a6008f61722ef21dddaac5acd2e3ba14d53 100644 (file)
@@ -4,6 +4,7 @@ import fs from 'node:fs';
 import path from 'node:path';
 
 import { Storage } from './Storage';
+import { BaseError } from '../../exception';
 import { FileType, type Statistics } from '../../types';
 import { AsyncLock, AsyncLockType, Constants, ErrorUtils, Utils } from '../../utils';
 
@@ -79,7 +80,7 @@ export class JsonFileStorage extends Storage {
 
   private checkPerformanceRecordsFile(): void {
     if (!this?.fd) {
-      throw new Error(
+      throw new BaseError(
         `${this.logPrefix} Performance records '${this.dbName}' file descriptor not found`
       );
     }
index aff757b5c343c58850bc36a9ac990ca37491f683..7dd505d3732f4a3316e4f40c1710d98ea7025afa 100644 (file)
@@ -3,6 +3,7 @@
 import { MongoClient } from 'mongodb';
 
 import { Storage } from './Storage';
+import { BaseError } from '../../exception';
 import { type Statistics, StorageType } from '../../types';
 import { Constants } from '../../utils';
 
@@ -55,14 +56,14 @@ export class MongoDBStorage extends Storage {
 
   private checkDBConnection() {
     if (!this?.client) {
-      throw new Error(
+      throw new BaseError(
         `${this.logPrefix} ${this.getDBNameFromStorageType(
           StorageType.MONGO_DB
         )} client initialization failed while trying to issue a request`
       );
     }
     if (!this.connected) {
-      throw new Error(
+      throw new BaseError(
         `${this.logPrefix} ${this.getDBNameFromStorageType(
           StorageType.MONGO_DB
         )} connection not opened while trying to issue a request`
index ba177524cfa9e86667072c6c5da4732c05250ee9..9df2ebca63b5ae35ce3968959c2f6f23e0b5ede5 100644 (file)
@@ -9,7 +9,7 @@ import {
   type Statistics,
   StorageType,
 } from '../../types';
-import { Utils, logger } from '../../utils';
+import { ErrorUtils, Utils, logger } from '../../utils';
 
 export abstract class Storage {
   protected readonly storageUri: URL;
@@ -25,8 +25,9 @@ export abstract class Storage {
     type: StorageType,
     error: Error,
     table?: string,
-    params: HandleErrorParams<EmptyObject> = { throwError: false }
+    params: HandleErrorParams<EmptyObject> = { throwError: false, consoleOut: false }
   ): void {
+    ErrorUtils.handleErrorParams(params, { throwError: false, consoleOut: false });
     const inTableOrCollectionStr =
       (!Utils.isNullOrUndefined(table) || !table) && ` in table or collection '${table}'`;
     logger.error(
index 09b58a15ffcda6b84363216bc2868eb84e6eb09f..1c109e1be628bfe4bf99d4499ae6227cd8a200d7 100644 (file)
@@ -4,6 +4,7 @@ import { JsonFileStorage } from './JsonFileStorage';
 import { MikroOrmStorage } from './MikroOrmStorage';
 import { MongoDBStorage } from './MongoDBStorage';
 import type { Storage } from './Storage';
+import { BaseError } from '../../exception';
 import { StorageType } from '../../types';
 
 export class StorageFactory {
@@ -26,7 +27,7 @@ export class StorageFactory {
       //   storageInstance = new MikroOrmStorage(connectionUri, logPrefix, type);
       //   break;
       default:
-        throw new Error(`${logPrefix} Unknown storage type: ${type}`);
+        throw new BaseError(`${logPrefix} Unknown storage type: ${type}`);
     }
     return storageInstance;
   }
index 76e3005f243236c7fcb65b6e6c204fee76418560..885edfdb3232650234fe7099c689feef485bd290 100644 (file)
@@ -1,4 +1,6 @@
-export type HandleErrorParams<T> = {
+import type { JsonType } from './JsonType';
+
+export type HandleErrorParams<T extends JsonType> = {
   throwError?: boolean;
   consoleOut?: boolean;
   errorResponse?: T;
index 214260701bb112351a4bcf6df6f627c4cdddf0e1..e4215f94c973e3be09c6283d9773e489067105d7 100644 (file)
@@ -413,10 +413,14 @@ export class Configuration {
       case 'EACCES':
         logMsg = `${fileType} file ${file} access denied:`;
         break;
+      case 'EPERM':
+        logMsg = `${fileType} file ${file} permission denied:`;
+        break;
       default:
         logMsg = `${fileType} file ${file} error:`;
     }
-    console.warn(`${chalk.green(prefix)}${chalk.yellow(`${logMsg} `)}`, error);
+    console.error(`${chalk.green(prefix)}${chalk.red(`${logMsg} `)}`, error);
+    throw error;
   }
 
   private static getDefaultPerformanceStorageUri(storageType: StorageType) {
index 7efbe514cab80209f24befc9b6eb565ca9083cb5..254bd9ecf4a821da81b0be440bbba16011b18511 100644 (file)
@@ -8,6 +8,7 @@ import type {
   FileType,
   HandleErrorParams,
   IncomingRequestCommand,
+  JsonType,
   RequestCommand,
 } from '../types';
 
@@ -35,6 +36,7 @@ export class ErrorUtils {
     logPrefix: string,
     params: HandleErrorParams<EmptyObject> = { throwError: true, consoleOut: false }
   ): void {
+    ErrorUtils.handleErrorParams(params);
     const prefix = Utils.isNotEmptyString(logPrefix) ? `${logPrefix} ` : '';
     let logMsg: string;
     switch (error.code) {
@@ -47,13 +49,24 @@ export class ErrorUtils {
       case 'EACCES':
         logMsg = `${fileType} file ${file} access denied:`;
         break;
+      case 'EPERM':
+        logMsg = `${fileType} file ${file} permission denied:`;
+        break;
       default:
         logMsg = `${fileType} file ${file} error:`;
     }
-    if (params?.consoleOut) {
-      console.warn(`${chalk.green(prefix)}${chalk.yellow(`${logMsg} `)}`, error);
-    } else {
-      logger.warn(`${prefix}${logMsg}`, error);
+    if (params?.consoleOut === true) {
+      if (params?.throwError) {
+        console.error(`${chalk.green(prefix)}${chalk.red(`${logMsg} `)}`, error);
+      } else {
+        console.warn(`${chalk.green(prefix)}${chalk.yellow(`${logMsg} `)}`, error);
+      }
+    } else if (params?.consoleOut === false) {
+      if (params?.throwError) {
+        logger.error(`${prefix}${logMsg}`, error);
+      } else {
+        logger.warn(`${prefix}${logMsg}`, error);
+      }
     }
     if (params?.throwError) {
       throw error;
@@ -64,11 +77,19 @@ export class ErrorUtils {
     chargingStation: ChargingStation,
     commandName: RequestCommand | IncomingRequestCommand,
     error: Error,
-    params: HandleErrorParams<EmptyObject> = { throwError: false }
+    params: HandleErrorParams<EmptyObject> = { throwError: false, consoleOut: false }
   ): void {
+    ErrorUtils.handleErrorParams(params, { throwError: false, consoleOut: false });
     logger.error(`${chargingStation.logPrefix()} Request command '${commandName}' error:`, error);
     if (params?.throwError === true) {
       throw error;
     }
   }
+
+  public static handleErrorParams<T extends JsonType>(
+    params: HandleErrorParams<T>,
+    defaultParams: HandleErrorParams<T> = { throwError: true, consoleOut: false }
+  ): HandleErrorParams<T> {
+    return { ...defaultParams, ...params };
+  }
 }