mirror of
https://github.com/rclone/rclone
synced 2024-12-23 14:23:44 +01:00
ae3963e4b4
Before this change options were read and set in native format. This means for example nanoseconds for durations or an integer for enumerated types, which isn't very convenient for humans. This change enables these types to be set with a string with the syntax as used in the command line instead, so `"10s"` rather than `10000000000` or `"DEBUG"` rather than `8` for log level.
215 lines
5.0 KiB
Go
215 lines
5.0 KiB
Go
package fs
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Duration is a time.Duration with some more parsing options
|
|
type Duration time.Duration
|
|
|
|
// DurationOff is the default value for flags which can be turned off
|
|
const DurationOff = Duration((1 << 63) - 1)
|
|
|
|
// Turn Duration into a string
|
|
func (d Duration) String() string {
|
|
if d == DurationOff {
|
|
return "off"
|
|
}
|
|
for i := len(ageSuffixes) - 2; i >= 0; i-- {
|
|
ageSuffix := &ageSuffixes[i]
|
|
if math.Abs(float64(d)) >= float64(ageSuffix.Multiplier) {
|
|
timeUnits := float64(d) / float64(ageSuffix.Multiplier)
|
|
return strconv.FormatFloat(timeUnits, 'f', -1, 64) + ageSuffix.Suffix
|
|
}
|
|
}
|
|
return time.Duration(d).String()
|
|
}
|
|
|
|
// IsSet returns if the duration is != DurationOff
|
|
func (d Duration) IsSet() bool {
|
|
return d != DurationOff
|
|
}
|
|
|
|
// We use time conventions
|
|
var ageSuffixes = []struct {
|
|
Suffix string
|
|
Multiplier time.Duration
|
|
}{
|
|
{Suffix: "d", Multiplier: time.Hour * 24},
|
|
{Suffix: "w", Multiplier: time.Hour * 24 * 7},
|
|
{Suffix: "M", Multiplier: time.Hour * 24 * 30},
|
|
{Suffix: "y", Multiplier: time.Hour * 24 * 365},
|
|
|
|
// Default to second
|
|
{Suffix: "", Multiplier: time.Second},
|
|
}
|
|
|
|
// parse the age as suffixed ages
|
|
func parseDurationSuffixes(age string) (time.Duration, error) {
|
|
var period float64
|
|
|
|
for _, ageSuffix := range ageSuffixes {
|
|
if strings.HasSuffix(age, ageSuffix.Suffix) {
|
|
numberString := age[:len(age)-len(ageSuffix.Suffix)]
|
|
var err error
|
|
period, err = strconv.ParseFloat(numberString, 64)
|
|
if err != nil {
|
|
return time.Duration(0), err
|
|
}
|
|
period *= float64(ageSuffix.Multiplier)
|
|
break
|
|
}
|
|
}
|
|
|
|
return time.Duration(period), nil
|
|
}
|
|
|
|
// time formats to try parsing ages as - in order
|
|
var timeFormats = []string{
|
|
time.RFC3339,
|
|
"2006-01-02T15:04:05",
|
|
"2006-01-02 15:04:05",
|
|
"2006-01-02",
|
|
}
|
|
|
|
// parse the age as time before the epoch in various date formats
|
|
func parseDurationDates(age string, epoch time.Time) (t time.Duration, err error) {
|
|
var instant time.Time
|
|
for _, timeFormat := range timeFormats {
|
|
instant, err = time.Parse(timeFormat, age)
|
|
if err == nil {
|
|
return epoch.Sub(instant), nil
|
|
}
|
|
}
|
|
return t, err
|
|
}
|
|
|
|
// parseDurationFromNow parses a duration string. Allows ParseDuration to match the time
|
|
// package and easier testing within the fs package.
|
|
func parseDurationFromNow(age string, getNow func() time.Time) (d time.Duration, err error) {
|
|
if age == "off" {
|
|
return time.Duration(DurationOff), nil
|
|
}
|
|
|
|
// Attempt to parse as a time.Duration first
|
|
d, err = time.ParseDuration(age)
|
|
if err == nil {
|
|
return d, nil
|
|
}
|
|
|
|
d, err = parseDurationSuffixes(age)
|
|
if err == nil {
|
|
return d, nil
|
|
}
|
|
|
|
d, err = parseDurationDates(age, getNow())
|
|
if err == nil {
|
|
return d, nil
|
|
}
|
|
|
|
return d, err
|
|
}
|
|
|
|
// ParseDuration parses a duration string. Accept ms|s|m|h|d|w|M|y suffixes. Defaults to second if not provided
|
|
func ParseDuration(age string) (time.Duration, error) {
|
|
return parseDurationFromNow(age, time.Now)
|
|
}
|
|
|
|
// ReadableString parses d into a human readable duration.
|
|
// Based on https://github.com/hako/durafmt
|
|
func (d Duration) ReadableString() string {
|
|
switch d {
|
|
case DurationOff:
|
|
return "off"
|
|
case 0:
|
|
return "0s"
|
|
}
|
|
|
|
readableString := ""
|
|
|
|
// Check for minus durations.
|
|
if d < 0 {
|
|
readableString += "-"
|
|
}
|
|
|
|
duration := time.Duration(math.Abs(float64(d)))
|
|
|
|
// Convert duration.
|
|
seconds := int64(duration.Seconds()) % 60
|
|
minutes := int64(duration.Minutes()) % 60
|
|
hours := int64(duration.Hours()) % 24
|
|
days := int64(duration/(24*time.Hour)) % 365 % 7
|
|
|
|
// Edge case between 364 and 365 days.
|
|
// We need to calculate weeks from what is left from years
|
|
leftYearDays := int64(duration/(24*time.Hour)) % 365
|
|
weeks := leftYearDays / 7
|
|
if leftYearDays >= 364 && leftYearDays < 365 {
|
|
weeks = 52
|
|
}
|
|
|
|
years := int64(duration/(24*time.Hour)) / 365
|
|
milliseconds := int64(duration/time.Millisecond) -
|
|
(seconds * 1000) - (minutes * 60000) - (hours * 3600000) -
|
|
(days * 86400000) - (weeks * 604800000) - (years * 31536000000)
|
|
|
|
// Create a map of the converted duration time.
|
|
durationMap := map[string]int64{
|
|
"ms": milliseconds,
|
|
"s": seconds,
|
|
"m": minutes,
|
|
"h": hours,
|
|
"d": days,
|
|
"w": weeks,
|
|
"y": years,
|
|
}
|
|
|
|
// Construct duration string.
|
|
for _, u := range [...]string{"y", "w", "d", "h", "m", "s", "ms"} {
|
|
v := durationMap[u]
|
|
strval := strconv.FormatInt(v, 10)
|
|
if v == 0 {
|
|
continue
|
|
}
|
|
readableString += strval + u
|
|
}
|
|
|
|
return readableString
|
|
}
|
|
|
|
// Set a Duration
|
|
func (d *Duration) Set(s string) error {
|
|
duration, err := ParseDuration(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*d = Duration(duration)
|
|
return nil
|
|
}
|
|
|
|
// Type of the value
|
|
func (d Duration) Type() string {
|
|
return "Duration"
|
|
}
|
|
|
|
// UnmarshalJSON makes sure the value can be parsed as a string or integer in JSON
|
|
func (d *Duration) UnmarshalJSON(in []byte) error {
|
|
return UnmarshalJSONFlag(in, d, func(i int64) error {
|
|
*d = Duration(i)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Scan implements the fmt.Scanner interface
|
|
func (d *Duration) Scan(s fmt.ScanState, ch rune) error {
|
|
token, err := s.Token(true, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return d.Set(string(token))
|
|
}
|