Skip to content

Commit a783d5b

Browse files
committed
Refactor remote sketchbook explorer
1 parent 4da5d57 commit a783d5b

14 files changed

+476
-538
lines changed

arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ import { CloudSketchbookCompositeWidget } from './widgets/cloud-sketchbook/cloud
236236
import { SketchbookWidget } from './widgets/sketchbook/sketchbook-widget';
237237
import { SketchbookTreeWidget } from './widgets/sketchbook/sketchbook-tree-widget';
238238
import { createSketchbookTreeWidget } from './widgets/sketchbook/sketchbook-tree-container';
239+
import { SketchCache } from './widgets/cloud-sketchbook/cloud-sketch-cache';
239240

240241
const ElementQueries = require('css-element-queries/src/ElementQueries');
241242

@@ -686,6 +687,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
686687
createCloudSketchbookTreeWidget(container)
687688
);
688689
bind(CreateApi).toSelf().inSingletonScope();
690+
bind(SketchCache).toSelf().inSingletonScope();
691+
689692
bind(ShareSketchDialog).toSelf().inSingletonScope();
690693
bind(AuthenticationClientService).toSelf().inSingletonScope();
691694
bind(CommandContribution).toService(AuthenticationClientService);

arduino-ide-extension/src/browser/create/create-api.ts

Lines changed: 52 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { injectable } from 'inversify';
1+
import { injectable, inject } from 'inversify';
22
import * as createPaths from './create-paths';
3-
import { posix, splitSketchPath } from './create-paths';
3+
import { posix } from './create-paths';
44
import { AuthenticationClientService } from '../auth/authentication-client-service';
55
import { ArduinoPreferences } from '../arduino-preferences';
6+
import { SketchCache } from '../widgets/cloud-sketchbook/cloud-sketch-cache';
67

78
export interface ResponseResultProvider {
89
(response: Response): Promise<any>;
@@ -15,10 +16,11 @@ export namespace ResponseResultProvider {
1516

1617
type ResourceType = 'f' | 'd';
1718

18-
export let sketchCache: Create.Sketch[] = [];
19-
2019
@injectable()
2120
export class CreateApi {
21+
@inject(SketchCache)
22+
protected readonly sketchCache: SketchCache;
23+
2224
protected authenticationService: AuthenticationClientService;
2325
protected arduinoPreferences: ArduinoPreferences;
2426

@@ -32,48 +34,24 @@ export class CreateApi {
3234
return this;
3335
}
3436

35-
public sketchCompareByPath = (param: string) => {
36-
return (sketch: Create.Sketch) => {
37-
const [, spath] = splitSketchPath(sketch.path);
38-
return param === spath;
39-
};
40-
};
41-
42-
async findSketchInCache(
43-
compareFn: (sketch: Create.Sketch) => boolean,
44-
trustCache = true
45-
): Promise<Create.Sketch | undefined> {
46-
const sketch = sketchCache.find((sketch) => compareFn(sketch));
47-
if (trustCache) {
48-
return Promise.resolve(sketch);
49-
}
50-
return await this.sketch({ id: sketch?.id });
37+
public wipeCache(): void {
38+
this.sketchCache.init();
5139
}
5240

5341
getSketchSecretStat(sketch: Create.Sketch): Create.Resource {
5442
return {
5543
href: `${sketch.href}${posix.sep}${Create.arduino_secrets_file}`,
5644
modified_at: sketch.modified_at,
45+
created_at: sketch.created_at,
5746
name: `${Create.arduino_secrets_file}`,
5847
path: `${sketch.path}${posix.sep}${Create.arduino_secrets_file}`,
5948
mimetype: 'text/x-c++src; charset=utf-8',
6049
type: 'file',
61-
sketchId: sketch.id,
6250
};
6351
}
6452

65-
async sketch(opt: {
66-
id?: string;
67-
path?: string;
68-
}): Promise<Create.Sketch | undefined> {
69-
let url;
70-
if (opt.id) {
71-
url = new URL(`${this.domain()}/sketches/byID/${opt.id}`);
72-
} else if (opt.path) {
73-
url = new URL(`${this.domain()}/sketches/byPath${opt.path}`);
74-
} else {
75-
return;
76-
}
53+
async sketch(id: string): Promise<Create.Sketch | undefined> {
54+
const url = new URL(`${this.domain()}/sketches/byID/${id}`);
7755

7856
url.searchParams.set('user_id', 'me');
7957
const headers = await this.headers();
@@ -92,7 +70,7 @@ export class CreateApi {
9270
method: 'GET',
9371
headers,
9472
});
95-
sketchCache = result.sketches;
73+
result.sketches.forEach((sketch) => this.sketchCache.addSketch(sketch));
9674
return result.sketches;
9775
}
9876

@@ -118,7 +96,7 @@ export class CreateApi {
11896

11997
async readDirectory(
12098
posixPath: string,
121-
options: { recursive?: boolean; match?: string; secrets?: boolean } = {}
99+
options: { recursive?: boolean; match?: string } = {}
122100
): Promise<Create.Resource[]> {
123101
const url = new URL(
124102
`${this.domain()}/files/d/$HOME/sketches_v2${posixPath}`
@@ -131,58 +109,21 @@ export class CreateApi {
131109
}
132110
const headers = await this.headers();
133111

134-
const sketchProm = options.secrets
135-
? this.sketches()
136-
: Promise.resolve(sketchCache);
137-
138-
return Promise.all([
139-
this.run<Create.RawResource[]>(url, {
140-
method: 'GET',
141-
headers,
142-
}),
143-
sketchProm,
144-
])
145-
.then(async ([result, sketches]) => {
146-
if (options.secrets) {
147-
// for every sketch with secrets, create a fake arduino_secrets.h
148-
result.forEach(async (res) => {
149-
if (res.type !== 'sketch') {
150-
return;
151-
}
152-
153-
const [, spath] = createPaths.splitSketchPath(res.path);
154-
const sketch = await this.findSketchInCache(
155-
this.sketchCompareByPath(spath)
156-
);
157-
if (sketch && sketch.secrets && sketch.secrets.length > 0) {
158-
result.push(this.getSketchSecretStat(sketch));
159-
}
160-
});
161-
162-
if (posixPath !== posix.sep) {
163-
const sketch = await this.findSketchInCache(
164-
this.sketchCompareByPath(posixPath)
165-
);
166-
if (sketch && sketch.secrets && sketch.secrets.length > 0) {
167-
result.push(this.getSketchSecretStat(sketch));
168-
}
112+
return this.run<Create.RawResource[]>(url, {
113+
method: 'GET',
114+
headers,
115+
})
116+
.then(async (result) => {
117+
// add arduino_secrets.h to the results, when reading a sketch main folder
118+
if (posixPath.length && posixPath !== posix.sep) {
119+
const sketch = this.sketchCache.getSketch(posixPath);
120+
121+
if (sketch && sketch.secrets && sketch.secrets.length > 0) {
122+
result.push(this.getSketchSecretStat(sketch));
169123
}
170124
}
171-
const sketchesMap: Record<string, Create.Sketch> = sketches.reduce(
172-
(prev, curr) => {
173-
return { ...prev, [curr.path]: curr };
174-
},
175-
{}
176-
);
177125

178-
// add the sketch id and isPublic to the resource
179-
return result.map((resource) => {
180-
return {
181-
...resource,
182-
sketchId: sketchesMap[resource.path]?.id || '',
183-
isPublic: sketchesMap[resource.path]?.is_public || false,
184-
};
185-
});
126+
return result;
186127
})
187128
.catch((reason) => {
188129
if (reason?.status === 404) return [] as Create.Resource[];
@@ -214,18 +155,16 @@ export class CreateApi {
214155

215156
let resources;
216157
if (basename === Create.arduino_secrets_file) {
217-
const sketch = await this.findSketchInCache(
218-
this.sketchCompareByPath(parentPosixPath)
219-
);
158+
const sketch = this.sketchCache.getSketch(parentPosixPath);
220159
resources = sketch ? [this.getSketchSecretStat(sketch)] : [];
221160
} else {
222161
resources = await this.readDirectory(parentPosixPath, {
223162
match: basename,
224163
});
225164
}
226-
227-
resources.sort((left, right) => left.path.length - right.path.length);
228-
const resource = resources.find(({ name }) => name === basename);
165+
const resource = resources.find(
166+
({ path }) => createPaths.splitSketchPath(path)[1] === posixPath
167+
);
229168
if (!resource) {
230169
throw new CreateError(`Not found: ${posixPath}.`, 404);
231170
}
@@ -248,10 +187,7 @@ export class CreateApi {
248187
return data;
249188
}
250189

251-
const sketch = await this.findSketchInCache((sketch) => {
252-
const [, spath] = splitSketchPath(sketch.path);
253-
return spath === createPaths.parentPosix(path);
254-
}, true);
190+
const sketch = this.sketchCache.getSketch(createPaths.parentPosix(path));
255191

256192
if (
257193
sketch &&
@@ -273,14 +209,25 @@ export class CreateApi {
273209

274210
if (basename === Create.arduino_secrets_file) {
275211
const parentPosixPath = createPaths.parentPosix(posixPath);
276-
const sketch = await this.findSketchInCache(
277-
this.sketchCompareByPath(parentPosixPath),
278-
false
279-
);
212+
213+
//retrieve the sketch id from the cache
214+
const cacheSketch = this.sketchCache.getSketch(parentPosixPath);
215+
if (!cacheSketch) {
216+
throw new Error(`Unable to find sketch ${parentPosixPath} in cache`);
217+
}
218+
219+
// get a fresh copy of the sketch in order to guarantee fresh secrets
220+
const sketch = await this.sketch(cacheSketch.id);
221+
if (!sketch) {
222+
throw new Error(
223+
`Unable to get a fresh copy of the sketch ${cacheSketch.id}`
224+
);
225+
}
226+
this.sketchCache.addSketch(sketch);
280227

281228
let file = '';
282229
if (sketch && sketch.secrets) {
283-
for (const item of sketch?.secrets) {
230+
for (const item of sketch.secrets) {
284231
file += `#define ${item.name} "${item.value}"\r\n`;
285232
}
286233
}
@@ -310,9 +257,9 @@ export class CreateApi {
310257

311258
if (basename === Create.arduino_secrets_file) {
312259
const parentPosixPath = createPaths.parentPosix(posixPath);
313-
const sketch = await this.findSketchInCache(
314-
this.sketchCompareByPath(parentPosixPath)
315-
);
260+
261+
const sketch = this.sketchCache.getSketch(parentPosixPath);
262+
316263
if (sketch) {
317264
const url = new URL(`${this.domain()}/sketches/${sketch.id}`);
318265
const headers = await this.headers();
@@ -357,8 +304,7 @@ export class CreateApi {
357304
};
358305

359306
// replace the sketch in the cache, so other calls will not overwrite each other
360-
sketchCache = sketchCache.filter((skt) => skt.id !== sketch.id);
361-
sketchCache.push({ ...sketch, secrets });
307+
this.sketchCache.addSketch(sketch);
362308

363309
const init = {
364310
method: 'POST',
@@ -543,8 +489,9 @@ export namespace Create {
543489
*/
544490
readonly path: string;
545491
readonly type: ResourceType;
546-
readonly sketchId: string;
492+
readonly sketchId?: string;
547493
readonly modified_at: string; // As an ISO-8601 formatted string: `YYYY-MM-DDTHH:mm:ss.sssZ`
494+
readonly created_at: string; // As an ISO-8601 formatted string: `YYYY-MM-DDTHH:mm:ss.sssZ`
548495
readonly children?: number; // For 'sketch' and 'folder' types.
549496
readonly size?: number; // For 'sketch' type only.
550497
readonly isPublic?: boolean; // For 'sketch' type only.

arduino-ide-extension/src/browser/create/create-fs-provider.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,7 @@ export class CreateFsProvider
106106

107107
async readdir(uri: URI): Promise<[string, FileType][]> {
108108
const resources = await this.getCreateApi.readDirectory(
109-
uri.path.toString(),
110-
{
111-
secrets: true,
112-
}
109+
uri.path.toString()
113110
);
114111
return resources
115112
.filter((res) => !REMOTE_ONLY_FILES.includes(res.name))

arduino-ide-extension/src/browser/local-cache/local-cache-fs-provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export class LocalCacheFsProvider
103103
});
104104
}
105105

106-
private get currentUserUri(): URI {
106+
public get currentUserUri(): URI {
107107
const { session } = this.authenticationService;
108108
if (!session) {
109109
throw new FileSystemProviderError(
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { FileStat } from '@theia/filesystem/lib/common/files';
2+
import { injectable } from 'inversify';
3+
import { Create } from '../../create/create-api';
4+
import { toPosixPath } from '../../create/create-paths';
5+
6+
@injectable()
7+
export class SketchCache {
8+
sketches: Record<string, Create.Sketch> = {};
9+
filestats: Record<string, FileStat> = {};
10+
11+
init(): void {
12+
// reset the data
13+
this.sketches = {};
14+
this.filestats = {};
15+
}
16+
17+
addItem(item: FileStat): void {
18+
this.filestats[item.resource.path.toString()] = item;
19+
}
20+
21+
getItem(path: string): FileStat | null {
22+
return this.filestats[path] || null;
23+
}
24+
25+
addSketch(sketch: Create.Sketch): void {
26+
const { path } = sketch;
27+
const posixPath = toPosixPath(path);
28+
this.sketches[posixPath] = sketch;
29+
}
30+
31+
getSketch(path: string): Create.Sketch | null {
32+
return this.sketches[path] || null;
33+
}
34+
}

arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-composite-widget.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ export class CloudSketchbookCompositeWidget extends BaseWidget {
3535
this.id = 'cloud-sketchbook-composite-widget';
3636
}
3737

38+
public getTreeWidget(): CloudSketchbookTreeWidget {
39+
return this.cloudSketchbookTreeWidget;
40+
}
41+
3842
protected onAfterAttach(message: Message): void {
3943
super.onAfterAttach(message);
4044
Widget.attach(this.cloudSketchbookTreeWidget, this.compositeNode);

arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,11 @@ export class CloudSketchbookContribution extends Contribution {
166166
isEnabled: (arg) =>
167167
CloudSketchbookCommands.Arg.is(arg) &&
168168
CloudSketchbookTree.CloudSketchDirNode.is(arg.node) &&
169-
!!arg.node.synced,
169+
CloudSketchbookTree.CloudSketchTreeNode.isSynced(arg.node),
170170
isVisible: (arg) =>
171171
CloudSketchbookCommands.Arg.is(arg) &&
172172
CloudSketchbookTree.CloudSketchDirNode.is(arg.node) &&
173-
!!arg.node.synced,
173+
CloudSketchbookTree.CloudSketchTreeNode.isSynced(arg.node),
174174
});
175175

176176
registry.registerCommand(CloudSketchbookCommands.OPEN_IN_CLOUD_EDITOR, {
@@ -257,18 +257,10 @@ export class CloudSketchbookContribution extends Contribution {
257257

258258
const currentSketch = await this.sketchServiceClient.currentSketch();
259259

260-
const localUri = await arg.model.cloudSketchbookTree.localUri(
261-
arg.node
262-
);
263-
let underlying = null;
264-
if (arg.node && localUri) {
265-
underlying = await this.fileService.toUnderlyingResource(localUri);
266-
}
267-
268260
// disable the "open sketch" command for the current sketch and for those not in sync
269261
if (
270-
!underlying ||
271-
(currentSketch && currentSketch.uri === underlying.toString())
262+
!CloudSketchbookTree.CloudSketchTreeNode.isSynced(arg.node) ||
263+
(currentSketch && currentSketch.uri === arg.node.uri.toString())
272264
) {
273265
const placeholder = new PlaceholderMenuNode(
274266
SKETCHBOOKSYNC__CONTEXT__MAIN_GROUP,
@@ -284,7 +276,6 @@ export class CloudSketchbookContribution extends Contribution {
284276
)
285277
);
286278
} else {
287-
arg.node.uri = localUri;
288279
this.menuRegistry.registerMenuAction(
289280
SKETCHBOOKSYNC__CONTEXT__MAIN_GROUP,
290281
{

0 commit comments

Comments
 (0)