Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multipart file upload for iOS #329

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 74 additions & 3 deletions ios/VydiaRNFileUploader.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
@interface VydiaRNFileUploader : RCTEventEmitter <RCTBridgeModule, NSURLSessionTaskDelegate>
{
NSMutableDictionary *_responsesData;
NSMutableDictionary<NSString*, NSURL*> *_filesMap;
}
@end

Expand All @@ -29,6 +30,7 @@ -(id) init {
if (self) {
staticEventEmitter = self;
_responsesData = [NSMutableDictionary dictionary];
_filesMap = @{}.mutableCopy;
}
return self;
}
Expand Down Expand Up @@ -132,6 +134,58 @@ - (void)copyAssetToFile: (NSString *)assetUrl completionHandler: (void(^)(NSStri
}];
}

- (NSURL *)saveMultipartUploadDataToDisk:(NSString *)uploadId data:(NSData *)data {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cacheDirectory = [paths objectAtIndex:0];
NSString *path = [NSString stringWithFormat:@"%@.multipart", uploadId];

NSString *uploaderDirectory = [cacheDirectory stringByAppendingPathComponent:@"/uploader"];

NSString *filePath = [uploaderDirectory stringByAppendingPathComponent:path];
NSLog(@"Path to save: %@", filePath);

NSFileManager *manager = [NSFileManager defaultManager];
NSError *error;

//Remove file if needed
if ([manager fileExistsAtPath:filePath]) {
[manager removeItemAtPath:filePath error:&error];
if (error) {
NSLog(@"Cannot delete file at path: %@. Error: %@", filePath, error.localizedDescription);
return nil;
}
}
//Create directory if needed
if (![manager fileExistsAtPath:uploaderDirectory] && [manager createDirectoryAtPath:uploaderDirectory withIntermediateDirectories:NO attributes:nil error:&error]) {
NSLog(@"Cannot save data at path %@. Error: %@", filePath, error.localizedDescription);
return nil;
}
//Save NSData to file
if (![data writeToFile:filePath options:NSDataWritingAtomic error:&error]) {
NSLog(@"Cannot save data at path %@. Error: %@", filePath, error.localizedDescription);
return nil;
}

return [NSURL fileURLWithPath:filePath];
}

- (void)removeFilesForUpload:(NSString *)uploadId {
NSFileManager *manager = [NSFileManager defaultManager];
NSError *error;

NSURL *fileUrl = _filesMap[uploadId];

if (!fileUrl) {
return;
}

if (![manager removeItemAtURL:fileUrl error:&error]) {
NSLog(@"Cannot delete file at path %@. Error: %@", fileUrl.absoluteString, error.localizedDescription);
}

[_filesMap removeObjectForKey:uploadId];
}

/*
* Starts a file upload.
* Options are passed in as the first argument as a js hash:
Expand Down Expand Up @@ -196,7 +250,8 @@ - (void)copyAssetToFile: (NSString *)assetUrl completionHandler: (void(^)(NSStri
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
}

NSURLSessionDataTask *uploadTask;
NSURLSessionUploadTask *uploadTask;
NSString *taskDescription = customUploadId ? customUploadId : [NSString stringWithFormat:@"%i", thisUploadId];

if ([uploadType isEqualToString:@"multipart"]) {
NSString *uuidStr = [[NSUUID UUID] UUIDString];
Expand All @@ -206,7 +261,15 @@ - (void)copyAssetToFile: (NSString *)assetUrl completionHandler: (void(^)(NSStri
[request setHTTPBodyStream: [NSInputStream inputStreamWithData:httpBody]];
[request setValue:[NSString stringWithFormat:@"%zd", httpBody.length] forHTTPHeaderField:@"Content-Length"];

uploadTask = [[self urlSession: appGroup] uploadTaskWithStreamedRequest:request];
NSURL *fileUrlOnDisk = [self saveMultipartUploadDataToDisk:taskDescription data:httpBody];
if (fileUrlOnDisk) {
_filesMap[taskDescription] = fileUrlOnDisk;
uploadTask = [[self urlSession: appGroup] uploadTaskWithRequest:request fromFile:fileUrlOnDisk];
} else {
NSLog(@"Cannot save multipart data file to disk, Fallback to old method wtih stream");
[request setHTTPBodyStream: [NSInputStream inputStreamWithData:httpBody]];
uploadTask = [[self urlSession: appGroup] uploadTaskWithStreamedRequest:request];
}
} else {
if (parameters.count > 0) {
reject(@"RN Uploader", @"Parameters supported only in multipart type", nil);
Expand All @@ -216,7 +279,7 @@ - (void)copyAssetToFile: (NSString *)assetUrl completionHandler: (void(^)(NSStri
uploadTask = [[self urlSession: appGroup] uploadTaskWithRequest:request fromFile:[NSURL URLWithString: fileURI]];
}

uploadTask.taskDescription = customUploadId ? customUploadId : [NSString stringWithFormat:@"%i", thisUploadId];
uploadTask.taskDescription = taskDescription;

[uploadTask resume];
resolve(uploadTask.taskDescription);
Expand All @@ -232,11 +295,16 @@ - (void)copyAssetToFile: (NSString *)assetUrl completionHandler: (void(^)(NSStri
* Event "cancelled" will be fired when upload is cancelled.
*/
RCT_EXPORT_METHOD(cancelUpload: (NSString *)cancelUploadId resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
__weak typeof(self) weakSelf = self;

[_urlSession getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
__strong typeof(self) strongSelf = weakSelf;

for (NSURLSessionTask *uploadTask in uploadTasks) {
if ([uploadTask.taskDescription isEqualToString:cancelUploadId]){
// == checks if references are equal, while isEqualToString checks the string value
[uploadTask cancel];
[strongSelf removeFilesForUpload:cancelUploadId];
}
}
}];
Expand Down Expand Up @@ -311,12 +379,15 @@ - (void)URLSession:(NSURLSession *)session
NSMutableData *responseData = _responsesData[@(task.taskIdentifier)];
if (responseData) {
[_responsesData removeObjectForKey:@(task.taskIdentifier)];

NSString *response = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
[data setObject:response forKey:@"responseBody"];
} else {
[data setObject:[NSNull null] forKey:@"responseBody"];
}

[self removeFilesForUpload:task.taskDescription];

if (error == nil)
{
[self _sendEventWithName:@"RNFileUploader-completed" body:data];
Expand Down