diff --git a/internal/indexer/search.go b/internal/indexer/search.go index 0c45199..4f93e2f 100644 --- a/internal/indexer/search.go +++ b/internal/indexer/search.go @@ -4,11 +4,14 @@ import ( "encoding/xml" pb "homelab.lan/music-agregator/gen/music_agregator/indexer/v1" + "homelab.lan/music-agregator/internal/tracker/rutracker" ) +var parserFactory = rutracker.NewParserFactory() + type SearchResult struct { XMLName xml.Name `xml:"rss"` - Items []Item `xml:"channel>item"` // Directly targets items inside channel + Items []Item `xml:"channel>item"` } type Item struct { @@ -38,7 +41,6 @@ func (sr *SearchResult) ToProto() *pb.SearchResponse { pbItems := make([]*pb.SearchItem, len(sr.Items)) for i, item := range sr.Items { - // Map Torznab Attributes pbAttrs := make([]*pb.TorznabAttr, len(item.TorznabAttrs)) for j, attr := range item.TorznabAttrs { pbAttrs[j] = &pb.TorznabAttr{ @@ -47,21 +49,23 @@ func (sr *SearchResult) ToProto() *pb.SearchResponse { } } - // Map the Item + release := parserFactory.GetParser(item.Categories).Parse(item.Title) + pbItems[i] = &pb.SearchItem{ - Title: item.Title, - Link: item.Link, - Guid: item.Guid, - PubDate: item.PubDate, - Size: item.Size, - Description: item.Description, - Categories: item.Categories, + Title: item.Title, + DownloadLink: item.Link, + TorrentPageUrl: item.Guid, + PubDate: item.PubDate, + Size: item.Size, + Description: item.Description, + Categories: item.Categories, Enclosure: &pb.Enclosure{ Url: item.Enclosure.URL, Length: item.Enclosure.Length, Type: item.Enclosure.Type, }, TorznabAttrs: pbAttrs, + Release: release.ToProto(), } } diff --git a/internal/indexer/server.go b/internal/indexer/server.go index 4c1ee81..359279b 100644 --- a/internal/indexer/server.go +++ b/internal/indexer/server.go @@ -2,7 +2,6 @@ package indexer import ( "context" - "fmt" "github.com/rs/zerolog/log" "google.golang.org/grpc" @@ -12,39 +11,28 @@ import ( ) type IndexerServer struct { - indexer Indexer - + service *IndexerService pb.UnimplementedIndexerServiceServer } func NewIndexerServer(cfg config.Config) (*IndexerServer, error) { - switch cfg.Indexer.Type { - case config.IndexerTypeJackett: - indexer := NewIndexer(cfg) - return &IndexerServer{indexer: indexer}, nil - default: - return nil, fmt.Errorf("Unable to create the indexer for type: %v", cfg.Indexer.Type) + service, err := NewIndexerService(cfg) + if err != nil { + log.Err(err).Msg("Failed to initialize IndexerService") + return nil, err } + + return &IndexerServer{service: service}, nil } func (server *IndexerServer) Search(ctx context.Context, req *pb.SearchRequest) (*pb.SearchResponse, error) { log.Debug().Str("query", req.GetQuery()).Int32("limit", req.GetLimit()).Str("indexer", req.GetTracker()).Msg("Running search with these prams") - searchResult, err := server.indexer.Search(req.GetQuery(), req.GetLimit(), req.GetTracker()) - if err != nil { - log.Error().Err(err).Msg("Failed to search in indexer") - return nil, err - } - return searchResult.ToProto(), nil + return server.service.Search(req) } func (server *IndexerServer) Capabilities(ctx context.Context, req *pb.CapabilitiesRequest) (*pb.CapabilitiesResponse, error) { - capabilities, err := server.indexer.Capabilities(req.GetIndexer()) - if err != nil { - log.Error().Err(err).Msg("Failed to get capabilities from indexer") - return nil, err - } - return capabilities.ToProto(), nil + return server.service.Capabilities(req) } func (s *IndexerServer) Register(server *grpc.Server) { diff --git a/internal/indexer/service.go b/internal/indexer/service.go new file mode 100644 index 0000000..a01f30f --- /dev/null +++ b/internal/indexer/service.go @@ -0,0 +1,42 @@ +package indexer + +import ( + "fmt" + + "github.com/rs/zerolog/log" + + pb "homelab.lan/music-agregator/gen/music_agregator/indexer/v1" + "homelab.lan/music-agregator/internal/config" +) + +type IndexerService struct { + indexer Indexer +} + +func NewIndexerService(cfg config.Config) (*IndexerService, error) { + switch cfg.Indexer.Type { + case config.IndexerTypeJackett: + indexer := NewIndexer(cfg) + return &IndexerService{indexer: indexer}, nil + default: + return nil, fmt.Errorf("Unable to create the indexer for type: %v", cfg.Indexer.Type) + } +} + +func (service *IndexerService) Search(req *pb.SearchRequest) (*pb.SearchResponse, error) { + searchResult, err := service.indexer.Search(req.GetQuery(), req.GetLimit(), req.GetTracker()) + if err != nil { + log.Error().Err(err).Msg("Failed to search in indexer") + return nil, err + } + return searchResult.ToProto(), nil +} + +func (service *IndexerService) Capabilities(req *pb.CapabilitiesRequest) (*pb.CapabilitiesResponse, error) { + capabilities, err := service.indexer.Capabilities(req.GetIndexer()) + if err != nil { + log.Error().Err(err).Msg("Failed to get capabilities from indexer") + return nil, err + } + return capabilities.ToProto(), nil +} diff --git a/internal/release/release.go b/internal/release/release.go index 1298ebe..fb1a0fc 100644 --- a/internal/release/release.go +++ b/internal/release/release.go @@ -1,5 +1,9 @@ package release +import ( + pb "homelab.lan/music-agregator/gen/music_agregator/indexer/v1" +) + type Type int const ( @@ -176,3 +180,30 @@ func (r *Release) IsSingleRelease() bool { func (r *Release) HasYearRange() bool { return r.YearEnd > 0 && r.YearEnd != r.Year } + +func (r *Release) ToProto() *pb.Release { + if r == nil { + return nil + } + return &pb.Release{ + RawTitle: r.RawTitle, + Artist: r.Artist, + Album: r.Album, + Year: int32(r.Year), + YearEnd: int32(r.YearEnd), + Type: r.Type.String(), + Genres: r.Genres, + Format: r.Format.String(), + Source: r.Source.String(), + Bitrate: r.Bitrate, + BitDepth: int32(r.BitDepth), + SampleRate: int32(r.SampleRate), + RipType: r.RipType, + ReleaseCount: int32(r.ReleaseCount), + Tags: r.Tags, + Label: r.Label, + CatalogNum: r.CatalogNum, + ParsedSuccessfully: r.ParsedSuccessfully, + ParseErrors: r.ParseErrors, + } +} diff --git a/internal/tracker/factory.go b/internal/tracker/factory.go new file mode 100644 index 0000000..84d369a --- /dev/null +++ b/internal/tracker/factory.go @@ -0,0 +1,5 @@ +package tracker + +type ParserFactory interface { + GetParser(categories []string) Parser +} diff --git a/internal/tracker/rutracker/parser/parser.go b/internal/tracker/parser.go similarity index 88% rename from internal/tracker/rutracker/parser/parser.go rename to internal/tracker/parser.go index cc61f23..87d1576 100644 --- a/internal/tracker/rutracker/parser/parser.go +++ b/internal/tracker/parser.go @@ -1,4 +1,4 @@ -package parser +package tracker import "homelab.lan/music-agregator/internal/release" diff --git a/internal/tracker/rutracker/factory.go b/internal/tracker/rutracker/factory.go new file mode 100644 index 0000000..bd0ba17 --- /dev/null +++ b/internal/tracker/rutracker/factory.go @@ -0,0 +1,140 @@ +package rutracker + +import ( + "strconv" + + "homelab.lan/music-agregator/internal/tracker" + "homelab.lan/music-agregator/internal/tracker/rutracker/parser" +) + +type parserType int + +const ( + parserGeneral parserType = iota + parserLossless + parserLossy + parserHiRes + parserVinylDigitization + parserClassical + parserJazz + parserMetal + parserSoundtracks + parserDiscography + parserLabelPacks +) + +var categoryToParser map[int]parserType + +func init() { + categoryToParser = make(map[int]parserType) + + torznabCategories := map[int]parserType{ + 3000: parserGeneral, + 3010: parserLossy, + 3040: parserLossless, + } + + losslessForumIDs := []int{ + 425, 429, 1760, 1635, 1634, 2495, 1299, 1141, 1660, 1662, 1661, 1852, 1648, + 1851, 1850, 1633, 1632, 1643, 1846, 2219, 2220, 2221, 1647, 1847, 1848, 1653, + 738, 739, 740, 1656, 1654, 1655, 1843, 1841, 1842, 408, 1844, 1845, 1849, + 1650, 1651, 1652, 1659, 1657, 1658, 445, 1664, 1665, 1666, 1669, 1667, 1668, + 1906, 1907, 1908, 1911, 1909, 1910, + } + + lossyForumIDs := []int{ + 424, 428, 1754, 1755, 1756, 1757, 1758, 1759, 1760, 1761, 441, 446, + 1765, 1766, 1767, 1768, 1769, 1770, 1771, + } + + hiResForumIDs := []int{ + 1801, 1807, 1808, 1809, 1810, 1811, 1812, 1813, 1814, 1815, 1816, 1817, + 2378, 2379, 2380, 2381, 2382, 2383, 2384, + } + + vinylForumIDs := []int{ + 1802, 1803, 1804, 1805, 1806, + } + + classicalForumIDs := []int{ + 436, 969, 1990, 984, 1125, 1126, 1127, 1128, 1129, 1130, 1131, 1132, + 1670, 1671, 1672, 1673, 1674, 1675, 1676, 1677, + } + + jazzForumIDs := []int{ + 1698, 1699, 1700, 1701, 1702, 1703, 1704, 1705, 1706, 1707, 1708, 1709, + } + + metalForumIDs := []int{ + 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, + 1730, 1731, 1732, 1733, 1734, 1735, 1736, 1737, 1738, 1739, + } + + soundtrackForumIDs := []int{ + 691, 702, 704, 705, 706, 707, 708, 709, 710, 711, + 1631, 469, 786, + } + + for id, pt := range torznabCategories { + categoryToParser[id] = pt + } + for _, id := range losslessForumIDs { + categoryToParser[id] = parserLossless + } + for _, id := range lossyForumIDs { + categoryToParser[id] = parserLossy + } + for _, id := range hiResForumIDs { + categoryToParser[id] = parserHiRes + } + for _, id := range vinylForumIDs { + categoryToParser[id] = parserVinylDigitization + } + for _, id := range classicalForumIDs { + categoryToParser[id] = parserClassical + } + for _, id := range jazzForumIDs { + categoryToParser[id] = parserJazz + } + for _, id := range metalForumIDs { + categoryToParser[id] = parserMetal + } + for _, id := range soundtrackForumIDs { + categoryToParser[id] = parserSoundtracks + } +} + +type ParserFactory struct { + parsers map[parserType]tracker.Parser +} + +func NewParserFactory() *ParserFactory { + return &ParserFactory{ + parsers: map[parserType]tracker.Parser{ + parserGeneral: parser.NewGeneralParser(), + parserLossless: parser.NewLosslessParser(), + parserLossy: parser.NewLossyParser(), + parserHiRes: parser.NewHiResParser(), + parserVinylDigitization: parser.NewVinylDigitizationParser(), + parserClassical: parser.NewClassicalParser(), + parserJazz: parser.NewJazzParser(), + parserMetal: parser.NewMetalParser(), + parserSoundtracks: parser.NewSoundtracksParser(), + parserDiscography: parser.NewDiscographyParser(), + parserLabelPacks: parser.NewLabelPacksParser(), + }, + } +} + +func (f *ParserFactory) GetParser(categories []string) tracker.Parser { + for _, cat := range categories { + catID, err := strconv.Atoi(cat) + if err != nil { + continue + } + if pt, ok := categoryToParser[catID]; ok { + return f.parsers[pt] + } + } + return f.parsers[parserGeneral] +} diff --git a/internal/tracker/rutracker/factory_test.go b/internal/tracker/rutracker/factory_test.go new file mode 100644 index 0000000..8083504 --- /dev/null +++ b/internal/tracker/rutracker/factory_test.go @@ -0,0 +1,72 @@ +package rutracker + +import ( + "testing" + + "homelab.lan/music-agregator/internal/tracker" + "homelab.lan/music-agregator/internal/tracker/rutracker/parser" +) + +func TestParserFactory_GetParser(t *testing.T) { + f := NewParserFactory() + + tests := []struct { + name string + categories []string + wantType string + }{ + {"torznab lossless", []string{"3040"}, "*parser.LosslessParser"}, + {"torznab lossy", []string{"3010"}, "*parser.LossyParser"}, + {"torznab general", []string{"3000"}, "*parser.GeneralParser"}, + {"rutracker lossless forum", []string{"425"}, "*parser.LosslessParser"}, + {"rutracker lossy forum", []string{"424"}, "*parser.LossyParser"}, + {"rutracker hires forum", []string{"1801"}, "*parser.HiResParser"}, + {"rutracker vinyl forum", []string{"1802"}, "*parser.VinylDigitizationParser"}, + {"rutracker classical forum", []string{"436"}, "*parser.ClassicalParser"}, + {"rutracker jazz forum", []string{"1698"}, "*parser.JazzParser"}, + {"rutracker metal forum", []string{"731"}, "*parser.MetalParser"}, + {"rutracker soundtrack forum", []string{"691"}, "*parser.SoundtracksParser"}, + {"unknown category falls back to general", []string{"99999"}, "*parser.GeneralParser"}, + {"empty categories falls back to general", []string{}, "*parser.GeneralParser"}, + {"multiple categories uses first match", []string{"99999", "3040"}, "*parser.LosslessParser"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := f.GetParser(tt.categories) + gotType := getParserTypeName(p) + if gotType != tt.wantType { + t.Errorf("GetParser(%v) = %v, want %v", tt.categories, gotType, tt.wantType) + } + }) + } +} + +func getParserTypeName(p tracker.Parser) string { + switch p.(type) { + case *parser.GeneralParser: + return "*parser.GeneralParser" + case *parser.LosslessParser: + return "*parser.LosslessParser" + case *parser.LossyParser: + return "*parser.LossyParser" + case *parser.HiResParser: + return "*parser.HiResParser" + case *parser.VinylDigitizationParser: + return "*parser.VinylDigitizationParser" + case *parser.ClassicalParser: + return "*parser.ClassicalParser" + case *parser.JazzParser: + return "*parser.JazzParser" + case *parser.MetalParser: + return "*parser.MetalParser" + case *parser.SoundtracksParser: + return "*parser.SoundtracksParser" + case *parser.DiscographyParser: + return "*parser.DiscographyParser" + case *parser.LabelPacksParser: + return "*parser.LabelPacksParser" + default: + return "unknown" + } +} diff --git a/internal/tracker/rutracker/parser/classical.go b/internal/tracker/rutracker/parser/classical.go index 20c5422..7936947 100644 --- a/internal/tracker/rutracker/parser/classical.go +++ b/internal/tracker/rutracker/parser/classical.go @@ -36,5 +36,3 @@ func (p *ClassicalParser) Parse(title string) *release.Release { return r } - -var _ Parser = (*ClassicalParser)(nil) diff --git a/internal/tracker/rutracker/parser/discography.go b/internal/tracker/rutracker/parser/discography.go index 8c99b15..d5830ac 100644 --- a/internal/tracker/rutracker/parser/discography.go +++ b/internal/tracker/rutracker/parser/discography.go @@ -52,5 +52,3 @@ func (p *DiscographyParser) extractDiscographyArtist(title string) string { artist, _ := p.ExtractArtistAlbum(title) return artist } - -var _ Parser = (*DiscographyParser)(nil) diff --git a/internal/tracker/rutracker/parser/general.go b/internal/tracker/rutracker/parser/general.go index 4403b13..9006553 100644 --- a/internal/tracker/rutracker/parser/general.go +++ b/internal/tracker/rutracker/parser/general.go @@ -33,5 +33,3 @@ func (p *GeneralParser) Parse(title string) *release.Release { return r } - -var _ Parser = (*GeneralParser)(nil) diff --git a/internal/tracker/rutracker/parser/hires.go b/internal/tracker/rutracker/parser/hires.go index 614d9a0..c0faf1c 100644 --- a/internal/tracker/rutracker/parser/hires.go +++ b/internal/tracker/rutracker/parser/hires.go @@ -43,5 +43,3 @@ func (p *HiResParser) Parse(title string) *release.Release { return r } - -var _ Parser = (*HiResParser)(nil) diff --git a/internal/tracker/rutracker/parser/jazz.go b/internal/tracker/rutracker/parser/jazz.go index 6693a70..ff7f0c4 100644 --- a/internal/tracker/rutracker/parser/jazz.go +++ b/internal/tracker/rutracker/parser/jazz.go @@ -36,5 +36,3 @@ func (p *JazzParser) Parse(title string) *release.Release { return r } - -var _ Parser = (*JazzParser)(nil) diff --git a/internal/tracker/rutracker/parser/label_packs.go b/internal/tracker/rutracker/parser/label_packs.go index 42fddaa..ff7c616 100644 --- a/internal/tracker/rutracker/parser/label_packs.go +++ b/internal/tracker/rutracker/parser/label_packs.go @@ -31,5 +31,3 @@ func (p *LabelPacksParser) Parse(title string) *release.Release { return r } - -var _ Parser = (*LabelPacksParser)(nil) diff --git a/internal/tracker/rutracker/parser/lossless.go b/internal/tracker/rutracker/parser/lossless.go index b73ba35..bfb9dae 100644 --- a/internal/tracker/rutracker/parser/lossless.go +++ b/internal/tracker/rutracker/parser/lossless.go @@ -35,5 +35,3 @@ func (p *LosslessParser) Parse(title string) *release.Release { return r } - -var _ Parser = (*LosslessParser)(nil) diff --git a/internal/tracker/rutracker/parser/lossy.go b/internal/tracker/rutracker/parser/lossy.go index 842b70e..54f78aa 100644 --- a/internal/tracker/rutracker/parser/lossy.go +++ b/internal/tracker/rutracker/parser/lossy.go @@ -34,5 +34,3 @@ func (p *LossyParser) Parse(title string) *release.Release { return r } - -var _ Parser = (*LossyParser)(nil) diff --git a/internal/tracker/rutracker/parser/metal.go b/internal/tracker/rutracker/parser/metal.go index d19db46..214ce80 100644 --- a/internal/tracker/rutracker/parser/metal.go +++ b/internal/tracker/rutracker/parser/metal.go @@ -36,5 +36,3 @@ func (p *MetalParser) Parse(title string) *release.Release { return r } - -var _ Parser = (*MetalParser)(nil) diff --git a/internal/tracker/rutracker/parser/soundtracks.go b/internal/tracker/rutracker/parser/soundtracks.go index 130349b..8cd3d7f 100644 --- a/internal/tracker/rutracker/parser/soundtracks.go +++ b/internal/tracker/rutracker/parser/soundtracks.go @@ -32,5 +32,3 @@ func (p *SoundtracksParser) Parse(title string) *release.Release { return r } - -var _ Parser = (*SoundtracksParser)(nil) diff --git a/internal/tracker/rutracker/parser/vinyl_digitization.go b/internal/tracker/rutracker/parser/vinyl_digitization.go index 6e24597..7a3d3f3 100644 --- a/internal/tracker/rutracker/parser/vinyl_digitization.go +++ b/internal/tracker/rutracker/parser/vinyl_digitization.go @@ -40,5 +40,3 @@ func (p *VinylDigitizationParser) Parse(title string) *release.Release { return r } - -var _ Parser = (*VinylDigitizationParser)(nil) diff --git a/proto/music_agregator/indexer/v1/indexer.proto b/proto/music_agregator/indexer/v1/indexer.proto index 89a90e2..544f5a3 100644 --- a/proto/music_agregator/indexer/v1/indexer.proto +++ b/proto/music_agregator/indexer/v1/indexer.proto @@ -18,14 +18,37 @@ message SearchResponse { message SearchItem { string title = 1; - string link = 2; - string guid = 3; + string download_link = 2; + string torrent_page_url = 3; string pub_date = 4; int64 size = 5; string description = 6; repeated string categories = 7; Enclosure enclosure = 8; repeated TorznabAttr torznab_attrs = 9; + Release release = 10; +} + +message Release { + string raw_title = 1; + string artist = 2; + string album = 3; + int32 year = 4; + int32 year_end = 5; + string type = 6; + repeated string genres = 7; + string format = 8; + string source = 9; + string bitrate = 10; + int32 bit_depth = 11; + int32 sample_rate = 12; + string rip_type = 13; + int32 release_count = 14; + repeated string tags = 15; + string label = 16; + string catalog_num = 17; + bool parsed_successfully = 18; + repeated string parse_errors = 19; } message Enclosure {