diff --git a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/GlobalCache.cs b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/GlobalCache.cs index 1853fecaf..3e48b182d 100644 --- a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/GlobalCache.cs +++ b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/GlobalCache.cs @@ -4,7 +4,7 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // | -// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki +// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki // Contact: JustArchi@JustArchi.net // | // Licensed under the Apache License, Version 2.0 (the "License"); @@ -237,35 +237,28 @@ internal sealed class GlobalCache : SerializableFile { } } - internal void UpdateDepotKeys(ICollection depotKeyResults) { - ArgumentNullException.ThrowIfNull(depotKeyResults); + internal void UpdateDepotKey(SteamApps.DepotKeyCallback depotKeyResult) { + ArgumentNullException.ThrowIfNull(depotKeyResult); - bool save = false; - - foreach (SteamApps.DepotKeyCallback depotKeyResult in depotKeyResults) { - if (depotKeyResult.Result != EResult.OK) { - continue; - } - - string depotKey = Convert.ToHexString(depotKeyResult.DepotKey); - - if (!IsValidDepotKey(depotKey)) { - ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.ErrorIsInvalid, nameof(depotKey))); - - continue; - } - - if (DepotKeys.TryGetValue(depotKeyResult.DepotID, out string? previousDepotKey) && (previousDepotKey == depotKey)) { - continue; - } - - DepotKeys[depotKeyResult.DepotID] = depotKey; - save = true; + if (depotKeyResult.Result != EResult.OK) { + return; } - if (save) { - Utilities.InBackground(Save); + string depotKey = Convert.ToHexString(depotKeyResult.DepotKey); + + if (!IsValidDepotKey(depotKey)) { + ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.ErrorIsInvalid, nameof(depotKey))); + + return; } + + if (DepotKeys.TryGetValue(depotKeyResult.DepotID, out string? previousDepotKey) && (previousDepotKey == depotKey)) { + return; + } + + DepotKeys[depotKeyResult.DepotID] = depotKey; + + Utilities.InBackground(Save); } internal void UpdatePackageTokens(IReadOnlyCollection> packageTokens) { diff --git a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/Localization/Strings.Designer.cs b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/Localization/Strings.Designer.cs index 47457ada0..c0b6af68e 100644 --- a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/Localization/Strings.Designer.cs +++ b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/Localization/Strings.Designer.cs @@ -117,12 +117,6 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Localization { } } - internal static string BotRetrievingDepotKeys { - get { - return ResourceManager.GetString("BotRetrievingDepotKeys", resourceCulture); - } - } - internal static string BotFinishedRetrievingDepotKeys { get { return ResourceManager.GetString("BotFinishedRetrievingDepotKeys", resourceCulture); diff --git a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/Localization/Strings.resx b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/Localization/Strings.resx index 64abba4a5..023a04765 100644 --- a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/Localization/Strings.resx +++ b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/Localization/Strings.resx @@ -109,13 +109,9 @@ Finished retrieving {0} app infos. {0} will be replaced by the number (count this batch) of app infos retrieved - - Retrieving {0} depot keys... - {0} will be replaced by the number (count this batch) of depot keys being retrieved - - Finished retrieving {0} depot keys. - {0} will be replaced by the number (count this batch) of depot keys retrieved + Successfully retrieved {0} out of {1} depot keys. + {0} will be replaced by the number (count this batch) of depot keys that were successfully retrieved, {1} will be replaced by the number (count this batch) of depot keys that were supposed to be retrieved Finished retrieving all depot keys for a total of {0} apps. diff --git a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs index 432a32ec6..019e07628 100644 --- a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs +++ b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs @@ -4,7 +4,7 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // | -// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki +// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki // Contact: JustArchi@JustArchi.net // | // Licensed under the Apache License, Version 2.0 (the "License"); @@ -46,6 +46,8 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper; [Export(typeof(IPlugin))] internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotCommand2, IBotSteamClient, ISteamPICSChanges { + private const ushort DepotsRateLimitingDelay = 500; + [JsonProperty] internal static SteamTokenDumperConfig? Config { get; private set; } @@ -332,6 +334,8 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC return; } + SemaphoreSlim depotsRateLimitingSemaphore = new(1, 1); + try { if (!bot.IsConnectedAndLoggedOn) { return; @@ -441,54 +445,74 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC Dictionary appChangeNumbers = new(); - HashSet> depotTasks = new(); + uint depotKeysSuccessful = 0; + uint depotKeysTotal = 0; foreach (SteamApps.PICSProductInfoCallback.PICSProductInfo app in response.Results.SelectMany(static result => result.Apps.Values)) { appChangeNumbers[app.ID] = app.ChangeNumber; if (GlobalCache.ShouldRefreshDepotKey(app.ID)) { - depotTasks.Add(bot.SteamApps.GetDepotDecryptionKey(app.ID, app.ID).ToLongRunningTask()); + depotKeysTotal++; + + await depotsRateLimitingSemaphore.WaitAsync().ConfigureAwait(false); + + try { + SteamApps.DepotKeyCallback depotResponse = await bot.SteamApps.GetDepotDecryptionKey(app.ID, app.ID).ToLongRunningTask().ConfigureAwait(false); + + depotKeysSuccessful++; + + GlobalCache.UpdateDepotKey(depotResponse); + } catch (Exception e) { + // We can still try other depots + bot.ArchiLogger.LogGenericWarningException(e); + } finally { + Utilities.InBackground( + async () => { + await Task.Delay(DepotsRateLimitingDelay).ConfigureAwait(false); + + // ReSharper disable once AccessToDisposedClosure - we're waiting for the semaphore to be free before disposing it + depotsRateLimitingSemaphore.Release(); + } + ); + } } foreach (KeyValue depot in app.KeyValues["depots"].Children) { - if (uint.TryParse(depot.Name, out uint depotID) && !Config.SecretDepotIDs.Contains(depotID) && GlobalCache.ShouldRefreshDepotKey(depotID)) { - depotTasks.Add(bot.SteamApps.GetDepotDecryptionKey(depotID, app.ID).ToLongRunningTask()); + if (!uint.TryParse(depot.Name, out uint depotID) || Config.SecretDepotIDs.Contains(depotID) || !GlobalCache.ShouldRefreshDepotKey(depotID)) { + continue; + } + + depotKeysTotal++; + + await depotsRateLimitingSemaphore.WaitAsync().ConfigureAwait(false); + + try { + SteamApps.DepotKeyCallback depotResponse = await bot.SteamApps.GetDepotDecryptionKey(depotID, app.ID).ToLongRunningTask().ConfigureAwait(false); + + depotKeysSuccessful++; + + GlobalCache.UpdateDepotKey(depotResponse); + } catch (Exception e) { + // We can still try other depots + bot.ArchiLogger.LogGenericWarningException(e); + } finally { + Utilities.InBackground( + async () => { + await Task.Delay(DepotsRateLimitingDelay).ConfigureAwait(false); + + // ReSharper disable once AccessToDisposedClosure - we're waiting for the semaphore to be free before disposing it + depotsRateLimitingSemaphore.Release(); + } + ); } } } - if (depotTasks.Count > 0) { - bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotRetrievingDepotKeys, depotTasks.Count)); + bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotFinishedRetrievingDepotKeys, depotKeysSuccessful, depotKeysTotal)); - IList results; - - try { - results = await Utilities.InParallel(depotTasks).ConfigureAwait(false); - } catch (Exception e) { - bot.ArchiLogger.LogGenericWarningException(e); - - // We can still do a recovery of tasks one-by-one if possible - results = new List(); - - foreach (Task depotTask in depotTasks.Where(static task => task.Status != TaskStatus.Faulted)) { - try { - SteamApps.DepotKeyCallback result = await depotTask.ConfigureAwait(false); - - results.Add(result); - } catch { - // We don't care anymore, already signalized the problem - } - } - } - - bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotFinishedRetrievingDepotKeys, results.Count)); - - GlobalCache.UpdateDepotKeys(results); - - if (results.Count < depotTasks.Count) { - // We're not going to record app change numbers, as we didn't fetch all the depot keys we wanted - continue; - } + if (depotKeysSuccessful < depotKeysTotal) { + // We're not going to record app change numbers, as we didn't fetch all the depot keys we wanted + continue; } GlobalCache.UpdateAppChangeNumbers(appChangeNumbers); @@ -500,7 +524,12 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC TimeSpan timeSpan = TimeSpan.FromHours(SharedInfo.MaximumHoursBetweenRefresh); synchronization.RefreshTimer.Change(timeSpan, timeSpan); + + await depotsRateLimitingSemaphore.WaitAsync().ConfigureAwait(false); + synchronization.RefreshSemaphore.Release(); + + depotsRateLimitingSemaphore.Dispose(); } }