mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
refactor codebase
This commit is contained in:
@@ -10,7 +10,7 @@ csharp_style_expression_bodied_constructors = false:silent
|
||||
csharp_style_expression_bodied_operators = false:silent
|
||||
csharp_style_expression_bodied_properties = false:silent
|
||||
csharp_style_expression_bodied_indexers = false:silent
|
||||
csharp_style_expression_bodied_accessors = when_on_single_line:silent
|
||||
csharp_style_expression_bodied_accessors = when_on_single_line:suggestion
|
||||
csharp_style_expression_bodied_lambdas = when_on_single_line:silent
|
||||
csharp_style_expression_bodied_local_functions = false:silent
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Animation;
|
||||
|
||||
/// <summary>
|
||||
/// 动画时长
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class AnimationDurations
|
||||
{
|
||||
/// <summary>
|
||||
/// 图片缩放动画
|
||||
/// </summary>
|
||||
public static readonly TimeSpan ImageZoom = TimeSpan.FromSeconds(0.5);
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
using CommunityToolkit.WinUI.UI;
|
||||
using CommunityToolkit.WinUI.UI.Animations;
|
||||
using Microsoft.UI.Composition;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Snap.Hutao.Control.Animation;
|
||||
@@ -11,17 +12,18 @@ namespace Snap.Hutao.Control.Animation;
|
||||
/// <summary>
|
||||
/// 图片放大动画
|
||||
/// </summary>
|
||||
internal class ImageZoomInAnimation : ImplicitAnimation<string, Vector3>
|
||||
[HighQuality]
|
||||
internal sealed class ImageZoomInAnimation : ImplicitAnimation<string, Vector3>
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的图片放大动画
|
||||
/// </summary>
|
||||
public ImageZoomInAnimation()
|
||||
{
|
||||
Duration = AnimationDurations.ImageZoom;
|
||||
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut;
|
||||
EasingType = CommunityToolkit.WinUI.UI.Animations.EasingType.Circle;
|
||||
To = "1.1";
|
||||
Duration = TimeSpan.FromSeconds(0.5);
|
||||
To = Core.StringLiterals.OnePointOne;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -35,4 +37,4 @@ internal class ImageZoomInAnimation : ImplicitAnimation<string, Vector3>
|
||||
{
|
||||
return (To?.ToVector3(), From?.ToVector3());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,17 +11,18 @@ namespace Snap.Hutao.Control.Animation;
|
||||
/// <summary>
|
||||
/// 图片缩小动画
|
||||
/// </summary>
|
||||
internal class ImageZoomOutAnimation : ImplicitAnimation<string, Vector3>
|
||||
[HighQuality]
|
||||
internal sealed class ImageZoomOutAnimation : ImplicitAnimation<string, Vector3>
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的图片缩小动画
|
||||
/// </summary>
|
||||
public ImageZoomOutAnimation()
|
||||
{
|
||||
Duration = AnimationDurations.ImageZoom;
|
||||
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut;
|
||||
EasingType = CommunityToolkit.WinUI.UI.Animations.EasingType.Circle;
|
||||
To = "1";
|
||||
Duration = TimeSpan.FromSeconds(0.5);
|
||||
To = Core.StringLiterals.One;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -9,10 +9,11 @@ namespace Snap.Hutao.Control.Behavior;
|
||||
/// <summary>
|
||||
/// 按给定比例自动调整高度的行为
|
||||
/// </summary>
|
||||
internal class AutoHeightBehavior : BehaviorBase<FrameworkElement>
|
||||
[HighQuality]
|
||||
internal sealed class AutoHeightBehavior : BehaviorBase<FrameworkElement>
|
||||
{
|
||||
private static readonly DependencyProperty TargetWidthProperty = Property<AutoHeightBehavior>.Depend(nameof(TargetWidth), 1080D);
|
||||
private static readonly DependencyProperty TargetHeightProperty = Property<AutoHeightBehavior>.Depend(nameof(TargetHeight), 390D);
|
||||
private static readonly DependencyProperty TargetWidthProperty = Property<AutoHeightBehavior>.DependBoxed<double>(nameof(TargetWidth), BoxedValues.DoubleOne);
|
||||
private static readonly DependencyProperty TargetHeightProperty = Property<AutoHeightBehavior>.DependBoxed<double>(nameof(TargetHeight), BoxedValues.DoubleOne);
|
||||
|
||||
/// <summary>
|
||||
/// 目标宽度
|
||||
@@ -35,7 +36,7 @@ internal class AutoHeightBehavior : BehaviorBase<FrameworkElement>
|
||||
/// <inheritdoc/>
|
||||
protected override void OnAssociatedObjectLoaded()
|
||||
{
|
||||
UpdateElementHeight();
|
||||
UpdateElement();
|
||||
AssociatedObject.SizeChanged += OnSizeChanged;
|
||||
}
|
||||
|
||||
@@ -48,11 +49,11 @@ internal class AutoHeightBehavior : BehaviorBase<FrameworkElement>
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
UpdateElementHeight();
|
||||
UpdateElement();
|
||||
}
|
||||
|
||||
private void UpdateElementHeight()
|
||||
private void UpdateElement()
|
||||
{
|
||||
AssociatedObject.Height = (double)AssociatedObject.ActualWidth * (TargetHeight / TargetWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,8 @@ namespace Snap.Hutao.Control.Behavior;
|
||||
/// <summary>
|
||||
/// 按给定比例自动调整高度的行为
|
||||
/// </summary>
|
||||
internal class AutoWidthBehavior : BehaviorBase<FrameworkElement>
|
||||
[HighQuality]
|
||||
internal sealed class AutoWidthBehavior : BehaviorBase<FrameworkElement>
|
||||
{
|
||||
private static readonly DependencyProperty TargetWidthProperty = Property<AutoWidthBehavior>.Depend(nameof(TargetWidth), 320D);
|
||||
private static readonly DependencyProperty TargetHeightProperty = Property<AutoWidthBehavior>.Depend(nameof(TargetHeight), 1024D);
|
||||
@@ -35,7 +36,7 @@ internal class AutoWidthBehavior : BehaviorBase<FrameworkElement>
|
||||
/// <inheritdoc/>
|
||||
protected override void OnAssociatedObjectLoaded()
|
||||
{
|
||||
UpdateElementWidth();
|
||||
UpdateElement();
|
||||
AssociatedObject.SizeChanged += OnSizeChanged;
|
||||
}
|
||||
|
||||
@@ -48,10 +49,10 @@ internal class AutoWidthBehavior : BehaviorBase<FrameworkElement>
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
UpdateElementWidth();
|
||||
UpdateElement();
|
||||
}
|
||||
|
||||
private void UpdateElementWidth()
|
||||
private void UpdateElement()
|
||||
{
|
||||
AssociatedObject.Width = (double)AssociatedObject.Height * (TargetWidth / TargetHeight);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Snap.Hutao.Control.Behavior;
|
||||
/// AppTitleBar Workaround
|
||||
/// https://github.com/microsoft/microsoft-ui-xaml/issues/7756
|
||||
/// </summary>
|
||||
internal class ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior : BehaviorBase<ComboBox>
|
||||
internal sealed class ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior : BehaviorBase<ComboBox>
|
||||
{
|
||||
private readonly IMessenger messenger;
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ namespace Snap.Hutao.Control.Behavior;
|
||||
/// <summary>
|
||||
/// 在元素加载完成后执行命令的行为
|
||||
/// </summary>
|
||||
internal class InvokeCommandOnLoadedBehavior : BehaviorBase<UIElement>
|
||||
[HighQuality]
|
||||
internal sealed class InvokeCommandOnLoadedBehavior : BehaviorBase<UIElement>
|
||||
{
|
||||
private static readonly DependencyProperty CommandProperty = Property<InvokeCommandOnLoadedBehavior>.Depend<ICommand>(nameof(Command));
|
||||
private static readonly DependencyProperty CommandParameterProperty = Property<InvokeCommandOnLoadedBehavior>.Depend<object>(nameof(CommandParameter));
|
||||
@@ -38,7 +39,7 @@ internal class InvokeCommandOnLoadedBehavior : BehaviorBase<UIElement>
|
||||
{
|
||||
if (Command != null && Command.CanExecute(CommandParameter))
|
||||
{
|
||||
Command?.Execute(CommandParameter);
|
||||
Command.Execute(CommandParameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.UI.Behaviors;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Snap.Hutao.Control.Behavior;
|
||||
|
||||
/// <summary>
|
||||
/// 在元素卸载完成后执行命令的行为
|
||||
/// </summary>
|
||||
internal class InvokeCommandOnUnloadedBehavior : BehaviorBase<UIElement>
|
||||
{
|
||||
private static readonly DependencyProperty CommandProperty = Property<InvokeCommandOnUnloadedBehavior>.Depend<ICommand>(nameof(Command));
|
||||
|
||||
/// <summary>
|
||||
/// 待执行的命令
|
||||
/// </summary>
|
||||
public ICommand Command
|
||||
{
|
||||
get => (ICommand)GetValue(CommandProperty);
|
||||
set => SetValue(CommandProperty, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
// 由于卸载顺序问题,必须重写此方法才能正确触发命令
|
||||
if (Command != null && Command.CanExecute(null))
|
||||
{
|
||||
Command.Execute(null);
|
||||
}
|
||||
|
||||
base.OnDetaching();
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,8 @@ namespace Snap.Hutao.Control.Behavior;
|
||||
/// <summary>
|
||||
/// 打开附着的浮出控件操作
|
||||
/// </summary>
|
||||
internal class OpenAttachedFlyoutAction : DependencyObject, IAction
|
||||
[HighQuality]
|
||||
internal sealed class OpenAttachedFlyoutAction : DependencyObject, IAction
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public object Execute(object sender, object parameter)
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Snap.Hutao.Control;
|
||||
/// DependencyObject will dispose inner ReferenceTracker in any time
|
||||
/// when object is not used anymore.
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
public class BindingProxy : DependencyObject
|
||||
{
|
||||
private static readonly DependencyProperty DataContextProperty = Property<BindingProxy>.Depend<object>(nameof(DataContext));
|
||||
|
||||
26
src/Snap.Hutao/Snap.Hutao/Control/BoxedValues.cs
Normal file
26
src/Snap.Hutao/Snap.Hutao/Control/BoxedValues.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control;
|
||||
|
||||
/// <summary>
|
||||
/// 封装的值
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class BoxedValues
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="double"/> 0
|
||||
/// </summary>
|
||||
public static readonly object DoubleZero = 0D;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="double"/> 0
|
||||
/// </summary>
|
||||
public static readonly object DoubleOne = 1D;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="true"/>
|
||||
/// </summary>
|
||||
public static readonly object True = true;
|
||||
}
|
||||
@@ -8,6 +8,7 @@ namespace Snap.Hutao.Control.Extension;
|
||||
/// <summary>
|
||||
/// 对话框扩展
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class ContentDialogExtension
|
||||
{
|
||||
/// <summary>
|
||||
@@ -15,7 +16,7 @@ internal static class ContentDialogExtension
|
||||
/// </summary>
|
||||
/// <param name="contentDialog">对话框</param>
|
||||
/// <returns>用于恢复用户交互</returns>
|
||||
public static async ValueTask<IAsyncDisposable> BlockAsync(this ContentDialog contentDialog)
|
||||
public static async ValueTask<IDisposable> BlockAsync(this ContentDialog contentDialog)
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
contentDialog.ShowAsync().AsTask().SafeForget();
|
||||
@@ -25,7 +26,7 @@ internal static class ContentDialogExtension
|
||||
return new ContentDialogHider(contentDialog);
|
||||
}
|
||||
|
||||
private readonly struct ContentDialogHider : IAsyncDisposable
|
||||
private class ContentDialogHider : IDisposable
|
||||
{
|
||||
private readonly ContentDialog contentDialog;
|
||||
|
||||
@@ -34,12 +35,10 @@ internal static class ContentDialogExtension
|
||||
this.contentDialog = contentDialog;
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
public void Dispose()
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
|
||||
// Hide() must be called on main thread.
|
||||
contentDialog.Hide();
|
||||
ThreadHelper.InvokeOnMainThread(contentDialog.Hide);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,8 @@ namespace Snap.Hutao.Control.Image;
|
||||
/// <summary>
|
||||
/// 缓存图像
|
||||
/// </summary>
|
||||
public class CachedImage : ImageEx
|
||||
[HighQuality]
|
||||
internal sealed class CachedImage : ImageEx
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的缓存图像
|
||||
@@ -22,17 +23,20 @@ public class CachedImage : ImageEx
|
||||
{
|
||||
IsCacheEnabled = true;
|
||||
EnableLazyLoading = true;
|
||||
LazyLoadingThreshold = 500;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task<ImageSource?> ProvideCachedResourceAsync(Uri imageUri, CancellationToken token)
|
||||
{
|
||||
// We can only use Ioc to retrive IImageCache,
|
||||
// no IServiceProvider is available.
|
||||
IImageCache imageCache = Ioc.Default.GetRequiredService<IImageCache>();
|
||||
|
||||
try
|
||||
{
|
||||
Verify.Operation(imageUri.Host != string.Empty, SH.ControlImageCachedImageInvalidResourceUri);
|
||||
|
||||
// BitmapImage need to be created by main thread.
|
||||
string file = await imageCache.GetFileFromCacheAsync(imageUri).ConfigureAwait(true);
|
||||
|
||||
// check token state to determine whether the operation should be canceled.
|
||||
|
||||
@@ -10,8 +10,14 @@ namespace Snap.Hutao.Control.Image;
|
||||
/// <summary>
|
||||
/// 合成扩展
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class CompositionExtension
|
||||
{
|
||||
private const string Background = nameof(Background);
|
||||
private const string Foreground = nameof(Foreground);
|
||||
private const string Source = nameof(Source);
|
||||
private const string AlphaMask = nameof(AlphaMask);
|
||||
|
||||
/// <summary>
|
||||
/// 创建拼合图视觉对象
|
||||
/// </summary>
|
||||
@@ -41,15 +47,15 @@ internal static class CompositionExtension
|
||||
{
|
||||
BlendEffect effect = new()
|
||||
{
|
||||
Background = new CompositionEffectSourceParameter("Background"),
|
||||
Foreground = new CompositionEffectSourceParameter("Foreground"),
|
||||
Background = new CompositionEffectSourceParameter(Background),
|
||||
Foreground = new CompositionEffectSourceParameter(Foreground),
|
||||
Mode = blendEffectMode,
|
||||
};
|
||||
|
||||
CompositionEffectBrush brush = compositor.CreateEffectFactory(effect).CreateBrush();
|
||||
|
||||
brush.SetSourceParameter("Background", background);
|
||||
brush.SetSourceParameter("Foreground", foreground);
|
||||
brush.SetSourceParameter(Background, background);
|
||||
brush.SetSourceParameter(Foreground, foreground);
|
||||
|
||||
return brush;
|
||||
}
|
||||
@@ -66,12 +72,12 @@ internal static class CompositionExtension
|
||||
{
|
||||
GrayscaleEffect effect = new()
|
||||
{
|
||||
Source = new CompositionEffectSourceParameter("Source"),
|
||||
Source = new CompositionEffectSourceParameter(Source),
|
||||
};
|
||||
|
||||
CompositionEffectBrush brush = compositor.CreateEffectFactory(effect).CreateBrush();
|
||||
|
||||
brush.SetSourceParameter("Source", source);
|
||||
brush.SetSourceParameter(Source, source);
|
||||
|
||||
return brush;
|
||||
}
|
||||
@@ -88,12 +94,12 @@ internal static class CompositionExtension
|
||||
{
|
||||
LuminanceToAlphaEffect effect = new()
|
||||
{
|
||||
Source = new CompositionEffectSourceParameter("Source"),
|
||||
Source = new CompositionEffectSourceParameter(Source),
|
||||
};
|
||||
|
||||
CompositionEffectBrush brush = compositor.CreateEffectFactory(effect).CreateBrush();
|
||||
|
||||
brush.SetSourceParameter("Source", sourceBrush);
|
||||
brush.SetSourceParameter(Source, sourceBrush);
|
||||
|
||||
return brush;
|
||||
}
|
||||
@@ -112,14 +118,14 @@ internal static class CompositionExtension
|
||||
{
|
||||
AlphaMaskEffect maskEffect = new()
|
||||
{
|
||||
AlphaMask = new CompositionEffectSourceParameter("AlphaMask"),
|
||||
Source = new CompositionEffectSourceParameter("Source"),
|
||||
AlphaMask = new CompositionEffectSourceParameter(AlphaMask),
|
||||
Source = new CompositionEffectSourceParameter(Source),
|
||||
};
|
||||
|
||||
CompositionEffectBrush brush = compositor.CreateEffectFactory(maskEffect).CreateBrush();
|
||||
|
||||
brush.SetSourceParameter("AlphaMask", alphaMask);
|
||||
brush.SetSourceParameter("Source", sourceBrush);
|
||||
brush.SetSourceParameter(AlphaMask, alphaMask);
|
||||
brush.SetSourceParameter(Source, sourceBrush);
|
||||
|
||||
return brush;
|
||||
}
|
||||
@@ -172,25 +178,6 @@ internal static class CompositionExtension
|
||||
return brush;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的蒙版画刷
|
||||
/// </summary>
|
||||
/// <param name="compositor">合成器</param>
|
||||
/// <param name="source">源</param>
|
||||
/// <param name="mask">蒙版</param>
|
||||
/// <returns>蒙版画刷</returns>
|
||||
public static CompositionMaskBrush CompositeMaskBrush(
|
||||
this Compositor compositor,
|
||||
CompositionBrush source,
|
||||
CompositionBrush mask)
|
||||
{
|
||||
CompositionMaskBrush brush = compositor.CreateMaskBrush();
|
||||
brush.Source = source;
|
||||
brush.Mask = mask;
|
||||
|
||||
return brush;
|
||||
}
|
||||
|
||||
private static Vector2 GetStartPointOfDirection(GradientDirection direction)
|
||||
{
|
||||
return direction switch
|
||||
@@ -216,6 +203,4 @@ internal static class CompositionExtension
|
||||
_ => Vector2.Zero,
|
||||
};
|
||||
}
|
||||
|
||||
public record struct GradientStop(float Offset, Windows.UI.Color Color);
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,11 @@ namespace Snap.Hutao.Control.Image;
|
||||
/// 合成图像控件
|
||||
/// 为其他图像类控件提供基类
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
{
|
||||
private static readonly DependencyProperty SourceProperty = Property<CompositionImage>.Depend(nameof(Source), default(Uri), OnSourceChanged);
|
||||
private static readonly DependencyProperty EnableLazyLoadingProperty = Property<CompositionImage>.Depend(nameof(EnableLazyLoading), true);
|
||||
private static readonly DependencyProperty EnableLazyLoadingProperty = Property<CompositionImage>.DependBoxed<bool>(nameof(EnableLazyLoading), BoxedValues.True);
|
||||
private static readonly ConcurrentCancellationTokenSource<CompositionImage> LoadingTokenSource = new();
|
||||
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
@@ -62,8 +63,8 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
/// </summary>
|
||||
public bool EnableLazyLoading
|
||||
{
|
||||
get { return (bool)GetValue(EnableLazyLoadingProperty); }
|
||||
set { SetValue(EnableLazyLoadingProperty, value); }
|
||||
get => (bool)GetValue(EnableLazyLoadingProperty);
|
||||
set => SetValue(EnableLazyLoadingProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -98,7 +99,31 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
spriteVisual.Size = ActualSize;
|
||||
}
|
||||
|
||||
private static void OnApplyImageFailed(Uri? uri, Exception exception)
|
||||
private static void OnSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs arg)
|
||||
{
|
||||
CompositionImage image = (CompositionImage)sender;
|
||||
CancellationToken token = LoadingTokenSource.Register(image);
|
||||
IServiceProvider serviceProvider = image.serviceProvider;
|
||||
ILogger<CompositionImage> logger = serviceProvider.GetRequiredService<ILogger<CompositionImage>>();
|
||||
|
||||
// source is valid
|
||||
if (arg.NewValue is Uri inner && !string.IsNullOrEmpty(inner.OriginalString))
|
||||
{
|
||||
// value is different from old one
|
||||
if (inner != (arg.OldValue as Uri))
|
||||
{
|
||||
image
|
||||
.ApplyImageAsync(inner, token)
|
||||
.SafeForget(logger, ex => OnApplyImageFailed(serviceProvider, inner, ex));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
image.HideAsync(token).SafeForget(logger);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnApplyImageFailed(IServiceProvider serviceProvider, Uri? uri, Exception exception)
|
||||
{
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
|
||||
@@ -116,36 +141,15 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs arg)
|
||||
{
|
||||
CompositionImage image = (CompositionImage)sender;
|
||||
CancellationToken token = LoadingTokenSource.Register(image);
|
||||
ILogger<CompositionImage> logger = Ioc.Default.GetRequiredService<ILogger<CompositionImage>>();
|
||||
|
||||
// source is valid
|
||||
if (arg.NewValue is Uri inner && !string.IsNullOrEmpty(inner.Host))
|
||||
{
|
||||
// value is different from old one
|
||||
if (inner != (arg.OldValue as Uri))
|
||||
{
|
||||
image.ApplyImageInternalAsync(inner, token).SafeForget(logger, ex => OnApplyImageFailed(inner, ex));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
image.HideAsync(token).SafeForget(logger);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyImageInternalAsync(Uri? uri, CancellationToken token)
|
||||
private async Task ApplyImageAsync(Uri? uri, CancellationToken token)
|
||||
{
|
||||
await HideAsync(token).ConfigureAwait(true);
|
||||
|
||||
LoadedImageSurface? imageSurface = null;
|
||||
Compositor compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
|
||||
|
||||
if (uri != null)
|
||||
{
|
||||
LoadedImageSurface? imageSurface = null;
|
||||
Compositor compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
|
||||
|
||||
IImageCache imageCache = serviceProvider.GetRequiredService<IImageCache>();
|
||||
string file = await imageCache.GetFileFromCacheAsync(uri).ConfigureAwait(true);
|
||||
|
||||
@@ -177,6 +181,8 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
{
|
||||
if (!isShow)
|
||||
{
|
||||
isShow = true;
|
||||
|
||||
if (EnableLazyLoading)
|
||||
{
|
||||
await AnimationBuilder.Create().Opacity(1d, 0d).StartAsync(this, token).ConfigureAwait(true);
|
||||
@@ -185,8 +191,6 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
{
|
||||
Opacity = 1;
|
||||
}
|
||||
|
||||
isShow = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,6 +198,8 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
{
|
||||
if (isShow)
|
||||
{
|
||||
isShow = false;
|
||||
|
||||
if (EnableLazyLoading)
|
||||
{
|
||||
await AnimationBuilder.Create().Opacity(0d, 1d).StartAsync(this, token).ConfigureAwait(true);
|
||||
@@ -202,8 +208,6 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
{
|
||||
Opacity = 0;
|
||||
}
|
||||
|
||||
isShow = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.UI;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Snap.Hutao.Win32;
|
||||
using System.IO;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage.Streams;
|
||||
@@ -12,9 +13,10 @@ using Windows.Storage.Streams;
|
||||
namespace Snap.Hutao.Control.Image;
|
||||
|
||||
/// <summary>
|
||||
/// 支持渐变的图像
|
||||
/// 渐变图像
|
||||
/// </summary>
|
||||
public class Gradient : CompositionImage
|
||||
[HighQuality]
|
||||
internal sealed class Gradient : CompositionImage
|
||||
{
|
||||
private static readonly DependencyProperty BackgroundDirectionProperty = Property<Gradient>.Depend(nameof(BackgroundDirection), GradientDirection.TopToBottom);
|
||||
private static readonly DependencyProperty ForegroundDirectionProperty = Property<Gradient>.Depend(nameof(ForegroundDirection), GradientDirection.TopToBottom);
|
||||
@@ -44,7 +46,7 @@ public class Gradient : CompositionImage
|
||||
{
|
||||
if (spriteVisual is not null)
|
||||
{
|
||||
Height = (double)Math.Clamp(ActualWidth / imageAspectRatio, 0, MaxHeight);
|
||||
Height = Math.Clamp(ActualWidth / imageAspectRatio, 0D, MaxHeight);
|
||||
spriteVisual.Size = ActualSize;
|
||||
}
|
||||
}
|
||||
@@ -52,19 +54,11 @@ public class Gradient : CompositionImage
|
||||
/// <inheritdoc/>
|
||||
protected override async Task<LoadedImageSurface> LoadImageSurfaceAsync(string file, CancellationToken token)
|
||||
{
|
||||
using (FileStream fileStream = new(file, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
using (IRandomAccessStream imageStream = fileStream.AsRandomAccessStream())
|
||||
{
|
||||
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(imageStream);
|
||||
imageAspectRatio = decoder.PixelWidth / (double)decoder.PixelHeight;
|
||||
}
|
||||
}
|
||||
|
||||
TaskCompletionSource loadCompleteTaskSource = new();
|
||||
LoadedImageSurface surface = LoadedImageSurface.StartLoadFromUri(new(file));
|
||||
surface.LoadCompleted += (s, e) => loadCompleteTaskSource.TrySetResult();
|
||||
await loadCompleteTaskSource.Task.ConfigureAwait(true);
|
||||
imageAspectRatio = surface.NaturalSize.AspectRatio();
|
||||
return surface;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace Snap.Hutao.Control.Image;
|
||||
/// <summary>
|
||||
/// 渐变方向
|
||||
/// </summary>
|
||||
public enum GradientDirection
|
||||
[HighQuality]
|
||||
internal enum GradientDirection
|
||||
{
|
||||
/// <summary>
|
||||
/// 下到上
|
||||
|
||||
34
src/Snap.Hutao/Snap.Hutao/Control/Image/GradientStop.cs
Normal file
34
src/Snap.Hutao/Snap.Hutao/Control/Image/GradientStop.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Image;
|
||||
|
||||
/// <summary>
|
||||
/// 渐变锚点
|
||||
/// </summary>
|
||||
/// <param name="Offset">便宜</param>
|
||||
/// <param name="Color">颜色</param>
|
||||
[HighQuality]
|
||||
internal struct GradientStop
|
||||
{
|
||||
/// <summary>
|
||||
/// 便宜
|
||||
/// </summary>
|
||||
public float Offset;
|
||||
|
||||
/// <summary>
|
||||
/// 颜色
|
||||
/// </summary>
|
||||
public Windows.UI.Color Color;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的渐变锚点
|
||||
/// </summary>
|
||||
/// <param name="offset">偏移</param>
|
||||
/// <param name="color">颜色</param>
|
||||
public GradientStop(float offset, Windows.UI.Color color)
|
||||
{
|
||||
Offset = offset;
|
||||
Color = color;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,8 @@ namespace Snap.Hutao.Control.Image;
|
||||
/// <summary>
|
||||
/// 支持单色的图像
|
||||
/// </summary>
|
||||
public class MonoChrome : CompositionImage
|
||||
[HighQuality]
|
||||
internal sealed class MonoChrome : CompositionImage
|
||||
{
|
||||
private CompositionColorBrush? backgroundBrush;
|
||||
|
||||
@@ -56,7 +57,7 @@ public class MonoChrome : CompositionImage
|
||||
{
|
||||
ApplicationTheme.Light => Colors.Black,
|
||||
ApplicationTheme.Dark => Colors.White,
|
||||
_ => throw Must.NeverHappen(),
|
||||
_ => Colors.Transparent,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,9 @@ namespace Snap.Hutao.Control.Markup;
|
||||
/// <summary>
|
||||
/// Custom <see cref="Markup"/> which can provide <see cref="BitmapIcon"/> values.
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[MarkupExtensionReturnType(ReturnType = typeof(BitmapIcon))]
|
||||
public sealed class BitmapIconExtension : MarkupExtension
|
||||
internal sealed class BitmapIconExtension : MarkupExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Uri"/> representing the image to display.
|
||||
|
||||
@@ -9,8 +9,9 @@ namespace Snap.Hutao.Control.Markup;
|
||||
/// <summary>
|
||||
/// Custom <see cref="MarkupExtension"/> which can provide <see cref="FontIcon"/> values.
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[MarkupExtensionReturnType(ReturnType = typeof(FontIcon))]
|
||||
internal class FontIconExtension : MarkupExtension
|
||||
internal sealed class FontIconExtension : MarkupExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="string"/> value representing the icon to display.
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Markup;
|
||||
|
||||
namespace Snap.Hutao.Control.Markup;
|
||||
|
||||
/// <summary>
|
||||
/// Custom <see cref="MarkupExtension"/> which can provide <see cref="FontIcon"/> values.
|
||||
/// </summary>
|
||||
[MarkupExtensionReturnType(ReturnType = typeof(FontIcon))]
|
||||
internal class FontIconSourceExtension : MarkupExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="string"/> value representing the icon to display.
|
||||
/// </summary>
|
||||
public string Glyph { get; set; } = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override object ProvideValue()
|
||||
{
|
||||
return new FontIconSource()
|
||||
{
|
||||
Glyph = Glyph,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -8,29 +8,18 @@ namespace Snap.Hutao.Control.Markup;
|
||||
/// <summary>
|
||||
/// Xaml extension to return a <see cref="string"/> value from resource file associated with a resource key
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[MarkupExtensionReturnType(ReturnType = typeof(string))]
|
||||
public sealed class ResourceStringExtension : MarkupExtension
|
||||
internal sealed class ResourceStringExtension : MarkupExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets associated ID from resource strings.
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a string value from resource file associated with a resource key.
|
||||
/// </summary>
|
||||
/// <param name="name">Resource key name.</param>
|
||||
/// <returns>A string value from resource file associated with a resource key.</returns>
|
||||
public static string GetValue(string name)
|
||||
{
|
||||
// This function is needed to accomodate compiled function usage without second paramater,
|
||||
// which doesn't work with optional values.
|
||||
return SH.ResourceManager.GetString(name)!;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override object ProvideValue()
|
||||
{
|
||||
return GetValue(Name ?? string.Empty);
|
||||
return SH.ResourceManager.GetString(Name ?? string.Empty) ?? Name ?? string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,9 @@ namespace Snap.Hutao.Control.Media;
|
||||
/// <summary>
|
||||
/// BGRA8 结构
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct Bgra8
|
||||
internal struct Bgra8
|
||||
{
|
||||
/// <summary>
|
||||
/// B
|
||||
|
||||
@@ -12,8 +12,9 @@ namespace Snap.Hutao.Control.Media;
|
||||
/// <summary>
|
||||
/// RGBA 颜色
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct Rgba8
|
||||
internal struct Rgba8
|
||||
{
|
||||
/// <summary>
|
||||
/// R
|
||||
@@ -48,7 +49,6 @@ public struct Rgba8
|
||||
/// <param name="hex">色值字符串</param>
|
||||
public Rgba8(ReadOnlySpan<char> hex)
|
||||
{
|
||||
Must.Argument(hex.Length == 8, "色值长度不为8");
|
||||
R = 0;
|
||||
G = 0;
|
||||
B = 0;
|
||||
|
||||
@@ -12,7 +12,8 @@ namespace Snap.Hutao.Control.Media;
|
||||
/// <summary>
|
||||
/// 软件位图拓展
|
||||
/// </summary>
|
||||
public static class SoftwareBitmapExtension
|
||||
[HighQuality]
|
||||
internal static class SoftwareBitmapExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 混合模式 正常
|
||||
|
||||
@@ -9,12 +9,13 @@ namespace Snap.Hutao.Control.Panel;
|
||||
/// <summary>
|
||||
/// 纵横比控件
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal class AspectRatio : Microsoft.UI.Xaml.Controls.Control
|
||||
{
|
||||
private const double Epsilon = 2.2204460492503131e-016;
|
||||
|
||||
private static readonly DependencyProperty TargetWidthProperty = Property<AspectRatio>.Depend(nameof(TargetWidth), 1D);
|
||||
private static readonly DependencyProperty TargetHeightProperty = Property<AspectRatio>.Depend(nameof(TargetHeight), 1D);
|
||||
private static readonly DependencyProperty TargetWidthProperty = Property<AspectRatio>.DependBoxed<double>(nameof(TargetWidth), BoxedValues.DoubleOne);
|
||||
private static readonly DependencyProperty TargetHeightProperty = Property<AspectRatio>.DependBoxed<double>(nameof(TargetHeight), BoxedValues.DoubleOne);
|
||||
|
||||
/// <summary>
|
||||
/// 目标宽度
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
<UserControl
|
||||
<SplitButton
|
||||
x:Class="Snap.Hutao.Control.Panel.PanelSelector"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
Name="RootSplitButton"
|
||||
Padding="0,6"
|
||||
Click="SplitButtonClick"
|
||||
Loaded="OnRootControlLoaded"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<SplitButton
|
||||
Name="RootSplitButton"
|
||||
Padding="0,6"
|
||||
Click="SplitButtonClick">
|
||||
<SplitButton.Content>
|
||||
<FontIcon Name="IconPresenter" Glyph=""/>
|
||||
</SplitButton.Content>
|
||||
<SplitButton.Flyout>
|
||||
<MenuFlyout>
|
||||
<RadioMenuFlyoutItem
|
||||
Click="RadioMenuFlyoutItemClick"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Tag="List"
|
||||
Text="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownListName}"/>
|
||||
<RadioMenuFlyoutItem
|
||||
Click="RadioMenuFlyoutItemClick"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Tag="Grid"
|
||||
Text="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownGridName}"/>
|
||||
</MenuFlyout>
|
||||
</SplitButton.Flyout>
|
||||
</SplitButton>
|
||||
</UserControl>
|
||||
<SplitButton.Content>
|
||||
<FontIcon Name="IconPresenter" Glyph=""/>
|
||||
</SplitButton.Content>
|
||||
<SplitButton.Flyout>
|
||||
<MenuFlyout>
|
||||
<RadioMenuFlyoutItem
|
||||
Click="RadioMenuFlyoutItemClick"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Tag="List"
|
||||
Text="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownListName}"/>
|
||||
<RadioMenuFlyoutItem
|
||||
Click="RadioMenuFlyoutItemClick"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Tag="Grid"
|
||||
Text="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownGridName}"/>
|
||||
</MenuFlyout>
|
||||
</SplitButton.Flyout>
|
||||
</SplitButton>
|
||||
|
||||
@@ -9,9 +9,12 @@ namespace Snap.Hutao.Control.Panel;
|
||||
/// <summary>
|
||||
/// 面板选择器
|
||||
/// </summary>
|
||||
public sealed partial class PanelSelector : UserControl
|
||||
[HighQuality]
|
||||
internal sealed partial class PanelSelector : SplitButton
|
||||
{
|
||||
private static readonly DependencyProperty CurrentProperty = Property<PanelSelector>.Depend(nameof(Current), "List", OnCurrentChanged);
|
||||
private const string List = nameof(List);
|
||||
|
||||
private static readonly DependencyProperty CurrentProperty = Property<PanelSelector>.Depend(nameof(Current), List, OnCurrentChanged);
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的面板选择器
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Snap.Hutao.Control;
|
||||
/// 快速创建 <see cref="TOwner"/> 的 <see cref="DependencyProperty"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="TOwner">所有者的类型</typeparam>
|
||||
[HighQuality]
|
||||
internal static class Property<TOwner>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -34,6 +35,18 @@ internal static class Property<TOwner>
|
||||
return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new(defaultValue));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册依赖属性
|
||||
/// </summary>
|
||||
/// <typeparam name="TProperty">属性的类型</typeparam>
|
||||
/// <param name="name">属性名称</param>
|
||||
/// <param name="defaultValue">封装的默认值</param>
|
||||
/// <returns>注册的依赖属性</returns>
|
||||
public static DependencyProperty DependBoxed<TProperty>(string name, object defaultValue)
|
||||
{
|
||||
return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new(defaultValue));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册依赖属性
|
||||
/// </summary>
|
||||
|
||||
@@ -13,8 +13,9 @@ namespace Snap.Hutao.Control;
|
||||
/// 表示支持取消加载的异步页面
|
||||
/// 在被导航到其他页面前触发取消异步通知
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[SuppressMessage("", "CA1001")]
|
||||
public class ScopedPage : Page
|
||||
internal class ScopedPage : Page
|
||||
{
|
||||
// Allow GC to Collect the IServiceScope
|
||||
private static readonly WeakReference<IServiceScope> PreviousScopeReference = new(null!);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
// Some part of this file came from:
|
||||
// https://github.com/xunkong/desktop/tree/main/src/Desktop/Desktop/Pages/CharacterInfoPage.xaml.cs
|
||||
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Xaml;
|
||||
@@ -16,8 +14,11 @@ namespace Snap.Hutao.Control.Text;
|
||||
|
||||
/// <summary>
|
||||
/// 专用于呈现描述文本的文本块
|
||||
/// Some part of this file came from:
|
||||
/// https://github.com/xunkong/desktop/tree/main/src/Desktop/Desktop/Pages/CharacterInfoPage.xaml.cs
|
||||
/// </summary>
|
||||
public class DescriptionTextBlock : ContentControl
|
||||
[HighQuality]
|
||||
internal sealed class DescriptionTextBlock : ContentControl
|
||||
{
|
||||
private static readonly DependencyProperty DescriptionProperty = Property<DescriptionTextBlock>.Depend(nameof(Description), string.Empty, OnDescriptionChanged);
|
||||
|
||||
@@ -33,6 +34,7 @@ public class DescriptionTextBlock : ContentControl
|
||||
public DescriptionTextBlock()
|
||||
{
|
||||
IsTabStop = false;
|
||||
|
||||
Content = new TextBlock()
|
||||
{
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Snap.Hutao.Control.Theme;
|
||||
/// <summary>
|
||||
/// 主题帮助工具类
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
public static class ThemeHelper
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Snap.Hutao.Control;
|
||||
/// </summary>
|
||||
/// <typeparam name="TFrom">源类型</typeparam>
|
||||
/// <typeparam name="TTo">目标类型</typeparam>
|
||||
public abstract class ValueConverter<TFrom, TTo> : IValueConverter
|
||||
internal abstract class ValueConverter<TFrom, TTo> : IValueConverter
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public object? Convert(object value, Type targetType, object parameter, string language)
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
|
||||
namespace Snap.Hutao.Core.Abstraction;
|
||||
|
||||
[HighQuality]
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public abstract class DisposableObject : IDisposable
|
||||
internal abstract class DisposableObject : IDisposable
|
||||
{
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Snap.Hutao.Core.Abstraction;
|
||||
/// </summary>
|
||||
/// <typeparam name="T1">元组的第一个类型</typeparam>
|
||||
/// <typeparam name="T2">元组的第二个类型</typeparam>
|
||||
[HighQuality]
|
||||
internal interface IDeconstructable<T1, T2>
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -7,7 +7,8 @@ namespace Snap.Hutao.Core.Abstraction;
|
||||
/// 有名称的对象
|
||||
/// 指示该对象可通过名称区分
|
||||
/// </summary>
|
||||
internal interface INamed
|
||||
[HighQuality]
|
||||
internal interface INamedService
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Annotation;
|
||||
|
||||
/// <summary>
|
||||
/// 高质量代码
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
internal class HighQualityAttribute : Attribute
|
||||
{
|
||||
}
|
||||
@@ -6,6 +6,7 @@ namespace Snap.Hutao.Core.Annotation;
|
||||
/// <summary>
|
||||
/// 本地化键
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
internal class LocalizationKeyAttribute : Attribute
|
||||
{
|
||||
@@ -22,4 +23,4 @@ internal class LocalizationKeyAttribute : Attribute
|
||||
/// 键
|
||||
/// </summary>
|
||||
public string Key { get; }
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ namespace Snap.Hutao.Core.Caching;
|
||||
/// 为图像缓存提供抽象
|
||||
/// </summary>
|
||||
/// <typeparam name="T">缓存类型</typeparam>
|
||||
[HighQuality]
|
||||
internal interface IImageCache
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Snap.Hutao.Core.Caching;
|
||||
/// <summary>
|
||||
/// 图像缓存 文件路径操作
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal interface IImageCacheFilePathOperation
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
@@ -18,10 +16,11 @@ namespace Snap.Hutao.Core.Caching;
|
||||
/// Provides methods and tools to cache files in a folder
|
||||
/// The class's name will become the cache folder's name
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Injection(InjectAs.Singleton, typeof(IImageCache))]
|
||||
[HttpClient(HttpClientConfigration.Default)]
|
||||
[PrimaryHttpMessageHandler(MaxConnectionsPerServer = 8)]
|
||||
public class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
public sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
{
|
||||
private const string CacheFolderName = nameof(ImageCache);
|
||||
|
||||
@@ -89,7 +88,7 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
foreach (Uri uri in uriForCachedItems)
|
||||
{
|
||||
string filePath = Path.Combine(folder, GetCacheFileName(uri));
|
||||
if (files.Contains(filePath))
|
||||
if (Array.IndexOf(files, filePath) >= 0)
|
||||
{
|
||||
filesToDelete.Add(filePath);
|
||||
}
|
||||
@@ -113,13 +112,12 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
{
|
||||
await DownloadFileAsync(uri, filePath).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
else if (concurrentTasks.TryGetValue(fileName, out Task? task))
|
||||
{
|
||||
if (concurrentTasks.TryGetValue(fileName, out Task? task))
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
concurrentTasks.TryRemove(fileName, out _);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -173,7 +171,7 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
|
||||
private async Task DownloadFileAsync(Uri uri, string baseFile)
|
||||
{
|
||||
logger.LogInformation(EventIds.FileCaching, "Begin downloading for {uri}", uri);
|
||||
logger.LogInformation("Begin downloading for {uri}", uri);
|
||||
|
||||
int retryCount = 0;
|
||||
while (retryCount < 6)
|
||||
|
||||
@@ -8,7 +8,8 @@ namespace Snap.Hutao.Core;
|
||||
/// <summary>
|
||||
/// 命令行建造器
|
||||
/// </summary>
|
||||
public class CommandLineBuilder
|
||||
[HighQuality]
|
||||
internal sealed class CommandLineBuilder
|
||||
{
|
||||
private const char WhiteSpace = ' ';
|
||||
private readonly Dictionary<string, string?> options = new();
|
||||
@@ -56,6 +57,7 @@ public class CommandLineBuilder
|
||||
{
|
||||
s.Append(WhiteSpace);
|
||||
s.Append(key);
|
||||
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
s.Append(WhiteSpace);
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Snap.Hutao.Core;
|
||||
/// <summary>
|
||||
/// 支持Md5转换
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class Convert
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Snap.Hutao.Core;
|
||||
/// <summary>
|
||||
/// 核心环境参数
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class CoreEnvironment
|
||||
{
|
||||
/// <summary>
|
||||
@@ -27,7 +28,7 @@ internal static class CoreEnvironment
|
||||
/// <summary>
|
||||
/// 米游社移动端请求UA
|
||||
/// </summary>
|
||||
public const string HoyolabMobileUA = $"Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/106.0.5249.126 Mobile Safari/537.36 miHoYoBBS/{HoyolabXrpcVersion}";
|
||||
public const string HoyolabMobileUA = $"Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/{HoyolabXrpcVersion}";
|
||||
|
||||
/// <summary>
|
||||
/// 米游社 Rpc 版本
|
||||
@@ -38,7 +39,7 @@ internal static class CoreEnvironment
|
||||
/// 盐
|
||||
/// </summary>
|
||||
// https://github.com/UIGF-org/Hoyolab.Salt
|
||||
public static readonly ImmutableDictionary<SaltType, string> DynamicSecrets = new Dictionary<SaltType, string>()
|
||||
public static readonly ImmutableDictionary<SaltType, string> DynamicSecretSalts = new Dictionary<SaltType, string>()
|
||||
{
|
||||
[SaltType.K2] = "dZAwGk4e9aC0MXXItkwnHamjA1x30IYw",
|
||||
[SaltType.LK2] = "IEIZiKYaput2OCKQprNuGsog1NZc1FkS",
|
||||
@@ -47,41 +48,6 @@ internal static class CoreEnvironment
|
||||
[SaltType.PROD] = "JwYDpKvLj6MrMqqYU6jTKF17KNO2PXoS",
|
||||
}.ToImmutableDictionary();
|
||||
|
||||
/// <summary>
|
||||
/// 标准UA
|
||||
/// </summary>
|
||||
public static readonly string CommonUA;
|
||||
|
||||
/// <summary>
|
||||
/// 当前版本
|
||||
/// </summary>
|
||||
public static readonly Version Version;
|
||||
|
||||
/// <summary>
|
||||
/// 米游社设备Id
|
||||
/// </summary>
|
||||
public static readonly string HoyolabDeviceId;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃设备Id
|
||||
/// </summary>
|
||||
public static readonly string HutaoDeviceId;
|
||||
|
||||
/// <summary>
|
||||
/// 包家族名称
|
||||
/// </summary>
|
||||
public static readonly string FamilyName;
|
||||
|
||||
/// <summary>
|
||||
/// 安装位置
|
||||
/// </summary>
|
||||
public static readonly string InstalledLocation;
|
||||
|
||||
/// <summary>
|
||||
/// 数据文件夹
|
||||
/// </summary>
|
||||
public static readonly string DataFolder;
|
||||
|
||||
/// <summary>
|
||||
/// 默认的Json序列化选项
|
||||
/// </summary>
|
||||
@@ -100,6 +66,41 @@ internal static class CoreEnvironment
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 当前版本
|
||||
/// </summary>
|
||||
public static readonly Version Version;
|
||||
|
||||
/// <summary>
|
||||
/// 标准UA
|
||||
/// </summary>
|
||||
public static readonly string CommonUA;
|
||||
|
||||
/// <summary>
|
||||
/// 数据文件夹
|
||||
/// </summary>
|
||||
public static readonly string DataFolder;
|
||||
|
||||
/// <summary>
|
||||
/// 包家族名称
|
||||
/// </summary>
|
||||
public static readonly string FamilyName;
|
||||
|
||||
/// <summary>
|
||||
/// 米游社设备Id
|
||||
/// </summary>
|
||||
public static readonly string HoyolabDeviceId;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃设备Id
|
||||
/// </summary>
|
||||
public static readonly string HutaoDeviceId;
|
||||
|
||||
/// <summary>
|
||||
/// 安装位置
|
||||
/// </summary>
|
||||
public static readonly string InstalledLocation;
|
||||
|
||||
private const string CryptographyKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\";
|
||||
private const string MachineGuidValue = "MachineGuid";
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ namespace Snap.Hutao.Core.Database;
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体的类型</typeparam>
|
||||
/// <typeparam name="TMessage">消息的类型</typeparam>
|
||||
internal class DbCurrent<TEntity, TMessage>
|
||||
[HighQuality]
|
||||
internal sealed class DbCurrent<TEntity, TMessage>
|
||||
where TEntity : class, ISelectable
|
||||
where TMessage : Message.ValueChangedMessage<TEntity>, new()
|
||||
{
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.Core.Database;
|
||||
|
||||
/// <summary>
|
||||
/// 数据库集合扩展
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
public static class DbSetExtension
|
||||
{
|
||||
/// <summary>
|
||||
@@ -17,6 +19,7 @@ public static class DbSetExtension
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <returns>对应的数据库上下文</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static DbContext Context<TEntity>(this DbSet<TEntity> dbSet)
|
||||
where TEntity : class
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Snap.Hutao.Core.Database;
|
||||
/// <summary>
|
||||
/// 可枚举扩展
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
public static class EnumerableExtension
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -5,8 +5,11 @@ namespace Snap.Hutao.Core.Database;
|
||||
|
||||
/// <summary>
|
||||
/// 可选择的项
|
||||
/// 若要使用 <see cref="DbCurrent{TEntity, TMessage}"/> 必须实现该接口
|
||||
/// 若要使用 <see cref="DbCurrent{TEntity, TMessage}"/>
|
||||
/// 或 <see cref="ScopedDbCurrent{TEntity, TMessage}"/>
|
||||
/// 必须实现该接口
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
public interface ISelectable
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq.Expressions;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.Core.Database;
|
||||
|
||||
/// <summary>
|
||||
/// 可查询扩展
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
public static class QueryableExtension
|
||||
{
|
||||
/// <summary>
|
||||
@@ -19,6 +21,7 @@ public static class QueryableExtension
|
||||
/// <param name="predicate">条件</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>SQL返回个数</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Task<int> ExecuteDeleteWhereAsync<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate, CancellationToken token = default)
|
||||
{
|
||||
return source.Where(predicate).ExecuteDeleteAsync(token);
|
||||
|
||||
@@ -13,12 +13,12 @@ namespace Snap.Hutao.Core.Database;
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体的类型</typeparam>
|
||||
/// <typeparam name="TMessage">消息的类型</typeparam>
|
||||
internal class ScopedDbCurrent<TEntity, TMessage>
|
||||
internal sealed class ScopedDbCurrent<TEntity, TMessage>
|
||||
where TEntity : class, ISelectable
|
||||
where TMessage : Message.ValueChangedMessage<TEntity>, new()
|
||||
{
|
||||
private readonly IServiceScopeFactory scopeFactory;
|
||||
private readonly Func<IServiceProvider, DbSet<TEntity>> dbSetFunc;
|
||||
private readonly Func<IServiceProvider, DbSet<TEntity>> dbSetSelector;
|
||||
private readonly IMessenger messenger;
|
||||
|
||||
private TEntity? current;
|
||||
@@ -27,12 +27,12 @@ internal class ScopedDbCurrent<TEntity, TMessage>
|
||||
/// 构造一个新的数据库当前项
|
||||
/// </summary>
|
||||
/// <param name="scopeFactory">范围工厂</param>
|
||||
/// <param name="dbSetFunc">数据集</param>
|
||||
/// <param name="dbSetSelector">数据集选择器</param>
|
||||
/// <param name="messenger">消息器</param>
|
||||
public ScopedDbCurrent(IServiceScopeFactory scopeFactory, Func<IServiceProvider, DbSet<TEntity>> dbSetFunc, IMessenger messenger)
|
||||
public ScopedDbCurrent(IServiceScopeFactory scopeFactory, Func<IServiceProvider, DbSet<TEntity>> dbSetSelector, IMessenger messenger)
|
||||
{
|
||||
this.scopeFactory = scopeFactory;
|
||||
this.dbSetFunc = dbSetFunc;
|
||||
this.dbSetSelector = dbSetSelector;
|
||||
this.messenger = messenger;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ internal class ScopedDbCurrent<TEntity, TMessage>
|
||||
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
DbSet<TEntity> dbSet = dbSetFunc(scope.ServiceProvider);
|
||||
DbSet<TEntity> dbSet = dbSetSelector(scope.ServiceProvider);
|
||||
|
||||
// only update when not processing a deletion
|
||||
if (value != null)
|
||||
|
||||
@@ -9,18 +9,9 @@ namespace Snap.Hutao.Core.Database;
|
||||
/// <summary>
|
||||
/// 设置帮助类
|
||||
/// </summary>
|
||||
public static class SettingEntryHelper
|
||||
[HighQuality]
|
||||
public static class SettingEntryExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// "True"
|
||||
/// </summary>
|
||||
public static readonly string TrueString = true.ToString();
|
||||
|
||||
/// <summary>
|
||||
/// "False"
|
||||
/// </summary>
|
||||
public static readonly string FalseString = false.ToString();
|
||||
|
||||
/// <summary>
|
||||
/// 获取或添加一个对应的设置
|
||||
/// </summary>
|
||||
@@ -35,8 +26,7 @@ public static class SettingEntryHelper
|
||||
if (entry == null)
|
||||
{
|
||||
entry = new(key, value);
|
||||
dbSet.Add(entry);
|
||||
dbSet.Context().SaveChanges();
|
||||
dbSet.AddAndSave(entry);
|
||||
}
|
||||
|
||||
return entry;
|
||||
@@ -11,6 +11,7 @@ namespace Snap.Hutao.Core.DependencyInjection;
|
||||
/// <summary>
|
||||
/// <see cref="Ioc"/> 配置
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class IocConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Snap.Hutao.Core.DependencyInjection;
|
||||
/// <summary>
|
||||
/// <see cref="Ioc"/> 与 <see cref="HttpClient"/> 配置
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static partial class IocHttpClientConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Snap.Hutao.Core.DependencyInjection;
|
||||
/// 服务管理器
|
||||
/// 依赖注入的核心管理类
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static partial class ServiceCollectionExtension
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
|
||||
@@ -11,7 +10,8 @@ namespace Snap.Hutao.Core.ExceptionService;
|
||||
/// <summary>
|
||||
/// 异常记录器
|
||||
/// </summary>
|
||||
internal class ExceptionRecorder
|
||||
[HighQuality]
|
||||
internal sealed class ExceptionRecorder
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
|
||||
@@ -49,6 +49,6 @@ internal class ExceptionRecorder
|
||||
|
||||
private void OnXamlBindingFailed(object? sender, BindingFailedEventArgs e)
|
||||
{
|
||||
logger.LogCritical(EventIds.XamlBindingError, "XAML绑定失败: {message}", e.Message);
|
||||
logger.LogCritical("XAML绑定失败: {message}", e.Message);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,8 @@ namespace Snap.Hutao.Core.ExceptionService;
|
||||
/// 运行环境异常
|
||||
/// 用户的计算机中的某些设置不符合要求
|
||||
/// </summary>
|
||||
internal class RuntimeEnvironmentException : Exception
|
||||
[HighQuality]
|
||||
internal sealed class RuntimeEnvironmentException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的运行环境异常
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Snap.Hutao.Core.ExceptionService;
|
||||
/// <summary>
|
||||
/// 帮助更好的抛出异常
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[System.Diagnostics.StackTraceHidden]
|
||||
internal static class ThrowHelper
|
||||
{
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace Snap.Hutao.Core.ExceptionService;
|
||||
/// <summary>
|
||||
/// 用户数据损坏异常
|
||||
/// </summary>
|
||||
internal class UserdataCorruptedException : Exception
|
||||
[HighQuality]
|
||||
internal sealed class UserdataCorruptedException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的用户数据损坏异常
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Snap.Hutao.Core.ExpressionService;
|
||||
/// Class to cast to type <see cref="TTo"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="TTo">Target type</typeparam>
|
||||
[HighQuality]
|
||||
public static class CastTo<TTo>
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -14,15 +14,16 @@ namespace Snap.Hutao.Core.IO.Bits;
|
||||
/// <summary>
|
||||
/// BITS Job
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[SuppressMessage("", "SA1600")]
|
||||
internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
internal sealed class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
{
|
||||
/// <summary>
|
||||
/// 任务名称前缀
|
||||
/// </summary>
|
||||
public const string JobNamePrefix = "SnapHutaoBitsJob";
|
||||
|
||||
private const uint Timeout = 44;
|
||||
private const uint Timeout = 29;
|
||||
private const int MaxResumeAttempts = 3;
|
||||
|
||||
private readonly string displayName;
|
||||
@@ -47,26 +48,26 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
|
||||
public static BitsJob CreateJob(IServiceProvider serviceProvider, IBackgroundCopyManager backgroundCopyManager, Uri uri, string filePath)
|
||||
{
|
||||
ILogger<BitsJob> service = serviceProvider.GetRequiredService<ILogger<BitsJob>>();
|
||||
string text = $"{JobNamePrefix} - {uri}";
|
||||
IBackgroundCopyJob ppJob;
|
||||
ILogger<BitsJob> logger = serviceProvider.GetRequiredService<ILogger<BitsJob>>();
|
||||
string jobName = $"{JobNamePrefix} - {uri}";
|
||||
IBackgroundCopyJob job;
|
||||
try
|
||||
{
|
||||
backgroundCopyManager.CreateJob(text, BG_JOB_TYPE.BG_JOB_TYPE_DOWNLOAD, out Guid _, out ppJob);
|
||||
backgroundCopyManager.CreateJob(jobName, BG_JOB_TYPE.BG_JOB_TYPE_DOWNLOAD, out Guid _, out job);
|
||||
|
||||
// BG_NOTIFY_JOB_TRANSFERRED & BG_NOTIFY_JOB_ERROR & BG_NOTIFY_JOB_MODIFICATION
|
||||
ppJob.SetNotifyFlags(0b1011);
|
||||
ppJob.SetNoProgressTimeout(Timeout);
|
||||
ppJob.SetPriority(BG_JOB_PRIORITY.BG_JOB_PRIORITY_FOREGROUND);
|
||||
ppJob.SetProxySettings(BG_JOB_PROXY_USAGE.BG_JOB_PROXY_USAGE_AUTODETECT, null, null);
|
||||
job.SetNotifyFlags(0B1011);
|
||||
job.SetNoProgressTimeout(Timeout);
|
||||
job.SetPriority(BG_JOB_PRIORITY.BG_JOB_PRIORITY_FOREGROUND);
|
||||
job.SetProxySettings(BG_JOB_PROXY_USAGE.BG_JOB_PROXY_USAGE_AUTODETECT, default, default);
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
service.LogInformation("Failed to create job. {message}", ex.Message);
|
||||
logger.LogInformation("Failed to create job. {message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
|
||||
BitsJob bitsJob = new(serviceProvider, text, ppJob);
|
||||
BitsJob bitsJob = new(serviceProvider, jobName, job);
|
||||
bitsJob.InitJob(uri.AbsoluteUri, filePath);
|
||||
return bitsJob;
|
||||
}
|
||||
@@ -115,7 +116,9 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
if (state == BG_JOB_STATE.BG_JOB_STATE_TRANSIENT_ERROR)
|
||||
{
|
||||
HRESULT errorCode = GetErrorCode(job);
|
||||
if (errorCode == -2145844944)
|
||||
|
||||
// BG_E_HTTP_ERROR_304
|
||||
if (errorCode == 0x80190130)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
CompleteOrCancel();
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Snap.Hutao.Core.IO.Bits;
|
||||
/// <summary>
|
||||
/// BITS 管理器
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal class BitsManager
|
||||
{
|
||||
@@ -25,8 +26,8 @@ internal class BitsManager
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
public BitsManager(IServiceProvider serviceProvider)
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
logger = serviceProvider.GetRequiredService<ILogger<BitsManager>>();
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -69,10 +70,14 @@ internal class BitsManager
|
||||
{
|
||||
uint actualFetched = 0;
|
||||
pJobs.Next(1, out IBackgroundCopyJob pJob, ref actualFetched);
|
||||
pJob.GetDisplayName(out PWSTR name);
|
||||
if (name.AsSpan().StartsWith(BitsJob.JobNamePrefix))
|
||||
|
||||
if (actualFetched != 0)
|
||||
{
|
||||
jobsToCancel.Add(pJob);
|
||||
pJob.GetDisplayName(out PWSTR name);
|
||||
if (name.AsSpan().StartsWith(BitsJob.JobNamePrefix))
|
||||
{
|
||||
jobsToCancel.Add(pJob);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +94,7 @@ internal class BitsManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger?.LogWarning("BITS download engine not supported: {message}", ex.Message);
|
||||
logger.LogWarning("BITS download engine not supported: {message}", ex.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -103,7 +108,7 @@ internal class BitsManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger?.LogWarning(ex, "BITS download failed:");
|
||||
logger.LogWarning(ex, "BITS download failed:");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Snap.Hutao.Core.IO.Bits;
|
||||
/// <summary>
|
||||
/// 进度更新状态
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[DebuggerDisplay("{BytesRead}/{TotalBytes}")]
|
||||
public class ProgressUpdateStatus
|
||||
{
|
||||
|
||||
@@ -7,8 +7,9 @@ using Windows.Storage.Streams;
|
||||
namespace Snap.Hutao.Core.IO.DataTransfer;
|
||||
|
||||
/// <summary>
|
||||
/// 剪贴板
|
||||
/// 剪贴板 在主线程使用
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class Clipboard
|
||||
{
|
||||
/// <summary>
|
||||
@@ -23,6 +24,7 @@ internal static class Clipboard
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
DataPackageView view = Windows.ApplicationModel.DataTransfer.Clipboard.GetContent();
|
||||
string json = await view.GetTextAsync();
|
||||
|
||||
await ThreadHelper.SwitchToBackgroundAsync();
|
||||
return JsonSerializer.Deserialize<T>(json, options);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Snap.Hutao.Core.IO;
|
||||
/// <summary>
|
||||
/// 摘要
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class Digest
|
||||
{
|
||||
/// <summary>
|
||||
@@ -17,11 +18,11 @@ internal static class Digest
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>文件 Md5 摘要</returns>
|
||||
public static async Task<string> GetFileMd5Async(string filePath, CancellationToken token = default)
|
||||
public static async Task<string> GetFileMD5Async(string filePath, CancellationToken token = default)
|
||||
{
|
||||
using (FileStream stream = File.OpenRead(filePath))
|
||||
{
|
||||
return await GetStreamMd5Async(stream, token).ConfigureAwait(false);
|
||||
return await GetStreamMD5Async(stream, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +32,7 @@ internal static class Digest
|
||||
/// <param name="stream">流</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>流 Md5 摘要</returns>
|
||||
public static async Task<string> GetStreamMd5Async(Stream stream, CancellationToken token = default)
|
||||
public static async Task<string> GetStreamMD5Async(Stream stream, CancellationToken token = default)
|
||||
{
|
||||
using (MD5 md5 = MD5.Create())
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Snap.Hutao.Core.IO;
|
||||
/// <summary>
|
||||
/// 文件操作
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class FileOperation
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace Snap.Hutao.Core.IO.Ini;
|
||||
/// <summary>
|
||||
/// Ini 注释
|
||||
/// </summary>
|
||||
internal class IniComment : IniElement
|
||||
[HighQuality]
|
||||
internal sealed class IniComment : IniElement
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的 Ini 注释
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Snap.Hutao.Core.IO.Ini;
|
||||
/// <summary>
|
||||
/// Ini 元素
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal abstract class IniElement
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace Snap.Hutao.Core.IO.Ini;
|
||||
/// <summary>
|
||||
/// Ini 参数
|
||||
/// </summary>
|
||||
internal class IniParameter : IniElement
|
||||
[HighQuality]
|
||||
internal sealed class IniParameter : IniElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Ini 参数
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace Snap.Hutao.Core.IO.Ini;
|
||||
/// <summary>
|
||||
/// Ini 节
|
||||
/// </summary>
|
||||
internal class IniSection : IniElement
|
||||
[HighQuality]
|
||||
internal sealed class IniSection : IniElement
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的Ini 节
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Snap.Hutao.Core.IO.Ini;
|
||||
/// <summary>
|
||||
/// Ini 序列化器
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class IniSerializer
|
||||
{
|
||||
/// <summary>
|
||||
@@ -17,7 +18,7 @@ internal static class IniSerializer
|
||||
/// <returns>Ini 元素集合</returns>
|
||||
public static IEnumerable<IniElement> Deserialize(FileStream fileStream)
|
||||
{
|
||||
using (TextReader reader = new StreamReader(fileStream))
|
||||
using (StreamReader reader = new(fileStream))
|
||||
{
|
||||
while (reader.ReadLine() is string line)
|
||||
{
|
||||
@@ -52,7 +53,7 @@ internal static class IniSerializer
|
||||
/// <param name="elements">元素</param>
|
||||
public static void Serialize(FileStream fileStream, IEnumerable<IniElement> elements)
|
||||
{
|
||||
using (TextWriter writer = new StreamWriter(fileStream))
|
||||
using (StreamWriter writer = new(fileStream))
|
||||
{
|
||||
foreach (IniElement element in elements)
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Snap.Hutao.Core.IO;
|
||||
/// <summary>
|
||||
/// 封装一个临时文件
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal sealed class TempFile : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
@@ -23,7 +24,7 @@ internal sealed class TempFile : IDisposable
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
throw ThrowHelper.RuntimeEnvironment(SH.CoreIOTempFileCreateFail, ex);
|
||||
ThrowHelper.RuntimeEnvironment(SH.CoreIOTempFileCreateFail, ex);
|
||||
}
|
||||
|
||||
if (delete)
|
||||
@@ -42,7 +43,7 @@ internal sealed class TempFile : IDisposable
|
||||
/// </summary>
|
||||
/// <param name="file">源文件</param>
|
||||
/// <returns>临时文件</returns>
|
||||
public static TempFile? CreateFromFileCopy(string file)
|
||||
public static TempFile? CreateCopyFrom(string file)
|
||||
{
|
||||
TempFile temporaryFile = new();
|
||||
try
|
||||
|
||||
@@ -10,7 +10,8 @@ namespace Snap.Hutao.Core.Json.Converter;
|
||||
/// 枚举转换器
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举的类型</typeparam>
|
||||
internal class ConfigurableEnumConverter<TEnum> : JsonConverter<TEnum>
|
||||
[HighQuality]
|
||||
internal sealed class ConfigurableEnumConverter<TEnum> : JsonConverter<TEnum>
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
private readonly JsonSerializeType readAs;
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Snap.Hutao.Core.Json.Converter;
|
||||
/// <summary>
|
||||
/// 实现日期的转换
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -1,24 +1,41 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Snap.Hutao.Extension;
|
||||
|
||||
namespace Snap.Hutao.Core.Json.Converter;
|
||||
|
||||
/// <summary>
|
||||
/// 逗号分隔列表转换器
|
||||
/// </summary>
|
||||
internal class SeparatorCommaInt32EnumerableConverter : JsonConverter<IEnumerable<int>>
|
||||
[HighQuality]
|
||||
internal sealed class SeparatorCommaInt32EnumerableConverter : JsonConverter<IEnumerable<int>>
|
||||
{
|
||||
private const char Comma = ',';
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IEnumerable<int> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
string? source = reader.GetString();
|
||||
IEnumerable<int>? ids = source?.Split(',').Select(int.Parse);
|
||||
return ids ?? Enumerable.Empty<int>();
|
||||
if (reader.GetString() is string source)
|
||||
{
|
||||
return EnumerateNumbers(source);
|
||||
}
|
||||
|
||||
return Enumerable.Empty<int>();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(Utf8JsonWriter writer, IEnumerable<int> value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(string.Join(',', value));
|
||||
writer.WriteStringValue(string.Join(Comma, value));
|
||||
}
|
||||
|
||||
private static IEnumerable<int> EnumerateNumbers(string source)
|
||||
{
|
||||
foreach (StringSegment id in new StringTokenizer(source, Comma.Enumerate().ToArray()))
|
||||
{
|
||||
yield return int.Parse(id.AsSpan());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,13 @@
|
||||
namespace Snap.Hutao.Core.Json.Converter;
|
||||
|
||||
/// <summary>
|
||||
/// Json字典转换器
|
||||
/// Json枚举键字典转换器
|
||||
/// </summary>
|
||||
public class StringEnumKeyDictionaryConverter : JsonConverterFactory
|
||||
[HighQuality]
|
||||
internal sealed class StringEnumKeyDictionaryConverter : JsonConverterFactory
|
||||
{
|
||||
private readonly Type converterType = typeof(StringEnumDictionaryConverterInner<,>);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanConvert(Type typeToConvert)
|
||||
{
|
||||
@@ -27,11 +30,7 @@ public class StringEnumKeyDictionaryConverter : JsonConverterFactory
|
||||
/// <inheritdoc/>
|
||||
public override JsonConverter CreateConverter(Type type, JsonSerializerOptions options)
|
||||
{
|
||||
Type[] arguments = type.GetGenericArguments();
|
||||
Type keyType = arguments[0];
|
||||
Type valueType = arguments[1];
|
||||
|
||||
Type innerConverterType = typeof(StringEnumDictionaryConverterInner<,>).MakeGenericType(keyType, valueType);
|
||||
Type innerConverterType = converterType.MakeGenericType(type.GetGenericArguments());
|
||||
JsonConverter converter = (JsonConverter)Activator.CreateInstance(innerConverterType)!;
|
||||
return converter;
|
||||
}
|
||||
@@ -71,7 +70,7 @@ public class StringEnumKeyDictionaryConverter : JsonConverterFactory
|
||||
|
||||
string? propertyName = reader.GetString();
|
||||
|
||||
if (!Enum.TryParse(propertyName, ignoreCase: false, out TKey key) && !Enum.TryParse(propertyName, ignoreCase: true, out key))
|
||||
if (!Enum.TryParse(propertyName, false, out TKey key) && !Enum.TryParse(propertyName, true, out key))
|
||||
{
|
||||
throw new JsonException($"Unable to convert \"{propertyName}\" to Enum \"{keyType}\".");
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Text.Encodings.Web;
|
||||
|
||||
namespace Snap.Hutao.Core.Json;
|
||||
|
||||
/// <summary>
|
||||
/// 替换 =
|
||||
/// </summary>
|
||||
internal class JsonTextEncoder : JavaScriptEncoder
|
||||
{
|
||||
private static readonly string BackSlashDoubleQuote = "\\\"";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int MaxOutputCharactersPerInputCharacter { get => 6; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe int FindFirstCharacterToEncode(char* text, int textLength)
|
||||
{
|
||||
Span<char> textSpan = new(text, textLength);
|
||||
return textSpan.IndexOf('=');
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten)
|
||||
{
|
||||
// " => \"
|
||||
if (unicodeScalar == '"')
|
||||
{
|
||||
numberOfCharactersWritten = 2;
|
||||
return BackSlashDoubleQuote.AsSpan().TryCopyTo(new Span<char>(buffer, bufferLength));
|
||||
}
|
||||
|
||||
string encoded = $"\\u{(uint)unicodeScalar:x4}";
|
||||
numberOfCharactersWritten = (encoded.Length <= (uint)bufferLength) ? encoded.Length : 0;
|
||||
return encoded.AsSpan().TryCopyTo(new Span<char>(buffer, bufferLength));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool WillEncode(int unicodeScalar)
|
||||
{
|
||||
return unicodeScalar == '=';
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ namespace Snap.Hutao.Core.Json;
|
||||
/// <summary>
|
||||
/// Json 类型信息解析器
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class JsonTypeInfoResolvers
|
||||
{
|
||||
private static readonly Type JsonEnumAttributeType = typeof(JsonEnumAttribute);
|
||||
@@ -16,22 +17,28 @@ internal static class JsonTypeInfoResolvers
|
||||
/// <summary>
|
||||
/// 解析枚举类型
|
||||
/// </summary>
|
||||
/// <param name="ti">Json 类型信息</param>
|
||||
public static void ResolveEnumType(JsonTypeInfo ti)
|
||||
/// <param name="typeInfo">Json 类型信息</param>
|
||||
public static void ResolveEnumType(JsonTypeInfo typeInfo)
|
||||
{
|
||||
if (ti.Kind != JsonTypeInfoKind.Object)
|
||||
if (typeInfo.Kind != JsonTypeInfoKind.Object)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IEnumerable<JsonPropertyInfo> enumProperties = ti.Properties
|
||||
.Where(p => p.PropertyType.IsEnum && (p.AttributeProvider?.IsDefined(JsonEnumAttributeType, false) ?? false));
|
||||
|
||||
foreach (JsonPropertyInfo enumProperty in enumProperties)
|
||||
foreach (JsonPropertyInfo property in typeInfo.Properties)
|
||||
{
|
||||
JsonEnumAttribute attr = enumProperty.AttributeProvider!.GetCustomAttributes(false).OfType<JsonEnumAttribute>().Single();
|
||||
|
||||
enumProperty.CustomConverter = attr.CreateConverter(enumProperty);
|
||||
if (property.PropertyType.IsEnum)
|
||||
{
|
||||
if (property.AttributeProvider is System.Reflection.ICustomAttributeProvider provider)
|
||||
{
|
||||
object[] attributes = provider.GetCustomAttributes(JsonEnumAttributeType, false);
|
||||
if (attributes.Length == 1)
|
||||
{
|
||||
JsonEnumAttribute attr = (JsonEnumAttribute)attributes[0];
|
||||
property.CustomConverter = attr.CreateConverter(property);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,8 @@ namespace Snap.Hutao.Core;
|
||||
/// <summary>
|
||||
/// 跳转列表帮助类
|
||||
/// </summary>
|
||||
public static class JumpListHelper
|
||||
[HighQuality]
|
||||
internal static class JumpListHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步配置跳转列表
|
||||
@@ -24,7 +25,6 @@ public static class JumpListHelper
|
||||
list.Items.Clear();
|
||||
|
||||
JumpListItem launchGameItem = JumpListItem.CreateWithArguments(Activation.LaunchGame, SH.CoreJumpListHelperLaunchGameItemDisplayName);
|
||||
launchGameItem.GroupName = SH.CoreJumpListHelperLaunchGameItemGroupName;
|
||||
launchGameItem.Logo = new("ms-appx:///Resource/Icon/UI_GuideIcon_PlayMethod.png");
|
||||
|
||||
list.Items.Add(launchGameItem);
|
||||
|
||||
@@ -18,8 +18,24 @@ namespace Snap.Hutao.Core.LifeCycle;
|
||||
/// <summary>
|
||||
/// 激活处理器
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class Activation
|
||||
{
|
||||
/// <summary>
|
||||
/// 操作
|
||||
/// </summary>
|
||||
public const string Action = nameof(Action);
|
||||
|
||||
/// <summary>
|
||||
/// 无操作
|
||||
/// </summary>
|
||||
public const string NoAction = "";
|
||||
|
||||
/// <summary>
|
||||
/// Uid
|
||||
/// </summary>
|
||||
public const string Uid = nameof(Uid);
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏启动参数
|
||||
/// </summary>
|
||||
@@ -30,6 +46,10 @@ internal static class Activation
|
||||
/// </summary>
|
||||
public const string ImportUIAFFromClipBoard = "ImportUIAFFromClipBoard";
|
||||
|
||||
private const string CategoryAchievement = "achievement";
|
||||
private const string CategoryDailyNote = "dailynote";
|
||||
private const string UrlActionImport = "/import";
|
||||
private const string UrlActionRefresh = "/refresh";
|
||||
private static readonly SemaphoreSlim ActivateSemaphore = new(1);
|
||||
|
||||
/// <summary>
|
||||
@@ -38,7 +58,7 @@ internal static class Activation
|
||||
/// <returns>是否提升了权限</returns>
|
||||
public static bool GetElevated()
|
||||
{
|
||||
if (System.Diagnostics.Debugger.IsAttached)
|
||||
if (Debugger.IsAttached)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -86,13 +106,12 @@ internal static class Activation
|
||||
public static void NotificationActivate(ToastNotificationActivatedEventArgsCompat args)
|
||||
{
|
||||
ToastArguments toastArgs = ToastArguments.Parse(args.Argument);
|
||||
_ = toastArgs;
|
||||
|
||||
if (toastArgs.TryGetValue("Action", out string? action))
|
||||
if (toastArgs.TryGetValue(Action, out string? action))
|
||||
{
|
||||
if (action == LaunchGame)
|
||||
{
|
||||
_ = toastArgs.TryGetValue("Uid", out string? uid);
|
||||
_ = toastArgs.TryGetValue(Uid, out string? uid);
|
||||
HandleLaunchGameActionAsync(uid).SafeForget();
|
||||
}
|
||||
}
|
||||
@@ -128,7 +147,7 @@ internal static class Activation
|
||||
{
|
||||
switch (arguments)
|
||||
{
|
||||
case "":
|
||||
case NoAction:
|
||||
{
|
||||
// Increase launch times
|
||||
LocalSetting.Set(SettingKeys.LaunchTimes, LocalSetting.Get(SettingKeys.LaunchTimes, 0) + 1);
|
||||
@@ -170,14 +189,14 @@ internal static class Activation
|
||||
|
||||
switch (category)
|
||||
{
|
||||
case "achievement":
|
||||
case CategoryAchievement:
|
||||
{
|
||||
await WaitMainWindowAsync().ConfigureAwait(false);
|
||||
await HandleAchievementActionAsync(action, parameter, isRedirected).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
case "dailynote":
|
||||
case CategoryDailyNote:
|
||||
{
|
||||
await HandleDailyNoteActionAsync(action, parameter, isRedirected).ConfigureAwait(false);
|
||||
break;
|
||||
@@ -191,7 +210,7 @@ internal static class Activation
|
||||
_ = isRedirected;
|
||||
switch (action)
|
||||
{
|
||||
case "/import":
|
||||
case UrlActionImport:
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
|
||||
@@ -210,7 +229,7 @@ internal static class Activation
|
||||
_ = parameter;
|
||||
switch (action)
|
||||
{
|
||||
case "/refresh":
|
||||
case UrlActionRefresh:
|
||||
{
|
||||
await Ioc.Default
|
||||
.GetRequiredService<IDailyNoteService>()
|
||||
@@ -242,7 +261,8 @@ internal static class Activation
|
||||
{
|
||||
await Ioc.Default
|
||||
.GetRequiredService<INavigationService>()
|
||||
.NavigateAsync<View.Page.LaunchGamePage>(INavigationAwaiter.Default, true).ConfigureAwait(false);
|
||||
.NavigateAsync<View.Page.LaunchGamePage>(INavigationAwaiter.Default, true)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,8 @@ namespace Snap.Hutao.Core.LifeCycle;
|
||||
/// <summary>
|
||||
/// <see cref="AppActivationArguments"/> 扩展
|
||||
/// </summary>
|
||||
public static class AppActivationArgumentsExtensions
|
||||
[HighQuality]
|
||||
internal static class AppActivationArgumentsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 尝试获取协议启动的Uri
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Snap.Hutao.Core.LifeCycle;
|
||||
/// <summary>
|
||||
/// App 实例拓展
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class AppInstanceExtension
|
||||
{
|
||||
// Hold the reference here to prevent memory corruption.
|
||||
@@ -23,11 +24,12 @@ internal static class AppInstanceExtension
|
||||
/// <param name="appInstance">app实例</param>
|
||||
/// <param name="args">参数</param>
|
||||
[SuppressMessage("", "VSTHRD002")]
|
||||
[SuppressMessage("", "VSTHRD110")]
|
||||
public static unsafe void RedirectActivationTo(this AppInstance appInstance, AppActivationArguments args)
|
||||
{
|
||||
redirectEventHandle = CreateEvent(default(SECURITY_ATTRIBUTES*), true, false, null);
|
||||
Task.Run(() =>
|
||||
|
||||
// use ThreadPool.UnsafeQueueUserWorkItem to cancel stacktrace
|
||||
ThreadPool.UnsafeQueueUserWorkItem(new(RunAction), () =>
|
||||
{
|
||||
appInstance.RedirectActivationToAsync(args).AsTask().Wait();
|
||||
SetEvent(redirectEventHandle);
|
||||
@@ -36,4 +38,9 @@ internal static class AppInstanceExtension
|
||||
ReadOnlySpan<HANDLE> handles = new(in redirectEventHandle);
|
||||
CoWaitForMultipleObjects((uint)CWMO_FLAGS.CWMO_DEFAULT, INFINITE, handles, out uint _);
|
||||
}
|
||||
|
||||
private static void RunAction(object? state)
|
||||
{
|
||||
((Action)state!)();
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// 事件Id定义
|
||||
/// </summary>
|
||||
[SuppressMessage("", "SA1124")]
|
||||
internal static class EventIds
|
||||
{
|
||||
#region 异常
|
||||
|
||||
/// <summary>
|
||||
/// 未经处理的异常
|
||||
/// </summary>
|
||||
public static readonly EventId UnhandledException = 100000;
|
||||
|
||||
/// <summary>
|
||||
/// Forget任务执行异常
|
||||
/// </summary>
|
||||
public static readonly EventId TaskException = 100001;
|
||||
|
||||
/// <summary>
|
||||
/// 异步命令执行异常
|
||||
/// </summary>
|
||||
public static readonly EventId AsyncCommandException = 100002;
|
||||
|
||||
/// <summary>
|
||||
/// WebView2环境异常
|
||||
/// </summary>
|
||||
public static readonly EventId WebView2EnvironmentException = 100003;
|
||||
|
||||
/// <summary>
|
||||
/// 缓存异常
|
||||
/// </summary>
|
||||
public static readonly EventId CacheException = 100004;
|
||||
|
||||
/// <summary>
|
||||
/// Xaml绑定错误
|
||||
/// </summary>
|
||||
public static readonly EventId XamlBindingError = 100005;
|
||||
|
||||
/// <summary>
|
||||
/// Xaml绑定错误
|
||||
/// </summary>
|
||||
public static readonly EventId UnobservedTaskException = 100006;
|
||||
|
||||
/// <summary>
|
||||
/// Xaml绑定错误
|
||||
/// </summary>
|
||||
public static readonly EventId HttpException = 100007;
|
||||
#endregion
|
||||
|
||||
#region 服务
|
||||
|
||||
/// <summary>
|
||||
/// 导航历史
|
||||
/// </summary>
|
||||
public static readonly EventId NavigationHistory = 100100;
|
||||
|
||||
/// <summary>
|
||||
/// 导航失败
|
||||
/// </summary>
|
||||
public static readonly EventId NavigationFailed = 100101;
|
||||
|
||||
/// <summary>
|
||||
/// 元数据初始化过程
|
||||
/// </summary>
|
||||
public static readonly EventId MetadataInitialization = 100110;
|
||||
|
||||
/// <summary>
|
||||
/// 元数据文件MD5检查
|
||||
/// </summary>
|
||||
public static readonly EventId MetadataFileMD5Check = 100111;
|
||||
|
||||
/// <summary>
|
||||
/// 文件缓存
|
||||
/// </summary>
|
||||
public static readonly EventId FileCaching = 100120;
|
||||
|
||||
/// <summary>
|
||||
/// 删除缓存文件
|
||||
/// </summary>
|
||||
public static readonly EventId CacheRemoveFile = 100121;
|
||||
|
||||
/// <summary>
|
||||
/// 成就
|
||||
/// </summary>
|
||||
public static readonly EventId Achievement = 100130;
|
||||
|
||||
/// <summary>
|
||||
/// 祈愿统计生成
|
||||
/// </summary>
|
||||
public static readonly EventId GachaStatisticGeneration = 100140;
|
||||
|
||||
/// <summary>
|
||||
/// 祈愿统计生成
|
||||
/// </summary>
|
||||
public static readonly EventId AvatarInfoGeneration = 100150;
|
||||
#endregion
|
||||
|
||||
#region 杂项
|
||||
|
||||
/// <summary>
|
||||
/// 杂项Log
|
||||
/// </summary>
|
||||
public static readonly EventId CommonLog = 200000;
|
||||
|
||||
/// <summary>
|
||||
/// 背景状态
|
||||
/// </summary>
|
||||
public static readonly EventId BackdropState = 200001;
|
||||
|
||||
/// <summary>
|
||||
/// 子类控制
|
||||
/// </summary>
|
||||
public static readonly EventId SubClassing = 200002;
|
||||
|
||||
/// <summary>
|
||||
/// 窗口状态
|
||||
/// </summary>
|
||||
public static readonly EventId WindowState = 200003;
|
||||
#endregion
|
||||
}
|
||||
@@ -12,6 +12,7 @@ namespace Snap.Hutao.Core;
|
||||
/// <summary>
|
||||
/// 任务计划器服务
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class ScheduleTaskHelper
|
||||
{
|
||||
private const string DailyNoteRefreshTaskName = "SnapHutaoDailyNoteRefreshTask";
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Snap.Hutao.Core.Setting;
|
||||
/// <summary>
|
||||
/// 本地设置
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class LocalSetting
|
||||
{
|
||||
private static readonly ApplicationDataContainer Container;
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Snap.Hutao.Core.Setting;
|
||||
/// <summary>
|
||||
/// 设置键
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class SettingKeys
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Snap.Hutao.Core.Setting;
|
||||
/// <summary>
|
||||
/// 静态资源
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class StaticResource
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
31
src/Snap.Hutao/Snap.Hutao/Core/StringLiterals.cs
Normal file
31
src/Snap.Hutao/Snap.Hutao/Core/StringLiterals.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 字符串字面量
|
||||
/// </summary>
|
||||
[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";
|
||||
}
|
||||
@@ -7,7 +7,7 @@ namespace Snap.Hutao.Core.Threading.Abstraction;
|
||||
/// 表示一个可等待对象,如果一个方法返回此类型的实例,则此方法可以使用 <see langword="await"/> 异步等待。
|
||||
/// </summary>
|
||||
/// <typeparam name="TAwaiter">用于给 await 确定返回时机的 IAwaiter 的实例。</typeparam>
|
||||
public interface IAwaitable<out TAwaiter>
|
||||
internal interface IAwaitable<out TAwaiter>
|
||||
where TAwaiter : IAwaiter
|
||||
{
|
||||
/// <summary>
|
||||
@@ -23,7 +23,7 @@ public interface IAwaitable<out TAwaiter>
|
||||
/// </summary>
|
||||
/// <typeparam name="TAwaiter">用于给 await 确定返回时机的 <see cref="IAwaiter{TResult}"/> 的实例。</typeparam>
|
||||
/// <typeparam name="TResult">异步返回的返回值类型。</typeparam>
|
||||
public interface IAwaitable<out TAwaiter, out TResult>
|
||||
internal interface IAwaitable<out TAwaiter, out TResult>
|
||||
where TAwaiter : IAwaiter<TResult>
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Core.Threading.Abstraction;
|
||||
/// <summary>
|
||||
/// 用于给 await 确定异步返回的时机。
|
||||
/// </summary>
|
||||
public interface IAwaiter : INotifyCompletion
|
||||
internal interface IAwaiter : INotifyCompletion
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取一个状态,该状态表示正在异步等待的操作已经完成(成功完成或发生了异常);此状态会被编译器自动调用。
|
||||
@@ -26,7 +26,7 @@ public interface IAwaiter : INotifyCompletion
|
||||
/// 用于给 await 确定异步返回的时机,并获取到返回值。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">异步返回的返回值类型。</typeparam>
|
||||
public interface IAwaiter<out TResult> : INotifyCompletion
|
||||
internal interface IAwaiter<out TResult> : INotifyCompletion
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取一个状态,该状态表示正在异步等待的操作已经完成(成功完成或发生了异常);此状态会被编译器自动调用。
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Core.Threading.Abstraction;
|
||||
/// 当执行关键代码(此代码中的错误可能给应用程序中的其他状态造成负面影响)时,
|
||||
/// 用于给 await 确定异步返回的时机。
|
||||
/// </summary>
|
||||
public interface ICriticalAwaiter : IAwaiter, ICriticalNotifyCompletion
|
||||
internal interface ICriticalAwaiter : IAwaiter, ICriticalNotifyCompletion
|
||||
{
|
||||
}
|
||||
|
||||
@@ -18,6 +18,6 @@ public interface ICriticalAwaiter : IAwaiter, ICriticalNotifyCompletion
|
||||
/// 用于给 await 确定异步返回的时机,并获取到返回值。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">异步返回的返回值类型。</typeparam>
|
||||
public interface ICriticalAwaiter<out TResult> : IAwaiter<TResult>, ICriticalNotifyCompletion
|
||||
internal interface ICriticalAwaiter<out TResult> : IAwaiter<TResult>, ICriticalNotifyCompletion
|
||||
{
|
||||
}
|
||||
@@ -8,6 +8,7 @@ namespace Snap.Hutao.Core.Threading;
|
||||
/// <summary>
|
||||
/// 无区分项的并发<see cref="CancellationTokenSource"/>
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[SuppressMessage("", "CA1001")]
|
||||
internal class ConcurrentCancellationTokenSource
|
||||
{
|
||||
@@ -29,6 +30,7 @@ internal class ConcurrentCancellationTokenSource
|
||||
/// 有区分项的并发<see cref="CancellationTokenSource"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">项类型</typeparam>
|
||||
[HighQuality]
|
||||
[SuppressMessage("", "SA1402")]
|
||||
internal class ConcurrentCancellationTokenSource<TItem>
|
||||
where TItem : notnull
|
||||
|
||||
@@ -8,7 +8,8 @@ namespace Snap.Hutao.Core.Threading;
|
||||
/// <summary>
|
||||
/// 调度器队列拓展
|
||||
/// </summary>
|
||||
public static class DispatcherQueueExtension
|
||||
[HighQuality]
|
||||
internal static class DispatcherQueueExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 在调度器队列同步调用,直到执行结束,会持续阻塞当前线程
|
||||
|
||||
@@ -10,7 +10,8 @@ namespace Snap.Hutao.Core.Threading;
|
||||
/// 调度器队列切换操作
|
||||
/// 等待此类型对象后上下文会被切换至主线程
|
||||
/// </summary>
|
||||
public readonly struct DispatherQueueSwitchOperation : IAwaitable<DispatherQueueSwitchOperation>, IAwaiter
|
||||
[HighQuality]
|
||||
internal readonly struct DispatherQueueSwitchOperation : IAwaitable<DispatherQueueSwitchOperation>, IAwaiter
|
||||
{
|
||||
private readonly DispatcherQueue dispatherQueue;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Core.Threading;
|
||||
/// <summary>
|
||||
/// 信号量扩展
|
||||
/// </summary>
|
||||
public static class SemaphoreSlimExtension
|
||||
internal static class SemaphoreSlimExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步进入信号量
|
||||
@@ -16,7 +16,7 @@ public static class SemaphoreSlimExtension
|
||||
/// <param name="semaphoreSlim">信号量</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>可释放的对象,用于释放信号量</returns>
|
||||
public static async Task<IDisposable> EnterAsync(this SemaphoreSlim semaphoreSlim, CancellationToken token = default)
|
||||
public static async ValueTask<IDisposable> EnterAsync(this SemaphoreSlim semaphoreSlim, CancellationToken token = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Logging;
|
||||
|
||||
namespace Snap.Hutao.Core.Threading;
|
||||
|
||||
/// <summary>
|
||||
/// 任务扩展
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[SuppressMessage("", "VSTHRD003")]
|
||||
[SuppressMessage("", "VSTHRD100")]
|
||||
public static class TaskExtensions
|
||||
internal static class TaskExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 安全的触发任务
|
||||
@@ -22,10 +21,20 @@ public static class TaskExtensions
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
#if DEBUG
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ex);
|
||||
if (System.Diagnostics.Debugger.IsAttached)
|
||||
{
|
||||
_ = ex;
|
||||
System.Diagnostics.Debugger.Break();
|
||||
}
|
||||
}
|
||||
#else
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -45,7 +54,7 @@ public static class TaskExtensions
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger?.LogError(EventIds.TaskException, e, "{caller}:\r\n{exception}", nameof(SafeForget), e.GetBaseException());
|
||||
logger?.LogError(e, "{caller}:\r\n{exception}", nameof(SafeForget), e.GetBaseException());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +76,7 @@ public static class TaskExtensions
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger?.LogError(EventIds.TaskException, e, "{caller}:\r\n{exception}", nameof(SafeForget), e.GetBaseException());
|
||||
logger?.LogError(e, "{caller}:\r\n{exception}", nameof(SafeForget), e.GetBaseException());
|
||||
onException?.Invoke(e);
|
||||
}
|
||||
}
|
||||
@@ -91,7 +100,7 @@ public static class TaskExtensions
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger?.LogError(EventIds.TaskException, e, "{caller}:\r\n{exception}", nameof(SafeForget), e.GetBaseException());
|
||||
logger?.LogError(e, "{caller}:\r\n{exception}", nameof(SafeForget), e.GetBaseException());
|
||||
onException?.Invoke(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Core.Threading;
|
||||
/// 线程池切换操作
|
||||
/// 等待此类型对象后上下文会被切换至线程池线程
|
||||
/// </summary>
|
||||
public readonly struct ThreadPoolSwitchOperation : IAwaitable<ThreadPoolSwitchOperation>, IAwaiter, ICriticalAwaiter
|
||||
internal readonly struct ThreadPoolSwitchOperation : IAwaitable<ThreadPoolSwitchOperation>, IAwaiter, ICriticalAwaiter
|
||||
{
|
||||
private static readonly WaitCallback WaitCallbackRunAction = RunAction;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Snap.Hutao.Core.Threading;
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">结果类型</typeparam>
|
||||
/// <typeparam name="TValue">值类型</typeparam>
|
||||
public readonly struct ValueResult<TResult, TValue> : IDeconstructable<TResult, TValue>
|
||||
internal readonly struct ValueResult<TResult, TValue> : IDeconstructable<TResult, TValue>
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否成功
|
||||
|
||||
@@ -8,7 +8,8 @@ namespace Snap.Hutao.Core.Validation;
|
||||
/// <summary>
|
||||
/// 封装验证方法,简化微软验证
|
||||
/// </summary>
|
||||
public static class Must
|
||||
[HighQuality]
|
||||
internal static class Must
|
||||
{
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> if a condition does not evaluate to true.
|
||||
@@ -16,6 +17,7 @@ public static class Must
|
||||
/// <param name="condition">The condition to check.</param>
|
||||
/// <param name="message">message</param>
|
||||
/// <param name="parameterName">The name of the parameter to blame in the exception, if thrown.</param>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void Argument([DoesNotReturnIf(false)] bool condition, string? message, [CallerArgumentExpression("condition")] string? parameterName = null)
|
||||
{
|
||||
if (!condition)
|
||||
@@ -30,6 +32,7 @@ public static class Must
|
||||
/// <param name="condition">The condition to check.</param>
|
||||
/// <param name="message">message</param>
|
||||
/// <param name="parameterName">The name of the parameter to blame in the exception, if thrown.</param>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void Range([DoesNotReturnIf(false)] bool condition, string? message, [CallerArgumentExpression("condition")] string? parameterName = null)
|
||||
{
|
||||
if (!condition)
|
||||
@@ -44,6 +47,7 @@ public static class Must
|
||||
/// <param name="context">上下文</param>
|
||||
/// <returns>Nothing. This method always throws.</returns>
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static Exception NeverHappen(string? context = null)
|
||||
{
|
||||
throw new NotSupportedException(context);
|
||||
@@ -57,6 +61,7 @@ public static class Must
|
||||
/// <param name="parameterName">The name of the parameter to include in any thrown exception.</param>
|
||||
/// <returns>The value of the parameter.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is <c>null</c>.</exception>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static T NotNull<T>([NotNull] T value, [CallerArgumentExpression("value")] string? parameterName = null)
|
||||
where T : class // ensures value-types aren't passed to a null checking method
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user