@@ -170,64 +170,61 @@ public actor WordPressAPI {
170170 }
171171
172172#if PROGRESS_REPORTING_ENABLED
173- public func uploadMedia(
174- params: MediaCreateParams ,
175- fulfilling progress: Progress
176- ) async throws -> MediaRequestCreateResponse {
173+ /// Track the progress of the given HTTP API calls in the `apiCall` closure.
174+ ///
175+ /// Note: pass the `RequestContext` parameter in `apiCall` to one and only one HTTP API call.
176+ public func fulfill< R: Sendable > (
177+ progress: Progress ,
178+ withApiCall apiCall: sending @escaping ( RequestContext ) async throws -> R
179+ ) async throws -> R {
177180 precondition ( progress. completedUnitCount == 0 && progress. totalUnitCount > 0 )
178181 precondition ( progress. cancellationHandler == nil )
179182
180183 let context = RequestContext ( )
181184
182185 let uploadTask = Task {
183- try await media. createCancellation ( params: params, context: context)
186+ try await withTaskCancellationHandler {
187+ try await apiCall ( context)
188+ } onCancel: {
189+ requestExecutor. cancel ( context: context)
190+ }
184191 }
185192
186193 let progressObserver = Task {
187- // A request id will be put into the `RequestContext` during the execution of the `media.create` above.
188- // This loop waits for the request id becomes available
189- let requestId : String
190- while true {
191- try await Task . sleep ( nanoseconds: 100_000 )
192- try Task . checkCancellation ( )
193-
194- guard let id = context. requestIds ( ) . first else {
195- continue
196- }
197-
198- requestId = id
199- break
194+ for await task in requestExecutor. progresses ( for: context) . values {
195+ // For one single request call, the Rust layer should send HTTP requests sequentially.
196+ // For example, the retry mechanism in the Rust layer only send the retry call when the initial
197+ // call fails.
198+ //
199+ // Since we can't know how many HTTP requests will be sent, the best we can do is make the `progress`
200+ // starts from zero to complete for each HTTP request.
201+ progress. completedUnitCount = 0
202+ progress. addChild ( task, withPendingUnitCount: progress. totalUnitCount)
200203 }
201-
202- // Get the progress of the `URLSessionTask` of the given request id.
203- guard let task = await requestExecutor
204- . progress ( forRequestWithId: requestId)
205- . values
206- . first ( where: { _ in true } ) else { return }
207-
208- try Task . checkCancellation ( )
209-
210- progress. addChild ( task, withPendingUnitCount: progress. totalUnitCount - progress. completedUnitCount)
211204 }
212205
213206 progress. cancellationHandler = {
214207 uploadTask. cancel ( )
215208 progressObserver. cancel ( )
216209 }
217210
211+ defer { progressObserver. cancel ( ) }
212+
218213 return try await withTaskCancellationHandler {
219214 try await uploadTask. value
220215 } onCancel: {
221- // Please note: the async functions exported by uniffi-rs _do not_ support cancellation.
222- // That means cancelling an API call like `Task { try await api.users.retrieveMe() }.cancel()`
223- // does not cancel the underlying HTTP request sent by URLSession.
224- //
225- // The `progress.cancel()` in this particular function can cancel the HTTP request, because the
226- // `progress` instance is the parent progress of `URLSessionTask.progress`, and cancelling a parent
227- // progress automatically cancels their child progress, which is the `URLSessionTask` in this case.
228216 progress. cancel ( )
229217 }
230218 }
219+
220+ public func uploadMedia(
221+ params: MediaCreateParams ,
222+ fulfilling progress: Progress
223+ ) async throws -> MediaRequestCreateResponse {
224+ try await fulfill ( progress: progress) { [ media] in
225+ try await media. createCancellation ( params: params, context: $0)
226+ }
227+ }
231228#endif
232229
233230 enum ParseError : Error {
0 commit comments