Skip to content

Commit 0a7c437

Browse files
authored
Expand progress tracking to all API calls (#987)
1 parent fb107b4 commit 0a7c437

File tree

3 files changed

+40
-41
lines changed

3 files changed

+40
-41
lines changed

native/swift/Sources/wordpress-api/SafeRequestExecutor.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ public protocol SafeRequestExecutor: RequestExecutor, Sendable {
1818
func upload(request: WpMultipartFormRequest) async -> Result<WpNetworkResponse, RequestExecutionError>
1919

2020
#if PROGRESS_REPORTING_ENABLED
21-
/// Returns a publisher that emits zero or one `Progress` instance representing the overall progress of the task
22-
/// for the given `requestId`.
23-
func progress(forRequestWithId requestId: String) -> AnyPublisher<Progress, Never>
21+
func progresses(for context: RequestContext) -> AnyPublisher<Progress, Never>
2422
#endif
2523
}
2624

@@ -124,10 +122,14 @@ public final class WpRequestExecutor: SafeRequestExecutor {
124122
}
125123

126124
#if PROGRESS_REPORTING_ENABLED
127-
public func progress(forRequestWithId requestId: String) -> AnyPublisher<Progress, Never> {
125+
public func progresses(for context: RequestContext) -> AnyPublisher<Progress, Never> {
128126
NotificationCenter.default.publisher(for: RequestExecutorDelegate.didCreateTaskNotification)
129127
.compactMap { $0.object as? URLSessionTask }
130-
.first { $0.originalRequest?.requestId == requestId }
128+
.filter {
129+
guard let requestId = $0.originalRequest?.requestId else { return false }
130+
131+
return context.requestIds().contains(requestId)
132+
}
131133
.map { $0.progress }
132134
.eraseToAnyPublisher()
133135
}

native/swift/Sources/wordpress-api/WordPressAPI.swift

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -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 {

native/swift/Tests/wordpress-api/Support/HTTPStubs.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ final class HTTPStubs: SafeRequestExecutor {
5353
}
5454

5555
#if PROGRESS_REPORTING_ENABLED
56-
func progress(forRequestWithId requestId: String) -> AnyPublisher<Progress, Never> {
56+
func progresses(for context: RequestContext) -> AnyPublisher<Progress, Never> {
5757
Record(output: [], completion: .finished).eraseToAnyPublisher()
5858
}
5959
#endif

0 commit comments

Comments
 (0)