// 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.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using OwlCore; using OwlCore.Events; using OwlCore.Remoting; using StrixMusic.Sdk.CoreModels; using StrixMusic.Sdk.MediaPlayback; namespace StrixMusic.Sdk.Plugins.CoreRemote { /// /// Wraps around an instance of an to enable controlling it remotely, or takes a remotingId to control another instance remotely. /// public sealed class RemoteCoreAlbum : ICoreAlbum { private readonly MemberRemote _memberRemote; private readonly ICoreAlbum? _album; private int _totalArtistItemsCount; private int _totalTrackCount; private int _totalImageCount; private int _totalUrlCount; private int _totalGenreCount; private string _name; private string? _description; private PlaybackState _playbackState; private TimeSpan _duration; private DateTime? _lastPlayed; private DateTime? _datePublished; private bool _isChangeDurationAsyncAvailable; private bool _isChangeDescriptionAsyncAvailable; private bool _isChangeNameAsyncAvailable; private bool _isPauseTrackCollectionAsyncAvailable; private bool _isPlayTrackCollectionAsyncAvailable; private bool _isPauseArtistCollectionAsyncAvailable; private bool _isPlayArtistCollectionAsyncAvailable; private bool _isChangeDatePublishedAsyncAvailable; private SemaphoreSlim _getTracksMutex = new SemaphoreSlim(1, 1); private SemaphoreSlim _getArtistsMutex = new SemaphoreSlim(1, 1); private SemaphoreSlim _getImagesMutex = new SemaphoreSlim(1, 1); private SemaphoreSlim _getUrlsMutex = new SemaphoreSlim(1, 1); private SemaphoreSlim _getGenresMutex = new SemaphoreSlim(1, 1); /// /// Creates a new instance of . /// /// The instance ID of the core that created this object. /// A unique identifier for this instance. /// The name of the data. [JsonConstructor] internal RemoteCoreAlbum(string sourceCoreInstanceId, string name, string id) { _name = name; Id = id; SourceCoreInstanceId = sourceCoreInstanceId; // Properties assigned before MemberRemote is created won't be set remotely. SourceCore = RemoteCore.GetInstance(sourceCoreInstanceId, RemotingMode.Client); // should be set remotely by the ctor. _memberRemote = new MemberRemote(this, $"{sourceCoreInstanceId}.{nameof(RemoteCoreAlbum)}.{id}", RemoteCoreMessageHandler.SingletonClient); } /// /// Wraps around and remotely relays events, property changes and method calls (with return data) from an album instance. /// /// internal RemoteCoreAlbum(ICoreAlbum coreAlbum) { _album = coreAlbum; _name = coreAlbum.Name; Id = coreAlbum.Id; SourceCoreInstanceId = coreAlbum.SourceCore.InstanceId; SourceCore = RemoteCore.GetInstance(SourceCoreInstanceId, RemotingMode.Host); _memberRemote = new MemberRemote(this, $"{coreAlbum.SourceCore.InstanceId}.{nameof(RemoteCoreAlbum)}.{coreAlbum.Id}", RemoteCoreMessageHandler.SingletonHost); } [RemoteMethod] private void OnTracksChanged(IReadOnlyList> addedItems, IReadOnlyList> removedItems) { TracksChanged?.Invoke(this, addedItems, removedItems); } [RemoteMethod] private void OnArtistItemsChanged(IReadOnlyList> addedItems, IReadOnlyList> removedItems) { ArtistItemsChanged?.Invoke(this, addedItems, removedItems); } [RemoteMethod] private void OnImagesChanged(IReadOnlyList> addedItems, IReadOnlyList> removedItems) { ImagesChanged?.Invoke(this, addedItems, removedItems); } [RemoteMethod] private void OnUrlsChanged(IReadOnlyList> addedItems, IReadOnlyList> removedItems) { UrlsChanged?.Invoke(this, addedItems, removedItems); } [RemoteMethod] private void OnGenresChanged(IReadOnlyList> addedItems, IReadOnlyList> removedItems) { GenresChanged?.Invoke(this, addedItems, removedItems); } /// 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? IsPlayTrackCollectionAsyncAvailableChanged; /// public event EventHandler? IsPauseTrackCollectionAsyncAvailableChanged; /// public event EventHandler? IsChangeNameAsyncAvailableChanged; /// public event EventHandler? IsChangeDescriptionAsyncAvailableChanged; /// public event EventHandler? IsChangeDurationAsyncAvailableChanged; /// public event EventHandler? IsChangeDatePublishedAsyncAvailableChanged; /// public event EventHandler? ArtistItemsCountChanged; /// public event EventHandler? TracksCountChanged; /// public event EventHandler? GenresCountChanged; /// public event EventHandler? ImagesCountChanged; /// public event EventHandler? UrlsCountChanged; /// public event CollectionChangedEventHandler? TracksChanged; /// public event CollectionChangedEventHandler? ArtistItemsChanged; /// public event CollectionChangedEventHandler? GenresChanged; /// public event CollectionChangedEventHandler? ImagesChanged; /// public event CollectionChangedEventHandler? UrlsChanged; /// public event EventHandler? DatePublishedChanged; /// public ICore SourceCore { get; set; } /// /// The instance ID of the /// public string SourceCoreInstanceId { get; set; } /// public string Id { get; set; } /// [RemoteProperty] public string Name { get => _name; set { _name = value; NameChanged?.Invoke(this, value); } } /// [RemoteProperty] public string? Description { get => _description; set { _description = value; DescriptionChanged?.Invoke(this, value); } } /// [RemoteProperty] public PlaybackState PlaybackState { get => _playbackState; set { _playbackState = value; PlaybackStateChanged?.Invoke(this, value); } } /// [RemoteProperty] public TimeSpan Duration { get => _duration; set { _duration = value; DurationChanged?.Invoke(this, value); } } /// [RemoteProperty] public DateTime? LastPlayed { get => _lastPlayed; set { _lastPlayed = value; LastPlayedChanged?.Invoke(this, value); } } /// [RemoteProperty] public DateTime? AddedAt { get; set; } /// [RemoteProperty] public ICorePlayableCollectionGroup? RelatedItems { get; set; } /// [RemoteProperty] public DateTime? DatePublished { get => _datePublished; set { _datePublished = value; DatePublishedChanged?.Invoke(this, value); } } /// [RemoteProperty] public int TotalArtistItemsCount { get => _totalArtistItemsCount; set { _totalArtistItemsCount = value; ArtistItemsCountChanged?.Invoke(this, value); } } /// [RemoteProperty] public int TotalTrackCount { get => _totalTrackCount; set { _totalTrackCount = value; TracksCountChanged?.Invoke(this, value); } } /// [RemoteProperty] public int TotalImageCount { get => _totalImageCount; internal set { _totalImageCount = value; ImagesCountChanged?.Invoke(this, value); } } /// [RemoteProperty] public int TotalUrlCount { get => _totalUrlCount; internal set { _totalUrlCount = value; UrlsCountChanged?.Invoke(this, value); } } /// public int TotalGenreCount { get => _totalGenreCount; internal set { _totalGenreCount = value; GenresCountChanged?.Invoke(this, value); } } /// [RemoteProperty] public bool IsPlayArtistCollectionAsyncAvailable { get => _isPlayArtistCollectionAsyncAvailable; set { _isPlayArtistCollectionAsyncAvailable = value; IsPlayArtistCollectionAsyncAvailableChanged?.Invoke(this, value); } } /// [RemoteProperty] public bool IsPauseArtistCollectionAsyncAvailable { get => _isPauseArtistCollectionAsyncAvailable; set { _isPauseArtistCollectionAsyncAvailable = value; IsPauseArtistCollectionAsyncAvailableChanged?.Invoke(this, value); } } /// [RemoteProperty] public bool IsPlayTrackCollectionAsyncAvailable { get => _isPlayTrackCollectionAsyncAvailable; set { _isPlayTrackCollectionAsyncAvailable = value; IsPlayTrackCollectionAsyncAvailableChanged?.Invoke(this, value); } } /// [RemoteProperty] public bool IsPauseTrackCollectionAsyncAvailable { get => _isPauseTrackCollectionAsyncAvailable; set { _isPauseTrackCollectionAsyncAvailable = value; IsPauseTrackCollectionAsyncAvailableChanged?.Invoke(this, value); } } /// [RemoteProperty] public bool IsChangeNameAsyncAvailable { get => _isChangeNameAsyncAvailable; set { _isChangeNameAsyncAvailable = value; IsChangeNameAsyncAvailableChanged?.Invoke(this, value); } } /// [RemoteProperty] public bool IsChangeDescriptionAsyncAvailable { get => _isChangeDescriptionAsyncAvailable; set { _isChangeDescriptionAsyncAvailable = value; IsChangeDescriptionAsyncAvailableChanged?.Invoke(this, value); } } /// [RemoteProperty] public bool IsChangeDurationAsyncAvailable { get => _isChangeDurationAsyncAvailable; set { _isChangeDurationAsyncAvailable = value; IsChangeDurationAsyncAvailableChanged?.Invoke(this, value); } } /// [RemoteProperty] public bool IsChangeDatePublishedAsyncAvailable { get => _isChangeDatePublishedAsyncAvailable; set { _isChangeDatePublishedAsyncAvailable = value; IsChangeDatePublishedAsyncAvailableChanged?.Invoke(this, value); } } /// [RemoteMethod] public Task IsAddTrackAvailableAsync(int index, CancellationToken cancellationToken = default) => _memberRemote.ReceiveDataAsync(nameof(IsAddTrackAvailableAsync)); /// [RemoteMethod] public Task IsAddArtistItemAvailableAsync(int index, CancellationToken cancellationToken = default) => _memberRemote.ReceiveDataAsync(nameof(IsAddArtistItemAvailableAsync)); /// [RemoteMethod] public Task IsAddImageAvailableAsync(int index, CancellationToken cancellationToken = default) => _memberRemote.ReceiveDataAsync(nameof(IsAddImageAvailableAsync)); /// [RemoteMethod] public Task IsAddUrlAvailableAsync(int index, CancellationToken cancellationToken = default) => _memberRemote.ReceiveDataAsync(nameof(IsAddUrlAvailableAsync)); /// [RemoteMethod] public Task IsRemoveTrackAvailableAsync(int index, CancellationToken cancellationToken = default) => _memberRemote.ReceiveDataAsync(nameof(IsRemoveTrackAvailableAsync)); /// [RemoteMethod] public Task IsRemoveImageAvailableAsync(int index, CancellationToken cancellationToken = default) => _memberRemote.ReceiveDataAsync(nameof(IsRemoveImageAvailableAsync)); /// [RemoteMethod] public Task IsRemoveUrlAvailableAsync(int index, CancellationToken cancellationToken = default) => _memberRemote.ReceiveDataAsync(nameof(IsRemoveUrlAvailableAsync)); /// [RemoteMethod] public Task IsRemoveArtistItemAvailableAsync(int index, CancellationToken cancellationToken = default) => _memberRemote.ReceiveDataAsync(nameof(IsRemoveArtistItemAvailableAsync)); /// [RemoteMethod] public Task IsAddGenreAvailableAsync(int index, CancellationToken cancellationToken = default) => _memberRemote.ReceiveDataAsync(nameof(IsAddGenreAvailableAsync)); /// [RemoteMethod] public Task IsRemoveGenreAvailableAsync(int index, CancellationToken cancellationToken = default) => _memberRemote.ReceiveDataAsync(nameof(IsRemoveGenreAvailableAsync)); /// [RemoteMethod] public Task ChangeDescriptionAsync(string? description, CancellationToken cancellationToken = default) => _memberRemote.ReceiveDataAsync(nameof(ChangeDescriptionAsync)); /// [RemoteMethod] public Task ChangeDurationAsync(TimeSpan duration, CancellationToken cancellationToken = default) => _memberRemote.RemoteWaitAsync(nameof(ChangeDurationAsync)); /// [RemoteMethod] public Task ChangeNameAsync(string name, CancellationToken cancellationToken = default) => _memberRemote.RemoteWaitAsync(nameof(ChangeNameAsync)); /// [RemoteMethod] public Task ChangeDatePublishedAsync(DateTime datePublished, CancellationToken cancellationToken = default) => _memberRemote.RemoteWaitAsync(nameof(ChangeDatePublishedAsync)); /// A cancellation token that may be used to cancel the ongoing task. /// [RemoteMethod] public Task PauseArtistCollectionAsync(CancellationToken cancellationToken = default) => _memberRemote.RemoteWaitAsync(nameof(PauseArtistCollectionAsync)); /// A cancellation token that may be used to cancel the ongoing task. /// [RemoteMethod] public Task PlayArtistCollectionAsync(CancellationToken cancellationToken = default) => _memberRemote.RemoteWaitAsync(nameof(PlayArtistCollectionAsync)); /// [RemoteMethod] public Task PlayArtistCollectionAsync(ICoreArtistCollectionItem artistItem, CancellationToken cancellationToken = default) => _memberRemote.RemoteWaitAsync(nameof(PlayArtistCollectionAsync)); /// [RemoteMethod] public Task PlayTrackCollectionAsync(ICoreTrack track, CancellationToken cancellationToken = default) => _memberRemote.RemoteWaitAsync(nameof(PlayTrackCollectionAsync)); /// A cancellation token that may be used to cancel the ongoing task. /// [RemoteMethod] public Task PauseTrackCollectionAsync(CancellationToken cancellationToken = default) => _memberRemote.RemoteWaitAsync(nameof(PauseTrackCollectionAsync)); /// A cancellation token that may be used to cancel the ongoing task. /// [RemoteMethod] public Task PlayTrackCollectionAsync(CancellationToken cancellationToken = default) => _memberRemote.RemoteWaitAsync(nameof(PauseTrackCollectionAsync)); /// [RemoteMethod] public async IAsyncEnumerable GetArtistItemsAsync(int limit, int offset, [EnumeratorCancellation] CancellationToken cancellationToken = default) { using (await Flow.EasySemaphore(_getArtistsMutex)) { var res = await _memberRemote.ReceiveDataAsync>(nameof(GetTracksAsync)); if (res is null) yield break; foreach (var item in res) yield return item; } } /// [RemoteMethod] public async IAsyncEnumerable GetTracksAsync(int limit, int offset, [EnumeratorCancellation] CancellationToken cancellationToken = default) { using (await Flow.EasySemaphore(_getTracksMutex)) { var res = await _memberRemote.ReceiveDataAsync>(nameof(GetTracksAsync)); if (res is null) yield break; foreach (var item in res) yield return item; } } /// [RemoteMethod] public async IAsyncEnumerable GetImagesAsync(int limit, int offset, [EnumeratorCancellation] CancellationToken cancellationToken = default) { using (await Flow.EasySemaphore(_getImagesMutex)) { var res = await _memberRemote.ReceiveDataAsync>(nameof(GetImagesAsync)); if (res is null) yield break; foreach (var item in res) yield return item; } } /// [RemoteMethod] public async IAsyncEnumerable GetUrlsAsync(int limit, int offset, [EnumeratorCancellation] CancellationToken cancellationToken = default) { using (await Flow.EasySemaphore(_getUrlsMutex)) { var res = await _memberRemote.ReceiveDataAsync>(nameof(GetUrlsAsync)); if (res is null) yield break; foreach (var item in res) yield return item; } } /// [RemoteMethod] public async IAsyncEnumerable GetGenresAsync(int limit, int offset, [EnumeratorCancellation] CancellationToken cancellationToken = default) { using (await Flow.EasySemaphore(_getGenresMutex)) { var res = await _memberRemote.ReceiveDataAsync>(nameof(GetGenresAsync)); if (res is null) yield break; foreach (var item in res) yield return item; } } /// [RemoteMethod] public Task AddTrackAsync(ICoreTrack track, int index, CancellationToken cancellationToken = default) => _memberRemote.RemoteWaitAsync(nameof(AddTrackAsync)); /// [RemoteMethod] public Task AddArtistItemAsync(ICoreArtistCollectionItem artist, int index, CancellationToken cancellationToken = default) => _memberRemote.RemoteWaitAsync(nameof(AddArtistItemAsync)); /// [RemoteMethod] public Task AddImageAsync(ICoreImage image, int index, CancellationToken cancellationToken = default) => _memberRemote.RemoteWaitAsync(nameof(AddImageAsync)); /// [RemoteMethod] public Task AddUrlAsync(ICoreUrl image, int index, CancellationToken cancellationToken = default) => _memberRemote.RemoteWaitAsync(nameof(AddUrlAsync)); /// [RemoteMethod] public Task AddGenreAsync(ICoreGenre genre, int index, CancellationToken cancellationToken = default) => _memberRemote.RemoteWaitAsync(nameof(AddGenreAsync)); /// [RemoteMethod] public Task RemoveTrackAsync(int index, CancellationToken cancellationToken = default) => _memberRemote.RemoteWaitAsync(nameof(RemoveTrackAsync)); /// [RemoteMethod] public Task RemoveArtistItemAsync(int index, CancellationToken cancellationToken = default) => _memberRemote.RemoteWaitAsync(nameof(RemoveArtistItemAsync)); /// [RemoteMethod] public Task RemoveImageAsync(int index, CancellationToken cancellationToken = default) => _memberRemote.RemoteWaitAsync(nameof(RemoveImageAsync)); /// [RemoteMethod] public Task RemoveUrlAsync(int index, CancellationToken cancellationToken = default) => _memberRemote.RemoteWaitAsync(nameof(RemoveUrlAsync)); /// [RemoteMethod] public Task RemoveGenreAsync(int index, CancellationToken cancellationToken = default) => _memberRemote.RemoteWaitAsync(nameof(RemoveGenreAsync)); /// [RemoteMethod] public ValueTask DisposeAsync() => new ValueTask(Task.Run(async () => { await _memberRemote.RemoteWaitAsync(nameof(DisposeAsync)); _memberRemote.Dispose(); })); } }