// // DraftsManager.swift // ComposeUI // // Created by Shadowfacts on 10/22/18. // Copyright © 2018 Shadowfacts. All rights reserved. // import Foundation import Combine public class DraftsManager: Codable, ObservableObject { public private(set) static var shared: DraftsManager = load() private static var appGroupDirectory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.space.vaccor.Tusker")! private static var archiveURL = appGroupDirectory.appendingPathComponent("drafts").appendingPathExtension("plist") private static let saveQueue = DispatchQueue(label: "DraftsManager", qos: .utility) public static func save() { saveQueue.async { let encoder = PropertyListEncoder() let data = try? encoder.encode(shared) try? data?.write(to: archiveURL, options: .noFileProtection) } } static func load() -> DraftsManager { let decoder = PropertyListDecoder() if let data = try? Data(contentsOf: archiveURL), let draftsManager = try? decoder.decode(DraftsManager.self, from: data) { return draftsManager } return DraftsManager() } private init() {} public required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if let dict = try? container.decode([UUID: SafeDraft].self, forKey: .drafts) { self.drafts = dict.compactMapValues { $0.draft } } else if let array = try? container.decode([SafeDraft].self, forKey: .drafts) { self.drafts = array.reduce(into: [:], { partialResult, safeDraft in if let draft = safeDraft.draft { partialResult[draft.id] = draft } }) } else { throw DecodingError.dataCorruptedError(forKey: .drafts, in: container, debugDescription: "expected drafts to be a dict or array of drafts") } } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(drafts, forKey: .drafts) } @Published private var drafts: [UUID: Draft] = [:] var sorted: [Draft] { return drafts.values.sorted(by: { $0.lastModified > $1.lastModified }) } public func add(_ draft: Draft) { drafts[draft.id] = draft } public func remove(_ draft: Draft) { drafts.removeValue(forKey: draft.id) } public func getBy(id: UUID) -> Draft? { return drafts[id] } enum CodingKeys: String, CodingKey { case drafts } // a container that always succeeds at decoding // so if a single draft can't be decoded, we don't lose all drafts struct SafeDraft: Decodable { let draft: Draft? init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() self.draft = try? container.decode(Draft.self) } } }