feat(logging); improve messages and errors (#336)

* feat(logger): add module context

* feat(logger): change errors package

* feat(logger): update tests
This commit is contained in:
Ludvig Lundgren 2022-07-05 13:31:44 +02:00 committed by GitHub
parent 95471a4cf7
commit 0e88117702
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 1172 additions and 957 deletions

View file

@ -91,7 +91,7 @@ func main() {
schedulingService = scheduler.NewService(log) schedulingService = scheduler.NewService(log)
apiService = indexer.NewAPIService(log) apiService = indexer.NewAPIService(log)
userService = user.NewService(userRepo) userService = user.NewService(userRepo)
authService = auth.NewService(userService) authService = auth.NewService(log, userService)
downloadClientService = download_client.NewService(log, downloadClientRepo) downloadClientService = download_client.NewService(log, downloadClientRepo)
actionService = action.NewService(log, actionRepo, downloadClientService, bus) actionService = action.NewService(log, actionRepo, downloadClientService, bus)
indexerService = indexer.NewService(log, cfg.Config, indexerRepo, apiService, schedulingService) indexerService = indexer.NewService(log, cfg.Config, indexerRepo, apiService, schedulingService)

View file

@ -8,12 +8,13 @@ import (
"log" "log"
"os" "os"
"golang.org/x/crypto/ssh/terminal"
_ "modernc.org/sqlite"
"github.com/autobrr/autobrr/internal/database" "github.com/autobrr/autobrr/internal/database"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/pkg/argon2id" "github.com/autobrr/autobrr/pkg/argon2id"
"github.com/autobrr/autobrr/pkg/errors"
"golang.org/x/crypto/ssh/terminal"
_ "modernc.org/sqlite"
) )
const usage = `usage: autobrrctl --config path <action> const usage = `usage: autobrrctl --config path <action>
@ -131,7 +132,7 @@ func readPassword() ([]byte, error) {
password = scanner.Bytes() password = scanner.Bytes()
if len(password) == 0 { if len(password) == 0 {
return nil, fmt.Errorf("zero length password") return nil, errors.New("zero length password")
} }
} }

2
go.mod
View file

@ -64,7 +64,7 @@ require (
github.com/subosito/gotenv v1.3.0 // indirect github.com/subosito/gotenv v1.3.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098 // indirect golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.10 // indirect golang.org/x/tools v0.1.10 // indirect

4
go.sum
View file

@ -615,8 +615,8 @@ golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098 h1:PgOr27OhUx2IRqGJ2RxAWI4dJQ7bi9cSrB82uzFzfUA= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e h1:CsOuNlbOuf0mzxJIefr6Q4uAUetRUwZE4qt7VfzP+xo=
golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

View file

@ -5,6 +5,7 @@ import (
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -63,7 +64,7 @@ func Test_service_parseExecArgs(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
s := &service{ s := &service{
log: logger.Mock(), log: logger.Mock().With().Logger(),
repo: nil, repo: nil,
clientSvc: nil, clientSvc: nil,
bus: nil, bus: nil,
@ -102,7 +103,7 @@ func Test_service_execCmd(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
s := &service{ s := &service{
log: logger.Mock(), log: logger.Mock().With().Logger(),
repo: nil, repo: nil,
clientSvc: nil, clientSvc: nil,
bus: nil, bus: nil,

View file

@ -30,6 +30,7 @@ func (s *service) lidarr(release domain.Release, action domain.Action) ([]string
cfg := lidarr.Config{ cfg := lidarr.Config{
Hostname: client.Host, Hostname: client.Host,
APIKey: client.Settings.APIKey, APIKey: client.Settings.APIKey,
Log: s.subLogger,
} }
// only set basic auth if enabled // only set basic auth if enabled

View file

@ -56,10 +56,10 @@ func (s *service) qbittorrent(qbt *qbittorrent.Client, action domain.Action, rel
options["tags"] = tagsArgs options["tags"] = tagsArgs
} }
if action.LimitUploadSpeed > 0 { if action.LimitUploadSpeed > 0 {
options["upLimit"] = strconv.FormatInt(action.LimitUploadSpeed * 1000, 10) options["upLimit"] = strconv.FormatInt(action.LimitUploadSpeed*1000, 10)
} }
if action.LimitDownloadSpeed > 0 { if action.LimitDownloadSpeed > 0 {
options["dlLimit"] = strconv.FormatInt(action.LimitDownloadSpeed * 1000, 10) options["dlLimit"] = strconv.FormatInt(action.LimitDownloadSpeed*1000, 10)
} }
if action.LimitRatio > 0 { if action.LimitRatio > 0 {
options["ratioLimit"] = strconv.FormatFloat(action.LimitRatio, 'r', 2, 64) options["ratioLimit"] = strconv.FormatFloat(action.LimitRatio, 'r', 2, 64)
@ -109,6 +109,7 @@ func (s *service) qbittorrentCheckRulesCanDownload(action domain.Action) (bool,
Password: client.Password, Password: client.Password,
TLS: client.TLS, TLS: client.TLS,
TLSSkipVerify: client.TLSSkipVerify, TLSSkipVerify: client.TLSSkipVerify,
Log: s.subLogger,
} }
// only set basic auth if enabled // only set basic auth if enabled

View file

@ -29,6 +29,7 @@ func (s *service) radarr(release domain.Release, action domain.Action) ([]string
cfg := radarr.Config{ cfg := radarr.Config{
Hostname: client.Host, Hostname: client.Host,
APIKey: client.Settings.APIKey, APIKey: client.Settings.APIKey,
Log: s.subLogger,
} }
// only set basic auth if enabled // only set basic auth if enabled

View file

@ -2,12 +2,15 @@ package action
import ( import (
"context" "context"
"log"
"github.com/asaskevich/EventBus"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/download_client" "github.com/autobrr/autobrr/internal/download_client"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/asaskevich/EventBus"
"github.com/dcarbone/zadapters/zstdlog"
"github.com/rs/zerolog"
) )
type Service interface { type Service interface {
@ -22,19 +25,24 @@ type Service interface {
} }
type service struct { type service struct {
log logger.Logger log zerolog.Logger
subLogger *log.Logger
repo domain.ActionRepo repo domain.ActionRepo
clientSvc download_client.Service clientSvc download_client.Service
bus EventBus.Bus bus EventBus.Bus
} }
func NewService(log logger.Logger, repo domain.ActionRepo, clientSvc download_client.Service, bus EventBus.Bus) Service { func NewService(log logger.Logger, repo domain.ActionRepo, clientSvc download_client.Service, bus EventBus.Bus) Service {
return &service{ s := &service{
log: log, log: log.With().Str("module", "action").Logger(),
repo: repo, repo: repo,
clientSvc: clientSvc, clientSvc: clientSvc,
bus: bus, bus: bus,
} }
s.subLogger = zstdlog.NewStdLoggerWithLevel(s.log.With().Logger(), zerolog.TraceLevel)
return s
} }
func (s *service) Store(ctx context.Context, action domain.Action) (*domain.Action, error) { func (s *service) Store(ctx context.Context, action domain.Action) (*domain.Action, error) {

View file

@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/autobrr/autobrr/pkg/sonarr" "github.com/autobrr/autobrr/pkg/sonarr"
) )
@ -17,18 +18,19 @@ func (s *service) sonarr(release domain.Release, action domain.Action) ([]string
client, err := s.clientSvc.FindByID(context.TODO(), action.ClientID) client, err := s.clientSvc.FindByID(context.TODO(), action.ClientID)
if err != nil { if err != nil {
s.log.Error().Err(err).Msgf("sonarr: error finding client: %v", action.ClientID) s.log.Error().Err(err).Msgf("sonarr: error finding client: %v", action.ClientID)
return nil, err return nil, errors.Wrap(err, "sonarr could not find client: %v", action.ClientID)
} }
// return early if no client found // return early if no client found
if client == nil { if client == nil {
return nil, err return nil, errors.New("no client found")
} }
// initial config // initial config
cfg := sonarr.Config{ cfg := sonarr.Config{
Hostname: client.Host, Hostname: client.Host,
APIKey: client.Settings.APIKey, APIKey: client.Settings.APIKey,
Log: s.subLogger,
} }
// only set basic auth if enabled // only set basic auth if enabled

View file

@ -29,6 +29,7 @@ func (s *service) whisparr(release domain.Release, action domain.Action) ([]stri
cfg := whisparr.Config{ cfg := whisparr.Config{
Hostname: client.Host, Hostname: client.Host,
APIKey: client.Settings.APIKey, APIKey: client.Settings.APIKey,
Log: s.subLogger,
} }
// only set basic auth if enabled // only set basic auth if enabled

View file

@ -2,17 +2,16 @@ package announce
import ( import (
"bytes" "bytes"
"errors"
"fmt"
"net/url" "net/url"
"regexp" "regexp"
"strings" "strings"
"text/template" "text/template"
"github.com/rs/zerolog"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/release" "github.com/autobrr/autobrr/internal/release"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/rs/zerolog"
) )
type Processor interface { type Processor interface {
@ -30,7 +29,7 @@ type announceProcessor struct {
func NewAnnounceProcessor(log zerolog.Logger, releaseSvc release.Service, indexer *domain.IndexerDefinition) Processor { func NewAnnounceProcessor(log zerolog.Logger, releaseSvc release.Service, indexer *domain.IndexerDefinition) Processor {
ap := &announceProcessor{ ap := &announceProcessor{
log: log, log: log.With().Str("module", "announce_processor").Logger(),
releaseSvc: releaseSvc, releaseSvc: releaseSvc,
indexer: indexer, indexer: indexer,
} }
@ -129,7 +128,7 @@ func (a *announceProcessor) AddLineToQueue(channel string, line string) error {
channel = strings.ToLower(channel) channel = strings.ToLower(channel)
queue, ok := a.queues[channel] queue, ok := a.queues[channel]
if !ok { if !ok {
return fmt.Errorf("no queue for channel (%v) found", channel) return errors.New("no queue for channel (%v) found", channel)
} }
queue <- line queue <- line
@ -226,6 +225,8 @@ func (a *announceProcessor) processTorrentUrl(match string, vars map[string]stri
return "", err return "", err
} }
a.log.Trace().Msg("torrenturl processed")
return b.String(), nil return b.String(), nil
} }
@ -234,7 +235,7 @@ func removeElement(s []string, i int) ([]string, error) {
// perform bounds checking first to prevent a panic! // perform bounds checking first to prevent a panic!
if i >= len(s) || i < 0 { if i >= len(s) || i < 0 {
return nil, fmt.Errorf("Index is out of range. Index is %d with slice length %d", i, len(s)) return nil, errors.New("Index is out of range. Index is %d with slice length %d", i, len(s))
} }
// This creates a new slice by creating 2 slices from the original: // This creates a new slice by creating 2 slices from the original:

View file

@ -4,6 +4,9 @@ import (
"context" "context"
"errors" "errors"
"github.com/autobrr/autobrr/internal/logger"
"github.com/rs/zerolog"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/user" "github.com/autobrr/autobrr/internal/user"
"github.com/autobrr/autobrr/pkg/argon2id" "github.com/autobrr/autobrr/pkg/argon2id"
@ -16,11 +19,13 @@ type Service interface {
} }
type service struct { type service struct {
log zerolog.Logger
userSvc user.Service userSvc user.Service
} }
func NewService(userSvc user.Service) Service { func NewService(log logger.Logger, userSvc user.Service) Service {
return &service{ return &service{
log: log.With().Str("module", "auth").Logger(),
userSvc: userSvc, userSvc: userSvc,
} }
} }
@ -37,6 +42,7 @@ func (s *service) Login(ctx context.Context, username, password string) (*domain
// find user // find user
u, err := s.userSvc.FindByUsername(ctx, username) u, err := s.userSvc.FindByUsername(ctx, username)
if err != nil { if err != nil {
s.log.Error().Err(err).Msgf("could not find user by username: %v", username)
return nil, err return nil, err
} }
@ -51,6 +57,7 @@ func (s *service) Login(ctx context.Context, username, password string) (*domain
} }
if !match { if !match {
s.log.Error().Msg("bad credentials")
return nil, errors.New("bad credentials") return nil, errors.New("bad credentials")
} }
@ -81,6 +88,7 @@ func (s *service) CreateUser(ctx context.Context, username, password string) err
Password: hashed, Password: hashed,
} }
if err := s.userSvc.CreateUser(context.Background(), newUser); err != nil { if err := s.userSvc.CreateUser(context.Background(), newUser); err != nil {
s.log.Error().Err(err).Msgf("could not create user: %v", username)
return errors.New("failed to create new user") return errors.New("failed to create new user")
} }

View file

@ -7,19 +7,21 @@ import (
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/pkg/errors"
sq "github.com/Masterminds/squirrel" sq "github.com/Masterminds/squirrel"
"github.com/rs/zerolog"
) )
type ActionRepo struct { type ActionRepo struct {
log logger.Logger log zerolog.Logger
db *DB db *DB
clientRepo domain.DownloadClientRepo clientRepo domain.DownloadClientRepo
} }
func NewActionRepo(log logger.Logger, db *DB, clientRepo domain.DownloadClientRepo) domain.ActionRepo { func NewActionRepo(log logger.Logger, db *DB, clientRepo domain.DownloadClientRepo) domain.ActionRepo {
return &ActionRepo{ return &ActionRepo{
log: log, log: log.With().Str("repo", "action").Logger(),
db: db, db: db,
clientRepo: clientRepo, clientRepo: clientRepo,
} }
@ -87,14 +89,12 @@ func (r *ActionRepo) findByFilterID(ctx context.Context, tx *Tx, filterID int) (
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("action.findByFilterID: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
rows, err := tx.QueryContext(ctx, query, args...) rows, err := tx.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("action.findByFilterID: query error") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
defer rows.Close() defer rows.Close()
@ -112,8 +112,7 @@ func (r *ActionRepo) findByFilterID(ctx context.Context, tx *Tx, filterID int) (
var paused, ignoreRules sql.NullBool var paused, ignoreRules sql.NullBool
if err := rows.Scan(&a.ID, &a.Name, &a.Type, &a.Enabled, &execCmd, &execArgs, &watchFolder, &category, &tags, &label, &savePath, &paused, &ignoreRules, &limitDl, &limitUl, &limitRatio, &limitSeedTime, &a.ReAnnounceSkip, &a.ReAnnounceDelete, &a.ReAnnounceInterval, &a.ReAnnounceMaxAttempts, &webhookHost, &webhookType, &webhookMethod, &webhookData, &clientID); err != nil { if err := rows.Scan(&a.ID, &a.Name, &a.Type, &a.Enabled, &execCmd, &execArgs, &watchFolder, &category, &tags, &label, &savePath, &paused, &ignoreRules, &limitDl, &limitUl, &limitRatio, &limitSeedTime, &a.ReAnnounceSkip, &a.ReAnnounceDelete, &a.ReAnnounceInterval, &a.ReAnnounceMaxAttempts, &webhookHost, &webhookType, &webhookMethod, &webhookData, &clientID); err != nil {
r.log.Error().Stack().Err(err).Msg("action.findByFilterID: error scanning row") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
a.ExecCmd = execCmd.String a.ExecCmd = execCmd.String
@ -141,8 +140,7 @@ func (r *ActionRepo) findByFilterID(ctx context.Context, tx *Tx, filterID int) (
actions = append(actions, &a) actions = append(actions, &a)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
r.log.Error().Stack().Err(err).Msg("action.findByFilterID: row error") return nil, errors.Wrap(err, "row error")
return nil, err
} }
return actions, nil return actions, nil
@ -168,28 +166,24 @@ func (r *ActionRepo) attachDownloadClient(ctx context.Context, tx *Tx, clientID
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("action.attachDownloadClient: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
row := tx.QueryRowContext(ctx, query, args...) row := tx.QueryRowContext(ctx, query, args...)
if err := row.Err(); err != nil { if err := row.Err(); err != nil {
r.log.Error().Stack().Err(err).Msg("action.attachDownloadClient: error query row") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
var client domain.DownloadClient var client domain.DownloadClient
var settingsJsonStr string var settingsJsonStr string
if err := row.Scan(&client.ID, &client.Name, &client.Type, &client.Enabled, &client.Host, &client.Port, &client.TLS, &client.TLSSkipVerify, &client.Username, &client.Password, &settingsJsonStr); err != nil { if err := row.Scan(&client.ID, &client.Name, &client.Type, &client.Enabled, &client.Host, &client.Port, &client.TLS, &client.TLSSkipVerify, &client.Username, &client.Password, &settingsJsonStr); err != nil {
r.log.Error().Stack().Err(err).Msg("action.attachDownloadClient: error scanning row") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
if settingsJsonStr != "" { if settingsJsonStr != "" {
if err := json.Unmarshal([]byte(settingsJsonStr), &client.Settings); err != nil { if err := json.Unmarshal([]byte(settingsJsonStr), &client.Settings); err != nil {
r.log.Error().Stack().Err(err).Msgf("action.attachDownloadClient: could not marshal download client settings %v", settingsJsonStr) return nil, errors.Wrap(err, "could not unmarshal download client settings: %v", settingsJsonStr)
return nil, err
} }
} }
@ -230,14 +224,12 @@ func (r *ActionRepo) List(ctx context.Context) ([]domain.Action, error) {
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("action.list: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
rows, err := r.db.handler.QueryContext(ctx, query, args...) rows, err := r.db.handler.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("action.list: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
defer rows.Close() defer rows.Close()
@ -253,8 +245,7 @@ func (r *ActionRepo) List(ctx context.Context) ([]domain.Action, error) {
var paused, ignoreRules sql.NullBool var paused, ignoreRules sql.NullBool
if err := rows.Scan(&a.ID, &a.Name, &a.Type, &a.Enabled, &execCmd, &execArgs, &watchFolder, &category, &tags, &label, &savePath, &paused, &ignoreRules, &limitDl, &limitUl, &limitRatio, &limitSeedTime, &a.ReAnnounceSkip, &a.ReAnnounceDelete, &a.ReAnnounceInterval, &a.ReAnnounceMaxAttempts, &webhookHost, &webhookType, &webhookMethod, &webhookData, &clientID); err != nil { if err := rows.Scan(&a.ID, &a.Name, &a.Type, &a.Enabled, &execCmd, &execArgs, &watchFolder, &category, &tags, &label, &savePath, &paused, &ignoreRules, &limitDl, &limitUl, &limitRatio, &limitSeedTime, &a.ReAnnounceSkip, &a.ReAnnounceDelete, &a.ReAnnounceInterval, &a.ReAnnounceMaxAttempts, &webhookHost, &webhookType, &webhookMethod, &webhookData, &clientID); err != nil {
r.log.Error().Stack().Err(err).Msg("action.list: error scanning row") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
a.Category = category.String a.Category = category.String
@ -279,8 +270,7 @@ func (r *ActionRepo) List(ctx context.Context) ([]domain.Action, error) {
actions = append(actions, a) actions = append(actions, a)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
r.log.Error().Stack().Err(err).Msg("action.list: row error") return nil, errors.Wrap(err, "rows error")
return nil, err
} }
return actions, nil return actions, nil
@ -293,14 +283,12 @@ func (r *ActionRepo) Delete(actionID int) error {
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("action.delete: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = r.db.handler.Exec(query, args...) _, err = r.db.handler.Exec(query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("action.delete: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
r.log.Debug().Msgf("action.delete: %v", actionID) r.log.Debug().Msgf("action.delete: %v", actionID)
@ -315,14 +303,12 @@ func (r *ActionRepo) DeleteByFilterID(ctx context.Context, filterID int) error {
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("action.deleteByFilterID: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = r.db.handler.ExecContext(ctx, query, args...) _, err = r.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("action.deleteByFilterID: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
r.log.Debug().Msgf("action.deleteByFilterID: %v", filterID) r.log.Debug().Msgf("action.deleteByFilterID: %v", filterID)
@ -415,8 +401,7 @@ func (r *ActionRepo) Store(ctx context.Context, action domain.Action) (*domain.A
err := queryBuilder.QueryRowContext(ctx).Scan(&retID) err := queryBuilder.QueryRowContext(ctx).Scan(&retID)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("action.store: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
r.log.Debug().Msgf("action.store: added new %v", retID) r.log.Debug().Msgf("action.store: added new %v", retID)
@ -480,14 +465,12 @@ func (r *ActionRepo) Update(ctx context.Context, action domain.Action) (*domain.
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("action.update: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
_, err = r.db.handler.ExecContext(ctx, query, args...) _, err = r.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("action.update: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
r.log.Debug().Msgf("action.update: %v", action.ID) r.log.Debug().Msgf("action.update: %v", action.ID)
@ -498,7 +481,7 @@ func (r *ActionRepo) Update(ctx context.Context, action domain.Action) (*domain.
func (r *ActionRepo) StoreFilterActions(ctx context.Context, actions []*domain.Action, filterID int64) ([]*domain.Action, error) { func (r *ActionRepo) StoreFilterActions(ctx context.Context, actions []*domain.Action, filterID int64) ([]*domain.Action, error) {
tx, err := r.db.handler.BeginTx(ctx, nil) tx, err := r.db.handler.BeginTx(ctx, nil)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "error begin transaction")
} }
defer tx.Rollback() defer tx.Rollback()
@ -509,13 +492,11 @@ func (r *ActionRepo) StoreFilterActions(ctx context.Context, actions []*domain.A
deleteQuery, deleteArgs, err := deleteQueryBuilder.ToSql() deleteQuery, deleteArgs, err := deleteQueryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("action.StoreFilterActions: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
_, err = tx.ExecContext(ctx, deleteQuery, deleteArgs...) _, err = tx.ExecContext(ctx, deleteQuery, deleteArgs...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("action.StoreFilterActions: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
for _, action := range actions { for _, action := range actions {
@ -602,8 +583,7 @@ func (r *ActionRepo) StoreFilterActions(ctx context.Context, actions []*domain.A
err = queryBuilder.QueryRowContext(ctx).Scan(&retID) err = queryBuilder.QueryRowContext(ctx).Scan(&retID)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("action.StoreFilterActions: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
action.ID = retID action.ID = retID
@ -613,8 +593,7 @@ func (r *ActionRepo) StoreFilterActions(ctx context.Context, actions []*domain.A
err = tx.Commit() err = tx.Commit()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("action.StoreFilterActions: error updating actions") return nil, errors.Wrap(err, "error updating filter actions")
return nil, err
} }
@ -631,14 +610,12 @@ func (r *ActionRepo) ToggleEnabled(actionID int) error {
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("action.toggleEnabled: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = r.db.handler.Exec(query, args...) _, err = r.db.handler.Exec(query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("action.toggleEnabled: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
r.log.Debug().Msgf("action.toggleEnabled: %v", actionID) r.log.Debug().Msgf("action.toggleEnabled: %v", actionID)

View file

@ -8,12 +8,14 @@ import (
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/pkg/errors"
sq "github.com/Masterminds/squirrel" sq "github.com/Masterminds/squirrel"
"github.com/rs/zerolog"
) )
type DB struct { type DB struct {
log logger.Logger log zerolog.Logger
handler *sql.DB handler *sql.DB
lock sync.RWMutex lock sync.RWMutex
ctx context.Context ctx context.Context
@ -29,7 +31,7 @@ func NewDB(cfg *domain.Config, log logger.Logger) (*DB, error) {
db := &DB{ db := &DB{
// set default placeholder for squirrel to support both sqlite and postgres // set default placeholder for squirrel to support both sqlite and postgres
squirrel: sq.StatementBuilder.PlaceholderFormat(sq.Dollar), squirrel: sq.StatementBuilder.PlaceholderFormat(sq.Dollar),
log: log, log: log.With().Str("module", "database").Logger(),
} }
db.ctx, db.cancel = context.WithCancel(context.Background()) db.ctx, db.cancel = context.WithCancel(context.Background())
@ -39,12 +41,12 @@ func NewDB(cfg *domain.Config, log logger.Logger) (*DB, error) {
db.DSN = dataSourceName(cfg.ConfigPath, "autobrr.db") db.DSN = dataSourceName(cfg.ConfigPath, "autobrr.db")
case "postgres": case "postgres":
if cfg.PostgresHost == "" || cfg.PostgresPort == 0 || cfg.PostgresDatabase == "" { if cfg.PostgresHost == "" || cfg.PostgresPort == 0 || cfg.PostgresDatabase == "" {
return nil, fmt.Errorf("postgres: bad variables") return nil, errors.New("postgres: bad variables")
} }
db.DSN = fmt.Sprintf("postgres://%v:%v@%v:%d/%v?sslmode=disable", cfg.PostgresUser, cfg.PostgresPass, cfg.PostgresHost, cfg.PostgresPort, cfg.PostgresDatabase) db.DSN = fmt.Sprintf("postgres://%v:%v@%v:%d/%v?sslmode=disable", cfg.PostgresUser, cfg.PostgresPass, cfg.PostgresHost, cfg.PostgresPort, cfg.PostgresDatabase)
db.Driver = "postgres" db.Driver = "postgres"
default: default:
return nil, fmt.Errorf("unsupported databse: %v", cfg.DatabaseType) return nil, errors.New("unsupported databse: %v", cfg.DatabaseType)
} }
return db, nil return db, nil
@ -52,7 +54,7 @@ func NewDB(cfg *domain.Config, log logger.Logger) (*DB, error) {
func (db *DB) Open() error { func (db *DB) Open() error {
if db.DSN == "" { if db.DSN == "" {
return fmt.Errorf("DSN required") return errors.New("DSN required")
} }
var err error var err error

View file

@ -7,10 +7,13 @@ import (
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/rs/zerolog"
) )
type DownloadClientRepo struct { type DownloadClientRepo struct {
log logger.Logger log zerolog.Logger
db *DB db *DB
cache *clientCache cache *clientCache
} }
@ -50,7 +53,7 @@ func (c *clientCache) Pop(id int) {
func NewDownloadClientRepo(log logger.Logger, db *DB) domain.DownloadClientRepo { func NewDownloadClientRepo(log logger.Logger, db *DB) domain.DownloadClientRepo {
return &DownloadClientRepo{ return &DownloadClientRepo{
log: log, log: log.With().Str("repo", "action").Logger(),
db: db, db: db,
cache: NewClientCache(), cache: NewClientCache(),
} }
@ -77,14 +80,12 @@ func (r *DownloadClientRepo) List(ctx context.Context) ([]domain.DownloadClient,
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("download_client.list: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
rows, err := r.db.handler.QueryContext(ctx, query, args...) rows, err := r.db.handler.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("download_client.list: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
defer rows.Close() defer rows.Close()
@ -94,22 +95,19 @@ func (r *DownloadClientRepo) List(ctx context.Context) ([]domain.DownloadClient,
var settingsJsonStr string var settingsJsonStr string
if err := rows.Scan(&f.ID, &f.Name, &f.Type, &f.Enabled, &f.Host, &f.Port, &f.TLS, &f.TLSSkipVerify, &f.Username, &f.Password, &settingsJsonStr); err != nil { if err := rows.Scan(&f.ID, &f.Name, &f.Type, &f.Enabled, &f.Host, &f.Port, &f.TLS, &f.TLSSkipVerify, &f.Username, &f.Password, &settingsJsonStr); err != nil {
r.log.Error().Stack().Err(err).Msg("download_client.list: error scanning row") return clients, errors.Wrap(err, "error scanning row")
return clients, err
} }
if settingsJsonStr != "" { if settingsJsonStr != "" {
if err := json.Unmarshal([]byte(settingsJsonStr), &f.Settings); err != nil { if err := json.Unmarshal([]byte(settingsJsonStr), &f.Settings); err != nil {
r.log.Error().Stack().Err(err).Msgf("could not marshal download client settings %v", settingsJsonStr) return clients, errors.Wrap(err, "could not unmarshal download client settings: %v", settingsJsonStr)
return clients, err
} }
} }
clients = append(clients, f) clients = append(clients, f)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
r.log.Error().Stack().Err(err).Msg("download_client.list: row error") return clients, errors.Wrap(err, "rows error")
return clients, err
} }
return clients, nil return clients, nil
@ -141,28 +139,24 @@ func (r *DownloadClientRepo) FindByID(ctx context.Context, id int32) (*domain.Do
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("download_client.findByID: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
row := r.db.handler.QueryRowContext(ctx, query, args...) row := r.db.handler.QueryRowContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("download_client.findByID: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
var client domain.DownloadClient var client domain.DownloadClient
var settingsJsonStr string var settingsJsonStr string
if err := row.Scan(&client.ID, &client.Name, &client.Type, &client.Enabled, &client.Host, &client.Port, &client.TLS, &client.TLSSkipVerify, &client.Username, &client.Password, &settingsJsonStr); err != nil { if err := row.Scan(&client.ID, &client.Name, &client.Type, &client.Enabled, &client.Host, &client.Port, &client.TLS, &client.TLSSkipVerify, &client.Username, &client.Password, &settingsJsonStr); err != nil {
r.log.Error().Stack().Err(err).Msg("download_client.findByID: error scanning row") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
if settingsJsonStr != "" { if settingsJsonStr != "" {
if err := json.Unmarshal([]byte(settingsJsonStr), &client.Settings); err != nil { if err := json.Unmarshal([]byte(settingsJsonStr), &client.Settings); err != nil {
r.log.Error().Stack().Err(err).Msgf("could not marshal download client settings %v", settingsJsonStr) return nil, errors.Wrap(err, "could not unmarshal download client settings: %v", settingsJsonStr)
return nil, err
} }
} }
@ -180,8 +174,7 @@ func (r *DownloadClientRepo) Store(ctx context.Context, client domain.DownloadCl
settingsJson, err := json.Marshal(&settings) settingsJson, err := json.Marshal(&settings)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msgf("could not marshal download client settings %v", settings) return nil, errors.Wrap(err, "error marshal download client settings %+v", settings)
return nil, err
} }
queryBuilder := r.db.squirrel. queryBuilder := r.db.squirrel.
@ -195,8 +188,7 @@ func (r *DownloadClientRepo) Store(ctx context.Context, client domain.DownloadCl
err = queryBuilder.QueryRowContext(ctx).Scan(&retID) err = queryBuilder.QueryRowContext(ctx).Scan(&retID)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("download_client.store: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
client.ID = retID client.ID = retID
@ -220,8 +212,7 @@ func (r *DownloadClientRepo) Update(ctx context.Context, client domain.DownloadC
settingsJson, err := json.Marshal(&settings) settingsJson, err := json.Marshal(&settings)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msgf("could not marshal download client settings %v", settings) return nil, errors.Wrap(err, "error marshal download client settings %+v", settings)
return nil, err
} }
queryBuilder := r.db.squirrel. queryBuilder := r.db.squirrel.
@ -240,14 +231,12 @@ func (r *DownloadClientRepo) Update(ctx context.Context, client domain.DownloadC
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("download_client.update: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
_, err = r.db.handler.ExecContext(ctx, query, args...) _, err = r.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("download_client.update: error querying data") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
r.log.Debug().Msgf("download_client.update: %d", client.ID) r.log.Debug().Msgf("download_client.update: %d", client.ID)
@ -265,14 +254,12 @@ func (r *DownloadClientRepo) Delete(ctx context.Context, clientID int) error {
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("download_client.delete: error building query") return errors.Wrap(err, "error building query")
return err
} }
res, err := r.db.handler.ExecContext(ctx, query, args...) res, err := r.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("download_client.delete: error query data") return errors.Wrap(err, "error executing query")
return err
} }
// remove from cache // remove from cache
@ -280,7 +267,7 @@ func (r *DownloadClientRepo) Delete(ctx context.Context, clientID int) error {
rows, _ := res.RowsAffected() rows, _ := res.RowsAffected()
if rows == 0 { if rows == 0 {
return err return errors.New("no rows affected")
} }
r.log.Info().Msgf("delete download client: %d", clientID) r.log.Info().Msgf("delete download client: %d", clientID)

View file

@ -6,19 +6,21 @@ import (
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/pkg/errors"
sq "github.com/Masterminds/squirrel" sq "github.com/Masterminds/squirrel"
"github.com/rs/zerolog"
) )
func NewFeedRepo(log logger.Logger, db *DB) domain.FeedRepo { func NewFeedRepo(log logger.Logger, db *DB) domain.FeedRepo {
return &FeedRepo{ return &FeedRepo{
log: log, log: log.With().Str("repo", "feed").Logger(),
db: db, db: db,
} }
} }
type FeedRepo struct { type FeedRepo struct {
log logger.Logger log zerolog.Logger
db *DB db *DB
} }
@ -41,14 +43,12 @@ func (r *FeedRepo) FindByID(ctx context.Context, id int) (*domain.Feed, error) {
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("feed.FindById: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
row := r.db.handler.QueryRowContext(ctx, query, args...) row := r.db.handler.QueryRowContext(ctx, query, args...)
if err := row.Err(); err != nil { if err := row.Err(); err != nil {
r.log.Error().Stack().Err(err).Msg("feed.FindById: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
var f domain.Feed var f domain.Feed
@ -56,8 +56,7 @@ func (r *FeedRepo) FindByID(ctx context.Context, id int) (*domain.Feed, error) {
var apiKey sql.NullString var apiKey sql.NullString
if err := row.Scan(&f.ID, &f.Indexer, &f.Name, &f.Type, &f.Enabled, &f.URL, &f.Interval, &apiKey, &f.CreatedAt, &f.UpdatedAt); err != nil { if err := row.Scan(&f.ID, &f.Indexer, &f.Name, &f.Type, &f.Enabled, &f.URL, &f.Interval, &apiKey, &f.CreatedAt, &f.UpdatedAt); err != nil {
r.log.Error().Stack().Err(err).Msg("feed.FindById: error scanning row") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
@ -85,14 +84,12 @@ func (r *FeedRepo) FindByIndexerIdentifier(ctx context.Context, indexer string)
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("feed.FindByIndexerIdentifier: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
row := r.db.handler.QueryRowContext(ctx, query, args...) row := r.db.handler.QueryRowContext(ctx, query, args...)
if err := row.Err(); err != nil { if err := row.Err(); err != nil {
r.log.Error().Stack().Err(err).Msg("feed.FindByIndexerIdentifier: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
var f domain.Feed var f domain.Feed
@ -100,8 +97,7 @@ func (r *FeedRepo) FindByIndexerIdentifier(ctx context.Context, indexer string)
var apiKey sql.NullString var apiKey sql.NullString
if err := row.Scan(&f.ID, &f.Indexer, &f.Name, &f.Type, &f.Enabled, &f.URL, &f.Interval, &apiKey, &f.CreatedAt, &f.UpdatedAt); err != nil { if err := row.Scan(&f.ID, &f.Indexer, &f.Name, &f.Type, &f.Enabled, &f.URL, &f.Interval, &apiKey, &f.CreatedAt, &f.UpdatedAt); err != nil {
r.log.Error().Stack().Err(err).Msg("feed.FindByIndexerIdentifier: error scanning row") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
@ -129,14 +125,12 @@ func (r *FeedRepo) Find(ctx context.Context) ([]domain.Feed, error) {
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("feed.Find: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
rows, err := r.db.handler.QueryContext(ctx, query, args...) rows, err := r.db.handler.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("feed.Find: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
defer rows.Close() defer rows.Close()
@ -148,8 +142,7 @@ func (r *FeedRepo) Find(ctx context.Context) ([]domain.Feed, error) {
var apiKey sql.NullString var apiKey sql.NullString
if err := rows.Scan(&f.ID, &f.Indexer, &f.Name, &f.Type, &f.Enabled, &f.URL, &f.Interval, &apiKey, &f.CreatedAt, &f.UpdatedAt); err != nil { if err := rows.Scan(&f.ID, &f.Indexer, &f.Name, &f.Type, &f.Enabled, &f.URL, &f.Interval, &apiKey, &f.CreatedAt, &f.UpdatedAt); err != nil {
r.log.Error().Stack().Err(err).Msg("feed.Find: error scanning row") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
@ -189,8 +182,7 @@ func (r *FeedRepo) Store(ctx context.Context, feed *domain.Feed) error {
var retID int var retID int
if err := queryBuilder.QueryRowContext(ctx).Scan(&retID); err != nil { if err := queryBuilder.QueryRowContext(ctx).Scan(&retID); err != nil {
r.log.Error().Stack().Err(err).Msg("feed.Store: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
feed.ID = retID feed.ID = retID
@ -212,14 +204,12 @@ func (r *FeedRepo) Update(ctx context.Context, feed *domain.Feed) error {
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("feed.Update: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = r.db.handler.ExecContext(ctx, query, args...) _, err = r.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("feed.Update: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
return nil return nil
@ -236,13 +226,11 @@ func (r *FeedRepo) ToggleEnabled(ctx context.Context, id int, enabled bool) erro
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("feed.ToggleEnabled: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = r.db.handler.ExecContext(ctx, query, args...) _, err = r.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("feed.ToggleEnabled: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
return nil return nil
@ -255,14 +243,12 @@ func (r *FeedRepo) Delete(ctx context.Context, id int) error {
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("feed.delete: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = r.db.handler.ExecContext(ctx, query, args...) _, err = r.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("feed.delete: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
r.log.Info().Msgf("feed.delete: successfully deleted: %v", id) r.log.Info().Msgf("feed.delete: successfully deleted: %v", id)

View file

@ -6,16 +6,19 @@ import (
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/rs/zerolog"
) )
type FeedCacheRepo struct { type FeedCacheRepo struct {
log logger.Logger log zerolog.Logger
db *DB db *DB
} }
func NewFeedCacheRepo(log logger.Logger, db *DB) domain.FeedCacheRepo { func NewFeedCacheRepo(log logger.Logger, db *DB) domain.FeedCacheRepo {
return &FeedCacheRepo{ return &FeedCacheRepo{
log: log, log: log.With().Str("repo", "feed_cache").Logger(),
db: db, db: db,
} }
} }
@ -33,22 +36,19 @@ func (r *FeedCacheRepo) Get(bucket string, key string) ([]byte, error) {
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("feedCache.Get: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
row := r.db.handler.QueryRow(query, args...) row := r.db.handler.QueryRow(query, args...)
if err := row.Err(); err != nil { if err := row.Err(); err != nil {
r.log.Error().Stack().Err(err).Msg("feedCache.Get: query error") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
var value []byte var value []byte
var ttl time.Duration var ttl time.Duration
if err := row.Scan(&value, &ttl); err != nil && err != sql.ErrNoRows { if err := row.Scan(&value, &ttl); err != nil && err != sql.ErrNoRows {
r.log.Error().Stack().Err(err).Msg("feedCache.Get: error scanning row") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
return value, nil return value, nil
@ -65,14 +65,13 @@ func (r *FeedCacheRepo) Exists(bucket string, key string) (bool, error) {
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("feedCache.Exists: error building query") return false, errors.Wrap(err, "error building query")
return false, err
} }
var exists bool var exists bool
err = r.db.handler.QueryRow(query, args...).Scan(&exists) err = r.db.handler.QueryRow(query, args...).Scan(&exists)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
r.log.Error().Stack().Err(err).Msg("feedCache.Exists: query error") return false, errors.Wrap(err, "error query")
} }
return exists, nil return exists, nil
@ -86,13 +85,11 @@ func (r *FeedCacheRepo) Put(bucket string, key string, val []byte, ttl time.Time
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("feedCache.Put: error building query") return errors.Wrap(err, "error building query")
return err
} }
if _, err = r.db.handler.Exec(query, args...); err != nil { if _, err = r.db.handler.Exec(query, args...); err != nil {
r.log.Error().Stack().Err(err).Msg("feedCache.Put: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
return nil return nil

View file

@ -7,19 +7,21 @@ import (
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/pkg/errors"
sq "github.com/Masterminds/squirrel" sq "github.com/Masterminds/squirrel"
"github.com/lib/pq" "github.com/lib/pq"
"github.com/rs/zerolog"
) )
type FilterRepo struct { type FilterRepo struct {
log logger.Logger log zerolog.Logger
db *DB db *DB
} }
func NewFilterRepo(log logger.Logger, db *DB) domain.FilterRepo { func NewFilterRepo(log logger.Logger, db *DB) domain.FilterRepo {
return &FilterRepo{ return &FilterRepo{
log: log, log: log.With().Str("repo", "filter").Logger(),
db: db, db: db,
} }
} }
@ -40,14 +42,12 @@ func (r *FilterRepo) ListFilters(ctx context.Context) ([]domain.Filter, error) {
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filter.list: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
rows, err := r.db.handler.QueryContext(ctx, query, args...) rows, err := r.db.handler.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filter.list: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
defer rows.Close() defer rows.Close()
@ -59,8 +59,7 @@ func (r *FilterRepo) ListFilters(ctx context.Context) ([]domain.Filter, error) {
var matchReleases, exceptReleases sql.NullString var matchReleases, exceptReleases sql.NullString
if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &matchReleases, &exceptReleases, &f.CreatedAt, &f.UpdatedAt); err != nil { if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &matchReleases, &exceptReleases, &f.CreatedAt, &f.UpdatedAt); err != nil {
r.log.Error().Stack().Err(err).Msg("filter.list: error scanning row") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
f.MatchReleases = matchReleases.String f.MatchReleases = matchReleases.String
@ -69,8 +68,7 @@ func (r *FilterRepo) ListFilters(ctx context.Context) ([]domain.Filter, error) {
filters = append(filters, f) filters = append(filters, f)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
r.log.Error().Stack().Err(err).Msg("filter.list: row error") return nil, errors.Wrap(err, "row error")
return nil, err
} }
return filters, nil return filters, nil
@ -133,14 +131,12 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filter.findByID: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
row := r.db.handler.QueryRowContext(ctx, query, args...) row := r.db.handler.QueryRowContext(ctx, query, args...)
if err := row.Err(); err != nil { if err := row.Err(); err != nil {
r.log.Error().Stack().Err(err).Msg("filter.findByID: error query row") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
var f domain.Filter var f domain.Filter
@ -149,8 +145,7 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter
var delay, maxDownloads, logScore sql.NullInt32 var delay, maxDownloads, logScore sql.NullInt32
if err := row.Scan(&f.ID, &f.Enabled, &f.Name, &minSize, &maxSize, &delay, &f.Priority, &maxDownloads, &maxDownloadsUnit, &matchReleases, &exceptReleases, &useRegex, &matchReleaseGroups, &exceptReleaseGroups, &scene, &freeleech, &freeleechPercent, &shows, &seasons, &episodes, pq.Array(&f.Resolutions), pq.Array(&f.Codecs), pq.Array(&f.Sources), pq.Array(&f.Containers), pq.Array(&f.MatchHDR), pq.Array(&f.ExceptHDR), pq.Array(&f.MatchOther), pq.Array(&f.ExceptOther), &years, &artists, &albums, pq.Array(&f.MatchReleaseTypes), pq.Array(&f.Formats), pq.Array(&f.Quality), pq.Array(&f.Media), &logScore, &hasLog, &hasCue, &perfectFlac, &matchCategories, &exceptCategories, &matchUploaders, &exceptUploaders, &tags, &exceptTags, pq.Array(&f.Origins), &f.CreatedAt, &f.UpdatedAt); err != nil { if err := row.Scan(&f.ID, &f.Enabled, &f.Name, &minSize, &maxSize, &delay, &f.Priority, &maxDownloads, &maxDownloadsUnit, &matchReleases, &exceptReleases, &useRegex, &matchReleaseGroups, &exceptReleaseGroups, &scene, &freeleech, &freeleechPercent, &shows, &seasons, &episodes, pq.Array(&f.Resolutions), pq.Array(&f.Codecs), pq.Array(&f.Sources), pq.Array(&f.Containers), pq.Array(&f.MatchHDR), pq.Array(&f.ExceptHDR), pq.Array(&f.MatchOther), pq.Array(&f.ExceptOther), &years, &artists, &albums, pq.Array(&f.MatchReleaseTypes), pq.Array(&f.Formats), pq.Array(&f.Quality), pq.Array(&f.Media), &logScore, &hasLog, &hasCue, &perfectFlac, &matchCategories, &exceptCategories, &matchUploaders, &exceptUploaders, &tags, &exceptTags, pq.Array(&f.Origins), &f.CreatedAt, &f.UpdatedAt); err != nil {
r.log.Error().Stack().Err(err).Msgf("filter.findByID: %v : error scanning row", filterID) return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
f.MinSize = minSize.String f.MinSize = minSize.String
@ -191,7 +186,7 @@ func (r *FilterRepo) FindByIndexerIdentifier(indexer string) ([]domain.Filter, e
ctx := context.TODO() ctx := context.TODO()
tx, err := r.db.BeginTx(ctx, nil) tx, err := r.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "error begin transaction")
} }
defer tx.Rollback() defer tx.Rollback()
@ -273,14 +268,12 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, tx *Tx, indexe
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filter.findByIndexerIdentifier: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
rows, err := tx.QueryContext(ctx, query, args...) rows, err := tx.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filter.findByIndexerIdentifier: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
defer rows.Close() defer rows.Close()
@ -294,8 +287,7 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, tx *Tx, indexe
var delay, maxDownloads, logScore sql.NullInt32 var delay, maxDownloads, logScore sql.NullInt32
if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &minSize, &maxSize, &delay, &f.Priority, &maxDownloads, &maxDownloadsUnit, &matchReleases, &exceptReleases, &useRegex, &matchReleaseGroups, &exceptReleaseGroups, &scene, &freeleech, &freeleechPercent, &shows, &seasons, &episodes, pq.Array(&f.Resolutions), pq.Array(&f.Codecs), pq.Array(&f.Sources), pq.Array(&f.Containers), pq.Array(&f.MatchHDR), pq.Array(&f.ExceptHDR), pq.Array(&f.MatchOther), pq.Array(&f.ExceptOther), &years, &artists, &albums, pq.Array(&f.MatchReleaseTypes), pq.Array(&f.Formats), pq.Array(&f.Quality), pq.Array(&f.Media), &logScore, &hasLog, &hasCue, &perfectFlac, &matchCategories, &exceptCategories, &matchUploaders, &exceptUploaders, &tags, &exceptTags, pq.Array(&f.Origins), &f.CreatedAt, &f.UpdatedAt); err != nil { if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &minSize, &maxSize, &delay, &f.Priority, &maxDownloads, &maxDownloadsUnit, &matchReleases, &exceptReleases, &useRegex, &matchReleaseGroups, &exceptReleaseGroups, &scene, &freeleech, &freeleechPercent, &shows, &seasons, &episodes, pq.Array(&f.Resolutions), pq.Array(&f.Codecs), pq.Array(&f.Sources), pq.Array(&f.Containers), pq.Array(&f.MatchHDR), pq.Array(&f.ExceptHDR), pq.Array(&f.MatchOther), pq.Array(&f.ExceptOther), &years, &artists, &albums, pq.Array(&f.MatchReleaseTypes), pq.Array(&f.Formats), pq.Array(&f.Quality), pq.Array(&f.Media), &logScore, &hasLog, &hasCue, &perfectFlac, &matchCategories, &exceptCategories, &matchUploaders, &exceptUploaders, &tags, &exceptTags, pq.Array(&f.Origins), &f.CreatedAt, &f.UpdatedAt); err != nil {
r.log.Error().Stack().Err(err).Msg("filter.findByIndexerIdentifier: error scanning row") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
f.MinSize = minSize.String f.MinSize = minSize.String
@ -438,8 +430,7 @@ func (r *FilterRepo) Store(ctx context.Context, filter domain.Filter) (*domain.F
err := queryBuilder.QueryRowContext(ctx).Scan(&retID) err := queryBuilder.QueryRowContext(ctx).Scan(&retID)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filter.store: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
filter.ID = retID filter.ID = retID
@ -502,14 +493,12 @@ func (r *FilterRepo) Update(ctx context.Context, filter domain.Filter) (*domain.
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filter.update: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
_, err = r.db.handler.ExecContext(ctx, query, args...) _, err = r.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filter.update: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
return &filter, nil return &filter, nil
@ -526,13 +515,11 @@ func (r *FilterRepo) ToggleEnabled(ctx context.Context, filterID int, enabled bo
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filter.toggleEnabled: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = r.db.handler.ExecContext(ctx, query, args...) _, err = r.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filter.toggleEnabled: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
return nil return nil
@ -552,13 +539,11 @@ func (r *FilterRepo) StoreIndexerConnections(ctx context.Context, filterID int,
deleteQuery, deleteArgs, err := deleteQueryBuilder.ToSql() deleteQuery, deleteArgs, err := deleteQueryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filter.StoreIndexerConnections: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = tx.ExecContext(ctx, deleteQuery, deleteArgs...) _, err = tx.ExecContext(ctx, deleteQuery, deleteArgs...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msgf("filter.StoreIndexerConnections: error deleting indexers for filter: %v", filterID) return errors.Wrap(err, "error executing query")
return err
} }
for _, indexer := range indexers { for _, indexer := range indexers {
@ -568,13 +553,11 @@ func (r *FilterRepo) StoreIndexerConnections(ctx context.Context, filterID int,
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filter.StoreIndexerConnections: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = tx.ExecContext(ctx, query, args...) _, err = tx.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filter.StoreIndexerConnections: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
r.log.Debug().Msgf("filter.StoreIndexerConnections: store '%v' on filter: %v", indexer.Name, filterID) r.log.Debug().Msgf("filter.StoreIndexerConnections: store '%v' on filter: %v", indexer.Name, filterID)
@ -582,8 +565,7 @@ func (r *FilterRepo) StoreIndexerConnections(ctx context.Context, filterID int,
err = tx.Commit() err = tx.Commit()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msgf("filter.StoreIndexerConnections: error storing indexers for filter: %v", filterID) return errors.Wrap(err, "error store indexers for filter: %v", filterID)
return err
} }
return nil return nil
@ -596,14 +578,12 @@ func (r *FilterRepo) StoreIndexerConnection(ctx context.Context, filterID int, i
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filter.storeIndexerConnection: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = r.db.handler.ExecContext(ctx, query, args...) _, err = r.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filter.storeIndexerConnection: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
return nil return nil
@ -616,14 +596,12 @@ func (r *FilterRepo) DeleteIndexerConnections(ctx context.Context, filterID int)
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filter.deleteIndexerConnections: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = r.db.handler.ExecContext(ctx, query, args...) _, err = r.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filter.deleteIndexerConnections: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
return nil return nil
@ -636,14 +614,12 @@ func (r *FilterRepo) Delete(ctx context.Context, filterID int) error {
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filter.delete: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = r.db.handler.ExecContext(ctx, query, args...) _, err = r.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filter.delete: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
r.log.Info().Msgf("filter.delete: successfully deleted: %v", filterID) r.log.Info().Msgf("filter.delete: successfully deleted: %v", filterID)
@ -671,15 +647,13 @@ WHERE "release".filter_id = ?;`
row := tx.QueryRowContext(ctx, query, filterID) row := tx.QueryRowContext(ctx, query, filterID)
if err := row.Err(); err != nil { if err := row.Err(); err != nil {
r.log.Error().Stack().Err(err).Msg("filter.downloadsByFilterSqlite: error querying stats") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
var f domain.FilterDownloads var f domain.FilterDownloads
if err := row.Scan(&f.HourCount, &f.DayCount, &f.WeekCount, &f.MonthCount, &f.TotalCount); err != nil { if err := row.Scan(&f.HourCount, &f.DayCount, &f.WeekCount, &f.MonthCount, &f.TotalCount); err != nil {
r.log.Error().Stack().Err(err).Msg("filter.downloadsByFilterSqlite: error scanning stats data to struct") return nil, errors.Wrap(err, "error scanning stats data sqlite")
return nil, err
} }
return &f, nil return &f, nil
@ -697,15 +671,13 @@ WHERE "release".filter_id = $1;`
row := tx.QueryRowContext(ctx, query, filterID) row := tx.QueryRowContext(ctx, query, filterID)
if err := row.Err(); err != nil { if err := row.Err(); err != nil {
r.log.Error().Stack().Err(err).Msg("filter.downloadsByFilterPostgres: error querying stats") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
var f domain.FilterDownloads var f domain.FilterDownloads
if err := row.Scan(&f.HourCount, &f.DayCount, &f.WeekCount, &f.MonthCount, &f.TotalCount); err != nil { if err := row.Scan(&f.HourCount, &f.DayCount, &f.WeekCount, &f.MonthCount, &f.TotalCount); err != nil {
r.log.Error().Stack().Err(err).Msg("filter.downloadsByFilterPostgres: error scanning stats data to struct") return nil, errors.Wrap(err, "error scanning stats data postgres")
return nil, err
} }
return &f, nil return &f, nil

View file

@ -8,16 +8,19 @@ import (
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/rs/zerolog"
) )
type IndexerRepo struct { type IndexerRepo struct {
log logger.Logger log zerolog.Logger
db *DB db *DB
} }
func NewIndexerRepo(log logger.Logger, db *DB) domain.IndexerRepo { func NewIndexerRepo(log logger.Logger, db *DB) domain.IndexerRepo {
return &IndexerRepo{ return &IndexerRepo{
log: log, log: log.With().Str("repo", "indexer").Logger(),
db: db, db: db,
} }
} }
@ -25,8 +28,7 @@ func NewIndexerRepo(log logger.Logger, db *DB) domain.IndexerRepo {
func (r *IndexerRepo) Store(ctx context.Context, indexer domain.Indexer) (*domain.Indexer, error) { func (r *IndexerRepo) Store(ctx context.Context, indexer domain.Indexer) (*domain.Indexer, error) {
settings, err := json.Marshal(indexer.Settings) settings, err := json.Marshal(indexer.Settings)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("error marshaling json data") return nil, errors.Wrap(err, "error marshaling json data")
return nil, err
} }
queryBuilder := r.db.squirrel. queryBuilder := r.db.squirrel.
@ -39,8 +41,7 @@ func (r *IndexerRepo) Store(ctx context.Context, indexer domain.Indexer) (*domai
err = queryBuilder.QueryRowContext(ctx).Scan(&retID) err = queryBuilder.QueryRowContext(ctx).Scan(&retID)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("indexer.store: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
indexer.ID = retID indexer.ID = retID
@ -51,8 +52,7 @@ func (r *IndexerRepo) Store(ctx context.Context, indexer domain.Indexer) (*domai
func (r *IndexerRepo) Update(ctx context.Context, indexer domain.Indexer) (*domain.Indexer, error) { func (r *IndexerRepo) Update(ctx context.Context, indexer domain.Indexer) (*domain.Indexer, error) {
settings, err := json.Marshal(indexer.Settings) settings, err := json.Marshal(indexer.Settings)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("error marshaling json data") return nil, errors.Wrap(err, "error marshaling json data")
return nil, err
} }
queryBuilder := r.db.squirrel. queryBuilder := r.db.squirrel.
@ -65,14 +65,12 @@ func (r *IndexerRepo) Update(ctx context.Context, indexer domain.Indexer) (*doma
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("indexer.update: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
_, err = r.db.handler.ExecContext(ctx, query, args...) _, err = r.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("indexer.update: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
return &indexer, nil return &indexer, nil
@ -81,8 +79,7 @@ func (r *IndexerRepo) Update(ctx context.Context, indexer domain.Indexer) (*doma
func (r *IndexerRepo) List(ctx context.Context) ([]domain.Indexer, error) { func (r *IndexerRepo) List(ctx context.Context) ([]domain.Indexer, error) {
rows, err := r.db.handler.QueryContext(ctx, "SELECT id, enabled, name, identifier, implementation, settings FROM indexer ORDER BY name ASC") rows, err := r.db.handler.QueryContext(ctx, "SELECT id, enabled, name, identifier, implementation, settings FROM indexer ORDER BY name ASC")
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("indexer.list: error query indexer") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
defer rows.Close() defer rows.Close()
@ -96,16 +93,14 @@ func (r *IndexerRepo) List(ctx context.Context) ([]domain.Indexer, error) {
var settingsMap map[string]string var settingsMap map[string]string
if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &f.Identifier, &implementation, &settings); err != nil { if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &f.Identifier, &implementation, &settings); err != nil {
r.log.Error().Stack().Err(err).Msg("indexer.list: error scanning data to struct") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
f.Implementation = implementation.String f.Implementation = implementation.String
err = json.Unmarshal([]byte(settings), &settingsMap) err = json.Unmarshal([]byte(settings), &settingsMap)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("indexer.list: error unmarshal settings") return nil, errors.Wrap(err, "error unmarshal settings")
return nil, err
} }
f.Settings = settingsMap f.Settings = settingsMap
@ -113,7 +108,7 @@ func (r *IndexerRepo) List(ctx context.Context) ([]domain.Indexer, error) {
indexers = append(indexers, f) indexers = append(indexers, f)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
return nil, err return nil, errors.Wrap(err, "error rows")
} }
return indexers, nil return indexers, nil
@ -128,14 +123,12 @@ func (r *IndexerRepo) FindByFilterID(ctx context.Context, id int) ([]domain.Inde
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.check_existing_network: error fetching data") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
rows, err := r.db.handler.QueryContext(ctx, query, args...) rows, err := r.db.handler.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("indexer.find_by_filter_id: error query indexer") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
defer rows.Close() defer rows.Close()
@ -148,14 +141,12 @@ func (r *IndexerRepo) FindByFilterID(ctx context.Context, id int) ([]domain.Inde
var settingsMap map[string]string var settingsMap map[string]string
if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &f.Identifier, &settings); err != nil { if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &f.Identifier, &settings); err != nil {
r.log.Error().Stack().Err(err).Msg("indexer.find_by_filter_id: error scanning data to struct") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
err = json.Unmarshal([]byte(settings), &settingsMap) err = json.Unmarshal([]byte(settings), &settingsMap)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("indexer.find_by_filter_id: error unmarshal settings") return nil, errors.Wrap(err, "error unmarshal settings")
return nil, err
} }
f.Settings = settingsMap f.Settings = settingsMap
@ -163,7 +154,7 @@ func (r *IndexerRepo) FindByFilterID(ctx context.Context, id int) ([]domain.Inde
indexers = append(indexers, f) indexers = append(indexers, f)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
return nil, err return nil, errors.Wrap(err, "error rows")
} }
return indexers, nil return indexers, nil
@ -177,14 +168,12 @@ func (r *IndexerRepo) Delete(ctx context.Context, id int) error {
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("indexer.delete: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = r.db.handler.ExecContext(ctx, query, args...) _, err = r.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msgf("indexer.delete: error executing query: '%v'", query) return errors.Wrap(err, "error executing query")
return err
} }
r.log.Debug().Msgf("indexer.delete: id %v", id) r.log.Debug().Msgf("indexer.delete: id %v", id)

View file

@ -7,18 +7,19 @@ import (
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/pkg/errors" "github.com/rs/zerolog"
) )
type IrcRepo struct { type IrcRepo struct {
log logger.Logger log zerolog.Logger
db *DB db *DB
} }
func NewIrcRepo(log logger.Logger, db *DB) domain.IrcRepo { func NewIrcRepo(log logger.Logger, db *DB) domain.IrcRepo {
return &IrcRepo{ return &IrcRepo{
log: log, log: log.With().Str("repo", "irc").Logger(),
db: db, db: db,
} }
} }
@ -31,8 +32,7 @@ func (r *IrcRepo) GetNetworkByID(ctx context.Context, id int64) (*domain.IrcNetw
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.getNetworkByID: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
r.log.Trace().Str("database", "irc.check_existing_network").Msgf("query: '%v', args: '%v'", query, args) r.log.Trace().Str("database", "irc.check_existing_network").Msgf("query: '%v', args: '%v'", query, args)
@ -44,8 +44,7 @@ func (r *IrcRepo) GetNetworkByID(ctx context.Context, id int64) (*domain.IrcNetw
row := r.db.handler.QueryRowContext(ctx, query, args...) row := r.db.handler.QueryRowContext(ctx, query, args...)
if err := row.Scan(&n.ID, &n.Enabled, &n.Name, &n.Server, &n.Port, &tls, &pass, &inviteCmd, &nsAccount, &nsPassword); err != nil { if err := row.Scan(&n.ID, &n.Enabled, &n.Name, &n.Server, &n.Port, &tls, &pass, &inviteCmd, &nsAccount, &nsPassword); err != nil {
r.log.Error().Stack().Err(err).Msg("irc.getNetworkByID: error executing query") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
n.TLS = tls.Bool n.TLS = tls.Bool
@ -60,7 +59,7 @@ func (r *IrcRepo) GetNetworkByID(ctx context.Context, id int64) (*domain.IrcNetw
func (r *IrcRepo) DeleteNetwork(ctx context.Context, id int64) error { func (r *IrcRepo) DeleteNetwork(ctx context.Context, id int64) error {
tx, err := r.db.BeginTx(ctx, nil) tx, err := r.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return err return errors.Wrap(err, "error begin transaction")
} }
defer tx.Rollback() defer tx.Rollback()
@ -71,14 +70,12 @@ func (r *IrcRepo) DeleteNetwork(ctx context.Context, id int64) error {
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.deleteNetwork: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = tx.ExecContext(ctx, query, args...) _, err = tx.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.deleteNetwork: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
netQueryBuilder := r.db.squirrel. netQueryBuilder := r.db.squirrel.
@ -87,20 +84,17 @@ func (r *IrcRepo) DeleteNetwork(ctx context.Context, id int64) error {
netQuery, netArgs, err := netQueryBuilder.ToSql() netQuery, netArgs, err := netQueryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.deleteNetwork: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = tx.ExecContext(ctx, netQuery, netArgs...) _, err = tx.ExecContext(ctx, netQuery, netArgs...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.deleteNetwork: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
err = tx.Commit() err = tx.Commit()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msgf("irc.deleteNetwork: error deleting network %v", id) return errors.Wrap(err, "error commit deleting network")
return err
} }
@ -115,14 +109,12 @@ func (r *IrcRepo) FindActiveNetworks(ctx context.Context) ([]domain.IrcNetwork,
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.findActiveNetworks: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
rows, err := r.db.handler.QueryContext(ctx, query, args...) rows, err := r.db.handler.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.findActiveNetworks: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
defer rows.Close() defer rows.Close()
@ -136,8 +128,7 @@ func (r *IrcRepo) FindActiveNetworks(ctx context.Context) ([]domain.IrcNetwork,
var tls sql.NullBool var tls sql.NullBool
if err := rows.Scan(&net.ID, &net.Enabled, &net.Name, &net.Server, &net.Port, &tls, &pass, &inviteCmd, &nsAccount, &nsPassword); err != nil { if err := rows.Scan(&net.ID, &net.Enabled, &net.Name, &net.Server, &net.Port, &tls, &pass, &inviteCmd, &nsAccount, &nsPassword); err != nil {
r.log.Error().Stack().Err(err).Msg("irc.findActiveNetworks: error scanning row") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
net.TLS = tls.Bool net.TLS = tls.Bool
@ -150,8 +141,7 @@ func (r *IrcRepo) FindActiveNetworks(ctx context.Context) ([]domain.IrcNetwork,
networks = append(networks, net) networks = append(networks, net)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
r.log.Error().Stack().Err(err).Msg("irc.findActiveNetworks: row error") return nil, errors.Wrap(err, "error row")
return nil, err
} }
return networks, nil return networks, nil
@ -165,14 +155,12 @@ func (r *IrcRepo) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error)
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.listNetworks: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
rows, err := r.db.handler.QueryContext(ctx, query, args...) rows, err := r.db.handler.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.listNetworks: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
defer rows.Close() defer rows.Close()
@ -186,8 +174,7 @@ func (r *IrcRepo) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error)
var tls sql.NullBool var tls sql.NullBool
if err := rows.Scan(&net.ID, &net.Enabled, &net.Name, &net.Server, &net.Port, &tls, &pass, &inviteCmd, &nsAccount, &nsPassword); err != nil { if err := rows.Scan(&net.ID, &net.Enabled, &net.Name, &net.Server, &net.Port, &tls, &pass, &inviteCmd, &nsAccount, &nsPassword); err != nil {
r.log.Error().Stack().Err(err).Msg("irc.listNetworks: error scanning row") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
net.TLS = tls.Bool net.TLS = tls.Bool
@ -200,8 +187,7 @@ func (r *IrcRepo) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error)
networks = append(networks, net) networks = append(networks, net)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
r.log.Error().Stack().Err(err).Msg("irc.listNetworks: row error") return nil, errors.Wrap(err, "error row")
return nil, err
} }
return networks, nil return networks, nil
@ -215,14 +201,12 @@ func (r *IrcRepo) ListChannels(networkID int64) ([]domain.IrcChannel, error) {
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.listChannels: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
rows, err := r.db.handler.Query(query, args...) rows, err := r.db.handler.Query(query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.listChannels: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
defer rows.Close() defer rows.Close()
@ -232,8 +216,7 @@ func (r *IrcRepo) ListChannels(networkID int64) ([]domain.IrcChannel, error) {
var pass sql.NullString var pass sql.NullString
if err := rows.Scan(&ch.ID, &ch.Name, &ch.Enabled, &pass); err != nil { if err := rows.Scan(&ch.ID, &ch.Name, &ch.Enabled, &pass); err != nil {
r.log.Error().Stack().Err(err).Msg("irc.listChannels: error scanning row") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
ch.Password = pass.String ch.Password = pass.String
@ -241,8 +224,7 @@ func (r *IrcRepo) ListChannels(networkID int64) ([]domain.IrcChannel, error) {
channels = append(channels, ch) channels = append(channels, ch)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
r.log.Error().Stack().Err(err).Msg("irc.listChannels: error row") return nil, errors.Wrap(err, "error row")
return nil, err
} }
return channels, nil return channels, nil
@ -257,8 +239,7 @@ func (r *IrcRepo) CheckExistingNetwork(ctx context.Context, network *domain.IrcN
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.checkExistingNetwork: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
r.log.Trace().Str("database", "irc.checkExistingNetwork").Msgf("query: '%v', args: '%v'", query, args) r.log.Trace().Str("database", "irc.checkExistingNetwork").Msgf("query: '%v', args: '%v'", query, args)
@ -274,8 +255,7 @@ func (r *IrcRepo) CheckExistingNetwork(ctx context.Context, network *domain.IrcN
// no result is not an error in our case // no result is not an error in our case
return nil, nil return nil, nil
} else if err != nil { } else if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.checkExistingNetwork: error scanning data to struct") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
net.TLS = tls.Bool net.TLS = tls.Bool
@ -326,7 +306,6 @@ func (r *IrcRepo) StoreNetwork(network *domain.IrcNetwork) error {
err = queryBuilder.QueryRow().Scan(&retID) err = queryBuilder.QueryRow().Scan(&retID)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.storeNetwork: error executing query")
return errors.Wrap(err, "error executing query") return errors.Wrap(err, "error executing query")
} }
@ -361,15 +340,13 @@ func (r *IrcRepo) UpdateNetwork(ctx context.Context, network *domain.IrcNetwork)
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.updateNetwork: error building query") return errors.Wrap(err, "error building query")
return err
} }
// update record // update record
_, err = r.db.handler.ExecContext(ctx, query, args...) _, err = r.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.updateNetwork: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
return err return err
@ -391,14 +368,12 @@ func (r *IrcRepo) StoreNetworkChannels(ctx context.Context, networkID int64, cha
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.storeNetworkChannels: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = tx.ExecContext(ctx, query, args...) _, err = tx.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.storeNetworkChannels: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
for _, channel := range channels { for _, channel := range channels {
@ -429,8 +404,7 @@ func (r *IrcRepo) StoreNetworkChannels(ctx context.Context, networkID int64, cha
err = channelQueryBuilder.QueryRowContext(ctx).Scan(&retID) err = channelQueryBuilder.QueryRowContext(ctx).Scan(&retID)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.storeNetworkChannels: error executing query") return errors.Wrap(err, "error executing query storeNetworkChannels")
return errors.Wrap(err, "error executing query")
} }
channel.ID = retID channel.ID = retID
@ -452,8 +426,7 @@ func (r *IrcRepo) StoreNetworkChannels(ctx context.Context, networkID int64, cha
err = tx.Commit() err = tx.Commit()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msgf("irc.storeNetworkChannels: error deleting network: %v", networkID) return errors.Wrap(err, "error commit transaction store network")
return err
} }
return nil return nil
@ -475,14 +448,12 @@ func (r *IrcRepo) StoreChannel(networkID int64, channel *domain.IrcChannel) erro
query, args, err := channelQueryBuilder.ToSql() query, args, err := channelQueryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.storeChannel: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = r.db.handler.Exec(query, args...) _, err = r.db.handler.Exec(query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.storeChannel: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
} else { } else {
queryBuilder := r.db.squirrel. queryBuilder := r.db.squirrel.
@ -509,7 +480,6 @@ func (r *IrcRepo) StoreChannel(networkID int64, channel *domain.IrcChannel) erro
err = queryBuilder.QueryRow().Scan(&retID) err = queryBuilder.QueryRow().Scan(&retID)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.storeChannels: error executing query")
return errors.Wrap(err, "error executing query") return errors.Wrap(err, "error executing query")
} }
@ -548,14 +518,12 @@ func (r *IrcRepo) UpdateChannel(channel *domain.IrcChannel) error {
query, args, err := channelQueryBuilder.ToSql() query, args, err := channelQueryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.updateChannel: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = r.db.handler.Exec(query, args...) _, err = r.db.handler.Exec(query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.updateChannel: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
return err return err
@ -571,14 +539,12 @@ func (r *IrcRepo) UpdateInviteCommand(networkID int64, invite string) error {
query, args, err := channelQueryBuilder.ToSql() query, args, err := channelQueryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.UpdateInviteCommand: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = r.db.handler.Exec(query, args...) _, err = r.db.handler.Exec(query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("irc.UpdateInviteCommand: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
return err return err

View file

@ -6,19 +6,21 @@ import (
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/pkg/errors"
sq "github.com/Masterminds/squirrel" sq "github.com/Masterminds/squirrel"
"github.com/lib/pq" "github.com/lib/pq"
"github.com/rs/zerolog"
) )
type NotificationRepo struct { type NotificationRepo struct {
log logger.Logger log zerolog.Logger
db *DB db *DB
} }
func NewNotificationRepo(log logger.Logger, db *DB) domain.NotificationRepo { func NewNotificationRepo(log logger.Logger, db *DB) domain.NotificationRepo {
return &NotificationRepo{ return &NotificationRepo{
log: log, log: log.With().Str("repo", "notification").Logger(),
db: db, db: db,
} }
} }
@ -32,14 +34,12 @@ func (r *NotificationRepo) Find(ctx context.Context, params domain.NotificationQ
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("notification.find: error building query") return nil, 0, errors.Wrap(err, "error building query")
return nil, 0, err
} }
rows, err := r.db.handler.QueryContext(ctx, query, args...) rows, err := r.db.handler.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("notification.find: error executing query") return nil, 0, errors.Wrap(err, "error executing query")
return nil, 0, err
} }
defer rows.Close() defer rows.Close()
@ -54,8 +54,7 @@ func (r *NotificationRepo) Find(ctx context.Context, params domain.NotificationQ
//if err := rows.Scan(&n.ID, &n.Name, &n.Type, &n.Enabled, pq.Array(&n.Events), &token, &apiKey, &webhook, &title, &icon, &host, &username, &password, &channel, &targets, &devices, &n.CreatedAt, &n.UpdatedAt); err != nil { //if err := rows.Scan(&n.ID, &n.Name, &n.Type, &n.Enabled, pq.Array(&n.Events), &token, &apiKey, &webhook, &title, &icon, &host, &username, &password, &channel, &targets, &devices, &n.CreatedAt, &n.UpdatedAt); err != nil {
//var token, apiKey, webhook, title, icon, host, username, password, channel, targets, devices sql.NullString //var token, apiKey, webhook, title, icon, host, username, password, channel, targets, devices sql.NullString
if err := rows.Scan(&n.ID, &n.Name, &n.Type, &n.Enabled, pq.Array(&n.Events), &webhook, &token, &channel, &n.CreatedAt, &n.UpdatedAt, &totalCount); err != nil { if err := rows.Scan(&n.ID, &n.Name, &n.Type, &n.Enabled, pq.Array(&n.Events), &webhook, &token, &channel, &n.CreatedAt, &n.UpdatedAt, &totalCount); err != nil {
r.log.Error().Stack().Err(err).Msg("notification.find: error scanning row") return nil, 0, errors.Wrap(err, "error scanning row")
return nil, 0, err
} }
//n.APIKey = apiKey.String //n.APIKey = apiKey.String
@ -74,7 +73,7 @@ func (r *NotificationRepo) Find(ctx context.Context, params domain.NotificationQ
notifications = append(notifications, n) notifications = append(notifications, n)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
return nil, 0, err return nil, 0, errors.Wrap(err, "error rows find")
} }
return notifications, totalCount, nil return notifications, totalCount, nil
@ -82,17 +81,9 @@ func (r *NotificationRepo) Find(ctx context.Context, params domain.NotificationQ
func (r *NotificationRepo) List(ctx context.Context) ([]domain.Notification, error) { func (r *NotificationRepo) List(ctx context.Context) ([]domain.Notification, error) {
//queryBuilder := r.db.squirrel.
// Select("r.id", "r.filter_status", "r.rejections", "r.indexer", "r.filter", "r.protocol", "r.title", "r.torrent_name", "r.size", "r.timestamp", "COUNT(*) OVER() AS total_count").
// From("release r").
// OrderBy("r.timestamp DESC")
//
//query, args, err := queryBuilder.ToSql()
rows, err := r.db.handler.QueryContext(ctx, "SELECT id, name, type, enabled, events, token, api_key, webhook, title, icon, host, username, password, channel, targets, devices, created_at, updated_at FROM notification ORDER BY name ASC") rows, err := r.db.handler.QueryContext(ctx, "SELECT id, name, type, enabled, events, token, api_key, webhook, title, icon, host, username, password, channel, targets, devices, created_at, updated_at FROM notification ORDER BY name ASC")
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("filters_list: error query data") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
defer rows.Close() defer rows.Close()
@ -104,8 +95,7 @@ func (r *NotificationRepo) List(ctx context.Context) ([]domain.Notification, err
var token, apiKey, webhook, title, icon, host, username, password, channel, targets, devices sql.NullString var token, apiKey, webhook, title, icon, host, username, password, channel, targets, devices sql.NullString
if err := rows.Scan(&n.ID, &n.Name, &n.Type, &n.Enabled, pq.Array(&n.Events), &token, &apiKey, &webhook, &title, &icon, &host, &username, &password, &channel, &targets, &devices, &n.CreatedAt, &n.UpdatedAt); err != nil { if err := rows.Scan(&n.ID, &n.Name, &n.Type, &n.Enabled, pq.Array(&n.Events), &token, &apiKey, &webhook, &title, &icon, &host, &username, &password, &channel, &targets, &devices, &n.CreatedAt, &n.UpdatedAt); err != nil {
r.log.Error().Stack().Err(err).Msg("notification_list: error scanning data to struct") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
//n.Events = ([]domain.NotificationEvent)(eventsSlice) //n.Events = ([]domain.NotificationEvent)(eventsSlice)
@ -124,7 +114,7 @@ func (r *NotificationRepo) List(ctx context.Context) ([]domain.Notification, err
notifications = append(notifications, n) notifications = append(notifications, n)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
return nil, err return nil, errors.Wrap(err, "error rows list")
} }
return notifications, nil return notifications, nil
@ -148,22 +138,20 @@ func (r *NotificationRepo) FindByID(ctx context.Context, id int) (*domain.Notifi
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("notification.findByID: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
//row := r.db.handler.QueryRowContext(ctx, "SELECT id, name, type, enabled, events, token, api_key, webhook, title, icon, host, username, password, channel, targets, devices, created_at, updated_at FROM notification WHERE id = ?", id) //row := r.db.handler.QueryRowContext(ctx, "SELECT id, name, type, enabled, events, token, api_key, webhook, title, icon, host, username, password, channel, targets, devices, created_at, updated_at FROM notification WHERE id = ?", id)
row := r.db.handler.QueryRowContext(ctx, query, args...) row := r.db.handler.QueryRowContext(ctx, query, args...)
if err := row.Err(); err != nil { if err := row.Err(); err != nil {
return nil, err return nil, errors.Wrap(err, "error executing query")
} }
var n domain.Notification var n domain.Notification
var token, apiKey, webhook, title, icon, host, username, password, channel, targets, devices sql.NullString var token, apiKey, webhook, title, icon, host, username, password, channel, targets, devices sql.NullString
if err := row.Scan(&n.ID, &n.Name, &n.Type, &n.Enabled, pq.Array(&n.Events), &token, &apiKey, &webhook, &title, &icon, &host, &username, &password, &channel, &targets, &devices, &n.CreatedAt, &n.UpdatedAt); err != nil { if err := row.Scan(&n.ID, &n.Name, &n.Type, &n.Enabled, pq.Array(&n.Events), &token, &apiKey, &webhook, &title, &icon, &host, &username, &password, &channel, &targets, &devices, &n.CreatedAt, &n.UpdatedAt); err != nil {
r.log.Error().Stack().Err(err).Msg("notification.findByID: error scanning row") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
n.Token = token.String n.Token = token.String
@ -213,8 +201,7 @@ func (r *NotificationRepo) Store(ctx context.Context, notification domain.Notifi
err := queryBuilder.QueryRowContext(ctx).Scan(&retID) err := queryBuilder.QueryRowContext(ctx).Scan(&retID)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("notification.store: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
r.log.Debug().Msgf("notification.store: added new %v", retID) r.log.Debug().Msgf("notification.store: added new %v", retID)
@ -242,14 +229,12 @@ func (r *NotificationRepo) Update(ctx context.Context, notification domain.Notif
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("action.update: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
_, err = r.db.handler.ExecContext(ctx, query, args...) _, err = r.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("notification.update: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
r.log.Debug().Msgf("notification.update: %v", notification.Name) r.log.Debug().Msgf("notification.update: %v", notification.Name)
@ -264,14 +249,12 @@ func (r *NotificationRepo) Delete(ctx context.Context, notificationID int) error
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("notification.delete: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = r.db.handler.ExecContext(ctx, query, args...) _, err = r.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("notification.delete: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
r.log.Info().Msgf("notification.delete: successfully deleted: %v", notificationID) r.log.Info().Msgf("notification.delete: successfully deleted: %v", notificationID)

View file

@ -2,8 +2,8 @@ package database
import ( import (
"database/sql" "database/sql"
"errors"
"fmt" "github.com/autobrr/autobrr/pkg/errors"
_ "github.com/lib/pq" _ "github.com/lib/pq"
) )
@ -14,19 +14,19 @@ func (db *DB) openPostgres() error {
// open database connection // open database connection
if db.handler, err = sql.Open("postgres", db.DSN); err != nil { if db.handler, err = sql.Open("postgres", db.DSN); err != nil {
db.log.Fatal().Err(err).Msg("could not open postgres connection") db.log.Fatal().Err(err).Msg("could not open postgres connection")
return err return errors.Wrap(err, "could not open postgres connection")
} }
err = db.handler.Ping() err = db.handler.Ping()
if err != nil { if err != nil {
db.log.Fatal().Err(err).Msg("could not ping postgres database") db.log.Fatal().Err(err).Msg("could not ping postgres database")
return err return errors.Wrap(err, "could not ping postgres database")
} }
// migrate db // migrate db
if err = db.migratePostgres(); err != nil { if err = db.migratePostgres(); err != nil {
db.log.Fatal().Err(err).Msg("could not migrate postgres database") db.log.Fatal().Err(err).Msg("could not migrate postgres database")
return err return errors.Wrap(err, "could not migrate postgres database")
} }
return nil return nil
@ -35,7 +35,7 @@ func (db *DB) openPostgres() error {
func (db *DB) migratePostgres() error { func (db *DB) migratePostgres() error {
tx, err := db.handler.Begin() tx, err := db.handler.Begin()
if err != nil { if err != nil {
return err return errors.Wrap(err, "error starting transaction")
} }
defer tx.Rollback() defer tx.Rollback()
@ -45,37 +45,37 @@ func (db *DB) migratePostgres() error {
);` );`
if _, err := tx.Exec(initialSchema); err != nil { if _, err := tx.Exec(initialSchema); err != nil {
return fmt.Errorf("failed to create schema_migrations table: %s", err) return errors.New("failed to create schema_migrations table")
} }
var version int var version int
err = tx.QueryRow(`SELECT version FROM schema_migrations`).Scan(&version) err = tx.QueryRow(`SELECT version FROM schema_migrations`).Scan(&version)
if err != nil && !errors.Is(err, sql.ErrNoRows) { if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err return errors.Wrap(err, "no rows")
} }
if version == len(postgresMigrations) { if version == len(postgresMigrations) {
return nil return nil
} }
if version > len(postgresMigrations) { if version > len(postgresMigrations) {
return fmt.Errorf("old") return errors.New("old")
} }
if version == 0 { if version == 0 {
if _, err := tx.Exec(postgresSchema); err != nil { if _, err := tx.Exec(postgresSchema); err != nil {
return fmt.Errorf("failed to initialize schema: %v", err) return errors.Wrap(err, "failed to initialize schema")
} }
} else { } else {
for i := version; i < len(postgresMigrations); i++ { for i := version; i < len(postgresMigrations); i++ {
if _, err := tx.Exec(postgresMigrations[i]); err != nil { if _, err := tx.Exec(postgresMigrations[i]); err != nil {
return fmt.Errorf("failed to execute migration #%v: %v", i, err) return errors.Wrap(err, "failed to execute migration #%v", i)
} }
} }
} }
_, err = tx.Exec(`INSERT INTO schema_migrations (id, version) VALUES (1, $1) ON CONFLICT (id) DO UPDATE SET version = $1`, len(postgresMigrations)) _, err = tx.Exec(`INSERT INTO schema_migrations (id, version) VALUES (1, $1) ON CONFLICT (id) DO UPDATE SET version = $1`, len(postgresMigrations))
if err != nil { if err != nil {
return fmt.Errorf("failed to bump schema version: %v", err) return errors.Wrap(err, "failed to bump schema version")
} }
return tx.Commit() return tx.Commit()

View file

@ -8,19 +8,21 @@ import (
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/pkg/errors"
sq "github.com/Masterminds/squirrel" sq "github.com/Masterminds/squirrel"
"github.com/lib/pq" "github.com/lib/pq"
"github.com/rs/zerolog"
) )
type ReleaseRepo struct { type ReleaseRepo struct {
log logger.Logger log zerolog.Logger
db *DB db *DB
} }
func NewReleaseRepo(log logger.Logger, db *DB) domain.ReleaseRepo { func NewReleaseRepo(log logger.Logger, db *DB) domain.ReleaseRepo {
return &ReleaseRepo{ return &ReleaseRepo{
log: log, log: log.With().Str("repo", "release").Logger(),
db: db, db: db,
} }
} }
@ -40,8 +42,7 @@ func (repo *ReleaseRepo) Store(ctx context.Context, r *domain.Release) (*domain.
err := queryBuilder.QueryRowContext(ctx).Scan(&retID) err := queryBuilder.QueryRowContext(ctx).Scan(&retID)
if err != nil { if err != nil {
repo.log.Error().Stack().Err(err).Msg("release.store: error executing query") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
r.ID = retID r.ID = retID
@ -63,14 +64,12 @@ func (repo *ReleaseRepo) StoreReleaseActionStatus(ctx context.Context, a *domain
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
repo.log.Error().Stack().Err(err).Msg("release.store: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = repo.db.handler.ExecContext(ctx, query, args...) _, err = repo.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
repo.log.Error().Stack().Err(err).Msg("error updating status of release") return errors.Wrap(err, "error executing query")
return err
} }
} else { } else {
@ -85,8 +84,7 @@ func (repo *ReleaseRepo) StoreReleaseActionStatus(ctx context.Context, a *domain
err := queryBuilder.QueryRowContext(ctx).Scan(&retID) err := queryBuilder.QueryRowContext(ctx).Scan(&retID)
if err != nil { if err != nil {
repo.log.Error().Stack().Err(err).Msg("release.storeReleaseActionStatus: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
a.ID = retID a.ID = retID
@ -100,7 +98,7 @@ func (repo *ReleaseRepo) StoreReleaseActionStatus(ctx context.Context, a *domain
func (repo *ReleaseRepo) Find(ctx context.Context, params domain.ReleaseQueryParams) ([]*domain.Release, int64, int64, error) { func (repo *ReleaseRepo) Find(ctx context.Context, params domain.ReleaseQueryParams) ([]*domain.Release, int64, int64, error) {
tx, err := repo.db.BeginTx(ctx, &sql.TxOptions{}) tx, err := repo.db.BeginTx(ctx, &sql.TxOptions{})
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, errors.Wrap(err, "error begin transaction")
} }
defer tx.Rollback() defer tx.Rollback()
@ -118,8 +116,7 @@ func (repo *ReleaseRepo) Find(ctx context.Context, params domain.ReleaseQueryPar
} }
if err = tx.Commit(); err != nil { if err = tx.Commit(); err != nil {
repo.log.Error().Stack().Err(err).Msg("error finding releases") return nil, 0, 0, errors.Wrap(err, "error commit transaction find releases")
return nil, 0, 0, err
} }
return releases, nextCursor, total, nil return releases, nextCursor, total, nil
@ -165,23 +162,20 @@ func (repo *ReleaseRepo) findReleases(ctx context.Context, tx *Tx, params domain
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
repo.log.Trace().Str("database", "release.find").Msgf("query: '%v', args: '%v'", query, args) repo.log.Trace().Str("database", "release.find").Msgf("query: '%v', args: '%v'", query, args)
if err != nil { if err != nil {
repo.log.Error().Stack().Err(err).Msg("error building query") return nil, 0, 0, errors.Wrap(err, "error building query")
return nil, 0, 0, err
} }
res := make([]*domain.Release, 0) res := make([]*domain.Release, 0)
rows, err := tx.QueryContext(ctx, query, args...) rows, err := tx.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
repo.log.Error().Stack().Err(err).Msg("error fetching releases") return nil, 0, 0, errors.Wrap(err, "error executing query")
return res, 0, 0, nil
} }
defer rows.Close() defer rows.Close()
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
repo.log.Error().Stack().Err(err) return res, 0, 0, errors.Wrap(err, "error rows findreleases")
return res, 0, 0, err
} }
var countItems int64 = 0 var countItems int64 = 0
@ -192,8 +186,7 @@ func (repo *ReleaseRepo) findReleases(ctx context.Context, tx *Tx, params domain
var indexer, filter sql.NullString var indexer, filter sql.NullString
if err := rows.Scan(&rls.ID, &rls.FilterStatus, pq.Array(&rls.Rejections), &indexer, &filter, &rls.Protocol, &rls.Title, &rls.TorrentName, &rls.Size, &rls.Timestamp, &countItems); err != nil { if err := rows.Scan(&rls.ID, &rls.FilterStatus, pq.Array(&rls.Rejections), &indexer, &filter, &rls.Protocol, &rls.Title, &rls.TorrentName, &rls.Size, &rls.Timestamp, &countItems); err != nil {
repo.log.Error().Stack().Err(err).Msg("release.find: error scanning data to struct") return res, 0, 0, errors.Wrap(err, "error scanning row")
return res, 0, 0, err
} }
rls.Indexer = indexer.String rls.Indexer = indexer.String
@ -373,23 +366,20 @@ func (repo *ReleaseRepo) attachActionStatus(ctx context.Context, tx *Tx, release
rows, err := tx.QueryContext(ctx, query, args...) rows, err := tx.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
repo.log.Error().Stack().Err(err).Msg("error fetching releases") return res, errors.Wrap(err, "error executing query")
return res, nil
} }
defer rows.Close() defer rows.Close()
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
repo.log.Error().Stack().Err(err) return res, errors.Wrap(err, "error rows")
return res, err
} }
for rows.Next() { for rows.Next() {
var rls domain.ReleaseActionStatus var rls domain.ReleaseActionStatus
if err := rows.Scan(&rls.ID, &rls.Status, &rls.Action, &rls.Type, pq.Array(&rls.Rejections), &rls.Timestamp); err != nil { if err := rows.Scan(&rls.ID, &rls.Status, &rls.Action, &rls.Type, pq.Array(&rls.Rejections), &rls.Timestamp); err != nil {
repo.log.Error().Stack().Err(err).Msg("release.find: error scanning data to struct") return res, errors.Wrap(err, "error scanning row")
return res, err
} }
res = append(res, rls) res = append(res, rls)
@ -411,15 +401,13 @@ FROM "release";`
row := repo.db.handler.QueryRowContext(ctx, query) row := repo.db.handler.QueryRowContext(ctx, query)
if err := row.Err(); err != nil { if err := row.Err(); err != nil {
repo.log.Error().Stack().Err(err).Msg("release.stats: error querying stats") return nil, errors.Wrap(err, "error executing query")
return nil, err
} }
var rls domain.ReleaseStats var rls domain.ReleaseStats
if err := row.Scan(&rls.TotalCount, &rls.FilteredCount, &rls.FilterRejectedCount, &rls.PushApprovedCount, &rls.PushRejectedCount); err != nil { if err := row.Scan(&rls.TotalCount, &rls.FilteredCount, &rls.FilterRejectedCount, &rls.PushApprovedCount, &rls.PushRejectedCount); err != nil {
repo.log.Error().Stack().Err(err).Msg("release.stats: error scanning stats data to struct") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
return &rls, nil return &rls, nil
@ -435,20 +423,17 @@ func (repo *ReleaseRepo) Delete(ctx context.Context) error {
_, err = tx.ExecContext(ctx, `DELETE FROM "release"`) _, err = tx.ExecContext(ctx, `DELETE FROM "release"`)
if err != nil { if err != nil {
repo.log.Error().Stack().Err(err).Msg("error deleting all releases") return errors.Wrap(err, "error executing query")
return err
} }
_, err = tx.ExecContext(ctx, `DELETE FROM release_action_status`) _, err = tx.ExecContext(ctx, `DELETE FROM release_action_status`)
if err != nil { if err != nil {
repo.log.Error().Stack().Err(err).Msg("error deleting all release_action_status") return errors.Wrap(err, "error executing query")
return err
} }
err = tx.Commit() err = tx.Commit()
if err != nil { if err != nil {
repo.log.Error().Stack().Err(err).Msg("error deleting all releases") return errors.Wrap(err, "error commit transaction delete")
return err
} }
return nil return nil

View file

@ -4,13 +4,15 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/lib/pq" "github.com/lib/pq"
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
) )
func (db *DB) openSQLite() error { func (db *DB) openSQLite() error {
if db.DSN == "" { if db.DSN == "" {
return fmt.Errorf("DSN required") return errors.New("DSN required")
} }
var err error var err error
@ -23,20 +25,20 @@ func (db *DB) openSQLite() error {
// Set busy timeout // Set busy timeout
//if _, err = db.handler.Exec(`PRAGMA busy_timeout = 5000;`); err != nil { //if _, err = db.handler.Exec(`PRAGMA busy_timeout = 5000;`); err != nil {
// return fmt.Errorf("busy timeout pragma: %w", err) // return errors.New("busy timeout pragma: %w", err)
//} //}
// Enable WAL. SQLite performs better with the WAL because it allows // Enable WAL. SQLite performs better with the WAL because it allows
// multiple readers to operate while data is being written. // multiple readers to operate while data is being written.
if _, err = db.handler.Exec(`PRAGMA journal_mode = wal;`); err != nil { if _, err = db.handler.Exec(`PRAGMA journal_mode = wal;`); err != nil {
return fmt.Errorf("enable wal: %w", err) return errors.Wrap(err, "enable wal")
} }
// Enable foreign key checks. For historical reasons, SQLite does not check // Enable foreign key checks. For historical reasons, SQLite does not check
// foreign key constraints by default. There's some overhead on inserts to // foreign key constraints by default. There's some overhead on inserts to
// verify foreign key integrity, but it's definitely worth it. // verify foreign key integrity, but it's definitely worth it.
//if _, err = db.handler.Exec(`PRAGMA foreign_keys = ON;`); err != nil { //if _, err = db.handler.Exec(`PRAGMA foreign_keys = ON;`); err != nil {
// return fmt.Errorf("foreign keys pragma: %w", err) // return errors.New("foreign keys pragma: %w", err)
//} //}
// migrate db // migrate db
@ -54,13 +56,13 @@ func (db *DB) migrateSQLite() error {
var version int var version int
if err := db.handler.QueryRow("PRAGMA user_version").Scan(&version); err != nil { if err := db.handler.QueryRow("PRAGMA user_version").Scan(&version); err != nil {
return fmt.Errorf("failed to query schema version: %v", err) return errors.Wrap(err, "failed to query schema version")
} }
if version == len(sqliteMigrations) { if version == len(sqliteMigrations) {
return nil return nil
} else if version > len(sqliteMigrations) { } else if version > len(sqliteMigrations) {
return fmt.Errorf("autobrr (version %d) older than schema (version: %d)", len(sqliteMigrations), version) return errors.New("autobrr (version %d) older than schema (version: %d)", len(sqliteMigrations), version)
} }
tx, err := db.handler.Begin() tx, err := db.handler.Begin()
@ -71,12 +73,12 @@ func (db *DB) migrateSQLite() error {
if version == 0 { if version == 0 {
if _, err := tx.Exec(sqliteSchema); err != nil { if _, err := tx.Exec(sqliteSchema); err != nil {
return fmt.Errorf("failed to initialize schema: %v", err) return errors.Wrap(err, "failed to initialize schema")
} }
} else { } else {
for i := version; i < len(sqliteMigrations); i++ { for i := version; i < len(sqliteMigrations); i++ {
if _, err := tx.Exec(sqliteMigrations[i]); err != nil { if _, err := tx.Exec(sqliteMigrations[i]); err != nil {
return fmt.Errorf("failed to execute migration #%v: %v", i, err) return errors.Wrap(err, "failed to execute migration #%v", i)
} }
} }
} }
@ -87,13 +89,13 @@ func (db *DB) migrateSQLite() error {
// TODO 2022-01-30 remove this in future version // TODO 2022-01-30 remove this in future version
if version == 5 && len(sqliteMigrations) == 6 { if version == 5 && len(sqliteMigrations) == 6 {
if err := customMigrateCopySourcesToMedia(tx); err != nil { if err := customMigrateCopySourcesToMedia(tx); err != nil {
return fmt.Errorf("could not run custom data migration: %v", err) return errors.Wrap(err, "could not run custom data migration")
} }
} }
_, err = tx.Exec(fmt.Sprintf("PRAGMA user_version = %d", len(sqliteMigrations))) _, err = tx.Exec(fmt.Sprintf("PRAGMA user_version = %d", len(sqliteMigrations)))
if err != nil { if err != nil {
return fmt.Errorf("failed to bump schema version: %v", err) return errors.Wrap(err, "failed to bump schema version")
} }
return tx.Commit() return tx.Commit()
@ -115,7 +117,7 @@ func customMigrateCopySourcesToMedia(tx *sql.Tx) error {
OR sources LIKE '%"SACD"%' OR sources LIKE '%"SACD"%'
;`) ;`)
if err != nil { if err != nil {
return fmt.Errorf("could not run custom data migration: %v", err) return errors.Wrap(err, "could not run custom data migration")
} }
defer rows.Close() defer rows.Close()

View file

@ -2,18 +2,22 @@ package database
import ( import (
"context" "context"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/rs/zerolog"
) )
type UserRepo struct { type UserRepo struct {
log logger.Logger log zerolog.Logger
db *DB db *DB
} }
func NewUserRepo(log logger.Logger, db *DB) domain.UserRepo { func NewUserRepo(log logger.Logger, db *DB) domain.UserRepo {
return &UserRepo{ return &UserRepo{
log: log, log: log.With().Str("repo", "user").Logger(),
db: db, db: db,
} }
} }
@ -23,19 +27,17 @@ func (r *UserRepo) GetUserCount(ctx context.Context) (int, error) {
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("user.store: error building query") return 0, errors.Wrap(err, "error building query")
return 0, err
} }
row := r.db.handler.QueryRowContext(ctx, query, args...) row := r.db.handler.QueryRowContext(ctx, query, args...)
if err := row.Err(); err != nil { if err := row.Err(); err != nil {
return 0, err return 0, errors.Wrap(err, "error executing query")
} }
result := 0 result := 0
if err := row.Scan(&result); err != nil { if err := row.Scan(&result); err != nil {
r.log.Error().Err(err).Msg("could not query number of users") return 0, errors.Wrap(err, "error scanning row")
return 0, err
} }
return result, nil return result, nil
@ -50,20 +52,18 @@ func (r *UserRepo) FindByUsername(ctx context.Context, username string) (*domain
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("user.store: error building query") return nil, errors.Wrap(err, "error building query")
return nil, err
} }
row := r.db.handler.QueryRowContext(ctx, query, args...) row := r.db.handler.QueryRowContext(ctx, query, args...)
if err := row.Err(); err != nil { if err := row.Err(); err != nil {
return nil, err return nil, errors.Wrap(err, "error executing query")
} }
var user domain.User var user domain.User
if err := row.Scan(&user.ID, &user.Username, &user.Password); err != nil { if err := row.Scan(&user.ID, &user.Username, &user.Password); err != nil {
r.log.Error().Err(err).Msg("could not scan user to struct") return nil, errors.Wrap(err, "error scanning row")
return nil, err
} }
return &user, nil return &user, nil
@ -80,14 +80,12 @@ func (r *UserRepo) Store(ctx context.Context, user domain.User) error {
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("user.store: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = r.db.handler.ExecContext(ctx, query, args...) _, err = r.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("user.store: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
return err return err
@ -105,14 +103,12 @@ func (r *UserRepo) Update(ctx context.Context, user domain.User) error {
query, args, err := queryBuilder.ToSql() query, args, err := queryBuilder.ToSql()
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("user.store: error building query") return errors.Wrap(err, "error building query")
return err
} }
_, err = r.db.handler.ExecContext(ctx, query, args...) _, err = r.db.handler.ExecContext(ctx, query, args...)
if err != nil { if err != nil {
r.log.Error().Stack().Err(err).Msg("user.store: error executing query") return errors.Wrap(err, "error executing query")
return err
} }
return err return err

View file

@ -3,7 +3,6 @@ package domain
import "context" import "context"
type DownloadClientRepo interface { type DownloadClientRepo interface {
//FindByActionID(actionID int) ([]DownloadClient, error)
List(ctx context.Context) ([]DownloadClient, error) List(ctx context.Context) ([]DownloadClient, error)
FindByID(ctx context.Context, id int32) (*DownloadClient, error) FindByID(ctx context.Context, id int32) (*DownloadClient, error)
Store(ctx context.Context, client DownloadClient) (*DownloadClient, error) Store(ctx context.Context, client DownloadClient) (*DownloadClient, error)

View file

@ -14,12 +14,12 @@ import (
"strings" "strings"
"time" "time"
"golang.org/x/net/publicsuffix" "github.com/autobrr/autobrr/pkg/errors"
"github.com/anacrolix/torrent/metainfo" "github.com/anacrolix/torrent/metainfo"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/moistari/rls" "github.com/moistari/rls"
"github.com/pkg/errors" "golang.org/x/net/publicsuffix"
) )
type ReleaseRepo interface { type ReleaseRepo interface {
@ -269,7 +269,7 @@ func (r *Release) DownloadTorrentFile() error {
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
if err != nil { if err != nil {
return err return errors.Wrap(err, "could not create cookiejar")
} }
customTransport := http.DefaultTransport.(*http.Transport).Clone() customTransport := http.DefaultTransport.(*http.Transport).Clone()
@ -300,7 +300,7 @@ func (r *Release) DownloadTorrentFile() error {
// retry logic // retry logic
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return fmt.Errorf("error downloading torrent (%v) file (%v) from '%v' - status code: %d", r.TorrentName, r.TorrentURL, r.Indexer, resp.StatusCode) return errors.New("error downloading torrent (%v) file (%v) from '%v' - status code: %d", r.TorrentName, r.TorrentURL, r.Indexer, resp.StatusCode)
} }
// Create tmp file // Create tmp file
@ -479,7 +479,7 @@ func getStringMapValue(stringMap map[string]string, key string) (string, error)
} }
} }
return "", fmt.Errorf("key was not found in map: %q", lowerKey) return "", errors.New("key was not found in map: %q", lowerKey)
} }
func SplitAny(s string, seps string) []string { func SplitAny(s string, seps string) []string {

View file

@ -3,6 +3,8 @@ package domain
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"github.com/autobrr/autobrr/pkg/errors"
) )
var types map[string][]*TagInfo var types map[string][]*TagInfo
@ -192,7 +194,7 @@ func init() {
var err error var err error
//if info.re, err = regexp.Compile(`(?i)^(?:` + info.RE() + `)$`); err != nil { //if info.re, err = regexp.Compile(`(?i)^(?:` + info.RE() + `)$`); err != nil {
if info.re, err = regexp.Compile(`(?i)(?:` + info.RE() + `)`); err != nil { if info.re, err = regexp.Compile(`(?i)(?:` + info.RE() + `)`); err != nil {
fmt.Errorf("tag %q has invalid regexp %q\n", s, info.re) errors.Wrap(err, "tag %q has invalid regexp %q\n", s, info.re)
} }
} }
} }

View file

@ -1,12 +1,10 @@
package download_client package download_client
import ( import (
"fmt"
"time" "time"
"github.com/pkg/errors"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/autobrr/autobrr/pkg/lidarr" "github.com/autobrr/autobrr/pkg/lidarr"
"github.com/autobrr/autobrr/pkg/qbittorrent" "github.com/autobrr/autobrr/pkg/qbittorrent"
"github.com/autobrr/autobrr/pkg/radarr" "github.com/autobrr/autobrr/pkg/radarr"
@ -48,6 +46,7 @@ func (s *service) testQbittorrentConnection(client domain.DownloadClient) error
Password: client.Password, Password: client.Password,
TLS: client.TLS, TLS: client.TLS,
TLSSkipVerify: client.TLSSkipVerify, TLSSkipVerify: client.TLSSkipVerify,
Log: s.subLogger,
} }
// only set basic auth if enabled // only set basic auth if enabled
@ -60,7 +59,7 @@ func (s *service) testQbittorrentConnection(client domain.DownloadClient) error
qbt := qbittorrent.NewClient(qbtSettings) qbt := qbittorrent.NewClient(qbtSettings)
err := qbt.Login() err := qbt.Login()
if err != nil { if err != nil {
return errors.Wrap(err, fmt.Sprintf("error logging into client: %v", client.Host)) return errors.Wrap(err, "error logging into client: %v", client.Host)
} }
s.log.Debug().Msgf("test client connection for qBittorrent: success") s.log.Debug().Msgf("test client connection for qBittorrent: success")
@ -94,7 +93,7 @@ func (s *service) testDelugeConnection(client domain.DownloadClient) error {
// perform connection to Deluge server // perform connection to Deluge server
err := deluge.Connect() err := deluge.Connect()
if err != nil { if err != nil {
return errors.Wrap(err, fmt.Sprintf("error logging into client: %v", client.Host)) return errors.Wrap(err, "error logging into client: %v", client.Host)
} }
defer deluge.Close() defer deluge.Close()
@ -102,7 +101,7 @@ func (s *service) testDelugeConnection(client domain.DownloadClient) error {
// print daemon version // print daemon version
ver, err := deluge.DaemonVersion() ver, err := deluge.DaemonVersion()
if err != nil { if err != nil {
return errors.Wrap(err, fmt.Sprintf("could not get daemon version: %v", client.Host)) return errors.Wrap(err, "could not get daemon version: %v", client.Host)
} }
s.log.Debug().Msgf("test client connection for Deluge: success - daemon version: %v", ver) s.log.Debug().Msgf("test client connection for Deluge: success - daemon version: %v", ver)
@ -117,11 +116,12 @@ func (s *service) testRadarrConnection(client domain.DownloadClient) error {
BasicAuth: client.Settings.Basic.Auth, BasicAuth: client.Settings.Basic.Auth,
Username: client.Settings.Basic.Username, Username: client.Settings.Basic.Username,
Password: client.Settings.Basic.Password, Password: client.Settings.Basic.Password,
Log: s.subLogger,
}) })
_, err := r.Test() _, err := r.Test()
if err != nil { if err != nil {
return errors.Wrap(err, fmt.Sprintf("radarr: connection test failed: %v", client.Host)) return errors.Wrap(err, "radarr: connection test failed: %v", client.Host)
} }
s.log.Debug().Msgf("test client connection for Radarr: success") s.log.Debug().Msgf("test client connection for Radarr: success")
@ -136,11 +136,12 @@ func (s *service) testSonarrConnection(client domain.DownloadClient) error {
BasicAuth: client.Settings.Basic.Auth, BasicAuth: client.Settings.Basic.Auth,
Username: client.Settings.Basic.Username, Username: client.Settings.Basic.Username,
Password: client.Settings.Basic.Password, Password: client.Settings.Basic.Password,
Log: s.subLogger,
}) })
_, err := r.Test() _, err := r.Test()
if err != nil { if err != nil {
return errors.Wrap(err, fmt.Sprintf("sonarr: connection test failed: %v", client.Host)) return errors.Wrap(err, "sonarr: connection test failed: %v", client.Host)
} }
s.log.Debug().Msgf("test client connection for Sonarr: success") s.log.Debug().Msgf("test client connection for Sonarr: success")
@ -155,11 +156,12 @@ func (s *service) testLidarrConnection(client domain.DownloadClient) error {
BasicAuth: client.Settings.Basic.Auth, BasicAuth: client.Settings.Basic.Auth,
Username: client.Settings.Basic.Username, Username: client.Settings.Basic.Username,
Password: client.Settings.Basic.Password, Password: client.Settings.Basic.Password,
Log: s.subLogger,
}) })
_, err := r.Test() _, err := r.Test()
if err != nil { if err != nil {
return errors.Wrap(err, fmt.Sprintf("lidarr: connection test failed: %v", client.Host)) return errors.Wrap(err, "lidarr: connection test failed: %v", client.Host)
} }
s.log.Debug().Msgf("test client connection for Lidarr: success") s.log.Debug().Msgf("test client connection for Lidarr: success")
@ -174,11 +176,12 @@ func (s *service) testWhisparrConnection(client domain.DownloadClient) error {
BasicAuth: client.Settings.Basic.Auth, BasicAuth: client.Settings.Basic.Auth,
Username: client.Settings.Basic.Username, Username: client.Settings.Basic.Username,
Password: client.Settings.Basic.Password, Password: client.Settings.Basic.Password,
Log: s.subLogger,
}) })
_, err := r.Test() _, err := r.Test()
if err != nil { if err != nil {
return errors.Wrap(err, fmt.Sprintf("whisparr: connection test failed: %v", client.Host)) return errors.Wrap(err, "whisparr: connection test failed: %v", client.Host)
} }
s.log.Debug().Msgf("test client connection for whisparr: success") s.log.Debug().Msgf("test client connection for whisparr: success")

View file

@ -3,9 +3,13 @@ package download_client
import ( import (
"context" "context"
"errors" "errors"
"log"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/dcarbone/zadapters/zstdlog"
"github.com/rs/zerolog"
) )
type Service interface { type Service interface {
@ -18,23 +22,40 @@ type Service interface {
} }
type service struct { type service struct {
log logger.Logger log zerolog.Logger
repo domain.DownloadClientRepo repo domain.DownloadClientRepo
subLogger *log.Logger
} }
func NewService(log logger.Logger, repo domain.DownloadClientRepo) Service { func NewService(log logger.Logger, repo domain.DownloadClientRepo) Service {
return &service{ s := &service{
log: log, log: log.With().Str("module", "download_client").Logger(),
repo: repo, repo: repo,
} }
s.subLogger = zstdlog.NewStdLoggerWithLevel(s.log.With().Logger(), zerolog.TraceLevel)
return s
} }
func (s *service) List(ctx context.Context) ([]domain.DownloadClient, error) { func (s *service) List(ctx context.Context) ([]domain.DownloadClient, error) {
return s.repo.List(ctx) clients, err := s.repo.List(ctx)
if err != nil {
s.log.Error().Err(err).Msg("could not list download clients")
return nil, err
}
return clients, nil
} }
func (s *service) FindByID(ctx context.Context, id int32) (*domain.DownloadClient, error) { func (s *service) FindByID(ctx context.Context, id int32) (*domain.DownloadClient, error) {
return s.repo.FindByID(ctx, id) client, err := s.repo.FindByID(ctx, id)
if err != nil {
s.log.Error().Err(err).Msgf("could not find download client by id: %v", id)
return nil, err
}
return client, nil
} }
func (s *service) Store(ctx context.Context, client domain.DownloadClient) (*domain.DownloadClient, error) { func (s *service) Store(ctx context.Context, client domain.DownloadClient) (*domain.DownloadClient, error) {
@ -46,7 +67,13 @@ func (s *service) Store(ctx context.Context, client domain.DownloadClient) (*dom
} }
// store // store
return s.repo.Store(ctx, client) c, err := s.repo.Store(ctx, client)
if err != nil {
s.log.Error().Err(err).Msgf("could not store download client: %+v", client)
return nil, err
}
return c, err
} }
func (s *service) Update(ctx context.Context, client domain.DownloadClient) (*domain.DownloadClient, error) { func (s *service) Update(ctx context.Context, client domain.DownloadClient) (*domain.DownloadClient, error) {
@ -57,12 +84,23 @@ func (s *service) Update(ctx context.Context, client domain.DownloadClient) (*do
return nil, errors.New("validation error: no type") return nil, errors.New("validation error: no type")
} }
// store // update
return s.repo.Update(ctx, client) c, err := s.repo.Update(ctx, client)
if err != nil {
s.log.Error().Err(err).Msgf("could not update download client: %+v", client)
return nil, err
}
return c, err
} }
func (s *service) Delete(ctx context.Context, clientID int) error { func (s *service) Delete(ctx context.Context, clientID int) error {
return s.repo.Delete(ctx, clientID) err := s.repo.Delete(ctx, clientID)
if err != nil {
s.log.Error().Err(err).Msgf("could not delete download client: %v", clientID)
return err
}
return nil
} }
func (s *service) Test(client domain.DownloadClient) error { func (s *service) Test(client domain.DownloadClient) error {

View file

@ -9,10 +9,11 @@ import (
"github.com/autobrr/autobrr/internal/release" "github.com/autobrr/autobrr/internal/release"
"github.com/asaskevich/EventBus" "github.com/asaskevich/EventBus"
"github.com/rs/zerolog"
) )
type Subscriber struct { type Subscriber struct {
log logger.Logger log zerolog.Logger
eventbus EventBus.Bus eventbus EventBus.Bus
notificationSvc notification.Service notificationSvc notification.Service
releaseSvc release.Service releaseSvc release.Service
@ -20,7 +21,7 @@ type Subscriber struct {
func NewSubscribers(log logger.Logger, eventbus EventBus.Bus, notificationSvc notification.Service, releaseSvc release.Service) Subscriber { func NewSubscribers(log logger.Logger, eventbus EventBus.Bus, notificationSvc notification.Service, releaseSvc release.Service) Subscriber {
s := Subscriber{ s := Subscriber{
log: log, log: log.With().Str("module", "events").Logger(),
eventbus: eventbus, eventbus: eventbus,
notificationSvc: notificationSvc, notificationSvc: notificationSvc,
releaseSvc: releaseSvc, releaseSvc: releaseSvc,
@ -55,7 +56,7 @@ func (s Subscriber) releasePushStatus(actionStatus *domain.ReleaseActionStatus)
} }
func (s Subscriber) sendNotification(event *domain.NotificationEvent, payload *domain.NotificationPayload) { func (s Subscriber) sendNotification(event *domain.NotificationEvent, payload *domain.NotificationPayload) {
s.log.Trace().Msgf("events: '%v' '%+v'", event, payload) s.log.Trace().Msgf("events: '%v' '%+v'", *event, payload)
s.notificationSvc.Send(*event, *payload) s.notificationSvc.Send(*event, *payload)
} }

View file

@ -2,14 +2,16 @@ package feed
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/internal/release" "github.com/autobrr/autobrr/internal/release"
"github.com/autobrr/autobrr/internal/scheduler" "github.com/autobrr/autobrr/internal/scheduler"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/autobrr/autobrr/pkg/torznab" "github.com/autobrr/autobrr/pkg/torznab"
"github.com/rs/zerolog"
) )
type Service interface { type Service interface {
@ -34,7 +36,7 @@ type feedInstance struct {
} }
type service struct { type service struct {
log logger.Logger log zerolog.Logger
jobs map[string]int jobs map[string]int
repo domain.FeedRepo repo domain.FeedRepo
@ -45,7 +47,7 @@ type service struct {
func NewService(log logger.Logger, repo domain.FeedRepo, cacheRepo domain.FeedCacheRepo, releaseSvc release.Service, scheduler scheduler.Service) Service { func NewService(log logger.Logger, repo domain.FeedRepo, cacheRepo domain.FeedCacheRepo, releaseSvc release.Service, scheduler scheduler.Service) Service {
return &service{ return &service{
log: log, log: log.With().Str("module", "feed").Logger(),
jobs: map[string]int{}, jobs: map[string]int{},
repo: repo, repo: repo,
cacheRepo: cacheRepo, cacheRepo: cacheRepo,
@ -55,31 +57,67 @@ func NewService(log logger.Logger, repo domain.FeedRepo, cacheRepo domain.FeedCa
} }
func (s *service) FindByID(ctx context.Context, id int) (*domain.Feed, error) { func (s *service) FindByID(ctx context.Context, id int) (*domain.Feed, error) {
return s.repo.FindByID(ctx, id) feed, err := s.repo.FindByID(ctx, id)
if err != nil {
s.log.Error().Err(err).Msgf("could not find feed by id: %v", id)
return nil, err
}
return feed, nil
} }
func (s *service) FindByIndexerIdentifier(ctx context.Context, indexer string) (*domain.Feed, error) { func (s *service) FindByIndexerIdentifier(ctx context.Context, indexer string) (*domain.Feed, error) {
return s.repo.FindByIndexerIdentifier(ctx, indexer) feed, err := s.repo.FindByIndexerIdentifier(ctx, indexer)
if err != nil {
s.log.Error().Err(err).Msgf("could not find feed by indexer: %v", indexer)
return nil, err
}
return feed, nil
} }
func (s *service) Find(ctx context.Context) ([]domain.Feed, error) { func (s *service) Find(ctx context.Context) ([]domain.Feed, error) {
return s.repo.Find(ctx) feeds, err := s.repo.Find(ctx)
if err != nil {
s.log.Error().Err(err).Msg("could not find feeds")
return nil, err
}
return feeds, err
} }
func (s *service) Store(ctx context.Context, feed *domain.Feed) error { func (s *service) Store(ctx context.Context, feed *domain.Feed) error {
return s.repo.Store(ctx, feed) if err := s.repo.Store(ctx, feed); err != nil {
s.log.Error().Err(err).Msgf("could not store feed: %+v", feed)
return err
}
return nil
} }
func (s *service) Update(ctx context.Context, feed *domain.Feed) error { func (s *service) Update(ctx context.Context, feed *domain.Feed) error {
return s.update(ctx, feed) if err := s.repo.Update(ctx, feed); err != nil {
s.log.Error().Err(err).Msgf("could not update feed: %+v", feed)
return err
}
return nil
} }
func (s *service) Delete(ctx context.Context, id int) error { func (s *service) Delete(ctx context.Context, id int) error {
return s.delete(ctx, id) if err := s.repo.Delete(ctx, id); err != nil {
s.log.Error().Err(err).Msgf("could not delete feed by id: %v", id)
return err
}
return nil
} }
func (s *service) ToggleEnabled(ctx context.Context, id int, enabled bool) error { func (s *service) ToggleEnabled(ctx context.Context, id int, enabled bool) error {
return s.toggleEnabled(ctx, id, enabled) err := s.repo.ToggleEnabled(ctx, id, enabled)
if err != nil {
s.log.Error().Err(err).Msgf("could not toggle feed by id: %v", id)
return err
}
return nil
} }
func (s *service) update(ctx context.Context, feed *domain.Feed) error { func (s *service) update(ctx context.Context, feed *domain.Feed) error {
@ -155,7 +193,7 @@ func (s *service) Start() error {
// get all torznab indexer definitions // get all torznab indexer definitions
feeds, err := s.repo.Find(context.TODO()) feeds, err := s.repo.Find(context.TODO())
if err != nil { if err != nil {
s.log.Error().Err(err).Msg("feed.Start: error getting feeds") s.log.Error().Err(err).Msg("feed.Start: error finding feeds")
return err return err
} }
@ -241,20 +279,12 @@ func (s *service) addTorznabJob(f feedInstance) error {
c := torznab.NewClient(f.URL, f.ApiKey) c := torznab.NewClient(f.URL, f.ApiKey)
// create job // create job
job := &TorznabJob{ job := NewTorznabJob(f.Name, f.IndexerIdentifier, l, f.URL, c, s.cacheRepo, s.releaseSvc)
Name: f.Name,
IndexerIdentifier: f.IndexerIdentifier,
Client: c,
Log: l,
Repo: s.cacheRepo,
ReleaseSvc: s.releaseSvc,
URL: f.URL,
}
// schedule job // schedule job
id, err := s.scheduler.AddJob(job, f.CronSchedule, f.IndexerIdentifier) id, err := s.scheduler.AddJob(job, f.CronSchedule, f.IndexerIdentifier)
if err != nil { if err != nil {
return fmt.Errorf("feed.AddTorznabJob: add job failed: %w", err) return errors.Wrap(err, "feed.AddTorznabJob: add job failed")
} }
job.JobID = id job.JobID = id
@ -269,7 +299,7 @@ func (s *service) addTorznabJob(f feedInstance) error {
func (s *service) stopTorznabJob(indexer string) error { func (s *service) stopTorznabJob(indexer string) error {
// remove job from scheduler // remove job from scheduler
if err := s.scheduler.RemoveJobByIdentifier(indexer); err != nil { if err := s.scheduler.RemoveJobByIdentifier(indexer); err != nil {
return fmt.Errorf("feed.stopTorznabJob: stop job failed: %w", err) return errors.Wrap(err, "feed.stopTorznabJob: stop job failed")
} }
s.log.Debug().Msgf("feed.stopTorznabJob: %v", indexer) s.log.Debug().Msgf("feed.stopTorznabJob: %v", indexer)

View file

@ -1,15 +1,15 @@
package feed package feed
import ( import (
"fmt"
"sort" "sort"
"time" "time"
"github.com/rs/zerolog"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/release" "github.com/autobrr/autobrr/internal/release"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/autobrr/autobrr/pkg/torznab" "github.com/autobrr/autobrr/pkg/torznab"
"github.com/rs/zerolog"
) )
type TorznabJob struct { type TorznabJob struct {
@ -27,6 +27,18 @@ type TorznabJob struct {
JobID int JobID int
} }
func NewTorznabJob(name string, indexerIdentifier string, log zerolog.Logger, url string, client *torznab.Client, repo domain.FeedCacheRepo, releaseSvc release.Service) *TorznabJob {
return &TorznabJob{
Name: name,
IndexerIdentifier: indexerIdentifier,
Log: log,
URL: url,
Client: client,
Repo: repo,
ReleaseSvc: releaseSvc,
}
}
func (j *TorznabJob) Run() { func (j *TorznabJob) Run() {
err := j.process() err := j.process()
if err != nil { if err != nil {
@ -44,7 +56,7 @@ func (j *TorznabJob) process() error {
items, err := j.getFeed() items, err := j.getFeed()
if err != nil { if err != nil {
j.Log.Error().Err(err).Msgf("torznab.process: error fetching feed items") j.Log.Error().Err(err).Msgf("torznab.process: error fetching feed items")
return fmt.Errorf("torznab.process: error getting feed items: %w", err) return errors.Wrap(err, "torznab.process: error getting feed items")
} }
if len(items) == 0 { if len(items) == 0 {
@ -82,7 +94,7 @@ func (j *TorznabJob) getFeed() ([]torznab.FeedItem, error) {
feedItems, err := j.Client.GetFeed() feedItems, err := j.Client.GetFeed()
if err != nil { if err != nil {
j.Log.Error().Err(err).Msgf("torznab.getFeed: error fetching feed items") j.Log.Error().Err(err).Msgf("torznab.getFeed: error fetching feed items")
return nil, err return nil, errors.Wrap(err, "error fetching feed items")
} }
j.Log.Trace().Msgf("torznab getFeed: refreshing feed: %v, found (%d) items", j.Name, len(feedItems)) j.Log.Trace().Msgf("torznab getFeed: refreshing feed: %v, found (%d) items", j.Name, len(feedItems))

View file

@ -10,6 +10,7 @@ import (
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/rs/zerolog"
) )
type Service interface { type Service interface {
@ -25,7 +26,7 @@ type Service interface {
} }
type service struct { type service struct {
log logger.Logger log zerolog.Logger
repo domain.FilterRepo repo domain.FilterRepo
actionRepo domain.ActionRepo actionRepo domain.ActionRepo
indexerSvc indexer.Service indexerSvc indexer.Service
@ -34,7 +35,7 @@ type service struct {
func NewService(log logger.Logger, repo domain.FilterRepo, actionRepo domain.ActionRepo, apiService indexer.APIService, indexerSvc indexer.Service) Service { func NewService(log logger.Logger, repo domain.FilterRepo, actionRepo domain.ActionRepo, apiService indexer.APIService, indexerSvc indexer.Service) Service {
return &service{ return &service{
log: log, log: log.With().Str("module", "filter").Logger(),
repo: repo, repo: repo,
actionRepo: actionRepo, actionRepo: actionRepo,
apiService: apiService, apiService: apiService,
@ -46,6 +47,7 @@ func (s *service) ListFilters(ctx context.Context) ([]domain.Filter, error) {
// get filters // get filters
filters, err := s.repo.ListFilters(ctx) filters, err := s.repo.ListFilters(ctx)
if err != nil { if err != nil {
s.log.Error().Err(err).Msgf("could not find list filters")
return nil, err return nil, err
} }
@ -238,7 +240,7 @@ func (s *service) CheckFilter(f domain.Filter, release *domain.Release) (bool, e
rejections, matchedFilter := f.CheckFilter(release) rejections, matchedFilter := f.CheckFilter(release)
if len(rejections) > 0 { if len(rejections) > 0 {
s.log.Trace().Msgf("filter.Service.CheckFilter: (%v) for release: %v rejections: (%v)", f.Name, release.TorrentName, release.RejectionsString()) s.log.Debug().Msgf("filter.Service.CheckFilter: (%v) for release: %v rejections: (%v)", f.Name, release.TorrentName, release.RejectionsString())
return false, nil return false, nil
} }

View file

@ -1,12 +1,13 @@
package indexer package indexer
import ( import (
"fmt" "github.com/rs/zerolog"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/internal/mock" "github.com/autobrr/autobrr/internal/mock"
"github.com/autobrr/autobrr/pkg/btn" "github.com/autobrr/autobrr/pkg/btn"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/autobrr/autobrr/pkg/ggn" "github.com/autobrr/autobrr/pkg/ggn"
"github.com/autobrr/autobrr/pkg/ptp" "github.com/autobrr/autobrr/pkg/ptp"
"github.com/autobrr/autobrr/pkg/red" "github.com/autobrr/autobrr/pkg/red"
@ -25,13 +26,13 @@ type apiClient interface {
} }
type apiService struct { type apiService struct {
log logger.Logger log zerolog.Logger
apiClients map[string]apiClient apiClients map[string]apiClient
} }
func NewAPIService(log logger.Logger) APIService { func NewAPIService(log logger.Logger) APIService {
return &apiService{ return &apiService{
log: log, log: log.With().Str("module", "indexer-api").Logger(),
apiClients: make(map[string]apiClient), apiClients: make(map[string]apiClient),
} }
} }
@ -42,7 +43,7 @@ func (s *apiService) GetTorrentByID(indexer string, torrentID string) (*domain.T
return nil, nil return nil, nil
} }
s.log.Trace().Str("service", "api").Str("method", "GetTorrentByID").Msgf("'%v' trying to fetch torrent from api", indexer) s.log.Trace().Str("method", "GetTorrentByID").Msgf("'%v' trying to fetch torrent from api", indexer)
t, err := v.GetTorrentByID(torrentID) t, err := v.GetTorrentByID(torrentID)
if err != nil { if err != nil {
@ -50,7 +51,7 @@ func (s *apiService) GetTorrentByID(indexer string, torrentID string) (*domain.T
return nil, err return nil, err
} }
s.log.Trace().Str("service", "api").Str("method", "GetTorrentByID").Msgf("'%v' successfully fetched torrent from api: %+v", indexer, t) s.log.Trace().Str("method", "GetTorrentByID").Msgf("'%v' successfully fetched torrent from api: %+v", indexer, t)
return t, nil return t, nil
} }
@ -63,6 +64,7 @@ func (s *apiService) TestConnection(indexer string) (bool, error) {
t, err := v.TestAPI() t, err := v.TestAPI()
if err != nil { if err != nil {
s.log.Error().Err(err).Msgf("error testing connection for api: %v", indexer)
return false, err return false, err
} }
@ -72,9 +74,9 @@ func (s *apiService) TestConnection(indexer string) (bool, error) {
func (s *apiService) AddClient(indexer string, settings map[string]string) error { func (s *apiService) AddClient(indexer string, settings map[string]string) error {
// basic validation // basic validation
if indexer == "" { if indexer == "" {
return fmt.Errorf("api.Service.AddClient: validation falied: indexer can't be empty") return errors.New("api.Service.AddClient: validation falied: indexer can't be empty")
} else if len(settings) == 0 { } else if len(settings) == 0 {
return fmt.Errorf("api.Service.AddClient: validation falied: settings can't be empty") return errors.New("api.Service.AddClient: validation falied: settings can't be empty")
} }
s.log.Trace().Msgf("api.Service.AddClient: init api client for '%v'", indexer) s.log.Trace().Msgf("api.Service.AddClient: init api client for '%v'", indexer)
@ -84,33 +86,33 @@ func (s *apiService) AddClient(indexer string, settings map[string]string) error
case "btn": case "btn":
key, ok := settings["api_key"] key, ok := settings["api_key"]
if !ok || key == "" { if !ok || key == "" {
return fmt.Errorf("api.Service.AddClient: could not initialize btn client: missing var 'api_key'") return errors.New("api.Service.AddClient: could not initialize btn client: missing var 'api_key'")
} }
s.apiClients[indexer] = btn.NewClient("", key) s.apiClients[indexer] = btn.NewClient("", key)
case "ptp": case "ptp":
user, ok := settings["api_user"] user, ok := settings["api_user"]
if !ok || user == "" { if !ok || user == "" {
return fmt.Errorf("api.Service.AddClient: could not initialize ptp client: missing var 'api_user'") return errors.New("api.Service.AddClient: could not initialize ptp client: missing var 'api_user'")
} }
key, ok := settings["api_key"] key, ok := settings["api_key"]
if !ok || key == "" { if !ok || key == "" {
return fmt.Errorf("api.Service.AddClient: could not initialize ptp client: missing var 'api_key'") return errors.New("api.Service.AddClient: could not initialize ptp client: missing var 'api_key'")
} }
s.apiClients[indexer] = ptp.NewClient("", user, key) s.apiClients[indexer] = ptp.NewClient("", user, key)
case "ggn": case "ggn":
key, ok := settings["api_key"] key, ok := settings["api_key"]
if !ok || key == "" { if !ok || key == "" {
return fmt.Errorf("api.Service.AddClient: could not initialize ggn client: missing var 'api_key'") return errors.New("api.Service.AddClient: could not initialize ggn client: missing var 'api_key'")
} }
s.apiClients[indexer] = ggn.NewClient("", key) s.apiClients[indexer] = ggn.NewClient("", key)
case "redacted": case "redacted":
key, ok := settings["api_key"] key, ok := settings["api_key"]
if !ok || key == "" { if !ok || key == "" {
return fmt.Errorf("api.Service.AddClient: could not initialize red client: missing var 'api_key'") return errors.New("api.Service.AddClient: could not initialize red client: missing var 'api_key'")
} }
s.apiClients[indexer] = red.NewClient("", key) s.apiClients[indexer] = red.NewClient("", key)
@ -118,7 +120,7 @@ func (s *apiService) AddClient(indexer string, settings map[string]string) error
s.apiClients[indexer] = mock.NewMockClient("", "mock") s.apiClients[indexer] = mock.NewMockClient("", "mock")
default: default:
return fmt.Errorf("api.Service.AddClient: could not initialize client: unsupported indexer '%v'", indexer) return errors.New("api.Service.AddClient: could not initialize client: unsupported indexer '%v'", indexer)
} }
return nil return nil

View file

@ -2,7 +2,6 @@ package indexer
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"io/fs" "io/fs"
"os" "os"
@ -13,6 +12,7 @@ import (
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/internal/scheduler" "github.com/autobrr/autobrr/internal/scheduler"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/gosimple/slug" "github.com/gosimple/slug"
"github.com/rs/zerolog" "github.com/rs/zerolog"
@ -52,7 +52,7 @@ type service struct {
func NewService(log logger.Logger, config *domain.Config, repo domain.IndexerRepo, apiService APIService, scheduler scheduler.Service) Service { func NewService(log logger.Logger, config *domain.Config, repo domain.IndexerRepo, apiService APIService, scheduler scheduler.Service) Service {
return &service{ return &service{
log: log.With().Str("service", "indexer").Logger(), log: log.With().Str("module", "indexer").Logger(),
config: config, config: config,
repo: repo, repo: repo,
apiService: apiService, apiService: apiService,
@ -94,6 +94,7 @@ func (s *service) Store(ctx context.Context, indexer domain.Indexer) (*domain.In
func (s *service) Update(ctx context.Context, indexer domain.Indexer) (*domain.Indexer, error) { func (s *service) Update(ctx context.Context, indexer domain.Indexer) (*domain.Indexer, error) {
i, err := s.repo.Update(ctx, indexer) i, err := s.repo.Update(ctx, indexer)
if err != nil { if err != nil {
s.log.Error().Err(err).Msgf("could not update indexer: %+v", indexer)
return nil, err return nil, err
} }
@ -115,6 +116,7 @@ func (s *service) Update(ctx context.Context, indexer domain.Indexer) (*domain.I
func (s *service) Delete(ctx context.Context, id int) error { func (s *service) Delete(ctx context.Context, id int) error {
if err := s.repo.Delete(ctx, id); err != nil { if err := s.repo.Delete(ctx, id); err != nil {
s.log.Error().Err(err).Msgf("could not delete indexer by id: %v", id)
return err return err
} }
@ -125,11 +127,23 @@ func (s *service) Delete(ctx context.Context, id int) error {
} }
func (s *service) FindByFilterID(ctx context.Context, id int) ([]domain.Indexer, error) { func (s *service) FindByFilterID(ctx context.Context, id int) ([]domain.Indexer, error) {
return s.repo.FindByFilterID(ctx, id) indexers, err := s.repo.FindByFilterID(ctx, id)
if err != nil {
s.log.Error().Err(err).Msgf("could not find indexers by filter id: %v", id)
return nil, err
}
return indexers, err
} }
func (s *service) List(ctx context.Context) ([]domain.Indexer, error) { func (s *service) List(ctx context.Context) ([]domain.Indexer, error) {
return s.repo.List(ctx) indexers, err := s.repo.List(ctx)
if err != nil {
s.log.Error().Err(err).Msg("could not get indexer list")
return nil, err
}
return indexers, err
} }
func (s *service) GetAll() ([]*domain.IndexerDefinition, error) { func (s *service) GetAll() ([]*domain.IndexerDefinition, error) {
@ -154,6 +168,7 @@ func (s *service) GetAll() ([]*domain.IndexerDefinition, error) {
func (s *service) mapIndexers() (map[string]*domain.IndexerDefinition, error) { func (s *service) mapIndexers() (map[string]*domain.IndexerDefinition, error) {
indexers, err := s.repo.List(context.Background()) indexers, err := s.repo.List(context.Background())
if err != nil { if err != nil {
s.log.Error().Err(err).Msg("could not read indexer list")
return nil, err return nil, err
} }
@ -264,6 +279,7 @@ func (s *service) Start() error {
// load all indexer definitions // load all indexer definitions
err := s.LoadIndexerDefinitions() err := s.LoadIndexerDefinitions()
if err != nil { if err != nil {
s.log.Error().Err(err).Msg("could not load indexer definitions")
return err return err
} }
@ -271,7 +287,7 @@ func (s *service) Start() error {
// load custom indexer definitions // load custom indexer definitions
err = s.LoadCustomIndexerDefinitions() err = s.LoadCustomIndexerDefinitions()
if err != nil { if err != nil {
return fmt.Errorf("could not load custom indexer definitions: %w", err) return errors.Wrap(err, "could not load custom indexer definitions")
} }
} }
@ -394,12 +410,12 @@ func (s *service) mapIRCServerDefinitionLookup(ircServer string, indexerDefiniti
func (s *service) LoadIndexerDefinitions() error { func (s *service) LoadIndexerDefinitions() error {
entries, err := fs.ReadDir(Definitions, "definitions") entries, err := fs.ReadDir(Definitions, "definitions")
if err != nil { if err != nil {
s.log.Fatal().Stack().Msgf("failed reading directory: %s", err) s.log.Fatal().Err(err).Stack().Msg("failed reading directory")
} }
if len(entries) == 0 { if len(entries) == 0 {
s.log.Fatal().Stack().Msgf("failed reading directory: %s", err) s.log.Fatal().Err(err).Stack().Msg("failed reading directory")
return err return errors.Wrap(err, "could not read directory")
} }
for _, f := range entries { for _, f := range entries {
@ -417,13 +433,13 @@ func (s *service) LoadIndexerDefinitions() error {
data, err := fs.ReadFile(Definitions, file) data, err := fs.ReadFile(Definitions, file)
if err != nil { if err != nil {
s.log.Error().Stack().Err(err).Msgf("failed reading file: %v", file) s.log.Error().Stack().Err(err).Msgf("failed reading file: %v", file)
return err return errors.Wrap(err, "could not read file: %v", file)
} }
err = yaml.Unmarshal(data, &d) err = yaml.Unmarshal(data, &d)
if err != nil { if err != nil {
s.log.Error().Stack().Err(err).Msgf("failed unmarshal file: %v", file) s.log.Error().Stack().Err(err).Msgf("failed unmarshal file: %v", file)
return err return errors.Wrap(err, "could not unmarshal file: %v", file)
} }
if d.Implementation == "" { if d.Implementation == "" {
@ -454,7 +470,8 @@ func (s *service) LoadCustomIndexerDefinitions() error {
entries, err := outputDirRead.ReadDir(0) entries, err := outputDirRead.ReadDir(0)
if err != nil { if err != nil {
s.log.Fatal().Stack().Msgf("failed reading directory: %s", err) s.log.Fatal().Err(err).Stack().Msg("failed reading directory")
return errors.Wrap(err, "could not read directory")
} }
customCount := 0 customCount := 0
@ -474,13 +491,13 @@ func (s *service) LoadCustomIndexerDefinitions() error {
data, err := os.ReadFile(file) data, err := os.ReadFile(file)
if err != nil { if err != nil {
s.log.Error().Stack().Err(err).Msgf("failed reading file: %v", file) s.log.Error().Stack().Err(err).Msgf("failed reading file: %v", file)
return err return errors.Wrap(err, "could not read file: %v", file)
} }
var d *domain.IndexerDefinition var d *domain.IndexerDefinition
if err = yaml.Unmarshal(data, &d); err != nil { if err = yaml.Unmarshal(data, &d); err != nil {
s.log.Error().Stack().Err(err).Msgf("failed unmarshal file: %v", file) s.log.Error().Stack().Err(err).Msgf("failed unmarshal file: %v", file)
return err return errors.Wrap(err, "could not unmarshal file: %v", file)
} }
if d == nil { if d == nil {

View file

@ -10,9 +10,9 @@ import (
"github.com/autobrr/autobrr/internal/announce" "github.com/autobrr/autobrr/internal/announce"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/internal/notification" "github.com/autobrr/autobrr/internal/notification"
"github.com/autobrr/autobrr/internal/release" "github.com/autobrr/autobrr/internal/release"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/avast/retry-go" "github.com/avast/retry-go"
"github.com/dcarbone/zadapters/zstdlog" "github.com/dcarbone/zadapters/zstdlog"
@ -80,7 +80,7 @@ type Handler struct {
authenticated bool authenticated bool
} }
func NewHandler(log logger.Logger, network domain.IrcNetwork, definitions []*domain.IndexerDefinition, releaseSvc release.Service, notificationSvc notification.Service) *Handler { func NewHandler(log zerolog.Logger, network domain.IrcNetwork, definitions []*domain.IndexerDefinition, releaseSvc release.Service, notificationSvc notification.Service) *Handler {
h := &Handler{ h := &Handler{
log: log.With().Str("network", network.Server).Logger(), log: log.With().Str("network", network.Server).Logger(),
client: nil, client: nil,
@ -550,7 +550,7 @@ func (h *Handler) sendToAnnounceProcessor(channel string, msg string) error {
// check if queue exists // check if queue exists
queue, ok := h.announceProcessors[channel] queue, ok := h.announceProcessors[channel]
if !ok { if !ok {
return fmt.Errorf("queue '%v' not found", channel) return errors.New("queue '%v' not found", channel)
} }
// if it exists, add msg // if it exists, add msg

View file

@ -2,7 +2,6 @@ package irc
import ( import (
"context" "context"
"fmt"
"strings" "strings"
"sync" "sync"
@ -11,8 +10,9 @@ import (
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/internal/notification" "github.com/autobrr/autobrr/internal/notification"
"github.com/autobrr/autobrr/internal/release" "github.com/autobrr/autobrr/internal/release"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/pkg/errors" "github.com/rs/zerolog"
) )
type Service interface { type Service interface {
@ -29,7 +29,7 @@ type Service interface {
} }
type service struct { type service struct {
log logger.Logger log zerolog.Logger
repo domain.IrcRepo repo domain.IrcRepo
releaseService release.Service releaseService release.Service
indexerService indexer.Service indexerService indexer.Service
@ -43,7 +43,7 @@ type service struct {
func NewService(log logger.Logger, repo domain.IrcRepo, releaseSvc release.Service, indexerSvc indexer.Service, notificationSvc notification.Service) Service { func NewService(log logger.Logger, repo domain.IrcRepo, releaseSvc release.Service, indexerSvc indexer.Service, notificationSvc notification.Service) Service {
return &service{ return &service{
log: log, log: log.With().Str("module", "irc").Logger(),
repo: repo, repo: repo,
releaseService: releaseSvc, releaseService: releaseSvc,
indexerService: indexerSvc, indexerService: indexerSvc,
@ -60,7 +60,7 @@ type handlerKey struct {
func (s *service) StartHandlers() { func (s *service) StartHandlers() {
networks, err := s.repo.FindActiveNetworks(context.Background()) networks, err := s.repo.FindActiveNetworks(context.Background())
if err != nil { if err != nil {
s.log.Error().Msgf("failed to list networks: %v", err) s.log.Error().Err(err).Msg("failed to list networks")
} }
for _, network := range networks { for _, network := range networks {
@ -356,7 +356,7 @@ func (s *service) GetNetworkByID(ctx context.Context, id int64) (*domain.IrcNetw
func (s *service) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error) { func (s *service) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error) {
networks, err := s.repo.ListNetworks(ctx) networks, err := s.repo.ListNetworks(ctx)
if err != nil { if err != nil {
s.log.Error().Err(err).Msgf("failed to list networks: %v", err) s.log.Error().Err(err).Msg("failed to list networks")
return nil, err return nil, err
} }
@ -379,7 +379,7 @@ func (s *service) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error)
func (s *service) GetNetworksWithHealth(ctx context.Context) ([]domain.IrcNetworkWithHealth, error) { func (s *service) GetNetworksWithHealth(ctx context.Context) ([]domain.IrcNetworkWithHealth, error) {
networks, err := s.repo.ListNetworks(ctx) networks, err := s.repo.ListNetworks(ctx)
if err != nil { if err != nil {
s.log.Error().Err(err).Msgf("failed to list networks: %v", err) s.log.Error().Err(err).Msg("failed to list networks")
return nil, err return nil, err
} }
@ -469,6 +469,7 @@ func (s *service) GetNetworksWithHealth(ctx context.Context) ([]domain.IrcNetwor
func (s *service) DeleteNetwork(ctx context.Context, id int64) error { func (s *service) DeleteNetwork(ctx context.Context, id int64) error {
network, err := s.GetNetworkByID(ctx, id) network, err := s.GetNetworkByID(ctx, id)
if err != nil { if err != nil {
s.log.Error().Stack().Err(err).Msgf("could not find network before delete: %v", network.Name)
return err return err
} }
@ -477,10 +478,12 @@ func (s *service) DeleteNetwork(ctx context.Context, id int64) error {
// Remove network and handler // Remove network and handler
//if err = s.StopNetwork(network.Server); err != nil { //if err = s.StopNetwork(network.Server); err != nil {
if err = s.StopAndRemoveNetwork(handlerKey{network.Server, network.NickServ.Account}); err != nil { if err = s.StopAndRemoveNetwork(handlerKey{network.Server, network.NickServ.Account}); err != nil {
s.log.Error().Stack().Err(err).Msgf("could not stop and delete network: %v", network.Name)
return err return err
} }
if err = s.repo.DeleteNetwork(ctx, id); err != nil { if err = s.repo.DeleteNetwork(ctx, id); err != nil {
s.log.Error().Stack().Err(err).Msgf("could not delete network: %v", network.Name)
return err return err
} }
@ -509,7 +512,7 @@ func (s *service) UpdateNetwork(ctx context.Context, network *domain.IrcNetwork)
err := s.checkIfNetworkRestartNeeded(network) err := s.checkIfNetworkRestartNeeded(network)
if err != nil { if err != nil {
s.log.Error().Stack().Err(err).Msgf("could not restart network: %+v", network.Name) s.log.Error().Stack().Err(err).Msgf("could not restart network: %+v", network.Name)
return fmt.Errorf("could not restart network: %v", network.Name) return errors.New("could not restart network: %v", network.Name)
} }
} else { } else {
@ -517,7 +520,7 @@ func (s *service) UpdateNetwork(ctx context.Context, network *domain.IrcNetwork)
err := s.StopAndRemoveNetwork(handlerKey{network.Server, network.NickServ.Account}) err := s.StopAndRemoveNetwork(handlerKey{network.Server, network.NickServ.Account})
if err != nil { if err != nil {
s.log.Error().Stack().Err(err).Msgf("could not stop network: %+v", network.Name) s.log.Error().Stack().Err(err).Msgf("could not stop network: %+v", network.Name)
return fmt.Errorf("could not stop network: %v", network.Name) return errors.New("could not stop network: %v", network.Name)
} }
} }
@ -585,7 +588,7 @@ func (s *service) StoreNetwork(ctx context.Context, network *domain.IrcNetwork)
err := s.checkIfNetworkRestartNeeded(existingNetwork) err := s.checkIfNetworkRestartNeeded(existingNetwork)
if err != nil { if err != nil {
s.log.Error().Err(err).Msgf("could not restart network: %+v", existingNetwork.Name) s.log.Error().Err(err).Msgf("could not restart network: %+v", existingNetwork.Name)
return fmt.Errorf("could not restart network: %v", existingNetwork.Name) return errors.New("could not restart network: %v", existingNetwork.Name)
} }
} }

View file

@ -1,9 +1,8 @@
package mock package mock
import ( import (
"fmt"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/pkg/errors"
) )
type IndexerApiClient interface { type IndexerApiClient interface {
@ -27,7 +26,7 @@ func NewMockClient(url string, apiKey string) IndexerApiClient {
func (c *IndexerClient) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) { func (c *IndexerClient) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) {
if torrentID == "" { if torrentID == "" {
return nil, fmt.Errorf("mock client: must have torrentID") return nil, errors.New("mock client: must have torrentID")
} }
r := &domain.TorrentBasic{ r := &domain.TorrentBasic{

View file

@ -11,7 +11,9 @@ import (
"time" "time"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/pkg/errors"
"github.com/rs/zerolog"
) )
type DiscordMessage struct { type DiscordMessage struct {
@ -42,12 +44,15 @@ const (
) )
type discordSender struct { type discordSender struct {
log logger.Logger log zerolog.Logger
Settings domain.Notification Settings domain.Notification
} }
func NewDiscordSender(log logger.Logger, settings domain.Notification) domain.NotificationSender { func NewDiscordSender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender {
return &discordSender{log: log, Settings: settings} return &discordSender{
log: log.With().Str("sender", "discord").Logger(),
Settings: settings,
}
} }
func (a *discordSender) Send(event domain.NotificationEvent, payload domain.NotificationPayload) error { func (a *discordSender) Send(event domain.NotificationEvent, payload domain.NotificationPayload) error {
@ -59,13 +64,13 @@ func (a *discordSender) Send(event domain.NotificationEvent, payload domain.Noti
jsonData, err := json.Marshal(m) jsonData, err := json.Marshal(m)
if err != nil { if err != nil {
a.log.Error().Err(err).Msgf("discord client could not marshal data: %v", m) a.log.Error().Err(err).Msgf("discord client could not marshal data: %v", m)
return err return errors.Wrap(err, "could not marshal data: %+v", m)
} }
req, err := http.NewRequest(http.MethodPost, a.Settings.Webhook, bytes.NewBuffer(jsonData)) req, err := http.NewRequest(http.MethodPost, a.Settings.Webhook, bytes.NewBuffer(jsonData))
if err != nil { if err != nil {
a.log.Error().Err(err).Msgf("discord client request error: %v", event) a.log.Error().Err(err).Msgf("discord client request error: %v", event)
return err return errors.Wrap(err, "could not create request")
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
@ -81,22 +86,23 @@ func (a *discordSender) Send(event domain.NotificationEvent, payload domain.Noti
res, err := client.Do(req) res, err := client.Do(req)
if err != nil { if err != nil {
a.log.Error().Err(err).Msgf("discord client request error: %v", event) a.log.Error().Err(err).Msgf("discord client request error: %v", event)
return err return errors.Wrap(err, "could not make request: %+v", req)
} }
body, err := ioutil.ReadAll(res.Body) body, err := ioutil.ReadAll(res.Body)
if err != nil { if err != nil {
a.log.Error().Err(err).Msgf("discord client request error: %v", event) a.log.Error().Err(err).Msgf("discord client request error: %v", event)
return err return errors.Wrap(err, "could not read data")
} }
defer res.Body.Close() defer res.Body.Close()
a.log.Trace().Msgf("discord status: %v response: %v", res.StatusCode, string(body)) a.log.Trace().Msgf("discord status: %v response: %v", res.StatusCode, string(body))
if res.StatusCode != http.StatusNoContent { // discord responds with 204, Notifiarr with 204 so lets take all 200 as ok
if res.StatusCode >= 300 {
a.log.Error().Err(err).Msgf("discord client request error: %v", string(body)) a.log.Error().Err(err).Msgf("discord client request error: %v", string(body))
return fmt.Errorf("err: %v", string(body)) return errors.New("bad status: %v body: %v", res.StatusCode, string(body))
} }
a.log.Debug().Msg("notification successfully sent to discord") a.log.Debug().Msg("notification successfully sent to discord")

View file

@ -5,6 +5,8 @@ import (
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/rs/zerolog"
) )
type Service interface { type Service interface {
@ -18,14 +20,14 @@ type Service interface {
} }
type service struct { type service struct {
log logger.Logger log zerolog.Logger
repo domain.NotificationRepo repo domain.NotificationRepo
senders []domain.NotificationSender senders []domain.NotificationSender
} }
func NewService(log logger.Logger, repo domain.NotificationRepo) Service { func NewService(log logger.Logger, repo domain.NotificationRepo) Service {
s := &service{ s := &service{
log: log, log: log.With().Str("module", "notification").Logger(),
repo: repo, repo: repo,
senders: []domain.NotificationSender{}, senders: []domain.NotificationSender{},
} }
@ -36,16 +38,29 @@ func NewService(log logger.Logger, repo domain.NotificationRepo) Service {
} }
func (s *service) Find(ctx context.Context, params domain.NotificationQueryParams) ([]domain.Notification, int, error) { func (s *service) Find(ctx context.Context, params domain.NotificationQueryParams) ([]domain.Notification, int, error) {
return s.repo.Find(ctx, params) n, count, err := s.repo.Find(ctx, params)
if err != nil {
s.log.Error().Err(err).Msgf("could not find notification with params: %+v", params)
return nil, 0, err
}
return n, count, err
} }
func (s *service) FindByID(ctx context.Context, id int) (*domain.Notification, error) { func (s *service) FindByID(ctx context.Context, id int) (*domain.Notification, error) {
return s.repo.FindByID(ctx, id) n, err := s.repo.FindByID(ctx, id)
if err != nil {
s.log.Error().Err(err).Msgf("could not find notification by id: %v", id)
return nil, err
}
return n, err
} }
func (s *service) Store(ctx context.Context, n domain.Notification) (*domain.Notification, error) { func (s *service) Store(ctx context.Context, n domain.Notification) (*domain.Notification, error) {
_, err := s.repo.Store(ctx, n) _, err := s.repo.Store(ctx, n)
if err != nil { if err != nil {
s.log.Error().Err(err).Msgf("could not store notification: %+v", n)
return nil, err return nil, err
} }
@ -61,6 +76,7 @@ func (s *service) Store(ctx context.Context, n domain.Notification) (*domain.Not
func (s *service) Update(ctx context.Context, n domain.Notification) (*domain.Notification, error) { func (s *service) Update(ctx context.Context, n domain.Notification) (*domain.Notification, error) {
_, err := s.repo.Update(ctx, n) _, err := s.repo.Update(ctx, n)
if err != nil { if err != nil {
s.log.Error().Err(err).Msgf("could not update notification: %+v", n)
return nil, err return nil, err
} }
@ -76,6 +92,7 @@ func (s *service) Update(ctx context.Context, n domain.Notification) (*domain.No
func (s *service) Delete(ctx context.Context, id int) error { func (s *service) Delete(ctx context.Context, id int) error {
err := s.repo.Delete(ctx, id) err := s.repo.Delete(ctx, id)
if err != nil { if err != nil {
s.log.Error().Err(err).Msgf("could not delete notification: %v", id)
return err return err
} }
@ -91,6 +108,7 @@ func (s *service) Delete(ctx context.Context, id int) error {
func (s *service) registerSenders() { func (s *service) registerSenders() {
senders, err := s.repo.List(context.Background()) senders, err := s.repo.List(context.Background())
if err != nil { if err != nil {
s.log.Error().Err(err).Msg("could not find notifications")
return return
} }

View file

@ -12,7 +12,9 @@ import (
"time" "time"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/pkg/errors"
"github.com/rs/zerolog"
) )
type TelegramMessage struct { type TelegramMessage struct {
@ -22,13 +24,13 @@ type TelegramMessage struct {
} }
type telegramSender struct { type telegramSender struct {
log logger.Logger log zerolog.Logger
Settings domain.Notification Settings domain.Notification
} }
func NewTelegramSender(log logger.Logger, settings domain.Notification) domain.NotificationSender { func NewTelegramSender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender {
return &telegramSender{ return &telegramSender{
log: log, log: log.With().Str("sender", "telegram").Logger(),
Settings: settings, Settings: settings,
} }
} }
@ -44,7 +46,7 @@ func (s *telegramSender) Send(event domain.NotificationEvent, payload domain.Not
jsonData, err := json.Marshal(m) jsonData, err := json.Marshal(m)
if err != nil { if err != nil {
s.log.Error().Err(err).Msgf("telegram client could not marshal data: %v", m) s.log.Error().Err(err).Msgf("telegram client could not marshal data: %v", m)
return err return errors.Wrap(err, "could not marshal data: %+v", m)
} }
url := fmt.Sprintf("https://api.telegram.org/bot%v/sendMessage", s.Settings.Token) url := fmt.Sprintf("https://api.telegram.org/bot%v/sendMessage", s.Settings.Token)
@ -52,7 +54,7 @@ func (s *telegramSender) Send(event domain.NotificationEvent, payload domain.Not
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(jsonData)) req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(jsonData))
if err != nil { if err != nil {
s.log.Error().Err(err).Msgf("telegram client request error: %v", event) s.log.Error().Err(err).Msgf("telegram client request error: %v", event)
return err return errors.Wrap(err, "could not create request")
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
@ -68,13 +70,13 @@ func (s *telegramSender) Send(event domain.NotificationEvent, payload domain.Not
res, err := client.Do(req) res, err := client.Do(req)
if err != nil { if err != nil {
s.log.Error().Err(err).Msgf("telegram client request error: %v", event) s.log.Error().Err(err).Msgf("telegram client request error: %v", event)
return err return errors.Wrap(err, "could not make request: %+v", req)
} }
body, err := ioutil.ReadAll(res.Body) body, err := ioutil.ReadAll(res.Body)
if err != nil { if err != nil {
s.log.Error().Err(err).Msgf("telegram client request error: %v", event) s.log.Error().Err(err).Msgf("telegram client request error: %v", event)
return err return errors.Wrap(err, "could not read data")
} }
defer res.Body.Close() defer res.Body.Close()
@ -83,7 +85,7 @@ func (s *telegramSender) Send(event domain.NotificationEvent, payload domain.Not
if res.StatusCode != http.StatusOK { if res.StatusCode != http.StatusOK {
s.log.Error().Err(err).Msgf("telegram client request error: %v", string(body)) s.log.Error().Err(err).Msgf("telegram client request error: %v", string(body))
return fmt.Errorf("err: %v", string(body)) return errors.New("bad status: %v body: %v", res.StatusCode, string(body))
} }
s.log.Debug().Msg("notification successfully sent to telegram") s.log.Debug().Msg("notification successfully sent to telegram")

View file

@ -9,6 +9,8 @@ import (
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/filter" "github.com/autobrr/autobrr/internal/filter"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/rs/zerolog"
) )
type Service interface { type Service interface {
@ -30,7 +32,7 @@ type actionClientTypeKey struct {
} }
type service struct { type service struct {
log logger.Logger log zerolog.Logger
repo domain.ReleaseRepo repo domain.ReleaseRepo
actionSvc action.Service actionSvc action.Service
@ -39,7 +41,7 @@ type service struct {
func NewService(log logger.Logger, repo domain.ReleaseRepo, actionSvc action.Service, filterSvc filter.Service) Service { func NewService(log logger.Logger, repo domain.ReleaseRepo, actionSvc action.Service, filterSvc filter.Service) Service {
return &service{ return &service{
log: log, log: log.With().Str("module", "release").Logger(),
repo: repo, repo: repo,
actionSvc: actionSvc, actionSvc: actionSvc,
filterSvc: filterSvc, filterSvc: filterSvc,
@ -91,7 +93,7 @@ func (s *service) Process(release *domain.Release) {
// get filters by priority // get filters by priority
filters, err := s.filterSvc.FindByIndexerIdentifier(release.Indexer) filters, err := s.filterSvc.FindByIndexerIdentifier(release.Indexer)
if err != nil { if err != nil {
s.log.Error().Err(err).Msgf("announce.Service.Process: error finding filters for indexer: %v", release.Indexer) s.log.Error().Err(err).Msgf("release.Process: error finding filters for indexer: %v", release.Indexer)
return return
} }
@ -105,6 +107,8 @@ func (s *service) Process(release *domain.Release) {
// loop over and check filters // loop over and check filters
for _, f := range filters { for _, f := range filters {
l := s.log.With().Str("indexer", release.Indexer).Str("filter", f.Name).Str("release", release.TorrentName).Logger()
// save filter on release // save filter on release
release.Filter = &f release.Filter = &f
release.FilterName = f.Name release.FilterName = f.Name
@ -115,23 +119,25 @@ func (s *service) Process(release *domain.Release) {
// test filter // test filter
match, err := s.filterSvc.CheckFilter(f, release) match, err := s.filterSvc.CheckFilter(f, release)
if err != nil { if err != nil {
s.log.Error().Err(err).Msg("announce.Service.Process: could not find filter") l.Error().Err(err).Msg("release.Process: error checking filter")
return return
} }
if !match { if !match {
s.log.Trace().Msgf("announce.Service.Process: indexer: %v, filter: %v release: %v, no match", release.Indexer, release.Filter.Name, release.TorrentName) l.Trace().Msgf("release.Process: indexer: %v, filter: %v release: %v, no match. rejections: %v", release.Indexer, release.Filter.Name, release.TorrentName, release.RejectionsString())
l.Debug().Msgf("release rejected: %v", release.RejectionsString())
continue continue
} }
s.log.Info().Msgf("Matched '%v' (%v) for %v", release.TorrentName, release.Filter.Name, release.Indexer) l.Info().Msgf("Matched '%v' (%v) for %v", release.TorrentName, release.Filter.Name, release.Indexer)
// save release here to only save those with rejections from actions instead of all releases // save release here to only save those with rejections from actions instead of all releases
if release.ID == 0 { if release.ID == 0 {
release.FilterStatus = domain.ReleaseStatusFilterApproved release.FilterStatus = domain.ReleaseStatusFilterApproved
err = s.Store(context.Background(), release) err = s.Store(context.Background(), release)
if err != nil { if err != nil {
s.log.Error().Err(err).Msgf("announce.Service.Process: error writing release to database: %+v", release) l.Error().Err(err).Msgf("release.Process: error writing release to database: %+v", release)
return return
} }
} }
@ -139,7 +145,7 @@ func (s *service) Process(release *domain.Release) {
// sleep for the delay period specified in the filter before running actions // sleep for the delay period specified in the filter before running actions
delay := release.Filter.Delay delay := release.Filter.Delay
if delay > 0 { if delay > 0 {
s.log.Debug().Msgf("Delaying processing of '%v' (%v) for %v by %d seconds as specified in the filter", release.TorrentName, release.Filter.Name, release.Indexer, delay) l.Debug().Msgf("Delaying processing of '%v' (%v) for %v by %d seconds as specified in the filter", release.TorrentName, release.Filter.Name, release.Indexer, delay)
time.Sleep(time.Duration(delay) * time.Second) time.Sleep(time.Duration(delay) * time.Second)
} }
@ -149,22 +155,22 @@ func (s *service) Process(release *domain.Release) {
for _, a := range release.Filter.Actions { for _, a := range release.Filter.Actions {
// only run enabled actions // only run enabled actions
if !a.Enabled { if !a.Enabled {
s.log.Trace().Msgf("announce.Service.Process: indexer: %v, filter: %v release: %v action '%v' not enabled, skip", release.Indexer, release.Filter.Name, release.TorrentName, a.Name) l.Trace().Msgf("release.Process: indexer: %v, filter: %v release: %v action '%v' not enabled, skip", release.Indexer, release.Filter.Name, release.TorrentName, a.Name)
continue continue
} }
s.log.Trace().Msgf("announce.Service.Process: indexer: %v, filter: %v release: %v , run action: %v", release.Indexer, release.Filter.Name, release.TorrentName, a.Name) l.Trace().Msgf("release.Process: indexer: %v, filter: %v release: %v , run action: %v", release.Indexer, release.Filter.Name, release.TorrentName, a.Name)
// keep track of action clients to avoid sending the same thing all over again // keep track of action clients to avoid sending the same thing all over again
_, tried := triedActionClients[actionClientTypeKey{Type: a.Type, ClientID: a.ClientID}] _, tried := triedActionClients[actionClientTypeKey{Type: a.Type, ClientID: a.ClientID}]
if tried { if tried {
s.log.Trace().Msgf("announce.Service.Process: indexer: %v, filter: %v release: %v action client already tried, skip", release.Indexer, release.Filter.Name, release.TorrentName) l.Trace().Msgf("release.Process: indexer: %v, filter: %v release: %v action client already tried, skip", release.Indexer, release.Filter.Name, release.TorrentName)
continue continue
} }
rejections, err = s.actionSvc.RunAction(a, *release) rejections, err = s.actionSvc.RunAction(a, *release)
if err != nil { if err != nil {
s.log.Error().Stack().Err(err).Msgf("announce.Service.Process: error running actions for filter: %v", release.Filter.Name) l.Error().Stack().Err(err).Msgf("release.Process: error running actions for filter: %v", release.Filter.Name)
continue continue
} }
@ -173,7 +179,7 @@ func (s *service) Process(release *domain.Release) {
triedActionClients[actionClientTypeKey{Type: a.Type, ClientID: a.ClientID}] = struct{}{} triedActionClients[actionClientTypeKey{Type: a.Type, ClientID: a.ClientID}] = struct{}{}
// log something and fire events // log something and fire events
s.log.Debug().Msgf("announce.Service.Process: indexer: %v, filter: %v release: %v, rejected: %v", release.Indexer, release.Filter.Name, release.TorrentName, strings.Join(rejections, ", ")) l.Debug().Str("action", a.Name).Str("action_type", string(a.Type)).Msgf("release rejected: %v", strings.Join(rejections, ", "))
} }
// if no rejections consider action approved, run next // if no rejections consider action approved, run next

View file

@ -1,11 +1,11 @@
package scheduler package scheduler
import ( import (
"fmt"
"github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
"github.com/rs/zerolog"
) )
type Service interface { type Service interface {
@ -17,7 +17,7 @@ type Service interface {
} }
type service struct { type service struct {
log logger.Logger log zerolog.Logger
cron *cron.Cron cron *cron.Cron
jobs map[string]cron.EntryID jobs map[string]cron.EntryID
@ -25,7 +25,7 @@ type service struct {
func NewService(log logger.Logger) Service { func NewService(log logger.Logger) Service {
return &service{ return &service{
log: log, log: log.With().Str("module", "scheduler").Logger(),
cron: cron.New(cron.WithChain( cron: cron.New(cron.WithChain(
cron.Recover(cron.DefaultLogger), cron.Recover(cron.DefaultLogger),
)), )),
@ -51,7 +51,7 @@ func (s *service) AddJob(job cron.Job, interval string, identifier string) (int,
cron.SkipIfStillRunning(cron.DiscardLogger)).Then(job), cron.SkipIfStillRunning(cron.DiscardLogger)).Then(job),
) )
if err != nil { if err != nil {
return 0, fmt.Errorf("scheduler: add job failed: %w", err) return 0, errors.Wrap(err, "scheduler: add job failed")
} }
s.log.Debug().Msgf("scheduler.AddJob: job successfully added: %v", id) s.log.Debug().Msgf("scheduler.AddJob: job successfully added: %v", id)

View file

@ -3,6 +3,8 @@ package server
import ( import (
"sync" "sync"
"github.com/rs/zerolog"
"github.com/autobrr/autobrr/internal/feed" "github.com/autobrr/autobrr/internal/feed"
"github.com/autobrr/autobrr/internal/indexer" "github.com/autobrr/autobrr/internal/indexer"
"github.com/autobrr/autobrr/internal/irc" "github.com/autobrr/autobrr/internal/irc"
@ -11,7 +13,7 @@ import (
) )
type Server struct { type Server struct {
log logger.Logger log zerolog.Logger
Hostname string Hostname string
Port int Port int
@ -26,7 +28,7 @@ type Server struct {
func NewServer(log logger.Logger, ircSvc irc.Service, indexerSvc indexer.Service, feedSvc feed.Service, scheduler scheduler.Service) *Server { func NewServer(log logger.Logger, ircSvc irc.Service, indexerSvc indexer.Service, feedSvc feed.Service, scheduler scheduler.Service) *Server {
return &Server{ return &Server{
log: log, log: log.With().Str("module", "server").Logger(),
indexerService: indexerSvc, indexerService: indexerSvc,
ircService: ircSvc, ircService: ircSvc,
feedService: feedSvc, feedService: feedSvc,

View file

@ -2,9 +2,9 @@ package user
import ( import (
"context" "context"
"errors"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/pkg/errors"
) )
type Service interface { type Service interface {

View file

@ -1,21 +1,20 @@
package btn package btn
import ( import (
"fmt"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/pkg/errors"
) )
func (c *Client) TestAPI() (bool, error) { func (c *Client) TestAPI() (bool, error) {
res, err := c.rpcClient.Call("userInfo", [2]string{c.APIKey}) res, err := c.rpcClient.Call("userInfo", [2]string{c.APIKey})
if err != nil { if err != nil {
return false, err return false, errors.Wrap(err, "test api userInfo failed")
} }
var u *UserInfo var u *UserInfo
err = res.GetObject(&u) err = res.GetObject(&u)
if err != nil { if err != nil {
return false, err return false, errors.Wrap(err, "test api get userInfo")
} }
if u.Username != "" { if u.Username != "" {
@ -27,12 +26,12 @@ func (c *Client) TestAPI() (bool, error) {
func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) { func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) {
if torrentID == "" { if torrentID == "" {
return nil, fmt.Errorf("btn client: must have torrentID") return nil, errors.New("btn client: must have torrentID")
} }
res, err := c.rpcClient.Call("getTorrentById", [2]string{c.APIKey, torrentID}) res, err := c.rpcClient.Call("getTorrentById", [2]string{c.APIKey, torrentID})
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "call getTorrentById failed")
} }
var r *domain.TorrentBasic var r *domain.TorrentBasic

View file

@ -2,10 +2,13 @@ package btn
import ( import (
"context" "context"
"io"
"log"
"net/http" "net/http"
"time" "time"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/autobrr/autobrr/pkg/jsonrpc" "github.com/autobrr/autobrr/pkg/jsonrpc"
"golang.org/x/time/rate" "golang.org/x/time/rate"
@ -23,6 +26,8 @@ type Client struct {
Ratelimiter *rate.Limiter Ratelimiter *rate.Limiter
APIKey string APIKey string
Headers http.Header Headers http.Header
Log *log.Logger
} }
func NewClient(url string, apiKey string) BTNClient { func NewClient(url string, apiKey string) BTNClient {
@ -41,6 +46,10 @@ func NewClient(url string, apiKey string) BTNClient {
Ratelimiter: rate.NewLimiter(rate.Every(150*time.Hour), 1), // 150 rpcRequest every 1 hour Ratelimiter: rate.NewLimiter(rate.Every(150*time.Hour), 1), // 150 rpcRequest every 1 hour
} }
if c.Log == nil {
c.Log = log.New(io.Discard, "", log.LstdFlags)
}
return c return c
} }
@ -48,11 +57,11 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) {
ctx := context.Background() ctx := context.Background()
err := c.Ratelimiter.Wait(ctx) // This is a blocking call. Honors the rate limit err := c.Ratelimiter.Wait(ctx) // This is a blocking call. Honors the rate limit
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "error waiting for ratelimiter")
} }
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "could not make request")
} }
return resp, nil return resp, nil
} }

171
pkg/errors/errors.go Normal file
View file

@ -0,0 +1,171 @@
package errors
import (
"fmt"
"reflect"
"runtime"
"unsafe"
"github.com/pkg/errors"
)
// Export a number of functions or variables from pkg/errors. We want people to be able to
// use them, if only via the entrypoints we've vetted in this file.
var (
As = errors.As
Is = errors.Is
Cause = errors.Cause
Unwrap = errors.Unwrap
)
// StackTrace should be aliases rather than newtype'd, so it can work with any of the
// functions we export from pkg/errors.
type StackTrace = errors.StackTrace
type StackTracer interface {
StackTrace() errors.StackTrace
}
// Sentinel is used to create compile-time errors that are intended to be value only, with
// no associated stack trace.
func Sentinel(msg string, args ...interface{}) error {
return fmt.Errorf(msg, args...)
}
// New acts as pkg/errors.New does, producing a stack traced error, but supports
// interpolating of message parameters. Use this when you want the stack trace to start at
// the place you create the error.
func New(msg string, args ...interface{}) error {
return PopStack(errors.New(fmt.Sprintf(msg, args...)))
}
// Wrap creates a new error from a cause, decorating the original error message with a
// prefix.
//
// It differs from the pkg/errors Wrap/Wrapf by idempotently creating a stack trace,
// meaning we won't create another stack trace when there is already a stack trace present
// that matches our current program position.
func Wrap(cause error, msg string, args ...interface{}) error {
causeStackTracer := new(StackTracer)
if errors.As(cause, causeStackTracer) {
// If our cause has set a stack trace, and that trace is a child of our own function
// as inferred by prefix matching our current program counter stack, then we only want
// to decorate the error message rather than add a redundant stack trace.
if ancestorOfCause(callers(1), (*causeStackTracer).StackTrace()) {
return errors.WithMessagef(cause, msg, args...) // no stack added, no pop required
}
}
// Otherwise we can't see a stack trace that represents ourselves, so let's add one.
return PopStack(errors.Wrapf(cause, msg, args...))
}
// ancestorOfCause returns true if the caller looks to be an ancestor of the given stack
// trace. We check this by seeing whether our stack prefix-matches the cause stack, which
// should imply the error was generated directly from our goroutine.
func ancestorOfCause(ourStack []uintptr, causeStack errors.StackTrace) bool {
// Stack traces are ordered such that the deepest frame is first. We'll want to check
// for prefix matching in reverse.
//
// As an example, imagine we have a prefix-matching stack for ourselves:
// [
// "github.com/onsi/ginkgo/internal/leafnodes.(*runner).runSync",
// "github.com/incident-io/core/server/pkg/errors_test.TestSuite",
// "testing.tRunner",
// "runtime.goexit"
// ]
//
// We'll want to compare this against an error cause that will have happened further
// down the stack. An example stack trace from such an error might be:
// [
// "github.com/incident-io/core/server/pkg/errors.New",
// "github.com/incident-io/core/server/pkg/errors_test.glob..func1.2.2.2.1",,
// "github.com/onsi/ginkgo/internal/leafnodes.(*runner).runSync",
// "github.com/incident-io/core/server/pkg/errors_test.TestSuite",
// "testing.tRunner",
// "runtime.goexit"
// ]
//
// They prefix match, but we'll have to handle the match carefully as we need to match
// from back to forward.
// We can't possibly prefix match if our stack is larger than the cause stack.
if len(ourStack) > len(causeStack) {
return false
}
// We know the sizes are compatible, so compare program counters from back to front.
for idx := 0; idx < len(ourStack); idx++ {
if ourStack[len(ourStack)-1] != (uintptr)(causeStack[len(causeStack)-1]) {
return false
}
}
// All comparisons checked out, these stacks match
return true
}
func callers(skip int) []uintptr {
pc := make([]uintptr, 32) // assume we'll have at most 32 frames
n := runtime.Callers(skip+3, pc) // capture those frames, skipping runtime.Callers, ourself and the calling function
return pc[:n] // return everything that we captured
}
// RecoverPanic turns a panic into an error, adjusting the stacktrace so it originates at
// the line that caused it.
//
// Example:
//
// func Do() (err error) {
// defer func() {
// errors.RecoverPanic(recover(), &err)
// }()
// }
func RecoverPanic(r interface{}, errPtr *error) {
var err error
if r != nil {
if panicErr, ok := r.(error); ok {
err = errors.Wrap(panicErr, "caught panic")
} else {
err = errors.New(fmt.Sprintf("caught panic: %v", r))
}
}
if err != nil {
// Pop twice: once for the errors package, then again for the defer function we must
// run this under. We want the stacktrace to originate at the source of the panic, not
// in the infrastructure that catches it.
err = PopStack(err) // errors.go
err = PopStack(err) // defer
*errPtr = err
}
}
// PopStack removes the top of the stack from an errors stack trace.
func PopStack(err error) error {
if err == nil {
return err
}
// We want to remove us, the internal/errors.New function, from the error stack we just
// produced. There's no official way of reaching into the error and adjusting this, as
// the stack is stored as a private field on an unexported struct.
//
// This does some unsafe badness to adjust that field, which should not be repeated
// anywhere else.
stackField := reflect.ValueOf(err).Elem().FieldByName("stack")
if stackField.IsZero() {
return err
}
stackFieldPtr := (**[]uintptr)(unsafe.Pointer(stackField.UnsafeAddr()))
// Remove the first of the frames, dropping 'us' from the error stack trace.
frames := (**stackFieldPtr)[1:]
// Assign to the internal stack field
*stackFieldPtr = &frames
return err
}

View file

@ -3,7 +3,6 @@ package ggn
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -12,6 +11,7 @@ import (
"time" "time"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/pkg/errors"
"golang.org/x/time/rate" "golang.org/x/time/rate"
) )
@ -147,11 +147,11 @@ func (c *client) Do(req *http.Request) (*http.Response, error) {
ctx := context.Background() ctx := context.Background()
err := c.Ratelimiter.Wait(ctx) // This is a blocking call. Honors the rate limit err := c.Ratelimiter.Wait(ctx) // This is a blocking call. Honors the rate limit
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "error waiting for ratelimiter")
} }
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "error making request")
} }
return resp, nil return resp, nil
} }
@ -159,7 +159,7 @@ func (c *client) Do(req *http.Request) (*http.Response, error) {
func (c *client) get(url string) (*http.Response, error) { func (c *client) get(url string) (*http.Response, error) {
req, err := http.NewRequest(http.MethodGet, url, http.NoBody) req, err := http.NewRequest(http.MethodGet, url, http.NoBody)
if err != nil { if err != nil {
return nil, errors.New(fmt.Sprintf("ggn client request error : %v", url)) return nil, errors.Wrap(err, "ggn client request error : %v", url)
} }
req.Header.Add("X-API-Key", c.APIKey) req.Header.Add("X-API-Key", c.APIKey)
@ -167,7 +167,7 @@ func (c *client) get(url string) (*http.Response, error) {
res, err := c.Do(req) res, err := c.Do(req)
if err != nil { if err != nil {
return nil, errors.New(fmt.Sprintf("ggn client request error : %v", url)) return nil, errors.Wrap(err, "ggn client request error : %v", url)
} }
if res.StatusCode == http.StatusUnauthorized { if res.StatusCode == http.StatusUnauthorized {
@ -183,7 +183,7 @@ func (c *client) get(url string) (*http.Response, error) {
func (c *client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) { func (c *client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) {
if torrentID == "" { if torrentID == "" {
return nil, fmt.Errorf("ggn client: must have torrentID") return nil, errors.New("ggn client: must have torrentID")
} }
var r Response var r Response
@ -192,27 +192,27 @@ func (c *client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error)
v.Add("id", torrentID) v.Add("id", torrentID)
params := v.Encode() params := v.Encode()
url := fmt.Sprintf("%v?%v&%v", c.Url, "request=torrent", params) reqUrl := fmt.Sprintf("%v?%v&%v", c.Url, "request=torrent", params)
resp, err := c.get(url) resp, err := c.get(reqUrl)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "error getting data")
} }
defer resp.Body.Close() defer resp.Body.Close()
body, readErr := ioutil.ReadAll(resp.Body) body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil { if readErr != nil {
return nil, readErr return nil, errors.Wrap(readErr, "error reading body")
} }
err = json.Unmarshal(body, &r) err = json.Unmarshal(body, &r)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "error unmarshal body")
} }
if r.Status != "success" { if r.Status != "success" {
return nil, fmt.Errorf("bad status: %v", r.Status) return nil, errors.New("bad status: %v", r.Status)
} }
t := &domain.TorrentBasic{ t := &domain.TorrentBasic{
@ -229,7 +229,7 @@ func (c *client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error)
func (c *client) TestAPI() (bool, error) { func (c *client) TestAPI() (bool, error) {
resp, err := c.get(c.Url) resp, err := c.get(c.Url)
if err != nil { if err != nil {
return false, err return false, errors.Wrap(err, "error getting data")
} }
defer resp.Body.Close() defer resp.Body.Close()

View file

@ -7,6 +7,8 @@ import (
"net/http" "net/http"
"reflect" "reflect"
"strconv" "strconv"
"github.com/autobrr/autobrr/pkg/errors"
) )
type Client interface { type Client interface {
@ -110,12 +112,12 @@ func (c *rpcClient) Call(method string, params ...interface{}) (*RPCResponse, er
func (c *rpcClient) newRequest(req interface{}) (*http.Request, error) { func (c *rpcClient) newRequest(req interface{}) (*http.Request, error) {
body, err := json.Marshal(req) body, err := json.Marshal(req)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "could not marshal request")
} }
request, err := http.NewRequest("POST", c.endpoint, bytes.NewReader(body)) request, err := http.NewRequest("POST", c.endpoint, bytes.NewReader(body))
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "error creating request")
} }
request.Header.Set("Content-Type", "application/json") request.Header.Set("Content-Type", "application/json")
request.Header.Set("Accept", "application/json") request.Header.Set("Accept", "application/json")
@ -131,12 +133,12 @@ func (c *rpcClient) doCall(request RPCRequest) (*RPCResponse, error) {
httpRequest, err := c.newRequest(request) httpRequest, err := c.newRequest(request)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "could not create rpc http request")
} }
httpResponse, err := c.httpClient.Do(httpRequest) httpResponse, err := c.httpClient.Do(httpRequest)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "error during rpc http request")
} }
defer httpResponse.Body.Close() defer httpResponse.Body.Close()
@ -149,7 +151,7 @@ func (c *rpcClient) doCall(request RPCRequest) (*RPCResponse, error) {
if err != nil { if err != nil {
if httpResponse.StatusCode >= 400 { if httpResponse.StatusCode >= 400 {
return nil, fmt.Errorf("rpc call %v() on %v status code: %v. Could not decode body to rpc response: %v", request.Method, httpRequest.URL.String(), httpResponse.StatusCode, err.Error()) return nil, errors.Wrap(err, fmt.Sprintf("rpc call %v() on %v status code: %v. Could not decode body to rpc response", request.Method, httpRequest.URL.String(), httpResponse.StatusCode))
} }
// if res.StatusCode == http.StatusUnauthorized { // if res.StatusCode == http.StatusUnauthorized {
// return nil, errors.New("unauthorized: bad credentials") // return nil, errors.New("unauthorized: bad credentials")
@ -167,7 +169,7 @@ func (c *rpcClient) doCall(request RPCRequest) (*RPCResponse, error) {
} }
if rpcResponse == nil { if rpcResponse == nil {
return nil, fmt.Errorf("rpc call %v() on %v status code: %v. rpc response missing", request.Method, httpRequest.URL.String(), httpResponse.StatusCode) return nil, errors.New("rpc call %v() on %v status code: %v. rpc response missing", request.Method, httpRequest.URL.String(), httpResponse.StatusCode)
} }
return rpcResponse, nil return rpcResponse, nil
@ -220,12 +222,12 @@ func Params(params ...interface{}) interface{} {
func (r *RPCResponse) GetObject(toType interface{}) error { func (r *RPCResponse) GetObject(toType interface{}) error {
js, err := json.Marshal(r.Result) js, err := json.Marshal(r.Result)
if err != nil { if err != nil {
return err return errors.Wrap(err, "could not marshal object")
} }
err = json.Unmarshal(js, toType) err = json.Unmarshal(js, toType)
if err != nil { if err != nil {
return err return errors.Wrap(err, "could not unmarshal object")
} }
return nil return nil

View file

@ -3,12 +3,12 @@ package lidarr
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"github.com/autobrr/autobrr/pkg/errors"
) )
func (c *client) get(endpoint string) (int, []byte, error) { func (c *client) get(endpoint string) (int, []byte, error) {
@ -18,7 +18,7 @@ func (c *client) get(endpoint string) (int, []byte, error) {
req, err := http.NewRequest(http.MethodGet, reqUrl, http.NoBody) req, err := http.NewRequest(http.MethodGet, reqUrl, http.NoBody)
if err != nil { if err != nil {
return 0, nil, errors.New(fmt.Sprintf("lidarr client request error : %v", reqUrl)) return 0, nil, errors.Wrap(err, "lidarr client request error : %v", reqUrl)
} }
if c.config.BasicAuth { if c.config.BasicAuth {
@ -29,14 +29,14 @@ func (c *client) get(endpoint string) (int, []byte, error) {
resp, err := c.http.Do(req) resp, err := c.http.Do(req)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("lidarr.http.Do(req): %w", err) return 0, nil, errors.Wrap(err, "lidarr.http.Do(req)")
} }
defer resp.Body.Close() defer resp.Body.Close()
var buf bytes.Buffer var buf bytes.Buffer
if _, err = io.Copy(&buf, resp.Body); err != nil { if _, err = io.Copy(&buf, resp.Body); err != nil {
return resp.StatusCode, nil, fmt.Errorf("lidarr.io.Copy: %w", err) return resp.StatusCode, nil, errors.Wrap(err, "lidarr.io.Copy error")
} }
return resp.StatusCode, buf.Bytes(), nil return resp.StatusCode, buf.Bytes(), nil
@ -49,12 +49,12 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error)
jsonData, err := json.Marshal(data) jsonData, err := json.Marshal(data)
if err != nil { if err != nil {
return nil, fmt.Errorf("lidarr client could not marshal data: %v", reqUrl) return nil, errors.Wrap(err, "lidarr client could not marshal data: %v", reqUrl)
} }
req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData)) req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData))
if err != nil { if err != nil {
return nil, fmt.Errorf("lidarr client request error: %v", reqUrl) return nil, errors.Wrap(err, "lidarr client request error: %v", reqUrl)
} }
if c.config.BasicAuth { if c.config.BasicAuth {
@ -67,7 +67,7 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error)
res, err := c.http.Do(req) res, err := c.http.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("lidarr client request error: %v", reqUrl) return nil, errors.Wrap(err, "lidarr client request error: %v", reqUrl)
} }
// validate response // validate response
@ -88,12 +88,12 @@ func (c *client) postBody(endpoint string, data interface{}) (int, []byte, error
jsonData, err := json.Marshal(data) jsonData, err := json.Marshal(data)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("lidarr client could not marshal data: %v", reqUrl) return 0, nil, errors.Wrap(err, "lidarr client could not marshal data: %v", reqUrl)
} }
req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData)) req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData))
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("lidarr client request error: %v", reqUrl) return 0, nil, errors.Wrap(err, "lidarr client request error: %v", reqUrl)
} }
if c.config.BasicAuth { if c.config.BasicAuth {
@ -104,18 +104,20 @@ func (c *client) postBody(endpoint string, data interface{}) (int, []byte, error
resp, err := c.http.Do(req) resp, err := c.http.Do(req)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("lidarr.http.Do(req): %w", err) return 0, nil, errors.Wrap(err, "lidarr.http.Do(req)")
} }
defer resp.Body.Close() defer resp.Body.Close()
var buf bytes.Buffer var buf bytes.Buffer
if _, err = io.Copy(&buf, resp.Body); err != nil { if _, err = io.Copy(&buf, resp.Body); err != nil {
return resp.StatusCode, nil, fmt.Errorf("lidarr.io.Copy: %w", err) return resp.StatusCode, nil, errors.Wrap(err, "lidarr.io.Copy")
} }
if resp.StatusCode < 200 || resp.StatusCode > 299 { if resp.StatusCode == http.StatusBadRequest {
return resp.StatusCode, buf.Bytes(), fmt.Errorf("lidarr: bad request: %v (status: %s): %s", resp.Request.RequestURI, resp.Status, buf.String()) return resp.StatusCode, buf.Bytes(), nil
} else if resp.StatusCode < 200 || resp.StatusCode > 401 {
return resp.StatusCode, buf.Bytes(), errors.New("lidarr: bad request: %v (status: %s): %s", resp.Request.RequestURI, resp.Status, buf.String())
} }
return resp.StatusCode, buf.Bytes(), nil return resp.StatusCode, buf.Bytes(), nil

View file

@ -2,11 +2,14 @@ package lidarr
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io"
"log"
"net/http" "net/http"
"strings" "strings"
"time" "time"
"github.com/autobrr/autobrr/pkg/errors"
) )
type Config struct { type Config struct {
@ -17,6 +20,8 @@ type Config struct {
BasicAuth bool BasicAuth bool
Username string Username string
Password string Password string
Log *log.Logger
} }
type Client interface { type Client interface {
@ -27,6 +32,8 @@ type Client interface {
type client struct { type client struct {
config Config config Config
http *http.Client http *http.Client
Log *log.Logger
} }
// New create new lidarr client // New create new lidarr client
@ -39,6 +46,11 @@ func New(config Config) Client {
c := &client{ c := &client{
config: config, config: config,
http: httpClient, http: httpClient,
Log: config.Log,
}
if config.Log == nil {
c.Log = log.New(io.Discard, "", log.LstdFlags)
} }
return c return c
@ -61,6 +73,13 @@ type PushResponse struct {
Rejections []string `json:"rejections"` Rejections []string `json:"rejections"`
} }
type BadRequestResponse struct {
PropertyName string `json:"propertyName"`
ErrorMessage string `json:"errorMessage"`
AttemptedValue string `json:"attemptedValue"`
Severity string `json:"severity"`
}
type SystemStatusResponse struct { type SystemStatusResponse struct {
Version string `json:"version"` Version string `json:"version"`
} }
@ -68,43 +87,56 @@ type SystemStatusResponse struct {
func (c *client) Test() (*SystemStatusResponse, error) { func (c *client) Test() (*SystemStatusResponse, error) {
status, res, err := c.get("system/status") status, res, err := c.get("system/status")
if err != nil { if err != nil {
return nil, fmt.Errorf("lidarr client get error: %w", err) return nil, errors.Wrap(err, "lidarr client get error")
} }
if status == http.StatusUnauthorized { if status == http.StatusUnauthorized {
return nil, errors.New("unauthorized: bad credentials") return nil, errors.New("unauthorized: bad credentials")
} }
//log.Trace().Msgf("lidarr system/status response status: %v body: %v", status, string(res)) c.Log.Printf("lidarr system/status response status: %v body: %v", status, string(res))
response := SystemStatusResponse{} response := SystemStatusResponse{}
err = json.Unmarshal(res, &response) err = json.Unmarshal(res, &response)
if err != nil { if err != nil {
return nil, fmt.Errorf("lidarr client error json unmarshal: %w", err) return nil, errors.Wrap(err, "lidarr client error json unmarshal")
} }
return &response, nil return &response, nil
} }
func (c *client) Push(release Release) ([]string, error) { func (c *client) Push(release Release) ([]string, error) {
_, res, err := c.postBody("release/push", release) status, res, err := c.postBody("release/push", release)
if err != nil { if err != nil {
return nil, fmt.Errorf("lidarr client post error: %w", err) return nil, errors.Wrap(err, "lidarr client post error")
} }
//log.Trace().Msgf("lidarr release/push response status: %v body: %v", status, string(res)) c.Log.Printf("lidarr release/push response status: %v body: %v", status, string(res))
if status == http.StatusBadRequest {
badreqResponse := make([]*BadRequestResponse, 0)
err = json.Unmarshal(res, &badreqResponse)
if err != nil {
return nil, errors.Wrap(err, "could not unmarshal data")
}
if badreqResponse[0] != nil && badreqResponse[0].PropertyName == "Title" && badreqResponse[0].ErrorMessage == "Unable to parse" {
rejections := []string{fmt.Sprintf("unable to parse: %v", badreqResponse[0].AttemptedValue)}
return rejections, err
}
}
pushResponse := PushResponse{} pushResponse := PushResponse{}
err = json.Unmarshal(res, &pushResponse) err = json.Unmarshal(res, &pushResponse)
if err != nil { if err != nil {
return nil, fmt.Errorf("lidarr client error json unmarshal: %w", err) return nil, errors.Wrap(err, "lidarr client error json unmarshal")
} }
// log and return if rejected // log and return if rejected
if pushResponse.Rejected { if pushResponse.Rejected {
rejections := strings.Join(pushResponse.Rejections, ", ") rejections := strings.Join(pushResponse.Rejections, ", ")
return pushResponse.Rejections, fmt.Errorf("lidarr push rejected: %s - reasons: %q: err %w", release.Title, rejections, err) return pushResponse.Rejections, errors.New("lidarr push rejected: %s - reasons: %q", release.Title, rejections)
} }
return nil, nil return nil, nil

View file

@ -1,7 +1,6 @@
package lidarr package lidarr
import ( import (
"errors"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -134,11 +133,11 @@ func Test_client_Test(t *testing.T) {
defer srv.Close() defer srv.Close()
tests := []struct { tests := []struct {
name string name string
cfg Config cfg Config
want *SystemStatusResponse want *SystemStatusResponse
err error expectedErr string
wantErr bool wantErr bool
}{ }{
{ {
name: "fetch", name: "fetch",
@ -149,9 +148,9 @@ func Test_client_Test(t *testing.T) {
Username: "", Username: "",
Password: "", Password: "",
}, },
want: &SystemStatusResponse{Version: "0.8.1.2135"}, want: &SystemStatusResponse{Version: "0.8.1.2135"},
err: nil, expectedErr: "",
wantErr: false, wantErr: false,
}, },
{ {
name: "fetch_unauthorized", name: "fetch_unauthorized",
@ -162,9 +161,9 @@ func Test_client_Test(t *testing.T) {
Username: "", Username: "",
Password: "", Password: "",
}, },
want: nil, want: nil,
wantErr: true, wantErr: true,
err: errors.New("unauthorized: bad credentials"), expectedErr: "unauthorized: bad credentials",
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@ -173,7 +172,7 @@ func Test_client_Test(t *testing.T) {
got, err := c.Test() got, err := c.Test()
if tt.wantErr && assert.Error(t, err) { if tt.wantErr && assert.Error(t, err) {
assert.Equal(t, tt.err, err) assert.EqualErrorf(t, err, tt.expectedErr, "Error should be: %v, got: %v", tt.wantErr, err)
} }
assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, got)

View file

@ -3,7 +3,6 @@ package ptp
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -11,6 +10,7 @@ import (
"time" "time"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/pkg/errors"
"golang.org/x/time/rate" "golang.org/x/time/rate"
) )
@ -87,11 +87,11 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) {
ctx := context.Background() ctx := context.Background()
err := c.Ratelimiter.Wait(ctx) // This is a blocking call. Honors the rate limit err := c.Ratelimiter.Wait(ctx) // This is a blocking call. Honors the rate limit
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "error waiting for ratelimiter")
} }
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "error making request")
} }
return resp, nil return resp, nil
} }
@ -99,7 +99,7 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) {
func (c *Client) get(url string) (*http.Response, error) { func (c *Client) get(url string) (*http.Response, error) {
req, err := http.NewRequest(http.MethodGet, url, http.NoBody) req, err := http.NewRequest(http.MethodGet, url, http.NoBody)
if err != nil { if err != nil {
return nil, fmt.Errorf("ptp client request error : %v", url) return nil, errors.Wrap(err, "ptp client request error : %v", url)
} }
req.Header.Add("ApiUser", c.APIUser) req.Header.Add("ApiUser", c.APIUser)
@ -108,7 +108,7 @@ func (c *Client) get(url string) (*http.Response, error) {
res, err := c.Do(req) res, err := c.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("ptp client request error : %v", url) return nil, errors.Wrap(err, "ptp client request error : %v", url)
} }
if res.StatusCode == http.StatusUnauthorized { if res.StatusCode == http.StatusUnauthorized {
@ -124,7 +124,7 @@ func (c *Client) get(url string) (*http.Response, error) {
func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) { func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) {
if torrentID == "" { if torrentID == "" {
return nil, fmt.Errorf("ptp client: must have torrentID") return nil, errors.New("ptp client: must have torrentID")
} }
var r TorrentResponse var r TorrentResponse
@ -133,23 +133,23 @@ func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error)
v.Add("torrentid", torrentID) v.Add("torrentid", torrentID)
params := v.Encode() params := v.Encode()
url := fmt.Sprintf("%v?%v", c.Url, params) reqUrl := fmt.Sprintf("%v?%v", c.Url, params)
resp, err := c.get(url) resp, err := c.get(reqUrl)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "error requesting data")
} }
defer resp.Body.Close() defer resp.Body.Close()
body, readErr := ioutil.ReadAll(resp.Body) body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil { if readErr != nil {
return nil, readErr return nil, errors.Wrap(readErr, "could not read body")
} }
err = json.Unmarshal(body, &r) err = json.Unmarshal(body, &r)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(readErr, "could not unmarshal body")
} }
for _, torrent := range r.Torrents { for _, torrent := range r.Torrents {
@ -169,7 +169,7 @@ func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error)
func (c *Client) TestAPI() (bool, error) { func (c *Client) TestAPI() (bool, error) {
resp, err := c.get(c.Url) resp, err := c.get(c.Url)
if err != nil { if err != nil {
return false, err return false, errors.Wrap(err, "error requesting data")
} }
defer resp.Body.Close() defer resp.Body.Close()

View file

@ -5,6 +5,7 @@ import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"io" "io"
"log"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"net/http/cookiejar" "net/http/cookiejar"
@ -14,8 +15,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/rs/zerolog/log" "github.com/autobrr/autobrr/pkg/errors"
"golang.org/x/net/publicsuffix"
publicsuffix "golang.org/x/net/publicsuffix"
) )
var ( var (
@ -31,6 +33,8 @@ type Client struct {
Name string Name string
settings Settings settings Settings
http *http.Client http *http.Client
Log *log.Logger
} }
type Settings struct { type Settings struct {
@ -43,6 +47,7 @@ type Settings struct {
protocol string protocol string
BasicAuth bool BasicAuth bool
Basic Basic Basic Basic
Log *log.Logger
} }
type Basic struct { type Basic struct {
@ -51,20 +56,24 @@ type Basic struct {
} }
func NewClient(s Settings) *Client { func NewClient(s Settings) *Client {
jarOptions := &cookiejar.Options{PublicSuffixList: publicsuffix.List}
//store cookies in jar
jar, err := cookiejar.New(jarOptions)
if err != nil {
log.Error().Err(err).Msg("new client cookie error")
}
httpClient := &http.Client{
Timeout: timeout,
Jar: jar,
}
c := &Client{ c := &Client{
settings: s, settings: s,
http: httpClient, }
if s.Log == nil {
c.Log = log.New(io.Discard, "qbittorrent", log.LstdFlags)
}
//store cookies in jar
jarOptions := &cookiejar.Options{PublicSuffixList: publicsuffix.List}
jar, err := cookiejar.New(jarOptions)
if err != nil {
c.Log.Println("new client cookie error")
}
c.http = &http.Client{
Timeout: timeout,
Jar: jar,
} }
c.settings.protocol = "http" c.settings.protocol = "http"
@ -92,8 +101,7 @@ func (c *Client) get(endpoint string, opts map[string]string) (*http.Response, e
req, err := http.NewRequest("GET", reqUrl, nil) req, err := http.NewRequest("GET", reqUrl, nil)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("GET: error %v", reqUrl) return nil, errors.Wrap(err, "could not build request")
return nil, err
} }
if c.settings.BasicAuth { if c.settings.BasicAuth {
@ -109,14 +117,13 @@ func (c *Client) get(endpoint string, opts map[string]string) (*http.Response, e
break break
} }
log.Debug().Msgf("qbit GET failed: retrying attempt %d - %v", i, reqUrl) c.Log.Printf("qbit GET failed: retrying attempt %d - %v\n", i, reqUrl)
time.Sleep(backoff) time.Sleep(backoff)
} }
if err != nil { if err != nil {
log.Error().Err(err).Msgf("GET: do %v", reqUrl) return nil, errors.Wrap(err, "error making get request: %v", reqUrl)
return nil, err
} }
return resp, nil return resp, nil
@ -138,8 +145,7 @@ func (c *Client) post(endpoint string, opts map[string]string) (*http.Response,
req, err := http.NewRequest("POST", reqUrl, strings.NewReader(form.Encode())) req, err := http.NewRequest("POST", reqUrl, strings.NewReader(form.Encode()))
if err != nil { if err != nil {
log.Error().Err(err).Msgf("POST: req %v", reqUrl) return nil, errors.Wrap(err, "could not build request")
return nil, err
} }
if c.settings.BasicAuth { if c.settings.BasicAuth {
@ -158,14 +164,13 @@ func (c *Client) post(endpoint string, opts map[string]string) (*http.Response,
break break
} }
log.Debug().Msgf("qbit POST failed: retrying attempt %d - %v", i, reqUrl) c.Log.Printf("qbit POST failed: retrying attempt %d - %v\n", i, reqUrl)
time.Sleep(backoff) time.Sleep(backoff)
} }
if err != nil { if err != nil {
log.Error().Err(err).Msgf("POST: do %v", reqUrl) return nil, errors.Wrap(err, "error making post request: %v", reqUrl)
return nil, err
} }
return resp, nil return resp, nil
@ -187,8 +192,7 @@ func (c *Client) postBasic(endpoint string, opts map[string]string) (*http.Respo
req, err := http.NewRequest("POST", reqUrl, strings.NewReader(form.Encode())) req, err := http.NewRequest("POST", reqUrl, strings.NewReader(form.Encode()))
if err != nil { if err != nil {
log.Error().Err(err).Msgf("POST: req %v", reqUrl) return nil, errors.Wrap(err, "could not build request")
return nil, err
} }
if c.settings.BasicAuth { if c.settings.BasicAuth {
@ -200,8 +204,7 @@ func (c *Client) postBasic(endpoint string, opts map[string]string) (*http.Respo
resp, err = c.http.Do(req) resp, err = c.http.Do(req)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("POST: do %v", reqUrl) return nil, errors.Wrap(err, "error making post request: %v", reqUrl)
return nil, err
} }
return resp, nil return resp, nil
@ -213,8 +216,7 @@ func (c *Client) postFile(endpoint string, fileName string, opts map[string]stri
file, err := os.Open(fileName) file, err := os.Open(fileName)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("POST file: opening file %v", fileName) return nil, errors.Wrap(err, "error opening file %v", fileName)
return nil, err
} }
// Close the file later // Close the file later
defer file.Close() defer file.Close()
@ -228,15 +230,13 @@ func (c *Client) postFile(endpoint string, fileName string, opts map[string]stri
// Initialize file field // Initialize file field
fileWriter, err := multiPartWriter.CreateFormFile("torrents", fileName) fileWriter, err := multiPartWriter.CreateFormFile("torrents", fileName)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("POST file: initializing file field %v", fileName) return nil, errors.Wrap(err, "error initializing file field %v", fileName)
return nil, err
} }
// Copy the actual file content to the fields writer // Copy the actual file content to the fields writer
_, err = io.Copy(fileWriter, file) _, err = io.Copy(fileWriter, file)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("POST file: could not copy file to writer %v", fileName) return nil, errors.Wrap(err, "error copy file contents to writer %v", fileName)
return nil, err
} }
// Populate other fields // Populate other fields
@ -244,14 +244,12 @@ func (c *Client) postFile(endpoint string, fileName string, opts map[string]stri
for key, val := range opts { for key, val := range opts {
fieldWriter, err := multiPartWriter.CreateFormField(key) fieldWriter, err := multiPartWriter.CreateFormField(key)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("POST file: could not add other fields %v", fileName) return nil, errors.Wrap(err, "error creating form field %v with value %v", key, val)
return nil, err
} }
_, err = fieldWriter.Write([]byte(val)) _, err = fieldWriter.Write([]byte(val))
if err != nil { if err != nil {
log.Error().Err(err).Msgf("POST file: could not write field %v", fileName) return nil, errors.Wrap(err, "error writing field %v with value %v", key, val)
return nil, err
} }
} }
} }
@ -262,8 +260,7 @@ func (c *Client) postFile(endpoint string, fileName string, opts map[string]stri
reqUrl := buildUrl(c.settings, endpoint) reqUrl := buildUrl(c.settings, endpoint)
req, err := http.NewRequest("POST", reqUrl, &requestBody) req, err := http.NewRequest("POST", reqUrl, &requestBody)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("POST file: could not create request object %v", fileName) return nil, errors.Wrap(err, "error creating request %v", fileName)
return nil, err
} }
if c.settings.BasicAuth { if c.settings.BasicAuth {
@ -282,14 +279,13 @@ func (c *Client) postFile(endpoint string, fileName string, opts map[string]stri
break break
} }
log.Debug().Msgf("qbit POST file failed: retrying attempt %d - %v", i, reqUrl) c.Log.Printf("qbit POST file failed: retrying attempt %d - %v\n", i, reqUrl)
time.Sleep(backoff) time.Sleep(backoff)
} }
if err != nil { if err != nil {
log.Error().Err(err).Msgf("POST file: could not perform request %v", fileName) return nil, errors.Wrap(err, "error making post file request %v", fileName)
return nil, err
} }
return resp, nil return resp, nil

View file

@ -2,14 +2,13 @@ package qbittorrent
import ( import (
"encoding/json" "encoding/json"
"errors"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"strconv" "strconv"
"strings" "strings"
"github.com/rs/zerolog/log" "github.com/autobrr/autobrr/pkg/errors"
) )
// Login https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#authentication // Login https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#authentication
@ -21,15 +20,12 @@ func (c *Client) Login() error {
resp, err := c.postBasic("auth/login", opts) resp, err := c.postBasic("auth/login", opts)
if err != nil { if err != nil {
log.Error().Err(err).Msg("login error") return errors.Wrap(err, "login error")
return err
} else if resp.StatusCode == http.StatusForbidden { } else if resp.StatusCode == http.StatusForbidden {
log.Error().Err(err).Msg("User's IP is banned for too many failed login attempts") return errors.New("User's IP is banned for too many failed login attempts")
return err
} else if resp.StatusCode != http.StatusOK { // check for correct status code } else if resp.StatusCode != http.StatusOK { // check for correct status code
log.Error().Err(err).Msgf("login bad status %v error", resp.StatusCode) return errors.New("qbittorrent login bad status %v", resp.StatusCode)
return errors.New("qbittorrent login bad status")
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -61,23 +57,20 @@ func (c *Client) GetTorrents() ([]Torrent, error) {
resp, err := c.get("torrents/info", nil) resp, err := c.get("torrents/info", nil)
if err != nil { if err != nil {
log.Error().Err(err).Msg("get torrents error") return nil, errors.Wrap(err, "get torrents error")
return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
body, readErr := ioutil.ReadAll(resp.Body) body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil { if readErr != nil {
log.Error().Err(err).Msg("get torrents read error") return nil, errors.Wrap(readErr, "could not read body")
return nil, readErr
} }
var torrents []Torrent var torrents []Torrent
err = json.Unmarshal(body, &torrents) err = json.Unmarshal(body, &torrents)
if err != nil { if err != nil {
log.Error().Err(err).Msg("get torrents unmarshal error") return nil, errors.Wrap(err, "could not unmarshal body")
return nil, err
} }
return torrents, nil return torrents, nil
@ -90,23 +83,20 @@ func (c *Client) GetTorrentsFilter(filter TorrentFilter) ([]Torrent, error) {
resp, err := c.get("torrents/info", opts) resp, err := c.get("torrents/info", opts)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("get filtered torrents error: %v", filter) return nil, errors.Wrap(err, "could not get filtered torrents with filter: %v", filter)
return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
body, readErr := ioutil.ReadAll(resp.Body) body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil { if readErr != nil {
log.Error().Err(err).Msgf("get filtered torrents read error: %v", filter) return nil, errors.Wrap(readErr, "could not read body")
return nil, readErr
} }
var torrents []Torrent var torrents []Torrent
err = json.Unmarshal(body, &torrents) err = json.Unmarshal(body, &torrents)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("get filtered torrents unmarshal error: %v", filter) return nil, errors.Wrap(err, "could not unmarshal body")
return nil, err
} }
return torrents, nil return torrents, nil
@ -121,23 +111,20 @@ func (c *Client) GetTorrentsActiveDownloads() ([]Torrent, error) {
resp, err := c.get("torrents/info", opts) resp, err := c.get("torrents/info", opts)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("get filtered torrents error: %v", filter) return nil, errors.Wrap(err, "could not get active torrents")
return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
body, readErr := ioutil.ReadAll(resp.Body) body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil { if readErr != nil {
log.Error().Err(err).Msgf("get filtered torrents read error: %v", filter) return nil, errors.Wrap(readErr, "could not read body")
return nil, readErr
} }
var torrents []Torrent var torrents []Torrent
err = json.Unmarshal(body, &torrents) err = json.Unmarshal(body, &torrents)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("get filtered torrents unmarshal error: %v", filter) return nil, errors.Wrap(readErr, "could not unmarshal body")
return nil, err
} }
res := make([]Torrent, 0) res := make([]Torrent, 0)
@ -155,13 +142,15 @@ func (c *Client) GetTorrentsActiveDownloads() ([]Torrent, error) {
func (c *Client) GetTorrentsRaw() (string, error) { func (c *Client) GetTorrentsRaw() (string, error) {
resp, err := c.get("torrents/info", nil) resp, err := c.get("torrents/info", nil)
if err != nil { if err != nil {
log.Error().Err(err).Msg("get torrent trackers raw error") return "", errors.Wrap(err, "could not get torrents raw")
return "", err
} }
defer resp.Body.Close() defer resp.Body.Close()
data, _ := ioutil.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", errors.Wrap(err, "could not get read body torrents raw")
}
return string(data), nil return string(data), nil
} }
@ -173,40 +162,35 @@ func (c *Client) GetTorrentTrackers(hash string) ([]TorrentTracker, error) {
resp, err := c.get("torrents/trackers", opts) resp, err := c.get("torrents/trackers", opts)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("get torrent trackers error: %v", hash) return nil, errors.Wrap(err, "could not get torrent trackers for hash: %v", hash)
return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
dump, err := httputil.DumpResponse(resp, true) dump, err := httputil.DumpResponse(resp, true)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("get torrent trackers dump response error: %v", err) c.Log.Printf("get torrent trackers error dump response: %v\n", string(dump))
} }
log.Trace().Msgf("get torrent trackers response dump: %v", string(dump)) c.Log.Printf("get torrent trackers response dump: %v\n", string(dump))
if resp.StatusCode == http.StatusNotFound { if resp.StatusCode == http.StatusNotFound {
//return nil, fmt.Errorf("torrent not found: %v", hash)
return nil, nil return nil, nil
} else if resp.StatusCode == http.StatusForbidden { } else if resp.StatusCode == http.StatusForbidden {
//return nil, fmt.Errorf("torrent not found: %v", hash)
return nil, nil return nil, nil
} }
body, readErr := ioutil.ReadAll(resp.Body) body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil { if readErr != nil {
log.Error().Err(err).Msgf("get torrent trackers read error: %v", hash) return nil, errors.Wrap(err, "could not read body")
return nil, readErr
} }
log.Trace().Msgf("get torrent trackers body: %v", string(body)) c.Log.Printf("get torrent trackers body: %v\n", string(body))
var trackers []TorrentTracker var trackers []TorrentTracker
err = json.Unmarshal(body, &trackers) err = json.Unmarshal(body, &trackers)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("get torrent trackers: %v", hash) return nil, errors.Wrap(err, "could not unmarshal body")
return nil, err
} }
return trackers, nil return trackers, nil
@ -217,11 +201,9 @@ func (c *Client) AddTorrentFromFile(file string, options map[string]string) erro
res, err := c.postFile("torrents/add", file, options) res, err := c.postFile("torrents/add", file, options)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("add torrents error: %v", file) return errors.Wrap(err, "could not add torrent %v", file)
return err
} else if res.StatusCode != http.StatusOK { } else if res.StatusCode != http.StatusOK {
log.Error().Err(err).Msgf("add torrents bad status: %v", file) return errors.Wrap(err, "could not add torrent %v unexpected status: %v", file, res.StatusCode)
return err
} }
defer res.Body.Close() defer res.Body.Close()
@ -240,11 +222,9 @@ func (c *Client) DeleteTorrents(hashes []string, deleteFiles bool) error {
resp, err := c.get("torrents/delete", opts) resp, err := c.get("torrents/delete", opts)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("delete torrents error: %v", hashes) return errors.Wrap(err, "could not delete torrents: %+v", hashes)
return err
} else if resp.StatusCode != http.StatusOK { } else if resp.StatusCode != http.StatusOK {
log.Error().Err(err).Msgf("delete torrents bad code: %v", hashes) return errors.Wrap(err, "could not delete torrents %v unexpected status: %v", hashes, resp.StatusCode)
return err
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -261,11 +241,9 @@ func (c *Client) ReAnnounceTorrents(hashes []string) error {
resp, err := c.get("torrents/reannounce", opts) resp, err := c.get("torrents/reannounce", opts)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("re-announce error: %v", hashes) return errors.Wrap(err, "could not re-announce torrents: %v", hashes)
return err
} else if resp.StatusCode != http.StatusOK { } else if resp.StatusCode != http.StatusOK {
log.Error().Err(err).Msgf("re-announce error bad status: %v", hashes) return errors.Wrap(err, "could not re-announce torrents: %v unexpected status: %v", hashes, resp.StatusCode)
return err
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -276,23 +254,20 @@ func (c *Client) ReAnnounceTorrents(hashes []string) error {
func (c *Client) GetTransferInfo() (*TransferInfo, error) { func (c *Client) GetTransferInfo() (*TransferInfo, error) {
resp, err := c.get("transfer/info", nil) resp, err := c.get("transfer/info", nil)
if err != nil { if err != nil {
log.Error().Err(err).Msg("get torrents error") return nil, errors.Wrap(err, "could not get transfer info")
return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
body, readErr := ioutil.ReadAll(resp.Body) body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil { if readErr != nil {
log.Error().Err(err).Msg("get torrents read error") return nil, errors.Wrap(readErr, "could not read body")
return nil, readErr
} }
var info TransferInfo var info TransferInfo
err = json.Unmarshal(body, &info) err = json.Unmarshal(body, &info)
if err != nil { if err != nil {
log.Error().Err(err).Msg("get torrents unmarshal error") return nil, errors.Wrap(readErr, "could not unmarshal body")
return nil, err
} }
return &info, nil return &info, nil

View file

@ -3,14 +3,12 @@ package radarr
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"github.com/rs/zerolog/log" "github.com/autobrr/autobrr/pkg/errors"
) )
func (c *client) get(endpoint string) (int, []byte, error) { func (c *client) get(endpoint string) (int, []byte, error) {
@ -20,8 +18,7 @@ func (c *client) get(endpoint string) (int, []byte, error) {
req, err := http.NewRequest(http.MethodGet, reqUrl, http.NoBody) req, err := http.NewRequest(http.MethodGet, reqUrl, http.NoBody)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("radarr client request error : %v", reqUrl) return 0, nil, errors.Wrap(err, "could not build request: %v", reqUrl)
return 0, nil, err
} }
if c.config.BasicAuth { if c.config.BasicAuth {
@ -32,15 +29,14 @@ func (c *client) get(endpoint string) (int, []byte, error) {
resp, err := c.http.Do(req) resp, err := c.http.Do(req)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("radarr client.get request error: %v", reqUrl) return 0, nil, errors.Wrap(err, "radarr.http.Do(req): %v", reqUrl)
return 0, nil, fmt.Errorf("radarr.http.Do(req): %w", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
var buf bytes.Buffer var buf bytes.Buffer
if _, err = io.Copy(&buf, resp.Body); err != nil { if _, err = io.Copy(&buf, resp.Body); err != nil {
return resp.StatusCode, nil, fmt.Errorf("radarr.io.Copy: %w", err) return resp.StatusCode, nil, errors.Wrap(err, "radarr.io.Copy")
} }
return resp.StatusCode, buf.Bytes(), nil return resp.StatusCode, buf.Bytes(), nil
@ -53,14 +49,12 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error)
jsonData, err := json.Marshal(data) jsonData, err := json.Marshal(data)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("radarr client could not marshal data: %v", reqUrl) return nil, errors.Wrap(err, "could not marshal data: %+v", data)
return nil, err
} }
req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData)) req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData))
if err != nil { if err != nil {
log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl) return nil, errors.Wrap(err, "could not build request: %v", reqUrl)
return nil, err
} }
if c.config.BasicAuth { if c.config.BasicAuth {
@ -73,19 +67,15 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error)
res, err := c.http.Do(req) res, err := c.http.Do(req)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl) return nil, errors.Wrap(err, "could not make request: %+v", req)
return nil, err
} }
// validate response // validate response
if res.StatusCode == http.StatusUnauthorized { if res.StatusCode == http.StatusUnauthorized {
log.Error().Err(err).Msgf("radarr client bad request: %v", reqUrl)
return nil, errors.New("unauthorized: bad credentials") return nil, errors.New("unauthorized: bad credentials")
} else if res.StatusCode == http.StatusBadRequest { } else if res.StatusCode == http.StatusBadRequest {
log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl)
return nil, errors.New("radarr: bad request") return nil, errors.New("radarr: bad request")
} else if res.StatusCode != http.StatusOK { } else if res.StatusCode != http.StatusOK {
log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl)
return nil, errors.New("radarr: bad request") return nil, errors.New("radarr: bad request")
} }
@ -100,14 +90,12 @@ func (c *client) postBody(endpoint string, data interface{}) (int, []byte, error
jsonData, err := json.Marshal(data) jsonData, err := json.Marshal(data)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("radarr client could not marshal data: %v", reqUrl) return 0, nil, errors.Wrap(err, "could not marshal data: %+v", data)
return 0, nil, err
} }
req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData)) req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData))
if err != nil { if err != nil {
log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl) return 0, nil, errors.Wrap(err, "could not build request: %v", reqUrl)
return 0, nil, err
} }
if c.config.BasicAuth { if c.config.BasicAuth {
@ -118,19 +106,20 @@ func (c *client) postBody(endpoint string, data interface{}) (int, []byte, error
resp, err := c.http.Do(req) resp, err := c.http.Do(req)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl) return 0, nil, errors.Wrap(err, "radarr.http.Do(req): %+v", req)
return 0, nil, fmt.Errorf("radarr.http.Do(req): %w", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
var buf bytes.Buffer var buf bytes.Buffer
if _, err = io.Copy(&buf, resp.Body); err != nil { if _, err = io.Copy(&buf, resp.Body); err != nil {
return resp.StatusCode, nil, fmt.Errorf("radarr.io.Copy: %w", err) return resp.StatusCode, nil, errors.Wrap(err, "radarr.io.Copy")
} }
if resp.StatusCode < 200 || resp.StatusCode > 299 { if resp.StatusCode == http.StatusBadRequest {
return resp.StatusCode, buf.Bytes(), fmt.Errorf("radarr: bad request: %v (status: %s): %s", resp.Request.RequestURI, resp.Status, buf.String()) return resp.StatusCode, buf.Bytes(), nil
} else if resp.StatusCode < 200 || resp.StatusCode > 401 {
return resp.StatusCode, buf.Bytes(), errors.New("radarr: bad request: %v (status: %s): %s", resp.Request.RequestURI, resp.Status, buf.String())
} }
return resp.StatusCode, buf.Bytes(), nil return resp.StatusCode, buf.Bytes(), nil

View file

@ -2,12 +2,14 @@ package radarr
import ( import (
"encoding/json" "encoding/json"
"errors" "fmt"
"io"
"log"
"net/http" "net/http"
"strings" "strings"
"time" "time"
"github.com/rs/zerolog/log" "github.com/autobrr/autobrr/pkg/errors"
) )
type Config struct { type Config struct {
@ -18,6 +20,8 @@ type Config struct {
BasicAuth bool BasicAuth bool
Username string Username string
Password string Password string
Log *log.Logger
} }
type Client interface { type Client interface {
@ -28,6 +32,8 @@ type Client interface {
type client struct { type client struct {
config Config config Config
http *http.Client http *http.Client
Log *log.Logger
} }
func New(config Config) Client { func New(config Config) Client {
@ -39,6 +45,11 @@ func New(config Config) Client {
c := &client{ c := &client{
config: config, config: config,
http: httpClient, http: httpClient,
Log: config.Log,
}
if config.Log == nil {
c.Log = log.New(io.Discard, "", log.LstdFlags)
} }
return c return c
@ -65,11 +76,17 @@ type SystemStatusResponse struct {
Version string `json:"version"` Version string `json:"version"`
} }
type BadRequestResponse struct {
PropertyName string `json:"propertyName"`
ErrorMessage string `json:"errorMessage"`
AttemptedValue string `json:"attemptedValue"`
Severity string `json:"severity"`
}
func (c *client) Test() (*SystemStatusResponse, error) { func (c *client) Test() (*SystemStatusResponse, error) {
status, res, err := c.get("system/status") status, res, err := c.get("system/status")
if err != nil { if err != nil {
log.Error().Stack().Err(err).Msg("radarr client get error") return nil, errors.Wrap(err, "radarr error running test")
return nil, err
} }
if status == http.StatusUnauthorized { if status == http.StatusUnauthorized {
@ -79,11 +96,10 @@ func (c *client) Test() (*SystemStatusResponse, error) {
response := SystemStatusResponse{} response := SystemStatusResponse{}
err = json.Unmarshal(res, &response) err = json.Unmarshal(res, &response)
if err != nil { if err != nil {
log.Error().Stack().Err(err).Msg("radarr client error json unmarshal") return nil, errors.Wrap(err, "could not unmarshal data")
return nil, err
} }
log.Trace().Msgf("radarr system/status response: %+v", response) c.Log.Printf("radarr system/status status: (%v) response: %v\n", status, string(res))
return &response, nil return &response, nil
} }
@ -91,24 +107,35 @@ func (c *client) Test() (*SystemStatusResponse, error) {
func (c *client) Push(release Release) ([]string, error) { func (c *client) Push(release Release) ([]string, error) {
status, res, err := c.postBody("release/push", release) status, res, err := c.postBody("release/push", release)
if err != nil { if err != nil {
log.Error().Stack().Err(err).Msgf("radarr client post error. status: %d", status) return nil, errors.Wrap(err, "error push release")
return nil, err }
c.Log.Printf("radarr release/push status: (%v) response: %v\n", status, string(res))
if status == http.StatusBadRequest {
badreqResponse := make([]*BadRequestResponse, 0)
err = json.Unmarshal(res, &badreqResponse)
if err != nil {
return nil, errors.Wrap(err, "could not unmarshal data")
}
if badreqResponse[0] != nil && badreqResponse[0].PropertyName == "Title" && badreqResponse[0].ErrorMessage == "Unable to parse" {
rejections := []string{fmt.Sprintf("unable to parse: %v", badreqResponse[0].AttemptedValue)}
return rejections, nil
}
} }
pushResponse := make([]PushResponse, 0) pushResponse := make([]PushResponse, 0)
err = json.Unmarshal(res, &pushResponse) err = json.Unmarshal(res, &pushResponse)
if err != nil { if err != nil {
log.Error().Stack().Err(err).Msg("radarr client error json unmarshal") return nil, errors.Wrap(err, "could not unmarshal data")
return nil, err
} }
log.Trace().Msgf("radarr release/push response status: %v body: %+v", status, string(res))
// log and return if rejected // log and return if rejected
if pushResponse[0].Rejected { if pushResponse[0].Rejected {
rejections := strings.Join(pushResponse[0].Rejections, ", ") rejections := strings.Join(pushResponse[0].Rejections, ", ")
log.Trace().Msgf("radarr push rejected: %s - reasons: %q", release.Title, rejections) c.Log.Printf("radarr release/push rejected %v reasons: %q\n", release.Title, rejections)
return pushResponse[0].Rejections, nil return pushResponse[0].Rejections, nil
} }

View file

@ -1,7 +1,6 @@
package radarr package radarr
import ( import (
"errors"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -90,8 +89,6 @@ func Test_client_Push(t *testing.T) {
PublishDate: "2021-08-21T15:36:00Z", PublishDate: "2021-08-21T15:36:00Z",
}}, }},
rejections: []string{"Could not find Some Old Movie"}, rejections: []string{"Could not find Some Old Movie"},
//err: errors.New("radarr push rejected Could not find Some Old Movie"),
//wantErr: true,
}, },
{ {
name: "push_error", name: "push_error",
@ -114,8 +111,6 @@ func Test_client_Push(t *testing.T) {
PublishDate: "2021-08-21T15:36:00Z", PublishDate: "2021-08-21T15:36:00Z",
}}, }},
rejections: []string{"Could not find Some Old Movie"}, rejections: []string{"Could not find Some Old Movie"},
//err: errors.New("radarr push rejected Could not find Some Old Movie"),
//wantErr: true,
}, },
{ {
name: "push_parse_error", name: "push_parse_error",
@ -137,8 +132,8 @@ func Test_client_Push(t *testing.T) {
Protocol: "torrent", Protocol: "torrent",
PublishDate: "2021-08-21T15:36:00Z", PublishDate: "2021-08-21T15:36:00Z",
}}, }},
err: errors.New("radarr: bad request: (status: 400 Bad Request): [\n {\n \"propertyName\": \"Title\",\n \"errorMessage\": \"Unable to parse\",\n \"attemptedValue\": \"Minx 1 epi 9 2160p\",\n \"severity\": \"error\"\n }\n]\n"), rejections: []string{"unable to parse: Minx 1 epi 9 2160p"},
wantErr: true, wantErr: false,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@ -177,11 +172,11 @@ func Test_client_Test(t *testing.T) {
defer srv.Close() defer srv.Close()
tests := []struct { tests := []struct {
name string name string
cfg Config cfg Config
want *SystemStatusResponse want *SystemStatusResponse
err error expectedErr string
wantErr bool wantErr bool
}{ }{
{ {
name: "fetch", name: "fetch",
@ -192,9 +187,9 @@ func Test_client_Test(t *testing.T) {
Username: "", Username: "",
Password: "", Password: "",
}, },
want: &SystemStatusResponse{Version: "3.2.2.5080"}, want: &SystemStatusResponse{Version: "3.2.2.5080"},
err: nil, expectedErr: "",
wantErr: false, wantErr: false,
}, },
{ {
name: "fetch_unauthorized", name: "fetch_unauthorized",
@ -205,9 +200,9 @@ func Test_client_Test(t *testing.T) {
Username: "", Username: "",
Password: "", Password: "",
}, },
want: nil, want: nil,
wantErr: true, wantErr: true,
err: errors.New("unauthorized: bad credentials"), expectedErr: "unauthorized: bad credentials",
}, },
{ {
name: "fetch_subfolder", name: "fetch_subfolder",
@ -218,9 +213,9 @@ func Test_client_Test(t *testing.T) {
Username: "", Username: "",
Password: "", Password: "",
}, },
want: &SystemStatusResponse{Version: "3.2.2.5080"}, want: &SystemStatusResponse{Version: "3.2.2.5080"},
err: nil, expectedErr: "",
wantErr: false, wantErr: false,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@ -229,7 +224,7 @@ func Test_client_Test(t *testing.T) {
got, err := c.Test() got, err := c.Test()
if tt.wantErr && assert.Error(t, err) { if tt.wantErr && assert.Error(t, err) {
assert.Equal(t, tt.err, err) assert.EqualErrorf(t, err, tt.expectedErr, "Error should be: %v, got: %v", tt.wantErr, err)
} }
assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, got)

View file

@ -3,18 +3,17 @@ package red
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
"time" "time"
"github.com/rs/zerolog/log"
"golang.org/x/time/rate"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/pkg/errors"
"golang.org/x/time/rate"
) )
type REDClient interface { type REDClient interface {
@ -131,8 +130,7 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) {
func (c *Client) get(url string) (*http.Response, error) { func (c *Client) get(url string) (*http.Response, error) {
req, err := http.NewRequest(http.MethodGet, url, http.NoBody) req, err := http.NewRequest(http.MethodGet, url, http.NoBody)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("red client request error : %v", url) return nil, errors.Wrap(err, "could not build request")
return nil, err
} }
req.Header.Add("Authorization", c.APIKey) req.Header.Add("Authorization", c.APIKey)
@ -140,8 +138,7 @@ func (c *Client) get(url string) (*http.Response, error) {
res, err := c.Do(req) res, err := c.Do(req)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("red client request error : %v", url) return nil, errors.Wrap(err, "could not make request: %+v", req)
return nil, err
} }
if res.StatusCode == http.StatusUnauthorized { if res.StatusCode == http.StatusUnauthorized {
@ -159,7 +156,7 @@ func (c *Client) get(url string) (*http.Response, error) {
func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) { func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) {
if torrentID == "" { if torrentID == "" {
return nil, fmt.Errorf("red client: must have torrentID") return nil, errors.New("red client: must have torrentID")
} }
var r TorrentDetailsResponse var r TorrentDetailsResponse
@ -168,23 +165,23 @@ func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error)
v.Add("id", torrentID) v.Add("id", torrentID)
params := v.Encode() params := v.Encode()
url := fmt.Sprintf("%v?action=torrent&%v", c.URL, params) reqUrl := fmt.Sprintf("%v?action=torrent&%v", c.URL, params)
resp, err := c.get(url) resp, err := c.get(reqUrl)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "could not get torrent by id: %v", torrentID)
} }
defer resp.Body.Close() defer resp.Body.Close()
body, readErr := ioutil.ReadAll(resp.Body) body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil { if readErr != nil {
return nil, readErr return nil, errors.Wrap(readErr, "could not read body")
} }
err = json.Unmarshal(body, &r) err = json.Unmarshal(body, &r)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(readErr, "could not unmarshal body")
} }
return &domain.TorrentBasic{ return &domain.TorrentBasic{
@ -199,7 +196,7 @@ func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error)
func (c *Client) TestAPI() (bool, error) { func (c *Client) TestAPI() (bool, error) {
resp, err := c.get(c.URL + "?action=index") resp, err := c.get(c.URL + "?action=index")
if err != nil { if err != nil {
return false, err return false, errors.Wrap(err, "could not run test api")
} }
defer resp.Body.Close() defer resp.Body.Close()

View file

@ -1,17 +1,15 @@
package red package red
import ( import (
"errors"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings" "strings"
"testing" "testing"
"github.com/autobrr/autobrr/internal/domain"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/autobrr/autobrr/internal/domain"
) )
func TestREDClient_GetTorrentByID(t *testing.T) { func TestREDClient_GetTorrentByID(t *testing.T) {
@ -57,7 +55,7 @@ func TestREDClient_GetTorrentByID(t *testing.T) {
fields fields fields fields
args args args args
want *domain.TorrentBasic want *domain.TorrentBasic
wantErr error wantErr string
}{ }{
{ {
name: "get_by_id_1", name: "get_by_id_1",
@ -71,7 +69,7 @@ func TestREDClient_GetTorrentByID(t *testing.T) {
InfoHash: "B2BABD3A361EAFC6C4E9142C422DF7DDF5D7E163", InfoHash: "B2BABD3A361EAFC6C4E9142C422DF7DDF5D7E163",
Size: "527749302", Size: "527749302",
}, },
wantErr: nil, wantErr: "",
}, },
{ {
name: "get_by_id_2", name: "get_by_id_2",
@ -81,7 +79,7 @@ func TestREDClient_GetTorrentByID(t *testing.T) {
}, },
args: args{torrentID: "100002"}, args: args{torrentID: "100002"},
want: nil, want: nil,
wantErr: errors.New("bad id parameter"), wantErr: "could not get torrent by id: 100002: bad id parameter",
}, },
{ {
name: "get_by_id_3", name: "get_by_id_3",
@ -91,7 +89,7 @@ func TestREDClient_GetTorrentByID(t *testing.T) {
}, },
args: args{torrentID: "100002"}, args: args{torrentID: "100002"},
want: nil, want: nil,
wantErr: errors.New("unauthorized: bad credentials"), wantErr: "could not get torrent by id: 100002: unauthorized: bad credentials",
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@ -99,8 +97,8 @@ func TestREDClient_GetTorrentByID(t *testing.T) {
c := NewClient(tt.fields.Url, tt.fields.APIKey) c := NewClient(tt.fields.Url, tt.fields.APIKey)
got, err := c.GetTorrentByID(tt.args.torrentID) got, err := c.GetTorrentByID(tt.args.torrentID)
if tt.wantErr != nil && assert.Error(t, err) { if tt.wantErr != "" && assert.Error(t, err) {
assert.Equal(t, tt.wantErr, err) assert.EqualErrorf(t, err, tt.wantErr, "Error should be: %v, got: %v", tt.wantErr, err)
} }
assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, got)

View file

@ -3,14 +3,12 @@ package sonarr
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"github.com/rs/zerolog/log" "github.com/autobrr/autobrr/pkg/errors"
) )
func (c *client) get(endpoint string) (int, []byte, error) { func (c *client) get(endpoint string) (int, []byte, error) {
@ -20,8 +18,7 @@ func (c *client) get(endpoint string) (int, []byte, error) {
req, err := http.NewRequest(http.MethodGet, reqUrl, http.NoBody) req, err := http.NewRequest(http.MethodGet, reqUrl, http.NoBody)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("sonarr client request error : %v", reqUrl) return 0, nil, errors.Wrap(err, "could not build request")
return 0, nil, err
} }
if c.config.BasicAuth { if c.config.BasicAuth {
@ -32,15 +29,14 @@ func (c *client) get(endpoint string) (int, []byte, error) {
resp, err := c.http.Do(req) resp, err := c.http.Do(req)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("sonarr client.get request error: %v", reqUrl) return 0, nil, errors.Wrap(err, "sonarr.http.Do(req): %+v", req)
return 0, nil, fmt.Errorf("sonarr.http.Do(req): %w", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
var buf bytes.Buffer var buf bytes.Buffer
if _, err = io.Copy(&buf, resp.Body); err != nil { if _, err = io.Copy(&buf, resp.Body); err != nil {
return resp.StatusCode, nil, fmt.Errorf("sonarr.io.Copy: %w", err) return resp.StatusCode, nil, errors.Wrap(err, "sonarr.io.Copy")
} }
return resp.StatusCode, buf.Bytes(), nil return resp.StatusCode, buf.Bytes(), nil
@ -53,14 +49,12 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error)
jsonData, err := json.Marshal(data) jsonData, err := json.Marshal(data)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("sonarr client could not marshal data: %v", reqUrl) return nil, errors.Wrap(err, "could not marshal data: %+v", data)
return nil, err
} }
req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData)) req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData))
if err != nil { if err != nil {
log.Error().Err(err).Msgf("sonarr client request error: %v", reqUrl) return nil, errors.Wrap(err, "could not build request")
return nil, err
} }
if c.config.BasicAuth { if c.config.BasicAuth {
@ -73,16 +67,13 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error)
res, err := c.http.Do(req) res, err := c.http.Do(req)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("sonarr client request error: %v", reqUrl) return nil, errors.Wrap(err, "could not make request: %+v", req)
return nil, err
} }
// validate response // validate response
if res.StatusCode == http.StatusUnauthorized { if res.StatusCode == http.StatusUnauthorized {
log.Error().Err(err).Msgf("sonarr client bad request: %v", reqUrl)
return nil, errors.New("unauthorized: bad credentials") return nil, errors.New("unauthorized: bad credentials")
} else if res.StatusCode != http.StatusOK { } else if res.StatusCode != http.StatusOK {
log.Error().Err(err).Msgf("sonarr client request error: %v", reqUrl)
return nil, errors.New("sonarr: bad request") return nil, errors.New("sonarr: bad request")
} }
@ -97,14 +88,12 @@ func (c *client) postBody(endpoint string, data interface{}) (int, []byte, error
jsonData, err := json.Marshal(data) jsonData, err := json.Marshal(data)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("sonarr client could not marshal data: %v", reqUrl) return 0, nil, errors.Wrap(err, "could not marshal data: %+v", data)
return 0, nil, err
} }
req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData)) req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData))
if err != nil { if err != nil {
log.Error().Err(err).Msgf("sonarr client request error: %v", reqUrl) return 0, nil, errors.Wrap(err, "could not build request")
return 0, nil, err
} }
if c.config.BasicAuth { if c.config.BasicAuth {
@ -115,19 +104,20 @@ func (c *client) postBody(endpoint string, data interface{}) (int, []byte, error
resp, err := c.http.Do(req) resp, err := c.http.Do(req)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("sonarr client request error: %v", reqUrl) return 0, nil, errors.Wrap(err, "sonarr.http.Do(req): %+v", req)
return 0, nil, fmt.Errorf("sonarr.http.Do(req): %w", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
var buf bytes.Buffer var buf bytes.Buffer
if _, err = io.Copy(&buf, resp.Body); err != nil { if _, err = io.Copy(&buf, resp.Body); err != nil {
return resp.StatusCode, nil, fmt.Errorf("sonarr.io.Copy: %w", err) return resp.StatusCode, nil, errors.Wrap(err, "sonarr.io.Copy")
} }
if resp.StatusCode < 200 || resp.StatusCode > 299 { if resp.StatusCode == http.StatusBadRequest {
return resp.StatusCode, buf.Bytes(), fmt.Errorf("sonarr: bad request: %v (status: %s): %s", resp.Request.RequestURI, resp.Status, buf.String()) return resp.StatusCode, buf.Bytes(), nil
} else if resp.StatusCode < 200 || resp.StatusCode > 401 {
return resp.StatusCode, buf.Bytes(), errors.New("sonarr: bad request: %v (status: %s): %s", resp.Request.RequestURI, resp.Status, buf.String())
} }
return resp.StatusCode, buf.Bytes(), nil return resp.StatusCode, buf.Bytes(), nil

View file

@ -2,12 +2,15 @@ package sonarr
import ( import (
"encoding/json" "encoding/json"
"errors" "fmt"
"io"
"net/http" "net/http"
"strings" "strings"
"time" "time"
"github.com/rs/zerolog/log" "log"
"github.com/autobrr/autobrr/pkg/errors"
) )
type Config struct { type Config struct {
@ -18,6 +21,8 @@ type Config struct {
BasicAuth bool BasicAuth bool
Username string Username string
Password string Password string
Log *log.Logger
} }
type Client interface { type Client interface {
@ -28,6 +33,8 @@ type Client interface {
type client struct { type client struct {
config Config config Config
http *http.Client http *http.Client
Log *log.Logger
} }
// New create new sonarr client // New create new sonarr client
@ -40,6 +47,12 @@ func New(config Config) Client {
c := &client{ c := &client{
config: config, config: config,
http: httpClient, http: httpClient,
Log: config.Log,
}
if config.Log == nil {
// if no provided logger then use io.Discard
c.Log = log.New(io.Discard, "", log.LstdFlags)
} }
return c return c
@ -62,6 +75,13 @@ type PushResponse struct {
Rejections []string `json:"rejections"` Rejections []string `json:"rejections"`
} }
type BadRequestResponse struct {
PropertyName string `json:"propertyName"`
ErrorMessage string `json:"errorMessage"`
AttemptedValue string `json:"attemptedValue"`
Severity string `json:"severity"`
}
type SystemStatusResponse struct { type SystemStatusResponse struct {
Version string `json:"version"` Version string `json:"version"`
} }
@ -69,21 +89,19 @@ type SystemStatusResponse struct {
func (c *client) Test() (*SystemStatusResponse, error) { func (c *client) Test() (*SystemStatusResponse, error) {
status, res, err := c.get("system/status") status, res, err := c.get("system/status")
if err != nil { if err != nil {
log.Error().Stack().Err(err).Msg("sonarr client get error") return nil, errors.Wrap(err, "could not make Test")
return nil, err
} }
if status == http.StatusUnauthorized { if status == http.StatusUnauthorized {
return nil, errors.New("unauthorized: bad credentials") return nil, errors.New("unauthorized: bad credentials")
} }
log.Trace().Msgf("sonarr system/status response: %v", string(res)) c.Log.Printf("sonarr system/status status: (%v) response: %v\n", status, string(res))
response := SystemStatusResponse{} response := SystemStatusResponse{}
err = json.Unmarshal(res, &response) err = json.Unmarshal(res, &response)
if err != nil { if err != nil {
log.Error().Stack().Err(err).Msg("sonarr client error json unmarshal") return nil, errors.Wrap(err, "could not unmarshal data")
return nil, err
} }
return &response, nil return &response, nil
@ -92,24 +110,35 @@ func (c *client) Test() (*SystemStatusResponse, error) {
func (c *client) Push(release Release) ([]string, error) { func (c *client) Push(release Release) ([]string, error) {
status, res, err := c.postBody("release/push", release) status, res, err := c.postBody("release/push", release)
if err != nil { if err != nil {
log.Error().Stack().Err(err).Msg("sonarr client post error") return nil, errors.Wrap(err, "could not push release to sonarr")
return nil, err
} }
log.Trace().Msgf("sonarr release/push response status: (%v) body: %v", status, string(res)) c.Log.Printf("sonarr release/push status: (%v) response: %v\n", status, string(res))
if status == http.StatusBadRequest {
badreqResponse := make([]*BadRequestResponse, 0)
err = json.Unmarshal(res, &badreqResponse)
if err != nil {
return nil, errors.Wrap(err, "could not unmarshal data")
}
if badreqResponse[0] != nil && badreqResponse[0].PropertyName == "Title" && badreqResponse[0].ErrorMessage == "Unable to parse" {
rejections := []string{fmt.Sprintf("unable to parse: %v", badreqResponse[0].AttemptedValue)}
return rejections, err
}
}
pushResponse := make([]PushResponse, 0) pushResponse := make([]PushResponse, 0)
err = json.Unmarshal(res, &pushResponse) err = json.Unmarshal(res, &pushResponse)
if err != nil { if err != nil {
log.Error().Stack().Err(err).Msg("sonarr client error json unmarshal") return nil, errors.Wrap(err, "could not unmarshal data")
return nil, err
} }
// log and return if rejected // log and return if rejected
if pushResponse[0].Rejected { if pushResponse[0].Rejected {
rejections := strings.Join(pushResponse[0].Rejections, ", ") rejections := strings.Join(pushResponse[0].Rejections, ", ")
log.Trace().Msgf("sonarr push rejected: %s - reasons: %q", release.Title, rejections) c.Log.Printf("sonarr release/push rejected %v reasons: %q\n", release.Title, rejections)
return pushResponse[0].Rejections, nil return pushResponse[0].Rejections, nil
} }

View file

@ -1,8 +1,8 @@
package sonarr package sonarr
import ( import (
"errors"
"io/ioutil" "io/ioutil"
"log"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
@ -15,6 +15,7 @@ import (
func Test_client_Push(t *testing.T) { func Test_client_Push(t *testing.T) {
// disable logger // disable logger
zerolog.SetGlobalLevel(zerolog.Disabled) zerolog.SetGlobalLevel(zerolog.Disabled)
log.SetOutput(ioutil.Discard)
mux := http.NewServeMux() mux := http.NewServeMux()
ts := httptest.NewServer(mux) ts := httptest.NewServer(mux)
@ -119,6 +120,7 @@ func Test_client_Push(t *testing.T) {
func Test_client_Test(t *testing.T) { func Test_client_Test(t *testing.T) {
// disable logger // disable logger
zerolog.SetGlobalLevel(zerolog.Disabled) zerolog.SetGlobalLevel(zerolog.Disabled)
log.SetOutput(ioutil.Discard)
key := "mock-key" key := "mock-key"
@ -139,11 +141,11 @@ func Test_client_Test(t *testing.T) {
defer srv.Close() defer srv.Close()
tests := []struct { tests := []struct {
name string name string
cfg Config cfg Config
want *SystemStatusResponse want *SystemStatusResponse
err error expectedErr string
wantErr bool wantErr bool
}{ }{
{ {
name: "fetch", name: "fetch",
@ -154,9 +156,9 @@ func Test_client_Test(t *testing.T) {
Username: "", Username: "",
Password: "", Password: "",
}, },
want: &SystemStatusResponse{Version: "3.0.6.1196"}, want: &SystemStatusResponse{Version: "3.0.6.1196"},
err: nil, expectedErr: "",
wantErr: false, wantErr: false,
}, },
{ {
name: "fetch_unauthorized", name: "fetch_unauthorized",
@ -167,9 +169,9 @@ func Test_client_Test(t *testing.T) {
Username: "", Username: "",
Password: "", Password: "",
}, },
want: nil, want: nil,
wantErr: true, wantErr: true,
err: errors.New("unauthorized: bad credentials"), expectedErr: "unauthorized: bad credentials",
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@ -178,7 +180,7 @@ func Test_client_Test(t *testing.T) {
got, err := c.Test() got, err := c.Test()
if tt.wantErr && assert.Error(t, err) { if tt.wantErr && assert.Error(t, err) {
assert.Equal(t, tt.err, err) assert.EqualErrorf(t, err, tt.expectedErr, "Error should be: %v, got: %v", tt.wantErr, err)
} }
assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, got)

View file

@ -5,12 +5,11 @@ import (
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"net/url" "net/url"
"time" "time"
"github.com/pkg/errors" "github.com/autobrr/autobrr/pkg/errors"
) )
type Response struct { type Response struct {
@ -69,12 +68,12 @@ func (t *Time) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
err := d.DecodeElement(&raw, &start) err := d.DecodeElement(&raw, &start)
if err != nil { if err != nil {
return err return errors.Wrap(err, "could not decode element")
} }
date, err := time.Parse(time.RFC1123Z, raw)
date, err := time.Parse(time.RFC1123Z, raw)
if err != nil { if err != nil {
return err return errors.Wrap(err, "could not parse date")
} }
*t = Time{date} *t = Time{date}
@ -115,7 +114,7 @@ func (c *Client) get(endpoint string, opts map[string]string) (int, *Response, e
req, err := http.NewRequest("GET", reqUrl, nil) req, err := http.NewRequest("GET", reqUrl, nil)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, errors.Wrap(err, "could not build request")
} }
if c.UseBasicAuth { if c.UseBasicAuth {
@ -128,19 +127,19 @@ func (c *Client) get(endpoint string, opts map[string]string) (int, *Response, e
resp, err := c.http.Do(req) resp, err := c.http.Do(req)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, errors.Wrap(err, "could not make request. %+v", req)
} }
defer resp.Body.Close() defer resp.Body.Close()
var buf bytes.Buffer var buf bytes.Buffer
if _, err = io.Copy(&buf, resp.Body); err != nil { if _, err = io.Copy(&buf, resp.Body); err != nil {
return resp.StatusCode, nil, fmt.Errorf("torznab.io.Copy: %w", err) return resp.StatusCode, nil, errors.Wrap(err, "torznab.io.Copy")
} }
var response Response var response Response
if err := xml.Unmarshal(buf.Bytes(), &response); err != nil { if err := xml.Unmarshal(buf.Bytes(), &response); err != nil {
return resp.StatusCode, nil, fmt.Errorf("torznab: could not decode feed: %w", err) return resp.StatusCode, nil, errors.Wrap(err, "torznab: could not decode feed")
} }
return resp.StatusCode, &response, nil return resp.StatusCode, &response, nil
@ -149,12 +148,11 @@ func (c *Client) get(endpoint string, opts map[string]string) (int, *Response, e
func (c *Client) GetFeed() ([]FeedItem, error) { func (c *Client) GetFeed() ([]FeedItem, error) {
status, res, err := c.get("?t=search", nil) status, res, err := c.get("?t=search", nil)
if err != nil { if err != nil {
//log.Fatalf("error fetching torznab feed: %v", err) return nil, errors.Wrap(err, "could not get feed")
return nil, err
} }
if status != http.StatusOK { if status != http.StatusOK {
return nil, err return nil, errors.New("could not get feed")
} }
return res.Channel.Items, nil return res.Channel.Items, nil
@ -167,11 +165,11 @@ func (c *Client) Search(query string) ([]FeedItem, error) {
status, res, err := c.get("&t=search&"+params, nil) status, res, err := c.get("&t=search&"+params, nil)
if err != nil { if err != nil {
log.Fatalf("error fetching torznab feed: %v", err) return nil, errors.Wrap(err, "could not search feed")
} }
if status != http.StatusOK { if status != http.StatusOK {
return nil, err return nil, errors.New("could not search feed")
} }
return res.Channel.Items, nil return res.Channel.Items, nil

View file

@ -3,12 +3,11 @@ package whisparr
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"github.com/rs/zerolog/log" "github.com/autobrr/autobrr/pkg/errors"
) )
func (c *client) get(endpoint string) (*http.Response, error) { func (c *client) get(endpoint string) (*http.Response, error) {
@ -18,8 +17,7 @@ func (c *client) get(endpoint string) (*http.Response, error) {
req, err := http.NewRequest(http.MethodGet, reqUrl, http.NoBody) req, err := http.NewRequest(http.MethodGet, reqUrl, http.NoBody)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("whisparr client request error : %v", reqUrl) return nil, errors.Wrap(err, "could not build request")
return nil, err
} }
if c.config.BasicAuth { if c.config.BasicAuth {
@ -31,8 +29,7 @@ func (c *client) get(endpoint string) (*http.Response, error) {
res, err := c.http.Do(req) res, err := c.http.Do(req)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("whisparr client request error : %v", reqUrl) return nil, errors.Wrap(err, "could not make request: %+v", req)
return nil, err
} }
if res.StatusCode == http.StatusUnauthorized { if res.StatusCode == http.StatusUnauthorized {
@ -49,14 +46,12 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error)
jsonData, err := json.Marshal(data) jsonData, err := json.Marshal(data)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("whisparr client could not marshal data: %v", reqUrl) return nil, errors.Wrap(err, "could not marshal data: %+v", data)
return nil, err
} }
req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData)) req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData))
if err != nil { if err != nil {
log.Error().Err(err).Msgf("whisparr client request error: %v", reqUrl) return nil, errors.Wrap(err, "could not build request")
return nil, err
} }
if c.config.BasicAuth { if c.config.BasicAuth {
@ -69,16 +64,13 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error)
res, err := c.http.Do(req) res, err := c.http.Do(req)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("whisparr client request error: %v", reqUrl) return nil, errors.Wrap(err, "could not make request: %+v", req)
return nil, err
} }
// validate response // validate response
if res.StatusCode == http.StatusUnauthorized { if res.StatusCode == http.StatusUnauthorized {
log.Error().Err(err).Msgf("whisparr client bad request: %v", reqUrl)
return nil, errors.New("unauthorized: bad credentials") return nil, errors.New("unauthorized: bad credentials")
} else if res.StatusCode != http.StatusOK { } else if res.StatusCode != http.StatusOK {
log.Error().Err(err).Msgf("whisparr client request error: %v", reqUrl)
return nil, errors.New("whisparr: bad request") return nil, errors.New("whisparr: bad request")
} }

View file

@ -3,11 +3,12 @@ package whisparr
import ( import (
"encoding/json" "encoding/json"
"io" "io"
"log"
"net/http" "net/http"
"strings" "strings"
"time" "time"
"github.com/rs/zerolog/log" "github.com/autobrr/autobrr/pkg/errors"
) )
type Config struct { type Config struct {
@ -18,6 +19,8 @@ type Config struct {
BasicAuth bool BasicAuth bool
Username string Username string
Password string Password string
Log *log.Logger
} }
type Client interface { type Client interface {
@ -28,6 +31,8 @@ type Client interface {
type client struct { type client struct {
config Config config Config
http *http.Client http *http.Client
Log *log.Logger
} }
func New(config Config) Client { func New(config Config) Client {
@ -39,6 +44,11 @@ func New(config Config) Client {
c := &client{ c := &client{
config: config, config: config,
http: httpClient, http: httpClient,
Log: config.Log,
}
if config.Log == nil {
c.Log = log.New(io.Discard, "", log.LstdFlags)
} }
return c return c
@ -68,26 +78,23 @@ type SystemStatusResponse struct {
func (c *client) Test() (*SystemStatusResponse, error) { func (c *client) Test() (*SystemStatusResponse, error) {
res, err := c.get("system/status") res, err := c.get("system/status")
if err != nil { if err != nil {
log.Error().Stack().Err(err).Msg("whisparr client get error") return nil, errors.Wrap(err, "could not test whisparr")
return nil, err
} }
defer res.Body.Close() defer res.Body.Close()
body, err := io.ReadAll(res.Body) body, err := io.ReadAll(res.Body)
if err != nil { if err != nil {
log.Error().Stack().Err(err).Msg("whisparr client error reading body") return nil, errors.Wrap(err, "could not read body")
return nil, err
} }
response := SystemStatusResponse{} response := SystemStatusResponse{}
err = json.Unmarshal(body, &response) err = json.Unmarshal(body, &response)
if err != nil { if err != nil {
log.Error().Stack().Err(err).Msg("whisparr client error json unmarshal") return nil, errors.Wrap(err, "could not unmarshal data")
return nil, err
} }
log.Trace().Msgf("whisparr system/status response: %+v", response) c.Log.Printf("whisparr system/status status: (%v) response: %v\n", res.Status, string(body))
return &response, nil return &response, nil
} }
@ -95,8 +102,7 @@ func (c *client) Test() (*SystemStatusResponse, error) {
func (c *client) Push(release Release) ([]string, error) { func (c *client) Push(release Release) ([]string, error) {
res, err := c.post("release/push", release) res, err := c.post("release/push", release)
if err != nil { if err != nil {
log.Error().Stack().Err(err).Msg("whisparr client post error") return nil, errors.Wrap(err, "could not push release to whisparr: %+v", release)
return nil, err
} }
if res == nil { if res == nil {
@ -107,24 +113,22 @@ func (c *client) Push(release Release) ([]string, error) {
body, err := io.ReadAll(res.Body) body, err := io.ReadAll(res.Body)
if err != nil { if err != nil {
log.Error().Stack().Err(err).Msg("whisparr client error reading body") return nil, errors.Wrap(err, "could not read body")
return nil, err
} }
pushResponse := make([]PushResponse, 0) pushResponse := make([]PushResponse, 0)
err = json.Unmarshal(body, &pushResponse) err = json.Unmarshal(body, &pushResponse)
if err != nil { if err != nil {
log.Error().Stack().Err(err).Msg("whisparr client error json unmarshal") return nil, errors.Wrap(err, "could not unmarshal data")
return nil, err
} }
log.Trace().Msgf("whisparr release/push response body: %+v", string(body)) c.Log.Printf("whisparr release/push status: (%v) response: %v\n", res.Status, string(body))
// log and return if rejected // log and return if rejected
if pushResponse[0].Rejected { if pushResponse[0].Rejected {
rejections := strings.Join(pushResponse[0].Rejections, ", ") rejections := strings.Join(pushResponse[0].Rejections, ", ")
log.Trace().Msgf("whisparr push rejected: %s - reasons: %q", release.Title, rejections) c.Log.Printf("whisparr release/push rejected %v reasons: %q\n", release.Title, rejections)
return pushResponse[0].Rejections, nil return pushResponse[0].Rejections, nil
} }