apple_kit

Apple-native services for Radroots iOS and macOS apps
git clone https://radroots.dev/git/apple_kit.git
Log | Files | Refs | README | LICENSE

RadrootsBackgroundTransferSnapshot.swift (6486B)


      1 import Foundation
      2 
      3 public struct RadrootsBackgroundTransferSnapshot: Sendable, Equatable, Hashable, Codable,
      4     CustomDebugStringConvertible {
      5     public let identifier: RadrootsBackgroundTransferIdentifier
      6     public let request: RadrootsBackgroundTransferRequest
      7     public let state: RadrootsBackgroundTransferState
      8     public let progress: RadrootsBackgroundTransferProgress
      9     public let failure: RadrootsBackgroundTransferFailure?
     10     public let response: RadrootsBackgroundTransferResponse?
     11     public let downloadedArtifact: RadrootsBackgroundDownloadedArtifact?
     12     public let possibleRemoteOrphan: Bool
     13     public let updatedAt: Date
     14     public let executionID: UUID?
     15     public let uploadLease: RadrootsStagedBlobReference?
     16 
     17     public init(
     18         request: RadrootsBackgroundTransferRequest, state: RadrootsBackgroundTransferState = .queued,
     19         progress: RadrootsBackgroundTransferProgress = .zero,
     20         failure: RadrootsBackgroundTransferFailure? = nil,
     21         response: RadrootsBackgroundTransferResponse? = nil,
     22         downloadedArtifact: RadrootsBackgroundDownloadedArtifact? = nil,
     23         possibleRemoteOrphan: Bool = false, updatedAt: Date = Date(),
     24         executionID: UUID? = nil, uploadLease: RadrootsStagedBlobReference? = nil
     25     ) throws {
     26         guard updatedAt.timeIntervalSinceReferenceDate.isFinite else {
     27             throw RadrootsBackgroundTransferError.invalidRequest
     28         }
     29         if let uploadLease {
     30             guard executionID != nil, request.isUpload,
     31                   uploadLease.blobID.range(of: "^[0-9a-f]{64}$", options: .regularExpression) != nil,
     32                   uploadLease.sizeBytes >= 0, UInt64(uploadLease.sizeBytes) <= request.maximumTransferBytes,
     33                   request.expectedSourceSHA256 == nil || request.expectedSourceSHA256 == uploadLease.blobID
     34             else { throw RadrootsBackgroundTransferError.invalidRequest }
     35         }
     36         guard response == nil || state == .awaitingVerification || state == .completed else {
     37             throw RadrootsBackgroundTransferError.invalidRequest
     38         }
     39         if let response {
     40             try RadrootsBackgroundTransferValidation.validate(
     41                 response: response, policy: request.responsePolicy
     42             )
     43         }
     44         if possibleRemoteOrphan {
     45             guard case .upload = request.operation,
     46                   state == .failed || state == .interrupted || state == .cancelled || state == .expired
     47             else {
     48                 throw RadrootsBackgroundTransferError.invalidRequest
     49             }
     50         }
     51         if let downloadedArtifact {
     52             guard case let .download(destination) = request.operation,
     53                   destination == .file(downloadedArtifact.file),
     54                   state == .awaitingVerification || state == .completed
     55             else {
     56                 throw RadrootsBackgroundTransferError.invalidRequest
     57             }
     58         }
     59         identifier = request.identifier
     60         self.request = request
     61         self.state = state
     62         self.progress = progress
     63         self.failure = failure
     64         self.response = response
     65         self.downloadedArtifact = downloadedArtifact
     66         self.possibleRemoteOrphan = possibleRemoteOrphan
     67         self.updatedAt = updatedAt
     68         self.executionID = executionID
     69         self.uploadLease = uploadLease
     70     }
     71 
     72     public var debugDescription: String {
     73         let statusCode = response?.statusCode.description ?? "none"
     74         return "RadrootsBackgroundTransferSnapshot(identifier: \(identifier.rawValue), state: \(state.rawValue), "
     75             + "bytesTransferred: \(progress.bytesTransferred), statusCode: \(statusCode), "
     76             + "downloadedArtifact: \(downloadedArtifact == nil ? "none" : "present"), "
     77             + "possibleRemoteOrphan: \(possibleRemoteOrphan))"
     78     }
     79 
     80     func redactedForPersistence() throws -> Self {
     81         try Self(
     82             request: request.redactedForPersistence(), state: state, progress: progress,
     83             failure: failure, response: response,
     84             downloadedArtifact: downloadedArtifact, possibleRemoteOrphan: possibleRemoteOrphan,
     85             updatedAt: updatedAt, executionID: executionID, uploadLease: uploadLease
     86         )
     87     }
     88 
     89     private enum CodingKeys: String, CodingKey {
     90         case request
     91         case state
     92         case progress
     93         case failure
     94         case response
     95         case downloadedArtifact
     96         case possibleRemoteOrphan
     97         case updatedAt
     98         case executionID
     99         case uploadLease
    100     }
    101 
    102     public init(from decoder: any Decoder) throws {
    103         let values = try decoder.container(keyedBy: CodingKeys.self)
    104         try self.init(
    105             request: values.decode(RadrootsBackgroundTransferRequest.self, forKey: .request),
    106             state: values.decode(RadrootsBackgroundTransferState.self, forKey: .state),
    107             progress: values.decode(RadrootsBackgroundTransferProgress.self, forKey: .progress),
    108             failure: values.decodeIfPresent(RadrootsBackgroundTransferFailure.self, forKey: .failure),
    109             response: values.decodeIfPresent(RadrootsBackgroundTransferResponse.self, forKey: .response),
    110             downloadedArtifact: values.decodeIfPresent(
    111                 RadrootsBackgroundDownloadedArtifact.self, forKey: .downloadedArtifact
    112             ),
    113             possibleRemoteOrphan: values.decodeIfPresent(Bool.self, forKey: .possibleRemoteOrphan)
    114                 ?? false,
    115             updatedAt: values.decode(Date.self, forKey: .updatedAt),
    116             executionID: values.decodeIfPresent(UUID.self, forKey: .executionID),
    117             uploadLease: values.decodeIfPresent(RadrootsStagedBlobReference.self, forKey: .uploadLease)
    118         )
    119     }
    120 
    121     public func encode(to encoder: any Encoder) throws {
    122         var values = encoder.container(keyedBy: CodingKeys.self)
    123         try values.encode(request, forKey: .request)
    124         try values.encode(state, forKey: .state)
    125         try values.encode(progress, forKey: .progress)
    126         try values.encodeIfPresent(failure, forKey: .failure)
    127         try values.encodeIfPresent(response, forKey: .response)
    128         try values.encodeIfPresent(downloadedArtifact, forKey: .downloadedArtifact)
    129         try values.encode(possibleRemoteOrphan, forKey: .possibleRemoteOrphan)
    130         try values.encode(updatedAt, forKey: .updatedAt)
    131         try values.encodeIfPresent(executionID, forKey: .executionID)
    132         try values.encodeIfPresent(uploadLease, forKey: .uploadLease)
    133     }
    134 }