@@ -14,6 +14,8 @@ import { ConfigService } from '../common/protocol/config-service';
14
14
import { SketchesService , Sketch } from '../common/protocol/sketches-service' ;
15
15
import { firstToLowerCase } from '../common/utils' ;
16
16
import { NotificationServiceServerImpl } from './notification-service-server' ;
17
+ import { EnvVariablesServer } from '@theia/core/lib/common/env-variables' ;
18
+ import { notEmpty } from '@theia/core' ;
17
19
18
20
// As currently implemented on Linux,
19
21
// the maximum number of symbolic links that will be followed while resolving a pathname is 40
@@ -33,7 +35,10 @@ export class SketchesServiceImpl implements SketchesService {
33
35
@inject ( NotificationServiceServerImpl )
34
36
protected readonly notificationService : NotificationServiceServerImpl ;
35
37
36
- async getSketches ( uri ?: string ) : Promise < Sketch [ ] > {
38
+ @inject ( EnvVariablesServer )
39
+ protected readonly envVariableServer : EnvVariablesServer ;
40
+
41
+ async getSketches ( uri ?: string ) : Promise < SketchWithDetails [ ] > {
37
42
let fsPath : undefined | string ;
38
43
if ( ! uri ) {
39
44
const { sketchDirUri } = await this . configService . getConfiguration ( ) ;
@@ -57,7 +62,7 @@ export class SketchesServiceImpl implements SketchesService {
57
62
/**
58
63
* Dev note: The keys are filesystem paths, not URI strings.
59
64
*/
60
- private sketchbooks = new Map < string , Sketch [ ] | Deferred < Sketch [ ] > > ( ) ;
65
+ private sketchbooks = new Map < string , SketchWithDetails [ ] | Deferred < SketchWithDetails [ ] > > ( ) ;
61
66
private fireSoonHandle ?: NodeJS . Timer ;
62
67
private bufferedSketchbookEvents : { type : 'created' | 'removed' , sketch : Sketch } [ ] = [ ] ;
63
68
@@ -88,7 +93,7 @@ export class SketchesServiceImpl implements SketchesService {
88
93
/**
89
94
* Assumes the `fsPath` points to an existing directory.
90
95
*/
91
- private async doGetSketches ( sketchbookPath : string ) : Promise < Sketch [ ] > {
96
+ private async doGetSketches ( sketchbookPath : string ) : Promise < SketchWithDetails [ ] > {
92
97
const resolvedSketches = this . sketchbooks . get ( sketchbookPath ) ;
93
98
if ( resolvedSketches ) {
94
99
if ( Array . isArray ( resolvedSketches ) ) {
@@ -97,9 +102,9 @@ export class SketchesServiceImpl implements SketchesService {
97
102
return resolvedSketches . promise ;
98
103
}
99
104
100
- const deferred = new Deferred < Sketch [ ] > ( ) ;
105
+ const deferred = new Deferred < SketchWithDetails [ ] > ( ) ;
101
106
this . sketchbooks . set ( sketchbookPath , deferred ) ;
102
- const sketches : Array < Sketch & { mtimeMs : number } > = [ ] ;
107
+ const sketches : Array < SketchWithDetails > = [ ] ;
103
108
const filenames = await fs . readdir ( sketchbookPath ) ;
104
109
for ( const fileName of filenames ) {
105
110
const filePath = path . join ( sketchbookPath , fileName ) ;
@@ -201,7 +206,7 @@ export class SketchesServiceImpl implements SketchesService {
201
206
* See: https://github.com/arduino/arduino-cli/issues/837
202
207
* Based on: https://github.com/arduino/arduino-cli/blob/eef3705c4afcba4317ec38b803d9ffce5dd59a28/arduino/builder/sketch.go#L100-L215
203
208
*/
204
- async loadSketch ( uri : string ) : Promise < Sketch > {
209
+ async loadSketch ( uri : string ) : Promise < SketchWithDetails > {
205
210
const sketchPath = FileUri . fsPath ( uri ) ;
206
211
const exists = await fs . exists ( sketchPath ) ;
207
212
if ( ! exists ) {
@@ -294,7 +299,80 @@ export class SketchesServiceImpl implements SketchesService {
294
299
295
300
}
296
301
297
- private newSketch ( sketchFolderPath : string , mainFilePath : string , allFilesPaths : string [ ] ) : Sketch {
302
+ private get recentSketchesFsPath ( ) : Promise < string > {
303
+ return this . envVariableServer . getConfigDirUri ( ) . then ( uri => path . join ( FileUri . fsPath ( uri ) , 'recent-sketches.json' ) ) ;
304
+ }
305
+
306
+ private async loadRecentSketches ( fsPath : string ) : Promise < Record < string , number > > {
307
+ let data : Record < string , number > = { } ;
308
+ try {
309
+ const raw = await fs . readFile ( fsPath , { encoding : 'utf8' } ) ;
310
+ data = JSON . parse ( raw ) ;
311
+ } catch { }
312
+ return data ;
313
+ }
314
+
315
+ async markAsRecentlyOpened ( uri : string ) : Promise < void > {
316
+ let sketch : Sketch | undefined = undefined ;
317
+ try {
318
+ sketch = await this . loadSketch ( uri ) ;
319
+ } catch {
320
+ return ;
321
+ }
322
+ if ( await this . isTemp ( sketch ) ) {
323
+ return ;
324
+ }
325
+
326
+ const fsPath = await this . recentSketchesFsPath ;
327
+ const data = await this . loadRecentSketches ( fsPath ) ;
328
+ const now = Date . now ( ) ;
329
+ data [ sketch . uri ] = now ;
330
+
331
+ let toDeleteUri : string | undefined = undefined ;
332
+ if ( Object . keys ( data ) . length > 10 ) {
333
+ let min = Number . MAX_SAFE_INTEGER ;
334
+ for ( const uri of Object . keys ( data ) ) {
335
+ if ( min > data [ uri ] ) {
336
+ min = data [ uri ] ;
337
+ toDeleteUri = uri ;
338
+ }
339
+ }
340
+ }
341
+
342
+ if ( toDeleteUri ) {
343
+ delete data [ toDeleteUri ] ;
344
+ }
345
+
346
+ await fs . writeFile ( fsPath , JSON . stringify ( data , null , 2 ) ) ;
347
+ this . recentlyOpenedSketches ( ) . then ( sketches => this . notificationService . notifyRecentSketchesChanged ( { sketches } ) ) ;
348
+ }
349
+
350
+ async recentlyOpenedSketches ( ) : Promise < Sketch [ ] > {
351
+ const configDirUri = await this . envVariableServer . getConfigDirUri ( ) ;
352
+ const fsPath = path . join ( FileUri . fsPath ( configDirUri ) , 'recent-sketches.json' ) ;
353
+ let data : Record < string , number > = { } ;
354
+ try {
355
+ const raw = await fs . readFile ( fsPath , { encoding : 'utf8' } ) ;
356
+ data = JSON . parse ( raw ) ;
357
+ } catch { }
358
+
359
+ const loadSketchSafe = ( uri : string ) => {
360
+ try {
361
+ return this . loadSketch ( uri ) ;
362
+ } catch {
363
+ return undefined ;
364
+ }
365
+ }
366
+
367
+ const sketches = await Promise . all ( Object . keys ( data )
368
+ . sort ( ( left , right ) => data [ right ] - data [ left ] )
369
+ . map ( loadSketchSafe )
370
+ . filter ( notEmpty ) ) ;
371
+
372
+ return sketches ;
373
+ }
374
+
375
+ private async newSketch ( sketchFolderPath : string , mainFilePath : string , allFilesPaths : string [ ] ) : Promise < SketchWithDetails > {
298
376
let mainFile : string | undefined ;
299
377
const paths = new Set < string > ( ) ;
300
378
for ( const p of allFilesPaths ) {
@@ -326,13 +404,15 @@ export class SketchesServiceImpl implements SketchesService {
326
404
additionalFiles . sort ( ) ;
327
405
otherSketchFiles . sort ( ) ;
328
406
407
+ const { mtimeMs } = await fs . lstat ( sketchFolderPath ) ;
329
408
return {
330
409
uri : FileUri . create ( sketchFolderPath ) . toString ( ) ,
331
410
mainFileUri : FileUri . create ( mainFile ) . toString ( ) ,
332
411
name : path . basename ( sketchFolderPath ) ,
333
412
additionalFileUris : additionalFiles . map ( p => FileUri . create ( p ) . toString ( ) ) ,
334
- otherSketchFileUris : otherSketchFiles . map ( p => FileUri . create ( p ) . toString ( ) )
335
- }
413
+ otherSketchFileUris : otherSketchFiles . map ( p => FileUri . create ( p ) . toString ( ) ) ,
414
+ mtimeMs
415
+ } ;
336
416
}
337
417
338
418
async cloneExample ( uri : string ) : Promise < Sketch > {
@@ -538,3 +618,8 @@ class SkipDir extends Error {
538
618
Object . setPrototypeOf ( this , SkipDir . prototype ) ;
539
619
}
540
620
}
621
+
622
+ interface SketchWithDetails extends Sketch {
623
+ readonly mtimeMs : number ;
624
+ }
625
+
0 commit comments