Browse Source

Refactor JS interface and add file write stream API

Ben Hsieh 8 years ago
parent
commit
fd87219502
3 changed files with 317 additions and 96 deletions
  1. 281
    0
      src/fs.js
  2. 31
    94
      src/index.js
  3. 5
    2
      src/types.js

+ 281
- 0
src/fs.js View File

@@ -0,0 +1,281 @@
1
+/**
2
+ * @name react-native-fetch-blob-fs
3
+ * @author wkh237
4
+ * @version 0.1.0
5
+ * @flow
6
+ */
7
+
8
+import {
9
+  NativeModules,
10
+  DeviceEventEmitter,
11
+  NativeAppEventEmitter,
12
+} from 'react-native'
13
+import type {
14
+  RNFetchBlobNative,
15
+  RNFetchBlobConfig,
16
+  RNFetchBlobStream
17
+} from './types'
18
+
19
+const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob
20
+const emitter = DeviceEventEmitter
21
+
22
+// session table
23
+let sessions = {}
24
+
25
+/**
26
+ * Get path of system directories.
27
+ * @return {object} Map contains PictureDir, MovieDir, DocumentDir, CacheDir,
28
+ * MusicDir, and DCIMDir, some directory might not be supported by platform.
29
+ */
30
+function getSystemDirs() {
31
+  return new Promise((resolve, reject) => {
32
+    try {
33
+      RNFetchBlob.getEnvironmentDirs((...dirs) => {
34
+        let [PictureDir, MovieDir, DocumentDir, CacheDir, MusicDir, DCIMDir] = [...dirs]
35
+        resolve({PictureDir, MovieDir, DocumentDir, CacheDir, MusicDir, DCIMDir})
36
+      })
37
+    } catch(err) {
38
+      reject(err)
39
+    }
40
+  })
41
+}
42
+
43
+/**
44
+ * Get a file cache session
45
+ * @param  {[type]} name:string [description]
46
+ * @return {[type]}             [description]
47
+ */
48
+function session(name:string):RNFetchBlobSession {
49
+  let s = sessions[name]
50
+  if(s)
51
+    return new RNFetchBlobSession(name)
52
+  else {
53
+    sessions[name] = []
54
+    return new RNFetchBlobSession(name, [])
55
+  }
56
+}
57
+
58
+function writeStream(
59
+  path:string,
60
+  encoding:'utf8' | 'ascii' | 'base64',
61
+  callback:(streamId:string) => void
62
+):Promise<WriteStream> {
63
+  if(!path)
64
+    throw Error('RNFetchBlob could not open file stream with empty `path`')
65
+  encoding = encoding || 'base64'
66
+  return new Promise((resolve, reject) => {
67
+    RNFetchBlob.writeStream(path, encoding || 'base64', (streamId:string) => {
68
+      resolve(new WriteStream(streamId))
69
+    })
70
+  })
71
+}
72
+
73
+/**
74
+ * Create file stream from file at `path`.
75
+ * @param  {String} path   The file path.
76
+ * @param  {String} encoding Data encoding, should be one of `base64`, `utf8`, `ascii`
77
+ * @param  {String} bufferSize Size of stream buffer.
78
+ * @return {RNFetchBlobStream} RNFetchBlobStream stream instance.
79
+ */
80
+function readStream(
81
+  path:string,
82
+  encoding:'utf8' | 'ascii' | 'base64',
83
+  bufferSize?:?number
84
+):RNFetchBlobStream {
85
+
86
+  if(!path)
87
+    throw Error('RNFetchBlob could not open file stream with empty `path`')
88
+
89
+  let stream:RNFetchBlobStream = {
90
+    onData : function(fn) {
91
+      this._onData = fn
92
+    },
93
+    onError : function(fn) {
94
+      this._onError = fn
95
+    },
96
+    onEnd : function(fn) {
97
+      this._onEnd = fn
98
+    },
99
+  }
100
+
101
+  // register for file stream event
102
+  let subscription = emitter.addListener(`RNFetchBlobStream+${path}`, (e) => {
103
+
104
+    let {event, detail} = e
105
+    if(stream._onData && event === 'data')
106
+      stream._onData(detail)
107
+    else if (stream._onEnd && event === 'end') {
108
+      stream._onEnd(detail)
109
+    }
110
+    else {
111
+      stream._onError(detail)
112
+    }
113
+    // when stream closed or error, remove event handler
114
+    if (event === 'error' || event === 'end') {
115
+      subscription.remove()
116
+    }
117
+  })
118
+
119
+  RNFetchBlob.readStream(path, encoding, bufferSize || 0)
120
+  return stream
121
+
122
+}
123
+
124
+function mkdir(path:string):Promise {
125
+
126
+  return new Promise((resolve, reject) => {
127
+    RNFetchBlob.mkdir(path, (err, res) => {
128
+      if(err)
129
+        reject(err)
130
+      else
131
+        resolve(res)
132
+    })
133
+  })
134
+
135
+}
136
+
137
+function cp(path:string, dest:string):Promise<boolean> {
138
+  return new Promise((resolve, reject) => {
139
+    RNFetchBlob.cp(path, dest, (err, res) => {
140
+      if(err)
141
+        reject(err)
142
+      else
143
+        resolve(res)
144
+    })
145
+  })
146
+}
147
+
148
+function mv(path:string, dest:string):Promise<boolean> {
149
+  return new Promise((resolve, reject) => {
150
+    RNFetchBlob.ls(path, dest, (err, res) => {
151
+      if(err)
152
+        reject(err)
153
+      else
154
+        resolve(res)
155
+    })
156
+  })
157
+}
158
+
159
+function ls(path:string):Promise<Array<String>> {
160
+  return new Promise((resolve, reject) => {
161
+    RNFetchBlob.ls(path, (err, res) => {
162
+      if(err)
163
+        reject(err)
164
+      else
165
+        resolve(res)
166
+    })
167
+  })
168
+}
169
+
170
+/**
171
+ * Remove file at path.
172
+ * @param  {string}   path:string Path of target file.
173
+ * @return {Promise}
174
+ */
175
+function unlink(path:string):Promise {
176
+  return new Promise((resolve, reject) => {
177
+    RNFetchBlob.unlink(path, (err) => {
178
+      if(err)
179
+        reject(err)
180
+      else
181
+        resolve()
182
+    })
183
+  })
184
+}
185
+
186
+/**
187
+ * Session class
188
+ * @class RNFetchBlobSession
189
+ */
190
+class RNFetchBlobSession {
191
+
192
+  add : (path:string) => RNFetchBlobSession;
193
+  remove : (path:string) => RNFetchBlobSession;
194
+  dispose : () => Promise;
195
+  list : () => Array<string>;
196
+  name : string;
197
+
198
+  constructor(name:string, list:Array<string>) {
199
+    this.name = name
200
+    if(!sessions[name]) {
201
+      if(Array.isArray(list))
202
+      sessions[name] = list
203
+      else
204
+      sessions[name] = []
205
+    }
206
+  }
207
+
208
+  add(path:string):RNFetchBlobSession {
209
+    sessions[this.name].push(path)
210
+    return this
211
+  }
212
+
213
+  remove(path:string):RNFetchBlobSession {
214
+    let list = sessions[this.name]
215
+    for(let i in list) {
216
+      if(list[i] === path) {
217
+        sessions[this.name].splice(i, 1)
218
+        break;
219
+      }
220
+    }
221
+    return this
222
+  }
223
+
224
+  list():Array<string> {
225
+    return sessions[this.name]
226
+  }
227
+
228
+  dispose():Promise {
229
+    return new Promise((resolve, reject) => {
230
+      RNFetchBlob.removeSession(sessions[this.name], (err) => {
231
+        if(err)
232
+          reject(err)
233
+        else {
234
+          delete sessions[this.name]
235
+          resolve()
236
+        }
237
+      })
238
+    })
239
+  }
240
+
241
+}
242
+
243
+class WriteStream {
244
+
245
+  id : string;
246
+  encoding : string;
247
+
248
+  constructor(streamId:string, encoding:string) {
249
+    this.id = streamId
250
+    this.encoding = encoding
251
+  }
252
+
253
+  write() {
254
+    return new Promise((resolve, reject) => {
255
+      try {
256
+        RNFetchBlob.writeChunk(this.id, data, this.encoding, () => {
257
+          resolve()
258
+        })
259
+      } catch(err) {
260
+        reject(err)
261
+      }
262
+    })
263
+  }
264
+
265
+  close() {
266
+    return new Promise((resolve, reject) => {
267
+      try {
268
+        RNFetchBlob.closeStream(this.id, () => {
269
+          resolve()
270
+        })
271
+      } catch (err) {
272
+        reject(err)
273
+      }
274
+    })
275
+  }
276
+
277
+}
278
+
279
+export default {
280
+  RNFetchBlobSession, unlink, mkdir, session, ls, readStream, getSystemDirs, mv, cp
281
+}

+ 31
- 94
src/index.js View File

@@ -10,17 +10,29 @@ import {
10 10
   DeviceEventEmitter,
11 11
   NativeAppEventEmitter,
12 12
   Platform,
13
+  AsyncStorage,
13 14
 } from 'react-native'
14 15
 import type {
15 16
   RNFetchBlobNative,
16 17
   RNFetchBlobConfig,
17 18
   RNFetchBlobStream
18 19
 } from './types'
20
+import fs from './fs'
19 21
 import base64 from 'base-64'
22
+const {
23
+  RNFetchBlobSession,
24
+  getSystemDirs,
25
+  readStream,
26
+  unlink,
27
+  mkdir,
28
+  session,
29
+  ls,
30
+  mv,
31
+  cp
32
+} = fs
20 33
 
21 34
 const emitter = DeviceEventEmitter
22 35
 const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob
23
-const pathPrefix = Platform.OS === 'android' ? 'file://' : ''
24 36
 
25 37
 // register message channel event handler.
26 38
 emitter.addListener("RNFetchBlobMessage", (e) => {
@@ -44,26 +56,6 @@ if(!RNFetchBlob || !RNFetchBlob.fetchBlobForm || !RNFetchBlob.fetchBlob) {
44 56
   )
45 57
 }
46 58
 
47
-/**
48
- * Get path of system directories.
49
- * @return {object} Map contains PictureDir, MovieDir, DocumentDir, CacheDir,
50
- * MusicDir, and DCIMDir, some directory might not be supported by platform.
51
- */
52
-function getSystemDirs() {
53
-  return new Promise((resolve, reject) => {
54
-    try {
55
-      RNFetchBlob.getEnvironmentDirs((...dirs) => {
56
-        let [PictureDir, MovieDir, DocumentDir, CacheDir, MusicDir, DCIMDir] = [...dirs]
57
-        console.log({PictureDir, MovieDir, DocumentDir, CacheDir, MusicDir, DCIMDir})
58
-        resolve({PictureDir, MovieDir, DocumentDir, CacheDir, MusicDir, DCIMDir})
59
-      })
60
-    } catch(err) {
61
-      reject(err)
62
-    }
63
-  })
64
-
65
-}
66
-
67 59
 /**
68 60
  * Calling this method will inject configurations into followed `fetch` method.
69 61
  * @param  {RNFetchBlobConfig} options
@@ -117,17 +109,21 @@ function fetch(...args:any):Promise {
117 109
     })
118 110
 
119 111
     let req = RNFetchBlob[nativeMethodName]
120
-    req(options, taskId, method, url, headers || {}, body, (err, ...data) => {
112
+    req(options, taskId, method, url, headers || {}, body, (err, data) => {
121 113
 
122 114
       // task done, remove event listener
123 115
       subscription.remove()
124 116
       if(err)
125
-        reject(new Error(err, ...data))
117
+        reject(new Error(err, data))
126 118
       else {
127 119
         let respType = 'base64'
128
-        if(options.path || options.fileCache)
120
+        // response data is saved to storage
121
+        if(options.path || options.fileCache) {
129 122
           respType = 'path'
130
-        resolve(new FetchBlobResponse(taskId, respType, ...data))
123
+          if(options.session)
124
+            session(options.session).add(data)
125
+        }
126
+        resolve(new FetchBlobResponse(taskId, respType, data))
131 127
       }
132 128
     })
133 129
 
@@ -144,73 +140,6 @@ function fetch(...args:any):Promise {
144 140
 
145 141
 }
146 142
 
147
-/**
148
- * Create file stream from file at `path`.
149
- * @param  {String} path   The file path.
150
- * @param  {String} encoding Data encoding, should be one of `base64`, `utf8`, `ascii`
151
- * @param  {String} bufferSize Size of stream buffer.
152
- * @return {RNFetchBlobStream} RNFetchBlobStream stream instance.
153
- */
154
-function openReadStream(
155
-  path:string,
156
-  encoding:'utf8' | 'ascii' | 'base64',
157
-  bufferSize?:?number
158
-):RNFetchBlobStream {
159
-
160
-  if(!path)
161
-    throw Error('RNFetchBlob could not open file stream with empty `path`')
162
-
163
-  let stream:RNFetchBlobStream = {
164
-    onData : function(fn) {
165
-      this._onData = fn
166
-    },
167
-    onError : function(fn) {
168
-      this._onError = fn
169
-    },
170
-    onEnd : function(fn) {
171
-      this._onEnd = fn
172
-    },
173
-  }
174
-
175
-  // register for file stream event
176
-  let subscription = emitter.addListener(`RNFetchBlobStream+${path}`, (e) => {
177
-
178
-    let {event, detail} = e
179
-    if(stream._onData && event === 'data')
180
-      stream._onData(detail)
181
-    else if (stream._onEnd && event === 'end') {
182
-      stream._onEnd(detail)
183
-    }
184
-    else {
185
-      stream._onError(detail)
186
-    }
187
-    // when stream closed or error, remove event handler
188
-    if (event === 'error' || event === 'end') {
189
-      subscription.remove()
190
-    }
191
-  })
192
-
193
-  RNFetchBlob.readStream(path, encoding, bufferSize || 0)
194
-  return stream
195
-
196
-}
197
-
198
-/**
199
- * Remove file at path.
200
- * @param  {string}   path:string Path of target file.
201
- * @return {Promise}
202
- */
203
-function unlink(path:string):Promise {
204
-  return new Promise((resolve, reject) => {
205
-    RNFetchBlob.unlink(path, (err) => {
206
-      if(err)
207
-        reject(err)
208
-      else
209
-        resolve()
210
-    })
211
-  })
212
-}
213
-
214 143
 /**
215 144
  * RNFetchBlob response object class.
216 145
  */
@@ -280,6 +209,14 @@ class FetchBlobResponse {
280 209
         return this.data
281 210
       return null
282 211
     }
212
+    this.session = (name) => {
213
+      if(this.type === 'path')
214
+        return session(name).add(this.data)
215
+      else {
216
+        console.warn('only file paths can be add into session.')
217
+        return null
218
+      }
219
+    }
283 220
     /**
284 221
      * Start read stream from cached file
285 222
      * @param  {String} encoding Encode type, should be one of `base64`, `ascrii`, `utf8`.
@@ -288,7 +225,7 @@ class FetchBlobResponse {
288 225
      */
289 226
     this.readStream = (encode: 'base64' | 'utf8' | 'ascii'):RNFetchBlobStream | null => {
290 227
       if(this.type === 'path') {
291
-        return openReadStream(this.data, encode)
228
+        return readStream(this.data, encode)
292 229
       }
293 230
       else {
294 231
         console.warn('RNFetchblob', 'this response data does not contains any available stream')
@@ -307,5 +244,5 @@ function getUUID() {
307 244
 }
308 245
 
309 246
 export default {
310
-  fetch, base64, config, getSystemDirs, readStream, unlink
247
+  fetch, base64, config, getSystemDirs, readStream, unlink, session, ls, mkdir, mv, cp
311 248
 }

+ 5
- 2
src/types.js View File

@@ -2,7 +2,8 @@
2 2
 type RNFetchBlobConfig = {
3 3
   fileCache : bool,
4 4
   path : string,
5
-  appendExt : string
5
+  appendExt : string,
6
+  session : string
6 7
 };
7 8
 
8 9
 type RNFetchBlobNative = {
@@ -34,7 +35,9 @@ type RNFetchBlobNative = {
34 35
   // get system folders
35 36
   getEnvironmentDirs : (dirs:any) => void,
36 37
   // unlink file by path
37
-  flush : (path:string) => void
38
+  unlink : (path:string, callback: (err:any) => void) => void,
39
+  removeSession : (paths:Array<string>, callback: (err:any) => void) => void,
40
+  ls : (path:string, callback: (err:any) => void) => void,
38 41
 };
39 42
 
40 43
 type RNFetchBlobStream = {