Invalidate cache upon pics changes

PICS restart indicates that we don't know what changes exactly we've missed. If package doesn't emit any change number (and it doesn't have to), we might miss the update that happened in the meantime without being aware of that happening.
This commit is contained in:
JustArchi
2021-01-03 21:37:16 +01:00
parent c7547e3b40
commit 273a6b0fbe
4 changed files with 49 additions and 20 deletions

View File

@@ -22,6 +22,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Globalization;
using System.Linq;
@@ -258,7 +259,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
HashSet<uint> appIDsToRefresh = new();
foreach (uint packageID in packageIDs) {
if (!ASF.GlobalDatabase.PackagesDataReadOnly.TryGetValue(packageID, out (uint ChangeNumber, HashSet<uint>? AppIDs) packageData) || (packageData.AppIDs == null)) {
if (!ASF.GlobalDatabase.PackagesDataReadOnly.TryGetValue(packageID, out (uint ChangeNumber, ImmutableHashSet<uint>? AppIDs) packageData) || (packageData.AppIDs == null)) {
// ASF might not have the package info for us at the moment, we'll retry later
continue;
}

View File

@@ -1219,7 +1219,7 @@ namespace ArchiSteamFarm {
internal async Task<HashSet<uint>?> GetMarketableAppIDs() => await ArchiWebHandler.GetAppList().ConfigureAwait(false);
internal async Task<Dictionary<uint, (uint ChangeNumber, HashSet<uint>? AppIDs)>?> GetPackagesData(IReadOnlyCollection<uint> packageIDs) {
internal async Task<Dictionary<uint, (uint ChangeNumber, ImmutableHashSet<uint>? AppIDs)>?> GetPackagesData(IReadOnlyCollection<uint> packageIDs) {
if ((packageIDs == null) || (packageIDs.Count == 0)) {
throw new ArgumentNullException(nameof(packageIDs));
}
@@ -1239,7 +1239,7 @@ namespace ArchiSteamFarm {
}
if (packageRequests.Count == 0) {
return new Dictionary<uint, (uint ChangeNumber, HashSet<uint>? AppIDs)>(0);
return new Dictionary<uint, (uint ChangeNumber, ImmutableHashSet<uint>? AppIDs)>(0);
}
AsyncJobMultiple<SteamApps.PICSProductInfoCallback>.ResultSet? productInfoResultSet = null;
@@ -1256,7 +1256,7 @@ namespace ArchiSteamFarm {
return null;
}
Dictionary<uint, (uint ChangeNumber, HashSet<uint>? AppIDs)> result = new();
Dictionary<uint, (uint ChangeNumber, ImmutableHashSet<uint>? AppIDs)> result = new();
foreach (SteamApps.PICSProductInfoCallback.PICSProductInfo productInfo in productInfoResultSet.Results.SelectMany(productInfoResult => productInfoResult.Packages).Where(productInfoPackages => productInfoPackages.Key != 0).Select(productInfoPackages => productInfoPackages.Value)) {
if (productInfo.KeyValues == KeyValue.Invalid) {
@@ -1265,28 +1265,29 @@ namespace ArchiSteamFarm {
return null;
}
(uint ChangeNumber, HashSet<uint>? AppIDs) value = (productInfo.ChangeNumber, null);
uint changeNumber = productInfo.ChangeNumber;
HashSet<uint>? appIDs = null;
try {
KeyValue appIDs = productInfo.KeyValues["appids"];
KeyValue appIDsKv = productInfo.KeyValues["appids"];
if (appIDs == KeyValue.Invalid) {
if (appIDsKv == KeyValue.Invalid) {
continue;
}
value.AppIDs = new HashSet<uint>(appIDs.Children.Count);
appIDs = new HashSet<uint>(appIDsKv.Children.Count);
foreach (string? appIDText in appIDs.Children.Select(app => app.Value)) {
foreach (string? appIDText in appIDsKv.Children.Select(app => app.Value)) {
if (!uint.TryParse(appIDText, out uint appID) || (appID == 0)) {
ArchiLogger.LogNullError(nameof(appID));
return null;
}
value.AppIDs.Add(appID);
appIDs.Add(appID);
}
} finally {
result[productInfo.ID] = value;
result[productInfo.ID] = (changeNumber, appIDs?.ToImmutableHashSet());
}
}
@@ -2620,7 +2621,7 @@ namespace ArchiSteamFarm {
// Package is always due to refresh with access token change
packagesToRefresh[license.PackageID] = (uint) license.LastChangeNumber;
} else if (!ASF.GlobalDatabase.PackagesDataReadOnly.TryGetValue(license.PackageID, out (uint ChangeNumber, HashSet<uint>? AppIDs) packageData) || (packageData.ChangeNumber < license.LastChangeNumber)) {
} else if (!ASF.GlobalDatabase.PackagesDataReadOnly.TryGetValue(license.PackageID, out (uint ChangeNumber, ImmutableHashSet<uint>? AppIDs) packageData) || (packageData.ChangeNumber < license.LastChangeNumber)) {
packagesToRefresh[license.PackageID] = (uint) license.LastChangeNumber;
}
}

View File

@@ -22,6 +22,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -41,7 +42,7 @@ namespace ArchiSteamFarm {
[JsonIgnore]
[PublicAPI]
public IReadOnlyDictionary<uint, (uint ChangeNumber, HashSet<uint>? AppIDs)> PackagesDataReadOnly => PackagesData;
public IReadOnlyDictionary<uint, (uint ChangeNumber, ImmutableHashSet<uint>? AppIDs)> PackagesDataReadOnly => PackagesData;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly InMemoryServerListProvider ServerListProvider = new();
@@ -50,7 +51,7 @@ namespace ArchiSteamFarm {
private readonly ConcurrentDictionary<uint, ulong> PackagesAccessTokens = new();
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentDictionary<uint, (uint ChangeNumber, HashSet<uint>? AppIDs)> PackagesData = new();
private readonly ConcurrentDictionary<uint, (uint ChangeNumber, ImmutableHashSet<uint>? AppIDs)> PackagesData = new();
private readonly SemaphoreSlim PackagesRefreshSemaphore = new(1, 1);
@@ -151,7 +152,7 @@ namespace ArchiSteamFarm {
HashSet<uint> result = new();
foreach (uint packageID in packageIDs.Where(packageID => packageID != 0)) {
if (!PackagesData.TryGetValue(packageID, out (uint ChangeNumber, HashSet<uint>? AppIDs) packagesData) || (packagesData.AppIDs?.Contains(appID) != true)) {
if (!PackagesData.TryGetValue(packageID, out (uint ChangeNumber, ImmutableHashSet<uint>? AppIDs) packagesData) || (packagesData.AppIDs?.Contains(appID) != true)) {
continue;
}
@@ -161,6 +162,24 @@ namespace ArchiSteamFarm {
return result;
}
internal void OnPICSChangesRestart() {
bool save = false;
if (!PackagesData.IsEmpty) {
PackagesData.Clear();
save = true;
}
if (!PackagesAccessTokens.IsEmpty) {
PackagesAccessTokens.Clear();
save = true;
}
if (save) {
Utilities.InBackground(Save);
}
}
internal void RefreshPackageAccessTokens(IReadOnlyDictionary<uint, ulong> packageAccessTokens) {
if ((packageAccessTokens == null) || (packageAccessTokens.Count == 0)) {
throw new ArgumentNullException(nameof(packageAccessTokens));
@@ -192,13 +211,13 @@ namespace ArchiSteamFarm {
await PackagesRefreshSemaphore.WaitAsync().ConfigureAwait(false);
try {
HashSet<uint> packageIDs = packages.Where(package => (package.Key != 0) && (!PackagesData.TryGetValue(package.Key, out (uint ChangeNumber, HashSet<uint>? AppIDs) packageData) || (packageData.ChangeNumber < package.Value))).Select(package => package.Key).ToHashSet();
HashSet<uint> packageIDs = packages.Where(package => (package.Key != 0) && (!PackagesData.TryGetValue(package.Key, out (uint ChangeNumber, ImmutableHashSet<uint>? AppIDs) packageData) || (packageData.ChangeNumber < package.Value))).Select(package => package.Key).ToHashSet();
if (packageIDs.Count == 0) {
return;
}
Dictionary<uint, (uint ChangeNumber, HashSet<uint>? AppIDs)>? packagesData = await bot.GetPackagesData(packageIDs).ConfigureAwait(false);
Dictionary<uint, (uint ChangeNumber, ImmutableHashSet<uint>? AppIDs)>? packagesData = await bot.GetPackagesData(packageIDs).ConfigureAwait(false);
if (packagesData == null) {
bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
@@ -208,8 +227,8 @@ namespace ArchiSteamFarm {
bool save = false;
foreach ((uint packageID, (uint ChangeNumber, HashSet<uint>? AppIDs) packageData) in packagesData) {
if (PackagesData.TryGetValue(packageID, out (uint ChangeNumber, HashSet<uint>? AppIDs) previousData) && (packageData.ChangeNumber < previousData.ChangeNumber)) {
foreach ((uint packageID, (uint ChangeNumber, ImmutableHashSet<uint>? AppIDs) packageData) in packagesData) {
if (PackagesData.TryGetValue(packageID, out (uint ChangeNumber, ImmutableHashSet<uint>? AppIDs) previousData) && (packageData.ChangeNumber < previousData.ChangeNumber)) {
continue;
}

View File

@@ -63,7 +63,7 @@ namespace ArchiSteamFarm {
SteamApps.PICSChangesCallback? picsChanges = null;
for (byte i = 0; (i < WebBrowser.MaxTries) && (picsChanges == null); i++) {
refreshBot = Bot.Bots?.Values.FirstOrDefault(bot => bot.IsConnectedAndLoggedOn);
refreshBot = Bot.Bots?.Values.Where(bot => bot.IsConnectedAndLoggedOn).OrderByDescending(bot => bot.OwnedPackageIDs.Count).FirstOrDefault();
if (refreshBot == null) {
return;
@@ -89,6 +89,14 @@ namespace ArchiSteamFarm {
LastChangeNumber = picsChanges.CurrentChangeNumber;
if (picsChanges.RequiresFullAppUpdate || picsChanges.RequiresFullPackageUpdate || ((picsChanges.AppChanges.Count == 0) && (picsChanges.PackageChanges.Count == 0))) {
if (ASF.GlobalDatabase != null) {
ASF.GlobalDatabase.OnPICSChangesRestart();
if (refreshBot.OwnedPackageIDs.Count > 0) {
await ASF.GlobalDatabase.RefreshPackages(refreshBot, refreshBot.OwnedPackageIDs.Keys.ToDictionary(packageID => packageID, _ => uint.MinValue)).ConfigureAwait(false);
}
}
await PluginsCore.OnPICSChangesRestart(picsChanges.CurrentChangeNumber).ConfigureAwait(false);
return;