mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-06-18 18:16:34 +00:00

* 1032 failing upload flag for monitor mgr * move upload failure fix logic to frontend * misc corrections * avoid starting monitor when upload is in progress * avoid starting monitor when upload is in progress * prevent monitor side effects on upload (WIP) * send upload req after notifying mgr * dispose instead of pause on upld (code not final) * Revert "dispose instead of pause on upld (code not final)" This reverts commit 2d5dff2a2d85754467470b5973b7ecac8017aa58. * force wait before upload (test) * always start queued services after uplaod finishes * test cli with monitor close delay * clean up unnecessary await(s) * remove unused dependency * revert CLI to 0.23 * use master cli for testing, await in upload finish * remove upload port from pending monitor requests * fix startQueuedServices * refinements queued monitors * clean up monitor mgr state * fix typo from prev cleanup * avoid dupl queued monitor services * variable name changes * reference latest cli commit in package.json Co-authored-by: Alberto Iannaccone <a.iannaccone@arduino.cc>
355 lines
11 KiB
TypeScript
355 lines
11 KiB
TypeScript
import { ILogger } from '@theia/core';
|
|
import { inject, injectable, named } from '@theia/core/shared/inversify';
|
|
import { Board, BoardsService, Port, Status } from '../common/protocol';
|
|
import { CoreClientAware } from './core-client-provider';
|
|
import { MonitorService } from './monitor-service';
|
|
import { MonitorServiceFactory } from './monitor-service-factory';
|
|
import {
|
|
MonitorSettings,
|
|
PluggableMonitorSettings,
|
|
} from './monitor-settings/monitor-settings-provider';
|
|
|
|
type MonitorID = string;
|
|
|
|
type UploadState = 'uploadInProgress' | 'pausedForUpload' | 'disposedForUpload';
|
|
type MonitorIDsByUploadState = Record<UploadState, MonitorID[]>;
|
|
|
|
export const MonitorManagerName = 'monitor-manager';
|
|
|
|
@injectable()
|
|
export class MonitorManager extends CoreClientAware {
|
|
@inject(BoardsService)
|
|
protected boardsService: BoardsService;
|
|
|
|
// Map of monitor services that manage the running pluggable monitors.
|
|
// Each service handles the lifetime of one, and only one, monitor.
|
|
// If either the board or port managed changes, a new service must
|
|
// be started.
|
|
private monitorServices = new Map<MonitorID, MonitorService>();
|
|
|
|
private monitorIDsByUploadState: MonitorIDsByUploadState = {
|
|
uploadInProgress: [],
|
|
pausedForUpload: [],
|
|
disposedForUpload: [],
|
|
};
|
|
|
|
private monitorServiceStartQueue: {
|
|
monitorID: string;
|
|
serviceStartParams: [Board, Port];
|
|
connectToClient: (status: Status) => void;
|
|
}[] = [];
|
|
|
|
@inject(MonitorServiceFactory)
|
|
private monitorServiceFactory: MonitorServiceFactory;
|
|
|
|
constructor(
|
|
@inject(ILogger)
|
|
@named(MonitorManagerName)
|
|
protected readonly logger: ILogger
|
|
) {
|
|
super();
|
|
}
|
|
|
|
/**
|
|
* Used to know if a monitor is started
|
|
* @param board board connected to port
|
|
* @param port port to monitor
|
|
* @returns true if the monitor is currently monitoring the board/port
|
|
* combination specifed, false in all other cases.
|
|
*/
|
|
isStarted(board: Board, port: Port): boolean {
|
|
const monitorID = this.monitorID(board, port);
|
|
const monitor = this.monitorServices.get(monitorID);
|
|
if (monitor) {
|
|
return monitor.isStarted();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private uploadIsInProgress(): boolean {
|
|
return this.monitorIDsByUploadState.uploadInProgress.length > 0;
|
|
}
|
|
|
|
private addToMonitorIDsByUploadState(
|
|
state: UploadState,
|
|
monitorID: string
|
|
): void {
|
|
this.monitorIDsByUploadState[state].push(monitorID);
|
|
}
|
|
|
|
private removeFromMonitorIDsByUploadState(
|
|
state: UploadState,
|
|
monitorID: string
|
|
): void {
|
|
this.monitorIDsByUploadState[state] = this.monitorIDsByUploadState[
|
|
state
|
|
].filter((id) => id !== monitorID);
|
|
}
|
|
|
|
private monitorIDIsInUploadState(
|
|
state: UploadState,
|
|
monitorID: string
|
|
): boolean {
|
|
return this.monitorIDsByUploadState[state].includes(monitorID);
|
|
}
|
|
|
|
/**
|
|
* Start a pluggable monitor that receives and sends messages
|
|
* to the specified board and port combination.
|
|
* @param board board connected to port
|
|
* @param port port to monitor
|
|
* @returns a Status object to know if the process has been
|
|
* started or if there have been errors.
|
|
*/
|
|
async startMonitor(
|
|
board: Board,
|
|
port: Port,
|
|
connectToClient: (status: Status) => void
|
|
): Promise<void> {
|
|
const monitorID = this.monitorID(board, port);
|
|
|
|
let monitor = this.monitorServices.get(monitorID);
|
|
if (!monitor) {
|
|
monitor = this.createMonitor(board, port);
|
|
}
|
|
|
|
if (this.uploadIsInProgress()) {
|
|
this.monitorServiceStartQueue = this.monitorServiceStartQueue.filter(
|
|
(request) => request.monitorID !== monitorID
|
|
);
|
|
|
|
this.monitorServiceStartQueue.push({
|
|
monitorID,
|
|
serviceStartParams: [board, port],
|
|
connectToClient,
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
const result = await monitor.start();
|
|
connectToClient(result);
|
|
}
|
|
|
|
/**
|
|
* Stop a pluggable monitor connected to the specified board/port
|
|
* combination. It's a noop if monitor is not running.
|
|
* @param board board connected to port
|
|
* @param port port monitored
|
|
*/
|
|
async stopMonitor(board: Board, port: Port): Promise<void> {
|
|
const monitorID = this.monitorID(board, port);
|
|
const monitor = this.monitorServices.get(monitorID);
|
|
if (!monitor) {
|
|
// There's no monitor to stop, bail
|
|
return;
|
|
}
|
|
return await monitor.stop();
|
|
}
|
|
|
|
/**
|
|
* Returns the port of the WebSocket used by the MonitorService
|
|
* that is handling the board/port combination
|
|
* @param board board connected to port
|
|
* @param port port to monitor
|
|
* @returns port of the MonitorService's WebSocket
|
|
*/
|
|
getWebsocketAddressPort(board: Board, port: Port): number {
|
|
const monitorID = this.monitorID(board, port);
|
|
const monitor = this.monitorServices.get(monitorID);
|
|
if (!monitor) {
|
|
return -1;
|
|
}
|
|
return monitor.getWebsocketAddressPort();
|
|
}
|
|
|
|
/**
|
|
* Notifies the monitor service of that board/port combination
|
|
* that an upload process started on that exact board/port combination.
|
|
* This must be done so that we can stop the monitor for the time being
|
|
* until the upload process finished.
|
|
* @param board board connected to port
|
|
* @param port port to monitor
|
|
*/
|
|
async notifyUploadStarted(board?: Board, port?: Port): Promise<void> {
|
|
if (!board || !port) {
|
|
// We have no way of knowing which monitor
|
|
// to retrieve if we don't have this information.
|
|
return;
|
|
}
|
|
|
|
const monitorID = this.monitorID(board, port);
|
|
this.addToMonitorIDsByUploadState('uploadInProgress', monitorID);
|
|
|
|
const monitor = this.monitorServices.get(monitorID);
|
|
if (!monitor) {
|
|
// There's no monitor running there, bail
|
|
return;
|
|
}
|
|
|
|
this.addToMonitorIDsByUploadState('pausedForUpload', monitorID);
|
|
return monitor.pause();
|
|
}
|
|
|
|
/**
|
|
* Notifies the monitor service of that board/port combination
|
|
* that an upload process started on that exact board/port combination.
|
|
* @param board board connected to port
|
|
* @param port port to monitor
|
|
* @returns a Status object to know if the process has been
|
|
* started or if there have been errors.
|
|
*/
|
|
async notifyUploadFinished(board?: Board, port?: Port): Promise<Status> {
|
|
let status: Status = Status.NOT_CONNECTED;
|
|
let portDidChangeOnUpload = false;
|
|
|
|
// We have no way of knowing which monitor
|
|
// to retrieve if we don't have this information.
|
|
if (board && port) {
|
|
const monitorID = this.monitorID(board, port);
|
|
this.removeFromMonitorIDsByUploadState('uploadInProgress', monitorID);
|
|
|
|
const monitor = this.monitorServices.get(monitorID);
|
|
if (monitor) {
|
|
status = await monitor.start();
|
|
}
|
|
|
|
// this monitorID will only be present in "disposedForUpload"
|
|
// if the upload changed the board port
|
|
portDidChangeOnUpload = this.monitorIDIsInUploadState(
|
|
'disposedForUpload',
|
|
monitorID
|
|
);
|
|
if (portDidChangeOnUpload) {
|
|
this.removeFromMonitorIDsByUploadState('disposedForUpload', monitorID);
|
|
}
|
|
|
|
// in case a service was paused but not disposed
|
|
this.removeFromMonitorIDsByUploadState('pausedForUpload', monitorID);
|
|
}
|
|
|
|
await this.startQueuedServices(portDidChangeOnUpload);
|
|
return status;
|
|
}
|
|
|
|
async startQueuedServices(portDidChangeOnUpload: boolean): Promise<void> {
|
|
// if the port changed during upload with the monitor open, "startMonitorPendingRequests"
|
|
// will include a request for our "upload port', most likely at index 0.
|
|
// We remove it, as this port was to be used exclusively for the upload
|
|
const queued = portDidChangeOnUpload
|
|
? this.monitorServiceStartQueue.slice(1)
|
|
: this.monitorServiceStartQueue;
|
|
this.monitorServiceStartQueue = [];
|
|
|
|
for (const {
|
|
monitorID,
|
|
serviceStartParams: [_, port],
|
|
connectToClient,
|
|
} of queued) {
|
|
const boardsState = await this.boardsService.getState();
|
|
const boardIsStillOnPort = Object.keys(boardsState)
|
|
.map((connection: string) => {
|
|
const portAddress = connection.split('|')[0];
|
|
return portAddress;
|
|
})
|
|
.some((portAddress: string) => port.address === portAddress);
|
|
|
|
if (boardIsStillOnPort) {
|
|
const monitorService = this.monitorServices.get(monitorID);
|
|
|
|
if (monitorService) {
|
|
const result = await monitorService.start();
|
|
connectToClient(result);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Changes the settings of a pluggable monitor even if it's running.
|
|
* If monitor is not running they're going to be used as soon as it's started.
|
|
* @param board board connected to port
|
|
* @param port port to monitor
|
|
* @param settings monitor settings to change
|
|
*/
|
|
changeMonitorSettings(
|
|
board: Board,
|
|
port: Port,
|
|
settings: PluggableMonitorSettings
|
|
) {
|
|
const monitorID = this.monitorID(board, port);
|
|
let monitor = this.monitorServices.get(monitorID);
|
|
if (!monitor) {
|
|
monitor = this.createMonitor(board, port);
|
|
monitor.changeSettings(settings);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the settings currently used by the pluggable monitor
|
|
* that's communicating with the specified board/port combination.
|
|
* @param board board connected to port
|
|
* @param port port monitored
|
|
* @returns map of current monitor settings
|
|
*/
|
|
async currentMonitorSettings(
|
|
board: Board,
|
|
port: Port
|
|
): Promise<MonitorSettings> {
|
|
const monitorID = this.monitorID(board, port);
|
|
const monitor = this.monitorServices.get(monitorID);
|
|
if (!monitor) {
|
|
return {};
|
|
}
|
|
return monitor.currentSettings();
|
|
}
|
|
|
|
/**
|
|
* Creates a MonitorService that handles the lifetime and the
|
|
* communication via WebSocket with the frontend.
|
|
* @param board board connected to specified port
|
|
* @param port port to monitor
|
|
* @returns a new instance of MonitorService ready to use.
|
|
*/
|
|
private createMonitor(board: Board, port: Port): MonitorService {
|
|
const monitorID = this.monitorID(board, port);
|
|
const monitor = this.monitorServiceFactory({
|
|
board,
|
|
port,
|
|
monitorID,
|
|
coreClientProvider: this.coreClientProvider,
|
|
});
|
|
this.monitorServices.set(monitorID, monitor);
|
|
monitor.onDispose(
|
|
(() => {
|
|
// if a service is disposed during upload and
|
|
// we paused it beforehand we know it was disposed
|
|
// of because the upload changed the board port
|
|
if (
|
|
this.uploadIsInProgress() &&
|
|
this.monitorIDIsInUploadState('pausedForUpload', monitorID)
|
|
) {
|
|
this.removeFromMonitorIDsByUploadState('pausedForUpload', monitorID);
|
|
|
|
this.addToMonitorIDsByUploadState('disposedForUpload', monitorID);
|
|
}
|
|
|
|
this.monitorServices.delete(monitorID);
|
|
}).bind(this)
|
|
);
|
|
return monitor;
|
|
}
|
|
|
|
/**
|
|
* Utility function to create a unique ID for a monitor service.
|
|
* @param board
|
|
* @param port
|
|
* @returns a unique monitor ID
|
|
*/
|
|
private monitorID(board: Board, port: Port): MonitorID {
|
|
const splitFqbn = board?.fqbn?.split(':') || [];
|
|
const shortenedFqbn = splitFqbn.slice(0, 3).join(':') || '';
|
|
return `${shortenedFqbn}-${port.address}-${port.protocol}`;
|
|
}
|
|
}
|