diff --git a/src/Lavalink4NET.Tests/Players/QueuedLavalinkPlayerTests.cs b/src/Lavalink4NET.Tests/Players/QueuedLavalinkPlayerTests.cs index 9d8f9a7b..65149c91 100644 --- a/src/Lavalink4NET.Tests/Players/QueuedLavalinkPlayerTests.cs +++ b/src/Lavalink4NET.Tests/Players/QueuedLavalinkPlayerTests.cs @@ -78,7 +78,7 @@ public async Task TestPlayerIsStartedOnSkipAsync() Logger: NullLogger.Instance); var player = new QueuedLavalinkPlayer(playerProperties); - player.Queue.Enqueue(new TrackQueueItem(new TrackReference("track1"))); + await player.Queue.AddAsync(new TrackQueueItem(new TrackReference("track1"))); // Act await player.SkipAsync().ConfigureAwait(false); @@ -146,8 +146,8 @@ public async Task TestPlayerStartsSecondTrackIfNoneIsPlayingButCountIsTwoOnSkipA Logger: NullLogger.Instance); var player = new QueuedLavalinkPlayer(playerProperties); - player.Queue.Enqueue(new TrackQueueItem(new TrackReference("track1"))); - player.Queue.Enqueue(new TrackQueueItem(new TrackReference("track2"))); + await player.Queue.AddAsync(new TrackQueueItem(new TrackReference("track1"))); + await player.Queue.AddAsync(new TrackQueueItem(new TrackReference("track2"))); // Act await player.SkipAsync(count: 2).ConfigureAwait(false); @@ -214,7 +214,7 @@ public async Task TestPlayerStopsOnSkipIfQueueIsEmptyAsync() Logger: NullLogger.Instance); var player = new QueuedLavalinkPlayer(playerProperties); - player.Queue.Enqueue(new TrackQueueItem(new TrackReference("track1"))); + await player.Queue.AddAsync(new TrackQueueItem(new TrackReference("track1"))); // Act await player.SkipAsync(count: 2).ConfigureAwait(false); @@ -284,7 +284,7 @@ public async Task TestPlayerPlaysNextAfterTrackEndAsync() Logger: NullLogger.Instance); var player = new QueuedLavalinkPlayer(playerProperties); - player.Queue.Enqueue(new TrackQueueItem(new TrackReference("track2"))); + await player.Queue.AddAsync(new TrackQueueItem(new TrackReference("track2"))); var listener = (ILavalinkPlayerListener)player; @@ -361,7 +361,7 @@ public async Task TestPlayerRepeatsTrackIfRepeatModeIsTrackAsync() var player = new QueuedLavalinkPlayer(playerProperties); player.RepeatMode = TrackRepeatMode.Track; - player.Queue.Enqueue(new TrackQueueItem(new TrackReference("track2"))); + await player.Queue.AddAsync(new TrackQueueItem(new TrackReference("track2"))); var listener = (ILavalinkPlayerListener)player; @@ -438,7 +438,7 @@ public async Task TestPlayerRepeatsTrackIfRepeatModeIsTrackOnSkipAsync() var player = new QueuedLavalinkPlayer(playerProperties); player.RepeatMode = TrackRepeatMode.Track; - player.Queue.Enqueue(new TrackQueueItem(new TrackReference("track2"))); + await player.Queue.AddAsync(new TrackQueueItem(new TrackReference("track2"))); // Act await player diff --git a/src/Lavalink4NET.Tests/Players/TrackQueueTests.cs b/src/Lavalink4NET.Tests/Players/TrackQueueTests.cs deleted file mode 100644 index ed0f48b6..00000000 --- a/src/Lavalink4NET.Tests/Players/TrackQueueTests.cs +++ /dev/null @@ -1,1145 +0,0 @@ -namespace Lavalink4NET.Tests.Players; - -using System; -using System.Collections.Generic; -using System.Linq; -using Lavalink4NET.Players; -using Lavalink4NET.Players.Queued; -using Xunit; - -/// -/// This class contains unit tests for the class. -/// -public sealed class TrackQueueTests -{ - [Fact] - public void CopyConstructorWithNullQueue() - { - static void Test() => _ = new TrackQueue(null!); - Assert.Throws(Test); - } - - /// - /// Tests the method of the class. - /// - [Fact] - public void TestArrayCopyTo() - { - var queue = new TrackQueue(); - var item = GetDummyTrack(); - queue.Enqueue(item); - - var array = new ITrackQueueItem[1]; - queue.CopyTo(array); - - Assert.Equal(item, array[0]); - } - - /// - /// Tests the method of the class with passing a array which should - /// throw an . - /// - [Fact] - public void TestArrayCopyToWithNullArrayShouldThrow() - { - static void Test() - { - var queue = new TrackQueue(); - queue.CopyTo(null!); - } - - Assert.Throws(Test); - } - - /// - /// Tests the method of the class with specifying a copy offset. - /// - [Fact] - public void TestArrayCopyToWithOffset() - { - var queue = new TrackQueue(); - var item = GetDummyTrack(); - queue.Enqueue(item); - - var array = new ITrackQueueItem[2]; - queue.CopyTo(array, 1); - - Assert.Null(array[0]); - Assert.Equal(item, array[1]); - } - - /// - /// Tests the method of the class with passing an array which can not hold the tracks in the - /// queue which should thrown an . - /// - [Fact] - public void TestArrayCopyToWithTooSmallArrayShouldThrow() - { - static void Test() - { - var queue = new TrackQueue(); - queue.Enqueue(GetDummyTrack()); - queue.CopyTo(Array.Empty()); - } - - Assert.Throws(Test); - } - - /// - /// Tests the constructor of the class - /// with specifying a history. - /// - [Fact] - public void TestCanCreateWithHistory() - { - var queue = new TrackQueue(historyCapacity: 10); - Assert.True(queue.HasHistory); - } - - /// - /// Tests the constructor of the class - /// without specifying a history. - /// - [Fact] - public void TestCanCreateWithoutHistory() - { - var queue = new TrackQueue(historyCapacity: 0); - Assert.False(queue.HasHistory); - } - - [Fact] - public void TestClearHistoryWhenDisabled() - { - var queue = new TrackQueue(historyCapacity: 0); - Assert.Equal(0, queue.ClearHistory()); - } - - [Fact] - public void TestClone() - { - var queue = new TrackQueue(); - var copiedQueue = queue.Clone(); - - Assert.NotSame(queue, copiedQueue); - } - - /// - /// Tests the method of the class. - /// - [Fact] - public void TestContains() - { - var queue = new TrackQueue(); - var item = GetDummyTrack(); - Assert.DoesNotContain(item, queue); - queue.Enqueue(item); - Assert.Contains(item, queue); - } - - /// - /// Tests the method of the class with passing a item which should - /// throw an . - /// - [Fact] - public void TestContainsWithNullItemShouldThrow() - { - static void Test() - { - var queue = new TrackQueue(); - queue.Enqueue(GetDummyTrack()); - queue.Contains(null!); - } - - Assert.Throws(Test); - } - - /// - /// Tests the constructor of the class - /// with passing an invalid history capacity. - /// - [Fact] - public void TestCreateWithInvalidHistoryCapacityShouldThrow() - { - static void Test() => _ = new TrackQueue(historyCapacity: -1); - Assert.Throws(Test); - } - - /// - /// Tests the constructor of the class - /// with passing an invalid initial capacity. - /// - [Fact] - public void TestCreateWithInvalidInitialCapacityShouldThrow() - { - static void Test() => _ = new TrackQueue(initialCapacity: -1); - Assert.Throws(Test); - } - - /// - /// Tests the method of the - /// class with items in the queue. - /// - [Fact] - public void TestDequeueWithItems() - { - var queue = new TrackQueue(); - - var track = GetDummyTrack(); - queue.Enqueue(track); - Assert.Equal(track, queue.Dequeue()); - } - - [Fact] - public void TestDequeueWithShuffleReturnsRandomElement() - { - var queue = new TrackQueue(); - var item = GetDummyTrack(); - - queue.Enqueue(item); - - queue.EnqueueRange( - Enumerable.Repeat(null, 50) - .Select(x => GetDummyTrack())); - - for (var index = 0; index < 10; index++) - { - var result = queue.Dequeue(true); - - if (!result!.Equals(item)) - { - return; // success - } - - // retry - queue.Insert(0, item); - } - - Assert.True(false, "Test failed after multiple tries."); - } - - /// - /// Tests the method of the - /// class with different items in the queue. - /// - [Fact] - public void TestDistinctWithDifferentElements() - { - var queue = new TrackQueue(); - queue.Enqueue(GetDummyTrack()); - queue.Enqueue(GetDummyTrack()); - - queue.Distinct(); - Assert.Equal(2, queue.Count); - } - - [Fact] - public void TestDistinctWithoutMultipleItems() - { - var queue = new TrackQueue(historyCapacity: 0); - Assert.Equal(0, queue.Distinct()); - } - - /// - /// Tests the method of the - /// class with the same items in the queue. - /// - [Fact] - public void TestDistinctWithSameElements() - { - var queue = new TrackQueue(); - var track = GetDummyTrack(); - - queue.Enqueue(track); - queue.Enqueue(track); - - queue.Distinct(); - Assert.Single(queue); - } - - /// - /// Tests the method of the class. - /// - [Fact] - public void TestEnqueue() - { - var queue = new TrackQueue(); - Assert.Equal(0, queue.Enqueue(GetDummyTrack())); - Assert.Equal(1, queue.Enqueue(GetDummyTrack())); - Assert.Equal(2, queue.Enqueue(GetDummyTrack())); - } - - /// - /// Tests the method of the - /// class. - /// - [Fact] - public void TestEnqueueRange() - { - var queue = new TrackQueue(); - queue.EnqueueRange(GetDummyTrack(), GetDummyTrack()); - Assert.Equal(2, queue.Count); - } - - /// - /// Tests the method of the - /// class with passing a array which - /// should throw an . - /// - [Fact] - public void TestEnqueueRangeWithNullArrayShouldThrow() - { - static void Test() - { - var queue = new TrackQueue(); -#pragma warning disable S3220 // Method calls should not resolve ambiguously to overloads with "params" - queue.EnqueueRange(items: null!); -#pragma warning restore S3220 // Method calls should not resolve ambiguously to overloads with "params" - } - - Assert.Throws(Test); - } - - /// - /// Tests the method - /// of the class with passing a - /// enumerable which should throw an . - /// - [Fact] - public void TestEnqueueRangeWithNullEnumerableShouldThrow() - { - static void Test() - { - var queue = new TrackQueue(); - queue.EnqueueRange(items: (IEnumerable)null!); - } - - Assert.Throws(Test); - } - - /// - /// Tests the method of the - /// class with passing an array that contains a reference for an item which should throw an . - /// - [Fact] - public void TestEnqueueRangeWithNullItemInArrayShouldThrow() - { - static void Test() - { - var queue = new TrackQueue(); -#pragma warning disable S3878 // Arrays should not be created for params parameters - queue.EnqueueRange(items: new ITrackQueueItem[] { GetDummyTrack(), null! }); -#pragma warning restore S3878 // Arrays should not be created for params parameters - } - - Assert.Throws(Test); - } - - /// - /// Tests the method - /// of the class with passing an enumerable that contains a - /// reference for an item which should throw an . - /// - [Fact] - public void TestEnqueueRangeWithNullItemInEnumerableShouldThrow() - { - static void Test() - { - var queue = new TrackQueue(); - queue.EnqueueRange(items: (IEnumerable)new ITrackQueueItem[] { GetDummyTrack(), null! }); - } - - Assert.Throws(Test); - } - - /// - /// Tests the method of the class with passing a item which should - /// throw a . - /// - [Fact] - public void TestEnqueueWithNullItemShouldThrow() - { - static void Test() - { - var queue = new TrackQueue(); - queue.Enqueue(null!); - } - - Assert.Throws(Test); - } - - [Fact] - public void TestGetHistoryWhenDisabled() - { - var queue = new TrackQueue(historyCapacity: 0); - Assert.False(queue.HasHistory); - Assert.Empty(queue.History); - } - - /// - /// Tests the indexer of the class with passing a negative index which should throw an . - /// - [Fact] - public void TestGetQueueItemWithNegativeIndexShouldThrow() - { - static void Test() => _ = new TrackQueue()[-1]; - Assert.Throws(Test); - } - - /// - /// Tests the indexer of the class with passing a non-existent index (out of range) which - /// should throw an . - /// - [Fact] - public void TestGetQueueItemWithNotExistientIndexShouldThrow() - { - static void Test() => _ = new TrackQueue()[0]; - Assert.Throws(Test); - } - - /// - /// Tests the method of the class. - /// - [Fact] - public void TestHistoryEmptyAfterClear() - { - var queue = new TrackQueue(); - Assert.Empty(queue); - - var track = GetDummyTrack(); - queue.Enqueue(track); - Assert.Equal(track, queue.Dequeue()); - - Assert.NotEmpty(queue.History); - queue.ClearHistory(); - Assert.Empty(queue.History); - } - - /// - /// Tests the constructor of the class - /// with checking that the queue is initially empty. - /// - [Fact] - public void TestHistoryInitialEmpty() - { - var queue = new TrackQueue(); - Assert.Empty(queue.History); - } - - /// - /// Tests the method of the - /// class with checking that the queue's history is not empty after dequeuing an item. - /// - [Fact] - public void TestHistoryNotEmptyAfterDequeue() - { - var queue = new TrackQueue(); - queue.Enqueue(GetDummyTrack()); - queue.Dequeue(); - Assert.Single(queue.History); - } - - /// - /// Tests the property of the class with checking that the history size increases when an item - /// is added to the history. - /// - [Fact] - public void TestHistorySizeIncrementingWhenTrackIsDequeued() - { - var queue = new TrackQueue(); - Assert.Equal(0, queue.Enqueue(GetDummyTrack())); - Assert.Equal(0, queue.HistorySize); - - queue.Dequeue(); - Assert.Equal(1, queue.HistorySize); - } - - /// - /// Tests the property of the class with checking that the history size is limited to the - /// specified size. - /// - [Fact] - public void TestHistorySizeIsLimited() - { - var queue = new TrackQueue(historyCapacity: 4); - - for (var index = 0; index < 8; index++) - { - queue.Enqueue(GetDummyTrack()); - queue.Dequeue(); - } - - Assert.Equal(4, queue.HistorySize); - Assert.Empty(queue); - } - - [Fact] - public void TestHistorySizeWhenDisabled() - { - var queue = new TrackQueue(historyCapacity: 0); - Assert.Equal(0, queue.HistorySize); - } - - [Fact] - public void TestICollectionAdd() - { - var queue = new TrackQueue(); - var item = GetDummyTrack(); - ((ICollection)queue).Add(item); - Assert.Single(queue); - } - - [Fact] - public void TestICollectionAddWithNullItemThrows() - { - var queue = new TrackQueue(); - void Test() => ((ICollection)queue).Add(null!); - Assert.Throws(Test); - } - - [Fact] - public void TestICollectionClear() - { - var queue = new TrackQueue(); - var item = GetDummyTrack(); - queue.Enqueue(item); - ((ICollection)queue).Clear(); - Assert.Empty(queue); - } - - /// - /// Tests the method of the class. - /// - [Fact] - public void TestInsertQueue() - { - var queue = new TrackQueue(); - queue.Insert(0, GetDummyTrack()); - Assert.NotEmpty(queue); - } - - /// - /// Tests the method of the class with passing a negative index which should throw a . - /// - [Fact] - public void TestInsertQueueItemWithNegativeIndexShouldThrow() - { - static void Test() => new TrackQueue().Insert(-1, GetDummyTrack()); - Assert.Throws(Test); - } - - /// - /// Tests the method of the class with passing a reference for the - /// item which should throw a . - /// - [Fact] - public void TestInsertQueueItemWithNullItemShouldThrow() - { - static void Test() => new TrackQueue().Insert(0, null!); - Assert.Throws(Test); - } - - /// - /// Tests the method of the - /// class. - /// - [Fact] - public void TestInsertRange() - { - var queue = new TrackQueue(); - queue.InsertRange(0, GetDummyTrack(), GetDummyTrack()); - Assert.Equal(2, queue.Count); - } - - /// - /// Tests the method of the - /// class with specifying a reference - /// for the array which should throw an . - /// - [Fact] - public void TestInsertRangeWithNullArrayShouldThrow() - { - static void Test() - { - var queue = new TrackQueue(); -#pragma warning disable S3220 // Method calls should not resolve ambiguously to overloads with "params" - queue.InsertRange(0, items: null!); -#pragma warning restore S3220 // Method calls should not resolve ambiguously to overloads with "params" - } - - Assert.Throws(Test); - } - - /// - /// Tests the - /// method of the class with specifying a reference for the enumerable which should throw an . - /// - [Fact] - public void TestInsertRangeWithNullEnumerableShouldThrow() - { - static void Test() - { - var queue = new TrackQueue(); - queue.InsertRange(0, items: (IEnumerable)null!); - } - - Assert.Throws(Test); - } - - /// - /// Tests the method of the - /// class with specifying a reference in - /// the array for an item which should throw an . - /// - [Fact] - public void TestInsertRangeWithNullItemInArrayShouldThrow() - { - static void Test() - { - var queue = new TrackQueue(); -#pragma warning disable S3878 // Arrays should not be created for params parameters - queue.InsertRange(0, items: new ITrackQueueItem[] { GetDummyTrack(), null! }); -#pragma warning restore S3878 // Arrays should not be created for params parameters - } - - Assert.Throws(Test); - } - - /// - /// Tests the - /// method of the class with specifying a reference in the enumerable for an item which should throw an . - /// - [Fact] - public void TestInsertRangeWithNullItemInEnumerableShouldThrow() - { - static void Test() - { - var queue = new TrackQueue(); - queue.InsertRange(0, items: (IEnumerable)new ITrackQueueItem[] { GetDummyTrack(), null! }); - } - - Assert.Throws(Test); - } - - [Fact] - public void TestPeekWithItems() - { - var queue = new TrackQueue(); - - var track = GetDummyTrack(); - queue.Enqueue(track); - Assert.Equal(track, queue.Peek()); - } - - [Fact] - public void TestPeekWithNoItems() - { - var queue = new TrackQueue(); - Assert.Null(queue.Peek()); - } - - /// - /// Tests the property of the class. - /// - [Fact] - public void TestQueueCountOnEmptyQueue() - { - var queue = new TrackQueue(); -#pragma warning disable xUnit2013 // Do not use equality check to check for collection size. - Assert.Equal(0, queue.Count); -#pragma warning restore xUnit2013 // Do not use equality check to check for collection size. - } - - /// - /// Tests the property of the class. - /// - [Fact] - public void TestQueueCountOnQueueWithItems() - { - var queue = new TrackQueue(); - queue.Enqueue(GetDummyTrack()); - queue.Enqueue(GetDummyTrack()); - - Assert.Equal(2, queue.Count); - queue.Dequeue(); - Assert.Single(queue); - queue.Dequeue(); - Assert.Empty(queue); - } - - /// - /// Tests the method of the - /// class on an empty queue which should throw an . - /// - [Fact] - public void TestQueueDequeueThrowsIfEmpty() - { - var queue = new TrackQueue(); - void Test() => queue.Dequeue(); - Assert.Throws(Test); - } - - /// - /// Tests the property of the - /// class after adding and removing items. - /// - [Fact] - public void TestQueueEmptyAfterClear() - { - var queue = new TrackQueue(); - Assert.Empty(queue); - - var track = GetDummyTrack(); - queue.Enqueue(track); - - Assert.NotEmpty(queue); - queue.Clear(); - Assert.Empty(queue); - } - - [Fact] - public void TestQueueEmptyIfEmpty() - { - var queue = new TrackQueue(); - Assert.True(queue.IsEmpty); - } - - /// - /// Tests the property of the - /// class on a fresh initialized queue. - /// - [Fact] - public void TestQueueEmptyOnEmptyQueue() - { - var queue = new TrackQueue(); - Assert.Empty(queue); - } - - /// - /// Tests the property of the - /// class on a queue with inserted items. - /// - [Fact] - public void TestQueueEmptyOnQueueWithItems() - { - var queue = new TrackQueue(); - - Assert.Empty(queue); - queue.Enqueue(GetDummyTrack()); - Assert.Single(queue); - queue.Dequeue(); - Assert.Empty(queue); - } - - /// - /// Tests the method of the class. - /// - [Fact] - public void TestQueueIndexOf() - { - var track1 = GetDummyTrack(); - var track2 = GetDummyTrack(); - var queue = new TrackQueue(); - - queue.Enqueue(track1); - queue.Enqueue(track2); - - Assert.Equal(0, queue.IndexOf(track1)); - Assert.Equal(1, queue.IndexOf(track2)); - } - - /// - /// Tests the method of the class with passing an item that is not in the queue. - /// - [Fact] - public void TestQueueIndexOfNotExisting() - { - var track1 = GetDummyTrack(); - var track2 = GetDummyTrack(); - var queue = new TrackQueue(); - - queue.Enqueue(track1); - Assert.Equal(-1, queue.IndexOf(track2)); - } - - /// - /// Tests the method of the class with passing a reference for the - /// item which should throw an . - /// - [Fact] - public void TestQueueIndexOfShouldThrowIfNull() - { - static void Test() => new TrackQueue().IndexOf(null!); - Assert.Throws(Test); - } - - [Fact] - public void TestQueueIsReadOnlyAlwaysFalse() - { - var queue = new TrackQueue(); - Assert.False(((ICollection)queue).IsReadOnly); - } - - [Fact] - public void TestRemoveAll() - { - var queue = new TrackQueue(); - var track = GetDummyTrack(); - - queue.EnqueueRange(track, GetDummyTrack(), GetDummyTrack()); - - Assert.Equal(1, queue.RemoveAll(x => x == (ITrackQueueItem)track)); - Assert.Equal(2, queue.Count); - } - - [Fact] - public void TestRemoveAllWithNullPredicatePassedThrows() - { - static void Test() - { - var queue = new TrackQueue(); - queue.RemoveAll(null!); - } - - Assert.Throws(Test); - } - - /// - /// Tests the method of the class. - /// - [Fact] - public void TestRemoveAt() - { - var queue = new TrackQueue(); - queue.Enqueue(GetDummyTrack()); - queue.RemoveAt(0); - Assert.Empty(queue); - } - - /// - /// Tests the method of the class with passing a negative index which should throw an . - /// - [Fact] - public void TestRemoveAtWithNegativeIndexShouldThrow() - { - static void Test() - { - var queue = new TrackQueue(); - queue.Enqueue(GetDummyTrack()); - queue.RemoveAt(-1); - } - - Assert.Throws(Test); - } - - [Fact] - public void TestRemoveRange() - { - var queue = new TrackQueue(); - - queue.EnqueueRange(GetDummyTrack(), GetDummyTrack(), GetDummyTrack(), GetDummyTrack()); - queue.RemoveRange(2, 2); - - Assert.Equal(2, queue.Count); - } - - /// - /// Tests the method of the class with removing an existing item. - /// - [Fact] - public void TestRemoveWithExistientItem() - { - var track = GetDummyTrack(); - var queue = new TrackQueue(); - queue.Enqueue(track); - Assert.True(queue.Remove(track)); - } - - /// - /// Tests the method of the class with removing a non-existing item. - /// - [Fact] - public void TestRemoveWithNonExistientItem() - { - var queue = new TrackQueue(); - Assert.False(queue.Remove(GetDummyTrack())); - } - - /// - /// Tests the method of the class with specifying a reference for the - /// item to remove which should throw an . - /// - [Fact] - public void TestRemoveWithNullItemShouldThrow() - { - static void Test() - { - var queue = new TrackQueue(); - queue.Remove(null!); - } - - Assert.Throws(Test); - } - - /// - /// Tests the indexer of the class on an existing item. - /// - [Fact] - public void TestSetQueueItemWithExistentIndex() - { - var item = GetDummyTrack(); - var queue = new TrackQueue(); - queue.Enqueue(item); - queue[0] = item; - Assert.Equal(item, queue[0]); - } - - /// - /// Tests the indexer of the class specifying a negative index which should throw an . - /// - [Fact] - public void TestSetQueueItemWithNegativeIndexShouldThrow() - { - static void Test() => new TrackQueue()[-1] = GetDummyTrack(); - Assert.Throws(Test); - } - - /// - /// Tests the indexer of the class specifying a non-existent index which should throw an . - /// - [Fact] - public void TestSetQueueItemWithNotExistientIndex() - { - static void Test() => new TrackQueue()[0] = GetDummyTrack(); - Assert.Throws(Test); - } - - /// - /// Tests the indexer of the class with trying to set a reference for - /// an existent index which should throw an . - /// - [Fact] - public void TestSetQueueWithNullItemWithItemsShouldThrow() - { - static void Test() - { - var queue = new TrackQueue(); - queue.Enqueue(GetDummyTrack()); - queue[0] = null!; - } - - Assert.Throws(Test); - } - - /// - /// Tests the method of the class. - /// - [Fact] - public void TestShuffle() - { - var queue = new TrackQueue(); - - for (var index = 0; index < 100; index++) - { - queue.Enqueue(GetDummyTrack()); - } - - var shuffledQueue = queue.Clone(); - - shuffledQueue.Shuffle(); - Assert.False(queue.SequenceEqual(shuffledQueue)); - } - - [Fact] - public void TestShuffleThrowsIfIndexAndCountIsOutOfRangePassed() - { - static void Test() - { - var queue = new TrackQueue(); - queue.Shuffle(1, 1); - } - - Assert.Throws(Test); - } - - [Fact] - public void TestShuffleThrowsIfNegativeCountPassed() - { - static void Test() - { - var queue = new TrackQueue(); - queue.Shuffle(0, -1); - } - - Assert.Throws(Test); - } - - [Fact] - public void TestShuffleThrowsIfNegativeIndexPassed() - { - static void Test() - { - var queue = new TrackQueue(); - queue.Shuffle(-1, 0); - } - - Assert.Throws(Test); - } - - [Fact] - public void TestTryDequeueWithHistoryCutOff() - { - var queue = new TrackQueue(); - - queue.EnqueueRange( - Enumerable.Repeat(null, 50) - .Select(x => GetDummyTrack())); - - while (!queue.IsEmpty) - { - queue.TryDequeue(out _); - } - - Assert.Equal(queue.HistoryCapacity, queue.HistorySize); - } - - [Fact] - public void TestTryDequeueWithItem() - { - var queue = new TrackQueue(historyCapacity: 0); - var item = GetDummyTrack(); - - queue.Enqueue(item); - - Assert.True(queue.TryDequeue(out var result)); - Assert.Equal(item, result); - } - - /// - /// Tests the method of the - /// class without any items. - /// - [Fact] - public void TestTryDequeueWithoutItems() - { - var queue = new TrackQueue(); - Assert.False(queue.TryDequeue(out var _)); - } - - [Fact] - public void TestTryDequeueWithShuffleReturnsRandomElement() - { - var queue = new TrackQueue(); - var item = GetDummyTrack(); - - queue.Enqueue(item); - - queue.EnqueueRange( - Enumerable.Repeat(null, 50) - .Select(x => GetDummyTrack())); - - for (var index = 0; index < 10; index++) - { - queue.TryDequeue(true, out var result); - - if (!result!.Equals(item)) - { - return; // success - } - - // retry - queue.Insert(0, item); - } - - Assert.True(false, "Test failed after multiple tries."); - } - - [Fact] - public void TestTryDequeueWithShuffleReturnsRandomElementWithoutHistory() - { - var queue = new TrackQueue(historyCapacity: 0); - var item = GetDummyTrack(); - - queue.Enqueue(item); - - queue.EnqueueRange( - Enumerable.Repeat(null, 50) - .Select(x => GetDummyTrack())); - - for (var index = 0; index < 10; index++) - { - queue.TryDequeue(true, out var result); - - if (!result!.Equals(item)) - { - return; // success - } - - // retry - queue.Insert(0, item); - } - - Assert.True(false, "Test failed after multiple tries."); - } - - [Fact] - public void TestTryPeekWithItems() - { - var queue = new TrackQueue(); - - var track = GetDummyTrack(); - queue.Enqueue(track); - - Assert.True(queue.TryPeek(out var peekedTrack)); - Assert.Equal(track, peekedTrack); - } - - [Fact] - public void TestTryPeekWithNoItems() - { - var queue = new TrackQueue(); - Assert.False(queue.TryPeek(out _)); - } - /// - /// Gets a dummy track used for testing. - /// - /// a dummy track used for testing - private static TrackQueueItem GetDummyTrack() - { - var data = new byte[20]; - Random.Shared.NextBytes(data); - - var value = Convert.ToBase64String(data); - return new(new TrackReference(value)); - } -} \ No newline at end of file diff --git a/src/Lavalink4NET/Players/Queued/ITrackCollection.cs b/src/Lavalink4NET/Players/Queued/ITrackCollection.cs new file mode 100644 index 00000000..c86a6c18 --- /dev/null +++ b/src/Lavalink4NET/Players/Queued/ITrackCollection.cs @@ -0,0 +1,29 @@ +namespace Lavalink4NET.Players.Queued; + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +public interface ITrackCollection : IReadOnlyList +{ + bool IsEmpty { get; } + + bool Contains(ITrackQueueItem item); + + ValueTask RemoveAtAsync(int index, CancellationToken cancellationToken = default); + + ValueTask RemoveAsync(ITrackQueueItem item, CancellationToken cancellationToken = default); + + ValueTask RemoveAllAsync(Predicate predicate, CancellationToken cancellationToken = default); + + ValueTask RemoveRangeAsync(int index, int count, CancellationToken cancellationToken = default); + + ValueTask DistinctAsync(IEqualityComparer? equalityComparer = null, CancellationToken cancellationToken = default); + + ValueTask AddAsync(ITrackQueueItem item, CancellationToken cancellationToken = default); + + ValueTask AddRangeAsync(IReadOnlyList items, CancellationToken cancellationToken = default); + + ValueTask ClearAsync(CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/Lavalink4NET/Players/Queued/ITrackHistory.cs b/src/Lavalink4NET/Players/Queued/ITrackHistory.cs new file mode 100644 index 00000000..c778b480 --- /dev/null +++ b/src/Lavalink4NET/Players/Queued/ITrackHistory.cs @@ -0,0 +1,6 @@ +namespace Lavalink4NET.Players.Queued; + +public interface ITrackHistory : ITrackCollection +{ + int? Capacity { get; } +} diff --git a/src/Lavalink4NET/Players/Queued/ITrackQueue.cs b/src/Lavalink4NET/Players/Queued/ITrackQueue.cs new file mode 100644 index 00000000..11c9db8e --- /dev/null +++ b/src/Lavalink4NET/Players/Queued/ITrackQueue.cs @@ -0,0 +1,27 @@ +namespace Lavalink4NET.Players.Queued; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +public interface ITrackQueue : ITrackCollection +{ + ValueTask InsertAsync(int index, ITrackQueueItem item, CancellationToken cancellationToken = default); + + ValueTask InsertRangeAsync(int index, IEnumerable items, CancellationToken cancellationToken = default); + + ValueTask ShuffleAsync(CancellationToken cancellationToken = default); + + ITrackHistory? History { get; } + + [MemberNotNullWhen(true, nameof(History))] + bool HasHistory { get; } + + ITrackQueueItem? Peek(); + + bool TryPeek([MaybeNullWhen(false)] out ITrackQueueItem? queueItem); + + ValueTask TryDequeueAsync( + TrackDequeueMode dequeueMode = TrackDequeueMode.Normal, + CancellationToken cancellationToken = default); +} diff --git a/src/Lavalink4NET/Players/Queued/ITrackQueueProvider.cs b/src/Lavalink4NET/Players/Queued/ITrackQueueProvider.cs new file mode 100644 index 00000000..397b51a5 --- /dev/null +++ b/src/Lavalink4NET/Players/Queued/ITrackQueueProvider.cs @@ -0,0 +1,12 @@ +namespace Lavalink4NET.Players.Queued; + +internal interface ITrackQueueProvider +{ + int Count { get; } + + void Add(ITrackQueueItem item); + + void Clear(); + + +} diff --git a/src/Lavalink4NET/Players/Queued/QueuedLavalinkPlayer.cs b/src/Lavalink4NET/Players/Queued/QueuedLavalinkPlayer.cs index ddb08ee3..babcb3c2 100644 --- a/src/Lavalink4NET/Players/Queued/QueuedLavalinkPlayer.cs +++ b/src/Lavalink4NET/Players/Queued/QueuedLavalinkPlayer.cs @@ -15,6 +15,7 @@ public class QueuedLavalinkPlayer : LavalinkPlayer { private readonly bool _disconnectOnStop; private readonly bool _clearQueueOnStop; + private readonly bool _clearHistoryOnStop; private readonly bool _resetTrackRepeatOnStop; private readonly bool _resetShuffleOnStop; private readonly TrackRepeatMode _defaultTrackRepeatMode; @@ -29,15 +30,14 @@ public QueuedLavalinkPlayer(IPlayerProperties /// Gets the track queue. /// - public TrackQueue Queue { get; } + public ITrackQueue Queue { get; } /// /// Gets or sets the loop mode for this player. @@ -64,10 +64,9 @@ public async ValueTask PlayAsync(ITrackQueueItem queueItem, bool enqueue = if (enqueue && (Queue.Count > 0 || State == PlayerState.Playing || State == PlayerState.Paused)) { // add the track to the queue - Queue.Enqueue(queueItem); - - // return track queue position - return Queue.Count; + return await Queue + .AddAsync(queueItem, cancellationToken) + .ConfigureAwait(false); } // play the track immediately @@ -115,7 +114,7 @@ public override async ValueTask PlayAsync(TrackReference trackReference, TrackPl /// the number of tracks to skip /// a task that represents the asynchronous operation /// thrown if the player is destroyed - public virtual ValueTask SkipAsync(int count = 1, CancellationToken cancellationToken = default) + public virtual async ValueTask SkipAsync(int count = 1, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); EnsureNotDestroyed(); @@ -128,25 +127,37 @@ public virtual ValueTask SkipAsync(int count = 1, CancellationToken cancellation "The count must not be negative."); } - var track = GetNextTrack(count); + var track = await GetNextTrackAsync(count, cancellationToken).ConfigureAwait(false); if (!track.IsPresent) { // Do nothing, stop - return StopAsync(_disconnectOnStop, cancellationToken); + await StopAsync(_disconnectOnStop, cancellationToken).ConfigureAwait(false); + return; } - return base.PlayAsync(track.Value.Track, properties: default, cancellationToken); + await base + .PlayAsync(track.Value.Track, properties: default, cancellationToken) + .ConfigureAwait(false); } - public override ValueTask StopAsync(bool disconnect = false, CancellationToken cancellationToken = default) + public override async ValueTask StopAsync(bool disconnect = false, CancellationToken cancellationToken = default) { EnsureNotDestroyed(); cancellationToken.ThrowIfCancellationRequested(); if (_clearQueueOnStop) { - Queue.Clear(); + await Queue + .ClearAsync(cancellationToken) + .ConfigureAwait(false); + } + + if (_clearHistoryOnStop && Queue.HasHistory) + { + await Queue.History + .ClearAsync(cancellationToken) + .ConfigureAwait(false); } if (_resetTrackRepeatOnStop) @@ -159,7 +170,9 @@ public override ValueTask StopAsync(bool disconnect = false, CancellationToken c Shuffle = false; } - return base.StopAsync(disconnect, cancellationToken); + await base + .StopAsync(disconnect, cancellationToken) + .ConfigureAwait(false); } protected override ValueTask OnTrackEndedAsync(LavalinkTrack track, TrackEndReason endReason, CancellationToken cancellationToken = default) @@ -175,8 +188,10 @@ protected override ValueTask OnTrackEndedAsync(LavalinkTrack track, TrackEndReas return ValueTask.CompletedTask; } - private Optional GetNextTrack(int count = 1) + private async ValueTask> GetNextTrackAsync(int count = 1, CancellationToken cancellationToken = default) { + cancellationToken.ThrowIfCancellationRequested(); + var track = default(Optional); if (RepeatMode is TrackRepeatMode.Track) @@ -186,29 +201,45 @@ private Optional GetNextTrack(int count = 1) : new Optional(new TrackQueueItem(new TrackReference(CurrentTrack))); } + var dequeueMode = Shuffle + ? TrackDequeueMode.Shuffle + : TrackDequeueMode.Normal; + while (count-- > 1) { - if (!Queue.TryDequeue(out var peekedTrack)) + var peekedTrack = await Queue + .TryDequeueAsync(dequeueMode, cancellationToken) + .ConfigureAwait(false); + + if (peekedTrack is null) { break; } if (RepeatMode is TrackRepeatMode.Queue) { - Queue.Enqueue(peekedTrack); + await Queue + .AddAsync(peekedTrack, cancellationToken) + .ConfigureAwait(false); } } if (count >= 0) { - if (!Queue.TryDequeue(shuffle: Shuffle, out var peekedTrack)) + var peekedTrack = await Queue + .TryDequeueAsync(dequeueMode, cancellationToken) + .ConfigureAwait(false); + + if (peekedTrack is null) { return Optional.Default; // do nothing } if (RepeatMode is TrackRepeatMode.Queue) { - Queue.Enqueue(peekedTrack); + await Queue + .AddAsync(peekedTrack, cancellationToken) + .ConfigureAwait(false); } track = new Optional(peekedTrack); diff --git a/src/Lavalink4NET/Players/Queued/QueuedLavalinkPlayerOptions.cs b/src/Lavalink4NET/Players/Queued/QueuedLavalinkPlayerOptions.cs index d510fb2b..e27396e0 100644 --- a/src/Lavalink4NET/Players/Queued/QueuedLavalinkPlayerOptions.cs +++ b/src/Lavalink4NET/Players/Queued/QueuedLavalinkPlayerOptions.cs @@ -2,12 +2,12 @@ public record class QueuedLavalinkPlayerOptions : LavalinkPlayerOptions { - public int InitialCapacity { get; init; } = 5; - - public int HistoryCapacity { get; init; } = 8; + public int? HistoryCapacity { get; init; } = 8; public bool ClearQueueOnStop { get; init; } = true; + public bool ClearHistoryOnStop { get; init; } = true; + public bool ResetTrackRepeatOnStop { get; init; } = true; public bool ResetShuffleOnStop { get; init; } = true; diff --git a/src/Lavalink4NET/Players/Queued/TrackCollection.cs b/src/Lavalink4NET/Players/Queued/TrackCollection.cs new file mode 100644 index 00000000..10bd2c55 --- /dev/null +++ b/src/Lavalink4NET/Players/Queued/TrackCollection.cs @@ -0,0 +1,225 @@ +namespace Lavalink4NET.Players.Queued; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +public abstract class TrackCollection : ITrackCollection +{ + protected TrackCollection() + { + Items = ImmutableList.Empty; + SyncRoot = new object(); + } + + public virtual ITrackQueueItem this[int index] => Items[index]; + + public virtual int Count => Items.Count; + + public virtual bool IsEmpty => Count is 0; + + protected object SyncRoot { get; } + + protected ImmutableList Items { get; set; } + + ValueTask ITrackCollection.AddAsync(ITrackQueueItem item, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var count = Add(item); + return new ValueTask(count); + } + + public virtual int Add(ITrackQueueItem item) + { + int count; + lock (SyncRoot) + { + Items = Items.Add(item); + count = Items.Count; + } + + return count; + } + + ValueTask ITrackCollection.AddRangeAsync(IReadOnlyList items, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var count = AddRange(items); + return new ValueTask(count); + } + + public virtual int AddRange(IReadOnlyList items) + { + int count; + lock (SyncRoot) + { + Items = Items.AddRange(items); + count = Items.Count; + } + + return count; + } + + ValueTask ITrackCollection.ClearAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var count = Clear(); + return new ValueTask(count); + } + + public virtual int Clear() + { + int count; + lock (SyncRoot) + { + count = Items.Count; + Items = ImmutableList.Empty; + } + + return count; + } + + public virtual bool Contains(ITrackQueueItem item) + { + return Items.Contains(item); + } + + ValueTask ITrackCollection.DistinctAsync(IEqualityComparer? equalityComparer, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return new ValueTask(Distinct(equalityComparer)); + } + + public virtual int Distinct(IEqualityComparer? equalityComparer) + { + int difference; + lock (SyncRoot) + { + var previousCount = Items.Count; + Items = Items.ToHashSet(equalityComparer ?? TrackEqualityComparer.Instance).ToImmutableList(); + difference = previousCount - Items.Count; + } + + return difference; + } + + public virtual IEnumerator GetEnumerator() + { + return ((IEnumerable)Items).GetEnumerator(); + } + + ValueTask ITrackCollection.RemoveAllAsync(Predicate predicate, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var count = RemoveAll(predicate); + + return new ValueTask(count); + } + + public virtual int RemoveAll(Predicate predicate) + { + int count; + lock (SyncRoot) + { + var previousCount = Items.Count; + Items = Items.RemoveAll(predicate); + count = previousCount - Items.Count; + } + + return count; + } + + ValueTask ITrackCollection.RemoveAtAsync(int index, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var removed = RemoveAt(index); + return new ValueTask(removed); + } + + public virtual bool RemoveAt(int index) + { + bool removed; + lock (SyncRoot) + { + if (index < 0 || index >= Items.Count) + { + removed = false; + } + else + { + Items = Items.RemoveAt(index); + removed = true; + } + } + + return removed; + } + + ValueTask ITrackCollection.RemoveAsync(ITrackQueueItem item, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var removed = Remove(item); + return new ValueTask(removed); + } + + public virtual bool Remove(ITrackQueueItem item) + { + bool removed; + lock (SyncRoot) + { + var previousItems = Items; + Items = Items.Remove(item); + removed = !ReferenceEquals(previousItems, Items); + } + + return removed; + } + + ValueTask ITrackCollection.RemoveRangeAsync(int index, int count, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + RemoveRange(index, count); + return default; + } + + public virtual void RemoveRange(int index, int count) + { + lock (SyncRoot) + { + Items = Items.RemoveRange(index, count); + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} + +file sealed class TrackEqualityComparer : IEqualityComparer +{ + public static TrackEqualityComparer Instance { get; } = new TrackEqualityComparer(); + + private static string? GetKey(ITrackQueueItem? item) + { + return item?.Track.Identifier ?? item?.Track.Identifier; + } + + public bool Equals(ITrackQueueItem? x, ITrackQueueItem? y) + { + return GetKey(x) == GetKey(y); + } + + public int GetHashCode([DisallowNull] ITrackQueueItem obj) + { + return GetKey(obj)?.GetHashCode() ?? 0; + } +} \ No newline at end of file diff --git a/src/Lavalink4NET/Players/Queued/TrackDequeueMode.cs b/src/Lavalink4NET/Players/Queued/TrackDequeueMode.cs new file mode 100644 index 00000000..71932bfb --- /dev/null +++ b/src/Lavalink4NET/Players/Queued/TrackDequeueMode.cs @@ -0,0 +1,7 @@ +namespace Lavalink4NET.Players.Queued; + +public enum TrackDequeueMode : byte +{ + Normal, + Shuffle, +} diff --git a/src/Lavalink4NET/Players/Queued/TrackHistory.cs b/src/Lavalink4NET/Players/Queued/TrackHistory.cs new file mode 100644 index 00000000..c197cf0e --- /dev/null +++ b/src/Lavalink4NET/Players/Queued/TrackHistory.cs @@ -0,0 +1,64 @@ +namespace Lavalink4NET.Players.Queued; + +using System.Collections.Generic; +using System.Linq; + +public sealed class TrackHistory : TrackCollection, ITrackHistory +{ + public TrackHistory(int? capacity) + { + Capacity = capacity; + } + + public int? Capacity { get; } + + public override int Add(ITrackQueueItem item) + { + int index; + lock (SyncRoot) + { + if (Capacity is not null) + { + var capacity = Capacity.Value; + + if (Items.Count >= capacity) + { + Items = Items.RemoveAt(0); + } + } + + Items = Items.Add(item); + index = Items.Count; + } + + return index; + } + + public override int AddRange(IReadOnlyList items) + { + int index; + lock (SyncRoot) + { + if (Capacity is not null) + { + var capacity = Capacity.Value; + + if (items.Count > capacity) + { + items = items.Skip(items.Count - capacity).ToList(); + } + + if (Items.Count + items.Count >= capacity) + { + var removeCount = Items.Count + items.Count - capacity; + Items = Items.RemoveRange(0, removeCount); + } + } + + Items = Items.AddRange(items); + index = Items.Count; + } + + return index; + } +} diff --git a/src/Lavalink4NET/Players/Queued/TrackQueue.cs b/src/Lavalink4NET/Players/Queued/TrackQueue.cs index 1db3ccb7..364ed50b 100644 --- a/src/Lavalink4NET/Players/Queued/TrackQueue.cs +++ b/src/Lavalink4NET/Players/Queued/TrackQueue.cs @@ -1,688 +1,122 @@ namespace Lavalink4NET.Players.Queued; using System; -using System.Collections; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; +using System.Threading.Tasks; -/// -/// A thread-safe implementation of a track queue with high-level features like shuffling or distinct. -/// -public class TrackQueue : IList, IReadOnlyList +public class TrackQueue : TrackCollection, ITrackQueue { - private readonly Stack? _history; - private readonly List _queue; - private readonly object _syncRoot; - - /// - /// Initializes a new instance of the class with copying the - /// queue and history contents from the specified . - /// - /// the queue to copy the history and queue contents from. - /// - /// thrown if the specified is . - /// - public TrackQueue(TrackQueue queue) - { - ArgumentNullException.ThrowIfNull(queue); - - // clone everything except the synchronization root - _syncRoot = new object(); - - // lock on the queue synchronization root being cloned - lock (queue._syncRoot) - { - HistoryCapacity = queue.HistoryCapacity; - _queue = new List(queue._queue); - - if (HistoryCapacity > 0) - { - _history = new Stack(queue._history!); - } - } - } - - /// - /// Initializes a new instance of the class. - /// - /// the initial capacity. - /// - /// the capacity of the history; or 0 to disable the queue history. - /// - /// - /// thrown if the specified initial capacity ( ) is negative. - /// - /// - /// thrown if the specified history capacity ( ) is negative. - /// - public TrackQueue(int initialCapacity = 5, int historyCapacity = 8) - { - if (initialCapacity < 0) - { - throw new ArgumentOutOfRangeException(nameof(initialCapacity), initialCapacity, - "The specified initial capacity must be greater than or equal to zero."); - } - - if (historyCapacity < 0) - { - throw new ArgumentOutOfRangeException(nameof(historyCapacity), historyCapacity, - "The specified history capacity must be greater than or equal to zero."); - } - - HistoryCapacity = historyCapacity; - _syncRoot = new object(); - _queue = new List(initialCapacity); - - if (historyCapacity > 0) - { - _history = new Stack(historyCapacity); - } - } - - /// - public int Count - { - get - { - lock (_syncRoot) - { - return _queue.Count; - } - } - } - - /// - /// Gets a value indicating whether the track queue remembers past tracks. - /// - /// a value indicating whether the track queue remembers past tracks. - [MemberNotNullWhen(true, nameof(_history))] - public bool HasHistory => _history != null; - - /// - /// Gets a read-only list containing the tracks dequeued in the past. - /// - /// If the queue does not remember any past tracks an empty array is returned. - /// a read-only list containing the tracks dequeued in the past. - public ImmutableArray History - { - get - { - if (!HasHistory) - { - return ImmutableArray.Empty; - } - - lock (_syncRoot) - { - return _history.ToImmutableArray(); - } - } - } - - /// - /// Gets the capacity if the history. - /// - /// the capacity if the history. - public int HistoryCapacity { get; } - - /// - /// Gets the number of elements stored in the track history. - /// - /// If the queue does not remember any past tracks zero ( 0) is returned. - /// the number of elements stored in the track history. - public int HistorySize - { - get - { - if (!HasHistory) - { - return 0; - } - - lock (_syncRoot) - { - return _history.Count; - } - } - } - - /// - /// Gets a value indicating whether the track queue is empty. - /// - /// a value indicating whether the track queue is empty. - public bool IsEmpty + public TrackQueue(int? historyCapacity = 8) { - get - { - lock (_syncRoot) - { - return _queue.Count is 0; - } - } + History = historyCapacity > 0 ? new TrackHistory(historyCapacity) : null; } - /// - bool ICollection.IsReadOnly => false; - - /// - public ITrackQueueItem this[int index] + ValueTask ITrackQueue.InsertAsync(int index, ITrackQueueItem item, CancellationToken cancellationToken) { - get - { - lock (_syncRoot) - { - return _queue[index]; - } - } - - set - { - ArgumentNullException.ThrowIfNull(value); + cancellationToken.ThrowIfCancellationRequested(); - lock (_syncRoot) - { - _queue[index] = value; - } - } - } - - /// - void ICollection.Add(ITrackQueueItem item) - { - ArgumentNullException.ThrowIfNull(item); - - lock (_syncRoot) - { - _queue.Add(item); - } - } - - /// - /// Clears the track queue. - /// - /// the number of tracks cleared. - public int Clear() - { - lock (_syncRoot) - { - var items = _queue.Count; - _queue.Clear(); - return items; - } + Insert(index, item); + return default; } - /// - void ICollection.Clear() + public virtual void Insert(int index, ITrackQueueItem item) { - lock (_syncRoot) + lock (SyncRoot) { - _queue.Clear(); + Items = Items.Insert(index, item); } } - /// - /// Clears the track history. - /// - /// the number of tracks cleared from the history. - public int ClearHistory() + ValueTask ITrackQueue.InsertRangeAsync(int index, IEnumerable items, CancellationToken cancellationToken) { - if (!HasHistory) - { - // queue has no history - return 0; - } + cancellationToken.ThrowIfCancellationRequested(); - lock (_syncRoot) - { - var items = _history.Count; - _history.Clear(); - return items; - } + InsertRange(index, items); + return default; } - /// - public TrackQueue Clone() => new(this); - - /// - public bool Contains(ITrackQueueItem item) + public virtual void InsertRange(int index, IEnumerable items) { - ArgumentNullException.ThrowIfNull(item); - - lock (_syncRoot) + lock (SyncRoot) { - return _queue.Contains(item); + Items = Items.InsertRange(index, items); } } - /// - /// Copies the elements in the track queue to the specified . - /// - /// the array to copy to. - /// - /// thrown if the specified is . - /// - /// - /// thrown if the specified has not enough space to the store - /// the queue items. - /// - public void CopyTo(ITrackQueueItem[] array) => CopyTo(array, arrayIndex: 0); - - /// - public void CopyTo(ITrackQueueItem[] array, int arrayIndex) + ValueTask ITrackQueue.ShuffleAsync(CancellationToken cancellationToken) { - ArgumentNullException.ThrowIfNull(array); + cancellationToken.ThrowIfCancellationRequested(); + Shuffle(); - lock (_syncRoot) - { - _queue.CopyTo(array, arrayIndex); - } + return default; } - /// - /// Dequeues a track item from the queue. - /// - /// the track item dequeued from the queue. - public ITrackQueueItem Dequeue() => Dequeue(shuffle: false); - - /// - /// Dequeues a track item from the queue. - /// - /// - /// a value indicating whether to shuffle tracks, if a a random - /// track is dequeued from the queue, if the first track in the - /// queue is dequeued. - /// - /// the track item dequeued from the queue. - public ITrackQueueItem Dequeue(bool shuffle) + public virtual void Shuffle() { - lock (_syncRoot) + lock (SyncRoot) { - if (_queue.Count == 0) - { - // no items in queue - throw new InvalidOperationException("Queue is empty"); - } - - var index = shuffle ? Random.Shared.Next(_queue.Count) : 0; - var item = _queue[index]; - _queue.RemoveAt(index); - - if (HasHistory) - { - if (_history.Count >= HistoryCapacity) - { - // remove first element from the history to make place - _history.Pop(); - } - - // push the track into the history - _history.Push(item); - } + var list = Items.ToBuilder(); - return item; - } - } - - /// - /// Purges all duplicate elements in the queue. - /// - /// the number of elements removed. - public int Distinct() - { - lock (_syncRoot) - { - if (_queue.Count <= 1) + for (var index = 0; index < list.Count; index++) { - // distinct would not make any changes - return 0; + var targetIndex = index + Random.Shared.Next(list.Count - index); + (list[index], list[targetIndex]) = (list[targetIndex], list[index]); } - var previousCount = _queue.Count; - - var items = _queue - .DistinctBy(x => x.Track.ToString()) - .ToArray(); - - // add the items back to the queue - _queue.Clear(); - _queue.AddRange(items); - - return previousCount - items.Length; - } - } - - /// - public int Enqueue(ITrackQueueItem item) - { - ArgumentNullException.ThrowIfNull(item); - - lock (_syncRoot) - { - var index = _queue.Count; - _queue.Add(item); - return index; - } - } - - /// - /// Adds the items of the specified array ( ) to the track queue. - /// - /// an enumerable that yields through the items to add. - /// - /// thrown if the specified array ( ) is . - /// - /// - /// thrown if an item in the specified array ( ) is . - /// - public void EnqueueRange(params ITrackQueueItem[] items) - { - ArgumentNullException.ThrowIfNull(items); - - EnqueueRange((IEnumerable)items); - } - - /// - /// Adds the enumerable of the specified array ( ) to the track queue. - /// - /// an enumerable that yields through the items to add. - /// - /// thrown if the specified array ( ) is . - /// - /// - /// thrown if an item in the specified enumerable ( ) is . - /// - public void EnqueueRange(IEnumerable items) - { - ArgumentNullException.ThrowIfNull(items); - - if (items.Any(x => x is null)) - { - throw new InvalidOperationException("An item was null."); - } - - lock (_syncRoot) - { - _queue.AddRange(items); - } - } - - /// - public IEnumerator GetEnumerator() - { - lock (_syncRoot) - { - return _queue.ToList().GetEnumerator(); + Items = list.ToImmutable(); } } - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public TrackHistory? History { get; } - /// - public int IndexOf(ITrackQueueItem item) - { - ArgumentNullException.ThrowIfNull(item); + [MemberNotNullWhen(true, nameof(History))] + public bool HasHistory => History is not null; - lock (_syncRoot) - { - return _queue.IndexOf(item); - } - } - - /// - public void Insert(int index, ITrackQueueItem item) - { - ArgumentNullException.ThrowIfNull(item); - - lock (_syncRoot) - { - _queue.Insert(index, item); - } - } - - /// - /// Inserts the items of the specified array ( ) into the track - /// queue at the specified . - /// - /// the zero-based index at which the new items should be inserted. - /// an enumerable that yields through the items to add. - /// - /// thrown if the specified is less than 0 (zero). - /// - /// - /// thrown if the specified is greater than . - /// - /// - /// thrown if the specified array ( ) is . - /// - /// - /// thrown if an item in the specified array ( ) is . - /// - public void InsertRange(int index, params ITrackQueueItem[] items) - { - ArgumentNullException.ThrowIfNull(items); - - InsertRange(index, (IEnumerable)items); - } - - /// - /// Inserts the items of the specified enumerable ( ) into the - /// track queue at the specified . - /// - /// the zero-based index at which the new items should be inserted. - /// an enumerable that yields through the items to add. - /// - /// thrown if the specified is less than 0 (zero). - /// - /// - /// thrown if the specified is greater than . - /// - /// - /// thrown if the specified enumerable ( ) is . - /// - /// - /// thrown if an item in the specified enumerable ( ) is . - /// - public void InsertRange(int index, IEnumerable items) - { - ArgumentNullException.ThrowIfNull(items); - - if (items.Any(x => x is null)) - { - throw new InvalidOperationException("An item was null."); - } - - lock (_syncRoot) - { - _queue.InsertRange(index, items); - } - } + ITrackHistory? ITrackQueue.History => History; public ITrackQueueItem? Peek() { - lock (_syncRoot) - { - return _queue.Count is 0 ? null : _queue[0]; - } - } - - /// - public bool Remove(ITrackQueueItem item) - { - ArgumentNullException.ThrowIfNull(item); - - lock (_syncRoot) + lock (SyncRoot) { - return _queue.Remove(item); + return Items.FirstOrDefault(); } } - /// - /// Removes all elements in the queue that match the specified . - /// - /// the predicate. - /// the number of total items removed from the queue. - /// - /// thrown if the specified is . - /// - public int RemoveAll(Predicate predicate) + ValueTask ITrackQueue.TryDequeueAsync(TrackDequeueMode dequeueMode, CancellationToken cancellationToken) { - ArgumentNullException.ThrowIfNull(predicate); - - lock (_syncRoot) - { - return _queue.RemoveAll(predicate); - } - } - - /// - public void RemoveAt(int index) - { - lock (_syncRoot) - { - _queue.RemoveAt(index); - } - } - - /// - /// Removes a range of items from the track queue. - /// - /// the zero-based starting index of the range of items to remove. - /// the number of items to remove. - /// - /// thrown if the specified is less than 0 (zero). - /// - /// - /// thrown if the specified is less than 0 (zero). - /// - /// - /// thrown if the specified and the specified do not denote a valid range of items in the queue. - /// - public void RemoveRange(int index, int count) - { - lock (_syncRoot) - { - _queue.RemoveRange(index, count); - } - } - - /// - /// Shuffles the whole queue. - /// - public void Shuffle() => Shuffle(index: 0, count: Count); - - /// - /// Shuffles the queue the specified range in the queue. - /// - /// the index to start shuffling items at. - /// the number of items to shuffle. - /// - /// thrown if the specified and is out - /// of bounds. - /// - /// - /// thrown if the specified is less than 0 (zero). - /// - /// - /// thrown if the specified is less than 0 (zero). - /// - /// - /// thrown if the specified and the specified do not denote a valid range of items in the queue. - /// - public void Shuffle(int index, int count) - { - if (index < 0) - { - throw new ArgumentOutOfRangeException(nameof(index), "The specified index can not be less than 0."); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(index), "The specified index can not be less than 0."); - } - - lock (_syncRoot) - { - if (index + count > _queue.Count) - { - throw new ArgumentException("The specified index and count do not denote a valid range of items in the queue."); - } - - for (; index < count - 1; index++) - { - var targetIndex = index + Random.Shared.Next(count - index); - (_queue[index], _queue[targetIndex]) = (_queue[targetIndex], _queue[index]); - } - } + cancellationToken.ThrowIfCancellationRequested(); + return TryDequeue(dequeueMode, out var track) ? new ValueTask(track) : default; } - /// - /// Tries to dequeue a track from the queue. - /// - /// - /// a value indicating whether to shuffle tracks, if a a random - /// track is dequeued from the queue, if the first track in the - /// queue is dequeued. - /// - /// ( out) the item dequeued. - /// a value indicating whether a track was dequeued. - public bool TryDequeue(bool shuffle, [MaybeNullWhen(false)] out ITrackQueueItem item) + private bool TryDequeue(TrackDequeueMode dequeueMode, out ITrackQueueItem? item) { - lock (_syncRoot) + lock (SyncRoot) { - if (_queue.Count == 0) + if (Items.IsEmpty) { - // no items in queue - item = default; + item = null; return false; } - var index = shuffle ? Random.Shared.Next(_queue.Count) : 0; + var index = dequeueMode is TrackDequeueMode.Shuffle + ? Random.Shared.Next(0, Items.Count) + : 0; - item = _queue[index]; - _queue.RemoveAt(index); + item = Items[index]; + Items = Items.RemoveAt(index); + } - if (HasHistory) - { - if (_history.Count >= HistoryCapacity) - { - // remove first element from the history to make place - _history.Pop(); - } + History?.Add(item); - // push the track into the history - _history.Push(item); - } - - return true; - } + return true; } - /// - /// Tries to dequeue a track from the queue. - /// - /// ( out) the item dequeued. - /// a value indicating whether a track was dequeued. - public bool TryDequeue([MaybeNullWhen(false)] out ITrackQueueItem item) - => TryDequeue(shuffle: false, out item); - - public bool TryPeek([MaybeNullWhen(false)] out ITrackQueueItem queueItem) + public bool TryPeek([MaybeNullWhen(false)] out ITrackQueueItem? queueItem) { - lock (_syncRoot) - { - if (_queue.Count is 0) - { - queueItem = default; - return false; - } - - queueItem = _queue[0]; - } - - return true; + queueItem = Peek(); + return queueItem is not null; } -} \ No newline at end of file +} diff --git a/src/Lavalink4NET/Players/TrackReference.cs b/src/Lavalink4NET/Players/TrackReference.cs index 71db7e76..19ff0d16 100644 --- a/src/Lavalink4NET/Players/TrackReference.cs +++ b/src/Lavalink4NET/Players/TrackReference.cs @@ -6,7 +6,7 @@ public readonly record struct TrackReference { - private readonly object _value; // eihter string or LavalinkTrack + private readonly object _value; // either string or LavalinkTrack public TrackReference(LavalinkTrack track) {