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.FileMetadata; using StrixMusic.Sdk.FileMetadata.Models; using StrixMusic.Sdk.MediaPlayback; namespace StrixMusic.Cores.Files.Models { /// /// Wraps around to provide artist information extracted from a file to the Strix SDK. /// public sealed class FilesCoreArtist : ICoreArtist { private readonly IFileMetadataManager? _fileMetadataManager; private ArtistMetadata _artistMetadata; /// /// Initializes a new instance of the class. /// /// The source core. /// The artist metadata to wrap around. public FilesCoreArtist(ICore sourceCore, ArtistMetadata artistMetadata) { SourceCore = sourceCore; _artistMetadata = artistMetadata; _fileMetadataManager = sourceCore.Cast().FileMetadataManager; AttachEvents(); } private void AttachEvents() { Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager)); _fileMetadataManager.Artists.MetadataUpdated += Artists_MetadataUpdated; } private void DetachEvents() { Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager)); _fileMetadataManager.Artists.MetadataUpdated -= Artists_MetadataUpdated; } private void Artists_MetadataUpdated(object sender, IEnumerable e) { foreach (var metadata in e) { if (metadata.Id != Id) return; Guard.IsNotNull(metadata.AlbumIds, nameof(metadata.AlbumIds)); Guard.IsNotNull(metadata.TrackIds, nameof(metadata.TrackIds)); Guard.IsNotNull(metadata.ImageIds, nameof(metadata.ImageIds)); Guard.IsNotNull(metadata.Genres, nameof(metadata.Genres)); var previousData = _artistMetadata; _artistMetadata = metadata; Guard.IsNotNull(previousData.TrackIds, nameof(previousData.TrackIds)); Guard.IsNotNull(previousData.AlbumIds, nameof(previousData.AlbumIds)); Guard.IsNotNull(previousData.ImageIds, nameof(previousData.ImageIds)); Guard.IsNotNull(previousData.Genres, nameof(previousData.Genres)); if (metadata.Name != previousData.Name) NameChanged?.Invoke(this, Name); if (metadata.Description != previousData.Description) DescriptionChanged?.Invoke(this, Description); 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.AlbumIds.Count != previousData.AlbumIds.Count) AlbumItemsCountChanged?.Invoke(this, metadata.AlbumIds.Count); _ = HandleImagesChanged(previousData.ImageIds, metadata.ImageIds); _ = HandleTracksChanged(previousData.TrackIds, metadata.TrackIds); _ = HandleAlbumsChanged(previousData.AlbumIds, metadata.AlbumIds); } } private async Task HandleAlbumsChanged(HashSet oldAlbumIds, HashSet newAlbumIds) { Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager)); if (oldAlbumIds.OrderBy(s => s).SequenceEqual(newAlbumIds.OrderBy(s => s))) { // Lists have identical content, so no images have changed. return; } var addedAlbums = newAlbumIds.Except(oldAlbumIds); var removedAlbums = oldAlbumIds.Except(newAlbumIds); if (oldAlbumIds.Count != newAlbumIds.Count) { AlbumItemsChanged?.Invoke(this, await TransformAsync(addedAlbums), await TransformAsync(removedAlbums)); AlbumItemsCountChanged?.Invoke(this, newAlbumIds.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 album = await _fileMetadataManager.Albums.GetByIdAsync(id); Guard.IsNotNullOrWhiteSpace(album?.Id, nameof(album.Id)); collectionChangedItems.Add(new CollectionChangedItem(InstanceCache.Albums.GetOrCreate(album.Id, SourceCore, album), collectionChangedItems.Count)); } return collectionChangedItems; } } private async Task HandleTracksChanged(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 HandleImagesChanged(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? DurationChanged; /// public event EventHandler? LastPlayedChanged; /// public event EventHandler? IsPlayTrackCollectionAsyncAvailableChanged; /// public event EventHandler? IsPauseTrackCollectionAsyncAvailableChanged; /// public event EventHandler? IsPlayAlbumCollectionAsyncAvailableChanged; /// public event EventHandler? IsPauseAlbumCollectionAsyncAvailableChanged; /// public event EventHandler? IsChangeNameAsyncAvailableChanged; /// public event EventHandler? IsChangeDescriptionAsyncAvailableChanged; /// public event EventHandler? IsChangeDurationAsyncAvailableChanged; /// public event EventHandler? ImagesCountChanged; /// public event EventHandler? AlbumItemsCountChanged; /// public event EventHandler? TracksCountChanged; /// public event CollectionChangedEventHandler? ImagesChanged; /// public event CollectionChangedEventHandler? AlbumItemsChanged; /// public event CollectionChangedEventHandler? TracksChanged; /// public event CollectionChangedEventHandler? GenresChanged; /// public event EventHandler? GenresCountChanged; /// public event CollectionChangedEventHandler? UrlsChanged; /// public event EventHandler? UrlsCountChanged; /// public string Id => _artistMetadata.Id ?? string.Empty; /// public int TotalTrackCount => _artistMetadata.TrackIds?.Count ?? 0; /// public int TotalAlbumItemsCount => _artistMetadata.AlbumIds?.Count ?? 0; /// public int TotalGenreCount => _artistMetadata.Genres?.Count ?? 0; /// public int TotalImageCount => _artistMetadata.ImageIds?.Count ?? 0; /// public int TotalUrlCount => 0; /// public ICore SourceCore { get; } /// public string Name => _artistMetadata.Name ?? string.Empty; /// public string Description => _artistMetadata.Description ?? string.Empty; /// public PlaybackState PlaybackState => PlaybackState.None; /// public TimeSpan Duration => new TimeSpan(0, 0, 0); /// public DateTime? LastPlayed { get; } /// public DateTime? AddedAt { get; } /// public ICorePlayableCollectionGroup? RelatedItems => null; /// public bool IsPlayTrackCollectionAsyncAvailable => false; /// public bool IsPauseTrackCollectionAsyncAvailable => false; /// public bool IsPlayAlbumCollectionAsyncAvailable => false; /// public bool IsPauseAlbumCollectionAsyncAvailable => false; /// public bool IsChangeNameAsyncAvailable => false; /// public bool IsChangeDescriptionAsyncAvailable => false; /// public bool IsChangeDurationAsyncAvailable => false; /// public Task IsAddImageAvailableAsync(int index, CancellationToken cancellationToken = default) { return Task.FromResult(false); } /// public Task IsAddTrackAvailableAsync(int index, CancellationToken cancellationToken = default) { return Task.FromResult(false); } /// public Task IsAddAlbumItemAvailableAsync(int index, CancellationToken cancellationToken = default) { return Task.FromResult(false); } /// public Task IsAddGenreAvailableAsync(int index, CancellationToken cancellationToken = default) { return Task.FromResult(false); } /// public Task IsAddUrlAvailableAsync(int index, CancellationToken cancellationToken = default) { return Task.FromResult(false); } /// public Task IsRemoveImageAvailableAsync(int index, CancellationToken cancellationToken = default) { return Task.FromResult(false); } /// public Task IsRemoveTrackAvailableAsync(int index, CancellationToken cancellationToken = default) { return Task.FromResult(false); } /// public Task IsRemoveAlbumItemAvailableAsync(int index, CancellationToken cancellationToken = default) { return Task.FromResult(false); } /// public Task IsRemoveGenreAvailableAsync(int index, CancellationToken cancellationToken = default) { return Task.FromResult(false); } /// public Task IsRemoveUrlAvailableAsync(int index, CancellationToken cancellationToken = default) { return Task.FromResult(false); } /// public Task ChangeDescriptionAsync(string? description, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task ChangeDurationAsync(TimeSpan duration, 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 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(); } /// A cancellation token that may be used to cancel the ongoing task. /// public Task PauseAlbumCollectionAsync(CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// A cancellation token that may be used to cancel the ongoing task. /// public Task PlayAlbumCollectionAsync(CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task PlayTrackCollectionAsync(ICoreTrack track, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task PlayAlbumCollectionAsync(ICoreAlbumCollectionItem albumItem, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task AddTrackAsync(ICoreTrack track, int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task AddAlbumItemAsync(ICoreAlbumCollectionItem album, int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task RemoveTrackAsync(int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task RemoveAlbumItemAsync(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 NotImplementedException(); } /// public Task AddImageAsync(ICoreImage image, int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task RemoveImageAsync(int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task AddUrlAsync(ICoreUrl image, int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public Task RemoveUrlAsync(int index, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public async IAsyncEnumerable GetAlbumItemsAsync(int limit, int offset, [EnumeratorCancellation] CancellationToken cancellationToken = default) { Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager)); var albums = await _fileMetadataManager.Albums.GetAlbumsByArtistId(Id, offset, limit); foreach (var album in albums) { Guard.IsNotNull(album.Id, nameof(album.Id)); yield return InstanceCache.Albums.GetOrCreate(album.Id, SourceCore, album); } } /// public async IAsyncEnumerable GetTracksAsync(int limit, int offset, [EnumeratorCancellation] CancellationToken cancellationToken = default) { Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager)); var tracks = await _fileMetadataManager.Tracks.GetTracksByArtistId(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 GetImagesAsync(int limit, int offset, [EnumeratorCancellation] CancellationToken cancellationToken = default) { Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager)); if (_artistMetadata.ImageIds == null) yield break; foreach (var imageId in _artistMetadata.ImageIds) { var image = await _fileMetadataManager.Images.GetByIdAsync(imageId); Guard.IsNotNull(image, nameof(image)); 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) { Guard.IsNotNull(_artistMetadata, nameof(_artistMetadata)); foreach (var genre in _artistMetadata.Genres ?? Enumerable.Empty()) yield return new FilesCoreGenre(SourceCore, genre); await Task.CompletedTask; } /// public ValueTask DisposeAsync() { DetachEvents(); return default; } } }