Cleanup on mapping logic. Movies with up to 4500 parts are now supported!

Rusk85 8 years ago committed by Leonardo Galli
parent f4031f1e5f
commit 27ab70333c

@ -1,5 +1,5 @@
<p align="center"> <p align="center">
<img src="/Logo/text256.png" alt="Radarr"> <img src="/Logo/text256.png" alt="Radarr">
</p> </p>
Radarr is an __independent__ fork of [Sonarr](https://github.com/Sonarr/Sonarr) reworked for automatically downloading movies via Usenet and BitTorrent. Radarr is an __independent__ fork of [Sonarr](https://github.com/Sonarr/Sonarr) reworked for automatically downloading movies via Usenet and BitTorrent.

@ -297,6 +297,7 @@
<Compile Include="OrganizerTests\FileNameBuilderTests\EpisodeTitleCollapseFixture.cs" /> <Compile Include="OrganizerTests\FileNameBuilderTests\EpisodeTitleCollapseFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderTests\MultiEpisodeFixture.cs" /> <Compile Include="OrganizerTests\FileNameBuilderTests\MultiEpisodeFixture.cs" />
<Compile Include="ParserTests\MiniSeriesEpisodeParserFixture.cs" /> <Compile Include="ParserTests\MiniSeriesEpisodeParserFixture.cs" />
<Compile Include="ParserTests\RomanNumeralTests\RomanNumeralConversionFixture.cs" />
<Compile Include="Qualities\RevisionComparableFixture.cs" /> <Compile Include="Qualities\RevisionComparableFixture.cs" />
<Compile Include="QueueTests\QueueServiceFixture.cs" /> <Compile Include="QueueTests\QueueServiceFixture.cs" />
<Compile Include="RemotePathMappingsTests\RemotePathMappingServiceFixture.cs" /> <Compile Include="RemotePathMappingsTests\RemotePathMappingServiceFixture.cs" />
@ -385,6 +386,9 @@
<Compile Include="UpdateTests\UpdateServiceFixture.cs" /> <Compile Include="UpdateTests\UpdateServiceFixture.cs" />
<Compile Include="XbmcVersionTests.cs" /> <Compile Include="XbmcVersionTests.cs" />
<Compile Include="BulkImport\AddMultiMoviesFixture.cs" /> <Compile Include="BulkImport\AddMultiMoviesFixture.cs" />
<Content Include="Files\ArabicRomanNumeralDictionary.JSON">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Marr.Data\Marr.Data.csproj"> <ProjectReference Include="..\Marr.Data\Marr.Data.csproj">

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using NUnit.Framework;
using NzbDrone.Core.Parser.RomanNumerals;
namespace NzbDrone.Core.Test.ParserTests.RomanNumeralTests
{
[TestFixture]
public class RomanNumeralConversionFixture
{
private const string TEST_VALUES = @"Files/ArabicRomanNumeralDictionary.JSON";
private Dictionary<int, string> _arabicToRomanNumeralsMapping;
[OneTimeSetUp]
public void PopulateDictionaryWithProvenValues()
{
var pathToTestValues = Path.Combine(TestContext.CurrentContext.TestDirectory, Path.Combine(TEST_VALUES.Split('/')));
_arabicToRomanNumeralsMapping =
JsonConvert.DeserializeObject<Dictionary<int, string>>(File.ReadAllText(pathToTestValues));
}
[Test(Description = "Converts the supported range [1-3999] of Arabic to Roman numerals.")]
[Order(0)]
public void should_convert_arabic_numeral_to_roman_numeral([Range(1,3999)] int arabicNumeral)
{
RomanNumeral romanNumeral = new RomanNumeral(arabicNumeral);
string expectedValue = _arabicToRomanNumeralsMapping[arabicNumeral];
Assert.AreEqual(romanNumeral.ToRomanNumeral(), expectedValue);
}
[Test]
[Order(1)]
public void should_convert_roman_numeral_to_arabic_numeral([Range(1, 3999)] int arabicNumeral)
{
RomanNumeral romanNumeral = new RomanNumeral(_arabicToRomanNumeralsMapping[arabicNumeral]);
int expectecdValue = arabicNumeral;
Assert.AreEqual(romanNumeral.ToInt(), expectecdValue);
}
}
}

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NLog; using NLog;

@ -992,6 +992,12 @@
<Compile Include="Parser\Model\LocalMovie.cs" /> <Compile Include="Parser\Model\LocalMovie.cs" />
<Compile Include="Parser\Model\ParsedMovieInfo.cs" /> <Compile Include="Parser\Model\ParsedMovieInfo.cs" />
<Compile Include="Parser\Model\RemoteMovie.cs" /> <Compile Include="Parser\Model\RemoteMovie.cs" />
<Compile Include="Parser\RomanNumerals\ArabicRomanNumeral.cs" />
<Compile Include="Parser\RomanNumerals\IRomanNumeral.cs" />
<Compile Include="Parser\RomanNumerals\RomanNumeral.cs" />
<Compile Include="Parser\RomanNumerals\RomanNumeralParser.cs" />
<Compile Include="Parser\RomanNumerals\SimpleArabicNumeral.cs" />
<Compile Include="Parser\RomanNumerals\SimpleRomanNumeral.cs" />
<Compile Include="Profiles\Delay\DelayProfile.cs" /> <Compile Include="Profiles\Delay\DelayProfile.cs" />
<Compile Include="Profiles\Delay\DelayProfileService.cs" /> <Compile Include="Profiles\Delay\DelayProfileService.cs" />
<Compile Include="Profiles\Delay\DelayProfileTagInUseValidator.cs" /> <Compile Include="Profiles\Delay\DelayProfileTagInUseValidator.cs" />

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using NLog; using NLog;
@ -7,6 +8,7 @@ using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Parser.RomanNumerals;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Parser namespace NzbDrone.Core.Parser
@ -33,20 +35,7 @@ namespace NzbDrone.Core.Parser
private readonly ISceneMappingService _sceneMappingService; private readonly ISceneMappingService _sceneMappingService;
private readonly IMovieService _movieService; private readonly IMovieService _movieService;
private readonly Logger _logger; private readonly Logger _logger;
private readonly Dictionary<string, string> romanNumeralsMapper = new Dictionary<string, string> private static HashSet<ArabicRomanNumeral> _arabicRomanNumeralMappings;
{
{ "1", "I"},
{ "2", "II"},
{ "3", "III"},
{ "4", "IV"},
{ "5", "V"},
{ "6", "VI"},
{ "7", "VII"},
{ "8", "VII"},
{ "9", "IX"},
{ "10", "X"},
}; //If a movie has more than 10 parts fuck 'em.
public ParsingService(IEpisodeService episodeService, public ParsingService(IEpisodeService episodeService,
ISeriesService seriesService, ISeriesService seriesService,
@ -59,6 +48,11 @@ namespace NzbDrone.Core.Parser
_sceneMappingService = sceneMappingService; _sceneMappingService = sceneMappingService;
_movieService = movieService; _movieService = movieService;
_logger = logger; _logger = logger;
if (_arabicRomanNumeralMappings == null)
{
_arabicRomanNumeralMappings = RomanNumeralParser.GetArabicRomanNumeralsMapping();
}
} }
public LocalEpisode GetLocalEpisode(string filename, Series series) public LocalEpisode GetLocalEpisode(string filename, Series series)
@ -354,91 +348,113 @@ namespace NzbDrone.Core.Parser
private Movie GetMovie(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria) private Movie GetMovie(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria)
{ {
if (searchCriteria != null) // TODO: Answer me this: Wouldn't it be smarter to start out looking for a movie if we have an ImDb Id?
if (!String.IsNullOrWhiteSpace(imdbId))
{ {
var possibleTitles = new List<string>(); Movie movieByImDb;
if (TryGetMovieByImDbId(parsedMovieInfo, imdbId, out movieByImDb))
Movie possibleMovie = null;
possibleTitles.Add(searchCriteria.Movie.CleanTitle);
foreach (string altTitle in searchCriteria.Movie.AlternativeTitles)
{ {
possibleTitles.Add(altTitle.CleanSeriesTitle()); return movieByImDb;
} }
foreach (string title in possibleTitles)
{
if (title == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
{
possibleMovie = searchCriteria.Movie;
} }
foreach (KeyValuePair<string, string> entry in romanNumeralsMapper) if (searchCriteria != null)
{ {
string num = entry.Key; Movie movieBySearchCriteria;
string roman = entry.Value.ToLower(); if (TryGetMovieBySearchCriteria(parsedMovieInfo, searchCriteria, out movieBySearchCriteria))
if (title.Replace(num, roman) == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
{ {
possibleMovie = searchCriteria.Movie; return movieBySearchCriteria;
} }
}
if (title.Replace(roman, num) == parsedMovieInfo.MovieTitle.CleanSeriesTitle()) else
{ {
possibleMovie = searchCriteria.Movie; Movie movieByTitleAndOrYear;
if (TryGetMovieByTitleAndOrYear(parsedMovieInfo, out movieByTitleAndOrYear))
{
return movieByTitleAndOrYear;
} }
} }
// nothing found up to here => logging that and returning null
_logger.Debug($"No matching movie {parsedMovieInfo.MovieTitle}");
return null;
} }
if (possibleMovie != null && (parsedMovieInfo.Year < 1800 || possibleMovie.Year == parsedMovieInfo.Year)) private bool TryGetMovieByImDbId(ParsedMovieInfo parsedMovieInfo, string imdbId, out Movie movie)
{ {
return possibleMovie; movie = _movieService.FindByImdbId(imdbId);
//Should fix practically all problems, where indexer is shite at adding correct imdbids to movies.
if (movie != null && parsedMovieInfo.Year > 1800 && parsedMovieInfo.Year != movie.Year)
{
movie = null;
return false;
} }
return movie != null;
} }
Movie movie = null; private bool TryGetMovieByTitleAndOrYear(ParsedMovieInfo parsedMovieInfo, out Movie movieByTitleAndOrYear)
if (searchCriteria == null)
{ {
Func<Movie, bool> isNotNull = movie => movie != null;
if (parsedMovieInfo.Year > 1800) if (parsedMovieInfo.Year > 1800)
{ {
movie = _movieService.FindByTitle(parsedMovieInfo.MovieTitle, parsedMovieInfo.Year); movieByTitleAndOrYear = _movieService.FindByTitle(parsedMovieInfo.MovieTitle, parsedMovieInfo.Year);
} if (isNotNull(movieByTitleAndOrYear))
else
{ {
movie = _movieService.FindByTitle(parsedMovieInfo.MovieTitle); return true;
} }
}
if (movie == null) movieByTitleAndOrYear = _movieService.FindByTitle(parsedMovieInfo.MovieTitle);
if (isNotNull(movieByTitleAndOrYear))
{ {
movie = _movieService.FindByTitle(parsedMovieInfo.MovieTitle); return true;
} }
// return movie; movieByTitleAndOrYear = null;
return false;
} }
private static bool TryGetMovieBySearchCriteria(ParsedMovieInfo parsedMovieInfo, SearchCriteriaBase searchCriteria, out Movie possibleMovie)
{
possibleMovie = null;
List<string> possibleTitles = new List<string>();
possibleTitles.Add(searchCriteria.Movie.CleanTitle);
if (movie == null && imdbId.IsNotNullOrWhiteSpace()) foreach (string altTitle in searchCriteria.Movie.AlternativeTitles)
{ {
movie = _movieService.FindByImdbId(imdbId); possibleTitles.Add(altTitle.CleanSeriesTitle());
}
//Should fix practically all problems, where indexer is shite at adding correct imdbids to movies. foreach (string title in possibleTitles)
if (movie != null && parsedMovieInfo.Year > 1800 && parsedMovieInfo.Year != movie.Year)
{ {
movie = null; if (title == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
{
possibleMovie = searchCriteria.Movie;
} }
foreach (ArabicRomanNumeral numeralMapping in _arabicRomanNumeralMappings)
{
string arabicNumeral = numeralMapping.ArabicNumeralAsString;
string romanNumeral = numeralMapping.RomanNumeralLowerCase;
if (title.Replace(arabicNumeral, romanNumeral) == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
{
possibleMovie = searchCriteria.Movie;
} }
if (movie == null) if (title.Replace(romanNumeral, arabicNumeral) == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
{ {
_logger.Debug($"No matching movie {parsedMovieInfo.MovieTitle}"); possibleMovie = searchCriteria.Movie;
return null; }
}
} }
return movie; if (possibleMovie != null && (parsedMovieInfo.Year < 1800 || possibleMovie.Year == parsedMovieInfo.Year))
{
return true;
}
possibleMovie = null;
return false;
} }
private Series GetSeries(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria) private Series GetSeries(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria)

@ -0,0 +1,18 @@
namespace NzbDrone.Core.Parser.RomanNumerals
{
public class ArabicRomanNumeral
{
public ArabicRomanNumeral(int arabicNumeral, string arabicNumeralAsString, string romanNumeral)
{
ArabicNumeral = arabicNumeral;
ArabicNumeralAsString = arabicNumeralAsString;
RomanNumeral = romanNumeral;
}
public int ArabicNumeral { get; private set; }
public string ArabicNumeralAsString { get; private set; }
public string RomanNumeral { get; private set; }
public string RomanNumeralLowerCase => RomanNumeral.ToLower();
}
}

@ -0,0 +1,12 @@
namespace NzbDrone.Core.Parser.RomanNumerals
{
public interface IRomanNumeral
{
int CompareTo(object obj);
int CompareTo(RomanNumeral other);
bool Equals(RomanNumeral other);
int ToInt();
long ToLong();
string ToString();
}
}

@ -0,0 +1,357 @@
using System;
using System.Text;
namespace NzbDrone.Core.Parser.RomanNumerals
{
/// <summary>
/// Represents the numeric system used in ancient Rome, employing combinations of letters from the Latin alphabet to signify values.
/// Implementation adapted from: http://www.c-sharpcorner.com/Blogs/14255/converting-to-and-from-roman-numerals.aspx
/// </summary>
public class RomanNumeral : IComparable, IComparable<RomanNumeral>, IEquatable<RomanNumeral>, IRomanNumeral
{
#region Fields
/// <summary>
/// The numeric value of the roman numeral.
/// </summary>
private readonly int _value;
/// <summary>
/// Represents the smallest possible value of an <see cref="T:RomanNumeral"/>. This field is constant.
/// </summary>
public static readonly int MinValue = 1;
/// <summary>
/// Represents the largest possible value of an <see cref="T:RomanNumeral"/>. This field is constant.
/// </summary>
public static readonly int MaxValue = 3999;
private static readonly string[] Thousands = { "MMM", "MM", "M" };
private static readonly string[] Hundreds = { "CM", "DCCC", "DCC", "DC", "D", "CD", "CCC", "CC", "C" };
private static readonly string[] Tens = { "XC", "LXXX", "LXX", "LX", "L", "XL", "XXX", "XX", "X" };
private static readonly string[] Units = { "IX", "VIII", "VII", "VI", "V", "IV", "III", "II", "I" };
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="RomanNumeral"/> class.
/// </summary>
public RomanNumeral()
{
_value = 1;
}
/// <summary>
/// Initializes a new instance of the <see cref="RomanNumeral"/> class.
/// </summary>
/// <param name="value">The value.</param>
public RomanNumeral(int value)
{
_value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="RomanNumeral"/> class.
/// </summary>
/// <param name="romanNumeral">The roman numeral.</param>
public RomanNumeral(string romanNumeral)
{
int value;
if (TryParse(romanNumeral, out value))
{
_value = value;
}
}
#endregion
#region Methods
/// <summary>
/// Converts this instance to an integer.
/// </summary>
/// <returns>A numeric int representation.</returns>
public int ToInt()
{
return _value;
}
/// <summary>
/// Converts this instance to a long.
/// </summary>
/// <returns>A numeric long representation.</returns>
public long ToLong()
{
return _value;
}
/// <summary>
/// Converts the string representation of a number to its 32-bit signed integer equivalent. A return value indicates whether the conversion succeeded.
/// </summary>
/// <param name="text">A string containing a number to convert. </param>
/// <param name="value">When this method returns, contains the 32-bit signed integer value equivalent of the number contained in <paramref name="text"/>,
/// if the conversion succeeded, or zero if the conversion failed. The conversion fails if the <paramref name="text"/> parameter is null or
/// <see cref="F:System.String.Empty"/>, is not of the correct format, or represents a number less than <see cref="F:System.Int32.MinValue"/> or greater than <see cref="F:System.Int32.MaxValue"/>. This parameter is passed uninitialized. </param><filterpriority>1</filterpriority>
/// <returns>
/// true if <paramref name="text"/> was converted successfully; otherwise, false.
/// </returns>
public static bool TryParse(string text, out int value)
{
value = 0;
if (string.IsNullOrEmpty(text)) return false;
text = text.ToUpper();
int len = 0;
for (int i = 0; i < 3; i++)
{
if (text.StartsWith(Thousands[i]))
{
value += 1000 * (3 - i);
len = Thousands[i].Length;
break;
}
}
if (len > 0)
{
text = text.Substring(len);
len = 0;
}
for (int i = 0; i < 9; i++)
{
if (text.StartsWith(Hundreds[i]))
{
value += 100 * (9 - i);
len = Hundreds[i].Length;
break;
}
}
if (len > 0)
{
text = text.Substring(len);
len = 0;
}
for (int i = 0; i < 9; i++)
{
if (text.StartsWith(Tens[i]))
{
value += 10 * (9 - i);
len = Tens[i].Length;
break;
}
}
if (len > 0)
{
text = text.Substring(len);
len = 0;
}
for (int i = 0; i < 9; i++)
{
if (text.StartsWith(Units[i]))
{
value += 9 - i;
len = Units[i].Length;
break;
}
}
if (text.Length > len)
{
value = 0;
return false;
}
return true;
}
/// <summary>
/// Converts a number into a roman numeral.
/// </summary>
/// <param name="number">The number.</param>
/// <returns></returns>
private static string ToRomanNumeral(int number)
{
RangeGuard(number);
int thousands, hundreds, tens, units;
thousands = number / 1000;
number %= 1000;
hundreds = number / 100;
number %= 100;
tens = number / 10;
units = number % 10;
var sb = new StringBuilder();
if (thousands > 0) sb.Append(Thousands[3 - thousands]);
if (hundreds > 0) sb.Append(Hundreds[9 - hundreds]);
if (tens > 0) sb.Append(Tens[9 - tens]);
if (units > 0) sb.Append(Units[9 - units]);
return sb.ToString();
}
/// <summary>
/// Returns the Roman numeral that was passed in as either an Arabic numeral
/// or a Roman numeral.
/// </summary>
/// <returns>A <see cref="System.String" /> representing a Roman Numeral</returns>
public string ToRomanNumeral()
{
return ToString();
}
/// <summary>
/// Determines whether a given number is within the valid range of values for a roman numeral.
/// </summary>
/// <param name="number">The number to validate.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// $Roman numerals can not be larger than {MaxValue}.
/// or
/// $Roman numerals can not be smaller than {MinValue}.
/// </exception>
private static void RangeGuard(int number)
{
if (number > MaxValue)
throw new ArgumentOutOfRangeException(nameof(number), number,
$"Roman numerals can not be larger than {MaxValue}.");
if (number < MinValue)
throw new ArgumentOutOfRangeException(nameof(number), number,
$"Roman numerals can not be smaller than {MinValue}.");
}
#endregion
#region Operators
/// <summary>
/// Implements the operator *.
/// </summary>
/// <param name="firstNumeral">The first numeral.</param>
/// <param name="secondNumeral">The second numeral.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static RomanNumeral operator *(RomanNumeral firstNumeral, RomanNumeral secondNumeral)
{
return new RomanNumeral(firstNumeral._value * secondNumeral._value);
}
/// <summary>
/// Implements the operator /.
/// </summary>
/// <param name="numerator">The numerator.</param>
/// <param name="denominator">The denominator.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static RomanNumeral operator /(RomanNumeral numerator, RomanNumeral denominator)
{
return new RomanNumeral(numerator._value / denominator._value);
}
/// <summary>
/// Implements the operator +.
/// </summary>
/// <param name="firstNumeral">The first numeral.</param>
/// <param name="secondNumeral">The second numeral.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static RomanNumeral operator +(RomanNumeral firstNumeral, RomanNumeral secondNumeral)
{
return new RomanNumeral(firstNumeral._value + secondNumeral._value);
}
/// <summary>
/// Implements the operator -.
/// </summary>
/// <param name="firstNumeral">The first numeral.</param>
/// <param name="secondNumeral">The second numeral.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static RomanNumeral operator -(RomanNumeral firstNumeral, RomanNumeral secondNumeral)
{
return new RomanNumeral(firstNumeral._value - secondNumeral._value);
}
#endregion
#region Interface Implementations
/// <summary>
/// </summary>
/// <param name="obj">The object.</param>
/// <returns></returns>
public int CompareTo(object obj)
{
if (obj is sbyte
|| obj is byte
|| obj is short
|| obj is ushort
|| obj is int
|| obj is uint
|| obj is long
|| obj is ulong
|| obj is float
|| obj is double
|| obj is decimal)
{
var value = (int)obj;
return _value.CompareTo(value);
}
else if (obj is string)
{
int value;
var numeral = obj as string;
if (TryParse(numeral, out value))
{
return _value.CompareTo(value);
}
}
return 0;
}
/// <summary>
/// Compares to.
/// </summary>
/// <param name="other">The other.</param>
/// <returns></returns>
public int CompareTo(RomanNumeral other)
{
return _value.CompareTo(other._value);
}
/// <summary>
/// Equalses the specified other.
/// </summary>
/// <param name="other">The other.</param>
/// <returns></returns>
public bool Equals(RomanNumeral other)
{
return _value == other._value;
}
/// <summary>
/// Returns the Roman Numeral which was passed to this Instance
/// during creation.
/// </summary>
/// <returns>
/// A <see cref="System.String" /> that represents a Roman Numeral.
/// </returns>
public override string ToString()
{
return ToRomanNumeral(_value);
}
#endregion
}
}

@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Parser.RomanNumerals
{
public static class RomanNumeralParser
{
private const int DICTIONARY_PREPOPULATION_SIZE = 20;
private static HashSet<ArabicRomanNumeral> _arabicRomanNumeralsMapping;
private static Dictionary<SimpleArabicNumeral, SimpleRomanNumeral> _simpleArabicNumeralMappings;
static RomanNumeralParser()
{
PopluateDictionariesReasonablyLarge();
}
private static void PopluateDictionariesReasonablyLarge()
{
if(_simpleArabicNumeralMappings != null || _arabicRomanNumeralsMapping != null)
{
return;
}
_arabicRomanNumeralsMapping = new HashSet<ArabicRomanNumeral>();
_simpleArabicNumeralMappings = new Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>();
foreach (int arabicNumeral in Enumerable.Range(1,DICTIONARY_PREPOPULATION_SIZE +1))
{
string romanNumeralAsString, arabicNumeralAsString;
GenerateRomanNumerals(arabicNumeral, out romanNumeralAsString, out arabicNumeralAsString);
ArabicRomanNumeral arm = new ArabicRomanNumeral(arabicNumeral, arabicNumeralAsString, romanNumeralAsString);
_arabicRomanNumeralsMapping.Add(arm);
SimpleArabicNumeral sam = new SimpleArabicNumeral(arabicNumeral);
SimpleRomanNumeral srm = new SimpleRomanNumeral(romanNumeralAsString);
_simpleArabicNumeralMappings.Add(sam, srm);
}
}
private static void GenerateRomanNumerals(int arabicNumeral, out string romanNumeral, out string arabicNumeralAsString)
{
RomanNumeral romanNumeralObject = new RomanNumeral(arabicNumeral);
romanNumeral = romanNumeralObject.ToRomanNumeral();
arabicNumeralAsString = Convert.ToString(arabicNumeral);
}
private static HashSet<ArabicRomanNumeral> GenerateAdditionalMappings(int offset, int length)
{
HashSet<ArabicRomanNumeral> additionalArabicRomanNumerals = new HashSet<ArabicRomanNumeral>();
foreach (int arabicNumeral in Enumerable.Range(offset, length))
{
string romanNumeral;
string arabicNumeralAsString;
GenerateRomanNumerals(arabicNumeral, out romanNumeral, out arabicNumeralAsString);
ArabicRomanNumeral arm = new ArabicRomanNumeral(arabicNumeral, arabicNumeralAsString, romanNumeral);
additionalArabicRomanNumerals.Add(arm);
}
return additionalArabicRomanNumerals;
}
public static HashSet<ArabicRomanNumeral> GetArabicRomanNumeralsMapping(int upToArabicNumber = DICTIONARY_PREPOPULATION_SIZE)
{
if (upToArabicNumber == DICTIONARY_PREPOPULATION_SIZE)
{
return new HashSet<ArabicRomanNumeral>(_arabicRomanNumeralsMapping.Take(upToArabicNumber));
}
if (upToArabicNumber < DICTIONARY_PREPOPULATION_SIZE)
{
return
(HashSet<ArabicRomanNumeral>)
new HashSet<ArabicRomanNumeral>(_arabicRomanNumeralsMapping).Take(upToArabicNumber);
}
if (upToArabicNumber >= DICTIONARY_PREPOPULATION_SIZE)
{
if (_arabicRomanNumeralsMapping.Count >= upToArabicNumber)
{
return new HashSet<ArabicRomanNumeral>(_arabicRomanNumeralsMapping.Take(upToArabicNumber));
}
HashSet<ArabicRomanNumeral> largerMapping = GenerateAdditionalMappings(DICTIONARY_PREPOPULATION_SIZE + 1, upToArabicNumber);
_arabicRomanNumeralsMapping = (HashSet<ArabicRomanNumeral>)_arabicRomanNumeralsMapping.Union(largerMapping);
}
return _arabicRomanNumeralsMapping;
}
public static Dictionary<SimpleArabicNumeral, SimpleRomanNumeral> GetArabicRomanNumeralAsDictionary(
int upToArabicNumer = DICTIONARY_PREPOPULATION_SIZE)
{
Func
<Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>, int,
Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>> take =
(mapping, amountToTake) =>
new Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>(
mapping.Take(amountToTake).ToDictionary(key => key.Key, value => value.Value));
if (upToArabicNumer == DICTIONARY_PREPOPULATION_SIZE)
{
return take(_simpleArabicNumeralMappings, upToArabicNumer);
}
if (upToArabicNumer > DICTIONARY_PREPOPULATION_SIZE)
{
if (_simpleArabicNumeralMappings.Count >= upToArabicNumer)
{
return take(_simpleArabicNumeralMappings, upToArabicNumer);
}
var moreSimpleNumerals = GenerateAdditionalSimpleNumerals(DICTIONARY_PREPOPULATION_SIZE, upToArabicNumer);
_simpleArabicNumeralMappings =
(Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>)
_simpleArabicNumeralMappings.Union(moreSimpleNumerals);
return take(_simpleArabicNumeralMappings, _arabicRomanNumeralsMapping.Count);
}
if (upToArabicNumer < DICTIONARY_PREPOPULATION_SIZE)
{
return take(_simpleArabicNumeralMappings, upToArabicNumer);
}
return _simpleArabicNumeralMappings;
}
private static Dictionary<SimpleArabicNumeral, SimpleRomanNumeral> GenerateAdditionalSimpleNumerals(int offset,
int length)
{
Dictionary<SimpleArabicNumeral,SimpleRomanNumeral> moreNumerals = new Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>();
foreach (int arabicNumeral in Enumerable.Range(offset, length))
{
string romanNumeral;
string arabicNumeralAsString;
GenerateRomanNumerals(arabicNumeral, out romanNumeral, out arabicNumeralAsString);
SimpleArabicNumeral san = new SimpleArabicNumeral(arabicNumeral);
SimpleRomanNumeral srn = new SimpleRomanNumeral(romanNumeral);
moreNumerals.Add(san,srn);
}
return moreNumerals;
}
}
}

@ -0,0 +1,15 @@
using System;
namespace NzbDrone.Core.Parser.RomanNumerals
{
public class SimpleArabicNumeral
{
public SimpleArabicNumeral(int numeral)
{
Numeral = numeral;
}
public int Numeral { get; private set; }
public string NumeralAsString => Convert.ToString(Numeral);
}
}

@ -0,0 +1,13 @@
namespace NzbDrone.Core.Parser.RomanNumerals
{
public class SimpleRomanNumeral
{
public SimpleRomanNumeral(string numeral)
{
Numeral = numeral;
}
public string Numeral { get; private set; }
public string NumeralLowerCase => Numeral.ToLower();
}
}

@ -6,7 +6,9 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Datastore.Extensions; using NzbDrone.Core.Datastore.Extensions;
using Marr.Data.QGen; using Marr.Data.QGen;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Parser.RomanNumerals;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using CoreParser = NzbDrone.Core.Parser.Parser;
namespace NzbDrone.Core.Tv namespace NzbDrone.Core.Tv
{ {
@ -28,21 +30,6 @@ namespace NzbDrone.Core.Tv
public class MovieRepository : BasicRepository<Movie>, IMovieRepository public class MovieRepository : BasicRepository<Movie>, IMovieRepository
{ {
private readonly Dictionary<string, string> romanNumeralsMapper = new Dictionary<string, string>
{
{ "1", "I"},
{ "2", "II"},
{ "3", "III"},
{ "4", "IV"},
{ "5", "V"},
{ "6", "VI"},
{ "7", "VII"},
{ "8", "VII"},
{ "9", "IX"},
{ "10", "X"},
}; //If a movie has more than 10 parts fuck 'em.
protected IMainDatabase _database; protected IMainDatabase _database;
public MovieRepository(IMainDatabase database, IEventAggregator eventAggregator) public MovieRepository(IMainDatabase database, IEventAggregator eventAggregator)
@ -58,94 +45,12 @@ namespace NzbDrone.Core.Tv
public Movie FindByTitle(string cleanTitle) public Movie FindByTitle(string cleanTitle)
{ {
cleanTitle = cleanTitle.ToLowerInvariant(); return FindByTitle(cleanTitle, null);
var cleanRoman = cleanTitle;
var cleanNum = cleanTitle;
foreach (KeyValuePair<string, string> entry in romanNumeralsMapper)
{
string num = entry.Key;
string roman = entry.Value.ToLower();
cleanRoman = cleanRoman.Replace(num, roman);
cleanNum = cleanNum.Replace(roman, num);
}
var result = Query.Where(s => s.CleanTitle == cleanTitle).FirstOrDefault();
if (result == null)
{
result = Query.Where(s => s.CleanTitle == cleanNum).OrWhere(s => s.CleanTitle == cleanRoman).FirstOrDefault();
if (result == null)
{
var movies = this.All();
result = movies.Where(m => m.AlternativeTitles.Any(t => Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanTitle ||
Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanRoman ||
Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanNum)).FirstOrDefault();
return result;
}
else
{
return result;
}
}
else
{
return result;
}
} }
public Movie FindByTitle(string cleanTitle, int year) public Movie FindByTitle(string cleanTitle, int year)
{ {
cleanTitle = cleanTitle.ToLowerInvariant(); return FindByTitle(cleanTitle, year as int?);
var cleanRoman = cleanTitle;
var cleanNum = cleanTitle;
foreach (KeyValuePair<string, string> entry in romanNumeralsMapper)
{
string num = entry.Key;
string roman = entry.Value.ToLower();
cleanRoman = cleanRoman.Replace(num, roman);
cleanNum = cleanNum.Replace(roman, num);
}
var results = Query.Where(s => s.CleanTitle == cleanTitle);
if (results == null)
{
results = Query.Where(s => s.CleanTitle == cleanNum).OrWhere(s => s.CleanTitle == cleanRoman);
if (results == null)
{
var movies = this.All();
var listResults = movies.Where(m => m.AlternativeTitles.Any(t => Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanTitle ||
Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanRoman ||
Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanNum));
return listResults.Where(m => m.Year == year).FirstOrDefault();
}
else
{
return results.Where(m => m.Year == year).FirstOrDefault();
}
}
else
{
return results.Where(m => m.Year == year).FirstOrDefault();
}
} }
public Movie FindByImdbId(string imdbid) public Movie FindByImdbId(string imdbid)
@ -318,6 +223,46 @@ namespace NzbDrone.Core.Tv
return string.Format("({0})", string.Join(" OR ", clauses)); return string.Format("({0})", string.Join(" OR ", clauses));
} }
private Movie FindByTitle(string cleanTitle, int? year)
{
cleanTitle = cleanTitle.ToLowerInvariant();
string cleanTitleWithRomanNumbers = cleanTitle;
string cleanTitleWithArabicNumbers = cleanTitle;
foreach (ArabicRomanNumeral arabicRomanNumeral in RomanNumeralParser.GetArabicRomanNumeralsMapping())
{
string arabicNumber = arabicRomanNumeral.ArabicNumeralAsString;
string romanNumber = arabicRomanNumeral.RomanNumeral;
cleanTitleWithRomanNumbers = cleanTitleWithRomanNumbers.Replace(arabicNumber, romanNumber);
cleanTitleWithArabicNumbers = cleanTitleWithArabicNumbers.Replace(romanNumber, arabicNumber);
}
IEnumerable<Movie> results = Query.Where(s => s.CleanTitle == cleanTitle);
if (results == null)
{
results = Query.Where(movie => movie.CleanTitle == cleanTitleWithArabicNumbers) ??
Query.Where(movie => movie.CleanTitle == cleanTitleWithRomanNumbers);
if (results == null)
{
IEnumerable<Movie> movies = All();
Func<string, string> titleCleaner = title => CoreParser.CleanSeriesTitle(title.ToLower());
Func<IEnumerable<string>, string, bool> altTitleComparer =
(alternativeTitles, atitle) =>
alternativeTitles.Any(altTitle => altTitle == titleCleaner(atitle));
results = movies.Where(m => altTitleComparer(m.AlternativeTitles, cleanTitle) ||
altTitleComparer(m.AlternativeTitles, cleanTitleWithRomanNumbers) ||
altTitleComparer(m.AlternativeTitles, cleanTitleWithArabicNumbers));
}
}
return year.HasValue
? results?.FirstOrDefault(movie => movie.Year == year.Value)
: results?.FirstOrDefault();
}
public Movie FindByTmdbId(int tmdbid) public Movie FindByTmdbId(int tmdbid)
{ {
return Query.Where(m => m.TmdbId == tmdbid).FirstOrDefault(); return Query.Where(m => m.TmdbId == tmdbid).FirstOrDefault();

Loading…
Cancel
Save