101 private filePath:
string | undefined = undefined;
102 private alternateFilePath:
string | undefined = undefined;
104 private cache: T | undefined = undefined;
106 private cwd:
string =
"";
108 private timeoutCheckMs: number = 5000;
110 private timeoutReadMs: number = 10000;
120 constructor(filePath:
string | undefined = undefined, cwd:
string | undefined = undefined, alternateFilePath:
string | undefined = undefined) {
123 this.filePath = filePath;
126 if (alternateFilePath) {
127 this.alternateFilePath = alternateFilePath;
128 logger.debug(
getMessage(
"foundAlternateFilePathToLoad", alternateFilePath));
140 private async withTimeout<T>(promise: Promise<T>, timeoutMs: number, label?:
string): Promise<T> {
141 return Promise.race([
143 new Promise<T>((_, reject) =>
144 setTimeout(() => reject(
new Error(`${label}`)), timeoutMs)
158 async pathExists(filePath:
string, timeoutMs: number = 5000): Promise<boolean> {
161 await this.withTimeout(fsp.access(filePath, fsp.constants.F_OK), timeoutMs, `pathExists:
'${filePath}'>${timeoutMs}ms`);
180 private async resolveAbsolutePath(filePath:
string): Promise<
string> {
181 logger.debug(
getMessage(
"inFunction",
"resolveAbsolutePath",
"LazyFileLoader"));
182 if (
path.isAbsolute(filePath)) {
185 return path.join(this.cwd, filePath);
202 async
get(): Promise<T | undefined> {
206 return this.cache ?? undefined;
208 const tryPaths:
string[] = [this.filePath, this.alternateFilePath].filter(
path =>
path !== undefined &&
path !==
null) as
string[];
209 if (tryPaths.length === 0) {
211 return this.cache ?? undefined;
213 for (
const candidate of tryPaths) {
216 const absolutePath = await this.resolveAbsolutePath(candidate);
219 const exists = await this.pathExists(absolutePath, this.timeoutCheckMs);
224 const content = await this.withTimeout(fsp.readFile(absolutePath,
"utf-8"),
this.timeoutReadMs,
getMessage(
"readTimeout", this.timeoutReadMs, absolutePath));
226 const fileExtension:
string =
path.extname(candidate).toLowerCase();
228 if (fileExtension ===
".json") {
229 this.cache = JSON.parse(content) as T;
230 }
else if (fileExtension ===
".jsonc") {
233 this.cache = content as T;
236 return this.cache ?? undefined;
240 const errorMsg:
string =
getMessage(
"fileParseError", absolutePath, String(err));
242 logger.Gui.error(errorMsg);
243 this.cache = content as T;
245 return this.cache ?? undefined;
248 const errMsg:
string =
getMessage(
"fileLoadError", candidate, String(e));
251 this.cache = undefined;
253 if ((e as any).code ===
'ENOENT' || (e as any).code ===
'EISDIR') {
258 return this.cache ?? undefined;
269 async reload(): Promise<T | undefined> {
273 return await this.
get();
285 this.cache = undefined;
298 async updateFilePath(filePath:
string, reload:
boolean =
false): Promise<boolean> {
300 const oldFilePath = this.filePath;
303 if (this.cache && oldFilePath && filePath !== oldFilePath) {
304 const absolutePath = await this.resolveAbsolutePath(filePath);
305 const exists = await this.pathExists(absolutePath, this.timeoutCheckMs);
307 const errorMessage =
getMessage(
"fileNotFound", absolutePath);
308 const error =
new Error(errorMessage);
309 (error as any).code =
'ENOENT';
314 this.filePath = filePath;
316 this.cache = undefined;
318 const status: T | undefined = await this.reload();
319 if (status === undefined) {
323 logger.info(
getMessage(
"filePathUpdated", String(oldFilePath), String(filePath)));
336 async updateAlternateFilePath(filePath:
string, reload:
boolean =
false): Promise<boolean> {
337 logger.debug(
getMessage(
"inFunction",
"updateAlternateFilePath",
"LazyFileLoader"));
338 const oldFilePath = this.alternateFilePath;
339 this.alternateFilePath = filePath;
340 if (this.cache && reload) {
341 const status: T | undefined = await this.reload();
342 if (status === undefined) {
346 logger.info(
getMessage(
"alternateFilePathUpdated", String(oldFilePath), String(this.alternateFilePath)));
360 async updateCurrentWorkingDirectory(cwd:
string): Promise<boolean> {
361 logger.debug(
getMessage(
"inFunction",
"updateCurrentWorkingDirectory",
"LazyFileLoader"));
362 const oldCwd:
string = this.cwd;
364 const stats = await fsp.stat(cwd);
365 if (!stats.isDirectory()) {
385 getFilePath():
string | undefined {
387 return this.filePath;
397 getAlternateFilePath():
string | undefined {
398 logger.debug(
getMessage(
"inFunction",
"getAlternateFilePath",
"LazyFileLoader"));
399 return this.alternateFilePath;
409 setCheckTimeout(timeoutCheckMs: number): void {
410 logger.debug(
getMessage(
"inFunction",
"setCheckTimeout",
"LazyFileLoader"));
411 this.timeoutCheckMs = timeoutCheckMs;
421 setReadTimeout(timeoutReadMs: number): void {
423 this.timeoutReadMs = timeoutReadMs;
435 setFilePathTimeout(timeoutCheckMs: number, timeoutReadMs: number): void {
436 this.setCheckTimeout(timeoutCheckMs);
437 this.setReadTimeout(timeoutReadMs);