// // LocalDataInterface.swift // Nextcloud Cookbook iOS Client // // Created by Vincent Meilinger on 07.05.24. // import Foundation import SwiftUI class LocalDataInterface: CookbookInterface { var id: String init(id: String) { self.id = id } enum LocalDataPath { case recipeStubs(category: String), recipe(id: String), image(id: String, size: RecipeImage.RecipeImageSize), categories, keywords var path: String { switch self { case .recipe(let id): "recipe_\(id).data" case .recipeStubs(let category): "recipes_\(category).data" case .image(let id, let size): if size == .FULL { "image_\(id).data" } else { "thumb_\(id).data" } case .categories: "categories.data" case .keywords: "keywords.data" } } } } // MARK: - Local Read Interface extension LocalDataInterface: ReadInterface { func getImage(id: String, size: RecipeImage.RecipeImageSize) async -> (UIImage?, (any UserAlert)?) { guard let data: String = await load(path: .image(id: id, size: size)) else { return (nil, PersistenceAlert.LOAD_FAILED) } guard let dataDecoded = Data(base64Encoded: data) else { return (nil, PersistenceAlert.DECODING_FAILED) } if let image = UIImage(data: dataDecoded) { return (image, nil) } return (nil, nil) } func getRecipeStubs() async -> ([RecipeStub]?, (any UserAlert)?) { return (nil, PersistenceAlert.LOAD_FAILED) } func getRecipe(id: String) async -> (Recipe?, (any UserAlert)?) { if let recipe: Recipe? = await load(path: LocalDataPath.recipe(id: id)) { return (recipe, nil) } return (nil, nil) } func getCategories() async -> ([Category]?, (any UserAlert)?) { return (await load(path: LocalDataPath.categories), nil) } func getRecipeStubsForCategory(named categoryName: String) async -> ([RecipeStub]?, (any UserAlert)?) { if let stubs: [RecipeStub] = await load(path: .recipeStubs(category: categoryName)) { return (stubs, nil) } return (nil, PersistenceAlert.LOAD_FAILED) } func getTags() async -> ([RecipeKeyword]?, (any UserAlert)?) { if let keywords: [RecipeKeyword] = await load(path: .keywords) { return (keywords, nil) } return (nil, PersistenceAlert.LOAD_FAILED) } func getRecipesTagged(keyword: String) async -> ([RecipeStub]?, (any UserAlert)?) { return (nil, PersistenceAlert.LOAD_FAILED) } } // MARK: - Local Write Interface extension LocalDataInterface: WriteInterface { func postImage(id: String, image: UIImage, size: RecipeImage.RecipeImageSize) async -> ((any UserAlert)?) { if let data = image.pngData() { await save( data, path: LocalDataPath.image(id: id, size: size) ) } } func postRecipe(recipe: Recipe) async -> ((any UserAlert)?) { await save(recipe, path: LocalDataPath.recipe(id: recipe.id)) return nil } func updateRecipe(recipe: Recipe) async -> ((any UserAlert)?) { return await postRecipe(recipe: recipe) } func deleteRecipe(id: String) async -> ((any UserAlert)?) { await delete(path: .recipe(id: id)) return nil } func renameCategory(named categoryName: String, newName: String) async -> ((any UserAlert)?) { guard let stubs: [RecipeStub] = await load(path: .recipeStubs(category: categoryName)) else { return PersistenceAlert.LOAD_FAILED } await delete(path: .recipeStubs(category: categoryName)) await save(stubs, path: .recipeStubs(category: newName)) } } // MARK: - Local Data Interface Utils extension LocalDataInterface { func load(path ldPath: LocalDataPath) async -> T? { do { return try await DataStore.shared.load(fromPath: ldPath.path) } catch (let error) { print(error) return nil } } func save(_ object: T, path ldPath: LocalDataPath) async { await DataStore.shared.save(data: object, toPath: ldPath.path) } func delete(path ldPath: LocalDataPath) async { DataStore.shared.delete(path: ldPath.path) } }