refactor: cleanup imports
[e-mobility-charging-stations-simulator.git] / src / charging-station / IdTagsCache.ts
CommitLineData
d972af76 1import { type FSWatcher, readFileSync } from 'node:fs';
f911a4af 2
4c3c0d59
JB
3import type { ChargingStation } from './ChargingStation';
4import { ChargingStationUtils } from './ChargingStationUtils';
f911a4af 5import { FileType, IdTagDistribution } from '../types';
fa5995d6 6import { Utils, handleFileException, logger, watchJsonFile } from '../utils';
f911a4af
JB
7
8type IdTagsCacheValueType = {
9 idTags: string[];
d972af76 10 idTagsFileWatcher: FSWatcher | undefined;
f911a4af
JB
11};
12
13export class IdTagsCache {
14 private static instance: IdTagsCache | null = null;
15 private readonly idTagsCaches: Map<string, IdTagsCacheValueType>;
16 private readonly idTagsCachesAddressableIndexes: Map<string, number>;
17
18 private constructor() {
19 this.idTagsCaches = new Map<string, IdTagsCacheValueType>();
20 this.idTagsCachesAddressableIndexes = new Map<string, number>();
21 }
22
23 public static getInstance(): IdTagsCache {
24 if (IdTagsCache.instance === null) {
25 IdTagsCache.instance = new IdTagsCache();
26 }
27 return IdTagsCache.instance;
28 }
29
7b5dbe91 30 /**
361c98f5 31 * Gets one idtag from the cache given the distribution
7b5dbe91
JB
32 * Must be called after checking the cache is not an empty array
33 *
4c8782ee
JB
34 * @param distribution -
35 * @param chargingStation -
36 * @param connectorId -
7b5dbe91
JB
37 * @returns
38 */
f911a4af
JB
39 public getIdTag(
40 distribution: IdTagDistribution,
41 chargingStation: ChargingStation,
42 connectorId: number
43 ): string {
44 const hashId = chargingStation.stationInfo.hashId;
e302df1d 45 const idTagsFile = ChargingStationUtils.getIdTagsFile(chargingStation.stationInfo);
f911a4af
JB
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);
53 default:
54 return this.getRoundRobinIdTag(hashId, idTagsFile);
55 }
56 }
57
7b5dbe91 58 /**
361c98f5 59 * Gets all idtags from the cache
7b5dbe91
JB
60 * Must be called after checking the cache is not an empty array
61 *
4c8782ee 62 * @param file -
7b5dbe91
JB
63 * @returns
64 */
f911a4af
JB
65 public getIdTags(file: string): string[] | undefined {
66 if (this.hasIdTagsCache(file) === false) {
6082281f 67 this.setIdTagsCache(file, this.getIdTagsFromFile(file));
f911a4af
JB
68 }
69 return this.getIdTagsCache(file);
70 }
71
72 public deleteIdTags(file: string): boolean {
26a17d93 73 return this.deleteIdTagsCache(file) && this.deleteIdTagsCacheIndexes(file);
f911a4af
JB
74 }
75
76 private getRandomIdTag(hashId: string, file: string): string {
77 const idTags = this.getIdTags(file);
32e8c8a5 78 const addressableKey = this.getIdTagsCacheIndexesAddressableKey(file, hashId);
f911a4af
JB
79 this.idTagsCachesAddressableIndexes.set(
80 addressableKey,
81 Math.floor(Utils.secureRandom() * idTags.length)
82 );
83 return idTags[this.idTagsCachesAddressableIndexes.get(addressableKey)];
84 }
85
86 private getRoundRobinIdTag(hashId: string, file: string): string {
87 const idTags = this.getIdTags(file);
32e8c8a5 88 const addressableKey = this.getIdTagsCacheIndexesAddressableKey(file, hashId);
f911a4af
JB
89 const idTagIndex = this.idTagsCachesAddressableIndexes.get(addressableKey) ?? 0;
90 const idTag = idTags[idTagIndex];
91 this.idTagsCachesAddressableIndexes.set(
92 addressableKey,
93 idTagIndex === idTags.length - 1 ? 0 : idTagIndex + 1
94 );
95 return idTag;
96 }
97
98 private getConnectorAffinityIdTag(chargingStation: ChargingStation, connectorId: number): string {
e302df1d 99 const file = ChargingStationUtils.getIdTagsFile(chargingStation.stationInfo);
f911a4af 100 const idTags = this.getIdTags(file);
728e01f0
JB
101 const addressableKey = this.getIdTagsCacheIndexesAddressableKey(
102 file,
103 chargingStation.stationInfo.hashId
104 );
f911a4af
JB
105 this.idTagsCachesAddressableIndexes.set(
106 addressableKey,
107 (chargingStation.index - 1 + (connectorId - 1)) % idTags.length
108 );
109 return idTags[this.idTagsCachesAddressableIndexes.get(addressableKey)];
110 }
111
112 private hasIdTagsCache(file: string): boolean {
113 return this.idTagsCaches.has(file);
114 }
115
116 private setIdTagsCache(file: string, idTags: string[]) {
117 return this.idTagsCaches.set(file, {
118 idTags,
fa5995d6 119 idTagsFileWatcher: watchJsonFile(
f911a4af
JB
120 file,
121 FileType.Authorization,
122 this.logPrefix(file),
123 undefined,
124 (event, filename) => {
125 if (Utils.isNotEmptyString(filename) && event === 'change') {
126 try {
127 logger.debug(
128 `${this.logPrefix(file)} ${FileType.Authorization} file have changed, reload`
129 );
130 this.deleteIdTagsCache(file);
131 this.deleteIdTagsCacheIndexes(file);
132 } catch (error) {
fa5995d6 133 handleFileException(
f911a4af
JB
134 file,
135 FileType.Authorization,
136 error as NodeJS.ErrnoException,
137 this.logPrefix(file),
138 {
139 throwError: false,
140 }
141 );
142 }
143 }
144 }
145 ),
146 });
147 }
148
149 private getIdTagsCache(file: string): string[] | undefined {
150 return this.idTagsCaches.get(file)?.idTags;
151 }
152
153 private deleteIdTagsCache(file: string): boolean {
154 this.idTagsCaches.get(file)?.idTagsFileWatcher?.close();
155 return this.idTagsCaches.delete(file);
156 }
157
26a17d93
JB
158 private deleteIdTagsCacheIndexes(file: string): boolean {
159 let deleted: boolean[];
f911a4af
JB
160 for (const [key] of this.idTagsCachesAddressableIndexes) {
161 if (key.startsWith(file)) {
26a17d93 162 deleted.push(this.idTagsCachesAddressableIndexes.delete(key));
f911a4af
JB
163 }
164 }
26a17d93 165 return !deleted.some((value) => value === false);
f911a4af
JB
166 }
167
32e8c8a5 168 private getIdTagsCacheIndexesAddressableKey(prefix: string, uid: string): string {
7af183e7
JB
169 return `${prefix}${uid}`;
170 }
171
f911a4af 172 private getIdTagsFromFile(file: string): string[] {
7b5dbe91 173 if (Utils.isNotEmptyString(file)) {
f911a4af 174 try {
d972af76 175 return JSON.parse(readFileSync(file, 'utf8')) as string[];
f911a4af 176 } catch (error) {
fa5995d6 177 handleFileException(
f911a4af
JB
178 file,
179 FileType.Authorization,
180 error as NodeJS.ErrnoException,
181 this.logPrefix(file)
182 );
183 }
f911a4af 184 }
7b5dbe91 185 return [];
f911a4af
JB
186 }
187
188 private logPrefix = (file: string): string => {
189 return Utils.logPrefix(` Id tags cache for id tags file '${file}' |`);
190 };
191}