field_ios

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

TeraComposerMediaOwnership.swift (2213B)


      1 import CryptoKit
      2 import Foundation
      3 import RadrootsKit
      4 
      5 /// The Apple host proves file durability; Rust remains the sole owner of scoped
      6 /// composer references. The two stores deliberately have no shared transaction.
      7 enum TeraComposerMediaOwnership {
      8   static func confirm(_ media: [TeraComposerMedia], roots: RadrootsAppleFileRoots) throws {
      9     guard !media.isEmpty else { return }
     10     let durable = try TeraDurableMediaRoots.selectingStaging(in: roots)
     11     guard media.count <= 20, roots.stagedBlobsRoot == durable.stagedBlobsRoot else {
     12       throw TeraComposerAcknowledgment.unconfirmed
     13     }
     14     let access = RadrootsAppleFileAccess(roots: roots)
     15     for item in media {
     16       try Task.checkCancellation()
     17       let blob = try reference(item)
     18       try TeraDurableMediaRoots.restoreLegacyBlob(blob, roots: roots)
     19       // Governed reads reject symlinks and oversized/replaced files. Process one
     20       // derivative at a time under the preparer's existing 10 MiB output bound.
     21       let bytes = try access.readStagedBlob(blob)
     22       let digest = SHA256.hash(data: bytes).map { String(format: "%02x", $0) }.joined()
     23       guard digest == item.sha256 else { throw TeraComposerAcknowledgment.unconfirmed }
     24       // This exact install flushes both file and directory, including an
     25       // existing matching object. It never deletes another draft's reference.
     26       try access.installStagedBlob(bytes, reference: blob)
     27     }
     28     // Hash-named published files are identifiable orphans if the following DB
     29     // commit fails or its result is lost. Keep them for C087 reconciliation.
     30   }
     31 
     32   private static func reference(_ item: TeraComposerMedia) throws -> RadrootsStagedBlobReference {
     33     guard item.sha256.range(of: "^[0-9a-f]{64}$", options: .regularExpression) != nil,
     34           item.opaqueReference == "media:\(item.sha256)", item.mediaType == "image/png",
     35           (1 ... 10 * 1024 * 1024).contains(item.byteSize), let size = Int(exactly: item.byteSize)
     36     else { throw TeraComposerAcknowledgment.unconfirmed }
     37     return try RadrootsStagedBlobReference(blobID: item.sha256, sizeBytes: size,
     38                                            mediaType: item.mediaType, filenameHint: "\(item.sha256).png")
     39   }
     40 }