mirror of
https://github.com/Readarr/Readarr
synced 2025-12-27 18:54:54 +01:00
New: Search for new editions from goodreads when identifying
This commit is contained in:
parent
c1f2ea6c8a
commit
41f5f0f2d4
19 changed files with 149 additions and 95 deletions
|
|
@ -48,7 +48,7 @@ class SelectAuthorModalContentConnector extends Component {
|
|||
id,
|
||||
author,
|
||||
book: undefined,
|
||||
bookReleaseId: undefined,
|
||||
foreignEditionId: undefined,
|
||||
rejections: []
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ class SelectBookModalContentConnector extends Component {
|
|||
this.props.updateInteractiveImportItem({
|
||||
id,
|
||||
book,
|
||||
editionId: undefined,
|
||||
foreignEditionId: undefined,
|
||||
rejections: []
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -34,8 +34,6 @@ class SelectBookRow extends Component {
|
|||
render() {
|
||||
const {
|
||||
title,
|
||||
disambiguation,
|
||||
bookType,
|
||||
releaseDate,
|
||||
statistics,
|
||||
monitored,
|
||||
|
|
@ -48,8 +46,6 @@ class SelectBookRow extends Component {
|
|||
totalBookCount
|
||||
} = statistics;
|
||||
|
||||
const extendedTitle = disambiguation ? `${title} (${disambiguation})` : title;
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
onClick={this.onPress}
|
||||
|
|
@ -69,15 +65,7 @@ class SelectBookRow extends Component {
|
|||
if (name === 'title') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
{extendedTitle}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'bookType') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
{bookType}
|
||||
{title}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
|
@ -121,8 +109,6 @@ class SelectBookRow extends Component {
|
|||
SelectBookRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
disambiguation: PropTypes.string.isRequired,
|
||||
bookType: PropTypes.string.isRequired,
|
||||
releaseDate: PropTypes.string.isRequired,
|
||||
onBookSelect: PropTypes.func.isRequired,
|
||||
statistics: PropTypes.object.isRequired,
|
||||
|
|
|
|||
|
|
@ -20,13 +20,14 @@ class SelectEditionModalContentConnector extends Component {
|
|||
//
|
||||
// Listeners
|
||||
|
||||
onEditionSelect = (bookId, editionId) => {
|
||||
onEditionSelect = (bookId, foreignEditionId) => {
|
||||
console.log(`book: ${bookId} id: ${foreignEditionId} ${typeof foreignEditionId}`);
|
||||
const ids = this.props.importIdsByBook[bookId];
|
||||
|
||||
ids.forEach((id) => {
|
||||
this.props.updateInteractiveImportItem({
|
||||
id,
|
||||
editionId,
|
||||
foreignEditionId,
|
||||
disableReleaseSwitching: true,
|
||||
tracks: [],
|
||||
rejections: []
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class SelectEditionRow extends Component {
|
|||
// Listeners
|
||||
|
||||
onInputChange = ({ name, value }) => {
|
||||
this.props.onEditionSelect(parseInt(name), parseInt(value));
|
||||
this.props.onEditionSelect(parseInt(name), value);
|
||||
}
|
||||
|
||||
//
|
||||
|
|
@ -49,6 +49,9 @@ class SelectEditionRow extends Component {
|
|||
if (bookEdition.isbn13) {
|
||||
extras.push(bookEdition.isbn13);
|
||||
}
|
||||
if (bookEdition.asin) {
|
||||
extras.push(bookEdition.asin);
|
||||
}
|
||||
if (bookEdition.format) {
|
||||
extras.push(bookEdition.format);
|
||||
}
|
||||
|
|
@ -61,7 +64,7 @@ class SelectEditionRow extends Component {
|
|||
}
|
||||
|
||||
return {
|
||||
key: bookEdition.id,
|
||||
key: bookEdition.foreignEditionId,
|
||||
value
|
||||
};
|
||||
});
|
||||
|
|
@ -114,9 +117,9 @@ class SelectEditionRow extends Component {
|
|||
|
||||
SelectEditionRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
matchedEditionId: PropTypes.number.isRequired,
|
||||
matchedEditionId: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
disambiguation: PropTypes.string.isRequired,
|
||||
disambiguation: PropTypes.string,
|
||||
editions: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onEditionSelect: PropTypes.func.isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||
|
|
|
|||
|
|
@ -115,9 +115,9 @@ class InteractiveImportModalContent extends Component {
|
|||
const selectedItems = _.filter(this.props.items, (x) => _.includes(selectedIds, x.id));
|
||||
|
||||
const inconsistent = _(selectedItems)
|
||||
.map((x) => ({ bookId: x.book ? x.book.id : 0, releaseId: x.EditionId }))
|
||||
.map((x) => ({ bookId: x.book ? x.book.id : 0, foreignEditionId: x.ForeignEditionId }))
|
||||
.groupBy('bookId')
|
||||
.mapValues((book) => _(book).groupBy((x) => x.releaseId).values().value().length)
|
||||
.mapValues((book) => _(book).groupBy((x) => x.foreignEditionId).values().value().length)
|
||||
.values()
|
||||
.some((x) => x !== undefined && x > 1);
|
||||
|
||||
|
|
@ -271,6 +271,8 @@ class InteractiveImportModalContent extends Component {
|
|||
|
||||
const selectedIds = this.getSelectedIds();
|
||||
const selectedItem = selectedIds.length ? _.find(items, { id: selectedIds[0] }) : null;
|
||||
const importIdsByBook = _.chain(items).filter((x) => x.book).groupBy((x) => x.book.id).mapValues((x) => x.map((y) => y.id)).value();
|
||||
const editions = _.chain(items).filter((x) => x.book).keyBy((x) => x.book.id).mapValues((x) => ({ matchedEditionId: x.foreignEditionId, book: x.book })).values().value();
|
||||
const errorMessage = getErrorMessage(error, 'Unable to load manual import items');
|
||||
|
||||
const bulkSelectOptions = [
|
||||
|
|
@ -475,8 +477,8 @@ class InteractiveImportModalContent extends Component {
|
|||
|
||||
<SelectEditionModal
|
||||
isOpen={selectModalOpen === EDITION}
|
||||
importIdsByBook={_.chain(items).filter((x) => x.book).groupBy((x) => x.book.id).mapValues((x) => x.map((y) => y.id)).value()}
|
||||
books={_.chain(items).filter((x) => x.book).keyBy((x) => x.book.id).mapValues((x) => ({ matchedEditionId: x.editionId, book: x.book })).values().value()}
|
||||
importIdsByBook={importIdsByBook}
|
||||
books={editions}
|
||||
onModalClose={this.onSelectModalClose}
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ class InteractiveImportModalContentConnector extends Component {
|
|||
const {
|
||||
author,
|
||||
book,
|
||||
editionId,
|
||||
foreignEditionId,
|
||||
quality,
|
||||
disableReleaseSwitching
|
||||
} = item;
|
||||
|
|
@ -151,7 +151,7 @@ class InteractiveImportModalContentConnector extends Component {
|
|||
path: item.path,
|
||||
authorId: author.id,
|
||||
bookId: book.id,
|
||||
editionId,
|
||||
foreignEditionId,
|
||||
quality,
|
||||
downloadId: this.props.downloadId,
|
||||
disableReleaseSwitching
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public void should_not_throw_on_goodreads_exception()
|
|||
}
|
||||
};
|
||||
|
||||
Subject.GetRemoteCandidates(edition).Should().BeEmpty();
|
||||
Subject.GetRemoteCandidates(edition, null).Should().BeEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Equ;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ namespace NzbDrone.Core.MediaFiles.BookImport.Identification
|
|||
public interface ICandidateService
|
||||
{
|
||||
List<CandidateEdition> GetDbCandidatesFromTags(LocalEdition localEdition, IdentificationOverrides idOverrides, bool includeExisting);
|
||||
IEnumerable<CandidateEdition> GetRemoteCandidates(LocalEdition localEdition);
|
||||
IEnumerable<CandidateEdition> GetRemoteCandidates(LocalEdition localEdition, IdentificationOverrides idOverrides);
|
||||
}
|
||||
|
||||
public class CandidateService : ICandidateService
|
||||
|
|
@ -184,8 +184,10 @@ private List<CandidateEdition> GetDbCandidates(LocalEdition localEdition, bool i
|
|||
return candidateReleases;
|
||||
}
|
||||
|
||||
public IEnumerable<CandidateEdition> GetRemoteCandidates(LocalEdition localEdition)
|
||||
public IEnumerable<CandidateEdition> GetRemoteCandidates(LocalEdition localEdition, IdentificationOverrides idOverrides)
|
||||
{
|
||||
// TODO handle edition override
|
||||
|
||||
// Gets candidate book releases from the metadata server.
|
||||
// Will eventually need adding locally if we find a match
|
||||
List<Book> remoteBooks;
|
||||
|
|
@ -210,7 +212,7 @@ public IEnumerable<CandidateEdition> GetRemoteCandidates(LocalEdition localEditi
|
|||
remoteBooks = new List<Book>();
|
||||
}
|
||||
|
||||
foreach (var candidate in ToCandidates(remoteBooks, seenCandidates))
|
||||
foreach (var candidate in ToCandidates(remoteBooks, seenCandidates, idOverrides))
|
||||
{
|
||||
yield return candidate;
|
||||
}
|
||||
|
|
@ -232,7 +234,7 @@ public IEnumerable<CandidateEdition> GetRemoteCandidates(LocalEdition localEditi
|
|||
remoteBooks = new List<Book>();
|
||||
}
|
||||
|
||||
foreach (var candidate in ToCandidates(remoteBooks, seenCandidates))
|
||||
foreach (var candidate in ToCandidates(remoteBooks, seenCandidates, idOverrides))
|
||||
{
|
||||
yield return candidate;
|
||||
}
|
||||
|
|
@ -255,15 +257,18 @@ public IEnumerable<CandidateEdition> GetRemoteCandidates(LocalEdition localEditi
|
|||
remoteBooks = new List<Book>();
|
||||
}
|
||||
|
||||
foreach (var candidate in ToCandidates(remoteBooks, seenCandidates))
|
||||
foreach (var candidate in ToCandidates(remoteBooks, seenCandidates, idOverrides))
|
||||
{
|
||||
yield return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we got an id result, stop
|
||||
if (seenCandidates.Any())
|
||||
// If we got an id result, or any overrides are set, stop
|
||||
if (seenCandidates.Any() ||
|
||||
idOverrides?.Edition != null ||
|
||||
idOverrides?.Book != null ||
|
||||
idOverrides?.Author != null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
|
@ -301,7 +306,7 @@ public IEnumerable<CandidateEdition> GetRemoteCandidates(LocalEdition localEditi
|
|||
remoteBooks = new List<Book>();
|
||||
}
|
||||
|
||||
foreach (var candidate in ToCandidates(remoteBooks, seenCandidates))
|
||||
foreach (var candidate in ToCandidates(remoteBooks, seenCandidates, idOverrides))
|
||||
{
|
||||
yield return candidate;
|
||||
}
|
||||
|
|
@ -324,7 +329,7 @@ public IEnumerable<CandidateEdition> GetRemoteCandidates(LocalEdition localEditi
|
|||
remoteBooks = new List<Book>();
|
||||
}
|
||||
|
||||
foreach (var candidate in ToCandidates(remoteBooks, seenCandidates))
|
||||
foreach (var candidate in ToCandidates(remoteBooks, seenCandidates, idOverrides))
|
||||
{
|
||||
yield return candidate;
|
||||
}
|
||||
|
|
@ -342,14 +347,14 @@ public IEnumerable<CandidateEdition> GetRemoteCandidates(LocalEdition localEditi
|
|||
remoteBooks = new List<Book>();
|
||||
}
|
||||
|
||||
foreach (var candidate in ToCandidates(remoteBooks, seenCandidates))
|
||||
foreach (var candidate in ToCandidates(remoteBooks, seenCandidates, idOverrides))
|
||||
{
|
||||
yield return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<CandidateEdition> ToCandidates(IEnumerable<Book> books, HashSet<string> seenCandidates)
|
||||
private List<CandidateEdition> ToCandidates(IEnumerable<Book> books, HashSet<string> seenCandidates, IdentificationOverrides idOverrides)
|
||||
{
|
||||
var candidates = new List<CandidateEdition>();
|
||||
|
||||
|
|
@ -359,10 +364,11 @@ private List<CandidateEdition> ToCandidates(IEnumerable<Book> books, HashSet<str
|
|||
// by a database lazy load
|
||||
foreach (var edition in book.Editions.Value)
|
||||
{
|
||||
if (!seenCandidates.Contains(edition.ForeignEditionId))
|
||||
edition.Book = book;
|
||||
|
||||
if (!seenCandidates.Contains(edition.ForeignEditionId) && SatisfiesOverride(edition, idOverrides))
|
||||
{
|
||||
seenCandidates.Add(edition.ForeignEditionId);
|
||||
edition.Book = book;
|
||||
candidates.Add(new CandidateEdition
|
||||
{
|
||||
Edition = edition,
|
||||
|
|
@ -374,5 +380,25 @@ private List<CandidateEdition> ToCandidates(IEnumerable<Book> books, HashSet<str
|
|||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
private bool SatisfiesOverride(Edition edition, IdentificationOverrides idOverride)
|
||||
{
|
||||
if (idOverride?.Edition != null)
|
||||
{
|
||||
return edition.ForeignEditionId == idOverride.Edition.ForeignEditionId;
|
||||
}
|
||||
|
||||
if (idOverride?.Book != null)
|
||||
{
|
||||
return edition.Book.Value.ForeignBookId == idOverride.Book.ForeignBookId;
|
||||
}
|
||||
|
||||
if (idOverride?.Author != null)
|
||||
{
|
||||
return edition.Book.Value.Author.Value.ForeignAuthorId == idOverride.Author.ForeignAuthorId;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ private List<LocalBook> ToLocalTrack(IEnumerable<BookFile> trackfiles, LocalEdit
|
|||
private void IdentifyRelease(LocalEdition localBookRelease, IdentificationOverrides idOverrides, ImportDecisionMakerConfig config)
|
||||
{
|
||||
var watch = System.Diagnostics.Stopwatch.StartNew();
|
||||
bool usedRemote = false;
|
||||
|
||||
IEnumerable<CandidateEdition> candidateReleases = _candidateService.GetDbCandidatesFromTags(localBookRelease, idOverrides, config.IncludeExisting);
|
||||
|
||||
|
|
@ -126,14 +127,20 @@ private void IdentifyRelease(LocalEdition localBookRelease, IdentificationOverri
|
|||
|
||||
_logger.Debug($"Retrieved {allLocalTracks.Count} possible tracks in {watch.ElapsedMilliseconds}ms");
|
||||
|
||||
if (!candidateReleases.Any() && config.AddNewAuthors)
|
||||
if (!candidateReleases.Any())
|
||||
{
|
||||
candidateReleases = _candidateService.GetRemoteCandidates(localBookRelease);
|
||||
candidateReleases = _candidateService.GetRemoteCandidates(localBookRelease, idOverrides);
|
||||
if (!config.AddNewAuthors)
|
||||
{
|
||||
candidateReleases = candidateReleases.Where(x => x.Edition.Book.Value.Id > 0);
|
||||
}
|
||||
|
||||
usedRemote = true;
|
||||
}
|
||||
|
||||
if (!candidateReleases.Any())
|
||||
{
|
||||
// can't find any candidates even after fingerprinting
|
||||
// can't find any candidates even after using remote search
|
||||
// populate the overrides and return
|
||||
foreach (var localTrack in localBookRelease.LocalBooks)
|
||||
{
|
||||
|
|
@ -147,6 +154,21 @@ private void IdentifyRelease(LocalEdition localBookRelease, IdentificationOverri
|
|||
|
||||
GetBestRelease(localBookRelease, candidateReleases, allLocalTracks);
|
||||
|
||||
// If the result isn't great and we haven't tried remote candidates, try looking for remote candidates
|
||||
// Goodreads may have a better edition of a local book
|
||||
if (localBookRelease.Distance.NormalizedDistance() > 0.15 && !usedRemote)
|
||||
{
|
||||
_logger.Debug("Match not good enough, trying remote candidates");
|
||||
candidateReleases = _candidateService.GetRemoteCandidates(localBookRelease, idOverrides);
|
||||
|
||||
if (!config.AddNewAuthors)
|
||||
{
|
||||
candidateReleases = candidateReleases.Where(x => x.Edition.Book.Value.Id > 0);
|
||||
}
|
||||
|
||||
GetBestRelease(localBookRelease, candidateReleases, allLocalTracks);
|
||||
}
|
||||
|
||||
_logger.Debug($"Best release found in {watch.ElapsedMilliseconds}ms");
|
||||
|
||||
localBookRelease.PopulateMatch();
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ public class ManualImportFile : IEquatable<ManualImportFile>
|
|||
public string Path { get; set; }
|
||||
public int AuthorId { get; set; }
|
||||
public int BookId { get; set; }
|
||||
public int EditionId { get; set; }
|
||||
public string ForeignEditionId { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
public bool DisableReleaseSwitching { get; set; }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
|
|
@ -15,6 +16,7 @@
|
|||
using NzbDrone.Core.Download.TrackedDownloads;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.MetadataSource;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
|
|
@ -37,6 +39,7 @@ public class ManualImportService : IExecute<ManualImportCommand>, IManualImportS
|
|||
private readonly IAuthorService _authorService;
|
||||
private readonly IBookService _bookService;
|
||||
private readonly IEditionService _editionService;
|
||||
private readonly IProvideBookInfo _bookInfo;
|
||||
private readonly IAudioTagService _audioTagService;
|
||||
private readonly IImportApprovedBooks _importApprovedBooks;
|
||||
private readonly ITrackedDownloadService _trackedDownloadService;
|
||||
|
|
@ -53,6 +56,7 @@ public ManualImportService(IDiskProvider diskProvider,
|
|||
IAuthorService authorService,
|
||||
IBookService bookService,
|
||||
IEditionService editionService,
|
||||
IProvideBookInfo bookInfo,
|
||||
IAudioTagService audioTagService,
|
||||
IImportApprovedBooks importApprovedBooks,
|
||||
ITrackedDownloadService trackedDownloadService,
|
||||
|
|
@ -69,6 +73,7 @@ public ManualImportService(IDiskProvider diskProvider,
|
|||
_authorService = authorService;
|
||||
_bookService = bookService;
|
||||
_editionService = editionService;
|
||||
_bookInfo = bookInfo;
|
||||
_audioTagService = audioTagService;
|
||||
_importApprovedBooks = importApprovedBooks;
|
||||
_trackedDownloadService = trackedDownloadService;
|
||||
|
|
@ -299,7 +304,14 @@ public void Execute(ManualImportCommand message)
|
|||
|
||||
var author = _authorService.GetAuthor(file.AuthorId);
|
||||
var book = _bookService.GetBook(file.BookId);
|
||||
var edition = _editionService.GetEdition(file.EditionId);
|
||||
|
||||
var edition = _editionService.GetEditionByForeignEditionId(file.ForeignEditionId);
|
||||
if (edition == null)
|
||||
{
|
||||
var tuple = _bookInfo.GetBookInfo(file.ForeignEditionId);
|
||||
edition = tuple.Item2.Editions.Value.SingleOrDefault(x => x.ForeignEditionId == file.ForeignEditionId);
|
||||
}
|
||||
|
||||
var fileTrackInfo = _audioTagService.ReadTags(file.Path) ?? new ParsedTrackInfo();
|
||||
var fileInfo = _diskProvider.GetFileInfo(file.Path);
|
||||
|
||||
|
|
@ -355,20 +367,23 @@ public void Execute(ManualImportCommand message)
|
|||
foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList())
|
||||
{
|
||||
var trackedDownload = groupedTrackedDownload.First().TrackedDownload;
|
||||
|
||||
var outputPath = trackedDownload.ImportItem.OutputPath.FullPath;
|
||||
|
||||
if (_diskProvider.FolderExists(outputPath))
|
||||
{
|
||||
if (_downloadedTracksImportService.ShouldDeleteFolder(
|
||||
_diskProvider.GetDirectoryInfo(outputPath),
|
||||
trackedDownload.RemoteBook.Author) && trackedDownload.DownloadItem.CanMoveFiles)
|
||||
if (_downloadedTracksImportService.ShouldDeleteFolder(_diskProvider.GetDirectoryInfo(outputPath)) &&
|
||||
trackedDownload.DownloadItem.CanMoveFiles)
|
||||
{
|
||||
_diskProvider.DeleteFolder(outputPath, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteBook.Books.Count))
|
||||
var importedCount = groupedTrackedDownload.Select(c => c.ImportResult)
|
||||
.Count(c => c.Result == ImportResultType.Imported);
|
||||
var downloadItemCount = Math.Max(1, trackedDownload.RemoteBook?.Books.Count ?? 1);
|
||||
var allItemsImported = importedCount >= downloadItemCount;
|
||||
|
||||
if (allItemsImported)
|
||||
{
|
||||
trackedDownload.State = TrackedDownloadState.Imported;
|
||||
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ public interface IDownloadedBooksImportService
|
|||
{
|
||||
List<ImportResult> ProcessRootFolder(IDirectoryInfo directoryInfo);
|
||||
List<ImportResult> ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Author author = null, DownloadClientItem downloadClientItem = null);
|
||||
bool ShouldDeleteFolder(IDirectoryInfo directoryInfo, Author author);
|
||||
bool ShouldDeleteFolder(IDirectoryInfo directoryInfo);
|
||||
}
|
||||
|
||||
public class DownloadedBooksImportService : IDownloadedBooksImportService
|
||||
|
|
@ -110,7 +110,7 @@ public List<ImportResult> ProcessPath(string path, ImportMode importMode = Impor
|
|||
return new List<ImportResult>();
|
||||
}
|
||||
|
||||
public bool ShouldDeleteFolder(IDirectoryInfo directoryInfo, Author author)
|
||||
public bool ShouldDeleteFolder(IDirectoryInfo directoryInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -238,7 +238,7 @@ private List<ImportResult> ProcessFolder(IDirectoryInfo directoryInfo, ImportMod
|
|||
|
||||
if (importMode == ImportMode.Move &&
|
||||
importResults.Any(i => i.Result == ImportResultType.Imported) &&
|
||||
ShouldDeleteFolder(directoryInfo, author))
|
||||
ShouldDeleteFolder(directoryInfo))
|
||||
{
|
||||
_logger.Debug("Deleting folder after importing valid files");
|
||||
_diskProvider.DeleteFolder(directoryInfo.FullName, true);
|
||||
|
|
|
|||
|
|
@ -421,30 +421,12 @@ public List<Book> SearchForNewBook(string title, string author)
|
|||
|
||||
public List<Book> SearchByIsbn(string isbn)
|
||||
{
|
||||
var result = SearchByField("isbn", isbn);
|
||||
|
||||
// we don't get isbn back in search result, but if only one result assume the query was correct
|
||||
// and add in the searched isbn
|
||||
if (result.Count == 1 && result[0].Editions.Value.Count == 1)
|
||||
{
|
||||
result[0].Editions.Value[0].Isbn13 = isbn;
|
||||
}
|
||||
|
||||
return result;
|
||||
return SearchByField("isbn", isbn, e => e.Isbn13 = isbn);
|
||||
}
|
||||
|
||||
public List<Book> SearchByAsin(string asin)
|
||||
{
|
||||
var result = SearchByField("asin", asin);
|
||||
|
||||
// we don't get isbn back in search result, but if only one result assume the query was correct
|
||||
// and add in the searched isbn
|
||||
if (result.Count == 1 && result[0].Editions.Value.Count == 1)
|
||||
{
|
||||
result[0].Editions.Value[0].Asin = asin;
|
||||
}
|
||||
|
||||
return result;
|
||||
return SearchByField("asin", asin, e => e.Asin = asin);
|
||||
}
|
||||
|
||||
public List<Book> SearchByGoodreadsId(int id)
|
||||
|
|
@ -492,7 +474,7 @@ public List<Book> SearchByGoodreadsId(int id)
|
|||
}
|
||||
}
|
||||
|
||||
public List<Book> SearchByField(string field, string query)
|
||||
public List<Book> SearchByField(string field, string query, Action<Edition> applyData = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -500,9 +482,9 @@ public List<Book> SearchByField(string field, string query)
|
|||
.AddQueryParam("q", query)
|
||||
.Build();
|
||||
|
||||
var result = _cachedHttpClient.Get<List<SearchJsonResource>>(httpRequest, true, TimeSpan.FromDays(5));
|
||||
var response = _cachedHttpClient.Get<List<SearchJsonResource>>(httpRequest, true, TimeSpan.FromDays(5));
|
||||
|
||||
return result.Resource.SelectList(MapJsonSearchResult);
|
||||
return response.Resource.SelectList(x => MapJsonSearchResult(x, response.Resource.Count == 1 ? applyData : null));
|
||||
}
|
||||
catch (HttpException)
|
||||
{
|
||||
|
|
@ -735,7 +717,7 @@ private Book MapSearchResult(WorkResource resource)
|
|||
return book;
|
||||
}
|
||||
|
||||
private Book MapJsonSearchResult(SearchJsonResource resource)
|
||||
private Book MapJsonSearchResult(SearchJsonResource resource, Action<Edition> applyData = null)
|
||||
{
|
||||
var book = _bookService.FindById(resource.WorkId.ToString());
|
||||
var edition = _editionService.GetEditionByForeignEditionId(resource.BookId.ToString());
|
||||
|
|
@ -751,6 +733,11 @@ private Book MapJsonSearchResult(SearchJsonResource resource)
|
|||
PageCount = resource.PageCount,
|
||||
Overview = resource.Description?.Html ?? string.Empty
|
||||
};
|
||||
|
||||
if (applyData != null)
|
||||
{
|
||||
applyData(edition);
|
||||
}
|
||||
}
|
||||
|
||||
edition.Monitored = true;
|
||||
|
|
@ -777,7 +764,19 @@ private Book MapJsonSearchResult(SearchJsonResource resource)
|
|||
};
|
||||
}
|
||||
|
||||
book.Editions = new List<Edition> { edition };
|
||||
if (book.Editions != null)
|
||||
{
|
||||
if (book.Editions.Value.Any())
|
||||
{
|
||||
edition.Monitored = false;
|
||||
}
|
||||
|
||||
book.Editions.Value.Add(edition);
|
||||
}
|
||||
else
|
||||
{
|
||||
book.Editions = new List<Edition> { edition };
|
||||
}
|
||||
|
||||
var authorId = resource.Author.Id.ToString();
|
||||
var author = _authorService.FindById(authorId);
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ public void cutoff_should_have_monitored_items()
|
|||
{
|
||||
EnsureProfileCutoff(1, Quality.AZW3);
|
||||
var author = EnsureAuthor("14586394", "43765115", "Andrew Hunter Murray", true);
|
||||
EnsureBookFile(author, 1, 1, Quality.MOBI);
|
||||
EnsureBookFile(author, 1, "43765115", Quality.MOBI);
|
||||
|
||||
var result = WantedCutoffUnmet.GetPaged(0, 15, "releaseDate", "desc");
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ public void cutoff_should_not_have_unmonitored_items()
|
|||
{
|
||||
EnsureProfileCutoff(1, Quality.AZW3);
|
||||
var author = EnsureAuthor("14586394", "43765115", "Andrew Hunter Murray", false);
|
||||
EnsureBookFile(author, 1, 1, Quality.MOBI);
|
||||
EnsureBookFile(author, 1, "43765115", Quality.MOBI);
|
||||
|
||||
var result = WantedCutoffUnmet.GetPaged(0, 15, "releaseDate", "desc");
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ public void cutoff_should_have_author()
|
|||
{
|
||||
EnsureProfileCutoff(1, Quality.AZW3);
|
||||
var author = EnsureAuthor("14586394", "43765115", "Andrew Hunter Murray", true);
|
||||
EnsureBookFile(author, 1, 1, Quality.MOBI);
|
||||
EnsureBookFile(author, 1, "43765115", Quality.MOBI);
|
||||
|
||||
var result = WantedCutoffUnmet.GetPaged(0, 15, "releaseDate", "desc");
|
||||
|
||||
|
|
@ -126,7 +126,7 @@ public void cutoff_should_have_unmonitored_items()
|
|||
{
|
||||
EnsureProfileCutoff(1, Quality.AZW3);
|
||||
var author = EnsureAuthor("14586394", "43765115", "Andrew Hunter Murray", false);
|
||||
EnsureBookFile(author, 1, 1, Quality.MOBI);
|
||||
EnsureBookFile(author, 1, "43765115", Quality.MOBI);
|
||||
|
||||
var result = WantedCutoffUnmet.GetPaged(0, 15, "releaseDate", "desc", "monitored", "false");
|
||||
|
||||
|
|
|
|||
|
|
@ -234,13 +234,13 @@ public static void WaitForCompletion(Func<bool> predicate, int timeout = 10000,
|
|||
Assert.Fail("Timed on wait");
|
||||
}
|
||||
|
||||
public AuthorResource EnsureAuthor(string authorId, string goodreadsBookId, string authorName, bool? monitored = null)
|
||||
public AuthorResource EnsureAuthor(string authorId, string goodreadsEditionId, string authorName, bool? monitored = null)
|
||||
{
|
||||
var result = Author.All().FirstOrDefault(v => v.ForeignAuthorId == authorId);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
var lookup = Author.Lookup("readarr:" + goodreadsBookId);
|
||||
var lookup = Author.Lookup("readarr:" + goodreadsEditionId);
|
||||
var author = lookup.First();
|
||||
author.QualityProfileId = 1;
|
||||
author.MetadataProfileId = 1;
|
||||
|
|
@ -291,9 +291,9 @@ public void EnsureNoAuthor(string readarrId, string authorTitle)
|
|||
}
|
||||
}
|
||||
|
||||
public void EnsureBookFile(AuthorResource author, int bookId, int editionId, Quality quality)
|
||||
public void EnsureBookFile(AuthorResource author, int bookId, string foreignEditionId, Quality quality)
|
||||
{
|
||||
var result = Books.GetBooksInAuthor(author.Id).Single(v => v.Id == editionId);
|
||||
var result = Books.GetBooksInAuthor(author.Id).Single(v => v.Id == bookId);
|
||||
|
||||
// if (result.BookFile == null)
|
||||
if (true)
|
||||
|
|
@ -312,14 +312,14 @@ public void EnsureBookFile(AuthorResource author, int bookId, int editionId, Qua
|
|||
Path = path,
|
||||
AuthorId = author.Id,
|
||||
BookId = bookId,
|
||||
EditionId = editionId,
|
||||
ForeignEditionId = foreignEditionId,
|
||||
Quality = new QualityModel(quality)
|
||||
}
|
||||
}
|
||||
});
|
||||
Commands.WaitAll();
|
||||
|
||||
var track = Books.GetBooksInAuthor(author.Id).Single(x => x.Id == editionId);
|
||||
var track = Books.GetBooksInAuthor(author.Id).Single(x => x.Id == bookId);
|
||||
|
||||
// track.BookFileId.Should().NotBe(0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ private List<ManualImportResource> UpdateImportItems(List<ManualImportResource>
|
|||
Size = resource.Size,
|
||||
Author = resource.Author == null ? null : _authorService.GetAuthor(resource.Author.Id),
|
||||
Book = resource.Book == null ? null : _bookService.GetBook(resource.Book.Id),
|
||||
Edition = resource.EditionId == 0 ? null : _editionService.GetEdition(resource.EditionId),
|
||||
Edition = resource.ForeignEditionId == null ? null : _editionService.GetEditionByForeignEditionId(resource.ForeignEditionId),
|
||||
Quality = resource.Quality,
|
||||
DownloadId = resource.DownloadId,
|
||||
AdditionalFile = resource.AdditionalFile,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public class ManualImportResource : RestResource
|
|||
public long Size { get; set; }
|
||||
public AuthorResource Author { get; set; }
|
||||
public BookResource Book { get; set; }
|
||||
public int EditionId { get; set; }
|
||||
public string ForeignEditionId { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public int QualityWeight { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
|
|
@ -45,7 +45,7 @@ public static ManualImportResource ToResource(this ManualImportItem model)
|
|||
Size = model.Size,
|
||||
Author = model.Author.ToResource(),
|
||||
Book = model.Book.ToResource(),
|
||||
EditionId = model.Edition?.Id ?? 0,
|
||||
ForeignEditionId = model.Edition?.ForeignEditionId,
|
||||
Quality = model.Quality,
|
||||
|
||||
//QualityWeight
|
||||
|
|
|
|||
Loading…
Reference in a new issue