From a57dc7387cdf81410954c6bb56805644961787d9 Mon Sep 17 00:00:00 2001 From: JustArchi Date: Sun, 23 Jul 2017 11:55:44 +0200 Subject: [PATCH] Fix FarmingOrder + add ordering by redeem datetimes Since we already have logic for that, such farming order is cool too. --- ArchiSteamFarm/Bot.cs | 8 +- ArchiSteamFarm/BotConfig.cs | 4 +- ArchiSteamFarm/CardsFarmer.cs | 38 +++- ArchiSteamFarm/ConcurrentEnumerator.cs | 54 ++++++ ArchiSteamFarm/ConcurrentHashSet.cs | 2 +- ArchiSteamFarm/ConcurrentSortedHashSet.cs | 220 ++++++++++++++++++++++ 6 files changed, 320 insertions(+), 6 deletions(-) create mode 100644 ArchiSteamFarm/ConcurrentEnumerator.cs create mode 100644 ArchiSteamFarm/ConcurrentSortedHashSet.cs diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index 706e7295d..74ab6a68a 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -59,6 +59,7 @@ namespace ArchiSteamFarm { internal readonly ArchiLogger ArchiLogger; internal readonly ArchiWebHandler ArchiWebHandler; + internal readonly ConcurrentDictionary OwnedPackageIDs = new ConcurrentDictionary(); internal bool CanReceiveSteamCards => !IsAccountLimited && !IsAccountLocked; internal bool HasMobileAuthenticator => BotDatabase?.MobileAuthenticator != null; @@ -81,7 +82,6 @@ namespace ArchiSteamFarm { private readonly Timer HeartBeatTimer; private readonly SemaphoreSlim InitializationSemaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim LootingSemaphore = new SemaphoreSlim(1); - private readonly ConcurrentDictionary OwnedPackageIDs = new ConcurrentDictionary(); private readonly Statistics Statistics; private readonly SteamApps SteamApps; private readonly SteamClient SteamClient; @@ -1713,8 +1713,10 @@ namespace ArchiSteamFarm { OwnedPackageIDs[license.PackageID] = (license.PaymentMethod, license.TimeCreated); } - if ((OwnedPackageIDs.Count > 0) && !BotConfig.IdleRefundableGames) { - Program.GlobalDatabase.RefreshPackageIDs(this, OwnedPackageIDs.Keys).Forget(); + if (OwnedPackageIDs.Count > 0) { + if (!BotConfig.IdleRefundableGames || (BotConfig.FarmingOrder == BotConfig.EFarmingOrder.RedeemDateTimesAscending) || (BotConfig.FarmingOrder == BotConfig.EFarmingOrder.RedeemDateTimesDescending)) { + Program.GlobalDatabase.RefreshPackageIDs(this, OwnedPackageIDs.Keys).Forget(); + } } await Task.Delay(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback or SharedLibraryLockStatusCallback diff --git a/ArchiSteamFarm/BotConfig.cs b/ArchiSteamFarm/BotConfig.cs index c9ec9a1d7..3593540ea 100644 --- a/ArchiSteamFarm/BotConfig.cs +++ b/ArchiSteamFarm/BotConfig.cs @@ -229,7 +229,9 @@ namespace ArchiSteamFarm { NamesDescending, Random, BadgeLevelsAscending, - BadgeLevelsDescending + BadgeLevelsDescending, + RedeemDateTimesAscending, + RedeemDateTimesDescending } internal enum EPermission : byte { diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs index 82d8b0e6a..10287e7ad 100755 --- a/ArchiSteamFarm/CardsFarmer.cs +++ b/ArchiSteamFarm/CardsFarmer.cs @@ -34,6 +34,7 @@ using System.Threading.Tasks; using ArchiSteamFarm.Localization; using HtmlAgilityPack; using Newtonsoft.Json; +using SteamKit2; namespace ArchiSteamFarm { internal sealed class CardsFarmer : IDisposable { @@ -47,7 +48,7 @@ namespace ArchiSteamFarm { internal readonly ConcurrentHashSet CurrentGamesFarming = new ConcurrentHashSet(); [JsonProperty] - internal readonly ConcurrentHashSet GamesToFarm = new ConcurrentHashSet(); + internal readonly ConcurrentSortedHashSet GamesToFarm = new ConcurrentSortedHashSet(); [JsonProperty] internal TimeSpan TimeRemaining => new TimeSpan( @@ -920,6 +921,41 @@ namespace ArchiSteamFarm { break; case BotConfig.EFarmingOrder.Random: gamesToFarm = gamesToFarm.ThenBy(game => Utilities.RandomNext()); + break; + case BotConfig.EFarmingOrder.RedeemDateTimesAscending: + case BotConfig.EFarmingOrder.RedeemDateTimesDescending: + Dictionary redeemDates = new Dictionary(GamesToFarm.Count); + + foreach (Game game in gamesToFarm) { + DateTime redeemDate = DateTime.MinValue; + if (Program.GlobalDatabase.AppIDsToPackageIDs.TryGetValue(game.AppID, out ConcurrentHashSet packageIDs)) { + // ReSharper disable once LoopCanBePartlyConvertedToQuery - C# 7.0 out can't be used within LINQ query yet | https://github.com/dotnet/roslyn/issues/15619 + foreach (uint packageID in packageIDs) { + if (!Bot.OwnedPackageIDs.TryGetValue(packageID, out (EPaymentMethod PaymentMethod, DateTime TimeCreated) packageData)) { + continue; + } + + if (packageData.TimeCreated > redeemDate) { + redeemDate = packageData.TimeCreated; + } + } + } + + redeemDates[game.AppID] = redeemDate; + } + + switch (Bot.BotConfig.FarmingOrder) { + case BotConfig.EFarmingOrder.RedeemDateTimesAscending: + gamesToFarm = gamesToFarm.ThenBy(game => redeemDates[game.AppID]); + break; + case BotConfig.EFarmingOrder.RedeemDateTimesDescending: + gamesToFarm = gamesToFarm.ThenByDescending(game => redeemDates[game.AppID]); + break; + default: + Bot.ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, nameof(Bot.BotConfig.FarmingOrder))); + return; + } + break; default: Bot.ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, nameof(Bot.BotConfig.FarmingOrder))); diff --git a/ArchiSteamFarm/ConcurrentEnumerator.cs b/ArchiSteamFarm/ConcurrentEnumerator.cs new file mode 100644 index 000000000..08e4e7d55 --- /dev/null +++ b/ArchiSteamFarm/ConcurrentEnumerator.cs @@ -0,0 +1,54 @@ +/* + _ _ _ ____ _ _____ + / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ + / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ + / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| + + Copyright 2015-2017 Ł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.Threading; + +namespace ArchiSteamFarm { + internal sealed class ConcurrentEnumerator : IEnumerator { + public T Current => Enumerator.Current; + + private readonly IEnumerator Enumerator; + private readonly SemaphoreSlim SemaphoreSlim; + + object IEnumerator.Current => Current; + + internal ConcurrentEnumerator(ICollection collection, SemaphoreSlim semaphoreSlim) { + if ((collection == null) || (semaphoreSlim == null)) { + throw new ArgumentNullException(nameof(collection) + " || " + nameof(semaphoreSlim)); + } + + SemaphoreSlim = semaphoreSlim; + SemaphoreSlim.Wait(); + + Enumerator = collection.GetEnumerator(); + } + + public void Dispose() => SemaphoreSlim.Release(); + public bool MoveNext() => Enumerator.MoveNext(); + public void Reset() => Enumerator.Reset(); + } +} \ No newline at end of file diff --git a/ArchiSteamFarm/ConcurrentHashSet.cs b/ArchiSteamFarm/ConcurrentHashSet.cs index 6365a2bd3..b7ab287f8 100644 --- a/ArchiSteamFarm/ConcurrentHashSet.cs +++ b/ArchiSteamFarm/ConcurrentHashSet.cs @@ -28,7 +28,7 @@ using System.Collections.Generic; using System.Linq; namespace ArchiSteamFarm { - internal sealed class ConcurrentHashSet : ISet { + internal sealed class ConcurrentHashSet : IReadOnlyCollection, ISet { public int Count => BackingCollection.Count; public bool IsReadOnly => false; diff --git a/ArchiSteamFarm/ConcurrentSortedHashSet.cs b/ArchiSteamFarm/ConcurrentSortedHashSet.cs new file mode 100644 index 000000000..17ac7951d --- /dev/null +++ b/ArchiSteamFarm/ConcurrentSortedHashSet.cs @@ -0,0 +1,220 @@ +/* + _ _ _ ____ _ _____ + / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ + / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ + / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| + + Copyright 2015-2017 Ł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.Threading; + +namespace ArchiSteamFarm { + internal sealed class ConcurrentSortedHashSet : IDisposable, IReadOnlyCollection, ISet { + public int Count { + get { + SemaphoreSlim.Wait(); + + try { + return BackingCollection.Count; + } finally { + SemaphoreSlim.Release(); + } + } + } + + public bool IsReadOnly => false; + + private readonly HashSet BackingCollection = new HashSet(); + private readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1); + + public bool Add(T item) { + SemaphoreSlim.Wait(); + + try { + return BackingCollection.Add(item); + } finally { + SemaphoreSlim.Release(); + } + } + + public void Clear() { + SemaphoreSlim.Wait(); + + try { + BackingCollection.Clear(); + } finally { + SemaphoreSlim.Release(); + } + } + + public bool Contains(T item) { + SemaphoreSlim.Wait(); + + try { + return BackingCollection.Contains(item); + } finally { + SemaphoreSlim.Release(); + } + } + + public void CopyTo(T[] array, int arrayIndex) { + SemaphoreSlim.Wait(); + + try { + BackingCollection.CopyTo(array, arrayIndex); + } finally { + SemaphoreSlim.Release(); + } + } + + public void Dispose() => SemaphoreSlim.Dispose(); + + public void ExceptWith(IEnumerable other) { + SemaphoreSlim.Wait(); + + try { + BackingCollection.ExceptWith(other); + } finally { + SemaphoreSlim.Release(); + } + } + + public IEnumerator GetEnumerator() => new ConcurrentEnumerator(BackingCollection, SemaphoreSlim); + + public void IntersectWith(IEnumerable other) { + SemaphoreSlim.Wait(); + + try { + BackingCollection.IntersectWith(other); + } finally { + SemaphoreSlim.Release(); + } + } + + public bool IsProperSubsetOf(IEnumerable other) { + SemaphoreSlim.Wait(); + + try { + return BackingCollection.IsProperSubsetOf(other); + } finally { + SemaphoreSlim.Release(); + } + } + + public bool IsProperSupersetOf(IEnumerable other) { + SemaphoreSlim.Wait(); + + try { + return BackingCollection.IsProperSupersetOf(other); + } finally { + SemaphoreSlim.Release(); + } + } + + public bool IsSubsetOf(IEnumerable other) { + SemaphoreSlim.Wait(); + + try { + return BackingCollection.IsSubsetOf(other); + } finally { + SemaphoreSlim.Release(); + } + } + + public bool IsSupersetOf(IEnumerable other) { + SemaphoreSlim.Wait(); + + try { + return BackingCollection.IsSupersetOf(other); + } finally { + SemaphoreSlim.Release(); + } + } + + public bool Overlaps(IEnumerable other) { + SemaphoreSlim.Wait(); + + try { + return BackingCollection.Overlaps(other); + } finally { + SemaphoreSlim.Release(); + } + } + + public bool Remove(T item) { + SemaphoreSlim.Wait(); + + try { + return BackingCollection.Remove(item); + } finally { + SemaphoreSlim.Release(); + } + } + + public bool SetEquals(IEnumerable other) { + SemaphoreSlim.Wait(); + + try { + return BackingCollection.SetEquals(other); + } finally { + SemaphoreSlim.Release(); + } + } + + public void SymmetricExceptWith(IEnumerable other) { + SemaphoreSlim.Wait(); + + try { + BackingCollection.SymmetricExceptWith(other); + } finally { + SemaphoreSlim.Release(); + } + } + + public void UnionWith(IEnumerable other) { + SemaphoreSlim.Wait(); + + try { + BackingCollection.UnionWith(other); + } finally { + SemaphoreSlim.Release(); + } + } + + void ICollection.Add(T item) => Add(item); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + internal void ReplaceWith(IEnumerable other) { + SemaphoreSlim.Wait(); + + try { + BackingCollection.Clear(); + + foreach (T item in other) { + BackingCollection.Add(item); + } + } finally { + SemaphoreSlim.Release(); + } + } + } +} \ No newline at end of file