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

RadrootsAppleBackgroundTransferFileResolver.swift (3650B)


      1 import Foundation
      2 
      3 public struct RadrootsAppleBackgroundTransferFileResolver: RadrootsBackgroundTransferFileResolver,
      4     Sendable {
      5     private let roots: RadrootsAppleFileRoots
      6 
      7     public init(roots: RadrootsAppleFileRoots) {
      8         self.roots = roots
      9     }
     10 
     11     public func prepareUploadLease(
     12         for request: RadrootsBackgroundTransferRequest, executionID: UUID,
     13         existing: RadrootsStagedBlobReference?
     14     ) throws -> RadrootsStagedBlobLease {
     15         guard case let .upload(source) = request.operation else { throw RadrootsBackgroundTransferError.invalidRequest }
     16         let owner = RadrootsAppleFileAccess(roots: roots)
     17         let identifier = executionID.uuidString.lowercased()
     18         if let existing {
     19             guard request.expectedSourceSHA256 == nil || request.expectedSourceSHA256 == existing.blobID else {
     20                 throw RadrootsBackgroundTransferError.invalidRequest
     21             }
     22             return try owner.leaseStagedBlob(existing, expectedSHA256: existing.blobID, identifier: identifier)
     23         }
     24         let bytes = try read(source, maximumBytes: Int(request.maximumTransferBytes))
     25         let digest = RadrootsAppleFileDigest.sha256(bytes)
     26         guard request.expectedSourceSHA256 == nil || request.expectedSourceSHA256 == digest else {
     27             throw RadrootsBackgroundTransferError.invalidRequest
     28         }
     29         return try owner.leaseUploadBytes(bytes, identifier: identifier)
     30     }
     31 
     32     public func releaseUploadLease(executionID: UUID) throws {
     33         try RadrootsAppleFileAccess(roots: roots).releaseUploadLease(executionID: executionID)
     34     }
     35 
     36     public func resolve(_ file: RadrootsBackgroundTransferLocalFile) throws -> URL {
     37         let candidate: URL
     38         let root: URL
     39         switch file {
     40         case let .file(reference):
     41             candidate = try roots.resolvedURL(for: reference)
     42             root = roots.root(for: reference.scope)
     43         case let .stagedBlob(blob):
     44             candidate = try roots.stagedBlobURL(for: blob)
     45             root = roots.stagedBlobsRoot
     46         }
     47         let resolvedRoot = root.resolvingSymlinksInPath().standardizedFileURL
     48         let resolvedCandidate = candidate.resolvingSymlinksInPath().standardizedFileURL
     49         guard resolvedCandidate.path.hasPrefix(resolvedRoot.path + "/") else {
     50             throw RadrootsBackgroundTransferError.invalidRequest
     51         }
     52         return candidate
     53     }
     54 
     55     public func read(_ file: RadrootsBackgroundTransferLocalFile, maximumBytes: Int) throws -> Data {
     56         guard maximumBytes >= 0 else {
     57             throw RadrootsBackgroundTransferError.invalidRequest
     58         }
     59         let root: URL
     60         let relativePath: String
     61         let expectedBytes: Int?
     62         switch file {
     63         case let .file(reference):
     64             root = roots.root(for: reference.scope)
     65             relativePath = reference.relativePath
     66             expectedBytes = nil
     67         case let .stagedBlob(blob):
     68             root = roots.stagedBlobsRoot
     69             relativePath = blob.blobID
     70             expectedBytes = blob.sizeBytes
     71         }
     72         do {
     73             let data = try RadrootsGovernedFileReader.read(
     74                 root: root,
     75                 relativePath: relativePath,
     76                 maximumBytes: maximumBytes
     77             )
     78             guard expectedBytes == nil || expectedBytes == data.count else {
     79                 throw RadrootsBackgroundTransferError.invalidRequest
     80             }
     81             return data
     82         } catch let error as RadrootsBackgroundTransferError {
     83             throw error
     84         } catch {
     85             throw RadrootsBackgroundTransferError.invalidRequest
     86         }
     87     }
     88 }