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;
}
}
}