Refactor download (#10534)

* Refactor download

* webapi

* Fallback

* Fix Sub

* Life cycle

* Yet another way of the life cycle

* Shared CookieContainer

* Fix
This commit is contained in:
DHR60
2026-11-08 07:47:24 +00:00
committed by GitHub
parent 6e48e507ea
commit 92a9da8b6b
24 changed files with 976 additions and 822 deletions
+10 -14
View File
@@ -2644,8 +2644,8 @@ public static class ConfigHandler
/// <returns>0 if successful</returns>
public static async Task<int> InitExternalRouting(Config config, bool blImportAdvancedRules = false)
{
var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(config.ConstItem.RouteRulesTemplateSourceUrl, true, "");
using var downloadHandle = await DownloadService.CreateDownloadServiceWithCheck();
var templateContent = await downloadHandle.TryDownloadString(config.ConstItem.RouteRulesTemplateSourceUrl);
if (templateContent.IsNullOrEmpty())
{
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
@@ -2674,7 +2674,7 @@ public static class ConfigHandler
var ruleSetsString = !item.RuleSet.IsNullOrEmpty()
? item.RuleSet
: await downloadHandle.TryDownloadString(item.Url, true, "");
: await downloadHandle.TryDownloadString(item.Url);
if (ruleSetsString.IsNullOrEmpty())
{
@@ -2872,8 +2872,8 @@ public static class ConfigHandler
{
var currentItem = await AppManager.Instance.GetDNSItem(type);
var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(url, true, "");
using var downloadHandle = await DownloadService.CreateDownloadServiceWithCheck();
var templateContent = await downloadHandle.TryDownloadString(url);
if (templateContent.IsNullOrEmpty())
{
return currentItem;
@@ -2887,12 +2887,12 @@ public static class ConfigHandler
if (!template.NormalDNS.IsNullOrEmpty())
{
template.NormalDNS = await downloadHandle.TryDownloadString(template.NormalDNS, true, "");
template.NormalDNS = await downloadHandle.TryDownloadString(template.NormalDNS);
}
if (!template.TunDNS.IsNullOrEmpty())
{
template.TunDNS = await downloadHandle.TryDownloadString(template.TunDNS, true, "");
template.TunDNS = await downloadHandle.TryDownloadString(template.TunDNS);
}
template.Id = currentItem.Id;
@@ -2922,20 +2922,16 @@ public static class ConfigHandler
};
}
public static async Task<SimpleDNSItem> GetExternalSimpleDNSItem(string url)
public static async Task<SimpleDNSItem?> GetExternalSimpleDNSItem(string url)
{
var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(url, true, "");
using var downloadHandle = await DownloadService.CreateDownloadServiceWithCheck();
var templateContent = await downloadHandle.TryDownloadString(url);
if (templateContent.IsNullOrEmpty())
{
return null;
}
var template = JsonUtils.Deserialize<SimpleDNSItem>(templateContent);
if (template == null)
{
return null;
}
return template;
}
+29 -28
View File
@@ -20,9 +20,12 @@ public static class ConnectionHandler
/// </summary>
private static async Task<string?> GetIPInfo()
{
var webProxy = await GetWebProxy();
var ipInfo = await GetIPInfo(webProxy);
using var downloadHandle = new DownloadService
{
Handler = HttpClientHelper.CreateSocketsHttpHandler(true, new()),
DisposeHandler = true,
};
var ipInfo = await GetIPInfo(downloadHandle);
return ipInfo?.ToString() ?? Global.None;
}
@@ -34,11 +37,9 @@ public static class ConnectionHandler
var responseTime = -1;
try
{
var webProxy = await GetWebProxy();
for (var i = 0; i < 2; i++)
{
responseTime = await GetRealPingTime(webProxy);
responseTime = await GetRealPingTime(true);
if (responseTime > 0)
{
break;
@@ -54,19 +55,29 @@ public static class ConnectionHandler
return responseTime;
}
/// <summary>
/// Creates local SOCKS proxy instance.
/// </summary>
private static async Task<WebProxy?> GetWebProxy()
{
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
return new WebProxy($"socks5://{Global.Loopback}:{port}");
}
/// <summary>
/// Measures response time by sending HTTP requests through proxy.
/// </summary>
public static async Task<int> GetRealPingTime(IWebProxy? webProxy, CancellationToken cancellationToken = default)
public static async Task<int> GetRealPingTime(bool useProxy, CancellationToken cancellationToken = default)
{
var responseTime = -1;
try
{
using var client = HttpClientHelper.Instance.GetHttpClient(useProxy);
responseTime = await GetRealPingTime(client, cancellationToken);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch
{
// Ignore
}
return responseTime;
}
public static async Task<int> GetRealPingTime(HttpClient client, CancellationToken cancellationToken = default)
{
var url = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
var responseTime = -1;
@@ -76,12 +87,6 @@ public static class ConnectionHandler
timeoutCts.CancelAfter(Global.LocalFetch);
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
var linkedToken = linkedCts.Token;
using var client = new HttpClient(new SocketsHttpHandler()
{
Proxy = webProxy,
UseProxy = webProxy != null,
ConnectTimeout = Global.LocalFetch,
});
List<int> oneTime = [];
for (var i = 0; i < 2; i++)
@@ -105,10 +110,7 @@ public static class ConnectionHandler
return responseTime;
}
/// <summary>
/// Gets IP and country information through specified proxy.
/// </summary>
public static async Task<IpInfoResult?> GetIPInfo(IWebProxy? webProxy, CancellationToken cancellationToken = default)
public static async Task<IpInfoResult?> GetIPInfo(DownloadService downloadHandle, CancellationToken cancellationToken = default)
{
try
{
@@ -118,8 +120,7 @@ public static class ConnectionHandler
return null;
}
var downloadHandle = new DownloadService();
var result = await downloadHandle.TryDownloadString(url, webProxy, "", cancellationToken);
var result = await downloadHandle.TryDownloadString(url, cancellationToken);
if (result == null)
{
return null;
+183 -126
View File
@@ -1,8 +1,14 @@
namespace ServiceLib.Handler;
public static class SubscriptionHandler
public class SubscriptionHandler(
Config config,
bool blProxy,
Func<bool, string, Task> updateFunc)
{
public static async Task UpdateProcess(Config config, string subId, bool blProxy, Func<bool, string, Task> updateFunc)
private readonly Config _config = config;
private readonly bool _blProxy = blProxy;
public async Task UpdateProcess(string subId)
{
await updateFunc?.Invoke(false, ResUI.MsgUpdateSubscriptionStart);
var subItem = await AppManager.Instance.SubItems();
@@ -24,21 +30,20 @@ public static class SubscriptionHandler
}
var hashCode = $"{item.Remarks}->";
if (item.Enabled == false)
if (!item.Enabled)
{
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSkipSubscriptionUpdate}");
continue;
}
// Create download handler
var downloadHandle = CreateDownloadHandler(item, hashCode, updateFunc);
using var processor = new SubscriptionHandlerProcessor(this, item);
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartGettingSubscriptions}");
// Get all subscription content (main subscription + additional subscriptions)
var result = await DownloadAllSubscriptions(config, item, blProxy, downloadHandle);
var result = await processor.DownloadAllSubscriptions();
// Process download result
if (await ProcessDownloadResult(config, item.Id, result, hashCode, updateFunc))
if (await ProcessDownloadResult(item.Id, result, hashCode))
{
successCount++;
}
@@ -57,7 +62,7 @@ public static class SubscriptionHandler
await updateFunc?.Invoke(successCount > 0, $"{ResUI.MsgUpdateSubscriptionEnd}");
}
private static bool IsValidSubscription(SubItem item, string subId)
private bool IsValidSubscription(SubItem item, string subId)
{
var id = item.Id.TrimEx();
var url = item.Url.TrimEx();
@@ -80,121 +85,7 @@ public static class SubscriptionHandler
return true;
}
private static DownloadService CreateDownloadHandler(SubItem item, string hashCode, Func<bool, string, Task> updateFunc)
{
if (!HttpRequestHeadersHelper.TryParse(item.RequestHeaders, out var requestHeaders))
{
throw new FormatException(ResUI.SubRequestHeadersInvalid);
}
var downloadHandle = new DownloadService
{
AcceptHeader = "*/*",
RequestHeaders = requestHeaders
};
downloadHandle.Error += (sender2, args) =>
{
updateFunc?.Invoke(false, $"{hashCode}{args.GetException().Message}");
};
return downloadHandle;
}
private static async Task<string> DownloadSubscriptionContent(DownloadService downloadHandle, string url, bool blProxy, string userAgent)
{
var result = await downloadHandle.TryDownloadString(url, blProxy, userAgent);
// If download with proxy fails, try direct connection
if (blProxy && result.IsNullOrEmpty())
{
result = await downloadHandle.TryDownloadString(url, false, userAgent);
}
return result ?? string.Empty;
}
private static async Task<string> DownloadAllSubscriptions(Config config, SubItem item, bool blProxy, DownloadService downloadHandle)
{
// Download main subscription content
var result = await DownloadMainSubscription(config, item, blProxy, downloadHandle);
// Process additional subscription links (if any)
if (item.ConvertTarget.IsNullOrEmpty() && item.MoreUrl.TrimEx().IsNotEmpty())
{
result = await DownloadAdditionalSubscriptions(item, result, blProxy, downloadHandle);
}
return result;
}
private static async Task<string> DownloadMainSubscription(Config config, SubItem item, bool blProxy, DownloadService downloadHandle)
{
// Prepare subscription URL and download directly
var url = Utils.GetPunycode(item.Url.TrimEx());
// If conversion is needed
if (item.ConvertTarget.IsNotEmpty())
{
var subConvertUrl = config.ConstItem.SubConvertUrl.IsNullOrEmpty()
? Global.SubConvertUrls.FirstOrDefault()
: config.ConstItem.SubConvertUrl;
url = string.Format(subConvertUrl!, Utils.UrlEncode(url));
if (!url.Contains("target="))
{
url += $"&target={item.ConvertTarget}";
}
if (!url.Contains("config="))
{
url += $"&config={Global.SubConvertConfig.FirstOrDefault()}";
}
}
// Download and return result directly
return await DownloadSubscriptionContent(downloadHandle, url, blProxy, item.UserAgent);
}
private static async Task<string> DownloadAdditionalSubscriptions(SubItem item, string mainResult, bool blProxy, DownloadService downloadHandle)
{
var result = mainResult;
// If main subscription result is Base64 encoded, decode it first
if (result.IsNotEmpty() && Utils.IsBase64String(result))
{
result = Utils.Base64Decode(result);
}
// Process additional URL list
var lstUrl = item.MoreUrl.TrimEx().Split(",") ?? [];
foreach (var it in lstUrl)
{
var url2 = Utils.GetPunycode(it);
if (url2.IsNullOrEmpty())
{
continue;
}
var additionalResult = await DownloadSubscriptionContent(downloadHandle, url2, blProxy, item.UserAgent);
if (additionalResult.IsNotEmpty())
{
// Process additional subscription results, add to main result
if (Utils.IsBase64String(additionalResult))
{
result += Environment.NewLine + Utils.Base64Decode(additionalResult);
}
else
{
result += Environment.NewLine + additionalResult;
}
}
}
return result;
}
private static async Task<bool> ProcessDownloadResult(Config config, string id, string result, string hashCode, Func<bool, string, Task> updateFunc)
private async Task<bool> ProcessDownloadResult(string id, string result, string hashCode)
{
if (result.IsNullOrEmpty())
{
@@ -213,7 +104,7 @@ public static class SubscriptionHandler
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartParsingSubscription}");
// Add servers to configuration
var ret = await ConfigHandler.AddBatchServers(config, result, id, true);
var ret = await ConfigHandler.AddBatchServers(_config, result, id, true);
if (ret <= 0)
{
Logging.SaveLog("FailedImportSubscription");
@@ -222,9 +113,175 @@ public static class SubscriptionHandler
// Update completion message
await updateFunc?.Invoke(false, ret > 0
? $"{hashCode}{ResUI.MsgUpdateSubscriptionEnd}"
: $"{hashCode}{ResUI.MsgFailedImportSubscription}");
? $"{hashCode}{ResUI.MsgUpdateSubscriptionEnd}"
: $"{hashCode}{ResUI.MsgFailedImportSubscription}");
return ret > 0;
}
private class SubscriptionHandlerProcessor : IDisposable
{
private readonly DownloadService[] _downloadServices;
private readonly SubscriptionHandler _handler;
private readonly SubItem _item;
public SubscriptionHandlerProcessor(SubscriptionHandler handler, SubItem item)
{
_handler = handler;
_item = item;
_downloadServices = CreateDownloadHandler();
}
private DownloadService[] CreateDownloadHandler()
{
if (!HttpRequestHeadersHelper.TryParse(_item.RequestHeaders, out var requestHeaders))
{
throw new FormatException(ResUI.SubRequestHeadersInvalid);
}
var userAgent = _item.UserAgent.IsNullOrEmpty() ? Utils.GetVersion(false) : _item.UserAgent;
//var proxyDownloadService = new DownloadService
//{
// GetHandlerFunc = () =>
// HttpRequestHeadersHelper.CreateHandler(HttpClientHelper.Instance.ProxyHandler, userAgent, "*/*",
// requestHeaders),
//};
//var directDownloadService = new DownloadService
//{
// GetHandlerFunc = () =>
// HttpRequestHeadersHelper.CreateHandler(HttpClientHelper.Instance.DirectHandler, userAgent, "*/*",
// requestHeaders),
//};
//return _handler._blProxy
// ? [
// proxyDownloadService,
// directDownloadService,
// ]
// : [
// directDownloadService,
// proxyDownloadService,
// ];
var handler = HttpClientHelper.Instance.CreateFallbackHandler(_handler._blProxy, new());
var headersHandler = HttpRequestHeadersHelper.CreateHandler(handler, userAgent, "*/*", requestHeaders);
return
[
new DownloadService
{
Handler = headersHandler,
DisposeHandler = true,
},
];
}
private async Task<string> DownloadSubscriptionContent(string url)
{
//var result = await downloadServices[0].TryDownloadString(url);
//// try other ways
//if (result.IsNullOrEmpty())
//{
// result = await downloadServices[1].TryDownloadString(url);
//}
foreach (var downloadService in _downloadServices)
{
var result = await downloadService.TryDownloadString(url);
if (!result.IsNullOrEmpty())
{
return result;
}
}
return string.Empty;
}
public async Task<string> DownloadAllSubscriptions()
{
// Download main subscription content
var result = await DownloadMainSubscription();
// Process additional subscription links (if any)
if (_item.ConvertTarget.IsNullOrEmpty() && _item.MoreUrl.TrimEx().IsNotEmpty())
{
result = await DownloadAdditionalSubscriptions(result);
}
return result;
}
private async Task<string> DownloadMainSubscription()
{
// Prepare subscription URL and download directly
var url = Utils.GetPunycode(_item.Url.TrimEx());
// If conversion is needed
if (_item.ConvertTarget.IsNotEmpty())
{
var subConvertUrl = _handler._config.ConstItem.SubConvertUrl.IsNullOrEmpty()
? Global.SubConvertUrls.FirstOrDefault()
: _handler._config.ConstItem.SubConvertUrl;
url = string.Format(subConvertUrl!, Utils.UrlEncode(url));
if (!url.Contains("target="))
{
url += $"&target={_item.ConvertTarget}";
}
if (!url.Contains("config="))
{
url += $"&config={Global.SubConvertConfig.FirstOrDefault()}";
}
}
// Download and return result directly
return await DownloadSubscriptionContent(url);
}
private async Task<string> DownloadAdditionalSubscriptions(string mainResult)
{
var result = mainResult;
// If main subscription result is Base64 encoded, decode it first
if (result.IsNotEmpty() && Utils.IsBase64String(result))
{
result = Utils.Base64Decode(result);
}
// Process additional URL list
var lstUrl = _item.MoreUrl.TrimEx().Split(",") ?? [];
foreach (var it in lstUrl)
{
var url2 = Utils.GetPunycode(it);
if (url2.IsNullOrEmpty())
{
continue;
}
var additionalResult = await DownloadSubscriptionContent(url2);
if (additionalResult.IsNotEmpty())
{
// Process additional subscription results, add to main result
if (Utils.IsBase64String(additionalResult))
{
result += Environment.NewLine + Utils.Base64Decode(additionalResult);
}
else
{
result += Environment.NewLine + additionalResult;
}
}
}
return result;
}
public void Dispose()
{
foreach (var downloadService in _downloadServices)
{
downloadService.Dispose();
}
}
}
}
+75 -158
View File
@@ -1,45 +1,41 @@
using System.Security.Authentication;
using Downloader;
using DownloadService = Downloader.DownloadService;
namespace ServiceLib.Helper;
public class DownloaderHelper
public class DownloaderHelper : IDisposable
{
private static readonly Lazy<DownloaderHelper> _instance = new(() => new());
public static DownloaderHelper Instance => _instance.Value;
public HttpMessageHandler Handler { get; init; } = HttpClientHelper.Instance.DirectHandler;
public async Task<string?> DownloadStringAsync(IWebProxy? webProxy, string url, string? userAgent,
IReadOnlyDictionary<string, string>? requestHeaders = null, string? acceptHeader = null, CancellationToken cancellationToken = default)
public bool DisposeHandler { get; init; } = false;
public async Task<string?> DownloadStringAsync(string url,
CancellationToken cancellationToken = default)
{
if (url.IsNullOrEmpty())
{
return null;
}
Uri uri = new(url);
//Authorization Header
var headers = new WebHeaderCollection();
var uri = new Uri(url);
if (uri.UserInfo.IsNotEmpty())
{
headers.Add(HttpRequestHeader.Authorization, "Basic " + Utils.Base64Encode(uri.UserInfo));
}
var requestConfiguration = new RequestConfiguration()
var requestConfiguration = new RequestConfiguration
{
Headers = headers,
Accept = acceptHeader,
UserAgent = userAgent,
ConnectTimeout = GetConnectTimeoutMs(webProxy != null),
Proxy = webProxy
};
var downloadOpt = new DownloadConfiguration()
var downloadOpt = new DownloadConfiguration
{
MaxTryAgainOnFailure = 2,
RequestConfiguration = requestConfiguration,
CustomHttpMessageHandlerFactory = () => HttpRequestHeadersHelper.CreateHandler(GetSocketsHttpHandler(requestConfiguration), requestHeaders),
CustomHttpMessageHandlerFactory = GetHandler,
};
await using var downloader = new Downloader.DownloadService(downloadOpt);
await using var downloader = new DownloadService(downloadOpt);
downloader.DownloadFileCompleted += (sender, value) =>
{
if (value.Error != null)
@@ -48,35 +44,30 @@ public class DownloaderHelper
}
};
await using var stream = await downloader.DownloadFileTaskAsync(address: url, cancellationToken);
await using var stream = await downloader.DownloadFileTaskAsync(url, cancellationToken);
using StreamReader reader = new(stream);
return await reader.ReadToEndAsync(cancellationToken);
}
public async Task DownloadDataAsync4Speed(IWebProxy webProxy, string url, Action<string> onProgress, CancellationToken cancellationToken = default)
public async Task DownloadDataAsync4Speed(string url, Action<string> onProgress,
CancellationToken cancellationToken = default)
{
if (url.IsNullOrEmpty())
{
throw new ArgumentNullException(nameof(url));
}
var requestConfiguration = new RequestConfiguration()
{
ConnectTimeout = GetConnectTimeoutMs(true),
Proxy = webProxy
};
var downloadOpt = new DownloadConfiguration()
var downloadOpt = new DownloadConfiguration
{
MaxTryAgainOnFailure = 2,
RequestConfiguration = requestConfiguration,
CustomHttpMessageHandlerFactory = () => GetSocketsHttpHandler(requestConfiguration),
CustomHttpMessageHandlerFactory = GetHandler,
};
var lastUpdateTime = DateTime.Now;
var hasValue = false;
double maxSpeed = 0;
await using var downloader = new Downloader.DownloadService(downloadOpt);
await using var downloader = new DownloadService(downloadOpt);
downloader.DownloadProgressChanged += (sender, value) =>
{
@@ -114,11 +105,12 @@ public class DownloaderHelper
onProgress.Invoke("0");
}
};
//progress.Invoke("......");
await using var stream = await downloader.DownloadFileTaskAsync(address: url, cancellationToken);
// progress.Invoke("......");
await using var stream = await downloader.DownloadFileTaskAsync(url, cancellationToken);
}
public async Task DownloadFileAsync(IWebProxy? webProxy, FileDownloadRequest request, Action<FileDownloadState> onProgress, CancellationToken cancellationToken = default)
public async Task DownloadFileAsync(FileDownloadRequest request, Action<FileDownloadState> onProgress,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
if (request.FilePath.IsNullOrEmpty())
@@ -135,25 +127,18 @@ public class DownloaderHelper
Request = request,
};
var requestConfiguration = new RequestConfiguration
{
ConnectTimeout = GetConnectTimeoutMs(webProxy != null),
Proxy = webProxy,
};
var downloadOpt = new DownloadConfiguration()
var downloadOpt = new DownloadConfiguration
{
ChunkCount = 16,
ParallelDownload = true,
ParallelCount = 4,
MinimumChunkSize = 2 * 1024 * 1024, // 2 MB
MinimumSizeOfChunking = 4 * 1024 * 1024, // 4 MB
RequestConfiguration = requestConfiguration,
CustomHttpMessageHandlerFactory = () => GetSocketsHttpHandler(requestConfiguration),
CustomHttpMessageHandlerFactory = GetHandler,
};
await using var downloader = new Downloader.DownloadService(downloadOpt);
await using var downloader = new DownloadService(downloadOpt);
downloader.DownloadStarted += (sender, value) =>
{
state = state with
@@ -185,7 +170,8 @@ public class DownloaderHelper
await downloader.DownloadFileTaskAsync(request.FileUrl, request.FilePath, cancellationToken);
}
public async Task DownloadSmallFilesAsync(IWebProxy? webProxy, List<FileDownloadRequest> requests, Action<ReadOnlyMemory<FileDownloadState>> onProgress, CancellationToken cancellationToken = default)
public async Task DownloadSmallFilesAsync(List<FileDownloadRequest> requests,
Action<ReadOnlyMemory<FileDownloadState>> onProgress, CancellationToken cancellationToken = default)
{
if (requests is not { Count: > 0 })
{
@@ -202,140 +188,71 @@ public class DownloaderHelper
}
var readOnlyStates = new ReadOnlyMemory<FileDownloadState>(states);
var requestConfiguration = new RequestConfiguration()
{
ConnectTimeout = GetConnectTimeoutMs(webProxy != null),
Proxy = webProxy,
KeepAlive = true,
};
using var socketsHttpHandler = GetSocketsHttpHandler(requestConfiguration);
var parallelOptions = new ParallelOptions
{
MaxDegreeOfParallelism = 4,
CancellationToken = cancellationToken,
};
await Parallel.ForEachAsync(Enumerable.Range(0, requests.Count), parallelOptions, async (index, parallelCancellationToken) =>
{
var request = requests[index];
var downloadOpt = new DownloadConfiguration()
await Parallel.ForEachAsync(Enumerable.Range(0, requests.Count), parallelOptions,
async (index, parallelCancellationToken) =>
{
RequestConfiguration = requestConfiguration,
// ReSharper disable once AccessToDisposedClosure
CustomHttpMessageHandlerFactory = () => socketsHttpHandler,
};
await using var downloader = new Downloader.DownloadService(downloadOpt);
downloader.DownloadStarted += (sender, value) =>
{
states[index] = states[index] with
var request = requests[index];
var downloadOpt = new DownloadConfiguration
{
DownloadedBytes = 0,
TotalBytes = value.TotalBytesToReceive,
SpeedBytesPerSecond = 0,
Completed = false,
CustomHttpMessageHandlerFactory = GetHandler,
};
onProgress.Invoke(readOnlyStates);
};
downloader.DownloadProgressChanged += (sender, value) =>
{
states[index] = states[index] with
await using var downloader = new DownloadService(downloadOpt);
downloader.DownloadStarted += (sender, value) =>
{
DownloadedBytes = value.ReceivedBytesSize,
TotalBytes = value.TotalBytesToReceive,
SpeedBytesPerSecond = value.BytesPerSecondSpeed,
Completed = false,
states[index] = states[index] with
{
DownloadedBytes = 0,
TotalBytes = value.TotalBytesToReceive,
SpeedBytesPerSecond = 0,
Completed = false,
};
onProgress.Invoke(readOnlyStates);
};
onProgress.Invoke(readOnlyStates);
};
downloader.DownloadFileCompleted += (sender, value) =>
{
var newState = states[index] with { Completed = true };
if (value.Error != null)
downloader.DownloadProgressChanged += (sender, value) =>
{
newState = newState with { Error = value.Error };
}
states[index] = newState;
onProgress.Invoke(readOnlyStates);
};
await downloader.DownloadFileTaskAsync(request.FileUrl, request.FilePath, parallelCancellationToken);
});
states[index] = states[index] with
{
DownloadedBytes = value.ReceivedBytesSize,
TotalBytes = value.TotalBytesToReceive,
SpeedBytesPerSecond = value.BytesPerSecondSpeed,
Completed = false,
};
onProgress.Invoke(readOnlyStates);
};
downloader.DownloadFileCompleted += (sender, value) =>
{
var newState = states[index] with { Completed = true };
if (value.Error != null)
{
newState = newState with { Error = value.Error };
}
states[index] = newState;
onProgress.Invoke(readOnlyStates);
};
await downloader.DownloadFileTaskAsync(request.FileUrl, request.FilePath, parallelCancellationToken);
});
}
// https://github.com/bezzad/Downloader/blob/a75a6e431acd6cbba6293f7afdcf676544a09174/src/Downloader/SocketClient.cs#L45
// There is a risk of MITM attacks
// https://github.com/bezzad/Downloader/blob/a75a6e431acd6cbba6293f7afdcf676544a09174/src/Downloader/Extensions/ExceptionHelper.cs#L111
private static SocketsHttpHandler GetSocketsHttpHandler(RequestConfiguration config)
private HttpMessageHandler GetHandler()
{
SocketsHttpHandler handler = new()
{
AllowAutoRedirect = config.AllowAutoRedirect,
MaxAutomaticRedirections = config.MaximumAutomaticRedirections,
AutomaticDecompression = config.AutomaticDecompression,
PreAuthenticate = config.PreAuthenticate,
UseCookies = config.CookieContainer != null,
UseProxy = config.Proxy != null,
MaxConnectionsPerServer = 1000,
PooledConnectionIdleTimeout = config.KeepAliveTimeout,
PooledConnectionLifetime = Timeout.InfiniteTimeSpan,
EnableMultipleHttp2Connections = true,
ConnectTimeout = TimeSpan.FromMilliseconds(config.ConnectTimeout),
};
// Set up the SslClientAuthenticationOptions for custom certificate validation
if (config.ClientCertificates?.Count > 0)
{
handler.SslOptions.ClientCertificates = config.ClientCertificates;
}
handler.SslOptions.EnabledSslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12;
//handler.SslOptions.RemoteCertificateValidationCallback = ExceptionHelper.CertificateValidationCallBack;
var certificateChainPolicy = CertPemManager.Instance.BuildCertificateChainPolicy();
if (certificateChainPolicy != null)
{
handler.SslOptions.CertificateChainPolicy = certificateChainPolicy;
handler.SslOptions.RemoteCertificateValidationCallback = null;
}
// Configure keep-alive
if (config.KeepAlive)
{
handler.KeepAlivePingTimeout = config.KeepAliveTimeout;
handler.KeepAlivePingPolicy = HttpKeepAlivePingPolicy.WithActiveRequests;
}
// Configure credentials
if (config.Credentials != null)
{
handler.Credentials = config.Credentials;
handler.PreAuthenticate = config.PreAuthenticate;
}
// Configure cookies
if (handler.UseCookies && config.CookieContainer != null)
{
handler.CookieContainer = config.CookieContainer;
}
// Configure proxy
if (handler.UseProxy && config.Proxy != null)
{
handler.Proxy = config.Proxy;
}
// Add expect header
if (!string.IsNullOrWhiteSpace(config.Expect))
{
handler.Expect100ContinueTimeout = TimeSpan.FromSeconds(1);
}
return handler;
return Handler;
}
private int GetConnectTimeoutMs(bool isProxy)
public void Dispose()
{
return (int)(isProxy ? Global.ProxyDownloadConnect : Global.DirectDownloadConnect).TotalMilliseconds;
if (!DisposeHandler)
{
return;
}
if (Handler is IDisposable disposableHandler)
{
disposableHandler.Dispose();
}
}
}
+210 -46
View File
@@ -1,78 +1,242 @@
using System.Net.Http.Headers;
using System.Net.Mime;
namespace ServiceLib.Helper;
/// <summary>
/// </summary>
public class HttpClientHelper
{
private static readonly Lazy<HttpClientHelper> _instance = new(() =>
{
SocketsHttpHandler handler = new() { UseCookies = false };
HttpClientHelper helper = new(new HttpClient(handler));
return helper;
});
private static readonly Lazy<HttpClientHelper> _instance = new(() => new());
// private readonly SocketsHttpHandler _directHandler = CreateSocketsHttpHandler(false);
private readonly Lazy<SocketsHttpHandler> _directHandlerLazy = new(() => CreateSocketsHttpHandler(false));
private Uri _proxyUri = null!;
public static HttpClientHelper Instance => _instance.Value;
private readonly HttpClient httpClient;
private HttpClientHelper(HttpClient httpClient)
public HttpMessageHandler DirectHandler => new SharedHandlerAdapter(_directHandlerLazy.Value);
public HttpMessageHandler ProxyHandler
{
this.httpClient = httpClient;
get
{
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
var proxyUri = new Uri($"socks5://{Global.Loopback}:{port}");
if (_proxyUri != proxyUri)
{
//field?.Dispose();
_proxyUri = proxyUri;
field = CreateProxySocketsHttpHandler(port);
}
return new SharedHandlerAdapter(field);
}
} = null!;
public HttpMessageHandler GetHandler(bool useProxy)
{
return useProxy ? ProxyHandler : DirectHandler;
}
public async Task<string?> TryGetAsync(string url, CancellationToken cancellationToken = default)
public HttpClient GetHttpClient(bool useProxy)
{
if (url.IsNullOrEmpty())
var handler = GetHandler(useProxy);
var client = new HttpClient(handler);
return client;
}
public static SocketsHttpHandler CreateSocketsHttpHandler(bool useProxy)
{
SocketsHttpHandler handler;
if (!useProxy)
{
return null;
handler = new()
{
UseProxy = false,
UseCookies = false,
ConnectTimeout = Global.DirectDownloadConnect,
};
}
else
{
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
handler = CreateProxySocketsHttpHandler(port);
}
var certificateChainPolicy = CertPemManager.Instance.BuildCertificateChainPolicy();
if (certificateChainPolicy != null)
{
handler.SslOptions.CertificateChainPolicy = certificateChainPolicy;
handler.SslOptions.RemoteCertificateValidationCallback = null;
}
return handler;
}
public static SocketsHttpHandler CreateSocketsHttpHandler(bool useProxy, CookieContainer cookieContainer)
{
var handler = CreateSocketsHttpHandler(useProxy);
handler.UseCookies = true;
handler.CookieContainer = cookieContainer;
return handler;
}
public static SocketsHttpHandler CreateProxySocketsHttpHandler(IWebProxy proxy)
{
var handler = new SocketsHttpHandler
{
UseCookies = false,
Proxy = proxy,
ConnectTimeout = Global.ProxyDownloadConnect,
};
var certificateChainPolicy = CertPemManager.Instance.BuildCertificateChainPolicy();
if (certificateChainPolicy != null)
{
handler.SslOptions.CertificateChainPolicy = certificateChainPolicy;
handler.SslOptions.RemoteCertificateValidationCallback = null;
}
return handler;
}
public static SocketsHttpHandler CreateProxySocketsHttpHandler(int port)
{
return CreateProxySocketsHttpHandler(new WebProxy($"socks5://{Global.Loopback}:{port}"));
}
public static SocketsHttpHandler CreateProxySocketsHttpHandler(IWebProxy proxy, CookieContainer cookieContainer)
{
var handler = CreateProxySocketsHttpHandler(proxy);
handler.UseCookies = true;
handler.CookieContainer = cookieContainer;
return handler;
}
public static SocketsHttpHandler CreateProxySocketsHttpHandler(int port, CookieContainer cookieContainer)
{
var handler = CreateProxySocketsHttpHandler(port);
handler.UseCookies = true;
handler.CookieContainer = cookieContainer;
return handler;
}
public HttpMessageHandler CreateFallbackHandler(bool preferProxy)
{
var primaryHandler = preferProxy ? ProxyHandler : DirectHandler;
var fallbackHandler = preferProxy ? DirectHandler : ProxyHandler;
return new DualHandlerFallbackHandler(primaryHandler, fallbackHandler);
}
public HttpMessageHandler CreateFallbackHandler(bool preferProxy, CookieContainer cookieContainer)
{
var primaryHandler = preferProxy ? CreateSocketsHttpHandler(true, cookieContainer) : CreateSocketsHttpHandler(false, cookieContainer);
var fallbackHandler = preferProxy ? CreateSocketsHttpHandler(false, cookieContainer) : CreateSocketsHttpHandler(true, cookieContainer);
return new DualHandlerFallbackHandler(primaryHandler, fallbackHandler);
}
private class DualHandlerFallbackHandler(
HttpMessageHandler primaryHandler,
HttpMessageHandler fallbackHandler)
: DelegatingHandler(primaryHandler)
{
private readonly HttpMessageInvoker _fallbackInvoker = new(fallbackHandler);
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var contentBytes = await BufferContentAsync(request, cancellationToken);
try
{
var response = await base.SendAsync(request, cancellationToken);
if (IsRetriableStatusCode(response.StatusCode))
{
response.Dispose();
ResetRequestContent(request, contentBytes);
return await _fallbackInvoker.SendAsync(request, cancellationToken);
}
return response;
}
catch (Exception ex) when (IsNetworkOrTimeoutException(ex) && !cancellationToken.IsCancellationRequested)
{
ResetRequestContent(request, contentBytes);
return await _fallbackInvoker.SendAsync(request, cancellationToken);
}
}
try
private static async Task<byte[]?> BufferContentAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await httpClient.GetAsync(url, cancellationToken);
return await response.Content.ReadAsStringAsync(cancellationToken);
if (request.Content == null)
{
return null;
}
return await request.Content.ReadAsByteArrayAsync(cancellationToken);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
private static void ResetRequestContent(HttpRequestMessage request, byte[]? contentBytes)
{
throw;
if (contentBytes == null)
{
return;
}
var newContent = new ByteArrayContent(contentBytes);
if (request.Content != null)
{
foreach (var header in request.Content.Headers)
{
newContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
}
request.Content = newContent;
}
catch
private static bool IsNetworkOrTimeoutException(Exception ex)
{
return null;
return ex is HttpRequestException or TaskCanceledException;
}
private static bool IsRetriableStatusCode(HttpStatusCode statusCode) =>
statusCode is HttpStatusCode.BadGateway // 502
or HttpStatusCode.ServiceUnavailable // 503
or HttpStatusCode.GatewayTimeout; // 504
protected override void Dispose(bool disposing)
{
if (disposing)
{
_fallbackInvoker.Dispose();
}
base.Dispose(disposing);
}
}
public async Task<string?> GetAsync(string url)
public class SharedHandlerAdapter(HttpMessageHandler sharedHandler) : HttpMessageHandler
{
if (url.IsNullOrEmpty())
private readonly HttpMessageInvoker _invoker = new(
sharedHandler,
disposeHandler: false);
protected override HttpResponseMessage Send(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
return null;
return _invoker.Send(request, cancellationToken);
}
return await httpClient.GetStringAsync(url);
}
public async Task PutAsync(string url, Dictionary<string, string> headers)
{
var jsonContent = JsonUtils.Serialize(headers);
var content = new StringContent(jsonContent, Encoding.UTF8, MediaTypeNames.Application.Json);
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
return _invoker.SendAsync(request, cancellationToken);
}
await httpClient.PutAsync(url, content);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_invoker.Dispose();
}
public async Task PatchAsync(string url, Dictionary<string, string> headers)
{
var myContent = JsonUtils.Serialize(headers);
var buffer = Encoding.UTF8.GetBytes(myContent);
var byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
await httpClient.PatchAsync(url, byteContent);
}
public async Task DeleteAsync(string url)
{
await httpClient.DeleteAsync(url);
base.Dispose(disposing);
}
}
}
@@ -39,11 +39,31 @@ public static class HttpRequestHeadersHelper
}
}
public static HttpMessageHandler CreateHandler(HttpMessageHandler innerHandler, IReadOnlyDictionary<string, string>? headers)
public static HttpMessageHandler CreateHandler(HttpMessageHandler innerHandler,
IReadOnlyDictionary<string, string>? headers)
{
return headers is { Count: > 0 } ? new RequestHeadersHandler(innerHandler, headers) : innerHandler;
}
public static HttpMessageHandler CreateHandler(HttpMessageHandler innerHandler,
string userAgent, string acceptHeader,
IReadOnlyDictionary<string, string>? headers)
{
var allHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
allHeaders["User-Agent"] = userAgent;
allHeaders["Accept"] = acceptHeader;
if (headers is { Count: > 0 })
{
foreach (var header in headers)
{
allHeaders[header.Key] = header.Value;
}
}
return new RequestHeadersHandler(innerHandler, allHeaders);
}
private static bool TryAddHeader(HttpRequestMessage request, string name, string value)
{
if (value.Any(c => char.IsControl(c) && c != '\t'))
@@ -52,13 +72,16 @@ public static class HttpRequestHeadersHelper
}
return request.Headers.TryAddWithoutValidation(name, value)
|| request.Content!.Headers.TryAddWithoutValidation(name, value);
|| request.Content!.Headers.TryAddWithoutValidation(name, value);
}
private sealed class RequestHeadersHandler(HttpMessageHandler innerHandler, IReadOnlyDictionary<string, string> headers)
private sealed class RequestHeadersHandler(
HttpMessageHandler innerHandler,
IReadOnlyDictionary<string, string> headers)
: DelegatingHandler(innerHandler)
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
using var customHeaders = new HttpRequestMessage { Content = new ByteArrayContent([]) };
foreach (var header in headers)
+82 -33
View File
@@ -1,3 +1,5 @@
using System.Net.Http.Headers;
namespace ServiceLib.Manager;
public sealed class ClashApiManager
@@ -10,20 +12,30 @@ public sealed class ClashApiManager
public async Task<ClashItem?> GetProxies()
{
using var httpClient = HttpClientHelper.Instance.GetHttpClient(false);
for (var i = 0; i < 3; i++)
{
var url = $"{ApiUrl}/proxies";
var resultTask = HttpClientHelper.Instance.TryGetAsync(url);
ClashProxies? clashProxies = null;
ClashProviders? clashProviders = null;
try
{
var url = $"{ApiUrl}/proxies";
var resultTask = httpClient.GetStringAsync(url);
var url2 = $"{ApiUrl}/providers/proxies";
var result2Task = HttpClientHelper.Instance.TryGetAsync(url2);
var url2 = $"{ApiUrl}/providers/proxies";
var result2Task = httpClient.GetStringAsync(url2);
await Task.WhenAll(resultTask, result2Task);
await Task.WhenAll(resultTask, result2Task);
var result = await resultTask;
var result2 = await result2Task;
var clashProxies = JsonUtils.Deserialize<ClashProxies>(result);
var clashProviders = JsonUtils.Deserialize<ClashProviders>(result2);
var result = await resultTask;
var result2 = await result2Task;
clashProxies = JsonUtils.Deserialize<ClashProxies>(result);
clashProviders = JsonUtils.Deserialize<ClashProviders>(result2);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
if (clashProxies is null && clashProviders is null)
{
@@ -36,7 +48,7 @@ public sealed class ClashApiManager
{
item = item with { Proxies = proxies };
}
if (clashProviders.providers is { Count: > 0 } providers)
if (clashProviders?.providers is { Count: > 0 } providers)
{
foreach (var provider in providers)
{
@@ -84,22 +96,36 @@ public sealed class ClashApiManager
private async Task<int> SendTestRequest(string url)
{
var result = await HttpClientHelper.Instance.TryGetAsync(url);
var jsonObject = JsonUtils.ParseJson(result) as JsonObject;
return jsonObject?["delay"] is { } n && n.GetValueKind() == JsonValueKind.Number &&
n.GetValue<JsonElement>().TryGetInt32(out var v)
? v
: -1;
try
{
using var httpClient = HttpClientHelper.Instance.GetHttpClient(false);
var result = await httpClient.GetStringAsync(url);
var jsonObject = JsonUtils.ParseJson(result) as JsonObject;
return jsonObject?["delay"] is { } n && n.GetValueKind() == JsonValueKind.Number &&
n.GetValue<JsonElement>().TryGetInt32(out var v)
? v
: -1;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return -1;
}
public async Task SetActiveProxy(string groupName, string nodeName)
{
try
{
using var httpClient = HttpClientHelper.Instance.GetHttpClient(false);
var url = $"{ApiUrl}/proxies/{Utils.UrlEncode(groupName)}";
var headers = new Dictionary<string, string>();
headers.Add("name", nodeName);
await HttpClientHelper.Instance.PutAsync(url, headers);
var jsonObject = new JsonObject
{
["name"] = nodeName,
};
var jsonString = JsonUtils.Serialize(jsonObject);
var content = new StringContent(jsonString, Encoding.UTF8, "application/json");
await httpClient.PutAsync(url, content);
}
catch (Exception ex)
{
@@ -109,18 +135,30 @@ public sealed class ClashApiManager
public async Task UpdateClashMode(string mode)
{
var headers = new Dictionary<string, string>
var jsonObject = new JsonObject
{
{ "mode", mode },
["mode"] = mode,
};
await UpdateConfig(headers);
await UpdateConfig(jsonObject);
}
public async Task UpdateConfig(Dictionary<string, string> headers)
public async Task UpdateConfig(JsonObject jsonObject)
{
var urlBase = $"{ApiUrl}/configs";
try
{
var urlBase = $"{ApiUrl}/configs";
await HttpClientHelper.Instance.PatchAsync(urlBase, headers);
using var httpClient = HttpClientHelper.Instance.GetHttpClient(false);
var jsonString = JsonUtils.Serialize(jsonObject);
var buffer = Encoding.UTF8.GetBytes(jsonString);
var byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
await httpClient.PatchAsync(urlBase, byteContent);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
public async Task<List<string>> GetClashModes()
@@ -153,14 +191,23 @@ public sealed class ClashApiManager
public async Task<JsonObject> GetConfig()
{
var url = $"{ApiUrl}/configs";
var result = await HttpClientHelper.Instance.TryGetAsync(url);
var jsonNode = JsonUtils.ParseJson(result);
if (jsonNode is not JsonObject jsonObject)
try
{
return new JsonObject();
var url = $"{ApiUrl}/configs";
using var httpClient = HttpClientHelper.Instance.GetHttpClient(false);
var result = await httpClient.GetStringAsync(url);
var jsonNode = JsonUtils.ParseJson(result);
if (jsonNode is not JsonObject jsonObject)
{
return new JsonObject();
}
return jsonObject;
}
return jsonObject;
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return new JsonObject();
}
public async Task<ClashConnections?> GetConnections()
@@ -168,7 +215,8 @@ public sealed class ClashApiManager
try
{
var url = $"{ApiUrl}/connections";
var result = await HttpClientHelper.Instance.TryGetAsync(url);
using var httpClient = HttpClientHelper.Instance.GetHttpClient(false);
var result = await httpClient.GetStringAsync(url);
var clashConnections = JsonUtils.Deserialize<ClashConnections>(result);
return clashConnections;
@@ -186,7 +234,8 @@ public sealed class ClashApiManager
try
{
var url = $"{ApiUrl}/connections/{id}";
await HttpClientHelper.Instance.DeleteAsync(url);
using var httpClient = HttpClientHelper.Instance.GetHttpClient(false);
await httpClient.DeleteAsync(url);
}
catch (Exception ex)
{
@@ -38,7 +38,7 @@ public class EndpointResolveManager
case "doh" or "https":
{
var uri = new Uri($"https://{domain}:{(port != 0 ? port : 443)}{path}");
var httpClient = new HttpClient();
var httpClient = new HttpClient(HttpClientHelper.Instance.CreateFallbackHandler(false));
return new DohDnsResolver(httpClient, uri);
}
+7 -4
View File
@@ -101,7 +101,7 @@ public class TaskManager
foreach (var item in lstSubs)
{
await SubscriptionHandler.UpdateProcess(_config, item.Id, true, async (success, msg) =>
var handler = new SubscriptionHandler(_config, true, async (success, msg) =>
{
await _updateFunc?.Invoke(success, msg);
if (success)
@@ -109,6 +109,7 @@ public class TaskManager
Logging.SaveLog($"Update subscription end. {msg}");
}
});
await handler.UpdateProcess(item.Id);
item.UpdateTime = updateTime;
await ConfigHandler.AddSubItem(_config, item);
await Task.Delay(1000);
@@ -121,7 +122,8 @@ public class TaskManager
{
Logging.SaveLog("Execute update geo files");
await new UpdateService(_config, async (success, msg) =>
using var downloadService = await DownloadService.CreateDownloadServiceWithCheck(_config.CheckUpdateItem.UpdateViaProxy);
await new UpdateService(downloadService, _config, async (success, msg) =>
{
await _updateFunc?.Invoke(false, msg);
}).UpdateGeoFileAll();
@@ -132,9 +134,10 @@ public class TaskManager
{
Logging.SaveLog("Execute check update");
var updateService = new UpdateService(_config, async (success, msg) => await Task.CompletedTask);
using var downloadService = await DownloadService.CreateDownloadServiceWithCheck(_config.CheckUpdateItem.UpdateViaProxy);
var updateService = new UpdateService(downloadService, _config, async (success, msg) => await Task.CompletedTask);
var msgs = await updateService.CheckHasUpdateOnlyAll(_config.CheckUpdateItem.UpdateViaProxy);
var msgs = await updateService.CheckHasUpdateOnlyAll();
foreach (var msg in msgs)
{
await _updateFunc?.Invoke(false, msg);
+113 -99
View File
@@ -1,33 +1,73 @@
using System.Net.Http.Headers;
namespace ServiceLib.Services;
/// <summary>
/// Download
/// Download
/// </summary>
public class DownloadService
public class DownloadService : IDisposable
{
private static readonly string _tag = "DownloadService";
public HttpMessageHandler Handler { get; init; } = HttpClientHelper.Instance.DirectHandler;
public bool DisposeHandler { get; init; } = false;
public DownloaderHelper DownloaderHelper
{
get
{
field ??= new DownloaderHelper
{
Handler = Handler,
DisposeHandler = false,
};
return field;
}
}
public event EventHandler<UpdateResult>? UpdateCompleted;
public event ErrorEventHandler? Error;
public string? AcceptHeader { get; init; }
public static async Task<DownloadService> CreateDownloadServiceWithCheck(bool preferProxy = true)
{
var webProxy = await CheckProxy(preferProxy);
var cookieContainer = new CookieContainer();
if (webProxy is not null)
{
return new DownloadService
{
Handler = HttpClientHelper.CreateProxySocketsHttpHandler(webProxy, cookieContainer),
DisposeHandler = true,
};
}
return new DownloadService
{
Handler = HttpClientHelper.CreateSocketsHttpHandler(false, cookieContainer),
DisposeHandler = true,
};
}
public IReadOnlyDictionary<string, string>? RequestHeaders { get; init; }
private static readonly string _tag = "DownloadService";
public static DownloadService CreateDownloadServiceWithFallback(bool preferProxy = true)
{
return new DownloadService
{
Handler = HttpClientHelper.Instance.CreateFallbackHandler(preferProxy, new()),
DisposeHandler = true,
};
}
/// <summary>
/// Downloads data with the specified proxy and reports progress messages.
/// Downloads data with the specified proxy and reports progress messages.
/// </summary>
public async Task<int> DownloadDataAsync(string url, IWebProxy webProxy, Func<bool, string, Task> updateFunc, CancellationToken cancellationToken = default)
public async Task<int> DownloadDataAsync(string url, Func<bool, string, Task> updateFunc,
CancellationToken cancellationToken = default)
{
try
{
await DownloaderHelper.Instance.DownloadDataAsync4Speed(webProxy,
url,
OnProgress,
cancellationToken);
await DownloaderHelper.DownloadDataAsync4Speed(
url,
OnProgress,
cancellationToken);
void OnProgress(string message)
{
@@ -51,16 +91,15 @@ public class DownloadService
}
/// <summary>
/// Downloads a file and reports progress through events.
/// Downloads a file and reports progress through events.
/// </summary>
public async Task DownloadFileAsync(FileDownloadRequest request, bool blProxy, CancellationToken cancellationToken = default)
public async Task DownloadFileAsync(FileDownloadRequest request, CancellationToken cancellationToken = default)
{
try
{
UpdateCompleted?.Invoke(this, new UpdateResult(false, $"{ResUI.Downloading} {request.FileUrl}"));
var webProxy = await GetWebProxy(blProxy, cancellationToken);
await DownloaderHelper.Instance.DownloadFileAsync(webProxy,
await DownloaderHelper.DownloadFileAsync(
request,
OnProgress,
cancellationToken);
@@ -68,7 +107,9 @@ public class DownloadService
void OnProgress(FileDownloadState state)
{
cancellationToken.ThrowIfCancellationRequested();
UpdateCompleted?.Invoke(this, new UpdateResult(state.Completed, $"{Utils.HumanFy((long)state.SpeedBytesPerSecond / 1024)}/s | {Utils.HumanFy(state.DownloadedBytes / 1024)}/{Utils.HumanFy(state.TotalBytes / 1024)}"));
UpdateCompleted?.Invoke(this,
new UpdateResult(state.Completed,
$"{Utils.HumanFy((long)state.SpeedBytesPerSecond / 1024)}/s | {Utils.HumanFy(state.DownloadedBytes / 1024)}/{Utils.HumanFy(state.TotalBytes / 1024)}"));
}
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
@@ -87,14 +128,14 @@ public class DownloadService
}
}
public async Task DownloadSmallFilesAsync(List<FileDownloadRequest> requests, bool blProxy, CancellationToken cancellationToken = default)
public async Task DownloadSmallFilesAsync(List<FileDownloadRequest> requests,
CancellationToken cancellationToken = default)
{
try
{
UpdateCompleted?.Invoke(this, new UpdateResult(false, $"{ResUI.Downloading} 0/{requests.Count}"));
var webProxy = await GetWebProxy(blProxy, cancellationToken);
await DownloaderHelper.Instance.DownloadSmallFilesAsync(webProxy,
await DownloaderHelper.DownloadSmallFilesAsync(
requests,
OnProgress,
cancellationToken);
@@ -139,7 +180,9 @@ public class DownloadService
throw failedState.Error;
}
}
UpdateCompleted?.Invoke(this, new UpdateResult(allCompleted, $"{completedCount}/{span.Length} | {Utils.HumanFy((long)totalSpeed / 1024)}/s {Utils.HumanFy(totalDownloadedBytes / 1024)}/{Utils.HumanFy(totalTotalBytes / 1024)} {downloadingFileName}"));
UpdateCompleted?.Invoke(this,
new UpdateResult(allCompleted,
$"{completedCount}/{span.Length} | {Utils.HumanFy((long)totalSpeed / 1024)}/s {Utils.HumanFy(totalDownloadedBytes / 1024)}/{Utils.HumanFy(totalTotalBytes / 1024)} {downloadingFileName}"));
}
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
@@ -159,53 +202,31 @@ public class DownloadService
}
/// <summary>
/// Gets redirect target URL without following redirects automatically.
/// Gets redirect target URL without following redirects automatically.
/// </summary>
public async Task<string?> UrlRedirectAsync(string url, bool blProxy, CancellationToken cancellationToken = default)
public async Task<string?> UrlRedirectAsync(string url, CancellationToken cancellationToken = default)
{
var webRequestHandler = new SocketsHttpHandler
{
AllowAutoRedirect = false,
Proxy = await GetWebProxy(blProxy, cancellationToken)
};
var certificateChainPolicy = CertPemManager.Instance.BuildCertificateChainPolicy();
if (certificateChainPolicy != null)
{
webRequestHandler.SslOptions.CertificateChainPolicy = certificateChainPolicy;
webRequestHandler.SslOptions.RemoteCertificateValidationCallback = null;
}
using var client = new HttpClient(webRequestHandler);
using var client = new HttpClient(GetHandler(), false);
var response = await client.GetAsync(url, cancellationToken);
if (response.StatusCode == HttpStatusCode.Redirect && response.Headers.Location is not null)
{
return response.Headers.Location.ToString();
}
else
{
Error?.Invoke(this, new ErrorEventArgs(new Exception("StatusCode error: " + response.StatusCode)));
Logging.SaveLog("StatusCode error: " + url);
return null;
}
Error?.Invoke(this, new ErrorEventArgs(new Exception("StatusCode error: " + response.StatusCode)));
Logging.SaveLog("StatusCode error: " + url);
return null;
}
/// <summary>
/// Tries to download string content using proxy switch setting.
/// Tries to download string content with a specified proxy.
/// </summary>
public async Task<string?> TryDownloadString(string url, bool blProxy, string userAgent, CancellationToken cancellationToken = default)
{
var webProxy = await GetWebProxy(blProxy, cancellationToken);
return await TryDownloadString(url, webProxy, userAgent, cancellationToken);
}
/// <summary>
/// Tries to download string content with a specified proxy.
/// </summary>
public async Task<string?> TryDownloadString(string url, IWebProxy? webProxy, string userAgent, CancellationToken cancellationToken = default)
public async Task<string?> TryDownloadString(string url,
CancellationToken cancellationToken = default)
{
try
{
var result1 = await DownloadStringAsync(url, webProxy, userAgent, cancellationToken);
var result1 = await DownloadStringAsync(url, cancellationToken);
if (result1.IsNotEmpty())
{
return result1;
@@ -227,7 +248,7 @@ public class DownloadService
try
{
var result2 = await DownloadStringViaDownloader(url, webProxy, userAgent, cancellationToken);
var result2 = await DownloadStringViaDownloader(url, cancellationToken);
if (result2.IsNotEmpty())
{
return result2;
@@ -251,48 +272,25 @@ public class DownloadService
}
/// <summary>
/// Downloads string content via HttpClient.
/// Downloads string content via HttpClient.
/// </summary>
private async Task<string?> DownloadStringAsync(string url, IWebProxy? webProxy, string userAgent, CancellationToken cancellationToken = default)
private async Task<string?> DownloadStringAsync(string url,
CancellationToken cancellationToken = default)
{
try
{
var handler = new SocketsHttpHandler
{
Proxy = webProxy,
UseProxy = webProxy != null,
AutomaticDecompression = DecompressionMethods.All,
ConnectTimeout = webProxy is null ? Global.DirectDownloadConnect : Global.ProxyDownloadConnect,
};
var certificateChainPolicy = CertPemManager.Instance.BuildCertificateChainPolicy();
if (certificateChainPolicy != null)
{
handler.SslOptions.CertificateChainPolicy = certificateChainPolicy;
handler.SslOptions.RemoteCertificateValidationCallback = null;
}
using var client = new HttpClient(HttpRequestHeadersHelper.CreateHandler(handler, RequestHeaders));
using var client = new HttpClient(GetHandler(), false);
client.Timeout = Timeout.InfiniteTimeSpan;
if (userAgent.IsNullOrEmpty())
{
userAgent = Utils.GetVersion(false);
}
client.DefaultRequestHeaders.UserAgent.TryParseAdd(userAgent);
if (AcceptHeader.IsNotEmpty())
{
client.DefaultRequestHeaders.Accept.ParseAdd(AcceptHeader);
}
Uri uri = new(url);
//Authorization Header
var uri = new Uri(url);
if (uri.UserInfo.IsNotEmpty())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Utils.Base64Encode(uri.UserInfo));
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Utils.Base64Encode(uri.UserInfo));
}
using var timeoutCts = new CancellationTokenSource();
timeoutCts.CancelAfter(webProxy is null ? Global.DirectFetch : Global.ProxyFetch);
timeoutCts.CancelAfter(Global.ProxyFetch);
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
return await client.GetStringAsync(url, linkedCts.Token);
@@ -315,17 +313,16 @@ public class DownloadService
}
/// <summary>
/// Downloads string content via DownloaderHelper.
/// Downloads string content via DownloaderHelper.
/// </summary>
private async Task<string?> DownloadStringViaDownloader(string url, IWebProxy? webProxy, string userAgent, CancellationToken cancellationToken = default)
private async Task<string?> DownloadStringViaDownloader(string url,
CancellationToken cancellationToken = default)
{
try
{
if (userAgent.IsNullOrEmpty())
{
userAgent = Utils.GetVersion(false);
}
var result = await DownloaderHelper.Instance.DownloadStringAsync(webProxy, url, userAgent, RequestHeaders, AcceptHeader, cancellationToken);
var result =
await DownloaderHelper.DownloadStringAsync(url,
cancellationToken);
return result;
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
@@ -345,16 +342,16 @@ public class DownloadService
}
/// <summary>
/// Creates local SOCKS proxy when proxy switch is enabled.
/// Creates local SOCKS proxy when proxy switch is enabled.
/// </summary>
private async Task<WebProxy?> GetWebProxy(bool blProxy, CancellationToken cancellationToken = default)
public static async Task<WebProxy?> CheckProxy(bool blProxy, CancellationToken cancellationToken = default)
{
if (!blProxy)
{
return null;
}
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
if (await SocksPortCheck(Global.Loopback, port, cancellationToken) == false)
if (!await SocksPortCheck(Global.Loopback, port, cancellationToken))
{
return null;
}
@@ -363,9 +360,9 @@ public class DownloadService
}
/// <summary>
/// Checks whether the specified TCP endpoint is reachable.
/// Checks whether the specified TCP endpoint is reachable.
/// </summary>
private async Task<bool> SocksPortCheck(string ip, int port, CancellationToken cancellationToken = default)
private static async Task<bool> SocksPortCheck(string ip, int port, CancellationToken cancellationToken = default)
{
using var rootTimeOutCts = new CancellationTokenSource(Global.LocalFetch);
using var rootCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, rootTimeOutCts.Token);
@@ -425,4 +422,21 @@ public class DownloadService
}
return false;
}
private HttpMessageHandler GetHandler()
{
return Handler;
}
public void Dispose()
{
if (!DisposeHandler)
{
return;
}
if (Handler is IDisposable disposableHandler)
{
disposableHandler.Dispose();
}
}
}
+45 -23
View File
@@ -435,8 +435,6 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
ConcurrentDictionary<string, byte> completedIds, int concurrencyCount, bool blSpeedTest,
CancellationToken ct = default)
{
var downloadHandle = new DownloadService();
var parallelOptions = new ParallelOptions
{
MaxDegreeOfParallelism = concurrencyCount,
@@ -464,7 +462,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
{
if (delay > 0)
{
await DoSpeedTest(downloadHandle, it, completedIds, innerCt);
await DoSpeedTest(it, completedIds, innerCt);
}
else
{
@@ -493,40 +491,64 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private async Task<int> DoRealPing(ServerTestItem it,
ConcurrentDictionary<string, byte> completedIds, CancellationToken ct = default)
{
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}");
var responseTime = await ConnectionHandler.GetRealPingTime(webProxy, ct);
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
await UpdateFunc(it.IndexId, responseTime.ToString());
if (!_config.UiItem.HideColumnIpInfo && responseTime > 0)
var responseTime = -1;
try
{
var ipInfo = await ConnectionHandler.GetIPInfo(webProxy, ct);
var ipStr = ipInfo?.ToString() ?? Global.None;
ProfileExManager.Instance.SetTestIpInfo(it.IndexId, ipStr);
await UpdateIpInfoFunc(it.IndexId, ipStr);
}
else
{
await UpdateIpInfoFunc(it.IndexId, ResUI.SpeedtestingSkip);
}
using var handler = HttpClientHelper.CreateProxySocketsHttpHandler(it.Port, new());
handler.ConnectTimeout = Global.LocalFetch;
using var client = new HttpClient(handler, false);
responseTime = await ConnectionHandler.GetRealPingTime(client, ct);
completedIds.TryAdd(it.IndexId, 0);
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
await UpdateFunc(it.IndexId, responseTime.ToString());
if (!_config.UiItem.HideColumnIpInfo && responseTime > 0)
{
using var downloadHandle = new DownloadService
{
Handler = handler,
DisposeHandler = false,
};
var ipInfo = await ConnectionHandler.GetIPInfo(downloadHandle, ct);
var ipStr = ipInfo?.ToString() ?? Global.None;
ProfileExManager.Instance.SetTestIpInfo(it.IndexId, ipStr);
await UpdateIpInfoFunc(it.IndexId, ipStr);
}
else
{
await UpdateIpInfoFunc(it.IndexId, ResUI.SpeedtestingSkip);
}
completedIds.TryAdd(it.IndexId, 0);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
throw;
}
catch
{
// Ignore
}
return responseTime;
}
private async Task DoSpeedTest(DownloadService downloadHandle, ServerTestItem it,
private async Task DoSpeedTest(ServerTestItem it,
ConcurrentDictionary<string, byte> completedIds, CancellationToken ct = default)
{
await UpdateFunc(it.IndexId, "", ResUI.Speedtesting);
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}");
var handler = HttpClientHelper.CreateProxySocketsHttpHandler(it.Port, new());
var url = _config.SpeedTestItem.SpeedTestUrl;
var timeout = _config.SpeedTestItem.SpeedTestTimeout;
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(timeout));
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(ct, timeoutCts.Token);
var linkedCt = linkedCts.Token;
await downloadHandle.DownloadDataAsync(url, webProxy, async (success, msg) =>
using var downloadHandle = new DownloadService
{
Handler = handler,
DisposeHandler = true,
};
await downloadHandle.DownloadDataAsync(url, async (success, msg) =>
{
decimal.TryParse(msg, out var dec);
if (dec > 0)
@@ -48,7 +48,9 @@ public class StatisticsXrayService
continue;
}
var result = await HttpClientHelper.Instance.TryGetAsync(Url, token);
//var result = await HttpClientHelper.Instance.TryGetAsync(Url, token);
using var httpClient = HttpClientHelper.Instance.GetHttpClient(false);
var result = await httpClient.GetStringAsync(Url, token).ConfigureAwait(false);
if (result != null)
{
var server = ParseOutput(result) ?? new ServerSpeedItem();
+26 -30
View File
@@ -1,18 +1,17 @@
namespace ServiceLib.Services;
public partial class UpdateService(Config config, Func<bool, string, Task> updateFunc)
public partial class UpdateService(DownloadService downloadService, Config config, Func<bool, string, Task> updateFunc)
{
private readonly Config? _config = config;
private readonly Func<bool, string, Task>? _updateFunc = updateFunc;
private static readonly string _tag = "UpdateService";
public async Task CheckUpdateGuiN(bool preRelease, bool blProxy = true, CancellationToken cancellationToken = default)
public async Task CheckUpdateGuiN(bool preRelease, CancellationToken cancellationToken = default)
{
var url = string.Empty;
var fileName = string.Empty;
DownloadService downloadHandle = new();
downloadHandle.UpdateCompleted += (sender2, args) =>
downloadService.UpdateCompleted += (sender2, args) =>
{
if (args.Success)
{
@@ -24,13 +23,13 @@ public partial class UpdateService(Config config, Func<bool, string, Task> updat
_ = UpdateFunc(false, args.Msg);
}
};
downloadHandle.Error += (sender2, args) =>
downloadService.Error += (sender2, args) =>
{
_ = UpdateFunc(false, args.GetException().Message);
};
await UpdateFunc(false, string.Format(ResUI.MsgStartUpdating, ECoreType.v2rayN));
var result = await CheckUpdateAsync(downloadHandle, ECoreType.v2rayN, preRelease, blProxy);
var result = await CheckUpdateAsync(ECoreType.v2rayN, preRelease, cancellationToken);
if (result.Success)
{
await UpdateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, ECoreType.v2rayN));
@@ -38,7 +37,7 @@ public partial class UpdateService(Config config, Func<bool, string, Task> updat
url = result.Url!;
fileName = Utils.GetTempPath(Utils.GetGuid());
await downloadHandle.DownloadFileAsync(new() { FileUrl = url, FilePath = fileName }, blProxy, cancellationToken);
await downloadService.DownloadFileAsync(new() { FileUrl = url, FilePath = fileName }, cancellationToken);
}
else
{
@@ -46,13 +45,12 @@ public partial class UpdateService(Config config, Func<bool, string, Task> updat
}
}
public async Task CheckUpdateCore(ECoreType type, bool preRelease, bool blProxy = true, CancellationToken cancellationToken = default)
public async Task CheckUpdateCore(ECoreType type, bool preRelease, CancellationToken cancellationToken = default)
{
var url = string.Empty;
var fileName = string.Empty;
DownloadService downloadHandle = new();
downloadHandle.UpdateCompleted += (sender2, args) =>
downloadService.UpdateCompleted += (sender2, args) =>
{
if (args.Success)
{
@@ -73,13 +71,13 @@ public partial class UpdateService(Config config, Func<bool, string, Task> updat
_ = UpdateFunc(false, args.Msg);
}
};
downloadHandle.Error += (sender2, args) =>
downloadService.Error += (sender2, args) =>
{
_ = UpdateFunc(false, args.GetException().Message);
};
await UpdateFunc(false, string.Format(ResUI.MsgStartUpdating, type));
var result = await CheckUpdateAsync(downloadHandle, type, preRelease, blProxy);
var result = await CheckUpdateAsync(type, preRelease, cancellationToken);
if (result.Success)
{
await UpdateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, type));
@@ -88,7 +86,7 @@ public partial class UpdateService(Config config, Func<bool, string, Task> updat
url = result.Url!;
var ext = url.Contains(".tar.gz") ? ".tar.gz" : Path.GetExtension(url);
fileName = Utils.GetTempPath(Utils.GetGuid() + ext);
await downloadHandle.DownloadFileAsync(new() { FileUrl = url, FilePath = fileName }, blProxy, cancellationToken);
await downloadService.DownloadFileAsync(new() { FileUrl = url, FilePath = fileName }, cancellationToken);
}
else
{
@@ -99,19 +97,18 @@ public partial class UpdateService(Config config, Func<bool, string, Task> updat
}
}
public async Task<UpdateResult> CheckHasUpdateOnly(ECoreType type, bool preRelease, bool blProxy = true, CancellationToken cancellationToken = default)
public async Task<UpdateResult> CheckHasUpdateOnly(ECoreType type, bool preRelease, CancellationToken cancellationToken = default)
{
if (!CoreInfoManager.Instance.IsCheckUpdateSupported(type))
{
return new UpdateResult(false, ResUI.MsgNotSupport);
}
var downloadHandle = new DownloadService();
var checkPreRelease = CoreInfoManager.Instance.GetCheckPreRelease(type, preRelease);
return await CheckUpdateAsync(downloadHandle, type, checkPreRelease, blProxy, cancellationToken);
return await CheckUpdateAsync(type, checkPreRelease, cancellationToken);
}
public async Task<List<string>> CheckHasUpdateOnlyAll(bool blProxy = true, CancellationToken cancellationToken = default)
public async Task<List<string>> CheckHasUpdateOnlyAll(CancellationToken cancellationToken = default)
{
var msgs = new List<string>();
foreach (var type in CoreInfoManager.Instance.GetCheckUpdateCoreTypes())
@@ -122,7 +119,7 @@ public partial class UpdateService(Config config, Func<bool, string, Task> updat
}
var preRelease = _config.CheckUpdateItem.CheckPreReleaseCoreTypes?.Contains(type.ToString()) ?? false;
var result = await CheckHasUpdateOnly(type, preRelease, blProxy, cancellationToken);
var result = await CheckHasUpdateOnly(type, preRelease, cancellationToken);
if (result.Success && result.Version != null)
{
var msg = string.Format(ResUI.MsgCheckUpdateHasNewVersion, type, result.Version);
@@ -137,7 +134,7 @@ public partial class UpdateService(Config config, Func<bool, string, Task> updat
return msgs;
}
public async Task UpdateGeoFileAll(bool blProxy = true, CancellationToken cancellationToken = default)
public async Task UpdateGeoFileAll(CancellationToken cancellationToken = default)
{
var requests = new List<FileDownloadRequest>();
requests.AddRange(GetGeoFilesRequest());
@@ -145,17 +142,17 @@ public partial class UpdateService(Config config, Func<bool, string, Task> updat
requests.AddRange(await GetSrsFileAllRequest());
// NOTE: srs files are more small, so we reverse the order to ensure a good download experience for the user.
requests.Reverse();
await DownloadGeoFiles(requests, blProxy, cancellationToken);
await DownloadGeoFiles(requests, cancellationToken);
await UpdateFunc(true, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, "geo"));
}
#region CheckUpdate private
private async Task<UpdateResult> CheckUpdateAsync(DownloadService downloadHandle, ECoreType type, bool preRelease, bool blProxy, CancellationToken cancellationToken = default)
private async Task<UpdateResult> CheckUpdateAsync(ECoreType type, bool preRelease, CancellationToken cancellationToken = default)
{
try
{
var result = await GetRemoteVersion(downloadHandle, type, preRelease, blProxy, cancellationToken);
var result = await GetRemoteVersion(type, preRelease, cancellationToken);
if (!result.Success || result.Version is null)
{
return result;
@@ -170,14 +167,14 @@ public partial class UpdateService(Config config, Func<bool, string, Task> updat
}
}
private async Task<UpdateResult> GetRemoteVersion(DownloadService downloadHandle, ECoreType type, bool preRelease, bool blProxy, CancellationToken cancellationToken = default)
private async Task<UpdateResult> GetRemoteVersion(ECoreType type, bool preRelease, CancellationToken cancellationToken = default)
{
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(type);
var tagName = string.Empty;
if (preRelease || coreInfo?.LockedMaxVersion != null)
{
var url = coreInfo?.ReleaseApiUrl;
var result = await downloadHandle.TryDownloadString(url, blProxy, Global.AppName, cancellationToken);
var result = await downloadService.TryDownloadString(url, cancellationToken);
if (result.IsNullOrEmpty())
{
return new UpdateResult(false, "");
@@ -209,7 +206,7 @@ public partial class UpdateService(Config config, Func<bool, string, Task> updat
else
{
var url = Path.Combine(coreInfo.Url, "latest");
var lastUrl = await downloadHandle.UrlRedirectAsync(url, blProxy, cancellationToken);
var lastUrl = await downloadService.UrlRedirectAsync(url, cancellationToken);
if (lastUrl == null)
{
return new UpdateResult(false, "");
@@ -535,7 +532,7 @@ public partial class UpdateService(Config config, Func<bool, string, Task> updat
};
}
private async Task DownloadGeoFiles(List<FileDownloadRequest> requests, bool blProxy, CancellationToken cancellationToken = default)
private async Task DownloadGeoFiles(List<FileDownloadRequest> requests, CancellationToken cancellationToken = default)
{
var tmpFilePathDict = new Dictionary<string, string>();
var tmpFileRequests = new List<FileDownloadRequest>();
@@ -549,8 +546,7 @@ public partial class UpdateService(Config config, Func<bool, string, Task> updat
});
}
DownloadService downloadHandle = new();
downloadHandle.UpdateCompleted += (sender2, args) =>
downloadService.UpdateCompleted += (sender2, args) =>
{
if (args.Success)
{
@@ -585,12 +581,12 @@ public partial class UpdateService(Config config, Func<bool, string, Task> updat
_ = UpdateFunc(false, args.Msg);
}
};
downloadHandle.Error += (sender2, args) =>
downloadService.Error += (sender2, args) =>
{
_ = UpdateFunc(false, args.GetException().Message);
};
await downloadHandle.DownloadSmallFilesAsync(tmpFileRequests, blProxy, cancellationToken);
await downloadService.DownloadSmallFilesAsync(tmpFileRequests, cancellationToken);
}
#endregion Geo private
@@ -163,8 +163,9 @@ public partial class CheckUpdateViewModel : MyReactiveObject
continue;
}
var updateService = new UpdateService(_config, async (success, msg) => await Task.CompletedTask);
var result = await updateService.CheckHasUpdateOnly(item.CoreType.Value, item.IsCheckPreRelease, EnableUpdateViaProxy);
using var downloadService = await DownloadService.CreateDownloadServiceWithCheck(EnableUpdateViaProxy);
var updateService = new UpdateService(downloadService, _config, async (success, msg) => await Task.CompletedTask);
var result = await updateService.CheckHasUpdateOnly(item.CoreType.Value, item.IsCheckPreRelease);
if (result.Success && result.Version != null)
{
await UpdateView(item.CoreType, string.Format(ResUI.MsgCheckUpdateHasNewVersion, item.CoreType, result.Version));
@@ -245,7 +246,8 @@ public partial class CheckUpdateViewModel : MyReactiveObject
UpdatedPlusPlus(null, "");
}
}
await new UpdateService(_config, _updateUI).UpdateGeoFileAll(EnableUpdateViaProxy)
using var downloadService = await DownloadService.CreateDownloadServiceWithCheck(EnableUpdateViaProxy);
await new UpdateService(downloadService, _config, _updateUI).UpdateGeoFileAll()
.ContinueWith(t => UpdatedPlusPlus(null, ""));
}
@@ -260,7 +262,8 @@ public partial class CheckUpdateViewModel : MyReactiveObject
UpdatedPlusPlus(_v2rayN, msg);
}
}
await new UpdateService(_config, _updateUI).CheckUpdateGuiN(preRelease, EnableUpdateViaProxy)
using var downloadService = await DownloadService.CreateDownloadServiceWithCheck(EnableUpdateViaProxy);
await new UpdateService(downloadService, _config, _updateUI).CheckUpdateGuiN(preRelease)
.ContinueWith(t => UpdatedPlusPlus(_v2rayN, ""));
}
@@ -278,7 +281,8 @@ public partial class CheckUpdateViewModel : MyReactiveObject
if (model.CoreType.HasValue)
{
await new UpdateService(_config, _updateUI).CheckUpdateCore(model.CoreType.Value, preRelease, EnableUpdateViaProxy)
using var downloadService = await DownloadService.CreateDownloadServiceWithCheck(EnableUpdateViaProxy);
await new UpdateService(downloadService, _config, _updateUI).CheckUpdateCore(model.CoreType.Value, preRelease)
.ContinueWith(t => UpdatedPlusPlus(model.CoreType, ""));
}
}
@@ -578,7 +578,11 @@ public partial class MainWindowViewModel : MyReactiveObject
public async Task UpdateSubscriptionProcess(string subId, bool blProxy)
{
await Task.Run(async () => await SubscriptionHandler.UpdateProcess(_config, subId, blProxy, UpdateTaskHandler));
await Task.Run(async () =>
{
var handler = new SubscriptionHandler(_config, blProxy, UpdateTaskHandler);
await handler.UpdateProcess(subId);
});
}
#endregion Subscription
@@ -792,7 +796,8 @@ public partial class MainWindowViewModel : MyReactiveObject
});
await ConfigHandler.SaveConfig(_config);
await new UpdateService(_config, UpdateTaskHandler).UpdateGeoFileAll();
using var downloadService = await DownloadService.CreateDownloadServiceWithCheck();
await new UpdateService(downloadService, _config, UpdateTaskHandler).UpdateGeoFileAll();
await Reload();
}
@@ -294,8 +294,8 @@ public partial class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
return;
}
var downloadHandle = new DownloadService();
var result = await downloadHandle.TryDownloadString(url, true, "");
using var downloadHandle = await DownloadService.CreateDownloadServiceWithCheck();
var result = await downloadHandle.TryDownloadString(url);
var ret = await AddBatchRoutingRulesAsync(SelectedRouting, result);
if (ret == 0)
{