Add download speed limit

This commit is contained in:
Michael Bucari-Tovo 2023-01-02 02:46:46 -07:00
parent 0ed5062683
commit 9ec877999e
11 changed files with 280 additions and 21 deletions

View File

@ -41,6 +41,10 @@ namespace AaxDecrypter
[JsonIgnore] [JsonIgnore]
public bool IsCancelled => _cancellationSource.IsCancellationRequested; public bool IsCancelled => _cancellationSource.IsCancellationRequested;
private static long _globalSpeedLimit = 0;
/// <summary>bytes per second</summary>
public static long GlobalSpeedLimit { get => _globalSpeedLimit; set => _globalSpeedLimit = value <= 0 ? 0 : Math.Max(value, MIN_BYTES_PER_SECOND); }
#endregion #endregion
#region Private Properties #region Private Properties
@ -61,6 +65,13 @@ namespace AaxDecrypter
//DATA_FLUSH_SZ bytes are written to the file stream. //DATA_FLUSH_SZ bytes are written to the file stream.
private const int DATA_FLUSH_SZ = 1024 * 1024; private const int DATA_FLUSH_SZ = 1024 * 1024;
//Number of times per second the download rate is checkd and throttled
private const int THROTTLE_FREQUENCY = 8;
//Minimum throttle rate. The minimum amount of data that can be throttled
//on each iteration of the download loop is DOWNLOAD_BUFF_SZ.
private const int MIN_BYTES_PER_SECOND = DOWNLOAD_BUFF_SZ * THROTTLE_FREQUENCY;
#endregion #endregion
#region Constructor #region Constructor
@ -168,6 +179,8 @@ namespace AaxDecrypter
try try
{ {
DateTime startTime = DateTime.Now;
long bytesReadSinceThrottle = 0;
int bytesRead; int bytesRead;
do do
{ {
@ -185,6 +198,22 @@ namespace AaxDecrypter
_downloadedPiece.Set(); _downloadedPiece.Set();
} }
#region throttle
bytesReadSinceThrottle += bytesRead;
if (GlobalSpeedLimit >= MIN_BYTES_PER_SECOND && bytesReadSinceThrottle > GlobalSpeedLimit / THROTTLE_FREQUENCY)
{
var delayMS = (int)(startTime.AddSeconds(1d / THROTTLE_FREQUENCY) - DateTime.Now).TotalMilliseconds;
if (delayMS > 0)
await Task.Delay(delayMS, _cancellationSource.Token);
startTime = DateTime.Now;
bytesReadSinceThrottle = 0;
}
#endregion
} while (downloadPosition < ContentLength && !IsCancelled && bytesRead > 0); } while (downloadPosition < ContentLength && !IsCancelled && bytesRead > 0);
WritePosition = downloadPosition; WritePosition = downloadPosition;
@ -195,9 +224,9 @@ namespace AaxDecrypter
if (WritePosition > ContentLength) if (WritePosition > ContentLength)
throw new WebException($"Downloaded size (0x{WritePosition:X10}) is greater than {nameof(ContentLength)} (0x{ContentLength:X10})."); throw new WebException($"Downloaded size (0x{WritePosition:X10}) is greater than {nameof(ContentLength)} (0x{ContentLength:X10}).");
} }
catch (Exception ex) catch (TaskCanceledException)
{ {
Serilog.Log.Error(ex, "An error was encountered while downloading {Uri}", Uri); Serilog.Log.Information("Download was cancelled");
} }
finally finally
{ {

View File

@ -176,6 +176,9 @@ namespace AppScaffolding
if (!config.Exists(nameof(config.AutoDownloadEpisodes))) if (!config.Exists(nameof(config.AutoDownloadEpisodes)))
config.AutoDownloadEpisodes = false; config.AutoDownloadEpisodes = false;
if (!config.Exists(nameof(config.DownloadSpeedLimit)))
config.DownloadSpeedLimit = 0;
} }
/// <summary>Initialize logging. Wire-up events. Run after migration</summary> /// <summary>Initialize logging. Wire-up events. Run after migration</summary>

View File

@ -2,6 +2,7 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Threading; using Avalonia.Threading;
using DataLayer; using DataLayer;
using LibationFileManager;
using ReactiveUI; using ReactiveUI;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -25,9 +26,14 @@ namespace LibationAvalonia.ViewModels
public ProcessQueueViewModel() public ProcessQueueViewModel()
{ {
Logger = LogMe.RegisterForm(this);
Queue.QueuededCountChanged += Queue_QueuededCountChanged; Queue.QueuededCountChanged += Queue_QueuededCountChanged;
Queue.CompletedCountChanged += Queue_CompletedCountChanged; Queue.CompletedCountChanged += Queue_CompletedCountChanged;
Logger = LogMe.RegisterForm(this);
if (Design.IsDesignMode)
AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists();
SpeedLimit = Configuration.Instance.DownloadSpeedLimit / 1024m / 1024;
} }
private int _completedCount; private int _completedCount;
@ -35,6 +41,7 @@ namespace LibationAvalonia.ViewModels
private int _queuedCount; private int _queuedCount;
private string _runningTime; private string _runningTime;
private bool _progressBarVisible; private bool _progressBarVisible;
private decimal _speedLimit;
public int CompletedCount { get => _completedCount; private set { this.RaiseAndSetIfChanged(ref _completedCount, value); this.RaisePropertyChanged(nameof(AnyCompleted)); } } public int CompletedCount { get => _completedCount; private set { this.RaiseAndSetIfChanged(ref _completedCount, value); this.RaisePropertyChanged(nameof(AnyCompleted)); } }
public int QueuedCount { get => _queuedCount; private set { this.RaiseAndSetIfChanged(ref _queuedCount, value); this.RaisePropertyChanged(nameof(AnyQueued)); } } public int QueuedCount { get => _queuedCount; private set { this.RaiseAndSetIfChanged(ref _queuedCount, value); this.RaisePropertyChanged(nameof(AnyQueued)); } }
@ -46,6 +53,37 @@ namespace LibationAvalonia.ViewModels
public bool AnyErrors => ErrorCount > 0; public bool AnyErrors => ErrorCount > 0;
public double Progress => 100d * Queue.Completed.Count / Queue.Count; public double Progress => 100d * Queue.Completed.Count / Queue.Count;
public decimal SpeedLimit
{
get
{
return _speedLimit;
}
set
{
var newValue = Math.Min(999 * 1024 * 1024, (long)(value * 1024 * 1024));
var config = Configuration.Instance;
config.DownloadSpeedLimit = newValue;
_speedLimit
= config.DownloadSpeedLimit <= newValue ? value
: value == 0.01m ? config.DownloadSpeedLimit / 1024m / 1024
: 0;
config.DownloadSpeedLimit = (long)(_speedLimit * 1024 * 1024);
SpeedLimitIncrement = _speedLimit > 100 ? 10
: _speedLimit > 10 ? 1
: _speedLimit > 1 ? 0.1m
: 0.01m;
this.RaisePropertyChanged(nameof(SpeedLimitIncrement));
this.RaisePropertyChanged();
}
}
public decimal SpeedLimitIncrement { get; private set; }
private void Queue_CompletedCountChanged(object sender, int e) private void Queue_CompletedCountChanged(object sender, int e)
{ {
int errCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.FailedAbort or ProcessBookResult.FailedSkip or ProcessBookResult.FailedRetry or ProcessBookResult.ValidationFail); int errCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.FailedAbort or ProcessBookResult.FailedSkip or ProcessBookResult.FailedRetry or ProcessBookResult.ValidationFail);

View File

@ -172,7 +172,7 @@
</Grid> </Grid>
<Border Grid.Row="2" BorderThickness="1" BorderBrush="{DynamicResource DataGridGridLinesBrush}"> <Border Grid.Row="2" BorderThickness="1" BorderBrush="{DynamicResource DataGridGridLinesBrush}">
<SplitView IsPaneOpen="{Binding QueueOpen}" DisplayMode="Inline" OpenPaneLength="375" PanePlacement="Right"> <SplitView IsPaneOpen="{Binding QueueOpen}" DisplayMode="Inline" OpenPaneLength="400" MinWidth="400" PanePlacement="Right">
<!-- Process Queue --> <!-- Process Queue -->
<SplitView.Pane> <SplitView.Pane>

View File

@ -4,10 +4,15 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:LibationAvalonia.Views" xmlns:views="clr-namespace:LibationAvalonia.Views"
xmlns:viewModels="clr-namespace:LibationAvalonia.ViewModels"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
mc:Ignorable="d" d:DesignWidth="450" d:DesignHeight="850" mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="850"
x:Class="LibationAvalonia.Views.ProcessQueueControl"> x:Class="LibationAvalonia.Views.ProcessQueueControl">
<UserControl.Resources>
<views:DecimalConverter x:Key="myConverter" />
</UserControl.Resources>
<UserControl.Resources> <UserControl.Resources>
<RecyclePool x:Key="RecyclePool" /> <RecyclePool x:Key="RecyclePool" />
<DataTemplate x:Key="queuedBook"> <DataTemplate x:Key="queuedBook">
@ -32,25 +37,43 @@
<TabItem.Header> <TabItem.Header>
<TextBlock FontSize="14" VerticalAlignment="Center">Process Queue</TextBlock> <TextBlock FontSize="14" VerticalAlignment="Center">Process Queue</TextBlock>
</TabItem.Header> </TabItem.Header>
<Grid ColumnDefinitions="*" RowDefinitions="*,40"> <Grid Background="AliceBlue" ColumnDefinitions="*" RowDefinitions="*,40">
<Border Grid.Column="0" Grid.Row="0" BorderThickness="1" BorderBrush="{DynamicResource DataGridGridLinesBrush}" Background="WhiteSmoke"> <Border Grid.Column="0" Grid.Row="0" BorderThickness="1" BorderBrush="{DynamicResource DataGridGridLinesBrush}" Background="WhiteSmoke">
<ScrollViewer <ScrollViewer
Name="scroller" Name="scroller"
HorizontalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"> VerticalScrollBarVisibility="Auto">
<ItemsRepeater IsVisible="True" <ItemsRepeater IsVisible="True"
Grid.Column="0" Grid.Column="0"
Name="repeater" Name="repeater"
VerticalCacheLength="1.2" VerticalCacheLength="1.2"
HorizontalCacheLength="1" HorizontalCacheLength="1"
Background="Transparent" Background="Transparent"
Items="{Binding Items}" Items="{Binding Items}"
ItemTemplate="{StaticResource elementFactory}" /> ItemTemplate="{StaticResource elementFactory}" />
</ScrollViewer> </ScrollViewer>
</Border> </Border>
<Grid Grid.Column="0" Grid.Row="1" ColumnDefinitions="*,Auto" Margin="6,0,6,0"> <Grid Grid.Column="0" Grid.Row="1" ColumnDefinitions="*,Auto,Auto">
<Button Grid.Column="0" FontSize="12" HorizontalAlignment="Left" Click="CancelAllBtn_Click">Cancel All</Button> <Button Name="cancelAllBtn" Grid.Column="0" FontSize="12" HorizontalAlignment="Left" Click="CancelAllBtn_Click">Cancel All</Button>
<Button Grid.Column="1" FontSize="12" HorizontalAlignment="Right" Click="ClearFinishedBtn_Click">Clear Finished</Button> <StackPanel Orientation="Horizontal" Grid.Column="1" Margin="0,0,10,0" >
<StackPanel.Styles>
<Style Selector="NumericUpDown#PART_Spinner">
<Setter Property="Background" Value="Black"/>
</Style>
</StackPanel.Styles>
<TextBlock Margin="0,0,6,0" FontSize="11" Text="DL&#xA;Limit" VerticalAlignment="Center" />
<NumericUpDown
FontSize="12"
VerticalContentAlignment="Center"
TextConverter="{StaticResource myConverter}"
Height="{Binding #cancelAllBtn.DesiredSize.Height}"
Value="{Binding SpeedLimit, Mode=TwoWay}"
Minimum="0"
KeyDown="NumericUpDown_KeyDown"
Increment="{Binding SpeedLimitIncrement}"
Maximum="999" />
</StackPanel>
<Button Grid.Column="2" FontSize="12" HorizontalAlignment="Right" Click="ClearFinishedBtn_Click">Clear Finished</Button>
</Grid> </Grid>
</Grid> </Grid>
</TabItem> </TabItem>

View File

@ -1,11 +1,13 @@
using ApplicationServices; using ApplicationServices;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Data.Converters;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using DataLayer; using DataLayer;
using LibationAvalonia.ViewModels; using LibationAvalonia.ViewModels;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
namespace LibationAvalonia.Views namespace LibationAvalonia.Views
@ -86,6 +88,11 @@ namespace LibationAvalonia.Views
#endregion #endregion
} }
public void NumericUpDown_KeyDown(object sender, Avalonia.Input.KeyEventArgs e)
{
if (e.Key == Avalonia.Input.Key.Enter && sender is Avalonia.Input.IInputElement input) input.Focus();
}
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
@ -148,4 +155,41 @@ namespace LibationAvalonia.Views
#endregion #endregion
} }
public class DecimalConverter : IValueConverter
{
public static readonly DecimalConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string sourceText && targetType.IsAssignableTo(typeof(decimal?)))
{
if (sourceText == "∞") return 0;
for (int i = sourceText.Length; i > 0; i--)
{
if (decimal.TryParse(sourceText[..i], out var val))
return val;
}
return 0;
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is decimal val)
{
return
val == 0 ? "∞"
: (
val >= 10 ? ((long)val).ToString()
: val >= 1 ? val.ToString("F1")
: val.ToString("F2")
) + " MB/s";
}
return value.ToString();
}
}
} }

View File

@ -274,9 +274,24 @@ namespace LibationFileManager
set => persistentDictionary.SetNonString(nameof(SavePodcastsToParentFolder), value); set => persistentDictionary.SetNonString(nameof(SavePodcastsToParentFolder), value);
} }
#region templates: custom file naming [Description("Global download speed limit in bytes per second.")]
public long DownloadSpeedLimit
{
get
{
AaxDecrypter.NetworkFileStream.GlobalSpeedLimit = persistentDictionary.GetNonString<long>(nameof(DownloadSpeedLimit));
return AaxDecrypter.NetworkFileStream.GlobalSpeedLimit;
}
set
{
AaxDecrypter.NetworkFileStream.GlobalSpeedLimit = value;
persistentDictionary.SetNonString(nameof(DownloadSpeedLimit), AaxDecrypter.NetworkFileStream.GlobalSpeedLimit);
}
}
[Description("Edit how filename characters are replaced")] #region templates: custom file naming
[Description("Edit how filename characters are replaced")]
public ReplacementCharacters ReplacementCharacters public ReplacementCharacters ReplacementCharacters
{ {
get => persistentDictionary.GetNonString<ReplacementCharacters>(nameof(ReplacementCharacters)); get => persistentDictionary.GetNonString<ReplacementCharacters>(nameof(ReplacementCharacters));

View File

@ -15,6 +15,7 @@ namespace LibationWinForms
private void Configure_ProcessQueue() private void Configure_ProcessQueue()
{ {
processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut; processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut;
splitContainer1.Panel2MinSize = 350;
var coppalseState = Configuration.Instance.GetNonString<bool>(nameof(splitContainer1.Panel2Collapsed)); var coppalseState = Configuration.Instance.GetNonString<bool>(nameof(splitContainer1.Panel2Collapsed));
WidthChange = splitContainer1.Panel2.Width + splitContainer1.SplitterWidth; WidthChange = splitContainer1.Panel2.Width + splitContainer1.SplitterWidth;
int width = this.Width; int width = this.Width;

View File

@ -31,6 +31,8 @@ namespace LibationWinForms.GridView
public override Type EditType => typeof(MyRatingCellEditor); public override Type EditType => typeof(MyRatingCellEditor);
public override Type ValueType => typeof(Rating); public override Type ValueType => typeof(Rating);
public MyRatingGridViewCell() { ToolTipText = "Click to change ratings"; }
public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle) public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
{ {
base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle); base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle);
@ -43,7 +45,7 @@ namespace LibationWinForms.GridView
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts) protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
{ {
if (value is Rating rating) if (value is Rating rating)
{ {
ToolTipText = "Click to change ratings"; ToolTipText = "Click to change ratings";
var starString = rating.ToStarString(); var starString = rating.ToStarString();

View File

@ -43,6 +43,8 @@
this.panel3 = new System.Windows.Forms.Panel(); this.panel3 = new System.Windows.Forms.Panel();
this.virtualFlowControl2 = new LibationWinForms.ProcessQueue.VirtualFlowControl(); this.virtualFlowControl2 = new LibationWinForms.ProcessQueue.VirtualFlowControl();
this.panel1 = new System.Windows.Forms.Panel(); this.panel1 = new System.Windows.Forms.Panel();
this.label1 = new System.Windows.Forms.Label();
this.numericUpDown1 = new LibationWinForms.ProcessQueue.NumericUpDownSuffix();
this.btnCleanFinished = new System.Windows.Forms.Button(); this.btnCleanFinished = new System.Windows.Forms.Button();
this.cancelAllBtn = new System.Windows.Forms.Button(); this.cancelAllBtn = new System.Windows.Forms.Button();
this.tabPage2 = new System.Windows.Forms.TabPage(); this.tabPage2 = new System.Windows.Forms.TabPage();
@ -58,6 +60,7 @@
this.tabControl1.SuspendLayout(); this.tabControl1.SuspendLayout();
this.tabPage1.SuspendLayout(); this.tabPage1.SuspendLayout();
this.panel1.SuspendLayout(); this.panel1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit();
this.tabPage2.SuspendLayout(); this.tabPage2.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.logDGV)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.logDGV)).BeginInit();
this.panel2.SuspendLayout(); this.panel2.SuspendLayout();
@ -166,6 +169,8 @@
// //
this.panel1.BackColor = System.Drawing.SystemColors.Control; this.panel1.BackColor = System.Drawing.SystemColors.Control;
this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.panel1.Controls.Add(this.label1);
this.panel1.Controls.Add(this.numericUpDown1);
this.panel1.Controls.Add(this.btnCleanFinished); this.panel1.Controls.Add(this.btnCleanFinished);
this.panel1.Controls.Add(this.cancelAllBtn); this.panel1.Controls.Add(this.cancelAllBtn);
this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom; this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom;
@ -174,6 +179,44 @@
this.panel1.Size = new System.Drawing.Size(390, 25); this.panel1.Size = new System.Drawing.Size(390, 25);
this.panel1.TabIndex = 2; this.panel1.TabIndex = 2;
// //
// label1
//
this.label1.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(148, 4);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(54, 15);
this.label1.TabIndex = 5;
this.label1.Text = "DL Limit:";
//
// numericUpDown1
//
this.numericUpDown1.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.numericUpDown1.DecimalPlaces = 1;
this.numericUpDown1.Increment = new decimal(new int[] {
1,
0,
0,
65536});
this.numericUpDown1.Location = new System.Drawing.Point(208, 0);
this.numericUpDown1.Maximum = new decimal(new int[] {
999,
0,
0,
0});
this.numericUpDown1.Name = "numericUpDown1";
this.numericUpDown1.Size = new System.Drawing.Size(84, 23);
this.numericUpDown1.Suffix = " MB/s";
this.numericUpDown1.TabIndex = 4;
this.numericUpDown1.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
this.numericUpDown1.ThousandsSeparator = true;
this.numericUpDown1.Value = new decimal(new int[] {
999,
0,
0,
0});
this.numericUpDown1.ValueChanged += new System.EventHandler(this.numericUpDown1_ValueChanged);
//
// btnCleanFinished // btnCleanFinished
// //
this.btnCleanFinished.Dock = System.Windows.Forms.DockStyle.Right; this.btnCleanFinished.Dock = System.Windows.Forms.DockStyle.Right;
@ -305,6 +348,8 @@
this.tabControl1.ResumeLayout(false); this.tabControl1.ResumeLayout(false);
this.tabPage1.ResumeLayout(false); this.tabPage1.ResumeLayout(false);
this.panel1.ResumeLayout(false); this.panel1.ResumeLayout(false);
this.panel1.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit();
this.tabPage2.ResumeLayout(false); this.tabPage2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.logDGV)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.logDGV)).EndInit();
this.panel2.ResumeLayout(false); this.panel2.ResumeLayout(false);
@ -337,5 +382,7 @@
private System.Windows.Forms.DataGridViewTextBoxColumn timestampColumn; private System.Windows.Forms.DataGridViewTextBoxColumn timestampColumn;
private System.Windows.Forms.DataGridViewTextBoxColumn logEntryColumn; private System.Windows.Forms.DataGridViewTextBoxColumn logEntryColumn;
private System.Windows.Forms.Button logCopyBtn; private System.Windows.Forms.Button logCopyBtn;
private NumericUpDownSuffix numericUpDown1;
private System.Windows.Forms.Label label1;
} }
} }

View File

@ -1,10 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using ApplicationServices; using ApplicationServices;
using LibationFileManager;
namespace LibationWinForms.ProcessQueue namespace LibationWinForms.ProcessQueue
{ {
@ -46,6 +48,9 @@ namespace LibationWinForms.ProcessQueue
{ {
InitializeComponent(); InitializeComponent();
var speedLimitMBps = Configuration.Instance.DownloadSpeedLimit / 1024m / 1024;
numericUpDown1.Value = speedLimitMBps > numericUpDown1.Maximum || speedLimitMBps < numericUpDown1.Minimum ? 0 : speedLimitMBps;
popoutBtn.DisplayStyle = ToolStripItemDisplayStyle.Text; popoutBtn.DisplayStyle = ToolStripItemDisplayStyle.Text;
popoutBtn.Name = "popoutBtn"; popoutBtn.Name = "popoutBtn";
popoutBtn.Text = "Pop Out"; popoutBtn.Text = "Pop Out";
@ -424,5 +429,57 @@ This error appears to be caused by a temporary interruption of service that some
} }
#endregion #endregion
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
var newValue = (long)(numericUpDown1.Value * 1024 * 1024);
var config = Configuration.Instance;
config.DownloadSpeedLimit = newValue;
if (config.DownloadSpeedLimit > newValue)
numericUpDown1.Value =
numericUpDown1.Value == 0.01m ? config.DownloadSpeedLimit / 1024m / 1024
: 0;
numericUpDown1.Increment =
numericUpDown1.Value > 100 ? 10
: numericUpDown1.Value > 10 ? 1
: numericUpDown1.Value > 1 ? 0.1m
: 0.01m;
numericUpDown1.DecimalPlaces =
numericUpDown1.Value >= 10 ? 0
: numericUpDown1.Value >= 1 ? 1
: 2;
}
}
public class NumericUpDownSuffix : NumericUpDown
{
[Description("Suffix displayed after numeric value."), Category("Data")]
[Browsable(true)]
[EditorBrowsable(EditorBrowsableState.Always)]
[DisallowNull]
public string Suffix
{
get => _suffix;
set
{
base.Text = string.IsNullOrEmpty(_suffix) ? base.Text : base.Text.Replace(_suffix, value);
_suffix = value;
ChangingText = true;
}
}
private string _suffix = string.Empty;
public override string Text
{
get => string.IsNullOrEmpty(Suffix) ? base.Text : base.Text.Replace(Suffix, string.Empty);
set
{
if (Value == Minimum)
base.Text = "∞";
else
base.Text = value + Suffix;
}
}
} }
} }