Skip to content

Commit

Permalink
fix(processor): match filenames to seasonpack (#110)
Browse files Browse the repository at this point in the history
* fix(processor): match filenames to seasonpack

* chore(formatting): proper import sorting

* fix(tests): update matching file names tests

* fix(processor): skip hardlink of mismatched episodes

* chore(processor): move size logging to existing line

* feat(formatting): move out episode comparing logic

* feat(processor): improve matching logic and logging

* fix(processor): wrong error used in if condition

* chore(processor): logging cleanup

* chore(processor): more logging cleanup

* chore(processor): remove todo
  • Loading branch information
nuxencs authored May 7, 2024
1 parent 6ff2a00 commit 8a046bc
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 84 deletions.
52 changes: 34 additions & 18 deletions internal/http/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ type entryTime struct {

type matchPaths struct {
epPathClient string
epSizeClient int64
packPathAnnounce string
}

Expand Down Expand Up @@ -214,7 +215,7 @@ func (p *processor) processSeasonPack() (int, error) {
if !ok {
return StatusClientNotFound, fmt.Errorf("client not found in config")
}
p.log.Info().Msgf("using %q client serving at %s:%d", clientName, client.Host, client.Port)
p.log.Info().Msgf("using %s client serving at %s:%d", clientName, client.Host, client.Port)

if len(p.req.Name) == 0 {
return StatusAnnounceNameError, fmt.Errorf("couldn't get announce name")
Expand All @@ -236,7 +237,7 @@ func (p *processor) processSeasonPack() (int, error) {
}

packNameAnnounce := utils.FormatSeasonPackTitle(p.req.Name)
p.log.Debug().Msgf("formatted season pack name: %q", packNameAnnounce)
p.log.Debug().Msgf("formatted season pack name: %s", packNameAnnounce)

for _, child := range v {
if release.CheckCandidates(&requestrls, &child, p.cfg.Config.FuzzyMatching) == StatusAlreadyInClient {
Expand Down Expand Up @@ -306,13 +307,15 @@ func (p *processor) processSeasonPack() (int, error) {
case StatusSuccessfulMatch:
m, err := p.getFiles(child.T.Hash)
if err != nil {
p.log.Error().Err(err).Msgf("error getting files: %q", child.T.Name)
p.log.Error().Err(err).Msgf("error getting files: %s", child.T.Name)
continue
}

fileName := ""
for _, v := range *m {
fileName = v.Name
var size int64 = 0
for _, f := range *m {
fileName = f.Name
size = f.Size
break
}

Expand All @@ -325,6 +328,7 @@ func (p *processor) processSeasonPack() (int, error) {
currentMatch := []matchPaths{
{
epPathClient: epPathClient,
epSizeClient: size,
packPathAnnounce: packPathAnnounce,
},
}
Expand All @@ -336,7 +340,8 @@ func (p *processor) processSeasonPack() (int, error) {

newMatches := append(oldMatches.([]matchPaths), currentMatch...)
matchesMap.Store(p.req.Name, newMatches)
p.log.Debug().Msgf("matched torrent from client %q: %q %q", clientName, child.T.Name, child.T.Hash)
p.log.Debug().Msgf("matched torrent from client: name(%s), size(%d), hash(%s)",
child.T.Name, size, child.T.Hash)
respCodes = append(respCodes, res)
continue
}
Expand Down Expand Up @@ -376,11 +381,11 @@ func (p *processor) processSeasonPack() (int, error) {

for _, match := range matches {
if err := utils.CreateHardlink(match.epPathClient, match.packPathAnnounce); err != nil {
p.log.Error().Err(err).Msgf("error creating hardlink for: %q", match.epPathClient)
p.log.Error().Err(err).Msgf("error creating hardlink: %s", match.epPathClient)
hardlinkRespCodes = append(hardlinkRespCodes, StatusFailedHardlink)
continue
}
p.log.Log().Msgf("created hardlink of %q into %q", match.epPathClient, match.packPathAnnounce)
p.log.Log().Msgf("created hardlink: source(%s), target(%s)", match.epPathClient, match.packPathAnnounce)
hardlinkRespCodes = append(hardlinkRespCodes, StatusSuccessfulHardlink)
}

Expand Down Expand Up @@ -435,14 +440,14 @@ func (p *processor) parseTorrent() (int, error) {
return StatusParseTorrentInfoError, err
}
packNameParsed := torrentInfo.BestName()
p.log.Debug().Msgf("parsed season pack name: %q", packNameParsed)
p.log.Debug().Msgf("parsed season pack name: %s", packNameParsed)

torrentEps, err := torrents.GetEpisodesFromTorrentInfo(torrentInfo)
if err != nil {
return StatusGetEpisodesError, err
}
for _, torrentEp := range torrentEps {
p.log.Debug().Msgf("found episode in pack: %q", torrentEp)
p.log.Debug().Msgf("found episode in pack: name(%s), size(%d)", torrentEp.Name, torrentEp.Size)
}

matchesSlice, ok := matchesMap.Load(p.req.Name)
Expand All @@ -452,24 +457,35 @@ func (p *processor) parseTorrent() (int, error) {

matches := utils.DedupeSlice(matchesSlice.([]matchPaths))
var hardlinkRespCodes []int
var matchedPath string
var matchErr error

for _, match := range matches {
newPackPath := utils.ReplaceParentFolder(match.packPathAnnounce, packNameParsed)

// TODO: rework this functionality as it currently leads to overwritten hardlinks
/*
newPackPath, err = utils.MatchFileNameToSeasonPackNaming(newPackPath, torrentEps)
if err != nil {
p.log.Error().Err(err).Msgf("error matching episode to file in season pack: %q", match.epPathClient)
for _, torrentEp := range torrentEps {
matchedPath, matchErr = utils.MatchEpToSeasonPackEp(newPackPath, match.epSizeClient, torrentEp)
if matchErr != nil {
p.log.Debug().Err(matchErr).Msgf("episode did not match: client(%s), torrent(%s)",
filepath.Base(match.epPathClient), torrentEp.Name)
continue
}
*/
break
}
if matchErr != nil {
p.log.Error().Err(matchErr).Msgf("error matching episode to file in pack, skipping hardlink: %s",
filepath.Base(match.epPathClient))
hardlinkRespCodes = append(hardlinkRespCodes, StatusFailedHardlink)
continue
}
newPackPath = matchedPath

if err = utils.CreateHardlink(match.epPathClient, newPackPath); err != nil {
p.log.Error().Err(err).Msgf("error creating hardlink for: %q", match.epPathClient)
p.log.Error().Err(err).Msgf("error creating hardlink: %s", match.epPathClient)
hardlinkRespCodes = append(hardlinkRespCodes, StatusFailedHardlink)
continue
}
p.log.Log().Msgf("created hardlink of %q into %q", match.epPathClient, newPackPath)
p.log.Log().Msgf("created hardlink: source(%s), target(%s)", match.epPathClient, newPackPath)
hardlinkRespCodes = append(hardlinkRespCodes, StatusSuccessfulHardlink)
}

Expand Down
34 changes: 25 additions & 9 deletions internal/torrents/torrents.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@ package torrents

import (
"bytes"
"cmp"
"fmt"
"path/filepath"
"slices"

"github.com/anacrolix/torrent/metainfo"
)

type Episode struct {
Name string
Size int64
}

func ParseTorrentInfoFromTorrentBytes(torrentBytes []byte) (metainfo.Info, error) {
r := bytes.NewReader(torrentBytes)

Expand All @@ -28,23 +34,33 @@ func ParseTorrentInfoFromTorrentBytes(torrentBytes []byte) (metainfo.Info, error
return info, nil
}

func GetEpisodesFromTorrentInfo(info metainfo.Info) ([]string, error) {
var fileNames []string
func GetEpisodesFromTorrentInfo(info metainfo.Info) ([]Episode, error) {
var episodes []Episode

if !info.IsDir() {
return []string{}, fmt.Errorf("not a directory")
return []Episode{}, fmt.Errorf("not a directory")
}

for _, file := range info.Files {
if filepath.Ext(file.BestPath()[0]) == ".mkv" {
fileNames = append(fileNames, file.BestPath()...)
for _, path := range file.BestPath() {
if filepath.Ext(path) != ".mkv" {
continue
}

episodes = append(episodes, Episode{
Name: path,
Size: file.Length,
})
break
}
}

if len(fileNames) == 0 {
return []string{}, fmt.Errorf("no .mkv files found")
if len(episodes) == 0 {
return []Episode{}, fmt.Errorf("no .mkv files found")
}

slices.Sort(fileNames)
return fileNames, nil
slices.SortStableFunc(episodes, func(a, b Episode) int {
return cmp.Compare(a.Name, b.Name)
})
return episodes, nil
}
43 changes: 32 additions & 11 deletions internal/utils/formatting.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"regexp"
"strings"

"seasonpackarr/internal/torrents"

"github.com/moistari/rls"
)

Expand Down Expand Up @@ -52,19 +54,38 @@ func ReplaceParentFolder(path, newFolder string) string {
return newPath
}

func MatchFileNameToSeasonPackNaming(episodeInClientPath string, torrentEpisodeNames []string) (string, error) {
episodeRls := rls.ParseString(filepath.Base(episodeInClientPath))
func MatchEpToSeasonPackEp(epInClientPath string, epInClientSize int64, torrentEp torrents.Episode) (string, error) {
episodeRls := rls.ParseString(filepath.Base(epInClientPath))
torrentEpRls := rls.ParseString(filepath.Base(torrentEp.Name))

err := compareEpisodes(episodeRls, torrentEpRls)
if err != nil {
return epInClientPath, err
}

if epInClientSize != torrentEp.Size {
return epInClientPath, fmt.Errorf("size mismatch")
}

return filepath.Join(filepath.Dir(epInClientPath), filepath.Base(torrentEp.Name)), nil
}

func compareEpisodes(episodeRls, torrentEpRls rls.Release) error {
if episodeRls.Series != torrentEpRls.Series {
return fmt.Errorf("series mismatch")
}

if episodeRls.Episode != torrentEpRls.Episode {
return fmt.Errorf("episode mismatch")
}

for _, packPath := range torrentEpisodeNames {
packRls := rls.ParseString(filepath.Base(packPath))
if episodeRls.Resolution != torrentEpRls.Resolution {
return fmt.Errorf("resolution mismatch")
}

if (episodeRls.Series == packRls.Series) &&
(episodeRls.Episode == packRls.Episode) &&
(episodeRls.Resolution == packRls.Resolution) &&
(episodeRls.Group == packRls.Group) {
return filepath.Join(filepath.Dir(episodeInClientPath), filepath.Base(packPath)), nil
}
if rls.MustNormalize(episodeRls.Group) != rls.MustNormalize(torrentEpRls.Group) {
return fmt.Errorf("group mismatch")
}

return episodeInClientPath, fmt.Errorf("couldn't find matching episode in season pack, using existing file name")
return nil
}
Loading

0 comments on commit 8a046bc

Please sign in to comment.