import fs from 'node:fs'; import { join } from 'node:path'; import { injectable, inject, postConstruct, } from '@theia/core/shared/inversify'; import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { FileUri } from '@theia/core/lib/node/file-uri'; import { promisify } from 'util'; import { MonitorSettingsProvider } from './monitor-settings-provider'; import { Deferred } from '@theia/core/lib/common/promise-util'; import { longestPrefixMatch, reconcileSettings, } from './monitor-settings-utils'; import { ILogger } from '@theia/core'; import { PluggableMonitorSettings } from '../../common/protocol'; const MONITOR_SETTINGS_FILE = 'pluggable-monitor-settings.json'; @injectable() export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { @inject(EnvVariablesServer) protected readonly envVariablesServer: EnvVariablesServer; @inject(ILogger) protected logger: ILogger; // deferred used to guarantee file operations are performed after the service is initialized protected ready = new Deferred(); // this contains actual values coming from the stored file and edited by the user // this is a map with MonitorId as key and PluggableMonitorSetting as value private monitorSettings: Record; // this is the path to the pluggable monitor settings file, set during init private pluggableMonitorSettingsPath: string; @postConstruct() protected init(): void { (async () => { // get the monitor settings file path const configDirUri = await this.envVariablesServer.getConfigDirUri(); this.pluggableMonitorSettingsPath = join( FileUri.fsPath(configDirUri), MONITOR_SETTINGS_FILE ); // read existing settings await this.readSettingsFromFS(); // init is done, resolve the deferred and unblock any call that was waiting for it this.ready.resolve(); })(); } async getSettings( monitorId: string, defaultSettings: PluggableMonitorSettings ): Promise { // wait for the service to complete the init await this.ready.promise; const { matchingSettings } = this.longestPrefixMatch(monitorId); this.monitorSettings[monitorId] = this.reconcileSettings( matchingSettings, defaultSettings ); return this.monitorSettings[monitorId]; } async setSettings( monitorId: string, settings: PluggableMonitorSettings ): Promise { // wait for the service to complete the init await this.ready.promise; const newSettings = this.reconcileSettings( settings, this.monitorSettings[monitorId] || {} ); this.monitorSettings[monitorId] = newSettings; await this.writeSettingsToFS(); return newSettings; } private reconcileSettings( newSettings: PluggableMonitorSettings, defaultSettings: PluggableMonitorSettings ): PluggableMonitorSettings { return reconcileSettings(newSettings, defaultSettings); } private async readSettingsFromFS(): Promise { const rawJson = await promisify(fs.readFile)( this.pluggableMonitorSettingsPath, { encoding: 'utf8', flag: 'a+', // a+ = append and read, creating the file if it doesn't exist } ); if (!rawJson) { this.monitorSettings = {}; } try { this.monitorSettings = JSON.parse(rawJson); } catch (error) { this.logger.error( 'Could not parse the pluggable monitor settings file. Using empty file.' ); this.monitorSettings = {}; } } private async writeSettingsToFS(): Promise { await promisify(fs.writeFile)( this.pluggableMonitorSettingsPath, JSON.stringify(this.monitorSettings) ); } private longestPrefixMatch(id: string): { matchingPrefix: string; matchingSettings: PluggableMonitorSettings; } { return longestPrefixMatch(id, this.monitorSettings); } }