10

I am trying to download a pdf file. earlier when i used the completion handler block i was able to see the file in the tmp location. I then wanted to show download progress so i implemented the delegate methods. But i can now see the progress bar working and the file being downloaded. but once the download is complete (bytes written/total bytes = 1) the error delegate is called and there is no file in the tmp location. what am i missing ? below is my code. I have uploaded the project at https://www.dropbox.com/s/vn5zwfwx9izq60a/trydownload.zip

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
    NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:[NSURL URLWithString:@"http://aayudham.com/URLLoadingSystem.pdf"]];
    [downloadTask resume];

}

-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    NSLog(@"%@",[error localizedDescription]);
}
-(void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    dispatch_async(dispatch_get_main_queue(), ^{
        _progressBar.progress = (double)totalBytesWritten/(double)totalBytesExpectedToWrite;
        double value =(double)totalBytesWritten/(double)totalBytesExpectedToWrite;
        NSLog(@"%f",value);
    });
}

-(void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{

}

-(void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
       NSError *error;
//getting docs dir path
NSArray * tempArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsDir = [tempArray objectAtIndex:0];

//adding folder path
NSString *appDir = [docsDir stringByAppendingPathComponent:@"/Reader/"];

NSFileManager *fileManager = [NSFileManager defaultManager];

if(![fileManager fileExistsAtPath:appDir])
{
    [fileManager createDirectoryAtPath:appDir withIntermediateDirectories:NO attributes:nil error:&error];
}


BOOL fileCopied = [fileManager moveItemAtPath:[location path] toPath:[appDir stringByAppendingString:@"/demo.pdf"] error:&error];

NSLog(fileCopied ? @"Yes" : @"No");
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end
Tamil
  • 1,173
  • 1
  • 13
  • 35
  • what is the error you are logging in `didCompleteWithError`? – sergio Mar 09 '14 at 08:14
  • When i print `[error localizedDescription]` i get "The operation couldn’t be completed. (Cocoa error 516.)". – Tamil Mar 09 '14 at 08:24
  • 1
    I was able to fix the problem by adding a filename to the end of the `toPath` and it now worked. Is there any other efficient way to do this? – Tamil Mar 09 '14 at 08:32
  • Not sure I understand, but maybe just generating a UUID as the file name would work... – sergio Mar 09 '14 at 08:39

3 Answers3

12

@Rob thank you for your prompt replies and that helped me a lot. Here is my code that worked. Hope it helps someone. I am able to get the actual file name and move the file to my documents directory using the original name.

-(void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSError *error;

    //getting application's document directory path
    NSArray * tempArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *docsDir = [tempArray objectAtIndex:0];

    //adding a new folder to the documents directory path
    NSString *appDir = [docsDir stringByAppendingPathComponent:@"/Reader/"];

    //Checking for directory existence and creating if not already exists
    if(![fileManager fileExistsAtPath:appDir])
    {
        [fileManager createDirectoryAtPath:appDir withIntermediateDirectories:NO attributes:nil error:&error];
    }

    //retrieving the filename from the response and appending it again to the path
    //this path "appDir" will be used as the target path 
    appDir =  [appDir stringByAppendingFormat:@"/%@",[[downloadTask response] suggestedFilename]];

    //checking for file existence and deleting if already present.
    if([fileManager fileExistsAtPath:appDir])
    {
        NSLog([fileManager removeItemAtPath:appDir error:&error]?@"deleted":@"not deleted");
    }

    //moving the file from temp location to app's own directory
    BOOL fileCopied = [fileManager moveItemAtPath:[location path] toPath:appDir error:&error];
    NSLog(fileCopied ? @"Yes" : @"No");

}
Tamil
  • 1,173
  • 1
  • 13
  • 35
7

In didFinishDownloadingToURL you should move the file from location to some place more permanent (e.g. your Documents folder). If you're looking for that file in the temporary location later, I'm not surprised it's no longer there.

As the documentation says, the location is defined as such:

A file URL for the temporary file. Because the file is temporary, you must either open the file for reading or move it to a permanent location in your app’s sandbox container directory before returning from this delegate method.

You must move the file to its new location before returning from didFinishDownloadingToURL.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • I am debugging the app and as soon as the download completes the file is missing in the tmp folder (i am not moving it anywhere). until the download progress is going on i can see a temporary file appear in the tmp folder. – Tamil Mar 09 '14 at 05:12
  • @Tamil I've updated my answer with the reference in the documentation that encourages you to move the file to a permanent location before exiting `didFinishDownloadingToURL`. The OS may remove it as soon as you return from your delegate method. – Rob Mar 09 '14 at 05:57
  • Hi @Rob thank you very much, that worked. but I am now having problem with copying the file. I created a directory inside documents directory and trying to move the file there using [fileManager copyItemAtPath:[location absoluteString] toPath:newPath error:&error] but this returns NO and the file is not copied :( – Tamil Mar 09 '14 at 06:54
  • @Tamil Personally, I would use `[fileManager copyItemAtURL:location toURL:[NSURL fileURLWithPath:newPath] error:&error]`. But did you look at the `error` object? That should tell you what's wrong. – Rob Mar 09 '14 at 06:58
  • when I check fileExistsAtPath it returns NO. this is all within the delegate method only. Am i doing something wrong? ...NSCocoaErrorDomain - code 260 – Tamil Mar 09 '14 at 07:10
  • @Tamil add an edit to the end of your original question with the code for your `didFinishDownloadingToURL` method. – Rob Mar 09 '14 at 07:45
  • @Rob I am using the `downloadTaskWithURL` with a completion handler. I want to do to file movement from temporary to permanent file outside the main queue using a dispatch_async. Will the temporary file be moved if I do it this way? I am getting an error but it appears to be different, Received error Error Domain=NSCocoaErrorDomain Code=516 "“CFNetworkDownload_aukj6k.tmp” couldn’t be moved to “1827B916-8B74-41CF-8868-537FAA8480CF” because an item with the same name already exists.". I can start a new post with full code, if needed. – Smart Home Aug 17 '16 at 03:37
  • 1
    @SmartHome - 1. Yes, in the future, pls post your own question. 2. Your error is telling you not that the old file was removed, but rather that a file of the chosen name already exists in the desired destination already. 3. You really should do the move the file on the queue that this was called (or if you need another queue for synchronization reasons, dispatch synchronously). 4. Moving files (unlike copying files) happens very quickly, so don't worry about performance issue there. 5. If you need another queue, specify that when setting up session. – Rob Aug 17 '16 at 03:56
  • Thanks a lot @Rob for your expertise. The file move issue turned out be because I was using Apple recommended code that was moving to directory. So, the error was saying that directory already exists. I changed it to filename and it is now working. I will remove the dispatch_async for now using the dispatch_async if on downloadTaskWithURL like you have suggested. – Smart Home Aug 17 '16 at 04:33
3

Just in case someone had the same issue that I did I thought I would post my solution here.

My problem was that the predicate methods are firing on a background thread so I was dispatching to my "file io" thread which handles any Writing to files, deleting etc within the app.

The problem with this is that the temporary file gets deleted as soon as the delegate method ends which was occurring at the exact moment I switched threads. So when I tried to access the file in my file io thread it had already been deleted.

My solution was to parse the file into NSData in the delegate method, then use the NSData to write to the filesystem in my file io thread.

Nick Kirsten
  • 1,187
  • 11
  • 27