package mi.hdm.filesystem; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import mi.hdm.TastyPages; import mi.hdm.mealPlan.MealPlan; import mi.hdm.recipes.*; import mi.hdm.shoppingList.ShoppingList; import mi.hdm.typeAdapters.CategoryManagerTypeAdapter; import mi.hdm.typeAdapters.MealPlanTypeAdapter; import mi.hdm.typeAdapters.RecipeTypeAdapter; import mi.hdm.typeAdapters.ShoppingListTypeAdapter; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.IOException; import java.math.BigDecimal; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Stream; /** * Containing methods to serialize and deserialize parts of the app content for saving to / loading from the filesystem */ public class FileManager { private final static String PATH_TO_DEFAULT_INGREDIENTS = "/data/nutrition.csv"; private final static String PATH_TO_USER_DATA = System.getProperty("user.home") + "\\AppData\\Roaming\\TastyPages"; private final static Logger log = LogManager.getLogger(FileManager.class); public static void serializeToFile(TastyPages app) { } @SuppressWarnings("ResultOfMethodCallIgnored") public static TastyPages deserializeFromFilesystem() throws IOException, URISyntaxException { //TODO: if this fails, log.fatal and System.exit log.info("Trying to read {}", PATH_TO_USER_DATA); Path userPath = Path.of(PATH_TO_USER_DATA); if (userPath.toFile().mkdirs()) { //If .mkdirs() returns true, that means the folder has not existed before -> in this case, only load default ingredients! log.info("TastyPages folder has been created at {}", userPath); IngredientManager deserializedIngredientManager = deserializeIngredientManager(getAbsolutePathFromResourceFolder()); return new TastyPages(deserializedIngredientManager); } else { //otherwise, read user data log.info("Found TastyPages folder at {}, loading user data from there.", userPath); IngredientManager ingredientManager; RecipeManager recipeManager; CategoryManager categoryManager; MealPlan mealPlan; ShoppingList shoppingList; //TODO: turn these paths into static variables? Path toIngredients = Path.of(userPath + "/ingredients.csv"); if (Files.exists(toIngredients)) { log.info("Loading ingredients from {}", toIngredients); ingredientManager = deserializeIngredientManager(PATH_TO_USER_DATA); } else { log.info("Could not find /ingredients.csv, falling back to default ingredients."); ingredientManager = deserializeIngredientManager(getAbsolutePathFromResourceFolder()); } Path recipeFolder = Path.of(userPath + "/recipes"); if (Files.exists(recipeFolder) && Files.isDirectory(recipeFolder)) { log.info("Loading recipes from {}", recipeFolder); recipeManager = deserializeRecipeManager(recipeFolder, ingredientManager); } else { log.info("Could not find recipe folder, creating empty instance of RecipeManger"); recipeFolder.toFile().mkdir(); recipeManager = new RecipeManager(); } Path toCategoryJson = Path.of(userPath + "/categories.json"); if (Files.exists(toCategoryJson) && Files.isRegularFile(toCategoryJson)) { log.info("Loading categories from {}", toCategoryJson); String fileContentCategories = Files.readString(toCategoryJson); categoryManager = deserializeCategoryManager(fileContentCategories); } else { log.info("Could not find /categories.json, creating empty instance of CategoryManager"); toCategoryJson.toFile().createNewFile(); categoryManager = new CategoryManager(); } Path toMealPlanJson = Path.of(userPath + "/mealplan.json"); if (Files.exists(toMealPlanJson) && Files.isRegularFile(toMealPlanJson)) { log.info("Loading meal plan from {}", toMealPlanJson); String fileContentMealPlan = Files.readString(toMealPlanJson); mealPlan = deserializeMealPlan(fileContentMealPlan, recipeManager); } else { log.info("Could not find /mealplan.json, creating empty instance of MealPlan"); toMealPlanJson.toFile().createNewFile(); mealPlan = new MealPlan(recipeManager); } Path toShoppingListJson = Path.of(userPath + "/shoppingList.json"); if (Files.exists(toShoppingListJson) && Files.isRegularFile(toShoppingListJson)) { log.info("Loading shopping list from {}", toShoppingListJson); String fileContentShoppingList = Files.readString(toShoppingListJson); shoppingList = deserializeShoppingList(fileContentShoppingList); } else { log.info("Could not find /shoppingList.json, creating empty shopping list"); toShoppingListJson.toFile().createNewFile(); shoppingList = new ShoppingList(); } return new TastyPages(recipeManager, ingredientManager, categoryManager, mealPlan, shoppingList); } } private static IngredientManager deserializeIngredientManager(String path) throws IOException { CSVParser parser = new CSVParser(); List<Ingredient> ingredients = parser.getIngredientsFromCSV( path, ',', "name", "calories", "carbohydrate", "fat", "protein", "fiber", "sodium" ); return new IngredientManager(ingredients); } private static RecipeManager deserializeRecipeManager(Path path, IngredientManager ingredientManager) throws IOException { List<Path> recipePaths; log.info("Reading recipes from '{}'", path); try (Stream<Path> stream = Files.list(path)) { //TODO: catch clause recipePaths = stream.toList(); } List<Recipe> recipes = recipePaths .stream() .map(p -> { try { return Files.readString(p); } catch (IOException e) { e.printStackTrace(); log.error("Could not read file contents for {}. Check file permissions and content", p); return null; } }) .filter(Objects::nonNull) .map(json -> JSONtoRecipe(json, ingredientManager)) .toList(); return new RecipeManager(recipes); } private static CategoryManager deserializeCategoryManager(String json) { if (json.isBlank()) return new CategoryManager(); Gson gson = new GsonBuilder().registerTypeAdapter(CategoryManager.class, new CategoryManagerTypeAdapter()).create(); return gson.fromJson(json, CategoryManager.class); } private static MealPlan deserializeMealPlan(String json, RecipeManager recipeManager) { if (json.isBlank()) return new MealPlan(recipeManager); Gson gson = new GsonBuilder() .registerTypeAdapter(MealPlan.class, new MealPlanTypeAdapter(recipeManager)) .create(); return gson.fromJson(json, MealPlan.class); } private static ShoppingList deserializeShoppingList(String json) { if (json.isBlank()) return new ShoppingList(); Gson gson = new GsonBuilder() .registerTypeAdapter(ShoppingList.class, new ShoppingListTypeAdapter()) .create(); return gson.fromJson(json, ShoppingList.class); } private static String recipeToJSON(Recipe recipe, IngredientManager ingredientManager) { Gson gson = new GsonBuilder() .registerTypeAdapter(Recipe.class, new RecipeTypeAdapter(ingredientManager)) .create(); return gson.toJson(recipe); } /** * Used to convert an ingredient into a CSV String seperated by comma * * @return a comma seperated String containing the name of the ingredient as well as the contents of its nutrition table */ private static String ingredientToCSV(Ingredient ingredient) { //"name", "calories", "carbohydrate", "fat", "protein", "fiber", "sodium" Map<Nutrition, BigDecimal> map = ingredient.getNutritionTable().getTable(); return String.format( "%s, %f, %f, %f, %f, %f, %f%n", ingredient.getName(), map.get(Nutrition.CALORIES).doubleValue(), map.get(Nutrition.CARBS).doubleValue(), map.get(Nutrition.FAT).doubleValue(), map.get(Nutrition.PROTEINS).doubleValue(), map.get(Nutrition.FIBERS).doubleValue(), map.get(Nutrition.SALT).doubleValue() ); } private static Recipe JSONtoRecipe(String json, IngredientManager ingredientManager) { Gson gson = new GsonBuilder().registerTypeAdapter(Recipe.class, new RecipeTypeAdapter(ingredientManager)).create(); return gson.fromJson(json, Recipe.class); } private static String getAbsolutePathFromResourceFolder() throws URISyntaxException { URL resourceUrl = FileManager.class.getResource(FileManager.PATH_TO_DEFAULT_INGREDIENTS); assert resourceUrl != null; Path path = Paths.get(resourceUrl.toURI()); return path.toFile().getAbsolutePath(); } }