1- import  {  injectable  }  from  'inversify' ; 
1+ import  {  injectable , inject  }  from  'inversify' ; 
22import  *  as  createPaths  from  './create-paths' ; 
3- import  {  posix , splitSketchPath  }  from  './create-paths' ; 
3+ import  {  posix  }  from  './create-paths' ; 
44import  {  AuthenticationClientService  }  from  '../auth/authentication-client-service' ; 
55import  {  ArduinoPreferences  }  from  '../arduino-preferences' ; 
6+ import  {  SketchCache  }  from  '../widgets/cloud-sketchbook/cloud-sketch-cache' ; 
7+ import  {  Create ,  CreateError  }  from  './typings' ; 
68
79export  interface  ResponseResultProvider  { 
810 ( response : Response ) : Promise < any > ; 
@@ -15,10 +17,11 @@ export namespace ResponseResultProvider {
1517
1618type  ResourceType  =  'f'  |  'd' ; 
1719
18- export  let  sketchCache : Create . Sketch [ ]  =  [ ] ; 
19- 2020@injectable ( ) 
2121export  class  CreateApi  { 
22+  @inject ( SketchCache ) 
23+  protected  sketchCache : SketchCache ; 
24+ 2225 protected  authenticationService : AuthenticationClientService ; 
2326 protected  arduinoPreferences : ArduinoPreferences ; 
2427
@@ -32,48 +35,20 @@ export class CreateApi {
3235 return  this ; 
3336 } 
3437
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  } ) ; 
51-  } 
52- 5338 getSketchSecretStat ( sketch : Create . Sketch ) : Create . Resource  { 
5439 return  { 
5540 href : `${ sketch . href } ${ posix . sep } ${ Create . arduino_secrets_file }  ` , 
5641 modified_at : sketch . modified_at , 
42+  created_at : sketch . created_at , 
5743 name : `${ Create . arduino_secrets_file }  ` , 
5844 path : `${ sketch . path } ${ posix . sep } ${ Create . arduino_secrets_file }  ` , 
5945 mimetype : 'text/x-c++src; charset=utf-8' , 
6046 type : 'file' , 
61-  sketchId : sketch . id , 
6247 } ; 
6348 } 
6449
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-  } 
50+  async  sketch ( id : string ) : Promise < Create . Sketch >  { 
51+  const  url  =  new  URL ( `${ this . domain ( ) }  /sketches/byID/${ id }  ` ) ; 
7752
7853 url . searchParams . set ( 'user_id' ,  'me' ) ; 
7954 const  headers  =  await  this . headers ( ) ; 
@@ -92,7 +67,7 @@ export class CreateApi {
9267 method : 'GET' , 
9368 headers, 
9469 } ) ; 
95-  sketchCache = result . sketches ; 
70+  result . sketches . forEach ( ( sketch ) => this . sketchCache . addSketch ( sketch ) ) ; 
9671 return  result . sketches ; 
9772 } 
9873
@@ -118,7 +93,7 @@ export class CreateApi {
11893
11994 async  readDirectory ( 
12095 posixPath : string , 
121-  options : {  recursive ?: boolean ;  match ?: string ; secrets ?:  boolean  }  =  { } 
96+  options : {  recursive ?: boolean ;  match ?: string  }  =  { } 
12297 ) : Promise < Create . Resource [ ] >  { 
12398 const  url  =  new  URL ( 
12499 `${ this . domain ( ) }  /files/d/$HOME/sketches_v2${ posixPath }  ` 
@@ -131,58 +106,21 @@ export class CreateApi {
131106 } 
132107 const  headers  =  await  this . headers ( ) ; 
133108
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-  } 
109+  return  this . run < Create . RawResource [ ] > ( url ,  { 
110+  method : 'GET' , 
111+  headers, 
112+  } ) 
113+  . then ( async  ( result )  =>  { 
114+  // add arduino_secrets.h to the results, when reading a sketch main folder 
115+  if  ( posixPath . length  &&  posixPath  !==  posix . sep )  { 
116+  const  sketch  =  this . sketchCache . getSketch ( posixPath ) ; 
117+ 118+  if  ( sketch  &&  sketch . secrets  &&  sketch . secrets . length  >  0 )  { 
119+  result . push ( this . getSketchSecretStat ( sketch ) ) ; 
169120 } 
170121 } 
171-  const  sketchesMap : Record < string ,  Create . Sketch >  =  sketches . reduce ( 
172-  ( prev ,  curr )  =>  { 
173-  return  {  ...prev ,  [ curr . path ] : curr  } ; 
174-  } , 
175-  { } 
176-  ) ; 
177122
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-  } ) ; 
123+  return  result ; 
186124 } ) 
187125 . catch ( ( reason )  =>  { 
188126 if  ( reason ?. status  ===  404 )  return  [ ]  as  Create . Resource [ ] ; 
@@ -214,18 +152,16 @@ export class CreateApi {
214152
215153 let  resources ; 
216154 if  ( basename  ===  Create . arduino_secrets_file )  { 
217-  const  sketch  =  await  this . findSketchInCache ( 
218-  this . sketchCompareByPath ( parentPosixPath ) 
219-  ) ; 
155+  const  sketch  =  this . sketchCache . getSketch ( parentPosixPath ) ; 
220156 resources  =  sketch  ? [ this . getSketchSecretStat ( sketch ) ]  : [ ] ; 
221157 }  else  { 
222158 resources  =  await  this . readDirectory ( parentPosixPath ,  { 
223159 match : basename , 
224160 } ) ; 
225161 } 
226- 227-  resources . sort ( ( left , right )  =>  left . path . length - right . path . length ) ; 
228-  const resource = resources . find ( ( {  name  } ) => name === basename ) ; 
162+ const resource = resources . find ( 
163+  ( {  path  } )  =>  createPaths . splitSketchPath ( path ) [ 1 ] === posixPath 
164+  ) ; 
229165 if  ( ! resource )  { 
230166 throw  new  CreateError ( `Not found: ${ posixPath }  .` ,  404 ) ; 
231167 } 
@@ -248,10 +184,7 @@ export class CreateApi {
248184 return  data ; 
249185 } 
250186
251-  const  sketch  =  await  this . findSketchInCache ( ( sketch )  =>  { 
252-  const  [ ,  spath ]  =  splitSketchPath ( sketch . path ) ; 
253-  return  spath  ===  createPaths . parentPosix ( path ) ; 
254-  } ,  true ) ; 
187+  const  sketch  =  this . sketchCache . getSketch ( createPaths . parentPosix ( path ) ) ; 
255188
256189 if  ( 
257190 sketch  && 
@@ -273,14 +206,25 @@ export class CreateApi {
273206
274207 if  ( basename  ===  Create . arduino_secrets_file )  { 
275208 const  parentPosixPath  =  createPaths . parentPosix ( posixPath ) ; 
276-  const  sketch  =  await  this . findSketchInCache ( 
277-  this . sketchCompareByPath ( parentPosixPath ) , 
278-  false 
279-  ) ; 
209+ 210+  //retrieve the sketch id from the cache 
211+  const  cacheSketch  =  this . sketchCache . getSketch ( parentPosixPath ) ; 
212+  if  ( ! cacheSketch )  { 
213+  throw  new  Error ( `Unable to find sketch ${ parentPosixPath }   in cache` ) ; 
214+  } 
215+ 216+  // get a fresh copy of the sketch in order to guarantee fresh secrets 
217+  const  sketch  =  await  this . sketch ( cacheSketch . id ) ; 
218+  if  ( ! sketch )  { 
219+  throw  new  Error ( 
220+  `Unable to get a fresh copy of the sketch ${ cacheSketch . id }  ` 
221+  ) ; 
222+  } 
223+  this . sketchCache . addSketch ( sketch ) ; 
280224
281225 let  file  =  '' ; 
282226 if  ( sketch  &&  sketch . secrets )  { 
283-  for  ( const  item  of  sketch ? .secrets )  { 
227+  for  ( const  item  of  sketch . secrets )  { 
284228 file  +=  `#define ${ item . name }   "${ item . value }  "\r\n` ; 
285229 } 
286230 } 
@@ -310,9 +254,9 @@ export class CreateApi {
310254
311255 if  ( basename  ===  Create . arduino_secrets_file )  { 
312256 const  parentPosixPath  =  createPaths . parentPosix ( posixPath ) ; 
313- const sketch = await this . findSketchInCache ( 
314-  this . sketchCompareByPath ( parentPosixPath ) 
315- ) ; 
257+ 258+  const sketch = this . sketchCache . getSketch ( parentPosixPath ) ; 
259+ 316260 if  ( sketch )  { 
317261 const  url  =  new  URL ( `${ this . domain ( ) }  /sketches/${ sketch . id }  ` ) ; 
318262 const  headers  =  await  this . headers ( ) ; 
@@ -356,9 +300,10 @@ export class CreateApi {
356300 secrets : {  data : secrets  } , 
357301 } ; 
358302
359-  // 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 } ) ; 
303+  // replace the sketch in the cache with the one we are pushing 
304+  // TODO: we should do a get after the POST, in order to be sure the cache 
305+  // is updated the most recent metadata 
306+  this . sketchCache . addSketch ( sketch ) ; 
362307
363308 const  init  =  { 
364309 method : 'POST' , 
@@ -370,6 +315,14 @@ export class CreateApi {
370315 return ; 
371316 } 
372317
318+  // do not upload "do_not_sync" files/directoris and their descendants 
319+  const  segments  =  posixPath . split ( posix . sep )  ||  [ ] ; 
320+  if  ( 
321+  segments . some ( ( segment )  =>  Create . do_not_sync_files . includes ( segment ) ) 
322+  )  { 
323+  return ; 
324+  } 
325+ 373326 const  url  =  new  URL ( 
374327 `${ this . domain ( ) }  /files/f/$HOME/sketches_v2${ posixPath }  ` 
375328 ) ; 
@@ -512,75 +465,3 @@ void loop() {
512465
513466` ; 
514467} 
515- 516- export  namespace  Create  { 
517-  export  interface  Sketch  { 
518-  readonly  name : string ; 
519-  readonly  path : string ; 
520-  readonly  modified_at : string ; 
521-  readonly  created_at : string ; 
522- 523-  readonly  secrets ?: {  name : string ;  value : string  } [ ] ; 
524- 525-  readonly  id : string ; 
526-  readonly  is_public : boolean ; 
527-  // readonly board_fqbn: '', 
528-  // readonly board_name: '', 
529-  // readonly board_type: 'serial' | 'network' | 'cloud' | '', 
530-  readonly  href ?: string ; 
531-  readonly  libraries : string [ ] ; 
532-  // readonly tutorials: string[] | null; 
533-  // readonly types: string[] | null; 
534-  // readonly user_id: string; 
535-  } 
536- 537-  export  type  ResourceType  =  'sketch'  |  'folder'  |  'file' ; 
538-  export  const  arduino_secrets_file  =  'arduino_secrets.h' ; 
539-  export  interface  Resource  { 
540-  readonly  name : string ; 
541-  /** 
542-  * Note: this path is **not** the POSIX path we use. It has the leading segments with the `user_id`. 
543-  */ 
544-  readonly  path : string ; 
545-  readonly  type : ResourceType ; 
546-  readonly  sketchId : string ; 
547-  readonly  modified_at : string ;  // As an ISO-8601 formatted string: `YYYY-MM-DDTHH:mm:ss.sssZ` 
548-  readonly  children ?: number ;  // For 'sketch' and 'folder' types. 
549-  readonly  size ?: number ;  // For 'sketch' type only. 
550-  readonly  isPublic ?: boolean ;  // For 'sketch' type only. 
551- 552-  readonly  mimetype ?: string ;  // For 'file' type. 
553-  readonly  href ?: string ; 
554-  } 
555-  export  namespace  Resource  { 
556-  export  function  is ( arg : any ) : arg  is Resource  { 
557-  return  ( 
558-  ! ! arg  && 
559-  'name'  in  arg  && 
560-  typeof  arg [ 'name' ]  ===  'string'  && 
561-  'path'  in  arg  && 
562-  typeof  arg [ 'path' ]  ===  'string'  && 
563-  'type'  in  arg  && 
564-  typeof  arg [ 'type' ]  ===  'string'  && 
565-  'modified_at'  in  arg  && 
566-  typeof  arg [ 'modified_at' ]  ===  'string'  && 
567-  ( arg [ 'type' ]  ===  'sketch'  || 
568-  arg [ 'type' ]  ===  'folder'  || 
569-  arg [ 'type' ]  ===  'file' ) 
570-  ) ; 
571-  } 
572-  } 
573- 574-  export  type  RawResource  =  Omit < Resource ,  'sketchId'  |  'isPublic' > ; 
575- } 
576- 577- export  class  CreateError  extends  Error  { 
578-  constructor ( 
579-  message : string , 
580-  readonly  status : number , 
581-  readonly  details ?: string 
582-  )  { 
583-  super ( message ) ; 
584-  Object . setPrototypeOf ( this ,  CreateError . prototype ) ; 
585-  } 
586- } 
0 commit comments