I am working on an iOS application that downloads images from amazon s3. I am trying to track progress of an image download.
I can not get the -(void)request:(AmazonServiceRequest *)request didSendData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite delegate method to fire.
This is the code that I have so far to set the delegate method.
-(void) viewDidLoad
{
self.s3 = [[AmazonS3Client alloc] initWithAccessKey:ACCESS_KEY_ID withSecretKey:SECRET_KEY];
self.s3.endpoint = [AmazonEndpoints s3Endpoint:US_WEST_2];
NSString *key = [[NSString alloc] initWithFormat:#"path1/%#", uniqueID];
S3GetObjectRequest *downloadRequest = [[S3GetObjectRequest alloc] initWithKey:key withBucket: PICTURE_BUCKET];
[downloadRequest setDelegate:self];
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = #"Loading Picture...";
S3GetObjectResponse *downloadResponse = [s3 getObject:downloadRequest];
}
-(void)request:(AmazonServiceRequest *)request didSendData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
NSLog(#"Bytes Written: %i", bytesWritten);
NSLog(#"Total Bytes Written: %i", totalBytesWritten);
NSLog(#"Total Bytes Expected to Write: %i", totalBytesExpectedToWrite);
}
I managed to get this delegate method to work for uploading images, but can not seem to get it to work for downloading. What do I need to do differently to track download progress?
Thanks
I came across this while researching myself on AWS and thought I would post an answer. -(void)request:(AmazonServiceRequest *)request didSendData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
only works when sending data, as per the name.
If you have a ballpark on how big the file is (you could set up some sort of server request to get this information before starting the download, or if there is a typical amount). Then you could use -(void)request:(AmazonServiceRequest *)request didReceiveData:(NSData *)data and continue to append the data to an #property of NSMutableData by calling [self.data appendData:data], then measure self.data.length which returns the number of bytes to your meta data size estimate which you could convert to bytes.
Hope this helps!
AdamG is right.
-(void)request:(AmazonServiceRequest *)request didSendData:(long long)bytesWritten totalBytesWritten:(long long)totalBytesWritten totalBytesExpectedToWrite:(long long)totalBytesExpectedToWrite is only for upload.
When you want to keep track of the progress of a download, you should use:
-(void)request:(AmazonServiceRequest *)request didReceiveData:(NSData *)data
And here I wan't to add some colaboration of my own. If you want to know the size of the file to download, here's a good way to do it.
S3GetObjectMetadataRequest *getMetadataObjectRequest = [[S3GetObjectMetadataRequest alloc] initWithKey:YOUR_KEY withBucket:YOUR_BUCKET];
S3GetObjectMetadataResponse *metadataResponse = [[AmazonClientManager s3] getObjectMetadata:getMetadataObjectRequest];
NSString *filesizeHeader = metadataResponse.headers[#"Content-Length"];
fileSize = [filesizeHeader floatValue];
I've found the documentation to be a little bit silent about this.
Also, the AWS iOS Samples also don't contain a very good example. Actually, there's a comment stating that "The progress bar for downlaod is just an estimate. In order to accurately reflect the progress bar, you need to first retrieve the file size", but without clue about how to do it.
So, I've found this way by messing around with the getMetadataObjectRequest.debugDescription property.
Hope this helps!
Related
I am downloading PDF file in my application and I want to show download progress bar when PDF downloading get started,I searched for this but didn't get any useful tutorial or answer,Can any one tell me any link or tutorial? Please help me, Thanks in advance.
You can use an NSURLConnection (here's an example) to get the remote file. The NSURLConnectionDataDelegate gets called with progress as data is received.
In order to estimate progress, you need to know (or have an estimate of) the number of bytes in the downloading file.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// allocate mutable responseData when connection didReceiveResponse
[self.responseData appendData:data];
float progress = self.responseData.length / self.expectedLength;
// this is the percentage progress
}
You can follow this link to display PDF download progress even if with multiple PDF files are downloading in queue. I mean you can display download progress status not only for single PDF file but also for multiple downloading PDF files in the application:
http://allseeing-i.com/ASIHTTPRequest/How-to-use
You can use AFHTTPRequestOperation to get the remote file,
for the progress you can try this code :
This will show you progress in progress view.
[httpClient enqueueBatchOfHTTPRequestOperations:tempOperations
progressBlock:^(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations) {
float progressValue=(float)numberOfCompletedOperations/(float)totalNumberOfOperations;
NSLog(#"%f", progressValue);
NSLog(#"%d / %d", numberOfCompletedOperations, totalNumberOfOperations);
[progressValueLabel setText:[NSString stringWithFormat:#"%.0f%%", (progressValue * 100)]];
[progressView setProgress:progressValue];
}
completionBlock:nil];
In my iphone app, I am displaying information of files added to documents directory, in a table view, as soon as those are added. For this I am using DirectoryWatcher class provided in one of the sample codes by apple.
Below is the block of code showing its use:
- (void)viewDidLoad
{
// start monitoring the document directory…
self.aDirectoryWatcher = [DirectoryWatcher watchFolderWithPath:[self applicationDocumentsDirectory] delegate:self];
// scan for existing documents
[self directoryDidChange:self.aDirectoryWatcher];
}
- (void)directoryDidChange:(DirectoryWatcher *)folderWatcher
{
[self reconcileData];
}
One of the information displayed in table view cell is- file size, which I am obtaining as below:
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:nil];
NSNumber * size = [fileAttributes objectForKey:NSFileSize];
Problem is-
When I am trying to add a large file, such as a movie file, then as
soon as transfer starts (copy or move operation) it invokes
directoryDidChange: immediately. It did not wait unless the transfer
is complete. So I always get size as 0.
In case of small sized files, such as images, it works fine.
Now I have two question:
Is there any way to know the complete size of file, which is in transfer state. eg. if message displayed is copying 30 MB of 100 MB, I want to get 100 MB?
Is there any alternative of DirectoryWatcher, which notifies only when file is completely added?
Please suggest.
You are currently looking to the file system, you should look into the response headers from your download requests.
For example when you use NSURLConnection to download the file, you can implement the delegate method connection:didReceiveResponse: and look into the response headers.
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSLog(#"Expected content length: %lld", httpResponse.expectedContentLength);
}
}
To get notified when it's finished you can implement connectionDidFinishLoading:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// Notify download success
}
and
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// Notify error
}
My game needs to fill tableView cells with a bunch of things from my server database. This has been working fine. Then I upgraded Xcode to 4.6 and targeted iOS6.1, to please the App Review Team folks. Now, one of my connections never completes. (All of the other Posts seem to work correctly, as always.) Here's my post:
- (void) fillCells {
Cell_QtoA *newCell = [[Cell_QtoA alloc] initCellUser:usrID Grp:grpID Qtn:0 Gnm:#"na" Cat:#"na" Sit:#"na" Pfl:#"na" Lks:0 isA:0 ddA:0 ];
NSMutableURLRequest *reqPost = [SimplePost urlencodedRequestWithURL:[NSURL URLWithString:kFillCells] andDataDictionary:[newCell toDictC]];
(void) [[NSURLConnection alloc] initWithRequest:reqPost delegate:self];
}
I think it's working fine. The PHP and database haven't changed. Everything worked great yesterday, before the upgrades. Here's my connection method:
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"data = %#", data);
NSString *error;
NSArray *array = (NSArray *)[NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:0 errorDescription:&error];
if( error ) {
NSLog(#"Error = %#", error);
return;
}
NSLog(#"1st object in array of %d is %#", [array count], array );
}
Because I suspected net speeds to be an issue, I added a timer to the call, which I never needed before:
timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(fillCells) userInfo:nil repeats:YES];
The timer didn't help. Still get errors:
"Unexpected EOF" and "Unexpected character z (or whatever) at Line 1"
The NSLog of data shows hex data that appears cut off, like:
<3c3f786d 6c207665 ... 63743e0a 3c2f6172 7261793e 0a3c2f70 6c697374 3e>
It's like the reception is being interrupted. Anyone know what's happening here? Thanks!
You aren't using all the response data; as mention in the NSURLConnectionDelegate reference:
The newly available data. The delegate should concatenate the contents
of each data object delivered to build up the complete data for a URL
load.
So you need to create an NSData instance variable; clear before the request and append to it whenever new data arrives. Then use the didFinishLoading delegate method to trigger the call to propertyListFromData with the complete response.
I have an issue on how to refresh the UI for iOS apps. What I wanted to achieve is this:
Show data in UITableView based on data retrieved from web service
The web service should be called from a separate thread (not main thread)
After the data is retrieved, it will refresh the contents of UITableView with the retrieved data
It is due so that the UI will not hang or the app will not block user input while in the process of receiving data from the web service in bad network connection
To do that, I create the following source code:
- (void)viewDidLoad
{
[super viewDidLoad];
NSURL *myURL = [[NSURL alloc] initWithString:[Constant webserviceURL]];
NSURLRequest *request = [NSURLRequest requestWithURL:myURL cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:60];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
[self myparser] = [[MyXMLParser alloc] initXMLParser];
[parser setDelegate:myparser];
BOOL success = [parser parse];
if (success) {
// show XML data to UITableView
[_tableView performSelectorOnMainThread:#selector(reloadData) withObject:[myparser xmldata] waitUntilDone:NO];
}
else {
NSLog(#"Error parsing XML from web service");
}
}
==================
Is my implementation correct? Anybody know how to resolve it?
You would want to call
+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler
It will make the call to get the Data on a different thread then when the data pulled down or it had problems download data from the url it will call your handler block on the same thread as the original call was made.
Here is one way to use it: https://stackoverflow.com/a/9409737/1540822
You can also use
- (id)initWithRequest:(NSURLRequest *)request delegate:(id < NSURLConnectionDelegate >)delegate
And this will call one of your NSURLConnectionDelegate methods when data is downloaded in chucks. If you going to have large data then you may want to use this so that you don't spend too much time in the response.
I'm quite new to Amazon S3 and I'm having difficulty downloading large files from S3.
I have successfully downloaded a file that is 35MB every time, but when the size of the file is really big around 500 MB - 1.7GB the application crashes.
When trying on the simulator I would get can't allocate region error after about 1GB of the download.
So then I tried it on the device. Now it seems to just crash at a random time and
no crash report is put in the device, therefor I'm having an issue debugging this problem.
At first I thought it was the device or even the simulator. But i'm not really sure.
Someone mentioned that S3 framework times out the downloads randomly occasionally for large files. Could this be the case?
I'm building the file by opening a data file seeking to the end, adding the data, then closing the file until the download is complete.
I'm not sure how to debug this problem.
Any help would be appreciated.
Thank you.
I am a maintainer of the AWS SDK for iOS. We recently patched the S3GetObjectResponse to allow the streaming of the data directly to disk without keeping the response data in memory.
S3GetObjectResponse.m
To enable this, you simply need to set the stream when creating your request:
NSOutputStream *outputStream = [[[NSOutputStream alloc] initToFileAtPath:FILE_NAME append:NO] autorelease];
[outputStream open];
S3GetObjectRequest *getObjectRequest = [[[S3GetObjectRequest alloc] initWithKey:FILE_NAME withBucket:BUCKET_NAME] autorelease];
getObjectRequest.outputStream = outputStream;
[s3 getObject:getObjectRequest];
Update: We added a post to our AWS Mobile Developer Blog on downloading large files with the AWS SDK for iOS that includes this info as well as other tips.
S3GetObjectRequest has NSMutableData* body where it appends all the data it downloads.
For large files as download progresses data is appended constantly, and it goes over the VM limit of 90MB and then app gets killed by iOS.
Quick and dirty workaround is to create your own S3GetObjectRequest and S3GetObjectResponse classes. AWS framework instantiates Response based on Class Name of Request (Class name of Request without last 7 chars "Request" and appends it with "Response", and tries to instantiate new class of that name).
Then to override -(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data to release body all the time.
This is quick and dirty fix simply because you still have constant data allocation, appending and then release. But it works when you are in a pinch. For my usage of downloading files of 150-700mb, this simple hack kept memory usage of the app at 2.55mb average, +/- 0.2mb.
As stated by the author of ASIHTTP library, it is no longer maintained.
Request - LargeFileS3GetObjectRequest.h
#interface LargeFileS3GetObjectRequest : S3GetObjectRequest
#end
Request - LargeFileS3GetObjectRequest.m
#implementation LargeFileS3GetObjectRequest
#end
Response - LargeFileS3GetObjectResponse.h
#interface LargeFileS3GetObjectResponse : S3GetObjectResponse
#end
Response - LargeFileS3GetObjectResponse.m
#implementation LargeFileS3GetObjectResponse
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// allow original implementation to send data to delegates
[super connection:connection didReceiveData:data];
// release body and set it to NULL so that underlying implementation doesn't
// append on released object, but instead allocates new one
[body release];
body = NULL;
}
#end
Hope it helps.
You may want to stream the data to your application via ASIHTTPRequest
http://allseeing-i.com/ASIHTTPRequest/S3
NSString *secretAccessKey = #"my-secret-access-key";
NSString *accessKey = #"my-access-key";
NSString *bucket = #"my-bucket";
NSString *path = #"path/to/the/object";
ASIS3ObjectRequest *request = [ASIS3ObjectRequest requestWithBucket:bucket key:path];
[request setSecretAccessKey:secretAccessKey];
[request setAccessKey:accessKey];
[request startSynchronous];
if (![request error]) {
NSData *data = [request responseData];
} else {
NSLog(#"%#",[[request error] localizedDescription]);
}
/* Set up the Amazon client */
_s3 = [[AmazonS3Client alloc] initWithAccessKey:k_Amazon_ACCESS_KEY_ID withSecretKey:k_Amazon_SECRET_KEY];
_s3.endpoint = [AmazonEndpoints s3Endpoint:SA_EAST_1];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
/* Open a file stream for the download */
NSOutputStream *outputStream = [[NSOutputStream alloc] initToFileAtPath:[DOCUMENTS_DIRECTORY stringByAppendingPathComponent:k_Amazon_Video_Local_File_Name] append:NO];
[outputStream open];
/* Set up the s3 get object */
S3GetObjectRequest *getVideoRequest = [[S3GetObjectRequest alloc] initWithKey:k_Amazon_Video_Path withBucket:#""];
/* Set the stream */
getVideoRequest.outputStream = outputStream;
/* Get the response from Amazon */
S3GetObjectResponse *getObjectResponse = [_s3 getObject:getVideoRequest];
dispatch_async(dispatch_get_main_queue(), ^{
if(getObjectResponse.error != nil)
{
NSLog(#"S3 Error: %#", getObjectResponse.error);
}
else
{
NSLog(#"S3 - Video download complete and successful");
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:k_Amazon_Video_Downloaded];
}
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
});
});