mirror of
https://github.com/wangdage12/Snap.Hutao.git
synced 2026-03-28 10:27:03 +08:00
优化第三方工具功能(#25)
This commit is contained in:
10
README.md
10
README.md
@@ -42,7 +42,7 @@ Snap Hutao is an open-source Genshin Impact toolkit under MIT license, designed
|
|||||||
|
|
||||||
**目前元数据的编写进度:**
|
**目前元数据的编写进度:**
|
||||||
|
|
||||||
| 项目(V6.3) | 是否完成 |
|
| 项目(V6.4) | 是否完成 |
|
||||||
| ----------- | ----------- |
|
| ----------- | ----------- |
|
||||||
| 总体数据 | ✔️ |
|
| 总体数据 | ✔️ |
|
||||||
|
|
||||||
@@ -67,6 +67,14 @@ https://deepwiki.com/DGP-Studio/Snap.Hutao.Server
|
|||||||
- 服务端:[Snap.Server](https://github.com/wangdage12/Snap.Server)
|
- 服务端:[Snap.Server](https://github.com/wangdage12/Snap.Server)
|
||||||
- Web管理后台和官网:[Snap.Server.Web](https://github.com/wangdage12/Snap.Server.Web)
|
- Web管理后台和官网:[Snap.Server.Web](https://github.com/wangdage12/Snap.Server.Web)
|
||||||
|
|
||||||
|
**第三方工具**
|
||||||
|
|
||||||
|
如果你想要添加你自己开发的工具到第三方工具列表中,请确保:
|
||||||
|
1. 工具应该提供源码或者开源,并且可以成功编译
|
||||||
|
2. 工具不应提供任何可能影响游戏公平性的功能
|
||||||
|
|
||||||
|
工具不限于注入功能,若满足以上条件,请提 issue,或者在 discord 服务器中联系管理员
|
||||||
|
|
||||||
## 打包测试
|
## 打包测试
|
||||||
|
|
||||||
由于采用了 wix 进行打包程序,VS 需要安装 **HeatWave for VS2022**(2026兼容)。需要 msi 安装包时,右键选中 Snap.Hutao.Installer 生成后即可在目标目录找到。默认目录:Snap.Hutao.Installer\bin\x64\Release\en-US\Snap.Hutao.Installer.msi
|
由于采用了 wix 进行打包程序,VS 需要安装 **HeatWave for VS2022**(2026兼容)。需要 msi 安装包时,右键选中 Snap.Hutao.Installer 生成后即可在目标目录找到。默认目录:Snap.Hutao.Installer\bin\x64\Release\en-US\Snap.Hutao.Installer.msi
|
||||||
|
|||||||
@@ -1598,6 +1598,9 @@
|
|||||||
<data name="ViewDialogThirdPartyToolLaunch" xml:space="preserve">
|
<data name="ViewDialogThirdPartyToolLaunch" xml:space="preserve">
|
||||||
<value>启动</value>
|
<value>启动</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ViewDialogThirdPartyToolVersion" xml:space="preserve">
|
||||||
|
<value>版本:</value>
|
||||||
|
</data>
|
||||||
<data name="ViewDialogQRCodeTitle" xml:space="preserve">
|
<data name="ViewDialogQRCodeTitle" xml:space="preserve">
|
||||||
<value>使用米游社扫描二维码</value>
|
<value>使用米游社扫描二维码</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -34,4 +34,18 @@ internal interface IThirdPartyToolService
|
|||||||
/// <param name="tool">工具信息</param>
|
/// <param name="tool">工具信息</param>
|
||||||
/// <returns>是否已下载</returns>
|
/// <returns>是否已下载</returns>
|
||||||
bool IsToolDownloaded(ToolInfo tool);
|
bool IsToolDownloaded(ToolInfo tool);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取本地工具信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tool">工具信息</param>
|
||||||
|
/// <returns>本地工具信息,如果不存在则返回null</returns>
|
||||||
|
LocalToolInfo? GetLocalToolInfo(ToolInfo tool);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查工具是否需要更新
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tool">工具信息</param>
|
||||||
|
/// <returns>是否需要更新</returns>
|
||||||
|
bool NeedsUpdate(ToolInfo tool);
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,9 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace Snap.Hutao.Service.ThirdPartyTool;
|
namespace Snap.Hutao.Service.ThirdPartyTool;
|
||||||
|
|
||||||
@@ -19,6 +21,7 @@ internal sealed partial class ThirdPartyToolService : IThirdPartyToolService
|
|||||||
{
|
{
|
||||||
private const string ApiBaseUrl = "https://htserver.wdg.cloudns.ch/api";
|
private const string ApiBaseUrl = "https://htserver.wdg.cloudns.ch/api";
|
||||||
private const string ToolsEndpoint = "/tools";
|
private const string ToolsEndpoint = "/tools";
|
||||||
|
private const string ToolInfoFileName = "tool_info.json";
|
||||||
|
|
||||||
private readonly IHttpClientFactory httpClientFactory;
|
private readonly IHttpClientFactory httpClientFactory;
|
||||||
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
|
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
|
||||||
@@ -89,40 +92,31 @@ internal sealed partial class ThirdPartyToolService : IThirdPartyToolService
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
string toolDirectory = GetToolDirectory(tool);
|
string toolDirectory = GetToolDirectory(tool);
|
||||||
Directory.CreateDirectory(toolDirectory);
|
|
||||||
|
|
||||||
int totalFiles = tool.Files.Count;
|
// 如果需要更新,先清理旧文件
|
||||||
int downloadedFiles = 0;
|
if (NeedsUpdate(tool) && Directory.Exists(toolDirectory))
|
||||||
|
{
|
||||||
|
Directory.Delete(toolDirectory, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory.CreateDirectory(toolDirectory);
|
||||||
|
|
||||||
using (HttpClient httpClient = httpClientFactory.CreateClient())
|
using (HttpClient httpClient = httpClientFactory.CreateClient())
|
||||||
{
|
{
|
||||||
foreach (string fileName in tool.Files)
|
if (tool.IsCompressed)
|
||||||
{
|
{
|
||||||
string fileUrl = $"{tool.Url}{fileName}";
|
// 压缩包模式:下载并解压
|
||||||
string localFilePath = Path.Combine(toolDirectory, fileName);
|
await DownloadAndExtractCompressedToolAsync(httpClient, tool, toolDirectory, progress, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
// 如果文件已存在,跳过下载
|
else
|
||||||
if (File.Exists(localFilePath))
|
|
||||||
{
|
{
|
||||||
downloadedFiles++;
|
// 非压缩包模式:直接下载所有文件
|
||||||
progress?.Report((double)downloadedFiles / totalFiles * 100);
|
await DownloadFilesAsync(httpClient, tool, toolDirectory, progress, token).ConfigureAwait(false);
|
||||||
continue;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 下载文件
|
// 保存本地工具信息
|
||||||
HttpResponseMessage response = await httpClient.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false);
|
SaveLocalToolInfo(tool);
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
using (Stream contentStream = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false))
|
|
||||||
using (FileStream fileStream = new(localFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
|
|
||||||
{
|
|
||||||
await contentStream.CopyToAsync(fileStream, token).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadedFiles++;
|
|
||||||
progress?.Report((double)downloadedFiles / totalFiles * 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -139,15 +133,31 @@ internal sealed partial class ThirdPartyToolService : IThirdPartyToolService
|
|||||||
{
|
{
|
||||||
string toolDirectory = GetToolDirectory(tool);
|
string toolDirectory = GetToolDirectory(tool);
|
||||||
|
|
||||||
// 查找可执行文件(.exe)
|
// 优先使用 main_exe,如果没有则查找可执行文件
|
||||||
string? executablePath = tool.Files.FirstOrDefault(f => f.EndsWith(".exe", StringComparison.OrdinalIgnoreCase));
|
string? executablePath = tool.MainExe;
|
||||||
|
if (string.IsNullOrEmpty(executablePath))
|
||||||
|
{
|
||||||
|
executablePath = tool.Files.FirstOrDefault(f => f.EndsWith(".exe", StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果还是没有,尝试从目录中查找
|
||||||
|
if (string.IsNullOrEmpty(executablePath))
|
||||||
|
{
|
||||||
|
string[] exeFiles = Directory.GetFiles(toolDirectory, "*.exe", SearchOption.TopDirectoryOnly);
|
||||||
|
executablePath = exeFiles.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(executablePath))
|
if (string.IsNullOrEmpty(executablePath))
|
||||||
{
|
{
|
||||||
messenger.Send(InfoBarMessage.Warning(SH.ServiceThirdPartyToolNoExecutableFound));
|
messenger.Send(InfoBarMessage.Warning(SH.ServiceThirdPartyToolNoExecutableFound));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
string fullPath = Path.Combine(toolDirectory, executablePath);
|
// 如果 executablePath 是完整路径,直接使用;否则拼接目录
|
||||||
|
string fullPath = Path.IsPathRooted(executablePath)
|
||||||
|
? executablePath
|
||||||
|
: Path.Combine(toolDirectory, Path.GetFileName(executablePath));
|
||||||
|
|
||||||
if (!File.Exists(fullPath))
|
if (!File.Exists(fullPath))
|
||||||
{
|
{
|
||||||
messenger.Send(InfoBarMessage.Warning(SH.FormatServiceThirdPartyToolFileNotFound(fullPath)));
|
messenger.Send(InfoBarMessage.Warning(SH.FormatServiceThirdPartyToolFileNotFound(fullPath)));
|
||||||
@@ -192,7 +202,20 @@ internal sealed partial class ThirdPartyToolService : IThirdPartyToolService
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查所有文件是否存在
|
// 检查工具信息文件是否存在
|
||||||
|
LocalToolInfo? localInfo = GetLocalToolInfo(tool);
|
||||||
|
if (localInfo is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对于压缩包,检查目录是否有内容
|
||||||
|
if (tool.IsCompressed)
|
||||||
|
{
|
||||||
|
return Directory.GetFiles(toolDirectory, "*", SearchOption.AllDirectories).Length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对于非压缩包,检查所有文件是否存在
|
||||||
foreach (string fileName in tool.Files)
|
foreach (string fileName in tool.Files)
|
||||||
{
|
{
|
||||||
string filePath = Path.Combine(toolDirectory, fileName);
|
string filePath = Path.Combine(toolDirectory, fileName);
|
||||||
@@ -205,6 +228,217 @@ internal sealed partial class ThirdPartyToolService : IThirdPartyToolService
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LocalToolInfo? GetLocalToolInfo(ToolInfo tool)
|
||||||
|
{
|
||||||
|
string toolDirectory = GetToolDirectory(tool);
|
||||||
|
string infoFilePath = Path.Combine(toolDirectory, ToolInfoFileName);
|
||||||
|
|
||||||
|
if (!File.Exists(infoFilePath))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string json = File.ReadAllText(infoFilePath);
|
||||||
|
return JsonSerializer.Deserialize<LocalToolInfo>(json);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool NeedsUpdate(ToolInfo tool)
|
||||||
|
{
|
||||||
|
LocalToolInfo? localInfo = GetLocalToolInfo(tool);
|
||||||
|
if (localInfo is null)
|
||||||
|
{
|
||||||
|
return true; // 没有本地信息,需要下载
|
||||||
|
}
|
||||||
|
|
||||||
|
// 比较版本号
|
||||||
|
return IsNewerVersion(tool.Version, localInfo.Version);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task DownloadAndExtractCompressedToolAsync(
|
||||||
|
HttpClient httpClient,
|
||||||
|
ToolInfo tool,
|
||||||
|
string toolDirectory,
|
||||||
|
IProgress<double>? progress,
|
||||||
|
CancellationToken token)
|
||||||
|
{
|
||||||
|
// 压缩包模式通常只有一个 zip 文件
|
||||||
|
string zipFileName = tool.Files.FirstOrDefault(f => f.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
|
||||||
|
?? tool.Files[0];
|
||||||
|
|
||||||
|
string zipUrl = $"{tool.Url}{zipFileName}";
|
||||||
|
string zipFilePath = Path.Combine(toolDirectory, zipFileName);
|
||||||
|
|
||||||
|
// 下载 zip 文件
|
||||||
|
progress?.Report(0);
|
||||||
|
|
||||||
|
HttpResponseMessage response = await httpClient.GetAsync(zipUrl, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
using (Stream contentStream = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false))
|
||||||
|
using (FileStream fileStream = new(zipFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||||
|
{
|
||||||
|
await contentStream.CopyToAsync(fileStream, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
progress?.Report(50);
|
||||||
|
|
||||||
|
// 解压 zip 文件
|
||||||
|
using (ZipArchive archive = ZipFile.OpenRead(zipFilePath))
|
||||||
|
{
|
||||||
|
// 检查是否有根目录需要处理
|
||||||
|
bool hasRootFolder = HasSingleRootFolder(archive);
|
||||||
|
|
||||||
|
foreach (ZipArchiveEntry entry in archive.Entries)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(entry.Name))
|
||||||
|
{
|
||||||
|
// 这是一个目录,创建它
|
||||||
|
string? destinationPath = GetDestinationPath(entry.FullName, toolDirectory, hasRootFolder);
|
||||||
|
if (destinationPath is not null)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(destinationPath);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string? destFilePath = GetDestinationPath(entry.FullName, toolDirectory, hasRootFolder);
|
||||||
|
if (destFilePath is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保目录存在
|
||||||
|
string? destDir = Path.GetDirectoryName(destFilePath);
|
||||||
|
if (!string.IsNullOrEmpty(destDir))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(destDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.ExtractToFile(destFilePath, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
progress?.Report(90);
|
||||||
|
|
||||||
|
// 删除 zip 文件
|
||||||
|
File.Delete(zipFilePath);
|
||||||
|
|
||||||
|
progress?.Report(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task DownloadFilesAsync(
|
||||||
|
HttpClient httpClient,
|
||||||
|
ToolInfo tool,
|
||||||
|
string toolDirectory,
|
||||||
|
IProgress<double>? progress,
|
||||||
|
CancellationToken token)
|
||||||
|
{
|
||||||
|
int totalFiles = tool.Files.Count;
|
||||||
|
int downloadedFiles = 0;
|
||||||
|
|
||||||
|
foreach (string fileName in tool.Files)
|
||||||
|
{
|
||||||
|
string fileUrl = $"{tool.Url}{fileName}";
|
||||||
|
string localFilePath = Path.Combine(toolDirectory, fileName);
|
||||||
|
|
||||||
|
// 如果文件已存在,跳过下载
|
||||||
|
if (File.Exists(localFilePath))
|
||||||
|
{
|
||||||
|
downloadedFiles++;
|
||||||
|
progress?.Report((double)downloadedFiles / totalFiles * 100);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载文件
|
||||||
|
HttpResponseMessage response = await httpClient.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
using (Stream contentStream = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false))
|
||||||
|
using (FileStream fileStream = new(localFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||||
|
{
|
||||||
|
await contentStream.CopyToAsync(fileStream, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadedFiles++;
|
||||||
|
progress?.Report((double)downloadedFiles / totalFiles * 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveLocalToolInfo(ToolInfo tool)
|
||||||
|
{
|
||||||
|
string toolDirectory = GetToolDirectory(tool);
|
||||||
|
string infoFilePath = Path.Combine(toolDirectory, ToolInfoFileName);
|
||||||
|
|
||||||
|
LocalToolInfo localInfo = LocalToolInfo.FromToolInfo(tool);
|
||||||
|
string json = JsonSerializer.Serialize(localInfo, new JsonSerializerOptions { WriteIndented = true });
|
||||||
|
File.WriteAllText(infoFilePath, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsNewerVersion(string remoteVersion, string localVersion)
|
||||||
|
{
|
||||||
|
// 使用 Version 类进行比较
|
||||||
|
if (Version.TryParse(remoteVersion, out Version? remote) && Version.TryParse(localVersion, out Version? local))
|
||||||
|
{
|
||||||
|
return remote > local;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果无法解析为版本号,进行字符串比较
|
||||||
|
return !string.Equals(remoteVersion, localVersion, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasSingleRootFolder(ZipArchive archive)
|
||||||
|
{
|
||||||
|
// 检查是否所有条目都在同一个根目录下
|
||||||
|
HashSet<string> rootFolders = [];
|
||||||
|
foreach (ZipArchiveEntry entry in archive.Entries)
|
||||||
|
{
|
||||||
|
int separatorIndex = entry.FullName.IndexOf('/');
|
||||||
|
if (separatorIndex > 0)
|
||||||
|
{
|
||||||
|
rootFolders.Add(entry.FullName[..separatorIndex]);
|
||||||
|
}
|
||||||
|
else if (separatorIndex < 0 && !string.IsNullOrEmpty(entry.Name))
|
||||||
|
{
|
||||||
|
// 直接在根目录下的文件
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootFolders.Count == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? GetDestinationPath(string entryPath, string toolDirectory, bool hasRootFolder)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(entryPath))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasRootFolder)
|
||||||
|
{
|
||||||
|
// 移除根目录前缀
|
||||||
|
int separatorIndex = entryPath.IndexOf('/');
|
||||||
|
if (separatorIndex >= 0 && separatorIndex < entryPath.Length - 1)
|
||||||
|
{
|
||||||
|
return Path.Combine(toolDirectory, entryPath[(separatorIndex + 1)..]);
|
||||||
|
}
|
||||||
|
else if (separatorIndex >= 0)
|
||||||
|
{
|
||||||
|
// 这是根目录本身
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Path.Combine(toolDirectory, entryPath);
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetToolDirectory(ToolInfo tool)
|
private static string GetToolDirectory(ToolInfo tool)
|
||||||
{
|
{
|
||||||
// 使用数据目录/工具名作为存储路径
|
// 使用数据目录/工具名作为存储路径
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
@@ -31,8 +32,17 @@
|
|||||||
Text="{x:Bind Tool.Description, Mode=OneWay}"
|
Text="{x:Bind Tool.Description, Mode=OneWay}"
|
||||||
TextWrapping="Wrap"/>
|
TextWrapping="Wrap"/>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="2" Orientation="Horizontal" Spacing="8">
|
||||||
|
<TextBlock
|
||||||
|
Style="{StaticResource BodyTextBlockStyle}"
|
||||||
|
Text="{shuxm:ResourceString Name=ViewDialogThirdPartyToolVersion}"/>
|
||||||
|
<TextBlock
|
||||||
|
Style="{StaticResource BodyStrongTextBlockStyle}"
|
||||||
|
Text="{x:Bind Tool.Version, Mode=OneWay}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
Grid.Row="2"
|
Grid.Row="3"
|
||||||
Height="4"
|
Height="4"
|
||||||
IsIndeterminate="{x:Bind IsDownloading.Value, Mode=OneWay, FallbackValue=False}"
|
IsIndeterminate="{x:Bind IsDownloading.Value, Mode=OneWay, FallbackValue=False}"
|
||||||
Visibility="{x:Bind IsDownloading.Value, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
Visibility="{x:Bind IsDownloading.Value, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||||
|
|||||||
@@ -39,8 +39,15 @@ internal sealed partial class ThirdPartyToolDialog : ContentDialog
|
|||||||
{
|
{
|
||||||
IsDownloading = true;
|
IsDownloading = true;
|
||||||
|
|
||||||
// 检查工具是否已下载
|
if (tool is null)
|
||||||
if (tool is not null && !thirdPartyToolService.IsToolDownloaded(tool))
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查工具是否需要下载或更新
|
||||||
|
bool needDownload = !thirdPartyToolService.IsToolDownloaded(tool) || thirdPartyToolService.NeedsUpdate(tool);
|
||||||
|
|
||||||
|
if (needDownload)
|
||||||
{
|
{
|
||||||
// 下载工具
|
// 下载工具
|
||||||
bool downloadSuccess = await thirdPartyToolService.DownloadToolAsync(tool, null).ConfigureAwait(false);
|
bool downloadSuccess = await thirdPartyToolService.DownloadToolAsync(tool, null).ConfigureAwait(false);
|
||||||
@@ -53,8 +60,6 @@ internal sealed partial class ThirdPartyToolDialog : ContentDialog
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 启动工具
|
// 启动工具
|
||||||
if (tool is not null)
|
|
||||||
{
|
|
||||||
bool launchSuccess = await thirdPartyToolService.LaunchToolAsync(tool).ConfigureAwait(false);
|
bool launchSuccess = await thirdPartyToolService.LaunchToolAsync(tool).ConfigureAwait(false);
|
||||||
if (launchSuccess)
|
if (launchSuccess)
|
||||||
{
|
{
|
||||||
@@ -63,7 +68,6 @@ internal sealed partial class ThirdPartyToolDialog : ContentDialog
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
messenger.Send(InfoBarMessage.Error(ex));
|
messenger.Send(InfoBarMessage.Error(ex));
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Web.ThirdPartyTool;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 本地工具信息,用于保存工具的本地状态
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class LocalToolInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 工具名称
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 工具版本号
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("version")]
|
||||||
|
public string Version { get; set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否为压缩包
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("is_compressed")]
|
||||||
|
public bool IsCompressed { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 主可执行文件名
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("main_exe")]
|
||||||
|
public string? MainExe { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载时间
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("download_time")]
|
||||||
|
public DateTimeOffset DownloadTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从 ToolInfo 创建 LocalToolInfo
|
||||||
|
/// </summary>
|
||||||
|
public static LocalToolInfo FromToolInfo(ToolInfo toolInfo)
|
||||||
|
{
|
||||||
|
return new LocalToolInfo
|
||||||
|
{
|
||||||
|
Name = toolInfo.Name,
|
||||||
|
Version = toolInfo.Version,
|
||||||
|
IsCompressed = toolInfo.IsCompressed,
|
||||||
|
MainExe = toolInfo.MainExe,
|
||||||
|
DownloadTime = DateTimeOffset.Now,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,4 +16,13 @@ internal sealed class ToolInfo
|
|||||||
|
|
||||||
[JsonPropertyName("files")]
|
[JsonPropertyName("files")]
|
||||||
public List<string> Files { get; set; } = default!;
|
public List<string> Files { get; set; } = default!;
|
||||||
|
|
||||||
|
[JsonPropertyName("is_compressed")]
|
||||||
|
public bool IsCompressed { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("main_exe")]
|
||||||
|
public string? MainExe { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("version")]
|
||||||
|
public string Version { get; set; } = default!;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user