Files
ArchiSteamFarm/ArchiSteamFarm/Utilities.cs
JustArchi 91e495605b Port ArchiBot's WebBrowser client errors handling
First important change is for all requests sent by ASF. Across those 4 years of development I do not remember a single situation where retrying on 4xx status code brought any improvement, bad request has to be handled by us, access denied and not found won't disappear after retry, all other ones are rather unused. Therefore, it makes sense to skip remaining tries on 4xx errors and do not flood the service with requests that are very unlikely to change anything.
Second change is smaller one and it allows the consumer of WebBrowser to declare that he's interested in handling client error codes himself. This way he can add extra logic and appropriately react to them - ASF uses it in statistics module, where the listing can signal refusal to list due to e.g. outdated ASF version through 403. Previously this was treated on the same level as timeout, which wasn't optimal.
2019-04-05 16:24:02 +02:00

273 lines
7.3 KiB
C#

// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2019 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// 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
// |
// http://www.apache.org/licenses/LICENSE-2.0
// |
// 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;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Humanizer;
using Humanizer.Localisation;
using JetBrains.Annotations;
namespace ArchiSteamFarm {
public static class Utilities {
// Normally we wouldn't need to use this singleton, but we want to ensure decent randomness across entire program's lifetime
private static readonly Random Random = new Random();
[PublicAPI]
public static string GetArgsAsText(string[] args, byte argsToSkip, string delimiter) {
if ((args == null) || (args.Length <= argsToSkip) || string.IsNullOrEmpty(delimiter)) {
ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(argsToSkip) + " || " + nameof(delimiter));
return null;
}
return string.Join(delimiter, args.Skip(argsToSkip));
}
[PublicAPI]
public static string GetArgsAsText(string text, byte argsToSkip) {
if (string.IsNullOrEmpty(text)) {
ASF.ArchiLogger.LogNullError(nameof(text));
return null;
}
string[] args = text.Split((char[]) null, argsToSkip + 1, StringSplitOptions.RemoveEmptyEntries);
return args[args.Length - 1];
}
[PublicAPI]
public static uint GetUnixTime() => (uint) DateTimeOffset.UtcNow.ToUnixTimeSeconds();
[PublicAPI]
public static void InBackground(Action action, bool longRunning = false) {
if (action == null) {
ASF.ArchiLogger.LogNullError(nameof(action));
return;
}
TaskCreationOptions options = TaskCreationOptions.DenyChildAttach;
if (longRunning) {
options |= TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness;
}
Task.Factory.StartNew(action, CancellationToken.None, options, TaskScheduler.Default);
}
[PublicAPI]
public static void InBackground<T>(Func<T> function, bool longRunning = false) {
if (function == null) {
ASF.ArchiLogger.LogNullError(nameof(function));
return;
}
TaskCreationOptions options = TaskCreationOptions.DenyChildAttach;
if (longRunning) {
options |= TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness;
}
Task.Factory.StartNew(function, CancellationToken.None, options, TaskScheduler.Default);
}
[PublicAPI]
public static async Task<IList<T>> InParallel<T>(IEnumerable<Task<T>> tasks) {
if (tasks == null) {
ASF.ArchiLogger.LogNullError(nameof(tasks));
return null;
}
IList<T> results;
switch (ASF.GlobalConfig.OptimizationMode) {
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
results = new List<T>();
foreach (Task<T> task in tasks) {
results.Add(await task.ConfigureAwait(false));
}
break;
default:
results = await Task.WhenAll(tasks).ConfigureAwait(false);
break;
}
return results;
}
[PublicAPI]
public static async Task InParallel(IEnumerable<Task> tasks) {
if (tasks == null) {
ASF.ArchiLogger.LogNullError(nameof(tasks));
return;
}
switch (ASF.GlobalConfig.OptimizationMode) {
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
foreach (Task task in tasks) {
await task.ConfigureAwait(false);
}
break;
default:
await Task.WhenAll(tasks).ConfigureAwait(false);
break;
}
}
[PublicAPI]
public static bool IsClientErrorCode(this HttpStatusCode statusCode) => (statusCode >= HttpStatusCode.BadRequest) && (statusCode < HttpStatusCode.InternalServerError);
[PublicAPI]
public static bool IsValidCdKey(string key) {
if (string.IsNullOrEmpty(key)) {
ASF.ArchiLogger.LogNullError(nameof(key));
return false;
}
return Regex.IsMatch(key, @"^[0-9A-Z]{4,7}-[0-9A-Z]{4,7}-[0-9A-Z]{4,7}(?:(?:-[0-9A-Z]{4,7})?(?:-[0-9A-Z]{4,7}))?$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
}
[PublicAPI]
public static bool IsValidDigitsText(string text) {
if (string.IsNullOrEmpty(text)) {
ASF.ArchiLogger.LogNullError(nameof(text));
return false;
}
return text.All(char.IsDigit);
}
[PublicAPI]
public static bool IsValidHexadecimalText(string text) {
if (string.IsNullOrEmpty(text)) {
ASF.ArchiLogger.LogNullError(nameof(text));
return false;
}
if (text.Length % 2 != 0) {
return false;
}
// ulong is 64-bits wide, each hexadecimal character is 4-bits wide, so we split each 16
const byte split = 16;
string lastHex;
if (text.Length >= split) {
StringBuilder hex = new StringBuilder(split, 16);
foreach (char character in text) {
hex.Append(character);
if (hex.Length < split) {
continue;
}
if (!ulong.TryParse(hex.ToString(), NumberStyles.HexNumber, null, out _)) {
return false;
}
hex.Clear();
}
if (hex.Length == 0) {
return true;
}
lastHex = hex.ToString();
} else {
lastHex = text;
}
switch (lastHex.Length) {
case 2:
return byte.TryParse(lastHex, NumberStyles.HexNumber, null, out _);
case 4:
return ushort.TryParse(lastHex, NumberStyles.HexNumber, null, out _);
case 8:
return uint.TryParse(lastHex, NumberStyles.HexNumber, null, out _);
default:
return false;
}
}
[PublicAPI]
public static IEnumerable<T> ToEnumerable<T>(this T item) {
yield return item;
}
[PublicAPI]
public static string ToHumanReadable(this TimeSpan timeSpan) => timeSpan.Humanize(3, maxUnit: TimeUnit.Year, minUnit: TimeUnit.Second);
internal static string GetCookieValue(this CookieContainer cookieContainer, string url, string name) {
if ((cookieContainer == null) || string.IsNullOrEmpty(url) || string.IsNullOrEmpty(name)) {
ASF.ArchiLogger.LogNullError(nameof(cookieContainer) + " || " + nameof(url) + " || " + nameof(name));
return null;
}
Uri uri;
try {
uri = new Uri(url);
} catch (UriFormatException e) {
ASF.ArchiLogger.LogGenericException(e);
return null;
}
CookieCollection cookies = cookieContainer.GetCookies(uri);
return cookies.Count > 0 ? (from Cookie cookie in cookies where cookie.Name.Equals(name) select cookie.Value).FirstOrDefault() : null;
}
internal static int RandomNext() {
lock (Random) {
return Random.Next();
}
}
}
}