mirror of
https://github.com/rclone/rclone.git
synced 2026-10-10 06:55:45 -04:00
BwPair.String printed a sub-KiB bandwidth as a bare number, and Set reads a bare number as KiB, so a rate under 1 KiB grew by a factor of 1024 whenever it went through a string. rc core/bwlimit reports the current rate in that format and accepts it back, so reading the limit and setting it again raised it.63c4fef27fixed the same corruption for config values by suffixing bare numbers with B inside Option.String. Move that into a SizeSuffix method and use it from BwPair.String as well. (cherry picked from commit60bdc6d454)
253 lines
6.0 KiB
Go
253 lines
6.0 KiB
Go
package fs
|
|
|
|
// SizeSuffix is parsed by flag with K/M/G binary suffixes
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// SizeSuffix is an int64 with a friendly way of printing setting
|
|
type SizeSuffix int64
|
|
|
|
// Common multipliers for SizeSuffix
|
|
const (
|
|
SizeSuffixBase SizeSuffix = 1 << (iota * 10)
|
|
Kibi
|
|
Mebi
|
|
Gibi
|
|
Tebi
|
|
Pebi
|
|
Exbi
|
|
)
|
|
const (
|
|
// SizeSuffixMax is the largest SizeSuffix multiplier
|
|
SizeSuffixMax = Exbi
|
|
// SizeSuffixMaxValue is the largest value that can be used to create SizeSuffix
|
|
SizeSuffixMaxValue = math.MaxInt64
|
|
// SizeSuffixMinValue is the smallest value that can be used to create SizeSuffix
|
|
SizeSuffixMinValue = math.MinInt64
|
|
)
|
|
|
|
// Turn SizeSuffix into a string and a suffix
|
|
func (x SizeSuffix) string() (string, string) {
|
|
scaled := float64(0)
|
|
suffix := ""
|
|
switch {
|
|
case x < 0:
|
|
return "off", ""
|
|
case x == 0:
|
|
return "0", ""
|
|
case x < Kibi:
|
|
scaled = float64(x)
|
|
suffix = ""
|
|
case x < Mebi:
|
|
scaled = float64(x) / float64(Kibi)
|
|
suffix = "Ki"
|
|
case x < Gibi:
|
|
scaled = float64(x) / float64(Mebi)
|
|
suffix = "Mi"
|
|
case x < Tebi:
|
|
scaled = float64(x) / float64(Gibi)
|
|
suffix = "Gi"
|
|
case x < Pebi:
|
|
scaled = float64(x) / float64(Tebi)
|
|
suffix = "Ti"
|
|
case x < Exbi:
|
|
scaled = float64(x) / float64(Pebi)
|
|
suffix = "Pi"
|
|
default:
|
|
scaled = float64(x) / float64(Exbi)
|
|
suffix = "Ei"
|
|
}
|
|
if math.Floor(scaled) == scaled {
|
|
return fmt.Sprintf("%.0f", scaled), suffix
|
|
}
|
|
return fmt.Sprintf("%.3f", scaled), suffix
|
|
}
|
|
|
|
// String turns SizeSuffix into a string
|
|
func (x SizeSuffix) String() string {
|
|
val, suffix := x.string()
|
|
return val + suffix
|
|
}
|
|
|
|
// stringRoundTrip turns SizeSuffix into a string which Set parses back to the
|
|
// same value. A bare number is read by Set as KiB, so values below 1 KiB get a
|
|
// "B" suffix to stop them being scaled up.
|
|
func (x SizeSuffix) stringRoundTrip() string {
|
|
str := x.String()
|
|
if len(str) > 0 && str != "0" {
|
|
if lastDigit := str[len(str)-1]; lastDigit >= '0' && lastDigit <= '9' {
|
|
str += "B"
|
|
}
|
|
}
|
|
return str
|
|
}
|
|
|
|
// Unit turns SizeSuffix into a string with a unit
|
|
func (x SizeSuffix) unit(unit string) string {
|
|
val, suffix := x.string()
|
|
if val == "off" {
|
|
return val
|
|
}
|
|
return val + " " + suffix + unit
|
|
}
|
|
|
|
// BitUnit turns SizeSuffix into a string with bit unit
|
|
func (x SizeSuffix) BitUnit() string {
|
|
return x.unit("bit")
|
|
}
|
|
|
|
// BitRateUnit turns SizeSuffix into a string with bit rate unit
|
|
func (x SizeSuffix) BitRateUnit() string {
|
|
return x.unit("bit/s")
|
|
}
|
|
|
|
// ByteUnit turns SizeSuffix into a string with byte unit
|
|
func (x SizeSuffix) ByteUnit() string {
|
|
return x.unit("B")
|
|
}
|
|
|
|
// ByteRateUnit turns SizeSuffix into a string with byte rate unit
|
|
func (x SizeSuffix) ByteRateUnit() string {
|
|
return x.unit("B/s")
|
|
}
|
|
|
|
func (x *SizeSuffix) multiplierFromSymbol(s byte) (found bool, multiplier float64) {
|
|
switch s {
|
|
case 'k', 'K':
|
|
return true, float64(Kibi)
|
|
case 'm', 'M':
|
|
return true, float64(Mebi)
|
|
case 'g', 'G':
|
|
return true, float64(Gibi)
|
|
case 't', 'T':
|
|
return true, float64(Tebi)
|
|
case 'p', 'P':
|
|
return true, float64(Pebi)
|
|
case 'e', 'E':
|
|
return true, float64(Exbi)
|
|
default:
|
|
return false, float64(SizeSuffixBase)
|
|
}
|
|
}
|
|
|
|
// Set a SizeSuffix
|
|
func (x *SizeSuffix) 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(Kibi)
|
|
case 'b', 'B':
|
|
if len(s) > 2 && s[len(s)-2] == 'i' {
|
|
suffix = s[len(s)-3]
|
|
suffixLen = 3
|
|
if multiplierFound, multiplier = x.multiplierFromSymbol(suffix); !multiplierFound {
|
|
return fmt.Errorf("bad suffix %q", suffix)
|
|
}
|
|
// Could also support SI form MB, and treat it equivalent to MiB, but perhaps better to reserve it for CountSuffix?
|
|
//} else if len(s) > 1 {
|
|
// suffix = s[len(s)-2]
|
|
// if multiplierFound, multiplier = x.multiplierFromSymbol(suffix); multiplierFound {
|
|
// suffixLen = 2
|
|
// }
|
|
//}
|
|
} else {
|
|
multiplier = float64(SizeSuffixBase)
|
|
}
|
|
case 'i', 'I':
|
|
if len(s) > 1 {
|
|
suffix = s[len(s)-2]
|
|
suffixLen = 2
|
|
multiplierFound, multiplier = x.multiplierFromSymbol(suffix)
|
|
}
|
|
if !multiplierFound {
|
|
return fmt.Errorf("bad suffix %q", suffix)
|
|
}
|
|
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 = SizeSuffix(value)
|
|
return nil
|
|
}
|
|
|
|
// Type of the value
|
|
func (x SizeSuffix) Type() string {
|
|
return "SizeSuffix"
|
|
}
|
|
|
|
// Scan implements the fmt.Scanner interface
|
|
func (x *SizeSuffix) Scan(s fmt.ScanState, ch rune) error {
|
|
token, err := s.Token(true, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return x.Set(string(token))
|
|
}
|
|
|
|
// SizeSuffixList is a slice SizeSuffix values
|
|
type SizeSuffixList []SizeSuffix
|
|
|
|
func (l SizeSuffixList) Len() int { return len(l) }
|
|
func (l SizeSuffixList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
|
func (l SizeSuffixList) Less(i, j int) bool { return l[i] < l[j] }
|
|
|
|
// Sort sorts the list
|
|
func (l SizeSuffixList) Sort() {
|
|
sort.Sort(l)
|
|
}
|
|
|
|
// UnmarshalJSONFlag unmarshals a JSON input for a flag. If the input
|
|
// is a string then it calls the Set method on the flag otherwise it
|
|
// calls the setInt function with a parsed int64.
|
|
func UnmarshalJSONFlag(in []byte, x interface{ Set(string) error }, setInt func(int64) error) error {
|
|
// Try to parse as string first
|
|
var s string
|
|
err := json.Unmarshal(in, &s)
|
|
if err == nil {
|
|
return x.Set(s)
|
|
}
|
|
// If that fails parse as integer
|
|
var i int64
|
|
err = json.Unmarshal(in, &i)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return setInt(i)
|
|
}
|
|
|
|
// UnmarshalJSON makes sure the value can be parsed as a string or integer in JSON
|
|
func (x *SizeSuffix) UnmarshalJSON(in []byte) error {
|
|
return UnmarshalJSONFlag(in, x, func(i int64) error {
|
|
*x = SizeSuffix(i)
|
|
return nil
|
|
})
|
|
}
|