From fd5e8a5166ba00b6270f9930fc250d546b719f15 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Sat, 21 Jun 2014 02:36:28 +0200 Subject: [PATCH 1/3] Added Series SortTitle Migration. (Left a slot for anime) --- src/NzbDrone.Api/Series/SeriesResource.cs | 1 + .../MetadataSourceTests/TraktProxyFixture.cs | 2 + .../Migration/053_add_series_sorttitle.cs | 53 +++++++++++++++++++ .../MetadataSource/TraktProxy.cs | 1 + src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + src/NzbDrone.Core/Tv/RefreshSeriesService.cs | 1 + src/NzbDrone.Core/Tv/Series.cs | 1 + src/NzbDrone.Core/Tv/SeriesService.cs | 1 + 8 files changed, 61 insertions(+) create mode 100644 src/NzbDrone.Core/Datastore/Migration/053_add_series_sorttitle.cs diff --git a/src/NzbDrone.Api/Series/SeriesResource.cs b/src/NzbDrone.Api/Series/SeriesResource.cs index 6fdff13df..95069ad20 100644 --- a/src/NzbDrone.Api/Series/SeriesResource.cs +++ b/src/NzbDrone.Api/Series/SeriesResource.cs @@ -15,6 +15,7 @@ namespace NzbDrone.Api.Series //View Only public String Title { get; set; } + public String SortTitle { get; set; } public List AlternativeTitles { get; set; } public Int32 SeasonCount diff --git a/src/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs b/src/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs index 41734daed..31a26f712 100644 --- a/src/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs +++ b/src/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs @@ -40,6 +40,7 @@ namespace NzbDrone.Core.Test.MetadataSourceTests [TestCase(75978)] [TestCase(83462)] + [TestCase(266189)] public void should_be_able_to_get_series_detail(int tvdbId) { var details = Subject.GetSeriesInfo(tvdbId); @@ -61,6 +62,7 @@ namespace NzbDrone.Core.Test.MetadataSourceTests series.Should().NotBeNull(); series.Title.Should().NotBeBlank(); series.CleanTitle.Should().Be(Parser.Parser.CleanSeriesTitle(series.Title)); + series.SortTitle.Should().Be(Parser.Parser.NormalizeEpisodeTitle(series.Title)); series.Overview.Should().NotBeBlank(); series.AirTime.Should().NotBeBlank(); series.FirstAired.Should().HaveValue(); diff --git a/src/NzbDrone.Core/Datastore/Migration/053_add_series_sorttitle.cs b/src/NzbDrone.Core/Datastore/Migration/053_add_series_sorttitle.cs new file mode 100644 index 000000000..ce004c87f --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/053_add_series_sorttitle.cs @@ -0,0 +1,53 @@ +using System; +using System.IO; +using System.Data; +using System.Linq; +using System.Collections.Generic; +using FluentMigrator; +using Newtonsoft.Json; +using NzbDrone.Common; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(53)] + public class add_series_sorttitle : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Create.Column("SortTitle").OnTable("Series").AsString().Nullable(); + + Execute.WithConnection(SetSortTitles); + } + + private void SetSortTitles(IDbConnection conn, IDbTransaction tran) + { + using (IDbCommand getSeriesCmd = conn.CreateCommand()) + { + getSeriesCmd.Transaction = tran; + getSeriesCmd.CommandText = @"SELECT Id, Title FROM Series"; + using (IDataReader seriesReader = getSeriesCmd.ExecuteReader()) + { + while (seriesReader.Read()) + { + var id = seriesReader.GetInt32(0); + var title = seriesReader.GetString(1); + + var sortTitle = Parser.Parser.NormalizeEpisodeTitle(title).ToLower(); + + using (IDbCommand updateCmd = conn.CreateCommand()) + { + updateCmd.Transaction = tran; + updateCmd.CommandText = "UPDATE Series SET SortTitle = ? WHERE Id = ?"; + updateCmd.AddParameter(sortTitle); + updateCmd.AddParameter(id); + + updateCmd.ExecuteNonQuery(); + } + } + } + } + } + } +} diff --git a/src/NzbDrone.Core/MetadataSource/TraktProxy.cs b/src/NzbDrone.Core/MetadataSource/TraktProxy.cs index 0236a4169..ee7c4b3a8 100644 --- a/src/NzbDrone.Core/MetadataSource/TraktProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/TraktProxy.cs @@ -105,6 +105,7 @@ namespace NzbDrone.Core.MetadataSource series.ImdbId = show.imdb_id; series.Title = show.title; series.CleanTitle = Parser.Parser.CleanSeriesTitle(show.title); + series.SortTitle = Parser.Parser.NormalizeEpisodeTitle(show.title).ToLower(); series.Year = GetYear(show.year, show.first_aired); series.FirstAired = FromIso(show.first_aired_iso); series.Overview = show.overview; diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 97f8093a6..b68285b8d 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -198,6 +198,7 @@ + diff --git a/src/NzbDrone.Core/Tv/RefreshSeriesService.cs b/src/NzbDrone.Core/Tv/RefreshSeriesService.cs index 5390d0502..a00e9e41b 100644 --- a/src/NzbDrone.Core/Tv/RefreshSeriesService.cs +++ b/src/NzbDrone.Core/Tv/RefreshSeriesService.cs @@ -58,6 +58,7 @@ namespace NzbDrone.Core.Tv series.Overview = seriesInfo.Overview; series.Status = seriesInfo.Status; series.CleanTitle = seriesInfo.CleanTitle; + series.SortTitle = seriesInfo.SortTitle; series.LastInfoSync = DateTime.UtcNow; series.Runtime = seriesInfo.Runtime; series.Images = seriesInfo.Images; diff --git a/src/NzbDrone.Core/Tv/Series.cs b/src/NzbDrone.Core/Tv/Series.cs index ace922cb1..2d81a305b 100644 --- a/src/NzbDrone.Core/Tv/Series.cs +++ b/src/NzbDrone.Core/Tv/Series.cs @@ -22,6 +22,7 @@ namespace NzbDrone.Core.Tv public string ImdbId { get; set; } public string Title { get; set; } public string CleanTitle { get; set; } + public string SortTitle { get; set; } public SeriesStatusType Status { get; set; } public string Overview { get; set; } public String AirTime { get; set; } diff --git a/src/NzbDrone.Core/Tv/SeriesService.cs b/src/NzbDrone.Core/Tv/SeriesService.cs index 9de113351..8a361175f 100644 --- a/src/NzbDrone.Core/Tv/SeriesService.cs +++ b/src/NzbDrone.Core/Tv/SeriesService.cs @@ -78,6 +78,7 @@ namespace NzbDrone.Core.Tv newSeries.Monitored = true; newSeries.CleanTitle = Parser.Parser.CleanSeriesTitle(newSeries.Title); + newSeries.SortTitle = Parser.Parser.NormalizeEpisodeTitle(newSeries.Title).ToLower(); _seriesRepository.Insert(newSeries); _eventAggregator.PublishEvent(new SeriesAddedEvent(GetSeries(newSeries.Id))); From 221a457f3a61ca1989b95fdddc970c025712fb37 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Sat, 21 Jun 2014 00:29:12 +0200 Subject: [PATCH 2/3] Fixed: Sorting on Series Title now ignores articles (a/an/the). --- src/NzbDrone.Api/Blacklist/BlacklistModule.cs | 2 +- src/NzbDrone.Api/History/HistoryModule.cs | 2 +- src/NzbDrone.Api/Wanted/CutoffModule.cs | 9 +++- src/NzbDrone.Api/Wanted/MissingModule.cs | 9 +++- src/UI/History/Blacklist/BlacklistLayout.js | 38 ++++++------- src/UI/History/Queue/QueueLayout.js | 39 +++++++------- src/UI/History/Table/HistoryTableLayout.js | 54 +++++++++---------- src/UI/Series/Editor/SeriesEditorLayout.js | 27 +++++----- src/UI/Series/Index/SeriesIndexLayout.js | 43 +++++++-------- src/UI/Wanted/Cutoff/CutoffUnmetLayout.js | 46 ++++++++-------- src/UI/Wanted/Missing/MissingLayout.js | 46 ++++++++-------- 11 files changed, 166 insertions(+), 149 deletions(-) diff --git a/src/NzbDrone.Api/Blacklist/BlacklistModule.cs b/src/NzbDrone.Api/Blacklist/BlacklistModule.cs index 913810767..bfbcbc2de 100644 --- a/src/NzbDrone.Api/Blacklist/BlacklistModule.cs +++ b/src/NzbDrone.Api/Blacklist/BlacklistModule.cs @@ -28,7 +28,7 @@ namespace NzbDrone.Api.Blacklist //This is a hack to deal with backgrid setting the sortKey to the column name instead of sortValue if (pagingSpec.SortKey.Equals("series", StringComparison.InvariantCultureIgnoreCase)) { - pagingSpec.SortKey = "series.title"; + pagingSpec.SortKey = "series.sortTitle"; } return ApplyToPage(_blacklistService.Paged, pagingSpec); diff --git a/src/NzbDrone.Api/History/HistoryModule.cs b/src/NzbDrone.Api/History/HistoryModule.cs index 69ed1dc15..ded75c54c 100644 --- a/src/NzbDrone.Api/History/HistoryModule.cs +++ b/src/NzbDrone.Api/History/HistoryModule.cs @@ -36,7 +36,7 @@ namespace NzbDrone.Api.History //This is a hack to deal with backgrid setting the sortKey to the column name instead of sortValue if (pagingSpec.SortKey.Equals("series", StringComparison.InvariantCultureIgnoreCase)) { - pagingSpec.SortKey = "series.title"; + pagingSpec.SortKey = "series.sortTitle"; } if (pagingResource.FilterKey == "eventType") diff --git a/src/NzbDrone.Api/Wanted/CutoffModule.cs b/src/NzbDrone.Api/Wanted/CutoffModule.cs index da2e44ef7..f4fd267ba 100644 --- a/src/NzbDrone.Api/Wanted/CutoffModule.cs +++ b/src/NzbDrone.Api/Wanted/CutoffModule.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using NzbDrone.Api.Episodes; using NzbDrone.Api.Extensions; using NzbDrone.Core.Datastore; @@ -30,6 +31,12 @@ namespace NzbDrone.Api.Wanted SortDirection = pagingResource.SortDirection }; + //This is a hack to deal with backgrid setting the sortKey to the column name instead of sortValue + if (pagingSpec.SortKey.Equals("series", StringComparison.InvariantCultureIgnoreCase)) + { + pagingSpec.SortKey = "series.sortTitle"; + } + if (pagingResource.FilterKey == "monitored" && pagingResource.FilterValue == "false") { pagingSpec.FilterExpression = v => v.Monitored == false || v.Series.Monitored == false; diff --git a/src/NzbDrone.Api/Wanted/MissingModule.cs b/src/NzbDrone.Api/Wanted/MissingModule.cs index 14fef9104..8852f0ae8 100644 --- a/src/NzbDrone.Api/Wanted/MissingModule.cs +++ b/src/NzbDrone.Api/Wanted/MissingModule.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using NzbDrone.Api.Episodes; using NzbDrone.Api.Extensions; using NzbDrone.Core.Datastore; @@ -30,6 +31,12 @@ namespace NzbDrone.Api.Wanted SortDirection = pagingResource.SortDirection }; + //This is a hack to deal with backgrid setting the sortKey to the column name instead of sortValue + if (pagingSpec.SortKey.Equals("series", StringComparison.InvariantCultureIgnoreCase)) + { + pagingSpec.SortKey = "series.sortTitle"; + } + if (pagingResource.FilterKey == "monitored" && pagingResource.FilterValue == "false") { pagingSpec.FilterExpression = v => v.Monitored == false || v.Series.Monitored == false; diff --git a/src/UI/History/Blacklist/BlacklistLayout.js b/src/UI/History/Blacklist/BlacklistLayout.js index d8b214ca2..c1476f6f7 100644 --- a/src/UI/History/Blacklist/BlacklistLayout.js +++ b/src/UI/History/Blacklist/BlacklistLayout.js @@ -35,33 +35,33 @@ define( columns: [ { - name : 'series', - label: 'Series', - cell : SeriesTitleCell, - sortValue: 'series.title' + name : 'series', + label : 'Series', + cell : SeriesTitleCell, + sortValue : 'series.sortTitle' }, { - name : 'sourceTitle', - label: 'Source Title', - cell : 'string', - sortValue: 'sourceTitle' + name : 'sourceTitle', + label : 'Source Title', + cell : 'string', + sortValue : 'sourceTitle' }, { - name : 'quality', - label : 'Quality', - cell : QualityCell, - sortable: false + name : 'quality', + label : 'Quality', + cell : QualityCell, + sortable : false }, { - name : 'date', - label: 'Date', - cell : RelativeDateCell + name : 'date', + label : 'Date', + cell : RelativeDateCell }, { - name : 'this', - label : '', - cell : BlacklistActionsCell, - sortable: false + name : 'this', + label : '', + cell : BlacklistActionsCell, + sortable : false } ], diff --git a/src/UI/History/Queue/QueueLayout.js b/src/UI/History/Queue/QueueLayout.js index b20044fd8..886381c49 100644 --- a/src/UI/History/Queue/QueueLayout.js +++ b/src/UI/History/Queue/QueueLayout.js @@ -32,33 +32,34 @@ define( columns: [ { - name : 'status', - label: '', - cell : QueueStatusCell, - cellValue: 'this' + name : 'status', + label : '', + cell : QueueStatusCell, + cellValue : 'this' }, { - name : 'series', - label: 'Series', - cell : SeriesTitleCell + name : 'series', + label : 'Series', + cell : SeriesTitleCell, + sortable : false }, { - name : 'episode', - label : 'Episode', - sortable: false, - cell : EpisodeNumberCell + name : 'episode', + label : 'Episode', + cell : EpisodeNumberCell, + sortable : false }, { - name : 'episode', - label : 'Episode Title', - sortable: false, - cell : EpisodeTitleCell + name : 'episode', + label : 'Episode Title', + cell : EpisodeTitleCell, + sortable : false }, { - name : 'quality', - label : 'Quality', - cell : QualityCell, - sortable: false + name : 'quality', + label : 'Quality', + cell : QualityCell, + sortable : false }, { name : 'timeleft', diff --git a/src/UI/History/Table/HistoryTableLayout.js b/src/UI/History/Table/HistoryTableLayout.js index cb7c66331..baab97395 100644 --- a/src/UI/History/Table/HistoryTableLayout.js +++ b/src/UI/History/Table/HistoryTableLayout.js @@ -39,45 +39,45 @@ define( columns: [ { - name : 'eventType', - label : '', - cell : EventTypeCell, - cellValue: 'this' + name : 'eventType', + label : '', + cell : EventTypeCell, + cellValue : 'this' }, { - name : 'series', - label: 'Series', - cell : SeriesTitleCell, - sortValue: 'series.title' + name : 'series', + label : 'Series', + cell : SeriesTitleCell, + sortValue : 'series.sortTitle' }, { - name : 'episode', - label : 'Episode', - sortable: false, - cell : EpisodeNumberCell + name : 'episode', + label : 'Episode', + cell : EpisodeNumberCell, + sortable : false }, { - name : 'episode', - label : 'Episode Title', - sortable: false, - cell : EpisodeTitleCell + name : 'episode', + label : 'Episode Title', + cell : EpisodeTitleCell, + sortable : false }, { - name : 'quality', - label : 'Quality', - cell : QualityCell, - sortable: false + name : 'quality', + label : 'Quality', + cell : QualityCell, + sortable : false }, { - name : 'date', - label: 'Date', - cell : RelativeDateCell + name : 'date', + label : 'Date', + cell : RelativeDateCell }, { - name : 'this', - label : '', - cell : HistoryDetailsCell, - sortable: false + name : 'this', + label : '', + cell : HistoryDetailsCell, + sortable : false } ], diff --git a/src/UI/Series/Editor/SeriesEditorLayout.js b/src/UI/Series/Editor/SeriesEditorLayout.js index 84ee8bca7..dc0b7f972 100644 --- a/src/UI/Series/Editor/SeriesEditorLayout.js +++ b/src/UI/Series/Editor/SeriesEditorLayout.js @@ -57,25 +57,26 @@ define( cell : SeriesStatusCell }, { - name : 'title', - label : 'Title', - cell : SeriesTitleCell, - cellValue: 'this' + name : 'title', + label : 'Title', + cell : SeriesTitleCell, + cellValue : 'this', + sortValue : 'sortTitle' }, { - name : 'qualityProfileId', - label: 'Quality', - cell : QualityProfileCell + name : 'qualityProfileId', + label : 'Quality', + cell : QualityProfileCell }, { - name : 'seasonFolder', - label: 'Season Folder', - cell : SeasonFolderCell + name : 'seasonFolder', + label : 'Season Folder', + cell : SeasonFolderCell }, { - name : 'path', - label: 'Path', - cell : 'string' + name : 'path', + label : 'Path', + cell : 'string' } ], diff --git a/src/UI/Series/Index/SeriesIndexLayout.js b/src/UI/Series/Index/SeriesIndexLayout.js index 292d7d37e..fb9782817 100644 --- a/src/UI/Series/Index/SeriesIndexLayout.js +++ b/src/UI/Series/Index/SeriesIndexLayout.js @@ -53,25 +53,26 @@ define( cell : SeriesStatusCell }, { - name : 'title', - label : 'Title', - cell : SeriesTitleCell, - cellValue: 'this' + name : 'title', + label : 'Title', + cell : SeriesTitleCell, + cellValue : 'this', + sortValue : 'sortTitle' }, { - name : 'seasonCount', - label: 'Seasons', - cell : 'integer' + name : 'seasonCount', + label : 'Seasons', + cell : 'integer' }, { - name : 'qualityProfileId', - label: 'Quality', - cell : QualityProfileCell + name : 'qualityProfileId', + label : 'Quality', + cell : QualityProfileCell }, { - name : 'network', - label: 'Network', - cell : 'string' + name : 'network', + label : 'Network', + cell : 'string' }, { name : 'nextAiring', @@ -80,16 +81,16 @@ define( sortValue : SeriesCollection.nextAiring }, { - name : 'percentOfEpisodes', - label : 'Episodes', - cell : EpisodeProgressCell, - className: 'episode-progress-cell' + name : 'percentOfEpisodes', + label : 'Episodes', + cell : EpisodeProgressCell, + className : 'episode-progress-cell' }, { - name : 'this', - label : '', - sortable: false, - cell : SeriesActionsCell + name : 'this', + label : '', + sortable : false, + cell : SeriesActionsCell } ], diff --git a/src/UI/Wanted/Cutoff/CutoffUnmetLayout.js b/src/UI/Wanted/Cutoff/CutoffUnmetLayout.js index df889f4f5..00503c2d4 100644 --- a/src/UI/Wanted/Cutoff/CutoffUnmetLayout.js +++ b/src/UI/Wanted/Cutoff/CutoffUnmetLayout.js @@ -45,39 +45,39 @@ define([ columns : [ { - name : '', - cell : 'select-row', - headerCell : 'select-all', - sortable : false + name : '', + cell : 'select-row', + headerCell: 'select-all', + sortable : false }, { - name : 'series', - label : 'Series Title', - sortable : false, - cell : SeriesTitleCell + name : 'series', + label : 'Series Title', + cell : SeriesTitleCell, + sortValue : 'series.sortTitle' }, { - name : 'this', - label : 'Episode', - sortable : false, - cell : EpisodeNumberCell + name : 'this', + label : 'Episode', + cell : EpisodeNumberCell, + sortable : false }, { - name : 'this', - label : 'Episode Title', - sortable : false, - cell : EpisodeTitleCell + name : 'this', + label : 'Episode Title', + cell : EpisodeTitleCell, + sortable : false }, { - name : 'airDateUtc', - label : 'Air Date', - cell : RelativeDateCell + name : 'airDateUtc', + label : 'Air Date', + cell : RelativeDateCell }, { - name : 'status', - label : 'Status', - cell : EpisodeStatusCell, - sortable : false + name : 'status', + label : 'Status', + cell : EpisodeStatusCell, + sortable : false } ], diff --git a/src/UI/Wanted/Missing/MissingLayout.js b/src/UI/Wanted/Missing/MissingLayout.js index 7e1cf6ae2..a85f867fc 100644 --- a/src/UI/Wanted/Missing/MissingLayout.js +++ b/src/UI/Wanted/Missing/MissingLayout.js @@ -45,39 +45,39 @@ define([ columns : [ { - name : '', - cell : 'select-row', - headerCell : 'select-all', - sortable : false + name : '', + cell : 'select-row', + headerCell: 'select-all', + sortable : false }, { - name : 'series', - label : 'Series Title', - sortable : false, - cell : SeriesTitleCell + name : 'series', + label : 'Series Title', + cell : SeriesTitleCell, + sortValue : 'series.sortTitle' }, { - name : 'this', - label : 'Episode', - sortable : false, - cell : EpisodeNumberCell + name : 'this', + label : 'Episode', + cell : EpisodeNumberCell, + sortable : false }, { - name : 'this', - label : 'Episode Title', - sortable : false, - cell : EpisodeTitleCell + name : 'this', + label : 'Episode Title', + cell : EpisodeTitleCell, + sortable : false }, { - name : 'airDateUtc', - label : 'Air Date', - cell : RelativeDateCell + name : 'airDateUtc', + label : 'Air Date', + cell : RelativeDateCell }, { - name : 'status', - label : 'Status', - cell : EpisodeStatusCell, - sortable : false + name : 'status', + label : 'Status', + cell : EpisodeStatusCell, + sortable : false } ], From c2b06d957d896fda6634f262d5f2b4c8289dd088 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Sat, 21 Jun 2014 00:27:53 +0200 Subject: [PATCH 3/3] New: Series Overview sorted by Next Airing now sorts all remaining items by their Last Aired date. --- src/NzbDrone.Api/Series/SeriesModule.cs | 1 + src/NzbDrone.Api/Series/SeriesResource.cs | 1 + .../SeriesStatisticsFixture.cs | 47 +++++++++++++++++++ .../SeriesStats/SeriesStatistics.cs | 13 +++++ .../SeriesStats/SeriesStatisticsRepository.cs | 3 +- src/UI/Series/SeriesCollection.js | 14 ++++-- 6 files changed, 74 insertions(+), 5 deletions(-) diff --git a/src/NzbDrone.Api/Series/SeriesModule.cs b/src/NzbDrone.Api/Series/SeriesModule.cs index 0439fdb27..8b26e5dd2 100644 --- a/src/NzbDrone.Api/Series/SeriesModule.cs +++ b/src/NzbDrone.Api/Series/SeriesModule.cs @@ -177,6 +177,7 @@ namespace NzbDrone.Api.Series resource.EpisodeCount = seriesStatistics.EpisodeCount; resource.EpisodeFileCount = seriesStatistics.EpisodeFileCount; resource.NextAiring = seriesStatistics.NextAiring; + resource.PreviousAiring = seriesStatistics.PreviousAiring; } public void Handle(EpisodeImportedEvent message) diff --git a/src/NzbDrone.Api/Series/SeriesResource.cs b/src/NzbDrone.Api/Series/SeriesResource.cs index 95069ad20..28a59fbfd 100644 --- a/src/NzbDrone.Api/Series/SeriesResource.cs +++ b/src/NzbDrone.Api/Series/SeriesResource.cs @@ -34,6 +34,7 @@ namespace NzbDrone.Api.Series public String QualityProfileName { get; set; } public String Overview { get; set; } public DateTime? NextAiring { get; set; } + public DateTime? PreviousAiring { get; set; } public String Network { get; set; } public String AirTime { get; set; } public List Images { get; set; } diff --git a/src/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs b/src/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs index 1b689c53c..1172daff2 100644 --- a/src/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs +++ b/src/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs @@ -39,6 +39,11 @@ namespace NzbDrone.Core.Test.SeriesStatsTests _episode.EpisodeFileId = 1; } + private void GivenOldEpisode() + { + _episode.AirDateUtc = DateTime.Now.AddSeconds(-10); + } + private void GivenMonitoredEpisode() { _episode.Monitored = true; @@ -59,6 +64,7 @@ namespace NzbDrone.Core.Test.SeriesStatsTests stats.Should().HaveCount(1); stats.First().NextAiring.Should().Be(_episode.AirDateUtc); + stats.First().PreviousAiring.Should().NotHaveValue(); } [Test] @@ -73,6 +79,47 @@ namespace NzbDrone.Core.Test.SeriesStatsTests stats.First().NextAiring.Should().NotHaveValue(); } + [Test] + public void should_have_previous_airing_for_old_episode_with_file() + { + GivenEpisodeWithFile(); + GivenOldEpisode(); + GivenFile(); + + var stats = Subject.SeriesStatistics(); + + stats.Should().HaveCount(1); + stats.First().NextAiring.Should().NotHaveValue(); + stats.First().PreviousAiring.Should().Be(_episode.AirDateUtc); + } + + [Test] + public void should_have_previous_airing_for_old_episode_without_file_monitored() + { + GivenMonitoredEpisode(); + GivenOldEpisode(); + GivenFile(); + + var stats = Subject.SeriesStatistics(); + + stats.Should().HaveCount(1); + stats.First().NextAiring.Should().NotHaveValue(); + stats.First().PreviousAiring.Should().Be(_episode.AirDateUtc); + } + + [Test] + public void should_not_have_previous_airing_for_old_episode_without_file_unmonitored() + { + GivenOldEpisode(); + GivenFile(); + + var stats = Subject.SeriesStatistics(); + + stats.Should().HaveCount(1); + stats.First().NextAiring.Should().NotHaveValue(); + stats.First().PreviousAiring.Should().NotHaveValue(); + } + [Test] public void should_not_include_unmonitored_episode_in_episode_count() { diff --git a/src/NzbDrone.Core/SeriesStats/SeriesStatistics.cs b/src/NzbDrone.Core/SeriesStats/SeriesStatistics.cs index 170985718..e43fb7c64 100644 --- a/src/NzbDrone.Core/SeriesStats/SeriesStatistics.cs +++ b/src/NzbDrone.Core/SeriesStats/SeriesStatistics.cs @@ -7,6 +7,7 @@ namespace NzbDrone.Core.SeriesStats { public int SeriesId { get; set; } public string NextAiringString { get; set; } + public string PreviousAiringString { get; set; } public int EpisodeFileCount { get; set; } public int EpisodeCount { get; set; } @@ -21,5 +22,17 @@ namespace NzbDrone.Core.SeriesStats return nextAiring; } } + + public DateTime? PreviousAiring + { + get + { + DateTime previousAiring; + + if (!DateTime.TryParse(PreviousAiringString, out previousAiring)) return null; + + return previousAiring; + } + } } } diff --git a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs index 6bb4c63d3..683736f77 100644 --- a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs +++ b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs @@ -56,7 +56,8 @@ namespace NzbDrone.Core.SeriesStats SeriesId, SUM(CASE WHEN (Monitored = 1 AND AirdateUtc <= @currentDate) OR EpisodeFileId > 0 THEN 1 ELSE 0 END) AS EpisodeCount, SUM(CASE WHEN EpisodeFileId > 0 THEN 1 ELSE 0 END) AS EpisodeFileCount, - 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 FROM Episodes"; } diff --git a/src/UI/Series/SeriesCollection.js b/src/UI/Series/SeriesCollection.js index 8bc23630e..45fb8dc2b 100644 --- a/src/UI/Series/SeriesCollection.js +++ b/src/UI/Series/SeriesCollection.js @@ -59,12 +59,18 @@ define( //Sorters nextAiring: function (model, attr) { var nextAiring = model.get(attr); - - if (!nextAiring) { - return Number.MAX_VALUE; + + if (nextAiring) { + return Moment(nextAiring).unix(); + } + + var previousAiring = model.get(attr.replace('nextAiring', 'previousAiring')); + + if (previousAiring) { + return 10000000000 - Moment(previousAiring).unix(); } - return Moment(nextAiring).unix(); + return Number.MAX_VALUE; } });