New: Goodreads series import list

pull/1319/head
ta264 3 years ago
parent cb2bd0273f
commit fb7ec5c61e

@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Net;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.ImportLists.Goodreads
{
public class GoodreadsSeriesImportList : ImportListBase<GoodreadsSeriesImportListSettings>
{
private readonly IProvideSeriesInfo _seriesInfo;
public override string Name => "Goodreads Series";
public override ImportListType ListType => ImportListType.Goodreads;
public GoodreadsSeriesImportList(IProvideSeriesInfo seriesInfo,
IImportListStatusService importListStatusService,
IConfigService configService,
IParsingService parsingService,
Logger logger)
: base(importListStatusService, configService, parsingService, logger)
{
_seriesInfo = seriesInfo;
}
public override IList<ImportListItemInfo> Fetch()
{
var result = new List<ImportListItemInfo>();
try
{
var series = _seriesInfo.GetSeriesInfo(Settings.SeriesId);
foreach (var work in series.Works)
{
result.Add(new ImportListItemInfo
{
BookGoodreadsId = work.Id.ToString(),
Book = work.OriginalTitle,
EditionGoodreadsId = work.BestBook.Id.ToString(),
Author = work.BestBook.AuthorName,
AuthorGoodreadsId = work.BestBook.AuthorId.ToString()
});
}
_importListStatusService.RecordSuccess(Definition.Id);
}
catch
{
_importListStatusService.RecordFailure(Definition.Id);
}
return CleanupListItems(result);
}
protected override void Test(List<ValidationFailure> failures)
{
failures.AddIfNotNull(TestConnection());
}
private ValidationFailure TestConnection()
{
try
{
_seriesInfo.GetSeriesInfo(Settings.SeriesId);
return null;
}
catch (HttpException e)
{
_logger.Warn(e, "Goodreads API Error");
if (e.Response.StatusCode == HttpStatusCode.NotFound)
{
return new ValidationFailure(nameof(Settings.SeriesId), $"Series {Settings.SeriesId} not found");
}
return new ValidationFailure(nameof(Settings.SeriesId), $"Could not get series data");
}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to connect to Goodreads");
return new ValidationFailure(string.Empty, "Unable to connect to import list, check the log for more details");
}
}
}
}

@ -0,0 +1,34 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.ImportLists.Goodreads
{
public class GoodreadsSeriesImportListValidator : AbstractValidator<GoodreadsSeriesImportListSettings>
{
public GoodreadsSeriesImportListValidator()
{
RuleFor(c => c.SeriesId).GreaterThan(0);
}
}
public class GoodreadsSeriesImportListSettings : IImportListSettings
{
private static readonly GoodreadsSeriesImportListValidator Validator = new ();
public GoodreadsSeriesImportListSettings()
{
BaseUrl = "www.goodreads.com";
}
public string BaseUrl { get; set; }
[FieldDefinition(0, Label = "Series ID", HelpText = "Goodreads series ID")]
public int SeriesId { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

@ -17,7 +17,7 @@ using NzbDrone.Core.Parser;
namespace NzbDrone.Core.MetadataSource.Goodreads namespace NzbDrone.Core.MetadataSource.Goodreads
{ {
public class GoodreadsProxy : IProvideBookInfo public class GoodreadsProxy : IProvideBookInfo, IProvideSeriesInfo
{ {
private static readonly RegexReplace FullSizeImageRegex = new RegexReplace(@"\._[SU][XY]\d+_.jpg$", private static readonly RegexReplace FullSizeImageRegex = new RegexReplace(@"\._[SU][XY]\d+_.jpg$",
".jpg", ".jpg",
@ -307,8 +307,35 @@ namespace NzbDrone.Core.MetadataSource.Goodreads
return result; return result;
} }
public SeriesResource GetSeriesInfo(int foreignSeriesId, bool useCache = true)
{ {
_logger.Debug("Getting Series with GoodreadsId of {0}", foreignSeriesId);
var httpRequest = _requestBuilder.Create()
.SetSegment("route", $"series/{foreignSeriesId}")
.AddQueryParam("format", "xml")
.Build();
httpRequest.AllowAutoRedirect = true;
httpRequest.SuppressHttpError = true;
var httpResponse = _cachedHttpClient.Get(httpRequest, useCache, TimeSpan.FromDays(7));
if (httpResponse.HasHttpError)
{
if (httpResponse.StatusCode == HttpStatusCode.BadRequest)
{
throw new BadRequestException(foreignSeriesId.ToString());
}
else
{
throw new HttpException(httpRequest, httpResponse);
}
}
var resource = httpResponse.Deserialize<ShowSeriesResource>();
return resource.Series;
} }
private bool TryGetBookInfo(string foreignEditionId, bool useCache, out Tuple<string, Book, List<AuthorMetadata>> result) private bool TryGetBookInfo(string foreignEditionId, bool useCache, out Tuple<string, Book, List<AuthorMetadata>> result)

@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml.Linq;
namespace NzbDrone.Core.MetadataSource.Goodreads
{
/// <summary>
/// This class models the best book in a work, as defined by the Goodreads API.
/// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public sealed class ShowSeriesResource : GoodreadsResource
{
public override string ElementName => "series";
public SeriesResource Series { get; private set; }
public override void Parse(XElement element)
{
Series = new SeriesResource();
Series.Parse(element);
Series.Works = element.ParseChildren<WorkResource>("series_works", "series_work");
}
}
}

@ -0,0 +1,9 @@
using NzbDrone.Core.MetadataSource.Goodreads;
namespace NzbDrone.Core.MetadataSource
{
public interface IProvideSeriesInfo
{
SeriesResource GetSeriesInfo(int id, bool useCache = true);
}
}
Loading…
Cancel
Save