@@ -15,9 +15,12 @@ const {
15
15
toStat,
16
16
fromMount,
17
17
toDriveStats,
18
+ toDownloadProgress,
19
+ toDiffEntry,
18
20
toChunks
19
21
} = require ( 'hyperdrive-daemon-client/lib/common' )
20
22
const { rpc } = require ( 'hyperdrive-daemon-client' )
23
+ const ArrayIndex = require ( './array-index.js' )
21
24
22
25
const log = require ( '../log' ) . child ( { component : 'drive-manager' } )
23
26
@@ -40,9 +43,9 @@ class DriveManager extends EventEmitter {
40
43
41
44
// TODO: Replace with an LRU cache.
42
45
this . _drives = new Map ( )
43
- this . _sessions = new Map ( )
44
46
this . _watchers = new Map ( )
45
- this . _sessionCounter = 0
47
+ this . _sessions = new ArrayIndex ( )
48
+ this . _downloads = new ArrayIndex ( )
46
49
47
50
this . _readyPromise = null
48
51
@@ -123,12 +126,12 @@ class DriveManager extends EventEmitter {
123
126
124
127
async createSession ( key , opts ) {
125
128
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 }
128
131
}
129
132
130
- async closeSession ( id ) {
131
- this . _sessions . delete ( id )
133
+ closeSession ( id ) {
134
+ return this . _sessions . delete ( id )
132
135
}
133
136
134
137
async getAllStats ( ) {
@@ -198,7 +201,8 @@ class DriveManager extends EventEmitter {
198
201
keyString = this . _generateKeyString ( key , opts )
199
202
200
203
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)
202
206
} else {
203
207
// All read-only drives are currently published by default.
204
208
await this . publish ( drive )
@@ -230,6 +234,18 @@ class DriveManager extends EventEmitter {
230
234
231
235
getHandlers ( ) {
232
236
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
+
233
249
get : async ( call ) => {
234
250
var driveOpts = call . request . getOpts ( )
235
251
if ( driveOpts ) driveOpts = fromHyperdriveOptions ( driveOpts )
@@ -293,6 +309,139 @@ class DriveManager extends EventEmitter {
293
309
return rsp
294
310
} ,
295
311
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
+
296
445
createReadStream : async ( call ) => {
297
446
const id = call . request . getId ( )
298
447
const path = call . request . getPath ( )
@@ -408,8 +557,10 @@ class DriveManager extends EventEmitter {
408
557
if ( ! path ) throw new Error ( 'A stat request must specify a path. ' )
409
558
const drive = this . driveForSession ( id )
410
559
560
+ const method = lstat ? drive . lstat . bind ( drive ) : drive . stat . bind ( drive )
561
+
411
562
return new Promise ( ( resolve , reject ) => {
412
- drive . stat ( path , { followLink : lstat } , ( err , stat ) => {
563
+ method ( path , ( err , stat ) => {
413
564
if ( err ) return reject ( err )
414
565
415
566
const rsp = new rpc . drive . messages . StatResponse ( )
0 commit comments