Skip to content

Commit 1d1ce3c

Browse files
authored
Merge pull request #2368 from afbjorklund/cache-time
Store time of last modified in cache dir
2 parents 7295d3b + d6bddec commit 1d1ce3c

File tree

2 files changed

+76
-3
lines changed

2 files changed

+76
-3
lines changed

pkg/downloader/downloader.go

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"path"
1414
"path/filepath"
1515
"strings"
16+
"time"
1617

1718
"github.com/cheggaaa/pb/v3"
1819
"github.com/containerd/continuity/fs"
@@ -44,6 +45,8 @@ const (
4445
type Result struct {
4546
Status Status
4647
CachePath string // "/Users/foo/Library/Caches/lima/download/by-url-sha256/<SHA256_OF_URL>/data"
48+
LastModified time.Time
49+
ContentType string
4750
ValidatedDigest bool
4851
}
4952

@@ -118,6 +121,38 @@ func WithExpectedDigest(expectedDigest digest.Digest) Opt {
118121
}
119122
}
120123

124+
func readFile(path string) string {
125+
if path == "" {
126+
return ""
127+
}
128+
if _, err := os.Stat(path); err != nil {
129+
return ""
130+
}
131+
b, err := os.ReadFile(path)
132+
if err != nil {
133+
return ""
134+
}
135+
return string(b)
136+
}
137+
138+
func readTime(path string) time.Time {
139+
if path == "" {
140+
return time.Time{}
141+
}
142+
if _, err := os.Stat(path); err != nil {
143+
return time.Time{}
144+
}
145+
b, err := os.ReadFile(path)
146+
if err != nil {
147+
return time.Time{}
148+
}
149+
t, err := time.Parse(http.TimeFormat, string(b))
150+
if err != nil {
151+
return time.Time{}
152+
}
153+
return t
154+
}
155+
121156
// Download downloads the remote resource into the local path.
122157
//
123158
// Download caches the remote resource if WithCache or WithCacheDir option is specified.
@@ -175,7 +210,7 @@ func Download(ctx context.Context, local, remote string, opts ...Opt) (*Result,
175210
}
176211

177212
if o.cacheDir == "" {
178-
if err := downloadHTTP(ctx, localPath, remote, o.description, o.expectedDigest); err != nil {
213+
if err := downloadHTTP(ctx, localPath, "", "", remote, o.description, o.expectedDigest); err != nil {
179214
return nil, err
180215
}
181216
res := &Result{
@@ -187,6 +222,8 @@ func Download(ctx context.Context, local, remote string, opts ...Opt) (*Result,
187222

188223
shad := cacheDirectoryPath(o.cacheDir, remote)
189224
shadData := filepath.Join(shad, "data")
225+
shadTime := filepath.Join(shad, "time")
226+
shadType := filepath.Join(shad, "type")
190227
shadDigest, err := cacheDigestPath(shad, o.expectedDigest)
191228
if err != nil {
192229
return nil, err
@@ -210,6 +247,8 @@ func Download(ctx context.Context, local, remote string, opts ...Opt) (*Result,
210247
res := &Result{
211248
Status: StatusUsedCache,
212249
CachePath: shadData,
250+
LastModified: readTime(shadTime),
251+
ContentType: readFile(shadType),
213252
ValidatedDigest: o.expectedDigest != "",
214253
}
215254
return res, nil
@@ -224,7 +263,7 @@ func Download(ctx context.Context, local, remote string, opts ...Opt) (*Result,
224263
if err := os.WriteFile(shadURL, []byte(remote), 0o644); err != nil {
225264
return nil, err
226265
}
227-
if err := downloadHTTP(ctx, shadData, remote, o.description, o.expectedDigest); err != nil {
266+
if err := downloadHTTP(ctx, shadData, shadTime, shadType, remote, o.description, o.expectedDigest); err != nil {
228267
return nil, err
229268
}
230269
// no need to pass the digest to copyLocal(), as we already verified the digest
@@ -239,6 +278,8 @@ func Download(ctx context.Context, local, remote string, opts ...Opt) (*Result,
239278
res := &Result{
240279
Status: StatusDownloaded,
241280
CachePath: shadData,
281+
LastModified: readTime(shadTime),
282+
ContentType: readFile(shadType),
242283
ValidatedDigest: o.expectedDigest != "",
243284
}
244285
return res, nil
@@ -266,6 +307,8 @@ func Cached(remote string, opts ...Opt) (*Result, error) {
266307

267308
shad := cacheDirectoryPath(o.cacheDir, remote)
268309
shadData := filepath.Join(shad, "data")
310+
shadTime := filepath.Join(shad, "time")
311+
shadType := filepath.Join(shad, "type")
269312
shadDigest, err := cacheDigestPath(shad, o.expectedDigest)
270313
if err != nil {
271314
return nil, err
@@ -285,6 +328,8 @@ func Cached(remote string, opts ...Opt) (*Result, error) {
285328
res := &Result{
286329
Status: StatusUsedCache,
287330
CachePath: shadData,
331+
LastModified: readTime(shadTime),
332+
ContentType: readFile(shadType),
288333
ValidatedDigest: o.expectedDigest != "",
289334
}
290335
return res, nil
@@ -293,6 +338,8 @@ func Cached(remote string, opts ...Opt) (*Result, error) {
293338
// cacheDirectoryPath returns the cache subdirectory path.
294339
// - "url" file contains the url
295340
// - "data" file contains the data
341+
// - "time" file contains the time (Last-Modified header)
342+
// - "type" file contains the type (Content-Type header)
296343
func cacheDirectoryPath(cacheDir, remote string) string {
297344
return filepath.Join(cacheDir, "download", "by-url-sha256", fmt.Sprintf("%x", sha256.Sum256([]byte(remote))))
298345
}
@@ -470,7 +517,7 @@ func validateLocalFileDigest(localPath string, expectedDigest digest.Digest) err
470517
return nil
471518
}
472519

473-
func downloadHTTP(ctx context.Context, localPath, url, description string, expectedDigest digest.Digest) error {
520+
func downloadHTTP(ctx context.Context, localPath, lastModified, contentType, url, description string, expectedDigest digest.Digest) error {
474521
if localPath == "" {
475522
return fmt.Errorf("downloadHTTP: got empty localPath")
476523
}
@@ -489,6 +536,18 @@ func downloadHTTP(ctx context.Context, localPath, url, description string, expec
489536
if err != nil {
490537
return err
491538
}
539+
if lastModified != "" {
540+
lm := resp.Header.Get("Last-Modified")
541+
if err := os.WriteFile(lastModified, []byte(lm), 0o644); err != nil {
542+
return err
543+
}
544+
}
545+
if contentType != "" {
546+
ct := resp.Header.Get("Content-Type")
547+
if err := os.WriteFile(contentType, []byte(ct), 0o644); err != nil {
548+
return err
549+
}
550+
}
492551
defer resp.Body.Close()
493552
bar, err := progressbar.New(resp.ContentLength)
494553
if err != nil {

pkg/downloader/downloader_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"runtime"
1111
"strings"
1212
"testing"
13+
"time"
1314

1415
"github.com/opencontainers/go-digest"
1516
"gotest.tools/v3/assert"
@@ -25,6 +26,8 @@ func TestDownloadRemote(t *testing.T) {
2526
t.Cleanup(ts.Close)
2627
dummyRemoteFileURL := ts.URL + "/downloader.txt"
2728
const dummyRemoteFileDigest = "sha256:380481d26f897403368be7cb86ca03a4bc14b125bfaf2b93bff809a5a2ad717e"
29+
dummyRemoteFileStat, err := os.Stat(filepath.Join("testdata", "downloader.txt"))
30+
assert.NilError(t, err)
2831

2932
t.Run("without cache", func(t *testing.T) {
3033
t.Run("without digest", func(t *testing.T) {
@@ -105,6 +108,17 @@ func TestDownloadRemote(t *testing.T) {
105108
_, err = Cached(dummyRemoteFileURL, WithExpectedDigest(wrongDigest), WithCacheDir(cacheDir))
106109
assert.ErrorContains(t, err, "expected digest")
107110
})
111+
t.Run("metadata", func(t *testing.T) {
112+
_, err := Cached(dummyRemoteFileURL, WithExpectedDigest(dummyRemoteFileDigest))
113+
assert.ErrorContains(t, err, "cache directory to be specified")
114+
115+
cacheDir := filepath.Join(t.TempDir(), "cache")
116+
r, err := Download(context.Background(), "", dummyRemoteFileURL, WithExpectedDigest(dummyRemoteFileDigest), WithCacheDir(cacheDir))
117+
assert.NilError(t, err)
118+
assert.Equal(t, StatusDownloaded, r.Status)
119+
assert.Equal(t, dummyRemoteFileStat.ModTime().Truncate(time.Second).UTC(), r.LastModified)
120+
assert.Equal(t, "text/plain; charset=utf-8", r.ContentType)
121+
})
108122
}
109123

110124
func TestDownloadLocal(t *testing.T) {

0 commit comments

Comments
 (0)