using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
namespace Timeline.Services
{
///
/// A data manager controlling data.
///
///
/// Identical data will be saved as one copy and return the same tag.
/// Every data has a ref count. When data is retained, ref count increase.
/// When data is freed, ref count decease. If ref count is decreased
/// to 0, the data entry will be destroyed and no longer occupy space.
///
public interface IDataManager
{
///
/// Saves the data to a new entry if it does not exist,
/// increases its ref count and returns a tag to the entry.
///
/// The data. Can't be null.
/// The tag of the created entry.
/// Thrown when is null.
public Task RetainEntry(byte[] data);
///
/// Decrease the the ref count of the entry.
/// Remove it if ref count is zero.
///
/// The tag of the entry.
/// Thrown when is null.
///
/// It's no-op if entry with tag does not exist.
///
public Task FreeEntry(string tag);
///
/// Retrieve the entry with given tag. If not exist, returns null.
///
/// The tag of the entry.
/// The data of the entry. If not exist, returns null.
/// Thrown when is null.
public Task GetEntry(string tag);
}
public class DataManager : IDataManager
{
private readonly DatabaseContext _database;
private readonly IETagGenerator _eTagGenerator;
public DataManager(DatabaseContext database, IETagGenerator eTagGenerator)
{
_database = database;
_eTagGenerator = eTagGenerator;
}
public async Task RetainEntry(byte[] data)
{
if (data == null)
throw new ArgumentNullException(nameof(data));
var tag = await _eTagGenerator.Generate(data);
var entity = await _database.Data.Where(d => d.Tag == tag).SingleOrDefaultAsync();
if (entity == null)
{
entity = new DataEntity
{
Tag = tag,
Data = data,
Ref = 1
};
_database.Data.Add(entity);
}
else
{
entity.Ref += 1;
}
await _database.SaveChangesAsync();
return tag;
}
public async Task FreeEntry(string tag)
{
if (tag == null)
throw new ArgumentNullException(nameof(tag));
var entity = await _database.Data.Where(d => d.Tag == tag).SingleOrDefaultAsync();
if (entity != null)
{
if (entity.Ref == 1)
{
_database.Data.Remove(entity);
}
else
{
entity.Ref -= 1;
}
await _database.SaveChangesAsync();
}
}
public async Task GetEntry(string tag)
{
if (tag == null)
throw new ArgumentNullException(nameof(tag));
var entity = await _database.Data.Where(d => d.Tag == tag).Select(d => new { d.Data }).SingleOrDefaultAsync();
if (entity is null)
return null;
return entity.Data;
}
}
public static class DataManagerExtensions
{
///
/// Try to get an entry and throw if not exist.
///
public static async Task GetEntryAndCheck(this IDataManager dataManager, string tag, string notExistMessage)
{
var data = await dataManager.GetEntry(tag);
if (data is null)
throw new DatabaseCorruptedException($"Can't get data of tag {tag}. {notExistMessage}");
return data;
}
}
}