Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Download in documents directory or move file async - iOS

I am building an iOS app in which the user can download different files.
I am using an URLSessionDownloadTask and an URLSession to download a file asynchronously.
When the download is finished, the destination folder is by default, the tmp/ directory.
So, when the download ends, I need to move the temporary file to another directory.
For a picture or a song, this takes only 1 second maybe even less.
But when the file is a video for example, it can take up to 15 seconds.

The issue

To allow the user to still interact with the app, I would like to make this move asynchronous.
Each time I try to do that, the file manager throws an exception.

“CFNetworkDownload_xxxxxx.tmp” couldn’t be moved to “Downloads” because either the former doesn't exist, or the folder containing the latter doesn't exist.

What have I tried

I tried to put the call to the file manager in a background thread, it throws.

I tried to remove the destination file before calling the move method, to make sure that the file doesn't already exists.

I tried to make a call to the copy function, before removing the file from the tmp/ directory.

My code

The call to the file manager looks like that.

func simpleMove(from location: URL, to dest: URL) -> Bool {
    let fileManager = FileManager.default
    do {
        try fileManager.moveItem(at: location, to: dest)
        return true
    } catch {
        print("\(error.localizedDescription)")
        return false
    }
}

When I put that in a background thread, I do it like that.

DispatchQueue.global().async {
    if !simpleMove(from: location, to: dest) {
        //Failure
    }
}

Questions

How can I possibly move a really large file without affecting the UI?

It would be an even better solution to download the file directly in a permanent directory. How can I do that?

When I make the call to my simpleMove(from:to:) synchronously, it works perfectly.
So, why the error says that the destination directory doesn't exists? (or something like that, I'm not sure of the meaning of that error)

Thanks.

Note

The code above is written in Swift 3, but if you have an Objective-C or a Swift 2 answer,
feel free to share it as well!

like image 618
MadreDeDios Avatar asked Oct 05 '16 09:10

MadreDeDios


1 Answers

Amusingly, the correct answer to this was posted in another question, where it was not the correct answer.

The solution is covered in Apple's Documentation where they state:

location

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.

If you choose to open the file for reading, you should do the actual reading in another thread to avoid blocking the delegate queue.

You are probably calling simpleMove from the success handler for the DownloadTask. When you call simpleMove on a background thread, the success handler returns and your temp file is cleaned up before simpleMove is even called.

The solution is to do as Apple says and open the file for reading:

public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
    do {
        let file: FileHandle = try FileHandle(forReadingFrom: location)
        
        DispatchQueue.global().async {
            let data = file.readDataToEndOfFile()
            FileManager().createFile(atPath: destination, contents: data, attributes: nil)
        }
    } catch {
        // Handle error
    }
}
like image 61
Matt Mc Avatar answered Sep 25 '22 10:09

Matt Mc