using DeepDrftContent.FileDatabase.Models;
using Microsoft.Extensions.Logging;
namespace DeepDrftContent.FileDatabase.Services;
///
/// Watches index files for external modifications and triggers reloads.
/// Uses FileSystemWatcher to detect changes made by other processes (e.g., CLI).
///
public class IndexWatcher : IDisposable
{
private readonly Dictionary _watchers = new();
private readonly Dictionary _reloadCallbacks = new();
private readonly object _lock = new();
private readonly ILogger? _logger;
private bool _disposed;
public IndexWatcher(ILogger? logger = null)
{
_logger = logger;
}
///
/// Registers an index file to be watched for changes.
///
/// Full path to the directory containing the index file
/// Callback to invoke when the index file changes
public void Watch(string indexPath, Action onChanged)
{
lock (_lock)
{
if (_disposed) return;
// Already watching this path
if (_watchers.ContainsKey(indexPath))
{
_reloadCallbacks[indexPath] = onChanged;
return;
}
try
{
var watcher = new FileSystemWatcher(indexPath)
{
Filter = "index",
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime,
EnableRaisingEvents = true
};
watcher.Changed += OnIndexChanged;
watcher.Created += OnIndexChanged;
_watchers[indexPath] = watcher;
_reloadCallbacks[indexPath] = onChanged;
if (_logger != null)
_logger.LogDebug("IndexWatcher: Watching {IndexPath}/index", indexPath);
else
Console.WriteLine($"IndexWatcher: Watching {indexPath}/index");
}
catch (Exception ex)
{
if (_logger != null)
_logger.LogWarning(ex, "IndexWatcher: Failed to watch {IndexPath}", indexPath);
else
Console.WriteLine($"IndexWatcher: Failed to watch {indexPath}: {ex.Message}");
}
}
}
///
/// Stops watching an index file.
///
public void Unwatch(string indexPath)
{
lock (_lock)
{
if (_watchers.TryGetValue(indexPath, out var watcher))
{
watcher.EnableRaisingEvents = false;
watcher.Dispose();
_watchers.Remove(indexPath);
_reloadCallbacks.Remove(indexPath);
}
}
}
private void OnIndexChanged(object sender, FileSystemEventArgs e)
{
var watcher = sender as FileSystemWatcher;
if (watcher == null) return;
var indexPath = watcher.Path;
lock (_lock)
{
if (_reloadCallbacks.TryGetValue(indexPath, out var callback))
{
if (_logger != null)
_logger.LogDebug("IndexWatcher: Index changed at {IndexPath}, triggering reload", indexPath);
else
Console.WriteLine($"IndexWatcher: Index changed at {indexPath}, triggering reload");
// Invoke callback on a background thread to avoid blocking the watcher
Task.Run(() =>
{
try
{
callback();
}
catch (Exception ex)
{
if (_logger != null)
_logger.LogWarning(ex, "IndexWatcher: Reload callback failed for {IndexPath}", indexPath);
else
Console.WriteLine($"IndexWatcher: Reload callback failed: {ex.Message}");
}
});
}
}
}
public void Dispose()
{
lock (_lock)
{
if (_disposed) return;
_disposed = true;
foreach (var watcher in _watchers.Values)
{
watcher.EnableRaisingEvents = false;
watcher.Dispose();
}
_watchers.Clear();
_reloadCallbacks.Clear();
}
}
}