feat: cache support (#19)
- Added file and memory cache handlers - Steam provider now uses a cache to store the app list for 24h Closes #12
This commit is contained in:
parent
967a03d394
commit
02f8f05538
|
@ -6,6 +6,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/fmartingr/games-screenshot-manager/internal/models"
|
"github.com/fmartingr/games-screenshot-manager/internal/models"
|
||||||
|
"github.com/fmartingr/games-screenshot-manager/pkg/cache"
|
||||||
"github.com/fmartingr/games-screenshot-manager/pkg/processor"
|
"github.com/fmartingr/games-screenshot-manager/pkg/processor"
|
||||||
"github.com/fmartingr/games-screenshot-manager/pkg/providers/minecraft"
|
"github.com/fmartingr/games-screenshot-manager/pkg/providers/minecraft"
|
||||||
"github.com/fmartingr/games-screenshot-manager/pkg/providers/nintendo_switch"
|
"github.com/fmartingr/games-screenshot-manager/pkg/providers/nintendo_switch"
|
||||||
|
@ -27,7 +28,9 @@ func Start() {
|
||||||
logger := logrus.New()
|
logger := logrus.New()
|
||||||
flagSet := flag.NewFlagSet("gsm", flag.ExitOnError)
|
flagSet := flag.NewFlagSet("gsm", flag.ExitOnError)
|
||||||
|
|
||||||
registry := registry.NewProviderRegistry(logger)
|
cache := cache.NewFileCache(logger)
|
||||||
|
|
||||||
|
registry := registry.NewProviderRegistry(logger, cache)
|
||||||
registry.Register(minecraft.Name, minecraft.NewMinecraftProvider)
|
registry.Register(minecraft.Name, minecraft.NewMinecraftProvider)
|
||||||
registry.Register(nintendo_switch.Name, nintendo_switch.NewNintendoSwitchProvider)
|
registry.Register(nintendo_switch.Name, nintendo_switch.NewNintendoSwitchProvider)
|
||||||
registry.Register(playstation4.Name, playstation4.NewPlaystation4Provider)
|
registry.Register(playstation4.Name, playstation4.NewPlaystation4Provider)
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrCacheKeyDontExist = errors.New("cache key don't exist")
|
||||||
|
|
||||||
|
type Cache interface {
|
||||||
|
Delete(key string) error
|
||||||
|
Get(key string) (string, error)
|
||||||
|
GetExpiry(key string, expiration time.Duration) (string, error)
|
||||||
|
Put(key, value string) error
|
||||||
|
}
|
|
@ -10,4 +10,4 @@ type Provider interface {
|
||||||
FindGames(options ProviderOptions) ([]*Game, error)
|
FindGames(options ProviderOptions) ([]*Game, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProviderFactory func(logger *logrus.Logger) Provider
|
type ProviderFactory func(logger *logrus.Logger, cache Cache) Provider
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fmartingr/games-screenshot-manager/internal/models"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileCache struct {
|
||||||
|
logger *logrus.Entry
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *FileCache) Get(key string) (result string, err error) {
|
||||||
|
path := filepath.Join(c.path, key)
|
||||||
|
|
||||||
|
contents, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) || errors.Is(err, os.ErrPermission) {
|
||||||
|
return result, models.ErrCacheKeyDontExist
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(contents), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *FileCache) GetExpiry(key string, expiration time.Duration) (result string, err error) {
|
||||||
|
path := filepath.Join(c.path, key)
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return result, models.ErrCacheKeyDontExist
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.ModTime().Add(expiration).Before(time.Now()) {
|
||||||
|
c.Delete(key)
|
||||||
|
return result, models.ErrCacheKeyDontExist
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *FileCache) Put(key, value string) error {
|
||||||
|
path := filepath.Join(c.path, key)
|
||||||
|
|
||||||
|
if err := os.WriteFile(path, []byte(value), 0766); err != nil {
|
||||||
|
return fmt.Errorf("error writting cache file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *FileCache) Delete(key string) error {
|
||||||
|
path := filepath.Join(c.path, key)
|
||||||
|
return os.Remove(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFileCache(logger *logrus.Logger) *FileCache {
|
||||||
|
userCacheDir, err := os.UserCacheDir()
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatalf("error getting cache directory: %s", err)
|
||||||
|
}
|
||||||
|
path := filepath.Join(userCacheDir, "games-screenshot-manager")
|
||||||
|
|
||||||
|
if err := os.MkdirAll(path, 0755); err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FileCache{
|
||||||
|
logger: logger.WithField("from", "cache.file"),
|
||||||
|
path: path,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fmartingr/games-screenshot-manager/internal/models"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MemoryCache struct {
|
||||||
|
logger *logrus.Entry
|
||||||
|
data map[string]string
|
||||||
|
dataMu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MemoryCache) Get(key string) (result string, err error) {
|
||||||
|
c.dataMu.RLock()
|
||||||
|
defer c.dataMu.RUnlock()
|
||||||
|
|
||||||
|
result, exists := c.data[key]
|
||||||
|
if !exists {
|
||||||
|
return result, models.ErrCacheKeyDontExist
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MemoryCache) Put(key, value string) error {
|
||||||
|
c.dataMu.Lock()
|
||||||
|
c.data[key] = value
|
||||||
|
c.dataMu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MemoryCache) GetExpiry(key string, expiration time.Duration) (string, error) {
|
||||||
|
// Since this is a in-memory storage, expiration is not required as of now.
|
||||||
|
return c.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MemoryCache) Delete(key string) error {
|
||||||
|
c.dataMu.Lock()
|
||||||
|
delete(c.data, key)
|
||||||
|
c.dataMu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMemoryCache(logger *logrus.Logger) *MemoryCache {
|
||||||
|
return &MemoryCache{
|
||||||
|
logger: logger.WithField("from", "cache.file"),
|
||||||
|
data: make(map[string]string),
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,7 +48,7 @@ func (p *MinecraftProvider) FindGames(options models.ProviderOptions) ([]*models
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMinecraftProvider(logger *logrus.Logger) models.Provider {
|
func NewMinecraftProvider(logger *logrus.Logger, cache models.Cache) models.Provider {
|
||||||
return &MinecraftProvider{
|
return &MinecraftProvider{
|
||||||
logger: logger.WithField("from", "provider."+Name),
|
logger: logger.WithField("from", "provider."+Name),
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ func (p *NintendoSwitchProvider) FindGames(options models.ProviderOptions) ([]*m
|
||||||
return userGames, nil
|
return userGames, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNintendoSwitchProvider(logger *logrus.Logger) models.Provider {
|
func NewNintendoSwitchProvider(logger *logrus.Logger, cache models.Cache) models.Provider {
|
||||||
return &NintendoSwitchProvider{
|
return &NintendoSwitchProvider{
|
||||||
logger: logger.WithField("from", "provider."+Name),
|
logger: logger.WithField("from", "provider."+Name),
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ func (p *Playstation4Provider) FindGames(options models.ProviderOptions) ([]*mod
|
||||||
return userGames, nil
|
return userGames, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPlaystation4Provider(logger *logrus.Logger) models.Provider {
|
func NewPlaystation4Provider(logger *logrus.Logger, cache models.Cache) models.Provider {
|
||||||
return &Playstation4Provider{
|
return &Playstation4Provider{
|
||||||
logger: logger.WithField("from", "provider."+Name),
|
logger: logger.WithField("from", "provider."+Name),
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ func (p *RetroArchProvider) FindGames(options models.ProviderOptions) ([]*models
|
||||||
return userGames, nil
|
return userGames, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRetroArchProvider(logger *logrus.Logger) models.Provider {
|
func NewRetroArchProvider(logger *logrus.Logger, cache models.Cache) models.Provider {
|
||||||
return &RetroArchProvider{
|
return &RetroArchProvider{
|
||||||
logger: logger.WithField("from", "provider."+Name),
|
logger: logger.WithField("from", "provider."+Name),
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package steam
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -9,6 +10,7 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/fmartingr/games-screenshot-manager/internal/models"
|
"github.com/fmartingr/games-screenshot-manager/internal/models"
|
||||||
"github.com/fmartingr/games-screenshot-manager/pkg/helpers"
|
"github.com/fmartingr/games-screenshot-manager/pkg/helpers"
|
||||||
|
@ -30,25 +32,47 @@ func getBasePathForOS() (string, error) {
|
||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSteamAppList(logger *logrus.Entry, c chan SteamAppList) {
|
func getSteamAppList(logger *logrus.Entry, cache models.Cache, c chan SteamAppList) {
|
||||||
defer close(c)
|
defer close(c)
|
||||||
|
cacheKey := "steam-applist"
|
||||||
|
download := true
|
||||||
|
var payload []byte
|
||||||
|
|
||||||
response, err := helpers.DoRequest("GET", gameListURL)
|
result, err := cache.GetExpiry(cacheKey, 24*time.Hour)
|
||||||
if err != nil {
|
if err != nil && !errors.Is(err, models.ErrCacheKeyDontExist) {
|
||||||
logger.Errorf("Error making request for Steam APP List: %s", err)
|
logger.Errorf("error retrieving cache: %s", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.Body != nil {
|
if len(result) > 0 {
|
||||||
defer response.Body.Close()
|
download = false
|
||||||
|
payload = []byte(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(response.Body)
|
if download {
|
||||||
if err != nil {
|
response, err := helpers.DoRequest("GET", gameListURL)
|
||||||
logger.Errorf("Error reading steam response: %s", err)
|
if err != nil {
|
||||||
|
logger.Errorf("Error making request for Steam APP List: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.Body != nil {
|
||||||
|
defer response.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err = ioutil.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Error reading steam response: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if download {
|
||||||
|
if err := cache.Put(cacheKey, string(payload)); err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
steamListResponse := SteamAppListResponse{}
|
steamListResponse := SteamAppListResponse{}
|
||||||
jsonErr := json.Unmarshal(body, &steamListResponse)
|
jsonErr := json.Unmarshal(payload, &steamListResponse)
|
||||||
if jsonErr != nil {
|
if jsonErr != nil {
|
||||||
logger.Errorf("Error unmarshalling steam's response: %s", jsonErr)
|
logger.Errorf("Error unmarshalling steam's response: %s", jsonErr)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ type SteamAppListResponse struct {
|
||||||
|
|
||||||
type SteamProvider struct {
|
type SteamProvider struct {
|
||||||
logger *logrus.Entry
|
logger *logrus.Entry
|
||||||
|
cache models.Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *SteamProvider) FindGames(options models.ProviderOptions) ([]*models.Game, error) {
|
func (p *SteamProvider) FindGames(options models.ProviderOptions) ([]*models.Game, error) {
|
||||||
|
@ -53,7 +54,7 @@ func (p *SteamProvider) FindGames(options models.ProviderOptions) ([]*models.Gam
|
||||||
|
|
||||||
var localGames []*models.Game
|
var localGames []*models.Game
|
||||||
c := make(chan SteamAppList)
|
c := make(chan SteamAppList)
|
||||||
go getSteamAppList(p.logger, c)
|
go getSteamAppList(p.logger, p.cache, c)
|
||||||
|
|
||||||
users, err := guessUsers(basePath)
|
users, err := guessUsers(basePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -93,8 +94,9 @@ func (p *SteamProvider) FindGames(options models.ProviderOptions) ([]*models.Gam
|
||||||
return localGames, nil
|
return localGames, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSteamProvider(logger *logrus.Logger) models.Provider {
|
func NewSteamProvider(logger *logrus.Logger, cache models.Cache) models.Provider {
|
||||||
return &SteamProvider{
|
return &SteamProvider{
|
||||||
|
cache: cache,
|
||||||
logger: logger.WithField("from", "provider."+Name),
|
logger: logger.WithField("from", "provider."+Name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,9 @@ var ErrProviderAlreadyRegistered = errors.New("provider already registered")
|
||||||
var ErrProviderNotRegistered = errors.New("provider not registered")
|
var ErrProviderNotRegistered = errors.New("provider not registered")
|
||||||
|
|
||||||
type ProviderRegistry struct {
|
type ProviderRegistry struct {
|
||||||
logger *logrus.Entry
|
logger *logrus.Entry
|
||||||
|
cache models.Cache
|
||||||
|
|
||||||
providers map[string]*models.Provider
|
providers map[string]*models.Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +23,7 @@ func (r *ProviderRegistry) Register(name string, providerFactory models.Provider
|
||||||
return ErrProviderAlreadyRegistered
|
return ErrProviderAlreadyRegistered
|
||||||
}
|
}
|
||||||
|
|
||||||
provider := providerFactory(r.logger.Logger)
|
provider := providerFactory(r.logger.Logger, r.cache)
|
||||||
r.providers[name] = &provider
|
r.providers[name] = &provider
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -35,9 +37,10 @@ func (r *ProviderRegistry) Get(providerName string) (models.Provider, error) {
|
||||||
return *provider, nil
|
return *provider, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProviderRegistry(logger *logrus.Logger) *ProviderRegistry {
|
func NewProviderRegistry(logger *logrus.Logger, cache models.Cache) *ProviderRegistry {
|
||||||
return &ProviderRegistry{
|
return &ProviderRegistry{
|
||||||
logger: logger.WithField("from", "registry"),
|
logger: logger.WithField("from", "registry"),
|
||||||
|
cache: cache,
|
||||||
providers: make(map[string]*models.Provider),
|
providers: make(map[string]*models.Provider),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue