Makes the CoverCache use diskv

because in memory cache is Aaaawwwweeeessssooomme
This commit is contained in:
2016-01-21 22:19:37 +01:00
parent 4a18666fe2
commit fe6e32ec7b
2 changed files with 50 additions and 75 deletions

View File

@@ -1,77 +1,32 @@
package main package main
import ( import (
"fmt" "encoding/json"
"io" "io"
"os"
"path"
"path/filepath"
"time" "time"
"github.com/peterbourgon/diskv"
) )
// An AlbumCoverCache is used to fetch and cache Album Cover image from www.bedetheque.com // An AlbumCoverCache is used to fetch and cache Album Cover image from www.bedetheque.com
type AlbumCoverCache struct { type AlbumCoverCache struct {
basepath string dv *diskv.Diskv
getter HTTPGetter getter HTTPGetter
// time to live of the cache, data which is older than this TTL will be automatically removed // time to live of the cache, data which is older than this TTL will be automatically removed
TTL time.Duration TTL time.Duration
} }
// NewAlbumCoverCache is creating a new cache at specified location on the fs // 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{ res := &AlbumCoverCache{
basepath: path, dv: diskv.New(diskv.Options{
getter: NewRateLimitedGetter(maxRequest, window), BasePath: basepath,
TTL: 3 * 31 * 24 * time.Hour, // 3 Months CacheSizeMax: 100 * 1024 * 1024, // 100 Mb
} }),
getter: NewRateLimitedGetter(maxRequest, window),
err := os.MkdirAll(filepath.Join(res.basepath, "covers"), 0755) TTL: 3 * 31 * 24 * time.Hour, // 3 Months
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,
} }
return res
} }
func (c *AlbumCoverCache) fetch(a *Album) (io.ReadCloser, error) { 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 { if err != nil {
return nil, err 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 { if err != nil {
closeOrPanic(resp.Body, "GET:"+a.CoverURL)
return nil, err 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 // 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) { 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 err != nil {
if os.IsNotExist(err) == false {
return nil, err
}
return c.fetch(a) return c.fetch(a)
} }
// check TTL // 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) return c.fetch(a)
} }
f, err := os.Open(c.coverPath(a)) return c.dv.ReadStream(c.dataKey(a), false)
if err != nil {
return nil, err
}
return f, nil
} }

View File

@@ -23,9 +23,8 @@ func (g *errorGetter) Get(URL string) (*http.Response, error) {
} }
func (s *AlbumCoverCacheSuite) TestCanFetchCache(c *C) { 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 cache.getter = testGetter
c.Assert(err, IsNil)
var resData = []bytes.Buffer{} var resData = []bytes.Buffer{}
start := time.Now() start := time.Now()