import { EventEmitter } from "events" ; import fs from "fs" ; import * as pathlib from "path" ; import hash from "@common/util/hash" ; import isFile from "@common/util/is-file" ; import isDir from "@common/util/is-dir" ; import { ignoreDir } from "@common/util/ignore-dir" ; import { ignoreFile } from "@common/util/ignore-file" ; import { IFileDesc , IDirectory } from "@common/files" ; import { FsalEvents , ChokidarEvents } from "@common/events" ; import FSALWatchdog from "@main/fsal/fsal-watcher" ; import { FSAL } from "@main/fsal/fsal" ; const FsalNotInitializedError = ( ) => { new Error ( "[fsal] not initialized!" ) ; } ; const FsalAlreadyInitializedError = ( ) => { new Error ( "[fsal] cannot initialize twice" ) ; } ; export default class FSALSystem extends EventEmitter implements FSAL { private _resources : null | { watchdog : FSALWatchdog ; globalWatchdog : FSALWatchdog ; } ; constructor ( ) { super ( ) ; this . _resources = null ; this . handleChokidarEvent = this . handleChokidarEvent . bind ( this ) ; this . handleGlobalChokidarEvent = this . handleGlobalChokidarEvent . bind ( this ) ; } readFile ( filePath ) { let fileText = null ; try { fileText = fs . readFileSync ( filePath , { encoding : "utf8" } ) ; } catch ( err ) { console . log ( err ) ; } return fileText ; } async saveFile ( filePath , fileText , mkdirs = false , overwrite = true ) { console . log ( "saveFile ::" , filePath , fileText ) ; try { if ( mkdirs ) { let dirname = pathlib . dirname ( filePath ) ; if ( ! fs . existsSync ( dirname ) ) { fs . mkdirSync ( dirname , { recursive : true } ) ; } } fs . writeFileSync ( filePath , fileText , { encoding : 'utf8' , flag : overwrite ? "w" : "wx" } ) ; } catch ( err ) { console . log ( err ) ; return false ; } return true ; } async createFile ( path , contents = "" ) { return this . saveFile ( path , contents , true , false ) ; } async parseDir ( dirPath , parent = null ) { let dir = { type : "directory" , parent : parent , path : dirPath , name : pathlib . basename ( dirPath ) , hash : hash ( dirPath ) , children : [ ] , modTime : 0 } ; try { let stats = await fs . promises . lstat ( dir . path ) ; dir . modTime = stats . ctimeMs ; } catch ( err ) { console . error ( `fsal-dir :: error reading metadata for directory ${ dir . path }` , err ) ; throw err ; } let children = await fs . promises . readdir ( dir . path ) ; for ( let child of children ) { todo todo let absolutePath = pathlib . join ( dir . path , child ) ; let pathIsDir = isDir ( absolutePath ) && ! ignoreDir ( absolutePath ) ; let pathIsFile = isFile ( absolutePath ) && ! ignoreFile ( absolutePath ) ; if ( pathIsFile ) { let file = await this . parseFile ( absolutePath , dir ) ; if ( file ) dir . children . push ( file ) ; } else if ( pathIsDir ) { dir . children . push ( await this . parseDir ( absolutePath , dir ) ) ; } } return dir ; } async parseFile ( filePath , parent = null ) { let file = { type : "file" , parent : parent , dirPath : pathlib . dirname ( filePath ) , path : filePath , name : pathlib . basename ( filePath ) , hash : hash ( filePath ) , ext : pathlib . extname ( filePath ) , contents : null , modTime : 0 , creationTime : 0 , } ; try { let stat = await fs . promises . lstat ( filePath ) ; file . modTime = stat . mtimeMs ; file . creationTime = stat . birthtimeMs ; } catch ( err ) { console . error ( "fsal-file :: file error during lstat" , filePath , err ) ; return null ; } return file ; } async init ( ) { console . log ( "fsal :: init()" ) ; if ( this . _resources ) { throw FsalAlreadyInitializedError ( ) ; } this . _resources = { watchdog : new FSALWatchdog ( true ) , globalWatchdog : new FSALWatchdog ( false ) } ; await this . _resources . watchdog . init ( ) ; this . attachEvents ( ) ; } async close ( ) { console . log ( "fsal :: destroy()" ) ; this . _resources ?. watchdog ?. unwatchAll ( ) ; this . _resources ?. watchdog ?. destroy ( ) ; this . _resources ?. globalWatchdog ?. unwatchAll ( ) ; this . _resources ?. globalWatchdog ?. destroy ( ) ; this . detachEvents ( ) ; this . emit ( FsalEvents . STATE_CHANGED , 'filetree' ) ; } private attachEvents ( ) { if ( ! this . _resources ) { throw FsalNotInitializedError ( ) ; } this . _resources . watchdog . on ( FsalEvents . CHOKIDAR_EVENT , this . handleChokidarEvent ) ; this . _resources . globalWatchdog . on ( FsalEvents . CHOKIDAR_EVENT , this . handleGlobalChokidarEvent ) ; } private detachEvents ( ) { if ( this . _resources ) { this . _resources . watchdog . off ( FsalEvents . CHOKIDAR_EVENT , this . handleChokidarEvent ) ; this . _resources . globalWatchdog . off ( FsalEvents . CHOKIDAR_EVENT , this . handleGlobalChokidarEvent ) ; } } private async handleChokidarEvent ( event , info ) { console . log ( `fsal :: chokidar-event :: ${ event }` , info . path ) ; if ( event == ChokidarEvents . ERROR ) { throw new Error ( `fsal :: chokidar error :: ${ info }` ) ; } this . emit ( FsalEvents . CHOKIDAR_EVENT , event , info ) ; this . emit ( FsalEvents . STATE_CHANGED , "filetree" ) ; } private async handleGlobalChokidarEvent ( event , info ) { console . log ( `fsal :: global-chokidar-event :: ${ event }` , info . path ) ; if ( event == ChokidarEvents . ERROR ) { throw new Error ( `fsal :: chokidar error :: ${ info }` ) ; } this . emit ( FsalEvents . GLOBAL_CHOKIDAR_EVENT , event , info ) ; } watch ( p ) { if ( ! this . _resources ) { throw FsalNotInitializedError ( ) ; } ; this . _resources . watchdog . watch ( p ) ; } /** TODO (2021-05-30) FSAL.unwatch() is currently unused */ unwatch ( p ) { if ( ! this . _resources ) { throw FsalNotInitializedError ( ) ; } ; this . _resources . watchdog . unwatch ( p ) ; } watchGlobal ( p ) { if ( ! this . _resources ) { throw FsalNotInitializedError ( ) ; } ; this . _resources . globalWatchdog . watch ( p ) ; } /** TODO (2021-05-30) FSAL.unwatchGlobal() is currently unused */ unwatchGlobal ( p ) { if ( ! this . _resources ) { throw FsalNotInitializedError ( ) ; } ; this . _resources . globalWatchdog . unwatch ( p ) ; } }