From deb02d10e6139bb74a63343e2a8b70fee11bec22 Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 27 Apr 2021 18:22:57 +0800 Subject: refactor: Refactor data services. --- BackEnd/Timeline/Services/Data/DataManager.cs | 93 +++++--------- .../Services/Data/DataManagerExtensions.cs | 26 ++++ BackEnd/Timeline/Services/Data/ETagGenerator.cs | 16 +-- BackEnd/Timeline/Services/Data/IDataManager.cs | 49 ++++++++ BackEnd/Timeline/Services/Data/IETagGenerator.cs | 19 +++ .../Timeline/Services/Data/Resource.Designer.cs | 117 +++++++++++++++++ BackEnd/Timeline/Services/Data/Resource.resx | 138 +++++++++++++++++++++ 7 files changed, 381 insertions(+), 77 deletions(-) create mode 100644 BackEnd/Timeline/Services/Data/DataManagerExtensions.cs create mode 100644 BackEnd/Timeline/Services/Data/IDataManager.cs create mode 100644 BackEnd/Timeline/Services/Data/IETagGenerator.cs create mode 100644 BackEnd/Timeline/Services/Data/Resource.Designer.cs create mode 100644 BackEnd/Timeline/Services/Data/Resource.resx (limited to 'BackEnd/Timeline/Services/Data') diff --git a/BackEnd/Timeline/Services/Data/DataManager.cs b/BackEnd/Timeline/Services/Data/DataManager.cs index d9a4491d..9de17da3 100644 --- a/BackEnd/Timeline/Services/Data/DataManager.cs +++ b/BackEnd/Timeline/Services/Data/DataManager.cs @@ -1,73 +1,40 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using System; using System.Linq; +using System.Threading; 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 ILogger _logger; private readonly DatabaseContext _database; private readonly IETagGenerator _eTagGenerator; - public DataManager(DatabaseContext database, IETagGenerator eTagGenerator) + public DataManager(ILogger logger, DatabaseContext database, IETagGenerator eTagGenerator) { + _logger = logger; _database = database; _eTagGenerator = eTagGenerator; } - public async Task RetainEntry(byte[] data) + public async Task RetainEntry(byte[] data, CancellationToken cancellationToken = default) { if (data == null) throw new ArgumentNullException(nameof(data)); - var tag = await _eTagGenerator.Generate(data); + var tag = await _eTagGenerator.GenerateETagAsync(data, cancellationToken); + + var entity = await _database.Data.Where(d => d.Tag == tag).SingleOrDefaultAsync(cancellationToken); + bool create; - var entity = await _database.Data.Where(d => d.Tag == tag).SingleOrDefaultAsync(); if (entity == null) { + create = true; entity = new DataEntity { Tag = tag, @@ -78,42 +45,54 @@ namespace Timeline.Services.Data } else { + create = false; entity.Ref += 1; } - await _database.SaveChangesAsync(); + await _database.SaveChangesAsync(cancellationToken); + + _logger.LogInformation(create ? Resource.LogDataManagerRetainEntryCreate : Resource.LogDataManagerRetainEntryAddRefCount, tag); return tag; } - public async Task FreeEntry(string tag) + public async Task FreeEntry(string tag, CancellationToken cancellationToken = default) { if (tag == null) throw new ArgumentNullException(nameof(tag)); - var entity = await _database.Data.Where(d => d.Tag == tag).SingleOrDefaultAsync(); + var entity = await _database.Data.Where(d => d.Tag == tag).SingleOrDefaultAsync(cancellationToken); if (entity != null) { + bool remove; + if (entity.Ref == 1) { + remove = true; _database.Data.Remove(entity); } else { + remove = false; entity.Ref -= 1; } - await _database.SaveChangesAsync(); + await _database.SaveChangesAsync(cancellationToken); + _logger.LogInformation(remove ? Resource.LogDataManagerFreeEntryRemove : Resource.LogDataManagerFreeEntryDecreaseRefCount, tag); + } + else + { + _logger.LogInformation(Resource.LogDataManagerFreeEntryNotExist, tag); } } - public async Task GetEntry(string tag) + public async Task GetEntry(string tag, CancellationToken cancellationToken = default) { if (tag == null) throw new ArgumentNullException(nameof(tag)); - var entity = await _database.Data.Where(d => d.Tag == tag).Select(d => new { d.Data }).SingleOrDefaultAsync(); + var entity = await _database.Data.Where(d => d.Tag == tag).Select(d => new { d.Data }).SingleOrDefaultAsync(cancellationToken); if (entity is null) return null; @@ -121,18 +100,4 @@ namespace Timeline.Services.Data 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; - } - } } diff --git a/BackEnd/Timeline/Services/Data/DataManagerExtensions.cs b/BackEnd/Timeline/Services/Data/DataManagerExtensions.cs new file mode 100644 index 00000000..64d35b9b --- /dev/null +++ b/BackEnd/Timeline/Services/Data/DataManagerExtensions.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading.Tasks; + +namespace Timeline.Services.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) + { + if (dataManager is null) + throw new ArgumentNullException(nameof(dataManager)); + if (tag is null) + throw new ArgumentNullException(nameof(tag)); + if (notExistMessage is null) + throw new ArgumentNullException(nameof(notExistMessage)); + + var data = await dataManager.GetEntry(tag); + if (data is null) + throw new DatabaseCorruptedException(string.Format(Resource.GetEntryAndCheckNotExist, tag, notExistMessage)); + return data; + } + } +} diff --git a/BackEnd/Timeline/Services/Data/ETagGenerator.cs b/BackEnd/Timeline/Services/Data/ETagGenerator.cs index 847c120b..03837dc9 100644 --- a/BackEnd/Timeline/Services/Data/ETagGenerator.cs +++ b/BackEnd/Timeline/Services/Data/ETagGenerator.cs @@ -1,20 +1,10 @@ using System; using System.Security.Cryptography; +using System.Threading; using System.Threading.Tasks; namespace Timeline.Services.Data { - public interface IETagGenerator - { - /// - /// Generate a etag for given source. - /// - /// The source data. - /// The generated etag. - /// Thrown if is null. - Task Generate(byte[] source); - } - public sealed class ETagGenerator : IETagGenerator, IDisposable { private readonly SHA1 _sha1; @@ -25,12 +15,12 @@ namespace Timeline.Services.Data _sha1 = SHA1.Create(); } - public Task Generate(byte[] source) + public Task GenerateETagAsync(byte[] source, CancellationToken cancellationToken = default) { if (source == null) throw new ArgumentNullException(nameof(source)); - return Task.Run(() => Convert.ToBase64String(_sha1.ComputeHash(source))); + return Task.Run(() => Convert.ToBase64String(_sha1.ComputeHash(source)), cancellationToken); } private bool _disposed; // To detect redundant calls diff --git a/BackEnd/Timeline/Services/Data/IDataManager.cs b/BackEnd/Timeline/Services/Data/IDataManager.cs new file mode 100644 index 00000000..6a87c1b2 --- /dev/null +++ b/BackEnd/Timeline/Services/Data/IDataManager.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +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. + /// Cancellation token. + /// The tag of the created entry. + /// Thrown when is null. + public Task RetainEntry(byte[] data, CancellationToken cancellationToken = default); + + /// + /// Decrease the the ref count of the entry. + /// Remove it if ref count is zero. + /// + /// The tag of the entry. + /// Cancellation token. + /// Thrown when is null. + /// + /// It's no-op if entry with tag does not exist. + /// + public Task FreeEntry(string tag, CancellationToken cancellationToken = default); + + /// + /// Retrieve the entry with given tag. If not exist, returns null. + /// + /// The tag of the entry. + /// Cancellation token. + /// The data of the entry. If not exist, returns null. + /// Thrown when is null. + public Task GetEntry(string tag, CancellationToken cancellationToken = default); + } +} diff --git a/BackEnd/Timeline/Services/Data/IETagGenerator.cs b/BackEnd/Timeline/Services/Data/IETagGenerator.cs new file mode 100644 index 00000000..fa81c449 --- /dev/null +++ b/BackEnd/Timeline/Services/Data/IETagGenerator.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Timeline.Services.Data +{ + public interface IETagGenerator + { + /// + /// Generate a etag for given source. + /// + /// The source data. + /// Cancellation token. + /// The generated etag. + /// Thrown if is null. + /// This function must guarantee the same result with equal given source. + Task GenerateETagAsync(byte[] source, CancellationToken cancellationToken = default); + } +} diff --git a/BackEnd/Timeline/Services/Data/Resource.Designer.cs b/BackEnd/Timeline/Services/Data/Resource.Designer.cs new file mode 100644 index 00000000..46b9eaf0 --- /dev/null +++ b/BackEnd/Timeline/Services/Data/Resource.Designer.cs @@ -0,0 +1,117 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Timeline.Services.Data { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resource() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Services.Data.Resource", typeof(Resource).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Can't get data entry of tag {0}. {1}. + /// + internal static string GetEntryAndCheckNotExist { + get { + return ResourceManager.GetString("GetEntryAndCheckNotExist", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Decrease ref count of existing entry with tag {0}.. + /// + internal static string LogDataManagerFreeEntryDecreaseRefCount { + get { + return ResourceManager.GetString("LogDataManagerFreeEntryDecreaseRefCount", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Attempt to free an entry that does not exist with tag {0}.. + /// + internal static string LogDataManagerFreeEntryNotExist { + get { + return ResourceManager.GetString("LogDataManagerFreeEntryNotExist", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Remove a entry with tag {0}.. + /// + internal static string LogDataManagerFreeEntryRemove { + get { + return ResourceManager.GetString("LogDataManagerFreeEntryRemove", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Add ref count of existing entry with tag {0}.. + /// + internal static string LogDataManagerRetainEntryAddRefCount { + get { + return ResourceManager.GetString("LogDataManagerRetainEntryAddRefCount", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Create a new entry with tag {0}.. + /// + internal static string LogDataManagerRetainEntryCreate { + get { + return ResourceManager.GetString("LogDataManagerRetainEntryCreate", resourceCulture); + } + } + } +} diff --git a/BackEnd/Timeline/Services/Data/Resource.resx b/BackEnd/Timeline/Services/Data/Resource.resx new file mode 100644 index 00000000..6783efae --- /dev/null +++ b/BackEnd/Timeline/Services/Data/Resource.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Can't get data entry of tag {0}. {1} + + + Decrease ref count of existing entry with tag {0}. + + + Attempt to free an entry that does not exist with tag {0}. + + + Remove a entry with tag {0}. + + + Add ref count of existing entry with tag {0}. + + + Create a new entry with tag {0}. + + \ No newline at end of file -- cgit v1.2.3