mirror of
https://github.com/rclone/rclone.git
synced 2024-11-26 10:25:03 +01:00
e43b5ce5e5
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.
158 lines
4.9 KiB
Go
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, ¬CreateNewFile, "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
|
|
}
|