field_ios

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

TeraSubmissionOperationModels.swift (4638B)


      1 import Foundation
      2 
      3 struct TeraSubmissionMedia: Sendable, Equatable {
      4   let opaqueReference: String
      5   let progress: TeraDraftMediaStatus
      6   var authorizations: [TeraUploadAttemptIdentity] = []
      7 }
      8 
      9 struct TeraUploadAttemptIdentity: Sendable, Equatable {
     10   let operationID: String
     11   let revision: UInt64?
     12   let expirationUnixSeconds: UInt64
     13 }
     14 
     15 struct TeraSubmissionUploadRenewal: Sendable, Equatable {
     16   let priorRevision: UInt64
     17   let priorAttempt: String
     18   let nativeFailed: Bool
     19 }
     20 
     21 struct TeraSubmissionStatus: Sendable, Equatable, Identifiable, CustomStringConvertible, CustomDebugStringConvertible {
     22   let request: TeraSubmissionRequest
     23   let intentID: String
     24   let operationID: String
     25   let revision: UInt64
     26   let captured: TeraComposerDraft
     27   let state: TeraOutboxState
     28   let committedAtUnixMilliseconds: UInt64
     29   let updatedAtUnixMilliseconds: UInt64
     30   let media: [TeraSubmissionMedia]
     31   let settlement: TeraOperationSettlement
     32   let delivery: TeraPublicationEvidence
     33   let targetDetails: TeraPublicationTargets
     34   let retry: TeraPublicationRetry
     35 
     36   var id: String {
     37     operationID
     38   }
     39 
     40   var description: String {
     41     "TeraSubmissionStatus(\(state.rawValue))"
     42   }
     43 
     44   var debugDescription: String {
     45     description
     46   }
     47 
     48   var summary: String {
     49     if delivery.isStopped {
     50       return delivery.stoppedSummary
     51     }
     52     if media.contains(where: \.progress.possibleOrphan) {
     53       return "Photo delivery needs attention. A remote copy may exist."
     54     }
     55     if delivery.state == .unknown {
     56       return "Relay delivery outcome is unknown. Check the original request before continuing."
     57     }
     58     if delivery.state == .partiallyAccepted {
     59       return "Some saved relays accepted this publication. Remaining delivery is not confirmed."
     60     }
     61     if case .needsAction = retry, delivery.state != .accepted {
     62       return "Saved publication needs attention."
     63     }
     64     return state.label
     65   }
     66 
     67   var canOfferStop: Bool {
     68     !delivery.isStopped && (state.canCancel || delivery.unresolvedClaims)
     69   }
     70 
     71   /// Presentation of an explicit action, never authority to bypass Rust policy.
     72   var canOfferContinuation: Bool {
     73     retry.mayStart && !delivery.isStopped && state != .complete && state != .cancelled && state != .terminal
     74   }
     75 
     76   var mediaSummary: String {
     77     let verified = media.filter { $0.progress.stage == .verified }.count
     78     let orphans = media.filter(\.progress.possibleOrphan).count
     79     let uncertain = media.filter { $0.progress.stage == .uploading }.count
     80     let summary = "\(verified) of \(media.count) photos verified"
     81     if delivery.isStopped, uncertain > 0 {
     82       return "\(summary); \(uncertain) upload outcome remains uncertain. Remote copies may exist."
     83     }
     84     return orphans > 0 ? "\(summary); \(orphans) possible orphan" : summary
     85   }
     86 
     87   /// Translation only: these URLs come from the validated Rust intent.
     88   var preparedMedia: [TeraPreparedMedia] {
     89     zip(captured.form.editingValue.media, media).map { source, state in
     90       TeraPreparedMedia(opaqueReference: source.opaqueReference, remoteURL: state.progress.url,
     91                         sha256: source.sha256, mediaType: source.mediaType, byteSize: source.byteSize,
     92                         width: source.width, height: source.height, alt: source.alt,
     93                         preparedAtUnixSeconds: source.preparedAtUnixSeconds)
     94     }
     95   }
     96 }
     97 
     98 enum TeraSubmissionSummaryState: Sendable, Equatable {
     99   case reserved
    100   case operation(intentID: String, operationID: String, revision: UInt64, state: TeraOutboxState)
    101 }
    102 
    103 enum TeraSubmissionRepairReason: Sendable, Equatable {
    104   case unsupportedSchema
    105   case corruptRecord
    106   case needsAttention
    107 }
    108 
    109 struct TeraSubmissionSummary: Sendable, Equatable, Identifiable {
    110   let request: TeraSubmissionRequest
    111   let reservationID: String
    112   let reservedAtUnixMilliseconds: UInt64
    113   let state: TeraSubmissionSummaryState
    114   var id: String {
    115     reservationID
    116   }
    117 }
    118 
    119 enum TeraSubmissionEntry: Sendable, Equatable, Identifiable {
    120   case submission(TeraSubmissionSummary)
    121   case repair(key: String, revision: UInt64, reason: TeraSubmissionRepairReason)
    122 
    123   var id: String {
    124     switch self {
    125     case let .submission(summary): summary.id
    126     case let .repair(key, _, _): key
    127     }
    128   }
    129 }
    130 
    131 struct TeraSubmissionPage: Sendable, Equatable {
    132   let scope: TeraComposerScope
    133   let entries: [TeraSubmissionEntry]
    134   let nextCursor: String?
    135 }
    136 
    137 struct TeraSubmissionMediaRequest: Sendable, Equatable {
    138   let request: TeraSubmissionRequest
    139   let expectedRevision: UInt64
    140   let media: TeraPreparedMediaHandle
    141 }
    142 
    143 struct TeraSubmissionUploadJob: Sendable, Equatable {
    144   let submission: TeraSubmissionStatus
    145   let transfer: TeraNativeTransferJob
    146 }