1 import { type FSWatcher
, readFileSync
} from
'node:fs';
3 import type { ChargingStation
} from
'./ChargingStation';
4 import { getIdTagsFile
} from
'./ChargingStationUtils';
5 import { FileType
, IdTagDistribution
} from
'../types';
15 interface IdTagsCacheValueType
{
17 idTagsFileWatcher
: FSWatcher
| undefined;
20 export class IdTagsCache
{
21 private static instance
: IdTagsCache
| null = null;
22 private readonly idTagsCaches
: Map
<string, IdTagsCacheValueType
>;
23 private readonly idTagsCachesAddressableIndexes
: Map
<string, number>;
25 private constructor() {
26 this.idTagsCaches
= new Map
<string, IdTagsCacheValueType
>();
27 this.idTagsCachesAddressableIndexes
= new Map
<string, number>();
30 public static getInstance(): IdTagsCache
{
31 if (IdTagsCache
.instance
=== null) {
32 IdTagsCache
.instance
= new IdTagsCache();
34 return IdTagsCache
.instance
;
38 * Gets one idtag from the cache given the distribution
39 * Must be called after checking the cache is not an empty array
41 * @param distribution -
42 * @param chargingStation -
43 * @param connectorId -
47 distribution
: IdTagDistribution
,
48 chargingStation
: ChargingStation
,
51 const hashId
= chargingStation
.stationInfo
.hashId
;
52 const idTagsFile
= getIdTagsFile(chargingStation
.stationInfo
)!;
53 switch (distribution
) {
54 case IdTagDistribution
.RANDOM
:
55 return this.getRandomIdTag(hashId
, idTagsFile
);
56 case IdTagDistribution
.ROUND_ROBIN
:
57 return this.getRoundRobinIdTag(hashId
, idTagsFile
);
58 case IdTagDistribution
.CONNECTOR_AFFINITY
:
59 return this.getConnectorAffinityIdTag(chargingStation
, connectorId
);
61 return this.getRoundRobinIdTag(hashId
, idTagsFile
);
66 * Gets all idtags from the cache
67 * Must be called after checking the cache is not an empty array
72 public getIdTags(file
: string): string[] | undefined {
73 if (this.hasIdTagsCache(file
) === false) {
74 this.setIdTagsCache(file
, this.getIdTagsFromFile(file
));
76 return this.getIdTagsCache(file
);
79 public deleteIdTags(file
: string): boolean {
80 return this.deleteIdTagsCache(file
) && this.deleteIdTagsCacheIndexes(file
);
83 private getRandomIdTag(hashId
: string, file
: string): string {
84 const idTags
= this.getIdTags(file
)!;
85 const addressableKey
= this.getIdTagsCacheIndexesAddressableKey(file
, hashId
);
86 this.idTagsCachesAddressableIndexes
.set(
88 Math.floor(secureRandom() * idTags
.length
),
90 return idTags
[this.idTagsCachesAddressableIndexes
.get(addressableKey
)!];
93 private getRoundRobinIdTag(hashId
: string, file
: string): string {
94 const idTags
= this.getIdTags(file
)!;
95 const addressableKey
= this.getIdTagsCacheIndexesAddressableKey(file
, hashId
);
96 const idTagIndex
= this.idTagsCachesAddressableIndexes
.get(addressableKey
) ?? 0;
97 const idTag
= idTags
[idTagIndex
];
98 this.idTagsCachesAddressableIndexes
.set(
100 idTagIndex
=== idTags
.length
- 1 ? 0 : idTagIndex
+ 1,
105 private getConnectorAffinityIdTag(chargingStation
: ChargingStation
, connectorId
: number): string {
106 const file
= getIdTagsFile(chargingStation
.stationInfo
)!;
107 const idTags
= this.getIdTags(file
)!;
108 const addressableKey
= this.getIdTagsCacheIndexesAddressableKey(
110 chargingStation
.stationInfo
.hashId
,
112 this.idTagsCachesAddressableIndexes
.set(
114 (chargingStation
.index
- 1 + (connectorId
- 1)) % idTags
.length
,
116 return idTags
[this.idTagsCachesAddressableIndexes
.get(addressableKey
)!];
119 private hasIdTagsCache(file
: string): boolean {
120 return this.idTagsCaches
.has(file
);
123 private setIdTagsCache(file
: string, idTags
: string[]) {
124 return this.idTagsCaches
.set(file
, {
126 idTagsFileWatcher
: watchJsonFile(
128 FileType
.Authorization
,
129 this.logPrefix(file
),
131 (event
, filename
) => {
132 if (isNotEmptyString(filename
) && event
=== 'change') {
135 `${this.logPrefix(file)} ${FileType.Authorization} file have changed, reload`,
137 this.deleteIdTagsCache(file
);
138 this.deleteIdTagsCacheIndexes(file
);
142 FileType
.Authorization
,
143 error
as NodeJS
.ErrnoException
,
144 this.logPrefix(file
),
156 private getIdTagsCache(file
: string): string[] | undefined {
157 return this.idTagsCaches
.get(file
)?.idTags
;
160 private deleteIdTagsCache(file
: string): boolean {
161 this.idTagsCaches
.get(file
)?.idTagsFileWatcher
?.close();
162 return this.idTagsCaches
.delete(file
);
165 private deleteIdTagsCacheIndexes(file
: string): boolean {
166 const deleted
: boolean[] = [];
167 for (const [key
] of this.idTagsCachesAddressableIndexes
) {
168 if (key
.startsWith(file
)) {
169 deleted
.push(this.idTagsCachesAddressableIndexes
.delete(key
));
172 return !deleted
.some((value
) => value
=== false);
175 private getIdTagsCacheIndexesAddressableKey(prefix
: string, uid
: string): string {
176 return `${prefix}${uid}`;
179 private getIdTagsFromFile(file
: string): string[] {
180 if (isNotEmptyString(file
)) {
182 return JSON
.parse(readFileSync(file
, 'utf8')) as string[];
186 FileType
.Authorization
,
187 error
as NodeJS
.ErrnoException
,
188 this.logPrefix(file
),
195 private logPrefix
= (file
: string): string => {
196 return logPrefix(` Id tags cache for id tags file '${file}' |`);