Files
note/Note/ClipStore.swift
T

113 lines
3.0 KiB
Swift

//
// ClipStore.swift
// Note
//
// Created by Kimi Code CLI on 2026/7/15.
//
import Foundation
struct Clip: Codable, Identifiable, Equatable, Sendable {
let id: UUID
var text: String
let createdAt: Date
var source: String?
nonisolated init(id: UUID = UUID(), text: String, createdAt: Date = Date(), source: String? = nil) {
self.id = id
self.text = text
self.createdAt = createdAt
self.source = source
}
}
enum ClipStoreError: Error, LocalizedError {
case appGroupUnavailable
var errorDescription: String? {
switch self {
case .appGroupUnavailable:
return "无法访问 App Group 容器,请检查签名和 entitlements 配置。"
}
}
}
actor ClipStore {
static let shared = ClipStore()
private let appGroupIdentifier = "group.com.fishestlife.note"
private let fileName = "clips.json"
private let subdirectory = "SharedFiles/Clips"
private var cachedClips: [Clip]?
private func fileURL() throws -> URL {
let fileManager = FileManager.default
guard let containerURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) else {
throw ClipStoreError.appGroupUnavailable
}
let directoryURL = containerURL.appendingPathComponent(subdirectory, isDirectory: true)
try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true)
return directoryURL.appendingPathComponent(fileName)
}
private init() {}
func save(text: String, source: String? = nil) async throws {
var clips = try await loadAll()
let clip = Clip(text: text, source: source)
clips.insert(clip, at: 0)
try await persist(clips)
}
func loadAll() async throws -> [Clip] {
if let cached = cachedClips {
return cached
}
let url = try fileURL()
let fileManager = FileManager.default
guard fileManager.fileExists(atPath: url.path) else {
cachedClips = []
return []
}
let data = try Data(contentsOf: url)
let clips = try JSONDecoder().decode([Clip].self, from: data)
cachedClips = clips
return clips
}
func update(_ clip: Clip) async throws {
var clips = try await loadAll()
guard let index = clips.firstIndex(where: { $0.id == clip.id }) else {
return
}
clips[index] = clip
try await persist(clips)
}
func delete(id: UUID) async throws {
var clips = try await loadAll()
clips.removeAll { $0.id == id }
try await persist(clips)
}
func clearAll() async throws {
try await persist([])
}
func count() async throws -> Int {
let clips = try await loadAll()
return clips.count
}
private func persist(_ clips: [Clip]) async throws {
let url = try fileURL()
let data = try JSONEncoder().encode(clips)
try data.write(to: url, options: .atomic)
cachedClips = clips
}
}