1 import { type FSWatcher
, readFileSync
} from
'node:fs'
3 import type { ChargingStation
} from
'./ChargingStation.js'
4 import { getIdTagsFile
} from
'./Helpers.js'
5 import { FileType
, IdTagDistribution
} from
'../types/index.js'
13 } from
'../utils/index.js'
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 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
53 const idTagsFile
= getIdTagsFile(chargingStation
.stationInfo
)!
54 switch (distribution
) {
55 case IdTagDistribution
.RANDOM
:
56 return this.getRandomIdTag(hashId
, idTagsFile
)
57 case IdTagDistribution
.ROUND_ROBIN
:
58 return this.getRoundRobinIdTag(hashId
, idTagsFile
)
59 case IdTagDistribution
.CONNECTOR_AFFINITY
:
60 return this.getConnectorAffinityIdTag(chargingStation
, connectorId
)
62 return this.getRoundRobinIdTag(hashId
, idTagsFile
)
67 * Gets all idtags from the cache
68 * Must be called after checking the cache is not an empty array
73 public getIdTags (file
: string): string[] | undefined {
74 if (!this.hasIdTagsCache(file
)) {
75 this.setIdTagsCache(file
, this.getIdTagsFromFile(file
))
77 return this.getIdTagsCache(file
)
80 public deleteIdTags (file
: string): boolean {
81 return this.deleteIdTagsCache(file
) && this.deleteIdTagsCacheIndexes(file
)
84 private getRandomIdTag (hashId
: string, file
: string): string {
85 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
86 const idTags
= this.getIdTags(file
)!
87 const addressableKey
= this.getIdTagsCacheIndexesAddressableKey(file
, hashId
)
88 this.idTagsCachesAddressableIndexes
.set(
90 Math.floor(secureRandom() * idTags
.length
)
92 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
93 return idTags
[this.idTagsCachesAddressableIndexes
.get(addressableKey
)!]
96 private getRoundRobinIdTag (hashId
: string, file
: string): string {
97 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
98 const idTags
= this.getIdTags(file
)!
99 const addressableKey
= this.getIdTagsCacheIndexesAddressableKey(file
, hashId
)
100 const idTagIndex
= this.idTagsCachesAddressableIndexes
.get(addressableKey
) ?? 0
101 const idTag
= idTags
[idTagIndex
]
102 this.idTagsCachesAddressableIndexes
.set(
104 idTagIndex
=== idTags
.length
- 1 ? 0 : idTagIndex
+ 1
109 private getConnectorAffinityIdTag (chargingStation
: ChargingStation
, connectorId
: number): string {
110 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
111 const file
= getIdTagsFile(chargingStation
.stationInfo
)!
112 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
113 const idTags
= this.getIdTags(file
)!
114 const addressableKey
= this.getIdTagsCacheIndexesAddressableKey(
116 chargingStation
.stationInfo
.hashId
118 this.idTagsCachesAddressableIndexes
.set(
120 (chargingStation
.index
- 1 + (connectorId
- 1)) % idTags
.length
122 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
123 return idTags
[this.idTagsCachesAddressableIndexes
.get(addressableKey
)!]
126 private hasIdTagsCache (file
: string): boolean {
127 return this.idTagsCaches
.has(file
)
130 private setIdTagsCache (file
: string, idTags
: string[]): Map
<string, IdTagsCacheValueType
> {
131 return this.idTagsCaches
.set(file
, {
133 idTagsFileWatcher
: watchJsonFile(
135 FileType
.Authorization
,
136 this.logPrefix(file
),
138 (event
, filename
) => {
139 if (isNotEmptyString(filename
) && event
=== 'change') {
142 `${this.logPrefix(file)} ${FileType.Authorization} file have changed, reload`
144 this.deleteIdTagsCache(file
)
145 this.deleteIdTagsCacheIndexes(file
)
149 FileType
.Authorization
,
150 error
as NodeJS
.ErrnoException
,
151 this.logPrefix(file
),
163 private getIdTagsCache (file
: string): string[] | undefined {
164 return this.idTagsCaches
.get(file
)?.idTags
167 private deleteIdTagsCache (file
: string): boolean {
168 this.idTagsCaches
.get(file
)?.idTagsFileWatcher
?.close()
169 return this.idTagsCaches
.delete(file
)
172 private deleteIdTagsCacheIndexes (file
: string): boolean {
173 const deleted
: boolean[] = []
174 for (const [key
] of this.idTagsCachesAddressableIndexes
) {
175 if (key
.startsWith(file
)) {
176 deleted
.push(this.idTagsCachesAddressableIndexes
.delete(key
))
179 return !deleted
.some((value
) => !value
)
182 private getIdTagsCacheIndexesAddressableKey (prefix
: string, uid
: string): string {
183 return `${prefix}${uid}`
186 private getIdTagsFromFile (file
: string): string[] {
187 if (isNotEmptyString(file
)) {
189 return JSON
.parse(readFileSync(file
, 'utf8')) as string[]
193 FileType
.Authorization
,
194 error
as NodeJS
.ErrnoException
,
202 private readonly logPrefix
= (file
: string): string => {
203 return logPrefix(` Id tags cache for id tags file '${file}' |`)