mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Merge pull request #1276 from DGP-Studio/feat/dynamic_proxy
This commit is contained in:
@@ -1,4 +1,15 @@
|
||||
// COMCTL32
|
||||
// ADVAPI32
|
||||
RegCloseKey
|
||||
RegOpenKeyExW
|
||||
RegNotifyChangeKeyValue
|
||||
REG_NOTIFY_FILTER
|
||||
HKEY_CLASSES_ROOT
|
||||
HKEY_CURRENT_USER
|
||||
HKEY_LOCAL_MACHINE
|
||||
HKEY_USERS
|
||||
HKEY_CURRENT_CONFIG
|
||||
|
||||
// COMCTL32
|
||||
DefSubclassProc
|
||||
RemoveWindowSubclass
|
||||
SetWindowSubclass
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Snap.Hutao.Core.IO.Http.DynamicProxy;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Service;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Windows.Globalization;
|
||||
|
||||
@@ -41,6 +43,7 @@ internal static class DependencyInjection
|
||||
|
||||
serviceProvider.InitializeConsoleWindow();
|
||||
serviceProvider.InitializeCulture();
|
||||
serviceProvider.InitializedDynamicHttpProxy();
|
||||
|
||||
return serviceProvider;
|
||||
}
|
||||
@@ -67,4 +70,9 @@ internal static class DependencyInjection
|
||||
{
|
||||
_ = serviceProvider.GetRequiredService<ConsoleWindowLifeTime>();
|
||||
}
|
||||
|
||||
private static void InitializedDynamicHttpProxy(this IServiceProvider serviceProvider)
|
||||
{
|
||||
HttpClient.DefaultProxy = serviceProvider.GetRequiredService<DynamicHttpProxy>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Win32.Registry;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Http.DynamicProxy;
|
||||
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed partial class DynamicHttpProxy : IWebProxy, IDisposable
|
||||
{
|
||||
private const string ProxySettingPath = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections";
|
||||
|
||||
private static readonly MethodInfo ConstructSystemProxyMethod;
|
||||
|
||||
private readonly RegistryWatcher watcher;
|
||||
|
||||
private IWebProxy innerProxy = default!;
|
||||
|
||||
static DynamicHttpProxy()
|
||||
{
|
||||
Type? systemProxyInfoType = typeof(System.Net.Http.SocketsHttpHandler).Assembly.GetType("System.Net.Http.SystemProxyInfo");
|
||||
ArgumentNullException.ThrowIfNull(systemProxyInfoType);
|
||||
|
||||
MethodInfo? constructSystemProxyMethod = systemProxyInfoType.GetMethod("ConstructSystemProxy", BindingFlags.Static | BindingFlags.Public);
|
||||
ArgumentNullException.ThrowIfNull(constructSystemProxyMethod);
|
||||
ConstructSystemProxyMethod = constructSystemProxyMethod;
|
||||
}
|
||||
|
||||
public DynamicHttpProxy()
|
||||
{
|
||||
UpdateProxy();
|
||||
|
||||
watcher = new(ProxySettingPath, UpdateProxy);
|
||||
watcher.Start();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ICredentials? Credentials
|
||||
{
|
||||
get => InnerProxy.Credentials;
|
||||
set => InnerProxy.Credentials = value;
|
||||
}
|
||||
|
||||
private IWebProxy InnerProxy
|
||||
{
|
||||
get => innerProxy;
|
||||
|
||||
[MemberNotNull(nameof(innerProxy))]
|
||||
set
|
||||
{
|
||||
if (ReferenceEquals(innerProxy, value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
(innerProxy as IDisposable)?.Dispose();
|
||||
innerProxy = value;
|
||||
}
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(innerProxy))]
|
||||
public void UpdateProxy()
|
||||
{
|
||||
IWebProxy? proxy = ConstructSystemProxyMethod.Invoke(default, default) as IWebProxy;
|
||||
ArgumentNullException.ThrowIfNull(proxy);
|
||||
|
||||
InnerProxy = proxy;
|
||||
}
|
||||
|
||||
public Uri? GetProxy(Uri destination)
|
||||
{
|
||||
return InnerProxy.GetProxy(destination);
|
||||
}
|
||||
|
||||
public bool IsBypassed(Uri host)
|
||||
{
|
||||
return InnerProxy.IsBypassed(host);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
(innerProxy as IDisposable)?.Dispose();
|
||||
watcher.Dispose();
|
||||
}
|
||||
}
|
||||
155
src/Snap.Hutao/Snap.Hutao/Win32/Registry/RegistryWatcher.cs
Normal file
155
src/Snap.Hutao/Snap.Hutao/Win32/Registry/RegistryWatcher.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.System.Registry;
|
||||
using static Windows.Win32.PInvoke;
|
||||
|
||||
namespace Snap.Hutao.Win32.Registry;
|
||||
|
||||
internal sealed partial class RegistryWatcher : IDisposable
|
||||
{
|
||||
private const REG_SAM_FLAGS RegSamFlags =
|
||||
REG_SAM_FLAGS.KEY_QUERY_VALUE |
|
||||
REG_SAM_FLAGS.KEY_NOTIFY |
|
||||
REG_SAM_FLAGS.KEY_READ;
|
||||
|
||||
private const REG_NOTIFY_FILTER RegNotifyFilters =
|
||||
REG_NOTIFY_FILTER.REG_NOTIFY_CHANGE_NAME |
|
||||
REG_NOTIFY_FILTER.REG_NOTIFY_CHANGE_ATTRIBUTES |
|
||||
REG_NOTIFY_FILTER.REG_NOTIFY_CHANGE_LAST_SET |
|
||||
REG_NOTIFY_FILTER.REG_NOTIFY_CHANGE_SECURITY;
|
||||
|
||||
private readonly ManualResetEvent disposeEvent = new(false);
|
||||
private readonly CancellationTokenSource cancellationTokenSource = new();
|
||||
|
||||
private readonly HKEY hKey;
|
||||
private readonly string subKey = default!;
|
||||
private readonly Action valueChangedCallback;
|
||||
private readonly object syncRoot = new();
|
||||
private bool disposed;
|
||||
|
||||
public RegistryWatcher(string keyName, Action valueChangedCallback)
|
||||
{
|
||||
string[] pathArray = keyName.Split('\\');
|
||||
|
||||
hKey = pathArray[0] switch
|
||||
{
|
||||
nameof(HKEY.HKEY_CLASSES_ROOT) => HKEY.HKEY_CLASSES_ROOT,
|
||||
nameof(HKEY.HKEY_CURRENT_USER) => HKEY.HKEY_CURRENT_USER,
|
||||
nameof(HKEY.HKEY_LOCAL_MACHINE) => HKEY.HKEY_LOCAL_MACHINE,
|
||||
nameof(HKEY.HKEY_USERS) => HKEY.HKEY_USERS,
|
||||
nameof(HKEY.HKEY_CURRENT_CONFIG) => HKEY.HKEY_CURRENT_CONFIG,
|
||||
_ => throw new ArgumentException("The registry hive '" + pathArray[0] + "' is not supported", nameof(keyName)),
|
||||
};
|
||||
|
||||
subKey = string.Join("\\", pathArray[1..]);
|
||||
this.valueChangedCallback = valueChangedCallback;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(disposed, this);
|
||||
WatchAsync(cancellationTokenSource.Token).SafeForget();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Standard no-reentrancy pattern
|
||||
if (disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (syncRoot)
|
||||
{
|
||||
if (disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// First cancel the outer while loop
|
||||
cancellationTokenSource.Cancel();
|
||||
|
||||
// Then signal the inner while loop to exit
|
||||
disposeEvent.Set();
|
||||
|
||||
// Wait for both loops to exit
|
||||
disposeEvent.WaitOne();
|
||||
|
||||
disposeEvent.Dispose();
|
||||
cancellationTokenSource.Dispose();
|
||||
|
||||
disposed = true;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SH002")]
|
||||
private static unsafe void UnsafeRegOpenKeyEx(HKEY hKey, string subKey, uint ulOptions, REG_SAM_FLAGS samDesired, out HKEY result)
|
||||
{
|
||||
fixed (HKEY* resultPtr = &result)
|
||||
{
|
||||
HRESULT hResult = HRESULT_FROM_WIN32(RegOpenKeyEx(hKey, subKey, ulOptions, samDesired, resultPtr));
|
||||
Marshal.ThrowExceptionForHR(hResult);
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SH002")]
|
||||
private static unsafe void UnsafeRegNotifyChangeKeyValue(HKEY hKey, BOOL bWatchSubtree, REG_NOTIFY_FILTER dwNotifyFilter, HANDLE hEvent, BOOL fAsynchronous)
|
||||
{
|
||||
HRESULT hRESULT = HRESULT_FROM_WIN32(RegNotifyChangeKeyValue(hKey, bWatchSubtree, dwNotifyFilter, hEvent, fAsynchronous));
|
||||
Marshal.ThrowExceptionForHR(hRESULT);
|
||||
}
|
||||
|
||||
private async ValueTask WatchAsync(CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
|
||||
|
||||
UnsafeRegOpenKeyEx(hKey, subKey, 0, RegSamFlags, out HKEY registryKey);
|
||||
|
||||
using (ManualResetEvent notifyEvent = new(false))
|
||||
{
|
||||
HANDLE hEvent = (HANDLE)notifyEvent.SafeWaitHandle.DangerousGetHandle();
|
||||
|
||||
try
|
||||
{
|
||||
// If terminateEvent is signaled, the Dispose method
|
||||
// has been called and the object is shutting down.
|
||||
// The outer token has already canceled, so we can
|
||||
// skip both loops and exit the method.
|
||||
while (!disposeEvent.WaitOne(0, true))
|
||||
{
|
||||
UnsafeRegNotifyChangeKeyValue(registryKey, true, RegNotifyFilters, hEvent, true);
|
||||
|
||||
if (WaitHandle.WaitAny([notifyEvent, disposeEvent]) is 0)
|
||||
{
|
||||
valueChangedCallback();
|
||||
notifyEvent.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
RegCloseKey(registryKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!disposed)
|
||||
{
|
||||
// Before exiting, signal the Dispose method.
|
||||
disposeEvent.Reset();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user