diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index 0fe188e37..d7aa41480 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -546,17 +546,17 @@ namespace ArchiSteamFarm { return Environment.NewLine + "<" + botName + "> " + response; } - internal async Task<(uint PlayableAppID, DateTime IgnoredUntil)> GetAppDataForIdling(uint appID, float hoursPlayed, bool allowRecursiveDiscovery = true, bool optimisticDiscovery = true) { + internal async Task<(uint PlayableAppID, DateTime IgnoredUntil, bool IgnoredGlobally)> GetAppDataForIdling(uint appID, float hoursPlayed, bool allowRecursiveDiscovery = true, bool optimisticDiscovery = true) { if ((appID == 0) || (hoursPlayed < 0)) { ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(hoursPlayed)); - return (0, DateTime.MaxValue); + return (0, DateTime.MaxValue, true); } HashSet packageIDs = ASF.GlobalDatabase.GetPackageIDs(appID, OwnedPackageIDs.Keys); if ((packageIDs == null) || (packageIDs.Count == 0)) { - return (0, DateTime.MaxValue); + return (0, DateTime.MaxValue, true); } if ((hoursPlayed < CardsFarmer.HoursForRefund) && !BotConfig.IdleRefundableGames) { @@ -576,7 +576,7 @@ namespace ArchiSteamFarm { DateTime playableIn = mostRecent.AddDays(CardsFarmer.DaysForRefund); if (playableIn > DateTime.UtcNow) { - return (0, playableIn); + return (0, playableIn, false); } } } @@ -596,7 +596,7 @@ namespace ArchiSteamFarm { } if (productInfoResultSet == null) { - return (optimisticDiscovery ? appID : 0, DateTime.MinValue); + return (optimisticDiscovery ? appID : 0, DateTime.MinValue, true); } foreach (Dictionary productInfoApps in productInfoResultSet.Results.Select(result => result.Apps)) { @@ -627,7 +627,7 @@ namespace ArchiSteamFarm { break; case "PRELOADONLY": case "PRERELEASE": - return (0, DateTime.MaxValue); + return (0, DateTime.MaxValue, true); default: ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(releaseState), releaseState)); @@ -638,7 +638,7 @@ namespace ArchiSteamFarm { string type = commonProductInfo["type"].Value; if (string.IsNullOrEmpty(type)) { - return (appID, DateTime.MinValue); + return (appID, DateTime.MinValue, true); } // We must convert this to uppercase, since Valve doesn't stick to any convention and we can have a case mismatch @@ -652,7 +652,7 @@ namespace ArchiSteamFarm { case "TOOL": case "VIDEO": // Types that can be idled - return (appID, DateTime.MinValue); + return (appID, DateTime.MinValue, true); case "ADVERTISING": case "DEMO": case "DLC": @@ -667,13 +667,13 @@ namespace ArchiSteamFarm { } if (!allowRecursiveDiscovery) { - return (0, DateTime.MinValue); + return (0, DateTime.MinValue, true); } string listOfDlc = productInfo["extended"]["listofdlc"].Value; if (string.IsNullOrEmpty(listOfDlc)) { - return (appID, DateTime.MinValue); + return (appID, DateTime.MinValue, true); } string[] dlcAppIDsTexts = listOfDlc.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); @@ -685,17 +685,17 @@ namespace ArchiSteamFarm { break; } - (uint playableAppID, _) = await GetAppDataForIdling(dlcAppID, hoursPlayed, false, false).ConfigureAwait(false); + (uint playableAppID, _, _) = await GetAppDataForIdling(dlcAppID, hoursPlayed, false, false).ConfigureAwait(false); if (playableAppID != 0) { - return (playableAppID, DateTime.MinValue); + return (playableAppID, DateTime.MinValue, true); } } - return (appID, DateTime.MinValue); + return (appID, DateTime.MinValue, true); } - return ((productInfoResultSet.Complete && !productInfoResultSet.Failed) || optimisticDiscovery ? appID : 0, DateTime.MinValue); + return ((productInfoResultSet.Complete && !productInfoResultSet.Failed) || optimisticDiscovery ? appID : 0, DateTime.MinValue, true); } internal static string GetFilePath(string botName, EFileType fileType) { diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs index 4a1172a33..fed62c39c 100755 --- a/ArchiSteamFarm/CardsFarmer.cs +++ b/ArchiSteamFarm/CardsFarmer.cs @@ -47,7 +47,7 @@ namespace ArchiSteamFarm { [PublicAPI] public static readonly ImmutableHashSet SalesBlacklist = ImmutableHashSet.Create(267420, 303700, 335590, 368020, 425280, 480730, 566020, 639900, 762800, 876740, 991980); - private static readonly ConcurrentDictionary IgnoredAppIDs = new ConcurrentDictionary(); // Reserved for unreleased games + private static readonly ConcurrentDictionary GloballyIgnoredAppIDs = new ConcurrentDictionary(); // Reserved for unreleased games // Games that were confirmed to show false status on general badges page private static readonly ImmutableHashSet UntrustedAppIDs = ImmutableHashSet.Create(440, 570, 730); @@ -71,6 +71,14 @@ namespace ArchiSteamFarm { private readonly SemaphoreSlim FarmingInitializationSemaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim FarmingResetSemaphore = new SemaphoreSlim(0, 1); private readonly Timer IdleFarmingTimer; + private readonly ConcurrentDictionary LocallyIgnoredAppIDs = new ConcurrentDictionary(); + + private IEnumerable> SourcesOfIgnoredAppIDs { + get { + yield return GloballyIgnoredAppIDs; + yield return LocallyIgnoredAppIDs; + } + } internal bool NowFarming { get; private set; } @@ -114,6 +122,9 @@ namespace ArchiSteamFarm { } internal async Task OnNewGameAdded() { + // This update has a potential to modify local ignores, therefore we need to purge our cache + LocallyIgnoredAppIDs.Clear(); + ShouldResumeFarming = true; // We aim to have a maximum of 2 tasks, one already parsing, and one waiting in the queue @@ -404,14 +415,26 @@ namespace ArchiSteamFarm { continue; } - if (IgnoredAppIDs.TryGetValue(appID, out DateTime ignoredUntil)) { - if (ignoredUntil < DateTime.UtcNow) { - // This game served its time as being ignored - IgnoredAppIDs.TryRemove(appID, out _); - } else { - // This game is still ignored + bool ignored = false; + + foreach (ConcurrentDictionary sourceOfIgnoredAppIDs in SourcesOfIgnoredAppIDs) { + if (!sourceOfIgnoredAppIDs.TryGetValue(appID, out DateTime ignoredUntil)) { continue; } + + if (ignoredUntil > DateTime.UtcNow) { + // This game is still ignored + ignored = true; + + break; + } + + // This game served its time as being ignored + sourceOfIgnoredAppIDs.TryRemove(appID, out _); + } + + if (ignored) { + continue; } // Cards @@ -1019,10 +1042,12 @@ namespace ArchiSteamFarm { return false; } - (uint playableAppID, DateTime ignoredUntil) = await Bot.GetAppDataForIdling(game.AppID, game.HoursPlayed).ConfigureAwait(false); + (uint playableAppID, DateTime ignoredUntil, bool ignoredGlobally) = await Bot.GetAppDataForIdling(game.AppID, game.HoursPlayed).ConfigureAwait(false); if (playableAppID == 0) { - IgnoredAppIDs[game.AppID] = ignoredUntil < DateTime.MaxValue ? ignoredUntil : DateTime.UtcNow.AddHours(HoursToIgnore); + ConcurrentDictionary ignoredAppIDs = ignoredGlobally ? GloballyIgnoredAppIDs : LocallyIgnoredAppIDs; + + ignoredAppIDs[game.AppID] = (ignoredUntil > DateTime.MinValue) && (ignoredUntil < DateTime.MaxValue) ? ignoredUntil : DateTime.UtcNow.AddHours(HoursToIgnore); Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingGameNotPossible, game.AppID, game.GameName)); return false;