@@ -15,9 +15,12 @@ const {
1515 toStat,
1616 fromMount,
1717 toDriveStats,
18+ toDownloadProgress,
19+ toDiffEntry,
1820 toChunks
1921} = require ( 'hyperdrive-daemon-client/lib/common' )
2022const { rpc } = require ( 'hyperdrive-daemon-client' )
23+ const ArrayIndex = require ( './array-index.js' )
2124
2225const log = require ( '../log' ) . child ( { component : 'drive-manager' } )
2326
@@ -40,9 +43,9 @@ class DriveManager extends EventEmitter {
4043
4144 // TODO: Replace with an LRU cache.
4245 this . _drives = new Map ( )
43- this . _sessions = new Map ( )
4446 this . _watchers = new Map ( )
45- this . _sessionCounter = 0
47+ this . _sessions = new ArrayIndex ( )
48+ this . _downloads = new ArrayIndex ( )
4649
4750 this . _readyPromise = null
4851
@@ -123,12 +126,12 @@ class DriveManager extends EventEmitter {
123126
124127 async createSession ( key , opts ) {
125128 const drive = await this . get ( key , opts )
126- this . _sessions . set ( ++ this . _sessionCounter , drive )
127- return { drive, session : this . _sessionCounter }
129+ const sessionId = this . _sessions . insert ( drive )
130+ return { drive, session : sessionId }
128131 }
129132
130- async closeSession ( id ) {
131- this . _sessions . delete ( id )
133+ closeSession ( id ) {
134+ return this . _sessions . delete ( id )
132135 }
133136
134137 async getAllStats ( ) {
@@ -198,7 +201,8 @@ class DriveManager extends EventEmitter {
198201 keyString = this . _generateKeyString ( key , opts )
199202
200203 if ( drive . writable ) {
201- await this . _configureDrive ( drive , opts && opts . configure )
204+ // TODO: Replace everything in _configureDrive with virtual files in the FUSE component.
205+ // await this._configureDrive(drive, opts && opts.configure)
202206 } else {
203207 // All read-only drives are currently published by default.
204208 await this . publish ( drive )
@@ -230,6 +234,18 @@ class DriveManager extends EventEmitter {
230234
231235 getHandlers ( ) {
232236 return {
237+ version : async ( call ) => {
238+ const id = call . request . getId ( )
239+
240+ if ( ! id ) throw new Error ( 'A version request must specify a session ID.' )
241+ const drive = this . driveForSession ( id )
242+
243+ const rsp = new rpc . drive . messages . DriveVersionResponse ( )
244+ rsp . setVersion ( drive . version )
245+
246+ return rsp
247+ } ,
248+
233249 get : async ( call ) => {
234250 var driveOpts = call . request . getOpts ( )
235251 if ( driveOpts ) driveOpts = fromHyperdriveOptions ( driveOpts )
@@ -293,6 +309,139 @@ class DriveManager extends EventEmitter {
293309 return rsp
294310 } ,
295311
312+ download : async ( call ) => {
313+ const id = call . request . getId ( )
314+ const path = call . request . getPath ( )
315+ const detailed = call . request . getDetailed ( )
316+
317+ if ( ! id ) throw new Error ( 'A download request must specify a session ID.' )
318+ const drive = this . driveForSession ( id )
319+ var downloadId = null
320+ var ended = false
321+
322+ const dl = drive . download ( path , { detailed } , ( ) => {
323+ if ( ended ) return null
324+ return finish ( true )
325+ } )
326+ downloadId = this . _downloads . insert ( dl )
327+
328+ call . on ( 'end' , cleanup )
329+ call . on ( 'close' , cleanup )
330+ call . on ( 'finish' , cleanup )
331+ call . on ( 'error' , cleanup )
332+
333+ dl . on ( 'start' , start )
334+ dl . on ( 'cancelled' , ( ) => finish ( false ) )
335+ dl . on ( 'progress' , progress )
336+
337+ function progress ( path , fileStats , totals ) {
338+ const rsp = new rpc . drive . messages . DownloadResponse ( )
339+ rsp . setDownloadid ( downloadId )
340+ rsp . setType ( rpc . drive . messages . DownloadResponse . Type . PROGRESS )
341+
342+ if ( detailed ) {
343+ const fileRsps = fileStats . map ( getFileStatusRsp )
344+ rsp . setFilesList ( fileRsps )
345+ }
346+
347+ const progressRsp = toDownloadProgress ( totals )
348+ rsp . setProgress ( progressRsp )
349+
350+ call . write ( rsp )
351+ }
352+
353+ function start ( files , totals ) {
354+ const rsp = new rpc . drive . messages . DownloadResponse ( )
355+ rsp . setDownloadid ( downloadId )
356+ rsp . setType ( rpc . drive . messages . DownloadResponse . Type . START )
357+
358+ if ( detailed ) {
359+ const fileRsps = files . map ( getFileStatusRsp )
360+ rsp . setFilesList ( fileRsps )
361+ }
362+
363+ const progressRsp = toDownload ( totals )
364+ rsp . setProgress ( progressRsp )
365+
366+ call . write ( rsp )
367+ }
368+
369+ function finish ( completed ) {
370+ ended = true
371+ const rsp = new rpc . drive . messages . DownloadResponse ( )
372+ rsp . setDownloadid ( downloadId )
373+ rsp . setType ( rpc . drive . messages . DownloadResponse . Type . FINISH )
374+
375+ const progress = new rpc . drive . messages . DownloadProgress ( )
376+ progress . setCompleted ( ! ! completed )
377+ progress . setCancelled ( ! completed )
378+ rsp . setProgress ( progress )
379+
380+ cleanup ( )
381+ call . end ( rsp )
382+ }
383+
384+ function cleanup ( ) {
385+ this . _downloads . delete ( downloadId )
386+ dl . removeAllListeners ( 'progress' )
387+ dl . removeAllListeners ( 'cancelled' )
388+ dl . removeAllListeners ( 'start' )
389+ }
390+
391+ function getFileStatusRsp ( path , stats ) {
392+ const fileStatus = new rpc . drive . messages . FileDownloadStatus ( )
393+ fileStatus . setPath ( path )
394+ if ( stats ) fileStatus . setProgress ( toDownloadProgress ( stats ) )
395+ return fileStatus
396+ }
397+ } ,
398+
399+ undownload : async ( call ) => {
400+ const id = call . request . getId ( )
401+ const downloadId = call . request . getDownloadid ( )
402+
403+ if ( ! id ) throw new Error ( 'An undownload request must specify a session ID.' )
404+ if ( ! downloadId ) throw new Error ( 'An undownload request must specify a download ID.' )
405+ const drive = this . driveForSession ( id )
406+
407+ const dl = this . _downloads . get ( downloadId )
408+ if ( dl ) dl . cancel ( )
409+ this . _downloads . delete ( downloadId )
410+
411+ return new rpc . drive . messages . UndownloadResponse ( )
412+ } ,
413+
414+ createDiffStream : async ( call ) => {
415+ const id = call . request . getId ( )
416+ const prefix = call . request . getPrefix ( )
417+ const otherVersion = call . request . getOther ( )
418+
419+ if ( ! id ) throw new Error ( 'A diff stream request must specify a session ID.' )
420+ const drive = this . driveForSession ( id )
421+
422+ const stream = drive . createDiffStream ( otherVersion , prefix )
423+
424+ const rspMapper = map . obj ( chunk => {
425+ const rsp = new rpc . drive . messages . DiffStreamResponse ( )
426+ if ( ! chunk ) return rsp
427+
428+ const { name, type, value } = chunk
429+ rsp . setType ( type )
430+ rsp . setName ( name )
431+ if ( type === 'put' ) {
432+ rsp . setValue ( toDiffEntry ( { stat : value } ) )
433+ } else {
434+ rsp . setValue ( toDiffEntry ( { mount : value } ) )
435+ }
436+
437+ return rsp
438+ } )
439+
440+ pump ( stream , rspMapper , call , err => {
441+ if ( err ) log . error ( { id, err } , 'createDiffStream error' )
442+ } )
443+ } ,
444+
296445 createReadStream : async ( call ) => {
297446 const id = call . request . getId ( )
298447 const path = call . request . getPath ( )
@@ -408,8 +557,10 @@ class DriveManager extends EventEmitter {
408557 if ( ! path ) throw new Error ( 'A stat request must specify a path. ' )
409558 const drive = this . driveForSession ( id )
410559
560+ const method = lstat ? drive . lstat . bind ( drive ) : drive . stat . bind ( drive )
561+
411562 return new Promise ( ( resolve , reject ) => {
412- drive . stat ( path , { followLink : lstat } , ( err , stat ) => {
563+ method ( path , ( err , stat ) => {
413564 if ( err ) return reject ( err )
414565
415566 const rsp = new rpc . drive . messages . StatResponse ( )
0 commit comments