Merge cms-track-replace-gating into dev
Replace track audio in CMS edit form + gate last-track delete.
This commit is contained in:
@@ -166,6 +166,55 @@ public class UnifiedTrackService
|
||||
return saveResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace an existing track's audio in place: look up the SQL row, swap only the vault bytes
|
||||
/// keyed by its EntryKey, then regenerate both waveform datums from the new audio. Track id,
|
||||
/// EntryKey, release membership, track number, and all metadata are preserved — nothing in SQL
|
||||
/// is written. The waveform regen is best-effort (a missing datum renders as a flat seekbar /
|
||||
/// blank visualizer downstream), so a datum failure is logged and swallowed rather than failing
|
||||
/// the replace. No release-cardinality cascade applies: the track count is unchanged, so the
|
||||
/// single-track-Mix case stays intact.
|
||||
/// </summary>
|
||||
public async Task<Result> ReplaceAudioAsync(long trackId, string tempFilePath, CancellationToken ct)
|
||||
{
|
||||
var lookup = await _sqlTrackService.GetById(trackId);
|
||||
if (!lookup.Success)
|
||||
{
|
||||
var error = lookup.Messages.FirstOrDefault()?.Message ?? "unknown error";
|
||||
_logger.LogError("ReplaceAudioAsync: GetById failed for track {TrackId}: {Error}", trackId, error);
|
||||
return Result.CreateFailResult("Failed to load track.");
|
||||
}
|
||||
|
||||
if (lookup.Value is null)
|
||||
{
|
||||
return Result.CreateFailResult(TrackNotFoundMessage);
|
||||
}
|
||||
|
||||
var entryKey = lookup.Value.EntryKey;
|
||||
|
||||
var newAudio = await _contentTrackContentService.ReplaceTrackAudioAsync(entryKey, tempFilePath);
|
||||
if (newAudio is null)
|
||||
{
|
||||
_logger.LogWarning("ReplaceAudioAsync: content swap returned null for track {TrackId} ({EntryKey})", trackId, entryKey);
|
||||
return Result.CreateFailResult("Failed to process and store the replacement audio.");
|
||||
}
|
||||
|
||||
// The old waveform no longer matches the new bytes. Regenerate both datums in place; keyed
|
||||
// by the same EntryKey, the re-run overwrites the stale data (proven re-runnable). The
|
||||
// freshly stored buffer is the authoritative source — no re-read of the vault needed.
|
||||
try
|
||||
{
|
||||
await _waveformProfileService.ComputeAndStoreAsync(newAudio.Buffer, entryKey);
|
||||
await _waveformProfileService.ComputeAndStoreHighResAsync(newAudio.Buffer, entryKey, newAudio.Duration);
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
_logger.LogError(ex, "ReplaceAudioAsync: waveform regen failed for {EntryKey}; replace unaffected.", entryKey);
|
||||
}
|
||||
|
||||
return Result.CreatePassResult();
|
||||
}
|
||||
|
||||
// Compute and store both waveform datums for a freshly uploaded track: the fixed 512-bucket profile
|
||||
// the player-bar seeker consumes, and the duration-derived high-res datum the lava visualizer
|
||||
// consumes (phase-12 §5 — every track now carries one, computed at upload). Both source the same
|
||||
|
||||
Reference in New Issue
Block a user