/** * @name react-native-fetch-blob-fs * @author wkh237 * @version 0.1.0 * @flow */ import { NativeModules, DeviceEventEmitter, NativeAppEventEmitter, } from 'react-native' import type { RNFetchBlobNative, RNFetchBlobConfig, RNFetchBlobStream } from './types' const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob const emitter = DeviceEventEmitter // session table let sessions = {} /** * Get path of system directories. * @return {object} Map contains DocumentDir, CacheDir, DCIMDir, DownloadDir, * , some directory might not be supported by platform. */ function getSystemDirs() { return new Promise((resolve, reject) => { try { RNFetchBlob.getEnvironmentDirs((...dirs) => { let [DocumentDir, CacheDir, DCIMDir, DownloadDir] = [...dirs] resolve({DocumentDir, CacheDir, DCIMDir, DownloadDir}) }) } catch(err) { reject(err) } }) } /** * Get a file cache session * @param {string} name Stream ID * @return {RNFetchBlobSession} */ function session(name:string):RNFetchBlobSession { let s = sessions[name] if(s) return new RNFetchBlobSession(name) else { sessions[name] = [] return new RNFetchBlobSession(name, []) } } function createFile(path:string, data:string, encoding: 'base64' | 'ascii' | 'utf8'):Promise { encoding = encoding || 'utf8' return new Promise((resolve, reject) => { let handler = (err) => { if(err) reject(err) else resolve() } if(encoding.toLowerCase() === 'ascii') { if(Array.isArray(data)) RNFetchBlob.createFileASCII(path, data, handler) else reject('`data` of ASCII file must be an array contains numbers') } else { RNFetchBlob.createFile(path, data, encoding, handler) } }) } /** * Create write stream to a file. * @param {string} path Target path of file stream. * @param {string} encoding Encoding of input data. * @param {bool} append A flag represent if data append to existing ones. * @return {Promise} A promise resolves a `WriteStream` object. */ function writeStream( path : string, encoding : 'utf8' | 'ascii' | 'base64', append? : ?bool, ):Promise { if(!path) throw Error('RNFetchBlob could not open file stream with empty `path`') return new Promise((resolve, reject) => { RNFetchBlob.writeStream(path, encoding || 'base64', append || false, (err, streamId:string) => { if(err) reject(err) else resolve(new WriteStream(streamId, encoding)) }) }) } /** * Create file stream from file at `path`. * @param {string} path The file path. * @param {string} encoding Data encoding, should be one of `base64`, `utf8`, `ascii` * @param {boolean} bufferSize Size of stream buffer. * @return {RNFetchBlobStream} RNFetchBlobStream stream instance. */ function readStream( path : string, encoding : 'utf8' | 'ascii' | 'base64', bufferSize? : ?number ):RNFetchBlobStream { if(!path) throw Error('RNFetchBlob could not open file stream with empty `path`') encoding = encoding || 'utf8' let stream:RNFetchBlobStream = { // parse JSON array when encoding is ASCII onData : function(fn) { if(encoding.toLowerCase() === 'ascii') this._onData = (data) => { fn(JSON.parse(data)) } else this._onData = fn }, onError : function(fn) { this._onError = fn }, onEnd : function(fn) { this._onEnd = fn }, } // register for file stream event let subscription = emitter.addListener(`RNFetchBlobStream+${path}`, (e) => { let {event, detail} = e if(stream._onData && event === 'data') stream._onData(detail) else if (stream._onEnd && event === 'end') { stream._onEnd(detail) } else { if(stream._onError) stream._onError(detail) else throw new Error(detail) } // when stream closed or error, remove event handler if (event === 'error' || event === 'end') { subscription.remove() } }) RNFetchBlob.readStream(path, encoding, bufferSize || 0) return stream } function mkdir(path:string):Promise { return new Promise((resolve, reject) => { RNFetchBlob.mkdir(path, (err, res) => { if(err) reject(err) else resolve() }) }) } function cp(path:string, dest:string):Promise { return new Promise((resolve, reject) => { RNFetchBlob.cp(path, dest, (err, res) => { if(err) reject(err) else resolve(res) }) }) } function mv(path:string, dest:string):Promise { return new Promise((resolve, reject) => { RNFetchBlob.mv(path, dest, (err, res) => { if(err) reject(err) else resolve(res) }) }) } function ls(path:string):Promise> { return new Promise((resolve, reject) => { RNFetchBlob.ls(path, (err, res) => { if(err) reject(err) else resolve(res) }) }) } /** * Remove file at path. * @param {string} path:string Path of target file. * @return {Promise} */ function unlink(path:string):Promise { return new Promise((resolve, reject) => { RNFetchBlob.unlink(path, (err) => { if(err) reject(err) else resolve() }) }) } /** * Check if file exists and if it is a folder. * @param {string} path Path to check * @return {Promise} */ function exists(path:string):Promise { return new Promise((resolve, reject) => { try { RNFetchBlob.exists(path, (exist) => { resolve(exist) }) } catch(err) { reject(err) } }) } function isDir(path:string):Promise { return new Promise((resolve, reject) => { try { RNFetchBlob.exists(path, (exist, isDir) => { resolve(isDir) }) } catch(err) { reject(err) } }) } /** * Session class * @class RNFetchBlobSession */ class RNFetchBlobSession { add : (path:string) => RNFetchBlobSession; remove : (path:string) => RNFetchBlobSession; dispose : () => Promise; list : () => Array; name : string; constructor(name:string, list:Array) { this.name = name if(!sessions[name]) { if(Array.isArray(list)) sessions[name] = list else sessions[name] = [] } } add(path:string):RNFetchBlobSession { sessions[this.name].push(path) return this } remove(path:string):RNFetchBlobSession { let list = sessions[this.name] for(let i in list) { if(list[i] === path) { sessions[this.name].splice(i, 1) break; } } return this } list():Array { return sessions[this.name] } dispose():Promise { return new Promise((resolve, reject) => { RNFetchBlob.removeSession(sessions[this.name], (err) => { if(err) reject(err) else { delete sessions[this.name] resolve() } }) }) } } class WriteStream { id : string; encoding : string; append : bool; constructor(streamId:string, encoding:string, append:string) { this.id = streamId this.encoding = encoding this.append = append } write(data:string) { return new Promise((resolve, reject) => { try { let method = this.encoding === 'ascii' ? 'writeArrayChunk' : 'writeChunk' if(this.encoding.toLocaleLowerCase() === 'ascii' && !Array.isArray(data)) { reject('ascii input data must be an Array') return } RNFetchBlob[method](this.id, data, (error) => { if(error) reject(error) else resolve() }) } catch(err) { reject(err) } }) } close() { return new Promise((resolve, reject) => { try { RNFetchBlob.closeStream(this.id, () => { resolve() }) } catch (err) { reject(err) } }) } } export default { RNFetchBlobSession, unlink, mkdir, session, ls, readStream, getSystemDirs, mv, cp, writeStream, exists, createFile, isDir }