mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-06-17 09:36:33 +00:00

Co-authored-by: Mark Sujew <mark.sujew@typefox.io> Co-authored-by: Akos Kitta <a.kitta@arduino.cc> Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
191 lines
5.9 KiB
TypeScript
191 lines
5.9 KiB
TypeScript
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
|
import { join, basename } from 'path';
|
|
import * as fs from 'fs';
|
|
import { promisify } from 'util';
|
|
import { FileUri } from '@theia/core/lib/node/file-uri';
|
|
import { notEmpty } from '@theia/core/lib/common/objects';
|
|
import { Sketch, SketchContainer } from '../common/protocol/sketches-service';
|
|
import { SketchesServiceImpl } from './sketches-service-impl';
|
|
import { ExamplesService } from '../common/protocol/examples-service';
|
|
import {
|
|
LibraryLocation,
|
|
LibraryPackage,
|
|
LibraryService,
|
|
} from '../common/protocol';
|
|
import { ConfigServiceImpl } from './config-service-impl';
|
|
|
|
@injectable()
|
|
export class ExamplesServiceImpl implements ExamplesService {
|
|
@inject(SketchesServiceImpl)
|
|
protected readonly sketchesService: SketchesServiceImpl;
|
|
|
|
@inject(LibraryService)
|
|
protected readonly libraryService: LibraryService;
|
|
|
|
@inject(ConfigServiceImpl)
|
|
protected readonly configService: ConfigServiceImpl;
|
|
|
|
protected _all: SketchContainer[] | undefined;
|
|
|
|
@postConstruct()
|
|
protected init(): void {
|
|
this.builtIns();
|
|
}
|
|
|
|
async builtIns(): Promise<SketchContainer[]> {
|
|
if (this._all) {
|
|
return this._all;
|
|
}
|
|
const exampleRootPath = join(__dirname, '..', '..', 'Examples');
|
|
const exampleNames = await promisify(fs.readdir)(exampleRootPath);
|
|
this._all = await Promise.all(
|
|
exampleNames
|
|
.map((name) => join(exampleRootPath, name))
|
|
.map((path) => this.load(path))
|
|
);
|
|
return this._all;
|
|
}
|
|
|
|
// TODO: decide whether it makes sense to cache them. Keys should be: `fqbn` + version of containing core/library.
|
|
async installed({ fqbn }: { fqbn?: string }): Promise<{
|
|
user: SketchContainer[];
|
|
current: SketchContainer[];
|
|
any: SketchContainer[];
|
|
}> {
|
|
const user: SketchContainer[] = [];
|
|
const current: SketchContainer[] = [];
|
|
const any: SketchContainer[] = [];
|
|
const packages: LibraryPackage[] = await this.libraryService.list({
|
|
fqbn,
|
|
});
|
|
for (const pkg of packages) {
|
|
const container = await this.tryGroupExamples(pkg);
|
|
const { location } = pkg;
|
|
if (location === LibraryLocation.USER) {
|
|
user.push(container);
|
|
} else if (
|
|
location === LibraryLocation.PLATFORM_BUILTIN ||
|
|
LibraryLocation.REFERENCED_PLATFORM_BUILTIN
|
|
) {
|
|
current.push(container);
|
|
} else {
|
|
any.push(container);
|
|
}
|
|
}
|
|
return { user, current, any };
|
|
}
|
|
|
|
/**
|
|
* The CLI provides direct FS paths to the examples so that menus and menu groups cannot be built for the UI by traversing the
|
|
* folder hierarchy. This method tries to workaround it by falling back to the `installDirUri` and manually creating the
|
|
* location of the examples. Otherwise it creates the example container from the direct examples FS paths.
|
|
*/
|
|
protected async tryGroupExamples({
|
|
label,
|
|
exampleUris,
|
|
installDirUri,
|
|
}: LibraryPackage): Promise<SketchContainer> {
|
|
const paths = exampleUris.map((uri) => FileUri.fsPath(uri));
|
|
if (installDirUri) {
|
|
for (const example of [
|
|
'example',
|
|
'Example',
|
|
'EXAMPLE',
|
|
'examples',
|
|
'Examples',
|
|
'EXAMPLES',
|
|
]) {
|
|
const examplesPath = join(FileUri.fsPath(installDirUri), example);
|
|
const exists = await promisify(fs.exists)(examplesPath);
|
|
const isDir =
|
|
exists && (await promisify(fs.lstat)(examplesPath)).isDirectory();
|
|
if (isDir) {
|
|
const fileNames = await promisify(fs.readdir)(examplesPath);
|
|
const children: SketchContainer[] = [];
|
|
const sketches: Sketch[] = [];
|
|
for (const fileName of fileNames) {
|
|
const subPath = join(examplesPath, fileName);
|
|
const subIsDir = (await promisify(fs.lstat)(subPath)).isDirectory();
|
|
if (subIsDir) {
|
|
const sketch = await this.tryLoadSketch(subPath);
|
|
if (!sketch) {
|
|
const container = await this.load(subPath);
|
|
if (container.children.length || container.sketches.length) {
|
|
children.push(container);
|
|
}
|
|
} else {
|
|
sketches.push(sketch);
|
|
}
|
|
}
|
|
}
|
|
return {
|
|
label,
|
|
children,
|
|
sketches,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
const sketches = await Promise.all(
|
|
paths.map((path) => this.tryLoadSketch(path))
|
|
);
|
|
return {
|
|
label,
|
|
children: [],
|
|
sketches: sketches.filter(notEmpty),
|
|
};
|
|
}
|
|
|
|
// Built-ins are included inside the IDE.
|
|
protected async load(path: string): Promise<SketchContainer> {
|
|
if (!(await promisify(fs.exists)(path))) {
|
|
throw new Error('Examples are not available');
|
|
}
|
|
const stat = await promisify(fs.stat)(path);
|
|
if (!stat.isDirectory) {
|
|
throw new Error(`${path} is not a directory.`);
|
|
}
|
|
const names = await promisify(fs.readdir)(path);
|
|
const sketches: Sketch[] = [];
|
|
const children: SketchContainer[] = [];
|
|
for (const p of names.map((name) => join(path, name))) {
|
|
const stat = await promisify(fs.stat)(p);
|
|
if (stat.isDirectory()) {
|
|
const sketch = await this.tryLoadSketch(p);
|
|
if (sketch) {
|
|
sketches.push(sketch);
|
|
} else {
|
|
const child = await this.load(p);
|
|
children.push(child);
|
|
}
|
|
}
|
|
}
|
|
const label = basename(path);
|
|
return {
|
|
label,
|
|
children,
|
|
sketches,
|
|
};
|
|
}
|
|
|
|
protected async group(paths: string[]): Promise<Map<string, fs.Stats>> {
|
|
const map = new Map<string, fs.Stats>();
|
|
for (const path of paths) {
|
|
const stat = await promisify(fs.stat)(path);
|
|
map.set(path, stat);
|
|
}
|
|
return map;
|
|
}
|
|
|
|
protected async tryLoadSketch(path: string): Promise<Sketch | undefined> {
|
|
try {
|
|
const sketch = await this.sketchesService.loadSketch(
|
|
FileUri.create(path).toString()
|
|
);
|
|
return sketch;
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
}
|