From f00b17ac478efc99c4a134414deb08d7ee2877b2 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 17 Dec 2012 22:41:08 -0800 Subject: [PATCH] Added TvRageProvider and model classes --- NzbDrone.Core.Test/NzbDrone.Core.Test.csproj | 2 + .../GetUtcOffsetFixture.cs | 50 ++++++ .../SearchSeriesFixture.cs | 82 +++++++++ NzbDrone.Core/Model/TvRage/TvRageEpisode.cs | 17 ++ .../Model/TvRage/TvRageSearchResult.cs | 22 +++ NzbDrone.Core/Model/TvRage/TvRageSeries.cs | 25 +++ NzbDrone.Core/NzbDrone.Core.csproj | 4 + NzbDrone.Core/Providers/TvRageProvider.cs | 168 ++++++++++++++++++ 8 files changed, 370 insertions(+) create mode 100644 NzbDrone.Core.Test/ProviderTests/TvRageProviderTests/GetUtcOffsetFixture.cs create mode 100644 NzbDrone.Core.Test/ProviderTests/TvRageProviderTests/SearchSeriesFixture.cs create mode 100644 NzbDrone.Core/Model/TvRage/TvRageEpisode.cs create mode 100644 NzbDrone.Core/Model/TvRage/TvRageSearchResult.cs create mode 100644 NzbDrone.Core/Model/TvRage/TvRageSeries.cs create mode 100644 NzbDrone.Core/Providers/TvRageProvider.cs diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 347380962..ae92b4807 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -139,6 +139,8 @@ + + diff --git a/NzbDrone.Core.Test/ProviderTests/TvRageProviderTests/GetUtcOffsetFixture.cs b/NzbDrone.Core.Test/ProviderTests/TvRageProviderTests/GetUtcOffsetFixture.cs new file mode 100644 index 000000000..8c55feaef --- /dev/null +++ b/NzbDrone.Core.Test/ProviderTests/TvRageProviderTests/GetUtcOffsetFixture.cs @@ -0,0 +1,50 @@ +// ReSharper disable RedundantUsingDirective + +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using NUnit.Framework; +using Ninject; +using NzbDrone.Common; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common; +using TvdbLib.Data; +using TvdbLib.Exceptions; + +namespace NzbDrone.Core.Test.ProviderTests.TvRageProviderTests +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class GetUtcOffsetFixture : CoreTest + { + [Test] + public void should_return_zero_if_timeZone_is_empty() + { + Mocker.Resolve().GetUtcOffset("").Should().Be(0); + } + + [Test] + public void should_return_zero_if_cannot_be_coverted_to_int() + { + Mocker.Resolve().GetUtcOffset("adfhadfhdjaf").Should().Be(0); + } + + [TestCase("GMT-5", -5)] + [TestCase("GMT+0", 0)] + [TestCase("GMT+8", 8)] + public void should_return_offset_when_not_dst(string timezone, int expected) + { + Mocker.Resolve().GetUtcOffset(timezone).Should().Be(expected); + } + + [TestCase("GMT-5 +DST", -4)] + [TestCase("GMT+0 +DST", 1)] + [TestCase("GMT+8 +DST", 9)] + public void should_return_offset_plus_one_when_dst(string timezone, int expected) + { + Mocker.Resolve().GetUtcOffset(timezone).Should().Be(expected); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/ProviderTests/TvRageProviderTests/SearchSeriesFixture.cs b/NzbDrone.Core.Test/ProviderTests/TvRageProviderTests/SearchSeriesFixture.cs new file mode 100644 index 000000000..1d64de299 --- /dev/null +++ b/NzbDrone.Core.Test/ProviderTests/TvRageProviderTests/SearchSeriesFixture.cs @@ -0,0 +1,82 @@ +// ReSharper disable RedundantUsingDirective + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using Ninject; +using NzbDrone.Common; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common; +using TvdbLib.Data; +using TvdbLib.Exceptions; + +namespace NzbDrone.Core.Test.ProviderTests.TvRageProviderTests +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class SearchSeriesFixture : CoreTest + { + private const string search = "http://services.tvrage.com/feeds/full_search.php?show="; + + private void WithEmptyResults() + { + Mocker.GetMock() + .Setup(s => s.DownloadStream(It.Is(u => u.StartsWith(search)), null)) + .Returns(new FileStream(@".\Files\TVRage\SearchResults_empty.xml", FileMode.Open, FileAccess.Read, FileShare.Read)); + } + + private void WithManyResults() + { + Mocker.GetMock() + .Setup(s => s.DownloadStream(It.Is(u => u.StartsWith(search)), null)) + .Returns(new FileStream(@".\Files\TVRage\SearchResults_many.xml", FileMode.Open, FileAccess.Read, FileShare.Read)); + } + + private void WithOneResult() + { + Mocker.GetMock() + .Setup(s => s.DownloadStream(It.Is(u => u.StartsWith(search)), null)) + .Returns(new FileStream(@".\Files\TVRage\SearchResults_one.xml", FileMode.Open, FileAccess.Read, FileShare.Read)); + } + + [Test] + public void should_be_empty_when_no_results_are_found() + { + WithEmptyResults(); + Mocker.Resolve().SearchSeries("asdasdasdasdas").Should().BeEmpty(); + } + + [Test] + public void should_be_have_more_than_one_when_multiple_results_are_returned() + { + WithManyResults(); + Mocker.Resolve().SearchSeries("top+gear").Should().NotBeEmpty(); + } + + [Test] + public void should_have_one_when_only_one_result_is_found() + { + WithOneResult(); + Mocker.Resolve().SearchSeries("suits").Should().HaveCount(1); + } + + [Test] + public void ended_should_not_have_a_value_when_series_has_not_ended() + { + WithOneResult(); + Mocker.Resolve().SearchSeries("suits").First().Ended.HasValue.Should().BeFalse(); + } + + [Test] + public void started_should_match_series() + { + WithOneResult(); + Mocker.Resolve().SearchSeries("suits").First().Started.Should().Be(new DateTime(2011, 6, 23)); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Model/TvRage/TvRageEpisode.cs b/NzbDrone.Core/Model/TvRage/TvRageEpisode.cs new file mode 100644 index 000000000..e7149fb11 --- /dev/null +++ b/NzbDrone.Core/Model/TvRage/TvRageEpisode.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Model.TvRage +{ + public class TvRageEpisode + { + public int EpisodeNumber { get; set; } + public int SeasonNumber { get; set; } + public string ProductionCode { get; set; } + public DateTime AirDate { get; set; } + public string Link { get; set; } + public string Title { get; set; } + } +} diff --git a/NzbDrone.Core/Model/TvRage/TvRageSearchResult.cs b/NzbDrone.Core/Model/TvRage/TvRageSearchResult.cs new file mode 100644 index 000000000..d109a991f --- /dev/null +++ b/NzbDrone.Core/Model/TvRage/TvRageSearchResult.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Model.TvRage +{ + public class TvRageSearchResult + { + public int ShowId { get; set; } + public string Name { get; set; } + public string Link { get; set; } + public string Country { get; set; } + public DateTime Started { get; set; } + public DateTime? Ended { get; set; } + public int Seasons { get; set; } + public string Status { get; set; } + public int RunTime { get; set; } + public DateTime AirTime { get; set; } + public DayOfWeek AirDay { get; set; } + } +} diff --git a/NzbDrone.Core/Model/TvRage/TvRageSeries.cs b/NzbDrone.Core/Model/TvRage/TvRageSeries.cs new file mode 100644 index 000000000..965738bbd --- /dev/null +++ b/NzbDrone.Core/Model/TvRage/TvRageSeries.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Model.TvRage +{ + public class TvRageSeries + { + public int ShowId { get; set; } + public string Name { get; set; } + public string Link { get; set; } + public int Seasons { get; set; } + public int Started { get; set; } + public DateTime StartDate { get; set; } + public DateTime Ended { get; set; } + public string OriginCountry { get; set; } + public string Status { get; set; } + public int RunTime { get; set; } + public string Network { get; set; } + public DateTime AirTime { get; set; } + public DayOfWeek AirDay { get; set; } + public int UtcOffset { get; set; } + } +} diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 26db534af..4a0183fc6 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -291,6 +291,9 @@ + + + @@ -343,6 +346,7 @@ + diff --git a/NzbDrone.Core/Providers/TvRageProvider.cs b/NzbDrone.Core/Providers/TvRageProvider.cs new file mode 100644 index 000000000..51bc35a74 --- /dev/null +++ b/NzbDrone.Core/Providers/TvRageProvider.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; +using NLog; +using Ninject; +using NzbDrone.Common; +using NzbDrone.Core.Model.TvRage; + +namespace NzbDrone.Core.Providers +{ + public class TvRageProvider + { + private readonly HttpProvider _httpProvider; + private const string TVRAGE_APIKEY = "NW4v0PSmQIoVmpbASLdD"; + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + [Inject] + public TvRageProvider(HttpProvider httpProvider) + { + _httpProvider = httpProvider; + } + + public virtual IList SearchSeries(string title) + { + var searchResults = new List(); + + var xmlStream = _httpProvider.DownloadStream("http://services.tvrage.com/feeds/full_search.php?show=" + title, null); + + var xml = XDocument.Load(xmlStream); + var shows = xml.Descendants("Results").Descendants("show"); + + foreach (var s in shows) + { + try + { + var show = new TvRageSearchResult(); + show.ShowId = Int32.Parse(s.Element("showid").Value); + show.Name = s.Element("name").Value; + show.Link = s.Element("link").Value; + show.Country = s.Element("country").Value; + + DateTime started; + if (DateTime.TryParse(s.Element("started").Value, out started)) ; + show.Started = started; + + DateTime ended; + if (DateTime.TryParse(s.Element("ended").Value, out ended)) ; + show.Ended = ended; + + if (show.Ended < new DateTime(1900, 1, 1)) + show.Ended = null; + + show.Seasons = Int32.Parse(s.Element("seasons").Value); + show.Status = s.Element("status").Value; + show.RunTime = Int32.Parse(s.Element("runtime").Value); + show.AirTime = DateTime.Parse(s.Element("airtime").Value); + show.AirDay = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), s.Element("airday").Value); + + searchResults.Add(show); + } + + catch (Exception ex) + { + logger.DebugException("Failed to parse TvRage Search Result. Search Term : " + title, ex); + } + } + + return searchResults; + } + + public virtual TvRageSeries GetSeries(int id) + { + var url = string.Format("http://services.tvrage.com/feeds/showinfo.php?key={0}sid={1}", TVRAGE_APIKEY, id); + var xmlStream = _httpProvider.DownloadStream(url, null); + var xml = XDocument.Load(xmlStream); + var s = xml.Descendants("Showinfo").First(); + try + { + var show = new TvRageSeries(); + show.ShowId = Int32.Parse(s.Element("showid").Value); + show.Name = s.Element("showname").Value; + show.Link = s.Element("showlink").Value; + show.Seasons = Int32.Parse(s.Element("seasons").Value); + show.Started = Int32.Parse(s.Element("started").Value); + + DateTime startDate; + if (DateTime.TryParse(s.Element("startdate").Value, out startDate)) ; + show.StartDate = startDate; + + DateTime ended; + if (DateTime.TryParse(s.Element("ended").Value, out ended)) ; + show.Ended = ended; + + show.OriginCountry = s.Element("origin_country").Value; + show.Status = s.Element("status").Value; + show.RunTime = Int32.Parse(s.Element("runtime").Value); + show.Network = s.Element("network").Value; + show.AirTime = DateTime.Parse(s.Element("airtime").Value); + show.AirDay = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), s.Element("airday").Value); + show.UtcOffset = GetUtcOffset(s.Element("timezone").Value); + return show; + } + + catch (Exception ex) + { + logger.DebugException("Failed to parse ShowInfo for ID: " + id, ex); + return null; + } + } + + public virtual List GetEpisodes(int id) + { + var url = String.Format("http://services.tvrage.com/feeds/episode_list.php?key={0}sid={1}", TVRAGE_APIKEY, id); + var xmlStream = _httpProvider.DownloadStream(url, null); + var xml = XDocument.Load(xmlStream); + var show = xml.Descendants("Show"); + var seasons = show.Descendants("Season"); + + var episodes = new List(); + + foreach (var season in seasons) + { + var eps = season.Descendants("episode"); + + foreach (var e in eps) + { + try + { + var episode = new TvRageEpisode(); + episode.EpisodeNumber = Int32.Parse(e.Element("epnum").Value); + episode.SeasonNumber = Int32.Parse(e.Element("seasonnum").Value); + episode.ProductionCode = e.Element("prodnum").Value; + episode.AirDate = DateTime.Parse(e.Element("airdate").Value); + episode.Link = e.Element("link").Value; + episode.Title = e.Element("title").Value; + episodes.Add(episode); + } + + catch (Exception ex) + { + logger.DebugException("Failed to parse TV Rage episode", ex); + } + } + } + + return episodes; + } + + internal int GetUtcOffset(string timeZone) + { + if (String.IsNullOrWhiteSpace(timeZone)) + return 0; + + var offsetString = timeZone.Substring(3, 2); + int offset; + + if (!Int32.TryParse(offsetString, out offset)) + return 0; + + if (timeZone.IndexOf("+DST", StringComparison.CurrentCultureIgnoreCase) > 0) + offset++; + + return offset; + } + } +}