field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

TeraBackgroundUploadWaiter.swift (5698B)


      1 import Foundation
      2 import RadrootsKit
      3 
      4 /// Exact retained-request discovery and cancellation-aware response waiting.
      5 enum TeraBackgroundUploadWaiter {
      6   static func matchingPersistedUpload(
      7     transfer: any RadrootsBackgroundTransfer,
      8     draftID: String,
      9     expectedRevision: UInt64,
     10     request: RadrootsBackgroundTransferRequest
     11   ) async throws -> RadrootsBackgroundTransferSnapshot? {
     12     try Task.checkCancellation()
     13     let snapshots = try await transfer.snapshots()
     14     let prefix = "radroots.add.\(draftID)."
     15     let owned = snapshots.filter { $0.identifier.rawValue.hasPrefix(prefix) }
     16     let parsed = try owned.map { snapshot in
     17       guard let identity = TeraBackgroundUploadRequest.transferIdentity(snapshot.identifier),
     18         identity.draftID == draftID,
     19         identity.revision <= expectedRevision
     20       else {
     21         throw Self.failure(
     22           code: "ios.add.background_upload_mismatch",
     23           message: "The persisted photo upload identity is invalid."
     24         )
     25       }
     26       return snapshot
     27     }
     28     let candidates = parsed.filter { snapshot in
     29       snapshot.state != .completed
     30         || TeraBackgroundUploadRequest.persistedRequestMatches(snapshot.request, request: request)
     31     }
     32     try Task.checkCancellation()
     33     let active = candidates.filter { $0.state != .completed }
     34     guard active.count <= 1 else {
     35       throw Self.failure(
     36         code: "ios.add.background_upload_ambiguous",
     37         message: "The persisted photo upload state is ambiguous."
     38       )
     39     }
     40     if let candidate = active.first {
     41       guard TeraBackgroundUploadRequest.persistedRequestMatches(candidate.request, request: request) else {
     42         throw Self.failure(
     43           code: "ios.add.background_upload_mismatch",
     44           message: "The persisted photo upload does not match the authorized upload."
     45         )
     46       }
     47       return candidate
     48     }
     49     let completed = candidates.filter {
     50       $0.state == .completed && TeraBackgroundUploadRequest.persistedRequestMatches($0.request, request: request)
     51     }
     52     guard completed.count <= 1 else {
     53       throw Self.failure(
     54         code: "ios.add.background_upload_ambiguous",
     55         message: "The persisted photo upload state is ambiguous."
     56       )
     57     }
     58     return completed.first
     59   }
     60 
     61   static func receipt(
     62     transfer: any RadrootsBackgroundTransfer,
     63     for identifier: RadrootsBackgroundTransferIdentifier,
     64     draftID: String,
     65     expectedRevision: UInt64,
     66     request: RadrootsBackgroundTransferRequest,
     67     baseline: RadrootsBackgroundTransferSnapshot? = nil,
     68     waitNanoseconds: UInt64 = TeraRuntimeDeadlinePolicy.production.operationNanoseconds
     69   ) async throws -> TeraAddBackgroundUploadReceipt {
     70     guard waitNanoseconds > 0, waitNanoseconds <= TeraRuntimeDeadlinePolicy.production.operationNanoseconds,
     71       let identity = TeraBackgroundUploadRequest.transferIdentity(identifier),
     72       identity.draftID == draftID, identity.revision <= expectedRevision
     73     else { throw TeraNativeUploadExecution.unknown }
     74     let clock = ContinuousClock()
     75     let deadline = clock.now.advanced(by: .nanoseconds(Int64(waitNanoseconds)))
     76     var observed = baseline
     77     while true {
     78       try Task.checkCancellation()
     79       guard clock.now < deadline else { throw TeraNativeUploadExecution.unknown }
     80       guard let snapshot = try await transfer.snapshot(for: identifier) else {
     81         throw failure(
     82           code: "ios.add.background_upload_missing",
     83           message: "The background photo upload could not be recovered."
     84         )
     85       }
     86       try Task.checkCancellation()
     87       guard clock.now < deadline else { throw TeraNativeUploadExecution.unknown }
     88       guard snapshot.identifier == identifier,
     89         TeraBackgroundUploadRequest.persistedRequestMatches(snapshot.request, request: request),
     90         observed == nil || observed?.executionID == snapshot.executionID
     91       else {
     92         throw Self.failure(
     93           code: "ios.add.background_upload_mismatch",
     94           message: "The persisted photo upload no longer matches its request."
     95         )
     96       }
     97       observed = snapshot
     98       switch snapshot.state {
     99       case .awaitingVerification, .completed:
    100         try Task.checkCancellation()
    101         return try completedReceipt(snapshot, identity: identity)
    102       case .failed, .interrupted, .cancelled, .expired:
    103         throw Self.failure(
    104           code: snapshot.failure?.rawValue ?? "ios.add.background_upload_failed",
    105           message: TeraUserMessages.text(.backgroundTransferFailed)
    106         )
    107       case .queued, .running:
    108         try await clock.sleep(until: min(deadline, clock.now.advanced(by: .milliseconds(100))))
    109       }
    110     }
    111   }
    112 
    113   private static func completedReceipt(_ snapshot: RadrootsBackgroundTransferSnapshot,
    114                                        identity: TeraNativeTransferIdentity) throws -> TeraAddBackgroundUploadReceipt
    115   {
    116     guard let response = snapshot.response,
    117       let statusCode = UInt16(exactly: response.statusCode), let body = response.body
    118     else {
    119       throw failure(code: "ios.add.background_response_invalid",
    120                     message: "The photo service returned an invalid response.")
    121     }
    122     return TeraAddBackgroundUploadReceipt(identifier: snapshot.identifier.rawValue,
    123                                           draftID: identity.draftID, expectedRevision: identity.revision,
    124                                           statusCode: statusCode, mediaType: response.mediaType,
    125                                           contentEncoding: response.contentEncoding, body: body)
    126   }
    127 
    128   private static func failure(code: String, message: String) -> TeraRuntimeFailure {
    129     .local(operation: "add.media.background", code: code, safeMessage: message)
    130   }
    131 }