Download.swift 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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(NSURLRequest)
  28. case ResumeData(NSData)
  29. }
  30. private func download(downloadable: Downloadable, destination: Request.DownloadFileDestination) -> Request {
  31. var downloadTask: NSURLSessionDownloadTask!
  32. switch downloadable {
  33. case .Request(let request):
  34. dispatch_sync(queue) {
  35. downloadTask = self.session.downloadTaskWithRequest(request)
  36. }
  37. case .ResumeData(let resumeData):
  38. dispatch_sync(queue) {
  39. downloadTask = self.session.downloadTaskWithResumeData(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! NSHTTPURLResponse)
  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: NSData, 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 = (NSURL, NSHTTPURLResponse) -> NSURL
  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 directory: NSSearchPathDirectory = .DocumentDirectory,
  121. domain: NSSearchPathDomainMask = .UserDomainMask)
  122. -> DownloadFileDestination
  123. {
  124. return { temporaryURL, response -> NSURL in
  125. let directoryURLs = NSFileManager.defaultManager().URLsForDirectory(directory, inDomains: domain)
  126. if !directoryURLs.isEmpty {
  127. #if swift(>=2.3)
  128. return directoryURLs[0].URLByAppendingPathComponent(response.suggestedFilename!)!
  129. #else
  130. return directoryURLs[0].URLByAppendingPathComponent(response.suggestedFilename!)
  131. #endif
  132. }
  133. return temporaryURL
  134. }
  135. }
  136. /// The resume data of the underlying download task if available after a failure.
  137. public var resumeData: NSData? {
  138. var data: NSData?
  139. if let delegate = delegate as? DownloadTaskDelegate {
  140. data = delegate.resumeData
  141. }
  142. return data
  143. }
  144. // MARK: - DownloadTaskDelegate
  145. class DownloadTaskDelegate: TaskDelegate, NSURLSessionDownloadDelegate {
  146. var downloadTask: NSURLSessionDownloadTask? { return task as? NSURLSessionDownloadTask }
  147. var downloadProgress: ((Int64, Int64, Int64) -> Void)?
  148. var resumeData: NSData?
  149. override var data: NSData? { return resumeData }
  150. // MARK: - NSURLSessionDownloadDelegate
  151. // MARK: Override Closures
  152. var downloadTaskDidFinishDownloadingToURL: ((NSURLSession, NSURLSessionDownloadTask, NSURL) -> NSURL)?
  153. var downloadTaskDidWriteData: ((NSURLSession, NSURLSessionDownloadTask, Int64, Int64, Int64) -> Void)?
  154. var downloadTaskDidResumeAtOffset: ((NSURLSession, NSURLSessionDownloadTask, Int64, Int64) -> Void)?
  155. // MARK: Delegate Methods
  156. func URLSession(
  157. session: NSURLSession,
  158. downloadTask: NSURLSessionDownloadTask,
  159. didFinishDownloadingToURL location: NSURL)
  160. {
  161. if let downloadTaskDidFinishDownloadingToURL = downloadTaskDidFinishDownloadingToURL {
  162. do {
  163. let destination = downloadTaskDidFinishDownloadingToURL(session, downloadTask, location)
  164. try NSFileManager.defaultManager().moveItemAtURL(location, toURL: destination)
  165. } catch {
  166. self.error = error as NSError
  167. }
  168. }
  169. }
  170. func URLSession(
  171. session: NSURLSession,
  172. downloadTask: NSURLSessionDownloadTask,
  173. didWriteData bytesWritten: Int64,
  174. totalBytesWritten: Int64,
  175. totalBytesExpectedToWrite: Int64)
  176. {
  177. if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
  178. if let downloadTaskDidWriteData = downloadTaskDidWriteData {
  179. downloadTaskDidWriteData(
  180. session,
  181. downloadTask,
  182. bytesWritten,
  183. totalBytesWritten,
  184. totalBytesExpectedToWrite
  185. )
  186. } else {
  187. progress.totalUnitCount = totalBytesExpectedToWrite
  188. progress.completedUnitCount = totalBytesWritten
  189. downloadProgress?(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
  190. }
  191. }
  192. func URLSession(
  193. session: NSURLSession,
  194. downloadTask: NSURLSessionDownloadTask,
  195. didResumeAtOffset fileOffset: Int64,
  196. expectedTotalBytes: Int64)
  197. {
  198. if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset {
  199. downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes)
  200. } else {
  201. progress.totalUnitCount = expectedTotalBytes
  202. progress.completedUnitCount = fileOffset
  203. }
  204. }
  205. }
  206. }