diff --git a/amazonclouddrive/amazonclouddrive.go b/amazonclouddrive/amazonclouddrive.go index 5d4e3deee..131f328db 100644 --- a/amazonclouddrive/amazonclouddrive.go +++ b/amazonclouddrive/amazonclouddrive.go @@ -31,7 +31,6 @@ import ( "github.com/ncw/rclone/pacer" "github.com/ncw/rclone/rest" "github.com/pkg/errors" - "github.com/spf13/pflag" "golang.org/x/oauth2" ) @@ -51,7 +50,7 @@ const ( var ( // Flags tempLinkThreshold = fs.SizeSuffix(9 << 30) // Download files bigger than this via the tempLink - uploadWaitPerGB = pflag.DurationP("acd-upload-wait-per-gb", "", 180*time.Second, "Additional time per GB to wait after a failed complete upload to see if it appears.") + uploadWaitPerGB = fs.DurationP("acd-upload-wait-per-gb", "", 180*time.Second, "Additional time per GB to wait after a failed complete upload to see if it appears.") // Description of how to auth for this app acdConfig = &oauth2.Config{ Scopes: []string{"clouddrive:read_all", "clouddrive:write"}, @@ -85,7 +84,7 @@ func init() { Help: "Amazon Application Client Secret - leave blank normally.", }}, }) - pflag.VarP(&tempLinkThreshold, "acd-templink-threshold", "", "Files >= this size will be downloaded via their tempLink.") + fs.VarP(&tempLinkThreshold, "acd-templink-threshold", "", "Files >= this size will be downloaded via their tempLink.") } // Fs represents a remote acd server diff --git a/b2/b2.go b/b2/b2.go index d2bef5f47..c1009bf51 100644 --- a/b2/b2.go +++ b/b2/b2.go @@ -25,7 +25,6 @@ import ( "github.com/ncw/rclone/pacer" "github.com/ncw/rclone/rest" "github.com/pkg/errors" - "github.com/spf13/pflag" ) const ( @@ -50,8 +49,8 @@ var ( minChunkSize = fs.SizeSuffix(100E6) chunkSize = fs.SizeSuffix(96 * 1024 * 1024) uploadCutoff = fs.SizeSuffix(200E6) - b2TestMode = pflag.StringP("b2-test-mode", "", "", "A flag string for X-Bz-Test-Mode header.") - b2Versions = pflag.BoolP("b2-versions", "", false, "Include old versions in directory listings.") + b2TestMode = fs.StringP("b2-test-mode", "", "", "A flag string for X-Bz-Test-Mode header.") + b2Versions = fs.BoolP("b2-versions", "", false, "Include old versions in directory listings.") errNotWithVersions = errors.New("can't modify or delete files in --b2-versions mode") ) @@ -73,8 +72,8 @@ func init() { }, }, }) - pflag.VarP(&uploadCutoff, "b2-upload-cutoff", "", "Cutoff for switching to chunked upload") - pflag.VarP(&chunkSize, "b2-chunk-size", "", "Upload chunk size. Must fit in memory.") + fs.VarP(&uploadCutoff, "b2-upload-cutoff", "", "Cutoff for switching to chunked upload") + fs.VarP(&chunkSize, "b2-chunk-size", "", "Upload chunk size. Must fit in memory.") } // Fs represents a remote b2 server diff --git a/cmd/cmd.go b/cmd/cmd.go index b50b7ecbe..5b8b155bb 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -26,13 +26,13 @@ import ( // Globals var ( // Flags - cpuProfile = pflag.StringP("cpuprofile", "", "", "Write cpu profile to file") - memProfile = pflag.String("memprofile", "", "Write memory profile to file") - statsInterval = pflag.DurationP("stats", "", time.Minute*1, "Interval between printing stats, e.g 500ms, 60s, 5m. (0 to disable)") - dataRateUnit = pflag.StringP("stats-unit", "", "bytes", "Show data rate in stats as either 'bits' or 'bytes'/s") + cpuProfile = fs.StringP("cpuprofile", "", "", "Write cpu profile to file") + memProfile = fs.StringP("memprofile", "", "", "Write memory profile to file") + statsInterval = fs.DurationP("stats", "", time.Minute*1, "Interval between printing stats, e.g 500ms, 60s, 5m. (0 to disable)") + dataRateUnit = fs.StringP("stats-unit", "", "bytes", "Show data rate in stats as either 'bits' or 'bytes'/s") version bool - logFile = pflag.StringP("log-file", "", "", "Log everything to this file") - retries = pflag.IntP("retries", "", 3, "Retry operations this many times if they fail") + logFile = fs.StringP("log-file", "", "", "Log everything to this file") + retries = fs.IntP("retries", "", 3, "Retry operations this many times if they fail") ) // Root is the main rclone command diff --git a/drive/drive.go b/drive/drive.go index 711a20cae..2abbea837 100644 --- a/drive/drive.go +++ b/drive/drive.go @@ -21,7 +21,6 @@ import ( "github.com/ncw/rclone/oauthutil" "github.com/ncw/rclone/pacer" "github.com/pkg/errors" - "github.com/spf13/pflag" "golang.org/x/oauth2" "golang.org/x/oauth2/google" "google.golang.org/api/drive/v2" @@ -42,10 +41,10 @@ const ( // Globals var ( // Flags - driveFullList = pflag.BoolP("drive-full-list", "", false, "Use a full listing for directory list. More data but usually quicker. (obsolete)") - driveAuthOwnerOnly = pflag.BoolP("drive-auth-owner-only", "", false, "Only consider files owned by the authenticated user. Requires drive-full-list.") - driveUseTrash = pflag.BoolP("drive-use-trash", "", false, "Send files to the trash instead of deleting permanently.") - driveExtensions = pflag.StringP("drive-formats", "", defaultExtensions, "Comma separated list of preferred formats for downloading Google docs.") + driveFullList = fs.BoolP("drive-full-list", "", false, "Use a full listing for directory list. More data but usually quicker. (obsolete)") + driveAuthOwnerOnly = fs.BoolP("drive-auth-owner-only", "", false, "Only consider files owned by the authenticated user. Requires drive-full-list.") + driveUseTrash = fs.BoolP("drive-use-trash", "", false, "Send files to the trash instead of deleting permanently.") + driveExtensions = fs.StringP("drive-formats", "", defaultExtensions, "Comma separated list of preferred formats for downloading Google docs.") // chunkSize is the size of the chunks created during a resumable upload and should be a power of two. // 1<<18 is the minimum size supported by the Google uploader, and there is no maximum. chunkSize = fs.SizeSuffix(8 * 1024 * 1024) @@ -103,8 +102,8 @@ func init() { Help: "Google Application Client Secret - leave blank normally.", }}, }) - pflag.VarP(&driveUploadCutoff, "drive-upload-cutoff", "", "Cutoff for switching to chunked upload") - pflag.VarP(&chunkSize, "drive-chunk-size", "", "Upload chunk size. Must a power of 2 >= 256k.") + fs.VarP(&driveUploadCutoff, "drive-upload-cutoff", "", "Cutoff for switching to chunked upload") + fs.VarP(&chunkSize, "drive-chunk-size", "", "Upload chunk size. Must a power of 2 >= 256k.") // Invert mimeTypeToExtension extensionToMimeType = make(map[string]string, len(mimeTypeToExtension)) diff --git a/dropbox/dropbox.go b/dropbox/dropbox.go index b7389dd4c..49989cbf8 100644 --- a/dropbox/dropbox.go +++ b/dropbox/dropbox.go @@ -22,7 +22,6 @@ import ( "github.com/ncw/rclone/fs" "github.com/ncw/rclone/oauthutil" "github.com/pkg/errors" - "github.com/spf13/pflag" "github.com/stacktic/dropbox" ) @@ -58,7 +57,7 @@ func init() { Help: "Dropbox App Secret - leave blank normally.", }}, }) - pflag.VarP(&uploadChunkSize, "dropbox-chunk-size", "", fmt.Sprintf("Upload chunk size. Max %v.", maxUploadChunkSize)) + fs.VarP(&uploadChunkSize, "dropbox-chunk-size", "", fmt.Sprintf("Upload chunk size. Max %v.", maxUploadChunkSize)) } // Configuration helper - called after the user has put in the defaults diff --git a/fs/config.go b/fs/config.go index 9c1cd3807..557e13573 100644 --- a/fs/config.go +++ b/fs/config.go @@ -14,7 +14,6 @@ import ( "io" "io/ioutil" "log" - "math" "os" "os/user" "path" @@ -26,7 +25,6 @@ import ( "github.com/Unknwon/goconfig" "github.com/pkg/errors" - "github.com/spf13/pflag" "golang.org/x/crypto/nacl/secretbox" "golang.org/x/text/unicode/norm" ) @@ -47,18 +45,6 @@ const ( ConfigAutomatic = "config_automatic" ) -// SizeSuffix is parsed by flag with k/M/G suffixes -type SizeSuffix int64 - -// BwTimeSlot represents a bandwidth configuration at a point in time. -type BwTimeSlot struct { - hhmm int - bandwidth SizeSuffix -} - -// BwTimetable contains all configured time slots. -type BwTimetable []BwTimeSlot - // Global var ( // ConfigFile is the config file data structure @@ -70,35 +56,35 @@ var ( // Config is the global config Config = &ConfigInfo{} // Flags - verbose = pflag.BoolP("verbose", "v", false, "Print lots more stuff") - quiet = pflag.BoolP("quiet", "q", false, "Print as little stuff as possible") - modifyWindow = pflag.DurationP("modify-window", "", time.Nanosecond, "Max time diff to be considered the same") - checkers = pflag.IntP("checkers", "", 8, "Number of checkers to run in parallel.") - transfers = pflag.IntP("transfers", "", 4, "Number of file transfers to run in parallel.") - configFile = pflag.StringP("config", "", ConfigPath, "Config file.") - checkSum = pflag.BoolP("checksum", "c", false, "Skip based on checksum & size, not mod-time & size") - sizeOnly = pflag.BoolP("size-only", "", false, "Skip based on size only, not mod-time or checksum") - ignoreTimes = pflag.BoolP("ignore-times", "I", false, "Don't skip files that match size and time - transfer all files") - ignoreExisting = pflag.BoolP("ignore-existing", "", false, "Skip all files that exist on destination") - dryRun = pflag.BoolP("dry-run", "n", false, "Do a trial run with no permanent changes") - connectTimeout = pflag.DurationP("contimeout", "", 60*time.Second, "Connect timeout") - timeout = pflag.DurationP("timeout", "", 5*60*time.Second, "IO idle timeout") - dumpHeaders = pflag.BoolP("dump-headers", "", false, "Dump HTTP headers - may contain sensitive info") - dumpBodies = pflag.BoolP("dump-bodies", "", false, "Dump HTTP headers and bodies - may contain sensitive info") - dumpAuth = pflag.BoolP("dump-auth", "", false, "Dump HTTP headers with auth info") - skipVerify = pflag.BoolP("no-check-certificate", "", false, "Do not verify the server SSL certificate. Insecure.") - AskPassword = pflag.BoolP("ask-password", "", true, "Allow prompt for password for encrypted configuration.") - deleteBefore = pflag.BoolP("delete-before", "", false, "When synchronizing, delete files on destination before transfering") - deleteDuring = pflag.BoolP("delete-during", "", false, "When synchronizing, delete files during transfer (default)") - deleteAfter = pflag.BoolP("delete-after", "", false, "When synchronizing, delete files on destination after transfering") - trackRenames = pflag.BoolP("track-renames", "", false, "When synchronizing, track file renames and do a server side move if possible") - lowLevelRetries = pflag.IntP("low-level-retries", "", 10, "Number of low level retries to do.") - updateOlder = pflag.BoolP("update", "u", false, "Skip files that are newer on the destination.") - noGzip = pflag.BoolP("no-gzip-encoding", "", false, "Don't set Accept-Encoding: gzip.") - maxDepth = pflag.IntP("max-depth", "", -1, "If set limits the recursion depth to this.") - ignoreSize = pflag.BoolP("ignore-size", "", false, "Ignore size when skipping use mod-time or checksum.") - noTraverse = pflag.BoolP("no-traverse", "", false, "Don't traverse destination file system on copy.") - noUpdateModTime = pflag.BoolP("no-update-modtime", "", false, "Don't update destination mod-time if files identical.") + verbose = BoolP("verbose", "v", false, "Print lots more stuff") + quiet = BoolP("quiet", "q", false, "Print as little stuff as possible") + modifyWindow = DurationP("modify-window", "", time.Nanosecond, "Max time diff to be considered the same") + checkers = IntP("checkers", "", 8, "Number of checkers to run in parallel.") + transfers = IntP("transfers", "", 4, "Number of file transfers to run in parallel.") + configFile = StringP("config", "", ConfigPath, "Config file.") + checkSum = BoolP("checksum", "c", false, "Skip based on checksum & size, not mod-time & size") + sizeOnly = BoolP("size-only", "", false, "Skip based on size only, not mod-time or checksum") + ignoreTimes = BoolP("ignore-times", "I", false, "Don't skip files that match size and time - transfer all files") + ignoreExisting = BoolP("ignore-existing", "", false, "Skip all files that exist on destination") + dryRun = BoolP("dry-run", "n", false, "Do a trial run with no permanent changes") + connectTimeout = DurationP("contimeout", "", 60*time.Second, "Connect timeout") + timeout = DurationP("timeout", "", 5*60*time.Second, "IO idle timeout") + dumpHeaders = BoolP("dump-headers", "", false, "Dump HTTP headers - may contain sensitive info") + dumpBodies = BoolP("dump-bodies", "", false, "Dump HTTP headers and bodies - may contain sensitive info") + dumpAuth = BoolP("dump-auth", "", false, "Dump HTTP headers with auth info") + skipVerify = BoolP("no-check-certificate", "", false, "Do not verify the server SSL certificate. Insecure.") + AskPassword = BoolP("ask-password", "", true, "Allow prompt for password for encrypted configuration.") + deleteBefore = BoolP("delete-before", "", false, "When synchronizing, delete files on destination before transfering") + deleteDuring = BoolP("delete-during", "", false, "When synchronizing, delete files during transfer (default)") + deleteAfter = BoolP("delete-after", "", false, "When synchronizing, delete files on destination after transfering") + trackRenames = BoolP("track-renames", "", false, "When synchronizing, track file renames and do a server side move if possible") + lowLevelRetries = IntP("low-level-retries", "", 10, "Number of low level retries to do.") + updateOlder = BoolP("update", "u", false, "Skip files that are newer on the destination.") + noGzip = BoolP("no-gzip-encoding", "", false, "Don't set Accept-Encoding: gzip.") + maxDepth = IntP("max-depth", "", -1, "If set limits the recursion depth to this.") + ignoreSize = BoolP("ignore-size", "", false, "Ignore size when skipping use mod-time or checksum.") + noTraverse = BoolP("no-traverse", "", false, "Don't traverse destination file system on copy.") + noUpdateModTime = BoolP("no-update-modtime", "", false, "Don't update destination mod-time if files identical.") bwLimit BwTimetable // Key to use for password en/decryption. @@ -107,216 +93,9 @@ var ( ) func init() { - pflag.VarP(&bwLimit, "bwlimit", "", "Bandwidth limit in kBytes/s, or use suffix b|k|M|G or a full timetable.") + VarP(&bwLimit, "bwlimit", "", "Bandwidth limit in kBytes/s, or use suffix b|k|M|G or a full timetable.") } -// 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 < 1024: - scaled = float64(x) - suffix = "" - case x < 1024*1024: - scaled = float64(x) / 1024 - suffix = "k" - case x < 1024*1024*1024: - scaled = float64(x) / 1024 / 1024 - suffix = "M" - default: - scaled = float64(x) / 1024 / 1024 / 1024 - suffix = "G" - } - 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 -} - -// 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 -} - -// 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 - var multiplier float64 - switch suffix { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.': - suffixLen = 0 - multiplier = 1 << 10 - case 'b', 'B': - multiplier = 1 - case 'k', 'K': - multiplier = 1 << 10 - case 'm', 'M': - multiplier = 1 << 20 - case 'g', 'G': - multiplier = 1 << 30 - default: - return errors.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 errors.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 "int64" -} - -// Check it satisfies the interface -var _ pflag.Value = (*SizeSuffix)(nil) - -// String returns a printable representation of BwTimetable. -func (x BwTimetable) String() string { - ret := []string{} - for _, ts := range x { - ret = append(ret, fmt.Sprintf("%04.4d,%s", ts.hhmm, ts.bandwidth.String())) - } - return strings.Join(ret, " ") -} - -// Set the bandwidth timetable. -func (x *BwTimetable) Set(s string) error { - // The timetable is formatted as: - // "hh:mm,bandwidth hh:mm,banwidth..." ex: "10:00,10G 11:30,1G 18:00,off" - // If only a single bandwidth identifier is provided, we assume constant bandwidth. - - if len(s) == 0 { - return errors.New("empty string") - } - // Single value without time specification. - if !strings.Contains(s, " ") && !strings.Contains(s, ",") { - ts := BwTimeSlot{} - if err := ts.bandwidth.Set(s); err != nil { - return err - } - ts.hhmm = 0 - *x = BwTimetable{ts} - return nil - } - - for _, tok := range strings.Split(s, " ") { - tv := strings.Split(tok, ",") - - // Format must be HH:MM,BW - if len(tv) != 2 { - return errors.Errorf("invalid time/bandwidth specification: %q", tok) - } - - // Basic timespec sanity checking - hhmm := tv[0] - if len(hhmm) != 5 { - return errors.Errorf("invalid time specification (hh:mm): %q", hhmm) - } - hh, err := strconv.Atoi(hhmm[0:2]) - if err != nil { - return errors.Errorf("invalid hour in time specification %q: %v", hhmm, err) - } - if hh < 0 || hh > 23 { - return errors.Errorf("invalid hour (must be between 00 and 23): %q", hh) - } - mm, err := strconv.Atoi(hhmm[3:]) - if err != nil { - return errors.Errorf("invalid minute in time specification: %q: %v", hhmm, err) - } - if mm < 0 || mm > 59 { - return errors.Errorf("invalid minute (must be between 00 and 59): %q", hh) - } - - ts := BwTimeSlot{ - hhmm: (hh * 100) + mm, - } - // Bandwidth limit for this time slot. - if err := ts.bandwidth.Set(tv[1]); err != nil { - return err - } - *x = append(*x, ts) - } - return nil -} - -// LimitAt returns a BwTimeSlot for the time requested. -func (x BwTimetable) LimitAt(tt time.Time) BwTimeSlot { - // If the timetable is empty, we return an unlimited BwTimeSlot starting at midnight. - if len(x) == 0 { - return BwTimeSlot{hhmm: 0, bandwidth: -1} - } - - hhmm := tt.Hour()*100 + tt.Minute() - - // By default, we return the last element in the timetable. This - // satisfies two conditions: 1) If there's only one element it - // will always be selected, and 2) The last element of the table - // will "wrap around" until overriden by an earlier time slot. - // there's only one time slot in the timetable. - ret := x[len(x)-1] - - mindif := 0 - first := true - - // Look for most recent time slot. - for _, ts := range x { - // Ignore the past - if hhmm < ts.hhmm { - continue - } - dif := ((hhmm / 100 * 60) + (hhmm % 100)) - ((ts.hhmm / 100 * 60) + (ts.hhmm % 100)) - if first { - mindif = dif - first = false - } - if dif <= mindif { - mindif = dif - ret = ts - } - } - - return ret -} - -// Type of the value -func (x BwTimetable) Type() string { - return "BwTimetable" -} - -// Check it satisfies the interface -var _ pflag.Value = (*BwTimetable)(nil) - // crypt internals var ( cryptKey = []byte{ diff --git a/fs/config_test.go b/fs/config_test.go index 7785d20f7..c21116b7f 100644 --- a/fs/config_test.go +++ b/fs/config_test.go @@ -4,190 +4,11 @@ import ( "bytes" "crypto/rand" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestSizeSuffixString(t *testing.T) { - for _, test := range []struct { - in float64 - want string - }{ - {0, "0"}, - {102, "102"}, - {1024, "1k"}, - {1024 * 1024, "1M"}, - {1024 * 1024 * 1024, "1G"}, - {10 * 1024 * 1024 * 1024, "10G"}, - {10.1 * 1024 * 1024 * 1024, "10.100G"}, - {-1, "off"}, - {-100, "off"}, - } { - ss := SizeSuffix(test.in) - got := ss.String() - assert.Equal(t, test.want, got) - } -} - -func TestSizeSuffixUnit(t *testing.T) { - for _, test := range []struct { - in float64 - want string - }{ - {0, "0 Bytes"}, - {102, "102 Bytes"}, - {1024, "1 kBytes"}, - {1024 * 1024, "1 MBytes"}, - {1024 * 1024 * 1024, "1 GBytes"}, - {10 * 1024 * 1024 * 1024, "10 GBytes"}, - {10.1 * 1024 * 1024 * 1024, "10.100 GBytes"}, - {-1, "off"}, - {-100, "off"}, - } { - ss := SizeSuffix(test.in) - got := ss.Unit("Bytes") - assert.Equal(t, test.want, got) - } -} - -func TestSizeSuffixSet(t *testing.T) { - for _, test := range []struct { - in string - want int64 - err bool - }{ - {"0", 0, false}, - {"1b", 1, false}, - {"102B", 102, false}, - {"0.1k", 102, false}, - {"0.1", 102, false}, - {"1K", 1024, false}, - {"1", 1024, false}, - {"2.5", 1024 * 2.5, false}, - {"1M", 1024 * 1024, false}, - {"1.g", 1024 * 1024 * 1024, false}, - {"10G", 10 * 1024 * 1024 * 1024, false}, - {"off", -1, false}, - {"OFF", -1, false}, - {"", 0, true}, - {"1p", 0, true}, - {"1.p", 0, true}, - {"1p", 0, true}, - {"-1K", 0, true}, - } { - ss := SizeSuffix(0) - err := ss.Set(test.in) - if test.err { - require.Error(t, err) - } else { - require.NoError(t, err) - } - assert.Equal(t, test.want, int64(ss)) - } -} - -func TestBwTimetableSet(t *testing.T) { - for _, test := range []struct { - in string - want BwTimetable - err bool - }{ - {"", BwTimetable{}, true}, - {"0", BwTimetable{BwTimeSlot{hhmm: 0, bandwidth: 0}}, false}, - {"666", BwTimetable{BwTimeSlot{hhmm: 0, bandwidth: 666 * 1024}}, false}, - {"10:20,666", BwTimetable{BwTimeSlot{hhmm: 1020, bandwidth: 666 * 1024}}, false}, - { - "11:00,333 13:40,666 23:50,10M 23:59,off", - BwTimetable{ - BwTimeSlot{hhmm: 1100, bandwidth: 333 * 1024}, - BwTimeSlot{hhmm: 1340, bandwidth: 666 * 1024}, - BwTimeSlot{hhmm: 2350, bandwidth: 10 * 1024 * 1024}, - BwTimeSlot{hhmm: 2359, bandwidth: -1}, - }, - false, - }, - {"bad,bad", BwTimetable{}, true}, - {"bad bad", BwTimetable{}, true}, - {"bad", BwTimetable{}, true}, - {"1000X", BwTimetable{}, true}, - {"2401,666", BwTimetable{}, true}, - {"1061,666", BwTimetable{}, true}, - } { - tt := BwTimetable{} - err := tt.Set(test.in) - if test.err { - require.Error(t, err) - } else { - require.NoError(t, err) - } - assert.Equal(t, test.want, tt) - } -} - -func TestBwTimetableLimitAt(t *testing.T) { - for _, test := range []struct { - tt BwTimetable - now time.Time - want BwTimeSlot - }{ - { - BwTimetable{}, - time.Date(2017, time.April, 20, 15, 0, 0, 0, time.UTC), - BwTimeSlot{hhmm: 0, bandwidth: -1}, - }, - { - BwTimetable{BwTimeSlot{hhmm: 1100, bandwidth: 333 * 1024}}, - time.Date(2017, time.April, 20, 15, 0, 0, 0, time.UTC), - BwTimeSlot{hhmm: 1100, bandwidth: 333 * 1024}, - }, - { - BwTimetable{ - BwTimeSlot{hhmm: 1100, bandwidth: 333 * 1024}, - BwTimeSlot{hhmm: 1300, bandwidth: 666 * 1024}, - BwTimeSlot{hhmm: 2301, bandwidth: 1024 * 1024}, - BwTimeSlot{hhmm: 2350, bandwidth: -1}, - }, - time.Date(2017, time.April, 20, 10, 15, 0, 0, time.UTC), - BwTimeSlot{hhmm: 2350, bandwidth: -1}, - }, - { - BwTimetable{ - BwTimeSlot{hhmm: 1100, bandwidth: 333 * 1024}, - BwTimeSlot{hhmm: 1300, bandwidth: 666 * 1024}, - BwTimeSlot{hhmm: 2301, bandwidth: 1024 * 1024}, - BwTimeSlot{hhmm: 2350, bandwidth: -1}, - }, - time.Date(2017, time.April, 20, 11, 0, 0, 0, time.UTC), - BwTimeSlot{hhmm: 1100, bandwidth: 333 * 1024}, - }, - { - BwTimetable{ - BwTimeSlot{hhmm: 1100, bandwidth: 333 * 1024}, - BwTimeSlot{hhmm: 1300, bandwidth: 666 * 1024}, - BwTimeSlot{hhmm: 2301, bandwidth: 1024 * 1024}, - BwTimeSlot{hhmm: 2350, bandwidth: -1}, - }, - time.Date(2017, time.April, 20, 13, 1, 0, 0, time.UTC), - BwTimeSlot{hhmm: 1300, bandwidth: 666 * 1024}, - }, - { - BwTimetable{ - BwTimeSlot{hhmm: 1100, bandwidth: 333 * 1024}, - BwTimeSlot{hhmm: 1300, bandwidth: 666 * 1024}, - BwTimeSlot{hhmm: 2301, bandwidth: 1024 * 1024}, - BwTimeSlot{hhmm: 2350, bandwidth: -1}, - }, - time.Date(2017, time.April, 20, 23, 59, 0, 0, time.UTC), - BwTimeSlot{hhmm: 2350, bandwidth: -1}, - }, - } { - slot := test.tt.LimitAt(test.now) - assert.Equal(t, test.want, slot) - } -} - func TestObscure(t *testing.T) { for _, test := range []struct { in string diff --git a/fs/filter.go b/fs/filter.go index 404a9d8c6..20e16e2de 100644 --- a/fs/filter.go +++ b/fs/filter.go @@ -13,31 +13,30 @@ import ( "time" "github.com/pkg/errors" - "github.com/spf13/pflag" ) // Global var ( // Flags - deleteExcluded = pflag.BoolP("delete-excluded", "", false, "Delete files on dest excluded from sync") - filterRule = pflag.StringArrayP("filter", "f", nil, "Add a file-filtering rule") - filterFrom = pflag.StringArrayP("filter-from", "", nil, "Read filtering patterns from a file") - excludeRule = pflag.StringArrayP("exclude", "", nil, "Exclude files matching pattern") - excludeFrom = pflag.StringArrayP("exclude-from", "", nil, "Read exclude patterns from file") - includeRule = pflag.StringArrayP("include", "", nil, "Include files matching pattern") - includeFrom = pflag.StringArrayP("include-from", "", nil, "Read include patterns from file") - filesFrom = pflag.StringArrayP("files-from", "", nil, "Read list of source-file names from file") - minAge = pflag.StringP("min-age", "", "", "Don't transfer any file younger than this in s or suffix ms|s|m|h|d|w|M|y") - maxAge = pflag.StringP("max-age", "", "", "Don't transfer any file older than this in s or suffix ms|s|m|h|d|w|M|y") + deleteExcluded = BoolP("delete-excluded", "", false, "Delete files on dest excluded from sync") + filterRule = StringArrayP("filter", "f", nil, "Add a file-filtering rule") + filterFrom = StringArrayP("filter-from", "", nil, "Read filtering patterns from a file") + excludeRule = StringArrayP("exclude", "", nil, "Exclude files matching pattern") + excludeFrom = StringArrayP("exclude-from", "", nil, "Read exclude patterns from file") + includeRule = StringArrayP("include", "", nil, "Include files matching pattern") + includeFrom = StringArrayP("include-from", "", nil, "Read include patterns from file") + filesFrom = StringArrayP("files-from", "", nil, "Read list of source-file names from file") + minAge = StringP("min-age", "", "", "Don't transfer any file younger than this in s or suffix ms|s|m|h|d|w|M|y") + maxAge = StringP("max-age", "", "", "Don't transfer any file older than this in s or suffix ms|s|m|h|d|w|M|y") minSize = SizeSuffix(-1) maxSize = SizeSuffix(-1) - dumpFilters = pflag.BoolP("dump-filters", "", false, "Dump the filters to the output") - //cvsExclude = pflag.BoolP("cvs-exclude", "C", false, "Exclude files in the same way CVS does") + dumpFilters = BoolP("dump-filters", "", false, "Dump the filters to the output") + //cvsExclude = BoolP("cvs-exclude", "C", false, "Exclude files in the same way CVS does") ) func init() { - pflag.VarP(&minSize, "min-size", "", "Don't transfer any file smaller than this in k or suffix b|k|M|G") - pflag.VarP(&maxSize, "max-size", "", "Don't transfer any file larger than this in k or suffix b|k|M|G") + VarP(&minSize, "min-size", "", "Don't transfer any file smaller than this in k or suffix b|k|M|G") + VarP(&maxSize, "max-size", "", "Don't transfer any file larger than this in k or suffix b|k|M|G") } // rule is one filter rule diff --git a/fs/flags.go b/fs/flags.go new file mode 100644 index 000000000..b530b0615 --- /dev/null +++ b/fs/flags.go @@ -0,0 +1,315 @@ +// This contains helper functions for managing flags + +package fs + +import ( + "fmt" + "log" + "math" + "os" + "strconv" + "strings" + "time" + + "github.com/pkg/errors" + "github.com/spf13/pflag" +) + +// SizeSuffix is parsed by flag with k/M/G suffixes +type SizeSuffix int64 + +// 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 < 1024: + scaled = float64(x) + suffix = "" + case x < 1024*1024: + scaled = float64(x) / 1024 + suffix = "k" + case x < 1024*1024*1024: + scaled = float64(x) / 1024 / 1024 + suffix = "M" + default: + scaled = float64(x) / 1024 / 1024 / 1024 + suffix = "G" + } + 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 +} + +// 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 +} + +// 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 + var multiplier float64 + switch suffix { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.': + suffixLen = 0 + multiplier = 1 << 10 + case 'b', 'B': + multiplier = 1 + case 'k', 'K': + multiplier = 1 << 10 + case 'm', 'M': + multiplier = 1 << 20 + case 'g', 'G': + multiplier = 1 << 30 + default: + return errors.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 errors.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 "int64" +} + +// Check it satisfies the interface +var _ pflag.Value = (*SizeSuffix)(nil) + +// BwTimeSlot represents a bandwidth configuration at a point in time. +type BwTimeSlot struct { + hhmm int + bandwidth SizeSuffix +} + +// BwTimetable contains all configured time slots. +type BwTimetable []BwTimeSlot + +// String returns a printable representation of BwTimetable. +func (x BwTimetable) String() string { + ret := []string{} + for _, ts := range x { + ret = append(ret, fmt.Sprintf("%04.4d,%s", ts.hhmm, ts.bandwidth.String())) + } + return strings.Join(ret, " ") +} + +// Set the bandwidth timetable. +func (x *BwTimetable) Set(s string) error { + // The timetable is formatted as: + // "hh:mm,bandwidth hh:mm,banwidth..." ex: "10:00,10G 11:30,1G 18:00,off" + // If only a single bandwidth identifier is provided, we assume constant bandwidth. + + if len(s) == 0 { + return errors.New("empty string") + } + // Single value without time specification. + if !strings.Contains(s, " ") && !strings.Contains(s, ",") { + ts := BwTimeSlot{} + if err := ts.bandwidth.Set(s); err != nil { + return err + } + ts.hhmm = 0 + *x = BwTimetable{ts} + return nil + } + + for _, tok := range strings.Split(s, " ") { + tv := strings.Split(tok, ",") + + // Format must be HH:MM,BW + if len(tv) != 2 { + return errors.Errorf("invalid time/bandwidth specification: %q", tok) + } + + // Basic timespec sanity checking + hhmm := tv[0] + if len(hhmm) != 5 { + return errors.Errorf("invalid time specification (hh:mm): %q", hhmm) + } + hh, err := strconv.Atoi(hhmm[0:2]) + if err != nil { + return errors.Errorf("invalid hour in time specification %q: %v", hhmm, err) + } + if hh < 0 || hh > 23 { + return errors.Errorf("invalid hour (must be between 00 and 23): %q", hh) + } + mm, err := strconv.Atoi(hhmm[3:]) + if err != nil { + return errors.Errorf("invalid minute in time specification: %q: %v", hhmm, err) + } + if mm < 0 || mm > 59 { + return errors.Errorf("invalid minute (must be between 00 and 59): %q", hh) + } + + ts := BwTimeSlot{ + hhmm: (hh * 100) + mm, + } + // Bandwidth limit for this time slot. + if err := ts.bandwidth.Set(tv[1]); err != nil { + return err + } + *x = append(*x, ts) + } + return nil +} + +// LimitAt returns a BwTimeSlot for the time requested. +func (x BwTimetable) LimitAt(tt time.Time) BwTimeSlot { + // If the timetable is empty, we return an unlimited BwTimeSlot starting at midnight. + if len(x) == 0 { + return BwTimeSlot{hhmm: 0, bandwidth: -1} + } + + hhmm := tt.Hour()*100 + tt.Minute() + + // By default, we return the last element in the timetable. This + // satisfies two conditions: 1) If there's only one element it + // will always be selected, and 2) The last element of the table + // will "wrap around" until overriden by an earlier time slot. + // there's only one time slot in the timetable. + ret := x[len(x)-1] + + mindif := 0 + first := true + + // Look for most recent time slot. + for _, ts := range x { + // Ignore the past + if hhmm < ts.hhmm { + continue + } + dif := ((hhmm / 100 * 60) + (hhmm % 100)) - ((ts.hhmm / 100 * 60) + (ts.hhmm % 100)) + if first { + mindif = dif + first = false + } + if dif <= mindif { + mindif = dif + ret = ts + } + } + + return ret +} + +// Type of the value +func (x BwTimetable) Type() string { + return "BwTimetable" +} + +// Check it satisfies the interface +var _ pflag.Value = (*BwTimetable)(nil) + +// optionToEnv converts an option name, eg "ignore-size" into an +// environment name "RCLONE_IGNORE_SIZE" +func optionToEnv(name string) string { + return "RCLONE_" + strings.ToUpper(strings.Replace(name, "-", "_", -1)) +} + +// setDefaultFromEnv constructs a name from the flag passed in and +// sets the default from the environment if possible. +func setDefaultFromEnv(name string) { + key := optionToEnv(name) + newValue, found := os.LookupEnv(key) + if found { + flag := pflag.Lookup(name) + if flag == nil { + log.Fatalf("Couldn't find flag %q", name) + } + err := flag.Value.Set(newValue) + if err != nil { + log.Fatalf("Invalid value for environment variable %q: %v", key, err) + } + // log.Printf("Set default for %q from %q to %q (%v)", name, key, newValue, flag.Value) + flag.DefValue = newValue + } +} + +// StringP defines a flag which can be overridden by an environment variable +// +// It is a thin wrapper around pflag.StringP +func StringP(name, shorthand string, value string, usage string) (out *string) { + out = pflag.StringP(name, shorthand, value, usage) + setDefaultFromEnv(name) + return out +} + +// BoolP defines a flag which can be overridden by an environment variable +// +// It is a thin wrapper around pflag.BoolP +func BoolP(name, shorthand string, value bool, usage string) (out *bool) { + out = pflag.BoolP(name, shorthand, value, usage) + setDefaultFromEnv(name) + return out +} + +// IntP defines a flag which can be overridden by an environment variable +// +// It is a thin wrapper around pflag.IntP +func IntP(name, shorthand string, value int, usage string) (out *int) { + out = pflag.IntP(name, shorthand, value, usage) + setDefaultFromEnv(name) + return out +} + +// DurationP defines a flag which can be overridden by an environment variable +// +// It is a thin wrapper around pflag.DurationP +func DurationP(name, shorthand string, value time.Duration, usage string) (out *time.Duration) { + out = pflag.DurationP(name, shorthand, value, usage) + setDefaultFromEnv(name) + return out +} + +// VarP defines a flag which can be overridden by an environment variable +// +// It is a thin wrapper around pflag.VarP +func VarP(value pflag.Value, name, shorthand, usage string) { + pflag.VarP(value, name, shorthand, usage) + setDefaultFromEnv(name) +} + +// StringArrayP defines a flag which can be overridden by an environment variable +// +// It sets one value only - command line flags can be used to set more. +// +// It is a thin wrapper around pflag.StringArrayP +func StringArrayP(name, shorthand string, value []string, usage string) (out *[]string) { + out = pflag.StringArrayP(name, shorthand, value, usage) + setDefaultFromEnv(name) + return out +} diff --git a/fs/flags_test.go b/fs/flags_test.go new file mode 100644 index 000000000..08589f445 --- /dev/null +++ b/fs/flags_test.go @@ -0,0 +1,187 @@ +package fs + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSizeSuffixString(t *testing.T) { + for _, test := range []struct { + in float64 + want string + }{ + {0, "0"}, + {102, "102"}, + {1024, "1k"}, + {1024 * 1024, "1M"}, + {1024 * 1024 * 1024, "1G"}, + {10 * 1024 * 1024 * 1024, "10G"}, + {10.1 * 1024 * 1024 * 1024, "10.100G"}, + {-1, "off"}, + {-100, "off"}, + } { + ss := SizeSuffix(test.in) + got := ss.String() + assert.Equal(t, test.want, got) + } +} + +func TestSizeSuffixUnit(t *testing.T) { + for _, test := range []struct { + in float64 + want string + }{ + {0, "0 Bytes"}, + {102, "102 Bytes"}, + {1024, "1 kBytes"}, + {1024 * 1024, "1 MBytes"}, + {1024 * 1024 * 1024, "1 GBytes"}, + {10 * 1024 * 1024 * 1024, "10 GBytes"}, + {10.1 * 1024 * 1024 * 1024, "10.100 GBytes"}, + {-1, "off"}, + {-100, "off"}, + } { + ss := SizeSuffix(test.in) + got := ss.Unit("Bytes") + assert.Equal(t, test.want, got) + } +} + +func TestSizeSuffixSet(t *testing.T) { + for _, test := range []struct { + in string + want int64 + err bool + }{ + {"0", 0, false}, + {"1b", 1, false}, + {"102B", 102, false}, + {"0.1k", 102, false}, + {"0.1", 102, false}, + {"1K", 1024, false}, + {"1", 1024, false}, + {"2.5", 1024 * 2.5, false}, + {"1M", 1024 * 1024, false}, + {"1.g", 1024 * 1024 * 1024, false}, + {"10G", 10 * 1024 * 1024 * 1024, false}, + {"off", -1, false}, + {"OFF", -1, false}, + {"", 0, true}, + {"1p", 0, true}, + {"1.p", 0, true}, + {"1p", 0, true}, + {"-1K", 0, true}, + } { + ss := SizeSuffix(0) + err := ss.Set(test.in) + if test.err { + require.Error(t, err) + } else { + require.NoError(t, err) + } + assert.Equal(t, test.want, int64(ss)) + } +} + +func TestBwTimetableSet(t *testing.T) { + for _, test := range []struct { + in string + want BwTimetable + err bool + }{ + {"", BwTimetable{}, true}, + {"0", BwTimetable{BwTimeSlot{hhmm: 0, bandwidth: 0}}, false}, + {"666", BwTimetable{BwTimeSlot{hhmm: 0, bandwidth: 666 * 1024}}, false}, + {"10:20,666", BwTimetable{BwTimeSlot{hhmm: 1020, bandwidth: 666 * 1024}}, false}, + { + "11:00,333 13:40,666 23:50,10M 23:59,off", + BwTimetable{ + BwTimeSlot{hhmm: 1100, bandwidth: 333 * 1024}, + BwTimeSlot{hhmm: 1340, bandwidth: 666 * 1024}, + BwTimeSlot{hhmm: 2350, bandwidth: 10 * 1024 * 1024}, + BwTimeSlot{hhmm: 2359, bandwidth: -1}, + }, + false, + }, + {"bad,bad", BwTimetable{}, true}, + {"bad bad", BwTimetable{}, true}, + {"bad", BwTimetable{}, true}, + {"1000X", BwTimetable{}, true}, + {"2401,666", BwTimetable{}, true}, + {"1061,666", BwTimetable{}, true}, + } { + tt := BwTimetable{} + err := tt.Set(test.in) + if test.err { + require.Error(t, err) + } else { + require.NoError(t, err) + } + assert.Equal(t, test.want, tt) + } +} + +func TestBwTimetableLimitAt(t *testing.T) { + for _, test := range []struct { + tt BwTimetable + now time.Time + want BwTimeSlot + }{ + { + BwTimetable{}, + time.Date(2017, time.April, 20, 15, 0, 0, 0, time.UTC), + BwTimeSlot{hhmm: 0, bandwidth: -1}, + }, + { + BwTimetable{BwTimeSlot{hhmm: 1100, bandwidth: 333 * 1024}}, + time.Date(2017, time.April, 20, 15, 0, 0, 0, time.UTC), + BwTimeSlot{hhmm: 1100, bandwidth: 333 * 1024}, + }, + { + BwTimetable{ + BwTimeSlot{hhmm: 1100, bandwidth: 333 * 1024}, + BwTimeSlot{hhmm: 1300, bandwidth: 666 * 1024}, + BwTimeSlot{hhmm: 2301, bandwidth: 1024 * 1024}, + BwTimeSlot{hhmm: 2350, bandwidth: -1}, + }, + time.Date(2017, time.April, 20, 10, 15, 0, 0, time.UTC), + BwTimeSlot{hhmm: 2350, bandwidth: -1}, + }, + { + BwTimetable{ + BwTimeSlot{hhmm: 1100, bandwidth: 333 * 1024}, + BwTimeSlot{hhmm: 1300, bandwidth: 666 * 1024}, + BwTimeSlot{hhmm: 2301, bandwidth: 1024 * 1024}, + BwTimeSlot{hhmm: 2350, bandwidth: -1}, + }, + time.Date(2017, time.April, 20, 11, 0, 0, 0, time.UTC), + BwTimeSlot{hhmm: 1100, bandwidth: 333 * 1024}, + }, + { + BwTimetable{ + BwTimeSlot{hhmm: 1100, bandwidth: 333 * 1024}, + BwTimeSlot{hhmm: 1300, bandwidth: 666 * 1024}, + BwTimeSlot{hhmm: 2301, bandwidth: 1024 * 1024}, + BwTimeSlot{hhmm: 2350, bandwidth: -1}, + }, + time.Date(2017, time.April, 20, 13, 1, 0, 0, time.UTC), + BwTimeSlot{hhmm: 1300, bandwidth: 666 * 1024}, + }, + { + BwTimetable{ + BwTimeSlot{hhmm: 1100, bandwidth: 333 * 1024}, + BwTimeSlot{hhmm: 1300, bandwidth: 666 * 1024}, + BwTimeSlot{hhmm: 2301, bandwidth: 1024 * 1024}, + BwTimeSlot{hhmm: 2350, bandwidth: -1}, + }, + time.Date(2017, time.April, 20, 23, 59, 0, 0, time.UTC), + BwTimeSlot{hhmm: 2350, bandwidth: -1}, + }, + } { + slot := test.tt.LimitAt(test.now) + assert.Equal(t, test.want, slot) + } +} diff --git a/local/read_device_unix.go b/local/read_device_unix.go index 472e72f8d..68ea8ae19 100644 --- a/local/read_device_unix.go +++ b/local/read_device_unix.go @@ -9,11 +9,10 @@ import ( "syscall" "github.com/ncw/rclone/fs" - "github.com/spf13/pflag" ) var ( - oneFileSystem = pflag.BoolP("one-file-system", "x", false, "Don't cross filesystem boundaries.") + oneFileSystem = fs.BoolP("one-file-system", "x", false, "Don't cross filesystem boundaries.") ) // readDevice turns a valid os.FileInfo into a device number, diff --git a/onedrive/onedrive.go b/onedrive/onedrive.go index aa1740fed..c702d6e8a 100644 --- a/onedrive/onedrive.go +++ b/onedrive/onedrive.go @@ -21,7 +21,6 @@ import ( "github.com/ncw/rclone/pacer" "github.com/ncw/rclone/rest" "github.com/pkg/errors" - "github.com/spf13/pflag" "golang.org/x/oauth2" ) @@ -75,8 +74,8 @@ func init() { Help: "Microsoft App Client Secret - leave blank normally.", }}, }) - pflag.VarP(&chunkSize, "onedrive-chunk-size", "", "Above this size files will be chunked - must be multiple of 320k.") - pflag.VarP(&uploadCutoff, "onedrive-upload-cutoff", "", "Cutoff for switching to chunked upload - must be <= 100MB") + fs.VarP(&chunkSize, "onedrive-chunk-size", "", "Above this size files will be chunked - must be multiple of 320k.") + fs.VarP(&uploadCutoff, "onedrive-upload-cutoff", "", "Cutoff for switching to chunked upload - must be <= 100MB") } // Fs represents a remote one drive diff --git a/s3/s3.go b/s3/s3.go index 1e166cd67..7f820eca2 100644 --- a/s3/s3.go +++ b/s3/s3.go @@ -36,7 +36,6 @@ import ( "github.com/ncw/rclone/fs" "github.com/ncw/swift" "github.com/pkg/errors" - "github.com/spf13/pflag" ) // Register with Fs @@ -210,8 +209,8 @@ const ( // Globals var ( // Flags - s3ACL = pflag.StringP("s3-acl", "", "", "Canned ACL used when creating buckets and/or storing objects in S3") - s3StorageClass = pflag.StringP("s3-storage-class", "", "", "Storage class to use when uploading S3 objects (STANDARD|REDUCED_REDUNDANCY|STANDARD_IA)") + s3ACL = fs.StringP("s3-acl", "", "", "Canned ACL used when creating buckets and/or storing objects in S3") + s3StorageClass = fs.StringP("s3-storage-class", "", "", "Storage class to use when uploading S3 objects (STANDARD|REDUCED_REDUNDANCY|STANDARD_IA)") ) // Fs represents a remote s3 server diff --git a/swift/swift.go b/swift/swift.go index b0e3fd090..6d28e31b5 100644 --- a/swift/swift.go +++ b/swift/swift.go @@ -14,7 +14,6 @@ import ( "github.com/ncw/rclone/fs" "github.com/ncw/swift" "github.com/pkg/errors" - "github.com/spf13/pflag" ) // Constants @@ -83,7 +82,7 @@ func init() { }, }) // snet = flag.Bool("swift-snet", false, "Use internal service network") // FIXME not implemented - pflag.VarP(&chunkSize, "swift-chunk-size", "", "Above this size files will be chunked into a _segments container.") + fs.VarP(&chunkSize, "swift-chunk-size", "", "Above this size files will be chunked into a _segments container.") } // Fs represents a remote swift server