feat: improved logging with logrus
Propagated a logrus.Logger/Entry along for the processor, registry and providers to use for events that may not require to stop processing but are useful to know otherwise. Changed some functions to return errors instead of failing with a logger trying to centralice the logging or returning of the main failing points in the main components of the application. Closes #13
This commit is contained in:
parent
cdd9ce1301
commit
967a03d394
1
go.mod
1
go.mod
|
@ -5,4 +5,5 @@ go 1.15
|
|||
require (
|
||||
github.com/gosimple/slug v1.12.0
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
)
|
||||
|
|
10
go.sum
10
go.sum
|
@ -1,6 +1,16 @@
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gosimple/slug v1.12.0 h1:xzuhj7G7cGtd34NXnW/yF0l+AGNfWqwgh/IXgFy7dnc=
|
||||
github.com/gosimple/slug v1.12.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
|
||||
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
|
||||
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
|
@ -3,7 +3,6 @@ package cli
|
|||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/fmartingr/games-screenshot-manager/internal/models"
|
||||
|
@ -14,6 +13,7 @@ import (
|
|||
"github.com/fmartingr/games-screenshot-manager/pkg/providers/retroarch"
|
||||
"github.com/fmartingr/games-screenshot-manager/pkg/providers/steam"
|
||||
"github.com/fmartingr/games-screenshot-manager/pkg/registry"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const defaultOutputPath string = "./Output"
|
||||
|
@ -24,15 +24,16 @@ const defaultDryRun bool = false
|
|||
const defaultDownloadCovers bool = false
|
||||
|
||||
func Start() {
|
||||
registry := registry.NewProviderRegistry()
|
||||
registry.Register(minecraft.Name, minecraft.NewMinecraftProvider())
|
||||
registry.Register(nintendo_switch.Name, nintendo_switch.NewNintendoSwitchProvider())
|
||||
registry.Register(playstation4.Name, playstation4.NewPlaystation4Provider())
|
||||
registry.Register(steam.Name, steam.NewSteamProvider())
|
||||
registry.Register(retroarch.Name, retroarch.NewRetroArchProvider())
|
||||
|
||||
logger := logrus.New()
|
||||
flagSet := flag.NewFlagSet("gsm", flag.ExitOnError)
|
||||
|
||||
registry := registry.NewProviderRegistry(logger)
|
||||
registry.Register(minecraft.Name, minecraft.NewMinecraftProvider)
|
||||
registry.Register(nintendo_switch.Name, nintendo_switch.NewNintendoSwitchProvider)
|
||||
registry.Register(playstation4.Name, playstation4.NewPlaystation4Provider)
|
||||
registry.Register(steam.Name, steam.NewSteamProvider)
|
||||
registry.Register(retroarch.Name, retroarch.NewRetroArchProvider)
|
||||
|
||||
options := models.Options{
|
||||
ProcessBufferSize: 32,
|
||||
}
|
||||
|
@ -46,22 +47,33 @@ func Start() {
|
|||
providerOptions := models.ProviderOptions{}
|
||||
flagSet.StringVar(&providerOptions.InputPath, "input-path", defaultInputPath, "Input path for the provider that requires it")
|
||||
|
||||
flagSet.Parse(os.Args[1:])
|
||||
loglevelFlag := flagSet.String("log-level", logrus.InfoLevel.String(), "Log level")
|
||||
|
||||
if err := flagSet.Parse(os.Args[1:]); err != nil {
|
||||
logger.Errorf("error parsing args: %s", err)
|
||||
}
|
||||
|
||||
loglevel, err := logrus.ParseLevel(*loglevelFlag)
|
||||
if err != nil {
|
||||
logger.Warnf("Invalid loglevel %s, using %s instead.", *loglevelFlag, logrus.InfoLevel.String())
|
||||
loglevel = logrus.InfoLevel
|
||||
}
|
||||
logger.SetLevel(loglevel)
|
||||
|
||||
provider, err := registry.Get(*providerName)
|
||||
if err != nil {
|
||||
log.Printf("Provider %s not found!", *providerName)
|
||||
logger.Errorf("Provider %s not found!", *providerName)
|
||||
return
|
||||
}
|
||||
games, err := provider.FindGames(providerOptions)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
logger.Errorf("Error obtaining game list: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(games) > 0 {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
processor := processor.NewProcessor(options)
|
||||
processor := processor.NewProcessor(logger, options)
|
||||
processor.Start(ctx)
|
||||
|
||||
for _, g := range games {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package models
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
type ProviderOptions struct {
|
||||
InputPath string
|
||||
}
|
||||
|
@ -7,3 +9,5 @@ type ProviderOptions struct {
|
|||
type Provider interface {
|
||||
FindGames(options ProviderOptions) ([]*Game, error)
|
||||
}
|
||||
|
||||
type ProviderFactory func(logger *logrus.Logger) Provider
|
||||
|
|
|
@ -2,12 +2,14 @@ package helpers
|
|||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var ErrCopyFileDestinationExists = errors.New("copy destination exists")
|
||||
|
||||
func CopyFile(src, dst string) (int64, error) {
|
||||
sourceFileStat, err := os.Stat(src)
|
||||
if err != nil {
|
||||
|
@ -26,8 +28,7 @@ func CopyFile(src, dst string) (int64, error) {
|
|||
|
||||
// Check if destination exists
|
||||
if _, err := os.Stat(dst); !os.IsNotExist(err) {
|
||||
log.Printf("- %s already exists, skipping...", dst)
|
||||
return 0, nil
|
||||
return 0, ErrCopyFileDestinationExists
|
||||
}
|
||||
|
||||
destination, err := os.Create(dst)
|
||||
|
|
|
@ -3,7 +3,6 @@ package processor
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -12,12 +11,15 @@ import (
|
|||
"github.com/fmartingr/games-screenshot-manager/internal/models"
|
||||
"github.com/fmartingr/games-screenshot-manager/pkg/helpers"
|
||||
"github.com/gosimple/slug"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Processor struct {
|
||||
games chan *models.Game
|
||||
logger *logrus.Entry
|
||||
options models.Options
|
||||
wg *sync.WaitGroup
|
||||
|
||||
games chan *models.Game
|
||||
wg *sync.WaitGroup
|
||||
}
|
||||
|
||||
func (p *Processor) Start(ctx context.Context) {
|
||||
|
@ -32,7 +34,7 @@ func (p *Processor) Process(game *models.Game) {
|
|||
}
|
||||
|
||||
func (p *Processor) process(ctx context.Context) {
|
||||
log.Println("Started worker process")
|
||||
p.logger.Debug("Worker started")
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
|
@ -40,7 +42,7 @@ func (p *Processor) process(ctx context.Context) {
|
|||
|
||||
case game := <-p.games:
|
||||
if err := p.processGame(game); err != nil {
|
||||
log.Printf("[err] %s", err)
|
||||
p.logger.Errorf("Error processing game %s from %s: %s", game.Name, game.Provider, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +56,10 @@ func (p *Processor) Wait() {
|
|||
func (p *Processor) processGame(game *models.Game) (err error) {
|
||||
defer p.wg.Done()
|
||||
|
||||
log.Printf("Processing game: %s", game.Name)
|
||||
p.logger.WithFields(logrus.Fields{
|
||||
"provider": game.Provider,
|
||||
"name": game.Name,
|
||||
}).Debugf("Processing game")
|
||||
|
||||
// Do not continue if there's no screenshots
|
||||
if len(game.Screenshots) == 0 {
|
||||
|
@ -65,7 +70,7 @@ func (p *Processor) processGame(game *models.Game) (err error) {
|
|||
if len(game.Name) > 0 {
|
||||
destinationPath = filepath.Join(destinationPath, game.Name)
|
||||
} else {
|
||||
log.Printf("[IMPORTANT] Game ID %s has no name!", game.ID)
|
||||
p.logger.Warnf("found game with ID: %s from %s without a name", game.ID, game.Provider)
|
||||
destinationPath = filepath.Join(destinationPath, game.ID)
|
||||
}
|
||||
|
||||
|
@ -73,7 +78,7 @@ func (p *Processor) processGame(game *models.Game) (err error) {
|
|||
if _, err := os.Stat(destinationPath); os.IsNotExist(err) && !p.options.DryRun {
|
||||
mkdirErr := os.MkdirAll(destinationPath, 0711)
|
||||
if mkdirErr != nil {
|
||||
log.Printf("[ERROR] Couldn't create directory with name %s, falling back to %s", game.Name, slug.Make(game.Name))
|
||||
p.logger.Errorf("Couldn't create directory with name %s, falling back to %s", game.Name, slug.Make(game.Name))
|
||||
destinationPath = filepath.Join(helpers.ExpandUser(p.options.OutputPath), game.Platform, slug.Make(game.Name))
|
||||
os.MkdirAll(destinationPath, 0711)
|
||||
}
|
||||
|
@ -83,12 +88,13 @@ func (p *Processor) processGame(game *models.Game) (err error) {
|
|||
destinationCoverPath := filepath.Join(destinationPath, ".cover")
|
||||
coverPath, err := helpers.DownloadURLIntoTempFile(game.CoverURL)
|
||||
if err != nil {
|
||||
log.Printf("[error] Error donwloading cover: %s", err)
|
||||
p.logger.Errorf("Error donwloading cover for game %s from %s: %s", game.Name, game.Provider, err)
|
||||
} else {
|
||||
if _, err := os.Stat(destinationCoverPath); os.IsNotExist(err) {
|
||||
helpers.CopyFile(coverPath, destinationCoverPath)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(destinationCoverPath); os.IsNotExist(err) {
|
||||
helpers.CopyFile(coverPath, destinationCoverPath)
|
||||
}
|
||||
}
|
||||
|
||||
for _, screenshot := range game.Screenshots {
|
||||
|
@ -97,26 +103,29 @@ func (p *Processor) processGame(game *models.Game) (err error) {
|
|||
if _, err := os.Stat(destinationPath); !os.IsNotExist(err) {
|
||||
sourceMd5, err := helpers.Md5File(screenshot.Path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
p.logger.Errorf("Can't get hash of source file for game %s from %s: %s", game.Name, game.Provider, err)
|
||||
return err
|
||||
}
|
||||
destinationMd5, err := helpers.Md5File(destinationPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
p.logger.Errorf("Can't get hash of destination file for game %s from %s: %s", game.Name, game.Provider, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !bytes.Equal(sourceMd5, destinationMd5) {
|
||||
// Images are not equal, we should copy it anyway, but how?
|
||||
log.Println("Found different screenshot with equal timestamp for game ", game.Name, screenshot.Path)
|
||||
p.logger.Warnf("Found different screenshot with equal timestamp for game %s from %s on %s", game.Name, game.Provider, screenshot.Path)
|
||||
}
|
||||
|
||||
} else {
|
||||
if p.options.DryRun {
|
||||
log.Println(filepath.Base(screenshot.Path), " -> ", strings.Replace(destinationPath, helpers.ExpandUser(p.options.OutputPath), "", 1))
|
||||
p.logger.Infof("cp %s %s", filepath.Base(screenshot.Path), strings.Replace(destinationPath, helpers.ExpandUser(p.options.OutputPath), "", 1))
|
||||
} else {
|
||||
if _, err := helpers.CopyFile(screenshot.Path, destinationPath); err != nil {
|
||||
log.Printf("[error] error during operation: %s", err)
|
||||
p.logger.WithFields(logrus.Fields{
|
||||
"src": screenshot.Path,
|
||||
"dest": destinationPath,
|
||||
}).Errorf("Error during copy operation: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -125,8 +134,9 @@ func (p *Processor) processGame(game *models.Game) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
func NewProcessor(options models.Options) *Processor {
|
||||
func NewProcessor(logger *logrus.Logger, options models.Options) *Processor {
|
||||
return &Processor{
|
||||
logger: logger.WithField("from", "processor"),
|
||||
games: make(chan *models.Game, options.ProcessBufferSize),
|
||||
options: options,
|
||||
wg: &sync.WaitGroup{},
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package minecraft
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
|
@ -10,12 +10,12 @@ import (
|
|||
"github.com/fmartingr/games-screenshot-manager/pkg/helpers"
|
||||
)
|
||||
|
||||
func getScreenshotsFromPath(game *models.Game, path string) {
|
||||
func getScreenshotsFromPath(game *models.Game, path string) error {
|
||||
path = helpers.ExpandUser(path)
|
||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return fmt.Errorf("error reading from %s: %s", path, err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
|
@ -24,4 +24,5 @@ func getScreenshotsFromPath(game *models.Game, path string) {
|
|||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -7,11 +7,14 @@ import (
|
|||
|
||||
"github.com/fmartingr/games-screenshot-manager/internal/models"
|
||||
"github.com/fmartingr/games-screenshot-manager/pkg/helpers"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const Name = "minecraft"
|
||||
|
||||
type MinecraftProvider struct{}
|
||||
type MinecraftProvider struct {
|
||||
logger *logrus.Entry
|
||||
}
|
||||
|
||||
func (p *MinecraftProvider) FindGames(options models.ProviderOptions) ([]*models.Game, error) {
|
||||
var result []*models.Game
|
||||
|
@ -19,24 +22,34 @@ func (p *MinecraftProvider) FindGames(options models.ProviderOptions) ([]*models
|
|||
minecraftStandalone := models.Game{Name: "Minecraft", Platform: "PC", Notes: "Standalone"}
|
||||
|
||||
if runtime.GOOS == "linux" {
|
||||
getScreenshotsFromPath(&minecraftStandalone, "~/.minecraft/screenshots")
|
||||
if err := getScreenshotsFromPath(&minecraftStandalone, "~/.minecraft/screenshots"); err != nil {
|
||||
p.logger.Error(err)
|
||||
}
|
||||
|
||||
// Flatpak minecraft
|
||||
minecraftFlatpak := models.Game{Name: "Minecraft", Platform: "PC", Notes: "Flatpak"}
|
||||
for _, path := range [2]string{"~/.var/app/com.mojang.Minecraft/.minecraft/screenshots", "~/.var/app/com.mojang.Minecraft/data/minecraft/screenshots"} {
|
||||
getScreenshotsFromPath(&minecraftFlatpak, path)
|
||||
if err := getScreenshotsFromPath(&minecraftFlatpak, path); err != nil {
|
||||
p.logger.Error(err)
|
||||
}
|
||||
}
|
||||
result = append(result, &minecraftFlatpak)
|
||||
} else if runtime.GOOS == "windows" {
|
||||
getScreenshotsFromPath(&minecraftStandalone, filepath.Join(os.Getenv("APPDATA"), ".minecraft/screenshots"))
|
||||
if err := getScreenshotsFromPath(&minecraftStandalone, filepath.Join(os.Getenv("APPDATA"), ".minecraft/screenshots")); err != nil {
|
||||
p.logger.Error(err)
|
||||
}
|
||||
} else if runtime.GOOS == "darwin" {
|
||||
getScreenshotsFromPath(&minecraftStandalone, filepath.Join(helpers.ExpandUser("~/Library/Application Support/minecraft/screenshots")))
|
||||
if err := getScreenshotsFromPath(&minecraftStandalone, filepath.Join(helpers.ExpandUser("~/Library/Application Support/minecraft/screenshots"))); err != nil {
|
||||
p.logger.Error(err)
|
||||
}
|
||||
}
|
||||
result = append(result, &minecraftStandalone)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func NewMinecraftProvider() *MinecraftProvider {
|
||||
return &MinecraftProvider{}
|
||||
func NewMinecraftProvider(logger *logrus.Logger) models.Provider {
|
||||
return &MinecraftProvider{
|
||||
logger: logger.WithField("from", "provider."+Name),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ package nintendo_switch
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/fmartingr/games-screenshot-manager/internal/models"
|
||||
|
@ -26,10 +26,10 @@ func findGameByEncryptedID(gameList []SwitchGame, encryptedGameID string) Switch
|
|||
return gameFound
|
||||
}
|
||||
|
||||
func getSwitchGameList() []SwitchGame {
|
||||
func getSwitchGameList() (result []SwitchGame, err error) {
|
||||
response, err := helpers.DoRequest("GET", gameListURL)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
return nil, fmt.Errorf("error getting switch game list: %s", err)
|
||||
}
|
||||
|
||||
if response.Body != nil {
|
||||
|
@ -38,18 +38,15 @@ func getSwitchGameList() []SwitchGame {
|
|||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
return nil, fmt.Errorf("error getting switch game list: %s", err)
|
||||
}
|
||||
|
||||
switchGameList := []SwitchGame{}
|
||||
jsonErr := json.Unmarshal(body, &switchGameList)
|
||||
jsonErr := json.Unmarshal(body, &result)
|
||||
if jsonErr != nil {
|
||||
log.Fatal(jsonErr)
|
||||
return nil, fmt.Errorf("error getting switch game list: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("Updated Nintendo Switch game list. Found %d games.", len(switchGameList))
|
||||
|
||||
return switchGameList
|
||||
return
|
||||
}
|
||||
|
||||
func addScreenshotToGame(userGames []*models.Game, switchGame SwitchGame, screenshot models.Screenshot) []*models.Game {
|
||||
|
|
|
@ -1,26 +1,33 @@
|
|||
package nintendo_switch
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fmartingr/games-screenshot-manager/internal/models"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const Name = "nintendo-switch"
|
||||
const platformName = "Nintendo Switch"
|
||||
const gameListURL = "https://fmartingr.github.io/switch-games-json/switch_id_names.json"
|
||||
|
||||
type NintendoSwitchProvider struct{}
|
||||
type NintendoSwitchProvider struct {
|
||||
logger *logrus.Entry
|
||||
}
|
||||
|
||||
func (p *NintendoSwitchProvider) FindGames(options models.ProviderOptions) ([]*models.Game, error) {
|
||||
switchGames := getSwitchGameList()
|
||||
switchGames, err := getSwitchGameList()
|
||||
if err != nil {
|
||||
p.logger.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var userGames []*models.Game
|
||||
|
||||
err := filepath.Walk(options.InputPath,
|
||||
err = filepath.Walk(options.InputPath,
|
||||
func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -36,7 +43,7 @@ func (p *NintendoSwitchProvider) FindGames(options models.ProviderOptions) ([]*m
|
|||
destinationName, err := time.Parse(layout, filenameParsed[0][0:14])
|
||||
|
||||
if err != nil {
|
||||
log.Panic("Could not parse filename: ", err)
|
||||
p.logger.Errorf("Could not parse filename '%s': %s", filename, err)
|
||||
}
|
||||
|
||||
screenshot := models.Screenshot{Path: path, DestinationName: destinationName.Format(models.DatetimeFormat) + extension}
|
||||
|
@ -45,11 +52,13 @@ func (p *NintendoSwitchProvider) FindGames(options models.ProviderOptions) ([]*m
|
|||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
return nil, err
|
||||
}
|
||||
return userGames, nil
|
||||
}
|
||||
|
||||
func NewNintendoSwitchProvider() *NintendoSwitchProvider {
|
||||
return &NintendoSwitchProvider{}
|
||||
func NewNintendoSwitchProvider(logger *logrus.Logger) models.Provider {
|
||||
return &NintendoSwitchProvider{
|
||||
logger: logger.WithField("from", "provider."+Name),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
package playstation4
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/fmartingr/games-screenshot-manager/internal/models"
|
||||
"github.com/rwcarlsen/goexif/exif"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const Name = "playstation-4"
|
||||
const platformName = "PlayStation 4"
|
||||
|
||||
type Playstation4Provider struct{}
|
||||
type Playstation4Provider struct {
|
||||
logger *logrus.Entry
|
||||
}
|
||||
|
||||
func (p *Playstation4Provider) FindGames(options models.ProviderOptions) ([]*models.Game, error) {
|
||||
var userGames []*models.Game
|
||||
|
@ -34,12 +36,12 @@ func (p *Playstation4Provider) FindGames(options models.ProviderOptions) ([]*mod
|
|||
if extension == ".jpg" {
|
||||
fileDescriptor, errFileDescriptor := os.Open(filePath)
|
||||
if errFileDescriptor != nil {
|
||||
log.Printf("[warning] Couldn't open file %s: %s", fileName, errFileDescriptor)
|
||||
p.logger.Warnf("Couldn't open file %s: %s", fileName, errFileDescriptor)
|
||||
return nil
|
||||
}
|
||||
exifData, errExifData := exif.Decode(fileDescriptor)
|
||||
if errExifData != nil {
|
||||
log.Printf("[Error] Decoding EXIF data from %s: %s", filePath, errExifData)
|
||||
p.logger.Errorf("Decoding EXIF data from %s: %s", filePath, errExifData)
|
||||
return nil
|
||||
}
|
||||
defer fileDescriptor.Close()
|
||||
|
@ -54,11 +56,11 @@ func (p *Playstation4Provider) FindGames(options models.ProviderOptions) ([]*mod
|
|||
if err == nil {
|
||||
destinationName = videoDatetime.Format(models.DatetimeFormat)
|
||||
} else {
|
||||
log.Printf("[Warning] File does not follow datetime convention: %s. (%s) skipping...", fileName, err)
|
||||
p.logger.Warnf("File %s does not follow datetime convention, skipping.", fileName, err)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
log.Printf("[Warning] File does not follow datetime convention: %s, skipping...", fileName)
|
||||
p.logger.Warnf("File %s does not follow datetime convention, skipping.", fileName)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -70,11 +72,13 @@ func (p *Playstation4Provider) FindGames(options models.ProviderOptions) ([]*mod
|
|||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
return nil, err
|
||||
}
|
||||
return userGames, nil
|
||||
}
|
||||
|
||||
func NewPlaystation4Provider() *Playstation4Provider {
|
||||
return &Playstation4Provider{}
|
||||
func NewPlaystation4Provider(logger *logrus.Logger) models.Provider {
|
||||
return &Playstation4Provider{
|
||||
logger: logger.WithField("from", "provider."+Name),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ package retroarch
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
|
@ -13,6 +13,7 @@ import (
|
|||
|
||||
"github.com/fmartingr/games-screenshot-manager/internal/models"
|
||||
"github.com/fmartingr/games-screenshot-manager/pkg/helpers"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type retroArchPlaylistItem struct {
|
||||
|
@ -53,13 +54,13 @@ func cleanGameName(gameName string) string {
|
|||
return splits[0]
|
||||
}
|
||||
|
||||
func readPlaylists(playlistsPath string) map[string]retroArchPlaylist {
|
||||
func readPlaylists(logger *logrus.Entry, playlistsPath string) (map[string]retroArchPlaylist, error) {
|
||||
var result = make(map[string]retroArchPlaylist)
|
||||
playlistsPath = helpers.ExpandUser(playlistsPath)
|
||||
if _, err := os.Stat(playlistsPath); !os.IsNotExist(err) {
|
||||
files, err := ioutil.ReadDir(playlistsPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return result, fmt.Errorf("error reading playlist directory: %s", err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
|
@ -67,18 +68,18 @@ func readPlaylists(playlistsPath string) map[string]retroArchPlaylist {
|
|||
var item retroArchPlaylist
|
||||
source, errOpen := os.Open(filepath.Join(playlistsPath, file.Name()))
|
||||
if errOpen != nil {
|
||||
log.Printf("[ERROR] Error reading playlist %s: %s", file.Name(), errOpen)
|
||||
logger.Errorf("Error reading playlist %s: %s", file.Name(), errOpen)
|
||||
continue
|
||||
}
|
||||
fileContents, errReadContent := ioutil.ReadAll(source)
|
||||
if errReadContent != nil {
|
||||
log.Printf("[ERROR] Reading contents of %s: %s", file.Name(), err)
|
||||
logger.Errorf("Error reading contents of %s: %s", file.Name(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
errUnmarshal := json.Unmarshal(fileContents, &item)
|
||||
if errUnmarshal != nil {
|
||||
log.Printf("[ERROR] Formatting %s: %s", file.Name(), errUnmarshal)
|
||||
logger.Errorf("Error formatting %s: %s", file.Name(), errUnmarshal)
|
||||
continue
|
||||
}
|
||||
result[strings.Replace(file.Name(), ".lpl", "", 1)] = item
|
||||
|
@ -86,16 +87,16 @@ func readPlaylists(playlistsPath string) map[string]retroArchPlaylist {
|
|||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func findScreenshotsForGame(item retroArchPlaylistItem) []models.Screenshot {
|
||||
func findScreenshotsForGame(logger *logrus.Entry, item retroArchPlaylistItem) ([]models.Screenshot, error) {
|
||||
var result []models.Screenshot
|
||||
filePath := filepath.Dir(item.Path)
|
||||
fileName := strings.Replace(filepath.Base(item.Path), filepath.Ext(item.Path), "", 1)
|
||||
files, err := ioutil.ReadDir(filePath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
|
@ -119,7 +120,7 @@ func findScreenshotsForGame(item retroArchPlaylistItem) []models.Screenshot {
|
|||
if err == nil {
|
||||
screenshotDestinationName = screenshotDate.Format(models.DatetimeFormat) + extension
|
||||
} else {
|
||||
log.Printf("[error] Formatting screenshot %s: %s", file.Name(), err)
|
||||
logger.Errorf("Error formatting screenshot %s: %s", file.Name(), err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
@ -127,5 +128,5 @@ func findScreenshotsForGame(item retroArchPlaylistItem) []models.Screenshot {
|
|||
result = append(result, models.Screenshot{Path: filepath.Join(filePath, file.Name()), DestinationName: screenshotDestinationName})
|
||||
}
|
||||
}
|
||||
return result
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ package retroarch
|
|||
|
||||
import (
|
||||
"github.com/fmartingr/games-screenshot-manager/internal/models"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const Name = "retroarch"
|
||||
|
@ -22,20 +23,30 @@ const libretroCoverURLBase = "http://thumbnails.libretro.com/"
|
|||
const datetimeLayout = "060102-150405"
|
||||
|
||||
type RetroArchProvider struct {
|
||||
logger *logrus.Entry
|
||||
}
|
||||
|
||||
func (p *RetroArchProvider) FindGames(options models.ProviderOptions) ([]*models.Game, error) {
|
||||
var userGames []*models.Game
|
||||
|
||||
playlists := readPlaylists(options.InputPath)
|
||||
playlists, err := readPlaylists(p.logger, options.InputPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for playlistName := range playlists {
|
||||
for _, item := range playlists[playlistName].Items {
|
||||
screenshots, err := findScreenshotsForGame(p.logger, item)
|
||||
if err != nil {
|
||||
p.logger.Errorf("Error retrieving game screenshots: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
userGames = append(userGames, &models.Game{
|
||||
Platform: cleanPlatformName(playlistName),
|
||||
Name: cleanGameName(item.Label),
|
||||
Provider: Name,
|
||||
Screenshots: findScreenshotsForGame(item),
|
||||
Screenshots: screenshots,
|
||||
CoverURL: formatLibretroBoxartURL(playlistName, item.Label),
|
||||
})
|
||||
}
|
||||
|
@ -44,6 +55,8 @@ func (p *RetroArchProvider) FindGames(options models.ProviderOptions) ([]*models
|
|||
return userGames, nil
|
||||
}
|
||||
|
||||
func NewRetroArchProvider() *RetroArchProvider {
|
||||
return &RetroArchProvider{}
|
||||
func NewRetroArchProvider(logger *logrus.Logger) models.Provider {
|
||||
return &RetroArchProvider{
|
||||
logger: logger.WithField("from", "provider."+Name),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ package steam
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
@ -12,9 +12,10 @@ import (
|
|||
|
||||
"github.com/fmartingr/games-screenshot-manager/internal/models"
|
||||
"github.com/fmartingr/games-screenshot-manager/pkg/helpers"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func getBasePathForOS() string {
|
||||
func getBasePathForOS() (string, error) {
|
||||
var path string
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
|
@ -24,16 +25,17 @@ func getBasePathForOS() string {
|
|||
case "windows":
|
||||
path = "C:\\Program Files (x86)\\Steam"
|
||||
default:
|
||||
log.Panic("Unsupported OS: ", runtime.GOOS)
|
||||
return "", fmt.Errorf("unsupported os: %s", runtime.GOOS)
|
||||
}
|
||||
return path
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func getSteamAppList(c chan SteamAppList) {
|
||||
log.Println("Updating steam game list...")
|
||||
func getSteamAppList(logger *logrus.Entry, c chan SteamAppList) {
|
||||
defer close(c)
|
||||
|
||||
response, err := helpers.DoRequest("GET", gameListURL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
logger.Errorf("Error making request for Steam APP List: %s", err)
|
||||
}
|
||||
|
||||
if response.Body != nil {
|
||||
|
@ -42,49 +44,45 @@ func getSteamAppList(c chan SteamAppList) {
|
|||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
logger.Errorf("Error reading steam response: %s", err)
|
||||
}
|
||||
|
||||
steamListResponse := SteamAppListResponse{}
|
||||
jsonErr := json.Unmarshal(body, &steamListResponse)
|
||||
if jsonErr != nil {
|
||||
log.Fatal(jsonErr)
|
||||
logger.Errorf("Error unmarshalling steam's response: %s", jsonErr)
|
||||
}
|
||||
|
||||
log.Printf("Updated Steam game list. Found %d apps.", len(steamListResponse.AppList.Apps))
|
||||
|
||||
c <- steamListResponse.AppList
|
||||
}
|
||||
|
||||
func guessUsers() []string {
|
||||
func guessUsers(basePath string) ([]string, error) {
|
||||
var users []string
|
||||
var path string = filepath.Join(getBasePathForOS(), "userdata")
|
||||
var path string = filepath.Join(basePath, "userdata")
|
||||
|
||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if _, err := strconv.ParseInt(file.Name(), 10, 64); err == nil {
|
||||
log.Printf("Found local install Steam user: %s", file.Name())
|
||||
users = append(users, file.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
return users
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func getGamesFromUser(user string) []string {
|
||||
log.Println("Getting Steam games for user: " + user)
|
||||
func getGamesFromUser(basePath, user string) ([]string, error) {
|
||||
var userGames []string
|
||||
var path string = filepath.Join(getBasePathForOS(), "userdata", user, "760", "remote")
|
||||
var path string = filepath.Join(basePath, "userdata", user, "760", "remote")
|
||||
|
||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
|
@ -93,14 +91,14 @@ func getGamesFromUser(user string) []string {
|
|||
}
|
||||
}
|
||||
|
||||
return userGames
|
||||
return userGames, nil
|
||||
}
|
||||
|
||||
func getScreenshotsForGame(user string, game *models.Game) {
|
||||
path := filepath.Join(getBasePathForOS(), "userdata", user, "/760/remote/", game.ID, "screenshots")
|
||||
func getScreenshotsForGame(basePath, user string, game *models.Game) error {
|
||||
path := filepath.Join(basePath, "userdata", user, "/760/remote/", game.ID, "screenshots")
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return fmt.Errorf("error reading game screenshot path: %s", err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
|
@ -108,4 +106,6 @@ func getScreenshotsForGame(user string, game *models.Game) {
|
|||
game.Screenshots = append(game.Screenshots, models.NewScreenshotWithoutDestination(path+"/"+file.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -3,16 +3,18 @@ package steam
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/fmartingr/games-screenshot-manager/internal/models"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const Name = "steam"
|
||||
const gameListURL = "https://api.steampowered.com/ISteamApps/GetAppList/v2/"
|
||||
const baseGameHeaderURL = "https://cdn.cloudflare.steamstatic.com/steam/apps/%d/header.jpg"
|
||||
|
||||
var errGameIDNotFound = errors.New("game ID not found")
|
||||
|
||||
type SteamApp struct {
|
||||
AppID uint64 `json:"appid"`
|
||||
Name string `json:"name"`
|
||||
|
@ -22,51 +24,77 @@ type SteamAppList struct {
|
|||
Apps []SteamApp `json:"apps"`
|
||||
}
|
||||
|
||||
func (appList SteamAppList) FindID(id string) (SteamApp, error) {
|
||||
GameIDNotFound := errors.New("game ID not found")
|
||||
func (appList SteamAppList) FindID(id string) (result SteamApp, err error) {
|
||||
for _, game := range appList.Apps {
|
||||
uintGameID, err := strconv.ParseUint(id, 10, 64)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
return result, fmt.Errorf("error parsing game ID: %s", err)
|
||||
}
|
||||
if game.AppID == uintGameID {
|
||||
return game, nil
|
||||
}
|
||||
}
|
||||
return SteamApp{}, GameIDNotFound
|
||||
return result, errGameIDNotFound
|
||||
}
|
||||
|
||||
type SteamAppListResponse struct {
|
||||
AppList SteamAppList `json:"applist"`
|
||||
}
|
||||
|
||||
type SteamProvider struct{}
|
||||
type SteamProvider struct {
|
||||
logger *logrus.Entry
|
||||
}
|
||||
|
||||
func (p *SteamProvider) FindGames(options models.ProviderOptions) ([]*models.Game, error) {
|
||||
basePath, err := getBasePathForOS()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting steam's base path: %s", err)
|
||||
}
|
||||
|
||||
var localGames []*models.Game
|
||||
c := make(chan SteamAppList)
|
||||
go getSteamAppList(c)
|
||||
users := guessUsers()
|
||||
go getSteamAppList(p.logger, c)
|
||||
|
||||
users, err := guessUsers(basePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting users: %s", err)
|
||||
}
|
||||
|
||||
p.logger.Debugf("Found %d users", len(users))
|
||||
|
||||
steamApps := <-c
|
||||
|
||||
if len(steamApps.Apps) == 0 {
|
||||
return nil, fmt.Errorf("coulnd't get steam app list")
|
||||
}
|
||||
|
||||
for _, userID := range users {
|
||||
userGames := getGamesFromUser(userID)
|
||||
userGames, err := getGamesFromUser(basePath, userID)
|
||||
if err != nil {
|
||||
p.logger.Errorf("error retrieving user's %s games: %s", userID, err)
|
||||
continue
|
||||
}
|
||||
for _, userGameID := range userGames {
|
||||
steamGame, err := steamApps.FindID(userGameID)
|
||||
if err != nil {
|
||||
log.Print("[ERROR] Steam game ID not found: ", userGameID)
|
||||
p.logger.Errorf("Steam game ID not found: %s", userGameID)
|
||||
}
|
||||
p.logger.WithField("userID", userID).Debugf("Found game: %s", steamGame.Name)
|
||||
userGame := models.NewGame(userGameID, steamGame.Name, "PC", Name)
|
||||
|
||||
userGame.CoverURL = fmt.Sprintf(baseGameHeaderURL, steamGame.AppID)
|
||||
|
||||
log.Printf("Found Steam game for user %s: %s (%s)", userID, userGame.Name, userGame.ID)
|
||||
getScreenshotsForGame(userID, &userGame)
|
||||
if err := getScreenshotsForGame(basePath, userID, &userGame); err != nil {
|
||||
p.logger.Errorf("error getting screenshots: %s", err)
|
||||
}
|
||||
localGames = append(localGames, &userGame)
|
||||
}
|
||||
}
|
||||
return localGames, nil
|
||||
}
|
||||
|
||||
func NewSteamProvider() *SteamProvider {
|
||||
return &SteamProvider{}
|
||||
func NewSteamProvider(logger *logrus.Logger) models.Provider {
|
||||
return &SteamProvider{
|
||||
logger: logger.WithField("from", "provider."+Name),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,21 +4,24 @@ import (
|
|||
"errors"
|
||||
|
||||
"github.com/fmartingr/games-screenshot-manager/internal/models"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var ErrProviderAlreadyRegistered = errors.New("provider already registered")
|
||||
var ErrProviderNotRegistered = errors.New("provider not registered")
|
||||
|
||||
type ProviderRegistry struct {
|
||||
logger *logrus.Entry
|
||||
providers map[string]*models.Provider
|
||||
}
|
||||
|
||||
func (r *ProviderRegistry) Register(name string, provider models.Provider) error {
|
||||
func (r *ProviderRegistry) Register(name string, providerFactory models.ProviderFactory) error {
|
||||
_, exists := r.providers[name]
|
||||
if exists {
|
||||
return ErrProviderAlreadyRegistered
|
||||
}
|
||||
|
||||
provider := providerFactory(r.logger.Logger)
|
||||
r.providers[name] = &provider
|
||||
|
||||
return nil
|
||||
|
@ -32,8 +35,9 @@ func (r *ProviderRegistry) Get(providerName string) (models.Provider, error) {
|
|||
return *provider, nil
|
||||
}
|
||||
|
||||
func NewProviderRegistry() *ProviderRegistry {
|
||||
func NewProviderRegistry(logger *logrus.Logger) *ProviderRegistry {
|
||||
return &ProviderRegistry{
|
||||
logger: logger.WithField("from", "registry"),
|
||||
providers: make(map[string]*models.Provider),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue