// // DraftRecoveryService.swift // Confab // // Crash-recovery draft for the in-progress conversation — a lightweight, // invisible mirror of the current chat, distinct from named saved conversations. // // SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 // Copyright (C) 2026 Rune Olsen // // This file is part of Confab. // // Confab is licensed under the PolyForm Noncommercial License 1.0.0. // You may use, study, modify, and share it for any noncommercial // purpose. Commercial use — including selling Confab or any part of // it, standalone or bundled into another product or service — // requires a separate commercial license from the copyright holder. // // See the LICENSE file or // for // the full license text. For commercial licensing, contact Rune // Olsen via . import Foundation struct DraftConversation: Codable, Sendable { let messages: [Message] let conversationId: UUID? let conversationName: String? let modelId: String? let savedAt: Date } final class DraftRecoveryService: Sendable { static let shared = DraftRecoveryService() private let fileURL: URL /// - Parameter fileURL: injection point for tests; production uses the default /// Application Support location, matching `DatabaseService`'s pattern. nonisolated init(fileURL: URL? = nil) { if let fileURL { self.fileURL = fileURL } else { let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first! let dir = appSupport.appendingPathComponent("oAI", isDirectory: true) try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) self.fileURL = dir.appendingPathComponent("draft_conversation.json") } } func save(_ draft: DraftConversation) { do { let data = try JSONEncoder().encode(draft) try data.write(to: fileURL, options: .atomic) Log.db.info("DraftRecoveryService: wrote draft to \(fileURL.path)") } catch { Log.db.error("DraftRecoveryService: save failed: \(error.localizedDescription)") } } func load() -> DraftConversation? { do { let data = try Data(contentsOf: fileURL) return try JSONDecoder().decode(DraftConversation.self, from: data) } catch { Log.db.info("DraftRecoveryService: load found nothing (\(error.localizedDescription))") return nil } } func clear() { try? FileManager.default.removeItem(at: fileURL) } }