From cc0cca9f0d371c614aea671f8b017b6564b339c0 Mon Sep 17 00:00:00 2001 From: Kyle Sanderson Date: Wed, 28 Aug 2024 23:51:20 -0700 Subject: [PATCH] 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 --- internal/action/porla.go | 3 +- internal/domain/notification.go | 1 + internal/domain/release.go | 3 +- internal/filter/service.go | 14 ++-- internal/notification/discord.go | 32 +++---- internal/notification/gotify.go | 30 +++---- internal/notification/lunasea.go | 25 ++++-- internal/notification/notifiarr.go | 30 +++---- internal/notification/ntfy.go | 27 +++--- internal/notification/pushover.go | 27 +++--- internal/notification/service.go | 4 +- internal/notification/shoutrrr.go | 4 + internal/notification/telegram.go | 31 +++---- pkg/ggn/ggn.go | 108 ++++++++++++++++-------- pkg/ggn/ggn_test.go | 16 +++- pkg/ops/ops.go | 129 +++++++++++++++++++---------- pkg/ptp/ptp.go | 109 +++++++++++++++--------- pkg/ptp/ptp_test.go | 8 +- pkg/red/red.go | 113 +++++++++++++++---------- pkg/sabnzbd/sabnzbd.go | 25 +++--- pkg/whisparr/whisparr.go | 5 +- web/build.go | 25 +----- 22 files changed, 465 insertions(+), 304 deletions(-) diff --git a/internal/action/porla.go b/internal/action/porla.go index 950d39e..7c8d472 100644 --- a/internal/action/porla.go +++ b/internal/action/porla.go @@ -86,8 +86,7 @@ func (s *service) porla(ctx context.Context, action *domain.Action, release doma } defer file.Close() - reader := bufio.NewReader(file) - content, err := io.ReadAll(reader) + content, err := io.ReadAll(bufio.NewReader(file)) if err != nil { return nil, errors.Wrap(err, "failed to read file: %s", release.TorrentTmpFile) } diff --git a/internal/domain/notification.go b/internal/domain/notification.go index 16b7dbb..56b2dff 100644 --- a/internal/domain/notification.go +++ b/internal/domain/notification.go @@ -20,6 +20,7 @@ type NotificationRepo interface { type NotificationSender interface { Send(event NotificationEvent, payload NotificationPayload) error CanSend(event NotificationEvent) bool + Name() string } type Notification struct { diff --git a/internal/domain/release.go b/internal/domain/release.go index 01bf52a..aaa607b 100644 --- a/internal/domain/release.go +++ b/internal/domain/release.go @@ -4,6 +4,7 @@ package domain import ( + "bufio" "bytes" "context" "fmt" @@ -535,7 +536,7 @@ func (r *Release) downloadTorrentFile(ctx context.Context) error { } // Read the body into bytes - bodyBytes, err := io.ReadAll(resp.Body) + bodyBytes, err := io.ReadAll(bufio.NewReader(resp.Body)) if err != nil { return errors.Wrap(err, "error reading response body") } diff --git a/internal/filter/service.go b/internal/filter/service.go index 05189df..f4448eb 100644 --- a/internal/filter/service.go +++ b/internal/filter/service.go @@ -773,13 +773,15 @@ func (s *service) webhook(ctx context.Context, external domain.FilterExternal, r s.log.Debug().Msgf("filter external webhook response status: %d", res.StatusCode) - body, err := io.ReadAll(res.Body) - if err != nil { - return res.StatusCode, errors.Wrap(err, "could not read request body") - } + if s.log.Debug().Enabled() { + body, err := io.ReadAll(res.Body) + if err != nil { + return res.StatusCode, errors.Wrap(err, "could not read request body") + } - if len(body) > 0 { - s.log.Debug().Msgf("filter external webhook response status: %d body: %s", res.StatusCode, body) + if len(body) > 0 { + s.log.Debug().Msgf("filter external webhook response status: %d body: %s", res.StatusCode, body) + } } if utils.StrSliceContains(retryStatusCodes, strconv.Itoa(res.StatusCode)) { diff --git a/internal/notification/discord.go b/internal/notification/discord.go index a659c01..6a63163 100644 --- a/internal/notification/discord.go +++ b/internal/notification/discord.go @@ -4,6 +4,7 @@ package notification import ( + "bufio" "bytes" "encoding/json" "fmt" @@ -54,6 +55,10 @@ type discordSender struct { httpClient *http.Client } +func (a *discordSender) Name() string { + return "discord" +} + func NewDiscordSender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender { return &discordSender{ log: log.With().Str("sender", "discord").Logger(), @@ -73,14 +78,12 @@ func (a *discordSender) Send(event domain.NotificationEvent, payload domain.Noti jsonData, err := json.Marshal(m) if err != nil { - a.log.Error().Err(err).Msgf("discord client could not marshal data: %v", m) - return errors.Wrap(err, "could not marshal data: %+v", m) + return errors.Wrap(err, "could not marshal json request for event: %v payload: %v", event, payload) } req, err := http.NewRequest(http.MethodPost, a.Settings.Webhook, bytes.NewBuffer(jsonData)) if err != nil { - a.log.Error().Err(err).Msgf("discord client request error: %v", event) - return errors.Wrap(err, "could not create request") + return errors.Wrap(err, "could not create request for event: %v payload: %v", event, payload) } req.Header.Set("Content-Type", "application/json") @@ -88,24 +91,21 @@ func (a *discordSender) Send(event domain.NotificationEvent, payload domain.Noti res, err := a.httpClient.Do(req) if err != nil { - a.log.Error().Err(err).Msgf("discord client request error: %v", event) - return errors.Wrap(err, "could not make request: %+v", req) + return errors.Wrap(err, "client request error for event: %v payload: %v", event, payload) } defer res.Body.Close() - body, err := io.ReadAll(res.Body) - if err != nil { - a.log.Error().Err(err).Msgf("discord client request error: %v", event) - return errors.Wrap(err, "could not read data") - } - - a.log.Trace().Msgf("discord status: %v response: %v", res.StatusCode, string(body)) + a.log.Trace().Msgf("discord response status: %d", res.StatusCode) // discord responds with 204, Notifiarr with 204 so lets take all 200 as ok - if res.StatusCode >= 300 { - a.log.Error().Err(err).Msgf("discord client request error: %v", string(body)) - return errors.New("bad status: %v body: %v", res.StatusCode, string(body)) + if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusNoContent { + body, err := io.ReadAll(bufio.NewReader(res.Body)) + if err != nil { + return errors.Wrap(err, "could not read body for event: %v payload: %v", event, payload) + } + + return errors.New("unexpected status: %v body: %v", res.StatusCode, string(body)) } a.log.Debug().Msg("notification successfully sent to discord") diff --git a/internal/notification/gotify.go b/internal/notification/gotify.go index 24f7007..50697ca 100644 --- a/internal/notification/gotify.go +++ b/internal/notification/gotify.go @@ -4,6 +4,7 @@ package notification import ( + "bufio" "fmt" "io" "net/http" @@ -31,6 +32,10 @@ type gotifySender struct { httpClient *http.Client } +func (s *gotifySender) Name() string { + return "gotify" +} + func NewGotifySender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender { return &gotifySender{ log: log.With().Str("sender", "gotify").Logger(), @@ -53,11 +58,9 @@ func (s *gotifySender) Send(event domain.NotificationEvent, payload domain.Notif data.Set("message", m.Message) data.Set("title", m.Title) - url := fmt.Sprintf("%v/message?token=%v", s.Settings.Host, s.Settings.Token) - req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(data.Encode())) + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%v/message?token=%v", s.Settings.Host, s.Settings.Token), strings.NewReader(data.Encode())) if err != nil { - s.log.Error().Err(err).Msgf("gotify client request error: %v", event) - return errors.Wrap(err, "could not create request") + return errors.Wrap(err, "could not create request for event: %v payload: %v", event, payload) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") @@ -65,23 +68,20 @@ func (s *gotifySender) Send(event domain.NotificationEvent, payload domain.Notif res, err := s.httpClient.Do(req) if err != nil { - s.log.Error().Err(err).Msgf("gotify client request error: %v", event) - return errors.Wrap(err, "could not make request: %+v", req) + return errors.Wrap(err, "client request error for event: %v payload: %v", event, payload) } defer res.Body.Close() - body, err := io.ReadAll(res.Body) - if err != nil { - s.log.Error().Err(err).Msgf("gotify client request error: %v", event) - return errors.Wrap(err, "could not read data") - } - - s.log.Trace().Msgf("gotify status: %v response: %v", res.StatusCode, string(body)) + s.log.Trace().Msgf("gotify status: %d", res.StatusCode) if res.StatusCode != http.StatusOK { - s.log.Error().Err(err).Msgf("gotify client request error: %v", string(body)) - return errors.New("bad status: %v body: %v", res.StatusCode, string(body)) + body, err := io.ReadAll(bufio.NewReader(res.Body)) + if err != nil { + return errors.Wrap(err, "could not read body for event: %v payload: %v", event, payload) + } + + return errors.New("unexpected status: %v body: %v", res.StatusCode, string(body)) } s.log.Debug().Msg("notification successfully sent to gotify") diff --git a/internal/notification/lunasea.go b/internal/notification/lunasea.go index 6acfd3f..9060960 100644 --- a/internal/notification/lunasea.go +++ b/internal/notification/lunasea.go @@ -1,8 +1,10 @@ package notification import ( + "bufio" "bytes" "encoding/json" + "io" "net/http" "regexp" "time" @@ -31,6 +33,10 @@ type lunaSeaSender struct { httpClient *http.Client } +func (s *lunaSeaSender) Name() string { + return "lunasea" +} + func (s *lunaSeaSender) rewriteWebhookURL(url string) string { re := regexp.MustCompile(`/(radarr|sonarr|lidarr|tautulli|overseerr)/`) return re.ReplaceAllString(url, "/custom/") @@ -57,31 +63,32 @@ func (s *lunaSeaSender) Send(event domain.NotificationEvent, payload domain.Noti jsonData, err := json.Marshal(m) if err != nil { - s.log.Error().Err(err).Msg("lunasea client could not marshal data") - return errors.Wrap(err, "could not marshal data") + return errors.Wrap(err, "could not marshal json request for event: %v payload: %v", event, payload) } rewrittenURL := s.rewriteWebhookURL(s.Settings.Webhook) req, err := http.NewRequest(http.MethodPost, rewrittenURL, bytes.NewBuffer(jsonData)) if err != nil { - s.log.Error().Err(err).Msg("lunasea client request error") - return errors.Wrap(err, "could not create request") + return errors.Wrap(err, "could not create request for event: %v payload: %v", event, payload) } req.Header.Set("Content-Type", "application/json") res, err := s.httpClient.Do(req) if err != nil { - s.log.Error().Err(err).Msg("lunasea client request error") - return errors.Wrap(err, "could not make request") + return errors.Wrap(err, "client request error for event: %v payload: %v", event, payload) } defer res.Body.Close() - if res.StatusCode >= 300 { - s.log.Error().Msgf("bad status from lunasea: %v", res.StatusCode) - return errors.New("bad status: %v", res.StatusCode) + if res.StatusCode != http.StatusOK { + body, err := io.ReadAll(bufio.NewReader(res.Body)) + if err != nil { + return errors.Wrap(err, "could not read body for event: %v payload: %v", event, payload) + } + + return errors.New("unexpected status: %v body: %v", res.StatusCode, string(body)) } s.log.Debug().Msg("notification successfully sent to lunasea") diff --git a/internal/notification/notifiarr.go b/internal/notification/notifiarr.go index 6e9d8ee..432de16 100644 --- a/internal/notification/notifiarr.go +++ b/internal/notification/notifiarr.go @@ -4,6 +4,7 @@ package notification import ( + "bufio" "bytes" "encoding/json" "io" @@ -49,6 +50,10 @@ type notifiarrSender struct { httpClient *http.Client } +func (s *notifiarrSender) Name() string { + return "notifiarr" +} + func NewNotifiarrSender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender { return ¬ifiarrSender{ log: log.With().Str("sender", "notifiarr").Logger(), @@ -69,14 +74,12 @@ func (s *notifiarrSender) Send(event domain.NotificationEvent, payload domain.No jsonData, err := json.Marshal(m) if err != nil { - s.log.Error().Err(err).Msgf("notifiarr client could not marshal data: %v", m) - return errors.Wrap(err, "could not marshal data: %+v", m) + return errors.Wrap(err, "could not marshal json request for event: %v payload: %v", event, payload) } req, err := http.NewRequest(http.MethodPost, s.baseUrl, bytes.NewBuffer(jsonData)) if err != nil { - s.log.Error().Err(err).Msgf("notifiarr client request error: %v", event) - return errors.Wrap(err, "could not create request") + return errors.Wrap(err, "could not create request for event: %v payload: %v", event, payload) } req.Header.Set("Content-Type", "application/json") @@ -85,23 +88,20 @@ func (s *notifiarrSender) Send(event domain.NotificationEvent, payload domain.No res, err := s.httpClient.Do(req) if err != nil { - s.log.Error().Err(err).Msgf("notifiarr client request error: %v", event) - return errors.Wrap(err, "could not make request: %+v", req) + return errors.Wrap(err, "client request error for event: %v payload: %v", event, payload) } defer res.Body.Close() - body, err := io.ReadAll(res.Body) - if err != nil { - s.log.Error().Err(err).Msgf("notifiarr client request error: %v", event) - return errors.Wrap(err, "could not read data") - } - - s.log.Trace().Msgf("notifiarr status: %v response: %v", res.StatusCode, string(body)) + s.log.Trace().Msgf("response status: %d", res.StatusCode) if res.StatusCode != http.StatusOK { - s.log.Error().Err(err).Msgf("notifiarr client request error: %v", string(body)) - return errors.New("bad status: %v body: %v", res.StatusCode, string(body)) + body, err := io.ReadAll(bufio.NewReader(res.Body)) + if err != nil { + return errors.Wrap(err, "could not read body for event: %v payload: %v", event, payload) + } + + return errors.New("unexpected status: %v body: %v", res.StatusCode, string(body)) } s.log.Debug().Msg("notification successfully sent to notifiarr") diff --git a/internal/notification/ntfy.go b/internal/notification/ntfy.go index 293f579..02bd8b7 100644 --- a/internal/notification/ntfy.go +++ b/internal/notification/ntfy.go @@ -4,6 +4,7 @@ package notification import ( + "bufio" "io" "net/http" "strconv" @@ -30,6 +31,10 @@ type ntfySender struct { httpClient *http.Client } +func (s *ntfySender) Name() string { + return "ntfy" +} + func NewNtfySender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender { return &ntfySender{ log: log.With().Str("sender", "ntfy").Logger(), @@ -50,8 +55,7 @@ func (s *ntfySender) Send(event domain.NotificationEvent, payload domain.Notific req, err := http.NewRequest(http.MethodPost, s.Settings.Host, strings.NewReader(m.Message)) if err != nil { - s.log.Error().Err(err).Msgf("ntfy client request error: %v", event) - return errors.Wrap(err, "could not create request") + return errors.Wrap(err, "could not create request for event: %v payload: %v", event, payload) } req.Header.Set("Content-Type", "text/plain") @@ -71,23 +75,20 @@ func (s *ntfySender) Send(event domain.NotificationEvent, payload domain.Notific res, err := s.httpClient.Do(req) if err != nil { - s.log.Error().Err(err).Msgf("ntfy client request error: %v", event) - return errors.Wrap(err, "could not make request: %+v", req) + return errors.Wrap(err, "client request error for event: %v payload: %v", event, payload) } defer res.Body.Close() - body, err := io.ReadAll(res.Body) - if err != nil { - s.log.Error().Err(err).Msgf("ntfy client request error: %v", event) - return errors.Wrap(err, "could not read data") - } - - s.log.Trace().Msgf("ntfy status: %v response: %v", res.StatusCode, string(body)) + s.log.Trace().Msgf("ntfy response status: %d", res.StatusCode) if res.StatusCode != http.StatusOK { - s.log.Error().Err(err).Msgf("ntfy client request error: %v", string(body)) - return errors.New("bad status: %v body: %v", res.StatusCode, string(body)) + body, err := io.ReadAll(bufio.NewReader(res.Body)) + if err != nil { + return errors.Wrap(err, "could not read body for event: %v payload: %v", event, payload) + } + + return errors.New("unexpected status: %v body: %v", res.StatusCode, string(body)) } s.log.Debug().Msg("notification successfully sent to ntfy") diff --git a/internal/notification/pushover.go b/internal/notification/pushover.go index d8dc2c5..ed0fff1 100644 --- a/internal/notification/pushover.go +++ b/internal/notification/pushover.go @@ -4,6 +4,7 @@ package notification import ( + "bufio" "fmt" "io" "net/http" @@ -38,6 +39,10 @@ type pushoverSender struct { httpClient *http.Client } +func (s *pushoverSender) Name() string { + return "pushover" +} + func NewPushoverSender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender { return &pushoverSender{ log: log.With().Str("sender", "pushover").Logger(), @@ -81,8 +86,7 @@ func (s *pushoverSender) Send(event domain.NotificationEvent, payload domain.Not req, err := http.NewRequest(http.MethodPost, s.baseUrl, strings.NewReader(data.Encode())) if err != nil { - s.log.Error().Err(err).Msgf("pushover client request error: %v", event) - return errors.Wrap(err, "could not create request") + return errors.Wrap(err, "could not create request for event: %v payload: %v", event, payload) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") @@ -90,23 +94,20 @@ func (s *pushoverSender) Send(event domain.NotificationEvent, payload domain.Not res, err := s.httpClient.Do(req) if err != nil { - s.log.Error().Err(err).Msgf("pushover client request error: %v", event) - return errors.Wrap(err, "could not make request: %+v", req) + return errors.Wrap(err, "client request error for event: %v payload: %v", event, payload) } defer res.Body.Close() - body, err := io.ReadAll(res.Body) - if err != nil { - s.log.Error().Err(err).Msgf("pushover client request error: %v", event) - return errors.Wrap(err, "could not read data") - } - - s.log.Trace().Msgf("pushover status: %v response: %v", res.StatusCode, string(body)) + s.log.Trace().Msgf("pushover response status: %d", res.StatusCode) if res.StatusCode != http.StatusOK { - s.log.Error().Err(err).Msgf("pushover client request error: %v", string(body)) - return errors.New("bad status: %v body: %v", res.StatusCode, string(body)) + body, err := io.ReadAll(bufio.NewReader(res.Body)) + if err != nil { + return errors.Wrap(err, "could not read body for event: %v payload: %v", event, payload) + } + + return errors.New("unexpected status: %v body: %v", res.StatusCode, string(body)) } s.log.Debug().Msg("notification successfully sent to pushover") diff --git a/internal/notification/service.go b/internal/notification/service.go index bca6c7a..dad9f58 100644 --- a/internal/notification/service.go +++ b/internal/notification/service.go @@ -155,7 +155,9 @@ func (s *service) Send(event domain.NotificationEvent, payload domain.Notificati for _, sender := range s.senders { // check if sender is active and have notification types if sender.CanSend(event) { - sender.Send(event, payload) + if err := sender.Send(event, payload); err != nil { + s.log.Error().Err(err).Msgf("could not send %s notification for %v", sender.Name(), string(event)) + } } } }() diff --git a/internal/notification/shoutrrr.go b/internal/notification/shoutrrr.go index 67d19bc..5fd247c 100644 --- a/internal/notification/shoutrrr.go +++ b/internal/notification/shoutrrr.go @@ -13,6 +13,10 @@ type shoutrrrSender struct { builder MessageBuilderPlainText } +func (s *shoutrrrSender) Name() string { + return "shoutrrr" +} + func NewShoutrrrSender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender { return &shoutrrrSender{ log: log.With().Str("sender", "shoutrrr").Logger(), diff --git a/internal/notification/telegram.go b/internal/notification/telegram.go index 1eb5b21..7d5a50d 100644 --- a/internal/notification/telegram.go +++ b/internal/notification/telegram.go @@ -4,6 +4,7 @@ package notification import ( + "bufio" "bytes" "encoding/json" "fmt" @@ -36,6 +37,10 @@ type telegramSender struct { httpClient *http.Client } +func (s *telegramSender) Name() string { + return "telegram" +} + func NewTelegramSender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender { threadID := 0 if t := settings.Topic; t != "" { @@ -69,8 +74,7 @@ func (s *telegramSender) Send(event domain.NotificationEvent, payload domain.Not jsonData, err := json.Marshal(m) if err != nil { - s.log.Error().Err(err).Msgf("telegram client could not marshal data: %v", m) - return errors.Wrap(err, "could not marshal data: %+v", m) + return errors.Wrap(err, "could not marshal json request for event: %v payload: %v", event, payload) } var host string @@ -85,8 +89,7 @@ func (s *telegramSender) Send(event domain.NotificationEvent, payload domain.Not req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(jsonData)) if err != nil { - s.log.Error().Err(err).Msgf("telegram client request error: %v", event) - return errors.Wrap(err, "could not create request") + return errors.Wrap(err, "could not create request for event: %v payload: %v", event, payload) } req.Header.Set("Content-Type", "application/json") @@ -94,26 +97,24 @@ func (s *telegramSender) Send(event domain.NotificationEvent, payload domain.Not res, err := s.httpClient.Do(req) if err != nil { - s.log.Error().Err(err).Msgf("telegram client request error: %v", event) - return errors.Wrap(err, "could not make request: %+v", req) + return errors.Wrap(err, "client request error for event: %v payload: %v", event, payload) } defer res.Body.Close() - body, err := io.ReadAll(res.Body) - if err != nil { - s.log.Error().Err(err).Msgf("telegram client request error: %v", event) - return errors.Wrap(err, "could not read data") - } - - s.log.Trace().Msgf("telegram status: %v response: %v", res.StatusCode, string(body)) + s.log.Trace().Msgf("telegram status: %d", res.StatusCode) if res.StatusCode != http.StatusOK { - s.log.Error().Err(err).Msgf("telegram client request error: %v", string(body)) - return errors.New("bad status: %v body: %v", res.StatusCode, string(body)) + body, err := io.ReadAll(bufio.NewReader(res.Body)) + if err != nil { + return errors.Wrap(err, "could not read body for event: %v payload: %v", event, payload) + } + + return errors.New("unexpected status: %v body: %v", res.StatusCode, string(body)) } s.log.Debug().Msg("notification successfully sent to telegram") + return nil } diff --git a/pkg/ggn/ggn.go b/pkg/ggn/ggn.go index 592fd05..b01fa05 100644 --- a/pkg/ggn/ggn.go +++ b/pkg/ggn/ggn.go @@ -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 } diff --git a/pkg/ggn/ggn_test.go b/pkg/ggn/ggn_test.go index 393114e..d502cd8 100644 --- a/pkg/ggn/ggn_test.go +++ b/pkg/ggn/ggn_test.go @@ -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) } diff --git a/pkg/ops/ops.go b/pkg/ops/ops.go index 6644d11..c3d9181 100644 --- a/pkg/ops/ops.go +++ b/pkg/ops/ops.go @@ -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 +} diff --git a/pkg/ptp/ptp.go b/pkg/ptp/ptp.go index 0006f89..9c52439 100644 --- a/pkg/ptp/ptp.go +++ b/pkg/ptp/ptp.go @@ -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"` +} diff --git a/pkg/ptp/ptp_test.go b/pkg/ptp/ptp_test.go index 42495e4..af8279b 100644 --- a/pkg/ptp/ptp_test.go +++ b/pkg/ptp/ptp_test.go @@ -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() diff --git a/pkg/red/red.go b/pkg/red/red.go index cdef853..0bedcec 100644 --- a/pkg/red/red.go +++ b/pkg/red/red.go @@ -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 +} diff --git a/pkg/sabnzbd/sabnzbd.go b/pkg/sabnzbd/sabnzbd.go index a821651..514b02c 100644 --- a/pkg/sabnzbd/sabnzbd.go +++ b/pkg/sabnzbd/sabnzbd.go @@ -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 diff --git a/pkg/whisparr/whisparr.go b/pkg/whisparr/whisparr.go index e11d170..14d0b45 100644 --- a/pkg/whisparr/whisparr.go +++ b/pkg/whisparr/whisparr.go @@ -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") } diff --git a/web/build.go b/web/build.go index f0a761f..3c58cb2 100644 --- a/web/build.go +++ b/web/build.go @@ -5,6 +5,7 @@ package web import ( + "bufio" "bytes" "embed" "fmt" @@ -107,7 +108,7 @@ func fsFile(w http.ResponseWriter, r *http.Request, file string, filesystem fs.F return } - data, err := io.ReadAll(f) + data, err := io.ReadAll(bufio.NewReader(f)) if err != nil { http.Error(w, "Failed to read the file", http.StatusInternalServerError) return @@ -164,27 +165,7 @@ func RegisterHandler(c *chi.Mux, version, baseUrl string) { } // if not valid web route then try and serve files - f, err := DistDirFS.Open(file) - if err != nil { - http.Error(w, "File not found", http.StatusNotFound) - return - } - defer f.Close() - - stat, err := f.Stat() - if err != nil { - http.Error(w, "File not found", http.StatusNotFound) - return - } - - data, err := io.ReadAll(f) - if err != nil { - http.Error(w, "Failed to read the file", http.StatusInternalServerError) - return - } - - reader := bytes.NewReader(data) - http.ServeContent(w, r, file, stat.ModTime(), reader) + fsFile(w, r, file, DistDirFS) }) }