Skip to content

Instantly share code, notes, and snippets.

@justinswart
Created March 14, 2018 18:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save justinswart/c2eb7be19827d49f9c4a3eda83aa54e6 to your computer and use it in GitHub Desktop.
Save justinswart/c2eb7be19827d49f9c4a3eda83aa54e6 to your computer and use it in GitHub Desktop.
Amazons3Uploader.swift
import Foundation
import RxSwift
import MobileCoreServices.UTType
public enum AmazonS3UploadError: Error {
case uploadFailed
}
public final class AmazonS3Uploader: NSObject, AmazonS3UploaderType {
private var uploadProgressSubject: PublishSubject<(Float, AmazonS3UploadTask)>?
public var uploadProgress: Observable<(Float, AmazonS3UploadTask)>?
fileprivate var uploadTask: URLSessionDataTask?
fileprivate var originalTask: AmazonS3UploadTask?
fileprivate var response = Data()
private lazy var session: URLSession = {
URLSession(
configuration: .default,
delegate: self,
delegateQueue: .main
)
}()
public func start(uploadTask task: AmazonS3UploadTask) -> Observable<(Float, AmazonS3UploadTask)> {
self.originalTask = task
self.uploadProgressSubject = PublishSubject<(Float, AmazonS3UploadTask)>()
self.uploadProgress = self.uploadProgressSubject?.asObservable().share(replay: 1, scope: .forever)
var request = URLRequest(url: task.url)
request.httpMethod = "POST"
let boundary = "Boundary-\(UUID().uuidString)"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
guard
let data = task.data,
let mimeType = task.mimeType else {
return .error(AmazonS3UploadError.uploadFailed)
}
let newFileName = fileName(replacing: task.originalFileName)
self.originalTask = task.adding(createdFileName: newFileName)
request.httpBody = createBody(parameters: task.params,
boundary: boundary,
data: data,
mimeType: mimeType,
filename: newFileName)
self.uploadTask = self.session.dataTask(with: request)
self.uploadTask?.resume()
guard let uploadProgress = self.uploadProgress else { return .empty() }
return uploadProgress
}
}
extension AmazonS3Uploader: URLSessionDataDelegate {
public func urlSession(_: URLSession, task _: URLSessionTask, didCompleteWithError error: Error?) {
guard
error == nil,
let task = self.originalTask else {
self.uploadProgressSubject?.onError(AmazonS3UploadError.uploadFailed)
return
}
self.uploadProgressSubject?.onNext((100, task.adding(completed: true)))
self.uploadProgressSubject?.onCompleted()
}
public func urlSession(_: URLSession, task _: URLSessionTask, didSendBodyData _: Int64,
totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
let uploadProgress: Float = Float(totalBytesSent) / Float(totalBytesExpectedToSend) * 100
guard let task = self.originalTask else { return }
self.uploadProgressSubject?.onNext((uploadProgress, task))
}
public func urlSession(_: URLSession, dataTask _: URLSessionDataTask,
didReceive _: URLResponse,
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
completionHandler(.allow)
}
public func urlSession(_: URLSession, dataTask _: URLSessionDataTask, didReceive data: Data) {
self.response.append(data)
}
}
private func createBody(parameters: [String: String],
boundary: String,
data: Data,
mimeType: String,
filename: String) -> Data {
var body = Data()
let boundaryPrefix = "--\(boundary)\r\n"
for (key, value) in parameters {
body.append(boundaryPrefix)
body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.append("\(value)\r\n")
}
body.append(boundaryPrefix)
body.append("Content-Disposition: form-data; name=\"file\"; filename=\"\(filename)\"\r\n")
body.append("Content-Type: \(mimeType)\r\n\r\n")
body.append(data)
body.append("\r\n")
body.append("--".appending(boundary.appending("--")))
return body
}
extension Data {
fileprivate mutating func append(_ string: String) {
string.data(using: .utf8).map { self.append($0) }
}
}
private func fileName(replacing fileName: String) -> String {
return "\(UUID().uuidString).\(fileExtension(fileName))"
}
func fileExtension(_ fileName: String) -> String {
return URL(fileURLWithPath: fileName.lowercased()).pathExtension
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment