From 4829527dac808168d3287967248da793aae2f83c Mon Sep 17 00:00:00 2001 From: albertony <12441419+albertony@users.noreply.github.com> Date: Sun, 24 Apr 2022 19:34:29 +0200 Subject: [PATCH] jottacloud: refactor timestamp handling Jottacloud have several different apis and endpoints using a mix of different timestamp formats. In existing code the list operation (after the recent liststream implementation) uses format from golang's time.RFC3339 constant. Uploads (using the allocate api) uses format from a hard coded constant with value identical to golang's time.RFC3339. And then we have the classic JFS time format, which is similar to RFC3339 but not identical, using a different constant. Also the naming is a bit confusing, since the term api is used both as a generic term and also as a reference to the newer format used in the api subdomain where the allocate endpoint is located. This commit refactors these things a bit. --- backend/jottacloud/api/types.go | 93 ++++++++++++++++++++------------ backend/jottacloud/jottacloud.go | 35 +++--------- 2 files changed, 68 insertions(+), 60 deletions(-) diff --git a/backend/jottacloud/api/types.go b/backend/jottacloud/api/types.go index ada266c8a..91e0c3095 100644 --- a/backend/jottacloud/api/types.go +++ b/backend/jottacloud/api/types.go @@ -8,42 +8,69 @@ import ( ) const ( - // default time format for almost all request and responses - timeFormat = "2006-01-02-T15:04:05Z0700" - // the API server seems to use a different format - apiTimeFormat = "2006-01-02T15:04:05Z07:00" + // default time format historically used for all request and responses. + // Similar to time.RFC3339, but with an extra '-' in front of 'T', + // and no ':' separator in timezone offset. Some newer endpoints have + // moved to proper time.RFC3339 conformant format instead. + jottaTimeFormat = "2006-01-02-T15:04:05Z0700" ) -// Time represents time values in the Jottacloud API. It uses a custom RFC3339 like format. -type Time time.Time - -// UnmarshalXML turns XML into a Time -func (t *Time) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { +// unmarshalXML turns XML into a Time +func unmarshalXMLTime(d *xml.Decoder, start xml.StartElement, timeFormat string) (time.Time, error) { var v string if err := d.DecodeElement(&v, &start); err != nil { - return err + return time.Time{}, err } if v == "" { - *t = Time(time.Time{}) - return nil + return time.Time{}, nil } newTime, err := time.Parse(timeFormat, v) if err == nil { - *t = Time(newTime) + return newTime, nil } + return time.Time{}, err +} + +// JottaTime represents time values in the classic API using a custom RFC3339 like format +type JottaTime time.Time + +// String returns JottaTime string in Jottacloud classic format +func (t JottaTime) String() string { return time.Time(t).Format(jottaTimeFormat) } + +// UnmarshalXML turns XML into a JottaTime +func (t *JottaTime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + tm, err := unmarshalXMLTime(d, start, jottaTimeFormat) + *t = JottaTime(tm) return err } -// MarshalXML turns a Time into XML -func (t *Time) MarshalXML(e *xml.Encoder, start xml.StartElement) error { +// MarshalXML turns a JottaTime into XML +func (t *JottaTime) MarshalXML(e *xml.Encoder, start xml.StartElement) error { return e.EncodeElement(t.String(), start) } -// Return Time string in Jottacloud format -func (t Time) String() string { return time.Time(t).Format(timeFormat) } +// Rfc3339Time represents time values in the newer APIs using standard RFC3339 format +type Rfc3339Time time.Time -// APIString returns Time string in Jottacloud API format -func (t Time) APIString() string { return time.Time(t).Format(apiTimeFormat) } +// String returns Rfc3339Time string in Jottacloud RFC3339 format +func (t Rfc3339Time) String() string { return time.Time(t).Format(time.RFC3339) } + +// UnmarshalXML turns XML into a Rfc3339Time +func (t *Rfc3339Time) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + tm, err := unmarshalXMLTime(d, start, time.RFC3339) + *t = Rfc3339Time(tm) + return err +} + +// MarshalXML turns a Rfc3339Time into XML +func (t *Rfc3339Time) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + return e.EncodeElement(t.String(), start) +} + +// MarshalJSON turns a Rfc3339Time into JSON +func (t *Rfc3339Time) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf("\"%s\"", t.String())), nil +} // LoginToken is struct representing the login token generated in the WebUI type LoginToken struct { @@ -333,9 +360,9 @@ type JottaFolder struct { Name string `xml:"name,attr"` Deleted Flag `xml:"deleted,attr"` Path string `xml:"path"` - CreatedAt Time `xml:"created"` - ModifiedAt Time `xml:"modified"` - Updated Time `xml:"updated"` + CreatedAt JottaTime `xml:"created"` + ModifiedAt JottaTime `xml:"modified"` + Updated JottaTime `xml:"updated"` Folders []JottaFolder `xml:"folders>folder"` Files []JottaFile `xml:"files>file"` } @@ -360,17 +387,17 @@ GET http://www.jottacloud.com/JFS////.../ // JottaFile represents a Jottacloud file type JottaFile struct { XMLName xml.Name - Name string `xml:"name,attr"` - Deleted Flag `xml:"deleted,attr"` - PublicURI string `xml:"publicURI"` - PublicSharePath string `xml:"publicSharePath"` - State string `xml:"currentRevision>state"` - CreatedAt Time `xml:"currentRevision>created"` - ModifiedAt Time `xml:"currentRevision>modified"` - Updated Time `xml:"currentRevision>updated"` - Size int64 `xml:"currentRevision>size"` - MimeType string `xml:"currentRevision>mime"` - MD5 string `xml:"currentRevision>md5"` + Name string `xml:"name,attr"` + Deleted Flag `xml:"deleted,attr"` + PublicURI string `xml:"publicURI"` + PublicSharePath string `xml:"publicSharePath"` + State string `xml:"currentRevision>state"` + CreatedAt JottaTime `xml:"currentRevision>created"` + ModifiedAt JottaTime `xml:"currentRevision>modified"` + Updated JottaTime `xml:"currentRevision>updated"` + Size int64 `xml:"currentRevision>size"` + MimeType string `xml:"currentRevision>mime"` + MD5 string `xml:"currentRevision>md5"` } // Error is a custom Error for wrapping Jottacloud error responses diff --git a/backend/jottacloud/jottacloud.go b/backend/jottacloud/jottacloud.go index be6889633..62491dea1 100644 --- a/backend/jottacloud/jottacloud.go +++ b/backend/jottacloud/jottacloud.go @@ -932,25 +932,6 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e return entries, nil } -type listStreamTime time.Time - -func (c *listStreamTime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { - var v string - if err := d.DecodeElement(&v, &start); err != nil { - return err - } - t, err := time.Parse(time.RFC3339, v) - if err != nil { - return err - } - *c = listStreamTime(t) - return nil -} - -func (c listStreamTime) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf("\"%s\"", time.Time(c).Format(time.RFC3339))), nil -} - func parseListRStream(ctx context.Context, r io.Reader, trimPrefix string, filesystem *Fs, callback func(fs.DirEntry) error) error { type stats struct { @@ -960,12 +941,12 @@ func parseListRStream(ctx context.Context, r io.Reader, trimPrefix string, files var expected, actual stats type xmlFile struct { - Path string `xml:"path"` - Name string `xml:"filename"` - Checksum string `xml:"md5"` - Size int64 `xml:"size"` - Modified listStreamTime `xml:"modified"` - Created listStreamTime `xml:"created"` + Path string `xml:"path"` + Name string `xml:"filename"` + Checksum string `xml:"md5"` + Size int64 `xml:"size"` + Modified api.Rfc3339Time `xml:"modified"` // Note: Liststream response includes 3 decimal milliseconds, but we ignore them since there is second precision everywhere else + Created api.Rfc3339Time `xml:"created"` } type xmlFolder struct { @@ -1228,7 +1209,7 @@ func (f *Fs) createOrUpdate(ctx context.Context, file string, modTime time.Time, opts.Parameters.Set("cphash", "true") - fileDate := api.Time(modTime).String() + fileDate := api.JottaTime(modTime).String() opts.ExtraHeaders["JSize"] = strconv.FormatInt(size, 10) opts.ExtraHeaders["JMd5"] = md5 opts.ExtraHeaders["JCreated"] = fileDate @@ -1749,7 +1730,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op Options: options, ExtraHeaders: make(map[string]string), } - fileDate := api.Time(src.ModTime(ctx)).APIString() + fileDate := api.Rfc3339Time(src.ModTime(ctx)).String() // the allocate request var request = api.AllocateFileRequest{