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