Changes AlbumCacheCover interface
This commit is contained in:
@@ -2,7 +2,9 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/peterbourgon/diskv"
|
"github.com/peterbourgon/diskv"
|
||||||
@@ -12,6 +14,8 @@ import (
|
|||||||
type AlbumCoverCache struct {
|
type AlbumCoverCache struct {
|
||||||
dv *diskv.Diskv
|
dv *diskv.Diskv
|
||||||
getter HTTPGetter
|
getter HTTPGetter
|
||||||
|
URLs map[AlbumID]string
|
||||||
|
lock sync.RWMutex
|
||||||
// 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
|
||||||
}
|
}
|
||||||
@@ -23,70 +27,85 @@ func NewAlbumCoverCache(basepath string, maxRequest uint, window time.Duration)
|
|||||||
BasePath: basepath,
|
BasePath: basepath,
|
||||||
CacheSizeMax: 100 * 1024 * 1024, // 100 Mb
|
CacheSizeMax: 100 * 1024 * 1024, // 100 Mb
|
||||||
}),
|
}),
|
||||||
|
URLs: make(map[AlbumID]string),
|
||||||
getter: NewRateLimitedGetter(maxRequest, window),
|
getter: NewRateLimitedGetter(maxRequest, window),
|
||||||
TTL: 3 * 31 * 24 * time.Hour, // 3 Months
|
TTL: 3 * 31 * 24 * time.Hour, // 3 Months
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *AlbumCoverCache) fetch(a *Album) (io.ReadCloser, error) {
|
func (c *AlbumCoverCache) fetch(ID AlbumID) (io.ReadCloser, error) {
|
||||||
resp, err := c.getter.Get(a.CoverURL)
|
URL, ok := c.URLs[ID]
|
||||||
if err != nil {
|
if ok == false {
|
||||||
return nil, err
|
return nil, fmt.Errorf("Cover URL for Album %d is not registered", ID)
|
||||||
}
|
}
|
||||||
defer closeOrPanic(resp.Body, "GET "+a.CoverURL)
|
|
||||||
|
|
||||||
err = c.dv.WriteStream(c.dataKey(a), resp.Body, false)
|
resp, err := c.getter.Get(URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer closeOrPanic(resp.Body, "GET "+URL)
|
||||||
|
|
||||||
|
err = c.dv.WriteStream(c.dataKey(ID), resp.Body, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
md := coverMetadata{
|
md := coverMetadata{
|
||||||
Age: time.Now(),
|
Age: time.Now(),
|
||||||
}
|
}
|
||||||
mdbyte, err := json.Marshal(&md)
|
mdbyte, err := json.Marshal(&md)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = c.dv.Write(c.metadataKey(a), mdbyte)
|
err = c.dv.Write(c.metadataKey(ID), mdbyte)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.dv.ReadStream(c.dataKey(a), false)
|
return c.dv.ReadStream(c.dataKey(ID), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
type coverMetadata struct {
|
type coverMetadata struct {
|
||||||
Age time.Time
|
Age time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *AlbumCoverCache) metadataKey(a *Album) string {
|
func (c *AlbumCoverCache) metadataKey(ID AlbumID) string {
|
||||||
return AlbumIDString(a.ID) + ".metadata"
|
return AlbumIDString(ID) + ".metadata"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *AlbumCoverCache) dataKey(a *Album) string {
|
func (c *AlbumCoverCache) dataKey(ID AlbumID) string {
|
||||||
return AlbumIDString(a.ID) + ".data"
|
return AlbumIDString(ID) + ".data"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AlbumCoverCache) RegisterCover(ID AlbumID, URL string) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
c.URLs[ID] = URL
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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(ID AlbumID) (io.ReadCloser, error) {
|
||||||
//
|
c.lock.RLock()
|
||||||
if c.dv.Has(c.metadataKey(a)) == false {
|
defer c.lock.RUnlock()
|
||||||
return c.fetch(a)
|
|
||||||
|
if c.dv.Has(c.metadataKey(ID)) == false {
|
||||||
|
return c.fetch(ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
mdbytes, err := c.dv.Read(c.metadataKey(a))
|
mdbytes, err := c.dv.Read(c.metadataKey(ID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.fetch(a)
|
return c.fetch(ID)
|
||||||
}
|
}
|
||||||
var md coverMetadata
|
var md coverMetadata
|
||||||
err = json.Unmarshal(mdbytes, &md)
|
err = json.Unmarshal(mdbytes, &md)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.fetch(a)
|
return c.fetch(ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check TTL
|
// check TTL
|
||||||
if time.Now().Add(-c.TTL).After(md.Age) == true {
|
if time.Now().Add(-c.TTL).After(md.Age) == true {
|
||||||
return c.fetch(a)
|
return c.fetch(ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.dv.ReadStream(c.dataKey(a), false)
|
return c.dv.ReadStream(c.dataKey(ID), false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import (
|
|||||||
. "gopkg.in/check.v1"
|
. "gopkg.in/check.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AlbumCoverCacheSuite struct{}
|
type AlbumCoverCacheSuite struct {
|
||||||
|
cache *AlbumCoverCache
|
||||||
|
}
|
||||||
|
|
||||||
var _ = Suite(&AlbumCoverCacheSuite{})
|
var _ = Suite(&AlbumCoverCacheSuite{})
|
||||||
|
|
||||||
@@ -22,14 +24,21 @@ func (g *errorGetter) Get(URL string) (*http.Response, error) {
|
|||||||
return nil, fmt.Errorf("I will always have an error")
|
return nil, fmt.Errorf("I will always have an error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *AlbumCoverCacheSuite) SetUpSuite(c *C) {
|
||||||
|
s.cache = NewAlbumCoverCache(c.MkDir(), 10, 10*time.Second)
|
||||||
|
s.cache.getter = testGetter
|
||||||
|
|
||||||
|
for _, a := range albumsDataTest {
|
||||||
|
s.cache.RegisterCover(a.ID, a.CoverURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *AlbumCoverCacheSuite) TestCanFetchCache(c *C) {
|
func (s *AlbumCoverCacheSuite) TestCanFetchCache(c *C) {
|
||||||
cache := NewAlbumCoverCache(c.MkDir(), 10, 10*time.Second)
|
|
||||||
cache.getter = testGetter
|
|
||||||
|
|
||||||
var resData = []bytes.Buffer{}
|
var resData = []bytes.Buffer{}
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
for _, a := range albumsDataTest {
|
for _, a := range albumsDataTest {
|
||||||
cover, err := cache.GetCover(&a)
|
cover, err := s.cache.GetCover(a.ID)
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if c.Check(err, IsNil) == true {
|
if c.Check(err, IsNil) == true {
|
||||||
_, err := io.Copy(&buf, cover)
|
_, err := io.Copy(&buf, cover)
|
||||||
@@ -39,12 +48,12 @@ func (s *AlbumCoverCacheSuite) TestCanFetchCache(c *C) {
|
|||||||
resData = append(resData, buf)
|
resData = append(resData, buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.getter = &errorGetter{}
|
s.cache.getter = &errorGetter{}
|
||||||
|
|
||||||
// now we check that we get it again, but from the disk, not
|
// now we check that we get it again, but from the disk, not
|
||||||
// hitting the web
|
// hitting the web
|
||||||
for i, a := range albumsDataTest {
|
for i, a := range albumsDataTest {
|
||||||
cover, err := cache.GetCover(&a)
|
cover, err := s.cache.GetCover(a.ID)
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if c.Check(err, IsNil) == true {
|
if c.Check(err, IsNil) == true {
|
||||||
_, err := io.Copy(&buf, cover)
|
_, err := io.Copy(&buf, cover)
|
||||||
@@ -56,10 +65,10 @@ func (s *AlbumCoverCacheSuite) TestCanFetchCache(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// now if we it the TTL, we will reftech and get error
|
// now if we it the TTL, we will reftech and get error
|
||||||
cache.TTL = 0
|
s.cache.TTL = 0
|
||||||
|
|
||||||
for _, a := range albumsDataTest {
|
for _, a := range albumsDataTest {
|
||||||
cover, err := cache.GetCover(&a)
|
cover, err := s.cache.GetCover(a.ID)
|
||||||
c.Check(cover, IsNil)
|
c.Check(cover, IsNil)
|
||||||
c.Check(err, ErrorMatches, "I will always have an error")
|
c.Check(err, ErrorMatches, "I will always have an error")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user