rclone/cmd/touch/touch.go
Nick Craig-Wood e43b5ce5e5 Remove github.com/pkg/errors and replace with std library version
This is possible now that we no longer support go1.12 and brings
rclone into line with standard practices in the Go world.

This also removes errors.New and errors.Errorf from lib/errors and
prefers the stdlib errors package over lib/errors.
2021-11-07 11:53:30 +00:00

158 lines
4.9 KiB
Go

package touch
import (
"bytes"
"context"
"errors"
"fmt"
"time"
"github.com/rclone/rclone/cmd"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config/flags"
"github.com/rclone/rclone/fs/object"
"github.com/rclone/rclone/fs/operations"
"github.com/spf13/cobra"
)
var (
notCreateNewFile bool
timeAsArgument string
localTime bool
recursive bool
)
const (
defaultLayout string = "060102"
layoutDateWithTime string = "2006-01-02T15:04:05"
layoutDateWithTimeNano string = "2006-01-02T15:04:05.999999999"
)
func init() {
cmd.Root.AddCommand(commandDefinition)
cmdFlags := commandDefinition.Flags()
flags.BoolVarP(cmdFlags, &notCreateNewFile, "no-create", "C", false, "Do not create the file if it does not exist (implied with --recursive)")
flags.StringVarP(cmdFlags, &timeAsArgument, "timestamp", "t", "", "Use specified time instead of the current time of day")
flags.BoolVarP(cmdFlags, &localTime, "localtime", "", false, "Use localtime for timestamp, not UTC")
flags.BoolVarP(cmdFlags, &recursive, "recursive", "R", false, "Recursively touch all files")
}
var commandDefinition = &cobra.Command{
Use: "touch remote:path",
Short: `Create new file or change file modification time.`,
Long: `
Set the modification time on file(s) as specified by remote:path to
have the current time.
If remote:path does not exist then a zero sized file will be created,
unless ` + "`--no-create`" + ` or ` + "`--recursive`" + ` is provided.
If ` + "`--recursive`" + ` is used then recursively sets the modification
time on all existing files that is found under the path. Filters are supported,
and you can test with the ` + "`--dry-run`" + ` or the ` + "`--interactive`" + ` flag.
If ` + "`--timestamp`" + ` is used then sets the modification time to that
time instead of the current time. Times may be specified as one of:
- 'YYMMDD' - e.g. 17.10.30
- 'YYYY-MM-DDTHH:MM:SS' - e.g. 2006-01-02T15:04:05
- 'YYYY-MM-DDTHH:MM:SS.SSS' - e.g. 2006-01-02T15:04:05.123456789
Note that value of ` + "`--timestamp`" + ` is in UTC. If you want local time
then add the ` + "`--localtime`" + ` flag.
`,
Run: func(command *cobra.Command, args []string) {
cmd.CheckArgs(1, 1, command, args)
f, fileName := cmd.NewFsFile(args[0])
cmd.Run(true, false, command, func() error {
return Touch(context.Background(), f, fileName)
})
},
}
// parseTimeArgument parses a timestamp string according to specific layouts
func parseTimeArgument(timeString string) (time.Time, error) {
layout := defaultLayout
if len(timeString) == len(layoutDateWithTime) {
layout = layoutDateWithTime
} else if len(timeString) > len(layoutDateWithTime) {
layout = layoutDateWithTimeNano
}
if localTime {
return time.ParseInLocation(layout, timeString, time.Local)
}
return time.Parse(layout, timeString)
}
// timeOfTouch returns the time value set on files
func timeOfTouch() (time.Time, error) {
var t time.Time
if timeAsArgument != "" {
var err error
if t, err = parseTimeArgument(timeAsArgument); err != nil {
return t, fmt.Errorf("failed to parse timestamp argument: %w", err)
}
} else {
t = time.Now()
}
return t, nil
}
// createEmptyObject creates an empty object (file) with specified timestamp
func createEmptyObject(ctx context.Context, remote string, modTime time.Time, f fs.Fs) error {
var buffer []byte
src := object.NewStaticObjectInfo(remote, modTime, int64(len(buffer)), true, nil, f)
_, err := f.Put(ctx, bytes.NewBuffer(buffer), src)
return err
}
// Touch create new file or change file modification time.
func Touch(ctx context.Context, f fs.Fs, fileName string) error {
t, err := timeOfTouch()
if err != nil {
return err
}
fs.Debugf(nil, "Touch time %v", t)
file, err := f.NewObject(ctx, fileName)
if err != nil {
if errors.Is(err, fs.ErrorObjectNotFound) {
// Touch single non-existent file
if notCreateNewFile {
fs.Logf(f, "Not touching non-existent file due to --no-create")
return nil
}
if recursive {
fs.Logf(f, "Not touching non-existent file due to --recursive")
return nil
}
if operations.SkipDestructive(ctx, f, "touch (create)") {
return nil
}
fs.Debugf(f, "Touching (creating)")
if err = createEmptyObject(ctx, fileName, t, f); err != nil {
return fmt.Errorf("failed to touch (create): %w", err)
}
}
if errors.Is(err, fs.ErrorIsDir) {
if recursive {
// Touch existing directory, recursive
fs.Debugf(nil, "Touching files in directory recursively")
return operations.TouchDir(ctx, f, t, true)
}
// Touch existing directory without recursing
fs.Debugf(nil, "Touching files in directory non-recursively")
return operations.TouchDir(ctx, f, t, false)
}
return err
}
// Touch single existing file
if !operations.SkipDestructive(ctx, fileName, "touch") {
fs.Debugf(f, "Touching %q", fileName)
err = file.SetModTime(ctx, t)
if err != nil {
return fmt.Errorf("failed to touch: %w", err)
}
}
return nil
}