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.
This commit is contained in:
Nick Craig-Wood
2026-09-08 10:34:07 +01:00
parent af382608c4
commit 4412cef804
3 changed files with 62 additions and 95 deletions
+9 -46
View File
@@ -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
}
+43
View File
@@ -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 {
@@ -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())