refactor: remove unneeded intermediate variable in id tags cache code
[e-mobility-charging-stations-simulator.git] / src / charging-station / IdTagsCache.ts
1 import fs from 'node:fs';
2
3 import { type ChargingStation, ChargingStationUtils } from './internal';
4 import { FileType, IdTagDistribution } from '../types';
5 import { FileUtils, Utils, logger } from '../utils';
6
7 type IdTagsCacheValueType = {
8 idTags: string[];
9 idTagsFileWatcher: fs.FSWatcher | undefined;
10 };
11
12 export class IdTagsCache {
13 private static instance: IdTagsCache | null = null;
14 private readonly idTagsCaches: Map<string, IdTagsCacheValueType>;
15 private readonly idTagsCachesAddressableIndexes: Map<string, number>;
16
17 private constructor() {
18 this.idTagsCaches = new Map<string, IdTagsCacheValueType>();
19 this.idTagsCachesAddressableIndexes = new Map<string, number>();
20 }
21
22 public static getInstance(): IdTagsCache {
23 if (IdTagsCache.instance === null) {
24 IdTagsCache.instance = new IdTagsCache();
25 }
26 return IdTagsCache.instance;
27 }
28
29 public getIdTag(
30 distribution: IdTagDistribution,
31 chargingStation: ChargingStation,
32 connectorId: number
33 ): string {
34 const hashId = chargingStation.stationInfo.hashId;
35 const idTagsFile = ChargingStationUtils.getIdTagsFile(chargingStation.stationInfo);
36 switch (distribution) {
37 case IdTagDistribution.RANDOM:
38 return this.getRandomIdTag(hashId, idTagsFile);
39 case IdTagDistribution.ROUND_ROBIN:
40 return this.getRoundRobinIdTag(hashId, idTagsFile);
41 case IdTagDistribution.CONNECTOR_AFFINITY:
42 return this.getConnectorAffinityIdTag(chargingStation, connectorId);
43 default:
44 return this.getRoundRobinIdTag(hashId, idTagsFile);
45 }
46 }
47
48 public getIdTags(file: string): string[] | undefined {
49 if (this.hasIdTagsCache(file) === false) {
50 this.setIdTagsCache(file, this.getIdTagsFromFile(file));
51 }
52 return this.getIdTagsCache(file);
53 }
54
55 public deleteIdTags(file: string): boolean {
56 return this.deleteIdTagsCache(file);
57 }
58
59 private getRandomIdTag(hashId: string, file: string): string {
60 const idTags = this.getIdTags(file);
61 const addressableKey = this.getIdTagsCacheIndexesAddressableKey(file, hashId);
62 this.idTagsCachesAddressableIndexes.set(
63 addressableKey,
64 Math.floor(Utils.secureRandom() * idTags.length)
65 );
66 return idTags[this.idTagsCachesAddressableIndexes.get(addressableKey)];
67 }
68
69 private getRoundRobinIdTag(hashId: string, file: string): string {
70 const idTags = this.getIdTags(file);
71 const addressableKey = this.getIdTagsCacheIndexesAddressableKey(file, hashId);
72 const idTagIndex = this.idTagsCachesAddressableIndexes.get(addressableKey) ?? 0;
73 const idTag = idTags[idTagIndex];
74 this.idTagsCachesAddressableIndexes.set(
75 addressableKey,
76 idTagIndex === idTags.length - 1 ? 0 : idTagIndex + 1
77 );
78 return idTag;
79 }
80
81 private getConnectorAffinityIdTag(chargingStation: ChargingStation, connectorId: number): string {
82 const file = ChargingStationUtils.getIdTagsFile(chargingStation.stationInfo);
83 const idTags = this.getIdTags(file);
84 const addressableKey = this.getIdTagsCacheIndexesAddressableKey(
85 file,
86 chargingStation.stationInfo.hashId
87 );
88 this.idTagsCachesAddressableIndexes.set(
89 addressableKey,
90 (chargingStation.index - 1 + (connectorId - 1)) % idTags.length
91 );
92 return idTags[this.idTagsCachesAddressableIndexes.get(addressableKey)];
93 }
94
95 private hasIdTagsCache(file: string): boolean {
96 return this.idTagsCaches.has(file);
97 }
98
99 private setIdTagsCache(file: string, idTags: string[]) {
100 return this.idTagsCaches.set(file, {
101 idTags,
102 idTagsFileWatcher: FileUtils.watchJsonFile(
103 file,
104 FileType.Authorization,
105 this.logPrefix(file),
106 undefined,
107 (event, filename) => {
108 if (Utils.isNotEmptyString(filename) && event === 'change') {
109 try {
110 logger.debug(
111 `${this.logPrefix(file)} ${FileType.Authorization} file have changed, reload`
112 );
113 this.deleteIdTagsCache(file);
114 this.deleteIdTagsCacheIndexes(file);
115 } catch (error) {
116 FileUtils.handleFileException(
117 file,
118 FileType.Authorization,
119 error as NodeJS.ErrnoException,
120 this.logPrefix(file),
121 {
122 throwError: false,
123 }
124 );
125 }
126 }
127 }
128 ),
129 });
130 }
131
132 private getIdTagsCache(file: string): string[] | undefined {
133 return this.idTagsCaches.get(file)?.idTags;
134 }
135
136 private deleteIdTagsCache(file: string): boolean {
137 this.idTagsCaches.get(file)?.idTagsFileWatcher?.close();
138 return this.idTagsCaches.delete(file);
139 }
140
141 private deleteIdTagsCacheIndexes(file: string): void {
142 for (const [key] of this.idTagsCachesAddressableIndexes) {
143 if (key.startsWith(file)) {
144 this.idTagsCachesAddressableIndexes.delete(key);
145 }
146 }
147 }
148
149 private getIdTagsCacheIndexesAddressableKey(prefix: string, uid: string): string {
150 return `${prefix}${uid}`;
151 }
152
153 private getIdTagsFromFile(file: string): string[] {
154 let idTags: string[] = [];
155 if (file) {
156 try {
157 // Load id tags file
158 idTags = JSON.parse(fs.readFileSync(file, 'utf8')) as string[];
159 } catch (error) {
160 FileUtils.handleFileException(
161 file,
162 FileType.Authorization,
163 error as NodeJS.ErrnoException,
164 this.logPrefix(file)
165 );
166 }
167 } else {
168 logger.info(`${this.logPrefix(file)} No id tags file given`);
169 }
170 return idTags;
171 }
172
173 private logPrefix = (file: string): string => {
174 return Utils.logPrefix(` Id tags cache for id tags file '${file}' |`);
175 };
176 }