Explorar el Código

Add Support for Resume Data on Error (#3419)

* Add resume data from error support.

* Add new download endpoint, additional test.
Jon Shier hace 4 años
padre
commit
b359efe63c
Se han modificado 4 ficheros con 51 adiciones y 21 borrados
  1. 5 0
      Source/AFError.swift
  2. 3 3
      Source/Request.swift
  3. 35 11
      Tests/DownloadTests.swift
  4. 8 7
      Tests/TestHelpers.swift

+ 5 - 0
Source/AFError.swift

@@ -433,6 +433,11 @@ extension AFError {
         guard case let .downloadedFileMoveFailed(_, _, destination) = self else { return nil }
         return destination
     }
+
+    /// The download resume data of any underlying network error. Only produced by `DownloadRequest`s.
+    public var downloadResumeData: Data? {
+        (underlyingError as? URLError)?.userInfo[NSURLSessionDownloadTaskResumeData] as? Data
+    }
 }
 
 extension AFError.ParameterEncodingFailureReason {

+ 3 - 3
Source/Request.swift

@@ -1528,11 +1528,11 @@ public class DownloadRequest: Request {
     @Protected
     private var mutableDownloadState = DownloadRequestMutableState()
 
-    /// If the download is resumable and eventually cancelled, this value may be used to resume the download using the
-    /// `download(resumingWith data:)` API.
+    /// If the download is resumable and is eventually cancelled or fails, this value may be used to resume the download
+    /// using the `download(resumingWith data:)` API.
     ///
     /// - Note: For more information about `resumeData`, see [Apple's documentation](https://developer.apple.com/documentation/foundation/urlsessiondownloadtask/1411634-cancel).
-    public var resumeData: Data? { mutableDownloadState.resumeData }
+    public var resumeData: Data? { mutableDownloadState.resumeData ?? error?.downloadResumeData }
     /// If the download is successful, the `URL` where the file was downloaded.
     public var fileURL: URL? { mutableDownloadState.fileURL }
 

+ 35 - 11
Tests/DownloadTests.swift

@@ -500,8 +500,6 @@ final class DownloadRequestEventsTestCase: BaseTestCase {
 // MARK: -
 
 final class DownloadResumeDataTestCase: BaseTestCase {
-    let endpoint = Endpoint.largeImage
-
     func testThatCancelledDownloadRequestDoesNotProduceResumeData() {
         // Given
         let expectation = self.expectation(description: "Download should be cancelled")
@@ -510,9 +508,8 @@ final class DownloadResumeDataTestCase: BaseTestCase {
         var response: DownloadResponse<URL?, AFError>?
 
         // When
-        let download = AF.download(endpoint)
+        let download = AF.download(.download())
         download.downloadProgress { [unowned download] progress in
-            NSLog("%@", progress)
             guard !cancelled else { return }
 
             if progress.fractionCompleted > 0.1 {
@@ -525,7 +522,7 @@ final class DownloadResumeDataTestCase: BaseTestCase {
             expectation.fulfill()
         }
 
-        waitForExpectations(timeout: 10)
+        waitForExpectations(timeout: timeout)
 
         // Then
         XCTAssertNotNil(response?.request)
@@ -537,6 +534,34 @@ final class DownloadResumeDataTestCase: BaseTestCase {
         XCTAssertNil(download.resumeData)
     }
 
+    func testThatDownloadRequestProducesResumeDataOnError() {
+        // Given
+        let expectation = self.expectation(description: "download complete")
+
+        var response: DownloadResponse<URL?, AFError>?
+
+        // When
+        let download = AF.download(.download(produceError: true))
+        download.response { resp in
+            response = resp
+            expectation.fulfill()
+        }
+
+        waitForExpectations(timeout: timeout)
+
+        // Then
+        XCTAssertNotNil(response?.request)
+        XCTAssertNotNil(response?.response)
+        XCTAssertNil(response?.fileURL)
+        XCTAssertNotNil(response?.error)
+
+        XCTAssertNotNil(download.error?.downloadResumeData)
+        XCTAssertNotNil(response?.resumeData)
+        XCTAssertNotNil(download.resumeData)
+        XCTAssertEqual(download.error?.downloadResumeData, response?.resumeData)
+        XCTAssertEqual(response?.resumeData, download.resumeData)
+    }
+
     func testThatCancelledDownloadResponseDataMatchesResumeData() {
         // Given
         let expectation = self.expectation(description: "Download should be cancelled")
@@ -545,7 +570,7 @@ final class DownloadResumeDataTestCase: BaseTestCase {
         var response: DownloadResponse<URL?, AFError>?
 
         // When
-        let download = AF.download(endpoint)
+        let download = AF.download(.download())
         download.downloadProgress { [unowned download] progress in
             guard !cancelled else { return }
 
@@ -581,7 +606,7 @@ final class DownloadResumeDataTestCase: BaseTestCase {
         var response: DownloadResponse<Any, AFError>?
 
         // When
-        let download = AF.download(endpoint)
+        let download = AF.download(.download())
         download.downloadProgress { [unowned download] progress in
             guard !cancelled else { return }
 
@@ -610,8 +635,7 @@ final class DownloadResumeDataTestCase: BaseTestCase {
         XCTAssertEqual(response?.resumeData, download.resumeData)
     }
 
-    // Disabled until we can find another source which supports resume ranges.
-    func _testThatCancelledDownloadCanBeResumedWithResumeData() {
+    func testThatCancelledDownloadCanBeResumedWithResumeData() {
         // Given
         let expectation1 = expectation(description: "Download should be cancelled")
         var cancelled = false
@@ -619,7 +643,7 @@ final class DownloadResumeDataTestCase: BaseTestCase {
         var response1: DownloadResponse<Data, AFError>?
 
         // When
-        let download = AF.download(endpoint)
+        let download = AF.download(.download())
         download.downloadProgress { [unowned download] progress in
             guard !cancelled else { return }
 
@@ -679,7 +703,7 @@ final class DownloadResumeDataTestCase: BaseTestCase {
         var response: DownloadResponse<URL?, AFError>?
 
         // When
-        let download = AF.download(endpoint)
+        let download = AF.download(.download())
         download.downloadProgress { [unowned download] progress in
             guard !cancelled else { return }
 

+ 8 - 7
Tests/TestHelpers.swift

@@ -64,10 +64,10 @@ struct Endpoint {
         case compression(Compression)
         case delay(interval: Int)
         case digestAuth(qop: String = "auth", username: String, password: String)
+        case download(count: Int)
         case hiddenBasicAuth(username: String, password: String)
         case image(Image)
         case ip
-        case largeImage
         case method(HTTPMethod)
         case payloads(count: Int)
         case redirect(count: Int)
@@ -91,14 +91,14 @@ struct Endpoint {
                 return "/delay/\(interval)"
             case let .digestAuth(qop, username, password):
                 return "/digest-auth/\(qop)/\(username)/\(password)"
+            case let .download(count):
+                return "/download/\(count)"
             case let .hiddenBasicAuth(username, password):
                 return "/hidden-basic-auth/\(username)/\(password)"
             case let .image(type):
                 return "/image/\(type.rawValue)"
             case .ip:
                 return "/ip"
-            case .largeImage:
-                return "/image/large"
             case let .method(method):
                 return "/\(method.rawValue.lowercased())"
             case let .payloads(count):
@@ -155,6 +155,11 @@ struct Endpoint {
         Endpoint(path: .digestAuth(username: user, password: password))
     }
 
+    static func download(_ count: Int = 10_000, produceError: Bool = false) -> Endpoint {
+        Endpoint(path: .download(count: count), queryItems: [.init(name: "shouldProduceError",
+                                                                   value: "\(produceError)")])
+    }
+
     static func hiddenBasicAuth(forUser user: String = "user", password: String = "password") -> Endpoint {
         Endpoint(path: .hiddenBasicAuth(username: user, password: password),
                  headers: [.authorization(username: user, password: password)])
@@ -168,10 +173,6 @@ struct Endpoint {
         Endpoint(path: .ip)
     }
 
-    static var largeImage: Endpoint {
-        Endpoint(path: .largeImage)
-    }
-
     static func method(_ method: HTTPMethod) -> Endpoint {
         Endpoint(path: .method(method), method: method)
     }