refactor(http): implement bufio (#1604)

* fix: misc http fixes

* feat(io): implement bufio around syscalls

* peek-a-boo

* this can't be right.

* you better be wearing a helmet

* jesus christ.

* refactor(notifications): check err on non-ok status

* fix(notifications): add missing name method

* refactor(indexer): api clients

* fix(indexer): ptp test

---------

Co-authored-by: ze0s <ze0s@riseup.net>
This commit is contained in:
Kyle Sanderson 2024-08-28 23:51:20 -07:00 committed by GitHub
parent d13b421c42
commit cc0cca9f0d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 465 additions and 304 deletions

View file

@ -4,10 +4,10 @@
package ggn
import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
@ -163,6 +163,13 @@ type Response struct {
Error string `json:"error,omitempty"`
}
type GetIndexResponse struct {
Status string `json:"status"`
Response struct {
ApiVersion string `json:"api_version"`
} `json:"response"`
}
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
@ -201,61 +208,96 @@ func (c *Client) get(ctx context.Context, url string) (*http.Response, error) {
return res, nil
}
func (c *Client) getJSON(ctx context.Context, params url.Values, data any) error {
reqUrl := fmt.Sprintf("%s?%s", c.url, params.Encode())
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, http.NoBody)
if err != nil {
return errors.Wrap(err, "ggn client request error : %s", reqUrl)
}
req.Header.Add("X-API-Key", c.APIKey)
req.Header.Set("User-Agent", "autobrr")
res, err := c.Do(req)
if err != nil {
return errors.Wrap(err, "ggn client request error : %s", reqUrl)
}
defer res.Body.Close()
if res.StatusCode == http.StatusUnauthorized {
return ErrUnauthorized
} else if res.StatusCode == http.StatusForbidden {
return ErrForbidden
} else if res.StatusCode == http.StatusTooManyRequests {
return ErrTooManyRequests
}
reader := bufio.NewReader(res.Body)
if err := json.NewDecoder(reader).Decode(&data); err != nil {
return errors.Wrap(err, "error unmarshal body")
}
return nil
}
func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) {
if torrentID == "" {
return nil, errors.New("ggn client: must have torrentID")
}
var r Response
var response *Response
v := url.Values{}
v.Add("id", torrentID)
params := v.Encode()
params := url.Values{}
params.Add("request", "torrent")
params.Add("id", torrentID)
reqUrl := fmt.Sprintf("%s?%s&%s", c.url, "request=torrent", params)
resp, err := c.get(ctx, reqUrl)
err := c.getJSON(ctx, params, &response)
if err != nil {
return nil, errors.Wrap(err, "error getting data")
}
defer resp.Body.Close()
body, readErr := io.ReadAll(resp.Body)
if readErr != nil {
return nil, errors.Wrap(readErr, "error reading body")
}
if err = json.Unmarshal(body, &r); err != nil {
return nil, errors.Wrap(err, "error unmarshal body")
}
if r.Status != "success" {
return nil, errors.New("bad status: %s", r.Status)
if response.Status != "success" {
return nil, errors.New("bad status: %s", response.Status)
}
t := &domain.TorrentBasic{
Id: strconv.Itoa(r.Response.Torrent.Id),
InfoHash: r.Response.Torrent.InfoHash,
Size: strconv.FormatUint(r.Response.Torrent.Size, 10),
Id: strconv.Itoa(response.Response.Torrent.Id),
InfoHash: response.Response.Torrent.InfoHash,
Size: strconv.FormatUint(response.Response.Torrent.Size, 10),
}
return t, nil
}
// TestAPI try api access against torrents page
// TestAPI try api access against index
func (c *Client) TestAPI(ctx context.Context) (bool, error) {
resp, err := c.get(ctx, c.url)
resp, err := c.GetIndex(ctx)
if err != nil {
return false, errors.Wrap(err, "error getting data")
return false, errors.Wrap(err, "test api error")
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return true, nil
if resp == nil {
return false, nil
}
return false, nil
return true, nil
}
func (c *Client) GetIndex(ctx context.Context) (*GetIndexResponse, error) {
var response *GetIndexResponse
params := url.Values{}
err := c.getJSON(ctx, params, &response)
if err != nil {
return nil, errors.Wrap(err, "error getting data")
}
if response.Status != "success" {
return nil, errors.New("bad status: %s", response.Status)
}
return response, nil
}

View file

@ -35,17 +35,26 @@ func Test_client_GetTorrentByID(t *testing.T) {
id := r.URL.Query().Get("id")
var jsonPayload []byte
var err error
switch id {
case "422368":
jsonPayload, _ = os.ReadFile("testdata/ggn_get_torrent_by_id.json")
jsonPayload, err = os.ReadFile("testdata/ggn_get_torrent_by_id.json")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
break
case "100002":
jsonPayload, _ = os.ReadFile("testdata/ggn_get_torrent_by_id_not_found.json")
jsonPayload, err = os.ReadFile("testdata/ggn_get_by_id_not_found.json")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.WriteHeader(http.StatusNotFound)
break
}
@ -102,6 +111,7 @@ func Test_client_GetTorrentByID(t *testing.T) {
got, err := c.GetTorrentByID(context.Background(), tt.args.torrentID)
if tt.wantErr && assert.Error(t, err) {
t.Logf("got err: %v", err)
assert.Equal(t, tt.wantErr, err)
}

View file

@ -4,10 +4,10 @@
package ops
import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
@ -133,6 +133,44 @@ type Torrent struct {
Username string `json:"username"`
}
type GetIndexResponse struct {
Status string `json:"status"`
Response Response `json:"response"`
Info Info `json:"info"`
}
type Info struct {
Source string `json:"source"`
Version int64 `json:"version"`
}
type Response struct {
Username string `json:"username"`
ID int64 `json:"id"`
Notifications Notifications `json:"notifications"`
Userstats Userstats `json:"userstats"`
//Authkey string `json:"authkey"`
//Passkey string `json:"passkey"`
}
type Notifications struct {
Messages int64 `json:"messages"`
Notifications int64 `json:"notifications"`
NewAnnouncement bool `json:"newAnnouncement"`
NewBlog bool `json:"newBlog"`
NewSubscriptions bool `json:"newSubscriptions"`
}
type Userstats struct {
Uploaded int64 `json:"uploaded"`
Downloaded int64 `json:"downloaded"`
Ratio float64 `json:"ratio"`
Requiredratio int64 `json:"requiredratio"`
BonusPoints int64 `json:"bonusPoints"`
BonusPointsPerHour float64 `json:"bonusPointsPerHour"`
Class string `json:"class"`
}
func (c *Client) Do(req *http.Request) (*http.Response, error) {
//ctx := context.Background()
err := c.RateLimiter.Wait(req.Context()) // This is a blocking call. Honors the rate limit
@ -146,14 +184,16 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) {
return resp, nil
}
func (c *Client) get(ctx context.Context, url string) (*http.Response, error) {
func (c *Client) getJSON(ctx context.Context, params url.Values, data any) error {
if c.APIKey == "" {
return nil, errors.New("orpheus client missing API key!")
return errors.New("orpheus client missing API key!")
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
reqUrl := fmt.Sprintf("%s?%s", c.url, params.Encode())
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, http.NoBody)
if err != nil {
return nil, errors.Wrap(err, "could not build request")
return errors.Wrap(err, "could not build request")
}
req.Header.Add("Authorization", fmt.Sprintf("token %s", c.APIKey))
@ -161,28 +201,29 @@ func (c *Client) get(ctx context.Context, url string) (*http.Response, error) {
res, err := c.Do(req)
if err != nil {
return res, errors.Wrap(err, "could not make request: %+v", req)
return errors.Wrap(err, "could not make request: %+v", req)
}
defer res.Body.Close()
body := bufio.NewReader(res.Body)
// return early if not OK
if res.StatusCode != http.StatusOK {
var r ErrorResponse
var errResponse ErrorResponse
defer res.Body.Close()
body, readErr := io.ReadAll(res.Body)
if readErr != nil {
return nil, errors.Wrap(readErr, "could not read body")
if err := json.NewDecoder(body).Decode(&errResponse); err != nil {
return errors.Wrap(err, "could not unmarshal body")
}
if err = json.Unmarshal(body, &r); err != nil {
return nil, errors.Wrap(err, "could not unmarshal body")
}
return nil, errors.New("status code: %d status: %s error: %s", res.StatusCode, r.Status, r.Error)
return errors.New("status code: %d status: %s error: %s", res.StatusCode, errResponse.Status, errResponse.Error)
}
return res, nil
if err := json.NewDecoder(body).Decode(&data); err != nil {
return errors.Wrap(err, "could not unmarshal body")
}
return nil
}
func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) {
@ -190,35 +231,22 @@ func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.
return nil, errors.New("orpheus client: must have torrentID")
}
var r TorrentDetailsResponse
var response TorrentDetailsResponse
v := url.Values{}
v.Add("id", torrentID)
params := v.Encode()
params := url.Values{}
params.Add("action", "torrent")
params.Add("id", torrentID)
reqUrl := fmt.Sprintf("%s?action=torrent&%s", c.url, params)
resp, err := c.get(ctx, reqUrl)
err := c.getJSON(ctx, params, &response)
if err != nil {
return nil, errors.Wrap(err, "could not get torrent by id: %v", torrentID)
}
defer resp.Body.Close()
body, readErr := io.ReadAll(resp.Body)
if readErr != nil {
return nil, errors.Wrap(readErr, "could not read body")
}
if err := json.Unmarshal(body, &r); err != nil {
return nil, errors.Wrap(err, "could not unmarshal body")
}
res := &domain.TorrentBasic{
Id: strconv.Itoa(r.Response.Torrent.Id),
InfoHash: r.Response.Torrent.InfoHash,
Size: strconv.Itoa(r.Response.Torrent.Size),
Uploader: r.Response.Torrent.Username,
Id: strconv.Itoa(response.Response.Torrent.Id),
InfoHash: response.Response.Torrent.InfoHash,
Size: strconv.Itoa(response.Response.Torrent.Size),
Uploader: response.Response.Torrent.Username,
}
return res, nil
@ -226,16 +254,29 @@ func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.
// TestAPI try api access against torrents page
func (c *Client) TestAPI(ctx context.Context) (bool, error) {
resp, err := c.get(ctx, c.url+"?action=index")
resp, err := c.GetIndex(ctx)
if err != nil {
return false, errors.Wrap(err, "test api error")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
if resp == nil {
return false, nil
}
return true, nil
}
// GetIndex get API index
func (c *Client) GetIndex(ctx context.Context) (*GetIndexResponse, error) {
var response GetIndexResponse
params := url.Values{}
params.Add("action", "index")
err := c.getJSON(ctx, params, &response)
if err != nil {
return nil, errors.Wrap(err, "test api error")
}
return &response, nil
}

View file

@ -4,10 +4,10 @@
package ptp
import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
@ -80,6 +80,7 @@ type TorrentResponse struct {
ImdbVoteCount int `json:"ImdbVoteCount"`
Torrents []Torrent `json:"Torrents"`
}
type Torrent struct {
Id string `json:"Id"`
InfoHash string `json:"InfoHash"`
@ -98,7 +99,9 @@ type Torrent struct {
ReleaseGroup *string `json:"ReleaseGroup"`
Checked bool `json:"Checked"`
GoldenPopcorn bool `json:"GoldenPopcorn"`
RemasterTitle string `json:"RemasterTitle,omitempty"`
FreeleechType *string `json:"FreeleechType,omitempty"`
RemasterTitle *string `json:"RemasterTitle,omitempty"`
RemasterYear *string `json:"RemasterYear,omitempty"`
}
func (c *Client) Do(req *http.Request) (*http.Response, error) {
@ -114,10 +117,12 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) {
return resp, nil
}
func (c *Client) get(ctx context.Context, url string) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
func (c *Client) getJSON(ctx context.Context, params url.Values, data any) error {
reqUrl := fmt.Sprintf("%s?%s", c.url, params.Encode())
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, http.NoBody)
if err != nil {
return nil, errors.Wrap(err, "ptp client request error : %v", url)
return errors.Wrap(err, "ptp client request error : %v", reqUrl)
}
req.Header.Add("ApiUser", c.APIUser)
@ -126,18 +131,26 @@ func (c *Client) get(ctx context.Context, url string) (*http.Response, error) {
res, err := c.Do(req)
if err != nil {
return res, errors.Wrap(err, "ptp client request error : %v", url)
return errors.Wrap(err, "ptp client request error : %v", reqUrl)
}
defer res.Body.Close()
if res.StatusCode == http.StatusUnauthorized {
return res, ErrUnauthorized
return ErrUnauthorized
} else if res.StatusCode == http.StatusForbidden {
return res, ErrForbidden
return ErrForbidden
} else if res.StatusCode == http.StatusTooManyRequests {
return res, ErrTooManyRequests
return ErrTooManyRequests
}
return res, nil
body := bufio.NewReader(res.Body)
if err := json.NewDecoder(body).Decode(&data); err != nil {
return errors.Wrap(err, "could not unmarshal body")
}
return nil
}
func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) {
@ -145,33 +158,17 @@ func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.
return nil, errors.New("ptp client: must have torrentID")
}
var r TorrentResponse
var response TorrentResponse
v := url.Values{}
v.Add("torrentid", torrentID)
params := v.Encode()
params := url.Values{}
params.Add("torrentid", torrentID)
reqUrl := fmt.Sprintf("%v?%v", c.url, params)
resp, err := c.get(ctx, reqUrl)
err := c.getJSON(ctx, params, &response)
if err != nil {
return nil, errors.Wrap(err, "error requesting data")
}
defer func() {
resp.Body.Close()
}()
body, readErr := io.ReadAll(resp.Body)
if readErr != nil {
return nil, errors.Wrap(readErr, "could not read body")
}
if err = json.Unmarshal(body, &r); err != nil {
return nil, errors.Wrap(readErr, "could not unmarshal body")
}
for _, torrent := range r.Torrents {
for _, torrent := range response.Torrents {
if torrent.Id == torrentID {
return &domain.TorrentBasic{
Id: torrent.Id,
@ -181,21 +178,59 @@ func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.
}
}
return nil, nil
return nil, errors.New("could not find torrent with id: %s", torrentID)
}
func (c *Client) GetTorrents(ctx context.Context) (*TorrentListResponse, error) {
var response TorrentListResponse
params := url.Values{}
err := c.getJSON(ctx, params, &response)
if err != nil {
return nil, errors.Wrap(err, "error requesting data")
}
return &response, nil
}
// TestAPI try api access against torrents page
func (c *Client) TestAPI(ctx context.Context) (bool, error) {
resp, err := c.get(ctx, c.url)
resp, err := c.GetTorrents(ctx)
if err != nil {
return false, errors.Wrap(err, "error requesting data")
return false, errors.Wrap(err, "test api error")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
if resp == nil {
return false, nil
}
return true, nil
}
type TorrentListResponse struct {
TotalResults string `json:"TotalResults"`
Movies []Movie `json:"Movies"`
Page string `json:"Page"`
}
type Movie struct {
GroupID string `json:"GroupId"`
Title string `json:"Title"`
Year string `json:"Year"`
Cover string `json:"Cover"`
Tags []string `json:"Tags"`
Directors []Director `json:"Directors,omitempty"`
ImdbID *string `json:"ImdbId,omitempty"`
LastUploadTime string `json:"LastUploadTime"`
MaxSize int64 `json:"MaxSize"`
TotalSnatched int64 `json:"TotalSnatched"`
TotalSeeders int64 `json:"TotalSeeders"`
TotalLeechers int64 `json:"TotalLeechers"`
Torrents []Torrent `json:"Torrents"`
}
type Director struct {
Name string `json:"Name"`
ID string `json:"Id"`
}

View file

@ -7,6 +7,7 @@ package ptp
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
@ -131,7 +132,12 @@ func Test(t *testing.T) {
// read json response
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(nil)
json.NewEncoder(w).Encode(TorrentListResponse{
TotalResults: "10",
Movies: []Movie{},
Page: "1",
})
//w.Write(nil)
}))
defer ts.Close()

View file

@ -4,10 +4,10 @@
package red
import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
@ -135,6 +135,30 @@ type Torrent struct {
Username string `json:"username"`
}
type IndexResponse struct {
Status string `json:"status"`
Response struct {
Username string `json:"username"`
Id int `json:"id"`
Authkey string `json:"authkey"`
Passkey string `json:"passkey"`
ApiVersion string `json:"api_version"`
Notifications struct {
Messages int `json:"messages"`
Notifications int `json:"notifications"`
NewAnnouncement bool `json:"newAnnouncement"`
NewBlog bool `json:"newBlog"`
} `json:"notifications"`
UserStats struct {
Uploaded int64 `json:"uploaded"`
Downloaded int64 `json:"downloaded"`
Ratio float64 `json:"ratio"`
RequiredRatio float64 `json:"requiredratio"`
Class string `json:"class"`
} `json:"userstats"`
} `json:"response"`
}
func (c *Client) Do(req *http.Request) (*http.Response, error) {
//ctx := context.Background()
err := c.rateLimiter.Wait(req.Context()) // This is a blocking call. Honors the rate limit
@ -148,14 +172,16 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) {
return resp, nil
}
func (c *Client) get(ctx context.Context, url string) (*http.Response, error) {
func (c *Client) getJSON(ctx context.Context, params url.Values, data any) error {
if c.APIKey == "" {
return nil, errors.New("RED client missing API key!")
return errors.New("RED client missing API key!")
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
reqUrl := fmt.Sprintf("%s?%s", c.url, params.Encode())
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, http.NoBody)
if err != nil {
return nil, errors.Wrap(err, "could not build request")
return errors.Wrap(err, "could not build request")
}
req.Header.Add("Authorization", c.APIKey)
@ -163,28 +189,29 @@ func (c *Client) get(ctx context.Context, url string) (*http.Response, error) {
res, err := c.Do(req)
if err != nil {
return res, errors.Wrap(err, "could not make request: %+v", req)
return errors.Wrap(err, "could not make request: %+v", req)
}
defer res.Body.Close()
body := bufio.NewReader(res.Body)
// return early if not OK
if res.StatusCode != http.StatusOK {
var r ErrorResponse
var errResponse ErrorResponse
body, readErr := io.ReadAll(res.Body)
if readErr != nil {
return res, errors.Wrap(readErr, "could not read body")
if err := json.NewDecoder(body).Decode(&errResponse); err != nil {
return errors.Wrap(err, "could not unmarshal body")
}
if err = json.Unmarshal(body, &r); err != nil {
return res, errors.Wrap(readErr, "could not unmarshal body")
}
res.Body.Close()
return res, errors.New("status code: %d status: %s error: %s", res.StatusCode, r.Status, r.Error)
return errors.New("status code: %d status: %s error: %s", res.StatusCode, errResponse.Status, errResponse.Error)
}
return res, nil
if err := json.NewDecoder(body).Decode(&data); err != nil {
return errors.Wrap(err, "could not unmarshal body")
}
return nil
}
func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) {
@ -192,50 +219,50 @@ func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.
return nil, errors.New("red client: must have torrentID")
}
var r TorrentDetailsResponse
var response TorrentDetailsResponse
v := url.Values{}
v.Add("id", torrentID)
params := v.Encode()
params := url.Values{}
params.Add("action", "torrent")
params.Add("id", torrentID)
reqUrl := fmt.Sprintf("%s?action=torrent&%s", c.url, params)
resp, err := c.get(ctx, reqUrl)
err := c.getJSON(ctx, params, &response)
if err != nil {
return nil, errors.Wrap(err, "could not get torrent by id: %v", torrentID)
}
defer resp.Body.Close()
body, readErr := io.ReadAll(resp.Body)
if readErr != nil {
return nil, errors.Wrap(readErr, "could not read body")
}
if err := json.Unmarshal(body, &r); err != nil {
return nil, errors.Wrap(readErr, "could not unmarshal body")
}
return &domain.TorrentBasic{
Id: strconv.Itoa(r.Response.Torrent.Id),
InfoHash: r.Response.Torrent.InfoHash,
Size: strconv.Itoa(r.Response.Torrent.Size),
Id: strconv.Itoa(response.Response.Torrent.Id),
InfoHash: response.Response.Torrent.InfoHash,
Size: strconv.Itoa(response.Response.Torrent.Size),
}, nil
}
// TestAPI try api access against torrents page
func (c *Client) TestAPI(ctx context.Context) (bool, error) {
resp, err := c.get(ctx, c.url+"?action=index")
resp, err := c.GetIndex(ctx)
if err != nil {
return false, errors.Wrap(err, "test api error")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
if resp == nil {
return false, nil
}
return true, nil
}
// GetIndex get API index
func (c *Client) GetIndex(ctx context.Context) (*IndexResponse, error) {
var response IndexResponse
params := url.Values{}
params.Add("action", "index")
err := c.getJSON(ctx, params, &response)
if err != nil {
return nil, errors.Wrap(err, "test api error")
}
return &response, nil
}

View file

@ -4,15 +4,16 @@
package sabnzbd
import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"time"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/autobrr/autobrr/pkg/sharedhttp"
)
@ -98,16 +99,14 @@ func (c *Client) AddFromUrl(ctx context.Context, r AddNzbRequest) (*AddFileRespo
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
body := bufio.NewReader(res.Body)
if _, err := body.Peek(1); err != nil && err != bufio.ErrBufferFull {
return nil, errors.Wrap(err, "could not read body")
}
fmt.Print(body)
var data AddFileResponse
if err := json.Unmarshal(body, &data); err != nil {
return nil, err
if err := json.NewDecoder(body).Decode(&data); err != nil {
return nil, errors.Wrap(err, "could not unmarshal body")
}
return &data, nil
@ -147,14 +146,14 @@ func (c *Client) Version(ctx context.Context) (*VersionResponse, error) {
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
body := bufio.NewReader(res.Body)
if _, err := body.Peek(1); err != nil && err != bufio.ErrBufferFull {
return nil, errors.Wrap(err, "could not read body")
}
var data VersionResponse
if err := json.Unmarshal(body, &data); err != nil {
return nil, err
if err := json.NewDecoder(body).Decode(&data); err != nil {
return nil, errors.Wrap(err, "could not unmarshal body")
}
return &data, nil

View file

@ -4,6 +4,7 @@
package whisparr
import (
"bufio"
"context"
"encoding/json"
"io"
@ -92,7 +93,7 @@ func (c *client) Test(ctx context.Context) (*SystemStatusResponse, error) {
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
body, err := io.ReadAll(bufio.NewReader(res.Body))
if err != nil {
return nil, errors.Wrap(err, "could not read body")
}
@ -120,7 +121,7 @@ func (c *client) Push(ctx context.Context, release Release) ([]string, error) {
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
body, err := io.ReadAll(bufio.NewReader(res.Body))
if err != nil {
return nil, errors.Wrap(err, "could not read body")
}