13

I have an app using a backgroundSessionConfiguration instance of NSURLSession to handle some NSURLSessionDownloadTask tasks. The tasks are created correctly, and finish downloading, but in URLSession:downloadTask:didFinishDownloadingToURL: when I go to move the downloaded file from location to a permanent spot on disk, I sometimes (read: often) get the error:

NSUnderlyingError=0x178247890 "The operation couldn’t be completed. No such file or directory"

It's as though the downloaded file is being cleared out of the temp directory (../Library/Caches/com.apple.nsnetworkd/..) before I get a chance to move it.

Running the operation for the same remote file will behave both ways with all other factors being equal, so I haven't been able to identify anything that would cause it to sometimes work.

Is there anyway to ensure that this file will stick around long enough to move it into place once it's done?

Edit: I'm also seeing this with some frequency for normal (not background) sessions as well

Farski
  • 1,670
  • 3
  • 15
  • 30
  • cann you please share your saving code so i can suggest you better – Anurag Soni Nov 30 '13 at 15:24
  • 1
    By the time the program gets to the save operation it's too late, the tmp file is – Farski Nov 30 '13 at 23:57
  • Unclear question. Do you need codelevel help or you just want to complain about a behaviour you think being a bug? In the later case t belongs this with an example project to bugreport.apple.com and no other place. As for nsurlsession, I am using it since the WWDC qnd never run into any problems like you did desribe. – Helge Becker Dec 01 '13 at 00:22
  • 1
    The code that is behaving this way is essentially code that I carry between a few different apps, and this is the first time I am seeing this kind of behavior. So if it is a bug it's certainly avoidable. I doubt it's a bug though, or I would have been able to find someone else seeing this happen. Even if I do nothing in `didCompleteWithError` or `didFinishDownloadingToURL`, the files simply disappear (from tmp) when they're done, so I'm not sure what I could change to prevent it. – Farski Dec 01 '13 at 04:34
  • Also worth noting that the docs say "When this method returns, the temporary file is deleted" (re: didFinishDownloadingToURL:), so it's not unusual that the file is going away, but that it's being deleted before that method returns. – Farski Dec 01 '13 at 04:38
  • 1
    Actually, I think this may just be the result of sloppy copy/paste and the move op ended up in a background thread so it *was* happening after the method returned. False alarm, thanks for the help though. – Farski Dec 01 '13 at 04:41
  • 1
    @farski You should post that as an answer to your own question here. It wouldn't surprise me if someone else managed to do the same thing in the future and came here in search of the solution. – Matt Gibson May 22 '14 at 07:39
  • There seems to be error in Apple implementation. location URL is sometimes wrong. There is no file at that location. – Juraj Antas Dec 31 '15 at 14:20
  • In my case, I stored the full path where to move the temp file (in caches) to. This full patch contained the container id, but this container ID changes between sessions, so the target path was not valid anymore. Had to recreate the target path. – guido Sep 03 '19 at 00:53

5 Answers5

11

The temporary files are deleted automatically after exiting this method, you can try to hold this returned file as an NSData and saves it directly into the desired location.

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {

    // Create a NSFileManager instance
    NSFileManager *fileManager = [[NSFileManager alloc] init];

    // Get the documents directory URL
    NSArray *documentURLs = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
    NSURL *documentsDirectory = [documentURLs firstObject];

    // Get the file name and create a destination URL
    NSString *sendingFileName = [downloadTask.originalRequest.URL lastPathComponent];
    NSURL *destinationUrl = [documentsDirectory URLByAppendingPathComponent:sendingFileName];

    // Hold this file as an NSData and write it to the new location
    NSData *fileData = [NSData dataWithContentsOfURL:location];
    [fileData writeToURL:destinationUrl atomically:NO];
}
Douglas Ferreira
  • 2,279
  • 2
  • 27
  • 42
3

Try this

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)downloadURL {

    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSArray *URLs = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
    NSURL *documentsDirectory = [URLs objectAtIndex:0];
    NSURL *originalURL = [[downloadTask originalRequest] URL];
    if(originalURL)
       destinationUrl = [documentsDirectory URLByAppendingPathComponent:[originalURL lastPathComponent]];

    NSError *errorCopy;

    [fileManager removeItemAtURL:destinationUrl error:NULL];
    BOOL success = [fileManager copyItemAtURL:downloadURL toURL:destinationUrl error:&errorCopy];

    if (success) {

    } else {

    }
}
1

This is the best way to do it

- (void)URLSession:(NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(nonnull NSURL *)location{

    NSFileManager *fileManager = [[NSFileManager alloc] init];
    NSArray      *documentURLs = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
    NSURL  *documentsDirectory = [documentURLs firstObject];

    // Get the file name and create a destination URL
    NSString *sendingFileName = [downloadTask.response suggestedFilename];
    NSURL     *destinationUrl = [documentsDirectory URLByAppendingPathComponent:sendingFileName];

    // Hold this file as an NSData and write it to the new location
    NSData *fileData = [NSData dataWithContentsOfURL:location];
    [fileData writeToURL:destinationUrl atomically:NO];
}
Fawaz
  • 584
  • 1
  • 11
  • 24
0

Ran into this using Swift under iOS 8, copy or move file did not work. Reading temp file directly and writing back out worked. Code creates image view from downloaded image:

var fileHandle:NSFileHandle = NSFileHandle(forReadingAtPath: location.path!)!
var data:NSData = fileHandle.readDataToEndOfFile()
var image:UIImage = UIImage(data: data)!
imageView.image = image
Walter
  • 1
0

In my case, I was running the results of my completion handler in the main thread to also handle some UI changes (i.e. using a DispatchQueue.main.async block). These blocks are not executed right away. It will be executed after the completion handler finishes, at which point the temp file had been deleted. It's obvious now, but what made this difficult to debug is that sometimes the file move function worked, whereas other times I got the error mentioned in your question.

JCutting8
  • 732
  • 9
  • 29