namespace AIStudio.Assistants.ModelComparison; public sealed partial class ModelComparisonStore { /// /// The folder of the presets, one file per preset. /// private const string PRESETS_DIRECTORY_NAME = "presets"; /// /// Gets the folder of the presets. /// private string PresetsDirectory => Path.Combine(this.RootDirectory, PRESETS_DIRECTORY_NAME); /// /// Gets the file of a preset. /// /// The ID of the preset. private string PresetPath(Guid presetId) => Path.Combine(this.PresetsDirectory, $"{presetId:D}.json"); /// /// Lists the presets, sorted by name. /// /// /// A preset this version cannot read is left out, but stays on the disk, see ModelComparisonJson. /// /// The cancellation token. /// The presets. public async Task> ListPresetsAsync(CancellationToken token = default) { await this.InitializeAsync(token); if (!Directory.Exists(this.PresetsDirectory)) return []; List presets = []; foreach (var path in Directory.EnumerateFiles(this.PresetsDirectory, "*.json")) { token.ThrowIfCancellationRequested(); if (!Guid.TryParseExact(Path.GetFileNameWithoutExtension(path), "D", out var presetId)) continue; // The ID inside the file must not lead to another file than the one it was read from: if (await this.ReadJsonAsync(path, token) is { } preset) presets.Add(preset with { Id = presetId }); } return [.. presets.OrderBy(preset => preset.Name, StringComparer.CurrentCultureIgnoreCase)]; } /// /// Stores a preset, as a new one or in place of the one with the same ID. /// /// The preset. /// The cancellation token. /// The preset has no ID or no name. public async Task SavePresetAsync(ModelComparisonPreset preset, CancellationToken token = default) { if (preset.Id == Guid.Empty) throw new ArgumentException("A preset needs an ID.", nameof(preset)); if (string.IsNullOrWhiteSpace(preset.Name)) throw new ArgumentException("A preset needs a name.", nameof(preset)); await this.InitializeAsync(token); var gate = this.GetLock(preset.Id); await gate.WaitAsync(token); try { await WriteJsonAtomicAsync(this.PresetPath(preset.Id), preset with { Name = preset.Name.Trim() }, token); } finally { gate.Release(); } } /// /// Deletes a preset. /// /// The ID of the preset. /// The cancellation token. public async Task DeletePresetAsync(Guid presetId, CancellationToken token = default) { await this.InitializeAsync(token); var gate = this.GetLock(presetId); await gate.WaitAsync(token); try { var path = this.PresetPath(presetId); if (File.Exists(path)) File.Delete(path); } finally { gate.Release(); this.ForgetLock(presetId); } } }