From 042fe93131b97aca3eed7585ddb0b2c823084d11 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Tue, 4 Nov 2025 09:08:18 -0300 Subject: [PATCH] refactor: custom AsyncSequence for HTTPBody progress tracking --- .../Interface/HTTPBody+Progress.swift | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/Sources/HTTPClient/Interface/HTTPBody+Progress.swift b/Sources/HTTPClient/Interface/HTTPBody+Progress.swift index 6f7cd6d..bb5d274 100644 --- a/Sources/HTTPClient/Interface/HTTPBody+Progress.swift +++ b/Sources/HTTPClient/Interface/HTTPBody+Progress.swift @@ -2,8 +2,7 @@ import Foundation extension HTTPBody { /// Tracks the progress of the body and calls the handler. - public func trackingProgress(handler: @escaping (Progress) -> Void) -> HTTPBody { - var totalBytesProcessed: Int64 = 0 + public func trackingProgress(handler: @escaping @Sendable (Progress) -> Void) -> HTTPBody { let totalLength: Int64? = switch self.length { case .known(let length): @@ -12,16 +11,51 @@ extension HTTPBody { nil } - let sequence = self.map { chunk in + let sequence = ProgressTrackingSequence( + base: self, + totalLength: totalLength, + handler: handler + ) + + return HTTPBody(sequence, length: self.length, iterationBehavior: self.iterationBehavior) + } +} + +/// An async sequence that tracks progress and forwards chunks from the underlying sequence. +private struct ProgressTrackingSequence: AsyncSequence, Sendable { + typealias Element = HTTPBody.ByteChunk + + let base: HTTPBody + let totalLength: Int64? + let handler: @Sendable (Progress) -> Void + + func makeAsyncIterator() -> Iterator { + Iterator( + base: base.makeAsyncIterator(), + totalLength: totalLength, + handler: handler + ) + } + + struct Iterator: AsyncIteratorProtocol { + var base: HTTPBody.Iterator + let totalLength: Int64? + let handler: @Sendable (Progress) -> Void + var totalBytesProcessed: Int64 = 0 + + mutating func next() async throws -> Element? { + guard let chunk = try await base.next() else { + return nil + } + let chunkSize = Int64(chunk.count) totalBytesProcessed += chunkSize let progress = Progress(completed: totalBytesProcessed, total: totalLength) handler(progress) + return chunk } - - return HTTPBody(sequence, length: self.length, iterationBehavior: self.iterationBehavior) } }