spb/focale Public
Swift 100%
1//2// RecipeStore.swift3// Focale4//5// Author: Simon-Pierre Boucher6// Contact: contact@spboucher.ai7//89import Foundation10import Observation1112@MainActor13@Observable14final class RecipeStore {15 private(set) var recipes: [Recipe] = []16 var activeRecipeID: Recipe.ID? {17 didSet { save() }18 }1920 private let fileURL: URL2122 var activeRecipe: Recipe? {23 guard let activeRecipeID else { return nil }24 return recipes.first { $0.id == activeRecipeID }25 }2627 init(directory: URL = .applicationSupportDirectory) {28 fileURL = directory.appending(path: "recipes.json")29 load()30 if recipes.isEmpty {31 recipes = Recipe.builtIns32 save()33 }34 }3536 func add(_ recipe: Recipe) {37 recipes.append(recipe)38 save()39 }4041 func update(_ recipe: Recipe) {42 guard let index = recipes.firstIndex(where: { $0.id == recipe.id }) else { return }43 recipes[index] = recipe44 save()45 }4647 func remove(_ id: Recipe.ID) {48 recipes.removeAll { $0.id == id }49 if activeRecipeID == id { activeRecipeID = nil }50 save()51 }5253 @discardableResult54 func importRecipe(from data: Data) throws -> Recipe {55 var recipe = try Recipe(importing: data)56 // Re-identify on import so two devices can hold the same shared file.57 recipe = Recipe(58 name: recipe.name,59 settings: recipe.settings,60 autoProjectName: recipe.autoProjectName,61 sceneTrigger: recipe.sceneTrigger,62 showsTimestamp: recipe.showsTimestamp,63 prioritizeOCR: recipe.prioritizeOCR64 )65 add(recipe)66 return recipe67 }6869 // MARK: - Persistence7071 private struct Snapshot: Codable {72 var recipes: [Recipe]73 var activeRecipeID: Recipe.ID?74 }7576 private func load() {77 guard let data = try? Data(contentsOf: fileURL),78 let snapshot = try? JSONDecoder().decode(Snapshot.self, from: data)79 else { return }80 recipes = snapshot.recipes81 activeRecipeID = snapshot.activeRecipeID82 }8384 private func save() {85 let snapshot = Snapshot(recipes: recipes, activeRecipeID: activeRecipeID)86 guard let data = try? JSONEncoder().encode(snapshot) else { return }87 try? FileManager.default.createDirectory(88 at: fileURL.deletingLastPathComponent(),89 withIntermediateDirectories: true90 )91 try? data.write(to: fileURL, options: .atomic)92 }93}94