|
- using System.Collections.Concurrent;
- using System.Text.Json;
- using System.Text.Json.Serialization;
- using Campaign_Tracker.Server.Seed.Models;
-
- namespace Campaign_Tracker.Server.Seed;
-
- public sealed class FileSeedDataStore : ISeedDataStore
- {
- private static readonly ConcurrentDictionary<string, SemaphoreSlim> FileLocks = new(
- StringComparer.OrdinalIgnoreCase);
-
- private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
- {
- WriteIndented = true,
- Converters = { new JsonStringEnumConverter() },
- };
-
- private readonly string _path;
- private readonly SemaphoreSlim _sync;
-
- public FileSeedDataStore(string path)
- {
- ArgumentException.ThrowIfNullOrWhiteSpace(path);
- _path = Path.GetFullPath(path);
- _sync = FileLocks.GetOrAdd(_path, _ => new SemaphoreSlim(1, 1));
- }
-
- public async Task UpsertSeedDataAsync(SeedDataSet seedData, CancellationToken cancellationToken = default)
- {
- await _sync.WaitAsync(cancellationToken);
- try
- {
- var snapshot = await LoadAsync(cancellationToken);
- InMemorySeedDataStore.UpsertMissing(
- snapshot.ReferenceValues, seedData.ReferenceValues, InMemorySeedDataStore.Clone);
- InMemorySeedDataStore.UpsertMissing(
- snapshot.RequiredFieldRules, seedData.RequiredFieldRules, InMemorySeedDataStore.Clone);
- InMemorySeedDataStore.UpsertMissing(
- snapshot.EscalationRules, seedData.EscalationRules, InMemorySeedDataStore.Clone);
- await SaveAsync(snapshot, cancellationToken);
- }
- finally
- {
- _sync.Release();
- }
- }
-
- public async Task<IReadOnlyList<ReferenceValue>> GetReferenceValuesAsync(CancellationToken cancellationToken = default)
- {
- await _sync.WaitAsync(cancellationToken);
- try
- {
- var snapshot = await LoadAsync(cancellationToken);
- return snapshot.ReferenceValues.Select(InMemorySeedDataStore.Clone).ToArray();
- }
- finally
- {
- _sync.Release();
- }
- }
-
- public async Task<IReadOnlyList<RequiredFieldRule>> GetRequiredFieldRulesAsync(CancellationToken cancellationToken = default)
- {
- await _sync.WaitAsync(cancellationToken);
- try
- {
- var snapshot = await LoadAsync(cancellationToken);
- return snapshot.RequiredFieldRules.Select(InMemorySeedDataStore.Clone).ToArray();
- }
- finally
- {
- _sync.Release();
- }
- }
-
- public async Task<IReadOnlyList<EscalationRule>> GetEscalationRulesAsync(CancellationToken cancellationToken = default)
- {
- await _sync.WaitAsync(cancellationToken);
- try
- {
- var snapshot = await LoadAsync(cancellationToken);
- return snapshot.EscalationRules.Select(InMemorySeedDataStore.Clone).ToArray();
- }
- finally
- {
- _sync.Release();
- }
- }
-
- public async Task SaveReferenceValueAsync(
- ReferenceValue referenceValue,
- CancellationToken cancellationToken = default)
- {
- await _sync.WaitAsync(cancellationToken);
- try
- {
- var snapshot = await LoadAsync(cancellationToken);
- InMemorySeedDataStore.ReplaceBySeedKey(
- snapshot.ReferenceValues, referenceValue, InMemorySeedDataStore.Clone);
- await SaveAsync(snapshot, cancellationToken);
- }
- finally
- {
- _sync.Release();
- }
- }
-
- public async Task SaveRequiredFieldRuleAsync(
- RequiredFieldRule rule,
- CancellationToken cancellationToken = default)
- {
- await _sync.WaitAsync(cancellationToken);
- try
- {
- var snapshot = await LoadAsync(cancellationToken);
- InMemorySeedDataStore.ReplaceBySeedKey(
- snapshot.RequiredFieldRules, rule, InMemorySeedDataStore.Clone);
- await SaveAsync(snapshot, cancellationToken);
- }
- finally
- {
- _sync.Release();
- }
- }
-
- public async Task SaveEscalationRuleAsync(
- EscalationRule rule,
- CancellationToken cancellationToken = default)
- {
- await _sync.WaitAsync(cancellationToken);
- try
- {
- var snapshot = await LoadAsync(cancellationToken);
- InMemorySeedDataStore.ReplaceBySeedKey(
- snapshot.EscalationRules, rule, InMemorySeedDataStore.Clone);
- await SaveAsync(snapshot, cancellationToken);
- }
- finally
- {
- _sync.Release();
- }
- }
-
- private async Task<InMemorySeedDataStore.SeedDataSnapshot> LoadAsync(CancellationToken cancellationToken)
- {
- if (!File.Exists(_path))
- {
- return new InMemorySeedDataStore.SeedDataSnapshot();
- }
-
- await using var stream = File.OpenRead(_path);
- var snapshot = await JsonSerializer.DeserializeAsync<InMemorySeedDataStore.SeedDataSnapshot>(
- stream,
- SerializerOptions,
- cancellationToken);
- return snapshot ?? new InMemorySeedDataStore.SeedDataSnapshot();
- }
-
- private async Task SaveAsync(
- InMemorySeedDataStore.SeedDataSnapshot snapshot,
- CancellationToken cancellationToken)
- {
- var directory = Path.GetDirectoryName(_path);
- if (!string.IsNullOrWhiteSpace(directory))
- {
- Directory.CreateDirectory(directory);
- }
-
- var tempPath = $"{_path}.{Guid.NewGuid():N}.tmp";
- await using (var stream = File.Create(tempPath))
- {
- await JsonSerializer.SerializeAsync(stream, snapshot, SerializerOptions, cancellationToken);
- }
-
- File.Move(tempPath, _path, overwrite: true);
- }
- }
|