1 import { type FSWatcher
, readFileSync
} from
'node:fs'
3 import { FileType
, IdTagDistribution
} from
'../types/index.js'
11 } from
'../utils/index.js'
12 import type { ChargingStation
} from
'./ChargingStation.js'
13 import { getIdTagsFile
} from
'./Helpers.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 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
52 const hashId
= chargingStation
.stationInfo
!.hashId
53 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
54 const idTagsFile
= getIdTagsFile(chargingStation
.stationInfo
!)!
55 switch (distribution
) {
56 case IdTagDistribution
.RANDOM
:
57 return this.getRandomIdTag(hashId
, idTagsFile
)
58 case IdTagDistribution
.ROUND_ROBIN
:
59 return this.getRoundRobinIdTag(hashId
, idTagsFile
)
60 case IdTagDistribution
.CONNECTOR_AFFINITY
:
61 return this.getConnectorAffinityIdTag(chargingStation
, connectorId
)
63 return this.getRoundRobinIdTag(hashId
, idTagsFile
)
68 * Gets all idtags from the cache
69 * Must be called after checking the cache is not an empty array
74 public getIdTags (file
: string): string[] | undefined {
75 if (!this.hasIdTagsCache(file
)) {
76 this.setIdTagsCache(file
, this.getIdTagsFromFile(file
))
78 return this.getIdTagsCache(file
)
81 public deleteIdTags (file
: string): boolean {
82 return this.deleteIdTagsCache(file
) && this.deleteIdTagsCacheIndexes(file
)
85 private getRandomIdTag (hashId
: string, file
: string): string {
86 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
87 const idTags
= this.getIdTags(file
)!
88 const addressableKey
= this.getIdTagsCacheIndexesAddressableKey(file
, hashId
)
89 this.idTagsCachesAddressableIndexes
.set(
91 Math.floor(secureRandom() * idTags
.length
)
93 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
94 return idTags
[this.idTagsCachesAddressableIndexes
.get(addressableKey
)!]
97 private getRoundRobinIdTag (hashId
: string, file
: string): string {
98 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
99 const idTags
= this.getIdTags(file
)!
100 const addressableKey
= this.getIdTagsCacheIndexesAddressableKey(file
, hashId
)
101 const idTagIndex
= this.idTagsCachesAddressableIndexes
.get(addressableKey
) ?? 0
102 const idTag
= idTags
[idTagIndex
]
103 this.idTagsCachesAddressableIndexes
.set(
105 idTagIndex
=== idTags
.length
- 1 ? 0 : idTagIndex
+ 1
110 private getConnectorAffinityIdTag (chargingStation
: ChargingStation
, connectorId
: number): string {
111 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
112 const file
= getIdTagsFile(chargingStation
.stationInfo
!)!
113 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
114 const idTags
= this.getIdTags(file
)!
115 const addressableKey
= this.getIdTagsCacheIndexesAddressableKey(
117 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
118 chargingStation
.stationInfo
!.hashId
120 this.idTagsCachesAddressableIndexes
.set(
122 (chargingStation
.index
- 1 + (connectorId
- 1)) % idTags
.length
124 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
125 return idTags
[this.idTagsCachesAddressableIndexes
.get(addressableKey
)!]
128 private hasIdTagsCache (file
: string): boolean {
129 return this.idTagsCaches
.has(file
)
132 private setIdTagsCache (file
: string, idTags
: string[]): Map
<string, IdTagsCacheValueType
> {
133 return this.idTagsCaches
.set(file
, {
135 idTagsFileWatcher
: watchJsonFile(
137 FileType
.Authorization
,
138 this.logPrefix(file
),
140 (event
, filename
) => {
141 if (isNotEmptyString(filename
) && event
=== 'change') {
144 `${this.logPrefix(file)} ${FileType.Authorization} file have changed, reload`
146 this.deleteIdTags(file
)
150 FileType
.Authorization
,
151 error
as NodeJS
.ErrnoException
,
152 this.logPrefix(file
),
164 private getIdTagsCache (file
: string): string[] | undefined {
165 return this.idTagsCaches
.get(file
)?.idTags
168 private deleteIdTagsCache (file
: string): boolean {
169 this.idTagsCaches
.get(file
)?.idTagsFileWatcher
?.close()
170 return this.idTagsCaches
.delete(file
)
173 private deleteIdTagsCacheIndexes (file
: string): boolean {
174 const deleted
: boolean[] = []
175 for (const [key
] of this.idTagsCachesAddressableIndexes
) {
176 if (key
.startsWith(file
)) {
177 deleted
.push(this.idTagsCachesAddressableIndexes
.delete(key
))
180 return !deleted
.some(value
=> !value
)
183 private getIdTagsCacheIndexesAddressableKey (prefix
: string, uid
: string): string {
184 return `${prefix}${uid}`
187 private getIdTagsFromFile (file
: string): string[] {
188 if (isNotEmptyString(file
)) {
190 return JSON
.parse(readFileSync(file
, 'utf8')) as string[]
194 FileType
.Authorization
,
195 error
as NodeJS
.ErrnoException
,
203 private readonly logPrefix
= (file
: string): string => {
204 return logPrefix(` Id tags cache for id tags file '${file}' |`)