diff --git a/Sources/XCRemoteCache/Network/NetworkClientImpl.swift b/Sources/XCRemoteCache/Network/NetworkClientImpl.swift index e449f36..5f3f803 100644 --- a/Sources/XCRemoteCache/Network/NetworkClientImpl.swift +++ b/Sources/XCRemoteCache/Network/NetworkClientImpl.swift @@ -77,7 +77,12 @@ class NetworkClientImpl: NetworkClient { func download(_ url: URL, to location: URL, completion: @escaping (Result) -> Void) { var request = URLRequest(url: url) setupAuthenticationSignatureIfPresent(&request) - makeDownloadRequest(request, output: location, completion: completion) + makeDownloadRequest( + request, + output: location, + retries: maxRetries, + completion: completion + ) } func upload(_ file: URL, as url: URL, completion: @escaping (Result) -> Void) { @@ -125,7 +130,7 @@ class NetworkClientImpl: NetworkClient { dataTask.resume() } - private func makeDownloadRequest(_ request: URLRequest, output: URL, completion: @escaping (Result) -> Void) { + private func makeDownloadRequest(_ request: URLRequest, output: URL, retries: Int, completion: @escaping (Result) -> Void) { guard fileManager.fileExists(atPath: output.path) == false else { infoLog("Download file found in the destination, skipping download.") completion(.success(())) @@ -135,6 +140,17 @@ class NetworkClientImpl: NetworkClient { let dataTask = session.downloadTask(with: request) { [fileManager] fileURL, _, error in guard let fileURL = fileURL else { let networkError = error.map(NetworkClientError.build) ?? .inconsistentSession + if retries > 0 { + infoLog("Download request failed with \(networkError). Left retries: \(retries).") + self.retryDownload( + request, + output: output, + retries: retries, + completion: completion, + after: self.retryDelay + ) + return + } errorLog("Download request failed: \(networkError)") completion(.failure(networkError)) return @@ -196,7 +212,24 @@ class NetworkClientImpl: NetworkClient { private func retryUpload(_ request: URLRequest, input: URL, retries: Int, completion: @escaping (Result) -> Void, after: TimeInterval) { DispatchQueue.global().asyncAfter(deadline: .now() + after) { [weak self] in guard let self = self else { return } - self.makeUploadRequest(request, input: input, retries: retries - 1, completion: completion) + self.makeUploadRequest( + request, + input: input, + retries: retries - 1, + completion: completion + ) + } + } + + private func retryDownload(_ request: URLRequest, output: URL, retries: Int, completion: @escaping (Result) -> Void, after: TimeInterval) { + DispatchQueue.global().asyncAfter(deadline: .now() + after) { [weak self] in + guard let self = self else { return } + self.makeDownloadRequest( + request, + output: output, + retries: retries - 1, + completion: completion + ) } } } diff --git a/Tests/XCRemoteCacheTests/Network/NetworkClientImplTests.swift b/Tests/XCRemoteCacheTests/Network/NetworkClientImplTests.swift index 4873aa6..ab525fc 100644 --- a/Tests/XCRemoteCacheTests/Network/NetworkClientImplTests.swift +++ b/Tests/XCRemoteCacheTests/Network/NetworkClientImplTests.swift @@ -167,7 +167,7 @@ class NetworkClientImplTests: XCTestCase { func testUploadSuccessDoesntRetry() throws { client = NetworkClientImpl( session: session, - retries: 0, + retries: 2, retryDelay: 0, fileManager: fileManager, awsV4Signature: nil @@ -178,6 +178,38 @@ class NetworkClientImplTests: XCTestCase { XCTAssertEqual(requests.map(\.url), [url], "Expected 1 request - original only") } + func testDownloadFilureWith400Retries() throws { + client = NetworkClientImpl( + session: session, + retries: 2, + retryDelay: 0, + fileManager: fileManager, + awsV4Signature: nil + ) + responses[url] = nil + _ = try waitForResponse({ client.download(url, to: fileURL, completion: $0) }, timeout: 0.5) + + XCTAssertEqual( + requests.map(\.url), + Array(repeating: url, count: 3), + "Expected 3 requests (original + 2 retries)" + ) + } + + func testDownloadSuccessDoesntRetry() throws { + client = NetworkClientImpl( + session: session, + retries: 2, + retryDelay: 0, + fileManager: fileManager, + awsV4Signature: nil + ) + responses[url] = .success(successResponse, Data()) + _ = try waitForResponse { client.download(url, to: fileURL, completion: $0) } + + XCTAssertEqual(requests.map(\.url), [url], "Expected 1 request - original only") + } + func testFileExits400CompletesWithFalse() throws { responses[url] = .success(failureResponse, Data()) let response = try waitForResponse { client.fileExists(url, completion: $0) }