Files
kangkang/康康/Persistence/FileVault.swift
link2026 44ed01acf4 ```
refactor: 重命名项目名称从"体己"到"康康"

将整个项目的目录结构从"体己"重命名为"康康",包括所有源代码文件、
资源文件、测试文件以及Xcode项目配置文件。此更改涉及项目中所有的
文件路径和应用入口点(App/TijiApp.swift → App/KangkangApp.swift)。
```
2026-05-25 19:01:16 +08:00

102 lines
3.1 KiB
Swift

import Foundation
import UIKit
enum FileVaultError: Error {
case readFailed
case writeFailed
case removeFailed
case decodeFailed
}
/// `@unchecked Sendable`:rootURL let, I/O (线),
/// actor / Task 访
/// `nonisolated(unsafe) shared`: ModelStore
final class FileVault: @unchecked Sendable {
nonisolated(unsafe) 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
}
// MARK: - Path Safety
private func resolveSafePath(_ relativePath: String) throws -> URL {
guard !relativePath.contains("/"),
!relativePath.contains(".."),
!relativePath.isEmpty else {
throw FileVaultError.readFailed
}
let url = rootURL.appendingPathComponent(relativePath)
guard url.path.hasPrefix(rootURL.path) else {
throw FileVaultError.readFailed
}
return url
}
// MARK: - Public API
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 = try resolveSafePath(relativePath)
let data: Data
do {
data = try Data(contentsOf: url)
} catch {
throw FileVaultError.readFailed
}
guard let image = UIImage(data: data) else { throw FileVaultError.decodeFailed }
return image
}
func remove(relativePath: String) throws {
let url = try resolveSafePath(relativePath)
do {
try FileManager.default.removeItem(at: url)
} catch {
throw FileVaultError.removeFailed
}
}
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)
}
}
}