2024-03-17 00:06:13 +01:00
|
|
|
// ----------------------------------------------------------------------------------------------
|
2018-12-16 01:08:09 +03:00
|
|
|
// _ _ _ ____ _ _____
|
2018-12-12 22:19:52 +01:00
|
|
|
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
|
|
|
|
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
|
|
|
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
|
|
|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
2024-03-17 00:06:13 +01:00
|
|
|
// ----------------------------------------------------------------------------------------------
|
2024-03-17 02:35:40 +01:00
|
|
|
// |
|
2024-01-08 11:33:28 +01:00
|
|
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
2018-12-12 22:19:52 +01:00
|
|
|
// Contact: JustArchi@JustArchi.net
|
2024-03-17 02:35:40 +01:00
|
|
|
// |
|
2018-12-12 22:19:52 +01:00
|
|
|
// 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
|
2024-03-17 02:35:40 +01:00
|
|
|
// |
|
2018-12-12 22:19:52 +01:00
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
2024-03-17 02:35:40 +01:00
|
|
|
// |
|
2018-12-12 22:19:52 +01:00
|
|
|
// 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;
|
2020-11-28 22:06:54 +01:00
|
|
|
using System.ComponentModel;
|
2018-12-12 22:19:52 +01:00
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
2023-11-14 21:15:18 +01:00
|
|
|
using ArchiSteamFarm.Core;
|
2019-01-10 22:33:07 +01:00
|
|
|
using JetBrains.Annotations;
|
2018-12-12 22:19:52 +01:00
|
|
|
|
2021-11-10 21:23:24 +01:00
|
|
|
namespace ArchiSteamFarm.Helpers;
|
|
|
|
|
|
|
|
|
|
public sealed class ArchiCacheable<T> : IDisposable {
|
|
|
|
|
private readonly TimeSpan CacheLifetime;
|
|
|
|
|
private readonly SemaphoreSlim InitSemaphore = new(1, 1);
|
2024-01-29 18:42:21 +01:00
|
|
|
private readonly Func<CancellationToken, Task<(bool Success, T? Result)>> ResolveFunction;
|
2021-11-10 21:23:24 +01:00
|
|
|
|
|
|
|
|
private bool IsInitialized => InitializedAt > DateTime.MinValue;
|
|
|
|
|
private bool IsPermanentCache => CacheLifetime == Timeout.InfiniteTimeSpan;
|
2024-01-29 18:42:21 +01:00
|
|
|
private bool IsRecent => IsInitialized && (IsPermanentCache || (DateTime.UtcNow.Subtract(InitializedAt) < CacheLifetime));
|
2021-11-10 21:23:24 +01:00
|
|
|
|
|
|
|
|
private DateTime InitializedAt;
|
|
|
|
|
private T? InitializedValue;
|
|
|
|
|
|
2024-01-29 18:42:21 +01:00
|
|
|
public ArchiCacheable(Func<CancellationToken, Task<(bool Success, T? Result)>> resolveFunction, TimeSpan? cacheLifetime = null) {
|
2023-11-14 19:12:33 +01:00
|
|
|
ArgumentNullException.ThrowIfNull(resolveFunction);
|
|
|
|
|
|
|
|
|
|
ResolveFunction = resolveFunction;
|
2021-11-10 21:23:24 +01:00
|
|
|
CacheLifetime = cacheLifetime ?? Timeout.InfiniteTimeSpan;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose() => InitSemaphore.Dispose();
|
|
|
|
|
|
|
|
|
|
[PublicAPI]
|
2023-11-14 21:10:35 +01:00
|
|
|
public async Task<(bool Success, T? Result)> GetValue(ECacheFallback cacheFallback = ECacheFallback.DefaultForType, CancellationToken cancellationToken = default) {
|
2023-02-11 15:58:15 +01:00
|
|
|
if (!Enum.IsDefined(cacheFallback)) {
|
|
|
|
|
throw new InvalidEnumArgumentException(nameof(cacheFallback), (int) cacheFallback, typeof(ECacheFallback));
|
2018-12-12 22:19:52 +01:00
|
|
|
}
|
|
|
|
|
|
Closes #3133
After investigation, it turns out that the token actually has correct scope (THANK GOD), it's the fact that Valve started issuing those on much shorter notice than our cache.
Up until now, we played it smartly by assuming cached access token should be valid for at least 6 hours, since every time we visited the page, we got a new token that was valid for 24h since issuing. This however is no longer the case and Valve seems to recycle the same token for every request now, probably until we get close to its expiration. This also means that with unlucky timing, we might be trying to use access token that has expired already even for up to 6 more hours, which is unwanted and causes all kind of issues, with 403 in trade offers being one of them.
I could make stupid solution and cache token for shorter, e.g. 1 minute, but instead I did 200 IQ move and rewrote the functionality in a way to actually parse that token, its validity, and set the cache to be valid for a brief moment before the token actually expires. This way, we're not only more efficient (we can cache the token even for 24h if needed), but we're also invalidating it as soon as it goes out of the scope.
2024-01-29 17:53:46 +01:00
|
|
|
if (IsRecent) {
|
2021-11-10 21:23:24 +01:00
|
|
|
return (true, InitializedValue);
|
|
|
|
|
}
|
2018-12-12 22:19:52 +01:00
|
|
|
|
2023-11-14 21:15:18 +01:00
|
|
|
try {
|
|
|
|
|
await InitSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
|
} catch (OperationCanceledException e) {
|
|
|
|
|
ASF.ArchiLogger.LogGenericDebuggingException(e);
|
|
|
|
|
|
2024-03-02 01:22:47 +01:00
|
|
|
return GetFailedValueFor(cacheFallback);
|
2023-11-14 21:15:18 +01:00
|
|
|
}
|
2019-01-21 22:41:03 +01:00
|
|
|
|
2021-11-10 21:23:24 +01:00
|
|
|
try {
|
Closes #3133
After investigation, it turns out that the token actually has correct scope (THANK GOD), it's the fact that Valve started issuing those on much shorter notice than our cache.
Up until now, we played it smartly by assuming cached access token should be valid for at least 6 hours, since every time we visited the page, we got a new token that was valid for 24h since issuing. This however is no longer the case and Valve seems to recycle the same token for every request now, probably until we get close to its expiration. This also means that with unlucky timing, we might be trying to use access token that has expired already even for up to 6 more hours, which is unwanted and causes all kind of issues, with 403 in trade offers being one of them.
I could make stupid solution and cache token for shorter, e.g. 1 minute, but instead I did 200 IQ move and rewrote the functionality in a way to actually parse that token, its validity, and set the cache to be valid for a brief moment before the token actually expires. This way, we're not only more efficient (we can cache the token even for 24h if needed), but we're also invalidating it as soon as it goes out of the scope.
2024-01-29 17:53:46 +01:00
|
|
|
if (IsRecent) {
|
2018-12-12 22:19:52 +01:00
|
|
|
return (true, InitializedValue);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-29 18:42:21 +01:00
|
|
|
(bool success, T? result) = await ResolveFunction(cancellationToken).ConfigureAwait(false);
|
2018-12-12 22:19:52 +01:00
|
|
|
|
2021-11-10 21:23:24 +01:00
|
|
|
if (!success) {
|
2024-03-02 01:22:47 +01:00
|
|
|
return GetFailedValueFor(cacheFallback, result);
|
2021-11-10 21:23:24 +01:00
|
|
|
}
|
2018-12-15 00:27:15 +01:00
|
|
|
|
2021-11-10 21:23:24 +01:00
|
|
|
InitializedValue = result;
|
|
|
|
|
InitializedAt = DateTime.UtcNow;
|
2018-12-12 22:19:52 +01:00
|
|
|
|
2021-11-10 21:23:24 +01:00
|
|
|
return (true, result);
|
2023-11-14 21:15:18 +01:00
|
|
|
} catch (OperationCanceledException e) {
|
|
|
|
|
ASF.ArchiLogger.LogGenericDebuggingException(e);
|
|
|
|
|
|
2024-03-02 01:22:47 +01:00
|
|
|
return GetFailedValueFor(cacheFallback);
|
2021-11-10 21:23:24 +01:00
|
|
|
} finally {
|
|
|
|
|
InitSemaphore.Release();
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-12 22:19:52 +01:00
|
|
|
|
2021-11-10 21:23:24 +01:00
|
|
|
[PublicAPI]
|
2023-11-14 21:10:35 +01:00
|
|
|
public async Task Reset(CancellationToken cancellationToken = default) {
|
2021-11-10 21:23:24 +01:00
|
|
|
if (!IsInitialized) {
|
|
|
|
|
return;
|
2018-12-12 22:19:52 +01:00
|
|
|
}
|
|
|
|
|
|
2023-11-14 21:10:35 +01:00
|
|
|
await InitSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
2021-11-10 21:23:24 +01:00
|
|
|
|
|
|
|
|
try {
|
2018-12-12 22:19:52 +01:00
|
|
|
if (!IsInitialized) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-10 21:23:24 +01:00
|
|
|
InitializedAt = DateTime.MinValue;
|
|
|
|
|
} finally {
|
|
|
|
|
InitSemaphore.Release();
|
2018-12-12 22:19:52 +01:00
|
|
|
}
|
2021-11-10 21:23:24 +01:00
|
|
|
}
|
2023-11-14 21:15:18 +01:00
|
|
|
|
2024-03-02 01:22:47 +01:00
|
|
|
private (bool Success, T? Result) GetFailedValueFor(ECacheFallback cacheFallback, T? result = default) {
|
2023-11-22 12:09:35 +01:00
|
|
|
if (!Enum.IsDefined(cacheFallback)) {
|
2023-11-14 21:15:18 +01:00
|
|
|
throw new InvalidEnumArgumentException(nameof(cacheFallback), (int) cacheFallback, typeof(ECacheFallback));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cacheFallback switch {
|
|
|
|
|
ECacheFallback.DefaultForType => (false, default(T?)),
|
|
|
|
|
ECacheFallback.FailedNow => (false, result),
|
|
|
|
|
ECacheFallback.SuccessPreviously => (false, InitializedValue),
|
|
|
|
|
_ => throw new InvalidOperationException(nameof(cacheFallback))
|
|
|
|
|
};
|
|
|
|
|
}
|
2018-12-12 22:19:52 +01:00
|
|
|
}
|