Documentation
¶
Overview ¶
Package streamer manages active torrents for HTTP streaming. Torrents stay loaded while clients are reading; idle ones are evicted.
Index ¶
- Constants
- Variables
- func ArtSourceRank(source string) int
- func DefaultFavoritesPath(dataDir string) string
- func DefaultMetadataCachePath(dataDir string) string
- func DefaultSeedsPath(dataDir string) string
- func InTimeRange(now time.Time, rangeStr string) bool
- func PhysicalBytes(info os.FileInfo) int64
- func StartBandwidthScheduler(ctx context.Context, s *Streamer, cfg *config.Config)
- type CacheEntry
- type CacheStats
- type CachedArt
- type CachedFile
- type CachedHealth
- type CachedMeta
- type Chapter
- type Config
- type DownloadStorageSpec
- type Favorite
- type FavoriteFolder
- type FavoritesStore
- func (f *FavoritesStore) Add(name, infoHash, magnet, reason string, userID int) error
- func (f *FavoritesStore) Close()
- func (f *FavoritesStore) CreateFolder(userID int, name string, parentID *int, hidden bool) (*FavoriteFolder, error)
- func (f *FavoritesStore) DeleteFolder(userID, id int) error
- func (f *FavoritesStore) GetFolder(userID, id int) (*FavoriteFolder, error)
- func (f *FavoritesStore) HashSetForUser(userID int, includeAll bool) (map[string]bool, error)
- func (f *FavoritesStore) HiddenHashSet(userID int, includeAll bool) (map[string]bool, error)
- func (f *FavoritesStore) HiddenLocalPaths(userID int) ([]HiddenLocalPath, error)
- func (f *FavoritesStore) HiddenLocalPathsAll() ([]HiddenLocalPathOwned, error)
- func (f *FavoritesStore) IsFavorite(name string) bool
- func (f *FavoritesStore) IsFavoriteByHash(infoHash string) bool
- func (f *FavoritesStore) IsFavoriteOf(name string, userID int) bool
- func (f *FavoritesStore) List(userID int, includeAll, includeHidden bool) ([]Favorite, error)
- func (f *FavoritesStore) ListFolders(userID int, includeHidden bool) ([]FavoriteFolder, error)
- func (f *FavoritesStore) MoveFavoriteToFolder(userID int, name string, folderID *int) error
- func (f *FavoritesStore) MoveFolder(userID, id int, newParent *int) error
- func (f *FavoritesStore) ReconcileMagnets() (int, error)
- func (f *FavoritesStore) RecoverViaSearch(searcher MagnetSearcher, limit int) (int, error)
- func (f *FavoritesStore) Remove(name string, userID int, includeAll bool) error
- func (f *FavoritesStore) RenameFolder(userID, id int, newName string) error
- func (f *FavoritesStore) SetFolderHidden(userID, id int, hidden bool) error
- func (f *FavoritesStore) SetLocalPathHidden(userID int, mount, path string, hidden bool) error
- type FileInfo
- type FilePathResolver
- type GlobalRate
- type HashResult
- type HiddenLocalPath
- type HiddenLocalPathOwned
- type MagnetMatch
- type MagnetSearcher
- type MetadataCache
- func (m *MetadataCache) ArtNegativeFresh(infoHash string, ttl time.Duration) bool
- func (m *MetadataCache) Close() error
- func (m *MetadataCache) Get(infoHash string) *CachedMeta
- func (m *MetadataCache) GetArt(infoHash string) *CachedArt
- func (m *MetadataCache) GetHealth(infoHash string) *CachedHealth
- func (m *MetadataCache) GetSortMeta(hashes []string) map[string]SortMeta
- func (m *MetadataCache) Set(info *TorrentInfo) error
- func (m *MetadataCache) SetArt(infoHash string, art *CachedArt) error
- func (m *MetadataCache) SetHealth(infoHash string, seeders, peers int) error
- type PeerInfo
- type ProbeResult
- type SeedEntry
- type SeedsStore
- type SidecarSubtitle
- type SortMeta
- type Streamer
- func (s *Streamer) AcquireViewer(hash metainfo.Hash)
- func (s *Streamer) ActiveList() []*TorrentInfo
- func (s *Streamer) Add(ctx context.Context, magnetOrURL string) (*TorrentInfo, error)
- func (s *Streamer) AddForDownload(ctx context.Context, magnetOrURL string, ds DownloadStorageSpec) (*TorrentInfo, error)
- func (s *Streamer) CanProbeHealth(hash metainfo.Hash, magnet string) bool
- func (s *Streamer) ClearAll() error
- func (s *Streamer) ClearEntry(name string) error
- func (s *Streamer) Client() *torrent.Client
- func (s *Streamer) Close()
- func (s *Streamer) Drop(hash metainfo.Hash)
- func (s *Streamer) DropSeed(hash metainfo.Hash)
- func (s *Streamer) EnsureActive(ctx context.Context, magnet string) (metainfo.Hash, error)
- func (s *Streamer) EnsureActiveForDownload(ctx context.Context, magnet string, ds DownloadStorageSpec) (metainfo.Hash, error)
- func (s *Streamer) ExtractArtwork(ctx context.Context, hash metainfo.Hash, fileIdx int) ([]byte, bool, error)
- func (s *Streamer) ExtractSubtitle(ctx context.Context, hash metainfo.Hash, fileIdx, trackIdx int) ([]byte, error)
- func (s *Streamer) ExtractThumbnail(ctx context.Context, hash metainfo.Hash, fileIdx int, atSeconds int) ([]byte, bool, error)
- func (s *Streamer) Favorites() *FavoritesStore
- func (s *Streamer) FileReader(hash metainfo.Hash, fileIdx int) (io.ReadSeekCloser, *torrent.File, error)
- func (s *Streamer) FileRelPath(h metainfo.Hash, fileIdx int) string
- func (s *Streamer) Get(hash metainfo.Hash) (*TorrentInfo, error)
- func (s *Streamer) GlobalStats() GlobalRate
- func (s *Streamer) HasFilePathResolver() bool
- func (s *Streamer) HealthSnapshot(hash metainfo.Hash) (health *CachedHealth, active bool)
- func (s *Streamer) ImportTorrentBytes(data []byte) (hash, name string, err error)
- func (s *Streamer) IsDownloadProtected(name string) bool
- func (s *Streamer) ListenPort() int
- func (s *Streamer) LiveStats(hash metainfo.Hash) (down, up, uploaded int64, seeders int, ok bool)
- func (s *Streamer) MatchesSeedTrackerCached(hash metainfo.Hash) bool
- func (s *Streamer) MetadataCache() *MetadataCache
- func (s *Streamer) MetainfoPath(h metainfo.Hash) string
- func (s *Streamer) OSHash(ctx context.Context, hash metainfo.Hash, fileIdx int) (HashResult, error)
- func (s *Streamer) ParseMagnet(magnet string) (hash, name string, err error)
- func (s *Streamer) Pause(hash metainfo.Hash) error
- func (s *Streamer) PauseAll() int
- func (s *Streamer) Peers(hash metainfo.Hash) ([]PeerInfo, error)
- func (s *Streamer) Prefetch(hash metainfo.Hash, fileIdx int) error
- func (s *Streamer) Probe(ctx context.Context, hash metainfo.Hash, fileIdx int) (ProbeResult, error)
- func (s *Streamer) ProbeHealthAsync(hash metainfo.Hash, magnet string)
- func (s *Streamer) RateLimits() (down, up int64)
- func (s *Streamer) ReadArtBytes(rel string) ([]byte, error)
- func (s *Streamer) ReadSidecar(ctx context.Context, hash metainfo.Hash, fileIdx int) ([]byte, string, error)
- func (s *Streamer) RecheckAllFiles(hash metainfo.Hash) error
- func (s *Streamer) RecheckFile(hash metainfo.Hash, fileIdx int) error
- func (s *Streamer) RegisterDownload(name string)
- func (s *Streamer) ReleaseViewer(hash metainfo.Hash) (scheduled, lastViewer bool)
- func (s *Streamer) Resume(hash metainfo.Hash) error
- func (s *Streamer) ResumeAll() int
- func (s *Streamer) SaveArtBytes(hash metainfo.Hash, data []byte) (string, error)
- func (s *Streamer) SetFavorites(f *FavoritesStore)
- func (s *Streamer) SetFilePathResolver(r FilePathResolver)
- func (s *Streamer) SetFilePriority(hash metainfo.Hash, fileIdx int, label string) error
- func (s *Streamer) SetMetadataCache(c *MetadataCache)
- func (s *Streamer) SetPriority(hash metainfo.Hash, label string) error
- func (s *Streamer) SetRateLimits(down, up int64)
- func (s *Streamer) SetSeedTrackers(trackers []string)
- func (s *Streamer) SetSeeds(st *SeedsStore)
- func (s *Streamer) SetStreamReadahead(mb int)
- func (s *Streamer) Sidecars(hash metainfo.Hash, videoFileIdx int) ([]SidecarSubtitle, error)
- func (s *Streamer) Stats() (*CacheStats, error)
- func (s *Streamer) StreamReadaheadForTesting() int64
- func (s *Streamer) TorrentImage(ctx context.Context, hash metainfo.Hash) ([]byte, string, error)
- func (s *Streamer) TrackerStats(ctx context.Context, hash metainfo.Hash, magnet string) []TrackerScrape
- func (s *Streamer) UnregisterDownload(name string)
- func (s *Streamer) UpdateJackettHost(rawURL string)
- func (s *Streamer) VerifyFile(hash metainfo.Hash, fileIdx int) error
- func (s *Streamer) VerifyTorrent(hash metainfo.Hash) error
- type TorrentInfo
- type Track
- type TrackerScrape
Constants ¶
const ( ErrFileIndexOutOfRange = "file index out of range" )
const ArtSourceNone = "none"
ArtSourceNone marks "the resolve chain ran and found nothing". Persisted (with a timestamp via SetArt) so ResolveArt doesn't re-run the whole AI+TMDB+web chain on every card render for a title that has no art — see ArtNegativeFresh.
const DefaultPeerPort = 51469
DefaultPeerPort is the inbound BitTorrent peer port used when none is configured (no VPN forwarded port, no JACKUI_PEER_PORT). Exported so the boot wiring can treat "fell back to default" identically to the streamer.
const ( // HealthFreshFor — re-probe only when the persisted snapshot is older. HealthFreshFor = 30 * time.Minute )
Variables ¶
var ErrTorrentNotActive = errors.New("torrent not active")
ErrTorrentNotActive is the sentinel returned by streamer methods when the requested torrent isn't loaded in the active set. Handlers match it with errors.Is to map to HTTP 404; wrap with %w when adding context.
Functions ¶
func ArtSourceRank ¶
ArtSourceRank orders art sources by trustworthiness so resolution only ever *upgrades* a persisted thumbnail (uploader-curated image > matched poster > web search > raw frame). The web search is a fallback for content TMDB can't match (adult/obscure) and ranks above a raw frame but below a real poster. Exported so the resolver in the handlers layer shares the order.
func DefaultFavoritesPath ¶
DefaultFavoritesPath returns the standard location inside the stream data dir.
func DefaultMetadataCachePath ¶
DefaultMetadataCachePath returns the standard location inside the stream data dir.
func DefaultSeedsPath ¶
DefaultSeedsPath returns the standard location inside the state dir.
func InTimeRange ¶
InTimeRange verifica se a hora atual cai dentro de um time_range como "08:00-18:00". Suporta faixas que viram a noite, ex: "22:00-06:00".
func PhysicalBytes ¶
PhysicalBytes returns the number of bytes actually allocated on disk for the file described by info. On POSIX filesystems this is `st.Blocks * 512` (the historical stat block unit), which correctly reflects sparse holes — a 10 GB sparse file with only one 4 KiB block written returns 4096.
If the underlying syscall struct isn't available for any reason, we fall back to the logical size so callers never see zero.
Types ¶
type CacheEntry ¶
type CacheEntry struct {
Path string `json:"path"` // relative to DataDir
Size int64 `json:"size"`
ModTime time.Time `json:"modTime"`
IsActive bool `json:"isActive"` // currently being downloaded/seeded
IsFavorite bool `json:"isFavorite"` // protected from eviction
// InfoHash is the torrent's hex-encoded SHA1 info hash. Populated when the
// torrent is either active or has a persisted .torrent in metainfoDir.
// Empty string when we can't resolve the hash — the UI hides Play in that case.
InfoHash string `json:"infoHash,omitempty"`
}
CacheEntry describes one item on disk in the cache directory.
type CacheStats ¶
type CacheStats struct {
DataDir string `json:"dataDir"`
TotalSize int64 `json:"totalSize"`
MaxSize int64 `json:"maxSize"` // 0 = unlimited
NumActive int `json:"numActive"` // currently loaded torrents
Entries []CacheEntry `json:"entries"`
// Filesystem footprint of the disk hosting DataDir (0 = statfs unavailable).
DiskFree int64 `json:"diskFree"`
DiskTotal int64 `json:"diskTotal"`
// Lifetime LRU eviction counters (since process start).
EvictedCount int64 `json:"evictedCount"`
EvictedBytes int64 `json:"evictedBytes"`
LastEvictionAt *time.Time `json:"lastEvictionAt,omitempty"`
}
CacheStats summarizes disk usage of the streaming cache.
type CachedArt ¶
type CachedArt struct {
// Source is "torrent" | "tmdb" | "frame" — the chain step that produced it.
Source string `json:"source"`
// Path is the DataDir-relative file for byte-backed sources (torrent/frame).
// Empty for tmdb (the image lives on the remote CDN at PosterURL).
Path string `json:"path,omitempty"`
// PosterURL is the remote image for source=="tmdb"; the handler 302s to it.
PosterURL string `json:"posterUrl,omitempty"`
TmdbID int `json:"tmdbId,omitempty"`
ImdbID string `json:"imdbId,omitempty"`
}
CachedArt is the resolved thumbnail for a torrent, persisted per info_hash so we never re-run the (expensive) resolution chain — embedded torrent image, TMDB lookup, or a captured video frame. Stored alongside the metadata row but updated via SetArt() with a disjoint column set, so caching metadata never clobbers the art and vice-versa.
type CachedFile ¶
type CachedHealth ¶
type CachedHealth struct {
Seeders int `json:"seeders"`
Peers int `json:"peers"`
Available bool `json:"available"` // seeders>0 || peers>0
CheckedAt time.Time `json:"checkedAt"`
}
CachedHealth is the last-known swarm health for a torrent, persisted so a card can show a prior estimate (with its age) instantly while a fresh probe runs.
type CachedMeta ¶
type CachedMeta struct {
InfoHash string `json:"infoHash"`
Name string `json:"name"`
TotalSize int64 `json:"totalSize"`
Files []CachedFile `json:"files"`
PrimaryFile int `json:"primaryFile"`
CachedAt time.Time `json:"cachedAt"`
}
CachedMeta is the minimal shape we cache (a strict subset of TorrentInfo). Rates and per-file Downloaded/Progress are runtime-only and NOT cached — they would be stale and misleading.
type Chapter ¶
type Chapter struct {
Index int `json:"index"`
StartSec float64 `json:"startSec"`
EndSec float64 `json:"endSec,omitempty"`
Title string `json:"title,omitempty"`
}
Chapter is one chapter marker embedded in the media (MKV/MP4). Times are in seconds. The player navigates by setting video.currentTime to StartSec — this works for both direct-play and HLS (the transcode drops embedded chapters, so a <track kind="chapters"> would be empty; the probe list is the source).
type Config ¶
type Config struct {
DataDir string // where pieces are written; subdirs per torrent
IdleTimeout time.Duration // drop torrent after this much inactivity (files stay)
MetadataWait time.Duration // how long to block waiting for .torrent metadata
MaxCacheSize int64 // total cache cap in bytes; 0 = unlimited (no eviction)
// MaxDownloadRate caps inbound peer bandwidth in bytes/sec; 0 = unlimited.
// Wired into the anacrolix ClientConfig.DownloadRateLimiter. Can be updated
// at runtime via Streamer.SetRateLimits.
MaxDownloadRate int64
// MaxUploadRate caps outbound peer bandwidth in bytes/sec; 0 = unlimited.
MaxUploadRate int64
// JackettHost is the host (no port) of the configured Jackett instance. The
// SSRF guard trusts it (Jackett lives on the private LAN, so its download
// links are legitimately private addresses) and the apikey below is injected
// server-side so it never has to travel through the browser.
JackettHost string
JackettAPIKey string
// ListenPort is the BitTorrent peer port for inbound connections. 0 → DefaultPeerPort.
// Behind a VPN this should be the provider's forwarded port so peers can
// reach us (seed + better leech). See resolvePeerPort in main.
ListenPort int
// ── Performance / hardware tuning (0/"" = default da lib) ──
// Readahead é o buffer de leitura à frente por sessão de streaming, em bytes.
// 0 → 32 MiB. Aplicado por Reader; mutável ao vivo via SetStreamReadahead.
Readahead int64
// StorageBackend: "file" (default, grava direto) ou "mmap" (page cache).
// Lido só na construção do client (New) — mudar exige reiniciar o processo.
StorageBackend string
// Tuning de peers/CPU — só aplicados em New (exigem reinício). 0 = default
// anacrolix (conns=50, half-open=25, peersHighWater=500, pieceHashers=2).
MaxConnsPerTorrent int
HalfOpenConns int
PeersHighWater int
PieceHashers int
// SeedTrackers lista substrings de announce URLs cujos torrents devem
// continuar seedando após o uso (não dropados). Ver Streamer.seedTrackers.
SeedTrackers []string
}
type DownloadStorageSpec ¶
DownloadStorageSpec tells the add path to write a torrent's data DIRECTLY to its final destination on bulk storage instead of the SSD piece cache.
- BaseDir is the PARENT directory already resolved by the downloads worker (e.g. downloadDir/<username> or sharedDir/<category>) — WITHOUT the torrent name segment. The name isn't known until metadata arrives, so the storage appends it itself inside TorrentDirMaker (which runs post-metadata).
- Sanitize turns the real torrent name (t.Name()) into the final folder segment. It is INJECTED (rather than imported) because the canonical sanitizer lives in internal/downloads, which already imports this package — importing it back would be a cycle. Passing it as a func keeps the path the storage writes to byte-identical to the path the worker's completionDest computes, so the move-on-completion is a no-op.
type Favorite ¶
type Favorite struct {
Name string `json:"name"` // matches CacheEntry.Path (filesystem name)
InfoHash string `json:"infoHash"` // hex hash, if known
Magnet string `json:"magnet"` // magnet URI — enables Play from /favorites without re-search
UserID int `json:"userId"`
FavoritedAt time.Time `json:"favoritedAt"`
Reason string `json:"reason"` // "manual" | "auto-5min"
FolderID *int `json:"folderId"` // nil = root level; otherwise nested in a FavoriteFolder
// Sort hints filled by the handler from the metadata cache (a separate DB, so
// no JOIN here). Zero/nil = unknown (never resolved/probed) — sorts last.
TotalSize int64 `json:"totalSize,omitempty"` // bytes; 0 = unknown
Seeders *int `json:"seeders,omitempty"` // nil = never probed
}
type FavoriteFolder ¶
type FavoriteFolder struct {
ID int `json:"id"`
UserID int `json:"userId"`
Name string `json:"name"`
ParentID *int `json:"parentId"`
Position int `json:"position"`
Hidden bool `json:"hidden"`
CreatedAt time.Time `json:"createdAt"`
}
FavoriteFolder represents an organizational folder in the user's favorites tree. Subfolders are modeled via ParentID (nil = root level). One user's tree can be arbitrarily deep; cycle prevention is handled at Move time.
type FavoritesStore ¶
type FavoritesStore struct {
// contains filtered or unexported fields
}
FavoritesStore persists "favorite" markings for streamed torrents. Favorites are protected from cache eviction (both LRU and manual clear-all).
Schema: one row per torrent name (as stored on disk), nullable info_hash for cross-reference.
func NewFavorites ¶
func NewFavorites(pool *sql.DB) (*FavoritesStore, error)
NewFavorites wires the favorites store onto the shared Postgres pool. Schema is applied centrally (internal/db migrations).
func (*FavoritesStore) Add ¶
func (f *FavoritesStore) Add(name, infoHash, magnet, reason string, userID int) error
Add marks a stream as favorite. Idempotent — re-adding refreshes the timestamp. userID=0 means "no auth/legacy". magnet may be empty if unknown.
func (*FavoritesStore) Close ¶
func (f *FavoritesStore) Close()
Close is a no-op: the shared pool's lifecycle is owned by main.
func (*FavoritesStore) CreateFolder ¶
func (f *FavoritesStore) CreateFolder(userID int, name string, parentID *int, hidden bool) (*FavoriteFolder, error)
CreateFolder makes a new folder under the optional parent. parentID nil creates a root-level folder. hidden keeps it out of the default listing.
func (*FavoritesStore) DeleteFolder ¶
func (f *FavoritesStore) DeleteFolder(userID, id int) error
DeleteFolder removes a folder. ON DELETE CASCADE removes subfolders too; favorites in deleted folders fall back to root (ON DELETE SET NULL).
func (*FavoritesStore) GetFolder ¶
func (f *FavoritesStore) GetFolder(userID, id int) (*FavoriteFolder, error)
GetFolder fetches a single folder; returns error if it doesn't belong to user.
func (*FavoritesStore) HashSetForUser ¶
HashSetForUser returns all info_hashes the user has favorited as a set. Used pelo handler de busca pra enriquecer SearchResult com isFavorited via uma query só, em vez de IsFavoriteByHash N vezes. includeAll=true devolve hashes de todos os usuários (admin "all=1"). Hashes vazios são pulados.
func (*FavoritesStore) HiddenHashSet ¶
HiddenHashSet returns, as a set, the info_hashes of favourites that live in a hidden folder. Used to keep hidden-folder titles out of Continue Watching and the downloads list the same way they're kept out of the favourites view. includeAll=true (admin) spans every user's hidden folders.
func (*FavoritesStore) HiddenLocalPaths ¶
func (f *FavoritesStore) HiddenLocalPaths(userID int) ([]HiddenLocalPath, error)
HiddenLocalPaths returns the local (mount, path) pairs the user has hidden.
func (*FavoritesStore) HiddenLocalPathsAll ¶
func (f *FavoritesStore) HiddenLocalPathsAll() ([]HiddenLocalPathOwned, error)
HiddenLocalPathsAll returns every hidden local (mount, path) across all users, tagged with the owning user_id. Admin-only callers use it so an item one user hid stays hidden in the cross-user listing too.
func (*FavoritesStore) IsFavorite ¶
func (f *FavoritesStore) IsFavorite(name string) bool
IsFavorite reports whether the given on-disk name is favorited (by any user). Used by Streamer cache eviction logic — protects favorites of all users from auto-delete.
func (*FavoritesStore) IsFavoriteByHash ¶
func (f *FavoritesStore) IsFavoriteByHash(infoHash string) bool
IsFavoriteByHash reports whether any favorite row references this infoHash.
func (*FavoritesStore) IsFavoriteOf ¶
func (f *FavoritesStore) IsFavoriteOf(name string, userID int) bool
IsFavoriteOf reports whether the given name is favorited specifically by a user.
func (*FavoritesStore) List ¶
func (f *FavoritesStore) List(userID int, includeAll, includeHidden bool) ([]Favorite, error)
List returns favorites for a user (or all when includeAll=true), most recent first.
func (*FavoritesStore) ListFolders ¶
func (f *FavoritesStore) ListFolders(userID int, includeHidden bool) ([]FavoriteFolder, error)
ListFolders returns all folders for a user. The UI builds the tree client-side via parent_id linkage — simpler than recursive SQL.
func (*FavoritesStore) MoveFavoriteToFolder ¶
func (f *FavoritesStore) MoveFavoriteToFolder(userID int, name string, folderID *int) error
MoveFavoriteToFolder reassigns a favorite to a folder (nil = root).
func (*FavoritesStore) MoveFolder ¶
func (f *FavoritesStore) MoveFolder(userID, id int, newParent *int) error
MoveFolder re-parents a folder. Pass nil to move to root. Cycle prevention: walk the parent chain — if we encounter the folder being moved, reject.
func (*FavoritesStore) ReconcileMagnets ¶
func (f *FavoritesStore) ReconcileMagnets() (int, error)
ReconcileMagnets repairs magnet-less favorites without any network call:
- info_hash present, magnet empty → synthesize a tracker-less magnet (anacrolix finds peers via DHT — same shape the quick-favorite fallback uses).
- both empty, but the metadata cache (same DB) has a row with the same name → adopt that info_hash and synthesize the magnet. Only helps favorites whose torrent was activated at least once (metadata is written post-activation).
Returns the number of rows repaired. Idempotent.
func (*FavoritesStore) RecoverViaSearch ¶
func (f *FavoritesStore) RecoverViaSearch(searcher MagnetSearcher, limit int) (int, error)
RecoverViaSearch re-searches each magnet-less favorite by name and links the best confident match (see bestMagnetMatch). Bounded by limit (re-links per boot) and conservative on purpose: a wrong match is worse than none. Returns the number repaired. A nil searcher or limit<=0 is a no-op.
func (*FavoritesStore) Remove ¶
func (f *FavoritesStore) Remove(name string, userID int, includeAll bool) error
Remove unmarks a favorite. If userID > 0 and not includeAll, only that user's row is removed.
func (*FavoritesStore) RenameFolder ¶
func (f *FavoritesStore) RenameFolder(userID, id int, newName string) error
RenameFolder updates the display name.
func (*FavoritesStore) SetFolderHidden ¶
func (f *FavoritesStore) SetFolderHidden(userID, id int, hidden bool) error
SetFolderHidden flips a folder's hidden curtain.
func (*FavoritesStore) SetLocalPathHidden ¶
func (f *FavoritesStore) SetLocalPathHidden(userID int, mount, path string, hidden bool) error
SetLocalPathHidden hides (hidden=true) or unhides a local (mount, path) for a user. Idempotent: hiding an already-hidden path is a no-op.
type FileInfo ¶
type FileInfo struct {
Index int `json:"index"`
Path string `json:"path"`
Size int64 `json:"size"`
IsVideo bool `json:"isVideo"`
Downloaded int64 `json:"downloaded"`
Progress float64 `json:"progress"` // 0..1
Priority string `json:"priority"` // none|low|normal|high
}
FileInfo is the JSON-friendly view of a file inside a torrent.
type FilePathResolver ¶
FilePathResolver resolves an info_hash and file index to a local physical file path. Returns the file path and true if the file is completed and exists on disk.
type GlobalRate ¶
type GlobalRate struct {
DownRate int64 `json:"downRate"`
UpRate int64 `json:"upRate"`
ActiveTorrents int `json:"activeTorrents"`
}
GlobalRate aggregates download/upload rates across all active torrents.
type HashResult ¶
HashResult is the cached OpenSubtitles file hash for one (torrent, file).
func ComputeFileOSHash ¶
func ComputeFileOSHash(f io.ReadSeeker, size int64) (HashResult, error)
ComputeFileOSHash opens a file on disk and returns its OpenSubtitles hash. Used by the /local subtitle endpoints — bypasses the torrent-only OSHash() path so legendas funcionam pra arquivos fora do streamer.
type HiddenLocalPath ¶
HiddenLocalPath is a (mount, path) the user has marked hidden in the local browser.
type HiddenLocalPathOwned ¶
HiddenLocalPathOwned is a hidden local (mount, path) together with the user that hid it. Used by the admin "all users" download view, which must resolve each path under the OWNER's scope (UserSubpath mounts) — not the requester's.
type MagnetMatch ¶
MagnetMatch is the minimal slice of a search result the recovery needs to re-link a magnet-less favorite by re-searching its title.
type MagnetSearcher ¶
type MagnetSearcher interface {
SearchByName(name string) ([]MagnetMatch, error)
}
MagnetSearcher re-resolves a favorite by its stored name (its release title). The adapter in cmd/server wraps the Jackett client; tests inject a fake.
type MetadataCache ¶
type MetadataCache struct {
// contains filtered or unexported fields
}
MetadataCache persists TorrentInfo snapshots keyed by info_hash so the UI can render the file list + name *instantly* when reopening a torrent the user has touched before, even before anacrolix finishes its DHT metadata fetch.
Why this exists: a magnet-only torrent takes 3-10s to resolve to .torrent metadata from peers/DHT on each fresh load. The first time we accept the delay; subsequent opens of the same hash should be ~instant. Metadata is immutable per info_hash so we can keep entries forever — only the on-demand torrent client load takes any time at all.
func NewMetadataCache ¶
func NewMetadataCache(pool *sql.DB) (*MetadataCache, error)
NewMetadataCache wires the metadata cache onto the shared Postgres pool. Schema is applied centrally (internal/db migrations).
func (*MetadataCache) ArtNegativeFresh ¶
func (m *MetadataCache) ArtNegativeFresh(infoHash string, ttl time.Duration) bool
ArtNegativeFresh reports whether a recent "no art found" marker exists, so the caller can short-circuit instead of re-running the expensive resolve chain. A real art source (rank > 0) is never treated as a negative marker.
func (*MetadataCache) Close ¶
func (m *MetadataCache) Close() error
Close is a no-op: the shared pool's lifecycle is owned by main.
func (*MetadataCache) Get ¶
func (m *MetadataCache) Get(infoHash string) *CachedMeta
Get returns a cached snapshot or nil if not present. Never returns error for "not found" — the caller treats nil as "no cache" and falls through to the live torrent client.
func (*MetadataCache) GetArt ¶
func (m *MetadataCache) GetArt(infoHash string) *CachedArt
GetArt returns the persisted thumbnail for an info_hash, or nil when none has been resolved yet. Never errors on "not found" — callers treat nil as "no art".
func (*MetadataCache) GetHealth ¶
func (m *MetadataCache) GetHealth(infoHash string) *CachedHealth
GetHealth returns the persisted swarm health, or nil if never probed.
func (*MetadataCache) GetSortMeta ¶
func (m *MetadataCache) GetSortMeta(hashes []string) map[string]SortMeta
GetSortMeta returns size+seeders for the given hashes in a single query, keyed by info_hash. Hashes with no cached row are simply absent from the map. Used to enrich the favorites list for sorting without a cross-DB JOIN.
func (*MetadataCache) Set ¶
func (m *MetadataCache) Set(info *TorrentInfo) error
Set saves a snapshot. Called by Streamer.Add() once anacrolix delivers metadata, so subsequent opens of the same hash hit the cache.
func (*MetadataCache) SetArt ¶
func (m *MetadataCache) SetArt(infoHash string, art *CachedArt) error
SetArt persists a resolved thumbnail. Uses a column set disjoint from Set() so it neither requires nor clobbers the metadata snapshot — an art-only row (name=”) is created if the torrent's metadata hasn't been cached yet.
type PeerInfo ¶
type PeerInfo struct {
Addr string `json:"addr"`
Client string `json:"client,omitempty"`
Network string `json:"network,omitempty"` // "tcp" | "utp" | ...
Availability float64 `json:"availability"` // 0..1 fraction of pieces the peer has
DownRate int64 `json:"downRate"` // bytes/s we receive from this peer
UpRate int64 `json:"upRate"` // bytes/s we send to this peer
Downloaded int64 `json:"downloaded"` // data bytes read from this peer
Uploaded int64 `json:"uploaded"` // data bytes written to this peer
IsSeeder bool `json:"isSeeder"` // peer reports all pieces
Receiving bool `json:"receiving"` // inferred: downRate > 0
Sending bool `json:"sending"` // inferred: upRate > 0
Encrypted bool `json:"encrypted,omitempty"`
}
PeerInfo is the JSON-friendly view of one connected peer, for the downloads "Peers" panel. anacrolix v1.61.0 doesn't export choke/interest, so Sending / Receiving are INFERRED from live transfer rates rather than read directly.
type ProbeResult ¶
type ProbeResult struct {
Audio []Track `json:"audio"`
Subtitles []Track `json:"subtitles"`
Chapters []Chapter `json:"chapters"`
// DurationSec is the total media duration in seconds, 0 when ffprobe
// couldn't determine it (e.g. MP4 with moov-at-end whose tail isn't
// downloaded yet). Callers must treat 0 as "unknown" and fall back.
DurationSec float64 `json:"durationSec"`
// VideoCodec / Container / AudioCodec são os fatos da fonte; NeedsTranscode é
// a DECISÃO (navegador-agnóstica): MKV/HEVC/AV1/AC3/DTS não tocam direto em
// browser nenhum → tem que transcodificar pra HLS. O front decide por isto
// (não mais pelo NOME do arquivo, que errava e mandava incompatível pro
// direct-play → errorCode 4 no Safari). Mesma lógica do classifyForBrowser
// dos arquivos locais. Vazio até o ffprobe rodar.
VideoCodec string `json:"videoCodec"`
Container string `json:"container"`
AudioCodec string `json:"audioCodec"`
NeedsTranscode bool `json:"needsTranscode"`
TranscodeReason string `json:"transcodeReason,omitempty"`
}
ProbeResult lists all switchable tracks in a torrent file.
func ProbeLocal ¶ added in v0.79.5
func ProbeLocal(ctx context.Context, path string) (ProbeResult, error)
ProbeLocal runs ffprobe on a local file path and returns the parsed tracks, chapters, duration and codec/container facts. It reuses parseProbeOutput — the SAME decoder the torrent path (Probe) uses — so /api/local/* handlers share ONE ffprobe invocation + parser with the torrent side instead of duplicating it. The caller owns the ctx/timeout.
type SeedEntry ¶
type SeedEntry struct {
InfoHash string `json:"infoHash"`
Magnet string `json:"magnet"`
Name string `json:"name"`
AddedAt time.Time `json:"addedAt"`
}
SeedEntry is one persisted seed row.
type SeedsStore ¶
type SeedsStore struct {
// contains filtered or unexported fields
}
SeedsStore persists the torrents that must keep seeding (because they belong to a configured seed-tracker, e.g. jackui). On boot the streamer re-adds every entry so seeding resumes without the user re-opening anything.
This is separate from FavoritesStore: favorites are a user-facing list that only protects pieces from LRU eviction, whereas a seed entry is an automatic, tracker-driven marker whose job is to bring the torrent back into the swarm.
func NewSeeds ¶
func NewSeeds(pool *sql.DB) (*SeedsStore, error)
NewSeeds wires the seeds store onto the shared Postgres pool. Schema is applied centrally (internal/db migrations).
func (*SeedsStore) Add ¶
func (s *SeedsStore) Add(infoHash, magnet, name string) error
Add upserts a seed entry. Idempotent — re-adding the same hash refreshes the magnet/name without disturbing added_at. Nil-safe receiver.
func (*SeedsStore) Close ¶
func (s *SeedsStore) Close() error
Close is a no-op: the shared pool's lifecycle is owned by main.
func (*SeedsStore) Has ¶
func (s *SeedsStore) Has(infoHash string) bool
Has reports whether a hash is already persisted. Nil-safe receiver.
func (*SeedsStore) List ¶
func (s *SeedsStore) List() ([]SeedEntry, error)
List returns all persisted seed entries, newest first. Nil-safe receiver.
func (*SeedsStore) Remove ¶
func (s *SeedsStore) Remove(infoHash string) error
Remove deletes a seed entry. Nil-safe receiver.
type SidecarSubtitle ¶
type SidecarSubtitle struct {
Index int `json:"index"` // file index in torrent (use for download)
Path string `json:"path"` // full path within torrent
Size int64 `json:"size"`
Language string `json:"language"` // ISO-639 best-effort from filename
Format string `json:"format"` // "srt" | "vtt" | "ass" | "ssa" | "sub"
}
SidecarSubtitle describes a standalone subtitle file inside the torrent (not embedded in the container, just next to the video).
type SortMeta ¶
SortMeta is the size+seeders pair used to sort the favorites list. Seeders is -1 when the swarm was never probed (so it sorts last).
type Streamer ¶
type Streamer struct {
// contains filtered or unexported fields
}
func NewForTesting ¶
func NewForTesting() *Streamer
NewForTesting returns a Streamer with only the fields the non-torrent-client-touching handlers exercise (active map, downloads protection set, rate limiters). Opening a real anacrolix client requires binding UDP :42069, which collides between parallel test packages and a running dev server. Use this in handler/unit tests that don't need the torrent transport.
func (*Streamer) AcquireViewer ¶
AcquireViewer registers an open player session ("lease") on a torrent and cancels any pending drop. Called when the player opens a stream. No-op if the torrent isn't active (e.g. a local file, which lives outside the streamer).
func (*Streamer) ActiveList ¶
func (s *Streamer) ActiveList() []*TorrentInfo
ActiveList returns a snapshot of every torrent currently loaded by the streamer, formatted for the Transmission-style downloads UI. Each entry has rate samples taken under the streamer lock so the numbers are consistent across the slice.
func (*Streamer) Add ¶
Add loads a magnet OR an HTTP(S) URL to a .torrent file and waits for metadata. Returns the torrent info once available.
For .torrent URLs (common in private trackers and some Jackett providers that don't return a magnet), we fetch the file, parse the metainfo, and add via AddTorrentSpec — same downstream behavior as magnet.
func (*Streamer) AddForDownload ¶
func (s *Streamer) AddForDownload(ctx context.Context, magnetOrURL string, ds DownloadStorageSpec) (*TorrentInfo, error)
AddForDownload is Add but writes the torrent's data DIRECTLY to its final destination on bulk storage (ds.BaseDir/<sanitize(name)>/...) instead of the SSD piece cache. Used by the downloads worker so torrents larger than the cache don't overflow it and the move-on-completion becomes a no-op. The streaming path (Add/EnsureActive) is unaffected — it passes ds=nil.
func (*Streamer) CanProbeHealth ¶
CanProbeHealth reports whether a swarm probe is possible for this hash: we need either a magnet (its tr= trackers) or a cached .torrent (its announce list, which carries a private tracker's passkey). Private results from jackui ship no magnet, so the cached .torrent is the only tracker source.
func (*Streamer) ClearAll ¶
ClearAll drops every active torrent and wipes the DataDir, *except* favorites. Favorites are preserved on disk; their active torrent is dropped but files remain.
func (*Streamer) ClearEntry ¶
ClearEntry removes a specific cache entry from disk (by relative path). Refuses if the entry is favorited (use Favorites().Remove first). If the torrent is currently active, it is dropped first.
func (*Streamer) Client ¶
Client exposes the underlying anacrolix torrent client so external packages (the downloads worker, primarily) can resolve a hash → *torrent.Torrent and inspect file progress without going through the streaming-oriented helpers.
func (*Streamer) DropSeed ¶
maybePersistSeed records the torrent in the seed store when it matches a configured seed-tracker, so seeding resumes automatically on the next boot. No-op without a seed store or when the torrent isn't a keep-seeding match. DropSeed para de auto-seedar um torrent de vez: remove o registro PERSISTENTE (.seeds.db) para que ele NÃO volte no próximo boot (resumeSeeding) e o dropa da memória. Usar nas ações EXPLÍCITAS do usuário (parar de seedar / remover torrent / excluir download) — ao contrário do Drop genérico (idle/health), que preserva o auto-seed. Sem isto, um torrent auto-seedado reaparecia como "ativo" para sempre, mesmo após ser removido.
func (*Streamer) EnsureActive ¶
EnsureActive guarantees the streamer has a torrent loaded for the given magnet. Wraps the regular Add() pipeline so metadata cache + favorites stay consistent. Returns the InfoHash for callers that need to address the torrent directly.
func (*Streamer) EnsureActiveForDownload ¶
func (s *Streamer) EnsureActiveForDownload(ctx context.Context, magnet string, ds DownloadStorageSpec) (metainfo.Hash, error)
EnsureActiveForDownload is EnsureActive for the download-to-bulk path: it adds the magnet writing data straight to ds.BaseDir on bulk storage and returns the info hash. Mirrors EnsureActive otherwise (metadata cache + favorites stay consistent via the shared Add pipeline).
func (*Streamer) ExtractArtwork ¶
func (s *Streamer) ExtractArtwork(ctx context.Context, hash metainfo.Hash, fileIdx int) ([]byte, bool, error)
ExtractArtwork pulls the embedded cover-art picture stream out of an audio file (MP3 APIC frame, FLAC PICTURE block, M4A covr atom, etc.) via ffmpeg and caches the JPEG on disk. Subsequent requests hit the disk cache for free.
Returns (jpegBytes, fromCache, error). Empty bytes + nil error means the file has no embedded artwork — caller should serve a fallback placeholder.
func (*Streamer) ExtractSubtitle ¶
func (*Streamer) ExtractThumbnail ¶
func (s *Streamer) ExtractThumbnail(ctx context.Context, hash metainfo.Hash, fileIdx int, atSeconds int) ([]byte, bool, error)
ExtractSubtitle pulls one embedded text-subtitle track out of the file as WebVTT. Image-based subs (PGS, DVD) are rejected — those need burn-in via transcoding. trackIdx must be the absolute stream index from Probe()'s Subtitles[i].Index. ExtractThumbnail seeks `atSeconds` into the given file and grabs a single frame as JPEG. Cached on disk under .thumbs/{hash}/{file}/{bucket}.jpg where bucket = round(seconds / 10), so consecutive hover positions reuse the same thumb and the disk doesn't explode. Resolution capped at 240 wide to keep payloads tiny (~20 KB each).
Returns (jpeg, fromCache, error). Empty bytes + nil error means we couldn't decode at that timestamp (rare — likely seeking past the end). The handler translates that into HTTP 204.
func (*Streamer) Favorites ¶
func (s *Streamer) Favorites() *FavoritesStore
Favorites returns the attached store (may be nil). Nil-safe receiver — when the streamer itself failed to init (some tests / degraded boots), handlers can still call s.Favorites() without panicking.
func (*Streamer) FileReader ¶
func (s *Streamer) FileReader(hash metainfo.Hash, fileIdx int) (io.ReadSeekCloser, *torrent.File, error)
FileReader returns a ReadSeeker for one file, configured for streaming. The reader keeps the torrent alive (refreshes lastAccess on each read).
func (*Streamer) FileRelPath ¶
FileRelPath resolves the torrent-relative path of file fileIdx — the same shape anacrolix File.Path() returns ("<name>/<sub>/<file>" on multi-file torrents, "<name>" on single-file ones) — WITHOUT activating the torrent. Sources, in order: the persisted metadata cache (filled on every Add), then the cached .torrent on disk. Returns "" when neither knows the torrent.
Why it exists: a whole-torrent download persists ONE completed row whose file_path is a directory; mapping a *file index* into that tree needs the in-torrent path, and resolving it from the live torrent would mean re-adding it to the swarm — the exact thing serving finished downloads from disk avoids (a dead swarm would block playback despite the bytes being local).
func (*Streamer) Get ¶
func (s *Streamer) Get(hash metainfo.Hash) (*TorrentInfo, error)
Get returns the current TorrentInfo for an active torrent.
func (*Streamer) GlobalStats ¶
func (s *Streamer) GlobalStats() GlobalRate
GlobalStats returns aggregate download/upload rates across all active torrents. Snapshot taken under the streamer lock; safe to poll from a handler.
func (*Streamer) HasFilePathResolver ¶
HasFilePathResolver reports whether the file-path resolver has been wired yet. Boot-time seed resumption waits on this so relocatedStorage (which needs the resolver to locate moved files) doesn't lose a race against the resolver being set — otherwise a resumed seed would fall back to the empty cache storage and show 0%.
func (*Streamer) HealthSnapshot ¶
func (s *Streamer) HealthSnapshot(hash metainfo.Hash) (health *CachedHealth, active bool)
HealthSnapshot returns the cheapest available swarm health: live stats when the torrent is active (also refreshing the persisted copy), else the last probe (nil if never probed). Never touches the swarm.
func (*Streamer) ImportTorrentBytes ¶
ImportTorrentBytes parses a raw .torrent file, persists its metainfo to the cache (so a later play skips the DHT round-trip), and returns the info hash + torrent name. Does NOT add the torrent to the active set — the import flow only records a favorite; playback adds it on demand.
func (*Streamer) IsDownloadProtected ¶
IsDownloadProtected reports whether `name` is currently in the download protection set. Used by tests + the cache eviction code.
anacrolix grava arquivos SINGLE-FILE como "<name>.part" enquanto o download não terminou — ao mesmo tempo `t.Name()` (que o worker registra) NÃO inclui o sufixo. Sem essa tolerância o enforceCacheLimit passa "<name>.part" e consulta um set que só tem "<name>", então conclui que o arquivo NÃO está protegido e o LRU deleta o .part — anacrolix perde os pieces no disco e recomeça do zero. (Multi-file torrents não sofrem porque o entry é o diretório, cujo nome casa com t.Name() exatamente.)
func (*Streamer) ListenPort ¶
func (*Streamer) LiveStats ¶
LiveStats returns a torrent's current down/up rate + connected seeders WITHOUT building the full file list. buildInfo (used by Get) iterates t.Files() — a 778-file pack walks every file under the client lock, so enriching the downloads list via Get made GET /api/downloads take many SECONDS (worse under active-download lock contention). The list only needs the per-torrent rate/seeders, so this skips the O(files) loop → O(1) per torrent. uploaded is the cumulative bytes served THIS session (anacrolix BytesWrittenData; resets on re-add). ok=false when the torrent isn't active.
func (*Streamer) MatchesSeedTrackerCached ¶
MatchesSeedTrackerCached reports whether the torrent's CACHED metainfo announce matches a configured seed-tracker, without activating the torrent. Used at boot to decide which completed downloads to auto-reactivate for seeding. Returns false when no metainfo is cached or no seed-trackers are configured.
func (*Streamer) MetadataCache ¶
func (s *Streamer) MetadataCache() *MetadataCache
MetadataCache returns the attached cache (may be nil).
func (*Streamer) OSHash ¶
OSHash returns the OpenSubtitles hash for one file in an active torrent. Blocks until the hash is computed or ctx expires. Caches the result. Returns ("", 0, error) if the file isn't streamable as hash (too small, not active, timeout).
func (*Streamer) ParseMagnet ¶
ParseMagnet validates a magnet URI and extracts its info hash + display name without touching the network. Used by the import flow to preview what a pasted magnet resolves to before committing it to favorites.
func (*Streamer) Pause ¶
Pause soft-pauses a torrent by zeroing its max established connections. anacrolix lacks a native Pause; this is the closest equivalent — existing peers drop off as TCP keepalives expire, and no new peers are accepted. On-disk pieces stay, so Resume picks up where we left off.
func (*Streamer) PauseAll ¶
PauseAll soft-pauses every active torrent. Returns the count of newly paused torrents (already-paused ones are not double-counted).
func (*Streamer) Peers ¶
Peers returns a snapshot of the currently-connected peers of an active torrent for the downloads inspector. Errors when the torrent isn't active (dropped or never opened). The peer set is read live from anacrolix.
func (*Streamer) Prefetch ¶
Prefetch hints the anacrolix piece scheduler to start downloading the head of `fileIdx` on the already-active torrent, *without* serving any bytes back.
Use case: while the user watches episode N of a series, we kick off pieces of N+1 in the background so the cut between episodes is near-instantaneous. Same idea for the next item in a playlist when it's the same torrent.
Implementation: opens a Reader, seeks to 0, sets a generous readahead, reads a small head chunk, then closes after a short delay so the priority hint outlives the request lifecycle. The bytes already on disk stay there — only the in-memory priority hint goes away when the reader closes.
Returns immediately; the actual download is asynchronous in anacrolix.
func (*Streamer) Probe ¶
Probe runs ffprobe on the torrent file and lists audio + subtitle tracks. Reads at most 16 MB from the start of the file (enough for MKV/MP4 headers). Results are cached per (torrent, file).
func (*Streamer) ProbeHealthAsync ¶
ProbeHealthAsync runs a background swarm probe for an INACTIVE torrent: scrape the trackers (magnet tr= + cached .torrent announce list) for the real swarm size, persist it, falling back to a brief swarm-connect count. Throttled + deduped. No-op when there's no tracker source or a probe is already running.
func (*Streamer) RateLimits ¶
RateLimits exposes the configured global bandwidth caps in bytes/sec. A value of 0 means unlimited.
func (*Streamer) ReadArtBytes ¶
ReadArtBytes returns the cached art file for a DataDir-relative path produced by SaveArtBytes. The path is validated to stay within the .art dir so a crafted CachedArt.Path can't read arbitrary files.
func (*Streamer) ReadSidecar ¶
func (s *Streamer) ReadSidecar(ctx context.Context, hash metainfo.Hash, fileIdx int) ([]byte, string, error)
ReadSidecar fetches the full subtitle file by index. Returns the raw bytes — caller is responsible for format conversion (e.g., SRT → VTT).
func (*Streamer) RecheckAllFiles ¶
RecheckAllFiles força o "Force Recheck" em TODOS os arquivos de um torrent (download de torrent inteiro). Mesmo contrato do RecheckFile; os arquivos são re-hashados sequencialmente numa única goroutine — um torrent de milhares de arquivos não pode disparar milhares de hash loops concorrentes.
func (*Streamer) RecheckFile ¶
RecheckFile força uma re-verificação completa dos pieces de um arquivo, IGNORANDO o dedup do verifiedFiles e re-hashando até pieces marcados como "complete" no momento. Caso de uso: ação manual do user via UI ("recheck") quando ele suspeita que os bytes no disco estão corrompidos (BitErrors) ou quando o tamanho/contagem do downloads.db não bate com o real. Diferente do VerifyFile, que pula pieces já completos e dedupa por processo, aqui valida tudo de novo — semantics equivalente ao "Force Recheck" do qBittorrent. Roda em goroutine porque um filme grande leva minutos.
func (*Streamer) RegisterDownload ¶
RegisterDownload marks a torrent (by directory name == torrent.Name()) as part of an in-progress background download. While registered, its on-disk pieces are protected from enforceCacheLimit eviction even if no one is streaming. Idempotent.
func (*Streamer) ReleaseViewer ¶
ReleaseViewer drops a player session's lease. When the LAST viewer leaves a stream-only (non-download) torrent, it schedules a drop after viewerGrace instead of dropping eagerly — so other viewers keep streaming and a quick reopen cancels the teardown.
Returns (scheduled, lastViewer). scheduled is true when a drop was scheduled. lastViewer is true whenever THIS call removed the final viewer — even when the torrent is kept alive (background download or seed-tracker): the HLS transcode exists only to feed the player, so the caller must stop it once nobody is watching, while the torrent keeps seeding/downloading on its own.
func (*Streamer) Resume ¶
Resume re-enables peer connections previously zeroed by Pause. Idempotent.
func (*Streamer) SaveArtBytes ¶
SaveArtBytes writes resolved art (torrent image or captured frame) under the .art cache dir and returns the DataDir-relative path to persist in CachedArt.
func (*Streamer) SetFavorites ¶
func (s *Streamer) SetFavorites(f *FavoritesStore)
SetFavorites attaches the favorites store. Must be called before any GC tick.
func (*Streamer) SetFilePathResolver ¶
func (s *Streamer) SetFilePathResolver(r FilePathResolver)
SetFilePathResolver registers the custom file path resolver function (typically querying the downloads DB).
func (*Streamer) SetFilePriority ¶
SetFilePriority changes the priority of a single file in the active torrent.
func (*Streamer) SetMetadataCache ¶
func (s *Streamer) SetMetadataCache(c *MetadataCache)
SetMetadataCache attaches the metadata snapshot cache. Optional — when set, every successful Add() persists the file list so the UI can render it instantly next time the same info_hash is opened.
func (*Streamer) SetPriority ¶
SetPriority changes the requested piece priority for every file in the torrent. anacrolix uses this to bias the request scheduler — "high" pieces will be fetched before "normal", which precede "low". Accepted labels: "low" | "normal" | "high".
func (*Streamer) SetRateLimits ¶
SetRateLimits updates the global download/upload bandwidth caps in bytes/sec. 0 = unlimited. Takes effect immediately — anacrolix re-reads the limiter on every chunk transfer.
func (*Streamer) SetSeedTrackers ¶
SetSeedTrackers replaces the live list of tracker substrings whose torrents must keep seeding. Applied immediately; matched case-insensitively against announce URLs. Safe to call at runtime (e.g. from the settings endpoint).
func (*Streamer) SetSeeds ¶
func (s *Streamer) SetSeeds(st *SeedsStore)
SetSeeds attaches the persistent seed store (info_hash → magnet) so torrents kept alive for seeding are re-added on boot. Optional — nil disables persistence (seeding still works in-memory until the process exits).
func (*Streamer) SetStreamReadahead ¶
SetStreamReadahead atualiza ao vivo o readahead de streaming (em MB). Vale a partir do próximo Reader aberto. mb<=0 volta ao default. Não exige reinício.
func (*Streamer) Sidecars ¶
Sidecars returns subtitle files inside the torrent. If videoFileIdx >= 0, results are ranked by path proximity to that video file (same directory first, then sibling 'subs' dirs, then anywhere).
func (*Streamer) Stats ¶
func (s *Streamer) Stats() (*CacheStats, error)
Stats walks the DataDir and returns disk usage stats. "Active" entries are torrents currently loaded in memory (likely being read).
func (*Streamer) StreamReadaheadForTesting ¶
StreamReadaheadForTesting expõe o readahead efetivo (em bytes) para testes de outros pacotes verificarem que um setter foi aplicado.
func (*Streamer) TorrentImage ¶
func (*Streamer) TrackerStats ¶
func (s *Streamer) TrackerStats(ctx context.Context, hash metainfo.Hash, magnet string) []TrackerScrape
TrackerStats scrapes every known tracker (magnet tr= + the cached .torrent's announce list, which carries a private tracker's passkey) and returns the per-tracker swarm sizes for the info panel. Tracker hosts are masked — the passkey is used to scrape but never returned.
func (*Streamer) UnregisterDownload ¶
UnregisterDownload removes the eviction protection. Call after the download completes or is cancelled. Idempotent.
func (*Streamer) UpdateJackettHost ¶
UpdateJackettHost refreshes the trusted Jackett hostname used by the SSRF guard. Called after the user changes the Jackett URL via the API.
func (*Streamer) VerifyFile ¶
VerifyFile is the exported entrypoint para o worker de downloads disparar a reconciliação de pieces no disco antes de pedir mais dados ao swarm. Reusa o mesmo dedupe set (`verifiedFiles`) que o caminho de streaming, então a verificação acontece NO MÁXIMO uma vez por (hash, file) por processo — não importa se foi streaming ou download que disparou primeiro.
Background: anacrolix tradicionalmente não re-verifica em startup; confia no bolt DB. Se o shutdown anterior foi ungraceful (SIGKILL, container OOM), o bolt fica desatualizado e anacrolix "esquece" pieces que estão no disco. Sem essa chamada, o worker pede ao swarm bytes que já temos.
func (*Streamer) VerifyTorrent ¶
VerifyTorrent reconciles on-disk pieces for EVERY file of a torrent — the whole-torrent download path. Same rationale and per-(hash,file) dedupe as VerifyFile, applied file by file (sequencial: custo proporcional ao que está no disco; pieces ausentes falham o hash rápido via sparse reads).
type TorrentInfo ¶
type TorrentInfo struct {
InfoHash string `json:"infoHash"`
Name string `json:"name"`
TotalSize int64 `json:"totalSize"`
Files []FileInfo `json:"files"`
Peers int `json:"peers"`
Seeders int `json:"seeders"`
DownRate int64 `json:"downRate"` // bytes/sec, sampled between polls
UpRate int64 `json:"upRate"` // bytes/sec, sampled between polls
// Cumulative payload byte counters. BytesDownloaded is the completed bytes of
// the selected pieces; BytesUploaded is what we've served this SESSION (the
// anacrolix counter resets when the torrent is re-added — e.g. after a restart).
BytesDownloaded int64 `json:"bytesDownloaded"`
BytesUploaded int64 `json:"bytesUploaded"`
Progress float64 `json:"progress"`
PrimaryFile int `json:"primaryFile"` // suggested video file index
// Status is one of "downloading", "paused", "seeding", "complete".
// Surfaced for the Transmission-style downloads UI.
Status string `json:"status,omitempty"`
// Priority is the user-set piece priority ("low" | "normal" | "high"); empty
// when the user has not changed it from the anacrolix default.
Priority string `json:"priority,omitempty"`
Trackers []string `json:"trackers,omitempty"`
}
TorrentInfo is the JSON-friendly view returned to the frontend.
type Track ¶
type Track struct {
Index int `json:"index"` // absolute stream index in the container (use with `-map 0:N`)
Type string `json:"type"` // "audio" | "subtitle"
Codec string `json:"codec"` // e.g. "aac", "ac3", "subrip", "ass", "hdmv_pgs_subtitle"
Language string `json:"language,omitempty"` // ISO 639-2 (e.g. "por", "eng") from container tags
Title string `json:"title,omitempty"` // human-friendly title set by uploader, if any
Default bool `json:"default"`
Forced bool `json:"forced,omitempty"`
Channels int `json:"channels,omitempty"`
Image bool `json:"image,omitempty"` // true if subtitle is image-based (PGS, DVD) — needs burn-in
}
Track describes one audio or subtitle stream inside a container.
type TrackerScrape ¶
type TrackerScrape struct {
Tracker string `json:"tracker"`
Seeders int `json:"seeders"`
Leechers int `json:"leechers"`
OK bool `json:"ok"`
}
TrackerScrape is one tracker's reported swarm size. Tracker is the masked display name (host only — never the passkey-bearing URL). OK is false when the tracker didn't answer or doesn't know the torrent.