Fix (recoverability bug): - TransferFilesToBucketActivity returned Completed() with no log, so its compensation could never delete the transferred files (orphaned objects on fault). IStorageService.TransferFilesAsync now returns the real uploaded keys; the activity logs them so Compensate can remove them. Also drops the Path.Combine key recomputation (produced backslash keys on Windows). Consistency / DRY: - Normalize all courier activities to internal + folder-based namespaces (several were public and in the flat .Activities namespace) - Extract the duplicated exponential retry into UseStandardRetry - Unify endpoint-name logic into a single EndpointHelper (remove the parallel MassTransitEndpointNames helper) Build: 0 warnings, 0 errors.
90 lines
3.8 KiB
C#
90 lines
3.8 KiB
C#
using MassTransit;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging;
|
|
using Musify.Application.Contracts;
|
|
using Musify.Domain.Entities;
|
|
using Musify.Domain.ValueObjects;
|
|
using Musify.Infrastructure.MassTransit.Arguments;
|
|
using Musify.Infrastructure.MassTransit.Logs;
|
|
|
|
namespace Musify.Infrastructure.MassTransit.Activities.Audio
|
|
{
|
|
internal class TranscodeAudioActivity(
|
|
IDatabase database,
|
|
IAudioTranscoderService audioTranscoder,
|
|
ILogger<TranscodeAudioActivity> logger)
|
|
: IActivity<TranscodeAudioArguments, TranscodeAudioLog>
|
|
{
|
|
public const string ExecuteEndpointName = "transcode-audio";
|
|
|
|
public async Task<ExecutionResult> Execute(ExecuteContext<TranscodeAudioArguments> executeContext)
|
|
{
|
|
var sourceFilePath = executeContext.GetVariable<string>(executeContext.Arguments.SourceFilePathVariable);
|
|
ArgumentNullException.ThrowIfNull(sourceFilePath, nameof(sourceFilePath));
|
|
var workingDirectory = executeContext.GetVariable<string>(executeContext.Arguments.WorkingDirectoryVariable);
|
|
ArgumentNullException.ThrowIfNull(workingDirectory, nameof(workingDirectory));
|
|
|
|
Track track;
|
|
try
|
|
{
|
|
var getTrack = await database.Tracks.SingleOrDefaultAsync(t => t.Id == executeContext.Arguments.TrackId, executeContext.CancellationToken);
|
|
track = getTrack ?? throw new InvalidOperationException($"Track with Id {executeContext.Arguments.TrackId} not found");
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
logger.LogError(exception, "Failed to retrieve track with Id {TrackId} from database", executeContext.Arguments.TrackId);
|
|
throw;
|
|
}
|
|
|
|
try
|
|
{
|
|
await using (var fileStream = File.OpenRead(sourceFilePath))
|
|
{
|
|
await audioTranscoder.TranscodeToAudioFileAsync(
|
|
fileStream,
|
|
workingDirectory,
|
|
executeContext.CancellationToken);
|
|
}
|
|
|
|
File.Delete(sourceFilePath);
|
|
return executeContext.Completed();
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
logger.LogError(exception, "Failed to transcode {SourceFilePath}", sourceFilePath);
|
|
|
|
try
|
|
{
|
|
track.AudioTranscodeProcessingStatus = ProcessingStatus.Failed;
|
|
await database.SaveChangesAsync(executeContext.CancellationToken);
|
|
}
|
|
catch (Exception dbException)
|
|
{
|
|
logger.LogError(dbException, "Failed to update ProcessingStatus to Failed for track {TrackId}",
|
|
executeContext.Arguments.TrackId);
|
|
}
|
|
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public Task<CompensationResult> Compensate(CompensateContext<TranscodeAudioLog> compensateContext)
|
|
{
|
|
try
|
|
{
|
|
if (Directory.Exists(compensateContext.Log.WorkingDirectory))
|
|
Directory.Delete(compensateContext.Log.WorkingDirectory, recursive: true);
|
|
else
|
|
logger.LogWarning("Working directory {WorkingDirectory} not found during compensation",
|
|
compensateContext.Log.WorkingDirectory);
|
|
return Task.FromResult(compensateContext.Compensated());
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
logger.LogError(exception, "Failed to compensate transcoded audio directory {WorkingDirectory}", compensateContext.Log.WorkingDirectory);
|
|
return Task.FromResult(compensateContext.Failed(exception));
|
|
}
|
|
}
|
|
}
|
|
}
|