using System.Text.Json;
using ClawdDotNet.Models;
namespace ClawdDotNet.Services;
///
/// Thread-safe JSON persistence for job execution history.
/// Uses file locking to handle concurrent access from multiple schedulers.
///
public sealed class JobHistoryService
{
private readonly string _filePath;
private readonly Lock _lock = new();
private readonly int _maxEntries;
private List _entries = new();
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true
};
public JobHistoryService(string instancePath, int maxEntries = 500)
{
_filePath = Path.Combine(instancePath, "job_history.json");
_maxEntries = maxEntries;
Load();
}
///
/// Adds a new entry to the history (thread-safe, persists immediately).
///
public void Add(JobHistoryEntry entry)
{
lock (_lock)
{
_entries.Insert(0, entry); // newest first
// Trim old entries
if (_entries.Count > _maxEntries)
_entries = _entries.Take(_maxEntries).ToList();
Save();
}
}
///
/// Returns a snapshot of all entries (newest first).
///
public List GetAll()
{
lock (_lock)
return new List(_entries);
}
private void Load()
{
lock (_lock)
{
try
{
if (!File.Exists(_filePath))
{
_entries = new List();
return;
}
using var stream = new FileStream(_filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
_entries = JsonSerializer.Deserialize>(stream, JsonOptions)
?? new List();
}
catch
{
_entries = new List();
}
}
}
private void Save()
{
try
{
var tmpPath = _filePath + ".tmp";
using (var stream = new FileStream(tmpPath, FileMode.Create, FileAccess.Write, FileShare.None))
{
JsonSerializer.Serialize(stream, _entries, JsonOptions);
}
File.Move(tmpPath, _filePath, overwrite: true);
}
catch
{
// Silently ignore write failures — next save will retry
}
}
}