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"
|
||||
|
||||
"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/providers/minecraft"
|
||||
"github.com/fmartingr/games-screenshot-manager/pkg/providers/nintendo_switch"
|
||||
|
@ -27,7 +28,9 @@ func Start() {
|
|||
logger := logrus.New()
|
||||
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(nintendo_switch.Name, nintendo_switch.NewNintendoSwitchProvider)
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func NewMinecraftProvider(logger *logrus.Logger) models.Provider {
|
||||
func NewMinecraftProvider(logger *logrus.Logger, cache models.Cache) models.Provider {
|
||||
return &MinecraftProvider{
|
||||
logger: logger.WithField("from", "provider."+Name),
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ func (p *NintendoSwitchProvider) FindGames(options models.ProviderOptions) ([]*m
|
|||
return userGames, nil
|
||||
}
|
||||
|
||||
func NewNintendoSwitchProvider(logger *logrus.Logger) models.Provider {
|
||||
func NewNintendoSwitchProvider(logger *logrus.Logger, cache models.Cache) models.Provider {
|
||||
return &NintendoSwitchProvider{
|
||||
logger: logger.WithField("from", "provider."+Name),
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ func (p *Playstation4Provider) FindGames(options models.ProviderOptions) ([]*mod
|
|||
return userGames, nil
|
||||
}
|
||||
|
||||
func NewPlaystation4Provider(logger *logrus.Logger) models.Provider {
|
||||
func NewPlaystation4Provider(logger *logrus.Logger, cache models.Cache) models.Provider {
|
||||
return &Playstation4Provider{
|
||||
logger: logger.WithField("from", "provider."+Name),
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ func (p *RetroArchProvider) FindGames(options models.ProviderOptions) ([]*models
|
|||
return userGames, nil
|
||||
}
|
||||
|
||||
func NewRetroArchProvider(logger *logrus.Logger) models.Provider {
|
||||
func NewRetroArchProvider(logger *logrus.Logger, cache models.Cache) models.Provider {
|
||||
return &RetroArchProvider{
|
||||
logger: logger.WithField("from", "provider."+Name),
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package steam
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
@ -9,6 +10,7 @@ import (
|
|||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fmartingr/games-screenshot-manager/internal/models"
|
||||
"github.com/fmartingr/games-screenshot-manager/pkg/helpers"
|
||||
|
@ -30,25 +32,47 @@ func getBasePathForOS() (string, error) {
|
|||
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)
|
||||
cacheKey := "steam-applist"
|
||||
download := true
|
||||
var payload []byte
|
||||
|
||||
response, err := helpers.DoRequest("GET", gameListURL)
|
||||
if err != nil {
|
||||
logger.Errorf("Error making request for Steam APP List: %s", err)
|
||||
result, err := cache.GetExpiry(cacheKey, 24*time.Hour)
|
||||
if err != nil && !errors.Is(err, models.ErrCacheKeyDontExist) {
|
||||
logger.Errorf("error retrieving cache: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if response.Body != nil {
|
||||
defer response.Body.Close()
|
||||
if len(result) > 0 {
|
||||
download = false
|
||||
payload = []byte(result)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
logger.Errorf("Error reading steam response: %s", err)
|
||||
if download {
|
||||
response, err := helpers.DoRequest("GET", gameListURL)
|
||||
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{}
|
||||
jsonErr := json.Unmarshal(body, &steamListResponse)
|
||||
jsonErr := json.Unmarshal(payload, &steamListResponse)
|
||||
if jsonErr != nil {
|
||||
logger.Errorf("Error unmarshalling steam's response: %s", jsonErr)
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ type SteamAppListResponse struct {
|
|||
|
||||
type SteamProvider struct {
|
||||
logger *logrus.Entry
|
||||
cache models.Cache
|
||||
}
|
||||
|
||||
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
|
||||
c := make(chan SteamAppList)
|
||||
go getSteamAppList(p.logger, c)
|
||||
go getSteamAppList(p.logger, p.cache, c)
|
||||
|
||||
users, err := guessUsers(basePath)
|
||||
if err != nil {
|
||||
|
@ -93,8 +94,9 @@ func (p *SteamProvider) FindGames(options models.ProviderOptions) ([]*models.Gam
|
|||
return localGames, nil
|
||||
}
|
||||
|
||||
func NewSteamProvider(logger *logrus.Logger) models.Provider {
|
||||
func NewSteamProvider(logger *logrus.Logger, cache models.Cache) models.Provider {
|
||||
return &SteamProvider{
|
||||
cache: cache,
|
||||
logger: logger.WithField("from", "provider."+Name),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,9 @@ var ErrProviderAlreadyRegistered = errors.New("provider already registered")
|
|||
var ErrProviderNotRegistered = errors.New("provider not registered")
|
||||
|
||||
type ProviderRegistry struct {
|
||||
logger *logrus.Entry
|
||||
logger *logrus.Entry
|
||||
cache models.Cache
|
||||
|
||||
providers map[string]*models.Provider
|
||||
}
|
||||
|
||||
|
@ -21,7 +23,7 @@ func (r *ProviderRegistry) Register(name string, providerFactory models.Provider
|
|||
return ErrProviderAlreadyRegistered
|
||||
}
|
||||
|
||||
provider := providerFactory(r.logger.Logger)
|
||||
provider := providerFactory(r.logger.Logger, r.cache)
|
||||
r.providers[name] = &provider
|
||||
|
||||
return nil
|
||||
|
@ -35,9 +37,10 @@ func (r *ProviderRegistry) Get(providerName string) (models.Provider, error) {
|
|||
return *provider, nil
|
||||
}
|
||||
|
||||
func NewProviderRegistry(logger *logrus.Logger) *ProviderRegistry {
|
||||
func NewProviderRegistry(logger *logrus.Logger, cache models.Cache) *ProviderRegistry {
|
||||
return &ProviderRegistry{
|
||||
logger: logger.WithField("from", "registry"),
|
||||
cache: cache,
|
||||
providers: make(map[string]*models.Provider),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue