mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
collection experssion
This commit is contained in:
@@ -109,6 +109,7 @@ dotnet_diagnostic.SA1629.severity = none
|
||||
dotnet_diagnostic.SA1642.severity = none
|
||||
|
||||
dotnet_diagnostic.IDE0060.severity = none
|
||||
dotnet_diagnostic.IDE0290.severity = none
|
||||
|
||||
# SA1208: System using directives should be placed before other using directives
|
||||
dotnet_diagnostic.SA1208.severity = none
|
||||
@@ -322,6 +323,8 @@ dotnet_diagnostic.CA2227.severity = suggestion
|
||||
|
||||
# CA2251: 使用 “string.Equals”
|
||||
dotnet_diagnostic.CA2251.severity = suggestion
|
||||
csharp_style_prefer_primary_constructors = true:suggestion
|
||||
dotnet_diagnostic.SA1010.severity = none
|
||||
|
||||
[*.vb]
|
||||
#### 命名样式 ####
|
||||
|
||||
@@ -86,7 +86,7 @@ internal sealed class HttpClientGenerator : IIncrementalGenerator
|
||||
|
||||
private static void FillUpWithAddHttpClient(StringBuilder sourceBuilder, SourceProductionContext production, ImmutableArray<GeneratorSyntaxContext2> contexts)
|
||||
{
|
||||
List<string> lines = new();
|
||||
List<string> lines = [];
|
||||
StringBuilder lineBuilder = new();
|
||||
|
||||
foreach (GeneratorSyntaxContext2 context in contexts.DistinctBy(c => c.Symbol.ToDisplayString()))
|
||||
|
||||
@@ -81,7 +81,7 @@ internal sealed class InjectionGenerator : IIncrementalGenerator
|
||||
|
||||
private static void FillUpWithAddServices(StringBuilder sourceBuilder, SourceProductionContext production, ImmutableArray<GeneratorSyntaxContext2> contexts)
|
||||
{
|
||||
List<string> lines = new();
|
||||
List<string> lines = [];
|
||||
StringBuilder lineBuilder = new();
|
||||
|
||||
foreach (GeneratorSyntaxContext2 context in contexts.DistinctBy(c => c.Symbol.ToDisplayString()))
|
||||
|
||||
@@ -41,10 +41,10 @@ public static class JsonParser
|
||||
public static T? FromJson<T>(this string json)
|
||||
{
|
||||
// Initialize, if needed, the ThreadStatic variables
|
||||
propertyInfoCache ??= new Dictionary<Type, Dictionary<string, PropertyInfo>>();
|
||||
fieldInfoCache ??= new Dictionary<Type, Dictionary<string, FieldInfo>>();
|
||||
stringBuilder ??= new StringBuilder();
|
||||
splitArrayPool ??= new Stack<List<string>>();
|
||||
propertyInfoCache ??= [];
|
||||
fieldInfoCache ??= [];
|
||||
stringBuilder ??= new();
|
||||
splitArrayPool ??= [];
|
||||
|
||||
// Remove all whitespace not within strings to make parsing simpler
|
||||
stringBuilder.Length = 0;
|
||||
@@ -99,7 +99,7 @@ public static class JsonParser
|
||||
// Splits { <value>:<value>, <value>:<value> } and [ <value>, <value> ] into a list of <value> strings
|
||||
private static List<string> Split(string json)
|
||||
{
|
||||
List<string> splitArray = splitArrayPool!.Count > 0 ? splitArrayPool.Pop() : new List<string>();
|
||||
List<string> splitArray = splitArrayPool!.Count > 0 ? splitArrayPool.Pop() : [];
|
||||
splitArray.Clear();
|
||||
if (json.Length == 2)
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ internal static class EnumerableExtension
|
||||
|
||||
if (enumerator.MoveNext())
|
||||
{
|
||||
HashSet<TKey> set = new();
|
||||
HashSet<TKey> set = [];
|
||||
|
||||
do
|
||||
{
|
||||
|
||||
@@ -39,10 +39,8 @@ public sealed class ResxGenerator : IIncrementalGenerator
|
||||
private static void Execute(SourceProductionContext context, AnalyzerConfigOptionsProvider options, string? assemblyName, bool supportNullableReferenceTypes, ImmutableArray<AdditionalText> files)
|
||||
{
|
||||
// Group additional file by resource kind ((a.resx, a.en.resx, a.en-us.resx), (b.resx, b.en-us.resx))
|
||||
List<IGrouping<string, AdditionalText>> resxGroups = files
|
||||
.GroupBy(file => GetResourceName(file.Path), StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(x => x.Key, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
List<IGrouping<string, AdditionalText>> resxGroups = [.. files.GroupBy(file => GetResourceName(file.Path), StringComparer.OrdinalIgnoreCase).OrderBy(x => x.Key, StringComparer.Ordinal)];
|
||||
|
||||
|
||||
foreach (IGrouping<string, AdditionalText>? resxGroug in resxGroups)
|
||||
{
|
||||
@@ -400,7 +398,7 @@ public sealed class ResxGenerator : IIncrementalGenerator
|
||||
|
||||
private static List<ResxEntry>? LoadResourceFiles(SourceProductionContext context, IGrouping<string, AdditionalText> resxGroug)
|
||||
{
|
||||
List<ResxEntry> entries = new();
|
||||
List<ResxEntry> entries = [];
|
||||
foreach (AdditionalText? entry in resxGroug.OrderBy(file => file.Path, StringComparer.Ordinal))
|
||||
{
|
||||
SourceText? content = entry.GetText(context.CancellationToken);
|
||||
|
||||
@@ -57,23 +57,13 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
|
||||
|
||||
private static void CompilationStart(CompilationStartAnalysisContext context)
|
||||
{
|
||||
SyntaxKind[] types =
|
||||
{
|
||||
SyntaxKind.ClassDeclaration,
|
||||
SyntaxKind.InterfaceDeclaration,
|
||||
SyntaxKind.StructDeclaration,
|
||||
SyntaxKind.EnumDeclaration,
|
||||
};
|
||||
SyntaxKind[] types = [SyntaxKind.ClassDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.EnumDeclaration,];
|
||||
context.RegisterSyntaxNodeAction(HandleTypeShouldBeInternal, types);
|
||||
context.RegisterSyntaxNodeAction(HandleMethodParameterShouldUseRefLikeKeyword, SyntaxKind.MethodDeclaration);
|
||||
context.RegisterSyntaxNodeAction(HandleMethodReturnTypeShouldUseValueTaskInsteadOfTask, SyntaxKind.MethodDeclaration);
|
||||
context.RegisterSyntaxNodeAction(HandleConstructorParameterShouldUseRefLikeKeyword, SyntaxKind.ConstructorDeclaration);
|
||||
|
||||
SyntaxKind[] expressions =
|
||||
{
|
||||
SyntaxKind.EqualsExpression,
|
||||
SyntaxKind.NotEqualsExpression,
|
||||
};
|
||||
SyntaxKind[] expressions = [SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression,];
|
||||
context.RegisterSyntaxNodeAction(HandleEqualsAndNotEqualsExpressionShouldUsePatternMatching, expressions);
|
||||
context.RegisterSyntaxNodeAction(HandleIsPatternShouldUseRecursivePattern, SyntaxKind.IsPatternExpression);
|
||||
context.RegisterSyntaxNodeAction(HandleArgumentNullExceptionThrowIfNull, SyntaxKind.SuppressNullableWarningExpression);
|
||||
|
||||
@@ -7,6 +7,11 @@ namespace Snap.Hutao.Test;
|
||||
[TestClass]
|
||||
public class JsonSerializeTest
|
||||
{
|
||||
private readonly JsonSerializerOptions AlowStringNumberOptions = new()
|
||||
{
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString,
|
||||
};
|
||||
|
||||
private const string SmapleObjectJson = """
|
||||
{
|
||||
"A" :1
|
||||
@@ -44,12 +49,7 @@ public class JsonSerializeTest
|
||||
[TestMethod]
|
||||
public void NumberStringKeyCanSerializeAsKey()
|
||||
{
|
||||
JsonSerializerOptions options = new()
|
||||
{
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString,
|
||||
};
|
||||
|
||||
Dictionary<int, string> sample = JsonSerializer.Deserialize<Dictionary<int, string>>(SmapleNumberKeyDictionaryJson, options)!;
|
||||
Dictionary<int, string> sample = JsonSerializer.Deserialize<Dictionary<int, string>>(SmapleNumberKeyDictionaryJson, AlowStringNumberOptions)!;
|
||||
Assert.AreEqual(sample[111], "12");
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,7 @@ public sealed class ForEachRuntimeBehaviorTest
|
||||
[TestMethod]
|
||||
public void ListOfStringCanEnumerateAsReadOnlySpanOfChar()
|
||||
{
|
||||
List<string> strings = new()
|
||||
{
|
||||
"a", "b", "c"
|
||||
};
|
||||
List<string> strings = ["a", "b", "c"];
|
||||
|
||||
int count = 0;
|
||||
foreach (ReadOnlySpan<char> chars in strings)
|
||||
|
||||
@@ -8,8 +8,8 @@ public sealed class RangeRuntimeBehaviorTest
|
||||
[TestMethod]
|
||||
public void RangeTrimLastOne()
|
||||
{
|
||||
int[] array = { 1, 2, 3, 4 };
|
||||
int[] test = { 1, 2, 3 };
|
||||
int[] array = [1, 2, 3, 4];
|
||||
int[] test = [1, 2, 3];
|
||||
int[] result = array[..^1];
|
||||
Assert.AreEqual(3, result.Length);
|
||||
Assert.IsTrue(MemoryExtensions.SequenceEqual<int>(test, result));
|
||||
|
||||
@@ -6,7 +6,7 @@ public sealed class UnsafeRuntimeBehaviorTest
|
||||
[TestMethod]
|
||||
public unsafe void UInt32AllSetIs()
|
||||
{
|
||||
byte[] bytes = { 0xFF, 0xFF, 0xFF, 0xFF, };
|
||||
byte[] bytes = [0xFF, 0xFF, 0xFF, 0xFF,];
|
||||
|
||||
fixed (byte* pBytes = bytes)
|
||||
{
|
||||
|
||||
@@ -27,9 +27,9 @@ internal sealed class DefaultItemCollectionTransitionProvider : ItemCollectionTr
|
||||
|
||||
protected override void StartTransitions(IList<ItemCollectionTransition> transitions)
|
||||
{
|
||||
List<ItemCollectionTransition> addTransitions = new();
|
||||
List<ItemCollectionTransition> removeTransitions = new();
|
||||
List<ItemCollectionTransition> moveTransitions = new();
|
||||
List<ItemCollectionTransition> addTransitions = [];
|
||||
List<ItemCollectionTransition> removeTransitions = [];
|
||||
List<ItemCollectionTransition> moveTransitions = [];
|
||||
|
||||
foreach (ItemCollectionTransition transition in addTransitions)
|
||||
{
|
||||
|
||||
@@ -118,7 +118,7 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
|
||||
|
||||
Span<double> columnHeights = new double[numberOfColumns];
|
||||
Span<int> itemsPerColumn = new int[numberOfColumns];
|
||||
HashSet<int> deadColumns = new();
|
||||
HashSet<int> deadColumns = [];
|
||||
|
||||
for (int i = 0; i < context.ItemCount; i++)
|
||||
{
|
||||
|
||||
@@ -9,9 +9,9 @@ namespace Snap.Hutao.Control.Layout;
|
||||
|
||||
internal sealed class UniformStaggeredLayoutState
|
||||
{
|
||||
private readonly List<UniformStaggeredItem> items = new();
|
||||
private readonly List<UniformStaggeredItem> items = [];
|
||||
private readonly VirtualizingLayoutContext context;
|
||||
private readonly Dictionary<int, UniformStaggeredColumnLayout> columnLayout = new();
|
||||
private readonly Dictionary<int, UniformStaggeredColumnLayout> columnLayout = [];
|
||||
private double lastAverageHeight;
|
||||
|
||||
public UniformStaggeredLayoutState(VirtualizingLayoutContext context)
|
||||
@@ -32,7 +32,7 @@ internal sealed class UniformStaggeredLayoutState
|
||||
{
|
||||
if (!this.columnLayout.TryGetValue(columnIndex, out UniformStaggeredColumnLayout? columnLayout))
|
||||
{
|
||||
columnLayout = new();
|
||||
columnLayout = [];
|
||||
this.columnLayout[columnIndex] = columnLayout;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ internal class Loading : Microsoft.UI.Xaml.Controls.ContentControl
|
||||
{
|
||||
public static readonly DependencyProperty IsLoadingProperty = DependencyProperty.Register(nameof(IsLoading), typeof(bool), typeof(Loading), new PropertyMetadata(default(bool), IsLoadingPropertyChanged));
|
||||
|
||||
[SuppressMessage("", "IDE0052")]
|
||||
private FrameworkElement? presenter;
|
||||
|
||||
public Loading()
|
||||
|
||||
@@ -62,7 +62,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
/// <inheritdoc/>
|
||||
public void Remove(Uri uriForCachedItem)
|
||||
{
|
||||
Remove(new ReadOnlySpan<Uri>(uriForCachedItem));
|
||||
Remove(new ReadOnlySpan<Uri>(ref uriForCachedItem));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -76,7 +76,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
string folder = GetCacheFolder();
|
||||
string[] files = Directory.GetFiles(folder);
|
||||
|
||||
List<string> filesToDelete = new();
|
||||
List<string> filesToDelete = [];
|
||||
foreach (ref readonly Uri uri in uriForCachedItems)
|
||||
{
|
||||
string filePath = Path.Combine(folder, GetCacheFileName(uri));
|
||||
|
||||
@@ -12,7 +12,8 @@ namespace Snap.Hutao.Core;
|
||||
internal sealed class CommandLineBuilder
|
||||
{
|
||||
private const char WhiteSpace = ' ';
|
||||
private readonly Dictionary<string, string?> options = new();
|
||||
|
||||
private readonly Dictionary<string, string?> options = [];
|
||||
|
||||
/// <summary>
|
||||
/// 当符合条件时添加参数
|
||||
|
||||
@@ -18,7 +18,7 @@ internal static class IniSerializer
|
||||
/// <returns>Ini 元素集合</returns>
|
||||
public static List<IniElement> Deserialize(FileStream fileStream)
|
||||
{
|
||||
List<IniElement> results = new();
|
||||
List<IniElement> results = [];
|
||||
using (StreamReader reader = new(fileStream))
|
||||
{
|
||||
while (reader.ReadLine() is { } line)
|
||||
|
||||
@@ -34,7 +34,7 @@ internal sealed class SeparatorCommaInt32EnumerableConverter : JsonConverter<IEn
|
||||
private static IEnumerable<int> EnumerateNumbers(string source)
|
||||
{
|
||||
// TODO: Use Collection Literals
|
||||
foreach (StringSegment id in new StringTokenizer(source, new[] { Comma }))
|
||||
foreach (StringSegment id in new StringTokenizer(source, [Comma]))
|
||||
{
|
||||
yield return int.Parse(id.AsSpan(), CultureInfo.CurrentCulture);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ internal static class AppInstanceExtension
|
||||
SetEvent(redirectEventHandle);
|
||||
});
|
||||
|
||||
ReadOnlySpan<HANDLE> handles = new(redirectEventHandle);
|
||||
ReadOnlySpan<HANDLE> handles = new(ref redirectEventHandle);
|
||||
CoWaitForMultipleObjects((uint)CWMO_FLAGS.CWMO_DEFAULT, INFINITE, handles, out uint _);
|
||||
|
||||
// TODO: Release handle
|
||||
|
||||
@@ -45,9 +45,7 @@ internal static class DispatcherQueueExtension
|
||||
});
|
||||
|
||||
blockEvent.Wait();
|
||||
#pragma warning disable CA1508
|
||||
exceptionDispatchInfo?.Throw();
|
||||
#pragma warning restore CA1508
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using static Windows.Win32.PInvoke;
|
||||
namespace Snap.Hutao.Core.Windowing.HotKey;
|
||||
|
||||
[SuppressMessage("", "CA1001")]
|
||||
[Injection(InjectAs.Singleton, typeof(IHotKeyController))]
|
||||
[ConstructorGenerated]
|
||||
internal sealed partial class HotKeyController : IHotKeyController
|
||||
{
|
||||
@@ -53,10 +54,10 @@ internal sealed partial class HotKeyController : IHotKeyController
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
INPUT[] inputs =
|
||||
{
|
||||
[
|
||||
CreateInputForMouseEvent(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTDOWN),
|
||||
CreateInputForMouseEvent(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTUP),
|
||||
};
|
||||
];
|
||||
|
||||
if (SendInput(inputs.AsSpan(), sizeof(INPUT)) is 0)
|
||||
{
|
||||
|
||||
@@ -204,6 +204,6 @@ internal sealed class WindowController
|
||||
|
||||
// 48 is the navigation button leftInset
|
||||
RectInt32 dragRect = StructMarshal.RectInt32(48, 0, options.TitleBar.ActualSize).Scale(scale);
|
||||
appTitleBar.SetDragRectangles(dragRect.ToArray());
|
||||
appTitleBar.SetDragRectangles([dragRect]);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Core.Windowing;
|
||||
|
||||
internal static class WindowExtension
|
||||
{
|
||||
private static readonly ConditionalWeakTable<Window, WindowController> WindowControllers = new();
|
||||
private static readonly ConditionalWeakTable<Window, WindowController> WindowControllers = [];
|
||||
|
||||
public static void InitializeController<TWindow>(this TWindow window, IServiceProvider serviceProvider)
|
||||
where TWindow : Window, IWindowOptionsSource
|
||||
|
||||
@@ -33,7 +33,8 @@ internal sealed class WindowSubclass : IDisposable
|
||||
this.window = window;
|
||||
this.options = options;
|
||||
this.serviceProvider = serviceProvider;
|
||||
hotKeyController = new HotKeyController(serviceProvider);
|
||||
|
||||
hotKeyController = serviceProvider.GetRequiredService<IHotKeyController>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -80,7 +80,7 @@ internal static partial class EnumerableExtension
|
||||
public static Dictionary<TKey, TSource> ToDictionaryIgnoringDuplicateKeys<TKey, TSource>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
|
||||
where TKey : notnull
|
||||
{
|
||||
Dictionary<TKey, TSource> dictionary = new();
|
||||
Dictionary<TKey, TSource> dictionary = [];
|
||||
|
||||
foreach (TSource value in source)
|
||||
{
|
||||
@@ -94,7 +94,7 @@ internal static partial class EnumerableExtension
|
||||
public static Dictionary<TKey, TValue> ToDictionaryIgnoringDuplicateKeys<TKey, TValue, TSource>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TValue> elementSelector)
|
||||
where TKey : notnull
|
||||
{
|
||||
Dictionary<TKey, TValue> dictionary = new();
|
||||
Dictionary<TKey, TValue> dictionary = [];
|
||||
|
||||
foreach (TSource value in source)
|
||||
{
|
||||
|
||||
@@ -69,7 +69,7 @@ internal static partial class EnumerableExtension
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static List<TSource> EmptyIfNull<TSource>(this List<TSource>? source)
|
||||
{
|
||||
return source ?? new();
|
||||
return source ?? [];
|
||||
}
|
||||
|
||||
public static List<T> GetRange<T>(this List<T> list, in Range range)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
|
||||
internal static partial class EnumerableExtension
|
||||
{
|
||||
public static bool TryGetValue(this NameValueCollection collection, string name, [NotNullWhen(true)] out string? value)
|
||||
{
|
||||
if (collection.AllKeys.Contains(name))
|
||||
{
|
||||
if (collection.GetValues(name) is [string single])
|
||||
{
|
||||
value = single;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = string.Empty;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -30,20 +30,6 @@ internal static partial class EnumerableExtension
|
||||
return source ?? Enumerable.Empty<TSource>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将源转换为仅包含单个元素的枚举
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源的类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
/// <returns>集合</returns>
|
||||
#if NET8_0
|
||||
[Obsolete("Use C# 12 Collection Literal instead")]
|
||||
#endif
|
||||
public static IEnumerable<TSource> Enumerate<TSource>(this TSource source)
|
||||
{
|
||||
yield return source;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 寻找枚举中唯一的值,找不到时
|
||||
/// 回退到首个或默认值
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// 对象拓展
|
||||
/// </summary>
|
||||
internal static class ObjectExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 转换到只有1长度的数组
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
/// <returns>数组</returns>
|
||||
#if NET8_0
|
||||
[Obsolete("Use C# 12 Collection Literals")]
|
||||
#endif
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T[] ToArray<T>(this T source)
|
||||
{
|
||||
// TODO: use C# 12 collection literals
|
||||
// [ source ]
|
||||
return new[] { source };
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Frozen;
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange.Achievement;
|
||||
|
||||
/// <summary>
|
||||
@@ -15,11 +17,7 @@ internal sealed class UIAF
|
||||
/// </summary>
|
||||
public const string CurrentVersion = "v1.1";
|
||||
|
||||
// TODO use FrozenSet
|
||||
private static readonly HashSet<string> SupportedVersion = new()
|
||||
{
|
||||
CurrentVersion,
|
||||
};
|
||||
private static readonly FrozenSet<string> SupportedVersion = FrozenSet.ToFrozenSet([CurrentVersion]);
|
||||
|
||||
/// <summary>
|
||||
/// 信息
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange.Inventory;
|
||||
@@ -16,10 +17,7 @@ internal sealed class UIIF
|
||||
/// </summary>
|
||||
public const string CurrentVersion = "v1.0";
|
||||
|
||||
private static readonly ImmutableList<string> SupportedVersion = new List<string>()
|
||||
{
|
||||
CurrentVersion,
|
||||
}.ToImmutableList();
|
||||
private static readonly FrozenSet<string> SupportedVersion = FrozenSet.ToFrozenSet([CurrentVersion]);
|
||||
|
||||
/// <summary>
|
||||
/// 信息
|
||||
|
||||
@@ -58,8 +58,8 @@ internal sealed class MonsterBaseValue : BaseValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return new()
|
||||
{
|
||||
return
|
||||
[
|
||||
FightPropertyFormat.ToNameValue(FightProperty.FIGHT_PROP_FIRE_SUB_HURT, FireSubHurt),
|
||||
FightPropertyFormat.ToNameValue(FightProperty.FIGHT_PROP_WATER_SUB_HURT, WaterSubHurt),
|
||||
FightPropertyFormat.ToNameValue(FightProperty.FIGHT_PROP_GRASS_SUB_HURT, GrassSubHurt),
|
||||
@@ -68,7 +68,7 @@ internal sealed class MonsterBaseValue : BaseValue
|
||||
FightPropertyFormat.ToNameValue(FightProperty.FIGHT_PROP_ICE_SUB_HURT, IceSubHurt),
|
||||
FightPropertyFormat.ToNameValue(FightProperty.FIGHT_PROP_ROCK_SUB_HURT, RockSubHurt),
|
||||
FightPropertyFormat.ToNameValue(FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, PhysicalSubHurt),
|
||||
};
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,10 +129,7 @@ internal sealed partial class AchievementDbService : IAchievementDbService
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
return appDbContext.Achievements
|
||||
.AsNoTracking()
|
||||
.Where(i => i.ArchiveId == archiveId)
|
||||
.ToList();
|
||||
return [.. appDbContext.Achievements.AsNoTracking().Where(i => i.ArchiveId == archiveId)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +151,7 @@ internal sealed partial class AchievementDbService : IAchievementDbService
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
return appDbContext.AchievementArchives.AsNoTracking().ToList();
|
||||
return [.. appDbContext.AchievementArchives.AsNoTracking()];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ internal sealed partial class AchievementStatisticsService : IAchievementStatist
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
List<AchievementStatistics> results = new();
|
||||
List<AchievementStatistics> results = [];
|
||||
foreach (AchievementArchive archive in await achievementDbService.GetAchievementArchiveListAsync().ConfigureAwait(false))
|
||||
{
|
||||
int finishedCount = await achievementDbService
|
||||
|
||||
@@ -21,14 +21,14 @@ internal sealed partial class AppOptions : DbStoreOptions
|
||||
{
|
||||
private readonly List<NameValue<BackdropType>> supportedBackdropTypesInner = CollectionsNameValue.ListFromEnum<BackdropType>();
|
||||
|
||||
private readonly List<NameValue<string>> supportedCulturesInner = new()
|
||||
{
|
||||
private readonly List<NameValue<string>> supportedCulturesInner =
|
||||
[
|
||||
ToNameValue(CultureInfo.GetCultureInfo("zh-Hans")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("zh-Hant")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("en")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("ko")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("ja")),
|
||||
};
|
||||
];
|
||||
|
||||
private string? gamePath;
|
||||
private string? powerShellPath;
|
||||
@@ -126,7 +126,7 @@ internal sealed partial class AppOptions : DbStoreOptions
|
||||
string? paths = Environment.GetEnvironmentVariable("Path");
|
||||
if (!string.IsNullOrEmpty(paths))
|
||||
{
|
||||
foreach (StringSegment path in new StringTokenizer(paths, ';'.ToArray()))
|
||||
foreach (StringSegment path in new StringTokenizer(paths, [';']))
|
||||
{
|
||||
if (path is { HasValue: true, Length: > 0 })
|
||||
{
|
||||
|
||||
@@ -243,7 +243,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
|
||||
if (distinctCount < dbInfos.Count)
|
||||
{
|
||||
avatarInfoDbService.RemoveAvatarInfoRangeByUid(uid);
|
||||
dbInfos = new();
|
||||
dbInfos = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ internal sealed partial class AvatarInfoDbService : IAvatarInfoDbService
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
return appDbContext.AvatarInfos.AsNoTracking().Where(i => i.Uid == uid).ToList();
|
||||
return [.. appDbContext.AvatarInfos.AsNoTracking().Where(i => i.Uid == uid)];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ internal sealed partial class CultivationService : ICultivationService
|
||||
Guid projectId = cultivateProject.InnerId;
|
||||
List<InventoryItem> entities = cultivationDbService.GetInventoryItemListByProjectId(projectId);
|
||||
|
||||
List<InventoryItemView> results = new();
|
||||
List<InventoryItemView> results = [];
|
||||
foreach (Material meta in metadata.Where(m => m.IsInventoryItem()).OrderBy(m => m.Id.Value))
|
||||
{
|
||||
InventoryItem entity = entities.SingleOrDefault(e => e.ItemId == meta.Id) ?? InventoryItem.From(projectId, meta.Id);
|
||||
@@ -64,7 +64,7 @@ internal sealed partial class CultivationService : ICultivationService
|
||||
List<CultivateEntryView> resultEntries = new(entries.Count);
|
||||
foreach (CultivateEntry entry in entries)
|
||||
{
|
||||
List<CultivateItemView> entryItems = new();
|
||||
List<CultivateItemView> entryItems = [];
|
||||
foreach (CultivateItem item in await cultivationDbService.GetCultivateItemListByEntryIdAsync(entry.InnerId).ConfigureAwait(false))
|
||||
{
|
||||
entryItems.Add(new(item, materials.Single(m => m.Id == item.ItemId)));
|
||||
@@ -94,7 +94,7 @@ internal sealed partial class CultivationService : ICultivationService
|
||||
CancellationToken token)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
List<StatisticsCultivateItem> resultItems = new();
|
||||
List<StatisticsCultivateItem> resultItems = [];
|
||||
|
||||
Guid projectId = cultivateProject.InnerId;
|
||||
|
||||
|
||||
@@ -66,11 +66,12 @@ internal struct GachaLogFetchContext
|
||||
/// </summary>
|
||||
/// <param name="configType">卡池类型</param>
|
||||
/// <param name="query">查询</param>
|
||||
[SuppressMessage("", "SA1010")]
|
||||
public void ResetForProcessingType(GachaConfigType configType, in GachaLogQuery query)
|
||||
{
|
||||
DbEndId = null;
|
||||
CurrentType = configType;
|
||||
ItemsToAdd = new();
|
||||
ItemsToAdd = [];
|
||||
FetchStatus = new(configType);
|
||||
QueryOptions = new(query, configType);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
using Snap.Hutao.Web.Request.QueryString;
|
||||
using System.Collections.Specialized;
|
||||
using System.Web;
|
||||
|
||||
namespace Snap.Hutao.Service.GachaLog.QueryProvider;
|
||||
|
||||
@@ -27,10 +28,11 @@ internal sealed partial class GachaLogQueryManualInputProvider : IGachaLogQueryP
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
QueryString query = QueryString.Parse(queryString);
|
||||
NameValueCollection query = HttpUtility.ParseQueryString(queryString);
|
||||
|
||||
if (query.TryGetValue("auth_appid", out string? appId) && appId is "webview_gacha")
|
||||
{
|
||||
string queryLanguageCode = query["lang"];
|
||||
string? queryLanguageCode = query["lang"];
|
||||
if (metadataOptions.IsCurrentLocale(queryLanguageCode))
|
||||
{
|
||||
return new(true, new(queryString));
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Service.User;
|
||||
using Snap.Hutao.ViewModel.User;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using Snap.Hutao.Web.Request;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Snap.Hutao.Service.GachaLog.QueryProvider;
|
||||
|
||||
@@ -37,7 +38,7 @@ internal sealed partial class GachaLogQuerySTokenProvider : IGachaLogQueryProvid
|
||||
|
||||
if (authkeyResponse.IsOk())
|
||||
{
|
||||
return new(true, new(GachaLogQueryOptions.ToQueryString(data, authkeyResponse.Data, metadataOptions.LanguageCode)));
|
||||
return new(true, new(ComposeQueryString(data, authkeyResponse.Data, metadataOptions.LanguageCode)));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -49,4 +50,17 @@ internal sealed partial class GachaLogQuerySTokenProvider : IGachaLogQueryProvid
|
||||
return new(false, SH.MustSelectUserAndUid);
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SA1010")]
|
||||
private static string ComposeQueryString(GenAuthKeyData genAuthKeyData, GameAuthKey gameAuthKey, string lang)
|
||||
{
|
||||
NameValueCollection collection = [];
|
||||
collection.Set("lang", lang);
|
||||
collection.Set("auth_appid", genAuthKeyData.AuthAppId);
|
||||
collection.Set("authkey", gameAuthKey.AuthKey);
|
||||
collection.Set("authkey_ver", $"{gameAuthKey.AuthKeyVersion:D}");
|
||||
collection.Set("sign_type", $"{gameAuthKey.SignType:D}");
|
||||
|
||||
return collection.ToQueryString();
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,11 @@
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Service.Game;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Web.Request.QueryString;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web;
|
||||
|
||||
namespace Snap.Hutao.Service.GachaLog.QueryProvider;
|
||||
|
||||
@@ -84,8 +85,8 @@ internal sealed partial class GachaLogQueryWebCacheProvider : IGachaLogQueryProv
|
||||
return new(false, SH.ServiceGachaLogUrlProviderCacheUrlNotFound);
|
||||
}
|
||||
|
||||
QueryString query = QueryString.Parse(result.TrimEnd("#/log"));
|
||||
string queryLanguageCode = query["lang"];
|
||||
NameValueCollection query = HttpUtility.ParseQueryString(result.TrimEnd("#/log"));
|
||||
string? queryLanguageCode = query["lang"];
|
||||
|
||||
if (metadataOptions.IsCurrentLocale(queryLanguageCode))
|
||||
{
|
||||
|
||||
@@ -108,7 +108,7 @@ internal sealed partial class MetadataOptions : IOptions<MetadataOptions>
|
||||
/// </summary>
|
||||
/// <param name="languageCode">语言代码</param>
|
||||
/// <returns>是否为当前语言名称</returns>
|
||||
public bool IsCurrentLocale(string languageCode)
|
||||
public bool IsCurrentLocale(string? languageCode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(languageCode))
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>Snap.Hutao</RootNamespace>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
@@ -278,6 +278,7 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.6.11" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231008000" />
|
||||
@@ -304,6 +305,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||
<None Include="Extension\EnumerableExtension.NameValueCollection.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -252,6 +252,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SA1010")]
|
||||
[Command("ExportAsUIAFToFileCommand")]
|
||||
private async Task ExportAsUIAFToFileAsync()
|
||||
{
|
||||
@@ -260,7 +261,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
string fileName = $"{achievementService.CurrentArchive?.Name}.json";
|
||||
Dictionary<string, IList<string>> fileTypes = new()
|
||||
{
|
||||
[SH.ViewModelAchievementExportFileType] = ".json".Enumerate().ToList(),
|
||||
[SH.ViewModelAchievementExportFileType] = [".json"],
|
||||
};
|
||||
|
||||
FileSavePicker picker = pickerFactory
|
||||
|
||||
@@ -6,7 +6,6 @@ using Snap.Hutao.View.Control;
|
||||
using Snap.Hutao.ViewModel.User;
|
||||
using Snap.Hutao.Web.Bridge;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Request.QueryString;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.DailyNote;
|
||||
|
||||
@@ -19,7 +18,7 @@ internal sealed class DailyNoteWebViewerSource : IWebViewerSource
|
||||
|
||||
public string GetSource(UserAndUid userAndUid)
|
||||
{
|
||||
QueryString query = userAndUid.Uid.ToQueryString();
|
||||
string query = userAndUid.Uid.ToQueryString();
|
||||
return $"https://webstatic.mihoyo.com/app/community-game-records/index.html?bbs_presentation_style=fullscreen#/ys/daily/?{query}";
|
||||
}
|
||||
}
|
||||
@@ -231,6 +231,7 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SA1010")]
|
||||
[Command("ExportToUIGFJsonCommand")]
|
||||
private async Task ExportToUIGFJsonAsync()
|
||||
{
|
||||
@@ -238,7 +239,7 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
|
||||
{
|
||||
Dictionary<string, IList<string>> fileTypes = new()
|
||||
{
|
||||
[SH.ViewModelGachaLogExportFileType] = ".json".Enumerate().ToList(),
|
||||
[SH.ViewModelGachaLogExportFileType] = [".json"],
|
||||
};
|
||||
|
||||
FileSavePicker picker = pickerFactory.GetFileSavePicker(
|
||||
|
||||
@@ -22,11 +22,12 @@ internal static class WeaponFilter
|
||||
return (object o) => o is Weapon weapon && DoFilter(input, weapon);
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SA1010")]
|
||||
private static bool DoFilter(string input, Weapon weapon)
|
||||
{
|
||||
List<bool> matches = new();
|
||||
List<bool> matches = [];
|
||||
|
||||
foreach (StringSegment segment in new StringTokenizer(input, ' '.Enumerate().ToArray()))
|
||||
foreach (StringSegment segment in new StringTokenizer(input, [' ']))
|
||||
{
|
||||
string value = segment.ToString();
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Service.GachaLog.QueryProvider;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using Snap.Hutao.Web.Request.QueryString;
|
||||
using System.Collections.Specialized;
|
||||
using System.Web;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
|
||||
|
||||
@@ -45,7 +45,7 @@ internal struct GachaLogQueryOptions
|
||||
/// size
|
||||
/// end_id
|
||||
/// </summary>
|
||||
private readonly QueryString innerQuery;
|
||||
private readonly NameValueCollection innerQuery;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的祈愿记录请求配置
|
||||
@@ -60,36 +60,17 @@ internal struct GachaLogQueryOptions
|
||||
// 对于每个类型我们需要单独创建
|
||||
// 对应类型的 GachaLogQueryOptions
|
||||
Type = queryType;
|
||||
innerQuery = QueryString.Parse(query.Query);
|
||||
|
||||
// innerQuery.Set("lang", "zh-cn");
|
||||
innerQuery.Set("gacha_type", (int)queryType);
|
||||
innerQuery.Set("size", Size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换到查询字符串
|
||||
/// </summary>
|
||||
/// <param name="genAuthKeyData">生成信息</param>
|
||||
/// <param name="gameAuthKey">验证包装</param>
|
||||
/// <param name="lang">语言</param>
|
||||
/// <returns>查询</returns>
|
||||
public static string ToQueryString(GenAuthKeyData genAuthKeyData, GameAuthKey gameAuthKey, string lang)
|
||||
{
|
||||
QueryString queryString = new();
|
||||
queryString.Set("lang", lang);
|
||||
queryString.Set("auth_appid", genAuthKeyData.AuthAppId);
|
||||
queryString.Set("authkey", Uri.EscapeDataString(gameAuthKey.AuthKey));
|
||||
queryString.Set("authkey_ver", gameAuthKey.AuthKeyVersion);
|
||||
queryString.Set("sign_type", gameAuthKey.SignType);
|
||||
|
||||
return queryString.ToString();
|
||||
innerQuery = HttpUtility.ParseQueryString(query.Query);
|
||||
innerQuery.Set("gacha_type", $"{queryType:D}");
|
||||
innerQuery.Set("size", $"{Size}");
|
||||
}
|
||||
|
||||
public readonly string ToQueryString()
|
||||
{
|
||||
// Make the cached end id into query.
|
||||
innerQuery.Set("end_id", EndId);
|
||||
return innerQuery.ToString();
|
||||
innerQuery.Set("end_id", $"{EndId:D}");
|
||||
string? query = innerQuery.ToString();
|
||||
ArgumentException.ThrowIfNullOrEmpty(query);
|
||||
return query;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,20 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Web.Request.QueryString;
|
||||
using Snap.Hutao.Web.Request;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
internal static class PlayerUidExtension
|
||||
{
|
||||
public static QueryString ToQueryString(this in PlayerUid playerUid)
|
||||
[SuppressMessage("", "SA1010")]
|
||||
public static string ToQueryString(this in PlayerUid playerUid)
|
||||
{
|
||||
QueryString queryString = new();
|
||||
queryString.Set("role_id", playerUid.Value);
|
||||
queryString.Set("server", playerUid.Region);
|
||||
NameValueCollection collection = [];
|
||||
collection.Set("role_id", playerUid.Value);
|
||||
collection.Set("server", playerUid.Region);
|
||||
|
||||
return queryString;
|
||||
return collection.ToQueryString();
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,13 @@ internal sealed class ReliquarySetsConverter : JsonConverter<ReliquarySets>
|
||||
private const char Separator = ',';
|
||||
|
||||
/// <inheritdoc/>
|
||||
[SuppressMessage("", "SA1010")]
|
||||
public override ReliquarySets? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.GetString() is { } source)
|
||||
{
|
||||
List<ReliquarySet> sets = new();
|
||||
foreach (StringSegment segment in new StringTokenizer(source, Separator.ToArray()))
|
||||
List<ReliquarySet> sets = [];
|
||||
foreach (StringSegment segment in new StringTokenizer(source, [Separator]))
|
||||
{
|
||||
if (segment is { HasValue: true, Length: > 0 })
|
||||
{
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Specialized;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
|
||||
namespace Snap.Hutao.Web.Request;
|
||||
|
||||
internal static class NameValueCollectionExtension
|
||||
{
|
||||
public static string ToQueryString(this NameValueCollection collection)
|
||||
{
|
||||
int count = collection.Count;
|
||||
if (count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
StringBuilder sb = new();
|
||||
string?[] keys = collection.AllKeys;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
string? key = keys[i];
|
||||
if (collection.GetValues(key) is { } values)
|
||||
{
|
||||
foreach (string value in values)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
sb.Append(key).Append('=');
|
||||
}
|
||||
|
||||
sb.Append(HttpUtility.UrlEncode(value)).Append('&');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.Length > 0 ? sb.ToString(0, sb.Length - 1) : string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Request.QueryString;
|
||||
|
||||
/// <summary>
|
||||
/// querystring serializer/deserializer
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal readonly struct QueryString
|
||||
{
|
||||
private readonly Dictionary<string, List<string>> dictionary = new();
|
||||
|
||||
/// <summary>
|
||||
/// Nothing
|
||||
/// </summary>
|
||||
public QueryString()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets the first value of the first parameter with the matching name. </para>
|
||||
/// <para>Throws <see cref="KeyNotFoundException"/> if a parameter with a matching name could not be found. </para>
|
||||
/// <para>O(n) where n = Count of the current object.</para>
|
||||
/// </summary>
|
||||
/// <param name="name">The parameter name to find.</param>
|
||||
/// <returns>query</returns>
|
||||
public string this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (TryGetValue(name, out string? value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new KeyNotFoundException($"A parameter with name '{name}' could not be found.");
|
||||
}
|
||||
}
|
||||
|
||||
#if NET8_0
|
||||
|
||||
private static QueryString Parse(ReadOnlySpan<char> value)
|
||||
{
|
||||
// TODO: .NET 8 ReadOnlySpan Split
|
||||
return default;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Parses a query string into a <see cref="QueryString"/> object. Keys/values are automatically URL decoded.
|
||||
/// </summary>
|
||||
/// <param name="queryString">
|
||||
/// The query string to deserialize.
|
||||
/// Valid input would be something like "a=1&b=5".
|
||||
/// URL decoding of keys/values is automatically performed.
|
||||
/// Also supports query strings that are serialized using ; instead of &, like "a=1;b=5"</param>
|
||||
/// <returns>A new QueryString represents the url query</returns>
|
||||
public static QueryString Parse(string? queryString)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(queryString))
|
||||
{
|
||||
return new QueryString();
|
||||
}
|
||||
|
||||
int questionMarkIndex = queryString.AsSpan().IndexOf('?');
|
||||
queryString = queryString[(questionMarkIndex + 1)..];
|
||||
|
||||
string[] pairs = queryString.Split('&', ';');
|
||||
QueryString answer = new();
|
||||
foreach (string pair in pairs)
|
||||
{
|
||||
string name;
|
||||
string? value;
|
||||
int indexOfEquals = pair.IndexOf('=', StringComparison.Ordinal);
|
||||
if (indexOfEquals == -1)
|
||||
{
|
||||
name = pair;
|
||||
value = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
name = pair[..indexOfEquals];
|
||||
value = pair[(indexOfEquals + 1)..];
|
||||
}
|
||||
|
||||
answer.Add(name, value);
|
||||
}
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first value of the first parameter with the matching name. If no parameter with a matching name exists, returns false.
|
||||
/// </summary>
|
||||
/// <param name="name">The parameter name to find.</param>
|
||||
/// <param name="value">The parameter's value will be written here once found.</param>
|
||||
/// <returns>value</returns>
|
||||
public bool TryGetValue(string name, [NotNullWhen(true)] out string? value)
|
||||
{
|
||||
if (dictionary.TryGetValue(name, out List<string>? values))
|
||||
{
|
||||
value = values.First();
|
||||
return true;
|
||||
}
|
||||
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the values of the parameter with the matching name. If no parameter with a matching name exists, sets <paramref name="values"/> to null and returns false.
|
||||
/// </summary>
|
||||
/// <param name="name">The parameter name to find.</param>
|
||||
/// <param name="values">The parameter's values will be written here once found.</param>
|
||||
/// <returns>values</returns>
|
||||
public bool TryGetValues(string name, [NotNullWhen(true)] out string?[]? values)
|
||||
{
|
||||
if (dictionary.TryGetValue(name, out List<string>? storedValues))
|
||||
{
|
||||
values = storedValues.ToArray();
|
||||
return true;
|
||||
}
|
||||
|
||||
values = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the count of parameters in the current query string.
|
||||
/// </summary>
|
||||
/// <returns>count of the queries</returns>
|
||||
public int Count()
|
||||
{
|
||||
return dictionary.Select(i => i.Value.Count).Sum();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a query string parameter to the query string.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the parameter.</param>
|
||||
/// <param name="value">The optional value of the parameter.</param>
|
||||
public void Add(string name, string value)
|
||||
{
|
||||
if (!dictionary.TryGetValue(name, out List<string>? values))
|
||||
{
|
||||
values = new List<string>();
|
||||
dictionary[name] = values;
|
||||
}
|
||||
|
||||
values.Add(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Set(string, string)"/>
|
||||
public void Set(string name, object value)
|
||||
{
|
||||
Set(name, value.ToString() ?? string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a query string parameter. If there are existing parameters with the same name, they are removed.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the parameter.</param>
|
||||
/// <param name="value">The optional value of the parameter.</param>
|
||||
public void Set(string name, string value)
|
||||
{
|
||||
dictionary[name] = new() { value };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the query string contains at least one parameter with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="name">The parameter name to look for.</param>
|
||||
/// <returns>True if the query string contains at least one parameter with the specified name, else false.</returns>
|
||||
public bool Contains(string name)
|
||||
{
|
||||
return dictionary.ContainsKey(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the query string contains a parameter with the specified name and value.
|
||||
/// </summary>
|
||||
/// <param name="name">The parameter name to look for.</param>
|
||||
/// <param name="value">The value to look for when the name has been matched.</param>
|
||||
/// <returns>True if the query string contains a parameter with the specified name and value, else false.</returns>
|
||||
public bool Contains(string name, string value)
|
||||
{
|
||||
return dictionary.TryGetValue(name, out List<string>? values) && values.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the first parameter with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of parameter to remove.</param>
|
||||
/// <returns>True if the parameters were removed, else false.</returns>
|
||||
public bool Remove(string name)
|
||||
{
|
||||
if (dictionary.TryGetValue(name, out List<string>? values))
|
||||
{
|
||||
if (values.Count == 1)
|
||||
{
|
||||
dictionary.Remove(name);
|
||||
}
|
||||
else
|
||||
{
|
||||
values.RemoveAt(0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all parameters with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of parameters to remove.</param>
|
||||
/// <returns>True if the parameters were removed, else false.</returns>
|
||||
public bool RemoveAll(string name)
|
||||
{
|
||||
return dictionary.Remove(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the first parameter with the specified name and value.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the parameter to remove.</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <returns>True if parameter was removed, else false.</returns>
|
||||
public bool Remove(string name, string value)
|
||||
{
|
||||
if (dictionary.TryGetValue(name, out List<string>? values))
|
||||
{
|
||||
if (values.RemoveFirstWhere(i => Equals(i, value)))
|
||||
{
|
||||
// If removed last value, remove the key
|
||||
if (values.Count == 0)
|
||||
{
|
||||
dictionary.Remove(name);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all parameters with the specified name and value.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of parameters to remove.</param>
|
||||
/// <param name="value">The value to match when deciding whether to remove.</param>
|
||||
/// <returns>The count of parameters removed.</returns>
|
||||
public int RemoveAll(string name, string value)
|
||||
{
|
||||
if (dictionary.TryGetValue(name, out List<string>? values))
|
||||
{
|
||||
int countRemoved = values.RemoveAll(i => Equals(i, value));
|
||||
|
||||
// If removed last value, remove the key
|
||||
if (values.Count == 0)
|
||||
{
|
||||
dictionary.Remove(name);
|
||||
}
|
||||
|
||||
return countRemoved;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the key-value pairs into a query string, using the default & separator.
|
||||
/// Produces something like "a=1&b=5".
|
||||
/// URL encoding of keys/values is automatically performed.
|
||||
/// Null values are not written (only their key is written).
|
||||
/// </summary>
|
||||
/// <returns>query</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Join('&', GetParameters());
|
||||
}
|
||||
|
||||
private IEnumerable<QueryStringParameter> GetParameters()
|
||||
{
|
||||
foreach ((string name, List<string> values) in dictionary)
|
||||
{
|
||||
foreach (string value in values)
|
||||
{
|
||||
yield return new QueryStringParameter(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Request.QueryString;
|
||||
|
||||
/// <summary>
|
||||
/// A single query string parameter (name and value pair).
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal struct QueryStringParameter
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the parameter. Cannot be null.
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// The value of the parameter (or null if there's no value).
|
||||
/// </summary>
|
||||
public string Value;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new query string parameter with the specified name and optional value.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the parameter. Cannot be null.</param>
|
||||
/// <param name="value">The optional value of the parameter.</param>
|
||||
internal QueryStringParameter(string name, string value)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override readonly string ToString()
|
||||
{
|
||||
return $"{Name}={Value}";
|
||||
}
|
||||
}
|
||||
1
src/Snap.Hutao/Temp.txt
Normal file
1
src/Snap.Hutao/Temp.txt
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
Reference in New Issue
Block a user