Implement --retries flag - fixes #109

This commit is contained in:
Nick Craig-Wood 2015-08-20 21:07:00 +01:00
parent dfe771fb0c
commit 0c6f816a49
2 changed files with 55 additions and 53 deletions

View File

@ -134,6 +134,13 @@ func (s *StatsInfo) ResetCounters() {
s.transfers = 0 s.transfers = 0
} }
// ResetErrors sets the errors count to 0
func (s *StatsInfo) ResetErrors() {
s.lock.RLock()
defer s.lock.RUnlock()
s.errors = 0
}
// Errored returns whether there have been any errors // Errored returns whether there have been any errors
func (s *StatsInfo) Errored() bool { func (s *StatsInfo) Errored() bool {
s.lock.RLock() s.lock.RLock()

101
rclone.go
View File

@ -31,16 +31,18 @@ var (
statsInterval = pflag.DurationP("stats", "", time.Minute*1, "Interval to print stats (0 to disable)") statsInterval = pflag.DurationP("stats", "", time.Minute*1, "Interval to print stats (0 to disable)")
version = pflag.BoolP("version", "V", false, "Print the version number") version = pflag.BoolP("version", "V", false, "Print the version number")
logFile = pflag.StringP("log-file", "", "", "Log everything to this file") logFile = pflag.StringP("log-file", "", "", "Log everything to this file")
retries = pflag.IntP("retries", "", 3, "Retry operations this many times if they fail")
) )
type Command struct { type Command struct {
Name string Name string
Help string Help string
ArgsHelp string ArgsHelp string
Run func(fdst, fsrc fs.Fs) Run func(fdst, fsrc fs.Fs) error
MinArgs int MinArgs int
MaxArgs int MaxArgs int
NoStats bool NoStats bool
Retry bool
} }
// checkArgs checks there are enough arguments and prints a message if not // checkArgs checks there are enough arguments and prints a message if not
@ -64,14 +66,12 @@ var Commands = []Command{
Copy the source to the destination. Doesn't transfer Copy the source to the destination. Doesn't transfer
unchanged files, testing by size and modification time or unchanged files, testing by size and modification time or
MD5SUM. Doesn't delete files from the destination.`, MD5SUM. Doesn't delete files from the destination.`,
Run: func(fdst, fsrc fs.Fs) { Run: func(fdst, fsrc fs.Fs) error {
err := fs.Sync(fdst, fsrc, false) return fs.Sync(fdst, fsrc, false)
if err != nil {
log.Fatalf("Failed to copy: %v", err)
}
}, },
MinArgs: 2, MinArgs: 2,
MaxArgs: 2, MaxArgs: 2,
Retry: true,
}, },
{ {
Name: "sync", Name: "sync",
@ -82,25 +82,20 @@ var Commands = []Command{
modification time or MD5SUM. Destination is updated to match modification time or MD5SUM. Destination is updated to match
source, including deleting files if necessary. Since this can source, including deleting files if necessary. Since this can
cause data loss, test first with the --dry-run flag.`, cause data loss, test first with the --dry-run flag.`,
Run: func(fdst, fsrc fs.Fs) { Run: func(fdst, fsrc fs.Fs) error {
err := fs.Sync(fdst, fsrc, true) return fs.Sync(fdst, fsrc, true)
if err != nil {
log.Fatalf("Failed to sync: %v", err)
}
}, },
MinArgs: 2, MinArgs: 2,
MaxArgs: 2, MaxArgs: 2,
Retry: true,
}, },
{ {
Name: "ls", Name: "ls",
ArgsHelp: "[remote:path]", ArgsHelp: "[remote:path]",
Help: ` Help: `
List all the objects in the the path with size and path.`, List all the objects in the the path with size and path.`,
Run: func(fdst, fsrc fs.Fs) { Run: func(fdst, fsrc fs.Fs) error {
err := fs.List(fdst, os.Stdout) return fs.List(fdst, os.Stdout)
if err != nil {
log.Fatalf("Failed to list: %v", err)
}
}, },
MinArgs: 1, MinArgs: 1,
MaxArgs: 1, MaxArgs: 1,
@ -110,11 +105,8 @@ var Commands = []Command{
ArgsHelp: "[remote:path]", ArgsHelp: "[remote:path]",
Help: ` Help: `
List all directories/containers/buckets in the the path.`, List all directories/containers/buckets in the the path.`,
Run: func(fdst, fsrc fs.Fs) { Run: func(fdst, fsrc fs.Fs) error {
err := fs.ListDir(fdst, os.Stdout) return fs.ListDir(fdst, os.Stdout)
if err != nil {
log.Fatalf("Failed to listdir: %v", err)
}
}, },
MinArgs: 1, MinArgs: 1,
MaxArgs: 1, MaxArgs: 1,
@ -125,11 +117,8 @@ var Commands = []Command{
Help: ` Help: `
List all the objects in the the path with modification time, List all the objects in the the path with modification time,
size and path.`, size and path.`,
Run: func(fdst, fsrc fs.Fs) { Run: func(fdst, fsrc fs.Fs) error {
err := fs.ListLong(fdst, os.Stdout) return fs.ListLong(fdst, os.Stdout)
if err != nil {
log.Fatalf("Failed to list long: %v", err)
}
}, },
MinArgs: 1, MinArgs: 1,
MaxArgs: 1, MaxArgs: 1,
@ -140,11 +129,8 @@ var Commands = []Command{
Help: ` Help: `
Produces an md5sum file for all the objects in the path. This Produces an md5sum file for all the objects in the path. This
is in the same format as the standard md5sum tool produces.`, is in the same format as the standard md5sum tool produces.`,
Run: func(fdst, fsrc fs.Fs) { Run: func(fdst, fsrc fs.Fs) error {
err := fs.Md5sum(fdst, os.Stdout) return fs.Md5sum(fdst, os.Stdout)
if err != nil {
log.Fatalf("Failed to list: %v", err)
}
}, },
MinArgs: 1, MinArgs: 1,
MaxArgs: 1, MaxArgs: 1,
@ -154,14 +140,12 @@ var Commands = []Command{
ArgsHelp: "remote:path", ArgsHelp: "remote:path",
Help: ` Help: `
Make the path if it doesn't already exist`, Make the path if it doesn't already exist`,
Run: func(fdst, fsrc fs.Fs) { Run: func(fdst, fsrc fs.Fs) error {
err := fs.Mkdir(fdst) return fs.Mkdir(fdst)
if err != nil {
log.Fatalf("Failed to mkdir: %v", err)
}
}, },
MinArgs: 1, MinArgs: 1,
MaxArgs: 1, MaxArgs: 1,
Retry: true,
}, },
{ {
Name: "rmdir", Name: "rmdir",
@ -169,28 +153,24 @@ var Commands = []Command{
Help: ` Help: `
Remove the path. Note that you can't remove a path with Remove the path. Note that you can't remove a path with
objects in it, use purge for that.`, objects in it, use purge for that.`,
Run: func(fdst, fsrc fs.Fs) { Run: func(fdst, fsrc fs.Fs) error {
err := fs.Rmdir(fdst) return fs.Rmdir(fdst)
if err != nil {
log.Fatalf("Failed to rmdir: %v", err)
}
}, },
MinArgs: 1, MinArgs: 1,
MaxArgs: 1, MaxArgs: 1,
Retry: true,
}, },
{ {
Name: "purge", Name: "purge",
ArgsHelp: "remote:path", ArgsHelp: "remote:path",
Help: ` Help: `
Remove the path and all of its contents.`, Remove the path and all of its contents.`,
Run: func(fdst, fsrc fs.Fs) { Run: func(fdst, fsrc fs.Fs) error {
err := fs.Purge(fdst) return fs.Purge(fdst)
if err != nil {
log.Fatalf("Failed to purge: %v", err)
}
}, },
MinArgs: 1, MinArgs: 1,
MaxArgs: 1, MaxArgs: 1,
Retry: true,
}, },
{ {
Name: "check", Name: "check",
@ -199,11 +179,8 @@ var Commands = []Command{
Checks the files in the source and destination match. It Checks the files in the source and destination match. It
compares sizes and MD5SUMs and prints a report of files which compares sizes and MD5SUMs and prints a report of files which
don't match. It doesn't alter the source or destination.`, don't match. It doesn't alter the source or destination.`,
Run: func(fdst, fsrc fs.Fs) { Run: func(fdst, fsrc fs.Fs) error {
err := fs.Check(fdst, fsrc) return fs.Check(fdst, fsrc)
if err != nil {
log.Fatalf("Failed to check: %v", err)
}
}, },
MinArgs: 2, MinArgs: 2,
MaxArgs: 2, MaxArgs: 2,
@ -212,8 +189,9 @@ var Commands = []Command{
Name: "config", Name: "config",
Help: ` Help: `
Enter an interactive configuration session.`, Enter an interactive configuration session.`,
Run: func(fdst, fsrc fs.Fs) { Run: func(fdst, fsrc fs.Fs) error {
fs.EditConfig() fs.EditConfig()
return nil
}, },
NoStats: true, NoStats: true,
}, },
@ -376,7 +354,24 @@ func main() {
// Run the actual command // Run the actual command
if command.Run != nil { if command.Run != nil {
command.Run(fdst, fsrc) var err error
for try := 1; try <= *retries; try++ {
err = command.Run(fdst, fsrc)
if !command.Retry || (err == nil && !fs.Stats.Errored()) {
break
}
if err != nil {
fs.Log(nil, "Attempt %d/%d failed with %d errors and: %v", try, *retries, fs.Stats.GetErrors(), err)
} else {
fs.Log(nil, "Attempt %d/%d failed with %d errors", try, *retries, fs.Stats.GetErrors())
}
if try < *retries {
fs.Stats.ResetErrors()
}
}
if err != nil {
log.Fatalf("Failed to %s: %v", command.Name, err)
}
if !command.NoStats && (!fs.Config.Quiet || fs.Stats.Errored() || *statsInterval > 0) { if !command.NoStats && (!fs.Config.Quiet || fs.Stats.Errored() || *statsInterval > 0) {
fmt.Fprintln(os.Stderr, fs.Stats) fmt.Fprintln(os.Stderr, fs.Stats)
} }