@@ -13,17 +13,22 @@ import { createDeferred } from '../utils/async';
13
13
import { isFileNotFoundError , isNoPermissionsError } from './errors' ;
14
14
import { FileSystemPaths , FileSystemPathUtils } from './fs-paths' ;
15
15
import { TemporaryFileSystem } from './fs-temp' ;
16
- // prettier-ignore
17
16
import {
18
- FileStat , FileType ,
19
- IFileSystem , IFileSystemPaths , IRawFileSystem ,
20
- ReadStream , TemporaryFile , WriteStream
17
+ FileStat ,
18
+ FileType ,
19
+ IFileSystem ,
20
+ IFileSystemPaths ,
21
+ IFileSystemPathUtils ,
22
+ IFileSystemUtils ,
23
+ IRawFileSystem ,
24
+ ITempFileSystem ,
25
+ ReadStream ,
26
+ TemporaryFile ,
27
+ WriteStream
21
28
} from './types' ;
22
29
23
30
const ENCODING : string = 'utf8' ;
24
31
25
- const globAsync = promisify ( glob ) ;
26
-
27
32
// This helper function determines the file type of the given stats
28
33
// object. The type follows the convention of node's fs module, where
29
34
// a file has exactly one type. Symlinks are not resolved.
@@ -272,62 +277,51 @@ export class RawFileSystem implements IRawFileSystem {
272
277
}
273
278
274
279
//==========================================
275
- // filesystem "utils" (& legacy aliases)
280
+ // filesystem "utils"
276
281
277
- @injectable ( )
278
- export class FileSystem implements IFileSystem {
279
- // We expose this for the sake of functional tests that do not have
280
- // access to the actual "vscode" namespace.
281
- protected raw : RawFileSystem ;
282
- private readonly paths : IFileSystemPaths ;
283
- private readonly pathUtils : FileSystemPathUtils ;
284
- private readonly tmp : TemporaryFileSystem ;
285
- constructor ( ) {
286
- this . paths = FileSystemPaths . withDefaults ( ) ;
287
- this . pathUtils = FileSystemPathUtils . withDefaults ( this . paths ) ;
288
- this . tmp = TemporaryFileSystem . withDefaults ( ) ;
289
- this . raw = RawFileSystem . withDefaults ( this . paths ) ;
290
- }
291
-
292
- //=================================
293
- // path-related
294
-
295
- public get directorySeparatorChar ( ) : string {
296
- return this . paths . sep ;
297
- }
298
-
299
- public arePathsSame ( path1 : string , path2 : string ) : boolean {
300
- return this . pathUtils . arePathsSame ( path1 , path2 ) ;
301
- }
302
-
303
- //=================================
304
- // "raw" operations
305
-
306
- public async stat ( filename : string ) : Promise < FileStat > {
307
- return this . raw . stat ( filename ) ;
308
- }
309
-
310
- public async lstat ( filename : string ) : Promise < FileStat > {
311
- return this . raw . lstat ( filename ) ;
312
- }
282
+ // This is the parts of the 'fs-extra' module that we use in RawFileSystem.
283
+ interface IFSExtraForUtils {
284
+ open ( path : string , flags : string | number , mode ?: string | number | null ) : Promise < number > ;
285
+ close ( fd : number ) : Promise < void > ;
286
+ unlink ( path : string ) : Promise < void > ;
287
+ existsSync ( path : string ) : boolean ;
288
+ }
313
289
314
- public async readFile ( filePath : string ) : Promise < string > {
315
- return this . raw . readText ( filePath ) ;
316
- }
317
- public readFileSync ( filePath : string ) : string {
318
- return this . raw . readTextSync ( filePath ) ;
319
- }
320
- public async readData ( filePath : string ) : Promise < Buffer > {
321
- return this . raw . readData ( filePath ) ;
290
+ // High-level filesystem operations used by the extension.
291
+ export class FileSystemUtils implements IFileSystemUtils {
292
+ constructor (
293
+ public readonly raw : IRawFileSystem ,
294
+ public readonly pathUtils : IFileSystemPathUtils ,
295
+ public readonly paths : IFileSystemPaths ,
296
+ public readonly tmp : ITempFileSystem ,
297
+ // tslint:disable-next-line:no-shadowed-variable
298
+ private readonly fs : IFSExtraForUtils ,
299
+ private readonly getHash : ( data : string ) => string ,
300
+ private readonly globFiles : ( pat : string , options ?: { cwd : string } ) => Promise < string [ ] >
301
+ ) { }
302
+ // Create a new object using common-case default values.
303
+ public static withDefaults (
304
+ raw ?: IRawFileSystem ,
305
+ pathUtils ?: IFileSystemPathUtils ,
306
+ tmp ?: ITempFileSystem ,
307
+ fsDeps ?: IFSExtraForUtils ,
308
+ getHash ?: ( data : string ) => string ,
309
+ globFiles ?: ( pat : string , options ?: { cwd : string } ) => Promise < string [ ] >
310
+ ) : FileSystemUtils {
311
+ pathUtils = pathUtils || FileSystemPathUtils . withDefaults ( ) ;
312
+ return new FileSystemUtils (
313
+ raw || RawFileSystem . withDefaults ( pathUtils . paths ) ,
314
+ pathUtils ,
315
+ pathUtils . paths ,
316
+ tmp || TemporaryFileSystem . withDefaults ( ) ,
317
+ fsDeps || fs ,
318
+ getHash || getHashString ,
319
+ globFiles || promisify ( glob )
320
+ ) ;
322
321
}
323
322
324
- public async writeFile ( filePath : string , text : string , _options : string | fs . WriteFileOptions = { encoding : 'utf8' } ) : Promise < void > {
325
- // tslint:disable-next-line:no-suspicious-comment
326
- // TODO (GH-8542) For now we ignore the options, since all call
327
- // sites already match the defaults. Later we will fix the call
328
- // sites.
329
- return this . raw . writeText ( filePath , text ) ;
330
- }
323
+ //****************************
324
+ // aliases
331
325
332
326
public async createDirectory ( directoryPath : string ) : Promise < void > {
333
327
return this . raw . mkdirp ( directoryPath ) ;
@@ -337,48 +331,12 @@ export class FileSystem implements IFileSystem {
337
331
return this . raw . rmtree ( directoryPath ) ;
338
332
}
339
333
340
- public async listdir ( dirname : string ) : Promise < [ string , FileType ] [ ] > {
341
- // prettier-ignore
342
- return this . raw . listdir ( dirname )
343
- . catch ( async err => {
344
- // We're only preserving pre-existng behavior here...
345
- if ( ! ( await this . pathExists ( dirname ) ) ) {
346
- return [ ] ;
347
- }
348
- throw err ; // re-throw
349
- } ) ;
350
- }
351
-
352
- public async appendFile ( filename : string , text : string ) : Promise < void > {
353
- return this . raw . appendText ( filename , text ) ;
354
- }
355
-
356
- public async copyFile ( src : string , dest : string ) : Promise < void > {
357
- return this . raw . copyFile ( src , dest ) ;
358
- }
359
-
360
334
public async deleteFile ( filename : string ) : Promise < void > {
361
335
return this . raw . rmfile ( filename ) ;
362
336
}
363
337
364
- public async chmod ( filePath : string , mode : string | number ) : Promise < void > {
365
- return this . raw . chmod ( filePath , mode ) ;
366
- }
367
-
368
- public async move ( src : string , tgt : string ) {
369
- await this . raw . move ( src , tgt ) ;
370
- }
371
-
372
- public createReadStream ( filePath : string ) : ReadStream {
373
- return this . raw . createReadStream ( filePath ) ;
374
- }
375
-
376
- public createWriteStream ( filePath : string ) : WriteStream {
377
- return this . raw . createWriteStream ( filePath ) ;
378
- }
379
-
380
- //=================================
381
- // utils
338
+ //****************************
339
+ // helpers
382
340
383
341
// prettier-ignore
384
342
public async pathExists (
@@ -409,13 +367,21 @@ export class FileSystem implements IFileSystem {
409
367
public async fileExists ( filename : string ) : Promise < boolean > {
410
368
return this . pathExists ( filename , FileType . File ) ;
411
369
}
412
- public fileExistsSync ( filePath : string ) : boolean {
413
- return fs . existsSync ( filePath ) ;
414
- }
415
370
public async directoryExists ( dirname : string ) : Promise < boolean > {
416
371
return this . pathExists ( dirname , FileType . Directory ) ;
417
372
}
418
373
374
+ public async listdir ( dirname : string ) : Promise < [ string , FileType ] [ ] > {
375
+ // prettier-ignore
376
+ return this . raw . listdir ( dirname )
377
+ . catch ( async err => {
378
+ // We're only preserving pre-existng behavior here...
379
+ if ( ! ( await this . pathExists ( dirname ) ) ) {
380
+ return [ ] ;
381
+ }
382
+ throw err ; // re-throw
383
+ } ) ;
384
+ }
419
385
public async getSubDirectories ( dirname : string ) : Promise < string [ ] > {
420
386
// prettier-ignore
421
387
return filterByFileType (
@@ -431,11 +397,31 @@ export class FileSystem implements IFileSystem {
431
397
) . map ( ( [ filename , _fileType ] ) => filename ) ;
432
398
}
433
399
400
+ public async isDirReadonly ( dirname : string ) : Promise < boolean > {
401
+ const filePath = `${ dirname } ${ this . paths . sep } ___vscpTest___` ;
402
+ const flags = fs . constants . O_CREAT | fs . constants . O_RDWR ;
403
+ let fd : number ;
404
+ try {
405
+ fd = await this . fs . open ( filePath , flags ) ;
406
+ } catch ( err ) {
407
+ if ( isNoPermissionsError ( err ) ) {
408
+ return true ;
409
+ }
410
+ throw err ; // re-throw
411
+ }
412
+ // Clean resources in the background.
413
+ this . fs
414
+ . close ( fd )
415
+ . finally ( ( ) => this . fs . unlink ( filePath ) )
416
+ . ignoreErrors ( ) ;
417
+ return false ;
418
+ }
419
+
434
420
public async getFileHash ( filename : string ) : Promise < string > {
435
421
// The reason for lstat rather than stat is not clear...
436
422
const stat = await this . raw . lstat ( filename ) ;
437
423
const data = `${ stat . ctime } -${ stat . mtime } ` ;
438
- return getHashString ( data ) ;
424
+ return this . getHash ( data ) ;
439
425
}
440
426
441
427
public async search ( globPattern : string , cwd ?: string ) : Promise < string [ ] > {
@@ -444,42 +430,118 @@ export class FileSystem implements IFileSystem {
444
430
const options = {
445
431
cwd : cwd
446
432
} ;
447
- found = await globAsync ( globPattern , options ) ;
433
+ found = await this . globFiles ( globPattern , options ) ;
448
434
} else {
449
- found = await globAsync ( globPattern ) ;
435
+ found = await this . globFiles ( globPattern ) ;
450
436
}
451
437
return Array . isArray ( found ) ? found : [ ] ;
452
438
}
453
439
454
- public createTemporaryFile ( extension : string ) : Promise < TemporaryFile > {
455
- return this . tmp . createFile ( extension ) ;
456
- }
440
+ //****************************
441
+ // helpers (non-async)
457
442
458
- public async isDirReadonly ( dirname : string ) : Promise < boolean > {
459
- const filePath = `${ dirname } ${ this . paths . sep } ___vscpTest___` ;
460
- const flags = fs . constants . O_CREAT | fs . constants . O_RDWR ;
461
- let fd : number ;
462
- try {
463
- fd = await fs . open ( filePath , flags ) ;
464
- // Clean resources in the background.
465
- fs . close ( fd )
466
- . finally ( ( ) => fs . unlink ( filePath ) )
467
- . ignoreErrors ( ) ;
468
- } catch ( err ) {
469
- if ( isNoPermissionsError ( err ) ) {
470
- return true ;
471
- }
472
- throw err ; // re-throw
473
- }
474
- return false ;
443
+ public fileExistsSync ( filePath : string ) : boolean {
444
+ return this . fs . existsSync ( filePath ) ;
475
445
}
476
446
}
477
447
478
448
// We *could* use ICryptoUtils, but it's a bit overkill, issue tracked
479
449
// in https://github.com/microsoft/vscode-python/issues/8438.
480
450
function getHashString ( data : string ) : string {
481
- // prettier-ignore
482
- const hash = createHash ( 'sha512' )
483
- . update ( data ) ;
451
+ const hash = createHash ( 'sha512' ) ;
452
+ hash . update ( data ) ;
484
453
return hash . digest ( 'hex' ) ;
485
454
}
455
+
456
+ //==========================================
457
+ // legacy filesystem API
458
+
459
+ // more aliases (to cause less churn)
460
+ @injectable ( )
461
+ export class FileSystem implements IFileSystem {
462
+ // We expose this for the sake of functional tests that do not have
463
+ // access to the actual "vscode" namespace.
464
+ protected utils : FileSystemUtils ;
465
+ constructor ( ) {
466
+ this . utils = FileSystemUtils . withDefaults ( ) ;
467
+ }
468
+
469
+ public get directorySeparatorChar ( ) : string {
470
+ return this . utils . paths . sep ;
471
+ }
472
+ public arePathsSame ( path1 : string , path2 : string ) : boolean {
473
+ return this . utils . pathUtils . arePathsSame ( path1 , path2 ) ;
474
+ }
475
+ public async stat ( filename : string ) : Promise < FileStat > {
476
+ return this . utils . raw . stat ( filename ) ;
477
+ }
478
+ public async createDirectory ( dirname : string ) : Promise < void > {
479
+ return this . utils . createDirectory ( dirname ) ;
480
+ }
481
+ public async deleteDirectory ( dirname : string ) : Promise < void > {
482
+ return this . utils . deleteDirectory ( dirname ) ;
483
+ }
484
+ public async listdir ( dirname : string ) : Promise < [ string , FileType ] [ ] > {
485
+ return this . utils . listdir ( dirname ) ;
486
+ }
487
+ public async readFile ( filePath : string ) : Promise < string > {
488
+ return this . utils . raw . readText ( filePath ) ;
489
+ }
490
+ public async readData ( filePath : string ) : Promise < Buffer > {
491
+ return this . utils . raw . readData ( filePath ) ;
492
+ }
493
+ public async writeFile ( filename : string , data : { } ) : Promise < void > {
494
+ return this . utils . raw . writeText ( filename , data ) ;
495
+ }
496
+ public async appendFile ( filename : string , text : string ) : Promise < void > {
497
+ return this . utils . raw . appendText ( filename , text ) ;
498
+ }
499
+ public async copyFile ( src : string , dest : string ) : Promise < void > {
500
+ return this . utils . raw . copyFile ( src , dest ) ;
501
+ }
502
+ public async deleteFile ( filename : string ) : Promise < void > {
503
+ return this . utils . deleteFile ( filename ) ;
504
+ }
505
+ public async chmod ( filename : string , mode : string ) : Promise < void > {
506
+ return this . utils . raw . chmod ( filename , mode ) ;
507
+ }
508
+ public async move ( src : string , tgt : string ) {
509
+ await this . utils . raw . move ( src , tgt ) ;
510
+ }
511
+ public readFileSync ( filePath : string ) : string {
512
+ return this . utils . raw . readTextSync ( filePath ) ;
513
+ }
514
+ public createReadStream ( filePath : string ) : ReadStream {
515
+ return this . utils . raw . createReadStream ( filePath ) ;
516
+ }
517
+ public createWriteStream ( filePath : string ) : WriteStream {
518
+ return this . utils . raw . createWriteStream ( filePath ) ;
519
+ }
520
+ public async fileExists ( filename : string ) : Promise < boolean > {
521
+ return this . utils . fileExists ( filename ) ;
522
+ }
523
+ public fileExistsSync ( filename : string ) : boolean {
524
+ return this . utils . fileExistsSync ( filename ) ;
525
+ }
526
+ public async directoryExists ( dirname : string ) : Promise < boolean > {
527
+ return this . utils . directoryExists ( dirname ) ;
528
+ }
529
+ public async getSubDirectories ( dirname : string ) : Promise < string [ ] > {
530
+ return this . utils . getSubDirectories ( dirname ) ;
531
+ }
532
+ public async getFiles ( dirname : string ) : Promise < string [ ] > {
533
+ return this . utils . getFiles ( dirname ) ;
534
+ }
535
+ public async getFileHash ( filename : string ) : Promise < string > {
536
+ return this . utils . getFileHash ( filename ) ;
537
+ }
538
+ public async search ( globPattern : string , cwd ?: string ) : Promise < string [ ] > {
539
+ return this . utils . search ( globPattern , cwd ) ;
540
+ }
541
+ public async createTemporaryFile ( suffix : string ) : Promise < TemporaryFile > {
542
+ return this . utils . tmp . createFile ( suffix ) ;
543
+ }
544
+ public async isDirReadonly ( dirname : string ) : Promise < boolean > {
545
+ return this . utils . isDirReadonly ( dirname ) ;
546
+ }
547
+ }
0 commit comments