Method, Variable, Class Renames in Readarr.Api

This commit is contained in:
Qstick
2020-05-15 17:22:44 -04:00
committed by ta264
parent 8080d375d0
commit ee4e44b81a
91 changed files with 945 additions and 948 deletions
@@ -0,0 +1,9 @@
namespace Readarr.Api.V1.Author
{
public class AlternateTitleResource
{
public string Title { get; set; }
public int? SeasonNumber { get; set; }
public int? SceneSeasonNumber { get; set; }
}
}
@@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace Readarr.Api.V1.Author
{
public class AuthorEditorDeleteResource
{
public List<int> AuthorIds { get; set; }
public bool DeleteFiles { get; set; }
}
}
@@ -0,0 +1,105 @@
using System.Collections.Generic;
using System.Linq;
using Nancy;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Books;
using NzbDrone.Core.Books.Commands;
using NzbDrone.Core.Messaging.Commands;
using Readarr.Http.Extensions;
namespace Readarr.Api.V1.Author
{
public class AuthorEditorModule : ReadarrV1Module
{
private readonly IAuthorService _authorService;
private readonly IManageCommandQueue _commandQueueManager;
public AuthorEditorModule(IAuthorService authorService, IManageCommandQueue commandQueueManager)
: base("/author/editor")
{
_authorService = authorService;
_commandQueueManager = commandQueueManager;
Put("/", author => SaveAll());
Delete("/", author => DeleteAuthor());
}
private object SaveAll()
{
var resource = Request.Body.FromJson<AuthorEditorResource>();
var authorsToUpdate = _authorService.GetAuthors(resource.AuthorIds);
var authorsToMove = new List<BulkMoveAuthor>();
foreach (var author in authorsToUpdate)
{
if (resource.Monitored.HasValue)
{
author.Monitored = resource.Monitored.Value;
}
if (resource.QualityProfileId.HasValue)
{
author.QualityProfileId = resource.QualityProfileId.Value;
}
if (resource.MetadataProfileId.HasValue)
{
author.MetadataProfileId = resource.MetadataProfileId.Value;
}
if (resource.RootFolderPath.IsNotNullOrWhiteSpace())
{
author.RootFolderPath = resource.RootFolderPath;
authorsToMove.Add(new BulkMoveAuthor
{
AuthorId = author.Id,
SourcePath = author.Path
});
}
if (resource.Tags != null)
{
var newTags = resource.Tags;
var applyTags = resource.ApplyTags;
switch (applyTags)
{
case ApplyTags.Add:
newTags.ForEach(t => author.Tags.Add(t));
break;
case ApplyTags.Remove:
newTags.ForEach(t => author.Tags.Remove(t));
break;
case ApplyTags.Replace:
author.Tags = new HashSet<int>(newTags);
break;
}
}
}
if (resource.MoveFiles && authorsToMove.Any())
{
_commandQueueManager.Push(new BulkMoveAuthorCommand
{
DestinationRootFolder = resource.RootFolderPath,
Author = authorsToMove
});
}
return ResponseWithCode(_authorService.UpdateAuthors(authorsToUpdate, !resource.MoveFiles)
.ToResource(),
HttpStatusCode.Accepted);
}
private object DeleteAuthor()
{
var resource = Request.Body.FromJson<AuthorEditorResource>();
foreach (var authorId in resource.AuthorIds)
{
_authorService.DeleteAuthor(authorId, false);
}
return new object();
}
}
}
@@ -0,0 +1,23 @@
using System.Collections.Generic;
namespace Readarr.Api.V1.Author
{
public class AuthorEditorResource
{
public List<int> AuthorIds { get; set; }
public bool? Monitored { get; set; }
public int? QualityProfileId { get; set; }
public int? MetadataProfileId { get; set; }
public string RootFolderPath { get; set; }
public List<int> Tags { get; set; }
public ApplyTags ApplyTags { get; set; }
public bool MoveFiles { get; set; }
}
public enum ApplyTags
{
Add,
Remove,
Replace
}
}
@@ -0,0 +1,28 @@
using System.Collections.Generic;
using Nancy;
using NzbDrone.Core.Books;
using Readarr.Http;
using Readarr.Http.Extensions;
namespace Readarr.Api.V1.Author
{
public class AuthorImportModule : ReadarrRestModule<AuthorResource>
{
private readonly IAddAuthorService _addAuthorService;
public AuthorImportModule(IAddAuthorService addAuthorService)
: base("/author/import")
{
_addAuthorService = addAuthorService;
Post("/", x => Import());
}
private object Import()
{
var resource = Request.Body.FromJson<List<AuthorResource>>();
var newAuthors = resource.ToModel();
return _addAuthorService.AddAuthors(newAuthors).ToResource();
}
}
}
@@ -0,0 +1,42 @@
using System.Collections.Generic;
using System.Linq;
using Nancy;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MetadataSource;
using Readarr.Http;
namespace Readarr.Api.V1.Author
{
public class AuthorLookupModule : ReadarrRestModule<AuthorResource>
{
private readonly ISearchForNewAuthor _searchProxy;
public AuthorLookupModule(ISearchForNewAuthor searchProxy)
: base("/author/lookup")
{
_searchProxy = searchProxy;
Get("/", x => Search());
}
private object Search()
{
var searchResults = _searchProxy.SearchForNewAuthor((string)Request.Query.term);
return MapToResource(searchResults).ToList();
}
private static IEnumerable<AuthorResource> MapToResource(IEnumerable<NzbDrone.Core.Books.Author> author)
{
foreach (var currentAuthor in author)
{
var resource = currentAuthor.ToResource();
var poster = currentAuthor.Metadata.Value.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
if (poster != null)
{
resource.RemotePoster = poster.Url;
}
yield return resource;
}
}
}
}
+287
View File
@@ -0,0 +1,287 @@
using System.Collections.Generic;
using System.Linq;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.AuthorStats;
using NzbDrone.Core.Books;
using NzbDrone.Core.Books.Commands;
using NzbDrone.Core.Books.Events;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Validation;
using NzbDrone.Core.Validation.Paths;
using NzbDrone.SignalR;
using Readarr.Http;
using Readarr.Http.Extensions;
namespace Readarr.Api.V1.Author
{
public class AuthorModule : ReadarrRestModuleWithSignalR<AuthorResource, NzbDrone.Core.Books.Author>,
IHandle<BookImportedEvent>,
IHandle<BookEditedEvent>,
IHandle<BookFileDeletedEvent>,
IHandle<AuthorUpdatedEvent>,
IHandle<AuthorEditedEvent>,
IHandle<AuthorDeletedEvent>,
IHandle<AuthorRenamedEvent>,
IHandle<MediaCoversUpdatedEvent>
{
private readonly IAuthorService _authorService;
private readonly IBookService _bookService;
private readonly IAddAuthorService _addAuthorService;
private readonly IAuthorStatisticsService _authorStatisticsService;
private readonly IMapCoversToLocal _coverMapper;
private readonly IManageCommandQueue _commandQueueManager;
private readonly IRootFolderService _rootFolderService;
public AuthorModule(IBroadcastSignalRMessage signalRBroadcaster,
IAuthorService authorService,
IBookService bookService,
IAddAuthorService addAuthorService,
IAuthorStatisticsService authorStatisticsService,
IMapCoversToLocal coverMapper,
IManageCommandQueue commandQueueManager,
IRootFolderService rootFolderService,
RootFolderValidator rootFolderValidator,
MappedNetworkDriveValidator mappedNetworkDriveValidator,
AuthorPathValidator authorPathValidator,
AuthorExistsValidator authorExistsValidator,
AuthorAncestorValidator authorAncestorValidator,
SystemFolderValidator systemFolderValidator,
QualityProfileExistsValidator qualityProfileExistsValidator,
MetadataProfileExistsValidator metadataProfileExistsValidator)
: base(signalRBroadcaster)
{
_authorService = authorService;
_bookService = bookService;
_addAuthorService = addAuthorService;
_authorStatisticsService = authorStatisticsService;
_coverMapper = coverMapper;
_commandQueueManager = commandQueueManager;
_rootFolderService = rootFolderService;
GetResourceAll = AllAuthors;
GetResourceById = GetAuthor;
CreateResource = AddAuthor;
UpdateResource = UpdateAuthor;
DeleteResource = DeleteAuthor;
Http.Validation.RuleBuilderExtensions.ValidId(SharedValidator.RuleFor(s => s.QualityProfileId));
Http.Validation.RuleBuilderExtensions.ValidId(SharedValidator.RuleFor(s => s.MetadataProfileId));
SharedValidator.RuleFor(s => s.Path)
.Cascade(CascadeMode.StopOnFirstFailure)
.IsValidPath()
.SetValidator(rootFolderValidator)
.SetValidator(mappedNetworkDriveValidator)
.SetValidator(authorPathValidator)
.SetValidator(authorAncestorValidator)
.SetValidator(systemFolderValidator)
.When(s => !s.Path.IsNullOrWhiteSpace());
SharedValidator.RuleFor(s => s.QualityProfileId).SetValidator(qualityProfileExistsValidator);
SharedValidator.RuleFor(s => s.MetadataProfileId).SetValidator(metadataProfileExistsValidator);
PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace());
PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => s.Path.IsNullOrWhiteSpace());
PostValidator.RuleFor(s => s.AuthorName).NotEmpty();
PostValidator.RuleFor(s => s.ForeignAuthorId).NotEmpty().SetValidator(authorExistsValidator);
PutValidator.RuleFor(s => s.Path).IsValidPath();
}
private AuthorResource GetAuthor(int id)
{
var author = _authorService.GetAuthor(id);
return GetArtistResource(author);
}
private AuthorResource GetArtistResource(NzbDrone.Core.Books.Author author)
{
if (author == null)
{
return null;
}
var resource = author.ToResource();
MapCoversToLocal(resource);
FetchAndLinkArtistStatistics(resource);
LinkNextPreviousAlbums(resource);
//PopulateAlternateTitles(resource);
LinkRootFolderPath(resource);
return resource;
}
private List<AuthorResource> AllAuthors()
{
var authorStats = _authorStatisticsService.AuthorStatistics();
var authorResources = _authorService.GetAllAuthors().ToResource();
MapCoversToLocal(authorResources.ToArray());
LinkNextPreviousAlbums(authorResources.ToArray());
LinkArtistStatistics(authorResources, authorStats);
//PopulateAlternateTitles(seriesResources);
return authorResources;
}
private int AddAuthor(AuthorResource authorResource)
{
var author = _addAuthorService.AddAuthor(authorResource.ToModel());
return author.Id;
}
private void UpdateAuthor(AuthorResource authorResource)
{
var moveFiles = Request.GetBooleanQueryParameter("moveFiles");
var author = _authorService.GetAuthor(authorResource.Id);
if (moveFiles)
{
var sourcePath = author.Path;
var destinationPath = authorResource.Path;
_commandQueueManager.Push(new MoveAuthorCommand
{
AuthorId = author.Id,
SourcePath = sourcePath,
DestinationPath = destinationPath,
Trigger = CommandTrigger.Manual
});
}
var model = authorResource.ToModel(author);
_authorService.UpdateAuthor(model);
BroadcastResourceChange(ModelAction.Updated, authorResource);
}
private void DeleteAuthor(int id)
{
var deleteFiles = Request.GetBooleanQueryParameter("deleteFiles");
var addImportListExclusion = Request.GetBooleanQueryParameter("addImportListExclusion");
_authorService.DeleteAuthor(id, deleteFiles, addImportListExclusion);
}
private void MapCoversToLocal(params AuthorResource[] authors)
{
foreach (var authorResource in authors)
{
_coverMapper.ConvertToLocalUrls(authorResource.Id, MediaCoverEntity.Author, authorResource.Images);
}
}
private void LinkNextPreviousAlbums(params AuthorResource[] authors)
{
var nextBooks = _bookService.GetNextBooksByAuthorMetadataId(authors.Select(x => x.AuthorMetadataId));
var lastBooks = _bookService.GetLastBooksByAuthorMetadataId(authors.Select(x => x.AuthorMetadataId));
foreach (var authorResource in authors)
{
authorResource.NextBook = nextBooks.FirstOrDefault(x => x.AuthorMetadataId == authorResource.AuthorMetadataId);
authorResource.LastBook = lastBooks.FirstOrDefault(x => x.AuthorMetadataId == authorResource.AuthorMetadataId);
}
}
private void FetchAndLinkArtistStatistics(AuthorResource resource)
{
LinkArtistStatistics(resource, _authorStatisticsService.AuthorStatistics(resource.Id));
}
private void LinkArtistStatistics(List<AuthorResource> resources, List<AuthorStatistics> authorStatistics)
{
foreach (var author in resources)
{
var stats = authorStatistics.SingleOrDefault(ss => ss.AuthorId == author.Id);
if (stats == null)
{
continue;
}
LinkArtistStatistics(author, stats);
}
}
private void LinkArtistStatistics(AuthorResource resource, AuthorStatistics authorStatistics)
{
resource.Statistics = authorStatistics.ToResource();
}
//private void PopulateAlternateTitles(List<ArtistResource> resources)
//{
// foreach (var resource in resources)
// {
// PopulateAlternateTitles(resource);
// }
//}
//private void PopulateAlternateTitles(ArtistResource resource)
//{
// var mappings = _sceneMappingService.FindByTvdbId(resource.TvdbId);
// if (mappings == null) return;
// resource.AlternateTitles = mappings.Select(v => new AlternateTitleResource { Title = v.Title, SeasonNumber = v.SeasonNumber, SceneSeasonNumber = v.SceneSeasonNumber }).ToList();
//}
private void LinkRootFolderPath(AuthorResource resource)
{
resource.RootFolderPath = _rootFolderService.GetBestRootFolderPath(resource.Path);
}
public void Handle(BookImportedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, GetArtistResource(message.Author));
}
public void Handle(BookEditedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, GetArtistResource(message.Book.Author.Value));
}
public void Handle(BookFileDeletedEvent message)
{
if (message.Reason == DeleteMediaFileReason.Upgrade)
{
return;
}
BroadcastResourceChange(ModelAction.Updated, GetArtistResource(message.BookFile.Author.Value));
}
public void Handle(AuthorUpdatedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, GetArtistResource(message.Author));
}
public void Handle(AuthorEditedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, GetArtistResource(message.Author));
}
public void Handle(AuthorDeletedEvent message)
{
BroadcastResourceChange(ModelAction.Deleted, message.Author.ToResource());
}
public void Handle(AuthorRenamedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, message.Author.Id);
}
public void Handle(MediaCoversUpdatedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, GetArtistResource(message.Author));
}
}
}
+171
View File
@@ -0,0 +1,171 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Books;
using NzbDrone.Core.MediaCover;
using Readarr.Http.REST;
namespace Readarr.Api.V1.Author
{
public class AuthorResource : RestResource
{
//Todo: Sorters should be done completely on the client
//Todo: Is there an easy way to keep IgnoreArticlesWhenSorting in sync between, Series, History, Missing?
//Todo: We should get the entire Profile instead of ID and Name separately
[JsonIgnore]
public int AuthorMetadataId { get; set; }
public AuthorStatusType Status { get; set; }
public bool Ended => Status == AuthorStatusType.Ended;
public string AuthorName { get; set; }
public string ForeignAuthorId { get; set; }
public int GoodreadsId { get; set; }
public string TitleSlug { get; set; }
public string Overview { get; set; }
public string AuthorType { get; set; }
public string Disambiguation { get; set; }
public List<Links> Links { get; set; }
public Book NextBook { get; set; }
public Book LastBook { get; set; }
public List<MediaCover> Images { get; set; }
public string RemotePoster { get; set; }
//View & Edit
public string Path { get; set; }
public int QualityProfileId { get; set; }
public int MetadataProfileId { get; set; }
//Editing Only
public bool Monitored { get; set; }
public string RootFolderPath { get; set; }
public List<string> Genres { get; set; }
public string CleanName { get; set; }
public string SortName { get; set; }
public HashSet<int> Tags { get; set; }
public DateTime Added { get; set; }
public AddAuthorOptions AddOptions { get; set; }
public Ratings Ratings { get; set; }
public AuthorStatisticsResource Statistics { get; set; }
}
public static class AuthorResourceMapper
{
public static AuthorResource ToResource(this NzbDrone.Core.Books.Author model)
{
if (model == null)
{
return null;
}
return new AuthorResource
{
Id = model.Id,
AuthorMetadataId = model.AuthorMetadataId,
AuthorName = model.Name,
//AlternateTitles
SortName = model.SortName,
Status = model.Metadata.Value.Status,
Overview = model.Metadata.Value.Overview,
AuthorType = model.Metadata.Value.Type,
Disambiguation = model.Metadata.Value.Disambiguation,
Images = model.Metadata.Value.Images.JsonClone(),
Path = model.Path,
QualityProfileId = model.QualityProfileId,
MetadataProfileId = model.MetadataProfileId,
Links = model.Metadata.Value.Links,
Monitored = model.Monitored,
CleanName = model.CleanName,
ForeignAuthorId = model.Metadata.Value.ForeignAuthorId,
GoodreadsId = model.Metadata.Value.GoodreadsId,
TitleSlug = model.Metadata.Value.TitleSlug,
// Root folder path is now calculated from the artist path
// RootFolderPath = model.RootFolderPath,
Genres = model.Metadata.Value.Genres,
Tags = model.Tags,
Added = model.Added,
AddOptions = model.AddOptions,
Ratings = model.Metadata.Value.Ratings,
Statistics = new AuthorStatisticsResource()
};
}
public static NzbDrone.Core.Books.Author ToModel(this AuthorResource resource)
{
if (resource == null)
{
return null;
}
return new NzbDrone.Core.Books.Author
{
Id = resource.Id,
Metadata = new NzbDrone.Core.Books.AuthorMetadata
{
ForeignAuthorId = resource.ForeignAuthorId,
GoodreadsId = resource.GoodreadsId,
TitleSlug = resource.TitleSlug,
Name = resource.AuthorName,
Status = resource.Status,
Overview = resource.Overview,
Links = resource.Links,
Images = resource.Images,
Genres = resource.Genres,
Ratings = resource.Ratings,
Type = resource.AuthorType
},
//AlternateTitles
SortName = resource.SortName,
Path = resource.Path,
QualityProfileId = resource.QualityProfileId,
MetadataProfileId = resource.MetadataProfileId,
Monitored = resource.Monitored,
CleanName = resource.CleanName,
RootFolderPath = resource.RootFolderPath,
Tags = resource.Tags,
Added = resource.Added,
AddOptions = resource.AddOptions,
};
}
public static NzbDrone.Core.Books.Author ToModel(this AuthorResource resource, NzbDrone.Core.Books.Author author)
{
var updatedAuthor = resource.ToModel();
author.ApplyChanges(updatedAuthor);
return author;
}
public static List<AuthorResource> ToResource(this IEnumerable<NzbDrone.Core.Books.Author> author)
{
return author.Select(ToResource).ToList();
}
public static List<NzbDrone.Core.Books.Author> ToModel(this IEnumerable<AuthorResource> resources)
{
return resources.Select(ToModel).ToList();
}
}
}
@@ -0,0 +1,43 @@
using NzbDrone.Core.AuthorStats;
namespace Readarr.Api.V1.Author
{
public class AuthorStatisticsResource
{
public int BookCount { get; set; }
public int BookFileCount { get; set; }
public int TotalBookCount { get; set; }
public long SizeOnDisk { get; set; }
public decimal PercentOfBooks
{
get
{
if (BookCount == 0)
{
return 0;
}
return BookFileCount / (decimal)BookCount * 100;
}
}
}
public static class AuthorStatisticsResourceMapper
{
public static AuthorStatisticsResource ToResource(this AuthorStatistics model)
{
if (model == null)
{
return null;
}
return new AuthorStatisticsResource
{
BookCount = model.BookCount,
BookFileCount = model.BookFileCount,
SizeOnDisk = model.SizeOnDisk
};
}
}
}