aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2021-04-27 18:22:57 +0800
committercrupest <crupest@outlook.com>2021-04-27 18:22:57 +0800
commitdeb02d10e6139bb74a63343e2a8b70fee11bec22 (patch)
tree160b7a2691786bfad8decbeeabe5fa1e09a876f3
parent956306bf47db6495386945c2dbfe623cf48b185a (diff)
downloadtimeline-deb02d10e6139bb74a63343e2a8b70fee11bec22.tar.gz
timeline-deb02d10e6139bb74a63343e2a8b70fee11bec22.tar.bz2
timeline-deb02d10e6139bb74a63343e2a8b70fee11bec22.zip
refactor: Refactor data services.
-rw-r--r--BackEnd/Timeline/Services/Data/DataManager.cs93
-rw-r--r--BackEnd/Timeline/Services/Data/DataManagerExtensions.cs26
-rw-r--r--BackEnd/Timeline/Services/Data/ETagGenerator.cs16
-rw-r--r--BackEnd/Timeline/Services/Data/IDataManager.cs49
-rw-r--r--BackEnd/Timeline/Services/Data/IETagGenerator.cs19
-rw-r--r--BackEnd/Timeline/Services/Data/Resource.Designer.cs (renamed from BackEnd/Timeline/Resources/Services/DataManager.Designer.cs)59
-rw-r--r--BackEnd/Timeline/Services/Data/Resource.resx (renamed from BackEnd/Timeline/Resources/Services/DataManager.resx)19
-rw-r--r--BackEnd/Timeline/Services/User/UserAvatarService.cs2
-rw-r--r--BackEnd/Timeline/Timeline.csproj18
9 files changed, 205 insertions, 96 deletions
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
{
- /// <summary>
- /// A data manager controlling data.
- /// </summary>
- /// <remarks>
- /// 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.
- /// </remarks>
- public interface IDataManager
- {
- /// <summary>
- /// Saves the data to a new entry if it does not exist,
- /// increases its ref count and returns a tag to the entry.
- /// </summary>
- /// <param name="data">The data. Can't be null.</param>
- /// <returns>The tag of the created entry.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="data"/> is null.</exception>
- public Task<string> RetainEntry(byte[] data);
-
- /// <summary>
- /// Decrease the the ref count of the entry.
- /// Remove it if ref count is zero.
- /// </summary>
- /// <param name="tag">The tag of the entry.</param>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="tag"/> is null.</exception>
- /// <remarks>
- /// It's no-op if entry with tag does not exist.
- /// </remarks>
- public Task FreeEntry(string tag);
-
- /// <summary>
- /// Retrieve the entry with given tag. If not exist, returns null.
- /// </summary>
- /// <param name="tag">The tag of the entry.</param>
- /// <returns>The data of the entry. If not exist, returns null.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="tag"/> is null.</exception>
- public Task<byte[]?> GetEntry(string tag);
- }
-
public class DataManager : IDataManager
{
+ private readonly ILogger<DataManager> _logger;
private readonly DatabaseContext _database;
private readonly IETagGenerator _eTagGenerator;
- public DataManager(DatabaseContext database, IETagGenerator eTagGenerator)
+ public DataManager(ILogger<DataManager> logger, DatabaseContext database, IETagGenerator eTagGenerator)
{
+ _logger = logger;
_database = database;
_eTagGenerator = eTagGenerator;
}
- public async Task<string> RetainEntry(byte[] data)
+ public async Task<string> 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<byte[]?> GetEntry(string tag)
+ public async Task<byte[]?> 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
- {
- /// <summary>
- /// Try to get an entry and throw <see cref="DatabaseCorruptedException"/> if not exist.
- /// </summary>
- public static async Task<byte[]> 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
+ {
+ /// <summary>
+ /// Try to get an entry and throw <see cref="DatabaseCorruptedException"/> if not exist.
+ /// </summary>
+ public static async Task<byte[]> 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
- {
- /// <summary>
- /// Generate a etag for given source.
- /// </summary>
- /// <param name="source">The source data.</param>
- /// <returns>The generated etag.</returns>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="source"/> is null.</exception>
- Task<string> 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<string> Generate(byte[] source)
+ public Task<string> 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
+{
+ /// <summary>
+ /// A data manager controlling data.
+ /// </summary>
+ /// <remarks>
+ /// 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.
+ /// </remarks>
+ public interface IDataManager
+ {
+ /// <summary>
+ /// Saves the data to a new entry if it does not exist,
+ /// increases its ref count and returns a tag to the entry.
+ /// </summary>
+ /// <param name="data">The data. Can't be null.</param>
+ /// <param name="cancellationToken">Cancellation token.</param>
+ /// <returns>The tag of the created entry.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="data"/> is null.</exception>
+ public Task<string> RetainEntry(byte[] data, CancellationToken cancellationToken = default);
+
+ /// <summary>
+ /// Decrease the the ref count of the entry.
+ /// Remove it if ref count is zero.
+ /// </summary>
+ /// <param name="tag">The tag of the entry.</param>
+ /// <param name="cancellationToken">Cancellation token.</param>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="tag"/> is null.</exception>
+ /// <remarks>
+ /// It's no-op if entry with tag does not exist.
+ /// </remarks>
+ public Task FreeEntry(string tag, CancellationToken cancellationToken = default);
+
+ /// <summary>
+ /// Retrieve the entry with given tag. If not exist, returns null.
+ /// </summary>
+ /// <param name="tag">The tag of the entry.</param>
+ /// <param name="cancellationToken">Cancellation token.</param>
+ /// <returns>The data of the entry. If not exist, returns null.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="tag"/> is null.</exception>
+ public Task<byte[]?> 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
+ {
+ /// <summary>
+ /// Generate a etag for given source.
+ /// </summary>
+ /// <param name="source">The source data.</param>
+ /// <param name="cancellationToken">Cancellation token.</param>
+ /// <returns>The generated etag.</returns>
+ /// <exception cref="ArgumentNullException">Thrown if <paramref name="source"/> is null.</exception>
+ /// <remarks>This function must guarantee the same result with equal given source.</remarks>
+ Task<string> GenerateETagAsync(byte[] source, CancellationToken cancellationToken = default);
+ }
+}
diff --git a/BackEnd/Timeline/Resources/Services/DataManager.Designer.cs b/BackEnd/Timeline/Services/Data/Resource.Designer.cs
index 0872059a..46b9eaf0 100644
--- a/BackEnd/Timeline/Resources/Services/DataManager.Designer.cs
+++ b/BackEnd/Timeline/Services/Data/Resource.Designer.cs
@@ -8,7 +8,7 @@
// </auto-generated>
//------------------------------------------------------------------------------
-namespace Timeline.Resources.Services {
+namespace Timeline.Services.Data {
using System;
@@ -22,14 +22,14 @@ namespace Timeline.Resources.Services {
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class DataManager {
+ 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 DataManager() {
+ internal Resource() {
}
/// <summary>
@@ -39,7 +39,7 @@ namespace Timeline.Resources.Services {
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Services.DataManager", typeof(DataManager).Assembly);
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Services.Data.Resource", typeof(Resource).Assembly);
resourceMan = temp;
}
return resourceMan;
@@ -61,11 +61,56 @@ namespace Timeline.Resources.Services {
}
/// <summary>
- /// Looks up a localized string similar to Entry with given tag does not exist..
+ /// Looks up a localized string similar to Can&apos;t get data entry of tag {0}. {1}.
/// </summary>
- internal static string ExceptionEntryNotExist {
+ internal static string GetEntryAndCheckNotExist {
get {
- return ResourceManager.GetString("ExceptionEntryNotExist", resourceCulture);
+ return ResourceManager.GetString("GetEntryAndCheckNotExist", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Decrease ref count of existing entry with tag {0}..
+ /// </summary>
+ internal static string LogDataManagerFreeEntryDecreaseRefCount {
+ get {
+ return ResourceManager.GetString("LogDataManagerFreeEntryDecreaseRefCount", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Attempt to free an entry that does not exist with tag {0}..
+ /// </summary>
+ internal static string LogDataManagerFreeEntryNotExist {
+ get {
+ return ResourceManager.GetString("LogDataManagerFreeEntryNotExist", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Remove a entry with tag {0}..
+ /// </summary>
+ internal static string LogDataManagerFreeEntryRemove {
+ get {
+ return ResourceManager.GetString("LogDataManagerFreeEntryRemove", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Add ref count of existing entry with tag {0}..
+ /// </summary>
+ internal static string LogDataManagerRetainEntryAddRefCount {
+ get {
+ return ResourceManager.GetString("LogDataManagerRetainEntryAddRefCount", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Create a new entry with tag {0}..
+ /// </summary>
+ internal static string LogDataManagerRetainEntryCreate {
+ get {
+ return ResourceManager.GetString("LogDataManagerRetainEntryCreate", resourceCulture);
}
}
}
diff --git a/BackEnd/Timeline/Resources/Services/DataManager.resx b/BackEnd/Timeline/Services/Data/Resource.resx
index 688e0e96..6783efae 100644
--- a/BackEnd/Timeline/Resources/Services/DataManager.resx
+++ b/BackEnd/Timeline/Services/Data/Resource.resx
@@ -117,7 +117,22 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
- <data name="ExceptionEntryNotExist" xml:space="preserve">
- <value>Entry with given tag does not exist.</value>
+ <data name="GetEntryAndCheckNotExist" xml:space="preserve">
+ <value>Can't get data entry of tag {0}. {1}</value>
+ </data>
+ <data name="LogDataManagerFreeEntryDecreaseRefCount" xml:space="preserve">
+ <value>Decrease ref count of existing entry with tag {0}.</value>
+ </data>
+ <data name="LogDataManagerFreeEntryNotExist" xml:space="preserve">
+ <value>Attempt to free an entry that does not exist with tag {0}.</value>
+ </data>
+ <data name="LogDataManagerFreeEntryRemove" xml:space="preserve">
+ <value>Remove a entry with tag {0}.</value>
+ </data>
+ <data name="LogDataManagerRetainEntryAddRefCount" xml:space="preserve">
+ <value>Add ref count of existing entry with tag {0}.</value>
+ </data>
+ <data name="LogDataManagerRetainEntryCreate" xml:space="preserve">
+ <value>Create a new entry with tag {0}.</value>
</data>
</root> \ No newline at end of file
diff --git a/BackEnd/Timeline/Services/User/UserAvatarService.cs b/BackEnd/Timeline/Services/User/UserAvatarService.cs
index 0a4b7438..a92df3cb 100644
--- a/BackEnd/Timeline/Services/User/UserAvatarService.cs
+++ b/BackEnd/Timeline/Services/User/UserAvatarService.cs
@@ -95,7 +95,7 @@ namespace Timeline.Services.User
if (_cacheData == null || File.GetLastWriteTime(path) > _cacheDigest!.LastModified)
{
var data = await File.ReadAllBytesAsync(path);
- _cacheDigest = new CacheableDataDigest(await _eTagGenerator.Generate(data), File.GetLastWriteTime(path));
+ _cacheDigest = new CacheableDataDigest(await _eTagGenerator.GenerateETagAsync(data), File.GetLastWriteTime(path));
Image.Identify(data, out var format);
_cacheData = new ByteData(data, format.DefaultMimeType);
}
diff --git a/BackEnd/Timeline/Timeline.csproj b/BackEnd/Timeline/Timeline.csproj
index 47713284..6a74d0c0 100644
--- a/BackEnd/Timeline/Timeline.csproj
+++ b/BackEnd/Timeline/Timeline.csproj
@@ -123,11 +123,6 @@
<AutoGen>True</AutoGen>
<DependentUpon>Validator.resx</DependentUpon>
</Compile>
- <Compile Update="Resources\Services\DataManager.Designer.cs">
- <DesignTime>True</DesignTime>
- <AutoGen>True</AutoGen>
- <DependentUpon>DataManager.resx</DependentUpon>
- </Compile>
<Compile Update="Resources\Services\TimelineService.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
@@ -148,6 +143,11 @@
<AutoGen>True</AutoGen>
<DependentUpon>UserTokenService.resx</DependentUpon>
</Compile>
+ <Compile Update="Services\Data\Resource.Designer.cs">
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Resource.resx</DependentUpon>
+ </Compile>
<Compile Update="Services\Imaging\Resource.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
@@ -237,10 +237,6 @@
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Validator.Designer.cs</LastGenOutput>
</EmbeddedResource>
- <EmbeddedResource Update="Resources\Services\DataManager.resx">
- <Generator>ResXFileCodeGenerator</Generator>
- <LastGenOutput>DataManager.Designer.cs</LastGenOutput>
- </EmbeddedResource>
<EmbeddedResource Update="Resources\Services\TimelineService.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>TimelineService.Designer.cs</LastGenOutput>
@@ -257,6 +253,10 @@
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>UserTokenService.Designer.cs</LastGenOutput>
</EmbeddedResource>
+ <EmbeddedResource Update="Services\Data\Resource.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resource.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
<EmbeddedResource Update="Services\Imaging\Resource.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resource.Designer.cs</LastGenOutput>