diff --git a/ArchiSteamFarm/ArchiSteamFarm.csproj b/ArchiSteamFarm/ArchiSteamFarm.csproj
index a3ec77f2a..d53817c8d 100644
--- a/ArchiSteamFarm/ArchiSteamFarm.csproj
+++ b/ArchiSteamFarm/ArchiSteamFarm.csproj
@@ -76,6 +76,7 @@
+
diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs
index d525a9e5f..b7ccc8d46 100755
--- a/ArchiSteamFarm/CardsFarmer.cs
+++ b/ArchiSteamFarm/CardsFarmer.cs
@@ -56,7 +56,7 @@ namespace ArchiSteamFarm {
internal readonly ConcurrentHashSet CurrentGamesFarming = new ConcurrentHashSet();
[JsonProperty]
- internal readonly ConcurrentSortedHashSet GamesToFarm = new ConcurrentSortedHashSet();
+ internal readonly ConcurrentList GamesToFarm = new ConcurrentList();
[JsonProperty]
internal TimeSpan TimeRemaining =>
@@ -100,7 +100,6 @@ namespace ArchiSteamFarm {
EventSemaphore.Dispose();
FarmingInitializationSemaphore.Dispose();
FarmingResetSemaphore.Dispose();
- GamesToFarm.Dispose();
// Those are objects that might be null and the check should be in-place
IdleFarmingTimer?.Dispose();
@@ -1058,34 +1057,34 @@ namespace ArchiSteamFarm {
private async Task SortGamesToFarm() {
// Put priority idling appIDs on top
- IOrderedEnumerable gamesToFarm = GamesToFarm.OrderByDescending(game => Bot.IsPriorityIdling(game.AppID));
+ IOrderedEnumerable orderedGamesToFarm = GamesToFarm.OrderByDescending(game => Bot.IsPriorityIdling(game.AppID));
foreach (BotConfig.EFarmingOrder farmingOrder in Bot.BotConfig.FarmingOrders) {
switch (farmingOrder) {
case BotConfig.EFarmingOrder.Unordered:
break;
case BotConfig.EFarmingOrder.AppIDsAscending:
- gamesToFarm = gamesToFarm.ThenBy(game => game.AppID);
+ orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => game.AppID);
break;
case BotConfig.EFarmingOrder.AppIDsDescending:
- gamesToFarm = gamesToFarm.ThenByDescending(game => game.AppID);
+ orderedGamesToFarm = orderedGamesToFarm.ThenByDescending(game => game.AppID);
break;
case BotConfig.EFarmingOrder.BadgeLevelsAscending:
- gamesToFarm = gamesToFarm.ThenBy(game => game.BadgeLevel);
+ orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => game.BadgeLevel);
break;
case BotConfig.EFarmingOrder.BadgeLevelsDescending:
- gamesToFarm = gamesToFarm.ThenByDescending(game => game.BadgeLevel);
+ orderedGamesToFarm = orderedGamesToFarm.ThenByDescending(game => game.BadgeLevel);
break;
case BotConfig.EFarmingOrder.CardDropsAscending:
- gamesToFarm = gamesToFarm.ThenBy(game => game.CardsRemaining);
+ orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => game.CardsRemaining);
break;
case BotConfig.EFarmingOrder.CardDropsDescending:
- gamesToFarm = gamesToFarm.ThenByDescending(game => game.CardsRemaining);
+ orderedGamesToFarm = orderedGamesToFarm.ThenByDescending(game => game.CardsRemaining);
break;
case BotConfig.EFarmingOrder.MarketableAscending:
@@ -1095,11 +1094,11 @@ namespace ArchiSteamFarm {
if ((marketableAppIDs != null) && (marketableAppIDs.Count > 0)) {
switch (farmingOrder) {
case BotConfig.EFarmingOrder.MarketableAscending:
- gamesToFarm = gamesToFarm.ThenBy(game => marketableAppIDs.Contains(game.AppID));
+ orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => marketableAppIDs.Contains(game.AppID));
break;
case BotConfig.EFarmingOrder.MarketableDescending:
- gamesToFarm = gamesToFarm.ThenByDescending(game => marketableAppIDs.Contains(game.AppID));
+ orderedGamesToFarm = orderedGamesToFarm.ThenByDescending(game => marketableAppIDs.Contains(game.AppID));
break;
default:
@@ -1111,23 +1110,23 @@ namespace ArchiSteamFarm {
break;
case BotConfig.EFarmingOrder.HoursAscending:
- gamesToFarm = gamesToFarm.ThenBy(game => game.HoursPlayed);
+ orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => game.HoursPlayed);
break;
case BotConfig.EFarmingOrder.HoursDescending:
- gamesToFarm = gamesToFarm.ThenByDescending(game => game.HoursPlayed);
+ orderedGamesToFarm = orderedGamesToFarm.ThenByDescending(game => game.HoursPlayed);
break;
case BotConfig.EFarmingOrder.NamesAscending:
- gamesToFarm = gamesToFarm.ThenBy(game => game.GameName);
+ orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => game.GameName);
break;
case BotConfig.EFarmingOrder.NamesDescending:
- gamesToFarm = gamesToFarm.ThenByDescending(game => game.GameName);
+ orderedGamesToFarm = orderedGamesToFarm.ThenByDescending(game => game.GameName);
break;
case BotConfig.EFarmingOrder.Random:
- gamesToFarm = gamesToFarm.ThenBy(game => Utilities.RandomNext());
+ orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => Utilities.RandomNext());
break;
case BotConfig.EFarmingOrder.RedeemDateTimesAscending:
@@ -1157,11 +1156,11 @@ namespace ArchiSteamFarm {
switch (farmingOrder) {
case BotConfig.EFarmingOrder.RedeemDateTimesAscending:
- gamesToFarm = gamesToFarm.ThenBy(game => redeemDates[game.AppID]);
+ orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => redeemDates[game.AppID]);
break;
case BotConfig.EFarmingOrder.RedeemDateTimesDescending:
- gamesToFarm = gamesToFarm.ThenByDescending(game => redeemDates[game.AppID]);
+ orderedGamesToFarm = orderedGamesToFarm.ThenByDescending(game => redeemDates[game.AppID]);
break;
default:
@@ -1178,8 +1177,11 @@ namespace ArchiSteamFarm {
}
}
- // We must call ToList() here as we can't replace items while enumerating
- GamesToFarm.ReplaceWith(gamesToFarm.ToList());
+ // We must call ToList() here as we can't do in-place replace
+ List gamesToFarm = orderedGamesToFarm.ToList();
+
+ GamesToFarm.Clear();
+ GamesToFarm.AddRange(gamesToFarm);
}
internal sealed class Game : IEquatable {
diff --git a/ArchiSteamFarm/Collections/ConcurrentEnumerator.cs b/ArchiSteamFarm/Collections/ConcurrentEnumerator.cs
index 66bc62e69..bd3f3196d 100644
--- a/ArchiSteamFarm/Collections/ConcurrentEnumerator.cs
+++ b/ArchiSteamFarm/Collections/ConcurrentEnumerator.cs
@@ -22,7 +22,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
-using System.Threading;
using JetBrains.Annotations;
namespace ArchiSteamFarm.Collections {
@@ -30,22 +29,20 @@ namespace ArchiSteamFarm.Collections {
public T Current => Enumerator.Current;
private readonly IEnumerator Enumerator;
- private readonly SemaphoreSlim Semaphore;
+ private readonly IDisposable Lock;
object IEnumerator.Current => Current;
- internal ConcurrentEnumerator([NotNull] IReadOnlyCollection collection, [NotNull] SemaphoreSlim semaphore) {
- if ((collection == null) || (semaphore == null)) {
- throw new ArgumentNullException(nameof(collection) + " || " + nameof(semaphore));
+ internal ConcurrentEnumerator([NotNull] IReadOnlyCollection collection, [NotNull] IDisposable @lock) {
+ if ((collection == null) || (@lock == null)) {
+ throw new ArgumentNullException(nameof(collection) + " || " + nameof(@lock));
}
- Semaphore = semaphore;
- semaphore.Wait();
-
+ Lock = @lock;
Enumerator = collection.GetEnumerator();
}
- public void Dispose() => Semaphore.Release();
+ public void Dispose() => Lock.Dispose();
public bool MoveNext() => Enumerator.MoveNext();
public void Reset() => Enumerator.Reset();
}
diff --git a/ArchiSteamFarm/Collections/ConcurrentList.cs b/ArchiSteamFarm/Collections/ConcurrentList.cs
new file mode 100644
index 000000000..63229ab69
--- /dev/null
+++ b/ArchiSteamFarm/Collections/ConcurrentList.cs
@@ -0,0 +1,122 @@
+// _ _ _ ____ _ _____
+// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
+// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
+// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
+// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
+// |
+// Copyright 2015-2019 Łukasz "JustArchi" Domeradzki
+// Contact: JustArchi@JustArchi.net
+// |
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// |
+// http://www.apache.org/licenses/LICENSE-2.0
+// |
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using JetBrains.Annotations;
+using Nito.AsyncEx;
+
+namespace ArchiSteamFarm.Collections {
+ internal sealed class ConcurrentList : IList, IReadOnlyList {
+ public bool IsReadOnly => false;
+
+ internal int Count {
+ get {
+ using (Lock.ReaderLock()) {
+ return BackingCollection.Count;
+ }
+ }
+ }
+
+ private readonly List BackingCollection = new List();
+ private readonly AsyncReaderWriterLock Lock = new AsyncReaderWriterLock();
+
+ int ICollection.Count => Count;
+ int IReadOnlyCollection.Count => Count;
+
+ public T this[int index] {
+ get {
+ using (Lock.ReaderLock()) {
+ return BackingCollection[index];
+ }
+ }
+
+ set {
+ using (Lock.WriterLock()) {
+ BackingCollection[index] = value;
+ }
+ }
+ }
+
+ public void Add(T item) {
+ using (Lock.WriterLock()) {
+ BackingCollection.Add(item);
+ }
+ }
+
+ public void Clear() {
+ using (Lock.WriterLock()) {
+ BackingCollection.Clear();
+ }
+ }
+
+ public bool Contains(T item) {
+ using (Lock.ReaderLock()) {
+ return BackingCollection.Contains(item);
+ }
+ }
+
+ public void CopyTo(T[] array, int arrayIndex) {
+ using (Lock.ReaderLock()) {
+ BackingCollection.CopyTo(array, arrayIndex);
+ }
+ }
+
+ [NotNull]
+ [SuppressMessage("ReSharper", "AnnotationRedundancyInHierarchy")]
+ public IEnumerator GetEnumerator() => new ConcurrentEnumerator(BackingCollection, Lock.ReaderLock());
+
+ public int IndexOf(T item) {
+ using (Lock.ReaderLock()) {
+ return BackingCollection.IndexOf(item);
+ }
+ }
+
+ public void Insert(int index, T item) {
+ using (Lock.WriterLock()) {
+ BackingCollection.Insert(index, item);
+ }
+ }
+
+ public bool Remove(T item) {
+ using (Lock.WriterLock()) {
+ return BackingCollection.Remove(item);
+ }
+ }
+
+ public void RemoveAt(int index) {
+ using (Lock.WriterLock()) {
+ BackingCollection.RemoveAt(index);
+ }
+ }
+
+ [NotNull]
+ [SuppressMessage("ReSharper", "AnnotationRedundancyInHierarchy")]
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ internal void AddRange([NotNull] IEnumerable collection) {
+ using (Lock.WriterLock()) {
+ BackingCollection.AddRange(collection);
+ }
+ }
+ }
+}
diff --git a/ArchiSteamFarm/Collections/ConcurrentSortedHashSet.cs b/ArchiSteamFarm/Collections/ConcurrentSortedHashSet.cs
deleted file mode 100644
index 268cf5a37..000000000
--- a/ArchiSteamFarm/Collections/ConcurrentSortedHashSet.cs
+++ /dev/null
@@ -1,227 +0,0 @@
-// _ _ _ ____ _ _____
-// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
-// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
-// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
-// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
-// |
-// Copyright 2015-2019 Łukasz "JustArchi" Domeradzki
-// Contact: JustArchi@JustArchi.net
-// |
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// |
-// http://www.apache.org/licenses/LICENSE-2.0
-// |
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Threading;
-using JetBrains.Annotations;
-
-namespace ArchiSteamFarm.Collections {
- internal sealed class ConcurrentSortedHashSet : IDisposable, IReadOnlyCollection, ISet {
- public int Count {
- get {
- CollectionSemaphore.Wait();
-
- try {
- return BackingCollection.Count;
- } finally {
- CollectionSemaphore.Release();
- }
- }
- }
-
- public bool IsReadOnly => false;
-
- private readonly HashSet BackingCollection = new HashSet();
- private readonly SemaphoreSlim CollectionSemaphore = new SemaphoreSlim(1, 1);
-
- public bool Add(T item) {
- CollectionSemaphore.Wait();
-
- try {
- return BackingCollection.Add(item);
- } finally {
- CollectionSemaphore.Release();
- }
- }
-
- public void Clear() {
- CollectionSemaphore.Wait();
-
- try {
- BackingCollection.Clear();
- } finally {
- CollectionSemaphore.Release();
- }
- }
-
- public bool Contains(T item) {
- CollectionSemaphore.Wait();
-
- try {
- return BackingCollection.Contains(item);
- } finally {
- CollectionSemaphore.Release();
- }
- }
-
- [SuppressMessage("ReSharper", "AnnotationRedundancyInHierarchy")]
- public void CopyTo([NotNull] T[] array, int arrayIndex) {
- CollectionSemaphore.Wait();
-
- try {
- BackingCollection.CopyTo(array, arrayIndex);
- } finally {
- CollectionSemaphore.Release();
- }
- }
-
- public void Dispose() => CollectionSemaphore.Dispose();
-
- public void ExceptWith(IEnumerable other) {
- CollectionSemaphore.Wait();
-
- try {
- BackingCollection.ExceptWith(other);
- } finally {
- CollectionSemaphore.Release();
- }
- }
-
- [NotNull]
- [SuppressMessage("ReSharper", "AnnotationRedundancyInHierarchy")]
- public IEnumerator GetEnumerator() => new ConcurrentEnumerator(BackingCollection, CollectionSemaphore);
-
- public void IntersectWith(IEnumerable other) {
- CollectionSemaphore.Wait();
-
- try {
- BackingCollection.IntersectWith(other);
- } finally {
- CollectionSemaphore.Release();
- }
- }
-
- public bool IsProperSubsetOf(IEnumerable other) {
- CollectionSemaphore.Wait();
-
- try {
- return BackingCollection.IsProperSubsetOf(other);
- } finally {
- CollectionSemaphore.Release();
- }
- }
-
- public bool IsProperSupersetOf(IEnumerable other) {
- CollectionSemaphore.Wait();
-
- try {
- return BackingCollection.IsProperSupersetOf(other);
- } finally {
- CollectionSemaphore.Release();
- }
- }
-
- public bool IsSubsetOf(IEnumerable other) {
- CollectionSemaphore.Wait();
-
- try {
- return BackingCollection.IsSubsetOf(other);
- } finally {
- CollectionSemaphore.Release();
- }
- }
-
- public bool IsSupersetOf(IEnumerable other) {
- CollectionSemaphore.Wait();
-
- try {
- return BackingCollection.IsSupersetOf(other);
- } finally {
- CollectionSemaphore.Release();
- }
- }
-
- public bool Overlaps(IEnumerable other) {
- CollectionSemaphore.Wait();
-
- try {
- return BackingCollection.Overlaps(other);
- } finally {
- CollectionSemaphore.Release();
- }
- }
-
- public bool Remove(T item) {
- CollectionSemaphore.Wait();
-
- try {
- return BackingCollection.Remove(item);
- } finally {
- CollectionSemaphore.Release();
- }
- }
-
- public bool SetEquals(IEnumerable other) {
- CollectionSemaphore.Wait();
-
- try {
- return BackingCollection.SetEquals(other);
- } finally {
- CollectionSemaphore.Release();
- }
- }
-
- public void SymmetricExceptWith(IEnumerable other) {
- CollectionSemaphore.Wait();
-
- try {
- BackingCollection.SymmetricExceptWith(other);
- } finally {
- CollectionSemaphore.Release();
- }
- }
-
- public void UnionWith(IEnumerable other) {
- CollectionSemaphore.Wait();
-
- try {
- BackingCollection.UnionWith(other);
- } finally {
- CollectionSemaphore.Release();
- }
- }
-
- [SuppressMessage("ReSharper", "AnnotationConflictInHierarchy")]
- [SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")]
- void ICollection.Add([NotNull] T item) => Add(item);
-
- [NotNull]
- [SuppressMessage("ReSharper", "AnnotationRedundancyInHierarchy")]
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
-
- internal void ReplaceWith([NotNull] IEnumerable other) {
- CollectionSemaphore.Wait();
-
- try {
- BackingCollection.Clear();
-
- foreach (T item in other) {
- BackingCollection.Add(item);
- }
- } finally {
- CollectionSemaphore.Release();
- }
- }
- }
-}