新增下载器切换

croire 3 years ago
parent c7e4719152
commit 6e6e5a4751

@ -290,6 +290,7 @@
<Compile Include="Logging\LogLevel.cs" />
<Compile Include="Logging\LogManager.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Settings\Downloader.cs" />
<Compile Include="Settings\Models\VideoContentSettings.cs" />
<Compile Include="Settings\OrderFormat.cs" />
<Compile Include="Settings\ParseScope.cs" />

@ -0,0 +1,9 @@
namespace DownKyi.Core.Settings
{
public enum Downloader
{
NOT_SET = 0,
BUILT_IN,
ARIA,
}
}

@ -8,9 +8,20 @@ namespace DownKyi.Core.Settings.Models
public class NetworkSettings
{
public AllowStatus IsLiftingOfRegion { get; set; } = AllowStatus.NONE;
public Downloader Downloader { get; set; } = Downloader.NOT_SET;
public int MaxCurrentDownloads { get; set; } = -1;
#region built-in
public int Split { get; set; } = -1;
public AllowStatus IsHttpProxy { get; set; } = AllowStatus.NONE;
public string HttpProxy { get; set; } = null;
public int HttpProxyListenPort { get; set; } = -1;
#endregion
#region Aria
public int AriaListenPort { get; set; } = -1;
public AriaConfigLogLevel AriaLogLevel { get; set; } = AriaConfigLogLevel.NOT_SET;
public int AriaMaxConcurrentDownloads { get; set; } = -1;
public int AriaSplit { get; set; } = -1;
public int AriaMaxOverallDownloadLimit { get; set; } = -1;
public int AriaMaxDownloadLimit { get; set; } = -1;
@ -19,5 +30,6 @@ namespace DownKyi.Core.Settings.Models
public AllowStatus IsAriaHttpProxy { get; set; } = AllowStatus.NONE;
public string AriaHttpProxy { get; set; } = null;
public int AriaHttpProxyListenPort { get; set; } = -1;
#endregion
}
}

@ -7,15 +7,26 @@ namespace DownKyi.Core.Settings
// 是否开启解除地区限制
private readonly AllowStatus isLiftingOfRegion = AllowStatus.YES;
// 下载器
private readonly Downloader downloader = Downloader.ARIA;
// 最大同时下载数(任务数)
private readonly int maxCurrentDownloads = 3;
// 单文件最大线程数
private readonly int split = 8;
// HttpProxy代理
private readonly AllowStatus isHttpProxy = AllowStatus.NO;
private readonly string httpProxy = "";
private readonly int httpProxyListenPort = 0;
// Aria服务器端口号
private readonly int ariaListenPort = 6800;
// Aria日志等级
private readonly AriaConfigLogLevel ariaLogLevel = AriaConfigLogLevel.INFO;
// Aria最大同时下载数(任务数)
private readonly int ariaMaxConcurrentDownloads = 3;
// Aria单文件最大线程数
private readonly int ariaSplit = 5;
@ -60,6 +71,168 @@ namespace DownKyi.Core.Settings
return SetSettings();
}
/// <summary>
/// 获取下载器
/// </summary>
/// <returns></returns>
public Downloader GetDownloader()
{
appSettings = GetSettings();
if (appSettings.Network.Downloader == Downloader.NOT_SET)
{
// 第一次获取,先设置默认值
SetDownloader(downloader);
return downloader;
}
return appSettings.Network.Downloader;
}
/// <summary>
/// 设置下载器
/// </summary>
/// <param name="downloader"></param>
/// <returns></returns>
public bool SetDownloader(Downloader downloader)
{
appSettings.Network.Downloader = downloader;
return SetSettings();
}
/// <summary>
/// 获取最大同时下载数(任务数)
/// </summary>
/// <returns></returns>
public int GetMaxCurrentDownloads()
{
appSettings = GetSettings();
if (appSettings.Network.MaxCurrentDownloads == -1)
{
// 第一次获取,先设置默认值
SetMaxCurrentDownloads(maxCurrentDownloads);
return maxCurrentDownloads;
}
return appSettings.Network.MaxCurrentDownloads;
}
/// <summary>
/// 设置最大同时下载数(任务数)
/// </summary>
/// <param name="ariaMaxConcurrentDownloads"></param>
/// <returns></returns>
public bool SetMaxCurrentDownloads(int maxCurrentDownloads)
{
appSettings.Network.MaxCurrentDownloads = maxCurrentDownloads;
return SetSettings();
}
/// <summary>
/// 获取单文件最大线程数
/// </summary>
/// <returns></returns>
public int GetSplit()
{
appSettings = GetSettings();
if (appSettings.Network.Split == -1)
{
// 第一次获取,先设置默认值
SetSplit(split);
return split;
}
return appSettings.Network.Split;
}
/// <summary>
/// 设置单文件最大线程数
/// </summary>
/// <param name="split"></param>
/// <returns></returns>
public bool SetSplit(int split)
{
appSettings.Network.Split = split;
return SetSettings();
}
/// <summary>
/// 获取是否开启Http代理
/// </summary>
/// <returns></returns>
public AllowStatus IsHttpProxy()
{
appSettings = GetSettings();
if (appSettings.Network.IsHttpProxy == AllowStatus.NONE)
{
// 第一次获取,先设置默认值
IsHttpProxy(isHttpProxy);
return isHttpProxy;
}
return appSettings.Network.IsHttpProxy;
}
/// <summary>
/// 设置是否开启Http代理
/// </summary>
/// <param name="isHttpProxy"></param>
/// <returns></returns>
public bool IsHttpProxy(AllowStatus isHttpProxy)
{
appSettings.Network.IsHttpProxy = isHttpProxy;
return SetSettings();
}
/// <summary>
/// 获取Http代理的地址
/// </summary>
/// <returns></returns>
public string GetHttpProxy()
{
appSettings = GetSettings();
if (appSettings.Network.HttpProxy == null)
{
// 第一次获取,先设置默认值
SetHttpProxy(httpProxy);
return httpProxy;
}
return appSettings.Network.HttpProxy;
}
/// <summary>
/// 设置Aria的http代理的地址
/// </summary>
/// <param name="httpProxy"></param>
/// <returns></returns>
public bool SetHttpProxy(string httpProxy)
{
appSettings.Network.HttpProxy = httpProxy;
return SetSettings();
}
/// <summary>
/// 获取Http代理的端口
/// </summary>
/// <returns></returns>
public int GetHttpProxyListenPort()
{
appSettings = GetSettings();
if (appSettings.Network.HttpProxyListenPort == -1)
{
// 第一次获取,先设置默认值
SetHttpProxyListenPort(httpProxyListenPort);
return httpProxyListenPort;
}
return appSettings.Network.HttpProxyListenPort;
}
/// <summary>
/// 设置Http代理的端口
/// </summary>
/// <param name="httpProxyListenPort"></param>
/// <returns></returns>
public bool SetHttpProxyListenPort(int httpProxyListenPort)
{
appSettings.Network.HttpProxyListenPort = httpProxyListenPort;
return SetSettings();
}
/// <summary>
/// 获取Aria服务器的端口号
/// </summary>
@ -114,33 +287,6 @@ namespace DownKyi.Core.Settings
return SetSettings();
}
/// <summary>
/// 获取Aria最大同时下载数(任务数)
/// </summary>
/// <returns></returns>
public int GetAriaMaxConcurrentDownloads()
{
appSettings = GetSettings();
if (appSettings.Network.AriaMaxConcurrentDownloads == -1)
{
// 第一次获取,先设置默认值
SetAriaMaxConcurrentDownloads(ariaMaxConcurrentDownloads);
return ariaMaxConcurrentDownloads;
}
return appSettings.Network.AriaMaxConcurrentDownloads;
}
/// <summary>
/// 设置Aria最大同时下载数(任务数)
/// </summary>
/// <param name="ariaMaxConcurrentDownloads"></param>
/// <returns></returns>
public bool SetAriaMaxConcurrentDownloads(int ariaMaxConcurrentDownloads)
{
appSettings.Network.AriaMaxConcurrentDownloads = ariaMaxConcurrentDownloads;
return SetSettings();
}
/// <summary>
/// 获取Aria单文件最大线程数
/// </summary>

@ -14,7 +14,6 @@ using DownKyi.Views.Settings;
using DownKyi.Views.Toolbox;
using DownKyi.Views.UserSpace;
using Prism.Ioc;
using Prism.Services.Dialogs;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@ -122,9 +121,22 @@ namespace DownKyi
});
// 启动下载服务
//downloadService = new AriaDownloadService(DownloadingList, DownloadedList);
downloadService = new BuiltinDownloadService(DownloadingList, DownloadedList);
downloadService.Start();
var download = SettingsManager.GetInstance().GetDownloader();
switch (download)
{
case Downloader.NOT_SET:
break;
case Downloader.BUILT_IN:
downloadService = new BuiltinDownloadService(DownloadingList, DownloadedList);
break;
case Downloader.ARIA:
downloadService = new AriaDownloadService(DownloadingList, DownloadedList);
break;
}
if (downloadService != null)
{
downloadService.Start();
}
return Container.Resolve<MainWindow>();
}

@ -183,6 +183,9 @@
<system:String x:Key="AutoDownloadAll">解析后自动下载已解析视频</system:String>
<system:String x:Key="Network">网络</system:String>
<system:String x:Key="SelectDownloader">选择下载器(重启生效):</system:String>
<system:String x:Key="BuiltinDownloader">内建下载器</system:String>
<system:String x:Key="Aria2cDownloader">Aria2下载器</system:String>
<system:String x:Key="AriaServerPort">Aria服务器端口</system:String>
<system:String x:Key="AriaLogLevel">Aria日志等级</system:String>
<system:String x:Key="AriaMaxConcurrentDownloads">Aria同时下载数</system:String>
@ -190,10 +193,12 @@
<system:String x:Key="AriaDownloadLimit">Aria下载速度限制(KB/s)</system:String>
<system:String x:Key="AriaMaxOverallDownloadLimit">全局下载速度限制[0: 无限制]</system:String>
<system:String x:Key="AriaMaxDownloadLimit">单任务下载速度限制[0: 无限制]</system:String>
<system:String x:Key="IsAriaHttpProxy">使用Http代理</system:String>
<system:String x:Key="AriaHttpProxy">代理地址:</system:String>
<system:String x:Key="AriaHttpProxyPort">端口:</system:String>
<system:String x:Key="AriaFileAllocation">Aria文件预分配</system:String>
<system:String x:Key="IsHttpProxy">使用Http代理</system:String>
<system:String x:Key="HttpProxy">代理地址:</system:String>
<system:String x:Key="HttpProxyPort">端口:</system:String>
<system:String x:Key="MaxCurrentDownloads">同时下载数:</system:String>
<system:String x:Key="Split">最大线程数:</system:String>
<system:String x:Key="Video">视频</system:String>
<system:String x:Key="FirstVideoCodecs">优先下载的视频编码:</system:String>
@ -300,6 +305,7 @@
<system:String x:Key="Allow">确定</system:String>
<system:String x:Key="Cancel">取消</system:String>
<system:String x:Key="ConfirmReboot">此项需重启生效,您确定要重新启动吗?</system:String>
<system:String x:Key="ConfirmDelete">您确定要删除吗?</system:String>
<system:String x:Key="SelectDirectory">请选择文件夹</system:String>

@ -282,7 +282,7 @@ namespace DownKyi.Services.Download
ListenPort = SettingsManager.GetInstance().GetAriaListenPort(),
Token = "downkyi",
LogLevel = SettingsManager.GetInstance().GetAriaLogLevel(),
MaxConcurrentDownloads = SettingsManager.GetInstance().GetAriaMaxConcurrentDownloads(),
MaxConcurrentDownloads = SettingsManager.GetInstance().GetMaxCurrentDownloads(),
MaxConnectionPerServer = 8, // 最大取16
Split = SettingsManager.GetInstance().GetAriaSplit(),
//MaxTries = 5,

@ -1,6 +1,7 @@
using DownKyi.Core.BiliApi.Login;
using DownKyi.Core.BiliApi.VideoStream.Models;
using DownKyi.Core.Downloader;
using DownKyi.Core.Settings;
using DownKyi.Core.Utils;
using DownKyi.Models;
using DownKyi.Utils;
@ -256,8 +257,11 @@ namespace DownKyi.Services.Download
foreach (var url in urls)
{
// 创建下载器
var mtd = new MultiThreadDownloader(url,
Environment.GetEnvironmentVariable("temp"),
Path.Combine(path, localFileName),
SettingsManager.GetInstance().GetSplit());
// 配置网络请求
var mtd = new MultiThreadDownloader(url, Environment.GetEnvironmentVariable("temp"), Path.Combine(path, localFileName), 8);
mtd.Configure(req =>
{
req.CookieContainer = LoginHelper.GetLoginInfoCookies();
@ -265,10 +269,10 @@ namespace DownKyi.Services.Download
req.Referer = "https://www.bilibili.com";
req.Headers.Add("Origin", "https://www.bilibili.com");
if (false)
if (SettingsManager.GetInstance().IsHttpProxy() == AllowStatus.YES)
{
// TODO
req.Proxy = new WebProxy("127.0.0.1", 1080);
req.Proxy = new WebProxy(SettingsManager.GetInstance().GetHttpProxy(),
SettingsManager.GetInstance().GetHttpProxyListenPort());
}
});

@ -345,7 +345,7 @@ namespace DownKyi.Services.Download
while (true)
{
int maxDownloading = SettingsManager.GetInstance().GetAriaMaxConcurrentDownloads();
int maxDownloading = SettingsManager.GetInstance().GetMaxCurrentDownloads();
int downloadingCount = 0;
try

@ -2,10 +2,12 @@
using DownKyi.Core.Settings;
using DownKyi.Core.Utils.Validator;
using DownKyi.Events;
using DownKyi.Services;
using DownKyi.Utils;
using Prism.Commands;
using Prism.Events;
using Prism.Regions;
using Prism.Services.Dialogs;
using System.Collections.Generic;
namespace DownKyi.ViewModels.Settings
@ -18,111 +20,182 @@ namespace DownKyi.ViewModels.Settings
#region 页面属性申明
private bool builtin;
public bool Builtin
{
get => builtin;
set => SetProperty(ref builtin, value);
}
private bool aria2c;
public bool Aria2c
{
get => aria2c;
set => SetProperty(ref aria2c, value);
}
private List<int> maxCurrentDownloads;
public List<int> MaxCurrentDownloads
{
get => maxCurrentDownloads;
set => SetProperty(ref maxCurrentDownloads, value);
}
private int selectedMaxCurrentDownload;
public int SelectedMaxCurrentDownload
{
get => selectedMaxCurrentDownload;
set => SetProperty(ref selectedMaxCurrentDownload, value);
}
private List<int> splits;
public List<int> Splits
{
get => splits;
set => SetProperty(ref splits, value);
}
private int selectedSplit;
public int SelectedSplit
{
get => selectedSplit;
set => SetProperty(ref selectedSplit, value);
}
private bool isHttpProxy;
public bool IsHttpProxy
{
get => isHttpProxy;
set => SetProperty(ref isHttpProxy, value);
}
private string httpProxy;
public string HttpProxy
{
get => httpProxy;
set => SetProperty(ref httpProxy, value);
}
private int httpProxyPort;
public int HttpProxyPort
{
get => httpProxyPort;
set => SetProperty(ref httpProxyPort, value);
}
private int ariaListenPort;
public int AriaListenPort
{
get { return ariaListenPort; }
set { SetProperty(ref ariaListenPort, value); }
get => ariaListenPort;
set => SetProperty(ref ariaListenPort, value);
}
private List<string> ariaLogLevels;
public List<string> AriaLogLevels
{
get { return ariaLogLevels; }
set { SetProperty(ref ariaLogLevels, value); }
get => ariaLogLevels;
set => SetProperty(ref ariaLogLevels, value);
}
private string selectedAriaLogLevel;
public string SelectedAriaLogLevel
{
get { return selectedAriaLogLevel; }
set { SetProperty(ref selectedAriaLogLevel, value); }
get => selectedAriaLogLevel;
set => SetProperty(ref selectedAriaLogLevel, value);
}
private List<int> ariaMaxConcurrentDownloads;
public List<int> AriaMaxConcurrentDownloads
{
get { return ariaMaxConcurrentDownloads; }
set { SetProperty(ref ariaMaxConcurrentDownloads, value); }
get => ariaMaxConcurrentDownloads;
set => SetProperty(ref ariaMaxConcurrentDownloads, value);
}
private int selectedAriaMaxConcurrentDownload;
public int SelectedAriaMaxConcurrentDownload
{
get { return selectedAriaMaxConcurrentDownload; }
set { SetProperty(ref selectedAriaMaxConcurrentDownload, value); }
get => selectedAriaMaxConcurrentDownload;
set => SetProperty(ref selectedAriaMaxConcurrentDownload, value);
}
private List<int> ariaSplits;
public List<int> AriaSplits
{
get { return ariaSplits; }
set { SetProperty(ref ariaSplits, value); }
get => ariaSplits;
set => SetProperty(ref ariaSplits, value);
}
private int selectedAriaSplit;
public int SelectedAriaSplit
{
get { return selectedAriaSplit; }
set { SetProperty(ref selectedAriaSplit, value); }
get => selectedAriaSplit;
set => SetProperty(ref selectedAriaSplit, value);
}
private int ariaMaxOverallDownloadLimit;
public int AriaMaxOverallDownloadLimit
{
get { return ariaMaxOverallDownloadLimit; }
set { SetProperty(ref ariaMaxOverallDownloadLimit, value); }
get => ariaMaxOverallDownloadLimit;
set => SetProperty(ref ariaMaxOverallDownloadLimit, value);
}
private int ariaMaxDownloadLimit;
public int AriaMaxDownloadLimit
{
get { return ariaMaxDownloadLimit; }
set { SetProperty(ref ariaMaxDownloadLimit, value); }
get => ariaMaxDownloadLimit;
set => SetProperty(ref ariaMaxDownloadLimit, value);
}
private bool isAriaHttpProxy;
public bool IsAriaHttpProxy
{
get { return isAriaHttpProxy; }
set { SetProperty(ref isAriaHttpProxy, value); }
get => isAriaHttpProxy;
set => SetProperty(ref isAriaHttpProxy, value);
}
private string ariaHttpProxy;
public string AriaHttpProxy
{
get { return ariaHttpProxy; }
set { SetProperty(ref ariaHttpProxy, value); }
get => ariaHttpProxy;
set => SetProperty(ref ariaHttpProxy, value);
}
private int ariaHttpProxyPort;
public int AriaHttpProxyPort
{
get { return ariaHttpProxyPort; }
set { SetProperty(ref ariaHttpProxyPort, value); }
get => ariaHttpProxyPort;
set => SetProperty(ref ariaHttpProxyPort, value);
}
private List<string> ariaFileAllocations;
public List<string> AriaFileAllocations
{
get { return ariaFileAllocations; }
set { SetProperty(ref ariaFileAllocations, value); }
get => ariaFileAllocations;
set => SetProperty(ref ariaFileAllocations, value);
}
private string selectedAriaFileAllocation;
public string SelectedAriaFileAllocation
{
get { return selectedAriaFileAllocation; }
set { SetProperty(ref selectedAriaFileAllocation, value); }
get => selectedAriaFileAllocation;
set => SetProperty(ref selectedAriaFileAllocation, value);
}
#endregion
public ViewNetworkViewModel(IEventAggregator eventAggregator) : base(eventAggregator)
public ViewNetworkViewModel(IEventAggregator eventAggregator, IDialogService dialogService) : base(eventAggregator, dialogService)
{
#region 属性初始化
// builtin同时下载数
MaxCurrentDownloads = new List<int>();
for (int i = 1; i <= 10; i++) { MaxCurrentDownloads.Add(i); }
// builtin最大线程数
Splits = new List<int>();
for (int i = 1; i <= 10; i++) { Splits.Add(i); }
// Aria的日志等级
AriaLogLevels = new List<string>
{
@ -154,7 +227,7 @@ namespace DownKyi.ViewModels.Settings
}
/// <summary>
/// 导航到VideoDetail页面时执行
/// 导航到页面时执行
/// </summary>
/// <param name="navigationContext"></param>
public override void OnNavigatedTo(NavigationContext navigationContext)
@ -163,6 +236,36 @@ namespace DownKyi.ViewModels.Settings
isOnNavigatedTo = true;
// 选择下载器
var downloader = SettingsManager.GetInstance().GetDownloader();
switch (downloader)
{
case Downloader.NOT_SET:
break;
case Downloader.BUILT_IN:
Builtin = true;
break;
case Downloader.ARIA:
Aria2c = true;
break;
}
// builtin同时下载数
SelectedMaxCurrentDownload = SettingsManager.GetInstance().GetMaxCurrentDownloads();
// builtin最大线程数
SelectedSplit = SettingsManager.GetInstance().GetSplit();
// 是否开启builtin http代理
AllowStatus isHttpProxy = SettingsManager.GetInstance().IsHttpProxy();
IsHttpProxy = isHttpProxy == AllowStatus.YES;
// builtin的http代理的地址
HttpProxy = SettingsManager.GetInstance().GetHttpProxy();
// builtin的http代理的端口
HttpProxyPort = SettingsManager.GetInstance().GetHttpProxyListenPort();
// Aria服务器端口
AriaListenPort = SettingsManager.GetInstance().GetAriaListenPort();
@ -171,7 +274,7 @@ namespace DownKyi.ViewModels.Settings
SelectedAriaLogLevel = ariaLogLevel.ToString("G");
// Aria同时下载数
SelectedAriaMaxConcurrentDownload = SettingsManager.GetInstance().GetAriaMaxConcurrentDownloads();
SelectedAriaMaxConcurrentDownload = SettingsManager.GetInstance().GetMaxCurrentDownloads();
// Aria最大线程数
SelectedAriaSplit = SettingsManager.GetInstance().GetAriaSplit();
@ -201,6 +304,120 @@ namespace DownKyi.ViewModels.Settings
#region 命令申明
// 下载器选择事件
private DelegateCommand<string> selectDownloaderCommand;
public DelegateCommand<string> SelectDownloaderCommand => selectDownloaderCommand ?? (selectDownloaderCommand = new DelegateCommand<string>(ExecuteSelectDownloaderCommand));
/// <summary>
/// 下载器选择事件
/// </summary>
/// <param name="parameter"></param>
private void ExecuteSelectDownloaderCommand(string parameter)
{
Downloader downloader;
switch (parameter)
{
case "Builtin":
downloader = Downloader.BUILT_IN;
break;
case "Aria2c":
downloader = Downloader.ARIA;
break;
default:
downloader = SettingsManager.GetInstance().GetDownloader();
break;
}
bool isSucceed = SettingsManager.GetInstance().SetDownloader(downloader);
PublishTip(isSucceed);
AlertService alertService = new AlertService(dialogService);
ButtonResult result = alertService.ShowInfo(DictionaryResource.GetString("ConfirmReboot"));
if (result == ButtonResult.OK)
{
System.Windows.Application.Current.Shutdown();
System.Diagnostics.Process.Start(System.Reflection.Assembly.GetExecutingAssembly().Location);
}
}
// builtin同时下载数事件
private DelegateCommand<object> maxCurrentDownloadsCommand;
public DelegateCommand<object> MaxCurrentDownloadsCommand => maxCurrentDownloadsCommand ?? (maxCurrentDownloadsCommand = new DelegateCommand<object>(ExecuteMaxCurrentDownloadsCommand));
/// <summary>
/// builtin同时下载数事件
/// </summary>
/// <param name="parameter"></param>
private void ExecuteMaxCurrentDownloadsCommand(object parameter)
{
SelectedMaxCurrentDownload = (int)parameter;
bool isSucceed = SettingsManager.GetInstance().SetMaxCurrentDownloads(SelectedMaxCurrentDownload);
PublishTip(isSucceed);
}
// builtin最大线程数事件
private DelegateCommand<object> splitsCommand;
public DelegateCommand<object> SplitsCommand => splitsCommand ?? (splitsCommand = new DelegateCommand<object>(ExecuteSplitsCommand));
/// <summary>
/// builtin最大线程数事件
/// </summary>
/// <param name="parameter"></param>
private void ExecuteSplitsCommand(object parameter)
{
SelectedSplit = (int)parameter;
bool isSucceed = SettingsManager.GetInstance().SetSplit(SelectedSplit);
PublishTip(isSucceed);
}
// 是否开启builtin http代理事件
private DelegateCommand isHttpProxyCommand;
public DelegateCommand IsHttpProxyCommand => isHttpProxyCommand ?? (isHttpProxyCommand = new DelegateCommand(ExecuteIsHttpProxyCommand));
/// <summary>
/// 是否开启builtin http代理事件
/// </summary>
private void ExecuteIsHttpProxyCommand()
{
AllowStatus isHttpProxy = IsHttpProxy ? AllowStatus.YES : AllowStatus.NO;
bool isSucceed = SettingsManager.GetInstance().IsHttpProxy(isHttpProxy);
PublishTip(isSucceed);
}
// builtin的http代理的地址事件
private DelegateCommand<string> httpProxyCommand;
public DelegateCommand<string> HttpProxyCommand => httpProxyCommand ?? (httpProxyCommand = new DelegateCommand<string>(ExecuteHttpProxyCommand));
/// <summary>
/// builtin的http代理的地址事件
/// </summary>
/// <param name="parameter"></param>
private void ExecuteHttpProxyCommand(string parameter)
{
bool isSucceed = SettingsManager.GetInstance().SetHttpProxy(parameter);
PublishTip(isSucceed);
}
// builtin的http代理的端口事件
private DelegateCommand<string> httpProxyPortCommand;
public DelegateCommand<string> HttpProxyPortCommand => httpProxyPortCommand ?? (httpProxyPortCommand = new DelegateCommand<string>(ExecuteHttpProxyPortCommand));
/// <summary>
/// builtin的http代理的端口事件
/// </summary>
/// <param name="parameter"></param>
private void ExecuteHttpProxyPortCommand(string parameter)
{
int httpProxyPort = (int)Number.GetInt(parameter);
HttpProxyPort = httpProxyPort;
bool isSucceed = SettingsManager.GetInstance().SetHttpProxyListenPort(HttpProxyPort);
PublishTip(isSucceed);
}
// Aria服务器端口事件
private DelegateCommand<string> ariaListenPortCommand;
public DelegateCommand<string> AriaListenPortCommand => ariaListenPortCommand ?? (ariaListenPortCommand = new DelegateCommand<string>(ExecuteAriaListenPortCommand));
@ -267,7 +484,7 @@ namespace DownKyi.ViewModels.Settings
{
SelectedAriaMaxConcurrentDownload = (int)parameter;
bool isSucceed = SettingsManager.GetInstance().SetAriaMaxConcurrentDownloads(SelectedAriaMaxConcurrentDownload);
bool isSucceed = SettingsManager.GetInstance().SetMaxCurrentDownloads(SelectedAriaMaxConcurrentDownload);
PublishTip(isSucceed);
}

@ -16,244 +16,425 @@
Text="{DynamicResource Network}" />
</StackPanel>
<StackPanel
Margin="0,20,0,0"
Orientation="Horizontal"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
<StackPanel Margin="0,20,0,0" Orientation="Vertical">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaServerPort}" />
<TextBox
Name="nameAriaListenPort"
Width="100"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaListenPort}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaListenPortCommand}"
CommandParameter="{Binding ElementName=nameAriaListenPort, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
Text="{DynamicResource SelectDownloader}" />
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaLogLevel}" />
<ComboBox
Name="nameAriaLogLevels"
Width="100"
VerticalContentAlignment="Center"
ItemsSource="{Binding AriaLogLevels}"
SelectedValue="{Binding SelectedAriaLogLevel}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding AriaLogLevelsCommand}" CommandParameter="{Binding ElementName=nameAriaLogLevels, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<GroupBox
Margin="0,10,0,0"
BorderBrush="{x:Null}"
BorderThickness="0">
<StackPanel Orientation="Horizontal">
<RadioButton
Command="{Binding SelectDownloaderCommand}"
CommandParameter="Builtin"
Content="{DynamicResource BuiltinDownloader}"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
IsChecked="{Binding Builtin}"
Style="{StaticResource RadioStyle}" />
<RadioButton
Margin="20,0,0,0"
Command="{Binding SelectDownloaderCommand}"
CommandParameter="Aria2c"
Content="{DynamicResource Aria2cDownloader}"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
IsChecked="{Binding Aria2c}"
Style="{StaticResource RadioStyle}" />
</StackPanel>
</GroupBox>
</StackPanel>
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaMaxConcurrentDownloads}" />
<ComboBox
Name="nameAriaMaxConcurrentDownloads"
Width="100"
VerticalContentAlignment="Center"
ItemsSource="{Binding AriaMaxConcurrentDownloads}"
SelectedValue="{Binding SelectedAriaMaxConcurrentDownload}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding AriaMaxConcurrentDownloadsCommand}" CommandParameter="{Binding ElementName=nameAriaMaxConcurrentDownloads, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
<TextBlock
Height="1"
Margin="0,20,0,0"
Background="{DynamicResource BrushBorder}" />
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaSplit}" />
<ComboBox
Name="nameAriaSplits"
Width="100"
VerticalContentAlignment="Center"
ItemsSource="{Binding AriaSplits}"
SelectedValue="{Binding SelectedAriaSplit}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding AriaSplitsCommand}" CommandParameter="{Binding ElementName=nameAriaSplits, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
<StackPanel x:Name="nameBuiltin">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding Builtin}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Builtin}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<GroupBox
Margin="0,20,0,0"
Padding="10,5"
HorizontalAlignment="Left">
<GroupBox.Header>
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaDownloadLimit}" />
</GroupBox.Header>
Text="{DynamicResource MaxCurrentDownloads}" />
<ComboBox
Name="nameMaxCurrentDownloads"
Width="100"
VerticalContentAlignment="Center"
ItemsSource="{Binding MaxCurrentDownloads}"
SelectedValue="{Binding SelectedMaxCurrentDownload}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding MaxCurrentDownloadsCommand}" CommandParameter="{Binding ElementName=nameMaxCurrentDownloads, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
<StackPanel>
<StackPanel Margin="0,10,0,0" Orientation="Horizontal">
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource Split}" />
<ComboBox
Name="nameSplits"
Width="100"
VerticalContentAlignment="Center"
ItemsSource="{Binding Splits}"
SelectedValue="{Binding SelectedSplit}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SplitsCommand}" CommandParameter="{Binding ElementName=nameSplits, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
<CheckBox
Name="nameIsHttpProxy"
Margin="0,20,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Command="{Binding IsHttpProxyCommand}"
Content="{DynamicResource IsHttpProxy}"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
IsChecked="{Binding IsHttpProxy, Mode=TwoWay}"
Style="{StaticResource CheckBoxStyle}" />
<StackPanel
Name="nameHttpProxyPanel"
Margin="0,20,0,0"
Orientation="Horizontal"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=nameIsHttpProxy, Path=IsChecked}" Value="false">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=nameIsHttpProxy, Path=IsChecked}" Value="true">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<StackPanel Orientation="Horizontal">
<TextBlock
Width="300"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaMaxOverallDownloadLimit}" />
Text="{DynamicResource HttpProxy}" />
<TextBox
Name="nameAriaMaxOverallDownloadLimit"
Width="100"
Name="nameHttpProxy"
Width="200"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaMaxOverallDownloadLimit}"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
Text="{Binding HttpProxy}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaMaxOverallDownloadLimitCommand}"
CommandParameter="{Binding ElementName=nameAriaMaxOverallDownloadLimit, Path=Text}" />
Command="{Binding HttpProxyCommand}"
CommandParameter="{Binding ElementName=nameHttpProxy, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
<StackPanel Margin="0,10,0,5" Orientation="Horizontal">
<StackPanel Margin="30,0,0,0" Orientation="Horizontal">
<TextBlock
Width="300"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaMaxDownloadLimit}" />
Text="{DynamicResource HttpProxyPort}" />
<TextBox
Name="nameAriaMaxDownloadLimit"
Name="nameHttpProxyPort"
Width="100"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaMaxDownloadLimit}"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
Text="{Binding HttpProxyPort}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaMaxDownloadLimitCommand}"
CommandParameter="{Binding ElementName=nameAriaMaxDownloadLimit, Path=Text}" />
Command="{Binding HttpProxyPortCommand}"
CommandParameter="{Binding ElementName=nameHttpProxyPort, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
</StackPanel>
</GroupBox>
<CheckBox
Name="nameIsAriaHttpProxy"
Margin="0,20,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Command="{Binding IsAriaHttpProxyCommand}"
Content="{DynamicResource IsAriaHttpProxy}"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
IsChecked="{Binding IsAriaHttpProxy, Mode=TwoWay}"
Style="{StaticResource CheckBoxStyle}" />
</StackPanel>
<StackPanel
Name="nameAriaHttpProxyPanel"
Margin="0,20,0,0"
Orientation="Horizontal"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
<StackPanel x:Name="nameAria">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=nameIsAriaHttpProxy, Path=IsChecked}" Value="false">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=nameIsAriaHttpProxy, Path=IsChecked}" Value="true">
<DataTrigger Binding="{Binding Aria2c}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Aria2c}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<StackPanel Orientation="Horizontal">
<StackPanel
Margin="0,20,0,0"
Orientation="Horizontal"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaHttpProxy}" />
Text="{DynamicResource AriaServerPort}" />
<TextBox
Name="nameAriaHttpProxy"
Width="200"
Name="nameAriaListenPort"
Width="100"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaHttpProxy}">
Text="{Binding AriaListenPort}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaHttpProxyCommand}"
CommandParameter="{Binding ElementName=nameAriaHttpProxy, Path=Text}" />
Command="{Binding AriaListenPortCommand}"
CommandParameter="{Binding ElementName=nameAriaListenPort, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
<StackPanel Margin="30,0,0,0" Orientation="Horizontal">
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaHttpProxyPort}" />
<TextBox
Name="nameAriaHttpProxyPort"
Text="{DynamicResource AriaLogLevel}" />
<ComboBox
Name="nameAriaLogLevels"
Width="100"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaHttpProxyPort}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaHttpProxyPortCommand}"
CommandParameter="{Binding ElementName=nameAriaHttpProxyPort, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
ItemsSource="{Binding AriaLogLevels}"
SelectedValue="{Binding SelectedAriaLogLevel}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding AriaLogLevelsCommand}" CommandParameter="{Binding ElementName=nameAriaLogLevels, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
</StackPanel>
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaMaxConcurrentDownloads}" />
<ComboBox
Name="nameAriaMaxConcurrentDownloads"
Width="100"
VerticalContentAlignment="Center"
ItemsSource="{Binding AriaMaxConcurrentDownloads}"
SelectedValue="{Binding SelectedAriaMaxConcurrentDownload}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding AriaMaxConcurrentDownloadsCommand}" CommandParameter="{Binding ElementName=nameAriaMaxConcurrentDownloads, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaSplit}" />
<ComboBox
Name="nameAriaSplits"
Width="100"
VerticalContentAlignment="Center"
ItemsSource="{Binding AriaSplits}"
SelectedValue="{Binding SelectedAriaSplit}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding AriaSplitsCommand}" CommandParameter="{Binding ElementName=nameAriaSplits, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
<GroupBox
Margin="0,20,0,0"
Padding="10,5"
HorizontalAlignment="Left">
<GroupBox.Header>
<TextBlock
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaDownloadLimit}" />
</GroupBox.Header>
<StackPanel>
<StackPanel Margin="0,10,0,0" Orientation="Horizontal">
<TextBlock
Width="300"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaMaxOverallDownloadLimit}" />
<TextBox
Name="nameAriaMaxOverallDownloadLimit"
Width="100"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaMaxOverallDownloadLimit}"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaMaxOverallDownloadLimitCommand}"
CommandParameter="{Binding ElementName=nameAriaMaxOverallDownloadLimit, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
<StackPanel Margin="0,10,0,5" Orientation="Horizontal">
<TextBlock
Width="300"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaMaxDownloadLimit}" />
<TextBox
Name="nameAriaMaxDownloadLimit"
Width="100"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaMaxDownloadLimit}"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaMaxDownloadLimitCommand}"
CommandParameter="{Binding ElementName=nameAriaMaxDownloadLimit, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
</StackPanel>
</GroupBox>
<CheckBox
Name="nameIsAriaHttpProxy"
Margin="0,20,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Command="{Binding IsAriaHttpProxyCommand}"
Content="{DynamicResource IsHttpProxy}"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaFileAllocation}" />
<ComboBox
Name="nameAriaFileAllocations"
Width="100"
VerticalContentAlignment="Center"
ItemsSource="{Binding AriaFileAllocations}"
SelectedValue="{Binding SelectedAriaFileAllocation}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding AriaFileAllocationsCommand}" CommandParameter="{Binding ElementName=nameAriaFileAllocations, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
IsChecked="{Binding IsAriaHttpProxy, Mode=TwoWay}"
Style="{StaticResource CheckBoxStyle}" />
<StackPanel
Name="nameAriaHttpProxyPanel"
Margin="0,20,0,0"
Orientation="Horizontal"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=nameIsAriaHttpProxy, Path=IsChecked}" Value="false">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=nameIsAriaHttpProxy, Path=IsChecked}" Value="true">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<StackPanel Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource HttpProxy}" />
<TextBox
Name="nameAriaHttpProxy"
Width="200"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaHttpProxy}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaHttpProxyCommand}"
CommandParameter="{Binding ElementName=nameAriaHttpProxy, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
<StackPanel Margin="30,0,0,0" Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource HttpProxyPort}" />
<TextBox
Name="nameAriaHttpProxyPort"
Width="100"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaHttpProxyPort}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaHttpProxyPortCommand}"
CommandParameter="{Binding ElementName=nameAriaHttpProxyPort, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
</StackPanel>
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaFileAllocation}" />
<ComboBox
Name="nameAriaFileAllocations"
Width="100"
VerticalContentAlignment="Center"
ItemsSource="{Binding AriaFileAllocations}"
SelectedValue="{Binding SelectedAriaFileAllocation}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding AriaFileAllocationsCommand}" CommandParameter="{Binding ElementName=nameAriaFileAllocations, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
</StackPanel>
<StackPanel Margin="10" />

Loading…
Cancel
Save