using System.Text.Json; using Microsoft.Extensions.Options; namespace MeetingAssistant.Recording; public sealed class FileOfflineTranscriptionBacklog : IOfflineTranscriptionBacklog { private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web) { WriteIndented = true }; private readonly MeetingAssistantOptions options; private readonly ILogger logger; public FileOfflineTranscriptionBacklog( IOptions options, ILogger logger) { this.options = options.Value; this.logger = logger; } public async Task EnqueueAsync(OfflineTranscriptionBacklogItem item, CancellationToken cancellationToken) { var folder = GetBacklogFolder(options); Directory.CreateDirectory(folder); var path = GetItemPath(folder, item.Id); await File.WriteAllTextAsync(path, JsonSerializer.Serialize(item, JsonOptions), cancellationToken); logger.LogInformation( "Queued offline transcription backlog item {BacklogItemId} for recording {RecordingPath}", item.Id, item.AudioPath); } public async Task> ListAsync(CancellationToken cancellationToken) { var folder = GetBacklogFolder(options); if (!Directory.Exists(folder)) { return []; } var items = new List(); foreach (var path in Directory.EnumerateFiles(folder, "*.json", SearchOption.TopDirectoryOnly)) { cancellationToken.ThrowIfCancellationRequested(); try { var content = await File.ReadAllTextAsync(path, cancellationToken); if (JsonSerializer.Deserialize(content, JsonOptions) is { } item) { items.Add(item); } } catch (JsonException exception) { logger.LogWarning(exception, "Could not read offline transcription backlog item {BacklogItemPath}", path); } catch (IOException exception) { logger.LogWarning(exception, "Could not read offline transcription backlog item {BacklogItemPath}", path); } } return items; } public Task CompleteAsync(OfflineTranscriptionBacklogItem item, CancellationToken cancellationToken) { var folder = GetBacklogFolder(options); var path = GetItemPath(folder, item.Id); if (!File.Exists(path)) { return Task.CompletedTask; } File.Delete(path); if (File.Exists(item.AudioPath)) { DeleteCompletedAudio(item.AudioPath); } return Task.CompletedTask; } private static string GetBacklogFolder(MeetingAssistantOptions options) { return Path.Combine(VaultPath.Resolve(options.Recording.TemporaryRecordingsFolder), "offline-transcription-backlog"); } private static string GetItemPath(string folder, string id) { return Path.Combine(folder, $"{id}.json"); } private void DeleteCompletedAudio(string audioPath) { try { File.Delete(audioPath); } catch (FileNotFoundException) { } catch (DirectoryNotFoundException) { } catch (IOException exception) { logger.LogWarning( exception, "Could not delete completed offline transcription recording file {RecordingPath}", audioPath); } catch (UnauthorizedAccessException exception) { logger.LogWarning( exception, "Could not delete completed offline transcription recording file {RecordingPath}", audioPath); } } }