mirror of
https://github.com/Radarr/Radarr.git
synced 2024-09-17 15:02:34 +02:00
Merge pull request #546 from Sonarr/season-pass
Season pass supports multi-select and new toggling options
This commit is contained in:
commit
27980b2cd6
@ -10,6 +10,8 @@ public static class ValueInjectorExtensions
|
|||||||
{
|
{
|
||||||
public static TTarget InjectTo<TTarget>(this object source) where TTarget : new()
|
public static TTarget InjectTo<TTarget>(this object source) where TTarget : new()
|
||||||
{
|
{
|
||||||
|
if (source == null) return default(TTarget);
|
||||||
|
|
||||||
var targetType = typeof(TTarget);
|
var targetType = typeof(TTarget);
|
||||||
|
|
||||||
if (targetType.IsGenericType &&
|
if (targetType.IsGenericType &&
|
||||||
|
@ -216,12 +216,15 @@
|
|||||||
<Compile Include="REST\RestResource.cs" />
|
<Compile Include="REST\RestResource.cs" />
|
||||||
<Compile Include="RootFolders\RootFolderModule.cs" />
|
<Compile Include="RootFolders\RootFolderModule.cs" />
|
||||||
<Compile Include="RootFolders\RootFolderResource.cs" />
|
<Compile Include="RootFolders\RootFolderResource.cs" />
|
||||||
|
<Compile Include="SeasonPass\SeasonPassResource.cs" />
|
||||||
<Compile Include="Series\AlternateTitleResource.cs" />
|
<Compile Include="Series\AlternateTitleResource.cs" />
|
||||||
<Compile Include="Series\SeasonResource.cs" />
|
<Compile Include="Series\SeasonResource.cs" />
|
||||||
|
<Compile Include="SeasonPass\SeasonPassModule.cs" />
|
||||||
<Compile Include="Series\SeriesEditorModule.cs" />
|
<Compile Include="Series\SeriesEditorModule.cs" />
|
||||||
<Compile Include="Series\SeriesLookupModule.cs" />
|
<Compile Include="Series\SeriesLookupModule.cs" />
|
||||||
<Compile Include="Series\SeriesModule.cs" />
|
<Compile Include="Series\SeriesModule.cs" />
|
||||||
<Compile Include="Series\SeriesResource.cs" />
|
<Compile Include="Series\SeriesResource.cs" />
|
||||||
|
<Compile Include="Series\SeasonStatisticsResource.cs" />
|
||||||
<Compile Include="System\Backup\BackupModule.cs" />
|
<Compile Include="System\Backup\BackupModule.cs" />
|
||||||
<Compile Include="System\Backup\BackupResource.cs" />
|
<Compile Include="System\Backup\BackupResource.cs" />
|
||||||
<Compile Include="System\Tasks\TaskModule.cs" />
|
<Compile Include="System\Tasks\TaskModule.cs" />
|
||||||
@ -272,4 +275,4 @@
|
|||||||
<Target Name="AfterBuild">
|
<Target Name="AfterBuild">
|
||||||
</Target>
|
</Target>
|
||||||
-->
|
-->
|
||||||
</Project>
|
</Project>
|
33
src/NzbDrone.Api/SeasonPass/SeasonPassModule.cs
Normal file
33
src/NzbDrone.Api/SeasonPass/SeasonPassModule.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Nancy;
|
||||||
|
using NzbDrone.Api.Extensions;
|
||||||
|
using NzbDrone.Api.Mapping;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.SeasonPass
|
||||||
|
{
|
||||||
|
public class SeasonPassModule : NzbDroneApiModule
|
||||||
|
{
|
||||||
|
private readonly IEpisodeMonitoredService _episodeMonitoredService;
|
||||||
|
|
||||||
|
public SeasonPassModule(IEpisodeMonitoredService episodeMonitoredService)
|
||||||
|
: base("/seasonpass")
|
||||||
|
{
|
||||||
|
_episodeMonitoredService = episodeMonitoredService;
|
||||||
|
Post["/"] = series => UpdateAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response UpdateAll()
|
||||||
|
{
|
||||||
|
//Read from request
|
||||||
|
var request = Request.Body.FromJson<SeasonPassResource>();
|
||||||
|
|
||||||
|
foreach (var s in request.Series)
|
||||||
|
{
|
||||||
|
_episodeMonitoredService.SetEpisodeMonitoredStatus(s, request.MonitoringOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "ok".AsResponse(HttpStatusCode.Accepted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
src/NzbDrone.Api/SeasonPass/SeasonPassResource.cs
Normal file
11
src/NzbDrone.Api/SeasonPass/SeasonPassResource.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.SeasonPass
|
||||||
|
{
|
||||||
|
public class SeasonPassResource
|
||||||
|
{
|
||||||
|
public List<Core.Tv.Series> Series { get; set; }
|
||||||
|
public MonitoringOptions MonitoringOptions { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,9 @@
|
|||||||
using System;
|
namespace NzbDrone.Api.Series
|
||||||
|
|
||||||
namespace NzbDrone.Api.Series
|
|
||||||
{
|
{
|
||||||
public class SeasonResource
|
public class SeasonResource
|
||||||
{
|
{
|
||||||
public int SeasonNumber { get; set; }
|
public int SeasonNumber { get; set; }
|
||||||
public Boolean Monitored { get; set; }
|
public bool Monitored { get; set; }
|
||||||
|
public SeasonStatisticsResource Statistics { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
src/NzbDrone.Api/Series/SeasonStatisticsResource.cs
Normal file
25
src/NzbDrone.Api/Series/SeasonStatisticsResource.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Series
|
||||||
|
{
|
||||||
|
public class SeasonStatisticsResource
|
||||||
|
{
|
||||||
|
public DateTime? NextAiring { get; set; }
|
||||||
|
public DateTime? PreviousAiring { get; set; }
|
||||||
|
public int EpisodeFileCount { get; set; }
|
||||||
|
public int EpisodeCount { get; set; }
|
||||||
|
public int TotalEpisodeCount { get; set; }
|
||||||
|
public long SizeOnDisk { get; set; }
|
||||||
|
|
||||||
|
public decimal PercentOfEpisodes
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (EpisodeCount == 0) return 0;
|
||||||
|
|
||||||
|
return (decimal)EpisodeFileCount / (decimal)EpisodeCount * 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -160,11 +160,17 @@ private void LinkSeriesStatistics(List<SeriesResource> resources, List<SeriesSta
|
|||||||
|
|
||||||
private void LinkSeriesStatistics(SeriesResource resource, SeriesStatistics seriesStatistics)
|
private void LinkSeriesStatistics(SeriesResource resource, SeriesStatistics seriesStatistics)
|
||||||
{
|
{
|
||||||
|
resource.TotalEpisodeCount = seriesStatistics.TotalEpisodeCount;
|
||||||
resource.EpisodeCount = seriesStatistics.EpisodeCount;
|
resource.EpisodeCount = seriesStatistics.EpisodeCount;
|
||||||
resource.EpisodeFileCount = seriesStatistics.EpisodeFileCount;
|
resource.EpisodeFileCount = seriesStatistics.EpisodeFileCount;
|
||||||
resource.NextAiring = seriesStatistics.NextAiring;
|
resource.NextAiring = seriesStatistics.NextAiring;
|
||||||
resource.PreviousAiring = seriesStatistics.PreviousAiring;
|
resource.PreviousAiring = seriesStatistics.PreviousAiring;
|
||||||
resource.SizeOnDisk = seriesStatistics.SizeOnDisk;
|
resource.SizeOnDisk = seriesStatistics.SizeOnDisk;
|
||||||
|
|
||||||
|
foreach (var season in resource.Seasons)
|
||||||
|
{
|
||||||
|
season.Statistics = seriesStatistics.SeasonStatistics.SingleOrDefault(s => s.SeasonNumber == season.SeasonNumber).InjectTo<SeasonStatisticsResource>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PopulateAlternateTitles(List<SeriesResource> resources)
|
private void PopulateAlternateTitles(List<SeriesResource> resources)
|
||||||
|
@ -28,6 +28,7 @@ public Int32 SeasonCount
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Int32? TotalEpisodeCount { get; set; }
|
||||||
public Int32? EpisodeCount { get; set; }
|
public Int32? EpisodeCount { get; set; }
|
||||||
public Int32? EpisodeFileCount { get; set; }
|
public Int32? EpisodeFileCount { get; set; }
|
||||||
public Int64? SizeOnDisk { get; set; }
|
public Int64? SizeOnDisk { get; set; }
|
||||||
@ -69,6 +70,8 @@ public Int32 SeasonCount
|
|||||||
public DateTime Added { get; set; }
|
public DateTime Added { get; set; }
|
||||||
public AddSeriesOptions AddOptions { get; set; }
|
public AddSeriesOptions AddOptions { get; set; }
|
||||||
|
|
||||||
|
//TODO: Add series statistics as a property of the series (instead of individual properties)
|
||||||
|
|
||||||
//Used to support legacy consumers
|
//Used to support legacy consumers
|
||||||
public Int32 QualityProfileId
|
public Int32 QualityProfileId
|
||||||
{
|
{
|
||||||
|
@ -338,7 +338,7 @@
|
|||||||
<Compile Include="TvTests\MoveSeriesServiceFixture.cs" />
|
<Compile Include="TvTests\MoveSeriesServiceFixture.cs" />
|
||||||
<Compile Include="TvTests\RefreshEpisodeServiceFixture.cs" />
|
<Compile Include="TvTests\RefreshEpisodeServiceFixture.cs" />
|
||||||
<Compile Include="TvTests\RefreshSeriesServiceFixture.cs" />
|
<Compile Include="TvTests\RefreshSeriesServiceFixture.cs" />
|
||||||
<Compile Include="TvTests\SeriesAddedHandlerTests\SetEpisodeMontitoredFixture.cs" />
|
<Compile Include="TvTests\EpisodeMonitoredServiceTests\SetEpisodeMontitoredFixture.cs" />
|
||||||
<Compile Include="TvTests\SeriesRepositoryTests\SeriesRepositoryFixture.cs" />
|
<Compile Include="TvTests\SeriesRepositoryTests\SeriesRepositoryFixture.cs" />
|
||||||
<Compile Include="TvTests\SeriesServiceTests\AddSeriesFixture.cs" />
|
<Compile Include="TvTests\SeriesServiceTests\AddSeriesFixture.cs" />
|
||||||
<Compile Include="TvTests\SeriesServiceTests\UpdateMultipleSeriesFixture.cs" />
|
<Compile Include="TvTests\SeriesServiceTests\UpdateMultipleSeriesFixture.cs" />
|
||||||
|
@ -9,10 +9,10 @@
|
|||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.TvTests.SeriesAddedHandlerTests
|
namespace NzbDrone.Core.Test.TvTests.EpisodeMonitoredServiceTests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class SetEpisodeMontitoredFixture : CoreTest<SeriesScannedHandler>
|
public class SetEpisodeMontitoredFixture : CoreTest<EpisodeMonitoredService>
|
||||||
{
|
{
|
||||||
private Series _series;
|
private Series _series;
|
||||||
private List<Episode> _episodes;
|
private List<Episode> _episodes;
|
||||||
@ -56,16 +56,6 @@ public void Setup()
|
|||||||
.Returns(_episodes);
|
.Returns(_episodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WithSeriesAddedEvent(AddSeriesOptions options)
|
|
||||||
{
|
|
||||||
_series.AddOptions = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TriggerSeriesScannedEvent()
|
|
||||||
{
|
|
||||||
Subject.Handle(new SeriesScannedEvent(_series));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenSpecials()
|
private void GivenSpecials()
|
||||||
{
|
{
|
||||||
foreach (var episode in _episodes)
|
foreach (var episode in _episodes)
|
||||||
@ -79,8 +69,7 @@ private void GivenSpecials()
|
|||||||
[Test]
|
[Test]
|
||||||
public void should_be_able_to_monitor_all_episodes()
|
public void should_be_able_to_monitor_all_episodes()
|
||||||
{
|
{
|
||||||
WithSeriesAddedEvent(new AddSeriesOptions());
|
Subject.SetEpisodeMonitoredStatus(_series, new MonitoringOptions());
|
||||||
TriggerSeriesScannedEvent();
|
|
||||||
|
|
||||||
Mocker.GetMock<IEpisodeService>()
|
Mocker.GetMock<IEpisodeService>()
|
||||||
.Verify(v => v.UpdateEpisodes(It.Is<List<Episode>>(l => l.All(e => e.Monitored))));
|
.Verify(v => v.UpdateEpisodes(It.Is<List<Episode>>(l => l.All(e => e.Monitored))));
|
||||||
@ -89,13 +78,13 @@ public void should_be_able_to_monitor_all_episodes()
|
|||||||
[Test]
|
[Test]
|
||||||
public void should_be_able_to_monitor_missing_episodes_only()
|
public void should_be_able_to_monitor_missing_episodes_only()
|
||||||
{
|
{
|
||||||
WithSeriesAddedEvent(new AddSeriesOptions
|
var monitoringOptions = new MonitoringOptions
|
||||||
{
|
{
|
||||||
IgnoreEpisodesWithFiles = true,
|
IgnoreEpisodesWithFiles = true,
|
||||||
IgnoreEpisodesWithoutFiles = false
|
IgnoreEpisodesWithoutFiles = false
|
||||||
});
|
};
|
||||||
|
|
||||||
TriggerSeriesScannedEvent();
|
Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions);
|
||||||
|
|
||||||
VerifyMonitored(e => !e.HasFile);
|
VerifyMonitored(e => !e.HasFile);
|
||||||
VerifyNotMonitored(e => e.HasFile);
|
VerifyNotMonitored(e => e.HasFile);
|
||||||
@ -104,13 +93,13 @@ public void should_be_able_to_monitor_missing_episodes_only()
|
|||||||
[Test]
|
[Test]
|
||||||
public void should_be_able_to_monitor_new_episodes_only()
|
public void should_be_able_to_monitor_new_episodes_only()
|
||||||
{
|
{
|
||||||
WithSeriesAddedEvent(new AddSeriesOptions
|
var monitoringOptions = new MonitoringOptions
|
||||||
{
|
{
|
||||||
IgnoreEpisodesWithFiles = true,
|
IgnoreEpisodesWithFiles = true,
|
||||||
IgnoreEpisodesWithoutFiles = true
|
IgnoreEpisodesWithoutFiles = true
|
||||||
});
|
};
|
||||||
|
|
||||||
TriggerSeriesScannedEvent();
|
Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions);
|
||||||
|
|
||||||
VerifyMonitored(e => e.AirDateUtc.HasValue && e.AirDateUtc.Value.After(DateTime.UtcNow));
|
VerifyMonitored(e => e.AirDateUtc.HasValue && e.AirDateUtc.Value.After(DateTime.UtcNow));
|
||||||
VerifyMonitored(e => !e.AirDateUtc.HasValue);
|
VerifyMonitored(e => !e.AirDateUtc.HasValue);
|
||||||
@ -122,13 +111,13 @@ public void should_not_monitor_missing_specials()
|
|||||||
{
|
{
|
||||||
GivenSpecials();
|
GivenSpecials();
|
||||||
|
|
||||||
WithSeriesAddedEvent(new AddSeriesOptions
|
var monitoringOptions = new MonitoringOptions
|
||||||
{
|
{
|
||||||
IgnoreEpisodesWithFiles = true,
|
IgnoreEpisodesWithFiles = true,
|
||||||
IgnoreEpisodesWithoutFiles = false
|
IgnoreEpisodesWithoutFiles = false
|
||||||
});
|
};
|
||||||
|
|
||||||
TriggerSeriesScannedEvent();
|
Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions);
|
||||||
|
|
||||||
VerifyMonitored(e => !e.HasFile);
|
VerifyMonitored(e => !e.HasFile);
|
||||||
VerifyNotMonitored(e => e.HasFile);
|
VerifyNotMonitored(e => e.HasFile);
|
||||||
@ -139,13 +128,14 @@ public void should_not_monitor_new_specials()
|
|||||||
{
|
{
|
||||||
GivenSpecials();
|
GivenSpecials();
|
||||||
|
|
||||||
WithSeriesAddedEvent(new AddSeriesOptions
|
var monitoringOptions = new MonitoringOptions
|
||||||
{
|
{
|
||||||
IgnoreEpisodesWithFiles = true,
|
IgnoreEpisodesWithFiles = true,
|
||||||
IgnoreEpisodesWithoutFiles = true
|
IgnoreEpisodesWithoutFiles = true
|
||||||
});
|
};
|
||||||
|
|
||||||
|
Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions);
|
||||||
|
|
||||||
TriggerSeriesScannedEvent();
|
|
||||||
VerifyMonitored(e => e.AirDateUtc.HasValue && e.AirDateUtc.Value.After(DateTime.UtcNow));
|
VerifyMonitored(e => e.AirDateUtc.HasValue && e.AirDateUtc.Value.After(DateTime.UtcNow));
|
||||||
VerifyMonitored(e => !e.AirDateUtc.HasValue);
|
VerifyMonitored(e => !e.AirDateUtc.HasValue);
|
||||||
VerifyNotMonitored(e => e.AirDateUtc.HasValue && e.AirDateUtc.Value.Before(DateTime.UtcNow));
|
VerifyNotMonitored(e => e.AirDateUtc.HasValue && e.AirDateUtc.Value.Before(DateTime.UtcNow));
|
||||||
@ -174,17 +164,28 @@ public void should_not_monitor_season_when_all_episodes_are_monitored_except_lat
|
|||||||
.Setup(s => s.GetEpisodeBySeries(It.IsAny<int>()))
|
.Setup(s => s.GetEpisodeBySeries(It.IsAny<int>()))
|
||||||
.Returns(_episodes);
|
.Returns(_episodes);
|
||||||
|
|
||||||
WithSeriesAddedEvent(new AddSeriesOptions
|
var monitoringOptions = new MonitoringOptions
|
||||||
{
|
{
|
||||||
IgnoreEpisodesWithoutFiles = true
|
IgnoreEpisodesWithoutFiles = true
|
||||||
});
|
};
|
||||||
|
|
||||||
TriggerSeriesScannedEvent();
|
Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions);
|
||||||
|
|
||||||
VerifySeasonMonitored(n => n.SeasonNumber == 2);
|
VerifySeasonMonitored(n => n.SeasonNumber == 2);
|
||||||
VerifySeasonNotMonitored(n => n.SeasonNumber == 1);
|
VerifySeasonNotMonitored(n => n.SeasonNumber == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_ignore_episodes_when_season_is_not_monitored()
|
||||||
|
{
|
||||||
|
_series.Seasons.ForEach(s => s.Monitored = false);
|
||||||
|
|
||||||
|
Subject.SetEpisodeMonitoredStatus(_series, new MonitoringOptions());
|
||||||
|
|
||||||
|
Mocker.GetMock<IEpisodeService>()
|
||||||
|
.Verify(v => v.UpdateEpisodes(It.Is<List<Episode>>(l => l.All(e => !e.Monitored))));
|
||||||
|
}
|
||||||
|
|
||||||
private void VerifyMonitored(Func<Episode, bool> predicate)
|
private void VerifyMonitored(Func<Episode, bool> predicate)
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IEpisodeService>()
|
Mocker.GetMock<IEpisodeService>()
|
@ -91,7 +91,7 @@ public static void Map()
|
|||||||
Mapper.Entity<Profile>().RegisterModel("Profiles");
|
Mapper.Entity<Profile>().RegisterModel("Profiles");
|
||||||
Mapper.Entity<Log>().RegisterModel("Logs");
|
Mapper.Entity<Log>().RegisterModel("Logs");
|
||||||
Mapper.Entity<NamingConfig>().RegisterModel("NamingConfig");
|
Mapper.Entity<NamingConfig>().RegisterModel("NamingConfig");
|
||||||
Mapper.Entity<SeriesStatistics>().MapResultSet();
|
Mapper.Entity<SeasonStatistics>().MapResultSet();
|
||||||
Mapper.Entity<Blacklist>().RegisterModel("Blacklist");
|
Mapper.Entity<Blacklist>().RegisterModel("Blacklist");
|
||||||
Mapper.Entity<MetadataFile>().RegisterModel("MetadataFiles");
|
Mapper.Entity<MetadataFile>().RegisterModel("MetadataFiles");
|
||||||
|
|
||||||
|
@ -869,6 +869,7 @@
|
|||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="RootFolders\UnmappedFolder.cs" />
|
<Compile Include="RootFolders\UnmappedFolder.cs" />
|
||||||
<Compile Include="Security.cs" />
|
<Compile Include="Security.cs" />
|
||||||
|
<Compile Include="SeriesStats\SeasonStatistics.cs" />
|
||||||
<Compile Include="SeriesStats\SeriesStatistics.cs" />
|
<Compile Include="SeriesStats\SeriesStatistics.cs" />
|
||||||
<Compile Include="SeriesStats\SeriesStatisticsRepository.cs" />
|
<Compile Include="SeriesStats\SeriesStatisticsRepository.cs" />
|
||||||
<Compile Include="SeriesStats\SeriesStatisticsService.cs" />
|
<Compile Include="SeriesStats\SeriesStatisticsService.cs" />
|
||||||
@ -892,6 +893,7 @@
|
|||||||
<Compile Include="Tv\Commands\RefreshSeriesCommand.cs" />
|
<Compile Include="Tv\Commands\RefreshSeriesCommand.cs" />
|
||||||
<Compile Include="Tv\Episode.cs" />
|
<Compile Include="Tv\Episode.cs" />
|
||||||
<Compile Include="Tv\EpisodeCutoffService.cs" />
|
<Compile Include="Tv\EpisodeCutoffService.cs" />
|
||||||
|
<Compile Include="Tv\EpisodeMonitoredService.cs" />
|
||||||
<Compile Include="Tv\EpisodeRepository.cs">
|
<Compile Include="Tv\EpisodeRepository.cs">
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
@ -903,6 +905,7 @@
|
|||||||
<Compile Include="Tv\Events\SeriesMovedEvent.cs" />
|
<Compile Include="Tv\Events\SeriesMovedEvent.cs" />
|
||||||
<Compile Include="Tv\Events\SeriesRefreshStartingEvent.cs" />
|
<Compile Include="Tv\Events\SeriesRefreshStartingEvent.cs" />
|
||||||
<Compile Include="Tv\Events\SeriesUpdatedEvent.cs" />
|
<Compile Include="Tv\Events\SeriesUpdatedEvent.cs" />
|
||||||
|
<Compile Include="Tv\MonitoringOptions.cs" />
|
||||||
<Compile Include="Tv\MoveSeriesService.cs" />
|
<Compile Include="Tv\MoveSeriesService.cs" />
|
||||||
<Compile Include="Tv\Ratings.cs" />
|
<Compile Include="Tv\Ratings.cs" />
|
||||||
<Compile Include="Tv\RefreshEpisodeService.cs" />
|
<Compile Include="Tv\RefreshEpisodeService.cs" />
|
||||||
|
41
src/NzbDrone.Core/SeriesStats/SeasonStatistics.cs
Normal file
41
src/NzbDrone.Core/SeriesStats/SeasonStatistics.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.SeriesStats
|
||||||
|
{
|
||||||
|
public class SeasonStatistics : ResultSet
|
||||||
|
{
|
||||||
|
public int SeriesId { get; set; }
|
||||||
|
public int SeasonNumber { get; set; }
|
||||||
|
public string NextAiringString { get; set; }
|
||||||
|
public string PreviousAiringString { get; set; }
|
||||||
|
public int EpisodeFileCount { get; set; }
|
||||||
|
public int EpisodeCount { get; set; }
|
||||||
|
public int TotalEpisodeCount { get; set; }
|
||||||
|
public long SizeOnDisk { get; set; }
|
||||||
|
|
||||||
|
public DateTime? NextAiring
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
DateTime nextAiring;
|
||||||
|
|
||||||
|
if (!DateTime.TryParse(NextAiringString, out nextAiring)) return null;
|
||||||
|
|
||||||
|
return nextAiring;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime? PreviousAiring
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
DateTime previousAiring;
|
||||||
|
|
||||||
|
if (!DateTime.TryParse(PreviousAiringString, out previousAiring)) return null;
|
||||||
|
|
||||||
|
return previousAiring;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,19 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
namespace NzbDrone.Core.SeriesStats
|
namespace NzbDrone.Core.SeriesStats
|
||||||
{
|
{
|
||||||
public class SeriesStatistics : ResultSet
|
public class SeriesStatistics : ResultSet
|
||||||
{
|
{
|
||||||
public Int32 SeriesId { get; set; }
|
public int SeriesId { get; set; }
|
||||||
public String NextAiringString { get; set; }
|
public string NextAiringString { get; set; }
|
||||||
public String PreviousAiringString { get; set; }
|
public string PreviousAiringString { get; set; }
|
||||||
public Int32 EpisodeFileCount { get; set; }
|
public int EpisodeFileCount { get; set; }
|
||||||
public Int32 EpisodeCount { get; set; }
|
public int EpisodeCount { get; set; }
|
||||||
public Int64 SizeOnDisk { get; set; }
|
public int TotalEpisodeCount { get; set; }
|
||||||
|
public long SizeOnDisk { get; set; }
|
||||||
|
public List<SeasonStatistics> SeasonStatistics { get; set; }
|
||||||
|
|
||||||
public DateTime? NextAiring
|
public DateTime? NextAiring
|
||||||
{
|
{
|
||||||
|
@ -7,8 +7,8 @@ namespace NzbDrone.Core.SeriesStats
|
|||||||
{
|
{
|
||||||
public interface ISeriesStatisticsRepository
|
public interface ISeriesStatisticsRepository
|
||||||
{
|
{
|
||||||
List<SeriesStatistics> SeriesStatistics();
|
List<SeasonStatistics> SeriesStatistics();
|
||||||
SeriesStatistics SeriesStatistics(Int32 seriesId);
|
List<SeasonStatistics> SeriesStatistics(Int32 seriesId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SeriesStatisticsRepository : ISeriesStatisticsRepository
|
public class SeriesStatisticsRepository : ISeriesStatisticsRepository
|
||||||
@ -20,7 +20,7 @@ public SeriesStatisticsRepository(IMainDatabase database)
|
|||||||
_database = database;
|
_database = database;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<SeriesStatistics> SeriesStatistics()
|
public List<SeasonStatistics> SeriesStatistics()
|
||||||
{
|
{
|
||||||
var mapper = _database.GetDataMapper();
|
var mapper = _database.GetDataMapper();
|
||||||
|
|
||||||
@ -32,10 +32,10 @@ public List<SeriesStatistics> SeriesStatistics()
|
|||||||
sb.AppendLine(GetGroupByClause());
|
sb.AppendLine(GetGroupByClause());
|
||||||
var queryText = sb.ToString();
|
var queryText = sb.ToString();
|
||||||
|
|
||||||
return mapper.Query<SeriesStatistics>(queryText);
|
return mapper.Query<SeasonStatistics>(queryText);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SeriesStatistics SeriesStatistics(Int32 seriesId)
|
public List<SeasonStatistics> SeriesStatistics(Int32 seriesId)
|
||||||
{
|
{
|
||||||
var mapper = _database.GetDataMapper();
|
var mapper = _database.GetDataMapper();
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ public SeriesStatistics SeriesStatistics(Int32 seriesId)
|
|||||||
sb.AppendLine(GetGroupByClause());
|
sb.AppendLine(GetGroupByClause());
|
||||||
var queryText = sb.ToString();
|
var queryText = sb.ToString();
|
||||||
|
|
||||||
return mapper.Find<SeriesStatistics>(queryText);
|
return mapper.Query<SeasonStatistics>(queryText);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String GetSelectClause()
|
private String GetSelectClause()
|
||||||
@ -57,17 +57,19 @@ private String GetSelectClause()
|
|||||||
return @"SELECT Episodes.*, SUM(EpisodeFiles.Size) as SizeOnDisk FROM
|
return @"SELECT Episodes.*, SUM(EpisodeFiles.Size) as SizeOnDisk FROM
|
||||||
(SELECT
|
(SELECT
|
||||||
Episodes.SeriesId,
|
Episodes.SeriesId,
|
||||||
|
Episodes.SeasonNumber,
|
||||||
|
SUM(CASE WHEN AirdateUtc <= @currentDate OR EpisodeFileId > 0 THEN 1 ELSE 0 END) AS TotalEpisodeCount,
|
||||||
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,
|
||||||
MIN(CASE WHEN AirDateUtc < @currentDate OR EpisodeFileId > 0 OR Monitored = 0 THEN NULL ELSE AirDateUtc END) AS NextAiringString,
|
MIN(CASE WHEN AirDateUtc < @currentDate OR EpisodeFileId > 0 OR Monitored = 0 THEN NULL ELSE AirDateUtc END) AS NextAiringString,
|
||||||
MAX(CASE WHEN AirDateUtc >= @currentDate OR EpisodeFileId = 0 AND Monitored = 0 THEN NULL ELSE AirDateUtc END) AS PreviousAiringString
|
MAX(CASE WHEN AirDateUtc >= @currentDate OR EpisodeFileId = 0 AND Monitored = 0 THEN NULL ELSE AirDateUtc END) AS PreviousAiringString
|
||||||
FROM Episodes
|
FROM Episodes
|
||||||
GROUP BY Episodes.SeriesId) as Episodes";
|
GROUP BY Episodes.SeriesId, Episodes.SeasonNumber) as Episodes";
|
||||||
}
|
}
|
||||||
|
|
||||||
private String GetGroupByClause()
|
private String GetGroupByClause()
|
||||||
{
|
{
|
||||||
return "GROUP BY Episodes.SeriesId";
|
return "GROUP BY Episodes.SeriesId, Episodes.SeasonNumber";
|
||||||
}
|
}
|
||||||
|
|
||||||
private String GetEpisodeFilesJoin()
|
private String GetEpisodeFilesJoin()
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace NzbDrone.Core.SeriesStats
|
namespace NzbDrone.Core.SeriesStats
|
||||||
{
|
{
|
||||||
@ -19,16 +21,48 @@ public SeriesStatisticsService(ISeriesStatisticsRepository seriesStatisticsRepos
|
|||||||
|
|
||||||
public List<SeriesStatistics> SeriesStatistics()
|
public List<SeriesStatistics> SeriesStatistics()
|
||||||
{
|
{
|
||||||
return _seriesStatisticsRepository.SeriesStatistics();
|
var seasonStatistics = _seriesStatisticsRepository.SeriesStatistics();
|
||||||
|
|
||||||
|
return seasonStatistics.GroupBy(s => s.SeriesId).Select(s => MapSeriesStatistics(s.ToList())).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SeriesStatistics SeriesStatistics(int seriesId)
|
public SeriesStatistics SeriesStatistics(int seriesId)
|
||||||
{
|
{
|
||||||
var stats = _seriesStatisticsRepository.SeriesStatistics(seriesId);
|
var stats = _seriesStatisticsRepository.SeriesStatistics(seriesId);
|
||||||
|
|
||||||
if (stats == null) return new SeriesStatistics();
|
if (stats == null || stats.Count == 0) return new SeriesStatistics();
|
||||||
|
|
||||||
return stats;
|
return MapSeriesStatistics(stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SeriesStatistics MapSeriesStatistics(List<SeasonStatistics> seasonStatistics)
|
||||||
|
{
|
||||||
|
return new SeriesStatistics
|
||||||
|
{
|
||||||
|
SeasonStatistics = seasonStatistics,
|
||||||
|
SeriesId = seasonStatistics.First().SeriesId,
|
||||||
|
EpisodeFileCount = seasonStatistics.Sum(s => s.EpisodeFileCount),
|
||||||
|
EpisodeCount = seasonStatistics.Sum(s => s.EpisodeCount),
|
||||||
|
TotalEpisodeCount = seasonStatistics.Sum(s => s.TotalEpisodeCount),
|
||||||
|
SizeOnDisk = seasonStatistics.Sum(s => s.SizeOnDisk),
|
||||||
|
NextAiringString = seasonStatistics.OrderBy(s =>
|
||||||
|
{
|
||||||
|
DateTime nextAiring;
|
||||||
|
|
||||||
|
if (!DateTime.TryParse(s.NextAiringString, out nextAiring)) return DateTime.MinValue;
|
||||||
|
|
||||||
|
return nextAiring;
|
||||||
|
}).First().NextAiringString,
|
||||||
|
|
||||||
|
PreviousAiringString = seasonStatistics.OrderBy(s =>
|
||||||
|
{
|
||||||
|
DateTime nextAiring;
|
||||||
|
|
||||||
|
if (!DateTime.TryParse(s.PreviousAiringString, out nextAiring)) return DateTime.MinValue;
|
||||||
|
|
||||||
|
return nextAiring;
|
||||||
|
}).Last().PreviousAiringString
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
using NzbDrone.Core.Datastore;
|
namespace NzbDrone.Core.Tv
|
||||||
|
|
||||||
namespace NzbDrone.Core.Tv
|
|
||||||
{
|
{
|
||||||
public class AddSeriesOptions : IEmbeddedDocument
|
public class AddSeriesOptions : MonitoringOptions
|
||||||
{
|
{
|
||||||
public bool SearchForMissingEpisodes { get; set; }
|
public bool SearchForMissingEpisodes { get; set; }
|
||||||
public bool IgnoreEpisodesWithFiles { get; set; }
|
|
||||||
public bool IgnoreEpisodesWithoutFiles { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
100
src/NzbDrone.Core/Tv/EpisodeMonitoredService.cs
Normal file
100
src/NzbDrone.Core/Tv/EpisodeMonitoredService.cs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Tv
|
||||||
|
{
|
||||||
|
public interface IEpisodeMonitoredService
|
||||||
|
{
|
||||||
|
void SetEpisodeMonitoredStatus(Series series, MonitoringOptions monitoringOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EpisodeMonitoredService : IEpisodeMonitoredService
|
||||||
|
{
|
||||||
|
private readonly ISeriesService _seriesService;
|
||||||
|
private readonly IEpisodeService _episodeService;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public EpisodeMonitoredService(ISeriesService seriesService, IEpisodeService episodeService, Logger logger)
|
||||||
|
{
|
||||||
|
_seriesService = seriesService;
|
||||||
|
_episodeService = episodeService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetEpisodeMonitoredStatus(Series series, MonitoringOptions monitoringOptions)
|
||||||
|
{
|
||||||
|
_logger.Debug("[{0}] Setting episode monitored status.", series.Title);
|
||||||
|
|
||||||
|
var episodes = _episodeService.GetEpisodeBySeries(series.Id);
|
||||||
|
|
||||||
|
if (monitoringOptions.IgnoreEpisodesWithFiles)
|
||||||
|
{
|
||||||
|
_logger.Debug("Ignoring Episodes with Files");
|
||||||
|
ToggleEpisodesMonitoredState(episodes.Where(e => e.HasFile), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Debug("Monitoring Episodes with Files");
|
||||||
|
ToggleEpisodesMonitoredState(episodes.Where(e => e.HasFile), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (monitoringOptions.IgnoreEpisodesWithoutFiles)
|
||||||
|
{
|
||||||
|
_logger.Debug("Ignoring Episodes without Files");
|
||||||
|
ToggleEpisodesMonitoredState(episodes.Where(e => !e.HasFile && e.AirDateUtc.HasValue && e.AirDateUtc.Value.Before(DateTime.UtcNow)), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Debug("Monitoring Episodes without Files");
|
||||||
|
ToggleEpisodesMonitoredState(episodes.Where(e => !e.HasFile && e.AirDateUtc.HasValue && e.AirDateUtc.Value.Before(DateTime.UtcNow)), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastSeason = series.Seasons.Select(s => s.SeasonNumber).MaxOrDefault();
|
||||||
|
|
||||||
|
foreach (var s in series.Seasons)
|
||||||
|
{
|
||||||
|
var season = s;
|
||||||
|
|
||||||
|
if (season.Monitored)
|
||||||
|
{
|
||||||
|
if (!monitoringOptions.IgnoreEpisodesWithFiles && !monitoringOptions.IgnoreEpisodesWithoutFiles)
|
||||||
|
{
|
||||||
|
ToggleEpisodesMonitoredState(episodes.Where(e => e.SeasonNumber == season.SeasonNumber), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!monitoringOptions.IgnoreEpisodesWithFiles && !monitoringOptions.IgnoreEpisodesWithoutFiles)
|
||||||
|
{
|
||||||
|
ToggleEpisodesMonitoredState(episodes.Where(e => e.SeasonNumber == season.SeasonNumber), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (season.SeasonNumber < lastSeason)
|
||||||
|
{
|
||||||
|
if (episodes.Where(e => e.SeasonNumber == season.SeasonNumber).All(e => !e.Monitored))
|
||||||
|
{
|
||||||
|
season.Monitored = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_seriesService.UpdateSeries(series);
|
||||||
|
_episodeService.UpdateEpisodes(episodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleEpisodesMonitoredState(IEnumerable<Episode> episodes, bool monitored)
|
||||||
|
{
|
||||||
|
foreach (var episode in episodes)
|
||||||
|
{
|
||||||
|
episode.Monitored = monitored;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
src/NzbDrone.Core/Tv/MonitoringOptions.cs
Normal file
10
src/NzbDrone.Core/Tv/MonitoringOptions.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Tv
|
||||||
|
{
|
||||||
|
public class MonitoringOptions : IEmbeddedDocument
|
||||||
|
{
|
||||||
|
public bool IgnoreEpisodesWithFiles { get; set; }
|
||||||
|
public bool IgnoreEpisodesWithoutFiles { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,4 @@
|
|||||||
using System;
|
using NLog;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using NLog;
|
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Core.IndexerSearch;
|
using NzbDrone.Core.IndexerSearch;
|
||||||
using NzbDrone.Core.MediaFiles.Events;
|
using NzbDrone.Core.MediaFiles.Events;
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
@ -13,61 +9,23 @@ namespace NzbDrone.Core.Tv
|
|||||||
public class SeriesScannedHandler : IHandle<SeriesScannedEvent>,
|
public class SeriesScannedHandler : IHandle<SeriesScannedEvent>,
|
||||||
IHandle<SeriesScanSkippedEvent>
|
IHandle<SeriesScanSkippedEvent>
|
||||||
{
|
{
|
||||||
|
private readonly IEpisodeMonitoredService _episodeMonitoredService;
|
||||||
private readonly ISeriesService _seriesService;
|
private readonly ISeriesService _seriesService;
|
||||||
private readonly IEpisodeService _episodeService;
|
|
||||||
private readonly IManageCommandQueue _commandQueueManager;
|
private readonly IManageCommandQueue _commandQueueManager;
|
||||||
|
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public SeriesScannedHandler(ISeriesService seriesService,
|
public SeriesScannedHandler(IEpisodeMonitoredService episodeMonitoredService,
|
||||||
IEpisodeService episodeService,
|
ISeriesService seriesService,
|
||||||
IManageCommandQueue commandQueueManager,
|
IManageCommandQueue commandQueueManager,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
{
|
{
|
||||||
|
_episodeMonitoredService = episodeMonitoredService;
|
||||||
_seriesService = seriesService;
|
_seriesService = seriesService;
|
||||||
_episodeService = episodeService;
|
|
||||||
_commandQueueManager = commandQueueManager;
|
_commandQueueManager = commandQueueManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetEpisodeMonitoredStatus(Series series, List<Episode> episodes)
|
|
||||||
{
|
|
||||||
_logger.Debug("[{0}] Setting episode monitored status.", series.Title);
|
|
||||||
|
|
||||||
if (series.AddOptions.IgnoreEpisodesWithFiles)
|
|
||||||
{
|
|
||||||
_logger.Debug("Ignoring Episodes with Files");
|
|
||||||
UnmonitorEpisodes(episodes.Where(e => e.HasFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (series.AddOptions.IgnoreEpisodesWithoutFiles)
|
|
||||||
{
|
|
||||||
_logger.Debug("Ignoring Episodes without Files");
|
|
||||||
UnmonitorEpisodes(episodes.Where(e => !e.HasFile && e.AirDateUtc.HasValue && e.AirDateUtc.Value.Before(DateTime.UtcNow)));
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastSeason = series.Seasons.Select(s => s.SeasonNumber).MaxOrDefault();
|
|
||||||
|
|
||||||
foreach (var season in series.Seasons.Where(s => s.SeasonNumber < lastSeason))
|
|
||||||
{
|
|
||||||
if (episodes.Where(e => e.SeasonNumber == season.SeasonNumber).All(e => !e.Monitored))
|
|
||||||
{
|
|
||||||
season.Monitored = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_seriesService.UpdateSeries(series);
|
|
||||||
_episodeService.UpdateEpisodes(episodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UnmonitorEpisodes(IEnumerable<Episode> episodes)
|
|
||||||
{
|
|
||||||
foreach (var episode in episodes)
|
|
||||||
{
|
|
||||||
episode.Monitored = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleScanEvents(Series series)
|
private void HandleScanEvents(Series series)
|
||||||
{
|
{
|
||||||
if (series.AddOptions == null)
|
if (series.AddOptions == null)
|
||||||
@ -76,9 +34,7 @@ private void HandleScanEvents(Series series)
|
|||||||
}
|
}
|
||||||
|
|
||||||
_logger.Info("[{0}] was recently added, performing post-add actions", series.Title);
|
_logger.Info("[{0}] was recently added, performing post-add actions", series.Title);
|
||||||
|
_episodeMonitoredService.SetEpisodeMonitoredStatus(series, series.AddOptions);
|
||||||
var episodes = _episodeService.GetEpisodeBySeries(series.Id);
|
|
||||||
SetEpisodeMonitoredStatus(series, episodes);
|
|
||||||
|
|
||||||
if (series.AddOptions.SearchForMissingEpisodes)
|
if (series.AddOptions.SearchForMissingEpisodes)
|
||||||
{
|
{
|
||||||
|
@ -72,8 +72,7 @@
|
|||||||
|
|
||||||
.icon-sonarr-spinner {
|
.icon-sonarr-spinner {
|
||||||
.fa-icon-content(@fa-var-spinner);
|
.fa-icon-content(@fa-var-spinner);
|
||||||
//TODO: Fix icon spin
|
margin: 0px -0.14em;
|
||||||
//.fa-spin
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-sonarr-rename {
|
.icon-sonarr-rename {
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
@import "../Shared/FileBrowser/filebrowser";
|
@import "../Shared/FileBrowser/filebrowser";
|
||||||
@import "badges";
|
@import "badges";
|
||||||
@import "../ManualImport/manualimport";
|
@import "../ManualImport/manualimport";
|
||||||
|
@import "../SeasonPass/seasonpass";
|
||||||
|
|
||||||
.main-region {
|
.main-region {
|
||||||
@media (min-width : @screen-lg-min) {
|
@media (min-width : @screen-lg-min) {
|
||||||
|
128
src/UI/SeasonPass/SeasonPassFooterView.js
Normal file
128
src/UI/SeasonPass/SeasonPassFooterView.js
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
var _ = require('underscore');
|
||||||
|
var $ = require('jquery');
|
||||||
|
var Marionette = require('marionette');
|
||||||
|
var vent = require('vent');
|
||||||
|
var RootFolders = require('../AddSeries/RootFolders/RootFolderCollection');
|
||||||
|
|
||||||
|
module.exports = Marionette.ItemView.extend({
|
||||||
|
template : 'SeasonPass/SeasonPassFooterViewTemplate',
|
||||||
|
|
||||||
|
ui : {
|
||||||
|
monitor : '.x-monitor',
|
||||||
|
selectedCount : '.x-selected-count',
|
||||||
|
container : '.series-editor-footer',
|
||||||
|
actions : '.x-action',
|
||||||
|
indicator : '.x-indicator',
|
||||||
|
indicatorIcon : '.x-indicator-icon'
|
||||||
|
},
|
||||||
|
|
||||||
|
events : {
|
||||||
|
'click .x-update' : '_update'
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize : function(options) {
|
||||||
|
this.seriesCollection = options.collection;
|
||||||
|
|
||||||
|
RootFolders.fetch().done(function() {
|
||||||
|
RootFolders.synced = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.editorGrid = options.editorGrid;
|
||||||
|
this.listenTo(this.seriesCollection, 'backgrid:selected', this._updateInfo);
|
||||||
|
},
|
||||||
|
|
||||||
|
onRender : function() {
|
||||||
|
this._updateInfo();
|
||||||
|
},
|
||||||
|
|
||||||
|
_update : function() {
|
||||||
|
var self = this;
|
||||||
|
var selected = this.editorGrid.getSelectedModels();
|
||||||
|
var monitoringOptions;
|
||||||
|
|
||||||
|
_.each(selected, function(model) {
|
||||||
|
monitoringOptions = self._getMonitoringOptions(model);
|
||||||
|
|
||||||
|
model.set('addOptions', monitoringOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
var promise = $.ajax({
|
||||||
|
url : window.NzbDrone.ApiRoot + '/seasonpass',
|
||||||
|
type : 'POST',
|
||||||
|
data : JSON.stringify({
|
||||||
|
series : _.map(selected, function (model) {
|
||||||
|
return model.toJSON();
|
||||||
|
}),
|
||||||
|
monitoringOptions : monitoringOptions
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ui.indicator.show();
|
||||||
|
|
||||||
|
promise.always(function () {
|
||||||
|
self.ui.indicator.hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.done(function () {
|
||||||
|
self.seriesCollection.trigger('seasonpass:saved');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateInfo : function() {
|
||||||
|
var selected = this.editorGrid.getSelectedModels();
|
||||||
|
var selectedCount = selected.length;
|
||||||
|
|
||||||
|
this.ui.selectedCount.html('{0} series selected'.format(selectedCount));
|
||||||
|
|
||||||
|
if (selectedCount === 0) {
|
||||||
|
this.ui.actions.attr('disabled', 'disabled');
|
||||||
|
} else {
|
||||||
|
this.ui.actions.removeAttr('disabled');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_getMonitoringOptions : function(model) {
|
||||||
|
var monitor = this.ui.monitor.val();
|
||||||
|
var lastSeason = _.max(model.get('seasons'), 'seasonNumber');
|
||||||
|
var firstSeason = _.min(_.reject(model.get('seasons'), { seasonNumber : 0 }), 'seasonNumber');
|
||||||
|
|
||||||
|
model.setSeasonPass(firstSeason.seasonNumber);
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
ignoreEpisodesWithFiles : false,
|
||||||
|
ignoreEpisodesWithoutFiles : false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (monitor === 'all') {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (monitor === 'future') {
|
||||||
|
options.ignoreEpisodesWithFiles = true;
|
||||||
|
options.ignoreEpisodesWithoutFiles = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (monitor === 'latest') {
|
||||||
|
model.setSeasonPass(lastSeason.seasonNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (monitor === 'first') {
|
||||||
|
model.setSeasonPass(lastSeason.seasonNumber + 1);
|
||||||
|
model.setSeasonMonitored(firstSeason.seasonNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (monitor === 'missing') {
|
||||||
|
options.ignoreEpisodesWithFiles = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (monitor === 'existing') {
|
||||||
|
options.ignoreEpisodesWithoutFiles = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (monitor === 'none') {
|
||||||
|
model.setSeasonPass(lastSeason.seasonNumber + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
});
|
25
src/UI/SeasonPass/SeasonPassFooterViewTemplate.hbs
Normal file
25
src/UI/SeasonPass/SeasonPassFooterViewTemplate.hbs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<div class="series-editor-footer">
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-group col-md-2">
|
||||||
|
<label>Monitor</label>
|
||||||
|
|
||||||
|
<select class="form-control x-action x-monitor">
|
||||||
|
<option value="all">All</option>
|
||||||
|
<option value="future">Future</option>
|
||||||
|
<option value="missing">Missing</option>
|
||||||
|
<option value="existing">Existing</option>
|
||||||
|
<option value="first">First Season</option>
|
||||||
|
<option value="latest">Latest Season</option>
|
||||||
|
<option value="none">None</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group col-md-3 actions">
|
||||||
|
<label class="x-selected-count">0 series selected</label>
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-primary x-action x-update">Update Selected Series</button>
|
||||||
|
<span class="indicator x-indicator"><i class="icon-sonarr-spinner fa-spin"></i></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -1,9 +1,15 @@
|
|||||||
|
var _ = require('underscore');
|
||||||
|
var vent = require('vent');
|
||||||
|
var Backgrid = require('backgrid');
|
||||||
var Marionette = require('marionette');
|
var Marionette = require('marionette');
|
||||||
|
var EmptyView = require('../Series/Index/EmptyView');
|
||||||
var SeriesCollection = require('../Series/SeriesCollection');
|
var SeriesCollection = require('../Series/SeriesCollection');
|
||||||
var SeasonCollection = require('../Series/SeasonCollection');
|
|
||||||
var SeriesCollectionView = require('./SeriesCollectionView');
|
|
||||||
var LoadingView = require('../Shared/LoadingView');
|
|
||||||
var ToolbarLayout = require('../Shared/Toolbar/ToolbarLayout');
|
var ToolbarLayout = require('../Shared/Toolbar/ToolbarLayout');
|
||||||
|
var FooterView = require('./SeasonPassFooterView');
|
||||||
|
var SelectAllCell = require('../Cells/SelectAllCell');
|
||||||
|
var SeriesStatusCell = require('../Cells/SeriesStatusCell');
|
||||||
|
var SeriesTitleCell = require('../Cells/SeriesTitleCell');
|
||||||
|
var SeasonsCell = require('./SeasonsCell');
|
||||||
require('../Mixins/backbone.signalr.mixin');
|
require('../Mixins/backbone.signalr.mixin');
|
||||||
|
|
||||||
module.exports = Marionette.Layout.extend({
|
module.exports = Marionette.Layout.extend({
|
||||||
@ -14,11 +20,38 @@ module.exports = Marionette.Layout.extend({
|
|||||||
series : '#x-series'
|
series : '#x-series'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
columns : [
|
||||||
|
{
|
||||||
|
name : '',
|
||||||
|
cell : SelectAllCell,
|
||||||
|
headerCell : 'select-all',
|
||||||
|
sortable : false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'statusWeight',
|
||||||
|
label : '',
|
||||||
|
cell : SeriesStatusCell
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'title',
|
||||||
|
label : 'Title',
|
||||||
|
cell : SeriesTitleCell,
|
||||||
|
cellValue : 'this'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'seasons',
|
||||||
|
label : 'Seasons',
|
||||||
|
cell : SeasonsCell,
|
||||||
|
cellValue : 'this'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
initialize : function() {
|
initialize : function() {
|
||||||
this.seriesCollection = SeriesCollection.clone();
|
this.seriesCollection = SeriesCollection.clone();
|
||||||
this.seriesCollection.shadowCollection.bindSignalR();
|
this.seriesCollection.shadowCollection.bindSignalR();
|
||||||
|
|
||||||
this.listenTo(this.seriesCollection, 'sync', this.render);
|
// this.listenTo(this.seriesCollection, 'sync', this.render);
|
||||||
|
this.listenTo(this.seriesCollection, 'seasonpass:saved', this.render);
|
||||||
|
|
||||||
this.filteringOptions = {
|
this.filteringOptions = {
|
||||||
type : 'radio',
|
type : 'radio',
|
||||||
@ -59,11 +92,13 @@ module.exports = Marionette.Layout.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onRender : function() {
|
onRender : function() {
|
||||||
this.series.show(new SeriesCollectionView({
|
this._showTable();
|
||||||
collection : this.seriesCollection
|
|
||||||
}));
|
|
||||||
|
|
||||||
this._showToolbar();
|
this._showToolbar();
|
||||||
|
this._showFooter();
|
||||||
|
},
|
||||||
|
|
||||||
|
onClose : function() {
|
||||||
|
vent.trigger(vent.Commands.CloseControlPanelCommand);
|
||||||
},
|
},
|
||||||
|
|
||||||
_showToolbar : function() {
|
_showToolbar : function() {
|
||||||
@ -73,6 +108,32 @@ module.exports = Marionette.Layout.extend({
|
|||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_showTable : function() {
|
||||||
|
if (this.seriesCollection.shadowCollection.length === 0) {
|
||||||
|
this.series.show(new EmptyView());
|
||||||
|
this.toolbar.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.columns[0].sortedCollection = this.seriesCollection;
|
||||||
|
|
||||||
|
this.editorGrid = new Backgrid.Grid({
|
||||||
|
collection : this.seriesCollection,
|
||||||
|
columns : this.columns,
|
||||||
|
className : 'table table-hover'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.series.show(this.editorGrid);
|
||||||
|
this._showFooter();
|
||||||
|
},
|
||||||
|
|
||||||
|
_showFooter : function() {
|
||||||
|
vent.trigger(vent.Commands.OpenControlPanelCommand, new FooterView({
|
||||||
|
editorGrid : this.editorGrid,
|
||||||
|
collection : this.seriesCollection
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
_setFilter : function(buttonContext) {
|
_setFilter : function(buttonContext) {
|
||||||
var mode = buttonContext.model.get('key');
|
var mode = buttonContext.model.get('key');
|
||||||
|
|
||||||
|
26
src/UI/SeasonPass/SeasonsCell.js
Normal file
26
src/UI/SeasonPass/SeasonsCell.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
var _ = require('underscore');
|
||||||
|
var TemplatedCell = require('../Cells/TemplatedCell');
|
||||||
|
//require('../Handlebars/Helpers/Numbers');
|
||||||
|
|
||||||
|
module.exports = TemplatedCell.extend({
|
||||||
|
className : 'seasons-cell',
|
||||||
|
template : 'SeasonPass/SeasonsCellTemplate',
|
||||||
|
|
||||||
|
events : {
|
||||||
|
'click .x-season-monitored' : '_toggleSeasonMonitored'
|
||||||
|
},
|
||||||
|
|
||||||
|
_toggleSeasonMonitored : function(e) {
|
||||||
|
var target = this.$(e.target).closest('.x-season-monitored');
|
||||||
|
var seasonNumber = parseInt(this.$(target).data('season-number'), 10);
|
||||||
|
var icon = this.$(target).children('.x-season-monitored-icon');
|
||||||
|
|
||||||
|
this.model.setSeasonMonitored(seasonNumber);
|
||||||
|
|
||||||
|
//TODO: unbounce the save so we don't multiple to the server at the same time
|
||||||
|
var savePromise = this.model.save();
|
||||||
|
|
||||||
|
icon.spinForPromise(savePromise);
|
||||||
|
savePromise.always(this.render.bind(this));
|
||||||
|
}
|
||||||
|
});
|
29
src/UI/SeasonPass/SeasonsCellTemplate.hbs
Normal file
29
src/UI/SeasonPass/SeasonsCellTemplate.hbs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{{#each seasons}}
|
||||||
|
{{debug}}
|
||||||
|
<span class="season">
|
||||||
|
<span class="label">
|
||||||
|
<span class="x-season-monitored season-monitored" title="Toggle season monitored status" data-season-number="{{seasonNumber}}">
|
||||||
|
<i class="x-season-monitored-icon {{#if monitored}}icon-sonarr-monitored{{else}}icon-sonarr-unmonitored{{/if}}"/>
|
||||||
|
</span>
|
||||||
|
{{#if_eq seasonNumber compare="0"}}
|
||||||
|
<span class="season-number">Specials</span>
|
||||||
|
{{else}}
|
||||||
|
<span class="season-number">S{{Pad2 seasonNumber}}</span>
|
||||||
|
{{/if_eq}}
|
||||||
|
</span><span class="label">
|
||||||
|
{{#with statistics}}
|
||||||
|
{{#if_eq totalEpisodeCount compare=0}}
|
||||||
|
<span class="season-status" title="No aired episodes"> </span>
|
||||||
|
{{else}}
|
||||||
|
{{#if_eq percentOfEpisodes compare=100}}
|
||||||
|
<span class="season-status" title="{{episodeFileCount}}/{{totalEpisodeCount}} episodes downloaded">{{episodeFileCount}}/{{totalEpisodeCount}}</span>
|
||||||
|
{{else}}
|
||||||
|
<span class="season-status" title="{{episodeFileCount}}/{{totalEpisodeCount}} episodes downloaded">{{episodeFileCount}}/{{totalEpisodeCount}}</span>
|
||||||
|
{{/if_eq}}
|
||||||
|
{{/if_eq}}
|
||||||
|
{{else}}
|
||||||
|
<span class="season-status" title="No aired episodes"> </span>
|
||||||
|
{{/with}}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
{{/each}}
|
@ -1,6 +0,0 @@
|
|||||||
var Marionette = require('marionette');
|
|
||||||
var SeriesLayout = require('./SeriesLayout');
|
|
||||||
|
|
||||||
module.exports = Marionette.CollectionView.extend({
|
|
||||||
itemView : SeriesLayout
|
|
||||||
});
|
|
@ -1,152 +0,0 @@
|
|||||||
var _ = require('underscore');
|
|
||||||
var Marionette = require('marionette');
|
|
||||||
var Backgrid = require('backgrid');
|
|
||||||
var SeasonCollection = require('../Series/SeasonCollection');
|
|
||||||
|
|
||||||
module.exports = Marionette.Layout.extend({
|
|
||||||
template : 'SeasonPass/SeriesLayoutTemplate',
|
|
||||||
|
|
||||||
ui : {
|
|
||||||
seasonSelect : '.x-season-select',
|
|
||||||
expander : '.x-expander',
|
|
||||||
seasonGrid : '.x-season-grid',
|
|
||||||
seriesMonitored : '.x-series-monitored'
|
|
||||||
},
|
|
||||||
|
|
||||||
events : {
|
|
||||||
'change .x-season-select' : '_seasonSelected',
|
|
||||||
'click .x-expander' : '_expand',
|
|
||||||
'click .x-latest' : '_latest',
|
|
||||||
'click .x-all' : '_all',
|
|
||||||
'click .x-monitored' : '_toggleSeasonMonitored',
|
|
||||||
'click .x-series-monitored' : '_toggleSeriesMonitored'
|
|
||||||
},
|
|
||||||
|
|
||||||
regions : {
|
|
||||||
seasonGrid : '.x-season-grid'
|
|
||||||
},
|
|
||||||
|
|
||||||
initialize : function() {
|
|
||||||
this.listenTo(this.model, 'sync', this._setSeriesMonitoredState);
|
|
||||||
this.seasonCollection = new SeasonCollection(this.model.get('seasons'));
|
|
||||||
this.expanded = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
onRender : function() {
|
|
||||||
if (!this.expanded) {
|
|
||||||
this.ui.seasonGrid.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
this._setExpanderIcon();
|
|
||||||
this._setSeriesMonitoredState();
|
|
||||||
},
|
|
||||||
|
|
||||||
_seasonSelected : function() {
|
|
||||||
var seasonNumber = parseInt(this.ui.seasonSelect.val(), 10);
|
|
||||||
|
|
||||||
if (seasonNumber === -1 || isNaN(seasonNumber)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._setSeasonMonitored(seasonNumber);
|
|
||||||
},
|
|
||||||
|
|
||||||
_expand : function() {
|
|
||||||
if (this.expanded) {
|
|
||||||
this.ui.seasonGrid.slideUp();
|
|
||||||
this.expanded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
this.ui.seasonGrid.slideDown();
|
|
||||||
this.expanded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._setExpanderIcon();
|
|
||||||
},
|
|
||||||
|
|
||||||
_setExpanderIcon : function() {
|
|
||||||
if (this.expanded) {
|
|
||||||
this.ui.expander.removeClass('icon-sonarr-expand');
|
|
||||||
this.ui.expander.addClass('icon-sonarr-expanded');
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
this.ui.expander.removeClass('icon-sonarr-expanded');
|
|
||||||
this.ui.expander.addClass('icon-sonarr-expand');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_latest : function() {
|
|
||||||
var season = _.max(this.model.get('seasons'), function(s) {
|
|
||||||
return s.seasonNumber;
|
|
||||||
});
|
|
||||||
|
|
||||||
this._setSeasonMonitored(season.seasonNumber);
|
|
||||||
},
|
|
||||||
|
|
||||||
_all : function() {
|
|
||||||
var minSeasonNotZero = _.min(_.reject(this.model.get('seasons'), { seasonNumber : 0 }), 'seasonNumber');
|
|
||||||
|
|
||||||
this._setSeasonMonitored(minSeasonNotZero.seasonNumber);
|
|
||||||
},
|
|
||||||
|
|
||||||
_setSeasonMonitored : function(seasonNumber) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
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(this.$(e.target).parent('td').attr('data-season-number'), 10);
|
|
||||||
element = this.$(e.target);
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
seasonNumber = parseInt(this.$(e.target).attr('data-season-number'), 10);
|
|
||||||
element = this.$(e.target).children('i');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.model.setSeasonMonitored(seasonNumber);
|
|
||||||
|
|
||||||
var savePromise = this.model.save().always(this.render.bind(this));
|
|
||||||
element.spinForPromise(savePromise);
|
|
||||||
},
|
|
||||||
|
|
||||||
_afterToggleSeasonMonitored : function() {
|
|
||||||
this.render();
|
|
||||||
},
|
|
||||||
|
|
||||||
_setSeriesMonitoredState : function() {
|
|
||||||
var monitored = this.model.get('monitored');
|
|
||||||
|
|
||||||
this.ui.seriesMonitored.removeAttr('data-idle-icon');
|
|
||||||
|
|
||||||
if (monitored) {
|
|
||||||
this.ui.seriesMonitored.addClass('icon-sonarr-monitored');
|
|
||||||
this.ui.seriesMonitored.removeClass('icon-sonarr-unmonitored');
|
|
||||||
} else {
|
|
||||||
this.ui.seriesMonitored.addClass('icon-sonarr-unmonitored');
|
|
||||||
this.ui.seriesMonitored.removeClass('icon-sonarr-monitored');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_toggleSeriesMonitored : function() {
|
|
||||||
var savePromise = this.model.save('monitored', !this.model.get('monitored'), {
|
|
||||||
wait : true
|
|
||||||
});
|
|
||||||
|
|
||||||
this.ui.seriesMonitored.spinForPromise(savePromise);
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,75 +0,0 @@
|
|||||||
<div class="seasonpass-series">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<i class="icon-sonarr-expand x-expander expander pull-left"/>
|
|
||||||
<i class="x-series-monitored series-monitor-toggle pull-left" title="Toggle monitored state for entire series"/>
|
|
||||||
<div class="title col-md-5">
|
|
||||||
<a href="{{route}}">
|
|
||||||
{{title}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-2">
|
|
||||||
<select class="form-control x-season-select season-select">
|
|
||||||
<option value="-1">Select season...</option>
|
|
||||||
{{#each seasons}}
|
|
||||||
{{#if_eq seasonNumber compare="0"}}
|
|
||||||
<option value="{{seasonNumber}}">Specials</option>
|
|
||||||
{{else}}
|
|
||||||
<option value="{{seasonNumber}}">Season {{seasonNumber}}</option>
|
|
||||||
{{/if_eq}}
|
|
||||||
{{/each}}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-1">
|
|
||||||
<span class="help-inline">
|
|
||||||
<i class="icon-sonarr-form-info" title="Selecting a season will unmonitor all previous seasons"/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span class="season-pass-button">
|
|
||||||
<button class="btn x-latest last">Latest Season Only</button>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="season-pass-button">
|
|
||||||
<button class="btn x-all">All Seasons</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-11">
|
|
||||||
<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-sonarr-monitored"></i>
|
|
||||||
{{else}}
|
|
||||||
<i class="icon-sonarr-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>
|
|
49
src/UI/SeasonPass/seasonpass.less
Normal file
49
src/UI/SeasonPass/seasonpass.less
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
@import "../Content/badges.less";
|
||||||
|
@import "../Shared/Styles/clickable.less";
|
||||||
|
|
||||||
|
.season {
|
||||||
|
display : inline-block;
|
||||||
|
margin-bottom : 4px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
.badge-inverse();
|
||||||
|
|
||||||
|
display : inline-block;
|
||||||
|
padding : 4px;
|
||||||
|
|
||||||
|
font-size : 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label:first-child {
|
||||||
|
border-right : 0px;
|
||||||
|
border-top-right-radius : 0.0em;
|
||||||
|
border-bottom-right-radius : 0.0em;
|
||||||
|
color : #777;
|
||||||
|
background-color : #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label:last-child {
|
||||||
|
border-left : 0px;
|
||||||
|
border-top-left-radius : 0.0em;
|
||||||
|
border-bottom-left-radius : 0.0em;
|
||||||
|
color : #999;
|
||||||
|
background-color : #f7f7f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-monitored {
|
||||||
|
width : 16px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
.clickable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-number {
|
||||||
|
font-size : 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-status {
|
||||||
|
display : inline-block;
|
||||||
|
vertical-align : baseline !important;
|
||||||
|
}
|
||||||
|
}
|
@ -127,6 +127,7 @@ module.exports = Marionette.Layout.extend({
|
|||||||
var monitored = this.model.get('monitored');
|
var monitored = this.model.get('monitored');
|
||||||
|
|
||||||
this.ui.monitored.removeAttr('data-idle-icon');
|
this.ui.monitored.removeAttr('data-idle-icon');
|
||||||
|
this.ui.monitored.removeClass('fa-spin icon-sonarr-spinner');
|
||||||
|
|
||||||
if (monitored) {
|
if (monitored) {
|
||||||
this.ui.monitored.addClass('icon-sonarr-monitored');
|
this.ui.monitored.addClass('icon-sonarr-monitored');
|
||||||
|
Loading…
Reference in New Issue
Block a user