bitmagnet/internal/torznab/httpserver/handler.go

166 lines
4.0 KiB
Go

package httpserver
import (
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/bitmagnet-io/bitmagnet/internal/model"
"github.com/bitmagnet-io/bitmagnet/internal/torznab"
"github.com/gin-gonic/gin"
)
type handler struct {
config torznab.Config
client torznab.Client
}
func (h handler) handleRequest(ctx *gin.Context) {
profile, err := h.getProfile(ctx)
if err != nil {
h.writeError(ctx, err)
return
}
tp := ctx.Query(torznab.ParamType)
switch tp {
case "":
h.writeError(ctx, torznab.Error{
Code: 200,
Description: fmt.Sprintf("missing parameter (%s)", torznab.ParamType),
})
case torznab.FunctionCaps:
h.writeXML(ctx, profile.Caps())
default:
h.handleSearch(ctx, profile, tp)
}
}
func (h handler) handleSearch(ctx *gin.Context, profile torznab.Profile, tp string) {
var cats []int
for _, csvCat := range ctx.QueryArray(torznab.ParamCat) {
for _, cat := range strings.Split(csvCat, ",") {
if intCat, err := strconv.Atoi(cat); err == nil {
cats = append(cats, intCat)
}
}
}
imdbID := model.NullString{}
if qImdbId := ctx.Query(torznab.ParamIMDBID); qImdbId != "" {
imdbID.Valid = true
imdbID.String = qImdbId
}
tmdbID := model.NullString{}
if qTmdbId := ctx.Query(torznab.ParamTMDBID); qTmdbId != "" {
tmdbID.Valid = true
tmdbID.String = qTmdbId
}
season := model.NullInt{}
episode := model.NullInt{}
if qSeason := ctx.Query(torznab.ParamSeason); qSeason != "" {
if intSeason, err := strconv.Atoi(qSeason); err == nil {
season.Valid = true
season.Int = intSeason
if qEpisode := ctx.Query(torznab.ParamEpisode); qEpisode != "" {
if intEpisode, err := strconv.Atoi(qEpisode); err == nil {
episode.Valid = true
episode.Int = intEpisode
}
}
}
}
limit := model.NullUint{}
if intLimit, limitErr := strconv.Atoi(ctx.Query(torznab.ParamLimit)); limitErr == nil && intLimit > 0 {
limit.Valid = true
limit.Uint = uint(intLimit)
}
offset := model.NullUint{}
if intOffset, offsetErr := strconv.Atoi(ctx.Query(torznab.ParamOffset)); offsetErr == nil {
offset.Valid = true
offset.Uint = uint(intOffset)
}
result, searchErr := h.client.Search(ctx, torznab.SearchRequest{
Profile: profile,
Query: ctx.Query(torznab.ParamQuery),
Type: tp,
Cats: cats,
IMDBID: imdbID,
TMDBID: tmdbID,
Season: season,
Episode: episode,
Limit: limit,
Offset: offset,
})
if searchErr != nil {
h.writeError(ctx, fmt.Errorf("failed to search: %w", searchErr))
return
}
h.writeXML(ctx, result)
}
func (h handler) writeXML(ctx *gin.Context, obj torznab.XMLer) {
body, err := obj.XML()
if err != nil {
h.writeHTTPError(ctx, fmt.Errorf("failed to encode xml: %w", err))
return
}
ctx.Status(http.StatusOK)
ctx.Header("Content-Type", "application/xml; charset=utf-8")
_, _ = ctx.Writer.Write(body)
}
func (h handler) writeError(ctx *gin.Context, err error) {
var torznabErr torznab.Error
if ok := errors.As(err, &torznabErr); ok {
h.writeXML(ctx, torznabErr)
} else {
h.writeHTTPError(ctx, err)
}
}
func (h handler) writeHTTPError(ctx *gin.Context, err error) {
code := http.StatusInternalServerError
var httpErr httpError
if ok := errors.As(err, &httpErr); ok {
code = httpErr.httpErrorCode()
}
_ = ctx.AbortWithError(code, err)
_, _ = ctx.Writer.WriteString(err.Error() + "\n")
}
type httpError interface {
error
httpErrorCode() int
}
type errProfileNotFound struct {
name string
}
func (e errProfileNotFound) Error() string {
return fmt.Sprintf("profile not found: %s", e.name)
}
func (e errProfileNotFound) httpErrorCode() int {
return http.StatusNotFound
}
func (h handler) getProfile(c *gin.Context) (torznab.Profile, error) {
profilePathPart := strings.ToLower(strings.Split(strings.Trim(c.Param("any"), "/"), "/")[0])
switch profilePathPart {
case "", "api", torznab.ProfileDefault.ID:
return torznab.ProfileDefault, nil
default:
profile, ok := h.config.GetProfile(profilePathPart)
if !ok {
return profile, errProfileNotFound{name: profilePathPart}
}
return profile, nil
}
}