mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2026-01-06 17:10:13 +00:00
Closes #709
This commit is contained in:
@@ -22,7 +22,6 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -356,7 +355,8 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
if ((hoursPlayed < CardsFarmer.HoursForRefund) && !BotConfig.IdleRefundableGames) {
|
||||
if (!Program.GlobalDatabase.AppIDsToPackageIDs.TryGetValue(appID, out ConcurrentHashSet<uint> packageIDs)) {
|
||||
HashSet<uint> packageIDs = Program.GlobalDatabase.GetPackageIDs(appID);
|
||||
if (packageIDs == null) {
|
||||
return (0, DateTime.MaxValue);
|
||||
}
|
||||
|
||||
@@ -497,85 +497,6 @@ namespace ArchiSteamFarm {
|
||||
return (appID, DateTime.MinValue);
|
||||
}
|
||||
|
||||
internal async Task<Dictionary<uint, HashSet<uint>>> GetAppIDsToPackageIDs(IReadOnlyCollection<uint> packageIDs) {
|
||||
if ((packageIDs == null) || (packageIDs.Count == 0)) {
|
||||
ArchiLogger.LogNullError(nameof(packageIDs));
|
||||
return null;
|
||||
}
|
||||
|
||||
AsyncJobMultiple<SteamApps.PICSProductInfoCallback>.ResultSet productInfoResultSet = null;
|
||||
|
||||
for (byte i = 0; (i < WebBrowser.MaxTries) && (productInfoResultSet == null); i++) {
|
||||
if (!IsConnectedAndLoggedOn) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await PICSSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
productInfoResultSet = await SteamApps.PICSGetProductInfo(Enumerable.Empty<uint>(), packageIDs);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
} finally {
|
||||
PICSSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
if (productInfoResultSet == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Dictionary<uint, HashSet<uint>> result = new Dictionary<uint, HashSet<uint>>();
|
||||
|
||||
foreach (KeyValuePair<uint, SteamApps.PICSProductInfoCallback.PICSProductInfo> productInfoPackages in productInfoResultSet.Results.SelectMany(productInfoResult => productInfoResult.Packages)) {
|
||||
KeyValue productInfo = productInfoPackages.Value.KeyValues;
|
||||
if (productInfo == KeyValue.Invalid) {
|
||||
ArchiLogger.LogNullError(nameof(productInfo));
|
||||
return null;
|
||||
}
|
||||
|
||||
KeyValue apps = productInfo["appids"];
|
||||
if (apps == KeyValue.Invalid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (apps.Children.Count == 0) {
|
||||
if (!result.TryGetValue(0, out HashSet<uint> packages)) {
|
||||
packages = new HashSet<uint>();
|
||||
result[0] = packages;
|
||||
}
|
||||
|
||||
packages.Add(productInfoPackages.Key);
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (string app in apps.Children.Select(app => app.Value)) {
|
||||
if (!uint.TryParse(app, out uint appID) || (appID == 0)) {
|
||||
ArchiLogger.LogNullError(nameof(appID));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!result.TryGetValue(appID, out HashSet<uint> packages)) {
|
||||
packages = new HashSet<uint>();
|
||||
result[appID] = packages;
|
||||
}
|
||||
|
||||
packages.Add(productInfoPackages.Key);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (uint packageID in productInfoResultSet.Results.SelectMany(productInfoResult => productInfoResult.UnknownPackages)) {
|
||||
if (!result.TryGetValue(0, out HashSet<uint> packages)) {
|
||||
packages = new HashSet<uint>();
|
||||
result[0] = packages;
|
||||
}
|
||||
|
||||
packages.Add(packageID);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static HashSet<Bot> GetBots(string args) {
|
||||
if (string.IsNullOrEmpty(args)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(args));
|
||||
@@ -629,6 +550,68 @@ namespace ArchiSteamFarm {
|
||||
return result;
|
||||
}
|
||||
|
||||
internal async Task<Dictionary<uint, (uint ChangeNumber, HashSet<uint> AppIDs)>> GetPackagesData(IReadOnlyCollection<uint> packageIDs) {
|
||||
if ((packageIDs == null) || (packageIDs.Count == 0)) {
|
||||
ArchiLogger.LogNullError(nameof(packageIDs));
|
||||
return null;
|
||||
}
|
||||
|
||||
AsyncJobMultiple<SteamApps.PICSProductInfoCallback>.ResultSet productInfoResultSet = null;
|
||||
|
||||
for (byte i = 0; (i < WebBrowser.MaxTries) && (productInfoResultSet == null); i++) {
|
||||
if (!IsConnectedAndLoggedOn) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await PICSSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
productInfoResultSet = await SteamApps.PICSGetProductInfo(Enumerable.Empty<uint>(), packageIDs);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
} finally {
|
||||
PICSSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
if (productInfoResultSet == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Dictionary<uint, (uint ChangeNumber, HashSet<uint> AppIDs)> result = new Dictionary<uint, (uint ChangeNumber, HashSet<uint> AppIDs)>();
|
||||
|
||||
foreach (SteamApps.PICSProductInfoCallback.PICSProductInfo productInfo in productInfoResultSet.Results.SelectMany(productInfoResult => productInfoResult.Packages).Select(productInfoPackages => productInfoPackages.Value)) {
|
||||
if (productInfo.KeyValues == KeyValue.Invalid) {
|
||||
ArchiLogger.LogNullError(nameof(productInfo));
|
||||
return null;
|
||||
}
|
||||
|
||||
(uint ChangeNumber, HashSet<uint> AppIDs) value = (productInfo.ChangeNumber, null);
|
||||
|
||||
try {
|
||||
KeyValue appIDs = productInfo.KeyValues["appids"];
|
||||
if (appIDs == KeyValue.Invalid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
value.AppIDs = new HashSet<uint>();
|
||||
|
||||
foreach (string appIDText in appIDs.Children.Select(app => app.Value)) {
|
||||
if (!uint.TryParse(appIDText, out uint appID) || (appID == 0)) {
|
||||
ArchiLogger.LogNullError(nameof(appID));
|
||||
return null;
|
||||
}
|
||||
|
||||
value.AppIDs.Add(appID);
|
||||
}
|
||||
} finally {
|
||||
result[productInfo.ID] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal async Task IdleGame(CardsFarmer.Game game) {
|
||||
if (game == null) {
|
||||
ArchiLogger.LogNullError(nameof(game));
|
||||
@@ -1808,16 +1791,28 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
OwnedPackageIDs.Clear();
|
||||
|
||||
bool refreshData = !BotConfig.IdleRefundableGames || (BotConfig.FarmingOrder == BotConfig.EFarmingOrder.RedeemDateTimesAscending) || (BotConfig.FarmingOrder == BotConfig.EFarmingOrder.RedeemDateTimesDescending);
|
||||
Dictionary<uint, uint> packagesToRefresh = new Dictionary<uint, uint>();
|
||||
|
||||
foreach (SteamApps.LicenseListCallback.License license in callback.LicenseList) {
|
||||
OwnedPackageIDs[license.PackageID] = (license.PaymentMethod, license.TimeCreated);
|
||||
|
||||
if (!refreshData) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (OwnedPackageIDs.Count > 0) {
|
||||
if (!BotConfig.IdleRefundableGames || (BotConfig.FarmingOrder == BotConfig.EFarmingOrder.RedeemDateTimesAscending) || (BotConfig.FarmingOrder == BotConfig.EFarmingOrder.RedeemDateTimesDescending)) {
|
||||
Program.GlobalDatabase.RefreshPackageIDs(this, OwnedPackageIDs.Keys.ToImmutableHashSet()).Forget();
|
||||
if (!Program.GlobalDatabase.PackagesData.TryGetValue(license.PackageID, out (uint ChangeNumber, HashSet<uint> _) packageData) || (packageData.ChangeNumber < license.LastChangeNumber)) {
|
||||
packagesToRefresh[license.PackageID] = (uint) license.LastChangeNumber;
|
||||
}
|
||||
}
|
||||
|
||||
if (packagesToRefresh.Count > 0) {
|
||||
ArchiLogger.LogGenericInfo(Strings.BotRefreshingPackagesData);
|
||||
await Program.GlobalDatabase.RefreshPackageIDs(this, packagesToRefresh).ConfigureAwait(false);
|
||||
ArchiLogger.LogGenericInfo(Strings.Done);
|
||||
}
|
||||
|
||||
// Wait a second for eventual PlayingSessionStateCallback or SharedLibraryLockStatusCallback
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
|
||||
|
||||
@@ -969,7 +969,9 @@ namespace ArchiSteamFarm {
|
||||
|
||||
foreach (Game game in GamesToFarm) {
|
||||
DateTime redeemDate = DateTime.MinValue;
|
||||
if (Program.GlobalDatabase.AppIDsToPackageIDs.TryGetValue(game.AppID, out ConcurrentHashSet<uint> packageIDs)) {
|
||||
HashSet<uint> packageIDs = Program.GlobalDatabase.GetPackageIDs(game.AppID);
|
||||
|
||||
if (packageIDs != null) {
|
||||
foreach (uint packageID in packageIDs) {
|
||||
if (!Bot.OwnedPackageIDs.TryGetValue(packageID, out (EPaymentMethod PaymentMethod, DateTime TimeCreated) packageData)) {
|
||||
continue;
|
||||
|
||||
@@ -31,10 +31,10 @@ using Newtonsoft.Json;
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class GlobalDatabase : IDisposable {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly ConcurrentDictionary<uint, ConcurrentHashSet<uint>> AppIDsToPackageIDs = new ConcurrentDictionary<uint, ConcurrentHashSet<uint>>();
|
||||
internal readonly Guid Guid = Guid.NewGuid();
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly Guid Guid = Guid.NewGuid();
|
||||
internal readonly ConcurrentDictionary<uint, (uint ChangeNumber, HashSet<uint> AppIDs)> PackagesData = new ConcurrentDictionary<uint, (uint ChangeNumber, HashSet<uint> AppIDs)>();
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly InMemoryServerListProvider ServerListProvider = new InMemoryServerListProvider();
|
||||
@@ -69,6 +69,16 @@ namespace ArchiSteamFarm {
|
||||
PackagesRefreshSemaphore.Dispose();
|
||||
}
|
||||
|
||||
internal HashSet<uint> GetPackageIDs(uint appID) {
|
||||
if (appID == 0) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(appID));
|
||||
return null;
|
||||
}
|
||||
|
||||
HashSet<uint> result = new HashSet<uint>(PackagesData.Where(package => (package.Value.AppIDs != null) && package.Value.AppIDs.Contains(appID)).Select(package => package.Key));
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static GlobalDatabase Load(string filePath) {
|
||||
if (string.IsNullOrEmpty(filePath)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(filePath));
|
||||
@@ -97,34 +107,30 @@ namespace ArchiSteamFarm {
|
||||
return globalDatabase;
|
||||
}
|
||||
|
||||
internal async Task RefreshPackageIDs(Bot bot, IReadOnlyCollection<uint> packageIDs) {
|
||||
if ((bot == null) || (packageIDs == null) || (packageIDs.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(packageIDs));
|
||||
internal async Task RefreshPackageIDs(Bot bot, IReadOnlyDictionary<uint, uint> packages) {
|
||||
if ((bot == null) || (packages == null) || (packages.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(packages));
|
||||
return;
|
||||
}
|
||||
|
||||
await PackagesRefreshSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
HashSet<uint> missingPackageIDs = new HashSet<uint>(packageIDs.AsParallel().Where(packageID => AppIDsToPackageIDs.Values.All(packages => !packages.Contains(packageID))));
|
||||
if (missingPackageIDs.Count == 0) {
|
||||
HashSet<uint> packageIDs = new HashSet<uint>();
|
||||
|
||||
foreach (KeyValuePair<uint, uint> package in packages) {
|
||||
if (!PackagesData.TryGetValue(package.Key, out (uint ChangeNumber, HashSet<uint> _) packageData) || (packageData.ChangeNumber < package.Value)) {
|
||||
packageIDs.Add(package.Key);
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<uint, (uint ChangeNumber, HashSet<uint> AppIDs)> packagesData = await bot.GetPackagesData(packageIDs).ConfigureAwait(false);
|
||||
if ((packagesData == null) || (packagesData.Count == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<uint, HashSet<uint>> appIDsToPackageIDs = await bot.GetAppIDsToPackageIDs(missingPackageIDs).ConfigureAwait(false);
|
||||
if ((appIDsToPackageIDs == null) || (appIDsToPackageIDs.Count == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<uint, HashSet<uint>> appIDtoPackageID in appIDsToPackageIDs) {
|
||||
if (!AppIDsToPackageIDs.TryGetValue(appIDtoPackageID.Key, out ConcurrentHashSet<uint> packages)) {
|
||||
packages = new ConcurrentHashSet<uint>();
|
||||
AppIDsToPackageIDs[appIDtoPackageID.Key] = packages;
|
||||
}
|
||||
|
||||
foreach (uint package in appIDtoPackageID.Value) {
|
||||
packages.Add(package);
|
||||
}
|
||||
foreach (KeyValuePair<uint, (uint ChangeNumber, HashSet<uint> AppIDs)> packageData in packagesData) {
|
||||
PackagesData[packageData.Key] = packageData.Value;
|
||||
}
|
||||
|
||||
await Save().ConfigureAwait(false);
|
||||
|
||||
9
ArchiSteamFarm/Localization/Strings.Designer.cs
generated
9
ArchiSteamFarm/Localization/Strings.Designer.cs
generated
@@ -528,6 +528,15 @@ namespace ArchiSteamFarm.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Refreshing packages data....
|
||||
/// </summary>
|
||||
internal static string BotRefreshingPackagesData {
|
||||
get {
|
||||
return ResourceManager.GetString("BotRefreshingPackagesData", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Removed expired login key!.
|
||||
/// </summary>
|
||||
|
||||
@@ -662,4 +662,7 @@ StackTrace:
|
||||
<value>There are {0}/{1} bots that already own all of the games being checked.</value>
|
||||
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
|
||||
</data>
|
||||
<data name="BotRefreshingPackagesData" xml:space="preserve">
|
||||
<value>Refreshing packages data...</value>
|
||||
</data>
|
||||
</root>
|
||||
Reference in New Issue
Block a user