import log from "electron-log" ; import fs from "fs" ; import { inject , injectable } from "inversify" ; import os from "os" ; import path from "path" ; import trash from "trash" ; import DirectoryError from "errors/DirectoryError" ; import TYPES from "ioc/types" ; import { IAttributesManager , IDirectoryManager } from "managers" ; import { IDirectoryItem , IListDirectoryOptions } from "models" ; import { DirectorySorter } from "objects" ; import { ItemType } from "types" ; import Utils from "Utils" ; import { IFileSystemWrapper } from "wrappers" ; /** Provides methods for reading, writing and creating files and folders. */ @ injectable ( ) class DirectoryManager implements IDirectoryManager { /** A watcher that observes changes to a directory. */ private watcher ? : fs . FSWatcher ; /** A wrapper that abstracts file system operations. */ private fileSystemWrapper : IFileSystemWrapper ; /** A manager that handles directory item attributes on Windows. */ private attributesManager : IAttributesManager ; param fileSystemWrapper param attributesManager public constructor ( @ inject ( TYPES . IFileSystemWrapper ) fileSystemWrapper , @ inject ( TYPES . IAttributesManager ) attributesManager ) { this . fileSystemWrapper = fileSystemWrapper ; this . attributesManager = attributesManager ; } inheritDoc public async listDirectory ( filePath , options ) { if ( ! ( await this . isDirectory ( filePath ) ) ) { throw new DirectoryError ( "Cannot call listDirectory on a non-directory item" , filePath ) ; } const filterCondition = options . filterCondition ? options . filterCondition : ( item ) => true ; const sort = options . sort ? options . sort : DirectorySorter . sortByTypeThenAlphaNumery ; let fileList ; try { fileList = await this . fileSystemWrapper . readdirAsync ( filePath ) ; } catch { throw new DirectoryError ( "Could not list items in directory" , filePath ) ; } const files = await Promise . all ( fileList . map ( async ( fileName ) => { const fullPath = path . join ( filePath , fileName ) ; let fileStats ; try { fileStats = await this . fileSystemWrapper . lstatAsync ( fullPath ) ; } catch { return { accessible : false , isDirectory : true , isHidden : await this . isHidden ( fullPath , options . hideUnixStyleHiddenItems ) , name : fileName , path : fullPath } as IDirectoryItem ; } return { accessible : await this . isAccessible ( fullPath ) , created : fileStats . ctime , isDirectory : fileStats . isDirectory ( ) , isHidden : await this . isHidden ( fullPath , options . hideUnixStyleHiddenItems ) , lastModified : fileStats . mtime , name : fileName , path : fullPath , size : ! fileStats . isDirectory ( ) && fileStats . size || undefined } as IDirectoryItem ; } ) ) ; return sort ( files ) . filter ( filterCondition ) ; } inheritDoc public async createItem ( itemName , itemPath , itemType ) { const fullItemName = path . join ( itemPath , itemName ) ; if ( itemType === "folder" ) { try { await this . fileSystemWrapper . mkdirAsync ( fullItemName ) ; } catch { throw new DirectoryError ( "Could not create directory" , fullItemName ) ; } } else { try { await this . fileSystemWrapper . writeFileAsync ( fullItemName , "" ) ; } catch { throw new DirectoryError ( "Could not create file" , fullItemName ) ; } } } inheritDoc public async renameItem ( oldName , newName , itemPath ) { if ( oldName === newName ) { return ; } const oldNameFull = path . join ( itemPath , oldName ) ; const newNameFull = path . join ( itemPath , newName ) ; try { await this . fileSystemWrapper . renameAsync ( oldNameFull , newNameFull ) ; } catch { throw new DirectoryError ( "Could not rename item" , oldNameFull , newNameFull ) ; } } inheritDoc public async deleteItems ( itemsToDelete ) { const itemDeletions = itemsToDelete . map ( async ( item ) => { await this . deleteItem ( item . path , Utils . parseItemType ( item ) ) ; } ) ; await Promise . all ( itemDeletions ) ; } inheritDoc public async sendItemsToTrash ( itemsToTrash ) { const itemSoftDeletions = itemsToTrash . map ( async ( item ) => { await DirectoryManager . sendItemToTrash ( item . path ) ; } ) ; await Promise . all ( itemSoftDeletions ) ; } inheritDoc public async copyItems ( itemsToCopy , destinationDirectory ) { const itemCopies = itemsToCopy . map ( async ( item ) => { await this . copyItem ( item . path , destinationDirectory ) ; } ) ; await Promise . all ( itemCopies ) ; } inheritDoc public async moveItems ( itemsToMove , destinationDirectory ) { const itemMoves = itemsToMove . map ( async ( item ) => { await this . moveItem ( item . path , destinationDirectory , Utils . parseItemType ( item ) ) ; } ) ; await Promise . all ( itemMoves ) ; } inheritDoc public readFileSync ( filePath ) { return fs . readFileSync ( filePath , "utf-8" ) ; } inheritDoc public startWatching ( pathToWatch , listener ) { this . watcher = fs . watch ( pathToWatch , listener ) ; } inheritDoc public stopWatching ( ) { this . watcher && this . watcher . close ( ) ; } param pathToItem param hideUnixStyleHiddenItems returns private async isHidden ( pathToItem , hideUnixStyleHiddenItems ) { if ( ! pathToItem ) { throw new ReferenceError ( "pathToItem must contain characters" ) ; } const itemName = path . basename ( pathToItem ) ; if ( os . platform ( ) === "linux" ) { return itemName . startsWith ( "." ) ; } else if ( os . platform ( ) === "win32" ) { return this . handleWindowsIshidden ( pathToItem , hideUnixStyleHiddenItems ) ; } log . warn ( "Only linux and win32 platforms currently supported" ) ; return false ; } param pathToItem param hideUnixStyleHiddenItems returns private async handleWindowsIshidden ( pathToItem , hideUnixStyleHiddenItems ) { const itemName = path . basename ( pathToItem ) ; if ( hideUnixStyleHiddenItems && itemName . startsWith ( "." ) ) { return true ; } try { const attributes = await this . attributesManager . getAttributesAsync ( pathToItem ) ; return attributes . hidden ; } catch ( error ) { log . warn ( "Could not determine attributes, defaulting path to not hidden" , pathToItem , error ) ; return false ; } } param pathToItem returns private async isAccessible ( pathToItem ) { try { await this . fileSystemWrapper . accessAsync ( pathToItem , fs . constants . R_OK ) ; return true ; } catch { return false ; } } param itemPath param destinationDirectory private async copyItem ( itemPath , destinationDirectory ) { const fileName = path . basename ( itemPath ) ; const destinationFileName = path . join ( destinationDirectory , fileName ) ; try { await this . fileSystemWrapper . copyAsync ( itemPath , destinationFileName ) ; } catch { throw new DirectoryError ( "Failed to copy item" , itemPath , destinationFileName ) ; } } param itemPath param destinationDirectory param itemType private async moveItem ( itemPath , destinationDirectory , itemType ) { try { await this . copyItem ( itemPath , destinationDirectory ) ; await this . deleteItem ( itemPath , itemType ) ; } catch { throw new DirectoryError ( "Failed to copy item" , itemPath , destinationDirectory ) ; } } param itemPath param itemType private async deleteItem ( itemPath , itemType ) { if ( itemType === "folder" ) { try { await this . fileSystemWrapper . rmdirAsync ( itemPath ) ; } catch { throw new DirectoryError ( "Cannot remove folder" , itemPath ) ; } } else { try { await this . fileSystemWrapper . unlinkAsync ( itemPath ) ; } catch { throw new DirectoryError ( "Cannot remove file" , itemPath ) ; } } } param pathToItem returns private async isDirectory ( pathToItem ) { const stats = await this . fileSystemWrapper . lstatAsync ( pathToItem ) ; return stats . isDirectory ( ) ; } param itemPath private static async sendItemToTrash ( itemPath ) { try { await trash ( [ itemPath ] , { glob : false } ) ; } catch { throw new DirectoryError ( "Could not send item to trash" , itemPath ) ; } } } export default DirectoryManager ;