using System.Collections.Generic; using System.IO; using System.IO.Abstractions; using System.Linq; using Microsoft.AspNetCore.Mvc; using NzbDrone.Core.Books; using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Exceptions; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; using NzbDrone.Http.REST.Attributes; using NzbDrone.SignalR; using Readarr.Http; using Readarr.Http.REST; using BadRequestException = NzbDrone.Core.Exceptions.BadRequestException; using HttpStatusCode = System.Net.HttpStatusCode; namespace Readarr.Api.V1.BookFiles { [V1ApiController] public class BookFileController : RestControllerWithSignalR, IHandle, IHandle { private readonly IMediaFileService _mediaFileService; private readonly IDeleteMediaFiles _mediaFileDeletionService; private readonly IMetadataTagService _metadataTagService; private readonly IAuthorService _authorService; private readonly IBookService _bookService; private readonly IUpgradableSpecification _upgradableSpecification; public BookFileController(IBroadcastSignalRMessage signalRBroadcaster, IMediaFileService mediaFileService, IDeleteMediaFiles mediaFileDeletionService, IMetadataTagService metadataTagService, IAuthorService authorService, IBookService bookService, IUpgradableSpecification upgradableSpecification) : base(signalRBroadcaster) { _mediaFileService = mediaFileService; _mediaFileDeletionService = mediaFileDeletionService; _metadataTagService = metadataTagService; _authorService = authorService; _bookService = bookService; _upgradableSpecification = upgradableSpecification; } private BookFileResource MapToResource(BookFile bookFile) { if (bookFile.EditionId > 0 && bookFile.Author != null && bookFile.Author.Value != null) { return bookFile.ToResource(bookFile.Author.Value, _upgradableSpecification); } else { return bookFile.ToResource(); } } public override BookFileResource GetResourceById(int id) { var resource = MapToResource(_mediaFileService.Get(id)); resource.AudioTags = _metadataTagService.ReadTags((FileInfoBase)new FileInfo(resource.Path)); return resource; } [HttpGet] public List GetBookFiles(int? authorId, [FromQuery]List bookFileIds, [FromQuery(Name="bookId")]List bookIds, bool? unmapped) { if (!authorId.HasValue && !bookFileIds.Any() && !bookIds.Any() && !unmapped.HasValue) { throw new BadRequestException("authorId, bookId, bookFileIds or unmapped must be provided"); } if (unmapped.HasValue && unmapped.Value) { var files = _mediaFileService.GetUnmappedFiles(); return files.ConvertAll(f => MapToResource(f)); } if (authorId.HasValue && !bookIds.Any()) { var author = _authorService.GetAuthor(authorId.Value); return _mediaFileService.GetFilesByAuthor(authorId.Value).ConvertAll(f => f.ToResource(author, _upgradableSpecification)); } if (bookIds.Any()) { var result = new List(); foreach (var bookId in bookIds) { var book = _bookService.GetBook(bookId); var bookAuthor = _authorService.GetAuthor(book.AuthorId); result.AddRange(_mediaFileService.GetFilesByBook(book.Id).ConvertAll(f => f.ToResource(bookAuthor, _upgradableSpecification))); } return result; } else { // trackfiles will come back with the author already populated var bookFiles = _mediaFileService.Get(bookFileIds); return bookFiles.ConvertAll(e => MapToResource(e)); } } [RestPutById] public ActionResult SetQuality(BookFileResource bookFileResource) { var bookFile = _mediaFileService.Get(bookFileResource.Id); bookFile.Quality = bookFileResource.Quality; _mediaFileService.Update(bookFile); return Accepted(bookFile.Id); } [HttpPut("editor")] public IActionResult SetQuality([FromBody] BookFileListResource resource) { var bookFiles = _mediaFileService.Get(resource.BookFileIds); foreach (var bookFile in bookFiles) { if (resource.Quality != null) { bookFile.Quality = resource.Quality; } } _mediaFileService.Update(bookFiles); return Accepted(bookFiles.ConvertAll(f => f.ToResource(bookFiles.First().Author.Value, _upgradableSpecification))); } [RestDeleteById] public void DeleteBookFile(int id) { var bookFile = _mediaFileService.Get(id); if (bookFile == null) { throw new NzbDroneClientException(HttpStatusCode.NotFound, "Book file not found"); } if (bookFile.EditionId > 0 && bookFile.Author != null && bookFile.Author.Value != null) { _mediaFileDeletionService.DeleteTrackFile(bookFile.Author.Value, bookFile); } else { _mediaFileDeletionService.DeleteTrackFile(bookFile, "Unmapped_Files"); } } [HttpDelete("bulk")] public IActionResult DeleteBookFiles([FromBody] BookFileListResource resource) { var bookFiles = _mediaFileService.Get(resource.BookFileIds); var author = bookFiles.First().Author.Value; foreach (var bookFile in bookFiles) { _mediaFileDeletionService.DeleteTrackFile(author, bookFile); } return Ok(); } [NonAction] public void Handle(BookFileAddedEvent message) { BroadcastResourceChange(ModelAction.Updated, MapToResource(message.BookFile)); } [NonAction] public void Handle(BookFileDeletedEvent message) { BroadcastResourceChange(ModelAction.Deleted, MapToResource(message.BookFile)); } } }