Billing/Billing.Shared/Store/StoreHelper.cs

308 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Billing.Models;
using SQLite;
using Xamarin.Essentials;
using Resource = Billing.Languages.Resource;
namespace Billing.Store
{
public class StoreHelper
{
public static readonly string PersonalFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
public static readonly string CacheFolder = FileSystem.CacheDirectory;
public static string DatabasePath => Path.Combine(PersonalFolder, dbfile);
#region Sqlite3
private const string dbfile = "data.db3";
private static SQLiteAsyncConnection database;
#endregion
public static async Task<bool> ReloadDatabase(string file)
{
var path = DatabasePath;
if (string.Equals(file, path, StringComparison.OrdinalIgnoreCase))
{
return false;
}
try
{
if (database != null)
{
await database.CloseAsync();
}
}
catch (Exception ex)
{
Helper.Error("database.close", ex);
}
try
{
File.Copy(file, path, true);
}
catch (Exception ex)
{
Helper.Error("file.import", ex);
}
try
{
database = new SQLiteAsyncConnection(path,
SQLiteOpenFlags.ReadWrite |
SQLiteOpenFlags.Create |
SQLiteOpenFlags.SharedCache);
}
catch (Exception ex)
{
Helper.Error("database.connect", ex);
}
return true;
}
public static readonly AsyncLazy<StoreHelper> Instance = new(async () =>
{
var instance = new StoreHelper();
try
{
await database.CreateTablesAsync<Category, Account, Bill, Logs>();
} catch (Exception ex)
{
Helper.Error("database.init.table", ex);
}
try
{
var count = await database.ExecuteScalarAsync<int>("SELECT COUNT(Id) FROM [Category]");
if (count <= 0)
{
await database.InsertAsync(new Account { Name = Resource.Cash, Icon = "wallet" });
}
}
catch (Exception ex)
{
Helper.Error("database.init.account", ex);
}
try
{
var count = await database.ExecuteScalarAsync<int>("SELECT COUNT(Id) FROM [Category]");
if (count <= 0)
{
// init categories
await database.InsertAllAsync(new List<Category>
{
// sample categories
new() { Name = Resource.Clothing, Icon = "clothes" },
new() { Name = Resource.Food, Icon = "food" },
new() { Name = Resource.Daily, Icon = "daily" },
new() { Name = Resource.Trans, Icon = "trans" },
new() { Name = Resource.Entertainment, Icon = "face" },
new() { Name = Resource.Learn, Icon = "learn" },
new() { Name = Resource.Medical, Icon = "medical" },
new() { Name = Resource.OtherSpending, Icon = "plus" },
new() { Type = CategoryType.Income, Name = Resource.Earnings, Icon = "#brand#btc" },
new() { Type = CategoryType.Income, Name = Resource.OtherIncome, Icon = "plus" },
// sub-categories
new() { ParentId = 1, Name = Resource.Jewellery, Icon = "gem" },
new() { ParentId = 1, Name = Resource.Cosmetics, Icon = "makeup" },
new() { ParentId = 2, Name = Resource.Brunch, Icon = "brunch" },
new() { ParentId = 2, Name = Resource.Dinner, Icon = "dinner" },
new() { ParentId = 2, Name = Resource.Drinks, Icon = "drink" },
new() { ParentId = 2, Name = Resource.Fruit, Icon = "fruit" },
new() { ParentId = 3, Name = Resource.UtilityBill, Icon = "bill" },
new() { ParentId = 3, Name = Resource.PropertyFee, Icon = "fee" },
new() { ParentId = 3, Name = Resource.Rent, Icon = "rent" },
new() { ParentId = 3, Name = Resource.Maintenance, Icon = "maintenance" },
new() { ParentId = 4, Name = Resource.LightRail, Icon = "rail" },
new() { ParentId = 4, Name = Resource.Taxi, Icon = "taxi" },
new() { ParentId = 5, Name = Resource.Fitness, Icon = "fitness" },
new() { ParentId = 5, Name = Resource.Party, Icon = "party" },
new() { ParentId = 9, Type = CategoryType.Income, Name = Resource.Salary, Icon = "#brand#buffer" },
new() { ParentId = 9, Type = CategoryType.Income, Name = Resource.Bonus, Icon = "dollar" },
});
}
}
catch (Exception ex)
{
Helper.Error("database.init.category", ex);
}
return instance;
});
public static async Task<int> SaveAccountItemAsync(Account account)
{
var instance = await Instance;
return await instance.SaveItemAsync(account);
}
public static async Task<int> DeleteAccountItemAsync(Account account)
{
var instance = await Instance;
return await instance.DeleteItemAsync(account);
}
public static async Task<int> SaveBillItemAsync(Bill bill)
{
var instance = await Instance;
return await instance.SaveItemAsync(bill);
}
public static async Task<int> DeleteBillItemAsync(Bill bill)
{
var instance = await Instance;
return await instance.DeleteItemAsync(bill);
}
public static async Task<int> SaveCategoryItemAsync(Category category)
{
var instance = await Instance;
return await instance.SaveItemAsync(category);
}
public static async Task<int> DeleteCategoryItemAsync(Category category)
{
var instance = await Instance;
return await instance.DeleteItemAsync(category);
}
public static async Task<int> GetLogsCount()
{
await Instance;
return await database.ExecuteScalarAsync<int>("SELECT COUNT(Id) FROM [Logs]");
}
public static async Task<int> SaveLogItemAsync(Logs log)
{
var instance = await Instance;
return await instance.SaveItemAsync(log);
}
public static string GetLogFile()
{
return Path.Combine(CacheFolder, "logs.csv");
}
public static async Task<string> ExportLogs()
{
try
{
var instance = await Instance;
var logs = await instance.GetListAsync<Logs>();
var file = GetLogFile();
using var writer = new StreamWriter(File.Open(file, FileMode.Create, FileAccess.Write));
writer.WriteLine("Id,DateTime,Category,Detail");
foreach (var log in logs)
{
var category = log.Category?.Replace("\n", " \\n ");
var detail = log.Detail?.Replace("\n", " \\n ");
writer.WriteLine($"{log.Id},{log.LogTime},{category},{detail}");
}
writer.Flush();
await database.ExecuteAsync("DELETE FROM [Logs]; DELETE FROM [sqlite_sequence] WHERE [name] = 'Logs'");
return file;
}
catch
{
return null;
}
}
private StoreHelper()
{
if (database == null)
{
try
{
database = new SQLiteAsyncConnection(DatabasePath,
SQLiteOpenFlags.ReadWrite |
SQLiteOpenFlags.Create |
SQLiteOpenFlags.SharedCache);
}
catch (Exception ex)
{
Helper.Error("database.ctor.connect", ex);
}
}
}
public Task<List<T>> GetListAsync<T>(string query, params object[] args) where T : new()
{
try
{
return database.QueryAsync<T>(query, args);
}
catch (Exception ex)
{
Helper.Error("db.read", $"failed to read db, query: {string.Format(query, args)}, error: {ex.Message}");
}
return default;
}
public Task<T> GetItemAsync<T>(int id) where T : IIdItem, new()
{
try
{
var source = new TaskCompletionSource<T>();
Task.Run(async () =>
{
var item = await database.FindWithQueryAsync<T>($"SELECT * FROM [{typeof(T).Name}] WHERE [Id] = ? LIMIT 1", id);
source.SetResult(item);
});
return source.Task;
}
catch (Exception ex)
{
Helper.Error("db.read", $"failed to get item, table: {typeof(T)}, id: {id}, error: {ex.Message}");
}
return default;
}
#region Helper
public Task<List<T>> GetListAsync<T>() where T : new()
{
try
{
return database.Table<T>().ToListAsync();
}
catch (Exception ex)
{
Helper.Error("db.read", $"failed to read db, error: {ex.Message}");
}
return default;
}
public Task<int> SaveItemAsync<T>(T item) where T : IIdItem
{
try
{
if (item.Id > 0)
{
return database.UpdateAsync(item);
}
else
{
return database.InsertAsync(item);
}
}
catch (Exception ex)
{
Helper.Error("db.write", $"failed to insert/update item, table: {typeof(T)}, id: {item.Id}, item: {item}, error: {ex.Message}");
}
return Task.FromResult(0);
}
public Task<int> DeleteItemAsync<T>(T item) where T : IIdItem
{
try
{
return database.DeleteAsync(item);
}
catch (Exception ex)
{
Helper.Error("db.delete", $"failed to delete item, table: {typeof(T)}, id: {item.Id}, item: {item}, error: {ex.Message}");
}
return Task.FromResult(0);
}
#endregion
}
}