diff --git a/LocalServices/Data/CatastrophicPerformancesDatabase.cs b/LocalServices/Data/CatastrophicPerformancesDatabase.cs
index 451a424..a1d386d 100644
--- a/LocalServices/Data/CatastrophicPerformancesDatabase.cs
+++ b/LocalServices/Data/CatastrophicPerformancesDatabase.cs
@@ -1,161 +1,188 @@
-using Models;
-using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Linq;
-using System.Runtime.Serialization;
-using System.Text;
+using Models;
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
-using System.Threading.Tasks;
-
-namespace LocalEndpoint.Data
-{
- ///
- /// Database implementation with catastrophic performances.
- /// This database implementation persists data in xml and will save all the data in their files on each mutable requests.
- ///
- internal class CatastrophicPerformancesDatabase : Database
- {
-
- private static readonly DataContractSerializer RECIPES_SERIALIZER = new DataContractSerializer(typeof(Dictionary));
- private static readonly DataContractSerializer USERS_SERIALIZER = new DataContractSerializer(typeof(Dictionary));
- private static readonly DataContractSerializer ACCOUNTS_SERIALIZER = new DataContractSerializer(typeof(Dictionary));
-
- private static readonly string RECIPES_FILENAME = "recipes_data.xml";
- private static readonly string USERS_FILENAME = "users_data.xml";
- private static readonly string ACCOUNTS_FILENAME = "accounts_data.xml";
-
- private readonly Dictionary recipesData;
- private readonly Dictionary usersData;
- private readonly Dictionary accountsData;
-
- private readonly string dbPath;
-
-
- public CatastrophicPerformancesDatabase(string folderPath)
- {
- dbPath = folderPath;
- if (!Directory.Exists(folderPath))
- Directory.CreateDirectory(folderPath);
-
- usersData = Load(USERS_FILENAME, USERS_SERIALIZER);
- recipesData = Load(RECIPES_FILENAME, RECIPES_SERIALIZER);
- accountsData = Load(ACCOUNTS_FILENAME, ACCOUNTS_SERIALIZER);
- }
-
- public bool IsEmpty()
- {
- return recipesData.Count == 0 && usersData.Count == 0 && accountsData.Count == 0;
- }
-
- public Account? GetAccount(string email, string passwordHash)
- {
- if (!accountsData.TryGetValue(email, out AccountData? data))
- return null;
-
- if (data.PasswordHash != passwordHash) return null;
- return new Account(usersData[data.UserId].User, data.Email);
- }
-
- public void InsertAccount(Account account, string passwordHash)
- {
- accountsData[account.Email] = new AccountData(account.User.Id, account.Email, passwordHash);
- Save(ACCOUNTS_FILENAME, ACCOUNTS_SERIALIZER, accountsData);
- }
-
- public Recipe? GetRecipe(Guid id)
- {
- if (recipesData.TryGetValue(id, out RecipeData? data))
- return ConvertRecipeDataToRecipe(data);
- return null;
- }
-
- public RecipeRate GetRecipeRate(Guid user, Guid recipe)
- {
- return usersData[user].Rates[recipe];
- }
-
- public async void InsertInUserList(Guid userId, Guid recipeId, uint persAmount)
- {
- usersData[userId].RecipesList[recipeId] = persAmount;
- Save(USERS_FILENAME, USERS_SERIALIZER, usersData);
- }
-
- public async void RemoveFromUserList(Guid userId, Guid recipeId)
- {
- usersData[userId].RecipesList.Remove(recipeId);
- Save(USERS_FILENAME, USERS_SERIALIZER, usersData);
- }
-
-
- public async void InsertRecipe(Recipe recipe)
- {
- recipesData[recipe.Info.Id] = new RecipeData(recipe.Info, recipe.Owner.Id, recipe.Ingredients, recipe.Steps);
- Save(RECIPES_FILENAME, RECIPES_SERIALIZER, recipesData);
- }
-
- public async void InsertUser(User user)
- {
- usersData[user.Id] = new UserData(user, new Dictionary(), new Dictionary());
- Save(USERS_FILENAME, USERS_SERIALIZER, usersData);
- }
-
- public async void InsertRate(Guid userId, Guid recipeId, RecipeRate rate)
- {
- usersData[userId].Rates[recipeId] = rate;
- Save(USERS_FILENAME, USERS_SERIALIZER, usersData);
- }
-
- public async void RemoveRecipe(Guid id)
- {
- recipesData.Remove(id);
- Save(RECIPES_FILENAME, RECIPES_SERIALIZER, recipesData);
- }
-
- public ImmutableList ListAllRecipes()
- {
- return recipesData.Values.ToImmutableList().ConvertAll(ConvertRecipeDataToRecipe);
- }
-
-
- public ImmutableDictionary ListRatesOf(Guid user)
- {
- return usersData[user].Rates.ToImmutableDictionary();
- }
-
- public ImmutableDictionary GetRecipeListOf(Guid user)
- {
- return usersData[user].RecipesList.ToImmutableDictionary();
- }
-
- private Recipe ConvertRecipeDataToRecipe(RecipeData rd)
- {
- var owner = usersData[rd.OwnerID].User;
- return new Recipe(rd.Info, owner, rd.Ingredients, rd.Steps);
- }
-
- private Dictionary Load(string fileName, DataContractSerializer deserializer)
- {
- var file = dbPath + "/" + fileName;
- var fileInfo = new FileInfo(file);
-
- if (!fileInfo.Exists)
- fileInfo.Create();
-
- if (fileInfo.Length == 0)
- return new Dictionary(); //file is empty thus there is nothing to deserialize
-
- string text = File.ReadAllText(file);
-
- return JsonSerializer.Deserialize>(text);
- }
-
- private async void Save(string fileName, DataContractSerializer serializer, Dictionary dict)
- {
- string json = JsonSerializer.Serialize(dict);
- File.WriteAllText(dbPath + "/" + fileName, json);
- }
-
- }
-}
+using System.Threading.Tasks;
+
+namespace LocalEndpoint.Data
+{
+ ///
+ /// Database implementation with catastrophic performances.
+ /// This database implementation persists data in xml and will save all the data in their files on each mutable requests.
+ ///
+ internal class CatastrophicPerformancesDatabase : Database
+ {
+
+ private static readonly DataContractSerializer RECIPES_SERIALIZER = new DataContractSerializer(typeof(Dictionary));
+ private static readonly DataContractSerializer USERS_SERIALIZER = new DataContractSerializer(typeof(Dictionary));
+ private static readonly DataContractSerializer ACCOUNTS_SERIALIZER = new DataContractSerializer(typeof(Dictionary));
+
+ private static readonly string RECIPES_FILENAME = "recipes_data.xml";
+ private static readonly string USERS_FILENAME = "users_data.xml";
+ private static readonly string ACCOUNTS_FILENAME = "accounts_data.xml";
+
+ private readonly Dictionary recipesData;
+ private readonly Dictionary usersData;
+ private readonly Dictionary accountsData;
+
+ private readonly string dbPath;
+
+
+ public CatastrophicPerformancesDatabase(string folderPath)
+ {
+ dbPath = folderPath;
+ if (!Directory.Exists(folderPath))
+ Directory.CreateDirectory(folderPath);
+
+ usersData = Load(USERS_FILENAME, USERS_SERIALIZER);
+ recipesData = Load(RECIPES_FILENAME, RECIPES_SERIALIZER);
+ accountsData = Load(ACCOUNTS_FILENAME, ACCOUNTS_SERIALIZER);
+ }
+
+ public bool IsEmpty()
+ {
+ return recipesData.Count == 0 && usersData.Count == 0 && accountsData.Count == 0;
+ }
+
+ public Account? GetAccount(string email, string passwordHash)
+ {
+ if (!accountsData.TryGetValue(email, out AccountData? data))
+ return null;
+
+ if (data.PasswordHash != passwordHash) return null;
+ return new Account(usersData[data.UserId].User, data.Email);
+ }
+
+ public void InsertAccount(Account account, string passwordHash)
+ {
+ accountsData[account.Email] = new AccountData(account.User.Id, account.Email, passwordHash);
+ Save(ACCOUNTS_FILENAME, ACCOUNTS_SERIALIZER, accountsData);
+ }
+
+ public Recipe? GetRecipe(Guid id)
+ {
+ if (recipesData.TryGetValue(id, out RecipeData? data))
+ return ConvertRecipeDataToRecipe(data);
+ return null;
+ }
+
+ public RecipeRate GetRecipeRate(Guid user, Guid recipe)
+ {
+ return usersData[user].Rates[recipe];
+ }
+
+ public void InsertInUserList(Guid userId, Guid recipeId, uint persAmount)
+ {
+ usersData[userId].RecipesList[recipeId] = persAmount;
+ Save(USERS_FILENAME, USERS_SERIALIZER, usersData);
+ }
+
+ public void RemoveFromUserList(Guid userId, Guid recipeId)
+ {
+ usersData[userId].RecipesList.Remove(recipeId);
+ Save(USERS_FILENAME, USERS_SERIALIZER, usersData);
+ }
+
+
+ public void InsertRecipe(Recipe recipe)
+ {
+ recipesData[recipe.Info.Id] = new RecipeData(recipe.Info, recipe.Owner.Id, recipe.Ingredients, recipe.Steps);
+ Save(RECIPES_FILENAME, RECIPES_SERIALIZER, recipesData);
+ }
+
+ public void InsertUser(User user)
+ {
+ usersData[user.Id] = new UserData(user, new Dictionary(), new Dictionary());
+ Save(USERS_FILENAME, USERS_SERIALIZER, usersData);
+ }
+
+ public void InsertRate(Guid userId, Guid recipeId, RecipeRate rate)
+ {
+ usersData[userId].Rates[recipeId] = rate;
+ Save(USERS_FILENAME, USERS_SERIALIZER, usersData);
+ }
+
+ public void RemoveRecipe(Guid id)
+ {
+ recipesData.Remove(id);
+ Save(RECIPES_FILENAME, RECIPES_SERIALIZER, recipesData);
+ }
+
+ public ImmutableList ListAllRecipes()
+ {
+ return recipesData.Values.ToImmutableList().ConvertAll(ConvertRecipeDataToRecipe);
+ }
+
+
+ public ImmutableDictionary ListRatesOf(Guid user)
+ {
+ return usersData[user].Rates.ToImmutableDictionary();
+ }
+
+ public ImmutableDictionary GetRecipeListOf(Guid user)
+ {
+ return usersData[user].RecipesList.ToImmutableDictionary();
+ }
+
+ private Recipe ConvertRecipeDataToRecipe(RecipeData rd)
+ {
+ var owner = usersData[rd.OwnerID].User;
+ return new Recipe(rd.Info, owner, rd.Ingredients, rd.Steps);
+ }
+
+ private Dictionary Load(string fileName, DataContractSerializer deserializer)
+ {
+ var file = dbPath + "/" + fileName;
+ var fileInfo = new FileInfo(file);
+
+ if (!fileInfo.Exists)
+ fileInfo.Create();
+
+ if (fileInfo.Length == 0)
+ return new Dictionary(); //file is empty thus there is nothing to deserialize
+
+ string text = File.ReadAllText(file);
+
+ return JsonSerializer.Deserialize>(text);
+ }
+
+ private async void Save(string fileName, DataContractSerializer serializer, Dictionary dict)
+ {
+ string json = JsonSerializer.Serialize(dict);
+ using (var stream = WaitForFile(fileName, FileMode.Open, FileAccess.Write, FileShare.Write))
+ {
+ var bytes = Encoding.ASCII.GetBytes(json);
+ await stream.WriteAsync(bytes, 0, bytes.Length);
+ }
+ }
+
+ // This is a workaround function to wait for a file to be released before opening it.
+ // This was to fix the Save method that used to throw sometimes as the file were oftenly being scanned by the androids' antivirus.
+ // Simply wait until the file is released and return it. This function will never return until
+ private static FileStream WaitForFile(string fullPath, FileMode mode, FileAccess access, FileShare share)
+ {
+ for (int attempt = 0 ; attempt < 20; attempt++) //20 attempts equals to 2 seconds of wait
+ {
+ FileStream? fs = null;
+ try
+ {
+ fs = new FileStream(fullPath, mode, access, share);
+ return fs;
+ }
+ catch (IOException)
+ {
+ if (fs != null)
+ fs.Dispose();
+
+ Thread.Sleep(100);
+ }
+ }
+ throw new TimeoutException("Could not access file '" + fullPath + "', maximum attempts reached.");
+ }
+ }
+}