1 import fs from
'node:fs';
3 import type { ChargingStation
} from
'./ChargingStation';
4 import { ChargingStationUtils
} from
'./ChargingStationUtils';
5 import { FileType
, IdTagDistribution
} from
'../types';
6 import { ErrorUtils
, FileUtils
, Utils
, logger
} from
'../utils';
8 type IdTagsCacheValueType
= {
10 idTagsFileWatcher
: fs
.FSWatcher
| undefined;
13 export class IdTagsCache
{
14 private static instance
: IdTagsCache
| null = null;
15 private readonly idTagsCaches
: Map
<string, IdTagsCacheValueType
>;
16 private readonly idTagsCachesAddressableIndexes
: Map
<string, number>;
18 private constructor() {
19 this.idTagsCaches
= new Map
<string, IdTagsCacheValueType
>();
20 this.idTagsCachesAddressableIndexes
= new Map
<string, number>();
23 public static getInstance(): IdTagsCache
{
24 if (IdTagsCache
.instance
=== null) {
25 IdTagsCache
.instance
= new IdTagsCache();
27 return IdTagsCache
.instance
;
31 * Get one idtag from the cache given the distribution
32 * Must be called after checking the cache is not an empty array
35 * @param chargingStation
40 distribution
: IdTagDistribution
,
41 chargingStation
: ChargingStation
,
44 const hashId
= chargingStation
.stationInfo
.hashId
;
45 const idTagsFile
= ChargingStationUtils
.getIdTagsFile(chargingStation
.stationInfo
);
46 switch (distribution
) {
47 case IdTagDistribution
.RANDOM
:
48 return this.getRandomIdTag(hashId
, idTagsFile
);
49 case IdTagDistribution
.ROUND_ROBIN
:
50 return this.getRoundRobinIdTag(hashId
, idTagsFile
);
51 case IdTagDistribution
.CONNECTOR_AFFINITY
:
52 return this.getConnectorAffinityIdTag(chargingStation
, connectorId
);
54 return this.getRoundRobinIdTag(hashId
, idTagsFile
);
59 * Get all idtags from the cache
60 * Must be called after checking the cache is not an empty array
65 public getIdTags(file
: string): string[] | undefined {
66 if (this.hasIdTagsCache(file
) === false) {
68 Utils
.isNotEmptyString(file
) ? file
: 'empty',
69 this.getIdTagsFromFile(file
)
72 return this.getIdTagsCache(file
);
75 public deleteIdTags(file
: string): boolean {
76 return this.deleteIdTagsCache(file
);
79 private getRandomIdTag(hashId
: string, file
: string): string {
80 const idTags
= this.getIdTags(file
);
81 const addressableKey
= this.getIdTagsCacheIndexesAddressableKey(file
, hashId
);
82 this.idTagsCachesAddressableIndexes
.set(
84 Math.floor(Utils
.secureRandom() * idTags
.length
)
86 return idTags
[this.idTagsCachesAddressableIndexes
.get(addressableKey
)];
89 private getRoundRobinIdTag(hashId
: string, file
: string): string {
90 const idTags
= this.getIdTags(file
);
91 const addressableKey
= this.getIdTagsCacheIndexesAddressableKey(file
, hashId
);
92 const idTagIndex
= this.idTagsCachesAddressableIndexes
.get(addressableKey
) ?? 0;
93 const idTag
= idTags
[idTagIndex
];
94 this.idTagsCachesAddressableIndexes
.set(
96 idTagIndex
=== idTags
.length
- 1 ? 0 : idTagIndex
+ 1
101 private getConnectorAffinityIdTag(chargingStation
: ChargingStation
, connectorId
: number): string {
102 const file
= ChargingStationUtils
.getIdTagsFile(chargingStation
.stationInfo
);
103 const idTags
= this.getIdTags(file
);
104 const addressableKey
= this.getIdTagsCacheIndexesAddressableKey(
106 chargingStation
.stationInfo
.hashId
108 this.idTagsCachesAddressableIndexes
.set(
110 (chargingStation
.index
- 1 + (connectorId
- 1)) % idTags
.length
112 return idTags
[this.idTagsCachesAddressableIndexes
.get(addressableKey
)];
115 private hasIdTagsCache(file
: string): boolean {
116 return this.idTagsCaches
.has(file
);
119 private setIdTagsCache(file
: string, idTags
: string[]) {
120 return this.idTagsCaches
.set(file
, {
122 idTagsFileWatcher
: FileUtils
.watchJsonFile(
124 FileType
.Authorization
,
125 this.logPrefix(file
),
127 (event
, filename
) => {
128 if (Utils
.isNotEmptyString(filename
) && event
=== 'change') {
131 `${this.logPrefix(file)} ${FileType.Authorization} file have changed, reload`
133 this.deleteIdTagsCache(file
);
134 this.deleteIdTagsCacheIndexes(file
);
136 ErrorUtils
.handleFileException(
138 FileType
.Authorization
,
139 error
as NodeJS
.ErrnoException
,
140 this.logPrefix(file
),
152 private getIdTagsCache(file
: string): string[] | undefined {
153 return this.idTagsCaches
.get(file
)?.idTags
;
156 private deleteIdTagsCache(file
: string): boolean {
157 this.idTagsCaches
.get(file
)?.idTagsFileWatcher
?.close();
158 return this.idTagsCaches
.delete(file
);
161 private deleteIdTagsCacheIndexes(file
: string): void {
162 for (const [key
] of this.idTagsCachesAddressableIndexes
) {
163 if (key
.startsWith(file
)) {
164 this.idTagsCachesAddressableIndexes
.delete(key
);
169 private getIdTagsCacheIndexesAddressableKey(prefix
: string, uid
: string): string {
170 return `${prefix}${uid}`;
173 private getIdTagsFromFile(file
: string): string[] {
174 if (Utils
.isNotEmptyString(file
)) {
176 return JSON
.parse(fs
.readFileSync(file
, 'utf8')) as string[];
178 ErrorUtils
.handleFileException(
180 FileType
.Authorization
,
181 error
as NodeJS
.ErrnoException
,
186 logger
.info(`${this.logPrefix(file)} No id tags file given in configuration`);
190 private logPrefix
= (file
: string): string => {
191 return Utils
.logPrefix(` Id tags cache for id tags file '${file}' |`);