|
|
|
@ -2,8 +2,11 @@ package database
|
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"context" |
|
|
|
|
"encoding/gob" |
|
|
|
|
"errors" |
|
|
|
|
"fmt" |
|
|
|
|
"log" |
|
|
|
|
"os" |
|
|
|
|
"strconv" |
|
|
|
|
"sync" |
|
|
|
|
"time" |
|
|
|
@ -20,9 +23,16 @@ type DiscogsCache struct {
|
|
|
|
|
lastRefresh time.Time |
|
|
|
|
client discogs.Discogs |
|
|
|
|
username string |
|
|
|
|
persistence bool |
|
|
|
|
persistFile string |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func NewDiscogsCache(token string, maxCacheAge time.Duration, username string) (*DiscogsCache, error) { |
|
|
|
|
type persistence struct { |
|
|
|
|
CachedRecordSlice []media.Record |
|
|
|
|
LastRefresh time.Time |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func NewDiscogsCache(token string, maxCacheAge time.Duration, username string, persist bool, persistFile string) (*DiscogsCache, error) { |
|
|
|
|
client, err := discogs.New(&discogs.Options{ |
|
|
|
|
UserAgent: "library.yetaga.in personal collection cache", |
|
|
|
|
Token: token, |
|
|
|
@ -30,21 +40,52 @@ func NewDiscogsCache(token string, maxCacheAge time.Duration, username string) (
|
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
return &DiscogsCache{ |
|
|
|
|
cache := &DiscogsCache{ |
|
|
|
|
authToken: token, |
|
|
|
|
client: client, |
|
|
|
|
maxCacheAge: maxCacheAge, |
|
|
|
|
username: username, |
|
|
|
|
}, nil |
|
|
|
|
persistence: persist, |
|
|
|
|
persistFile: persistFile, |
|
|
|
|
} |
|
|
|
|
if cache.persistence && cache.persistFile != "" { |
|
|
|
|
p := &persistence{} |
|
|
|
|
f, err := os.Open(cache.persistFile) |
|
|
|
|
if errors.Is(err, os.ErrNotExist) { |
|
|
|
|
log.Printf("%s not found, skipping file load...", cache.persistFile) |
|
|
|
|
return cache, nil |
|
|
|
|
} |
|
|
|
|
if err != nil { |
|
|
|
|
return cache, fmt.Errorf("error opening cache file %s: %w", cache.persistFile, err) |
|
|
|
|
} |
|
|
|
|
err = gob.NewDecoder(f).Decode(p) |
|
|
|
|
if err != nil { |
|
|
|
|
return cache, fmt.Errorf("error readhing from cache file %s: %w", cache.persistFile, err) |
|
|
|
|
} |
|
|
|
|
log.Printf("loaded %d records from %s", len(p.CachedRecordSlice), cache.persistFile) |
|
|
|
|
cache.cache = p.CachedRecordSlice |
|
|
|
|
cache.lastRefresh = p.LastRefresh |
|
|
|
|
if time.Now().After(cache.lastRefresh.Add(cache.maxCacheAge)) { |
|
|
|
|
log.Printf("cache expired, running refresh...") |
|
|
|
|
go func() { |
|
|
|
|
err := cache.FlushCache(context.Background()) |
|
|
|
|
if err != nil { |
|
|
|
|
log.Printf("error loading discogs content: %v", err) |
|
|
|
|
} |
|
|
|
|
}() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return cache, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (c *DiscogsCache) FlushCache(ctx context.Context) error { |
|
|
|
|
c.m.Lock() |
|
|
|
|
defer c.m.Unlock() |
|
|
|
|
records, err := c.fetchRecords(ctx, nil) |
|
|
|
|
c.cache = records |
|
|
|
|
c.lastRefresh = time.Now() |
|
|
|
|
return err |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
return c.saveRecordsToCache(ctx, records) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (c *DiscogsCache) GetAllRecords(ctx context.Context) ([]media.Record, error) { |
|
|
|
@ -52,13 +93,36 @@ func (c *DiscogsCache) GetAllRecords(ctx context.Context) ([]media.Record, error
|
|
|
|
|
defer c.m.Unlock() |
|
|
|
|
if time.Now().After(c.lastRefresh.Add(c.maxCacheAge)) { |
|
|
|
|
records, err := c.fetchRecords(ctx, nil) |
|
|
|
|
c.cache = records |
|
|
|
|
c.lastRefresh = time.Now() |
|
|
|
|
if err != nil { |
|
|
|
|
return c.cache, err |
|
|
|
|
} |
|
|
|
|
err = c.saveRecordsToCache(ctx, records) |
|
|
|
|
return c.cache, err |
|
|
|
|
} |
|
|
|
|
return c.cache, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (c *DiscogsCache) saveRecordsToCache(ctx context.Context, records []media.Record) error { |
|
|
|
|
c.cache = records |
|
|
|
|
c.lastRefresh = time.Now() |
|
|
|
|
if c.persistence && c.persistFile != "" { |
|
|
|
|
p := persistence{ |
|
|
|
|
CachedRecordSlice: c.cache, |
|
|
|
|
LastRefresh: c.lastRefresh, |
|
|
|
|
} |
|
|
|
|
f, err := os.OpenFile(c.persistFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) |
|
|
|
|
if err != nil { |
|
|
|
|
return fmt.Errorf("error opening cache file %s: %w", c.persistFile, err) |
|
|
|
|
} |
|
|
|
|
err = gob.NewEncoder(f).Encode(p) |
|
|
|
|
if err != nil { |
|
|
|
|
return fmt.Errorf("error writing to cache file %s: %w", c.persistFile, err) |
|
|
|
|
} |
|
|
|
|
log.Printf("wrote %d records to %s", len(c.cache), c.persistFile) |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (c *DiscogsCache) fetchRecords(ctx context.Context, pagination *discogs.Pagination) ([]media.Record, error) { |
|
|
|
|
records := []media.Record{} |
|
|
|
|
if pagination == nil { |
|
|
|
|