按 W2 plan Task 3 落地原图加密存储: - writeJPEG / loadImage / remove / wipe 四个核心操作 - Application Support/Vault/ 目录全程 .completeFileProtection - 文件写入用 .completeFileProtection options(双保险) - FileVault(rootURL:) 注入便于测试隔离 - shared 单例用真实 App Support 路径 测试 3 条:roundtrip / remove / wipe。 注:.swift 文件需用户在 Xcode 拖入 target(Persistence group + 体己Tests), 之后 ⌘U 跑测试,若全绿再 amend 提交 .pbxproj。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
73 lines
2.2 KiB
Swift
73 lines
2.2 KiB
Swift
import Foundation
|
|
import UIKit
|
|
|
|
enum FileVaultError: Error {
|
|
case readFailed
|
|
case writeFailed
|
|
case removeFailed
|
|
case decodeFailed
|
|
}
|
|
|
|
final class FileVault {
|
|
static let shared: FileVault = {
|
|
do {
|
|
let appSupport = try FileManager.default.url(
|
|
for: .applicationSupportDirectory,
|
|
in: .userDomainMask,
|
|
appropriateFor: nil,
|
|
create: true
|
|
)
|
|
let vaultURL = appSupport.appendingPathComponent("Vault", isDirectory: true)
|
|
return try FileVault(rootURL: vaultURL)
|
|
} catch {
|
|
fatalError("FileVault.shared init failed: \(error)")
|
|
}
|
|
}()
|
|
|
|
let rootURL: URL
|
|
|
|
init(rootURL: URL) throws {
|
|
self.rootURL = rootURL
|
|
try FileManager.default.createDirectory(
|
|
at: rootURL,
|
|
withIntermediateDirectories: true,
|
|
attributes: [.protectionKey: FileProtectionType.complete]
|
|
)
|
|
}
|
|
|
|
struct SavedAsset {
|
|
let relativePath: String
|
|
let bytes: Int
|
|
}
|
|
|
|
func writeJPEG(_ image: UIImage, quality: CGFloat = 0.85) throws -> SavedAsset {
|
|
guard let data = image.jpegData(compressionQuality: quality) else {
|
|
throw FileVaultError.writeFailed
|
|
}
|
|
let filename = "\(UUID().uuidString).jpg"
|
|
let url = rootURL.appendingPathComponent(filename)
|
|
try data.write(to: url, options: [.atomic, .completeFileProtection])
|
|
return SavedAsset(relativePath: filename, bytes: data.count)
|
|
}
|
|
|
|
func loadImage(relativePath: String) throws -> UIImage {
|
|
let url = rootURL.appendingPathComponent(relativePath)
|
|
let data = try Data(contentsOf: url)
|
|
guard let image = UIImage(data: data) else { throw FileVaultError.decodeFailed }
|
|
return image
|
|
}
|
|
|
|
func remove(relativePath: String) throws {
|
|
let url = rootURL.appendingPathComponent(relativePath)
|
|
try FileManager.default.removeItem(at: url)
|
|
}
|
|
|
|
func wipe() throws {
|
|
let fm = FileManager.default
|
|
let contents = try fm.contentsOfDirectory(at: rootURL, includingPropertiesForKeys: nil)
|
|
for url in contents {
|
|
try fm.removeItem(at: url)
|
|
}
|
|
}
|
|
}
|