diff --git a/NzbDrone.Api/NzbDrone.Api.csproj b/NzbDrone.Api/NzbDrone.Api.csproj index 0be2d8ce9..6d918a2ef 100644 --- a/NzbDrone.Api/NzbDrone.Api.csproj +++ b/NzbDrone.Api/NzbDrone.Api.csproj @@ -140,8 +140,6 @@ - - diff --git a/NzbDrone.Api/Seasons/SeasonModule.cs b/NzbDrone.Api/Seasons/SeasonModule.cs deleted file mode 100644 index 0521b8518..000000000 --- a/NzbDrone.Api/Seasons/SeasonModule.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Collections.Generic; -using NzbDrone.Api.Mapping; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Api.Seasons -{ - public class SeasonModule : NzbDroneRestModule - { - private readonly ISeasonService _seasonService; - - public SeasonModule(ISeasonService seasonService) - : base("/season") - { - _seasonService = seasonService; - - GetResourceAll = GetSeasons; - GetResourceById = GetSeason; - UpdateResource = Update; - - Post["/pass"] = x => SetSeasonPass(); - } - - private List GetSeasons() - { - var seriesId = Request.Query.SeriesId; - - if (seriesId.HasValue) - { - return ToListResource(() => _seasonService.GetSeasonsBySeries(seriesId)); - } - - return ToListResource(() => _seasonService.GetAllSeasons()); - } - - private SeasonResource GetSeason(int id) - { - return _seasonService.Get(id).InjectTo(); - } - - private void Update(SeasonResource seasonResource) - { - _seasonService.SetMonitored(seasonResource.SeriesId, seasonResource.SeasonNumber, seasonResource.Monitored); - } - - private List SetSeasonPass() - { - var seriesId = Request.Form.SeriesId; - var seasonNumber = Request.Form.SeasonNumber; - - return ToListResource(() => _seasonService.SetSeasonPass(seriesId, seasonNumber)); - } - } -} \ No newline at end of file diff --git a/NzbDrone.Api/Seasons/SeasonResource.cs b/NzbDrone.Api/Seasons/SeasonResource.cs deleted file mode 100644 index 46e14be9d..000000000 --- a/NzbDrone.Api/Seasons/SeasonResource.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using NzbDrone.Api.REST; - -namespace NzbDrone.Api.Seasons -{ - public class SeasonResource : RestResource - { - public int SeriesId { get; set; } - public int SeasonNumber { get; set; } - public Boolean Monitored { get; set; } - } -} diff --git a/NzbDrone.Api/Series/SeriesModule.cs b/NzbDrone.Api/Series/SeriesModule.cs index fe7b820a6..ea01b1a06 100644 --- a/NzbDrone.Api/Series/SeriesModule.cs +++ b/NzbDrone.Api/Series/SeriesModule.cs @@ -117,7 +117,6 @@ namespace NzbDrone.Api.Series { resource.EpisodeCount = seriesStatistics.EpisodeCount; resource.EpisodeFileCount = seriesStatistics.EpisodeFileCount; - resource.SeasonCount = seriesStatistics.SeasonCount; resource.NextAiring = seriesStatistics.NextAiring; } } diff --git a/NzbDrone.Api/Series/SeriesResource.cs b/NzbDrone.Api/Series/SeriesResource.cs index 34f7dcead..6d9557f32 100644 --- a/NzbDrone.Api/Series/SeriesResource.cs +++ b/NzbDrone.Api/Series/SeriesResource.cs @@ -14,7 +14,17 @@ namespace NzbDrone.Api.Series //View Only public String Title { get; set; } - public Int32 SeasonCount { get; set; } + + public Int32 SeasonCount + { + get + { + if (Seasons != null) return Seasons.Count; + + return 0; + } + } + public Int32 EpisodeCount { get; set; } public Int32 EpisodeFileCount { get; set; } public SeriesStatusType Status { get; set; } @@ -26,7 +36,7 @@ namespace NzbDrone.Api.Series public List Images { get; set; } public String RemotePoster { get; set; } - + public List Seasons { get; set; } //View & Edit public String Path { get; set; } diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 40131b5d9..d882f7905 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -175,7 +175,6 @@ - @@ -187,9 +186,6 @@ - - - diff --git a/NzbDrone.Core.Test/TvTests/SeasonProviderTest.cs b/NzbDrone.Core.Test/TvTests/SeasonProviderTest.cs deleted file mode 100644 index efb11c7bb..000000000 --- a/NzbDrone.Core.Test/TvTests/SeasonProviderTest.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Linq; -using FizzWare.NBuilder; -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Core.Tv; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.TvTests -{ - public class SeasonProviderTest : DbTest - { - [TestCase(true)] - [TestCase(false)] - public void Ismonitored_should_return_monitored_status_of_season(bool monitored) - { - var fakeSeason = Builder.CreateNew() - .With(s => s.Monitored = monitored) - .BuildNew(); - - Db.Insert(fakeSeason); - - var result = Subject.IsMonitored(fakeSeason.SeriesId, fakeSeason.SeasonNumber); - - result.Should().Be(monitored); - } - - [Test] - public void Monitored_should_return_true_if_not_in_db() - { - Subject.IsMonitored(10, 0).Should().BeTrue(); - } - - [Test] - public void GetSeason_should_return_seasons_for_specified_series_only() - { - var seriesA = new[] { 1, 2, 3 }; - var seriesB = new[] { 4, 5, 6 }; - - var seasonsA = seriesA.Select(c => new Season {SeasonNumber = c, SeriesId = 1}).ToList(); - var seasonsB = seriesB.Select(c => new Season {SeasonNumber = c, SeriesId = 2}).ToList(); - - Subject.InsertMany(seasonsA); - Subject.InsertMany(seasonsB); - - Subject.GetSeasonNumbers(1).Should().Equal(seriesA); - Subject.GetSeasonNumbers(2).Should().Equal(seriesB); - } - - - [Test] - public void GetSeason_should_return_emptylist_if_series_doesnt_exist() - { - Subject.GetSeasonNumbers(1).Should().BeEmpty(); - } - - } -} diff --git a/NzbDrone.Core.Test/TvTests/SeasonServiceTests/HandleEpisodeInfoDeletedEventFixture.cs b/NzbDrone.Core.Test/TvTests/SeasonServiceTests/HandleEpisodeInfoDeletedEventFixture.cs deleted file mode 100644 index d31c2b679..000000000 --- a/NzbDrone.Core.Test/TvTests/SeasonServiceTests/HandleEpisodeInfoDeletedEventFixture.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using FizzWare.NBuilder; -using Moq; -using NUnit.Framework; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; -using NzbDrone.Core.Tv.Events; - -namespace NzbDrone.Core.Test.TvTests.SeasonServiceTests -{ - [TestFixture] - public class HandleEpisodeInfoDeletedEventFixture : CoreTest - { - private List _seasons; - private List _episodes; - - [SetUp] - public void Setup() - { - _seasons = Builder - .CreateListOfSize(1) - .All() - .With(s => s.SeriesId = 1) - .Build() - .ToList(); - - _episodes = Builder - .CreateListOfSize(1) - .All() - .With(e => e.SeasonNumber = _seasons.First().SeasonNumber) - .With(s => s.SeriesId = _seasons.First().SeasonNumber) - .Build() - .ToList(); - - Mocker.GetMock() - .Setup(s => s.GetSeasonBySeries(It.IsAny())) - .Returns(_seasons); - - Mocker.GetMock() - .Setup(s => s.GetEpisodesBySeason(It.IsAny(), _seasons.First().SeasonNumber)) - .Returns(_episodes); - } - - private void GivenAbandonedSeason() - { - Mocker.GetMock() - .Setup(s => s.GetEpisodesBySeason(It.IsAny(), _seasons.First().SeasonNumber)) - .Returns(new List()); - } - - [Test] - public void should_not_delete_when_season_is_still_valid() - { - Subject.Handle(new EpisodeInfoDeletedEvent(_episodes)); - - Mocker.GetMock() - .Verify(v => v.Delete(It.IsAny()), Times.Never()); - } - - [Test] - public void should_delete_season_if_no_episodes_exist_in_that_season() - { - GivenAbandonedSeason(); - - Subject.Handle(new EpisodeInfoDeletedEvent(_episodes)); - - Mocker.GetMock() - .Verify(v => v.Delete(It.IsAny()), Times.Once()); - } - - [Test] - public void should_only_delete_a_season_once() - { - _episodes = Builder - .CreateListOfSize(5) - .All() - .With(e => e.SeasonNumber = _seasons.First().SeasonNumber) - .With(s => s.SeriesId = _seasons.First().SeasonNumber) - .Build() - .ToList(); - - GivenAbandonedSeason(); - - Subject.Handle(new EpisodeInfoDeletedEvent(_episodes)); - - Mocker.GetMock() - .Verify(v => v.Delete(It.IsAny()), Times.Once()); - } - } -} diff --git a/NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetMonitoredFixture.cs b/NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetMonitoredFixture.cs deleted file mode 100644 index 775d7d41c..000000000 --- a/NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetMonitoredFixture.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Moq; -using NUnit.Framework; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Core.Test.TvTests.SeasonServiceTests -{ - [TestFixture] - public class SetMonitoredFixture : CoreTest - { - private Season _season; - - [SetUp] - public void Setup() - { - _season = new Season - { - Id = 1, - SeasonNumber = 1, - SeriesId = 1, - Monitored = false - }; - - Mocker.GetMock() - .Setup(s => s.Get(It.IsAny(), It.IsAny())) - .Returns(_season); - } - - [TestCase(true)] - [TestCase(false)] - public void should_update_season(bool monitored) - { - Subject.SetMonitored(_season.SeriesId, _season.SeasonNumber, monitored); - - Mocker.GetMock() - .Verify(v => v.Update(_season), Times.Once()); - } - - [TestCase(true)] - [TestCase(false)] - public void should_update_episodes_in_season(bool monitored) - { - Subject.SetMonitored(_season.SeriesId, _season.SeasonNumber, monitored); - - Mocker.GetMock() - .Verify(v => v.SetEpisodeMonitoredBySeason(_season.SeriesId, _season.SeasonNumber, monitored), Times.Once()); - } - } -} diff --git a/NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetSeasonPassFixture.cs b/NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetSeasonPassFixture.cs deleted file mode 100644 index b8ac4a9ea..000000000 --- a/NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetSeasonPassFixture.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using FizzWare.NBuilder; -using FluentAssertions; -using Moq; -using NUnit.Framework; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Core.Test.TvTests.SeasonServiceTests -{ - [TestFixture] - public class SetSeasonPassFixture : CoreTest - { - private const Int32 SERIES_ID = 1; - private List _seasons; - - [SetUp] - public void Setup() - { - _seasons = Builder.CreateListOfSize(5) - .All() - .With(s => s.SeriesId = SERIES_ID) - .Build() - .ToList(); - - Mocker.GetMock() - .Setup(s => s.GetSeasonBySeries(It.IsAny())) - .Returns(_seasons); - } - - [Test] - public void should_updateMany() - { - Subject.SetSeasonPass(SERIES_ID, 1); - - Mocker.GetMock() - .Verify(v => v.UpdateMany(It.IsAny>()), Times.Once()); - } - - [Test] - public void should_set_lower_seasons_to_false() - { - const int seasonNumber = 3; - - var result = Subject.SetSeasonPass(SERIES_ID, seasonNumber); - - result.Where(s => s.SeasonNumber < seasonNumber).Should().OnlyContain(s => s.Monitored == false); - } - - [Test] - public void should_set_equal_or_higher_seasons_to_false() - { - const int seasonNumber = 3; - - var result = Subject.SetSeasonPass(SERIES_ID, seasonNumber); - - result.Where(s => s.SeasonNumber >= seasonNumber).Should().OnlyContain(s => s.Monitored == true); - } - - [Test] - public void should_set_episodes_in_lower_seasons_to_false() - { - const int seasonNumber = 3; - - Subject.SetSeasonPass(SERIES_ID, seasonNumber); - - Mocker.GetMock() - .Verify(v => v.SetEpisodeMonitoredBySeason(SERIES_ID, It.Is(i => i < seasonNumber), false), Times.AtLeastOnce()); - - Mocker.GetMock() - .Verify(v => v.SetEpisodeMonitoredBySeason(SERIES_ID, It.Is(i => i < seasonNumber), true), Times.Never()); - } - - [Test] - public void should_set_episodes_in_equal_or_higher_seasons_to_false() - { - const int seasonNumber = 3; - - Subject.SetSeasonPass(SERIES_ID, seasonNumber); - - Mocker.GetMock() - .Verify(v => v.SetEpisodeMonitoredBySeason(SERIES_ID, It.Is(i => i >= seasonNumber), true), Times.AtLeastOnce()); - - Mocker.GetMock() - .Verify(v => v.SetEpisodeMonitoredBySeason(SERIES_ID, It.Is(i => i >= seasonNumber), false), Times.Never()); - } - } -} diff --git a/NzbDrone.Core/Datastore/Migration/020_add_year_and_seasons_to_series.cs b/NzbDrone.Core/Datastore/Migration/020_add_year_and_seasons_to_series.cs new file mode 100644 index 000000000..30323d4d2 --- /dev/null +++ b/NzbDrone.Core/Datastore/Migration/020_add_year_and_seasons_to_series.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Data; +using FluentMigrator; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(20)] + public class add_year_and_seasons_to_series : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("Series").AddColumn("Year").AsInt32().Nullable(); + Alter.Table("Series").AddColumn("Seasons").AsString().Nullable(); + + Execute.WithConnection(ConvertSeasons); + } + + private void ConvertSeasons(IDbConnection conn, IDbTransaction tran) + { + using (IDbCommand allSeriesCmd = conn.CreateCommand()) + { + allSeriesCmd.Transaction = tran; + allSeriesCmd.CommandText = @"SELECT Id FROM Series"; + using (IDataReader allSeriesReader = allSeriesCmd.ExecuteReader()) + { + while (allSeriesReader.Read()) + { + int seriesId = allSeriesReader.GetInt32(0); + var seasons = new List(); + + using (IDbCommand seasonsCmd = conn.CreateCommand()) + { + seasonsCmd.Transaction = tran; + seasonsCmd.CommandText = String.Format(@"SELECT SeasonNumber, Monitored FROM Seasons WHERE SeriesId = {0}", seriesId); + + using (IDataReader seasonReader = seasonsCmd.ExecuteReader()) + { + while (seasonReader.Read()) + { + int seasonNumber = seasonReader.GetInt32(0); + bool monitored = seasonReader.GetBoolean(1); + + seasons.Add(new { seasonNumber, monitored }); + } + } + } + + using (IDbCommand updateCmd = conn.CreateCommand()) + { + var text = String.Format("UPDATE Series SET Seasons = '{0}' WHERE Id = {1}", seasons.ToJson() , seriesId); + + updateCmd.Transaction = tran; + updateCmd.CommandText = text; + updateCmd.ExecuteNonQuery(); + } + } + } + } + } + } +} diff --git a/NzbDrone.Core/Datastore/Migration/021_drop_seasons_table.cs b/NzbDrone.Core/Datastore/Migration/021_drop_seasons_table.cs new file mode 100644 index 000000000..d2527c755 --- /dev/null +++ b/NzbDrone.Core/Datastore/Migration/021_drop_seasons_table.cs @@ -0,0 +1,14 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(21)] + public class drop_seasons_table : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Delete.Table("Seasons"); + } + } +} diff --git a/NzbDrone.Core/Datastore/TableMapping.cs b/NzbDrone.Core/Datastore/TableMapping.cs index 80242f783..533b3ab70 100644 --- a/NzbDrone.Core/Datastore/TableMapping.cs +++ b/NzbDrone.Core/Datastore/TableMapping.cs @@ -45,8 +45,6 @@ namespace NzbDrone.Core.Datastore .Relationship() .HasOne(s => s.QualityProfile, s => s.QualityProfileId); - Mapper.Entity().RegisterModel("Seasons"); - Mapper.Entity().RegisterModel("Episodes") .Ignore(e => e.SeriesTitle) .Ignore(e => e.Series) diff --git a/NzbDrone.Core/MetadataSource/Trakt/FullShow.cs b/NzbDrone.Core/MetadataSource/Trakt/FullShow.cs index 32c60c5fb..bf7745d43 100644 --- a/NzbDrone.Core/MetadataSource/Trakt/FullShow.cs +++ b/NzbDrone.Core/MetadataSource/Trakt/FullShow.cs @@ -29,31 +29,4 @@ namespace NzbDrone.Core.MetadataSource.Trakt public List genres { get; set; } public List seasons { get; set; } } - - public class SearchShow - { - public string title { get; set; } - public int year { get; set; } - public string url { get; set; } - public int first_aired { get; set; } - public string first_aired_iso { get; set; } - public int first_aired_utc { get; set; } - public string country { get; set; } - public string overview { get; set; } - public int runtime { get; set; } - public string status { get; set; } - public string network { get; set; } - public string air_day { get; set; } - public string air_day_utc { get; set; } - public string air_time { get; set; } - public string air_time_utc { get; set; } - public string certification { get; set; } - public string imdb_id { get; set; } - public int tvdb_id { get; set; } - public int tvrage_id { get; set; } - public int last_updated { get; set; } - public string poster { get; set; } - public Images images { get; set; } - public List genres { get; set; } - } } \ No newline at end of file diff --git a/NzbDrone.Core/MetadataSource/TraktProxy.cs b/NzbDrone.Core/MetadataSource/TraktProxy.cs index f488a4f65..53fd0c4e7 100644 --- a/NzbDrone.Core/MetadataSource/TraktProxy.cs +++ b/NzbDrone.Core/MetadataSource/TraktProxy.cs @@ -30,10 +30,10 @@ namespace NzbDrone.Core.MetadataSource try { var client = BuildClient("search", "shows"); - var restRequest = new RestRequest(GetSearchTerm(title)); - var response = client.ExecuteAndValidate>(restRequest); + var restRequest = new RestRequest(GetSearchTerm(title) +"/30/seasons"); + var response = client.ExecuteAndValidate>(restRequest); - return response.Select(MapSearchSeries).ToList(); + return response.Select(MapSeries).ToList(); } catch (WebException ex) { @@ -71,6 +71,7 @@ namespace NzbDrone.Core.MetadataSource series.ImdbId = show.imdb_id; series.Title = show.title; series.CleanTitle = Parser.Parser.CleanSeriesTitle(show.title); + series.Year = show.year; series.FirstAired = FromIso(show.first_aired_iso); series.Overview = show.overview; series.Runtime = show.runtime; @@ -79,27 +80,10 @@ namespace NzbDrone.Core.MetadataSource series.TitleSlug = show.url.ToLower().Replace("http://trakt.tv/show/", ""); series.Status = GetSeriesStatus(show.status); - series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Banner, Url = show.images.banner }); - series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Poster, Url = GetPosterThumbnailUrl(show.images.poster) }); - series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Fanart, Url = show.images.fanart }); - return series; - } - - private static Series MapSearchSeries(SearchShow show) - { - var series = new Series(); - series.TvdbId = show.tvdb_id; - series.TvRageId = show.tvrage_id; - series.ImdbId = show.imdb_id; - series.Title = show.title; - series.CleanTitle = Parser.Parser.CleanSeriesTitle(show.title); - series.FirstAired = FromIso(show.first_aired_iso); - series.Overview = show.overview; - series.Runtime = show.runtime; - series.Network = show.network; - series.AirTime = show.air_time_utc; - series.TitleSlug = show.url.ToLower().Replace("http://trakt.tv/show/", ""); - series.Status = GetSeriesStatus(show.status); + series.Seasons = show.seasons.Select(s => new Tv.Season + { + SeasonNumber = s.season + }).ToList(); series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Banner, Url = show.images.banner }); series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Poster, Url = GetPosterThumbnailUrl(show.images.poster) }); diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 8fa7bbee6..e2a6f8cb3 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -162,6 +162,8 @@ + + diff --git a/NzbDrone.Core/SeriesStats/SeriesStatistics.cs b/NzbDrone.Core/SeriesStats/SeriesStatistics.cs index 24893eb26..170985718 100644 --- a/NzbDrone.Core/SeriesStats/SeriesStatistics.cs +++ b/NzbDrone.Core/SeriesStats/SeriesStatistics.cs @@ -6,7 +6,6 @@ namespace NzbDrone.Core.SeriesStats public class SeriesStatistics : ResultSet { public int SeriesId { get; set; } - public int SeasonCount { get; set; } public string NextAiringString { get; set; } public int EpisodeFileCount { get; set; } public int EpisodeCount { get; set; } diff --git a/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs b/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs index 752989d31..09244dc06 100644 --- a/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs +++ b/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs @@ -56,7 +56,6 @@ namespace NzbDrone.Core.SeriesStats SeriesId, SUM(CASE WHEN (Monitored = 1 AND AirdateUtc <= @currentDate) OR EpisodeFileId > 0 THEN 1 ELSE 0 END) AS EpisodeCount, SUM(CASE WHEN EpisodeFileId > 0 THEN 1 ELSE 0 END) AS EpisodeFileCount, - COUNT(DISTINCT(CASE WHEN SeasonNumber > 0 THEN SeasonNumber ELSE NULL END)) as SeasonCount, MIN(CASE WHEN AirDateUtc < @currentDate OR EpisodeFileId > 0 THEN NULL ELSE AirDateUtc END) AS NextAiringString FROM Episodes"; } diff --git a/NzbDrone.Core/Tv/RefreshSeriesService.cs b/NzbDrone.Core/Tv/RefreshSeriesService.cs index 6e9f38dda..e776a4a18 100644 --- a/NzbDrone.Core/Tv/RefreshSeriesService.cs +++ b/NzbDrone.Core/Tv/RefreshSeriesService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using NLog; @@ -64,6 +65,8 @@ namespace NzbDrone.Core.Tv _logger.WarnException("Couldn't update series path for " + series.Path, e); } + series.Seasons = UpdateSeasons(series, seriesInfo); + _seriesService.UpdateSeries(series); _refreshEpisodeService.RefreshEpisodeInfo(series, tuple.Item2); @@ -71,6 +74,21 @@ namespace NzbDrone.Core.Tv _messageAggregator.PublishEvent(new SeriesUpdatedEvent(series)); } + private List UpdateSeasons(Series series, Series seriesInfo) + { + foreach (var season in seriesInfo.Seasons) + { + var existingSeason = series.Seasons.SingleOrDefault(s => s.SeasonNumber == season.SeasonNumber); + + if (existingSeason != null) + { + season.Monitored = existingSeason.Monitored; + } + } + + return seriesInfo.Seasons; + } + public void Execute(RefreshSeriesCommand message) { if (message.SeriesId.HasValue) diff --git a/NzbDrone.Core/Tv/Season.cs b/NzbDrone.Core/Tv/Season.cs index be36fcc86..0f9ed168c 100644 --- a/NzbDrone.Core/Tv/Season.cs +++ b/NzbDrone.Core/Tv/Season.cs @@ -4,12 +4,9 @@ using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Tv { - public class Season : ModelBase + public class Season : IEmbeddedDocument { - public int SeriesId { get; set; } public int SeasonNumber { get; set; } public Boolean Monitored { get; set; } - - public List Episodes { get; set; } } } \ No newline at end of file diff --git a/NzbDrone.Core/Tv/SeasonRepository.cs b/NzbDrone.Core/Tv/SeasonRepository.cs index f580c8d94..7fc3e65af 100644 --- a/NzbDrone.Core/Tv/SeasonRepository.cs +++ b/NzbDrone.Core/Tv/SeasonRepository.cs @@ -6,44 +6,35 @@ using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Tv { - public interface ISeasonRepository : IBasicRepository + public interface ISeasonRepository : IBasicRepository { - IList GetSeasonNumbers(int seriesId); Season Get(int seriesId, int seasonNumber); bool IsMonitored(int seriesId, int seasonNumber); List GetSeasonBySeries(int seriesId); } - public class SeasonRepository : BasicRepository, ISeasonRepository + public class SeasonRepository : BasicRepository, ISeasonRepository { - public SeasonRepository(IDatabase database, IMessageAggregator messageAggregator) : base(database, messageAggregator) { } - public IList GetSeasonNumbers(int seriesId) - { - return Query.Where(c => c.SeriesId == seriesId).Select(c => c.SeasonNumber).ToList(); - } - public Season Get(int seriesId, int seasonNumber) { - return Query.Single(s => s.SeriesId == seriesId && s.SeasonNumber == seasonNumber); + var series = Query.Single(s => s.Id == seriesId); + return series.Seasons.Single(s => s.SeasonNumber == seasonNumber); } public bool IsMonitored(int seriesId, int seasonNumber) { - var season = Query.SingleOrDefault(s => s.SeriesId == seriesId && s.SeasonNumber == seasonNumber); - - if (season == null) return true; - - return season.Monitored; + var series = Query.Single(s => s.Id == seriesId); + return series.Seasons.Single(s => s.SeasonNumber == seasonNumber).Monitored; } public List GetSeasonBySeries(int seriesId) { - return Query.Where(s => s.SeriesId == seriesId); + return Query.Single(s => s.Id == seriesId).Seasons; } } } \ No newline at end of file diff --git a/NzbDrone.Core/Tv/SeasonService.cs b/NzbDrone.Core/Tv/SeasonService.cs index 6355796a4..7581942f7 100644 Binary files a/NzbDrone.Core/Tv/SeasonService.cs and b/NzbDrone.Core/Tv/SeasonService.cs differ diff --git a/NzbDrone.Core/Tv/Series.cs b/NzbDrone.Core/Tv/Series.cs index 55a6adb73..107162aaf 100644 --- a/NzbDrone.Core/Tv/Series.cs +++ b/NzbDrone.Core/Tv/Series.cs @@ -34,12 +34,15 @@ namespace NzbDrone.Core.Tv public bool UseSceneNumbering { get; set; } public string TitleSlug { get; set; } public string Path { get; set; } + public int Year { get; set; } public string RootFolderPath { get; set; } public DateTime? FirstAired { get; set; } public LazyLoaded QualityProfile { get; set; } + public List Seasons { get; set; } + public override string ToString() { return string.Format("[{0}][{1}]", TvdbId, Title.NullSafe()); diff --git a/NzbDrone.Core/Tv/SeriesService.cs b/NzbDrone.Core/Tv/SeriesService.cs index 7c6dba091..27aab2b7e 100644 --- a/NzbDrone.Core/Tv/SeriesService.cs +++ b/NzbDrone.Core/Tv/SeriesService.cs @@ -37,18 +37,21 @@ namespace NzbDrone.Core.Tv private readonly IConfigService _configService; private readonly IMessageAggregator _messageAggregator; private readonly ISceneMappingService _sceneMappingService; + private readonly IEpisodeService _episodeService; private readonly Logger _logger; public SeriesService(ISeriesRepository seriesRepository, IConfigService configServiceService, IMessageAggregator messageAggregator, ISceneMappingService sceneMappingService, + IEpisodeService episodeService, Logger logger) { _seriesRepository = seriesRepository; _configService = configServiceService; _messageAggregator = messageAggregator; _sceneMappingService = sceneMappingService; + _episodeService = episodeService; _logger = logger; } @@ -155,6 +158,11 @@ namespace NzbDrone.Core.Tv public Series UpdateSeries(Series series) { + foreach (var season in series.Seasons) + { + _episodeService.SetEpisodeMonitoredBySeason(series.Id, season.SeasonNumber, season.Monitored); + } + return _seriesRepository.Update(series); } diff --git a/NzbDrone.Integration.Test/Client/SeasonClient.cs b/NzbDrone.Integration.Test/Client/SeasonClient.cs deleted file mode 100644 index af0572a70..000000000 --- a/NzbDrone.Integration.Test/Client/SeasonClient.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using System.Net; -using NzbDrone.Api.Episodes; -using NzbDrone.Api.Seasons; -using RestSharp; - -namespace NzbDrone.Integration.Test.Client -{ - public class SeasonClient : ClientBase - { - public SeasonClient(IRestClient restClient) - : base(restClient) - { - } - - public List GetSeasonsInSeries(int seriesId) - { - var request = BuildRequest("?seriesId=" + seriesId.ToString()); - return Get>(request); - } - } -} diff --git a/NzbDrone.Integration.Test/IntegrationTest.cs b/NzbDrone.Integration.Test/IntegrationTest.cs index 562b24175..996f7350e 100644 --- a/NzbDrone.Integration.Test/IntegrationTest.cs +++ b/NzbDrone.Integration.Test/IntegrationTest.cs @@ -27,7 +27,6 @@ namespace NzbDrone.Integration.Test protected ClientBase History; protected IndexerClient Indexers; protected EpisodeClient Episodes; - protected SeasonClient Seasons; protected ClientBase NamingConfig; private NzbDroneRunner _runner; @@ -64,7 +63,6 @@ namespace NzbDrone.Integration.Test History = new ClientBase(RestClient); Indexers = new IndexerClient(RestClient); Episodes = new EpisodeClient(RestClient); - Seasons = new SeasonClient(RestClient); NamingConfig = new ClientBase(RestClient, "config/naming"); } @@ -75,5 +73,4 @@ namespace NzbDrone.Integration.Test _runner.KillAll(); } } - } diff --git a/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj b/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj index 040fd500b..9bba4d240 100644 --- a/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj +++ b/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj @@ -94,7 +94,6 @@ - @@ -102,7 +101,6 @@ - diff --git a/NzbDrone.Integration.Test/SeasonIntegrationTests.cs b/NzbDrone.Integration.Test/SeasonIntegrationTests.cs deleted file mode 100644 index 9fa6177f3..000000000 --- a/NzbDrone.Integration.Test/SeasonIntegrationTests.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Threading; -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Api.Series; -using System.Linq; -using NzbDrone.Test.Common; - -namespace NzbDrone.Integration.Test -{ - [TestFixture] - public class SeasonIntegrationTests : IntegrationTest - { - private SeriesResource GivenSeriesWithEpisodes() - { - var series = Series.Lookup("archer").First(); - - series.QualityProfileId = 1; - series.Path = @"C:\Test\Archer".AsOsAgnostic(); - - series = Series.Post(series); - - while (true) - { - if (Seasons.GetSeasonsInSeries(series.Id).Count > 0) - { - return series; - } - - Thread.Sleep(1000); - } - } - - [Test] - public void should_be_able_to_get_all_seasons_in_series() - { - var series = GivenSeriesWithEpisodes(); - Seasons.GetSeasonsInSeries(series.Id).Count.Should().BeGreaterThan(0); - } - - [Test] - public void should_be_able_to_get_a_single_season() - { - var series = GivenSeriesWithEpisodes(); - var seasons = Seasons.GetSeasonsInSeries(series.Id); - - Seasons.Get(seasons.First().Id).Should().NotBeNull(); - } - - [Test] - public void should_be_able_to_set_monitor_status_via_api() - { - var series = GivenSeriesWithEpisodes(); - var seasons = Seasons.GetSeasonsInSeries(series.Id); - var updatedSeason = seasons.First(); - updatedSeason.Monitored = false; - - Seasons.Put(updatedSeason).Monitored.Should().BeFalse(); - } - } -} \ No newline at end of file diff --git a/UI/AddSeries/SearchResultTemplate.html b/UI/AddSeries/SearchResultTemplate.html index e60f15039..47ec8a9b3 100644 --- a/UI/AddSeries/SearchResultTemplate.html +++ b/UI/AddSeries/SearchResultTemplate.html @@ -2,8 +2,7 @@
diff --git a/UI/Content/icons.less b/UI/Content/icons.less index 06c6292cd..e48924f8b 100644 --- a/UI/Content/icons.less +++ b/UI/Content/icons.less @@ -113,4 +113,12 @@ .icon-nd-status:before { .icon(@circle); +} + +.icon-nd-monitored:before { + .icon(@bookmark); +} + +.icon-nd-unmonitored:before { + .icon(@bookmark-empty); } \ No newline at end of file diff --git a/UI/SeasonPass/Layout.js b/UI/SeasonPass/Layout.js index cb4500daf..ebf2609cf 100644 --- a/UI/SeasonPass/Layout.js +++ b/UI/SeasonPass/Layout.js @@ -24,16 +24,10 @@ define( this.series.show(new LoadingView()); this.seriesCollection = SeriesCollection; - this.seasonCollection = new SeasonCollection(); - var promise = this.seasonCollection.fetch(); - - promise.done(function () { - self.series.show(new SeriesCollectionView({ - collection: self.seriesCollection, - seasonCollection: self.seasonCollection - })); - }); + self.series.show(new SeriesCollectionView({ + collection: self.seriesCollection + })); } }); }); diff --git a/UI/SeasonPass/SeriesCollectionView.js b/UI/SeasonPass/SeriesCollectionView.js index 48cf5c39e..d5de2530e 100644 --- a/UI/SeasonPass/SeriesCollectionView.js +++ b/UI/SeasonPass/SeriesCollectionView.js @@ -5,22 +5,6 @@ define( 'SeasonPass/SeriesLayout' ], function (Marionette, SeriesLayout) { return Marionette.CollectionView.extend({ - - itemView: SeriesLayout, - - initialize: function (options) { - - if (!options.seasonCollection) { - throw 'seasonCollection is needed'; - } - - this.seasonCollection = options.seasonCollection; - }, - - itemViewOptions: function () { - return { - seasonCollection: this.seasonCollection - }; - } + itemView: SeriesLayout }); }); diff --git a/UI/SeasonPass/SeriesLayout.js b/UI/SeasonPass/SeriesLayout.js index fd4d6b985..e69a155e4 100644 --- a/UI/SeasonPass/SeriesLayout.js +++ b/UI/SeasonPass/SeriesLayout.js @@ -2,11 +2,9 @@ define( [ 'marionette', - 'backgrid', 'Series/SeasonCollection', - 'Cells/ToggleCell', 'Shared/Actioneer' - ], function (Marionette, Backgrid, SeasonCollection, ToggleCell, Actioneer) { + ], function (Marionette, SeasonCollection, Actioneer) { return Marionette.Layout.extend({ template: 'SeasonPass/SeriesLayoutTemplate', @@ -19,48 +17,22 @@ define( events: { 'change .x-season-select': '_seasonSelected', 'click .x-expander' : '_expand', - 'click .x-latest' : '_latest' + 'click .x-latest' : '_latest', + 'click .x-monitored' : '_toggleSeasonMonitored' }, regions: { seasonGrid: '.x-season-grid' }, - columns: - [ - { - name : 'monitored', - label : '', - cell : ToggleCell, - trueClass : 'icon-bookmark', - falseClass: 'icon-bookmark-empty', - tooltip : 'Toggle monitored status', - sortable : false - }, - { - name : 'seasonNumber', - label: 'Season', - cell : Backgrid.IntegerCell.extend({ - className: 'season-number-cell' - }) - } - ], - - initialize: function (options) { - this.seasonCollection = options.seasonCollection.bySeries(this.model.get('id')); - this.model.set('seasons', this.seasonCollection); + initialize: function () { + this.seasonCollection = new SeasonCollection(this.model.get('seasons')); this.expanded = false; }, onRender: function () { - this.seasonGrid.show(new Backgrid.Grid({ - columns : this.columns, - collection: this.seasonCollection, - className : 'table table-condensed season-grid span5' - })); - if (!this.expanded) { - this.seasonGrid.$el.hide(); + this.ui.seasonGrid.hide(); } this._setExpanderIcon(); @@ -103,33 +75,51 @@ define( }, _latest: function () { - var season = _.max(this.seasonCollection.models, function (model) { - return model.get('seasonNumber'); + var season = _.max(this.model.get('seasons'), function (s) { + return s.seasonNumber; }); - //var seasonNumber = season.get('seasonNumber'); - - this._setMonitored(season.get('seasonNumber')) + this._setMonitored(season.seasonNumber); }, _setMonitored: function (seasonNumber) { - //TODO: use Actioneer? - var self = this; - var promise = $.ajax({ - url: this.seasonCollection.url + '/pass', - type: 'POST', - data: { - seriesId: this.model.get('id'), - seasonNumber: seasonNumber - } - }); + this.model.setSeasonPass(seasonNumber); + + var promise = this.model.save(); promise.done(function (data) { self.seasonCollection = new SeasonCollection(data); self.render(); }); + }, + + _toggleSeasonMonitored: function (e) { + var seasonNumber = 0; + var element; + + if (e.target.localName === 'i') { + seasonNumber = parseInt($(e.target).parent('td').attr('data-season-number')); + element = $(e.target); + } + + else { + seasonNumber = parseInt($(e.target).attr('data-season-number')); + element = $(e.target).children('i'); + } + + this.model.setSeasonMonitored(seasonNumber); + + Actioneer.SaveModel({ + element: element, + context: this, + always : this._afterToggleSeasonMonitored + }); + }, + + _afterToggleSeasonMonitored: function () { + this.render(); } }); }); diff --git a/UI/SeasonPass/SeriesLayoutTemplate.html b/UI/SeasonPass/SeriesLayoutTemplate.html index 026718493..9c12a4483 100644 --- a/UI/SeasonPass/SeriesLayoutTemplate.html +++ b/UI/SeasonPass/SeriesLayoutTemplate.html @@ -12,12 +12,12 @@ @@ -36,7 +36,36 @@
-
+
+ + + + + + + + + {{#each seasons}} + + + + + {{/each}} + +
Season
+ {{#if monitored}} + + {{else}} + + {{/if}} + + {{#if_eq seasonNumber compare="0"}} + Specials + {{else}} + Season {{seasonNumber}} + {{/if_eq}} +
+
diff --git a/UI/Series/Details/SeasonLayout.js b/UI/Series/Details/SeasonLayout.js index 201103fd8..d6234ee87 100644 --- a/UI/Series/Details/SeasonLayout.js +++ b/UI/Series/Details/SeasonLayout.js @@ -117,8 +117,10 @@ define( _seasonMonitored: function () { var name = 'monitored'; this.model.set(name, !this.model.get(name)); + this.series.setSeasonMonitored(this.model.get('seasonNumber')); Actioneer.SaveModel({ + model : this.series, context: this, element: this.ui.seasonMonitored, always : this._afterSeasonMonitored diff --git a/UI/Series/Details/SeriesDetailsLayout.js b/UI/Series/Details/SeriesDetailsLayout.js index fb271a94c..bd794fbfe 100644 --- a/UI/Series/Details/SeriesDetailsLayout.js +++ b/UI/Series/Details/SeriesDetailsLayout.js @@ -113,12 +113,12 @@ define( this.ui.monitored.removeClass('icon-spin icon-spinner'); if (this.model.get('monitored')) { - this.ui.monitored.addClass('icon-bookmark'); - this.ui.monitored.removeClass('icon-bookmark-empty'); + this.ui.monitored.addClass('icon-nd-monitored'); + this.ui.monitored.removeClass('icon-nd-unmonitored'); } else { - this.ui.monitored.addClass('icon-bookmark-empty'); - this.ui.monitored.removeClass('icon-bookmark'); + this.ui.monitored.addClass('icon-nd-unmonitored'); + this.ui.monitored.removeClass('icon-nd-monitored'); } }, @@ -176,11 +176,11 @@ define( this.seasons.show(new LoadingView()); - this.seasonCollection = new SeasonCollection(); + this.seasonCollection = new SeasonCollection(this.model.get('seasons')); this.episodeCollection = new EpisodeCollection({ seriesId: this.model.id }); this.episodeFileCollection = new EpisodeFileCollection({ seriesId: this.model.id }); - $.when(this.episodeCollection.fetch(), this.episodeFileCollection.fetch(), this.seasonCollection.fetch({data: { seriesId: this.model.id }})).done(function () { + $.when(this.episodeCollection.fetch(), this.episodeFileCollection.fetch()).done(function () { var seasonCollectionView = new SeasonCollectionView({ collection : self.seasonCollection, episodeCollection: self.episodeCollection, diff --git a/UI/Series/SeasonCollection.js b/UI/Series/SeasonCollection.js index 68e312ff1..35984204a 100644 --- a/UI/Series/SeasonCollection.js +++ b/UI/Series/SeasonCollection.js @@ -5,21 +5,10 @@ define( 'Series/SeasonModel' ], function (Backbone, SeasonModel) { return Backbone.Collection.extend({ - url : window.ApiRoot + '/season', model: SeasonModel, comparator: function (season) { return -season.get('seasonNumber'); - }, - - bySeries: function (series) { - var filtered = this.filter(function (season) { - return season.get('seriesId') === series; - }); - - var SeasonCollection = require('Series/SeasonCollection'); - - return new SeasonCollection(filtered); } }); }); diff --git a/UI/Series/SeriesModel.js b/UI/Series/SeriesModel.js index 075e0fc80..5be5eecb4 100644 --- a/UI/Series/SeriesModel.js +++ b/UI/Series/SeriesModel.js @@ -14,6 +14,25 @@ define( episodeCount : 0, isExisting : false, status : 0 + }, + + setSeasonMonitored: function (seasonNumber) { + _.each(this.get('seasons'), function (season) { + if (season.seasonNumber === seasonNumber) { + season.monitored = !season.monitored; + } + }); + }, + + setSeasonPass: function (seasonNumber) { + _.each(this.get('seasons'), function (season) { + if (season.seasonNumber >= seasonNumber) { + season.monitored = true; + } + else { + season.monitored = false; + } + }); } }); }); diff --git a/UI/Series/series.less b/UI/Series/series.less index 5a8222bd5..c3b7170d0 100644 --- a/UI/Series/series.less +++ b/UI/Series/series.less @@ -256,6 +256,7 @@ .clickable; line-height: 30px; margin-left: 8px; + width: 16px; } .season-grid { diff --git a/UI/Shared/Actioneer.js b/UI/Shared/Actioneer.js index de9fcef8f..2eac452b3 100644 --- a/UI/Shared/Actioneer.js +++ b/UI/Shared/Actioneer.js @@ -33,7 +33,9 @@ define( this._showStartMessage(options); this._setSpinnerOnElement(options); - var promise = options.context.model.save(); + var model = options.model ? options.model : options.context.model; + + var promise = model.save(); this._handlePromise(promise, options); },