diff --git a/DeepDrftContent.Services/DeepDrftContent.Services.csproj b/DeepDrftContent.Services/DeepDrftContent.Services.csproj index e132d96..30e03d1 100644 --- a/DeepDrftContent.Services/DeepDrftContent.Services.csproj +++ b/DeepDrftContent.Services/DeepDrftContent.Services.csproj @@ -10,4 +10,8 @@ + + + + diff --git a/DeepDrftContent.Services/FileDatabase/Models/MediaFactories.cs b/DeepDrftContent.Services/FileDatabase/Models/MediaFactories.cs index e277f79..80be94a 100644 --- a/DeepDrftContent.Services/FileDatabase/Models/MediaFactories.cs +++ b/DeepDrftContent.Services/FileDatabase/Models/MediaFactories.cs @@ -3,12 +3,20 @@ using DeepDrftContent.Services.FileDatabase.Services; namespace DeepDrftContent.Services.FileDatabase.Models; +/// +/// Shared media type registry instance — one allocation for all factory classes in this file. +/// +file static class SharedMediaTypeRegistry +{ + internal static readonly IMediaTypeRegistry Instance = new SimpleMediaTypeRegistry(); +} + /// /// Type mappings for media vault types - simple dictionary-based approach /// public static class MediaVaultTypeMap { - private static readonly IMediaTypeRegistry _registry = new SimpleMediaTypeRegistry(); + private static readonly IMediaTypeRegistry _registry = SharedMediaTypeRegistry.Instance; public static Type GetBinaryType(MediaVaultType vaultType) => _registry.GetBinaryType(vaultType); @@ -55,7 +63,7 @@ public static class MetaDataFactory return new AudioMetaData(entryKey, extension, duration, bitrate); } - private static readonly IMediaTypeRegistry _metaDataRegistry = new SimpleMediaTypeRegistry(); + private static readonly IMediaTypeRegistry _metaDataRegistry = SharedMediaTypeRegistry.Instance; public static MetaData CreateFromMedia(MediaVaultType type, string entryKey, string extension, object media) { @@ -75,7 +83,7 @@ public static class MetaDataFactory /// public static class MediaParamsFactory { - private static readonly IMediaTypeRegistry _registry = new SimpleMediaTypeRegistry(); + private static readonly IMediaTypeRegistry _registry = SharedMediaTypeRegistry.Instance; public static object Create(MediaVaultType type, FileBinary fileBinary, MetaData metaData) { @@ -94,7 +102,7 @@ public static class MediaParamsFactory /// public static class FileBinaryFactory { - private static readonly IMediaTypeRegistry _registry = new SimpleMediaTypeRegistry(); + private static readonly IMediaTypeRegistry _registry = SharedMediaTypeRegistry.Instance; public static object Create(MediaVaultType vaultType, object parameters) { @@ -124,7 +132,7 @@ public static class FileBinaryFactory /// public static class FileBinaryDtoFactory { - private static readonly IMediaTypeRegistry _registry = new SimpleMediaTypeRegistry(); + private static readonly IMediaTypeRegistry _registry = SharedMediaTypeRegistry.Instance; public static object From(MediaVaultType type, object mediaBinary) { diff --git a/DeepDrftContent.Services/FileDatabase/Models/MediaModels.cs b/DeepDrftContent.Services/FileDatabase/Models/MediaModels.cs index 6a096b5..862fac9 100644 --- a/DeepDrftContent.Services/FileDatabase/Models/MediaModels.cs +++ b/DeepDrftContent.Services/FileDatabase/Models/MediaModels.cs @@ -68,7 +68,7 @@ public class MediaBinary : FileBinary return new MediaBinary(new MediaBinaryParams(buffer, dto.Size, extension)); } - private static string GetExtensionType(string mime) + protected static string GetExtensionType(string mime) { return MimeTypeExtensions.GetExtension(mime); } @@ -116,11 +116,6 @@ public class ImageBinary : MediaBinary var extension = GetExtensionType(dto.Mime); return new ImageBinary(new ImageBinaryParams(buffer, dto.Size, extension, dto.AspectRatio)); } - - private static string GetExtensionType(string mime) - { - return MimeTypeExtensions.GetExtension(mime); - } } /// @@ -171,11 +166,6 @@ public class AudioBinary : MediaBinary var extension = GetExtensionType(dto.Mime); return new AudioBinary(new AudioBinaryParams(buffer, dto.Size, extension, dto.Duration, dto.Bitrate)); } - - private static string GetExtensionType(string mime) - { - return MimeTypeExtensions.GetExtension(mime); - } } /// diff --git a/DeepDrftContent.Services/FileDatabase/Services/FileDatabase.cs b/DeepDrftContent.Services/FileDatabase/Services/FileDatabase.cs index 975d463..87dd442 100644 --- a/DeepDrftContent.Services/FileDatabase/Services/FileDatabase.cs +++ b/DeepDrftContent.Services/FileDatabase/Services/FileDatabase.cs @@ -11,6 +11,7 @@ public class FileDatabase : DirectoryIndexDirectory, IDisposable { private readonly StructuralMap _vaults; private readonly IndexWatcher _indexWatcher; + private readonly IndexFactoryService _indexFactory; private bool _disposed; /// @@ -23,7 +24,7 @@ public class FileDatabase : DirectoryIndexDirectory, IDisposable if (rootIndex != null) { - var db = new FileDatabase(rootPath, rootIndex); + var db = new FileDatabase(rootPath, rootIndex, factoryService); await db.InitVaultsAsync(); return db; } @@ -31,10 +32,11 @@ public class FileDatabase : DirectoryIndexDirectory, IDisposable return null; } - private FileDatabase(string rootPath, IDirectoryIndex index) : base(rootPath, index) + private FileDatabase(string rootPath, IDirectoryIndex index, IndexFactoryService indexFactory) : base(rootPath, index) { _vaults = new StructuralMap(); _indexWatcher = new IndexWatcher(); + _indexFactory = indexFactory; } /// @@ -58,7 +60,7 @@ public class FileDatabase : DirectoryIndexDirectory, IDisposable private async Task InitVaultAsync(string vaultId, MediaVaultType vaultType) { var path = Path.Combine(RootPath, vaultId); - var directoryVault = await MediaVaultFactory.From(path, vaultType); + var directoryVault = await MediaVaultFactory.From(path, vaultType, _indexFactory); if (directoryVault != null) { @@ -80,9 +82,8 @@ public class FileDatabase : DirectoryIndexDirectory, IDisposable { try { - var factoryService = new IndexFactoryService(); var vaultPath = Path.Combine(RootPath, vaultId); - var index = await factoryService.LoadIndexAsync(IndexType.Vault, vaultPath); + var index = await _indexFactory.LoadIndexAsync(IndexType.Vault, vaultPath); if (index is VaultIndex vaultIndex) { @@ -115,25 +116,18 @@ public class FileDatabase : DirectoryIndexDirectory, IDisposable } /// - /// Creates a new vault + /// Creates a new vault. Propagates exceptions to the caller — vault creation failure is not + /// silently swallowable because a partially-created vault would leave the index inconsistent. /// public async Task CreateVaultAsync(string vaultId, MediaVaultType vaultType) { - try - { - var path = Path.Combine(RootPath, vaultId); - var directoryVault = await MediaVaultFactory.From(path, vaultType); + var path = Path.Combine(RootPath, vaultId); + var directoryVault = await MediaVaultFactory.From(path, vaultType, _indexFactory); - if (directoryVault != null) - { - _vaults.Set(vaultId, directoryVault); - // Now using string-based index - await AddToIndexAsync(vaultId); - } - } - catch + if (directoryVault != null) { - throw; + _vaults.Set(vaultId, directoryVault); + await AddToIndexAsync(vaultId); } } diff --git a/DeepDrftContent.Services/FileDatabase/Services/IndexSystem.cs b/DeepDrftContent.Services/FileDatabase/Services/IndexSystem.cs index 5802b8f..7f79375 100644 --- a/DeepDrftContent.Services/FileDatabase/Services/IndexSystem.cs +++ b/DeepDrftContent.Services/FileDatabase/Services/IndexSystem.cs @@ -1,6 +1,7 @@ using DeepDrftContent.Services.FileDatabase.Abstractions; using DeepDrftContent.Services.FileDatabase.Models; using DeepDrftContent.Services.FileDatabase.Utils; +using Microsoft.Extensions.Logging; namespace DeepDrftContent.Services.FileDatabase.Services; @@ -96,12 +97,15 @@ public class VaultIndexDirectory : IndexDirectory { private IVaultIndex _vaultIndex; private readonly SemaphoreSlim _indexLock = new(1, 1); - private readonly IndexFactoryService _factoryService = new(); + private readonly IndexFactoryService _factoryService; + private readonly ILogger? _logger; - public VaultIndexDirectory(string rootPath, IVaultIndex index, IIndexDataFactory? indexDataFactory = null) - : base(rootPath, IndexType.Vault, index, indexDataFactory) + public VaultIndexDirectory(string rootPath, IVaultIndex index, IIndexDataFactory? indexDataFactory = null, ILogger? logger = null, IndexFactoryService? factoryService = null) + : base(rootPath, IndexType.Vault, index, indexDataFactory ?? factoryService) { _vaultIndex = index; + _logger = logger; + _factoryService = factoryService ?? new IndexFactoryService(); } protected async Task AddToIndexAsync(string entryId, MetaData metaData) @@ -131,12 +135,18 @@ public class VaultIndexDirectory : IndexDirectory { _vaultIndex = vaultIndex; Index = vaultIndex; - Console.WriteLine($"VaultIndexDirectory: Reloaded index for {RootPath}, {vaultIndex.GetEntriesSize()} entries"); + if (_logger != null) + _logger.LogDebug("VaultIndexDirectory: Reloaded index for {RootPath}, {EntryCount} entries", RootPath, vaultIndex.GetEntriesSize()); + else + Console.WriteLine($"VaultIndexDirectory: Reloaded index for {RootPath}, {vaultIndex.GetEntriesSize()} entries"); } } catch (Exception ex) { - Console.WriteLine($"VaultIndexDirectory: Failed to reload index for {RootPath}: {ex.Message}"); + if (_logger != null) + _logger.LogWarning(ex, "VaultIndexDirectory: Failed to reload index for {RootPath}", RootPath); + else + Console.WriteLine($"VaultIndexDirectory: Failed to reload index for {RootPath}: {ex.Message}"); } finally { diff --git a/DeepDrftContent.Services/FileDatabase/Services/IndexWatcher.cs b/DeepDrftContent.Services/FileDatabase/Services/IndexWatcher.cs index 9daad5b..2c6e3ec 100644 --- a/DeepDrftContent.Services/FileDatabase/Services/IndexWatcher.cs +++ b/DeepDrftContent.Services/FileDatabase/Services/IndexWatcher.cs @@ -1,4 +1,5 @@ using DeepDrftContent.Services.FileDatabase.Models; +using Microsoft.Extensions.Logging; namespace DeepDrftContent.Services.FileDatabase.Services; @@ -11,8 +12,14 @@ public class IndexWatcher : IDisposable private readonly Dictionary _watchers = new(); private readonly Dictionary _reloadCallbacks = new(); private readonly object _lock = new(); + private readonly ILogger? _logger; private bool _disposed; + public IndexWatcher(ILogger? logger = null) + { + _logger = logger; + } + /// /// Registers an index file to be watched for changes. /// @@ -46,11 +53,17 @@ public class IndexWatcher : IDisposable _watchers[indexPath] = watcher; _reloadCallbacks[indexPath] = onChanged; - Console.WriteLine($"IndexWatcher: Watching {indexPath}/index"); + if (_logger != null) + _logger.LogDebug("IndexWatcher: Watching {IndexPath}/index", indexPath); + else + Console.WriteLine($"IndexWatcher: Watching {indexPath}/index"); } catch (Exception ex) { - Console.WriteLine($"IndexWatcher: Failed to watch {indexPath}: {ex.Message}"); + if (_logger != null) + _logger.LogWarning(ex, "IndexWatcher: Failed to watch {IndexPath}", indexPath); + else + Console.WriteLine($"IndexWatcher: Failed to watch {indexPath}: {ex.Message}"); } } } @@ -83,7 +96,10 @@ public class IndexWatcher : IDisposable { if (_reloadCallbacks.TryGetValue(indexPath, out var callback)) { - Console.WriteLine($"IndexWatcher: Index changed at {indexPath}, triggering reload"); + if (_logger != null) + _logger.LogDebug("IndexWatcher: Index changed at {IndexPath}, triggering reload", indexPath); + else + Console.WriteLine($"IndexWatcher: Index changed at {indexPath}, triggering reload"); // Invoke callback on a background thread to avoid blocking the watcher Task.Run(() => @@ -94,7 +110,10 @@ public class IndexWatcher : IDisposable } catch (Exception ex) { - Console.WriteLine($"IndexWatcher: Reload callback failed: {ex.Message}"); + if (_logger != null) + _logger.LogWarning(ex, "IndexWatcher: Reload callback failed for {IndexPath}", indexPath); + else + Console.WriteLine($"IndexWatcher: Reload callback failed: {ex.Message}"); } }); } diff --git a/DeepDrftContent.Services/FileDatabase/Services/MediaVault.cs b/DeepDrftContent.Services/FileDatabase/Services/MediaVault.cs index 525d5a5..a10f29a 100644 --- a/DeepDrftContent.Services/FileDatabase/Services/MediaVault.cs +++ b/DeepDrftContent.Services/FileDatabase/Services/MediaVault.cs @@ -9,7 +9,8 @@ namespace DeepDrftContent.Services.FileDatabase.Services; /// public abstract class MediaVault : VaultIndexDirectory { - protected MediaVault(string rootPath, VaultIndex index) : base(rootPath, index) { } + protected MediaVault(string rootPath, VaultIndex index, IndexFactoryService? factoryService = null) + : base(rootPath, index, factoryService: factoryService) { } /// /// Generates a media key from an entry key by sanitizing special characters @@ -105,19 +106,20 @@ public abstract class MediaVault : VaultIndexDirectory /// public class ImageVault : MediaVault { - private ImageVault(string rootPath, VaultIndex index) : base(rootPath, index) { } + private ImageVault(string rootPath, VaultIndex index, IndexFactoryService? factoryService = null) + : base(rootPath, index, factoryService) { } /// /// Factory method to create an ImageVault instance /// - public static async Task FromAsync(string rootPath) + public static async Task FromAsync(string rootPath, IndexFactoryService? factoryService = null) { - var factoryService = new IndexFactoryService(); - var index = await factoryService.LoadOrCreateVaultIndexAsync(rootPath, MediaVaultType.Image); + var factory = factoryService ?? new IndexFactoryService(); + var index = await factory.LoadOrCreateVaultIndexAsync(rootPath, MediaVaultType.Image); if (index != null) { - return new ImageVault(rootPath, (VaultIndex)index); + return new ImageVault(rootPath, (VaultIndex)index, factory); } return null; @@ -126,16 +128,17 @@ public class ImageVault : MediaVault public class AudioVault : MediaVault { - private AudioVault(string rootPath, VaultIndex index) : base(rootPath, index) { } - - public static async Task FromAsync(string rootPath) + private AudioVault(string rootPath, VaultIndex index, IndexFactoryService? factoryService = null) + : base(rootPath, index, factoryService) { } + + public static async Task FromAsync(string rootPath, IndexFactoryService? factoryService = null) { - var factoryService = new IndexFactoryService(); - var index = await factoryService.LoadOrCreateVaultIndexAsync(rootPath, MediaVaultType.Audio); + var factory = factoryService ?? new IndexFactoryService(); + var index = await factory.LoadOrCreateVaultIndexAsync(rootPath, MediaVaultType.Audio); if (index != null) { - return new AudioVault(rootPath, (VaultIndex)index); + return new AudioVault(rootPath, (VaultIndex)index, factory); } return null; diff --git a/DeepDrftContent.Services/FileDatabase/Services/MediaVaultFactory.cs b/DeepDrftContent.Services/FileDatabase/Services/MediaVaultFactory.cs index 63d53d1..54b0f04 100644 --- a/DeepDrftContent.Services/FileDatabase/Services/MediaVaultFactory.cs +++ b/DeepDrftContent.Services/FileDatabase/Services/MediaVaultFactory.cs @@ -1,5 +1,4 @@ -using DeepDrftContent.Services.FileDatabase.Abstractions; -using DeepDrftContent.Services.FileDatabase.Models; +using DeepDrftContent.Services.FileDatabase.Models; namespace DeepDrftContent.Services.FileDatabase.Services; @@ -8,10 +7,13 @@ namespace DeepDrftContent.Services.FileDatabase.Services; /// public static class MediaVaultFactory { - private static readonly IMediaTypeRegistry _registry = new SimpleMediaTypeRegistry(); - - public static async Task From(string rootPath, MediaVaultType mediaType) + public static async Task From(string rootPath, MediaVaultType mediaType, IndexFactoryService? factoryService = null) { - return await _registry.CreateVaultAsync(mediaType, rootPath); + return mediaType switch + { + MediaVaultType.Image => await ImageVault.FromAsync(rootPath, factoryService), + MediaVaultType.Audio => await AudioVault.FromAsync(rootPath, factoryService), + _ => null + }; } } \ No newline at end of file diff --git a/DeepDrftContent.Services/TrackService.cs b/DeepDrftContent.Services/TrackService.cs index f6d5794..1906031 100644 --- a/DeepDrftContent.Services/TrackService.cs +++ b/DeepDrftContent.Services/TrackService.cs @@ -75,9 +75,10 @@ public class TrackService return trackEntity; } - catch (Exception ex) + catch (Exception ex) when (ex is not OperationCanceledException) { - throw new InvalidOperationException($"Failed to add track: {ex.Message}", ex); + Console.WriteLine($"TrackService.AddTrackFromWavAsync failed: {ex.Message}"); + return null; } } diff --git a/DeepDrftModels/DTOs/TrackDto.cs b/DeepDrftModels/DTOs/TrackDto.cs index 1993481..f63d8ed 100644 --- a/DeepDrftModels/DTOs/TrackDto.cs +++ b/DeepDrftModels/DTOs/TrackDto.cs @@ -4,8 +4,8 @@ public class TrackDto { public long Id { get; set; } public required string EntryKey { get; set; } - public string TrackName { get; set; } - public string Artist { get; set; } + public required string TrackName { get; set; } + public required string Artist { get; set; } public string? Album { get; set; } public string? Genre { get; set; } public DateOnly? ReleaseDate { get; set; } diff --git a/DeepDrftModels/Models/PagedResult.cs b/DeepDrftModels/Models/PagedResult.cs index 39a20ec..46ab081 100644 --- a/DeepDrftModels/Models/PagedResult.cs +++ b/DeepDrftModels/Models/PagedResult.cs @@ -6,7 +6,7 @@ public class PagedResult public int TotalCount { get; set; } public int Page { get; set; } public int PageSize { get; set; } - public int TotalPages => (int)Math.Ceiling((double)TotalCount / PageSize); + public int TotalPages => PageSize > 0 ? (int)Math.Ceiling((double)TotalCount / PageSize) : 0; public bool HasNextPage => Page < TotalPages; public bool HasPreviousPage => Page > 1; @@ -27,7 +27,7 @@ public class PagedResult public PagedResult(IEnumerable items, int totalCount, int page, int pageSize) { - Items = items.ToList() ?? new List(); + Items = items.ToList(); TotalCount = totalCount; Page = page; PageSize = pageSize;