2021-01-27 19:42:58 +02:00
import FsDriverBase from '@joplin/lib/fs-driver-base' ;
2020-01-08 20:57:40 +02:00
const RNFetchBlob = require ( 'rn-fetch-blob' ) . default ;
2021-01-27 19:42:58 +02:00
const RNFS = require ( 'react-native-fs' ) ;
2021-08-11 12:24:01 +02:00
const { Writable } = require ( 'stream-browserify' ) ;
const { Buffer } = require ( 'buffer' ) ;
2017-12-12 01:52:42 +02:00
2021-01-27 19:42:58 +02:00
export default class FsDriverRN extends FsDriverBase {
public appendFileSync() {
2018-03-09 22:59:12 +02:00
throw new Error ( 'Not implemented' ) ;
2017-12-12 01:52:42 +02:00
}
2020-01-08 20:57:40 +02:00
// Encoding can be either "utf8" or "base64"
2021-01-27 19:42:58 +02:00
public appendFile ( path : string , content : any , encoding = 'base64' ) {
2020-01-08 20:57:40 +02:00
return RNFS . appendFile ( path , content , encoding ) ;
2017-12-12 01:52:42 +02:00
}
2020-01-08 20:57:40 +02:00
// Encoding can be either "utf8" or "base64"
2021-01-27 19:42:58 +02:00
public writeFile ( path : string , content : any , encoding = 'base64' ) {
2020-01-08 20:57:40 +02:00
// We need to use rn-fetch-blob here due to this bug:
// https://github.com/itinance/react-native-fs/issues/700
return RNFetchBlob . fs . writeFile ( path , content , encoding ) ;
2018-01-17 20:51:15 +02:00
}
2018-01-25 23:15:58 +02:00
// same as rm -rf
2021-01-27 19:42:58 +02:00
public async remove ( path : string ) {
2018-03-18 01:00:01 +02:00
return await this . unlink ( path ) ;
2018-01-25 23:15:58 +02:00
}
2021-08-11 12:24:01 +02:00
public writeBinaryFile ( path : string , content : any ) {
const buffer = Buffer . from ( content ) ;
return RNFetchBlob . fs . writeStream ( path , 'base64' ) . then ( ( stream : any ) = > {
const fileStream = new Writable ( {
write ( chunk : any , _encoding : any , callback : Function ) {
this . stream . write ( chunk . toString ( 'base64' ) ) ;
callback ( ) ;
} ,
final ( callback : Function ) {
this . stream . close ( ) ;
callback ( ) ;
} ,
} ) ;
// using options.construct is not implemented in readable-stream so lets
// pass the stream from RNFetchBlob to the Writable instance here
fileStream . stream = stream ;
fileStream . write ( buffer ) ;
fileStream . end ( ) ;
} ) ;
}
2018-01-17 23:01:41 +02:00
// Returns a format compatible with Node.js format
2021-01-27 19:42:58 +02:00
private rnfsStatToStd_ ( stat : any , path : string ) {
2018-01-17 23:01:41 +02:00
return {
birthtime : stat.ctime ? stat.ctime : stat.mtime , // Confusingly, "ctime" normally means "change time" but here it's used as "creation time". Also sometimes it is null
mtime : stat.mtime ,
isDirectory : ( ) = > stat . isDirectory ( ) ,
path : path ,
size : stat.size ,
2019-07-29 15:43:53 +02:00
} ;
2018-01-17 23:01:41 +02:00
}
2021-01-27 19:42:58 +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 ;
2019-07-29 15:43:53 +02:00
2019-12-29 19:58:40 +02:00
let items = [ ] ;
try {
items = await RNFS . readDir ( path ) ;
} catch ( error ) {
throw new Error ( ` Could not read directory: ${ path } : ${ error . message } ` ) ;
}
2021-01-27 19:42:58 +02:00
let output : any [ ] = [ ] ;
2018-01-17 23:01:41 +02:00
for ( let i = 0 ; i < items . length ; i ++ ) {
const item = items [ i ] ;
const relativePath = item . path . substr ( path . length + 1 ) ;
output . push ( this . rnfsStatToStd_ ( item , relativePath ) ) ;
2018-02-25 23:08:32 +02:00
output = await this . readDirStatsHandleRecursion_ ( path , item , output , options ) ;
2018-01-17 23:01:41 +02:00
}
return output ;
}
2021-01-27 19:42:58 +02:00
public async move ( source : string , dest : string ) {
2017-12-28 21:57:21 +02:00
return RNFS . moveFile ( source , dest ) ;
}
2021-01-27 19:42:58 +02:00
public async exists ( path : string ) {
2017-12-28 21:57:21 +02:00
return RNFS . exists ( path ) ;
2017-12-19 21:01:29 +02:00
}
2021-01-27 19:42:58 +02:00
public async mkdir ( path : string ) {
2018-01-17 23:01:41 +02:00
return RNFS . mkdir ( path ) ;
2018-01-17 20:51:15 +02:00
}
2021-01-27 19:42:58 +02:00
public async stat ( path : string ) {
2018-01-17 23:01:41 +02:00
try {
const r = await RNFS . stat ( path ) ;
return this . rnfsStatToStd_ ( r , path ) ;
} catch ( error ) {
2018-03-09 22:59:12 +02:00
if ( error && ( ( error . message && error . message . indexOf ( 'exist' ) >= 0 ) || error . code === 'ENOENT' ) ) {
2018-01-17 23:01:41 +02:00
// Probably { [Error: File does not exist] framesToPop: 1, code: 'EUNSPECIFIED' }
// which unfortunately does not have a proper error code. Can be ignored.
return null ;
} else {
throw error ;
}
}
2018-01-17 20:51:15 +02:00
}
2018-01-17 23:01:41 +02:00
// NOTE: DOES NOT WORK - no error is thrown and the function is called with the right
// arguments but the function returns `false` and the timestamp is not set.
// Current setTimestamp is not really used so keep it that way, but careful if it
// becomes needed.
2021-01-27 19:42:58 +02:00
public async setTimestamp() {
2018-01-17 23:01:41 +02:00
// return RNFS.touch(path, timestampDate, timestampDate);
2018-01-17 20:51:15 +02:00
}
2021-01-27 19:42:58 +02:00
public async open ( path : string , mode : number ) {
2017-12-12 01:52:42 +02:00
// Note: RNFS.read() doesn't provide any way to know if the end of file has been reached.
// So instead we stat the file here and use stat.size to manually check for end of file.
// Bug: https://github.com/itinance/react-native-fs/issues/342
2018-01-17 20:51:15 +02:00
const stat = await this . stat ( path ) ;
2017-12-12 01:52:42 +02:00
return {
path : path ,
offset : 0 ,
mode : mode ,
stat : stat ,
2019-07-29 15:43:53 +02:00
} ;
2017-12-12 01:52:42 +02:00
}
2021-01-27 19:42:58 +02:00
public close ( ) : void {
// Nothing
2017-12-12 01:52:42 +02:00
}
2021-01-27 19:42:58 +02:00
public readFile ( path : string , encoding = 'utf8' ) {
2018-03-09 22:59:12 +02:00
if ( encoding === 'Buffer' ) throw new Error ( 'Raw buffer output not supported for FsDriverRN.readFile' ) ;
2018-01-17 20:51:15 +02:00
return RNFS . readFile ( path , encoding ) ;
}
// Always overwrite destination
2021-01-27 19:42:58 +02:00
public async copy ( source : string , dest : string ) {
2018-01-17 20:51:15 +02:00
let retry = false ;
try {
await RNFS . copyFile ( source , dest ) ;
} catch ( error ) {
// On iOS it will throw an error if the file already exist
retry = true ;
await this . unlink ( dest ) ;
}
if ( retry ) await RNFS . copyFile ( source , dest ) ;
2017-12-12 01:52:42 +02:00
}
2021-01-27 19:42:58 +02:00
public async unlink ( path : string ) {
2017-12-12 01:52:42 +02:00
try {
await RNFS . unlink ( path ) ;
} catch ( error ) {
2018-03-09 22:59:12 +02:00
if ( error && ( ( error . message && error . message . indexOf ( 'exist' ) >= 0 ) || error . code === 'ENOENT' ) ) {
2017-12-12 01:52:42 +02:00
// Probably { [Error: File does not exist] framesToPop: 1, code: 'EUNSPECIFIED' }
// which unfortunately does not have a proper error code. Can be ignored.
} else {
throw error ;
}
}
}
2021-01-27 19:42:58 +02:00
public async readFileChunk ( handle : any , length : number , encoding = 'base64' ) {
2017-12-12 01:52:42 +02:00
if ( handle . offset + length > handle . stat . size ) {
length = handle . stat . size - handle . offset ;
}
if ( ! length ) return null ;
2020-03-14 01:46:14 +02:00
const output = await RNFS . read ( handle . path , length , handle . offset , encoding ) ;
2019-07-30 09:35:42 +02:00
// eslint-disable-next-line require-atomic-updates
2017-12-12 01:52:42 +02:00
handle . offset += length ;
return output ? output : null ;
}
2020-10-09 19:35:46 +02:00
2021-01-27 19:42:58 +02:00
public resolve ( path : string ) {
2020-10-09 19:35:46 +02:00
throw new Error ( ` Not implemented: resolve(): ${ path } ` ) ;
}
2020-10-21 01:23:55 +02:00
2021-01-27 19:42:58 +02:00
public resolveRelativePathWithinDir ( _baseDir : string , relativePath : string ) {
2020-10-21 01:23:55 +02:00
throw new Error ( ` Not implemented: resolveRelativePathWithinDir(): ${ relativePath } ` ) ;
}
2017-12-12 01:52:42 +02:00
2021-01-27 19:42:58 +02:00
public async md5File ( path : string ) : Promise < string > {
throw new Error ( ` Not implemented: md5File(): ${ path } ` ) ;
}
}