persistent uploads to s3 with pause/resumeAll when app is backgrounded? - ios

I'm using this method to upload images/videos to S3 (see code below)
I'm wondering if a user backgrounds the app and much later opens it back up can the pause/resume be used to resume all those uploads? It looks like maybe it's persistently caching the uploads in the SDK with self.cache.diskCache. In other words can I use a UIBackgroundTask to pause the downloads in the expiration handler and when the app comes back in the foreground resumeAll?
I was watching this talk on how to do persistent uploads with NSURLSession and am trying to design a good way to do it in my current app.
Something like this:
- (void)applicationWillEnterForeground:(NSNotification *)notification
{
[self.transferManager resumeAll:^(AWSRequest *request) {
POLYLog(#"request %#",request.description);
}];
}
- (void)applicationDidEnterBackground:(NSNotification *)notification
{
UIApplication* app = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier task;
task = [app beginBackgroundTaskWithExpirationHandler:^{
[self.transferManager pauseAll];
[app endBackgroundTask:task];
task = UIBackgroundTaskInvalid;
}];
}
Reference from AWS Docs:
// Construct the NSURL for the download location.
NSString *downloadingFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:#"downloaded-myImage.jpg"];
NSURL *downloadingFileURL = [NSURL fileURLWithPath:downloadingFilePath];
// Construct the download request.
AWSS3TransferManagerDownloadRequest *downloadRequest = [AWSS3TransferManagerDownloadRequest new];
downloadRequest.bucket = #"myBucket";
downloadRequest.key = #"myImage.jpg";
downloadRequest.downloadingFileURL = downloadingFileURL;
Now we can pass the download request to the download: method of the TransferManager client. The AWS Mobile SDK for iOS uses AWSTask to support asynchronous calls to Amazon Web Services. The download: method is asynchronous and returns a AWSTask object, so we’ll use it accordingly:
[[transferManager upload:uploadRequest] continueWithExecutor:[AWSExecutor mainThreadExecutor]
withBlock:^id(AWSTask *task) {
if (task.error) {
if ([task.error.domain isEqualToString:AWSS3TransferManagerErrorDomain]) {
switch (task.error.code) {
case AWSS3TransferManagerErrorCancelled:
case AWSS3TransferManagerErrorPaused:
break;
default:
NSLog(#"Error: %#", task.error);
break;
}
} else {
// Unknown error.
NSLog(#"Error: %#", task.error);
}
}
if (task.result) {
AWSS3TransferManagerUploadOutput *uploadOutput = task.result;
// The file uploaded successfully.
}
return nil;
}];

Related

Using AWSS3TransferUtility to upload data on iOS

I've managed to successfully upload data using the AWS 2.0 SDK, however I'm having some trouble understanding the "re-wiring" of completion handlers that is meant to take place on resuming of the app.
What I believe I should be doing, is at comment 6, saving the upload task. Then if it reaches the completionHandler block, deleting it. However, if the app is terminated before this, I can look at my saved upload tasks in the block at comment 4. Any saved tasks would be "re-wired" to the completion handler.
For reference, imagine this code happening in an "Upload" class, and in a block, which is the "uploadSuccess()" code you see in the completionHandler.
For reference here is the blog that introduced the AWSS3TransferUtility, and here is its documentation.
Hopefully someone can guide me on this.
// 1. Get/Setup credentials
AWSCognitoCredentialsProvider *credentialsProvider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AWSRegionUSEast1 identityPoolId:#"IdentityPoolId"];
AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast1 credentialsProvider:credentialsProvider];
AWSServiceManager.defaultServiceManager.defaultServiceConfiguration = configuration;
// 2. Create AWSS3 Transfer blocks
AWSS3TransferUtilityUploadExpression *expression = [AWSS3TransferUtilityUploadExpression new];
expression.uploadProgress = ^(AWSS3TransferUtilityTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) {
dispatch_async(dispatch_get_main_queue(), ^{
// Do something e.g. Update a progress bar.
});
};
AWSS3TransferUtilityUploadCompletionHandlerBlock completionHandler = ^(AWSS3TransferUtilityUploadTask *task, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
// Do something e.g. Alert a user for transfer completion.
// On failed uploads, `error` contains the error object.
if (error) {
uploadSuccess(NO);
} else {
uploadSuccess(YES);
}
});
};
// 3. Create Transfer Utility
AWSS3TransferUtility *transferUtility = [AWSS3TransferUtility defaultS3TransferUtility];
// 4. Rewire Transfer Utility blocks
[transferUtility
enumerateToAssignBlocksForUploadTask:^(AWSS3TransferUtilityUploadTask *uploadTask, __autoreleasing AWSS3TransferUtilityUploadProgressBlock *uploadProgressBlockReference, __autoreleasing AWSS3TransferUtilityUploadCompletionHandlerBlock *completionHandlerReference) {
NSLog(#"Upload task identifier = %lu", (unsigned long)uploadTask.taskIdentifier);
// Use `uploadTask.taskIdentifier` to determine what blocks to assign.
//*uploadProgressBlockReference = // Reassign your progress feedback block.
*completionHandlerReference = completionHandler;// Reassign your completion handler.
}
downloadTask:^(AWSS3TransferUtilityDownloadTask *downloadTask, __autoreleasing AWSS3TransferUtilityDownloadProgressBlock *downloadProgressBlockReference, __autoreleasing AWSS3TransferUtilityDownloadCompletionHandlerBlock *completionHandlerReference) {
}];
// 5. Upload data using Transfer Utility
[[transferUtility uploadData:myNSDataObject
bucket:#"bucketName"
key:#"keyName"
contentType:#"text/plain"
expression:expression
completionHander:completionHandler] continueWithBlock:^id(AWSTask *task) {
if (task.error) {
NSLog(#"Error: %#", task.error);
}
if (task.exception) {
NSLog(#"Exception: %#", task.exception);
}
if (task.result) {
AWSS3TransferUtilityUploadTask *uploadTask = task.result;
// 6. Should i be saving uploadTasks here?
}
return nil;
}];

AWS S3 media transfer slow & pause when application send to background in IOS

I am developing an chat app where I transfer media like videos / audio/ images.
When the transfer is done with below code snippet, the transfer is smooth on Wifi but is very very slow when moved to 3G network.
Also another issue is when I go in background the AWS SDK pause the transfer and resumes when I come in foreground.
1) How can I make the transfer fast as I have tried changing the AWS region still no success.
2) Also use the S3Background transfer example compared to AWS S3 transfer manager used but the download paused when I press home button.
Below is my code snipped.
//Upload function
AWSS3TransferManager *transferManager = [AWSS3TransferManager defaultS3TransferManager];
AWSS3TransferManagerUploadRequest *uploadRequest = [AWSS3TransferManagerUploadRequest new];
uploadRequest.bucket = S3BucketName;
User *userMO = .....my user details
NSString *fileName = [[filePath componentsSeparatedByString:#"/"] lastObject];
uploadRequest.key = [userMO.user_pool_id stringByAppendingPathComponent:fileName];
NSURL *uploadFileURL = [NSURL fileURLWithPath:filePath];
uploadRequest.body = uploadFileURL;
uploadRequest.expires = [NSDate dateWithTimeIntervalSinceNow:3600];
NSString *fileContentTypeStr = #"image/jpg";
if ([FileManager isVideoFile:fileName]) {
// fileContentTypeStr = [NSString stringWithFormat:#"video/%#",[[fileName pathExtension] lowercaseString]];
fileContentTypeStr = [NSString stringWithFormat:#"video/quicktime"];
}
else if ([FileManager isImageFile:fileName]) {
fileContentTypeStr = [NSString stringWithFormat:#"image/%#",[[fileName pathExtension] lowercaseString]];
}
else if ([FileManager isAudioFile:fileName]) {
fileContentTypeStr = [NSString stringWithFormat:#"audio/%#",[[fileName pathExtension] lowercaseString]];
}
uploadRequest.contentType = fileContentTypeStr;
[[transferManager upload:uploadRequest] continueWithBlock:^id(AWSTask *task) {
if (task.error) {
if ([task.error.domain isEqualToString:AWSS3TransferManagerErrorDomain]) {
switch (task.error.code) {
case AWSS3TransferManagerErrorCancelled:
{
//updating ui to notify cancel
}
break;
case AWSS3TransferManagerErrorPaused:
{
//updating ui to notify paused
}
break;
default:
{
NSLog(#"Upload task failed: %#",[task.error localizedDescription]);
//updating ui to notify failed
}
break;
}
} else {
NSLog(#"Upload task failed: %#",[task.error localizedDescription]);
//updating ui to notify failed
}
}
if (task.result)
{
//updating ui to notify finished
}
return nil;
}];
if(uploadRequest.state == AWSS3TransferManagerRequestStateRunning)
{
//updating ui to notify progress
}
//Add the request to the current upload cache handler entry as handle
Any body have any suggestions and faced similar issue.
Use AWS background transfer not AWS transfer manager to use the background mode supported transfer.

Download Multiple Items from Amazon S3 Bucket iOS

I've recently implemented the new AWS 2.0 iOS SDK in my application (yay, cocoapods!), and using the sample code from Amazon managed to properly configure access and downloads. I can successfully download a single item without issue, but I need to be able to download multiple files dynamically generated based on the current tableview. There doesn't appear to be a way to set up a batch download, so I'm simply trying to loop through an array of objects and trigger a download with each one. It works, but if the list includes more than a few items, it starts randomly misfiring. For example, if my dynamically created list has 14 items in it, 12 will be downloaded, and the other 2 aren't even attempted. The request just vanishes. In my testing, I added a sleep(1) timer, and then all 14 are triggered and downloaded, so I'm guessing that I'm overwhelming the download requests and they are getting dropped unless I slow it down. Slowing it down is not ideal... perhaps there is another way? Here is the code:
- (IBAction)downloadAllPics:(UIBarButtonItem *)sender {
if (debug==1) {
NSLog(#"Running %# '%#'", self.class, NSStringFromSelector(_cmd));
}
CoreDataHelper *cdh =
[(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
// for loop iterates through all of the items in the tableview
for (Item *item in self.frc.fetchedObjects) {
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *downloadingFilePath1 = [NSString stringWithFormat:#"%#/%##2x.jpg",docDir, item.imageName];
NSURL *downloadingFileURL1 = [NSURL fileURLWithPath:downloadingFilePath1];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
if ([fileManager fileExistsAtPath:downloadingFilePath1]) {
fileAlreadyExists = TRUE;
if (![fileManager removeItemAtPath:downloadingFilePath1
error:&error]) {
NSLog(#"Error: %#", error);
}
}
__weak typeof(self) weakSelf = self;
self.downloadRequest1 = [AWSS3TransferManagerDownloadRequest new];
self.downloadRequest1.bucket = S3BucketName;
// self.downloadRequest1.key = S3KeyDownloadName1;
self.downloadRequest1.key = [NSString stringWithFormat:#"images/%##2x.jpg", item.imageName];
self.downloadRequest1.downloadingFileURL = downloadingFileURL1;
self.downloadRequest1.downloadProgress = ^(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite){
// update progress
dispatch_sync(dispatch_get_main_queue(), ^{
weakSelf.file1AlreadyDownloaded = totalBytesWritten;
weakSelf.file1Size = totalBytesExpectedToWrite;
});
};
// this launches the actual S3 transfer manager - it is successfully launched with each pass of loop
[self downloadFiles];
}
[cdh backgroundSaveContext];
}
That launches the downloadFiles method:
- (void) downloadFiles {
//if I add this sleep, all 14 download. If I don't usually 11-13 download.
sleep(1);
AWSS3TransferManager *transferManager = [AWSS3TransferManager defaultS3TransferManager];
__block int downloadCount = 0;
[[transferManager download:self.downloadRequest1] continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:^id(BFTask *task) {
if (task.error != nil){
if(task.error.code != AWSS3TransferManagerErrorCancelled && task.error.code != AWSS3TransferManagerErrorPaused){
NSLog(#"%s Errorx: [%#]",__PRETTY_FUNCTION__, task.error);
}
} else {
self.downloadRequest1 = nil;
}
return nil;
}];
}
There has got to be a way to download a dynamic list of files from an Amazon S3 bucket, right? Maybe there is a transfer manager that allows an array of files instead of doing them individually?
Any and all help is appreciated.
Zack
Sounds like request timeout interval setting issue.
First, when you configure AWSServiceConfiguration *configuration = ... try to configure the timeoutIntervalForRequest property. Also, maxRetryCount as well. maxRetryCount will attempt to download if failure for downloading each operation.
AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc] initWithRegion:DefaultServiceRegionType
credentialsProvider:credentialsProvider];
[configuration setMaxRetryCount:2]; // 10 is the max
[configuration setTimeoutIntervalForRequest:120]; // 120 seconds
Second, for the multiple items downloading try to collect each AWSTask into one array and get the result at the end of group operation. ex)
// task collector
NSMutableSet *uniqueTasks = [NSMutableSet new];
// Loop
for (0 -> numOfDownloads) {
AWSS3TransferManagerDownloadRequest *downloadRequest = [AWSS3TransferManagerDownloadRequest new];
[downloadRequest setBucket:S3BucketNameForProductImage];
[downloadRequest setKey:filename];
[downloadRequest setDownloadingFileURL:sourceURL];
[showroomGroupDownloadRequests addObject:downloadRequest];
AWSTask *task = [[AWSS3TransferManager defaultS3TransferManager] download:downloadRequest];
[task continueWithBlock:^id(AWSTask *task) {
// handle each individual operation
if (task.error == nil) {
}
else if (task.error) {
}
// add to the tasks
[uniqueTasks addObject:task];
return nil;
}
[[AWSTask taskForCompletionOfAllTasks:tasks] continueWithBlock:^id(AWSTask *task) {
if (task.error == nil) {
// all downloads succeess
}
else if (task.error != nil) {
// failure happen one of download
}
return nil;
}];
The reason some requests seem to vanish is that you define AWSS3TransferManagerDownloadRequest as a property. self.downloadRequest1 = nil; is executed on the background thread, and it is possible that when [transferManager download:self.downloadRequest1] is executed, self.downloadRequest1 is nil.
You should remove the property and simply pass an instance of AWSS3TransferManagerDownloadRequest as an argument for - downloadFiles:.

iOS Background process

I need to make a synchronization with a server when my application is running.
Sometimes this synchronization can take several minutes.
On the iPhone this task is interrupted if the user press the home button or if the device does auto-lock.
I tried something like:
backgroundTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID];
backgroundTaskID = UIBackgroundTaskInvalid;
}];
[self Synchronization];
funcao sincronização:
-(void)Synchronization{
object=nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[TheAppDelegate setSync:YES];
send = NO;
responseObj = [[ConteinerInicial alloc] init];
object = [self fillObjectContainer:1];
error = NO;
[[objectManager HTTPClient] setDefaultHeader:#"Token" value:TheAppDelegate.token.token];
[[objectManager HTTPClient] setDefaultHeader:#"Username" value:TheAppDelegate.token.userName];
[[objectManager HTTPClient] setDefaultHeader:#"IDDevice" value:TheAppDelegate.token.IDDevice];
[objectManager postObject:object path:#"" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *result){
NSArray *response = [result array];
NSLog(#"Loading mapping result: %#", result);
NSDictionary *headerDictionary = [operation.HTTPRequestOperation.response allHeaderFields];
NSString *authenticationStatus = [headerDictionary objectForKey:#"AuthenticationStatus"];
// if server returns "NotAuthorized", user must login again
if ([authenticationStatus isEqualToString:#"NotAuthorized"]) {
[AppDelegate showErrorAlert:#"Login expired!"];
[TheAppDelegate clearLoginToken];
error = YES;
return;
}
responseObj = [response objectAtIndex:0];
if(responseObj.Package.count>0)
{
[self savePackage:responseObj tipo:1];
[self Synchronization];
}else
{
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID];
backgroundTaskID = UIBackgroundTaskInvalid;
}
} failure: ^(RKObjectRequestOperation *operation, NSError *reportError){
RKLogError(#"Operation failed with error: %#", reportError);
[AppDelegate showErrorAlert:#"There is some problem with the connection or the server! Please, check your connection or the address of the server and try again."];
send = YES;
[TheAppDelegate setSync:NO];
error = YES;
}];
});
}
And at the end of synchronization is ready:
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID];
backgroundTaskID = UIBackgroundTaskInvalid;
but this code does not work, anyone have an idea how I can overcome this challenge?
The way to sync is to be asking for data until no longer exist. but with this code it just ends up in the backgroud object that is receiving at the time that goes into background
Your code is calling [self synchronization]; inside the expiration handler. That block is only called when your app runs out of time for running in the background. So you are trying to start your synchronisation only when there is no time left to do it.
Call [self synchronization]; outside of the block. The block should contain clean up / pause code and end the background task...
Also, look at using NSURLSession for good iOS 7 compatibility.

download task is null for the first request when when in backgroundfetch IOS

I was wondering if you have seen this or might have some ideas as to why I see the following behavior in my code: I have an NSURLsession with background config. I initiate periodic download task when the program runs in the Foreground, and everything works. WhenI simulate backgroundfetch (in xcode), my task gets a null value (eventhough the request and the session are not null). of course in this case, my session delegate never gets fired to do completionhandler. if I simulate subsequent background fetches, they all work afterward. at this point, if I bring the app to the foreground in the simulator, and I simulate another backgroundfetch, the symptoms star all over. I am using this code in my appdelegate class.
your help is greatly appreciated.
- (NSURLSession *)FlickrSession
{
if(!_FlickrSession)
{
NSLog(#"setting new FlickrSession");
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:FLICKR_SESSION];
configuration.allowsCellularAccess = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_FlickrSession = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
_FlickrSession.sessionDescription = FLICKR_SESSION;
NSLog(#" new self is %#", _FlickrSession);
NSLog(#"queue in session %#", dispatch_get_current_queue());
});
}
return _FlickrSession;
}
-(void) startFlickrFetch
{
// initialize session config and the background session
[self.FlickrSession getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks)
{
if(![downloadTasks count])
{
NSLog(#"new downloadtask session %#", self.FlickrSession.sessionDescription);
NSURLRequest *request = [NSURLRequest requestWithURL:[FlickrFetcher URLforRecentGeoreferencedPhotos]];
// NSURLSessionDownloadTask *task = [self.FlickrSession downloadTaskWithURL:[FlickrFetcher URLforRecentGeoreferencedPhotos]];
NSURLSessionDownloadTask *task = [self.FlickrSession downloadTaskWithRequest:request];
task.taskDescription = FLICKR_DOWNLOAD_TASK;
NSLog(#"new request %#", request);
NSLog(#"new downloadtask %#", task);
[task resume];
//task?[task resume]:[self fireBackgroungFetchCompletionHandler];;
//[self fireBackgroungFetchCompletionHandler];
NSLog(#"queue in task %#", dispatch_get_current_queue());
}
else
{
NSLog(#"resuming old downloadtask %d", [downloadTasks count]);
for(NSURLSessionDownloadTask *task in dataTasks) [task resume];
}
}];
NSLog(#"queue outside the block %#", dispatch_get_current_queue());
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// open the file, if there is no managedContext. this is the case wjere the application was launched directly by user
// it did not come from the bckground state;
//need to enable background fetch
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
//[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(documentChangedState) name:UIDocumentStateChangedNotification object: self.document];
NSLog(#"in application didfinishlaunching");
[self openDatabaseFile];
[self startFlickrFetch];
return YES;
}
-(void) application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
self.backgroundFetchCompletionHandler = completionHandler;
if(self.document.documentState == UIDocumentStateNormal)
{
//[self openDatabaseFile];
NSLog(#"in performFetchWithCompletionHandler");
[self startFlickrFetch];
}
}
- (void) application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
{
self.completionhandler = completionHandler;
NSLog(#"handle event for backgroundURLsession***********");
}
This could have something to do with a strange interaction of the background fetch and a background session, in which case I have no advice.
However, in the background, iOS doesn't know to wait for async calls, e.g. getTasksWithCompletionHandler. You can solve this by wrapping those calls with a UIBackgroundTaskIdentifier†-based [UIApplication] begin/end task (in this case, with the "end (app) task" inside the "get (session) tasks completion handler" block).
But if all you need is a count, here's what I did, which I think is simpler:
Create an ivar:
NSMutableSet *activeTaskIDs;
When you create a task, add it to the set:
[activeTaskIDs addObject:#(task.taskIdentifier)];
When the task completes, remove it.
You can get your count from there, no async.
† Confusingly, a different kind of task. I differentiate with the terms "app tasks" vs. "session tasks".

Resources