using System.Runtime.CompilerServices;
namespace AIStudio.Assistants.ModelComparison;
public sealed partial class ModelComparisonStore
{
///
/// The folder of the stored comparisons, one file per batch.
///
private const string RESULTS_DIRECTORY_NAME = "results";
///
/// Gets the folder of the stored comparisons.
///
private string ResultsDirectory => Path.Combine(this.RootDirectory, RESULTS_DIRECTORY_NAME);
///
/// Gets the file of a stored comparison.
///
/// The ID of the batch.
private string ResultPath(Guid recordId) => Path.Combine(this.ResultsDirectory, $"{recordId:D}.json");
///
/// Stores what the assistant keeps of a batch, in place of what it kept of the same batch before.
///
/// The stored comparison.
/// The cancellation token.
/// The stored comparison has no ID.
public async Task SaveResultAsync(ModelComparisonRecord record, CancellationToken token = default)
{
if (record.Id == Guid.Empty)
throw new ArgumentException("A stored comparison needs an ID.", nameof(record));
await this.InitializeAsync(token);
var gate = this.GetLock(record.Id);
await gate.WaitAsync(token);
try
{
await WriteJsonAtomicAsync(this.ResultPath(record.Id), record, token);
}
finally
{
gate.Release();
}
}
///
/// Reads the stored comparisons one by one, so that never more than one of them is in memory at once.
///
///
/// A file this version cannot read is skipped, but stays on the disk, see ModelComparisonJson.
///
/// The cancellation token.
/// The stored comparisons, in no particular order.
public async IAsyncEnumerable ReadResultsAsync([EnumeratorCancellation] CancellationToken token = default)
{
await this.InitializeAsync(token);
if (!Directory.Exists(this.ResultsDirectory))
yield break;
foreach (var path in Directory.EnumerateFiles(this.ResultsDirectory, "*.json"))
{
token.ThrowIfCancellationRequested();
if (!Guid.TryParseExact(Path.GetFileNameWithoutExtension(path), "D", out var recordId))
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 { } record)
yield return record with { Id = recordId };
}
}
///
/// Deletes all stored comparisons, the presets aside.
///
///
/// Unlike reading, deleting takes the files this version cannot read along: the user asked for all results to go.
///
/// The cancellation token.
public async Task DeleteAllResultsAsync(CancellationToken token = default)
{
await this.InitializeAsync(token);
if (!Directory.Exists(this.ResultsDirectory))
return;
foreach (var path in Directory.EnumerateFiles(this.ResultsDirectory, "*.json").ToList())
{
token.ThrowIfCancellationRequested();
if (!Guid.TryParseExact(Path.GetFileNameWithoutExtension(path), "D", out var recordId))
continue;
var gate = this.GetLock(recordId);
await gate.WaitAsync(token);
try
{
File.Delete(path);
}
finally
{
gate.Release();
this.ForgetLock(recordId);
}
}
}
}