1

The downloadTaskWithURL function sometimes returns a non-nil location for a file that does not exist.

There is no file at http://192.168.0.102:3000/p/1461224691.mp4 in the test environment.

Most of the time, invoking downloadTaskWithURL on this URL results in the expected error message:

Error downloading message during network operation. Download URL: http://192.168.0.102:3000/p/1461224691.mp4. Location: nil. Error: Optional(Error Domain=NSURLErrorDomain Code=-1100 "The requested URL was not found on this server." UserInfo=0x17547b640 {NSErrorFailingURLKey=http://192.168.0.102:3000/p/1461224691.mp4, NSLocalizedDescription=The requested URL was not found on this server., NSErrorFailingURLStringKey=http://192.168.0.102:3000/p/1461224691.mp4})

Occasionally, and in a non-deterministic way, downloadTaskWithURL believes the file exists and writes something to the location variable. As a result, the guard condition does not fail, and the code continues to execute ... which it should not.

The permanent file created by fileManager.moveItemAtURL(location!, toURL: fileURL) is only 1 byte, confirming that the network file never existed in the first place.

Why does downloadTaskWithURL behave like this?

func download() {
    // Verify <networkURL>
    guard let downloadURL = NSURL(string: networkURL) where !networkURL.isEmpty else {
        print("Error downloading message: invalid download URL. URL: \(networkURL)")
        return
    }

    // Generate filename for storing locally
    let suffix = (networkURL as NSString).pathExtension
    let localFilename = getUniqueFilename("." + suffix)

    // Create download request
    let task = NSURLSession.sharedSession().downloadTaskWithURL(downloadURL) { location, response, error in
        guard location != nil && error == nil else {
            print("Error downloading message during network operation. Download URL: \(downloadURL). Location: \(location). Error: \(error)")
            return
        }

        // If here, no errors so save message to permanent location
        let fileManager = NSFileManager.defaultManager()
        do {
            let documents = try fileManager.URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)
            let fileURL = documents.URLByAppendingPathComponent(localFilename)
            try fileManager.moveItemAtURL(location!, toURL: fileURL)
            self.doFileDownloaded(fileURL, localFilename: localFilename)
            print("Downloaded file @ \(localFilename). Download URL: \(downloadURL)")
        } catch {
            print("Error downloading message during save operation. Network URL: \(self.networkURL). Filename: \(localFilename). Error: \(error)")
        }
    }

    // Start download
    print("Starting download @ \(downloadURL)")
    task.resume()
}
Crashalot
  • 33,605
  • 61
  • 269
  • 439

1 Answers1

1

Just to clarify, the server is returning a 404, but the download task is returning a basically-empty file? And you're certain that the server actually returned an error code (by verifying the server logs)?

Either way, I would suggest checking the status code in the response object. If it isn't a 200, then the download task probably just downloaded the response body of an error page. Or, if the status code is 0, the connection timed out. Either way, treat it as a failure.

You might also try forcing this to all happen on a single thread and see if the nondeterminism goes away.

dgatwood
  • 10,129
  • 1
  • 28
  • 49