From db0ab5ab728a2b307425c63ccab420b1ce07e8cc Mon Sep 17 00:00:00 2001 From: Brennan Stehling Date: Fri, 18 Mar 2022 14:55:46 -0700 Subject: [PATCH] feat(S3): adds suspend and resume features for multipart upload tasks #4167 --- AWSS3/AWSS3TransferUtility.h | 24 ++ AWSS3/AWSS3TransferUtility.m | 278 +++++++++++++++++---- AWSS3/AWSS3TransferUtilityDatabaseHelper.m | 3 + AWSS3/AWSS3TransferUtilityTasks.h | 14 ++ AWSS3/AWSS3TransferUtilityTasks.m | 25 +- AWSS3/AWSS3TransferUtility_private.h | 4 +- 6 files changed, 288 insertions(+), 60 deletions(-) diff --git a/AWSS3/AWSS3TransferUtility.h b/AWSS3/AWSS3TransferUtility.h index 177c6578353..58c8d93d3e2 100644 --- a/AWSS3/AWSS3TransferUtility.h +++ b/AWSS3/AWSS3TransferUtility.h @@ -620,6 +620,30 @@ handleEventsForBackgroundURLSession:(NSString *)identifier expression:(nullable AWSS3TransferUtilityDownloadExpression *)expression completionHandler:(nullable AWSS3TransferUtilityDownloadCompletionHandlerBlock)completionHandler; +/** + Suspends multipart upload + @param multipartUploadTask task + */ +- (nullable NSError *)suspendMultipartUpload:(AWSS3TransferUtilityMultiPartUploadTask *)multipartUploadTask; + +/** + Suspends all multipart uploads. + @param completionHandler completion handler + */ +- (void)suspendAllMultipartUploadsWithCompletionHandler:(nullable AWSS3TransferUtilityMultiPartUploadSuspendBlock)completionHandler; + +/** + Resumes a multipart upload. + @param multiPartUploadTask The task to resume + */ +- (nullable NSError *)resumeMultipartUpload:(nonnull AWSS3TransferUtilityMultiPartUploadTask *)multiPartUploadTask; + +/** + Resumes all multipart uploads. + @param completionHandler completion handler + */ +- (void)resumeAllMultipartUploadsWithCompletionHandler:(nullable AWSS3TransferUtilityMultiPartUploadResumeBlock)completionHandler; + /** Assigns progress feedback and completion handler blocks. This method should be called when the app was suspended while the transfer is still happening. diff --git a/AWSS3/AWSS3TransferUtility.m b/AWSS3/AWSS3TransferUtility.m index 812348597b2..e130cebfe25 100644 --- a/AWSS3/AWSS3TransferUtility.m +++ b/AWSS3/AWSS3TransferUtility.m @@ -321,8 +321,11 @@ - (instancetype)initWithConfiguration:(AWSServiceConfiguration *)serviceConfigur //Setup internal Data Structures - _taskDictionary = [AWSSynchronizedMutableDictionary new]; - _completedTaskDictionary = [AWSSynchronizedMutableDictionary new]; + AWSSynchronizedMutableDictionary *taskDictionary = [AWSSynchronizedMutableDictionary new]; + AWSSynchronizedMutableDictionary *completedTaskDictionary = taskDictionary.syncedDictionary; + + _taskDictionary = taskDictionary; + _completedTaskDictionary = completedTaskDictionary; //Instantiate the Database Helper self.databaseQueue = [AWSS3TransferUtilityDatabaseHelper createDatabase:self.cacheDirectoryPath]; @@ -566,11 +569,22 @@ - (void) markTransferAsCompleted:(AWSS3TransferUtilityTask *) transferUtilityTas AWSDDLogError(@"Task [%lu] has an error status [%@]. Marking transfer status as Error", (unsigned long)transferUtilityTask.taskIdentifier, taskError); transferUtilityTask.error = taskError; } - [self.completedTaskDictionary setObject:transferUtilityTask forKey:transferUtilityTask.transferID]; - [self.taskDictionary removeObjectForKey:@(transferUtilityTask.taskIdentifier)]; + + [self completeTransferUtilityTask:transferUtilityTask]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityTask.transferID databaseQueue:self->_databaseQueue]; } +- (void)completeTransferUtilityTask:(AWSS3TransferUtilityTask *)transferUtilityTask { + [AWSSynchronizedMutableDictionary mutateSyncedDictionaries:@[self.taskDictionary, self.completedTaskDictionary] withBlock:^(NSUUID * instanceKey, NSMutableDictionary *dictionary) { + if ([instanceKey isEqual:self.completedTaskDictionary.instanceKey]) { + [dictionary setObject:transferUtilityTask forKey:transferUtilityTask.transferID]; + } else if ([instanceKey isEqual:self.taskDictionary.instanceKey]) { + [dictionary removeObjectForKey:@(transferUtilityTask.taskIdentifier)]; + } + }]; +} + - (void) handleUnlinkedTransfers:(NSMutableDictionary *) tempMultiPartMasterTaskDictionary tempTransferDictionary: (NSMutableDictionary *) tempTransferDictionary { //At this point, we have finished iterating through the tasks present in the NSURLSession and removed all the matching ones from the transferRequests dictionary. @@ -974,7 +988,7 @@ - (NSURLSessionUploadTask *)getURLSessionUploadTaskWithRequest:(NSURLRequest *) } transferUtilityUploadTask.sessionTask = uploadTask; - if ( startTransfer) { + if (startTransfer) { transferUtilityUploadTask.status = AWSS3TransferUtilityTransferStatusInProgress; } else { @@ -1118,7 +1132,7 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa completionHandler:(AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock) completionHandler { //Validate input parameters. - AWSTask *error = [self validateParameters:bucket key:key fileURL:fileURL accelerationModeEnabled:self.transferUtilityConfiguration.isAccelerateModeEnabled]; + AWSTask *error = [self validateParameters:bucket key:key fileURL:fileURL accelerationModeEnabled:self.transferUtilityConfiguration.isAccelerateModeEnabled]; if (error) { if (temporaryFileCreated) { [self removeFile:[fileURL path]]; @@ -1230,19 +1244,16 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa NSError *subTaskCreationError; //Move to inProgress or Waiting based on concurrency limit - if (i <= [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { - subTaskCreationError = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:NO internalDictionaryToAddSubTaskTo:transferUtilityMultiPartUploadTask.inProgressPartsDictionary]; - if(!subTaskCreationError) { - subTask.status = AWSS3TransferUtilityTransferStatusInProgress; - AWSDDLogDebug(@"Added task for part [%@] to inProgress list", subTask.partNumber); - } - } - else { - subTaskCreationError = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:NO internalDictionaryToAddSubTaskTo:transferUtilityMultiPartUploadTask.waitingPartsDictionary]; - if (!subTaskCreationError ) { - subTask.status = AWSS3TransferUtilityTransferStatusWaiting; - AWSDDLogDebug(@"Added task for part [%@] to Waiting list", subTask.partNumber); - } + BOOL isUnderConcurrencyLimit = i <= [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]; + subTaskCreationError = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:NO + internalDictionaryToAddSubTaskTo: isUnderConcurrencyLimit ? + transferUtilityMultiPartUploadTask.inProgressPartsDictionary : + transferUtilityMultiPartUploadTask.waitingPartsDictionary]; + if (!subTaskCreationError) { + subTask.status = isUnderConcurrencyLimit ? AWSS3TransferUtilityTransferStatusInProgress : + AWSS3TransferUtilityTransferStatusWaiting; + AWSDDLogDebug(@"Added task for part [%@] to %@ list", subTask.partNumber, isUnderConcurrencyLimit ? + @"inProgress" : @"Waiting"); } if (!subTaskCreationError) { @@ -1263,7 +1274,7 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa } //Start the subTasks - for(id taskIdentifier in transferUtilityMultiPartUploadTask.inProgressPartsDictionary) { + for (id taskIdentifier in transferUtilityMultiPartUploadTask.inProgressPartsDictionary) { AWSS3TransferUtilityUploadSubTask *subTask = [transferUtilityMultiPartUploadTask.inProgressPartsDictionary objectForKey:taskIdentifier]; AWSDDLogDebug(@"Starting subTask %@", @(subTask.taskIdentifier)); [subTask.sessionTask resume]; @@ -1507,6 +1518,39 @@ -(NSError *) createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *) tra return error; } + +- (void)completeMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)transferUtilityMultiPartUploadTask { + //Call the Multipart completion step here. + AWSTask * finishTask = [self callFinishMultiPartForUploadTask:transferUtilityMultiPartUploadTask]; + [finishTask continueWithBlock:^id (AWSTask *task) { + if (task.error) { + AWSDDLogError(@"Error finishing up MultiPartForUpload Task[%@]", task.error); + transferUtilityMultiPartUploadTask.error = task.error; + transferUtilityMultiPartUploadTask.status = AWSS3TransferUtilityTransferStatusError; + + //Abort the request, so the server can clean up any partials. + [self callAbortMultiPartForUploadTask:transferUtilityMultiPartUploadTask]; + } + else { + //Set progress to 100% and call progressBlock. + AWSDDLogInfo(@"Completed Multipart Transfer: %@", transferUtilityMultiPartUploadTask.uploadID); + transferUtilityMultiPartUploadTask.status = AWSS3TransferUtilityTransferStatusCompleted; + + transferUtilityMultiPartUploadTask.progress.completedUnitCount = transferUtilityMultiPartUploadTask.progress.totalUnitCount; + if (transferUtilityMultiPartUploadTask.expression.progressBlock ) { + transferUtilityMultiPartUploadTask.expression.progressBlock(transferUtilityMultiPartUploadTask, transferUtilityMultiPartUploadTask.progress); + } + } + + [self cleanupForMultiPartUploadTask:transferUtilityMultiPartUploadTask]; + + //Call the callback function is specified. + [self completeTask:transferUtilityMultiPartUploadTask]; + return nil; + }]; + +} + -(void) retryUploadSubTask: (AWSS3TransferUtilityMultiPartUploadTask *) transferUtilityMultiPartUploadTask subTask: (AWSS3TransferUtilityUploadSubTask *) subTask startTransfer: (BOOL) startTransfer { @@ -1532,14 +1576,14 @@ -(void) retryUploadSubTask: (AWSS3TransferUtilityMultiPartUploadTask *) transfer NSError *subTaskCreationError; - if (inWaitingPartsDictionary ) { + if (inWaitingPartsDictionary) { subTaskCreationError = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:startTransfer internalDictionaryToAddSubTaskTo:transferUtilityMultiPartUploadTask.waitingPartsDictionary]; } else { subTaskCreationError = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:startTransfer internalDictionaryToAddSubTaskTo:transferUtilityMultiPartUploadTask.inProgressPartsDictionary]; } - if ( subTaskCreationError ) { + if (subTaskCreationError) { //cancel the multipart transfer [transferUtilityMultiPartUploadTask cancel]; transferUtilityMultiPartUploadTask.status = AWSS3TransferUtilityTransferStatusError; @@ -1877,10 +1921,157 @@ - (NSMutableArray *) getTasksHelper:(AWSSynchronizedMutableDictionary *)dictiona return tasks; } +#pragma mark - Suspend and Resume + +- (nullable NSError *)suspendMultipartUpload:(AWSS3TransferUtilityMultiPartUploadTask *)multipartUploadTask { + // uses NSNumber * for taskIdentifier to set values in inProgressPartsDictionary + // these tasks need to be canceled and cleared out and replaced with tasks which have + // not been started yet so that completed uploads of parts do not cause pending part + // uploads to be started. + + [multipartUploadTask suspend]; + + for (NSNumber *taskIdentifier in multipartUploadTask.inProgressPartsDictionary.allKeys) { + AWSS3TransferUtilityUploadSubTask *inProgressSubTask = multipartUploadTask.inProgressPartsDictionary[taskIdentifier]; + NSCAssert(inProgressSubTask != nil, @"Task is required"); + + // cancel the URLSessionTask + [inProgressSubTask.sessionTask cancel]; + + AWSS3TransferUtilityUploadSubTask *subTask = [AWSS3TransferUtilityUploadSubTask new]; + subTask.transferID = multipartUploadTask.transferID; + subTask.partNumber = inProgressSubTask.partNumber; + subTask.transferType = @"MULTI_PART_UPLOAD_SUB_TASK"; + subTask.totalBytesExpectedToSend = inProgressSubTask.totalBytesExpectedToSend; + subTask.totalBytesSent = (long long) 0; + subTask.responseData = @""; + subTask.file = @""; + subTask.eTag = @""; + subTask.status = AWSS3TransferUtilityTransferStatusPaused; + + NSError *error = [self createUploadSubTask:multipartUploadTask + subTask:subTask + startTransfer:NO + internalDictionaryToAddSubTaskTo:multipartUploadTask.waitingPartsDictionary]; + + if (error) { + AWSDDLogError(@"Error creating AWSS3TransferUtilityUploadSubTask [%@]", error); + return error; + } else { + multipartUploadTask.inProgressPartsDictionary[taskIdentifier] = nil; + + [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:inProgressSubTask.transferID partNumber:inProgressSubTask.partNumber taskIdentifier:inProgressSubTask.taskIdentifier eTag:nil status:AWSS3TransferUtilityTransferStatusCancelled retry_count:0 databaseQueue:self.databaseQueue]; + + [AWSS3TransferUtilityDatabaseHelper insertMultiPartUploadRequestSubTaskInDB:multipartUploadTask subTask:subTask databaseQueue:self.databaseQueue]; + } + } + + return nil; +} + +/// Suspends all active tasks. Downloads will be +- (void)suspendAllMultipartUploadsWithCompletionHandler:(nullable AWSS3TransferUtilityMultiPartUploadSuspendBlock)completionHandler { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [[self getMultiPartUploadTasks] continueWithBlock:^id (AWSTask *> * _Nonnull task) { + if (task.error) { + AWSDDLogError(@"Error getting MultiPartForUpload Tasks [%@]", task.error); + completionHandler(task.error); + return nil; + } else { + AWSDDLogInfo(@"Suspending MultiPartForUpload Tasks: %lu", task.result.count); + NSError *error = nil; + NSArray * tasks = task.result; + for (AWSS3TransferUtilityMultiPartUploadTask *multipartUploadTask in tasks) { + NSCAssert(multipartUploadTask != nil, @"Task cannot be nil"); + error = [self suspendMultipartUpload:multipartUploadTask]; + if (error) { + if (completionHandler) { + completionHandler(error); + } + return nil; + } + } + } + + if (completionHandler) { + completionHandler(nil); + } + + return nil; + }]; + }); +} + +- (nullable NSError *)resumeMultipartUpload:(AWSS3TransferUtilityMultiPartUploadTask *)multiPartUploadTask { + [multiPartUploadTask resume]; + + // move parts from waiting to in progress if under the concurrency limit + if (multiPartUploadTask.inProgressPartsDictionary.count < [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { + long numberOfPartsInProgress = [multiPartUploadTask.inProgressPartsDictionary count]; + while (numberOfPartsInProgress < [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { + if ([multiPartUploadTask.waitingPartsDictionary count] > 0) { + //Get a part from the waitingList + AWSS3TransferUtilityUploadSubTask *nextSubTask = [[multiPartUploadTask.waitingPartsDictionary allValues] objectAtIndex:0]; + + //Add to inProgress list + [multiPartUploadTask.inProgressPartsDictionary setObject:nextSubTask forKey:@(nextSubTask.taskIdentifier)]; + + //Remove it from the waitingList + [multiPartUploadTask.waitingPartsDictionary removeObjectForKey:@(nextSubTask.taskIdentifier)]; + AWSDDLogDebug(@"Moving Task[%@] to progress for Multipart[%@]", @(nextSubTask.taskIdentifier), multiPartUploadTask.uploadID); + [nextSubTask.sessionTask resume]; + nextSubTask.status = AWSS3TransferUtilityTransferStatusInProgress; + numberOfPartsInProgress++; + continue; + } + break; + } + } + + // Change status from paused to waiting + for (AWSS3TransferUtilityUploadSubTask * nextSubTask in multiPartUploadTask.waitingPartsDictionary.allValues) { + nextSubTask.status = AWSS3TransferUtilityTransferStatusWaiting; + } + + // Complete multipart upload if in progress and waiting tasks are done + if (multiPartUploadTask.isDone) { + AWSDDLogDebug(@"There are %lu waiting upload parts.", (unsigned long)multiPartUploadTask.waitingPartsDictionary.count); + AWSDDLogDebug(@"There are %lu in progress upload parts.", (unsigned long)multiPartUploadTask.inProgressPartsDictionary.count); + AWSDDLogDebug(@"There are %lu completed upload parts.", (unsigned long)multiPartUploadTask.completedPartsSet.count); + [self completeMultiPartForUploadTask:multiPartUploadTask]; + } + + return nil; +} + +- (void)resumeAllMultipartUploadsWithCompletionHandler:(nullable AWSS3TransferUtilityMultiPartUploadResumeBlock)completionHandler { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [[self getMultiPartUploadTasks] continueWithBlock:^id (AWSTask *> * _Nonnull task) { + if (task.error) { + AWSDDLogError(@"Error getting MultiPartForUpload Tasks [%@]", task.error); + completionHandler(task.error); + return nil; + } else { + AWSDDLogInfo(@"Resuming MultiPartForUpload Tasks: %lu", task.result.count); + NSArray * tasks = task.result; + for (AWSS3TransferUtilityMultiPartUploadTask *multipartUploadTask in tasks) { + NSCAssert(multipartUploadTask != nil, @"Task cannot be nil"); + [self resumeMultipartUpload:multipartUploadTask]; + } + } + + if (completionHandler) { + completionHandler(nil); + } + + return nil; + }]; + }); +} + #pragma mark - Internal helper methods - (AWSTask *)callFinishMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)uploadTask { - NSMutableArray *completedParts = [NSMutableArray arrayWithCapacity:[uploadTask.completedPartsSet count]]; NSMutableDictionary *tempDictionary = [NSMutableDictionary new]; @@ -2103,6 +2294,11 @@ - (void)URLSession:(NSURLSession *)session AWSDDLogDebug(@"Unable to find information for task %lu in inProgress Dictionary", (unsigned long)task.taskIdentifier); return; } + + if (subTask.status == AWSS3TransferUtilityTransferStatusPaused) { + AWSDDLogDebug(@"Subtask is paused: %lu", (unsigned long)task.taskIdentifier); + return; + } //Check if the task was cancelled. if (transferUtilityMultiPartUploadTask.cancelled) { @@ -2140,7 +2336,7 @@ - (void)URLSession:(NSURLSession *)session //Error is not retriable. transferUtilityMultiPartUploadTask.error = updatedError; transferUtilityMultiPartUploadTask.status = AWSS3TransferUtilityTransferStatusError; - + //Execute call back if provided. [self completeTask:transferUtilityMultiPartUploadTask]; @@ -2207,7 +2403,7 @@ - (void)URLSession:(NSURLSession *)session } else if ([transferUtilityMultiPartUploadTask.inProgressPartsDictionary count] == 0) { //If there are no more inProgress parts, then we are done. - + //Validate that all the content has been uploaded. int64_t totalBytesSent = 0; for (AWSS3TransferUtilityUploadSubTask *aSubTask in transferUtilityMultiPartUploadTask.completedPartsSet) { @@ -2235,36 +2431,8 @@ - (void)URLSession:(NSURLSession *)session [self cleanupForMultiPartUploadTask:transferUtilityMultiPartUploadTask]; return; } - - - //Call the Multipart completion step here. - [[ self callFinishMultiPartForUploadTask:transferUtilityMultiPartUploadTask] continueWithBlock:^id (AWSTask *task) { - if (task.error) { - AWSDDLogError(@"Error finishing up MultiPartForUpload Task[%@]", task.error); - transferUtilityMultiPartUploadTask.error = error; - transferUtilityMultiPartUploadTask.status = AWSS3TransferUtilityTransferStatusError; - - //Abort the request, so the server can clean up any partials. - [self callAbortMultiPartForUploadTask:transferUtilityMultiPartUploadTask]; - - } - else { - //Set progress to 100% and call progressBlock. - AWSDDLogInfo(@"Completed Multipart Transfer: %@", transferUtilityMultiPartUploadTask.uploadID); - transferUtilityMultiPartUploadTask.status = AWSS3TransferUtilityTransferStatusCompleted; - - transferUtilityMultiPartUploadTask.progress.completedUnitCount = transferUtilityMultiPartUploadTask.progress.totalUnitCount; - if (transferUtilityMultiPartUploadTask.expression.progressBlock ) { - transferUtilityMultiPartUploadTask.expression.progressBlock(transferUtilityMultiPartUploadTask, transferUtilityMultiPartUploadTask.progress); - } - } - - [self cleanupForMultiPartUploadTask:transferUtilityMultiPartUploadTask]; - - //Call the callback function is specified. - [self completeTask:transferUtilityMultiPartUploadTask]; - return nil; - }]; + + [self completeMultiPartForUploadTask:transferUtilityMultiPartUploadTask]; } } } diff --git a/AWSS3/AWSS3TransferUtilityDatabaseHelper.m b/AWSS3/AWSS3TransferUtilityDatabaseHelper.m index 76e80a64226..fcdc27aa954 100644 --- a/AWSS3/AWSS3TransferUtilityDatabaseHelper.m +++ b/AWSS3/AWSS3TransferUtilityDatabaseHelper.m @@ -127,6 +127,9 @@ + (void) updateTransferRequestInDB: (NSString *) transferID status: (AWSS3TransferUtilityTransferStatusType) status retry_count: (NSUInteger) retryCount databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + if (eTag == nil) { + eTag = @""; + } NSString *const AWSS3TransferUtilityUpdateTransferUtilityStatusAndETag = @"UPDATE awstransfer " @"SET status=:status, etag = :etag, session_task_id = :session_task_id, retry_count = :retry_count " @"WHERE transfer_id=:transfer_id and " diff --git a/AWSS3/AWSS3TransferUtilityTasks.h b/AWSS3/AWSS3TransferUtilityTasks.h index 0140e3acbd8..b1a99eac077 100644 --- a/AWSS3/AWSS3TransferUtilityTasks.h +++ b/AWSS3/AWSS3TransferUtilityTasks.h @@ -87,6 +87,20 @@ typedef void (^AWSS3TransferUtilityMultiPartProgressBlock) (AWSS3TransferUtility NSProgress *progress); +/** + The suspend multipart upload completion handler. + + @param error Returns the error object when the suspending failed. Returns `nil` on success. + */ +typedef void (^AWSS3TransferUtilityMultiPartUploadSuspendBlock) (NSError * _Nullable error); + +/** + The resume multipart upload completion handler. + + @param error Returns the error object when the resume failed. Returns `nil` on success. + */ +typedef void (^AWSS3TransferUtilityMultiPartUploadResumeBlock) (NSError * _Nullable error); + #pragma mark - AWSS3TransferUtilityTasks /** diff --git a/AWSS3/AWSS3TransferUtilityTasks.m b/AWSS3/AWSS3TransferUtilityTasks.m index 0e91e5dfb44..4221db59583 100644 --- a/AWSS3/AWSS3TransferUtilityTasks.m +++ b/AWSS3/AWSS3TransferUtilityTasks.m @@ -64,7 +64,7 @@ - (void)suspend { //Pause called on a transfer that is not in progress. No op. return; } - + [self.sessionTask suspend]; self.status = AWSS3TransferUtilityTransferStatusPaused; [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:self.transferID @@ -132,6 +132,10 @@ - (instancetype)init { return self; } +- (BOOL)isDone { + return _waitingPartsDictionary.count == 0 && _inProgressPartsDictionary.count == 0; +} + - (AWSS3TransferUtilityMultiPartUploadExpression *)expression { if (!_expression) { _expression = [AWSS3TransferUtilityMultiPartUploadExpression new]; @@ -156,7 +160,10 @@ - (void)cancel { } - (void)resume { - if (self.status != AWSS3TransferUtilityTransferStatusPaused ) { + // no parts should be paused. instead all in progress part uploads should be canceled and + // replaced with a new subtask which has not been started so that resuming will instead + // start up to the number up to the concurrency limit. + if (self.status != AWSS3TransferUtilityTransferStatusPaused) { //Resume called on a transfer that hasn't been paused. No op. return; } @@ -173,6 +180,7 @@ - (void)resume { databaseQueue:self.databaseQueue]; [subTask.sessionTask resume]; } + self.status = AWSS3TransferUtilityTransferStatusInProgress; //Update the Master Record [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:self.transferID @@ -189,9 +197,18 @@ - (void)suspend { //Pause called on a transfer that is not in progresss. No op. return; } - + for (NSNumber *key in [self.inProgressPartsDictionary allKeys]) { + // all in progress tasks should be cancelled and a new subtask should replace it which is + // put in the waiting dictionary and set with that status with a URLSessionTask which + // has not been started. + + // then resuming should start uploading a number of parts up to the concurrency limit. + AWSS3TransferUtilityUploadSubTask *subTask = [self.inProgressPartsDictionary objectForKey:key]; + if (!subTask) { + continue; + } [subTask.sessionTask suspend]; subTask.status = AWSS3TransferUtilityTransferStatusPaused; @@ -214,7 +231,7 @@ - (void)suspend { databaseQueue:self.databaseQueue]; } --(void) setCompletionHandler:(AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock)completionHandler { +- (void)setCompletionHandler:(AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock)completionHandler { self.expression.completionHandler = completionHandler; //If the task has already completed successfully diff --git a/AWSS3/AWSS3TransferUtility_private.h b/AWSS3/AWSS3TransferUtility_private.h index 1918cabdb3f..7623f950a35 100644 --- a/AWSS3/AWSS3TransferUtility_private.h +++ b/AWSS3/AWSS3TransferUtility_private.h @@ -20,7 +20,7 @@ @interface AWSS3TransferUtilityTask() @property (strong, nonatomic) NSURLSessionTask *sessionTask; -@property (readwrite) NSUInteger taskIdentifier; +@property (nonatomic, readwrite) NSUInteger taskIdentifier; @property (strong, nonatomic) NSString *transferID; @property (strong, nonatomic) NSString *bucket; @property (strong, nonatomic) NSString *key; @@ -53,12 +53,14 @@ @property (copy) NSString * uploadID; @property BOOL cancelled; @property BOOL temporaryFileCreated; +@property (readonly) BOOL isDone; @property NSMutableDictionary *waitingPartsDictionary; @property (strong, nonatomic) NSMutableSet *completedPartsSet; @property (strong, nonatomic) NSMutableDictionary *inProgressPartsDictionary; @property int partNumber; @property NSNumber *contentLength; + @end @interface AWSS3TransferUtilityDownloadTask()