13

I'm trying to use Alamofire4 for Swift3. I need to download .mp3 files and save them to the Documents directory. Current code is like so:

func downloadAudioFromURL(url: String, completion: ((_ status: ResponseStatus, _ audioLocalURL: URL?) -> Void)?) {


        let fileManager = FileManager.default
        let directoryURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
        let audioFullURL = String.ensureFullURLPath(url)


        alamoManager.download(audioFullURL)
            .validate { request, response, temporaryURL, destinationURL in

                var pathComponent = response.suggestedFilename!
                if pathComponent == "m4a.mp4" {
                    // Due to the old Android audio files without a filename
                    // Assign a unique name so audio files don't overwrite each other
                    pathComponent = "\(NSUUID().uuidString).mp4"
                }
                let localURL = directoryURL.appendingPathComponent(pathComponent)

                if response.statusCode == 200 {
                    completion?(.success, localURL)
                } else {
                    completion?(.failure, nil)
                }
                return .success
            }

            .responseJSON { response in
                debugPrint(response)
                print(response.temporaryURL)
                print(response.destinationURL)
        }
    }

However I can't actually access the files from the localURL after saving. I have also noticed that the localURL will be exactly the same for different files I try to download (maybe they are overwriting?). E.g: file:///Users/testuser/Library/Developer/CoreSimulator/Devices/D4254AEA-76DD-4F01-80AF-F1AF3BE8A204/data/Containers/Data/Application/29755154-DD21-4D4C-B340-6628607DC053/Documents/file1.mp3

Any ideas what I am doing wrong here?

Edited my code to look like this:

func downloadAudioFromURL(url: String, completion: ((_ status: ResponseStatus, _ audioLocalURL: URL?) -> Void)?) {

        let destination: DownloadRequest.DownloadFileDestination = { _, _ in
            var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]

            documentsURL.appendPathComponent("Audiofile.mp3")
            return (documentsURL, [.removePreviousFile])
        }

        Alamofire.download(url, to: destination).response { response in

            if let localURL = response.destinationURL {

                completion?(.success, localURL)

            } else {

                completion?(.failure, nil)
            }

        }
}

How would I check for m4a.mp4 though?

Zoe
  • 23,712
  • 16
  • 99
  • 132
Kex
  • 6,762
  • 5
  • 39
  • 93

5 Answers5

21

Why are you performing .validate? You are not storing any data after download in your current code. Alamofire allows you to store a file directly after download:

let destination: DownloadRequest.DownloadFileDestination = { _, _ in
    let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
    let fileURL = documentsURL.appendPathComponent("pig.png")

    return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}

Alamofire.download(urlString, to: destination).response { response in
    print(response)

    if response.result.isSuccess, let imagePath = response.destinationURL?.path {
        let image = UIImage(contentsOfFile: imagePath)
    }
}

And by the way, the download path you are providing in the download method, is the local URL to the Documents directory and not a URL of a server.

ezcoding
  • 2,866
  • 3
  • 20
  • 30
  • 1
    On the return line I have the error `Cannot convert return expression of type (), to type URL`. Also, I see you are setting the path component before the download, but actually I want to use the recommended path component. – Kex Oct 05 '16 at 09:26
  • @Kex See my answer for this fix `Cannot convert return expression of type (), to type URL` – Rajan Maheshwari Oct 05 '16 at 09:29
  • @Kex How are you going to access the file afterwards, if you don't know the name of it? Even if you only have one file, I'd strongly recommend that you provide the name yourself. Then answer I've provided is the recommended way by Alamofire. I don't see the point in trying to use it differently as it was intended to be used. – ezcoding Oct 05 '16 at 09:33
  • Please see my edited question. I will add a name but I also need to check pathComponent for if it is `m4a.mp4`. Is it possible to do this before calling download? – Kex Oct 05 '16 at 09:59
  • `.validate` allows you check for a MIME Type. But this is a different question. First try to successfully download any kind of file. If you succeed in that, start using validate. How to use `.validate` to check if a file is .m4a or .mp3 is a different question though. – ezcoding Oct 05 '16 at 10:26
  • Can anyone tell how to open the file using the destination url after the file has been downloaded – Praburaj Nov 09 '17 at 13:31
  • In filePath - i have used as follows let documentsDirectory = "file://\(documentsDirectory)" – Sakthimuthiah Aug 22 '19 at 13:45
8

Swift 3.x and Alamofire 4.x version

Well certainly the Alamofire example posted by Alamofire itself has bugs. Since fileURL returns Void it can't be used as a parameter in return statement.

Also remove .createIntermediateDirectories from option list of return statement if you don't want any Directories for the file you downloaded

EDIT
If you want to know the type of file, simply grab the last component part and convert String to NSString as NSString have these functionalities.

//audioUrl should be of type URL
let audioFileName = String((audioUrl?.lastPathComponent)!) as NSString

//path extension will consist of the type of file it is, m4a or mp4
let pathExtension = audioFileName.pathExtension

let destination: DownloadRequest.DownloadFileDestination = { _, _ in
    var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]

    // the name of the file here I kept is yourFileName with appended extension
    documentsURL.appendPathComponent("yourFileName."+pathExtension)
    return (documentsURL, [.removePreviousFile])
}

Alamofire.download("yourAudioUrl", to: destination).response { response in
            if response.destinationURL != nil {
                print(response.destinationURL!)
            }
        }

Output is

file:///Users/rajan/Library/Developer/CoreSimulator/Devices/92B4AB6E-92C0-4864-916F-9CB8F9443014/data/Containers/Data/Application/781AA5AC-9BE7-46BB-8DD9-564BBB343F3B/Documents/yourFileName.mp3

which is the actual path of your file where it is stored.

Rajan Maheshwari
  • 13,526
  • 5
  • 59
  • 93
  • You want to check about the file type whether its m4a or mp4? I believe you are hitting the url and from that URL you can easily access the last component which can tell you its mp4 or m4a – Rajan Maheshwari Oct 05 '16 at 10:07
  • @Kex From your audioURL we can get the extension and make the document path for saving the file accordingly. See the edit. – Rajan Maheshwari Oct 05 '16 at 10:53
  • did you already got this error on the line "documentsURL.appendPathComponent" : "libc++abi.dylib: terminating with uncaught exception of type realm::IncorrectThreadException: Realm accessed from incorrect thread" ? – Jordan Montel Mar 19 '17 at 21:26
  • Alamofire example code itself has bugs ! You are absolutely right @Rajan ! I've lost all day trying to make this example working. Your solution work, thanks. – Fox5150 Mar 21 '17 at 19:20
2

Swift 5 - Alamofire 5.1. This is the approach.

let destination: DownloadRequest.Destination = { _, _ in
        let documentsURL = FileManager.default.urls(for: .picturesDirectory, in: .userDomainMask)[0]
            let fileURL = documentsURL.appendingPathComponent("image.png")

            return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
    }

    AF.download("https://httpbin.org/image/png", to: destination).response { response in
        debugPrint(response)

        if response.error == nil, let imagePath = response.fileURL?.path {
            let image = UIImage(contentsOfFile: imagePath)
        }
    }
Cristian Tovar
  • 121
  • 1
  • 4
1

Objective: Downloaded files from the server like gif, pdf, or zip will be store inside your specified folder name.

If you want to store your own folder structure like the name is "ZipFiles"

call .

self downloadZipFileFromServer(downloadFolderName: "ZipFiles");

Downloaded zip data is stored inside the document/ZiFiles/abc.zip

this just creates a folder inside the document

func createFolder(folderName:String)

Alamofire 4
Swift 4
/******Download image/zip/pdf  from the server and save in specific Dir********/
func downloadZipFileFromServer(downloadFolderName: string)
{
    let destination: DownloadRequest.DownloadFileDestination = { _, _ in
        var fileURL = self.createFolder(folderName: downloadFolderName)
        let fileName = URL(string : "www.xymob.com/abc.zip")
        fileURL = fileURL.appendingPathComponent((fileName?.lastPathComponent)!)
        return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
    }
    Alamofire.download("www.xymob.com/abc.zip", to: destination).response(completionHandler: { (DefaultDownloadResponse) in                
        print("res ",DefaultDownloadResponse.destinationURL!);
    })
}        

func createFolder(folderName:String)->URL
{
    var paths: [Any] = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
    let documentsDirectory: String = paths[0] as? String ?? ""
    let dataPath: String = URL(fileURLWithPath: documentsDirectory).appendingPathComponent(folderName).absoluteString
    if !FileManager.default.fileExists(atPath: dataPath) {
        try? FileManager.default.createDirectory(atPath: dataPath, withIntermediateDirectories: false, attributes: nil)
    }
    let fileURL = URL(string: dataPath)
    return fileURL!
}
  • 1
    Posting code without any explanation isn't welcome here. Would you please complete your answer ? – Cid Jun 08 '18 at 11:19
1

Although the question is an older one,
I have re-written and tested it on Swift 5

import Foundation
import Alamofire

class DownloadFileService {
    
    static func downloadFile(using url: URL, completion: @escaping () -> Void) {
        let fileName = url.lastPathComponent
        
        let destination: DownloadRequest.Destination = { _, _ in
            var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
            
            documentsURL.appendPathComponent(fileName)
            return (documentsURL, [.removePreviousFile])
        }
        
        AF.download(url, to: destination).response { response in
            print(response)
            completion()
        }
    }
    
}
Ankur Lahiry
  • 1,599
  • 1
  • 8
  • 17