mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Merge pull request #1103 from DGP-Studio/dotnet8
This commit is contained in:
@@ -108,7 +108,9 @@ dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
dotnet_diagnostic.SA1629.severity = none
|
||||
dotnet_diagnostic.SA1642.severity = none
|
||||
|
||||
dotnet_diagnostic.IDE0005.severity = warning
|
||||
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 +324,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]
|
||||
#### 命名样式 ####
|
||||
|
||||
@@ -140,6 +140,8 @@ internal sealed class AttributeGenerator : IIncrementalGenerator
|
||||
public InjectionAttribute(InjectAs injectAs, Type interfaceType)
|
||||
{
|
||||
}
|
||||
|
||||
public object Key { get; set; }
|
||||
}
|
||||
""";
|
||||
context.AddSource("Snap.Hutao.Core.DependencyInjection.Annotation.Attributes.g.cs", coreDependencyInjectionAnnotations);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Snap.Hutao.SourceGeneration.Primitive;
|
||||
using System.Collections.Generic;
|
||||
@@ -68,7 +69,8 @@ internal sealed class DependencyPropertyGenerator : IIncrementalGenerator
|
||||
|
||||
string propertyName = (string)arguments[0].Value!;
|
||||
string propertyType = arguments[1].Value!.ToString();
|
||||
string defaultValue = GetLiteralString(arguments.ElementAtOrDefault(2)) ?? "default";
|
||||
string defaultValue = arguments.ElementAtOrDefault(2).ToCSharpString() ?? "default";
|
||||
defaultValue = defaultValue == "null" ? "default" : defaultValue;
|
||||
string propertyChangedCallback = arguments.ElementAtOrDefault(3) is { IsNull: false } arg3 ? $", {arg3.Value}" : string.Empty;
|
||||
|
||||
string code;
|
||||
@@ -125,25 +127,4 @@ internal sealed class DependencyPropertyGenerator : IIncrementalGenerator
|
||||
production.AddSource($"{normalizedClassName}.{propertyName}.g.cs", code);
|
||||
}
|
||||
}
|
||||
|
||||
private static string? GetLiteralString(TypedConstant typedConstant)
|
||||
{
|
||||
if (typedConstant.IsNull)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (typedConstant.Value is bool boolValue)
|
||||
{
|
||||
return boolValue ? "true" : "false";
|
||||
}
|
||||
|
||||
string result = typedConstant.Value!.ToString();
|
||||
if (string.IsNullOrEmpty(result))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,6 @@ internal sealed class HttpClientGenerator : IIncrementalGenerator
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection;
|
||||
@@ -86,7 +85,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()))
|
||||
@@ -92,17 +92,29 @@ internal sealed class InjectionGenerator : IIncrementalGenerator
|
||||
ImmutableArray<TypedConstant> arguments = injectionInfo.ConstructorArguments;
|
||||
|
||||
string injectAsName = arguments[0].ToCSharpString();
|
||||
switch (injectAsName)
|
||||
|
||||
bool hasKey = injectionInfo.TryGetNamedArgumentValue("Key", out TypedConstant key);
|
||||
|
||||
switch (injectAsName, hasKey)
|
||||
{
|
||||
case InjectAsSingletonName:
|
||||
case (InjectAsSingletonName, false):
|
||||
lineBuilder.Append(" services.AddSingleton<");
|
||||
break;
|
||||
case InjectAsTransientName:
|
||||
case (InjectAsSingletonName, true):
|
||||
lineBuilder.Append(" services.AddKeyedSingleton<");
|
||||
break;
|
||||
case (InjectAsTransientName, false):
|
||||
lineBuilder.Append(" services.AddTransient<");
|
||||
break;
|
||||
case InjectAsScopedName:
|
||||
case (InjectAsTransientName, true):
|
||||
lineBuilder.Append(" services.AddKeyedTransient<");
|
||||
break;
|
||||
case (InjectAsScopedName, false):
|
||||
lineBuilder.Append(" services.AddScoped<");
|
||||
break;
|
||||
case (InjectAsScopedName, true):
|
||||
lineBuilder.Append(" services.AddKeyedScoped<");
|
||||
break;
|
||||
default:
|
||||
production.ReportDiagnostic(Diagnostic.Create(invalidInjectionDescriptor, context.Context.Node.GetLocation(), injectAsName));
|
||||
break;
|
||||
@@ -113,7 +125,14 @@ internal sealed class InjectionGenerator : IIncrementalGenerator
|
||||
lineBuilder.Append($"{arguments[1].Value}, ");
|
||||
}
|
||||
|
||||
lineBuilder.Append($"{context.Symbol.ToDisplayString()}>();");
|
||||
if (hasKey)
|
||||
{
|
||||
lineBuilder.Append($"{context.Symbol.ToDisplayString()}>({key.ToCSharpString()});");
|
||||
}
|
||||
else
|
||||
{
|
||||
lineBuilder.Append($"{context.Symbol.ToDisplayString()}>();");
|
||||
}
|
||||
|
||||
lines.Add(lineBuilder.ToString());
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Snap.Hutao.SourceGeneration.Primitive;
|
||||
@@ -13,4 +14,19 @@ internal static class AttributeDataExtension
|
||||
{
|
||||
return data.NamedArguments.Any(a => a.Key == key && predicate((TValue)a.Value.Value!));
|
||||
}
|
||||
|
||||
public static bool TryGetNamedArgumentValue(this AttributeData data, string key, out TypedConstant value)
|
||||
{
|
||||
foreach (KeyValuePair<string, TypedConstant> pair in data.NamedArguments)
|
||||
{
|
||||
if (pair.Key == key)
|
||||
{
|
||||
value = pair.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ internal static class EnumerableExtension
|
||||
|
||||
if (enumerator.MoveNext())
|
||||
{
|
||||
HashSet<TKey> set = new();
|
||||
HashSet<TKey> set = [];
|
||||
|
||||
do
|
||||
{
|
||||
|
||||
@@ -39,44 +39,44 @@ 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
|
||||
IOrderedEnumerable<IGrouping<string, AdditionalText>> group = files
|
||||
.GroupBy(file => GetResourceName(file.Path), StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(x => x.Key, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
.OrderBy(x => x.Key, StringComparer.Ordinal);
|
||||
List<IGrouping<string, AdditionalText>> resxGroups = [.. group];
|
||||
|
||||
foreach (IGrouping<string, AdditionalText>? resxGroug in resxGroups)
|
||||
foreach (IGrouping<string, AdditionalText>? resxGroup in resxGroups)
|
||||
{
|
||||
string? rootNamespaceConfiguration = GetMetadataValue(context, options, "RootNamespace", resxGroug);
|
||||
string? projectDirConfiguration = GetMetadataValue(context, options, "ProjectDir", resxGroug);
|
||||
string? namespaceConfiguration = GetMetadataValue(context, options, "Namespace", "DefaultResourcesNamespace", resxGroug);
|
||||
string? resourceNameConfiguration = GetMetadataValue(context, options, "ResourceName", globalName: null, resxGroug);
|
||||
string? classNameConfiguration = GetMetadataValue(context, options, "ClassName", globalName: null, resxGroug);
|
||||
string? rootNamespaceConfiguration = GetMetadataValue(context, options, "RootNamespace", resxGroup);
|
||||
string? projectDirConfiguration = GetMetadataValue(context, options, "ProjectDir", resxGroup);
|
||||
string? namespaceConfiguration = GetMetadataValue(context, options, "Namespace", "DefaultResourcesNamespace", resxGroup);
|
||||
string? resourceNameConfiguration = GetMetadataValue(context, options, "ResourceName", globalName: null, resxGroup);
|
||||
string? classNameConfiguration = GetMetadataValue(context, options, "ClassName", globalName: null, resxGroup);
|
||||
|
||||
string rootNamespace = rootNamespaceConfiguration ?? assemblyName ?? "";
|
||||
string projectDir = projectDirConfiguration ?? assemblyName ?? "";
|
||||
string? defaultResourceName = ComputeResourceName(rootNamespace, projectDir, resxGroug.Key);
|
||||
string? defaultNamespace = ComputeNamespace(rootNamespace, projectDir, resxGroug.Key);
|
||||
string? defaultResourceName = ComputeResourceName(rootNamespace, projectDir, resxGroup.Key);
|
||||
string? defaultNamespace = ComputeNamespace(rootNamespace, projectDir, resxGroup.Key);
|
||||
|
||||
string? ns = namespaceConfiguration ?? defaultNamespace;
|
||||
string? resourceName = resourceNameConfiguration ?? defaultResourceName;
|
||||
string className = classNameConfiguration ?? ToCSharpNameIdentifier(Path.GetFileName(resxGroug.Key));
|
||||
string className = classNameConfiguration ?? ToCSharpNameIdentifier(Path.GetFileName(resxGroup.Key));
|
||||
|
||||
if (ns == null)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(InvalidPropertiesForNamespace, location: null, resxGroug.First().Path));
|
||||
context.ReportDiagnostic(Diagnostic.Create(InvalidPropertiesForNamespace, location: null, resxGroup.First().Path));
|
||||
}
|
||||
|
||||
if (resourceName == null)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(InvalidPropertiesForResourceName, location: null, resxGroug.First().Path));
|
||||
context.ReportDiagnostic(Diagnostic.Create(InvalidPropertiesForResourceName, location: null, resxGroup.First().Path));
|
||||
}
|
||||
|
||||
List<ResxEntry>? entries = LoadResourceFiles(context, resxGroug);
|
||||
List<ResxEntry>? entries = LoadResourceFiles(context, resxGroup);
|
||||
|
||||
string content = $"""
|
||||
// Debug info:
|
||||
// key: {resxGroug.Key}
|
||||
// files: {string.Join(", ", resxGroug.Select(f => f.Path))}
|
||||
// key: {resxGroup.Key}
|
||||
// files: {string.Join(", ", resxGroup.Select(f => f.Path))}
|
||||
// RootNamespace (metadata): {rootNamespaceConfiguration}
|
||||
// ProjectDir (metadata): {projectDirConfiguration}
|
||||
// Namespace / DefaultResourcesNamespace (metadata): {namespaceConfiguration}
|
||||
@@ -97,7 +97,7 @@ public sealed class ResxGenerator : IIncrementalGenerator
|
||||
content += GenerateCode(ns, className, resourceName, entries, supportNullableReferenceTypes);
|
||||
}
|
||||
|
||||
context.AddSource($"{Path.GetFileName(resxGroug.Key)}.resx.g.cs", SourceText.From(content, Encoding.UTF8));
|
||||
context.AddSource($"{Path.GetFileName(resxGroup.Key)}.resx.g.cs", SourceText.From(content, Encoding.UTF8));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,7 +285,10 @@ public sealed class ResxGenerator : IIncrementalGenerator
|
||||
|
||||
if (!entry.IsFileRef)
|
||||
{
|
||||
summary.Add(new XElement("para", $"Value: \"{entry.Value}\"."));
|
||||
foreach((string? each, string locale) in entry.Values.Zip(entry.Locales,(x,y)=>(x,y)))
|
||||
{
|
||||
summary.Add(new XElement("para", $"{GetStringWithPadding(locale, 8)} Value: \"{each}\""));
|
||||
}
|
||||
}
|
||||
|
||||
string comment = summary.ToString().Replace("\r\n", "\r\n /// ", StringComparison.Ordinal);
|
||||
@@ -299,9 +302,9 @@ public sealed class ResxGenerator : IIncrementalGenerator
|
||||
|
||||
""");
|
||||
|
||||
if (entry.Value != null)
|
||||
if (entry.Values.FirstOrDefault() is string value)
|
||||
{
|
||||
int args = Regex.Matches(entry.Value, "\\{(?<num>[0-9]+)(\\:[^}]*)?\\}", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant)
|
||||
int args = Regex.Matches(value, "\\{(?<num>[0-9]+)(\\:[^}]*)?\\}", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant)
|
||||
.Cast<Match>()
|
||||
.Select(m => int.Parse(m.Groups["num"].Value, CultureInfo.InvariantCulture))
|
||||
.Distinct()
|
||||
@@ -360,6 +363,16 @@ public sealed class ResxGenerator : IIncrementalGenerator
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string GetStringWithPadding(string source, int length)
|
||||
{
|
||||
if (source.Length >= length)
|
||||
{
|
||||
return source;
|
||||
}
|
||||
|
||||
return source + new string('_', length - source.Length);
|
||||
}
|
||||
|
||||
private static string? ComputeResourceName(string rootNamespace, string projectDir, string resourcePath)
|
||||
{
|
||||
string fullProjectDir = EnsureEndSeparator(Path.GetFullPath(projectDir));
|
||||
@@ -400,11 +413,11 @@ 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);
|
||||
if (content == null)
|
||||
if (content is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -423,10 +436,12 @@ public sealed class ResxGenerator : IIncrementalGenerator
|
||||
if (existingEntry != null)
|
||||
{
|
||||
existingEntry.Comment ??= comment;
|
||||
existingEntry.Values.Add(value);
|
||||
existingEntry.Locales.Add(GetLocaleName(entry.Path));
|
||||
}
|
||||
else
|
||||
{
|
||||
entries.Add(new ResxEntry { Name = name, Value = value, Comment = comment, Type = type });
|
||||
entries.Add(new() { Name = name, Values = [value], Locales = [GetLocaleName(entry.Path)], Comment = comment, Type = type });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -532,15 +547,28 @@ public sealed class ResxGenerator : IIncrementalGenerator
|
||||
return pathWithoutExtension;
|
||||
}
|
||||
|
||||
return Regex.IsMatch(pathWithoutExtension.Substring(indexOf + 1), "^[a-zA-Z]{2}(-[a-zA-Z]{2})?$", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant, TimeSpan.FromSeconds(1))
|
||||
return Regex.IsMatch(pathWithoutExtension.Substring(indexOf + 1), "^[a-zA-Z]{2}(-[a-zA-Z]{2,4})?$", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant, TimeSpan.FromSeconds(1))
|
||||
? pathWithoutExtension.Substring(0, indexOf)
|
||||
: pathWithoutExtension;
|
||||
}
|
||||
|
||||
private static string GetLocaleName(string path)
|
||||
{
|
||||
string fileName = Path.GetFileNameWithoutExtension(path);
|
||||
int indexOf = fileName.LastIndexOf('.');
|
||||
if (indexOf < 0)
|
||||
{
|
||||
return "Neutral";
|
||||
}
|
||||
|
||||
return fileName.Substring(indexOf + 1);
|
||||
}
|
||||
|
||||
private sealed class ResxEntry
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public string? Value { get; set; }
|
||||
public List<string?> Values { get; set; } = default!;
|
||||
public List<string> Locales { get; set; } = default!;
|
||||
public string? Comment { get; set; }
|
||||
public string? Type { get; set; }
|
||||
|
||||
@@ -553,9 +581,9 @@ public sealed class ResxGenerator : IIncrementalGenerator
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Value != null)
|
||||
if (Values.FirstOrDefault() is string value)
|
||||
{
|
||||
string[] parts = Value.Split(';');
|
||||
string[] parts = value.Split(';');
|
||||
if (parts.Length > 1)
|
||||
{
|
||||
string type = parts[1];
|
||||
@@ -579,9 +607,9 @@ public sealed class ResxGenerator : IIncrementalGenerator
|
||||
return "string";
|
||||
}
|
||||
|
||||
if (Value != null)
|
||||
if (Values.FirstOrDefault() is string value)
|
||||
{
|
||||
string[] parts = Value.Split(';');
|
||||
string[] parts = value.Split(';');
|
||||
if (parts.Length > 1)
|
||||
{
|
||||
string type = parts[1];
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<Platforms>x64</Platforms>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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,15 @@ namespace Snap.Hutao.Test;
|
||||
[TestClass]
|
||||
public class JsonSerializeTest
|
||||
{
|
||||
private TestContext? testContext;
|
||||
|
||||
public TestContext? TestContext { get => testContext; set => testContext = value; }
|
||||
|
||||
private readonly JsonSerializerOptions AlowStringNumberOptions = new()
|
||||
{
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString,
|
||||
};
|
||||
|
||||
private const string SmapleObjectJson = """
|
||||
{
|
||||
"A" :1
|
||||
@@ -44,13 +53,29 @@ public class JsonSerializeTest
|
||||
[TestMethod]
|
||||
public void NumberStringKeyCanSerializeAsKey()
|
||||
{
|
||||
JsonSerializerOptions options = new()
|
||||
Dictionary<int, string> sample = JsonSerializer.Deserialize<Dictionary<int, string>>(SmapleNumberKeyDictionaryJson, AlowStringNumberOptions)!;
|
||||
Assert.AreEqual(sample[111], "12");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ByteArraySerializeAsBase64()
|
||||
{
|
||||
byte[] array =
|
||||
#if NET8_0_OR_GREATER
|
||||
[1, 2, 3, 4, 5];
|
||||
#else
|
||||
{ 1, 2, 3, 4, 5 };
|
||||
#endif
|
||||
ByteArraySample sample = new()
|
||||
{
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString,
|
||||
Array = array,
|
||||
};
|
||||
|
||||
Dictionary<int, string> sample = JsonSerializer.Deserialize<Dictionary<int, string>>(SmapleNumberKeyDictionaryJson, options)!;
|
||||
Assert.AreEqual(sample[111], "12");
|
||||
string result = JsonSerializer.Serialize(sample);
|
||||
TestContext!.WriteLine($"ByteArray Serialize Result: {result}");
|
||||
Assert.AreEqual(result, """
|
||||
{"Array":"AQIDBAU="}
|
||||
""");
|
||||
}
|
||||
|
||||
private sealed class Sample
|
||||
@@ -64,4 +89,9 @@ public class JsonSerializeTest
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
|
||||
public int A { get; set; }
|
||||
}
|
||||
|
||||
private sealed class ByteArraySample
|
||||
{
|
||||
public byte[]? Array { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,12 @@ public sealed class ForEachRuntimeBehaviorTest
|
||||
[TestMethod]
|
||||
public void ListOfStringCanEnumerateAsReadOnlySpanOfChar()
|
||||
{
|
||||
List<string> strings = new()
|
||||
{
|
||||
"a", "b", "c"
|
||||
};
|
||||
|
||||
List<string> strings =
|
||||
#if NET8_0_OR_GREATER
|
||||
["a", "b", "c"];
|
||||
#else
|
||||
new() { "a", "b", "c" };
|
||||
#endif
|
||||
int count = 0;
|
||||
foreach (ReadOnlySpan<char> chars in strings)
|
||||
{
|
||||
|
||||
@@ -8,8 +8,13 @@ public sealed class RangeRuntimeBehaviorTest
|
||||
[TestMethod]
|
||||
public void RangeTrimLastOne()
|
||||
{
|
||||
#if NET8_0_OR_GREATER
|
||||
int[] array = [1, 2, 3, 4];
|
||||
int[] test = [1, 2, 3];
|
||||
#else
|
||||
int[] array = { 1, 2, 3, 4 };
|
||||
int[] test = { 1, 2, 3 };
|
||||
#endif
|
||||
int[] result = array[..^1];
|
||||
Assert.AreEqual(3, result.Length);
|
||||
Assert.IsTrue(MemoryExtensions.SequenceEqual<int>(test, result));
|
||||
|
||||
@@ -6,7 +6,12 @@ public sealed class UnsafeRuntimeBehaviorTest
|
||||
[TestMethod]
|
||||
public unsafe void UInt32AllSetIs()
|
||||
{
|
||||
byte[] bytes = { 0xFF, 0xFF, 0xFF, 0xFF, };
|
||||
byte[] bytes =
|
||||
#if NET8_0_OR_GREATER
|
||||
[0xFF, 0xFF, 0xFF, 0xFF];
|
||||
#else
|
||||
{ 0xFF, 0xFF, 0xFF, 0xFF, };
|
||||
#endif
|
||||
|
||||
fixed (byte* pBytes = bytes)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -3,12 +3,18 @@
|
||||
|
||||
namespace Snap.Hutao.Control.Animation;
|
||||
|
||||
/// <summary>
|
||||
/// 动画时长
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class AnimationDurations
|
||||
internal static class ControlAnimationConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// 1
|
||||
/// </summary>
|
||||
public const string One = "1";
|
||||
|
||||
/// <summary>
|
||||
/// 1.1
|
||||
/// </summary>
|
||||
public const string OnePointOne = "1.1";
|
||||
|
||||
/// <summary>
|
||||
/// 图片缩放动画
|
||||
/// </summary>
|
||||
@@ -19,10 +19,10 @@ internal sealed class ImageZoomInAnimation : ImplicitAnimation<string, Vector3>
|
||||
/// </summary>
|
||||
public ImageZoomInAnimation()
|
||||
{
|
||||
Duration = AnimationDurations.ImageZoom;
|
||||
Duration = ControlAnimationConstants.ImageZoom;
|
||||
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut;
|
||||
EasingType = CommunityToolkit.WinUI.Animations.EasingType.Circle;
|
||||
To = Core.StringLiterals.OnePointOne;
|
||||
To = ControlAnimationConstants.OnePointOne;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -19,10 +19,10 @@ internal sealed class ImageZoomOutAnimation : ImplicitAnimation<string, Vector3>
|
||||
/// </summary>
|
||||
public ImageZoomOutAnimation()
|
||||
{
|
||||
Duration = AnimationDurations.ImageZoom;
|
||||
Duration = ControlAnimationConstants.ImageZoom;
|
||||
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut;
|
||||
EasingType = CommunityToolkit.WinUI.Animations.EasingType.Circle;
|
||||
To = Core.StringLiterals.One;
|
||||
To = ControlAnimationConstants.One;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.Web.Bridge;
|
||||
namespace Snap.Hutao.Control.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// Bridge 拓展
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class CoreWebView2Extension
|
||||
internal static class WebView2Extension
|
||||
{
|
||||
[Conditional("RELEASE")]
|
||||
public static void DisableDevToolsForReleaseBuild(this CoreWebView2 webView)
|
||||
@@ -37,4 +38,9 @@ internal static class CoreWebView2Extension
|
||||
manager.DeleteCookie(item);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsDisposed(this WebView2 webView2)
|
||||
{
|
||||
return WinRTExtension.IsDisposed(webView2);
|
||||
}
|
||||
}
|
||||
@@ -110,7 +110,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
||||
|
||||
if (exception is HttpRequestException httpRequestException)
|
||||
{
|
||||
infoBarService.Error(httpRequestException, SH.ControlImageCompositionImageHttpRequest.Format(uri));
|
||||
infoBarService.Error(httpRequestException, SH.FormatControlImageCompositionImageHttpRequest(uri));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -196,7 +196,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
||||
{
|
||||
await AnimationBuilder
|
||||
.Create()
|
||||
.Opacity(from: 0D, to: 1D, duration: AnimationDurations.ImageFadeIn)
|
||||
.Opacity(from: 0D, to: 1D, duration: ControlAnimationConstants.ImageFadeIn)
|
||||
.StartAsync(this, token)
|
||||
.ConfigureAwait(true);
|
||||
}
|
||||
@@ -217,7 +217,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
||||
{
|
||||
await AnimationBuilder
|
||||
.Create()
|
||||
.Opacity(from: 1D, to: 0D, duration: AnimationDurations.ImageFadeOut)
|
||||
.Opacity(from: 1D, to: 0D, duration: ControlAnimationConstants.ImageFadeOut)
|
||||
.StartAsync(this, token)
|
||||
.ConfigureAwait(true);
|
||||
}
|
||||
|
||||
@@ -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++)
|
||||
{
|
||||
@@ -131,7 +131,9 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
|
||||
// https://github.com/DGP-Studio/Snap.Hutao/issues/1079
|
||||
// The first element must be force refreshed otherwise
|
||||
// it will use the old one realized
|
||||
ElementRealizationOptions options = i == 0 ? ElementRealizationOptions.ForceCreate : ElementRealizationOptions.None;
|
||||
// https://github.com/DGP-Studio/Snap.Hutao/issues/1099
|
||||
// Now we need to refresh the first element of each column
|
||||
ElementRealizationOptions options = i < numberOfColumns ? ElementRealizationOptions.ForceCreate : ElementRealizationOptions.None;
|
||||
|
||||
// Item has not been measured yet. Get the element and store the values
|
||||
UIElement element = context.GetOrCreateElementAt(i, options);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Core.IO;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Frozen;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
@@ -24,17 +25,16 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
{
|
||||
private const string CacheFolderName = nameof(ImageCache);
|
||||
|
||||
// TODO: use FrozenDictionary
|
||||
private static readonly Dictionary<int, TimeSpan> RetryCountToDelay = new()
|
||||
private static readonly FrozenDictionary<int, TimeSpan> RetryCountToDelay = new Dictionary<int, TimeSpan>()
|
||||
{
|
||||
[0] = TimeSpan.FromSeconds(4),
|
||||
[1] = TimeSpan.FromSeconds(16),
|
||||
[2] = TimeSpan.FromSeconds(64),
|
||||
};
|
||||
}.ToFrozenDictionary();
|
||||
|
||||
private readonly ILogger logger;
|
||||
private readonly IHttpClientFactory httpClientFactory;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ILogger<ImageCache> logger;
|
||||
|
||||
private readonly ConcurrentDictionary<string, Task> concurrentTasks = new();
|
||||
|
||||
@@ -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>
|
||||
/// 当符合条件时添加参数
|
||||
|
||||
@@ -38,7 +38,11 @@ internal sealed partial class ScopedDbCurrent<TEntity, TMessage>
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Troubeshooting why the serviceProvider will NRE
|
||||
if (serviceProvider.IsDisposedSlow())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
@@ -92,7 +96,11 @@ internal sealed partial class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Troubeshooting why the serviceProvider will NRE
|
||||
if (serviceProvider.IsDisposedSlow())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
|
||||
/// <summary>
|
||||
/// 由于 AddHttpClient 不支持 KeyedService, 所以使用工厂模式
|
||||
/// </summary>
|
||||
/// <typeparam name="TClient">抽象类型</typeparam>
|
||||
/// <typeparam name="TClientCN">官服/米游社类型</typeparam>
|
||||
/// <typeparam name="TClientOS">国际/HoYoLAB类型</typeparam>
|
||||
internal abstract class OverseaSupportFactory<TClient, TClientCN, TClientOS> : IOverseaSupportFactory<TClient>
|
||||
where TClientCN : notnull, TClient
|
||||
where TClientOS : notnull, TClient
|
||||
|
||||
@@ -35,7 +35,6 @@ internal static class DependencyInjection
|
||||
|
||||
// Discrete services
|
||||
.AddSingleton<IMessenger, WeakReferenceMessenger>()
|
||||
|
||||
.BuildServiceProvider(true);
|
||||
|
||||
Ioc.Default.ConfigureServices(serviceProvider);
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// 服务集合扩展
|
||||
/// </summary>
|
||||
internal static class EnumerableServiceExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 选择对应的服务
|
||||
/// </summary>
|
||||
/// <typeparam name="TService">服务类型</typeparam>
|
||||
/// <param name="services">服务集合</param>
|
||||
/// <param name="isOversea">是否为海外服/Hoyolab</param>
|
||||
/// <returns>对应的服务</returns>
|
||||
[Obsolete("该方法会导致不必要的服务实例化")]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TService Pick<TService>(this IEnumerable<TService> services, bool isOversea)
|
||||
where TService : IOverseaSupport
|
||||
{
|
||||
return services.Single(s => s.IsOversea == isOversea);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 选择对应的服务
|
||||
/// </summary>
|
||||
/// <typeparam name="TService">服务类型</typeparam>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
/// <param name="isOversea">是否为海外服/Hoyolab</param>
|
||||
/// <returns>对应的服务</returns>
|
||||
[Obsolete("该方法会导致不必要的服务实例化")]
|
||||
public static TService PickRequiredService<TService>(this IServiceProvider serviceProvider, bool isOversea)
|
||||
where TService : IOverseaSupport
|
||||
{
|
||||
return serviceProvider.GetRequiredService<IEnumerable<TService>>().Pick(isOversea);
|
||||
}
|
||||
}
|
||||
@@ -16,4 +16,15 @@ internal static class ServiceProviderExtension
|
||||
{
|
||||
return ActivatorUtilities.CreateInstance<T>(serviceProvider, parameters);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsDisposedSlow(this IServiceProvider? serviceProvider)
|
||||
{
|
||||
if (serviceProvider is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return serviceProvider.GetType().GetField("_disposed")?.GetValue(serviceProvider) is true;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ internal sealed class DatabaseCorruptedException : Exception
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="innerException">内部错误</param>
|
||||
public DatabaseCorruptedException(string message, Exception? innerException)
|
||||
: base(SH.CoreExceptionServiceDatabaseCorruptedMessage.Format($"{message}\n{innerException?.Message}"), innerException)
|
||||
: base(SH.FormatCoreExceptionServiceDatabaseCorruptedMessage($"{message}\n{innerException?.Message}"), innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ internal sealed class UserdataCorruptedException : Exception
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="innerException">内部错误</param>
|
||||
public UserdataCorruptedException(string message, Exception? innerException)
|
||||
: base(SH.CoreExceptionServiceUserdataCorruptedMessage.Format($"{message}\n{innerException?.Message}"), innerException)
|
||||
: base(SH.FormatCoreExceptionServiceUserdataCorruptedMessage($"{message}\n{innerException?.Message}"), innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,9 @@ using Windows.Storage.Streams;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.DataTransfer;
|
||||
|
||||
/// <summary>
|
||||
/// 剪贴板互操作
|
||||
/// </summary>
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Transient, typeof(IClipboardInterop))]
|
||||
internal sealed partial class ClipboardInterop : IClipboardInterop
|
||||
[Injection(InjectAs.Transient, typeof(IClipboardProvider))]
|
||||
internal sealed partial class ClipboardProvider : IClipboardProvider
|
||||
{
|
||||
private readonly JsonSerializerOptions options;
|
||||
private readonly ITaskContext taskContext;
|
||||
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Core.IO.DataTransfer;
|
||||
/// <summary>
|
||||
/// 剪贴板互操作
|
||||
/// </summary>
|
||||
internal interface IClipboardInterop
|
||||
internal interface IClipboardProvider
|
||||
{
|
||||
/// <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)
|
||||
|
||||
@@ -98,7 +98,7 @@ internal static class PickerExtension
|
||||
.GetRequiredService<IInfoBarService>()
|
||||
.Warning(
|
||||
SH.CoreIOPickerExtensionPickerExceptionInfoBarTitle,
|
||||
SH.CoreIOPickerExtensionPickerExceptionInfoBarMessage.Format(exception.Message));
|
||||
SH.FormatCoreIOPickerExtensionPickerExceptionInfoBarMessage(exception.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,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,10 +37,9 @@ 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
|
||||
CloseHandle(redirectEventHandle);
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SH007")]
|
||||
|
||||
22
src/Snap.Hutao/Snap.Hutao/Core/Random.cs
Normal file
22
src/Snap.Hutao/Snap.Hutao/Core/Random.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
|
||||
internal static class Random
|
||||
{
|
||||
public static string GetLowerHexString(int length)
|
||||
{
|
||||
return new(System.Random.Shared.GetItems("0123456789abcdef".AsSpan(), length));
|
||||
}
|
||||
|
||||
public static string GetUpperAndNumberString(int length)
|
||||
{
|
||||
return new(System.Random.Shared.GetItems("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".AsSpan(), length));
|
||||
}
|
||||
|
||||
public static string GetLowerAndNumberString(int length)
|
||||
{
|
||||
return new(System.Random.Shared.GetItems("0123456789abcdefghijklmnopqrstuvwxyz".AsSpan(), length));
|
||||
}
|
||||
}
|
||||
@@ -11,26 +11,6 @@ namespace Snap.Hutao.Core;
|
||||
[HighQuality]
|
||||
internal static class StringLiterals
|
||||
{
|
||||
/// <summary>
|
||||
/// 1
|
||||
/// </summary>
|
||||
public const string One = "1";
|
||||
|
||||
/// <summary>
|
||||
/// 1.1
|
||||
/// </summary>
|
||||
public const string OnePointOne = "1.1";
|
||||
|
||||
/// <summary>
|
||||
/// True
|
||||
/// </summary>
|
||||
public const string True = "True";
|
||||
|
||||
/// <summary>
|
||||
/// False
|
||||
/// </summary>
|
||||
public const string False = "False";
|
||||
|
||||
/// <summary>
|
||||
/// CRLF 换行符
|
||||
/// </summary>
|
||||
|
||||
@@ -45,9 +45,7 @@ internal static class DispatcherQueueExtension
|
||||
});
|
||||
|
||||
blockEvent.Wait();
|
||||
#pragma warning disable CA1508
|
||||
exceptionDispatchInfo?.Throw();
|
||||
#pragma warning restore CA1508
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
namespace Snap.Hutao.Core;
|
||||
|
||||
internal struct UnsafeDateTimeOffset
|
||||
{
|
||||
@@ -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)
|
||||
{
|
||||
@@ -68,7 +69,7 @@ internal sealed partial class HotKeyController : IHotKeyController
|
||||
return;
|
||||
}
|
||||
|
||||
Thread.Sleep(Random.Shared.Next(100, 150));
|
||||
Thread.Sleep(System.Random.Shared.Next(100, 150));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ internal sealed class WindowController
|
||||
{
|
||||
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
|
||||
window.AppWindow.Title = SH.AppNameAndVersion.Format(hutaoOptions.Version);
|
||||
window.AppWindow.Title = SH.FormatAppNameAndVersion(hutaoOptions.Version);
|
||||
window.AppWindow.SetIcon(Path.Combine(hutaoOptions.InstalledLocation, "Assets/Logo.ico"));
|
||||
ExtendsContentIntoTitleBar();
|
||||
|
||||
@@ -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,7 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
@@ -28,22 +27,4 @@ internal static class StringExtension
|
||||
{
|
||||
return source.AsSpan().TrimEnd(value).ToString();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string Format(this string value, object? arg)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, value, arg);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string Format(this string value, object? arg0, object? arg1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, value, arg0, arg1);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string Format(this string value, object? arg0, object? arg1, object? arg2)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, value, arg0, arg1, arg2);
|
||||
}
|
||||
}
|
||||
@@ -67,4 +67,4 @@ internal static class StructExtension
|
||||
{
|
||||
return size.Width * size.Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
src/Snap.Hutao/Snap.Hutao/Extension/WinRTExtension.cs
Normal file
19
src/Snap.Hutao/Snap.Hutao/Extension/WinRTExtension.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using WinRT;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
|
||||
internal static class WinRTExtension
|
||||
{
|
||||
public static bool IsDisposed(this IWinRTObject obj)
|
||||
{
|
||||
return GetDisposed(obj.NativeObject);
|
||||
}
|
||||
|
||||
// protected bool disposed;
|
||||
[UnsafeAccessor(UnsafeAccessorKind.Field, Name ="disposed")]
|
||||
private static extern ref bool GetDisposed(IObjectReference objRef);
|
||||
}
|
||||
@@ -64,7 +64,7 @@ internal sealed class DailyNoteEntry : ObservableObject, IMappingFrom<DailyNoteE
|
||||
{
|
||||
return RefreshTime == DateTimeOffsetExtension.DatebaseDefaultTime
|
||||
? SH.ModelEntityDailyNoteNotRefreshed
|
||||
: SH.ModelEntityDailyNoteRefreshTimeFormat.Format(RefreshTime);
|
||||
: SH.FormatModelEntityDailyNoteRefreshTimeFormat(RefreshTime.ToLocalTime());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ internal sealed class DailyNoteEntry : ObservableObject, IMappingFrom<DailyNoteE
|
||||
DailyNote = dailyNote;
|
||||
OnPropertyChanged(nameof(DailyNote));
|
||||
|
||||
RefreshTime = DateTimeOffset.Now;
|
||||
RefreshTime = DateTimeOffset.UtcNow;
|
||||
OnPropertyChanged(nameof(RefreshTimeFormatted));
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@ internal sealed partial class GachaItem
|
||||
ArchiveId = archiveId,
|
||||
GachaType = item.GachaType,
|
||||
QueryType = item.UIGFGachaType,
|
||||
ItemId = uint.Parse(item.ItemId, CultureInfo.CurrentCulture), // TODO: catch the FormatException and throw v2.3 incompat exception
|
||||
ItemId = uint.Parse(item.ItemId, CultureInfo.CurrentCulture),
|
||||
Time = item.Time,
|
||||
Id = item.Id,
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ internal sealed class ObjectCacheEntry
|
||||
/// 获取该对象是否过期
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
public bool IsExpired { get => ExpireTime < DateTimeOffset.Now; }
|
||||
public bool IsExpired { get => ExpireTime < DateTimeOffset.UtcNow; }
|
||||
|
||||
/// <summary>
|
||||
/// 值字符串
|
||||
|
||||
@@ -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>
|
||||
/// 信息
|
||||
|
||||
@@ -49,7 +49,7 @@ internal sealed class UIAFInfo : IMappingFrom<UIAFInfo, RuntimeOptions>
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ExportTimestamp = DateTimeOffset.Now.ToUnixTimeSeconds(),
|
||||
ExportTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||||
ExportApp = SH.AppName,
|
||||
ExportAppVersion = runtimeOptions.Version.ToString(),
|
||||
UIAFVersion = UIAF.CurrentVersion,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ internal sealed class UIGFInfo : IMappingFrom<UIGFInfo, RuntimeOptions, Metadata
|
||||
{
|
||||
Uid = uid,
|
||||
Language = metadataOptions.LanguageCode,
|
||||
ExportTimestamp = DateTimeOffset.Now.ToUnixTimeSeconds(),
|
||||
ExportTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||||
ExportApp = SH.AppName,
|
||||
ExportAppVersion = runtimeOptions.Version.ToString(),
|
||||
UIGFVersion = UIGF.CurrentVersion,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Frozen;
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange.Inventory;
|
||||
|
||||
@@ -16,10 +16,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>
|
||||
/// 信息
|
||||
|
||||
@@ -69,7 +69,7 @@ internal sealed class UIIFInfo
|
||||
return new()
|
||||
{
|
||||
Uid = uid,
|
||||
ExportTimestamp = DateTimeOffset.Now.ToUnixTimeSeconds(),
|
||||
ExportTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||||
ExportApp = SH.AppName,
|
||||
ExportAppVersion = hutaoOptions.Version.ToString(),
|
||||
UIIFVersion = UIIF.CurrentVersion,
|
||||
|
||||
@@ -46,7 +46,7 @@ internal sealed class FetterInfo
|
||||
/// </summary>
|
||||
public string BirthFormatted
|
||||
{
|
||||
get => SH.ModelMetadataFetterInfoBirthdayFormat.Format(BirthMonth, BirthDay);
|
||||
get => SH.FormatModelMetadataFetterInfoBirthdayFormat(BirthMonth, BirthDay);
|
||||
}
|
||||
|
||||
/// <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),
|
||||
};
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ internal sealed class LevelDescription
|
||||
/// 格式化的等级
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string LevelFormatted { get => SH.ModelWeaponAffixFormat.Format(Level + 1); }
|
||||
public string LevelFormatted { get => SH.FormatModelWeaponAffixFormat(Level + 1); }
|
||||
|
||||
/// <summary>
|
||||
/// 描述
|
||||
|
||||
@@ -129,10 +129,8 @@ 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();
|
||||
IQueryable<EntityAchievement> result = appDbContext.Achievements.AsNoTracking().Where(i => i.ArchiveId == archiveId);
|
||||
return [.. result];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +152,8 @@ internal sealed partial class AchievementDbService : IAchievementDbService
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
return appDbContext.AchievementArchives.AsNoTracking().ToList();
|
||||
IQueryable<AchievementArchive> result = appDbContext.AchievementArchives.AsNoTracking();
|
||||
return [.. result];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -40,6 +40,6 @@ internal readonly struct ImportResult
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return SH.ServiceAchievementImportResultFormat.Format(Add, Update, Remove);
|
||||
return SH.FormatServiceAchievementImportResultFormat(Add, Update, Remove);
|
||||
}
|
||||
}
|
||||
@@ -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 })
|
||||
{
|
||||
|
||||
@@ -181,13 +181,13 @@ internal sealed partial class AvatarInfoDbBulkOperation
|
||||
if (entity is null)
|
||||
{
|
||||
entity = EntityAvatarInfo.From(uid, webInfo);
|
||||
entity.ShowcaseRefreshTime = DateTimeOffset.Now;
|
||||
entity.ShowcaseRefreshTime = DateTimeOffset.UtcNow;
|
||||
appDbContext.AvatarInfos.AddAndSave(entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
entity.Info = webInfo;
|
||||
entity.ShowcaseRefreshTime = DateTimeOffset.Now;
|
||||
entity.ShowcaseRefreshTime = DateTimeOffset.UtcNow;
|
||||
appDbContext.AvatarInfos.UpdateAndSave(entity);
|
||||
}
|
||||
}
|
||||
@@ -200,7 +200,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
|
||||
EnkaAvatarInfo avatarInfo = new() { AvatarId = avatarId };
|
||||
transformer.Transform(ref avatarInfo, source);
|
||||
entity = EntityAvatarInfo.From(uid, avatarInfo);
|
||||
entity.CalculatorRefreshTime = DateTimeOffset.Now;
|
||||
entity.CalculatorRefreshTime = DateTimeOffset.UtcNow;
|
||||
appDbContext.AvatarInfos.AddAndSave(entity);
|
||||
}
|
||||
else
|
||||
@@ -208,7 +208,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
|
||||
EnkaAvatarInfo avatarInfo = entity.Info;
|
||||
transformer.Transform(ref avatarInfo, source);
|
||||
entity.Info = avatarInfo;
|
||||
entity.CalculatorRefreshTime = DateTimeOffset.Now;
|
||||
entity.CalculatorRefreshTime = DateTimeOffset.UtcNow;
|
||||
appDbContext.AvatarInfos.UpdateAndSave(entity);
|
||||
}
|
||||
}
|
||||
@@ -221,7 +221,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
|
||||
EnkaAvatarInfo avatarInfo = new() { AvatarId = avatarId };
|
||||
transformer.Transform(ref avatarInfo, source);
|
||||
entity = EntityAvatarInfo.From(uid, avatarInfo);
|
||||
entity.GameRecordRefreshTime = DateTimeOffset.Now;
|
||||
entity.GameRecordRefreshTime = DateTimeOffset.UtcNow;
|
||||
appDbContext.AvatarInfos.AddAndSave(entity);
|
||||
}
|
||||
else
|
||||
@@ -229,7 +229,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
|
||||
EnkaAvatarInfo avatarInfo = entity.Info;
|
||||
transformer.Transform(ref avatarInfo, source);
|
||||
entity.Info = avatarInfo;
|
||||
entity.GameRecordRefreshTime = DateTimeOffset.Now;
|
||||
entity.GameRecordRefreshTime = DateTimeOffset.UtcNow;
|
||||
appDbContext.AvatarInfos.UpdateAndSave(entity);
|
||||
}
|
||||
}
|
||||
@@ -243,7 +243,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
|
||||
if (distinctCount < dbInfos.Count)
|
||||
{
|
||||
avatarInfoDbService.RemoveAvatarInfoRangeByUid(uid);
|
||||
dbInfos = new();
|
||||
dbInfos = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,8 @@ 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();
|
||||
IQueryable<EntityAvatarInfo> result = appDbContext.AvatarInfos.AsNoTracking().Where(i => i.Uid == uid);
|
||||
return [.. result];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,13 +80,13 @@ internal sealed class SummaryAvatarFactory
|
||||
// times
|
||||
ShowcaseRefreshTimeFormat = showcaseRefreshTime == DateTimeOffsetExtension.DatebaseDefaultTime
|
||||
? SH.ServiceAvatarInfoSummaryShowcaseNotRefreshed
|
||||
: SH.ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat.Format(showcaseRefreshTime),
|
||||
: SH.FormatServiceAvatarInfoSummaryShowcaseRefreshTimeFormat(showcaseRefreshTime.ToLocalTime()),
|
||||
GameRecordRefreshTimeFormat = gameRecordRefreshTime == DateTimeOffsetExtension.DatebaseDefaultTime
|
||||
? SH.ServiceAvatarInfoSummaryGameRecordNotRefreshed
|
||||
: SH.ServiceAvatarInfoSummaryGameRecordRefreshTimeFormat.Format(gameRecordRefreshTime),
|
||||
: SH.FormatServiceAvatarInfoSummaryGameRecordRefreshTimeFormat(gameRecordRefreshTime.ToLocalTime()),
|
||||
CalculatorRefreshTimeFormat = calculatorRefreshTime == DateTimeOffsetExtension.DatebaseDefaultTime
|
||||
? SH.ServiceAvatarInfoSummaryCalculatorNotRefreshed
|
||||
: SH.ServiceAvatarInfoSummaryCalculatorRefreshTimeFormat.Format(calculatorRefreshTime),
|
||||
: SH.FormatServiceAvatarInfoSummaryCalculatorRefreshTimeFormat(calculatorRefreshTime.ToLocalTime()),
|
||||
};
|
||||
|
||||
ApplyCostumeIconOrDefault(ref propertyAvatar, avatar);
|
||||
@@ -110,12 +110,12 @@ internal sealed class SummaryAvatarFactory
|
||||
}
|
||||
}
|
||||
|
||||
private ReliquaryAndWeapon ProcessEquip(List<Web.Enka.Model.Equip> equipments)
|
||||
private ReliquaryAndWeapon ProcessEquip(List<Equip> equipments)
|
||||
{
|
||||
List<PropertyReliquary> reliquaryList = new();
|
||||
List<PropertyReliquary> reliquaryList = [];
|
||||
PropertyWeapon? weapon = null;
|
||||
|
||||
foreach (Web.Enka.Model.Equip equip in equipments)
|
||||
foreach (Equip equip in equipments)
|
||||
{
|
||||
switch (equip.Flat.ItemType)
|
||||
{
|
||||
@@ -132,7 +132,7 @@ internal sealed class SummaryAvatarFactory
|
||||
return new(reliquaryList, weapon);
|
||||
}
|
||||
|
||||
private PropertyWeapon CreateWeapon(Web.Enka.Model.Equip equip)
|
||||
private PropertyWeapon CreateWeapon(Equip equip)
|
||||
{
|
||||
MetadataWeapon weapon = metadataContext.IdWeaponMap[equip.ItemId];
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ internal static class SummaryAvatarProperties
|
||||
{
|
||||
if (fightPropMap is null)
|
||||
{
|
||||
return new();
|
||||
return [];
|
||||
}
|
||||
|
||||
AvatarProperty hpProp = ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_HP, fightPropMap);
|
||||
|
||||
@@ -31,17 +31,15 @@ internal sealed partial class SummaryFactory : ISummaryFactory
|
||||
Reliquaries = await metadataService.GetReliquariesAsync(token).ConfigureAwait(false),
|
||||
};
|
||||
|
||||
IOrderedEnumerable<AvatarView> avatars = avatarInfos
|
||||
.Where(a => !AvatarIds.IsPlayer(a.Info.AvatarId))
|
||||
.Select(a => new SummaryAvatarFactory(metadataContext, a).Create())
|
||||
.OrderByDescending(a => a.LevelNumber)
|
||||
.ThenBy(a => a.Name);
|
||||
|
||||
return new()
|
||||
{
|
||||
Avatars = avatarInfos
|
||||
.Where(a => !AvatarIds.IsPlayer(a.Info.AvatarId))
|
||||
.Select(a => new SummaryAvatarFactory(metadataContext, a).Create())
|
||||
.OrderByDescending(a => a.LevelNumber)
|
||||
.ThenBy(a => a.Name)
|
||||
.ToList(),
|
||||
|
||||
// .ThenByDescending(a => a.Quality)
|
||||
// .ThenByDescending(a => a.ActivatedConstellationCount)
|
||||
Avatars = [.. avatars],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ internal static class SummaryHelper
|
||||
/// <returns>命之座</returns>
|
||||
public static List<ConstellationView> CreateConstellations(List<Skill> talents, List<SkillId>? talentIds)
|
||||
{
|
||||
talentIds ??= new();
|
||||
talentIds ??= [];
|
||||
|
||||
return talents.SelectList(talent => new ConstellationView()
|
||||
{
|
||||
@@ -45,7 +45,7 @@ internal static class SummaryHelper
|
||||
{
|
||||
if (skillLevelMap.IsNullOrEmpty())
|
||||
{
|
||||
return new();
|
||||
return [];
|
||||
}
|
||||
|
||||
Dictionary<SkillId, SkillLevel> skillExtraLeveledMap = new(skillLevelMap);
|
||||
|
||||
@@ -114,7 +114,7 @@ internal sealed class SummaryReliquaryFactory
|
||||
|
||||
private List<ReliquaryComposedSubProperty> CreateComposedSubProperties(List<ReliquarySubAffixId> appendProps)
|
||||
{
|
||||
List<SummaryReliquarySubPropertyCompositionInfo> infos = new();
|
||||
List<SummaryReliquarySubPropertyCompositionInfo> infos = [];
|
||||
foreach (ref readonly ReliquarySubAffixId subAffixId in CollectionsMarshal.AsSpan(appendProps))
|
||||
{
|
||||
ReliquarySubAffix subAffix = metadataContext.IdReliquarySubAffixMap[subAffixId];
|
||||
@@ -128,7 +128,7 @@ internal sealed class SummaryReliquaryFactory
|
||||
ThrowHelper.InvalidOperation("无效的圣遗物数据");
|
||||
}
|
||||
|
||||
List<ReliquaryComposedSubProperty> results = new();
|
||||
List<ReliquaryComposedSubProperty> results = [];
|
||||
foreach (ref readonly SummaryReliquarySubPropertyCompositionInfo info in CollectionsMarshal.AsSpan(infos))
|
||||
{
|
||||
results.Add(info.ToReliquaryComposedSubProperty());
|
||||
|
||||
@@ -22,7 +22,7 @@ internal sealed class GameRecordCharacterAvatarInfoTransformer : IAvatarInfoTran
|
||||
avatarInfo.FetterInfo.ExpLevel = source.Fetter;
|
||||
|
||||
// update level
|
||||
avatarInfo.PropMap ??= new Dictionary<PlayerProperty, TypeValue>();
|
||||
avatarInfo.PropMap ??= [];
|
||||
avatarInfo.PropMap[PlayerProperty.PROP_LEVEL] = new(PlayerProperty.PROP_LEVEL, source.Level);
|
||||
|
||||
// update constellations
|
||||
|
||||
@@ -20,10 +20,8 @@ internal sealed partial class CultivationDbService : ICultivationDbService
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
return appDbContext.InventoryItems
|
||||
.AsNoTracking()
|
||||
.Where(a => a.ProjectId == projectId)
|
||||
.ToList();
|
||||
IQueryable<InventoryItem> result = appDbContext.InventoryItems.AsNoTracking().Where(a => a.ProjectId == projectId);
|
||||
return [.. result];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Query;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
@@ -64,7 +65,8 @@ internal sealed partial class DailyNoteDbService : IDailyNoteDbService
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
return appDbContext.DailyNotes.AsNoTracking().Include(n => n.User).ToList();
|
||||
IIncludableQueryable<DailyNoteEntry, Model.Entity.User> result = appDbContext.DailyNotes.AsNoTracking().Include(n => n.User);
|
||||
return [.. result];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ internal sealed partial class DailyNoteNotificationOperation
|
||||
return;
|
||||
}
|
||||
|
||||
List<DailyNoteNotifyInfo> notifyInfos = new();
|
||||
List<DailyNoteNotifyInfo> notifyInfos = [];
|
||||
|
||||
CheckNotifySuppressed(entry, notifyInfos);
|
||||
|
||||
@@ -139,7 +139,7 @@ internal sealed partial class DailyNoteNotificationOperation
|
||||
SH.ServiceDailyNoteNotifierResin,
|
||||
Web.HutaoEndpoints.StaticFile("ItemIcon", "UI_ItemIcon_210.png"),
|
||||
$"{entry.DailyNote.CurrentResin}",
|
||||
SH.ServiceDailyNoteNotifierResinCurrent.Format(entry.DailyNote.CurrentResin)));
|
||||
SH.FormatServiceDailyNoteNotifierResinCurrent(entry.DailyNote.CurrentResin)));
|
||||
entry.ResinNotifySuppressed = true;
|
||||
}
|
||||
}
|
||||
@@ -160,7 +160,7 @@ internal sealed partial class DailyNoteNotificationOperation
|
||||
SH.ServiceDailyNoteNotifierHomeCoin,
|
||||
Web.HutaoEndpoints.StaticFile("ItemIcon", "UI_ItemIcon_204.png"),
|
||||
$"{entry.DailyNote.CurrentHomeCoin}",
|
||||
SH.ServiceDailyNoteNotifierHomeCoinCurrent.Format(entry.DailyNote.CurrentHomeCoin)));
|
||||
SH.FormatServiceDailyNoteNotifierHomeCoinCurrent(entry.DailyNote.CurrentHomeCoin)));
|
||||
entry.HomeCoinNotifySuppressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,15 @@ internal sealed partial class DailyNoteOptions : DbStoreOptions
|
||||
{
|
||||
private const int OneMinute = 60;
|
||||
|
||||
private readonly List<NameValue<int>> refreshTimes =
|
||||
[
|
||||
new(SH.ViewModelDailyNoteRefreshTime4, OneMinute * 4),
|
||||
new(SH.ViewModelDailyNoteRefreshTime8, OneMinute * 8),
|
||||
new(SH.ViewModelDailyNoteRefreshTime30, OneMinute * 30),
|
||||
new(SH.ViewModelDailyNoteRefreshTime40, OneMinute * 40),
|
||||
new(SH.ViewModelDailyNoteRefreshTime60, OneMinute * 60),
|
||||
];
|
||||
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly IScheduleTaskInterop scheduleTaskInterop;
|
||||
@@ -32,14 +41,7 @@ internal sealed partial class DailyNoteOptions : DbStoreOptions
|
||||
/// <summary>
|
||||
/// 刷新时间
|
||||
/// </summary>
|
||||
public List<NameValue<int>> RefreshTimes { get; } = new()
|
||||
{
|
||||
new(SH.ViewModelDailyNoteRefreshTime4, OneMinute * 4),
|
||||
new(SH.ViewModelDailyNoteRefreshTime8, OneMinute * 8),
|
||||
new(SH.ViewModelDailyNoteRefreshTime30, OneMinute * 30),
|
||||
new(SH.ViewModelDailyNoteRefreshTime40, OneMinute * 40),
|
||||
new(SH.ViewModelDailyNoteRefreshTime60, OneMinute * 60),
|
||||
};
|
||||
public List<NameValue<int>> RefreshTimes { get => refreshTimes; }
|
||||
|
||||
public bool IsAutoRefreshEnabled
|
||||
{
|
||||
|
||||
@@ -50,10 +50,10 @@ internal static class GachaStatisticsExtension
|
||||
public static List<StatisticsItem> ToStatisticsList<TItem>(this Dictionary<TItem, int> dict)
|
||||
where TItem : IStatisticsItemSource
|
||||
{
|
||||
return dict
|
||||
IOrderedEnumerable<StatisticsItem> result = dict
|
||||
.Select(kvp => kvp.Key.ToStatisticsItem(kvp.Value))
|
||||
.OrderByDescending(item => item.Count)
|
||||
.ToList();
|
||||
.OrderByDescending(item => item.Count);
|
||||
return [.. result];
|
||||
}
|
||||
|
||||
[SuppressMessage("", "IDE0057")]
|
||||
|
||||
@@ -54,11 +54,11 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
|
||||
TypedWishSummaryBuilderContext weaponContext = TypedWishSummaryBuilderContext.WeaponEventWish(taskContext, gachaLogClient);
|
||||
TypedWishSummaryBuilder weaponWishBuilder = new(weaponContext);
|
||||
|
||||
Dictionary<Avatar, int> orangeAvatarCounter = new();
|
||||
Dictionary<Avatar, int> purpleAvatarCounter = new();
|
||||
Dictionary<Weapon, int> orangeWeaponCounter = new();
|
||||
Dictionary<Weapon, int> purpleWeaponCounter = new();
|
||||
Dictionary<Weapon, int> blueWeaponCounter = new();
|
||||
Dictionary<Avatar, int> orangeAvatarCounter = [];
|
||||
Dictionary<Avatar, int> purpleAvatarCounter = [];
|
||||
Dictionary<Weapon, int> orangeWeaponCounter = [];
|
||||
Dictionary<Weapon, int> purpleWeaponCounter = [];
|
||||
Dictionary<Weapon, int> blueWeaponCounter = [];
|
||||
|
||||
// Pre group builders
|
||||
Dictionary<GachaConfigType, List<HistoryWishBuilder>> historyWishBuilderMap = historyWishBuilders
|
||||
@@ -132,7 +132,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
|
||||
|
||||
default:
|
||||
// ItemId string length not correct.
|
||||
ThrowHelper.UserdataCorrupted(SH.ServiceGachaStatisticsFactoryItemIdInvalid.Format(item.ItemId), default!);
|
||||
ThrowHelper.UserdataCorrupted(SH.FormatServiceGachaStatisticsFactoryItemIdInvalid(item.ItemId), default!);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,11 @@ internal sealed class HistoryWishBuilder
|
||||
{
|
||||
private readonly GachaEvent gachaEvent;
|
||||
|
||||
private readonly Dictionary<IStatisticsItemSource, int> orangeUpCounter = new();
|
||||
private readonly Dictionary<IStatisticsItemSource, int> purpleUpCounter = new();
|
||||
private readonly Dictionary<IStatisticsItemSource, int> orangeCounter = new();
|
||||
private readonly Dictionary<IStatisticsItemSource, int> purpleCounter = new();
|
||||
private readonly Dictionary<IStatisticsItemSource, int> blueCounter = new();
|
||||
private readonly Dictionary<IStatisticsItemSource, int> orangeUpCounter = [];
|
||||
private readonly Dictionary<IStatisticsItemSource, int> purpleUpCounter = [];
|
||||
private readonly Dictionary<IStatisticsItemSource, int> orangeCounter = [];
|
||||
private readonly Dictionary<IStatisticsItemSource, int> purpleCounter = [];
|
||||
private readonly Dictionary<IStatisticsItemSource, int> blueCounter = [];
|
||||
|
||||
private int totalCountTracker;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ internal sealed class HutaoStatisticsFactory
|
||||
// TODO: when in new verion
|
||||
// due to lack of newer metadata
|
||||
// this can crash
|
||||
DateTimeOffset now = DateTimeOffset.Now;
|
||||
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||
avatarEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.AvatarEventWish);
|
||||
avatarEvent2 = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.AvatarEventWish2);
|
||||
weaponEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.WeaponEventWish);
|
||||
@@ -44,10 +44,10 @@ internal sealed class HutaoStatisticsFactory
|
||||
|
||||
private HutaoWishSummary CreateWishSummary(GachaEvent gachaEvent, List<ItemCount> items)
|
||||
{
|
||||
List<StatisticsItem> upItems = new();
|
||||
List<StatisticsItem> orangeItems = new();
|
||||
List<StatisticsItem> purpleItems = new();
|
||||
List<StatisticsItem> blueItems = new();
|
||||
List<StatisticsItem> upItems = [];
|
||||
List<StatisticsItem> orangeItems = [];
|
||||
List<StatisticsItem> purpleItems = [];
|
||||
List<StatisticsItem> blueItems = [];
|
||||
|
||||
foreach (ref readonly ItemCount item in CollectionsMarshal.AsSpan(items))
|
||||
{
|
||||
@@ -55,7 +55,7 @@ internal sealed class HutaoStatisticsFactory
|
||||
{
|
||||
8U => context.IdAvatarMap[item.Item],
|
||||
5U => context.IdWeaponMap[item.Item],
|
||||
_ => throw ThrowHelper.UserdataCorrupted(SH.ServiceGachaStatisticsFactoryItemIdInvalid.Format(item.Item), default!),
|
||||
_ => throw ThrowHelper.UserdataCorrupted(SH.FormatServiceGachaStatisticsFactoryItemIdInvalid(item.Item), default!),
|
||||
};
|
||||
StatisticsItem statisticsItem = source.ToStatisticsItem(unchecked((int)item.Count));
|
||||
|
||||
@@ -80,10 +80,10 @@ internal sealed class HutaoStatisticsFactory
|
||||
return new()
|
||||
{
|
||||
Event = gachaEvent,
|
||||
UpItems = upItems.OrderByDescending(i => i.Quality).ThenByDescending(i => i.Count).ToList(),
|
||||
OrangeItems = orangeItems.OrderByDescending(i => i.Count).ToList(),
|
||||
PurpleItems = purpleItems.OrderByDescending(i => i.Count).ToList(),
|
||||
BlueItems = blueItems.OrderByDescending(i => i.Count).ToList(),
|
||||
UpItems = [.. upItems.OrderByDescending(i => i.Quality).ThenByDescending(i => i.Count)],
|
||||
OrangeItems = [.. orangeItems.OrderByDescending(i => i.Count)],
|
||||
PurpleItems = [.. purpleItems.OrderByDescending(i => i.Count)],
|
||||
BlueItems = [.. blueItems.OrderByDescending(i => i.Count)],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -32,9 +32,9 @@ internal sealed class TypedWishSummaryBuilder
|
||||
|
||||
private readonly TypedWishSummaryBuilderContext context;
|
||||
|
||||
private readonly List<int> averageOrangePullTracker = new();
|
||||
private readonly List<int> averageUpOrangePullTracker = new();
|
||||
private readonly List<SummaryItem> summaryItems = new();
|
||||
private readonly List<int> averageOrangePullTracker = [];
|
||||
private readonly List<int> averageUpOrangePullTracker = [];
|
||||
private readonly List<SummaryItem> summaryItems = [];
|
||||
|
||||
private int maxOrangePullTracker;
|
||||
private int minOrangePullTracker;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Frozen;
|
||||
|
||||
namespace Snap.Hutao.Service.GachaLog;
|
||||
|
||||
@@ -14,11 +14,11 @@ internal static class GachaLog
|
||||
/// <summary>
|
||||
/// 查询类型
|
||||
/// </summary>
|
||||
public static readonly ImmutableList<GachaConfigType> QueryTypes = new List<GachaConfigType>
|
||||
{
|
||||
public static readonly FrozenSet<GachaConfigType> QueryTypes = FrozenSet.ToFrozenSet(
|
||||
[
|
||||
GachaConfigType.NoviceWish,
|
||||
GachaConfigType.StandardWish,
|
||||
GachaConfigType.AvatarEventWish,
|
||||
GachaConfigType.WeaponEventWish,
|
||||
}.ToImmutableList(); // TODO: FrozenSet
|
||||
]);
|
||||
}
|
||||
@@ -30,7 +30,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
|
||||
}
|
||||
catch (SqliteException ex)
|
||||
{
|
||||
string message = SH.ServiceGachaLogArchiveCollectionUserdataCorruptedMessage.Format(ex.Message);
|
||||
string message = SH.FormatServiceGachaLogArchiveCollectionUserdataCorruptedMessage(ex.Message);
|
||||
throw ThrowHelper.UserdataCorrupted(message, ex);
|
||||
}
|
||||
}
|
||||
@@ -40,11 +40,11 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
return appDbContext.GachaItems
|
||||
IOrderedQueryable<GachaItem> result = appDbContext.GachaItems
|
||||
.AsNoTracking()
|
||||
.Where(i => i.ArchiveId == archiveId)
|
||||
.OrderBy(i => i.Id)
|
||||
.ToList();
|
||||
.OrderBy(i => i.Id);
|
||||
return [.. result];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
return appDbContext.GachaItems
|
||||
IQueryable<Web.Hutao.GachaLog.GachaItem> result = appDbContext.GachaItems
|
||||
.AsNoTracking()
|
||||
.Where(i => i.ArchiveId == archiveId)
|
||||
.Where(i => i.QueryType == queryType)
|
||||
@@ -286,8 +286,8 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
|
||||
ItemId = i.ItemId,
|
||||
Time = i.Time,
|
||||
Id = i.Id,
|
||||
})
|
||||
.ToList();
|
||||
});
|
||||
return [..result];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ internal struct GachaLogFetchContext
|
||||
{
|
||||
DbEndId = null;
|
||||
CurrentType = configType;
|
||||
ItemsToAdd = new();
|
||||
ItemsToAdd = [];
|
||||
FetchStatus = new(configType);
|
||||
QueryOptions = new(query, configType);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ internal sealed class GachaLogFetchStatus
|
||||
{
|
||||
return AuthKeyTimeout
|
||||
? SH.ViewDialogGachaLogRefreshProgressAuthkeyTimeout
|
||||
: SH.ViewDialogGachaLogRefreshProgressDescription.Format(ConfigType.GetLocalizedDescription());
|
||||
: SH.FormatViewDialogGachaLogRefreshProgressDescription(ConfigType.GetLocalizedDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer
|
||||
string uid = gachaArchive.Uid;
|
||||
if (await GetEndIdsFromCloudAsync(uid, token).ConfigureAwait(false) is { } endIds)
|
||||
{
|
||||
List<Web.Hutao.GachaLog.GachaItem> items = new();
|
||||
List<Web.Hutao.GachaLog.GachaItem> items = [];
|
||||
foreach ((GachaConfigType type, long endId) in endIds)
|
||||
{
|
||||
List<Web.Hutao.GachaLog.GachaItem> part = await gachaLogDbService
|
||||
|
||||
@@ -99,7 +99,7 @@ internal sealed partial class GachaLogService : IGachaLogService
|
||||
await InitializeAsync(token).ConfigureAwait(false);
|
||||
ArgumentNullException.ThrowIfNull(ArchiveCollection);
|
||||
|
||||
List<GachaStatisticsSlim> statistics = new();
|
||||
List<GachaStatisticsSlim> statistics = [];
|
||||
foreach (GachaArchive archive in ArchiveCollection)
|
||||
{
|
||||
List<GachaItem> items = await gachaLogDbService.GetGachaItemListByArchiveIdAsync(archive.InnerId).ConfigureAwait(false);
|
||||
|
||||
@@ -18,7 +18,7 @@ internal readonly struct GachaLogServiceMetadataContext
|
||||
/// <summary>
|
||||
/// 物品缓存
|
||||
/// </summary>
|
||||
public readonly Dictionary<string, Item> ItemCache = new();
|
||||
public readonly Dictionary<string, Item> ItemCache = [];
|
||||
|
||||
/// <summary>
|
||||
/// Id 角色 映射
|
||||
|
||||
@@ -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,18 +28,18 @@ 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));
|
||||
}
|
||||
else
|
||||
{
|
||||
string message = SH.ServiceGachaLogUrlProviderUrlLanguageNotMatchCurrentLocale
|
||||
.Format(queryLanguageCode, metadataOptions.LanguageCode);
|
||||
string message = SH.FormatServiceGachaLogUrlProviderUrlLanguageNotMatchCurrentLocale(queryLanguageCode, metadataOptions.LanguageCode);
|
||||
return new(false, message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,16 @@ internal sealed partial class GachaLogQuerySTokenProvider : IGachaLogQueryProvid
|
||||
return new(false, SH.MustSelectUserAndUid);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user