Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert "1.5TB", "500MB" into a single unit of file size

Tags:

c#

.net

byte

I want to allow a user to enter a file size, using any of the standard suffixes (such as TB, MB, GB)

I'd like to get the value in a way that i can compare them to a folder size.

The idea is to have a program that'll warn if a folder gets above a certain size, with the size dictated by a user-inputted string.

Is there anything built into the .net framework that allows me to parse strings such as 1.5TB, 400GB, 1.9GB and 0.5KB ?

like image 337
George Duckett Avatar asked Sep 07 '11 10:09

George Duckett


3 Answers

This is a good candidate for a simple Interpreter.

Code like this is a simple start, you will need to handle perhaps more cases, and account for differences in casing (Gb vs GB for example).

You start with a definition for a context and an Expression:

public class FileSizeContext
{
    private string input;
    private long output;

    public FileSizeContext(string input)
    {
        this.Input = input;
    }

    public string Input { get; set; }

    public long Output { get; set; }
}

public abstract class FileSizeExpression
{
    public abstract void Interpret(FileSizeContext value);
}

Then you define your terminal expression,a nd all of the variants:

public abstract class TerminalFileSizeExpression : FileSizeExpression
{
    public override void Interpret(FileSizeContext value)
    {
        if(value.Input.EndsWith(this.ThisPattern()))
        {
            double amount = double.Parse(value.Input.Replace(this.ThisPattern(),String.Empty));
            var fileSize = (long)(amount*1024);
            value.Input = String.Format("{0}{1}",fileSize,this.NextPattern());
            value.Output = fileSize;
        }
    }
    protected abstract string ThisPattern();
    protected abstract string NextPattern();
}

public class KbFileSizeExpression : TerminalFileSizeExpression
{
    protected override string ThisPattern(){return "KB";}
    protected override string NextPattern() { return "bytes"; }
}
public class MbFileSizeExpression : TerminalFileSizeExpression
{
    protected override string ThisPattern() { return "MB"; }
    protected override string NextPattern() { return "KB"; }
}
public class GbFileSizeExpression : TerminalFileSizeExpression
{
    protected override string ThisPattern() { return "GB"; }
    protected override string NextPattern() { return "MB"; }
}
public class TbFileSizeExpression : TerminalFileSizeExpression
{
    protected override string ThisPattern() { return "TB"; }
    protected override string NextPattern() { return "GB"; }
}

Then you add a non-terminal expression (this does the bulk of the work):

public class FileSizeParser : FileSizeExpression
{
    private List<FileSizeExpression> expressionTree = new List<FileSizeExpression>()
                                                  {
                                                      new TbFileSizeExpression(),
                                                      new GbFileSizeExpression(),
                                                      new MbFileSizeExpression(),
                                                      new KbFileSizeExpression()
                                                  };

    public override void Interpret(FileSizeContext value)
    {
        foreach (FileSizeExpression exp in expressionTree)
        {
            exp.Interpret(value);
        }
    }
}

Finally, here is the sort of client code:

var ctx = new FileSizeContext("10Mb");
var parser = new FileSizeParser();
parser.Interpret(ctx);
Console.WriteLine("{0} bytes", ctx.Output); // 10485760 bytes

Live example: http://rextester.com/rundotnet?code=WMGOQ13650

Edits. Changed to MB from Mb (one is officially MegaByte other is MegaBit). Changed int to long to account for large sizes.

like image 190
Jamiec Avatar answered Oct 23 '22 12:10

Jamiec


Short answer: no, there is no built in method.

Long answer: use my converter.

public class FileSizeConverter
{
    private static System.Globalization.NumberFormatInfo numberFormat;
    private static Dictionary<string, long> knownUnits;

    static FileSizeConverter()
    {
        knownUnits = new Dictionary<string, long>
        { 
            { "", 1L },                                 // no unit is same as unit B(yte)
            { "B", 1L },
            { "KB", 1024L },
            { "MB", 1024L * 1024L},
            { "GB", 1024L * 1024L * 1024L},
            { "TB", 1024L * 1024L * 1024L * 1024L}
            // fill rest as needed
        };

        // since I live in a locale where "," is the decimal separator I will enforce US number format
        numberFormat = new System.Globalization.CultureInfo("en-US").NumberFormat;
    }

    public long Parse(string value)
    {
        // ignore spaces around the actual value
        value = value.Trim();   

        string unit = ExtractUnit(value);
        string sizeAsString = value.Substring(0, value.Length - unit.Length).Trim();  // trim spaces

        long multiplicator = MultiplicatorForUnit(unit);
        decimal size;

        if (!decimal.TryParse(sizeAsString, System.Globalization.NumberStyles.Number, numberFormat, out size))
            throw new ArgumentException("illegal number", "value");

        return (long)(multiplicator * size);
    }

    private bool IsDigit(char value)
    {
        // we don't want to use char.IsDigit since it would accept esoterical unicode digits
        if (value < '0') return false;
        if (value > '9') return false;

        return true;
    }

    private string ExtractUnit(string sizeWithUnit)
    {
        // start right, end at the first digit
        int lastChar = sizeWithUnit.Length-1;
        int unitLength = 0;

        while (unitLength <= lastChar 
            && sizeWithUnit[lastChar - unitLength] != ' '       // stop when a space
            && !IsDigit(sizeWithUnit[lastChar - unitLength]))   // or digit is found
        {
            unitLength++;
        }

        return sizeWithUnit.Substring(sizeWithUnit.Length - unitLength).ToUpperInvariant();
    }

    private long MultiplicatorForUnit(string unit)
    {
        unit = unit.ToUpperInvariant();

        if (!knownUnits.ContainsKey(unit))
            throw new ArgumentException("illegal or unknown unit", "unit");

        return knownUnits[unit];
    }
}

EDIT: here's live demonstration: http://rextester.com/rundotnet?code=BQYCB2587 (thanks @Jamiec for the link, really handy to run C# source online)

like image 35
VVS Avatar answered Oct 23 '22 13:10

VVS


I couldn't find functionality like this in the .NET Framework with a quick Google search, so I guess it's up to you to implement it.

I think splitting the string on numeric values and a dot (or comma, think international) as first part and extracting the KB/MB/etc as second part and parse each part manually will be the way to go.

like image 3
CodeCaster Avatar answered Oct 23 '22 13:10

CodeCaster