Skip to content

Commit aa9e5c4

Browse files
tpill90FrankRay78
andauthored
Adding TransferSpeedColumn configuration to display bits/bytes + binary/decimal prefixes (#904)
* Adding configuration to TransferSpeedColumn to be able to display in both bytes/bits, as well as using binary/decimal prefix definitions. --------- Co-authored-by: Frank Ray <[email protected]>
1 parent 8d06daf commit aa9e5c4

File tree

9 files changed

+311
-55
lines changed

9 files changed

+311
-55
lines changed

docs/input/live/progress.md

+2
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ AnsiConsole.Progress()
8585
new PercentageColumn(), // Percentage
8686
new RemainingTimeColumn(), // Remaining time
8787
new SpinnerColumn(), // Spinner
88+
new DownloadedColumn(), // Downloaded
89+
new TransferSpeedColumn(), // Transfer speed
8890
})
8991
.Start(ctx =>
9092
{

src/Spectre.Console/Internal/FileSize.cs

+101-32
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,71 @@ namespace Spectre.Console;
33
internal struct FileSize
44
{
55
public double Bytes { get; }
6-
public FileSizeUnit Unit { get; }
6+
public double Bits => Bytes * 8;
7+
8+
public FileSizePrefix Prefix { get; } = FileSizePrefix.None;
9+
10+
private readonly FileSizeBase _prefixBase = FileSizeBase.Binary;
11+
12+
/// <summary>
13+
/// If enabled, will display the output in bits, rather than bytes.
14+
/// </summary>
15+
private readonly bool _showBits = false;
16+
717
public string Suffix => GetSuffix();
818

919
public FileSize(double bytes)
1020
{
1121
Bytes = bytes;
12-
Unit = Detect(bytes);
22+
Prefix = DetectPrefix(bytes);
23+
}
24+
25+
public FileSize(double bytes, FileSizeBase @base)
26+
{
27+
Bytes = bytes;
28+
_prefixBase = @base;
29+
Prefix = DetectPrefix(bytes);
30+
}
31+
32+
public FileSize(double bytes, FileSizeBase @base, bool showBits)
33+
{
34+
Bytes = bytes;
35+
_showBits = showBits;
36+
37+
_prefixBase = @base;
38+
Prefix = DetectPrefix(bytes);
39+
}
40+
41+
public FileSize(double bytes, FileSizePrefix prefix)
42+
{
43+
Bytes = bytes;
44+
Prefix = prefix;
1345
}
1446

15-
public FileSize(double bytes, FileSizeUnit unit)
47+
public FileSize(double bytes, FileSizePrefix prefix, FileSizeBase @base, bool showBits)
1648
{
1749
Bytes = bytes;
18-
Unit = unit;
50+
_showBits = showBits;
51+
52+
_prefixBase = @base;
53+
Prefix = prefix;
1954
}
2055

2156
public string Format(CultureInfo? culture = null)
2257
{
23-
var @base = GetBase(Unit);
24-
if (@base == 0)
58+
var unitBase = Math.Pow((int)_prefixBase, (int)Prefix);
59+
60+
if (_showBits)
2561
{
26-
@base = 1;
62+
var bits = Bits / unitBase;
63+
return Prefix == FileSizePrefix.None ?
64+
((int)bits).ToString(culture ?? CultureInfo.InvariantCulture)
65+
: bits.ToString("F1", culture ?? CultureInfo.InvariantCulture);
2766
}
2867

29-
var bytes = Bytes / @base;
30-
31-
return Unit == FileSizeUnit.Byte
32-
? ((int)bytes).ToString(culture ?? CultureInfo.InvariantCulture)
68+
var bytes = Bytes / unitBase;
69+
return Prefix == FileSizePrefix.None ?
70+
((int)bytes).ToString(culture ?? CultureInfo.InvariantCulture)
3371
: bytes.ToString("F1", culture ?? CultureInfo.InvariantCulture);
3472
}
3573

@@ -50,36 +88,67 @@ public string ToString(bool suffix = true, CultureInfo? culture = null)
5088

5189
private string GetSuffix()
5290
{
53-
return (Bytes, Unit) switch
91+
return (Bytes, Unit: Prefix, PrefixBase: _prefixBase, ShowBits: _showBits) switch
5492
{
55-
(_, FileSizeUnit.KiloByte) => "KB",
56-
(_, FileSizeUnit.MegaByte) => "MB",
57-
(_, FileSizeUnit.GigaByte) => "GB",
58-
(_, FileSizeUnit.TeraByte) => "TB",
59-
(_, FileSizeUnit.PetaByte) => "PB",
60-
(_, FileSizeUnit.ExaByte) => "EB",
61-
(_, FileSizeUnit.ZettaByte) => "ZB",
62-
(_, FileSizeUnit.YottaByte) => "YB",
63-
(1, _) => "byte",
64-
(_, _) => "bytes",
93+
(_, FileSizePrefix.Kilo, FileSizeBase.Binary, false) => "KiB",
94+
(_, FileSizePrefix.Mega, FileSizeBase.Binary, false) => "MiB",
95+
(_, FileSizePrefix.Giga, FileSizeBase.Binary, false) => "GiB",
96+
(_, FileSizePrefix.Tera, FileSizeBase.Binary, false) => "TiB",
97+
(_, FileSizePrefix.Peta, FileSizeBase.Binary, false) => "PiB",
98+
(_, FileSizePrefix.Exa, FileSizeBase.Binary, false) => "EiB",
99+
(_, FileSizePrefix.Zetta, FileSizeBase.Binary, false) => "ZiB",
100+
(_, FileSizePrefix.Yotta, FileSizeBase.Binary, false) => "YiB",
101+
102+
(_, FileSizePrefix.Kilo, FileSizeBase.Binary, true) => "Kibit",
103+
(_, FileSizePrefix.Mega, FileSizeBase.Binary, true) => "Mibit",
104+
(_, FileSizePrefix.Giga, FileSizeBase.Binary, true) => "Gibit",
105+
(_, FileSizePrefix.Tera, FileSizeBase.Binary, true) => "Tibit",
106+
(_, FileSizePrefix.Peta, FileSizeBase.Binary, true) => "Pibit",
107+
(_, FileSizePrefix.Exa, FileSizeBase.Binary, true) => "Eibit",
108+
(_, FileSizePrefix.Zetta, FileSizeBase.Binary, true) => "Zibit",
109+
(_, FileSizePrefix.Yotta, FileSizeBase.Binary, true) => "Yibit",
110+
111+
(_, FileSizePrefix.Kilo, FileSizeBase.Decimal, false) => "KB",
112+
(_, FileSizePrefix.Mega, FileSizeBase.Decimal, false) => "MB",
113+
(_, FileSizePrefix.Giga, FileSizeBase.Decimal, false) => "GB",
114+
(_, FileSizePrefix.Tera, FileSizeBase.Decimal, false) => "TB",
115+
(_, FileSizePrefix.Peta, FileSizeBase.Decimal, false) => "PB",
116+
(_, FileSizePrefix.Exa, FileSizeBase.Decimal, false) => "EB",
117+
(_, FileSizePrefix.Zetta, FileSizeBase.Decimal, false) => "ZB",
118+
(_, FileSizePrefix.Yotta, FileSizeBase.Decimal, false) => "YB",
119+
120+
(_, FileSizePrefix.Kilo, FileSizeBase.Decimal, true) => "Kbit",
121+
(_, FileSizePrefix.Mega, FileSizeBase.Decimal, true) => "Mbit",
122+
(_, FileSizePrefix.Giga, FileSizeBase.Decimal, true) => "Gbit",
123+
(_, FileSizePrefix.Tera, FileSizeBase.Decimal, true) => "Tbit",
124+
(_, FileSizePrefix.Peta, FileSizeBase.Decimal, true) => "Pbit",
125+
(_, FileSizePrefix.Exa, FileSizeBase.Decimal, true) => "Ebit",
126+
(_, FileSizePrefix.Zetta, FileSizeBase.Decimal, true) => "Zbit",
127+
(_, FileSizePrefix.Yotta, FileSizeBase.Decimal, true) => "Ybit",
128+
129+
(1, _, _, true) => "bit",
130+
(_, _, _, true) => "bits",
131+
(1, _, _, false) => "byte",
132+
(_, _, _, false) => "bytes",
65133
};
66134
}
67135

68-
private static FileSizeUnit Detect(double bytes)
136+
private FileSizePrefix DetectPrefix(double bytes)
69137
{
70-
foreach (var unit in (FileSizeUnit[])Enum.GetValues(typeof(FileSizeUnit)))
138+
if (_showBits)
139+
{
140+
bytes *= 8;
141+
}
142+
143+
foreach (var prefix in (FileSizePrefix[])Enum.GetValues(typeof(FileSizePrefix)))
71144
{
72-
if (bytes < (GetBase(unit) * 1024))
145+
// Trying to find the largest unit, that the number of bytes can fit under. Ex. 40kb < 1mb
146+
if (bytes < Math.Pow((int)_prefixBase, (int)prefix + 1))
73147
{
74-
return unit;
148+
return prefix;
75149
}
76150
}
77151

78-
return FileSizeUnit.Byte;
79-
}
80-
81-
private static double GetBase(FileSizeUnit unit)
82-
{
83-
return Math.Pow(1024, (int)unit);
152+
return FileSizePrefix.None;
84153
}
85154
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace Spectre.Console;
2+
3+
/// <summary>
4+
/// Determines possible file size base prefixes. (base 2/base 10).
5+
/// </summary>
6+
public enum FileSizeBase
7+
{
8+
/// <summary>
9+
/// The SI prefix definition (base 10) of kilobyte, megabyte, etc.
10+
/// </summary>
11+
Decimal = 1000,
12+
13+
/// <summary>
14+
/// The IEC binary prefix definition (base 2) of kibibyte, mebibyte, etc.
15+
/// </summary>
16+
Binary = 1024,
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Spectre.Console;
2+
3+
internal enum FileSizePrefix
4+
{
5+
None = 0,
6+
Kilo = 1,
7+
Mega = 2,
8+
Giga = 3,
9+
Tera = 4,
10+
Peta = 5,
11+
Exa = 6,
12+
Zetta = 7,
13+
Yotta = 8,
14+
}

src/Spectre.Console/Internal/FileSizeUnit.cs

-14
This file was deleted.

src/Spectre.Console/Live/Progress/Columns/DownloadedColumn.cs

+12-2
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,20 @@ public sealed class DownloadedColumn : ProgressColumn
1010
/// </summary>
1111
public CultureInfo? Culture { get; set; }
1212

13+
/// <summary>
14+
/// Gets or sets the <see cref="FileSizeBase"/> to use.
15+
/// </summary>
16+
public FileSizeBase Base { get; set; } = FileSizeBase.Binary;
17+
18+
/// <summary>
19+
/// Gets or sets a value indicating whether to display the transfer speed in bits.
20+
/// </summary>
21+
public bool ShowBits { get; set; }
22+
1323
/// <inheritdoc/>
1424
public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
1525
{
16-
var total = new FileSize(task.MaxValue);
26+
var total = new FileSize(task.MaxValue, Base, ShowBits);
1727

1828
if (task.IsFinished)
1929
{
@@ -24,7 +34,7 @@ public override IRenderable Render(RenderOptions options, ProgressTask task, Tim
2434
}
2535
else
2636
{
27-
var downloaded = new FileSize(task.Value, total.Unit);
37+
var downloaded = new FileSize(task.Value, total.Prefix, Base, ShowBits);
2838

2939
return new Markup(string.Format(
3040
"{0}[grey]/[/]{1} [grey]{2}[/]",

src/Spectre.Console/Live/Progress/Columns/TransferSpeedColumn.cs

+19-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ public sealed class TransferSpeedColumn : ProgressColumn
1010
/// </summary>
1111
public CultureInfo? Culture { get; set; }
1212

13+
/// <summary>
14+
/// Gets or sets the <see cref="FileSizeBase"/> to use.
15+
/// </summary>
16+
public FileSizeBase Base { get; set; } = FileSizeBase.Binary;
17+
18+
/// <summary>
19+
/// Gets or sets a value indicating whether to display the transfer speed in bits.
20+
/// </summary>
21+
public bool ShowBits { get; set; }
22+
1323
/// <inheritdoc/>
1424
public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
1525
{
@@ -18,7 +28,14 @@ public override IRenderable Render(RenderOptions options, ProgressTask task, Tim
1828
return new Text("?/s");
1929
}
2030

21-
var size = new FileSize(task.Speed.Value);
22-
return new Markup(string.Format("{0}/s", size.ToString(suffix: true, Culture)));
31+
if (task.IsFinished)
32+
{
33+
return new Markup(string.Empty, Style.Plain);
34+
}
35+
else
36+
{
37+
var size = new FileSize(task.Speed.Value, Base, ShowBits);
38+
return new Markup(string.Format("{0}/s", size.ToString(suffix: true, Culture)));
39+
}
2340
}
2441
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
namespace Spectre.Console.Tests.Unit.Internal;
2+
3+
public sealed class FileSizeTests
4+
{
5+
[Theory]
6+
[InlineData(0, "0 bytes")]
7+
[InlineData(37, "37 bytes")]
8+
[InlineData(512, "512 bytes")]
9+
[InlineData(15 * 1024, "15.0 KiB")]
10+
[InlineData(1024 * 512, "512.0 KiB")]
11+
[InlineData(5 * 1024 * 1024, "5.0 MiB")]
12+
[InlineData(9 * 1024 * 1024, "9.0 MiB")]
13+
public void Binary_Unit_In_Bytes_Should_Return_Expected(double bytes, string expected)
14+
{
15+
// Given
16+
var filesize = new FileSize(bytes, FileSizeBase.Binary);
17+
18+
// When
19+
var result = filesize.ToString();
20+
21+
// Then
22+
result.ShouldBe(expected);
23+
}
24+
25+
[Theory]
26+
[InlineData(0, "0 bits")]
27+
[InlineData(37, "296 bits")]
28+
[InlineData(512, "4.0 Kibit")]
29+
[InlineData(15 * 1024, "120.0 Kibit")]
30+
[InlineData(1024 * 512, "4.0 Mibit")]
31+
[InlineData(5 * 1024 * 1024, "40.0 Mibit")]
32+
[InlineData(210 * 1024 * 1024, "1.6 Gibit")]
33+
[InlineData(900 * 1024 * 1024, "7.0 Gibit")]
34+
public void Binary_Unit_In_Bits_Should_Return_Expected(double bytes, string expected)
35+
{
36+
// Given
37+
var filesize = new FileSize(bytes, FileSizeBase.Binary, showBits: true);
38+
39+
// When
40+
var result = filesize.ToString();
41+
42+
// Then
43+
result.ShouldBe(expected);
44+
}
45+
46+
[Theory]
47+
[InlineData(0, "0 bytes")]
48+
[InlineData(37, "37 bytes")]
49+
[InlineData(512, "512 bytes")]
50+
[InlineData(15 * 1024, "15.4 KB")]
51+
[InlineData(1024 * 512, "524.3 KB")]
52+
[InlineData(5 * 1024 * 1024, "5.2 MB")]
53+
[InlineData(9 * 1024 * 1024, "9.4 MB")]
54+
public void Decimal_Unit_In_Bytes_Should_Return_Expected(double bytes, string expected)
55+
{
56+
// Given
57+
var filesize = new FileSize(bytes, FileSizeBase.Decimal);
58+
59+
// When
60+
var result = filesize.ToString();
61+
62+
// Then
63+
result.ShouldBe(expected);
64+
}
65+
66+
[Theory]
67+
[InlineData(0, "0 bits")]
68+
[InlineData(37, "296 bits")]
69+
[InlineData(512, "4.1 Kbit")]
70+
[InlineData(15 * 1024, "122.9 Kbit")]
71+
[InlineData(1024 * 512, "4.2 Mbit")]
72+
[InlineData(5 * 1024 * 1024, "41.9 Mbit")]
73+
[InlineData(900 * 1024 * 1024, "7.5 Gbit")]
74+
public void Decimal_Unit_In_Bits_Should_Return_Expected(double bytes, string expected)
75+
{
76+
// Given
77+
var filesize = new FileSize(bytes, FileSizeBase.Decimal, showBits: true);
78+
79+
// When
80+
var result = filesize.ToString();
81+
82+
// Then
83+
result.ShouldBe(expected);
84+
}
85+
}

0 commit comments

Comments
 (0)