暂无描述

Blob.js 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. // Copyright 2016 wkh237@github. All rights reserved.
  2. // Use of this source code is governed by a MIT-style license that can be
  3. // found in the LICENSE file.
  4. import RNFetchBlob from '../index.js'
  5. import fs from '../fs.js'
  6. import getUUID from '../utils/uuid'
  7. import Log from '../utils/log.js'
  8. import EventTarget from './EventTarget'
  9. const log = new Log('Blob')
  10. const blobCacheDir = fs.dirs.DocumentDir + '/RNFetchBlob-blobs/'
  11. log.disable()
  12. // log.level(3)
  13. /**
  14. * A RNFetchBlob style Blob polyfill class, this is a Blob which compatible to
  15. * Response object attain fron RNFetchBlob.fetch.
  16. */
  17. export default class Blob extends EventTarget {
  18. cacheName:string;
  19. type:string;
  20. size:number;
  21. isRNFetchBlobPolyfill:boolean = true;
  22. multipartBoundary:string = null;
  23. _ref:string = null;
  24. _blobCreated:boolean = false;
  25. _onCreated:Array<any> = [];
  26. _closed:boolean = false;
  27. /**
  28. * Static method that remove all files in Blob cache folder.
  29. * @nonstandard
  30. * @return {Promise}
  31. */
  32. static clearCache() {
  33. return fs.unlink(blobCacheDir).then(() => fs.mkdir(blobCacheDir))
  34. }
  35. static build(data:any, cType:any):Promise<Blob> {
  36. return new Promise((resolve, reject) => {
  37. new Blob(data, cType).onCreated(resolve)
  38. })
  39. }
  40. get blobPath() {
  41. return this._ref
  42. }
  43. /**
  44. * RNFetchBlob Blob polyfill, create a Blob directly from file path, BASE64
  45. * encoded data, and string. The conversion is done implicitly according to
  46. * given `mime`. However, the blob creation is asynchronously, to register
  47. * event `onCreated` is need to ensure the Blob is creadted.
  48. * @param {any} data Content of Blob object
  49. * @param {any} mime Content type settings of Blob object, `text/plain`
  50. * by default
  51. */
  52. constructor(data:any, cType:any) {
  53. super()
  54. cType = cType || {}
  55. this.cacheName = getBlobName()
  56. this.isRNFetchBlobPolyfill = true
  57. this.type = cType.type || 'text/plain'
  58. log.verbose('Blob constructor called', 'mime', this.type, 'type', typeof data, 'length', data? data.length:0)
  59. this._ref = blobCacheDir + this.cacheName
  60. let p = null
  61. if(!data)
  62. data = ''
  63. if(data.isRNFetchBlobPolyfill) {
  64. log.verbose('create Blob cache file from Blob object')
  65. let size = 0
  66. this._ref = String(data.getRNFetchBlobRef())
  67. let orgPath = this._ref
  68. p = fs.exists(orgPath)
  69. .then((exist) => {
  70. if(exist)
  71. return fs.writeFile(orgPath, data, 'uri')
  72. .then((size) => Promise.resolve(size))
  73. .catch((err) => {
  74. throw `RNFetchBlob Blob file creation error, ${err}`
  75. })
  76. else
  77. throw `could not create Blob from path ${orgPath}, file not exists`
  78. })
  79. }
  80. // process FormData
  81. else if(data instanceof FormData) {
  82. log.verbose('create Blob cache file from FormData', data)
  83. let boundary = `RNFetchBlob-${this.cacheName}-${Date.now()}`
  84. this.multipartBoundary = boundary
  85. let parts = data.getParts()
  86. let formArray = []
  87. for(let i in parts) {
  88. formArray.push('\r\n--'+boundary+'\r\n')
  89. let part = parts[i]
  90. for(let j in part.headers) {
  91. formArray.push(j + ': ' +part.headers[j] + ';\r\n')
  92. }
  93. formArray.push('\r\n')
  94. if(part.isRNFetchBlobPolyfill)
  95. formArray.push(part)
  96. else
  97. formArray.push(part.string)
  98. }
  99. log.verbose('FormData array', formArray)
  100. formArray.push('\r\n--'+boundary+'--\r\n')
  101. p = createMixedBlobData(this._ref, formArray)
  102. }
  103. // if the data is a string starts with `RNFetchBlob-file://`, append the
  104. // Blob data from file path
  105. else if(typeof data === 'string' && data.startsWith('RNFetchBlob-file://')) {
  106. log.verbose('create Blob cache file from file path', data)
  107. this._ref = String(data).replace('RNFetchBlob-file://', '')
  108. let orgPath = this._ref
  109. p = fs.stat(orgPath)
  110. .then((stat) => {
  111. return Promise.resolve(stat.size)
  112. })
  113. }
  114. // content from variable need create file
  115. else if(typeof data === 'string') {
  116. let encoding = 'utf8'
  117. let mime = String(this.type)
  118. // when content type contains application/octet* or *;base64, RNFetchBlob
  119. // fs will treat it as BASE64 encoded string binary data
  120. if(/(application\/octet|\;base64)/i.test(mime))
  121. encoding = 'base64+urlencode'
  122. else
  123. data = data.toString()
  124. // create cache file
  125. this.type = String(this.type).replace(/;base64/ig, '')
  126. log.verbose('create Blob cache file from string', 'encode', encoding)
  127. p = fs.writeFile(this._ref, data, encoding)
  128. .then((size) => {
  129. return Promise.resolve(size)
  130. })
  131. }
  132. // TODO : ArrayBuffer support
  133. // else if (data instanceof ArrayBuffer ) {
  134. //
  135. // }
  136. // when input is an array of mixed data types, create a file cache
  137. else if(Array.isArray(data)) {
  138. log.verbose('create Blob cache file from mixed array', data)
  139. p = createMixedBlobData(this._ref, data)
  140. }
  141. else {
  142. data = data.toString()
  143. p = fs.writeFile(this._ref, data, 'utf8')
  144. .then((size) => Promise.resolve(size))
  145. }
  146. p && p.then((size) => {
  147. this.size = size
  148. this._invokeOnCreateEvent()
  149. })
  150. .catch((err) => {
  151. log.error('RNFetchBlob could not create Blob : '+ this._ref, err)
  152. })
  153. }
  154. /**
  155. * Since Blob content will asynchronously write to a file during creation,
  156. * use this method to register an event handler for Blob initialized event.
  157. * @nonstandard
  158. * @param {(b:Blob) => void} An event handler invoked when Blob created
  159. * @return {Blob} The Blob object instance itself
  160. */
  161. onCreated(fn:() => void):Blob {
  162. log.verbose('register blob onCreated', this._onCreated.length)
  163. if(!this._blobCreated)
  164. this._onCreated.push(fn)
  165. else
  166. fn(this)
  167. return this
  168. }
  169. /**
  170. * Get file reference of the Blob object.
  171. * @nonstandard
  172. * @return {string} Blob file reference which can be consumed by RNFetchBlob fs
  173. */
  174. getRNFetchBlobRef() {
  175. return this._ref
  176. }
  177. /**
  178. * Create a Blob object which is sliced from current object
  179. * @param {number} start Start byte number
  180. * @param {number} end End byte number
  181. * @param {string} contentType Optional, content type of new Blob object
  182. * @return {Blob}
  183. */
  184. slice(start:?number, end:?number, encoding:?string):Blob {
  185. if(this._closed)
  186. throw 'Blob has been released.'
  187. log.verbose('slice called', start, end, encoding)
  188. console.warn('RNFB#Blob.slice() is not implemented yet, to read Blob content, use Blob.readBlob(encoding:string) instead.')
  189. // TODO : fs.slice
  190. // return fs.slice(this.cacheName, getBlobName(), contentType, start, end)
  191. }
  192. /**
  193. * Read data of the Blob object, this is not standard method.
  194. * @nonstandard
  195. * @param {string} encoding Read data with encoding
  196. * @return {Promise}
  197. */
  198. readBlob(encoding:string):Promise<any> {
  199. if(this._closed)
  200. throw 'Blob has been released.'
  201. return fs.readFile(this._ref, encoding || 'utf8')
  202. }
  203. /**
  204. * Release the resource of the Blob object.
  205. * @nonstandard
  206. * @return {Promise}
  207. */
  208. close() {
  209. if(this._closed)
  210. return Promise.reject('Blob has been released.')
  211. this._closed = true
  212. return fs.unlink(this._ref)
  213. }
  214. _invokeOnCreateEvent() {
  215. log.verbose('invoke create event')
  216. this._blobCreated = true
  217. let fns = this._onCreated
  218. for(let i in fns) {
  219. if(typeof fns[i] === 'function')
  220. fns[i](this)
  221. }
  222. delete this._onCreated
  223. }
  224. }
  225. /**
  226. * Get a temp filename for Blob object
  227. * @return {string} Temporary filename
  228. */
  229. function getBlobName() {
  230. return 'blob-' + getUUID()
  231. }
  232. /**
  233. * Create a file according to given array. The element in array can be a number,
  234. * Blob, String, Array.
  235. * @param {string} ref File path reference
  236. * @param {Array} dataArray An array contains different types of data.
  237. * @return {Promise}
  238. */
  239. function createMixedBlobData(ref, dataArray) {
  240. let p = fs.writeFile(ref, '')
  241. let args = []
  242. let size = 0
  243. for(let i in dataArray) {
  244. let part = dataArray[i]
  245. if(part.isRNFetchBlobPolyfill) {
  246. args.push([ref, part._ref, 'uri'])
  247. }
  248. else if(typeof part === 'string')
  249. args.push([ref, part, 'utf8'])
  250. // TODO : ArrayBuffer
  251. // else if (part instanceof ArrayBuffer) {
  252. //
  253. // }
  254. else if (Array.isArray(part))
  255. args.push([ref, part, 'ascii'])
  256. }
  257. return p.then(() => {
  258. let promises = args.map((p) => {
  259. log.verbose('mixed blob write', ...p)
  260. return fs.appendFile.call(this, ...p)
  261. })
  262. return Promise.all(promises).then((sizes) => {
  263. console.log('blob write size', sizes)
  264. for(let i in sizes) {
  265. size += sizes[i]
  266. }
  267. return Promise.resolve(size)
  268. })
  269. })
  270. }