0

I was doing download system with the code from https://stackoverflow.com/a/32322851/7789222. It was a great and complete code but I can find a way to pass foldername from view controller to download file to specific folder. Can anyone help me with it please. I am using swift 3 xcode 8.

If I hard code the custom directory in func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) , every file will be downloaded to same folder. I want to pass the foldername from view controller so I can download files to different folder. I cant hardcode because I retrieve file name and foldername from server

Palle
  • 11,511
  • 2
  • 40
  • 61
Shsgge
  • 13
  • 7

1 Answers1

0

The destination URL in the example is given by

let destinationURL = try manager.url(
    for: .documentDirectory, 
    in: .userDomainMask, 
    appropriateFor: nil, 
    create: false
).appendingPathComponent(url.lastPathComponent)

(Line 17)

You can just pass a destination folder URL to the initializer of DownloadOperation which replaces the destination URL in the example:

let destinationURL = yourDestinationFolder.appendingPathComponent(url.lastPathComponent)

Your modified DownloadOperation would look something like this:

class DownloadOperation : AsynchronousOperation {
    var task: URLSessionTask!
    let destinationFolder: URL

    init(session: URLSession, url: URL, destinationFolder: URL) {
        super.init()
        self.destinationFolder = destinationFolder
        task = session.downloadTask(with: url) { temporaryURL, response, error in
            defer { self.completeOperation() }

            guard error == nil && temporaryURL != nil else {
                print("\(error)")
                return
            }

            do {
                let manager = FileManager.default
                let destinationURL = destinationFolder.appendingPathComponent(url.lastPathComponent)
                _ = try? manager.removeItem(at: destinationURL)                    // remove the old one, if any
                try manager.moveItem(at: temporaryURL!, to: destinationURL)    // move new one there
            } catch let moveError {
                print("\(moveError)")
            }
        }
    }

    ...

}

The code for adding operations is then

queue.addOperation(DownloadOperation(session: session, url: url, destinationFolder: destinationFolder))

If you want to use the DownloadManager:

class DownloadManager {
    @discardableResult
    func addDownload(_ url: URL, to destinationFolder: URL) -> DownloadOperation {
        let operation = DownloadOperation(session: session, url: url, destinationFolder: destinationFolder)
        operations[operation.task.taskIdentifier] = operation
        queue.addOperation(operation)
        return operation
    }
    ...
}

The extension:

extension DownloadOperation: URLSessionDownloadDelegate {

    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        do {
            let manager = FileManager.default
            let destinationURL = destinationFolder.appendingPathComponent(downloadTask.originalRequest!.url!.lastPathComponent)
            if manager.fileExists(atPath: destinationURL.path) {
                try manager.removeItem(at: destinationURL)
            }
            try manager.moveItem(at: location, to: destinationURL)
        } catch {
            print("\(error)")
        }
    }

    ...
}

Then you can add downloads with

downloadManager.addDownload(url, to: destinationFolder)
Palle
  • 11,511
  • 2
  • 40
  • 61
  • that one is for file name right? I would like to pass folder name because I want to segment each file to different folder – Shsgge Aug 11 '17 at 03:16
  • Yes, the `destinationURL` is the URL for the file path at which the file is saved. By replacing `try manager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)` with your own folder URL, you can choose to save the file into a different folder. – Palle Aug 11 '17 at 03:18
  • but my problem is how to pass the foldername from viewcontroller to func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) – Shsgge Aug 11 '17 at 03:21
  • The URL session downloads to a temporary folder from where you can copy the file. You can see that the same pattern as described in my answer is used in the `extension DownloadOperation: URLSessionDownloadDelegate`, which you would - as described in my answer - replace with the code for your custom directory. – Palle Aug 11 '17 at 03:28
  • I am so sorry but can you give me example of passing foldername from view controller. If I hard code the custom directory in func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) , every file will be downloaded to same folder. I want to pass the foldername from view controller so I can download files to different folder. I cant hardcode because I retrieve file name and foldername from server. – Shsgge Aug 11 '17 at 03:32
  • I edited the answer so you can use a new folder for every download task you add. – Palle Aug 11 '17 at 03:41
  • "task = session.downloadTask(with: url) { temporaryURL, response, error in is" giving me "self" captured by a closure before all members were initialized. I have tried many ways but still not helping. Do u have same problem? – Shsgge Aug 11 '17 at 05:22
  • ah sorry i have fixed the error by putting code in this order 1. self.destinationFolder = destinationFolder 2. super.init() 3. task = session.downloadTask(with: url) { temporaryURL, response, error in – Shsgge Aug 11 '17 at 05:29
  • the error Im getting now is "Terminating app due to uncaught exception 'NSGenericException', reason: 'Completion handler blocks are not supported in background sessions. Use a delegate instead.'" – Shsgge Aug 11 '17 at 05:40
  • Then you have to use a foreground session or use `session.downloadTask(...)` without the completion handler, so the delegate methods in `DownloadManager` get called instead. – Palle Aug 11 '17 at 06:16