private CachedSeedConfiguration FetchIndexer(string infoHash) { var historyItem = _downloadHistoryService.GetLatestGrab(infoHash); if (historyItem == null) { _logger.Debug("No download history item for infohash {0}, unable to provide seed configuration", infoHash); return(null); } ParsedBookInfo parsedBookInfo = null; if (historyItem.Release != null) { parsedBookInfo = Parser.Parser.ParseBookTitle(historyItem.Release.Title); } if (parsedBookInfo == null) { _logger.Debug("No parsed title in download history item for infohash {0}, unable to provide seed configuration", infoHash); return(null); } return(new CachedSeedConfiguration { IndexerId = historyItem.IndexerId, Discography = parsedBookInfo.Discography }); }
private Author GetAuthor(ParsedBookInfo parsedBookInfo, SearchCriteriaBase searchCriteria) { Author author = null; if (searchCriteria != null) { if (searchCriteria.Author.CleanName == parsedBookInfo.AuthorName.CleanAuthorName()) { return(searchCriteria.Author); } } author = _authorService.FindByName(parsedBookInfo.AuthorName); if (author == null) { _logger.Debug("Trying inexact author match for {0}", parsedBookInfo.AuthorName); author = _authorService.FindByNameInexact(parsedBookInfo.AuthorName); } if (author == null) { _logger.Debug("No matching author {0}", parsedBookInfo.AuthorName); return(null); } return(author); }
public RemoteBook Map(ParsedBookInfo parsedBookInfo, int authorId, IEnumerable <int> bookIds) { return(new RemoteBook { ParsedBookInfo = parsedBookInfo, Author = _authorService.GetAuthor(authorId), Books = _bookService.GetBooks(bookIds) }); }
private static ParsedBookInfo ParseBookMatchCollection(MatchCollection matchCollection) { var authorName = matchCollection[0].Groups["author"].Value.Replace('.', ' ').Replace('_', ' '); var bookTitle = matchCollection[0].Groups["book"].Value.Replace('.', ' ').Replace('_', ' '); var releaseVersion = matchCollection[0].Groups["version"].Value.Replace('.', ' ').Replace('_', ' '); authorName = RequestInfoRegex.Replace(authorName, "").Trim(' '); bookTitle = RequestInfoRegex.Replace(bookTitle, "").Trim(' '); releaseVersion = RequestInfoRegex.Replace(releaseVersion, "").Trim(' '); int releaseYear; int.TryParse(matchCollection[0].Groups["releaseyear"].Value, out releaseYear); ParsedBookInfo result; result = new ParsedBookInfo(); result.AuthorName = authorName; result.BookTitle = bookTitle; result.AuthorTitleInfo = GetAuthorTitleInfo(result.AuthorName); result.ReleaseDate = releaseYear.ToString(); result.ReleaseVersion = releaseVersion; if (matchCollection[0].Groups["discography"].Success) { int discStart; int discEnd; int.TryParse(matchCollection[0].Groups["startyear"].Value, out discStart); int.TryParse(matchCollection[0].Groups["endyear"].Value, out discEnd); result.Discography = true; if (discStart > 0 && discEnd > 0) { result.DiscographyStart = discStart; result.DiscographyEnd = discEnd; } else if (discEnd > 0) { result.DiscographyEnd = discEnd; } result.BookTitle = "Discography"; } Logger.Debug("Book Parsed. {0}", result); return(result); }
public RemoteBook Map(ParsedBookInfo parsedBookInfo, SearchCriteriaBase searchCriteria = null) { var remoteBook = new RemoteBook { ParsedBookInfo = parsedBookInfo, }; var author = GetAuthor(parsedBookInfo, searchCriteria); if (author == null) { return(remoteBook); } remoteBook.Author = author; remoteBook.Books = GetBooks(parsedBookInfo, author, searchCriteria); return(remoteBook); }
public void should_not_fail_if_search_criteria_contains_multiple_albums_with_the_same_name() { var artist = Builder <Author> .CreateNew().Build(); var albums = Builder <Book> .CreateListOfSize(2).All().With(x => x.Title = "IdenticalTitle").Build().ToList(); var criteria = new BookSearchCriteria { Author = artist, Books = albums }; var parsed = new ParsedBookInfo { BookTitle = "IdenticalTitle" }; Subject.GetAlbums(parsed, artist, criteria).Should().BeEquivalentTo(new List <Book>()); Mocker.GetMock <IBookService>() .Verify(s => s.FindByTitle(artist.AuthorMetadataId, "IdenticalTitle"), Times.Once()); }
public void Setup() { Mocker.Resolve <UpgradableSpecification>(); _parsedBookInfo = Builder <ParsedBookInfo> .CreateNew() .With(p => p.Quality = new QualityModel(Quality.FLAC, new Revision(2, 0, false))) .With(p => p.ReleaseGroup = "Readarr") .Build(); _albums = Builder <Book> .CreateListOfSize(1) .All() .BuildList(); _trackFiles = Builder <BookFile> .CreateListOfSize(3) .All() .With(t => t.EditionId = _albums.First().Id) .BuildList(); Mocker.GetMock <IMediaFileService>() .Setup(c => c.GetFilesByBook(It.IsAny <int>())) .Returns(_trackFiles); }
public static ParsedBookInfo ParseBookTitleWithSearchCriteria(string title, Author author, List <Book> books) { try { if (!ValidateBeforeParsing(title)) { return(null); } var authorName = author.Name == "Various Authors" ? "VA" : author.Name.RemoveAccent(); Logger.Debug("Parsing string '{0}' using search criteria author: '{1}' books: '{2}'", title, authorName.RemoveAccent(), string.Join(", ", books.Select(a => a.Title.RemoveAccent()))); var releaseTitle = RemoveFileExtension(title); var simpleTitle = SimpleTitleRegex.Replace(releaseTitle); simpleTitle = WebsitePrefixRegex.Replace(simpleTitle); simpleTitle = WebsitePostfixRegex.Replace(simpleTitle); simpleTitle = CleanTorrentSuffixRegex.Replace(simpleTitle); var bestBook = books .OrderByDescending(x => simpleTitle.FuzzyMatch(x.Editions.Value.Single(x => x.Monitored).Title, wordDelimiters: WordDelimiters)) .First() .Editions.Value .Single(x => x.Monitored); var foundAuthor = GetTitleFuzzy(simpleTitle, authorName, out var remainder); if (foundAuthor == null) { foundAuthor = GetTitleFuzzy(simpleTitle, authorName.ToLastFirst(), out remainder); } var foundBook = GetTitleFuzzy(remainder, bestBook.Title, out _); if (foundBook == null) { foundBook = GetTitleFuzzy(remainder, bestBook.Title.SplitBookTitle(authorName).Item1, out _); } Logger.Trace($"Found {foundAuthor} - {foundBook} with fuzzy parser"); if (foundAuthor == null || foundBook == null) { return(null); } var result = new ParsedBookInfo { AuthorName = foundAuthor, AuthorTitleInfo = GetAuthorTitleInfo(foundAuthor), BookTitle = foundBook }; try { result.Quality = QualityParser.ParseQuality(title); Logger.Debug("Quality parsed: {0}", result.Quality); result.ReleaseGroup = ParseReleaseGroup(releaseTitle); Logger.Debug("Release Group parsed: {0}", result.ReleaseGroup); return(result); } catch (InvalidDateException ex) { Logger.Debug(ex, ex.Message); } } catch (Exception e) { if (!title.ToLower().Contains("password") && !title.ToLower().Contains("yenc")) { Logger.Error(e, "An error has occurred while trying to parse {0}", title); } } Logger.Debug("Unable to parse {0}", title); return(null); }
public void Setup() { _author = Builder <Author> .CreateNew() .Build(); _book = Builder <Book> .CreateNew() .Build(); _profile = new QualityProfile { Name = "Test", Cutoff = Quality.MP3_320.Id, Items = new List <QualityProfileQualityItem> { new QualityProfileQualityItem { Allowed = true, Quality = Quality.MP3_320 }, new QualityProfileQualityItem { Allowed = true, Quality = Quality.MP3_320 }, new QualityProfileQualityItem { Allowed = true, Quality = Quality.MP3_320 } }, }; _author.QualityProfile = new LazyLoaded <QualityProfile>(_profile); _release = Builder <ReleaseInfo> .CreateNew().Build(); _parsedBookInfo = Builder <ParsedBookInfo> .CreateNew().Build(); _parsedBookInfo.Quality = new QualityModel(Quality.MP3_320); _remoteBook = new RemoteBook(); _remoteBook.Books = new List <Book> { _book }; _remoteBook.Author = _author; _remoteBook.ParsedBookInfo = _parsedBookInfo; _remoteBook.Release = _release; _temporarilyRejected = new DownloadDecision(_remoteBook, new Rejection("Temp Rejected", RejectionType.Temporary)); Mocker.GetMock <IPendingReleaseRepository>() .Setup(s => s.All()) .Returns(new List <PendingRelease>()); Mocker.GetMock <IAuthorService>() .Setup(s => s.GetAuthor(It.IsAny <int>())) .Returns(_author); Mocker.GetMock <IAuthorService>() .Setup(s => s.GetAuthors(It.IsAny <IEnumerable <int> >())) .Returns(new List <Author> { _author }); Mocker.GetMock <IParsingService>() .Setup(s => s.GetBooks(It.IsAny <ParsedBookInfo>(), _author, null)) .Returns(new List <Book> { _book }); Mocker.GetMock <IPrioritizeDownloadDecision>() .Setup(s => s.PrioritizeDecisions(It.IsAny <List <DownloadDecision> >())) .Returns((List <DownloadDecision> d) => d); }
public List <Book> GetBooks(ParsedBookInfo parsedBookInfo, Author author, SearchCriteriaBase searchCriteria = null) { var bookTitle = parsedBookInfo.BookTitle; var result = new List <Book>(); if (parsedBookInfo.BookTitle == null) { return(new List <Book>()); } Book bookInfo = null; if (parsedBookInfo.Discography) { if (parsedBookInfo.DiscographyStart > 0) { return(_bookService.AuthorBooksBetweenDates(author, new DateTime(parsedBookInfo.DiscographyStart, 1, 1), new DateTime(parsedBookInfo.DiscographyEnd, 12, 31), false)); } if (parsedBookInfo.DiscographyEnd > 0) { return(_bookService.AuthorBooksBetweenDates(author, new DateTime(1800, 1, 1), new DateTime(parsedBookInfo.DiscographyEnd, 12, 31), false)); } return(_bookService.GetBooksByAuthor(author.Id)); } if (searchCriteria != null) { var cleanTitle = Parser.CleanAuthorName(parsedBookInfo.BookTitle); bookInfo = searchCriteria.Books.ExclusiveOrDefault(e => e.Title == bookTitle || e.CleanTitle == cleanTitle); } if (bookInfo == null) { // TODO: Search by Title and Year instead of just Title when matching bookInfo = _bookService.FindByTitle(author.AuthorMetadataId, parsedBookInfo.BookTitle); } if (bookInfo == null) { var edition = _editionService.FindByTitle(author.AuthorMetadataId, parsedBookInfo.BookTitle); bookInfo = edition?.Book.Value; } if (bookInfo == null) { _logger.Debug("Trying inexact book match for {0}", parsedBookInfo.BookTitle); bookInfo = _bookService.FindByTitleInexact(author.AuthorMetadataId, parsedBookInfo.BookTitle); } if (bookInfo == null) { _logger.Debug("Trying inexact edition match for {0}", parsedBookInfo.BookTitle); var edition = _editionService.FindByTitleInexact(author.AuthorMetadataId, parsedBookInfo.BookTitle); bookInfo = edition?.Book.Value; } if (bookInfo != null) { result.Add(bookInfo); } else { _logger.Debug("Unable to find {0}", parsedBookInfo); } return(result); }
private IEnumerable <DownloadDecision> GetBookDecisions(List <ReleaseInfo> reports, SearchCriteriaBase searchCriteria = null) { if (reports.Any()) { _logger.ProgressInfo("Processing {0} releases", reports.Count); } else { _logger.ProgressInfo("No results found"); } var reportNumber = 1; foreach (var report in reports) { DownloadDecision decision = null; _logger.ProgressTrace("Processing release {0}/{1}", reportNumber, reports.Count); _logger.Debug("Processing release '{0}' from '{1}'", report.Title, report.Indexer); try { var parsedBookInfo = Parser.Parser.ParseBookTitle(report.Title); if (parsedBookInfo == null) { if (searchCriteria != null) { parsedBookInfo = Parser.Parser.ParseBookTitleWithSearchCriteria(report.Title, searchCriteria.Author, searchCriteria.Books); } else { // try parsing fuzzy parsedBookInfo = _parsingService.ParseAlbumTitleFuzzy(report.Title); } } if (parsedBookInfo != null && !parsedBookInfo.AuthorName.IsNullOrWhiteSpace()) { var remoteBook = _parsingService.Map(parsedBookInfo, searchCriteria); // try parsing again using the search criteria, in case it parsed but parsed incorrectly if ((remoteBook.Author == null || remoteBook.Books.Empty()) && searchCriteria != null) { _logger.Debug("Author/Book null for {0}, reparsing with search criteria", report.Title); var parsedBookInfoWithCriteria = Parser.Parser.ParseBookTitleWithSearchCriteria(report.Title, searchCriteria.Author, searchCriteria.Books); if (parsedBookInfoWithCriteria != null && parsedBookInfoWithCriteria.AuthorName.IsNotNullOrWhiteSpace()) { remoteBook = _parsingService.Map(parsedBookInfoWithCriteria, searchCriteria); } } remoteBook.Release = report; if (remoteBook.Author == null) { decision = new DownloadDecision(remoteBook, new Rejection("Unknown Author")); // shove in the searched author in case of forced download in interactive search if (searchCriteria != null) { remoteBook.Author = searchCriteria.Author; remoteBook.Books = searchCriteria.Books; } } else if (remoteBook.Books.Empty()) { decision = new DownloadDecision(remoteBook, new Rejection("Unable to parse books from release name")); if (searchCriteria != null) { remoteBook.Books = searchCriteria.Books; } } else { _aggregationService.Augment(remoteBook); remoteBook.DownloadAllowed = remoteBook.Books.Any(); decision = GetDecisionForReport(remoteBook, searchCriteria); } } if (searchCriteria != null) { if (parsedBookInfo == null) { parsedBookInfo = new ParsedBookInfo { Quality = QualityParser.ParseQuality(report.Title) }; } if (parsedBookInfo.AuthorName.IsNullOrWhiteSpace()) { var remoteBook = new RemoteBook { Release = report, ParsedBookInfo = parsedBookInfo }; decision = new DownloadDecision(remoteBook, new Rejection("Unable to parse release")); } } } catch (Exception e) { _logger.Error(e, "Couldn't process release."); var remoteBook = new RemoteBook { Release = report }; decision = new DownloadDecision(remoteBook, new Rejection("Unexpected error processing release")); } reportNumber++; if (decision != null) { if (decision.Rejections.Any()) { _logger.Debug("Release rejected for the following reasons: {0}", string.Join(", ", decision.Rejections)); } else { _logger.Debug("Release accepted"); } yield return(decision); } } }
public Tuple <List <LocalBook>, List <ImportDecision <LocalBook> > > GetLocalTracks(List <IFileInfo> musicFiles, DownloadClientItem downloadClientItem, ParsedTrackInfo folderInfo, FilterFilesType filter) { var watch = new System.Diagnostics.Stopwatch(); watch.Start(); var files = _mediaFileService.FilterUnchangedFiles(musicFiles, filter); var localTracks = new List <LocalBook>(); var decisions = new List <ImportDecision <LocalBook> >(); _logger.Debug("Analyzing {0}/{1} files.", files.Count, musicFiles.Count); if (!files.Any()) { return(Tuple.Create(localTracks, decisions)); } ParsedBookInfo downloadClientItemInfo = null; if (downloadClientItem != null) { downloadClientItemInfo = Parser.Parser.ParseBookTitle(downloadClientItem.Title); } var i = 1; foreach (var file in files) { _logger.ProgressInfo($"Reading file {i++}/{files.Count}"); var localTrack = new LocalBook { DownloadClientAlbumInfo = downloadClientItemInfo, FolderTrackInfo = folderInfo, Path = file.FullName, Size = file.Length, Modified = file.LastWriteTimeUtc, FileTrackInfo = _eBookTagService.ReadTags(file), AdditionalFile = false }; try { // TODO fix otherfiles? _augmentingService.Augment(localTrack, true); localTracks.Add(localTrack); } catch (AugmentingFailedException) { decisions.Add(new ImportDecision <LocalBook>(localTrack, new Rejection("Unable to parse file"))); } catch (Exception e) { _logger.Error(e, "Couldn't import file. {0}", localTrack.Path); decisions.Add(new ImportDecision <LocalBook>(localTrack, new Rejection("Unexpected error processing file"))); } } _logger.Debug($"Tags parsed for {files.Count} files in {watch.ElapsedMilliseconds}ms"); return(Tuple.Create(localTracks, decisions)); }