package main import ( "encoding/json" "io" "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 // 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(basepath string, maxRequest uint, window time.Duration) *AlbumCoverCache { res := &AlbumCoverCache{ 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) { resp, err := c.getter.Get(a.CoverURL) if err != nil { return nil, err } defer closeOrPanic(resp.Body, "GET "+a.CoverURL) err = c.dv.WriteStream(c.dataKey(a), resp.Body, false) if err != nil { return nil, err } md := coverMetadata{ Age: time.Now(), } mdbyte, err := json.Marshal(&md) if err == nil { err = c.dv.Write(c.metadataKey(a), mdbyte) if err != nil { return nil, err } } 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) { // 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 { return c.fetch(a) } // check TTL if time.Now().Add(-c.TTL).After(md.Age) == true { return c.fetch(a) } return c.dv.ReadStream(c.dataKey(a), false) }