123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347 |
- // 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))
- }
|