Compare commits

..
Author SHA1 Message Date
Aigio Liu 2883405ace 🎨 ResetSettings Toast 2024-09-25 14:29:59 +08:00
Aigio Liu 75e5b6e897 🔖 3.0.0-rc.11 2024-09-25 10:04:42 +08:00
BuknSS 66f3aa22d6 🐛 Fix missing GetMessage parameters 2024-09-24 18:39:37 +08:00
Aigio Liu 7930120e0b 🐛 ResetProxySettings 2024-09-24 17:44:36 +08:00
RMBGAME aa44b137c4 Merge pull request #3471 from BeyondDimension/feature/ipv6check
Feature/ipv6check
2024-09-24 16:51:10 +08:00
RMBGAME 103918060a ✨ 代理设置增加重置按钮 2024-09-24 16:50:47 +08:00
RMBGAME 0644bbf924 ✨ 完善加速相关QA提示 2024-09-24 16:07:15 +08:00
RMBGAME 3853181d8a 🚧 Misc 2024-09-23 18:12:55 +08:00
RMBGAME de694024bb Merge pull request #3475 from BeyondDimension/fix_accelerator_by_startup
🐛 修复启用 “启动时自动加速” 后,测试页面不显示问题
2024-09-23 17:53:02 +08:00
RMBGAME 6ceaeb0710 🐛 通知消息弹出不再重复弹出同样消息 2024-09-23 17:45:46 +08:00
RMBGAME 71f104b947 🐛 修复默认doh地址导致加速失效的问题 2024-09-23 17:21:26 +08:00
k1mlka a9e913434a 🚧 重新实现EnableProxyDomainGroupVMs数据源订阅 2024-09-23 11:28:23 +08:00
RMBGAME f1ffae11aa 🐛 修复在启用了启动时最小化之后某些情况会导致Toast提示弹出失败的问题 2024-09-20 21:58:36 +08:00
Aigio Liu 7f7fde20bd 🔥 Remove ProxyMode.DNSIntercept with REMOVE_DNS_INTERCEPT 2024-09-20 21:13:49 +08:00
k1mlka 2e97c2edc2 🐛 修复任意输入错误域名,返回延迟和空ip问题 2024-09-12 17:03:44 +08:00
k1mlka 69b8590881 🐛 修复启用 “启动时自动加速” 后,测试页面不显示问题 2024-09-06 11:56:47 +08:00
ff m 7b9ba5e8a0 ➕ BsDiff 2024-09-04 13:30:44 +08:00
k1mlka d6ab3c7f03 💄 网路测试页面重写 2024-09-02 17:27:43 +08:00
k1mlka 12d062d4de ✨ ipv6检测 2024-09-02 17:19:07 +08:00
k1mlka f27d3c9111 Merge pull request #3469 from luojunyuan/develop
🐛 初次启动时"设置页面-启动页设置项"显示修复
2024-09-02 11:59:32 +08:00
k1mlka 6ab74d449e 🐛 初次启动时"设置页面-启动页设置项"显示修复 2024-09-02 11:54:17 +08:00
41 changed files with 974 additions and 901 deletions
+1 -1
View File
@@ -973,7 +973,7 @@
},
{
"TypeName": "string",
"PropertyName": "CustomDohAddres",
"PropertyName": "CustomDohAddres2",
"DefaultValue": "null",
"DefaultValueIsConst": true,
"Summary": "自定义 DNS over HTTPS 地址",
+1 -1
View File
@@ -21,7 +21,7 @@ public static partial class AssemblyInfo
/// <summary>
/// RC 版本号,范围 0~9,只增不减
/// </summary>
const string ver_for_rc = "10";
const string ver_for_rc = "11";
public const string FileVersion = $"{Version2}.{ver_for_rc}1{ver_for_preview}.0";
@@ -44,12 +44,14 @@ sealed class AvaloniaToastServiceImpl : IToastService
if (NotificationManager == null)
{
var host = AvaloniaWindowManagerImpl.GetWindowTopLevel();
NotificationManager = new SnackbarManager(host)
if (host != null)
{
Position = NotificationPosition.BottomRight,
MaxItems = 5,
};
NotificationManager = new SnackbarManager(host)
{
Position = NotificationPosition.BottomRight,
MaxItems = 5,
};
}
}
var notificationType = GetNotificationType(icon);
@@ -61,6 +61,10 @@ public class SnackbarManager : TemplatedControl, IManagedNotificationManager
{
Install(host);
}
else
{
Log.Error("SnackbarManager", "Install Faild, host is null");
}
UpdatePseudoClasses(Position);
}
@@ -7,7 +7,7 @@
<RootNamespace>BD.WTTS</RootNamespace>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<SelfContained>false</SelfContained>
<DefineConstants>APP_REVERSE_PROXY;NONE_UI;$(DefineConstants)</DefineConstants>
<DefineConstants>APP_REVERSE_PROXY;NONE_UI;REMOVE_DNS_INTERCEPT;$(DefineConstants)</DefineConstants>
<ApplicationManifest>Properties\app.manifest</ApplicationManifest>
</PropertyGroup>
@@ -110,7 +110,7 @@
<PackageReference Include="Utf8StringInterpolation" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('windows')) AND ($(RuntimeIdentifier.StartsWith('win-')) OR $(RuntimeIdentifier) == '')">
<ItemGroup Condition="!$(DefineConstants.Contains('REMOVE_DNS_INTERCEPT')) AND $([MSBuild]::IsOSPlatform('windows')) AND ($(RuntimeIdentifier.StartsWith('win-')) OR $(RuntimeIdentifier) == '')">
<PackageReference Include="Aigio.WinDivertSharp" />
</ItemGroup>
@@ -148,6 +148,7 @@ public static partial class ServiceCollectionExtensions
#if WINDOWS
#if !REMOVE_DNS_INTERCEPT
/// <summary>
/// 注册数据包拦截器
/// </summary>
@@ -170,6 +171,7 @@ public static partial class ServiceCollectionExtensions
return services;
}
#endif
#endif
}
@@ -24,7 +24,7 @@ sealed class DnsDohAnalysisService : GeneralHttpClientFactory
{
}
public const string DefaultDohAddres = Dnspod_DohAddres;
public const string DefaultDohAddres = Dnspod_DohAddres2;
static Uri GetDohAddres(string? dohAddres)
{
@@ -1,6 +1,6 @@
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/DnsInterceptHostedService.cs
#if WINDOWS
#if WINDOWS && !REMOVE_DNS_INTERCEPT
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services.Implementation;
@@ -1,6 +1,6 @@
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/Dns/DnsInterceptor.cs
#if WINDOWS
#if WINDOWS && !REMOVE_DNS_INTERCEPT
using WinDivertSharp;
using DNS.Protocol;
@@ -1,6 +1,6 @@
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/Tcp/GitInterceptor.cs
#if WINDOWS
#if WINDOWS && !REMOVE_DNS_INTERCEPT
using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
@@ -1,6 +1,6 @@
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/Tcp/HttpInterceptor.cs
#if WINDOWS
#if WINDOWS && !REMOVE_DNS_INTERCEPT
using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
@@ -1,6 +1,6 @@
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/Tcp/HttpsInterceptor.cs
#if WINDOWS
#if WINDOWS && !REMOVE_DNS_INTERCEPT
using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
@@ -1,6 +1,6 @@
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/Tcp/SshInterceptor.cs
#if WINDOWS
#if WINDOWS && !REMOVE_DNS_INTERCEPT
using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
@@ -1,6 +1,6 @@
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/TcpInterceptHostedService.cs
#if WINDOWS
#if WINDOWS && !REMOVE_DNS_INTERCEPT
using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
@@ -1,6 +1,6 @@
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/Tcp/TcpInterceptor.cs
#if WINDOWS
#if WINDOWS && !REMOVE_DNS_INTERCEPT
using WinDivertSharp;
@@ -23,7 +23,7 @@ partial class YarpReverseProxyServiceImpl
services.AddReverseProxyServer();
services.AddFlowAnalyze();
#if WINDOWS
#if WINDOWS && !REMOVE_DNS_INTERCEPT
if (ProxyMode == ProxyMode.DNSIntercept)
{
services.AddPacketIntercept();
@@ -3,7 +3,7 @@
<PropertyGroup>
<RootNamespace>BD.WTTS</RootNamespace>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>WTTS_PLUGIN;WTTS_PLUGIN_ACCELERATOR;$(DefineConstants)</DefineConstants>
<DefineConstants>WTTS_PLUGIN;WTTS_PLUGIN_ACCELERATOR;REMOVE_DNS_INTERCEPT;$(DefineConstants)</DefineConstants>
<!--<EnableDynamicLoading>true</EnableDynamicLoading>-->
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
@@ -124,7 +124,7 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">
<ItemGroup Condition="!$(DefineConstants.Contains('REMOVE_DNS_INTERCEPT')) AND $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">
<Compile Update="Properties\WinDivertResource.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
@@ -136,7 +136,7 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'windows'">
<ItemGroup Condition="$(DefineConstants.Contains('REMOVE_DNS_INTERCEPT')) OR $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'windows'">
<Compile Remove="Properties\WinDivertResource.Designer.cs" />
<EmbeddedResource Remove="Properties\WinDivertResource.resx" />
</ItemGroup>
@@ -1,4 +1,4 @@
#if WINDOWS
#if WINDOWS && !REMOVE_DNS_INTERCEPT
using WinDivertBinRes = BD.WTTS.Properties.WinDivertResource;
namespace Mobius.Helpers;
@@ -0,0 +1,48 @@
using FluentAvalonia.Core;
using System.Reactive;
namespace BD.WTTS.Models;
public class ProxyDomainGroupViewModel : ReactiveObject
{
private readonly INetworkTestService networkTestService = INetworkTestService.Instance;
public string Name { get; set; } = string.Empty;
public string IconUrl { get; set; } = string.Empty;
public ReadOnlyCollection<ProxyDomainViewModel>? EnableProxyDomainVMs { get; set; }
public ReactiveCommand<Unit, Unit> ConnectTestCommand { get; set; }
public ProxyDomainGroupViewModel()
{
ConnectTestCommand = ReactiveCommand.CreateFromTask(async () =>
{
if (EnableProxyDomainVMs == null)
{
Toast.Show(ToastIcon.Warning, "没有可测试的项目");
return;
}
var tasks = EnableProxyDomainVMs.Select(async enableDomain =>
{
enableDomain.DelayMillseconds = "- ms";
var (success, delayMs) = await networkTestService.TestOpenUrlAsync(enableDomain.Url);
enableDomain.DelayMillseconds = success switch
{
true when delayMs > 20000 => "Timeout",
true => delayMs.ToString() + " ms",
false => "error",
};
});
await Task.WhenAll(tasks);
if (EnableProxyDomainVMs.Count(x => x.DelayMillseconds == "Timeout") == EnableProxyDomainVMs.Count)
{
Toast.Show(ToastIcon.Error, "当前测试项全部未通过,请检查网络链接状况,以及代理设置里的设置项是否正常。");
}
});
}
}
@@ -0,0 +1,55 @@
using Avalonia.Media;
namespace BD.WTTS.Models;
public class ProxyDomainViewModel : ReactiveObject
{
private readonly INetworkTestService networkTestService = INetworkTestService.Instance;
public string Name { get; }
public ProxyType ProxyType { get; }
public string Url { get; }
[Reactive]
public string DelayMillseconds { get; set; } = string.Empty;
[ObservableAsProperty]
public bool RetestButtonVisible { get; }
[ObservableAsProperty]
public IBrush DelayColor { get; } = null!;
public ReadOnlyCollection<ProxyDomainViewModel>? Children { get; }
public ProxyDomainViewModel(string name, ProxyType proxyType, string url, List<ProxyDomainViewModel>? children = null)
{
Name = name;
ProxyType = proxyType;
Url = url;
Children = children?.AsReadOnly();
this.WhenAnyValue(x => x.DelayMillseconds)
.Subscribe(delay => Children?.ForEach(child => child.DelayMillseconds = delay));
this.WhenAnyValue(x => x.DelayMillseconds)
.Select(d => d != string.Empty && d != "-")
.ToPropertyEx(this, x => x.RetestButtonVisible);
const int DelayMiddle = 1000;
this.WhenAnyValue(x => x.DelayMillseconds)
.Select(d => d switch
{
"Timeout" or "error" => Brushes.Red,
var s when s.Split(' ') is [var num, "ms"] && int.TryParse(num, out int ms)
=> ms switch
{
<= DelayMiddle => Brushes.Green,
> DelayMiddle => Brushes.Orange,
},
_ => Brushes.Gray,
})
.ToPropertyEx(this, x => x.DelayColor);
}
}
@@ -64,7 +64,7 @@ partial class ProxyService
string? proxyDNS = ProxySettings.ProxyMasterDns.Value;
bool isSupportIpv6 = await RefreshIpv6Support();
bool useDoh = ProxySettings.UseDoh.Value;
string? customDohAddres = ProxySettings.CustomDohAddres.Value;
string? customDohAddres = ProxySettings.CustomDohAddres2.Value;
Lazy<IPAddress> proxyIp_ = new(() => ReverseProxySettings.GetProxyIp(proxyIp));
void SetProxyIp(IPAddress proxyIPAddress)
@@ -120,6 +120,9 @@ partial class ProxyService
switch (proxyMode)
{
#if REMOVE_DNS_INTERCEPT
case ProxyMode.DNSIntercept:
#endif
case ProxyMode.Hosts:
var inUsePort = SocketHelper.IsUsePort(proxyIp_.Value, httpsPort);
if (inUsePort)
@@ -186,7 +189,7 @@ partial class ProxyService
return Strings.CommunityFix_SetAsSystemPACProxyFail;
}
break;
#if WINDOWS
#if WINDOWS && !REMOVE_DNS_INTERCEPT
case ProxyMode.DNSIntercept:
{
await Mobius.Helpers.WinDivertInitHelper.InitializeAsync();
@@ -315,6 +318,9 @@ partial class ProxyService
#endif
switch (proxyMode) // 先停止接入代理流量
{
#if REMOVE_DNS_INTERCEPT
case ProxyMode.DNSIntercept:
#endif
case ProxyMode.Hosts:
var needClear = hostsFileService.ContainsHostsByTag();
if (needClear)
@@ -351,7 +357,7 @@ partial class ProxyService
await platformService.SetAsSystemPACProxyAsync(false);
}
break;
#if WINDOWS
#if WINDOWS && !REMOVE_DNS_INTERCEPT
case ProxyMode.DNSIntercept:
{
// 停止时也调用初始化
@@ -153,7 +153,7 @@ public partial interface IProxySettings
/// <summary>
/// 自定义 DNS over HTTPS 地址
/// </summary>
string? CustomDohAddres { get; set; }
string? CustomDohAddres2 { get; set; }
/// <summary>
/// 加速页面 Tab 选项卡选中下标
@@ -278,7 +278,7 @@ public partial interface IProxySettings
/// <summary>
/// 自定义 DNS over HTTPS 地址的默认值
/// </summary>
const string? DefaultCustomDohAddres = null;
const string? DefaultCustomDohAddres2 = null;
/// <summary>
/// 加速页面 Tab 选项卡选中下标的默认值
@@ -11,7 +11,9 @@ public static partial class ProxySettings
{
#if WINDOWS
yield return EProxyMode.Hosts;
#if !REMOVE_DNS_INTERCEPT
yield return EProxyMode.DNSIntercept;
#endif
yield return EProxyMode.PAC;
yield return EProxyMode.System;
#elif ANDROID
@@ -195,7 +195,7 @@ public sealed partial class ProxySettings_ : IProxySettings, ISettings, ISetting
/// 自定义 DNS over HTTPS 地址
/// </summary>
[MPKey(22), MP2Key(22), JsonPropertyOrder(22)]
public string? CustomDohAddres { get; set; } = IProxySettings.DefaultCustomDohAddres;
public string? CustomDohAddres2 { get; set; } = IProxySettings.DefaultCustomDohAddres2;
/// <summary>
/// 加速页面 Tab 选项卡选中下标
@@ -368,8 +368,8 @@ public static partial class ProxySettings
/// <summary>
/// 自定义 DNS over HTTPS 地址
/// </summary>
public static SettingsProperty<string, ProxySettings_> CustomDohAddres { get; }
= new(DefaultCustomDohAddres);
public static SettingsProperty<string, ProxySettings_> CustomDohAddres2 { get; }
= new(DefaultCustomDohAddres2);
/// <summary>
/// 加速页面 Tab 选项卡选中下标
@@ -17,6 +17,31 @@ public sealed partial class AcceleratorPageViewModel
public AcceleratorPageViewModel()
{
ProxyService.Current.WhenValueChanged(x => x.ProxyStatus)
.Where(x => x == true)
.Subscribe(_ =>
{
// Create new ProxyEnableDomain for 加速服务 page
var enableGroupDomain = ProxyService.Current.ProxyDomainsList
.Where(list => list.ThreeStateEnable == true || list.ThreeStateEnable == null)
.Select(list => new ProxyDomainGroupViewModel
{
Name = list.Name,
IconUrl = list.IconUrl ?? string.Empty,
EnableProxyDomainVMs = new(
list.Items!
.Where(i => i.ThreeStateEnable == true)
.Select(i => new ProxyDomainViewModel(i.Name, i.ProxyType, "https://" + i.ListenDomainNames.Split(";")[0],
i.Items?
.Select(c => new ProxyDomainViewModel(c.Name, c.ProxyType, "https://" + c.ListenDomainNames.Split(';')[0]))
.ToList()))
.ToList()),
})
.ToList();
EnableProxyDomainGroupVMs = enableGroupDomain.AsReadOnly();
});
StartProxyCommand = ReactiveCommand.CreateFromTask(async _ =>
{
#if LINUX
@@ -24,29 +49,6 @@ public sealed partial class AcceleratorPageViewModel
#endif
//ProxyService.Current.ProxyStatus = !ProxyService.Current.ProxyStatus;
await ProxyService.Current.StartOrStopProxyService(!ProxyService.Current.ProxyStatus);
if (ProxyService.Current.ProxyStatus == false)
return;
// Create new ProxyEnableDomain for 加速服务 page
var enableGroupDomain = ProxyService.Current.ProxyDomainsList
.Where(list => list.ThreeStateEnable == true || list.ThreeStateEnable == null)
.Select(list => new ProxyDomainGroupViewModel
{
Name = list.Name,
IconUrl = list.IconUrl ?? string.Empty,
EnableProxyDomainVMs = new(
list.Items!
.Where(i => i.ThreeStateEnable == true)
.Select(i => new ProxyDomainViewModel(i.Name, i.ProxyType, "https://" + i.ListenDomainNames.Split(";")[0],
i.Items?
.Select(c => new ProxyDomainViewModel(c.Name, c.ProxyType, "https://" + c.ListenDomainNames.Split(';')[0]))
.ToList()))
.ToList()),
})
.ToList();
EnableProxyDomainGroupVMs = enableGroupDomain.AsReadOnly();
});
RefreshCommand = ReactiveCommand.Create(async () =>
@@ -102,7 +104,7 @@ public sealed partial class AcceleratorPageViewModel
IPAddress[] address;
if (ProxySettings.UseDoh)
{
var configDoh = ProxySettings.CustomDohAddres.Value ?? ProxySettingsWindowViewModel.DohAddress.FirstOrDefault() ?? string.Empty;
var configDoh = ProxySettings.CustomDohAddres2.Value ?? ProxySettingsWindowViewModel.DohAddress.FirstOrDefault() ?? string.Empty;
(delayMs, address) = await networkTestService.TestDNSOverHttpsAsync(testDomain, configDoh);
}
else
@@ -110,8 +112,11 @@ public sealed partial class AcceleratorPageViewModel
var configDns = ProxySettings.ProxyMasterDns.Value ?? string.Empty;
(delayMs, address) = await networkTestService.TestDNSAsync(testDomain, configDns, 53);
}
if (address.Length == 0)
throw new Exception("Parsing failed. Return empty ip address.");
DNSTestDelay = delayMs + "ms ";
DNSTestResult = "" + address.FirstOrDefault() ?? "0.0.0.0";
DNSTestResult = string.Empty + address.FirstOrDefault();
}
catch (Exception ex)
{
@@ -124,6 +129,24 @@ public sealed partial class AcceleratorPageViewModel
.IsExecuting
.ToPropertyEx(this, x => x.IsDNSChecking);
IPv6CheckCommand = ReactiveCommand.CreateFromTask(async () =>
{
var result = await IMicroServiceClient.Instance.Accelerate.GetMyIP(ipV6: true);
if (result.IsSuccess)
{
IsSupportIPv6 = true;
IPv6Address = result.Content ?? string.Empty;
}
else
{
IsSupportIPv6 = false;
IPv6Address = string.Empty;
}
});
IPv6CheckCommand
.IsExecuting
.ToPropertyEx(this, x => x.IsIPv6Checking);
ProxySettingsCommand = ReactiveCommand.Create(() =>
{
var vm = new ProxySettingsWindowViewModel();
@@ -1,95 +1,7 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Media;
using System.Reactive;
namespace BD.WTTS.UI.ViewModels;
public class ProxyDomainGroupViewModel : ReactiveObject
{
private readonly INetworkTestService networkTestService = INetworkTestService.Instance;
public string Name { get; set; } = string.Empty;
public string IconUrl { get; set; } = string.Empty;
public ReadOnlyCollection<ProxyDomainViewModel>? EnableProxyDomainVMs { get; set; }
public ReactiveCommand<Unit, Unit> ConnectTestCommand { get; set; }
public ProxyDomainGroupViewModel()
{
ConnectTestCommand = ReactiveCommand.CreateFromTask(async () =>
{
var tasks = EnableProxyDomainVMs!.Select(async enableDomain =>
{
enableDomain.DelayMillseconds = "- ms";
var (success, delayMs) = await networkTestService.TestOpenUrlAsync(enableDomain.Url);
enableDomain.DelayMillseconds = success switch
{
true when delayMs > 20000 => "Timeout",
true => delayMs.ToString() + " ms",
false => "error",
};
});
await Task.WhenAll(tasks);
});
}
}
public class ProxyDomainViewModel : ReactiveObject
{
private readonly INetworkTestService networkTestService = INetworkTestService.Instance;
public string Name { get; }
public ProxyType ProxyType { get; }
public string Url { get; }
[Reactive]
public string DelayMillseconds { get; set; } = string.Empty;
[ObservableAsProperty]
public bool RetestButtonVisible { get; }
[ObservableAsProperty]
public IBrush DelayColor { get; } = null!;
public ReadOnlyCollection<ProxyDomainViewModel>? Children { get; }
public ProxyDomainViewModel(string name, ProxyType proxyType, string url, List<ProxyDomainViewModel>? children = null)
{
Name = name;
ProxyType = proxyType;
Url = url;
Children = children?.AsReadOnly();
this.WhenAnyValue(x => x.DelayMillseconds)
.Subscribe(delay => Children?.ForEach(child => child.DelayMillseconds = delay));
this.WhenAnyValue(x => x.DelayMillseconds)
.Select(d => d != string.Empty && d != "-")
.ToPropertyEx(this, x => x.RetestButtonVisible);
const int DelayMiddle = 1000;
this.WhenAnyValue(x => x.DelayMillseconds)
.Select(d => d switch
{
"Timeout" or "error" => Brushes.Red,
var s when s.Split(' ') is [var num, "ms"] && int.TryParse(num, out int ms)
=> ms switch
{
<= DelayMiddle => Brushes.Green,
> DelayMiddle => Brushes.Orange,
},
_ => Brushes.Gray,
})
.ToPropertyEx(this, x => x.DelayColor);
}
}
public sealed partial class AcceleratorPageViewModel : TabItemViewModel
{
public enum NatTypeSimple
@@ -119,6 +31,9 @@ public sealed partial class AcceleratorPageViewModel : TabItemViewModel
[ObservableAsProperty]
public bool IsDNSChecking { get; }
[ObservableAsProperty]
public bool IsIPv6Checking { get; }
[Reactive]
public string DomainPendingTest { get; set; } = string.Empty;
@@ -137,10 +52,18 @@ public sealed partial class AcceleratorPageViewModel : TabItemViewModel
[Reactive]
public string DNSTestResult { get; set; } = string.Empty;
[Reactive]
public string IPv6Address { get; set; } = string.Empty;
[Reactive]
public bool IsSupportIPv6 { get; set; }
public ReactiveCommand<Unit, (NatTypeSimple Nat, bool PingSuccess)> NATCheckCommand { get; }
public ReactiveCommand<Unit, Unit> DNSCheckCommand { get; }
public ReactiveCommand<Unit, Unit> IPv6CheckCommand { get; }
public ICommand StartProxyCommand { get; }
public ICommand RefreshCommand { get; }
@@ -44,4 +44,22 @@ public sealed class ProxySettingsWindowViewModel : WindowViewModel
DohAddres_360,
TUNA_DohAddres,
};
public void ResetSettings()
{
// 更改多个设置项不立即保存
ProxySettings.SystemProxyIp.Reset(save: false);
ProxySettings.ProxyMasterDns.Reset(save: false);
ProxySettings.SystemProxyPortId.Reset(save: false);
ProxySettings.ProgramStartupRunProxy.Reset(save: false);
ProxySettings.EnableHttpProxyToHttps.Reset(save: false);
ProxySettings.UseDoh.Reset(save: false);
ProxySettings.CustomDohAddres2.Reset(save: false);
ProxySettings.OnlyEnableProxyScript.Reset(save: false);
// 更改完成后保存一次
ProxySettings.OnlyEnableProxyScript.Save();
Toast.Show(ToastIcon.Success, "重置成功");
}
}
@@ -24,7 +24,7 @@
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate x:DataType="spp:ProxyDomainGroupViewModel">
<Border>
<ui:SettingsExpander IsClickEnabled="False" IsExpanded="True">
<ui:SettingsExpander.Header>
@@ -77,7 +77,8 @@
DockPanel.Dock="Right"
Foreground="{Binding DelayColor}"
Text="{Binding DelayMillseconds}"
ToolTip.Tip="此数值代表测试网页打开访问所需时间,仅供参考" />
ToolTip.ShowDelay="0"
ToolTip.Tip="此数值代表测试网页打开访问所需时间,请以实际浏览器访问网页状况为准" />
</DockPanel>
</TreeDataTemplate>
</TreeView.ItemTemplate>
@@ -18,23 +18,15 @@
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="8">
<ui:SettingsExpander IsClickEnabled="False" IsExpanded="True">
<ui:SettingsExpander.Header>
<StackPanel Orientation="Horizontal">
<ui:FontIcon
FontFamily="Segoe Fluent Icons"
FontSize="26"
Glyph="&#xE969;" />
<TextBlock
Margin="12,0,0,0"
VerticalAlignment="Center"
Text="NAT 类型检测" />
</StackPanel>
</ui:SettingsExpander.Header>
<!-- NAT -->
<ui:SettingsExpander Header="NAT 类型检测" IsExpanded="True">
<ui:SettingsExpander.IconSource>
<ui:FontIconSource FontFamily="Segoe Fluent Icons" Glyph="&#xE969;" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<WrapPanel>
<ui:FAComboBox
Margin="4"
Margin="0,0,4,4"
IsEditable="False"
IsEnabled="{Binding !IsNATChecking}"
ItemsSource="{Binding STUNAddress}"
@@ -49,7 +41,7 @@
<Button
x:Name="NATCheckButton"
Width="100"
Margin="4"
Margin="0,0,0,4"
x:FieldModifier="public"
Command="{Binding NATCheckCommand}"
Content="检测"
@@ -57,276 +49,283 @@
</WrapPanel>
</ui:SettingsExpander.Footer>
<StackPanel
Margin="24,0,0,0"
Background="Transparent"
Spacing="8">
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock VerticalAlignment="Center" Text="NAT 类型:" />
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
HorizontalAlignment="Right"
IsVisible="{Binding IsNATChecking}" />
<ui:SettingsExpanderItem Content="NAT 类型:">
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
HorizontalAlignment="Right"
IsVisible="{Binding IsNATChecking}" />
<DockPanel IsVisible="{Binding !IsNATChecking}">
<SelectableTextBlock
x:Name="NATTextBlock"
VerticalAlignment="Center"
Foreground="Gray"
Text="未知" />
<ui:FontIcon
Margin="3,0"
FontSize="20"
Glyph="&#xE946;">
<ToolTip.Tip>
<TextBlock x:Name="NATTypeTip" Text="未知类型" />
</ToolTip.Tip>
</ui:FontIcon>
</DockPanel>
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<Separator Margin="-64,0" Classes="Separator" />
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock VerticalAlignment="Center" Text="本地IP地址:" />
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
HorizontalAlignment="Right"
IsVisible="{Binding IsNATChecking}" />
<DockPanel IsVisible="{Binding !IsNATChecking}">
<SelectableTextBlock
x:Name="NATTextBlock"
VerticalAlignment="Center"
Foreground="Gray"
IsVisible="{Binding !IsNATChecking}"
Text="{Binding LocalEndPoint}" />
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
Text="未知" />
<ui:FontIcon
Margin="3,0"
FontSize="20"
Glyph="&#xE946;"
ToolTip.ShowDelay="0">
<ToolTip.Tip>
<TextBlock x:Name="NATTypeTip" Text="未知类型" />
</ToolTip.Tip>
</ui:FontIcon>
</DockPanel>
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<Separator Margin="-64,0" Classes="Separator" />
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock
VerticalAlignment="Center"
Text="对外IP地址:"
ToolTip.Tip="若与您路由器 WAN IP 地址相同,则为公共网络" />
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
HorizontalAlignment="Right"
IsVisible="{Binding IsNATChecking}" />
<SelectableTextBlock
Foreground="Gray"
IsVisible="{Binding !IsNATChecking}"
Text="{Binding PublicEndPoint}" />
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Content="本机IP地址:">
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
HorizontalAlignment="Right"
IsVisible="{Binding IsNATChecking}" />
<SelectableTextBlock
Foreground="Gray"
IsVisible="{Binding !IsNATChecking}"
Text="{Binding LocalEndPoint}" />
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<TextBlock
VerticalAlignment="Center"
Text="对外IP地址:"
ToolTip.Tip="若与您路由器 WAN IP 地址相同,则为公共网络" />
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
HorizontalAlignment="Right"
IsVisible="{Binding IsNATChecking}" />
<SelectableTextBlock
Foreground="Gray"
IsVisible="{Binding !IsNATChecking}"
Text="{Binding PublicEndPoint}" />
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Content="互联网连通情况:">
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
HorizontalAlignment="Right"
IsVisible="{Binding IsNATChecking}" />
<Separator Margin="-64,0" Classes="Separator" />
<ui:FontIcon
x:Name="PingOK"
HorizontalAlignment="Right"
x:FieldModifier="public"
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="26"
Foreground="Green"
Glyph="&#xe73d;" />
<!--<Border Classes="paper" Margin="-64 0" />-->
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock VerticalAlignment="Center" Text="互联网连通情况:" />
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
<Grid x:Name="PingError" x:FieldModifier="public">
<ui:FontIcon
Margin="3"
HorizontalAlignment="Right"
IsVisible="{Binding IsNATChecking}" />
FontFamily="Segoe Fluent Icons"
FontSize="20"
Foreground="Red"
Glyph="&#xf127;" />
<ui:FontIcon
x:Name="PingOK"
HorizontalAlignment="Right"
x:FieldModifier="public"
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="26"
Foreground="Green"
Glyph="&#xe73d;" />
<Grid x:Name="PingError" x:FieldModifier="public">
<ui:FontIcon
Margin="3"
HorizontalAlignment="Right"
FontFamily="Segoe Fluent Icons"
FontSize="20"
Foreground="Red"
Glyph="&#xf127;" />
<ui:FontIcon
HorizontalAlignment="Right"
FontSize="26"
Foreground="White"
Glyph="&#xea39;" />
</Grid>
Foreground="White"
Glyph="&#xea39;" />
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<!--<Separator Classes="Separator" Margin="-64 0" />-->
<Separator Margin="-64,0" Classes="Horizontal" />
<StackPanel Spacing="8">
<TextBlock
Margin="-28,0,0,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="相关链接" />
<ui:SettingsExpanderItem Margin="-28,0,0,-4" Background="Transparent">
<TextBlock
<WrapPanel Margin="-10,0,0,0">
<HyperlinkButton
VerticalAlignment="Center"
FontWeight="Bold"
Text="相关链接" />
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Margin="-28,-24,0,-4" Background="Transparent">
<WrapPanel Orientation="Horizontal">
<HyperlinkButton
VerticalAlignment="Center"
Content="XBOX 关于 NAT 文档"
NavigateUri="https://support.xbox.com/zh-CN/help/hardware-network/connect-network/xbox-one-nat-error" />
<HyperlinkButton
VerticalAlignment="Center"
Content="PlayStation 关于 NAT 文档"
NavigateUri="https://manuals.playstation.net/document/ct/ps4/settings/nw_test.html" />
<HyperlinkButton
VerticalAlignment="Center"
Content="Nintendo Switch 关于 NAT 文档"
NavigateUri="https://www.nintendoswitch.com.cn/support/faq/373.html" />
<HyperlinkButton
VerticalAlignment="Center"
Content="Bungie 关于 NAT 文档"
NavigateUri="https://help.bungie.net/hc/zh-cn/articles/360049496751-%E9%AB%98%E7%BA%A7%E7%96%91%E9%9A%BE%E6%8E%92%E8%A7%A3-%E9%80%9A%E7%94%A8%E5%8D%B3%E6%8F%92%E5%8D%B3%E7%94%A8-%E7%AB%AF%E5%8F%A3%E8%BD%AC%E9%80%81%E4%B8%8E%E7%BD%91%E7%BB%9C%E5%9C%B0%E5%9D%80%E8%BD%AC%E6%8D%A2%E7%B1%BB%E5%9E%8B" />
<HyperlinkButton
VerticalAlignment="Center"
Content="Activision 关于 NAT 文档"
NavigateUri="https://support.activision.com/tw/zh/articles/port-forward-and-nat-faq" />
<HyperlinkButton
VerticalAlignment="Center"
Content="RockStar Games 关于 NAT 文档"
NavigateUri="https://support.rockstargames.com/tw/articles/206210548/%E5%A6%82%E4%BD%95%E8%A7%A3%E6%B1%BA-GTA-%E7%B7%9A%E4%B8%8A%E6%A8%A1%E5%BC%8F%E4%B8%AD%E8%88%87%E5%9A%B4%E6%A0%BC-Strict-NAT-%E9%A1%9E%E5%9E%8B%E7%9B%B8%E9%97%9C%E7%9A%84%E9%8C%AF%E8%AA%A4" />
<HyperlinkButton
VerticalAlignment="Center"
Content="迅游关于 NAT 文档"
NavigateUri="https://cs.xunyou.com/html/282/15271.shtml" />
</WrapPanel>
</ui:SettingsExpanderItem>
Content="XBOX 关于 NAT 文档"
NavigateUri="https://support.xbox.com/zh-CN/help/hardware-network/connect-network/xbox-one-nat-error" />
<HyperlinkButton
VerticalAlignment="Center"
Content="PlayStation 关于 NAT 文档"
NavigateUri="https://manuals.playstation.net/document/ct/ps4/settings/nw_test.html" />
<HyperlinkButton
VerticalAlignment="Center"
Content="Nintendo Switch 关于 NAT 文档"
NavigateUri="https://www.nintendoswitch.com.cn/support/faq/373.html" />
<HyperlinkButton
VerticalAlignment="Center"
Content="Bungie 关于 NAT 文档"
NavigateUri="https://help.bungie.net/hc/zh-cn/articles/360049496751-%E9%AB%98%E7%BA%A7%E7%96%91%E9%9A%BE%E6%8E%92%E8%A7%A3-%E9%80%9A%E7%94%A8%E5%8D%B3%E6%8F%92%E5%8D%B3%E7%94%A8-%E7%AB%AF%E5%8F%A3%E8%BD%AC%E9%80%81%E4%B8%8E%E7%BD%91%E7%BB%9C%E5%9C%B0%E5%9D%80%E8%BD%AC%E6%8D%A2%E7%B1%BB%E5%9E%8B" />
<HyperlinkButton
VerticalAlignment="Center"
Content="Activision 关于 NAT 文档"
NavigateUri="https://support.activision.com/tw/zh/articles/port-forward-and-nat-faq" />
<HyperlinkButton
VerticalAlignment="Center"
Content="RockStar Games 关于 NAT 文档"
NavigateUri="https://support.rockstargames.com/tw/articles/206210548/%E5%A6%82%E4%BD%95%E8%A7%A3%E6%B1%BA-GTA-%E7%B7%9A%E4%B8%8A%E6%A8%A1%E5%BC%8F%E4%B8%AD%E8%88%87%E5%9A%B4%E6%A0%BC-Strict-NAT-%E9%A1%9E%E5%9E%8B%E7%9B%B8%E9%97%9C%E7%9A%84%E9%8C%AF%E8%AA%A4" />
<HyperlinkButton
VerticalAlignment="Center"
Content="迅游关于 NAT 文档"
NavigateUri="https://cs.xunyou.com/html/282/15271.shtml" />
</WrapPanel>
</StackPanel>
</ui:SettingsExpander>
<!-- DNS -->
<ui:SettingsExpander IsClickEnabled="False" IsExpanded="True">
<ui:SettingsExpander.Header>
<StackPanel Orientation="Horizontal">
<ui:FontIcon
FontFamily="Segoe Fluent Icons"
FontSize="26"
Glyph="&#xEDA3;" />
<TextBlock
Margin="12,0,0,0"
VerticalAlignment="Center"
Text="DNS 检测" />
</StackPanel>
</ui:SettingsExpander.Header>
<ui:SettingsExpander Header="DNS 检测" IsExpanded="True">
<ui:SettingsExpander.IconSource>
<ui:FontIconSource FontFamily="Segoe Fluent Icons" Glyph="&#xEDA3;" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<Button
Width="100"
Command="{Binding DNSCheckCommand}"
Content="检测"
IsEnabled="True" />
Content="检测" />
</ui:SettingsExpander.Footer>
<StackPanel Margin="24,0,0,0">
<TextBlock
Margin="-28,0,0,-4"
VerticalAlignment="Center"
FontWeight="Bold"
Text="检测项目" />
<ui:SettingsExpanderItem Margin="-28,0,0,-4" Background="Transparent">
<TextBlock
VerticalAlignment="Center"
FontWeight="Bold"
Text="检测项目" />
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Content="待检测的域名">
<ui:SettingsExpanderItem.Footer>
<TextBox
Width="220"
Text="{Binding DomainPendingTest, Mode=TwoWay}"
Watermark="store.steampowered.com" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<Separator Margin="-64,0" Classes="Separator" />
<ui:SettingsExpanderItem Content="{Binding Path=Res.Settings_Proxy_CustomDNS, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<ui:SettingsExpanderItem.Footer>
<ui:FAComboBox
Width="220"
IsEditable="True"
IsEnabled="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=!Value}"
ItemsSource="{Binding Source={x:Static spp:ProxySettingsWindowViewModel.ProxyDNSs}}"
Text="{Binding Source={x:Static ms:ProxySettings.ProxyMasterDns}, Path=Value, Mode=TwoWay}" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock Text="待检测的域名" />
<ui:SettingsExpanderItem.Footer>
<TextBox
Width="200"
Text="{Binding DomainPendingTest, Mode=TwoWay}"
Watermark="store.steampowered.com" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Content="{Binding Path=Res.CommunityFix_EnableUseDoh, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<ui:SettingsExpanderItem.Footer>
<ToggleSwitch Classes="LeftContent" IsChecked="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=Value, Mode=TwoWay}" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock Text="{Binding Path=Res.Settings_Proxy_CustomDNS, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:SettingsExpanderItem.Footer>
<ui:FAComboBox
Width="200"
IsEditable="True"
IsEnabled="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=!Value}"
ItemsSource="{Binding Source={x:Static spp:ProxySettingsWindowViewModel.ProxyDNSs}}"
Text="{Binding Source={x:Static ms:ProxySettings.ProxyMasterDns}, Path=Value, Mode=TwoWay}" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<TextBlock Text="{Binding Path=Res.CommunityFix_CustomDohAddres, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:SettingsExpanderItem.Footer>
<ui:FAComboBox
Width="220"
IsEditable="True"
IsEnabled="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=Value}"
ItemsSource="{Binding Source={x:Static spp:ProxySettingsWindowViewModel.DohAddress}}"
PlaceholderText="{Binding Source={x:Static s:IDnsAnalysisService.Dnspod_DohAddres2}, Mode=OneWay}"
Text="{Binding Source={x:Static ms:ProxySettings.CustomDohAddres2}, Path=Value, Mode=TwoWay}" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock Text="{Binding Path=Res.CommunityFix_EnableUseDoh, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:SettingsExpanderItem.Footer>
<ToggleSwitch Classes="LeftContent" IsChecked="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=Value, Mode=TwoWay}" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<TextBlock
Margin="-28,0,0,-4"
VerticalAlignment="Center"
FontWeight="Bold"
Text="测试结果" />
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock Text="{Binding Path=Res.CommunityFix_CustomDohAddres, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:SettingsExpanderItem.Footer>
<ui:FAComboBox
Width="200"
IsEditable="True"
IsEnabled="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=Value}"
ItemsSource="{Binding Source={x:Static spp:ProxySettingsWindowViewModel.DohAddress}}"
PlaceholderText="{Binding Source={x:Static s:IDnsAnalysisService.Dnspod_DohAddres}, Mode=OneWay}"
Text="{Binding Source={x:Static ms:ProxySettings.CustomDohAddres}, Path=Value, Mode=TwoWay}" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Content="延迟">
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
IsVisible="{Binding IsDNSChecking}" />
<SelectableTextBlock IsVisible="{Binding !IsDNSChecking}" Text="{Binding DNSTestDelay}" />
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Margin="-28,0,0,-4" Background="Transparent">
<TextBlock
VerticalAlignment="Center"
FontWeight="Bold"
Text="测试结果" />
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Content="解析地址">
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
IsVisible="{Binding IsDNSChecking}" />
<SelectableTextBlock IsVisible="{Binding !IsDNSChecking}" Text="{Binding DNSTestResult}" />
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<Separator Margin="-64,0" Classes="Separator" />
<!-- IPV6 -->
<ui:SettingsExpander Header="IPv6 检测" IsExpanded="True">
<ui:SettingsExpander.IconSource>
<ui:FontIconSource FontFamily="Segoe Fluent Icons" Glyph="&#xE968;" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<Button
Width="100"
Command="{Binding IPv6CheckCommand}"
Content="检测" />
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock Text="延迟" />
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
IsVisible="{Binding IsDNSChecking}" />
<SelectableTextBlock Text="{Binding DNSTestDelay}" />
<ui:SettingsExpanderItem Content="支持情况">
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
IsVisible="{Binding IsIPv6Checking}" />
<Grid IsVisible="{Binding !IsIPv6Checking}">
<TextBlock
Foreground="{DynamicResource SystemFillColorSuccessBrush}"
IsVisible="{Binding IsSupportIPv6}"
Text="已支持 IPv6 访问" />
<TextBlock
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
IsVisible="{Binding !IsSupportIPv6}"
Text="不支持 IPv6 访问" />
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock Text="解析地址" />
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
IsVisible="{Binding IsDNSChecking}" />
<SelectableTextBlock Text="{Binding DNSTestResult}" />
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</StackPanel>
<ui:SettingsExpanderItem Content="IPv6 地址">
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
IsVisible="{Binding IsIPv6Checking}" />
<SelectableTextBlock
Foreground="Gray"
IsVisible="{Binding !IsIPv6Checking}"
Text="{Binding IPv6Address}" />
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
</StackPanel>
</ScrollViewer>
</UserControl>
@@ -238,28 +238,7 @@
</TabStrip.ItemTemplate>
</TabStrip>
<DockPanel>
<TextBlock Text="{x:Static spp:Strings.CommunityFix_MenuName}" Theme="{StaticResource BodyStrongTextBlockStyle}" />
<TextBlock
HorizontalAlignment="Right"
VerticalAlignment="Center"
Classes.NotSupport="{Binding Source={x:Static s:ProxyService.Current}, Path=IPv6AddresString, Mode=OneWay, Converter={StaticResource IsNullConverter}}"
Classes.Support="{Binding Source={x:Static s:ProxyService.Current}, Path=IPv6AddresString, Mode=OneWay, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}"
Theme="{StaticResource CaptionTextBlockStyle}"
ToolTip.Tip="{Binding Source={x:Static s:ProxyService.Current}, Path=IPv6AddresString, Mode=OneWay}">
<TextBlock.Styles>
<Style Selector="TextBlock.Support">
<Setter Property="Text" Value="已支持 IPv6 访问" />
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorSuccessBrush}" />
</Style>
<Style Selector="TextBlock.NotSupport">
<Setter Property="Text" Value="不支持 IPv6 访问" />
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorCriticalBrush}" />
</Style>
</TextBlock.Styles>
</TextBlock>
</DockPanel>
<TextBlock Text="{x:Static spp:Strings.CommunityFix_MenuName}" Theme="{StaticResource BodyStrongTextBlockStyle}" />
<ToggleSwitch
VerticalAlignment="Center"
@@ -424,7 +403,7 @@
</spp:PageBase.PaneContent>
<Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,Auto,*">
<Grid.Resources>
<sys:Double x:Key="AdaptiveWidth">800</sys:Double>
<sys:Double x:Key="AdaptiveWidth">900</sys:Double>
</Grid.Resources>
<Grid.Styles>
<Style Selector="StackPanel.small">
@@ -806,12 +785,20 @@
<Run Text="A:流量统计图表是用于查看实时流量数据信息,您需要先访问对应的网站,这边才会有相应的流量波动数据,请检查是否已经选择了对应的网站加速项目,如果确定有正常勾选加速项目并且访问网站依然无流量,则可能是加速的监听服务没有启用成功,尝试更换加速模式。" />
<LineBreak />
<LineBreak />
<Run FontSize="16" Text="Q:加速后对应网站打不开显示SSL错误或者Steam浏览器显示-100错误?" />
<Run FontSize="16" Text="Q:加速后对应网站打不开显示 SSL 错误或者 Steam 浏览器显示 -100 错误?" />
<LineBreak />
<Run Text="A:可以尝试执行以下操作进行修复,关闭加速,点击加速设置-移除证书再安装证书,重开加速。或者加速设置-代理设置-尝试更换doh地址或者关闭doh使用dns等方案。" />
<Run Text="A:可以尝试执行以下操作进行修复,关闭加速,点击加速设置-移除证书再安装证书,重开加速。或者加速设置 - 代理设置- 尝试更换 doh 地址或者关闭 doh 使用 dns 等方案。" />
<LineBreak />
<LineBreak />
<Run FontSize="16" Text="Q:为什么加速经常不可用?还要自己去改设置或重新安装证书?" />
<LineBreak />
<Run Text="A:Steam++ 的加速与游戏加速器的加速功能不同,加速是完全运行于你电脑本地的,所以会受到你电脑本地网络环境,系统环境各种因素影响,也正因如此才有这么多加速相关的选项,请合理根据情况适进行测试以及更改对应的选项以便加速能正常使用。" />
</TextBlock>
</ToolTip.Tip>
<ui:FontIcon FontSize="20" Glyph="&#xE82F;" />
<StackPanel Orientation="Horizontal" Spacing="5">
<ui:FontIcon FontSize="20" Glyph="&#xE82F;" />
<TextBlock Text="加速不能用?" />
</StackPanel>
</Button>
<TextBlock Text="{x:Static spp:Strings.CommunityFix_ProxyMode}" />
<TextBlock Text="{Binding Source={x:Static ms:ProxySettings.ProxyMode}, Path=Value, Mode=OneWay, Converter={StaticResource EnumLocalizationNameConverter}}" />
@@ -97,6 +97,7 @@ public partial class AcceleratorPage2 : PageBase<AcceleratorPageViewModel>
NetworkCheckControl.PingError.IsVisible = true;
}
}).DisposeWith(disposables);
ViewModel.IPv6CheckCommand.Execute().Subscribe();
});
SearchGameBox.DropDownClosed += SearchGameBox_DropDownClosed;
@@ -15,7 +15,9 @@
mc:Ignorable="d">
<StackPanel Margin="{StaticResource PageMargin}" Spacing="{StaticResource SettingsSpacing}">
<ui:SettingsExpander Header="{Binding Path=Res.CommunityFix_ProxySettings, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" IsExpanded="True">
<ui:SettingsExpander.Footer>
<Button Command="{Binding ResetSettings}" Content="{Binding Path=Res.Reset, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem>
<TextBlock Text="{Binding Path=Res.Settings_Proxy_SystemProxyIp, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:SettingsExpanderItem.Footer>
@@ -81,8 +83,8 @@
IsEditable="True"
IsEnabled="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=Value}"
ItemsSource="{Binding DohAddress}"
PlaceholderText="{Binding Source={x:Static s:IDnsAnalysisService.Dnspod_DohAddres}, Mode=OneWay}"
Text="{Binding Source={x:Static ms:ProxySettings.CustomDohAddres}, Path=Value, Mode=TwoWay}" />
PlaceholderText="{Binding Source={x:Static s:IDnsAnalysisService.Dnspod_DohAddres2}, Mode=OneWay}"
Text="{Binding Source={x:Static ms:ProxySettings.CustomDohAddres2}, Path=Value, Mode=TwoWay}" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
@@ -122,7 +122,7 @@
<PackageReference Include="Squid-Box.SevenZipSharp.Lite" />
<PackageReference Include="SharpZipLib" />
<PackageReference Include="ZstdNet" />
<!--<PackageReference Include="BsDiff" />-->
<PackageReference Include="BsDiff" />
<PackageReference Include="7-Zip.NativeAssets.Win32" />
<PackageReference Include="Portable.BouncyCastle" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" />
@@ -1,492 +1,492 @@
//using BsDiff;
//using JsonSerializer = System.Text.Json.JsonSerializer;
//using BD.WTTS.Models;
using BsDiff;
using JsonSerializer = System.Text.Json.JsonSerializer;
using BD.WTTS.Models;
//namespace BD.WTTS.Client.Tools.Publish.Commands;
namespace BD.WTTS.Client.Tools.Publish.Commands;
//interface IGenerateBsPatch : ICommand
//{
// const string commandName = "bs";
// const string BaseUrl =
//#if DEBUG
// "https://localhost:9881";
interface IGenerateBsPatch : ICommand
{
const string commandName = "bs";
const string BaseUrl =
#if DEBUG
"https://localhost:9881";
//#else
// "http://192.168.2.16:19001";
//#endif
#else
"http://192.168.2.16:19001";
#endif
// static Command ICommand.GetCommand()
// {
// var type = new Option<int>("--type", "type 0:Create 1:Apply 2:UploadFile 3:传入版本号生成补丁");
// //旧文件
// var oldFile = new Option<string>("--old", "old file");
// //新文件
// var newFile = new Option<string>("--new", "new file");
// //保存路径
// var patchFile = new Option<string>("--path", "new file");
// //程序版本号
// var clientPlatform = new Option<ClientPlatform>("--platform", "ClientPlatform");
static Command ICommand.GetCommand()
{
var type = new Option<int>("--type", "type 0:Create 1:Apply 2:UploadFile 3:传入版本号生成补丁");
//旧文件
var oldFile = new Option<string>("--old", "old file");
//新文件
var newFile = new Option<string>("--new", "new file");
//保存路径
var patchFile = new Option<string>("--path", "new file");
//程序版本号
var clientPlatform = new Option<ClientPlatform>("--platform", "ClientPlatform");
// var command = new Command(commandName, "Bridge csproj xml generate")
// {
// type, oldFile, newFile, patchFile, clientPlatform
// };
// command.SetHandler(Handler, type, oldFile, newFile, patchFile, clientPlatform);
// return command;
// }
var command = new Command(commandName, "Bridge csproj xml generate")
{
type, oldFile, newFile, patchFile, clientPlatform
};
command.SetHandler(Handler, type, oldFile, newFile, patchFile, clientPlatform);
return command;
}
// internal static void Handler(int type, string oldFile, string newFile, string patchFile, ClientPlatform platform = ClientPlatform.Win32X64)
// {
// if (type < 2)
// {
// if (!File.Exists(oldFile))
// {
// Console.WriteLine("old file not exists");
// return;
// }
// if (!File.Exists(newFile))
// {
// Console.WriteLine("new file not exists");
// return;
// }
// if (string.IsNullOrWhiteSpace(patchFile))
// {
// Console.WriteLine("The patch file path cannot be empty");
// return;
// }
// }
// switch (type)
// {
// case 0:
// using (var output = new FileStream(patchFile, FileMode.Create))
// GenerateBsPatch(File.ReadAllBytes(oldFile), File.ReadAllBytes(newFile), output);
// break;
// case 1:
// using (var input = new FileStream(oldFile, FileMode.Open, FileAccess.Read, FileShare.Read))
// using (var output = new FileStream(newFile, FileMode.Create))
// BinaryPatch.Apply(input, () => new FileStream(patchFile, FileMode.Open, FileAccess.Read, FileShare.Read), output);
// break;
// case 2:
// UploadFiles(oldFile).Wait();
// break;
// case 3:
// GenerateAppPublishPatchBundleInfo(oldFile, newFile, platform, patchFile).Wait();
// break;
// case 4:
// UploadAppVer(oldFile, newFile, platform).Wait();
// break;
// case 5:
// ApplyPatch(oldFile, newFile, patchFile).Wait();
// break;
internal static void Handler(int type, string oldFile, string newFile, string patchFile, ClientPlatform platform = ClientPlatform.Win32X64)
{
if (type < 2)
{
if (!File.Exists(oldFile))
{
Console.WriteLine("old file not exists");
return;
}
if (!File.Exists(newFile))
{
Console.WriteLine("new file not exists");
return;
}
if (string.IsNullOrWhiteSpace(patchFile))
{
Console.WriteLine("The patch file path cannot be empty");
return;
}
}
switch (type)
{
case 0:
using (var output = new FileStream(patchFile, FileMode.Create))
GenerateBsPatch(File.ReadAllBytes(oldFile), File.ReadAllBytes(newFile), output);
break;
case 1:
using (var input = new FileStream(oldFile, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var output = new FileStream(newFile, FileMode.Create))
BinaryPatch.Apply(input, () => new FileStream(patchFile, FileMode.Open, FileAccess.Read, FileShare.Read), output);
break;
case 2:
UploadFiles(oldFile).Wait();
break;
case 3:
GenerateAppPublishPatchBundleInfo(oldFile, newFile, platform, patchFile).Wait();
break;
case 4:
UploadAppVer(oldFile, newFile, platform).Wait();
break;
case 5:
ApplyPatch(oldFile, newFile, patchFile).Wait();
break;
// }
// Console.WriteLine("end");
// }
}
Console.WriteLine("end");
}
// /// <summary>
// /// 应用补丁
// /// </summary>
// /// <param name="applyPath"></param>
// /// <param name="saveFilePath"></param>
// /// <param name="patchPath"></param>
// /// <returns></returns>
// internal static async ThreadTask ApplyPatch(string applyPath, string saveFilePath, string patchPath)
// {
// if (!File.Exists(patchPath))
// {
// Console.WriteLine("PatchPath not exists");
// return;
// }
/// <summary>
/// 应用补丁
/// </summary>
/// <param name="applyPath"></param>
/// <param name="saveFilePath"></param>
/// <param name="patchPath"></param>
/// <returns></returns>
internal static async ThreadTask ApplyPatch(string applyPath, string saveFilePath, string patchPath)
{
if (!File.Exists(patchPath))
{
Console.WriteLine("PatchPath not exists");
return;
}
// if (!Directory.Exists(applyPath))
// {
// Console.WriteLine("ApplyPath not exists");
// return;
// }
// var fileStream = File.OpenRead(patchPath);
// var jsonData = new BrotliStream(fileStream, CompressionMode.Decompress);
// var dic = await MemoryPackSerializer.DeserializeAsync<Dictionary<string, PatchBundleInfo?>>(jsonData);
// if (dic is null)
// {
// Console.WriteLine("Patch Error");
// return;
// }
// if (!Directory.Exists(saveFilePath))
// {
// Directory.CreateDirectory(saveFilePath);
// }
// else
// {
// Directory.Delete(saveFilePath);
// }
// CopyDirectory(applyPath, saveFilePath);
// FileInfo? fileInfo = null;
// foreach (var item in dic.Where(x => x.Value is not null))
// {
// PatchBundleInfo info = item.Value!;
// if (info.PatchData is not null)
// {
// fileInfo = new FileInfo(Path.Combine(saveFilePath, item.Key));
// if (!(fileInfo.Directory?.Exists ?? false))
// fileInfo.Directory?.Create();
// if (info.IsFullOrPatch)
// {
// File.WriteAllBytes(Path.Combine(saveFilePath, item.Key), info.PatchData);
// }
// else
// {
// using var oldFile = File.OpenRead(Path.Combine(applyPath, item.Key));
// var newFile = Path.Combine(saveFilePath, item.Key);
// using var saveFile = File.Exists(newFile) ? File.Create(newFile) : File.OpenWrite(newFile);
// BinaryPatch.Apply(oldFile, () => new MemoryStream(info.PatchData), saveFile);
// }
// }
// }
// Console.WriteLine("ApplyPatch OK");
// }
if (!Directory.Exists(applyPath))
{
Console.WriteLine("ApplyPath not exists");
return;
}
var fileStream = File.OpenRead(patchPath);
var jsonData = new BrotliStream(fileStream, CompressionMode.Decompress);
var dic = await MemoryPackSerializer.DeserializeAsync<Dictionary<string, PatchBundleInfo?>>(jsonData);
if (dic is null)
{
Console.WriteLine("Patch Error");
return;
}
if (!Directory.Exists(saveFilePath))
{
Directory.CreateDirectory(saveFilePath);
}
else
{
Directory.Delete(saveFilePath);
}
CopyDirectory(applyPath, saveFilePath);
FileInfo? fileInfo = null;
foreach (var item in dic.Where(x => x.Value is not null))
{
PatchBundleInfo info = item.Value!;
if (info.PatchData is not null)
{
fileInfo = new FileInfo(Path.Combine(saveFilePath, item.Key));
if (!(fileInfo.Directory?.Exists ?? false))
fileInfo.Directory?.Create();
if (info.IsFullOrPatch)
{
File.WriteAllBytes(Path.Combine(saveFilePath, item.Key), info.PatchData);
}
else
{
using var oldFile = File.OpenRead(Path.Combine(applyPath, item.Key));
var newFile = Path.Combine(saveFilePath, item.Key);
using var saveFile = File.Exists(newFile) ? File.Create(newFile) : File.OpenWrite(newFile);
BinaryPatch.Apply(oldFile, () => new MemoryStream(info.PatchData), saveFile);
}
}
}
Console.WriteLine("ApplyPatch OK");
}
// /// <summary>
// /// 复制目录下的文件
// /// </summary>
// /// <param name="sourceDir"></param>
// /// <param name="targetDir"></param>
// /// <exception cref="DirectoryNotFoundException"></exception>
// public static void CopyDirectory(string sourceDir, string targetDir)
// {
// DirectoryInfo dir = new DirectoryInfo(sourceDir);
// DirectoryInfo[] dirs = dir.GetDirectories();
/// <summary>
/// 复制目录下的文件
/// </summary>
/// <param name="sourceDir"></param>
/// <param name="targetDir"></param>
/// <exception cref="DirectoryNotFoundException"></exception>
public static void CopyDirectory(string sourceDir, string targetDir)
{
DirectoryInfo dir = new DirectoryInfo(sourceDir);
DirectoryInfo[] dirs = dir.GetDirectories();
// // If the source directory does not exist, throw an exception.
// if (!dir.Exists)
// {
// throw new DirectoryNotFoundException($"Source directory does not exist or could not be found: {sourceDir}");
// }
// If the source directory does not exist, throw an exception.
if (!dir.Exists)
{
throw new DirectoryNotFoundException($"Source directory does not exist or could not be found: {sourceDir}");
}
// // If the destination directory does not exist, create it.
// if (!Directory.Exists(targetDir))
// {
// Directory.CreateDirectory(targetDir);
// }
// If the destination directory does not exist, create it.
if (!Directory.Exists(targetDir))
{
Directory.CreateDirectory(targetDir);
}
// // Get the files in the directory and copy them to the new location.
// FileInfo[] files = dir.GetFiles();
// foreach (FileInfo file in files)
// {
// string tempPath = Path.Combine(targetDir, file.Name);
// file.CopyTo(tempPath, false);
// }
// Get the files in the directory and copy them to the new location.
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
string tempPath = Path.Combine(targetDir, file.Name);
file.CopyTo(tempPath, false);
}
// // If copying subdirectories, copy them and their contents to the new location.
// foreach (DirectoryInfo subdir in dirs)
// {
// string tempPath = Path.Combine(targetDir, subdir.Name);
// CopyDirectory(subdir.FullName, tempPath);
// }
// }
// If copying subdirectories, copy them and their contents to the new location.
foreach (DirectoryInfo subdir in dirs)
{
string tempPath = Path.Combine(targetDir, subdir.Name);
CopyDirectory(subdir.FullName, tempPath);
}
}
// /// <summary>
// /// 根据 2个 文件生成补丁
// /// </summary>
// /// <param name="oldByte"></param>
// /// <param name="newByte"></param>
// /// <param name="output"></param>
// internal static void GenerateBsPatch(byte[] oldByte, byte[] newByte, Stream output)
// {
// BinaryPatch.Create(oldByte, newByte, output);
// }
/// <summary>
/// 根据 2个 文件生成补丁
/// </summary>
/// <param name="oldByte"></param>
/// <param name="newByte"></param>
/// <param name="output"></param>
internal static void GenerateBsPatch(byte[] oldByte, byte[] newByte, Stream output)
{
BinaryPatch.Create(oldByte, newByte, output);
}
// /// <summary>
// /// 上传版本信息 加 FileList
// /// </summary>
// /// <param name="appVer"></param>
// /// <param name="jsonPath">发布工具生成的 Json</param>
// /// <param name="platform"></param>
// /// <returns></returns>
// internal static async ThreadTask UploadAppVer(string appVer, string jsonPath, ClientPlatform platform)
// {
// if (string.IsNullOrWhiteSpace(appVer))
// {
// Console.WriteLine("AppVer not exists");
// return;
// }
// if (string.IsNullOrWhiteSpace(jsonPath))
// {
// Console.WriteLine("jsonPath not exists");
// return;
// }
// if (!File.Exists(jsonPath))
// {
// Console.WriteLine("jsonPath not Find");
// return;
// }
/// <summary>
/// 上传版本信息 加 FileList
/// </summary>
/// <param name="appVer"></param>
/// <param name="jsonPath">发布工具生成的 Json</param>
/// <param name="platform"></param>
/// <returns></returns>
internal static async ThreadTask UploadAppVer(string appVer, string jsonPath, ClientPlatform platform)
{
if (string.IsNullOrWhiteSpace(appVer))
{
Console.WriteLine("AppVer not exists");
return;
}
if (string.IsNullOrWhiteSpace(jsonPath))
{
Console.WriteLine("jsonPath not exists");
return;
}
if (!File.Exists(jsonPath))
{
Console.WriteLine("jsonPath not Find");
return;
}
// var fileListRaw = File.ReadAllBytes(jsonPath);
// var appver = await GetAppVer(appVer);
// if (appver is not null)
// {
// var thisFileList = appver.FileList?.FirstOrDefault(x => x.Platform == platform);
// if (thisFileList is not null)
// {
// if (Hashs.String.SHA384(thisFileList.FileListRaw) != Hashs.String.SHA384(fileListRaw))
// {
// thisFileList.FileListRaw = fileListRaw;
// }
// }
// else
// {
// if (appver.FileList is null)
// {
// appver.FileList = new List<AppVerFileList>
// {
// new AppVerFileList
// {
// Platform = platform,
// FileListRaw = fileListRaw
// }
// };
// }
// else
// {
// appver.FileList.Add(new AppVerFileList
// {
// Platform = platform,
// FileListRaw = fileListRaw
// });
// }
// }
// await UploadAppVer(appver);
// }
// else
// {
// appver = new AppVer
// {
// Platform = platform,
// Ver = appVer,
// Published = DateTimeOffset.Now,
// FileList = new List<AppVerFileList>
// {
// new AppVerFileList
// {
// Platform = platform,
// FileListRaw = fileListRaw
// }
// }
// };
// await UploadAppVer(appver);
// }
// }
var fileListRaw = File.ReadAllBytes(jsonPath);
var appver = await GetAppVer(appVer);
if (appver is not null)
{
var thisFileList = appver.FileList?.FirstOrDefault(x => x.Platform == platform);
if (thisFileList is not null)
{
if (Hashs.String.SHA384(thisFileList.FileListRaw) != Hashs.String.SHA384(fileListRaw))
{
thisFileList.FileListRaw = fileListRaw;
}
}
else
{
if (appver.FileList is null)
{
appver.FileList = new List<AppVerFileList>
{
new AppVerFileList
{
Platform = platform,
FileListRaw = fileListRaw
}
};
}
else
{
appver.FileList.Add(new AppVerFileList
{
Platform = platform,
FileListRaw = fileListRaw
});
}
}
await UploadAppVer(appver);
}
else
{
appver = new AppVer
{
Platform = platform,
Ver = appVer,
Published = DateTimeOffset.Now,
FileList = new List<AppVerFileList>
{
new AppVerFileList
{
Platform = platform,
FileListRaw = fileListRaw
}
}
};
await UploadAppVer(appver);
}
}
// /// <summary>
// /// 上传版本信息
// /// </summary>
// /// <param name="appVer"></param>
// /// <returns></returns>
// internal static async ThreadTask UploadAppVer(AppVer appVer)
// {
// using var httpClient = new HttpClient();
// var content = JsonContent.Create(appVer);
// var response = await httpClient.PostAsync($"{BaseUrl}/appver/", content);
// }
/// <summary>
/// 上传版本信息
/// </summary>
/// <param name="appVer"></param>
/// <returns></returns>
internal static async ThreadTask UploadAppVer(AppVer appVer)
{
using var httpClient = new HttpClient();
var content = JsonContent.Create(appVer);
var response = await httpClient.PostAsync($"{BaseUrl}/appver/", content);
}
// /// <summary>
// /// 传入 2 个版本号生成补丁
// /// </summary>
// /// <param name="oldVer"></param>
// /// <param name="newVer"></param>
// /// <param name="platform"></param>
// /// <param name="patchFilePath"></param>
// /// <returns></returns>
// internal static async ThreadTask GenerateAppPublishPatchBundleInfo(string oldVer, string newVer, ClientPlatform platform, string patchFilePath)
// {
// var oldAppVer = await GetAppVer(oldVer);
// var newAppVer = await GetAppVer(newVer);
// if (oldAppVer is null || newAppVer is null)
// {
// Console.WriteLine("AppVer not exists");
// return;
// }
// var oldFileList = await GetAppPublishInfo(oldAppVer.Id, platform);
// var newFileList = await GetAppPublishInfo(newAppVer.Id, platform);
// if (oldFileList is null || newFileList is null)
// {
// Console.WriteLine("AppPublishInfo not exists");
// return;
// }
// var oldAppVerFileList = JsonSerializer.Deserialize<AppPublishInfo>(oldFileList.FileListRaw);
// var newAppVerFileList = JsonSerializer.Deserialize<AppPublishInfo>(newFileList.FileListRaw);
// if (oldAppVerFileList is null || newAppVerFileList is null)
// {
// Console.WriteLine("AppPublishInfo not exists");
// return;
// }
// var pathSavePath = Path.Combine(patchFilePath, oldVer, $"{newVer}.dat");
// var pathFileInfo = new FileInfo(pathSavePath);
// if (!(pathFileInfo.Directory?.Exists ?? false))
// {
// pathFileInfo.Directory?.Create();
// }
// Console.WriteLine($"开始生成: {oldVer} -> {newVer} platform:{platform}");
// await ContrastVersion(oldAppVerFileList, newAppVerFileList, pathSavePath);
/// <summary>
/// 传入 2 个版本号生成补丁
/// </summary>
/// <param name="oldVer"></param>
/// <param name="newVer"></param>
/// <param name="platform"></param>
/// <param name="patchFilePath"></param>
/// <returns></returns>
internal static async ThreadTask GenerateAppPublishPatchBundleInfo(string oldVer, string newVer, ClientPlatform platform, string patchFilePath)
{
var oldAppVer = await GetAppVer(oldVer);
var newAppVer = await GetAppVer(newVer);
if (oldAppVer is null || newAppVer is null)
{
Console.WriteLine("AppVer not exists");
return;
}
var oldFileList = await GetAppPublishInfo(oldAppVer.Id, platform);
var newFileList = await GetAppPublishInfo(newAppVer.Id, platform);
if (oldFileList is null || newFileList is null)
{
Console.WriteLine("AppPublishInfo not exists");
return;
}
var oldAppVerFileList = JsonSerializer.Deserialize<AppPublishInfo>(oldFileList.FileListRaw);
var newAppVerFileList = JsonSerializer.Deserialize<AppPublishInfo>(newFileList.FileListRaw);
if (oldAppVerFileList is null || newAppVerFileList is null)
{
Console.WriteLine("AppPublishInfo not exists");
return;
}
var pathSavePath = Path.Combine(patchFilePath, oldVer, $"{newVer}.dat");
var pathFileInfo = new FileInfo(pathSavePath);
if (!(pathFileInfo.Directory?.Exists ?? false))
{
pathFileInfo.Directory?.Create();
}
Console.WriteLine($"开始生成: {oldVer} -> {newVer} platform:{platform}");
await ContrastVersion(oldAppVerFileList, newAppVerFileList, pathSavePath);
// }
}
// /// <summary>
// /// 获取版本号信息
// /// </summary>
// /// <param name="ver"></param>
// /// <returns></returns>
// public static async Task<AppVer?> GetAppVer(string ver)
// {
// using var httpClient = new HttpClient();
// var response = await httpClient.GetAsync($"{BaseUrl}/appver/{ver}");
// var data = JsonSerializer.Deserialize<ApiRsp<AppVer?>>(await response.Content.ReadAsStringAsync(), new JsonSerializerOptions
// {
// PropertyNameCaseInsensitive = true
// });
// return data?.Content;
// }
/// <summary>
/// 获取版本号信息
/// </summary>
/// <param name="ver"></param>
/// <returns></returns>
public static async Task<AppVer?> GetAppVer(string ver)
{
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync($"{BaseUrl}/appver/{ver}");
var data = JsonSerializer.Deserialize<ApiRsp<AppVer?>>(await response.Content.ReadAsStringAsync(), new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
return data?.Content;
}
// /// <summary>
// /// 更具 APP 版本 ID 获取发布信息
// /// </summary>
// /// <param name="appVerId">版本号</param>
// /// <param name="platform">平台</param>
// /// <returns></returns>
// public static async Task<AppVerFileList?> GetAppPublishInfo(Guid appVerId, ClientPlatform platform)
// {
// using var httpClient = new HttpClient();
// var response = await httpClient.GetAsync($"{BaseUrl}/appver/file/{appVerId}/{platform}/list");
// var data = JsonSerializer.Deserialize<ApiRsp<AppVerFileList?>>(await response.Content.ReadAsStringAsync(), new JsonSerializerOptions
// {
// PropertyNameCaseInsensitive = true
// });
// return data?.Content;
// }
/// <summary>
/// 更具 APP 版本 ID 获取发布信息
/// </summary>
/// <param name="appVerId">版本号</param>
/// <param name="platform">平台</param>
/// <returns></returns>
public static async Task<AppVerFileList?> GetAppPublishInfo(Guid appVerId, ClientPlatform platform)
{
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync($"{BaseUrl}/appver/file/{appVerId}/{platform}/list");
var data = JsonSerializer.Deserialize<ApiRsp<AppVerFileList?>>(await response.Content.ReadAsStringAsync(), new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
return data?.Content;
}
// /// <summary>
// /// 根据 2 个版本的文件列表生成补丁
// /// </summary>
// /// <param name="oldFileList"></param>
// /// <param name="newFileList"></param>
// /// <param name="patchFilePath"></param>
// /// <returns></returns>
// public static async ThreadTask ContrastVersion(AppPublishInfo oldFileList, AppPublishInfo newFileList, string patchFilePath)
// {
// var oldFiles = oldFileList.Files.Where(x => !x.RelativePath.StartsWith("dotnet\\"));
// var newFiles = newFileList.Files.Where(x => !x.RelativePath.StartsWith("dotnet\\"));
// Console.WriteLine($"旧版本文件数:{oldFiles.Count()}");
// Console.WriteLine($"新版本文件数:{newFiles.Count()}");
// var changeList = new Dictionary<string, PatchBundleInfo?>();
/// <summary>
/// 根据 2 个版本的文件列表生成补丁
/// </summary>
/// <param name="oldFileList"></param>
/// <param name="newFileList"></param>
/// <param name="patchFilePath"></param>
/// <returns></returns>
public static async ThreadTask ContrastVersion(AppPublishInfo oldFileList, AppPublishInfo newFileList, string patchFilePath)
{
var oldFiles = oldFileList.Files.Where(x => !x.RelativePath.StartsWith("dotnet\\"));
var newFiles = newFileList.Files.Where(x => !x.RelativePath.StartsWith("dotnet\\"));
Console.WriteLine($"旧版本文件数:{oldFiles.Count()}");
Console.WriteLine($"新版本文件数:{newFiles.Count()}");
var changeList = new Dictionary<string, PatchBundleInfo?>();
// var removeFiles = oldFiles.Select(x => x.RelativePath).Except(newFiles.Select(x => x.RelativePath));
// //文件删除
// foreach (var item in removeFiles)
// {
// changeList.Add(item, null);
// }
// Console.WriteLine($"需要删除的文件数:{changeList.Count}");
// foreach (var item in newFiles)
// {
// var oldFile = oldFiles.FirstOrDefault(x => x.RelativePath.Equals(item.RelativePath));
// //文件新增
// if (oldFile == null)
// {
// var newFileRaw = string.IsNullOrWhiteSpace(item.SignatureSHA384) ? await GetFileRaw(item.SHA384) : await GetFileRaw(item.SignatureSHA384);
// if (newFileRaw is null)
// {
// Console.WriteLine("new file not exists");
// return;
// }
// changeList.Add(item.RelativePath, new PatchBundleInfo
// {
// SHA384 = item.SHA384,
// IsFullOrPatch = true,
// PatchData = newFileRaw.DataRaw
// });
// }
// else
// {
// //文件修改
// if (oldFile.SHA384 != item.SHA384)
// {
// var oldFileRaw = string.IsNullOrWhiteSpace(oldFile.SignatureSHA384) ? await GetFileRaw(oldFile.SHA384) : await GetFileRaw(oldFile.SignatureSHA384);
// if (oldFileRaw is null)
// {
// Console.WriteLine("old file not exists");
// return;
// }
// var newFileRaw = string.IsNullOrWhiteSpace(item.SignatureSHA384) ? await GetFileRaw(item.SHA384) : await GetFileRaw(item.SignatureSHA384); // await GetFileRaw(item.SignatureSHA384);
// if (newFileRaw is null)
// {
// Console.WriteLine("new file not exists");
// return;
// }
// using (var output = new MemoryStream())
// {
// //BinaryPatch.Create(oldFileRaw.DataRaw, File.ReadAllBytes(item.RelativePath), output);
// BinaryPatch.Create(oldFileRaw.DataRaw, newFileRaw.DataRaw, output);
// changeList.Add(item.RelativePath, new PatchBundleInfo
// {
// SHA384 = item.SHA384,
// IsFullOrPatch = false,
// PatchData = output.ToArray()
// });
// }
// }
// }
// }
// if (File.Exists(patchFilePath))
// File.Delete(patchFilePath);
var removeFiles = oldFiles.Select(x => x.RelativePath).Except(newFiles.Select(x => x.RelativePath));
//文件删除
foreach (var item in removeFiles)
{
changeList.Add(item, null);
}
Console.WriteLine($"需要删除的文件数:{changeList.Count}");
foreach (var item in newFiles)
{
var oldFile = oldFiles.FirstOrDefault(x => x.RelativePath.Equals(item.RelativePath));
//文件新增
if (oldFile == null)
{
var newFileRaw = string.IsNullOrWhiteSpace(item.SignatureSHA384) ? await GetFileRaw(item.SHA384) : await GetFileRaw(item.SignatureSHA384);
if (newFileRaw is null)
{
Console.WriteLine("new file not exists");
return;
}
changeList.Add(item.RelativePath, new PatchBundleInfo
{
SHA384 = item.SHA384,
IsFullOrPatch = true,
PatchData = newFileRaw.DataRaw
});
}
else
{
//文件修改
if (oldFile.SHA384 != item.SHA384)
{
var oldFileRaw = string.IsNullOrWhiteSpace(oldFile.SignatureSHA384) ? await GetFileRaw(oldFile.SHA384) : await GetFileRaw(oldFile.SignatureSHA384);
if (oldFileRaw is null)
{
Console.WriteLine("old file not exists");
return;
}
var newFileRaw = string.IsNullOrWhiteSpace(item.SignatureSHA384) ? await GetFileRaw(item.SHA384) : await GetFileRaw(item.SignatureSHA384); // await GetFileRaw(item.SignatureSHA384);
if (newFileRaw is null)
{
Console.WriteLine("new file not exists");
return;
}
using (var output = new MemoryStream())
{
//BinaryPatch.Create(oldFileRaw.DataRaw, File.ReadAllBytes(item.RelativePath), output);
BinaryPatch.Create(oldFileRaw.DataRaw, newFileRaw.DataRaw, output);
changeList.Add(item.RelativePath, new PatchBundleInfo
{
SHA384 = item.SHA384,
IsFullOrPatch = false,
PatchData = output.ToArray()
});
}
}
}
}
if (File.Exists(patchFilePath))
File.Delete(patchFilePath);
// Console.WriteLine($"变动文件数量:{changeList.Count}");
// using var fileStream = File.OpenWrite(patchFilePath);
// using var brSave = new BrotliStream(fileStream, CompressionLevel.SmallestSize);
// Console.WriteLine($"开始压缩");
// await MemoryPackSerializer.SerializeAsync(typeof(Dictionary<string, PatchBundleInfo?>), brSave, changeList);
// //fileStream.Flush();
// Console.WriteLine($"结束");
// }
Console.WriteLine($"变动文件数量:{changeList.Count}");
using var fileStream = File.OpenWrite(patchFilePath);
using var brSave = new BrotliStream(fileStream, CompressionLevel.SmallestSize);
Console.WriteLine($"开始压缩");
await MemoryPackSerializer.SerializeAsync(typeof(Dictionary<string, PatchBundleInfo?>), brSave, changeList);
//fileStream.Flush();
Console.WriteLine($"结束");
}
// /// <summary>
// /// Hash 384 获取源文件数据
// /// </summary>
// /// <param name="sha384"></param>
// /// <returns></returns>
// public static async Task<AppVerFile?> GetFileRaw(string sha384)
// {
// using var httpClient = new HttpClient();
// var response = await httpClient.GetAsync($"{BaseUrl}/appver/file/{sha384}");
// var data = JsonSerializer.Deserialize<ApiRsp<AppVerFile?>>(await response.Content.ReadAsStringAsync(), new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
// return data?.Content;
// }
/// <summary>
/// Hash 384 获取源文件数据
/// </summary>
/// <param name="sha384"></param>
/// <returns></returns>
public static async Task<AppVerFile?> GetFileRaw(string sha384)
{
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync($"{BaseUrl}/appver/file/{sha384}");
var data = JsonSerializer.Deserialize<ApiRsp<AppVerFile?>>(await response.Content.ReadAsStringAsync(), new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
return data?.Content;
}
// /// <summary>
// /// 上传指定文件到服务器
// /// </summary>
// /// <param name="directoryPath">需要上传的目录</param>
// internal static async ThreadTask UploadFiles(string directoryPath)
// {
// DirectoryInfo directoryInfo = new(directoryPath);
// if (directoryInfo.Exists)
// {
// using var httpClient = new HttpClient();
// var fileList = new List<AppPublishFileInfo>();
// IScanPublicDirectoryCommand.ScanPathCore(directoryPath, fileList, null, null);
// using var form = new MultipartFormDataContent();
// fileList.ForEach(file =>
// {
// var fileStream = file.FileInfo!.OpenRead();
// form.Add(new StreamContent(fileStream), file.FileInfo!.Name, file.FileInfo!.Name);
// });
// if (form.Count() > 0)
// {
// var response = await httpClient.PostAsync($"{BaseUrl}/appver/file/upload", form);
// Console.WriteLine(response);
// }
// }
// else
// {
// Console.WriteLine("文件夹不存在");
// }
// }
//}
/// <summary>
/// 上传指定文件到服务器
/// </summary>
/// <param name="directoryPath">需要上传的目录</param>
internal static async ThreadTask UploadFiles(string directoryPath)
{
DirectoryInfo directoryInfo = new(directoryPath);
if (directoryInfo.Exists)
{
using var httpClient = new HttpClient();
var fileList = new List<AppPublishFileInfo>();
IScanPublicDirectoryCommand.ScanPathCore(directoryPath, fileList, null, null);
using var form = new MultipartFormDataContent();
fileList.ForEach(file =>
{
var fileStream = file.FileInfo!.OpenRead();
form.Add(new StreamContent(fileStream), file.FileInfo!.Name, file.FileInfo!.Name);
});
if (form.Count() > 0)
{
var response = await httpClient.PostAsync($"{BaseUrl}/appver/file/upload", form);
Console.WriteLine(response);
}
}
else
{
Console.WriteLine("文件夹不存在");
}
}
}
@@ -28,9 +28,9 @@ public sealed class NoticeService : ReactiveObject
{
Notices = new ObservableCollection<OfficialMessageItemDTO>();
_timer = new Timer(async _ =>
_timer = new Timer(_ =>
{
await GetNewsAsync();
GetNewsAsync().Wait();
}, null, TimeSpan.FromHours(2), TimeSpan.FromHours(2));
//NoticesSource = new(x => x.Id);
@@ -60,7 +60,6 @@ public sealed class NoticeService : ReactiveObject
if (item.PushTime > GeneralSettings.LastLookNoticeDateTime.Value)
{
item.Unread = true;
UnreadNotificationsCount++;
}
}
@@ -75,6 +74,7 @@ public sealed class NoticeService : ReactiveObject
//}
var notice = Notices.First(x => x.Unread == true);
INotificationService.Instance.Notify(notice.Content, NotificationType.Announcement, title: notice.Title);
GeneralSettings.LastLookNoticeDateTime.Value = DateTimeOffset.Now;
}
}
}
@@ -12,7 +12,7 @@ public abstract class SettingsPropertyBase
public abstract void RaiseValueChanged(bool notSave = false);
public abstract void Reset();
public abstract void Reset(bool save = true);
static readonly Dictionary<Type, List<string>> SettingsProperties = new();
static readonly Dictionary<(Type SettingsType, string PropertyName), bool?> SaveNameStatus = new();
@@ -89,7 +89,7 @@ public class SettingsStructPropertyBase<TValue, [DynamicallyAccessedMembers(Dyna
}
}
public override void Reset()
public override void Reset(bool save = true)
{
var oldValue = value;
value = Default; // 赋值当前字段
@@ -97,7 +97,7 @@ public class SettingsStructPropertyBase<TValue, [DynamicallyAccessedMembers(Dyna
OnValueChanged(oldValue, value); // 调用变更事件
if (AutoSave) // 自动保存
if (save && AutoSave) // 自动保存
{
Save();
}
@@ -136,7 +136,7 @@ public class SettingsProperty<TValue, [DynamicallyAccessedMembers(DynamicallyAcc
}
}
public override void Reset()
public override void Reset(bool save = true)
{
var oldValue = value;
value = Default; // 赋值当前字段
@@ -144,7 +144,7 @@ public class SettingsProperty<TValue, [DynamicallyAccessedMembers(DynamicallyAcc
OnValueChanged(oldValue, value); // 调用变更事件
if (AutoSave) // 自动保存
if (save && AutoSave) // 自动保存
{
Save();
}
@@ -39,7 +39,7 @@ public sealed partial class SettingsPageViewModel : TabItemViewModel
{
if (string.IsNullOrEmpty(UISettings.StartDefaultPageName.Value))
{
return s.Id == "Welcome";
return s.Id == "Watt Toolkit-Welcome";
}
return s.Id == UISettings.StartDefaultPageName.Value;
});