AFNetworking: downloading files in queue - iphone

I just started using AFNetworking and so far it worked fine for just getting data from the web.
But now I have a list of files that need to be downloaded to the device
for(NSDictionary *issueDic in data)
{
Issue *issue = [[Issue alloc] init];
...
[self getOrDownloadCover:issue];
...
}
getOrDownloadCover:issue checks if a file already exists locally, if it does, it just saves that path, if it doesn't, it downloads the file from a given url
- (void)getOrDownloadCover:(Issue *)issue
{
NSLog(#"issue.thumb: %#", issue.thumb);
NSString *coverName = [issue.thumb lastPathComponent];
NSLog(#"coverName: %#", coverName);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
__block NSString *filePath = [documentsDirectory stringByAppendingPathComponent:coverName];
if([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
// Cover already exists
issue.thumb_location = filePath;
NSLog(#"issue.thumb_location: %#", filePath);
}
else
{
NSLog(#"load thumb");
// Download the cover
NSURL *url = [NSURL URLWithString:issue.thumb];
AFHTTPClient *httpClient = [[[AFHTTPClient alloc] initWithBaseURL:url] autorelease];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:issue.thumb parameters:nil];
AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc] initWithRequest:request] autorelease];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:filePath append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
issue.thumb_location = filePath;
NSLog(#"issue.thumb_location: %#", filePath);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"FAILED");
}];
[operation start];
}
}
getOrDownloadCover:issue can be called 20 times in a row, so I would need to put all my requests into a queue, and once the queue is completed, it should still be able to save the path (or just send out a notification, since I already know what the path is)
Any suggestions for this?

Add an NSOperationQueue to an object you can get the instance of at any point in your application, for example in your appDelegate. Then just add the AFHTTPRequestOperation to this queue like:
[[(AppDelegate *) [UIApplication sharedApplication].delegate operartionQueue] addOperation:operation];
Just handle the saving in the completion block, either call a methode from this block on the main thread or post a NSNotification.
To call the main thread use GCD :
dispatch_async(dispatch_get_main_queue(), ^{
// Call any method from on the instance that created the operation here.
[self updateGUI]; // example
});

Not sure if this is helpful but...
Instead of using your own operationQueue you could use AFHTTPClient's operationQueue. Enqueue single operations with enqueueHTTPOperation or enqueue an array of operations with enqueueBatchOperations (I'm recalling these method names from memory, likely off a little).
As far as storing data specific to each operation, you could subclass AFHTTPRequestOperation and set properties for the path you'd like to store. Something like this.

Related

Downloading a File with AFNetworking

Trying to write a method for my iPhone program that given a URL address to a file, it would download to the iOS App's Documents Directory.
Following AFNetowrking's Documentation, it seems to work fine except that the filename is always some garbage.
I'm using Xcode 5 with AFNetworking 2.0 added to my project. Here's the code that I have so far:
//#import "AFURLSessionManager.h"
//On load (or wherever):
[self downloadFile:#"http://www.irs.gov/pub/irs-pdf/fw4.pdf"];
-(void)downloadFile:(NSString *)UrlAddress
{ NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:UrlAddress];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response)
{
NSURL *documentsDirectoryPath = [NSURL fileURLWithPath:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]];
return [documentsDirectoryPath URLByAppendingPathComponent:[targetPath lastPathComponent]];
}
completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error)
{
NSLog(#"File downloaded to: %#", filePath);
}];
[downloadTask resume];
}
The end result is that the file is successfully downloaded to my documents directory, but with a garbled name:
File downloaded to: file:///Users/myname/Library/Application%20Support/iPhone%20Simulator/7.0/Applications/25188DCA-4277-488E-B08A-4BEC83E59194/Documents/CFNetworkDownload_60NWIf.tmp
The end result I'm expecting:
File downloaded to: file:///Users/myname/Library/Application%20Support/iPhone%20Simulator/7.0/Applications/25188DCA-4277-488E-B08A-4BEC83E59194/Documents/fw4.pdf
I used cocoapods to add AFNetworking to my project:
pod 'AFNetworking', "~> 2.0"
Lastly, what do I need to do to get the progress of the download?
This is the answer I was able to create:
-(void)downloadFile:(NSString *)UrlAddress
{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:UrlAddress]];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
NSString *pdfName = #"The_PDF_Name_I_Want.pdf";
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:pdfName];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Successfully downloaded file to %#", path);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
NSLog(#"Download = %f", (float)totalBytesRead / totalBytesExpectedToRead);
}];
[operation start];
}
I am open to improvements or suggestions :)
Simply you can replace this line:
return [documentsDirectoryPath URLByAppendingPathComponent:[targetPath lastPathComponent]];
with:
return [documentsDirectoryPath URLByAppendingPathComponent:#"fw4.pdf"];
since [targetPath lastPathComponent] returns something like CFNetworkDownload_60NWIf.tmp

How to avoid delay of webservice(ASIHTTPRequest) response in iPhone?

In my iPhone app handling web service for storing and retrieving data.Now i am using the following code for web service handling.
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:#"1" forKey:#"id"];
[request setTag:100];
[request setDelegate:self];
[request startAsynchronous];
By this code i got response in 'requestFinished' method.My problem is web service response is very delay(depends upon internet speed).How to make response from web service very fast?Please help me.
I think you want to send json objects by post method..delay is depend on your server(how fast it handle request and response back) but i suggest you to use progress bar and blocks to handle network request..
loadingHUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
loadingHUD.labelText = NSLocalizedString(#"Downloading", nil);
loadingHUD.mode=MBProgressHUDModeAnnularDeterminate;
NSString *documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES) lastObject];
// Add your filename to the directory to create your saved file location
NSString* destPath = [documentDirectory stringByAppendingPathComponent:[fileName stringByAppendingString:#".mov"]];
NSURL *url = [NSURL URLWithString:mainURL];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"POST" path:postURL parameters:postRequest];
NSLog(#"postRequest: %#", postRequest);
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:destPath append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"Successfully downloaded file to %#",[[NSString alloc] initWithData:operation.responseData encoding:NSASCIIStringEncoding]);
// Give alert that downloading successful.
NSLog(#"Successfully downloaded file to %#", destPath);
NSLog(#"response: %#", operation.responseString); // Give alert that downloading successful.
// [self.target parserDidDownloadItem:destPath];
loadingHUD.detailsLabelText = [NSString stringWithFormat:#"%# %i%%",#"Downloading",100];
[loadingHUD hide:TRUE];
[DBHelper savePurchaseId:fileName];
[self movieReceived];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
// Give alert that downloading failed
NSLog(#"Error: %#", error);
// [self.target parserDidFailToDownloadItem:error];
[loadingHUD hide:TRUE];
}];
[operation setDownloadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite)
{
// Progress
progress = ((float)totalBytesWritten) / fileSize;
loadingHUD.progress = progress;
}];
[operation start];
}
We cant control the internet speed due to the different network provider or environment when the client is using your app.
But you can put your web-services to run in background without effecting your main function.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//code for webservices calling
dispatch_async(dispatch_get_main_queue(), ^{
//functions after your webservices done, for example reload the table or hide the loading bar.
});
});

NSData writeToFile works on simulator but not on device

In my iphone app I am downloading some number of images from the web. It doesn't matter if it blocks the UI thread, in fact it needs to block UI thread till fully downloaded. Once done, I notify the UI to wake up and display them.
My (simplified) code goes like this:
for (int i=0; i<10; i++)
{
//call saveImageFromURL (params)
}
//Call to Notify UI to wake up and show the images
+(void) saveImageFromURL:(NSString *)fileURL :(NSString *)destPath :(NSString *)fileName
{
NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:fileURL]];
NSFileManager * fileManager = [NSFileManager defaultManager];
BOOL bExists, isDir;
bExists = [fileManager fileExistsAtPath:destPath isDirectory:&isDir];
if (!bExists)
{
NSError *error = nil;
[fileManager createDirectoryAtPath:destPath withIntermediateDirectories:YES attributes:nil error:&error];
if (error)
{
NSLog(#"%#",[error description]);
return;
}
}
NSString *filePath = [destPath stringByAppendingPathComponent:fileName];
[data writeToFile:filePath options:NSAtomicWrite error:nil];
}
When I am done with my for loop, I am pretty sure that all images are stored locally. And it works fine in simulator.
However it does not work well on my device. UI wakes up before images are stored. And almost all images seem empty.
What am I doing wrong?
Check that if your device can download those images, visit the image URLs in Mobile Safari to test. dataWithContentsOfURL: will return nil OR it's not a correct image data, like 404 not found
Log errors of [data writeToFile:filePath] to see the details of saving .
After some research, I used AFHttpClient enqueueBatchOfHTTPRequestOperations to accomplish multiple file downloads.
Here is how it goes:
//Consider I get destFilesArray filled with Dicts already with URLs and local paths
NSMutableArray * opArray = [NSMutableArray array];
AFHTTPClient *httpClient = nil;
for (id item in destFilesArray)
{
NSDictionary * fileDetailDict = (NSDictionary *)item;
NSString * url = [fileDetailDict objectForKey:#"fileURL"];
if (!httpClient)
httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:url]];
NSString * filePath = [photoDetailDict objectForKey:#"filePath"];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:filePath append:NO];
[opArray addObject:operation];
}
[httpClient enqueueBatchOfHTTPRequestOperations:opArray progressBlock:nil completionBlock:^(NSArray *operations)
{
//gets called JUST ONCE when all operations complete with success or failure
for (AFJSONRequestOperation *operation in operations)
{
if (operation.response.statusCode != 200)
{
NSLog(#"operation: %#", operation.request.URL);
}
}
}];

how to store data in NS-Mutable-data in did-receive-data and than write to file

I am stuck from 2 days i want to display downlaod progress bar.
I am sending json in post to server and in response server send me video data.
To display progress bar i write some logic code like
In didreceivedata method of ASIhttep i am appending receive data with global NSmutabledata and in request done method i write that global Nsmutalbedata into file.
but file is blank it wont get store into file.
I know ASIHttprequest is old library but everyone suggest me to use AFnetworking but I dont want to change the code because it will take so much time and i have to read documents again.
anybody can help me how can i append data and after downlaod done write that appended data to file??
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[[NSURL alloc] initWithString:#"http://testing.io/dev.php/video/verifyReceipt"]];
[request setDidReceiveDataSelector:#selector(request:didReceiveData:)];
[request setPostValue:resultAsString forKey:#"verify"];
[request setDidFinishSelector:#selector(requestDone:)];
[request setTimeOutSeconds:120];
[request setDelegate:self];
[request setNumberOfTimesToRetryOnTimeout:2];
[request setDownloadProgressDelegate:progressBar];
request.showAccurateProgress = YES;
[request startSynchronous];
}
-(void)request:(ASIHTTPRequest *)request didReceiveData:(NSData *)data
{
[videoData appendData:data];
NSLog(#"data is %#",data);
}
- (void)requestDone:(ASIHTTPRequest *)request
{
//[MBProgressHUD hideHUDForView:self.view animated:YES];
// SAVED PDF PATH
// Get the Document directory
NSString *documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
// Add your filename to the directory to create your saved pdf location
NSString* movLocation = [documentDirectory stringByAppendingPathComponent:[fileName stringByAppendingString:#".mov"]];
if(request.responseStatusCode==200)
{
[videoData writeToFile:movLocation atomically:NO];
NSLog(#"in request done sucsessfully downlaod and store in database %d",request.responseStatusCode);
[DBHelper savePurchaseId:fileName];
[self movieReceived];
}
else
{
NSLog(#"in request downlaod and store in database failed %#",request.responseHeaders);
}
}
It's better to use asynchronous requests for the task like this. You can use the same ASIHTTPRequest class, but with block approach. Try to write the code similar to this:
-(void) verifyReceipt {
NSURL *theURL = [NSURL URLWithString:#"http://testing.io/dev.php/video/verifyReceipt"];
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:theURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0f];
[theRequest setHTTPMethod:#"POST"];
NSString *param1 = [self getParam1]; // getParam1 - some method to get useful data for request's body
NSNumber *param2 = [self getParam2];
NSString *postString = [NSString stringWithFormat:#"param1=%#&param2=%#", param1, param2];
[theRequest setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:theRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if ([data length] > 0 && error == nil) {
//[delegate receivedData:data]; // - if you want to notify some delegate about data arrival
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [NSString stringWithFormat:#"%#/fileArrived.ext", rootPath];
//try to access that local file for writing to it...
NSFileHandle *hFile = [NSFileHandle fileHandleForWritingAtPath:filePath];
//did we succeed in opening the existing file?
if (!hFile)
{ //nope->create that file!
[[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
//try to open it again...
hFile = [NSFileHandle fileHandleForWritingAtPath:filePath];
}
//did we finally get an accessable file?
if (!hFile)
{ //nope->bomb out!
NSLog(#"could not write to file %#", filePath);
return;
}
//we never know - hence we better catch possible exceptions!
#try
{
//seek to the end of the file
[hFile seekToEndOfFile];
//finally write our data to it
[hFile writeData:data];
}
#catch (NSException * e)
{
NSLog(#"exception when writing to file %#", filePath);
}
[hFile closeFile];
} else if ([data length] == 0 && error == nil) {
// [delegate emptyReply];
} else if (error != nil && error.code == NSURLErrorTimedOut) {
// [delegate timedOut];
} else if (error != nil) {
// [delegate downloadError:error];
}
[queue release];
}];
}
This will append every arrived chunk of your big data into a file as you wanted.
Customize the request POST body for your needs, and this should work. Asynchronously :)
Ok, first check your file path, I usually prefer to refer file path in this way:
you need to get the root of your application in this way:
NSString* rootPath = NSHomeDirectory();
and save the the data in one of the sub folder as specified by Apple file system guide line
NSString* fullPath = [rootPath stringByAppendingPathComponent:#"subFoldeder/file.extension"];
About append new data to old data a very quick solution could be initialize your videoData in this way:
NSMutableData *videoData = [[NSMutableData alloc] initWithContentsOfFile:#"filePath"];
After that you can procede like you are already duing appending data when you receive it, and write the complete file at the end
The correct thing to do, to don't use too much memory should be open a file seek it to the end and append data to the file

AFNetworking check progress

I'm using the AFNetworking framework to download files and write them to the local file system.
But since the files can be quite big I want to add a UIProgressView, but I can't seem to find any method that gets updated with the progress.
I see people talking about setProgressBlock, but I can't find any information about this in the docs: http://afnetworking.org/Documentation/Classes/AFHTTPRequestOperation.html
Is there a method that does this? I'm just using AFHTTPRequestOperation to download the files.
refer a following code. this is a some file download using a AFNetworking Code.
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://blahblah.com/blahblah.mp3"]];
AFURLConnectionOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *filePath = [paths objectAtIndex:0] stringByAppendingPathComponent:#"blahblah.mp3"];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:filePath append:NO];
[operation setDownloadProgressBlock:^(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead)
{
myProgressView.progress = (float)totalBytesRead / totalBytesExpectedToRead;
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"downloadComplete!");
}];
[operation start];
The setDownloadProgressBlock method is part of AFURLConnectionOperation, from which AFHTTPRequestOperation inherits - that's why you don't see it in the AFHTTPRequestOperation documentation. The documentation you're after is here:
http://cocoadocs.org/docsets/AFNetworking/1.3.1/Classes/AFURLConnectionOperation.html