Families.Web 1.0.0.0
사진, 동영상, 글, 댓글과 반응을 가족끼리만 공유하는 비공개 앨범·타임라인 서비스입니다.
로딩중...
검색중...
일치하는것 없음
JsonReactionStore.cs
이 파일의 문서화 페이지로 가기
1using System.IO;
2using System.Text.Json;
4
6
15public sealed class JsonReactionStore : IReactionStore
16{
25 private readonly IFamilyTenantStore _tenants;
34 private static readonly SemaphoreSlim _gate = new(1, 1);
35
44 private static readonly JsonSerializerOptions _jsonOpts = new()
45 {
46 WriteIndented = true,
47 PropertyNameCaseInsensitive = true
48 };
49
66 public JsonReactionStore(IFamilyTenantStore tenants) => _tenants = tenants;
67
92 private string ReactionsDir(string slug) =>
93 Path.Combine(_tenants.GetTenantDataPath(slug), "reactions");
94
127 private string ReactionPath(string slug, string postId) =>
128 Path.Combine(ReactionsDir(slug), $"{Sanitize(postId)}.json");
129
170 public async Task<ReactionSummary> GetAsync(string slug, string postId, CancellationToken ct = default)
171 {
172 var path = ReactionPath(slug, postId);
173 if (!File.Exists(path)) return new ReactionSummary { PostId = postId };
174
175 await _gate.WaitAsync(ct).ConfigureAwait(false);
176 try
177 {
178 await using var fs = File.OpenRead(path);
179 return await JsonSerializer.DeserializeAsync<ReactionSummary>(fs, _jsonOpts, ct).ConfigureAwait(false)
180 ?? new ReactionSummary { PostId = postId };
181 }
182 finally { _gate.Release(); }
183 }
184
233 public async Task AddReactionAsync(string slug, string postId, string emoji, CancellationToken ct = default)
234 {
235 var summary = await GetAsync(slug, postId, ct).ConfigureAwait(false);
236 summary.EmojiCounts.TryGetValue(emoji, out int current);
237 summary.EmojiCounts[emoji] = current + 1;
238 await SaveAsync(slug, summary, ct).ConfigureAwait(false);
239 }
240
289 public async Task AddCommentAsync(string slug, string postId, CommentEntry comment, CancellationToken ct = default)
290 {
291 var summary = await GetAsync(slug, postId, ct).ConfigureAwait(false);
292 comment.PostId = postId;
293 summary.Comments.Add(comment);
294 await SaveAsync(slug, summary, ct).ConfigureAwait(false);
295 }
296
345 public async Task DeleteCommentAsync(string slug, string postId, string commentId, CancellationToken ct = default)
346 {
347 var summary = await GetAsync(slug, postId, ct).ConfigureAwait(false);
348 summary.Comments.RemoveAll(c => c.Id == commentId);
349 await SaveAsync(slug, summary, ct).ConfigureAwait(false);
350 }
351
392 private async Task SaveAsync(string slug, ReactionSummary summary, CancellationToken ct = default)
393 {
394 var dir = ReactionsDir(slug);
395 Directory.CreateDirectory(dir);
396
397 var path = ReactionPath(slug, summary.PostId);
398 var tmp = path + ".tmp";
399
400 await _gate.WaitAsync(ct).ConfigureAwait(false);
401 try
402 {
403 await using (var fs = File.Create(tmp))
404 await JsonSerializer.SerializeAsync(fs, summary, _jsonOpts, ct).ConfigureAwait(false);
405
406 File.Copy(tmp, path, overwrite: true);
407 File.Delete(tmp);
408 }
409 finally { _gate.Release(); }
410 }
411
436 private static string Sanitize(string id) =>
437 string.Concat(id.Split(Path.GetInvalidFileNameChars()));
438}
async Task DeleteCommentAsync(string slug, string postId, string commentId, CancellationToken ct=default)
JsonReactionStore(IFamilyTenantStore tenants)
async Task< ReactionSummary > GetAsync(string slug, string postId, CancellationToken ct=default)
readonly IFamilyTenantStore _tenants
async Task SaveAsync(string slug, ReactionSummary summary, CancellationToken ct=default)
async Task AddCommentAsync(string slug, string postId, CommentEntry comment, CancellationToken ct=default)
static readonly JsonSerializerOptions _jsonOpts
static readonly SemaphoreSlim _gate
string ReactionPath(string slug, string postId)
async Task AddReactionAsync(string slug, string postId, string emoji, CancellationToken ct=default)