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; namespace StrixMusic.Cores.Files.Models { /// public sealed class FilesCoreLibrary : FilesCorePlayableCollectionGroupBase, ICoreLibrary { private readonly SemaphoreSlim _initSemaphore = new(1, 1); private IFileMetadataManager? _fileMetadataManager; /// /// Initializes a new instance of the class. /// /// The core that created this instance. public FilesCoreLibrary(ICore sourceCore) : base(sourceCore) { AttachEvents(); } /// public override async Task InitAsync(CancellationToken cancellationToken = default) { using var initReleaseRegistration = cancellationToken.Register(() => _initSemaphore.Release()); await _initSemaphore.WaitAsync(cancellationToken); _fileMetadataManager = SourceCore.Cast().FileMetadataManager; Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager)); AttachEvents(_fileMetadataManager); TotalAlbumItemsCount = await _fileMetadataManager.Albums.GetItemCount(); TotalArtistItemsCount = await _fileMetadataManager.Artists.GetItemCount(); TotalPlaylistItemsCount = await _fileMetadataManager.Playlists.GetItemCount(); TotalTrackCount = await _fileMetadataManager.Tracks.GetItemCount(); await base.InitAsync(cancellationToken); IsInitialized = true; _initSemaphore.Release(); } private void AttachEvents() { base.TracksCountChanged += FilesCoreLibrary_TracksCountChanged; base.ArtistItemsCountChanged += FilesCoreLibrary_ArtistItemsCountChanged; base.AlbumItemsCountChanged += FilesCoreLibrary_AlbumItemsCountChanged; base.PlaylistItemsCountChanged += FilesCoreLibrary_PlaylistItemsCountChanged; base.ImagesCountChanged += FilesCoreLibrary_ImagesCountChanged; } private void DetachEvents() { base.TracksCountChanged -= FilesCoreLibrary_TracksCountChanged; base.ArtistItemsCountChanged -= FilesCoreLibrary_ArtistItemsCountChanged; base.AlbumItemsCountChanged -= FilesCoreLibrary_AlbumItemsCountChanged; base.PlaylistItemsCountChanged -= FilesCoreLibrary_PlaylistItemsCountChanged; base.ImagesCountChanged -= FilesCoreLibrary_ImagesCountChanged; } private void AttachEvents(IFileMetadataManager fileMetadataManager) { fileMetadataManager.Tracks.MetadataAdded += Tracks_MetadataAdded; fileMetadataManager.Albums.MetadataAdded += Albums_MetadataAdded; fileMetadataManager.Artists.MetadataAdded += Artists_MetadataAdded; fileMetadataManager.Playlists.MetadataAdded += Playlists_MetadataAdded; fileMetadataManager.Tracks.MetadataRemoved += Tracks_MetadataRemoved; fileMetadataManager.Albums.MetadataRemoved += Albums_MetadataRemoved; fileMetadataManager.Artists.MetadataRemoved += Artists_MetadataRemoved; fileMetadataManager.Playlists.MetadataRemoved += Playlists_MetadataRemoved; } private void DetachEvents(IFileMetadataManager fileMetadataManager) { fileMetadataManager.Tracks.MetadataAdded -= Tracks_MetadataAdded; fileMetadataManager.Albums.MetadataAdded -= Albums_MetadataAdded; fileMetadataManager.Artists.MetadataAdded -= Artists_MetadataAdded; fileMetadataManager.Playlists.MetadataAdded -= Playlists_MetadataAdded; fileMetadataManager.Tracks.MetadataRemoved -= Tracks_MetadataRemoved; fileMetadataManager.Albums.MetadataRemoved -= Albums_MetadataRemoved; fileMetadataManager.Artists.MetadataRemoved -= Artists_MetadataRemoved; fileMetadataManager.Playlists.MetadataRemoved -= Playlists_MetadataRemoved; } private void FilesCoreLibrary_PlaylistItemsCountChanged(object sender, int e) { PlaylistItemsCountChanged?.Invoke(sender, e); } private void FilesCoreLibrary_AlbumItemsCountChanged(object sender, int e) { AlbumItemsCountChanged?.Invoke(sender, e); } private void FilesCoreLibrary_ArtistItemsCountChanged(object sender, int e) { ArtistItemsCountChanged?.Invoke(sender, e); } private void FilesCoreLibrary_ImagesCountChanged(object sender, int e) { ImagesCountChanged?.Invoke(this, e); } private void FilesCoreLibrary_TracksCountChanged(object sender, int e) { TracksCountChanged?.Invoke(sender, e); } private void Playlists_MetadataAdded(object sender, IEnumerable e) { // ReSharper disable once CollectionNeverUpdated.Local var removedItems = new List>(); var addedItems = new List>(); foreach (var item in e) { Guard.IsNotNullOrWhiteSpace(item.Id, nameof(item.Id)); addedItems.Add(new CollectionChangedItem(InstanceCache.Playlists.GetOrCreate(item.Id, SourceCore, item), addedItems.Count)); } TotalPlaylistItemsCount += addedItems.Count - removedItems.Count; PlaylistItemsChanged?.Invoke(this, addedItems, removedItems); PlaylistItemsCountChanged?.Invoke(this, TotalPlaylistItemsCount); } private void Tracks_MetadataAdded(object sender, IEnumerable e) { // ReSharper disable once CollectionNeverUpdated.Local var removedItems = new List>(); var addedItems = new List>(); foreach (var item in e) { Guard.IsNotNullOrWhiteSpace(item.Id, nameof(item.Id)); addedItems.Add(new CollectionChangedItem(InstanceCache.Tracks.GetOrCreate(item.Id, SourceCore, item), addedItems.Count)); } TotalTrackCount += addedItems.Count - removedItems.Count; TracksChanged?.Invoke(this, addedItems, removedItems); TracksCountChanged?.Invoke(this, TotalTrackCount); } private void Artists_MetadataAdded(object sender, IEnumerable e) { // ReSharper disable once CollectionNeverUpdated.Local var removedItems = new List>(); var addedItems = new List>(); foreach (var item in e) { Guard.IsNotNullOrWhiteSpace(item.Id, nameof(item.Id)); addedItems.Add(new CollectionChangedItem(InstanceCache.Artists.GetOrCreate(item.Id, SourceCore, item), addedItems.Count)); } TotalArtistItemsCount += addedItems.Count - removedItems.Count; ArtistItemsChanged?.Invoke(this, addedItems, removedItems); ArtistItemsCountChanged?.Invoke(this, TotalAlbumItemsCount); } private void Albums_MetadataAdded(object sender, IEnumerable e) { // ReSharper disable once CollectionNeverUpdated.Local var removedItems = new List>(); var addedItems = new List>(); foreach (var item in e) { Guard.IsNotNullOrWhiteSpace(item.Id, nameof(item.Id)); addedItems.Add(new CollectionChangedItem(InstanceCache.Albums.GetOrCreate(item.Id, SourceCore, item), addedItems.Count)); } TotalAlbumItemsCount += addedItems.Count - removedItems.Count; AlbumItemsChanged?.Invoke(this, addedItems, removedItems); AlbumItemsCountChanged?.Invoke(this, TotalAlbumItemsCount); } private void Tracks_MetadataRemoved(object sender, IEnumerable e) { var addedItems = new List>(); var removedItems = new List>(); foreach (var item in e) { Guard.IsNotNullOrWhiteSpace(item.Id, nameof(item.Id)); TotalTrackCount--; TracksCountChanged?.Invoke(this, TotalTrackCount); if (!InstanceCache.Tracks.HasId(item.Id)) continue; removedItems.Add(new CollectionChangedItem(InstanceCache.Tracks.GetOrCreate(item.Id, SourceCore, item), removedItems.Count)); TracksChanged?.Invoke(this, addedItems, removedItems); InstanceCache.Tracks.Remove(item.Id); } } private void Artists_MetadataRemoved(object sender, IEnumerable e) { var addedItems = new List>(); var removedItems = new List>(); foreach (var item in e) { Guard.IsNotNullOrWhiteSpace(item.Id, nameof(item.Id)); TotalArtistItemsCount--; ArtistItemsCountChanged?.Invoke(this, TotalArtistItemsCount); if (!InstanceCache.Artists.HasId(item.Id)) continue; removedItems.Add(new CollectionChangedItem(InstanceCache.Artists.GetOrCreate(item.Id, SourceCore, item), removedItems.Count)); ArtistItemsChanged?.Invoke(this, addedItems, removedItems); InstanceCache.Artists.Remove(item.Id); } } private void Albums_MetadataRemoved(object sender, IEnumerable e) { var addedItems = new List>(); var removedItems = new List>(); foreach (var item in e) { Guard.IsNotNullOrWhiteSpace(item.Id, nameof(item.Id)); TotalAlbumItemsCount--; AlbumItemsCountChanged?.Invoke(this, TotalAlbumItemsCount); if (!InstanceCache.Albums.HasId(item.Id)) continue; removedItems.Add(new CollectionChangedItem(InstanceCache.Albums.GetOrCreate(item.Id, SourceCore, item), removedItems.Count)); AlbumItemsChanged?.Invoke(this, addedItems, removedItems); InstanceCache.Albums.Remove(item.Id); } } private void Playlists_MetadataRemoved(object sender, IEnumerable e) { // TODO. Need to get the index of each item being removed. // Remember to remove from instance cache and dispose the objects being removed after emitted. } /// /// Determines if collection base is initialized or not. /// public override bool IsInitialized { get; protected set; } /// public override string Id { get; protected set; } = "library"; /// public override string Name { get; protected set; } = "Library"; /// public override string? Description { get; protected set; } = null; /// ? public override event CollectionChangedEventHandler? PlaylistItemsChanged; /// public override event CollectionChangedEventHandler? AlbumItemsChanged; /// public override event CollectionChangedEventHandler? ArtistItemsChanged; /// public override event CollectionChangedEventHandler? TracksChanged; /// public override event EventHandler? AlbumItemsCountChanged; /// public override event EventHandler? ArtistItemsCountChanged; /// public override event EventHandler? TracksCountChanged; /// public override event EventHandler? PlaylistItemsCountChanged; /// public override event EventHandler? ChildrenCountChanged; /// public override event EventHandler? ImagesCountChanged; /// public override IAsyncEnumerable GetChildrenAsync(int limit, int offset, CancellationToken cancellationToken = default) { return AsyncEnumerable.Empty(); } /// public override async IAsyncEnumerable GetPlaylistItemsAsync(int limit, int offset, [EnumeratorCancellation] CancellationToken cancellationToken = default) { Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager)); var playlistsMetadata = await _fileMetadataManager.Playlists.GetItemsAsync(offset, limit); foreach (var playList in playlistsMetadata) { Guard.IsNotNullOrWhiteSpace(playList.Id, nameof(playList.Id)); yield return InstanceCache.Playlists.GetOrCreate(playList.Id, SourceCore, playList); } } /// public override async IAsyncEnumerable GetAlbumItemsAsync(int limit, int offset, [EnumeratorCancellation] CancellationToken cancellationToken = default) { Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager)); var albumMetadata = await _fileMetadataManager.Albums.GetItemsAsync(offset, limit); foreach (var album in albumMetadata) { Guard.IsNotNull(album.Id, nameof(album.Id)); yield return InstanceCache.Albums.GetOrCreate(album.Id, SourceCore, album); } } /// public override async IAsyncEnumerable GetArtistItemsAsync(int limit, int offset, [EnumeratorCancellation] CancellationToken cancellationToken = default) { Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager)); var artistMetadata = await _fileMetadataManager.Artists.GetItemsAsync(offset, limit); foreach (var artist in artistMetadata) { Guard.IsNotNullOrWhiteSpace(artist.Id, nameof(artist.Id)); yield return InstanceCache.Artists.GetOrCreate(artist.Id, SourceCore, artist); } } /// public override async IAsyncEnumerable GetTracksAsync(int limit, int offset, [EnumeratorCancellation] CancellationToken cancellationToken = default) { Guard.IsNotNull(_fileMetadataManager, nameof(_fileMetadataManager)); var artistMetadata = await _fileMetadataManager.Tracks.GetItemsAsync(offset, limit); foreach (var track in artistMetadata) { Guard.IsNotNullOrWhiteSpace(track.Id, nameof(track.Id)); yield return InstanceCache.Tracks.GetOrCreate(track.Id, SourceCore, track); } } /// public override IAsyncEnumerable GetUrlsAsync(int limit, int offset, CancellationToken cancellationToken = default) { return AsyncEnumerable.Empty(); } /// public override async ValueTask DisposeAsync() { if (!IsInitialized) return; using (await OwlCore.Flow.EasySemaphore(_initSemaphore)) { if (!IsInitialized) return; if (_fileMetadataManager is not null) DetachEvents(_fileMetadataManager); DetachEvents(); await base.DisposeAsync(); IsInitialized = false; } } } }