switch to sqlite

This commit is contained in:
2022-03-11 16:10:11 +08:00
parent f5f16d43f4
commit 5ec4119025
25 changed files with 286 additions and 435 deletions

View File

@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Billing.Models;
using Billing.UI;
using SQLite;
using Xamarin.Essentials;
using Resource = Billing.Languages.Resource;
@ -14,50 +15,20 @@ namespace Billing.Store
public static readonly string PersonalFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
public static readonly string CacheFolder = FileSystem.CacheDirectory;
private const string accountFile = "accounts.xml";
private const string billFile = "bills.xml";
private const string categoryFile = "categories.xml";
#region Sqlite3
private const string dbfile = "data.db3";
private static SQLiteAsyncConnection database;
#endregion
private static StoreHelper instance;
private static StoreHelper Instance => instance ??= new StoreHelper();
public static List<Account> GetAccounts() => Instance.GetAccountsInternal();
public static void WriteAccounts(IEnumerable<Account> accounts) => Instance.WriteAccountsInternal(accounts);
public static List<Bill> GetBills() => Instance.GetBillsInternal();
public static void WriteBills(IEnumerable<Bill> bills) => Instance.WriteBillsInternal(bills);
public static List<Category> GetCategories() => Instance.GetCategoriesInternal();
public static void WriteCategories(IEnumerable<Category> categories) => Instance.WriteCategoriesInternal(categories);
private StoreHelper() { }
private List<Account> GetAccountsInternal()
private static readonly AsyncLazy<StoreHelper> Instance = new(async () =>
{
return GetList<Account>(Path.Combine(PersonalFolder, accountFile));
}
private void WriteAccountsInternal(IEnumerable<Account> accounts)
{
var filename = Path.Combine(PersonalFolder, accountFile);
WriteList(filename, accounts);
}
private List<Bill> GetBillsInternal()
{
return GetList<Bill>(Path.Combine(PersonalFolder, billFile));
}
private void WriteBillsInternal(IEnumerable<Bill> bills)
{
var filename = Path.Combine(PersonalFolder, billFile);
WriteList(filename, bills);
}
private List<Category> GetCategoriesInternal()
{
var list = GetList<Category>(Path.Combine(PersonalFolder, categoryFile));
if (list == null || list.Count == 0)
var instance = new StoreHelper();
await database.CreateTablesAsync<Category, Account, Bill>();
var count = await database.ExecuteScalarAsync<int>("SELECT COUNT(Id) FROM [Category]");
if (count <= 0)
{
list = new List<Category>
// init categories
await database.InsertAllAsync(new List<Category>
{
// sample categories
new() { Id = 1, Name = Resource.Clothing, Icon = "clothes" },
@ -89,53 +60,145 @@ namespace Billing.Store
new() { Id = 113, ParentId = 6, Name = Resource.Party, Icon = "party" },
new() { Id = 200, ParentId = 10, Type = CategoryType.Income, Name = Resource.Salary, Icon = "#brand#buffer" },
new() { Id = 201, ParentId = 10, Type = CategoryType.Income, Name = Resource.Bonus, Icon = "dollar" },
};
Task.Run(() => WriteCategoriesInternal(list));
});
}
return list;
return instance;
});
public static async Task<List<Account>> GetAccountsAsync()
{
var instance = await Instance;
return await instance.GetListAsync<Account>();
}
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);
}
private void WriteCategoriesInternal(IEnumerable<Category> categories)
public static async Task<List<Bill>> GetBillsAsync()
{
var filename = Path.Combine(PersonalFolder, categoryFile);
WriteList(filename, categories);
var instance = await Instance;
return await instance.GetListAsync<Bill>();
}
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<List<Category>> GetCategoriesAsync()
{
var instance = await Instance;
return await instance.GetListAsync<Category>();
}
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);
}
private StoreHelper()
{
database = new SQLiteAsyncConnection(Path.Combine(PersonalFolder, dbfile),
SQLiteOpenFlags.ReadWrite |
SQLiteOpenFlags.Create |
SQLiteOpenFlags.SharedCache);
}
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 list = await database.QueryAsync<T>($"SELECT * FROM [{typeof(T).Name}] WHERE [Id] = ?", id);
source.SetResult(list.FirstOrDefault());
});
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
private void WriteList<T>(string filename, IEnumerable<T> list) where T : IModel, new()
private Task<List<T>> GetListAsync<T>() where T : new()
{
if (list == null)
{
return;
}
try
{
using var stream = File.Open(filename, FileMode.Create);
list.ToStream(stream);
return database.Table<T>().ToListAsync();
}
catch (Exception ex)
{
Helper.Error("file.write", $"failed to write file: {filename}, error: {ex.Message}");
Helper.Error("db.read", $"failed to read db, error: {ex.Message}");
}
return default;
}
private List<T> GetList<T>(string file) where T : IModel, new()
private Task<int> SaveItemAsync<T>(T item) where T : IIdItem
{
try
{
if (File.Exists(file))
if (item.Id < 0)
{
using var stream = File.OpenRead(file);
var list = ModelExtensionHelper.FromStream<T>(stream);
return list;
return database.InsertAsync(item);
}
else
{
return database.UpdateAsync(item);
}
}
catch (Exception ex)
{
Helper.Error("file.read", $"failed to read file: {file}, error: {ex.Message}");
Helper.Error("db.write", $"failed to insert/update item, table: {typeof(T)}, id: {item.Id}, item: {item}, error: {ex.Message}");
}
return default;
return Task.FromResult(-1);
}
private 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(-1);
}
#endregion