diff --git a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts index 9ecd1bde..da1ef8a0 100644 --- a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts @@ -85,8 +85,14 @@ import { ArduinoLocalizationContribution } from './arduino-localization-contribu import { LocalizationContribution } from '@theia/core/lib/node/i18n/localization-contribution'; import { MonitorManagerProxyImpl } from './monitor-manager-proxy-impl'; import { MonitorManager, MonitorManagerName } from './monitor-manager'; -import { MonitorManagerProxy, MonitorManagerProxyClient, MonitorManagerProxyPath } from '../common/protocol/monitor-service'; +import { + MonitorManagerProxy, + MonitorManagerProxyClient, + MonitorManagerProxyPath, +} from '../common/protocol/monitor-service'; import { MonitorServiceName } from './monitor-service'; +import { MonitorSettingsProvider } from './monitor-settings/monitor-settings-provider'; +import { MonitorSettingsProviderImpl } from './monitor-settings/monitor-settings-provider-impl'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(BackendApplication).toSelf().inSingletonScope(); @@ -198,6 +204,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { // a single MonitorManager is responsible for handling the actual connections to the pluggable monitors bind(MonitorManager).toSelf().inSingletonScope(); + bind(MonitorSettingsProviderImpl).toSelf().inSingletonScope(); + bind(MonitorSettingsProvider).toService(MonitorSettingsProviderImpl); + // Serial client provider per connected frontend. bind(ConnectionContainerModule).toConstantValue( ConnectionContainerModule.create(({ bind, bindBackendService }) => { diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index 1696756f..e3facecf 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -122,10 +122,18 @@ export class MonitorService extends CoreClientAware implements Disposable { } this.logger.info('starting monitor'); - this.settings = await this.monitorSettingsProvider.init( - monitorID, - this.coreClientProvider + + // get default monitor settings from the CLI + const defaultSettings = await this.portMonitorSettings( + this.port.protocol, + this.board.fqbn ); + // get actual settings from the settings provider + this.settings = await this.monitorSettingsProvider.getSettings( + monitorID, + defaultSettings + ); + await this.coreClientProvider.initialized; const coreClient = await this.coreClient(); const { client, instance } = coreClient; diff --git a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts index 40dca6d7..54e3b569 100644 --- a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts +++ b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts @@ -1,47 +1,145 @@ -import { injectable } from 'inversify'; -import { CoreClientProvider } from '../core-client-provider'; +import * as fs from 'fs'; +import { join } from 'path'; +import { injectable, inject, postConstruct } from 'inversify'; +import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; +import { FileUri } from '@theia/core/lib/node/file-uri'; +import { promisify } from 'util'; + import { PluggableMonitorSettings, MonitorSettingsProvider, - MonitorSettings, } from './monitor-settings-provider'; +import { Deferred } from '@theia/core/lib/common/promise-util'; + +const MONITOR_SETTINGS_FILE = 'pluggable-monitor-settings.json'; @injectable() export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { - // this is populated with all settings coming from the CLI. This should never get modified - // as it is used to double actual values set by the user - private monitorSettings: MonitorSettings; + @inject(EnvVariablesServer) + protected readonly envVariablesServer: EnvVariablesServer; - // this contains values for setting of the monitorSettings - // the key is MonitorSetting.id, the value should be one of the MonitorSetting.values - private monitorSettingsValues: Record; + protected ready = new Deferred(); - init( - id: string, - coreClientProvider: CoreClientProvider + // this is populated with all settings coming from the CLI. This should never be modified + // // as it is used to double check the monitorSettings attribute + // private monitorDefaultSettings: PluggableMonitorSettings; + + // 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; + + private pluggableMonitorSettingsPath: string; + + @postConstruct() + protected async init(): Promise { + // get the monitor settings file path + const configDirUri = await this.envVariablesServer.getConfigDirUri(); + this.pluggableMonitorSettingsPath = join( + FileUri.fsPath(configDirUri), + MONITOR_SETTINGS_FILE + ); + + // read existing settings + this.readFile(); + + console.log(this.monitorSettings); + this.ready.resolve(); + } + + async getSettings( + monitorId: string, + defaultSettings: PluggableMonitorSettings ): Promise { - throw new Error('Method not implemented.'); + // wait for the service to complete the init + await this.ready.promise; - // 1. query the CLI (via coreClientProvider) and return all available settings for the pluggable monitor. - // store these in `monitorSettings` for later checkings + const { matchingSettings } = this.longestPrefixMatch(monitorId); - // 2. check for the settings file in the user's home directory - // a. if it doesn't exist, create it as an empty json file - // 3. search the file, looking for the longest prefix matching the id - // a. miss: populate `monitorSettingsValues` with all default settings from `monitorSettings` - // b. hit: populate `monitorSettingsValues` with the result for the search - // i. purge the `monitorSettingsValues` removing keys that are not defined in `monitorSettings` - // and adding those that are missing - // ii. save the `monitorSettingsValues` in the file, using the id as the key + return this.reconcileSettings(matchingSettings, defaultSettings); } - get(): Promise { - throw new Error('Method not implemented.'); - } - set(settings: PluggableMonitorSettings): Promise { - throw new Error('Method not implemented.'); + async setSettings( + monitorId: string, + settings: PluggableMonitorSettings + ): Promise { + // wait for the service to complete the init + await this.ready.promise; - // 1. parse the settings parameter and remove any setting that is not defined in `monitorSettings` - // 2. update `monitorSettingsValues` accordingly - // 3. save it to the file + const newSettings = this.reconcileSettings( + settings, + this.monitorSettings[monitorId] + ); + this.monitorSettings[monitorId] = newSettings; + + await this.writeFile(); + return newSettings; + } + + private reconcileSettings( + newSettings: PluggableMonitorSettings, + defaultSettings: PluggableMonitorSettings + ): PluggableMonitorSettings { + // TODO: implement + return newSettings; + } + + private async readFile(): Promise { + const rawJson = await promisify(fs.readFile)( + this.pluggableMonitorSettingsPath, + { + encoding: 'utf-8', + 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) { + console.error( + 'Could not parse the pluggable monitor settings file. Using empty file.' + ); + this.monitorSettings = {}; + } + } + + private async writeFile() { + await promisify(fs.writeFile)( + this.pluggableMonitorSettingsPath, + JSON.stringify(this.monitorSettings) + ); + } + + private longestPrefixMatch(id: string): { + matchingPrefix: string; + matchingSettings: PluggableMonitorSettings; + } { + const separator = '-'; + const idTokens = id.split(separator); + + let matchingPrefix = ''; + let matchingSettings: PluggableMonitorSettings = {}; + + const monitorSettingsKeys = Object.keys(this.monitorSettings); + + for (let i = 0; i < idTokens.length; i++) { + const prefix = idTokens.slice(0, i + 1).join(separator); + + for (let k = 0; k < monitorSettingsKeys.length; k++) { + if (monitorSettingsKeys[k].startsWith(prefix)) { + matchingPrefix = prefix; + matchingSettings = this.monitorSettings[monitorSettingsKeys[k]]; + break; + } + } + + if (matchingPrefix.length) { + break; + } + } + + return { matchingPrefix, matchingSettings }; } } diff --git a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider.ts b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider.ts index 5ca5ad4a..e8949a60 100644 --- a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider.ts +++ b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider.ts @@ -1,6 +1,5 @@ import { MonitorModel } from '../../browser/monitor-model'; import { PluggableMonitorSetting } from '../../common/protocol'; -import { CoreClientProvider } from '../core-client-provider'; export type PluggableMonitorSettings = Record; export interface MonitorSettings { @@ -10,10 +9,12 @@ export interface MonitorSettings { export const MonitorSettingsProvider = Symbol('MonitorSettingsProvider'); export interface MonitorSettingsProvider { - init( - id: string, - coreClientProvider: CoreClientProvider + getSettings( + monitorId: string, + defaultSettings: PluggableMonitorSettings + ): Promise; + setSettings( + monitorId: string, + settings: PluggableMonitorSettings ): Promise; - get(): Promise; - set(settings: PluggableMonitorSettings): Promise; }