You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
258 lines
9.0 KiB
258 lines
9.0 KiB
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using FluentValidation;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using NzbDrone.Common.Extensions;
|
|
using NzbDrone.Core.AuthorStats;
|
|
using NzbDrone.Core.Books;
|
|
using NzbDrone.Core.Books.Events;
|
|
using NzbDrone.Core.Datastore.Events;
|
|
using NzbDrone.Core.DecisionEngine.Specifications;
|
|
using NzbDrone.Core.Download;
|
|
using NzbDrone.Core.MediaCover;
|
|
using NzbDrone.Core.MediaFiles;
|
|
using NzbDrone.Core.MediaFiles.Events;
|
|
using NzbDrone.Core.Messaging.Events;
|
|
using NzbDrone.Core.Validation;
|
|
using NzbDrone.Core.Validation.Paths;
|
|
using NzbDrone.Http.REST.Attributes;
|
|
using NzbDrone.SignalR;
|
|
using Readarr.Http;
|
|
using Readarr.Http.Extensions;
|
|
|
|
namespace Readarr.Api.V1.Books
|
|
{
|
|
[V1ApiController]
|
|
public class BookController : BookControllerWithSignalR,
|
|
IHandle<BookGrabbedEvent>,
|
|
IHandle<BookEditedEvent>,
|
|
IHandle<BookUpdatedEvent>,
|
|
IHandle<BookDeletedEvent>,
|
|
IHandle<BookImportedEvent>,
|
|
IHandle<TrackImportedEvent>,
|
|
IHandle<BookFileDeletedEvent>
|
|
{
|
|
protected readonly IAuthorService _authorService;
|
|
protected readonly IEditionService _editionService;
|
|
protected readonly IAddBookService _addBookService;
|
|
|
|
public BookController(IAuthorService authorService,
|
|
IBookService bookService,
|
|
IAddBookService addBookService,
|
|
IEditionService editionService,
|
|
ISeriesBookLinkService seriesBookLinkService,
|
|
IAuthorStatisticsService authorStatisticsService,
|
|
IMapCoversToLocal coverMapper,
|
|
IUpgradableSpecification upgradableSpecification,
|
|
IBroadcastSignalRMessage signalRBroadcaster,
|
|
QualityProfileExistsValidator qualityProfileExistsValidator,
|
|
MetadataProfileExistsValidator metadataProfileExistsValidator)
|
|
|
|
: base(bookService, seriesBookLinkService, authorStatisticsService, coverMapper, upgradableSpecification, signalRBroadcaster)
|
|
{
|
|
_authorService = authorService;
|
|
_editionService = editionService;
|
|
_addBookService = addBookService;
|
|
|
|
PostValidator.RuleFor(s => s.ForeignBookId).NotEmpty();
|
|
PostValidator.RuleFor(s => s.Author.QualityProfileId).SetValidator(qualityProfileExistsValidator);
|
|
PostValidator.RuleFor(s => s.Author.MetadataProfileId).SetValidator(metadataProfileExistsValidator);
|
|
PostValidator.RuleFor(s => s.Author.RootFolderPath).IsValidPath().When(s => s.Author.Path.IsNullOrWhiteSpace());
|
|
PostValidator.RuleFor(s => s.Author.ForeignAuthorId).NotEmpty();
|
|
}
|
|
|
|
[HttpGet]
|
|
public List<BookResource> GetBooks([FromQuery]int? authorId,
|
|
[FromQuery]List<int> bookIds,
|
|
[FromQuery]string titleSlug,
|
|
[FromQuery]bool includeAllAuthorBooks = false)
|
|
{
|
|
if (!authorId.HasValue && !bookIds.Any() && titleSlug.IsNullOrWhiteSpace())
|
|
{
|
|
var editionTask = Task.Run(() => _editionService.GetAllMonitoredEditions());
|
|
var metadataTask = Task.Run(() => _authorService.GetAllAuthors());
|
|
var books = _bookService.GetAllBooks();
|
|
|
|
var editions = editionTask.GetAwaiter().GetResult().GroupBy(x => x.BookId).ToDictionary(x => x.Key, y => y.ToList());
|
|
|
|
var authors = metadataTask.GetAwaiter().GetResult().ToDictionary(x => x.AuthorMetadataId);
|
|
|
|
foreach (var book in books)
|
|
{
|
|
book.Author = authors[book.AuthorMetadataId];
|
|
if (editions.TryGetValue(book.Id, out var bookEditions))
|
|
{
|
|
book.Editions = bookEditions;
|
|
}
|
|
else
|
|
{
|
|
book.Editions = new List<Edition>();
|
|
}
|
|
}
|
|
|
|
return MapToResource(books, false);
|
|
}
|
|
|
|
if (authorId.HasValue)
|
|
{
|
|
var books = _bookService.GetBooksByAuthor(authorId.Value);
|
|
|
|
var author = _authorService.GetAuthor(authorId.Value);
|
|
var editions = _editionService.GetEditionsByAuthor(authorId.Value)
|
|
.GroupBy(x => x.BookId)
|
|
.ToDictionary(x => x.Key, y => y.ToList());
|
|
|
|
foreach (var book in books)
|
|
{
|
|
book.Author = author;
|
|
if (editions.TryGetValue(book.Id, out var bookEditions))
|
|
{
|
|
book.Editions = bookEditions;
|
|
}
|
|
else
|
|
{
|
|
book.Editions = new List<Edition>();
|
|
}
|
|
}
|
|
|
|
return MapToResource(books, false);
|
|
}
|
|
|
|
if (titleSlug.IsNotNullOrWhiteSpace())
|
|
{
|
|
var book = _bookService.FindBySlug(titleSlug);
|
|
|
|
if (book == null)
|
|
{
|
|
return MapToResource(new List<Book>(), false);
|
|
}
|
|
|
|
if (includeAllAuthorBooks)
|
|
{
|
|
return MapToResource(_bookService.GetBooksByAuthor(book.AuthorId), false);
|
|
}
|
|
else
|
|
{
|
|
return MapToResource(new List<Book> { book }, false);
|
|
}
|
|
}
|
|
|
|
return MapToResource(_bookService.GetBooks(bookIds), false);
|
|
}
|
|
|
|
[HttpGet("{id:int}/overview")]
|
|
public object Overview(int id)
|
|
{
|
|
var overview = _editionService.GetEditionsByBook(id).Single(x => x.Monitored).Overview;
|
|
return new
|
|
{
|
|
id,
|
|
overview
|
|
};
|
|
}
|
|
|
|
[RestPostById]
|
|
public ActionResult<BookResource> AddBook(BookResource bookResource)
|
|
{
|
|
var book = _addBookService.AddBook(bookResource.ToModel());
|
|
|
|
return Created(book.Id);
|
|
}
|
|
|
|
[RestPutById]
|
|
public ActionResult<BookResource> UpdateBook(BookResource bookResource)
|
|
{
|
|
var book = _bookService.GetBook(bookResource.Id);
|
|
|
|
var model = bookResource.ToModel(book);
|
|
|
|
_bookService.UpdateBook(model);
|
|
_editionService.UpdateMany(model.Editions.Value);
|
|
|
|
BroadcastResourceChange(ModelAction.Updated, model.Id);
|
|
|
|
return Accepted(model.Id);
|
|
}
|
|
|
|
[RestDeleteById]
|
|
public void DeleteBook(int id)
|
|
{
|
|
var deleteFiles = Request.GetBooleanQueryParameter("deleteFiles");
|
|
var addImportListExclusion = Request.GetBooleanQueryParameter("addImportListExclusion");
|
|
|
|
_bookService.DeleteBook(id, deleteFiles, addImportListExclusion);
|
|
}
|
|
|
|
[HttpPut("monitor")]
|
|
public IActionResult SetBooksMonitored([FromBody]BooksMonitoredResource resource)
|
|
{
|
|
_bookService.SetMonitored(resource.BookIds, resource.Monitored);
|
|
|
|
if (resource.BookIds.Count == 1)
|
|
{
|
|
_bookService.SetBookMonitored(resource.BookIds.First(), resource.Monitored);
|
|
}
|
|
else
|
|
{
|
|
_bookService.SetMonitored(resource.BookIds, resource.Monitored);
|
|
}
|
|
|
|
return Accepted(MapToResource(_bookService.GetBooks(resource.BookIds), false));
|
|
}
|
|
|
|
[NonAction]
|
|
public void Handle(BookGrabbedEvent message)
|
|
{
|
|
foreach (var book in message.Book.Books)
|
|
{
|
|
var resource = book.ToResource();
|
|
resource.Grabbed = true;
|
|
|
|
BroadcastResourceChange(ModelAction.Updated, resource);
|
|
}
|
|
}
|
|
|
|
[NonAction]
|
|
public void Handle(BookEditedEvent message)
|
|
{
|
|
BroadcastResourceChange(ModelAction.Updated, MapToResource(message.Book, true));
|
|
}
|
|
|
|
[NonAction]
|
|
public void Handle(BookUpdatedEvent message)
|
|
{
|
|
BroadcastResourceChange(ModelAction.Updated, MapToResource(message.Book, true));
|
|
}
|
|
|
|
[NonAction]
|
|
public void Handle(BookDeletedEvent message)
|
|
{
|
|
BroadcastResourceChange(ModelAction.Deleted, message.Book.ToResource());
|
|
}
|
|
|
|
[NonAction]
|
|
public void Handle(BookImportedEvent message)
|
|
{
|
|
BroadcastResourceChange(ModelAction.Updated, MapToResource(message.Book, true));
|
|
}
|
|
|
|
[NonAction]
|
|
public void Handle(TrackImportedEvent message)
|
|
{
|
|
BroadcastResourceChange(ModelAction.Updated, message.BookInfo.Book.ToResource());
|
|
}
|
|
|
|
[NonAction]
|
|
public void Handle(BookFileDeletedEvent message)
|
|
{
|
|
if (message.Reason == DeleteMediaFileReason.Upgrade)
|
|
{
|
|
return;
|
|
}
|
|
|
|
BroadcastResourceChange(ModelAction.Updated, MapToResource(message.BookFile.Edition.Value.Book.Value, true));
|
|
}
|
|
}
|
|
}
|