Commit | Line | Data |
---|---|---|
130783a7 | 1 | import fs from 'node:fs'; |
8114d10e | 2 | |
aaf2bf9c JB |
3 | import { type ChargingStation, ChargingStationUtils } from './internal'; |
4 | import { FileType, IdTagDistribution } from '../types'; | |
60a74391 | 5 | import { FileUtils, Utils, logger } from '../utils'; |
9d7484a4 | 6 | |
d6a62ad9 JB |
7 | type TagsCacheValueType = { |
8 | tags: string[]; | |
9 | tagsFileWatcher: fs.FSWatcher | undefined; | |
10 | }; | |
11 | ||
268a74bb | 12 | export class AuthorizedTagsCache { |
9d7484a4 | 13 | private static instance: AuthorizedTagsCache | null = null; |
d6a62ad9 | 14 | private readonly tagsCaches: Map<string, TagsCacheValueType>; |
aaf2bf9c | 15 | private readonly tagsCachesAddressableIndexes: Map<string, number>; |
9d7484a4 JB |
16 | |
17 | private constructor() { | |
d6a62ad9 | 18 | this.tagsCaches = new Map<string, TagsCacheValueType>(); |
aaf2bf9c | 19 | this.tagsCachesAddressableIndexes = new Map<string, number>(); |
9d7484a4 JB |
20 | } |
21 | ||
22 | public static getInstance(): AuthorizedTagsCache { | |
1ca780f9 | 23 | if (AuthorizedTagsCache.instance === null) { |
9d7484a4 JB |
24 | AuthorizedTagsCache.instance = new AuthorizedTagsCache(); |
25 | } | |
26 | return AuthorizedTagsCache.instance; | |
27 | } | |
28 | ||
aaf2bf9c JB |
29 | public getIdTag( |
30 | distribution: IdTagDistribution, | |
31 | chargingStation: ChargingStation, | |
32 | connectorId: number | |
33 | ): string { | |
34 | const hashId = chargingStation.stationInfo.hashId; | |
35 | const authorizationFile = ChargingStationUtils.getAuthorizationFile( | |
36 | chargingStation.stationInfo | |
37 | ); | |
38 | switch (distribution) { | |
39 | case IdTagDistribution.RANDOM: | |
40 | return this.getRandomIdTag(hashId, authorizationFile); | |
41 | case IdTagDistribution.ROUND_ROBIN: | |
42 | return this.getRoundRobinIdTag(hashId, authorizationFile); | |
43 | case IdTagDistribution.CONNECTOR_AFFINITY: | |
44 | return this.getConnectorAffinityIdTag(chargingStation, connectorId); | |
45 | default: | |
46 | return this.getRoundRobinIdTag(hashId, authorizationFile); | |
47 | } | |
48 | } | |
49 | ||
1895299d | 50 | public getAuthorizedTags(file: string): string[] | undefined { |
0638ddd2 | 51 | if (this.hasTags(file) === false) { |
9d7484a4 | 52 | this.setTags(file, this.getAuthorizedTagsFromFile(file)); |
9d7484a4 JB |
53 | } |
54 | return this.getTags(file); | |
55 | } | |
56 | ||
a36bad10 JB |
57 | public deleteAuthorizedTags(file: string): boolean { |
58 | return this.deleteTags(file); | |
59 | } | |
60 | ||
aaf2bf9c JB |
61 | private getRandomIdTag(hashId: string, file: string): string { |
62 | const tags = this.getAuthorizedTags(file); | |
63 | this.tagsCachesAddressableIndexes.set( | |
64 | file + hashId, | |
65 | Math.floor(Utils.secureRandom() * tags.length) | |
66 | ); | |
67 | return tags[this.tagsCachesAddressableIndexes.get(file + hashId)]; | |
68 | } | |
69 | ||
70 | private getRoundRobinIdTag(hashId: string, file: string): string { | |
71 | const tags = this.getAuthorizedTags(file); | |
72 | const idTagIndex = this.tagsCachesAddressableIndexes.get(file + hashId) ?? 0; | |
73 | const idTag = tags[idTagIndex]; | |
74 | this.tagsCachesAddressableIndexes.set( | |
75 | file + hashId, | |
76 | idTagIndex === tags.length - 1 ? 0 : idTagIndex + 1 | |
77 | ); | |
78 | return idTag; | |
79 | } | |
80 | ||
81 | private getConnectorAffinityIdTag(chargingStation: ChargingStation, connectorId: number): string { | |
82 | const file = ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo); | |
83 | const tags = this.getAuthorizedTags(file); | |
84 | const hashId = chargingStation.stationInfo.hashId; | |
85 | this.tagsCachesAddressableIndexes.set( | |
86 | file + hashId, | |
87 | (chargingStation.index - 1 + (connectorId - 1)) % tags.length | |
88 | ); | |
89 | return tags[this.tagsCachesAddressableIndexes.get(file + hashId)]; | |
90 | } | |
91 | ||
9d7484a4 JB |
92 | private hasTags(file: string): boolean { |
93 | return this.tagsCaches.has(file); | |
94 | } | |
95 | ||
96 | private setTags(file: string, tags: string[]) { | |
d6a62ad9 JB |
97 | return this.tagsCaches.set(file, { |
98 | tags, | |
99 | tagsFileWatcher: FileUtils.watchJsonFile( | |
100 | file, | |
101 | FileType.Authorization, | |
102 | this.logPrefix(file), | |
103 | undefined, | |
104 | (event, filename) => { | |
105 | if (Utils.isNotEmptyString(filename) && event === 'change') { | |
106 | try { | |
107 | logger.debug( | |
108 | `${this.logPrefix(file)} ${FileType.Authorization} file have changed, reload` | |
109 | ); | |
110 | this.deleteTags(file); | |
aaf2bf9c | 111 | this.deleteTagsIndexes(file); |
d6a62ad9 JB |
112 | } catch (error) { |
113 | FileUtils.handleFileException( | |
114 | file, | |
115 | FileType.Authorization, | |
116 | error as NodeJS.ErrnoException, | |
117 | this.logPrefix(file), | |
118 | { | |
119 | throwError: false, | |
120 | } | |
121 | ); | |
122 | } | |
123 | } | |
124 | } | |
125 | ), | |
126 | }); | |
9d7484a4 JB |
127 | } |
128 | ||
1895299d | 129 | private getTags(file: string): string[] | undefined { |
d6a62ad9 | 130 | return this.tagsCaches.get(file)?.tags; |
9d7484a4 JB |
131 | } |
132 | ||
133 | private deleteTags(file: string): boolean { | |
d6a62ad9 | 134 | this.tagsCaches.get(file)?.tagsFileWatcher?.close(); |
9d7484a4 JB |
135 | return this.tagsCaches.delete(file); |
136 | } | |
137 | ||
aaf2bf9c JB |
138 | private deleteTagsIndexes(file: string): void { |
139 | for (const [key] of this.tagsCachesAddressableIndexes) { | |
140 | if (key.startsWith(file)) { | |
141 | this.tagsCachesAddressableIndexes.delete(key); | |
142 | } | |
143 | } | |
144 | } | |
145 | ||
9d7484a4 JB |
146 | private getAuthorizedTagsFromFile(file: string): string[] { |
147 | let authorizedTags: string[] = []; | |
148 | if (file) { | |
149 | try { | |
150 | // Load authorization file | |
151 | authorizedTags = JSON.parse(fs.readFileSync(file, 'utf8')) as string[]; | |
152 | } catch (error) { | |
153 | FileUtils.handleFileException( | |
9d7484a4 | 154 | file, |
7164966d JB |
155 | FileType.Authorization, |
156 | error as NodeJS.ErrnoException, | |
157 | this.logPrefix(file) | |
9d7484a4 JB |
158 | ); |
159 | } | |
160 | } else { | |
44eb6026 | 161 | logger.info(`${this.logPrefix(file)} No authorization file given`); |
9d7484a4 JB |
162 | } |
163 | return authorizedTags; | |
164 | } | |
165 | ||
8b7072dc | 166 | private logPrefix = (file: string): string => { |
9d7484a4 | 167 | return Utils.logPrefix(` Authorized tags cache for authorization file '${file}' |`); |
8b7072dc | 168 | }; |
9d7484a4 | 169 | } |