1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
|
// Package touch provides the touch command.
package touch
import (
"bytes"
"context"
"errors"
"fmt"
"log"
"time"
"github.com/rclone/rclone/cmd"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config/flags"
"github.com/rclone/rclone/fs/fspath"
"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`/`-i`" + ` 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.
`,
Annotations: map[string]string{
"versionIntroduced": "v1.39",
"groups": "Filter,Listing,Important",
},
Run: func(command *cobra.Command, args []string) {
cmd.CheckArgs(1, 1, command, args)
f, remote := newFsDst(args)
cmd.Run(true, false, command, func() error {
return Touch(context.Background(), f, remote)
})
},
}
// newFsDst creates a new dst fs from the arguments.
//
// The returned fs will never point to a file. It will point to the
// parent directory of specified path, and is returned together with
// the basename of file or directory, except if argument is only a
// remote name. Similar to cmd.NewFsDstFile, but without raising fatal
// when name of file or directory is empty (e.g. "remote:" or "remote:path/").
func newFsDst(args []string) (f fs.Fs, remote string) {
root, remote, err := fspath.Split(args[0])
if err != nil {
log.Fatalf("Parsing %q failed: %v", args[0], err)
}
if root == "" {
root = "."
}
f = cmd.NewFsDir([]string{root})
return f, remote
}
// 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, remote string) error {
t, err := timeOfTouch()
if err != nil {
return err
}
fs.Debugf(nil, "Touch time %v", t)
file, err := f.NewObject(ctx, remote)
if err != nil {
if errors.Is(err, fs.ErrorObjectNotFound) {
// Touching non-existent path, possibly creating it as new file
if remote == "" {
fs.Logf(f, "Not touching empty directory")
return nil
}
if notCreateNewFile {
fs.Logf(f, "Not touching nonexistent file due to --no-create")
return nil
}
if recursive {
// For consistency, --recursive never creates new files.
fs.Logf(f, "Not touching nonexistent file due to --recursive")
return nil
}
if operations.SkipDestructive(ctx, f, "touch (create)") {
return nil
}
fs.Debugf(f, "Touching (creating) %q", remote)
if err = createEmptyObject(ctx, remote, t, f); err != nil {
return fmt.Errorf("failed to touch (create): %w", err)
}
}
if errors.Is(err, fs.ErrorIsDir) {
// Touching existing directory
if recursive {
fs.Debugf(f, "Touching recursively files in directory %q", remote)
return operations.TouchDir(ctx, f, remote, t, true)
}
fs.Debugf(f, "Touching non-recursively files in directory %q", remote)
return operations.TouchDir(ctx, f, remote, t, false)
}
return err
}
// Touch single existing file
if !operations.SkipDestructive(ctx, remote, "touch") {
fs.Debugf(f, "Touching %q", remote)
err = file.SetModTime(ctx, t)
if err != nil {
return fmt.Errorf("failed to touch: %w", err)
}
}
return nil
}
|