import { DownloadError } from './ChartDownload' ; import { tempPath } from '../../shared/Paths' ; import { AnyFunction } from '../../shared/UtilFunctions' ; import { devLog } from '../../shared/ElectronUtilFunctions' ; import { randomBytes as _randomBytes } from 'crypto' ; import { mkdir , access , constants } from 'fs' ; import { join } from 'path' ; import { promisify } from 'util' ; import { getSettings } from '../SettingsHandler.ipc' ; const randomBytes = promisify ( _randomBytes ) ; type EventCallback = { 'start' : ( ) => void ; 'error' : ( err : DownloadError , retry : ( ) => void | Promise < void > ) => void ; 'complete' : ( tempPath : string ) => void ; } ; type Callbacks = { [ E in keyof EventCallback ] : EventCallback [ E ] ; } ; const filesystemErrors = { libraryFolder : ( ) => { return { header : 'Library folder not specified' , body : 'Please go to the settings to set your library folder.' } ; } , libraryAccess : ( err ) => fsError ( err , 'Failed to access library folder.' ) , destinationFolderExists : ( destinationPath ) => { return { header : 'This chart already exists in your library folder.' , body : destinationPath , isLink : true } ; } , mkdirError : ( err ) => fsError ( err , 'Failed to create temporary folder.' ) } ; function fsError ( err , description ) { return { header : description , body : `${ err . name }: ${ err . message }` } ; } export class FilesystemChecker { private callbacks = { } as Callbacks ; private wasCanceled = false ; constructor ( private destinationFolderName ) { } /**
     * Calls `callback` when `event` fires. (no events will be fired after `this.cancelDownload()` is called)
     */ on < E extends keyof EventCallback > ( event , callback ) { this . callbacks [ event ] = callback ; } /**
     * Check that the filesystem is set up for the download.
     */ beginCheck ( ) { this . callbacks . start ( ) ; this . checkLibraryFolder ( ) ; } /**
     * Verifies that the user has specified a library folder.
     */ private checkLibraryFolder ( ) { if ( getSettings ( ) . libraryPath == undefined ) { this . callbacks . error ( filesystemErrors . libraryFolder ( ) , ( ) => this . beginCheck ( ) ) ; } else { access ( getSettings ( ) . libraryPath , constants . W_OK , this . cancelable ( ( err ) => { if ( err ) { this . callbacks . error ( filesystemErrors . libraryAccess ( err ) , ( ) => this . beginCheck ( ) ) ; } else { this . checkDestinationFolder ( ) ; } } ) ) ; } } /**
     * Checks that the destination folder doesn't already exist.
     */ private checkDestinationFolder ( ) { const destinationPath = join ( getSettings ( ) . libraryPath , this . destinationFolderName ) ; access ( destinationPath , constants . F_OK , this . cancelable ( ( err ) => { if ( err ) { this . createDownloadFolder ( ) ; } else { this . callbacks . error ( filesystemErrors . destinationFolderExists ( destinationPath ) , ( ) => this . beginCheck ( ) ) ; } } ) ) ; } /**
     * Attempts to create a unique folder in Bridge's data paths.
     */ private async createDownloadFolder ( retryCount = 0 ) { const tempChartPath = join ( tempPath , `chart_${ ( await randomBytes ( 5 ) ) . toString ( 'hex' ) }` ) ; mkdir ( tempChartPath , this . cancelable ( ( err ) => { if ( err ) { if ( retryCount < 5 ) { devLog ( `Error creating folder [${ tempChartPath }], retrying with a different folder...` ) ; this . createDownloadFolder ( retryCount + 1 ) ; } else { this . callbacks . error ( filesystemErrors . mkdirError ( err ) , ( ) => this . createDownloadFolder ( ) ) ; } } else { this . callbacks . complete ( tempChartPath ) ; } } ) ) ; } /**
     * Stop the process of checking the filesystem permissions. (no more events will be fired after this is called)
     */ cancelCheck ( ) { this . wasCanceled = true ; } /**
     * Wraps a function that is able to be prevented if `this.cancelCheck()` was called.
     */ private cancelable < F extends AnyFunction > ( fn ) { return ( ... args ) : ReturnType < F > => { if ( this . wasCanceled ) { return ; } return fn ( ... Array . from ( args ) ) ; } ; } }