This commit is contained in:
Lightczx
2024-04-10 16:53:21 +08:00
parent 043e3f07d8
commit 2367c4759d
15 changed files with 168 additions and 117 deletions

View File

@@ -110,7 +110,6 @@ 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
@@ -321,7 +320,8 @@ dotnet_diagnostic.CA2227.severity = suggestion
# CA2251: 使用 “string.Equals”
dotnet_diagnostic.CA2251.severity = suggestion
csharp_style_prefer_primary_constructors = true:suggestion
csharp_style_prefer_primary_constructors = false:none
[*.vb]
#### 命名样式 ####

View File

@@ -3,6 +3,7 @@
using Microsoft.Win32.SafeHandles;
using Snap.Hutao.Core.Diagnostics;
using System.Buffers;
using System.IO;
using System.Net.Http;
@@ -77,34 +78,36 @@ internal sealed class HttpShardCopyWorker<TStatus> : IDisposable
using (HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false))
{
response.EnsureSuccessStatusCode();
Memory<byte> buffer = new byte[bufferSize];
using (Stream stream = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false))
using (IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent())
{
int totalBytesRead = 0;
int bytesReadAfterPreviousReport = 0;
do
Memory<byte> buffer = memoryOwner.Memory;
using (Stream stream = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false))
{
int bytesRead = await stream.ReadAsync(buffer, token).ConfigureAwait(false);
if (bytesRead <= 0)
int totalBytesRead = 0;
int bytesReadAfterPreviousReport = 0;
do
{
progress.Report(new(bytesReadAfterPreviousReport));
bytesReadAfterPreviousReport = 0;
break;
}
int bytesRead = await stream.ReadAsync(buffer, token).ConfigureAwait(false);
if (bytesRead <= 0)
{
progress.Report(new(bytesReadAfterPreviousReport));
bytesReadAfterPreviousReport = 0;
break;
}
await RandomAccess.WriteAsync(destFileHandle, buffer[..bytesRead], shard.StartOffset + totalBytesRead, token).ConfigureAwait(false);
await RandomAccess.WriteAsync(destFileHandle, buffer[..bytesRead], shard.StartOffset + totalBytesRead, token).ConfigureAwait(false);
totalBytesRead += bytesRead;
bytesReadAfterPreviousReport += bytesRead;
if (stopwatch.GetElapsedTime().TotalMilliseconds > 500)
{
progress.Report(new(bytesReadAfterPreviousReport));
bytesReadAfterPreviousReport = 0;
stopwatch = ValueStopwatch.StartNew();
totalBytesRead += bytesRead;
bytesReadAfterPreviousReport += bytesRead;
if (stopwatch.GetElapsedTime().TotalMilliseconds > 500)
{
progress.Report(new(bytesReadAfterPreviousReport));
bytesReadAfterPreviousReport = 0;
stopwatch = ValueStopwatch.StartNew();
}
}
while (true);
}
while (true);
}
}
}

View File

@@ -1,7 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Buffer;
using Snap.Hutao.Core.Diagnostics;
using System.Buffers;
using System.IO;
namespace Snap.Hutao.Core.IO;
@@ -51,26 +53,30 @@ internal class StreamCopyWorker<TStatus>
long totalBytesRead = 0;
int bytesRead;
Memory<byte> buffer = new byte[bufferSize];
do
using (IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(bufferSize))
{
bytesRead = await source.ReadAsync(buffer).ConfigureAwait(false);
if (bytesRead == 0)
{
progress.Report(statusFactory(totalBytesRead));
break;
}
Memory<byte> buffer = memoryOwner.Memory;
await destination.WriteAsync(buffer[..bytesRead]).ConfigureAwait(false);
totalBytesRead += bytesRead;
if (stopwatch.GetElapsedTime().TotalMilliseconds > 1000)
do
{
progress.Report(statusFactory(totalBytesRead));
stopwatch = ValueStopwatch.StartNew();
bytesRead = await source.ReadAsync(buffer).ConfigureAwait(false);
if (bytesRead is 0)
{
progress.Report(statusFactory(totalBytesRead));
break;
}
await destination.WriteAsync(buffer[..bytesRead]).ConfigureAwait(false);
totalBytesRead += bytesRead;
if (stopwatch.GetElapsedTime().TotalMilliseconds > 1000)
{
progress.Report(statusFactory(totalBytesRead));
stopwatch = ValueStopwatch.StartNew();
}
}
while (bytesRead > 0);
}
while (bytesRead > 0);
}
}

View File

@@ -20,7 +20,9 @@ internal static class SettingKeys
#region Application
public const string LaunchTimes = "LaunchTimes";
public const string DataFolderPath = "DataFolderPath";
public const string Major1Minor7Revision0GuideState = "Major1Minor7Revision0GuideState";
public const string Major1Minor10Revision0GuideState = "Major1Minor10Revision0GuideState";
public const string StaticResourceImageQuality = "StaticResourceImageQuality";
public const string StaticResourceUseTrimmedArchive = "StaticResourceUseTrimmedArchive";
public const string HotKeyMouseClickRepeatForever = "HotKeyMouseClickRepeatForever";
public const string IsAllocConsoleDebugModeEnabled = "IsAllocConsoleDebugModeEnabled2";
#endregion
@@ -60,6 +62,10 @@ internal static class SettingKeys
#endregion
#region Obsolete
[Obsolete("重置新手引导状态")]
public const string Major1Minor7Revision0GuideState = "Major1Minor7Revision0GuideState";
[Obsolete("重置调试控制台开关")]
public const string IsAllocConsoleDebugModeEnabledLegacy1 = "IsAllocConsoleDebugModeEnabled";
#endregion

View File

@@ -8,7 +8,7 @@
xmlns:shvg="using:Snap.Hutao.View.Guide"
mc:Ignorable="d">
<Grid x:Name="RootGrid">
<Grid x:Name="RootGrid" Background="{ThemeResource SolidBackgroundFillColorBaseBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>

View File

@@ -347,9 +347,6 @@
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'">
<ProjectCapability Include="Msix" />
</ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<Page Update="View\Dialog\LaunchGameConfigurationFixDialog.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -7,105 +7,107 @@ using Snap.Hutao.Core;
using Snap.Hutao.Core.Caching;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.IO;
using Snap.Hutao.Factory.Progress;
using Snap.Hutao.Web.Request.Builder;
using Snap.Hutao.Web.Request.Builder.Abstraction;
using System.Collections.Frozen;
using System.IO;
using System.IO.Compression;
using System.Net.Http;
namespace Snap.Hutao.ViewModel.Guide;
/// <summary>
/// 下载信息
/// </summary>
internal sealed class DownloadSummary : ObservableObject
{
private static readonly FrozenSet<string?> AllowedMediaTypes = FrozenSet.ToFrozenSet(
[
"application/octet-stream",
"application/zip",
]);
private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext;
private readonly IImageCache imageCache;
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly HttpClient httpClient;
private readonly string fileName;
private readonly string fileUrl;
private readonly Progress<StreamCopyStatus> progress;
private readonly IProgress<StreamCopyStatus> progress;
private string description = SH.ViewModelWelcomeDownloadSummaryDefault;
private double progressValue;
/// <summary>
/// 构造一个新的下载信息
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="fileName">压缩文件名称</param>
public DownloadSummary(IServiceProvider serviceProvider, string fileName)
{
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
httpRequestMessageBuilderFactory = serviceProvider.GetRequiredService<IHttpRequestMessageBuilderFactory>();
httpClient = serviceProvider.GetRequiredService<HttpClient>();
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(serviceProvider.GetRequiredService<RuntimeOptions>().UserAgent);
imageCache = serviceProvider.GetRequiredService<IImageCache>();
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(runtimeOptions.UserAgent);
this.serviceProvider = serviceProvider;
DisplayName = fileName;
this.fileName = fileName;
fileUrl = Web.HutaoEndpoints.StaticZip(fileName);
progress = new(UpdateProgressStatus);
progress = serviceProvider.GetRequiredService<IProgressFactory>().CreateForMainThread<StreamCopyStatus>(UpdateProgressStatus);
}
/// <summary>
/// 显示名称
/// </summary>
public string DisplayName { get; init; }
public string DisplayName { get => fileName; }
/// <summary>
/// 描述
/// </summary>
public string Description { get => description; private set => SetProperty(ref description, value); }
/// <summary>
/// 进度值最大1
/// </summary>
public double ProgressValue { get => progressValue; set => SetProperty(ref progressValue, value); }
/// <summary>
/// 异步下载并解压
/// </summary>
/// <returns>任务</returns>
public async ValueTask<bool> DownloadAndExtractAsync()
{
ILogger<DownloadSummary> logger = serviceProvider.GetRequiredService<ILogger<DownloadSummary>>();
try
{
HttpResponseMessage response = await httpClient.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
HttpRequestMessage message = httpRequestMessageBuilderFactory
.Create()
.SetRequestUri(fileUrl)
.SetStaticResourceControlHeaders()
.Get()
.HttpRequestMessage;
if (response.Content.Headers.ContentType?.MediaType is not ("application/octet-stream" or "application/zip"))
using (message)
{
logger.LogWarning("Download Static Zip failed, Content-Type is {Type}", response.Content.Headers.ContentType);
await taskContext.SwitchToMainThreadAsync();
Description = SH.ViewModelWelcomeDownloadSummaryContentTypeNotMatch;
return false;
}
long contentLength = response.Content.Headers.ContentLength ?? 0;
logger.LogInformation("Begin download, length: {length}", contentLength);
using (Stream content = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
using (TempFileStream temp = new(FileMode.OpenOrCreate, FileAccess.ReadWrite))
using (HttpResponseMessage response = await httpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
{
await new StreamCopyWorker(content, temp, contentLength).CopyAsync(progress).ConfigureAwait(false);
ExtractFiles(temp);
if (!AllowedMediaTypes.Contains(response.Content.Headers.ContentType?.MediaType))
{
logger.LogWarning("Download Static Zip failed, Content-Type is {Type}", response.Content.Headers.ContentType);
await taskContext.SwitchToMainThreadAsync();
Description = SH.ViewModelWelcomeDownloadSummaryContentTypeNotMatch;
return false;
}
await taskContext.SwitchToMainThreadAsync();
ProgressValue = 1;
Description = SH.ViewModelWelcomeDownloadSummaryComplete;
return true;
long contentLength = response.Content.Headers.ContentLength ?? 0;
logger.LogInformation("Begin download, size: {length}", Converters.ToFileSizeString(contentLength));
using (Stream content = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
using (TempFileStream temp = new(FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
await new StreamCopyWorker(content, temp, contentLength).CopyAsync(progress).ConfigureAwait(false);
ExtractFiles(temp);
await taskContext.SwitchToMainThreadAsync();
ProgressValue = 1;
Description = SH.ViewModelWelcomeDownloadSummaryComplete;
return true;
}
}
}
}
}
catch (Exception ex)
{
logger.LogError(ex, "Download Static Zip failed");
logger.LogError(ex, "Download static zip failed");
await taskContext.SwitchToMainThreadAsync();
Description = ex is HttpRequestException httpRequestException
? $"{SH.ViewModelWelcomeDownloadSummaryException} - [HTTP '{httpRequestException.StatusCode:D}'] [Error '{httpRequestException.HttpRequestError}']"
Description = ex is HttpRequestException httpRequestEx
? $"{SH.ViewModelWelcomeDownloadSummaryException} - [HTTP '{httpRequestEx.StatusCode:D}'] [Error '{httpRequestEx.HttpRequestError}']"
: ex.Message;
return false;
}
@@ -114,7 +116,7 @@ internal sealed class DownloadSummary : ObservableObject
private void UpdateProgressStatus(StreamCopyStatus status)
{
Description = $"{Converters.ToFileSizeString(status.BytesCopied)}/{Converters.ToFileSizeString(status.TotalBytes)}";
ProgressValue = status.TotalBytes == 0 ? 0 : (double)status.BytesCopied / status.TotalBytes;
ProgressValue = status.TotalBytes is 0 ? 0 : (double)status.BytesCopied / status.TotalBytes;
}
private void ExtractFiles(Stream stream)

View File

@@ -24,7 +24,17 @@ internal enum GuideState : uint
Environment,
/// <summary>
/// 开始下载资源
/// 正在查看常用设置
/// </summary>
CommonSetting,
/// <summary>
/// 正在查看图像资源设置
/// </summary>
StaticResourceSetting,
/// <summary>
/// 开始下载图像资源
/// </summary>
StaticResourceBegin,

View File

@@ -36,8 +36,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
{
get
{
uint value = LocalSetting.Get(SettingKeys.Major1Minor7Revision0GuideState, 0U);
GuideState state = (GuideState)value;
GuideState state = UnsafeLocalSetting.Get(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language);
if (state is GuideState.Document)
{
@@ -61,12 +60,12 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
(NextOrCompleteButtonText, IsNextOrCompleteButtonEnabled) = (SH.ViewModelGuideActionNext, true);
}
return value;
return (uint)state;
}
set
{
LocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, value);
LocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, value);
OnPropertyChanged();
}
}
@@ -93,13 +92,14 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
}
}
#region Agreement
public bool IsTermOfServiceAgreed
{
get => isTermOfServiceAgreed; set
{
if (SetProperty(ref isTermOfServiceAgreed, value))
{
OnAgreeSateChanged();
OnAgreementStateChanged();
}
}
}
@@ -110,7 +110,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
{
if (SetProperty(ref isPrivacyPolicyAgreed, value))
{
OnAgreeSateChanged();
OnAgreementStateChanged();
}
}
}
@@ -121,7 +121,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
{
if (SetProperty(ref isIssueReportAgreed, value))
{
OnAgreeSateChanged();
OnAgreementStateChanged();
}
}
}
@@ -132,14 +132,12 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
{
if (SetProperty(ref isOpenSourceLicenseAgreed, value))
{
OnAgreeSateChanged();
OnAgreementStateChanged();
}
}
}
#endregion
/// <summary>
/// 下载信息
/// </summary>
public ObservableCollection<DownloadSummary>? DownloadSummaries
{
get => downloadSummaries;
@@ -152,7 +150,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
++State;
}
private void OnAgreeSateChanged()
private void OnAgreementStateChanged()
{
IsNextOrCompleteButtonEnabled = IsTermOfServiceAgreed && IsPrivacyPolicyAgreed && IsIssueReportAgreed && IsOpenSourceLicenseAgreed;
}
@@ -173,7 +171,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
}).ConfigureAwait(false);
StaticResource.FulfillAll();
UnsafeLocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, GuideState.Completed);
UnsafeLocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Completed);
AppInstance.Restart(string.Empty);
}
}

View File

@@ -16,7 +16,7 @@ internal static class StaticResource
private static readonly ApplicationDataCompositeValue DefaultResourceVersionMap = new()
{
// DO NOT MIDIFY THIS MAP
// DO NOT MODIFY THIS MAP
{ "AchievementIcon", 0 },
{ "AvatarCard", 0 },
{ "AvatarIcon", 0 },

View File

@@ -0,0 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Web.Request.Builder;
using Snap.Hutao.Web.Request.Builder.Abstraction;
using System.Net.Http.Headers;
namespace Snap.Hutao.ViewModel.Guide;
internal static class StaticResourceHttpHeaderBuilderExtension
{
public static TBuilder SetStaticResourceControlHeaders<TBuilder>(this TBuilder builder)
where TBuilder : IHttpHeadersBuilder<HttpHeaders>
{
return builder
.SetHeader("x-quality", $"{UnsafeLocalSetting.Get(SettingKeys.StaticResourceImageQuality, StaticResourceQuality.Raw)}")
.SetHeader("x-minimum", $"{LocalSetting.Get(SettingKeys.StaticResourceUseTrimmedArchive, false)}");
}
}

View File

@@ -0,0 +1,10 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.ViewModel.Guide;
internal enum StaticResourceQuality
{
Raw,
High,
}

View File

@@ -35,8 +35,7 @@ internal static class HttpContentBuilderExtension
}
[DebuggerStepThrough]
public static T SetFormUrlEncodedContent<T>(
this T builder, IEnumerable<KeyValuePair<string, string>> content)
public static T SetFormUrlEncodedContent<T>(this T builder, IEnumerable<KeyValuePair<string, string>> content)
where T : IHttpContentBuilder
{
ArgumentNullException.ThrowIfNull(builder);

View File

@@ -41,12 +41,6 @@ internal static partial class HttpHeadersBuilderExtension
return builder.ConfigureHeaders<TBuilder, THeaders>(headers => headers.Add(name, value));
}
public static T SetReferer<T>(this T builder, string referer)
where T : IHttpHeadersBuilder<HttpHeaders>
{
return builder.SetHeader("Referer", referer);
}
[DebuggerStepThrough]
public static T SetHeader<T>(this T builder, string name)
where T : IHttpHeadersBuilder<HttpHeaders>
@@ -80,6 +74,12 @@ internal static partial class HttpHeadersBuilderExtension
.AddHeader<TBuilder, THeaders>(name, value);
}
public static T SetReferer<T>(this T builder, string referer)
where T : IHttpHeadersBuilder<HttpHeaders>
{
return builder.SetHeader("Referer", referer);
}
[DebuggerStepThrough]
public static T RemoveHeader<T>(this T builder, params string?[]? names)
where T : IHttpHeadersBuilder<HttpHeaders>

View File

@@ -12,7 +12,7 @@ internal static class HttpHeadersExtension
ArgumentNullException.ThrowIfNull(name);
// We have to work around the .NET API a little bit. See the comment below for details.
values ??= Enumerable.Empty<string?>();
values ??= [];
values = values.Where(v => v is not null);
if (values.Any())