FileUpload.swift 3.75 KB
import Foundation
import Alamofire

private struct FileUploadInfo {
    var name: String
    var mimeType: String
    var fileName: String
    var url: URL?
    var data: Data?

    init(name: String, withFileURL url: URL, withMimeType mimeType: String? = nil) {
        self.name = name
        self.url = url
        self.fileName = name
        self.mimeType = "application/octet-stream"
        if mimeType != nil {
            self.mimeType = mimeType!
        }
        fileName = url.lastPathComponent
        if mimeType == nil {
            let _extension = url.pathExtension
            switch _extension.lowercased() {
            case "jpeg", "jpg":
                self.mimeType = "image/jpeg"

            case "png":
                self.mimeType = "image/png"

            default:
                self.mimeType = "application/octet-stream"
            }
        }
    }

    init(name: String, withData data: Data, withMimeType mimeType: String) {
        self.name = name
        self.data = data
        self.fileName = name
        self.mimeType = mimeType
    }
}

class FileUploader {

    private var parameters = [String: String]()
    private var files = [FileUploadInfo]()
    private var headers = [String: String]()

    func setValue(value: String, forParameter parameter: String) {
        parameters[parameter] = value
    }

    func setValue(value: String, forHeader header: String) {
        headers[header] = value
    }

    func addParametersFrom(_ map: [String: String]) {
        for (key, value) in map {
            parameters[key] = value
        }
    }

    func addHeadersFrom(_ map: [String: String]) {
        for (key, value) in map {
            headers[key] = value
        }
    }

    func addFileURL(url: URL, withName name: String, withMimeType mimeType: String? = nil) {
        files.append(FileUploadInfo(name: name, withFileURL: url, withMimeType: mimeType))
    }

    func addFileData(data: Data, withName name: String, withMimeType mimeType: String = "application/octet-stream") {
        files.append(FileUploadInfo(name: name, withData: data, withMimeType: mimeType))
    }

    func uploadFile(to: String, method: HTTPMethod) -> UploadRequest? {
        let data = NSMutableData()
        let boundary = "FileUploader-boundary-\(arc4random())-\(arc4random())"
        // Amazon S3 (probably others) wont take parameters after files, so we put them first
        for (key, value) in parameters {
            data.append("\r\n--\(boundary)\r\n".data(using: String.Encoding.utf8)!)
            data.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n\(value)".data(using: String.Encoding.utf8)!)
        }

        for fileUploadInfo in files {
            data.append("\r\n--\(boundary)\r\n".data(using: String.Encoding.utf8)!)
            data.append("Content-Disposition: form-data; name=\"\(fileUploadInfo.name)\"; filename=\"\(fileUploadInfo.fileName)\"\r\n".data(using: String.Encoding.utf8)!)
            data.append("Content-Type: \(fileUploadInfo.mimeType)\r\n\r\n".data(using: String.Encoding.utf8)!)
            if fileUploadInfo.data != nil {
                data.append(fileUploadInfo.data!)
            } else if fileUploadInfo.url != nil, let fileData = try? Data(contentsOf: fileUploadInfo.url!) {
                data.append(fileData)
            } else {
                // ToDo: report error
                return nil
            }
        }
        data.append("\r\n--\(boundary)--\r\n".data(using: String.Encoding.utf8)!)

        var headerParam = HTTPHeaders()
        headerParam["multipart/form-data;boundary=\(boundary)"] = "Content-Type"
        for (name, value) in headers {
            headerParam[value] = name
        }

        return Alamofire.upload(Data(referencing: data), to: to, method: method, headers: headerParam)
    }

}