Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/input/live/progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ AnsiConsole.Progress()
new PercentageColumn(), // Percentage
new RemainingTimeColumn(), // Remaining time
new SpinnerColumn(), // Spinner
new DownloadedColumn(), // Downloaded
new TransferSpeedColumn(), // Transfer speed
})
.Start(ctx =>
{
Expand Down
133 changes: 101 additions & 32 deletions src/Spectre.Console/Internal/FileSize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,71 @@ namespace Spectre.Console;
internal struct FileSize
{
public double Bytes { get; }
public FileSizeUnit Unit { get; }
public double Bits => Bytes * 8;

public FileSizePrefix Prefix { get; } = FileSizePrefix.None;

private readonly FileSizeBase _prefixBase = FileSizeBase.Binary;

/// <summary>
/// If enabled, will display the output in bits, rather than bytes.
/// </summary>
private readonly bool _showBits = false;

public string Suffix => GetSuffix();

public FileSize(double bytes)
{
Bytes = bytes;
Unit = Detect(bytes);
Prefix = DetectPrefix(bytes);
}

public FileSize(double bytes, FileSizeBase @base)
{
Bytes = bytes;
_prefixBase = @base;
Prefix = DetectPrefix(bytes);
}

public FileSize(double bytes, FileSizeBase @base, bool showBits)
{
Bytes = bytes;
_showBits = showBits;

_prefixBase = @base;
Prefix = DetectPrefix(bytes);
}

public FileSize(double bytes, FileSizePrefix prefix)
{
Bytes = bytes;
Prefix = prefix;
}

public FileSize(double bytes, FileSizeUnit unit)
public FileSize(double bytes, FileSizePrefix prefix, FileSizeBase @base, bool showBits)
{
Bytes = bytes;
Unit = unit;
_showBits = showBits;

_prefixBase = @base;
Prefix = prefix;
}

public string Format(CultureInfo? culture = null)
{
var @base = GetBase(Unit);
if (@base == 0)
var unitBase = Math.Pow((int)_prefixBase, (int)Prefix);

if (_showBits)
{
@base = 1;
var bits = Bits / unitBase;
return Prefix == FileSizePrefix.None ?
((int)bits).ToString(culture ?? CultureInfo.InvariantCulture)
: bits.ToString("F1", culture ?? CultureInfo.InvariantCulture);
}

var bytes = Bytes / @base;

return Unit == FileSizeUnit.Byte
? ((int)bytes).ToString(culture ?? CultureInfo.InvariantCulture)
var bytes = Bytes / unitBase;
return Prefix == FileSizePrefix.None ?
((int)bytes).ToString(culture ?? CultureInfo.InvariantCulture)
: bytes.ToString("F1", culture ?? CultureInfo.InvariantCulture);
}

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

private string GetSuffix()
{
return (Bytes, Unit) switch
return (Bytes, Unit: Prefix, PrefixBase: _prefixBase, ShowBits: _showBits) switch
{
(_, FileSizeUnit.KiloByte) => "KB",
(_, FileSizeUnit.MegaByte) => "MB",
(_, FileSizeUnit.GigaByte) => "GB",
(_, FileSizeUnit.TeraByte) => "TB",
(_, FileSizeUnit.PetaByte) => "PB",
(_, FileSizeUnit.ExaByte) => "EB",
(_, FileSizeUnit.ZettaByte) => "ZB",
(_, FileSizeUnit.YottaByte) => "YB",
(1, _) => "byte",
(_, _) => "bytes",
(_, FileSizePrefix.Kilo, FileSizeBase.Binary, false) => "KiB",
(_, FileSizePrefix.Mega, FileSizeBase.Binary, false) => "MiB",
(_, FileSizePrefix.Giga, FileSizeBase.Binary, false) => "GiB",
(_, FileSizePrefix.Tera, FileSizeBase.Binary, false) => "TiB",
(_, FileSizePrefix.Peta, FileSizeBase.Binary, false) => "PiB",
(_, FileSizePrefix.Exa, FileSizeBase.Binary, false) => "EiB",
(_, FileSizePrefix.Zetta, FileSizeBase.Binary, false) => "ZiB",
(_, FileSizePrefix.Yotta, FileSizeBase.Binary, false) => "YiB",

(_, FileSizePrefix.Kilo, FileSizeBase.Binary, true) => "Kibit",
(_, FileSizePrefix.Mega, FileSizeBase.Binary, true) => "Mibit",
(_, FileSizePrefix.Giga, FileSizeBase.Binary, true) => "Gibit",
(_, FileSizePrefix.Tera, FileSizeBase.Binary, true) => "Tibit",
(_, FileSizePrefix.Peta, FileSizeBase.Binary, true) => "Pibit",
(_, FileSizePrefix.Exa, FileSizeBase.Binary, true) => "Eibit",
(_, FileSizePrefix.Zetta, FileSizeBase.Binary, true) => "Zibit",
(_, FileSizePrefix.Yotta, FileSizeBase.Binary, true) => "Yibit",

(_, FileSizePrefix.Kilo, FileSizeBase.Decimal, false) => "KB",
(_, FileSizePrefix.Mega, FileSizeBase.Decimal, false) => "MB",
(_, FileSizePrefix.Giga, FileSizeBase.Decimal, false) => "GB",
(_, FileSizePrefix.Tera, FileSizeBase.Decimal, false) => "TB",
(_, FileSizePrefix.Peta, FileSizeBase.Decimal, false) => "PB",
(_, FileSizePrefix.Exa, FileSizeBase.Decimal, false) => "EB",
(_, FileSizePrefix.Zetta, FileSizeBase.Decimal, false) => "ZB",
(_, FileSizePrefix.Yotta, FileSizeBase.Decimal, false) => "YB",

(_, FileSizePrefix.Kilo, FileSizeBase.Decimal, true) => "Kbit",
(_, FileSizePrefix.Mega, FileSizeBase.Decimal, true) => "Mbit",
(_, FileSizePrefix.Giga, FileSizeBase.Decimal, true) => "Gbit",
(_, FileSizePrefix.Tera, FileSizeBase.Decimal, true) => "Tbit",
(_, FileSizePrefix.Peta, FileSizeBase.Decimal, true) => "Pbit",
(_, FileSizePrefix.Exa, FileSizeBase.Decimal, true) => "Ebit",
(_, FileSizePrefix.Zetta, FileSizeBase.Decimal, true) => "Zbit",
(_, FileSizePrefix.Yotta, FileSizeBase.Decimal, true) => "Ybit",

(1, _, _, true) => "bit",
(_, _, _, true) => "bits",
(1, _, _, false) => "byte",
(_, _, _, false) => "bytes",
};
}

private static FileSizeUnit Detect(double bytes)
private FileSizePrefix DetectPrefix(double bytes)
{
foreach (var unit in (FileSizeUnit[])Enum.GetValues(typeof(FileSizeUnit)))
if (_showBits)
{
bytes *= 8;
}

foreach (var prefix in (FileSizePrefix[])Enum.GetValues(typeof(FileSizePrefix)))
{
if (bytes < (GetBase(unit) * 1024))
// Trying to find the largest unit, that the number of bytes can fit under. Ex. 40kb < 1mb
if (bytes < Math.Pow((int)_prefixBase, (int)prefix + 1))
{
return unit;
return prefix;
}
}

return FileSizeUnit.Byte;
}

private static double GetBase(FileSizeUnit unit)
{
return Math.Pow(1024, (int)unit);
return FileSizePrefix.None;
}
}
17 changes: 17 additions & 0 deletions src/Spectre.Console/Internal/FileSizeBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Spectre.Console;

/// <summary>
/// Determines possible file size base prefixes. (base 2/base 10).
/// </summary>
public enum FileSizeBase
{
/// <summary>
/// The SI prefix definition (base 10) of kilobyte, megabyte, etc.
/// </summary>
Decimal = 1000,

/// <summary>
/// The IEC binary prefix definition (base 2) of kibibyte, mebibyte, etc.
/// </summary>
Binary = 1024,
}
14 changes: 14 additions & 0 deletions src/Spectre.Console/Internal/FileSizePrefix.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Spectre.Console;

internal enum FileSizePrefix
{
None = 0,
Kilo = 1,
Mega = 2,
Giga = 3,
Tera = 4,
Peta = 5,
Exa = 6,
Zetta = 7,
Yotta = 8,
}
14 changes: 0 additions & 14 deletions src/Spectre.Console/Internal/FileSizeUnit.cs

This file was deleted.

14 changes: 12 additions & 2 deletions src/Spectre.Console/Live/Progress/Columns/DownloadedColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,20 @@ public sealed class DownloadedColumn : ProgressColumn
/// </summary>
public CultureInfo? Culture { get; set; }

/// <summary>
/// Gets or sets the <see cref="FileSizeBase"/> to use.
/// </summary>
public FileSizeBase Base { get; set; } = FileSizeBase.Binary;

/// <summary>
/// Gets or sets a value indicating whether to display the transfer speed in bits.
/// </summary>
public bool ShowBits { get; set; }

/// <inheritdoc/>
public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
{
var total = new FileSize(task.MaxValue);
var total = new FileSize(task.MaxValue, Base, ShowBits);

if (task.IsFinished)
{
Expand All @@ -24,7 +34,7 @@ public override IRenderable Render(RenderOptions options, ProgressTask task, Tim
}
else
{
var downloaded = new FileSize(task.Value, total.Unit);
var downloaded = new FileSize(task.Value, total.Prefix, Base, ShowBits);

return new Markup(string.Format(
"{0}[grey]/[/]{1} [grey]{2}[/]",
Expand Down
21 changes: 19 additions & 2 deletions src/Spectre.Console/Live/Progress/Columns/TransferSpeedColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ public sealed class TransferSpeedColumn : ProgressColumn
/// </summary>
public CultureInfo? Culture { get; set; }

/// <summary>
/// Gets or sets the <see cref="FileSizeBase"/> to use.
/// </summary>
public FileSizeBase Base { get; set; } = FileSizeBase.Binary;

/// <summary>
/// Gets or sets a value indicating whether to display the transfer speed in bits.
/// </summary>
public bool ShowBits { get; set; }

/// <inheritdoc/>
public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
{
Expand All @@ -18,7 +28,14 @@ public override IRenderable Render(RenderOptions options, ProgressTask task, Tim
return new Text("?/s");
}

var size = new FileSize(task.Speed.Value);
return new Markup(string.Format("{0}/s", size.ToString(suffix: true, Culture)));
if (task.IsFinished)
{
return new Markup(string.Empty, Style.Plain);
}
else
{
var size = new FileSize(task.Speed.Value, Base, ShowBits);
return new Markup(string.Format("{0}/s", size.ToString(suffix: true, Culture)));
}
}
}
85 changes: 85 additions & 0 deletions src/Tests/Spectre.Console.Tests/Unit/Internal/FileSizeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
namespace Spectre.Console.Tests.Unit.Internal;

public sealed class FileSizeTests
{
[Theory]
[InlineData(0, "0 bytes")]
[InlineData(37, "37 bytes")]
[InlineData(512, "512 bytes")]
[InlineData(15 * 1024, "15.0 KiB")]
[InlineData(1024 * 512, "512.0 KiB")]
[InlineData(5 * 1024 * 1024, "5.0 MiB")]
[InlineData(9 * 1024 * 1024, "9.0 MiB")]
public void Binary_Unit_In_Bytes_Should_Return_Expected(double bytes, string expected)
{
// Given
var filesize = new FileSize(bytes, FileSizeBase.Binary);

// When
var result = filesize.ToString();

// Then
result.ShouldBe(expected);
}

[Theory]
[InlineData(0, "0 bits")]
[InlineData(37, "296 bits")]
[InlineData(512, "4.0 Kibit")]
[InlineData(15 * 1024, "120.0 Kibit")]
[InlineData(1024 * 512, "4.0 Mibit")]
[InlineData(5 * 1024 * 1024, "40.0 Mibit")]
[InlineData(210 * 1024 * 1024, "1.6 Gibit")]
[InlineData(900 * 1024 * 1024, "7.0 Gibit")]
public void Binary_Unit_In_Bits_Should_Return_Expected(double bytes, string expected)
{
// Given
var filesize = new FileSize(bytes, FileSizeBase.Binary, showBits: true);

// When
var result = filesize.ToString();

// Then
result.ShouldBe(expected);
}

[Theory]
[InlineData(0, "0 bytes")]
[InlineData(37, "37 bytes")]
[InlineData(512, "512 bytes")]
[InlineData(15 * 1024, "15.4 KB")]
[InlineData(1024 * 512, "524.3 KB")]
[InlineData(5 * 1024 * 1024, "5.2 MB")]
[InlineData(9 * 1024 * 1024, "9.4 MB")]
public void Decimal_Unit_In_Bytes_Should_Return_Expected(double bytes, string expected)
{
// Given
var filesize = new FileSize(bytes, FileSizeBase.Decimal);

// When
var result = filesize.ToString();

// Then
result.ShouldBe(expected);
}

[Theory]
[InlineData(0, "0 bits")]
[InlineData(37, "296 bits")]
[InlineData(512, "4.1 Kbit")]
[InlineData(15 * 1024, "122.9 Kbit")]
[InlineData(1024 * 512, "4.2 Mbit")]
[InlineData(5 * 1024 * 1024, "41.9 Mbit")]
[InlineData(900 * 1024 * 1024, "7.5 Gbit")]
public void Decimal_Unit_In_Bits_Should_Return_Expected(double bytes, string expected)
{
// Given
var filesize = new FileSize(bytes, FileSizeBase.Decimal, showBits: true);

// When
var result = filesize.ToString();

// Then
result.ShouldBe(expected);
}
}
Loading