Files
meeting-assistant/MeetingAssistant/Recording/TemporaryRecordedAudioStore.cs
T
2026-05-20 02:06:16 +02:00

142 lines
4.3 KiB
C#

using Microsoft.Extensions.Options;
using NAudio.Wave;
namespace MeetingAssistant.Recording;
public sealed class TemporaryRecordedAudioStore : IRecordedAudioStore
{
private readonly MeetingAssistantOptions options;
private readonly ILogger<TemporaryRecordedAudioStore> logger;
public TemporaryRecordedAudioStore(
IOptions<MeetingAssistantOptions> options,
ILogger<TemporaryRecordedAudioStore> logger)
{
this.options = options.Value;
this.logger = logger;
}
public Task<IRecordedAudioSink> CreateSessionAsync(CancellationToken cancellationToken)
{
var folder = GetTemporaryRecordingsFolder();
Directory.CreateDirectory(folder);
var path = Path.Combine(folder, $"{DateTimeOffset.Now:yyyyMMdd-HHmmss}-{Guid.NewGuid():N}-recording.wav");
var format = new WaveFormat(options.Recording.SampleRate, 16, options.Recording.Channels);
logger.LogInformation("Created temporary meeting recording file {RecordingPath}", path);
return Task.FromResult<IRecordedAudioSink>(new TemporaryRecordedAudioSink(path, format, logger));
}
public Task DeleteStaleRecordingsAsync(CancellationToken cancellationToken)
{
var folder = GetTemporaryRecordingsFolder();
if (!Directory.Exists(folder))
{
return Task.CompletedTask;
}
foreach (var path in Directory.EnumerateFiles(folder, "*.wav", SearchOption.TopDirectoryOnly))
{
cancellationToken.ThrowIfCancellationRequested();
DeleteFile(path);
}
return Task.CompletedTask;
}
private string GetTemporaryRecordingsFolder()
{
return VaultPath.Resolve(options.Recording.TemporaryRecordingsFolder);
}
private void DeleteFile(string path)
{
try
{
File.Delete(path);
logger.LogInformation("Deleted temporary meeting recording file {RecordingPath}", path);
}
catch (FileNotFoundException)
{
}
catch (DirectoryNotFoundException)
{
}
catch (IOException exception)
{
logger.LogWarning(exception, "Could not delete temporary meeting recording file {RecordingPath}", path);
}
catch (UnauthorizedAccessException exception)
{
logger.LogWarning(exception, "Could not delete temporary meeting recording file {RecordingPath}", path);
}
}
private sealed class TemporaryRecordedAudioSink : IRecordedAudioSink
{
private readonly WaveFileWriter writer;
private readonly ILogger logger;
private bool completed;
private bool disposed;
public TemporaryRecordedAudioSink(string audioPath, WaveFormat format, ILogger logger)
{
AudioPath = audioPath;
this.logger = logger;
writer = new WaveFileWriter(audioPath, format);
}
public string AudioPath { get; }
public Task AppendAsync(AudioChunk chunk, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
writer.Write(chunk.Pcm, 0, chunk.Pcm.Length);
return Task.CompletedTask;
}
public Task CompleteAsync(CancellationToken cancellationToken)
{
if (completed)
{
return Task.CompletedTask;
}
cancellationToken.ThrowIfCancellationRequested();
writer.Flush();
completed = true;
return Task.CompletedTask;
}
public ValueTask DisposeAsync()
{
if (disposed)
{
return ValueTask.CompletedTask;
}
writer.Dispose();
disposed = true;
return ValueTask.CompletedTask;
}
public async Task DeleteAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
await DisposeAsync();
try
{
File.Delete(AudioPath);
logger.LogInformation("Deleted temporary meeting recording file {RecordingPath}", AudioPath);
}
catch (FileNotFoundException)
{
}
catch (DirectoryNotFoundException)
{
}
}
}
}