@@ -78,14 +78,19 @@ class SftpClient {
7878 if ( stat ) options += "d" ;
7979
8080 sftp . exec (
81- `ls ${ options } --full-time "${ path } " | awk '{$2=\"\"; print $0}'` ,
82- ( res ) => {
81+ `ls ${ options } --full-time -L "${ path } " | awk '{$2=\"\"; print $0}'` ,
82+ async ( res ) => {
8383 if ( res . code <= 0 ) {
8484 if ( stat ) {
85- resolve ( this . #parseFile( res . result , Url . dirname ( filename ) ) ) ;
85+ const file = await this . #parseFile(
86+ res . result ,
87+ Url . dirname ( filename ) ,
88+ ) ;
89+ resolve ( file ) ;
8690 return ;
8791 }
88- resolve ( this . #parseDir( filename , res . result ) ) ;
92+ const dirList = await this . #parseDir( filename , res . result ) ;
93+ resolve ( dirList ) ;
8994 return ;
9095 }
9196 reject ( this . #errorCodes( res . code ) ) ;
@@ -490,18 +495,19 @@ class SftpClient {
490495 * @param {String } dirname
491496 * @param {String } res
492497 */
493- #parseDir( dirname , res ) {
498+ async #parseDir( dirname , res ) {
494499 if ( ! res ) return [ ] ;
495500
496501 const list = res . split ( "\n" ) ;
497502
498503 if ( / t o t a l / . test ( list [ 0 ] ) ) list . splice ( 0 , 1 ) ;
499504
500- const fileList = list . map ( ( i ) => this . #parseFile( i , dirname ) ) ;
505+ const filePromises = list . map ( ( i ) => this . #parseFile( i , dirname ) ) ;
506+ const fileList = await Promise . all ( filePromises ) ;
501507 return fileList . filter ( ( i ) => ! ! i ) ;
502508 }
503509
504- #parseFile( item , dirname ) {
510+ async #parseFile( item , dirname ) {
505511 if ( ! item ) return null ;
506512 const PERMISSIONS = 0 ;
507513 const SIZE = 2 ;
@@ -536,9 +542,67 @@ class SftpClient {
536542 const canrw = permissions . substr ( 1 , 2 ) ;
537543 const type = DIR_TYPE ( permissions [ 0 ] ) ;
538544
545+ let isDirectory = type === "directory" ;
546+ let isFile = type === "file" ;
547+ let targetType = type ;
548+
539549 if ( type === "link" ) {
540550 name . splice ( name . indexOf ( "->" ) ) ;
551+ if ( name . length === 0 ) return null ;
552+ const symlinkPath = Url . join (
553+ dirname ,
554+ Url . basename ( name [ name . length - 1 ] ) ,
555+ ) ;
556+ let linkTarget = await new Promise ( ( resolve , reject ) => {
557+ sftp . exec (
558+ `readlink "${ this . #safeName( symlinkPath ) } "` ,
559+ ( res ) => {
560+ if ( res . code <= 0 ) {
561+ resolve ( res . result . trim ( ) ) ;
562+ } else {
563+ reject ( this . #errorCodes( res . code ) ) ;
564+ }
565+ } ,
566+ ( err ) => {
567+ reject ( err ) ;
568+ } ,
569+ ) ;
570+ } ) ;
571+ linkTarget = linkTarget . startsWith ( "/" )
572+ ? linkTarget
573+ : Url . join ( dirname , linkTarget ) ;
574+
575+ targetType = await new Promise ( ( resolve , reject ) => {
576+ sftp . exec (
577+ `ls -ld "${ this . #safeName( linkTarget ) } "` ,
578+ ( res ) => {
579+ if ( res . code <= 0 ) {
580+ const output = res . result . trim ( ) ;
581+ switch ( output [ 0 ] ) {
582+ case "d" :
583+ resolve ( "directory" ) ;
584+ break ;
585+ default :
586+ resolve ( "file" ) ;
587+ }
588+ } else {
589+ resolve ( [ "file" , this . #errorCodes( res . code ) ] ) ;
590+ }
591+ } ,
592+ ( err ) => {
593+ // If the exec fails, maybe the target is deleted, continue anyway
594+ resolve ( [ "file" , this . #errorCodes( res . code ) ] ) ;
595+ } ,
596+ ) ;
597+ } ) ;
598+ if ( Array . isArray ( targetType ) ) {
599+ const [ type , err ] = targetType ;
600+ targetType = type ;
601+ }
602+ isDirectory = targetType === "directory" ;
603+ isFile = targetType === "file" ;
541604 }
605+
542606 name = Url . basename ( name . join ( " " ) ) ;
543607 if ( [ ".." , "." , "`" ] . includes ( name ) ) return null ;
544608
@@ -550,13 +614,13 @@ class SftpClient {
550614 url,
551615 name,
552616 size,
553- type,
617+ type : targetType ,
554618 uri : url ,
555619 canRead : / r / . test ( canrw ) ,
556620 canWrite : / w / . test ( canrw ) ,
557- isDirectory : type === "directory" ,
621+ isDirectory,
558622 isLink : type === "link" ,
559- isFile : type === "file" ,
623+ isFile,
560624 modifiedDate : date ,
561625 } ;
562626 }
0 commit comments