import slash from 'slash' ; import { Writable } from 'stream' ; import { isAbsolute , join , normalize } from 'path' ; import { Arguments , Capabilities , Events , ParsedOptions , REPL , Table , getCurrentTab , Util } from '@kui-shell/core' ; import { FStat } from '../lib/fstat' ; import { KuiGlobOptions , GlobStats } from '../lib/glob' ; import { pushInitDone , waitForMountsToFinish } from './initDone' ; type DirEntry = GlobStats ; export { DirEntry } ; export interface ParallelismOptions extends ParsedOptions { /** Parallelism */ P : number ; /** Memory per task */ m : string ; memory : string ; } /**
 * A Virtual File System implements `ls`, `cp`, etc. Filesystem
 * operations against filepaths that match this `mountPath` as a
 * prefix will be delegated to this `VFS` impl.
 *
 */ export interface VFS { /** Path to mount point */ mountPath : string ; /** Is this a local mount? */ isLocal : boolean ; /** Is this a virtual mount? i.e. one that works in a browser without server-side proxy support */ isVirtual : boolean ; /** Any tags that the provider might want to associate with their VFS */ tags ? : string [ ] ; /** Directory listing */ ls ( opts , filepaths ) : DirEntry [ ] | Promise < DirEntry [ ] > ; /** Insert filepath into directory */ cp ( opts , srcFilepaths , dstFilepath , srcIsSelf , dstIsSelf , srcProvider , dstProvider ) : Promise < string | Table | true | ( string | Table ) [ ] > ; /** Remove filepath */ rm ( opts , filepath , recursive ? ) : Promise < string | boolean > ; /** Fetch contents */ fstat ( opts , filepath , withData ? , enoentOk ? ) : Promise < FStat > ; /** Fetch content slice */ fslice ( filename , offset , length ) : Promise < string > ; /** Pipe a content slice to the given `stream` */ pipe ? : ( filename : string , offset : number , length : number , stream : Writable ) => Promise < void > ; /** write data to file */ fwrite ( opts , fullpath , data , options ? ) : Promise < void > ; /** Create a directory/bucket */ mkdir ( opts , filepath ) : Promise < void > ; /** remove a directory/bucket */ rmdir ( opts , filepath ) : Promise < void > ; } /**
 * Current VFS mounts
 *
 */ const _currentMounts = [ ] ; /**
 * Establish a total ordering of mounts, based on depth in the
 * tree. Sibling order is arbitrary, at the moment.
 *
 */ function orient ( A , B ) { const a = A . mountPath . split ( /\// ) . filter ( _ => _ ) ; const b = B . mountPath . split ( /\// ) . filter ( _ => _ ) ; let lastCommonIdx = 0 ; const N = Math . min ( a . length , b . length ) ; while ( ++ lastCommonIdx < N ) { if ( a [ lastCommonIdx ] !== b [ lastCommonIdx ] ) { break ; } } if ( lastCommonIdx === 0 || a . length === b . length ) { return 0 ; } else if ( lastCommonIdx === a . length ) { return - 1 ; } else { return b . length - a . length ; } } /** Low-level mount */ function _mount ( vfs ) { const existingIdx = _currentMounts . findIndex ( mount => mount . mountPath === vfs . mountPath ) ; if ( existingIdx >= 0 ) { _currentMounts . splice ( existingIdx , 1 , vfs ) ; } else { _currentMounts . push ( vfs ) ; _currentMounts . sort ( orient ) ; } } type VFSProducingFunction = ( repl : REPL ) => VFS | VFS [ ] | Promise < VFS | VFS [ ] > ; /** Invoke the given VFS-producing function, and mount all of the resulting VFS's */ async function mountAll ( { REPL } , vfsFn ) { const mounts = await vfsFn ( REPL ) ; const A = Array . isArray ( mounts ) ? mounts : [ mounts ] ; await Promise . all ( A . map ( _mount ) ) ; } /**
 * Mount a VFS
 *
 */ export function mount ( vfs , placeholderMountPath ? ) { if ( typeof vfs !== 'function' ) { _mount ( vfs ) ; } else { pushInitDone ( new Promise ( async ( resolve , reject ) => { const tab = getCurrentTab ( ) ; if ( ! tab ) { try { let debounce = false ; Events . eventBus . on ( '/tab/new' , async ( tab ) => { try { if ( ! debounce ) { debounce = true ; await mountAll ( tab , vfs ) ; resolve ( undefined ) ; } } catch ( err ) { console . error ( 'Error in mount 1' , err ) ; reject ( err ) ; } } ) ; } catch ( err ) { console . error ( 'Error in mount 2' , err ) ; reject ( err ) ; } } else { try { await mountAll ( tab , vfs ) ; resolve ( undefined ) ; } catch ( err ) { console . error ( 'Error in mount 3' , err ) ; reject ( err ) ; } } } ) , placeholderMountPath ) ; } } return export function absolute ( filepath ) { return isAbsolute ( Util . expandHomeDir ( filepath . toString ( ) ) ) ? filepath : normalize ( join ( Util . cwd ( ) , filepath ) ) ; } /** Lookup compiatible matching mount */ export function findMatchingMounts ( filepath , checkClient = false ) { const isClient = Capabilities . inBrowser ( ) ; filepath = absolute ( filepath ) ; const mounts = _currentMounts . filter ( mount => mount . mountPath . startsWith ( filepath ) && ( ! checkClient || ! isClient || mount . isVirtual ) ) ; return mounts ; } /**
 * Lookup compiatible mount. Returns one of:
 * - a VFS mount, if it encloses the given filepath
 *
 * - true if `allowInner` and there exists a mount s.t. filepath
 *     encloses it (i.e. filepath is a parent of some mount)
 *
 * - otherwise, the local VFS mount
 */ export async function findMount ( filepath , checkClient = false , allowInner = false ) { await waitForMountsToFinish ( filepath ) ; const isClient = Capabilities . inBrowser ( ) ; filepath = absolute ( filepath ) ; const splitPath = filepath . split ( /\// ) ; const possibilities = [ ] ; for ( let idx = 1 ; idx < splitPath . length ; idx ++ ) { if ( splitPath [ idx ] ) { possibilities . push ( '/' + splitPath . slice ( 1 , idx + 1 ) . join ( '/' ) ) ; } } let foundMount ; for ( let idx = 0 ; idx < _currentMounts . length ; idx ++ ) { const mount = _currentMounts [ idx ] ; const { mountPath } = mount ; if ( possibilities . includes ( mountPath ) && ( ! checkClient || ! isClient || mount . isVirtual ) ) { foundMount = mount ; break ; } } if ( ! foundMount ) { if ( allowInner ) { for ( let idx = 0 ; idx < _currentMounts . length ; idx ++ ) { const mount = _currentMounts [ idx ] ; if ( mount . mountPath . startsWith ( filepath ) && ( ! checkClient || ! isClient || mount . isVirtual ) ) { return ; } } } foundMount = _currentMounts . find ( mount => mount . isLocal ) ; } return foundMount ; } /** Lookup compatible mounts */ export async function multiFindMount ( filepaths , checkClient = false ) { if ( filepaths . length === 0 ) { return multiFindMount ( [ Util . cwd ( ) ] , checkClient ) ; } return ( await Promise . all ( filepaths . map ( async ( filepath ) => ( { filepaths : [ slash ( filepath ) ] , mount : await findMount ( filepath , checkClient ) } ) ) ) ) . filter ( _ => _ . mount !== undefined ) . reduce ( ( mounts , mount ) => { if ( mounts . length === 0 || mounts [ mounts . length - 1 ] . mount . mountPath !== mount . mount . mountPath ) { mounts . push ( mount ) ; } else { mounts [ mounts . length - 1 ] . filepaths . splice ( mounts . length , 0 , ... mount . filepaths ) ; } return mounts ; } , [ ] ) ; } /**
 * Does the filepath specify a local file?
 *
 */ export async function isLocal ( filepath ) { const mount = await findMount ( filepath ) ; return mount && mount . isLocal ; }