dirscan

package
v1.13.0 Latest Latest
Warning

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

Go to latest
Published: Jan 27, 2026 License: GPL-2.0 Imports: 34 Imported by: 0

Documentation

Overview

Package dirscan provides directory scanning functionality to find media files and match them against Torznab indexers for cross-seeding.

Index

Constants

View Source
const SearchScope = "dir-scan"

SearchScope is the cache scope used for dir-scan searches.

Variables

This section is empty.

Functions

func CalculateSizeRange

func CalculateSizeRange(searcheeSize int64, tolerancePercent float64) (minSize, maxSize int64)

CalculateSizeRange calculates min/max size based on searchee size and tolerance.

func CalculateTotalSize

func CalculateTotalSize(searchee *Searchee) int64

CalculateTotalSize calculates the total size of a searchee.

func ParseSeasonFolder

func ParseSeasonFolder(name string) (season int, ok bool)

ParseSeasonFolder attempts to parse a folder name as a season folder. Returns the season number (0 for Specials) and true if it matches, or 0 and false if not.

Types

type Config

type Config struct {
	// SchedulerInterval is how often to check for scheduled scans.
	SchedulerInterval time.Duration

	// MaxJitter is the maximum random delay before starting a scheduled scan.
	MaxJitter time.Duration

	// MaxConcurrentRuns limits how many scans can run across all directories.
	// This helps avoid stampeding indexers if multiple directories are due at once.
	MaxConcurrentRuns int
}

Config holds configuration for the directory scanner service.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the default configuration.

type InjectRequest

type InjectRequest struct {
	// InstanceID is the target qBittorrent instance.
	InstanceID int

	// TorrentBytes contains the .torrent file contents.
	TorrentBytes []byte

	// ParsedTorrent is the parsed torrent metadata for TorrentBytes.
	ParsedTorrent *ParsedTorrent

	// Searchee that was matched.
	Searchee *Searchee

	// MatchResult is the file-level match for this searchee and ParsedTorrent.
	MatchResult *MatchResult

	// SearchResult that matched the searchee.
	SearchResult *jackett.SearchResult

	// SavePath is the path where qBittorrent should save the torrent.
	// This should point to the parent directory of the searchee.
	SavePath string

	// QbitPathPrefix is an optional path prefix to apply for container path mapping.
	// If set, replaces the searchee's path prefix for qBittorrent injection.
	QbitPathPrefix string

	// Category to assign to the torrent.
	Category string

	// Tags to assign to the torrent.
	Tags []string

	// StartPaused adds the torrent in paused state.
	StartPaused bool
}

InjectRequest contains parameters for injecting a torrent.

type InjectResult

type InjectResult struct {
	// Success is true if the torrent was added successfully.
	Success bool

	// TorrentHash is the hash of the added torrent.
	TorrentHash string

	// Mode describes how the torrent was added: hardlink, reflink, or regular.
	Mode string

	// SavePath is the save path used when adding the torrent.
	SavePath string

	// Error message if injection failed.
	ErrorMessage string
}

InjectResult contains the result of an injection attempt.

type Injector

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

Injector handles downloading and injecting torrents into qBittorrent.

func NewInjector

func NewInjector(
	jackettService JackettDownloader,
	syncManager TorrentAdder,
	torrentChecker TorrentChecker,
	instanceStore InstanceProvider,
	trackerCustomizationStore trackerCustomizationProvider,
) *Injector

NewInjector creates a new injector.

func (*Injector) Inject

func (i *Injector) Inject(ctx context.Context, req *InjectRequest) (*InjectResult, error)

Inject downloads a torrent and injects it into qBittorrent.

func (*Injector) InjectBatch

func (i *Injector) InjectBatch(ctx context.Context, requests []*InjectRequest) []*InjectResult

InjectBatch injects multiple torrents. Returns results for each injection attempt.

func (*Injector) TorrentExists

func (i *Injector) TorrentExists(ctx context.Context, instanceID int, hash string) (bool, error)

TorrentExists checks if a torrent with the given hash already exists in qBittorrent.

func (*Injector) TorrentExistsAny

func (i *Injector) TorrentExistsAny(ctx context.Context, instanceID int, hashes []string) (bool, error)

type InstanceProvider

type InstanceProvider interface {
	Get(ctx context.Context, id int) (*models.Instance, error)
}

type JackettDownloader

type JackettDownloader interface {
	DownloadTorrent(ctx context.Context, req jackett.TorrentDownloadRequest) ([]byte, error)
}

JackettDownloader is the interface for downloading torrent files.

type JackettSearcher

type JackettSearcher interface {
	SearchWithScope(ctx context.Context, req *jackett.TorznabSearchRequest, scope string) error
}

JackettSearcher is the interface for the jackett service search functionality.

type MatchMode

type MatchMode string

MatchMode defines how strictly files are compared.

const (
	// MatchModeStrict requires files to match by name AND size.
	MatchModeStrict MatchMode = "strict"
	// MatchModeFlexible allows files to match by size only.
	MatchModeFlexible MatchMode = "flexible"
)

type MatchResult

type MatchResult struct {
	// IsMatch is true if the match criteria were met
	IsMatch bool

	// MatchedFiles contains the matched file pairs
	MatchedFiles []MatchedFilePair

	// UnmatchedSearcheeFiles contains searchee files that weren't matched
	UnmatchedSearcheeFiles []*ScannedFile

	// UnmatchedTorrentFiles contains torrent files that weren't matched
	UnmatchedTorrentFiles []TorrentFile

	// MatchRatio is the ratio of matched files (0.0-1.0)
	MatchRatio float64

	// SizeMatchRatio is the ratio of matched size (0.0-1.0)
	SizeMatchRatio float64

	// IsPerfectMatch is true if all files matched
	IsPerfectMatch bool

	// IsPartialMatch is true if some but not all files matched
	IsPartialMatch bool
}

MatchResult represents the result of matching a searchee against a torrent.

type MatchedFilePair

type MatchedFilePair struct {
	SearcheeFile *ScannedFile
	TorrentFile  TorrentFile
}

MatchedFilePair represents a matched pair of searchee file and torrent file.

type Matcher

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

Matcher handles matching searchees against torrent file lists.

func NewMatcher

func NewMatcher(mode MatchMode, sizeTolerancePct float64) *Matcher

NewMatcher creates a new matcher.

func (*Matcher) Match

func (m *Matcher) Match(searchee *Searchee, torrentFiles []TorrentFile) *MatchResult

Match attempts to match a searchee's files against a torrent's files.

type ParsedTorrent

type ParsedTorrent struct {
	Name        string
	InfoHash    string
	Files       []TorrentFile
	TotalSize   int64
	PieceLength int64
	PieceCount  int
}

ParsedTorrent holds parsed torrent metadata.

func ParseTorrentBytes

func ParseTorrentBytes(data []byte) (*ParsedTorrent, error)

ParseTorrentBytes parses a .torrent file and extracts file information.

type Parser

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

Parser handles name parsing with TRaSH ID extraction and rls integration.

func NewParser

func NewParser(rlsParser *releases.Parser) *Parser

NewParser creates a new name parser.

func (*Parser) Parse

func (p *Parser) Parse(name string) *SearcheeMetadata

Parse extracts TRaSH IDs and parses release metadata from a searchee name.

type ScanResult

type ScanResult struct {
	Searchees    []*Searchee // Searchees found
	TotalFiles   int         // Total media files found
	TotalSize    int64       // Total size in bytes
	SkippedFiles int         // Files skipped (already seeding, etc.)
}

ScanResult holds the results of a directory scan.

type ScannedFile

type ScannedFile struct {
	Path      string          // Absolute path to the file
	RelPath   string          // Relative path from searchee root
	Size      int64           // File size in bytes
	ModTime   time.Time       // Modification time
	FileID    hardlink.FileID // Platform-specific file identifier
	LinkCount uint64          // Hardlink count
	HasLinks  bool            // True if file has multiple hardlinks (count > 1)
}

ScannedFile represents a file found during directory scanning.

type Scanner

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

Scanner walks directories and collects media files into searchees.

func NewScanner

func NewScanner() *Scanner

NewScanner creates a new directory scanner.

func (*Scanner) CheckAlreadySeeding

func (s *Scanner) CheckAlreadySeeding(searchee *Searchee) (allSeeding bool, torrentHash string)

CheckAlreadySeeding checks if a searchee's files are already being seeded.

func (*Scanner) ScanDirectory

func (s *Scanner) ScanDirectory(ctx context.Context, rootPath string) (*ScanResult, error)

ScanDirectory walks a directory and returns searchees.

func (*Scanner) SetFileIDIndex

func (s *Scanner) SetFileIDIndex(index map[string]string)

SetFileIDIndex sets the FileID index for detecting already-seeding files.

type SearchRequest

type SearchRequest struct {
	// Searchee to search for
	Searchee *Searchee

	// Metadata parsed from the searchee name (optional, will be parsed if nil)
	Metadata *SearcheeMetadata

	// IndexerIDs to search (empty = all enabled indexers)
	IndexerIDs []int

	// Categories to search (optional, but recommended for better results).
	Categories []int

	// Limit results per indexer
	Limit int

	// OnAllComplete is called when all search jobs complete with the final results
	OnAllComplete func(response *jackett.SearchResponse, err error)
}

SearchRequest contains parameters for searching a searchee.

type SearchResult

type SearchResult struct {
	*jackett.SearchResult

	// IndexerID that returned this result
	IndexerID int

	// Searchee this result is for
	Searchee *Searchee

	// MatchScore indicates match quality (higher is better)
	MatchScore float64
}

SearchResult wraps a jackett search result with additional matching context.

func FilterResults

func FilterResults(results []*SearchResult, minSize, maxSize int64) []*SearchResult

FilterResults filters search results based on basic criteria.

type Searchee

type Searchee struct {
	Name   string         // Release name (folder or file base name)
	Path   string         // Absolute path to the searchee root
	Files  []*ScannedFile // Files in this searchee
	IsDisc bool           // True if this is a disc-layout folder
}

Searchee represents a unit to search for on indexers (folder or single file).

type SearcheeMetadata

type SearcheeMetadata struct {
	// Original name before any processing
	OriginalName string

	// CleanedName has TRaSH IDs removed (suitable for rls parsing)
	CleanedName string

	// TRaSH IDs extracted from the name
	TRaSH TRaSHMetadata

	// Release metadata from rls parsing
	Release *rls.Release

	// Derived search fields
	Title   string // Cleaned title for search queries
	Year    int    // Year if detected
	Season  *int   // Season number for TV
	Episode *int   // Episode number for TV

	// Content type hints
	IsTV    bool
	IsMovie bool
	IsMusic bool
}

SearcheeMetadata combines TRaSH IDs with rls parsed release metadata.

func (*SearcheeMetadata) GetIMDbID

func (m *SearcheeMetadata) GetIMDbID() string

GetIMDbID returns the IMDb ID in a format suitable for Torznab search. Returns empty string if not available.

func (*SearcheeMetadata) GetTMDbID

func (m *SearcheeMetadata) GetTMDbID() int

GetTMDbID returns the TMDb ID if available, 0 otherwise.

func (*SearcheeMetadata) GetTVDbID

func (m *SearcheeMetadata) GetTVDbID() int

GetTVDbID returns the TVDb ID if available, 0 otherwise.

func (*SearcheeMetadata) HasExternalIDs

func (m *SearcheeMetadata) HasExternalIDs() bool

HasExternalIDs returns true if any external database IDs are available.

func (*SearcheeMetadata) SetExternalIDs

func (m *SearcheeMetadata) SetExternalIDs(imdbID string, tmdbID, tvdbID int)

SetExternalIDs updates the metadata with external IDs from arr lookup. Only sets IDs that are not already present from TRaSH naming.

type Searcher

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

Searcher handles searching Torznab indexers for matching torrents.

func NewSearcher

func NewSearcher(jackettService JackettSearcher, parser *Parser) *Searcher

NewSearcher creates a new searcher.

func (*Searcher) Search

func (s *Searcher) Search(ctx context.Context, req *SearchRequest) error

Search searches Torznab indexers for torrents matching a searchee. Uses embedded IDs first (most accurate), then falls back to title + year.

type Service

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

Service handles directory scanning and torrent matching.

func NewService

func NewService(
	cfg Config,
	store *models.DirScanStore,
	instanceStore *models.InstanceStore,
	syncManager *qbittorrent.SyncManager,
	jackettService *jackett.Service,
	arrService *arr.Service,
	trackerCustomizationStore *models.TrackerCustomizationStore,
) *Service

NewService creates a new directory scanner service.

func (*Service) CancelScan

func (s *Service) CancelScan(ctx context.Context, directoryID int) error

CancelScan cancels an active scan.

func (*Service) CreateDirectory

func (s *Service) CreateDirectory(ctx context.Context, dir *models.DirScanDirectory) (*models.DirScanDirectory, error)

CreateDirectory creates a new scan directory.

func (*Service) DeleteDirectory

func (s *Service) DeleteDirectory(ctx context.Context, id int) error

DeleteDirectory deletes a scan directory.

func (*Service) GetActiveRun

func (s *Service) GetActiveRun(ctx context.Context, directoryID int) (*models.DirScanRun, error)

GetActiveRun returns the currently active run for a directory, if any.

func (*Service) GetDirectory

func (s *Service) GetDirectory(ctx context.Context, id int) (*models.DirScanDirectory, error)

GetDirectory returns a scan directory by ID.

func (*Service) GetSettings

func (s *Service) GetSettings(ctx context.Context) (*models.DirScanSettings, error)

GetSettings returns the global directory scanner settings.

func (*Service) GetStatus

func (s *Service) GetStatus(ctx context.Context, directoryID int) (*models.DirScanRun, error)

GetStatus returns the status of a directory's current or most recent scan.

func (*Service) ListDirectories

func (s *Service) ListDirectories(ctx context.Context) ([]*models.DirScanDirectory, error)

ListDirectories returns all configured scan directories.

func (*Service) ListFiles

func (s *Service) ListFiles(ctx context.Context, directoryID int, status *models.DirScanFileStatus, limit, offset int) ([]*models.DirScanFile, error)

ListFiles returns scanned files for a directory.

func (*Service) ListRunInjections

func (s *Service) ListRunInjections(ctx context.Context, directoryID int, runID int64, limit, offset int) ([]*models.DirScanRunInjection, error)

ListRunInjections returns injection attempts (added/failed) for a run.

func (*Service) ListRuns

func (s *Service) ListRuns(ctx context.Context, directoryID, limit int) ([]*models.DirScanRun, error)

ListRuns returns recent scan runs for a directory.

func (*Service) ResetFilesForDirectory

func (s *Service) ResetFilesForDirectory(ctx context.Context, directoryID int) error

ResetFilesForDirectory deletes all tracked scan progress files for a directory.

func (*Service) Start

func (s *Service) Start(ctx context.Context) error

Start starts the scheduler loop.

func (*Service) StartManualScan

func (s *Service) StartManualScan(ctx context.Context, directoryID int) (int64, error)

StartManualScan starts a manual scan for a directory.

func (*Service) Stop

func (s *Service) Stop()

Stop stops the scheduler and waits for completion.

func (*Service) UpdateDirectory

func (s *Service) UpdateDirectory(ctx context.Context, id int, params *models.DirScanDirectoryUpdateParams) (*models.DirScanDirectory, error)

UpdateDirectory updates a scan directory.

func (*Service) UpdateSettings

func (s *Service) UpdateSettings(ctx context.Context, settings *models.DirScanSettings) (*models.DirScanSettings, error)

UpdateSettings updates the global directory scanner settings.

type TRaSHMetadata

type TRaSHMetadata struct {
	TMDbID  int    // From {tmdb-345691}
	IMDbID  string // From {imdb-tt1234567}
	TVDbID  int    // From [tvdb-123] or [tvdbid-123]
	Edition string // From {edition-Extended}
}

TRaSHMetadata holds database IDs extracted from TRaSH Guides naming conventions. See: https://trash-guides.info/

type TorrentAdder

type TorrentAdder interface {
	AddTorrent(ctx context.Context, instanceID int, fileContent []byte, options map[string]string) error
	BulkAction(ctx context.Context, instanceID int, hashes []string, action string) error
	ResumeWhenComplete(instanceID int, hashes []string, opts qbsync.ResumeWhenCompleteOptions)
}

TorrentAdder is the interface for adding torrents to qBittorrent.

type TorrentChecker

type TorrentChecker interface {
	HasTorrentByAnyHash(ctx context.Context, instanceID int, hashes []string) (*qbt.Torrent, bool, error)
}

TorrentChecker is the interface for checking if torrents exist in qBittorrent.

type TorrentFile

type TorrentFile struct {
	Path   string // Relative path within the torrent
	Size   int64  // Size in bytes
	Offset int64  // Byte offset in the torrent's byte stream (for piece calculation)
}

TorrentFile represents a file within a torrent.

Jump to

Keyboard shortcuts

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