torrent

package
v0.0.0-...-bb93e99 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 12, 2026 License: MIT Imports: 36 Imported by: 0

Documentation

Overview

Package torrent owns the anacrolix/torrent client lifecycle and the Session type that exposes it to the rest of haul.

⚠ Before changing anything in this file, run:

go test ./internal/core/torrent/... -run TestSessionIntegration_DownloadFromPeer

This test spins up a local seeder and verifies the Session can actually download through its configured peer-wire / DHT / IPBlocklist wiring. The "torrent stalls at 0 peers" bug has regressed three times — the test catches it in <1s. See haul/CLAUDE.md for the full list of files guarded by this regression suite.

Index

Constants

View Source
const (
	ReasonNoPeersEver    = "no_peers_ever" // pre-metadata, no peer in the firstPeerTimeout window
	ReasonNoPeers        = "no_peers"      // had peers at some point, now has none + no data for stall_timeout
	ReasonNoSeeders      = "no_seeders"    // has peers but no seeds, no data for stall_timeout
	ReasonNoDataReceived = "no_data_received"
)

Stall reason strings. These show up in the event bus and in the HTTP API response for /torrents/{hash}/stall — Pilot/Prism use them to decide whether to blocklist the release.

Variables

View Source
var DefaultPublicTrackers = [][]string{

	{
		"https://tracker.opentrackr.org:443/announce",
		"https://tracker.torrent.eu.org:443/announce",
		"https://tracker1.bt.moack.co.kr:443/announce",
	},

	{
		"udp://tracker.opentrackr.org:1337/announce",
		"udp://open.demonii.com:1337/announce",
		"udp://open.stealth.si:80/announce",
		"udp://tracker.torrent.eu.org:451/announce",
		"udp://exodus.desync.com:6969/announce",
	},

	{
		"udp://tracker.moeking.me:6969/announce",
		"udp://explodie.org:6969/announce",
		"udp://tracker1.bt.moack.co.kr:80/announce",
		"udp://tracker.tiny-vps.com:6969/announce",
	},
}

DefaultPublicTrackers is a curated list of reliable public trackers. Magnets from public indexers often arrive with zero tracker URLs, making metadata resolution depend entirely on DHT — which is slow and unreliable behind VPNs. Adding these trackers dramatically speeds up peer discovery.

Sources: ngosang/trackerslist, newtrackon.com Prefer HTTPS/HTTP trackers over UDP for better VPN compatibility.

Functions

func CheckVPN

func CheckVPN()

CheckVPN detects VPN status by looking for tunnel interfaces and checking the external IP. Call periodically (every 60s).

func GetVPNStatus

func GetVPNStatus() (active bool, iface, ip string)

GetVPNStatus returns the cached VPN status.

func SetFirstPeerTimeoutForTesting

func SetFirstPeerTimeoutForTesting(d time.Duration) time.Duration

SetFirstPeerTimeoutForTesting lets cross-package tests (e.g. api/v1 stall handler tests) shrink the no-peers-ever stall threshold so a fresh torrent crosses it in milliseconds. Returns the previous value so the caller can restore it on cleanup. Production code must NOT call this — it changes the stall-detection contract globally.

func SetPublicIPDetectTimeoutForTesting

func SetPublicIPDetectTimeoutForTesting(d time.Duration) time.Duration

SetPublicIPDetectTimeoutForTesting lets tests in other packages (e.g. api/v1) short-circuit the 10-second public-IP lookup when constructing a real Session. Returns the previous value so the caller can restore it. Production code must NOT call this.

func SetSessionStartupGraceForTesting

func SetSessionStartupGraceForTesting(d time.Duration) time.Duration

SetSessionStartupGraceForTesting lets cross-package tests bypass the 10-minute warm-up suppression window so ListStalled actually surfaces the seeded torrent. Returns the previous value so the caller can restore it. Production code must NOT call this.

Types

type AddRequest

type AddRequest struct {
	// URI is a magnet link, HTTP URL to a .torrent file, or empty if File is set.
	URI string `json:"uri"`
	// File is raw .torrent file bytes. Mutually exclusive with URI.
	File []byte `json:"-"`
	// Category to assign.
	Category string `json:"category"`
	// SavePath overrides the default download directory.
	SavePath string `json:"save_path"`
	// Tags to assign.
	Tags []string `json:"tags"`
	// Paused starts the torrent in paused state.
	Paused bool `json:"paused"`
	// Metadata holds optional media context from the requesting service (Pilot/Prism).
	Metadata *RequesterMetadata `json:"metadata,omitempty"`
}

AddRequest is the input for adding a new torrent.

type FileInfo

type FileInfo struct {
	Index    int     `json:"index"`
	Path     string  `json:"path"`
	Size     int64   `json:"size"`
	Priority string  `json:"priority"` // "skip", "normal", "high"
	Progress float64 `json:"progress"`
}

FileInfo describes a file within a torrent.

type HealthReport

type HealthReport struct {
	ActiveDownloads int64  `json:"active_downloads"`
	ActiveUploads   int64  `json:"active_uploads"`
	TotalTorrents   int    `json:"total_torrents"`
	DownloadSpeed   int64  `json:"download_speed_bps"`
	UploadSpeed     int64  `json:"upload_speed_bps"`
	DiskFreeBytes   int64  `json:"disk_free_bytes"`
	DiskTotalBytes  int64  `json:"disk_total_bytes"`
	StalledCount    int    `json:"stalled_count"`
	EngineStatus    string `json:"engine_status"`
	PeersConnected  int    `json:"peers_connected"`
	VPNActive       bool   `json:"vpn_active"`
	VPNInterface    string `json:"vpn_interface,omitempty"`
	ExternalIP      string `json:"external_ip,omitempty"`
}

HealthReport is the structured health data for Pulse dashboard.

type HistoryFilter

type HistoryFilter struct {
	// Service narrows to "pilot" / "prism" / "manual" / "". Required
	// when looking up by an arr-side ID, since IDs are only unique
	// within a service's namespace.
	Service string

	// One of these is the typical lookup key. Multiple may be set
	// (combined with AND), but in practice the caller picks one.
	InfoHash  string
	MovieID   string
	SeriesID  string
	EpisodeID string

	// Semantic match — for "find any download for this episode of
	// this show, regardless of release". Requires at least TMDBID.
	TMDBID  int
	Season  int
	Episode int

	// IncludeRemoved=false (default) returns only active records.
	// Set true to include removed-but-not-yet-purged history.
	IncludeRemoved bool

	// Limit caps the result set. 0 → 100 default.
	Limit int
}

HistoryFilter narrows a LookupHistory query. All fields are optional — only non-zero values are added to the WHERE clause. The query joins the filters with AND.

type HistoryRecord

type HistoryRecord struct {
	InfoHash    string `json:"info_hash"`
	Name        string `json:"name"`
	SavePath    string `json:"save_path"`
	Category    string `json:"category"`
	AddedAt     string `json:"added_at"`               // RFC3339
	CompletedAt string `json:"completed_at,omitempty"` // empty when still in progress
	RemovedAt   string `json:"removed_at,omitempty"`   // empty when active

	// Requester metadata — opaque IDs the arr supplied at grab time.
	// Empty when the torrent was added without metadata (e.g. directly
	// in Haul's UI rather than via Pilot/Prism).
	Requester string `json:"requester,omitempty"`
	MovieID   string `json:"movie_id,omitempty"`
	SeriesID  string `json:"series_id,omitempty"`
	EpisodeID string `json:"episode_id,omitempty"`
	TMDBID    int    `json:"tmdb_id,omitempty"`
	Season    int    `json:"season,omitempty"`
	Episode   int    `json:"episode,omitempty"`
}

HistoryRecord is the lookup-API view of a torrent's persistent history record. Pilot/Prism use this to answer "have I ever downloaded this episode/movie?" without polling Haul's live state.

Both active and previously-removed torrents are returned (callers distinguish via RemovedAt). Files-on-disk status is NOT tracked here — Haul knows the save_path but not whether the file still exists; if Pilot/Prism care, they stat the path themselves.

type HookRunner

type HookRunner struct {
	// contains filtered or unexported fields
}

HookRunner executes external commands on torrent events.

func NewHookRunner

func NewHookRunner(onAdd, onComplete string, logger *slog.Logger) *HookRunner

NewHookRunner creates a hook runner with optional commands. Commands support these variables:

  • %h = info hash
  • %n = torrent name
  • %p = content path
  • %c = category

func (*HookRunner) HandleEvent

func (r *HookRunner) HandleEvent(_ context.Context, e events.Event)

HandleEvent implements events.Handler.

type Info

type Info struct {
	InfoHash     string     `json:"info_hash"`
	Name         string     `json:"name"`
	Status       Status     `json:"status"`
	SavePath     string     `json:"save_path"`
	Category     string     `json:"category"`
	Tags         []string   `json:"tags"`
	Size         int64      `json:"size"`
	Downloaded   int64      `json:"downloaded"`
	Uploaded     int64      `json:"uploaded"`
	Progress     float64    `json:"progress"`
	DownloadRate int64      `json:"download_rate"`
	UploadRate   int64      `json:"upload_rate"`
	Seeds        int        `json:"seeds"`
	Peers        int        `json:"peers"`
	SeedRatio    float64    `json:"seed_ratio"`
	ETA          int64      `json:"eta"`
	AddedAt      time.Time  `json:"added_at"`
	CompletedAt  *time.Time `json:"completed_at,omitempty"`
	ContentPath  string     `json:"content_path"`
	Requester    string     `json:"requester,omitempty"` // "pilot" | "prism" | "manual" | ""

	// Stalled is true when the backend's stall detector classifies this
	// torrent as inactive — see internal/core/torrent/stall.go for the
	// thresholds. The frontend uses this to render a distinct "Stalled"
	// status badge and color, replacing the old "download_rate == 0"
	// frontend-side heuristic which flipped on every brief connection blip.
	// Always false for non-downloading statuses.
	Stalled bool `json:"stalled"`

	// StalledAt is non-nil when the stall watcher escalated past level 3
	// and auto-paused the torrent. Distinct from Stalled (which is
	// transient and only meaningful while downloading): once StalledAt
	// is set, the torrent is permanently marked as needing user
	// attention until they resume it. Resume clears StalledAt and
	// removes the auto-applied 'stalled' tag.
	StalledAt *time.Time `json:"stalled_at,omitempty"`
}

Info is the external representation of a torrent.

type PeerInfo

type PeerInfo struct {
	Addr         string  `json:"addr"`          // "1.2.3.4:54321"
	Client       string  `json:"client"`        // "qBittorrent 4.5.0", "unknown" if the peer hasn't sent a client name
	Network      string  `json:"network"`       // "tcp" or "utp"
	Encrypted    bool    `json:"encrypted"`     // the peer prefers / supports encryption
	Progress     float64 `json:"progress"`      // 0..1 — fraction of pieces the peer has
	DownloadRate int64   `json:"download_rate"` // bytes/sec we're receiving from them
	UploadRate   int64   `json:"upload_rate"`   // bytes/sec we're sending them (best-effort; 0 until anacrolix exposes per-peer upload rate)
	Downloaded   int64   `json:"downloaded"`    // total useful data bytes read from this peer
	Uploaded     int64   `json:"uploaded"`      // total data bytes written to this peer
}

PeerInfo is the external representation of a single connected peer. Built on demand by Session.Peers — not part of the bulk torrent list to keep the hot-path response small.

type PieceStateRun

type PieceStateRun struct {
	Length int    `json:"length"`
	State  string `json:"state"` // "complete" | "partial" | "checking" | "missing"
}

PieceStateRun is a run-length-encoded entry describing a series of consecutive pieces in the same state. Mirrors anacrolix's PieceStateRuns output but serialised as plain JSON.

type PiecesInfo

type PiecesInfo struct {
	NumPieces int             `json:"num_pieces"`
	PieceSize int64           `json:"piece_size"`
	Runs      []PieceStateRun `json:"runs"`
}

PiecesInfo is a snapshot of a torrent's piece-level state. See plans/haul-torrent-detail-enhancements.md §4 for how the frontend consumes this (canvas-rendered piece bar with per-piece arrival flashes).

type RequesterMetadata

type RequesterMetadata struct {
	Requester     string `json:"requester,omitempty"`      // "prism", "pilot", "manual"
	MediaType     string `json:"media_type,omitempty"`     // "movie", "tv", "unknown"
	Title         string `json:"title,omitempty"`          // "Breaking Bad" or "Fight Club"
	Year          int    `json:"year,omitempty"`           // 2008
	TMDBID        int    `json:"tmdb_id,omitempty"`        // 550
	SeasonNumber  int    `json:"season_number,omitempty"`  // 1
	EpisodeNumber int    `json:"episode_number,omitempty"` // 4
	EpisodeTitle  string `json:"episode_title,omitempty"`  // "Cancer Man"
	Quality       string `json:"quality,omitempty"`        // "Bluray-1080p"
	QualityCodec  string `json:"quality_codec,omitempty"`  // "x265"
	RequestedBy   string `json:"requested_by,omitempty"`   // user who requested
	RequestedAt   string `json:"requested_at,omitempty"`   // ISO8601
	// Arr-side identifiers — UUID-shaped strings the requester uses to
	// reference its own DB rows. Empty when the caller didn't supply them.
	MovieID   string `json:"movie_id,omitempty"`   // Prism movie UUID
	SeriesID  string `json:"series_id,omitempty"`  // Pilot series UUID
	EpisodeID string `json:"episode_id,omitempty"` // Pilot episode UUID
}

RequesterMetadata holds structured context from the service that requested the download.

The MovieID/SeriesID/EpisodeID fields carry the arr's own UUIDs so Pilot/Prism can later look up "have I downloaded anything for episode_id=X?" via Haul's history endpoints. They're opaque strings to Haul — Haul never resolves them, just stores and returns them.

type Session

type Session struct {
	// contains filtered or unexported fields
}

Session manages the torrent engine and wraps anacrolix/torrent.

func NewSession

func NewSession(cfg config.TorrentConfig, db *sql.DB, bus *events.Bus, logger *slog.Logger) (*Session, error)

NewSession creates a new torrent session.

func (*Session) Add

func (s *Session) Add(ctx context.Context, req AddRequest) (result *Info, resultErr error)

Add adds a new torrent from a magnet link, URL, or .torrent file bytes.

func (*Session) AddNoPeersTorrentForTesting

func (s *Session) AddNoPeersTorrentForTesting(seed string, addedAt time.Time) string

AddNoPeersTorrentForTesting registers a torrent in the session's internal map with `addedAt = past`, no peers, no metadata — the exact shape both GetStallInfo and ListStalled classify as no_peers_ever once firstPeerTimeout elapses. The hash is derived from `seed` so each test can predict the resulting info_hash.

Cross-package callers (api/v1 stall handler tests) use this to seed state that the production add-torrent path would otherwise require real anacrolix peers to produce. The torrent is NOT registered with the anacrolix client — the stall classifiers only read managedTorrent fields, so the missing client-side handle is safe for the tested no-peers-ever path. Returns the info-hash hex.

Production code MUST NOT call this — it bypasses Add, the session DB, and the lifecycle hooks.

func (*Session) AddTags

func (s *Session) AddTags(hash string, tags []string) error

AddTags adds tags to a torrent.

func (*Session) AddTrackers

func (s *Session) AddTrackers(hash string, urls []string, tier int) error

AddTrackers appends one or more announce URLs to the torrent at the given tier. Idempotent — duplicates are silently ignored. Empty URLs are skipped. The torrent kicks off an announce on the new trackers asynchronously; effect is visible on next Trackers() call.

func (*Session) CheckSeedLimits

func (s *Session) CheckSeedLimits(ctx context.Context)

CheckSeedLimits checks all seeding torrents against their seed limits and pauses any that have exceeded them. Call this periodically.

func (*Session) CheckSpeedSchedule

func (s *Session) CheckSpeedSchedule(cfg config.SpeedScheduleConfig)

CheckSpeedSchedule enables/disables alt speed based on the schedule config.

func (*Session) CheckStalls

func (s *Session) CheckStalls(ctx context.Context)

CheckStalls inspects all torrents for stall conditions and publishes events for the severe cases. There are two distinct stall classes:

  1. **No peers ever**: the torrent was added more than firstPeerTimeout ago but has never observed a single peer. Typically means the release is dead (stale indexer data, trackers have no alive seeders), or Haul's networking is misconfigured. Pilot/Prism subscribe to this event and use it to blocklist the release.

  2. **Activity-based escalation**: the torrent had peers at some point, started downloading, but lost all activity. Progressive remediation: Level 1 reannounce → Level 2 force DHT → Level 3 archive.

Both classes are suppressed during the sessionStartupGrace window (default 10 min) because anacrolix itself is still warming up.

func (*Session) Close

func (s *Session) Close()

Close shuts down the torrent engine. The piece-completion store is owned by anacrolix once handed to it in NewSession — fileClientImpl.Close() closes it, so we must not close it again here (a double-close errors with "database not open" on bolt).

func (*Session) ExportTorrent

func (s *Session) ExportTorrent(hash string) ([]byte, error)

ExportTorrent returns the raw .torrent bytes for the given hash so an operator can re-add it elsewhere. Reads from the torrents.torrent_data column populated at add-time by saveTorrentData.

Returns (nil, error) if the hash is unknown OR the torrent_data column is empty (legacy rows from before persistence wiring or before metadata arrived). Caller should distinguish those cases via the error message.

func (*Session) ForceStart

func (s *Session) ForceStart(hash string) error

ForceStart resumes a torrent and marks it to bypass queue limits.

func (*Session) Get

func (s *Session) Get(hash string) (*Info, error)

Get returns info about a specific torrent.

func (*Session) GetArchivedCount

func (s *Session) GetArchivedCount() int

GetArchivedCount returns the number of torrents in the "archived" category.

func (*Session) GetFiles

func (s *Session) GetFiles(hash string) ([]FileInfo, error)

GetFiles returns the file list for a torrent.

func (*Session) GetHealth

func (s *Session) GetHealth() *HealthReport

GetHealth returns a structured health report.

func (*Session) GetMetadata

func (s *Session) GetMetadata(hash string) (*RequesterMetadata, error)

GetMetadata retrieves the requester metadata for a torrent.

func (*Session) GetStallInfo

func (s *Session) GetStallInfo(hash string) (*StallInfo, error)

GetStallInfo returns stall information for a specific torrent. Unlike the bulk ListStalled it also carries the inactivity counters for torrents that haven't crossed the stall threshold yet.

func (*Session) GetTransferStats

func (s *Session) GetTransferStats() TransferStats

GetTransferStats returns aggregate session statistics.

func (*Session) IsAltSpeedActive

func (s *Session) IsAltSpeedActive() bool

IsAltSpeedActive returns whether alt speed mode is currently active.

func (*Session) List

func (s *Session) List() []Info

List returns info about all torrents.

func (*Session) ListStalled

func (s *Session) ListStalled() []StalledTorrent

ListStalled iterates all managed torrents and returns those currently classified as stalled, filtering out the session startup grace period. Semantics match CheckStalls exactly; callers get the same decisions without having to re-implement the heuristic client-side.

func (*Session) LookupHistory

func (s *Session) LookupHistory(ctx context.Context, f HistoryFilter) ([]HistoryRecord, error)

LookupHistory returns torrent records matching the filter. Used by Pilot/Prism's history-aware UIs (library badges, manual-search guardrail, "downloaded but not in library" rail).

Returns an empty slice (not an error) when nothing matches — callers should treat "no record" as "Haul has never seen this".

func (*Session) MaxActiveDownloads

func (s *Session) MaxActiveDownloads() int

MaxActiveDownloads returns the runtime-effective cap on concurrently downloading torrents. Zero or negative means unlimited.

func (*Session) Pause

func (s *Session) Pause(hash string) error

Pause pauses a torrent. This is the user-initiated pause path: it clears queuePaused so the queue gate will treat this torrent as sticky-paused and never auto-resume it.

func (*Session) PauseOnComplete

func (s *Session) PauseOnComplete() bool

PauseOnComplete returns the runtime-effective value of the pause-on- complete setting. Initialized from cfg.PauseOnComplete at Session creation, optionally overridden by the `settings` DB table at startup, and mutated at runtime by SetPauseOnComplete (which the settings HTTP handler calls on UI toggle).

func (*Session) Peers

func (s *Session) Peers(hash string) ([]PeerInfo, error)

Peers returns a snapshot of currently-connected peers for a torrent. Empty slice if the torrent has no peers or metadata isn't ready. Error only for unknown hash.

func (*Session) Pieces

func (s *Session) Pieces(hash string) (*PiecesInfo, error)

Pieces returns a run-length-encoded snapshot of the torrent's piece state. Returns (nil, nil) if metadata hasn't been received yet — the frontend renders "Waiting for metadata…" in that case.

func (*Session) PublishHealth

func (s *Session) PublishHealth(ctx context.Context)

PublishHealth publishes a health update event. Call periodically.

func (*Session) Reannounce

func (s *Session) Reannounce(hash string) error

Reannounce forces a fresh announce to every tracker by restarting the torrent's announcers. ModifyTrackers stops the existing tracker scrapers and starts new ones, which announce immediately; Metainfo() clones the live announce list, so runtime-added trackers survive.

func (*Session) Recheck

func (s *Session) Recheck(ctx context.Context, hash string) error

Recheck re-verifies all pieces of a torrent.

func (*Session) Remove

func (s *Session) Remove(ctx context.Context, hash string, deleteFiles bool) error

Remove removes a torrent. If deleteFiles is true, downloaded data is deleted.

DB cleanup runs in a defer so it survives a panic from anacrolix/torrent's Drop() — without that, a library-internal crash mid-Drop would leave an orphan torrents row that restoreFromDB would resurrect on next startup, re-triggering the same panic. Real bug observed in the field with John Wick (info hash ec5086c1c…): library panic during tracker announce dispatcher → DB DELETE never ran → permanent crashloop on restart.

func (*Session) RemoveTags

func (s *Session) RemoveTags(hash string, tags []string) error

RemoveTags removes tags from a torrent.

func (*Session) RemoveTracker

func (s *Session) RemoveTracker(hash, url string) error

RemoveTracker rebuilds the announce list for the torrent without the given URL. anacrolix doesn't expose a "remove this tracker" call, so we collect the current list, filter out the removed URL, and reset the list via SetAnnounceList.

func (*Session) Resume

func (s *Session) Resume(hash string) error

Resume resumes a paused torrent. This is the user-initiated resume path: it clears queuePaused so subsequent state changes are clean. The queue gate runs immediately after; if Resume puts the active set over the cap, the lowest-priority active torrent (which may be the one just resumed) gets queue-paused.

func (*Session) SetAltSpeedEnabled

func (s *Session) SetAltSpeedEnabled(enabled bool)

SetAltSpeedEnabled toggles alternative speed limits at runtime. Note: anacrolix/torrent rate limiters are set at client creation. We track the state for the schedule checker and API to report.

func (*Session) SetCategory

func (s *Session) SetCategory(hash, category string) error

SetCategory assigns a category to a torrent.

func (*Session) SetGlobalDownloadLimit

func (s *Session) SetGlobalDownloadLimit(n int)

SetGlobalDownloadLimit updates the global download rate limit at runtime. n is in bytes per second; n == 0 means unlimited. Operates on the *rate.Limiter that was wired into anacrolix's client at startup, so the new limit applies immediately to every active torrent without rebuilding the engine.

func (*Session) SetGlobalUploadLimit

func (s *Session) SetGlobalUploadLimit(n int)

SetGlobalUploadLimit mirrors SetGlobalDownloadLimit for upload.

func (*Session) SetLocation

func (s *Session) SetLocation(hash, newPath string) error

SetLocation moves a torrent's data to a new save path.

func (*Session) SetMaxActiveDownloads

func (s *Session) SetMaxActiveDownloads(n int)

SetMaxActiveDownloads updates the runtime cap on concurrently downloading torrents and immediately re-runs the queue gate so the new value takes effect now (not just on next add/complete). Called by the settings HTTP handler.

func (*Session) SetMetadata

func (s *Session) SetMetadata(hash string, meta RequesterMetadata) error

SetMetadata attaches structured requester metadata to a torrent.

Persists the full struct to the `metadata` JSON column AND denormalizes the indexed fields (requester_*, requester_tmdb_id, season, episode, movie_id, series_id, episode_id) into their own columns so Pilot/Prism's history-lookup queries can hit indexes instead of scanning JSON.

func (*Session) SetPauseOnComplete

func (s *Session) SetPauseOnComplete(v bool)

SetPauseOnComplete updates the runtime pause-on-complete setting. Called by the settings HTTP handler when the user flips the "Stop seeding when complete" toggle in the UI. This is the only method that affects runtime behavior — writing to the `settings` DB table alone does NOT take effect until this is called.

func (*Session) SetPriority

func (s *Session) SetPriority(hash string, priority int) error

SetPriority sets the queue priority (lower = higher priority).

After updating the DB, the queue gate re-runs so a priority change takes effect immediately: if the user dragged a queued torrent above the cap, it gets resumed and the displaced torrent is queue-paused.

func (*Session) SetSeedLimits

func (s *Session) SetSeedLimits(hash string, ratioLimit float64, timeLimitSecs int) error

SetSeedLimits sets per-torrent seed ratio and time limits.

func (*Session) StartWatchDir

func (s *Session) StartWatchDir(ctx context.Context, dir string) error

StartWatchDir launches the auto-add goroutine. Caller (main.go) is responsible for setting up Session lifecycle; we tie our cleanup to a passed-in done channel so a session shutdown closes the watcher cleanly. Errors during goroutine setup are returned synchronously; runtime errors are logged.

func (*Session) Trackers

func (s *Session) Trackers(hash string) ([]TrackerInfo, error)

Trackers returns the configured tracker list from the torrent's metainfo, flattened across tiers with the tier index preserved on each entry. Does NOT include live announce state — see plan §6.1.

type StallInfo

type StallInfo struct {
	Stalled      bool       `json:"stalled"`
	Level        StallLevel `json:"level"`
	InactiveSecs int64      `json:"inactive_secs"`
	LastActivity *time.Time `json:"last_activity,omitempty"`
	Reason       string     `json:"reason"`
}

StallInfo holds stall detection data for a torrent.

type StallLevel

type StallLevel int

StallLevel classifies the severity of a stall.

const (
	StallNone        StallLevel = 0
	StallLevel1      StallLevel = 1 // No activity for stall_timeout — reannounce
	StallLevel2      StallLevel = 2 // No activity for 2x stall_timeout — force DHT
	StallLevel3      StallLevel = 3 // No activity for 5x stall_timeout — notify ecosystem
	StallNoPeersEver StallLevel = 4 // Never got a single peer — classic "dead torrent" signal
)

type StalledTorrent

type StalledTorrent struct {
	InfoHash     string     `json:"info_hash"`
	Name         string     `json:"name"`
	Level        StallLevel `json:"level"`
	Reason       string     `json:"reason"`
	InactiveSecs int64      `json:"inactive_secs"`
	AddedAt      time.Time  `json:"added_at"`
}

StalledTorrent pairs a torrent's identity with its current stall status. Used by the HTTP /api/v1/stalls endpoint so a consumer (Pilot's stall watcher) can get all stalled torrents in one call instead of N+1.

type Status

type Status string

Status represents the current state of a torrent.

const (
	StatusDownloading Status = "downloading"
	StatusSeeding     Status = "seeding"
	StatusPaused      Status = "paused"
	StatusChecking    Status = "checking"
	StatusQueued      Status = "queued"
	StatusCompleted   Status = "completed"
	StatusFailed      Status = "failed"
)

type TrackerInfo

type TrackerInfo struct {
	Tier int    `json:"tier"`
	URL  string `json:"url"`
}

TrackerInfo is a single configured tracker from the torrent's metainfo. v1 does NOT include live announce state (last announce time, reported peers/seeds, errors) — see plans/haul-torrent-detail-enhancements.md §6.1.

type TransferStats

type TransferStats struct {
	TotalTorrents   int   `json:"total_torrents"`
	ActiveDownloads int   `json:"active_downloads"`
	ActiveUploads   int   `json:"active_uploads"`
	TotalDownloaded int64 `json:"total_downloaded"`
	TotalUploaded   int64 `json:"total_uploaded"`
	DownloadSpeed   int64 `json:"download_speed"`
	UploadSpeed     int64 `json:"upload_speed"`
	TotalPeers      int   `json:"peers_connected"`
	TotalSeeds      int   `json:"seeds_connected"`
}

TransferStats holds aggregate session statistics.

type WebhookDispatcher

type WebhookDispatcher struct {
	// contains filtered or unexported fields
}

WebhookDispatcher sends events to configured webhook URLs.

func NewWebhookDispatcher

func NewWebhookDispatcher(webhooks []config.WebhookConfig, logger *slog.Logger) *WebhookDispatcher

NewWebhookDispatcher creates a dispatcher for outbound webhooks.

func (*WebhookDispatcher) HandleEvent

func (d *WebhookDispatcher) HandleEvent(_ context.Context, e events.Event)

HandleEvent implements events.Handler — sends matching events to webhooks.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL