Browse Source

Merge pull request #198 from capriza/master

Adding iOS background upload support
Travis Nuttall 6 years ago
parent
commit
d69c72bb3f
No account linked to committer's email address
4 changed files with 109 additions and 59 deletions
  1. 29
    0
      README.md
  2. 2
    8
      ios/RNFetchBlobNetwork.m
  3. 4
    1
      ios/RNFetchBlobRequest.h
  4. 74
    50
      ios/RNFetchBlobRequest.m

+ 29
- 0
README.md View File

29
  * [Multipart/form upload](#user-content-multipartform-data-example--post-form-data-with-file-and-data)
29
  * [Multipart/form upload](#user-content-multipartform-data-example--post-form-data-with-file-and-data)
30
  * [Upload/Download progress](#user-content-uploaddownload-progress)
30
  * [Upload/Download progress](#user-content-uploaddownload-progress)
31
  * [Cancel HTTP request](#user-content-cancel-request)
31
  * [Cancel HTTP request](#user-content-cancel-request)
32
+ * [iOS Background Uploading](#user-content-ios-background-uploading)
32
  * [Android Media Scanner, and Download Manager Support](#user-content-android-media-scanner-and-download-manager-support)
33
  * [Android Media Scanner, and Download Manager Support](#user-content-android-media-scanner-and-download-manager-support)
33
  * [Self-Signed SSL Server](#user-content-self-signed-ssl-server)
34
  * [Self-Signed SSL Server](#user-content-self-signed-ssl-server)
34
  * [Transfer Encoding](#user-content-transfer-encoding)
35
  * [Transfer Encoding](#user-content-transfer-encoding)
475
 
476
 
476
 [See document and examples](https://github.com/joltup/rn-fetch-blob/wiki/Fetch-API#fetch-replacement)
477
 [See document and examples](https://github.com/joltup/rn-fetch-blob/wiki/Fetch-API#fetch-replacement)
477
 
478
 
479
+### iOS Background Uploading
480
+ Normally, iOS interrupts network connections when an app is moved to the background, and will throw an error 'Lost connection to background transfer service' when the app resumes. To continue the upload of large files even when the app is in the background, you will need to enable IOSUploadTask options.
481
+
482
+First add the following property to your AppDelegate.h:
483
+```
484
+@property (nonatomic, copy) void(^backgroundTransferCompletionHandler)();
485
+```
486
+Then add the following to your AppDelegate.m:
487
+```
488
+- (void)application:(UIApplication *)application
489
+handleEventsForBackgroundURLSession:(NSString *)identifier
490
+  completionHandler:(void (^)(void))completionHandler {
491
+  self.backgroundTransferCompletionHandler = completionHandler;
492
+}
493
+```
494
+The following example shows how to upload a file in the background:
495
+ ```js
496
+ RNFetchBlob
497
+    .config({
498
+        IOSBackgroundTask: true, // required for both upload
499
+        IOSUploadTask: true, // Use instead of IOSDownloadTask if uploading
500
+        uploadFilePath : 'file://' + filePath
501
+    })
502
+    .fetch('PUT', url, {
503
+            'Content-Type': mediaType
504
+        }, RNFetchBlob.wrap(filePath));
505
+```
506
+
478
 ### Android Media Scanner, and Download Manager Support
507
 ### Android Media Scanner, and Download Manager Support
479
 
508
 
480
 If you want to make a file in `External Storage` becomes visible in Picture, Downloads, or other built-in apps, you will have to use `Media Scanner` or `Download Manager`.
509
 If you want to make a file in `External Storage` becomes visible in Picture, Downloads, or other built-in apps, you will have to use `Media Scanner` or `Download Manager`.

+ 2
- 8
ios/RNFetchBlobNetwork.m View File

63
 + (RNFetchBlobNetwork* _Nullable)sharedInstance {
63
 + (RNFetchBlobNetwork* _Nullable)sharedInstance {
64
     static id _sharedInstance = nil;
64
     static id _sharedInstance = nil;
65
     static dispatch_once_t onceToken;
65
     static dispatch_once_t onceToken;
66
-
66
+    
67
     dispatch_once(&onceToken, ^{
67
     dispatch_once(&onceToken, ^{
68
         _sharedInstance = [[self alloc] init];
68
         _sharedInstance = [[self alloc] init];
69
     });
69
     });
135
 
135
 
136
 - (void) cancelRequest:(NSString *)taskId
136
 - (void) cancelRequest:(NSString *)taskId
137
 {
137
 {
138
-    NSURLSessionDataTask * task;
139
-    
140
     @synchronized ([RNFetchBlobNetwork class]) {
138
     @synchronized ([RNFetchBlobNetwork class]) {
141
-        task = [self.requestsTable objectForKey:taskId].task;
142
-    }
143
-    
144
-    if (task && task.state == NSURLSessionTaskStateRunning) {
145
-        [task cancel];
139
+        [[self.requestsTable objectForKey:taskId] cancelRequest:taskId];
146
     }
140
     }
147
 }
141
 }
148
 
142
 

+ 4
- 1
ios/RNFetchBlobRequest.h View File

32
 @property (nullable, nonatomic) NSError * error;
32
 @property (nullable, nonatomic) NSError * error;
33
 @property (nullable, nonatomic) RNFetchBlobProgress *progressConfig;
33
 @property (nullable, nonatomic) RNFetchBlobProgress *progressConfig;
34
 @property (nullable, nonatomic) RNFetchBlobProgress *uploadProgressConfig;
34
 @property (nullable, nonatomic) RNFetchBlobProgress *uploadProgressConfig;
35
-@property (nullable, nonatomic, weak) NSURLSessionDataTask *task;
35
+//@property (nullable, nonatomic, weak) NSURLSessionDataTask *task;
36
+@property (nonatomic, strong) __block NSURLSession * session;
36
 
37
 
37
 - (void) sendRequest:(NSDictionary  * _Nullable )options
38
 - (void) sendRequest:(NSDictionary  * _Nullable )options
38
        contentLength:(long)contentLength
39
        contentLength:(long)contentLength
42
   taskOperationQueue:(NSOperationQueue * _Nonnull)operationQueue
43
   taskOperationQueue:(NSOperationQueue * _Nonnull)operationQueue
43
             callback:(_Nullable RCTResponseSenderBlock) callback;
44
             callback:(_Nullable RCTResponseSenderBlock) callback;
44
 
45
 
46
+- (void) cancelRequest:(NSString *)taskId;
47
+
45
 @end
48
 @end
46
 
49
 
47
 #endif /* RNFetchBlobRequest_h */
50
 #endif /* RNFetchBlobRequest_h */

+ 74
- 50
ios/RNFetchBlobRequest.m View File

11
 #import "RNFetchBlobFS.h"
11
 #import "RNFetchBlobFS.h"
12
 #import "RNFetchBlobConst.h"
12
 #import "RNFetchBlobConst.h"
13
 #import "RNFetchBlobReqBuilder.h"
13
 #import "RNFetchBlobReqBuilder.h"
14
+#if __has_include(<React/RCTLog.h>)
15
+#import <React/RCTLog.h>
16
+#else
17
+#import "RCTLog.h"
18
+#endif
14
 
19
 
15
 #import "IOS7Polyfill.h"
20
 #import "IOS7Polyfill.h"
16
 #import <CommonCrypto/CommonDigest.h>
21
 #import <CommonCrypto/CommonDigest.h>
17
 
22
 
23
+NSMapTable * taskTable;
24
+
25
+__attribute__((constructor))
26
+static void initialize_tables() {
27
+    if(taskTable == nil)
28
+    {
29
+        taskTable = [[NSMapTable alloc] init];
30
+    }
31
+}
18
 
32
 
19
 typedef NS_ENUM(NSUInteger, ResponseFormat) {
33
 typedef NS_ENUM(NSUInteger, ResponseFormat) {
20
     UTF8,
34
     UTF8,
36
     ResponseFormat responseFormat;
50
     ResponseFormat responseFormat;
37
     BOOL followRedirect;
51
     BOOL followRedirect;
38
     BOOL backgroundTask;
52
     BOOL backgroundTask;
53
+    BOOL uploadTask;
39
 }
54
 }
40
 
55
 
41
 @end
56
 @end
82
     self.options = options;
97
     self.options = options;
83
     
98
     
84
     backgroundTask = [[options valueForKey:@"IOSBackgroundTask"] boolValue];
99
     backgroundTask = [[options valueForKey:@"IOSBackgroundTask"] boolValue];
100
+    uploadTask = [options valueForKey:@"IOSUploadTask"] == nil ? NO : [[options valueForKey:@"IOSUploadTask"] boolValue];
101
+    
102
+    NSString * filepath = [options valueForKey:@"uploadFilePath"];
103
+
104
+    if (uploadTask && ![[NSFileManager defaultManager] fileExistsAtPath:[NSURL URLWithString:filepath].path]) {
105
+        RCTLog(@"[RNFetchBlobRequest] sendRequest uploadTask file doesn't exist %@", filepath);
106
+        callback(@[@"uploadTask file doesn't exist", @"", [NSNull null]]);
107
+        return;
108
+    }
109
+    
85
     // when followRedirect not set in options, defaults to TRUE
110
     // when followRedirect not set in options, defaults to TRUE
86
     followRedirect = [options valueForKey:@"followRedirect"] == nil ? YES : [[options valueForKey:@"followRedirect"] boolValue];
111
     followRedirect = [options valueForKey:@"followRedirect"] == nil ? YES : [[options valueForKey:@"followRedirect"] boolValue];
87
     isIncrement = [[options valueForKey:@"increment"] boolValue];
112
     isIncrement = [[options valueForKey:@"increment"] boolValue];
104
     
129
     
105
     NSString * path = [self.options valueForKey:CONFIG_FILE_PATH];
130
     NSString * path = [self.options valueForKey:CONFIG_FILE_PATH];
106
     NSString * key = [self.options valueForKey:CONFIG_KEY];
131
     NSString * key = [self.options valueForKey:CONFIG_KEY];
107
-    NSURLSession * session;
108
     
132
     
109
     bodyLength = contentLength;
133
     bodyLength = contentLength;
110
     
134
     
117
         defaultConfigObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:taskId];
141
         defaultConfigObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:taskId];
118
     }
142
     }
119
     
143
     
144
+    
120
     // request timeout, -1 if not set in options
145
     // request timeout, -1 if not set in options
121
     float timeout = [options valueForKey:@"timeout"] == nil ? -1 : [[options valueForKey:@"timeout"] floatValue];
146
     float timeout = [options valueForKey:@"timeout"] == nil ? -1 : [[options valueForKey:@"timeout"] floatValue];
122
     
147
     
125
     }
150
     }
126
     
151
     
127
     defaultConfigObject.HTTPMaximumConnectionsPerHost = 10;
152
     defaultConfigObject.HTTPMaximumConnectionsPerHost = 10;
128
-    session = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue:operationQueue];
153
+    _session = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue:operationQueue];
129
     
154
     
130
     if (path || [self.options valueForKey:CONFIG_USE_TEMP]) {
155
     if (path || [self.options valueForKey:CONFIG_USE_TEMP]) {
131
         respFile = YES;
156
         respFile = YES;
157
         respFile = NO;
182
         respFile = NO;
158
     }
183
     }
159
     
184
     
160
-    self.task = [session dataTaskWithRequest:req];
161
-    [self.task resume];
185
+    __block NSURLSessionTask * task;
186
+    
187
+    if(uploadTask)
188
+    {
189
+        task = [_session uploadTaskWithRequest:req fromFile:[NSURL URLWithString:filepath]];
190
+    }
191
+    else
192
+    {
193
+        task = [_session dataTaskWithRequest:req];
194
+    }
195
+    
196
+    [taskTable setObject:task forKey:taskId];
197
+    [task resume];
162
     
198
     
163
     // network status indicator
199
     // network status indicator
164
     if ([[options objectForKey:CONFIG_INDICATOR] boolValue]) {
200
     if ([[options objectForKey:CONFIG_INDICATOR] boolValue]) {
182
 // set expected content length on response received
218
 // set expected content length on response received
183
 - (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
219
 - (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
184
 {
220
 {
221
+    NSLog(@"sess didReceiveResponse");
185
     expectedBytes = [response expectedContentLength];
222
     expectedBytes = [response expectedContentLength];
186
     
223
     
187
     NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
224
     NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
207
             
244
             
208
             partBuffer = [[NSMutableData alloc] init];
245
             partBuffer = [[NSMutableData alloc] init];
209
             completionHandler(NSURLSessionResponseAllow);
246
             completionHandler(NSURLSessionResponseAllow);
210
-
247
+            
211
             return;
248
             return;
212
         } else {
249
         } else {
213
             self.isServerPush = [[respCType lowercaseString] RNFBContainsString:@"multipart/x-mixed-replace;"];
250
             self.isServerPush = [[respCType lowercaseString] RNFBContainsString:@"multipart/x-mixed-replace;"];
269
         NSLog(@"oops");
306
         NSLog(@"oops");
270
     }
307
     }
271
     
308
     
272
-    if (respFile)
273
-    {
274
-        @try{
275
-            NSFileManager * fm = [NSFileManager defaultManager];
276
-            NSString * folder = [destPath stringByDeletingLastPathComponent];
277
-            
278
-            if (![fm fileExistsAtPath:folder]) {
279
-                [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:nil];
280
-            }
281
-            
282
-            // if not set overwrite in options, defaults to TRUE
283
-            BOOL overwrite = [options valueForKey:@"overwrite"] == nil ? YES : [[options valueForKey:@"overwrite"] boolValue];
284
-            BOOL appendToExistingFile = [destPath RNFBContainsString:@"?append=true"];
285
-            
286
-            appendToExistingFile = !overwrite;
287
-            
288
-            // For solving #141 append response data if the file already exists
289
-            // base on PR#139 @kejinliang
290
-            if (appendToExistingFile) {
291
-                destPath = [destPath stringByReplacingOccurrencesOfString:@"?append=true" withString:@""];
292
-            }
293
-            
294
-            if (![fm fileExistsAtPath:destPath]) {
295
-                [fm createFileAtPath:destPath contents:[[NSData alloc] init] attributes:nil];
296
-            }
297
-            
298
-            writeStream = [[NSOutputStream alloc] initToFileAtPath:destPath append:appendToExistingFile];
299
-            [writeStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
300
-            [writeStream open];
301
-        }
302
-        @catch(NSException * ex)
303
-        {
304
-            NSLog(@"write file error");
305
-        }
306
-    }
307
-    
308
     completionHandler(NSURLSessionResponseAllow);
309
     completionHandler(NSURLSessionResponseAllow);
309
 }
310
 }
310
 
311
 
328
         chunkString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
329
         chunkString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
329
     }
330
     }
330
     
331
     
331
-    if (respFile) {
332
-        [writeStream write:[data bytes] maxLength:[data length]];
333
-    } else {
334
-        [respData appendData:data];
335
-    }
332
+    [respData appendData:data];
336
     
333
     
337
     if (expectedBytes == 0) {
334
     if (expectedBytes == 0) {
338
         return;
335
         return;
353
     }
350
     }
354
 }
351
 }
355
 
352
 
353
+- (void) cancelRequest:(NSString *)taskId
354
+{
355
+    NSURLSessionDataTask * task = [taskTable objectForKey:taskId];
356
+    if(task != nil && task.state == NSURLSessionTaskStateRunning)
357
+        [task cancel];
358
+}
359
+
356
 - (void) URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error
360
 - (void) URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error
357
 {
361
 {
362
+    RCTLog(@"[RNFetchBlobRequest] session didBecomeInvalidWithError %@", [error description]);
358
     if ([session isEqual:session]) {
363
     if ([session isEqual:session]) {
359
         session = nil;
364
         session = nil;
360
     }
365
     }
363
 
368
 
364
 - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
369
 - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
365
 {
370
 {
366
-    
371
+    RCTLog(@"[RNFetchBlobRequest] session didCompleteWithError %@", [error description]);
367
     self.error = error;
372
     self.error = error;
368
     NSString * errMsg;
373
     NSString * errMsg;
369
     NSString * respStr;
374
     NSString * respStr;
416
                respStr ?: [NSNull null]
421
                respStr ?: [NSNull null]
417
                ]);
422
                ]);
418
     
423
     
424
+    @synchronized(taskTable)
425
+    {
426
+        if([taskTable objectForKey:taskId] == nil)
427
+            NSLog(@"object released by ARC.");
428
+        else
429
+            [taskTable removeObjectForKey:taskId];
430
+    }
431
+    
419
     respData = nil;
432
     respData = nil;
420
     receivedBytes = 0;
433
     receivedBytes = 0;
421
     [session finishTasksAndInvalidate];
434
     [session finishTasksAndInvalidate];
422
-    
423
 }
435
 }
424
 
436
 
425
 // upload progress handler
437
 // upload progress handler
430
     }
442
     }
431
     
443
     
432
     NSNumber * now = [NSNumber numberWithFloat:((float)totalBytesWritten/(float)totalBytesExpectedToWrite)];
444
     NSNumber * now = [NSNumber numberWithFloat:((float)totalBytesWritten/(float)totalBytesExpectedToWrite)];
433
-
445
+    
434
     if ([self.uploadProgressConfig shouldReport:now]) {
446
     if ([self.uploadProgressConfig shouldReport:now]) {
435
         [self.bridge.eventDispatcher
447
         [self.bridge.eventDispatcher
436
          sendDeviceEventWithName:EVENT_PROGRESS_UPLOAD
448
          sendDeviceEventWithName:EVENT_PROGRESS_UPLOAD
456
 
468
 
457
 - (void) URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
469
 - (void) URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
458
 {
470
 {
459
-    NSLog(@"sess done in background");
471
+    RCTLog(@"[RNFetchBlobRequest] session done in background");
472
+    dispatch_async(dispatch_get_main_queue(), ^{
473
+        id<UIApplicationDelegate> appDelegate = [UIApplication sharedApplication].delegate;
474
+        SEL selector = NSSelectorFromString(@"backgroundTransferCompletionHandler");
475
+        if ([appDelegate respondsToSelector:selector]) {
476
+            void(^completionHandler)() = [appDelegate performSelector:selector];
477
+            if (completionHandler != nil) {
478
+                completionHandler();
479
+                completionHandler = nil;
480
+            }
481
+        }
482
+        
483
+    });
460
 }
484
 }
461
 
485
 
462
 - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler
486
 - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler