diff --git a/config.yml.example b/config.yml.example index 1648b81..43f8e4b 100644 --- a/config.yml.example +++ b/config.yml.example @@ -13,6 +13,7 @@ directories: # Configuration for TV shows shows: group: media # directories on different groups have duplicates of the same torrent + group_order: 1 filters: - regex: /season[\s\.]?\d/i # Capture torrent names with the term 'season' in any case - regex: /saison[\s\.]?\d/i # For non-English namings @@ -24,7 +25,8 @@ directories: # Configuration for movies movies: - group: media # because movies and shows are in the same group, and shows come first before movies, all torrents that doesn't fall into shows will fall into movies + group: media # because movies and shows are in the same group, + group_order: 2 # and shows has a lower group_order number than movies, all torrents that doesn't fall into shows will fall into movies filters: - regex: /.*/ # you cannot leave a directory without filters because it will not have any torrents in it diff --git a/internal/config/load.go b/internal/config/load.go index 3813711..4616c0d 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -16,7 +16,7 @@ type ConfigInterface interface { EnableRepair() bool GetPort() string GetDirectories() []string - MeetsConditions(directory, fileID, fileName string) bool + MeetsConditions(directory, torrentID, torrentName string, fileNames []string) bool } func LoadZurgConfig(filename string) (ConfigInterface, error) { diff --git a/internal/config/v1.go b/internal/config/v1.go index a673fa8..7a255ff 100644 --- a/internal/config/v1.go +++ b/internal/config/v1.go @@ -2,6 +2,7 @@ package config import ( "regexp" + "sort" "strings" "gopkg.in/yaml.v3" @@ -56,25 +57,38 @@ func (z *ZurgConfigV1) GetDirectories() []string { func (z *ZurgConfigV1) GetGroupMap() map[string][]string { var groupMap = make(map[string][]string) + var groupOrderMap = make(map[string]int) // To store GroupOrder for each group + + // Populate the groupMap and groupOrderMap for directory, val := range z.Directories { groupMap[val.Group] = append(groupMap[val.Group], directory) + groupOrderMap[val.Group] = val.GroupOrder } + + // Sort the slice based on GroupOrder + for group, dirs := range groupMap { + sort.Slice(dirs, func(i, j int) bool { + return groupOrderMap[dirs[i]] < groupOrderMap[dirs[j]] + }) + groupMap[group] = dirs + } + return groupMap } -func (z *ZurgConfigV1) MeetsConditions(directory, fileID, torrentName string) bool { +func (z *ZurgConfigV1) MeetsConditions(directory, torrentID, torrentName string, fileNames []string) bool { if _, ok := z.Directories[directory]; !ok { return false } for _, filter := range z.Directories[directory].Filters { - if z.matchFilter(fileID, torrentName, filter) { + if z.matchFilter(torrentID, torrentName, fileNames, filter) { return true } } return false } -func (z *ZurgConfigV1) matchFilter(fileID, torrentName string, filter *FilterConditionsV1) bool { +func (z *ZurgConfigV1) matchFilter(fileID, torrentName string, fileNames []string, filter *FilterConditionsV1) bool { if filter.ID != "" && fileID == filter.ID { return true } @@ -99,7 +113,7 @@ func (z *ZurgConfigV1) matchFilter(fileID, torrentName string, filter *FilterCon if len(filter.And) > 0 { andResult := true for _, andFilter := range filter.And { - andResult = andResult && z.matchFilter(fileID, torrentName, andFilter) + andResult = andResult && z.matchFilter(fileID, torrentName, fileNames, andFilter) if !andResult { return false } @@ -108,7 +122,29 @@ func (z *ZurgConfigV1) matchFilter(fileID, torrentName string, filter *FilterCon } if len(filter.Or) > 0 { for _, orFilter := range filter.Or { - if z.matchFilter(fileID, torrentName, orFilter) { + if z.matchFilter(fileID, torrentName, fileNames, orFilter) { + return true + } + } + } + if filter.FileInsideRegexStr != "" { + for _, filename := range fileNames { + regex := compilePattern(filter.FileInsideRegexStr) + if regex.MatchString(filename) { + return true + } + } + } + if filter.FileInsideContains != "" { + for _, filename := range fileNames { + if strings.Contains(strings.ToLower(filename), strings.ToLower(filter.FileInsideContains)) { + return true + } + } + } + if filter.FileInsideContainsStrict != "" { + for _, filename := range fileNames { + if strings.Contains(filename, filter.FileInsideContainsStrict) { return true } } diff --git a/internal/config/v1types.go b/internal/config/v1types.go index cfb2f94..67ca0dc 100644 --- a/internal/config/v1types.go +++ b/internal/config/v1types.go @@ -5,17 +5,23 @@ type ZurgConfigV1 struct { Directories map[string]*DirectoryFilterConditionsV1 `yaml:"directories"` } type DirectoryFilterConditionsV1 struct { - Group string `yaml:"group"` - Filters []*FilterConditionsV1 `yaml:"filters"` + GroupOrder int `yaml:"group_order"` + Group string `yaml:"group"` + Filters []*FilterConditionsV1 `yaml:"filters"` } type FilterConditionsV1 struct { - RegexStr string `yaml:"regex"` - Contains string `yaml:"contains"` - ContainsStrict string `yaml:"contains_strict"` - NotContains string `yaml:"not_contains"` - NotContainsStrict string `yaml:"not_contains_strict"` - ID string `yaml:"id"` - And []*FilterConditionsV1 `yaml:"and"` - Or []*FilterConditionsV1 `yaml:"or"` + ID string `yaml:"id"` + RegexStr string `yaml:"regex"` + Contains string `yaml:"contains"` + ContainsStrict string `yaml:"contains_strict"` + NotContains string `yaml:"not_contains"` + NotContainsStrict string `yaml:"not_contains_strict"` + + And []*FilterConditionsV1 `yaml:"and"` + Or []*FilterConditionsV1 `yaml:"or"` + + FileInsideRegexStr string `yaml:"any_file_inside_regex"` + FileInsideContains string `yaml:"any_file_inside_contains"` + FileInsideContainsStrict string `yaml:"any_file_inside_contains_strict"` } diff --git a/internal/torrent/manager.go b/internal/torrent/manager.go index 0c85f65..673ae59 100644 --- a/internal/torrent/manager.go +++ b/internal/torrent/manager.go @@ -16,13 +16,15 @@ import ( ) type TorrentManager struct { - requiredVersion string - torrents []Torrent - inProgress []string - checksum string - config config.ConfigInterface - cache *expirable.LRU[string, string] - workerPool chan bool + requiredVersion string + torrents []Torrent + inProgress []string + checksum string + config config.ConfigInterface + cache *expirable.LRU[string, string] + workerPool chan bool + TorrentDirectoriesMap map[string][]string + processedTorrents map[string]bool } // NewTorrentManager creates a new torrent manager @@ -30,10 +32,12 @@ type TorrentManager struct { // and store them in-memory func NewTorrentManager(config config.ConfigInterface, cache *expirable.LRU[string, string]) *TorrentManager { t := &TorrentManager{ - requiredVersion: "24.10.2023", - config: config, - cache: cache, - workerPool: make(chan bool, config.GetNumOfWorkers()), + requiredVersion: "24.10.2023", + config: config, + cache: cache, + workerPool: make(chan bool, config.GetNumOfWorkers()), + TorrentDirectoriesMap: make(map[string][]string), + processedTorrents: make(map[string]bool), } // Initialize torrents for the first time @@ -85,7 +89,7 @@ func (t *TorrentManager) repairAll(wg *sync.WaitGroup) { func (t *TorrentManager) GetByDirectory(directory string) []Torrent { var torrents []Torrent for i := range t.torrents { - for _, dir := range t.torrents[i].Directories { + for _, dir := range t.TorrentDirectoriesMap[t.torrents[i].Name] { if dir == directory { torrents = append(torrents, t.torrents[i]) } @@ -567,35 +571,58 @@ func (t *TorrentManager) repair(torrentID string, selectedFiles []File, tryReins } func (t *TorrentManager) mapToDirectories() { - // Map to directories - version := t.config.GetVersion() - if version == "v1" { + // Map torrents to directories + switch t.config.GetVersion() { + case "v1": configV1 := t.config.(*config.ZurgConfigV1) groupMap := configV1.GetGroupMap() + // for every group, iterate over every torrent + // and then sprinkle/distribute the torrents to the directories of the group for group, directories := range groupMap { - log.Printf("Processing directory group: %s\n", group) - var directoryMap = make(map[string]int) + log.Printf("Processing directory group '%s', sequence: %s\n", group, strings.Join(directories, " > ")) + counter := make(map[string]int) for i := range t.torrents { + // don't process torrents that are already mapped if it is not the first run + if _, exists := t.processedTorrents[t.torrents[i].ID]; exists { + continue + } + for _, directory := range directories { - if configV1.MeetsConditions(directory, t.torrents[i].ID, t.torrents[i].Name) { - // append to t.torrents[i].Directories if not yet there + var filenames []string + for _, file := range t.torrents[i].Files { + filenames = append(filenames, file.Path) + } + if configV1.MeetsConditions(directory, t.torrents[i].ID, t.torrents[i].Name, filenames) { found := false - for _, dir := range t.torrents[i].Directories { + for _, dir := range t.TorrentDirectoriesMap[t.torrents[i].Name] { if dir == directory { found = true break } } if !found { - t.torrents[i].Directories = append(t.torrents[i].Directories, directory) + counter[directory]++ + t.TorrentDirectoriesMap[t.torrents[i].Name] = append(t.TorrentDirectoriesMap[t.torrents[i].Name], directory) + break } - directoryMap[directory]++ - break } } } - log.Printf("Directory group: %v\n", directoryMap) + sum := 0 + for _, count := range counter { + sum += count + } + if sum > 0 { + log.Printf("Directory group processed: %s %v %d\n", group, counter, sum) + } else { + log.Println("No new additions to directory group", group) + } } + default: + log.Println("Unknown config version") + } + for _, torrent := range t.torrents { + t.processedTorrents[torrent.ID] = true } log.Println("Finished mapping to directories") } diff --git a/internal/torrent/types.go b/internal/torrent/types.go index 5ad4a1a..d98c422 100644 --- a/internal/torrent/types.go +++ b/internal/torrent/types.go @@ -5,7 +5,6 @@ import "github.com/debridmediamanager.com/zurg/pkg/realdebrid" type Torrent struct { Version string realdebrid.Torrent - Directories []string SelectedFiles []File ForRepair bool }