Use NSURLSessionDownloadTask to download only part of a file - ios

I am trying to use NSURLSessionDownloadTask to download a large file. However, I only want to download the first 10MB of that file. I know how to cancel the operation once 10MB has been downloaded, but how do I force a call on didFinishDownloadingToURL in order to retrieve the location of the saved file?
-(void) downloadFileWithProgress
{
NSURL * url = [NSURL URLWithString:#"https://s3.amazonaws.com/hayageek/downloads/SimpleBackgroundFetch.zip"];
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate:self delegateQueue: [NSOperationQueue mainQueue]];
NSURLSessionDownloadTask * downloadTask =[ defaultSession downloadTaskWithURL:url];
[downloadTask resume];
}
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
    NSLog(#"Temporary File :%#\n", location);
    NSError *err = nil;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
 
    NSURL *docsDirURL = [NSURL fileURLWithPath:[docsDir stringByAppendingPathComponent:#"out1.zip"]];
    if ([fileManager moveItemAtURL:location
                             toURL:docsDirURL
                             error: &err])
    {
        NSLog(#"File is saved to =%#",docsDir);
    }
    else
    {
        NSLog(#"failed to move: %#",[err userInfo]);
    }
 
}
 
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    //You can get progress here
    NSLog(#"Received: %lld bytes (Downloaded: %lld bytes)  Expected: %lld bytes.\n",
          bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}

If the server supports it the optimal solution would be to use HTTP range requests instead of messing with NSURLSessionDownloadTask.
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.2

Related

don't download video again if I close the app and open again if it's already downloaded before iOS

I am developing an app where I need to download a video and then perform some editing on it.So, I am downloading a video using NSURLSession and when I Have downloaded it,I close the app and then open it again.Now instead of using that downloaded video,I need to check if its already downloaded and then get the URL.Here is the code that I have been using:
-(void)startDownloadingWithURLString:(NSString*)urlString{
NSURL *url = [NSURL URLWithString:urlString];
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
self.urlSession = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
task = [self.urlSession downloadTaskWithURL:url];
[task resume];
}
#pragma mark - NSURLSession delagate methods
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSFileManager *fileManager_ = [NSFileManager defaultManager];
NSURL *url = [NSURL URLWithString:[path stringByAppendingPathComponent:#"video.mp4"]];
if([fileManager_ fileExistsAtPath:[location path]]){
[fileManager_ replaceItemAtURL:url withItemAtURL:location backupItemName:nil options:NSFileManagerItemReplacementUsingNewMetadataOnly resultingItemURL:nil error:nil];
_videoURLPath = url;
}
UISaveVideoAtPathToSavedPhotosAlbum([url path
], nil, nil, nil);
dispatch_async(dispatch_get_main_queue(), ^{
self.progressView_.progress = 0.0;
});
}
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
NSLog(#"%f",(double)totalBytesWritten/(double)totalBytesExpectedToWrite);
dispatch_async(dispatch_get_main_queue(), ^{
self.progressView_.progress = (double)totalBytesWritten/(double)totalBytesExpectedToWrite;
});
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
[KSToastView ks_showToast:#"Download complete"];
NSData *data = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:_videoURLPath.path]];
[AppHelper saveToUserDefaults:_videoURLPath.path withKey:#"videoURL"];
dispatch_async(dispatch_get_main_queue(), ^{
SAEditUploadViewController *editVC = [self.storyboard instantiateViewControllerWithIdentifier:#"SAEditUploadViewController"];
editVC.videoPathData = data;
[self.navigationController pushViewController:editVC animated:YES];
});
}

iOS NSURLSession Download

I got this code to implement something which helps me downloading a file from a given URL.
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
NSLog(#"Temporary File :%#\n", location);
NSError *err = nil;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSURL *docsDirURL = [NSURL fileURLWithPath:[docsDir stringByAppendingPathComponent:#"out1.zip"]];
if ([fileManager moveItemAtURL:location
toURL:docsDirURL
error: &err])
{
NSLog(#"File is saved to =%#",docsDir);
}
else
{
NSLog(#"failed to move: %#",[err userInfo]);
}
}
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
//You can get progress here
NSLog(#"Received: %lld bytes (Downloaded: %lld bytes) Expected: %lld bytes.\n",
bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}
Second part:
-(void) downloadFileWithProgress
{
NSURL * url = [NSURL URLWithString:#"https://s3.amazonaws.com/hayageek/downloads/SimpleBackgroundFetch.zip"];
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate:self delegateQueue: [NSOperationQueue mainQueue]];
NSURLSessionDownloadTask * downloadTask =[ defaultSession downloadTaskWithURL:url];
[downloadTask resume];
}
All of this code is in my Download.m
My download.h is:
#interface Download : NSObject
-(void) downloadFileWithProgress
#end
I really dont know how to get the download starting. In another class I created a button which should start the download:
-(IBAction)buttonStartDownload:(id)sender {
[Download downloadFileWithProgress];
}
The error is in the last line:
No known class method for selector 'downloadFileWithProgress'
But why?
The method '-(void) downloadFileWithProgress' is an instance method so you cant call this method by using class name 'Download'.
In order to call this method you need to create an instance of 'Download' class and call the method on that instance.
Method -(void)downloadFilwWithProgress in instance method...So to call that method
-(IBAction)buttonStartDownload:(id)sender {
Download *downldObj=[[Download alloc]init];
[downldObj downloadFileWithProgress];
}
If you write method +(void)downloadFilwWithProgress then you can call like this.[Download downloadFileWithProgress]

NSInputStream with url coming up nil in iOS

I'm trying to set up a NSInputStream, but my input stream is comes out as nil when I step into the code. The url comes from a Dropbox account.
Getting the file through NSData after I have the url through Dropbox Chooser crashes my iPhone 4 (although not when it is running through XCode). The files are just too big, so I wanted to try NSInputStream.
I saw from I cannot initialize a NSInputStream that the url is supposed to be local. Any idea how to stream a file from Dropbox?
Thanks.
- (void)setUpStreamForFile {
// iStream is NSInputStream instance variable already declared
iStream = [NSInputStream inputStreamWithURL:url];
[iStream setDelegate:self];
[iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[iStream open];
}
Hey don't hesitate to use AFNetworking it is a good framework to manipulate your connections and download content. This is an example to download a file from an URL:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:#"http://example.com/download.zip"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(#"File downloaded to: %#", filePath);
}];
[downloadTask resume];
For more information you can check the official information HERE
so thanks to rmaddy's suggestion, I looked up NSURLConnection but decided to use the features of NSURLSession instead.
I used the NSURLSessionDownloadTask like this. Familiarity with the Dropbox chooser should help.
-(IBAction)didPressChooser:(id)sender {
{
[[DBChooser defaultChooser] openChooserForLinkType:DBChooserLinkTypeDirect
fromViewController:self completion:^(NSArray *results)
{
if ([results count]) {
DBChooserResult *_result = results[0];
NSString *extension = [_result.name pathExtension];
if ([extension isEqualToString:#"m4a"]) {
url = _result.link; //url has already been declared elsewhere
DBFileName = _result.name; //DPFileName has also been declared. It's a string
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate:self delegateQueue: [NSOperationQueue mainQueue]];
NSURLSessionDownloadTask *getFile = [session downloadTaskWithURL:url];
[getFile resume];
}
} else {
// User canceled the action
}
}];
}
}
Once you have that, you put in another method that works as the completion handler.
-(void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); //I put the file in a temporary folder here so it doesn't take up too much room.
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [NSString stringWithFormat:#"%#/%#", documentsDirectory, DBFileName];
NSData *data = [NSData dataWithContentsOfURL:location];
[data writeToFile:filePath atomically:YES];
url = [NSURL fileURLWithPath:filePath]; //Yep, I needed to re-assign url for use elsewhere.
//do other stuff with your local file now that you have its url!
}
A bonus is that you get to keep track of the download progress with this awesome feature:
-(void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
NSLog(#"%f / %f", (double)totalBytesWritten,
(double)totalBytesExpectedToWrite);
}
Anyway, hope someone finds this useful. Works much faster on my iPhone 4 than NSURLSessionDataTask which works in a similar manner.

Background Download file in ios7,pop view then push ,it can not update UI

I push my download view, then download file in background model, then update it progress in
delegate (uRLSession:downloadTask:didWriteData: totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:).The progressView can be updated, then pop this controller, then push it again.download file again,The UI can not be updated.
DownloadCode:
- (IBAction)download:(UIButton *)sender {
self.image.backgroundColor = [UIColor whiteColor];
NSString * downloadURLString = [NSString stringWithFormat:#"http://ww3.sinaimg.cn/mw600/bce52ee1jw1e2xe4zdqarj.jpg"];
NSURL* downloadURL = [NSURL URLWithString:downloadURLString];
NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL];
NSURLSessionDownloadTask *task = [[self backgroundURLSession] downloadTaskWithRequest:request];
[task resume];
}
- (NSURLSession *)backgroundURLSession
{
static NSURLSession *session = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *identifier = #"example.demon";
NSURLSessionConfiguration* sessionConfig = [NSURLSessionConfiguration backgroundSessionConfiguration:identifier];
session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
});
return session;
}
ProgessView update
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
float progress = totalBytesWritten*1.0/totalBytesExpectedToWrite;
dispatch_async(dispatch_get_main_queue(),^ {
[self.progress setProgress:progress animated:YES];
});
NSLog(#"Progress =%f",progress);
NSLog(#"Received: %lld bytes (Downloaded: %lld bytes) Expected: %lld bytes.\n",
bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}
Put below method and your code will work
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.session invalidateAndCancel];
[self.session finishTasksAndInvalidate];
self.session = nil;
}

NSURLSession authentication and downloading file in background

In iPad app, I need to connect to a server and download files that's using a self-signed SSL certificate with NSURLSession object in background mode:
static NSString *const URL = #"https://...";
- (void)testBackgroundDownloadTask {
if (self.downloadTask) {
return self;
}
self.session = [self backgroundSession];
NSURL *downloadURL = [NSURL URLWithString:URL];
NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL];
self.downloadTask = [self.session downloadTaskWithRequest:request];
[self.downloadTask resume];
}
- (NSURLSession *)backgroundSession{
static NSURLSession *sess = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:URL];
// Default configuration: working perfectly
//NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.sessionSendsLaunchEvents = YES;
//configuration.TLSMinimumSupportedProtocol = kSSLProtocolAll;
configuration.networkServiceType = NSURLNetworkServiceTypeBackground;
sess = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
});
return sess;
}
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
NSURLProtectionSpace *protectionSpace = challenge.protectionSpace;
NSString *authMethod = protectionSpace.authenticationMethod;
if ([authMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
// obtain challenge, it working with NSURLSession in default session config
[self.challengeHandler handleChallenge:challenge onCompletion:^(id obj) {
NSURLCredential *c = (NSURLCredential *)obj;
if (c) {
[challenge.sender useCredential:c forAuthenticationChallenge:challenge];
completionHandler(NSURLSessionAuthChallengeUseCredential, c);
}
else {
[challenge.sender cancelAuthenticationChallenge:challenge];
}
}];
}
else if ([authMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}
}
If I use defaultSessionConfiguration, my app after call didReceiveChallenge method perform successfully download file (call NSURLSesionDownloadDelegate methods
– URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes: 
– URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite: 
– URLSession:downloadTask:didFinishDownloadingToURL: 
But I use backgroundSessionConfiguration, after call didReceiveChallenge other delegate’s methods not called (file not downloading and didCompleteWithError not called)
Any ideas on how to resolve this problem?
Are you testing this in the background?
On your NSURLSessionDownloadDelegate, you should see what you get when you implement
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
NSLog(#"%s %# %#", __PRETTY_FUNCTION__, task.response, error);
}
If any of that stuff in –URLSession:didReceiveChallenge:completionHandler: is async, then you need to surround it with a UIApplication/UIBackgroundTaskIdentifier {begin,end}BackgroundTask..., or iOS will kill your background process when the stack pops.

Resources