using System; using System.Collections.Generic; using System.Threading.Tasks; using FluentValidation; using Microsoft.AspNetCore.Mvc; using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.Extensions; using NzbDrone.Core.Books; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Download; using NzbDrone.Core.Exceptions; using NzbDrone.Core.Indexers; using NzbDrone.Core.IndexerSearch; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Validation; using Readarr.Http; using HttpStatusCode = System.Net.HttpStatusCode; namespace Readarr.Api.V1.Indexers { [V1ApiController] public class ReleaseController : ReleaseControllerBase { private readonly IFetchAndParseRss _rssFetcherAndParser; private readonly ISearchForReleases _releaseSearchService; private readonly IMakeDownloadDecision _downloadDecisionMaker; private readonly IPrioritizeDownloadDecision _prioritizeDownloadDecision; private readonly IDownloadService _downloadService; private readonly IAuthorService _authorService; private readonly IBookService _bookService; private readonly IParsingService _parsingService; private readonly Logger _logger; private readonly ICached _remoteBookCache; public ReleaseController(IFetchAndParseRss rssFetcherAndParser, ISearchForReleases releaseSearchService, IMakeDownloadDecision downloadDecisionMaker, IPrioritizeDownloadDecision prioritizeDownloadDecision, IDownloadService downloadService, IAuthorService authorService, IBookService bookService, IParsingService parsingService, ICacheManager cacheManager, Logger logger) { _rssFetcherAndParser = rssFetcherAndParser; _releaseSearchService = releaseSearchService; _downloadDecisionMaker = downloadDecisionMaker; _prioritizeDownloadDecision = prioritizeDownloadDecision; _downloadService = downloadService; _authorService = authorService; _bookService = bookService; _parsingService = parsingService; _logger = logger; PostValidator.RuleFor(s => s.IndexerId).ValidId(); PostValidator.RuleFor(s => s.Guid).NotEmpty(); _remoteBookCache = cacheManager.GetCache(GetType(), "remoteBooks"); } [HttpPost] public async Task> DownloadRelease(ReleaseResource release) { ValidateResource(release); var remoteBook = _remoteBookCache.Find(GetCacheKey(release)); if (remoteBook == null) { _logger.Debug("Couldn't find requested release in cache, cache timeout probably expired."); throw new NzbDroneClientException(HttpStatusCode.NotFound, "Couldn't find requested release in cache, try searching again"); } try { if (remoteBook.Author == null) { if (release.BookId.HasValue) { var book = _bookService.GetBook(release.BookId.Value); remoteBook.Author = _authorService.GetAuthor(book.AuthorId); remoteBook.Books = new List { book }; } else if (release.AuthorId.HasValue) { var author = _authorService.GetAuthor(release.AuthorId.Value); var books = _parsingService.GetBooks(remoteBook.ParsedBookInfo, author); if (books.Empty()) { throw new NzbDroneClientException(HttpStatusCode.NotFound, "Unable to parse books in the release"); } remoteBook.Author = author; remoteBook.Books = books; } else { throw new NzbDroneClientException(HttpStatusCode.NotFound, "Unable to find matching author and books"); } } else if (remoteBook.Books.Empty()) { var books = _parsingService.GetBooks(remoteBook.ParsedBookInfo, remoteBook.Author); if (books.Empty() && release.BookId.HasValue) { var book = _bookService.GetBook(release.BookId.Value); books = new List { book }; } remoteBook.Books = books; } if (remoteBook.Books.Empty()) { throw new NzbDroneClientException(HttpStatusCode.NotFound, "Unable to parse books in the release"); } await _downloadService.DownloadReport(remoteBook, release.DownloadClientId); } catch (ReleaseDownloadException ex) { _logger.Error(ex, "Getting release from indexer failed"); throw new NzbDroneClientException(HttpStatusCode.Conflict, "Getting release from indexer failed"); } return Ok(release); } [HttpGet] public async Task> GetReleases(int? bookId, int? authorId) { if (bookId.HasValue) { return await GetBookReleases(int.Parse(Request.Query["bookId"])); } if (authorId.HasValue) { return await GetAuthorReleases(int.Parse(Request.Query["authorId"])); } return await GetRss(); } private async Task> GetBookReleases(int bookId) { try { var decisions = await _releaseSearchService.BookSearch(bookId, true, true, true); var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions); return MapDecisions(prioritizedDecisions); } catch (Exception ex) { _logger.Error(ex, "Book search failed"); throw new NzbDroneClientException(HttpStatusCode.InternalServerError, ex.Message); } } private async Task> GetAuthorReleases(int authorId) { try { var decisions = await _releaseSearchService.AuthorSearch(authorId, false, true, true); var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions); return MapDecisions(prioritizedDecisions); } catch (Exception ex) { _logger.Error(ex, "Author search failed"); throw new NzbDroneClientException(HttpStatusCode.InternalServerError, ex.Message); } } private async Task> GetRss() { var reports = await _rssFetcherAndParser.Fetch(); var decisions = _downloadDecisionMaker.GetRssDecision(reports); var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions); return MapDecisions(prioritizedDecisions); } protected override ReleaseResource MapDecision(DownloadDecision decision, int initialWeight) { var resource = base.MapDecision(decision, initialWeight); _remoteBookCache.Set(GetCacheKey(resource), decision.RemoteBook, TimeSpan.FromMinutes(30)); return resource; } private string GetCacheKey(ReleaseResource resource) { return string.Concat(resource.IndexerId, "_", resource.Guid); } } }