Newer
Older

Karsch Lukas
committed
package mi.hdm.filesystem;

Karsch Lukas
committed
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

Karsch Lukas
committed
import mi.hdm.TastyPages;

Karsch Lukas
committed
import mi.hdm.mealPlan.MealPlan;
import mi.hdm.recipes.*;
import mi.hdm.shoppingList.ShoppingList;
import mi.hdm.typeAdapters.CategoryManagerTypeAdapter;
import mi.hdm.typeAdapters.MealPlanTypeAdapter;

Karsch Lukas
committed
import mi.hdm.typeAdapters.RecipeTypeAdapter;

Karsch Lukas
committed
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

Karsch Lukas
committed
import java.io.BufferedWriter;
import java.io.FileWriter;

Karsch Lukas
committed
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.*;
import java.util.stream.Collectors;

Karsch Lukas
committed
import java.util.stream.Stream;
/**
* Containing methods to serialize and deserialize parts of the app content for saving to / loading from the filesystem
*/

Karsch Lukas
committed
public class FileManager {
private final static String PATH_TO_DEFAULT_INGREDIENTS = "/data/simple_nutrition.csv";
private final static String PATH_TO_DEMO_RECIPES = "/recipes/";
private final static String PATH_TO_DEMO_CATEGORIES = "/data/categories.json";
private final static String FOLDER_NAME = "TastyPages";
private final static Path PATH_TO_USER_DATA = Path.of(System.getProperty("user.home"), FOLDER_NAME);

Karsch Lukas
committed
private final static Logger log = LogManager.getLogger(FileManager.class);
public static void serializeToFile(TastyPages app) {
log.info("Writing user data to '{}'", PATH_TO_USER_DATA);
//Serialize ingredient manager
serializeIngredientManager(app.getIngredientManager(), PATH_TO_USER_DATA.resolve("ingredients.csv"));
serializeRecipeManager(app.getRecipeManager(), PATH_TO_USER_DATA.resolve("recipes"));
serializeCategoryManager(app.getCategoryManager(), PATH_TO_USER_DATA.resolve("categories.json"));
serializeMealPlan(app.getMealPlan(), PATH_TO_USER_DATA.resolve("mealplan.json"));
serializeShoppingList(app.getShoppingList(), app.getRecipeManager(), PATH_TO_USER_DATA.resolve("shoppingList.json"));

Karsch Lukas
committed
}
public static TastyPages deserializeFromFilesystem() throws IOException {

Karsch Lukas
committed
log.info("Trying to read {}", PATH_TO_USER_DATA);
if (PATH_TO_USER_DATA.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 {}", PATH_TO_USER_DATA);
IngredientManager deserializedIngredientManager = deserializeIngredientManager(getAbsolutePathFromResourceFolder(FileManager.PATH_TO_DEFAULT_INGREDIENTS));
RecipeManager recipeManager = deserializeRecipeManager(Path.of(getAbsolutePathFromResourceFolder(FileManager.PATH_TO_DEMO_RECIPES)));
String fileContent = Files.readString(Path.of(FileManager.getAbsolutePathFromResourceFolder(FileManager.PATH_TO_DEMO_CATEGORIES)));
CategoryManager categoryManager = deserializeCategoryManager(fileContent);
return new TastyPages(deserializedIngredientManager, recipeManager, categoryManager);

Karsch Lukas
committed
} else { //otherwise, read user data
log.info("Found TastyPages folder at {}, loading user data from there.", PATH_TO_USER_DATA);

Karsch Lukas
committed
IngredientManager ingredientManager;
RecipeManager recipeManager;
CategoryManager categoryManager;
MealPlan mealPlan;
ShoppingList shoppingList;
//Deserialize ingredient manager
{
Path toIngredients = PATH_TO_USER_DATA.resolve("ingredients.csv");
if (Files.exists(toIngredients)) {
log.info("Loading ingredients from '{}'", toIngredients);
ingredientManager = deserializeIngredientManager(toIngredients.toString());
} else {
log.info("Could not find /ingredients.csv, falling back to default ingredients.");
ingredientManager = deserializeIngredientManager(getAbsolutePathFromResourceFolder(FileManager.PATH_TO_DEFAULT_INGREDIENTS));

Karsch Lukas
committed
}
//Deserialize recipe manager
{
Path recipeFolder = PATH_TO_USER_DATA.resolve("recipes");
if (Files.exists(recipeFolder) && Files.isDirectory(recipeFolder)) {
log.info("Loading recipes from '{}'", recipeFolder);
recipeManager = deserializeRecipeManager(recipeFolder);
} else {
log.info("Could not find recipe folder, creating empty instance of RecipeManger");
recipeFolder.toFile().mkdir();
recipeManager = deserializeRecipeManager(Path.of(getAbsolutePathFromResourceFolder(FileManager.PATH_TO_DEMO_RECIPES)));

Karsch Lukas
committed
}
//Deserialize category manager
{
Path toCategoryJson = PATH_TO_USER_DATA.resolve("categories.json");
if (Files.exists(toCategoryJson) && Files.isRegularFile(toCategoryJson)) {
log.info("Loading categories from '{}'", toCategoryJson);
String fileContent = Files.readString(toCategoryJson);
categoryManager = deserializeCategoryManager(fileContent);
} else {
log.info("Could not find /categories.json, creating empty instance of CategoryManager");
String fileContent = Files.readString(Path.of(FileManager.getAbsolutePathFromResourceFolder(FileManager.PATH_TO_DEMO_CATEGORIES)));
categoryManager = deserializeCategoryManager(fileContent);
/*toCategoryJson.toFile().createNewFile();
categoryManager = new CategoryManager();*/
//Deserialize meal plan
{
Path toMealPlanJson = PATH_TO_USER_DATA.resolve("mealplan.json");
if (Files.exists(toMealPlanJson) && Files.isRegularFile(toMealPlanJson)) {
log.info("Loading meal plan from '{}'", toMealPlanJson);
String fileContent = Files.readString(toMealPlanJson);
mealPlan = deserializeMealPlan(fileContent, recipeManager);
} else {
log.info("Could not find /mealplan.json, creating empty instance of MealPlan");
toMealPlanJson.toFile().createNewFile();
mealPlan = new MealPlan(recipeManager);
}
//Deserialize shopping list
{
Path toShoppingListJson = PATH_TO_USER_DATA.resolve("shoppingList.json");
if (Files.exists(toShoppingListJson) && Files.isRegularFile(toShoppingListJson)) {
log.info("Loading shopping list from '{}'", toShoppingListJson);
String fileContent = Files.readString(toShoppingListJson);
shoppingList = deserializeShoppingList(fileContent, recipeManager);
} else {
log.info("Could not find /shoppingList.json, creating empty shopping list");
toShoppingListJson.toFile().createNewFile();
shoppingList = new ShoppingList(recipeManager);
return new TastyPages(recipeManager, ingredientManager, categoryManager, mealPlan, shoppingList);

Karsch Lukas
committed
}

Karsch Lukas
committed
}

Karsch Lukas
committed
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 void serializeIngredientManager(IngredientManager ingredientManager, Path path) {
log.info("Writing ingredients to '{}'", path.getFileName());
try (BufferedWriter writer = new BufferedWriter(new FileWriter(path.toFile()))) {
writer.write("name,calories,carbohydrate,fat,protein,fiber,sodium\n");
final List<Ingredient> ingredients = ingredientManager.getAllIngredients().values().stream().toList();
for (int i = 0; i < ingredients.size(); i++) {
if (i % 200 == 0)
writer.flush(); //flush the writer regularly
writer.write(ingredientToCSV(ingredients.get(i)));
}
} catch (IOException e) {
log.error("Exception when writing ingredients to file!");
e.printStackTrace();
throw new RuntimeException(e);
}
}
private static RecipeManager deserializeRecipeManager(Path path) throws IOException {

Karsch Lukas
committed
List<Path> recipePaths;
try (Stream<Path> stream = Files.list(path)) {

Karsch Lukas
committed
recipePaths = stream.toList();
}

Karsch Lukas
committed
.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);

Karsch Lukas
committed
}
})
.map(FileManager::JSONtoRecipe)

Karsch Lukas
committed
.toList();
return new RecipeManager(recipes);
}
private static void serializeRecipeManager(RecipeManager recipeManager, Path path) {
String[] recipeFolderContent = path.toFile().list();
if (recipeFolderContent == null) { //wrong path provided
log.fatal("Path to recipe folder is NOT a folder! '{}' Unable to save recipes.", path);
return;
}
List<String> toDelete = Arrays.stream(recipeFolderContent).collect(Collectors.toCollection(ArrayList::new));
log.info("Writing all recipes to /recipes");
recipeManager.getRecipes()
.forEach(recipe -> {
try {
String recipeName = recipe.getName() + ".json";
String recipePath = path + "\\" + recipeName;
Files.writeString(Path.of(recipePath), recipeToJSON(recipe));
log.debug("Wrote recipe '{}' to '{}'", recipe.getName(), recipePath);
toDelete.remove(recipeName);
} catch (IOException e) {
log.error("Error writing recipe '{}' to file. Recipe will be lost.", recipe.getName());
e.printStackTrace();
}
}
);
long deleted = toDelete.stream()
.filter(filename -> {
try {
return Files.deleteIfExists(Path.of(path + "\\" + filename));
} catch (IOException e) {
log.warn("Could not delete '{}': {}", filename, e.getMessage());
return false;
}
})
.count();
log.info("Deleted {} recipes from the filesystem", deleted);
}
private static CategoryManager deserializeCategoryManager(String json) {
if (json.isBlank()) {
log.info("JSON file is blank, creating empty instance of Category Manager");
return new CategoryManager();
}
Gson gson = new GsonBuilder().registerTypeAdapter(CategoryManager.class, new CategoryManagerTypeAdapter()).create();
return gson.fromJson(json, CategoryManager.class);

Karsch Lukas
committed
}
private static void serializeCategoryManager(CategoryManager categoryManager, Path path) {
log.info("Writing categories to categories.json");
Gson gson = new GsonBuilder()
.registerTypeAdapter(CategoryManager.class, new CategoryManagerTypeAdapter())
.create();
String json = gson.toJson(categoryManager);
try {
Files.writeString(path, json);
} catch (IOException io) {
log.error("Error writing categories to file.");
io.printStackTrace();
throw new RuntimeException();
}
}
private static MealPlan deserializeMealPlan(String json, RecipeManager recipeManager) {
if (json.isBlank()) {
log.info("JSON file is blank, creating empty instance of Meal Plan");
return new MealPlan(recipeManager);
}
Gson gson = new GsonBuilder()
.registerTypeAdapter(MealPlan.class, new MealPlanTypeAdapter(recipeManager))
.create();
return gson.fromJson(json, MealPlan.class);

Karsch Lukas
committed
}
private static void serializeMealPlan(MealPlan mealPlan, Path path) {
Gson gson = new GsonBuilder().
registerTypeAdapter(MealPlan.class, new MealPlanTypeAdapter(null))
.create();
String json = gson.toJson(mealPlan);
try {
Files.writeString(path, json);
} catch (IOException io) {
log.error("Error writing meal plan to file.");
io.printStackTrace();
throw new RuntimeException();
}
}
private static ShoppingList deserializeShoppingList(String json, RecipeManager recipeManager) {
if (json.isBlank()) {
log.info("JSON file is blank, creating empty instance of Shopping List");
return new ShoppingList(recipeManager);
.registerTypeAdapter(ShoppingList.class, new ShoppingListTypeAdapter(recipeManager))
return gson.fromJson(json, ShoppingList.class);

Karsch Lukas
committed
}
private static void serializeShoppingList(ShoppingList shoppingList, RecipeManager recipeManager, Path path) {
Gson gson = new GsonBuilder()
.registerTypeAdapter(ShoppingList.class, new ShoppingListTypeAdapter(recipeManager))
.create();
String json = gson.toJson(shoppingList);
try {
Files.writeString(path, json);
} catch (IOException io) {
log.error("Error writing shopping list to file.");
io.printStackTrace();
throw new RuntimeException();
}
}
private static String recipeToJSON(Recipe recipe) {

Karsch Lukas
committed
Gson gson = new GsonBuilder()
.registerTypeAdapter(Recipe.class, new RecipeTypeAdapter())

Karsch Lukas
committed
.create();
return gson.toJson(recipe);
}
/**
* Used to convert an ingredient into a CSV String seperated by comma. Floating point numbers
* will use dots as decimal points

Karsch Lukas
committed
*
* @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(
Locale.ENGLISH,
"\"%s\", %f, %f, %f, %f, %f, %f%n",

Karsch Lukas
committed
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) {
Gson gson = new GsonBuilder().registerTypeAdapter(Recipe.class, new RecipeTypeAdapter()).create();

Karsch Lukas
committed
}
public static String getAbsolutePathFromResourceFolder(String p) {
try {
URL resourceUrl = FileManager.class.getResource(p);
assert resourceUrl != null;
Path path = Paths.get(resourceUrl.toURI());
return path.toFile().getAbsolutePath();
} catch (URISyntaxException e) {
log.error("Absolute path for \"{}\" not found", p);
return null;
}