Files
deepdrft/DeepDrftManager/Components/Pages/Tracks/TrackPreProcessing.razor.cs
T
daniel-c-harvey 6e25ad3085 Add CMS waveform pre-processing panel with backfill endpoints
GET api/track/waveform-status and POST api/track/{id}/waveform (ApiKey);
CmsTrackService methods; TrackPreProcessing page with per-row and
sequential bulk generation; nav links from TrackList and Index.
2026-06-05 17:56:25 -04:00

136 lines
4.1 KiB
C#

using DeepDrftManager.Services;
using DeepDrftModels.DTOs;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace DeepDrftManager.Components.Pages.Tracks;
public partial class TrackPreProcessing : ComponentBase
{
private List<WaveformStatusDto> _rows = new();
private readonly HashSet<string> _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<TrackPreProcessing> 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<WaveformStatusDto>();
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);
}
}
/// <summary>
/// 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.
/// </summary>
private async Task<bool> 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();
}
}
}