// Copyright © WireMock.Net using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json; using Stef.Validation; namespace WireMock.Handlers; /// /// Provides a file-based implementation of that persists scenario states to disk and allows concurrent access. /// public class FileBasedScenarioStateStore : IScenarioStateStore { private readonly ConcurrentDictionary _scenarios = new(StringComparer.OrdinalIgnoreCase); private readonly string _scenariosFolder; private readonly object _lock = new(); /// /// Initializes a new instance of the FileBasedScenarioStateStore class using the specified root folder as the base directory for scenario state storage. /// /// The root directory under which scenario state data will be stored. Must be a valid file system path. public FileBasedScenarioStateStore(string rootFolder) { Guard.NotNullOrEmpty(rootFolder); _scenariosFolder = Path.Combine(rootFolder, "__admin", "scenarios"); Directory.CreateDirectory(_scenariosFolder); LoadScenariosFromDisk(); } /// public bool TryGet(string name, [NotNullWhen(true)] out ScenarioState? state) { return _scenarios.TryGetValue(name, out state); } /// public IReadOnlyList GetAll() { return _scenarios.Values.ToArray(); } /// public bool ContainsKey(string name) { return _scenarios.ContainsKey(name); } /// public bool TryAdd(string name, ScenarioState scenarioState) { if (_scenarios.TryAdd(name, scenarioState)) { WriteScenarioToFile(name, scenarioState); return true; } return false; } /// public ScenarioState AddOrUpdate(string name, Func addFactory, Func updateFactory) { lock (_lock) { var result = _scenarios.AddOrUpdate(name, addFactory, updateFactory); WriteScenarioToFile(name, result); return result; } } /// public ScenarioState? Update(string name, Action updateAction) { lock (_lock) { if (_scenarios.TryGetValue(name, out var state)) { updateAction(state); WriteScenarioToFile(name, state); return state; } return null; } } /// public bool TryRemove(string name) { if (_scenarios.TryRemove(name, out _)) { DeleteScenarioFile(name); return true; } return false; } /// public void Clear() { _scenarios.Clear(); foreach (var file in Directory.GetFiles(_scenariosFolder, "*.json")) { File.Delete(file); } } private string GetScenarioFilePath(string name) { var sanitized = string.Concat(name.Select(c => Path.GetInvalidFileNameChars().Contains(c) ? '_' : c)); return Path.Combine(_scenariosFolder, sanitized + ".json"); } private void WriteScenarioToFile(string name, ScenarioState state) { var json = JsonConvert.SerializeObject(state, Formatting.Indented); File.WriteAllText(GetScenarioFilePath(name), json); } private void DeleteScenarioFile(string name) { var path = GetScenarioFilePath(name); if (File.Exists(path)) { File.Delete(path); } } private void LoadScenariosFromDisk() { foreach (var file in Directory.GetFiles(_scenariosFolder, "*.json")) { var json = File.ReadAllText(file); var state = JsonConvert.DeserializeObject(json); if (state != null) { _scenarios.TryAdd(state.Name, state); } } } }