package main import ( "encoding/json" "fmt" "io" "sync" "time" "github.com/peterbourgon/diskv" ) // An AlbumCoverCache is used to fetch and cache Album Cover image from www.bedetheque.com type AlbumCoverCache struct { dv *diskv.Diskv 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 TTL time.Duration } type ErrorNotRegistered AlbumID func (e ErrorNotRegistered) Error() string { return fmt.Sprintf("Cover URL for Album %d is not registered", AlbumID(e)) } // NewAlbumCoverCache is creating a new cache at specified location on the fs func NewAlbumCoverCache(basepath string, maxRequest uint, window time.Duration) *AlbumCoverCache { res := &AlbumCoverCache{ dv: diskv.New(diskv.Options{ BasePath: basepath, CacheSizeMax: 100 * 1024 * 1024, // 100 Mb }), URLs: make(map[AlbumID]string), getter: NewRateLimitedGetter(maxRequest, window), TTL: 3 * 31 * 24 * time.Hour, // 3 Months } return res } func (c *AlbumCoverCache) fetch(ID AlbumID) (io.ReadCloser, string, error) { URL, ok := c.URLs[ID] if ok == false { return nil, "", ErrorNotRegistered(ID) } ext := AlbumCoverExt(URL) resp, err := c.getter.Get(URL) if err != nil { return nil, ext, err } defer closeOrPanic(resp.Body, "GET "+URL) err = c.dv.WriteStream(c.dataKey(ID), resp.Body, false) if err != nil { return nil, ext, err } md := coverMetadata{ Age: time.Now(), } mdbyte, err := json.Marshal(&md) if err == nil { err = c.dv.Write(c.metadataKey(ID), mdbyte) if err != nil { return nil, ext, err } } rc, err := c.dv.ReadStream(c.dataKey(ID), false) return rc, ext, err } type coverMetadata struct { Age time.Time } func (c *AlbumCoverCache) metadataKey(ID AlbumID) string { return AlbumIDString(ID) + ".metadata" } func (c *AlbumCoverCache) dataKey(ID AlbumID) string { 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 func (c *AlbumCoverCache) GetCover(ID AlbumID) (io.ReadCloser, string, error) { c.lock.RLock() defer c.lock.RUnlock() if c.dv.Has(c.metadataKey(ID)) == false { return c.fetch(ID) } mdbytes, err := c.dv.Read(c.metadataKey(ID)) if err != nil { return c.fetch(ID) } var md coverMetadata err = json.Unmarshal(mdbytes, &md) if err != nil { return c.fetch(ID) } // check TTL if time.Now().Add(-c.TTL).After(md.Age) == true { return c.fetch(ID) } URL := c.URLs[ID] rc, err := c.dv.ReadStream(c.dataKey(ID), false) return rc, AlbumCoverExt(URL), err }