From faf54763fad6471346873740694b1b24698fe3ec Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Tue, 28 Jan 2025 17:08:54 +0700 Subject: [PATCH 01/31] [skip ci] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 32d5ca851..229656061 100644 --- a/README.md +++ b/README.md @@ -232,8 +232,8 @@ Not only that, this launcher also has some advanced features for **Genshin Impac [](https://github.com/CollapseLauncher/Collapse/releases/download/CL-v1.82.14/CollapseLauncher-stable-Setup.exe) > **Note**: The version for this build is `1.82.14` (Released on: January 14th, 2025). -[](https://github.com/CollapseLauncher/Collapse/releases/download/CL-v1.82.14-pre/CollapseLauncher-preview-Setup.exe) -> **Note**: The version for this build is `1.82.14` (Released on: January 14th, 2025). +[](https://github.com/CollapseLauncher/Collapse/releases/download/CL-v1.82.15-pre/CollapseLauncher-preview-Setup.exe) +> **Note**: The version for this build is `1.82.15` (Released on: January 28th, 2025). To view all releases, [**click here**](https://github.com/neon-nyan/CollapseLauncher/releases). From 3bc65b8287475a110a2057fb9c0a3e6080a0f862 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Wed, 29 Jan 2025 18:35:34 +0700 Subject: [PATCH 02/31] Avoid early dispose on Stream Hashing methods --- .../Classes/Helper/Hash.FileStream.cs | 142 ++++++++++++++---- 1 file changed, 113 insertions(+), 29 deletions(-) diff --git a/CollapseLauncher/Classes/Helper/Hash.FileStream.cs b/CollapseLauncher/Classes/Helper/Hash.FileStream.cs index 56df89174..b5122f118 100644 --- a/CollapseLauncher/Classes/Helper/Hash.FileStream.cs +++ b/CollapseLauncher/Classes/Helper/Hash.FileStream.cs @@ -6,6 +6,7 @@ using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; +// ReSharper disable CheckNamespace #nullable enable namespace CollapseLauncher.Helper @@ -26,11 +27,8 @@ public static ConfiguredTaskAwaitable GetCryptoHashAsync( byte[]? hmacKey = null, Action? readProgress = null, CancellationToken token = default) - where T : HashAlgorithm - { - using FileStream fileStream = File.OpenRead(filePath); - return GetCryptoHashAsync(fileStream, hmacKey, readProgress, token); - } + where T : HashAlgorithm => + GetCryptoHashAsync(() => File.OpenRead(filePath), hmacKey, readProgress, token); /// /// Asynchronously computes the cryptographic hash of a file specified by a object. @@ -46,11 +44,8 @@ public static ConfiguredTaskAwaitable GetCryptoHashAsync( byte[]? hmacKey = null, Action? readProgress = null, CancellationToken token = default) - where T : HashAlgorithm - { - using FileStream fileStream = fileInfo.OpenRead(); - return GetCryptoHashAsync(fileStream, hmacKey, readProgress, token); - } + where T : HashAlgorithm => + GetCryptoHashAsync(fileInfo.OpenRead, hmacKey, readProgress, token); /// /// Asynchronously computes the cryptographic hash of a stream. @@ -80,6 +75,38 @@ public static ConfiguredTaskAwaitable GetCryptoHashAsync( return task.ConfigureAwait(false); } + /// + /// Asynchronously computes the cryptographic hash of a stream. + /// + /// The type of the hash algorithm to use. Must inherit from . + /// A delegate function which returns the stream to compute the hash for. + /// A cancellation token to observe while waiting for the task to complete. + /// The key to use for HMAC-based hash algorithms. If null, a standard hash algorithm is used. + /// An action to report the read progress. + /// A task that represents the asynchronous operation. The task result contains the computed hash as a byte array. + public static ConfiguredTaskAwaitable GetCryptoHashAsync( + Func streamDelegate, + byte[]? hmacKey = null, + Action? readProgress = null, + CancellationToken token = default) + where T : HashAlgorithm + { + // Create a new task from factory, assign a synchronous method to it with detached thread. + Task task = Task + .Factory + .StartNew(() => + { + using Stream stream = streamDelegate(); + return GetCryptoHash(stream, hmacKey, readProgress, token); + }, + token, + TaskCreationOptions.DenyChildAttach, + TaskScheduler.Default); + + // Create awaitable returnable-task + return task.ConfigureAwait(false); + } + /// /// Synchronously computes the cryptographic hash of a file specified by its path. /// @@ -94,11 +121,8 @@ public static byte[] GetCryptoHash( byte[]? hmacKey = null, Action? readProgress = null, CancellationToken token = default) - where T : HashAlgorithm - { - using FileStream fileStream = File.OpenRead(filePath); - return GetCryptoHash(fileStream, hmacKey, readProgress, token); - } + where T : HashAlgorithm => + GetCryptoHash(() => File.OpenRead(filePath), hmacKey, readProgress, token); /// /// Synchronously computes the cryptographic hash of a file specified by a object. @@ -114,10 +138,27 @@ public static byte[] GetCryptoHash( byte[]? hmacKey = null, Action? readProgress = null, CancellationToken token = default) + where T : HashAlgorithm => + GetCryptoHash(fileInfo.OpenRead, hmacKey, readProgress, token); + + /// + /// Synchronously computes the cryptographic hash of a file specified by a object. + /// + /// The type of the hash algorithm to use. Must inherit from . + /// A delegate function which returns the stream to compute the hash for. + /// A cancellation token to observe while waiting for the operation to complete. + /// The key to use for HMAC-based hash algorithms. If null, a standard hash algorithm is used. + /// An action to report the read progress. + /// The computed hash as a byte array. + public static byte[] GetCryptoHash( + Func streamDelegate, + byte[]? hmacKey = null, + Action? readProgress = null, + CancellationToken token = default) where T : HashAlgorithm { - using FileStream fileStream = fileInfo.OpenRead(); - return GetCryptoHash(fileStream, hmacKey, readProgress, token); + using Stream stream = streamDelegate(); + return GetCryptoHash(stream, hmacKey, readProgress, token); } /// @@ -142,7 +183,8 @@ public static byte[] GetCryptoHash( CreateCryptoHash(); // Get length based on stream length or at least if bigger, use the default one - int bufferLen = BufferLength > stream.Length ? (int)stream.Length : BufferLength; + long streamLen = GetStreamLength(stream); + int bufferLen = streamLen != -1 && BufferLength > streamLen ? (int)streamLen : BufferLength; // Initialize buffer byte[] buffer = ArrayPool.Shared.Rent(bufferLen); @@ -188,11 +230,8 @@ public static ConfiguredTaskAwaitable GetHashAsync( string filePath, Action? readProgress = null, CancellationToken token = default) - where T : NonCryptographicHashAlgorithm, new() - { - using FileStream fileStream = File.OpenRead(filePath); - return GetHashAsync(fileStream, readProgress, token); - } + where T : NonCryptographicHashAlgorithm, new() => + GetHashAsync(() => File.OpenRead(filePath), readProgress, token); /// /// Asynchronously computes the non-cryptographic hash of a file specified by a object. @@ -206,11 +245,8 @@ public static ConfiguredTaskAwaitable GetHashAsync( FileInfo fileInfo, Action? readProgress = null, CancellationToken token = default) - where T : NonCryptographicHashAlgorithm, new() - { - using FileStream fileStream = fileInfo.OpenRead(); - return GetHashAsync(fileStream, readProgress, token); - } + where T : NonCryptographicHashAlgorithm, new() => + GetHashAsync(fileInfo.OpenRead, readProgress, token); /// /// Asynchronously computes the non-cryptographic hash of a stream. @@ -238,6 +274,36 @@ public static ConfiguredTaskAwaitable GetHashAsync( return task.ConfigureAwait(false); } + /// + /// Asynchronously computes the non-cryptographic hash of a stream. + /// + /// The type of the non-cryptographic hash algorithm to use. Must inherit from and have a parameterless constructor. + /// A delegate function which returns the stream to compute the hash for. + /// An action to report the read progress. + /// A cancellation token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains the computed hash as a byte array. + public static ConfiguredTaskAwaitable GetHashAsync( + Func streamDelegate, + Action? readProgress = null, + CancellationToken token = default) + where T : NonCryptographicHashAlgorithm, new() + { + // Create a new task from factory, assign a synchronous method to it with detached thread. + Task task = Task + .Factory + .StartNew(() => + { + using Stream stream = streamDelegate(); + return GetHash(stream, readProgress, token); + }, + token, + TaskCreationOptions.DenyChildAttach, + TaskScheduler.Default); + + // Create awaitable returnable-task + return task.ConfigureAwait(false); + } + /// /// Synchronously computes the non-cryptographic hash of a file specified by its path. /// @@ -292,7 +358,8 @@ public static byte[] GetHash( NonCryptographicHashAlgorithm hashProvider = CreateHash(); // Get length based on stream length or at least if bigger, use the default one - int bufferLen = BufferLength > stream.Length ? (int)stream.Length : BufferLength; + long streamLen = GetStreamLength(stream); + int bufferLen = streamLen != -1 && BufferLength > streamLen ? (int)streamLen : BufferLength; // Initialize buffer byte[] buffer = ArrayPool.Shared.Rent(bufferLen); @@ -322,5 +389,22 @@ public static byte[] GetHash( ArrayPool.Shared.Return(buffer); } } + + /// + /// Try to get the length of the stream. + /// + /// The stream to get the length to. + /// If it doesn't have exact length (which will throw), return -1. Otherwise, return the actual length. + private static long GetStreamLength(Stream stream) + { + try + { + return stream.Length; + } + catch + { + return -1; + } + } } } From c06d19607a007aa07bc6d1b1480a3d1b149eda36 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Fri, 31 Jan 2025 15:35:34 +0700 Subject: [PATCH 03/31] Allow Hash methods to run with LongRunning flag + This to ensure that the thread for other hashing routines to be available --- .../Classes/Helper/Hash.FileStream.cs | 246 ++++++++++++++++-- .../Classes/Helper/Hash.StringAndBytes.cs | 52 ++-- .../Classes/Interfaces/Class/ProgressBase.cs | 20 +- 3 files changed, 263 insertions(+), 55 deletions(-) diff --git a/CollapseLauncher/Classes/Helper/Hash.FileStream.cs b/CollapseLauncher/Classes/Helper/Hash.FileStream.cs index b5122f118..e50663496 100644 --- a/CollapseLauncher/Classes/Helper/Hash.FileStream.cs +++ b/CollapseLauncher/Classes/Helper/Hash.FileStream.cs @@ -18,9 +18,9 @@ public static partial class Hash /// /// The type of the hash algorithm to use. Must inherit from . /// The path of the file to compute the hash for. - /// A cancellation token to observe while waiting for the task to complete. /// The key to use for HMAC-based hash algorithms. If null, a standard hash algorithm is used. /// An action to report the read progress. + /// A cancellation token to observe while waiting for the task to complete. /// A task that represents the asynchronous operation. The task result contains the computed hash as a byte array. public static ConfiguredTaskAwaitable GetCryptoHashAsync( string filePath, @@ -28,16 +28,39 @@ public static ConfiguredTaskAwaitable GetCryptoHashAsync( Action? readProgress = null, CancellationToken token = default) where T : HashAlgorithm => - GetCryptoHashAsync(() => File.OpenRead(filePath), hmacKey, readProgress, token); + GetCryptoHashAsync(() => File.OpenRead(filePath), hmacKey, readProgress, false, token); + + /// + /// Asynchronously computes the cryptographic hash of a file specified by its path. + /// + /// The type of the hash algorithm to use. Must inherit from . + /// The path of the file to compute the hash for. + /// The key to use for HMAC-based hash algorithms. If null, a standard hash algorithm is used. + /// An action to report the read progress. + /// + /// Define where the async method should run for hashing big files.
+ /// This to hint the default TaskScheduler to allow more hashing threads to be running at the same time.
+ /// Set to true if the data stream is big, otherwise false for small data stream. + /// + /// A cancellation token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains the computed hash as a byte array. + public static ConfiguredTaskAwaitable GetCryptoHashAsync( + string filePath, + byte[]? hmacKey, + Action? readProgress, + bool isLongRunning, + CancellationToken token) + where T : HashAlgorithm => + GetCryptoHashAsync(() => File.OpenRead(filePath), hmacKey, readProgress, isLongRunning, token); /// /// Asynchronously computes the cryptographic hash of a file specified by a object. /// /// The type of the hash algorithm to use. Must inherit from . /// The object representing the file to compute the hash for. - /// A cancellation token to observe while waiting for the task to complete. /// The key to use for HMAC-based hash algorithms. If null, a standard hash algorithm is used. /// An action to report the read progress. + /// A cancellation token to observe while waiting for the task to complete. /// A task that represents the asynchronous operation. The task result contains the computed hash as a byte array. public static ConfiguredTaskAwaitable GetCryptoHashAsync( FileInfo fileInfo, @@ -45,34 +68,87 @@ public static ConfiguredTaskAwaitable GetCryptoHashAsync( Action? readProgress = null, CancellationToken token = default) where T : HashAlgorithm => - GetCryptoHashAsync(fileInfo.OpenRead, hmacKey, readProgress, token); + GetCryptoHashAsync(fileInfo.OpenRead, hmacKey, readProgress, false, token); + + /// + /// Asynchronously computes the cryptographic hash of a file specified by a object. + /// + /// The type of the hash algorithm to use. Must inherit from . + /// The object representing the file to compute the hash for. + /// The key to use for HMAC-based hash algorithms. If null, a standard hash algorithm is used. + /// An action to report the read progress. + /// + /// Define where the async method should run for hashing big files.
+ /// This to hint the default TaskScheduler to allow more hashing threads to be running at the same time.
+ /// Set to true if the data stream is big, otherwise false for small data stream. + /// + /// A cancellation token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains the computed hash as a byte array. + public static ConfiguredTaskAwaitable GetCryptoHashAsync( + FileInfo fileInfo, + byte[]? hmacKey, + Action? readProgress, + bool isLongRunning, + CancellationToken token) + where T : HashAlgorithm => + GetCryptoHashAsync(fileInfo.OpenRead, hmacKey, readProgress, isLongRunning, token); /// /// Asynchronously computes the cryptographic hash of a stream. /// /// The type of the hash algorithm to use. Must inherit from . /// The stream to compute the hash for. - /// A cancellation token to observe while waiting for the task to complete. /// The key to use for HMAC-based hash algorithms. If null, a standard hash algorithm is used. /// An action to report the read progress. + /// A cancellation token to observe while waiting for the task to complete. /// A task that represents the asynchronous operation. The task result contains the computed hash as a byte array. public static ConfiguredTaskAwaitable GetCryptoHashAsync( Stream stream, byte[]? hmacKey = null, Action? readProgress = null, CancellationToken token = default) + where T : HashAlgorithm => + GetCryptoHashAsync(stream, hmacKey, readProgress, false, token); + + /// + /// Asynchronously computes the cryptographic hash of a stream. + /// + /// The type of the hash algorithm to use. Must inherit from . + /// The stream to compute the hash for. + /// The key to use for HMAC-based hash algorithms. If null, a standard hash algorithm is used. + /// An action to report the read progress. + /// + /// Define where the async method should run for hashing big files.
+ /// This to hint the default TaskScheduler to allow more hashing threads to be running at the same time.
+ /// Set to true if the data stream is big, otherwise false for small data stream. + /// + /// A cancellation token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains the computed hash as a byte array. + public static ConfiguredTaskAwaitable GetCryptoHashAsync( + Stream stream, + byte[]? hmacKey, + Action? readProgress, + bool isLongRunning, + CancellationToken token) where T : HashAlgorithm { // Create a new task from factory, assign a synchronous method to it with detached thread. Task task = Task .Factory - .StartNew(() => GetCryptoHash(stream, hmacKey, readProgress, token), + .StartNew(Impl, + (stream, hmacKey, readProgress, token), token, - TaskCreationOptions.DenyChildAttach, + isLongRunning ? TaskCreationOptions.LongRunning : TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); // Create awaitable returnable-task return task.ConfigureAwait(false); + + static byte[] Impl(object? state) + { + (Stream stream, byte[]? hmacKey, Action? readProgress, CancellationToken token) = ((Stream, byte[]?, Action?, CancellationToken))state!; + return GetCryptoHash(stream, hmacKey, readProgress, token); + } } /// @@ -80,31 +156,58 @@ public static ConfiguredTaskAwaitable GetCryptoHashAsync( /// /// The type of the hash algorithm to use. Must inherit from . /// A delegate function which returns the stream to compute the hash for. - /// A cancellation token to observe while waiting for the task to complete. /// The key to use for HMAC-based hash algorithms. If null, a standard hash algorithm is used. /// An action to report the read progress. + /// A cancellation token to observe while waiting for the task to complete. /// A task that represents the asynchronous operation. The task result contains the computed hash as a byte array. public static ConfiguredTaskAwaitable GetCryptoHashAsync( Func streamDelegate, byte[]? hmacKey = null, Action? readProgress = null, CancellationToken token = default) + where T : HashAlgorithm => + GetCryptoHashAsync(streamDelegate, hmacKey, readProgress, false, token); + + /// + /// Asynchronously computes the cryptographic hash of a stream. + /// + /// The type of the hash algorithm to use. Must inherit from . + /// A delegate function which returns the stream to compute the hash for. + /// The key to use for HMAC-based hash algorithms. If null, a standard hash algorithm is used. + /// An action to report the read progress. + /// + /// Define where the async method should run for hashing big files.
+ /// This to hint the default TaskScheduler to allow more hashing threads to be running at the same time.
+ /// Set to true if the data stream is big, otherwise false for small data stream. + /// + /// A cancellation token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains the computed hash as a byte array. + public static ConfiguredTaskAwaitable GetCryptoHashAsync( + Func streamDelegate, + byte[]? hmacKey, + Action? readProgress, + bool isLongRunning, + CancellationToken token) where T : HashAlgorithm { // Create a new task from factory, assign a synchronous method to it with detached thread. Task task = Task .Factory - .StartNew(() => - { - using Stream stream = streamDelegate(); - return GetCryptoHash(stream, hmacKey, readProgress, token); - }, + .StartNew(Impl, + (streamDelegate, hmacKey, readProgress, token), token, - TaskCreationOptions.DenyChildAttach, + isLongRunning ? TaskCreationOptions.LongRunning : TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); // Create awaitable returnable-task return task.ConfigureAwait(false); + + static byte[] Impl(object? state) + { + (Func streamDelegate, byte[]? hmacKey, Action? readProgress, CancellationToken token) = ((Func, byte[]?, Action?, CancellationToken))state!; + using Stream stream = streamDelegate(); + return GetCryptoHash(stream, hmacKey, readProgress, token); + } } /// @@ -231,7 +334,28 @@ public static ConfiguredTaskAwaitable GetHashAsync( Action? readProgress = null, CancellationToken token = default) where T : NonCryptographicHashAlgorithm, new() => - GetHashAsync(() => File.OpenRead(filePath), readProgress, token); + GetHashAsync(() => File.OpenRead(filePath), readProgress, false, token); + + /// + /// Asynchronously computes the non-cryptographic hash of a file specified by its path. + /// + /// The type of the non-cryptographic hash algorithm to use. Must inherit from and have a parameterless constructor. + /// The path of the file to compute the hash for. + /// An action to report the read progress. + /// + /// Define where the async method should run for hashing big files.
+ /// This to hint the default TaskScheduler to allow more hashing threads to be running at the same time.
+ /// Set to true if the data stream is big, otherwise false for small data stream. + /// + /// A cancellation token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains the computed hash as a byte array. + public static ConfiguredTaskAwaitable GetHashAsync( + string filePath, + Action? readProgress, + bool isLongRunning, + CancellationToken token) + where T : NonCryptographicHashAlgorithm, new() => + GetHashAsync(() => File.OpenRead(filePath), readProgress, isLongRunning, token); /// /// Asynchronously computes the non-cryptographic hash of a file specified by a object. @@ -246,7 +370,28 @@ public static ConfiguredTaskAwaitable GetHashAsync( Action? readProgress = null, CancellationToken token = default) where T : NonCryptographicHashAlgorithm, new() => - GetHashAsync(fileInfo.OpenRead, readProgress, token); + GetHashAsync(fileInfo.OpenRead, readProgress, false, token); + + /// + /// Asynchronously computes the non-cryptographic hash of a file specified by a object. + /// + /// The type of the non-cryptographic hash algorithm to use. Must inherit from and have a parameterless constructor. + /// The object representing the file to compute the hash for. + /// An action to report the read progress. + /// + /// Define where the async method should run for hashing big files.
+ /// This to hint the default TaskScheduler to allow more hashing threads to be running at the same time.
+ /// Set to true if the data stream is big, otherwise false for small data stream. + /// + /// A cancellation token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains the computed hash as a byte array. + public static ConfiguredTaskAwaitable GetHashAsync( + FileInfo fileInfo, + Action? readProgress, + bool isLongRunning, + CancellationToken token) + where T : NonCryptographicHashAlgorithm, new() => + GetHashAsync(fileInfo.OpenRead, readProgress, isLongRunning, token); /// /// Asynchronously computes the non-cryptographic hash of a stream. @@ -260,18 +405,46 @@ public static ConfiguredTaskAwaitable GetHashAsync( Stream stream, Action? readProgress = null, CancellationToken token = default) + where T : NonCryptographicHashAlgorithm, new() => + GetHashAsync(stream, readProgress, false, token); + + /// + /// Asynchronously computes the non-cryptographic hash of a stream. + /// + /// The type of the non-cryptographic hash algorithm to use. Must inherit from and have a parameterless constructor. + /// The stream to compute the hash for. + /// An action to report the read progress. + /// + /// Define where the async method should run for hashing big files.
+ /// This to hint the default TaskScheduler to allow more hashing threads to be running at the same time.
+ /// Set to true if the data stream is big, otherwise false for small data stream. + /// + /// A cancellation token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains the computed hash as a byte array. + public static ConfiguredTaskAwaitable GetHashAsync( + Stream stream, + Action? readProgress, + bool isLongRunning, + CancellationToken token) where T : NonCryptographicHashAlgorithm, new() { // Create a new task from factory, assign a synchronous method to it with detached thread. Task task = Task .Factory - .StartNew(() => GetHash(stream, readProgress, token), + .StartNew(Impl, + (stream, readProgress, token), token, - TaskCreationOptions.DenyChildAttach, + isLongRunning ? TaskCreationOptions.LongRunning : TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); // Create awaitable returnable-task return task.ConfigureAwait(false); + + static byte[] Impl(object? state) + { + (Stream stream, Action? readProgress, CancellationToken token) = ((Stream, Action?, CancellationToken))state!; + return GetHash(stream, readProgress, token); + } } /// @@ -286,22 +459,47 @@ public static ConfiguredTaskAwaitable GetHashAsync( Func streamDelegate, Action? readProgress = null, CancellationToken token = default) + where T : NonCryptographicHashAlgorithm, new() => + GetHashAsync(streamDelegate, readProgress, false, token); + + /// + /// Asynchronously computes the non-cryptographic hash of a stream. + /// + /// The type of the non-cryptographic hash algorithm to use. Must inherit from and have a parameterless constructor. + /// A delegate function which returns the stream to compute the hash for. + /// An action to report the read progress. + /// + /// Define where the async method should run for hashing big files.
+ /// This to hint the default TaskScheduler to allow more hashing threads to be running at the same time.
+ /// Set to true if the data stream is big, otherwise false for small data stream. + /// + /// A cancellation token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains the computed hash as a byte array. + public static ConfiguredTaskAwaitable GetHashAsync( + Func streamDelegate, + Action? readProgress, + bool isLongRunning, + CancellationToken token) where T : NonCryptographicHashAlgorithm, new() { // Create a new task from factory, assign a synchronous method to it with detached thread. Task task = Task .Factory - .StartNew(() => - { - using Stream stream = streamDelegate(); - return GetHash(stream, readProgress, token); - }, + .StartNew(Impl, + (streamDelegate, readProgress, token), token, - TaskCreationOptions.DenyChildAttach, + isLongRunning ? TaskCreationOptions.LongRunning : TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); // Create awaitable returnable-task return task.ConfigureAwait(false); + + static byte[] Impl(object? state) + { + (Func streamDelegate, Action? readProgress, CancellationToken token) = ((Func, Action?, CancellationToken))state!; + using Stream stream = streamDelegate(); + return GetHash(stream, readProgress, token); + } } /// diff --git a/CollapseLauncher/Classes/Helper/Hash.StringAndBytes.cs b/CollapseLauncher/Classes/Helper/Hash.StringAndBytes.cs index 7e9bbf62e..6454b9f2b 100644 --- a/CollapseLauncher/Classes/Helper/Hash.StringAndBytes.cs +++ b/CollapseLauncher/Classes/Helper/Hash.StringAndBytes.cs @@ -52,10 +52,11 @@ public static byte[] GetHashFromBytes(ReadOnlySpan source) where T : NonCryptographicHashAlgorithm { // Get the shared hash algorithm and the thread lock instance - ref Tuple hash = ref GetSharedHash(); + ref (NonCryptographicHashAlgorithm? Hash, Lock Lock) hash = ref GetSharedHash(); // Allocate the return buffer and calculate the hash from the source span - byte[] hashBytesReturn = new byte[hash.Item1!.HashLengthInBytes]; + int hashLenInBytes = hash.Hash!.HashLengthInBytes; + byte[] hashBytesReturn = new byte[hashLenInBytes]; if (!TryGetHashFromBytes(ref hash!, source, hashBytesReturn, out _)) { throw new InvalidOperationException("Failed to get the hash."); @@ -75,11 +76,12 @@ public static string GetHashStringFromBytes(ReadOnlySpan source) where T : NonCryptographicHashAlgorithm { // Get the shared hash algorithm and the thread lock instance - ref Tuple hash = ref GetSharedHash(); + ref (NonCryptographicHashAlgorithm? Hash, Lock Lock) hash = ref GetSharedHash(); // Allocate the hash buffer to be written to - Span hashBuffer = stackalloc byte[hash.Item1!.HashLengthInBytes]; - Span hashCharBuffer = stackalloc char[hash.Item1.HashLengthInBytes * 2]; + int hashLenInBytes = hash.Hash!.HashLengthInBytes; + Span hashBuffer = stackalloc byte[hashLenInBytes]; + Span hashCharBuffer = stackalloc char[hashLenInBytes * 2]; // Compute the hash and reset if (!TryGetHashFromBytes(ref hash!, source, hashBuffer, out _)) @@ -107,20 +109,20 @@ public static string GetHashStringFromBytes(ReadOnlySpan source) /// The length of how much bytes is the hash written to the . /// True if it's successfully calculate the hash, False as failed. public static bool TryGetHashFromBytes( - ref Tuple hashSource, - ReadOnlySpan source, - Span destination, - out int hashBytesWritten) + ref (NonCryptographicHashAlgorithm Hash, Lock Lock) hashSource, + ReadOnlySpan source, + Span destination, + out int hashBytesWritten) where T : NonCryptographicHashAlgorithm { // Lock the thread and append the span to the hash algorithm - lock (hashSource.Item2) + lock (hashSource.Lock) { // Append and calculate the hash of the span - hashSource.Item1.Append(source); + hashSource.Hash.Append(source); // Return the bool as success or not, then reset the hash while writing the hash bytes to destination span. - return hashSource.Item1.TryGetHashAndReset(destination, out hashBytesWritten); + return hashSource.Hash.TryGetHashAndReset(destination, out hashBytesWritten); } } #endregion @@ -168,10 +170,11 @@ public static byte[] GetCryptoHashFromBytes(ReadOnlySpan source) where T : HashAlgorithm { // Get the shared hash algorithm and the thread lock instance - ref Tuple hash = ref GetSharedCryptoHash(); + ref (HashAlgorithm? Hash, Lock Lock) hash = ref GetSharedCryptoHash(); // Allocate the return buffer and calculate the hash from the source span - byte[] hashBytesReturn = new byte[hash.Item1!.HashSize]; + int hashLenInBytes = hash.Hash!.HashSize; + byte[] hashBytesReturn = new byte[hashLenInBytes]; if (!TryGetCryptoHashFromBytes(ref hash!, source, hashBytesReturn, out _)) { throw new InvalidOperationException("Failed to get the hash."); @@ -191,11 +194,12 @@ public static string GetCryptoHashStringFromBytes(ReadOnlySpan source) where T : HashAlgorithm { // Get the shared hash algorithm and the thread lock instance - ref Tuple hash = ref GetSharedCryptoHash(); + ref (HashAlgorithm? Hash, Lock Lock) hash = ref GetSharedCryptoHash(); // Allocate the hash buffer to be written to - Span hashBuffer = stackalloc byte[hash.Item1!.HashSize]; - Span hashCharBuffer = stackalloc char[hash.Item1.HashSize * 2]; + int hashLenInBytes = hash.Hash!.HashSize; + Span hashBuffer = stackalloc byte[hashLenInBytes]; + Span hashCharBuffer = stackalloc char[hashLenInBytes * 2]; // Compute the hash and reset if (!TryGetCryptoHashFromBytes(ref hash!, source, hashBuffer, out _)) @@ -223,20 +227,20 @@ public static string GetCryptoHashStringFromBytes(ReadOnlySpan source) /// The length of how much bytes is the hash written to the . /// True if it's successfully calculate the hash, False as failed. public static bool TryGetCryptoHashFromBytes( - ref Tuple hashSource, - ReadOnlySpan source, - Span destination, - out int hashBytesWritten) + ref (HashAlgorithm Hash, Lock Lock) hashSource, + ReadOnlySpan source, + Span destination, + out int hashBytesWritten) where T : HashAlgorithm { // Lock the thread and compute the hash of the span - lock (hashSource.Item2) + lock (hashSource.Lock) { // Reset the hash instance state. - hashSource.Item1.Initialize(); + hashSource.Hash.Initialize(); // Compute the source bytes and return the success state - return hashSource.Item1.TryComputeHash(source, destination, out hashBytesWritten); + return hashSource.Hash.TryComputeHash(source, destination, out hashBytesWritten); } } #endregion diff --git a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs index ff3ffe0f7..712e63aa1 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs @@ -1020,10 +1020,11 @@ protected virtual ConfiguredTaskAwaitable GetCryptoHashAsync( bool updateTotalProgress = true, CancellationToken token = default) where T : HashAlgorithm => - Hash.GetCryptoHashAsync(filePath, - hmacKey, - read => UpdateHashReadProgress(read, updateProgress, updateTotalProgress), - token); + GetCryptoHashAsync(new FileInfo(filePath), + hmacKey, + updateProgress, + updateTotalProgress, + token); protected virtual ConfiguredTaskAwaitable GetCryptoHashAsync( FileInfo fileInfo, @@ -1035,6 +1036,7 @@ protected virtual ConfiguredTaskAwaitable GetCryptoHashAsync( Hash.GetCryptoHashAsync(fileInfo, hmacKey, read => UpdateHashReadProgress(read, updateProgress, updateTotalProgress), + fileInfo is { Exists: true, Length: > 100 << 20 }, token); protected virtual ConfiguredTaskAwaitable GetCryptoHashAsync( @@ -1047,6 +1049,7 @@ protected virtual ConfiguredTaskAwaitable GetCryptoHashAsync( Hash.GetCryptoHashAsync(stream, hmacKey, read => UpdateHashReadProgress(read, updateProgress, updateTotalProgress), + stream is { Length: > 100 << 20 }, token); protected virtual byte[] GetCryptoHash( @@ -1091,9 +1094,10 @@ protected virtual ConfiguredTaskAwaitable GetHashAsync( bool updateTotalProgress = true, CancellationToken token = default) where T : NonCryptographicHashAlgorithm, new() => - Hash.GetHashAsync(filePath, - read => UpdateHashReadProgress(read, updateProgress, updateTotalProgress), - token); + GetHashAsync(new FileInfo(filePath), + updateProgress, + updateTotalProgress, + token); protected virtual ConfiguredTaskAwaitable GetHashAsync( FileInfo fileInfo, @@ -1103,6 +1107,7 @@ protected virtual ConfiguredTaskAwaitable GetHashAsync( where T : NonCryptographicHashAlgorithm, new() => Hash.GetHashAsync(fileInfo, read => UpdateHashReadProgress(read, updateProgress, updateTotalProgress), + fileInfo is { Exists: true, Length: > 100 << 20 }, token); protected virtual ConfiguredTaskAwaitable GetHashAsync( @@ -1113,6 +1118,7 @@ protected virtual ConfiguredTaskAwaitable GetHashAsync( where T : NonCryptographicHashAlgorithm, new() => Hash.GetHashAsync(stream, read => UpdateHashReadProgress(read, updateProgress, updateTotalProgress), + stream is { Length: > 100 << 20 }, token); protected virtual byte[] GetHash( From dce1f09e5d3c8da84451c264fa903f248013b19f Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Fri, 31 Jan 2025 15:37:30 +0700 Subject: [PATCH 04/31] Use element return instead of tuple Also remove early null check on Get###Hash methods --- CollapseLauncher/Classes/Helper/Hash.cs | 64 ++++++++----------------- 1 file changed, 20 insertions(+), 44 deletions(-) diff --git a/CollapseLauncher/Classes/Helper/Hash.cs b/CollapseLauncher/Classes/Helper/Hash.cs index 047a15cc9..8dc1965de 100644 --- a/CollapseLauncher/Classes/Helper/Hash.cs +++ b/CollapseLauncher/Classes/Helper/Hash.cs @@ -34,26 +34,26 @@ public static partial class Hash { typeof(HMACSHA3_512).GetHashCode(), key => new HMACSHA3_512(key) } }; - private static readonly Dictionary> CryptoHashDictShared = new() + private static readonly Dictionary CryptoHashDictShared = new() { - { typeof(MD5).GetHashCode(), new Tuple(CreateHashAndNullIfUnsupported(MD5.Create), new Lock()) }, - { typeof(SHA1).GetHashCode(), new Tuple(CreateHashAndNullIfUnsupported(SHA1.Create), new Lock()) }, - { typeof(SHA256).GetHashCode(), new Tuple(CreateHashAndNullIfUnsupported(SHA256.Create), new Lock()) }, - { typeof(SHA384).GetHashCode(), new Tuple(CreateHashAndNullIfUnsupported(SHA384.Create), new Lock()) }, - { typeof(SHA512).GetHashCode(), new Tuple(CreateHashAndNullIfUnsupported(SHA512.Create), new Lock()) }, - { typeof(SHA3_256).GetHashCode(), new Tuple(CreateHashAndNullIfUnsupported(SHA3_256.Create), new Lock()) }, - { typeof(SHA3_384).GetHashCode(), new Tuple(CreateHashAndNullIfUnsupported(SHA3_384.Create), new Lock()) }, - { typeof(SHA3_512).GetHashCode(), new Tuple(CreateHashAndNullIfUnsupported(SHA3_512.Create), new Lock()) } + { typeof(MD5).GetHashCode(), (CreateHashAndNullIfUnsupported(MD5.Create), new Lock()) }, + { typeof(SHA1).GetHashCode(), (CreateHashAndNullIfUnsupported(SHA1.Create), new Lock()) }, + { typeof(SHA256).GetHashCode(), (CreateHashAndNullIfUnsupported(SHA256.Create), new Lock()) }, + { typeof(SHA384).GetHashCode(), (CreateHashAndNullIfUnsupported(SHA384.Create), new Lock()) }, + { typeof(SHA512).GetHashCode(), (CreateHashAndNullIfUnsupported(SHA512.Create), new Lock()) }, + { typeof(SHA3_256).GetHashCode(), (CreateHashAndNullIfUnsupported(SHA3_256.Create), new Lock()) }, + { typeof(SHA3_384).GetHashCode(), (CreateHashAndNullIfUnsupported(SHA3_384.Create), new Lock()) }, + { typeof(SHA3_512).GetHashCode(), (CreateHashAndNullIfUnsupported(SHA3_512.Create), new Lock()) } }; - private static readonly Dictionary> HashDictShared = new() + private static readonly Dictionary HashDictShared = new() { - { typeof(Crc32).GetHashCode(), new Tuple(CreateHashAndNullIfUnsupported(() => new Crc32()), new Lock()) }, - { typeof(Crc64).GetHashCode(), new Tuple(CreateHashAndNullIfUnsupported(() => new Crc64()), new Lock()) }, - { typeof(XxHash3).GetHashCode(), new Tuple(CreateHashAndNullIfUnsupported(() => new XxHash3()), new Lock()) }, - { typeof(XxHash32).GetHashCode(), new Tuple(CreateHashAndNullIfUnsupported(() => new XxHash32()), new Lock()) }, - { typeof(XxHash64).GetHashCode(), new Tuple(CreateHashAndNullIfUnsupported(() => new XxHash64()), new Lock()) }, - { typeof(XxHash128).GetHashCode(), new Tuple(CreateHashAndNullIfUnsupported(() => new XxHash128()), new Lock()) } + { typeof(Crc32).GetHashCode(), (CreateHashAndNullIfUnsupported(() => new Crc32()), new Lock()) }, + { typeof(Crc64).GetHashCode(), (CreateHashAndNullIfUnsupported(() => new Crc64()), new Lock()) }, + { typeof(XxHash3).GetHashCode(), (CreateHashAndNullIfUnsupported(() => new XxHash3()), new Lock()) }, + { typeof(XxHash32).GetHashCode(), (CreateHashAndNullIfUnsupported(() => new XxHash32()), new Lock()) }, + { typeof(XxHash64).GetHashCode(), (CreateHashAndNullIfUnsupported(() => new XxHash64()), new Lock()) }, + { typeof(XxHash128).GetHashCode(), (CreateHashAndNullIfUnsupported(() => new XxHash128()), new Lock()) } }; private static T? CreateHashAndNullIfUnsupported(Func delegateCreate) @@ -82,12 +82,6 @@ public static HashAlgorithm CreateCryptoHash() ref Func createHashDelegate = ref CollectionsMarshal .GetValueRefOrNullRef(CryptoHashDict, typeof(T).GetHashCode()); - // If the delegate is null, then throw an exception - if (createHashDelegate == null) - { - throw new NotSupportedException($"Cannot create hash algorithm instance from {typeof(T)}."); - } - // Create the hash algorithm instance return createHashDelegate(); } @@ -106,12 +100,6 @@ public static HashAlgorithm CreateHmacCryptoHash(byte[] key) ref Func createHashDelegate = ref CollectionsMarshal .GetValueRefOrNullRef(CryptoHmacHashDict, typeof(T).GetHashCode()); - // If the delegate is null, then throw an exception - if (createHashDelegate == null) - { - throw new NotSupportedException($"Cannot create HMAC-based hash algorithm instance from {typeof(T)}."); - } - // Create the hash algorithm instance return createHashDelegate(key); } @@ -130,19 +118,13 @@ public static NonCryptographicHashAlgorithm CreateHash() /// The type of the non-cryptographic hash algorithm to use. /// A tuple of the non-cryptographic hash algorithm and the thread instance. /// Thrown when the specified non-cryptographic hash algorithm type is not supported. - public static ref Tuple GetSharedHash() + public static ref (NonCryptographicHashAlgorithm? Hash, Lock Lock) GetSharedHash() where T : NonCryptographicHashAlgorithm { // Get reference from the dictionary - ref Tuple hash = ref CollectionsMarshal + ref (NonCryptographicHashAlgorithm? Hash, Lock Lock) hash = ref CollectionsMarshal .GetValueRefOrNullRef(HashDictShared, typeof(T).GetHashCode()); - // If the tuple is null, then throw an exception - if (hash == null || hash.Item1 == null) - { - throw new NotSupportedException($"Cannot create HMAC-based hash algorithm instance from {typeof(T)}."); - } - // Return the tuple reference return ref hash; } @@ -153,19 +135,13 @@ public static NonCryptographicHashAlgorithm CreateHash() /// The type of the cryptographic hash algorithm to use. /// A tuple of the cryptographic hash algorithm and the thread instance. /// Thrown when the specified cryptographic hash algorithm type is not supported. - public static ref Tuple GetSharedCryptoHash() + public static ref (HashAlgorithm? Hash, Lock Lock) GetSharedCryptoHash() where T : HashAlgorithm { // Get reference from the dictionary - ref Tuple hash = ref CollectionsMarshal + ref (HashAlgorithm? Hash, Lock Lock) hash = ref CollectionsMarshal .GetValueRefOrNullRef(CryptoHashDictShared, typeof(T).GetHashCode()); - // If the tuple is null, then throw an exception - if (hash == null || hash.Item1 == null) - { - throw new NotSupportedException($"Cannot create HMAC-based hash algorithm instance from {typeof(T)}."); - } - // Return the tuple reference return ref hash; } From 7ae1dbb66c84dc0675eae17f7b3d3d03230f8223 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Fri, 31 Jan 2025 22:16:20 +0700 Subject: [PATCH 05/31] Bump version + Update NuGet --- CollapseLauncher/CollapseLauncher.csproj | 16 +-- CollapseLauncher/packages.lock.json | 104 +++++++++--------- ...Toolkit.WinUI.Controls.ImageCropper.csproj | 4 +- .../ImageCropper/packages.lock.json | 30 ++--- ...kit.WinUI.Controls.SettingsControls.csproj | 2 +- .../SettingsControls/packages.lock.json | 24 ++-- ImageEx | 2 +- 7 files changed, 91 insertions(+), 91 deletions(-) diff --git a/CollapseLauncher/CollapseLauncher.csproj b/CollapseLauncher/CollapseLauncher.csproj index 2e8d9a7f6..b5cae28ad 100644 --- a/CollapseLauncher/CollapseLauncher.csproj +++ b/CollapseLauncher/CollapseLauncher.csproj @@ -16,7 +16,7 @@ $(Company). neon-nyan, Cry0, bagusnl, shatyuka, gablm. Copyright 2022-2024 $(Company) - 1.82.15 + 1.82.16 preview x64 @@ -162,12 +162,12 @@ - - - - - - + + + + + + Dialog_MigrationChoiceDialog(UIEle isHasOnlyMigrateOption ? null : Lang._Misc.MoveToDifferentDir ); } - public static async Task Dialog_SteamConversionNoPermission(UIElement content) => - await SpawnDialog( - Lang._Dialogs.SteamConvertNeedMigrateTitle, - Lang._Dialogs.SteamConvertNeedMigrateSubtitle, - content, - Lang._Misc.Cancel, - Lang._Misc.Yes, - Lang._Misc.NoOtherLocation, - ContentDialogButton.Secondary, - ContentDialogTheme.Error - ); - - public static async Task Dialog_SteamConversionDownloadDialog(UIElement content, string sizeString) => - await SpawnDialog( - Lang._Dialogs.SteamConvertIntegrityDoneTitle, - Lang._Dialogs.SteamConvertIntegrityDoneSubtitle, - content, - Lang._Misc.Cancel, - Lang._Misc.Yes, - null, - ContentDialogButton.Secondary, - ContentDialogTheme.Success - ); - - public static async Task Dialog_SteamConversionFailedDialog(UIElement content) => - await SpawnDialog( - Lang._Dialogs.SteamConvertFailedTitle, - Lang._Dialogs.SteamConvertFailedSubtitle, - content, - Lang._Misc.OkaySad, - null, - null, - ContentDialogButton.Close, - ContentDialogTheme.Success - ); public static async Task Dialog_GameInstallationFileCorrupt(UIElement content, string sourceHash, string downloadedHash) => await SpawnDialog( @@ -836,18 +801,6 @@ await SpawnDialog( Lang._StartupPage.ChooseFolderDialogSecondary ); - public static async Task Dialog_CannotUseAppLocationForGameDir(UIElement content) => - await SpawnDialog( - Lang._Dialogs.CannotUseAppLocationForGameDirTitle, - Lang._Dialogs.CannotUseAppLocationForGameDirSubtitle, - content, - Lang._Misc.Okay, - null, - null, - ContentDialogButton.Close, - ContentDialogTheme.Error - ); - public static async Task Dialog_ExistingDownload(UIElement content, double partialLength, double contentLength) => await SpawnDialog( Lang._Dialogs.InstallDataDownloadResumeTitle, diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/HonkaiGameSettingsPage.Ext.cs b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/HonkaiGameSettingsPage.Ext.cs index 848eabfb7..9c77622dc 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/HonkaiGameSettingsPage.Ext.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/HonkaiGameSettingsPage.Ext.cs @@ -37,16 +37,16 @@ private void OnPropertyChanged([CallerMemberName] string propertyName = null) #region Presets public ICollection PresetRenderingNames { - get => Settings.Preset_SettingsGraphics.PresetKeys; + get => Settings.PresetSettingsGraphics.PresetKeys; } public int PresetRenderingIndex { get { - string name = Settings.Preset_SettingsGraphics.GetPresetKey(); - int index = Settings.Preset_SettingsGraphics.PresetKeys.IndexOf(name); - PersonalGraphicsSettingV2 presetValue = Settings.Preset_SettingsGraphics.GetPresetFromKey(name); + string name = Settings.PresetSettingsGraphics.GetPresetKey(); + int index = Settings.PresetSettingsGraphics.PresetKeys.IndexOf(name); + PersonalGraphicsSettingV2 presetValue = Settings.PresetSettingsGraphics.GetPresetFromKey(name); if (presetValue != null) { @@ -60,14 +60,14 @@ public int PresetRenderingIndex { if (value < 0) return; - string name = Settings.Preset_SettingsGraphics.PresetKeys[value]; - PersonalGraphicsSettingV2 presetValue = Settings.Preset_SettingsGraphics.GetPresetFromKey(name); + string name = Settings.PresetSettingsGraphics.PresetKeys[value]; + PersonalGraphicsSettingV2 presetValue = Settings.PresetSettingsGraphics.GetPresetFromKey(name); if (presetValue != null) { Settings.SettingsGraphics = presetValue; } - Settings.Preset_SettingsGraphics.SetPresetKey(presetValue); + Settings.PresetSettingsGraphics.SetPresetKey(presetValue); ToggleRenderingSettings(name == PresetConst.DefaultPresetName); UpdatePresetRenderingSettings(); diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.Ext.cs b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.Ext.cs index 909e45ae8..a1e835cfd 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.Ext.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.Ext.cs @@ -193,7 +193,7 @@ public int FPS { get { - int value = Model.FPSIndexDict[NormalizeFPSNumber(Settings.GraphicsSettings.FPS)]; + int value = Model.FpsIndexDict[NormalizeFPSNumber(Settings.GraphicsSettings.FPS)]; if (value != 2) { return value; @@ -213,12 +213,12 @@ public int FPS } else { VSyncToggle.IsEnabled = true; } - Settings.GraphicsSettings.FPS = Model.FPSIndex[value]; + Settings.GraphicsSettings.FPS = Model.FpsIndex[value]; } } // Set it to 60 (default) if the value isn't within Model.FPSIndexDict - private static int NormalizeFPSNumber(int input) => !Model.FPSIndexDict.ContainsKey(input) ? Model.FPSIndex[Model.FPSDefaultIndex] : input; + private static int NormalizeFPSNumber(int input) => !Model.FpsIndexDict.ContainsKey(input) ? Model.FpsIndex[Model.FpsDefaultIndex] : input; //VSync public bool EnableVSync @@ -328,26 +328,26 @@ public bool HalfResTransparent #region Audio public int AudioMasterVolume { - get => Settings.AudioSettings_Master.MasterVol = Settings.AudioSettings_Master.MasterVol; - set => Settings.AudioSettings_Master.MasterVol = value; + get => Settings.AudioSettingsMaster.MasterVol = Settings.AudioSettingsMaster.MasterVol; + set => Settings.AudioSettingsMaster.MasterVol = value; } public int AudioBGMVolume { - get => Settings.AudioSettings_BGM.BGMVol = Settings.AudioSettings_BGM.BGMVol; - set => Settings.AudioSettings_BGM.BGMVol = value; + get => Settings.AudioSettingsBgm.BGMVol = Settings.AudioSettingsBgm.BGMVol; + set => Settings.AudioSettingsBgm.BGMVol = value; } public int AudioSFXVolume { - get => Settings.AudioSettings_SFX.SFXVol = Settings.AudioSettings_SFX.SFXVol; - set => Settings.AudioSettings_SFX.SFXVol = value; + get => Settings.AudioSettingsSfx.SFXVol = Settings.AudioSettingsSfx.SFXVol; + set => Settings.AudioSettingsSfx.SFXVol = value; } public int AudioVOVolume { - get => Settings.AudioSettings_VO.VOVol = Settings.AudioSettings_VO.VOVol; - set => Settings.AudioSettings_VO.VOVol = value; + get => Settings.AudioSettingsVo.VOVol = Settings.AudioSettingsVo.VOVol; + set => Settings.AudioSettingsVo.VOVol = value; } public int AudioLang diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.xaml index 26dafa735..5b8a3cfa5 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.xaml @@ -115,7 +115,7 @@ - - + + + + { - Status.Text = e.status; - if (string.IsNullOrEmpty(e.newver)) + Status.Text = e.Status; + if (string.IsNullOrEmpty(e.Newver)) { return; } - GameVersion version = new GameVersion(e.newver); + GameVersion version = new GameVersion(e.Newver); NewVersionLabel.Text = version.VersionString; }); } diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/DefaultSVGRenderer.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/DefaultSVGRenderer.cs index 615db00a6..bd2f5faf1 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/DefaultSVGRenderer.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/DefaultSVGRenderer.cs @@ -7,6 +7,7 @@ using System; using System.IO; using System.Threading.Tasks; +// ReSharper disable InconsistentNaming namespace CommunityToolkit.Labs.WinUI.Labs.MarkdownTextBlock; @@ -18,7 +19,7 @@ public async Task SvgToImage(string svgString) var image = new Image(); // Create a MemoryStream object and write the SVG string to it using (var memoryStream = new MemoryStream()) - using (var streamWriter = new StreamWriter(memoryStream)) + await using (var streamWriter = new StreamWriter(memoryStream)) { await streamWriter.WriteAsync(svgString); await streamWriter.FlushAsync(); diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/Extensions.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/Extensions.cs index 5ced96e0a..fe5d1bc5b 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/Extensions.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/Extensions.cs @@ -19,6 +19,8 @@ using Windows.Foundation; using Windows.UI; using Windows.UI.ViewManagement; +// ReSharper disable GrammarMistakeInComment +// ReSharper disable UnusedMember.Global namespace CommunityToolkit.Labs.WinUI.Labs.MarkdownTextBlock; diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/ISVGRenderer.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/ISVGRenderer.cs index 76d93ae28..8793b55f7 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/ISVGRenderer.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/ISVGRenderer.cs @@ -4,6 +4,7 @@ using Microsoft.UI.Xaml.Controls; using System.Threading.Tasks; +// ReSharper disable InconsistentNaming namespace CommunityToolkit.Labs.WinUI.Labs.MarkdownTextBlock; diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/MarkdownThemes.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/MarkdownThemes.cs index ed5d674b1..55cacf83d 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/MarkdownThemes.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/MarkdownThemes.cs @@ -8,6 +8,7 @@ using FontWeight = Windows.UI.Text.FontWeight; using FontWeights = Microsoft.UI.Text.FontWeights; // ReSharper disable PartialTypeWithSinglePart +// ReSharper disable UnusedMember.Global namespace CommunityToolkit.WinUI.Controls.MarkdownTextBlockRns; diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/Renderers/ObjectRenderers/Inlines/DelimiterInlineRenderer.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/Renderers/ObjectRenderers/Inlines/DelimiterInlineRenderer.cs index d42c8be0b..06ea4201c 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/Renderers/ObjectRenderers/Inlines/DelimiterInlineRenderer.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/Renderers/ObjectRenderers/Inlines/DelimiterInlineRenderer.cs @@ -5,6 +5,7 @@ using Markdig.Renderers; using Markdig.Syntax.Inlines; using System; +// ReSharper disable GrammarMistakeInComment namespace CommunityToolkit.Labs.WinUI.Labs.MarkdownTextBlock.Renderers.ObjectRenderers.Inlines; @@ -12,10 +13,10 @@ internal class DelimiterInlineRenderer : MarkdownObjectRenderer x.Name == "summary" || + x.Name == "header"); - var header = _htmlNode.ChildNodes - .FirstOrDefault( - x => x.Name == "summary" || - x.Name == "header"); - - InlineUIContainer _inlineUIContainer = new InlineUIContainer(); - Expander _expander = new Expander + InlineUIContainer inlineUIContainer = new InlineUIContainer(); + Expander expander = new Expander { HorizontalAlignment = HorizontalAlignment.Stretch }; @@ -42,16 +40,16 @@ public MyDetails(HtmlNode details) HorizontalAlignment = HorizontalAlignment.Stretch } }; - _expander.Content = _flowDocument.RichTextBlock; + expander.Content = _flowDocument.RichTextBlock; var headerBlock = new TextBlock { Text = header?.InnerText, HorizontalAlignment = HorizontalAlignment.Stretch }; - _expander.Header = headerBlock; - _inlineUIContainer.Child = _expander; + expander.Header = headerBlock; + inlineUIContainer.Child = expander; _paragraph = new Paragraph(); - _paragraph.Inlines.Add(_inlineUIContainer); + _paragraph.Inlines.Add(inlineUIContainer); } public void AddChild(IAddChild child) diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/Html/MyInline.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/Html/MyInline.cs index dbcd88f11..d6dca2f9c 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/Html/MyInline.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/Html/MyInline.cs @@ -22,11 +22,11 @@ public MyInline() { _paragraph = new Paragraph(); _inlineUIContainer = new InlineUIContainer(); - RichTextBlock _richTextBlock = new RichTextBlock(); - _richTextBlock.Blocks.Add(_paragraph); + RichTextBlock richTextBlock = new RichTextBlock(); + richTextBlock.Blocks.Add(_paragraph); - _richTextBlock.HorizontalAlignment = HorizontalAlignment.Stretch; - _inlineUIContainer.Child = _richTextBlock; + richTextBlock.HorizontalAlignment = HorizontalAlignment.Stretch; + _inlineUIContainer.Child = richTextBlock; } public void AddChild(IAddChild child) diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyAutolinkInline.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyAutolinkInline.cs index cdca85915..10a8c1b5a 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyAutolinkInline.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyAutolinkInline.cs @@ -8,17 +8,12 @@ namespace CommunityToolkit.Labs.WinUI.Labs.MarkdownTextBlock.TextElements; -internal class MyAutolinkInline : IAddChild +internal class MyAutolinkInline(AutolinkInline autoLinkInline) : IAddChild { - public TextElement TextElement { get; } - - public MyAutolinkInline(AutolinkInline autoLinkInline) + public TextElement TextElement { get; } = new Hyperlink { - TextElement = new Hyperlink - { - NavigateUri = new Uri(autoLinkInline.Url) - }; - } + NavigateUri = new Uri(autoLinkInline.Url) + }; public void AddChild(IAddChild child) diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyBlockContainer.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyBlockContainer.cs index 3cf9f79b3..e7a4f4188 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyBlockContainer.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyBlockContainer.cs @@ -18,11 +18,11 @@ public TextElement TextElement public MyBlockContainer() { - InlineUIContainer _inlineUIContainer = new InlineUIContainer(); + InlineUIContainer inlineUIContainer = new InlineUIContainer(); _flowDocument = new MyFlowDocument(); - _inlineUIContainer.Child = _flowDocument.RichTextBlock; + inlineUIContainer.Child = _flowDocument.RichTextBlock; _paragraph = new Paragraph(); - _paragraph.Inlines.Add(_inlineUIContainer); + _paragraph.Inlines.Add(inlineUIContainer); } public void AddChild(IAddChild child) diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyCodeBlock.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyCodeBlock.cs index 0f2891922..2ad7485d7 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyCodeBlock.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyCodeBlock.cs @@ -10,6 +10,7 @@ using Microsoft.UI.Xaml.Media; using System.Collections.Generic; using System.Text; +// ReSharper disable CommentTypo namespace CommunityToolkit.Labs.WinUI.Labs.MarkdownTextBlock.TextElements; @@ -24,15 +25,14 @@ public TextElement TextElement public MyCodeBlock(CodeBlock codeBlock, MarkdownConfig config) { - MarkdownConfig _config = config; _paragraph = new Paragraph(); var container = new InlineUIContainer(); var border = new Border { Background = (Brush)Application.Current.Resources["ExpanderHeaderBackground"], - Padding = _config.Themes.Padding, - Margin = _config.Themes.InternalMargin, - CornerRadius = _config.Themes.CornerRadius + Padding = config.Themes.Padding, + Margin = config.Themes.InternalMargin, + CornerRadius = config.Themes.CornerRadius }; var richTextBlock = new RichTextBlock(); diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyEmphasisInline.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyEmphasisInline.cs index 139c02d5a..40d6f95e7 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyEmphasisInline.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyEmphasisInline.cs @@ -12,7 +12,7 @@ namespace CommunityToolkit.Labs.WinUI.Labs.MarkdownTextBlock.TextElements; internal class MyEmphasisInline : IAddChild { - private readonly Span _span; + private readonly Span _span = new(); private bool _isBold; private bool _isItalic; @@ -23,11 +23,6 @@ public TextElement TextElement get => _span; } - public MyEmphasisInline() - { - _span = new Span(); - } - public void AddChild(IAddChild child) { try diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyHeading.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyHeading.cs index 87c581ad5..c473f0c73 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyHeading.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyHeading.cs @@ -7,6 +7,7 @@ using Markdig.Syntax; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Documents; +// ReSharper disable UnusedMember.Global namespace CommunityToolkit.Labs.WinUI.Labs.MarkdownTextBlock.TextElements; @@ -25,27 +26,26 @@ public TextElement TextElement public MyHeading(HeadingBlock headingBlock, MarkdownConfig config) { _paragraph = new Paragraph(); - MarkdownConfig _config = config; var level = headingBlock.Level; _paragraph.FontSize = level switch { - 1 => _config.Themes.H1FontSize, - 2 => _config.Themes.H2FontSize, - 3 => _config.Themes.H3FontSize, - 4 => _config.Themes.H4FontSize, - 5 => _config.Themes.H5FontSize, - _ => _config.Themes.H6FontSize + 1 => config.Themes.H1FontSize, + 2 => config.Themes.H2FontSize, + 3 => config.Themes.H3FontSize, + 4 => config.Themes.H4FontSize, + 5 => config.Themes.H5FontSize, + _ => config.Themes.H6FontSize }; - _paragraph.Foreground = _config.Themes.HeadingForeground; + _paragraph.Foreground = config.Themes.HeadingForeground; _paragraph.FontWeight = level switch { - 1 => _config.Themes.H1FontWeight, - 2 => _config.Themes.H2FontWeight, - 3 => _config.Themes.H3FontWeight, - 4 => _config.Themes.H4FontWeight, - 5 => _config.Themes.H5FontWeight, - _ => _config.Themes.H6FontWeight + 1 => config.Themes.H1FontWeight, + 2 => config.Themes.H2FontWeight, + 3 => config.Themes.H3FontWeight, + 4 => config.Themes.H4FontWeight, + 5 => config.Themes.H5FontWeight, + _ => config.Themes.H6FontWeight }; _paragraph.Margin = new Thickness(0, 8, 0, level switch { @@ -60,7 +60,6 @@ public MyHeading(HtmlNode htmlNode, MarkdownConfig config) { _htmlNode = htmlNode; _paragraph = new Paragraph(); - MarkdownConfig _config = config; var align = _htmlNode?.GetAttributeValue("align", "left"); _paragraph.TextAlignment = align switch @@ -75,22 +74,22 @@ public MyHeading(HtmlNode htmlNode, MarkdownConfig config) var level = int.Parse(htmlNode.Name.Substring(1)); _paragraph.FontSize = level switch { - 1 => _config.Themes.H1FontSize, - 2 => _config.Themes.H2FontSize, - 3 => _config.Themes.H3FontSize, - 4 => _config.Themes.H4FontSize, - 5 => _config.Themes.H5FontSize, - _ => _config.Themes.H6FontSize + 1 => config.Themes.H1FontSize, + 2 => config.Themes.H2FontSize, + 3 => config.Themes.H3FontSize, + 4 => config.Themes.H4FontSize, + 5 => config.Themes.H5FontSize, + _ => config.Themes.H6FontSize }; - _paragraph.Foreground = _config.Themes.HeadingForeground; + _paragraph.Foreground = config.Themes.HeadingForeground; _paragraph.FontWeight = level switch { - 1 => _config.Themes.H1FontWeight, - 2 => _config.Themes.H2FontWeight, - 3 => _config.Themes.H3FontWeight, - 4 => _config.Themes.H4FontWeight, - 5 => _config.Themes.H5FontWeight, - _ => _config.Themes.H6FontWeight + 1 => config.Themes.H1FontWeight, + 2 => config.Themes.H2FontWeight, + 3 => config.Themes.H3FontWeight, + 4 => config.Themes.H4FontWeight, + 5 => config.Themes.H5FontWeight, + _ => config.Themes.H6FontWeight }; } diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyImage.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyImage.cs index 29ffe8e9c..6de75e041 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyImage.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyImage.cs @@ -41,7 +41,7 @@ public MyImage(LinkInline linkInline, Uri uri, MarkdownConfig config) { _uri = uri; _imageProvider = config.ImageProvider; - _svgRenderer = config.SVGRenderer == null ? new DefaultSVGRenderer() : config.SVGRenderer; + _svgRenderer = config.SVGRenderer ?? new DefaultSVGRenderer(); Init(); var size = Extensions.GetMarkdownImageSize(linkInline); if (size.Width != 0) @@ -58,7 +58,7 @@ public MyImage(HtmlNode htmlNode, MarkdownConfig? config) { Uri.TryCreate(htmlNode.GetAttributeValue("src", "#"), UriKind.RelativeOrAbsolute, out _uri); _imageProvider = config?.ImageProvider; - _svgRenderer = config?.SVGRenderer == null ? new DefaultSVGRenderer() : config.SVGRenderer; + _svgRenderer = config?.SVGRenderer ?? new DefaultSVGRenderer(); Init(); int.TryParse( htmlNode.GetAttributeValue("width", "0"), diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyInlineCode.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyInlineCode.cs index b0e4d9f94..4da910b6a 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyInlineCode.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyInlineCode.cs @@ -22,16 +22,15 @@ public TextElement TextElement public MyInlineCode(CodeInline codeInline, MarkdownConfig config) { - MarkdownConfig _config = config; _inlineContainer = new InlineUIContainer(); var border = new Border { VerticalAlignment = VerticalAlignment.Bottom, - Background = _config.Themes.InlineCodeBackground, + Background = config.Themes.InlineCodeBackground, // border.BorderBrush = _config.Themes.InlineCodeBorderBrush; // border.BorderThickness = _config.Themes.InlineCodeBorderThickness; - CornerRadius = _config.Themes.InlineCodeCornerRadius, - Padding = _config.Themes.InlineCodePadding + CornerRadius = config.Themes.InlineCodeCornerRadius, + Padding = config.Themes.InlineCodePadding }; CompositeTransform3D transform = new CompositeTransform3D { @@ -40,9 +39,9 @@ public MyInlineCode(CodeInline codeInline, MarkdownConfig config) border.Transform3D = transform; var textBlock = new TextBlock { - FontFamily = _config.Themes.InlineCodeFontFamily, - FontSize = _config.Themes.InlineCodeFontSize, - FontWeight = _config.Themes.InlineCodeFontWeight, + FontFamily = config.Themes.InlineCodeFontFamily, + FontSize = config.Themes.InlineCodeFontSize, + FontWeight = config.Themes.InlineCodeFontWeight, Text = codeInline.Content }; border.Child = textBlock; diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyInlineText.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyInlineText.cs index 4baa7b194..6119923b6 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyInlineText.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyInlineText.cs @@ -6,22 +6,17 @@ namespace CommunityToolkit.Labs.WinUI.Labs.MarkdownTextBlock.TextElements; -internal class MyInlineText : IAddChild +internal class MyInlineText(string text) : IAddChild { - private readonly Run _run; + private readonly Run _run = new() + { + Text = text + }; public TextElement TextElement { get => _run; } - public MyInlineText(string text) - { - _run = new Run - { - Text = text - }; - } - public void AddChild(IAddChild child) { } } diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyLineBreak.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyLineBreak.cs index d33e3ca2c..8a627c919 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyLineBreak.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyLineBreak.cs @@ -8,17 +8,12 @@ namespace CommunityToolkit.Labs.WinUI.Labs.MarkdownTextBlock.TextElements; internal class MyLineBreak : IAddChild { - private readonly LineBreak _lineBreak; + private readonly LineBreak _lineBreak = new(); public TextElement TextElement { get => _lineBreak; } - public MyLineBreak() - { - _lineBreak = new LineBreak(); - } - public void AddChild(IAddChild child) { } } diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyParagraph.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyParagraph.cs index 0525df184..3b64c1640 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyParagraph.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyParagraph.cs @@ -10,18 +10,13 @@ namespace CommunityToolkit.Labs.WinUI.Labs.MarkdownTextBlock.TextElements; internal class MyParagraph : IAddChild { - private readonly Paragraph _paragraph; + private readonly Paragraph _paragraph = new(); public TextElement TextElement { get => _paragraph; } - public MyParagraph() - { - _paragraph = new Paragraph(); - } - public void AddChild(IAddChild child) { if (child.TextElement is Inline inlineChild) diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyTableCell.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyTableCell.cs index c9e01974d..7de24bcbc 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyTableCell.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/TextElements/MyTableCell.cs @@ -15,19 +15,13 @@ internal class MyTableCell : IAddChild private readonly TableCell _tableCell; private readonly Paragraph _paragraph = new(); private readonly MyFlowDocument _flowDocument; - private readonly int _columnIndex; - private readonly int _rowIndex; - private readonly Grid _container; public TextElement TextElement { get => _paragraph; } - public Grid Container - { - get => _container; - } + public Grid Container { get; } public int ColumnSpan { @@ -39,22 +33,16 @@ public int RowSpan get => _tableCell.RowSpan; } - public int ColumnIndex - { - get => _columnIndex; - } + public int ColumnIndex { get; } - public int RowIndex - { - get => _rowIndex; - } + public int RowIndex { get; } public MyTableCell(TableCell tableCell, TextAlignment textAlignment, bool isHeader, int columnIndex, int rowIndex) { _tableCell = tableCell; - _columnIndex = columnIndex; - _rowIndex = rowIndex; - _container = new Grid(); + ColumnIndex = columnIndex; + RowIndex = rowIndex; + Container = new Grid(); _flowDocument = new MyFlowDocument { @@ -73,7 +61,7 @@ public MyTableCell(TableCell tableCell, TextAlignment textAlignment, bool isHead } }; - _container.Padding = new Thickness(4); + Container.Padding = new Thickness(4); if (isHeader) { _flowDocument.RichTextBlock.FontWeight = FontWeights.Bold; @@ -85,7 +73,7 @@ public MyTableCell(TableCell tableCell, TextAlignment textAlignment, bool isHead TextAlignment.Right => HorizontalAlignment.Right, _ => HorizontalAlignment.Left }; - _container.Children.Add(_flowDocument.RichTextBlock); + Container.Children.Add(_flowDocument.RichTextBlock); } public void AddChild(IAddChild child) diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/CompressedTextBlock.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/CompressedTextBlock.cs index 32658f1e5..d2096706a 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/CompressedTextBlock.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/CompressedTextBlock.cs @@ -24,15 +24,15 @@ class CompressedText public int Spacing; } - class CompressedContext + private class CompressedContext { public bool BeginOffset; public readonly List Texts = []; } - private readonly CompressedContext Context = new(); - private readonly TextBlock MeasureTextBlock = new(); - private readonly TextBlock ContentTextBlock = new(); + private readonly CompressedContext _context = new(); + private readonly TextBlock _measureTextBlock = new(); + private readonly TextBlock _contentTextBlock = new(); #endregion #region Properties @@ -91,13 +91,13 @@ public double FontSize private static void OnForegroundChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { var compressedTextBlock = (CompressedTextBlock)sender; - compressedTextBlock.ContentTextBlock.Foreground = (Brush)e.NewValue; + compressedTextBlock._contentTextBlock.Foreground = (Brush)e.NewValue; } private static void OnTextTrimmingChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { var compressedTextBlock = (CompressedTextBlock)sender; - compressedTextBlock.ContentTextBlock.TextTrimming = (TextTrimming)e.NewValue; + compressedTextBlock._contentTextBlock.TextTrimming = (TextTrimming)e.NewValue; } private static void OnTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) @@ -115,26 +115,26 @@ private static void OnFontSizeChanged(DependencyObject sender, DependencyPropert public CompressedTextBlock() { - Children.Add(ContentTextBlock); + Children.Add(_contentTextBlock); } private bool CheckCompressMark(string marks, char ch) { var contains = marks.Contains(ch); if (!contains) return false; - MeasureTextBlock.Text = "" + ch; - MeasureTextBlock.Measure(new Size(float.MaxValue, float.MaxValue)); - return MeasureTextBlock.DesiredSize.Height <= MeasureTextBlock.DesiredSize.Width * 1.5; + _measureTextBlock.Text = "" + ch; + _measureTextBlock.Measure(new Size(float.MaxValue, float.MaxValue)); + return _measureTextBlock.DesiredSize.Height <= _measureTextBlock.DesiredSize.Width * 1.5; } private void UpdateFontSize(double value) { - ContentTextBlock.FontSize = value; + _contentTextBlock.FontSize = value; } private void InvalidateText() { - ContentTextBlock.Inlines.Clear(); + _contentTextBlock.Inlines.Clear(); if (Text.Length <= 0) return; var lastChar = Text[0]; @@ -142,8 +142,8 @@ private void InvalidateText() var isLastCloseMark = CheckCompressMark(CloseMarks, lastChar); var compressedText = new CompressedText(); - Context.BeginOffset = isLastOpenMark; - Context.Texts.Clear(); + _context.BeginOffset = isLastOpenMark; + _context.Texts.Clear(); for (var i = 1; i < Text.Length; i++) { @@ -159,7 +159,7 @@ private void InvalidateText() if (compressedText.Text.Length > 0 && compressedText.Spacing != spacing) { - Context.Texts.Add(compressedText); + _context.Texts.Add(compressedText); compressedText = new CompressedText(); } @@ -173,23 +173,23 @@ private void InvalidateText() if (compressedText.Spacing != 0) { - Context.Texts.Add(compressedText); + _context.Texts.Add(compressedText); compressedText = new CompressedText(); } compressedText.Text += lastChar; - Context.Texts.Add(compressedText); + _context.Texts.Add(compressedText); - foreach (var text in Context.Texts) + foreach (var text in _context.Texts) { - ContentTextBlock.Inlines.Add(new Run + _contentTextBlock.Inlines.Add(new Run { Text = text.Text, CharacterSpacing = text.Spacing }); } - ContentTextBlock.Translation = Context.BeginOffset ? new Vector3(1 - (float)ContentTextBlock.FontSize / 2, 0, 0) : new Vector3(); + _contentTextBlock.Translation = _context.BeginOffset ? new Vector3(1 - (float)_contentTextBlock.FontSize / 2, 0, 0) : new Vector3(); } } } diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/ContentDialogOverlay.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/ContentDialogOverlay.cs index 15eb4cfed..757506a73 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/ContentDialogOverlay.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/ContentDialogOverlay.cs @@ -26,12 +26,12 @@ public ContentDialogOverlay(ContentDialogTheme theme = ContentDialogTheme.Warnin _ => UIElementExtensions.GetApplicationResource("SystemFillColorAttentionBrush") }; - if (brushObj is not null and SolidColorBrush brush) + if (brushObj is SolidColorBrush brush) { NColor titleColor = brush.Color; titleColor.A = 255; - if (UIElementExtensions.GetApplicationResource("DialogTitleBrush") is not null and SolidColorBrush brushTitle) + if (UIElementExtensions.GetApplicationResource("DialogTitleBrush") is SolidColorBrush brushTitle) brushTitle.Color = titleColor; } diff --git a/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs b/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs index 5bda5e816..9632f80fb 100644 --- a/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs +++ b/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs @@ -34,65 +34,65 @@ namespace CollapseLauncher; public partial class Updater : IDisposable { // ReSharper disable PrivateFieldCanBeConvertedToLocalVariable - private readonly string ChannelURL; - private readonly string ChannelName; - private Stopwatch UpdateStopwatch; - private readonly UpdateManager UpdateManager; - private readonly IFileDownloader UpdateDownloader; + private readonly string _channelURL; + private readonly string _channelName; + private Stopwatch _updateStopwatch; + private readonly UpdateManager _updateManager; + private readonly IFileDownloader _updateDownloader; #if USEVELOPACK - private readonly ILogger UpdateManagerLogger; - private VelopackAsset VelopackVersionToUpdate; + private readonly ILogger _updateManagerLogger; + private VelopackAsset _velopackVersionToUpdate; #endif - private GameVersion NewVersionTag; + private GameVersion _newVersionTag; // ReSharper disable once RedundantDefaultMemberInitializer - private bool IsUseLegacyDownload = false; + private bool _isUseLegacyDownload = false; // ReSharper restore PrivateFieldCanBeConvertedToLocalVariable - private static readonly string execPath = Process.GetCurrentProcess().MainModule!.FileName; - private static readonly string workingDir = Path.GetDirectoryName(execPath); - private static readonly string applyElevatedPath = Path.Combine(workingDir, "..\\", "ApplyUpdate.exe"); + private static readonly string ExecPath = AppExecutablePath; + private static readonly string WorkingDir = Path.GetDirectoryName(ExecPath); + private static readonly string ApplyElevatedPath = Path.Combine(WorkingDir, "..\\", "ApplyUpdate.exe"); public event EventHandler UpdaterStatusChanged; public event EventHandler UpdaterProgressChanged; - private readonly UpdaterStatus Status; - private UpdaterProgress Progress; + private readonly UpdaterStatus _status; + private UpdaterProgress _progress; public Updater(string channelName) { - ChannelName = channelName; - ChannelURL = CombineURLFromString(FallbackCDNUtil.GetPreferredCDN().URLPrefix, + _channelName = channelName; + _channelURL = CombineURLFromString(FallbackCDNUtil.GetPreferredCDN().URLPrefix, #if USEVELOPACK "velopack", #else "squirrel", #endif - ChannelName + _channelName ); - UpdateDownloader = new UpdateManagerHttpAdapter(); + _updateDownloader = new UpdateManagerHttpAdapter(); #if USEVELOPACK - UpdateManagerLogger = ILoggerHelper.GetILogger(); - VelopackLocator updateManagerLocator = VelopackLocator.GetDefault(UpdateManagerLogger); + _updateManagerLogger = ILoggerHelper.GetILogger(); + VelopackLocator updateManagerLocator = VelopackLocator.GetDefault(_updateManagerLogger); UpdateOptions updateManagerOptions = new UpdateOptions { AllowVersionDowngrade = true, - ExplicitChannel = ChannelName + ExplicitChannel = _channelName }; // Initialize update manager source - IUpdateSource updateSource = new SimpleWebSource(ChannelURL, UpdateDownloader); - UpdateManager = new UpdateManager( + IUpdateSource updateSource = new SimpleWebSource(_channelURL, _updateDownloader); + _updateManager = new UpdateManager( updateSource, updateManagerOptions, - UpdateManagerLogger, + _updateManagerLogger, updateManagerLocator); #else UpdateManager = new UpdateManager(ChannelURL, null, null, UpdateDownloader); #endif - UpdateStopwatch = Stopwatch.StartNew(); - Status = new UpdaterStatus(); - Progress = new UpdaterProgress(UpdateStopwatch, 0, 100); + _updateStopwatch = Stopwatch.StartNew(); + _status = new UpdaterStatus(); + _progress = new UpdaterProgress(_updateStopwatch, 0, 100); } ~Updater() @@ -112,7 +112,7 @@ public async Task StartCheck() #if !USEVELOPACK return await UpdateManager.CheckForUpdate(); #else - return await UpdateManager.CheckForUpdatesAsync(); + return await _updateManager.CheckForUpdatesAsync(); #endif } @@ -120,10 +120,10 @@ public async Task StartUpdate(UpdateInfo updateInfo, CancellationToken tok { if (token.IsCancellationRequested) return false; - Status.status = string.Format(Lang._UpdatePage.UpdateStatus3, 1, 1); + _status.Status = string.Format(Lang._UpdatePage.UpdateStatus3, 1, 1); UpdateStatus(); UpdateProgress(); - UpdateStopwatch = Stopwatch.StartNew(); + _updateStopwatch = Stopwatch.StartNew(); try { @@ -147,14 +147,14 @@ public async Task StartUpdate(UpdateInfo updateInfo, CancellationToken tok } NewVersionTag = new GameVersion(updateInfo.ReleasesToApply.FirstOrDefault()!.Version.Version); #else - NewVersionTag = new GameVersion(updateInfo.TargetFullRelease.Version.ToString()); - if (IsCurrentHasLatestVersion(NewVersionTag.VersionString)) + _newVersionTag = new GameVersion(updateInfo.TargetFullRelease.Version.ToString()); + if (IsCurrentHasLatestVersion(_newVersionTag.VersionString)) { - Status.status = string.Format(Lang._UpdatePage.UpdateStatus4, LauncherUpdateHelper.LauncherCurrentVersionString); - Status.message = Lang._UpdatePage.UpdateMessage4; + _status.Status = string.Format(Lang._UpdatePage.UpdateStatus4, LauncherUpdateHelper.LauncherCurrentVersionString); + _status.Message = Lang._UpdatePage.UpdateMessage4; UpdateStatus(); - await Task.Delay(3000); + await Task.Delay(3000, token); return false; } #endif @@ -164,15 +164,15 @@ public async Task StartUpdate(UpdateInfo updateInfo, CancellationToken tok await UpdateManager.ApplyReleases(updateInfo, InvokeApplyUpdateProgress); #else - await UpdateManager.DownloadUpdatesAsync(updateInfo, InvokeDownloadUpdateProgress, false, token); - VelopackVersionToUpdate = updateInfo.TargetFullRelease; + await _updateManager.DownloadUpdatesAsync(updateInfo, InvokeDownloadUpdateProgress, false, token); + _velopackVersionToUpdate = updateInfo.TargetFullRelease; await EnsureVelopackUpdateExec(token); #endif void InvokeDownloadUpdateProgress(int progress) { - Progress = new UpdaterProgress(UpdateStopwatch, progress + _progress = new UpdaterProgress(_updateStopwatch, progress #if !USEVELOPACK / 2 #endif @@ -192,7 +192,7 @@ void InvokeApplyUpdateProgress(int progress) { Logger.LogWriteLine($"Failed while running update via Squirrel. Fallback to legacy method...\r\n{ex}", LogType.Error, true); - IsUseLegacyDownload = true; + _isUseLegacyDownload = true; await StartLegacyUpdate(); } @@ -221,7 +221,7 @@ private async Task EnsureVelopackUpdateExec(CancellationToken token) bool IsFileVersionValid(FileInfo fileInfo) { - const string VelopackDesc = "Velopack"; + const string velopackDesc = "Velopack"; if (!fileInfo.Exists || fileInfo.Length == 0) return false; @@ -229,7 +229,7 @@ bool IsFileVersionValid(FileInfo fileInfo) try { FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(fileInfo.FullName); - bool isVelopack = fileVersionInfo.FileDescription?.StartsWith(VelopackDesc, StringComparison.OrdinalIgnoreCase) ?? false; + bool isVelopack = fileVersionInfo.FileDescription?.StartsWith(velopackDesc, StringComparison.OrdinalIgnoreCase) ?? false; return isVelopack; } @@ -253,34 +253,34 @@ private async Task StartLegacyUpdate() DownloadClient downloadClient = DownloadClient.CreateInstance(client); - UpdateStopwatch = Stopwatch.StartNew(); + _updateStopwatch = Stopwatch.StartNew(); CDNURLProperty preferredCdn = FallbackCDNUtil.GetPreferredCDN(); - string updateFileIndexUrl = CombineURLFromString(preferredCdn.URLPrefix, ChannelName.ToLower(), "fileindex.json"); + string updateFileIndexUrl = CombineURLFromString(preferredCdn.URLPrefix, _channelName.ToLower(), "fileindex.json"); AppUpdateVersionProp updateInfo = await FallbackCDNUtil.DownloadAsJSONType(updateFileIndexUrl, AppUpdateVersionPropJsonContext.Default.AppUpdateVersionProp, default)!; GameVersion? gameVersion = updateInfo!.Version; - if (gameVersion.HasValue) NewVersionTag = gameVersion.Value!; + if (gameVersion.HasValue) _newVersionTag = gameVersion.Value!; UpdateStatus(); UpdateProgress(); FallbackCDNUtil.DownloadProgress += FallbackCDNUtil_DownloadProgress; - await FallbackCDNUtil.DownloadCDNFallbackContent(downloadClient, applyElevatedPath, + await FallbackCDNUtil.DownloadCDNFallbackContent(downloadClient, ApplyElevatedPath, Environment.ProcessorCount > 8 ? 8 : Environment.ProcessorCount, - $"{ChannelName.ToLower()}/ApplyUpdate.exe", default); + $"{_channelName.ToLower()}/ApplyUpdate.exe", default); FallbackCDNUtil.DownloadProgress -= FallbackCDNUtil_DownloadProgress; - await File.WriteAllTextAsync(Path.Combine(workingDir, "..\\", "release"), ChannelName.ToLower()); + await File.WriteAllTextAsync(Path.Combine(WorkingDir, "..\\", "release"), _channelName.ToLower()); } private void FallbackCDNUtil_DownloadProgress(object sender, DownloadEvent e) { - Progress = new UpdaterProgress(UpdateStopwatch, (int)e.ProgressPercentage, 100); + _progress = new UpdaterProgress(_updateStopwatch, (int)e.ProgressPercentage, 100); UpdateProgress(); } @@ -326,17 +326,17 @@ public async Task FinishUpdate(bool noSuicide = false) var needInnoLogUpdatePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "AppData", "LocalLow", "CollapseLauncher", "_NeedInnoLogUpdate"); - Status.status = string.Format(Lang._UpdatePage.UpdateStatus5 + $" {Lang._UpdatePage.UpdateMessage5}", - NewVersionTag.VersionString); + _status.Status = string.Format(Lang._UpdatePage.UpdateStatus5 + $" {Lang._UpdatePage.UpdateMessage5}", + _newVersionTag.VersionString); UpdateStatus(); - Progress = new UpdaterProgress(UpdateStopwatch, 100, 100); + _progress = new UpdaterProgress(_updateStopwatch, 100, 100); UpdateProgress(); - await File.WriteAllTextAsync(newVerTagPath, NewVersionTag.VersionString); - await File.WriteAllTextAsync(needInnoLogUpdatePath, NewVersionTag.VersionString); + await File.WriteAllTextAsync(newVerTagPath, _newVersionTag.VersionString); + await File.WriteAllTextAsync(needInnoLogUpdatePath, _newVersionTag.VersionString); - if (IsUseLegacyDownload) + if (_isUseLegacyDownload) { SuicideLegacy(); return; @@ -352,7 +352,7 @@ private async Task Suicide() #if !USEVELOPACK UpdateManager.RestartApp(); #else - if (VelopackVersionToUpdate != null) + if (_velopackVersionToUpdate != null) { try { @@ -360,7 +360,7 @@ private async Task Suicide() if (!Directory.Exists(currentAppPath)) Directory.CreateDirectory(currentAppPath); - UpdateManager.ApplyUpdatesAndRestart(VelopackVersionToUpdate); + _updateManager.ApplyUpdatesAndRestart(_velopackVersionToUpdate); } catch (Exception ex) { @@ -378,7 +378,7 @@ private static void SuicideLegacy() { StartInfo = new ProcessStartInfo { - FileName = applyElevatedPath, + FileName = ApplyElevatedPath, UseShellExecute = true } }; @@ -388,23 +388,23 @@ private static void SuicideLegacy() public void UpdateStatus() { - UpdaterStatusChanged?.Invoke(this, Status); + UpdaterStatusChanged?.Invoke(this, _status); } public void UpdateProgress() { - UpdaterProgressChanged?.Invoke(this, Progress); + UpdaterProgressChanged?.Invoke(this, _progress); } public class UpdaterStatus { - public string status { get; set; } + public string Status { get; set; } // ReSharper disable once UnusedAutoPropertyAccessor.Global - public string message { get; set; } + public string Message { get; set; } // ReSharper disable once UnusedAutoPropertyAccessor.Global - public string newver { get; set; } + public string Newver { get; set; } } public class UpdaterProgress diff --git a/CollapseLauncher/XAMLs/Updater/UpdaterWindow.xaml.cs b/CollapseLauncher/XAMLs/Updater/UpdaterWindow.xaml.cs index 47d1c2a9b..457bf419a 100644 --- a/CollapseLauncher/XAMLs/Updater/UpdaterWindow.xaml.cs +++ b/CollapseLauncher/XAMLs/Updater/UpdaterWindow.xaml.cs @@ -17,6 +17,7 @@ using static Hi3Helper.Logger; using static Hi3Helper.Shared.Region.LauncherConfig; // ReSharper disable StringLiteralTypo +// ReSharper disable UnusedMember.Global namespace CollapseLauncher; From fb4a6186ce9ecea1799dbe07d5b240ba6395907f Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sat, 1 Feb 2025 16:10:41 +0700 Subject: [PATCH 15/31] Make SonarQube happy :) --- .../Extension/TaskExtensions.TaskAwaitable.cs | 87 +++++++++++++++++++ .../Classes/Extension/TaskExtensions.cs | 77 +--------------- 2 files changed, 88 insertions(+), 76 deletions(-) create mode 100644 CollapseLauncher/Classes/Extension/TaskExtensions.TaskAwaitable.cs diff --git a/CollapseLauncher/Classes/Extension/TaskExtensions.TaskAwaitable.cs b/CollapseLauncher/Classes/Extension/TaskExtensions.TaskAwaitable.cs new file mode 100644 index 000000000..f2d61f5ee --- /dev/null +++ b/CollapseLauncher/Classes/Extension/TaskExtensions.TaskAwaitable.cs @@ -0,0 +1,87 @@ +using Hi3Helper; +using System; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +// ReSharper disable UnusedMember.Global + +#nullable enable +namespace CollapseLauncher.Extension +{ + public delegate ConfiguredTaskAwaitable ActionTimeoutTaskAwaitableCallback(CancellationToken token); + internal static partial class TaskExtensions + { + internal static Task + WaitForRetryAsync(this ActionTimeoutTaskAwaitableCallback funcCallback, + int? timeout = null, + int? timeoutStep = null, + int? retryAttempt = null, + ActionOnTimeOutRetry? actionOnRetry = null, + CancellationToken fromToken = default) + => WaitForRetryAsync(() => funcCallback, timeout, timeoutStep, retryAttempt, actionOnRetry, fromToken); + + internal static async Task + WaitForRetryAsync(Func> funcCallback, + int? timeout = null, + int? timeoutStep = null, + int? retryAttempt = null, + ActionOnTimeOutRetry? actionOnRetry = null, + CancellationToken fromToken = default) + { + timeout ??= DefaultTimeoutSec; + timeoutStep ??= 0; + retryAttempt ??= DefaultRetryAttempt; + + int retryAttemptCurrent = 1; + Exception? lastException = null; + while (retryAttemptCurrent < retryAttempt) + { + fromToken.ThrowIfCancellationRequested(); + CancellationTokenSource? innerCancellationToken = null; + CancellationTokenSource? consolidatedToken = null; + + try + { + innerCancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(timeout ?? DefaultTimeoutSec)); + consolidatedToken = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationToken.Token, fromToken); + + ActionTimeoutTaskAwaitableCallback delegateCallback = funcCallback(); + return await delegateCallback(consolidatedToken.Token); + } + catch (OperationCanceledException) when (fromToken.IsCancellationRequested) { throw; } + catch (Exception ex) + { + lastException = ex; + actionOnRetry?.Invoke(retryAttemptCurrent, (int)retryAttempt, timeout ?? 0, timeoutStep ?? 0); + + if (ex is TimeoutException) + { + string msg = $"The operation has timed out! Retrying attempt left: {retryAttemptCurrent}/{retryAttempt}"; + Logger.LogWriteLine(msg, LogType.Warning, true); + } + else + { + string msg = $"The operation has thrown an exception! Retrying attempt left: {retryAttemptCurrent}/{retryAttempt}\r\n{ex}"; + Logger.LogWriteLine(msg, LogType.Error, true); + } + + retryAttemptCurrent++; + timeout += timeoutStep; + } + finally + { + innerCancellationToken?.Dispose(); + consolidatedToken?.Dispose(); + } + } + + if (lastException is not null + && !fromToken.IsCancellationRequested) + throw lastException is TaskCanceledException ? + new TimeoutException("The operation has timed out with inner exception!", lastException) : + lastException; + + throw new TimeoutException("The operation has timed out!"); + } + } +} diff --git a/CollapseLauncher/Classes/Extension/TaskExtensions.cs b/CollapseLauncher/Classes/Extension/TaskExtensions.cs index 4b25f25f0..d96abc8c3 100644 --- a/CollapseLauncher/Classes/Extension/TaskExtensions.cs +++ b/CollapseLauncher/Classes/Extension/TaskExtensions.cs @@ -1,6 +1,5 @@ using Hi3Helper; using System; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; // ReSharper disable UnusedMember.Global @@ -9,86 +8,12 @@ namespace CollapseLauncher.Extension { public delegate Task ActionTimeoutTaskCallback(CancellationToken token); - public delegate ConfiguredTaskAwaitable ActionTimeoutTaskAwaitableCallback(CancellationToken token); public delegate void ActionOnTimeOutRetry(int retryAttemptCount, int retryAttemptTotal, int timeOutSecond, int timeOutStep); - internal static class TaskExtensions + internal static partial class TaskExtensions { internal const int DefaultTimeoutSec = 10; internal const int DefaultRetryAttempt = 5; - internal static Task - WaitForRetryAsync(this ActionTimeoutTaskAwaitableCallback funcCallback, - int? timeout = null, - int? timeoutStep = null, - int? retryAttempt = null, - ActionOnTimeOutRetry? actionOnRetry = null, - CancellationToken fromToken = default) - => WaitForRetryAsync(() => funcCallback, timeout, timeoutStep, retryAttempt, actionOnRetry, fromToken); - - internal static async Task - WaitForRetryAsync(Func> funcCallback, - int? timeout = null, - int? timeoutStep = null, - int? retryAttempt = null, - ActionOnTimeOutRetry? actionOnRetry = null, - CancellationToken fromToken = default) - { - timeout ??= DefaultTimeoutSec; - timeoutStep ??= 0; - retryAttempt ??= DefaultRetryAttempt; - - int retryAttemptCurrent = 1; - Exception? lastException = null; - while (retryAttemptCurrent < retryAttempt) - { - fromToken.ThrowIfCancellationRequested(); - CancellationTokenSource? innerCancellationToken = null; - CancellationTokenSource? consolidatedToken = null; - - try - { - innerCancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(timeout ?? DefaultTimeoutSec)); - consolidatedToken = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationToken.Token, fromToken); - - ActionTimeoutTaskAwaitableCallback delegateCallback = funcCallback(); - return await delegateCallback(consolidatedToken.Token); - } - catch (OperationCanceledException) when (fromToken.IsCancellationRequested) { throw; } - catch (Exception ex) - { - lastException = ex; - actionOnRetry?.Invoke(retryAttemptCurrent, (int)retryAttempt, timeout ?? 0, timeoutStep ?? 0); - - if (ex is TimeoutException) - { - string msg = $"The operation has timed out! Retrying attempt left: {retryAttemptCurrent}/{retryAttempt}"; - Logger.LogWriteLine(msg, LogType.Warning, true); - } - else - { - string msg = $"The operation has thrown an exception! Retrying attempt left: {retryAttemptCurrent}/{retryAttempt}\r\n{ex}"; - Logger.LogWriteLine(msg, LogType.Error, true); - } - - retryAttemptCurrent++; - timeout += timeoutStep; - } - finally - { - innerCancellationToken?.Dispose(); - consolidatedToken?.Dispose(); - } - } - - if (lastException is not null - && !fromToken.IsCancellationRequested) - throw lastException is TaskCanceledException ? - new TimeoutException("The operation has timed out with inner exception!", lastException) : - lastException; - - throw new TimeoutException("The operation has timed out!"); - } - internal static async Task WaitForRetryAsync(this ActionTimeoutTaskCallback funcCallback, int? timeout = null, From 72084d99f918e9215aa59b6c621c792c18835520 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sat, 1 Feb 2025 16:45:10 +0700 Subject: [PATCH 16/31] Adjust event icon size --- .../WindowSizeProp/WindowSizeProp.cs | 84 +++++++++---------- .../XAMLs/MainApp/Pages/HomePage.Variable.cs | 6 +- .../XAMLs/MainApp/Pages/HomePage.xaml | 2 +- 3 files changed, 46 insertions(+), 46 deletions(-) diff --git a/CollapseLauncher/Classes/Properties/WindowSizeProp/WindowSizeProp.cs b/CollapseLauncher/Classes/Properties/WindowSizeProp/WindowSizeProp.cs index 86b4ae849..108c5934d 100644 --- a/CollapseLauncher/Classes/Properties/WindowSizeProp/WindowSizeProp.cs +++ b/CollapseLauncher/Classes/Properties/WindowSizeProp/WindowSizeProp.cs @@ -27,52 +27,52 @@ Dictionary WindowSizeProfiles "Normal", new WindowSizeProp { - WindowBounds = new Size(1280, 720), - PostEventPanelScaleFactor = 1.35f, - SidePanel1Width = new GridLength(340, GridUnitType.Pixel), - EventPostCarouselBounds = new Size(340, 158), - PostPanelBounds = new Size(340, 84), - PostPanelBottomMargin = new Thickness(0, 0, 0, 20), - PostPanelPaimonHeight = 110, - PostPanelPaimonMargin = new Thickness(0, -48, -56, 0), - PostPanelPaimonInnerMargin = new Thickness(0, 0, 0, 0), - PostPanelPaimonTextMargin = new Thickness(0, 0, 0, 18), - PostPanelPaimonTextSize = 11, - BannerIconWidth = 136, - BannerIconWidthHYP = 111, - BannerIconMargin = new Thickness(0, 0, 94, 84), - BannerIconMarginHYP = new Thickness(48, 252, 0, 0), - BannerIconAlignHorizontal = HorizontalAlignment.Right, - BannerIconAlignHorizontalHYP = HorizontalAlignment.Left, - BannerIconAlignVertical = VerticalAlignment.Bottom, - BannerIconAlignVerticalHYP = VerticalAlignment.Top, - SettingsPanelWidth = 676 + WindowBounds = new Size(1280, 720), + PostEventPanelScaleFactor = 1.35f, + SidePanel1Width = new GridLength(340, GridUnitType.Pixel), + EventPostCarouselBounds = new Size(340, 158), + PostPanelBounds = new Size(340, 84), + PostPanelBottomMargin = new Thickness(0, 0, 0, 20), + PostPanelPaimonHeight = 110, + PostPanelPaimonMargin = new Thickness(0, -48, -56, 0), + PostPanelPaimonInnerMargin = new Thickness(0, 0, 0, 0), + PostPanelPaimonTextMargin = new Thickness(0, 0, 0, 18), + PostPanelPaimonTextSize = 11, + BannerIconHeight = 40, + BannerIconHeightHYP = 40, + BannerIconMargin = new Thickness(0, 0, 94, 84), + BannerIconMarginHYP = new Thickness(48, 244, 0, 0), + BannerIconAlignHorizontal = HorizontalAlignment.Right, + BannerIconAlignHorizontalHYP = HorizontalAlignment.Left, + BannerIconAlignVertical = VerticalAlignment.Bottom, + BannerIconAlignVerticalHYP = VerticalAlignment.Top, + SettingsPanelWidth = 676 } }, { "Small", new WindowSizeProp { - WindowBounds = new Size(1024, 576), - PostEventPanelScaleFactor = 1.25f, - SidePanel1Width = new GridLength(280, GridUnitType.Pixel), - EventPostCarouselBounds = new Size(280, 130), - PostPanelBounds = new Size(280, 82), - PostPanelBottomMargin = new Thickness(0, 0, 0, 12), - PostPanelPaimonHeight = 110, - PostPanelPaimonMargin = new Thickness(0, -48, -56, 0), - PostPanelPaimonInnerMargin = new Thickness(0, 0, 0, 0), - PostPanelPaimonTextMargin = new Thickness(0, 0, 0, 18), - PostPanelPaimonTextSize = 11, - BannerIconWidth = 100, - BannerIconWidthHYP = 86, - BannerIconMargin = new Thickness(0, 0, 70, 52), - BannerIconMarginHYP = new Thickness(22, 186, 0, 0), - BannerIconAlignHorizontal = HorizontalAlignment.Right, - BannerIconAlignHorizontalHYP = HorizontalAlignment.Left, - BannerIconAlignVertical = VerticalAlignment.Bottom, - BannerIconAlignVerticalHYP = VerticalAlignment.Top, - SettingsPanelWidth = 464 + WindowBounds = new Size(1024, 576), + PostEventPanelScaleFactor = 1.25f, + SidePanel1Width = new GridLength(280, GridUnitType.Pixel), + EventPostCarouselBounds = new Size(280, 130), + PostPanelBounds = new Size(280, 82), + PostPanelBottomMargin = new Thickness(0, 0, 0, 12), + PostPanelPaimonHeight = 110, + PostPanelPaimonMargin = new Thickness(0, -48, -56, 0), + PostPanelPaimonInnerMargin = new Thickness(0, 0, 0, 0), + PostPanelPaimonTextMargin = new Thickness(0, 0, 0, 18), + PostPanelPaimonTextSize = 11, + BannerIconHeight = 32, + BannerIconHeightHYP = 32, + BannerIconMargin = new Thickness(0, 0, 70, 52), + BannerIconMarginHYP = new Thickness(22, 184, 0, 0), + BannerIconAlignHorizontal = HorizontalAlignment.Right, + BannerIconAlignHorizontalHYP = HorizontalAlignment.Left, + BannerIconAlignVertical = VerticalAlignment.Bottom, + BannerIconAlignVerticalHYP = VerticalAlignment.Top, + SettingsPanelWidth = 464 } } }; @@ -107,8 +107,8 @@ internal class WindowSizeProp public Thickness PostPanelPaimonMargin { get; set; } public Thickness PostPanelPaimonInnerMargin { get; set; } public Thickness PostPanelPaimonTextMargin { get; set; } - public int BannerIconWidth { get; set; } - public int BannerIconWidthHYP { get; set; } + public int BannerIconHeight { get; set; } + public int BannerIconHeightHYP { get; set; } public Thickness BannerIconMargin { get; set; } public Thickness BannerIconMarginHYP { get; set; } public HorizontalAlignment BannerIconAlignHorizontal { get; set; } diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Variable.cs b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Variable.cs index 7a5f5f8ca..2f4b29d49 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Variable.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Variable.cs @@ -139,11 +139,11 @@ internal string NoNewsSplashMascot } } - internal int CurrentBannerIconWidth + internal int CurrentBannerIconHeight { get => CurrentGameProperty?.GamePreset.LauncherType == LauncherType.Sophon ? - WindowSize.WindowSize.CurrentWindowSize.BannerIconWidth : - WindowSize.WindowSize.CurrentWindowSize.BannerIconWidthHYP; + WindowSize.WindowSize.CurrentWindowSize.BannerIconHeight : + WindowSize.WindowSize.CurrentWindowSize.BannerIconHeightHYP; } internal Thickness CurrentBannerIconMargin diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml index 220cdb85f..187fdf504 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml @@ -50,7 +50,7 @@ Grid.RowSpan="{x:Bind pages:HomePage.CurrentBannerIconRowSpan}" Grid.Column="{x:Bind CurrentBannerIconColumn}" Grid.ColumnSpan="{x:Bind pages:HomePage.CurrentBannerIconColumnSpan}" - Width="{x:Bind CurrentBannerIconWidth}" + Height="{x:Bind CurrentBannerIconHeight}" Margin="{x:Bind CurrentBannerIconMargin}" HorizontalAlignment="{x:Bind CurrentBannerIconHorizontalAlign}" VerticalAlignment="{x:Bind CurrentBannerIconVerticalAlign}" From ee328990aad7d4e93ad7d7fc5ca3b1a6afa88690 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sat, 1 Feb 2025 17:59:24 +0700 Subject: [PATCH 17/31] Run ConfiguredTaskAwaitable callback as Task --- .../Extension/TaskExtensions.TaskAwaitable.cs | 68 ++++--------------- 1 file changed, 15 insertions(+), 53 deletions(-) diff --git a/CollapseLauncher/Classes/Extension/TaskExtensions.TaskAwaitable.cs b/CollapseLauncher/Classes/Extension/TaskExtensions.TaskAwaitable.cs index f2d61f5ee..30586a261 100644 --- a/CollapseLauncher/Classes/Extension/TaskExtensions.TaskAwaitable.cs +++ b/CollapseLauncher/Classes/Extension/TaskExtensions.TaskAwaitable.cs @@ -1,5 +1,4 @@ -using Hi3Helper; -using System; +using System; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -27,61 +26,24 @@ internal static async Task int? retryAttempt = null, ActionOnTimeOutRetry? actionOnRetry = null, CancellationToken fromToken = default) + => await WaitForRetryAsync(funcCallback.AsTaskCallback(fromToken), + timeout, + timeoutStep, + retryAttempt, + actionOnRetry, + fromToken); + + internal static ActionTimeoutTaskCallback AsTaskCallback(this Func> func, + CancellationToken fromToken) { - timeout ??= DefaultTimeoutSec; - timeoutStep ??= 0; - retryAttempt ??= DefaultRetryAttempt; + return ActionTimeoutCallback; - int retryAttemptCurrent = 1; - Exception? lastException = null; - while (retryAttemptCurrent < retryAttempt) + async Task ActionTimeoutCallback(CancellationToken innerToken) { - fromToken.ThrowIfCancellationRequested(); - CancellationTokenSource? innerCancellationToken = null; - CancellationTokenSource? consolidatedToken = null; - - try - { - innerCancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(timeout ?? DefaultTimeoutSec)); - consolidatedToken = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationToken.Token, fromToken); - - ActionTimeoutTaskAwaitableCallback delegateCallback = funcCallback(); - return await delegateCallback(consolidatedToken.Token); - } - catch (OperationCanceledException) when (fromToken.IsCancellationRequested) { throw; } - catch (Exception ex) - { - lastException = ex; - actionOnRetry?.Invoke(retryAttemptCurrent, (int)retryAttempt, timeout ?? 0, timeoutStep ?? 0); - - if (ex is TimeoutException) - { - string msg = $"The operation has timed out! Retrying attempt left: {retryAttemptCurrent}/{retryAttempt}"; - Logger.LogWriteLine(msg, LogType.Warning, true); - } - else - { - string msg = $"The operation has thrown an exception! Retrying attempt left: {retryAttemptCurrent}/{retryAttempt}\r\n{ex}"; - Logger.LogWriteLine(msg, LogType.Error, true); - } - - retryAttemptCurrent++; - timeout += timeoutStep; - } - finally - { - innerCancellationToken?.Dispose(); - consolidatedToken?.Dispose(); - } + ActionTimeoutTaskAwaitableCallback callback = func.Invoke(); + ConfiguredTaskAwaitable callbackAwaitable = callback.Invoke(fromToken); + return await callbackAwaitable; } - - if (lastException is not null - && !fromToken.IsCancellationRequested) - throw lastException is TaskCanceledException ? - new TimeoutException("The operation has timed out with inner exception!", lastException) : - lastException; - - throw new TimeoutException("The operation has timed out!"); } } } From 1ce88ddde75a0a04e92b0b0e490c32b334ff7e5f Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sat, 1 Feb 2025 18:09:05 +0700 Subject: [PATCH 18/31] Remove redundant token capture --- .../Extension/TaskExtensions.TaskAwaitable.cs | 16 ++++++---------- .../Classes/Extension/TaskExtensions.cs | 1 + 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/CollapseLauncher/Classes/Extension/TaskExtensions.TaskAwaitable.cs b/CollapseLauncher/Classes/Extension/TaskExtensions.TaskAwaitable.cs index 30586a261..d049cf3f8 100644 --- a/CollapseLauncher/Classes/Extension/TaskExtensions.TaskAwaitable.cs +++ b/CollapseLauncher/Classes/Extension/TaskExtensions.TaskAwaitable.cs @@ -8,6 +8,7 @@ namespace CollapseLauncher.Extension { public delegate ConfiguredTaskAwaitable ActionTimeoutTaskAwaitableCallback(CancellationToken token); + internal static partial class TaskExtensions { internal static Task @@ -26,24 +27,19 @@ internal static async Task int? retryAttempt = null, ActionOnTimeOutRetry? actionOnRetry = null, CancellationToken fromToken = default) - => await WaitForRetryAsync(funcCallback.AsTaskCallback(fromToken), + => await WaitForRetryAsync(funcCallback.AsTaskCallback, timeout, timeoutStep, retryAttempt, actionOnRetry, fromToken); - internal static ActionTimeoutTaskCallback AsTaskCallback(this Func> func, - CancellationToken fromToken) - { - return ActionTimeoutCallback; - - async Task ActionTimeoutCallback(CancellationToken innerToken) + private static ActionTimeoutTaskCallback AsTaskCallback(this Func> func) => + async ctx => { ActionTimeoutTaskAwaitableCallback callback = func.Invoke(); - ConfiguredTaskAwaitable callbackAwaitable = callback.Invoke(fromToken); + ConfiguredTaskAwaitable callbackAwaitable = callback.Invoke(ctx); return await callbackAwaitable; - } - } + }; } } diff --git a/CollapseLauncher/Classes/Extension/TaskExtensions.cs b/CollapseLauncher/Classes/Extension/TaskExtensions.cs index d96abc8c3..f4a2ef67b 100644 --- a/CollapseLauncher/Classes/Extension/TaskExtensions.cs +++ b/CollapseLauncher/Classes/Extension/TaskExtensions.cs @@ -9,6 +9,7 @@ namespace CollapseLauncher.Extension { public delegate Task ActionTimeoutTaskCallback(CancellationToken token); public delegate void ActionOnTimeOutRetry(int retryAttemptCount, int retryAttemptTotal, int timeOutSecond, int timeOutStep); + internal static partial class TaskExtensions { internal const int DefaultTimeoutSec = 10; From b5eaf18e739cce72dd6b1e865833ab66c5ae49aa Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sat, 1 Feb 2025 18:23:23 +0700 Subject: [PATCH 19/31] Fix crash on LoadLocalNotificationData --- .../Classes/Properties/InnerLauncherConfig.cs | 58 ++++++++++++++----- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/CollapseLauncher/Classes/Properties/InnerLauncherConfig.cs b/CollapseLauncher/Classes/Properties/InnerLauncherConfig.cs index c87a355b2..21586ce22 100644 --- a/CollapseLauncher/Classes/Properties/InnerLauncherConfig.cs +++ b/CollapseLauncher/Classes/Properties/InnerLauncherConfig.cs @@ -241,24 +241,54 @@ public static void SaveLocalNotificationData() public static async Task LoadLocalNotificationData() { - await using FileStream fileStream = File.Open(AppNotifIgnoreFile, FileMode.OpenOrCreate, FileAccess.ReadWrite); - if (!File.Exists(AppNotifIgnoreFile)) + FileStream? fileStream = null; + + bool forceCreate = false; + while (true) { - await new NotificationPush().SerializeAsync(fileStream, NotificationPushJsonContext.Default.NotificationPush).ConfigureAwait(false); - } + try + { + fileStream = File.Open(AppNotifIgnoreFile, forceCreate ? FileMode.Create : FileMode.OpenOrCreate, FileAccess.ReadWrite); + if (fileStream.Length == 0) + { + await new NotificationPush() + .SerializeAsync(fileStream, NotificationPushJsonContext.Default.NotificationPush) + .ConfigureAwait(false); + } - fileStream.Position = 0; - NotificationPush? localNotificationData = await fileStream.DeserializeAsync(NotificationPushJsonContext.Default.NotificationPush).ConfigureAwait(false); + fileStream.Position = 0; + NotificationPush? localNotificationData = await fileStream + .DeserializeAsync(NotificationPushJsonContext.Default.NotificationPush) + .ConfigureAwait(false); - if (NotificationData == null) - { - return; - } + if (NotificationData == null) + { + return; + } + + NotificationData.AppPushIgnoreMsgIds = localNotificationData?.AppPushIgnoreMsgIds; + NotificationData.RegionPushIgnoreMsgIds = localNotificationData?.RegionPushIgnoreMsgIds; + NotificationData.CurrentShowMsgIds = localNotificationData?.CurrentShowMsgIds; + NotificationData.EliminatePushList(); - NotificationData.AppPushIgnoreMsgIds = localNotificationData?.AppPushIgnoreMsgIds; - NotificationData.RegionPushIgnoreMsgIds = localNotificationData?.RegionPushIgnoreMsgIds; - NotificationData.CurrentShowMsgIds = localNotificationData?.CurrentShowMsgIds; - NotificationData.EliminatePushList(); + return; + } + catch + { + if (forceCreate) + { + throw; + } + forceCreate = true; + } + finally + { + if (fileStream != null) + { + await fileStream.DisposeAsync(); + } + } + } } } } \ No newline at end of file From a3b2c1e3aa2f1e487b0aada8130a8a7af73dce84 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sat, 1 Feb 2025 22:49:31 +0700 Subject: [PATCH 20/31] Use CanvasBitmap instead of SoftwareBitmap to draw video frame --- .../Background/BackgroundMediaUtility.cs | 6 +- .../Background/Loaders/MediaPlayerLoader.cs | 304 ++++++++++-------- 2 files changed, 167 insertions(+), 143 deletions(-) diff --git a/CollapseLauncher/Classes/Helper/Background/BackgroundMediaUtility.cs b/CollapseLauncher/Classes/Helper/Background/BackgroundMediaUtility.cs index f9f49305c..a95adc79a 100644 --- a/CollapseLauncher/Classes/Helper/Background/BackgroundMediaUtility.cs +++ b/CollapseLauncher/Classes/Helper/Background/BackgroundMediaUtility.cs @@ -346,14 +346,12 @@ private async Task LoadBackgroundInner(string mediaPath, bool EnsureCurrentImageRegistered(); EnsureCurrentMediaPlayerRegistered(); - _loaderMediaPlayer ??= new MediaPlayerLoader( - _parentUI!, + _loaderMediaPlayer ??= new MediaPlayerLoader(_parentUI!, _bgAcrylicMask!, _bgOverlayTitleBar!, _parentBgMediaPlayerBackgroundGrid!, _bgMediaPlayerBackground); - _loaderStillImage ??= new StillImageLoader( - _parentUI!, + _loaderStillImage ??= new StillImageLoader(_parentUI!, _bgAcrylicMask!, _bgOverlayTitleBar!, _parentBgImageBackgroundGrid!, _bgImageBackground, _bgImageBackgroundLast); diff --git a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs index 540c7659e..7139d2d46 100644 --- a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs +++ b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs @@ -6,25 +6,27 @@ using FFmpegInteropX; #endif using Hi3Helper; +using Hi3Helper.SentryHelper; using Hi3Helper.Shared.Region; using Microsoft.Graphics.Canvas; using Microsoft.Graphics.Canvas.UI.Xaml; +using Microsoft.UI; using Microsoft.UI.Composition; +using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; using System; +using System.Buffers; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; -using Windows.Graphics.Imaging; using Windows.Media.Playback; using Windows.Storage; using Windows.Storage.FileProperties; using Windows.UI; -using Hi3Helper.SentryHelper; using ImageUI = Microsoft.UI.Xaml.Controls.Image; using static Hi3Helper.Logger; // ReSharper disable PartialTypeWithSinglePart @@ -37,60 +39,64 @@ namespace CollapseLauncher.Helper.Background.Loaders [SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")] internal sealed partial class MediaPlayerLoader : IBackgroundMediaLoader { - #pragma warning disable CS0169 // Field is never used - private bool _isFocusChangeRunning; - #pragma warning restore CS0169 // Field is never used - - private FrameworkElement ParentUI { get; } - private Compositor CurrentCompositor { get; } - - private MediaPlayerElement? CurrentMediaPlayerFrame { get; } - private Grid CurrentMediaPlayerFrameParentGrid { get; } - private static bool IsUseVideoBgDynamicColorUpdate { get => LauncherConfig.IsUseVideoBGDynamicColorUpdate && LauncherConfig.EnableAcrylicEffect; } - - private Grid AcrylicMask { get; } - private Grid OverlayTitleBar { get; } - - public bool IsBackgroundDimm { get; set; } - private FileStream? CurrentMediaStream { get; set; } - private MediaPlayer? CurrentMediaPlayer { get; set; } -#if USEFFMPEGFORVIDEOBG - private FFmpegMediaSource? CurrentFFmpegMediaSource { get; set; } + private readonly Color _currentDefaultColor = Color.FromArgb(0, 0, 0, 0); + private bool _isCanvasCurrentlyDrawing; + + private FrameworkElement ParentUI { get; } + private Compositor CurrentCompositor { get; } + private DispatcherQueue CurrentDispatcherQueue { get; } + private static bool IsUseVideoBgDynamicColorUpdate { get => LauncherConfig.IsUseVideoBGDynamicColorUpdate && LauncherConfig.EnableAcrylicEffect; } + + private Grid AcrylicMask { get; } + private Grid OverlayTitleBar { get; } + public bool IsBackgroundDimm { get; set; } + + private FileStream? _currentMediaStream; + private MediaPlayer? _currentMediaPlayer; + #if USEFFMPEGFORVIDEOBG + private FFmpegMediaSource? _currentFFmpegMediaSource; #endif - private ImageUI? CurrentMediaImage { get; } - private SoftwareBitmap? CurrentFrameBitmap { get; set; } - private CanvasImageSource? CurrentCanvasImageSource { get; set; } - private CanvasDevice? CanvasDevice { get; set; } - private CanvasBitmap? CanvasBitmap { get; set; } - private bool IsCanvasCurrentlyDrawing { get; set; } + private CanvasImageSource? _currentCanvasImageSource; + private CanvasBitmap? _currentCanvasBitmap; + private CanvasDevice? _currentCanvasDevice; + private readonly int _currentCanvasWidth; + private readonly int _currentCanvasHeight; + private readonly float _currentCanvasDpi; + private readonly MediaPlayerElement? _currentMediaPlayerFrame; + private readonly Grid _currentMediaPlayerFrameParentGrid; + private readonly ImageUI _currentImage; internal MediaPlayerLoader( FrameworkElement parentUI, Grid acrylicMask, Grid overlayTitleBar, Grid mediaPlayerParentGrid, MediaPlayerElement? mediaPlayerCurrent) { - ParentUI = parentUI; - CurrentCompositor = parentUI.GetElementCompositor(); + ParentUI = parentUI; + CurrentCompositor = parentUI.GetElementCompositor(); + CurrentDispatcherQueue = parentUI.DispatcherQueue; AcrylicMask = acrylicMask; OverlayTitleBar = overlayTitleBar; - CurrentMediaPlayerFrameParentGrid = mediaPlayerParentGrid; - CurrentMediaPlayerFrame = mediaPlayerCurrent; + _currentMediaPlayerFrameParentGrid = mediaPlayerParentGrid; + _currentMediaPlayerFrame = mediaPlayerCurrent; - CurrentMediaImage = mediaPlayerParentGrid.AddElementToGridRowColumn(new ImageUI() - .WithHorizontalAlignment(HorizontalAlignment - .Center) - .WithVerticalAlignment(VerticalAlignment - .Center) - .WithStretch(Stretch - .UniformToFill)); + _currentCanvasWidth = (int)_currentMediaPlayerFrameParentGrid.ActualWidth; + _currentCanvasHeight = (int)_currentMediaPlayerFrameParentGrid.ActualHeight; + _currentCanvasDpi = 96f; + + _currentImage = mediaPlayerParentGrid.AddElementToGridRowColumn(new ImageUI + { + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + Stretch = Stretch.UniformToFill + }); } ~MediaPlayerLoader() { - LogWriteLine("[~MediaPlayerLoader()] MediaPlayerLoader Deconstructor has been called!", LogType.Warning, true); + LogWriteLine("[~MediaPlayerLoader()] MediaPlayerLoader Destructor has been called!", LogType.Warning, true); Dispose(); } @@ -115,56 +121,71 @@ public async Task LoadAsync(string filePath, bool isImageLoadF try { DisposeMediaModules(); - - int canvasWidth = (int)CurrentMediaPlayerFrameParentGrid.ActualWidth; - int canvasHeight = (int)CurrentMediaPlayerFrameParentGrid.ActualHeight; + _currentMediaPlayer ??= new MediaPlayer(); if (IsUseVideoBgDynamicColorUpdate) { - CanvasDevice ??= CanvasDevice.GetSharedDevice(); - CurrentFrameBitmap ??= new SoftwareBitmap(BitmapPixelFormat.Rgba8, canvasWidth, canvasHeight, - BitmapAlphaMode.Ignore); - CurrentCanvasImageSource ??= - new CanvasImageSource(CanvasDevice, canvasWidth, canvasHeight, 96, CanvasAlphaMode.Ignore); - - CurrentMediaImage!.Source = CurrentCanvasImageSource; - CurrentMediaImage.Visibility = Visibility.Visible; + _currentCanvasDevice ??= CanvasDevice.GetSharedDevice(); + _currentCanvasImageSource ??= new CanvasImageSource(_currentCanvasDevice, + _currentCanvasWidth, + _currentCanvasHeight, + _currentCanvasDpi, + CanvasAlphaMode.Premultiplied); + _currentImage.Source = _currentCanvasImageSource; + + byte[] temporaryBuffer = ArrayPool.Shared.Rent(_currentCanvasWidth * _currentCanvasHeight * 4); + try + { + _currentCanvasBitmap ??= CanvasBitmap.CreateFromBytes(_currentCanvasDevice, + temporaryBuffer, + _currentCanvasWidth, + _currentCanvasHeight, + Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, + _currentCanvasDpi, + CanvasAlphaMode.Premultiplied); + } + finally + { + ArrayPool.Shared.Return(temporaryBuffer); + } + + _currentImage.Visibility = Visibility.Visible; App.ToggleBlurBackdrop(); } - else if (CurrentMediaImage != null) + else if (_currentImage != null) { - CurrentMediaImage.Visibility = Visibility.Collapsed; - + _currentImage.Visibility = Visibility.Collapsed; } await GetPreviewAsColorPalette(filePath); - CurrentMediaStream ??= BackgroundMediaUtility.GetAlternativeFileStream() ?? File.Open(filePath, StreamExtension.FileStreamOpenReadOpt); + _currentMediaStream ??= BackgroundMediaUtility.GetAlternativeFileStream() ?? File.Open(filePath, StreamExtension.FileStreamOpenReadOpt); #if !USEFFMPEGFORVIDEOBG - EnsureIfFormatIsDashOrUnsupported(CurrentMediaStream); + EnsureIfFormatIsDashOrUnsupported(_currentMediaStream); #endif - CurrentMediaPlayer ??= new MediaPlayer(); + + _currentMediaPlayer ??= new MediaPlayer(); if (WindowUtility.IsCurrentWindowInFocus()) { - CurrentMediaPlayer.AutoPlay = true; + _currentMediaPlayer.AutoPlay = true; } bool isAudioMute = LauncherConfig.GetAppConfigValue("BackgroundAudioIsMute").ToBool(); double lastAudioVolume = LauncherConfig.GetAppConfigValue("BackgroundAudioVolume").ToDouble(); - CurrentMediaPlayer.IsMuted = isAudioMute; - CurrentMediaPlayer.Volume = lastAudioVolume; - CurrentMediaPlayer.IsLoopingEnabled = true; + _currentMediaPlayer.IsMuted = isAudioMute; + _currentMediaPlayer.Volume = lastAudioVolume; + _currentMediaPlayer.IsLoopingEnabled = true; #if !USEFFMPEGFORVIDEOBG - CurrentMediaPlayer.SetStreamSource(CurrentMediaStream.AsRandomAccessStream()); + _currentMediaPlayer.SetStreamSource(_currentMediaStream.AsRandomAccessStream()); #else - CurrentFFmpegMediaSource ??= await FFmpegMediaSource.CreateFromStreamAsync(CurrentMediaStream.AsRandomAccessStream()); + _currentFFmpegMediaSource ??= await FFmpegMediaSource.CreateFromStreamAsync(CurrentMediaStream.AsRandomAccessStream()); - await CurrentFFmpegMediaSource.OpenWithMediaPlayerAsync(CurrentMediaPlayer); + await _currentFFmpegMediaSource.OpenWithMediaPlayerAsync(CurrentMediaPlayer); const string MediaInfoStrFormat = @"Playing background video with FFmpeg! Media Duration: {0} Video Resolution: {9}x{10} px @@ -181,35 +202,35 @@ public async Task LoadAsync(string filePath, bool isImageLoadF "; Logger.LogWriteLine( string.Format(MediaInfoStrFormat, - CurrentFFmpegMediaSource.Duration.ToString("c"), // 0 - CurrentFFmpegMediaSource.CurrentVideoStream?.CodecName ?? "No Video Stream", // 1 - CurrentFFmpegMediaSource.CurrentVideoStream?.Bitrate ?? 0, // 2 - CurrentFFmpegMediaSource.CurrentVideoStream?.DecoderEngine // 3 + _currentFFmpegMediaSource.Duration.ToString("c"), // 0 + _currentFFmpegMediaSource.CurrentVideoStream?.CodecName ?? "No Video Stream", // 1 + _currentFFmpegMediaSource.CurrentVideoStream?.Bitrate ?? 0, // 2 + _currentFFmpegMediaSource.CurrentVideoStream?.DecoderEngine // 3 == DecoderEngine.FFmpegD3D11HardwareDecoder ? "Hardware" : "Software", - CurrentFFmpegMediaSource.CurrentAudioStream?.CodecName ?? "No Audio Stream", // 4 - CurrentFFmpegMediaSource.CurrentAudioStream?.Bitrate ?? 0, // 5 - CurrentFFmpegMediaSource.CurrentAudioStream?.Channels ?? 0, // 6 - CurrentFFmpegMediaSource.CurrentAudioStream?.SampleRate ?? 0, // 7 - CurrentFFmpegMediaSource.CurrentAudioStream?.BitsPerSample ?? 0, // 8 - CurrentFFmpegMediaSource.CurrentVideoStream?.PixelWidth ?? 0, // 9 - CurrentFFmpegMediaSource.CurrentVideoStream?.PixelHeight ?? 0, // 10 - CurrentFFmpegMediaSource.CurrentVideoStream?.BitsPerSample ?? 0, // 11 - CurrentFFmpegMediaSource.CurrentVideoStream?.DecoderEngine ?? 0 // 12 + _currentFFmpegMediaSource.CurrentAudioStream?.CodecName ?? "No Audio Stream", // 4 + _currentFFmpegMediaSource.CurrentAudioStream?.Bitrate ?? 0, // 5 + _currentFFmpegMediaSource.CurrentAudioStream?.Channels ?? 0, // 6 + _currentFFmpegMediaSource.CurrentAudioStream?.SampleRate ?? 0, // 7 + _currentFFmpegMediaSource.CurrentAudioStream?.BitsPerSample ?? 0, // 8 + _currentFFmpegMediaSource.CurrentVideoStream?.PixelWidth ?? 0, // 9 + _currentFFmpegMediaSource.CurrentVideoStream?.PixelHeight ?? 0, // 10 + _currentFFmpegMediaSource.CurrentVideoStream?.BitsPerSample ?? 0, // 11 + _currentFFmpegMediaSource.CurrentVideoStream?.DecoderEngine ?? 0 // 12 ), LogType.Debug, true); #endif - CurrentMediaPlayer.IsVideoFrameServerEnabled = IsUseVideoBgDynamicColorUpdate; + _currentMediaPlayer.IsVideoFrameServerEnabled = IsUseVideoBgDynamicColorUpdate; if (IsUseVideoBgDynamicColorUpdate) { - CurrentMediaPlayer.VideoFrameAvailable += FrameGrabberEvent; + _currentMediaPlayer.VideoFrameAvailable += FrameGrabberEvent; } - CurrentMediaPlayerFrame?.SetMediaPlayer(CurrentMediaPlayer); - CurrentMediaPlayer.Play(); + _currentMediaPlayerFrame?.SetMediaPlayer(_currentMediaPlayer); + _currentMediaPlayer.Play(); } catch { DisposeMediaModules(); - await BackgroundMediaUtility.AssignDefaultImage(CurrentMediaImage); + // await BackgroundMediaUtility.AssignDefaultImage(CurrentMediaImage); throw; } finally @@ -221,39 +242,48 @@ public async Task LoadAsync(string filePath, bool isImageLoadF public void DisposeMediaModules() { - if (CurrentMediaPlayer != null) + if (_currentMediaPlayer != null) { - CurrentMediaPlayer.VideoFrameAvailable -= FrameGrabberEvent; + _currentMediaPlayer.VideoFrameAvailable -= FrameGrabberEvent; + _currentMediaPlayer.Dispose(); + Interlocked.Exchange(ref _currentMediaPlayer, null); } if (IsUseVideoBgDynamicColorUpdate) { - while (IsCanvasCurrentlyDrawing) + while (_isCanvasCurrentlyDrawing) { Thread.Sleep(100); } + } + + if (_currentCanvasImageSource != null) + { + Interlocked.Exchange(ref _currentCanvasImageSource, null); + } + + if (_currentCanvasBitmap != null) + { + _currentCanvasBitmap.Dispose(); + Interlocked.Exchange(ref _currentCanvasBitmap, null); + } - CanvasDevice?.Dispose(); - CanvasDevice = null; - CurrentFrameBitmap?.Dispose(); - CurrentFrameBitmap = null; - CanvasBitmap?.Dispose(); - CanvasBitmap = null; + if (_currentCanvasDevice != null) + { + _currentCanvasDevice.Dispose(); + Interlocked.Exchange(ref _currentCanvasDevice, null); } #if USEFFMPEGFORVIDEOBG - CurrentFFmpegMediaSource?.Dispose(); - CurrentFFmpegMediaSource = null; + _currentFFmpegMediaSource?.Dispose(); + _currentFFmpegMediaSource = null; #endif - CurrentMediaPlayer?.Dispose(); - CurrentMediaPlayer = null; - CurrentCanvasImageSource = null; - CurrentMediaStream?.Dispose(); - CurrentMediaStream = null; + _currentMediaStream?.Dispose(); + _currentMediaStream = null; } #if !USEFFMPEGFORVIDEOBG - private void EnsureIfFormatIsDashOrUnsupported(Stream stream) + private static void EnsureIfFormatIsDashOrUnsupported(Stream stream) { ReadOnlySpan dashSignature = "ftypdash"u8; @@ -272,7 +302,7 @@ private void EnsureIfFormatIsDashOrUnsupported(Stream stream) } #endif - private async ValueTask GetPreviewAsColorPalette(string file) + private async Task GetPreviewAsColorPalette(string file) { StorageFile storageFile = await GetFileAsStorageFile(file); using StorageItemThumbnail thumbnail = await storageFile.GetThumbnailAsync(ThumbnailMode.VideosView); @@ -287,37 +317,33 @@ private static async ValueTask GetFileAsStorageFile(string filePath private void FrameGrabberEvent(MediaPlayer mediaPlayer, object args) { - IsCanvasCurrentlyDrawing = true; - - if (CurrentCanvasImageSource == null) + if (_isCanvasCurrentlyDrawing) { - IsCanvasCurrentlyDrawing = false; return; } - lock (this) + Interlocked.Exchange(ref _isCanvasCurrentlyDrawing, true); + try { - CurrentCanvasImageSource?.DispatcherQueue.TryEnqueue(() => - { - try - { - // Check one more time due to high possibility of thread-race issue. - if (CurrentCanvasImageSource == null) - return; + CurrentDispatcherQueue.TryEnqueue(DispatcherQueuePriority.High, RunImpl); + } + catch (Exception e) + { + LogWriteLine($"[FrameGrabberEvent] Error drawing frame to canvas.\r\n{e}", LogType.Error, true); + } + finally + { + Interlocked.Exchange(ref _isCanvasCurrentlyDrawing, false); + } - CanvasBitmap ??= CanvasBitmap.CreateFromSoftwareBitmap(CanvasDevice, CurrentFrameBitmap); - using CanvasDrawingSession canvasDrawingSession = CurrentCanvasImageSource.CreateDrawingSession(Color.FromArgb(0, 0, 0, 0)); + return; - mediaPlayer.CopyFrameToVideoSurface(CanvasBitmap); - canvasDrawingSession.DrawImage(CanvasBitmap); - } - catch (Exception e) - { - LogWriteLine($"[FrameGrabberEvent] Error drawing frame to canvas.\r\n{e}", LogType.Error, true); - } - }); + void RunImpl() + { + using CanvasDrawingSession canvasDrawingSession = _currentCanvasImageSource!.CreateDrawingSession(_currentDefaultColor); + mediaPlayer.CopyFrameToVideoSurface(_currentCanvasBitmap); + canvasDrawingSession.DrawImage(_currentCanvasBitmap); } - IsCanvasCurrentlyDrawing = false; } public void Dimm() @@ -361,7 +387,7 @@ public void Show(bool isForceShow = false) private async Task ShowInner() { - if (CurrentMediaPlayerFrameParentGrid.Opacity > 0f) return; + if (_currentMediaPlayerFrameParentGrid.Opacity > 0f) return; if (!IsUseVideoBgDynamicColorUpdate) { @@ -369,7 +395,7 @@ private async Task ShowInner() } TimeSpan duration = TimeSpan.FromSeconds(BackgroundMediaUtility.TransitionDuration); - await CurrentMediaPlayerFrameParentGrid + await _currentMediaPlayerFrameParentGrid .StartAnimation(duration, CurrentCompositor .CreateScalarKeyFrameAnimation("Opacity", 1f, 0f) @@ -390,14 +416,14 @@ private async Task HideInner() App.ToggleBlurBackdrop(isLastAcrylicEnabled); } - if (CurrentMediaPlayerFrameParentGrid.Opacity < 1f) return; + if (_currentMediaPlayerFrameParentGrid.Opacity < 1f) return; TimeSpan duration = TimeSpan.FromSeconds(BackgroundMediaUtility.TransitionDuration); - await CurrentMediaPlayerFrameParentGrid + await _currentMediaPlayerFrameParentGrid .StartAnimation(duration, CurrentCompositor .CreateScalarKeyFrameAnimation("Opacity", 0f, - (float)CurrentMediaPlayerFrameParentGrid + (float)_currentMediaPlayerFrameParentGrid .Opacity) ); @@ -418,7 +444,7 @@ public async void WindowUnfocused() private async Task WindowUnfocusedInner() { - double currentAudioVolume = CurrentMediaPlayer?.Volume ?? 0; + double currentAudioVolume = _currentMediaPlayer?.Volume ?? 0; await InterpolateVolumeChange((float)currentAudioVolume, 0f, true); Pause(); } @@ -445,22 +471,22 @@ private async Task WindowFocusedInner() public void Mute() { - if (CurrentMediaPlayer == null) return; - CurrentMediaPlayer.IsMuted = true; + if (_currentMediaPlayer == null) return; + _currentMediaPlayer.IsMuted = true; LauncherConfig.SetAndSaveConfigValue("BackgroundAudioIsMute", true); } public void Unmute() { - if (CurrentMediaPlayer == null) return; + if (_currentMediaPlayer == null) return; - CurrentMediaPlayer.IsMuted = false; + _currentMediaPlayer.IsMuted = false; LauncherConfig.SetAndSaveConfigValue("BackgroundAudioIsMute", false); } private async ValueTask InterpolateVolumeChange(float from, float to, bool isMute) { - if (CurrentMediaPlayer == null) return; + if (_currentMediaPlayer == null) return; double tFrom = from; double tTo = to; @@ -470,7 +496,7 @@ private async ValueTask InterpolateVolumeChange(float from, float to, bool isMut Loops: current += inc; - CurrentMediaPlayer.Volume = current; + _currentMediaPlayer.Volume = current; await Task.Delay(10); switch (isMute) @@ -480,24 +506,24 @@ private async ValueTask InterpolateVolumeChange(float from, float to, bool isMut goto Loops; } - CurrentMediaPlayer.Volume = tTo; + _currentMediaPlayer.Volume = tTo; } public void SetVolume(double value) { - if (CurrentMediaPlayer != null) - CurrentMediaPlayer.Volume = value; + if (_currentMediaPlayer != null) + _currentMediaPlayer.Volume = value; LauncherConfig.SetAndSaveConfigValue("BackgroundAudioVolume", value); } public void Play() { - CurrentMediaPlayer?.Play(); + _currentMediaPlayer?.Play(); } public void Pause() { - CurrentMediaPlayer?.Pause(); + _currentMediaPlayer?.Pause(); } } } \ No newline at end of file From c609bfd735cee16d0e6bb4c4245a78b4e760c405 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sat, 1 Feb 2025 22:50:30 +0700 Subject: [PATCH 21/31] Reassign to use default image if video fails --- .../Classes/Helper/Background/Loaders/MediaPlayerLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs index 7139d2d46..16e282b9f 100644 --- a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs +++ b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs @@ -230,7 +230,7 @@ public async Task LoadAsync(string filePath, bool isImageLoadF catch { DisposeMediaModules(); - // await BackgroundMediaUtility.AssignDefaultImage(CurrentMediaImage); + await BackgroundMediaUtility.AssignDefaultImage(_currentImage); throw; } finally From a4510a42aab137ef6e9b9f5b052e5fe7bbe310cf Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sat, 1 Feb 2025 23:34:17 +0700 Subject: [PATCH 22/31] Remove unused namespace --- .../Classes/Helper/Background/Loaders/MediaPlayerLoader.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs index 16e282b9f..1810d3ae5 100644 --- a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs +++ b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs @@ -10,7 +10,6 @@ using Hi3Helper.Shared.Region; using Microsoft.Graphics.Canvas; using Microsoft.Graphics.Canvas.UI.Xaml; -using Microsoft.UI; using Microsoft.UI.Composition; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; From e242dcc37b7cceff024d08149c0862ad0fe8031f Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sun, 2 Feb 2025 15:00:08 +0700 Subject: [PATCH 23/31] Use StorageItemThumbnail directly as stream --- .../Classes/Helper/Background/Loaders/MediaPlayerLoader.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs index 1810d3ae5..154c62d96 100644 --- a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs +++ b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs @@ -305,9 +305,11 @@ private async Task GetPreviewAsColorPalette(string file) { StorageFile storageFile = await GetFileAsStorageFile(file); using StorageItemThumbnail thumbnail = await storageFile.GetThumbnailAsync(ThumbnailMode.VideosView); - await using Stream stream = thumbnail.AsStream(); - await ColorPaletteUtility.ApplyAccentColor(ParentUI, stream.AsRandomAccessStream(), string.Empty, false, + await ColorPaletteUtility.ApplyAccentColor(ParentUI, + thumbnail, + string.Empty, + false, true); } From 3863fdb981db31b9677db1a3d428366acff024d2 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sun, 2 Feb 2025 18:57:21 +0700 Subject: [PATCH 24/31] Another video playback improvements - Switch to more thread-safe CanvasVirtualImageSource. - This canvas type can be used in non-UI thread and can be shared between other threads while drawing frames. - Though, the canvas still need to be disposed in the UI thread. - Fix compilation error while enabling FFmpeg as MediaSource. - Ignore throwing on FrameGrabberEvent only on release build. - Fix crash while InterpolateVolumeChange is performing volume change when _currentMediaPlayer is null. - Always ignore alpha channel. - Pre-cached drawing area of the canvas. - Favor to use Task instead of ValueTask for some methods --- .../Background/Loaders/MediaPlayerLoader.cs | 150 +++++++++--------- 1 file changed, 78 insertions(+), 72 deletions(-) diff --git a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs index 154c62d96..6189a6c6e 100644 --- a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs +++ b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs @@ -22,6 +22,7 @@ using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; +using Windows.Foundation; using Windows.Media.Playback; using Windows.Storage; using Windows.Storage.FileProperties; @@ -29,6 +30,7 @@ using ImageUI = Microsoft.UI.Xaml.Controls.Image; using static Hi3Helper.Logger; // ReSharper disable PartialTypeWithSinglePart +// ReSharper disable StringLiteralTypo #nullable enable namespace CollapseLauncher.Helper.Background.Loaders @@ -39,12 +41,15 @@ namespace CollapseLauncher.Helper.Background.Loaders internal sealed partial class MediaPlayerLoader : IBackgroundMediaLoader { private readonly Color _currentDefaultColor = Color.FromArgb(0, 0, 0, 0); - private bool _isCanvasCurrentlyDrawing; + private int _isCanvasCurrentlyDrawing; private FrameworkElement ParentUI { get; } private Compositor CurrentCompositor { get; } private DispatcherQueue CurrentDispatcherQueue { get; } - private static bool IsUseVideoBgDynamicColorUpdate { get => LauncherConfig.IsUseVideoBGDynamicColorUpdate && LauncherConfig.EnableAcrylicEffect; } + private static bool IsUseVideoBgDynamicColorUpdate + { + get => LauncherConfig.IsUseVideoBGDynamicColorUpdate && LauncherConfig.EnableAcrylicEffect; + } private Grid AcrylicMask { get; } private Grid OverlayTitleBar { get; } @@ -52,19 +57,20 @@ internal sealed partial class MediaPlayerLoader : IBackgroundMediaLoader private FileStream? _currentMediaStream; private MediaPlayer? _currentMediaPlayer; - #if USEFFMPEGFORVIDEOBG +#if USEFFMPEGFORVIDEOBG private FFmpegMediaSource? _currentFFmpegMediaSource; #endif - private CanvasImageSource? _currentCanvasImageSource; - private CanvasBitmap? _currentCanvasBitmap; - private CanvasDevice? _currentCanvasDevice; - private readonly int _currentCanvasWidth; - private readonly int _currentCanvasHeight; - private readonly float _currentCanvasDpi; - private readonly MediaPlayerElement? _currentMediaPlayerFrame; - private readonly Grid _currentMediaPlayerFrameParentGrid; - private readonly ImageUI _currentImage; + private CanvasVirtualImageSource? _currentCanvasVirtualImageSource; + private CanvasBitmap? _currentCanvasBitmap; + private CanvasDevice? _currentCanvasDevice; + private readonly int _currentCanvasWidth; + private readonly int _currentCanvasHeight; + private readonly float _currentCanvasDpi; + private readonly Rect _currentCanvasDrawArea; + private readonly MediaPlayerElement? _currentMediaPlayerFrame; + private readonly Grid _currentMediaPlayerFrameParentGrid; + private readonly ImageUI _currentImage; internal MediaPlayerLoader( FrameworkElement parentUI, @@ -81,9 +87,11 @@ internal MediaPlayerLoader( _currentMediaPlayerFrameParentGrid = mediaPlayerParentGrid; _currentMediaPlayerFrame = mediaPlayerCurrent; - _currentCanvasWidth = (int)_currentMediaPlayerFrameParentGrid.ActualWidth; - _currentCanvasHeight = (int)_currentMediaPlayerFrameParentGrid.ActualHeight; - _currentCanvasDpi = 96f; + _currentCanvasWidth = (int)_currentMediaPlayerFrameParentGrid.ActualWidth; + _currentCanvasHeight = (int)_currentMediaPlayerFrameParentGrid.ActualHeight; + _currentCanvasDpi = 96f; + + _currentCanvasDrawArea = new Rect(0, 0, _currentCanvasWidth, _currentCanvasHeight); _currentImage = mediaPlayerParentGrid.AddElementToGridRowColumn(new ImageUI { @@ -125,12 +133,13 @@ public async Task LoadAsync(string filePath, bool isImageLoadF if (IsUseVideoBgDynamicColorUpdate) { _currentCanvasDevice ??= CanvasDevice.GetSharedDevice(); - _currentCanvasImageSource ??= new CanvasImageSource(_currentCanvasDevice, - _currentCanvasWidth, - _currentCanvasHeight, - _currentCanvasDpi, - CanvasAlphaMode.Premultiplied); - _currentImage.Source = _currentCanvasImageSource; + _currentCanvasVirtualImageSource ??= new CanvasVirtualImageSource(_currentCanvasDevice, + _currentCanvasWidth, + _currentCanvasHeight, + _currentCanvasDpi, + CanvasAlphaMode.Ignore); + + _currentImage.Source = _currentCanvasVirtualImageSource.Source; byte[] temporaryBuffer = ArrayPool.Shared.Rent(_currentCanvasWidth * _currentCanvasHeight * 4); try @@ -141,7 +150,7 @@ public async Task LoadAsync(string filePath, bool isImageLoadF _currentCanvasHeight, Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, _currentCanvasDpi, - CanvasAlphaMode.Premultiplied); + CanvasAlphaMode.Ignore); } finally { @@ -162,9 +171,8 @@ public async Task LoadAsync(string filePath, bool isImageLoadF #if !USEFFMPEGFORVIDEOBG EnsureIfFormatIsDashOrUnsupported(_currentMediaStream); -#endif - _currentMediaPlayer ??= new MediaPlayer(); +#endif if (WindowUtility.IsCurrentWindowInFocus()) { @@ -181,26 +189,26 @@ public async Task LoadAsync(string filePath, bool isImageLoadF #if !USEFFMPEGFORVIDEOBG _currentMediaPlayer.SetStreamSource(_currentMediaStream.AsRandomAccessStream()); #else - - _currentFFmpegMediaSource ??= await FFmpegMediaSource.CreateFromStreamAsync(CurrentMediaStream.AsRandomAccessStream()); - - await _currentFFmpegMediaSource.OpenWithMediaPlayerAsync(CurrentMediaPlayer); - const string MediaInfoStrFormat = @"Playing background video with FFmpeg! - Media Duration: {0} - Video Resolution: {9}x{10} px - Video Codec: {1} - Video Codec Decoding Method: {3} - Video Decoder Engine: {11} - Video Bitrate: {2} bps - Video Bitdepth: {11} Bits - Audio Codec: {4} - Audio Bitrate: {5} bps - Audio Channel: {6} - Audio Sample: {7}Hz - Audio Bitwide: {8} Bits -"; - Logger.LogWriteLine( - string.Format(MediaInfoStrFormat, + _currentFFmpegMediaSource ??= await FFmpegMediaSource.CreateFromStreamAsync(_currentMediaStream.AsRandomAccessStream()); + + await _currentFFmpegMediaSource.OpenWithMediaPlayerAsync(_currentMediaPlayer); + const string mediaInfoStrFormat = """ + Playing background video with FFmpeg! + Media Duration: {0} + Video Resolution: {9}x{10} px + Video Codec: {1} + Video Codec Decoding Method: {3} + Video Decoder Engine: {11} + Video Bitrate: {2} bps + Video Bitdepth: {11} Bits + Audio Codec: {4} + Audio Bitrate: {5} bps + Audio Channel: {6} + Audio Sample: {7}Hz + Audio Bitwide: {8} Bits + """; + LogWriteLine( + string.Format(mediaInfoStrFormat, _currentFFmpegMediaSource.Duration.ToString("c"), // 0 _currentFFmpegMediaSource.CurrentVideoStream?.CodecName ?? "No Video Stream", // 1 _currentFFmpegMediaSource.CurrentVideoStream?.Bitrate ?? 0, // 2 @@ -213,8 +221,7 @@ public async Task LoadAsync(string filePath, bool isImageLoadF _currentFFmpegMediaSource.CurrentAudioStream?.BitsPerSample ?? 0, // 8 _currentFFmpegMediaSource.CurrentVideoStream?.PixelWidth ?? 0, // 9 _currentFFmpegMediaSource.CurrentVideoStream?.PixelHeight ?? 0, // 10 - _currentFFmpegMediaSource.CurrentVideoStream?.BitsPerSample ?? 0, // 11 - _currentFFmpegMediaSource.CurrentVideoStream?.DecoderEngine ?? 0 // 12 + _currentFFmpegMediaSource.CurrentVideoStream?.BitsPerSample ?? 0 // 11 ), LogType.Debug, true); #endif _currentMediaPlayer.IsVideoFrameServerEnabled = IsUseVideoBgDynamicColorUpdate; @@ -241,24 +248,26 @@ public async Task LoadAsync(string filePath, bool isImageLoadF public void DisposeMediaModules() { +#if !USEFFMPEGFORVIDEOBG if (_currentMediaPlayer != null) { _currentMediaPlayer.VideoFrameAvailable -= FrameGrabberEvent; _currentMediaPlayer.Dispose(); Interlocked.Exchange(ref _currentMediaPlayer, null); } +#endif if (IsUseVideoBgDynamicColorUpdate) { - while (_isCanvasCurrentlyDrawing) + while (_isCanvasCurrentlyDrawing == 1) { Thread.Sleep(100); } } - if (_currentCanvasImageSource != null) + if (_currentCanvasVirtualImageSource != null) { - Interlocked.Exchange(ref _currentCanvasImageSource, null); + Interlocked.Exchange(ref _currentCanvasVirtualImageSource, null); } if (_currentCanvasBitmap != null) @@ -313,37 +322,34 @@ await ColorPaletteUtility.ApplyAccentColor(ParentUI, true); } - private static async ValueTask GetFileAsStorageFile(string filePath) + private static async Task GetFileAsStorageFile(string filePath) => await StorageFile.GetFileFromPathAsync(filePath); private void FrameGrabberEvent(MediaPlayer mediaPlayer, object args) { - if (_isCanvasCurrentlyDrawing) - { - return; - } - - Interlocked.Exchange(ref _isCanvasCurrentlyDrawing, true); + CanvasDrawingSession? drawingSession = null; try { - CurrentDispatcherQueue.TryEnqueue(DispatcherQueuePriority.High, RunImpl); + Interlocked.Exchange(ref _isCanvasCurrentlyDrawing, 1); + mediaPlayer.CopyFrameToVideoSurface(_currentCanvasBitmap); + drawingSession = _currentCanvasVirtualImageSource?.CreateDrawingSession(_currentDefaultColor, _currentCanvasDrawArea); + drawingSession?.DrawImage(_currentCanvasBitmap); } - catch (Exception e) + catch + #if DEBUG + (Exception e) { - LogWriteLine($"[FrameGrabberEvent] Error drawing frame to canvas.\r\n{e}", LogType.Error, true); + LogWriteLine($"[FrameGrabberEvent] Error while drawing frame to bitmap.\r\n{e}", LogType.Warning, true); } - finally + #else { - Interlocked.Exchange(ref _isCanvasCurrentlyDrawing, false); + // ignored } - - return; - - void RunImpl() + #endif + finally { - using CanvasDrawingSession canvasDrawingSession = _currentCanvasImageSource!.CreateDrawingSession(_currentDefaultColor); - mediaPlayer.CopyFrameToVideoSurface(_currentCanvasBitmap); - canvasDrawingSession.DrawImage(_currentCanvasBitmap); + CurrentDispatcherQueue.TryEnqueue(() => drawingSession?.Dispose()); + Interlocked.Exchange(ref _isCanvasCurrentlyDrawing, 0); } } @@ -485,10 +491,8 @@ public void Unmute() LauncherConfig.SetAndSaveConfigValue("BackgroundAudioIsMute", false); } - private async ValueTask InterpolateVolumeChange(float from, float to, bool isMute) + private async Task InterpolateVolumeChange(float from, float to, bool isMute) { - if (_currentMediaPlayer == null) return; - double tFrom = from; double tTo = to; @@ -496,7 +500,9 @@ private async ValueTask InterpolateVolumeChange(float from, float to, bool isMut double inc = isMute ? -0.05 : 0.05; Loops: - current += inc; + if (_currentMediaPlayer == null) return; + + current += inc; _currentMediaPlayer.Volume = current; await Task.Delay(10); From 35eede775ff061c2a529128220aa50b34c4be238 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sun, 2 Feb 2025 19:06:53 +0700 Subject: [PATCH 25/31] Always suspend drawing session before disposing Oops, forgor to include it before disposing it. https://microsoft.github.io/Win2D/WinUI3/html/M_Microsoft_Graphics_Canvas_UI_Xaml_CanvasVirtualImageSource_CreateDrawingSession.htm#:~:text=Type%3A%C2%A0CanvasDrawingSession-,Remarks,-The%20specified%20region --- .../Classes/Helper/Background/Loaders/MediaPlayerLoader.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs index 6189a6c6e..aaeca2092 100644 --- a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs +++ b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs @@ -327,6 +327,11 @@ private static async Task GetFileAsStorageFile(string filePath) private void FrameGrabberEvent(MediaPlayer mediaPlayer, object args) { + if (_isCanvasCurrentlyDrawing == 1) + { + return; + } + CanvasDrawingSession? drawingSession = null; try { @@ -334,6 +339,7 @@ private void FrameGrabberEvent(MediaPlayer mediaPlayer, object args) mediaPlayer.CopyFrameToVideoSurface(_currentCanvasBitmap); drawingSession = _currentCanvasVirtualImageSource?.CreateDrawingSession(_currentDefaultColor, _currentCanvasDrawArea); drawingSession?.DrawImage(_currentCanvasBitmap); + _currentCanvasVirtualImageSource?.SuspendDrawingSession(drawingSession); } catch #if DEBUG From 7751c57d730abf6af7d327b2fbb9bb94dae17689 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sun, 2 Feb 2025 19:55:43 +0700 Subject: [PATCH 26/31] Lock drawing routine and allow frame skip + This allows other frames to be skipped if the first frame doesn't have enough time to be drawn between other frames (in some cases, devices with lower specs). --- .../Background/Loaders/MediaPlayerLoader.cs | 55 +++++++++++-------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs index aaeca2092..ade72cc94 100644 --- a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs +++ b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs @@ -31,6 +31,7 @@ using static Hi3Helper.Logger; // ReSharper disable PartialTypeWithSinglePart // ReSharper disable StringLiteralTypo +// ReSharper disable AsyncVoidMethod #nullable enable namespace CollapseLauncher.Helper.Background.Loaders @@ -71,6 +72,7 @@ private static bool IsUseVideoBgDynamicColorUpdate private readonly MediaPlayerElement? _currentMediaPlayerFrame; private readonly Grid _currentMediaPlayerFrameParentGrid; private readonly ImageUI _currentImage; + private readonly Lock _frameGrabberEventLock = new(); internal MediaPlayerLoader( FrameworkElement parentUI, @@ -325,37 +327,42 @@ await ColorPaletteUtility.ApplyAccentColor(ParentUI, private static async Task GetFileAsStorageFile(string filePath) => await StorageFile.GetFileFromPathAsync(filePath); - private void FrameGrabberEvent(MediaPlayer mediaPlayer, object args) +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + private async void FrameGrabberEvent(MediaPlayer mediaPlayer, object args) +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { if (_isCanvasCurrentlyDrawing == 1) { return; } - CanvasDrawingSession? drawingSession = null; - try - { - Interlocked.Exchange(ref _isCanvasCurrentlyDrawing, 1); - mediaPlayer.CopyFrameToVideoSurface(_currentCanvasBitmap); - drawingSession = _currentCanvasVirtualImageSource?.CreateDrawingSession(_currentDefaultColor, _currentCanvasDrawArea); - drawingSession?.DrawImage(_currentCanvasBitmap); - _currentCanvasVirtualImageSource?.SuspendDrawingSession(drawingSession); - } - catch - #if DEBUG - (Exception e) - { - LogWriteLine($"[FrameGrabberEvent] Error while drawing frame to bitmap.\r\n{e}", LogType.Warning, true); - } - #else - { - // ignored - } - #endif - finally + lock (_frameGrabberEventLock) { - CurrentDispatcherQueue.TryEnqueue(() => drawingSession?.Dispose()); - Interlocked.Exchange(ref _isCanvasCurrentlyDrawing, 0); + _isCanvasCurrentlyDrawing = 1; + CanvasDrawingSession? drawingSession = null; + + try + { + mediaPlayer.CopyFrameToVideoSurface(_currentCanvasBitmap); + drawingSession = _currentCanvasVirtualImageSource?.CreateDrawingSession(_currentDefaultColor, _currentCanvasDrawArea); + drawingSession?.DrawImage(_currentCanvasBitmap); + } + catch + #if DEBUG + (Exception e) + { + LogWriteLine($"[FrameGrabberEvent] Error while drawing frame to bitmap.\r\n{e}", LogType.Warning, true); + } + #else + { + // ignored + } + #endif + finally + { + CurrentDispatcherQueue.TryEnqueue(() => drawingSession?.Dispose()); + _isCanvasCurrentlyDrawing = 0; + } } } From 9adb3dcb58293230c6b55bba779013cdffe62ce4 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sun, 2 Feb 2025 21:52:31 +0700 Subject: [PATCH 27/31] Fix race condition on FrameGrabberEvent --- .../Background/Loaders/MediaPlayerLoader.cs | 77 ++++++++++++------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs index ade72cc94..719a6af30 100644 --- a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs +++ b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs @@ -32,6 +32,7 @@ // ReSharper disable PartialTypeWithSinglePart // ReSharper disable StringLiteralTypo // ReSharper disable AsyncVoidMethod +// ReSharper disable BadControlBracesIndent #nullable enable namespace CollapseLauncher.Helper.Background.Loaders @@ -42,7 +43,6 @@ namespace CollapseLauncher.Helper.Background.Loaders internal sealed partial class MediaPlayerLoader : IBackgroundMediaLoader { private readonly Color _currentDefaultColor = Color.FromArgb(0, 0, 0, 0); - private int _isCanvasCurrentlyDrawing; private FrameworkElement ParentUI { get; } private Compositor CurrentCompositor { get; } @@ -65,6 +65,7 @@ private static bool IsUseVideoBgDynamicColorUpdate private CanvasVirtualImageSource? _currentCanvasVirtualImageSource; private CanvasBitmap? _currentCanvasBitmap; private CanvasDevice? _currentCanvasDevice; + private volatile CanvasDrawingSession? _currentCanvasDrawingSession; private readonly int _currentCanvasWidth; private readonly int _currentCanvasHeight; private readonly float _currentCanvasDpi; @@ -72,7 +73,8 @@ private static bool IsUseVideoBgDynamicColorUpdate private readonly MediaPlayerElement? _currentMediaPlayerFrame; private readonly Grid _currentMediaPlayerFrameParentGrid; private readonly ImageUI _currentImage; - private readonly Lock _frameGrabberEventLock = new(); + private readonly Lock _currentLock = new(); + internal MediaPlayerLoader( FrameworkElement parentUI, @@ -261,9 +263,12 @@ public void DisposeMediaModules() if (IsUseVideoBgDynamicColorUpdate) { - while (_isCanvasCurrentlyDrawing == 1) + lock (_currentLock) { - Thread.Sleep(100); + while (_currentCanvasDrawingSession is not null) + { + Thread.Sleep(100); + } } } @@ -327,41 +332,55 @@ await ColorPaletteUtility.ApplyAccentColor(ParentUI, private static async Task GetFileAsStorageFile(string filePath) => await StorageFile.GetFileFromPathAsync(filePath); -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously private async void FrameGrabberEvent(MediaPlayer mediaPlayer, object args) -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { - if (_isCanvasCurrentlyDrawing == 1) + lock (_currentLock) { - return; - } - - lock (_frameGrabberEventLock) - { - _isCanvasCurrentlyDrawing = 1; - CanvasDrawingSession? drawingSession = null; - - try + if (_currentCanvasVirtualImageSource is null) { - mediaPlayer.CopyFrameToVideoSurface(_currentCanvasBitmap); - drawingSession = _currentCanvasVirtualImageSource?.CreateDrawingSession(_currentDefaultColor, _currentCanvasDrawArea); - drawingSession?.DrawImage(_currentCanvasBitmap); + return; } - catch - #if DEBUG - (Exception e) + + if (_currentCanvasDrawingSession is not null) { - LogWriteLine($"[FrameGrabberEvent] Error while drawing frame to bitmap.\r\n{e}", LogType.Warning, true); +#if DEBUG + LogWriteLine($@"[FrameGrabberEvent] Frame skipped at: {mediaPlayer.Position:hh\:mm\:ss\.ffffff}", LogType.Debug, true); +#endif + return; } - #else + } + + try + { + lock (_currentLock) { - // ignored + mediaPlayer.CopyFrameToVideoSurface(_currentCanvasBitmap); + _currentCanvasDrawingSession = _currentCanvasVirtualImageSource.CreateDrawingSession(_currentDefaultColor, _currentCanvasDrawArea); + _currentCanvasDrawingSession.DrawImage(_currentCanvasBitmap); } - #endif - finally + } + catch +#if DEBUG + (Exception e) + { + LogWriteLine($"[FrameGrabberEvent] Error while drawing frame to bitmap.\r\n{e}", LogType.Warning, true); + } +#else + { + // ignored + } +#endif + finally + { + lock (_currentLock) { - CurrentDispatcherQueue.TryEnqueue(() => drawingSession?.Dispose()); - _isCanvasCurrentlyDrawing = 0; + CurrentDispatcherQueue.TryEnqueue(() => + { + _currentCanvasDrawingSession?.Dispose(); + _currentCanvasDrawingSession = null; + }); } } } From 4ca0340c153bea926b1888e3c148aae05386984c Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sun, 2 Feb 2025 22:24:20 +0700 Subject: [PATCH 28/31] Use stackalloc on dash-hreader check --- .../Helper/Background/Loaders/MediaPlayerLoader.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs index 719a6af30..9da779c5d 100644 --- a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs +++ b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs @@ -302,12 +302,12 @@ private static void EnsureIfFormatIsDashOrUnsupported(Stream stream) { ReadOnlySpan dashSignature = "ftypdash"u8; - byte[] buffer = new byte[64]; + Span buffer = stackalloc byte[64]; stream.ReadExactly(buffer); try { - if (buffer.AsSpan(4).StartsWith(dashSignature)) + if (buffer.StartsWith(dashSignature)) throw new FormatException("The video format is in \"MPEG-DASH\" format, which is unsupported."); } finally @@ -338,10 +338,10 @@ private async void FrameGrabberEvent(MediaPlayer mediaPlayer, object args) { lock (_currentLock) { - if (_currentCanvasVirtualImageSource is null) - { - return; - } + if (_currentCanvasVirtualImageSource is null) + { + return; + } if (_currentCanvasDrawingSession is not null) { From c8b49d2d23fa25bdb728c25025cd7641c5f7e869 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sun, 2 Feb 2025 22:24:56 +0700 Subject: [PATCH 29/31] Immediately dispose the draw session --- .../Classes/Helper/Background/Loaders/MediaPlayerLoader.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs index 9da779c5d..45b34ef87 100644 --- a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs +++ b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs @@ -265,10 +265,8 @@ public void DisposeMediaModules() { lock (_currentLock) { - while (_currentCanvasDrawingSession is not null) - { - Thread.Sleep(100); - } + _currentCanvasDrawingSession?.Dispose(); + Interlocked.Exchange(ref _currentCanvasDrawingSession, null); } } From 1ed22e9e43eedf8f308583a29d33ef6b137fa740 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Mon, 3 Feb 2025 00:17:01 +0700 Subject: [PATCH 30/31] Dynamically change canvas size + Fix incorrect canvas size --- .../Background/Loaders/MediaPlayerLoader.cs | 117 ++++++++++++------ 1 file changed, 78 insertions(+), 39 deletions(-) diff --git a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs index 45b34ef87..bb996ade5 100644 --- a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs +++ b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs @@ -62,20 +62,20 @@ private static bool IsUseVideoBgDynamicColorUpdate private FFmpegMediaSource? _currentFFmpegMediaSource; #endif + private const float CanvasBaseDpi = 96f; + private CanvasVirtualImageSource? _currentCanvasVirtualImageSource; private CanvasBitmap? _currentCanvasBitmap; private CanvasDevice? _currentCanvasDevice; private volatile CanvasDrawingSession? _currentCanvasDrawingSession; - private readonly int _currentCanvasWidth; - private readonly int _currentCanvasHeight; - private readonly float _currentCanvasDpi; - private readonly Rect _currentCanvasDrawArea; + private volatile float _currentCanvasWidth; + private volatile float _currentCanvasHeight; + private Rect _currentCanvasDrawArea; private readonly MediaPlayerElement? _currentMediaPlayerFrame; private readonly Grid _currentMediaPlayerFrameParentGrid; private readonly ImageUI _currentImage; private readonly Lock _currentLock = new(); - internal MediaPlayerLoader( FrameworkElement parentUI, Grid acrylicMask, Grid overlayTitleBar, @@ -88,14 +88,17 @@ internal MediaPlayerLoader( AcrylicMask = acrylicMask; OverlayTitleBar = overlayTitleBar; - _currentMediaPlayerFrameParentGrid = mediaPlayerParentGrid; - _currentMediaPlayerFrame = mediaPlayerCurrent; + _currentMediaPlayerFrameParentGrid = mediaPlayerParentGrid; + _currentMediaPlayerFrameParentGrid.SizeChanged += UpdateCanvasOnSizeChangeEvent; + _currentMediaPlayerFrame = mediaPlayerCurrent; - _currentCanvasWidth = (int)_currentMediaPlayerFrameParentGrid.ActualWidth; - _currentCanvasHeight = (int)_currentMediaPlayerFrameParentGrid.ActualHeight; - _currentCanvasDpi = 96f; + float actualWidth = (float)_currentMediaPlayerFrameParentGrid.ActualWidth; + float actualHeight = (float)_currentMediaPlayerFrameParentGrid.ActualHeight; + float scalingFactor = (float)WindowUtility.CurrentWindowMonitorScaleFactor; - _currentCanvasDrawArea = new Rect(0, 0, _currentCanvasWidth, _currentCanvasHeight); + _currentCanvasWidth = actualWidth * scalingFactor; + _currentCanvasHeight = actualHeight * scalingFactor; + _currentCanvasDrawArea = new Rect(0f, 0f, _currentCanvasWidth, _currentCanvasHeight); _currentImage = mediaPlayerParentGrid.AddElementToGridRowColumn(new ImageUI { @@ -115,6 +118,7 @@ public void Dispose() { try { + _currentMediaPlayerFrameParentGrid.SizeChanged -= UpdateCanvasOnSizeChangeEvent; DisposeMediaModules(); } catch (Exception ex) @@ -137,29 +141,8 @@ public async Task LoadAsync(string filePath, bool isImageLoadF if (IsUseVideoBgDynamicColorUpdate) { _currentCanvasDevice ??= CanvasDevice.GetSharedDevice(); - _currentCanvasVirtualImageSource ??= new CanvasVirtualImageSource(_currentCanvasDevice, - _currentCanvasWidth, - _currentCanvasHeight, - _currentCanvasDpi, - CanvasAlphaMode.Ignore); - - _currentImage.Source = _currentCanvasVirtualImageSource.Source; - - byte[] temporaryBuffer = ArrayPool.Shared.Rent(_currentCanvasWidth * _currentCanvasHeight * 4); - try - { - _currentCanvasBitmap ??= CanvasBitmap.CreateFromBytes(_currentCanvasDevice, - temporaryBuffer, - _currentCanvasWidth, - _currentCanvasHeight, - Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, - _currentCanvasDpi, - CanvasAlphaMode.Ignore); - } - finally - { - ArrayPool.Shared.Return(temporaryBuffer); - } + CreateAndAssignCanvasVirtualImageSource(); + CreateCanvasBitmap(); _currentImage.Visibility = Visibility.Visible; App.ToggleBlurBackdrop(); @@ -250,6 +233,60 @@ Playing background video with FFmpeg! } } + private void UpdateCanvasOnSizeChangeEvent(object sender, SizeChangedEventArgs e) + { + lock (_currentLock) + { + float scalingFactor = (float)WindowUtility.CurrentWindowMonitorScaleFactor; + float newWidth = (float)(e.NewSize.Width * scalingFactor); + float newHeight = (float)(e.NewSize.Height * scalingFactor); + + LogWriteLine($"Updating video canvas size from: {_currentCanvasWidth}x{_currentCanvasHeight} to {newWidth}x{newHeight}", LogType.Debug, true); + + _currentCanvasWidth = newWidth; + _currentCanvasHeight = newHeight; + _currentCanvasDrawArea = new Rect(0, 0, _currentCanvasWidth, _currentCanvasHeight); + + _currentCanvasBitmap?.Dispose(); + _currentCanvasBitmap = null; + _currentCanvasVirtualImageSource = null; + CreateAndAssignCanvasVirtualImageSource(); + CreateCanvasBitmap(); + } + } + + private void CreateAndAssignCanvasVirtualImageSource() + { + _currentCanvasVirtualImageSource ??= new CanvasVirtualImageSource(_currentCanvasDevice, + _currentCanvasWidth, + _currentCanvasHeight, + CanvasBaseDpi); + + _currentImage.Source = _currentCanvasVirtualImageSource.Source; + } + + private void CreateCanvasBitmap() + { + int widthInt = (int)_currentCanvasWidth; + int heightInt = (int)_currentCanvasHeight; + + byte[] temporaryBuffer = ArrayPool.Shared.Rent(widthInt * heightInt * 4); + try + { + _currentCanvasBitmap ??= CanvasBitmap.CreateFromBytes(_currentCanvasDevice, + temporaryBuffer, + widthInt, + heightInt, + Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, + CanvasBaseDpi, + CanvasAlphaMode.Ignore); + } + finally + { + ArrayPool.Shared.Return(temporaryBuffer); + } + } + public void DisposeMediaModules() { #if !USEFFMPEGFORVIDEOBG @@ -336,10 +373,10 @@ private async void FrameGrabberEvent(MediaPlayer mediaPlayer, object args) { lock (_currentLock) { - if (_currentCanvasVirtualImageSource is null) - { - return; - } + if (_currentCanvasVirtualImageSource is null) + { + return; + } if (_currentCanvasDrawingSession is not null) { @@ -355,7 +392,9 @@ private async void FrameGrabberEvent(MediaPlayer mediaPlayer, object args) lock (_currentLock) { mediaPlayer.CopyFrameToVideoSurface(_currentCanvasBitmap); - _currentCanvasDrawingSession = _currentCanvasVirtualImageSource.CreateDrawingSession(_currentDefaultColor, _currentCanvasDrawArea); + _currentCanvasDrawingSession = _currentCanvasVirtualImageSource + .CreateDrawingSession(_currentDefaultColor, + _currentCanvasDrawArea); _currentCanvasDrawingSession.DrawImage(_currentCanvasBitmap); } } From 65bb262584978a3d7183284e4a6af284b93922b3 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Mon, 3 Feb 2025 00:53:02 +0700 Subject: [PATCH 31/31] CodeQA --- .../Background/Loaders/MediaPlayerLoader.cs | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs index bb996ade5..de85abacb 100644 --- a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs +++ b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs @@ -147,7 +147,7 @@ public async Task LoadAsync(string filePath, bool isImageLoadF _currentImage.Visibility = Visibility.Visible; App.ToggleBlurBackdrop(); } - else if (_currentImage != null) + else { _currentImage.Visibility = Visibility.Collapsed; } @@ -273,13 +273,14 @@ private void CreateCanvasBitmap() byte[] temporaryBuffer = ArrayPool.Shared.Rent(widthInt * heightInt * 4); try { - _currentCanvasBitmap ??= CanvasBitmap.CreateFromBytes(_currentCanvasDevice, - temporaryBuffer, - widthInt, - heightInt, - Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, - CanvasBaseDpi, - CanvasAlphaMode.Ignore); + _currentCanvasBitmap ??= CanvasBitmap + .CreateFromBytes(_currentCanvasDevice, + temporaryBuffer, + widthInt, + heightInt, + Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, + CanvasBaseDpi, + CanvasAlphaMode.Ignore); } finally { @@ -357,11 +358,12 @@ private async Task GetPreviewAsColorPalette(string file) StorageFile storageFile = await GetFileAsStorageFile(file); using StorageItemThumbnail thumbnail = await storageFile.GetThumbnailAsync(ThumbnailMode.VideosView); - await ColorPaletteUtility.ApplyAccentColor(ParentUI, - thumbnail, - string.Empty, - false, - true); + await ColorPaletteUtility + .ApplyAccentColor(ParentUI, + thumbnail, + string.Empty, + false, + true); } private static async Task GetFileAsStorageFile(string filePath)