diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
index 2cee27fdc..66bd95291 100644
--- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
+++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
@@ -169,7 +169,9 @@
+
+
diff --git a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetSeriesFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetSeriesFixture.cs
new file mode 100644
index 000000000..bad109bf9
--- /dev/null
+++ b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetSeriesFixture.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Moq;
+using NUnit.Framework;
+using NzbDrone.Core.Parser;
+using NzbDrone.Core.Test.Framework;
+using NzbDrone.Core.Tv;
+
+namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
+{
+ [TestFixture]
+ public class GetSeriesFixture : CoreTest
+ {
+ [Test]
+ public void should_use_passed_in_title_when_it_cannot_be_parsed()
+ {
+ const string title = "30 Rock";
+
+ Subject.GetSeries(title);
+
+ Mocker.GetMock()
+ .Verify(s => s.FindByTitle(title), Times.Once());
+ }
+
+ [Test]
+ public void should_use_parsed_series_title()
+ {
+ const string title = "30.Rock.S01E01.720p.hdtv";
+
+ Subject.GetSeries(title);
+
+ Mocker.GetMock()
+ .Verify(s => s.FindByTitle(Parser.Parser.ParseTitle(title).SeriesTitle), Times.Once());
+ }
+
+ [Test]
+ public void should_fallback_to_title_without_year_and_year_when_title_lookup_fails()
+ {
+ const string title = "House.2004.S01E01.720p.hdtv";
+ var parsedEpisodeInfo = Parser.Parser.ParseTitle(title);
+
+ Subject.GetSeries(title);
+
+ Mocker.GetMock()
+ .Verify(s => s.FindByTitle(parsedEpisodeInfo.SeriesTitleInfo.TitleWithoutYear,
+ parsedEpisodeInfo.SeriesTitleInfo.Year), Times.Once());
+ }
+ }
+}
diff --git a/src/NzbDrone.Core.Test/ParserTests/SeriesTitleInfoFixture.cs b/src/NzbDrone.Core.Test/ParserTests/SeriesTitleInfoFixture.cs
new file mode 100644
index 000000000..5f2e00b9c
--- /dev/null
+++ b/src/NzbDrone.Core.Test/ParserTests/SeriesTitleInfoFixture.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Core.Test.Framework;
+
+namespace NzbDrone.Core.Test.ParserTests
+{
+ [TestFixture]
+ public class SeriesTitleInfoFixture : CoreTest
+ {
+ [Test]
+ public void should_have_year_zero_when_title_doesnt_have_a_year()
+ {
+ const string title = "House.S01E01.pilot.720p.hdtv";
+
+ var result = Parser.Parser.ParseTitle(title).SeriesTitleInfo;
+
+ result.Year.Should().Be(0);
+ }
+
+ [Test]
+ public void should_have_same_title_for_title_and_title_without_year_when_title_doesnt_have_a_year()
+ {
+ const string title = "House.S01E01.pilot.720p.hdtv";
+
+ var result = Parser.Parser.ParseTitle(title).SeriesTitleInfo;
+
+ result.Title.Should().Be(result.TitleWithoutYear);
+ }
+
+ [Test]
+ public void should_have_year_when_title_has_a_year()
+ {
+ const string title = "House.2004.S01E01.pilot.720p.hdtv";
+
+ var result = Parser.Parser.ParseTitle(title).SeriesTitleInfo;
+
+ result.Year.Should().Be(2004);
+ }
+
+ [Test]
+ public void should_have_year_in_title_when_title_has_a_year()
+ {
+ const string title = "House.2004.S01E01.pilot.720p.hdtv";
+
+ var result = Parser.Parser.ParseTitle(title).SeriesTitleInfo;
+
+ result.Title.Should().Be("house2004");
+ }
+
+ [Test]
+ public void should_title_without_year_should_not_contain_year()
+ {
+ const string title = "House.2004.S01E01.pilot.720p.hdtv";
+
+ var result = Parser.Parser.ParseTitle(title).SeriesTitleInfo;
+
+ result.TitleWithoutYear.Should().Be("house");
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj
index 7a7d5b516..9007ad74e 100644
--- a/src/NzbDrone.Core/NzbDrone.Core.csproj
+++ b/src/NzbDrone.Core/NzbDrone.Core.csproj
@@ -319,6 +319,7 @@
+
diff --git a/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs b/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs
index fe89f6dee..14fd53a80 100644
--- a/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs
+++ b/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs
@@ -7,6 +7,7 @@ namespace NzbDrone.Core.Parser.Model
public class ParsedEpisodeInfo
{
public string SeriesTitle { get; set; }
+ public SeriesTitleInfo SeriesTitleInfo { get; set; }
public QualityModel Quality { get; set; }
public int SeasonNumber { get; set; }
public int[] EpisodeNumbers { get; set; }
diff --git a/src/NzbDrone.Core/Parser/Model/SeriesTitleInfo.cs b/src/NzbDrone.Core/Parser/Model/SeriesTitleInfo.cs
new file mode 100644
index 000000000..5ced83c40
--- /dev/null
+++ b/src/NzbDrone.Core/Parser/Model/SeriesTitleInfo.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace NzbDrone.Core.Parser.Model
+{
+ public class SeriesTitleInfo
+ {
+ public string Title { get; set; }
+ public string TitleWithoutYear { get; set; }
+ public int Year { get; set; }
+ }
+}
diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs
index a58464c96..9122245ab 100644
--- a/src/NzbDrone.Core/Parser/Parser.cs
+++ b/src/NzbDrone.Core/Parser/Parser.cs
@@ -76,6 +76,9 @@ namespace NzbDrone.Core.Parser
private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?ita|italian)|(?german\b)|(?flemish)|(?greek)|(?(?:\W|_)FR)(?:\W|_)",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
+ private static readonly Regex YearInTitleRegex = new Regex(@"^(?.+?)(?:\W|_)?(?\d{4})",
+ RegexOptions.IgnoreCase | RegexOptions.Compiled);
+
public static ParsedEpisodeInfo ParsePath(string path)
{
var fileInfo = new FileInfo(path);
@@ -139,6 +142,58 @@ namespace NzbDrone.Core.Parser
return null;
}
+ public static string ParseSeriesName(string title)
+ {
+ Logger.Trace("Parsing string '{0}'", title);
+
+ var parseResult = ParseTitle(title);
+
+ if (parseResult == null)
+ {
+ return CleanSeriesTitle(title);
+ }
+
+ return parseResult.SeriesTitle;
+ }
+
+ public static string CleanSeriesTitle(this string title)
+ {
+ long number = 0;
+
+ //If Title only contains numbers return it as is.
+ if (Int64.TryParse(title, out number))
+ return title;
+
+ return NormalizeRegex.Replace(title, String.Empty).ToLower();
+ }
+
+ public static string CleanupEpisodeTitle(string title)
+ {
+ //this will remove (1),(2) from the end of multi part episodes.
+ return MultiPartCleanupRegex.Replace(title, string.Empty).Trim();
+ }
+
+ private static SeriesTitleInfo GetSeriesTitleInfo(string title)
+ {
+ var seriesTitleInfo = new SeriesTitleInfo();
+ seriesTitleInfo.Title = title;
+
+ var match = YearInTitleRegex.Match(title);
+
+ if (!match.Success)
+ {
+ seriesTitleInfo.TitleWithoutYear = title;
+ }
+
+ else
+ {
+ seriesTitleInfo.TitleWithoutYear = match.Groups["title"].Value;
+ seriesTitleInfo.Year = Convert.ToInt32(match.Groups["year"].Value);
+ }
+
+ return seriesTitleInfo;
+ }
+
private static ParsedEpisodeInfo ParseMatchCollection(MatchCollection matchCollection)
{
var seriesName = matchCollection[0].Groups["title"].Value.Replace('.', ' ');
@@ -168,10 +223,10 @@ namespace NzbDrone.Core.Parser
return null;
result = new ParsedEpisodeInfo
- {
- SeasonNumber = seasons.First(),
- EpisodeNumbers = new int[0],
- };
+ {
+ SeasonNumber = seasons.First(),
+ EpisodeNumbers = new int[0],
+ };
foreach (Match matchGroup in matchCollection)
{
@@ -226,32 +281,19 @@ namespace NzbDrone.Core.Parser
}
result = new ParsedEpisodeInfo
- {
- AirDate = airDate.ToString(Episode.AIR_DATE_FORMAT),
- };
+ {
+ AirDate = airDate.ToString(Episode.AIR_DATE_FORMAT),
+ };
}
result.SeriesTitle = CleanSeriesTitle(seriesName);
+ result.SeriesTitleInfo = GetSeriesTitleInfo(result.SeriesTitle);
Logger.Trace("Episode Parsed. {0}", result);
return result;
}
- public static string ParseSeriesName(string title)
- {
- Logger.Trace("Parsing string '{0}'", title);
-
- var parseResult = ParseTitle(title);
-
- if (parseResult == null)
- {
- return CleanSeriesTitle(title);
- }
-
- return parseResult.SeriesTitle;
- }
-
private static Language ParseLanguage(string title)
{
var lowerTitle = title.ToLower();
@@ -345,22 +387,5 @@ namespace NzbDrone.Core.Parser
return true;
}
-
- public static string CleanSeriesTitle(this string title)
- {
- long number = 0;
-
- //If Title only contains numbers return it as is.
- if (Int64.TryParse(title, out number))
- return title;
-
- return NormalizeRegex.Replace(title, String.Empty).ToLower();
- }
-
- public static string CleanupEpisodeTitle(string title)
- {
- //this will remove (1),(2) from the end of multi part episodes.
- return MultiPartCleanupRegex.Replace(title, string.Empty).Trim();
- }
}
}
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs
index 134e507e3..3cd3aa407 100644
--- a/src/NzbDrone.Core/Parser/ParsingService.cs
+++ b/src/NzbDrone.Core/Parser/ParsingService.cs
@@ -68,15 +68,22 @@ namespace NzbDrone.Core.Parser
public Series GetSeries(string title)
{
- var searchTitle = title;
var parsedEpisodeInfo = Parser.ParseTitle(title);
- if (parsedEpisodeInfo != null)
+ if (parsedEpisodeInfo == null)
+ {
+ return _seriesService.FindByTitle(title);
+ }
+
+ var series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle);
+
+ if (series == null)
{
- searchTitle = parsedEpisodeInfo.SeriesTitle;
+ series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitleInfo.TitleWithoutYear,
+ parsedEpisodeInfo.SeriesTitleInfo.Year);
}
- return _seriesService.FindByTitle(searchTitle);
+ return series;
}
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvRageId, SearchCriteriaBase searchCriteria = null)
diff --git a/src/NzbDrone.Core/Tv/SeriesRepository.cs b/src/NzbDrone.Core/Tv/SeriesRepository.cs
index 0c7d0288e..dbdc1c191 100644
--- a/src/NzbDrone.Core/Tv/SeriesRepository.cs
+++ b/src/NzbDrone.Core/Tv/SeriesRepository.cs
@@ -10,6 +10,7 @@ namespace NzbDrone.Core.Tv
{
bool SeriesPathExists(string path);
Series FindByTitle(string cleanTitle);
+ Series FindByTitle(string cleanTitle, int year);
Series FindByTvdbId(int tvdbId);
Series FindByTvRageId(int tvRageId);
void SetSeriesType(int seriesId, SeriesTypes seriesTypes);
@@ -32,6 +33,12 @@ namespace NzbDrone.Core.Tv
return Query.SingleOrDefault(s => s.CleanTitle.Equals(cleanTitle, StringComparison.InvariantCultureIgnoreCase));
}
+ public Series FindByTitle(string cleanTitle, int year)
+ {
+ return Query.SingleOrDefault(s => s.CleanTitle.Equals(cleanTitle, StringComparison.InvariantCultureIgnoreCase) &&
+ s.Year == year);
+ }
+
public Series FindByTvdbId(int tvdbId)
{
return Query.SingleOrDefault(s => s.TvdbId.Equals(tvdbId));
diff --git a/src/NzbDrone.Core/Tv/SeriesService.cs b/src/NzbDrone.Core/Tv/SeriesService.cs
index 7496a15e7..6f94faa84 100644
--- a/src/NzbDrone.Core/Tv/SeriesService.cs
+++ b/src/NzbDrone.Core/Tv/SeriesService.cs
@@ -19,6 +19,7 @@ namespace NzbDrone.Core.Tv
Series FindByTvdbId(int tvdbId);
Series FindByTvRageId(int tvRageId);
Series FindByTitle(string title);
+ Series FindByTitle(string title, int year);
void SetSeriesType(int seriesId, SeriesTypes seriesTypes);
void DeleteSeries(int seriesId, bool deleteFiles);
List GetAllSeries();
@@ -100,6 +101,11 @@ namespace NzbDrone.Core.Tv
return _seriesRepository.FindByTitle(Parser.Parser.CleanSeriesTitle(title));
}
+ public Series FindByTitle(string title, int year)
+ {
+ return _seriesRepository.FindByTitle(title, year);
+ }
+
public void SetSeriesType(int seriesId, SeriesTypes seriesTypes)
{
_seriesRepository.SetSeriesType(seriesId, seriesTypes);