react-native-fetch-blob
Version:
A module provides upload, download, and files access API. Supports file stream read/write for process large files.
347 lines (318 loc) • 10.2 kB
JavaScript
// Copyright 2016 wkh237@github. All rights reserved.
// Use of this source code is governed by a MIT-style license that can be
// found in the LICENSE file.
import RNFetchBlob from '../index.js'
import fs from '../fs.js'
import getUUID from '../utils/uuid'
import Log from '../utils/log.js'
import EventTarget from './EventTarget'
const log = new Log('Blob')
const blobCacheDir = fs.dirs.DocumentDir + '/RNFetchBlob-blobs/'
log.disable()
// log.level(3)
/**
* A RNFetchBlob style Blob polyfill class, this is a Blob which compatible to
* Response object attain fron RNFetchBlob.fetch.
*/
export default class Blob extends EventTarget {
cacheName:string;
type:string;
size:number;
isRNFetchBlobPolyfill:boolean = true;
multipartBoundary:string = null;
_ref:string = null;
_blobCreated:boolean = false;
_onCreated:Array<any> = [];
_closed:boolean = false;
/**
* Static method that remove all files in Blob cache folder.
* @nonstandard
* @return {Promise}
*/
static clearCache() {
return fs.unlink(blobCacheDir).then(() => fs.mkdir(blobCacheDir))
}
static build(data:any, cType:any):Promise<Blob> {
return new Promise((resolve, reject) => {
new Blob(data, cType).onCreated(resolve)
})
}
get blobPath() {
return this._ref
}
static setLog(level:number) {
if(level === -1)
log.disable()
else
log.level(level)
}
/**
* RNFetchBlob Blob polyfill, create a Blob directly from file path, BASE64
* encoded data, and string. The conversion is done implicitly according to
* given `mime`. However, the blob creation is asynchronously, to register
* event `onCreated` is need to ensure the Blob is creadted.
* @param {any} data Content of Blob object
* @param {any} mime Content type settings of Blob object, `text/plain`
* by default
* @param {boolean} defer When this argument set to `true`, blob constructor
* will not invoke blob created event automatically.
*/
constructor(data:any, cType:any, defer:boolean) {
super()
cType = cType || {}
this.cacheName = getBlobName()
this.isRNFetchBlobPolyfill = true
this.isDerived = defer
this.type = cType.type || 'text/plain'
log.verbose('Blob constructor called', 'mime', this.type, 'type', typeof data, 'length', data? data.length:0)
this._ref = blobCacheDir + this.cacheName
let p = null
if(!data)
data = ''
if(data.isRNFetchBlobPolyfill) {
log.verbose('create Blob cache file from Blob object')
let size = 0
this._ref = String(data.getRNFetchBlobRef())
let orgPath = this._ref
p = fs.exists(orgPath)
.then((exist) => {
if(exist)
return fs.writeFile(orgPath, data, 'uri')
.then((size) => Promise.resolve(size))
.catch((err) => {
throw `RNFetchBlob Blob file creation error, ${err}`
})
else
throw `could not create Blob from path ${orgPath}, file not exists`
})
}
// process FormData
else if(data instanceof FormData) {
log.verbose('create Blob cache file from FormData', data)
let boundary = `RNFetchBlob-${this.cacheName}-${Date.now()}`
this.multipartBoundary = boundary
let parts = data.getParts()
let formArray = []
if(!parts) {
p = fs.writeFile(this._ref, '', 'utf8')
}
else {
for(let i in parts) {
formArray.push('\r\n--'+boundary+'\r\n')
let part = parts[i]
for(let j in part.headers) {
formArray.push(j + ': ' +part.headers[j] + ';\r\n')
}
formArray.push('\r\n')
if(part.isRNFetchBlobPolyfill)
formArray.push(part)
else
formArray.push(part.string)
}
log.verbose('FormData array', formArray)
formArray.push('\r\n--'+boundary+'--\r\n')
p = createMixedBlobData(this._ref, formArray)
}
}
// if the data is a string starts with `RNFetchBlob-file://`, append the
// Blob data from file path
else if(typeof data === 'string' && data.startsWith('RNFetchBlob-file://')) {
log.verbose('create Blob cache file from file path', data)
this._ref = String(data).replace('RNFetchBlob-file://', '')
let orgPath = this._ref
if(defer)
return
else {
p = fs.stat(orgPath)
.then((stat) => {
return Promise.resolve(stat.size)
})
}
}
// content from variable need create file
else if(typeof data === 'string') {
let encoding = 'utf8'
let mime = String(this.type)
// when content type contains application/octet* or *;base64, RNFetchBlob
// fs will treat it as BASE64 encoded string binary data
if(/(application\/octet|\;base64)/i.test(mime))
encoding = 'base64'
else
data = data.toString()
// create cache file
this.type = String(this.type).replace(/;base64/ig, '')
log.verbose('create Blob cache file from string', 'encode', encoding)
p = fs.writeFile(this._ref, data, encoding)
.then((size) => {
return Promise.resolve(size)
})
}
// TODO : ArrayBuffer support
// else if (data instanceof ArrayBuffer ) {
//
// }
// when input is an array of mixed data types, create a file cache
else if(Array.isArray(data)) {
log.verbose('create Blob cache file from mixed array', data)
p = createMixedBlobData(this._ref, data)
}
else {
data = data.toString()
p = fs.writeFile(this._ref, data, 'utf8')
.then((size) => Promise.resolve(size))
}
p && p.then((size) => {
this.size = size
this._invokeOnCreateEvent()
})
.catch((err) => {
log.error('RNFetchBlob could not create Blob : '+ this._ref, err)
})
}
/**
* Since Blob content will asynchronously write to a file during creation,
* use this method to register an event handler for Blob initialized event.
* @nonstandard
* @param {(b:Blob) => void} An event handler invoked when Blob created
* @return {Blob} The Blob object instance itself
*/
onCreated(fn:() => void):Blob {
log.verbose('#register blob onCreated', this._blobCreated)
if(!this._blobCreated)
this._onCreated.push(fn)
else {
fn(this)
}
return this
}
markAsDerived() {
this._isDerived = true
}
get isDerived() {
return this._isDerived || false
}
/**
* Get file reference of the Blob object.
* @nonstandard
* @return {string} Blob file reference which can be consumed by RNFetchBlob fs
*/
getRNFetchBlobRef() {
return this._ref
}
/**
* Create a Blob object which is sliced from current object
* @param {number} start Start byte number
* @param {number} end End byte number
* @param {string} contentType Optional, content type of new Blob object
* @return {Blob}
*/
slice(start:?number, end:?number, contentType:?string=''):Blob {
if(this._closed)
throw 'Blob has been released.'
log.verbose('slice called', start, end, contentType)
let resPath = blobCacheDir + getBlobName()
let pass = false
log.debug('fs.slice new blob will at', resPath)
let result = new Blob(RNFetchBlob.wrap(resPath), { type : contentType }, true)
fs.exists(blobCacheDir)
.then((exist) => {
if(exist)
return Promise.resolve()
return fs.mkdir(blobCacheDir)
})
.then(() => fs.slice(this._ref, resPath, start, end))
.then((dest) => {
log.debug('fs.slice done', dest)
result._invokeOnCreateEvent()
pass = true
})
.catch((err) => {
console.warn('Blob.slice failed:', err)
pass = true
})
log.debug('slice returning new Blob')
return result
}
/**
* Read data of the Blob object, this is not standard method.
* @nonstandard
* @param {string} encoding Read data with encoding
* @return {Promise}
*/
readBlob(encoding:string):Promise<any> {
if(this._closed)
throw 'Blob has been released.'
return fs.readFile(this._ref, encoding || 'utf8')
}
/**
* Release the resource of the Blob object.
* @nonstandard
* @return {Promise}
*/
close() {
if(this._closed)
return Promise.reject('Blob has been released.')
this._closed = true
return fs.unlink(this._ref).catch((err) => {
console.warn(err)
})
}
_invokeOnCreateEvent() {
log.verbose('invoke create event', this._onCreated)
this._blobCreated = true
let fns = this._onCreated
for(let i in fns) {
if(typeof fns[i] === 'function') {
fns[i](this)
}
}
delete this._onCreated
}
}
/**
* Get a temp filename for Blob object
* @return {string} Temporary filename
*/
function getBlobName() {
return 'blob-' + getUUID()
}
/**
* Create a file according to given array. The element in array can be a number,
* Blob, String, Array.
* @param {string} ref File path reference
* @param {Array} dataArray An array contains different types of data.
* @return {Promise}
*/
function createMixedBlobData(ref, dataArray) {
// create an empty file for store blob data
let p = fs.writeFile(ref, '')
let args = []
let size = 0
for(let i in dataArray) {
let part = dataArray[i]
if(!part)
continue
if(part.isRNFetchBlobPolyfill) {
args.push([ref, part._ref, 'uri'])
}
else if(typeof part === 'string')
args.push([ref, part, 'utf8'])
// TODO : ArrayBuffer
// else if (part instanceof ArrayBuffer) {
//
// }
else if (Array.isArray(part))
args.push([ref, part, 'ascii'])
}
// start write blob data
for(let i in args) {
p = p.then(function(written){
let arg = this
if(written)
size += written
log.verbose('mixed blob write', args[i], written)
return fs.appendFile(...arg)
}.bind(args[i]))
}
return p.then(() => Promise.resolve(size))
}