2019-02-16 17:34:17 +01:00
// _ _ _ ____ _ _____
2017-11-18 17:27:06 +01:00
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
2019-01-14 19:11:17 +01:00
// |
2024-01-08 11:33:28 +01:00
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
2018-07-27 04:52:14 +02:00
// Contact: JustArchi@JustArchi.net
2019-01-14 19:11:17 +01:00
// |
2018-07-27 04:52:14 +02: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
2019-01-14 19:11:17 +01:00
// |
2018-07-27 04:52:14 +02:00
// http://www.apache.org/licenses/LICENSE-2.0
2019-01-14 19:11:17 +01:00
// |
2018-07-27 04:52:14 +02: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.
2015-10-28 19:21:27 +01:00
2016-04-01 20:18:21 +02:00
using System ;
2021-10-24 18:40:26 +02:00
using System.Collections ;
2024-01-03 13:46:54 +01:00
using System.Collections.Frozen ;
2017-01-28 15:29:38 +01:00
using System.Collections.Generic ;
2024-01-29 18:42:21 +01:00
using System.Diagnostics.CodeAnalysis ;
2021-10-24 18:40:26 +02:00
using System.Globalization ;
2019-04-30 14:31:24 +02:00
using System.IO ;
2016-04-01 20:18:21 +02:00
using System.Linq ;
2016-04-12 07:40:02 +02:00
using System.Net ;
2021-10-24 18:40:26 +02:00
using System.Resources ;
2023-11-29 00:08:16 +01:00
using System.Security.Cryptography ;
2018-02-26 19:48:29 +01:00
using System.Threading ;
2017-07-03 18:54:57 +02:00
using System.Threading.Tasks ;
2020-04-02 18:01:55 +03:00
using AngleSharp.Dom ;
using AngleSharp.XPath ;
2021-10-24 18:40:26 +02:00
using ArchiSteamFarm.Localization ;
2021-05-08 01:37:22 +02:00
using ArchiSteamFarm.Storage ;
2017-04-08 05:05:09 +02:00
using Humanizer ;
2017-09-23 06:44:37 +02:00
using Humanizer.Localisation ;
2019-01-10 23:44:32 +01:00
using JetBrains.Annotations ;
2024-01-30 13:26:32 +01:00
using Microsoft.IdentityModel.JsonWebTokens ;
2020-06-15 16:55:57 +02:00
using SteamKit2 ;
2021-10-13 21:44:48 +02:00
using Zxcvbn ;
2015-10-25 06:16:50 +01:00
2021-11-10 21:23:24 +01:00
namespace ArchiSteamFarm.Core ;
2020-06-15 16:55:57 +02:00
2021-11-10 21:23:24 +01:00
public static class Utilities {
private const byte TimeoutForLongRunningTasksInSeconds = 60 ;
2021-10-13 21:44:48 +02:00
2021-11-10 21:23:24 +01:00
// normally we'd just use words like "steam" and "farm", but the library we're currently using is a bit iffy about banned words, so we need to also add combinations such as "steamfarm"
2024-01-03 13:46:54 +01:00
private static readonly FrozenSet < string > ForbiddenPasswordPhrases = new HashSet < string > ( 10 , StringComparer . InvariantCultureIgnoreCase ) { "archisteamfarm" , "archi" , "steam" , "farm" , "archisteam" , "archifarm" , "steamfarm" , "asf" , "asffarm" , "password" } . ToFrozenSet ( StringComparer . InvariantCultureIgnoreCase ) ;
2016-12-23 03:37:29 +01:00
2023-11-29 00:08:16 +01:00
[PublicAPI]
public static string GenerateChecksumFor ( byte [ ] source ) {
ArgumentNullException . ThrowIfNull ( source ) ;
byte [ ] hash = SHA512 . HashData ( source ) ;
return Convert . ToHexString ( hash ) ;
}
2021-11-10 21:23:24 +01:00
[PublicAPI]
public static string GetArgsAsText ( string [ ] args , byte argsToSkip , string delimiter ) {
2021-12-12 01:12:54 +01:00
ArgumentNullException . ThrowIfNull ( args ) ;
2019-01-15 12:30:29 +01:00
2021-11-10 21:23:24 +01:00
if ( args . Length < = argsToSkip ) {
throw new InvalidOperationException ( $"{nameof(args.Length)} && {nameof(argsToSkip)}" ) ;
2019-01-15 12:30:29 +01:00
}
2023-11-14 19:12:33 +01:00
ArgumentException . ThrowIfNullOrEmpty ( delimiter ) ;
2019-01-15 12:30:29 +01:00
2021-11-10 21:23:24 +01:00
return string . Join ( delimiter , args . Skip ( argsToSkip ) ) ;
}
2019-01-15 12:30:29 +01:00
2021-11-10 21:23:24 +01:00
[PublicAPI]
public static string GetArgsAsText ( string text , byte argsToSkip ) {
2023-11-14 19:12:33 +01:00
ArgumentException . ThrowIfNullOrEmpty ( text ) ;
2019-01-15 12:30:29 +01:00
2021-11-10 21:23:24 +01:00
string [ ] args = text . Split ( Array . Empty < char > ( ) , argsToSkip + 1 , StringSplitOptions . RemoveEmptyEntries ) ;
2020-11-14 22:37:00 +01:00
2021-11-10 21:23:24 +01:00
return args [ ^ 1 ] ;
}
2020-11-14 22:37:00 +01:00
2021-11-10 21:23:24 +01:00
[PublicAPI]
public static string? GetCookieValue ( this CookieContainer cookieContainer , Uri uri , string name ) {
2021-12-12 01:12:54 +01:00
ArgumentNullException . ThrowIfNull ( cookieContainer ) ;
2022-05-26 13:29:12 +02:00
ArgumentNullException . ThrowIfNull ( uri ) ;
2023-11-14 19:12:33 +01:00
ArgumentException . ThrowIfNullOrEmpty ( name ) ;
2021-11-10 21:23:24 +01:00
CookieCollection cookies = cookieContainer . GetCookies ( uri ) ;
2020-09-13 21:43:25 +02:00
2023-12-02 15:16:26 +01:00
return cookies . FirstOrDefault ( cookie = > cookie . Name = = name ) ? . Value ;
2021-11-10 21:23:24 +01:00
}
2020-09-13 21:43:25 +02:00
2021-11-10 21:23:24 +01:00
[PublicAPI]
2022-06-01 21:13:50 +02:00
public static ulong GetUnixTime ( ) = > ( ulong ) DateTimeOffset . UtcNow . ToUnixTimeSeconds ( ) ;
2016-12-23 03:37:29 +01:00
2021-11-10 21:23:24 +01:00
[PublicAPI]
public static async void InBackground ( Action action , bool longRunning = false ) {
2021-12-12 01:12:54 +01:00
ArgumentNullException . ThrowIfNull ( action ) ;
2018-02-26 18:48:57 +01:00
2021-11-10 21:23:24 +01:00
TaskCreationOptions options = TaskCreationOptions . DenyChildAttach ;
2018-02-26 18:48:57 +01:00
2021-11-10 21:23:24 +01:00
if ( longRunning ) {
options | = TaskCreationOptions . LongRunning | TaskCreationOptions . PreferFairness ;
2018-02-26 18:48:57 +01:00
}
2021-11-10 21:23:24 +01:00
await Task . Factory . StartNew ( action , CancellationToken . None , options , TaskScheduler . Default ) . ConfigureAwait ( false ) ;
}
2018-02-26 18:48:57 +01:00
2021-11-10 21:23:24 +01:00
[PublicAPI]
2022-04-13 21:44:57 +02:00
public static void InBackground < T > ( Func < T > function , bool longRunning = false ) {
2021-12-12 01:12:54 +01:00
ArgumentNullException . ThrowIfNull ( function ) ;
2018-02-26 18:48:57 +01:00
2023-08-10 21:36:17 +02:00
InBackground ( void ( ) = > function ( ) , longRunning ) ;
2021-11-10 21:23:24 +01:00
}
2018-09-19 18:25:17 +02:00
2021-11-10 21:23:24 +01:00
[PublicAPI]
public static async Task < IList < T > > InParallel < T > ( IEnumerable < Task < T > > tasks ) {
2021-12-12 01:12:54 +01:00
ArgumentNullException . ThrowIfNull ( tasks ) ;
2018-09-19 18:25:17 +02:00
2021-11-10 21:23:24 +01:00
switch ( ASF . GlobalConfig ? . OptimizationMode ) {
case GlobalConfig . EOptimizationMode . MinMemoryUsage :
2023-12-11 23:55:13 +01:00
List < T > results = [ ] ;
2018-09-19 18:25:17 +02:00
2021-11-10 21:23:24 +01:00
foreach ( Task < T > task in tasks ) {
results . Add ( await task . ConfigureAwait ( false ) ) ;
}
2018-12-15 00:27:15 +01:00
2023-11-14 19:12:33 +01:00
return results ;
2021-11-10 21:23:24 +01:00
default :
2023-11-14 19:12:33 +01:00
return await Task . WhenAll ( tasks ) . ConfigureAwait ( false ) ;
2018-09-19 18:25:17 +02:00
}
2021-11-10 21:23:24 +01:00
}
[PublicAPI]
public static async Task InParallel ( IEnumerable < Task > tasks ) {
2021-12-12 01:12:54 +01:00
ArgumentNullException . ThrowIfNull ( tasks ) ;
2018-09-19 18:25:17 +02:00
2021-11-10 21:23:24 +01:00
switch ( ASF . GlobalConfig ? . OptimizationMode ) {
case GlobalConfig . EOptimizationMode . MinMemoryUsage :
foreach ( Task task in tasks ) {
await task . ConfigureAwait ( false ) ;
}
2018-09-19 18:25:17 +02:00
2021-11-10 21:23:24 +01:00
break ;
default :
await Task . WhenAll ( tasks ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
break ;
2018-09-19 18:25:17 +02:00
}
2021-11-10 21:23:24 +01:00
}
2018-09-19 18:25:17 +02:00
2021-11-10 21:23:24 +01:00
[PublicAPI]
public static bool IsClientErrorCode ( this HttpStatusCode statusCode ) = > statusCode is > = HttpStatusCode . BadRequest and < HttpStatusCode . InternalServerError ;
2020-08-23 19:24:10 +02:00
2022-05-24 12:13:54 +02:00
[PublicAPI]
public static bool IsRedirectionCode ( this HttpStatusCode statusCode ) = > statusCode is > = HttpStatusCode . Ambiguous and < HttpStatusCode . BadRequest ;
2021-11-10 21:23:24 +01:00
[PublicAPI]
public static bool IsServerErrorCode ( this HttpStatusCode statusCode ) = > statusCode is > = HttpStatusCode . InternalServerError and < ( HttpStatusCode ) 600 ;
2021-09-27 19:50:18 +02:00
2021-11-10 21:23:24 +01:00
[PublicAPI]
public static bool IsSuccessCode ( this HttpStatusCode statusCode ) = > statusCode is > = HttpStatusCode . OK and < HttpStatusCode . Ambiguous ;
2018-09-15 22:34:32 +02:00
2021-11-10 21:23:24 +01:00
[PublicAPI]
public static bool IsValidCdKey ( string key ) {
2023-11-14 19:12:33 +01:00
ArgumentException . ThrowIfNullOrEmpty ( key ) ;
2018-09-15 22:34:32 +02:00
2022-11-16 12:33:17 +01:00
return GeneratedRegexes . CdKey ( ) . IsMatch ( key ) ;
2021-11-10 21:23:24 +01:00
}
2017-01-11 15:22:00 +01:00
2021-11-10 21:23:24 +01:00
[PublicAPI]
public static bool IsValidHexadecimalText ( string text ) {
2023-11-14 19:12:33 +01:00
ArgumentException . ThrowIfNullOrEmpty ( text ) ;
2017-01-11 15:22:00 +01:00
2021-11-10 21:23:24 +01:00
return ( text . Length % 2 = = 0 ) & & text . All ( Uri . IsHexDigit ) ;
}
2023-10-19 13:38:39 +02:00
2021-11-10 21:23:24 +01:00
[PublicAPI]
2022-08-06 18:51:32 +02:00
public static IList < INode > SelectNodes ( this IDocument document , string xpath ) {
2021-12-12 01:12:54 +01:00
ArgumentNullException . ThrowIfNull ( document ) ;
2020-04-02 18:01:55 +03:00
2022-08-06 18:51:32 +02:00
return document . Body . SelectNodes ( xpath ) ;
2021-11-10 21:23:24 +01:00
}
2020-04-02 18:01:55 +03:00
2021-11-10 21:23:24 +01:00
[PublicAPI]
2022-08-06 18:51:32 +02:00
public static IEnumerable < T > SelectNodes < T > ( this IDocument document , string xpath ) where T : class , INode {
ArgumentNullException . ThrowIfNull ( document ) ;
2022-08-08 12:40:19 +02:00
return document . Body . SelectNodes ( xpath ) . OfType < T > ( ) ;
2022-08-06 18:51:32 +02:00
}
[PublicAPI]
public static IEnumerable < T > SelectNodes < T > ( this IElement element , string xpath ) where T : class , INode {
2022-06-04 21:41:07 +02:00
ArgumentNullException . ThrowIfNull ( element ) ;
2022-08-06 18:51:32 +02:00
return element . SelectNodes ( xpath ) . OfType < T > ( ) ;
2022-06-04 21:41:07 +02:00
}
2021-05-06 23:32:50 +02:00
2021-11-10 21:23:24 +01:00
[PublicAPI]
2022-08-06 18:51:32 +02:00
public static INode ? SelectSingleNode ( this IDocument document , string xpath ) {
2021-12-12 01:12:54 +01:00
ArgumentNullException . ThrowIfNull ( document ) ;
2020-04-02 18:01:55 +03:00
2022-08-06 18:51:32 +02:00
return document . Body . SelectSingleNode ( xpath ) ;
}
[PublicAPI]
public static T ? SelectSingleNode < T > ( this IDocument document , string xpath ) where T : class , INode {
ArgumentNullException . ThrowIfNull ( document ) ;
return document . Body . SelectSingleNode ( xpath ) as T ;
}
[PublicAPI]
public static T ? SelectSingleNode < T > ( this IElement element , string xpath ) where T : class , INode {
ArgumentNullException . ThrowIfNull ( element ) ;
return element . SelectSingleNode ( xpath ) as T ;
2021-11-10 21:23:24 +01:00
}
2019-01-12 17:51:24 +01:00
2021-11-10 21:23:24 +01:00
[PublicAPI]
public static IEnumerable < T > ToEnumerable < T > ( this T item ) {
yield return item ;
}
2019-01-12 17:51:24 +01:00
2021-11-10 21:23:24 +01:00
[PublicAPI]
public static string ToHumanReadable ( this TimeSpan timeSpan ) = > timeSpan . Humanize ( 3 , maxUnit : TimeUnit . Year , minUnit : TimeUnit . Second ) ;
[PublicAPI]
public static Task < T > ToLongRunningTask < T > ( this AsyncJob < T > job ) where T : CallbackMsg {
2021-12-12 01:12:54 +01:00
ArgumentNullException . ThrowIfNull ( job ) ;
2021-11-10 21:23:24 +01:00
job . Timeout = TimeSpan . FromSeconds ( TimeoutForLongRunningTasksInSeconds ) ;
2020-06-15 16:55:57 +02:00
2021-11-10 21:23:24 +01:00
return job . ToTask ( ) ;
}
2020-06-15 16:55:57 +02:00
2021-11-10 21:23:24 +01:00
[PublicAPI]
public static Task < AsyncJobMultiple < T > . ResultSet > ToLongRunningTask < T > ( this AsyncJobMultiple < T > job ) where T : CallbackMsg {
2021-12-12 01:12:54 +01:00
ArgumentNullException . ThrowIfNull ( job ) ;
2020-06-15 16:55:57 +02:00
2021-11-10 21:23:24 +01:00
job . Timeout = TimeSpan . FromSeconds ( TimeoutForLongRunningTasksInSeconds ) ;
2020-06-15 16:55:57 +02:00
2021-11-10 21:23:24 +01:00
return job . ToTask ( ) ;
}
2020-06-15 16:55:57 +02:00
2024-01-30 13:26:32 +01:00
[PublicAPI]
public static bool TryReadJsonWebToken ( string token , [ NotNullWhen ( true ) ] out JsonWebToken ? result ) {
ArgumentException . ThrowIfNullOrEmpty ( token ) ;
try {
result = new JsonWebToken ( token ) ;
} catch ( Exception e ) {
ASF . ArchiLogger . LogGenericDebuggingException ( e ) ;
result = null ;
return false ;
}
return true ;
}
2021-11-10 21:23:24 +01:00
internal static void DeleteEmptyDirectoriesRecursively ( string directory ) {
2023-11-14 19:12:33 +01:00
ArgumentException . ThrowIfNullOrEmpty ( directory ) ;
2020-06-15 16:55:57 +02:00
2021-11-10 21:23:24 +01:00
if ( ! Directory . Exists ( directory ) ) {
return ;
}
2019-04-30 14:31:24 +02:00
2021-11-10 21:23:24 +01:00
try {
foreach ( string subDirectory in Directory . EnumerateDirectories ( directory ) ) {
DeleteEmptyDirectoriesRecursively ( subDirectory ) ;
2019-04-30 14:31:24 +02:00
}
2021-11-10 21:23:24 +01:00
if ( ! Directory . EnumerateFileSystemEntries ( directory ) . Any ( ) ) {
Directory . Delete ( directory ) ;
2019-04-30 14:31:24 +02:00
}
2021-11-10 21:23:24 +01:00
} catch ( Exception e ) {
ASF . ArchiLogger . LogGenericException ( e ) ;
2019-04-30 14:31:24 +02:00
}
2021-11-10 21:23:24 +01:00
}
2019-04-30 14:31:24 +02:00
2022-06-01 21:13:50 +02:00
internal static ulong MathAdd ( ulong first , int second ) {
if ( second > = 0 ) {
2022-06-05 12:39:04 +02:00
return first + ( uint ) second ;
2022-06-01 21:13:50 +02:00
}
2022-06-05 12:39:04 +02:00
return first - ( uint ) - second ;
2022-06-01 21:13:50 +02:00
}
2021-11-10 21:23:24 +01:00
internal static bool RelativeDirectoryStartsWith ( string directory , params string [ ] prefixes ) {
2023-11-14 19:12:33 +01:00
ArgumentException . ThrowIfNullOrEmpty ( directory ) ;
2021-10-13 23:24:07 +02:00
#pragma warning disable CA1508 // False positive, params could be null when explicitly set
2021-11-10 21:23:24 +01:00
if ( ( prefixes = = null ) | | ( prefixes . Length = = 0 ) ) {
2021-10-13 23:24:07 +02:00
#pragma warning restore CA1508 // False positive, params could be null when explicitly set
2021-11-10 21:23:24 +01:00
throw new ArgumentNullException ( nameof ( prefixes ) ) ;
}
2021-10-13 23:24:07 +02:00
2021-11-10 21:23:24 +01:00
return ( from prefix in prefixes where directory . Length > prefix . Length let pathSeparator = directory [ prefix . Length ] where ( pathSeparator = = Path . DirectorySeparatorChar ) | | ( pathSeparator = = Path . AltDirectorySeparatorChar ) select prefix ) . Any ( prefix = > directory . StartsWith ( prefix , StringComparison . Ordinal ) ) ;
}
internal static ( bool IsWeak , string? Reason ) TestPasswordStrength ( string password , ISet < string > ? additionallyForbiddenPhrases = null ) {
2023-11-14 19:12:33 +01:00
ArgumentException . ThrowIfNullOrEmpty ( password ) ;
2021-10-13 23:24:07 +02:00
2021-11-10 21:23:24 +01:00
HashSet < string > forbiddenPhrases = ForbiddenPasswordPhrases . ToHashSet ( StringComparer . InvariantCultureIgnoreCase ) ;
2021-10-13 21:44:48 +02:00
2021-11-10 21:23:24 +01:00
if ( additionallyForbiddenPhrases ! = null ) {
forbiddenPhrases . UnionWith ( additionallyForbiddenPhrases ) ;
}
2021-10-13 21:44:48 +02:00
2021-11-10 21:23:24 +01:00
Result result = Zxcvbn . Core . EvaluatePassword ( password , forbiddenPhrases ) ;
2021-10-13 21:44:48 +02:00
2022-04-18 20:35:47 +02:00
IList < string > ? suggestions = result . Feedback . Suggestions ;
if ( ! string . IsNullOrEmpty ( result . Feedback . Warning ) ) {
suggestions ? ? = new List < string > ( 1 ) ;
suggestions . Insert ( 0 , result . Feedback . Warning ) ;
}
if ( suggestions ! = null ) {
for ( byte i = 0 ; i < suggestions . Count ; i + + ) {
string suggestion = suggestions [ i ] ;
2022-04-18 21:08:30 +02:00
if ( ( suggestion . Length = = 0 ) | | ( suggestion [ ^ 1 ] = = '.' ) ) {
2022-04-18 20:35:47 +02:00
continue ;
}
suggestions [ i ] = $"{suggestion}." ;
}
}
2023-11-29 00:08:16 +01:00
return ( result . Score < 4 , suggestions is { Count : > 0 } ? string . Join ( ' ' , suggestions . Where ( static suggestion = > suggestion . Length > 0 ) ) : null ) ;
2021-11-10 21:23:24 +01:00
}
2021-10-13 21:44:48 +02:00
2021-11-10 21:23:24 +01:00
internal static void WarnAboutIncompleteTranslation ( ResourceManager resourceManager ) {
2021-12-12 01:12:54 +01:00
ArgumentNullException . ThrowIfNull ( resourceManager ) ;
2021-10-24 18:40:26 +02:00
2021-11-10 21:23:24 +01:00
// Skip translation progress for English and invariant (such as "C") cultures
switch ( CultureInfo . CurrentUICulture . TwoLetterISOLanguageName ) {
2022-06-05 15:59:34 +02:00
case "en" or "iv" or "qps" :
2021-11-10 21:23:24 +01:00
return ;
}
2021-10-24 18:40:26 +02:00
2021-11-10 21:23:24 +01:00
// We can't dispose this resource set, as we can't be sure if it isn't used somewhere else, rely on GC in this case
ResourceSet ? defaultResourceSet = resourceManager . GetResourceSet ( CultureInfo . GetCultureInfo ( "en-US" ) , true , true ) ;
2021-10-24 18:40:26 +02:00
2021-11-10 21:23:24 +01:00
if ( defaultResourceSet = = null ) {
2022-04-13 23:16:36 +02:00
ASF . ArchiLogger . LogNullError ( defaultResourceSet ) ;
2021-10-24 18:40:26 +02:00
2021-11-10 21:23:24 +01:00
return ;
}
2021-10-24 18:40:26 +02:00
2021-11-10 21:23:24 +01:00
HashSet < DictionaryEntry > defaultStringObjects = defaultResourceSet . Cast < DictionaryEntry > ( ) . ToHashSet ( ) ;
2021-10-24 18:40:26 +02:00
2021-11-10 21:23:24 +01:00
if ( defaultStringObjects . Count = = 0 ) {
2023-07-25 11:26:22 +02:00
// This means we don't have entries for English, so there is nothing to check against
// Can happen e.g. for plugins with no strings declared which are calling this function
2021-11-10 21:23:24 +01:00
return ;
}
2021-10-24 18:40:26 +02:00
2021-11-10 21:23:24 +01:00
// We can't dispose this resource set, as we can't be sure if it isn't used somewhere else, rely on GC in this case
ResourceSet ? currentResourceSet = resourceManager . GetResourceSet ( CultureInfo . CurrentUICulture , true , true ) ;
2021-10-24 18:40:26 +02:00
2021-11-10 21:23:24 +01:00
if ( currentResourceSet = = null ) {
2022-04-13 23:16:36 +02:00
ASF . ArchiLogger . LogNullError ( currentResourceSet ) ;
2021-10-24 18:40:26 +02:00
2021-11-10 21:23:24 +01:00
return ;
}
2021-10-24 18:40:26 +02:00
2021-11-10 21:23:24 +01:00
HashSet < DictionaryEntry > currentStringObjects = currentResourceSet . Cast < DictionaryEntry > ( ) . ToHashSet ( ) ;
2021-10-24 18:40:26 +02:00
2021-11-10 21:23:24 +01:00
if ( currentStringObjects . Count > = defaultStringObjects . Count ) {
// Either we have 100% finished translation, or we're missing it entirely and using en-US
HashSet < DictionaryEntry > testStringObjects = currentStringObjects . ToHashSet ( ) ;
testStringObjects . ExceptWith ( defaultStringObjects ) ;
2021-10-24 18:40:26 +02:00
2021-11-10 21:23:24 +01:00
// If we got 0 as final result, this is the missing language
// Otherwise it's just a small amount of strings that happen to be the same
if ( testStringObjects . Count = = 0 ) {
currentStringObjects = testStringObjects ;
2021-10-24 18:40:26 +02:00
}
2021-11-10 21:23:24 +01:00
}
2021-10-24 18:40:26 +02:00
2021-11-10 21:23:24 +01:00
if ( currentStringObjects . Count < defaultStringObjects . Count ) {
float translationCompleteness = currentStringObjects . Count / ( float ) defaultStringObjects . Count ;
ASF . ArchiLogger . LogGenericInfo ( string . Format ( CultureInfo . CurrentCulture , Strings . TranslationIncomplete , $"{CultureInfo.CurrentUICulture.Name} ({CultureInfo.CurrentUICulture.EnglishName})" , translationCompleteness . ToString ( "P1" , CultureInfo . CurrentCulture ) ) ) ;
2021-10-24 18:40:26 +02:00
}
2015-10-25 06:16:50 +01:00
}
2018-09-08 00:05:23 +02:00
}