1 import fs from
'node:fs';
3 import { type ChargingStation
, ChargingStationUtils
} from
'./internal';
4 import { FileType
, IdTagDistribution
} from
'../types';
5 import { FileUtils
, Utils
, logger
} from
'../utils';
7 type TagsCacheValueType
= {
9 tagsFileWatcher
: fs
.FSWatcher
| undefined;
12 export class AuthorizedTagsCache
{
13 private static instance
: AuthorizedTagsCache
| null = null;
14 private readonly tagsCaches
: Map
<string, TagsCacheValueType
>;
15 private readonly tagsCachesAddressableIndexes
: Map
<string, number>;
17 private constructor() {
18 this.tagsCaches
= new Map
<string, TagsCacheValueType
>();
19 this.tagsCachesAddressableIndexes
= new Map
<string, number>();
22 public static getInstance(): AuthorizedTagsCache
{
23 if (AuthorizedTagsCache
.instance
=== null) {
24 AuthorizedTagsCache
.instance
= new AuthorizedTagsCache();
26 return AuthorizedTagsCache
.instance
;
30 distribution
: IdTagDistribution
,
31 chargingStation
: ChargingStation
,
34 const hashId
= chargingStation
.stationInfo
.hashId
;
35 const authorizationFile
= ChargingStationUtils
.getAuthorizationFile(
36 chargingStation
.stationInfo
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
);
46 return this.getRoundRobinIdTag(hashId
, authorizationFile
);
50 public getAuthorizedTags(file
: string): string[] | undefined {
51 if (this.hasTags(file
) === false) {
52 this.setTags(file
, this.getAuthorizedTagsFromFile(file
));
54 return this.getTags(file
);
57 public deleteAuthorizedTags(file
: string): boolean {
58 return this.deleteTags(file
);
61 private getRandomIdTag(hashId
: string, file
: string): string {
62 const tags
= this.getAuthorizedTags(file
);
63 const addressableKey
= file
+ hashId
;
64 this.tagsCachesAddressableIndexes
.set(
66 Math.floor(Utils
.secureRandom() * tags
.length
)
68 return tags
[this.tagsCachesAddressableIndexes
.get(addressableKey
)];
71 private getRoundRobinIdTag(hashId
: string, file
: string): string {
72 const tags
= this.getAuthorizedTags(file
);
73 const addressableKey
= file
+ hashId
;
74 const idTagIndex
= this.tagsCachesAddressableIndexes
.get(addressableKey
) ?? 0;
75 const idTag
= tags
[idTagIndex
];
76 this.tagsCachesAddressableIndexes
.set(
78 idTagIndex
=== tags
.length
- 1 ? 0 : idTagIndex
+ 1
83 private getConnectorAffinityIdTag(chargingStation
: ChargingStation
, connectorId
: number): string {
84 const file
= ChargingStationUtils
.getAuthorizationFile(chargingStation
.stationInfo
);
85 const tags
= this.getAuthorizedTags(file
);
86 const hashId
= chargingStation
.stationInfo
.hashId
;
87 const addressableKey
= file
+ hashId
;
88 this.tagsCachesAddressableIndexes
.set(
90 (chargingStation
.index
- 1 + (connectorId
- 1)) % tags
.length
92 return tags
[this.tagsCachesAddressableIndexes
.get(addressableKey
)];
95 private hasTags(file
: string): boolean {
96 return this.tagsCaches
.has(file
);
99 private setTags(file
: string, tags
: string[]) {
100 return this.tagsCaches
.set(file
, {
102 tagsFileWatcher
: FileUtils
.watchJsonFile(
104 FileType
.Authorization
,
105 this.logPrefix(file
),
107 (event
, filename
) => {
108 if (Utils
.isNotEmptyString(filename
) && event
=== 'change') {
111 `${this.logPrefix(file)} ${FileType.Authorization} file have changed, reload`
113 this.deleteTags(file
);
114 this.deleteTagsIndexes(file
);
116 FileUtils
.handleFileException(
118 FileType
.Authorization
,
119 error
as NodeJS
.ErrnoException
,
120 this.logPrefix(file
),
132 private getTags(file
: string): string[] | undefined {
133 return this.tagsCaches
.get(file
)?.tags
;
136 private deleteTags(file
: string): boolean {
137 this.tagsCaches
.get(file
)?.tagsFileWatcher
?.close();
138 return this.tagsCaches
.delete(file
);
141 private deleteTagsIndexes(file
: string): void {
142 for (const [key
] of this.tagsCachesAddressableIndexes
) {
143 if (key
.startsWith(file
)) {
144 this.tagsCachesAddressableIndexes
.delete(key
);
149 private getAuthorizedTagsFromFile(file
: string): string[] {
150 let authorizedTags
: string[] = [];
153 // Load authorization file
154 authorizedTags
= JSON
.parse(fs
.readFileSync(file
, 'utf8')) as string[];
156 FileUtils
.handleFileException(
158 FileType
.Authorization
,
159 error
as NodeJS
.ErrnoException
,
164 logger
.info(`${this.logPrefix(file)} No authorization file given`);
166 return authorizedTags
;
169 private logPrefix
= (file
: string): string => {
170 return Utils
.logPrefix(` Authorized tags cache for authorization file '${file}' |`);