mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Merge pull request #1788 from DGP-Studio/develop
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
files:
|
files:
|
||||||
- source: /src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
|
- source: /src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
|
||||||
translation: /src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.%osx_locale%.resx
|
translation: /src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.%osx_locale%.resx
|
||||||
|
- source: /src/Snap.Hutao/Snap.Hutao/Resource/Localization/SHRegex.resx
|
||||||
|
translation: /src/Snap.Hutao/Snap.Hutao/Resource/Localization/SHRegex.%osx_locale%.resx
|
||||||
@@ -322,6 +322,7 @@ dotnet_diagnostic.CA2227.severity = suggestion
|
|||||||
dotnet_diagnostic.CA2251.severity = suggestion
|
dotnet_diagnostic.CA2251.severity = suggestion
|
||||||
|
|
||||||
csharp_style_prefer_primary_constructors = false:none
|
csharp_style_prefer_primary_constructors = false:none
|
||||||
|
dotnet_diagnostic.SA1124.severity = none
|
||||||
|
|
||||||
[*.vb]
|
[*.vb]
|
||||||
#### 命名样式 ####
|
#### 命名样式 ####
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Test.BaseClassLibrary;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public sealed class HttpClientTest
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void RedirectionHeaderTest()
|
||||||
|
{
|
||||||
|
HttpClientHandler handler = new()
|
||||||
|
{
|
||||||
|
UseCookies = false,
|
||||||
|
AllowAutoRedirect = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
using (handler)
|
||||||
|
{
|
||||||
|
using (HttpClient httpClient = new(handler))
|
||||||
|
{
|
||||||
|
using (HttpRequestMessage request = new(HttpMethod.Get, "https://api.snapgenshin.com/patch/hutao/download"))
|
||||||
|
{
|
||||||
|
using (HttpResponseMessage response = httpClient.Send(request))
|
||||||
|
{
|
||||||
|
_ = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,19 +13,19 @@ public sealed class JsonSerializeTest
|
|||||||
NumberHandling = JsonNumberHandling.AllowReadingFromString,
|
NumberHandling = JsonNumberHandling.AllowReadingFromString,
|
||||||
};
|
};
|
||||||
|
|
||||||
private const string SmapleObjectJson = """
|
private const string SampleObjectJson = """
|
||||||
{
|
{
|
||||||
"A" :1
|
"A" :1
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
private const string SmapleEmptyStringObjectJson = """
|
private const string SampleEmptyStringObjectJson = """
|
||||||
{
|
{
|
||||||
"A" : ""
|
"A" : ""
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
private const string SmapleNumberKeyDictionaryJson = """
|
private const string SampleNumberKeyDictionaryJson = """
|
||||||
{
|
{
|
||||||
"111" : "12",
|
"111" : "12",
|
||||||
"222" : "34"
|
"222" : "34"
|
||||||
@@ -35,7 +35,7 @@ public sealed class JsonSerializeTest
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void DelegatePropertyCanSerialize()
|
public void DelegatePropertyCanSerialize()
|
||||||
{
|
{
|
||||||
SampleDelegatePropertyClass sample = JsonSerializer.Deserialize<SampleDelegatePropertyClass>(SmapleObjectJson)!;
|
SampleDelegatePropertyClass sample = JsonSerializer.Deserialize<SampleDelegatePropertyClass>(SampleObjectJson)!;
|
||||||
Assert.AreEqual(sample.B, 1);
|
Assert.AreEqual(sample.B, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,14 +43,23 @@ public sealed class JsonSerializeTest
|
|||||||
[ExpectedException(typeof(JsonException))]
|
[ExpectedException(typeof(JsonException))]
|
||||||
public void EmptyStringCannotSerializeAsNumber()
|
public void EmptyStringCannotSerializeAsNumber()
|
||||||
{
|
{
|
||||||
SampleStringReadWriteNumberPropertyClass sample = JsonSerializer.Deserialize<SampleStringReadWriteNumberPropertyClass>(SmapleEmptyStringObjectJson)!;
|
SampleStringReadWriteNumberPropertyClass sample = JsonSerializer.Deserialize<SampleStringReadWriteNumberPropertyClass>(SampleEmptyStringObjectJson)!;
|
||||||
Assert.AreEqual(sample.A, 0);
|
Assert.AreEqual(sample.A, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void EmptyStringCanSerializeAsUri()
|
||||||
|
{
|
||||||
|
SampleEmptyUriClass sample = JsonSerializer.Deserialize<SampleEmptyUriClass>(SampleEmptyStringObjectJson)!;
|
||||||
|
Uri.TryCreate("", UriKind.RelativeOrAbsolute, out Uri? value);
|
||||||
|
Console.WriteLine(value);
|
||||||
|
Assert.AreEqual(sample.A, value);
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void NumberStringKeyCanSerializeAsKey()
|
public void NumberStringKeyCanSerializeAsKey()
|
||||||
{
|
{
|
||||||
Dictionary<int, string> sample = JsonSerializer.Deserialize<Dictionary<int, string>>(SmapleNumberKeyDictionaryJson, AlowStringNumberOptions)!;
|
Dictionary<int, string> sample = JsonSerializer.Deserialize<Dictionary<int, string>>(SampleNumberKeyDictionaryJson, AlowStringNumberOptions)!;
|
||||||
Assert.AreEqual(sample[111], "12");
|
Assert.AreEqual(sample[111], "12");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +101,11 @@ public sealed class JsonSerializeTest
|
|||||||
public int A { get; set; }
|
public int A { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class SampleEmptyUriClass
|
||||||
|
{
|
||||||
|
public Uri A { get; set; } = default!;
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class SampleByteArrayPropertyClass
|
private sealed class SampleByteArrayPropertyClass
|
||||||
{
|
{
|
||||||
public byte[]? Array { get; set; }
|
public byte[]? Array { get; set; }
|
||||||
|
|||||||
14
src/Snap.Hutao/Snap.Hutao.Test/BaseClassLibrary/ListTest.cs
Normal file
14
src/Snap.Hutao/Snap.Hutao.Test/BaseClassLibrary/ListTest.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Test.BaseClassLibrary;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public sealed class ListTest
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void IndexOfNullIsNegativeOne()
|
||||||
|
{
|
||||||
|
List<object> list = [new()];
|
||||||
|
Assert.AreEqual(-1, list.IndexOf(default!));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,6 +56,51 @@ public sealed class UnsafeRuntimeBehaviorTest
|
|||||||
Console.WriteLine(System.Text.Encoding.UTF8.GetString(bytes));
|
Console.WriteLine(System.Text.Encoding.UTF8.GetString(bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public unsafe void UnsafeSizeInt32ToRectInt32Test()
|
||||||
|
{
|
||||||
|
RectInt32 rectInt32 = ToRectInt32(new(100, 200));
|
||||||
|
Assert.AreEqual(rectInt32.X, 0);
|
||||||
|
Assert.AreEqual(rectInt32.Y, 0);
|
||||||
|
Assert.AreEqual(rectInt32.Width, 100);
|
||||||
|
Assert.AreEqual(rectInt32.Height, 200);
|
||||||
|
|
||||||
|
unsafe RectInt32 ToRectInt32(SizeInt32 sizeInt32)
|
||||||
|
{
|
||||||
|
byte* pBytes = stackalloc byte[sizeof(RectInt32)];
|
||||||
|
*(SizeInt32*)(pBytes + 8) = sizeInt32;
|
||||||
|
return *(RectInt32*)pBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct RectInt32
|
||||||
|
{
|
||||||
|
public int X;
|
||||||
|
public int Y;
|
||||||
|
public int Width;
|
||||||
|
public int Height;
|
||||||
|
|
||||||
|
public RectInt32(int x, int y, int width, int height)
|
||||||
|
{
|
||||||
|
X = x;
|
||||||
|
Y = y;
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct SizeInt32
|
||||||
|
{
|
||||||
|
public int Width;
|
||||||
|
public int Height;
|
||||||
|
|
||||||
|
public SizeInt32(int width, int height)
|
||||||
|
{
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly struct TestStruct
|
private readonly struct TestStruct
|
||||||
{
|
{
|
||||||
public readonly int Value1;
|
public readonly int Value1;
|
||||||
|
|||||||
@@ -7,31 +7,38 @@
|
|||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
<XamlControlsResources/>
|
<XamlControlsResources/>
|
||||||
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.SettingsControls/SettingsCard/SettingsCard.xaml"/>
|
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.SettingsControls/SettingsCard/SettingsCard.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.TokenizingTextBox/TokenizingTextBox.xaml"/>
|
<ResourceDictionary Source="ms-appx:///CommunityToolkit.Labs.WinUI.TokenView/TokenItem/TokenItem.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Loading.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Elevation.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Image/CachedImage.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/ItemIcon.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Card.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Loading.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Color.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/StandardView.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/ComboBox.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/AutoSuggestBox/AutoSuggestTokenBox.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Converter.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Card/CardBlock.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/CornerRadius.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Card/CardProgressBar.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/FlyoutStyle.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Card/HorizontalCard.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/FontStyle.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Card/VerticalCard.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Glyph.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Image/CachedImage.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/InfoBarOverride.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/TextBlock/RateDeltaTextBlock.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/ItemsPanelTemplate.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/Card.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/NumericValue.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/Color.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/PageOverride.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/ComboBox.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/PivotOverride.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/Converter.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/ScrollViewer.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/CornerRadius.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/SegmentedOverride.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/FlyoutStyle.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/SettingsStyle.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/FontStyle.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Thickness.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/Glyph.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/TransitionCollection.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/InfoBarOverride.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Uri.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/ItemsPanelTemplate.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///Control/Theme/WindowOverride.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/NumericValue.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///View/Card/Primitive/CardProgressBar.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/PageOverride.xaml"/>
|
||||||
<ResourceDictionary Source="ms-appx:///View/Control/RateDeltaTextBlockStyle.xaml"/>
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/PivotOverride.xaml"/>
|
||||||
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/ScrollViewer.xaml"/>
|
||||||
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/SegmentedOverride.xaml"/>
|
||||||
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/SettingsStyle.xaml"/>
|
||||||
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/Thickness.xaml"/>
|
||||||
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/TransitionCollection.xaml"/>
|
||||||
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/Uri.xaml"/>
|
||||||
|
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/WindowOverride.xaml"/>
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
|
||||||
<Style
|
<Style
|
||||||
@@ -45,15 +52,15 @@
|
|||||||
x:Name="NoneSelectionListViewItemStyle"
|
x:Name="NoneSelectionListViewItemStyle"
|
||||||
BasedOn="{StaticResource DefaultListViewItemStyle}"
|
BasedOn="{StaticResource DefaultListViewItemStyle}"
|
||||||
TargetType="ListViewItem">
|
TargetType="ListViewItem">
|
||||||
<Setter Property="Padding" Value="0"/>
|
|
||||||
<Setter Property="Margin" Value="0,4,0,0"/>
|
<Setter Property="Margin" Value="0,4,0,0"/>
|
||||||
|
<Setter Property="Padding" Value="0"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style
|
<Style
|
||||||
x:Name="NoneSelectionGridViewItemStyle"
|
x:Name="NoneSelectionGridViewItemStyle"
|
||||||
BasedOn="{StaticResource DefaultGridViewItemStyle}"
|
BasedOn="{StaticResource DefaultGridViewItemStyle}"
|
||||||
TargetType="GridViewItem">
|
TargetType="GridViewItem">
|
||||||
<Setter Property="Padding" Value="0"/>
|
|
||||||
<Setter Property="Margin" Value="0,0,2,4"/>
|
<Setter Property="Margin" Value="0,0,2,4"/>
|
||||||
|
<Setter Property="Padding" Value="0"/>
|
||||||
</Style>
|
</Style>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ using Snap.Hutao.Core.ExceptionService;
|
|||||||
using Snap.Hutao.Core.LifeCycle;
|
using Snap.Hutao.Core.LifeCycle;
|
||||||
using Snap.Hutao.Core.LifeCycle.InterProcess;
|
using Snap.Hutao.Core.LifeCycle.InterProcess;
|
||||||
using Snap.Hutao.Core.Logging;
|
using Snap.Hutao.Core.Logging;
|
||||||
using Snap.Hutao.Core.Windowing;
|
using Snap.Hutao.UI.Xaml;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Snap.Hutao;
|
namespace Snap.Hutao;
|
||||||
@@ -60,7 +60,7 @@ public sealed partial class App : Application
|
|||||||
|
|
||||||
public new void Exit()
|
public new void Exit()
|
||||||
{
|
{
|
||||||
XamlLifetime.ApplicationExiting = true;
|
XamlApplicationLifetime.Exiting = true;
|
||||||
base.Exit();
|
base.Exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using CommunityToolkit.WinUI;
|
|
||||||
using CommunityToolkit.WinUI.Controls;
|
|
||||||
using Microsoft.UI.Xaml;
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
|
||||||
using Snap.Hutao.Control.Extension;
|
|
||||||
using System.Collections;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.AutoSuggestBox;
|
|
||||||
|
|
||||||
[DependencyProperty("FilterCommand", typeof(ICommand))]
|
|
||||||
[DependencyProperty("FilterCommandParameter", typeof(object))]
|
|
||||||
[DependencyProperty("AvailableTokens", typeof(IReadOnlyDictionary<string, SearchToken>))]
|
|
||||||
internal sealed partial class AutoSuggestTokenBox : TokenizingTextBox
|
|
||||||
{
|
|
||||||
public AutoSuggestTokenBox()
|
|
||||||
{
|
|
||||||
DefaultStyleKey = typeof(TokenizingTextBox);
|
|
||||||
TextChanged += OnFilterSuggestionRequested;
|
|
||||||
QuerySubmitted += OnQuerySubmitted;
|
|
||||||
TokenItemAdding += OnTokenItemAdding;
|
|
||||||
TokenItemAdded += OnTokenItemCollectionChanged;
|
|
||||||
TokenItemRemoved += OnTokenItemCollectionChanged;
|
|
||||||
Loaded += OnLoaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (this.FindDescendant("SuggestionsPopup") is Popup { Child: Border { Child: ListView listView } border })
|
|
||||||
{
|
|
||||||
IAppResourceProvider appResourceProvider = this.ServiceProvider().GetRequiredService<IAppResourceProvider>();
|
|
||||||
|
|
||||||
listView.Background = null;
|
|
||||||
listView.Margin = appResourceProvider.GetResource<Thickness>("AutoSuggestListPadding");
|
|
||||||
|
|
||||||
border.Background = appResourceProvider.GetResource<Microsoft.UI.Xaml.Media.Brush>("AutoSuggestBoxSuggestionsListBackground");
|
|
||||||
CornerRadius overlayCornerRadius = appResourceProvider.GetResource<CornerRadius>("OverlayCornerRadius");
|
|
||||||
CornerRadiusFilterConverter cornerRadiusFilterConverter = new() { Filter = CornerRadiusFilterKind.Bottom };
|
|
||||||
border.CornerRadius = (CornerRadius)cornerRadiusFilterConverter.Convert(overlayCornerRadius, typeof(CornerRadius), default, default);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnFilterSuggestionRequested(Microsoft.UI.Xaml.Controls.AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(Text))
|
|
||||||
{
|
|
||||||
sender.ItemsSource = AvailableTokens
|
|
||||||
.OrderBy(kvp => kvp.Value.Kind)
|
|
||||||
.Select(kvp => kvp.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
|
|
||||||
{
|
|
||||||
sender.ItemsSource = AvailableTokens
|
|
||||||
.Where(kvp => kvp.Value.Value.Contains(Text, StringComparison.OrdinalIgnoreCase))
|
|
||||||
.OrderBy(kvp => kvp.Value.Kind)
|
|
||||||
.ThenBy(kvp => kvp.Value.Order)
|
|
||||||
.Select(kvp => kvp.Value)
|
|
||||||
.DefaultIfEmpty(SearchToken.NotFound);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnQuerySubmitted(Microsoft.UI.Xaml.Controls.AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
|
|
||||||
{
|
|
||||||
if (args.ChosenSuggestion is not null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CommandInvocation.TryExecute(FilterCommand, FilterCommandParameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnTokenItemAdding(TokenizingTextBox sender, TokenItemAddingEventArgs args)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(args.TokenText))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (AvailableTokens.GetValueOrDefault(args.TokenText) is { } token)
|
|
||||||
{
|
|
||||||
args.Item = token;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
args.Cancel = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnTokenItemCollectionChanged(TokenizingTextBox sender, object args)
|
|
||||||
{
|
|
||||||
if (args is SearchToken { Kind: SearchTokenKind.None } token)
|
|
||||||
{
|
|
||||||
((IList)sender.ItemsSource).Remove(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
FilterCommand.TryExecute(FilterCommandParameter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using CommunityToolkit.Labs.WinUI.MarqueeTextRns;
|
|
||||||
using CommunityToolkit.WinUI.Behaviors;
|
|
||||||
using Microsoft.UI.Xaml.Input;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Behavior;
|
|
||||||
|
|
||||||
internal sealed class MarqueeTextBehavior : BehaviorBase<MarqueeText>
|
|
||||||
{
|
|
||||||
private readonly PointerEventHandler pointerEnteredEventHandler;
|
|
||||||
private readonly PointerEventHandler pointerExitedEventHandler;
|
|
||||||
|
|
||||||
public MarqueeTextBehavior()
|
|
||||||
{
|
|
||||||
pointerEnteredEventHandler = OnPointerEntered;
|
|
||||||
pointerExitedEventHandler = OnPointerExited;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool Initialize()
|
|
||||||
{
|
|
||||||
AssociatedObject.PointerEntered += pointerEnteredEventHandler;
|
|
||||||
AssociatedObject.PointerExited += pointerExitedEventHandler;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool Uninitialize()
|
|
||||||
{
|
|
||||||
AssociatedObject.PointerEntered -= pointerEnteredEventHandler;
|
|
||||||
AssociatedObject.PointerExited -= pointerExitedEventHandler;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPointerEntered(object sender, PointerRoutedEventArgs e)
|
|
||||||
{
|
|
||||||
AssociatedObject.StartMarquee();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPointerExited(object sender, PointerRoutedEventArgs e)
|
|
||||||
{
|
|
||||||
AssociatedObject.StopMarquee();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Windows.UI;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Brush;
|
|
||||||
|
|
||||||
internal sealed class ColorSegment : IColorSegment
|
|
||||||
{
|
|
||||||
public ColorSegment(Color color, double value)
|
|
||||||
{
|
|
||||||
Color = color;
|
|
||||||
Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Color Color { get; set; }
|
|
||||||
|
|
||||||
public double Value { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Brush;
|
|
||||||
|
|
||||||
internal sealed class ColorSegmentCollection : List<IColorSegment>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Windows.UI;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Brush;
|
|
||||||
|
|
||||||
internal interface IColorSegment
|
|
||||||
{
|
|
||||||
Color Color { get; }
|
|
||||||
|
|
||||||
double Value { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Microsoft.UI.Xaml;
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
using Microsoft.UI.Xaml.Media;
|
|
||||||
using Microsoft.UI.Xaml.Shapes;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Brush;
|
|
||||||
|
|
||||||
[DependencyProperty("Source", typeof(ColorSegmentCollection), default!, nameof(OnSourceChanged))]
|
|
||||||
internal sealed partial class SegmentedBar : ContentControl
|
|
||||||
{
|
|
||||||
private readonly LinearGradientBrush brush = new() { StartPoint = new(0, 0), EndPoint = new(1, 0), };
|
|
||||||
|
|
||||||
public SegmentedBar()
|
|
||||||
{
|
|
||||||
HorizontalContentAlignment = HorizontalAlignment.Stretch;
|
|
||||||
VerticalContentAlignment = VerticalAlignment.Stretch;
|
|
||||||
|
|
||||||
Content = new Rectangle()
|
|
||||||
{
|
|
||||||
Fill = brush,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
|
||||||
VerticalAlignment = VerticalAlignment.Stretch,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
|
||||||
{
|
|
||||||
UpdateLinearGradientBrush((SegmentedBar)obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void UpdateLinearGradientBrush(SegmentedBar segmentedBar)
|
|
||||||
{
|
|
||||||
GradientStopCollection collection = segmentedBar.brush.GradientStops;
|
|
||||||
collection.Clear();
|
|
||||||
|
|
||||||
ColorSegmentCollection segmentCollection = segmentedBar.Source;
|
|
||||||
|
|
||||||
double total = segmentCollection.Sum(seg => seg.Value);
|
|
||||||
if (total is 0D)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
double offset = 0;
|
|
||||||
foreach (ref readonly IColorSegment segment in CollectionsMarshal.AsSpan(segmentCollection))
|
|
||||||
{
|
|
||||||
collection.Add(new() { Color = segment.Color, Offset = offset, });
|
|
||||||
offset += segment.Value / total;
|
|
||||||
collection.Add(new() { Color = segment.Color, Offset = offset, });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Builder.ButtonBase;
|
|
||||||
|
|
||||||
internal class ButtonBaseBuilder<TButton> : IButtonBaseBuilder<TButton>
|
|
||||||
where TButton : Microsoft.UI.Xaml.Controls.Primitives.ButtonBase, new()
|
|
||||||
{
|
|
||||||
public TButton Button { get; } = new();
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Snap.Hutao.Core.Abstraction.Extension;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Builder.ButtonBase;
|
|
||||||
|
|
||||||
internal static class ButtonBaseBuilderExtension
|
|
||||||
{
|
|
||||||
public static TBuilder SetContent<TBuilder, TButton>(this TBuilder builder, object? content)
|
|
||||||
where TBuilder : IButtonBaseBuilder<TButton>
|
|
||||||
where TButton : Microsoft.UI.Xaml.Controls.Primitives.ButtonBase
|
|
||||||
{
|
|
||||||
builder.Configure(builder => builder.Button.Content = content);
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TBuilder SetCommand<TBuilder, TButton>(this TBuilder builder, ICommand command)
|
|
||||||
where TBuilder : IButtonBaseBuilder<TButton>
|
|
||||||
where TButton : Microsoft.UI.Xaml.Controls.Primitives.ButtonBase
|
|
||||||
{
|
|
||||||
builder.Configure(builder => builder.Button.Command = command);
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Builder.ButtonBase;
|
|
||||||
|
|
||||||
internal sealed class ButtonBuilder : ButtonBaseBuilder<Button>;
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Builder.ButtonBase;
|
|
||||||
|
|
||||||
internal static class ButtonBuilderExtension
|
|
||||||
{
|
|
||||||
public static ButtonBuilder SetContent(this ButtonBuilder builder, object? content)
|
|
||||||
{
|
|
||||||
return builder.SetContent<ButtonBuilder, Button>(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ButtonBuilder SetCommand(this ButtonBuilder builder, ICommand command)
|
|
||||||
{
|
|
||||||
return builder.SetCommand<ButtonBuilder, Button>(command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Snap.Hutao.Core.Abstraction;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Builder.ButtonBase;
|
|
||||||
|
|
||||||
internal interface IButtonBaseBuilder<TButton> : IBuilder
|
|
||||||
where TButton : Microsoft.UI.Xaml.Controls.Primitives.ButtonBase
|
|
||||||
{
|
|
||||||
TButton Button { get; }
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Microsoft.UI.Xaml;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Helper;
|
|
||||||
|
|
||||||
[SuppressMessage("", "SH001")]
|
|
||||||
[DependencyProperty("SquareLength", typeof(double), 0D, nameof(OnSquareLengthChanged), IsAttached = true, AttachedType = typeof(FrameworkElement))]
|
|
||||||
[DependencyProperty("IsActualThemeBindingEnabled", typeof(bool), false, nameof(OnIsActualThemeBindingEnabled), IsAttached = true, AttachedType = typeof(FrameworkElement))]
|
|
||||||
[DependencyProperty("ActualTheme", typeof(ElementTheme), ElementTheme.Default, IsAttached = true, AttachedType = typeof(FrameworkElement))]
|
|
||||||
public sealed partial class FrameworkElementHelper
|
|
||||||
{
|
|
||||||
private static void OnSquareLengthChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
FrameworkElement element = (FrameworkElement)dp;
|
|
||||||
element.Width = (double)e.NewValue;
|
|
||||||
element.Height = (double)e.NewValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnIsActualThemeBindingEnabled(DependencyObject dp, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
FrameworkElement element = (FrameworkElement)dp;
|
|
||||||
if ((bool)e.NewValue)
|
|
||||||
{
|
|
||||||
element.ActualThemeChanged += OnActualThemeChanged;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
element.ActualThemeChanged -= OnActualThemeChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void OnActualThemeChanged(FrameworkElement sender, object args)
|
|
||||||
{
|
|
||||||
SetActualTheme(sender, sender.ActualTheme);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Microsoft.UI.Xaml.Media.Imaging;
|
|
||||||
using Snap.Hutao.Control.Extension;
|
|
||||||
using Snap.Hutao.Core.Caching;
|
|
||||||
using Snap.Hutao.Core.ExceptionService;
|
|
||||||
using Snap.Hutao.Core.IO.DataTransfer;
|
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Windows.Graphics.Imaging;
|
|
||||||
using Windows.Storage.Streams;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Image;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 缓存图像
|
|
||||||
/// </summary>
|
|
||||||
[HighQuality]
|
|
||||||
[DependencyProperty("SourceName", typeof(string), "Unknown")]
|
|
||||||
[DependencyProperty("CachedName", typeof(string), "Unknown")]
|
|
||||||
internal sealed partial class CachedImage : Implementation.ImageEx
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 构造一个新的缓存图像
|
|
||||||
/// </summary>
|
|
||||||
public CachedImage()
|
|
||||||
{
|
|
||||||
DefaultStyleKey = typeof(CachedImage);
|
|
||||||
DefaultStyleResourceUri = "ms-appx:///Control/Image/CachedImage.xaml".ToUri();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override async Task<Uri?> ProvideCachedResourceAsync(Uri imageUri, CancellationToken token)
|
|
||||||
{
|
|
||||||
SourceName = Path.GetFileName(imageUri.ToString());
|
|
||||||
IImageCache imageCache = this.ServiceProvider().GetRequiredService<IImageCache>();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
HutaoException.ThrowIf(string.IsNullOrEmpty(imageUri.Host), SH.ControlImageCachedImageInvalidResourceUri);
|
|
||||||
string file = await imageCache.GetFileFromCacheAsync(imageUri).ConfigureAwait(true); // BitmapImage need to be created by main thread.
|
|
||||||
CachedName = Path.GetFileName(file);
|
|
||||||
token.ThrowIfCancellationRequested(); // check token state to determine whether the operation should be canceled.
|
|
||||||
return file.ToUri();
|
|
||||||
}
|
|
||||||
catch (COMException)
|
|
||||||
{
|
|
||||||
// The image is corrupted, remove it.
|
|
||||||
imageCache.Remove(imageUri);
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Command("CopyToClipboardCommand")]
|
|
||||||
private async Task CopyToClipboard()
|
|
||||||
{
|
|
||||||
if (Image is Microsoft.UI.Xaml.Controls.Image { Source: BitmapImage bitmap })
|
|
||||||
{
|
|
||||||
using (FileStream netStream = File.OpenRead(bitmap.UriSource.LocalPath))
|
|
||||||
{
|
|
||||||
using (IRandomAccessStream fxStream = netStream.AsRandomAccessStream())
|
|
||||||
{
|
|
||||||
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fxStream);
|
|
||||||
SoftwareBitmap softwareBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
|
|
||||||
using (InMemoryRandomAccessStream memory = new())
|
|
||||||
{
|
|
||||||
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.BmpEncoderId, memory);
|
|
||||||
encoder.SetSoftwareBitmap(softwareBitmap);
|
|
||||||
await encoder.FlushAsync();
|
|
||||||
Ioc.Default.GetRequiredService<IClipboardProvider>().SetBitmap(memory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Microsoft.UI.Composition;
|
|
||||||
using Microsoft.UI.Xaml;
|
|
||||||
using Windows.Media.Casting;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Image.Implementation;
|
|
||||||
|
|
||||||
[DependencyProperty("NineGrid", typeof(Thickness))]
|
|
||||||
internal partial class ImageEx : ImageExBase
|
|
||||||
{
|
|
||||||
public ImageEx()
|
|
||||||
: base()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override CompositionBrush GetAlphaMask()
|
|
||||||
{
|
|
||||||
if (IsInitialized && Image is Microsoft.UI.Xaml.Controls.Image image)
|
|
||||||
{
|
|
||||||
return image.GetAlphaMask();
|
|
||||||
}
|
|
||||||
|
|
||||||
return default!;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CastingSource GetAsCastingSource()
|
|
||||||
{
|
|
||||||
if (IsInitialized && Image is Microsoft.UI.Xaml.Controls.Image image)
|
|
||||||
{
|
|
||||||
return image.GetAsCastingSource();
|
|
||||||
}
|
|
||||||
|
|
||||||
return default!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Microsoft.Graphics.Canvas.Effects;
|
|
||||||
using Microsoft.UI;
|
|
||||||
using Microsoft.UI.Composition;
|
|
||||||
using Microsoft.UI.Xaml;
|
|
||||||
using Microsoft.UI.Xaml.Media;
|
|
||||||
using Snap.Hutao.Control.Theme;
|
|
||||||
using Windows.Foundation;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Image;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 支持单色的图像
|
|
||||||
/// </summary>
|
|
||||||
[HighQuality]
|
|
||||||
internal sealed class MonoChrome : CompositionImage
|
|
||||||
{
|
|
||||||
private readonly TypedEventHandler<FrameworkElement, object> actualThemeChangedEventHandler;
|
|
||||||
|
|
||||||
private CompositionColorBrush? backgroundBrush;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造一个新的单色图像
|
|
||||||
/// </summary>
|
|
||||||
public MonoChrome()
|
|
||||||
{
|
|
||||||
actualThemeChangedEventHandler = OnActualThemeChanged;
|
|
||||||
ActualThemeChanged += actualThemeChangedEventHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override SpriteVisual CompositeSpriteVisual(Compositor compositor, LoadedImageSurface imageSurface)
|
|
||||||
{
|
|
||||||
CompositionColorBrush blackLayerBrush = compositor.CreateColorBrush(Colors.Black);
|
|
||||||
CompositionSurfaceBrush imageSurfaceBrush = compositor.CompositeSurfaceBrush(imageSurface, stretch: CompositionStretch.Uniform, vRatio: 0f);
|
|
||||||
CompositionEffectBrush overlayBrush = compositor.CompositeBlendEffectBrush(blackLayerBrush, imageSurfaceBrush, BlendEffectMode.Overlay);
|
|
||||||
CompositionEffectBrush opacityBrush = compositor.CompositeLuminanceToAlphaEffectBrush(overlayBrush);
|
|
||||||
|
|
||||||
backgroundBrush = compositor.CreateColorBrush();
|
|
||||||
SetBackgroundColor(backgroundBrush);
|
|
||||||
CompositionEffectBrush alphaMaskEffectBrush = compositor.CompositeAlphaMaskEffectBrush(backgroundBrush, opacityBrush);
|
|
||||||
|
|
||||||
return compositor.CompositeSpriteVisual(alphaMaskEffectBrush);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnActualThemeChanged(FrameworkElement sender, object args)
|
|
||||||
{
|
|
||||||
if (backgroundBrush is not null)
|
|
||||||
{
|
|
||||||
SetBackgroundColor(backgroundBrush);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetBackgroundColor(CompositionColorBrush backgroundBrush)
|
|
||||||
{
|
|
||||||
ApplicationTheme theme = ThemeHelper.ElementToApplication(ActualTheme);
|
|
||||||
|
|
||||||
backgroundBrush.Color = theme switch
|
|
||||||
{
|
|
||||||
ApplicationTheme.Light => Colors.Black,
|
|
||||||
ApplicationTheme.Dark => Colors.White,
|
|
||||||
_ => Colors.Transparent,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using System.Buffers.Binary;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using Windows.UI;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Media;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// BGRA 结构
|
|
||||||
/// </summary>
|
|
||||||
[HighQuality]
|
|
||||||
internal struct Bgra32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// B
|
|
||||||
/// </summary>
|
|
||||||
public byte B;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// G
|
|
||||||
/// </summary>
|
|
||||||
public byte G;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// R
|
|
||||||
/// </summary>
|
|
||||||
public byte R;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A
|
|
||||||
/// </summary>
|
|
||||||
public byte A;
|
|
||||||
|
|
||||||
public Bgra32(byte b, byte g, byte r, byte a)
|
|
||||||
{
|
|
||||||
B = b;
|
|
||||||
G = g;
|
|
||||||
R = r;
|
|
||||||
A = a;
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly double Luminance { get => ((0.299 * R) + (0.587 * G) + (0.114 * B)) / 255; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 从 Color 转换
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="color">颜色</param>
|
|
||||||
/// <returns>新的 BGRA8 结构</returns>
|
|
||||||
public static unsafe implicit operator Bgra32(Color color)
|
|
||||||
{
|
|
||||||
Unsafe.SkipInit(out Bgra32 bgra8);
|
|
||||||
*(uint*)&bgra8 = BinaryPrimitives.ReverseEndianness(*(uint*)&color);
|
|
||||||
return bgra8;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static unsafe implicit operator Color(Bgra32 bgra8)
|
|
||||||
{
|
|
||||||
Unsafe.SkipInit(out Color color);
|
|
||||||
*(uint*)&color = BinaryPrimitives.ReverseEndianness(*(uint*)&bgra8);
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
// 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
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Media;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines a color in Hue/Saturation/Lightness (HSL) space.
|
|
||||||
/// </summary>
|
|
||||||
internal struct Hsla32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The Hue in 0..360 range.
|
|
||||||
/// </summary>
|
|
||||||
public double H;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The Saturation in 0..1 range.
|
|
||||||
/// </summary>
|
|
||||||
public double S;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The Lightness in 0..1 range.
|
|
||||||
/// </summary>
|
|
||||||
public double L;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The Alpha/opacity in 0..1 range.
|
|
||||||
/// </summary>
|
|
||||||
public double A;
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
// 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 System.Buffers.Binary;
|
|
||||||
using Windows.UI;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Media;
|
|
||||||
|
|
||||||
[HighQuality]
|
|
||||||
internal struct Rgba32
|
|
||||||
{
|
|
||||||
public byte R;
|
|
||||||
public byte G;
|
|
||||||
public byte B;
|
|
||||||
public byte A;
|
|
||||||
|
|
||||||
public Rgba32(string hex)
|
|
||||||
: this(hex.Length == 6 ? Convert.ToUInt32($"{hex}FF", 16) : Convert.ToUInt32(hex, 16))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe Rgba32(uint xrgbaCode)
|
|
||||||
{
|
|
||||||
// uint layout: 0xRRGGBBAA is AABBGGRR
|
|
||||||
// AABBGGRR -> RRGGBBAA
|
|
||||||
fixed (Rgba32* pSelf = &this)
|
|
||||||
{
|
|
||||||
*(uint*)pSelf = BinaryPrimitives.ReverseEndianness(xrgbaCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Rgba32(byte r, byte g, byte b, byte a)
|
|
||||||
{
|
|
||||||
R = r;
|
|
||||||
G = g;
|
|
||||||
B = b;
|
|
||||||
A = a;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static unsafe implicit operator Color(Rgba32 hexColor)
|
|
||||||
{
|
|
||||||
// Goal : Rgba32:RRGGBBAA(0xAABBGGRR) -> Color: AARRGGBB(0xBBGGRRAA)
|
|
||||||
// Step1: Rgba32:RRGGBBAA(0xAABBGGRR) -> UInt32:AA000000(0x000000AA)
|
|
||||||
uint a = ((*(uint*)&hexColor) >> 24) & 0x000000FF;
|
|
||||||
|
|
||||||
// Step2: Rgba32:RRGGBBAA(0xAABBGGRR) -> UInt32:00RRGGBB(0xRRGGBB00)
|
|
||||||
uint rgb = ((*(uint*)&hexColor) << 8) & 0xFFFFFF00;
|
|
||||||
|
|
||||||
// Step2: UInt32:00RRGGBB(0xRRGGBB00) + UInt32:AA000000(0x000000AA) -> UInt32:AARRGGBB(0xRRGGBBAA)
|
|
||||||
uint rgba = rgb + a;
|
|
||||||
|
|
||||||
return *(Color*)&rgba;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Rgba32 FromHsl(Hsla32 hsl)
|
|
||||||
{
|
|
||||||
double chroma = (1 - Math.Abs((2 * hsl.L) - 1)) * hsl.S;
|
|
||||||
double h1 = hsl.H / 60;
|
|
||||||
double x = chroma * (1 - Math.Abs((h1 % 2) - 1));
|
|
||||||
double m = hsl.L - (0.5 * chroma);
|
|
||||||
double r1, g1, b1;
|
|
||||||
|
|
||||||
if (h1 < 1)
|
|
||||||
{
|
|
||||||
r1 = chroma;
|
|
||||||
g1 = x;
|
|
||||||
b1 = 0;
|
|
||||||
}
|
|
||||||
else if (h1 < 2)
|
|
||||||
{
|
|
||||||
r1 = x;
|
|
||||||
g1 = chroma;
|
|
||||||
b1 = 0;
|
|
||||||
}
|
|
||||||
else if (h1 < 3)
|
|
||||||
{
|
|
||||||
r1 = 0;
|
|
||||||
g1 = chroma;
|
|
||||||
b1 = x;
|
|
||||||
}
|
|
||||||
else if (h1 < 4)
|
|
||||||
{
|
|
||||||
r1 = 0;
|
|
||||||
g1 = x;
|
|
||||||
b1 = chroma;
|
|
||||||
}
|
|
||||||
else if (h1 < 5)
|
|
||||||
{
|
|
||||||
r1 = x;
|
|
||||||
g1 = 0;
|
|
||||||
b1 = chroma;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
r1 = chroma;
|
|
||||||
g1 = 0;
|
|
||||||
b1 = x;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte r = (byte)(255 * (r1 + m));
|
|
||||||
byte g = (byte)(255 * (g1 + m));
|
|
||||||
byte b = (byte)(255 * (b1 + m));
|
|
||||||
byte a = (byte)(255 * hsl.A);
|
|
||||||
|
|
||||||
return new(r, g, b, a);
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly Hsla32 ToHsl()
|
|
||||||
{
|
|
||||||
const double toDouble = 1.0 / 255;
|
|
||||||
double r = toDouble * R;
|
|
||||||
double g = toDouble * G;
|
|
||||||
double b = toDouble * B;
|
|
||||||
double max = Math.Max(Math.Max(r, g), b);
|
|
||||||
double min = Math.Min(Math.Min(r, g), b);
|
|
||||||
double chroma = max - min;
|
|
||||||
double h1;
|
|
||||||
|
|
||||||
if (chroma == 0)
|
|
||||||
{
|
|
||||||
h1 = 0;
|
|
||||||
}
|
|
||||||
else if (max == r)
|
|
||||||
{
|
|
||||||
// The % operator doesn't do proper modulo on negative
|
|
||||||
// numbers, so we'll add 6 before using it
|
|
||||||
h1 = (((g - b) / chroma) + 6) % 6;
|
|
||||||
}
|
|
||||||
else if (max == g)
|
|
||||||
{
|
|
||||||
h1 = 2 + ((b - r) / chroma);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
h1 = 4 + ((r - g) / chroma);
|
|
||||||
}
|
|
||||||
|
|
||||||
double lightness = 0.5 * (max + min);
|
|
||||||
double saturation = chroma == 0 ? 0 : chroma / (1 - Math.Abs((2 * lightness) - 1));
|
|
||||||
|
|
||||||
Hsla32 ret;
|
|
||||||
ret.H = 60 * h1;
|
|
||||||
ret.S = saturation;
|
|
||||||
ret.L = lightness;
|
|
||||||
ret.A = toDouble * A;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Snap.Hutao.Win32;
|
|
||||||
using Windows.UI;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Theme;
|
|
||||||
|
|
||||||
internal static class KnownColors
|
|
||||||
{
|
|
||||||
public static readonly Color Orange = StructMarshal.Color(0xFFBC6932);
|
|
||||||
public static readonly Color Purple = StructMarshal.Color(0xFFA156E0);
|
|
||||||
public static readonly Color Blue = StructMarshal.Color(0xFF5180CB);
|
|
||||||
public static readonly Color Green = StructMarshal.Color(0xFF2A8F72);
|
|
||||||
public static readonly Color White = StructMarshal.Color(0xFF72778B);
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.Abstraction.Extension;
|
namespace Snap.Hutao.Core.Abstraction;
|
||||||
|
|
||||||
internal static class BuilderExtension
|
internal static class BuilderExtension
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
using Snap.Hutao.Core.IO;
|
using Snap.Hutao.Core.IO;
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.Caching;
|
namespace Snap.Hutao.Core.Caching;
|
||||||
@@ -11,27 +12,11 @@ namespace Snap.Hutao.Core.Caching;
|
|||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal interface IImageCache
|
internal interface IImageCache
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets the file path containing cached item for given Uri
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="uri">Uri of the item.</param>
|
|
||||||
/// <returns>a string path</returns>
|
|
||||||
ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri);
|
ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri);
|
||||||
|
|
||||||
/// <summary>
|
ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri, ElementTheme theme);
|
||||||
/// Removed items based on uri list passed
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="uriForCachedItems">Enumerable uri list</param>
|
|
||||||
void Remove(in ReadOnlySpan<Uri> uriForCachedItems);
|
void Remove(in ReadOnlySpan<Uri> uriForCachedItems);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removed item based on uri passed
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="uriForCachedItem">uri</param>
|
|
||||||
void Remove(Uri uriForCachedItem);
|
void Remove(Uri uriForCachedItem);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes invalid cached files
|
|
||||||
/// </summary>
|
|
||||||
void RemoveInvalid();
|
|
||||||
}
|
}
|
||||||
@@ -2,19 +2,27 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||||
|
using Snap.Hutao.Core.ExceptionService;
|
||||||
using Snap.Hutao.Core.IO;
|
using Snap.Hutao.Core.IO;
|
||||||
using Snap.Hutao.Core.IO.Hashing;
|
using Snap.Hutao.Core.IO.Hashing;
|
||||||
using Snap.Hutao.Core.Logging;
|
using Snap.Hutao.Core.Logging;
|
||||||
|
using Snap.Hutao.UI;
|
||||||
using Snap.Hutao.ViewModel.Guide;
|
using Snap.Hutao.ViewModel.Guide;
|
||||||
|
using Snap.Hutao.Web;
|
||||||
using Snap.Hutao.Web.Request.Builder;
|
using Snap.Hutao.Web.Request.Builder;
|
||||||
using Snap.Hutao.Web.Request.Builder.Abstraction;
|
using Snap.Hutao.Web.Request.Builder.Abstraction;
|
||||||
|
using Snap.Hutao.Win32.System.WinRT;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Frozen;
|
using System.Collections.Frozen;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.Graphics.Imaging;
|
||||||
|
using WinRT;
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.Caching;
|
namespace Snap.Hutao.Core.Caching;
|
||||||
|
|
||||||
@@ -34,7 +42,8 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
|||||||
KeyValuePair.Create(2, TimeSpan.FromSeconds(64)),
|
KeyValuePair.Create(2, TimeSpan.FromSeconds(64)),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, Task> concurrentTasks = new();
|
private readonly ConcurrentDictionary<ElementThemeValueFile, Task> themefileTasks = [];
|
||||||
|
private readonly ConcurrentDictionary<string, Task> downloadTasks = [];
|
||||||
|
|
||||||
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
|
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
|
||||||
private readonly IHttpClientFactory httpClientFactory;
|
private readonly IHttpClientFactory httpClientFactory;
|
||||||
@@ -48,23 +57,18 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
|||||||
{
|
{
|
||||||
get => LazyInitializer.EnsureInitialized(ref cacheFolder, () =>
|
get => LazyInitializer.EnsureInitialized(ref cacheFolder, () =>
|
||||||
{
|
{
|
||||||
return serviceProvider.GetRequiredService<RuntimeOptions>().GetLocalCacheImageCacheFolder();
|
string folder = serviceProvider.GetRequiredService<RuntimeOptions>().GetLocalCacheImageCacheFolder();
|
||||||
|
Directory.CreateDirectory(Path.Combine(folder, "Light"));
|
||||||
|
Directory.CreateDirectory(Path.Combine(folder, "Dark"));
|
||||||
|
return folder;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void RemoveInvalid()
|
|
||||||
{
|
|
||||||
RemoveCore(Directory.GetFiles(CacheFolder).Where(file => IsFileInvalid(file, false)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Remove(Uri uriForCachedItem)
|
public void Remove(Uri uriForCachedItem)
|
||||||
{
|
{
|
||||||
Remove([uriForCachedItem]);
|
Remove([uriForCachedItem]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Remove(in ReadOnlySpan<Uri> uriForCachedItems)
|
public void Remove(in ReadOnlySpan<Uri> uriForCachedItems)
|
||||||
{
|
{
|
||||||
if (uriForCachedItems.Length <= 0)
|
if (uriForCachedItems.Length <= 0)
|
||||||
@@ -88,45 +92,87 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
|||||||
RemoveCore(filesToDelete);
|
RemoveCore(filesToDelete);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
public ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri)
|
||||||
public async ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri)
|
{
|
||||||
|
return GetFileFromCacheAsync(uri, ElementTheme.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri, ElementTheme theme)
|
||||||
{
|
{
|
||||||
string fileName = GetCacheFileName(uri);
|
string fileName = GetCacheFileName(uri);
|
||||||
string filePath = Path.Combine(CacheFolder, fileName);
|
string defaultFilePath = Path.Combine(CacheFolder, fileName);
|
||||||
|
string themeOrDefaultFilePath = theme is ElementTheme.Dark or ElementTheme.Light
|
||||||
|
? Path.Combine(CacheFolder, $"{theme}", fileName)
|
||||||
|
: defaultFilePath;
|
||||||
|
|
||||||
if (!IsFileInvalid(filePath))
|
if (!IsFileInvalid(themeOrDefaultFilePath))
|
||||||
{
|
{
|
||||||
return filePath;
|
return themeOrDefaultFilePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskCompletionSource taskCompletionSource = new();
|
ElementThemeValueFile key = new(fileName, theme);
|
||||||
|
|
||||||
|
// To prevent re-entrancy, always try add first, and if add failed, we try to get the task
|
||||||
|
TaskCompletionSource themeFileTcs = new();
|
||||||
|
if (themefileTasks.TryAdd(key, themeFileTcs.Task))
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (concurrentTasks.TryAdd(fileName, taskCompletionSource.Task))
|
if (!IsFileInvalid(defaultFilePath))
|
||||||
{
|
{
|
||||||
logger.LogColorizedInformation("Begin to download file from '{Uri}' to '{File}'", (uri, ConsoleColor.Cyan), (filePath, ConsoleColor.Cyan));
|
await ConvertAndSaveFileToMonoChromeAsync(defaultFilePath, themeOrDefaultFilePath, theme).ConfigureAwait(false);
|
||||||
await DownloadFileAsync(uri, filePath).ConfigureAwait(false);
|
return themeOrDefaultFilePath;
|
||||||
}
|
}
|
||||||
else if (concurrentTasks.TryGetValue(fileName, out Task? task))
|
|
||||||
|
TaskCompletionSource downloadTcs = new();
|
||||||
|
if (downloadTasks.TryAdd(fileName, downloadTcs.Task))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
logger.LogColorizedInformation("Begin to download file from '{Uri}' to '{File}'", (uri, ConsoleColor.Cyan), (defaultFilePath, ConsoleColor.Cyan));
|
||||||
|
await DownloadFileAsync(uri, defaultFilePath).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
downloadTcs.TrySetResult();
|
||||||
|
downloadTasks.TryRemove(fileName, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (downloadTasks.TryGetValue(fileName, out Task? task))
|
||||||
{
|
{
|
||||||
logger.LogDebug("Waiting for a queued image download task to complete for '{Uri}'", (uri, ConsoleColor.Cyan));
|
logger.LogDebug("Waiting for a queued image download task to complete for '{Uri}'", (uri, ConsoleColor.Cyan));
|
||||||
await task.ConfigureAwait(false);
|
await task.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
concurrentTasks.TryRemove(fileName, out _);
|
if (!IsFileInvalid(defaultFilePath))
|
||||||
|
{
|
||||||
|
await ConvertAndSaveFileToMonoChromeAsync(defaultFilePath, themeOrDefaultFilePath, theme).ConfigureAwait(false);
|
||||||
|
return themeOrDefaultFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeOrDefaultFilePath;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
taskCompletionSource.TrySetResult();
|
themeFileTcs.TrySetResult();
|
||||||
|
themefileTasks.TryRemove(key, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (themefileTasks.TryGetValue(key, out Task? themeTask))
|
||||||
|
{
|
||||||
|
await themeTask.ConfigureAwait(false);
|
||||||
|
return themeOrDefaultFilePath;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw HutaoException.NotSupported("The task should not be null.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return filePath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ValueFile GetFileFromCategoryAndName(string category, string fileName)
|
public ValueFile GetFileFromCategoryAndName(string category, string fileName)
|
||||||
{
|
{
|
||||||
Uri dummyUri = Web.HutaoEndpoints.StaticRaw(category, fileName).ToUri();
|
Uri dummyUri = HutaoEndpoints.StaticRaw(category, fileName).ToUri();
|
||||||
return Path.Combine(CacheFolder, GetCacheFileName(dummyUri));
|
return Path.Combine(CacheFolder, GetCacheFileName(dummyUri));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,6 +191,50 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
|||||||
return new FileInfo(file).Length == 0;
|
return new FileInfo(file).Length == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async ValueTask ConvertAndSaveFileToMonoChromeAsync(string sourceFile, string themeFile, ElementTheme theme)
|
||||||
|
{
|
||||||
|
if (string.Equals(sourceFile, themeFile, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (FileStream sourceStream = File.OpenRead(sourceFile))
|
||||||
|
{
|
||||||
|
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(sourceStream.AsRandomAccessStream());
|
||||||
|
|
||||||
|
// Always premultiplied to prevent some channels have a non-zero value when the alpha channel is zero
|
||||||
|
using (SoftwareBitmap sourceBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied))
|
||||||
|
{
|
||||||
|
using (BitmapBuffer sourceBuffer = sourceBitmap.LockBuffer(BitmapBufferAccessMode.ReadWrite))
|
||||||
|
{
|
||||||
|
using (IMemoryBufferReference reference = sourceBuffer.CreateReference())
|
||||||
|
{
|
||||||
|
IMemoryBufferByteAccess byteAccess = reference.As<IMemoryBufferByteAccess>();
|
||||||
|
byte value = theme is ElementTheme.Light ? (byte)0x00 : (byte)0xFF;
|
||||||
|
ConvertToMonoChrome(byteAccess, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (FileStream themeStream = File.Create(themeFile))
|
||||||
|
{
|
||||||
|
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, themeStream.AsRandomAccessStream());
|
||||||
|
encoder.SetSoftwareBitmap(sourceBitmap);
|
||||||
|
await encoder.FlushAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ConvertToMonoChrome(IMemoryBufferByteAccess byteAccess, byte background)
|
||||||
|
{
|
||||||
|
byteAccess.GetBuffer(out Span<Rgba32> span);
|
||||||
|
foreach (ref Rgba32 pixel in span)
|
||||||
|
{
|
||||||
|
pixel.A = (byte)pixel.Luminance255;
|
||||||
|
pixel.R = pixel.G = pixel.B = background;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void RemoveCore(IEnumerable<string> filePaths)
|
private void RemoveCore(IEnumerable<string> filePaths)
|
||||||
{
|
{
|
||||||
foreach (string filePath in filePaths)
|
foreach (string filePath in filePaths)
|
||||||
@@ -236,4 +326,21 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
|||||||
HashSet<string>? set = memoryCache.GetOrCreate(CacheFailedDownloadTasksName, entry => new HashSet<string>());
|
HashSet<string>? set = memoryCache.GetOrCreate(CacheFailedDownloadTasksName, entry => new HashSet<string>());
|
||||||
set?.Add(uri.ToString());
|
set?.Add(uri.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly struct ElementThemeValueFile
|
||||||
|
{
|
||||||
|
public readonly ValueFile File;
|
||||||
|
public readonly ElementTheme Theme;
|
||||||
|
|
||||||
|
public ElementThemeValueFile(ValueFile file, ElementTheme theme)
|
||||||
|
{
|
||||||
|
File = file;
|
||||||
|
Theme = theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(File, Theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
using Windows.ApplicationModel.DataTransfer;
|
using Windows.ApplicationModel.DataTransfer;
|
||||||
using Windows.Storage.Streams;
|
using Windows.Storage.Streams;
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.IO.DataTransfer;
|
namespace Snap.Hutao.Core.DataTransfer;
|
||||||
|
|
||||||
[ConstructorGenerated]
|
[ConstructorGenerated]
|
||||||
[Injection(InjectAs.Transient, typeof(IClipboardProvider))]
|
[Injection(InjectAs.Transient, typeof(IClipboardProvider))]
|
||||||
@@ -13,7 +13,6 @@ internal sealed partial class ClipboardProvider : IClipboardProvider
|
|||||||
private readonly JsonSerializerOptions options;
|
private readonly JsonSerializerOptions options;
|
||||||
private readonly ITaskContext taskContext;
|
private readonly ITaskContext taskContext;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public async ValueTask<T?> DeserializeFromJsonAsync<T>()
|
public async ValueTask<T?> DeserializeFromJsonAsync<T>()
|
||||||
where T : class
|
where T : class
|
||||||
{
|
{
|
||||||
@@ -31,7 +30,6 @@ internal sealed partial class ClipboardProvider : IClipboardProvider
|
|||||||
return JsonSerializer.Deserialize<T>(json, options);
|
return JsonSerializer.Deserialize<T>(json, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public bool SetText(string text)
|
public bool SetText(string text)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -48,7 +46,23 @@ internal sealed partial class ClipboardProvider : IClipboardProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
public async ValueTask<bool> SetTextAsync(string text)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
|
DataPackage content = new() { RequestedOperation = DataPackageOperation.Copy };
|
||||||
|
content.SetText(text);
|
||||||
|
Clipboard.SetContent(content);
|
||||||
|
Clipboard.Flush();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool SetBitmap(IRandomAccessStream stream)
|
public bool SetBitmap(IRandomAccessStream stream)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -65,4 +79,22 @@ internal sealed partial class ClipboardProvider : IClipboardProvider
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async ValueTask<bool> SetBitmapAsync(IRandomAccessStream stream)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
|
RandomAccessStreamReference reference = RandomAccessStreamReference.CreateFromStream(stream);
|
||||||
|
DataPackage content = new() { RequestedOperation = DataPackageOperation.Copy };
|
||||||
|
content.SetBitmap(reference);
|
||||||
|
Clipboard.SetContent(content);
|
||||||
|
Clipboard.Flush();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Windows.Storage.Streams;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.DataTransfer;
|
||||||
|
|
||||||
|
internal interface IClipboardProvider
|
||||||
|
{
|
||||||
|
ValueTask<T?> DeserializeFromJsonAsync<T>()
|
||||||
|
where T : class;
|
||||||
|
|
||||||
|
bool SetBitmap(IRandomAccessStream stream);
|
||||||
|
|
||||||
|
ValueTask<bool> SetBitmapAsync(IRandomAccessStream stream);
|
||||||
|
|
||||||
|
bool SetText(string text);
|
||||||
|
|
||||||
|
ValueTask<bool> SetTextAsync(string text);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.Database;
|
namespace Snap.Hutao.Core.Database.Abstraction;
|
||||||
|
|
||||||
internal interface IReorderable
|
internal interface IReorderable
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Model.Entity.Abstraction;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Database.Abstraction;
|
||||||
|
|
||||||
|
internal interface ISelectable : IAppDbEntity
|
||||||
|
{
|
||||||
|
bool IsSelected { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Snap.Hutao.Core.Database.Abstraction;
|
||||||
|
using Snap.Hutao.Model;
|
||||||
|
using Snap.Hutao.Model.Entity.Database;
|
||||||
|
using Snap.Hutao.UI.Xaml.Data;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Database;
|
||||||
|
|
||||||
|
// The scope of the view follows the scope of the service provider.
|
||||||
|
internal sealed class AdvancedDbCollectionView<TEntity> : AdvancedCollectionView<TEntity>, IAdvancedDbCollectionView<TEntity>
|
||||||
|
where TEntity : class, IAdvancedCollectionViewItem, ISelectable
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider serviceProvider;
|
||||||
|
|
||||||
|
private bool savingToDatabase = true;
|
||||||
|
|
||||||
|
public AdvancedDbCollectionView(IList<TEntity> source, IServiceProvider serviceProvider)
|
||||||
|
: base(source, true)
|
||||||
|
{
|
||||||
|
this.serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDisposable SuppressChangeCurrentItem()
|
||||||
|
{
|
||||||
|
return new CurrentItemSuppression(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnCurrentChangedOverride()
|
||||||
|
{
|
||||||
|
if (serviceProvider is null || !savingToDatabase)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEntity? currentItem = CurrentItem;
|
||||||
|
|
||||||
|
foreach (TEntity item in Source)
|
||||||
|
{
|
||||||
|
item.IsSelected = ReferenceEquals(item, currentItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||||
|
{
|
||||||
|
AppDbContext dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
|
dbContext.Set<TEntity>().ExecuteUpdate(update => update.SetProperty(entity => entity.IsSelected, false));
|
||||||
|
|
||||||
|
if (currentItem is not null)
|
||||||
|
{
|
||||||
|
dbContext.Set<TEntity>().UpdateAndSave(currentItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class CurrentItemSuppression : IDisposable
|
||||||
|
{
|
||||||
|
private readonly AdvancedDbCollectionView<TEntity> view;
|
||||||
|
private readonly TEntity? currentItem;
|
||||||
|
|
||||||
|
public CurrentItemSuppression(AdvancedDbCollectionView<TEntity> view)
|
||||||
|
{
|
||||||
|
this.view = view;
|
||||||
|
currentItem = view.CurrentItem;
|
||||||
|
view.savingToDatabase = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
view.MoveCurrentTo(currentItem);
|
||||||
|
view.savingToDatabase = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The scope of the view follows the scope of the service provider.
|
||||||
|
[SuppressMessage("", "SA1402")]
|
||||||
|
internal sealed class AdvancedDbCollectionView<TEntityAccess, TEntity> : AdvancedCollectionView<TEntityAccess>, IAdvancedDbCollectionView<TEntityAccess>
|
||||||
|
where TEntityAccess : class, IEntityAccess<TEntity>, IAdvancedCollectionViewItem
|
||||||
|
where TEntity : class, ISelectable
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider serviceProvider;
|
||||||
|
|
||||||
|
private bool savingToDatabase = true;
|
||||||
|
|
||||||
|
public AdvancedDbCollectionView(IList<TEntityAccess> source, IServiceProvider serviceProvider)
|
||||||
|
: base(source, true)
|
||||||
|
{
|
||||||
|
this.serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDisposable SuppressChangeCurrentItem()
|
||||||
|
{
|
||||||
|
return new CurrentItemSuppression(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnCurrentChangedOverride()
|
||||||
|
{
|
||||||
|
if (serviceProvider is null || !savingToDatabase)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEntityAccess? currentItem = CurrentItem;
|
||||||
|
|
||||||
|
foreach (TEntityAccess item in Source)
|
||||||
|
{
|
||||||
|
item.Entity.IsSelected = ReferenceEquals(item, currentItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||||
|
{
|
||||||
|
AppDbContext dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
|
dbContext.Set<TEntity>().ExecuteUpdate(update => update.SetProperty(entity => entity.IsSelected, false));
|
||||||
|
|
||||||
|
if (currentItem is not null)
|
||||||
|
{
|
||||||
|
dbContext.Set<TEntity>().UpdateAndSave(currentItem.Entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class CurrentItemSuppression : IDisposable
|
||||||
|
{
|
||||||
|
private readonly AdvancedDbCollectionView<TEntityAccess, TEntity> view;
|
||||||
|
private readonly TEntityAccess? currentItem;
|
||||||
|
|
||||||
|
public CurrentItemSuppression(AdvancedDbCollectionView<TEntityAccess, TEntity> view)
|
||||||
|
{
|
||||||
|
this.view = view;
|
||||||
|
currentItem = view.CurrentItem;
|
||||||
|
view.savingToDatabase = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
view.MoveCurrentTo(currentItem);
|
||||||
|
view.savingToDatabase = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,13 +20,6 @@ internal static class DbSetExtension
|
|||||||
return dbSet.SaveChangesAndClearChangeTracker();
|
return dbSet.SaveChangesAndClearChangeTracker();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ValueTask<int> AddAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity, CancellationToken token = default)
|
|
||||||
where TEntity : class
|
|
||||||
{
|
|
||||||
dbSet.Add(entity);
|
|
||||||
return dbSet.SaveChangesAndClearChangeTrackerAsync(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int AddRangeAndSave<TEntity>(this DbSet<TEntity> dbSet, IEnumerable<TEntity> entities)
|
public static int AddRangeAndSave<TEntity>(this DbSet<TEntity> dbSet, IEnumerable<TEntity> entities)
|
||||||
where TEntity : class
|
where TEntity : class
|
||||||
{
|
{
|
||||||
@@ -34,13 +27,6 @@ internal static class DbSetExtension
|
|||||||
return dbSet.SaveChangesAndClearChangeTracker();
|
return dbSet.SaveChangesAndClearChangeTracker();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ValueTask<int> AddRangeAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, IEnumerable<TEntity> entities, CancellationToken token = default)
|
|
||||||
where TEntity : class
|
|
||||||
{
|
|
||||||
dbSet.AddRange(entities);
|
|
||||||
return dbSet.SaveChangesAndClearChangeTrackerAsync(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int RemoveAndSave<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
public static int RemoveAndSave<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||||
where TEntity : class
|
where TEntity : class
|
||||||
{
|
{
|
||||||
@@ -48,13 +34,6 @@ internal static class DbSetExtension
|
|||||||
return dbSet.SaveChangesAndClearChangeTracker();
|
return dbSet.SaveChangesAndClearChangeTracker();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ValueTask<int> RemoveAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity, CancellationToken token = default)
|
|
||||||
where TEntity : class
|
|
||||||
{
|
|
||||||
dbSet.Remove(entity);
|
|
||||||
return dbSet.SaveChangesAndClearChangeTrackerAsync(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int UpdateAndSave<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
public static int UpdateAndSave<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||||
where TEntity : class
|
where TEntity : class
|
||||||
{
|
{
|
||||||
@@ -62,13 +41,6 @@ internal static class DbSetExtension
|
|||||||
return dbSet.SaveChangesAndClearChangeTracker();
|
return dbSet.SaveChangesAndClearChangeTracker();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ValueTask<int> UpdateAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity, CancellationToken token = default)
|
|
||||||
where TEntity : class
|
|
||||||
{
|
|
||||||
dbSet.Update(entity);
|
|
||||||
return dbSet.SaveChangesAndClearChangeTrackerAsync(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static int SaveChangesAndClearChangeTracker<TEntity>(this DbSet<TEntity> dbSet)
|
private static int SaveChangesAndClearChangeTracker<TEntity>(this DbSet<TEntity> dbSet)
|
||||||
where TEntity : class
|
where TEntity : class
|
||||||
@@ -79,16 +51,6 @@ internal static class DbSetExtension
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static async ValueTask<int> SaveChangesAndClearChangeTrackerAsync<TEntity>(this DbSet<TEntity> dbSet, CancellationToken token = default)
|
|
||||||
where TEntity : class
|
|
||||||
{
|
|
||||||
DbContext dbContext = dbSet.Context();
|
|
||||||
int count = await dbContext.SaveChangesAsync(token).ConfigureAwait(false);
|
|
||||||
dbContext.ChangeTracker.Clear();
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static DbContext Context<TEntity>(this DbSet<TEntity> dbSet)
|
private static DbContext Context<TEntity>(this DbSet<TEntity> dbSet)
|
||||||
where TEntity : class
|
where TEntity : class
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.UI.Xaml.Data;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Database;
|
||||||
|
|
||||||
|
internal interface IAdvancedDbCollectionView<TEntity> : IAdvancedCollectionView<TEntity>
|
||||||
|
where TEntity : class
|
||||||
|
{
|
||||||
|
IDisposable SuppressChangeCurrentItem();
|
||||||
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.Database;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 可选择的项
|
|
||||||
/// 若要使用 <see cref="ScopedDbCurrent{TEntity, TMessage}"/>
|
|
||||||
/// 必须实现该接口
|
|
||||||
/// </summary>
|
|
||||||
[HighQuality]
|
|
||||||
internal interface ISelectable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 数据库内部Id
|
|
||||||
/// </summary>
|
|
||||||
Guid InnerId { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取或设置当前项的选中状态
|
|
||||||
/// </summary>
|
|
||||||
bool IsSelected { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using CommunityToolkit.WinUI.Collections;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Snap.Hutao.Core.Database.Abstraction;
|
||||||
using Snap.Hutao.Model;
|
using Snap.Hutao.Model;
|
||||||
using Snap.Hutao.Model.Entity.Database;
|
using Snap.Hutao.Model.Entity.Database;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
@@ -22,8 +22,6 @@ internal sealed class ObservableReorderableDbCollection<TEntity> : ObservableCol
|
|||||||
this.serviceProvider = serviceProvider;
|
this.serviceProvider = serviceProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAdvancedCollectionView? View { get; set; }
|
|
||||||
|
|
||||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnCollectionChanged(e);
|
base.OnCollectionChanged(e);
|
||||||
@@ -50,8 +48,6 @@ internal sealed class ObservableReorderableDbCollection<TEntity> : ObservableCol
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void OnReorder()
|
private void OnReorder()
|
||||||
{
|
|
||||||
using (View?.DeferRefresh())
|
|
||||||
{
|
{
|
||||||
AdjustIndex((List<TEntity>)Items);
|
AdjustIndex((List<TEntity>)Items);
|
||||||
|
|
||||||
@@ -65,24 +61,21 @@ internal sealed class ObservableReorderableDbCollection<TEntity> : ObservableCol
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("", "SA1402")]
|
[SuppressMessage("", "SA1402")]
|
||||||
internal sealed class ObservableReorderableDbCollection<TEntityOnly, TEntity> : ObservableCollection<TEntityOnly>
|
internal sealed class ObservableReorderableDbCollection<TEntityAccess, TEntity> : ObservableCollection<TEntityAccess>
|
||||||
where TEntityOnly : class, IEntityAccess<TEntity>
|
where TEntityAccess : class, IEntityAccess<TEntity>
|
||||||
where TEntity : class, IReorderable
|
where TEntity : class, IReorderable
|
||||||
{
|
{
|
||||||
private readonly IServiceProvider serviceProvider;
|
private readonly IServiceProvider serviceProvider;
|
||||||
|
|
||||||
public ObservableReorderableDbCollection(List<TEntityOnly> items, IServiceProvider serviceProvider)
|
public ObservableReorderableDbCollection(List<TEntityAccess> items, IServiceProvider serviceProvider)
|
||||||
: base(AdjustIndex(items.SortBy(x => x.Entity.Index)))
|
: base(AdjustIndex(items.SortBy(x => x.Entity.Index)))
|
||||||
{
|
{
|
||||||
this.serviceProvider = serviceProvider;
|
this.serviceProvider = serviceProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAdvancedCollectionView? View { get; set; }
|
|
||||||
|
|
||||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnCollectionChanged(e);
|
base.OnCollectionChanged(e);
|
||||||
@@ -96,12 +89,12 @@ internal sealed class ObservableReorderableDbCollection<TEntityOnly, TEntity> :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<TEntityOnly> AdjustIndex(List<TEntityOnly> list)
|
private static List<TEntityAccess> AdjustIndex(List<TEntityAccess> list)
|
||||||
{
|
{
|
||||||
Span<TEntityOnly> span = CollectionsMarshal.AsSpan(list);
|
Span<TEntityAccess> span = CollectionsMarshal.AsSpan(list);
|
||||||
for (int i = 0; i < list.Count; i++)
|
for (int i = 0; i < list.Count; i++)
|
||||||
{
|
{
|
||||||
ref readonly TEntityOnly item = ref span[i];
|
ref readonly TEntityAccess item = ref span[i];
|
||||||
item.Entity.Index = i;
|
item.Entity.Index = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,20 +103,17 @@ internal sealed class ObservableReorderableDbCollection<TEntityOnly, TEntity> :
|
|||||||
|
|
||||||
private void OnReorder()
|
private void OnReorder()
|
||||||
{
|
{
|
||||||
using (View?.DeferRefresh())
|
AdjustIndex((List<TEntityAccess>)Items);
|
||||||
{
|
|
||||||
AdjustIndex((List<TEntityOnly>)Items);
|
|
||||||
|
|
||||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||||
{
|
{
|
||||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
|
|
||||||
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
|
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
|
||||||
foreach (ref readonly TEntityOnly item in CollectionsMarshal.AsSpan((List<TEntityOnly>)Items))
|
foreach (ref readonly TEntityAccess item in CollectionsMarshal.AsSpan((List<TEntityAccess>)Items))
|
||||||
{
|
{
|
||||||
dbSet.UpdateAndSave(item.Entity);
|
dbSet.UpdateAndSave(item.Entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Core.Database.Abstraction;
|
||||||
|
using Snap.Hutao.Model;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Database;
|
||||||
|
|
||||||
|
internal static class ObservableReorderableDbCollectionExtension
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ObservableReorderableDbCollection<TEntity> ToObservableReorderableDbCollection<TEntity>(this IEnumerable<TEntity> source, IServiceProvider serviceProvider)
|
||||||
|
where TEntity : class, IReorderable
|
||||||
|
{
|
||||||
|
return source is List<TEntity> list
|
||||||
|
? new ObservableReorderableDbCollection<TEntity>(list, serviceProvider)
|
||||||
|
: new ObservableReorderableDbCollection<TEntity>([.. source], serviceProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ObservableReorderableDbCollection<TEntityOnly, TEntity> ToObservableReorderableDbCollection<TEntityOnly, TEntity>(this IEnumerable<TEntityOnly> source, IServiceProvider serviceProvider)
|
||||||
|
where TEntityOnly : class, IEntityAccess<TEntity>
|
||||||
|
where TEntity : class, IReorderable
|
||||||
|
{
|
||||||
|
return source is List<TEntityOnly> list
|
||||||
|
? new ObservableReorderableDbCollection<TEntityOnly, TEntity>(list, serviceProvider)
|
||||||
|
: new ObservableReorderableDbCollection<TEntityOnly, TEntity>([.. source], serviceProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using System.Linq.Expressions;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.Database;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 可查询扩展
|
|
||||||
/// </summary>
|
|
||||||
[HighQuality]
|
|
||||||
internal static class QueryableExtension
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// <code>source.Where(predicate).ExecuteDelete()</code>
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TSource">源类型</typeparam>
|
|
||||||
/// <param name="source">源</param>
|
|
||||||
/// <param name="predicate">条件</param>
|
|
||||||
/// <returns>SQL返回个数</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static int ExecuteDeleteWhere<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
|
|
||||||
{
|
|
||||||
return source.Where(predicate).ExecuteDelete();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <code>source.Where(predicate).ExecuteDeleteAsync(token)</code>
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TSource">源类型</typeparam>
|
|
||||||
/// <param name="source">源</param>
|
|
||||||
/// <param name="predicate">条件</param>
|
|
||||||
/// <param name="token">取消令牌</param>
|
|
||||||
/// <returns>SQL返回个数</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static ValueTask<int> ExecuteDeleteWhereAsync<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate, CancellationToken token = default)
|
|
||||||
{
|
|
||||||
return source.Where(predicate).ExecuteDeleteAsync(token).AsValueTask();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Snap.Hutao.Model;
|
|
||||||
using Snap.Hutao.Model.Entity.Database;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.Database;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 范围化的数据库当前项
|
|
||||||
/// 简化对数据库中选中项的管理
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TEntity">实体的类型</typeparam>
|
|
||||||
/// <typeparam name="TMessage">消息的类型</typeparam>
|
|
||||||
[ConstructorGenerated]
|
|
||||||
internal sealed partial class ScopedDbCurrent<TEntity, TMessage>
|
|
||||||
where TEntity : class, ISelectable
|
|
||||||
where TMessage : Message.ValueChangedMessage<TEntity>, new()
|
|
||||||
{
|
|
||||||
private readonly IServiceProvider serviceProvider;
|
|
||||||
private readonly IMessenger messenger;
|
|
||||||
|
|
||||||
private TEntity? current;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 当前选中的项
|
|
||||||
/// </summary>
|
|
||||||
public TEntity? Current
|
|
||||||
{
|
|
||||||
get => current;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
// prevent useless sets
|
|
||||||
if (current == value)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serviceProvider.IsDisposed())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
|
||||||
{
|
|
||||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
||||||
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
|
|
||||||
|
|
||||||
// only update when not processing a deletion
|
|
||||||
if (value is not null && current is not null)
|
|
||||||
{
|
|
||||||
current.IsSelected = false;
|
|
||||||
dbSet.UpdateAndSave(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
TMessage message = new() { OldValue = current, NewValue = value };
|
|
||||||
|
|
||||||
current = value;
|
|
||||||
|
|
||||||
if (current is not null)
|
|
||||||
{
|
|
||||||
current.IsSelected = true;
|
|
||||||
dbSet.UpdateAndSave(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
messenger.Send(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConstructorGenerated]
|
|
||||||
internal sealed partial class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
|
|
||||||
where TEntityOnly : class, IEntityAccess<TEntity>
|
|
||||||
where TEntity : class, ISelectable
|
|
||||||
where TMessage : Message.ValueChangedMessage<TEntityOnly>, new()
|
|
||||||
{
|
|
||||||
private readonly IServiceProvider serviceProvider;
|
|
||||||
private readonly IMessenger messenger;
|
|
||||||
|
|
||||||
private TEntityOnly? current;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 当前选中的项
|
|
||||||
/// </summary>
|
|
||||||
public TEntityOnly? Current
|
|
||||||
{
|
|
||||||
get => current;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
// prevent useless sets
|
|
||||||
if (current == value)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serviceProvider.IsDisposed())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
|
||||||
{
|
|
||||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
||||||
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
|
|
||||||
|
|
||||||
// only update when not processing a deletion
|
|
||||||
if (value is not null)
|
|
||||||
{
|
|
||||||
if (current is not null)
|
|
||||||
{
|
|
||||||
current.Entity.IsSelected = false;
|
|
||||||
dbSet.UpdateAndSave(current.Entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TMessage message = new() { OldValue = current, NewValue = value };
|
|
||||||
|
|
||||||
current = value;
|
|
||||||
|
|
||||||
if (current is not null)
|
|
||||||
{
|
|
||||||
current.Entity.IsSelected = true;
|
|
||||||
dbSet.UpdateAndSave(current.Entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
messenger.Send(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Core.Database.Abstraction;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.Database;
|
namespace Snap.Hutao.Core.Database;
|
||||||
@@ -11,13 +12,6 @@ namespace Snap.Hutao.Core.Database;
|
|||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal static class SelectableExtension
|
internal static class SelectableExtension
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 获取选中的值或默认值
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TSource">源类型</typeparam>
|
|
||||||
/// <param name="source">源</param>
|
|
||||||
/// <returns>选中的值或默认值</returns>
|
|
||||||
/// <exception cref="InvalidOperationException">存在多个选中的值</exception>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static TSource? SelectedOrDefault<TSource>(this IEnumerable<TSource> source)
|
public static TSource? SelectedOrDefault<TSource>(this IEnumerable<TSource> source)
|
||||||
where TSource : ISelectable
|
where TSource : ISelectable
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 可转换类型服务
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Not useful anymore")]
|
|
||||||
internal interface ICastService
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -20,7 +20,7 @@ internal abstract class OverseaSupportFactory<TClient, TClientCN, TClientOS> : I
|
|||||||
this.serviceProvider = serviceProvider;
|
this.serviceProvider = serviceProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TClient Create(bool isOversea)
|
public virtual TClient Create(bool isOversea)
|
||||||
{
|
{
|
||||||
return isOversea
|
return isOversea
|
||||||
? serviceProvider.GetRequiredService<TClientOS>()
|
? serviceProvider.GetRequiredService<TClientOS>()
|
||||||
|
|||||||
@@ -30,10 +30,7 @@ internal static class IocConfiguration
|
|||||||
/// <returns>可继续操作的集合</returns>
|
/// <returns>可继续操作的集合</returns>
|
||||||
public static IServiceCollection AddDatabase(this IServiceCollection services)
|
public static IServiceCollection AddDatabase(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
return services
|
return services.AddDbContextPool<AppDbContext>(AddDbContextCore);
|
||||||
.AddTransient(typeof(Database.ScopedDbCurrent<,>))
|
|
||||||
.AddTransient(typeof(Database.ScopedDbCurrent<,,>))
|
|
||||||
.AddDbContextPool<AppDbContext>(AddDbContextCore);
|
|
||||||
|
|
||||||
static void AddDbContextCore(IServiceProvider serviceProvider, DbContextOptionsBuilder builder)
|
static void AddDbContextCore(IServiceProvider serviceProvider, DbContextOptionsBuilder builder)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,11 +38,6 @@ internal static partial class IocHttpClientConfiguration
|
|||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
public static partial IServiceCollection AddHttpClients(this IServiceCollection services);
|
public static partial IServiceCollection AddHttpClients(this IServiceCollection services);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 默认配置
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serviceProvider">服务提供器</param>
|
|
||||||
/// <param name="client">配置后的客户端</param>
|
|
||||||
private static void DefaultConfiguration(IServiceProvider serviceProvider, HttpClient client)
|
private static void DefaultConfiguration(IServiceProvider serviceProvider, HttpClient client)
|
||||||
{
|
{
|
||||||
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||||
@@ -51,10 +46,6 @@ internal static partial class IocHttpClientConfiguration
|
|||||||
client.DefaultRequestHeaders.UserAgent.ParseAdd(runtimeOptions.UserAgent);
|
client.DefaultRequestHeaders.UserAgent.ParseAdd(runtimeOptions.UserAgent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 对于需要添加动态密钥1的客户端使用此配置
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client">配置后的客户端</param>
|
|
||||||
private static void XRpcConfiguration(HttpClient client)
|
private static void XRpcConfiguration(HttpClient client)
|
||||||
{
|
{
|
||||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||||
@@ -65,10 +56,6 @@ internal static partial class IocHttpClientConfiguration
|
|||||||
client.DefaultRequestHeaders.Add("x-rpc-device_id", HoyolabOptions.DeviceId36);
|
client.DefaultRequestHeaders.Add("x-rpc-device_id", HoyolabOptions.DeviceId36);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 对于需要添加动态密钥2的客户端使用此配置
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client">配置后的客户端</param>
|
|
||||||
private static void XRpc2Configuration(HttpClient client)
|
private static void XRpc2Configuration(HttpClient client)
|
||||||
{
|
{
|
||||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||||
@@ -84,11 +71,6 @@ internal static partial class IocHttpClientConfiguration
|
|||||||
client.DefaultRequestHeaders.Add("x-rpc-sdk_version", "2.16.0");
|
client.DefaultRequestHeaders.Add("x-rpc-sdk_version", "2.16.0");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 对于需要添加动态密钥1的客户端使用此配置
|
|
||||||
/// HoYoLAB app
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client">配置后的客户端</param>
|
|
||||||
private static void XRpc3Configuration(HttpClient client)
|
private static void XRpc3Configuration(HttpClient client)
|
||||||
{
|
{
|
||||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||||
@@ -100,11 +82,6 @@ internal static partial class IocHttpClientConfiguration
|
|||||||
client.DefaultRequestHeaders.Add("x-rpc-device_id", HoyolabOptions.DeviceId36);
|
client.DefaultRequestHeaders.Add("x-rpc-device_id", HoyolabOptions.DeviceId36);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 对于需要添加动态密钥2的客户端使用此配置
|
|
||||||
/// HoYoLAB web
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client">配置后的客户端</param>
|
|
||||||
[SuppressMessage("", "IDE0051")]
|
[SuppressMessage("", "IDE0051")]
|
||||||
private static void XRpc4Configuration(HttpClient client)
|
private static void XRpc4Configuration(HttpClient client)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,11 +10,6 @@ namespace Snap.Hutao.Core.DependencyInjection;
|
|||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal static partial class ServiceCollectionExtension
|
internal static partial class ServiceCollectionExtension
|
||||||
{
|
{
|
||||||
/// <summary>
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
/// 向容器注册服务
|
|
||||||
/// 此方法将会自动生成
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="services">容器</param>
|
|
||||||
/// <returns>可继续操作的服务集合</returns>
|
|
||||||
public static partial IServiceCollection AddInjections(this IServiceCollection services);
|
public static partial IServiceCollection AddInjections(this IServiceCollection services);
|
||||||
}
|
}
|
||||||
@@ -10,30 +10,22 @@ namespace Snap.Hutao.Core.DependencyInjection;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class ServiceProviderExtension
|
internal static class ServiceProviderExtension
|
||||||
{
|
{
|
||||||
/// <inheritdoc cref="ActivatorUtilities.CreateInstance{T}(IServiceProvider, object[])"/>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static T CreateInstance<T>(this IServiceProvider serviceProvider, params object[] parameters)
|
public static bool IsDisposed(this IServiceProvider? serviceProvider, bool treatNullAsDisposed = true)
|
||||||
{
|
|
||||||
return ActivatorUtilities.CreateInstance<T>(serviceProvider, parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static bool IsDisposed(this IServiceProvider? serviceProvider)
|
|
||||||
{
|
{
|
||||||
if (serviceProvider is null)
|
if (serviceProvider is null)
|
||||||
|
{
|
||||||
|
return treatNullAsDisposed;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ = serviceProvider.GetRequiredService<IServiceScopeFactory>();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceProvider is ServiceProvider serviceProviderImpl)
|
|
||||||
{
|
|
||||||
return GetPrivateDisposed(serviceProviderImpl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return serviceProvider.GetType().GetField("_disposed")?.GetValue(serviceProvider) is true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// private bool _disposed;
|
|
||||||
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_disposed")]
|
|
||||||
private static extern ref bool GetPrivateDisposed(ServiceProvider serviceProvider);
|
|
||||||
}
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 指示此特性附加的属性会在属性改变后会异步地设置的其他属性
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
|
|
||||||
internal sealed class AlsoAsyncSetsAttribute : Attribute
|
|
||||||
{
|
|
||||||
public AlsoAsyncSetsAttribute(string propertyName)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public AlsoAsyncSetsAttribute(string propertyName1, string propertyName2)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public AlsoAsyncSetsAttribute(params string[] propertyNames)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 指示此特性附加的属性会在属性改变后会设置的其他属性
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
|
|
||||||
internal sealed class AlsoSetsAttribute : Attribute
|
|
||||||
{
|
|
||||||
public AlsoSetsAttribute(string propertyName)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public AlsoSetsAttribute(string propertyName1, string propertyName2)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public AlsoSetsAttribute(params string[] propertyNames)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,13 +2,10 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.ExceptionService;
|
namespace Snap.Hutao.Core.ExceptionService;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 异常记录器
|
|
||||||
/// </summary>
|
|
||||||
[HighQuality]
|
|
||||||
[ConstructorGenerated]
|
[ConstructorGenerated]
|
||||||
[Injection(InjectAs.Singleton)]
|
[Injection(InjectAs.Singleton)]
|
||||||
internal sealed partial class ExceptionRecorder
|
internal sealed partial class ExceptionRecorder
|
||||||
@@ -16,13 +13,15 @@ internal sealed partial class ExceptionRecorder
|
|||||||
private readonly ILogger<ExceptionRecorder> logger;
|
private readonly ILogger<ExceptionRecorder> logger;
|
||||||
private readonly IServiceProvider serviceProvider;
|
private readonly IServiceProvider serviceProvider;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 记录应用程序异常
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="app">应用程序</param>
|
|
||||||
public void Record(Application app)
|
public void Record(Application app)
|
||||||
{
|
{
|
||||||
app.UnhandledException += OnAppUnhandledException;
|
app.UnhandledException += OnAppUnhandledException;
|
||||||
|
ConfigureDebugSettings(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Conditional("DEBUG")]
|
||||||
|
private void ConfigureDebugSettings(Application app)
|
||||||
|
{
|
||||||
app.DebugSettings.FailFastOnErrors = false;
|
app.DebugSettings.FailFastOnErrors = false;
|
||||||
|
|
||||||
app.DebugSettings.IsBindingTracingEnabled = true;
|
app.DebugSettings.IsBindingTracingEnabled = true;
|
||||||
@@ -35,19 +34,13 @@ internal sealed partial class ExceptionRecorder
|
|||||||
app.DebugSettings.LayoutCycleDebugBreakLevel = LayoutCycleDebugBreakLevel.High;
|
app.DebugSettings.LayoutCycleDebugBreakLevel = LayoutCycleDebugBreakLevel.High;
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("", "CA2012")]
|
|
||||||
private void OnAppUnhandledException(object? sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
private void OnAppUnhandledException(object? sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
ValueTask<string?> task = serviceProvider
|
|
||||||
.GetRequiredService<Web.Hutao.Log.HutaoLogUploadClient>()
|
|
||||||
.UploadLogAsync(e.Exception);
|
|
||||||
|
|
||||||
if (!task.IsCompleted)
|
|
||||||
{
|
|
||||||
task.GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.LogError("未经处理的全局异常:\r\n{Detail}", ExceptionFormat.Format(e.Exception));
|
logger.LogError("未经处理的全局异常:\r\n{Detail}", ExceptionFormat.Format(e.Exception));
|
||||||
|
|
||||||
|
serviceProvider
|
||||||
|
.GetRequiredService<Web.Hutao.Log.HutaoLogUploadClient>()
|
||||||
|
.UploadLog(e.Exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnXamlBindingFailed(object? sender, BindingFailedEventArgs e)
|
private void OnXamlBindingFailed(object? sender, BindingFailedEventArgs e)
|
||||||
|
|||||||
@@ -51,13 +51,6 @@ internal sealed class HutaoException : Exception
|
|||||||
throw new HutaoException(SH.FormatServiceGachaStatisticsFactoryItemIdInvalid(id), innerException);
|
throw new HutaoException(SH.FormatServiceGachaStatisticsFactoryItemIdInvalid(id), innerException);
|
||||||
}
|
}
|
||||||
|
|
||||||
[DoesNotReturn]
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
||||||
public static HutaoException UserdataCorrupted(string message, Exception? innerException = default)
|
|
||||||
{
|
|
||||||
throw new HutaoException(message, innerException);
|
|
||||||
}
|
|
||||||
|
|
||||||
[DoesNotReturn]
|
[DoesNotReturn]
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public static InvalidCastException InvalidCast<TFrom, TTo>(string name, Exception? innerException = default)
|
public static InvalidCastException InvalidCast<TFrom, TTo>(string name, Exception? innerException = default)
|
||||||
|
|||||||
@@ -1,24 +1,16 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.UI;
|
||||||
using Snap.Hutao.Win32.System.WinRT;
|
using Snap.Hutao.Win32.System.WinRT;
|
||||||
using Windows.Foundation;
|
using Windows.Foundation;
|
||||||
using Windows.Graphics.Imaging;
|
using Windows.Graphics.Imaging;
|
||||||
using WinRT;
|
using WinRT;
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Media;
|
namespace Snap.Hutao.Core.Graphics.Imaging;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 软件位图拓展
|
|
||||||
/// </summary>
|
|
||||||
[HighQuality]
|
|
||||||
internal static class SoftwareBitmapExtension
|
internal static class SoftwareBitmapExtension
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 混合模式 正常
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="softwareBitmap">软件位图</param>
|
|
||||||
/// <param name="tint">底色</param>
|
|
||||||
public static unsafe void NormalBlend(this SoftwareBitmap softwareBitmap, Bgra32 tint)
|
public static unsafe void NormalBlend(this SoftwareBitmap softwareBitmap, Bgra32 tint)
|
||||||
{
|
{
|
||||||
using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.ReadWrite))
|
using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.ReadWrite))
|
||||||
@@ -39,7 +31,7 @@ internal static class SoftwareBitmapExtension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static unsafe Bgra32 GetAccentColor(this SoftwareBitmap softwareBitmap)
|
public static unsafe Bgra32 GetBgra32AccentColor(this SoftwareBitmap softwareBitmap)
|
||||||
{
|
{
|
||||||
using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
|
using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
|
||||||
{
|
{
|
||||||
@@ -59,4 +51,25 @@ internal static class SoftwareBitmapExtension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static unsafe Rgba32 GetRgba32AccentColor(this SoftwareBitmap softwareBitmap)
|
||||||
|
{
|
||||||
|
using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
|
||||||
|
{
|
||||||
|
using (IMemoryBufferReference reference = buffer.CreateReference())
|
||||||
|
{
|
||||||
|
reference.As<IMemoryBufferByteAccess>().GetBuffer(out Span<Bgra32> bytes);
|
||||||
|
double b = 0, g = 0, r = 0, a = 0;
|
||||||
|
foreach (ref readonly Bgra32 pixel in bytes)
|
||||||
|
{
|
||||||
|
b += pixel.B;
|
||||||
|
g += pixel.G;
|
||||||
|
r += pixel.R;
|
||||||
|
a += pixel.A;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new((byte)(r / bytes.Length), (byte)(g / bytes.Length), (byte)(b / bytes.Length), (byte)(a / bytes.Length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
17
src/Snap.Hutao/Snap.Hutao/Core/Graphics/PointInt32Kind.cs
Normal file
17
src/Snap.Hutao/Snap.Hutao/Core/Graphics/PointInt32Kind.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Graphics;
|
||||||
|
|
||||||
|
internal enum PointInt32Kind
|
||||||
|
{
|
||||||
|
TopLeft,
|
||||||
|
TopCenter,
|
||||||
|
TopRight,
|
||||||
|
CenterLeft,
|
||||||
|
Center,
|
||||||
|
CenterRight,
|
||||||
|
BottomLeft,
|
||||||
|
BottomCenter,
|
||||||
|
BottomRight,
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.Windowing.NotifyIcon;
|
namespace Snap.Hutao.Core.Graphics;
|
||||||
|
|
||||||
internal readonly struct PointUInt16
|
internal readonly struct PointUInt16
|
||||||
{
|
{
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
using Windows.Graphics;
|
using Windows.Graphics;
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.Windowing;
|
namespace Snap.Hutao.Core.Graphics;
|
||||||
|
|
||||||
internal readonly struct RectInt16
|
internal readonly struct RectInt16
|
||||||
{
|
{
|
||||||
35
src/Snap.Hutao/Snap.Hutao/Core/Graphics/RectInt32Convert.cs
Normal file
35
src/Snap.Hutao/Snap.Hutao/Core/Graphics/RectInt32Convert.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Win32.Foundation;
|
||||||
|
using System.Numerics;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.Graphics;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Graphics;
|
||||||
|
|
||||||
|
internal static class RectInt32Convert
|
||||||
|
{
|
||||||
|
public static RectInt32 RectInt32(RECT rect)
|
||||||
|
{
|
||||||
|
return new(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RectInt32 RectInt32(Point position, Vector2 size)
|
||||||
|
{
|
||||||
|
return new((int)position.X, (int)position.Y, (int)size.X, (int)size.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RectInt32 RectInt32(int x, int y, Vector2 size)
|
||||||
|
{
|
||||||
|
return new(x, y, (int)size.X, (int)size.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unsafe RectInt32 RectInt32(PointInt32 position, SizeInt32 size)
|
||||||
|
{
|
||||||
|
RectInt32View view = default;
|
||||||
|
view.Position = position;
|
||||||
|
view.Size = size;
|
||||||
|
return *(RectInt32*)&view;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||||
|
using Snap.Hutao.Win32.Foundation;
|
||||||
|
using Windows.Graphics;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Graphics;
|
||||||
|
|
||||||
|
internal static class RectInt32Extension
|
||||||
|
{
|
||||||
|
public static RectInt32 Scale(this RectInt32 rectInt32, double scale)
|
||||||
|
{
|
||||||
|
return new((int)(rectInt32.X * scale), (int)(rectInt32.Y * scale), (int)(rectInt32.Width * scale), (int)(rectInt32.Height * scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int Size(this RectInt32 rectInt32)
|
||||||
|
{
|
||||||
|
return rectInt32.Width * rectInt32.Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unsafe SizeInt32 GetSizeInt32(this RectInt32 rectInt32)
|
||||||
|
{
|
||||||
|
return ((RectInt32View*)&rectInt32)->Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unsafe PointInt32 GetPointInt32(this RectInt32 rectInt32, PointInt32Kind kind)
|
||||||
|
{
|
||||||
|
RectInt32View* pView = (RectInt32View*)&rectInt32;
|
||||||
|
PointInt32 topLeft = pView->Position;
|
||||||
|
SizeInt32 size = pView->Size;
|
||||||
|
return kind switch
|
||||||
|
{
|
||||||
|
PointInt32Kind.TopLeft => topLeft,
|
||||||
|
PointInt32Kind.TopCenter => new PointInt32(topLeft.X + (size.Width / 2), topLeft.Y),
|
||||||
|
PointInt32Kind.TopRight => new PointInt32(topLeft.X + size.Width, topLeft.Y),
|
||||||
|
PointInt32Kind.CenterLeft => new PointInt32(topLeft.X, topLeft.Y + (size.Height / 2)),
|
||||||
|
PointInt32Kind.Center => new PointInt32(topLeft.X + (size.Width / 2), topLeft.Y + (size.Height / 2)),
|
||||||
|
PointInt32Kind.CenterRight => new PointInt32(topLeft.X + size.Width, topLeft.Y + (size.Height / 2)),
|
||||||
|
PointInt32Kind.BottomLeft => new PointInt32(topLeft.X, topLeft.Y + size.Height),
|
||||||
|
PointInt32Kind.BottomCenter => new PointInt32(topLeft.X + (size.Width / 2), topLeft.Y + size.Height),
|
||||||
|
PointInt32Kind.BottomRight => new PointInt32(topLeft.X + size.Width, topLeft.Y + size.Height),
|
||||||
|
_ => default,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RECT ToRECT(this RectInt32 rect)
|
||||||
|
{
|
||||||
|
return new(rect.X, rect.Y, rect.X + rect.Width, rect.Y + rect.Height);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/Snap.Hutao/Snap.Hutao/Core/Graphics/RectInt32View.cs
Normal file
12
src/Snap.Hutao/Snap.Hutao/Core/Graphics/RectInt32View.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Windows.Graphics;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Graphics;
|
||||||
|
|
||||||
|
internal struct RectInt32View
|
||||||
|
{
|
||||||
|
public PointInt32 Position;
|
||||||
|
public SizeInt32 Size;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Windows.Graphics;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Graphics;
|
||||||
|
|
||||||
|
internal static class SizeInt32Extension
|
||||||
|
{
|
||||||
|
public static SizeInt32 Scale(this SizeInt32 sizeInt32, double scale)
|
||||||
|
{
|
||||||
|
return new((int)(sizeInt32.Width * scale), (int)(sizeInt32.Height * scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int Size(this SizeInt32 sizeInt32)
|
||||||
|
{
|
||||||
|
return sizeInt32.Width * sizeInt32.Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unsafe RectInt32 ToRectInt32(this SizeInt32 sizeInt32)
|
||||||
|
{
|
||||||
|
RectInt32View view = default;
|
||||||
|
view.Size = sizeInt32;
|
||||||
|
return *(RectInt32*)&view;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Windows.Storage.Streams;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.IO.DataTransfer;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 剪贴板互操作
|
|
||||||
/// </summary>
|
|
||||||
internal interface IClipboardProvider
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 从剪贴板文本中反序列化
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">目标类型</typeparam>
|
|
||||||
/// <returns>实例</returns>
|
|
||||||
ValueTask<T?> DeserializeFromJsonAsync<T>()
|
|
||||||
where T : class;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置位图
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="stream">图片流</param>
|
|
||||||
/// <returns>是否设置成功</returns>
|
|
||||||
bool SetBitmap(IRandomAccessStream stream);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置文本
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text">文本</param>
|
|
||||||
/// <returns>是否设置成功</returns>
|
|
||||||
bool SetText(string text);
|
|
||||||
}
|
|
||||||
@@ -13,16 +13,23 @@ namespace Snap.Hutao.Core.IO;
|
|||||||
|
|
||||||
internal static class DirectoryOperation
|
internal static class DirectoryOperation
|
||||||
{
|
{
|
||||||
public static bool Move(string sourceDirName, string destDirName)
|
public static bool TryMove(string sourceDirName, string destDirName)
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(sourceDirName))
|
if (!Directory.Exists(sourceDirName))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
FileSystem.MoveDirectory(sourceDirName, destDirName, true);
|
FileSystem.MoveDirectory(sourceDirName, destDirName, true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static unsafe bool UnsafeRename(string path, string name, FILEOPERATION_FLAGS flags = FILEOPERATION_FLAGS.FOF_ALLOWUNDO | FILEOPERATION_FLAGS.FOF_NOCONFIRMMKDIR)
|
public static unsafe bool UnsafeRename(string path, string name, FILEOPERATION_FLAGS flags = FILEOPERATION_FLAGS.FOF_ALLOWUNDO | FILEOPERATION_FLAGS.FOF_NOCONFIRMMKDIR)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,19 +31,44 @@ internal static class FileOperation
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (overwrite)
|
if (overwrite)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
File.Move(sourceFileName, destFileName, true);
|
File.Move(sourceFileName, destFileName, true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (File.Exists(destFileName))
|
if (File.Exists(destFileName))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
File.Move(sourceFileName, destFileName, false);
|
File.Move(sourceFileName, destFileName, false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Delete(string path)
|
||||||
|
{
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
File.Delete(path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public static unsafe bool UnsafeDelete(string path)
|
public static unsafe bool UnsafeDelete(string path)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ using System.Text;
|
|||||||
namespace Snap.Hutao.Core.IO.Hashing;
|
namespace Snap.Hutao.Core.IO.Hashing;
|
||||||
|
|
||||||
#if NET9_0_OR_GREATER
|
#if NET9_0_OR_GREATER
|
||||||
[Obsolete]
|
[Obsolete("Use CryptographicOperations.HashData()")]
|
||||||
#endif
|
#endif
|
||||||
internal static class Hash
|
internal static class Hash
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ namespace Snap.Hutao.Core.IO.Hashing;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 摘要
|
/// 摘要
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
#if NET9_0_OR_GREATER
|
||||||
|
[Obsolete("Use CryptographicOperations.HashData()")]
|
||||||
|
#endif
|
||||||
internal static class MD5
|
internal static class MD5
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ using System.IO;
|
|||||||
|
|
||||||
namespace Snap.Hutao.Core.IO.Hashing;
|
namespace Snap.Hutao.Core.IO.Hashing;
|
||||||
|
|
||||||
|
#if NET9_0_OR_GREATER
|
||||||
|
[Obsolete("Use CryptographicOperations.HashData()")]
|
||||||
|
#endif
|
||||||
internal static class SHA256
|
internal static class SHA256
|
||||||
{
|
{
|
||||||
public static async ValueTask<string> HashFileAsync(string filePath, CancellationToken token = default)
|
public static async ValueTask<string> HashFileAsync(string filePath, CancellationToken token = default)
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ namespace Snap.Hutao.Core.IO.Hashing;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// XXH64 摘要
|
/// XXH64 摘要
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
#if NET9_0_OR_GREATER
|
||||||
|
[Obsolete("Use CryptographicOperations.HashData()")]
|
||||||
|
#endif
|
||||||
internal static class XXH64
|
internal static class XXH64
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using Snap.Hutao.Win32.Security;
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using static Snap.Hutao.Win32.AdvApi32;
|
using static Snap.Hutao.Win32.AdvApi32;
|
||||||
using static Snap.Hutao.Win32.FirewallApi;
|
using static Snap.Hutao.Win32.FirewallApi;
|
||||||
|
using static Snap.Hutao.Win32.Kernel32;
|
||||||
using static Snap.Hutao.Win32.Macros;
|
using static Snap.Hutao.Win32.Macros;
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.IO.Http.Loopback;
|
namespace Snap.Hutao.Core.IO.Http.Loopback;
|
||||||
@@ -26,45 +27,7 @@ internal sealed unsafe class LoopbackManager : ObservableObject
|
|||||||
runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||||
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||||
|
|
||||||
INET_FIREWALL_APP_CONTAINER* pContainers = default;
|
Initialize(out hutaoContainerStringSID);
|
||||||
try
|
|
||||||
{
|
|
||||||
{
|
|
||||||
WIN32_ERROR error = NetworkIsolationEnumAppContainers(NETISO_FLAG.NETISO_FLAG_MAX, out uint acCount, out pContainers);
|
|
||||||
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(error));
|
|
||||||
for (uint i = 0; i < acCount; i++)
|
|
||||||
{
|
|
||||||
INET_FIREWALL_APP_CONTAINER* pContainer = pContainers + i;
|
|
||||||
ReadOnlySpan<char> appContainerName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(pContainer->appContainerName);
|
|
||||||
if (appContainerName.Equals(runtimeOptions.FamilyName, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
ConvertSidToStringSidW(pContainer->appContainerSid, out PWSTR stringSid);
|
|
||||||
hutaoContainerStringSID = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(stringSid).ToString();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
// This function returns 1 rather than 0 specfied in the document.
|
|
||||||
_ = NetworkIsolationFreeAppContainers(pContainers);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
WIN32_ERROR error = NetworkIsolationGetAppContainerConfig(out uint accCount, out SID_AND_ATTRIBUTES* pSids);
|
|
||||||
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(error));
|
|
||||||
for (uint i = 0; i < accCount; i++)
|
|
||||||
{
|
|
||||||
ConvertSidToStringSidW((pSids + i)->Sid, out PWSTR stringSid);
|
|
||||||
ReadOnlySpan<char> stringSidSpan = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(stringSid);
|
|
||||||
if (stringSidSpan.Equals(hutaoContainerStringSID, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
IsLoopbackEnabled = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsLoopbackEnabled { get => isLoopbackEnabled; private set => SetProperty(ref isLoopbackEnabled, value); }
|
public bool IsLoopbackEnabled { get => isLoopbackEnabled; private set => SetProperty(ref isLoopbackEnabled, value); }
|
||||||
@@ -84,4 +47,60 @@ internal sealed unsafe class LoopbackManager : ObservableObject
|
|||||||
sids.Add(sidAndAttributes);
|
sids.Add(sidAndAttributes);
|
||||||
IsLoopbackEnabled = NetworkIsolationSetAppContainerConfig(CollectionsMarshal.AsSpan(sids)) is WIN32_ERROR.ERROR_SUCCESS;
|
IsLoopbackEnabled = NetworkIsolationSetAppContainerConfig(CollectionsMarshal.AsSpan(sids)) is WIN32_ERROR.ERROR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Initialize(out string hutaoContainerStringSID)
|
||||||
|
{
|
||||||
|
hutaoContainerStringSID = string.Empty;
|
||||||
|
|
||||||
|
INET_FIREWALL_APP_CONTAINER* pContainers = default;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
WIN32_ERROR error = NetworkIsolationEnumAppContainers(NETISO_FLAG.NETISO_FLAG_MAX, out uint acCount, out pContainers);
|
||||||
|
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(error));
|
||||||
|
for (uint i = 0; i < acCount; i++)
|
||||||
|
{
|
||||||
|
INET_FIREWALL_APP_CONTAINER* pContainer = pContainers + i;
|
||||||
|
ReadOnlySpan<char> appContainerName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(pContainer->appContainerName);
|
||||||
|
if (appContainerName.Equals(runtimeOptions.FamilyName, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
ConvertSidToStringSidW(pContainer->appContainerSid, out PWSTR stringSid);
|
||||||
|
hutaoContainerStringSID = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(stringSid).ToString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// This function returns 1 rather than 0 specfied in the document.
|
||||||
|
_ = NetworkIsolationFreeAppContainers(pContainers);
|
||||||
|
}
|
||||||
|
|
||||||
|
SID_AND_ATTRIBUTES* pSids = default;
|
||||||
|
uint count = default;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
WIN32_ERROR error = NetworkIsolationGetAppContainerConfig(out count, out pSids);
|
||||||
|
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(error));
|
||||||
|
|
||||||
|
for (uint i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
ConvertSidToStringSidW((pSids + i)->Sid, out PWSTR stringSid);
|
||||||
|
ReadOnlySpan<char> stringSidSpan = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(stringSid);
|
||||||
|
if (stringSidSpan.Equals(hutaoContainerStringSID, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
IsLoopbackEnabled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
for (uint index = 0; index < count; index++)
|
||||||
|
{
|
||||||
|
HeapFree(GetProcessHeap(), 0, pSids[index].Sid);
|
||||||
|
}
|
||||||
|
|
||||||
|
HeapFree(GetProcessHeap(), 0, pSids);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@ internal sealed partial class HttpProxyUsingSystemProxy : ObservableObject, IWeb
|
|||||||
UpdateInnerProxy();
|
UpdateInnerProxy();
|
||||||
|
|
||||||
watcher = new(ProxySettingPath, OnSystemProxySettingsChanged);
|
watcher = new(ProxySettingPath, OnSystemProxySettingsChanged);
|
||||||
watcher.Start();
|
watcher.Start(serviceProvider.GetRequiredService<ILogger<HttpProxyUsingSystemProxy>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public string CurrentProxyUri
|
public string CurrentProxyUri
|
||||||
@@ -75,8 +75,8 @@ internal sealed partial class HttpProxyUsingSystemProxy : ObservableObject, IWeb
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
(innerProxy as IDisposable)?.Dispose();
|
|
||||||
watcher.Dispose();
|
watcher.Dispose();
|
||||||
|
(innerProxy as IDisposable)?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnSystemProxySettingsChanged()
|
public void OnSystemProxySettingsChanged()
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using System.Net.Http;
|
|||||||
|
|
||||||
namespace Snap.Hutao.Core.IO.Http.Sharding;
|
namespace Snap.Hutao.Core.IO.Http.Sharding;
|
||||||
|
|
||||||
|
// TODO: refactor to use tree structure to calculate shards
|
||||||
internal sealed class HttpShardCopyWorker<TStatus> : IDisposable
|
internal sealed class HttpShardCopyWorker<TStatus> : IDisposable
|
||||||
{
|
{
|
||||||
private const int ShardSize = 4 * 1024 * 1024;
|
private const int ShardSize = 4 * 1024 * 1024;
|
||||||
|
|||||||
@@ -3,7 +3,15 @@
|
|||||||
|
|
||||||
namespace Snap.Hutao.Core.IO;
|
namespace Snap.Hutao.Core.IO;
|
||||||
|
|
||||||
/// <summary>
|
internal sealed class StreamCopyStatus
|
||||||
/// 流复制状态
|
{
|
||||||
/// </summary>
|
public StreamCopyStatus(long bytesCopied, long totalBytes)
|
||||||
internal sealed record StreamCopyStatus(long BytesCopied, long TotalBytes);
|
{
|
||||||
|
BytesCopied = bytesCopied;
|
||||||
|
TotalBytes = totalBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long BytesCopied { get; }
|
||||||
|
|
||||||
|
public long TotalBytes { get; }
|
||||||
|
}
|
||||||
@@ -12,15 +12,8 @@ namespace Snap.Hutao.Core.IO;
|
|||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal readonly struct TempFile : IDisposable
|
internal readonly struct TempFile : IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 路径
|
|
||||||
/// </summary>
|
|
||||||
public readonly string Path;
|
public readonly string Path;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造一个新的临时文件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="delete">是否在创建时删除文件</param>
|
|
||||||
private TempFile(bool delete)
|
private TempFile(bool delete)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -38,11 +31,6 @@ internal readonly struct TempFile : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 创建临时文件并复制内容
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="file">源文件</param>
|
|
||||||
/// <returns>临时文件</returns>
|
|
||||||
public static TempFile? CopyFrom(string file)
|
public static TempFile? CopyFrom(string file)
|
||||||
{
|
{
|
||||||
TempFile temporaryFile = new(false);
|
TempFile temporaryFile = new(false);
|
||||||
@@ -57,9 +45,6 @@ internal readonly struct TempFile : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 删除临时文件
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -5,76 +5,60 @@ using System.IO;
|
|||||||
|
|
||||||
namespace Snap.Hutao.Core.IO;
|
namespace Snap.Hutao.Core.IO;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 临时文件流
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class TempFileStream : Stream
|
internal sealed class TempFileStream : Stream
|
||||||
{
|
{
|
||||||
private readonly string path;
|
private readonly string path;
|
||||||
private readonly FileStream stream;
|
private readonly FileStream stream;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造一个新的临时的文件流
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mode">文件模式</param>
|
|
||||||
/// <param name="access">访问方式</param>
|
|
||||||
public TempFileStream(FileMode mode, FileAccess access)
|
public TempFileStream(FileMode mode, FileAccess access)
|
||||||
{
|
{
|
||||||
path = Path.GetTempFileName();
|
path = Path.GetTempFileName();
|
||||||
stream = File.Open(path, mode, access);
|
stream = File.Open(path, mode, access);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override bool CanRead { get => stream.CanRead; }
|
public override bool CanRead { get => stream.CanRead; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override bool CanSeek { get => stream.CanSeek; }
|
public override bool CanSeek { get => stream.CanSeek; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override bool CanWrite { get => stream.CanWrite; }
|
public override bool CanWrite { get => stream.CanWrite; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override long Length { get => stream.Length; }
|
public override long Length { get => stream.Length; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override long Position { get => stream.Position; set => stream.Position = value; }
|
public override long Position { get => stream.Position; set => stream.Position = value; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void Flush()
|
public override void Flush()
|
||||||
{
|
{
|
||||||
stream.Flush();
|
stream.Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override int Read(byte[] buffer, int offset, int count)
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
{
|
{
|
||||||
return stream.Read(buffer, offset, count);
|
return stream.Read(buffer, offset, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override long Seek(long offset, SeekOrigin origin)
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
{
|
{
|
||||||
return stream.Seek(offset, origin);
|
return stream.Seek(offset, origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void SetLength(long value)
|
public override void SetLength(long value)
|
||||||
{
|
{
|
||||||
stream.SetLength(value);
|
stream.SetLength(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void Write(byte[] buffer, int offset, int count)
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
{
|
{
|
||||||
stream.Write(buffer, offset, count);
|
stream.Write(buffer, offset, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
base.Dispose(disposing);
|
if (disposing)
|
||||||
|
{
|
||||||
stream.Dispose();
|
stream.Dispose();
|
||||||
File.Delete(path);
|
File.Delete(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,12 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.IO;
|
namespace Snap.Hutao.Core.IO;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件路径
|
|
||||||
/// </summary>
|
|
||||||
internal readonly struct ValueFile
|
internal readonly struct ValueFile
|
||||||
{
|
{
|
||||||
private readonly string value;
|
private readonly string value;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="ValueFile"/> struct.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">value</param>
|
|
||||||
private ValueFile(string value)
|
private ValueFile(string value)
|
||||||
{
|
{
|
||||||
this.value = value;
|
this.value = value;
|
||||||
@@ -31,55 +22,6 @@ internal readonly struct ValueFile
|
|||||||
return new(value);
|
return new(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 异步反序列化文件中的内容
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">内容的类型</typeparam>
|
|
||||||
/// <param name="options">序列化选项</param>
|
|
||||||
/// <returns>操作是否成功,反序列化后的内容</returns>
|
|
||||||
public async ValueTask<ValueResult<bool, T?>> DeserializeFromJsonAsync<T>(JsonSerializerOptions options)
|
|
||||||
where T : class
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (FileStream stream = File.OpenRead(value))
|
|
||||||
{
|
|
||||||
T? t = await JsonSerializer.DeserializeAsync<T>(stream, options).ConfigureAwait(false);
|
|
||||||
return new(true, t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_ = ex;
|
|
||||||
return new(false, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 将对象异步序列化入文件
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">对象的类型</typeparam>
|
|
||||||
/// <param name="obj">对象</param>
|
|
||||||
/// <param name="options">序列化选项</param>
|
|
||||||
/// <returns>操作是否成功</returns>
|
|
||||||
public async ValueTask<bool> SerializeToJsonAsync<T>(T obj, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (FileStream stream = File.Create(value))
|
|
||||||
{
|
|
||||||
await JsonSerializer.SerializeAsync(stream, obj, options).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[SuppressMessage("", "CA1307")]
|
[SuppressMessage("", "CA1307")]
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
{
|
{
|
||||||
|
|||||||
44
src/Snap.Hutao/Snap.Hutao/Core/IO/ValueFileExtension.cs
Normal file
44
src/Snap.Hutao/Snap.Hutao/Core/IO/ValueFileExtension.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.IO;
|
||||||
|
|
||||||
|
internal static class ValueFileExtension
|
||||||
|
{
|
||||||
|
public static async ValueTask<ValueResult<bool, T?>> DeserializeFromJsonAsync<T>(this ValueFile file, JsonSerializerOptions options)
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (FileStream stream = File.OpenRead(file))
|
||||||
|
{
|
||||||
|
T? t = await JsonSerializer.DeserializeAsync<T>(stream, options).ConfigureAwait(false);
|
||||||
|
return new(true, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_ = ex;
|
||||||
|
return new(false, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async ValueTask<bool> SerializeToJsonAsync<T>(this ValueFile file, T obj, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (FileStream stream = File.Create(file))
|
||||||
|
{
|
||||||
|
await JsonSerializer.SerializeAsync(stream, obj, options).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,17 +5,11 @@ using System.Globalization;
|
|||||||
|
|
||||||
namespace Snap.Hutao.Core.Json.Converter;
|
namespace Snap.Hutao.Core.Json.Converter;
|
||||||
|
|
||||||
/// <summary>
|
// 此转换器无法实现无损往返 必须在反序列化后调整 Offset
|
||||||
/// 实现日期的转换
|
|
||||||
/// 此转换器无法实现无损往返
|
|
||||||
/// 必须在反序列化后调整 Offset
|
|
||||||
/// </summary>
|
|
||||||
[HighQuality]
|
|
||||||
internal class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
|
internal class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
|
||||||
{
|
{
|
||||||
private const string Format = "yyyy-MM-dd HH:mm:ss";
|
private const string Format = "yyyy-MM-dd HH:mm:ss";
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
if (reader.GetString() is { } dataTimeString)
|
if (reader.GetString() is { } dataTimeString)
|
||||||
@@ -29,7 +23,6 @@ internal class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
|
|||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
|
public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
writer.WriteStringValue(value.DateTime.ToString(Format, CultureInfo.InvariantCulture));
|
writer.WriteStringValue(value.DateTime.ToString(Format, CultureInfo.InvariantCulture));
|
||||||
|
|||||||
@@ -6,15 +6,11 @@ using System.Globalization;
|
|||||||
|
|
||||||
namespace Snap.Hutao.Core.Json.Converter;
|
namespace Snap.Hutao.Core.Json.Converter;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 逗号分隔列表转换器
|
|
||||||
/// </summary>
|
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal sealed class SeparatorCommaInt32EnumerableConverter : JsonConverter<IEnumerable<int>>
|
internal sealed class SeparatorCommaInt32EnumerableConverter : JsonConverter<IEnumerable<int>>
|
||||||
{
|
{
|
||||||
private const char Comma = ',';
|
private const char Comma = ',';
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override IEnumerable<int> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
public override IEnumerable<int> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
if (reader.GetString() is { } source)
|
if (reader.GetString() is { } source)
|
||||||
@@ -25,7 +21,6 @@ internal sealed class SeparatorCommaInt32EnumerableConverter : JsonConverter<IEn
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void Write(Utf8JsonWriter writer, IEnumerable<int> value, JsonSerializerOptions options)
|
public override void Write(Utf8JsonWriter writer, IEnumerable<int> value, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
writer.WriteStringValue(string.Join(Comma, value));
|
writer.WriteStringValue(string.Join(Comma, value));
|
||||||
|
|||||||
@@ -6,10 +6,6 @@ using System.Runtime.CompilerServices;
|
|||||||
|
|
||||||
namespace Snap.Hutao.Core.Json.Converter;
|
namespace Snap.Hutao.Core.Json.Converter;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 枚举转换器
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TEnum">枚举的类型</typeparam>
|
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal sealed class UnsafeEnumConverter<TEnum> : JsonConverter<TEnum>
|
internal sealed class UnsafeEnumConverter<TEnum> : JsonConverter<TEnum>
|
||||||
where TEnum : struct, Enum
|
where TEnum : struct, Enum
|
||||||
@@ -19,18 +15,12 @@ internal sealed class UnsafeEnumConverter<TEnum> : JsonConverter<TEnum>
|
|||||||
private readonly JsonSerializeType readAs;
|
private readonly JsonSerializeType readAs;
|
||||||
private readonly JsonSerializeType writeAs;
|
private readonly JsonSerializeType writeAs;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造一个新的枚举转换器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="readAs">读取</param>
|
|
||||||
/// <param name="writeAs">写入</param>
|
|
||||||
public UnsafeEnumConverter(JsonSerializeType readAs, JsonSerializeType writeAs)
|
public UnsafeEnumConverter(JsonSerializeType readAs, JsonSerializeType writeAs)
|
||||||
{
|
{
|
||||||
this.readAs = readAs;
|
this.readAs = readAs;
|
||||||
this.writeAs = writeAs;
|
this.writeAs = writeAs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConverTEnum, JsonSerializerOptions options)
|
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConverTEnum, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
if (readAs == JsonSerializeType.Number)
|
if (readAs == JsonSerializeType.Number)
|
||||||
@@ -46,13 +36,12 @@ internal sealed class UnsafeEnumConverter<TEnum> : JsonConverter<TEnum>
|
|||||||
throw new JsonException();
|
throw new JsonException();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
|
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
switch (writeAs)
|
switch (writeAs)
|
||||||
{
|
{
|
||||||
case JsonSerializeType.Number:
|
case JsonSerializeType.Number:
|
||||||
WriteEnumValue(writer, value, enumTypeCode);
|
WriteNumberValue(writer, value, enumTypeCode);
|
||||||
break;
|
break;
|
||||||
case JsonSerializeType.NumberString:
|
case JsonSerializeType.NumberString:
|
||||||
writer.WriteStringValue(value.ToString("D"));
|
writer.WriteStringValue(value.ToString("D"));
|
||||||
@@ -123,7 +112,7 @@ internal sealed class UnsafeEnumConverter<TEnum> : JsonConverter<TEnum>
|
|||||||
throw new JsonException();
|
throw new JsonException();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void WriteEnumValue(Utf8JsonWriter writer, TEnum value, TypeCode typeCode)
|
private static void WriteNumberValue(Utf8JsonWriter writer, TEnum value, TypeCode typeCode)
|
||||||
{
|
{
|
||||||
switch (typeCode)
|
switch (typeCode)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,14 +6,8 @@ using System.Text.Json.Serialization.Metadata;
|
|||||||
|
|
||||||
namespace Snap.Hutao.Core.Json;
|
namespace Snap.Hutao.Core.Json;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Json 选项
|
|
||||||
/// </summary>
|
|
||||||
internal static class JsonOptions
|
internal static class JsonOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 默认的Json序列化选项
|
|
||||||
/// </summary>
|
|
||||||
public static readonly JsonSerializerOptions Default = new()
|
public static readonly JsonSerializerOptions Default = new()
|
||||||
{
|
{
|
||||||
AllowTrailingCommas = true,
|
AllowTrailingCommas = true,
|
||||||
|
|||||||
@@ -6,18 +6,10 @@ using System.Text.Json.Serialization.Metadata;
|
|||||||
|
|
||||||
namespace Snap.Hutao.Core.Json;
|
namespace Snap.Hutao.Core.Json;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Json 类型信息解析器
|
|
||||||
/// </summary>
|
|
||||||
[HighQuality]
|
|
||||||
internal static class JsonTypeInfoResolvers
|
internal static class JsonTypeInfoResolvers
|
||||||
{
|
{
|
||||||
private static readonly Type JsonEnumAttributeType = typeof(JsonEnumAttribute);
|
private static readonly Type JsonEnumAttributeType = typeof(JsonEnumAttribute);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 解析枚举类型
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="typeInfo">Json 类型信息</param>
|
|
||||||
public static void ResolveEnumType(JsonTypeInfo typeInfo)
|
public static void ResolveEnumType(JsonTypeInfo typeInfo)
|
||||||
{
|
{
|
||||||
if (typeInfo.Kind != JsonTypeInfoKind.Object)
|
if (typeInfo.Kind != JsonTypeInfoKind.Object)
|
||||||
|
|||||||
@@ -7,23 +7,22 @@ using Microsoft.Windows.AppNotifications;
|
|||||||
using Snap.Hutao.Core.LifeCycle.InterProcess;
|
using Snap.Hutao.Core.LifeCycle.InterProcess;
|
||||||
using Snap.Hutao.Core.Setting;
|
using Snap.Hutao.Core.Setting;
|
||||||
using Snap.Hutao.Core.Shell;
|
using Snap.Hutao.Core.Shell;
|
||||||
using Snap.Hutao.Core.Windowing;
|
|
||||||
using Snap.Hutao.Core.Windowing.HotKey;
|
|
||||||
using Snap.Hutao.Core.Windowing.NotifyIcon;
|
|
||||||
using Snap.Hutao.Service;
|
using Snap.Hutao.Service;
|
||||||
using Snap.Hutao.Service.Discord;
|
using Snap.Hutao.Service.Discord;
|
||||||
using Snap.Hutao.Service.Hutao;
|
using Snap.Hutao.Service.Hutao;
|
||||||
using Snap.Hutao.Service.Job;
|
using Snap.Hutao.Service.Job;
|
||||||
using Snap.Hutao.Service.Metadata;
|
using Snap.Hutao.Service.Metadata;
|
||||||
using Snap.Hutao.Service.Navigation;
|
using Snap.Hutao.Service.Navigation;
|
||||||
|
using Snap.Hutao.UI.Input.HotKey;
|
||||||
|
using Snap.Hutao.UI.Shell;
|
||||||
|
using Snap.Hutao.UI.Xaml;
|
||||||
|
using Snap.Hutao.UI.Xaml.View.Page;
|
||||||
|
using Snap.Hutao.UI.Xaml.View.Window;
|
||||||
using Snap.Hutao.ViewModel.Guide;
|
using Snap.Hutao.ViewModel.Guide;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.LifeCycle;
|
namespace Snap.Hutao.Core.LifeCycle;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 激活
|
|
||||||
/// </summary>
|
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
[ConstructorGenerated]
|
[ConstructorGenerated]
|
||||||
[Injection(InjectAs.Singleton, typeof(IAppActivation))]
|
[Injection(InjectAs.Singleton, typeof(IAppActivation))]
|
||||||
@@ -40,14 +39,14 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
|
|||||||
|
|
||||||
private readonly ICurrentXamlWindowReference currentWindowReference;
|
private readonly ICurrentXamlWindowReference currentWindowReference;
|
||||||
private readonly IServiceProvider serviceProvider;
|
private readonly IServiceProvider serviceProvider;
|
||||||
|
private readonly ILogger<AppActivation> logger;
|
||||||
private readonly ITaskContext taskContext;
|
private readonly ITaskContext taskContext;
|
||||||
|
|
||||||
private readonly SemaphoreSlim activateSemaphore = new(1);
|
private readonly SemaphoreSlim activateSemaphore = new(1);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Activate(HutaoActivationArguments args)
|
public void Activate(HutaoActivationArguments args)
|
||||||
{
|
{
|
||||||
HandleActivationExclusiveAsync(args).SafeForget();
|
HandleActivationExclusiveAsync(args).SafeForget(logger);
|
||||||
|
|
||||||
async ValueTask HandleActivationExclusiveAsync(HutaoActivationArguments args)
|
async ValueTask HandleActivationExclusiveAsync(HutaoActivationArguments args)
|
||||||
{
|
{
|
||||||
@@ -87,13 +86,12 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
|
|||||||
|
|
||||||
public void NotificationInvoked(AppNotificationManager manager, AppNotificationActivatedEventArgs args)
|
public void NotificationInvoked(AppNotificationManager manager, AppNotificationActivatedEventArgs args)
|
||||||
{
|
{
|
||||||
HandleAppNotificationActivationAsync(args.Arguments, false).SafeForget();
|
HandleAppNotificationActivationAsync(args.Arguments, false).SafeForget(logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void PostInitialization()
|
public void PostInitialization()
|
||||||
{
|
{
|
||||||
RunPostInitializationAsync().SafeForget();
|
RunPostInitializationAsync().SafeForget(logger);
|
||||||
|
|
||||||
async ValueTask RunPostInitializationAsync()
|
async ValueTask RunPostInitializationAsync()
|
||||||
{
|
{
|
||||||
@@ -103,7 +101,7 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
|
|||||||
{
|
{
|
||||||
// TODO: Introduced in 1.10.2, remove in later version
|
// TODO: Introduced in 1.10.2, remove in later version
|
||||||
{
|
{
|
||||||
serviceProvider.GetRequiredService<IJumpListInterop>().ClearAsync().SafeForget();
|
serviceProvider.GetRequiredService<IJumpListInterop>().ClearAsync().SafeForget(logger);
|
||||||
serviceProvider.GetRequiredService<IScheduleTaskInterop>().UnregisterAllTasks();
|
serviceProvider.GetRequiredService<IScheduleTaskInterop>().UnregisterAllTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +110,7 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceProvider.GetRequiredService<PrivateNamedPipeServer>().RunAsync().SafeForget();
|
serviceProvider.GetRequiredService<PrivateNamedPipeServer>().RunAsync().SafeForget(logger);
|
||||||
|
|
||||||
// RegisterHotKey should be called from main thread
|
// RegisterHotKey should be called from main thread
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
@@ -120,24 +118,24 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
|
|||||||
|
|
||||||
if (serviceProvider.GetRequiredService<AppOptions>().IsNotifyIconEnabled)
|
if (serviceProvider.GetRequiredService<AppOptions>().IsNotifyIconEnabled)
|
||||||
{
|
{
|
||||||
XamlLifetime.ApplicationLaunchedWithNotifyIcon = true;
|
XamlApplicationLifetime.LaunchedWithNotifyIcon = true;
|
||||||
|
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
serviceProvider.GetRequiredService<App>().DispatcherShutdownMode = DispatcherShutdownMode.OnExplicitShutdown;
|
serviceProvider.GetRequiredService<App>().DispatcherShutdownMode = DispatcherShutdownMode.OnExplicitShutdown;
|
||||||
_ = serviceProvider.GetRequiredService<NotifyIconController>();
|
_ = serviceProvider.GetRequiredService<NotifyIconController>();
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceProvider.GetRequiredService<IDiscordService>().SetNormalActivityAsync().SafeForget();
|
serviceProvider.GetRequiredService<IDiscordService>().SetNormalActivityAsync().SafeForget(logger);
|
||||||
serviceProvider.GetRequiredService<IQuartzService>().StartAsync().SafeForget();
|
serviceProvider.GetRequiredService<IQuartzService>().StartAsync().SafeForget(logger);
|
||||||
|
|
||||||
if (serviceProvider.GetRequiredService<IMetadataService>() is IMetadataServiceInitialization metadataServiceInitialization)
|
if (serviceProvider.GetRequiredService<IMetadataService>() is IMetadataServiceInitialization metadataServiceInitialization)
|
||||||
{
|
{
|
||||||
metadataServiceInitialization.InitializeInternalAsync().SafeForget();
|
metadataServiceInitialization.InitializeInternalAsync().SafeForget(logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceProvider.GetRequiredService<IHutaoUserService>() is IHutaoUserServiceInitialization hutaoUserServiceInitialization)
|
if (serviceProvider.GetRequiredService<IHutaoUserService>() is IHutaoUserServiceInitialization hutaoUserServiceInitialization)
|
||||||
{
|
{
|
||||||
hutaoUserServiceInitialization.InitializeInternalAsync().SafeForget();
|
hutaoUserServiceInitialization.InitializeInternalAsync().SafeForget(logger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,7 +167,7 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
|
|||||||
case MainWindow:
|
case MainWindow:
|
||||||
await serviceProvider
|
await serviceProvider
|
||||||
.GetRequiredService<INavigationService>()
|
.GetRequiredService<INavigationService>()
|
||||||
.NavigateAsync<View.Page.LaunchGamePage>(INavigationAwaiter.Default, true)
|
.NavigateAsync<LaunchGamePage>(INavigationAwaiter.Default, true)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -210,10 +208,13 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
|
|||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
|
|
||||||
INavigationAwaiter navigationAwaiter = new NavigationExtra(ImportUIAFFromClipboard);
|
INavigationAwaiter navigationAwaiter = new NavigationExtra(ImportUIAFFromClipboard);
|
||||||
await serviceProvider
|
#pragma warning disable CA1849
|
||||||
|
// We can't await here to navigate to Achievment Page, the Achievement
|
||||||
|
// ViewModel requires the Metadata Service to be initialized.
|
||||||
|
serviceProvider
|
||||||
.GetRequiredService<INavigationService>()
|
.GetRequiredService<INavigationService>()
|
||||||
.NavigateAsync<View.Page.AchievementPage>(navigationAwaiter, true)
|
.Navigate<AchievementPage>(navigationAwaiter, true);
|
||||||
.ConfigureAwait(false);
|
#pragma warning restore CA1849
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,6 @@ internal static class AppInstanceExtension
|
|||||||
// Hold the reference here to prevent memory corruption.
|
// Hold the reference here to prevent memory corruption.
|
||||||
private static HANDLE redirectEventHandle;
|
private static HANDLE redirectEventHandle;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 同步非阻塞重定向
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="appInstance">app实例</param>
|
|
||||||
/// <param name="args">参数</param>
|
|
||||||
public static unsafe void RedirectActivationTo(this AppInstance appInstance, AppActivationArguments args)
|
public static unsafe void RedirectActivationTo(this AppInstance appInstance, AppActivationArguments args)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Snap.Hutao.Core.Windowing;
|
using Snap.Hutao.UI.Xaml;
|
||||||
using Snap.Hutao.Win32.Foundation;
|
using Snap.Hutao.Win32.Foundation;
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.LifeCycle;
|
namespace Snap.Hutao.Core.LifeCycle;
|
||||||
|
|||||||
@@ -5,6 +5,18 @@ using System.Runtime.InteropServices;
|
|||||||
|
|
||||||
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
|
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
|
||||||
|
|
||||||
|
// Layout:
|
||||||
|
// 0 1 2 3 4 Bytes
|
||||||
|
// ┌─────────┬──────┬─────────┬─────────────┐
|
||||||
|
// │ Version │ Type │ Command │ ContentType │
|
||||||
|
// ├─────────┴──────┴─────────┴─────────────┤ 4 Bytes
|
||||||
|
// │ ContentLength │
|
||||||
|
// ├────────────────────────────────────────┤ 8 Bytes
|
||||||
|
// │ │
|
||||||
|
// │─────────────── Checksum ───────────────│
|
||||||
|
// │ │
|
||||||
|
// └────────────────────────────────────────┘ 16 Bytes
|
||||||
|
// Any content will be placed after the header.
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
internal struct PipePacketHeader
|
internal struct PipePacketHeader
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using Snap.Hutao.Core.ExceptionService;
|
|||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.IO.Hashing;
|
using System.IO.Hashing;
|
||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
|
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
|
||||||
|
|
||||||
@@ -35,7 +34,6 @@ internal static class PipeStreamExtension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[SkipLocalsInit]
|
|
||||||
public static unsafe void ReadPacket(this PipeStream stream, out PipePacketHeader header)
|
public static unsafe void ReadPacket(this PipeStream stream, out PipePacketHeader header)
|
||||||
{
|
{
|
||||||
fixed (PipePacketHeader* pHeader = &header)
|
fixed (PipePacketHeader* pHeader = &header)
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
|
|||||||
serverStream.WritePacketWithJsonContent(PrivateNamedPipe.Version, PipePacketType.Response, PipePacketCommand.ResponseElevationStatus, resp);
|
serverStream.WritePacketWithJsonContent(PrivateNamedPipe.Version, PipePacketType.Response, PipePacketCommand.ResponseElevationStatus, resp);
|
||||||
serverStream.Flush();
|
serverStream.Flush();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case (PipePacketType.Request, PipePacketCommand.RedirectActivation):
|
case (PipePacketType.Request, PipePacketCommand.RedirectActivation):
|
||||||
HutaoActivationArguments? hutaoArgs = serverStream.ReadJsonContent<HutaoActivationArguments>(in header);
|
HutaoActivationArguments? hutaoArgs = serverStream.ReadJsonContent<HutaoActivationArguments>(in header);
|
||||||
if (hutaoArgs is not null)
|
if (hutaoArgs is not null)
|
||||||
@@ -98,6 +99,7 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
|
|||||||
|
|
||||||
messageDispatcher.RedirectActivation(hutaoArgs);
|
messageDispatcher.RedirectActivation(hutaoArgs);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case (PipePacketType.SessionTermination, _):
|
case (PipePacketType.SessionTermination, _):
|
||||||
serverStream.Disconnect();
|
serverStream.Disconnect();
|
||||||
if (header.Command is PipePacketCommand.Exit)
|
if (header.Command is PipePacketCommand.Exit)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using System.Text;
|
|||||||
|
|
||||||
namespace Snap.Hutao.Core.Logging;
|
namespace Snap.Hutao.Core.Logging;
|
||||||
|
|
||||||
[SuppressMessage("", "SH002")]
|
|
||||||
internal static class LoggerExtension
|
internal static class LoggerExtension
|
||||||
{
|
{
|
||||||
public static void LogColorizedDebug(this ILogger logger, Exception? exception, LogMessage message, params LogArgument[] args)
|
public static void LogColorizedDebug(this ILogger logger, Exception? exception, LogMessage message, params LogArgument[] args)
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
|
|
||||||
using Microsoft.Web.WebView2.Core;
|
using Microsoft.Web.WebView2.Core;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
|
using Microsoft.Windows.AppNotifications;
|
||||||
using Snap.Hutao.Core.IO.Hashing;
|
using Snap.Hutao.Core.IO.Hashing;
|
||||||
using Snap.Hutao.Core.Setting;
|
using Snap.Hutao.Core.Setting;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
using Windows.ApplicationModel;
|
using Windows.ApplicationModel;
|
||||||
using Windows.Storage;
|
using Windows.Storage;
|
||||||
using Windows.UI.Notifications;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Core;
|
namespace Snap.Hutao.Core;
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ internal sealed class RuntimeOptions
|
|||||||
private readonly LazySlim<bool> lazyToastAvailable = new(() =>
|
private readonly LazySlim<bool> lazyToastAvailable = new(() =>
|
||||||
{
|
{
|
||||||
ITaskContext taskContext = Ioc.Default.GetRequiredService<ITaskContext>();
|
ITaskContext taskContext = Ioc.Default.GetRequiredService<ITaskContext>();
|
||||||
return taskContext.InvokeOnMainThread(() => ToastNotificationManager.CreateToastNotifier().Setting is NotificationSetting.Enabled);
|
return taskContext.InvokeOnMainThread(() => AppNotificationManager.Default.Setting is AppNotificationSetting.Enabled);
|
||||||
});
|
});
|
||||||
|
|
||||||
public RuntimeOptions()
|
public RuntimeOptions()
|
||||||
|
|||||||
@@ -5,117 +5,96 @@ using Windows.Storage;
|
|||||||
|
|
||||||
namespace Snap.Hutao.Core.Setting;
|
namespace Snap.Hutao.Core.Setting;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 本地设置
|
|
||||||
/// </summary>
|
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal static class LocalSetting
|
internal static class LocalSetting
|
||||||
{
|
{
|
||||||
private static readonly ApplicationDataContainer Container = ApplicationData.Current.LocalSettings;
|
private static readonly ApplicationDataContainer Container = ApplicationData.Current.LocalSettings;
|
||||||
|
|
||||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
|
||||||
public static byte Get(string key, byte defaultValue)
|
public static byte Get(string key, byte defaultValue)
|
||||||
{
|
{
|
||||||
return Get<byte>(key, defaultValue);
|
return Get<byte>(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
|
||||||
public static short Get(string key, short defaultValue)
|
public static short Get(string key, short defaultValue)
|
||||||
{
|
{
|
||||||
return Get<short>(key, defaultValue);
|
return Get<short>(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
|
||||||
public static ushort Get(string key, ushort defaultValue)
|
public static ushort Get(string key, ushort defaultValue)
|
||||||
{
|
{
|
||||||
return Get<ushort>(key, defaultValue);
|
return Get<ushort>(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
|
||||||
public static int Get(string key, int defaultValue)
|
public static int Get(string key, int defaultValue)
|
||||||
{
|
{
|
||||||
return Get<int>(key, defaultValue);
|
return Get<int>(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
|
||||||
public static uint Get(string key, uint defaultValue)
|
public static uint Get(string key, uint defaultValue)
|
||||||
{
|
{
|
||||||
return Get<uint>(key, defaultValue);
|
return Get<uint>(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
|
||||||
public static long Get(string key, long defaultValue)
|
public static long Get(string key, long defaultValue)
|
||||||
{
|
{
|
||||||
return Get<long>(key, defaultValue);
|
return Get<long>(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
|
||||||
public static ulong Get(string key, ulong defaultValue)
|
public static ulong Get(string key, ulong defaultValue)
|
||||||
{
|
{
|
||||||
return Get<ulong>(key, defaultValue);
|
return Get<ulong>(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
|
||||||
public static float Get(string key, float defaultValue)
|
public static float Get(string key, float defaultValue)
|
||||||
{
|
{
|
||||||
return Get<float>(key, defaultValue);
|
return Get<float>(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
|
||||||
public static double Get(string key, double defaultValue)
|
public static double Get(string key, double defaultValue)
|
||||||
{
|
{
|
||||||
return Get<double>(key, defaultValue);
|
return Get<double>(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
|
||||||
public static bool Get(string key, bool defaultValue)
|
public static bool Get(string key, bool defaultValue)
|
||||||
{
|
{
|
||||||
return Get<bool>(key, defaultValue);
|
return Get<bool>(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
|
||||||
public static char Get(string key, char defaultValue)
|
public static char Get(string key, char defaultValue)
|
||||||
{
|
{
|
||||||
return Get<char>(key, defaultValue);
|
return Get<char>(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
|
||||||
public static string Get(string key, string defaultValue)
|
public static string Get(string key, string defaultValue)
|
||||||
{
|
{
|
||||||
return Get<string>(key, defaultValue);
|
return Get<string>(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
|
||||||
public static DateTimeOffset Get(string key, in DateTimeOffset defaultValue)
|
public static DateTimeOffset Get(string key, in DateTimeOffset defaultValue)
|
||||||
{
|
{
|
||||||
return Get<DateTimeOffset>(key, defaultValue);
|
return Get<DateTimeOffset>(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
|
||||||
public static TimeSpan Get(string key, in TimeSpan defaultValue)
|
public static TimeSpan Get(string key, in TimeSpan defaultValue)
|
||||||
{
|
{
|
||||||
return Get<TimeSpan>(key, defaultValue);
|
return Get<TimeSpan>(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
|
||||||
public static Guid Get(string key, in Guid defaultValue)
|
public static Guid Get(string key, in Guid defaultValue)
|
||||||
{
|
{
|
||||||
return Get<Guid>(key, defaultValue);
|
return Get<Guid>(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
|
||||||
public static Windows.Foundation.Point Get(string key, Windows.Foundation.Point defaultValue)
|
public static Windows.Foundation.Point Get(string key, Windows.Foundation.Point defaultValue)
|
||||||
{
|
{
|
||||||
return Get<Windows.Foundation.Point>(key, defaultValue);
|
return Get<Windows.Foundation.Point>(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
|
||||||
public static Windows.Foundation.Size Get(string key, Windows.Foundation.Size defaultValue)
|
public static Windows.Foundation.Size Get(string key, Windows.Foundation.Size defaultValue)
|
||||||
{
|
{
|
||||||
return Get<Windows.Foundation.Size>(key, defaultValue);
|
return Get<Windows.Foundation.Size>(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
|
||||||
public static Windows.Foundation.Rect Get(string key, Windows.Foundation.Rect defaultValue)
|
public static Windows.Foundation.Rect Get(string key, Windows.Foundation.Rect defaultValue)
|
||||||
{
|
{
|
||||||
return Get<Windows.Foundation.Rect>(key, defaultValue);
|
return Get<Windows.Foundation.Rect>(key, defaultValue);
|
||||||
@@ -126,109 +105,91 @@ internal static class LocalSetting
|
|||||||
return Get<ApplicationDataCompositeValue>(key, defaultValue);
|
return Get<ApplicationDataCompositeValue>(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
|
||||||
public static void Set(string key, byte value)
|
public static void Set(string key, byte value)
|
||||||
{
|
{
|
||||||
Set<byte>(key, value);
|
Set<byte>(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
|
||||||
public static void Set(string key, short value)
|
public static void Set(string key, short value)
|
||||||
{
|
{
|
||||||
Set<short>(key, value);
|
Set<short>(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
|
||||||
public static void Set(string key, ushort value)
|
public static void Set(string key, ushort value)
|
||||||
{
|
{
|
||||||
Set<ushort>(key, value);
|
Set<ushort>(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
|
||||||
public static void Set(string key, int value)
|
public static void Set(string key, int value)
|
||||||
{
|
{
|
||||||
Set<int>(key, value);
|
Set<int>(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
|
||||||
public static void Set(string key, uint value)
|
public static void Set(string key, uint value)
|
||||||
{
|
{
|
||||||
Set<uint>(key, value);
|
Set<uint>(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
|
||||||
public static void Set(string key, long value)
|
public static void Set(string key, long value)
|
||||||
{
|
{
|
||||||
Set<long>(key, value);
|
Set<long>(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
|
||||||
public static void Set(string key, ulong value)
|
public static void Set(string key, ulong value)
|
||||||
{
|
{
|
||||||
Set<ulong>(key, value);
|
Set<ulong>(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
|
||||||
public static void Set(string key, float value)
|
public static void Set(string key, float value)
|
||||||
{
|
{
|
||||||
Set<float>(key, value);
|
Set<float>(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
|
||||||
public static void Set(string key, double value)
|
public static void Set(string key, double value)
|
||||||
{
|
{
|
||||||
Set<double>(key, value);
|
Set<double>(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
|
||||||
public static void Set(string key, bool value)
|
public static void Set(string key, bool value)
|
||||||
{
|
{
|
||||||
Set<bool>(key, value);
|
Set<bool>(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
|
||||||
public static void Set(string key, char value)
|
public static void Set(string key, char value)
|
||||||
{
|
{
|
||||||
Set<char>(key, value);
|
Set<char>(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
|
||||||
public static void Set(string key, string value)
|
public static void Set(string key, string value)
|
||||||
{
|
{
|
||||||
Set<string>(key, value);
|
Set<string>(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
|
||||||
public static void Set(string key, in DateTimeOffset value)
|
public static void Set(string key, in DateTimeOffset value)
|
||||||
{
|
{
|
||||||
Set<DateTimeOffset>(key, value);
|
Set<DateTimeOffset>(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
|
||||||
public static void Set(string key, in TimeSpan value)
|
public static void Set(string key, in TimeSpan value)
|
||||||
{
|
{
|
||||||
Set<TimeSpan>(key, value);
|
Set<TimeSpan>(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
|
||||||
public static void Set(string key, in Guid value)
|
public static void Set(string key, in Guid value)
|
||||||
{
|
{
|
||||||
Set<Guid>(key, value);
|
Set<Guid>(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
|
||||||
public static void Set(string key, Windows.Foundation.Point value)
|
public static void Set(string key, Windows.Foundation.Point value)
|
||||||
{
|
{
|
||||||
Set<Windows.Foundation.Point>(key, value);
|
Set<Windows.Foundation.Point>(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
|
||||||
public static void Set(string key, Windows.Foundation.Size value)
|
public static void Set(string key, Windows.Foundation.Size value)
|
||||||
{
|
{
|
||||||
Set<Windows.Foundation.Size>(key, value);
|
Set<Windows.Foundation.Size>(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
|
||||||
public static void Set(string key, Windows.Foundation.Rect value)
|
public static void Set(string key, Windows.Foundation.Rect value)
|
||||||
{
|
{
|
||||||
Set<Windows.Foundation.Rect>(key, value);
|
Set<Windows.Foundation.Rect>(key, value);
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ namespace Snap.Hutao.Core.Setting;
|
|||||||
/// 设置键
|
/// 设置键
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
[SuppressMessage("", "SA1124")]
|
|
||||||
internal static class SettingKeys
|
internal static class SettingKeys
|
||||||
{
|
{
|
||||||
#region MainWindow
|
#region MainWindow
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Core;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 字符串字面量
|
|
||||||
/// </summary>
|
|
||||||
[HighQuality]
|
|
||||||
internal static class StringLiterals
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// CRLF 换行符
|
|
||||||
/// </summary>
|
|
||||||
public const string CRLF = "\r\n";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取小写的布尔字符串
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">值</param>
|
|
||||||
/// <returns>小写布尔字符串</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static string LowerBoolean(bool value)
|
|
||||||
{
|
|
||||||
return value ? "true" : "false";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,26 +3,11 @@
|
|||||||
|
|
||||||
namespace Snap.Hutao.Core.Threading;
|
namespace Snap.Hutao.Core.Threading;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// An asynchronous barrier that blocks the signaler until all other participants have signaled.
|
|
||||||
/// FIFO
|
|
||||||
/// </summary>
|
|
||||||
internal class AsyncBarrier
|
internal class AsyncBarrier
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The number of participants being synchronized.
|
|
||||||
/// </summary>
|
|
||||||
private readonly int participantCount;
|
private readonly int participantCount;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The set of participants who have reached the barrier, with their awaiters that can resume those participants.
|
|
||||||
/// </summary>
|
|
||||||
private readonly Queue<TaskCompletionSource> waiters;
|
private readonly Queue<TaskCompletionSource> waiters;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="AsyncBarrier"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="participants">The number of participants.</param>
|
|
||||||
public AsyncBarrier(int participants)
|
public AsyncBarrier(int participants)
|
||||||
{
|
{
|
||||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(participants, "Participants of AsyncBarrier must be greater than 0");
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(participants, "Participants of AsyncBarrier must be greater than 0");
|
||||||
@@ -33,11 +18,6 @@ internal class AsyncBarrier
|
|||||||
waiters = new Queue<TaskCompletionSource>(participants - 1);
|
waiters = new Queue<TaskCompletionSource>(participants - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Signals that a participant is ready, and returns a Task
|
|
||||||
/// that completes when all other participants have also signaled ready.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A Task, which will complete (or may already be completed) when the last participant calls this method.</returns>
|
|
||||||
[SuppressMessage("", "SH007")]
|
[SuppressMessage("", "SH007")]
|
||||||
public ValueTask SignalAndWaitAsync()
|
public ValueTask SignalAndWaitAsync()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,22 +3,17 @@
|
|||||||
|
|
||||||
namespace Snap.Hutao.Core.Threading;
|
namespace Snap.Hutao.Core.Threading;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 无区分项的并发<see cref="CancellationTokenSource"/>
|
|
||||||
/// </summary>
|
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
[SuppressMessage("", "CA1001")]
|
[SuppressMessage("", "CA1001")]
|
||||||
internal class ConcurrentCancellationTokenSource
|
internal class ConcurrentCancellationTokenSource
|
||||||
{
|
{
|
||||||
private CancellationTokenSource source = new();
|
private CancellationTokenSource source = new();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 注册取消令牌
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>取消令牌</returns>
|
|
||||||
public CancellationToken Register()
|
public CancellationToken Register()
|
||||||
{
|
{
|
||||||
source.Cancel();
|
source.Cancel();
|
||||||
|
source.Dispose();
|
||||||
|
|
||||||
source = new();
|
source = new();
|
||||||
return source.Token;
|
return source.Token;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
namespace Snap.Hutao.Core.Threading;
|
namespace Snap.Hutao.Core.Threading;
|
||||||
|
|
||||||
|
#if NET9_0_OR_GREATER
|
||||||
|
[Obsolete]
|
||||||
|
#endif
|
||||||
internal readonly struct Delay
|
internal readonly struct Delay
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -15,14 +18,4 @@ internal readonly struct Delay
|
|||||||
{
|
{
|
||||||
return Task.Delay((int)(System.Random.Shared.NextDouble() * (max - min)) + min).AsValueTask();
|
return Task.Delay((int)(System.Random.Shared.NextDouble() * (max - min)) + min).AsValueTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ValueTask FromSeconds(int seconds)
|
|
||||||
{
|
|
||||||
return Task.Delay(TimeSpan.FromSeconds(seconds)).AsValueTask();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ValueTask FromMilliSeconds(int seconds)
|
|
||||||
{
|
|
||||||
return Task.Delay(TimeSpan.FromMilliseconds(seconds)).AsValueTask();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -6,17 +6,9 @@ using System.Runtime.ExceptionServices;
|
|||||||
|
|
||||||
namespace Snap.Hutao.Core.Threading;
|
namespace Snap.Hutao.Core.Threading;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 调度器队列拓展
|
|
||||||
/// </summary>
|
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal static class DispatcherQueueExtension
|
internal static class DispatcherQueueExtension
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 在调度器队列同步调用,直到执行结束,会持续阻塞当前线程
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dispatcherQueue">调度器队列</param>
|
|
||||||
/// <param name="action">执行的回调</param>
|
|
||||||
public static void Invoke(this DispatcherQueue dispatcherQueue, Action action)
|
public static void Invoke(this DispatcherQueue dispatcherQueue, Action action)
|
||||||
{
|
{
|
||||||
if (dispatcherQueue.HasThreadAccess)
|
if (dispatcherQueue.HasThreadAccess)
|
||||||
@@ -49,13 +41,6 @@ internal static class DispatcherQueueExtension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 在调度器队列同步调用,直到执行结束,会持续阻塞当前线程
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dispatcherQueue">调度器队列</param>
|
|
||||||
/// <param name="action">执行的回调</param>
|
|
||||||
/// <typeparam name="T">返回类型</typeparam>
|
|
||||||
/// <returns>回调返回值</returns>
|
|
||||||
public static T Invoke<T>(this DispatcherQueue dispatcherQueue, Func<T> action)
|
public static T Invoke<T>(this DispatcherQueue dispatcherQueue, Func<T> action)
|
||||||
{
|
{
|
||||||
T result = default!;
|
T result = default!;
|
||||||
|
|||||||
@@ -6,48 +6,35 @@ using Snap.Hutao.Core.Threading.Abstraction;
|
|||||||
|
|
||||||
namespace Snap.Hutao.Core.Threading;
|
namespace Snap.Hutao.Core.Threading;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 调度器队列切换操作
|
|
||||||
/// 等待此类型对象后上下文会被切换至主线程
|
|
||||||
/// </summary>
|
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal readonly struct DispatcherQueueSwitchOperation : IAwaitable<DispatcherQueueSwitchOperation>, ICriticalAwaiter
|
internal readonly struct DispatcherQueueSwitchOperation : IAwaitable<DispatcherQueueSwitchOperation>, ICriticalAwaiter
|
||||||
{
|
{
|
||||||
private readonly DispatcherQueue dispatherQueue;
|
private readonly DispatcherQueue dispatherQueue;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造一个新的调度器队列等待器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dispatherQueue">调度器队列</param>
|
|
||||||
public DispatcherQueueSwitchOperation(DispatcherQueue dispatherQueue)
|
public DispatcherQueueSwitchOperation(DispatcherQueue dispatherQueue)
|
||||||
{
|
{
|
||||||
this.dispatherQueue = dispatherQueue;
|
this.dispatherQueue = dispatherQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public bool IsCompleted
|
public bool IsCompleted
|
||||||
{
|
{
|
||||||
get => dispatherQueue.HasThreadAccess;
|
get => dispatherQueue.HasThreadAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public DispatcherQueueSwitchOperation GetAwaiter()
|
public DispatcherQueueSwitchOperation GetAwaiter()
|
||||||
{
|
{
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void GetResult()
|
public void GetResult()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void OnCompleted(Action continuation)
|
public void OnCompleted(Action continuation)
|
||||||
{
|
{
|
||||||
dispatherQueue.TryEnqueue(new DispatcherQueueHandler(continuation));
|
dispatherQueue.TryEnqueue(new DispatcherQueueHandler(continuation));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void UnsafeOnCompleted(Action continuation)
|
public void UnsafeOnCompleted(Action continuation)
|
||||||
{
|
{
|
||||||
using (ExecutionContext.SuppressFlow())
|
using (ExecutionContext.SuppressFlow())
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
namespace Snap.Hutao.Core;
|
namespace Snap.Hutao.Core.Threading;
|
||||||
|
|
||||||
internal sealed class LazySlim<T>
|
internal sealed class LazySlim<T>
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Threading;
|
||||||
|
|
||||||
|
internal sealed class ScopedTaskCompletionSource : IDisposable
|
||||||
|
{
|
||||||
|
private readonly TaskCompletionSource tcs = new();
|
||||||
|
|
||||||
|
public Task Task { get => tcs.Task; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
tcs.TrySetResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,6 @@ internal static class SpinWaitPolyfill
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("", "SH002")]
|
|
||||||
public static unsafe bool SpinUntil<T>(ref readonly T state, delegate*<ref readonly T, bool> condition, TimeSpan timeout)
|
public static unsafe bool SpinUntil<T>(ref readonly T state, delegate*<ref readonly T, bool> condition, TimeSpan timeout)
|
||||||
{
|
{
|
||||||
long startTime = Stopwatch.GetTimestamp();
|
long startTime = Stopwatch.GetTimestamp();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user