diff --git a/client.go b/client.go index dfa9a2c..d6f94db 100644 --- a/client.go +++ b/client.go @@ -187,6 +187,13 @@ func (c *Client) doHTTPRequest(hreq *http.Request, resp *Response) (bool, error) hreq.Header.Set("User-Agent", c.UserAgent) } + // TODO: set If-Modified-Since header + // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25 + // if resp.fi != nil { + // since := resp.fi.ModTime().Format(http.TimeFormat) + // hreq.Header.Set("If-Modified-Since", since) + // } + hresp, err := c.HTTPClient.Do(hreq) if err != nil { return false, err diff --git a/client_test.go b/client_test.go index 877dabf..47077fc 100644 --- a/client_test.go +++ b/client_test.go @@ -427,3 +427,44 @@ func TestRemoteTime(t *testing.T) { t.Errorf("expected %v, got %v", time.Unix(lastmod, 0), fi.ModTime()) } } + +// TestTimeModified +func TestLastModified(t *testing.T) { + filename := "./.testLastModified" + defer os.Remove(filename) + + oldURL := fmt.Sprintf("%s?lastmod=%d", ts.URL, time.Now().Add(-24*time.Hour).Unix()) + newURL := fmt.Sprintf("%s?lastmod=%d", ts.URL, time.Now().Unix()) + + // download initial file + if _, err := Get(filename, oldURL); err != nil { + panic(err) + } + + // skip unmodifed remote file + resp, err := Get(filename, oldURL) + if err != nil { + panic(err) + } + if !resp.DidResume || resp.bytesResumed < 1 { + t.Errorf("expected client to skip unmodified file") + } + + // download modified remote file + resp, err = Get(filename, newURL) + if err != nil { + panic(err) + } + if resp.DidResume || resp.bytesResumed != 0 { + t.Errorf("expected client to download modified file") + } + + // skip unmodifed remote file + resp, err = Get(filename, oldURL) + if err != nil { + panic(err) + } + if !resp.DidResume || resp.bytesResumed < 1 { + t.Errorf("expected client to skip unmodified file") + } +} diff --git a/grab_test.go b/grab_test.go index b99a28e..5e3b796 100644 --- a/grab_test.go +++ b/grab_test.go @@ -69,7 +69,7 @@ var ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http panic(err) } lastmodt := time.Unix(lastmodi, 0).UTC() - lastmod := lastmodt.Format("Mon, 02 Jan 2006 15:04:05") + " GMT" + lastmod := lastmodt.Format(http.TimeFormat) w.Header().Set("Last-Modified", lastmod) } diff --git a/request.go b/request.go index 6d4245b..a14b98b 100644 --- a/request.go +++ b/request.go @@ -45,9 +45,10 @@ type Request struct { // exist. NoCreateDirectories bool - // IgnoreRemoteTime specifies that grab should not attempt to set the - // timestamp of the local file to match the remote file. - IgnoreRemoteTime bool + // NoSetLocalModTime specifies that grab should not attempt to set the + // timestamp of the local file to match the remote file. The operating system + // will instead set the timestamp to the current system time. + NoSetLocalModTime bool // Size specifies the expected size of the file transfer if known. If the // server response size does not match, the transfer is cancelled and diff --git a/response.go b/response.go index 4316431..dad6dfb 100644 --- a/response.go +++ b/response.go @@ -254,8 +254,6 @@ func (c *Response) readResponse(resp *http.Response) error { // // If a checksum has been requested, it will be executed on the existing file // and an error returned if it fails validation. -// -// TODO: check timestamps and/or E-Tags func (c *Response) checkExisting() (bool, error) { if c.fi == nil { return false, nil @@ -265,6 +263,16 @@ func (c *Response) checkExisting() (bool, error) { return true, ErrFileExists } + // compare last-modified timestamp + // BUG: resumed downloads always compute the local partial to be most recent + if c.HTTPResponse != nil { + // discard error as zero-time is never greater than fi.ModTime + lastmod, _ := http.ParseTime(c.HTTPResponse.Header.Get("Last-Modified")) + if lastmod.Unix() > c.fi.ModTime().Unix() { + return false, nil + } + } + // determine expected file size size := c.Request.Size if size == 0 && c.HTTPResponse != nil { @@ -441,7 +449,7 @@ func (c *Response) copy() error { } // set timestamp - if !c.Request.IgnoreRemoteTime { + if !c.Request.NoSetLocalModTime { if err := setLocalTimestamp(c.HTTPResponse, c.Filename); err != nil { return c.close(err) } diff --git a/util.go b/util.go index 1d03a1b..922dc07 100644 --- a/util.go +++ b/util.go @@ -15,7 +15,7 @@ func isCanceled(ctx context.Context) error { } } -// copyBuffer behaves similarly to io.CopyBuffer except that is checks for +// copyBuffer behaves similarly to io.CopyBuffer except that it checks for // cancelation of the given context.Context. func copyBuffer(ctx context.Context, dst io.Writer, src io.Reader, buf []byte) (written int64, err error) { if buf == nil {