SOURCE RECORD · S13
S13 · 发财了
Apple CloudKit private database and initial state
保留原行号。个人/账户信息如有删除,已用 REDACTED 标识。原文件 SHA-256 只用于识别截取时的本机原文件;不能证明编写或提交时间,也不等于脱敏下载文件的哈希。
Original line numbers are retained. Redactions are explicitly marked. The original full-file hash identifies a captured local file, not its creation/submission time or the redacted download. Public-file hashes are listed separately. This is selected source, not a whole-repository or binary absence finding.
Project key: facai
Original relative path: FaCai/Services/PersonalCloudStore.swift
Captured: 2026-09-12T18:57:15+08:00
Original full-file SHA256: aaa6434707f615a526280f75aeea3cf125f1e675b38c180c728a952079b7a1f4
Evidence level: local source excerpt
Build correspondence: not yet established with an Apple-received binary.
Line numbers refer to the captured original file. Gaps are explicitly marked. No source code was changed.
Original lines 1 to 64
0001 import CloudKit
0002 import Foundation
0003 import Network
0004 import Observation
0005
0006 @MainActor protocol CloudTransport {
0007 func account() async throws -> String
0008 func fetch(_ id: String) async throws -> CloudObject?
0009 func save(_ id: String, data: Data, replacing: CloudObject?) async throws -> CloudObject
0010 }
0011 struct CloudObject {
0012 var bytes: Data
0013 var record: CKRecord?
0014 }
0015 @MainActor final class AppleCloudTransport: CloudTransport {
0016 private var container: CKContainer { CKContainer(identifier: "iCloud.com.facaile") }
0017 private var database: CKDatabase { container.privateCloudDatabase }
0018 func account() async throws -> String {
0019 guard try await container.accountStatus() == .available else {
0020 throw AppProblem("请在系统设置登录 iCloud,并允许发财了使用 iCloud。")
0021 }
0022 return try await container.userRecordID().recordName
0023 }
0024 func fetch(_ id: String) async throws -> CloudObject? {
0025 do {
0026 let record = try await database.record(for: CKRecord.ID(recordName: id))
0027 guard let asset = record["body"] as? CKAsset, let url = asset.fileURL,
0028 let hash = record["checksum"] as? String,
0029 (try url.resourceValues(forKeys: [.fileSizeKey]).fileSize ?? Int.max) <= 25 * 1024 * 1024
0030 else { throw AppProblem("iCloud 文件不完整或过大。") }
0031 let bytes = try Data(contentsOf: url)
0032 guard BackupService.hash(bytes) == hash else { throw AppProblem("iCloud 文件校验失败,请稍后重试。") }
0033 return CloudObject(bytes: bytes, record: record)
0034 } catch let error as CKError where error.code == .unknownItem { return nil }
0035 }
0036 func save(_ id: String, data: Data, replacing: CloudObject?) async throws -> CloudObject {
0037 guard data.count <= 25 * 1024 * 1024 else { throw AppProblem("单份云端索引超过25 MiB,请减少内容后重试。") }
0038 let url = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
0039 try data.write(
0040 to: url, options: [.atomic, .completeFileProtectionUntilFirstUserAuthentication])
0041 defer { try? FileManager.default.removeItem(at: url) }
0042 let record =
0043 replacing?.record
0044 ?? CKRecord(recordType: "FaCaiPrivateData", recordID: CKRecord.ID(recordName: id))
0045 record["body"] = CKAsset(fileURL: url)
0046 record["checksum"] = BackupService.hash(data) as CKRecordValue
0047 // One record per request: optimistic record-version checks provide the required CAS.
0048 // The default zone does not require/support a multi-record atomic transaction.
0049 let result = try await database.modifyRecords(
0050 saving: [record], deleting: [], savePolicy: .ifServerRecordUnchanged, atomically: false)
0051 guard let saved = result.saveResults[record.recordID] else {
0052 throw AppProblem("iCloud 未确认保存,请重试。")
0053 }
0054 return try CloudObject(bytes: data, record: saved.get())
0055 }
0056 }
0057
0058 @MainActor @Observable final class PersonalCloudStore {
0059 private(set) var preferences = CloudPreferences()
0060 private(set) var status = "尚未开启,内容保存在本机"
0061 private(set) var running = false
0062 private(set) var backups: [CloudBackupItem] = []
0063 var error: String?
0064 private var ledger = CloudLedger()