import fs from 'node:fs';
-import { FileType } from '../types/FileType';
-import FileUtils from '../utils/FileUtils';
-import logger from '../utils/Logger';
-import Utils from '../utils/Utils';
+import { type ChargingStation, ChargingStationUtils } from './internal';
+import { FileType, IdTagDistribution } from '../types';
+import { FileUtils, Utils, logger } from '../utils';
-export default class AuthorizedTagsCache {
+type TagsCacheValueType = {
+ tags: string[];
+ tagsFileWatcher: fs.FSWatcher | undefined;
+};
+
+export class AuthorizedTagsCache {
private static instance: AuthorizedTagsCache | null = null;
- private readonly tagsCaches: Map<string, string[]>;
- private readonly FSWatchers: Map<string, fs.FSWatcher | undefined>;
+ private readonly tagsCaches: Map<string, TagsCacheValueType>;
+ private readonly tagsCachesAddressableIndexes: Map<string, number>;
private constructor() {
- this.tagsCaches = new Map<string, string[]>();
- this.FSWatchers = new Map<string, fs.FSWatcher | undefined>();
+ this.tagsCaches = new Map<string, TagsCacheValueType>();
+ this.tagsCachesAddressableIndexes = new Map<string, number>();
}
public static getInstance(): AuthorizedTagsCache {
return AuthorizedTagsCache.instance;
}
+ public getIdTag(
+ distribution: IdTagDistribution,
+ chargingStation: ChargingStation,
+ connectorId: number
+ ): string {
+ const hashId = chargingStation.stationInfo.hashId;
+ const authorizationFile = ChargingStationUtils.getAuthorizationFile(
+ chargingStation.stationInfo
+ );
+ switch (distribution) {
+ case IdTagDistribution.RANDOM:
+ return this.getRandomIdTag(hashId, authorizationFile);
+ case IdTagDistribution.ROUND_ROBIN:
+ return this.getRoundRobinIdTag(hashId, authorizationFile);
+ case IdTagDistribution.CONNECTOR_AFFINITY:
+ return this.getConnectorAffinityIdTag(chargingStation, connectorId);
+ default:
+ return this.getRoundRobinIdTag(hashId, authorizationFile);
+ }
+ }
+
public getAuthorizedTags(file: string): string[] | undefined {
if (this.hasTags(file) === false) {
this.setTags(file, this.getAuthorizedTagsFromFile(file));
- // Monitor authorization file
- this.FSWatchers.has(file) === false &&
- this.FSWatchers.set(
- file,
- FileUtils.watchJsonFile(
- this.logPrefix(file),
- FileType.Authorization,
- file,
- null,
- (event, filename) => {
- if (!Utils.isEmptyString(filename) && event === 'change') {
- try {
- logger.debug(
- `${this.logPrefix(file)} ${FileType.Authorization} file have changed, reload`
- );
- this.deleteTags(file);
- this.deleteFSWatcher(file);
- } catch (error) {
- FileUtils.handleFileException(
- this.logPrefix(file),
- FileType.Authorization,
- file,
- error as NodeJS.ErrnoException,
- {
- throwError: false,
- }
- );
- }
- }
- }
- )
- );
}
return this.getTags(file);
}
return this.deleteTags(file);
}
+ private getRandomIdTag(hashId: string, file: string): string {
+ const tags = this.getAuthorizedTags(file);
+ const addressableKey = file + hashId;
+ this.tagsCachesAddressableIndexes.set(
+ addressableKey,
+ Math.floor(Utils.secureRandom() * tags.length)
+ );
+ return tags[this.tagsCachesAddressableIndexes.get(addressableKey)];
+ }
+
+ private getRoundRobinIdTag(hashId: string, file: string): string {
+ const tags = this.getAuthorizedTags(file);
+ const addressableKey = file + hashId;
+ const idTagIndex = this.tagsCachesAddressableIndexes.get(addressableKey) ?? 0;
+ const idTag = tags[idTagIndex];
+ this.tagsCachesAddressableIndexes.set(
+ addressableKey,
+ idTagIndex === tags.length - 1 ? 0 : idTagIndex + 1
+ );
+ return idTag;
+ }
+
+ private getConnectorAffinityIdTag(chargingStation: ChargingStation, connectorId: number): string {
+ const file = ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo);
+ const tags = this.getAuthorizedTags(file);
+ const hashId = chargingStation.stationInfo.hashId;
+ const addressableKey = file + hashId;
+ this.tagsCachesAddressableIndexes.set(
+ addressableKey,
+ (chargingStation.index - 1 + (connectorId - 1)) % tags.length
+ );
+ return tags[this.tagsCachesAddressableIndexes.get(addressableKey)];
+ }
+
private hasTags(file: string): boolean {
return this.tagsCaches.has(file);
}
private setTags(file: string, tags: string[]) {
- return this.tagsCaches.set(file, tags);
+ return this.tagsCaches.set(file, {
+ tags,
+ tagsFileWatcher: FileUtils.watchJsonFile(
+ file,
+ FileType.Authorization,
+ this.logPrefix(file),
+ undefined,
+ (event, filename) => {
+ if (Utils.isNotEmptyString(filename) && event === 'change') {
+ try {
+ logger.debug(
+ `${this.logPrefix(file)} ${FileType.Authorization} file have changed, reload`
+ );
+ this.deleteTags(file);
+ this.deleteTagsIndexes(file);
+ } catch (error) {
+ FileUtils.handleFileException(
+ file,
+ FileType.Authorization,
+ error as NodeJS.ErrnoException,
+ this.logPrefix(file),
+ {
+ throwError: false,
+ }
+ );
+ }
+ }
+ }
+ ),
+ });
}
private getTags(file: string): string[] | undefined {
- return this.tagsCaches.get(file);
+ return this.tagsCaches.get(file)?.tags;
}
private deleteTags(file: string): boolean {
+ this.tagsCaches.get(file)?.tagsFileWatcher?.close();
return this.tagsCaches.delete(file);
}
- private deleteFSWatcher(file: string): boolean {
- this.FSWatchers.get(file)?.close();
- return this.FSWatchers.delete(file);
+ private deleteTagsIndexes(file: string): void {
+ for (const [key] of this.tagsCachesAddressableIndexes) {
+ if (key.startsWith(file)) {
+ this.tagsCachesAddressableIndexes.delete(key);
+ }
+ }
}
private getAuthorizedTagsFromFile(file: string): string[] {
authorizedTags = JSON.parse(fs.readFileSync(file, 'utf8')) as string[];
} catch (error) {
FileUtils.handleFileException(
- this.logPrefix(file),
- FileType.Authorization,
file,
- error as NodeJS.ErrnoException
+ FileType.Authorization,
+ error as NodeJS.ErrnoException,
+ this.logPrefix(file)
);
}
} else {
return authorizedTags;
}
- private logPrefix(file: string): string {
+ private logPrefix = (file: string): string => {
return Utils.logPrefix(` Authorized tags cache for authorization file '${file}' |`);
- }
+ };
}