using System;
using System.Collections.Generic;
using System.Globalization;
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.AppModels;
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 track information extracted from a file to the Strix SDK.
///
public sealed class FilesCoreTrack : ICoreTrack
{
private readonly IFileMetadataManager? _fileMetadataManager;
private TrackMetadata _trackMetadata;
///
/// Initializes a new instance of the class.
///
/// The source core.
/// The track metadata to wrap around
public FilesCoreTrack(ICore sourceCore, TrackMetadata trackMetadata)
{
SourceCore = sourceCore;
_trackMetadata = trackMetadata;
_fileMetadataManager = SourceCore.Cast().FileMetadataManager;
AttachEvents();
}
private void AttachEvents()
{
Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager));
_fileMetadataManager.Tracks.MetadataUpdated += Tracks_MetadataUpdated;
}
private void DetachEvents()
{
Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager));
_fileMetadataManager.Tracks.MetadataUpdated -= Tracks_MetadataUpdated;
}
private void Tracks_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.Genres, nameof(metadata.Genres));
var previousData = _trackMetadata;
_trackMetadata = metadata;
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.Description != previousData.Description)
DescriptionChanged?.Invoke(this, Description);
if (metadata.DiscNumber != previousData.DiscNumber)
TrackNumberChanged?.Invoke(this, TrackNumber);
if (!Equals(metadata.Language, previousData.Language))
LanguageChanged?.Invoke(this, Language);
if (!ReferenceEquals(metadata.Lyrics, previousData.Lyrics))
LyricsChanged?.Invoke(this, Lyrics);
if (metadata.TrackNumber != previousData.TrackNumber)
TrackNumberChanged?.Invoke(this, TrackNumber);
if (metadata.Duration != previousData.Duration)
DurationChanged?.Invoke(this, Duration);
if (metadata.ArtistIds.Count != previousData.ArtistIds.Count)
ArtistItemsCountChanged?.Invoke(this, metadata.ArtistIds.Count);
if (metadata.ImageIds.Count != previousData.ImageIds.Count)
ImagesCountChanged?.Invoke(this, metadata.ImageIds.Count);
_ = HandleImagesChanged(previousData.ImageIds, metadata.ImageIds);
_ = HandleArtistsChanged(previousData.ArtistIds, metadata.ArtistIds);
}
}
private async Task HandleArtistsChanged(HashSet oldArtistIds, HashSet newArtistIds)
{
Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager));
if (oldArtistIds.OrderBy(s => s).SequenceEqual(newArtistIds.OrderBy(s => s)))
{
// Lists have identical content, so no items have changed.
return;
}
var addedArtists = newArtistIds.Except(oldArtistIds);
var removedArtists = oldArtistIds.Except(newArtistIds);
if (oldArtistIds.Count != newArtistIds.Count)
{
ArtistItemsChanged?.Invoke(this, await TransformAsync(addedArtists), await TransformAsync(removedArtists));
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 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? AlbumChanged;
///
public event EventHandler? TrackNumberChanged;
///
public event EventHandler? LanguageChanged;
///
public event EventHandler? LyricsChanged;
///
public event EventHandler? IsExplicitChanged;
///
public event EventHandler? PlaybackStateChanged;
///
public event EventHandler? NameChanged;
///
public event EventHandler? DescriptionChanged;
///
public event EventHandler? DurationChanged;
///
public event EventHandler? LastPlayedChanged;
///
public event EventHandler? IsPlayArtistCollectionAsyncAvailableChanged;
///
public event EventHandler? IsPauseArtistCollectionAsyncAvailableChanged;
///
public event EventHandler? IsChangeNameAsyncAvailableChanged;
///
public event EventHandler? IsChangeDescriptionAsyncAvailableChanged;
///
public event EventHandler? IsChangeDurationAsyncAvailableChanged;
///
public event EventHandler? ArtistItemsCountChanged;
///
public event EventHandler? GenresCountChanged;
///
public event EventHandler? ImagesCountChanged;
///
public event EventHandler? UrlsCountChanged;
///
public event CollectionChangedEventHandler? ArtistItemsChanged;
///
public event CollectionChangedEventHandler? GenresChanged;
///
public event CollectionChangedEventHandler? ImagesChanged;
///
public event CollectionChangedEventHandler? UrlsChanged;
///
public string Id => _trackMetadata.Id ?? string.Empty;
///
public TrackType Type => TrackType.Song;
///
public int TotalArtistItemsCount => _trackMetadata.ArtistIds?.Count ?? 0;
///
public int TotalImageCount => _trackMetadata.ImageIds?.Count ?? 0;
///
public int TotalGenreCount => _trackMetadata.Genres?.Count ?? 0;
///
public int TotalUrlCount => 0;
///
public ICoreAlbum? Album { get; }
///
/// Is not passed into the constructor. Should be set on object creation.
public int? TrackNumber => Convert.ToInt32(_trackMetadata.TrackNumber);
///
public int? DiscNumber { get; }
///
public CultureInfo? Language { get; }
///
public ICoreLyrics? Lyrics => null;
///
public bool IsExplicit => false;
///
public ICore SourceCore { get; }
///
/// The path to the playable music file on disk.
///
public string? LocalTrackPath => _trackMetadata.Url;
///
public string Name => _trackMetadata.Title ?? string.Empty;
///
public string? Description => _trackMetadata.Description;
///
public PlaybackState PlaybackState { get; }
///
public TimeSpan Duration => _trackMetadata.Duration ?? new TimeSpan(0, 0, 0);
///
public DateTime? LastPlayed { get; }
///
public DateTime? AddedAt { get; }
///
public ICorePlayableCollectionGroup? RelatedItems => null;
///
public bool IsChangeAlbumAsyncAvailable => false;
///
public bool IsChangeTrackNumberAsyncAvailable => false;
///
public bool IsChangeLanguageAsyncAvailable => false;
///
public bool IsChangeLyricsAsyncAvailable => false;
///
public bool IsChangeIsExplicitAsyncAvailable => false;
///
public bool IsPlayArtistCollectionAsyncAvailable => false;
///
public bool IsPauseArtistCollectionAsyncAvailable => false;
///
public bool IsChangeNameAsyncAvailable => false;
///
public bool IsChangeDescriptionAsyncAvailable => false;
///
public bool IsChangeDurationAsyncAvailable => false;
///
public Task IsAddArtistItemAvailableAsync(int index, CancellationToken cancellationToken = default)
{
return Task.FromResult(false);
}
///
public Task IsAddImageAvailableAsync(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 IsRemoveArtistItemAvailableAsync(int index, CancellationToken cancellationToken = default)
{
return Task.FromResult(false);
}
///
public Task IsRemoveImageAvailableAsync(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 ChangeAlbumAsync(ICoreAlbum? albums, 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 ChangeIsExplicitAsync(bool isExplicit, CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
///
public Task ChangeLanguageAsync(CultureInfo language, CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
///
public Task ChangeLyricsAsync(ICoreLyrics? lyrics, CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
///
public Task ChangeNameAsync(string name, CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
///
public Task ChangeTrackNumberAsync(int? trackNumber, 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();
}
///
public Task PlayArtistCollectionAsync(ICoreArtistCollectionItem artistItem, CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
///
public Task AddArtistItemAsync(ICoreArtistCollectionItem artist, int index, CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
///
public Task AddImageAsync(ICoreImage image, int index, CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
///
public Task AddGenreAsync(ICoreGenre genre, 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 RemoveImageAsync(int index, CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
///
public Task RemoveGenreAsync(int index, CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
///
public Task RemoveUrlAsync(int index, CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
///
public async IAsyncEnumerable GetArtistItemsAsync(int limit, int offset, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager));
var artists = await _fileMetadataManager.Artists.GetArtistsByTrackId(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 (_trackMetadata.ImageIds == null)
yield break;
foreach (var imageId in _trackMetadata.ImageIds)
{
var image = await _fileMetadataManager.Images.GetByIdAsync(imageId);
Guard.IsNotNull(image, nameof(image));
yield return InstanceCache.Images.GetOrCreate(imageId, SourceCore, image);
}
}
///
public async IAsyncEnumerable GetGenresAsync(int limit, int offset, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
foreach (var genre in _trackMetadata.Genres ?? Enumerable.Empty())
{
yield return new FilesCoreGenre(SourceCore, genre);
}
await Task.CompletedTask;
}
///
public IAsyncEnumerable GetUrlsAsync(int limit, int offset, CancellationToken cancellationToken = default)
{
return AsyncEnumerable.Empty();
}
///
public ValueTask DisposeAsync()
{
DetachEvents();
return default;
}
}
}