import Debug from 'debug' ; import { basename } from 'path' ; import { Writable } from 'stream' ; import { Arguments , CodedError , Util } from '@kui-shell/core' ; import { DirEntry , VFS , absolute , findMount , multiFindMount , findMatchingMounts } from '.' ; import CommandsFS from './CommandsFS' ; const debug = Debug ( 'plugin/bash-like/fs/vfs/delegates' ) ; /** '/a/b/c' -> 3 */ function countSlashes ( path ) { let count = 0 ; for ( let idx = 0 ; idx < path . length ; idx ++ ) { if ( path . charAt ( idx ) === '/' ) { count ++ ; } } return count ; } /** ('/a/b/c', 2) => '/a/b' */ function cropToSlashDepth ( path , depth ) { let count = 0 ; for ( let idx = 0 ; idx < path . length ; idx ++ ) { if ( path . charAt ( idx ) === '/' ) { if ( count ++ === depth ) { return path . slice ( 0 , idx ) ; } } } return path ; } /** Remove duplicates from an array of strings */ function removeDuplicates ( args ) { return args . filter ( ( { mountPath } , idx ) => args . findIndex ( _ => _ . mountPath === mountPath ) === idx ) ; } const blankStats = { size : 0 , mtimeMs : 0 , uid : 0 , gid : 0 , mode : 0 } ; /**
 * `ls` handler for mounts
 *
 */ async function lsMounts ( path ) { if ( ! /[*/]$/ . test ( path ) ) { path = path + '/' ; } const depthOfPath = countSlashes ( path ) ; try { const mounts = await findMatchingMounts ( path ) ; if ( mounts ) { return removeDuplicates ( mounts . filter ( mount => ! mount . isLocal ) . map ( mount => ( { mountPath : cropToSlashDepth ( mount . mountPath , depthOfPath ) , isLocal : mount . isLocal , tags : mount . tags } ) ) ) . map ( ( { mountPath , isLocal , tags } ) => ( { name : basename ( mountPath ) , nameForDisplay : basename ( mountPath ) , path : mountPath , stats : blankStats , dirent : { mount : { isLocal , tags , mountPath } , isFile : false , isDirectory : true , isSymbolicLink : false , isSpecial : false , isExecutable : false , permissions : '' , username : '' } } ) ) ; } } catch ( err ) { console . error ( 'tab completion vfs match mounts error' , err ) ; throw err ; } } function removeDuplicates2 ( vfses ) { const set = new Set ( ) ; return vfses . filter ( _ => { const canon = _ . path . replace ( /\/$/ , '' ) ; return ! set . has ( canon ) && set . add ( canon ) ; } ) ; } /**
 * ls delegate
 *
 */ export async function ls ( ... parameters ) { const filepaths = parameters [ 1 ] . length === 0 ? [ Util . cwd ( ) ] : parameters [ 1 ] . map ( absolute ) ; const mounts = await multiFindMount ( filepaths , true ) ; const vfsContentP = Promise . all ( mounts . map ( async ( { filepaths , mount } ) => { try { return await mount . ls ( parameters [ 0 ] , filepaths ) ; } catch ( err ) { if ( err . code !== 404 ) { if ( ! /globby is not defined/ . test ( err . message ) ) { debug ( err ) ; } throw err ; } } } ) ) . then ( Util . flatten ) ; const mountContentPerInput = Promise . all ( filepaths . map ( lsMounts ) ) ; const commandContentPromise = CommandsFS . ls ( parameters [ 0 ] , filepaths ) ; const mountContent = Util . flatten ( await mountContentPerInput ) ; if ( mounts . length === 0 && mountContent . length === 0 ) { const err = new Error ( `VFS not mounted: ${ filepaths }` ) ; err . code = 404 ; throw err ; } const commandContent = await commandContentPromise ; const vfsContent = ( await vfsContentP ) . filter ( _ => _ ) ; return removeDuplicates2 ( vfsContent . concat ( commandContent ) . concat ( mountContent ) ) . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ; } /**
 * cp delegate
 *
 */ export async function cp ( opts , srcFilepaths , dstFilepath ) { const mount1 = await Promise . all ( srcFilepaths . map ( _ => findMount ( _ ) ) ) ; const mount2 = await findMount ( dstFilepath ) ; if ( mount2 . isLocal ) { const localSrc = mount1 . map ( ( _ , idx ) => ( _ . isLocal ? srcFilepaths [ idx ] : undefined ) ) . filter ( _ => _ ) ; const nonLocalSrc = mount1 . map ( ( _ , idx ) => ( ! _ . isLocal ? { filepath : srcFilepaths [ idx ] , mount : _ } : undefined ) ) . filter ( _ => _ ) ; const tasks = localSrc . length === 0 ? [ ] : [ mount2 . cp ( opts , localSrc , dstFilepath , localSrc . map ( ( ) => true ) , true , mount1 , mount2 ) ] ; if ( nonLocalSrc . length > 0 ) { const mountThatManagesTheCopy = nonLocalSrc [ 0 ] . mount ; const dstIsSelf = mount2 === mountThatManagesTheCopy ; tasks . push ( mountThatManagesTheCopy . cp ( opts , nonLocalSrc . map ( _ => _ . filepath ) , dstFilepath , nonLocalSrc . map ( _ => _ . mount === mountThatManagesTheCopy ) , dstIsSelf , mount1 , mount2 ) ) ; } const responses = await Promise . all ( tasks ) ; if ( responses . length === 1 && responses [ 0 ] === true ) { return true ; } else { return responses . join ( ', ' ) ; } } else { const mountThatManagesTheCopy = mount2 ; const srcIsSelf = mount1 . map ( mount => mount === mountThatManagesTheCopy ) ; const dstIsSelf = mount2 === mountThatManagesTheCopy ; return mountThatManagesTheCopy . cp ( opts , srcFilepaths , dstFilepath , srcIsSelf , dstIsSelf , mount1 , mount2 ) ; } } /**
 * rm delegate
 *
 */ export async function rm ( ... parameters ) { const mount = await findMount ( parameters [ 1 ] ) ; return mount . rm ( parameters [ 0 ] , parameters [ 1 ] , parameters [ 2 ] ) ; } /**
 * fstat delegate
 *
 */ export async function fstat ( ... parameters ) { const mount = await findMount ( parameters [ 1 ] ) ; return mount . fstat ( parameters [ 0 ] , parameters [ 1 ] , parameters [ 2 ] , parameters [ 3 ] ) ; } /**
 * fwrite delegate
 *
 */ export async function fwrite ( ... parameters ) { const mount = await findMount ( parameters [ 1 ] ) ; return mount . fwrite ( parameters [ 0 ] , parameters [ 1 ] , parameters [ 2 ] , parameters [ 3 ] ) ; } /**
 * pipe delegate
 *
 */ export async function pipe ( filepath , offset , length , stream ) { const mount = await findMount ( filepath , undefined , true ) ; if ( ! mount || ! mount . pipe ) { throw new Error ( `pipe: can not find ${ filepath }` ) ; } return mount . pipe ( filepath , offset , length , stream ) ; } /**
 * fslice delegate
 *
 */ export async function fslice ( filepath , offsetAsProvided , length , unit = 'bytes' , end ? ) { const mount = await findMount ( filepath , undefined , true ) ; if ( ! mount ) { throw new Error ( `fslice: can not find ${ filepath }` ) ; } if ( unit === 'bytes' ) { return mount . fslice ( filepath , offsetAsProvided , length ) ; } else { let offset = offsetAsProvided ; let dataRead = '' ; while ( true ) { try { const data = await mount . fslice ( filepath , offset , end !== undefined ? end - offset : 4000 ) ; if ( data ) { dataRead = dataRead . concat ( data ) ; const lines = dataRead . split ( '\n' ) ; if ( lines . length >= Math . abs ( length ) ) { dataRead = ( length < 0 ? lines . slice ( length ) : lines . slice ( 0 , length ) ) . join ( '\n' ) ; break ; } else { offset = Buffer . from ( dataRead ) . length ; } } else { console . error ( 'bash-like fslice: no data read' ) ; break ; } } catch ( err ) { debug ( err ) ; break ; } } return dataRead ; } } /**
 * mkdir delegate
 *
 */ export async function mkdir ( ... parameters ) { const mount = await findMount ( parameters [ 1 ] ) ; return mount . mkdir ( parameters [ 0 ] , parameters [ 1 ] ) ; } /**
 * rmdir delegate
 *
 */ export async function rmdir ( ... parameters ) { const mount = await findMount ( parameters [ 1 ] ) ; return mount . rmdir ( parameters [ 0 ] , parameters [ 1 ] ) ; }