module xmcl import { open , readEntry , readAllEntries } from "@xmcl/unzip" ; import { access , stat , writeFile , readFile , readdir } from "fs" ; import { join , sep } from "path" ; import { FileSystem } from "./system" ; import { promisify } from "util" ; import { ZipFile , Entry } from "yauzl" ; const paccess = promisify ( access ) ; const pstat = promisify ( stat ) ; const pwriteFile = promisify ( writeFile ) ; const preadFile = promisify ( readFile ) ; const preaddir = promisify ( readdir ) ; export async function openFileSystem ( basePath ) { if ( typeof basePath === "string" ) { const stat = await pstat ( basePath ) ; if ( stat . isDirectory ( ) ) { return new NodeFileSystem ( basePath ) ; } else { const zip = await open ( basePath ) ; const entries = await readAllEntries ( zip ) ; const entriesRecord = { } ; for ( const entry of entries ) { entriesRecord [ entry . fileName ] = entry ; } return new NodeZipFileSystem ( basePath , zip , entriesRecord ) ; } } else { const zip = await open ( basePath as Buffer ) ; const entries = await readAllEntries ( zip ) ; const entriesRecord = { } ; for ( const entry of entries ) { entriesRecord [ entry . fileName ] = entry ; } return new NodeZipFileSystem ( "" , zip , entriesRecord ) ; } } export function resolveFileSystem ( base ) { if ( typeof base === "string" || base instanceof Uint8Array || base instanceof Buffer ) { return openFileSystem ( base ) ; } else { return Promise . resolve ( base ) ; } } class NodeFileSystem extends FileSystem { sep = sep ; type = "path" as const ; writeable = true ; join ( ... paths ) { return join ( ... paths ) ; } getUrl ( name ) { return `file://${ this . join ( this . root , name ) }` ; } isDirectory ( name ) { return pstat ( join ( this . root , name ) ) . then ( ( s ) => s . isDirectory ( ) ) ; } writeFile ( name , data ) { return pwriteFile ( join ( this . root , name ) , data ) ; } existsFile ( name ) { return paccess ( join ( this . root , name ) ) . then ( ( ) => true , ( ) => false ) ; } readFile ( name , encoding ? ) { return preadFile ( join ( this . root , name ) , { encoding } ) as any ; } listFiles ( name ) { return preaddir ( join ( this . root , name ) ) ; } cd ( name ) { this . root = join ( this . root , name ) ; } constructor ( public root ) { super ( ) ; } } class NodeZipFileSystem extends FileSystem { sep = "/" ; type = "zip" as const ; writeable = false ; private zipRoot : string = "" ; private fileRoot : string ; constructor ( root , private zip , private entries ) { super ( ) ; this . fileRoot = root ; } close ( ) { this . zip . close ( ) ; } get root ( ) { return this . fileRoot + ( this . zipRoot . length === 0 ? "" : `/${ this . zipRoot }` ) ; } protected normalizePath ( name ) { if ( name . startsWith ( "/" ) ) { name = name . substring ( 1 ) ; } if ( this . zipRoot !== "" ) { name = [ this . root , name ] . join ( "/" ) ; } return name ; } join ( ... paths ) { return paths . join ( "/" ) ; } isDirectory ( name ) { name = this . normalizePath ( name ) ; if ( name === "" ) { return Promise . resolve ( true ) ; } if ( this . entries [ name ] ) { return Promise . resolve ( name . endsWith ( "/" ) ) ; } if ( this . entries [ name + "/" ] ) { return Promise . resolve ( true ) ; } const entries = Object . keys ( this . entries ) ; return Promise . resolve ( entries . some ( ( e ) => e . startsWith ( name + "/" ) ) ) ; } existsFile ( name ) { name = this . normalizePath ( name ) ; if ( this . entries [ name ] || this . entries [ name + "/" ] ) { return Promise . resolve ( true ) ; } const entries = Object . keys ( this . entries ) ; return Promise . resolve ( entries . some ( ( e ) => e . startsWith ( name + "/" ) ) ) ; } async readFile ( name , encoding ? ) { name = this . normalizePath ( name ) ; const entry = this . entries [ name ] ; if ( ! entry ) { throw new Error ( `Not found file named ${ name }` ) ; } const buffer = await readEntry ( this . zip , entry ) ; if ( encoding === "utf-8" ) { return buffer . toString ( "utf-8" ) ; } if ( encoding === "base64" ) { return buffer . toString ( "base64" ) ; } return buffer ; } listFiles ( name ) { name = this . normalizePath ( name ) ; return Promise . resolve ( [ ... new Set ( Object . keys ( this . entries ) . filter ( ( n ) => n . startsWith ( name ) ) . map ( ( n ) => n . substring ( name . length ) ) . map ( ( n ) => n . startsWith ( "/" ) ? n . substring ( 1 ) : n ) . map ( ( n ) => n . split ( "/" ) [ 0 ] ) ) , ] ) ; } cd ( name ) { if ( name . startsWith ( "/" ) ) { this . zipRoot = name . substring ( 1 ) ; return ; } let paths = name . split ( "/" ) ; for ( let path of paths ) { if ( path === "." ) { continue ; } else if ( path === ".." ) { let sub = this . zipRoot . split ( "/" ) ; if ( sub . length > 0 ) { sub . pop ( ) ; this . zipRoot = sub . join ( "/" ) ; } } else { if ( this . zipRoot === "" ) { this . zipRoot = path ; } else { this . zipRoot += `/${ path }` ; } } } } async walkFiles ( startingDir , walker ) { startingDir = this . normalizePath ( startingDir ) ; const root = startingDir . startsWith ( "/" ) ? startingDir . substring ( 1 ) : startingDir ; for ( const child of Object . keys ( this . entries ) . filter ( ( e ) => e . startsWith ( root ) ) ) { if ( child . endsWith ( "/" ) ) { continue ; } const result = walker ( child ) ; if ( result instanceof Promise ) { await result ; } } } } export * from "./system" ;