All checks were successful
		
		
	
	ci/woodpecker/push/woodpecker Pipeline was successful
				
			
		
			
				
	
	
		
			183 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			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),
 | 
						|
	}
 | 
						|
}
 |