mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-11-08 09:58:34 +00:00
Make tab width 2 spaces (#445)
This commit is contained in:
@@ -11,15 +11,15 @@ import { FileUri } from '@theia/core/lib/node/file-uri';
|
||||
import { Event, Emitter } from '@theia/core/lib/common/event';
|
||||
import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application';
|
||||
import {
|
||||
ConfigService,
|
||||
Config,
|
||||
NotificationServiceServer,
|
||||
Network,
|
||||
ConfigService,
|
||||
Config,
|
||||
NotificationServiceServer,
|
||||
Network,
|
||||
} from '../common/protocol';
|
||||
import { spawnCommand } from './exec-util';
|
||||
import {
|
||||
MergeRequest,
|
||||
WriteRequest,
|
||||
MergeRequest,
|
||||
WriteRequest,
|
||||
} from './cli-protocol/cc/arduino/cli/settings/v1/settings_pb';
|
||||
import { SettingsServiceClient } from './cli-protocol/cc/arduino/cli/settings/v1/settings_grpc_pb';
|
||||
import * as serviceGrpcPb from './cli-protocol/cc/arduino/cli/settings/v1/settings_grpc_pb';
|
||||
@@ -34,273 +34,268 @@ const track = temp.track();
|
||||
|
||||
@injectable()
|
||||
export class ConfigServiceImpl
|
||||
implements BackendApplicationContribution, ConfigService
|
||||
implements BackendApplicationContribution, ConfigService
|
||||
{
|
||||
@inject(ILogger)
|
||||
@named('config')
|
||||
protected readonly logger: ILogger;
|
||||
@inject(ILogger)
|
||||
@named('config')
|
||||
protected readonly logger: ILogger;
|
||||
|
||||
@inject(EnvVariablesServer)
|
||||
protected readonly envVariablesServer: EnvVariablesServer;
|
||||
@inject(EnvVariablesServer)
|
||||
protected readonly envVariablesServer: EnvVariablesServer;
|
||||
|
||||
@inject(ArduinoDaemonImpl)
|
||||
protected readonly daemon: ArduinoDaemonImpl;
|
||||
@inject(ArduinoDaemonImpl)
|
||||
protected readonly daemon: ArduinoDaemonImpl;
|
||||
|
||||
@inject(NotificationServiceServer)
|
||||
protected readonly notificationService: NotificationServiceServer;
|
||||
@inject(NotificationServiceServer)
|
||||
protected readonly notificationService: NotificationServiceServer;
|
||||
|
||||
protected config: Config;
|
||||
protected cliConfig: DefaultCliConfig | undefined;
|
||||
protected ready = new Deferred<void>();
|
||||
protected readonly configChangeEmitter = new Emitter<Config>();
|
||||
protected config: Config;
|
||||
protected cliConfig: DefaultCliConfig | undefined;
|
||||
protected ready = new Deferred<void>();
|
||||
protected readonly configChangeEmitter = new Emitter<Config>();
|
||||
|
||||
async onStart(): Promise<void> {
|
||||
await this.ensureCliConfigExists();
|
||||
this.cliConfig = await this.loadCliConfig();
|
||||
if (this.cliConfig) {
|
||||
const config = await this.mapCliConfigToAppConfig(this.cliConfig);
|
||||
if (config) {
|
||||
this.config = config;
|
||||
this.ready.resolve();
|
||||
return;
|
||||
}
|
||||
async onStart(): Promise<void> {
|
||||
await this.ensureCliConfigExists();
|
||||
this.cliConfig = await this.loadCliConfig();
|
||||
if (this.cliConfig) {
|
||||
const config = await this.mapCliConfigToAppConfig(this.cliConfig);
|
||||
if (config) {
|
||||
this.config = config;
|
||||
this.ready.resolve();
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.fireInvalidConfig();
|
||||
}
|
||||
|
||||
async getCliConfigFileUri(): Promise<string> {
|
||||
const configDirUri = await this.envVariablesServer.getConfigDirUri();
|
||||
return new URI(configDirUri).resolve(CLI_CONFIG).toString();
|
||||
}
|
||||
|
||||
async getConfiguration(): Promise<Config> {
|
||||
await this.ready.promise;
|
||||
return this.config;
|
||||
}
|
||||
|
||||
async setConfiguration(config: Config): Promise<void> {
|
||||
await this.ready.promise;
|
||||
if (Config.sameAs(this.config, config)) {
|
||||
return;
|
||||
}
|
||||
let copyDefaultCliConfig: DefaultCliConfig | undefined = deepClone(
|
||||
this.cliConfig
|
||||
);
|
||||
if (!copyDefaultCliConfig) {
|
||||
copyDefaultCliConfig = await this.getFallbackCliConfig();
|
||||
}
|
||||
const {
|
||||
additionalUrls,
|
||||
dataDirUri,
|
||||
downloadsDirUri,
|
||||
sketchDirUri,
|
||||
network,
|
||||
} = config;
|
||||
copyDefaultCliConfig.directories = {
|
||||
data: FileUri.fsPath(dataDirUri),
|
||||
downloads: FileUri.fsPath(downloadsDirUri),
|
||||
user: FileUri.fsPath(sketchDirUri),
|
||||
};
|
||||
copyDefaultCliConfig.board_manager = {
|
||||
additional_urls: [...additionalUrls],
|
||||
};
|
||||
const proxy = Network.stringify(network);
|
||||
copyDefaultCliConfig.network = { proxy };
|
||||
const { port } = copyDefaultCliConfig.daemon;
|
||||
await this.updateDaemon(port, copyDefaultCliConfig);
|
||||
await this.writeDaemonState(port);
|
||||
|
||||
this.config = deepClone(config);
|
||||
this.cliConfig = copyDefaultCliConfig;
|
||||
this.fireConfigChanged(this.config);
|
||||
}
|
||||
|
||||
get cliConfiguration(): DefaultCliConfig | undefined {
|
||||
return this.cliConfig;
|
||||
}
|
||||
|
||||
get onConfigChange(): Event<Config> {
|
||||
return this.configChangeEmitter.event;
|
||||
}
|
||||
|
||||
async getVersion(): Promise<
|
||||
Readonly<{ version: string; commit: string; status?: string }>
|
||||
> {
|
||||
return this.daemon.getVersion();
|
||||
}
|
||||
|
||||
async isInDataDir(uri: string): Promise<boolean> {
|
||||
return this.getConfiguration().then(({ dataDirUri }) =>
|
||||
new URI(dataDirUri).isEqualOrParent(new URI(uri))
|
||||
);
|
||||
}
|
||||
|
||||
async isInSketchDir(uri: string): Promise<boolean> {
|
||||
return this.getConfiguration().then(({ sketchDirUri }) =>
|
||||
new URI(sketchDirUri).isEqualOrParent(new URI(uri))
|
||||
);
|
||||
}
|
||||
|
||||
protected async loadCliConfig(): Promise<DefaultCliConfig | undefined> {
|
||||
const cliConfigFileUri = await this.getCliConfigFileUri();
|
||||
const cliConfigPath = FileUri.fsPath(cliConfigFileUri);
|
||||
try {
|
||||
const content = await promisify(fs.readFile)(cliConfigPath, {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
const model = yaml.safeLoad(content) || {};
|
||||
// The CLI can run with partial (missing `port`, `directories`), the app cannot, we merge the default with the user's config.
|
||||
const fallbackModel = await this.getFallbackCliConfig();
|
||||
return deepmerge(fallbackModel, model) as DefaultCliConfig;
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Error occurred when loading CLI config from ${cliConfigPath}.`,
|
||||
error
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected async getFallbackCliConfig(): Promise<DefaultCliConfig> {
|
||||
const cliPath = await this.daemon.getExecPath();
|
||||
const throwawayDirPath = await new Promise<string>((resolve, reject) => {
|
||||
track.mkdir({}, (err, dirPath) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
this.fireInvalidConfig();
|
||||
}
|
||||
resolve(dirPath);
|
||||
});
|
||||
});
|
||||
await spawnCommand(`"${cliPath}"`, [
|
||||
'config',
|
||||
'init',
|
||||
'--dest-dir',
|
||||
`"${throwawayDirPath}"`,
|
||||
]);
|
||||
const rawYaml = await promisify(fs.readFile)(
|
||||
path.join(throwawayDirPath, CLI_CONFIG),
|
||||
{ encoding: 'utf-8' }
|
||||
);
|
||||
const model = yaml.safeLoad(rawYaml.trim());
|
||||
return model as DefaultCliConfig;
|
||||
}
|
||||
|
||||
async getCliConfigFileUri(): Promise<string> {
|
||||
const configDirUri = await this.envVariablesServer.getConfigDirUri();
|
||||
return new URI(configDirUri).resolve(CLI_CONFIG).toString();
|
||||
}
|
||||
|
||||
async getConfiguration(): Promise<Config> {
|
||||
await this.ready.promise;
|
||||
return this.config;
|
||||
}
|
||||
|
||||
async setConfiguration(config: Config): Promise<void> {
|
||||
await this.ready.promise;
|
||||
if (Config.sameAs(this.config, config)) {
|
||||
return;
|
||||
}
|
||||
let copyDefaultCliConfig: DefaultCliConfig | undefined = deepClone(
|
||||
this.cliConfig
|
||||
protected async ensureCliConfigExists(): Promise<void> {
|
||||
const cliConfigFileUri = await this.getCliConfigFileUri();
|
||||
const cliConfigPath = FileUri.fsPath(cliConfigFileUri);
|
||||
let exists = await promisify(fs.exists)(cliConfigPath);
|
||||
if (!exists) {
|
||||
await this.initCliConfigTo(path.dirname(cliConfigPath));
|
||||
exists = await promisify(fs.exists)(cliConfigPath);
|
||||
if (!exists) {
|
||||
throw new Error(
|
||||
`Could not initialize the default CLI configuration file at ${cliConfigPath}.`
|
||||
);
|
||||
if (!copyDefaultCliConfig) {
|
||||
copyDefaultCliConfig = await this.getFallbackCliConfig();
|
||||
}
|
||||
const {
|
||||
additionalUrls,
|
||||
dataDirUri,
|
||||
downloadsDirUri,
|
||||
sketchDirUri,
|
||||
network,
|
||||
} = config;
|
||||
copyDefaultCliConfig.directories = {
|
||||
data: FileUri.fsPath(dataDirUri),
|
||||
downloads: FileUri.fsPath(downloadsDirUri),
|
||||
user: FileUri.fsPath(sketchDirUri),
|
||||
};
|
||||
copyDefaultCliConfig.board_manager = {
|
||||
additional_urls: [...additionalUrls],
|
||||
};
|
||||
const proxy = Network.stringify(network);
|
||||
copyDefaultCliConfig.network = { proxy };
|
||||
const { port } = copyDefaultCliConfig.daemon;
|
||||
await this.updateDaemon(port, copyDefaultCliConfig);
|
||||
await this.writeDaemonState(port);
|
||||
|
||||
this.config = deepClone(config);
|
||||
this.cliConfig = copyDefaultCliConfig;
|
||||
this.fireConfigChanged(this.config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get cliConfiguration(): DefaultCliConfig | undefined {
|
||||
return this.cliConfig;
|
||||
protected async initCliConfigTo(fsPathToDir: string): Promise<void> {
|
||||
const cliPath = await this.daemon.getExecPath();
|
||||
await spawnCommand(`"${cliPath}"`, [
|
||||
'config',
|
||||
'init',
|
||||
'--dest-dir',
|
||||
`"${fsPathToDir}"`,
|
||||
]);
|
||||
}
|
||||
|
||||
protected async mapCliConfigToAppConfig(
|
||||
cliConfig: DefaultCliConfig
|
||||
): Promise<Config> {
|
||||
const { directories } = cliConfig;
|
||||
const { data, user, downloads } = directories;
|
||||
const additionalUrls: Array<string> = [];
|
||||
if (cliConfig.board_manager && cliConfig.board_manager.additional_urls) {
|
||||
additionalUrls.push(
|
||||
...Array.from(new Set(cliConfig.board_manager.additional_urls))
|
||||
);
|
||||
}
|
||||
const network = Network.parse(cliConfig.network?.proxy);
|
||||
return {
|
||||
dataDirUri: FileUri.create(data).toString(),
|
||||
sketchDirUri: FileUri.create(user).toString(),
|
||||
downloadsDirUri: FileUri.create(downloads).toString(),
|
||||
additionalUrls,
|
||||
network,
|
||||
};
|
||||
}
|
||||
|
||||
get onConfigChange(): Event<Config> {
|
||||
return this.configChangeEmitter.event;
|
||||
}
|
||||
protected fireConfigChanged(config: Config): void {
|
||||
this.configChangeEmitter.fire(config);
|
||||
this.notificationService.notifyConfigChanged({ config });
|
||||
}
|
||||
|
||||
async getVersion(): Promise<
|
||||
Readonly<{ version: string; commit: string; status?: string }>
|
||||
> {
|
||||
return this.daemon.getVersion();
|
||||
}
|
||||
protected fireInvalidConfig(): void {
|
||||
this.notificationService.notifyConfigChanged({ config: undefined });
|
||||
}
|
||||
|
||||
async isInDataDir(uri: string): Promise<boolean> {
|
||||
return this.getConfiguration().then(({ dataDirUri }) =>
|
||||
new URI(dataDirUri).isEqualOrParent(new URI(uri))
|
||||
);
|
||||
}
|
||||
|
||||
async isInSketchDir(uri: string): Promise<boolean> {
|
||||
return this.getConfiguration().then(({ sketchDirUri }) =>
|
||||
new URI(sketchDirUri).isEqualOrParent(new URI(uri))
|
||||
);
|
||||
}
|
||||
|
||||
protected async loadCliConfig(): Promise<DefaultCliConfig | undefined> {
|
||||
const cliConfigFileUri = await this.getCliConfigFileUri();
|
||||
const cliConfigPath = FileUri.fsPath(cliConfigFileUri);
|
||||
protected async updateDaemon(
|
||||
port: string | number,
|
||||
config: DefaultCliConfig
|
||||
): Promise<void> {
|
||||
const client = this.createClient(port);
|
||||
const req = new MergeRequest();
|
||||
const json = JSON.stringify(config, null, 2);
|
||||
req.setJsonData(json);
|
||||
console.log(`Updating daemon with 'data': ${json}`);
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
client.merge(req, (error) => {
|
||||
try {
|
||||
const content = await promisify(fs.readFile)(cliConfigPath, {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
const model = yaml.safeLoad(content) || {};
|
||||
// The CLI can run with partial (missing `port`, `directories`), the app cannot, we merge the default with the user's config.
|
||||
const fallbackModel = await this.getFallbackCliConfig();
|
||||
return deepmerge(fallbackModel, model) as DefaultCliConfig;
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Error occurred when loading CLI config from ${cliConfigPath}.`,
|
||||
error
|
||||
);
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected async getFallbackCliConfig(): Promise<DefaultCliConfig> {
|
||||
const cliPath = await this.daemon.getExecPath();
|
||||
const throwawayDirPath = await new Promise<string>(
|
||||
(resolve, reject) => {
|
||||
track.mkdir({}, (err, dirPath) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(dirPath);
|
||||
});
|
||||
}
|
||||
);
|
||||
await spawnCommand(`"${cliPath}"`, [
|
||||
'config',
|
||||
'init',
|
||||
'--dest-dir',
|
||||
`"${throwawayDirPath}"`,
|
||||
]);
|
||||
const rawYaml = await promisify(fs.readFile)(
|
||||
path.join(throwawayDirPath, CLI_CONFIG),
|
||||
{ encoding: 'utf-8' }
|
||||
);
|
||||
const model = yaml.safeLoad(rawYaml.trim());
|
||||
return model as DefaultCliConfig;
|
||||
}
|
||||
|
||||
protected async ensureCliConfigExists(): Promise<void> {
|
||||
const cliConfigFileUri = await this.getCliConfigFileUri();
|
||||
const cliConfigPath = FileUri.fsPath(cliConfigFileUri);
|
||||
let exists = await promisify(fs.exists)(cliConfigPath);
|
||||
if (!exists) {
|
||||
await this.initCliConfigTo(path.dirname(cliConfigPath));
|
||||
exists = await promisify(fs.exists)(cliConfigPath);
|
||||
if (!exists) {
|
||||
throw new Error(
|
||||
`Could not initialize the default CLI configuration file at ${cliConfigPath}.`
|
||||
);
|
||||
}
|
||||
protected async writeDaemonState(port: string | number): Promise<void> {
|
||||
const client = this.createClient(port);
|
||||
const req = new WriteRequest();
|
||||
const cliConfigUri = await this.getCliConfigFileUri();
|
||||
const cliConfigPath = FileUri.fsPath(cliConfigUri);
|
||||
req.setFilePath(cliConfigPath);
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
client.write(req, (error) => {
|
||||
try {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected async initCliConfigTo(fsPathToDir: string): Promise<void> {
|
||||
const cliPath = await this.daemon.getExecPath();
|
||||
await spawnCommand(`"${cliPath}"`, [
|
||||
'config',
|
||||
'init',
|
||||
'--dest-dir',
|
||||
`"${fsPathToDir}"`,
|
||||
]);
|
||||
}
|
||||
|
||||
protected async mapCliConfigToAppConfig(
|
||||
cliConfig: DefaultCliConfig
|
||||
): Promise<Config> {
|
||||
const { directories } = cliConfig;
|
||||
const { data, user, downloads } = directories;
|
||||
const additionalUrls: Array<string> = [];
|
||||
if (
|
||||
cliConfig.board_manager &&
|
||||
cliConfig.board_manager.additional_urls
|
||||
) {
|
||||
additionalUrls.push(
|
||||
...Array.from(new Set(cliConfig.board_manager.additional_urls))
|
||||
);
|
||||
}
|
||||
const network = Network.parse(cliConfig.network?.proxy);
|
||||
return {
|
||||
dataDirUri: FileUri.create(data).toString(),
|
||||
sketchDirUri: FileUri.create(user).toString(),
|
||||
downloadsDirUri: FileUri.create(downloads).toString(),
|
||||
additionalUrls,
|
||||
network,
|
||||
};
|
||||
}
|
||||
|
||||
protected fireConfigChanged(config: Config): void {
|
||||
this.configChangeEmitter.fire(config);
|
||||
this.notificationService.notifyConfigChanged({ config });
|
||||
}
|
||||
|
||||
protected fireInvalidConfig(): void {
|
||||
this.notificationService.notifyConfigChanged({ config: undefined });
|
||||
}
|
||||
|
||||
protected async updateDaemon(
|
||||
port: string | number,
|
||||
config: DefaultCliConfig
|
||||
): Promise<void> {
|
||||
const client = this.createClient(port);
|
||||
const req = new MergeRequest();
|
||||
const json = JSON.stringify(config, null, 2);
|
||||
req.setJsonData(json);
|
||||
console.log(`Updating daemon with 'data': ${json}`);
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
client.merge(req, (error) => {
|
||||
try {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected async writeDaemonState(port: string | number): Promise<void> {
|
||||
const client = this.createClient(port);
|
||||
const req = new WriteRequest();
|
||||
const cliConfigUri = await this.getCliConfigFileUri();
|
||||
const cliConfigPath = FileUri.fsPath(cliConfigUri);
|
||||
req.setFilePath(cliConfigPath);
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
client.write(req, (error) => {
|
||||
try {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private createClient(port: string | number): SettingsServiceClient {
|
||||
// https://github.com/agreatfool/grpc_tools_node_protoc_ts/blob/master/doc/grpcjs_support.md#usage
|
||||
const SettingsServiceClient = grpc.makeClientConstructor(
|
||||
// @ts-expect-error: ignore
|
||||
serviceGrpcPb['cc.arduino.cli.settings.v1.SettingsService'],
|
||||
'SettingsServiceService'
|
||||
) as any;
|
||||
return new SettingsServiceClient(
|
||||
`localhost:${port}`,
|
||||
grpc.credentials.createInsecure()
|
||||
) as SettingsServiceClient;
|
||||
}
|
||||
private createClient(port: string | number): SettingsServiceClient {
|
||||
// https://github.com/agreatfool/grpc_tools_node_protoc_ts/blob/master/doc/grpcjs_support.md#usage
|
||||
const SettingsServiceClient = grpc.makeClientConstructor(
|
||||
// @ts-expect-error: ignore
|
||||
serviceGrpcPb['cc.arduino.cli.settings.v1.SettingsService'],
|
||||
'SettingsServiceService'
|
||||
) as any;
|
||||
return new SettingsServiceClient(
|
||||
`localhost:${port}`,
|
||||
grpc.credentials.createInsecure()
|
||||
) as SettingsServiceClient;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user