using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using SQLVision.Core.Enums; using SQLVision.Core.Interfaces; using SQLVision.Core.Models; using System.Collections.Concurrent; namespace SQLVision.Services.Services; public class ScriptManager : IScriptManager, IDisposable { private readonly ISqlScriptParser _parser; private readonly ILogger _logger; private readonly FileSystemWatcher _watcher; private readonly ConcurrentDictionary _scripts; private readonly string _scriptsDirectory; public event EventHandler? ScriptChanged; public event EventHandler? ScriptsReloaded; public ScriptManager(ISqlScriptParser parser, IConfiguration configuration, ILogger logger) { _parser = parser; _logger = logger; _scripts = new ConcurrentDictionary(); _scriptsDirectory = configuration["Scripts:Directory"] ?? "Scripts"; _watcher = new FileSystemWatcher { Path = _scriptsDirectory, Filter = "*.sql", NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName, EnableRaisingEvents = false }; _watcher.Changed += OnScriptChanged; _watcher.Created += OnScriptCreated; _watcher.Deleted += OnScriptDeleted; _watcher.Renamed += OnScriptRenamed; } public async Task> LoadScriptsAsync(string? directory = null) { var targetDirectory = directory ?? _scriptsDirectory; if (!Directory.Exists(targetDirectory)) { Directory.CreateDirectory(targetDirectory); _logger.LogInformation("Created scripts directory: {Directory}", targetDirectory); return Enumerable.Empty(); } var sqlFiles = Directory.GetFiles(targetDirectory, "*.sql", SearchOption.AllDirectories); var tasks = sqlFiles.Select(LoadScriptAsync); var results = await Task.WhenAll(tasks); _scripts.Clear(); foreach (var script in results.Where(s => s != null)) { _scripts[script!.FullPath] = script; } StartWatching(); ScriptsReloaded?.Invoke(this, new ScriptsReloadedEventArgs(results.Where(s => s != null).ToList()!)); _logger.LogInformation("Loaded {Count} scripts from {Directory}", _scripts.Count, targetDirectory); return _scripts.Values; } private async Task LoadScriptAsync(string filePath) { try { return await _parser.ParseAsync(filePath); } catch (Exception ex) { _logger.LogError(ex, "Error loading script: {FilePath}", filePath); return null; } } public async Task ReloadScriptAsync(string filePath) { try { var script = await LoadScriptAsync(filePath); if (script != null) { _scripts[filePath] = script; ScriptChanged?.Invoke(this, new ScriptChangedEventArgs(filePath, ScriptChangeType.Updated, script)); } return script!; } catch (Exception ex) { _logger.LogError(ex, "Error reloading script: {FilePath}", filePath); throw; } } private void OnScriptChanged(object sender, FileSystemEventArgs e) { // Задержка для избежания многократных вызовов Task.Delay(300).ContinueWith(async _ => { try { var script = await ReloadScriptAsync(e.FullPath); if (script != null) { _logger.LogInformation("Script changed: {FileName}", e.Name); } } catch { /* Игнорируем ошибки */ } }); } private void OnScriptCreated(object sender, FileSystemEventArgs e) { Task.Delay(300).ContinueWith(async _ => { try { var script = await LoadScriptAsync(e.FullPath); if (script != null) { _scripts[e.FullPath] = script; ScriptChanged?.Invoke(this, new ScriptChangedEventArgs(e.FullPath, ScriptChangeType.Created, script)); _logger.LogInformation("Script created: {FileName}", e.Name); } } catch { /* Игнорируем */ } }); } private void OnScriptDeleted(object sender, FileSystemEventArgs e) { if (_scripts.TryRemove(e.FullPath, out var script)) { ScriptChanged?.Invoke(this, new ScriptChangedEventArgs(e.FullPath, ScriptChangeType.Deleted, script)); _logger.LogInformation("Script deleted: {FileName}", e.Name); } } private void OnScriptRenamed(object sender, RenamedEventArgs e) { Task.Delay(300).ContinueWith(async _ => { try { // Удаляем старый файл _scripts.TryRemove(e.OldFullPath, out var s); // Загружаем новый var script = await LoadScriptAsync(e.FullPath); if (script != null) { _scripts[e.FullPath] = script; ScriptChanged?.Invoke(this, new ScriptChangedEventArgs(e.FullPath, ScriptChangeType.Renamed, script)); _logger.LogInformation("Script renamed: {OldName} -> {NewName}", Path.GetFileName(e.OldFullPath), e.Name); } } catch { /* Игнорируем */ } }); } private void StartWatching() { if (!_watcher.EnableRaisingEvents) { _watcher.EnableRaisingEvents = true; _logger.LogDebug("Started watching directory: {Directory}", _scriptsDirectory); } } public void WatchDirectory(string directory, Action onScriptChanged) { if (_watcher.EnableRaisingEvents) { _watcher.EnableRaisingEvents = false; } _watcher.Path = directory; _watcher.EnableRaisingEvents = true; ScriptChanged += (sender, e) => onScriptChanged?.Invoke(e.FilePath); } public void Dispose() { _watcher?.Dispose(); } }