// Copyright (c) 2020 Gitpod GmbH. All rights reserved. // Licensed under the GNU Affero General Public License (AGPL). // See License.AGPL.txt in the project root for license information. package quota import ( "encoding/json" "errors" "fmt" "regexp" "strconv" "golang.org/x/xerrors" ) // Size denotes a filesize / amount of bytes type Size int64 const ( // Byte is a single byte Byte Size = 1 // Kilobyte is 1024 bytes Kilobyte Size = 1024 * Byte // Megabyte is 1024 kilobytes Megabyte Size = 1024 * Kilobyte // Gigabyte is 1024 megabytes Gigabyte Size = 1024 * Megabyte // Terabyte is 1024 gigabyte Terabyte Size = 1024 * Gigabyte ) var ( sizeRegexp = regexp.MustCompile(`^(\d+)(k|m|g|t)?$`) // ErrInvalidSize is returned by ParseSize if input was not a valid size ErrInvalidSize = errors.New("invalid size") ) // ParseSize parses a number of bytes (e.g. 10) into a size. // ParseSize supports units: k(ilobyte), m(egabyte), (g)igatebyte, (t)erabyte. E.g. 50m or 200g func ParseSize(q string) (Size, error) { matches := sizeRegexp.FindSubmatch([]byte(q)) if len(matches) == 0 { return 0, ErrInvalidSize } val, err := strconv.ParseUint(string(matches[1]), 10, 64) if err != nil { return 0, ErrInvalidSize } size := Size(val) unit := string(matches[2]) switch unit { case "": size = size * Byte case "k": size = size * Kilobyte case "m": size = size * Megabyte case "g": size = size * Gigabyte case "t": size = size * Terabyte } return size, nil } // String converts the size to a string func (s Size) String() string { if s == 0 { return "0" } steps := []struct { Prefix string Q Size }{ {"t", Terabyte}, {"g", Gigabyte}, {"m", Megabyte}, {"k", Kilobyte}, } for _, stp := range steps { if s%stp.Q == 0 { return fmt.Sprintf("%d%s", s/stp.Q, stp.Prefix) } } return fmt.Sprintf("%d", s) } // MarshalJSON marshals a duration to JSON func (s Size) MarshalJSON() ([]byte, error) { return json.Marshal(s.String()) } // UnmarshalJSON unmarshals a duration from JSON func (s *Size) UnmarshalJSON(b []byte) error { var v interface{} if err := json.Unmarshal(b, &v); err != nil { return err } switch value := v.(type) { case string: if value == "" { *s = 0 return nil } tmp, err := ParseSize(value) if err != nil { return err } *s = tmp return nil default: return xerrors.Errorf("invalid size: %s", string(b)) } }