From 4412cef80403041b2fb9d22cb9fdd1ef863ee710 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Tue, 1 Sep 2026 11:59:46 +0100 Subject: [PATCH] operations: share the report file opening between check and sync The code which opens the --combined, --differ etc report files (or stdout for "-") and closes them afterwards was duplicated between the check command and the sync logger flags. This moves it into operations.OpenReportFiles which both now use. It also closes any files already opened if a later one fails to open. --- cmd/check/check.go | 55 +++-------------- fs/operations/logger.go | 43 ++++++++++++++ .../operationsflags/operationsflags.go | 59 ++++--------------- 3 files changed, 62 insertions(+), 95 deletions(-) diff --git a/cmd/check/check.go b/cmd/check/check.go index 3ab5c06ab..d7675d4a8 100644 --- a/cmd/check/check.go +++ b/cmd/check/check.go @@ -4,8 +4,6 @@ package check import ( "context" "fmt" - "io" - "os" "strings" "github.com/rclone/rclone/cmd" @@ -78,58 +76,23 @@ option for more information.`, "|", "`") // GetCheckOpt gets the options corresponding to the check flags func GetCheckOpt(fsrc, fdst fs.Fs) (opt *operations.CheckOpt, close func(), err error) { - closers := []io.Closer{} - opt = &operations.CheckOpt{ Fsrc: fsrc, Fdst: fdst, OneWay: oneway, } - open := func(name string, pout *io.Writer) error { - if name == "" { - return nil - } - if name == "-" { - *pout = os.Stdout - return nil - } - out, err := os.Create(name) - if err != nil { - return err - } - *pout = out - closers = append(closers, out) - return nil - } - - if err = open(combined, &opt.Combined); err != nil { + close, err = operations.OpenReportFiles( + operations.ReportFile{Name: combined, Out: &opt.Combined}, + operations.ReportFile{Name: missingOnSrc, Out: &opt.MissingOnSrc}, + operations.ReportFile{Name: missingOnDst, Out: &opt.MissingOnDst}, + operations.ReportFile{Name: match, Out: &opt.Match}, + operations.ReportFile{Name: differ, Out: &opt.Differ}, + operations.ReportFile{Name: errFile, Out: &opt.Error}, + ) + if err != nil { return nil, nil, err } - if err = open(missingOnSrc, &opt.MissingOnSrc); err != nil { - return nil, nil, err - } - if err = open(missingOnDst, &opt.MissingOnDst); err != nil { - return nil, nil, err - } - if err = open(match, &opt.Match); err != nil { - return nil, nil, err - } - if err = open(differ, &opt.Differ); err != nil { - return nil, nil, err - } - if err = open(errFile, &opt.Error); err != nil { - return nil, nil, err - } - - close = func() { - for _, closer := range closers { - err := closer.Close() - if err != nil { - fs.Errorf(nil, "Failed to close report output: %v", err) - } - } - } return opt, close, nil } diff --git a/fs/operations/logger.go b/fs/operations/logger.go index f9fb41f0e..4f4a501e0 100644 --- a/fs/operations/logger.go +++ b/fs/operations/logger.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "os" mutex "sync" "github.com/rclone/rclone/fs" @@ -302,6 +303,48 @@ func WinningSide(ctx context.Context, sigil Sigil, src, dst fs.DirEntry, err err return winner } +// ReportFile pairs the name of a file a report is written to with +// the writer to set when it is opened. +type ReportFile struct { + Name string // file name, "" for no report or "-" for stdout + Out *io.Writer // set to the opened file +} + +// OpenReportFiles opens the report files for writing and returns a +// function to close them. +// +// For each file an empty Name leaves Out unchanged so the report is +// not written, "-" sets it to stdout and any other Name is created as +// a file, truncating it if it exists. +func OpenReportFiles(files ...ReportFile) (close func(), err error) { + closers := []io.Closer{} + close = func() { + for _, closer := range closers { + err := closer.Close() + if err != nil { + fs.Errorf(nil, "Failed to close report output: %v", err) + } + } + } + for _, file := range files { + switch file.Name { + case "": + continue + case "-": + *file.Out = os.Stdout + default: + out, err := os.Create(file.Name) + if err != nil { + close() + return nil, err + } + *file.Out = out + closers = append(closers, out) + } + } + return close, nil +} + // NewSyncLoggerOpt returns a LoggerOpt with no report writers set and // the default listing options for the DestAfter report. func NewSyncLoggerOpt() LoggerOpt { diff --git a/fs/operations/operationsflags/operationsflags.go b/fs/operations/operationsflags/operationsflags.go index e2cfda034..d48acee6d 100644 --- a/fs/operations/operationsflags/operationsflags.go +++ b/fs/operations/operationsflags/operationsflags.go @@ -5,8 +5,6 @@ package operationsflags import ( "context" _ "embed" - "io" - "os" "strings" "github.com/rclone/rclone/fs" @@ -76,55 +74,18 @@ func AddLoggerFlags(cmdFlags *pflag.FlagSet, opt *operations.LoggerOpt, flagsOpt // ConfigureLoggers verifies and sets up writers for log files requested via CLI flags func ConfigureLoggers(ctx context.Context, fdst fs.Fs, command *cobra.Command, opt *operations.LoggerOpt, flagsOpt AddLoggerFlagsOptions) (func(), error) { - closers := []io.Closer{} - - open := func(name string, pout *io.Writer) error { - if name == "" { - return nil - } - if name == "-" { - *pout = os.Stdout - return nil - } - out, err := os.Create(name) - if err != nil { - return err - } - *pout = out - closers = append(closers, out) - return nil - } - - if err := open(flagsOpt.Combined, &opt.Combined); err != nil { + close, err := operations.OpenReportFiles( + operations.ReportFile{Name: flagsOpt.Combined, Out: &opt.Combined}, + operations.ReportFile{Name: flagsOpt.MissingOnSrc, Out: &opt.MissingOnSrc}, + operations.ReportFile{Name: flagsOpt.MissingOnDst, Out: &opt.MissingOnDst}, + operations.ReportFile{Name: flagsOpt.Match, Out: &opt.Match}, + operations.ReportFile{Name: flagsOpt.Differ, Out: &opt.Differ}, + operations.ReportFile{Name: flagsOpt.ErrFile, Out: &opt.Error}, + operations.ReportFile{Name: flagsOpt.DestAfter, Out: &opt.DestAfter}, + ) + if err != nil { return nil, err } - if err := open(flagsOpt.MissingOnSrc, &opt.MissingOnSrc); err != nil { - return nil, err - } - if err := open(flagsOpt.MissingOnDst, &opt.MissingOnDst); err != nil { - return nil, err - } - if err := open(flagsOpt.Match, &opt.Match); err != nil { - return nil, err - } - if err := open(flagsOpt.Differ, &opt.Differ); err != nil { - return nil, err - } - if err := open(flagsOpt.ErrFile, &opt.Error); err != nil { - return nil, err - } - if err := open(flagsOpt.DestAfter, &opt.DestAfter); err != nil { - return nil, err - } - - close := func() { - for _, closer := range closers { - err := closer.Close() - if err != nil { - fs.Errorf(nil, "Failed to close report output: %v", err) - } - } - } opt.Init(ctx, fdst, command.Flags())