// // 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 } } 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 var fileURL: URL { let fileManager = FileManager.default guard let containerURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) else { fatalError("无法访问 App Group 容器: \(appGroupIdentifier)") } 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 = 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 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 = fileURL let data = try JSONEncoder().encode(clips) try data.write(to: url, options: .atomic) cachedClips = clips } }