Browse Source

Merge pull request #198 from capriza/master

Adding iOS background upload support
Travis Nuttall 5 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,6 +29,7 @@ A project committed to making file access and data transfer easier and more effi
29 29
  * [Multipart/form upload](#user-content-multipartform-data-example--post-form-data-with-file-and-data)
30 30
  * [Upload/Download progress](#user-content-uploaddownload-progress)
31 31
  * [Cancel HTTP request](#user-content-cancel-request)
32
+ * [iOS Background Uploading](#user-content-ios-background-uploading)
32 33
  * [Android Media Scanner, and Download Manager Support](#user-content-android-media-scanner-and-download-manager-support)
33 34
  * [Self-Signed SSL Server](#user-content-self-signed-ssl-server)
34 35
  * [Transfer Encoding](#user-content-transfer-encoding)
@@ -475,6 +476,34 @@ If you have existing code that uses `whatwg-fetch`(the official **fetch**), it's
475 476
 
476 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 507
 ### Android Media Scanner, and Download Manager Support
479 508
 
480 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,7 +63,7 @@ static void initialize_tables() {
63 63
 + (RNFetchBlobNetwork* _Nullable)sharedInstance {
64 64
     static id _sharedInstance = nil;
65 65
     static dispatch_once_t onceToken;
66
-
66
+    
67 67
     dispatch_once(&onceToken, ^{
68 68
         _sharedInstance = [[self alloc] init];
69 69
     });
@@ -135,14 +135,8 @@ static void initialize_tables() {
135 135
 
136 136
 - (void) cancelRequest:(NSString *)taskId
137 137
 {
138
-    NSURLSessionDataTask * task;
139
-    
140 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,7 +32,8 @@
32 32
 @property (nullable, nonatomic) NSError * error;
33 33
 @property (nullable, nonatomic) RNFetchBlobProgress *progressConfig;
34 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 38
 - (void) sendRequest:(NSDictionary  * _Nullable )options
38 39
        contentLength:(long)contentLength
@@ -42,6 +43,8 @@
42 43
   taskOperationQueue:(NSOperationQueue * _Nonnull)operationQueue
43 44
             callback:(_Nullable RCTResponseSenderBlock) callback;
44 45
 
46
+- (void) cancelRequest:(NSString *)taskId;
47
+
45 48
 @end
46 49
 
47 50
 #endif /* RNFetchBlobRequest_h */

+ 74
- 50
ios/RNFetchBlobRequest.m View File

@@ -11,10 +11,24 @@
11 11
 #import "RNFetchBlobFS.h"
12 12
 #import "RNFetchBlobConst.h"
13 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 20
 #import "IOS7Polyfill.h"
16 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 33
 typedef NS_ENUM(NSUInteger, ResponseFormat) {
20 34
     UTF8,
@@ -36,6 +50,7 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
36 50
     ResponseFormat responseFormat;
37 51
     BOOL followRedirect;
38 52
     BOOL backgroundTask;
53
+    BOOL uploadTask;
39 54
 }
40 55
 
41 56
 @end
@@ -82,6 +97,16 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
82 97
     self.options = options;
83 98
     
84 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 110
     // when followRedirect not set in options, defaults to TRUE
86 111
     followRedirect = [options valueForKey:@"followRedirect"] == nil ? YES : [[options valueForKey:@"followRedirect"] boolValue];
87 112
     isIncrement = [[options valueForKey:@"increment"] boolValue];
@@ -104,7 +129,6 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
104 129
     
105 130
     NSString * path = [self.options valueForKey:CONFIG_FILE_PATH];
106 131
     NSString * key = [self.options valueForKey:CONFIG_KEY];
107
-    NSURLSession * session;
108 132
     
109 133
     bodyLength = contentLength;
110 134
     
@@ -117,6 +141,7 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
117 141
         defaultConfigObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:taskId];
118 142
     }
119 143
     
144
+    
120 145
     // request timeout, -1 if not set in options
121 146
     float timeout = [options valueForKey:@"timeout"] == nil ? -1 : [[options valueForKey:@"timeout"] floatValue];
122 147
     
@@ -125,7 +150,7 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
125 150
     }
126 151
     
127 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 155
     if (path || [self.options valueForKey:CONFIG_USE_TEMP]) {
131 156
         respFile = YES;
@@ -157,8 +182,19 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
157 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 199
     // network status indicator
164 200
     if ([[options objectForKey:CONFIG_INDICATOR] boolValue]) {
@@ -182,6 +218,7 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
182 218
 // set expected content length on response received
183 219
 - (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
184 220
 {
221
+    NSLog(@"sess didReceiveResponse");
185 222
     expectedBytes = [response expectedContentLength];
186 223
     
187 224
     NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
@@ -207,7 +244,7 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
207 244
             
208 245
             partBuffer = [[NSMutableData alloc] init];
209 246
             completionHandler(NSURLSessionResponseAllow);
210
-
247
+            
211 248
             return;
212 249
         } else {
213 250
             self.isServerPush = [[respCType lowercaseString] RNFBContainsString:@"multipart/x-mixed-replace;"];
@@ -269,42 +306,6 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
269 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 309
     completionHandler(NSURLSessionResponseAllow);
309 310
 }
310 311
 
@@ -328,11 +329,7 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
328 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 334
     if (expectedBytes == 0) {
338 335
         return;
@@ -353,8 +350,16 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
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 360
 - (void) URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error
357 361
 {
362
+    RCTLog(@"[RNFetchBlobRequest] session didBecomeInvalidWithError %@", [error description]);
358 363
     if ([session isEqual:session]) {
359 364
         session = nil;
360 365
     }
@@ -363,7 +368,7 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
363 368
 
364 369
 - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
365 370
 {
366
-    
371
+    RCTLog(@"[RNFetchBlobRequest] session didCompleteWithError %@", [error description]);
367 372
     self.error = error;
368 373
     NSString * errMsg;
369 374
     NSString * respStr;
@@ -416,10 +421,17 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
416 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 432
     respData = nil;
420 433
     receivedBytes = 0;
421 434
     [session finishTasksAndInvalidate];
422
-    
423 435
 }
424 436
 
425 437
 // upload progress handler
@@ -430,7 +442,7 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
430 442
     }
431 443
     
432 444
     NSNumber * now = [NSNumber numberWithFloat:((float)totalBytesWritten/(float)totalBytesExpectedToWrite)];
433
-
445
+    
434 446
     if ([self.uploadProgressConfig shouldReport:now]) {
435 447
         [self.bridge.eventDispatcher
436 448
          sendDeviceEventWithName:EVENT_PROGRESS_UPLOAD
@@ -456,7 +468,19 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
456 468
 
457 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 486
 - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler