@@ -14,6 +14,8 @@ import { ConfigService } from '../common/protocol/config-service';
1414import { SketchesService , Sketch } from '../common/protocol/sketches-service' ;
1515import { firstToLowerCase } from '../common/utils' ;
1616import { NotificationServiceServerImpl } from './notification-service-server' ;
17+ import { EnvVariablesServer } from '@theia/core/lib/common/env-variables' ;
18+ import { notEmpty } from '@theia/core' ;
1719
1820// As currently implemented on Linux,
1921// 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 {
3335 @inject ( NotificationServiceServerImpl )
3436 protected readonly notificationService : NotificationServiceServerImpl ;
3537
36- async getSketches ( uri ?: string ) : Promise < Sketch [ ] > {
38+ @inject ( EnvVariablesServer )
39+ protected readonly envVariableServer : EnvVariablesServer ;
40+
41+ async getSketches ( uri ?: string ) : Promise < SketchWithDetails [ ] > {
3742 let fsPath : undefined | string ;
3843 if ( ! uri ) {
3944 const { sketchDirUri } = await this . configService . getConfiguration ( ) ;
@@ -57,7 +62,7 @@ export class SketchesServiceImpl implements SketchesService {
5762 /**
5863 * Dev note: The keys are filesystem paths, not URI strings.
5964 */
60- private sketchbooks = new Map < string , Sketch [ ] | Deferred < Sketch [ ] > > ( ) ;
65+ private sketchbooks = new Map < string , SketchWithDetails [ ] | Deferred < SketchWithDetails [ ] > > ( ) ;
6166 private fireSoonHandle ?: NodeJS . Timer ;
6267 private bufferedSketchbookEvents : { type : 'created' | 'removed' , sketch : Sketch } [ ] = [ ] ;
6368
@@ -88,7 +93,7 @@ export class SketchesServiceImpl implements SketchesService {
8893 /**
8994 * Assumes the `fsPath` points to an existing directory.
9095 */
91- private async doGetSketches ( sketchbookPath : string ) : Promise < Sketch [ ] > {
96+ private async doGetSketches ( sketchbookPath : string ) : Promise < SketchWithDetails [ ] > {
9297 const resolvedSketches = this . sketchbooks . get ( sketchbookPath ) ;
9398 if ( resolvedSketches ) {
9499 if ( Array . isArray ( resolvedSketches ) ) {
@@ -97,9 +102,9 @@ export class SketchesServiceImpl implements SketchesService {
97102 return resolvedSketches . promise ;
98103 }
99104
100- const deferred = new Deferred < Sketch [ ] > ( ) ;
105+ const deferred = new Deferred < SketchWithDetails [ ] > ( ) ;
101106 this . sketchbooks . set ( sketchbookPath , deferred ) ;
102- const sketches : Array < Sketch & { mtimeMs : number } > = [ ] ;
107+ const sketches : Array < SketchWithDetails > = [ ] ;
103108 const filenames = await fs . readdir ( sketchbookPath ) ;
104109 for ( const fileName of filenames ) {
105110 const filePath = path . join ( sketchbookPath , fileName ) ;
@@ -201,7 +206,7 @@ export class SketchesServiceImpl implements SketchesService {
201206 * See: https://github.com/arduino/arduino-cli/issues/837
202207 * Based on: https://github.com/arduino/arduino-cli/blob/eef3705c4afcba4317ec38b803d9ffce5dd59a28/arduino/builder/sketch.go#L100-L215
203208 */
204- async loadSketch ( uri : string ) : Promise < Sketch > {
209+ async loadSketch ( uri : string ) : Promise < SketchWithDetails > {
205210 const sketchPath = FileUri . fsPath ( uri ) ;
206211 const exists = await fs . exists ( sketchPath ) ;
207212 if ( ! exists ) {
@@ -294,7 +299,80 @@ export class SketchesServiceImpl implements SketchesService {
294299
295300 }
296301
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 > {
298376 let mainFile : string | undefined ;
299377 const paths = new Set < string > ( ) ;
300378 for ( const p of allFilesPaths ) {
@@ -326,13 +404,15 @@ export class SketchesServiceImpl implements SketchesService {
326404 additionalFiles . sort ( ) ;
327405 otherSketchFiles . sort ( ) ;
328406
407+ const { mtimeMs } = await fs . lstat ( sketchFolderPath ) ;
329408 return {
330409 uri : FileUri . create ( sketchFolderPath ) . toString ( ) ,
331410 mainFileUri : FileUri . create ( mainFile ) . toString ( ) ,
332411 name : path . basename ( sketchFolderPath ) ,
333412 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+ } ;
336416 }
337417
338418 async cloneExample ( uri : string ) : Promise < Sketch > {
@@ -538,3 +618,8 @@ class SkipDir extends Error {
538618 Object . setPrototypeOf ( this , SkipDir . prototype ) ;
539619 }
540620}
621+
622+ interface SketchWithDetails extends Sketch {
623+ readonly mtimeMs : number ;
624+ }
625+
0 commit comments