using System;
namespace PSMinIO.Utils
{
///
/// Utility class for formatting byte sizes into human-readable strings
///
public static class SizeFormatter
{
///
/// Size units in order from smallest to largest
///
private static readonly string[] SizeUnits = { "B", "KB", "MB", "GB", "TB", "PB", "EB" };
///
/// Formats bytes into a human-readable string with appropriate units
///
/// Number of bytes
/// Number of decimal places to show (default: 2)
/// Formatted string with appropriate unit
public static string FormatBytes(long bytes, int decimalPlaces = 2)
{
if (bytes == 0)
return "0 B";
if (bytes < 0)
return $"-{FormatBytes(-bytes, decimalPlaces)}";
int unitIndex = 0;
double size = bytes;
// Find the appropriate unit
while (size >= 1024 && unitIndex < SizeUnits.Length - 1)
{
size /= 1024;
unitIndex++;
}
// Format with specified decimal places
var formatString = $"{{0:F{decimalPlaces}}} {{1}}";
return string.Format(formatString, size, SizeUnits[unitIndex]);
}
///
/// Formats bytes into a human-readable string with appropriate units (double overload)
///
/// Number of bytes
/// Number of decimal places to show (default: 2)
/// Formatted string with appropriate unit
public static string FormatBytes(double bytes, int decimalPlaces = 2)
{
return FormatBytes((long)Math.Round(bytes), decimalPlaces);
}
///
/// Formats bytes per second into a human-readable string
///
/// Bytes per second
/// Number of decimal places to show (default: 2)
/// Formatted string with appropriate unit and "/s" suffix
public static string FormatBytesPerSecond(double bytesPerSecond, int decimalPlaces = 2)
{
return $"{FormatBytes(bytesPerSecond, decimalPlaces)}/s";
}
///
/// Formats a transfer rate with context
///
/// Number of bytes transferred
/// Time elapsed for the transfer
/// Number of decimal places to show (default: 2)
/// Formatted transfer rate string
public static string FormatTransferRate(long bytesTransferred, TimeSpan elapsedTime, int decimalPlaces = 2)
{
if (elapsedTime.TotalSeconds <= 0)
return "0 B/s";
var bytesPerSecond = bytesTransferred / elapsedTime.TotalSeconds;
return FormatBytesPerSecond(bytesPerSecond, decimalPlaces);
}
///
/// Formats a progress string showing current/total with percentages
///
/// Current bytes processed
/// Total bytes to process
/// Number of decimal places to show (default: 2)
/// Formatted progress string
public static string FormatProgress(long current, long total, int decimalPlaces = 2)
{
var currentFormatted = FormatBytes(current, decimalPlaces);
var totalFormatted = FormatBytes(total, decimalPlaces);
if (total > 0)
{
var percentage = (double)current / total * 100;
return $"{currentFormatted} / {totalFormatted} ({percentage:F1}%)";
}
return $"{currentFormatted} / {totalFormatted}";
}
///
/// Gets the appropriate unit for a given byte size without formatting
///
/// Number of bytes
/// Appropriate unit string
public static string GetAppropriateUnit(long bytes)
{
if (bytes == 0)
return "B";
int unitIndex = 0;
double size = Math.Abs(bytes);
while (size >= 1024 && unitIndex < SizeUnits.Length - 1)
{
size /= 1024;
unitIndex++;
}
return SizeUnits[unitIndex];
}
///
/// Converts bytes to the specified unit
///
/// Number of bytes
/// Target unit (B, KB, MB, GB, TB, PB, EB)
/// Value in the specified unit
public static double ConvertToUnit(long bytes, string unit)
{
var unitIndex = Array.IndexOf(SizeUnits, unit.ToUpperInvariant());
if (unitIndex == -1)
throw new ArgumentException($"Invalid unit: {unit}. Valid units are: {string.Join(", ", SizeUnits)}");
if (unitIndex == 0) // Bytes
return bytes;
return bytes / Math.Pow(1024, unitIndex);
}
///
/// Parses a size string back to bytes (e.g., "1.5 GB" -> bytes)
///
/// Size string to parse
/// Number of bytes
public static long ParseSizeString(string sizeString)
{
if (string.IsNullOrWhiteSpace(sizeString))
throw new ArgumentException("Size string cannot be null or empty");
var parts = sizeString.Trim().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2)
throw new ArgumentException($"Invalid size string format: {sizeString}. Expected format: '1.5 GB'");
if (!double.TryParse(parts[0], out var value))
throw new ArgumentException($"Invalid numeric value: {parts[0]}");
var unit = parts[1].ToUpperInvariant();
var unitIndex = Array.IndexOf(SizeUnits, unit);
if (unitIndex == -1)
throw new ArgumentException($"Invalid unit: {unit}. Valid units are: {string.Join(", ", SizeUnits)}");
return (long)(value * Math.Pow(1024, unitIndex));
}
///
/// Formats a size comparison between two values
///
/// First value in bytes
/// Second value in bytes
/// Label for first value
/// Label for second value
/// Number of decimal places to show (default: 2)
/// Formatted comparison string
public static string FormatComparison(long value1, long value2, string label1, string label2, int decimalPlaces = 2)
{
var formatted1 = FormatBytes(value1, decimalPlaces);
var formatted2 = FormatBytes(value2, decimalPlaces);
var difference = value1 - value2;
var diffFormatted = FormatBytes(Math.Abs(difference), decimalPlaces);
var diffDirection = difference >= 0 ? "larger" : "smaller";
return $"{label1}: {formatted1}, {label2}: {formatted2} ({diffFormatted} {diffDirection})";
}
}
}