2020-10-21 01:23:55 +02:00
import { resolve as nodeResolve } from 'path' ;
2020-12-09 14:50:51 +02:00
import FsDriverBase , { Stat } from './fs-driver-base' ;
import time from './time' ;
2021-01-27 19:42:58 +02:00
const md5File = require ( 'md5-file/promise' ) ;
2018-03-09 22:59:12 +02:00
const fs = require ( 'fs-extra' ) ;
2017-07-05 23:52:31 +02:00
2020-10-21 01:23:55 +02:00
export default class FsDriverNode extends FsDriverBase {
2020-11-12 21:13:28 +02:00
private fsErrorToJsError_ ( error : any , path : string = null ) {
2018-01-28 19:36:36 +02:00
let msg = error . toString ( ) ;
2019-09-19 23:51:18 +02:00
if ( path !== null ) msg += ` . Path: ${ path } ` ;
2020-11-12 21:13:28 +02:00
const output : any = new Error ( msg ) ;
2018-01-28 19:36:36 +02:00
if ( error . code ) output . code = error . code ;
return output ;
}
2020-11-12 21:13:28 +02:00
public appendFileSync ( path : string , string : string ) {
2017-07-05 23:52:31 +02:00
return fs . appendFileSync ( path , string ) ;
}
2020-11-12 21:13:28 +02:00
public async appendFile ( path : string , string : string , encoding : string = 'base64' ) {
2018-01-28 19:36:36 +02:00
try {
return await fs . appendFile ( path , string , { encoding : encoding } ) ;
} catch ( error ) {
throw this . fsErrorToJsError_ ( error , path ) ;
}
2017-12-12 01:52:42 +02:00
}
2020-11-12 21:13:28 +02:00
public async writeBinaryFile ( path : string , content : any ) {
2018-01-28 19:36:36 +02:00
try {
2018-09-27 20:35:10 +02:00
// let buffer = new Buffer(content);
2020-03-14 01:46:14 +02:00
const buffer = Buffer . from ( content ) ;
2018-01-28 19:36:36 +02:00
return await fs . writeFile ( path , buffer ) ;
} catch ( error ) {
throw this . fsErrorToJsError_ ( error , path ) ;
}
2017-07-05 23:52:31 +02:00
}
2020-11-12 21:13:28 +02:00
public async writeFile ( path : string , string : string , encoding : string = 'base64' ) {
2018-01-28 19:36:36 +02:00
try {
2018-03-12 20:01:47 +02:00
if ( encoding === 'buffer' ) {
return await fs . writeFile ( path , string ) ;
} else {
return await fs . writeFile ( path , string , { encoding : encoding } ) ;
}
2018-01-28 19:36:36 +02:00
} catch ( error ) {
throw this . fsErrorToJsError_ ( error , path ) ;
}
2018-01-17 20:51:15 +02:00
}
2018-01-25 23:15:58 +02:00
// same as rm -rf
2020-11-12 21:13:28 +02:00
public async remove ( path : string ) {
2018-01-28 19:36:36 +02:00
try {
2018-06-18 20:56:07 +02:00
const r = await fs . remove ( path ) ;
return r ;
2018-01-28 19:36:36 +02:00
} catch ( error ) {
throw this . fsErrorToJsError_ ( error , path ) ;
}
2018-01-25 23:15:58 +02:00
}
2020-11-12 21:13:28 +02:00
public async move ( source : string , dest : string ) {
2018-01-17 20:51:15 +02:00
let lastError = null ;
for ( let i = 0 ; i < 5 ; i ++ ) {
try {
const output = await fs . move ( source , dest , { overwrite : true } ) ;
return output ;
} catch ( error ) {
lastError = error ;
// Normally cannot happen with the `overwrite` flag but sometime it still does.
// In this case, retry.
2018-03-09 22:59:12 +02:00
if ( error . code == 'EEXIST' ) {
2018-01-17 20:51:15 +02:00
await time . sleep ( 1 ) ;
continue ;
}
2018-01-28 19:36:36 +02:00
throw this . fsErrorToJsError_ ( error ) ;
2018-01-17 20:51:15 +02:00
}
}
throw lastError ;
2017-12-19 21:01:29 +02:00
}
2020-11-12 21:13:28 +02:00
public exists ( path : string ) {
2017-12-19 21:01:29 +02:00
return fs . pathExists ( path ) ;
}
2020-11-12 21:13:28 +02:00
public async mkdir ( path : string ) {
2019-11-21 21:54:09 +02:00
// Note that mkdirp() does not throw an error if the directory
// could not be created. This would make the synchroniser to
// incorrectly try to sync with a non-existing dir:
// https://github.com/laurent22/joplin/issues/2117
const r = await fs . mkdirp ( path ) ;
if ( ! ( await this . exists ( path ) ) ) throw new Error ( ` Could not create directory: ${ path } ` ) ;
return r ;
2018-01-17 20:51:15 +02:00
}
2020-11-12 21:13:28 +02:00
public async stat ( path : string ) {
2018-01-17 23:01:41 +02:00
try {
2018-02-25 23:08:32 +02:00
const stat = await fs . stat ( path ) ;
return {
birthtime : stat.birthtime ,
mtime : stat.mtime ,
isDirectory : ( ) = > stat . isDirectory ( ) ,
path : path ,
size : stat.size ,
} ;
2018-01-17 23:01:41 +02:00
} catch ( error ) {
2018-03-09 22:59:12 +02:00
if ( error . code == 'ENOENT' ) return null ;
2018-01-17 23:01:41 +02:00
throw error ;
}
2018-01-17 20:51:15 +02:00
}
2020-11-12 21:13:28 +02:00
public async setTimestamp ( path : string , timestampDate : any ) {
2018-01-17 20:51:15 +02:00
return fs . utimes ( path , timestampDate , timestampDate ) ;
}
2020-11-12 21:13:28 +02:00
public async readDirStats ( path : string , options : any = null ) {
2018-02-25 23:08:32 +02:00
if ( ! options ) options = { } ;
2018-03-09 22:59:12 +02:00
if ( ! ( 'recursive' in options ) ) options . recursive = false ;
2018-02-25 23:08:32 +02:00
2018-02-26 20:43:50 +02:00
let items = [ ] ;
try {
items = await fs . readdir ( path ) ;
} catch ( error ) {
throw this . fsErrorToJsError_ ( error ) ;
}
2020-12-09 14:50:51 +02:00
let output : Stat [ ] = [ ] ;
2018-01-17 20:51:15 +02:00
for ( let i = 0 ; i < items . length ; i ++ ) {
2018-02-25 23:08:32 +02:00
const item = items [ i ] ;
2020-03-14 01:46:14 +02:00
const stat = await this . stat ( ` ${ path } / ${ item } ` ) ;
2018-01-17 20:51:15 +02:00
if ( ! stat ) continue ; // Has been deleted between the readdir() call and now
stat . path = stat . path . substr ( path . length + 1 ) ;
output . push ( stat ) ;
2018-02-25 23:08:32 +02:00
output = await this . readDirStatsHandleRecursion_ ( path , stat , output , options ) ;
2018-01-17 20:51:15 +02:00
}
return output ;
}
2020-11-12 21:13:28 +02:00
public async open ( path : string , mode : any ) {
2018-01-28 19:36:36 +02:00
try {
return await fs . open ( path , mode ) ;
} catch ( error ) {
throw this . fsErrorToJsError_ ( error , path ) ;
}
2017-12-12 01:52:42 +02:00
}
2020-11-12 21:13:28 +02:00
public async close ( handle : any ) {
2018-01-28 19:36:36 +02:00
try {
return await fs . close ( handle ) ;
} catch ( error ) {
2019-07-30 09:35:42 +02:00
throw this . fsErrorToJsError_ ( error , '' ) ;
2018-01-28 19:36:36 +02:00
}
2017-12-12 01:52:42 +02:00
}
2020-11-12 21:13:28 +02:00
public async readFile ( path : string , encoding : string = 'utf8' ) {
2018-02-26 21:25:54 +02:00
try {
2018-03-09 22:59:12 +02:00
if ( encoding === 'Buffer' ) return await fs . readFile ( path ) ; // Returns the raw buffer
2018-02-26 21:25:54 +02:00
return await fs . readFile ( path , encoding ) ;
} catch ( error ) {
throw this . fsErrorToJsError_ ( error , path ) ;
}
2018-01-17 20:51:15 +02:00
}
// Always overwrite destination
2020-11-12 21:13:28 +02:00
public async copy ( source : string , dest : string ) {
2018-02-26 21:25:54 +02:00
try {
return await fs . copy ( source , dest , { overwrite : true } ) ;
} catch ( error ) {
throw this . fsErrorToJsError_ ( error , source ) ;
}
2017-07-05 23:52:31 +02:00
}
2020-11-12 21:13:28 +02:00
public async unlink ( path : string ) {
2017-12-12 01:52:42 +02:00
try {
await fs . unlink ( path ) ;
} catch ( error ) {
2018-03-09 22:59:12 +02:00
if ( error . code === 'ENOENT' ) return ; // Don't throw if the file does not exist
2017-12-12 01:52:42 +02:00
throw error ;
}
}
2020-11-12 21:13:28 +02:00
public async readFileChunk ( handle : any , length : number , encoding : string = 'base64' ) {
2019-10-09 21:35:13 +02:00
// let buffer = new Buffer(length);
2018-09-27 20:35:10 +02:00
let buffer = Buffer . alloc ( length ) ;
2017-12-23 22:21:11 +02:00
const result = await fs . read ( handle , buffer , 0 , length , null ) ;
2017-12-21 21:06:08 +02:00
if ( ! result . bytesRead ) return null ;
2017-12-23 22:21:11 +02:00
buffer = buffer . slice ( 0 , result . bytesRead ) ;
2018-03-09 22:59:12 +02:00
if ( encoding === 'base64' ) return buffer . toString ( 'base64' ) ;
if ( encoding === 'ascii' ) return buffer . toString ( 'ascii' ) ;
2019-09-19 23:51:18 +02:00
throw new Error ( ` Unsupported encoding: ${ encoding } ` ) ;
2017-12-12 01:52:42 +02:00
}
2020-10-09 19:35:46 +02:00
2020-11-12 21:13:28 +02:00
public resolve ( path : string ) {
2020-10-09 19:35:46 +02:00
return require ( 'path' ) . resolve ( path ) ;
}
2020-10-23 14:21:37 +02:00
2020-10-21 01:23:55 +02:00
// Resolves the provided relative path to an absolute path within baseDir. The function
// also checks that the absolute path is within baseDir, to avoid security issues.
// It is expected that baseDir is a safe path (not user-provided).
2020-11-12 21:13:28 +02:00
public resolveRelativePathWithinDir ( baseDir : string , relativePath : string ) {
2020-10-23 14:21:37 +02:00
const resolvedBaseDir = nodeResolve ( baseDir ) ;
2020-10-21 01:23:55 +02:00
const resolvedPath = nodeResolve ( baseDir , relativePath ) ;
2020-10-23 14:21:37 +02:00
if ( resolvedPath . indexOf ( resolvedBaseDir ) !== 0 ) throw new Error ( ` Resolved path for relative path " ${ relativePath } " is not within base directory " ${ baseDir } " (Was resolved to ${ resolvedPath } ) ` ) ;
2020-10-21 01:23:55 +02:00
return resolvedPath ;
}
2020-10-09 19:35:46 +02:00
2021-01-27 19:42:58 +02:00
public async md5File ( path : string ) : Promise < string > {
return md5File ( path ) ;
}
public async tarExtract ( options : any ) {
await require ( 'tar' ) . extract ( options ) ;
}
public async tarCreate ( options : any , filePaths : string [ ] ) {
await require ( 'tar' ) . create ( options , filePaths ) ;
}
2017-07-05 23:52:31 +02:00
}