diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs index db4f9b19..d58b9257 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs @@ -24,7 +24,13 @@ internal static class DependencyInjection ServiceProvider serviceProvider = new ServiceCollection() // Microsoft extension - .AddLogging(builder => builder.AddDebug().AddConsoleWindow()) + .AddLogging(builder => + { + builder + .SetMinimumLevel(LogLevel.Trace) + .AddDebug() + .AddConsoleWindow(); + }) .AddMemoryCache() // Hutao extensions diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/LoggerFactoryExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/LoggerFactoryExtension.cs index fe77f4b7..12604469 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Logging/LoggerFactoryExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Logging/LoggerFactoryExtension.cs @@ -9,7 +9,11 @@ internal static class LoggerFactoryExtension { builder.Services.AddSingleton(); - builder.AddSimpleConsole(); + builder.AddSimpleConsole(options => + { + options.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff "; + }); + return builder; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx index 39eac43e..a685346f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx @@ -2834,6 +2834,15 @@ 上传数据 + + 是否立即下载 + + + 下载更新失败 + + + 胡桃 {0} 版本已发布 + 是否立即安装? @@ -2843,6 +2852,9 @@ 自动连点 + + 正在安装更新 + 工具 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Update/CheckUpdateResult.cs b/src/Snap.Hutao/Snap.Hutao/Service/Update/CheckUpdateResult.cs new file mode 100644 index 00000000..f3f7226d --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Update/CheckUpdateResult.cs @@ -0,0 +1,25 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core; +using Snap.Hutao.Core.IO.Hashing; +using Snap.Hutao.Core.IO.Http.Sharding; +using Snap.Hutao.Core.Setting; +using Snap.Hutao.Service.Abstraction; +using Snap.Hutao.Service.Notification; +using Snap.Hutao.Web.Hutao; +using Snap.Hutao.Web.Hutao.Response; +using Snap.Hutao.Web.Response; +using System.Diagnostics; +using System.IO; +using System.Net.Http; +using Windows.Storage; + +namespace Snap.Hutao.Service.Update; + +internal sealed class CheckUpdateResult +{ + public CheckUpdateResultKind Kind { get; set; } + + public HutaoVersionInformation? HutaoVersionInformation { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Update/CheckUpdateResultKind.cs b/src/Snap.Hutao/Snap.Hutao/Service/Update/CheckUpdateResultKind.cs new file mode 100644 index 00000000..f8cc55f4 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Update/CheckUpdateResultKind.cs @@ -0,0 +1,15 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.Update; + +internal enum CheckUpdateResultKind +{ + None = 0, + VersionApiInvalidResponse = 1, + VersionApiInvalidSha256 = 2, + AlreayUpdated = 3, + + NeedDownload = 4, + NeedInstall = 5, +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Update/IUpdateService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Update/IUpdateService.cs index 51213be1..d1984bd4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Update/IUpdateService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Update/IUpdateService.cs @@ -7,7 +7,9 @@ namespace Snap.Hutao.Service.Abstraction; internal interface IUpdateService { - ValueTask CheckForUpdateAndDownloadAsync(IProgress progress, CancellationToken token = default); + ValueTask CheckUpdateAsync(IProgress progress, CancellationToken token = default); - ValueTask LaunchUpdaterAsync(); + ValueTask DownloadUpdateAsync(CheckUpdateResult checkUpdateResult, IProgress progress, CancellationToken token = default); + + ValueTask LaunchUpdaterAsync(); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Update/LaunchUpdaterResult.cs b/src/Snap.Hutao/Snap.Hutao/Service/Update/LaunchUpdaterResult.cs new file mode 100644 index 00000000..122264f9 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Update/LaunchUpdaterResult.cs @@ -0,0 +1,24 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core; +using Snap.Hutao.Core.IO.Hashing; +using Snap.Hutao.Core.IO.Http.Sharding; +using Snap.Hutao.Core.Setting; +using Snap.Hutao.Service.Abstraction; +using Snap.Hutao.Service.Notification; +using Snap.Hutao.Web.Hutao; +using Snap.Hutao.Web.Hutao.Response; +using System.Diagnostics; +using System.IO; +using System.Net.Http; +using Windows.Storage; + +namespace Snap.Hutao.Service.Update; + +internal sealed class LaunchUpdaterResult +{ + public bool IsSuccess { get; set; } + + public Process? Process { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Update/UpdateService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Update/UpdateService.cs index 093dace0..c74d1518 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Update/UpdateService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Update/UpdateService.cs @@ -24,54 +24,71 @@ internal sealed partial class UpdateService : IUpdateService private readonly IServiceProvider serviceProvider; - public async ValueTask CheckForUpdateAndDownloadAsync(IProgress progress, CancellationToken token = default) + public async ValueTask CheckUpdateAsync(IProgress progress, CancellationToken token = default) { using (IServiceScope scope = serviceProvider.CreateScope()) { ITaskContext taskContext = scope.ServiceProvider.GetRequiredService(); await taskContext.SwitchToBackgroundAsync(); - HutaoInfrastructureClient infrastructureClient = serviceProvider.GetRequiredService(); + HutaoInfrastructureClient infrastructureClient = scope.ServiceProvider.GetRequiredService(); HutaoResponse response = await infrastructureClient.GetHutaoVersionInfomationAsync(token).ConfigureAwait(false); + CheckUpdateResult checkUpdateResult = new(); + if (!response.IsOk()) { - return false; + checkUpdateResult.Kind = CheckUpdateResultKind.VersionApiInvalidResponse; + return checkUpdateResult; + } + else + { + checkUpdateResult.Kind = CheckUpdateResultKind.NeedDownload; + checkUpdateResult.HutaoVersionInformation = response.Data; } - HutaoVersionInformation versionInformation = response.Data; string msixPath = GetUpdatePackagePath(); if (!LocalSetting.Get(SettingKeys.OverrideUpdateVersionComparison, false)) { - if (scope.ServiceProvider.GetRequiredService().Version >= versionInformation.Version) + // Launched in an updated version + if (scope.ServiceProvider.GetRequiredService().Version >= checkUpdateResult.HutaoVersionInformation.Version) { if (File.Exists(msixPath)) { File.Delete(msixPath); } - return false; + checkUpdateResult.Kind = CheckUpdateResultKind.AlreayUpdated; + return checkUpdateResult; } } - progress.Report(new(versionInformation.Version.ToString(), 0, 0)); + progress.Report(new(checkUpdateResult.HutaoVersionInformation.Version.ToString(), 0, 0)); - if (versionInformation.Sha256 is not { Length: > 0 } sha256) + if (checkUpdateResult.HutaoVersionInformation.Sha256 is not { Length: > 0 } sha256) { - return false; + checkUpdateResult.Kind = CheckUpdateResultKind.VersionApiInvalidSha256; + return checkUpdateResult; } if (File.Exists(msixPath) && await CheckUpdateCacheSHA256Async(msixPath, sha256, token).ConfigureAwait(false)) { - return true; + checkUpdateResult.Kind = CheckUpdateResultKind.NeedInstall; + return checkUpdateResult; } - return await DownloadUpdatePackageAsync(versionInformation, msixPath, progress, token).ConfigureAwait(false); + return checkUpdateResult; } } - public async ValueTask LaunchUpdaterAsync() + public ValueTask DownloadUpdateAsync(CheckUpdateResult checkUpdateResult, IProgress progress, CancellationToken token = default) + { + ArgumentNullException.ThrowIfNull(checkUpdateResult.HutaoVersionInformation); + return DownloadUpdatePackageAsync(checkUpdateResult.HutaoVersionInformation, GetUpdatePackagePath(), progress, token); + } + + public async ValueTask LaunchUpdaterAsync() { RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService(); string updaterTargetPath = runtimeOptions.GetDataFolderUpdateCacheFolderFile(UpdaterFilename); @@ -88,19 +105,19 @@ internal sealed partial class UpdateService : IUpdateService try { - Process.Start(new ProcessStartInfo() + Process? process = Process.Start(new ProcessStartInfo() { Arguments = commandLine, FileName = updaterTargetPath, UseShellExecute = true, }); - return true; + return new() { IsSuccess = true, Process = process }; } catch (Exception ex) { serviceProvider.GetRequiredService().Error(ex); - return false; + return new() { IsSuccess = false }; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index 03200409..fb93f415 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -321,11 +321,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/TitleViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/TitleViewModel.cs index 8793ba2c..d3de17c5 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/TitleViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/TitleViewModel.cs @@ -2,12 +2,15 @@ // Licensed under the MIT license. using Microsoft.UI.Xaml.Controls; +using Snap.Hutao.Control.Extension; using Snap.Hutao.Core; using Snap.Hutao.Core.Windowing.HotKey; using Snap.Hutao.Factory.ContentDialog; using Snap.Hutao.Factory.Progress; using Snap.Hutao.Service.Abstraction; +using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.Update; +using System.Diagnostics; using System.Globalization; using System.Text; @@ -19,6 +22,7 @@ internal sealed partial class TitleViewModel : Abstraction.ViewModel { private readonly IContentDialogFactory contentDialogFactory; private readonly IProgressFactory progressFactory; + private readonly IInfoBarService infoBarService; private readonly RuntimeOptions runtimeOptions; private readonly HotKeyOptions hotKeyOptions; private readonly IUpdateService updateService; @@ -61,21 +65,79 @@ internal sealed partial class TitleViewModel : Abstraction.ViewModel private async ValueTask DoCheckUpdateAsync() { IProgress progress = progressFactory.CreateForMainThread(status => UpdateStatus = status); - if (await updateService.CheckForUpdateAndDownloadAsync(progress).ConfigureAwait(false)) + CheckUpdateResult checkUpdateResult = await updateService.CheckUpdateAsync(progress).ConfigureAwait(false); + + if (checkUpdateResult.Kind is CheckUpdateResultKind.NeedDownload) { - ContentDialogResult result = await contentDialogFactory + ContentDialogResult downloadUpdateUserConsentResult = await contentDialogFactory + .CreateForConfirmCancelAsync( + SH.FormatViewTitileUpdatePackageDownloadTitle(UpdateStatus?.Version), + SH.ViewTitileUpdatePackageDownloadContent, + ContentDialogButton.Primary) + .ConfigureAwait(false); + + if (downloadUpdateUserConsentResult is ContentDialogResult.Primary) + { + // This method will set CheckUpdateResult.Kind to NeedInstall if download success + if (!await DownloadPackageAsync(progress, checkUpdateResult).ConfigureAwait(false)) + { + infoBarService.Warning(SH.ViewTitileUpdatePackageDownloadFailedMessage); + return; + } + } + } + + if (checkUpdateResult.Kind is CheckUpdateResultKind.NeedInstall) + { + ContentDialogResult installUpdateUserConsentResult = await contentDialogFactory .CreateForConfirmCancelAsync( SH.FormatViewTitileUpdatePackageReadyTitle(UpdateStatus?.Version), SH.ViewTitileUpdatePackageReadyContent, ContentDialogButton.Primary) .ConfigureAwait(false); - if (result == ContentDialogResult.Primary) + + if (installUpdateUserConsentResult is ContentDialogResult.Primary) { - await updateService.LaunchUpdaterAsync().ConfigureAwait(false); + LaunchUpdaterResult launchUpdaterResult = await updateService.LaunchUpdaterAsync().ConfigureAwait(false); + if (launchUpdaterResult.IsSuccess) + { + ContentDialog contentDialog = await contentDialogFactory + .CreateForIndeterminateProgressAsync(SH.ViewTitleUpdatePackageInstallingContent) + .ConfigureAwait(false); + using (await contentDialog.BlockAsync(taskContext).ConfigureAwait(false)) + { + if (launchUpdaterResult.Process is { } updater) + { + await updater.WaitForExitAsync().ConfigureAwait(false); + } + } + } } } await taskContext.SwitchToMainThreadAsync(); UpdateStatus = null; } + + private async ValueTask DownloadPackageAsync(IProgress progress, CheckUpdateResult checkUpdateResult) + { + bool downloadSuccess = true; + try + { + if (await updateService.DownloadUpdateAsync(checkUpdateResult, progress).ConfigureAwait(false)) + { + checkUpdateResult.Kind = CheckUpdateResultKind.NeedInstall; + } + else + { + downloadSuccess = false; + } + } + catch + { + downloadSuccess = false; + } + + return downloadSuccess; + } } \ No newline at end of file