using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Diagnostics; using OwlCore.Events; using OwlCore.Extensions; using StrixMusic.Cores.Files.Services; using StrixMusic.Sdk.CoreModels; using StrixMusic.Sdk.Extensions; using StrixMusic.Sdk.FileMetadata; using StrixMusic.Sdk.FileMetadata.Models; using StrixMusic.Sdk.MediaPlayback; namespace StrixMusic.Cores.Files.Models { /// /// Wraps around to provide album information extracted from a file to the Strix SDK. /// public sealed class FilesCoreAlbum : ICoreAlbum { private readonly IFileMetadataManager? _fileMetadataManager; private AlbumMetadata _albumMetadata; /// /// Initializes a new instance of the class. /// /// The core that created this object. /// The source album metadata to wrap around. public FilesCoreAlbum(ICore sourceCore, AlbumMetadata albumMetadata) { Guard.IsNotNullOrWhiteSpace(albumMetadata.Id, nameof(albumMetadata.Id)); Id = albumMetadata.Id; _fileMetadataManager = sourceCore.Cast().FileMetadataManager; SourceCore = sourceCore; _albumMetadata = albumMetadata; Guard.IsNotNull(albumMetadata.ArtistIds, nameof(albumMetadata.ArtistIds)); Guard.IsNotNull(albumMetadata.TrackIds, nameof(albumMetadata.TrackIds)); AttachEvents(); } private void AttachEvents() { Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager)); _fileMetadataManager.Albums.MetadataUpdated += Albums_MetadataUpdated; } private void DetachEvents() { Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager)); _fileMetadataManager.Albums.MetadataUpdated -= Albums_MetadataUpdated; } private void Albums_MetadataUpdated(object sender, IEnumerable e) { foreach (var metadata in e) { if (metadata.Id != Id) return; Guard.IsNotNull(metadata.ArtistIds, nameof(metadata.ArtistIds)); Guard.IsNotNull(metadata.ImageIds, nameof(metadata.ImageIds)); Guard.IsNotNull(metadata.TrackIds, nameof(metadata.TrackIds)); Guard.IsNotNull(metadata.Genres, nameof(metadata.Genres)); var previousData = _albumMetadata; _albumMetadata = metadata; Guard.IsNotNull(previousData.TrackIds, nameof(previousData.TrackIds)); Guard.IsNotNull(previousData.ArtistIds, nameof(previousData.ArtistIds)); Guard.IsNotNull(previousData.ImageIds, nameof(previousData.ImageIds)); Guard.IsNotNull(previousData.Genres, nameof(previousData.Genres)); if (metadata.Title != previousData.Title) NameChanged?.Invoke(this, Name); if (metadata.DatePublished != previousData.DatePublished) DatePublishedChanged?.Invoke(this, DatePublished); if (metadata.Description != previousData.Description) DescriptionChanged?.Invoke(this, Description); if (metadata.Duration != previousData.Duration) DurationChanged?.Invoke(this, Duration); if (metadata.Genres.Count != previousData.Genres.Count) GenresCountChanged?.Invoke(this, metadata.Genres.Count); if (metadata.TrackIds.Count != previousData.TrackIds.Count) TracksCountChanged?.Invoke(this, metadata.TrackIds.Count); if (metadata.ArtistIds.Count != previousData.ArtistIds.Count) ArtistItemsCountChanged?.Invoke(this, metadata.ArtistIds.Count); _ = HandleImagesChangedAsync(previousData.ImageIds, metadata.ImageIds); _ = HandleArtistsChangedAsync(previousData.ArtistIds, metadata.ArtistIds); _ = HandleTracksChangedAsync(previousData.TrackIds, metadata.TrackIds); } } private async Task HandleTracksChangedAsync(HashSet oldTrackIds, HashSet newTrackIds) { Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager)); if (oldTrackIds.OrderBy(s => s).SequenceEqual(newTrackIds.OrderBy(s => s))) { // Lists have identical content, so no images have changed. return; } var addedImages = newTrackIds.Except(oldTrackIds); var removedImages = oldTrackIds.Except(newTrackIds); if (oldTrackIds.Count != newTrackIds.Count) { TracksChanged?.Invoke(this, await TransformAsync(addedImages), await TransformAsync(removedImages)); TracksCountChanged?.Invoke(this, newTrackIds.Count); } async Task>> TransformAsync(IEnumerable ids) { var idArray = ids as string[] ?? ids.ToArray(); var collectionChangedItems = new List>(idArray.Length); foreach (var id in idArray) { var track = await _fileMetadataManager.Tracks.GetByIdAsync(id); Guard.IsNotNullOrWhiteSpace(track?.Id, nameof(track.Id)); collectionChangedItems.Add(new CollectionChangedItem(InstanceCache.Tracks.GetOrCreate(track.Id, SourceCore, track), collectionChangedItems.Count)); } return collectionChangedItems; } } private async Task HandleArtistsChangedAsync(HashSet newArtistIds, HashSet oldArtistIds) { Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager)); if (oldArtistIds.OrderBy(s => s).SequenceEqual(newArtistIds.OrderBy(s => s))) { // Lists have identical content, so no images have changed. return; } var addedImages = newArtistIds.Except(oldArtistIds); var removedImages = oldArtistIds.Except(newArtistIds); if (oldArtistIds.Count != newArtistIds.Count) { ArtistItemsChanged?.Invoke(this, await TransformAsync(addedImages), await TransformAsync(removedImages)); ArtistItemsCountChanged?.Invoke(this, newArtistIds.Count); } async Task>> TransformAsync(IEnumerable ids) { var idArray = ids as string[] ?? ids.ToArray(); var collectionChangedItems = new List>(idArray.Length); foreach (var id in idArray) { var artist = await _fileMetadataManager.Artists.GetByIdAsync(id); Guard.IsNotNullOrWhiteSpace(artist?.Id, nameof(artist.Id)); collectionChangedItems.Add(new CollectionChangedItem(InstanceCache.Artists.GetOrCreate(artist.Id, SourceCore, artist), collectionChangedItems.Count)); } return collectionChangedItems; } } private async Task HandleImagesChangedAsync(HashSet oldImageIds, HashSet newImageIds) { Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager)); if (oldImageIds.OrderBy(s => s).SequenceEqual(newImageIds.OrderBy(s => s))) { // Lists have identical content, so no images have changed. return; } var addedImages = newImageIds.Except(oldImageIds); var removedImages = oldImageIds.Except(newImageIds); if (oldImageIds.Count != newImageIds.Count) { ImagesChanged?.Invoke(this, await TransformAsync(addedImages), await TransformAsync(removedImages)); ImagesCountChanged?.Invoke(this, newImageIds.Count); } async Task>> TransformAsync(IEnumerable ids) { var idArray = ids as string[] ?? ids.ToArray(); var collectionChangedItems = new List>(idArray.Length); foreach (var id in idArray) { var image = await _fileMetadataManager.Images.GetByIdAsync(id); Guard.IsNotNullOrWhiteSpace(image?.Id, nameof(image.Id)); collectionChangedItems.Add(new CollectionChangedItem(InstanceCache.Images.GetOrCreate(image.Id, SourceCore, image), collectionChangedItems.Count)); } return collectionChangedItems; } } /// public event EventHandler? PlaybackStateChanged; /// public event EventHandler? NameChanged; /// public event EventHandler? DescriptionChanged; /// public event EventHandler? DatePublishedChanged; /// public event EventHandler? DurationChanged; /// public event EventHandler? LastPlayedChanged; /// public event EventHandler? IsPlayArtistCollectionAsyncAvailableChanged; /// public event EventHandler? IsPauseArtistCollectionAsyncAvailableChanged; /// public event EventHandler? IsPlayTrackCollectionAsyncAvailableChanged; /// public event EventHandler? IsPauseTrackCollectionAsyncAvailableChanged; /// public event EventHandler? IsChangeNameAsyncAvailableChanged; /// public event EventHandler? IsChangeDescriptionAsyncAvailableChanged; /// public event EventHandler? IsChangeDurationAsyncAvailableChanged; /// public event EventHandler? ArtistItemsCountChanged; /// public event EventHandler? TracksCountChanged; /// public event EventHandler? ImagesCountChanged; /// public event EventHandler? UrlsCountChanged; /// public event EventHandler? GenresCountChanged; /// public event CollectionChangedEventHandler? ArtistItemsChanged; /// public event CollectionChangedEventHandler? TracksChanged; /// public event CollectionChangedEventHandler? ImagesChanged; /// public event CollectionChangedEventHandler? UrlsChanged; /// public event CollectionChangedEventHandler? GenresChanged; /// public ICore SourceCore { get; } /// public string Id { get; } /// public string Name => _albumMetadata.Title ?? string.Empty; /// public DateTime? DatePublished => _albumMetadata.DatePublished; /// public string? Description => _albumMetadata.Description; /// public PlaybackState PlaybackState { get; } /// public TimeSpan Duration => _albumMetadata.Duration ?? new TimeSpan(0, 0, 0); /// public DateTime? LastPlayed { get; } /// public DateTime? AddedAt { get; } /// public int TotalImageCount => _albumMetadata.ImageIds?.Count ?? 0; /// public int TotalArtistItemsCount => _albumMetadata.ArtistIds?.Count ?? 0; /// public int TotalTrackCount => _albumMetadata.TrackIds?.Count ?? 0; /// public int TotalGenreCount => _albumMetadata.Genres?.Count ?? 0; /// public int TotalUrlCount => 0; /// public ICorePlayableCollectionGroup? RelatedItems { get; } /// public bool IsPlayTrackCollectionAsyncAvailable => false; /// public bool IsPauseTrackCollectionAsyncAvailable => false; /// public bool IsPlayArtistCollectionAsyncAvailable => false; /// public bool IsPauseArtistCollectionAsyncAvailable => false; /// public bool IsChangeNameAsyncAvailable => false; /// public bool IsChangeDatePublishedAsyncAvailable => false; /// public bool IsChangeDescriptionAsyncAvailable => false; /// public bool IsChangeDurationAsyncAvailable => false; /// public event EventHandler? IsChangeDatePublishedAsyncAvailableChanged; /// public Task IsAddGenreAvailableAsync(int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task IsAddTrackAvailableAsync(int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task IsAddImageAvailableAsync(int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task IsAddUrlAvailableAsync(int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task IsRemoveTrackAvailableAsync(int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task IsRemoveImageAvailableAsync(int index, CancellationToken cancellationToken = default) { return Task.FromResult(false); } /// public Task IsRemoveUrlAvailableAsync(int index, CancellationToken cancellationToken = default) { return Task.FromResult(false); } /// public Task IsRemoveGenreAvailableAsync(int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task IsAddArtistItemAvailableAsync(int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task IsRemoveArtistItemAvailableAsync(int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task ChangeDescriptionAsync(string? description, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task ChangeDurationAsync(TimeSpan duration, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task ChangeDatePublishedAsync(DateTime datePublished, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task ChangeNameAsync(string name, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// A cancellation token that may be used to cancel the ongoing task. /// public Task PauseArtistCollectionAsync(CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// A cancellation token that may be used to cancel the ongoing task. /// public Task PlayArtistCollectionAsync(CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// A cancellation token that may be used to cancel the ongoing task. /// public Task PauseTrackCollectionAsync(CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// A cancellation token that may be used to cancel the ongoing task. /// public Task PlayTrackCollectionAsync(CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task PlayArtistCollectionAsync(ICoreArtistCollectionItem artistItem, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task PlayTrackCollectionAsync(ICoreTrack track, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task AddArtistItemAsync(ICoreArtistCollectionItem artist, int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task AddTrackAsync(ICoreTrack track, int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task AddImageAsync(ICoreImage image, int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task AddUrlAsync(ICoreUrl image, int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task RemoveArtistItemAsync(int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task RemoveTrackAsync(int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task RemoveImageAsync(int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task RemoveUrlAsync(int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task AddGenreAsync(ICoreGenre genre, int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task RemoveGenreAsync(int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public async IAsyncEnumerable GetTracksAsync(int limit, int offset, [EnumeratorCancellation] CancellationToken cancellationToken = default) { Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager)); var tracks = await _fileMetadataManager.Tracks.GetTracksByAlbumId(Id, offset, limit); foreach (var track in tracks) { Guard.IsNotNullOrWhiteSpace(track.Id, nameof(track.Id)); yield return InstanceCache.Tracks.GetOrCreate(track.Id, SourceCore, track); } } /// public async IAsyncEnumerable GetArtistItemsAsync(int limit, int offset, [EnumeratorCancellation] CancellationToken cancellationToken = default) { Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager)); var artists = await _fileMetadataManager.Artists.GetArtistsByAlbumId(Id, offset, limit); foreach (var artist in artists) { Guard.IsNotNullOrWhiteSpace(artist.Id, nameof(artist.Id)); yield return InstanceCache.Artists.GetOrCreate(artist.Id, SourceCore, artist); } } /// public async IAsyncEnumerable GetImagesAsync(int limit, int offset, [EnumeratorCancellation] CancellationToken cancellationToken = default) { Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager)); if (_albumMetadata.ImageIds == null) yield break; foreach (var imageId in _albumMetadata.ImageIds.Skip(offset).Take(limit)) { var image = await _fileMetadataManager.Images.GetByIdAsync(imageId); Guard.IsNotNullOrWhiteSpace(image?.Id, nameof(image.Id)); yield return InstanceCache.Images.GetOrCreate(imageId, SourceCore, image); } } /// public async IAsyncEnumerable GetUrlsAsync(int limit, int offset, [EnumeratorCancellation] CancellationToken cancellationToken = default) { await Task.CompletedTask; yield break; } /// public async IAsyncEnumerable GetGenresAsync(int limit, int offset, [EnumeratorCancellation] CancellationToken cancellationToken = default) { foreach (var genre in _albumMetadata.Genres ?? Enumerable.Empty()) { yield return new FilesCoreGenre(SourceCore, genre); } await Task.CompletedTask; } /// public ValueTask DisposeAsync() { DetachEvents(); return default; } } }