mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 08:49:13 +00:00
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:
parent
95471a4cf7
commit
0e88117702
69 changed files with 1172 additions and 957 deletions
|
@ -1,21 +1,20 @@
|
|||
package btn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
)
|
||||
|
||||
func (c *Client) TestAPI() (bool, error) {
|
||||
res, err := c.rpcClient.Call("userInfo", [2]string{c.APIKey})
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, errors.Wrap(err, "test api userInfo failed")
|
||||
}
|
||||
|
||||
var u *UserInfo
|
||||
err = res.GetObject(&u)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, errors.Wrap(err, "test api get userInfo")
|
||||
}
|
||||
|
||||
if u.Username != "" {
|
||||
|
@ -27,12 +26,12 @@ func (c *Client) TestAPI() (bool, error) {
|
|||
|
||||
func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) {
|
||||
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})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "call getTorrentById failed")
|
||||
}
|
||||
|
||||
var r *domain.TorrentBasic
|
||||
|
|
|
@ -2,10 +2,13 @@ package btn
|
|||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
"github.com/autobrr/autobrr/pkg/jsonrpc"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
|
@ -23,6 +26,8 @@ type Client struct {
|
|||
Ratelimiter *rate.Limiter
|
||||
APIKey string
|
||||
Headers http.Header
|
||||
|
||||
Log *log.Logger
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if c.Log == nil {
|
||||
c.Log = log.New(io.Discard, "", log.LstdFlags)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
|
@ -48,11 +57,11 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
|||
ctx := context.Background()
|
||||
err := c.Ratelimiter.Wait(ctx) // This is a blocking call. Honors the rate limit
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error waiting for ratelimiter")
|
||||
}
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not make request")
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
|
171
pkg/errors/errors.go
Normal file
171
pkg/errors/errors.go
Normal 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
|
||||
}
|
|
@ -3,7 +3,6 @@ package ggn
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
@ -12,6 +11,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
@ -147,11 +147,11 @@ func (c *client) Do(req *http.Request) (*http.Response, error) {
|
|||
ctx := context.Background()
|
||||
err := c.Ratelimiter.Wait(ctx) // This is a blocking call. Honors the rate limit
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error waiting for ratelimiter")
|
||||
}
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error making request")
|
||||
}
|
||||
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) {
|
||||
req, err := http.NewRequest(http.MethodGet, url, http.NoBody)
|
||||
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)
|
||||
|
@ -167,7 +167,7 @@ func (c *client) get(url string) (*http.Response, error) {
|
|||
|
||||
res, err := c.Do(req)
|
||||
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 {
|
||||
|
@ -183,7 +183,7 @@ func (c *client) get(url string) (*http.Response, error) {
|
|||
|
||||
func (c *client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) {
|
||||
if torrentID == "" {
|
||||
return nil, fmt.Errorf("ggn client: must have torrentID")
|
||||
return nil, errors.New("ggn client: must have torrentID")
|
||||
}
|
||||
|
||||
var r Response
|
||||
|
@ -192,27 +192,27 @@ func (c *client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error)
|
|||
v.Add("id", torrentID)
|
||||
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 {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error getting data")
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, readErr := ioutil.ReadAll(resp.Body)
|
||||
if readErr != nil {
|
||||
return nil, readErr
|
||||
return nil, errors.Wrap(readErr, "error reading body")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error unmarshal body")
|
||||
}
|
||||
|
||||
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{
|
||||
|
@ -229,7 +229,7 @@ func (c *client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error)
|
|||
func (c *client) TestAPI() (bool, error) {
|
||||
resp, err := c.get(c.Url)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, errors.Wrap(err, "error getting data")
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
|
|
@ -7,6 +7,8 @@ import (
|
|||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
)
|
||||
|
||||
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) {
|
||||
body, err := json.Marshal(req)
|
||||
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))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error creating request")
|
||||
}
|
||||
request.Header.Set("Content-Type", "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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not create rpc http request")
|
||||
}
|
||||
|
||||
httpResponse, err := c.httpClient.Do(httpRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error during rpc http request")
|
||||
}
|
||||
|
||||
defer httpResponse.Body.Close()
|
||||
|
@ -149,7 +151,7 @@ func (c *rpcClient) doCall(request RPCRequest) (*RPCResponse, error) {
|
|||
|
||||
if err != nil {
|
||||
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 {
|
||||
// return nil, errors.New("unauthorized: bad credentials")
|
||||
|
@ -167,7 +169,7 @@ func (c *rpcClient) doCall(request RPCRequest) (*RPCResponse, error) {
|
|||
}
|
||||
|
||||
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
|
||||
|
@ -220,12 +222,12 @@ func Params(params ...interface{}) interface{} {
|
|||
func (r *RPCResponse) GetObject(toType interface{}) error {
|
||||
js, err := json.Marshal(r.Result)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "could not marshal object")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(js, toType)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "could not unmarshal object")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -3,12 +3,12 @@ package lidarr
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
)
|
||||
|
||||
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)
|
||||
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 {
|
||||
|
@ -29,14 +29,14 @@ func (c *client) get(endpoint string) (int, []byte, error) {
|
|||
|
||||
resp, err := c.http.Do(req)
|
||||
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()
|
||||
|
||||
var buf bytes.Buffer
|
||||
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
|
||||
|
@ -49,12 +49,12 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error)
|
|||
|
||||
jsonData, err := json.Marshal(data)
|
||||
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))
|
||||
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 {
|
||||
|
@ -67,7 +67,7 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error)
|
|||
|
||||
res, err := c.http.Do(req)
|
||||
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
|
||||
|
@ -88,12 +88,12 @@ func (c *client) postBody(endpoint string, data interface{}) (int, []byte, error
|
|||
|
||||
jsonData, err := json.Marshal(data)
|
||||
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))
|
||||
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 {
|
||||
|
@ -104,18 +104,20 @@ func (c *client) postBody(endpoint string, data interface{}) (int, []byte, error
|
|||
|
||||
resp, err := c.http.Do(req)
|
||||
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()
|
||||
|
||||
var buf bytes.Buffer
|
||||
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 {
|
||||
return resp.StatusCode, buf.Bytes(), fmt.Errorf("lidarr: bad request: %v (status: %s): %s", resp.Request.RequestURI, resp.Status, buf.String())
|
||||
if resp.StatusCode == http.StatusBadRequest {
|
||||
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
|
||||
|
|
|
@ -2,11 +2,14 @@ package lidarr
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
|
@ -17,6 +20,8 @@ type Config struct {
|
|||
BasicAuth bool
|
||||
Username string
|
||||
Password string
|
||||
|
||||
Log *log.Logger
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
|
@ -27,6 +32,8 @@ type Client interface {
|
|||
type client struct {
|
||||
config Config
|
||||
http *http.Client
|
||||
|
||||
Log *log.Logger
|
||||
}
|
||||
|
||||
// New create new lidarr client
|
||||
|
@ -39,6 +46,11 @@ func New(config Config) Client {
|
|||
c := &client{
|
||||
config: config,
|
||||
http: httpClient,
|
||||
Log: config.Log,
|
||||
}
|
||||
|
||||
if config.Log == nil {
|
||||
c.Log = log.New(io.Discard, "", log.LstdFlags)
|
||||
}
|
||||
|
||||
return c
|
||||
|
@ -61,6 +73,13 @@ type PushResponse struct {
|
|||
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 {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
@ -68,43 +87,56 @@ type SystemStatusResponse struct {
|
|||
func (c *client) Test() (*SystemStatusResponse, error) {
|
||||
status, res, err := c.get("system/status")
|
||||
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 {
|
||||
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{}
|
||||
err = json.Unmarshal(res, &response)
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
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{}
|
||||
err = json.Unmarshal(res, &pushResponse)
|
||||
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
|
||||
if pushResponse.Rejected {
|
||||
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
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package lidarr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
@ -134,11 +133,11 @@ func Test_client_Test(t *testing.T) {
|
|||
defer srv.Close()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg Config
|
||||
want *SystemStatusResponse
|
||||
err error
|
||||
wantErr bool
|
||||
name string
|
||||
cfg Config
|
||||
want *SystemStatusResponse
|
||||
expectedErr string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "fetch",
|
||||
|
@ -149,9 +148,9 @@ func Test_client_Test(t *testing.T) {
|
|||
Username: "",
|
||||
Password: "",
|
||||
},
|
||||
want: &SystemStatusResponse{Version: "0.8.1.2135"},
|
||||
err: nil,
|
||||
wantErr: false,
|
||||
want: &SystemStatusResponse{Version: "0.8.1.2135"},
|
||||
expectedErr: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "fetch_unauthorized",
|
||||
|
@ -162,9 +161,9 @@ func Test_client_Test(t *testing.T) {
|
|||
Username: "",
|
||||
Password: "",
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
err: errors.New("unauthorized: bad credentials"),
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
expectedErr: "unauthorized: bad credentials",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
@ -173,7 +172,7 @@ func Test_client_Test(t *testing.T) {
|
|||
|
||||
got, err := c.Test()
|
||||
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)
|
||||
|
|
|
@ -3,7 +3,6 @@ package ptp
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
@ -11,6 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
@ -87,11 +87,11 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
|||
ctx := context.Background()
|
||||
err := c.Ratelimiter.Wait(ctx) // This is a blocking call. Honors the rate limit
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error waiting for ratelimiter")
|
||||
}
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error making request")
|
||||
}
|
||||
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) {
|
||||
req, err := http.NewRequest(http.MethodGet, url, http.NoBody)
|
||||
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)
|
||||
|
@ -108,7 +108,7 @@ func (c *Client) get(url string) (*http.Response, error) {
|
|||
|
||||
res, err := c.Do(req)
|
||||
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 {
|
||||
|
@ -124,7 +124,7 @@ func (c *Client) get(url string) (*http.Response, error) {
|
|||
|
||||
func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) {
|
||||
if torrentID == "" {
|
||||
return nil, fmt.Errorf("ptp client: must have torrentID")
|
||||
return nil, errors.New("ptp client: must have torrentID")
|
||||
}
|
||||
|
||||
var r TorrentResponse
|
||||
|
@ -133,23 +133,23 @@ func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error)
|
|||
v.Add("torrentid", torrentID)
|
||||
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 {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error requesting data")
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, readErr := ioutil.ReadAll(resp.Body)
|
||||
if readErr != nil {
|
||||
return nil, readErr
|
||||
return nil, errors.Wrap(readErr, "could not read body")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(readErr, "could not unmarshal body")
|
||||
}
|
||||
|
||||
for _, torrent := range r.Torrents {
|
||||
|
@ -169,7 +169,7 @@ func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error)
|
|||
func (c *Client) TestAPI() (bool, error) {
|
||||
resp, err := c.get(c.Url)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, errors.Wrap(err, "error requesting data")
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
|
@ -14,8 +15,9 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
|
||||
publicsuffix "golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -31,6 +33,8 @@ type Client struct {
|
|||
Name string
|
||||
settings Settings
|
||||
http *http.Client
|
||||
|
||||
Log *log.Logger
|
||||
}
|
||||
|
||||
type Settings struct {
|
||||
|
@ -43,6 +47,7 @@ type Settings struct {
|
|||
protocol string
|
||||
BasicAuth bool
|
||||
Basic Basic
|
||||
Log *log.Logger
|
||||
}
|
||||
|
||||
type Basic struct {
|
||||
|
@ -51,20 +56,24 @@ type Basic struct {
|
|||
}
|
||||
|
||||
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{
|
||||
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"
|
||||
|
@ -92,8 +101,7 @@ func (c *Client) get(endpoint string, opts map[string]string) (*http.Response, e
|
|||
|
||||
req, err := http.NewRequest("GET", reqUrl, nil)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("GET: error %v", reqUrl)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not build request")
|
||||
}
|
||||
|
||||
if c.settings.BasicAuth {
|
||||
|
@ -109,14 +117,13 @@ func (c *Client) get(endpoint string, opts map[string]string) (*http.Response, e
|
|||
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)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("GET: do %v", reqUrl)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error making get request: %v", reqUrl)
|
||||
}
|
||||
|
||||
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()))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("POST: req %v", reqUrl)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not build request")
|
||||
}
|
||||
|
||||
if c.settings.BasicAuth {
|
||||
|
@ -158,14 +164,13 @@ func (c *Client) post(endpoint string, opts map[string]string) (*http.Response,
|
|||
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)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("POST: do %v", reqUrl)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error making post request: %v", reqUrl)
|
||||
}
|
||||
|
||||
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()))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("POST: req %v", reqUrl)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not build request")
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("POST: do %v", reqUrl)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error making post request: %v", reqUrl)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
@ -213,8 +216,7 @@ func (c *Client) postFile(endpoint string, fileName string, opts map[string]stri
|
|||
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("POST file: opening file %v", fileName)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error opening file %v", fileName)
|
||||
}
|
||||
// Close the file later
|
||||
defer file.Close()
|
||||
|
@ -228,15 +230,13 @@ func (c *Client) postFile(endpoint string, fileName string, opts map[string]stri
|
|||
// Initialize file field
|
||||
fileWriter, err := multiPartWriter.CreateFormFile("torrents", fileName)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("POST file: initializing file field %v", fileName)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error initializing file field %v", fileName)
|
||||
}
|
||||
|
||||
// Copy the actual file content to the fields writer
|
||||
_, err = io.Copy(fileWriter, file)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("POST file: could not copy file to writer %v", fileName)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error copy file contents to writer %v", fileName)
|
||||
}
|
||||
|
||||
// Populate other fields
|
||||
|
@ -244,14 +244,12 @@ func (c *Client) postFile(endpoint string, fileName string, opts map[string]stri
|
|||
for key, val := range opts {
|
||||
fieldWriter, err := multiPartWriter.CreateFormField(key)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("POST file: could not add other fields %v", fileName)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error creating form field %v with value %v", key, val)
|
||||
}
|
||||
|
||||
_, err = fieldWriter.Write([]byte(val))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("POST file: could not write field %v", fileName)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error writing field %v with value %v", key, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -262,8 +260,7 @@ func (c *Client) postFile(endpoint string, fileName string, opts map[string]stri
|
|||
reqUrl := buildUrl(c.settings, endpoint)
|
||||
req, err := http.NewRequest("POST", reqUrl, &requestBody)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("POST file: could not create request object %v", fileName)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error creating request %v", fileName)
|
||||
}
|
||||
|
||||
if c.settings.BasicAuth {
|
||||
|
@ -282,14 +279,13 @@ func (c *Client) postFile(endpoint string, fileName string, opts map[string]stri
|
|||
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)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("POST file: could not perform request %v", fileName)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error making post file request %v", fileName)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
|
|
@ -2,14 +2,13 @@ package qbittorrent
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strconv"
|
||||
"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
|
||||
|
@ -21,15 +20,12 @@ func (c *Client) Login() error {
|
|||
|
||||
resp, err := c.postBasic("auth/login", opts)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("login error")
|
||||
return err
|
||||
return errors.Wrap(err, "login error")
|
||||
} else if resp.StatusCode == http.StatusForbidden {
|
||||
log.Error().Err(err).Msg("User's IP is banned for too many failed login attempts")
|
||||
return err
|
||||
return errors.New("User's IP is banned for too many failed login attempts")
|
||||
|
||||
} 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")
|
||||
return errors.New("qbittorrent login bad status %v", resp.StatusCode)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
@ -61,23 +57,20 @@ func (c *Client) GetTorrents() ([]Torrent, error) {
|
|||
|
||||
resp, err := c.get("torrents/info", nil)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("get torrents error")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "get torrents error")
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, readErr := ioutil.ReadAll(resp.Body)
|
||||
if readErr != nil {
|
||||
log.Error().Err(err).Msg("get torrents read error")
|
||||
return nil, readErr
|
||||
return nil, errors.Wrap(readErr, "could not read body")
|
||||
}
|
||||
|
||||
var torrents []Torrent
|
||||
err = json.Unmarshal(body, &torrents)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("get torrents unmarshal error")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not unmarshal body")
|
||||
}
|
||||
|
||||
return torrents, nil
|
||||
|
@ -90,23 +83,20 @@ func (c *Client) GetTorrentsFilter(filter TorrentFilter) ([]Torrent, error) {
|
|||
|
||||
resp, err := c.get("torrents/info", opts)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("get filtered torrents error: %v", filter)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not get filtered torrents with filter: %v", filter)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, readErr := ioutil.ReadAll(resp.Body)
|
||||
if readErr != nil {
|
||||
log.Error().Err(err).Msgf("get filtered torrents read error: %v", filter)
|
||||
return nil, readErr
|
||||
return nil, errors.Wrap(readErr, "could not read body")
|
||||
}
|
||||
|
||||
var torrents []Torrent
|
||||
err = json.Unmarshal(body, &torrents)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("get filtered torrents unmarshal error: %v", filter)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not unmarshal body")
|
||||
}
|
||||
|
||||
return torrents, nil
|
||||
|
@ -121,23 +111,20 @@ func (c *Client) GetTorrentsActiveDownloads() ([]Torrent, error) {
|
|||
|
||||
resp, err := c.get("torrents/info", opts)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("get filtered torrents error: %v", filter)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not get active torrents")
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, readErr := ioutil.ReadAll(resp.Body)
|
||||
if readErr != nil {
|
||||
log.Error().Err(err).Msgf("get filtered torrents read error: %v", filter)
|
||||
return nil, readErr
|
||||
return nil, errors.Wrap(readErr, "could not read body")
|
||||
}
|
||||
|
||||
var torrents []Torrent
|
||||
err = json.Unmarshal(body, &torrents)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("get filtered torrents unmarshal error: %v", filter)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(readErr, "could not unmarshal body")
|
||||
}
|
||||
|
||||
res := make([]Torrent, 0)
|
||||
|
@ -155,13 +142,15 @@ func (c *Client) GetTorrentsActiveDownloads() ([]Torrent, error) {
|
|||
func (c *Client) GetTorrentsRaw() (string, error) {
|
||||
resp, err := c.get("torrents/info", nil)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("get torrent trackers raw error")
|
||||
return "", err
|
||||
return "", errors.Wrap(err, "could not get torrents raw")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -173,40 +162,35 @@ func (c *Client) GetTorrentTrackers(hash string) ([]TorrentTracker, error) {
|
|||
|
||||
resp, err := c.get("torrents/trackers", opts)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("get torrent trackers error: %v", hash)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not get torrent trackers for hash: %v", hash)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
dump, err := httputil.DumpResponse(resp, true)
|
||||
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 {
|
||||
//return nil, fmt.Errorf("torrent not found: %v", hash)
|
||||
return nil, nil
|
||||
} else if resp.StatusCode == http.StatusForbidden {
|
||||
//return nil, fmt.Errorf("torrent not found: %v", hash)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
body, readErr := ioutil.ReadAll(resp.Body)
|
||||
if readErr != nil {
|
||||
log.Error().Err(err).Msgf("get torrent trackers read error: %v", hash)
|
||||
return nil, readErr
|
||||
return nil, errors.Wrap(err, "could not read body")
|
||||
}
|
||||
|
||||
log.Trace().Msgf("get torrent trackers body: %v", string(body))
|
||||
c.Log.Printf("get torrent trackers body: %v\n", string(body))
|
||||
|
||||
var trackers []TorrentTracker
|
||||
err = json.Unmarshal(body, &trackers)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("get torrent trackers: %v", hash)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not unmarshal body")
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("add torrents error: %v", file)
|
||||
return err
|
||||
return errors.Wrap(err, "could not add torrent %v", file)
|
||||
} else if res.StatusCode != http.StatusOK {
|
||||
log.Error().Err(err).Msgf("add torrents bad status: %v", file)
|
||||
return err
|
||||
return errors.Wrap(err, "could not add torrent %v unexpected status: %v", file, res.StatusCode)
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
@ -240,11 +222,9 @@ func (c *Client) DeleteTorrents(hashes []string, deleteFiles bool) error {
|
|||
|
||||
resp, err := c.get("torrents/delete", opts)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("delete torrents error: %v", hashes)
|
||||
return err
|
||||
return errors.Wrap(err, "could not delete torrents: %+v", hashes)
|
||||
} else if resp.StatusCode != http.StatusOK {
|
||||
log.Error().Err(err).Msgf("delete torrents bad code: %v", hashes)
|
||||
return err
|
||||
return errors.Wrap(err, "could not delete torrents %v unexpected status: %v", hashes, resp.StatusCode)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
@ -261,11 +241,9 @@ func (c *Client) ReAnnounceTorrents(hashes []string) error {
|
|||
|
||||
resp, err := c.get("torrents/reannounce", opts)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("re-announce error: %v", hashes)
|
||||
return err
|
||||
return errors.Wrap(err, "could not re-announce torrents: %v", hashes)
|
||||
} else if resp.StatusCode != http.StatusOK {
|
||||
log.Error().Err(err).Msgf("re-announce error bad status: %v", hashes)
|
||||
return err
|
||||
return errors.Wrap(err, "could not re-announce torrents: %v unexpected status: %v", hashes, resp.StatusCode)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
@ -276,23 +254,20 @@ func (c *Client) ReAnnounceTorrents(hashes []string) error {
|
|||
func (c *Client) GetTransferInfo() (*TransferInfo, error) {
|
||||
resp, err := c.get("transfer/info", nil)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("get torrents error")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not get transfer info")
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, readErr := ioutil.ReadAll(resp.Body)
|
||||
if readErr != nil {
|
||||
log.Error().Err(err).Msg("get torrents read error")
|
||||
return nil, readErr
|
||||
return nil, errors.Wrap(readErr, "could not read body")
|
||||
}
|
||||
|
||||
var info TransferInfo
|
||||
err = json.Unmarshal(body, &info)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("get torrents unmarshal error")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(readErr, "could not unmarshal body")
|
||||
}
|
||||
|
||||
return &info, nil
|
||||
|
|
|
@ -3,14 +3,12 @@ package radarr
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
)
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("radarr client request error : %v", reqUrl)
|
||||
return 0, nil, err
|
||||
return 0, nil, errors.Wrap(err, "could not build request: %v", reqUrl)
|
||||
}
|
||||
|
||||
if c.config.BasicAuth {
|
||||
|
@ -32,15 +29,14 @@ func (c *client) get(endpoint string) (int, []byte, error) {
|
|||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("radarr client.get request error: %v", reqUrl)
|
||||
return 0, nil, fmt.Errorf("radarr.http.Do(req): %w", err)
|
||||
return 0, nil, errors.Wrap(err, "radarr.http.Do(req): %v", reqUrl)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
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
|
||||
|
@ -53,14 +49,12 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error)
|
|||
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("radarr client could not marshal data: %v", reqUrl)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not marshal data: %+v", data)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not build request: %v", reqUrl)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not make request: %+v", req)
|
||||
}
|
||||
|
||||
// validate response
|
||||
if res.StatusCode == http.StatusUnauthorized {
|
||||
log.Error().Err(err).Msgf("radarr client bad request: %v", reqUrl)
|
||||
return nil, errors.New("unauthorized: bad credentials")
|
||||
} else if res.StatusCode == http.StatusBadRequest {
|
||||
log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl)
|
||||
return nil, errors.New("radarr: bad request")
|
||||
} else if res.StatusCode != http.StatusOK {
|
||||
log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl)
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("radarr client could not marshal data: %v", reqUrl)
|
||||
return 0, nil, err
|
||||
return 0, nil, errors.Wrap(err, "could not marshal data: %+v", data)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl)
|
||||
return 0, nil, err
|
||||
return 0, nil, errors.Wrap(err, "could not build request: %v", reqUrl)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl)
|
||||
return 0, nil, fmt.Errorf("radarr.http.Do(req): %w", err)
|
||||
return 0, nil, errors.Wrap(err, "radarr.http.Do(req): %+v", req)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
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 {
|
||||
return resp.StatusCode, buf.Bytes(), fmt.Errorf("radarr: bad request: %v (status: %s): %s", resp.Request.RequestURI, resp.Status, buf.String())
|
||||
if resp.StatusCode == http.StatusBadRequest {
|
||||
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
|
||||
|
|
|
@ -2,12 +2,14 @@ package radarr
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
|
@ -18,6 +20,8 @@ type Config struct {
|
|||
BasicAuth bool
|
||||
Username string
|
||||
Password string
|
||||
|
||||
Log *log.Logger
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
|
@ -28,6 +32,8 @@ type Client interface {
|
|||
type client struct {
|
||||
config Config
|
||||
http *http.Client
|
||||
|
||||
Log *log.Logger
|
||||
}
|
||||
|
||||
func New(config Config) Client {
|
||||
|
@ -39,6 +45,11 @@ func New(config Config) Client {
|
|||
c := &client{
|
||||
config: config,
|
||||
http: httpClient,
|
||||
Log: config.Log,
|
||||
}
|
||||
|
||||
if config.Log == nil {
|
||||
c.Log = log.New(io.Discard, "", log.LstdFlags)
|
||||
}
|
||||
|
||||
return c
|
||||
|
@ -65,11 +76,17 @@ type SystemStatusResponse struct {
|
|||
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) {
|
||||
status, res, err := c.get("system/status")
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msg("radarr client get error")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "radarr error running test")
|
||||
}
|
||||
|
||||
if status == http.StatusUnauthorized {
|
||||
|
@ -79,11 +96,10 @@ func (c *client) Test() (*SystemStatusResponse, error) {
|
|||
response := SystemStatusResponse{}
|
||||
err = json.Unmarshal(res, &response)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msg("radarr client error json unmarshal")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not unmarshal data")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -91,24 +107,35 @@ func (c *client) Test() (*SystemStatusResponse, error) {
|
|||
func (c *client) Push(release Release) ([]string, error) {
|
||||
status, res, err := c.postBody("release/push", release)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msgf("radarr client post error. status: %d", status)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error push release")
|
||||
}
|
||||
|
||||
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)
|
||||
err = json.Unmarshal(res, &pushResponse)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msg("radarr client error json unmarshal")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not unmarshal data")
|
||||
}
|
||||
|
||||
log.Trace().Msgf("radarr release/push response status: %v body: %+v", status, string(res))
|
||||
|
||||
// log and return if rejected
|
||||
if pushResponse[0].Rejected {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package radarr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
@ -90,8 +89,6 @@ func Test_client_Push(t *testing.T) {
|
|||
PublishDate: "2021-08-21T15:36:00Z",
|
||||
}},
|
||||
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",
|
||||
|
@ -114,8 +111,6 @@ func Test_client_Push(t *testing.T) {
|
|||
PublishDate: "2021-08-21T15:36:00Z",
|
||||
}},
|
||||
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",
|
||||
|
@ -137,8 +132,8 @@ func Test_client_Push(t *testing.T) {
|
|||
Protocol: "torrent",
|
||||
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"),
|
||||
wantErr: true,
|
||||
rejections: []string{"unable to parse: Minx 1 epi 9 2160p"},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
@ -177,11 +172,11 @@ func Test_client_Test(t *testing.T) {
|
|||
defer srv.Close()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg Config
|
||||
want *SystemStatusResponse
|
||||
err error
|
||||
wantErr bool
|
||||
name string
|
||||
cfg Config
|
||||
want *SystemStatusResponse
|
||||
expectedErr string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "fetch",
|
||||
|
@ -192,9 +187,9 @@ func Test_client_Test(t *testing.T) {
|
|||
Username: "",
|
||||
Password: "",
|
||||
},
|
||||
want: &SystemStatusResponse{Version: "3.2.2.5080"},
|
||||
err: nil,
|
||||
wantErr: false,
|
||||
want: &SystemStatusResponse{Version: "3.2.2.5080"},
|
||||
expectedErr: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "fetch_unauthorized",
|
||||
|
@ -205,9 +200,9 @@ func Test_client_Test(t *testing.T) {
|
|||
Username: "",
|
||||
Password: "",
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
err: errors.New("unauthorized: bad credentials"),
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
expectedErr: "unauthorized: bad credentials",
|
||||
},
|
||||
{
|
||||
name: "fetch_subfolder",
|
||||
|
@ -218,9 +213,9 @@ func Test_client_Test(t *testing.T) {
|
|||
Username: "",
|
||||
Password: "",
|
||||
},
|
||||
want: &SystemStatusResponse{Version: "3.2.2.5080"},
|
||||
err: nil,
|
||||
wantErr: false,
|
||||
want: &SystemStatusResponse{Version: "3.2.2.5080"},
|
||||
expectedErr: "",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
@ -229,7 +224,7 @@ func Test_client_Test(t *testing.T) {
|
|||
|
||||
got, err := c.Test()
|
||||
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)
|
||||
|
|
|
@ -3,18 +3,17 @@ package red
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
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) {
|
||||
req, err := http.NewRequest(http.MethodGet, url, http.NoBody)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("red client request error : %v", url)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not build request")
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", c.APIKey)
|
||||
|
@ -140,8 +138,7 @@ func (c *Client) get(url string) (*http.Response, error) {
|
|||
|
||||
res, err := c.Do(req)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("red client request error : %v", url)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not make request: %+v", req)
|
||||
}
|
||||
|
||||
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) {
|
||||
if torrentID == "" {
|
||||
return nil, fmt.Errorf("red client: must have torrentID")
|
||||
return nil, errors.New("red client: must have torrentID")
|
||||
}
|
||||
|
||||
var r TorrentDetailsResponse
|
||||
|
@ -168,23 +165,23 @@ func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error)
|
|||
v.Add("id", torrentID)
|
||||
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 {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not get torrent by id: %v", torrentID)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, readErr := ioutil.ReadAll(resp.Body)
|
||||
if readErr != nil {
|
||||
return nil, readErr
|
||||
return nil, errors.Wrap(readErr, "could not read body")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(readErr, "could not unmarshal body")
|
||||
}
|
||||
|
||||
return &domain.TorrentBasic{
|
||||
|
@ -199,7 +196,7 @@ func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error)
|
|||
func (c *Client) TestAPI() (bool, error) {
|
||||
resp, err := c.get(c.URL + "?action=index")
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, errors.Wrap(err, "could not run test api")
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
package red
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
)
|
||||
|
||||
func TestREDClient_GetTorrentByID(t *testing.T) {
|
||||
|
@ -57,7 +55,7 @@ func TestREDClient_GetTorrentByID(t *testing.T) {
|
|||
fields fields
|
||||
args args
|
||||
want *domain.TorrentBasic
|
||||
wantErr error
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "get_by_id_1",
|
||||
|
@ -71,7 +69,7 @@ func TestREDClient_GetTorrentByID(t *testing.T) {
|
|||
InfoHash: "B2BABD3A361EAFC6C4E9142C422DF7DDF5D7E163",
|
||||
Size: "527749302",
|
||||
},
|
||||
wantErr: nil,
|
||||
wantErr: "",
|
||||
},
|
||||
{
|
||||
name: "get_by_id_2",
|
||||
|
@ -81,7 +79,7 @@ func TestREDClient_GetTorrentByID(t *testing.T) {
|
|||
},
|
||||
args: args{torrentID: "100002"},
|
||||
want: nil,
|
||||
wantErr: errors.New("bad id parameter"),
|
||||
wantErr: "could not get torrent by id: 100002: bad id parameter",
|
||||
},
|
||||
{
|
||||
name: "get_by_id_3",
|
||||
|
@ -91,7 +89,7 @@ func TestREDClient_GetTorrentByID(t *testing.T) {
|
|||
},
|
||||
args: args{torrentID: "100002"},
|
||||
want: nil,
|
||||
wantErr: errors.New("unauthorized: bad credentials"),
|
||||
wantErr: "could not get torrent by id: 100002: unauthorized: bad credentials",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
@ -99,8 +97,8 @@ func TestREDClient_GetTorrentByID(t *testing.T) {
|
|||
c := NewClient(tt.fields.Url, tt.fields.APIKey)
|
||||
|
||||
got, err := c.GetTorrentByID(tt.args.torrentID)
|
||||
if tt.wantErr != nil && assert.Error(t, err) {
|
||||
assert.Equal(t, tt.wantErr, err)
|
||||
if tt.wantErr != "" && assert.Error(t, err) {
|
||||
assert.EqualErrorf(t, err, tt.wantErr, "Error should be: %v, got: %v", tt.wantErr, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.want, got)
|
||||
|
|
|
@ -3,14 +3,12 @@ package sonarr
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
)
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("sonarr client request error : %v", reqUrl)
|
||||
return 0, nil, err
|
||||
return 0, nil, errors.Wrap(err, "could not build request")
|
||||
}
|
||||
|
||||
if c.config.BasicAuth {
|
||||
|
@ -32,15 +29,14 @@ func (c *client) get(endpoint string) (int, []byte, error) {
|
|||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("sonarr client.get request error: %v", reqUrl)
|
||||
return 0, nil, fmt.Errorf("sonarr.http.Do(req): %w", err)
|
||||
return 0, nil, errors.Wrap(err, "sonarr.http.Do(req): %+v", req)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
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
|
||||
|
@ -53,14 +49,12 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error)
|
|||
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("sonarr client could not marshal data: %v", reqUrl)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not marshal data: %+v", data)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("sonarr client request error: %v", reqUrl)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not build request")
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("sonarr client request error: %v", reqUrl)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not make request: %+v", req)
|
||||
}
|
||||
|
||||
// validate response
|
||||
if res.StatusCode == http.StatusUnauthorized {
|
||||
log.Error().Err(err).Msgf("sonarr client bad request: %v", reqUrl)
|
||||
return nil, errors.New("unauthorized: bad credentials")
|
||||
} else if res.StatusCode != http.StatusOK {
|
||||
log.Error().Err(err).Msgf("sonarr client request error: %v", reqUrl)
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("sonarr client could not marshal data: %v", reqUrl)
|
||||
return 0, nil, err
|
||||
return 0, nil, errors.Wrap(err, "could not marshal data: %+v", data)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("sonarr client request error: %v", reqUrl)
|
||||
return 0, nil, err
|
||||
return 0, nil, errors.Wrap(err, "could not build request")
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("sonarr client request error: %v", reqUrl)
|
||||
return 0, nil, fmt.Errorf("sonarr.http.Do(req): %w", err)
|
||||
return 0, nil, errors.Wrap(err, "sonarr.http.Do(req): %+v", req)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
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 {
|
||||
return resp.StatusCode, buf.Bytes(), fmt.Errorf("sonarr: bad request: %v (status: %s): %s", resp.Request.RequestURI, resp.Status, buf.String())
|
||||
if resp.StatusCode == http.StatusBadRequest {
|
||||
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
|
||||
|
|
|
@ -2,12 +2,15 @@ package sonarr
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"log"
|
||||
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
|
@ -18,6 +21,8 @@ type Config struct {
|
|||
BasicAuth bool
|
||||
Username string
|
||||
Password string
|
||||
|
||||
Log *log.Logger
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
|
@ -28,6 +33,8 @@ type Client interface {
|
|||
type client struct {
|
||||
config Config
|
||||
http *http.Client
|
||||
|
||||
Log *log.Logger
|
||||
}
|
||||
|
||||
// New create new sonarr client
|
||||
|
@ -40,6 +47,12 @@ func New(config Config) Client {
|
|||
c := &client{
|
||||
config: config,
|
||||
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
|
||||
|
@ -62,6 +75,13 @@ type PushResponse struct {
|
|||
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 {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
@ -69,21 +89,19 @@ type SystemStatusResponse struct {
|
|||
func (c *client) Test() (*SystemStatusResponse, error) {
|
||||
status, res, err := c.get("system/status")
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msg("sonarr client get error")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not make Test")
|
||||
}
|
||||
|
||||
if status == http.StatusUnauthorized {
|
||||
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{}
|
||||
err = json.Unmarshal(res, &response)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msg("sonarr client error json unmarshal")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not unmarshal data")
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
|
@ -92,24 +110,35 @@ func (c *client) Test() (*SystemStatusResponse, error) {
|
|||
func (c *client) Push(release Release) ([]string, error) {
|
||||
status, res, err := c.postBody("release/push", release)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msg("sonarr client post error")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not push release to sonarr")
|
||||
}
|
||||
|
||||
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)
|
||||
err = json.Unmarshal(res, &pushResponse)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msg("sonarr client error json unmarshal")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not unmarshal data")
|
||||
}
|
||||
|
||||
// log and return if rejected
|
||||
if pushResponse[0].Rejected {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package sonarr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
@ -15,6 +15,7 @@ import (
|
|||
func Test_client_Push(t *testing.T) {
|
||||
// disable logger
|
||||
zerolog.SetGlobalLevel(zerolog.Disabled)
|
||||
log.SetOutput(ioutil.Discard)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
ts := httptest.NewServer(mux)
|
||||
|
@ -119,6 +120,7 @@ func Test_client_Push(t *testing.T) {
|
|||
func Test_client_Test(t *testing.T) {
|
||||
// disable logger
|
||||
zerolog.SetGlobalLevel(zerolog.Disabled)
|
||||
log.SetOutput(ioutil.Discard)
|
||||
|
||||
key := "mock-key"
|
||||
|
||||
|
@ -139,11 +141,11 @@ func Test_client_Test(t *testing.T) {
|
|||
defer srv.Close()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg Config
|
||||
want *SystemStatusResponse
|
||||
err error
|
||||
wantErr bool
|
||||
name string
|
||||
cfg Config
|
||||
want *SystemStatusResponse
|
||||
expectedErr string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "fetch",
|
||||
|
@ -154,9 +156,9 @@ func Test_client_Test(t *testing.T) {
|
|||
Username: "",
|
||||
Password: "",
|
||||
},
|
||||
want: &SystemStatusResponse{Version: "3.0.6.1196"},
|
||||
err: nil,
|
||||
wantErr: false,
|
||||
want: &SystemStatusResponse{Version: "3.0.6.1196"},
|
||||
expectedErr: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "fetch_unauthorized",
|
||||
|
@ -167,9 +169,9 @@ func Test_client_Test(t *testing.T) {
|
|||
Username: "",
|
||||
Password: "",
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
err: errors.New("unauthorized: bad credentials"),
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
expectedErr: "unauthorized: bad credentials",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
@ -178,7 +180,7 @@ func Test_client_Test(t *testing.T) {
|
|||
|
||||
got, err := c.Test()
|
||||
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)
|
||||
|
|
|
@ -5,12 +5,11 @@ import (
|
|||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
|
@ -69,12 +68,12 @@ func (t *Time) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||
|
||||
err := d.DecodeElement(&raw, &start)
|
||||
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 {
|
||||
return err
|
||||
return errors.Wrap(err, "could not parse 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)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
return 0, nil, errors.Wrap(err, "could not build request")
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
return 0, nil, errors.Wrap(err, "could not make request. %+v", req)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
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
|
||||
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
|
||||
|
@ -149,12 +148,11 @@ func (c *Client) get(endpoint string, opts map[string]string) (int, *Response, e
|
|||
func (c *Client) GetFeed() ([]FeedItem, error) {
|
||||
status, res, err := c.get("?t=search", nil)
|
||||
if err != nil {
|
||||
//log.Fatalf("error fetching torznab feed: %v", err)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not get feed")
|
||||
}
|
||||
|
||||
if status != http.StatusOK {
|
||||
return nil, err
|
||||
return nil, errors.New("could not get feed")
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
log.Fatalf("error fetching torznab feed: %v", err)
|
||||
return nil, errors.Wrap(err, "could not search feed")
|
||||
}
|
||||
|
||||
if status != http.StatusOK {
|
||||
return nil, err
|
||||
return nil, errors.New("could not search feed")
|
||||
}
|
||||
|
||||
return res.Channel.Items, nil
|
||||
|
|
|
@ -3,12 +3,11 @@ package whisparr
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
)
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("whisparr client request error : %v", reqUrl)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not build request")
|
||||
}
|
||||
|
||||
if c.config.BasicAuth {
|
||||
|
@ -31,8 +29,7 @@ func (c *client) get(endpoint string) (*http.Response, error) {
|
|||
|
||||
res, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("whisparr client request error : %v", reqUrl)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not make request: %+v", req)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("whisparr client could not marshal data: %v", reqUrl)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not marshal data: %+v", data)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("whisparr client request error: %v", reqUrl)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not build request")
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("whisparr client request error: %v", reqUrl)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not make request: %+v", req)
|
||||
}
|
||||
|
||||
// validate response
|
||||
if res.StatusCode == http.StatusUnauthorized {
|
||||
log.Error().Err(err).Msgf("whisparr client bad request: %v", reqUrl)
|
||||
return nil, errors.New("unauthorized: bad credentials")
|
||||
} else if res.StatusCode != http.StatusOK {
|
||||
log.Error().Err(err).Msgf("whisparr client request error: %v", reqUrl)
|
||||
return nil, errors.New("whisparr: bad request")
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,12 @@ package whisparr
|
|||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
|
@ -18,6 +19,8 @@ type Config struct {
|
|||
BasicAuth bool
|
||||
Username string
|
||||
Password string
|
||||
|
||||
Log *log.Logger
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
|
@ -28,6 +31,8 @@ type Client interface {
|
|||
type client struct {
|
||||
config Config
|
||||
http *http.Client
|
||||
|
||||
Log *log.Logger
|
||||
}
|
||||
|
||||
func New(config Config) Client {
|
||||
|
@ -39,6 +44,11 @@ func New(config Config) Client {
|
|||
c := &client{
|
||||
config: config,
|
||||
http: httpClient,
|
||||
Log: config.Log,
|
||||
}
|
||||
|
||||
if config.Log == nil {
|
||||
c.Log = log.New(io.Discard, "", log.LstdFlags)
|
||||
}
|
||||
|
||||
return c
|
||||
|
@ -68,26 +78,23 @@ type SystemStatusResponse struct {
|
|||
func (c *client) Test() (*SystemStatusResponse, error) {
|
||||
res, err := c.get("system/status")
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msg("whisparr client get error")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not test whisparr")
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msg("whisparr client error reading body")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not read body")
|
||||
}
|
||||
|
||||
response := SystemStatusResponse{}
|
||||
err = json.Unmarshal(body, &response)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msg("whisparr client error json unmarshal")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not unmarshal data")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -95,8 +102,7 @@ func (c *client) Test() (*SystemStatusResponse, error) {
|
|||
func (c *client) Push(release Release) ([]string, error) {
|
||||
res, err := c.post("release/push", release)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msg("whisparr client post error")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not push release to whisparr: %+v", release)
|
||||
}
|
||||
|
||||
if res == nil {
|
||||
|
@ -107,24 +113,22 @@ func (c *client) Push(release Release) ([]string, error) {
|
|||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msg("whisparr client error reading body")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not read body")
|
||||
}
|
||||
|
||||
pushResponse := make([]PushResponse, 0)
|
||||
err = json.Unmarshal(body, &pushResponse)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msg("whisparr client error json unmarshal")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not unmarshal data")
|
||||
}
|
||||
|
||||
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
|
||||
if pushResponse[0].Rejected {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue