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
Related
I am trying to read text file downloaded from my server.
-(void)downloadFile
{
NSURLRequest request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://myserver.com/website/file.txt"]];
AFURLConnectionOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *filePath = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"file.txt"];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:filePath append:NO];
[operation setCompletionBlock:^{
NSLog(#"downloadComplete!");
NSString *path;
path = [[NSBundle mainBundle] pathForResource: #"file" ofType: #"txt"];
NSString *data = [self readFile: path];
NSLog(#"%#",data);
}];
[operation start];
}
-(NSString *)readFile:(NSString *)fileName
{
NSLog(#"readFile");
NSString *appFile = fileName;
NSFileManager *fileManager=[NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:appFile])
{
NSError *error= NULL;
NSString *resultData = [NSString stringWithContentsOfFile: appFile encoding: NSUTF8StringEncoding error: &error];
if (error == NULL)
return resultData;
}
return NULL;
}
It downloads the file successfully but I can't read file. Returns null. Probably I can't set file path correctly. I want to read file from device disk, not project bundle.
Change
path = [[NSBundle mainBundle] pathForResource: #"file" ofType: #"txt"];
to
path = filePath;
I made your stuff work, i dont know if you still need it
-(void)downloadFile
{
CNMRouter *routext;
routext = ((LDAppDelegate *)[UIApplication sharedApplication].delegate).router;
[[[Singleton sharedClient]httpClient] setParameterEncoding:AFFormURLParameterEncoding];
NSMutableURLRequest *request;
if([routext postOrGet]){
request = [[[Singleton sharedClient]httpClient] requestWithMethod:#"GET"
path:[routext getLoginURLPath:#"admin" andPassword:#"admin"]
parameters:nil];
}else{
request = [[[Singleton sharedClient]httpClient] requestWithMethod:#"POST"
path:[routext getBackupUrl]
parameters:[routext getBackupURLPath]];
}
// NSURLRequest request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://myserver.com/website/file.txt"]];
AFURLConnectionOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlock:^{
NSLog(#"downloadComplete!");
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSLog(#"%#", [paths objectAtIndex:0]);
NSString *filePath = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"file.txt"];
// operation.outputStream = [NSOutputStream outputStreamToFileAtPath:filePath append:NO];
NSString *path;
// path = [[NSBundle mainBundle] pathForResource: #"file" ofType: #"txt"];
path = filePath;
NSString *data = [self readFile: path];
NSLog(#"%#",data);
}];
[operation start];
}
I moved some stuf from its place but basicaly you were trying to get your file before it was even downloaded so i just put some stuff inside the succes block. Its working for me, thank you!
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);
}
}
}];
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.
i'm using this code to upload a file to the server , the stream is not detected on the server , the problem is that i use the same code but in a different project and it works perfectly ! I've included the appropriate headers ! what is going on ? i've spent the last couple of hours working on this problem please help
-(void)upload
{
NSString *st = [[NSBundle mainBundle] pathForResource:#"R" ofType:#"caf"];
NSData *d = [NSData dataWithContentsOfFile:st];
AFHTTPClient *client= [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:#"http://192.168.0.116:8084/RasaylServer/"]];
NSString *action = #"SendStreamMessage";
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
[parameters setObject:#"209" forKey:#"param1"];
[parameters setObject:#"205" forKey:#"param2"];
[parameters setObject:#"01/12/2012 15:13" forKey:#"param3"];
NSMutableURLRequest *myRequest = [client multipartFormRequestWithMethod:#"POST" path:action parameters:parameters constructingBodyWithBlock: ^(id <AFMultipartFormData>formData)
{
[formData appendPartWithFileData:d name:#"a" fileName:#"Test2" mimeType:#"audio/x-caf"];
}];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:myRequest];
[operation setCompletionBlock:^(){
} ];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
[operation start];
operation.completionBlock = ^{
if ( operation.error == nil )
NSLog(#"%#", operation.responseString);
else {
NSLog(#"%#", operation.error);
}
};
}
I have a game where there are different categories to play with. These categories are basically plist files. So, if in the future, I want to add plists, the user should be able to download a plist and play new levels. So what I am aiming for is:
Download .plist from URL
Save .plist file in documents directory on iphone (forever)
use .plist
Here is what I did and it works (console logs "finished !!"), but if I check whether the file is in the documents directory, there is no response (the boolean fileExists stays NO).
-(void)startGame:(id)sender{
NSArray *categoriesArray = [[GameStateSingleton sharedMySingleton] categoriesArray];
NSString *category = [NSString stringWithFormat:[categoriesArray objectAtIndex:[sender tag]]];
NSString *thePath = [mainPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.plist",category]];
NSDictionary *categoryPlist = [NSDictionary dictionaryWithContentsOfFile:thePath];
if(categoryPlist != nil){
[[GameStateSingleton sharedMySingleton]setCurrentCategory:category];
[[GameStateSingleton sharedMySingleton]updateHexCountAndInitalTime];
[[CCDirector sharedDirector]replaceScene:[CCTransitionFade transitionWithDuration:TRANSITIONDURATION scene:[LevelSets scene]]];
}
else{
[self plistForCategory:category];
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *myCategory= [documentsPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.plist",category]];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:myCategory];
if(fileExists == YES){
[[GameStateSingleton sharedMySingleton]setCurrentCategory:category];
[[GameStateSingleton sharedMySingleton]updateHexCountAndInitalTime];
[[CCDirector sharedDirector]replaceScene:[CCTransitionFade transitionWithDuration:TRANSITIONDURATION scene:[LevelSets scene]]];
NSLog(#"category exists now");
}
}
}
-(void)plistForCategory:(NSString*)category
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSURL *url = [NSURL URLWithString:[ NSString stringWithFormat:#"http://www.tinycles.com/wannaplay/plists/%#.plist",category]];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDownloadDestinationPath:documentsDirectory];
[request setDelegate:self];
[request startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
NSLog(#"finished !!");
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
// Download failed. This is why.
NSError *error = [request error];
NSLog(#"%#", error);
}
Maybe the problem is because you're starting an async request, so if the request has not finished, your if(fileExist==YES) still returns NO; i suggest you in this case to start a sync request:
- (IBAction)startRequest:(id)sender
{
NSURL *url = [NSURL URLWithString:LINK_TO_PLIST];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDownloadDestinationPath:DOC_PATH];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
//start your game after the file is downloaded
}
}