Seasons are now subdocuments of series
This commit is contained in:
parent
0861e5f8c1
commit
33986a9185
|
@ -140,8 +140,6 @@
|
||||||
<Compile Include="RootFolders\RootFolderModule.cs" />
|
<Compile Include="RootFolders\RootFolderModule.cs" />
|
||||||
<Compile Include="RootFolders\RootFolderResource.cs" />
|
<Compile Include="RootFolders\RootFolderResource.cs" />
|
||||||
<Compile Include="RootFolders\RootFolderConnection.cs" />
|
<Compile Include="RootFolders\RootFolderConnection.cs" />
|
||||||
<Compile Include="Seasons\SeasonModule.cs" />
|
|
||||||
<Compile Include="Seasons\SeasonResource.cs" />
|
|
||||||
<Compile Include="Series\SeriesConnection.cs" />
|
<Compile Include="Series\SeriesConnection.cs" />
|
||||||
<Compile Include="Series\SeriesResource.cs" />
|
<Compile Include="Series\SeriesResource.cs" />
|
||||||
<Compile Include="Series\SeriesModule.cs" />
|
<Compile Include="Series\SeriesModule.cs" />
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using NzbDrone.Api.Mapping;
|
|
||||||
using NzbDrone.Core.Tv;
|
|
||||||
|
|
||||||
namespace NzbDrone.Api.Seasons
|
|
||||||
{
|
|
||||||
public class SeasonModule : NzbDroneRestModule<SeasonResource>
|
|
||||||
{
|
|
||||||
private readonly ISeasonService _seasonService;
|
|
||||||
|
|
||||||
public SeasonModule(ISeasonService seasonService)
|
|
||||||
: base("/season")
|
|
||||||
{
|
|
||||||
_seasonService = seasonService;
|
|
||||||
|
|
||||||
GetResourceAll = GetSeasons;
|
|
||||||
GetResourceById = GetSeason;
|
|
||||||
UpdateResource = Update;
|
|
||||||
|
|
||||||
Post["/pass"] = x => SetSeasonPass();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SeasonResource> GetSeasons()
|
|
||||||
{
|
|
||||||
var seriesId = Request.Query.SeriesId;
|
|
||||||
|
|
||||||
if (seriesId.HasValue)
|
|
||||||
{
|
|
||||||
return ToListResource<Season>(() => _seasonService.GetSeasonsBySeries(seriesId));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ToListResource(() => _seasonService.GetAllSeasons());
|
|
||||||
}
|
|
||||||
|
|
||||||
private SeasonResource GetSeason(int id)
|
|
||||||
{
|
|
||||||
return _seasonService.Get(id).InjectTo<SeasonResource>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Update(SeasonResource seasonResource)
|
|
||||||
{
|
|
||||||
_seasonService.SetMonitored(seasonResource.SeriesId, seasonResource.SeasonNumber, seasonResource.Monitored);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SeasonResource> SetSeasonPass()
|
|
||||||
{
|
|
||||||
var seriesId = Request.Form.SeriesId;
|
|
||||||
var seasonNumber = Request.Form.SeasonNumber;
|
|
||||||
|
|
||||||
return ToListResource<Season>(() => _seasonService.SetSeasonPass(seriesId, seasonNumber));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -117,7 +117,6 @@ namespace NzbDrone.Api.Series
|
||||||
{
|
{
|
||||||
resource.EpisodeCount = seriesStatistics.EpisodeCount;
|
resource.EpisodeCount = seriesStatistics.EpisodeCount;
|
||||||
resource.EpisodeFileCount = seriesStatistics.EpisodeFileCount;
|
resource.EpisodeFileCount = seriesStatistics.EpisodeFileCount;
|
||||||
resource.SeasonCount = seriesStatistics.SeasonCount;
|
|
||||||
resource.NextAiring = seriesStatistics.NextAiring;
|
resource.NextAiring = seriesStatistics.NextAiring;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,17 @@ namespace NzbDrone.Api.Series
|
||||||
|
|
||||||
//View Only
|
//View Only
|
||||||
public String Title { get; set; }
|
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 EpisodeCount { get; set; }
|
||||||
public Int32 EpisodeFileCount { get; set; }
|
public Int32 EpisodeFileCount { get; set; }
|
||||||
public SeriesStatusType Status { get; set; }
|
public SeriesStatusType Status { get; set; }
|
||||||
|
@ -26,7 +36,7 @@ namespace NzbDrone.Api.Series
|
||||||
public List<MediaCover> Images { get; set; }
|
public List<MediaCover> Images { get; set; }
|
||||||
|
|
||||||
public String RemotePoster { get; set; }
|
public String RemotePoster { get; set; }
|
||||||
|
public List<Season> Seasons { get; set; }
|
||||||
|
|
||||||
//View & Edit
|
//View & Edit
|
||||||
public String Path { get; set; }
|
public String Path { get; set; }
|
||||||
|
|
|
@ -175,7 +175,6 @@
|
||||||
<Compile Include="TvTests\EpisodeRepositoryTests\EpisodesRepositoryReadFixture.cs" />
|
<Compile Include="TvTests\EpisodeRepositoryTests\EpisodesRepositoryReadFixture.cs" />
|
||||||
<Compile Include="TvTests\EpisodeRepositoryTests\EpisodesWithoutFilesFixture.cs" />
|
<Compile Include="TvTests\EpisodeRepositoryTests\EpisodesWithoutFilesFixture.cs" />
|
||||||
<Compile Include="TvTests\EpisodeRepositoryTests\EpisodesBetweenDatesFixture.cs" />
|
<Compile Include="TvTests\EpisodeRepositoryTests\EpisodesBetweenDatesFixture.cs" />
|
||||||
<Compile Include="TvTests\SeasonProviderTest.cs" />
|
|
||||||
<Compile Include="DecisionEngineTests\RetentionSpecificationFixture.cs" />
|
<Compile Include="DecisionEngineTests\RetentionSpecificationFixture.cs" />
|
||||||
<Compile Include="DecisionEngineTests\QualityAllowedByProfileSpecificationFixture.cs" />
|
<Compile Include="DecisionEngineTests\QualityAllowedByProfileSpecificationFixture.cs" />
|
||||||
<Compile Include="DecisionEngineTests\UpgradeHistorySpecificationFixture.cs" />
|
<Compile Include="DecisionEngineTests\UpgradeHistorySpecificationFixture.cs" />
|
||||||
|
@ -187,9 +186,6 @@
|
||||||
<Compile Include="ProviderTests\DiskProviderTests\ArchiveProviderFixture.cs" />
|
<Compile Include="ProviderTests\DiskProviderTests\ArchiveProviderFixture.cs" />
|
||||||
<Compile Include="MediaFileTests\DropFolderImportServiceFixture.cs" />
|
<Compile Include="MediaFileTests\DropFolderImportServiceFixture.cs" />
|
||||||
<Compile Include="SeriesStatsTests\SeriesStatisticsFixture.cs" />
|
<Compile Include="SeriesStatsTests\SeriesStatisticsFixture.cs" />
|
||||||
<Compile Include="TvTests\SeasonServiceTests\HandleEpisodeInfoDeletedEventFixture.cs" />
|
|
||||||
<Compile Include="TvTests\SeasonServiceTests\SetSeasonPassFixture.cs" />
|
|
||||||
<Compile Include="TvTests\SeasonServiceTests\SetMonitoredFixture.cs" />
|
|
||||||
<Compile Include="TvTests\SeriesRepositoryTests\QualityProfileRepositoryFixture.cs" />
|
<Compile Include="TvTests\SeriesRepositoryTests\QualityProfileRepositoryFixture.cs" />
|
||||||
<Compile Include="UpdateTests\UpdateServiceFixture.cs" />
|
<Compile Include="UpdateTests\UpdateServiceFixture.cs" />
|
||||||
<Compile Include="ProviderTests\XemCommunicationProviderTests\GetSceneTvdbMappingsFixture.cs" />
|
<Compile Include="ProviderTests\XemCommunicationProviderTests\GetSceneTvdbMappingsFixture.cs" />
|
||||||
|
|
|
@ -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<SeasonRepository, Season>
|
|
||||||
{
|
|
||||||
[TestCase(true)]
|
|
||||||
[TestCase(false)]
|
|
||||||
public void Ismonitored_should_return_monitored_status_of_season(bool monitored)
|
|
||||||
{
|
|
||||||
var fakeSeason = Builder<Season>.CreateNew()
|
|
||||||
.With(s => s.Monitored = monitored)
|
|
||||||
.BuildNew<Season>();
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<SeasonService>
|
|
||||||
{
|
|
||||||
private List<Season> _seasons;
|
|
||||||
private List<Episode> _episodes;
|
|
||||||
|
|
||||||
[SetUp]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
_seasons = Builder<Season>
|
|
||||||
.CreateListOfSize(1)
|
|
||||||
.All()
|
|
||||||
.With(s => s.SeriesId = 1)
|
|
||||||
.Build()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
_episodes = Builder<Episode>
|
|
||||||
.CreateListOfSize(1)
|
|
||||||
.All()
|
|
||||||
.With(e => e.SeasonNumber = _seasons.First().SeasonNumber)
|
|
||||||
.With(s => s.SeriesId = _seasons.First().SeasonNumber)
|
|
||||||
.Build()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
Mocker.GetMock<ISeasonRepository>()
|
|
||||||
.Setup(s => s.GetSeasonBySeries(It.IsAny<int>()))
|
|
||||||
.Returns(_seasons);
|
|
||||||
|
|
||||||
Mocker.GetMock<IEpisodeService>()
|
|
||||||
.Setup(s => s.GetEpisodesBySeason(It.IsAny<int>(), _seasons.First().SeasonNumber))
|
|
||||||
.Returns(_episodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenAbandonedSeason()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IEpisodeService>()
|
|
||||||
.Setup(s => s.GetEpisodesBySeason(It.IsAny<int>(), _seasons.First().SeasonNumber))
|
|
||||||
.Returns(new List<Episode>());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_not_delete_when_season_is_still_valid()
|
|
||||||
{
|
|
||||||
Subject.Handle(new EpisodeInfoDeletedEvent(_episodes));
|
|
||||||
|
|
||||||
Mocker.GetMock<ISeasonRepository>()
|
|
||||||
.Verify(v => v.Delete(It.IsAny<Season>()), Times.Never());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_delete_season_if_no_episodes_exist_in_that_season()
|
|
||||||
{
|
|
||||||
GivenAbandonedSeason();
|
|
||||||
|
|
||||||
Subject.Handle(new EpisodeInfoDeletedEvent(_episodes));
|
|
||||||
|
|
||||||
Mocker.GetMock<ISeasonRepository>()
|
|
||||||
.Verify(v => v.Delete(It.IsAny<Season>()), Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_only_delete_a_season_once()
|
|
||||||
{
|
|
||||||
_episodes = Builder<Episode>
|
|
||||||
.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<ISeasonRepository>()
|
|
||||||
.Verify(v => v.Delete(It.IsAny<Season>()), Times.Once());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<SeasonService>
|
|
||||||
{
|
|
||||||
private Season _season;
|
|
||||||
|
|
||||||
[SetUp]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
_season = new Season
|
|
||||||
{
|
|
||||||
Id = 1,
|
|
||||||
SeasonNumber = 1,
|
|
||||||
SeriesId = 1,
|
|
||||||
Monitored = false
|
|
||||||
};
|
|
||||||
|
|
||||||
Mocker.GetMock<ISeasonRepository>()
|
|
||||||
.Setup(s => s.Get(It.IsAny<Int32>(), It.IsAny<Int32>()))
|
|
||||||
.Returns(_season);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestCase(true)]
|
|
||||||
[TestCase(false)]
|
|
||||||
public void should_update_season(bool monitored)
|
|
||||||
{
|
|
||||||
Subject.SetMonitored(_season.SeriesId, _season.SeasonNumber, monitored);
|
|
||||||
|
|
||||||
Mocker.GetMock<ISeasonRepository>()
|
|
||||||
.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<IEpisodeService>()
|
|
||||||
.Verify(v => v.SetEpisodeMonitoredBySeason(_season.SeriesId, _season.SeasonNumber, monitored), Times.Once());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<SeasonService>
|
|
||||||
{
|
|
||||||
private const Int32 SERIES_ID = 1;
|
|
||||||
private List<Season> _seasons;
|
|
||||||
|
|
||||||
[SetUp]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
_seasons = Builder<Season>.CreateListOfSize(5)
|
|
||||||
.All()
|
|
||||||
.With(s => s.SeriesId = SERIES_ID)
|
|
||||||
.Build()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
Mocker.GetMock<ISeasonRepository>()
|
|
||||||
.Setup(s => s.GetSeasonBySeries(It.IsAny<Int32>()))
|
|
||||||
.Returns(_seasons);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_updateMany()
|
|
||||||
{
|
|
||||||
Subject.SetSeasonPass(SERIES_ID, 1);
|
|
||||||
|
|
||||||
Mocker.GetMock<ISeasonRepository>()
|
|
||||||
.Verify(v => v.UpdateMany(It.IsAny<List<Season>>()), 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<IEpisodeService>()
|
|
||||||
.Verify(v => v.SetEpisodeMonitoredBySeason(SERIES_ID, It.Is<Int32>(i => i < seasonNumber), false), Times.AtLeastOnce());
|
|
||||||
|
|
||||||
Mocker.GetMock<IEpisodeService>()
|
|
||||||
.Verify(v => v.SetEpisodeMonitoredBySeason(SERIES_ID, It.Is<Int32>(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<IEpisodeService>()
|
|
||||||
.Verify(v => v.SetEpisodeMonitoredBySeason(SERIES_ID, It.Is<Int32>(i => i >= seasonNumber), true), Times.AtLeastOnce());
|
|
||||||
|
|
||||||
Mocker.GetMock<IEpisodeService>()
|
|
||||||
.Verify(v => v.SetEpisodeMonitoredBySeason(SERIES_ID, It.Is<Int32>(i => i >= seasonNumber), false), Times.Never());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<dynamic>();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,8 +45,6 @@ namespace NzbDrone.Core.Datastore
|
||||||
.Relationship()
|
.Relationship()
|
||||||
.HasOne(s => s.QualityProfile, s => s.QualityProfileId);
|
.HasOne(s => s.QualityProfile, s => s.QualityProfileId);
|
||||||
|
|
||||||
Mapper.Entity<Season>().RegisterModel("Seasons");
|
|
||||||
|
|
||||||
Mapper.Entity<Episode>().RegisterModel("Episodes")
|
Mapper.Entity<Episode>().RegisterModel("Episodes")
|
||||||
.Ignore(e => e.SeriesTitle)
|
.Ignore(e => e.SeriesTitle)
|
||||||
.Ignore(e => e.Series)
|
.Ignore(e => e.Series)
|
||||||
|
|
|
@ -29,31 +29,4 @@ namespace NzbDrone.Core.MetadataSource.Trakt
|
||||||
public List<string> genres { get; set; }
|
public List<string> genres { get; set; }
|
||||||
public List<Season> seasons { get; set; }
|
public List<Season> 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<string> genres { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -30,10 +30,10 @@ namespace NzbDrone.Core.MetadataSource
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = BuildClient("search", "shows");
|
var client = BuildClient("search", "shows");
|
||||||
var restRequest = new RestRequest(GetSearchTerm(title));
|
var restRequest = new RestRequest(GetSearchTerm(title) +"/30/seasons");
|
||||||
var response = client.ExecuteAndValidate<List<SearchShow>>(restRequest);
|
var response = client.ExecuteAndValidate<List<Show>>(restRequest);
|
||||||
|
|
||||||
return response.Select(MapSearchSeries).ToList();
|
return response.Select(MapSeries).ToList();
|
||||||
}
|
}
|
||||||
catch (WebException ex)
|
catch (WebException ex)
|
||||||
{
|
{
|
||||||
|
@ -71,6 +71,7 @@ namespace NzbDrone.Core.MetadataSource
|
||||||
series.ImdbId = show.imdb_id;
|
series.ImdbId = show.imdb_id;
|
||||||
series.Title = show.title;
|
series.Title = show.title;
|
||||||
series.CleanTitle = Parser.Parser.CleanSeriesTitle(show.title);
|
series.CleanTitle = Parser.Parser.CleanSeriesTitle(show.title);
|
||||||
|
series.Year = show.year;
|
||||||
series.FirstAired = FromIso(show.first_aired_iso);
|
series.FirstAired = FromIso(show.first_aired_iso);
|
||||||
series.Overview = show.overview;
|
series.Overview = show.overview;
|
||||||
series.Runtime = show.runtime;
|
series.Runtime = show.runtime;
|
||||||
|
@ -79,27 +80,10 @@ namespace NzbDrone.Core.MetadataSource
|
||||||
series.TitleSlug = show.url.ToLower().Replace("http://trakt.tv/show/", "");
|
series.TitleSlug = show.url.ToLower().Replace("http://trakt.tv/show/", "");
|
||||||
series.Status = GetSeriesStatus(show.status);
|
series.Status = GetSeriesStatus(show.status);
|
||||||
|
|
||||||
series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Banner, Url = show.images.banner });
|
series.Seasons = show.seasons.Select(s => new Tv.Season
|
||||||
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 });
|
SeasonNumber = s.season
|
||||||
return series;
|
}).ToList();
|
||||||
}
|
|
||||||
|
|
||||||
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.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Banner, Url = show.images.banner });
|
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.Poster, Url = GetPosterThumbnailUrl(show.images.poster) });
|
||||||
|
|
|
@ -162,6 +162,8 @@
|
||||||
<Compile Include="Datastore\Migration\017_reset_scene_names.cs" />
|
<Compile Include="Datastore\Migration\017_reset_scene_names.cs" />
|
||||||
<Compile Include="Datastore\Migration\018_remove_duplicates.cs" />
|
<Compile Include="Datastore\Migration\018_remove_duplicates.cs" />
|
||||||
<Compile Include="Datastore\Migration\019_restore_unique_constraints.cs" />
|
<Compile Include="Datastore\Migration\019_restore_unique_constraints.cs" />
|
||||||
|
<Compile Include="Datastore\Migration\020_add_year_and_seasons_to_series.cs" />
|
||||||
|
<Compile Include="Datastore\Migration\021_drop_seasons_table.cs" />
|
||||||
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
|
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
|
||||||
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
|
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
|
||||||
<Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" />
|
<Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" />
|
||||||
|
|
|
@ -6,7 +6,6 @@ namespace NzbDrone.Core.SeriesStats
|
||||||
public class SeriesStatistics : ResultSet
|
public class SeriesStatistics : ResultSet
|
||||||
{
|
{
|
||||||
public int SeriesId { get; set; }
|
public int SeriesId { get; set; }
|
||||||
public int SeasonCount { get; set; }
|
|
||||||
public string NextAiringString { get; set; }
|
public string NextAiringString { get; set; }
|
||||||
public int EpisodeFileCount { get; set; }
|
public int EpisodeFileCount { get; set; }
|
||||||
public int EpisodeCount { get; set; }
|
public int EpisodeCount { get; set; }
|
||||||
|
|
|
@ -56,7 +56,6 @@ namespace NzbDrone.Core.SeriesStats
|
||||||
SeriesId,
|
SeriesId,
|
||||||
SUM(CASE WHEN (Monitored = 1 AND AirdateUtc <= @currentDate) OR EpisodeFileId > 0 THEN 1 ELSE 0 END) AS EpisodeCount,
|
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,
|
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
|
MIN(CASE WHEN AirDateUtc < @currentDate OR EpisodeFileId > 0 THEN NULL ELSE AirDateUtc END) AS NextAiringString
|
||||||
FROM Episodes";
|
FROM Episodes";
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
@ -64,6 +65,8 @@ namespace NzbDrone.Core.Tv
|
||||||
_logger.WarnException("Couldn't update series path for " + series.Path, e);
|
_logger.WarnException("Couldn't update series path for " + series.Path, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
series.Seasons = UpdateSeasons(series, seriesInfo);
|
||||||
|
|
||||||
_seriesService.UpdateSeries(series);
|
_seriesService.UpdateSeries(series);
|
||||||
_refreshEpisodeService.RefreshEpisodeInfo(series, tuple.Item2);
|
_refreshEpisodeService.RefreshEpisodeInfo(series, tuple.Item2);
|
||||||
|
|
||||||
|
@ -71,6 +74,21 @@ namespace NzbDrone.Core.Tv
|
||||||
_messageAggregator.PublishEvent(new SeriesUpdatedEvent(series));
|
_messageAggregator.PublishEvent(new SeriesUpdatedEvent(series));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Season> 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)
|
public void Execute(RefreshSeriesCommand message)
|
||||||
{
|
{
|
||||||
if (message.SeriesId.HasValue)
|
if (message.SeriesId.HasValue)
|
||||||
|
|
|
@ -4,12 +4,9 @@ using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Tv
|
namespace NzbDrone.Core.Tv
|
||||||
{
|
{
|
||||||
public class Season : ModelBase
|
public class Season : IEmbeddedDocument
|
||||||
{
|
{
|
||||||
public int SeriesId { get; set; }
|
|
||||||
public int SeasonNumber { get; set; }
|
public int SeasonNumber { get; set; }
|
||||||
public Boolean Monitored { get; set; }
|
public Boolean Monitored { get; set; }
|
||||||
|
|
||||||
public List<Episode> Episodes { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,44 +6,35 @@ using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Tv
|
namespace NzbDrone.Core.Tv
|
||||||
{
|
{
|
||||||
public interface ISeasonRepository : IBasicRepository<Season>
|
public interface ISeasonRepository : IBasicRepository<Series>
|
||||||
{
|
{
|
||||||
IList<int> GetSeasonNumbers(int seriesId);
|
|
||||||
Season Get(int seriesId, int seasonNumber);
|
Season Get(int seriesId, int seasonNumber);
|
||||||
bool IsMonitored(int seriesId, int seasonNumber);
|
bool IsMonitored(int seriesId, int seasonNumber);
|
||||||
List<Season> GetSeasonBySeries(int seriesId);
|
List<Season> GetSeasonBySeries(int seriesId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SeasonRepository : BasicRepository<Season>, ISeasonRepository
|
public class SeasonRepository : BasicRepository<Series>, ISeasonRepository
|
||||||
{
|
{
|
||||||
|
|
||||||
public SeasonRepository(IDatabase database, IMessageAggregator messageAggregator)
|
public SeasonRepository(IDatabase database, IMessageAggregator messageAggregator)
|
||||||
: base(database, messageAggregator)
|
: base(database, messageAggregator)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public IList<int> GetSeasonNumbers(int seriesId)
|
|
||||||
{
|
|
||||||
return Query.Where(c => c.SeriesId == seriesId).Select(c => c.SeasonNumber).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Season Get(int seriesId, int seasonNumber)
|
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)
|
public bool IsMonitored(int seriesId, int seasonNumber)
|
||||||
{
|
{
|
||||||
var season = Query.SingleOrDefault(s => s.SeriesId == seriesId && s.SeasonNumber == seasonNumber);
|
var series = Query.Single(s => s.Id == seriesId);
|
||||||
|
return series.Seasons.Single(s => s.SeasonNumber == seasonNumber).Monitored;
|
||||||
if (season == null) return true;
|
|
||||||
|
|
||||||
return season.Monitored;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Season> GetSeasonBySeries(int seriesId)
|
public List<Season> GetSeasonBySeries(int seriesId)
|
||||||
{
|
{
|
||||||
return Query.Where(s => s.SeriesId == seriesId);
|
return Query.Single(s => s.Id == seriesId).Seasons;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Binary file not shown.
|
@ -34,12 +34,15 @@ namespace NzbDrone.Core.Tv
|
||||||
public bool UseSceneNumbering { get; set; }
|
public bool UseSceneNumbering { get; set; }
|
||||||
public string TitleSlug { get; set; }
|
public string TitleSlug { get; set; }
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
|
public int Year { get; set; }
|
||||||
|
|
||||||
public string RootFolderPath { get; set; }
|
public string RootFolderPath { get; set; }
|
||||||
|
|
||||||
public DateTime? FirstAired { get; set; }
|
public DateTime? FirstAired { get; set; }
|
||||||
public LazyLoaded<QualityProfile> QualityProfile { get; set; }
|
public LazyLoaded<QualityProfile> QualityProfile { get; set; }
|
||||||
|
|
||||||
|
public List<Season> Seasons { get; set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return string.Format("[{0}][{1}]", TvdbId, Title.NullSafe());
|
return string.Format("[{0}][{1}]", TvdbId, Title.NullSafe());
|
||||||
|
|
|
@ -37,18 +37,21 @@ namespace NzbDrone.Core.Tv
|
||||||
private readonly IConfigService _configService;
|
private readonly IConfigService _configService;
|
||||||
private readonly IMessageAggregator _messageAggregator;
|
private readonly IMessageAggregator _messageAggregator;
|
||||||
private readonly ISceneMappingService _sceneMappingService;
|
private readonly ISceneMappingService _sceneMappingService;
|
||||||
|
private readonly IEpisodeService _episodeService;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public SeriesService(ISeriesRepository seriesRepository,
|
public SeriesService(ISeriesRepository seriesRepository,
|
||||||
IConfigService configServiceService,
|
IConfigService configServiceService,
|
||||||
IMessageAggregator messageAggregator,
|
IMessageAggregator messageAggregator,
|
||||||
ISceneMappingService sceneMappingService,
|
ISceneMappingService sceneMappingService,
|
||||||
|
IEpisodeService episodeService,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
{
|
{
|
||||||
_seriesRepository = seriesRepository;
|
_seriesRepository = seriesRepository;
|
||||||
_configService = configServiceService;
|
_configService = configServiceService;
|
||||||
_messageAggregator = messageAggregator;
|
_messageAggregator = messageAggregator;
|
||||||
_sceneMappingService = sceneMappingService;
|
_sceneMappingService = sceneMappingService;
|
||||||
|
_episodeService = episodeService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,6 +158,11 @@ namespace NzbDrone.Core.Tv
|
||||||
|
|
||||||
public Series UpdateSeries(Series series)
|
public Series UpdateSeries(Series series)
|
||||||
{
|
{
|
||||||
|
foreach (var season in series.Seasons)
|
||||||
|
{
|
||||||
|
_episodeService.SetEpisodeMonitoredBySeason(series.Id, season.SeasonNumber, season.Monitored);
|
||||||
|
}
|
||||||
|
|
||||||
return _seriesRepository.Update(series);
|
return _seriesRepository.Update(series);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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<SeasonResource>
|
|
||||||
{
|
|
||||||
public SeasonClient(IRestClient restClient)
|
|
||||||
: base(restClient)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<SeasonResource> GetSeasonsInSeries(int seriesId)
|
|
||||||
{
|
|
||||||
var request = BuildRequest("?seriesId=" + seriesId.ToString());
|
|
||||||
return Get<List<SeasonResource>>(request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -27,7 +27,6 @@ namespace NzbDrone.Integration.Test
|
||||||
protected ClientBase<HistoryResource> History;
|
protected ClientBase<HistoryResource> History;
|
||||||
protected IndexerClient Indexers;
|
protected IndexerClient Indexers;
|
||||||
protected EpisodeClient Episodes;
|
protected EpisodeClient Episodes;
|
||||||
protected SeasonClient Seasons;
|
|
||||||
protected ClientBase<NamingConfigResource> NamingConfig;
|
protected ClientBase<NamingConfigResource> NamingConfig;
|
||||||
|
|
||||||
private NzbDroneRunner _runner;
|
private NzbDroneRunner _runner;
|
||||||
|
@ -64,7 +63,6 @@ namespace NzbDrone.Integration.Test
|
||||||
History = new ClientBase<HistoryResource>(RestClient);
|
History = new ClientBase<HistoryResource>(RestClient);
|
||||||
Indexers = new IndexerClient(RestClient);
|
Indexers = new IndexerClient(RestClient);
|
||||||
Episodes = new EpisodeClient(RestClient);
|
Episodes = new EpisodeClient(RestClient);
|
||||||
Seasons = new SeasonClient(RestClient);
|
|
||||||
NamingConfig = new ClientBase<NamingConfigResource>(RestClient, "config/naming");
|
NamingConfig = new ClientBase<NamingConfigResource>(RestClient, "config/naming");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,5 +73,4 @@ namespace NzbDrone.Integration.Test
|
||||||
_runner.KillAll();
|
_runner.KillAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,6 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Client\ClientBase.cs" />
|
<Compile Include="Client\ClientBase.cs" />
|
||||||
<Compile Include="Client\SeasonClient.cs" />
|
|
||||||
<Compile Include="Client\EpisodeClient.cs" />
|
<Compile Include="Client\EpisodeClient.cs" />
|
||||||
<Compile Include="Client\IndexerClient.cs" />
|
<Compile Include="Client\IndexerClient.cs" />
|
||||||
<Compile Include="Client\ReleaseClient.cs" />
|
<Compile Include="Client\ReleaseClient.cs" />
|
||||||
|
@ -102,7 +101,6 @@
|
||||||
<Compile Include="CommandIntegerationTests.cs" />
|
<Compile Include="CommandIntegerationTests.cs" />
|
||||||
<Compile Include="HistoryIntegrationTest.cs" />
|
<Compile Include="HistoryIntegrationTest.cs" />
|
||||||
<Compile Include="NamingConfigTests.cs" />
|
<Compile Include="NamingConfigTests.cs" />
|
||||||
<Compile Include="SeasonIntegrationTests.cs" />
|
|
||||||
<Compile Include="EpisodeIntegrationTests.cs" />
|
<Compile Include="EpisodeIntegrationTests.cs" />
|
||||||
<Compile Include="IndexerIntegrationFixture.cs" />
|
<Compile Include="IndexerIntegrationFixture.cs" />
|
||||||
<Compile Include="IntegrationTestDirectoryInfo.cs" />
|
<Compile Include="IntegrationTestDirectoryInfo.cs" />
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,8 +2,7 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="span2">
|
<div class="span2">
|
||||||
<a href="{{traktUrl}}" target="_blank">
|
<a href="{{traktUrl}}" target="_blank">
|
||||||
<img class="new-series-poster" src="{{remotePoster}}"
|
<img class="new-series-poster" src="{{remotePoster}}" {{defaultImg}} >
|
||||||
{{defaultImg}} >
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="span9">
|
<div class="span9">
|
||||||
|
|
|
@ -113,4 +113,12 @@
|
||||||
|
|
||||||
.icon-nd-status:before {
|
.icon-nd-status:before {
|
||||||
.icon(@circle);
|
.icon(@circle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-nd-monitored:before {
|
||||||
|
.icon(@bookmark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-nd-unmonitored:before {
|
||||||
|
.icon(@bookmark-empty);
|
||||||
}
|
}
|
|
@ -24,16 +24,10 @@ define(
|
||||||
this.series.show(new LoadingView());
|
this.series.show(new LoadingView());
|
||||||
|
|
||||||
this.seriesCollection = SeriesCollection;
|
this.seriesCollection = SeriesCollection;
|
||||||
this.seasonCollection = new SeasonCollection();
|
|
||||||
|
|
||||||
var promise = this.seasonCollection.fetch();
|
self.series.show(new SeriesCollectionView({
|
||||||
|
collection: self.seriesCollection
|
||||||
promise.done(function () {
|
}));
|
||||||
self.series.show(new SeriesCollectionView({
|
|
||||||
collection: self.seriesCollection,
|
|
||||||
seasonCollection: self.seasonCollection
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,22 +5,6 @@ define(
|
||||||
'SeasonPass/SeriesLayout'
|
'SeasonPass/SeriesLayout'
|
||||||
], function (Marionette, SeriesLayout) {
|
], function (Marionette, SeriesLayout) {
|
||||||
return Marionette.CollectionView.extend({
|
return Marionette.CollectionView.extend({
|
||||||
|
itemView: SeriesLayout
|
||||||
itemView: SeriesLayout,
|
|
||||||
|
|
||||||
initialize: function (options) {
|
|
||||||
|
|
||||||
if (!options.seasonCollection) {
|
|
||||||
throw 'seasonCollection is needed';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.seasonCollection = options.seasonCollection;
|
|
||||||
},
|
|
||||||
|
|
||||||
itemViewOptions: function () {
|
|
||||||
return {
|
|
||||||
seasonCollection: this.seasonCollection
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,11 +2,9 @@
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'marionette',
|
'marionette',
|
||||||
'backgrid',
|
|
||||||
'Series/SeasonCollection',
|
'Series/SeasonCollection',
|
||||||
'Cells/ToggleCell',
|
|
||||||
'Shared/Actioneer'
|
'Shared/Actioneer'
|
||||||
], function (Marionette, Backgrid, SeasonCollection, ToggleCell, Actioneer) {
|
], function (Marionette, SeasonCollection, Actioneer) {
|
||||||
return Marionette.Layout.extend({
|
return Marionette.Layout.extend({
|
||||||
template: 'SeasonPass/SeriesLayoutTemplate',
|
template: 'SeasonPass/SeriesLayoutTemplate',
|
||||||
|
|
||||||
|
@ -19,48 +17,22 @@ define(
|
||||||
events: {
|
events: {
|
||||||
'change .x-season-select': '_seasonSelected',
|
'change .x-season-select': '_seasonSelected',
|
||||||
'click .x-expander' : '_expand',
|
'click .x-expander' : '_expand',
|
||||||
'click .x-latest' : '_latest'
|
'click .x-latest' : '_latest',
|
||||||
|
'click .x-monitored' : '_toggleSeasonMonitored'
|
||||||
},
|
},
|
||||||
|
|
||||||
regions: {
|
regions: {
|
||||||
seasonGrid: '.x-season-grid'
|
seasonGrid: '.x-season-grid'
|
||||||
},
|
},
|
||||||
|
|
||||||
columns:
|
initialize: function () {
|
||||||
[
|
this.seasonCollection = new SeasonCollection(this.model.get('seasons'));
|
||||||
{
|
|
||||||
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);
|
|
||||||
this.expanded = false;
|
this.expanded = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
onRender: function () {
|
onRender: function () {
|
||||||
this.seasonGrid.show(new Backgrid.Grid({
|
|
||||||
columns : this.columns,
|
|
||||||
collection: this.seasonCollection,
|
|
||||||
className : 'table table-condensed season-grid span5'
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!this.expanded) {
|
if (!this.expanded) {
|
||||||
this.seasonGrid.$el.hide();
|
this.ui.seasonGrid.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
this._setExpanderIcon();
|
this._setExpanderIcon();
|
||||||
|
@ -103,33 +75,51 @@ define(
|
||||||
},
|
},
|
||||||
|
|
||||||
_latest: function () {
|
_latest: function () {
|
||||||
var season = _.max(this.seasonCollection.models, function (model) {
|
var season = _.max(this.model.get('seasons'), function (s) {
|
||||||
return model.get('seasonNumber');
|
return s.seasonNumber;
|
||||||
});
|
});
|
||||||
|
|
||||||
//var seasonNumber = season.get('seasonNumber');
|
this._setMonitored(season.seasonNumber);
|
||||||
|
|
||||||
this._setMonitored(season.get('seasonNumber'))
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_setMonitored: function (seasonNumber) {
|
_setMonitored: function (seasonNumber) {
|
||||||
//TODO: use Actioneer?
|
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var promise = $.ajax({
|
this.model.setSeasonPass(seasonNumber);
|
||||||
url: this.seasonCollection.url + '/pass',
|
|
||||||
type: 'POST',
|
var promise = this.model.save();
|
||||||
data: {
|
|
||||||
seriesId: this.model.get('id'),
|
|
||||||
seasonNumber: seasonNumber
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
promise.done(function (data) {
|
promise.done(function (data) {
|
||||||
self.seasonCollection = new SeasonCollection(data);
|
self.seasonCollection = new SeasonCollection(data);
|
||||||
self.render();
|
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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,12 +12,12 @@
|
||||||
<span class="span3">
|
<span class="span3">
|
||||||
<select class="x-season-select season-select">
|
<select class="x-season-select season-select">
|
||||||
<option value="-1">Select season...</option>
|
<option value="-1">Select season...</option>
|
||||||
{{#each seasons.models}}
|
{{#each seasons}}
|
||||||
{{#if_eq attributes.seasonNumber compare="0"}}
|
{{#if_eq seasonNumber compare="0"}}
|
||||||
<option value="{{attributes.seasonNumber}}">Specials</option>
|
<option value="{{seasonNumber}}">Specials</option>
|
||||||
{{else}}
|
{{else}}
|
||||||
<option value="{{attributes.seasonNumber}}">Season {{attributes.seasonNumber}}</option>
|
<option value="{{seasonNumber}}">Season {{seasonNumber}}</option>
|
||||||
{{/if_eq}}
|
{{/if_eq}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
@ -36,7 +36,36 @@
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="span11">
|
<div class="span11">
|
||||||
<div class="x-season-grid season-grid"></div>
|
<div class="x-season-grid season-grid">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th class="sortable">Season</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each seasons}}
|
||||||
|
<tr>
|
||||||
|
<td class="toggle-cell x-monitored" data-season-number="{{seasonNumber}}">
|
||||||
|
{{#if monitored}}
|
||||||
|
<i class="icon-nd-monitored"></i>
|
||||||
|
{{else}}
|
||||||
|
<i class="icon-nd-unmonitored"></i>
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{#if_eq seasonNumber compare="0"}}
|
||||||
|
Specials
|
||||||
|
{{else}}
|
||||||
|
Season {{seasonNumber}}
|
||||||
|
{{/if_eq}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -117,8 +117,10 @@ define(
|
||||||
_seasonMonitored: function () {
|
_seasonMonitored: function () {
|
||||||
var name = 'monitored';
|
var name = 'monitored';
|
||||||
this.model.set(name, !this.model.get(name));
|
this.model.set(name, !this.model.get(name));
|
||||||
|
this.series.setSeasonMonitored(this.model.get('seasonNumber'));
|
||||||
|
|
||||||
Actioneer.SaveModel({
|
Actioneer.SaveModel({
|
||||||
|
model : this.series,
|
||||||
context: this,
|
context: this,
|
||||||
element: this.ui.seasonMonitored,
|
element: this.ui.seasonMonitored,
|
||||||
always : this._afterSeasonMonitored
|
always : this._afterSeasonMonitored
|
||||||
|
|
|
@ -113,12 +113,12 @@ define(
|
||||||
this.ui.monitored.removeClass('icon-spin icon-spinner');
|
this.ui.monitored.removeClass('icon-spin icon-spinner');
|
||||||
|
|
||||||
if (this.model.get('monitored')) {
|
if (this.model.get('monitored')) {
|
||||||
this.ui.monitored.addClass('icon-bookmark');
|
this.ui.monitored.addClass('icon-nd-monitored');
|
||||||
this.ui.monitored.removeClass('icon-bookmark-empty');
|
this.ui.monitored.removeClass('icon-nd-unmonitored');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.ui.monitored.addClass('icon-bookmark-empty');
|
this.ui.monitored.addClass('icon-nd-unmonitored');
|
||||||
this.ui.monitored.removeClass('icon-bookmark');
|
this.ui.monitored.removeClass('icon-nd-monitored');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -176,11 +176,11 @@ define(
|
||||||
|
|
||||||
this.seasons.show(new LoadingView());
|
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.episodeCollection = new EpisodeCollection({ seriesId: this.model.id });
|
||||||
this.episodeFileCollection = new EpisodeFileCollection({ 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({
|
var seasonCollectionView = new SeasonCollectionView({
|
||||||
collection : self.seasonCollection,
|
collection : self.seasonCollection,
|
||||||
episodeCollection: self.episodeCollection,
|
episodeCollection: self.episodeCollection,
|
||||||
|
|
|
@ -5,21 +5,10 @@ define(
|
||||||
'Series/SeasonModel'
|
'Series/SeasonModel'
|
||||||
], function (Backbone, SeasonModel) {
|
], function (Backbone, SeasonModel) {
|
||||||
return Backbone.Collection.extend({
|
return Backbone.Collection.extend({
|
||||||
url : window.ApiRoot + '/season',
|
|
||||||
model: SeasonModel,
|
model: SeasonModel,
|
||||||
|
|
||||||
comparator: function (season) {
|
comparator: function (season) {
|
||||||
return -season.get('seasonNumber');
|
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);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,6 +14,25 @@ define(
|
||||||
episodeCount : 0,
|
episodeCount : 0,
|
||||||
isExisting : false,
|
isExisting : false,
|
||||||
status : 0
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -256,6 +256,7 @@
|
||||||
.clickable;
|
.clickable;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
|
width: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.season-grid {
|
.season-grid {
|
||||||
|
|
|
@ -33,7 +33,9 @@ define(
|
||||||
this._showStartMessage(options);
|
this._showStartMessage(options);
|
||||||
this._setSpinnerOnElement(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);
|
this._handlePromise(promise, options);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue