using DeepDrftManager.Services; using DeepDrftModels.DTOs; using Microsoft.AspNetCore.Components; using MudBlazor; namespace DeepDrftManager.Components.Pages.Tracks; public partial class TrackPreProcessing : ComponentBase { private List _rows = new(); private readonly HashSet _generating = new(); private bool _loading = true; private bool _bulkRunning; private int _bulkTotal; private int _bulkDone; private int _missingCount => _rows.Count(r => !r.HasProfile); [Inject] private ICmsTrackService CmsTrackService { get; set; } = default!; [Inject] private ISnackbar Snackbar { get; set; } = default!; [Inject] private ILogger Logger { get; set; } = default!; protected override async Task OnInitializedAsync() { await LoadStatus(); } private async Task LoadStatus() { _loading = true; var result = await CmsTrackService.GetWaveformStatusAsync(); _loading = false; if (!result.Success || result.Value is null) { var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error"; Snackbar.Add($"Failed to load waveform status: {error}", Severity.Error); _rows = new List(); return; } _rows = result.Value.OrderBy(r => r.HasProfile).ThenBy(r => r.TrackName).ToList(); } private bool IsGenerating(string entryKey) => _generating.Contains(entryKey); private async Task GenerateOne(WaveformStatusDto row) { if (!await GenerateForRow(row)) { return; } Snackbar.Add($"Generated profile for '{row.TrackName}'.", Severity.Success); } private async Task GenerateAllMissing() { var missing = _rows.Where(r => !r.HasProfile).ToList(); if (missing.Count == 0) { return; } _bulkRunning = true; _bulkTotal = missing.Count; _bulkDone = 0; var failures = 0; // Sequential by design: one request at a time so a large backfill does not flood the API // with concurrent WAV decodes. foreach (var row in missing) { if (!await GenerateForRow(row)) { failures++; } _bulkDone++; StateHasChanged(); } _bulkRunning = false; var succeeded = missing.Count - failures; if (failures == 0) { Snackbar.Add($"Generated {succeeded} profile(s).", Severity.Success); } else { Snackbar.Add($"Generated {succeeded} profile(s); {failures} failed.", Severity.Warning); } } /// /// Runs generation for a single row, flipping its status on success. Returns false on failure /// (a snackbar is raised here for the per-row path; the bulk path aggregates a summary). Marks /// the row busy for the duration so its button shows a spinner and stays disabled. /// private async Task GenerateForRow(WaveformStatusDto row) { _generating.Add(row.EntryKey); StateHasChanged(); try { var result = await CmsTrackService.GenerateWaveformProfileAsync(row.EntryKey); if (result.Success) { row.HasProfile = true; return true; } var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error"; if (!_bulkRunning) { Snackbar.Add($"Generate failed for '{row.TrackName}': {error}", Severity.Error); } return false; } catch (Exception ex) { Logger.LogError(ex, "Waveform generation failed for {EntryKey}", row.EntryKey); if (!_bulkRunning) { Snackbar.Add($"Generate failed for '{row.TrackName}' — please try again.", Severity.Error); } return false; } finally { _generating.Remove(row.EntryKey); StateHasChanged(); } } }