Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

What about filesize? #94

Closed
DoCode opened this issue Feb 17, 2014 · 17 comments
Closed

What about filesize? #94

DoCode opened this issue Feb 17, 2014 · 17 comments
Labels

Comments

@DoCode
Copy link

DoCode commented Feb 17, 2014

=> input as bytes(long) => output as readable string(eg. 1.2 MB)

@MehdiK MehdiK added the jump in label Feb 17, 2014
@MehdiK
Copy link
Member

MehdiK commented Feb 17, 2014

Although it's a very specific problem I think it fits in nicely.

P.S. Give me a PR :)

@GeorgeHahn
Copy link
Contributor

https://github.com/omar/ByteSize may have some helpful bits you could
incorporate
On Feb 17, 2014 8:38 AM, "Mehdi Khalili" notifications@github.com wrote:

Although it's a very specific problem I think it fits in nicely.

P.S. Give me a PR :)

Reply to this email directly or view it on GitHubhttps://github.com//issues/94#issuecomment-35300453
.

@MehdiK
Copy link
Member

MehdiK commented Feb 18, 2014

Thanks.

That's a pretty extensive lib and I really like how it works. The API is very easy to use but also inconsistent with the way Humanizer works! Maybe Humanizer should provide some non-extension API too!

@faisalr
Copy link
Contributor

faisalr commented Mar 4, 2014

It can be made extension API like this

(2.0).FromTeraBytes(FileSizeUnit.MB) => 2097152 MB

you must tell what is input and what is output

public static string FromTeraBytes(this double value, FileSizeUnit target)
        {
            // TODO
        }

This is enum as an example

public enum FileSizeUnit
{
    B,
    KB,
    MB,
    GB,
    TB,
    PB,
    EB,
    ZB,
    YB
}

Or a general way to convert byte to the best size

public static string BytesToString(this long byteCount)
    {
        string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; //Longs run out around EB
        if (byteCount == 0)
            return "0" + suf[0];
        long bytes = Math.Abs(byteCount);
        int place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024)));
        double num = Math.Round(bytes / Math.Pow(1024, place), 1);
        return (Math.Sign(byteCount) * num).ToString() + suf[place];
    }

@GeorgeHahn
Copy link
Contributor

What about

public static bytecount FromTerabytes(this double val)
public static string ToMegabytes(this bytecount val)

That gives an API that looks like this:
(2.0).FromTerabytes().ToMegabytes()

@MehdiK
Copy link
Member

MehdiK commented Mar 5, 2014

Thanks for the input guys. Reading your great API suggestions made me think of a third variation:

var byteSize = (2.0).Terabytes(); // which returns an instance of a class called, hmmmm, perhaps ByteSize

Obviously there will be many extensions like this that know how to instantiate the ByteSize class. WRT the ByteSize class itself I think it could more or less look like the ByteSize library!! So basically you could write your code as (2.0).Terabytes().Kilobytes to get the numeric byte count or (2.0).Terabytes().ToString() or (2.0).Terabytes().Humanize() to get the human readable string representation. Considering ByteSize lib is in Apache 2 we can just steal take Omar's code in and include the license and just add a few extensions to bridge the two libs.

What do you think?

@MehdiK
Copy link
Member

MehdiK commented Mar 5, 2014

Oh or should I say Omar and George's code?!

@GeorgeHahn - just noticed you're a contributor on ByteSize. It's so much easier to steal the code now that we have an insider ;)

@GeorgeHahn
Copy link
Contributor

Ha, don't give me too much credit - all I did was write the parser; the API is all Omar's 😀

@GeorgeHahn
Copy link
Contributor

I like that API!

Would it be possible to take a dependency on the ByteSize lib? It would have to be PCL first, but I've already submitted a PR to solve that.

@DoCode
Copy link
Author

DoCode commented Mar 6, 2014

Hi folks,
my initial question was for a solution that resolve the humanizing of any filesizes(in bytes).
I came up to this solution:

internal class SizeHumanizerConstants
{
    public const long OneEb = OneKb * OnePb;

    public const long OneGb = OneKb * OneMb;

    public const long OneKb = 1024;

    public const long OneMb = OneKb * OneKb;

    public const long OnePb = OneKb * OneTb;

    public const long OneTb = OneKb * OneGb;
}

public static class SizeHumanizer
{
    public static string FormatSize(long size)
    {
        return FormatSizeImpl((ulong)size, 1);
    }

    public static string FormatSize(long size, byte precision)
    {
        return FormatSizeImpl((ulong)size, precision);
    }

    public static string FormatSize(ulong size)
    {
        return FormatSizeImpl(size, 1);
    }

    public static string FormatSize(ulong size, byte precision)
    {
        return FormatSizeImpl(size, 1);
    }

    public static string FormatSizeImpl(ulong size, byte precision)
    {
        string displaySize;
        double doubleSize = size;

        if ((size / SizeHumanizerConstants.OneEb).CompareTo(0L) > 0)
        {
            displaySize =
                ToDecimalString(
                    Math.Round(doubleSize / SizeHumanizerConstants.OneEb, precision, MidpointRounding.ToEven), 
                    precision) + " EB";
        }
        else if ((size / SizeHumanizerConstants.OnePb).CompareTo(0L) > 0)
        {
            displaySize =
                ToDecimalString(
                    Math.Round(doubleSize / SizeHumanizerConstants.OnePb, precision, MidpointRounding.ToEven), 
                    precision) + " PB";
        }
        else if ((size / SizeHumanizerConstants.OneTb).CompareTo(0L) > 0)
        {
            displaySize =
                ToDecimalString(
                    Math.Round(doubleSize / SizeHumanizerConstants.OneTb, precision, MidpointRounding.ToEven), 
                    precision) + " TB";
        }
        else if ((size / SizeHumanizerConstants.OneGb).CompareTo(0L) > 0)
        {
            displaySize =
                ToDecimalString(
                    Math.Round(doubleSize / SizeHumanizerConstants.OneGb, precision, MidpointRounding.ToEven), 
                    precision) + " GB";
        }
        else if ((size / SizeHumanizerConstants.OneMb).CompareTo(0L) > 0)
        {
            displaySize =
                ToDecimalString(
                    Math.Round(doubleSize / SizeHumanizerConstants.OneMb, precision, MidpointRounding.ToEven), 
                    precision) + " MB";
        }
        else if ((size / SizeHumanizerConstants.OneKb).CompareTo(0L) > 0)
        {
            displaySize =
                ToDecimalString(
                    Math.Round(doubleSize / SizeHumanizerConstants.OneKb, precision, MidpointRounding.ToEven), 
                    precision) + " KB";
        }
        else
        {
            displaySize = size + " bytes";
        }

        return displaySize;
    }

    private static byte GetScale(decimal size)
    {
        return BitConverter.GetBytes(decimal.GetBits(size)[3])[2];
    }

    private static string ToDecimalString(double size, byte precision)
    {
        if (GetScale((decimal)size) < precision)
        {
            var sb = new StringBuilder("0.");
            for (byte i = 0; i < precision; ++i)
            {
                sb.Append("0");
            }

            return size.ToString(sb.ToString(), CultureInfo.InvariantCulture);
        }

        return size.ToString(CultureInfo.InvariantCulture);
    }
}

public static class HumanizerExtensions
{
    public static string Humanize(this long size)
    {
        return SizeHumanizer.FormatSize(size);
    }

    public static string Humanize(this long size, byte precision)
    {
        return SizeHumanizer.FormatSize(size, precision);
    }

    public static string Humanize(this double size)
    {
        return SizeHumanizer.FormatSize((long)size);
    }

    public static string Humanize(this double size, byte precision)
    {
        return SizeHumanizer.FormatSize((long)size, precision);
    }
}

My 2c...

@MehdiK
Copy link
Member

MehdiK commented Mar 6, 2014

@GeorgeHahn maybe arguable, but for a small lib like ByteSize I prefer to port or even reimplement than to depend. Also Humanizer has relatively aggressive release cycles and I don't want to have to depend on another library to turn something over quickly. Just a personal quirk I guess; but it's worked well for me in other frameworks too. So I'd have to say no to nuget dependency. In BDDfy I took dependency on RazorEngine in the beginning and that came back and bite me bad - so we implemented our own tiny templating engine inside BDDfy, and I couldn't be happier with the decision and effort as that has been extended a lot since.

@DoCode thanks a lot for sending the code. Making this a byte count extension we're kinda locked with byte input while the other suggested APIs allow us to take any scale in and also output it in the desired scale which is more liberating, and I'm sure a few days after releasing this someone is going to ask for support for other size scales (e.g. terabyte input and gigabyte output). We could obviously later extend it with taking a second optional enum like that suggested by @faisalr. It's just that I am quite sold on the ByteSize API, and when there's already such extensive and well designed lib out there, I think providing a limited API in Humanizer won't do justice for the users.

@faisalr
Copy link
Contributor

faisalr commented Mar 6, 2014

I agree that Humanizer should never have any dependencies except for Xunit for unit testing. I wish we can make Humanizer trend in Github again like what happened in January 2014.

@DoCode
Copy link
Author

DoCode commented Mar 6, 2014

@MehdiK
Ok, I understand, of course.
My request was just a different:
Bytes for people to read.

@MehdiK
Copy link
Member

MehdiK commented Mar 6, 2014

@faisalr nitpicking: Humanizer doesn't depend on xUnit. It's only used in tests and internally; i.e. when you install-package Humanizer the ONLY thing you get is Humanizer :p

TBH I'm not quite sure how and why that happened in Jan: it might have been that it was blogged about on C9 MSDN, donno; but man I'd love to see it trend again.

@DoCode the proposed APIs do that too: 20000000.Bytes().Humanize() => "20 MB" but it will leave our hands open for more types for input and output. I hope you don't mind it!

@DoCode
Copy link
Author

DoCode commented Mar 6, 2014

@MehdiK
No, no...
That would be very nice.

GeorgeHahn added a commit to GeorgeHahn/Humanizer that referenced this issue Mar 6, 2014
Needs:
- [] Rest of tests
- [] Dehumanize byte quantities
- [] Refactor namespaces all around
- [] Add to readme
- [] Licensing
@GeorgeHahn
Copy link
Contributor

Needs work, but what does everyone think of 7e1bdc6?

Here's what needs to be done:

  • Rest of tests
  • Dehumanize support
  • Refactor namespaces all around
  • Add API to readme
  • Licensing

API is (2.0).Terabytes(), which gives you a ByteSize instance. From there, you can perform operations ((2.0).Terabytes() + (2.0).Gigabytes())), convert sizes ((1.0).Bytes().Bits), or humanize them ((1.0).Gigabytes().Humanize()).

@MehdiK
Copy link
Member

MehdiK commented Mar 17, 2014

Hey guys,

Just wanted to say that with a great effort from @GeorgeHahn this has now been released to NuGet as v1.13.2.

Special thanks goes to @omar for his awesome ByteSize library. Hopefully this will invite Omar to bring more of his awesomeness to Humanizer :)

@MehdiK MehdiK closed this as completed Mar 17, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants