I am developing an application which retrieves its data from a plist located on my webserver. Therefore the application is dependent on a network connection. I want the user to be able to use my application when offline and therefore I am saving a copy of the plist on the users device every time the application is loaded with network connection. From there, I read the data from the plist located on the device.
I am having troubles, though. I am starting the download of the data in didFinishLaunchingWithOptions method of the AppDelegate. This is done like this:
if(hasConnection) { // returns TRUE or FALSE based on Apple's example on checking network reachability
NSLog(#"Starting download of data.");
// loading using NSURLConnection
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:FETCH_URL] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
receivedData = [[NSMutableData data] retain];
}
}
Then I add the data to my plist Bands.plist in connectionDidFinishLoading
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// throw data into a property list (plist)
NSMutableArray *tmpArray = [NSPropertyListSerialization propertyListFromData:receivedData mutabilityOption:NSPropertyListMutableContainers format:nil errorDescription:nil];
NSMutableArray *plistArray = [[NSMutableArray alloc] initWithArray:tmpArray];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"Bands.plist"];
[plistArray writeToFile:path atomically:YES];
[plistArray release];
// release the connection, and the data object
[connection release];
[receivedData release];
}
But when loading the application for the first time, it crashes. I believe this is due to the application trying to reach this data even though it is not yet saved. If I remove the part that tries to reach the data saved locally, I have no problem. Neither do I have a problem if I add it again and restart the application (second load).
Does anyone have an idea how to fix this issue?
It is as if my application tries to load and handle data which does not yet exist.
Simply try checking for the existence of the plist first:
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
... // read the data, etc.
}
else {
... // don't try to read it
}
Cheers,
Sascha
Related
1) I want to download the data of any kind like the files of type .text, .png, .jpg, .docx, .xls, .pdf, .mp4, or whatever be the kind of files, Then i want to save it to the application sandboxs document directorys any of the sub directories that i have created under document directory of application sandbox.
2) Again whenever the user want to upload the files saved in the subdirectories of the application sandboxs document directory, The user will be able to browse through the data in the different directories of application sandboxs document directory, For that i have listed the data in the subdirectories of document directory of application sandbox in UITableView so that the user should be able to choose any of the file from the particular directory.
Problems/ things where i have stucked
I am using ASIHttpRequest for the upload and download , Where
1) For first need , means for downloading data i am using the methods -(void)grabURLInBackground to download the data from web and if its downloaded successfully then in the method -(void)requestFinished:(ASIHTTPRequest *)request i am saving that data to the subdirectory of the document directory of application sandbox with the particular name. The working code is below
-(void)grabURLInBackground
{
NSURL *url = [NSURL URLWithString:#"http://wordpress.org/plugins/about/readme.txt"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
}
-(void)requestFinished:(ASIHTTPRequest *)request
{
// Use when fetching text data
NSString *responseString = [request responseString];
NSLog(#"responseString:%#",responseString);
UIAlertView *alt = [[UIAlertView alloc] initWithTitle:#"Download Status" message:#"Download finished" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alt show];
//Use when fetching binary data
//NSData *responseData = [request responseData];
//NSLog(#"responseData:%#",responseData);
//For storing the data to the subdirectory of the document directory named Doc the following code is used.
NSArray *paths;
NSString *documentsDirectory,*docDirectoryPath,*docFilePath;
//NSString *imageCachePath,*imageDicPath;
paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
documentsDirectory = [paths objectAtIndex:0]; // Get documents folder
NSLog(#"documentsDirectory:%#",documentsDirectory);
docDirectoryPath = [documentsDirectory stringByAppendingPathComponent:#"/Docs"];
NSLog(#"docDirectoryPath:%#",docDirectoryPath);
docFilePath = [docDirectoryPath stringByAppendingPathComponent:#"textFileTwo"];
NSLog(#"docFilePath:%#",docFilePath);
if (![[NSFileManager defaultManager] fileExistsAtPath:docFilePath])
[[NSFileManager defaultManager] createFileAtPath:docFilePath
contents:[NSData dataWithContentsOfFile:responseString]
attributes:nil];
//************************************//
Here what i want after the download finishes we have the two option the way to fetch the text data and the way to fetch the binary data, Thats what is the thing , Here in my case the data will be of any kind, And i want to save that to particular directory, I will save it on my own but i want the Unique way to fetch the any kind of data and to save it to particular directory .
//************************************//
}
-(void)requestFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
NSLog(#"error:%#",error);
}
2) For the 2nd need means for the uploading data to any URL m using the same ASIHttpRequest like
-(void)uploadData {
//Suppose i want to upload the file that i have juz downloaded by the download code above.
// i fetched the path of the file i just saved with download code above, See the code below.
NSArray *paths;
NSString *documentsDirectory,*docDirectoryPath,*docFilePath;
//NSString *imageCachePath,*imageDicPath;
paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
documentsDirectory = [paths objectAtIndex:0]; // Get documents folder
NSLog(#"documentsDirectory:%#",documentsDirectory);
docDirectoryPath = [documentsDirectory stringByAppendingPathComponent:#"/Docs"];
NSLog(#"docDirectoryPath:%#",docDirectoryPath);
docFilePath = [docDirectoryPath stringByAppendingPathComponent:#"textFileTwo"];
NSLog(#"docFilePath:%#",docFilePath);
// Upload Code
NSString *strURL = #"http://192.168.1.201/MyLegalNetMobile/MyLegalNetService.svc/FileUpload";
ASIFormDataRequest *uploadRequest = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:strURL]]; // Upload a file on disk
// Upload image data using asihttprequest
//UIImage *tempImg=[UIImage imageWithContentsOfFile:[NSString stringWithContentsOfURL:[NSURL URLWithString:imageCachePath] encoding:NSUTF8StringEncoding error:nil]];
//NSData *imageData1=UIImageJPEGRepresentation(tempImg, 1.0);
NSString *fetchedDataOfTxtFiles = [NSString stringWithContentsOfURL:[NSURL URLWithString:docFilePath] encoding:NSUTF8StringEncoding error:nil];
NSData *textData = [NSData dataWithContentsOfFile:fetchedDataOfTxtFiles];
NSLog(#"fetchedDataOfTxtFiles:%#",fetchedDataOfTxtFiles);
NSLog(#"textData:%#",textData);
[uploadRequest setData:textData withFileName:#"textFileTrialThree" andContentType:#"txt" forKey:#"txtData"];
[uploadRequest setRequestMethod:#"POST"];
[uploadRequest setDelegate:self];
[uploadRequest setTimeOutSeconds:10.0];
uploadRequest.shouldAttemptPersistentConnection = NO;
[uploadRequest setDidFinishSelector:#selector(uploadRequestFinished:)];
[uploadRequest setDidFailSelector:#selector(uploadRequestFailed:)];
[uploadRequest startAsynchronous];
//************************************//
Here again i have the different ways to upload the different kind of data, like for uploading the text data, different, ways is there same for the pdf, and image data is also, here i want the unique way to upload any kind of data to server, Also here I tried the image data uploading and text data uploading , Means i uploaded the files that i download from the any url. At the time of saving that downloaded files i converted them to NSData and saved to particular path of application sandboxs belonging directories. So while uploading again i got that path and for image data i converted the nsdata to uiimage , for the text file i only gave the path of file and uploaded the fiels to somewhere , The Files get uploaded on server, but there size was 0 bytes only, and the formate was different.
//************************************//
}
-(void)uploadRequestFinished:(ASIHTTPRequest *)request
{
NSString *responseString = [request responseString];
NSLog(#"Upload response %#", responseString);
}
-(void)uploadRequestFailed:(ASIHTTPRequest *)request{
NSLog(#" Error - Statistics file upload failed: \"%#\"",[[request error] localizedDescription]);
}
// Exact Problem.
/*
Any data that we download from the web using ASIHttpRequest before saving it to any path to application sandbox we convert some kind of data to NSData, And it get saved .
On the click of Browse button i have populated the data from different different subdirectories of the Document directory of the application sandbox in the UITableView, So I want to show the names of files with their extensions means with type that files were downloaded [as we save all data with converting to NSData it get saved with the names we give while saving only].
And then the time comes for the users to upload that data to any of the URL at that time also the files should get stored with their original formates means with which we downloaded the, */
To get list of files in directory try
- (NSArray *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError **)error
To get file extension take a look on responce headers. They can contain ContentType which was downloaded.
why not use the request property called downloadDestinationPath?? If you use it, you don´t need to do anything in RequestFinished method because the ASIHTTPRequest library keeps the type of the files what you have downloaded.
The request finished method is always for doing something with the data you have downloaded, as parsing an html file for remove the html headers. If you don´t want to modify the file that you are downloading you should use this method for show download status only.
Edit the download path before start the request:
NSArray *paths;
NSString *documentsDirectory,*docDirectoryPath,*docFilePath;
paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
documentsDirectory = [paths objectAtIndex:0];
docDirectoryPath = [documentsDirectory stringByAppendingPathComponent:#"Docs"];// Remove the "/" from the string paths because you are using "stringByAppendingPathComponent"
docFilePath = [docDirectoryPath stringByAppendingPathComponent:#"textFileTwo"];
request = [ASIHTTPRequest requestWithURL:YOUR URL];
[request setDownloadDestinationPath:docFilePath];
[request startAsynchronous];
To list the content:
NSArray *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error2];
for (int i = 0; i<[directoryContent count]; i++){
NSLog(#"content == %d", [directoryContent objectAtIndex:i];
}
What is the easiest way to write a JSON data/NSDictionary and read it back again? I know there's NSFileManager, but is there an open source framework library out there that would make this process easier? Does iOS5 NSJSONSerialization class supports writing the data to disk?
Yup, NSJSONSerialization is the way to go:
NSString *fileName = #"myJsonDict.dat"; // probably somewhere in 'Documents'
NSDictionary *dict = #{ #"key" : #"value" };
NSOutputStream *os = [[NSOutputStream alloc] initToFileAtPath:fileName append:NO];
[os open];
[NSJSONSerialization writeJSONObject:dict toStream:os options:0 error:nil];
[os close];
// reading back in...
NSInputStream *is = [[NSInputStream alloc] initWithFileAtPath:fileName];
[is open];
NSDictionary *readDict = [NSJSONSerialization JSONObjectWithStream:is options:0 error:nil];
[is close];
NSLog(#"%#", readDict);
It seems you must ensure that the directories in the path exist, otherwise your app will hang and consume 100% of your CPU when using + writeJSONObject:toStream:options:error:. The file itself will be created by the stream.
I am using the following lines of code to download and save an html page ::
NSURL *goo = [[NSURL alloc] initWithString:#"http://www.google.com"];
NSData *data = [[NSData alloc] initWithContentsOfURL:goo];
NSString *html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; //Remove the autorelease if using ARC
NSString *documentsDirectory = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
NSLog(#"%#", documentsDirectory);
NSString *htmlFilePath = [documentsDirectory stringByAppendingPathComponent:#"file.html"];
[html writeToFile:htmlFilePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
After downloading and saving it, I need to re-use it i.e. upload it. But, I am unable to download the css and image files alongwith the html page i.e. while re-uploading it .. I am not getting the images that should have been displayed on the google home page ..
Can someone help me sort out the issue ?? Thanks and Regards.
The data that is being downloaded is just what the web server returns - pure html. If you need the resources from inside - images/sounds/flash/css/javascripts/etc.. you have parse this html and download all other resources.. Your html may also contain the full path of those resources so you may need to change their urls to be relative (if you want to display it offline or upload it to another server). Parsing can be done with regular expressions or some other 3rd party parsers or libraries that can download the whole web page...
You may take a look at ASIWebPageRequest, which claims to be able to download a whole website, but I haven't tried this functionality...
Use of ASIWebPageRequest will solve problem :
- (void)downloadHtml:(NSURL *)url
{
// Assume request is a property of our controller
// First, we'll cancel any in-progress page load
[[self request] setDelegate:nil];
[[self request] cancel];
[self setRequest:[ASIWebPageRequest requestWithURL:url]];
[[self request] setDelegate:self];
[[self request] setDidFailSelector:#selector(webPageFetchFailed:)];
[[self request] setDidFinishSelector:#selector(webPageFetchSucceeded:)];
// Tell the request to embed external resources directly in the page
[[self request] setUrlReplacementMode:ASIReplaceExternalResourcesWithData];
// It is strongly recommended you use a download cache with ASIWebPageRequest
// When using a cache, external resources are automatically stored in the cache
// and can be pulled from the cache on subsequent page loads
[[self request] setDownloadCache:[ASIDownloadCache sharedCache]];
// Ask the download cache for a place to store the cached data
// This is the most efficient way for an ASIWebPageRequest to store a web page
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
[[self request] setDownloadDestinationPath:documentsDirectory] // downloaded path
//[[ASIDownloadCache sharedCache] pathToStoreCachedResponseDataForRequest:[self request]]]; use this instead of documentsDirectory if u want to cache the page
[[self request] startAsynchronous];
}
//These are delegates methods:
- (void)webPageFetchFailed:(ASIHTTPRequest *)theRequest
{
// Obviously you should handle the error properly...
NSLog(#"%#",[theRequest error]);
}
- (void)webPageFetchSucceeded:(ASIHTTPRequest *)theRequest
{
NSString *response = [NSString stringWithContentsOfFile:
[theRequest downloadDestinationPath] encoding:[theRequest responseEncoding] error:nil];
// Note we're setting the baseURL to the url of the page we downloaded. This is important!
[webView loadHTMLString:response baseURL:[request url]];
}
- (void)viewDidLoad {
/// js=yourHtmlSring;
NSString *js; (.h)
[self.myWebView loadHTMLString:js baseURL:nil];
}
//delegate
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[myWebView stringByEvaluatingJavaScriptFromString:js];
}`
Hey I don't think you can download all the files from google just try with any other url . And you can directly write the NSData to your file htmlFilePath.
[data writeToFile:htmlFilePath atomically:YES];
I need to parse a XML file which is located on my server and return the node values without ever downloading the file to the device. Right now, the file is downloading to the device for testing purposes. Here is my current code:
+ (NSString *)dataFilePath:(BOOL)forSave {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *documentsPath = [documentsDirectory stringByAppendingPathComponent:#"file.xml"];
return documentsPath;
}
+ (NSString *)parse:(NSString *)nodesForPath:(NSString *)elementsForName {
NSString *filePath = [self dataFilePath:FALSE];
NSData *xmlData = [[NSMutableData alloc] initWithContentsOfFile:filePath];
NSError *error;
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&error];
if (doc == nil) { return nil; }
ViewController *view = [[[ViewController alloc] init] autorelease];
NSArray *getVersionInfo = [doc nodesForXPath:nodesForPath error:nil];
for (GDataXMLElement *versionInfo in getVersionInfo) {
NSArray *elm1 = [versionInfo elementsForName:elementsForName];
GDataXMLElement *elm2 = (GDataXMLElement *) [elm1 objectAtIndex:0];
return elm2.stringValue;
}
[doc release];
[xmlData release];
}
It works fine but it parses the file in the document's directory. How would I set it up to parse directly from the web server? Thanks
The only way I can see to do this would be to run a program on the server to parse the file and return the result to the phone.
For the phone to run the code that parses the file it has to read the file which, by definition, will download it.
You could try using a pull parser for the XML and just work with the data received from the network as a stream, discarding unneeded contents.
You read a few bytes from the response, parse them, process, discard, read some more. You might have to make your own implementation of a pull parser because your dealing with incomplete, hence invalid, XML or to look up a parser that's tolerant to data ending (definitely not a validating one).
It might be worth-while to make your own implementation for the parser if you are dealing with really huge XML files. This way you can only store pieces of the XML that you need (you might need part of the XML 'tree' or none at all or you might need to retain other nodes referenced by another node in your XML).
I'm sorry for the generic answer, but I haven't used XML parsers on iOS yet and I'm not anywhere near a Mac atm to be able to test if NSXMLParser works for you.
Edit: Related post here.
Even if you don't want to save the file to your hard disk, you must have the contents of that file loaded on your RAM.
Convert NSData* to NSString, and then NSString to char* and give that to RapidXML.
I'm creating an app which downloads a .zip file from S3 server.
All works fine. Now I want to be able to interrupt the current download. If I could save the current size (bytes) of the file, I would be able to send a new request with a range header for the other part of the file.
Problem lies in the fact that I cannot determine the size of the 'already' downloaded content, because I can only see the file in my directory when the download is completed. So if I interrupt, there isn't a partial file saved.
At this time I use the following code for this:
-(void) downloadFile:(NSMutableArray*)paramArray withDict:(NSMutableDictionary*)options
{
NSLog(#"DOWNLOAD THREAD STARTED");
NSString * sourceUrl = [paramArray objectAtIndex:0];
NSString * fileName = [paramArray objectAtIndex:1];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *newFilePath = [documentsDirectory stringByAppendingString:fileName];
NSError *error=[[[NSError alloc]init] autorelease];
NSURLConnection *fileURL = [NSData dataWithContentsOfURL: [NSURL URLWithString:sourceUrl]];
BOOL response = [fileURL writeToFile:newFilePath options:NSDataWritingFileProtectionNone error:&error];
if (response == TRUE)
{
NSLog(#"DOWNLOAD COMPLETED");
[self performSelectorOnMainThread:#selector(downloadComplete:withDict:) withObject:paramArray waitUntilDone:YES];
}
else
{
NSLog(#"Something went wrong while downloading file.");
NSString *callback = [NSString stringWithFormat:#"downloadInterrupted('%#');",fileName];
[webView stringByEvaluatingJavaScriptFromString:callback];
}
[pool drain];
}
AsiHTTP isn't an option because there are issues with the PhoneGap I'm using.
A better idea is to download the file asynchronously. This has several advantages: The most important one is that your user interface stays responsive. The user can go on using your application while it is downloading and waiting for the data. If the data you are downloading is absolutely essential for the application, display some sort of loading indicator.
You can easily start the asynchronous download via
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:sourceUrl]];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
Now, how do I get the downloades data in an NSData object? You implement the following delegate methods for self:
-connection:didReceiveData:
-connection:didFailWithError:
-connectionDidFinishLoading:
The idea is that you are notified whenever some data drops in through your connection or anything important else happens (success or failure for exmple). So you are going to declare a temporary NSMutableData object as an instance variable (say downloadData) and write to it until the download is complete. Do not forget to initialize the empty object and declare a property as well!
-connection:didReceiveData: is called whenever some sort of data (that is, a part of your downloaded file) arrives. So you are going to append it to your temporary object like this:
-(void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.downloadData appendData:data];
}
Once the download has finished (successfully), the next delegate method is called:
-(void) connectionDidFinishLoading:(NSURLConnection *)connection {
//do whatever you need to do with the data in self.downloadData
}
If the downloads fails, -connection:didFailWithError: is called. You can then save the temporary object, get its size and resume the download later. [self.downloadData length]; gets you the size in bytes of the data in your object.
You are going to have to use a lower level api.
time to read up on unix socket programming. http://www.fortunecity.com/skyscraper/arpanet/6/cc.htm would be a good start.
It really won't be too hard. honest.
I recommend you to build a method that save data chunk every 1, 2 MB or maybe less in order to resume properly your download and avoid memory crash.
This because if you get an error in your transfer maybe your file could be result corrupted.
Anyway send a range HTML header is pretty simple
NSFileHandle *fileHandler = [NSFileHandle fileHandleForReadingAtPath:dataPreviouslySavedPath];
[fileHandler seekToEndOfFile];
unsigned long long int range = [fileHandler offsetInFile];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:downloadURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
[request setValue:[NSString stringWithFormat:#"bytes=%lli-", range] forHTTPHeaderField:#"Range"];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
Hope this help you.