how can I download a Cocoa .bundle file from a server and then load it into a app? I've tried using a zip but the shouldDecodeSourceDataOfMIMEType function doesn't get called.
- (IBAction)testDownload:(id)sender {
// Create the request.
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://localhost/SampleBundle.zip"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// Create the connection with the request and start loading the data.
NSURLDownload *theDownload = [[NSURLDownload alloc] initWithRequest:theRequest
delegate:self];
if (theDownload) {
// Set the destination file.
[theDownload setDestination:#"/Users/developer/Desktop/Test-Downloads/SampleBundle" allowOverwrite:YES];
} else {
// inform the user that the download failed.
}
}
- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
{
download = nil;
// Inform the user.
NSLog(#"Download failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)downloadDidFinish:(NSURLDownload *)download
{
download = nil;
// Do something with the data.
NSLog(#"%#",#"downloadDidFinish");
}
- (BOOL)download:(NSURLDownload *)download shouldDecodeSourceDataOfMIMEType:(NSString *)encodingType;
{
BOOL shouldDecode = YES;
NSLog(#"EncodingType: %#",encodingType);
return shouldDecode;
}
So how can I download a .bundle from a server uncompress it and load it into the application?
Thanks
According to the documentation of download:shouldDecodeSourceDataOfMIMEType:
This method is not called if the downloaded file is not encoded.
So, I'm guessing that might have something to do with it. You're probably better off implementing download:didReceiveResponse: and examining the NSURLResponse object, especially the status code -- if that's not 200 than something is going wrong and you need to look at HTTP codes to see what exactly the problem is.
Also, I'm not sure of this, do you need elevated permissions to install a bundle, it being an executable?
The shouldDecodeSourceDataOfMIMEType delegate works great but only on gzip (.gz) archives. I've tested extensively with both .zip & .gz.
It should also be noted that it doesn't call tar, so if you applied compression and tar at the same time as in:
tar czvf ArchiveName.tar.gz ./ArchiveName/
the shouldDecodeSourceDataOfMIMEType delegate will leave you with:
ArchiveName.tar
So, the archive will not be immediately useable.
For .zip archives, as others have pointed out, your best bet is MiniZip (C API) or a Objective-C framework based on it like ZipArchive (2005) or the more recent SSZipArchive (2013).
Related
NSURLConnection can be used to calculate the md5 on-the-fly:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData
// theData is a small piece
NSURLSessionDownloadTask is an "upgrade" of NSURLConnection. But how can we check the md5 without read through the whole file again after it is downloaded? Its interface is like:
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request
completionHandler:
^(NSURL *location, NSURLResponse *response, NSError *error) {
// the whole file is downloaded and saved at location.
}];
The key requirement here is low memory footprint and the file has to be downloaded completely.
If you want the data to arrive in small NSData pieces that you can examine and append to a larger NSMutableData bit by bit, as you did with connection:didReceiveData:, ask for a data task instead of a download task.
You call dataTaskWithRequest:, supply a delegate, and start the data task (with resume) - and the delegate receives URLSession:dataTask:didReceiveData:, exactly like in the old NSURLConnection days.
Here's a complete working example (except that I don't tell you what to do with the bits of data as they arrive):
- (NSURLSession*) configureSession {
NSURLSessionConfiguration* config =
[NSURLSessionConfiguration ephemeralSessionConfiguration];
config.allowsCellularAccess = NO;
NSURLSession* session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
return session;
}
- (IBAction) doHTTP: (id) sender {
if (!self.session)
self.session = [self configureSession];
NSString* s = // some URL string
NSURL* url = [NSURL URLWithString:s];
NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url];
NSURLSessionDataTask* task = [[self session] dataTaskWithRequest:req];
self.task = task;
[task resume];
}
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
NSLog(#"received %lu bytes of data", (unsigned long)data.length);
// do something with the data here!
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
NSLog(#"completed; error: %#", error);
}
As Matt said, you can use a data task, which lets you easily see the data as it is downloaded.
However, if you want to observe a download task, you can accomplish a similar thing if you are willing to take a few small risks.
I'm sure I'll get a million down votes for the following, but just remember... when you need a screwdriver, and all you have is a hammer, you flip the hammer over, and use it as a screwdriver... or bang on the screw so much that it turns into a nail...
First, I think the API is broken. The delegates should provide at least one of these two things. If you agree, file a radar. The delegate should provide the temporary file (much less preferred - I think it should remain opaque) or it should provide the NSData that is being written in URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite: -- this is the right answer.
Anyway, if you are willing to use an undocumented, unofficial approach...
The temporary files are stored in Library/Caches/com.apple.nsnetworkd/ so you can easily look in there and determine which files are being used as the temporary destination.
Or, you can, again unofficially, determine the temporary file by canceling the download with cancelByProducingResumeData: and then unarchiving the resume data blob -- the resume data blob is currently an archived dictionary -- and get the file path from the dictionary. Then, you can resume the download, knowing which temporary file is being used for the download.
Anyway, once you have the file, inside your URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite: you can then just read the most recently written chunk from the file.
Now, having said that, you may want to just use a data task, because it will officially provide you the piece of data it just downloaded... but you can resort to this hack to get at the data that was downloaded to the file, if you must do a background download -- which must be down with a download task.
One problem you may have is that the file IO may be buffered, so what's been actually flushed (and available form a separate file descriptor) may be different from what has been reported in the delegate method. You may just need to keep track of the last byte you read, and inside that delegate, just read from there to the current end of the file...
Your mileage will most certainly vary, but it will give you access to the data as it is being written to the file.
You will have to do the same thing for URLSession:downloadTask:didFinishDownloadingToURL: to get the final piece of data.
Is there any delegate method that will be called when the user upgrades to or reinstalls a newer version of the iOS app?
I use Core Data to cache some information from server. When the schema of any entity is changed, I need to manually delete the SQLite database from the simulator, otherwise the app will crash on startup, with an error "The model used to open the store is incompatible with the one used to create the store." If there is any delegate method for app upgrade, the deletion could be automated.
You need to use CoreData versioning:
http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/CoreDataVersioning/Articles/Introduction.html
Daniel Smith's answer is the proper one, but I just want to add how my app determines its been updated. I look keep a 'current version' string in the defaults. When the app starts up, I compare it to the current version:
defaults has no string - this is the first run of the app
defaults version is different - the user updated the app
defaults is the same - user just restarted the app
Sometimes its nice to know the above. Make sure to save the defaults immediately after you set the tag and do whatever versioning you want, so a crash doesn't have you do it again.
EDIT: how not to crash if he model changes. I use this now, keep the old repository, and tweaking the model, on every tweak it just removes the old one (if it cannot open it) and creates a new one. This is modeled on Apple's code but not sure about what changes I made. In any case you don't get a crash if the model changes.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
//LTLog(#"_persistentStoreCoordinator = %#", _persistentStoreCoordinator);
if (_persistentStoreCoordinator)
{
return _persistentStoreCoordinator;
}
NSFileManager *manager = [NSFileManager defaultManager];
NSString *path = [[appDelegate applicationAppSupportDirectory] stringByAppendingPathComponent:[_dbName stringByAppendingPathExtension:#"SQLite"]];
storeURL = [NSURL fileURLWithPath:path];
BOOL fileExists = [manager fileExistsAtPath:path];
if(!fileExists) {
_didCreateNewRepository = YES;
}
if(_createNewRepository) {
[manager removeItemAtURL:storeURL error:nil];
if(fileExists) _didDestroyOldRepository = YES;
_didCreateNewRepository = YES;
}
while(YES) {
__autoreleasing NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if ([_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
break;
} else {
_persistentStoreCoordinator = nil;
[manager removeItemAtURL:storeURL error:&error];
if(fileExists) {
_didDestroyOldRepository = YES; // caller didn't want a new one but got a new one anyway (old one corrupt???)
_didCreateNewRepository = YES;
}
#ifndef NDEBUG
LTLog(#"CORE DATA failed to open store %#: error=%#", _dbName, error);
#endif
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
Typical reasons for an error here include:
* The persistent store is not accessible
* The schema for the persistent store is incompatible with current managed object model
Check the error message to determine what the actual problem was.
*/
//LTLog(#"Unresolved error %#, %#", error, [error userInfo]);
//abort();
}
}
return _persistentStoreCoordinator;
}
Follow the blog its good:
http://blog.10to1.be/cocoa/2011/11/28/core-data-versioning/
I am trying to download a file using NDURL Download. For that, I have to log in to a site.
I do this using a NSMutableURLRequest that I send using sendSynchronousRequest of NSURLConnection
The data that I receive from that message call is indeed the html page confirming my successful login.
To download the file I use the following code:
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString: #"http://www.domain.com/getfile.php?file=1"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// Create the connection with the request and start loading the data.
NSURLDownload *theDownload = [[NSURLDownload alloc] initWithRequest:theRequest
delegate:self];
if (theDownload) {
// Set the destination file.
NSLog(#"Starting Download...");
NSLog(#"%#", [theDownload description]);
[theDownload setDestination:destinationFilename allowOverwrite:YES];
pathToZipFile = destinationFilename;
} else {
NSLog(#"Download failed...");
return nil;
}
But the data I receive is the HTML page telling me I have to be logged in to download the file.
Any idea on this one?
Does the NSURLDownload have an different session than the NSURLConnection?
Thanks in advance!
Okey, so you have logged in and then you trying to download a file. But how the server knows you are the same user that has logged in before?
There are different ways how it can know it. Some cookie, some request parameter, some HTTP header. But you have to add something to the request, that says "I am the user that has logged in a minute ago".
I feel you have to implement delegates for NSURLDownload, like this :
- (void)downloadDidBegin:(NSURLDownload *)download{
}
- (void)download:(NSURLDownload *)download didReceiveResponse:(NSURLResponse *)response{
_totalLength = response.expectedContentLength;
}
- (void)download:(NSURLDownload *)download willResumeWithResponse:(NSURLResponse *)response fromByte:(long long)startingByte{
}
- (void)download:(NSURLDownload *)download didReceiveDataOfLength:(NSUInteger)length{
_recievedLength = _recievedLength + length;
}
- (void)downloadDidFinish:(NSURLDownload *)download{
//Completed//
}
- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error{
//Error
}
I've recently started trying to implement HTTP upload support to a program, but I've been having some difficulty doing so. This is the first time I've ever used objective-c (although I have a C background), so I'm still very new to the language. I've been trying to get it to work using the HTTPRequest library, but haven't been able to get anything to start working. It's a fairly large program (2500~ lines) so I won't paste it here. I'll just paste the function itself here.
- (void)Upload2HTTP2:(NSString *)ZipPath
{
[self UpdateProgressBar:#"Upload2HTTP opening Connection to Website..."];
NSLog(#"Upload2HTTP called\n");
//URL to be used to upload
NSURL *url = [NSURL URLWithString:#"http://ftp.website.com"];
NSLog(#"Upload2HTTP -%#\n",url);
//Creates the new ASIFormDataRequest object to do the uploading
//Uses the ASIHTTPRequest and ASIFormDataRequest libraries
// http://allseeing-i.com/ASIHTTPRequest/ for more information
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request addRequestHeader:#"Referer" value:#"http://ftp.website.com"];
//Sets the authentication information
//This should have already been retrieved in RetrieveFromBrowser
[request setUsername:RespUID];
[request setPassword:RespPWD];
//Sets the file to be uploaded
[request setFile:ZipPath forKey:#"Customer Upload"];
//Starts the transfer?
[request startAsynchronous];
}
ZipPath, RespUID, and RespPWD are all set in another area of the program. Basically, I've got the username/PW for the HTTP authentication, and the path to the file I want to upload, but I've very little experience with the language and this library, so I'm a bit lost. I can't give any specific errors or reasons as to why it hangs, I just know that after I click upload in the program, it runs through this function, and the program hangs trying to upload the file. Is there anything I'm missing or doing wrong in this function? I'd really appreciate any help you guys could lend.
Thanks :)
ASIHTTPRequest's asynchronous networking takes advantage of the delegate design pattern. Setting the request's delegate property to self, having that class adhere to the ASIHTTPRequestDelegate protocol, and implementing - (void)requestDidFinish: and - (void)requestdidFail: should give you callbacks for finish and failure. Quick example:
- (void)Upload2HTTP2:(NSString *)ZipPath
{
...
request.delegate = self;
[request startAsynchronous];
}
- (void)requestDidFinish:(ASIHTTPRequest *)request
{
NSLog(#"Success! Do stuff here with [request responseData] or [request responseString]");
}
- (void)requestDidFail:(ASIHTTPRequest *)request
{
NSLog(#"Failure! Check out [request error] for details");
}
I have a Cocoa Mac application set up to download files to a specific folder using NSURLDownload. This works great with a single download at a time. However, if I attempt to start multiple downloads, all but the last will fail immediately.
Is there any way to use NSURLDownload for multiple simultaneous downloads? Or what would be a good way to queue up multiple URLs to be downloaded in order? Or is there a more appropriate way to accomplish this (NSURLConnection seemed possible but I was unsure if I could set the download location and filename as I can with NSURLDownload)?
Each NSURLDownload represents a single downloading instance. You're probably trying to reuse the same one multiple times. It's an inherently asynchronous system that already used background threads. Here's an example based on Apple's sample code:
- (void)startDownloadingURL:sender
{
// Create a couple requests.
NSURLRequest *requestOne = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.apple.com"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSURLRequest *requestTwo = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://stackoverflow.com"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// Create two download instances
NSURLDownload *downloadOne = [[NSURLDownload alloc] initWithRequest:requestOne delegate:self];
NSURLDownload *downloadTwo = [[NSURLDownload alloc] initWithRequest:requestTwo delegate:self];
if (downloadOne) {
// Set the destination file.
[downloadOne setDestination:#"/tmp" allowOverwrite:YES];
} else {
// inform the user that the download failed.
}
if (downloadTwo) {
// Set the destination file.
[downloadTwo setDestination:#"/tmp" allowOverwrite:YES];
} else {
// inform the user that the download failed.
}
}
- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
{
// Release the connection.
[download release];
// Inform the user.
NSLog(#"Download failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSErrorFailingURLStringErrorKey]);
}
- (void)downloadDidFinish:(NSURLDownload *)download
{
NSLog(#"The download %# has finished.", download)
// Release the download connection.
[download release];
}
If you attempt to use the same NSURLDownload for both NSURLRequests, then it will kill the previous connection.
I'd second using NSOperation if your on 10.5+ or greater. You could just throw 1 operation onto a queue for each download. Or you could even just use sendSynchronous request and use it with NSOperationQUeue's addOperationWithBlock (10.6+) method and then in your block you are throwing onto the queue you can just use [[NSOperationQueue mainQueue] addOperationWithBlock:^{ when you are doe with the code you need to execute or just periodically need to refresh the UI on the main thread, like so...
[myQueue addOperationWithBlock:^{
//download stuff here...
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//update main thread UI
}];
}];
you would just need to do this for each download.
If you're targeting 10.5+, you should look at NSOperation. It should allow you to build a generic solution for a single download, and then use the built-in queue facilities to manage dependencies if you require certain operations finish downloading before others begin.
Keep in mind that these APIs often expect delegate methods involved to be run on the main thread, so you'll need ensure that occurs if you're working with asynchronous APIs that function via delegate methods. (You can do this pretty simply by using performSelectorOnMainThread: and friends)