2021-08-03 04:43:28 +00:00
using System ;
2013-10-03 01:01:32 +00:00
using System.Collections.Generic ;
using System.Linq ;
using NLog ;
2014-12-02 06:26:25 +00:00
using NzbDrone.Common.Extensions ;
2013-10-03 01:01:32 +00:00
using NzbDrone.Core.Messaging.Events ;
using NzbDrone.Core.Tv.Events ;
namespace NzbDrone.Core.Tv
{
public interface IRefreshEpisodeService
{
void RefreshEpisodeInfo ( Series series , IEnumerable < Episode > remoteEpisodes ) ;
}
public class RefreshEpisodeService : IRefreshEpisodeService
{
private readonly IEpisodeService _episodeService ;
private readonly IEventAggregator _eventAggregator ;
private readonly Logger _logger ;
2015-01-04 01:02:45 +00:00
public RefreshEpisodeService ( IEpisodeService episodeService , IEventAggregator eventAggregator , Logger logger )
2013-10-03 01:01:32 +00:00
{
_episodeService = episodeService ;
_eventAggregator = eventAggregator ;
_logger = logger ;
}
public void RefreshEpisodeInfo ( Series series , IEnumerable < Episode > remoteEpisodes )
{
_logger . Info ( "Starting episode info refresh for: {0}" , series ) ;
var successCount = 0 ;
var failCount = 0 ;
var existingEpisodes = _episodeService . GetEpisodeBySeries ( series . Id ) ;
var seasons = series . Seasons ;
2020-03-12 19:25:54 +00:00
var hasExisting = existingEpisodes . Any ( ) ;
2013-10-03 01:01:32 +00:00
var updateList = new List < Episode > ( ) ;
var newList = new List < Episode > ( ) ;
var dupeFreeRemoteEpisodes = remoteEpisodes . DistinctBy ( m = > new { m . SeasonNumber , m . EpisodeNumber } ) . ToList ( ) ;
2014-07-11 02:23:57 +00:00
if ( series . SeriesType = = SeriesTypes . Anime )
{
2016-02-11 08:09:29 +00:00
dupeFreeRemoteEpisodes = MapAbsoluteEpisodeNumbers ( dupeFreeRemoteEpisodes ) ;
2014-07-11 02:23:57 +00:00
}
2023-08-19 08:36:18 +00:00
var orderedEpisodes = OrderEpisodes ( series , dupeFreeRemoteEpisodes ) . ToList ( ) ;
var episodesPerSeason = orderedEpisodes . GroupBy ( s = > s . SeasonNumber ) . ToDictionary ( g = > g . Key , g = > g . Count ( ) ) ;
2023-09-10 22:24:58 +00:00
var latestSeason = seasons . MaxBy ( s = > s . SeasonNumber ) ;
2023-08-19 08:36:18 +00:00
foreach ( var episode in orderedEpisodes )
2013-10-03 01:01:32 +00:00
{
try
{
2024-02-26 00:32:32 +00:00
var episodeToUpdate = existingEpisodes . FirstOrDefault ( e = > e . SeasonNumber = = episode . SeasonNumber & & e . EpisodeNumber = = episode . EpisodeNumber ) ;
2013-10-03 01:01:32 +00:00
if ( episodeToUpdate ! = null )
{
existingEpisodes . Remove ( episodeToUpdate ) ;
updateList . Add ( episodeToUpdate ) ;
2024-02-25 23:16:06 +00:00
// Anime series with newly added absolute episode number
if ( series . SeriesType = = SeriesTypes . Anime & &
! episodeToUpdate . AbsoluteEpisodeNumber . HasValue & &
episode . AbsoluteEpisodeNumber . HasValue )
{
episodeToUpdate . AbsoluteEpisodeNumberAdded = true ;
}
2013-10-03 01:01:32 +00:00
}
else
{
episodeToUpdate = new Episode ( ) ;
2023-10-22 02:57:59 +00:00
episodeToUpdate . Monitored = GetMonitoredStatus ( episode , seasons , series ) ;
2013-10-03 01:01:32 +00:00
newList . Add ( episodeToUpdate ) ;
}
episodeToUpdate . SeriesId = series . Id ;
2022-02-22 05:28:24 +00:00
episodeToUpdate . TvdbId = episode . TvdbId ;
2013-10-03 01:01:32 +00:00
episodeToUpdate . EpisodeNumber = episode . EpisodeNumber ;
episodeToUpdate . SeasonNumber = episode . SeasonNumber ;
2014-07-11 02:23:57 +00:00
episodeToUpdate . AbsoluteEpisodeNumber = episode . AbsoluteEpisodeNumber ;
2019-04-19 19:19:39 +00:00
episodeToUpdate . AiredAfterSeasonNumber = episode . AiredAfterSeasonNumber ;
episodeToUpdate . AiredBeforeSeasonNumber = episode . AiredBeforeSeasonNumber ;
episodeToUpdate . AiredBeforeEpisodeNumber = episode . AiredBeforeEpisodeNumber ;
2015-01-10 19:31:05 +00:00
episodeToUpdate . Title = episode . Title ? ? "TBA" ;
2013-10-03 01:01:32 +00:00
episodeToUpdate . Overview = episode . Overview ;
episodeToUpdate . AirDate = episode . AirDate ;
episodeToUpdate . AirDateUtc = episode . AirDateUtc ;
2023-03-03 23:17:59 +00:00
episodeToUpdate . Runtime = episode . Runtime ;
2023-08-19 08:36:18 +00:00
episodeToUpdate . FinaleType = episode . FinaleType ;
2014-01-22 05:22:09 +00:00
episodeToUpdate . Ratings = episode . Ratings ;
episodeToUpdate . Images = episode . Images ;
2013-10-03 01:01:32 +00:00
2023-08-19 08:36:18 +00:00
// TheTVDB has a severe lack of season/series finales, this helps smooth out that limitation so they can be displayed in the UI
2023-09-10 22:24:58 +00:00
if ( series . Status = = SeriesStatusType . Ended & &
episodeToUpdate . FinaleType = = null & &
episodeToUpdate . SeasonNumber > 0 & &
episodeToUpdate . SeasonNumber = = latestSeason . SeasonNumber & &
episodeToUpdate . EpisodeNumber > 1 & &
episodeToUpdate . EpisodeNumber = = episodesPerSeason [ episodeToUpdate . SeasonNumber ] & &
episodeToUpdate . AirDateUtc . HasValue & &
episodeToUpdate . AirDateUtc . Value . After ( DateTime . UtcNow . AddDays ( - 14 ) ) & &
orderedEpisodes . None ( e = > e . SeasonNumber = = latestSeason . SeasonNumber & & e . FinaleType ! = null ) )
2023-08-19 08:36:18 +00:00
{
2023-09-10 22:24:58 +00:00
episodeToUpdate . FinaleType = "series" ;
2023-08-19 08:36:18 +00:00
}
2013-10-03 01:01:32 +00:00
successCount + + ;
}
catch ( Exception e )
{
2017-01-05 23:32:17 +00:00
_logger . Fatal ( e , "An error has occurred while updating episode info for series {0}. {1}" , series , episode ) ;
2013-10-03 01:01:32 +00:00
failCount + + ;
}
}
2020-03-12 19:25:54 +00:00
UnmonitorReaddedEpisodes ( series , newList , hasExisting ) ;
2013-10-03 01:01:32 +00:00
var allEpisodes = new List < Episode > ( ) ;
allEpisodes . AddRange ( newList ) ;
allEpisodes . AddRange ( updateList ) ;
AdjustMultiEpisodeAirTime ( series , allEpisodes ) ;
2014-08-07 12:42:46 +00:00
AdjustDirectToDvdAirDate ( series , allEpisodes ) ;
2013-10-03 01:01:32 +00:00
_episodeService . DeleteMany ( existingEpisodes ) ;
_episodeService . UpdateMany ( updateList ) ;
_episodeService . InsertMany ( newList ) ;
2021-02-08 04:25:44 +00:00
_eventAggregator . PublishEvent ( new EpisodeInfoRefreshedEvent ( series , newList , updateList , existingEpisodes ) ) ;
2013-10-03 01:01:32 +00:00
if ( failCount ! = 0 )
{
_logger . Info ( "Finished episode refresh for series: {0}. Successful: {1} - Failed: {2} " ,
2021-08-03 04:43:28 +00:00
series . Title ,
successCount ,
failCount ) ;
2013-10-03 01:01:32 +00:00
}
else
{
_logger . Info ( "Finished episode refresh for series: {0}." , series ) ;
}
}
2023-10-22 02:57:59 +00:00
private bool GetMonitoredStatus ( Episode episode , IEnumerable < Season > seasons , Series series )
2013-10-03 01:01:32 +00:00
{
2023-10-22 02:57:59 +00:00
if ( ( episode . EpisodeNumber = = 0 & & episode . SeasonNumber ! = 1 ) | | series . MonitorNewItems = = NewItemMonitorTypes . None )
2013-10-03 01:01:32 +00:00
{
return false ;
}
var season = seasons . SingleOrDefault ( c = > c . SeasonNumber = = episode . SeasonNumber ) ;
return season = = null | | season . Monitored ;
}
2020-03-12 19:25:54 +00:00
private void UnmonitorReaddedEpisodes ( Series series , List < Episode > episodes , bool hasExisting )
{
if ( series . AddOptions ! = null )
{
return ;
}
var threshold = DateTime . UtcNow . AddDays ( - 14 ) ;
var oldEpisodes = episodes . Where ( e = > e . AirDateUtc . HasValue & & e . AirDateUtc . Value . Before ( threshold ) ) . ToList ( ) ;
if ( oldEpisodes . Any ( ) )
{
if ( hasExisting )
{
_logger . Warn ( "Show {0} ({1}) had {2} old episodes appear, please check monitored status." , series . TvdbId , series . Title , oldEpisodes . Count ) ;
}
else
{
threshold = DateTime . UtcNow . AddDays ( - 1 ) ;
foreach ( var episode in episodes )
{
if ( episode . AirDateUtc . HasValue & & episode . AirDateUtc . Value . Before ( threshold ) )
{
episode . Monitored = false ;
}
}
_logger . Warn ( "Show {0} ({1}) had {2} old episodes appear, unmonitored aired episodes to prevent unexpected downloads." , series . TvdbId , series . Title , oldEpisodes . Count ) ;
}
}
}
2015-01-10 20:00:00 +00:00
private void AdjustMultiEpisodeAirTime ( Series series , IEnumerable < Episode > allEpisodes )
2013-10-03 01:01:32 +00:00
{
2015-01-10 20:00:00 +00:00
var groups = allEpisodes . Where ( c = > c . AirDateUtc . HasValue )
2017-01-05 22:33:40 +00:00
. GroupBy ( e = > new { e . SeasonNumber , e . AirDate } )
2015-01-10 20:00:00 +00:00
. Where ( g = > g . Count ( ) > 1 )
. ToList ( ) ;
2013-10-03 01:01:32 +00:00
foreach ( var group in groups )
{
2019-04-20 21:50:22 +00:00
if ( group . Key . SeasonNumber ! = 0 & & group . Count ( ) > 3 )
{
_logger . Debug ( "Not adjusting multi-episode air times for series {0} season {1} since more than 3 episodes 'aired' on the same day" , series . Title , group . Key . SeasonNumber ) ;
continue ;
}
2013-10-03 01:01:32 +00:00
var episodeCount = 0 ;
2015-01-10 20:00:00 +00:00
2014-02-01 22:26:49 +00:00
foreach ( var episode in group . OrderBy ( e = > e . SeasonNumber ) . ThenBy ( e = > e . EpisodeNumber ) )
2013-10-03 01:01:32 +00:00
{
episode . AirDateUtc = episode . AirDateUtc . Value . AddMinutes ( series . Runtime * episodeCount ) ;
episodeCount + + ;
}
}
}
2014-02-01 22:26:49 +00:00
2023-03-27 03:40:51 +00:00
private void AdjustDirectToDvdAirDate ( Series series , IList < Episode > allEpisodes )
2014-08-07 12:42:46 +00:00
{
if ( series . Status = = SeriesStatusType . Ended & & allEpisodes . All ( v = > ! v . AirDateUtc . HasValue ) & & series . FirstAired . HasValue )
{
foreach ( var episode in allEpisodes )
{
episode . AirDateUtc = series . FirstAired ;
episode . AirDate = series . FirstAired . Value . ToString ( "yyyy-MM-dd" ) ;
}
}
}
2016-02-11 08:09:29 +00:00
private List < Episode > MapAbsoluteEpisodeNumbers ( List < Episode > remoteEpisodes )
2014-02-01 22:26:49 +00:00
{
2022-11-07 00:44:13 +00:00
// Return all episodes with no abs number, but distinct for those with abs number
2016-02-11 08:09:29 +00:00
return remoteEpisodes . Where ( e = > e . AbsoluteEpisodeNumber . HasValue )
. OrderByDescending ( e = > e . SeasonNumber )
. DistinctBy ( e = > e . AbsoluteEpisodeNumber . Value )
. Concat ( remoteEpisodes . Where ( e = > ! e . AbsoluteEpisodeNumber . HasValue ) )
. ToList ( ) ;
2014-07-11 02:23:57 +00:00
}
2014-08-07 12:42:46 +00:00
private IEnumerable < Episode > OrderEpisodes ( Series series , List < Episode > episodes )
2014-07-11 02:23:57 +00:00
{
if ( series . SeriesType = = SeriesTypes . Anime )
{
2014-10-14 22:19:35 +00:00
var withAbs = episodes . Where ( e = > e . AbsoluteEpisodeNumber . HasValue )
2014-07-11 02:23:57 +00:00
. OrderBy ( e = > e . AbsoluteEpisodeNumber ) ;
2014-10-14 22:19:35 +00:00
var withoutAbs = episodes . Where ( e = > ! e . AbsoluteEpisodeNumber . HasValue )
2014-07-11 02:23:57 +00:00
. OrderBy ( e = > e . SeasonNumber )
. ThenBy ( e = > e . EpisodeNumber ) ;
return withAbs . Concat ( withoutAbs ) ;
}
return episodes . OrderBy ( e = > e . SeasonNumber ) . ThenBy ( e = > e . EpisodeNumber ) ;
2014-02-01 22:26:49 +00:00
}
2013-10-03 01:01:32 +00:00
}
2021-08-03 04:43:28 +00:00
}