using Microsoft.EntityFrameworkCore; using System; using System.Linq; using System.Threading.Tasks; using Timeline.Entities; namespace Timeline.Services.Data { /// /// 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; } } }