mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2026-01-01 06:00:46 +00:00
Closes #3067
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Web;
|
||||
using ArchiSteamFarm.Web.Responses;
|
||||
@@ -34,12 +35,12 @@ namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin;
|
||||
internal static class CatAPI {
|
||||
private const string URL = "https://api.thecatapi.com";
|
||||
|
||||
internal static async Task<Uri?> GetRandomCatURL(WebBrowser webBrowser) {
|
||||
internal static async Task<Uri?> GetRandomCatURL(WebBrowser webBrowser, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(webBrowser);
|
||||
|
||||
Uri request = new($"{URL}/v1/images/search");
|
||||
|
||||
ObjectResponse<ImmutableList<MeowResponse>>? response = await webBrowser.UrlGetToJsonObject<ImmutableList<MeowResponse>>(request).ConfigureAwait(false);
|
||||
ObjectResponse<ImmutableList<MeowResponse>>? response = await webBrowser.UrlGetToJsonObject<ImmutableList<MeowResponse>>(request, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return response?.Content?.FirstOrDefault()?.URL;
|
||||
}
|
||||
|
||||
@@ -77,8 +77,8 @@ internal sealed class CrossProcessFileBasedSemaphore : IAsyncDisposable, ICrossP
|
||||
LocalSemaphore.Release();
|
||||
}
|
||||
|
||||
async Task ICrossProcessSemaphore.WaitAsync() {
|
||||
await LocalSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
async Task ICrossProcessSemaphore.WaitAsync(CancellationToken cancellationToken) {
|
||||
await LocalSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
bool success = false;
|
||||
|
||||
@@ -99,7 +99,7 @@ internal sealed class CrossProcessFileBasedSemaphore : IAsyncDisposable, ICrossP
|
||||
return;
|
||||
}
|
||||
} catch (IOException) {
|
||||
await Task.Delay(SpinLockDelay).ConfigureAwait(false);
|
||||
await Task.Delay(SpinLockDelay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -109,10 +109,10 @@ internal sealed class CrossProcessFileBasedSemaphore : IAsyncDisposable, ICrossP
|
||||
}
|
||||
}
|
||||
|
||||
async Task<bool> ICrossProcessSemaphore.WaitAsync(int millisecondsTimeout) {
|
||||
async Task<bool> ICrossProcessSemaphore.WaitAsync(int millisecondsTimeout, CancellationToken cancellationToken) {
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
|
||||
if (!await LocalSemaphore.WaitAsync(millisecondsTimeout).ConfigureAwait(false)) {
|
||||
if (!await LocalSemaphore.WaitAsync(millisecondsTimeout, cancellationToken).ConfigureAwait(false)) {
|
||||
stopwatch.Stop();
|
||||
|
||||
return false;
|
||||
@@ -149,7 +149,7 @@ internal sealed class CrossProcessFileBasedSemaphore : IAsyncDisposable, ICrossP
|
||||
return false;
|
||||
}
|
||||
|
||||
await Task.Delay(SpinLockDelay).ConfigureAwait(false);
|
||||
await Task.Delay(SpinLockDelay, cancellationToken).ConfigureAwait(false);
|
||||
millisecondsTimeout -= SpinLockDelay;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
@@ -27,6 +28,6 @@ namespace ArchiSteamFarm.Helpers;
|
||||
[PublicAPI]
|
||||
public interface ICrossProcessSemaphore {
|
||||
void Release();
|
||||
Task WaitAsync();
|
||||
Task<bool> WaitAsync(int millisecondsTimeout);
|
||||
Task WaitAsync(CancellationToken cancellationToken = default);
|
||||
Task<bool> WaitAsync(int millisecondsTimeout, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.IPC.Responses;
|
||||
using ArchiSteamFarm.Localization;
|
||||
@@ -44,7 +45,9 @@ public sealed class GitHubController : ArchiController {
|
||||
[ProducesResponseType(typeof(GenericResponse<GitHubReleaseResponse>), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
|
||||
public async Task<ActionResult<GenericResponse>> GitHubReleaseGet() {
|
||||
GitHub.ReleaseResponse? releaseResponse = await GitHub.GetLatestRelease(false).ConfigureAwait(false);
|
||||
CancellationToken cancellationToken = HttpContext.RequestAborted;
|
||||
|
||||
GitHub.ReleaseResponse? releaseResponse = await GitHub.GetLatestRelease(false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return releaseResponse != null ? Ok(new GenericResponse<GitHubReleaseResponse>(new GitHubReleaseResponse(releaseResponse))) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)));
|
||||
}
|
||||
@@ -62,11 +65,13 @@ public sealed class GitHubController : ArchiController {
|
||||
public async Task<ActionResult<GenericResponse>> GitHubReleaseGet(string version) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(version);
|
||||
|
||||
CancellationToken cancellationToken = HttpContext.RequestAborted;
|
||||
|
||||
GitHub.ReleaseResponse? releaseResponse;
|
||||
|
||||
switch (version.ToUpperInvariant()) {
|
||||
case "LATEST":
|
||||
releaseResponse = await GitHub.GetLatestRelease().ConfigureAwait(false);
|
||||
releaseResponse = await GitHub.GetLatestRelease(cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
break;
|
||||
default:
|
||||
@@ -74,7 +79,7 @@ public sealed class GitHubController : ArchiController {
|
||||
return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(version))));
|
||||
}
|
||||
|
||||
releaseResponse = await GitHub.GetRelease(parsedVersion.ToString(4)).ConfigureAwait(false);
|
||||
releaseResponse = await GitHub.GetRelease(parsedVersion.ToString(4), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -95,7 +100,9 @@ public sealed class GitHubController : ArchiController {
|
||||
public async Task<ActionResult<GenericResponse>> GitHubWikiHistoryGet(string page) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(page);
|
||||
|
||||
Dictionary<string, DateTime>? revisions = await GitHub.GetWikiHistory(page).ConfigureAwait(false);
|
||||
CancellationToken cancellationToken = HttpContext.RequestAborted;
|
||||
|
||||
Dictionary<string, DateTime>? revisions = await GitHub.GetWikiHistory(page, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return revisions != null ? revisions.Count > 0 ? Ok(new GenericResponse<ImmutableDictionary<string, DateTime>>(revisions.ToImmutableDictionary())) : BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(page)))) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)));
|
||||
}
|
||||
@@ -114,7 +121,9 @@ public sealed class GitHubController : ArchiController {
|
||||
public async Task<ActionResult<GenericResponse>> GitHubWikiPageGet(string page, [FromQuery] string? revision = null) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(page);
|
||||
|
||||
string? html = await GitHub.GetWikiPage(page, revision).ConfigureAwait(false);
|
||||
CancellationToken cancellationToken = HttpContext.RequestAborted;
|
||||
|
||||
string? html = await GitHub.GetWikiPage(page, revision, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return html switch {
|
||||
null => StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries))),
|
||||
|
||||
@@ -795,7 +795,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<HtmlDocumentResponse?> UrlGetToHtmlDocumentWithSession(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries, int rateLimitingDelay = 0, bool allowSessionRefresh = true) {
|
||||
public async Task<HtmlDocumentResponse?> UrlGetToHtmlDocumentWithSession(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries, int rateLimitingDelay = 0, bool allowSessionRefresh = true, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentOutOfRangeException.ThrowIfZero(maxTries);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(rateLimitingDelay);
|
||||
@@ -810,7 +810,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
|
||||
if (sessionExpired.GetValueOrDefault(true)) {
|
||||
if (allowSessionRefresh && await RefreshSession().ConfigureAwait(false)) {
|
||||
return await UrlGetToHtmlDocumentWithSession(request, headers, referer, requestOptions, true, maxTries, rateLimitingDelay, false).ConfigureAwait(false);
|
||||
return await UrlGetToHtmlDocumentWithSession(request, headers, referer, requestOptions, true, maxTries, rateLimitingDelay, false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
|
||||
@@ -820,7 +820,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
}
|
||||
} else {
|
||||
// If session refresh is already in progress, just wait for it
|
||||
await SessionSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await SessionSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
SessionSemaphore.Release();
|
||||
}
|
||||
|
||||
@@ -828,7 +828,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
byte connectionTimeout = ASF.GlobalConfig?.ConnectionTimeout ?? GlobalConfig.DefaultConnectionTimeout;
|
||||
|
||||
for (byte i = 0; (i < connectionTimeout) && !Initialized && Bot.IsConnectedAndLoggedOn; i++) {
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!Initialized) {
|
||||
@@ -842,7 +842,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
Uri host = new(request.GetLeftPart(UriPartial.Authority));
|
||||
|
||||
// ReSharper disable once AccessToModifiedClosure - evaluated fully before returning
|
||||
HtmlDocumentResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlGetToHtmlDocument(request, headers, referer, requestOptions, maxTries, rateLimitingDelay).ConfigureAwait(false)).ConfigureAwait(false);
|
||||
HtmlDocumentResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlGetToHtmlDocument(request, headers, referer, requestOptions, maxTries, rateLimitingDelay, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
return null;
|
||||
@@ -850,7 +850,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
|
||||
if (IsSessionExpiredUri(response.FinalUri)) {
|
||||
if (allowSessionRefresh && await RefreshSession().ConfigureAwait(false)) {
|
||||
return await UrlGetToHtmlDocumentWithSession(request, headers, referer, requestOptions, checkSessionPreemptively, maxTries, rateLimitingDelay, false).ConfigureAwait(false);
|
||||
return await UrlGetToHtmlDocumentWithSession(request, headers, referer, requestOptions, checkSessionPreemptively, maxTries, rateLimitingDelay, false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
|
||||
@@ -870,14 +870,14 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await UrlGetToHtmlDocumentWithSession(request, headers, referer, requestOptions, checkSessionPreemptively, maxTries, rateLimitingDelay, allowSessionRefresh).ConfigureAwait(false);
|
||||
return await UrlGetToHtmlDocumentWithSession(request, headers, referer, requestOptions, checkSessionPreemptively, maxTries, rateLimitingDelay, allowSessionRefresh, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<ObjectResponse<T>?> UrlGetToJsonObjectWithSession<T>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries, int rateLimitingDelay = 0, bool allowSessionRefresh = true) {
|
||||
public async Task<ObjectResponse<T>?> UrlGetToJsonObjectWithSession<T>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries, int rateLimitingDelay = 0, bool allowSessionRefresh = true, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentOutOfRangeException.ThrowIfZero(maxTries);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(rateLimitingDelay);
|
||||
@@ -892,7 +892,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
|
||||
if (sessionExpired.GetValueOrDefault(true)) {
|
||||
if (allowSessionRefresh && await RefreshSession().ConfigureAwait(false)) {
|
||||
return await UrlGetToJsonObjectWithSession<T>(request, headers, referer, requestOptions, true, maxTries, rateLimitingDelay, false).ConfigureAwait(false);
|
||||
return await UrlGetToJsonObjectWithSession<T>(request, headers, referer, requestOptions, true, maxTries, rateLimitingDelay, false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
|
||||
@@ -902,7 +902,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
}
|
||||
} else {
|
||||
// If session refresh is already in progress, just wait for it
|
||||
await SessionSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await SessionSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
SessionSemaphore.Release();
|
||||
}
|
||||
|
||||
@@ -910,7 +910,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
byte connectionTimeout = ASF.GlobalConfig?.ConnectionTimeout ?? GlobalConfig.DefaultConnectionTimeout;
|
||||
|
||||
for (byte i = 0; (i < connectionTimeout) && !Initialized && Bot.IsConnectedAndLoggedOn; i++) {
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!Initialized) {
|
||||
@@ -924,7 +924,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
Uri host = new(request.GetLeftPart(UriPartial.Authority));
|
||||
|
||||
// ReSharper disable once AccessToModifiedClosure - evaluated fully before returning
|
||||
ObjectResponse<T>? response = await WebLimitRequest(host, async () => await WebBrowser.UrlGetToJsonObject<T>(request, headers, referer, requestOptions, maxTries, rateLimitingDelay).ConfigureAwait(false)).ConfigureAwait(false);
|
||||
ObjectResponse<T>? response = await WebLimitRequest(host, async () => await WebBrowser.UrlGetToJsonObject<T>(request, headers, referer, requestOptions, maxTries, rateLimitingDelay, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
return default(ObjectResponse<T>?);
|
||||
@@ -932,7 +932,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
|
||||
if (IsSessionExpiredUri(response.FinalUri)) {
|
||||
if (allowSessionRefresh && await RefreshSession().ConfigureAwait(false)) {
|
||||
return await UrlGetToJsonObjectWithSession<T>(request, headers, referer, requestOptions, checkSessionPreemptively, maxTries, rateLimitingDelay, false).ConfigureAwait(false);
|
||||
return await UrlGetToJsonObjectWithSession<T>(request, headers, referer, requestOptions, checkSessionPreemptively, maxTries, rateLimitingDelay, false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
|
||||
@@ -952,14 +952,14 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await UrlGetToJsonObjectWithSession<T>(request, headers, referer, requestOptions, checkSessionPreemptively, maxTries, rateLimitingDelay, allowSessionRefresh).ConfigureAwait(false);
|
||||
return await UrlGetToJsonObjectWithSession<T>(request, headers, referer, requestOptions, checkSessionPreemptively, maxTries, rateLimitingDelay, allowSessionRefresh, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<bool> UrlHeadWithSession(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries, int rateLimitingDelay = 0, bool allowSessionRefresh = true) {
|
||||
public async Task<bool> UrlHeadWithSession(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries, int rateLimitingDelay = 0, bool allowSessionRefresh = true, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentOutOfRangeException.ThrowIfZero(maxTries);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(rateLimitingDelay);
|
||||
@@ -974,7 +974,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
|
||||
if (sessionExpired.GetValueOrDefault(true)) {
|
||||
if (allowSessionRefresh && await RefreshSession().ConfigureAwait(false)) {
|
||||
return await UrlHeadWithSession(request, headers, referer, requestOptions, true, maxTries, rateLimitingDelay, false).ConfigureAwait(false);
|
||||
return await UrlHeadWithSession(request, headers, referer, requestOptions, true, maxTries, rateLimitingDelay, false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
|
||||
@@ -984,7 +984,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
}
|
||||
} else {
|
||||
// If session refresh is already in progress, just wait for it
|
||||
await SessionSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await SessionSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
SessionSemaphore.Release();
|
||||
}
|
||||
|
||||
@@ -992,7 +992,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
byte connectionTimeout = ASF.GlobalConfig?.ConnectionTimeout ?? GlobalConfig.DefaultConnectionTimeout;
|
||||
|
||||
for (byte i = 0; (i < connectionTimeout) && !Initialized && Bot.IsConnectedAndLoggedOn; i++) {
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!Initialized) {
|
||||
@@ -1006,7 +1006,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
Uri host = new(request.GetLeftPart(UriPartial.Authority));
|
||||
|
||||
// ReSharper disable once AccessToModifiedClosure - evaluated fully before returning
|
||||
BasicResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlHead(request, headers, referer, requestOptions, maxTries, rateLimitingDelay).ConfigureAwait(false)).ConfigureAwait(false);
|
||||
BasicResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlHead(request, headers, referer, requestOptions, maxTries, rateLimitingDelay, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
return false;
|
||||
@@ -1014,7 +1014,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
|
||||
if (IsSessionExpiredUri(response.FinalUri)) {
|
||||
if (allowSessionRefresh && await RefreshSession().ConfigureAwait(false)) {
|
||||
return await UrlHeadWithSession(request, headers, referer, requestOptions, checkSessionPreemptively, maxTries, rateLimitingDelay, false).ConfigureAwait(false);
|
||||
return await UrlHeadWithSession(request, headers, referer, requestOptions, checkSessionPreemptively, maxTries, rateLimitingDelay, false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
|
||||
@@ -1034,14 +1034,14 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
return false;
|
||||
}
|
||||
|
||||
return await UrlHeadWithSession(request, headers, referer, requestOptions, checkSessionPreemptively, maxTries, rateLimitingDelay, allowSessionRefresh).ConfigureAwait(false);
|
||||
return await UrlHeadWithSession(request, headers, referer, requestOptions, checkSessionPreemptively, maxTries, rateLimitingDelay, allowSessionRefresh, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<HtmlDocumentResponse?> UrlPostToHtmlDocumentWithSession(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, IDictionary<string, string>? data = null, Uri? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, ESession session = ESession.Lowercase, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries, int rateLimitingDelay = 0, bool allowSessionRefresh = true) {
|
||||
public async Task<HtmlDocumentResponse?> UrlPostToHtmlDocumentWithSession(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, IDictionary<string, string>? data = null, Uri? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, ESession session = ESession.Lowercase, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries, int rateLimitingDelay = 0, bool allowSessionRefresh = true, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
if (!Enum.IsDefined(session)) {
|
||||
@@ -1061,7 +1061,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
|
||||
if (sessionExpired.GetValueOrDefault(true)) {
|
||||
if (allowSessionRefresh && await RefreshSession().ConfigureAwait(false)) {
|
||||
return await UrlPostToHtmlDocumentWithSession(request, headers, data, referer, requestOptions, session, true, maxTries, rateLimitingDelay, false).ConfigureAwait(false);
|
||||
return await UrlPostToHtmlDocumentWithSession(request, headers, data, referer, requestOptions, session, true, maxTries, rateLimitingDelay, false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
|
||||
@@ -1071,7 +1071,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
}
|
||||
} else {
|
||||
// If session refresh is already in progress, just wait for it
|
||||
await SessionSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await SessionSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
SessionSemaphore.Release();
|
||||
}
|
||||
|
||||
@@ -1079,7 +1079,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
byte connectionTimeout = ASF.GlobalConfig?.ConnectionTimeout ?? GlobalConfig.DefaultConnectionTimeout;
|
||||
|
||||
for (byte i = 0; (i < connectionTimeout) && !Initialized && Bot.IsConnectedAndLoggedOn; i++) {
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!Initialized) {
|
||||
@@ -1116,7 +1116,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
}
|
||||
|
||||
// ReSharper disable once AccessToModifiedClosure - evaluated fully before returning
|
||||
HtmlDocumentResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlPostToHtmlDocument(request, headers, data, referer, requestOptions, maxTries, rateLimitingDelay).ConfigureAwait(false)).ConfigureAwait(false);
|
||||
HtmlDocumentResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlPostToHtmlDocument(request, headers, data, referer, requestOptions, maxTries, rateLimitingDelay, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
return null;
|
||||
@@ -1124,7 +1124,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
|
||||
if (IsSessionExpiredUri(response.FinalUri)) {
|
||||
if (allowSessionRefresh && await RefreshSession().ConfigureAwait(false)) {
|
||||
return await UrlPostToHtmlDocumentWithSession(request, headers, data, referer, requestOptions, session, checkSessionPreemptively, maxTries, rateLimitingDelay, false).ConfigureAwait(false);
|
||||
return await UrlPostToHtmlDocumentWithSession(request, headers, data, referer, requestOptions, session, checkSessionPreemptively, maxTries, rateLimitingDelay, false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
|
||||
@@ -1144,14 +1144,14 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await UrlPostToHtmlDocumentWithSession(request, headers, data, referer, requestOptions, session, checkSessionPreemptively, maxTries, rateLimitingDelay, allowSessionRefresh).ConfigureAwait(false);
|
||||
return await UrlPostToHtmlDocumentWithSession(request, headers, data, referer, requestOptions, session, checkSessionPreemptively, maxTries, rateLimitingDelay, allowSessionRefresh, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<ObjectResponse<T>?> UrlPostToJsonObjectWithSession<T>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, IDictionary<string, string>? data = null, Uri? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, ESession session = ESession.Lowercase, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries, int rateLimitingDelay = 0, bool allowSessionRefresh = true) {
|
||||
public async Task<ObjectResponse<T>?> UrlPostToJsonObjectWithSession<T>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, IDictionary<string, string>? data = null, Uri? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, ESession session = ESession.Lowercase, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries, int rateLimitingDelay = 0, bool allowSessionRefresh = true, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
if (!Enum.IsDefined(session)) {
|
||||
@@ -1171,7 +1171,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
|
||||
if (sessionExpired.GetValueOrDefault(true)) {
|
||||
if (allowSessionRefresh && await RefreshSession().ConfigureAwait(false)) {
|
||||
return await UrlPostToJsonObjectWithSession<T>(request, headers, data, referer, requestOptions, session, true, maxTries, rateLimitingDelay, false).ConfigureAwait(false);
|
||||
return await UrlPostToJsonObjectWithSession<T>(request, headers, data, referer, requestOptions, session, true, maxTries, rateLimitingDelay, false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
|
||||
@@ -1181,7 +1181,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
}
|
||||
} else {
|
||||
// If session refresh is already in progress, just wait for it
|
||||
await SessionSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await SessionSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
SessionSemaphore.Release();
|
||||
}
|
||||
|
||||
@@ -1189,7 +1189,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
byte connectionTimeout = ASF.GlobalConfig?.ConnectionTimeout ?? GlobalConfig.DefaultConnectionTimeout;
|
||||
|
||||
for (byte i = 0; (i < connectionTimeout) && !Initialized && Bot.IsConnectedAndLoggedOn; i++) {
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!Initialized) {
|
||||
@@ -1226,7 +1226,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
}
|
||||
|
||||
// ReSharper disable once AccessToModifiedClosure - evaluated fully before returning
|
||||
ObjectResponse<T>? response = await WebLimitRequest(host, async () => await WebBrowser.UrlPostToJsonObject<T, IDictionary<string, string>>(request, headers, data, referer, requestOptions, maxTries, rateLimitingDelay).ConfigureAwait(false)).ConfigureAwait(false);
|
||||
ObjectResponse<T>? response = await WebLimitRequest(host, async () => await WebBrowser.UrlPostToJsonObject<T, IDictionary<string, string>>(request, headers, data, referer, requestOptions, maxTries, rateLimitingDelay, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
return null;
|
||||
@@ -1234,7 +1234,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
|
||||
if (IsSessionExpiredUri(response.FinalUri)) {
|
||||
if (allowSessionRefresh && await RefreshSession().ConfigureAwait(false)) {
|
||||
return await UrlPostToJsonObjectWithSession<T>(request, headers, data, referer, requestOptions, session, checkSessionPreemptively, maxTries, rateLimitingDelay, false).ConfigureAwait(false);
|
||||
return await UrlPostToJsonObjectWithSession<T>(request, headers, data, referer, requestOptions, session, checkSessionPreemptively, maxTries, rateLimitingDelay, false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
|
||||
@@ -1254,14 +1254,14 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await UrlPostToJsonObjectWithSession<T>(request, headers, data, referer, requestOptions, session, checkSessionPreemptively, maxTries, rateLimitingDelay, allowSessionRefresh).ConfigureAwait(false);
|
||||
return await UrlPostToJsonObjectWithSession<T>(request, headers, data, referer, requestOptions, session, checkSessionPreemptively, maxTries, rateLimitingDelay, allowSessionRefresh, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<ObjectResponse<T>?> UrlPostToJsonObjectWithSession<T>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, ICollection<KeyValuePair<string, string>>? data = null, Uri? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, ESession session = ESession.Lowercase, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries, int rateLimitingDelay = 0, bool allowSessionRefresh = true) {
|
||||
public async Task<ObjectResponse<T>?> UrlPostToJsonObjectWithSession<T>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, ICollection<KeyValuePair<string, string>>? data = null, Uri? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, ESession session = ESession.Lowercase, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries, int rateLimitingDelay = 0, bool allowSessionRefresh = true, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
if (!Enum.IsDefined(session)) {
|
||||
@@ -1281,7 +1281,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
|
||||
if (sessionExpired.GetValueOrDefault(true)) {
|
||||
if (allowSessionRefresh && await RefreshSession().ConfigureAwait(false)) {
|
||||
return await UrlPostToJsonObjectWithSession<T>(request, headers, data, referer, requestOptions, session, true, maxTries, rateLimitingDelay, false).ConfigureAwait(false);
|
||||
return await UrlPostToJsonObjectWithSession<T>(request, headers, data, referer, requestOptions, session, true, maxTries, rateLimitingDelay, false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
|
||||
@@ -1291,7 +1291,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
}
|
||||
} else {
|
||||
// If session refresh is already in progress, just wait for it
|
||||
await SessionSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await SessionSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
SessionSemaphore.Release();
|
||||
}
|
||||
|
||||
@@ -1299,7 +1299,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
byte connectionTimeout = ASF.GlobalConfig?.ConnectionTimeout ?? GlobalConfig.DefaultConnectionTimeout;
|
||||
|
||||
for (byte i = 0; (i < connectionTimeout) && !Initialized && Bot.IsConnectedAndLoggedOn; i++) {
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!Initialized) {
|
||||
@@ -1339,7 +1339,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
}
|
||||
|
||||
// ReSharper disable once AccessToModifiedClosure - evaluated fully before returning
|
||||
ObjectResponse<T>? response = await WebLimitRequest(host, async () => await WebBrowser.UrlPostToJsonObject<T, ICollection<KeyValuePair<string, string>>>(request, headers, data, referer, requestOptions, maxTries, rateLimitingDelay).ConfigureAwait(false)).ConfigureAwait(false);
|
||||
ObjectResponse<T>? response = await WebLimitRequest(host, async () => await WebBrowser.UrlPostToJsonObject<T, ICollection<KeyValuePair<string, string>>>(request, headers, data, referer, requestOptions, maxTries, rateLimitingDelay, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
return null;
|
||||
@@ -1347,7 +1347,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
|
||||
if (IsSessionExpiredUri(response.FinalUri)) {
|
||||
if (allowSessionRefresh && await RefreshSession().ConfigureAwait(false)) {
|
||||
return await UrlPostToJsonObjectWithSession<T>(request, headers, data, referer, requestOptions, session, checkSessionPreemptively, maxTries, rateLimitingDelay, false).ConfigureAwait(false);
|
||||
return await UrlPostToJsonObjectWithSession<T>(request, headers, data, referer, requestOptions, session, checkSessionPreemptively, maxTries, rateLimitingDelay, false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
|
||||
@@ -1367,14 +1367,14 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await UrlPostToJsonObjectWithSession<T>(request, headers, data, referer, requestOptions, session, checkSessionPreemptively, maxTries, rateLimitingDelay, allowSessionRefresh).ConfigureAwait(false);
|
||||
return await UrlPostToJsonObjectWithSession<T>(request, headers, data, referer, requestOptions, session, checkSessionPreemptively, maxTries, rateLimitingDelay, allowSessionRefresh, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<bool> UrlPostWithSession(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, IDictionary<string, string>? data = null, Uri? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, ESession session = ESession.Lowercase, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries, int rateLimitingDelay = 0, bool allowSessionRefresh = true) {
|
||||
public async Task<bool> UrlPostWithSession(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, IDictionary<string, string>? data = null, Uri? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, ESession session = ESession.Lowercase, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries, int rateLimitingDelay = 0, bool allowSessionRefresh = true, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
if (!Enum.IsDefined(session)) {
|
||||
@@ -1394,7 +1394,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
|
||||
if (sessionExpired.GetValueOrDefault(true)) {
|
||||
if (allowSessionRefresh && await RefreshSession().ConfigureAwait(false)) {
|
||||
return await UrlPostWithSession(request, headers, data, referer, requestOptions, session, true, maxTries, rateLimitingDelay, false).ConfigureAwait(false);
|
||||
return await UrlPostWithSession(request, headers, data, referer, requestOptions, session, true, maxTries, rateLimitingDelay, false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
|
||||
@@ -1404,7 +1404,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
}
|
||||
} else {
|
||||
// If session refresh is already in progress, just wait for it
|
||||
await SessionSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await SessionSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
SessionSemaphore.Release();
|
||||
}
|
||||
|
||||
@@ -1412,7 +1412,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
byte connectionTimeout = ASF.GlobalConfig?.ConnectionTimeout ?? GlobalConfig.DefaultConnectionTimeout;
|
||||
|
||||
for (byte i = 0; (i < connectionTimeout) && !Initialized && Bot.IsConnectedAndLoggedOn; i++) {
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!Initialized) {
|
||||
@@ -1449,7 +1449,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
}
|
||||
|
||||
// ReSharper disable once AccessToModifiedClosure - evaluated fully before returning
|
||||
BasicResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlPost(request, headers, data, referer, requestOptions, maxTries, rateLimitingDelay).ConfigureAwait(false)).ConfigureAwait(false);
|
||||
BasicResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlPost(request, headers, data, referer, requestOptions, maxTries, rateLimitingDelay, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
return false;
|
||||
@@ -1457,7 +1457,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
|
||||
if (IsSessionExpiredUri(response.FinalUri)) {
|
||||
if (allowSessionRefresh && await RefreshSession().ConfigureAwait(false)) {
|
||||
return await UrlPostWithSession(request, headers, data, referer, requestOptions, session, checkSessionPreemptively, maxTries, rateLimitingDelay, false).ConfigureAwait(false);
|
||||
return await UrlPostWithSession(request, headers, data, referer, requestOptions, session, checkSessionPreemptively, maxTries, rateLimitingDelay, false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
|
||||
@@ -1477,14 +1477,14 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
return false;
|
||||
}
|
||||
|
||||
return await UrlPostWithSession(request, headers, data, referer, requestOptions, session, checkSessionPreemptively, maxTries, rateLimitingDelay, allowSessionRefresh).ConfigureAwait(false);
|
||||
return await UrlPostWithSession(request, headers, data, referer, requestOptions, session, checkSessionPreemptively, maxTries, rateLimitingDelay, allowSessionRefresh, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static async Task<T> WebLimitRequest<T>(Uri service, Func<Task<T>> function) {
|
||||
public static async Task<T> WebLimitRequest<T>(Uri service, Func<Task<T>> function, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(service);
|
||||
ArgumentNullException.ThrowIfNull(function);
|
||||
|
||||
@@ -1508,15 +1508,16 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
}
|
||||
|
||||
// Sending a request opens a new connection
|
||||
await limiters.OpenConnectionsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await limiters.OpenConnectionsSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
// It also increases number of requests
|
||||
await limiters.RateLimitingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await limiters.RateLimitingSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// We release rate-limiter semaphore regardless of our task completion, since we use that one only to guarantee rate-limiting of their creation
|
||||
Utilities.InBackground(
|
||||
async () => {
|
||||
// ReSharper disable once MethodSupportsCancellation - we must always wait given time before releasing semaphore
|
||||
await Task.Delay(WebLimiterDelay).ConfigureAwait(false);
|
||||
limiters.RateLimitingSemaphore.Release();
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Dom;
|
||||
using ArchiSteamFarm.Core;
|
||||
@@ -40,27 +41,27 @@ using Newtonsoft.Json;
|
||||
namespace ArchiSteamFarm.Web;
|
||||
|
||||
internal static class GitHub {
|
||||
internal static async Task<ReleaseResponse?> GetLatestRelease(bool stable = true) {
|
||||
internal static async Task<ReleaseResponse?> GetLatestRelease(bool stable = true, CancellationToken cancellationToken = default) {
|
||||
Uri request = new($"{SharedInfo.GithubReleaseURL}{(stable ? "/latest" : "?per_page=1")}");
|
||||
|
||||
if (stable) {
|
||||
return await GetReleaseFromURL(request).ConfigureAwait(false);
|
||||
return await GetReleaseFromURL(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
ImmutableList<ReleaseResponse>? response = await GetReleasesFromURL(request).ConfigureAwait(false);
|
||||
ImmutableList<ReleaseResponse>? response = await GetReleasesFromURL(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return response?.FirstOrDefault();
|
||||
}
|
||||
|
||||
internal static async Task<ReleaseResponse?> GetRelease(string version) {
|
||||
internal static async Task<ReleaseResponse?> GetRelease(string version, CancellationToken cancellationToken = default) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(version);
|
||||
|
||||
Uri request = new($"{SharedInfo.GithubReleaseURL}/tags/{version}");
|
||||
|
||||
return await GetReleaseFromURL(request).ConfigureAwait(false);
|
||||
return await GetReleaseFromURL(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task<Dictionary<string, DateTime>?> GetWikiHistory(string page) {
|
||||
internal static async Task<Dictionary<string, DateTime>?> GetWikiHistory(string page, CancellationToken cancellationToken = default) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(page);
|
||||
|
||||
if (ASF.WebBrowser == null) {
|
||||
@@ -69,7 +70,7 @@ internal static class GitHub {
|
||||
|
||||
Uri request = new($"{SharedInfo.ProjectURL}/wiki/{page}/_history");
|
||||
|
||||
using HtmlDocumentResponse? response = await ASF.WebBrowser.UrlGetToHtmlDocument(request, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors).ConfigureAwait(false);
|
||||
using HtmlDocumentResponse? response = await ASF.WebBrowser.UrlGetToHtmlDocument(request, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response?.StatusCode.IsClientErrorCode() == true) {
|
||||
return response.StatusCode switch {
|
||||
@@ -131,7 +132,7 @@ internal static class GitHub {
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static async Task<string?> GetWikiPage(string page, string? revision = null) {
|
||||
internal static async Task<string?> GetWikiPage(string page, string? revision = null, CancellationToken cancellationToken = default) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(page);
|
||||
|
||||
if (ASF.WebBrowser == null) {
|
||||
@@ -140,7 +141,7 @@ internal static class GitHub {
|
||||
|
||||
Uri request = new($"{SharedInfo.ProjectURL}/wiki/{page}{(!string.IsNullOrEmpty(revision) ? $"/{revision}" : "")}");
|
||||
|
||||
using HtmlDocumentResponse? response = await ASF.WebBrowser.UrlGetToHtmlDocument(request).ConfigureAwait(false);
|
||||
using HtmlDocumentResponse? response = await ASF.WebBrowser.UrlGetToHtmlDocument(request, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response?.Content == null) {
|
||||
return null;
|
||||
@@ -166,26 +167,26 @@ internal static class GitHub {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static async Task<ReleaseResponse?> GetReleaseFromURL(Uri request) {
|
||||
private static async Task<ReleaseResponse?> GetReleaseFromURL(Uri request, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
if (ASF.WebBrowser == null) {
|
||||
throw new InvalidOperationException(nameof(ASF.WebBrowser));
|
||||
}
|
||||
|
||||
ObjectResponse<ReleaseResponse>? response = await ASF.WebBrowser.UrlGetToJsonObject<ReleaseResponse>(request).ConfigureAwait(false);
|
||||
ObjectResponse<ReleaseResponse>? response = await ASF.WebBrowser.UrlGetToJsonObject<ReleaseResponse>(request, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return response?.Content;
|
||||
}
|
||||
|
||||
private static async Task<ImmutableList<ReleaseResponse>?> GetReleasesFromURL(Uri request) {
|
||||
private static async Task<ImmutableList<ReleaseResponse>?> GetReleasesFromURL(Uri request, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
if (ASF.WebBrowser == null) {
|
||||
throw new InvalidOperationException(nameof(ASF.WebBrowser));
|
||||
}
|
||||
|
||||
ObjectResponse<ImmutableList<ReleaseResponse>>? response = await ASF.WebBrowser.UrlGetToJsonObject<ImmutableList<ReleaseResponse>>(request).ConfigureAwait(false);
|
||||
ObjectResponse<ImmutableList<ReleaseResponse>>? response = await ASF.WebBrowser.UrlGetToJsonObject<ImmutableList<ReleaseResponse>>(request, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return response?.Content;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp;
|
||||
using AngleSharp.Dom;
|
||||
@@ -43,12 +44,12 @@ public sealed class HtmlDocumentResponse : BasicResponse, IDisposable {
|
||||
public void Dispose() => Content?.Dispose();
|
||||
|
||||
[PublicAPI]
|
||||
public static async Task<HtmlDocumentResponse?> Create(StreamResponse streamResponse) {
|
||||
public static async Task<HtmlDocumentResponse?> Create(StreamResponse streamResponse, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(streamResponse);
|
||||
|
||||
IBrowsingContext context = BrowsingContext.New();
|
||||
|
||||
IDocument document = await context.OpenAsync(request => request.Content(streamResponse.Content, true)).ConfigureAwait(false);
|
||||
IDocument document = await context.OpenAsync(request => request.Content(streamResponse.Content, true), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return new HtmlDocumentResponse(streamResponse, document);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Localization;
|
||||
@@ -111,17 +112,17 @@ public sealed class WebBrowser : IDisposable {
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<BinaryResponse?> UrlGetToBinary(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0, IProgress<byte>? progressReporter = null) {
|
||||
public async Task<BinaryResponse?> UrlGetToBinary(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0, IProgress<byte>? progressReporter = null, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentOutOfRangeException.ThrowIfZero(maxTries);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(rateLimitingDelay);
|
||||
|
||||
for (byte i = 0; i < maxTries; i++) {
|
||||
if ((i > 0) && (rateLimitingDelay > 0)) {
|
||||
await Task.Delay(rateLimitingDelay).ConfigureAwait(false);
|
||||
await Task.Delay(rateLimitingDelay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
StreamResponse? response = await UrlGetToStream(request, headers, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1, rateLimitingDelay).ConfigureAwait(false);
|
||||
StreamResponse? response = await UrlGetToStream(request, headers, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1, rateLimitingDelay, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
// Request timed out, try again
|
||||
@@ -173,13 +174,13 @@ public sealed class WebBrowser : IDisposable {
|
||||
|
||||
try {
|
||||
while (response.Content.CanRead) {
|
||||
int read = await response.Content.ReadAsync(buffer.AsMemory(0, buffer.Length)).ConfigureAwait(false);
|
||||
int read = await response.Content.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (read == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
await ms.WriteAsync(buffer.AsMemory(0, read)).ConfigureAwait(false);
|
||||
await ms.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if ((progressReporter == null) || (batchIncreaseSize == 0) || (batch >= 99)) {
|
||||
continue;
|
||||
@@ -192,6 +193,8 @@ public sealed class WebBrowser : IDisposable {
|
||||
progressReporter.Report(++batch);
|
||||
}
|
||||
}
|
||||
} catch (OperationCanceledException) {
|
||||
throw;
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.ErrorFailingRequest, request));
|
||||
@@ -215,17 +218,17 @@ public sealed class WebBrowser : IDisposable {
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<HtmlDocumentResponse?> UrlGetToHtmlDocument(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0) {
|
||||
public async Task<HtmlDocumentResponse?> UrlGetToHtmlDocument(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentOutOfRangeException.ThrowIfZero(maxTries);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(rateLimitingDelay);
|
||||
|
||||
for (byte i = 0; i < maxTries; i++) {
|
||||
if ((i > 0) && (rateLimitingDelay > 0)) {
|
||||
await Task.Delay(rateLimitingDelay).ConfigureAwait(false);
|
||||
await Task.Delay(rateLimitingDelay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
StreamResponse? response = await UrlGetToStream(request, headers, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1, rateLimitingDelay).ConfigureAwait(false);
|
||||
StreamResponse? response = await UrlGetToStream(request, headers, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1, rateLimitingDelay, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
// Request timed out, try again
|
||||
@@ -256,7 +259,9 @@ public sealed class WebBrowser : IDisposable {
|
||||
}
|
||||
|
||||
try {
|
||||
return await HtmlDocumentResponse.Create(response).ConfigureAwait(false);
|
||||
return await HtmlDocumentResponse.Create(response, cancellationToken).ConfigureAwait(false);
|
||||
} catch (OperationCanceledException) {
|
||||
throw;
|
||||
} catch (Exception e) {
|
||||
if ((requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnSuccess) && response.StatusCode.IsSuccessCode()) || (requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnErrors) && !response.StatusCode.IsSuccessCode())) {
|
||||
return new HtmlDocumentResponse(response);
|
||||
@@ -275,17 +280,17 @@ public sealed class WebBrowser : IDisposable {
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<ObjectResponse<T>?> UrlGetToJsonObject<T>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0) {
|
||||
public async Task<ObjectResponse<T>?> UrlGetToJsonObject<T>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentOutOfRangeException.ThrowIfZero(maxTries);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(rateLimitingDelay);
|
||||
|
||||
for (byte i = 0; i < maxTries; i++) {
|
||||
if ((i > 0) && (rateLimitingDelay > 0)) {
|
||||
await Task.Delay(rateLimitingDelay).ConfigureAwait(false);
|
||||
await Task.Delay(rateLimitingDelay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
StreamResponse? response = await UrlGetToStream(request, headers, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1, rateLimitingDelay).ConfigureAwait(false);
|
||||
StreamResponse? response = await UrlGetToStream(request, headers, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1, rateLimitingDelay, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
// Request timed out, try again
|
||||
@@ -329,6 +334,8 @@ public sealed class WebBrowser : IDisposable {
|
||||
|
||||
obj = serializer.Deserialize<T>(jsonReader);
|
||||
}
|
||||
} catch (OperationCanceledException) {
|
||||
throw;
|
||||
} catch (Exception e) {
|
||||
if ((requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnSuccess) && response.StatusCode.IsSuccessCode()) || (requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnErrors) && !response.StatusCode.IsSuccessCode())) {
|
||||
return new ObjectResponse<T>(response);
|
||||
@@ -361,17 +368,17 @@ public sealed class WebBrowser : IDisposable {
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<StreamResponse?> UrlGetToStream(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0) {
|
||||
public async Task<StreamResponse?> UrlGetToStream(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentOutOfRangeException.ThrowIfZero(maxTries);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(rateLimitingDelay);
|
||||
|
||||
for (byte i = 0; i < maxTries; i++) {
|
||||
if ((i > 0) && (rateLimitingDelay > 0)) {
|
||||
await Task.Delay(rateLimitingDelay).ConfigureAwait(false);
|
||||
await Task.Delay(rateLimitingDelay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
HttpResponseMessage? response = await InternalGet(request, headers, referer, requestOptions, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
|
||||
HttpResponseMessage? response = await InternalGet(request, headers, referer, requestOptions, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
// Request timed out, try again
|
||||
@@ -396,7 +403,7 @@ public sealed class WebBrowser : IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
return new StreamResponse(response, await response.Content.ReadAsStreamAsync().ConfigureAwait(false));
|
||||
return new StreamResponse(response, await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false));
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, maxTries));
|
||||
@@ -406,17 +413,17 @@ public sealed class WebBrowser : IDisposable {
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<BasicResponse?> UrlHead(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0) {
|
||||
public async Task<BasicResponse?> UrlHead(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentOutOfRangeException.ThrowIfZero(maxTries);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(rateLimitingDelay);
|
||||
|
||||
for (byte i = 0; i < maxTries; i++) {
|
||||
if ((i > 0) && (rateLimitingDelay > 0)) {
|
||||
await Task.Delay(rateLimitingDelay).ConfigureAwait(false);
|
||||
await Task.Delay(rateLimitingDelay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
using HttpResponseMessage? response = await InternalHead(request, headers, referer, requestOptions).ConfigureAwait(false);
|
||||
using HttpResponseMessage? response = await InternalHead(request, headers, referer, requestOptions, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
continue;
|
||||
@@ -450,17 +457,17 @@ public sealed class WebBrowser : IDisposable {
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<BasicResponse?> UrlPost<T>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, T? data = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0) where T : class {
|
||||
public async Task<BasicResponse?> UrlPost<T>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, T? data = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0, CancellationToken cancellationToken = default) where T : class {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentOutOfRangeException.ThrowIfZero(maxTries);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(rateLimitingDelay);
|
||||
|
||||
for (byte i = 0; i < maxTries; i++) {
|
||||
if ((i > 0) && (rateLimitingDelay > 0)) {
|
||||
await Task.Delay(rateLimitingDelay).ConfigureAwait(false);
|
||||
await Task.Delay(rateLimitingDelay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
using HttpResponseMessage? response = await InternalPost(request, headers, data, referer, requestOptions).ConfigureAwait(false);
|
||||
using HttpResponseMessage? response = await InternalPost(request, headers, data, referer, requestOptions, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
continue;
|
||||
@@ -494,17 +501,17 @@ public sealed class WebBrowser : IDisposable {
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<HtmlDocumentResponse?> UrlPostToHtmlDocument<T>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, T? data = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0) where T : class {
|
||||
public async Task<HtmlDocumentResponse?> UrlPostToHtmlDocument<T>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, T? data = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0, CancellationToken cancellationToken = default) where T : class {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentOutOfRangeException.ThrowIfZero(maxTries);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(rateLimitingDelay);
|
||||
|
||||
for (byte i = 0; i < maxTries; i++) {
|
||||
if ((i > 0) && (rateLimitingDelay > 0)) {
|
||||
await Task.Delay(rateLimitingDelay).ConfigureAwait(false);
|
||||
await Task.Delay(rateLimitingDelay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
StreamResponse? response = await UrlPostToStream(request, headers, data, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1, rateLimitingDelay).ConfigureAwait(false);
|
||||
StreamResponse? response = await UrlPostToStream(request, headers, data, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1, rateLimitingDelay, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
// Request timed out, try again
|
||||
@@ -535,7 +542,9 @@ public sealed class WebBrowser : IDisposable {
|
||||
}
|
||||
|
||||
try {
|
||||
return await HtmlDocumentResponse.Create(response).ConfigureAwait(false);
|
||||
return await HtmlDocumentResponse.Create(response, cancellationToken).ConfigureAwait(false);
|
||||
} catch (OperationCanceledException) {
|
||||
throw;
|
||||
} catch (Exception e) {
|
||||
if ((requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnSuccess) && response.StatusCode.IsSuccessCode()) || (requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnErrors) && !response.StatusCode.IsSuccessCode())) {
|
||||
return new HtmlDocumentResponse(response);
|
||||
@@ -554,17 +563,17 @@ public sealed class WebBrowser : IDisposable {
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<ObjectResponse<TResult>?> UrlPostToJsonObject<TResult, TData>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, TData? data = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0) where TData : class {
|
||||
public async Task<ObjectResponse<TResult>?> UrlPostToJsonObject<TResult, TData>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, TData? data = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0, CancellationToken cancellationToken = default) where TData : class {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentOutOfRangeException.ThrowIfZero(maxTries);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(rateLimitingDelay);
|
||||
|
||||
for (byte i = 0; i < maxTries; i++) {
|
||||
if ((i > 0) && (rateLimitingDelay > 0)) {
|
||||
await Task.Delay(rateLimitingDelay).ConfigureAwait(false);
|
||||
await Task.Delay(rateLimitingDelay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
StreamResponse? response = await UrlPostToStream(request, headers, data, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1, rateLimitingDelay).ConfigureAwait(false);
|
||||
StreamResponse? response = await UrlPostToStream(request, headers, data, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1, rateLimitingDelay, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
// Request timed out, try again
|
||||
@@ -608,6 +617,8 @@ public sealed class WebBrowser : IDisposable {
|
||||
|
||||
obj = serializer.Deserialize<TResult>(jsonReader);
|
||||
}
|
||||
} catch (OperationCanceledException) {
|
||||
throw;
|
||||
} catch (Exception e) {
|
||||
if ((requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnSuccess) && response.StatusCode.IsSuccessCode()) || (requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnErrors) && !response.StatusCode.IsSuccessCode())) {
|
||||
return new ObjectResponse<TResult>(response);
|
||||
@@ -640,17 +651,17 @@ public sealed class WebBrowser : IDisposable {
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<StreamResponse?> UrlPostToStream<T>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, T? data = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0) where T : class {
|
||||
public async Task<StreamResponse?> UrlPostToStream<T>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, T? data = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0, CancellationToken cancellationToken = default) where T : class {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentOutOfRangeException.ThrowIfZero(maxTries);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(rateLimitingDelay);
|
||||
|
||||
for (byte i = 0; i < maxTries; i++) {
|
||||
if ((i > 0) && (rateLimitingDelay > 0)) {
|
||||
await Task.Delay(rateLimitingDelay).ConfigureAwait(false);
|
||||
await Task.Delay(rateLimitingDelay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
HttpResponseMessage? response = await InternalPost(request, headers, data, referer, requestOptions, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
|
||||
HttpResponseMessage? response = await InternalPost(request, headers, data, referer, requestOptions, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
// Request timed out, try again
|
||||
@@ -675,7 +686,7 @@ public sealed class WebBrowser : IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
return new StreamResponse(response, await response.Content.ReadAsStreamAsync().ConfigureAwait(false));
|
||||
return new StreamResponse(response, await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false));
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, maxTries));
|
||||
@@ -698,25 +709,25 @@ public sealed class WebBrowser : IDisposable {
|
||||
ServicePointManager.ReusePort = true;
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage?> InternalGet(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead) {
|
||||
private async Task<HttpResponseMessage?> InternalGet(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
return await InternalRequest<object>(request, HttpMethod.Get, headers, null, referer, requestOptions, httpCompletionOption).ConfigureAwait(false);
|
||||
return await InternalRequest<object>(request, HttpMethod.Get, headers, null, referer, requestOptions, httpCompletionOption, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage?> InternalHead(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead) {
|
||||
private async Task<HttpResponseMessage?> InternalHead(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
return await InternalRequest<object>(request, HttpMethod.Head, headers, null, referer, requestOptions, httpCompletionOption).ConfigureAwait(false);
|
||||
return await InternalRequest<object>(request, HttpMethod.Head, headers, null, referer, requestOptions, httpCompletionOption, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage?> InternalPost<T>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, T? data = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead) where T : class {
|
||||
private async Task<HttpResponseMessage?> InternalPost<T>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, T? data = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) where T : class {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
return await InternalRequest(request, HttpMethod.Post, headers, data, referer, requestOptions, httpCompletionOption).ConfigureAwait(false);
|
||||
return await InternalRequest(request, HttpMethod.Post, headers, data, referer, requestOptions, httpCompletionOption, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage?> InternalRequest<T>(Uri request, HttpMethod httpMethod, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, T? data = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead, byte maxRedirections = MaxTries) where T : class {
|
||||
private async Task<HttpResponseMessage?> InternalRequest<T>(Uri request, HttpMethod httpMethod, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, T? data = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead, byte maxRedirections = MaxTries, CancellationToken cancellationToken = default) where T : class {
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentNullException.ThrowIfNull(httpMethod);
|
||||
|
||||
@@ -778,7 +789,9 @@ public sealed class WebBrowser : IDisposable {
|
||||
}
|
||||
|
||||
try {
|
||||
response = await HttpClient.SendAsync(requestMessage, httpCompletionOption).ConfigureAwait(false);
|
||||
response = await HttpClient.SendAsync(requestMessage, httpCompletionOption, cancellationToken).ConfigureAwait(false);
|
||||
} catch (OperationCanceledException) {
|
||||
throw;
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericDebuggingException(e);
|
||||
|
||||
@@ -867,7 +880,7 @@ public sealed class WebBrowser : IDisposable {
|
||||
|
||||
if (response.StatusCode.IsClientErrorCode()) {
|
||||
if (Debugging.IsUserDebugging) {
|
||||
ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.Content, await response.Content.ReadAsStringAsync().ConfigureAwait(false)));
|
||||
ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.Content, await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false)));
|
||||
}
|
||||
|
||||
// Do not retry on client errors
|
||||
@@ -876,7 +889,7 @@ public sealed class WebBrowser : IDisposable {
|
||||
|
||||
if (requestOptions.HasFlag(ERequestOptions.ReturnServerErrors) && response.StatusCode.IsServerErrorCode()) {
|
||||
if (Debugging.IsUserDebugging) {
|
||||
ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.Content, await response.Content.ReadAsStringAsync().ConfigureAwait(false)));
|
||||
ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.Content, await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false)));
|
||||
}
|
||||
|
||||
// Do not retry on server errors in this case
|
||||
@@ -885,7 +898,7 @@ public sealed class WebBrowser : IDisposable {
|
||||
|
||||
using (response) {
|
||||
if (Debugging.IsUserDebugging) {
|
||||
ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.Content, await response.Content.ReadAsStringAsync().ConfigureAwait(false)));
|
||||
ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.Content, await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false)));
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user