Download.swift 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. //
  2. // Download.swift
  3. //
  4. // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy
  7. // of this software and associated documentation files (the "Software"), to deal
  8. // in the Software without restriction, including without limitation the rights
  9. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. // copies of the Software, and to permit persons to whom the Software is
  11. // furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. // THE SOFTWARE.
  23. //
  24. import Foundation
  25. extension Manager {
  26. private enum Downloadable {
  27. case request(Foundation.URLRequest)
  28. case resumeData(Data)
  29. }
  30. private func download(_ downloadable: Downloadable, destination: Request.DownloadFileDestination) -> Request {
  31. var downloadTask: URLSessionDownloadTask!
  32. switch downloadable {
  33. case .request(let request):
  34. queue.sync {
  35. downloadTask = self.session.downloadTask(with: request)
  36. }
  37. case .resumeData(let resumeData):
  38. queue.sync {
  39. downloadTask = self.session.downloadTask(withResumeData: resumeData)
  40. }
  41. }
  42. let request = Request(session: session, task: downloadTask)
  43. if let downloadDelegate = request.delegate as? Request.DownloadTaskDelegate {
  44. downloadDelegate.downloadTaskDidFinishDownloadingToURL = { session, downloadTask, URL in
  45. return destination(URL, downloadTask.response as! HTTPURLResponse)
  46. }
  47. }
  48. delegate[request.delegate.task] = request.delegate
  49. if startRequestsImmediately {
  50. request.resume()
  51. }
  52. return request
  53. }
  54. // MARK: Request
  55. /**
  56. Creates a download request for the specified method, URL string, parameters, parameter encoding, headers
  57. and destination.
  58. If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  59. - parameter method: The HTTP method.
  60. - parameter URLString: The URL string.
  61. - parameter parameters: The parameters. `nil` by default.
  62. - parameter encoding: The parameter encoding. `.URL` by default.
  63. - parameter headers: The HTTP headers. `nil` by default.
  64. - parameter destination: The closure used to determine the destination of the downloaded file.
  65. - returns: The created download request.
  66. */
  67. public func download(
  68. _ method: Method,
  69. _ URLString: URLStringConvertible,
  70. parameters: [String: AnyObject]? = nil,
  71. encoding: ParameterEncoding = .url,
  72. headers: [String: String]? = nil,
  73. destination: Request.DownloadFileDestination)
  74. -> Request
  75. {
  76. let mutableURLRequest = URLRequest(method, URLString, headers: headers)
  77. let encodedURLRequest = encoding.encode(mutableURLRequest, parameters: parameters).0
  78. return download(encodedURLRequest, destination: destination)
  79. }
  80. /**
  81. Creates a request for downloading from the specified URL request.
  82. If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  83. - parameter urlRequest: The URL request
  84. - parameter destination: The closure used to determine the destination of the downloaded file.
  85. - returns: The created download request.
  86. */
  87. public func download(_ urlRequest: URLRequestConvertible, destination: Request.DownloadFileDestination) -> Request {
  88. return download(.request(urlRequest.urlRequest), destination: destination)
  89. }
  90. // MARK: Resume Data
  91. /**
  92. Creates a request for downloading from the resume data produced from a previous request cancellation.
  93. If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  94. - parameter resumeData: The resume data. This is an opaque data blob produced by `NSURLSessionDownloadTask`
  95. when a task is cancelled. See `NSURLSession -downloadTaskWithResumeData:` for
  96. additional information.
  97. - parameter destination: The closure used to determine the destination of the downloaded file.
  98. - returns: The created download request.
  99. */
  100. public func download(_ resumeData: Data, destination: Request.DownloadFileDestination) -> Request {
  101. return download(.resumeData(resumeData), destination: destination)
  102. }
  103. }
  104. // MARK: -
  105. extension Request {
  106. /**
  107. A closure executed once a request has successfully completed in order to determine where to move the temporary
  108. file written to during the download process. The closure takes two arguments: the temporary file URL and the URL
  109. response, and returns a single argument: the file URL where the temporary file should be moved.
  110. */
  111. public typealias DownloadFileDestination = (URL, HTTPURLResponse) -> URL
  112. /**
  113. Creates a download file destination closure which uses the default file manager to move the temporary file to a
  114. file URL in the first available directory with the specified search path directory and search path domain mask.
  115. - parameter directory: The search path directory. `.DocumentDirectory` by default.
  116. - parameter domain: The search path domain mask. `.UserDomainMask` by default.
  117. - returns: A download file destination closure.
  118. */
  119. public class func suggestedDownloadDestination(
  120. directory: FileManager.SearchPathDirectory = .documentDirectory,
  121. domain: FileManager.SearchPathDomainMask = .userDomainMask)
  122. -> DownloadFileDestination
  123. {
  124. return { temporaryURL, response -> URL in
  125. let directoryURLs = FileManager.default.urlsForDirectory(directory, inDomains: domain)
  126. if !directoryURLs.isEmpty {
  127. return try! directoryURLs[0].appendingPathComponent(response.suggestedFilename!)
  128. }
  129. return temporaryURL
  130. }
  131. }
  132. /// The resume data of the underlying download task if available after a failure.
  133. public var resumeData: Data? {
  134. var data: Data?
  135. if let delegate = delegate as? DownloadTaskDelegate {
  136. data = delegate.resumeData
  137. }
  138. return data
  139. }
  140. // MARK: - DownloadTaskDelegate
  141. class DownloadTaskDelegate: TaskDelegate, URLSessionDownloadDelegate {
  142. var downloadTask: URLSessionDownloadTask? { return task as? URLSessionDownloadTask }
  143. var downloadProgress: ((Int64, Int64, Int64) -> Void)?
  144. var resumeData: Data?
  145. override var data: Data? { return resumeData }
  146. // MARK: - NSURLSessionDownloadDelegate
  147. // MARK: Override Closures
  148. var downloadTaskDidFinishDownloadingToURL: ((Foundation.URLSession, URLSessionDownloadTask, URL) -> URL)?
  149. var downloadTaskDidWriteData: ((Foundation.URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)?
  150. var downloadTaskDidResumeAtOffset: ((Foundation.URLSession, URLSessionDownloadTask, Int64, Int64) -> Void)?
  151. // MARK: Delegate Methods
  152. func urlSession(
  153. _ session: URLSession,
  154. downloadTask: URLSessionDownloadTask,
  155. didFinishDownloadingTo location: URL)
  156. {
  157. if let downloadTaskDidFinishDownloadingToURL = downloadTaskDidFinishDownloadingToURL {
  158. do {
  159. let destination = downloadTaskDidFinishDownloadingToURL(session, downloadTask, location)
  160. try FileManager.default.moveItem(at: location, to: destination)
  161. } catch {
  162. self.error = error as NSError
  163. }
  164. }
  165. }
  166. func urlSession(
  167. _ session: URLSession,
  168. downloadTask: URLSessionDownloadTask,
  169. didWriteData bytesWritten: Int64,
  170. totalBytesWritten: Int64,
  171. totalBytesExpectedToWrite: Int64)
  172. {
  173. if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
  174. if let downloadTaskDidWriteData = downloadTaskDidWriteData {
  175. downloadTaskDidWriteData(
  176. session,
  177. downloadTask,
  178. bytesWritten,
  179. totalBytesWritten,
  180. totalBytesExpectedToWrite
  181. )
  182. } else {
  183. progress.totalUnitCount = totalBytesExpectedToWrite
  184. progress.completedUnitCount = totalBytesWritten
  185. downloadProgress?(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
  186. }
  187. }
  188. func urlSession(
  189. _ session: URLSession,
  190. downloadTask: URLSessionDownloadTask,
  191. didResumeAtOffset fileOffset: Int64,
  192. expectedTotalBytes: Int64)
  193. {
  194. if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset {
  195. downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes)
  196. } else {
  197. progress.totalUnitCount = expectedTotalBytes
  198. progress.completedUnitCount = fileOffset
  199. }
  200. }
  201. }
  202. }