feat: introduced cloud state in sketchbook view

Closes #1879
Closes #1876
Closes #1899
Closes #1878

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
Akos Kitta
2023-02-15 18:03:37 +01:00
committed by Akos Kitta
parent b09ae48536
commit 0ab28266df
53 changed files with 1971 additions and 659 deletions

View File

@@ -1,4 +1,4 @@
import { Disposable } from '@theia/core/lib/common/disposable';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { Container } from '@theia/core/shared/inversify';
import { expect } from 'chai';
import { BoardSearch, BoardsService } from '../../common/protocol';
@@ -10,26 +10,18 @@ import {
describe('boards-service-impl', () => {
let boardService: BoardsService;
let toDispose: Disposable[] = [];
let toDispose: DisposableCollection;
before(async function () {
configureBackendApplicationConfigProvider();
this.timeout(20_000);
toDispose = [];
toDispose = new DisposableCollection();
const container = createContainer();
await start(container, toDispose);
boardService = container.get<BoardsService>(BoardsService);
});
after(() => {
let disposable = toDispose.pop();
while (disposable) {
try {
disposable?.dispose();
} catch {}
disposable = toDispose.pop();
}
});
after(() => toDispose.dispose());
describe('search', () => {
it('should run search', async function () {
@@ -37,7 +29,7 @@ describe('boards-service-impl', () => {
expect(result).is.not.empty;
});
it("should boost a result when 'types' includes 'arduino', and lower the score if deprecated", async function () {
it("should boost a result when 'types' includes 'arduino', and lower the score if deprecated", async () => {
const result = await boardService.search({});
const arduinoIndexes: number[] = [];
const otherIndexes: number[] = [];
@@ -108,7 +100,7 @@ function createContainer(): Container {
async function start(
container: Container,
toDispose: Disposable[]
toDispose: DisposableCollection
): Promise<void> {
return startDaemon(container, toDispose);
}

View File

@@ -1,6 +1,6 @@
import { CancellationTokenSource } from '@theia/core/lib/common/cancellation';
import { CommandRegistry } from '@theia/core/lib/common/command';
import { Disposable } from '@theia/core/lib/common/disposable';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { isWindows } from '@theia/core/lib/common/os';
import { FileUri } from '@theia/core/lib/node/file-uri';
import { Container, injectable } from '@theia/core/shared/inversify';
@@ -23,7 +23,7 @@ const uno = 'arduino:avr:uno';
describe('core-service-impl', () => {
let container: Container;
let toDispose: Disposable[];
let toDispose: DisposableCollection;
before(() => {
configureBackendApplicationConfigProvider();
@@ -31,20 +31,12 @@ describe('core-service-impl', () => {
beforeEach(async function () {
this.timeout(setupTimeout);
toDispose = [];
toDispose = new DisposableCollection();
container = createContainer();
await start(container, toDispose);
});
afterEach(() => {
let disposable = toDispose.pop();
while (disposable) {
try {
disposable?.dispose();
} catch {}
disposable = toDispose.pop();
}
});
afterEach(() => toDispose.dispose());
describe('compile', () => {
it('should execute a command with the build path', async function () {
@@ -92,7 +84,7 @@ describe('core-service-impl', () => {
async function start(
container: Container,
toDispose: Disposable[]
toDispose: DisposableCollection
): Promise<void> {
await startDaemon(container, toDispose, async (container) => {
const boardService = container.get<BoardsService>(BoardsService);

View File

@@ -1,4 +1,4 @@
import { Disposable } from '@theia/core/lib/common/disposable';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { Container } from '@theia/core/shared/inversify';
import { expect } from 'chai';
import { LibrarySearch, LibraryService } from '../../common/protocol';
@@ -11,26 +11,18 @@ import {
describe('library-service-impl', () => {
let libraryService: LibraryService;
let toDispose: Disposable[] = [];
let toDispose: DisposableCollection;
before(async function () {
configureBackendApplicationConfigProvider();
this.timeout(20_000);
toDispose = [];
toDispose = new DisposableCollection();
const container = createContainer();
await start(container, toDispose);
libraryService = container.get<LibraryService>(LibraryService);
});
after(() => {
let disposable = toDispose.pop();
while (disposable) {
try {
disposable?.dispose();
} catch {}
disposable = toDispose.pop();
}
});
after(() => toDispose.dispose());
describe('search', () => {
it('should run search', async function () {
@@ -89,7 +81,7 @@ function createContainer(): Container {
async function start(
container: Container,
toDispose: Disposable[]
toDispose: DisposableCollection
): Promise<void> {
return startDaemon(container, toDispose);
}

View File

@@ -0,0 +1,262 @@
import {
Disposable,
DisposableCollection,
} from '@theia/core/lib/common/disposable';
import { FileUri } from '@theia/core/lib/node/file-uri';
import { Container } from '@theia/core/shared/inversify';
import { expect } from 'chai';
import { promises as fs } from 'fs';
import { basename, join } from 'path';
import { sync as rimrafSync } from 'rimraf';
import { Sketch, SketchesService } from '../../common/protocol';
import { SketchesServiceImpl } from '../../node/sketches-service-impl';
import { ErrnoException } from '../../node/utils/errors';
import {
configureBackendApplicationConfigProvider,
createBaseContainer,
startDaemon,
} from './test-bindings';
const testTimeout = 10_000;
describe('sketches-service-impl', () => {
let container: Container;
let toDispose: DisposableCollection;
before(async () => {
configureBackendApplicationConfigProvider();
toDispose = new DisposableCollection();
container = createContainer();
await start(container, toDispose);
});
after(() => toDispose.dispose());
describe('copy', () => {
it('should copy a sketch when the destination does not exist', async function () {
this.timeout(testTimeout);
const sketchesService =
container.get<SketchesServiceImpl>(SketchesService);
const destinationPath = await sketchesService['createTempFolder']();
let sketch = await sketchesService.createNewSketch();
toDispose.push(disposeSketch(sketch));
const sourcePath = FileUri.fsPath(sketch.uri);
const libBasename = 'lib.cpp';
const libContent = 'lib content';
const libPath = join(sourcePath, libBasename);
await fs.writeFile(libPath, libContent, { encoding: 'utf8' });
const headerBasename = 'header.h';
const headerContent = 'header content';
const headerPath = join(sourcePath, headerBasename);
await fs.writeFile(headerPath, headerContent, { encoding: 'utf8' });
sketch = await sketchesService.loadSketch(sketch.uri);
expect(Sketch.isInSketch(FileUri.create(libPath), sketch)).to.be.true;
expect(Sketch.isInSketch(FileUri.create(headerPath), sketch)).to.be.true;
const copied = await sketchesService.copy(sketch, {
destinationUri: FileUri.create(destinationPath).toString(),
});
toDispose.push(disposeSketch(copied));
expect(copied.name).to.be.equal(basename(destinationPath));
expect(
Sketch.isInSketch(
FileUri.create(
join(destinationPath, `${basename(destinationPath)}.ino`)
),
copied
)
).to.be.true;
expect(
Sketch.isInSketch(
FileUri.create(join(destinationPath, libBasename)),
copied
)
).to.be.true;
expect(
Sketch.isInSketch(
FileUri.create(join(destinationPath, headerBasename)),
copied
)
).to.be.true;
});
it("should copy only sketch files if 'onlySketchFiles' is true", async function () {
this.timeout(testTimeout);
const sketchesService =
container.get<SketchesServiceImpl>(SketchesService);
const destinationPath = await sketchesService['createTempFolder']();
let sketch = await sketchesService.createNewSketch();
toDispose.push(disposeSketch(sketch));
const sourcePath = FileUri.fsPath(sketch.uri);
const libBasename = 'lib.cpp';
const libContent = 'lib content';
const libPath = join(sourcePath, libBasename);
await fs.writeFile(libPath, libContent, { encoding: 'utf8' });
const headerBasename = 'header.h';
const headerContent = 'header content';
const headerPath = join(sourcePath, headerBasename);
await fs.writeFile(headerPath, headerContent, { encoding: 'utf8' });
const logBasename = 'inols-clangd-err.log';
const logContent = 'log file content';
const logPath = join(sourcePath, logBasename);
await fs.writeFile(logPath, logContent, { encoding: 'utf8' });
sketch = await sketchesService.loadSketch(sketch.uri);
expect(Sketch.isInSketch(FileUri.create(libPath), sketch)).to.be.true;
expect(Sketch.isInSketch(FileUri.create(headerPath), sketch)).to.be.true;
expect(Sketch.isInSketch(FileUri.create(logPath), sketch)).to.be.false;
const reloadedLogContent = await fs.readFile(logPath, {
encoding: 'utf8',
});
expect(reloadedLogContent).to.be.equal(logContent);
const copied = await sketchesService.copy(sketch, {
destinationUri: FileUri.create(destinationPath).toString(),
onlySketchFiles: true,
});
toDispose.push(disposeSketch(copied));
expect(copied.name).to.be.equal(basename(destinationPath));
expect(
Sketch.isInSketch(
FileUri.create(
join(destinationPath, `${basename(destinationPath)}.ino`)
),
copied
)
).to.be.true;
expect(
Sketch.isInSketch(
FileUri.create(join(destinationPath, libBasename)),
copied
)
).to.be.true;
expect(
Sketch.isInSketch(
FileUri.create(join(destinationPath, headerBasename)),
copied
)
).to.be.true;
expect(
Sketch.isInSketch(
FileUri.create(join(destinationPath, logBasename)),
copied
)
).to.be.false;
try {
await fs.readFile(join(destinationPath, logBasename), {
encoding: 'utf8',
});
expect.fail(
'Log file must not exist in the destination. Expected ENOENT when loading the log file.'
);
} catch (err) {
expect(ErrnoException.isENOENT(err)).to.be.true;
}
});
it('should copy sketch inside the sketch folder', async function () {
this.timeout(testTimeout);
const sketchesService =
container.get<SketchesServiceImpl>(SketchesService);
let sketch = await sketchesService.createNewSketch();
const destinationPath = join(FileUri.fsPath(sketch.uri), 'nested_copy');
toDispose.push(disposeSketch(sketch));
const sourcePath = FileUri.fsPath(sketch.uri);
const libBasename = 'lib.cpp';
const libContent = 'lib content';
const libPath = join(sourcePath, libBasename);
await fs.writeFile(libPath, libContent, { encoding: 'utf8' });
const headerBasename = 'header.h';
const headerContent = 'header content';
const headerPath = join(sourcePath, headerBasename);
await fs.writeFile(headerPath, headerContent, { encoding: 'utf8' });
sketch = await sketchesService.loadSketch(sketch.uri);
expect(Sketch.isInSketch(FileUri.create(libPath), sketch)).to.be.true;
expect(Sketch.isInSketch(FileUri.create(headerPath), sketch)).to.be.true;
const copied = await sketchesService.copy(sketch, {
destinationUri: FileUri.create(destinationPath).toString(),
});
toDispose.push(disposeSketch(copied));
expect(copied.name).to.be.equal(basename(destinationPath));
expect(
Sketch.isInSketch(
FileUri.create(
join(destinationPath, `${basename(destinationPath)}.ino`)
),
copied
)
).to.be.true;
expect(
Sketch.isInSketch(
FileUri.create(join(destinationPath, libBasename)),
copied
)
).to.be.true;
expect(
Sketch.isInSketch(
FileUri.create(join(destinationPath, headerBasename)),
copied
)
).to.be.true;
});
it('should copy sketch with overwrite when source and destination sketch folder names are the same', async function () {
this.timeout(testTimeout);
const sketchesService =
container.get<SketchesServiceImpl>(SketchesService);
const sketchFolderName = 'alma';
const contentOne = 'korte';
const contentTwo = 'szilva';
const [sketchOne, sketchTwo] = await Promise.all([
sketchesService.createNewSketch(sketchFolderName, contentOne),
sketchesService.createNewSketch(sketchFolderName, contentTwo),
]);
toDispose.push(disposeSketch(sketchOne, sketchTwo));
const [mainFileContentOne, mainFileContentTwo] = await Promise.all([
mainFileContentOf(sketchOne),
mainFileContentOf(sketchTwo),
]);
expect(mainFileContentOne).to.be.equal(contentOne);
expect(mainFileContentTwo).to.be.equal(contentTwo);
await sketchesService.copy(sketchOne, { destinationUri: sketchTwo.uri });
const [mainFileContentOneAfterCopy, mainFileContentTwoAfterCopy] =
await Promise.all([
mainFileContentOf(sketchOne),
mainFileContentOf(sketchTwo),
]);
expect(mainFileContentOneAfterCopy).to.be.equal(contentOne);
expect(mainFileContentTwoAfterCopy).to.be.equal(contentOne);
});
});
});
function disposeSketch(...sketch: Sketch[]): Disposable {
return new DisposableCollection(
...sketch
.map(({ uri }) => FileUri.fsPath(uri))
.map((path) =>
Disposable.create(() => rimrafSync(path, { maxBusyTries: 5 }))
)
);
}
async function mainFileContentOf(sketch: Sketch): Promise<string> {
return fs.readFile(FileUri.fsPath(sketch.mainFileUri), {
encoding: 'utf8',
});
}
async function start(
container: Container,
toDispose: DisposableCollection
): Promise<void> {
await startDaemon(container, toDispose);
}
function createContainer(): Container {
return createBaseContainer();
}

View File

@@ -4,7 +4,10 @@ import {
CommandService,
} from '@theia/core/lib/common/command';
import { bindContributionProvider } from '@theia/core/lib/common/contribution-provider';
import { Disposable } from '@theia/core/lib/common/disposable';
import {
Disposable,
DisposableCollection,
} from '@theia/core/lib/common/disposable';
import { EnvVariablesServer as TheiaEnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { ILogger, Loggable } from '@theia/core/lib/common/logger';
import { LogLevel } from '@theia/core/lib/common/logger-protocol';
@@ -289,18 +292,23 @@ export function createBaseContainer(
export async function startDaemon(
container: Container,
toDispose: Disposable[],
toDispose: DisposableCollection,
startCustomizations?: (
container: Container,
toDispose: Disposable[]
toDispose: DisposableCollection
) => Promise<void>
): Promise<void> {
const daemon = container.get<ArduinoDaemonImpl>(ArduinoDaemonImpl);
const configService = container.get<ConfigServiceImpl>(ConfigServiceImpl);
const coreClientProvider =
container.get<CoreClientProvider>(CoreClientProvider);
toDispose.push(Disposable.create(() => daemon.stop()));
configService.onStart();
daemon.onStart();
await waitForEvent(daemon.onDaemonStarted, 10_000);
await Promise.all([
waitForEvent(daemon.onDaemonStarted, 10_000),
coreClientProvider.client,
]);
if (startCustomizations) {
await startCustomizations(container, toDispose);
}