@@ -17,7 +17,6 @@ import {
1717 PreferenceScope ,
1818} from '@theia/core/lib/browser/preferences/preference-service' ;
1919import { MessageService } from '@theia/core/lib/common/message-service' ;
20- import { REMOTE_ONLY_FILES } from './../../create/create-fs-provider' ;
2120import { CreateApi } from '../../create/create-api' ;
2221import { CreateUri } from '../../create/create-uri' ;
2322import { CloudSketchbookTreeModel } from './cloud-sketchbook-tree-model' ;
@@ -33,10 +32,17 @@ import { ArduinoPreferences } from '../../arduino-preferences';
3332import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl' ;
3433import { FileStat } from '@theia/filesystem/lib/common/files' ;
3534import { WorkspaceNode } from '@theia/navigator/lib/browser/navigator-tree' ;
35+ import { posix , splitSketchPath } from '../../create/create-paths' ;
36+ import { Create } from '../../create/typings' ;
3637
3738const MESSAGE_TIMEOUT = 5 * 1000 ;
3839const deepmerge = require ( 'deepmerge' ) . default ;
3940
41+ type FilesToWrite = { source : URI ; dest : URI } ;
42+ type FilesToSync = {
43+ filesToWrite : FilesToWrite [ ] ;
44+ filesToDelete : URI [ ] ;
45+ } ;
4046@injectable ( )
4147export class CloudSketchbookTree extends SketchbookTree {
4248 @inject ( FileService )
@@ -94,7 +100,7 @@ export class CloudSketchbookTree extends SketchbookTree {
94100
95101 async pull ( arg : any ) : Promise < void > {
96102 const {
97- model,
103+ // model,
98104 node,
99105 } : {
100106 model : CloudSketchbookTreeModel ;
@@ -127,47 +133,12 @@ export class CloudSketchbookTree extends SketchbookTree {
127133 const commandsCopy = node . commands ;
128134 node . commands = [ ] ;
129135
130- // check if the sketch dir already exist
131- if ( CloudSketchbookTree . CloudSketchTreeNode . isSynced ( node ) ) {
132- const filesToPull = (
133- await this . createApi . readDirectory ( node . remoteUri . path . toString ( ) )
134- ) . filter ( ( file : any ) => ! REMOTE_ONLY_FILES . includes ( file . name ) ) ;
135-
136- await Promise . all (
137- filesToPull . map ( ( file : any ) => {
138- const uri = CreateUri . toUri ( file ) ;
139- this . fileService . copy ( uri , LocalCacheUri . root . resolve ( uri . path ) , {
140- overwrite : true ,
141- } ) ;
142- } )
143- ) ;
136+ const localUri = await this . fileService . toUnderlyingResource (
137+ LocalCacheUri . root . resolve ( node . remoteUri . path )
138+ ) ;
139+ await this . sync ( node . remoteUri , localUri ) ;
144140
145- // open the pulled files in the current workspace
146- const currentSketch = await this . sketchServiceClient . currentSketch ( ) ;
147-
148- if (
149- ! CreateUri . is ( node . uri ) &&
150- currentSketch &&
151- currentSketch . uri === node . uri . toString ( )
152- ) {
153- filesToPull . forEach ( async ( file ) => {
154- const localUri = LocalCacheUri . root . resolve (
155- CreateUri . toUri ( file ) . path
156- ) ;
157- const underlying = await this . fileService . toUnderlyingResource (
158- localUri
159- ) ;
160-
161- model . open ( underlying ) ;
162- } ) ;
163- }
164- } else {
165- await this . fileService . copy (
166- node . remoteUri ,
167- LocalCacheUri . root . resolve ( node . uri . path ) ,
168- { overwrite : true }
169- ) ;
170- }
141+ this . sketchCache . purgeByPath ( node . remoteUri . path . toString ( ) ) ;
171142
172143 node . commands = commandsCopy ;
173144 this . messageService . info ( `Done pulling ‘${ node . fileStat . name } ’.` , {
@@ -214,17 +185,107 @@ export class CloudSketchbookTree extends SketchbookTree {
214185 }
215186 const commandsCopy = node . commands ;
216187 node . commands = [ ] ;
217- // delete every first level file, then push everything
218- const result = await this . fileService . copy ( node . uri , node . remoteUri , {
219- overwrite : true ,
220- } ) ;
188+
189+ const localUri = await this . fileService . toUnderlyingResource (
190+ LocalCacheUri . root . resolve ( node . remoteUri . path )
191+ ) ;
192+ await this . sync ( localUri , node . remoteUri ) ;
193+
194+ this . sketchCache . purgeByPath ( node . remoteUri . path . toString ( ) ) ;
195+
221196 node . commands = commandsCopy ;
222- this . messageService . info ( `Done pushing ‘${ result . name } ’.` , {
197+ this . messageService . info ( `Done pushing ‘${ node . fileStat . name } ’.` , {
223198 timeout : MESSAGE_TIMEOUT ,
224199 } ) ;
225200 } ) ;
226201 }
227202
203+ async recursiveURIs ( uri : URI ) : Promise < URI [ ] > {
204+ // remote resources can be fetched one-shot via api
205+ if ( CreateUri . is ( uri ) ) {
206+ const resources = await this . createApi . readDirectory (
207+ uri . path . toString ( ) ,
208+ { recursive : true , skipSketchCache : true }
209+ ) ;
210+ return resources . map ( ( resource ) =>
211+ CreateUri . toUri ( splitSketchPath ( resource . path ) [ 1 ] )
212+ ) ;
213+ }
214+
215+ const fileStat = await this . fileService . resolve ( uri , {
216+ resolveMetadata : false ,
217+ } ) ;
218+
219+ if ( ! fileStat . children || ! fileStat . isDirectory ) {
220+ return [ fileStat . resource ] ;
221+ }
222+
223+ let childrenUris : URI [ ] = [ ] ;
224+
225+ for await ( const child of fileStat . children ) {
226+ childrenUris = [
227+ ...childrenUris ,
228+ ...( await this . recursiveURIs ( child . resource ) ) ,
229+ ] ;
230+ }
231+
232+ return [ fileStat . resource , ...childrenUris ] ;
233+ }
234+
235+ private URIsToMap ( uris : URI [ ] , basepath : string ) : Record < string , URI > {
236+ return uris . reduce ( ( prev : Record < string , URI > , curr ) => {
237+ const path = curr . toString ( ) . split ( basepath ) ;
238+
239+ if ( path . length !== 2 || path [ 1 ] . length === 0 ) {
240+ return prev ;
241+ }
242+
243+ // do not map "do_not_sync" files/directoris and their descendants
244+ const segments = path [ 1 ] . split ( posix . sep ) || [ ] ;
245+ if (
246+ segments . some ( ( segment ) => Create . do_not_sync_files . includes ( segment ) )
247+ ) {
248+ return prev ;
249+ }
250+
251+ // skip when the filename is a hidden file (starts with `.`)
252+ if ( segments [ segments . length - 1 ] . indexOf ( '.' ) === 0 ) {
253+ return prev ;
254+ }
255+
256+ return { ...prev , [ path [ 1 ] ] : curr } ;
257+ } , { } ) ;
258+ }
259+
260+ async getUrisMap ( uri : URI ) {
261+ const basepath = uri . toString ( ) ;
262+ const exists = await this . fileService . exists ( uri ) ;
263+ const uris =
264+ ( exists && this . URIsToMap ( await this . recursiveURIs ( uri ) , basepath ) ) || { } ;
265+ return uris ;
266+ }
267+
268+ async treeDiff ( source : URI , dest : URI ) : Promise < FilesToSync > {
269+ const [ sourceURIs , destURIs ] = await Promise . all ( [
270+ this . getUrisMap ( source ) ,
271+ this . getUrisMap ( dest ) ,
272+ ] ) ;
273+
274+ const destBase = dest . toString ( ) ;
275+ const filesToWrite : FilesToWrite [ ] = [ ] ;
276+
277+ Object . keys ( sourceURIs ) . forEach ( ( path ) => {
278+ const destUri = destURIs [ path ] || new URI ( destBase + path ) ;
279+
280+ filesToWrite . push ( { source : sourceURIs [ path ] , dest : destUri } ) ;
281+ delete destURIs [ path ] ;
282+ } ) ;
283+
284+ const filesToDelete = Object . values ( destURIs ) ;
285+
286+ return { filesToWrite, filesToDelete } ;
287+ }
288+
228289 async refresh (
229290 node ?: CompositeTreeNode
230291 ) : Promise < CompositeTreeNode | undefined > {
@@ -266,6 +327,25 @@ export class CloudSketchbookTree extends SketchbookTree {
266327 }
267328 }
268329
330+ async sync ( source : URI , dest : URI ) {
331+ const { filesToWrite, filesToDelete } = await this . treeDiff ( source , dest ) ;
332+ await Promise . all (
333+ filesToWrite . map ( async ( { source, dest } ) => {
334+ if ( ( await this . fileService . resolve ( source ) ) . isFile ) {
335+ const content = await this . fileService . read ( source ) ;
336+ return this . fileService . write ( dest , content . value ) ;
337+ }
338+ return this . fileService . createFolder ( dest ) ;
339+ } )
340+ ) ;
341+
342+ await Promise . all (
343+ filesToDelete . map ( ( file ) =>
344+ this . fileService . delete ( file , { recursive : true } )
345+ )
346+ ) ;
347+ }
348+
269349 async resolveChildren ( parent : CompositeTreeNode ) : Promise < TreeNode [ ] > {
270350 return ( await super . resolveChildren ( parent ) ) . sort ( ( a , b ) => {
271351 if (
@@ -295,7 +375,7 @@ export class CloudSketchbookTree extends SketchbookTree {
295375
296376 /**
297377 * Retrieve fileStats for the given node, merging the local and remote childrens
298- * Local children take prevedence over remote ones
378+ * Local children take precedence over remote ones
299379 * @param node
300380 * @returns
301381 */
@@ -376,6 +456,7 @@ export class CloudSketchbookTree extends SketchbookTree {
376456 const node = this . getNode ( id ) ;
377457 if ( fileStat . isDirectory ) {
378458 if ( DirNode . is ( node ) ) {
459+ node . uri = uri ;
379460 node . fileStat = fileStat ;
380461 return node ;
381462 }
@@ -391,6 +472,7 @@ export class CloudSketchbookTree extends SketchbookTree {
391472 }
392473 if ( FileNode . is ( node ) ) {
393474 node . fileStat = fileStat ;
475+ node . uri = uri ;
394476 return node ;
395477 }
396478 return < FileNode > {
0 commit comments