aboutsummaryrefslogtreecommitdiff
path: root/BackEnd/Timeline/Services/Data/DataManager.cs
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2021-04-25 21:20:04 +0800
committercrupest <crupest@outlook.com>2021-04-25 21:20:04 +0800
commita4a75188bd17e31b39a02511bbd6d628bab5c909 (patch)
treef2f9c63eb5beabb7a1a2f2605c2d5022f6a72c08 /BackEnd/Timeline/Services/Data/DataManager.cs
parent434be212c77bdade04722046e92c3dac25d0aff3 (diff)
downloadtimeline-a4a75188bd17e31b39a02511bbd6d628bab5c909.tar.gz
timeline-a4a75188bd17e31b39a02511bbd6d628bab5c909.tar.bz2
timeline-a4a75188bd17e31b39a02511bbd6d628bab5c909.zip
...
Diffstat (limited to 'BackEnd/Timeline/Services/Data/DataManager.cs')
-rw-r--r--BackEnd/Timeline/Services/Data/DataManager.cs138
1 files changed, 138 insertions, 0 deletions
diff --git a/BackEnd/Timeline/Services/Data/DataManager.cs b/BackEnd/Timeline/Services/Data/DataManager.cs
new file mode 100644
index 00000000..d9a4491d
--- /dev/null
+++ b/BackEnd/Timeline/Services/Data/DataManager.cs
@@ -0,0 +1,138 @@
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Linq;
+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 DatabaseContext _database;
+ private readonly IETagGenerator _eTagGenerator;
+
+ public DataManager(DatabaseContext database, IETagGenerator eTagGenerator)
+ {
+ _database = database;
+ _eTagGenerator = eTagGenerator;
+ }
+
+ public async Task<string> 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<byte[]?> 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
+ {
+ /// <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;
+ }
+ }
+}