Pluggable monitor (#982)

* backend structure WIP

* Scaffold interfaces and classes for pluggable monitors

* Implement MonitorService to handle pluggable monitor lifetime

* Rename WebSocketService to WebSocketProvider and uninjected it

* Moved some interfaces

* Changed upload settings

* Enhance MonitorManager APIs

* Fixed WebSocketChange event signature

* Add monitor proxy functions for the frontend

* Moved settings to MonitorService

* Remove several unnecessary serial monitor classes

* Changed how connection is handled on upload

* Proxied more monitor methods to frontend

* WebSocketProvider is not injectable anymore

* Add generic monitor settings storaging

* More serial classes removal

* Remove unused file

* Changed plotter contribution to use new manager proxy

* Changed MonitorWidget and children to use new monitor proxy

* Updated MonitorWidget to use new monitor proxy

* Fix backend logger bindings

* Delete unnecessary Symbol

* coreClientProvider is now set when constructing MonitorService

* Add missing binding

* Fix `MonitorManagerProxy` DI issue

* fix monitor connection

* delete duplex when connection is closed

* update arduino-cli to 0.22.0

* fix upload when monitor is open

* add MonitorSettingsProvider interface

* monitor settings provider stub

* updated pseudo code

* refactor monitor settings interfaces

* monitor service provider singleton

* add unit tests

* change MonitorService providers to injectable deps

* fix monitor settings client communication

* refactor monitor commands protocol

* use monitor settings provider properly

* add settings to monitor model

* add settings to monitor model

* reset serial monitor when port changes

* fix serial plotter opening

* refine monitor connection settings

* fix hanging web socket connections

* add serial plotter reset command

* send port to web socket clients

* monitor service wait for success serial port open

* fix reset loop

* update serial plotter version

* update arduino-cli version to 0.23.0-rc1 and regenerate grpc protocol

* remove useless plotter protocol file

* localize web socket errors

* clean-up code

* update translation file

* Fix duplicated editor tabs (#1012)

* Save dialog for closing temporary sketch and unsaved files (#893)

* Use normal `OnWillStop` event

* Align `CLOSE` command to rest of app

* Fixed FS path vs encoded URL comparision when handling stop request.

Ref: https://github.com/eclipse-theia/theia/issues/11226
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>

* Fixed the translations.

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>

* Fixed the translations again.

Removed `electron` from the `nls-extract`. It does not contain app code.

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>

* Aligned the stop handler code to Theia.

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>

Co-authored-by: Akos Kitta <a.kitta@arduino.cc>

* fix serial monitor send line ending

* refactor monitor-service poll for test/readability

* localize web socket errors

* update translation file

* Fix duplicated editor tabs (#1012)

* i18n:check rerun

* Speed up IDE startup time.

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>

* override coreClientProvider in monitor-service

* cleanup merged code

Co-authored-by: Francesco Stasi <f.stasi@me.com>
Co-authored-by: Silvano Cerza <silvanocerza@gmail.com>
Co-authored-by: Mark Sujew <mark.sujew@typefox.io>
Co-authored-by: David Simpson <45690499+davegarthsimpson@users.noreply.github.com>
Co-authored-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
Alberto Iannaccone
2022-06-07 15:51:12 +02:00
committed by GitHub
parent 4c55807392
commit df8658eff9
43 changed files with 2329 additions and 1567 deletions

View File

@@ -0,0 +1,130 @@
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,
} 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';
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<void>();
// 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<string, PluggableMonitorSettings>;
// this is the path to the pluggable monitor settings file, set during init
private pluggableMonitorSettingsPath: string;
@postConstruct()
protected async init(): Promise<void> {
// 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<PluggableMonitorSettings> {
// 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<PluggableMonitorSettings> {
// 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<void> {
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) {
this.logger.error(
'Could not parse the pluggable monitor settings file. Using empty file.'
);
this.monitorSettings = {};
}
}
private async writeSettingsToFS(): Promise<void> {
await promisify(fs.writeFile)(
this.pluggableMonitorSettingsPath,
JSON.stringify(this.monitorSettings)
);
}
private longestPrefixMatch(id: string): {
matchingPrefix: string;
matchingSettings: PluggableMonitorSettings;
} {
return longestPrefixMatch(id, this.monitorSettings);
}
}