You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

179 line
5.7KB

  1. using System.Collections.Concurrent;
  2. using System.Text.Json;
  3. using System.Text.Json.Serialization;
  4. using Campaign_Tracker.Server.Seed.Models;
  5. namespace Campaign_Tracker.Server.Seed;
  6. public sealed class FileSeedDataStore : ISeedDataStore
  7. {
  8. private static readonly ConcurrentDictionary<string, SemaphoreSlim> FileLocks = new(
  9. StringComparer.OrdinalIgnoreCase);
  10. private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
  11. {
  12. WriteIndented = true,
  13. Converters = { new JsonStringEnumConverter() },
  14. };
  15. private readonly string _path;
  16. private readonly SemaphoreSlim _sync;
  17. public FileSeedDataStore(string path)
  18. {
  19. ArgumentException.ThrowIfNullOrWhiteSpace(path);
  20. _path = Path.GetFullPath(path);
  21. _sync = FileLocks.GetOrAdd(_path, _ => new SemaphoreSlim(1, 1));
  22. }
  23. public async Task UpsertSeedDataAsync(SeedDataSet seedData, CancellationToken cancellationToken = default)
  24. {
  25. await _sync.WaitAsync(cancellationToken);
  26. try
  27. {
  28. var snapshot = await LoadAsync(cancellationToken);
  29. InMemorySeedDataStore.UpsertMissing(
  30. snapshot.ReferenceValues, seedData.ReferenceValues, InMemorySeedDataStore.Clone);
  31. InMemorySeedDataStore.UpsertMissing(
  32. snapshot.RequiredFieldRules, seedData.RequiredFieldRules, InMemorySeedDataStore.Clone);
  33. InMemorySeedDataStore.UpsertMissing(
  34. snapshot.EscalationRules, seedData.EscalationRules, InMemorySeedDataStore.Clone);
  35. await SaveAsync(snapshot, cancellationToken);
  36. }
  37. finally
  38. {
  39. _sync.Release();
  40. }
  41. }
  42. public async Task<IReadOnlyList<ReferenceValue>> GetReferenceValuesAsync(CancellationToken cancellationToken = default)
  43. {
  44. await _sync.WaitAsync(cancellationToken);
  45. try
  46. {
  47. var snapshot = await LoadAsync(cancellationToken);
  48. return snapshot.ReferenceValues.Select(InMemorySeedDataStore.Clone).ToArray();
  49. }
  50. finally
  51. {
  52. _sync.Release();
  53. }
  54. }
  55. public async Task<IReadOnlyList<RequiredFieldRule>> GetRequiredFieldRulesAsync(CancellationToken cancellationToken = default)
  56. {
  57. await _sync.WaitAsync(cancellationToken);
  58. try
  59. {
  60. var snapshot = await LoadAsync(cancellationToken);
  61. return snapshot.RequiredFieldRules.Select(InMemorySeedDataStore.Clone).ToArray();
  62. }
  63. finally
  64. {
  65. _sync.Release();
  66. }
  67. }
  68. public async Task<IReadOnlyList<EscalationRule>> GetEscalationRulesAsync(CancellationToken cancellationToken = default)
  69. {
  70. await _sync.WaitAsync(cancellationToken);
  71. try
  72. {
  73. var snapshot = await LoadAsync(cancellationToken);
  74. return snapshot.EscalationRules.Select(InMemorySeedDataStore.Clone).ToArray();
  75. }
  76. finally
  77. {
  78. _sync.Release();
  79. }
  80. }
  81. public async Task SaveReferenceValueAsync(
  82. ReferenceValue referenceValue,
  83. CancellationToken cancellationToken = default)
  84. {
  85. await _sync.WaitAsync(cancellationToken);
  86. try
  87. {
  88. var snapshot = await LoadAsync(cancellationToken);
  89. InMemorySeedDataStore.ReplaceBySeedKey(
  90. snapshot.ReferenceValues, referenceValue, InMemorySeedDataStore.Clone);
  91. await SaveAsync(snapshot, cancellationToken);
  92. }
  93. finally
  94. {
  95. _sync.Release();
  96. }
  97. }
  98. public async Task SaveRequiredFieldRuleAsync(
  99. RequiredFieldRule rule,
  100. CancellationToken cancellationToken = default)
  101. {
  102. await _sync.WaitAsync(cancellationToken);
  103. try
  104. {
  105. var snapshot = await LoadAsync(cancellationToken);
  106. InMemorySeedDataStore.ReplaceBySeedKey(
  107. snapshot.RequiredFieldRules, rule, InMemorySeedDataStore.Clone);
  108. await SaveAsync(snapshot, cancellationToken);
  109. }
  110. finally
  111. {
  112. _sync.Release();
  113. }
  114. }
  115. public async Task SaveEscalationRuleAsync(
  116. EscalationRule rule,
  117. CancellationToken cancellationToken = default)
  118. {
  119. await _sync.WaitAsync(cancellationToken);
  120. try
  121. {
  122. var snapshot = await LoadAsync(cancellationToken);
  123. InMemorySeedDataStore.ReplaceBySeedKey(
  124. snapshot.EscalationRules, rule, InMemorySeedDataStore.Clone);
  125. await SaveAsync(snapshot, cancellationToken);
  126. }
  127. finally
  128. {
  129. _sync.Release();
  130. }
  131. }
  132. private async Task<InMemorySeedDataStore.SeedDataSnapshot> LoadAsync(CancellationToken cancellationToken)
  133. {
  134. if (!File.Exists(_path))
  135. {
  136. return new InMemorySeedDataStore.SeedDataSnapshot();
  137. }
  138. await using var stream = File.OpenRead(_path);
  139. var snapshot = await JsonSerializer.DeserializeAsync<InMemorySeedDataStore.SeedDataSnapshot>(
  140. stream,
  141. SerializerOptions,
  142. cancellationToken);
  143. return snapshot ?? new InMemorySeedDataStore.SeedDataSnapshot();
  144. }
  145. private async Task SaveAsync(
  146. InMemorySeedDataStore.SeedDataSnapshot snapshot,
  147. CancellationToken cancellationToken)
  148. {
  149. var directory = Path.GetDirectoryName(_path);
  150. if (!string.IsNullOrWhiteSpace(directory))
  151. {
  152. Directory.CreateDirectory(directory);
  153. }
  154. var tempPath = $"{_path}.{Guid.NewGuid():N}.tmp";
  155. await using (var stream = File.Create(tempPath))
  156. {
  157. await JsonSerializer.SerializeAsync(stream, snapshot, SerializerOptions, cancellationToken);
  158. }
  159. File.Move(tempPath, _path, overwrite: true);
  160. }
  161. }

Powered by TurnKey Linux.