mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
200 Commits
feat/windo
...
1.10.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a259750624 | ||
|
|
4e7f8e2a97 | ||
|
|
1ea92413f9 | ||
|
|
46c117edff | ||
|
|
9c4a9fc09a | ||
|
|
605fe5a3af | ||
|
|
7581cf8c8f | ||
|
|
6b67811bae | ||
|
|
6863cbb113 | ||
|
|
6b03ccdacc | ||
|
|
ad90c6b792 | ||
|
|
3ea7d59985 | ||
|
|
555043dfaa | ||
|
|
84e05017ba | ||
|
|
2c139a1ff6 | ||
|
|
04114fb170 | ||
|
|
a8065bf6e6 | ||
|
|
bfdb4b0060 | ||
|
|
6489f66d13 | ||
|
|
eb57ac5952 | ||
|
|
d57865fed9 | ||
|
|
110af48385 | ||
|
|
d30ef6daa0 | ||
|
|
e98bee8a9b | ||
|
|
71e0452c6e | ||
|
|
d866c46646 | ||
|
|
05d0faf131 | ||
|
|
83f5f25324 | ||
|
|
0c0290c446 | ||
|
|
c3efd8d806 | ||
|
|
e8d613d81f | ||
|
|
b7d03bee77 | ||
|
|
55799d0731 | ||
|
|
0a24e19625 | ||
|
|
a32b787352 | ||
|
|
f73b3af180 | ||
|
|
bad60f1d65 | ||
|
|
823ffdb5ad | ||
|
|
544469f078 | ||
|
|
04d4fa0c29 | ||
|
|
f5dbabc586 | ||
|
|
bfefbc58fa | ||
|
|
5b98ba3fc4 | ||
|
|
32a22695e3 | ||
|
|
ab20aa1c64 | ||
|
|
84c8d8a2e3 | ||
|
|
364b056e17 | ||
|
|
a4913e84e4 | ||
|
|
7f32437ec0 | ||
|
|
e230d7a3ef | ||
|
|
1cd6aad518 | ||
|
|
d3fbf35f34 | ||
|
|
72a1ec2122 | ||
|
|
497a5fb0f8 | ||
|
|
bfec29504b | ||
|
|
de77639f57 | ||
|
|
951ecd19d5 | ||
|
|
7f6430fe80 | ||
|
|
d133295599 | ||
|
|
b22eead953 | ||
|
|
d75c680a45 | ||
|
|
52949e3431 | ||
|
|
eeee9af09d | ||
|
|
3526e73f35 | ||
|
|
81c5acb742 | ||
|
|
48ce0c2761 | ||
|
|
c5f8d6bfd5 | ||
|
|
4ef1262d01 | ||
|
|
c05c62ac91 | ||
|
|
618299b296 | ||
|
|
0db7aa239e | ||
|
|
6a4cc56d32 | ||
|
|
5784c55d1e | ||
|
|
53167952f4 | ||
|
|
2a6b386c2c | ||
|
|
4fdb72ca30 | ||
|
|
9df46ad60e | ||
|
|
5c49818a2f | ||
|
|
4ba819ce3b | ||
|
|
96ed31c09e | ||
|
|
481753da02 | ||
|
|
ec6e1696da | ||
|
|
417b537de4 | ||
|
|
e837e425c5 | ||
|
|
a3fb0486c2 | ||
|
|
64d9d04608 | ||
|
|
fe05c8dd04 | ||
|
|
a2f9ff95a4 | ||
|
|
bd5c244eeb | ||
|
|
b619dd5b09 | ||
|
|
ee5a94b961 | ||
|
|
e30c97d0aa | ||
|
|
261377fe0a | ||
|
|
a080938ee2 | ||
|
|
deb34c2a7b | ||
|
|
c0a9e0b301 | ||
|
|
f2c9b676c9 | ||
|
|
a02ce183eb | ||
|
|
06cd462f01 | ||
|
|
17d27f9535 | ||
|
|
d97bd4fd79 | ||
|
|
0c7c25f303 | ||
|
|
2b8eed0ccc | ||
|
|
154c31e8f7 | ||
|
|
c2116af19f | ||
|
|
3144ed4ecb | ||
|
|
b6c68c69d6 | ||
|
|
12d2f2235e | ||
|
|
e89c5488d9 | ||
|
|
489bb6bab3 | ||
|
|
423b220bb3 | ||
|
|
08a082ae65 | ||
|
|
b99bcb53f2 | ||
|
|
0160e96837 | ||
|
|
ca29320139 | ||
|
|
e8a6acd2d8 | ||
|
|
3bca3a8148 | ||
|
|
e1bbaf5dc9 | ||
|
|
b1774e8365 | ||
|
|
f5a81e2f57 | ||
|
|
cfb72755a0 | ||
|
|
0a7e9afcaf | ||
|
|
743e8d8069 | ||
|
|
4c64fac354 | ||
|
|
bf50d6b9b3 | ||
|
|
471260de59 | ||
|
|
dc6dc94b45 | ||
|
|
c0f7293921 | ||
|
|
98b5436828 | ||
|
|
ff785387dc | ||
|
|
5875147bd3 | ||
|
|
cd80250fd0 | ||
|
|
f1bcef4869 | ||
|
|
2c58f34c5d | ||
|
|
b90e8d062c | ||
|
|
00c9417997 | ||
|
|
514edd97c8 | ||
|
|
7f06b0a07c | ||
|
|
3211bfbbd6 | ||
|
|
8067665026 | ||
|
|
0c1968ff49 | ||
|
|
0075d79b0c | ||
|
|
1f8a70da0d | ||
|
|
034655dc26 | ||
|
|
fb293cfc18 | ||
|
|
0433ecbce8 | ||
|
|
077243fa38 | ||
|
|
83347dfafb | ||
|
|
c9df6ac77b | ||
|
|
b626bbe443 | ||
|
|
ea8685523d | ||
|
|
b02f2b47c8 | ||
|
|
b4f7bf934e | ||
|
|
eeffa446a2 | ||
|
|
6746610ab6 | ||
|
|
f8c224048e | ||
|
|
3f110fd4d3 | ||
|
|
44ddae602d | ||
|
|
ab91f4e738 | ||
|
|
5bf1cf0530 | ||
|
|
70f4dcb2c9 | ||
|
|
f490805875 | ||
|
|
681bf08047 | ||
|
|
7b11215551 | ||
|
|
8b20f3beca | ||
|
|
18a088d83b | ||
|
|
a6971042dc | ||
|
|
87f1f2c46b | ||
|
|
c576d8f7c4 | ||
|
|
a0c1241b32 | ||
|
|
a3ab24554a | ||
|
|
9ae45a4cc4 | ||
|
|
f700faae14 | ||
|
|
57b51ed5ee | ||
|
|
5dfb7fbb63 | ||
|
|
046823245c | ||
|
|
0497d89559 | ||
|
|
9d364a291c | ||
|
|
c342147809 | ||
|
|
a86caaf229 | ||
|
|
d0b07f1308 | ||
|
|
409a223213 | ||
|
|
75ea2b807f | ||
|
|
719d934222 | ||
|
|
e8eed46d82 | ||
|
|
ff9b553a19 | ||
|
|
95d64c2895 | ||
|
|
558551c8ad | ||
|
|
d05c196b7c | ||
|
|
502fb6dbed | ||
|
|
4fa5270070 | ||
|
|
94fda223fc | ||
|
|
18103b4deb | ||
|
|
16ac52e71d | ||
|
|
73825d391e | ||
|
|
3b2eeb84a7 | ||
|
|
3e8655fd55 | ||
|
|
3513268ad9 | ||
|
|
850ea7ed4b | ||
|
|
fd59b471cb |
2
.github/ISSUE_TEMPLATE/CHS-bug-report.yml
vendored
2
.github/ISSUE_TEMPLATE/CHS-bug-report.yml
vendored
@@ -19,7 +19,7 @@ body:
|
||||
- label: 我已阅读 Snap Hutao 文档中的[常见问题](https://hut.ao/advanced/FAQ.html)和[常见程序异常](https://hut.ao/advanced/exceptions.html),我的问题没有在文档中得到解答
|
||||
required: true
|
||||
|
||||
- label: 我知道文档站的导航栏中有**搜索功能**,且已经搜索过相关关键词
|
||||
- label: 我知道[文档站](https://hut.ao/zh/menu.html)的导航栏中有**搜索功能**,且已经搜索过相关关键词
|
||||
required: true
|
||||
|
||||
- label: 我的问题不是[已完成](https://github.com/DGP-Studio/Snap.Hutao/issues?q=is%3Aopen+is%3Aissue+label%3A%E5%B7%B2%E5%AE%8C%E6%88%90)的问题也不是一个别人已发布的**重复的**问题
|
||||
|
||||
@@ -2,8 +2,6 @@ name: 功能请求
|
||||
description: 通过这个议题来向开发团队分享你的想法
|
||||
title: "[Feat]: 在这里填写一个合适的标题"
|
||||
labels: ["feature request", "needs-triage", "priority:none"]
|
||||
assignees:
|
||||
- Lightczx
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
@@ -2,8 +2,6 @@ name: Feature Request [English Form]
|
||||
description: Tell us about your thought
|
||||
title: "[Feat]: Place your title here"
|
||||
labels: ["feature request", "needs-triage", "priority:none"]
|
||||
assignees:
|
||||
- Lightczx
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -1,5 +1,5 @@
|
||||
<!--- Hi, thanks for considering make a PR contribution to Snap Hutao, we appreciate your work. -->
|
||||
<!--- Before you create this PR, please fill the following form and checklist -->
|
||||
<!--- Before you create this PR, please check our contribution guide (https://hut.ao/en/development/contribute.html) and fill out the following form and checklist -->
|
||||
|
||||
## Description
|
||||
|
||||
|
||||
12
.github/workflows/alpha.yml
vendored
12
.github/workflows/alpha.yml
vendored
@@ -64,12 +64,10 @@ jobs:
|
||||
> 该版本是由 CI 程序自动打包生成的 `Alpha` 测试版本,**仅供开发者测试使用**
|
||||
|
||||
> [!TIP]
|
||||
> 普通用户请[点击这里](https://github.com/DGP-Studio/Snap.Hutao/releases/latest/)下载最新的稳定版本
|
||||
> 普通用户请 [点击这里](https://github.com/DGP-Studio/Snap.Hutao/releases/latest/) 下载最新的稳定版本
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 请注意,从 Snap Hutao Alpha 2023.12.21.3 开始,我们将使用全新的 CI 证书,原有的 Snap.Hutao.CI.cer 将在几天后过期停止使用。
|
||||
>
|
||||
> 请安装 [DGP_Studio_CA.crt](https://github.com/DGP-Automation/Hutao-Auto-Release/releases/download/certificate-ca/DGP_Studio_CA.crt) 到 `受信任的根证书颁发机构` 以安装测试版安装包
|
||||
> 请先安装 **[DGP_Studio_CA.crt](https://github.com/DGP-Automation/Hutao-Auto-Release/releases/download/certificate-ca/DGP_Studio_CA.crt)** 到 **受信任的根证书颁发机构** 以安装测试版安装包
|
||||
"
|
||||
|
||||
echo $summary >> $Env:GITHUB_STEP_SUMMARY
|
||||
@@ -111,12 +109,10 @@ jobs:
|
||||
> 该版本是由 CI 程序自动打包生成的 `Alpha` 测试版本,**仅供开发者测试使用**
|
||||
|
||||
> [!TIP]
|
||||
> 普通用户请[点击这里](https://github.com/DGP-Studio/Snap.Hutao/releases/latest/)下载最新的稳定版本
|
||||
> 普通用户请 [点击这里](https://github.com/DGP-Studio/Snap.Hutao/releases/latest/) 下载最新的稳定版本
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 请注意,从 Snap Hutao Alpha 2023.12.21.3 开始,我们将使用全新的 CI 证书,原有的 Snap.Hutao.CI.cer 将在几天后过期停止使用。
|
||||
>
|
||||
> 请安装 [DGP_Studio_CA.crt](https://github.com/DGP-Automation/Hutao-Auto-Release/releases/download/certificate-ca/DGP_Studio_CA.crt) 到 `受信任的根证书颁发机构` 以安装测试版安装包
|
||||
> 请先安装 **[DGP_Studio_CA.crt](https://github.com/DGP-Automation/Hutao-Auto-Release/releases/download/certificate-ca/DGP_Studio_CA.crt)** 到 **受信任的根证书颁发机构** 以安装测试版安装包
|
||||
"
|
||||
|
||||
echo $summary >> $Env:GITHUB_STEP_SUMMARY
|
||||
|
||||
@@ -72,9 +72,9 @@ Snap Hutao uses [Crowdin](https://translate.hut.ao/) as a client text translatio
|
||||
Snap Hutao is currently using sponsored software from the following service providers.
|
||||
|
||||
| [](https://www.netlify.com/) | [](https://crowdin.com/) | [](https://gitlab.cn/) |
|
||||
|:-:|:-:|:-:|
|
||||
|[](https://about.signpath.io)|[](https://1password.com/)|[](https://www.digitalocean.com)|
|
||||
|[](https://www.jetbrains.com/opensource/)|||
|
||||
| :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: |
|
||||
| [](https://about.signpath.io) | [](https://1password.com/) | [](https://www.digitalocean.com) |
|
||||
| [](https://hi.ducalis.io/) | [](https://www.jetbrains.com/opensource/) | |
|
||||
|
||||
- Netlify provides document and home page hosting service for Snap Hutao
|
||||
|
||||
@@ -88,6 +88,8 @@ Snap Hutao is currently using sponsored software from the following service prov
|
||||
|
||||
- DigitalOcean provides reliable cloud database for Snap Hutao database backup
|
||||
|
||||
- [Ducalis.io](https://hi.ducalis.io/) provides Snap Hutao project with a complete decision-making toolkit for project management
|
||||
|
||||
- Jetbrains provides powerful IDE for Snap Hutao infrastructure services coding
|
||||
|
||||
## 开发 / Development
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
files:
|
||||
- source: /src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.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
|
||||
|
||||
csharp_style_prefer_primary_constructors = false:none
|
||||
dotnet_diagnostic.SA1124.severity = none
|
||||
|
||||
[*.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,
|
||||
};
|
||||
|
||||
private const string SmapleObjectJson = """
|
||||
private const string SampleObjectJson = """
|
||||
{
|
||||
"A" :1
|
||||
}
|
||||
""";
|
||||
|
||||
private const string SmapleEmptyStringObjectJson = """
|
||||
private const string SampleEmptyStringObjectJson = """
|
||||
{
|
||||
"A" : ""
|
||||
}
|
||||
""";
|
||||
|
||||
private const string SmapleNumberKeyDictionaryJson = """
|
||||
private const string SampleNumberKeyDictionaryJson = """
|
||||
{
|
||||
"111" : "12",
|
||||
"222" : "34"
|
||||
@@ -35,7 +35,7 @@ public sealed class JsonSerializeTest
|
||||
[TestMethod]
|
||||
public void DelegatePropertyCanSerialize()
|
||||
{
|
||||
SampleDelegatePropertyClass sample = JsonSerializer.Deserialize<SampleDelegatePropertyClass>(SmapleObjectJson)!;
|
||||
SampleDelegatePropertyClass sample = JsonSerializer.Deserialize<SampleDelegatePropertyClass>(SampleObjectJson)!;
|
||||
Assert.AreEqual(sample.B, 1);
|
||||
}
|
||||
|
||||
@@ -43,14 +43,23 @@ public sealed class JsonSerializeTest
|
||||
[ExpectedException(typeof(JsonException))]
|
||||
public void EmptyStringCannotSerializeAsNumber()
|
||||
{
|
||||
SampleStringReadWriteNumberPropertyClass sample = JsonSerializer.Deserialize<SampleStringReadWriteNumberPropertyClass>(SmapleEmptyStringObjectJson)!;
|
||||
SampleStringReadWriteNumberPropertyClass sample = JsonSerializer.Deserialize<SampleStringReadWriteNumberPropertyClass>(SampleEmptyStringObjectJson)!;
|
||||
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]
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -92,6 +101,11 @@ public sealed class JsonSerializeTest
|
||||
public int A { get; set; }
|
||||
}
|
||||
|
||||
private sealed class SampleEmptyUriClass
|
||||
{
|
||||
public Uri A { get; set; } = default!;
|
||||
}
|
||||
|
||||
private sealed class SampleByteArrayPropertyClass
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
[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
|
||||
{
|
||||
public readonly int Value1;
|
||||
|
||||
@@ -7,30 +7,38 @@
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources/>
|
||||
<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:///Control/Loading.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Image/CachedImage.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Card.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Color.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/ComboBox.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Converter.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/CornerRadius.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/FlyoutStyle.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/FontStyle.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Glyph.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/InfoBarOverride.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/ItemsPanelTemplate.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/NumericValue.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/PageOverride.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/PivotOverride.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/ScrollViewer.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/SegmentedOverride.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/SettingsStyle.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Thickness.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/TransitionCollection.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Uri.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/WindowOverride.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///View/Card/Primitive/CardProgressBar.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///CommunityToolkit.Labs.WinUI.TokenView/TokenItem/TokenItem.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Elevation.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/ItemIcon.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Loading.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/StandardView.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/AutoSuggestBox/AutoSuggestTokenBox.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Card/CardBlock.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Card/CardProgressBar.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Card/HorizontalCard.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Card/VerticalCard.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Image/CachedImage.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/TextBlock/RateDeltaTextBlock.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/Card.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/Color.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/ComboBox.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/Converter.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/CornerRadius.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/FlyoutStyle.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/FontStyle.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/Glyph.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/InfoBarOverride.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/ItemsPanelTemplate.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/NumericValue.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///UI/Xaml/Control/Theme/PageOverride.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>
|
||||
|
||||
<Style
|
||||
@@ -44,15 +52,15 @@
|
||||
x:Name="NoneSelectionListViewItemStyle"
|
||||
BasedOn="{StaticResource DefaultListViewItemStyle}"
|
||||
TargetType="ListViewItem">
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Margin" Value="0,4,0,0"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
</Style>
|
||||
<Style
|
||||
x:Name="NoneSelectionGridViewItemStyle"
|
||||
BasedOn="{StaticResource DefaultGridViewItemStyle}"
|
||||
TargetType="GridViewItem">
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Margin" Value="0,0,2,4"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Microsoft.Windows.AppNotifications;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
using Snap.Hutao.Core.LifeCycle.InterProcess;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Core.Windowing;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao;
|
||||
@@ -59,7 +59,7 @@ public sealed partial class App : Application
|
||||
|
||||
public new void Exit()
|
||||
{
|
||||
XamlLifetime.ApplicationExiting = true;
|
||||
XamlApplicationLifetime.Exiting = true;
|
||||
base.Exit();
|
||||
}
|
||||
|
||||
@@ -68,6 +68,10 @@ public sealed partial class App : Application
|
||||
{
|
||||
try
|
||||
{
|
||||
// Important: You must call AppNotificationManager::Default().Register
|
||||
// before calling AppInstance.GetCurrent.GetActivatedEventArgs.
|
||||
AppNotificationManager.Default.NotificationInvoked += activation.NotificationInvoked;
|
||||
AppNotificationManager.Default.Register();
|
||||
AppActivationArguments activatedEventArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
|
||||
|
||||
if (serviceProvider.GetRequiredService<PrivateNamedPipeClient>().TryRedirectActivationTo(activatedEventArgs))
|
||||
@@ -81,14 +85,7 @@ public sealed partial class App : Application
|
||||
LogDiagnosticInformation();
|
||||
|
||||
// Manually invoke
|
||||
HutaoActivationArguments hutaoArgs = HutaoActivationArguments.FromAppActivationArguments(activatedEventArgs);
|
||||
if (hutaoArgs.Kind is HutaoActivationKind.Toast)
|
||||
{
|
||||
Exit();
|
||||
return;
|
||||
}
|
||||
|
||||
activation.Activate(hutaoArgs);
|
||||
activation.Activate(HutaoActivationArguments.FromAppActivationArguments(activatedEventArgs));
|
||||
activation.PostInitialization();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 应用程序资源提供器
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Transient, typeof(IAppResourceProvider))]
|
||||
internal sealed class AppResourceProvider : IAppResourceProvider
|
||||
{
|
||||
private readonly App app;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的应用程序资源提供器
|
||||
/// </summary>
|
||||
/// <param name="app">应用</param>
|
||||
public AppResourceProvider(App app)
|
||||
{
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T GetResource<T>(string name)
|
||||
{
|
||||
return (T)app.Resources[name];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
namespace Snap.Hutao.Core.Abstraction.Extension;
|
||||
namespace Snap.Hutao.Core.Abstraction;
|
||||
|
||||
internal static class BuilderExtension
|
||||
{
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.IO;
|
||||
|
||||
namespace Snap.Hutao.Core.Caching;
|
||||
@@ -11,27 +12,11 @@ namespace Snap.Hutao.Core.Caching;
|
||||
[HighQuality]
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// Removed items based on uri list passed
|
||||
/// </summary>
|
||||
/// <param name="uriForCachedItems">Enumerable uri list</param>
|
||||
ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri, ElementTheme theme);
|
||||
|
||||
void Remove(in ReadOnlySpan<Uri> uriForCachedItems);
|
||||
|
||||
/// <summary>
|
||||
/// Removed item based on uri passed
|
||||
/// </summary>
|
||||
/// <param name="uriForCachedItem">uri</param>
|
||||
void Remove(Uri uriForCachedItem);
|
||||
|
||||
/// <summary>
|
||||
/// Removes invalid cached files
|
||||
/// </summary>
|
||||
void RemoveInvalid();
|
||||
}
|
||||
@@ -2,19 +2,27 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Core.IO.Hashing;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.UI;
|
||||
using Snap.Hutao.ViewModel.Guide;
|
||||
using Snap.Hutao.Web;
|
||||
using Snap.Hutao.Web.Request.Builder;
|
||||
using Snap.Hutao.Web.Request.Builder.Abstraction;
|
||||
using Snap.Hutao.Win32.System.WinRT;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Frozen;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics.Imaging;
|
||||
using WinRT;
|
||||
|
||||
namespace Snap.Hutao.Core.Caching;
|
||||
|
||||
@@ -34,7 +42,8 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
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 IHttpClientFactory httpClientFactory;
|
||||
@@ -48,23 +57,18 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
{
|
||||
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)
|
||||
{
|
||||
Remove([uriForCachedItem]);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Remove(in ReadOnlySpan<Uri> uriForCachedItems)
|
||||
{
|
||||
if (uriForCachedItems.Length <= 0)
|
||||
@@ -88,45 +92,87 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
RemoveCore(filesToDelete);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri)
|
||||
public ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri)
|
||||
{
|
||||
return GetFileFromCacheAsync(uri, ElementTheme.Default);
|
||||
}
|
||||
|
||||
public async ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri, ElementTheme theme)
|
||||
{
|
||||
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();
|
||||
try
|
||||
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))
|
||||
{
|
||||
if (concurrentTasks.TryAdd(fileName, taskCompletionSource.Task))
|
||||
try
|
||||
{
|
||||
logger.LogColorizedInformation("Begin to download file from '{Uri}' to '{File}'", (uri, ConsoleColor.Cyan), (filePath, ConsoleColor.Cyan));
|
||||
await DownloadFileAsync(uri, filePath).ConfigureAwait(false);
|
||||
if (!IsFileInvalid(defaultFilePath))
|
||||
{
|
||||
await ConvertAndSaveFileToMonoChromeAsync(defaultFilePath, themeOrDefaultFilePath, theme).ConfigureAwait(false);
|
||||
return themeOrDefaultFilePath;
|
||||
}
|
||||
|
||||
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));
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!IsFileInvalid(defaultFilePath))
|
||||
{
|
||||
await ConvertAndSaveFileToMonoChromeAsync(defaultFilePath, themeOrDefaultFilePath, theme).ConfigureAwait(false);
|
||||
return themeOrDefaultFilePath;
|
||||
}
|
||||
|
||||
return themeOrDefaultFilePath;
|
||||
}
|
||||
else if (concurrentTasks.TryGetValue(fileName, out Task? task))
|
||||
finally
|
||||
{
|
||||
logger.LogDebug("Waiting for a queued image download task to complete for '{Uri}'", (uri, ConsoleColor.Cyan));
|
||||
await task.ConfigureAwait(false);
|
||||
themeFileTcs.TrySetResult();
|
||||
themefileTasks.TryRemove(key, out _);
|
||||
}
|
||||
|
||||
concurrentTasks.TryRemove(fileName, out _);
|
||||
}
|
||||
finally
|
||||
else if (themefileTasks.TryGetValue(key, out Task? themeTask))
|
||||
{
|
||||
taskCompletionSource.TrySetResult();
|
||||
await themeTask.ConfigureAwait(false);
|
||||
return themeOrDefaultFilePath;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw HutaoException.NotSupported("The task should not be null.");
|
||||
}
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -145,6 +191,50 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
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)
|
||||
{
|
||||
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>());
|
||||
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.Storage.Streams;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.DataTransfer;
|
||||
namespace Snap.Hutao.Core.DataTransfer;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Transient, typeof(IClipboardProvider))]
|
||||
@@ -13,7 +13,6 @@ internal sealed partial class ClipboardProvider : IClipboardProvider
|
||||
private readonly JsonSerializerOptions options;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<T?> DeserializeFromJsonAsync<T>()
|
||||
where T : class
|
||||
{
|
||||
@@ -31,7 +30,6 @@ internal sealed partial class ClipboardProvider : IClipboardProvider
|
||||
return JsonSerializer.Deserialize<T>(json, options);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool SetText(string text)
|
||||
{
|
||||
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)
|
||||
{
|
||||
try
|
||||
@@ -65,4 +79,22 @@ internal sealed partial class ClipboardProvider : IClipboardProvider
|
||||
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.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Database;
|
||||
namespace Snap.Hutao.Core.Database.Abstraction;
|
||||
|
||||
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)
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public IDisposable SuppressChangeCurrentItem()
|
||||
{
|
||||
return new CurrentItemSuppression(this);
|
||||
}
|
||||
|
||||
protected override void OnCurrentChangedOverride()
|
||||
{
|
||||
if (!savingToDatabase)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TEntity? currentItem = CurrentItem;
|
||||
|
||||
foreach (TEntity item in SourceCollection)
|
||||
{
|
||||
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)
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public IDisposable SuppressChangeCurrentItem()
|
||||
{
|
||||
return new CurrentItemSuppression(this);
|
||||
}
|
||||
|
||||
protected override void OnCurrentChangedOverride()
|
||||
{
|
||||
if (!savingToDatabase)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TEntityAccess? currentItem = CurrentItem;
|
||||
|
||||
foreach (TEntityAccess item in SourceCollection)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
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)
|
||||
where TEntity : class
|
||||
{
|
||||
@@ -34,13 +27,6 @@ internal static class DbSetExtension
|
||||
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)
|
||||
where TEntity : class
|
||||
{
|
||||
@@ -48,13 +34,6 @@ internal static class DbSetExtension
|
||||
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)
|
||||
where TEntity : class
|
||||
{
|
||||
@@ -62,13 +41,6 @@ internal static class DbSetExtension
|
||||
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)]
|
||||
private static int SaveChangesAndClearChangeTracker<TEntity>(this DbSet<TEntity> dbSet)
|
||||
where TEntity : class
|
||||
@@ -79,16 +51,6 @@ internal static class DbSetExtension
|
||||
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)]
|
||||
private static DbContext Context<TEntity>(this DbSet<TEntity> dbSet)
|
||||
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.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Collections;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Core.Database.Abstraction;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -22,8 +22,6 @@ internal sealed class ObservableReorderableDbCollection<TEntity> : ObservableCol
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public IAdvancedCollectionView? View { get; set; }
|
||||
|
||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
base.OnCollectionChanged(e);
|
||||
@@ -51,38 +49,33 @@ internal sealed class ObservableReorderableDbCollection<TEntity> : ObservableCol
|
||||
|
||||
private void OnReorder()
|
||||
{
|
||||
using (View?.DeferRefresh())
|
||||
{
|
||||
AdjustIndex((List<TEntity>)Items);
|
||||
AdjustIndex((List<TEntity>)Items);
|
||||
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
|
||||
foreach (ref readonly TEntity item in CollectionsMarshal.AsSpan((List<TEntity>)Items))
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
|
||||
foreach (ref readonly TEntity item in CollectionsMarshal.AsSpan((List<TEntity>)Items))
|
||||
{
|
||||
dbSet.UpdateAndSave(item);
|
||||
}
|
||||
dbSet.UpdateAndSave(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SA1402")]
|
||||
internal sealed class ObservableReorderableDbCollection<TEntityOnly, TEntity> : ObservableCollection<TEntityOnly>
|
||||
where TEntityOnly : class, IEntityAccess<TEntity>
|
||||
internal sealed class ObservableReorderableDbCollection<TEntityAccess, TEntity> : ObservableCollection<TEntityAccess>
|
||||
where TEntityAccess : class, IEntityAccess<TEntity>
|
||||
where TEntity : class, IReorderable
|
||||
{
|
||||
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)))
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public IAdvancedCollectionView? View { get; set; }
|
||||
|
||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs 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++)
|
||||
{
|
||||
ref readonly TEntityOnly item = ref span[i];
|
||||
ref readonly TEntityAccess item = ref span[i];
|
||||
item.Entity.Index = i;
|
||||
}
|
||||
|
||||
@@ -110,19 +103,16 @@ internal sealed class ObservableReorderableDbCollection<TEntityOnly, TEntity> :
|
||||
|
||||
private void OnReorder()
|
||||
{
|
||||
using (View?.DeferRefresh())
|
||||
AdjustIndex((List<TEntityAccess>)Items);
|
||||
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AdjustIndex((List<TEntityOnly>)Items);
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
|
||||
foreach (ref readonly TEntityAccess item in CollectionsMarshal.AsSpan((List<TEntityAccess>)Items))
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
|
||||
foreach (ref readonly TEntityOnly item in CollectionsMarshal.AsSpan((List<TEntityOnly>)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.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Database.Abstraction;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.Core.Database;
|
||||
@@ -11,17 +12,17 @@ namespace Snap.Hutao.Core.Database;
|
||||
[HighQuality]
|
||||
internal static class SelectableExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取选中的值或默认值
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
/// <returns>选中的值或默认值</returns>
|
||||
/// <exception cref="InvalidOperationException">存在多个选中的值</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TSource? SelectedOrDefault<TSource>(this IEnumerable<TSource> source)
|
||||
where TSource : ISelectable
|
||||
{
|
||||
return source.SingleOrDefault(i => i.IsSelected);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TSource? SelectedOrFirstOrDefault<TSource>(this IEnumerable<TSource> source)
|
||||
where TSource : ISelectable
|
||||
{
|
||||
return source.SingleOrDefault(i => i.IsSelected) ?? source.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
public TClient Create(bool isOversea)
|
||||
public virtual TClient Create(bool isOversea)
|
||||
{
|
||||
return isOversea
|
||||
? serviceProvider.GetRequiredService<TClientOS>()
|
||||
|
||||
@@ -30,10 +30,7 @@ internal static class IocConfiguration
|
||||
/// <returns>可继续操作的集合</returns>
|
||||
public static IServiceCollection AddDatabase(this IServiceCollection services)
|
||||
{
|
||||
return services
|
||||
.AddTransient(typeof(Database.ScopedDbCurrent<,>))
|
||||
.AddTransient(typeof(Database.ScopedDbCurrent<,,>))
|
||||
.AddDbContextPool<AppDbContext>(AddDbContextCore);
|
||||
return services.AddDbContextPool<AppDbContext>(AddDbContextCore);
|
||||
|
||||
static void AddDbContextCore(IServiceProvider serviceProvider, DbContextOptionsBuilder builder)
|
||||
{
|
||||
|
||||
@@ -38,11 +38,6 @@ internal static partial class IocHttpClientConfiguration
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
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)
|
||||
{
|
||||
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
@@ -51,10 +46,6 @@ internal static partial class IocHttpClientConfiguration
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd(runtimeOptions.UserAgent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对于需要添加动态密钥1的客户端使用此配置
|
||||
/// </summary>
|
||||
/// <param name="client">配置后的客户端</param>
|
||||
private static void XRpcConfiguration(HttpClient client)
|
||||
{
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
@@ -65,10 +56,6 @@ internal static partial class IocHttpClientConfiguration
|
||||
client.DefaultRequestHeaders.Add("x-rpc-device_id", HoyolabOptions.DeviceId36);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对于需要添加动态密钥2的客户端使用此配置
|
||||
/// </summary>
|
||||
/// <param name="client">配置后的客户端</param>
|
||||
private static void XRpc2Configuration(HttpClient client)
|
||||
{
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
@@ -84,11 +71,6 @@ internal static partial class IocHttpClientConfiguration
|
||||
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)
|
||||
{
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
@@ -100,11 +82,6 @@ internal static partial class IocHttpClientConfiguration
|
||||
client.DefaultRequestHeaders.Add("x-rpc-device_id", HoyolabOptions.DeviceId36);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对于需要添加动态密钥2的客户端使用此配置
|
||||
/// HoYoLAB web
|
||||
/// </summary>
|
||||
/// <param name="client">配置后的客户端</param>
|
||||
[SuppressMessage("", "IDE0051")]
|
||||
private static void XRpc4Configuration(HttpClient client)
|
||||
{
|
||||
|
||||
@@ -10,11 +10,6 @@ namespace Snap.Hutao.Core.DependencyInjection;
|
||||
[HighQuality]
|
||||
internal static partial class ServiceCollectionExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 向容器注册服务
|
||||
/// 此方法将会自动生成
|
||||
/// </summary>
|
||||
/// <param name="services">容器</param>
|
||||
/// <returns>可继续操作的服务集合</returns>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static partial IServiceCollection AddInjections(this IServiceCollection services);
|
||||
}
|
||||
@@ -10,30 +10,22 @@ namespace Snap.Hutao.Core.DependencyInjection;
|
||||
/// </summary>
|
||||
internal static class ServiceProviderExtension
|
||||
{
|
||||
/// <inheritdoc cref="ActivatorUtilities.CreateInstance{T}(IServiceProvider, object[])"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T CreateInstance<T>(this IServiceProvider serviceProvider, params object[] parameters)
|
||||
{
|
||||
return ActivatorUtilities.CreateInstance<T>(serviceProvider, parameters);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsDisposed(this IServiceProvider? serviceProvider)
|
||||
public static bool IsDisposed(this IServiceProvider? serviceProvider, bool treatNullAsDisposed = true)
|
||||
{
|
||||
if (serviceProvider is null)
|
||||
{
|
||||
return treatNullAsDisposed;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_ = serviceProvider.GetRequiredService<IServiceScopeFactory>();
|
||||
return false;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
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.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.Core.ExceptionService;
|
||||
|
||||
/// <summary>
|
||||
/// 异常记录器
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed partial class ExceptionRecorder
|
||||
@@ -16,13 +13,15 @@ internal sealed partial class ExceptionRecorder
|
||||
private readonly ILogger<ExceptionRecorder> logger;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
/// <summary>
|
||||
/// 记录应用程序异常
|
||||
/// </summary>
|
||||
/// <param name="app">应用程序</param>
|
||||
public void Record(Application app)
|
||||
{
|
||||
app.UnhandledException += OnAppUnhandledException;
|
||||
ConfigureDebugSettings(app);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void ConfigureDebugSettings(Application app)
|
||||
{
|
||||
app.DebugSettings.FailFastOnErrors = false;
|
||||
|
||||
app.DebugSettings.IsBindingTracingEnabled = true;
|
||||
@@ -35,19 +34,13 @@ internal sealed partial class ExceptionRecorder
|
||||
app.DebugSettings.LayoutCycleDebugBreakLevel = LayoutCycleDebugBreakLevel.High;
|
||||
}
|
||||
|
||||
[SuppressMessage("", "CA2012")]
|
||||
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));
|
||||
|
||||
serviceProvider
|
||||
.GetRequiredService<Web.Hutao.Log.HutaoLogUploadClient>()
|
||||
.UploadLog(e.Exception);
|
||||
}
|
||||
|
||||
private void OnXamlBindingFailed(object? sender, BindingFailedEventArgs e)
|
||||
|
||||
@@ -51,13 +51,6 @@ internal sealed class HutaoException : Exception
|
||||
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]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static InvalidCastException InvalidCast<TFrom, TTo>(string name, Exception? innerException = default)
|
||||
@@ -80,6 +73,15 @@ internal sealed class HutaoException : Exception
|
||||
throw new NotSupportedException(message, innerException);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void NotSupportedIf(bool condition, string? message = default, Exception? innerException = default)
|
||||
{
|
||||
if (condition)
|
||||
{
|
||||
throw new NotSupportedException(message, innerException);
|
||||
}
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static OperationCanceledException OperationCanceled(string message, Exception? innerException = default)
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.UI;
|
||||
using Snap.Hutao.Win32.System.WinRT;
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics.Imaging;
|
||||
using WinRT;
|
||||
|
||||
namespace Snap.Hutao.Control.Media;
|
||||
namespace Snap.Hutao.Core.Graphics.Imaging;
|
||||
|
||||
/// <summary>
|
||||
/// 软件位图拓展
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class SoftwareBitmapExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 混合模式 正常
|
||||
/// </summary>
|
||||
/// <param name="softwareBitmap">软件位图</param>
|
||||
/// <param name="tint">底色</param>
|
||||
public static unsafe void NormalBlend(this SoftwareBitmap softwareBitmap, Bgra32 tint)
|
||||
{
|
||||
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))
|
||||
{
|
||||
@@ -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.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Windowing.NotifyIcon;
|
||||
namespace Snap.Hutao.Core.Graphics;
|
||||
|
||||
internal readonly struct PointUInt16
|
||||
{
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using Windows.Graphics;
|
||||
|
||||
namespace Snap.Hutao.Core.Windowing;
|
||||
namespace Snap.Hutao.Core.Graphics;
|
||||
|
||||
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,50 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
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,15 +13,22 @@ namespace Snap.Hutao.Core.IO;
|
||||
|
||||
internal static class DirectoryOperation
|
||||
{
|
||||
public static bool Move(string sourceDirName, string destDirName)
|
||||
public static bool TryMove(string sourceDirName, string destDirName)
|
||||
{
|
||||
if (!Directory.Exists(sourceDirName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FileSystem.MoveDirectory(sourceDirName, destDirName, true);
|
||||
return true;
|
||||
try
|
||||
{
|
||||
FileSystem.MoveDirectory(sourceDirName, destDirName, 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)
|
||||
|
||||
@@ -32,8 +32,15 @@ internal static class FileOperation
|
||||
|
||||
if (overwrite)
|
||||
{
|
||||
File.Move(sourceFileName, destFileName, true);
|
||||
return true;
|
||||
try
|
||||
{
|
||||
File.Move(sourceFileName, destFileName, true);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (File.Exists(destFileName))
|
||||
@@ -41,7 +48,25 @@ internal static class FileOperation
|
||||
return false;
|
||||
}
|
||||
|
||||
File.Move(sourceFileName, destFileName, false);
|
||||
try
|
||||
{
|
||||
File.Move(sourceFileName, destFileName, false);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Delete(string path)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
File.Delete(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ using System.Text;
|
||||
namespace Snap.Hutao.Core.IO.Hashing;
|
||||
|
||||
#if NET9_0_OR_GREATER
|
||||
[Obsolete]
|
||||
[Obsolete("Use CryptographicOperations.HashData()")]
|
||||
#endif
|
||||
internal static class Hash
|
||||
{
|
||||
|
||||
@@ -8,7 +8,9 @@ namespace Snap.Hutao.Core.IO.Hashing;
|
||||
/// <summary>
|
||||
/// 摘要
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
#if NET9_0_OR_GREATER
|
||||
[Obsolete("Use CryptographicOperations.HashData()")]
|
||||
#endif
|
||||
internal static class MD5
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -5,6 +5,9 @@ using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Hashing;
|
||||
|
||||
#if NET9_0_OR_GREATER
|
||||
[Obsolete("Use CryptographicOperations.HashData()")]
|
||||
#endif
|
||||
internal static class SHA256
|
||||
{
|
||||
public static async ValueTask<string> HashFileAsync(string filePath, CancellationToken token = default)
|
||||
|
||||
@@ -9,6 +9,9 @@ namespace Snap.Hutao.Core.IO.Hashing;
|
||||
/// <summary>
|
||||
/// XXH64 摘要
|
||||
/// </summary>
|
||||
#if NET9_0_OR_GREATER
|
||||
[Obsolete("Use CryptographicOperations.HashData()")]
|
||||
#endif
|
||||
internal static class XXH64
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -8,6 +8,7 @@ using Snap.Hutao.Win32.Security;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Snap.Hutao.Win32.AdvApi32;
|
||||
using static Snap.Hutao.Win32.FirewallApi;
|
||||
using static Snap.Hutao.Win32.Kernel32;
|
||||
using static Snap.Hutao.Win32.Macros;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Http.Loopback;
|
||||
@@ -26,45 +27,7 @@ internal sealed unsafe class LoopbackManager : ObservableObject
|
||||
runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Initialize(out hutaoContainerStringSID);
|
||||
}
|
||||
|
||||
public bool IsLoopbackEnabled { get => isLoopbackEnabled; private set => SetProperty(ref isLoopbackEnabled, value); }
|
||||
@@ -84,4 +47,60 @@ internal sealed unsafe class LoopbackManager : ObservableObject
|
||||
sids.Add(sidAndAttributes);
|
||||
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();
|
||||
|
||||
watcher = new(ProxySettingPath, OnSystemProxySettingsChanged);
|
||||
watcher.Start();
|
||||
watcher.Start(serviceProvider.GetRequiredService<ILogger<HttpProxyUsingSystemProxy>>());
|
||||
}
|
||||
|
||||
public string CurrentProxyUri
|
||||
@@ -75,8 +75,8 @@ internal sealed partial class HttpProxyUsingSystemProxy : ObservableObject, IWeb
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
(innerProxy as IDisposable)?.Dispose();
|
||||
watcher.Dispose();
|
||||
(innerProxy as IDisposable)?.Dispose();
|
||||
}
|
||||
|
||||
public void OnSystemProxySettingsChanged()
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Net.Http;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Http.Sharding;
|
||||
|
||||
// TODO: refactor to use tree structure to calculate shards
|
||||
internal sealed class HttpShardCopyWorker<TStatus> : IDisposable
|
||||
{
|
||||
private const int ShardSize = 4 * 1024 * 1024;
|
||||
|
||||
@@ -3,7 +3,15 @@
|
||||
|
||||
namespace Snap.Hutao.Core.IO;
|
||||
|
||||
/// <summary>
|
||||
/// 流复制状态
|
||||
/// </summary>
|
||||
internal sealed record StreamCopyStatus(long BytesCopied, long TotalBytes);
|
||||
internal sealed class StreamCopyStatus
|
||||
{
|
||||
public 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]
|
||||
internal readonly struct TempFile : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 路径
|
||||
/// </summary>
|
||||
public readonly string Path;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的临时文件
|
||||
/// </summary>
|
||||
/// <param name="delete">是否在创建时删除文件</param>
|
||||
private TempFile(bool delete)
|
||||
{
|
||||
try
|
||||
@@ -38,11 +31,6 @@ internal readonly struct TempFile : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建临时文件并复制内容
|
||||
/// </summary>
|
||||
/// <param name="file">源文件</param>
|
||||
/// <returns>临时文件</returns>
|
||||
public static TempFile? CopyFrom(string file)
|
||||
{
|
||||
TempFile temporaryFile = new(false);
|
||||
@@ -57,9 +45,6 @@ internal readonly struct TempFile : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除临时文件
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
|
||||
@@ -5,76 +5,60 @@ using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Core.IO;
|
||||
|
||||
/// <summary>
|
||||
/// 临时文件流
|
||||
/// </summary>
|
||||
internal sealed class TempFileStream : Stream
|
||||
{
|
||||
private readonly string path;
|
||||
private readonly FileStream stream;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的临时的文件流
|
||||
/// </summary>
|
||||
/// <param name="mode">文件模式</param>
|
||||
/// <param name="access">访问方式</param>
|
||||
public TempFileStream(FileMode mode, FileAccess access)
|
||||
{
|
||||
path = Path.GetTempFileName();
|
||||
stream = File.Open(path, mode, access);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanRead { get => stream.CanRead; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSeek { get => stream.CanSeek; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanWrite { get => stream.CanWrite; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override long Length { get => stream.Length; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override long Position { get => stream.Position; set => stream.Position = value; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Flush()
|
||||
{
|
||||
stream.Flush();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return stream.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
return stream.Seek(offset, origin);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
stream.SetLength(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
stream.Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (disposing)
|
||||
{
|
||||
stream.Dispose();
|
||||
File.Delete(path);
|
||||
}
|
||||
|
||||
stream.Dispose();
|
||||
File.Delete(path);
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,12 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Core.IO;
|
||||
|
||||
/// <summary>
|
||||
/// 文件路径
|
||||
/// </summary>
|
||||
internal readonly struct ValueFile
|
||||
{
|
||||
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)
|
||||
{
|
||||
this.value = value;
|
||||
@@ -31,55 +22,6 @@ internal readonly struct ValueFile
|
||||
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")]
|
||||
public override int GetHashCode()
|
||||
{
|
||||
|
||||
43
src/Snap.Hutao/Snap.Hutao/Core/IO/ValueFileExtension.cs
Normal file
43
src/Snap.Hutao/Snap.Hutao/Core/IO/ValueFileExtension.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
// 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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Globalization;
|
||||
|
||||
namespace Snap.Hutao.Core.Json.Converter;
|
||||
|
||||
internal sealed class DateTimeConverter : JsonConverter<DateTime>
|
||||
{
|
||||
private const string Format = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.GetString() is { } dataTimeString)
|
||||
{
|
||||
return DateTime.ParseExact(dataTimeString, Format, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToString(Format, CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
@@ -5,17 +5,11 @@ using System.Globalization;
|
||||
|
||||
namespace Snap.Hutao.Core.Json.Converter;
|
||||
|
||||
/// <summary>
|
||||
/// 实现日期的转换
|
||||
/// 此转换器无法实现无损往返
|
||||
/// 必须在反序列化后调整 Offset
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
|
||||
// 此转换器无法实现无损往返 必须在反序列化后调整 Offset
|
||||
internal sealed class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
|
||||
{
|
||||
private const string Format = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.GetString() is { } dataTimeString)
|
||||
@@ -29,7 +23,6 @@ internal class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.DateTime.ToString(Format, CultureInfo.InvariantCulture));
|
||||
|
||||
@@ -6,15 +6,11 @@ using System.Globalization;
|
||||
|
||||
namespace Snap.Hutao.Core.Json.Converter;
|
||||
|
||||
/// <summary>
|
||||
/// 逗号分隔列表转换器
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal sealed class SeparatorCommaInt32EnumerableConverter : JsonConverter<IEnumerable<int>>
|
||||
{
|
||||
private const char Comma = ',';
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IEnumerable<int> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.GetString() is { } source)
|
||||
@@ -25,7 +21,6 @@ internal sealed class SeparatorCommaInt32EnumerableConverter : JsonConverter<IEn
|
||||
return [];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(Utf8JsonWriter writer, IEnumerable<int> value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(string.Join(Comma, value));
|
||||
|
||||
@@ -6,10 +6,6 @@ using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.Core.Json.Converter;
|
||||
|
||||
/// <summary>
|
||||
/// 枚举转换器
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举的类型</typeparam>
|
||||
[HighQuality]
|
||||
internal sealed class UnsafeEnumConverter<TEnum> : JsonConverter<TEnum>
|
||||
where TEnum : struct, Enum
|
||||
@@ -19,18 +15,12 @@ internal sealed class UnsafeEnumConverter<TEnum> : JsonConverter<TEnum>
|
||||
private readonly JsonSerializeType readAs;
|
||||
private readonly JsonSerializeType writeAs;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的枚举转换器
|
||||
/// </summary>
|
||||
/// <param name="readAs">读取</param>
|
||||
/// <param name="writeAs">写入</param>
|
||||
public UnsafeEnumConverter(JsonSerializeType readAs, JsonSerializeType writeAs)
|
||||
{
|
||||
this.readAs = readAs;
|
||||
this.writeAs = writeAs;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConverTEnum, JsonSerializerOptions options)
|
||||
{
|
||||
if (readAs == JsonSerializeType.Number)
|
||||
@@ -46,13 +36,12 @@ internal sealed class UnsafeEnumConverter<TEnum> : JsonConverter<TEnum>
|
||||
throw new JsonException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
|
||||
{
|
||||
switch (writeAs)
|
||||
{
|
||||
case JsonSerializeType.Number:
|
||||
WriteEnumValue(writer, value, enumTypeCode);
|
||||
WriteNumberValue(writer, value, enumTypeCode);
|
||||
break;
|
||||
case JsonSerializeType.NumberString:
|
||||
writer.WriteStringValue(value.ToString("D"));
|
||||
@@ -123,7 +112,7 @@ internal sealed class UnsafeEnumConverter<TEnum> : JsonConverter<TEnum>
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -6,14 +6,8 @@ using System.Text.Json.Serialization.Metadata;
|
||||
|
||||
namespace Snap.Hutao.Core.Json;
|
||||
|
||||
/// <summary>
|
||||
/// Json 选项
|
||||
/// </summary>
|
||||
internal static class JsonOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认的Json序列化选项
|
||||
/// </summary>
|
||||
public static readonly JsonSerializerOptions Default = new()
|
||||
{
|
||||
AllowTrailingCommas = true,
|
||||
|
||||
@@ -6,18 +6,10 @@ using System.Text.Json.Serialization.Metadata;
|
||||
|
||||
namespace Snap.Hutao.Core.Json;
|
||||
|
||||
/// <summary>
|
||||
/// Json 类型信息解析器
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class JsonTypeInfoResolvers
|
||||
{
|
||||
private static readonly Type JsonEnumAttributeType = typeof(JsonEnumAttribute);
|
||||
|
||||
/// <summary>
|
||||
/// 解析枚举类型
|
||||
/// </summary>
|
||||
/// <param name="typeInfo">Json 类型信息</param>
|
||||
public static void ResolveEnumType(JsonTypeInfo typeInfo)
|
||||
{
|
||||
if (typeInfo.Kind != JsonTypeInfoKind.Object)
|
||||
|
||||
@@ -1,30 +1,28 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Notifications;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.AppNotifications;
|
||||
using Snap.Hutao.Core.LifeCycle.InterProcess;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
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.DailyNote;
|
||||
using Snap.Hutao.Service.Discord;
|
||||
using Snap.Hutao.Service.Hutao;
|
||||
using Snap.Hutao.Service.Job;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
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 System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.Core.LifeCycle;
|
||||
|
||||
/// <summary>
|
||||
/// 激活
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Singleton, typeof(IAppActivation))]
|
||||
@@ -37,58 +35,109 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
|
||||
public const string ImportUIAFFromClipboard = nameof(ImportUIAFFromClipboard);
|
||||
|
||||
private const string CategoryAchievement = "ACHIEVEMENT";
|
||||
private const string CategoryDailyNote = "DAILYNOTE";
|
||||
private const string UrlActionImport = "/IMPORT";
|
||||
private const string UrlActionRefresh = "/REFRESH";
|
||||
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ICurrentXamlWindowReference currentWindowReference;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly ILogger<AppActivation> logger;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
private readonly SemaphoreSlim activateSemaphore = new(1);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Activate(HutaoActivationArguments args)
|
||||
{
|
||||
HandleActivationAsync(args).SafeForget();
|
||||
HandleActivationExclusiveAsync(args).SafeForget(logger);
|
||||
|
||||
async ValueTask HandleActivationExclusiveAsync(HutaoActivationArguments args)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
if (activateSemaphore.CurrentCount > 0)
|
||||
{
|
||||
using (await activateSemaphore.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
switch (args.Kind)
|
||||
{
|
||||
case HutaoActivationKind.Protocol:
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(args.ProtocolActivatedUri);
|
||||
await HandleProtocolActivationAsync(args.ProtocolActivatedUri, args.IsRedirectTo).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
case HutaoActivationKind.Launch:
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(args.LaunchActivatedArguments);
|
||||
await HandleLaunchActivationAsync(args.IsRedirectTo).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
case HutaoActivationKind.AppNotification:
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(args.AppNotificationActivatedArguments);
|
||||
await HandleAppNotificationActivationAsync(args.AppNotificationActivatedArguments, args.IsRedirectTo).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void NotificationInvoked(AppNotificationManager manager, AppNotificationActivatedEventArgs args)
|
||||
{
|
||||
HandleAppNotificationActivationAsync(args.Arguments, false).SafeForget(logger);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PostInitialization()
|
||||
{
|
||||
RunPostInitializationAsync().SafeForget();
|
||||
RunPostInitializationAsync().SafeForget(logger);
|
||||
|
||||
async ValueTask RunPostInitializationAsync()
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
serviceProvider.GetRequiredService<PrivateNamedPipeServer>().RunAsync().SafeForget();
|
||||
ToastNotificationManagerCompat.OnActivated += NotificationActivate;
|
||||
|
||||
using (await activateSemaphore.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
// TODO: Introduced in 1.10.2, remove in later version
|
||||
serviceProvider.GetRequiredService<IJumpListInterop>().ClearAsync().SafeForget();
|
||||
serviceProvider.GetRequiredService<IScheduleTaskInterop>().UnregisterAllTasks();
|
||||
{
|
||||
serviceProvider.GetRequiredService<IJumpListInterop>().ClearAsync().SafeForget(logger);
|
||||
serviceProvider.GetRequiredService<IScheduleTaskInterop>().UnregisterAllTasks();
|
||||
}
|
||||
|
||||
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language) < GuideState.Completed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
serviceProvider.GetRequiredService<PrivateNamedPipeServer>().RunAsync().SafeForget(logger);
|
||||
|
||||
// RegisterHotKey should be called from main thread
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
serviceProvider.GetRequiredService<HotKeyOptions>().RegisterAll();
|
||||
|
||||
if (serviceProvider.GetRequiredService<AppOptions>().IsNotifyIconEnabled)
|
||||
{
|
||||
XamlLifetime.ApplicationLaunchedWithNotifyIcon = true;
|
||||
XamlApplicationLifetime.LaunchedWithNotifyIcon = true;
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
serviceProvider.GetRequiredService<App>().DispatcherShutdownMode = DispatcherShutdownMode.OnExplicitShutdown;
|
||||
_ = serviceProvider.GetRequiredService<NotifyIconController>();
|
||||
}
|
||||
|
||||
serviceProvider.GetRequiredService<IQuartzService>().StartAsync(default).SafeForget();
|
||||
serviceProvider.GetRequiredService<IDiscordService>().SetNormalActivityAsync().SafeForget(logger);
|
||||
serviceProvider.GetRequiredService<IQuartzService>().StartAsync().SafeForget(logger);
|
||||
|
||||
if (serviceProvider.GetRequiredService<IMetadataService>() is IMetadataServiceInitialization metadataServiceInitialization)
|
||||
{
|
||||
metadataServiceInitialization.InitializeInternalAsync().SafeForget(logger);
|
||||
}
|
||||
|
||||
if (serviceProvider.GetRequiredService<IHutaoUserService>() is IHutaoUserServiceInitialization hutaoUserServiceInitialization)
|
||||
{
|
||||
hutaoUserServiceInitialization.InitializeInternalAsync().SafeForget(logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,7 +168,7 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
|
||||
case MainWindow:
|
||||
await serviceProvider
|
||||
.GetRequiredService<INavigationService>()
|
||||
.NavigateAsync<View.Page.LaunchGamePage>(INavigationAwaiter.Default, true)
|
||||
.NavigateAsync<LaunchGamePage>(INavigationAwaiter.Default, true)
|
||||
.ConfigureAwait(false);
|
||||
return;
|
||||
|
||||
@@ -134,55 +183,55 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
|
||||
}
|
||||
}
|
||||
|
||||
private void NotificationActivate(ToastNotificationActivatedEventArgsCompat args)
|
||||
private async ValueTask HandleProtocolActivationAsync(Uri uri, bool isRedirectTo)
|
||||
{
|
||||
ToastArguments toastArgs = ToastArguments.Parse(args.Argument);
|
||||
UriBuilder builder = new(uri);
|
||||
|
||||
if (toastArgs.TryGetValue(Action, out string? action))
|
||||
{
|
||||
if (action == LaunchGame)
|
||||
{
|
||||
_ = toastArgs.TryGetValue(Uid, out string? uid);
|
||||
HandleLaunchGameActionAsync(uid).SafeForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
string category = builder.Host.ToUpperInvariant();
|
||||
string action = builder.Path.ToUpperInvariant();
|
||||
|
||||
private async ValueTask HandleActivationAsync(HutaoActivationArguments args)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
if (activateSemaphore.CurrentCount > 0)
|
||||
// string parameter = builder.Query.ToUpperInvariant();
|
||||
switch (category)
|
||||
{
|
||||
using (await activateSemaphore.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
await HandleActivationCoreAsync(args).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask HandleActivationCoreAsync(HutaoActivationArguments args)
|
||||
{
|
||||
if (args.Kind is HutaoActivationKind.Protocol)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(args.ProtocolActivatedUri);
|
||||
await HandleUrlActivationAsync(args.ProtocolActivatedUri, args.IsRedirectTo).ConfigureAwait(false);
|
||||
}
|
||||
else if (args.Kind is HutaoActivationKind.Launch)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(args.LaunchActivatedArguments);
|
||||
switch (args.LaunchActivatedArguments)
|
||||
{
|
||||
default:
|
||||
case CategoryAchievement:
|
||||
{
|
||||
await WaitMainWindowOrCurrentAsync().ConfigureAwait(false);
|
||||
if (currentWindowReference.Window is not MainWindow)
|
||||
{
|
||||
await HandleNormalLaunchActionAsync(args.IsRedirectTo).ConfigureAwait(false);
|
||||
break;
|
||||
// TODO: Send notification to hint?
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case UrlActionImport:
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
|
||||
INavigationAwaiter navigationAwaiter = new NavigationExtra(ImportUIAFFromClipboard);
|
||||
#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>()
|
||||
.Navigate<AchievementPage>(navigationAwaiter, true);
|
||||
#pragma warning restore CA1849
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
await HandleLaunchActivationAsync(isRedirectTo).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask HandleNormalLaunchActionAsync(bool isRedirectTo)
|
||||
private async ValueTask HandleLaunchActivationAsync(bool isRedirectTo)
|
||||
{
|
||||
if (!isRedirectTo)
|
||||
{
|
||||
@@ -209,11 +258,33 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
|
||||
guideWindow.BringToForeground();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Version.Parse(LocalSetting.Get(SettingKeys.LastVersion, "0.0.0.0")) < runtimeOptions.Version)
|
||||
{
|
||||
XamlApplicationLifetime.IsFirstRunAfterUpdate = true;
|
||||
LocalSetting.Set(SettingKeys.LastVersion, $"{runtimeOptions.Version}");
|
||||
}
|
||||
}
|
||||
|
||||
await WaitMainWindowOrCurrentAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async ValueTask HandleAppNotificationActivationAsync(IDictionary<string, string> arguments, bool isRedirectTo)
|
||||
{
|
||||
if (arguments.TryGetValue(Action, out string? action))
|
||||
{
|
||||
if (action == LaunchGame)
|
||||
{
|
||||
_ = arguments.TryGetValue(Uid, out string? uid);
|
||||
await HandleLaunchGameActionAsync(uid).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await HandleLaunchActivationAsync(isRedirectTo).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask WaitMainWindowOrCurrentAsync()
|
||||
{
|
||||
if (currentWindowReference.Window is { } window)
|
||||
@@ -230,100 +301,5 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
|
||||
|
||||
mainWindow.SwitchTo();
|
||||
mainWindow.BringToForeground();
|
||||
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
if (serviceProvider.GetRequiredService<IMetadataService>() is IMetadataServiceInitialization metadataServiceInitialization)
|
||||
{
|
||||
metadataServiceInitialization.InitializeInternalAsync().SafeForget();
|
||||
}
|
||||
|
||||
if (serviceProvider.GetRequiredService<IHutaoUserService>() is IHutaoUserServiceInitialization hutaoUserServiceInitialization)
|
||||
{
|
||||
hutaoUserServiceInitialization.InitializeInternalAsync().SafeForget();
|
||||
}
|
||||
|
||||
serviceProvider.GetRequiredService<IDiscordService>().SetNormalActivityAsync().SafeForget();
|
||||
}
|
||||
|
||||
private async ValueTask HandleUrlActivationAsync(Uri uri, bool isRedirectTo)
|
||||
{
|
||||
UriBuilder builder = new(uri);
|
||||
|
||||
string category = builder.Host.ToUpperInvariant();
|
||||
string action = builder.Path.ToUpperInvariant();
|
||||
string parameter = builder.Query.ToUpperInvariant();
|
||||
|
||||
switch (category)
|
||||
{
|
||||
case CategoryAchievement:
|
||||
{
|
||||
await WaitMainWindowOrCurrentAsync().ConfigureAwait(false);
|
||||
await HandleAchievementActionAsync(action, parameter, isRedirectTo).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
case CategoryDailyNote:
|
||||
{
|
||||
await HandleDailyNoteActionAsync(action, parameter, isRedirectTo).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
await HandleNormalLaunchActionAsync(isRedirectTo).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask HandleAchievementActionAsync(string action, string parameter, bool isRedirectTo)
|
||||
{
|
||||
_ = parameter;
|
||||
_ = isRedirectTo;
|
||||
switch (action)
|
||||
{
|
||||
case UrlActionImport:
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
|
||||
INavigationAwaiter navigationAwaiter = new NavigationExtra(ImportUIAFFromClipboard);
|
||||
await serviceProvider
|
||||
.GetRequiredService<INavigationService>()
|
||||
.NavigateAsync<View.Page.AchievementPage>(navigationAwaiter, true)
|
||||
.ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask HandleDailyNoteActionAsync(string action, string parameter, bool isRedirectTo)
|
||||
{
|
||||
_ = parameter;
|
||||
switch (action)
|
||||
{
|
||||
case UrlActionRefresh:
|
||||
{
|
||||
try
|
||||
{
|
||||
await serviceProvider
|
||||
.GetRequiredService<IDailyNoteService>()
|
||||
.RefreshDailyNotesAsync()
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
// Check if it's redirected.
|
||||
if (!isRedirectTo)
|
||||
{
|
||||
// It's a direct open process, should exit immediately.
|
||||
Process.GetCurrentProcess().Kill();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Microsoft.Windows.AppNotifications;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
|
||||
namespace Snap.Hutao.Core.LifeCycle;
|
||||
@@ -12,12 +13,6 @@ namespace Snap.Hutao.Core.LifeCycle;
|
||||
[HighQuality]
|
||||
internal static class AppActivationArgumentsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 尝试获取协议启动的Uri
|
||||
/// </summary>
|
||||
/// <param name="activatedEventArgs">应用程序激活参数</param>
|
||||
/// <param name="uri">协议Uri</param>
|
||||
/// <returns>是否存在协议Uri</returns>
|
||||
public static bool TryGetProtocolActivatedUri(this AppActivationArguments activatedEventArgs, [NotNullWhen(true)] out Uri? uri)
|
||||
{
|
||||
uri = null;
|
||||
@@ -30,15 +25,10 @@ internal static class AppActivationArgumentsExtensions
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取启动的参数
|
||||
/// </summary>
|
||||
/// <param name="activatedEventArgs">应用程序激活参数</param>
|
||||
/// <param name="arguments">参数</param>
|
||||
/// <returns>是否存在参数</returns>
|
||||
public static bool TryGetLaunchActivatedArguments(this AppActivationArguments activatedEventArgs, [NotNullWhen(true)] out string? arguments)
|
||||
{
|
||||
arguments = null;
|
||||
|
||||
if (activatedEventArgs.Data is not ILaunchActivatedEventArgs launchArgs)
|
||||
{
|
||||
return false;
|
||||
@@ -47,4 +37,21 @@ internal static class AppActivationArgumentsExtensions
|
||||
arguments = launchArgs.Arguments.Trim();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryGetAppNotificationActivatedArguments(this AppActivationArguments activatedEventArgs, out string? argument, [NotNullWhen(true)] out IDictionary<string, string>? arguments, [NotNullWhen(true)] out IDictionary<string, string>? userInput)
|
||||
{
|
||||
argument = null;
|
||||
arguments = null;
|
||||
userInput = null;
|
||||
|
||||
if (activatedEventArgs.Data is not AppNotificationActivatedEventArgs appNotificationArgs)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
argument = appNotificationArgs.Argument;
|
||||
arguments = appNotificationArgs.Arguments;
|
||||
userInput = appNotificationArgs.UserInput;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -21,11 +21,6 @@ internal static class AppInstanceExtension
|
||||
// Hold the reference here to prevent memory corruption.
|
||||
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)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Windowing;
|
||||
using Snap.Hutao.UI.Xaml;
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
|
||||
namespace Snap.Hutao.Core.LifeCycle;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
|
||||
namespace Snap.Hutao.Core.LifeCycle;
|
||||
@@ -10,14 +9,16 @@ internal sealed class HutaoActivationArguments
|
||||
{
|
||||
public bool IsRedirectTo { get; set; }
|
||||
|
||||
public bool IsToastActivated { get; set; }
|
||||
|
||||
public HutaoActivationKind Kind { get; set; }
|
||||
|
||||
public Uri? ProtocolActivatedUri { get; set; }
|
||||
|
||||
public string? LaunchActivatedArguments { get; set; }
|
||||
|
||||
public IDictionary<string, string>? AppNotificationActivatedArguments { get; set; }
|
||||
|
||||
public IDictionary<string, string>? AppNotificationActivatedUserInput { get; set; }
|
||||
|
||||
public static HutaoActivationArguments FromAppActivationArguments(AppActivationArguments args, bool isRedirected = false)
|
||||
{
|
||||
HutaoActivationArguments result = new()
|
||||
@@ -33,15 +34,6 @@ internal sealed class HutaoActivationArguments
|
||||
if (args.TryGetLaunchActivatedArguments(out string? arguments))
|
||||
{
|
||||
result.LaunchActivatedArguments = arguments;
|
||||
|
||||
foreach (StringSegment segment in new StringTokenizer(arguments, [' ']))
|
||||
{
|
||||
if (segment.AsSpan().SequenceEqual("-ToastActivated"))
|
||||
{
|
||||
result.Kind = HutaoActivationKind.Toast;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -55,6 +47,19 @@ internal sealed class HutaoActivationArguments
|
||||
result.ProtocolActivatedUri = uri;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ExtendedActivationKind.AppNotification:
|
||||
{
|
||||
result.Kind = HutaoActivationKind.AppNotification;
|
||||
if (args.TryGetAppNotificationActivatedArguments(out string? argument, out IDictionary<string, string>? arguments, out IDictionary<string, string>? userInput))
|
||||
{
|
||||
result.LaunchActivatedArguments = argument;
|
||||
result.AppNotificationActivatedArguments = arguments;
|
||||
result.AppNotificationActivatedUserInput = userInput;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@ internal enum HutaoActivationKind
|
||||
{
|
||||
None,
|
||||
Launch,
|
||||
Toast,
|
||||
AppNotification,
|
||||
Protocol,
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Windows.AppNotifications;
|
||||
|
||||
namespace Snap.Hutao.Core.LifeCycle;
|
||||
|
||||
internal interface IAppActivation
|
||||
{
|
||||
void Activate(HutaoActivationArguments args);
|
||||
|
||||
void PostInitialization();
|
||||
}
|
||||
void NotificationInvoked(AppNotificationManager manager, AppNotificationActivatedEventArgs args);
|
||||
|
||||
internal interface IAppActivationActionHandlersAccess
|
||||
{
|
||||
ValueTask HandleLaunchGameActionAsync(string? uid = null);
|
||||
void PostInitialization();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.LifeCycle;
|
||||
|
||||
internal interface IAppActivationActionHandlersAccess
|
||||
{
|
||||
ValueTask HandleLaunchGameActionAsync(string? uid = null);
|
||||
}
|
||||
@@ -5,6 +5,18 @@ using System.Runtime.InteropServices;
|
||||
|
||||
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)]
|
||||
internal struct PipePacketHeader
|
||||
{
|
||||
|
||||
@@ -5,7 +5,6 @@ using Snap.Hutao.Core.ExceptionService;
|
||||
using System.Buffers;
|
||||
using System.IO.Hashing;
|
||||
using System.IO.Pipes;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
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)
|
||||
{
|
||||
fixed (PipePacketHeader* pHeader = &header)
|
||||
|
||||
@@ -13,6 +13,7 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
|
||||
{
|
||||
private readonly PrivateNamedPipeMessageDispatcher messageDispatcher;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly ILogger<PrivateNamedPipeServer> logger;
|
||||
|
||||
private readonly CancellationTokenSource serverTokenSource = new();
|
||||
private readonly SemaphoreSlim serverSemaphore = new(1);
|
||||
@@ -23,6 +24,7 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
|
||||
{
|
||||
messageDispatcher = serviceProvider.GetRequiredService<PrivateNamedPipeMessageDispatcher>();
|
||||
runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
logger = serviceProvider.GetRequiredService<ILogger<PrivateNamedPipeServer>>();
|
||||
|
||||
PipeSecurity? pipeSecurity = default;
|
||||
|
||||
@@ -64,6 +66,7 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
|
||||
try
|
||||
{
|
||||
await serverStream.WaitForConnectionAsync(serverTokenSource.Token).ConfigureAwait(false);
|
||||
logger.LogInformation("Pipe session created");
|
||||
RunPacketSession(serverStream, serverTokenSource.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -78,6 +81,7 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
|
||||
while (serverStream.IsConnected && !token.IsCancellationRequested)
|
||||
{
|
||||
serverStream.ReadPacket(out PipePacketHeader header);
|
||||
logger.LogInformation("Pipe packet: [Type:{Type}] [Command:{Command}]", header.Type, header.Command);
|
||||
switch ((header.Type, header.Command))
|
||||
{
|
||||
case (PipePacketType.Request, PipePacketCommand.RequestElevationStatus):
|
||||
@@ -85,10 +89,17 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
|
||||
serverStream.WritePacketWithJsonContent(PrivateNamedPipe.Version, PipePacketType.Response, PipePacketCommand.ResponseElevationStatus, resp);
|
||||
serverStream.Flush();
|
||||
break;
|
||||
|
||||
case (PipePacketType.Request, PipePacketCommand.RedirectActivation):
|
||||
HutaoActivationArguments? hutaoArgs = serverStream.ReadJsonContent<HutaoActivationArguments>(in header);
|
||||
if (hutaoArgs is not null)
|
||||
{
|
||||
logger.LogInformation("Redirect activation: [Kind:{Kind}] [Arguments:{Arguments}]", hutaoArgs.Kind, hutaoArgs.LaunchActivatedArguments);
|
||||
}
|
||||
|
||||
messageDispatcher.RedirectActivation(hutaoArgs);
|
||||
break;
|
||||
|
||||
case (PipePacketType.SessionTermination, _):
|
||||
serverStream.Disconnect();
|
||||
if (header.Command is PipePacketCommand.Exit)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user