diff --git a/album_cover_cache.go b/album_cover_cache.go index 7134777..06f813c 100644 --- a/album_cover_cache.go +++ b/album_cover_cache.go @@ -1,77 +1,32 @@ package main import ( - "fmt" + "encoding/json" "io" - "os" - "path" - "path/filepath" "time" + + "github.com/peterbourgon/diskv" ) // An AlbumCoverCache is used to fetch and cache Album Cover image from www.bedetheque.com type AlbumCoverCache struct { - basepath string - getter HTTPGetter + dv *diskv.Diskv + getter HTTPGetter // time to live of the cache, data which is older than this TTL will be automatically removed TTL time.Duration } // NewAlbumCoverCache is creating a new cache at specified location on the fs -func NewAlbumCoverCache(path string, maxRequest uint, window time.Duration) (*AlbumCoverCache, error) { +func NewAlbumCoverCache(basepath string, maxRequest uint, window time.Duration) *AlbumCoverCache { res := &AlbumCoverCache{ - basepath: path, - getter: NewRateLimitedGetter(maxRequest, window), - TTL: 3 * 31 * 24 * time.Hour, // 3 Months - } - - err := os.MkdirAll(filepath.Join(res.basepath, "covers"), 0755) - if err != nil { - return nil, err - } - - return res, nil -} - -// CoverPath gets the path of the cover in the cache -func (c *AlbumCoverCache) coverPath(a *Album) string { - return filepath.Join(c.basepath, "covers", fmt.Sprintf("%d%s", a.ID, path.Ext(a.CoverURL))) -} - -type teeReaderCloser struct { - r io.Reader - cr, cw io.Closer -} - -func (t *teeReaderCloser) Read(p []byte) (int, error) { - return t.r.Read(p) -} - -func (t *teeReaderCloser) Close() error { - err1 := t.cr.Close() - err2 := t.cw.Close() - if err1 == nil && err2 == nil { - return nil - } - - if err1 == nil { - return err2 - } - - if err2 == nil { - return err1 - } - - return fmt.Errorf("%s;%s", err1, err2) -} - -// NewTeeReadCloser creates a new ReadCloser that writes to w the byte it reads from r -func NewTeeReadCloser(r io.ReadCloser, w io.WriteCloser) io.ReadCloser { - return &teeReaderCloser{ - r: io.TeeReader(r, w), - cr: r, - cw: w, + dv: diskv.New(diskv.Options{ + BasePath: basepath, + CacheSizeMax: 100 * 1024 * 1024, // 100 Mb + }), + getter: NewRateLimitedGetter(maxRequest, window), + TTL: 3 * 31 * 24 * time.Hour, // 3 Months } + return res } func (c *AlbumCoverCache) fetch(a *Album) (io.ReadCloser, error) { @@ -79,35 +34,56 @@ func (c *AlbumCoverCache) fetch(a *Album) (io.ReadCloser, error) { if err != nil { return nil, err } - f, err := os.Create(c.coverPath(a)) + defer closeOrPanic(resp.Body, "GET "+a.CoverURL) + + err = c.dv.WriteStream(c.dataKey(a), resp.Body, false) if err != nil { - closeOrPanic(resp.Body, "GET:"+a.CoverURL) return nil, err } + md := coverMetadata{ + Age: time.Now(), + } + mdbyte, err := json.Marshal(&md) + if err == nil { + c.dv.Write(c.metadataKey(a), mdbyte) + } - return NewTeeReadCloser(resp.Body, f), nil + return c.dv.ReadStream(c.dataKey(a), false) +} + +type coverMetadata struct { + Age time.Time +} + +func (c *AlbumCoverCache) metadataKey(a *Album) string { + return AlbumIDString(a.ID) + ".metadata" +} + +func (c *AlbumCoverCache) dataKey(a *Album) string { + return AlbumIDString(a.ID) + ".data" } // GetCover retrieves from the cache or either from www.bedetheque.com the Cover of an album func (c *AlbumCoverCache) GetCover(a *Album) (io.ReadCloser, error) { - // we should lock the cache while we are using it - info, err := os.Stat(c.coverPath(a)) + // + if c.dv.Has(c.metadataKey(a)) == false { + return c.fetch(a) + } + + mdbytes, err := c.dv.Read(c.metadataKey(a)) + if err != nil { + return c.fetch(a) + } + var md coverMetadata + err = json.Unmarshal(mdbytes, &md) if err != nil { - if os.IsNotExist(err) == false { - return nil, err - } return c.fetch(a) } // check TTL - if info.ModTime().Before(time.Now().Add(-c.TTL)) == true { + if time.Now().Add(-c.TTL).After(md.Age) == true { return c.fetch(a) } - f, err := os.Open(c.coverPath(a)) - if err != nil { - return nil, err - } - - return f, nil + return c.dv.ReadStream(c.dataKey(a), false) } diff --git a/album_cover_cache_test.go b/album_cover_cache_test.go index 2a78586..78b4e32 100644 --- a/album_cover_cache_test.go +++ b/album_cover_cache_test.go @@ -23,9 +23,8 @@ func (g *errorGetter) Get(URL string) (*http.Response, error) { } func (s *AlbumCoverCacheSuite) TestCanFetchCache(c *C) { - cache, err := NewAlbumCoverCache(c.MkDir(), 10, 10*time.Second) + cache := NewAlbumCoverCache(c.MkDir(), 10, 10*time.Second) cache.getter = testGetter - c.Assert(err, IsNil) var resData = []bytes.Buffer{} start := time.Now()