aboutsummaryrefslogtreecommitdiff
path: root/weed/util
diff options
context:
space:
mode:
Diffstat (limited to 'weed/util')
-rw-r--r--weed/util/bytes.go107
-rw-r--r--weed/util/bytes_test.go85
2 files changed, 192 insertions, 0 deletions
diff --git a/weed/util/bytes.go b/weed/util/bytes.go
index c2a4df108..260e5067e 100644
--- a/weed/util/bytes.go
+++ b/weed/util/bytes.go
@@ -5,8 +5,13 @@ import (
"crypto/md5"
"crypto/rand"
"encoding/base64"
+ "errors"
"fmt"
"io"
+ "math"
+ "strconv"
+ "strings"
+ "unicode"
)
// BytesToHumanReadable returns the converted human readable representation of the bytes.
@@ -161,3 +166,105 @@ func NewBytesReader(b []byte) *BytesReader {
Reader: bytes.NewReader(b),
}
}
+
+// EmptyTo returns to if s is empty.
+func EmptyTo(s, to string) string {
+ if s == "" {
+ return to
+ }
+
+ return s
+}
+
+var ErrMinFreeSpaceBadValue = errors.New("minFreeSpace is invalid")
+
+// ParseMinFreeSpace parses min free space expression s as percentage like 1,10 or human readable size like 10G
+func ParseMinFreeSpace(s string) (float32, error) {
+ if value, e := strconv.ParseFloat(s, 32); e == nil {
+ if value < 0 || value > 100 {
+ return 0, ErrMinFreeSpaceBadValue
+ }
+ return float32(value), nil
+ } else if directSize, e2 := ParseBytes(s); e2 == nil {
+ if directSize <= 100 {
+ return 0, ErrMinFreeSpaceBadValue
+ }
+ return float32(directSize), nil
+ }
+
+ return 0, ErrMinFreeSpaceBadValue
+}
+
+// ParseBytes parses a string representation of bytes into the number
+// of bytes it represents.
+//
+// See Also: Bytes, IBytes.
+//
+// ParseBytes("42MB") -> 42000000, nil
+// ParseBytes("42 MB") -> 42000000, nil
+// ParseBytes("42 mib") -> 44040192, nil
+func ParseBytes(s string) (uint64, error) {
+ lastDigit := 0
+ hasComma := false
+ for _, r := range s {
+ if !(unicode.IsDigit(r) || r == '.' || r == ',') {
+ break
+ }
+ if r == ',' {
+ hasComma = true
+ }
+ lastDigit++
+ }
+
+ num := s[:lastDigit]
+ if hasComma {
+ num = strings.Replace(num, ",", "", -1)
+ }
+
+ f, err := strconv.ParseFloat(num, 64)
+ if err != nil {
+ return 0, err
+ }
+
+ extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
+ if m, ok := bytesSizeTable[extra]; ok {
+ f *= float64(m)
+ if f >= math.MaxUint64 {
+ return 0, fmt.Errorf("too large: %v", s)
+ }
+ return uint64(f), nil
+ }
+
+ return 0, fmt.Errorf("unhandled size name: %v", extra)
+}
+
+var bytesSizeTable = map[string]uint64{
+ "b": Byte, "kib": KiByte, "kb": KByte, "mib": MiByte, "mb": MByte, "gib": GiByte, "gb": GByte,
+ "tib": TiByte, "tb": TByte, "pib": PiByte, "pb": PByte, "eib": EiByte, "eb": EByte,
+ // Without suffix
+ "": Byte, "ki": KiByte, "k": KByte, "mi": MiByte, "m": MByte, "gi": GiByte, "g": GByte,
+ "ti": TiByte, "t": TByte, "pi": PiByte, "p": PByte, "ei": EiByte, "e": EByte,
+}
+
+// IEC Sizes.
+// kibis of bits
+const (
+ Byte = 1 << (iota * 10)
+ KiByte
+ MiByte
+ GiByte
+ TiByte
+ PiByte
+ EiByte
+)
+
+// SI Sizes.
+const (
+ IByte = 1
+ KByte = IByte * 1000
+ MByte = KByte * 1000
+ GByte = MByte * 1000
+ TByte = GByte * 1000
+ PByte = TByte * 1000
+ EByte = PByte * 1000
+)
diff --git a/weed/util/bytes_test.go b/weed/util/bytes_test.go
new file mode 100644
index 000000000..4a9c25e52
--- /dev/null
+++ b/weed/util/bytes_test.go
@@ -0,0 +1,85 @@
+package util
+
+import "testing"
+
+func TestParseMinFreeSpace(t *testing.T) {
+ tests := []struct {
+ in string
+ ok bool
+ value float32
+ }{
+ {in: "42", ok: true, value: 42},
+ {in: "-1", ok: false, value: 0},
+ {in: "101", ok: false, value: 0},
+ {in: "100B", ok: false, value: 0},
+ {in: "100Ki", ok: true, value: 100 * 1024},
+ {in: "100GiB", ok: true, value: 100 * 1024 * 1024 * 1024},
+ {in: "42M", ok: true, value: 42 * 1000 * 1000},
+ }
+
+ for _, p := range tests {
+ got, err := ParseMinFreeSpace(p.in)
+ if p.ok != (err == nil) {
+ t.Errorf("failed to test %v", p.in)
+ }
+ if p.ok && err == nil && got != p.value {
+ t.Errorf("failed to test %v", p.in)
+ }
+ }
+}
+
+func TestByteParsing(t *testing.T) {
+ tests := []struct {
+ in string
+ exp uint64
+ }{
+ {"42", 42},
+ {"42MB", 42000000},
+ {"42MiB", 44040192},
+ {"42mb", 42000000},
+ {"42mib", 44040192},
+ {"42MIB", 44040192},
+ {"42 MB", 42000000},
+ {"42 MiB", 44040192},
+ {"42 mb", 42000000},
+ {"42 mib", 44040192},
+ {"42 MIB", 44040192},
+ {"42.5MB", 42500000},
+ {"42.5MiB", 44564480},
+ {"42.5 MB", 42500000},
+ {"42.5 MiB", 44564480},
+ // No need to say B
+ {"42M", 42000000},
+ {"42Mi", 44040192},
+ {"42m", 42000000},
+ {"42mi", 44040192},
+ {"42MI", 44040192},
+ {"42 M", 42000000},
+ {"42 Mi", 44040192},
+ {"42 m", 42000000},
+ {"42 mi", 44040192},
+ {"42 MI", 44040192},
+ {"42.5M", 42500000},
+ {"42.5Mi", 44564480},
+ {"42.5 M", 42500000},
+ {"42.5 Mi", 44564480},
+ // Bug #42
+ {"1,005.03 MB", 1005030000},
+ // Large testing, breaks when too much larger than
+ // this.
+ {"12.5 EB", uint64(12.5 * float64(EByte))},
+ {"12.5 E", uint64(12.5 * float64(EByte))},
+ {"12.5 EiB", uint64(12.5 * float64(EiByte))},
+ }
+
+ for _, p := range tests {
+ got, err := ParseBytes(p.in)
+ if err != nil {
+ t.Errorf("Couldn't parse %v: %v", p.in, err)
+ }
+ if got != p.exp {
+ t.Errorf("Expected %v for %v, got %v",
+ p.exp, p.in, got)
+ }
+ }
+}