Documentation
¶
Overview ¶
Package indexer manages indexer configurations and orchestrates release searches.
⚠ Before changing anything in this file — especially `seedWeight`, `applyMinSeedersFilter`, or the `Search()` sort/filter closure — run:
go test ./internal/core/indexer/...
The tests in service_test.go lock down the dead-torrent regression fix: the 847-seeders-on-a-5-year-old-release bug would have been caught by `TestSearchSort_ReproducesIncident` if the suite had existed at the time. See pilot/CLAUDE.md "Regression guard: dead-torrent release search" for the full list of guarded files and the rationale.
Index ¶
- Variables
- type Config
- type CreateRequest
- type GrabRequest
- type PackType
- type SearchResult
- type Service
- func (s *Service) Create(ctx context.Context, req CreateRequest) (Config, error)
- func (s *Service) CreateGrab(ctx context.Context, req GrabRequest) (db.GrabHistory, error)
- func (s *Service) Delete(ctx context.Context, id string) error
- func (s *Service) Get(ctx context.Context, id string) (Config, error)
- func (s *Service) GetGrabByInfoHash(ctx context.Context, infoHash string) (db.GrabHistory, error)
- func (s *Service) GetRecent(ctx context.Context) ([]SearchResult, error)
- func (s *Service) GrabHistory(ctx context.Context, seriesID string) ([]db.GrabHistory, error)
- func (s *Service) List(ctx context.Context) ([]Config, error)
- func (s *Service) Search(ctx context.Context, q plugin.SearchQuery) ([]SearchResult, error)
- func (s *Service) Test(ctx context.Context, id string) error
- func (s *Service) Update(ctx context.Context, id string, req UpdateRequest) (Config, error)
- func (s *Service) UpdateGrabDownloadClient(ctx context.Context, arg db.UpdateGrabDownloadClientParams) error
- func (s *Service) UpdateGrabInfoHash(ctx context.Context, grabID, infoHash string) error
- func (s *Service) UpdateGrabStatus(ctx context.Context, grabID, status string) error
- type UpdateRequest
Constants ¶
This section is empty.
Variables ¶
var ErrNotFound = errors.New("indexer not found")
ErrNotFound is returned when an indexer config does not exist.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
ID string
Name string
Kind string // "torznab", "newznab"
Enabled bool
Priority int
MinSeeders int // releases below this threshold get tagged with a filter reason
Settings json.RawMessage
CreatedAt time.Time
UpdatedAt time.Time
}
Config is the domain representation of a stored indexer configuration.
type CreateRequest ¶
type CreateRequest struct {
Name string
Kind string
Enabled bool
Priority int
MinSeeders int // 0 means "use the sensible default (5)"
Settings json.RawMessage
}
CreateRequest carries the fields needed to create an indexer config.
type GrabRequest ¶
type GrabRequest struct {
SeriesID string
EpisodeID string // optional; empty for season-pack grabs
SeasonNumber int // optional; 0 when not known
Release plugin.Release
IndexerID string
// Source is "interactive" (user-initiated via UI) or "auto_search"
// (triggered by scheduled episode search). Controls whether the stall
// watcher triggers automatic re-search on failure.
Source string
}
GrabRequest carries the fields needed to record a grab history entry.
type PackType ¶
type PackType string
PackType classifies a release by what it covers: a full season, multiple episodes, a single episode, or unknown. Populated from parser.EpisodeInfo.
type SearchResult ¶
type SearchResult struct {
plugin.Release
// IndexerID is the DB UUID of the indexer that returned this release.
IndexerID string
IndexerName string
QualityScore int
// PackType classifies the release as a season pack, multi-episode pack,
// single episode, or unknown. Parsed from the release title once at
// ingestion so the sort comparator and API response can share it.
PackType PackType
// EpisodeCount is how many discrete episodes the release covers. For
// season packs it is sentinel-large (see effectiveEpisodeCount) so the
// ranking comparator prefers full packs over partial packs within the
// same quality tier.
EpisodeCount int
// FilterReasons lists all the reasons this release was filtered out
// of the "active" set. If non-empty, the UI should render the row
// grayed with the reasons shown and an "override" button. Empty means
// the release is in the active/top list. We return filtered rows
// intentionally so users can see what was dropped and why — hiding
// them creates "content loss traps" when false-positive blocklists
// or overly-aggressive min_seeders thresholds silently eat content.
FilterReasons []string
}
SearchResult pairs a plugin release with its source indexer identity.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service manages indexer configuration and search orchestration.
func NewService ¶
func NewService(q db.Querier, reg *registry.Registry, bus *events.Bus, rl *ratelimit.Registry, logger *slog.Logger) *Service
NewService creates a new Service. A nil logger falls back to slog.Default() so existing callers continue to work without migration.
func (*Service) CreateGrab ¶
func (s *Service) CreateGrab(ctx context.Context, req GrabRequest) (db.GrabHistory, error)
CreateGrab records a grab history entry linking the release to the series and optionally to a specific episode. Pass empty strings for fields that are not applicable (e.g. season-pack grabs have no episodeID).
func (*Service) GetGrabByInfoHash ¶
GetGrabByInfoHash returns the most-recent grab for the given info_hash. Returns sql.ErrNoRows when none matches — callers map that to 404 at the API edge. Used by the /grabs/by-hash/{hash}/research endpoint to find the original grab so it can blocklist the dead release and retry.
func (*Service) GetRecent ¶
func (s *Service) GetRecent(ctx context.Context) ([]SearchResult, error)
GetRecent fetches the most recent releases from all enabled indexers via their RSS feeds. Results from all indexers are merged and returned together. Errors from individual indexers are collected; a combined error is returned alongside any results that were gathered.
func (*Service) GrabHistory ¶
GrabHistory returns the grab history for a series, newest first.
func (*Service) Search ¶
func (s *Service) Search(ctx context.Context, q plugin.SearchQuery) ([]SearchResult, error)
Search queries all enabled indexers with the given search query and returns results sorted by quality score, then an age-aware log10 seed weight, then release age. Errors from individual indexers are collected; a combined error is returned alongside any results that were gathered.
func (*Service) Update ¶
Update replaces the mutable fields of an indexer config. Returns ErrNotFound if the indexer does not exist.
func (*Service) UpdateGrabDownloadClient ¶
func (s *Service) UpdateGrabDownloadClient(ctx context.Context, arg db.UpdateGrabDownloadClientParams) error
UpdateGrabDownloadClient records the download client and client-assigned item ID on an existing grab history entry. Called after a release is successfully submitted to a download client.
func (*Service) UpdateGrabInfoHash ¶
UpdateGrabInfoHash records the BitTorrent info_hash on a grab row. The stall watcher uses this to correlate Haul's stall events back to the grab that initiated them.
func (*Service) UpdateGrabStatus ¶
UpdateGrabStatus sets the download_status on a grab row. Used when the grab handler can't make forward progress (e.g. download client rejects the release because the indexer response had no download URL) — without this, the row stays at "queued" forever and shows up in the search guardrail as "already grabbed", suppressing the retry the user wants.
type UpdateRequest ¶
type UpdateRequest = CreateRequest
UpdateRequest carries the fields needed to update an indexer config.