diff --git a/frontend/src/Series/Details/SeriesDetails.js b/frontend/src/Series/Details/SeriesDetails.js index 6f05d593b..8f6189cbe 100644 --- a/frontend/src/Series/Details/SeriesDetails.js +++ b/frontend/src/Series/Details/SeriesDetails.js @@ -190,7 +190,7 @@ class SeriesDetails extends Component { genres, tags, year, - previousAiring, + lastAired, isSaving, isRefreshing, isSearching, @@ -227,7 +227,7 @@ class SeriesDetails extends Component { } = this.state; const statusDetails = getSeriesStatusDetails(status); - const runningYears = statusDetails.title === translate('Ended') ? `${year}-${getDateYear(previousAiring)}` : `${year}-`; + const runningYears = statusDetails.title === translate('Ended') ? `${year}-${getDateYear(lastAired)}` : `${year}-`; let episodeFilesCountMessage = translate('SeriesDetailsNoEpisodeFiles'); @@ -711,6 +711,7 @@ SeriesDetails.propTypes = { genres: PropTypes.arrayOf(PropTypes.string).isRequired, tags: PropTypes.arrayOf(PropTypes.number).isRequired, year: PropTypes.number.isRequired, + lastAired: PropTypes.string, previousAiring: PropTypes.string, isSaving: PropTypes.bool.isRequired, saveError: PropTypes.object, diff --git a/src/NzbDrone.Core/Datastore/Migration/199_series_last_aired.cs b/src/NzbDrone.Core/Datastore/Migration/199_series_last_aired.cs new file mode 100644 index 000000000..2264dabd1 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/199_series_last_aired.cs @@ -0,0 +1,16 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(199)] + public class series_last_aired : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("Series").AddColumn("LastAired").AsDateTimeOffset().Nullable(); + + // Execute.Sql("UPDATE Series SET LastAired = "); + } + } +} diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ShowResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ShowResource.cs index a44137f5a..c7acd354a 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ShowResource.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ShowResource.cs @@ -20,6 +20,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource // public string Language { get; set; } public string Slug { get; set; } public string FirstAired { get; set; } + public string LastAired { get; set; } public int? TvRageId { get; set; } public int? TvMazeId { get; set; } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index 4e5077c02..b067c3dd8 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -189,6 +189,11 @@ namespace NzbDrone.Core.MetadataSource.SkyHook series.Year = series.FirstAired.Value.Year; } + if (show.LastAired != null) + { + series.LastAired = DateTime.ParseExact(show.LastAired, "yyyy-MM-dd", DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + } + series.Overview = show.Overview; if (show.Runtime != null) diff --git a/src/NzbDrone.Core/SeriesStats/SeasonStatistics.cs b/src/NzbDrone.Core/SeriesStats/SeasonStatistics.cs index 8c730bed6..5fe1507c2 100644 --- a/src/NzbDrone.Core/SeriesStats/SeasonStatistics.cs +++ b/src/NzbDrone.Core/SeriesStats/SeasonStatistics.cs @@ -12,6 +12,7 @@ namespace NzbDrone.Core.SeriesStats public int SeasonNumber { get; set; } public string NextAiringString { get; set; } public string PreviousAiringString { get; set; } + public string LastAiredString { get; set; } public int EpisodeFileCount { get; set; } public int EpisodeCount { get; set; } public int AvailableEpisodeCount { get; set; } @@ -65,6 +66,29 @@ namespace NzbDrone.Core.SeriesStats } } + public DateTime? LastAired + { + get + { + DateTime lastAired; + + try + { + if (!DateTime.TryParse(LastAiredString, out lastAired)) + { + return null; + } + } + catch (ArgumentOutOfRangeException) + { + // GHI 3518: Can throw on mono (6.x?) despite being a Try* + return null; + } + + return lastAired; + } + } + public List ReleaseGroups { get diff --git a/src/NzbDrone.Core/SeriesStats/SeriesStatistics.cs b/src/NzbDrone.Core/SeriesStats/SeriesStatistics.cs index fb6215e18..cb054da7c 100644 --- a/src/NzbDrone.Core/SeriesStats/SeriesStatistics.cs +++ b/src/NzbDrone.Core/SeriesStats/SeriesStatistics.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using NzbDrone.Core.Datastore; @@ -9,6 +9,7 @@ namespace NzbDrone.Core.SeriesStats public int SeriesId { get; set; } public string NextAiringString { get; set; } public string PreviousAiringString { get; set; } + public string LastAiredString { get; set; } public int EpisodeFileCount { get; set; } public int EpisodeCount { get; set; } public int TotalEpisodeCount { get; set; } @@ -61,5 +62,28 @@ namespace NzbDrone.Core.SeriesStats return previousAiring; } } + + public DateTime? LastAired + { + get + { + DateTime lastAired; + + try + { + if (!DateTime.TryParse(LastAiredString, out lastAired)) + { + return null; + } + } + catch (ArgumentOutOfRangeException) + { + // GHI 3518: Can throw on mono (6.x?) despite being a Try* + return null; + } + + return lastAired; + } + } } } diff --git a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs index ae0dd8fe4..be64ca1b3 100644 --- a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs +++ b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs @@ -80,7 +80,8 @@ namespace NzbDrone.Core.SeriesStats SUM(CASE WHEN (""Monitored"" = {trueIndicator} 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 ""Monitored"" = {falseIndicator} THEN NULL ELSE ""AirDateUtc"" END) AS NextAiringString, - MAX(CASE WHEN ""AirDateUtc"" >= @currentDate OR ""Monitored"" = {falseIndicator} THEN NULL ELSE ""AirDateUtc"" END) AS PreviousAiringString", parameters) + MAX(CASE WHEN ""AirDateUtc"" >= @currentDate OR ""Monitored"" = {falseIndicator} THEN NULL ELSE ""AirDateUtc"" END) AS PreviousAiringString, + MAX(""AirDate"") AS LastAiredString", parameters) .GroupBy(x => x.SeriesId) .GroupBy(x => x.SeasonNumber); } diff --git a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsService.cs b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsService.cs index 44b5ddc16..5baef28f1 100644 --- a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsService.cs +++ b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsService.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; namespace NzbDrone.Core.SeriesStats @@ -52,9 +52,11 @@ namespace NzbDrone.Core.SeriesStats var nextAiring = seasonStatistics.Where(s => s.NextAiring != null).MinBy(s => s.NextAiring); var previousAiring = seasonStatistics.Where(s => s.PreviousAiring != null).MaxBy(s => s.PreviousAiring); + var lastAired = seasonStatistics.Where(s => s.SeasonNumber > 0 && s.LastAired != null).MaxBy(s => s.LastAired); seriesStatistics.NextAiringString = nextAiring?.NextAiringString; seriesStatistics.PreviousAiringString = previousAiring?.PreviousAiringString; + seriesStatistics.LastAiredString = lastAired?.LastAiredString; return seriesStatistics; } diff --git a/src/NzbDrone.Core/Tv/RefreshSeriesService.cs b/src/NzbDrone.Core/Tv/RefreshSeriesService.cs index a3c74b5e5..3cd0ab6b3 100644 --- a/src/NzbDrone.Core/Tv/RefreshSeriesService.cs +++ b/src/NzbDrone.Core/Tv/RefreshSeriesService.cs @@ -104,6 +104,7 @@ namespace NzbDrone.Core.Tv series.Images = seriesInfo.Images; series.Network = seriesInfo.Network; series.FirstAired = seriesInfo.FirstAired; + series.LastAired = seriesInfo.LastAired; series.Ratings = seriesInfo.Ratings; series.Actors = seriesInfo.Actors; series.Genres = seriesInfo.Genres; diff --git a/src/NzbDrone.Core/Tv/Series.cs b/src/NzbDrone.Core/Tv/Series.cs index b10ac3de5..9f557af42 100644 --- a/src/NzbDrone.Core/Tv/Series.cs +++ b/src/NzbDrone.Core/Tv/Series.cs @@ -48,6 +48,7 @@ namespace NzbDrone.Core.Tv public string RootFolderPath { get; set; } public DateTime Added { get; set; } public DateTime? FirstAired { get; set; } + public DateTime? LastAired { get; set; } public LazyLoaded QualityProfile { get; set; } public Language OriginalLanguage { get; set; } diff --git a/src/Sonarr.Api.V3/Series/SeriesController.cs b/src/Sonarr.Api.V3/Series/SeriesController.cs index 9f6c6823d..2622aa7ba 100644 --- a/src/Sonarr.Api.V3/Series/SeriesController.cs +++ b/src/Sonarr.Api.V3/Series/SeriesController.cs @@ -245,6 +245,9 @@ namespace Sonarr.Api.V3.Series private void LinkSeriesStatistics(SeriesResource resource, SeriesStatistics seriesStatistics) { + // Only set last aired from statistics if it's missing from the series itself + resource.LastAired ??= seriesStatistics.LastAired; + resource.PreviousAiring = seriesStatistics.PreviousAiring; resource.NextAiring = seriesStatistics.NextAiring; resource.Statistics = seriesStatistics.ToResource(resource.Seasons); diff --git a/src/Sonarr.Api.V3/Series/SeriesResource.cs b/src/Sonarr.Api.V3/Series/SeriesResource.cs index c57e52af5..021518149 100644 --- a/src/Sonarr.Api.V3/Series/SeriesResource.cs +++ b/src/Sonarr.Api.V3/Series/SeriesResource.cs @@ -51,6 +51,7 @@ namespace Sonarr.Api.V3.Series public int TvRageId { get; set; } public int TvMazeId { get; set; } public DateTime? FirstAired { get; set; } + public DateTime? LastAired { get; set; } public SeriesTypes SeriesType { get; set; } public string CleanTitle { get; set; } public string ImdbId { get; set; } @@ -121,6 +122,7 @@ namespace Sonarr.Api.V3.Series TvRageId = model.TvRageId, TvMazeId = model.TvMazeId, FirstAired = model.FirstAired, + LastAired = model.LastAired, SeriesType = model.SeriesType, CleanTitle = model.CleanTitle, ImdbId = model.ImdbId,