|
|
@ -1,7 +1,10 @@
|
|
|
|
|
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Linq;
|
|
|
|
|
|
|
|
using NzbDrone.Common.Extensions;
|
|
|
|
using NzbDrone.Core.Books.Events;
|
|
|
|
using NzbDrone.Core.Books.Events;
|
|
|
|
using NzbDrone.Core.Messaging.Events;
|
|
|
|
using NzbDrone.Core.Messaging.Events;
|
|
|
|
|
|
|
|
using NzbDrone.Core.Parser;
|
|
|
|
|
|
|
|
|
|
|
|
namespace NzbDrone.Core.Books
|
|
|
|
namespace NzbDrone.Core.Books
|
|
|
|
{
|
|
|
|
{
|
|
|
@ -16,6 +19,9 @@ namespace NzbDrone.Core.Books
|
|
|
|
List<Edition> GetEditionsForRefresh(int bookId, IEnumerable<string> foreignEditionIds);
|
|
|
|
List<Edition> GetEditionsForRefresh(int bookId, IEnumerable<string> foreignEditionIds);
|
|
|
|
List<Edition> GetEditionsByBook(int bookId);
|
|
|
|
List<Edition> GetEditionsByBook(int bookId);
|
|
|
|
List<Edition> GetEditionsByAuthor(int authorId);
|
|
|
|
List<Edition> GetEditionsByAuthor(int authorId);
|
|
|
|
|
|
|
|
Edition FindByTitle(int authorMetadataId, string title);
|
|
|
|
|
|
|
|
Edition FindByTitleInexact(int authorMetadataId, string title);
|
|
|
|
|
|
|
|
List<Edition> GetCandidates(int authorMetadataId, string title);
|
|
|
|
List<Edition> SetMonitored(Edition edition);
|
|
|
|
List<Edition> SetMonitored(Edition edition);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -81,6 +87,40 @@ namespace NzbDrone.Core.Books
|
|
|
|
return _editionRepository.FindByAuthor(authorId);
|
|
|
|
return _editionRepository.FindByAuthor(authorId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Edition FindByTitle(int authorMetadataId, string title)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return _editionRepository.FindByTitle(authorMetadataId, title);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Edition FindByTitleInexact(int authorMetadataId, string title)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var books = _editionRepository.FindByAuthorMetadataId(authorMetadataId, true);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var func in EditionScoringFunctions(title))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var results = FindByStringInexact(books, func.Item1, func.Item2);
|
|
|
|
|
|
|
|
if (results.Count == 1)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return results[0];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public List<Edition> GetCandidates(int authorMetadataId, string title)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var books = _editionRepository.FindByAuthorMetadataId(authorMetadataId, true);
|
|
|
|
|
|
|
|
var output = new List<Edition>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var func in EditionScoringFunctions(title))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
output.AddRange(FindByStringInexact(books, func.Item1, func.Item2));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return output.DistinctBy(x => x.Id).ToList();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public List<Edition> SetMonitored(Edition edition)
|
|
|
|
public List<Edition> SetMonitored(Edition edition)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
return _editionRepository.SetMonitored(edition);
|
|
|
|
return _editionRepository.SetMonitored(edition);
|
|
|
@ -91,5 +131,40 @@ namespace NzbDrone.Core.Books
|
|
|
|
var editions = GetEditionsByBook(message.Book.Id);
|
|
|
|
var editions = GetEditionsByBook(message.Book.Id);
|
|
|
|
DeleteMany(editions);
|
|
|
|
DeleteMany(editions);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private List<Tuple<Func<Edition, string, double>, string>> EditionScoringFunctions(string title)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Func<Func<Edition, string, double>, string, Tuple<Func<Edition, string, double>, string>> tc = Tuple.Create;
|
|
|
|
|
|
|
|
var scoringFunctions = new List<Tuple<Func<Edition, string, double>, string>>
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
tc((a, t) => a.Title.FuzzyMatch(t), title),
|
|
|
|
|
|
|
|
tc((a, t) => a.Title.FuzzyMatch(t), title.RemoveBracketsAndContents().CleanAuthorName()),
|
|
|
|
|
|
|
|
tc((a, t) => a.Title.FuzzyMatch(t), title.RemoveAfterDash().CleanAuthorName()),
|
|
|
|
|
|
|
|
tc((a, t) => a.Title.FuzzyMatch(t), title.RemoveBracketsAndContents().RemoveAfterDash().CleanAuthorName()),
|
|
|
|
|
|
|
|
tc((a, t) => t.FuzzyContains(a.Title), title)
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return scoringFunctions;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private List<Edition> FindByStringInexact(List<Edition> editions, Func<Edition, string, double> scoreFunction, string title)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
const double fuzzThreshold = 0.7;
|
|
|
|
|
|
|
|
const double fuzzGap = 0.4;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var sortedEditions = editions.Select(s => new
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
MatchProb = scoreFunction(s, title),
|
|
|
|
|
|
|
|
Edition = s
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.ToList()
|
|
|
|
|
|
|
|
.OrderByDescending(s => s.MatchProb)
|
|
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return sortedEditions.TakeWhile((x, i) => i == 0 || sortedEditions[i - 1].MatchProb - x.MatchProb < fuzzGap)
|
|
|
|
|
|
|
|
.TakeWhile((x, i) => x.MatchProb > fuzzThreshold || (i > 0 && sortedEditions[i - 1].MatchProb > fuzzThreshold))
|
|
|
|
|
|
|
|
.Select(x => x.Edition)
|
|
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|