Makes the CoverCache use diskv
because in memory cache is Aaaawwwweeeessssooomme
This commit is contained in:
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user