Ver código fonte

#15 file stream reader IOS implementation

Ben Hsieh 8 anos atrás
pai
commit
25a5f9a6bf
4 arquivos alterados com 168 adições e 88 exclusões
  1. 62
    59
      src/index.js
  2. 1
    1
      src/ios/RNFetchBlob/RNFetchBlob.h
  3. 58
    28
      src/ios/RNFetchBlob/RNFetchBlob.m
  4. 47
    0
      src/types.js

+ 62
- 59
src/index.js Ver arquivo

@@ -10,47 +10,19 @@ import {
10 10
   NativeAppEventEmitter,
11 11
   Platform,
12 12
 } from 'react-native'
13
-
14
-
15
-type fetchConfig = {
16
-  fileCache : bool,
17
-  path : string,
18
-  appendExt : string
19
-};
20
-
21
-type RNFetchBlobNative = {
22
-  fetchBlob : (
23
-    options:fetchConfig,
24
-    taskId:string,
25
-    method:string,
26
-    url:string,
27
-    headers:any,
28
-    body:any,
29
-    callback:(err:any, ...data:any) => void
30
-  ) => void,
31
-  fetchBlobForm : (
32
-    options:fetchConfig,
33
-    taskId:string,
34
-    method:string,
35
-    url:string,
36
-    headers:any,
37
-    form:Array<any>,
38
-    callback:(err:any, ...data:any) => void
39
-  ) => void,
40
-  readStream : (
41
-    path:string,
42
-    encode: 'utf8' | 'ascii' | 'base64'
43
-  ) => void,
44
-  getEnvironmentDirs : (dirs:any) => void,
45
-  flush : () => void
46
-};
47
-
13
+import type {
14
+  RNFetchBlobNative,
15
+  RNFetchBlobConfig,
16
+  RNFetchBlobStream
17
+}from './types'
48 18
 import base64 from 'base-64'
49
-const emitter = (Platform.OS === 'android' ? DeviceEventEmitter : NativeAppEventEmitter)
19
+
20
+// const emitter = (Platform.OS === 'android' ? DeviceEventEmitter : NativeAppEventEmitter)
21
+const emitter = DeviceEventEmitter
50 22
 const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob
51 23
 
52 24
 emitter.addListener("RNFetchBlobMessage", (e) => {
53
-
25
+  console.log(e)
54 26
   if(e.event === 'warn') {
55 27
     console.warn(e.detail)
56 28
   }
@@ -86,17 +58,16 @@ function getSystemDirs() {
86 58
 
87 59
 }
88 60
 
89
-function config (options:fetchConfig) {
61
+function config (options:RNFetchBlobConfig) {
90 62
   return { fetch : fetch.bind(options) }
91 63
 }
92 64
 
93 65
 // Promise wrapper function
94 66
 function fetch(...args:any) {
95 67
 
96
-  let options = this || {}
97
-
98 68
   // create task ID for receiving progress event
99 69
   let taskId = getUUID()
70
+  let options = this || {}
100 71
 
101 72
   let promise = new Promise((resolve, reject) => {
102 73
 
@@ -123,7 +94,6 @@ function fetch(...args:any) {
123 94
           respType = 'path'
124 95
         resolve(new FetchBlobResponse(taskId, respType, ...data))
125 96
       }
126
-
127 97
     })
128 98
 
129 99
   })
@@ -137,6 +107,47 @@ function fetch(...args:any) {
137 107
 
138 108
 }
139 109
 
110
+function openReadStream(path:string, encoding:'utf8' | 'ascii' | 'base64'):RNFetchBlobStream {
111
+
112
+  if(!path)
113
+    throw Error('RNFetchBlob could not open file stream with empty `path`')
114
+
115
+  let stream:RNFetchBlobStream = {
116
+    onData : function(fn) {
117
+      this._onData = fn
118
+    },
119
+    onError : function(fn) {
120
+      this._onError = fn
121
+    },
122
+    onEnd : function(fn) {
123
+      this._onEnd = fn
124
+    },
125
+  }
126
+
127
+  // register for file stream event
128
+  let subscription = emitter.addListener(`RNFetchBlobStream+${path}`, (e) => {
129
+    let {event, detail} = e
130
+    if(stream._onData && event === 'data')
131
+      stream._onData(detail)
132
+    else if (stream._onEnd && event === 'end') {
133
+      stream._onEnd(detail)
134
+    }
135
+    else {
136
+      stream._onError(detail)
137
+    }
138
+    // when stream closed or error, remove event handler
139
+    if (event === 'error' || event === 'end') {
140
+      subscription.remove()
141
+    }
142
+
143
+  })
144
+
145
+  RNFetchBlob.readStream(path, encoding)
146
+
147
+  return stream
148
+
149
+}
150
+
140 151
 /**
141 152
  * RNFetchBlob response object class.
142 153
  */
@@ -153,8 +164,7 @@ class FetchBlobResponse {
153 164
   flush : () => void;
154 165
   readStream : (
155 166
     encode: 'utf8' | 'ascii' | 'base64',
156
-    fn:(event : 'data' | 'end', chunk:any) => void
157
-  ) => void;
167
+  ) => RNFetchBlobStream | null;
158 168
 
159 169
   constructor(taskId:string, type:'base64' | 'path', data:any) {
160 170
     this.data = data
@@ -198,43 +208,36 @@ class FetchBlobResponse {
198 208
     this.flush = () => {
199 209
       RNFetchBlob.flush(this.path())
200 210
     }
201
-
211
+    /**
212
+     * get path of response temp file
213
+     * @return {string} File path of temp file.
214
+     */
202 215
     this.path = () => {
203 216
       if(this.type === 'path')
204 217
         return this.data
205 218
       return null
206 219
     }
207
-
208 220
     /**
209 221
      * Start read stream from cached file
210 222
      * @param  {String} encoding Encode type, should be one of `base64`, `ascrii`, `utf8`.
211 223
      * @param  {Function} fn On data event handler
212 224
      * @return {void}
213 225
      */
214
-    this.readStream = (encode: 'base64' | 'utf8' | 'ascii', fn) => {
215
-
216
-      // register for file stream event
217
-      let subscription = emitter.addListener(`RNFetchBlobStream${this.taskId}`, (event, chunk) => {
218
-        fn(event, chunk)
219
-        // when stream closed, remove event handler
220
-        if(event === 'end')
221
-          subscription()
222
-      })
223
-
226
+    this.readStream = (encode: 'base64' | 'utf8' | 'ascii'):RNFetchBlobStream | null => {
224 227
       if(this.type === 'path') {
225
-        RNFetchBlob.readStream(this.data, encode)
228
+        return openReadStream(this.data, encode)
226 229
       }
227 230
       else {
228 231
         console.warn('RNFetchblob', 'this response data does not contains any available stream')
232
+        return null
229 233
       }
230
-
231 234
     }
232 235
 
233 236
   }
234 237
 
235 238
 }
236 239
 
237
-function getUUID(){
240
+function getUUID() {
238 241
   return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
239 242
     let r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
240 243
     return v.toString(16);
@@ -242,5 +245,5 @@ function getUUID(){
242 245
 }
243 246
 
244 247
 export default {
245
-  fetch, base64, config, getSystemDirs
248
+  fetch, base64, config, getSystemDirs, openReadStream
246 249
 }

+ 1
- 1
src/ios/RNFetchBlob/RNFetchBlob.h Ver arquivo

@@ -46,6 +46,7 @@ extern NSString *const FS_EVENT_ERROR;
46 46
 @property (nonatomic) NSString * path;
47 47
 
48 48
 + (NSString *) getTempPath;
49
+- (id) init;
49 50
 - (void) initWithCallback;
50 51
 - (void) initWithBridgeRef;
51 52
 - (void) openWithDestination;
@@ -78,7 +79,6 @@ extern NSString *const FS_EVENT_ERROR;
78 79
 @property (nonatomic) FetchBlobFS * fileStream;
79 80
 
80 81
 - (id) init;
81
-- (id) delegate;
82 82
 - (void) sendRequest;
83 83
 
84 84
 + (NSMutableDictionary *) normalizeHeaders;

+ 58
- 28
src/ios/RNFetchBlob/RNFetchBlob.m Ver arquivo

@@ -42,13 +42,12 @@ NSString *const FS_EVENT_ERROR = @"error";
42 42
 
43 43
 
44 44
 
45
-+ (NSString *) getCacheDir {
46 45
 
46
++ (NSString *) getCacheDir {
47 47
     return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
48 48
 }
49 49
 
50 50
 + (NSString *) getDocumentDir {
51
-
52 51
     return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
53 52
 }
54 53
 
@@ -75,6 +74,12 @@ NSString *const FS_EVENT_ERROR = @"error";
75 74
     return tempPath;
76 75
 }
77 76
 
77
+- (id)init {
78
+    self = [super init];
79
+    return self;
80
+}
81
+
82
+
78 83
 - (id)initWithCallback:(RCTResponseSenderBlock)callback {
79 84
     self = [super init];
80 85
     self.callback = callback;
@@ -83,12 +88,13 @@ NSString *const FS_EVENT_ERROR = @"error";
83 88
 
84 89
 - (id)initWithBridgeRef:(RCTBridge *)bridgeRef {
85 90
     self = [super init];
86
-    self.callback = callback;
91
+    self.bridge = bridgeRef;
87 92
     return self;
88 93
 }
89 94
 
90 95
 - (void)openWithPath:(NSString *)destPath {
91 96
     self.outStream = [[NSOutputStream alloc] initToFileAtPath:destPath append:YES];
97
+    [self.outStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
92 98
     [self.outStream open];
93 99
 }
94 100
 
@@ -108,13 +114,25 @@ NSString *const FS_EVENT_ERROR = @"error";
108 114
 }
109 115
 
110 116
 - (void)readWithPath:(NSString *)path useEncoding:(NSString *)encoding {
111
-    self.inStream = [[NSInputStream alloc]init];
117
+    
118
+    self.inStream = [[NSInputStream alloc] initWithFileAtPath:path];
119
+    self.inStream.delegate = self;
112 120
     self.encoding = encoding;
113
-    [self.inStream setDelegate:self];
121
+    self.path = path;
122
+    
123
+    // NSStream needs a runloop so let's create a run loop for it
124
+    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
125
+    // start NSStream is a runloop
126
+    dispatch_async(queue, ^ {
127
+        [inStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
128
+                             forMode:NSDefaultRunLoopMode];
129
+        [inStream open];
130
+        [[NSRunLoop currentRunLoop] run];
131
+    
132
+    });
114 133
     
115 134
 }
116 135
 
117
-
118 136
 // close file write stream
119 137
 - (void)closeOutStream {
120 138
     if(self.outStream != nil) {
@@ -133,10 +151,25 @@ NSString *const FS_EVENT_ERROR = @"error";
133 151
     
134 152
 }
135 153
 
154
+void runOnMainQueueWithoutDeadlocking(void (^block)(void))
155
+{
156
+    if ([NSThread isMainThread])
157
+    {
158
+        block();
159
+    }
160
+    else
161
+    {
162
+        dispatch_sync(dispatch_get_main_queue(), block);
163
+    }
164
+}
165
+
166
+
136 167
 #pragma mark RNFetchBlobFS read stream delegate
137 168
 
138 169
 - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
139 170
 
171
+    NSString * streamEventCode = [NSString stringWithFormat:@"RNFetchBlobStream+%@", self.path];
172
+    
140 173
     switch(eventCode) {
141 174
             
142 175
         // write stream event
@@ -149,10 +182,7 @@ NSString *const FS_EVENT_ERROR = @"error";
149 182
         // read stream incoming chunk
150 183
         case NSStreamEventHasBytesAvailable:
151 184
         {
152
-            
153
-            
154 185
             NSMutableData * chunkData = [[NSMutableData data] init];
155
-            
156 186
             uint8_t buf[1024];
157 187
             unsigned int len = 0;
158 188
             len = [(NSInputStream *)stream read:buf maxLength:1024];
@@ -160,41 +190,41 @@ NSString *const FS_EVENT_ERROR = @"error";
160 190
             if(len) {
161 191
                 [chunkData appendBytes:(const void *)buf length:len];
162 192
                 // TODO : file read progress ?
163
-//                [bytesRead setIntValue:[bytesRead intValue]+len];
164
-                
165 193
                 // dispatch data event
166
-                NSString * encodedChunk = [NSString alloc];
167
-                if( [self.encoding caseInsensitiveCompare:@"utf8"] ) {
194
+                NSString * encodedChunk;
195
+                if( [[self.encoding lowercaseString] isEqualToString:@"utf8"] ) {
168 196
                     encodedChunk = [encodedChunk initWithData:chunkData encoding:NSUTF8StringEncoding];
169 197
                 }
170
-                else if ( [self.encoding caseInsensitiveCompare:@"ascii"] ) {
198
+                else if ( [[self.encoding lowercaseString] isEqualToString:@"ascii"] ) {
171 199
                     encodedChunk = [encodedChunk initWithData:chunkData encoding:NSASCIIStringEncoding];
172 200
                 }
173
-                else if ( [self.encoding caseInsensitiveCompare:@"base64"] ) {
201
+                else if ( [[self.encoding lowercaseString] isEqualToString:@"base64"] ) {
174 202
                     encodedChunk = [chunkData base64EncodedStringWithOptions:0];
175 203
                 }
176 204
                 else {
177 205
                     [self.bridge.eventDispatcher
178
-                     sendAppEventWithName: [NSString stringWithFormat:@"RNFetchBlobStream%s", self.taskId]
179
-                     body:@{
206
+                        sendDeviceEventWithName:streamEventCode
207
+                        body:@{
180 208
                             @"event": FS_EVENT_ERROR,
181 209
                             @"detail": @"unrecognized encoding"
182 210
                         }
183 211
                      ];
184 212
                     return;
185 213
                 }
186
-                [self.bridge.eventDispatcher
187
-                 sendAppEventWithName: [NSString stringWithFormat:@"RNFetchBlobStream%s", self.taskId]
188
-                 body:@{
189
-                        @"event": FS_EVENT_DATA,
190
-                        @"detail": encodedChunk
191
-                    }
192
-                 ];
214
+                runOnMainQueueWithoutDeadlocking(^{
215
+                    [self.bridge.eventDispatcher
216
+                     sendDeviceEventWithName:streamEventCode
217
+                     body:@{
218
+                            @"event": FS_EVENT_DATA,
219
+                            @"detail": encodedChunk
220
+                            }
221
+                     ];
222
+                });
193 223
             }
194 224
             // end of stream
195 225
             else {
196 226
                 [self.bridge.eventDispatcher
197
-                sendAppEventWithName: [NSString stringWithFormat:@"RNFetchBlobStream%s", self.taskId]
227
+                sendDeviceEventWithName:streamEventCode
198 228
                 body:@{
199 229
                        @"event": FS_EVENT_END,
200 230
                        @"detail": @""
@@ -208,7 +238,7 @@ NSString *const FS_EVENT_ERROR = @"error";
208 238
         case NSStreamEventErrorOccurred:
209 239
         {
210 240
             [self.bridge.eventDispatcher
211
-             sendAppEventWithName: [NSString stringWithFormat:@"RNFetchBlobStream%s", self.taskId]
241
+             sendDeviceEventWithName:streamEventCode
212 242
              body:@{
213 243
                     @"event": FS_EVENT_ERROR,
214 244
                     @"detail": @"error when read file with stream"
@@ -318,7 +348,7 @@ NSString *const FS_EVENT_ERROR = @"error";
318 348
     }
319 349
     
320 350
     [self.bridge.eventDispatcher
321
-        sendAppEventWithName:@"RNFetchBlobProgress"
351
+        sendDeviceEventWithName:@"RNFetchBlobProgress"
322 352
         body:@{
323 353
             @"taskId": taskId,
324 354
             @"written": [NSString stringWithFormat:@"%d", receivedBytes],
@@ -332,7 +362,7 @@ NSString *const FS_EVENT_ERROR = @"error";
332 362
     expectedBytes = totalBytesExpectedToWrite;
333 363
     receivedBytes += totalBytesWritten;
334 364
     [self.bridge.eventDispatcher
335
-        sendAppEventWithName:@"RNFetchBlobProgress"
365
+        sendDeviceEventWithName:@"RNFetchBlobProgress"
336 366
             body:@{
337 367
                     @"taskId": taskId,
338 368
                     @"written": [NSString stringWithFormat:@"%d", receivedBytes],

+ 47
- 0
src/types.js Ver arquivo

@@ -0,0 +1,47 @@
1
+
2
+type RNFetchBlobConfig = {
3
+  fileCache : bool,
4
+  path : string,
5
+  appendExt : string
6
+};
7
+
8
+type RNFetchBlobNative = {
9
+  // API for fetch octet-stream data
10
+  fetchBlob : (
11
+    options:fetchConfig,
12
+    taskId:string,
13
+    method:string,
14
+    url:string,
15
+    headers:any,
16
+    body:any,
17
+    callback:(err:any, ...data:any) => void
18
+  ) => void,
19
+  // API for fetch form data
20
+  fetchBlobForm : (
21
+    options:fetchConfig,
22
+    taskId:string,
23
+    method:string,
24
+    url:string,
25
+    headers:any,
26
+    form:Array<any>,
27
+    callback:(err:any, ...data:any) => void
28
+  ) => void,
29
+  // open file stream
30
+  readStream : (
31
+    path:string,
32
+    encode: 'utf8' | 'ascii' | 'base64'
33
+  ) => void,
34
+  // get system folders
35
+  getEnvironmentDirs : (dirs:any) => void,
36
+  // unlink file by path
37
+  flush : (path:string) => void
38
+};
39
+
40
+type RNFetchBlobStream = {
41
+  onData : () => void,
42
+  onError : () => void,
43
+  onEnd : () => void,
44
+  _onData : () => void,
45
+  _onEnd : () => void,
46
+  _onError : () => void,
47
+}