diff --git a/NzbDrone.Core.Test/Files/Xem/Failure.txt b/NzbDrone.Core.Test/Files/Xem/Failure.txt new file mode 100644 index 000000000..63d217c10 --- /dev/null +++ b/NzbDrone.Core.Test/Files/Xem/Failure.txt @@ -0,0 +1,7 @@ +{ + + "result": "failure", + "data": [ ], + "message": "no show with the tvdb_id 79488 found" + +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/Files/Xem/Ids.txt b/NzbDrone.Core.Test/Files/Xem/Ids.txt new file mode 100644 index 000000000..fb005862c --- /dev/null +++ b/NzbDrone.Core.Test/Files/Xem/Ids.txt @@ -0,0 +1,24 @@ +{ + + "result": "success", + "data": { + "220571": [ + "Is This a Zombie? Of the Dead", + "Kore wa Zombie Desuka?", + "Kore wa Zombie Desuka? Of the Dead", + "Kore wa Zombie Desuka Of the Dead", + "Kore wa Zombie Desu ka - Of the Dead", + "Kore wa Zombie Desu ka of the Dead" + ], + "79151": [ + "Fate Stay Night", + "Fate/Zero", + "Fate Zero", + "Fate/Zero (2012)", + "Fate Zero S2", + "Fate Zero" + ] + }, + "message": "" + +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/Files/Xem/Mappings.txt b/NzbDrone.Core.Test/Files/Xem/Mappings.txt new file mode 100644 index 000000000..bc7f223ac --- /dev/null +++ b/NzbDrone.Core.Test/Files/Xem/Mappings.txt @@ -0,0 +1,32 @@ +{ + + "result": "success", + "data": [ + { + "scene": { + "season": 1, + "episode": 1, + "absolute": 1 + }, + "tvdb": { + "season": 1, + "episode": 1, + "absolute": 1 + } + }, + { + "scene": { + "season": 1, + "episode": 2, + "absolute": 2 + }, + "tvdb": { + "season": 1, + "episode": 2, + "absolute": 2 + } + } + ], + "message": "full mapping for 73388 on tvdb. this was a cached version" + +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 42e947a8a..f8b43b823 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -191,6 +191,8 @@ + + @@ -332,6 +334,15 @@ Designer Always + + Always + + + Always + + + Always + Designer Always diff --git a/NzbDrone.Core.Test/ProviderTests/XemCommunicationProviderTests/GetSceneTvdbMappingsFixture.cs b/NzbDrone.Core.Test/ProviderTests/XemCommunicationProviderTests/GetSceneTvdbMappingsFixture.cs new file mode 100644 index 000000000..f5ffc95ae --- /dev/null +++ b/NzbDrone.Core.Test/ProviderTests/XemCommunicationProviderTests/GetSceneTvdbMappingsFixture.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; +using System.Linq; +using System.Net; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Repository.Quality; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common.AutoMoq; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.ProviderTests.XemCommunicationProviderTests +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class GetSceneTvdbMappingsFixture : CoreTest + { + private void WithFailureJson() + { + Mocker.GetMock().Setup(s => s.DownloadString(It.IsAny())) + .Returns(File.ReadAllText(@".\Files\Xem\Failure.txt")); + } + + private void WithIdsJson() + { + Mocker.GetMock().Setup(s => s.DownloadString(It.IsAny())) + .Returns(File.ReadAllText(@".\Files\Xem\Ids.txt")); + } + + private void WithMappingsJson() + { + Mocker.GetMock().Setup(s => s.DownloadString(It.IsAny())) + .Returns(File.ReadAllText(@".\Files\Xem\Mappings.txt")); + } + + [Test] + public void should_throw_when_failure_is_found() + { + WithFailureJson(); + Assert.Throws(() => Mocker.Resolve().GetSceneTvdbMappings(12345)); + } + + [Test] + public void should_get_list_of_mappings() + { + WithMappingsJson(); + Mocker.Resolve().GetSceneTvdbMappings(12345).Should().NotBeEmpty(); + } + + [Test] + public void should_have_two_mappings() + { + WithMappingsJson(); + Mocker.Resolve().GetSceneTvdbMappings(12345).Should().HaveCount(2); + } + + [Test] + public void should_have_expected_results() + { + WithMappingsJson(); + var results = Mocker.Resolve().GetSceneTvdbMappings(12345); + var first = results.First(); + first.Scene.Absolute.Should().Be(1); + first.Scene.Season.Should().Be(1); + first.Scene.Episode.Should().Be(1); + first.Tvdb.Absolute.Should().Be(1); + first.Tvdb.Season.Should().Be(1); + first.Tvdb.Episode.Should().Be(1); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/ProviderTests/XemCommunicationProviderTests/GetXemSeriesIdsFixture.cs b/NzbDrone.Core.Test/ProviderTests/XemCommunicationProviderTests/GetXemSeriesIdsFixture.cs new file mode 100644 index 000000000..55c6885de --- /dev/null +++ b/NzbDrone.Core.Test/ProviderTests/XemCommunicationProviderTests/GetXemSeriesIdsFixture.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; +using System.Linq; +using System.Net; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Repository.Quality; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common.AutoMoq; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.ProviderTests.XemCommunicationProviderTests +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class GetXemSeriesIdsFixture : CoreTest + { + private void WithFailureJson() + { + Mocker.GetMock().Setup(s => s.DownloadString(It.IsAny())) + .Returns(File.ReadAllText(@".\Files\Xem\Failure.txt")); + } + + private void WithIdsJson() + { + Mocker.GetMock().Setup(s => s.DownloadString(It.IsAny())) + .Returns(File.ReadAllText(@".\Files\Xem\Ids.txt")); + } + + private void WithMappingsJson() + { + Mocker.GetMock().Setup(s => s.DownloadString(It.IsAny())) + .Returns(File.ReadAllText(@".\Files\Xem\Mappings.txt")); + } + + [Test] + public void should_throw_when_failure_is_found() + { + WithFailureJson(); + Assert.Throws(() => Mocker.Resolve().GetXemSeriesIds()); + } + + [Test] + public void should_get_list_of_int() + { + WithIdsJson(); + Mocker.Resolve().GetXemSeriesIds().Should().NotBeEmpty(); + } + + [Test] + public void should_have_two_ids() + { + WithIdsJson(); + Mocker.Resolve().GetXemSeriesIds().Should().HaveCount(2); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Datastore/Migrations/Migration20121016.cs b/NzbDrone.Core/Datastore/Migrations/Migration20121016.cs new file mode 100644 index 000000000..22aec34fc --- /dev/null +++ b/NzbDrone.Core/Datastore/Migrations/Migration20121016.cs @@ -0,0 +1,18 @@ +using System; +using System.Data; +using Migrator.Framework; +using NzbDrone.Common; + +namespace NzbDrone.Core.Datastore.Migrations +{ + [Migration(20121016)] + public class Migration20121016 : NzbDroneMigration + { + protected override void MainDbUpgrade() + { + Database.AddColumn("Episodes", new Column("SceneAbsoluteEpisodeNumber", DbType.Int32, ColumnProperty.Null)); + Database.AddColumn("Episodes", new Column("SceneSeasonNumber", DbType.Int32, ColumnProperty.Null)); + Database.AddColumn("Episodes", new Column("SceneEpisodeNumber", DbType.Int32, ColumnProperty.Null)); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Model/Xem/XemResult.cs b/NzbDrone.Core/Model/Xem/XemResult.cs new file mode 100644 index 000000000..408aa6551 --- /dev/null +++ b/NzbDrone.Core/Model/Xem/XemResult.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Model.Xem +{ + public class XemResult + { + public string Result { get; set; } + public T Data { get; set; } + public string Message { get; set; } + } +} diff --git a/NzbDrone.Core/Model/Xem/XemSceneTvdbMapping.cs b/NzbDrone.Core/Model/Xem/XemSceneTvdbMapping.cs new file mode 100644 index 000000000..29e242e5b --- /dev/null +++ b/NzbDrone.Core/Model/Xem/XemSceneTvdbMapping.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Model.Xem +{ + public class XemSceneTvdbMapping + { + public XemValues Scene { get; set; } + public XemValues Tvdb { get; set; } + } +} diff --git a/NzbDrone.Core/Model/Xem/XemValues.cs b/NzbDrone.Core/Model/Xem/XemValues.cs new file mode 100644 index 000000000..781fbd5c4 --- /dev/null +++ b/NzbDrone.Core/Model/Xem/XemValues.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Model.Xem +{ + public class XemValues + { + public int Season { get; set; } + public int Episode { get; set; } + public int Absolute { get; set; } + } +} diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 27f6da914..fd55c83f4 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -292,6 +292,9 @@ + + + @@ -330,6 +333,7 @@ + diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index e3e4bfb3e..398938c5e 100644 --- a/NzbDrone.Core/Providers/EpisodeProvider.cs +++ b/NzbDrone.Core/Providers/EpisodeProvider.cs @@ -331,7 +331,7 @@ namespace NzbDrone.Core.Providers episodeToUpdate.TvDbEpisodeId = episode.Id; episodeToUpdate.EpisodeNumber = episode.EpisodeNumber; episodeToUpdate.SeasonNumber = episode.SeasonNumber; - episodeToUpdate.AbsoluteEpisodeNumber = episode.AbsoluteEpisodeNumber; + episodeToUpdate.AbsoluteEpisodeNumber = episode.AbsoluteNumber; episodeToUpdate.Title = episode.EpisodeName; episodeToUpdate.Overview = episode.Overview.Truncate(3500); @@ -435,5 +435,10 @@ namespace NzbDrone.Core.Providers logger.Trace("Updating PostDownloadStatus for all episodeIds in {0}", episodeIdString); _database.Execute(episodeIdQuery); } + + public virtual void UpdateEpisodes(List episodes) + { + _database.UpdateMany(episodes); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/XemCommunicationProvider.cs b/NzbDrone.Core/Providers/XemCommunicationProvider.cs new file mode 100644 index 000000000..7c0d97e3a --- /dev/null +++ b/NzbDrone.Core/Providers/XemCommunicationProvider.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NLog; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Ninject; +using NzbDrone.Common; +using NzbDrone.Core.Model.Xem; + +namespace NzbDrone.Core.Providers +{ + public class XemCommunicationProvider + { + private readonly HttpProvider _httpProvider; + + private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); + + private const string XEM_BASE_URL = "http://thexem.de/map/"; + + [Inject] + public XemCommunicationProvider(HttpProvider httpProvider) + { + _httpProvider = httpProvider; + } + + public XemCommunicationProvider() + { + } + + public virtual List GetXemSeriesIds(string origin = "tvdb") + { + _logger.Trace("Fetching Series IDs from: {0}", origin); + var url = String.Format("{0}allNames?origin={1}", XEM_BASE_URL, origin); + var response =_httpProvider.DownloadString(url); + + CheckForFailureResult(response); + + var result = JsonConvert.DeserializeObject>>(JObject.Parse(response).SelectToken("data").ToString()); + + return result.Keys.ToList(); + } + + public virtual List GetSceneTvdbMappings(int id) + { + _logger.Trace("Fetching Mappings for: {0}", id); + var url = String.Format("{0}all?id={1}&origin=tvdb", XEM_BASE_URL, id); + var response = _httpProvider.DownloadString(url); + + CheckForFailureResult(response); + + var result = JsonConvert.DeserializeObject>(JObject.Parse(response).SelectToken("data").ToString()); + + return result; + } + + public virtual void CheckForFailureResult(string response) + { + var result = JsonConvert.DeserializeObject>(response); + + if (result != null && result.Result.Equals("failure", StringComparison.InvariantCultureIgnoreCase)) + throw new Exception("Error response received from Xem: " + result.Message); + } + } +} diff --git a/NzbDrone.Core/Providers/XemProvider.cs b/NzbDrone.Core/Providers/XemProvider.cs new file mode 100644 index 000000000..83381fb0a --- /dev/null +++ b/NzbDrone.Core/Providers/XemProvider.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NLog; +using Ninject; +using NzbDrone.Core.Repository; + +namespace NzbDrone.Core.Providers +{ + public class XemProvider + { + private readonly SeriesProvider _seriesProvider; + private readonly EpisodeProvider _episodeProvider; + private readonly XemCommunicationProvider _xemCommunicationProvider; + + private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); + + [Inject] + public XemProvider(SeriesProvider seriesProvider, EpisodeProvider episodeProvider, + XemCommunicationProvider xemCommunicationProvider) + { + _seriesProvider = seriesProvider; + _episodeProvider = episodeProvider; + _xemCommunicationProvider = xemCommunicationProvider; + } + + public XemProvider() + { + } + + public virtual void UpdateMappings() + { + _logger.Trace("Starting scene numbering update"); + try + { + var ids = _xemCommunicationProvider.GetXemSeriesIds(); + var series = _seriesProvider.GetAllSeries(); + var wantedSeries = series.Where(s => ids.Contains(s.SeriesId)); + + foreach(var ser in wantedSeries) + { + _logger.Trace("Updating scene numbering mapping for: {0}", ser.Title); + try + { + var episodesToUpdate = new List(); + var mappings = _xemCommunicationProvider.GetSceneTvdbMappings(ser.SeriesId); + + if (mappings == null) + { + _logger.Trace("Mappings for: {0} are null, skipping", ser.Title); + continue; + } + + foreach(var mapping in mappings) + { + _logger.Trace("Setting scene numbering mappings for {0} S{1:00}E{2:00}", ser.Title, mapping.Tvdb.Season, mapping.Tvdb.Episode); + + var episode = _episodeProvider.GetEpisode(ser.SeriesId, mapping.Tvdb.Season, mapping.Tvdb.Episode); + episode.AbsoluteEpisodeNumber = mapping.Scene.Absolute; + episode.SceneSeasonNumber = mapping.Scene.Season; + episode.SceneEpisodeNumber = mapping.Scene.Episode; + episodesToUpdate.Add(episode); + } + + _logger.Trace("Committing scene numbering mappings to database for: {0}", ser.Title); + _episodeProvider.UpdateEpisodes(episodesToUpdate); + } + + catch(Exception ex) + { + _logger.WarnException("Error updating scene numbering mappings for: " + ser, ex); + } + } + + _logger.Trace("Completed scene numbering update"); + } + + catch(Exception ex) + { + _logger.WarnException("Error updating Scene Mappings", ex); + throw; + } + } + } +} diff --git a/NzbDrone.Core/Repository/Episode.cs b/NzbDrone.Core/Repository/Episode.cs index 9c3e38968..f93782fd6 100644 --- a/NzbDrone.Core/Repository/Episode.cs +++ b/NzbDrone.Core/Repository/Episode.cs @@ -22,6 +22,9 @@ namespace NzbDrone.Core.Repository public Boolean Ignored { get; set; } public PostDownloadStatusType PostDownloadStatus { get; set; } public int AbsoluteEpisodeNumber { get; set; } + public int SceneAbsoluteEpisodeNumber { get; set; } + public int SceneSeasonNumber { get; set; } + public int SceneEpisodeNumber { get; set; } /// /// Gets or sets the grab date.