Skip to content

fix: try fetch the sketch by path if not in the cache #2010

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 58 additions & 3 deletions arduino-ide-extension/src/browser/create/create-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,30 @@ export class CreateApi {
return result;
}

/**
* `sketchPath` is not the POSIX path but the path with the user UUID, username, etc.
* See [Create.Resource#path](./typings.ts). If `cache` is `true` and a sketch exists with the path,
* the cache will be updated with the new state of the sketch.
*/
// TODO: no nulls in API
async sketchByPath(
sketchPath: string,
cache = false
): Promise<Create.Sketch | null> {
const url = new URL(`${this.domain()}/sketches/byPath/${sketchPath}`);
const headers = await this.headers();
const sketch = await this.run<Create.Sketch>(url, {
method: 'GET',
headers,
});
if (sketch && cache) {
this.sketchCache.addSketch(sketch);
const posixPath = createPaths.toPosixPath(sketch.path);
this.sketchCache.purgeByPath(posixPath);
}
return sketch;
}

async sketches(limit = 50): Promise<Create.Sketch[]> {
const url = new URL(`${this.domain()}/sketches`);
url.searchParams.set('user_id', 'me');
Expand Down Expand Up @@ -86,7 +110,11 @@ export class CreateApi {

async createSketch(
posixPath: string,
contentProvider: MaybePromise<string> = this.sketchesService.defaultInoContent()
contentProvider: MaybePromise<string> = this.sketchesService.defaultInoContent(),
payloadOverride: Record<
string,
string | boolean | number | Record<string, unknown>
> = {}
): Promise<Create.Sketch> {
const url = new URL(`${this.domain()}/sketches`);
const [headers, content] = await Promise.all([
Expand All @@ -97,6 +125,7 @@ export class CreateApi {
ino: btoa(content),
path: posixPath,
user_id: 'me',
...payloadOverride,
};
const init = {
method: 'PUT',
Expand Down Expand Up @@ -212,7 +241,17 @@ export class CreateApi {
return data;
}

const sketch = this.sketchCache.getSketch(createPaths.parentPosix(path));
const posixPath = createPaths.parentPosix(path);
let sketch = this.sketchCache.getSketch(posixPath);
// Workaround for https://github.com/arduino/arduino-ide/issues/1999.
if (!sketch) {
// Convert the ordinary sketch POSIX path to the Create path.
// For example, `/sketch_apr6a` will be transformed to `8a694e4b83878cc53472bd75ee928053:kittaakos/sketches_v2/sketch_apr6a`.
const createPathPrefix = this.sketchCache.createPathPrefix;
if (createPathPrefix) {
sketch = await this.sketchByPath(createPathPrefix + posixPath, true);
}
}

if (
sketch &&
Expand Down Expand Up @@ -448,13 +487,18 @@ export class CreateApi {
await this.run(url, init, ResponseResultProvider.NOOP);
}

private fetchCounter = 0;
private async run<T>(
requestInfo: URL,
init: RequestInit | undefined,
resultProvider: ResponseResultProvider = ResponseResultProvider.JSON
): Promise<T> {
console.debug(`HTTP ${init?.method}: ${requestInfo.toString()}`);
const fetchCount = `[${++this.fetchCounter}]`;
const fetchStart = performance.now();
const method = init?.method ? `${init.method}: ` : '';
const url = requestInfo.toString();
const response = await fetch(requestInfo.toString(), init);
const fetchEnd = performance.now();
if (!response.ok) {
let details: string | undefined = undefined;
try {
Expand All @@ -465,7 +509,18 @@ export class CreateApi {
const { statusText, status } = response;
throw new CreateError(statusText, status, details);
}
const parseStart = performance.now();
const result = await resultProvider(response);
const parseEnd = performance.now();
console.debug(
`HTTP ${fetchCount} ${method} ${url} [fetch: ${(
fetchEnd - fetchStart
).toFixed(2)} ms, parse: ${(parseEnd - parseStart).toFixed(
2
)} ms] body: ${
typeof result === 'string' ? result : JSON.stringify(result)
}`
);
return result;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { FileStat } from '@theia/filesystem/lib/common/files';
import { injectable } from '@theia/core/shared/inversify';
import { toPosixPath } from '../../create/create-paths';
import { splitSketchPath } from '../../create/create-paths';
import { Create } from '../../create/typings';

@injectable()
export class SketchCache {
sketches: Record<string, Create.Sketch> = {};
fileStats: Record<string, FileStat> = {};
private _createPathPrefix: string | undefined;

init(): void {
// reset the data
Expand All @@ -32,14 +33,21 @@ export class SketchCache {

addSketch(sketch: Create.Sketch): void {
const { path } = sketch;
const posixPath = toPosixPath(path);
const [pathPrefix, posixPath] = splitSketchPath(path);
if (pathPrefix !== this._createPathPrefix) {
this._createPathPrefix = pathPrefix;
}
this.sketches[posixPath] = sketch;
}

getSketch(path: string): Create.Sketch | null {
return this.sketches[path] || null;
}

get createPathPrefix(): string | undefined {
return this._createPathPrefix;
}

toString(): string {
return JSON.stringify({
sketches: this.sketches,
Expand Down
34 changes: 34 additions & 0 deletions arduino-ide-extension/src/test/browser/create-api.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Container, ContainerModule } from '@theia/core/shared/inversify';
import { assert, expect } from 'chai';
import fetch from 'cross-fetch';
import { posix } from 'path';
import { v4 } from 'uuid';
import { ArduinoPreferences } from '../../browser/arduino-preferences';
import { AuthenticationClientService } from '../../browser/auth/authentication-client-service';
Expand Down Expand Up @@ -251,6 +252,39 @@ describe('create-api', () => {
expect(sketch).to.be.undefined;
});
});

it("should fetch the sketch when transforming the 'secrets' into '#include' and the sketch is not in the cache", async () => {
const name = v4();
const posixPath = toPosix(name);
const newSketch = await createApi.createSketch(
posixPath,
'void setup(){} void loop(){}',
{
secrets: {
data: [
{
name: 'SECRET_THING',
value: '❤︎',
},
],
},
}
);
expect(newSketch).to.be.not.undefined;
expect(newSketch.secrets).to.be.not.undefined;
expect(Array.isArray(newSketch.secrets)).to.be.true;
expect(newSketch.secrets?.length).to.be.equal(1);
expect(newSketch.secrets?.[0]).to.be.deep.equal({
name: 'SECRET_THING',
value: '❤︎',
});
createApi.sketchCache.init(); // invalidate the cache
const content = await createApi.readFile(
posix.join(posixPath, `${name}.ino`)
);
expect(content.includes(`#include "${Create.arduino_secrets_file}"`)).to.be
.true;
});
});

// Using environment variables is recommended for testing but you can modify the module too.
Expand Down