@ -1,10 +1,16 @@
#pragma warning disable CS1591
#pragma warning disable CS1591
using System.Collections.Generic ;
using System.Globalization ;
using System.Linq ;
using System.Threading ;
using System.Threading.Tasks ;
using MediaBrowser.Controller.Configuration ;
using MediaBrowser.Controller.Configuration ;
using MediaBrowser.Controller.Entities.TV ;
using MediaBrowser.Controller.Entities.TV ;
using MediaBrowser.Controller.Library ;
using MediaBrowser.Controller.Library ;
using MediaBrowser.Controller.Providers ;
using MediaBrowser.Controller.Providers ;
using MediaBrowser.Model.Entities ;
using MediaBrowser.Model.Entities ;
using MediaBrowser.Model.Globalization ;
using MediaBrowser.Model.IO ;
using MediaBrowser.Model.IO ;
using MediaBrowser.Providers.Manager ;
using MediaBrowser.Providers.Manager ;
using Microsoft.Extensions.Logging ;
using Microsoft.Extensions.Logging ;
@ -13,14 +19,27 @@ namespace MediaBrowser.Providers.TV
{
{
public class SeriesMetadataService : MetadataService < Series , SeriesInfo >
public class SeriesMetadataService : MetadataService < Series , SeriesInfo >
{
{
private readonly ILocalizationManager _localizationManager ;
public SeriesMetadataService (
public SeriesMetadataService (
IServerConfigurationManager serverConfigurationManager ,
IServerConfigurationManager serverConfigurationManager ,
ILogger < SeriesMetadataService > logger ,
ILogger < SeriesMetadataService > logger ,
IProviderManager providerManager ,
IProviderManager providerManager ,
IFileSystem fileSystem ,
IFileSystem fileSystem ,
ILibraryManager libraryManager )
ILibraryManager libraryManager ,
ILocalizationManager localizationManager )
: base ( serverConfigurationManager , logger , providerManager , fileSystem , libraryManager )
: base ( serverConfigurationManager , logger , providerManager , fileSystem , libraryManager )
{
{
_localizationManager = localizationManager ;
}
/// <inheritdoc />
protected override async Task AfterMetadataRefresh ( Series item , MetadataRefreshOptions refreshOptions , CancellationToken cancellationToken )
{
await base . AfterMetadataRefresh ( item , refreshOptions , cancellationToken ) . ConfigureAwait ( false ) ;
RemoveObsoleteSeasons ( item ) ;
await FillInMissingSeasonsAsync ( item , cancellationToken ) . ConfigureAwait ( false ) ;
}
}
/// <inheritdoc />
/// <inheritdoc />
@ -62,5 +81,117 @@ namespace MediaBrowser.Providers.TV
targetItem . AirDays = sourceItem . AirDays ;
targetItem . AirDays = sourceItem . AirDays ;
}
}
}
}
private void RemoveObsoleteSeasons ( Series series )
{
// TODO Legacy. It's not really "physical" seasons as any virtual seasons are always converted to non-virtual in FillInMissingSeasonsAsync.
var physicalSeasonNumbers = new HashSet < int > ( ) ;
var virtualSeasons = new List < Season > ( ) ;
foreach ( var existingSeason in series . Children . OfType < Season > ( ) )
{
if ( existingSeason . LocationType ! = LocationType . Virtual & & existingSeason . IndexNumber . HasValue )
{
physicalSeasonNumbers . Add ( existingSeason . IndexNumber . Value ) ;
}
else if ( existingSeason . LocationType = = LocationType . Virtual )
{
virtualSeasons . Add ( existingSeason ) ;
}
}
foreach ( var virtualSeason in virtualSeasons )
{
var seasonNumber = virtualSeason . IndexNumber ;
// If there's a physical season with the same number or no episodes in the season, delete it
if ( ( seasonNumber . HasValue & & physicalSeasonNumbers . Contains ( seasonNumber . Value ) )
| | ! virtualSeason . GetEpisodes ( ) . Any ( ) )
{
Logger . LogInformation ( "Removing virtual season {SeasonNumber} in series {SeriesName}" , virtualSeason . IndexNumber , series . Name ) ;
LibraryManager . DeleteItem (
virtualSeason ,
new DeleteOptions
{
DeleteFileLocation = true
} ,
false ) ;
}
}
}
/// <summary>
/// Creates seasons for all episodes that aren't in a season folder.
/// If no season number can be determined, a dummy season will be created.
/// </summary>
/// <param name="series">The series.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The async task.</returns>
private async Task FillInMissingSeasonsAsync ( Series series , CancellationToken cancellationToken )
{
var episodesInSeriesFolder = series . GetRecursiveChildren ( i = > i is Episode )
. Cast < Episode > ( )
. Where ( i = > ! i . IsInSeasonFolder ) ;
List < Season > seasons = series . Children . OfType < Season > ( ) . ToList ( ) ;
// Loop through the unique season numbers
foreach ( var episode in episodesInSeriesFolder )
{
// Null season numbers will have a 'dummy' season created because seasons are always required.
var seasonNumber = episode . ParentIndexNumber > = 0 ? episode . ParentIndexNumber : null ;
var existingSeason = seasons . FirstOrDefault ( i = > i . IndexNumber = = seasonNumber ) ;
if ( existingSeason = = null )
{
var season = await CreateSeasonAsync ( series , seasonNumber , cancellationToken ) . ConfigureAwait ( false ) ;
seasons . Add ( season ) ;
}
else if ( existingSeason . IsVirtualItem )
{
existingSeason . IsVirtualItem = false ;
await existingSeason . UpdateToRepositoryAsync ( ItemUpdateType . MetadataEdit , cancellationToken ) . ConfigureAwait ( false ) ;
}
}
}
/// <summary>
/// Creates a new season, adds it to the database by linking it to the [series] and refreshes the metadata.
/// </summary>
/// <param name="series">The series.</param>
/// <param name="seasonNumber">The season number.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The newly created season.</returns>
private async Task < Season > CreateSeasonAsync (
Series series ,
int? seasonNumber ,
CancellationToken cancellationToken )
{
string seasonName = seasonNumber switch
{
null = > _localizationManager . GetLocalizedString ( "NameSeasonUnknown" ) ,
0 = > LibraryManager . GetLibraryOptions ( series ) . SeasonZeroDisplayName ,
_ = > string . Format ( CultureInfo . InvariantCulture , _localizationManager . GetLocalizedString ( "NameSeasonNumber" ) , seasonNumber . Value )
} ;
Logger . LogInformation ( "Creating Season {SeasonName} entry for {SeriesName}" , seasonName , series . Name ) ;
var season = new Season
{
Name = seasonName ,
IndexNumber = seasonNumber ,
Id = LibraryManager . GetNewItemId (
series . Id + ( seasonNumber ? ? - 1 ) . ToString ( CultureInfo . InvariantCulture ) + seasonName ,
typeof ( Season ) ) ,
IsVirtualItem = false ,
SeriesId = series . Id ,
SeriesName = series . Name
} ;
series . AddChild ( season , cancellationToken ) ;
await season . RefreshMetadata ( new MetadataRefreshOptions ( new DirectoryService ( FileSystem ) ) , cancellationToken ) . ConfigureAwait ( false ) ;
return season ;
}
}
}
}
}