rclone/fs/countsuffix.go
2024-08-15 22:08:34 +01:00

182 lines
4.2 KiB
Go

package fs
// CountSuffix is parsed by flag with k/M/G decimal suffixes
import (
"errors"
"fmt"
"math"
"sort"
"strconv"
"strings"
)
// CountSuffix is an int64 with a friendly way of printing setting
type CountSuffix int64
// Common multipliers for SizeSuffix
const (
CountSuffixBase CountSuffix = 1
Kilo = 1000 * CountSuffixBase
Mega = 1000 * Kilo
Giga = 1000 * Mega
Tera = 1000 * Giga
Peta = 1000 * Tera
Exa = 1000 * Peta
)
const (
// CountSuffixMax is the largest CountSuffix multiplier
CountSuffixMax = Exa
// CountSuffixMaxValue is the largest value that can be used to create CountSuffix
CountSuffixMaxValue = math.MaxInt64
// CountSuffixMinValue is the smallest value that can be used to create CountSuffix
CountSuffixMinValue = math.MinInt64
)
// Turn CountSuffix into a string and a suffix
func (x CountSuffix) string() (string, string) {
scaled := float64(0)
suffix := ""
switch {
case x < 0:
return "off", ""
case x == 0:
return "0", ""
case x < Kilo:
scaled = float64(x)
suffix = ""
case x < Mega:
scaled = float64(x) / float64(Kilo)
suffix = "k"
case x < Giga:
scaled = float64(x) / float64(Mega)
suffix = "M"
case x < Tera:
scaled = float64(x) / float64(Giga)
suffix = "G"
case x < Peta:
scaled = float64(x) / float64(Tera)
suffix = "T"
case x < Exa:
scaled = float64(x) / float64(Peta)
suffix = "P"
default:
scaled = float64(x) / float64(Exa)
suffix = "E"
}
if math.Floor(scaled) == scaled {
return fmt.Sprintf("%.0f", scaled), suffix
}
return fmt.Sprintf("%.3f", scaled), suffix
}
// String turns CountSuffix into a string
func (x CountSuffix) String() string {
val, suffix := x.string()
return val + suffix
}
// Unit turns CountSuffix into a string with a unit
func (x CountSuffix) Unit(unit string) string {
val, suffix := x.string()
if val == "off" {
return val
}
return val + " " + suffix + unit
}
func (x *CountSuffix) multiplierFromSymbol(s byte) (found bool, multiplier float64) {
switch s {
case 'k', 'K':
return true, float64(Kilo)
case 'm', 'M':
return true, float64(Mega)
case 'g', 'G':
return true, float64(Giga)
case 't', 'T':
return true, float64(Tera)
case 'p', 'P':
return true, float64(Peta)
case 'e', 'E':
return true, float64(Exa)
default:
return false, float64(CountSuffixBase)
}
}
// Set a CountSuffix
func (x *CountSuffix) Set(s string) error {
if len(s) == 0 {
return errors.New("empty string")
}
if strings.ToLower(s) == "off" {
*x = -1
return nil
}
suffix := s[len(s)-1]
suffixLen := 1
multiplierFound := false
var multiplier float64
switch suffix {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.':
suffixLen = 0
multiplier = float64(Kilo)
case 'b', 'B':
if len(s) > 1 {
suffix = s[len(s)-2]
if multiplierFound, multiplier = x.multiplierFromSymbol(suffix); multiplierFound {
suffixLen = 2
}
} else {
multiplier = float64(CountSuffixBase)
}
default:
if multiplierFound, multiplier = x.multiplierFromSymbol(suffix); !multiplierFound {
return fmt.Errorf("bad suffix %q", suffix)
}
}
s = s[:len(s)-suffixLen]
value, err := strconv.ParseFloat(s, 64)
if err != nil {
return err
}
if value < 0 {
return fmt.Errorf("size can't be negative %q", s)
}
value *= multiplier
*x = CountSuffix(value)
return nil
}
// Type of the value
func (x CountSuffix) Type() string {
return "CountSuffix"
}
// Scan implements the fmt.Scanner interface
func (x *CountSuffix) Scan(s fmt.ScanState, ch rune) error {
token, err := s.Token(true, nil)
if err != nil {
return err
}
return x.Set(string(token))
}
// CountSuffixList is a slice CountSuffix values
type CountSuffixList []CountSuffix
func (l CountSuffixList) Len() int { return len(l) }
func (l CountSuffixList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func (l CountSuffixList) Less(i, j int) bool { return l[i] < l[j] }
// Sort sorts the list
func (l CountSuffixList) Sort() {
sort.Sort(l)
}
// UnmarshalJSON makes sure the value can be parsed as a string or integer in JSON
func (x *CountSuffix) UnmarshalJSON(in []byte) error {
return UnmarshalJSONFlag(in, x, func(i int64) error {
*x = CountSuffix(i)
return nil
})
}