@ -23,12 +23,19 @@ const profile = {
icon : 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNjk2IDM4NHE0MCAwIDY4IDI4dDI4IDY4djEyMTZxMCA0MC0yOCA2OHQtNjggMjhoLTk2MHEtNDAgMC02OC0yOHQtMjgtNjh2LTI4OGgtNTQ0cS00MCAwLTY4LTI4dC0yOC02OHYtNjcycTAtNDAgMjAtODh0NDgtNzZsNDA4LTQwOHEyOC0yOCA3Ni00OHQ4OC0yMGg0MTZxNDAgMCA2OCAyOHQyOCA2OHYzMjhxNjgtNDAgMTI4LTQwaDQxNnptLTU0NCAyMTNsLTI5OSAyOTloMjk5di0yOTl6bS02NDAtMzg0bC0yOTkgMjk5aDI5OXYtMjk5em0xOTYgNjQ3bDMxNi0zMTZ2LTQxNmgtMzg0djQxNnEwIDQwLTI4IDY4dC02OCAyOGgtNDE2djY0MGg1MTJ2LTI1NnEwLTQwIDIwLTg4dDQ4LTc2em05NTYgODA0di0xMTUyaC0zODR2NDE2cTAgNDAtMjggNjh0LTY4IDI4aC00MTZ2NjQwaDg5NnoiLz48L3N2Zz4=' ,
permission : true ,
version : packageJson . version ,
methods : [ 'getFolder ' , 'getCurrentFile' , 'getFile' , 'setFile' , 'switchFile' ] ,
methods : [ 'file' , 'exists' , 'open' , 'writeFile' , 'readFile' , 'copyFile' , 'rename' , 'readdir' , 'remove ' , 'getCurrentFile' , 'getFile' , 'getFolder ' , 'setFile' , 'switchFile' ] ,
kind : 'file-system'
}
// File System profile
// - methods: ['getFolder', 'getCurrentFile', 'getFile', 'setFile', 'switchFile']
const errorMsg = {
ENOENT : 'No such file or directory' ,
EISDIR : 'Path is a directory' ,
ENOTDIR : 'Path is not on a directory' ,
EEXIST : 'File already exists' ,
EPERM : 'Permission denied'
}
const createError = ( err ) => {
return new Error ( ` ${ errorMsg [ err . code ] } ${ err . message || '' } ` )
}
class FileManager extends Plugin {
constructor ( editor ) {
@ -42,6 +49,202 @@ class FileManager extends Plugin {
this . init ( )
}
/ * *
* Emit error if path doesn ' t exist
* @ param { string } path path of the file / directory
* @ param { string } message message to display if path doesn ' t exist .
* /
async _handleExists ( path , message ) {
const exists = await this . exists ( path )
if ( ! exists ) {
throw createError ( { code : 'ENOENT' , message } )
}
}
/ * *
* Emit error if path is not a file
* @ param { string } path path of the file / directory
* @ param { string } message message to display if path is not a file .
* /
async _handleIsFile ( path , message ) {
const isFile = await this . isFile ( path )
if ( ! isFile ) {
throw createError ( { code : 'EISDIR' , message } )
}
}
/ * *
* Emit error if path is not a directory
* @ param { string } path path of the file / directory
* @ param { string } message message to display if path is not a directory .
* /
async _handleIsDir ( path , message ) {
const isDir = await this . isDirectory ( path )
if ( ! isDir ) {
throw createError ( { code : 'ENOTDIR' , message } )
}
}
/** The current opened file */
file ( ) {
const file = this . currentFile ( )
if ( ! file ) throw createError ( { code : 'ENOENT' , message : 'No file selected' } )
return file
}
/ * *
* Verify if the path exists ( directory or file )
* @ param { string } path path of the directory or file
* @ returns { boolean } true if the path exists
* /
exists ( path ) {
const provider = this . fileProviderOf ( path )
const result = provider . exists ( path , ( err , result ) => {
if ( err ) return false
return result
} )
return result
}
/ * *
* Verify if the path provided is a file
* @ param { string } path path of the directory or file
* @ returns { boolean } true if path is a file .
* /
isFile ( path ) {
const provider = this . fileProviderOf ( path )
const result = provider . isFile ( path )
return result
}
/ * *
* Verify if the path provided is a directory
* @ param { string } path path of the directory
* @ returns { boolean } true if path is a directory .
* /
isDirectory ( path ) {
const provider = this . fileProviderOf ( path )
const result = provider . isDirectory ( path )
return result
}
/ * *
* Open the content of the file in the context ( eg : Editor )
* @ param { string } path path of the file
* @ returns { void }
* /
async open ( path ) {
await this . _handleExists ( path , ` Cannot open file ${ path } ` )
await this . _handleIsFile ( path , ` Cannot open file ${ path } ` )
return this . openFile ( path )
}
/ * *
* Set the content of a specific file
* @ param { string } path path of the file
* @ param { string } data content to write on the file
* @ returns { void }
* /
async writeFile ( path , data ) {
if ( await this . exists ( path ) ) {
await this . _handleIsFile ( path , ` Cannot write file ${ path } ` )
return await this . setFileContent ( path , data )
} else {
return await this . setFileContent ( path , data )
}
}
/ * *
* Return the content of a specific file
* @ param { string } path path of the file
* @ returns { string } content of the file
* /
async readFile ( path ) {
await this . _handleExists ( path , ` Cannot read file ${ path } ` )
await this . _handleIsFile ( path , ` Cannot read file ${ path } ` )
return this . getFileContent ( path )
}
/ * *
* Upsert a file with the content of the source file
* @ param { string } src path of the source file
* @ param { string } dest path of the destrination file
* @ returns { void }
* /
async copyFile ( src , dest ) {
await this . _handleExists ( src , ` Cannot copy from ${ src } ` )
await this . _handleIsFile ( src , ` Cannot copy from ${ src } ` )
await this . _handleIsFile ( dest , ` Cannot paste content into ${ dest } ` )
const content = await this . readFile ( src )
await this . writeFile ( dest , content )
}
/ * *
* Change the path of a file / directory
* @ param { string } oldPath current path of the file / directory
* @ param { string } newPath new path of the file / directory
* @ returns { void }
* /
async rename ( oldPath , newPath ) {
await this . _ _handleExists ( oldPath , ` Cannot rename ${ oldPath } ` )
const isFile = await this . isFile ( oldPath )
this . fileRenamedEvent ( oldPath , newPath , ! isFile )
}
/ * *
* Create a directory
* @ param { string } path path of the new directory
* @ returns { void }
* /
async mkdir ( path ) {
if ( await this . exists ( path ) ) {
throw createError ( { code : 'EEXIST' , message : ` Cannot create directory ${ path } ` } )
}
const provider = this . fileProviderOf ( path )
provider . createDir ( path )
}
/ * *
* Get the list of files in the directory
* @ param { string } path path of the directory
* @ returns { string [ ] } list of the file / directory name in this directory
* /
async readdir ( path ) {
await this . _handleExists ( path )
await this . _handleIsDir ( path )
return new Promise ( ( resolve , reject ) => {
const provider = this . fileProviderOf ( path )
provider . resolveDirectory ( path , ( error , filesProvider ) => {
if ( error ) reject ( error )
resolve ( filesProvider )
} )
} )
}
/ * *
* Removes a file or directory recursively
* @ param { string } path path of the directory / file to remove
* @ returns { void }
* /
async remove ( path ) {
await this . _handleExists ( path , ` Cannot remove file or directory ${ path } ` )
const provider = this . fileProviderOf ( path )
return await provider . remove ( path )
}
init ( ) {
this . _deps = {
config : this . _components . registry . get ( 'config' ) . api ,
@ -56,6 +259,11 @@ class FileManager extends Plugin {
this . _deps . localhostExplorer . event . register ( 'fileRemoved' , ( path ) => { this . fileRemovedEvent ( path ) } )
this . _deps . localhostExplorer . event . register ( 'errored' , ( event ) => { this . removeTabsOf ( this . _deps . localhostExplorer ) } )
this . _deps . localhostExplorer . event . register ( 'closed' , ( event ) => { this . removeTabsOf ( this . _deps . localhostExplorer ) } )
this . getCurrentFile = this . file
this . getFile = this . readFile
this . getFolder = this . readdir
this . setFile = this . writeFile
this . switchFile = this . open
}
fileChangedEvent ( path ) {
@ -71,7 +279,7 @@ class FileManager extends Plugin {
delete this . openedFiles [ oldName ]
this . openedFiles [ newName ] = newName
}
this . switch File( newName )
this . open File( newName )
} else {
var newFocus
for ( var k in this . openedFiles ) {
@ -85,7 +293,7 @@ class FileManager extends Plugin {
}
}
if ( newFocus ) {
this . switch File( newFocus )
this . open File( newFocus )
}
}
// TODO: Only keep `this.emit` (issue#2210)
@ -129,15 +337,10 @@ class FileManager extends Plugin {
return path ? path [ 1 ] : null
}
getCurrentFile ( ) {
const path = this . currentFile ( )
if ( ! path ) throw new Error ( 'No file selected' )
return path
}
getFile ( path ) {
getFileContent ( path ) {
const provider = this . fileProviderOf ( path )
if ( ! provider ) throw new Error ( ` ${ path } not available ` )
if ( ! provider ) throw createError ( { code : 'ENOENT' , message : ` ${ path } not available ` } )
// TODO: change provider to Promise
return new Promise ( ( resolve , reject ) => {
if ( this . currentFile ( ) === path ) return resolve ( this . editor . currentContent ( ) )
@ -148,11 +351,10 @@ class FileManager extends Plugin {
} )
}
async setFile ( path , content ) {
async setFileContent ( path , content ) {
if ( this . currentRequest ) {
const canCall = await this . askUserPermission ( 'set File' , '' )
const canCall = await this . askUserPermission ( 'write File' , '' )
if ( canCall ) {
this . _setFileInternal ( path , content )
// inform the user about modification after permission is granted and even if permission was saved before
toaster ( yo `
< div >
@ -167,12 +369,12 @@ class FileManager extends Plugin {
` , '', { time: 3000 })
}
}
this . _setFileInternal ( path , content )
return await this . _setFileInternal ( path , content )
}
_setFileInternal ( path , content ) {
const provider = this . fileProviderOf ( path )
if ( ! provider ) throw new Error ( ` ${ path } not availble ` )
if ( ! provider ) throw createError ( { code : 'ENOENT' , message : ` ${ path } not availa ble ` } )
// TODO : Add permission
// TODO : Change Provider to Promise
return new Promise ( ( resolve , reject ) => {
@ -189,11 +391,10 @@ class FileManager extends Plugin {
if ( fileProvider ) {
helper . createNonClashingNameWithPrefix ( path , fileProvider , '' , ( error , copyName ) => {
if ( error ) {
console . log ( 'createNonClashingNameWithPrefix' , error )
copyName = path + '.' + this . currentRequest . from
}
this . _setFileInternal ( copyName , content )
this . switch File( copyName )
this . open File( copyName )
} )
}
}
@ -216,7 +417,7 @@ class FileManager extends Plugin {
// TODO: Only keep `this.emit` (issue#2210)
this . emit ( 'fileRemoved' , path )
this . events . emit ( 'fileRemoved' , path )
this . switch File( )
this . open File( )
}
unselectCurrentFile ( ) {
@ -227,16 +428,19 @@ class FileManager extends Plugin {
this . events . emit ( 'noFileSelected' )
}
switch File ( file ) {
const _switch File = ( file ) => {
open File ( file ) {
const _open File = ( file ) => {
this . saveCurrentFile ( )
const provider = this . fileProviderOf ( file )
if ( ! provider ) return console . error ( ` no provider for ${ file } ` )
file = provider . getPathFromUrl ( file ) || file // in case an external URL is given as input, we resolve it to the right internal path
this . _deps . config . set ( 'currentFile' , file )
this . openedFiles [ file ] = file
this . fileProviderOf ( file ) . get ( file , ( error , content ) => {
provider . get ( file , ( error , content ) => {
if ( error ) {
console . log ( error )
} else {
if ( this . fileProviderOf ( file ) . isReadOnly ( file ) ) {
if ( provider . isReadOnly ( file ) ) {
this . editor . openReadOnly ( file , content )
} else {
this . editor . open ( file , content )
@ -247,14 +451,14 @@ class FileManager extends Plugin {
}
} )
}
if ( file ) return _switch File ( file )
if ( file ) return _open File ( file )
else {
var browserProvider = this . _deps . filesProviders [ 'browser' ]
browserProvider . resolveDirectory ( 'browser' , ( error , filesProvider ) => {
if ( error ) console . error ( error )
var fileList = Object . keys ( filesProvider )
if ( fileList . length ) {
_switch File ( browserProvider . type + '/' + fileList [ 0 ] )
_open File ( browserProvider . type + '/' + fileList [ 0 ] )
} else {
// TODO: Only keep `this.emit` (issue#2210)
this . emit ( 'noFileSelected' )
@ -264,18 +468,6 @@ class FileManager extends Plugin {
}
}
getFolder ( path ) {
// TODO : Change provider with promise
return new Promise ( ( resolve , reject ) => {
const provider = this . fileProviderOf ( path )
if ( ! provider ) return reject ( ` provider for path ${ path } not found ` )
provider . resolveDirectory ( path , ( error , filesProvider ) => {
if ( error ) reject ( error )
resolve ( filesProvider )
} )
} )
}
getProvider ( name ) {
return this . _deps . filesProviders [ name ]
}