2024-03-16 23:56:57 +01:00
// ----------------------------------------------------------------------------------------------
2019-02-16 17:34:17 +01:00
// _ _ _ ____ _ _____
2017-11-18 17:27:06 +01:00
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
2024-03-16 23:56:57 +01:00
// ----------------------------------------------------------------------------------------------
2024-03-17 02:35:40 +01:00
// |
2024-01-08 11:33:28 +01:00
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
2018-07-27 04:52:14 +02:00
// Contact: JustArchi@JustArchi.net
2024-03-17 02:35:40 +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
2024-03-17 02:35:40 +01:00
// |
2018-07-27 04:52:14 +02:00
// http://www.apache.org/licenses/LICENSE-2.0
2024-03-17 02:35:40 +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 ;
2024-03-16 23:56:57 +01:00
using System.IO.Compression ;
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 ;
2024-03-16 23:56:57 +01:00
using ArchiSteamFarm.NLog ;
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 {
2024-04-04 21:21:58 +02:00
private const byte MaxSharingViolationTries = 15 ;
private const uint SharingViolationHResult = 0x80070020 ;
2021-11-10 21:23:24 +01:00
private const byte TimeoutForLongRunningTasksInSeconds = 60 ;
2021-10-13 21:44:48 +02:00
2024-04-04 21:21:58 +02:00
private static readonly FrozenSet < char > DirectorySeparators = new HashSet < char > ( 2 ) { Path . DirectorySeparatorChar , Path . AltDirectorySeparatorChar } . ToFrozenSet ( ) ;
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 ;
}
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
}
2024-03-16 23:56:57 +01:00
internal static void OnProgressChanged ( string fileName , byte progressPercentage ) {
ArgumentException . ThrowIfNullOrEmpty ( fileName ) ;
ArgumentOutOfRangeException . ThrowIfGreaterThan ( progressPercentage , 100 ) ;
2021-10-13 23:24:07 +02:00
2024-03-16 23:56:57 +01:00
const byte printEveryPercentage = 10 ;
if ( progressPercentage % printEveryPercentage ! = 0 ) {
return ;
2021-11-10 21:23:24 +01:00
}
2021-10-13 23:24:07 +02:00
2024-03-16 23:56:57 +01:00
ASF . ArchiLogger . LogGenericDebug ( $"{fileName} {progressPercentage}%..." ) ;
2021-11-10 21:23:24 +01:00
}
2024-04-04 02:15:16 +02:00
internal static ( bool IsWeak , string? Reason ) TestPasswordStrength ( string password , IEnumerable < 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
2024-04-04 21:21:58 +02:00
internal static async Task < bool > UpdateCleanup ( string targetDirectory ) {
2024-03-16 23:56:57 +01:00
ArgumentException . ThrowIfNullOrEmpty ( targetDirectory ) ;
2024-04-04 21:21:58 +02:00
bool updateCleanup = false ;
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
try {
string updateDirectory = Path . Combine ( targetDirectory , SharedInfo . UpdateDirectoryNew ) ;
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
if ( Directory . Exists ( updateDirectory ) ) {
if ( ! updateCleanup ) {
updateCleanup = true ;
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
ASF . ArchiLogger . LogGenericInfo ( Strings . UpdateCleanup ) ;
}
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
Directory . Delete ( updateDirectory , true ) ;
2024-03-16 23:56:57 +01:00
}
2024-04-04 21:21:58 +02:00
string backupDirectory = Path . Combine ( targetDirectory , SharedInfo . UpdateDirectoryOld ) ;
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
if ( Directory . Exists ( backupDirectory ) ) {
if ( ! updateCleanup ) {
updateCleanup = true ;
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
ASF . ArchiLogger . LogGenericInfo ( Strings . UpdateCleanup ) ;
}
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
await DeletePotentiallyUsedDirectory ( backupDirectory ) . ConfigureAwait ( false ) ;
2024-03-16 23:56:57 +01:00
}
2024-04-04 21:21:58 +02:00
} catch ( Exception e ) {
ASF . ArchiLogger . LogGenericException ( e ) ;
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
return false ;
2024-03-16 23:56:57 +01:00
}
2024-04-04 21:21:58 +02:00
if ( updateCleanup ) {
ASF . ArchiLogger . LogGenericInfo ( Strings . Done ) ;
2024-03-16 23:56:57 +01:00
}
2024-04-04 21:21:58 +02:00
return true ;
}
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
internal static async Task < bool > UpdateFromArchive ( ZipArchive zipArchive , string targetDirectory ) {
ArgumentNullException . ThrowIfNull ( zipArchive ) ;
ArgumentException . ThrowIfNullOrEmpty ( targetDirectory ) ;
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
// Firstly, ensure once again our directories are purged and ready to work with
if ( ! await UpdateCleanup ( targetDirectory ) . ConfigureAwait ( false ) ) {
return false ;
}
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
// Now extract the zip file to entirely new location, this decreases chance of corruptions if user kills the process during this stage
string updateDirectory = Path . Combine ( targetDirectory , SharedInfo . UpdateDirectoryNew ) ;
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
zipArchive . ExtractToDirectory ( updateDirectory , true ) ;
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
// Now, critical section begins, we're going to move all files from target directory to a backup directory
string backupDirectory = Path . Combine ( targetDirectory , SharedInfo . UpdateDirectoryOld ) ;
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
Directory . CreateDirectory ( backupDirectory ) ;
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
MoveAllUpdateFiles ( targetDirectory , backupDirectory , true ) ;
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
// Finally, we can move the newly extracted files to target directory
MoveAllUpdateFiles ( updateDirectory , targetDirectory , false ) ;
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
// Critical section has finished, we can now cleanup the update directory, backup directory must wait for the process restart
Directory . Delete ( updateDirectory , true ) ;
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
// The update process is done
2024-03-16 23:56:57 +01:00
return true ;
}
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
}
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
private static async Task DeletePotentiallyUsedDirectory ( string directory ) {
2024-03-16 23:56:57 +01:00
ArgumentException . ThrowIfNullOrEmpty ( directory ) ;
2024-04-04 21:21:58 +02:00
for ( byte i = 1 ; ( i < = MaxSharingViolationTries ) & & Directory . Exists ( directory ) ; i + + ) {
if ( i > 1 ) {
await Task . Delay ( 1000 ) . ConfigureAwait ( false ) ;
}
try {
Directory . Delete ( directory , true ) ;
} catch ( IOException e ) when ( ( i < MaxSharingViolationTries ) & & ( ( uint ) e . HResult = = SharingViolationHResult ) ) {
// It's entirely possible that old process is still running, we allow this to happen and add additional delay
ASF . ArchiLogger . LogGenericDebuggingException ( e ) ;
2024-04-05 11:48:42 +02:00
continue ;
} catch ( UnauthorizedAccessException e ) when ( i < MaxSharingViolationTries ) {
// TODO: What HResult here?
// It's entirely possible that old process is still running, we allow this to happen and add additional delay
ASF . ArchiLogger . LogGenericDebuggingException ( e ) ;
2024-04-04 21:21:58 +02:00
continue ;
}
2024-03-16 23:56:57 +01:00
return ;
}
2024-04-04 21:21:58 +02:00
}
2024-03-16 23:56:57 +01:00
2024-04-04 21:21:58 +02:00
private static void MoveAllUpdateFiles ( string sourceDirectory , string targetDirectory , bool keepUserFiles ) {
ArgumentException . ThrowIfNullOrEmpty ( sourceDirectory ) ;
ArgumentException . ThrowIfNullOrEmpty ( sourceDirectory ) ;
// Determine if targetDirectory is within sourceDirectory, if yes we need to skip it from enumeration further below
string targetRelativeDirectoryPath = Path . GetRelativePath ( sourceDirectory , targetDirectory ) ;
foreach ( string file in Directory . EnumerateFiles ( sourceDirectory , "*" , SearchOption . AllDirectories ) ) {
string fileName = Path . GetFileName ( file ) ;
if ( string . IsNullOrEmpty ( fileName ) ) {
throw new InvalidOperationException ( nameof ( fileName ) ) ;
2024-03-16 23:56:57 +01:00
}
2024-04-04 21:21:58 +02:00
string relativeFilePath = Path . GetRelativePath ( sourceDirectory , file ) ;
if ( string . IsNullOrEmpty ( relativeFilePath ) ) {
throw new InvalidOperationException ( nameof ( relativeFilePath ) ) ;
2024-03-16 23:56:57 +01:00
}
2024-04-04 21:21:58 +02:00
string? relativeDirectoryName = Path . GetDirectoryName ( relativeFilePath ) ;
switch ( relativeDirectoryName ) {
case null :
throw new InvalidOperationException ( nameof ( keepUserFiles ) ) ;
case "" :
// No directory, root folder
switch ( fileName ) {
case Logging . NLogConfigurationFile when keepUserFiles :
case SharedInfo . LogFile when keepUserFiles :
// Files with those names in root directory we want to keep
continue ;
}
break ;
case SharedInfo . ArchivalLogsDirectory when keepUserFiles :
case SharedInfo . ConfigDirectory when keepUserFiles :
case SharedInfo . DebugDirectory when keepUserFiles :
case SharedInfo . PluginsDirectory when keepUserFiles :
case SharedInfo . UpdateDirectoryNew :
case SharedInfo . UpdateDirectoryOld :
// Files in those constant directories we want to keep in their current place
continue ;
default :
// If we're moving files deeper into source location, we need to skip the newly created location from it
if ( ! string . IsNullOrEmpty ( targetRelativeDirectoryPath ) & & ( ( relativeDirectoryName = = targetRelativeDirectoryPath ) | | RelativeDirectoryStartsWith ( relativeDirectoryName , targetRelativeDirectoryPath ) ) ) {
continue ;
}
// Below code block should match the case above, it handles subdirectories
if ( RelativeDirectoryStartsWith ( relativeDirectoryName , SharedInfo . UpdateDirectoryNew , SharedInfo . UpdateDirectoryOld ) ) {
continue ;
}
if ( keepUserFiles & & RelativeDirectoryStartsWith ( relativeDirectoryName , SharedInfo . ArchivalLogsDirectory , SharedInfo . ConfigDirectory , SharedInfo . DebugDirectory , SharedInfo . PluginsDirectory ) ) {
continue ;
}
break ;
}
// We're going to move this file out of the current place, overwriting existing one if needed
string targetUpdateDirectory ;
if ( relativeDirectoryName . Length > 0 ) {
// File inside a subdirectory
targetUpdateDirectory = Path . Combine ( targetDirectory , relativeDirectoryName ) ;
Directory . CreateDirectory ( targetUpdateDirectory ) ;
} else {
// File in root directory
targetUpdateDirectory = targetDirectory ;
}
string targetUpdateFile = Path . Combine ( targetUpdateDirectory , fileName ) ;
File . Move ( file , targetUpdateFile , true ) ;
2024-03-16 23:56:57 +01:00
}
}
private static bool RelativeDirectoryStartsWith ( string directory , params string [ ] prefixes ) {
ArgumentException . ThrowIfNullOrEmpty ( directory ) ;
if ( ( prefixes = = null ) | | ( prefixes . Length = = 0 ) ) {
throw new ArgumentNullException ( nameof ( prefixes ) ) ;
}
2024-04-04 21:21:58 +02:00
return prefixes . Any ( prefix = > ! string . IsNullOrEmpty ( prefix ) & & ( directory . Length > prefix . Length ) & & DirectorySeparators . Contains ( directory [ prefix . Length ] ) & & directory . StartsWith ( prefix , StringComparison . Ordinal ) ) ;
2024-03-16 23:56:57 +01:00
}
2018-09-08 00:05:23 +02:00
}