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. /// /// The tag of the entry. /// The data of the entry. /// Thrown when is null. /// Thrown when entry with given tag does not exist. 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 == null) throw new InvalidOperationException(Resources.Services.DataManager.ExceptionEntryNotExist); return entity.Data; } } }