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 { Utils
, handleFileException
, logger
, watchJsonFile
} 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
34 * @param distribution -
35 * @param chargingStation -
36 * @param connectorId -
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) {
67 this.setIdTagsCache(file
, this.getIdTagsFromFile(file
));
69 return this.getIdTagsCache(file
);
72 public deleteIdTags(file
: string): boolean {
73 return this.deleteIdTagsCache(file
) && this.deleteIdTagsCacheIndexes(file
);
76 private getRandomIdTag(hashId
: string, file
: string): string {
77 const idTags
= this.getIdTags(file
);
78 const addressableKey
= this.getIdTagsCacheIndexesAddressableKey(file
, hashId
);
79 this.idTagsCachesAddressableIndexes
.set(
81 Math.floor(Utils
.secureRandom() * idTags
.length
)
83 return idTags
[this.idTagsCachesAddressableIndexes
.get(addressableKey
)];
86 private getRoundRobinIdTag(hashId
: string, file
: string): string {
87 const idTags
= this.getIdTags(file
);
88 const addressableKey
= this.getIdTagsCacheIndexesAddressableKey(file
, hashId
);
89 const idTagIndex
= this.idTagsCachesAddressableIndexes
.get(addressableKey
) ?? 0;
90 const idTag
= idTags
[idTagIndex
];
91 this.idTagsCachesAddressableIndexes
.set(
93 idTagIndex
=== idTags
.length
- 1 ? 0 : idTagIndex
+ 1
98 private getConnectorAffinityIdTag(chargingStation
: ChargingStation
, connectorId
: number): string {
99 const file
= ChargingStationUtils
.getIdTagsFile(chargingStation
.stationInfo
);
100 const idTags
= this.getIdTags(file
);
101 const addressableKey
= this.getIdTagsCacheIndexesAddressableKey(
103 chargingStation
.stationInfo
.hashId
105 this.idTagsCachesAddressableIndexes
.set(
107 (chargingStation
.index
- 1 + (connectorId
- 1)) % idTags
.length
109 return idTags
[this.idTagsCachesAddressableIndexes
.get(addressableKey
)];
112 private hasIdTagsCache(file
: string): boolean {
113 return this.idTagsCaches
.has(file
);
116 private setIdTagsCache(file
: string, idTags
: string[]) {
117 return this.idTagsCaches
.set(file
, {
119 idTagsFileWatcher
: watchJsonFile(
121 FileType
.Authorization
,
122 this.logPrefix(file
),
124 (event
, filename
) => {
125 if (Utils
.isNotEmptyString(filename
) && event
=== 'change') {
128 `${this.logPrefix(file)} ${FileType.Authorization} file have changed, reload`
130 this.deleteIdTagsCache(file
);
131 this.deleteIdTagsCacheIndexes(file
);
135 FileType
.Authorization
,
136 error
as NodeJS
.ErrnoException
,
137 this.logPrefix(file
),
149 private getIdTagsCache(file
: string): string[] | undefined {
150 return this.idTagsCaches
.get(file
)?.idTags
;
153 private deleteIdTagsCache(file
: string): boolean {
154 this.idTagsCaches
.get(file
)?.idTagsFileWatcher
?.close();
155 return this.idTagsCaches
.delete(file
);
158 private deleteIdTagsCacheIndexes(file
: string): boolean {
159 let deleted
: boolean[];
160 for (const [key
] of this.idTagsCachesAddressableIndexes
) {
161 if (key
.startsWith(file
)) {
162 deleted
.push(this.idTagsCachesAddressableIndexes
.delete(key
));
165 return !deleted
.some((value
) => value
=== false);
168 private getIdTagsCacheIndexesAddressableKey(prefix
: string, uid
: string): string {
169 return `${prefix}${uid}`;
172 private getIdTagsFromFile(file
: string): string[] {
173 if (Utils
.isNotEmptyString(file
)) {
175 return JSON
.parse(fs
.readFileSync(file
, 'utf8')) as string[];
179 FileType
.Authorization
,
180 error
as NodeJS
.ErrnoException
,
188 private logPrefix
= (file
: string): string => {
189 return Utils
.logPrefix(` Id tags cache for id tags file '${file}' |`);