// Copyright (c) Arlo Godfrey. All Rights Reserved. // Licensed under the GNU Lesser General Public License, Version 3.0 with additional terms. // See the LICENSE, LICENSE.LESSER and LICENSE.ADDITIONAL files in the project root for more information. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Diagnostics; using OwlCore.Events; using OwlCore.Extensions; using StrixMusic.Sdk.AppModels; using StrixMusic.Sdk.BaseModels; using StrixMusic.Sdk.CoreModels; using StrixMusic.Sdk.Extensions; using StrixMusic.Sdk.MediaPlayback; namespace StrixMusic.Sdk.AdapterModels { /// /// A concrete class that merged multiple s. /// public class MergedTrack : ITrack, IMergedMutable { private readonly MergedCollectionConfig _config; private readonly ICoreTrack _preferredSource; private readonly List _sources; private readonly MergedCollectionMap _artistMap; private readonly MergedCollectionMap _imageCollectionMap; private readonly MergedCollectionMap _genreCollectionMap; private readonly MergedCollectionMap _urlCollectionMap; /// /// Initializes a new instance of the class. /// public MergedTrack(IEnumerable tracks, MergedCollectionConfig config) { _config = config; _sources = tracks.ToList(); // TODO: Use top Preferred core. _preferredSource = _sources.First(); _artistMap = new MergedCollectionMap(this, config); _imageCollectionMap = new MergedCollectionMap(this, config); _genreCollectionMap = new MergedCollectionMap(this, config); _urlCollectionMap = new MergedCollectionMap(this, config); Name = _preferredSource.Name; foreach (var item in _sources) { TotalArtistItemsCount += item.TotalArtistItemsCount; TotalImageCount += item.TotalImageCount; TotalGenreCount += item.TotalGenreCount; TotalUrlCount += item.TotalUrlCount; if (item.IsExplicit) IsExplicit = true; } AttachEvents(_preferredSource); } private void AttachEvents(ICoreTrack source) { AttachPlayableEvents(source); source.IsPlayArtistCollectionAsyncAvailableChanged += IsPlayArtistCollectionAsyncAvailableChanged; source.IsPauseArtistCollectionAsyncAvailableChanged += IsPauseArtistCollectionAsyncAvailableChanged; source.LanguageChanged += LanguageChanged; source.LyricsChanged += Source_LyricsChanged; source.IsExplicitChanged += IsExplicitChanged; source.AlbumChanged += Source_AlbumChanged; source.TrackNumberChanged += TrackNumberChanged; _imageCollectionMap.ItemsChanged += ImageCollectionMap_ItemsChanged; _imageCollectionMap.ItemsCountChanged += ImageCollectionMap_ItemsCountChanged; _artistMap.ItemsChanged += ArtistMap_ItemsChanged; _artistMap.ItemsCountChanged += ArtistMap_ItemsCountChanged; _genreCollectionMap.ItemsChanged += GenreCollectionMap_ItemsChanged; _genreCollectionMap.ItemsCountChanged += GenreCollectionMap_ItemsCountChanged; _urlCollectionMap.ItemsChanged += UrlCollectionMap_ItemsChanged; _urlCollectionMap.ItemsCountChanged += UrlCollectionMap_ItemsCountChanged; } private void DetachEvents(ICoreTrack source) { DetachPlayableEvents(source); source.IsPlayArtistCollectionAsyncAvailableChanged -= IsPlayArtistCollectionAsyncAvailableChanged; source.IsPauseArtistCollectionAsyncAvailableChanged -= IsPauseArtistCollectionAsyncAvailableChanged; source.LanguageChanged -= LanguageChanged; source.LyricsChanged -= Source_LyricsChanged; source.IsExplicitChanged -= IsExplicitChanged; source.AlbumChanged -= Source_AlbumChanged; source.TrackNumberChanged -= TrackNumberChanged; _imageCollectionMap.ItemsChanged -= ImageCollectionMap_ItemsChanged; _imageCollectionMap.ItemsCountChanged -= ImageCollectionMap_ItemsCountChanged; _artistMap.ItemsChanged -= ArtistMap_ItemsChanged; _artistMap.ItemsCountChanged -= ArtistMap_ItemsCountChanged; _genreCollectionMap.ItemsChanged -= GenreCollectionMap_ItemsChanged; _genreCollectionMap.ItemsCountChanged -= GenreCollectionMap_ItemsCountChanged; _urlCollectionMap.ItemsChanged -= UrlCollectionMap_ItemsChanged; _urlCollectionMap.ItemsCountChanged -= UrlCollectionMap_ItemsCountChanged; } private void AttachPlayableEvents(IPlayableBase source) { source.PlaybackStateChanged += PlaybackStateChanged; source.NameChanged += NameChanged; source.DescriptionChanged += DescriptionChanged; source.DurationChanged += DurationChanged; source.LastPlayedChanged += LastPlayedChanged; source.IsChangeNameAsyncAvailableChanged += IsChangeNameAsyncAvailableChanged; source.IsChangeDurationAsyncAvailableChanged += IsChangeDurationAsyncAvailableChanged; source.IsChangeDescriptionAsyncAvailableChanged += IsChangeDescriptionAsyncAvailableChanged; } private void DetachPlayableEvents(IPlayableBase source) { source.PlaybackStateChanged -= PlaybackStateChanged; source.NameChanged -= NameChanged; source.DescriptionChanged -= DescriptionChanged; source.DurationChanged -= DurationChanged; source.LastPlayedChanged -= LastPlayedChanged; source.IsChangeNameAsyncAvailableChanged -= IsChangeNameAsyncAvailableChanged; source.IsChangeDurationAsyncAvailableChanged -= IsChangeDurationAsyncAvailableChanged; source.IsChangeDescriptionAsyncAvailableChanged -= IsChangeDescriptionAsyncAvailableChanged; } private void ArtistMap_ItemsCountChanged(object sender, int e) { TotalArtistItemsCount = e; ArtistItemsCountChanged?.Invoke(this, e); } private void ImageCollectionMap_ItemsCountChanged(object sender, int e) { TotalImageCount = e; ImagesCountChanged?.Invoke(this, e); } private void GenreCollectionMap_ItemsCountChanged(object sender, int e) { TotalGenreCount = e; GenresCountChanged?.Invoke(this, e); } private void UrlCollectionMap_ItemsCountChanged(object sender, int e) { TotalUrlCount = e; UrlsCountChanged?.Invoke(this, e); } private void ArtistMap_ItemsChanged(object sender, IReadOnlyList> addedItems, IReadOnlyList> removedItems) { ArtistItemsChanged?.Invoke(this, addedItems, removedItems); } private void GenreCollectionMap_ItemsChanged(object sender, IReadOnlyList> addedItems, IReadOnlyList> removedItems) { GenresChanged?.Invoke(this, addedItems, removedItems); } private void ImageCollectionMap_ItemsChanged(object sender, IReadOnlyList> addedItems, IReadOnlyList> removedItems) { ImagesChanged?.Invoke(this, addedItems, removedItems); } private void UrlCollectionMap_ItemsChanged(object sender, IReadOnlyList> addedItems, IReadOnlyList> removedItems) { UrlsChanged?.Invoke(this, addedItems, removedItems); } private void Source_LyricsChanged(object sender, ICoreLyrics? e) { if (e is null) return; var merged = new MergedLyrics(e, _config); LyricsChanged?.Invoke(this, merged); } private void Source_AlbumChanged(object sender, ICoreAlbum? e) { if (e is null) return; var merged = new MergedAlbum(e.IntoList(), _config); AlbumChanged?.Invoke(this, merged); } /// public event EventHandler? AlbumChanged; /// public event EventHandler? TrackNumberChanged; /// public event EventHandler? PlaybackStateChanged; /// public event EventHandler? LanguageChanged; /// public event EventHandler? LyricsChanged; /// public event EventHandler? IsExplicitChanged; /// 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 event EventHandler? DownloadInfoChanged; /// public event EventHandler? SourcesChanged; /// IReadOnlyList IMerged.Sources => Sources; /// IReadOnlyList IMerged.Sources => Sources; /// IReadOnlyList IMerged.Sources => Sources; /// IReadOnlyList IMerged.Sources => Sources; /// IReadOnlyList IMerged.Sources => Sources; /// IReadOnlyList IMerged.Sources => Sources; /// /// The original sources for this merged item. /// public IReadOnlyList Sources => _sources; /// public string Id => _preferredSource.Id; /// public string Name { get; internal set; } /// public TrackType Type => _preferredSource.Type; /// public int TotalArtistItemsCount { get; private set; } /// public int TotalImageCount { get; private set; } /// public int TotalGenreCount { get; private set; } /// public int TotalUrlCount { get; private set; } /// public IPlayableCollectionGroup? RelatedItems { get; } /// public IAlbum? Album { get; } /// public int? TrackNumber => _preferredSource.TrackNumber; /// public int? DiscNumber => _preferredSource.DiscNumber; /// public CultureInfo? Language => _preferredSource.Language; /// public ILyrics? Lyrics { get; } /// public bool IsExplicit { get; } /// public string? Description => _preferredSource.Description; /// public PlaybackState PlaybackState => _preferredSource.PlaybackState; /// public DownloadInfo DownloadInfo => default; /// public TimeSpan Duration => _preferredSource.Duration; /// public DateTime? LastPlayed => _preferredSource.LastPlayed; /// public DateTime? AddedAt => _preferredSource.AddedAt; /// public bool IsChangeAlbumAsyncAvailable => _preferredSource.IsChangeAlbumAsyncAvailable; /// public bool IsChangeTrackNumberAsyncAvailable => _preferredSource.IsChangeTrackNumberAsyncAvailable; /// public bool IsChangeLanguageAsyncAvailable => _preferredSource.IsChangeLanguageAsyncAvailable; /// public bool IsChangeLyricsAsyncAvailable => _preferredSource.IsChangeLyricsAsyncAvailable; /// public bool IsChangeIsExplicitAsyncAvailable => _preferredSource.IsChangeIsExplicitAsyncAvailable; /// public bool IsPlayArtistCollectionAsyncAvailable => _preferredSource.IsPlayArtistCollectionAsyncAvailable; /// public bool IsPauseArtistCollectionAsyncAvailable => _preferredSource.IsPauseArtistCollectionAsyncAvailable; /// public bool IsChangeNameAsyncAvailable => _preferredSource.IsChangeNameAsyncAvailable; /// public bool IsChangeDescriptionAsyncAvailable => _preferredSource.IsChangeDescriptionAsyncAvailable; /// public bool IsChangeDurationAsyncAvailable => _preferredSource.IsChangeDurationAsyncAvailable; /// public Task PauseArtistCollectionAsync(CancellationToken cancellationToken = default) => _preferredSource.PauseArtistCollectionAsync(cancellationToken); /// public Task PlayArtistCollectionAsync(CancellationToken cancellationToken = default) => _preferredSource.PlayArtistCollectionAsync(cancellationToken); /// public Task PlayArtistCollectionAsync(IArtistCollectionItem artistItem, CancellationToken cancellationToken = default) { var targetCore = _preferredSource.SourceCore; ICoreArtistCollectionItem? source = null; if (artistItem is IArtist artist) source = artist.GetSources().FirstOrDefault(x => x.SourceCore.InstanceId == targetCore.InstanceId); if (artistItem is IArtistCollection collection) source = collection.GetSources().FirstOrDefault(x => x.SourceCore.InstanceId == targetCore.InstanceId); Guard.IsNotNull(source, nameof(source)); return _preferredSource.PlayArtistCollectionAsync(source, cancellationToken); } /// public Task ChangeAlbumAsync(IAlbum? album, CancellationToken cancellationToken = default) => _sources.InParallel(x => { if (!x.IsChangeAlbumAsyncAvailable) return Task.CompletedTask; var sourceAlbum = album?.GetSources().First(y => y.SourceCore == x.SourceCore); return x.ChangeAlbumAsync(sourceAlbum, cancellationToken); }); /// public Task ChangeTrackNumberAsync(int? trackNumber, CancellationToken cancellationToken = default) { return _sources.InParallel(x => x.ChangeTrackNumberAsync(trackNumber, cancellationToken)); } /// public Task ChangeLanguageAsync(CultureInfo language, CancellationToken cancellationToken = default) { return _preferredSource.ChangeLanguageAsync(language, cancellationToken); } /// public Task ChangeLyricsAsync(ILyrics? lyrics, CancellationToken cancellationToken = default) { var sourceToChange = lyrics?.GetSources().First(x => x.SourceCore == _preferredSource.SourceCore); Guard.IsNotNull(sourceToChange, nameof(sourceToChange)); return _preferredSource.ChangeLyricsAsync(sourceToChange, cancellationToken); } /// public Task ChangeIsExplicitAsync(bool isExplicit, CancellationToken cancellationToken = default) => _preferredSource.ChangeIsExplicitAsync(isExplicit, cancellationToken); /// public Task ChangeNameAsync(string name, CancellationToken cancellationToken = default) => _preferredSource.ChangeNameAsync(name, cancellationToken); /// public Task ChangeDescriptionAsync(string? description, CancellationToken cancellationToken = default) => _preferredSource.ChangeDescriptionAsync(description, cancellationToken); /// public Task ChangeDurationAsync(TimeSpan duration, CancellationToken cancellationToken = default) => _preferredSource.ChangeDurationAsync(duration, cancellationToken); /// public Task IsAddArtistItemAvailableAsync(int index, CancellationToken cancellationToken = default) => _preferredSource.IsAddArtistItemAvailableAsync(index, cancellationToken); /// public Task IsAddImageAvailableAsync(int index, CancellationToken cancellationToken = default) => _preferredSource.IsAddImageAvailableAsync(index, cancellationToken); /// public Task IsAddGenreAvailableAsync(int index, CancellationToken cancellationToken = default) => _genreCollectionMap.IsAddItemAvailableAsync(index, cancellationToken); /// public Task IsAddUrlAvailableAsync(int index, CancellationToken cancellationToken = default) => _urlCollectionMap.IsAddItemAvailableAsync(index, cancellationToken); /// public Task IsRemoveArtistItemAvailableAsync(int index, CancellationToken cancellationToken = default) => _artistMap.IsRemoveItemAvailableAsync(index, cancellationToken); /// public Task IsRemoveImageAvailableAsync(int index, CancellationToken cancellationToken = default) => _imageCollectionMap.IsRemoveItemAvailableAsync(index, cancellationToken); /// public Task IsRemoveGenreAvailableAsync(int index, CancellationToken cancellationToken = default) => _genreCollectionMap.IsRemoveItemAvailableAsync(index, cancellationToken); /// public Task IsRemoveUrlAvailableAsync(int index, CancellationToken cancellationToken = default) => _urlCollectionMap.IsRemoveItemAvailableAsync(index, cancellationToken); /// public IAsyncEnumerable GetArtistItemsAsync(int limit, int offset, CancellationToken cancellationToken = default) => _artistMap.GetItemsAsync(limit, offset, cancellationToken); /// public IAsyncEnumerable GetImagesAsync(int limit, int offset, CancellationToken cancellationToken = default) => _imageCollectionMap.GetItemsAsync(limit, offset, cancellationToken); /// public IAsyncEnumerable GetGenresAsync(int limit, int offset, CancellationToken cancellationToken = default) => _genreCollectionMap.GetItemsAsync(limit, offset, cancellationToken); /// public IAsyncEnumerable GetUrlsAsync(int limit, int offset, CancellationToken cancellationToken = default) => _urlCollectionMap.GetItemsAsync(limit, offset, cancellationToken); /// public Task AddArtistItemAsync(IArtistCollectionItem artistItem, int index, CancellationToken cancellationToken = default) => _artistMap.InsertItemAsync(artistItem, index, cancellationToken); /// public Task AddImageAsync(IImage image, int index, CancellationToken cancellationToken = default) => _imageCollectionMap.InsertItemAsync(image, index, cancellationToken); /// public Task AddGenreAsync(IGenre genre, int index, CancellationToken cancellationToken = default) => _genreCollectionMap.InsertItemAsync(genre, index, cancellationToken); /// public Task AddUrlAsync(IUrl genre, int index, CancellationToken cancellationToken = default) => _urlCollectionMap.InsertItemAsync(genre, index, cancellationToken); /// public Task RemoveArtistItemAsync(int index, CancellationToken cancellationToken = default) => _artistMap.RemoveAtAsync(index, cancellationToken); /// public Task RemoveImageAsync(int index, CancellationToken cancellationToken = default) => _imageCollectionMap.RemoveAtAsync(index, cancellationToken); /// public Task RemoveGenreAsync(int index, CancellationToken cancellationToken = default) => _genreCollectionMap.RemoveAtAsync(index, cancellationToken); /// public Task RemoveUrlAsync(int index, CancellationToken cancellationToken = default) => _urlCollectionMap.RemoveAtAsync(index, cancellationToken); /// public Task StartDownloadOperationAsync(DownloadOperation operation, CancellationToken cancellationToken = default) { throw new NotSupportedException(); } /// public bool Equals(ICoreArtistCollectionItem other) => Equals(other as ICoreTrack); /// public bool Equals(ICoreImageCollection other) => Equals(other as ICoreTrack); /// public bool Equals(ICoreUrlCollection other) => Equals(other as ICoreTrack); /// public bool Equals(ICoreArtistCollection other) => Equals(other as ICoreTrack); /// public bool Equals(ICoreGenreCollection other) => Equals(other as ICoreTrack); /// public override bool Equals(object? obj) => Equals(obj as ICoreTrack); /// public override int GetHashCode() => _preferredSource.Id.GetHashCode(); /// public void AddSource(ICoreTrack itemToMerge) { Guard.IsNotNull(itemToMerge, nameof(itemToMerge)); _artistMap.AddSource(itemToMerge); _imageCollectionMap.AddSource(itemToMerge); _urlCollectionMap.AddSource(itemToMerge); _sources.Add(itemToMerge); SourcesChanged?.Invoke(this, EventArgs.Empty); } /// public void RemoveSource(ICoreTrack itemToRemove) { Guard.IsNotNull(itemToRemove, nameof(itemToRemove)); _artistMap.RemoveSource(itemToRemove); _imageCollectionMap.RemoveSource(itemToRemove); _urlCollectionMap.RemoveSource(itemToRemove); _sources.Remove(itemToRemove); SourcesChanged?.Invoke(this, EventArgs.Empty); } /// public bool Equals(ICoreTrack? other) { return other != null && other.Name == Name && other.TrackNumber == TrackNumber && other.Type == Type && other.DiscNumber == DiscNumber && other.Duration == Duration && Album is MergedAlbum album && !(other.Album is null) && album.Equals(other.Album); } /// public async ValueTask DisposeAsync() { DetachEvents(_preferredSource); await _sources.InParallel(x => x.DisposeAsync().AsTask()); await _imageCollectionMap.DisposeAsync(); await _artistMap.DisposeAsync(); await _genreCollectionMap.DisposeAsync(); await _urlCollectionMap.DisposeAsync(); } } }