A database based on .net
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

261 lines
11 KiB

// Author: Gockner, Simon
// Created: 2020-01-27
// Copyright(c) 2020 SimonG. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using GBase.Attributes;
using GBase.Exceptions;
using GBase.Factories;
using GBase.FileHandling.Factories;
using GBase.Helpers;
using GBase.Interfaces;
using GBase.Interfaces.DataHandling;
using GBase.Interfaces.DataHandling.Pool;
using GBase.Interfaces.FileHandling;
namespace GBase
{
/// <summary>
/// A <see cref="IGBase"/> table
/// </summary>
public class GBaseTable<T> : IGBaseTable<T> where T : IGBaseObject, new()
{
private readonly IFileHandler _fileHandler;
private readonly IDataHandlerPool _dataHandlerPool;
private readonly IGBaseColumnFactory _gBaseColumnFactory;
/// <summary>
/// A <see cref="IGBase"/> table
/// </summary>
/// <param name="fileHandlerFactory"></param>
/// <param name="dataHandlerPool">The <see cref="IDataHandlerPool"/></param>
/// <param name="gBaseColumnFactory"></param>
public GBaseTable(IFileHandlerFactory fileHandlerFactory, IDataHandlerPool dataHandlerPool, IGBaseColumnFactory gBaseColumnFactory)
{
_fileHandler = fileHandlerFactory.Create();
_dataHandlerPool = dataHandlerPool;
_gBaseColumnFactory = gBaseColumnFactory;
Columns = new List<IGBaseColumn>();
Entries = new List<T>();
}
/// <summary>
/// The <see cref="System.Type"/> of the class that this <see cref="IGBaseTable"/> represents
/// </summary>
public Type Type { get; private set; }
/// <summary>
/// The name of this <see cref="IGBaseTable"/>
/// </summary>
public string FolderName { get; private set; }
/// <summary>
/// The <see cref="IGBaseColumn"/>s of this <see cref="IGBaseTable"/>
/// </summary>
public List<IGBaseColumn> Columns { get; }
/// <summary>
/// The entries of this <see cref="IGBaseTable"/>
/// </summary>
public List<T> Entries { get; }
private int CurrentLastKey
{
get
{
T lastEntry = Entries.LastOrDefault();
if (lastEntry == null)
return -1;
return lastEntry.Key;
}
}
/// <summary>
/// Initialize this <see cref="IGBase"/>
/// </summary>
/// <param name="type">The <see cref="System.Type"/> of the class that this <see cref="IGBaseTable"/> represents</param>
/// <param name="databasePath">The path to the database files</param>
/// <param name="folderName"></param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to cancel the asynchronous operation</param>
/// <returns>True if successful, false if not</returns>
public async Task Init(Type type, string databasePath, string folderName, CancellationToken cancellationToken)
{
Type = type;
FolderName = folderName;
//TODO: Init columns list depending on GBaseColumnAttributes set for this GBaseTable
foreach (var property in type.GetProperties())
{
GBaseColumnAttribute gBaseColumnAttribute = property.GetCustomAttribute<GBaseColumnAttribute>() ??
property.GetAttributeFromInheritedInterfaces<GBaseColumnAttribute>();
if (gBaseColumnAttribute == null)
continue;
IGBaseColumn gBaseColumn = _gBaseColumnFactory.Create(property.Name, property.PropertyType, gBaseColumnAttribute.IsKey);
AddColumn(gBaseColumn);
}
List<IGBaseFile> files = _fileHandler.Init(databasePath, FolderName, cancellationToken);
if (files == null)
return;
foreach (var file in files) //create entries for existing files
{
using (file.UseFile())
{
T entry = new T();
List<object> parameters = new List<object>();
foreach (var column in Columns.Where(c => !c.IsKey))
{
using IPoolItem<IDataHandler> dataHandlerItem = await _dataHandlerPool.RequestDataHandler(this, false, cancellationToken);
parameters.Add(await GenericMethodCaller.CallAsync(dataHandlerItem.Use(),
nameof(IDataHandler.GetValue),
BindingFlags.Public | BindingFlags.Instance,
typeof(T), column.Type,
file.File, column.Name, cancellationToken));
}
IGBaseColumn keyColumn = Columns.FirstOrDefault(c => c.IsKey);
if (keyColumn == null)
throw new MissingKeyColumnException<T>();
GBaseKey key = new GBaseKey();
using (IPoolItem<IDataHandler> dataHandlerItem = await _dataHandlerPool.RequestDataHandler(this, false, cancellationToken))
{
key.Key = await dataHandlerItem.Use().GetValue<T, int>(file.File, keyColumn.Name, cancellationToken);
}
entry.Initialize(key, parameters);
file.Entry = entry;
Entries.Add(entry);
}
}
}
/// <summary>
/// Add a given <see cref="IGBaseColumn"/> to this <see cref="IGBaseTable"/>
/// </summary>
/// <param name="column">The given <see cref="IGBaseColumn"/></param>
/// <returns>True if successful, false if not</returns>
public bool AddColumn(IGBaseColumn column) //TODO: if a column is added, it has to be added to every entry file as well
{
if (Columns.Contains(column))
return false;
Columns.Add(column);
return true;
}
/// <summary>
/// Remove a given <see cref="IGBaseColumn"/> from this <see cref="IGBaseTable"/>
/// </summary>
/// <param name="column">The given <see cref="IGBaseColumn"/></param>
/// <returns>True if successful, false if not</returns>
public bool RemoveColumn(IGBaseColumn column) //TODO: if a column is removed, it has to be removed from every entry file as well
{
if (!Columns.Contains(column))
return false;
return Columns.Remove(column);
}
/// <summary>
/// Add an entry that implements <see cref="IGBaseObject"/> to this <see cref="IGBaseTable"/>
/// </summary>
/// <param name="entry">The entry implementing <see cref="IGBaseObject"/></param>
/// <param name="cancellationToken"></param>
/// <returns>True if successful, false if not</returns>
public async Task<bool> AddEntry(T entry, CancellationToken cancellationToken)
{
entry.Key ??= new GBaseKey();
if (entry.Key != -1)
throw new InvalidKeyException("Key of entry is already set. Make sure it is -1 when initializing.");
entry.Key.Key = CurrentLastKey + 1; //set next possible key
Entries.Add(entry);
using IGBaseFile file = _fileHandler.CreateEntryFile(entry, this);
using IPoolItem<IDataHandler> dataHandlerItem = await _dataHandlerPool.RequestDataHandler(this, false, cancellationToken);
await dataHandlerItem.Use().AddEntry(entry, this, file.File, cancellationToken);
return true;
}
public T GetEntryForKey(int key) => Entries.FirstOrDefault(e => e.Key == key);
/// <summary>
/// Remove an entry that implements <see cref="IGBaseObject"/> from this <see cref="IGBaseTable"/>
/// </summary>
/// <param name="entry">The entry implementing <see cref="IGBaseObject"/></param>
/// <returns>True if successful, false if not</returns>
public async Task<bool> RemoveEntry(T entry)
{
if (!Entries.Contains(entry))
return false;
await _fileHandler.DeleteEntryFile(entry);
return Entries.Remove(entry);
}
public async Task SetValue<TProperty>(T entry, string propertyName, TProperty value, CancellationToken cancellationToken)
{
using IGBaseFile file = await _fileHandler.RequestEntryFile(entry);
using IPoolItem<IDataHandler> dataHandlerItem = await _dataHandlerPool.RequestDataHandler(this, false, cancellationToken);
await dataHandlerItem.Use().SetValue<T, TProperty>(file.File, propertyName, value, cancellationToken);
}
public async Task<TProperty> GetValue<TProperty>(T entry, string propertyName, CancellationToken cancellationToken)
{
using IGBaseFile file = await _fileHandler.RequestEntryFile(entry);
using IPoolItem<IDataHandler> dataHandlerItem = await _dataHandlerPool.RequestDataHandler(this, false, cancellationToken);
return await dataHandlerItem.Use().GetValue<T, TProperty>(file.File, propertyName, cancellationToken);
}
public async Task<IEnumerable<TProperty>> GetValues<TProperty>(T entry, string propertyName, CancellationToken cancellationToken)
{
using IGBaseFile file = await _fileHandler.RequestEntryFile(entry);
using IPoolItem<IDataHandler> dataHandlerItem = await _dataHandlerPool.RequestDataHandler(this, false, cancellationToken);
return await dataHandlerItem.Use().GetValues<T, TProperty>(file.File, propertyName, cancellationToken);
}
/// <summary>
/// The <see cref="IAsyncDisposable.DisposeAsync"/> method
/// </summary>
/// <returns>A <see cref="ValueTask"/> to await</returns>
public async ValueTask DisposeAsync()
{
await _fileHandler.DisposeAsync();
await _dataHandlerPool.DisposeAsync();
foreach (var column in Columns)
{
await column.DisposeAsync();
}
Columns.Clear();
foreach (var entry in Entries.ToList())
{
await RemoveEntry(entry);
}
Entries.Clear();
}
}
}