mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-06-17 17:46:33 +00:00
Merge pull request #97 from bcmi-labs/robust_workspace_init
More robust workspace initialization
This commit is contained in:
commit
41bf1ce6dc
12
.vscode/launch.json
vendored
12
.vscode/launch.json
vendored
@ -4,6 +4,12 @@
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Attach by Process ID",
|
||||
"processId": "${command:PickProcess}"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
@ -33,7 +39,7 @@
|
||||
"${workspaceRoot}/electron-app/src-gen/backend/*.js",
|
||||
"${workspaceRoot}/electron-app/src-gen/frontend/*.js",
|
||||
"${workspaceRoot}/electron-app/lib/**/*.js",
|
||||
"${workspaceRoot}/arduino-ide-extension/*/lib/**/*.js"
|
||||
"${workspaceRoot}/arduino-ide-extension/lib/**/*.js"
|
||||
],
|
||||
"smartStep": true,
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
@ -63,7 +69,7 @@
|
||||
"outFiles": [
|
||||
"${workspaceRoot}/browser-app/src-gen/backend/*.js",
|
||||
"${workspaceRoot}/browser-app/lib/**/*.js",
|
||||
"${workspaceRoot}/arduino-ide-extension/*/lib/**/*.js"
|
||||
"${workspaceRoot}/arduino-ide-extension/lib/**/*.js"
|
||||
],
|
||||
"smartStep": true,
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
@ -88,7 +94,7 @@
|
||||
"outFiles": [
|
||||
"${workspaceRoot}/browser-app/src-gen/backend/*.js",
|
||||
"${workspaceRoot}/browser-app/lib/**/*.js",
|
||||
"${workspaceRoot}/arduino-ide-extension/*/lib/**/*.js"
|
||||
"${workspaceRoot}/arduino-ide-extension/lib/**/*.js"
|
||||
],
|
||||
"smartStep": true,
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
|
@ -198,7 +198,7 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
|
||||
}).inSingletonScope();
|
||||
|
||||
bind(ArduinoWorkspaceService).toSelf().inSingletonScope();
|
||||
rebind(WorkspaceService).to(ArduinoWorkspaceService).inSingletonScope();
|
||||
rebind(WorkspaceService).toService(ArduinoWorkspaceService);
|
||||
|
||||
const themeService = ThemeService.get();
|
||||
themeService.register(...ArduinoTheme.themes);
|
||||
|
@ -47,7 +47,7 @@ export class ArduinoWorkspaceRootResolver {
|
||||
}
|
||||
|
||||
protected isValid(uri: string): MaybePromise<boolean> {
|
||||
return this.options.isValid.bind(this)(uri);
|
||||
return this.options.isValid(uri);
|
||||
}
|
||||
|
||||
// Note: here, the `hash` was defined as new `URI(yourValidFsPath).path` so we have to map it to a valid FS path first.
|
||||
@ -59,8 +59,8 @@ export class ArduinoWorkspaceRootResolver {
|
||||
if (hash
|
||||
&& hash.length > 1
|
||||
&& hash.startsWith('#')) {
|
||||
const path = hash.slice(1); // Trim the leading `#`.
|
||||
return new URI(toUnix(path.slice(isWindows && hash.startsWith('/') ? 1 : 0))).withScheme('file').toString();
|
||||
const path = hash.slice(1); // Trim the leading `#`.
|
||||
return new URI(toUnix(path.slice(isWindows && hash.startsWith('/') ? 1 : 0))).withScheme('file').toString();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { MessageService } from '@theia/core';
|
||||
import { LabelProvider } from '@theia/core/lib/browser';
|
||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { ConfigService } from '../common/protocol/config-service';
|
||||
@ -21,26 +22,44 @@ export class ArduinoWorkspaceService extends WorkspaceService {
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
async getDefaultWorkspaceUri(): Promise<string | undefined> {
|
||||
const [hash, recentWorkspaces, recentSketches] = await Promise.all([
|
||||
window.location.hash,
|
||||
this.sketchService.getSketches().then(sketches => sketches.map(({ uri }) => uri)),
|
||||
this.server.getRecentWorkspaces()
|
||||
]);
|
||||
const toOpen = await new ArduinoWorkspaceRootResolver({
|
||||
isValid: this.isValid.bind(this)
|
||||
}).resolve({
|
||||
hash,
|
||||
recentWorkspaces,
|
||||
recentSketches
|
||||
});
|
||||
if (toOpen) {
|
||||
const { uri } = toOpen;
|
||||
await this.server.setMostRecentlyUsedWorkspace(uri);
|
||||
return toOpen.uri;
|
||||
@inject(MessageService)
|
||||
protected readonly messageService: MessageService;
|
||||
|
||||
private workspaceUri?: Promise<string | undefined>;
|
||||
|
||||
protected getDefaultWorkspaceUri(): Promise<string | undefined> {
|
||||
if (this.workspaceUri) {
|
||||
// Avoid creating a new sketch twice
|
||||
return this.workspaceUri;
|
||||
}
|
||||
const { sketchDirUri } = (await this.configService.getConfiguration());
|
||||
return (await this.sketchService.createNewSketch(sketchDirUri)).uri;
|
||||
this.workspaceUri = (async () => {
|
||||
try {
|
||||
const hash = window.location.hash;
|
||||
const [recentWorkspaces, recentSketches] = await Promise.all([
|
||||
this.server.getRecentWorkspaces(),
|
||||
this.sketchService.getSketches().then(sketches => sketches.map(s => s.uri))
|
||||
]);
|
||||
const toOpen = await new ArduinoWorkspaceRootResolver({
|
||||
isValid: this.isValid.bind(this)
|
||||
}).resolve({ hash, recentWorkspaces, recentSketches });
|
||||
if (toOpen) {
|
||||
const { uri } = toOpen;
|
||||
await this.server.setMostRecentlyUsedWorkspace(uri);
|
||||
return toOpen.uri;
|
||||
}
|
||||
const { sketchDirUri } = (await this.configService.getConfiguration());
|
||||
this.logger.info(`No valid workspace URI found. Creating new sketch in ${sketchDirUri}`)
|
||||
return (await this.sketchService.createNewSketch(sketchDirUri)).uri;
|
||||
} catch (err) {
|
||||
this.logger.fatal(`Failed to determine the sketch directory: ${err}`)
|
||||
this.messageService.error(
|
||||
'There was an error creating the sketch directory. ' +
|
||||
'See the log for more details. ' +
|
||||
'The application will probably not work as expected.')
|
||||
return super.getDefaultWorkspaceUri();
|
||||
}
|
||||
})();
|
||||
return this.workspaceUri;
|
||||
}
|
||||
|
||||
private async isValid(uri: string): Promise<boolean> {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { inject, injectable, postConstruct } from 'inversify';
|
||||
import { Diagnostic } from 'vscode-languageserver-types';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { ILogger } from '@theia/core';
|
||||
import { Marker } from '@theia/markers/lib/common/marker';
|
||||
import { ProblemManager } from '@theia/markers/lib/browser/problem/problem-manager';
|
||||
import { ConfigService } from '../../common/protocol/config-service';
|
||||
@ -10,12 +11,18 @@ export class ArduinoProblemManager extends ProblemManager {
|
||||
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
|
||||
@inject(ILogger)
|
||||
protected readonly logger: ILogger;
|
||||
|
||||
protected dataDirUri: URI | undefined;
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
super.init();
|
||||
this.configService.getConfiguration().then(({ dataDirUri }) => this.dataDirUri = new URI(dataDirUri));
|
||||
this.configService.getConfiguration()
|
||||
.then(({ dataDirUri }) => this.dataDirUri = new URI(dataDirUri))
|
||||
.catch(err => this.logger.error(`Failed to determine the data directory: ${err}`));
|
||||
}
|
||||
|
||||
setMarkers(uri: URI, owner: string, data: Diagnostic[]): Marker<Diagnostic>[] {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { inject, injectable, postConstruct } from 'inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { Title, Widget } from '@phosphor/widgets';
|
||||
import { ILogger } from '@theia/core';
|
||||
import { WidgetDecoration } from '@theia/core/lib/browser/widget-decoration';
|
||||
import { TabBarDecoratorService } from '@theia/core/lib/browser/shell/tab-bar-decorator';
|
||||
import { ConfigService } from '../../common/protocol/config-service';
|
||||
@ -11,12 +12,19 @@ export class ArduinoTabBarDecoratorService extends TabBarDecoratorService {
|
||||
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
|
||||
@inject(ILogger)
|
||||
protected readonly logger: ILogger;
|
||||
|
||||
|
||||
protected dataDirUri: URI | undefined;
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
super.init();
|
||||
this.configService.getConfiguration().then(({ dataDirUri }) => this.dataDirUri = new URI(dataDirUri));
|
||||
this.configService.getConfiguration()
|
||||
.then(({ dataDirUri }) => this.dataDirUri = new URI(dataDirUri))
|
||||
.catch(err => this.logger.error(`Failed to determine the data directory: ${err}`));
|
||||
}
|
||||
|
||||
getDecorations(title: Title<Widget>): WidgetDecoration.Data[] {
|
||||
|
@ -71,7 +71,7 @@ export class ArduinoDaemon implements BackendApplicationContribution {
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
this.isReady.resolve();
|
||||
if (!this.cliContribution.debugCli) {
|
||||
this.logger.info(`<<< The 'arduino-cli' daemon is up an running.`);
|
||||
this.logger.info(`<<< The 'arduino-cli' daemon is up and running.`);
|
||||
} else {
|
||||
this.logger.info(`Assuming the 'arduino-cli' already runs in debug mode.`);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { mkdirpSync, existsSync } from 'fs-extra';
|
||||
import * as fs from './fs-extra';
|
||||
import { injectable, inject, postConstruct } from 'inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { FileUri } from '@theia/core/lib/node/file-uri';
|
||||
@ -14,32 +14,35 @@ export class ConfigServiceImpl implements ConfigService {
|
||||
protected readonly config: Deferred<Config> = new Deferred();
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.cli.getDefaultConfig().then(config => {
|
||||
protected async init(): Promise<void> {
|
||||
try {
|
||||
const config = await this.cli.getDefaultConfig();
|
||||
const { dataDirUri, sketchDirUri } = config;
|
||||
for (const uri of [dataDirUri, sketchDirUri]) {
|
||||
const path = FileUri.fsPath(uri);
|
||||
if (!existsSync(path)) {
|
||||
mkdirpSync(path);
|
||||
if (!fs.existsSync(path)) {
|
||||
await fs.mkdirp(path);
|
||||
}
|
||||
}
|
||||
this.config.resolve(config);
|
||||
});
|
||||
} catch (err) {
|
||||
this.config.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getConfiguration(): Promise<Config> {
|
||||
getConfiguration(): Promise<Config> {
|
||||
return this.config.promise;
|
||||
}
|
||||
|
||||
async getVersion(): Promise<string> {
|
||||
getVersion(): Promise<string> {
|
||||
return this.cli.getVersion();
|
||||
}
|
||||
|
||||
async isInDataDir(uri: string): Promise<boolean> {
|
||||
isInDataDir(uri: string): Promise<boolean> {
|
||||
return this.getConfiguration().then(({ dataDirUri }) => new URI(dataDirUri).isEqualOrParent(new URI(uri)));
|
||||
}
|
||||
|
||||
async isInSketchDir(uri: string): Promise<boolean> {
|
||||
isInSketchDir(uri: string): Promise<boolean> {
|
||||
return this.getConfiguration().then(({ sketchDirUri }) => new URI(sketchDirUri).isEqualOrParent(new URI(uri)));
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,25 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { ILogger } from '@theia/core';
|
||||
import { DefaultWorkspaceServer } from '@theia/workspace/lib/node/default-workspace-server';
|
||||
import { ConfigService } from '../common/protocol/config-service';
|
||||
|
||||
@injectable()
|
||||
export class DefaultWorkspaceServerExt extends DefaultWorkspaceServer {
|
||||
|
||||
@inject(ConfigService) protected readonly configService: ConfigService;
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
|
||||
@inject(ILogger)
|
||||
protected readonly logger: ILogger;
|
||||
|
||||
protected async getWorkspaceURIFromCli(): Promise<string | undefined> {
|
||||
const config = await this.configService.getConfiguration();
|
||||
return config.sketchDirUri;
|
||||
try {
|
||||
const config = await this.configService.getConfiguration();
|
||||
return config.sketchDirUri;
|
||||
} catch (err) {
|
||||
this.logger.error(`Failed to determine the sketch directory: ${err}`);
|
||||
return super.getWorkspaceURIFromCli();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
36
arduino-ide-extension/src/node/fs-extra.ts
Normal file
36
arduino-ide-extension/src/node/fs-extra.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import * as fs from 'fs';
|
||||
import { promisify } from 'util';
|
||||
|
||||
export const existsSync = fs.existsSync;
|
||||
export const lstatSync = fs.lstatSync;
|
||||
export const readdirSync = fs.readdirSync;
|
||||
export const statSync = fs.statSync;
|
||||
export const writeFileSync = fs.writeFileSync;
|
||||
|
||||
export const exists = promisify(fs.exists);
|
||||
export const lstat = promisify(fs.lstat);
|
||||
export const readdir = promisify(fs.readdir);
|
||||
export const stat = promisify(fs.stat);
|
||||
export const writeFile = promisify(fs.writeFile);
|
||||
|
||||
export function mkdirp(path: string, timeout: number = 3000): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let timeoutHandle: NodeJS.Timeout;
|
||||
if (timeout > 0) {
|
||||
timeoutHandle = setTimeout(() => {
|
||||
reject(new Error(`Timeout of ${timeout} ms exceeded while trying to create the directory "${path}"`));
|
||||
}, timeout);
|
||||
}
|
||||
fs.mkdir(path, { recursive: true }, err => {
|
||||
clearTimeout(timeoutHandle);
|
||||
if (err)
|
||||
reject(err);
|
||||
else
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function mkdirpSync(path: string): void {
|
||||
fs.mkdirSync(path, { recursive: true });
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as fs from './fs-extra';
|
||||
import { FileUri } from '@theia/core/lib/node';
|
||||
import { ConfigService } from '../common/protocol/config-service';
|
||||
import { SketchesService, Sketch } from '../common/protocol/sketches-service';
|
||||
@ -18,7 +18,7 @@ export class SketchesServiceImpl implements SketchesService {
|
||||
const sketches: Array<Sketch & { mtimeMs: number }> = [];
|
||||
let fsPath: undefined | string;
|
||||
if (!uri) {
|
||||
const { sketchDirUri } = (await this.configService.getConfiguration());
|
||||
const { sketchDirUri } = await this.configService.getConfiguration();
|
||||
fsPath = FileUri.fsPath(sketchDirUri);
|
||||
if (!fs.existsSync(fsPath)) {
|
||||
await fs.mkdirp(fsPath);
|
||||
@ -29,11 +29,11 @@ export class SketchesServiceImpl implements SketchesService {
|
||||
if (!fs.existsSync(fsPath)) {
|
||||
return [];
|
||||
}
|
||||
const fileNames = fs.readdirSync(fsPath);
|
||||
const fileNames = await fs.readdir(fsPath);
|
||||
for (const fileName of fileNames) {
|
||||
const filePath = path.join(fsPath, fileName);
|
||||
if (await this.isSketchFolder(FileUri.create(filePath).toString())) {
|
||||
const stat = fs.statSync(filePath);
|
||||
const stat = await fs.stat(filePath);
|
||||
sketches.push({
|
||||
mtimeMs: stat.mtimeMs,
|
||||
name: fileName,
|
||||
@ -51,10 +51,9 @@ export class SketchesServiceImpl implements SketchesService {
|
||||
async getSketchFiles(uri: string): Promise<string[]> {
|
||||
const uris: string[] = [];
|
||||
const fsPath = FileUri.fsPath(uri);
|
||||
const stats = fs.lstatSync(fsPath);
|
||||
if (stats.isDirectory) {
|
||||
if (fs.lstatSync(fsPath).isDirectory()) {
|
||||
if (await this.isSketchFolder(uri)) {
|
||||
const fileNames = fs.readdirSync(fsPath);
|
||||
const fileNames = await fs.readdir(fsPath);
|
||||
for (const fileName of fileNames) {
|
||||
const filePath = path.join(fsPath, fileName);
|
||||
if (ALLOWED_FILE_EXTENSIONS.indexOf(path.extname(filePath)) !== -1
|
||||
@ -116,26 +115,13 @@ void loop() {
|
||||
|
||||
async isSketchFolder(uri: string): Promise<boolean> {
|
||||
const fsPath = FileUri.fsPath(uri);
|
||||
const exists = await fs.pathExists(fsPath);
|
||||
if (exists) {
|
||||
const stats = await fs.lstat(fsPath);
|
||||
if (stats.isDirectory()) {
|
||||
const basename = path.basename(fsPath);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
fs.readdir(fsPath, (error, files) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (files[i] === basename + '.ino') {
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
resolve(false);
|
||||
});
|
||||
})
|
||||
if (fs.existsSync(fsPath) && fs.lstatSync(fsPath).isDirectory()) {
|
||||
const basename = path.basename(fsPath);
|
||||
const files = await fs.readdir(fsPath);
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (files[i] === basename + '.ino') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -7,7 +7,7 @@
|
||||
"arduino-ide-extension": "file:../working-copy/arduino-ide-extension"
|
||||
},
|
||||
"resolutions": {
|
||||
"**/fs-extra": "^8.1.0"
|
||||
"**/fs-extra": "^4.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron-builder": "^21.2.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user