import process from './process' ; import { bufferAllocUnsafe , bufferFrom } from './internal/buffer' ; import { constants , S } from './constants' ; import { Volume } from './volume' ; import { EventEmitter } from 'events' ; import Stats from './Stats' ; const { S_IFMT , S_IFDIR , S_IFREG , S_IFLNK , O_APPEND } = constants ; export const SEP = '/' ; /**
 * Node in a file system (like i-node, v-node).
 */ export class Node extends EventEmitter { ino : number ; uid : number = process . getuid ( ) ; gid : number = process . getgid ( ) ; atime = new Date ( ) ; mtime = new Date ( ) ; ctime = new Date ( ) ; buf : Buffer ; perm = 0o666 ; mode = S_IFREG ; nlink = 1 ; symlink : string [ ] ; constructor ( ino , perm = 0o666 ) { super ( ) ; this . perm = perm ; this . mode |= perm ; this . ino = ino ; } getString ( encoding = 'utf8' ) { return this . getBuffer ( ) . toString ( encoding ) ; } setString ( str ) { this . buf = bufferFrom ( str , 'utf8' ) ; this . touch ( ) ; } getBuffer ( ) { if ( ! this . buf ) this . setBuffer ( bufferAllocUnsafe ( 0 ) ) ; return bufferFrom ( this . buf ) ; } setBuffer ( buf ) { this . buf = bufferFrom ( buf ) ; this . touch ( ) ; } getSize ( ) { return this . buf ? this . buf . length : 0 ; } setModeProperty ( property ) { this . mode = ( this . mode & ~ S_IFMT ) | property ; } setIsFile ( ) { this . setModeProperty ( S_IFREG ) ; } setIsDirectory ( ) { this . setModeProperty ( S_IFDIR ) ; } setIsSymlink ( ) { this . setModeProperty ( S_IFLNK ) ; } isFile ( ) { return ( this . mode & S_IFMT ) === S_IFREG ; } isDirectory ( ) { return ( this . mode & S_IFMT ) === S_IFDIR ; } isSymlink ( ) { return ( this . mode & S_IFMT ) === S_IFLNK ; } makeSymlink ( steps ) { this . symlink = steps ; this . setIsSymlink ( ) ; } write ( buf , off = 0 , len = buf . length , pos = 0 ) { if ( ! this . buf ) this . buf = bufferAllocUnsafe ( 0 ) ; if ( pos + len > this . buf . length ) { const newBuf = bufferAllocUnsafe ( pos + len ) ; this . buf . copy ( newBuf , 0 , 0 , this . buf . length ) ; this . buf = newBuf ; } buf . copy ( this . buf , pos , off , off + len ) ; this . touch ( ) ; return len ; } read ( buf , off = 0 , len = buf . byteLength , pos = 0 ) { if ( ! this . buf ) this . buf = bufferAllocUnsafe ( 0 ) ; let actualLen = len ; if ( actualLen > buf . byteLength ) { actualLen = buf . byteLength ; } if ( actualLen + pos > this . buf . length ) { actualLen = this . buf . length - pos ; } this . buf . copy ( buf as Buffer , off , pos , pos + actualLen ) ; return actualLen ; } truncate ( len = 0 ) { if ( ! len ) this . buf = bufferAllocUnsafe ( 0 ) ; else { if ( ! this . buf ) this . buf = bufferAllocUnsafe ( 0 ) ; if ( len <= this . buf . length ) { this . buf = this . buf . slice ( 0 , len ) ; } else { const buf = bufferAllocUnsafe ( 0 ) ; this . buf . copy ( buf ) ; buf . fill ( 0 , len ) ; } } this . touch ( ) ; } chmod ( perm ) { this . perm = perm ; this . mode = ( this . mode & ~ 0o777 ) | perm ; this . touch ( ) ; } chown ( uid , gid ) { this . uid = uid ; this . gid = gid ; this . touch ( ) ; } touch ( ) { this . mtime = new Date ( ) ; this . emit ( 'change' , this ) ; } canRead ( uid = process . getuid ( ) , gid = process . getgid ( ) ) { if ( this . perm & S . IROTH ) { return true ; } if ( gid === this . gid ) { if ( this . perm & S . IRGRP ) { return true ; } } if ( uid === this . uid ) { if ( this . perm & S . IRUSR ) { return true ; } } return false ; } canWrite ( uid = process . getuid ( ) , gid = process . getgid ( ) ) { if ( this . perm & S . IWOTH ) { return true ; } if ( gid === this . gid ) { if ( this . perm & S . IWGRP ) { return true ; } } if ( uid === this . uid ) { if ( this . perm & S . IWUSR ) { return true ; } } return false ; } del ( ) { this . emit ( 'delete' , this ) ; } toJSON ( ) { return { ino : this . ino , uid : this . uid , gid : this . gid , atime : this . atime . getTime ( ) , mtime : this . mtime . getTime ( ) , ctime : this . ctime . getTime ( ) , perm : this . perm , mode : this . mode , nlink : this . nlink , symlink : this . symlink , data : this . getString ( ) , } ; } } /**
 * Represents a hard link that points to an i-node `node`.
 */ export class Link extends EventEmitter { vol : Volume ; parent : Link ; children : { [ child ] : Link | undefined ; } = { } ; private _steps : string [ ] = [ ] ; node : Node ; ino : number = 0 ; length : number = 0 ; name : string ; get steps ( ) { return this . _steps ; } set steps ( val ) { this . _steps = val ; for ( const child of Object . values ( this . children ) ) { child ?. syncSteps ( ) ; } } constructor ( vol , parent , name ) { super ( ) ; this . vol = vol ; this . parent = parent ; this . name = name ; this . syncSteps ( ) ; } setNode ( node ) { this . node = node ; this . ino = node . ino ; } getNode ( ) { return this . node ; } createChild ( name , node = this . vol . createNode ( ) ) { const link = new Link ( this . vol , this , name ) ; link . setNode ( node ) ; if ( node . isDirectory ( ) ) { } this . setChild ( name , link ) ; return link ; } setChild ( name , link = new Link ( this . vol , this , name ) ) { this . children [ name ] = link ; link . parent = this ; this . length ++ ; this . emit ( 'child:add' , link , this ) ; return link ; } deleteChild ( link ) { delete this . children [ link . getName ( ) ] ; this . length -- ; this . emit ( 'child:delete' , link , this ) ; } getChild ( name ) { if ( Object . hasOwnProperty . call ( this . children , name ) ) { return this . children [ name ] ; } } getPath ( ) { return this . steps . join ( SEP ) ; } getName ( ) { return this . steps [ this . steps . length - 1 ] ; } param steps { string [ ] } param stop { number } param i { number } return { Link | null } walk ( steps , stop = steps . length , i = 0 ) { if ( i >= steps . length ) return this ; if ( i >= stop ) return this ; const step = steps [ i ] ; const link = this . getChild ( step ) ; if ( ! link ) return null ; return link . walk ( steps , stop , i + 1 ) ; } toJSON ( ) { return { steps : this . steps , ino : this . ino , children : Object . keys ( this . children ) , } ; } syncSteps ( ) { this . steps = this . parent ? this . parent . steps . concat ( [ this . name ] ) : [ this . name ] ; } } /**
 * Represents an open file (file descriptor) that points to a `Link` (Hard-link) and a `Node`.
 */ export class File { fd : number ; type { any } link : Link ; type { Node } node : Node ; /**
     * A cursor/offset position in a file, where data will be written on write.
     * User can "seek" this position.
     */ position : number = 0 ; flags : number ; param link param node param flags param fd constructor ( link , node , flags , fd ) { this . link = link ; this . node = node ; this . flags = flags ; this . fd = fd ; } getString ( encoding = 'utf8' ) { return this . node . getString ( ) ; } setString ( str ) { this . node . setString ( str ) ; } getBuffer ( ) { return this . node . getBuffer ( ) ; } setBuffer ( buf ) { this . node . setBuffer ( buf ) ; } getSize ( ) { return this . node . getSize ( ) ; } truncate ( len ? ) { this . node . truncate ( len ) ; } seekTo ( position ) { this . position = position ; } stats ( ) { return Stats . build ( this . node ) as Stats < number > ; } write ( buf , offset = 0 , length = buf . length , position ? ) { if ( typeof position !== 'number' ) position = this . position ; if ( this . flags & O_APPEND ) position = this . getSize ( ) ; const bytes = this . node . write ( buf , offset , length , position ) ; this . position = position + bytes ; return bytes ; } read ( buf , offset = 0 , length = buf . byteLength , position ? ) { if ( typeof position !== 'number' ) position = this . position ; const bytes = this . node . read ( buf , offset , length , position ) ; this . position = position + bytes ; return bytes ; } chmod ( perm ) { this . node . chmod ( perm ) ; } chown ( uid , gid ) { this . node . chown ( uid , gid ) ; } }