library/database/records.go

183 lines
5.1 KiB
Go

package database
import (
"context"
"encoding/gob"
"errors"
"fmt"
"log"
"os"
"strconv"
"sync"
"time"
"git.yetaga.in/alazyreader/library/media"
"github.com/irlndts/go-discogs"
)
type DiscogsCache struct {
authToken string
m sync.Mutex
cache []media.Record
maxCacheAge time.Duration
lastRefresh time.Time
client discogs.Discogs
username string
persistence bool
persistFile string
}
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,
})
if err != nil {
return nil, err
}
cache := &DiscogsCache{
authToken: token,
client: client,
maxCacheAge: maxCacheAge,
username: username,
persistence: persist,
persistFile: persistFile,
}
if cache.persistence && cache.persistFile != "" {
cache.cache, cache.lastRefresh, err = cache.loadRecordsFromFS(context.Background())
if err != nil {
return nil, fmt.Errorf("cache load failed: %w", err)
}
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)
if err != nil {
return err
}
return c.saveRecordsToCache(ctx, records)
}
func (c *DiscogsCache) GetAllRecords(ctx context.Context) ([]media.Record, error) {
c.m.Lock()
defer c.m.Unlock()
if time.Now().After(c.lastRefresh.Add(c.maxCacheAge)) {
records, err := c.fetchRecords(ctx, nil)
if err != nil {
return c.cache, err
}
err = c.saveRecordsToCache(ctx, records)
return c.cache, err
}
return c.cache, nil
}
func (c *DiscogsCache) loadRecordsFromFS(ctx context.Context) ([]media.Record, time.Time, error) {
p := &persistence{}
f, err := os.Open(c.persistFile)
if errors.Is(err, os.ErrNotExist) {
log.Printf("%s not found, skipping file load...", c.persistFile)
return nil, time.Time{}, nil
}
if err != nil {
return nil, time.Time{}, fmt.Errorf("error opening cache file %s: %w", c.persistFile, err)
}
err = gob.NewDecoder(f).Decode(p)
if err != nil {
return nil, time.Time{}, fmt.Errorf("error readhing from cache file %s: %w", c.persistFile, err)
}
log.Printf("loaded %d records from %s", len(p.CachedRecordSlice), c.persistFile)
return p.CachedRecordSlice, p.LastRefresh, 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 {
pagination = getPagination(1)
}
log.Printf("calling discogs API, page %v", pagination.Page)
coll, err := c.client.CollectionItemsByFolder(c.username, 0, pagination)
if err != nil {
return records, fmt.Errorf("error loading collection: %w", err)
}
log.Printf("length: %v, first item in list: %s", len(coll.Items), coll.Items[0].BasicInformation.Title)
for i := range coll.Items {
records = append(records, collectionItemToRecord(&coll.Items[i]))
}
// recurse down the list
if coll.Pagination.URLs.Next != "" {
coll, err := c.fetchRecords(ctx, getPagination(pagination.Page+1))
if err != nil {
return records, err
}
records = append(records, coll...)
}
return records, nil
}
func getPagination(page int) *discogs.Pagination {
return &discogs.Pagination{Page: page, Sort: "added", SortOrder: "asc", PerPage: 100}
}
func collectionItemToRecord(item *discogs.CollectionItemSource) media.Record {
artists := []string{}
for _, artist := range item.BasicInformation.Artists {
artists = append(artists, artist.Name)
}
year := strconv.Itoa(item.BasicInformation.Year)
if year == "0" {
year = ""
}
return media.Record{
ID: item.ID,
AlbumName: item.BasicInformation.Title,
Artists: artists,
Identifier: item.BasicInformation.Labels[0].Catno,
Format: item.BasicInformation.Formats[0].Name,
Genre: item.BasicInformation.Genres[0],
Label: item.BasicInformation.Labels[0].Name,
Year: year,
CoverURL: item.BasicInformation.CoverImage,
DiscogsURL: fmt.Sprintf("https://www.discogs.com/release/%v", item.ID),
}
}