@@ -8,17 +8,19 @@ import * as fs from 'fs';
88import * as fsextra from 'fs-extra' ;
99import * as glob from 'glob' ;
1010import { injectable } from 'inversify' ;
11- import * as path from 'path' ;
11+ import * as fspath from 'path' ;
1212import * as tmp from 'tmp' ;
1313import * as vscode from 'vscode' ;
1414import { createDeferred } from '../utils/async' ;
1515import { getOSType , OSType } from '../utils/platform' ;
1616import {
1717 FileStat , FileType ,
18- IFileSystem , IFileSystemUtils , IRawFileSystem ,
18+ IFileSystem , IFileSystemPath , IFileSystemUtils , IRawFileSystem ,
1919 TemporaryFile , WriteStream
2020} from './types' ;
2121
22+ // tslint:disable:max-classes-per-file
23+
2224const ENCODING : string = 'utf8' ;
2325
2426function getFileType ( stat : FileStat ) : FileType {
@@ -46,7 +48,7 @@ interface IRawFS {
4648}
4749
4850interface IRawFSExtra {
49- chmod ( filePath : string , mode : string ) : Promise < void > ;
51+ chmod ( filePath : string , mode : string | number ) : Promise < void > ;
5052 readFile ( path : string , encoding : string ) : Promise < string > ;
5153 //tslint:disable-next-line:no-any
5254 writeFile ( path : string , data : any , options : any ) : Promise < void > ;
@@ -56,6 +58,7 @@ interface IRawFSExtra {
5658 mkdirp ( dirname : string ) : Promise < void > ;
5759 rmdir ( dirname : string ) : Promise < void > ;
5860 readdir ( dirname : string ) : Promise < string [ ] > ;
61+ remove ( dirname : string ) : Promise < void > ;
5962
6063 // non-async
6164 statSync ( filename : string ) : fsextra . Stats ;
@@ -67,7 +70,7 @@ interface IRawFSExtra {
6770// Later we will drop "FileSystem", switching usage to
6871// "FileSystemUtils" and then rename "RawFileSystem" to "FileSystem".
6972
70- class RawFileSystem {
73+ export class RawFileSystem implements IRawFileSystem {
7174 constructor (
7275 private readonly nodefs : IRawFS = fs ,
7376 private readonly fsExtra : IRawFSExtra = fsextra
@@ -88,18 +91,23 @@ class RawFileSystem {
8891 }
8992
9093 public async mkdirp ( dirname : string ) : Promise < void > {
91- return this . fsExtra . mkdirp ( dirname ) ;
94+ return this . fsExtra . mkdirp ( dirname )
95+ . catch ( _err => {
96+ //throw err;
97+ } ) ;
9298 }
9399
94100 public async rmtree ( dirname : string ) : Promise < void > {
95- return this . fsExtra . rmdir ( dirname ) ;
101+ return this . fsExtra . stat ( dirname )
102+ . then ( ( ) => this . fsExtra . remove ( dirname ) ) ;
103+ //.catch((err) => this.fsExtra.rmdir(dirname));
96104 }
97105
98106 public async rmfile ( filename : string ) : Promise < void > {
99107 return this . fsExtra . unlink ( filename ) ;
100108 }
101109
102- public async chmod ( filename : string , mode : string ) : Promise < void > {
110+ public async chmod ( filename : string , mode : string | number ) : Promise < void > {
103111 return this . fsExtra . chmod ( filename , mode ) ;
104112 }
105113
@@ -111,8 +119,18 @@ class RawFileSystem {
111119 return this . fsExtra . lstat ( filename ) ;
112120 }
113121
114- public async listdir ( dirname : string ) : Promise < string [ ] > {
115- return this . fsExtra . readdir ( dirname ) ;
122+ // Once we move to the VS Code API, this method becomes a trivial
123+ // wrapper and The "path" parameter can go away.
124+ public async listdir ( dirname : string , path : IFileSystemPath ) : Promise < [ string , FileType ] [ ] > {
125+ const names : string [ ] = await this . fsExtra . readdir ( dirname ) ;
126+ const promises = names
127+ . map ( name => {
128+ const filename = path . join ( dirname , name ) ;
129+ return this . lstat ( filename )
130+ . then ( stat => [ name , getFileType ( stat ) ] as [ string , FileType ] )
131+ . catch ( ( ) => [ name , FileType . Unknown ] as [ string , FileType ] ) ;
132+ } ) ;
133+ return Promise . all ( promises ) ;
116134 }
117135
118136 public async copyFile ( src : string , dest : string ) : Promise < void > {
@@ -168,13 +186,49 @@ class RawFileSystem {
168186 }
169187}
170188
171- // more aliases (to cause less churn)
189+ interface INodePath {
190+ join ( ...filenames : string [ ] ) : string ;
191+ normalize ( filename : string ) : string ;
192+ }
193+
194+ // Eventually we will merge PathUtils into FileSystemPath.
195+
196+ export class FileSystemPath implements IFileSystemPath {
197+ constructor (
198+ private readonly isWindows = ( getOSType ( ) === OSType . Windows ) ,
199+ private readonly raw : INodePath = fspath
200+ ) { }
201+
202+ public join ( ...filenames : string [ ] ) : string {
203+ return this . raw . join ( ...filenames ) ;
204+ }
205+
206+ public normCase ( filename : string ) : string {
207+ filename = this . raw . normalize ( filename ) ;
208+ return this . isWindows ? filename . toUpperCase ( ) : filename ;
209+ }
210+ }
211+
212+ // We *could* use ICryptUtils, but it's a bit overkill.
213+ function getHashString ( data : string ) : string {
214+ const hash = createHash ( 'sha512' )
215+ . update ( data ) ;
216+ return hash . digest ( 'hex' ) ;
217+ }
218+
219+ type GlobCallback = ( err : Error | null , matches : string [ ] ) => void ;
220+ //tslint:disable-next-line:no-any
221+ type TempCallback = ( err : any , path : string , fd : number , cleanupCallback : ( ) => void ) => void ;
222+
172223@injectable ( )
173224export class FileSystemUtils implements IFileSystemUtils {
174225 constructor (
175- private readonly isWindows = ( getOSType ( ) === OSType . Windows ) ,
176- //public readonly raw: IFileSystem = {}
177- public readonly raw : IRawFileSystem = new RawFileSystem ( )
226+ public readonly raw : IRawFileSystem = new RawFileSystem ( ) ,
227+ public readonly path : IFileSystemPath = new FileSystemPath ( ) ,
228+ private readonly getHash = getHashString ,
229+ // tslint:disable-next-line:no-unnecessary-callback-wrapper
230+ private readonly globFile = ( ( pat : string , cb : GlobCallback ) => glob ( pat , cb ) ) ,
231+ private readonly makeTempFile = ( ( s : string , cb : TempCallback ) => tmp . file ( { postfix : s } , cb ) )
178232 ) { }
179233
180234 //****************************
@@ -196,13 +250,12 @@ export class FileSystemUtils implements IFileSystemUtils {
196250 // helpers
197251
198252 public arePathsSame ( path1 : string , path2 : string ) : boolean {
199- path1 = path . normalize ( path1 ) ;
200- path2 = path . normalize ( path2 ) ;
201- if ( this . isWindows ) {
202- return path1 . toUpperCase ( ) === path2 . toUpperCase ( ) ;
203- } else {
204- return path1 === path2 ;
253+ if ( path1 === path2 ) {
254+ return true ;
205255 }
256+ path1 = this . path . normCase ( path1 ) ;
257+ path2 = this . path . normCase ( path2 ) ;
258+ return path1 === path2 ;
206259 }
207260
208261 public async pathExists (
@@ -212,7 +265,7 @@ export class FileSystemUtils implements IFileSystemUtils {
212265 let stat : FileStat ;
213266 try {
214267 stat = await this . raw . stat ( filename ) ;
215- } catch {
268+ } catch ( err ) {
216269 return false ;
217270 }
218271 if ( fileType === undefined ) {
@@ -231,7 +284,7 @@ export class FileSystemUtils implements IFileSystemUtils {
231284 public async directoryExists ( dirname : string ) : Promise < boolean > {
232285 return this . pathExists ( dirname , FileType . Directory ) ;
233286 }
234- public fileExistsSync ( filename : string ) : boolean {
287+ public pathExistsSync ( filename : string ) : boolean {
235288 try {
236289 this . raw . statSync ( filename ) ;
237290 } catch {
@@ -240,56 +293,47 @@ export class FileSystemUtils implements IFileSystemUtils {
240293 return true ;
241294 }
242295
243- public async listdir (
244- dirname : string
245- ) : Promise < [ string , FileType ] [ ] > {
246- const filenames : string [ ] = await (
247- this . raw . listdir ( dirname )
248- . then ( names => names . map ( name => path . join ( dirname , name ) ) )
249- . catch ( ( ) => [ ] )
250- ) ;
251- const promises = filenames
252- . map ( filename => (
253- this . raw . stat ( filename )
254- . then ( stat => [ filename , getFileType ( stat ) ] as [ string , FileType ] )
255- . catch ( ( ) => [ filename , FileType . Unknown ] as [ string , FileType ] )
256- ) ) ;
257- return Promise . all ( promises ) ;
296+ public async listdir ( dirname : string ) : Promise < [ string , FileType ] [ ] > {
297+ try {
298+ return await this . raw . listdir ( dirname , this . path ) ;
299+ } catch {
300+ return [ ] ;
301+ }
258302 }
259303 public async getSubDirectories ( dirname : string ) : Promise < string [ ] > {
260304 return ( await this . listdir ( dirname ) )
261- . filter ( ( [ _filename , fileType ] ) => fileType === FileType . Directory )
262- . map ( ( [ filename , _fileType ] ) => filename ) ;
305+ . filter ( ( [ _name , fileType ] ) => fileType === FileType . Directory )
306+ . map ( ( [ name , _fileType ] ) => this . path . join ( dirname , name ) ) ;
263307 }
264308 public async getFiles ( dirname : string ) : Promise < string [ ] > {
265309 return ( await this . listdir ( dirname ) )
266- . filter ( ( [ _filename , fileType ] ) => fileType === FileType . File )
267- . map ( ( [ filename , _fileType ] ) => filename ) ;
310+ . filter ( ( [ _name , fileType ] ) => fileType === FileType . File )
311+ . map ( ( [ name , _fileType ] ) => this . path . join ( dirname , name ) ) ;
268312 }
269313
270314 public async isDirReadonly ( dirname : string ) : Promise < boolean > {
271315 // Alternative: use tmp.file().
272- const filename = path . join ( dirname , '___vscpTest___' ) ;
316+ const filename = this . path . join ( dirname , '___vscpTest___' ) ;
273317 try {
274318 await this . raw . touch ( filename ) ;
275319 } catch {
276- return false ;
320+ await this . raw . stat ( dirname ) ; // fails if does not exist
321+ return true ;
277322 }
278323 await this . raw . rmfile ( filename ) ;
279- return true ;
324+ return false ;
280325 }
281326
282327 public async getFileHash ( filename : string ) : Promise < string > {
283328 const stat = await this . raw . lstat ( filename ) ;
284- const hash = createHash ( 'sha512' )
285- . update ( `${ stat . ctimeMs } -${ stat . mtimeMs } ` ) ;
286- return hash . digest ( 'hex' ) ;
329+ const data = `${ stat . ctimeMs } -${ stat . mtimeMs } ` ;
330+ return this . getHash ( data ) ;
287331 }
288332
289333 public async search ( globPattern : string ) : Promise < string [ ] > {
290334 // We could use util.promisify() here.
291335 return new Promise < string [ ] > ( ( resolve , reject ) => {
292- glob ( globPattern , ( ex , files ) => {
336+ this . globFile ( globPattern , ( ex , files ) => {
293337 if ( ex ) {
294338 return reject ( ex ) ;
295339 }
@@ -301,7 +345,7 @@ export class FileSystemUtils implements IFileSystemUtils {
301345 public async createTemporaryFile ( suffix : string ) : Promise < TemporaryFile > {
302346 // We could use util.promisify() here.
303347 return new Promise < TemporaryFile > ( ( resolve , reject ) => {
304- tmp . file ( { postfix : suffix } , ( err , tmpFile , _ , cleanupCallback ) => {
348+ this . makeTempFile ( suffix , ( err , tmpFile , _ , cleanupCallback ) => {
305349 if ( err ) {
306350 return reject ( err ) ;
307351 }
@@ -314,8 +358,21 @@ export class FileSystemUtils implements IFileSystemUtils {
314358 }
315359}
316360
361+ // more aliases (to cause less churn)
317362@injectable ( )
318363export class FileSystem extends FileSystemUtils implements IFileSystem {
364+ constructor (
365+ isWindows : boolean = ( getOSType ( ) === OSType . Windows )
366+ ) {
367+ super (
368+ new RawFileSystem ( ) ,
369+ new FileSystemPath ( isWindows )
370+ ) ;
371+ }
372+
373+ //****************************
374+ // aliases
375+
319376 public async stat ( filePath : string ) : Promise < vscode . FileStat > {
320377 // Do not import vscode directly, as this isn't available in the Debugger Context.
321378 // If stat is used in debugger context, it will fail, however theres a separate PR that will resolve this.
@@ -340,6 +397,10 @@ export class FileSystem extends FileSystemUtils implements IFileSystem {
340397 return this . raw . copyFile ( src , dest ) ;
341398 }
342399
400+ public fileExistsSync ( filename : string ) : boolean {
401+ return this . pathExistsSync ( filename ) ;
402+ }
403+
343404 public readFileSync ( filename : string ) : string {
344405 return this . raw . readTextSync ( filename ) ;
345406 }
0 commit comments