From e07d03fea125e2947843300394e52591468f673d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Petryka?= <35800402+MichalPetryka@users.noreply.github.com> Date: Sun, 30 Jun 2024 08:45:27 +0200 Subject: [PATCH] Code cleanup, improve Windows and Wine version fetching (#61) * Code cleanup, improve Windows and Wine version fetching * Improve obsolete test * Return to pool, format --- NorthwoodLib.Tests/ActionDispatcherTest.cs | 153 +++++----- NorthwoodLib.Tests/NativeMemoryTest.cs | 56 ++-- NorthwoodLib.Tests/OperatingSystemTest.cs | 83 +++--- NorthwoodLib.Tests/Pools/HashSetPoolTest.cs | 63 ++--- NorthwoodLib.Tests/Pools/ListPoolTest.cs | 63 ++--- .../Pools/StringBuilderPoolTest.cs | 79 +++--- NorthwoodLib.Tests/StringUtilsTest.cs | 211 +++++++------- NorthwoodLib.Tests/Utilities/LoggingTest.cs | 76 +++-- NorthwoodLib.Tests/Utilities/XunitLogger.cs | 139 +++++---- NorthwoodLib.Tests/WineInfoTest.cs | 78 +++--- NorthwoodLib.Tests/XunitLoggerTest.cs | 111 ++++---- NorthwoodLib/ActionDispatcher.cs | 139 +++++---- NorthwoodLib/Logging/LogType.cs | 41 ++- NorthwoodLib/NativeMemory.cs | 106 +++---- NorthwoodLib/NorthwoodLib.csproj | 2 +- NorthwoodLib/OperatingSystem.Unix.cs | 24 +- NorthwoodLib/OperatingSystem.Windows.cs | 256 +++++++++++------ NorthwoodLib/OperatingSystem.cs | 59 ++-- NorthwoodLib/PlatformSettings.cs | 45 ++- NorthwoodLib/Pools/HashSetPool.cs | 117 ++++---- NorthwoodLib/Pools/IPool.cs | 31 +- NorthwoodLib/Pools/ListPool.cs | 107 ++++--- NorthwoodLib/Pools/StringBuilderPool.cs | 129 +++++---- NorthwoodLib/StringUtils.cs | 239 ++++++++-------- NorthwoodLib/WineInfo.cs | 264 +++++++++--------- 25 files changed, 1366 insertions(+), 1305 deletions(-) diff --git a/NorthwoodLib.Tests/ActionDispatcherTest.cs b/NorthwoodLib.Tests/ActionDispatcherTest.cs index ad80454..3b324f8 100644 --- a/NorthwoodLib.Tests/ActionDispatcherTest.cs +++ b/NorthwoodLib.Tests/ActionDispatcherTest.cs @@ -3,81 +3,81 @@ using System.Threading; using Xunit; -namespace NorthwoodLib.Tests +namespace NorthwoodLib.Tests; + +public class ActionDispatcherTest { - public class ActionDispatcherTest + [Fact] + public void InvokeTest() { - [Fact] - public void InvokeTest() - { - bool executed = false; - ActionDispatcher dispatcher = new(); - dispatcher.Dispatch(() => executed = true); - dispatcher.Invoke(); - Assert.True(executed); - } + bool executed = false; + ActionDispatcher dispatcher = new(); + dispatcher.Dispatch(() => executed = true); + dispatcher.Invoke(); + Assert.True(executed); + } - [Fact] - public void OrderTest() - { - List list = new(); - ActionDispatcher dispatcher = new(); - dispatcher.Dispatch(() => list.Add(1)); - dispatcher.Dispatch(() => list.Add(2)); - dispatcher.Dispatch(() => list.Add(3)); - dispatcher.Dispatch(() => list.Add(4)); - dispatcher.Dispatch(() => list.Add(5)); - dispatcher.Invoke(); - Assert.True(list.SequenceEqual(new[] { 1, 2, 3, 4, 5 })); - } + [Fact] + public void OrderTest() + { + List list = []; + ActionDispatcher dispatcher = new(); + dispatcher.Dispatch(() => list.Add(1)); + dispatcher.Dispatch(() => list.Add(2)); + dispatcher.Dispatch(() => list.Add(3)); + dispatcher.Dispatch(() => list.Add(4)); + dispatcher.Dispatch(() => list.Add(5)); + dispatcher.Invoke(); + Assert.True(list.SequenceEqual([1, 2, 3, 4, 5])); + } - [Fact] - public void WaitActionTest() - { - bool threadRunning = true; - ActionDispatcher dispatcher = new(); - new Thread(() => + [Fact] + public void WaitActionTest() + { + bool threadRunning = true; + ActionDispatcher dispatcher = new(); + new Thread(() => + { + // ReSharper disable once AccessToModifiedClosure + while (Volatile.Read(ref threadRunning)) { - // ReSharper disable once AccessToModifiedClosure - while (Volatile.Read(ref threadRunning)) - { - dispatcher.Invoke(); - Thread.Sleep(15); - } - }) + dispatcher.Invoke(); + Thread.Sleep(15); + } + }) { IsBackground = true }.Start(); - bool done = false; - dispatcher.Wait(() => done = true, 5); - Volatile.Write(ref threadRunning, false); - Assert.True(done); - } + bool done = false; + dispatcher.Wait(() => { done = true; }, 5); + Volatile.Write(ref threadRunning, false); + Assert.True(done); + } - [Fact] - public void WaitFuncTest() - { - bool threadRunning = true; - ActionDispatcher dispatcher = new(); - new Thread(() => + [Fact] + public void WaitFuncTest() + { + bool threadRunning = true; + ActionDispatcher dispatcher = new(); + new Thread(() => + { + // ReSharper disable once AccessToModifiedClosure + while (Volatile.Read(ref threadRunning)) { - // ReSharper disable once AccessToModifiedClosure - while (Volatile.Read(ref threadRunning)) - { - dispatcher.Invoke(); - Thread.Sleep(15); - } - }) + dispatcher.Invoke(); + Thread.Sleep(15); + } + }) { IsBackground = true }.Start(); - bool done = dispatcher.Wait(() => true, 5); - Volatile.Write(ref threadRunning, false); - Assert.True(done); - } + bool done = dispatcher.Wait(() => true, 5); + Volatile.Write(ref threadRunning, false); + Assert.True(done); + } - [Fact] - public void WaitArrayTest() - { - bool threadRunning = true; - ActionDispatcher dispatcher = new(); - new Thread(() => + [Fact] + public void WaitArrayTest() + { + bool threadRunning = true; + ActionDispatcher dispatcher = new(); + new Thread(() => { // ReSharper disable once AccessToModifiedClosure while (Volatile.Read(ref threadRunning)) @@ -87,17 +87,16 @@ public void WaitArrayTest() } }) { IsBackground = true }.Start(); - List list = new(); - dispatcher.Wait(new[] - { - () => list.Add(1), - () => list.Add(2), - () => list.Add(3), - () => list.Add(4), - () => list.Add(5) - }, 5); - Volatile.Write(ref threadRunning, false); - Assert.True(list.SequenceEqual(new[] { 1, 2, 3, 4, 5 })); - } + List list = []; + dispatcher.Wait( + [ + () => list.Add(1), + () => list.Add(2), + () => list.Add(3), + () => list.Add(4), + () => list.Add(5) + ], 5); + Volatile.Write(ref threadRunning, false); + Assert.True(list.SequenceEqual([1, 2, 3, 4, 5])); } } diff --git a/NorthwoodLib.Tests/NativeMemoryTest.cs b/NorthwoodLib.Tests/NativeMemoryTest.cs index e069f49..b61da69 100644 --- a/NorthwoodLib.Tests/NativeMemoryTest.cs +++ b/NorthwoodLib.Tests/NativeMemoryTest.cs @@ -1,42 +1,40 @@ -using System; using Xunit; -namespace NorthwoodLib.Tests +namespace NorthwoodLib.Tests; + +public unsafe class NativeMemoryTest { - public class NativeMemoryTest + [Theory] + [InlineData(0)] + [InlineData(10)] + [InlineData(16384)] + public void CreateTest(int size) { - [Theory] - [InlineData(0)] - [InlineData(10)] - [InlineData(16384)] - public void CreateTest(int size) + using (NativeMemory memory = new(size)) { - using (NativeMemory memory = new(size)) - { - Assert.NotEqual(IntPtr.Zero, memory.Data); - Assert.Equal(size, memory.Length); - } + Assert.NotEqual(nuint.Zero, (nuint)memory.Data); + Assert.Equal(size, memory.Length); } + } - [Fact] - public unsafe void AccessTest() + [Fact] + public void AccessTest() + { + using (NativeMemory memory = new(sizeof(int) * 10)) { - using (NativeMemory memory = new(sizeof(int) * 10)) - { - int* ptr = memory.ToPointer(); - for (int i = 0; i < 10; i++) - ptr[i] = i; + int* ptr = memory.ToPointer(); + for (int i = 0; i < 10; i++) + ptr[i] = i; - for (int i = 0; i < 10; i++) - Assert.Equal(i, ptr[i]); - } + for (int i = 0; i < 10; i++) + Assert.Equal(i, ptr[i]); } + } - [Fact] - public unsafe void PointerTest() - { - using (NativeMemory memory = new(sizeof(int) * 10)) - Assert.Equal(memory.Data, new IntPtr(memory.ToPointer())); - } + [Fact] + public void PointerTest() + { + using (NativeMemory memory = new(sizeof(int) * 10)) + Assert.Equal((nuint)memory.Data, (nuint)memory.ToPointer()); } } diff --git a/NorthwoodLib.Tests/OperatingSystemTest.cs b/NorthwoodLib.Tests/OperatingSystemTest.cs index 048d533..b0b2fda 100644 --- a/NorthwoodLib.Tests/OperatingSystemTest.cs +++ b/NorthwoodLib.Tests/OperatingSystemTest.cs @@ -5,55 +5,52 @@ using Xunit; using Xunit.Abstractions; -namespace NorthwoodLib.Tests +namespace NorthwoodLib.Tests; + +public class OperatingSystemTest(ITestOutputHelper output) : LoggingTest(output) { - public class OperatingSystemTest : LoggingTest + [Fact] + public void UsesNativeDataTest() { - public OperatingSystemTest(ITestOutputHelper output) : base(output) - { - } - - [Fact] - public void UsesNativeDataTest() - { - Assert.Equal(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || File.Exists("/etc/os-release"), OperatingSystem.UsesNativeData); - } + Assert.Equal(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || File.Exists("/etc/os-release"), OperatingSystem.UsesNativeData); + } - [Fact] - [Obsolete("UsesWine is obsolete")] - public void UsesWineTest() - { - Assert.Equal(WineInfo.UsesWine, OperatingSystem.UsesWine); - } + [Fact] + [Obsolete("UsesWine is obsolete")] + public void UsesWineTest() + { + Assert.Equal(WineInfo.UsesWine, OperatingSystem.UsesWine); + } - [Fact] - public void CorrectStringTest() - { - string version = OperatingSystem.VersionString; - Logger.WriteLine(version); - Assert.NotNull(version); - Assert.NotEqual("", version); - } + [Fact] + public void ValidStringTest() + { + string version = OperatingSystem.VersionString; + Logger.WriteLine(version); + Assert.NotNull(version); + Assert.NotEqual("", version); + } - [Fact] - public void CorrectVersionTest() - { - Version version = OperatingSystem.Version; - Logger.WriteLine(version.ToString()); - Assert.NotEqual(new Version(0, 0, 0), version); - } + [Fact] + public void ValidVersionTest() + { + Version version = OperatingSystem.Version; + Logger.WriteLine(version.ToString()); + Assert.NotEqual(new Version(0, 0, 0), version); + } - [Fact] - public void TrueVersionTest() - { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - return; + [Fact] + public void TrueVersionTest() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return; - Version version = OperatingSystem.Version; - OperatingSystem.TryCheckWindowsFileVersion(out Version v); - Assert.Equal(version.Major, v.Major); - Assert.Equal(version.Minor, v.Minor); - Assert.Equal(version.Build, v.Build); - } + Version version = OperatingSystem.Version; + OperatingSystem.TryCheckWindowsFileVersion(out Version v, OperatingSystem.GetWindowsRegistryBuild()); + Logger.WriteLine(version.ToString()); + Logger.WriteLine(v.ToString()); + Assert.Equal(version.Major, v.Major); + Assert.Equal(version.Minor, v.Minor); + Assert.Equal(version.Build, v.Build); } } diff --git a/NorthwoodLib.Tests/Pools/HashSetPoolTest.cs b/NorthwoodLib.Tests/Pools/HashSetPoolTest.cs index 35da40a..9da6e39 100644 --- a/NorthwoodLib.Tests/Pools/HashSetPoolTest.cs +++ b/NorthwoodLib.Tests/Pools/HashSetPoolTest.cs @@ -3,43 +3,42 @@ using NorthwoodLib.Pools; using Xunit; -namespace NorthwoodLib.Tests.Pools +namespace NorthwoodLib.Tests.Pools; + +public class HashSetPoolTest { - public class HashSetPoolTest + [Fact] + public void ValidTest() { - [Fact] - public void ValidTest() - { - HashSet set = HashSetPool.Shared.Rent(); - Assert.NotNull(set); - Assert.Empty(set); - HashSetPool.Shared.Return(set); - } + HashSet set = HashSetPool.Shared.Rent(); + Assert.NotNull(set); + Assert.Empty(set); + HashSetPool.Shared.Return(set); + } - [Theory] - [InlineData(0)] - [InlineData(256)] - [InlineData(512)] - [InlineData(1024)] - public void CapacityTest(int capacity) - { - HashSet set = HashSetPool.Shared.Rent(capacity); + [Theory] + [InlineData(0)] + [InlineData(256)] + [InlineData(512)] + [InlineData(1024)] + public void CapacityTest(int capacity) + { + HashSet set = HashSetPool.Shared.Rent(capacity); #if NETCOREAPP - Assert.True(set.EnsureCapacity(0) >= capacity); + Assert.True(set.EnsureCapacity(0) >= capacity); #endif - HashSetPool.Shared.Return(set); - } + HashSetPool.Shared.Return(set); + } - [Theory] - [InlineData(new object[] { new string[0] })] - [InlineData(new object[] { new[] { "test" } })] - [InlineData(new object[] { new[] { "test", "test2" } })] - [InlineData(new object[] { new[] { "test", "test2", "test3" } })] - public void SequenceTest(string[] input) - { - HashSet set = HashSetPool.Shared.Rent(input); - Assert.True(set.SequenceEqual(input)); - HashSetPool.Shared.Return(set); - } + [Theory] + [InlineData([new string[0]])] + [InlineData([new[] { "test" }])] + [InlineData([new[] { "test", "test2" }])] + [InlineData([new[] { "test", "test2", "test3" }])] + public void SequenceTest(string[] input) + { + HashSet set = HashSetPool.Shared.Rent(input); + Assert.True(set.SequenceEqual(input)); + HashSetPool.Shared.Return(set); } } diff --git a/NorthwoodLib.Tests/Pools/ListPoolTest.cs b/NorthwoodLib.Tests/Pools/ListPoolTest.cs index 37c0087..cedf55d 100644 --- a/NorthwoodLib.Tests/Pools/ListPoolTest.cs +++ b/NorthwoodLib.Tests/Pools/ListPoolTest.cs @@ -3,41 +3,40 @@ using NorthwoodLib.Pools; using Xunit; -namespace NorthwoodLib.Tests.Pools +namespace NorthwoodLib.Tests.Pools; + +public class ListPoolTest { - public class ListPoolTest + [Fact] + public void ValidTest() { - [Fact] - public void ValidTest() - { - List list = ListPool.Shared.Rent(); - Assert.NotNull(list); - Assert.Empty(list); - ListPool.Shared.Return(list); - } + List list = ListPool.Shared.Rent(); + Assert.NotNull(list); + Assert.Empty(list); + ListPool.Shared.Return(list); + } - [Theory] - [InlineData(0)] - [InlineData(256)] - [InlineData(512)] - [InlineData(1024)] - public void CapacityTest(int capacity) - { - List list = ListPool.Shared.Rent(capacity); - Assert.True(list.Capacity >= capacity); - ListPool.Shared.Return(list); - } + [Theory] + [InlineData(0)] + [InlineData(256)] + [InlineData(512)] + [InlineData(1024)] + public void CapacityTest(int capacity) + { + List list = ListPool.Shared.Rent(capacity); + Assert.True(list.Capacity >= capacity); + ListPool.Shared.Return(list); + } - [Theory] - [InlineData(new object[] { new string[0] })] - [InlineData(new object[] { new[] { "test" } })] - [InlineData(new object[] { new[] { "test", "test2" } })] - [InlineData(new object[] { new[] { "test", "test2", "test3" } })] - public void SequenceTest(string[] input) - { - List list = ListPool.Shared.Rent(input); - Assert.True(list.SequenceEqual(input)); - ListPool.Shared.Return(list); - } + [Theory] + [InlineData([new string[0]])] + [InlineData([new[] { "test" }])] + [InlineData([new[] { "test", "test2" }])] + [InlineData([new[] { "test", "test2", "test3" }])] + public void SequenceTest(string[] input) + { + List list = ListPool.Shared.Rent(input); + Assert.True(list.SequenceEqual(input)); + ListPool.Shared.Return(list); } } diff --git a/NorthwoodLib.Tests/Pools/StringBuilderPoolTest.cs b/NorthwoodLib.Tests/Pools/StringBuilderPoolTest.cs index 9a3fc2e..003c21d 100644 --- a/NorthwoodLib.Tests/Pools/StringBuilderPoolTest.cs +++ b/NorthwoodLib.Tests/Pools/StringBuilderPoolTest.cs @@ -2,50 +2,49 @@ using NorthwoodLib.Pools; using Xunit; -namespace NorthwoodLib.Tests.Pools +namespace NorthwoodLib.Tests.Pools; + +public class StringBuilderPoolTest { - public class StringBuilderPoolTest + [Fact] + public void ValidTest() { - [Fact] - public void ValidTest() - { - StringBuilder sb = StringBuilderPool.Shared.Rent(); - Assert.NotNull(sb); - Assert.True(sb.Length == 0); - StringBuilderPool.Shared.Return(sb); - } + StringBuilder sb = StringBuilderPool.Shared.Rent(); + Assert.NotNull(sb); + Assert.Equal(0, sb.Length); + StringBuilderPool.Shared.Return(sb); + } - [Theory] - [InlineData(0)] - [InlineData(256)] - [InlineData(512)] - [InlineData(1024)] - public void CapacityTest(int capacity) - { - StringBuilder sb = StringBuilderPool.Shared.Rent(capacity); - Assert.True(sb.Capacity >= capacity); - StringBuilderPool.Shared.Return(sb); - } + [Theory] + [InlineData(0)] + [InlineData(256)] + [InlineData(512)] + [InlineData(1024)] + public void CapacityTest(int capacity) + { + StringBuilder sb = StringBuilderPool.Shared.Rent(capacity); + Assert.True(sb.Capacity >= capacity); + StringBuilderPool.Shared.Return(sb); + } - [Theory] - [InlineData("")] - [InlineData("test 1")] - [InlineData("test \n \0 \r")] - public void TextTest(string input) - { - StringBuilder sb = StringBuilderPool.Shared.Rent(input); - Assert.Equal(input, sb.ToString()); - StringBuilderPool.Shared.Return(sb); - } + [Theory] + [InlineData("")] + [InlineData("test 1")] + [InlineData("test \n \0 \r")] + public void TextTest(string input) + { + StringBuilder sb = StringBuilderPool.Shared.Rent(input); + Assert.Equal(input, sb.ToString()); + StringBuilderPool.Shared.Return(sb); + } - [Theory] - [InlineData("")] - [InlineData("test 1")] - [InlineData("test \n \0 \r")] - public void ToStringReturnTest(string input) - { - StringBuilder sb = StringBuilderPool.Shared.Rent(input); - Assert.Equal(input, StringBuilderPool.Shared.ToStringReturn(sb)); - } + [Theory] + [InlineData("")] + [InlineData("test 1")] + [InlineData("test \n \0 \r")] + public void ToStringReturnTest(string input) + { + StringBuilder sb = StringBuilderPool.Shared.Rent(input); + Assert.Equal(input, StringBuilderPool.Shared.ToStringReturn(sb)); } } diff --git a/NorthwoodLib.Tests/StringUtilsTest.cs b/NorthwoodLib.Tests/StringUtilsTest.cs index 793073d..98ced14 100644 --- a/NorthwoodLib.Tests/StringUtilsTest.cs +++ b/NorthwoodLib.Tests/StringUtilsTest.cs @@ -3,123 +3,120 @@ using Xunit; using Xunit.Abstractions; -namespace NorthwoodLib.Tests +namespace NorthwoodLib.Tests; + +public class StringUtilsTest(ITestOutputHelper output) : LoggingTest(output) { - public class StringUtilsTest : LoggingTest + [Theory] + [InlineData(5, '8', "1234567890", "12345")] + [InlineData(5, '3', "1234567890", "123")] + [InlineData(5, '2', "1232567890", "1232")] + public void TruncateToLastCharTest(int maxSize, char c, string text, string truncated) { - public StringUtilsTest(ITestOutputHelper output) : base(output) - { - } - - [Theory] - [InlineData(5, '8', "1234567890", "12345")] - [InlineData(5, '3', "1234567890", "123")] - [InlineData(5, '2', "1232567890", "1232")] - public void TruncateToLastCharTest(int maxSize, char c, string text, string truncated) - { - string tr = text.TruncateToLast(maxSize, c); - Logger.WriteLine(tr); - Assert.True(tr.Length <= maxSize); - Assert.Equal(truncated, tr); - } + string tr = text.TruncateToLast(maxSize, c); + Logger.WriteLine(tr); + Assert.True(tr.Length <= maxSize); + Assert.Equal(truncated, tr); + } - [Theory] - [InlineData(5, "8", StringComparison.Ordinal, "1234567890", "12345")] - [InlineData(5, "3", StringComparison.Ordinal, "1234567890", "123")] - [InlineData(5, "2", StringComparison.Ordinal, "1232567890", "1232")] - [InlineData(5, "a", StringComparison.Ordinal, "bcad54gfd", "bca")] - [InlineData(5, "a", StringComparison.OrdinalIgnoreCase, "BCAD54GFD", "BCA")] - [InlineData(5, "ad", StringComparison.Ordinal, "bcad54gfd", "bcad")] - [InlineData(5, "ad", StringComparison.OrdinalIgnoreCase, "BCAD54GFD", "BCAD")] - [InlineData(5, "A", StringComparison.Ordinal, "bcad54gfd", "bcad5")] - public void TruncateToLastStringTest(int maxSize, string s, StringComparison comparision, string text, string truncated) - { - string tr = text.TruncateToLast(maxSize, s, comparision); - Logger.WriteLine(tr); - Assert.True(tr.Length <= maxSize); - Assert.Equal(truncated, tr); - } + [Theory] + [InlineData(5, "8", StringComparison.Ordinal, "1234567890", "12345")] + [InlineData(5, "3", StringComparison.Ordinal, "1234567890", "123")] + [InlineData(5, "2", StringComparison.Ordinal, "1232567890", "1232")] + [InlineData(5, "a", StringComparison.Ordinal, "bcad54gfd", "bca")] + [InlineData(5, "a", StringComparison.OrdinalIgnoreCase, "BCAD54GFD", "BCA")] + [InlineData(5, "ad", StringComparison.Ordinal, "bcad54gfd", "bcad")] + [InlineData(5, "ad", StringComparison.OrdinalIgnoreCase, "BCAD54GFD", "BCAD")] + [InlineData(5, "A", StringComparison.Ordinal, "bcad54gfd", "bcad5")] + public void TruncateToLastStringTest(int maxSize, string s, StringComparison comparision, string text, string truncated) + { + string tr = text.TruncateToLast(maxSize, s, comparision); + Logger.WriteLine(tr); + Assert.True(tr.Length <= maxSize); + Assert.Equal(truncated, tr); + } - [Theory] - [InlineData("")] - [InlineData("fdds DASFD żąćźż \n \0 test")] - public void Base64Test(string text) - { - string base64 = StringUtils.Base64Encode(text); - Logger.WriteLine($"{text} - {base64}"); - Assert.Equal(text, StringUtils.Base64Decode(base64)); - } + [Theory] + [InlineData("")] + [InlineData("fdds DASFD żąćźż \n \0 test")] + public void Base64Test(string text) + { + string base64 = StringUtils.Base64Encode(text); + Logger.WriteLine($"{text} - {base64}"); + Assert.Equal(text, StringUtils.Base64Decode(base64)); + } - [Theory] - [InlineData("", "")] - [InlineData("fdds DASFD żąćźż \n \0 test", "ZmRkcyBEQVNGRCDFvMSFxIfFusW8IAogACB0ZXN0")] - public void Base64EncodeDecodeTest(string text, string base64) - { - Assert.Equal(text, StringUtils.Base64Decode(base64)); - Assert.Equal(base64, StringUtils.Base64Encode(text)); - } + [Theory] + [InlineData("", "")] + [InlineData("fdds DASFD żąćźż \n \0 test", "ZmRkcyBEQVNGRCDFvMSFxIfFusW8IAogACB0ZXN0")] + public void Base64EncodeDecodeTest(string text, string base64) + { + Assert.Equal(text, StringUtils.Base64Decode(base64)); + Assert.Equal(base64, StringUtils.Base64Encode(text)); + } - [Theory] - [InlineData("1234567890", '6', true)] - [InlineData("1234567890", 'c', false)] - public void ContainsCharTest(string text, char c, bool contains) - { - Assert.Equal(contains, text.Contains(c)); - } + [Theory] + [InlineData("1234567890", '6', true)] + [InlineData("1234567890", 'c', false)] + [Obsolete("Contains is obsolete")] + public void ContainsCharTest(string text, char c, bool contains) + { + Assert.Equal(contains, StringUtils.Contains(text, c)); + } - [Theory] - [InlineData("1234567890", "6", StringComparison.Ordinal, true)] - [InlineData("1234567890", "c", StringComparison.Ordinal, false)] - [InlineData("abcd", "c", StringComparison.Ordinal, true)] - [InlineData("abcd", "C", StringComparison.Ordinal, false)] - [InlineData("abcd", "C", StringComparison.OrdinalIgnoreCase, true)] - [InlineData("abcd", "bc", StringComparison.Ordinal, true)] - [InlineData("abcd", "BC", StringComparison.Ordinal, false)] - [InlineData("abcd", "BC", StringComparison.OrdinalIgnoreCase, true)] - public void ContainsStringTest(string text, string s, StringComparison comparision, bool contains) - { - Assert.Equal(contains, text.Contains(s, comparision)); - } + [Theory] + [InlineData("1234567890", "6", StringComparison.Ordinal, true)] + [InlineData("1234567890", "c", StringComparison.Ordinal, false)] + [InlineData("abcd", "c", StringComparison.Ordinal, true)] + [InlineData("abcd", "C", StringComparison.Ordinal, false)] + [InlineData("abcd", "C", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("abcd", "bc", StringComparison.Ordinal, true)] + [InlineData("abcd", "BC", StringComparison.Ordinal, false)] + [InlineData("abcd", "BC", StringComparison.OrdinalIgnoreCase, true)] + [Obsolete("Contains is obsolete")] + public void ContainsStringTest(string text, string s, StringComparison comparision, bool contains) + { + Assert.Equal(contains, StringUtils.Contains(text, s, comparision)); + } - [Theory] - [InlineData("")] - [InlineData("abcd")] - [InlineData("abcd \0 \u0000 \u007F \n żźććą 主江热")] - public void StripUnicodeCharactersTest(string text) - { - Assert.DoesNotMatch(@"[^\u0000-\u007F]", StringUtils.StripUnicodeCharacters(text)); - } + [Theory] + [InlineData("")] + [InlineData("abcd")] + [InlineData("abcd \0 \u0000 \u007F \n żźććą 主江热")] + public void StripUnicodeCharactersTest(string text) + { + Assert.DoesNotMatch(@"[^\u0000-\u007F]", StringUtils.StripUnicodeCharacters(text)); + } - [Theory] - [InlineData("", "")] - [InlineData("abcd", "abcd")] - [InlineData("ab cd \0 \u0000 \u007F \n żźććą 主江热", "ab cd ")] - public void RemoveSpecialCharactersTest(string text, string output) - { - Assert.Equal(output, StringUtils.RemoveSpecialCharacters(text)); - } + [Theory] + [InlineData("", "")] + [InlineData("abcd", "abcd")] + [InlineData("ab cd \0 \u0000 \u007F \n żźććą 主江热", "ab cd ")] + public void RemoveSpecialCharactersTest(string text, string output) + { + Assert.Equal(output, StringUtils.RemoveSpecialCharacters(text)); + } - [Theory] - [InlineData("", "color")] - [InlineData("abcd", "color")] - [InlineData("ab cd \0 \u0000 \u007F \n żźććą 主江热", "color")] - [InlineData("ab cd \0 \u0000 text \u007F \n żźććą 主江热", "color")] - [InlineData("ab cd \0 \u0000 text b
\u007F \n żźććą 主江热", "color")] - [InlineData("ab cd \0 \u0000 text b
\u007F \n żźććą 主江热", "br")] - public void StripTagTest(string text, string tag) - { - Assert.DoesNotMatch($"<.*?{tag}.*?>", StringUtils.StripTag(text, tag)); - } + [Theory] + [InlineData("", "color")] + [InlineData("abcd", "color")] + [InlineData("ab cd \0 \u0000 \u007F \n żźććą 主江热", "color")] + [InlineData("ab cd \0 \u0000 text \u007F \n żźććą 主江热", "color")] + [InlineData("ab cd \0 \u0000 text b
\u007F \n żźććą 主江热", "color")] + [InlineData("ab cd \0 \u0000 text b
\u007F \n żźććą 主江热", "br")] + public void StripTagTest(string text, string tag) + { + Assert.DoesNotMatch($"<.*?{tag}.*?>", StringUtils.StripTag(text, tag)); + } - [Theory] - [InlineData("")] - [InlineData("abcd")] - [InlineData("ab cd \0 \u0000 \u007F \n żźććą 主江热")] - [InlineData("ab cd \0 \u0000 text \u007F \n żźććą 主江热")] - [InlineData("ab cd \0 \u0000 text b
\u007F \n żźććą 主江热")] - public void StripTagsTest(string text) - { - Assert.DoesNotMatch("<.*?>", StringUtils.StripTags(text)); - } + [Theory] + [InlineData("")] + [InlineData("abcd")] + [InlineData("ab cd \0 \u0000 \u007F \n żźććą 主江热")] + [InlineData("ab cd \0 \u0000 text \u007F \n żźććą 主江热")] + [InlineData("ab cd \0 \u0000 text b
\u007F \n żźććą 主江热")] + public void StripTagsTest(string text) + { + Assert.DoesNotMatch("<.*?>", StringUtils.StripTags(text)); } } diff --git a/NorthwoodLib.Tests/Utilities/LoggingTest.cs b/NorthwoodLib.Tests/Utilities/LoggingTest.cs index 61b4171..2825fb3 100644 --- a/NorthwoodLib.Tests/Utilities/LoggingTest.cs +++ b/NorthwoodLib.Tests/Utilities/LoggingTest.cs @@ -1,53 +1,51 @@ using System; -using System.Threading; using NorthwoodLib.Logging; using Xunit.Abstractions; -namespace NorthwoodLib.Tests.Utilities +namespace NorthwoodLib.Tests.Utilities; + +/// +/// Base for tests using +/// +public abstract class LoggingTest : IDisposable { + protected readonly XunitLogger Logger; + private readonly int _currentThread; + /// - /// Base for tests using + /// Creates the and starts listening to with it /// - public abstract class LoggingTest : IDisposable + /// Xunit output handler + protected LoggingTest(ITestOutputHelper output) { - protected readonly XunitLogger Logger; - private readonly int _currentThread; - - /// - /// Creates the and starts listening to with it - /// - /// Xunit output handler - protected LoggingTest(ITestOutputHelper output) - { - Logger = new XunitLogger(output, GetType()); - _currentThread = Thread.CurrentThread.ManagedThreadId; - PlatformSettings.Logged += Log; - } + Logger = new XunitLogger(output, GetType()); + _currentThread = Environment.CurrentManagedThreadId; + PlatformSettings.Logged += Log; + } - private void Log(string message, LogType type) - { - if (Thread.CurrentThread.ManagedThreadId == _currentThread) - Logger.WriteLine(message); - } + private void Log(string message, LogType type) + { + if (Environment.CurrentManagedThreadId == _currentThread) + Logger.WriteLine(message); + } - private void Close() - { - PlatformSettings.Logged -= Log; - Logger.Dispose(); - } + private void Close() + { + PlatformSettings.Logged -= Log; + Logger.Dispose(); + } - /// - /// Disposes the - /// - public void Dispose() - { - Close(); - GC.SuppressFinalize(this); - } + /// + /// Disposes the + /// + public void Dispose() + { + Close(); + GC.SuppressFinalize(this); + } - ~LoggingTest() - { - Close(); - } + ~LoggingTest() + { + Close(); } } diff --git a/NorthwoodLib.Tests/Utilities/XunitLogger.cs b/NorthwoodLib.Tests/Utilities/XunitLogger.cs index c2414c4..8201fd1 100644 --- a/NorthwoodLib.Tests/Utilities/XunitLogger.cs +++ b/NorthwoodLib.Tests/Utilities/XunitLogger.cs @@ -2,91 +2,90 @@ using System.IO; using Xunit.Abstractions; -namespace NorthwoodLib.Tests.Utilities +namespace NorthwoodLib.Tests.Utilities; + +/// +/// Logs data to XUnit output and log file set with environment variable xunitlogpath +/// +public sealed class XunitLogger : ITestOutputHelper, IDisposable { + private readonly ITestOutputHelper _outputHelper; + private readonly string _className; + + private static StreamWriter _writer; + private static int _refCount; + + private static readonly string LogPath = Environment.GetEnvironmentVariable("xunitlogpath"); + private static readonly object WriteLock = new(); + /// /// Logs data to XUnit output and log file set with environment variable xunitlogpath /// - public sealed class XunitLogger : ITestOutputHelper, IDisposable + /// XUnit logger + /// Test class + public XunitLogger(ITestOutputHelper output, Type type) { - private readonly ITestOutputHelper _outputHelper; - private readonly string _className; + _outputHelper = output; + _className = type.FullName; - private static StreamWriter _writer; - private static int _refCount; - - private static readonly string _logPath = Environment.GetEnvironmentVariable("xunitlogpath"); - private static readonly object _writeLock = new(); - - /// - /// Logs data to XUnit output and log file set with environment variable xunitlogpath - /// - /// XUnit logger - /// Test class - public XunitLogger(ITestOutputHelper output, Type type) + lock (WriteLock) { - _outputHelper = output; - _className = type.FullName; - - lock (_writeLock) - { - if (_logPath == null) - return; - if (_refCount++ == 0) - _writer = new StreamWriter(new FileStream(_logPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite)); - } + if (LogPath == null) + return; + if (_refCount++ == 0) + _writer = new StreamWriter(new FileStream(LogPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite)); } + } - /// - /// Writes a message to XUnit output and log file - /// - /// Text to write - public void WriteLine(string message) - { - _outputHelper?.WriteLine(message); - lock (_writeLock) - _writer?.WriteLine($"[{_className}] {message}\n{Environment.StackTrace.TruncateToLast(1000, '\n').TrimEnd()}\n"); - } + /// + /// Writes a message to XUnit output and log file + /// + /// Text to write + public void WriteLine(string message) + { + _outputHelper?.WriteLine(message); + lock (WriteLock) + _writer?.WriteLine($"[{_className}] {message}\n{Environment.StackTrace.TruncateToLast(1000, '\n').TrimEnd()}\n"); + } - /// - /// Writes a formatted message to XUnit output and log file - /// - /// Text used for formatting - /// Data inserted into format - public void WriteLine(string format, params object[] args) - { - _outputHelper?.WriteLine(format, args); - lock (_writeLock) - _writer?.WriteLine($"[{_className}] {string.Format(format, args)}\n{Environment.StackTrace.TruncateToLast(1000, '\n').TrimEnd()}\n"); - } + /// + /// Writes a formatted message to XUnit output and log file + /// + /// Text used for formatting + /// Data inserted into format + public void WriteLine(string format, params object[] args) + { + _outputHelper?.WriteLine(format, args); + lock (WriteLock) + _writer?.WriteLine($"[{_className}] {string.Format(format, args)}\n{Environment.StackTrace.TruncateToLast(1000, '\n').TrimEnd()}\n"); + } - private static void ReleaseUnmanagedResources() + private static void ReleaseUnmanagedResources() + { + lock (WriteLock) { - lock (_writeLock) - { - if (_writer == null) - return; + if (_writer == null) + return; - if (--_refCount == 0) - { - _writer.Dispose(); - _writer = null; - } - } - } + if (--_refCount != 0) + return; - /// - /// Releases the logfile stream - /// - public void Dispose() - { - ReleaseUnmanagedResources(); - GC.SuppressFinalize(this); + _writer.Dispose(); + _writer = null; } + } - ~XunitLogger() - { - ReleaseUnmanagedResources(); - } + /// + /// Releases the logfile stream + /// + public void Dispose() + { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + ~XunitLogger() + { + ReleaseUnmanagedResources(); } } diff --git a/NorthwoodLib.Tests/WineInfoTest.cs b/NorthwoodLib.Tests/WineInfoTest.cs index 3f4e7cc..f7b5704 100644 --- a/NorthwoodLib.Tests/WineInfoTest.cs +++ b/NorthwoodLib.Tests/WineInfoTest.cs @@ -4,54 +4,46 @@ using Xunit; using Xunit.Abstractions; -namespace NorthwoodLib.Tests +namespace NorthwoodLib.Tests; + +public class WineInfoTest(ITestOutputHelper output) : LoggingTest(output) { - public class WineInfoTest : LoggingTest + [Fact] + public void NotWindowsTest() { - public WineInfoTest(ITestOutputHelper output) : base(output) - { - } - - [Fact] - public void NotWindowsTest() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - return; - Assert.False(WineInfo.UsesWine); - Assert.Null(WineInfo.WineVersion); -#pragma warning disable CS0618 - Assert.Null(WineInfo.WinePatches); -#pragma warning restore CS0618 - Assert.Null(WineInfo.WineHost); - } + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return; + Assert.False(WineInfo.UsesWine); + Assert.Null(WineInfo.WineVersion); + Assert.Null(WineInfo.WineHost); + } - [Fact] - public void UsesWineTest() - { - if (WineInfo.WineVersion != null) - Assert.True(WineInfo.UsesWine); - } + [Fact] + public void UsesWineTest() + { + if (WineInfo.WineVersion != null) + Assert.True(WineInfo.UsesWine); + } - [Fact] - public void UsesProtonTest() - { - if (WineInfo.UsesProton) - Assert.True(WineInfo.UsesWine); - } + [Fact] + public void UsesProtonTest() + { + if (WineInfo.UsesProton) + Assert.True(WineInfo.UsesWine); + } - [Fact] - [Obsolete("WinePatches is obsolete")] - public void WinePatchesTest() - { - Assert.Null(WineInfo.WinePatches); - } + [Fact] + [Obsolete("WinePatches is obsolete")] + public void WinePatchesTest() + { + Assert.Null(WineInfo.WinePatches); + } - [Fact] - public void WineHostTest() - { - if (WineInfo.WineHost == null) - return; - Assert.False(string.IsNullOrWhiteSpace(WineInfo.WineHost)); - } + [Fact] + public void WineHostTest() + { + if (WineInfo.WineHost == null) + return; + Assert.False(string.IsNullOrWhiteSpace(WineInfo.WineHost)); } } diff --git a/NorthwoodLib.Tests/XunitLoggerTest.cs b/NorthwoodLib.Tests/XunitLoggerTest.cs index 157e3d1..4d0f89b 100644 --- a/NorthwoodLib.Tests/XunitLoggerTest.cs +++ b/NorthwoodLib.Tests/XunitLoggerTest.cs @@ -1,74 +1,71 @@ using System; -using System.Threading; using NorthwoodLib.Logging; using NorthwoodLib.Tests.Utilities; using Xunit; using Xunit.Abstractions; -namespace NorthwoodLib.Tests +namespace NorthwoodLib.Tests; + +public sealed class XunitLoggerTest : IDisposable { - public sealed class XunitLoggerTest : IDisposable - { - private bool _logged; + private bool _logged; - private readonly XunitLogger _logger; - private readonly int _currentThread; - public XunitLoggerTest(ITestOutputHelper output) - { - _logger = new XunitLogger(output, GetType()); - _currentThread = Thread.CurrentThread.ManagedThreadId; - PlatformSettings.Logged += Log; - } + private readonly XunitLogger _logger; + private readonly int _currentThread; + public XunitLoggerTest(ITestOutputHelper output) + { + _logger = new XunitLogger(output, GetType()); + _currentThread = Environment.CurrentManagedThreadId; + PlatformSettings.Logged += Log; + } - private void Log(string message, LogType type) - { - // check thread id cause unit tests run in parallel - if (Thread.CurrentThread.ManagedThreadId == _currentThread) - { - _logger.WriteLine(message); - _logged = true; - } - } + private void Log(string message, LogType type) + { + // check thread id cause unit tests run in parallel + if (Environment.CurrentManagedThreadId != _currentThread) + return; + _logger.WriteLine(message); + _logged = true; + } - [Theory] - [InlineData("XunitLoggerTest")] - [InlineData("Test data \n data żźćńąśłę€ó")] - public void WriteTest(string text) - { - _logger.WriteLine(text); - } + [Theory] + [InlineData("XunitLoggerTest")] + [InlineData("Test data \n data żźćńąśłę€ó")] + public void WriteTest(string text) + { + _logger.WriteLine(text); + } - [Theory] - [InlineData("{0} {1} {2}", new object[] { 1, 2, 3 })] - public void WriteFormatTest(string format, object[] args) - { - _logger.WriteLine(format, args); - } + [Theory] + [InlineData("{0} {1} {2}", (object[])[ 1, 2, 3 ])] + public void WriteFormatTest(string format, object[] args) + { + _logger.WriteLine(format, args); + } - [Theory] - [InlineData("XunitLoggerTest")] - [InlineData("Test data \n data żźćńąśłę€ó")] - public void EventTest(string text) - { - PlatformSettings.Log(text, LogType.Info); - Assert.True(_logged); - } + [Theory] + [InlineData("XunitLoggerTest")] + [InlineData("Test data \n data żźćńąśłę€ó")] + public void EventTest(string text) + { + PlatformSettings.Log(text, LogType.Info); + Assert.True(_logged); + } - private void Close() - { - PlatformSettings.Logged -= Log; - _logger.Dispose(); - } + private void Close() + { + PlatformSettings.Logged -= Log; + _logger.Dispose(); + } - public void Dispose() - { - Close(); - GC.SuppressFinalize(this); - } + public void Dispose() + { + Close(); + GC.SuppressFinalize(this); + } - ~XunitLoggerTest() - { - Close(); - } + ~XunitLoggerTest() + { + Close(); } } diff --git a/NorthwoodLib/ActionDispatcher.cs b/NorthwoodLib/ActionDispatcher.cs index b4346fd..19254b5 100644 --- a/NorthwoodLib/ActionDispatcher.cs +++ b/NorthwoodLib/ActionDispatcher.cs @@ -3,87 +3,86 @@ using System.Collections.Generic; using System.Threading; -namespace NorthwoodLib +namespace NorthwoodLib; + +/// +/// Queues and runs them on +/// +public sealed class ActionDispatcher { + private readonly ConcurrentQueue _actionQueue = new(); + /// - /// Queues and runs them on + /// Queues an /// - public sealed class ActionDispatcher + /// Queued + public void Dispatch(Action action) { - private readonly ConcurrentQueue _actionQueue = new(); + _actionQueue.Enqueue(action); + } - /// - /// Queues an - /// - /// Queued - public void Dispatch(Action action) + /// + /// Queues an and waits for it to finish + /// + /// Queued + /// Finish check sleep time + public void Wait(Action action, int sleepTime) + { + bool finished = false; + _actionQueue.Enqueue(() => { - _actionQueue.Enqueue(action); - } + action(); + Volatile.Write(ref finished, true); + }); + while (!Volatile.Read(ref finished)) + Thread.Sleep(sleepTime); + } - /// - /// Queues an and waits for it to finish - /// - /// Queued - /// Finish check sleep time - public void Wait(Action action, int sleepTime) + /// + /// Queues a collection of s and waits for all of them to finish + /// + /// Queued collection of s + /// Finish check sleep time + public void Wait(IEnumerable actions, int sleepTime) + { + bool finished = false; + _actionQueue.Enqueue(() => { - bool finished = false; - _actionQueue.Enqueue(() => - { + foreach (Action action in actions) action(); - finished = true; - }); - while (!finished) - Thread.Sleep(sleepTime); - } - - /// - /// Queues a collection of s and waits for all of them to finish - /// - /// Queued collection of s - /// Finish check sleep time - public void Wait(IEnumerable actions, int sleepTime) - { - bool finished = false; - _actionQueue.Enqueue(() => - { - foreach (Action action in actions) - action(); - finished = true; - }); - while (!finished) - Thread.Sleep(sleepTime); - } + Volatile.Write(ref finished, true); + }); + while (!Volatile.Read(ref finished)) + Thread.Sleep(sleepTime); + } - /// - /// Queues a and waits for it to finish - /// - /// Function return type - /// Queued - /// Finish check sleep time - /// Value returned by - public T Wait(Func func, int sleepTime) + /// + /// Queues a and waits for it to finish + /// + /// Function return type + /// Queued + /// Finish check sleep time + /// Value returned by + public T Wait(Func func, int sleepTime) + { + T result = default; + bool finished = false; + _actionQueue.Enqueue(() => { - T result = default; - bool finished = false; - _actionQueue.Enqueue(() => - { - result = func(); - finished = true; - }); - while (!finished) - Thread.Sleep(sleepTime); - return result; - } + result = func(); + Volatile.Write(ref finished, true); + }); + while (!Volatile.Read(ref finished)) + Thread.Sleep(sleepTime); + return result; + } - /// - /// Runs all scheduled - /// - public void Invoke() - { - while (_actionQueue.TryDequeue(out Action action)) - action(); - } + /// + /// Runs all scheduled + /// + public void Invoke() + { + while (_actionQueue.TryDequeue(out Action action)) + action(); } } diff --git a/NorthwoodLib/Logging/LogType.cs b/NorthwoodLib/Logging/LogType.cs index 55ad937..5eceec4 100644 --- a/NorthwoodLib/Logging/LogType.cs +++ b/NorthwoodLib/Logging/LogType.cs @@ -1,25 +1,24 @@ -namespace NorthwoodLib.Logging +namespace NorthwoodLib.Logging; + +/// +/// Specified the log message type +/// +public enum LogType { /// - /// Specified the log message type + /// Debugging logs /// - public enum LogType - { - /// - /// Debugging logs - /// - Debug, - /// - /// Informational logs - /// - Info, - /// - /// Warning logs - /// - Warning, - /// - /// Error logs - /// - Error - } + Debug, + /// + /// Informational logs + /// + Info, + /// + /// Warning logs + /// + Warning, + /// + /// Error logs + /// + Error } diff --git a/NorthwoodLib/NativeMemory.cs b/NorthwoodLib/NativeMemory.cs index fd16557..c79d825 100644 --- a/NorthwoodLib/NativeMemory.cs +++ b/NorthwoodLib/NativeMemory.cs @@ -1,66 +1,66 @@ using System; using System.Runtime.InteropServices; -namespace NorthwoodLib +namespace NorthwoodLib; + +/// +/// Stores a reference to unmanaged memory allocated with and prevents it from leaking +/// +public sealed unsafe class NativeMemory : IDisposable { /// - /// Stores a reference to unmanaged memory allocated with and prevents it from leaking + /// Pointer to allocated memory /// - public sealed unsafe class NativeMemory : IDisposable - { - /// - /// Pointer to allocated memory - /// - public readonly IntPtr Data; - /// - /// Allocated memory length - /// - public readonly int Length; + public readonly void* Data; + /// + /// Allocated memory length + /// + public readonly int Length; - /// - /// Creates a with requested size - /// - /// Allocation size - public NativeMemory(int size) - { - Data = Marshal.AllocCoTaskMem(size); - Length = size; - if (Length > 0) - GC.AddMemoryPressure(Length); - } + /// + /// Creates a with requested size + /// + /// Allocation size + public NativeMemory(int size) + { + Data = (void*) Marshal.AllocCoTaskMem(size); + Length = size; + if (Length > 0) + GC.AddMemoryPressure(Length); + } - /// - /// Converts the to specified pointer type - /// - /// Pointer type - /// Pointer to allocated memory - public T* ToPointer() where T : unmanaged - { - return (T*) Data.ToPointer(); - } + /// + /// Converts the to specified pointer type + /// + /// Pointer type + /// Pointer to allocated memory + public T* ToPointer() where T : unmanaged + { + return (T*) Data; + } - private void Free() - { - Marshal.FreeCoTaskMem(Data); - if (Length > 0) - GC.RemoveMemoryPressure(Length); - } + private void Free() + { + Marshal.FreeCoTaskMem((nint) Data); + if (Length > 0) + GC.RemoveMemoryPressure(Length); + GC.KeepAlive(this); + } - /// - /// Frees allocated memory - /// - public void Dispose() - { - Free(); - GC.SuppressFinalize(this); - } + /// + /// Frees allocated memory + /// + public void Dispose() + { + Free(); + GC.SuppressFinalize(this); + } - /// - /// Frees allocated memory - /// - ~NativeMemory() - { - Free(); - } + /// + /// Frees allocated memory + /// + ~NativeMemory() + { + Free(); } } diff --git a/NorthwoodLib/NorthwoodLib.csproj b/NorthwoodLib/NorthwoodLib.csproj index 1b17b83..f55e4e7 100644 --- a/NorthwoodLib/NorthwoodLib.csproj +++ b/NorthwoodLib/NorthwoodLib.csproj @@ -31,7 +31,7 @@ - + diff --git a/NorthwoodLib/OperatingSystem.Unix.cs b/NorthwoodLib/OperatingSystem.Unix.cs index 17fc37b..fb09996 100644 --- a/NorthwoodLib/OperatingSystem.Unix.cs +++ b/NorthwoodLib/OperatingSystem.Unix.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using NorthwoodLib.Logging; @@ -21,18 +21,18 @@ private static bool TryGetUnixOs(out Version version, out string name) try { using (FileStream fs = new(osrelease, FileMode.Open, FileAccess.Read, FileShare.Read)) - using (StreamReader sr = new(fs)) - while (sr.ReadLine() is { } line) - { - line = line.Trim(); - const string prettyname = "PRETTY_NAME="; + using (StreamReader sr = new(fs)) + while (sr.ReadLine() is { } line) + { + line = line.Trim(); + const string prettyname = "PRETTY_NAME="; - if (line.StartsWith(prettyname)) - { - name = $"{line[prettyname.Length..].Replace("\"", "").Trim()} {Environment.OSVersion.VersionString}".Trim(); - return true; - } - } + if (!line.StartsWith(prettyname)) + continue; + + name = $"{line[prettyname.Length..].Replace("\"", "").Trim()} {version}".Trim(); + return true; + } } catch (FileNotFoundException) { diff --git a/NorthwoodLib/OperatingSystem.Windows.cs b/NorthwoodLib/OperatingSystem.Windows.cs index e6a7e28..9ea33da 100644 --- a/NorthwoodLib/OperatingSystem.Windows.cs +++ b/NorthwoodLib/OperatingSystem.Windows.cs @@ -1,9 +1,11 @@ -using System; +using System; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; +using System.Text; using NorthwoodLib.Logging; +using NorthwoodLib.Pools; namespace NorthwoodLib; @@ -11,6 +13,8 @@ public static unsafe partial class OperatingSystem { // ReSharper disable InconsistentNaming // ReSharper disable FieldCanBeMadeReadOnly.Local +#pragma warning disable IDE1006 +#pragma warning disable IDE0044 /// /// Managed version of /// @@ -73,10 +77,12 @@ private struct OSVERSIONINFO /// private byte wReserved; } +#pragma warning restore IDE0044 // ReSharper restore FieldCanBeMadeReadOnly.Local private const string Ntdll = "ntdll"; private const string Kernel32 = "kernel32"; + private const string RegistryPath = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion"; /// /// Returns version information about the currently running operating system. @@ -91,10 +97,10 @@ private struct OSVERSIONINFO /// Converts the specified NTSTATUS code to its equivalent system error code. /// /// - /// The NTSTATUS code to be converted. + /// The NTSTATUS code to be converted. /// Corresponding system error code. [DllImport(Ntdll, EntryPoint = "RtlNtStatusToDosError", ExactSpelling = true)] - private static extern int NtStatusToDosCode(uint Status); + private static extern int NtStatusToDosCode(uint status); /// /// Returns version information about the currently running operating system. @@ -117,11 +123,12 @@ private struct OSVERSIONINFO /// A nonzero value on success. This function fails if one of the input parameters is invalid. [DllImport(Kernel32, EntryPoint = "GetProductInfo", ExactSpelling = true)] private static extern uint GetProductInfo(uint idwOSMajorVersiond, uint dwOSMinorVersion, uint dwSpMajorVersion, uint dwSpMinorVersion, uint* pdwReturnedProductType); +#pragma warning restore IDE1006 // ReSharper restore InconsistentNaming private static bool TryGetWindowsVersion(out Version version, out string name) { - name = WineInfo.UsesWine ? $"{WineInfo.WineVersion} " : ""; + StringBuilder nameBuilder = StringBuilderPool.Shared.Rent(WineInfo.UsesWine ? $"{WineInfo.WineVersion} " : ""); version = null; bool server = false; @@ -130,8 +137,9 @@ private static bool TryGetWindowsVersion(out Version version, out string name) OSVERSIONINFO osVersionInfo = new() { - dwOSVersionInfoSize = (uint)sizeof(OSVERSIONINFO) + dwOSVersionInfoSize = (uint) sizeof(OSVERSIONINFO) }; + try { uint status = GetVersion(&osVersionInfo); @@ -157,20 +165,21 @@ private static bool TryGetWindowsVersion(out Version version, out string name) PlatformSettings.Log($"Failed to correct Windows version with GetVersionExW! {ex.Message}", LogType.Warning); } - try - { - if (!IsValidWindowsVersion(version) && TryCheckWindowsFileVersion(out Version fileVersion)) + if (!IsValidWindowsVersion(version)) + try { - PlatformSettings.Log( - $"Correcting system version using files from {version.PrintVersion()} to {fileVersion.Major}.{fileVersion.Minor}.{fileVersion.Build}", - LogType.Warning); - version = fileVersion; + if (TryCheckWindowsFileVersion(out Version fileVersion, GetWindowsRegistryBuild())) + { + PlatformSettings.Log( + $"Correcting system version using files from {version.PrintVersion()} to {fileVersion.Major}.{fileVersion.Minor}.{fileVersion.Build}", + LogType.Warning); + version = fileVersion; + } + } + catch (Exception ex) + { + PlatformSettings.Log($"Failed to correct Windows version using files! {ex.Message}", LogType.Warning); } - } - catch (Exception ex) - { - PlatformSettings.Log($"Failed to correct Windows version using files! {ex.Message}", LogType.Warning); - } if (!IsValidWindowsVersion(version)) version = Environment.OSVersion.Version; @@ -178,60 +187,79 @@ private static bool TryGetWindowsVersion(out Version version, out string name) if (string.IsNullOrWhiteSpace(servicePack)) servicePack = Environment.OSVersion.ServicePack; - if (servicePackVersion == null) - servicePackVersion = Environment.OSVersion.ServicePack switch - { - "Service Pack 1" => new Version(1, 0), - "Service Pack 2" => new Version(2, 0), - "Service Pack 3" => new Version(3, 0), - "Service Pack 4" => new Version(4, 0), - "Service Pack 5" => new Version(5, 0), - _ => null - }; + servicePackVersion ??= Environment.OSVersion.ServicePack switch + { + "Service Pack 1" => new Version(1, 0), + "Service Pack 2" => new Version(2, 0), + "Service Pack 3" => new Version(3, 0), + "Service Pack 4" => new Version(4, 0), + "Service Pack 5" => new Version(5, 0), + _ => null + }; // ReSharper disable HeapView.BoxingAllocation - name += $"Windows {ProcessWindowsVersion(version, server)}"; + nameBuilder.Append($"Windows {ProcessWindowsVersion(version, server, GetHklmString(RegistryPath, "DisplayVersion"))}"); string product = GetProductInfo(version, servicePackVersion); if (!string.IsNullOrWhiteSpace(product)) - name += $" {product}"; + nameBuilder.Append($" {product}"); if (!string.IsNullOrWhiteSpace(servicePack)) - name += $" {servicePack}"; + nameBuilder.Append($" {servicePack}"); int systemBits = Environment.Is64BitOperatingSystem ? 64 : 32; - int processBits = IntPtr.Size * 8; + int processBits = sizeof(nuint) * 8; - if (systemBits == processBits) - name += $" {systemBits}bit"; - else - name += $" {systemBits}bit Process: {processBits}bit"; - name = name.Trim(); + nameBuilder.Append(systemBits == processBits ? + $" {systemBits}bit" : + $" {systemBits}bit Process: {processBits}bit"); + + name = StringBuilderPool.Shared.ToStringReturn(nameBuilder).Trim(); // ReSharper restore HeapView.BoxingAllocation return true; } private static void ParseWindowsVersion(OSVERSIONINFO osVersionInfo, out Version version, out bool server, out string servicePack, out Version servicePackVersion) { - version = new Version((int)osVersionInfo.dwMajorVersion, (int)osVersionInfo.dwMinorVersion, (int)osVersionInfo.dwBuildNumber); + version = new Version((int) osVersionInfo.dwMajorVersion, (int) osVersionInfo.dwMinorVersion, (int) osVersionInfo.dwBuildNumber); // from https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfoexw server = osVersionInfo.wProductType != 1; - servicePack = new string((char*)osVersionInfo.szCSDVersion); + servicePack = new string((char*) osVersionInfo.szCSDVersion); servicePackVersion = new Version(osVersionInfo.wServicePackMajor, osVersionInfo.wServicePackMinor); } + /// + /// Attempts to retrieve the Windows Build from registry + /// + /// Parsed value if successful, otherwise + internal static int? GetWindowsRegistryBuild() + { + return int.TryParse(GetHklmString(RegistryPath, "CurrentBuildNumber"), out int build) && build > 0 + ? build + : int.TryParse(GetHklmString(RegistryPath, "CurrentBuild"), out build) && build > 0 + ? build + : null; + } + /// /// Fetches the Windows version from system files /// /// System version - /// True if successful, false otherwise - internal static bool TryCheckWindowsFileVersion(out Version version) + /// Real build value if known + /// if successful, otherwise + internal static bool TryCheckWindowsFileVersion(out Version version, int? realBuild) { - foreach (string file in new[] - { - "cmd.exe", "conhost.exe", "dxdiag.exe", "msinfo32.exe", "msconfig.exe", "mspaint.exe", "notepad.exe", "winver.exe" - }) + foreach (string file in (ReadOnlySpan) [ + "cmd.exe", + "conhost.exe", + "dxdiag.exe", + "msinfo32.exe", + "msconfig.exe", + "mspaint.exe", + "notepad.exe", + "winver.exe" + ]) { FileVersionInfo fileVersionInfo; @@ -250,10 +278,10 @@ internal static bool TryCheckWindowsFileVersion(out Version version) continue; } - version = new Version(fileVersionInfo.FileMajorPart, fileVersionInfo.FileMinorPart, fileVersionInfo.FileBuildPart); + version = new Version(fileVersionInfo.FileMajorPart, fileVersionInfo.FileMinorPart, realBuild ?? fileVersionInfo.FileBuildPart); if (!IsValidWindowsVersion(version)) - version = new Version(fileVersionInfo.ProductMajorPart, fileVersionInfo.ProductMinorPart, fileVersionInfo.ProductBuildPart); + version = new Version(fileVersionInfo.ProductMajorPart, fileVersionInfo.ProductMinorPart, realBuild ?? fileVersionInfo.ProductBuildPart); if (IsValidWindowsVersion(version)) return true; @@ -279,55 +307,91 @@ private static string PrintVersion(this Version version) return version == null ? "(null)" : $"{version.Major}.{version.Minor}{(version.Build > 0 ? $".{version.Build}" : "")}"; } - private static string ProcessWindowsVersion(Version version, bool server) + private static string ProcessWindowsVersion(Version version, bool server, string displayVersion) { switch (version?.Major) { case 10 when version.Minor == 0 && server: - return version.Build switch { - 14393 => "Server 2016 1607", - 16299 => "Server 2016 1709", - 17134 => "Server 2016 1803", - 17763 => "Server 2019 1809", - 18362 => "Server 2019 1903", - 18363 => "Server 2019 1909", - 19041 => "Server 2019 2004", - 19042 => "Server 2019 20H2", - 20348 => "Server 2022 21H2", - _ => $"Server {(version.Build < 20000 ? version.Build < 17677 ? 2016 : 2019 : 2022)} build {version.Build}" - }; + string main = version.Build switch + { + 25398 => "Annual Channel", + > 26040 => "2025", + > 20000 => "2022", + > 17677 => "2019", + _ => "2016" + }; + string patch = displayVersion ?? version.Build switch + { + 14393 => "1607", + 16299 => "1709", + 17134 => "1803", + 17763 => "1809", + 18362 => "1903", + 18363 => "1909", + 19041 => "2004", + 19042 => "20H2", + 20348 => "21H2", + 25398 => "23H2", + 26040 => "23H2", + 26100 => "24H2", + _ => $"build {version.Build}" + }; + return $"Server {main} {patch}"; + } case 10 when version.Minor == 0: - return version.Build switch { - 10240 => "10 1507", - 10586 => "10 1511", - 14393 => "10 1607", - 15063 => "10 1703", - 16299 => "10 1709", - 17134 => "10 1803", - 17763 => "10 1809", - 18362 => "10 1903", - 18363 => "10 1909", - 19041 => "10 2004", - 19042 => "10 20H2", - 19043 => "10 21H1", - 19044 => "10 21H2", - 19045 => "10 22H2", - 22000 => "11 21H2", - 22621 => "11 22H2", - 22631 => "11 23H2", - _ => $"{(version.Build < 22000 ? 10 : 11)} build {version.Build}" - }; + string main = version.Build switch + { + > 22000 => "11", + _ => "10" + }; + string patch = displayVersion ?? version.Build switch + { + 10240 => "1507", + 10586 => "1511", + 14393 => "1607", + 15063 => "1703", + 16299 => "1709", + 17134 => "1803", + 17763 => "1809", + 18362 => "1903", + 18363 => "1909", + 19041 => "2004", + 19042 => "20H2", + 19043 => "21H1", + 19044 => "21H2", + 19045 => "22H2", + 22000 => "21H2", + 22621 => "22H2", + 22631 => "23H2", + 26100 => "24H2", + _ => $"build {version.Build}" + }; + return $"{main} {patch}"; + } + case 6 when server: + { + switch (version.Minor) + { + case 3: return "Server 2012 R2"; + case 2: return "Server 2012"; + case 1: return "Server 2008 R2"; + case 0: return "Server 2008"; + } + break; + } case 6: - switch (version.Minor) { - case 3: return server ? "Server 2012 R2" : "8.1"; - case 2: return server ? "Server 2012" : "8"; - case 1: return server ? "Server 2008 R2" : "7"; - case 0: return server ? "Server 2008" : "Vista"; + switch (version.Minor) + { + case 3: return "8.1"; + case 2: return "8"; + case 1: return "7"; + case 0: return "Vista"; + } + break; } - break; } return version.PrintVersion(); @@ -456,4 +520,26 @@ private static string GetProductInfo(Version osVersion, Version spVersion) _ => $"0x{pdwReturnedProductType:X8}" }; } + + private static string GetHklmString(string key, string value) + { + [DllImport("Advapi32", EntryPoint = "RegGetValueW", ExactSpelling = true)] + static extern int RegGetValue(nint hkey, ushort* key, ushort* value, uint flags, uint* type, void* data, uint* dataLength); + + const nint hklm = -2147483646; + const uint sz = 0x00000002; + + const int bufferSize = 8; + ushort* data = stackalloc ushort[bufferSize]; + + uint dataSize = bufferSize * sizeof(ushort); + fixed (char* keyPointer = key) + fixed (char* valuePointer = value) + { + if (RegGetValue(hklm, (ushort*) keyPointer, (ushort*) valuePointer, sz, null, data, &dataSize) != 0) + return null; + } + + return new string((char*) data); + } } diff --git a/NorthwoodLib/OperatingSystem.cs b/NorthwoodLib/OperatingSystem.cs index 6064fc9..2a4993b 100644 --- a/NorthwoodLib/OperatingSystem.cs +++ b/NorthwoodLib/OperatingSystem.cs @@ -1,44 +1,43 @@ using System; using System.Runtime.InteropServices; -namespace NorthwoodLib +namespace NorthwoodLib; + +/// +/// Provides data about currently used Operating System +/// +public static partial class OperatingSystem { /// - /// Provides data about currently used Operating System + /// Informs if code uses P/Invokes to obtain the data /// - public static partial class OperatingSystem - { - /// - /// Informs if code uses P/Invokes to obtain the data - /// - public static readonly bool UsesNativeData; + public static readonly bool UsesNativeData; - /// - /// Informs if user uses Wine. User can hide Wine so don't rely on this for uses other than diagnostic usage - /// - [Obsolete("Use WineInfo.UsesWine instead")] - public static readonly bool UsesWine; + /// + /// Informs if user uses Wine. User can hide Wine so don't rely on this for uses other than diagnostic usage + /// + [Obsolete("Use WineInfo.UsesWine instead")] + public static readonly bool UsesWine; - /// - /// Used Operating System - /// - public static readonly Version Version; + /// + /// Used Operating System + /// + public static readonly Version Version; - /// - /// Returns human readable description of the used Used Operating System - /// - public static readonly string VersionString; + /// + /// Returns human readable description of the used Operating System + /// + public static readonly string VersionString; - static OperatingSystem() - { - UsesNativeData = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? - TryGetWindowsVersion(out Version version, out string os) : - TryGetUnixOs(out version, out os); - Version = UsesNativeData ? version : Environment.OSVersion.Version; - VersionString = UsesNativeData ? os : $"{Environment.OSVersion.VersionString} {Environment.OSVersion.ServicePack}".Trim(); + static OperatingSystem() + { + UsesNativeData = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? + TryGetWindowsVersion(out Version version, out string os) : + TryGetUnixOs(out version, out os); + Version = UsesNativeData ? version : Environment.OSVersion.Version; + VersionString = UsesNativeData ? os : $"{Environment.OSVersion.VersionString} {Environment.OSVersion.ServicePack}".Trim(); #pragma warning disable 618 - UsesWine = WineInfo.UsesWine; + UsesWine = WineInfo.UsesWine; #pragma warning restore 618 - } } } diff --git a/NorthwoodLib/PlatformSettings.cs b/NorthwoodLib/PlatformSettings.cs index 2c5f8a9..5378547 100644 --- a/NorthwoodLib/PlatformSettings.cs +++ b/NorthwoodLib/PlatformSettings.cs @@ -1,33 +1,32 @@ using System; using NorthwoodLib.Logging; -namespace NorthwoodLib +namespace NorthwoodLib; + +/// +/// Stores static data provided by the software using the library +/// +public static class PlatformSettings { /// - /// Stores static data provided by the software using the library + /// Current library version /// - public static class PlatformSettings - { - /// - /// Current library version - /// - internal const string VersionConst = "1.3.0"; + internal const string VersionConst = "1.3.1"; - /// - /// Returns the library version - /// - public static string Version => VersionConst; + /// + /// Returns the library version + /// + public static string Version => VersionConst; - /// - /// Logs all messages from the code - /// - public static event Action Logged; + /// + /// Logs all messages from the code + /// + public static event Action Logged; - /// - /// Fires with provided data - /// - /// Message text - /// Message type - internal static void Log(string message, LogType type) => Logged?.Invoke(message, type); - } + /// + /// Fires with provided data + /// + /// Message text + /// Message type + internal static void Log(string message, LogType type) => Logged?.Invoke(message, type); } diff --git a/NorthwoodLib/Pools/HashSetPool.cs b/NorthwoodLib/Pools/HashSetPool.cs index 92a0679..5f6847a 100644 --- a/NorthwoodLib/Pools/HashSetPool.cs +++ b/NorthwoodLib/Pools/HashSetPool.cs @@ -2,79 +2,78 @@ using System.Collections.Concurrent; using System.Collections.Generic; -namespace NorthwoodLib.Pools +namespace NorthwoodLib.Pools; + +/// +/// Returns pooled +/// +/// Element type +public sealed class HashSetPool : IPool> { /// - /// Returns pooled + /// Gets a shared instance /// - /// Element type - public sealed class HashSetPool : IPool> - { - /// - /// Gets a shared instance - /// - public static readonly HashSetPool Shared = new(); + public static readonly HashSetPool Shared = new(); - private readonly ConcurrentQueue> _pool = new(); + private readonly ConcurrentQueue> _pool = new(); - /// - /// Gives a pooled - /// - /// from the pool - public HashSet Rent() - { - return _pool.TryDequeue(out HashSet set) ? set : new HashSet(512); - } + /// + /// Gives a pooled + /// + /// from the pool + public HashSet Rent() + { + return _pool.TryDequeue(out HashSet set) ? set : new HashSet(512); + } - /// - /// Gives a pooled with provided capacity - /// - /// Requested capacity - /// from the pool - public HashSet Rent(int capacity) + /// + /// Gives a pooled with provided capacity + /// + /// Requested capacity + /// from the pool + public HashSet Rent(int capacity) + { + // ReSharper disable once ConvertIfStatementToReturnStatement + if (_pool.TryDequeue(out HashSet set)) { - // ReSharper disable once ConvertIfStatementToReturnStatement - if (_pool.TryDequeue(out HashSet set)) - { #if NETSTANDARD - set.EnsureCapacity(capacity); + set.EnsureCapacity(capacity); #endif - return set; - } - - return new HashSet(Math.Max(capacity, 512)); + return set; } - /// - /// Gives a pooled with initial content - /// - /// Initial content - /// from the pool - public HashSet Rent(IEnumerable enumerable) - { - if (_pool.TryDequeue(out HashSet set)) - { - if (enumerable is IReadOnlyList list) - for (int i = 0; i < list.Count; i++) - set.Add(list[i]); - else - foreach (T t in enumerable) - set.Add(t); + return new HashSet(Math.Max(capacity, 512)); + } - return set; - } + /// + /// Gives a pooled with initial content + /// + /// Initial content + /// from the pool + public HashSet Rent(IEnumerable enumerable) + { + if (_pool.TryDequeue(out HashSet set)) + { + if (enumerable is IReadOnlyList list) + for (int i = 0; i < list.Count; i++) + set.Add(list[i]); + else + foreach (T t in enumerable) + set.Add(t); - return new HashSet(enumerable); + return set; } - /// - /// Returns a to the pool - /// - /// Returned - public void Return(HashSet set) - { - set.Clear(); - _pool.Enqueue(set); - } + return [.. enumerable]; + } + + /// + /// Returns a to the pool + /// + /// Returned + public void Return(HashSet set) + { + set.Clear(); + _pool.Enqueue(set); } } diff --git a/NorthwoodLib/Pools/IPool.cs b/NorthwoodLib/Pools/IPool.cs index d9cceae..23071d9 100644 --- a/NorthwoodLib/Pools/IPool.cs +++ b/NorthwoodLib/Pools/IPool.cs @@ -1,20 +1,19 @@ -namespace NorthwoodLib.Pools +namespace NorthwoodLib.Pools; + +/// +/// Provides pooled instances of requested type +/// +/// Pooled type +public interface IPool where T : class { /// - /// Provides pooled instances of requested type + /// Returns a pooled instance of /// - /// Pooled type - public interface IPool where T : class - { - /// - /// Returns a pooled instance of - /// - /// from the pool - T Rent(); - /// - /// Returns a to the pool - /// - /// Pooled object - void Return(T obj); - } + /// from the pool + T Rent(); + /// + /// Returns a to the pool + /// + /// Pooled object + void Return(T obj); } diff --git a/NorthwoodLib/Pools/ListPool.cs b/NorthwoodLib/Pools/ListPool.cs index 3f5c3ca..4a3dc55 100644 --- a/NorthwoodLib/Pools/ListPool.cs +++ b/NorthwoodLib/Pools/ListPool.cs @@ -2,71 +2,70 @@ using System.Collections.Concurrent; using System.Collections.Generic; -namespace NorthwoodLib.Pools +namespace NorthwoodLib.Pools; + +/// +/// Returns pooled +/// +/// Element type +public sealed class ListPool : IPool> { /// - /// Returns pooled + /// Gets a shared instance /// - /// Element type - public sealed class ListPool : IPool> - { - /// - /// Gets a shared instance - /// - public static readonly ListPool Shared = new(); + public static readonly ListPool Shared = new(); - private readonly ConcurrentQueue> _pool = new(); + private readonly ConcurrentQueue> _pool = new(); - /// - /// Gives a pooled - /// - /// from the pool - public List Rent() - { - return _pool.TryDequeue(out List list) ? list : new List(512); - } + /// + /// Gives a pooled + /// + /// from the pool + public List Rent() + { + return _pool.TryDequeue(out List list) ? list : new List(512); + } - /// - /// Gives a pooled with provided capacity - /// - /// Requested capacity - /// from the pool - public List Rent(int capacity) + /// + /// Gives a pooled with provided capacity + /// + /// Requested capacity + /// from the pool + public List Rent(int capacity) + { + if (_pool.TryDequeue(out List list)) { - if (_pool.TryDequeue(out List list)) - { - if (list.Capacity < capacity) - list.Capacity = capacity; - return list; - } - - return new List(Math.Max(capacity, 512)); + if (list.Capacity < capacity) + list.Capacity = capacity; + return list; } - /// - /// Gives a pooled with initial content - /// - /// Initial content - /// from the pool - public List Rent(IEnumerable enumerable) - { - if (_pool.TryDequeue(out List list)) - { - list.AddRange(enumerable); - return list; - } - - return new List(enumerable); - } + return new List(Math.Max(capacity, 512)); + } - /// - /// Returns a to the pool - /// - /// Returned - public void Return(List list) + /// + /// Gives a pooled with initial content + /// + /// Initial content + /// from the pool + public List Rent(IEnumerable enumerable) + { + if (_pool.TryDequeue(out List list)) { - list.Clear(); - _pool.Enqueue(list); + list.AddRange(enumerable); + return list; } + + return [..enumerable]; + } + + /// + /// Returns a to the pool + /// + /// Returned + public void Return(List list) + { + list.Clear(); + _pool.Enqueue(list); } } diff --git a/NorthwoodLib/Pools/StringBuilderPool.cs b/NorthwoodLib/Pools/StringBuilderPool.cs index 2f3eb50..73ecc64 100644 --- a/NorthwoodLib/Pools/StringBuilderPool.cs +++ b/NorthwoodLib/Pools/StringBuilderPool.cs @@ -2,83 +2,82 @@ using System.Collections.Concurrent; using System.Text; -namespace NorthwoodLib.Pools +namespace NorthwoodLib.Pools; + +/// +/// Returns pooled +/// +public sealed class StringBuilderPool : IPool { /// - /// Returns pooled + /// Gets a shared instance /// - public sealed class StringBuilderPool : IPool - { - /// - /// Gets a shared instance - /// - public static readonly StringBuilderPool Shared = new(); + public static readonly StringBuilderPool Shared = new(); - private readonly ConcurrentQueue _pool = new(); + private readonly ConcurrentQueue _pool = new(); - /// - /// Gives a pooled - /// - /// from the pool - public StringBuilder Rent() - { - return _pool.TryDequeue(out StringBuilder stringBuilder) ? stringBuilder : new StringBuilder(512); - } + /// + /// Gives a pooled + /// + /// from the pool + public StringBuilder Rent() + { + return _pool.TryDequeue(out StringBuilder stringBuilder) ? stringBuilder : new StringBuilder(512); + } - /// - /// Gives a pooled with provided capacity - /// - /// Requested capacity - /// from the pool - public StringBuilder Rent(int capacity) + /// + /// Gives a pooled with provided capacity + /// + /// Requested capacity + /// from the pool + public StringBuilder Rent(int capacity) + { + if (_pool.TryDequeue(out StringBuilder stringBuilder)) { - if (_pool.TryDequeue(out StringBuilder stringBuilder)) - { - if (stringBuilder.Capacity < capacity) - stringBuilder.Capacity = capacity; - return stringBuilder; - } - - return new StringBuilder(Math.Max(capacity, 512)); + if (stringBuilder.Capacity < capacity) + stringBuilder.Capacity = capacity; + return stringBuilder; } - /// - /// Gives a pooled with initial content - /// - /// Initial content - /// from the pool - public StringBuilder Rent(string text) - { - if (_pool.TryDequeue(out StringBuilder stringBuilder)) - { - stringBuilder.Append(text); - return stringBuilder; - } - - return new StringBuilder(text, 512); - } + return new StringBuilder(Math.Max(capacity, 512)); + } - /// - /// Returns a to the pool - /// - /// Returned - public void Return(StringBuilder stringBuilder) + /// + /// Gives a pooled with initial content + /// + /// Initial content + /// from the pool + public StringBuilder Rent(string text) + { + if (_pool.TryDequeue(out StringBuilder stringBuilder)) { - stringBuilder.Clear(); - _pool.Enqueue(stringBuilder); + stringBuilder.Append(text); + return stringBuilder; } - /// - /// Returns the content of a and returns it to the pool - /// - /// Returned - /// The content of the - public string ToStringReturn(StringBuilder stringBuilder) - { - string value = stringBuilder.ToString(); - stringBuilder.Clear(); - _pool.Enqueue(stringBuilder); - return value; - } + return new StringBuilder(text, 512); + } + + /// + /// Returns a to the pool + /// + /// Returned + public void Return(StringBuilder stringBuilder) + { + stringBuilder.Clear(); + _pool.Enqueue(stringBuilder); + } + + /// + /// Returns the content of a and returns it to the pool + /// + /// Returned + /// The content of the + public string ToStringReturn(StringBuilder stringBuilder) + { + string value = stringBuilder.ToString(); + stringBuilder.Clear(); + _pool.Enqueue(stringBuilder); + return value; } } diff --git a/NorthwoodLib/StringUtils.cs b/NorthwoodLib/StringUtils.cs index 3a89e5e..0ca32c4 100644 --- a/NorthwoodLib/StringUtils.cs +++ b/NorthwoodLib/StringUtils.cs @@ -4,138 +4,139 @@ using System.Text.RegularExpressions; using NorthwoodLib.Pools; -namespace NorthwoodLib +namespace NorthwoodLib; + +/// +/// Utility methods for +/// +public static class StringUtils { + private static readonly Regex UnicodeRegex = new(@"[^\u0000-\u007F]", RegexOptions.Compiled); + private static readonly Regex TagRegex = new("<.*?>", RegexOptions.Compiled); + /// - /// Utility methods for + /// Truncates a string to the last occurance of within characters if it's longer than it /// - public static class StringUtils + /// Processed text + /// Maximum size + /// Checked character + /// Truncated + public static string TruncateToLast(this string text, int maxSize, char character) { - private static readonly Regex UnicodeRegex = new(@"[^\u0000-\u007F]", RegexOptions.Compiled); - private static readonly Regex TagRegex = new("<.*?>", RegexOptions.Compiled); - - /// - /// Truncates a string to the last occurance of within characters if it's longer than it - /// - /// Processed text - /// Maximum size - /// Checked character - /// Truncated - public static string TruncateToLast(this string text, int maxSize, char character) - { - int index = text.LastIndexOf(character, maxSize - 1, maxSize); - return text.Length <= maxSize ? text : text[..(index == -1 ? maxSize : index + 1)]; - } + int index = text.LastIndexOf(character, maxSize - 1, maxSize); + return text.Length <= maxSize ? text : text[..(index == -1 ? maxSize : index + 1)]; + } - /// - /// Truncates a string to the last occurance of within characters if it's longer than it - /// - /// Processed text - /// Maximum size - /// Checked string - /// String comparison - /// Truncated - public static string TruncateToLast(this string text, int maxSize, string str, StringComparison comparison = StringComparison.Ordinal) - { - int index = text.LastIndexOf(str, maxSize - 1, maxSize, comparison); - return text.Length <= maxSize ? text : text[..(index == -1 ? maxSize : index + str.Length)]; - } + /// + /// Truncates a string to the last occurance of within characters if it's longer than it + /// + /// Processed text + /// Maximum size + /// Checked string + /// String comparison + /// Truncated + public static string TruncateToLast(this string text, int maxSize, string str, StringComparison comparison = StringComparison.Ordinal) + { + int index = text.LastIndexOf(str, maxSize - 1, maxSize, comparison); + return text.Length <= maxSize ? text : text[..(index == -1 ? maxSize : index + str.Length)]; + } - /// - /// Converts a text to Base64 encoded UTF8 - /// - /// Processed text - /// Base64 encoded UTF8 of - public static string Base64Encode(string plainText) - { - byte[] buffer = ArrayPool.Shared.Rent(Encoding.UTF8.GetMaxByteCount(plainText.Length)); - int plainTextByteCount = Encoding.UTF8.GetBytes(plainText, 0, plainText.Length, buffer, 0); - string result = Convert.ToBase64String(buffer, 0, plainTextByteCount); - ArrayPool.Shared.Return(buffer); - return result; - } + /// + /// Converts a text to Base64 encoded UTF8 + /// + /// Processed text + /// Base64 encoded UTF8 of + public static string Base64Encode(string plainText) + { + byte[] buffer = ArrayPool.Shared.Rent(Encoding.UTF8.GetMaxByteCount(plainText.Length)); + int plainTextByteCount = Encoding.UTF8.GetBytes(plainText, 0, plainText.Length, buffer, 0); + string result = Convert.ToBase64String(buffer, 0, plainTextByteCount); + ArrayPool.Shared.Return(buffer); + return result; + } - /// - /// Converts Base64 encoded UTF8 data to text - /// - /// Base64 encoded UTF8 data - /// Converted text - public static string Base64Decode(string base64EncodedData) - { - byte[] base64EncodedBytes = Convert.FromBase64String(base64EncodedData); - return Encoding.UTF8.GetString(base64EncodedBytes); - } + /// + /// Converts Base64 encoded UTF8 data to text + /// + /// Base64 encoded UTF8 data + /// Converted text + public static string Base64Decode(string base64EncodedData) + { + byte[] base64EncodedBytes = Convert.FromBase64String(base64EncodedData); + return Encoding.UTF8.GetString(base64EncodedBytes); + } - /// - /// Returns a value indicating whether a specified character occurs within this string. - /// - /// Checked string - /// The character to seek. - /// if the value parameter occurs within this string; otherwise, . - public static bool Contains(this string s, char value) - { - return s.IndexOf(value) >= 0; - } + /// + /// Returns a value indicating whether a specified character occurs within this string. + /// + /// Checked string + /// The character to seek. + /// if the value parameter occurs within this string; otherwise, . + [Obsolete("Use the runtime provided API")] + public static bool Contains(this string s, char value) + { + return s.Contains(value); + } - /// - /// Returns a value indicating whether a specified string occurs within this string, using the specified comparison rules. - /// - /// Checked string - /// The string to seek. - /// One of the enumeration values that specifies the rules to use in the comparison. - /// if the occurs within this string, or if is the empty string (""); otherwise, . - public static bool Contains(this string s, string value, StringComparison comparison) - { - return s.IndexOf(value, comparison) >= 0; - } + /// + /// Returns a value indicating whether a specified string occurs within this string, using the specified comparison rules. + /// + /// Checked string + /// The string to seek. + /// One of the enumeration values that specifies the rules to use in the comparison. + /// if the occurs within this string, or if is the empty string (""); otherwise, . + [Obsolete("Use the runtime provided API")] + public static bool Contains(this string s, string value, StringComparison comparison) + { + return s.Contains(value, comparison); + } - /// - /// Replaces Unicode characters in a string. - /// - /// Input string - /// String to replace Unicode characters with - /// Processed string - public static string StripUnicodeCharacters(string input, string replacement = "") - { - return UnicodeRegex.Replace(input, replacement); - } + /// + /// Replaces Unicode characters in a string. + /// + /// Input string + /// String to replace Unicode characters with + /// Processed string + public static string StripUnicodeCharacters(string input, string replacement = "") + { + return UnicodeRegex.Replace(input, replacement); + } - /// - /// Removes special characters from provided text - /// - /// Processed text - /// Filtered text - public static string RemoveSpecialCharacters(string str) - { - StringBuilder sb = StringBuilderPool.Shared.Rent(str.Length); - foreach (char c in str) - if (c is >= '0' and <= '9' or >= 'A' and <= 'Z' or >= 'a' and <= 'z' or ' ' or '-' or '.' or ',' or '_') - sb.Append(c); - string text = sb.Length == str.Length ? str : sb.ToString(); - StringBuilderPool.Shared.Return(sb); + /// + /// Removes special characters from provided text + /// + /// Processed text + /// Filtered text + public static string RemoveSpecialCharacters(string str) + { + StringBuilder sb = StringBuilderPool.Shared.Rent(str.Length); + foreach (char c in str) + if (c is >= '0' and <= '9' or >= 'A' and <= 'Z' or >= 'a' and <= 'z' or ' ' or '-' or '.' or ',' or '_') + sb.Append(c); + string text = sb.Length == str.Length ? str : sb.ToString(); + StringBuilderPool.Shared.Return(sb); - return text; - } + return text; + } - /// - /// Removes a tag from a string - /// - /// Processed text - /// Removed tag - /// Filtered text - public static string StripTag(string input, string tag) - { - return Regex.Replace(input, $"<.*?{tag}.*?>", string.Empty); - } + /// + /// Removes a tag from a string + /// + /// Processed text + /// Removed tag + /// Filtered text + public static string StripTag(string input, string tag) + { + return Regex.Replace(input, $"<.*?{tag}.*?>", string.Empty); + } - /// - /// Removes tags from a string - /// - /// Processed text - /// Filtered text - public static string StripTags(string input) - { - return TagRegex.Replace(input, string.Empty); - } + /// + /// Removes tags from a string + /// + /// Processed text + /// Filtered text + public static string StripTags(string input) + { + return TagRegex.Replace(input, string.Empty); } } diff --git a/NorthwoodLib/WineInfo.cs b/NorthwoodLib/WineInfo.cs index f3d8905..5debc45 100644 --- a/NorthwoodLib/WineInfo.cs +++ b/NorthwoodLib/WineInfo.cs @@ -1,160 +1,168 @@ using System; using System.IO; +using System.IO.MemoryMappedFiles; using System.Runtime.InteropServices; using NorthwoodLib.Logging; -namespace NorthwoodLib +namespace NorthwoodLib; + +/// +/// Detects usage of Wine and informs about its version +/// +public static unsafe class WineInfo { + private const string Ntdll = "ntdll"; + + /// + /// Returns used Wine version + /// + /// Used Wine version + [DllImport(Ntdll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "wine_get_version")] + private static extern byte* GetWineVersion(); + + /// + /// Returns used Wine build + /// + /// Used Wine build + [DllImport(Ntdll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "wine_get_build_id")] + private static extern byte* GetWineBuildId(); + + /// + /// Returns Wine host + /// + /// Used Wine host + [DllImport(Ntdll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "wine_get_host_version")] + private static extern void GetWineHostVersion(byte** sysname, byte** release); + + /// + /// Informs if user uses Wine. Detection isn't fully reliable so don't rely on this for anything but diagnostics + /// + public static readonly bool UsesWine; + + /// + /// Informs if user uses Proton. Detection isn't fully reliable so don't rely on this for anything but diagnostics + /// + public static readonly bool UsesProton; + /// - /// Detects usage of Wine and informs about its version + /// Returns used Wine Version /// - public static unsafe class WineInfo + public static readonly string WineVersion; + + /// + /// Returns used Wine Staging patches + /// + [Obsolete("Always returns null since Wine removed the API")] + public static readonly string WinePatches = null; + + /// + /// Returns used Wine host + /// + public static readonly string WineHost; + + static WineInfo() { - private const string Ntdll = "ntdll"; - - /// - /// Returns used Wine version - /// - /// Used Wine version - [DllImport(Ntdll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "wine_get_version")] - private static extern byte* GetWineVersion(); - - /// - /// Returns used Wine build - /// - /// Used Wine build - [DllImport(Ntdll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "wine_get_build_id")] - private static extern byte* GetWineBuildId(); - - /// - /// Returns Wine host - /// - /// Used Wine host - [DllImport(Ntdll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "wine_get_host_version")] - private static extern void GetWineHostVersion(byte** sysname, byte** release); - - /// - /// Informs if user uses Wine. Detection isn't fully reliable so don't rely on this for anything but diagnostics - /// - public static readonly bool UsesWine; - - /// - /// Informs if user uses Proton. Detection isn't fully reliable so don't rely on this for anything but diagnostics - /// - public static readonly bool UsesProton; - - /// - /// Returns used Wine Version - /// - public static readonly string WineVersion; - - /// - /// Returns used Wine Staging patches - /// - [Obsolete("Always returns null since Wine removed the API")] - public static readonly string WinePatches = null; - - /// - /// Returns used Wine host - /// - public static readonly string WineHost; - - static WineInfo() + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // Wine will always report as Windows - UsesWine = false; - WineVersion = null; - return; - } + // Wine will always report as Windows + UsesWine = false; + WineVersion = null; + return; + } - ReadOnlySpan kernelBase = default; + static MemoryMappedFile GetKernelBaseMemoryMap() + { try { - kernelBase = File.ReadAllBytes(Path.Combine(Environment.SystemDirectory, "kernelbase.dll")); - - // avoid parsing huge files - if (kernelBase.Length > 20 * 1024 * 1024) - kernelBase = default; + return MemoryMappedFile.CreateFromFile(Path.Combine(Environment.SystemDirectory, "kernelbase.dll"), + FileMode.Open, null, 0, MemoryMappedFileAccess.Read); } catch { - // ignored + return null; } + } - try + using MemoryMappedFile kernelBase = GetKernelBaseMemoryMap(); + using MemoryMappedViewAccessor kernelAccessor = kernelBase?.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read); + byte* bytes = null; + kernelAccessor?.SafeMemoryMappedViewHandle.AcquirePointer(ref bytes); + ReadOnlySpan kernelBytes = kernelBase == null ? default : + new ReadOnlySpan(bytes, (int) Math.Min(kernelAccessor.SafeMemoryMappedViewHandle.ByteLength, 20 * 1024 * 1024)); + + try + { + // this will fail on Windows or when user disables exporting wine_get_version + string wineVersion = Marshal.PtrToStringAnsi((nint) GetWineVersion()); + + if (string.IsNullOrWhiteSpace(wineVersion)) { - // this will fail on Windows or when user disables exporting wine_get_version - string wineVersion = Marshal.PtrToStringAnsi((nint)GetWineVersion()); - - if (string.IsNullOrWhiteSpace(wineVersion)) - { - UsesWine = false; - WineVersion = null; - return; - } - - UsesWine = true; - WineVersion = $"Wine {wineVersion}"; + UsesWine = false; + WineVersion = null; + return; } - catch (Exception ex) + + UsesWine = true; + WineVersion = $"Wine {wineVersion}"; + } + catch (Exception ex) + { + if (kernelBytes.IndexOf("Wine "u8) < 0) { - if (kernelBase.IndexOf("Wine "u8) < 0) - { - // not using Wine, ignore - PlatformSettings.Log($"Wine not detected: {ex.Message}", LogType.Debug); - UsesWine = false; - WineVersion = null; - return; - } - - // Wine hidden in winecfg - PlatformSettings.Log("Detected hidden Wine", LogType.Debug); - UsesWine = true; - WineVersion = "Wine Hidden"; + // not using Wine, ignore + PlatformSettings.Log($"Wine not detected: {ex.Message}", LogType.Debug); + UsesWine = false; + WineVersion = null; + return; } - try - { - string wineBuild = Marshal.PtrToStringAnsi((nint)GetWineBuildId()); + // Wine hidden in winecfg + PlatformSettings.Log("Detected hidden Wine", LogType.Debug); + UsesWine = true; + WineVersion = "Wine Hidden"; + } - if (!string.IsNullOrWhiteSpace(wineBuild)) - WineVersion = wineBuild; - } - catch (Exception ex) - { - PlatformSettings.Log($"Wine build not detected: {ex.Message}", LogType.Debug); - } + try + { + string wineBuild = Marshal.PtrToStringAnsi((nint) GetWineBuildId()); - UsesProton = kernelBase.IndexOf("Proton "u8) >= 0; - if (UsesProton) - WineVersion = $"Proton {WineVersion}"; + if (!string.IsNullOrWhiteSpace(wineBuild)) + WineVersion = wineBuild; + } + catch (Exception ex) + { + PlatformSettings.Log($"Wine build not detected: {ex.Message}", LogType.Debug); + } - try - { - byte* sysnamePtr = null; - byte* releasePtr = null; - GetWineHostVersion(&sysnamePtr, &releasePtr); - string sysname = Marshal.PtrToStringAnsi((nint)sysnamePtr)?.Trim() ?? ""; - string release = Marshal.PtrToStringAnsi((nint)releasePtr)?.Trim() ?? ""; - if (sysname != "" || release != "") - { - WineHost = sysname; - if (!string.IsNullOrWhiteSpace(release)) - WineHost += $" {release}"; - WineHost = WineHost.Trim(); - WineVersion += $" Host: {WineHost}"; - } - } - catch (Exception ex) + UsesProton = kernelBytes.IndexOf("Proton "u8) >= 0; + if (UsesProton) + WineVersion = $"Proton based on {WineVersion}"; + + kernelAccessor?.SafeMemoryMappedViewHandle.ReleasePointer(); + + try + { + byte* sysnamePtr = null; + byte* releasePtr = null; + GetWineHostVersion(&sysnamePtr, &releasePtr); + string sysname = Marshal.PtrToStringAnsi((nint) sysnamePtr)?.Trim() ?? ""; + string release = Marshal.PtrToStringAnsi((nint) releasePtr)?.Trim() ?? ""; + if (sysname != "" || release != "") { - PlatformSettings.Log($"Wine host not detected: {ex.Message}", LogType.Debug); + WineHost = sysname; + if (!string.IsNullOrWhiteSpace(release)) + WineHost += $" {release}"; + WineHost = WineHost.Trim(); + WineVersion += $" Host: {WineHost}"; } + } + catch (Exception ex) + { + PlatformSettings.Log($"Wine host not detected: {ex.Message}", LogType.Debug); + } - WineVersion = WineVersion.Trim(); + WineVersion = WineVersion.Trim(); - PlatformSettings.Log($"{WineVersion} detected", LogType.Info); - } + PlatformSettings.Log($"{WineVersion} detected", LogType.Info); } }