From 33986a9185bb930212ac220681e95b459e760951 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 9 Sep 2013 22:22:38 -0700 Subject: [PATCH 1/5] Seasons are now subdocuments of series --- NzbDrone.Api/NzbDrone.Api.csproj | 2 - NzbDrone.Api/Seasons/SeasonModule.cs | 53 ---------- NzbDrone.Api/Seasons/SeasonResource.cs | 12 --- NzbDrone.Api/Series/SeriesModule.cs | 1 - NzbDrone.Api/Series/SeriesResource.cs | 14 ++- NzbDrone.Core.Test/NzbDrone.Core.Test.csproj | 4 - .../TvTests/SeasonProviderTest.cs | 57 ----------- .../HandleEpisodeInfoDeletedEventFixture.cs | 93 ------------------ .../SeasonServiceTests/SetMonitoredFixture.cs | 53 ---------- .../SetSeasonPassFixture.cs | 91 ----------------- .../020_add_year_and_seasons_to_series.cs | 64 ++++++++++++ .../Migration/021_drop_seasons_table.cs | 14 +++ NzbDrone.Core/Datastore/TableMapping.cs | 2 - .../MetadataSource/Trakt/FullShow.cs | 27 ----- NzbDrone.Core/MetadataSource/TraktProxy.cs | 32 ++---- NzbDrone.Core/NzbDrone.Core.csproj | 2 + NzbDrone.Core/SeriesStats/SeriesStatistics.cs | 1 - .../SeriesStats/SeriesStatisticsRepository.cs | 1 - NzbDrone.Core/Tv/RefreshSeriesService.cs | 18 ++++ NzbDrone.Core/Tv/Season.cs | 5 +- NzbDrone.Core/Tv/SeasonRepository.cs | 23 ++--- NzbDrone.Core/Tv/SeasonService.cs | Bin 9871 -> 7067 bytes NzbDrone.Core/Tv/Series.cs | 3 + NzbDrone.Core/Tv/SeriesService.cs | 8 ++ .../Client/SeasonClient.cs | 22 ----- NzbDrone.Integration.Test/IntegrationTest.cs | 3 - .../NzbDrone.Integration.Test.csproj | 2 - .../SeasonIntegrationTests.cs | 61 ------------ UI/AddSeries/SearchResultTemplate.html | 3 +- UI/Content/icons.less | 8 ++ UI/SeasonPass/Layout.js | 12 +-- UI/SeasonPass/SeriesCollectionView.js | 18 +--- UI/SeasonPass/SeriesLayout.js | 88 ++++++++--------- UI/SeasonPass/SeriesLayoutTemplate.html | 43 ++++++-- UI/Series/Details/SeasonLayout.js | 2 + UI/Series/Details/SeriesDetailsLayout.js | 12 +-- UI/Series/SeasonCollection.js | 11 --- UI/Series/SeriesModel.js | 19 ++++ UI/Series/series.less | 1 + UI/Shared/Actioneer.js | 4 +- 40 files changed, 256 insertions(+), 633 deletions(-) delete mode 100644 NzbDrone.Api/Seasons/SeasonModule.cs delete mode 100644 NzbDrone.Api/Seasons/SeasonResource.cs delete mode 100644 NzbDrone.Core.Test/TvTests/SeasonProviderTest.cs delete mode 100644 NzbDrone.Core.Test/TvTests/SeasonServiceTests/HandleEpisodeInfoDeletedEventFixture.cs delete mode 100644 NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetMonitoredFixture.cs delete mode 100644 NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetSeasonPassFixture.cs create mode 100644 NzbDrone.Core/Datastore/Migration/020_add_year_and_seasons_to_series.cs create mode 100644 NzbDrone.Core/Datastore/Migration/021_drop_seasons_table.cs delete mode 100644 NzbDrone.Integration.Test/Client/SeasonClient.cs delete mode 100644 NzbDrone.Integration.Test/SeasonIntegrationTests.cs 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 6355796a47cb4e1da5460d78e1594785dad261cc..7581942f7fd51c22ff18e699c80a0d039beb45cf 100644 GIT binary patch delta 551 zcmeD8oo&8BjFD4cUjYIp8?ZXDTJv)0>rcMVC^~s1yYA#AjA@hAnM@}0G74_4VqU?> z1e8-^4dRFEQK$y1Pvj7rJeAd$O&PEwvzH!AA4($^c!akKqEamBB&?Eg(NZ z-DU|hcn*gylA$0K5a-|EumU+FkxLj%F^Nom!z~E$K_EzEA*UWpqK3_8asfC0WIZkp zV^qsP#=+eQ;vj?6oZ?i7Yp~ei$(@!6l-GoW$ww}IRLdlEa9D(F0o-PAV4mU@1DWxF zI}_%0Zys5Y2qUk~gJS;4M0?3DmQj&#*nT)GJC!j#PeI6j&w(>9gKi8c6a|5CFRj^-c|R^O!knp`ZFf^+qaHKhd4R;kt)V~xu_+m=wt$*QVHdd0{+N1Dbk-tJ zR4q#}z?H1i6pyxLU0?}Is1790K<-Pmx1Sg`8-g4&P6mllZ*=$>okxtkrIe6WBAUec zl2=bUH_z$GU}~82!nkFD(2C7)iD}Vue3^XfMh785l0LD>>cG=iW0Fq)AUlE6w3IKI zjy$HBJ+RrOX(zgre82NhvrDPH55oP9)HDyDf zyzDi0p^$o_GAs1k6B}Co)^sitL~{V2jr_MKbPyIYO0<}(LvZ(eQ0?t5;X9q^GIY1Y zG1Y;pcxqh7RlSc_Kvj#jJRKfANA!{E^|_v|;LhAmr3aa$O?HWCHMUc-+h%$h9suS} z&DP(4iUW5?llZ+BUV}&; z?Pz{!rYN`B2Cl2#Oe4dMDDg~bCog<|q=k>WC}kIyEXB5>28&l3{i;#Z^!Zk+lL6#X z0D+b)jCqC_8YS^BuzMelNk$m{!jRyAq}|6od$LF!6tbO2!YVULRg>1zEsO=eSEQAt z!5&4E__{%XPlr^lb4l;#7N%z-{^JP*=;Bv)iR^cQclbiit0d>-fn&WJ3Ez=>dND&c zd<+;!>ko%7dW?vIAd#w`K+&?v1XOLxa^SU3xosR{>42D>+%B5)%GM2;qwA&L*KW+` z93fRsOKnJKOE?S@R+locuh2fjy4=FAV)K-6$aEg@H9hS1&zZksgl21i<$FRiBR;SblmK+HWfcqIief)}a zoc)hq3_oV#2UBpU38?UoY%Ly}q;ZiS0Kolr_AfzD0Z^X+Ux(1&Pmf@u`GOazW9cv` zwu}Z|Zw)G?Jf63oS4|ELWutQhc~iYkGW>b@ZoEbYMg~R(Mg~R(Mg~R(Mg~R(Mg~R( MMg~R({xb}G1p&0#YXATM 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); }, From 97eb5ffc61e4db502bb88b8dd59ac81cd57b406a Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 9 Sep 2013 23:22:54 -0700 Subject: [PATCH 2/5] Default to starting at lowest season above 0 --- NzbDrone.Core/MetadataSource/TraktProxy.cs | 2 +- .../RootFolders/RootFolderSelectionPartial.html | 2 +- .../StartingSeasonSelectionPartial.html | 9 +++++++++ UI/AddSeries/SearchResultView.js | 15 +++++++++++---- ...emplate.html => SearchResultViewTemplate.html} | 15 +++++++-------- 5 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 UI/AddSeries/RootFolders/StartingSeasonSelectionPartial.html rename UI/AddSeries/{SearchResultTemplate.html => SearchResultViewTemplate.html} (70%) diff --git a/NzbDrone.Core/MetadataSource/TraktProxy.cs b/NzbDrone.Core/MetadataSource/TraktProxy.cs index 53fd0c4e7..eb8bb263e 100644 --- a/NzbDrone.Core/MetadataSource/TraktProxy.cs +++ b/NzbDrone.Core/MetadataSource/TraktProxy.cs @@ -83,7 +83,7 @@ namespace NzbDrone.Core.MetadataSource series.Seasons = show.seasons.Select(s => new Tv.Season { SeasonNumber = s.season - }).ToList(); + }).OrderByDescending(s => s.SeasonNumber).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/UI/AddSeries/RootFolders/RootFolderSelectionPartial.html b/UI/AddSeries/RootFolders/RootFolderSelectionPartial.html index 60f2a49d7..56d547da5 100644 --- a/UI/AddSeries/RootFolders/RootFolderSelectionPartial.html +++ b/UI/AddSeries/RootFolders/RootFolderSelectionPartial.html @@ -1,4 +1,4 @@ - {{#if this}} {{#each this}} diff --git a/UI/AddSeries/RootFolders/StartingSeasonSelectionPartial.html b/UI/AddSeries/RootFolders/StartingSeasonSelectionPartial.html new file mode 100644 index 000000000..0827ba052 --- /dev/null +++ b/UI/AddSeries/RootFolders/StartingSeasonSelectionPartial.html @@ -0,0 +1,9 @@ + diff --git a/UI/AddSeries/SearchResultView.js b/UI/AddSeries/SearchResultView.js index e7d16631a..28ba1a774 100644 --- a/UI/AddSeries/SearchResultView.js +++ b/UI/AddSeries/SearchResultView.js @@ -3,7 +3,6 @@ define( [ 'app', 'marionette', - 'Quality/QualityProfileCollection', 'AddSeries/RootFolders/Collection', 'AddSeries/RootFolders/Layout', @@ -15,13 +14,14 @@ define( return Marionette.ItemView.extend({ - template: 'AddSeries/SearchResultTemplate', + template: 'AddSeries/SearchResultViewTemplate', ui: { qualityProfile: '.x-quality-profile', rootFolder : '.x-root-folder', addButton : '.x-add', - overview : '.x-overview' + overview : '.x-overview', + startingSeason: '.x-starting-season' }, events: { @@ -57,6 +57,12 @@ define( this.ui.rootFolder.val(defaultRoot); } + var minSeasonNotZero = _.min(_.reject(this.model.get('seasons'), { seasonNumber: 0 }), 'seasonNumber'); + + if (minSeasonNotZero) { + this.ui.startingSeason.val(minSeasonNotZero.seasonNumber); + } + //TODO: make this work via onRender, FM? //works with onShow, but stops working after the first render this.ui.overview.dotdotdot({ @@ -117,15 +123,16 @@ define( var quality = this.ui.qualityProfile.val(); var rootFolderPath = this.ui.rootFolder.children(':selected').text(); + var startingSeason = this.ui.startingSeason.val(); this.model.set('qualityProfileId', quality); this.model.set('rootFolderPath', rootFolderPath); + this.model.setSeasonPass(startingSeason); var self = this; SeriesCollection.add(this.model); - this.model.save().done(function () { self.close(); icon.removeClass('icon-spin icon-spinner disabled').addClass('icon-search'); diff --git a/UI/AddSeries/SearchResultTemplate.html b/UI/AddSeries/SearchResultViewTemplate.html similarity index 70% rename from UI/AddSeries/SearchResultTemplate.html rename to UI/AddSeries/SearchResultViewTemplate.html index 47ec8a9b3..27d7dcd0e 100644 --- a/UI/AddSeries/SearchResultTemplate.html +++ b/UI/AddSeries/SearchResultViewTemplate.html @@ -15,22 +15,21 @@
{{#if existing}} -
+
Already Exists
{{else}} -
- Add - -
{{#unless path}} {{> RootFolderSelectionPartial rootFolders}} {{/unless}} -
- {{> QualityProfileSelectionPartial qualityProfiles}} + + {{> StartingSeasonSelectionPartial seasons}} + {{> QualityProfileSelectionPartial qualityProfiles}} + +
+ Add
{{/if}} -
From d8928d655dc316077716b0ea41ae19ae65f1858d Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 9 Sep 2013 23:38:25 -0700 Subject: [PATCH 3/5] Cleaned up season service/repo --- NzbDrone.Core/Tv/RefreshEpisodeService.cs | 5 ----- NzbDrone.Core/Tv/SeasonRepository.cs | 14 -------------- NzbDrone.Core/Tv/SeasonService.cs | Bin 7067 -> 4634 bytes 3 files changed, 19 deletions(-) diff --git a/NzbDrone.Core/Tv/RefreshEpisodeService.cs b/NzbDrone.Core/Tv/RefreshEpisodeService.cs index 6c2e1c4b0..56aba9a9c 100644 --- a/NzbDrone.Core/Tv/RefreshEpisodeService.cs +++ b/NzbDrone.Core/Tv/RefreshEpisodeService.cs @@ -114,11 +114,6 @@ namespace NzbDrone.Core.Tv private static bool GetMonitoredStatus(Episode episode, IEnumerable seasons) { - if (episode.SeasonNumber == 0) - { - return false; - } - if (episode.EpisodeNumber == 0 && episode.SeasonNumber != 1) { return false; diff --git a/NzbDrone.Core/Tv/SeasonRepository.cs b/NzbDrone.Core/Tv/SeasonRepository.cs index 7fc3e65af..5b9b41bfc 100644 --- a/NzbDrone.Core/Tv/SeasonRepository.cs +++ b/NzbDrone.Core/Tv/SeasonRepository.cs @@ -8,8 +8,6 @@ namespace NzbDrone.Core.Tv { public interface ISeasonRepository : IBasicRepository { - Season Get(int seriesId, int seasonNumber); - bool IsMonitored(int seriesId, int seasonNumber); List GetSeasonBySeries(int seriesId); } @@ -20,18 +18,6 @@ namespace NzbDrone.Core.Tv { } - public Season Get(int seriesId, int 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 series = Query.Single(s => s.Id == seriesId); - return series.Seasons.Single(s => s.SeasonNumber == seasonNumber).Monitored; - } - public List GetSeasonBySeries(int seriesId) { return Query.Single(s => s.Id == seriesId).Seasons; diff --git a/NzbDrone.Core/Tv/SeasonService.cs b/NzbDrone.Core/Tv/SeasonService.cs index 7581942f7fd51c22ff18e699c80a0d039beb45cf..f6a459f9739c96b92dd1958cc1a5021e5ad7ff7c 100644 GIT binary patch delta 48 zcmbPjK1*eT7~|x}j1rUgbIVSCz{ozCk7?@UdrTpdgP5Zy-(i-YY{?@Jl&jc$mggKF E0IoSkmX>IC3g*Bu|nvvXKA2do9WGXSYz; z7}zh4?E7)<(Ye=3t{C5v`!F8mm1X!vq$!Ox6I_M2lvBy#rpX=S-^*tYcVg=o zKiAh%a0*d&JHf+ys#LUvWL*}LhSxM^3R2y0FrwiygLT5<2NC{MI%qIZNN4sN6e>5qtFsk|aHB#y~ zVKIyWL`W^jtqY<)AD{|ARg3hJ-={P+`BmV|PRJwXu|&lwKm(u^j>x=^G>0#fY#(V# zB#kzLrw3>Mjb=h2D+ltVije+ErM$NLO~#biP`gRwiPqLHxq=|KRFbE(xPzr<(N6qJ2iQW4J&}bAtlfcJ;kB`NsV9wMf*CGHRu!LAmfCT+F zi(I+UV;im+41GHT;)@<;%WimWBWjt`HoKS^uZ})MhgJ(3Yd~8cnP;_PjkGE02iDTI z%T7}Thm878;k$oEZV~~av)C;>{sz4-Kr8rloZ`Kc@V>ElAMt~21Jy3QC1>mtGaYc; zy>>!0cVAMS%d(AYyh2g_$j~(%J7!4raN2+Nuu0nll@ZIsWOXPx#{UFZ^>FE{0G+!a zc<;pl-oXUg4gY?a?IM>~MA-&ZlW-;QJ5ionF&Ln-x6_;98W Date: Mon, 9 Sep 2013 23:44:55 -0700 Subject: [PATCH 4/5] Added year to SeriesResource --- NzbDrone.Api/Series/SeriesResource.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/NzbDrone.Api/Series/SeriesResource.cs b/NzbDrone.Api/Series/SeriesResource.cs index 6d9557f32..170fa8301 100644 --- a/NzbDrone.Api/Series/SeriesResource.cs +++ b/NzbDrone.Api/Series/SeriesResource.cs @@ -37,6 +37,7 @@ namespace NzbDrone.Api.Series public String RemotePoster { get; set; } public List Seasons { get; set; } + public Int32 Year { get; set; } //View & Edit public String Path { get; set; } From ba5702021b87f60f31093f4e875d243feaf01d02 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 10 Sep 2013 10:13:30 -0700 Subject: [PATCH 5/5] Added year to seaarch results --- UI/AddSeries/SearchResultViewTemplate.html | 2 +- UI/Handlebars/Helpers/Series.js | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/UI/AddSeries/SearchResultViewTemplate.html b/UI/AddSeries/SearchResultViewTemplate.html index 27d7dcd0e..574422b17 100644 --- a/UI/AddSeries/SearchResultViewTemplate.html +++ b/UI/AddSeries/SearchResultViewTemplate.html @@ -8,7 +8,7 @@
-

{{title}}

+

{{titleWithYear}}

{{overview}} diff --git a/UI/Handlebars/Helpers/Series.js b/UI/Handlebars/Helpers/Series.js index ac2a21747..f9d849325 100644 --- a/UI/Handlebars/Helpers/Series.js +++ b/UI/Handlebars/Helpers/Series.js @@ -62,4 +62,12 @@ define( return new Handlebars.SafeString('{0} Seasons'.format(seasonCount)) }); + + Handlebars.registerHelper('titleWithYear', function () { + if (this.title.endsWith(' ({0})'.format(this.year))) { + return this.title; + } + + return '{0} ({1})'.format(this.title, this.year); + }); });