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 this.tagsCachesAddressableIndexes
.set(
65 Math.floor(Utils
.secureRandom() * tags
.length
)
67 return tags
[this.tagsCachesAddressableIndexes
.get(file
+ hashId
)];
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(
76 idTagIndex
=== tags
.length
- 1 ? 0 : idTagIndex
+ 1
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(
87 (chargingStation
.index
- 1 + (connectorId
- 1)) % tags
.length
89 return tags
[this.tagsCachesAddressableIndexes
.get(file
+ hashId
)];
92 private hasTags(file
: string): boolean {
93 return this.tagsCaches
.has(file
);
96 private setTags(file
: string, tags
: string[]) {
97 return this.tagsCaches
.set(file
, {
99 tagsFileWatcher
: FileUtils
.watchJsonFile(
101 FileType
.Authorization
,
102 this.logPrefix(file
),
104 (event
, filename
) => {
105 if (Utils
.isNotEmptyString(filename
) && event
=== 'change') {
108 `${this.logPrefix(file)} ${FileType.Authorization} file have changed, reload`
110 this.deleteTags(file
);
111 this.deleteTagsIndexes(file
);
113 FileUtils
.handleFileException(
115 FileType
.Authorization
,
116 error
as NodeJS
.ErrnoException
,
117 this.logPrefix(file
),
129 private getTags(file
: string): string[] | undefined {
130 return this.tagsCaches
.get(file
)?.tags
;
133 private deleteTags(file
: string): boolean {
134 this.tagsCaches
.get(file
)?.tagsFileWatcher
?.close();
135 return this.tagsCaches
.delete(file
);
138 private deleteTagsIndexes(file
: string): void {
139 for (const [key
] of this.tagsCachesAddressableIndexes
) {
140 if (key
.startsWith(file
)) {
141 this.tagsCachesAddressableIndexes
.delete(key
);
146 private getAuthorizedTagsFromFile(file
: string): string[] {
147 let authorizedTags
: string[] = [];
150 // Load authorization file
151 authorizedTags
= JSON
.parse(fs
.readFileSync(file
, 'utf8')) as string[];
153 FileUtils
.handleFileException(
155 FileType
.Authorization
,
156 error
as NodeJS
.ErrnoException
,
161 logger
.info(`${this.logPrefix(file)} No authorization file given`);
163 return authorizedTags
;
166 private logPrefix
= (file
: string): string => {
167 return Utils
.logPrefix(` Authorized tags cache for authorization file '${file}' |`);