fix(indexer): panic on size check via api (#239)

* fix(indexer): panic on size check via api

* feat(indexer): add mock api
This commit is contained in:
Ludvig Lundgren 2022-04-12 16:45:46 +02:00 committed by GitHub
parent 824aecafdf
commit 9e5b7b0aa5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 91 additions and 58 deletions

View file

@ -123,6 +123,7 @@ func (t TorrentBasic) ReleaseSizeBytes() uint64 {
releaseSizeBytes, err := humanize.ParseBytes(t.Size) releaseSizeBytes, err := humanize.ParseBytes(t.Size)
if err != nil { if err != nil {
// log could not parse into bytes // log could not parse into bytes
return 0
} }
return releaseSizeBytes return releaseSizeBytes
} }

View file

@ -249,7 +249,7 @@ func (s *service) CheckFilter(f domain.Filter, release *domain.Release) (bool, e
// additional size check. Some indexers have api implemented to fetch this data and for the others // additional size check. Some indexers have api implemented to fetch this data and for the others
// it will download the torrent file to parse and make the size check. This is all to minimize the amount of downloads. // it will download the torrent file to parse and make the size check. This is all to minimize the amount of downloads.
// do additional size check against indexer api or torrent for size // do additional size check against indexer api or download torrent for size check
if release.AdditionalSizeCheckRequired { if release.AdditionalSizeCheckRequired {
log.Debug().Msgf("filter.Service.CheckFilter: (%v) additional size check required", f.Name) log.Debug().Msgf("filter.Service.CheckFilter: (%v) additional size check required", f.Name)
@ -286,71 +286,52 @@ func (s *service) CheckFilter(f domain.Filter, release *domain.Release) (bool, e
return false, nil return false, nil
} }
// AdditionalSizeCheck
// Some indexers do not announce the size and if size (min,max) is set in a filter then it will need
// additional size check. Some indexers have api implemented to fetch this data and for the others
// it will download the torrent file to parse and make the size check. This is all to minimize the amount of downloads.
func (s *service) AdditionalSizeCheck(f domain.Filter, release *domain.Release) (bool, error) { func (s *service) AdditionalSizeCheck(f domain.Filter, release *domain.Release) (bool, error) {
// save outside of loop to check multiple filters with only one fetch
// TODO put on filter to reuse
var torrentInfo *domain.TorrentBasic
// Some indexers do not announce the size and if size (min,max) is set in a filter then it will need
// additional size check. Some indexers have api implemented to fetch this data and for the others
// it will download the torrent file to parse and make the size check. This is all to minimize the amount of downloads.
// do additional size check against indexer api or torrent for size // do additional size check against indexer api or torrent for size
log.Debug().Msgf("filter-service.find_and_check_filters: (%v) additional size check required", f.Name) log.Debug().Msgf("filter.Service.AdditionalSizeCheck: (%v) additional size check required", f.Name)
// check if indexer = btn, ptp, ggn or red switch release.Indexer {
if release.Indexer == "ptp" || release.Indexer == "btn" || release.Indexer == "ggn" || release.Indexer == "redacted" { case "ptp", "btn", "ggn", "redacted", "mock":
// fetch torrent info from api if release.Size == 0 {
// save outside of loop to check multiple filters with only one fetch log.Trace().Msgf("filter.Service.AdditionalSizeCheck: (%v) preparing to check via api", f.Name)
if torrentInfo == nil {
torrentInfo, err := s.apiService.GetTorrentByID(release.Indexer, release.TorrentID) torrentInfo, err := s.apiService.GetTorrentByID(release.Indexer, release.TorrentID)
if err != nil || torrentInfo == nil { if err != nil || torrentInfo == nil {
log.Error().Stack().Err(err).Msgf("filter-service.find_and_check_filters: (%v) could not get torrent: '%v' from: %v", f.Name, release.TorrentID, release.Indexer) log.Error().Stack().Err(err).Msgf("filter.Service.AdditionalSizeCheck: (%v) could not get torrent info from api: '%v' from: %v", f.Name, release.TorrentID, release.Indexer)
return false, err return false, err
} }
log.Debug().Msgf("filter-service.find_and_check_filters: (%v) got torrent info: %+v", f.Name, torrentInfo) log.Debug().Msgf("filter.Service.AdditionalSizeCheck: (%v) got torrent info from api: %+v", f.Name, torrentInfo)
}
// compare size against filters
match, err := checkSizeFilter(f.MinSize, f.MaxSize, torrentInfo.ReleaseSizeBytes())
if err != nil {
log.Error().Stack().Err(err).Msgf("filter-service.find_and_check_filters: (%v) could not check size filter", f.Name)
return false, err
}
// no match, lets continue to next filter
if !match {
log.Debug().Msgf("filter-service.find_and_check_filters: (%v) filter did not match after additional size check, trying next", f.Name)
return false, nil
}
// store size on the release
release.Size = torrentInfo.ReleaseSizeBytes() release.Size = torrentInfo.ReleaseSizeBytes()
} else { }
log.Trace().Msgf("filter-service.find_and_check_filters: (%v) additional size check required: preparing to download metafile", f.Name)
default:
log.Trace().Msgf("filter.Service.AdditionalSizeCheck: (%v) preparing to download torrent metafile", f.Name)
// if indexer doesn't have api, download torrent and add to tmpPath // if indexer doesn't have api, download torrent and add to tmpPath
err := release.DownloadTorrentFile() err := release.DownloadTorrentFile()
if err != nil { if err != nil {
log.Error().Stack().Err(err).Msgf("filter-service.find_and_check_filters: (%v) could not download torrent file with id: '%v' from: %v", f.Name, release.TorrentID, release.Indexer) log.Error().Stack().Err(err).Msgf("filter.Service.AdditionalSizeCheck: (%v) could not download torrent file with id: '%v' from: %v", f.Name, release.TorrentID, release.Indexer)
return false, err return false, err
} }
}
// compare size against filter // compare size against filter
match, err := checkSizeFilter(f.MinSize, f.MaxSize, release.Size) match, err := checkSizeFilter(f.MinSize, f.MaxSize, release.Size)
if err != nil { if err != nil {
log.Error().Stack().Err(err).Msgf("filter-service.find_and_check_filters: (%v) could not check size filter", f.Name) log.Error().Stack().Err(err).Msgf("filter.Service.AdditionalSizeCheck: (%v) error checking extra size filter", f.Name)
return false, err return false, err
} }
//no match, lets continue to next filter
// no match, lets continue to next filter
if !match { if !match {
log.Debug().Msgf("filter-service.find_and_check_filters: (%v) filter did not match after additional size check, trying next", f.Name) log.Debug().Msgf("filter.Service.AdditionalSizeCheck: (%v) filter did not match after additional size check, trying next", f.Name)
return false, nil return false, nil
} }
}
return true, nil return true, nil
} }

View file

@ -3,13 +3,14 @@ package indexer
import ( import (
"fmt" "fmt"
"github.com/rs/zerolog/log"
"github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/mock"
"github.com/autobrr/autobrr/pkg/btn" "github.com/autobrr/autobrr/pkg/btn"
"github.com/autobrr/autobrr/pkg/ggn" "github.com/autobrr/autobrr/pkg/ggn"
"github.com/autobrr/autobrr/pkg/ptp" "github.com/autobrr/autobrr/pkg/ptp"
"github.com/autobrr/autobrr/pkg/red" "github.com/autobrr/autobrr/pkg/red"
"github.com/rs/zerolog/log"
) )
type APIService interface { type APIService interface {
@ -70,50 +71,53 @@ func (s *apiService) TestConnection(indexer string) (bool, error) {
func (s *apiService) AddClient(indexer string, settings map[string]string) error { func (s *apiService) AddClient(indexer string, settings map[string]string) error {
// basic validation // basic validation
if indexer == "" { if indexer == "" {
return fmt.Errorf("api_service.add_client: validation falied: indexer can't be empty") return fmt.Errorf("api.Service.AddClient: validation falied: indexer can't be empty")
} else if len(settings) == 0 { } else if len(settings) == 0 {
return fmt.Errorf("api_service.add_client: validation falied: settings can't be empty") return fmt.Errorf("api.Service.AddClient: validation falied: settings can't be empty")
} }
log.Trace().Msgf("api-service.add_client: init api client for '%v'", indexer) log.Trace().Msgf("api.Service.AddClient: init api client for '%v'", indexer)
// init client // init client
switch indexer { switch indexer {
case "btn": case "btn":
key, ok := settings["api_key"] key, ok := settings["api_key"]
if !ok || key == "" { if !ok || key == "" {
return fmt.Errorf("api_service: could not initialize btn client: missing var 'api_key'") return fmt.Errorf("api.Service.AddClient: could not initialize btn client: missing var 'api_key'")
} }
s.apiClients[indexer] = btn.NewClient("", key) s.apiClients[indexer] = btn.NewClient("", key)
case "ptp": case "ptp":
user, ok := settings["api_user"] user, ok := settings["api_user"]
if !ok || user == "" { if !ok || user == "" {
return fmt.Errorf("api_service: could not initialize ptp client: missing var 'api_user'") return fmt.Errorf("api.Service.AddClient: could not initialize ptp client: missing var 'api_user'")
} }
key, ok := settings["api_key"] key, ok := settings["api_key"]
if !ok || key == "" { if !ok || key == "" {
return fmt.Errorf("api_service: could not initialize ptp client: missing var 'api_key'") return fmt.Errorf("api.Service.AddClient: could not initialize ptp client: missing var 'api_key'")
} }
s.apiClients[indexer] = ptp.NewClient("", user, key) s.apiClients[indexer] = ptp.NewClient("", user, key)
case "ggn": case "ggn":
key, ok := settings["api_key"] key, ok := settings["api_key"]
if !ok || key == "" { if !ok || key == "" {
return fmt.Errorf("api_service: could not initialize ggn client: missing var 'api_key'") return fmt.Errorf("api.Service.AddClient: could not initialize ggn client: missing var 'api_key'")
} }
s.apiClients[indexer] = ggn.NewClient("", key) s.apiClients[indexer] = ggn.NewClient("", key)
case "redacted": case "redacted":
key, ok := settings["api_key"] key, ok := settings["api_key"]
if !ok || key == "" { if !ok || key == "" {
return fmt.Errorf("api_service: could not initialize red client: missing var 'api_key'") return fmt.Errorf("api.Service.AddClient: could not initialize red client: missing var 'api_key'")
} }
s.apiClients[indexer] = red.NewClient("", key) s.apiClients[indexer] = red.NewClient("", key)
case "mock":
s.apiClients[indexer] = mock.NewMockClient("", "mock")
default: default:
return fmt.Errorf("api_service: could not initialize client: unsupported indexer '%v'", indexer) return fmt.Errorf("api.Service.AddClient: could not initialize client: unsupported indexer '%v'", indexer)
} }
return nil return nil

View file

@ -0,0 +1,46 @@
package mock
import (
"fmt"
"github.com/autobrr/autobrr/internal/domain"
)
type IndexerApiClient interface {
GetTorrentByID(torrentID string) (*domain.TorrentBasic, error)
TestAPI() (bool, error)
}
type IndexerClient struct {
URL string
APIKey string
}
func NewMockClient(url string, apiKey string) IndexerApiClient {
c := &IndexerClient{
URL: url,
APIKey: apiKey,
}
return c
}
func (c *IndexerClient) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) {
if torrentID == "" {
return nil, fmt.Errorf("mock client: must have torrentID")
}
r := &domain.TorrentBasic{
Id: torrentID,
InfoHash: "",
Size: "10GB",
}
return r, nil
}
// TestAPI try api access against torrents page
func (c *IndexerClient) TestAPI() (bool, error) {
return true, nil
}

View file

@ -10,6 +10,7 @@ privacy: private
protocol: torrent protocol: torrent
supports: supports:
- irc - irc
- api
source: custom source: custom
settings: settings:
- name: rsskey - name: rsskey