Skip to content

Commit

Permalink
touch: added custom modTime setting
Browse files Browse the repository at this point in the history
Fixes #726.

Added custom modification time setting with two different modes:
+ Custom time with an optional format specifier
```shell
$ drive touch --time 20120202120000 ComedyPunchlineDrumSound.mp3
/share-testing/ComedyPunchlineDrumSound.mp3: 2012-02-02 12:00:00 +0000 UTC
$ drive touch --format "2006-01-02-15:04:05.0000Z" --time "2016-02-03-08:12:15.0070Z" outf.go
/share-testing/outf.go: 2016-02-03 08:12:15 +0000 UTC
```
The default format specifier is 20060102150405.
The mentioned custom time format has to be relative to how you would represent
"Mon Jan 2 15:04:05 -0700 MST 2006".
See the documentation for time formatting here
[time.Parse](https://golang.org/pkg/time/#Parse)

+ Custom time offset from the current clock time.
```shell
$ drive touch --duration -30h ComedyPunchlineDrumSound.mp3 outf.go
/share-testing/outf.go: 2016-09-10 08:06:39 +0000 UTC
/share-testing/ComedyPunchlineDrumSound.mp3: 2016-09-10 08:06:39 +0000
UTC
```
To set that file's modTime to 30 hours ago. For allowable
duration formats, see https://golang.org/pkg/time/#Parse.
  • Loading branch information
odeke-em committed Sep 11, 2016
1 parent 1a2e4bc commit a32b9e3
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 61 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,30 @@ $ drive touch --depth -1 --id 0fM9rt0Yc9RTPeHRfRHRRU0dIY97 0fM9rt0Yc9kJRPSTFNk9k
$ drive touch --depth 1 --matches $(seq 0 9)
```
+ You can also touch and explicitly set the modification time for files by:
```shell
$ drive touch --time 20120202120000 ComedyPunchlineDrumSound.mp3
/share-testing/ComedyPunchlineDrumSound.mp3: 2012-02-02 12:00:00 +0000 UTC
```
+ Specify the time format that you'd like to use when specifying the time e.g
```shell
$ drive touch --format "2006-01-02-15:04:05.0000Z" --time "2016-02-03-08:12:15.0070Z" outf.go
/share-testing/outf.go: 2016-02-03 08:12:15 +0000 UTC
```
The mentioned time format has to be relative to how you would represent
"Mon Jan 2 15:04:05 -0700 MST 2006".
See the documentation for time formatting here [time.Parse](https://golang.org/pkg/time/#Parse)
+ Specify the touch time offset from the clock on your machine where:
- minus(-) means ago e.g 30 hours ago -> -30h
- blank or plus(+) means from now e.g 10 minutes -> 10m or +10m
```shell
$ drive touch --duration -30h ComedyPunchlineDrumSound.mp3 outf.go
/share-testing/outf.go: 2016-09-10 08:06:39 +0000 UTC
/share-testing/ComedyPunchlineDrumSound.mp3: 2016-09-10 08:06:39 +0000 UTC
```
### Trashing and Untrashing
Files can be trashed using the `trash` command:
Expand Down
16 changes: 16 additions & 0 deletions cmd/drive/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,10 @@ type touchCmd struct {
Matches *bool `json:"matches"`
Quiet *bool `json:"quiet"`
Verbose *bool `json:"verbose"`

TouchTimeStr *string `json:"time"`
OffsetDurationStr *string `json:"duration"`
TimeFormatSpecifier *string `json:"format"`
}

func (cmd *touchCmd) Flags(fs *flag.FlagSet) *flag.FlagSet {
Expand All @@ -931,6 +935,10 @@ func (cmd *touchCmd) Flags(fs *flag.FlagSet) *flag.FlagSet {
cmd.Depth = fs.Int(drive.DepthKey, drive.DefaultMaxTraversalDepth, "max traversal depth")
cmd.Verbose = fs.Bool(drive.CLIOptionVerboseKey, true, drive.DescVerbose)

cmd.TouchTimeStr = fs.String(drive.TouchModTimeKey, "", drive.DescTouchTimeStr)
cmd.OffsetDurationStr = fs.String(drive.TouchOffsetDurationKey, "", drive.DescTouchOffsetDuration)
cmd.TimeFormatSpecifier = fs.String(drive.TouchTimeFmtSpecifierKey, drive.DefaultTouchTimeSpecifier, drive.DescTouchTimeFmtSpecifier)

return fs
}

Expand All @@ -953,6 +961,14 @@ func (cmd *touchCmd) Run(args []string, definedFlags map[string]*flag.Flag) {
Verbose: *cmd.Verbose,
}

meta := map[string][]string{
drive.TouchModTimeKey: drive.NonEmptyTrimmedStrings(*cmd.TouchTimeStr),
drive.TouchOffsetDurationKey: drive.NonEmptyTrimmedStrings(*cmd.OffsetDurationStr),
drive.TouchTimeFmtSpecifierKey: drive.NonEmptyTrimmedStrings(*cmd.TimeFormatSpecifier),
}

opts.Meta = &meta

if *cmd.Matches {
exitWithError(drive.New(context, &opts).TouchByMatch())
} else {
Expand Down
107 changes: 57 additions & 50 deletions src/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,56 +67,59 @@ const (
StarKey = "star"
UnStarKey = "unstar"

CoercedMimeKeyKey = "coerced-mime"
ExportsKey = "export"
ExportsDirKey = "exports-dir"
NoClobberKey = "no-clobber"
RecursiveKey = "recursive"
IgnoreChecksumKey = "ignore-checksum"
ExcludeOpsKey = "exclude-ops"
IgnoreConflictKey = "ignore-conflict"
IgnoreNameClashesKey = "ignore-name-clashes"
ClashesKey = "clashes"
CommentStr = "#"
DepthKey = "depth"
EmailsKey = "emails"
EmailMessageKey = "emailMessage"
ForceKey = "force"
QuietKey = "quiet"
IdKey = "id"
QuitShortKey = "q"
YesShortKey = "Y"
QuitLongKey = "quit"
MatchesKey = "matches"
HiddenKey = "hidden"
Md5Key = "md5"
NoPromptKey = "no-prompt"
SizeKey = "size"
NameKey = "name"
OpenKey = "open"
OriginalNameKey = "oname"
ModTimeKey = "modt"
LastViewedByMeTimeKey = "lvt"
AccountTypeKey = "account-type"
RoleKey = "role"
TypeKey = "type"
TrashedKey = "trashed"
SkipMimeKeyKey = "skip-mime"
MatchMimeKeyKey = "exact-mime"
ExactTitleKey = "exact-title"
MatchOwnerKey = "match-owner"
ExactOwnerKey = "exact-owner"
NotOwnerKey = "skip-owner"
SortKey = "sort"
FolderKey = "folder"
MimeKey = "mime-key"
PageSizeKey = "pagesize"
DriveRepoRelPath = "github.com/odeke-em/drive"
UrlKey = "url"
ReportIssueKey = "report-issue"
IssueTitleKey = "title"
IssueBodyKey = "body"
SkipContentCheckKey = "skip-content-check"
CoercedMimeKeyKey = "coerced-mime"
ExportsKey = "export"
ExportsDirKey = "exports-dir"
NoClobberKey = "no-clobber"
RecursiveKey = "recursive"
IgnoreChecksumKey = "ignore-checksum"
ExcludeOpsKey = "exclude-ops"
IgnoreConflictKey = "ignore-conflict"
IgnoreNameClashesKey = "ignore-name-clashes"
ClashesKey = "clashes"
CommentStr = "#"
DepthKey = "depth"
EmailsKey = "emails"
EmailMessageKey = "emailMessage"
ForceKey = "force"
QuietKey = "quiet"
IdKey = "id"
QuitShortKey = "q"
YesShortKey = "Y"
QuitLongKey = "quit"
MatchesKey = "matches"
HiddenKey = "hidden"
Md5Key = "md5"
NoPromptKey = "no-prompt"
SizeKey = "size"
NameKey = "name"
OpenKey = "open"
OriginalNameKey = "oname"
ModTimeKey = "modt"
LastViewedByMeTimeKey = "lvt"
AccountTypeKey = "account-type"
RoleKey = "role"
TypeKey = "type"
TrashedKey = "trashed"
SkipMimeKeyKey = "skip-mime"
MatchMimeKeyKey = "exact-mime"
ExactTitleKey = "exact-title"
MatchOwnerKey = "match-owner"
ExactOwnerKey = "exact-owner"
NotOwnerKey = "skip-owner"
SortKey = "sort"
FolderKey = "folder"
MimeKey = "mime-key"
PageSizeKey = "pagesize"
DriveRepoRelPath = "github.com/odeke-em/drive"
UrlKey = "url"
ReportIssueKey = "report-issue"
IssueTitleKey = "title"
IssueBodyKey = "body"
SkipContentCheckKey = "skip-content-check"
TouchModTimeKey = "time"
TouchTimeFmtSpecifierKey = "format"
TouchOffsetDurationKey = "duration"
)

const (
Expand Down Expand Up @@ -195,6 +198,10 @@ const (
DescDecryptionPassword = "decryption password"
DescWithLink = "turn off file indexing so that only those with the link can view it"
DescAllowDesktopLinks = "allows docs + sheets to be pulled as .desktop files or URL linked files"

DescTouchTimeStr = "the time each file's modification time should be set to"
DescTouchOffsetDuration = "the duration offset from now that each file's modification time should be set to e.g -32h\nSee https://golang.org/pkg/time/#ParseDuration"
DescTouchTimeFmtSpecifier = "the custom layout that you'd like your time to be set in, representative of the way 'Mon Jan 2 15:04:05 -0700 MST 2006' should be represented\nSee https://golang.org/pkg/time/#Parse"
)

const (
Expand Down
24 changes: 24 additions & 0 deletions src/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -1122,3 +1122,27 @@ func allTruthsHold(truths ...bool) bool {
}
return true
}

func parseDate(dateStr string, fmtSpecifiers ...string) (*time.Time, error) {
var err error

for _, fmtSpecifier := range fmtSpecifiers {
var t time.Time
t, err = time.Parse(fmtSpecifier, dateStr)
if err == nil {
return &t, err
}
}

return nil, err
}

func parseDurationOffsetFromNow(durationOffsetStr string) (*time.Time, error) {
d, err := time.ParseDuration(durationOffsetStr)
if err != nil {
return nil, err
}

offsetFromNow := time.Now().Add(d)
return &offsetFromNow, nil
}
23 changes: 23 additions & 0 deletions src/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,29 @@ func (r *Remote) Touch(id string) (*File, error) {
return NewRemoteFile(f), err
}

// SetModTime is an explicit command to just set the modification
// time of a remote file. It serves the purpose of Touch but with
// a custom time instead of the time on the remote server.
// See Issue https://github.com/odeke-em/drive/issues/726.
func (r *Remote) SetModTime(fileId string, modTime time.Time) (*File, error) {
repr := &drive.File{}

// Ensure that the ModifiedDate is retrieved from local
repr.ModifiedDate = toUTCString(modTime)

req := r.service.Files.Update(fileId, repr)

// We always want it to match up with the local time
req.SetModifiedDate(true)

retrieved, err := req.Do()
if err != nil {
return nil, err
}

return NewRemoteFile(retrieved), nil
}

func toUTCString(t time.Time) string {
utc := t.UTC().Round(time.Second)
// Ugly but straight forward formatting as time.Parse is such a prima donna
Expand Down
75 changes: 64 additions & 11 deletions src/touch.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,53 @@ func (g *Commands) Touch(byId bool) (err error) {
throttle := time.Tick(1e9 / 10)

chanMap := map[int]chan *keyValue{}
touchModTime, err := g.requestedTouchModTime()
if err != nil {
return err
}

for i, relToRootPath := range g.opts.Sources {
fileId := ""
if byId {
fileId = relToRootPath
}
chanMap[i] = g.touch(relToRootPath, fileId, g.opts.Depth)
chanMap[i] = g.touch(relToRootPath, fileId, g.opts.Depth, touchModTime)
<-throttle
}

multiplexOnChanMapResults(g, chanMap)
return
}

const (
DefaultTouchTimeSpecifier = "20060102150405"
)

func (g *Commands) requestedTouchModTime() (*time.Time, error) {
var requestedModTime *time.Time
metaPtr := g.opts.Meta
if metaPtr == nil {
return requestedModTime, nil
}

meta := *metaPtr

formatSpecifiers := meta[TouchTimeFmtSpecifierKey]
formatSpecifiers = append(formatSpecifiers, DefaultTouchTimeSpecifier)
modDateStrL, ok := meta[TouchModTimeKey]
if ok && len(modDateStrL) >= 1 {
return parseDate(modDateStrL[0], formatSpecifiers...)
}

// Otherwise for last resort try parsing by duration
durationOffsetStrL, ok := meta[TouchOffsetDurationKey]
if ok && len(durationOffsetStrL) >= 1 {
return parseDurationOffsetFromNow(durationOffsetStrL[0])
}

return requestedModTime, nil
}

func (g *Commands) TouchByMatch() (err error) {
mq := matchQuery{
dirPath: g.opts.Path,
Expand All @@ -74,6 +107,11 @@ func (g *Commands) TouchByMatch() (err error) {
},
}

touchModTime, err := g.requestedTouchModTime()
if err != nil {
return err
}

matches, err := g.rem.FindMatches(&mq)
if err != nil {
return err
Expand All @@ -88,7 +126,7 @@ func (g *Commands) TouchByMatch() (err error) {
continue
}

chanMap[i] = g.touch(g.opts.Path+"/"+match.Name, match.Id, g.opts.Depth)
chanMap[i] = g.touch(g.opts.Path+"/"+match.Name, match.Id, g.opts.Depth, touchModTime)
<-throttle
i += 1
}
Expand All @@ -97,7 +135,22 @@ func (g *Commands) TouchByMatch() (err error) {
return
}

func (g *Commands) touch(relToRootPath, fileId string, depth int) chan *keyValue {
// resolveTouch figures out which function to invoke
// in order to perform a touch/modTime change of a file.
func (g *Commands) resolveTouch(fileId, relToRootPath string, modTime *time.Time) (*File, error) {
if fileId == "" {
return g.touchByPath(relToRootPath, modTime)
}

// Now dealing with only fileId
if modTime == nil {
return g.rem.Touch(fileId)
}

return g.rem.SetModTime(fileId, *modTime)
}

func (g *Commands) touch(relToRootPath, fileId string, depth int, modTime *time.Time) chan *keyValue {
fileChan := make(chan *keyValue)

go func() {
Expand All @@ -111,11 +164,7 @@ func (g *Commands) touch(relToRootPath, fileId string, depth int) chan *keyValue
close(fileChan)
}()

f, arg := g.rem.Touch, fileId
if fileId == "" {
f, arg = g.touchByPath, relToRootPath
}
file, err := f(arg)
file, err := g.resolveTouch(fileId, relToRootPath, modTime)

if err != nil {
kv.value = err
Expand All @@ -140,7 +189,7 @@ func (g *Commands) touch(relToRootPath, fileId string, depth int) chan *keyValue
go func() {
defer close(childResults)
for child := range childrenChan {
childResults <- g.touch(relToRootPath+"/"+child.Name, child.Id, depth)
childResults <- g.touch(relToRootPath+"/"+child.Name, child.Id, depth, modTime)
<-throttle
}
}()
Expand All @@ -156,13 +205,17 @@ func (g *Commands) touch(relToRootPath, fileId string, depth int) chan *keyValue
return fileChan
}

func (g *Commands) touchByPath(relToRootPath string) (*File, error) {
func (g *Commands) touchByPath(relToRootPath string, modTime *time.Time) (*File, error) {
file, err := g.rem.FindByPath(relToRootPath)
if err != nil {
return nil, err
}
if file == nil {
return nil, ErrPathNotExists
}
return g.rem.Touch(file.Id)
if modTime == nil {
return g.rem.Touch(file.Id)
}

return g.rem.SetModTime(file.Id, *modTime)
}

0 comments on commit a32b9e3

Please sign in to comment.