TeraUploadRenewal.swift (5958B)
1 import Foundation 2 import RadrootsKit 3 4 /// Explicit foreground user action only. Rust owns budget, expiry and signing. 5 struct TeraUploadRenewal: Sendable { 6 let transfer: any RadrootsBackgroundTransfer 7 let preparer: RadrootsAppleMediaPreparer 8 struct Prior: Sendable { 9 let identifier: RadrootsBackgroundTransferIdentifier 10 let attempt: String 11 let revision: UInt64 12 } 13 14 func run(_ submission: TeraSubmissionStatus, media: TeraPreparedMedia, handle: TeraPreparedMediaHandle, 15 backend: any TeraRuntimeBackend) async throws -> TeraSubmissionStatus 16 { 17 try Task.checkCancellation() 18 guard !submission.delivery.isStopped, 19 let item = submission.media.first(where: { $0.opaqueReference == media.opaqueReference }), 20 !item.authorizations.isEmpty, item.authorizations.count <= 5, 21 let uploadURL = item.progress.uploadURL else { throw TeraNativeUploadExecution.unknown } 22 let inventory = try await transfer.snapshots() 23 let lineage = try Self.identities(item.authorizations, parent: submission.intentID, inventory: inventory) 24 let known = Set(lineage.map(\.identifier)) 25 for snapshot in inventory where snapshot.identifier.rawValue.hasPrefix("radroots.add.\(submission.intentID).") 26 && snapshot.request.expectedSourceSHA256 == media.sha256 27 { 28 guard known.contains(snapshot.identifier), 29 try TeraBackgroundUploadRequest.persistedRequestMatchesMedia(snapshot.request, media: media, uploadURL: uploadURL) 30 else { throw TeraNativeUploadExecution.unknown } 31 } 32 guard let latest = lineage.last else { throw TeraNativeUploadExecution.unknown } 33 return try await Self.holding(lineage, transfer: transfer) { snapshots in 34 try Task.checkCancellation() 35 for snapshot in snapshots { 36 guard try TeraBackgroundUploadRequest.persistedRequestMatchesMedia(snapshot.request, media: media, uploadURL: uploadURL) 37 else { throw TeraNativeUploadExecution.unknown } 38 } 39 let retry = TeraSubmissionUploadRenewal(priorRevision: latest.revision, priorAttempt: latest.attempt, 40 nativeFailed: snapshots.contains { $0.identifier == latest.identifier && [.failed, .expired].contains($0.state) }) 41 let input = TeraSubmissionMediaRequest(request: submission.request, expectedRevision: submission.revision, media: handle) 42 let policy: RadrootsBackgroundTransferNetworkPolicy = uploadURL.hasPrefix("https:") ? .publicHTTPS : .simulatorLoopbackHTTP 43 if !RadrootsAppleBackgroundTransferAdapters.supportsNewEnqueue(for: policy) { 44 return try await backend.renewSubmissionMedia(input: input, renewal: retry) 45 } 46 let job = try await backend.renewSubmissionUpload(input: input, renewal: retry) 47 try Task.checkCancellation() 48 let current = try await backend.submissionStatus(request: submission.request) 49 guard !current.delivery.isStopped, current.revision == job.submission.revision else { throw TeraNativeUploadExecution.unknown } 50 let request = try await TeraBackgroundUploadRequest.prepare(job: job.transfer, media: media, preparer: preparer) 51 guard !known.contains(request.identifier) else { throw TeraNativeUploadExecution.unknown } 52 let active = try await TeraNativeUploadExecution.start(transfer: transfer, request: request, retrying: false) 53 let receipt = try await TeraBackgroundUploadWaiter.receipt(transfer: transfer, for: active.identifier, 54 draftID: submission.intentID, expectedRevision: job.submission.revision, request: request, baseline: active) 55 let status = try await backend.completeSubmissionUpload(input: .init(request: submission.request, 56 expectedRevision: receipt.expectedRevision, media: handle), response: receipt) 57 try await transfer.settle(active.identifier, verification: .accepted) 58 return status 59 } 60 } 61 62 static func identities(_ attempts: [TeraUploadAttemptIdentity], parent: String, 63 inventory: [RadrootsBackgroundTransferSnapshot]) throws -> [Prior] 64 { 65 guard !attempts.isEmpty, attempts.count <= 5 else { throw TeraNativeUploadExecution.unknown } 66 return try attempts.map { attempt in 67 let matches = inventory.compactMap { snapshot -> Prior? in 68 guard let identity = TeraBackgroundUploadRequest.transferIdentity(snapshot.identifier), 69 identity.draftID == parent, identity.attempt == attempt.operationID else { return nil } 70 return Prior(identifier: snapshot.identifier, attempt: identity.attempt, revision: identity.revision) 71 } 72 guard matches.count <= 1 else { throw TeraNativeUploadExecution.unknown } 73 guard let revision = attempt.revision ?? matches.first?.revision, revision > 0, 74 matches.first.map({ $0.revision == revision }) ?? true else { throw TeraNativeUploadExecution.unknown } 75 return try Prior(identifier: RadrootsBackgroundTransferIdentifier("radroots.add.\(parent).\(revision).\(attempt.operationID)"), 76 attempt: attempt.operationID, revision: revision) 77 } 78 } 79 80 static func holding<Result: Sendable>(_ lineage: [Prior], transfer: any RadrootsBackgroundTransfer, 81 snapshots: [RadrootsBackgroundTransferSnapshot] = [], 82 operation: @escaping @Sendable ([RadrootsBackgroundTransferSnapshot]) async throws -> Result) async throws -> Result 83 { 84 guard let first = lineage.first else { return try await operation(snapshots) } 85 return try await transfer.withInactiveExecution(for: first.identifier) { snapshot in 86 try Task.checkCancellation() 87 return try await holding(Array(lineage.dropFirst()), transfer: transfer, 88 snapshots: snapshots + (snapshot.map { [$0] } ?? []), operation: operation) 89 } 90 } 91 }