|
@@ -8,7 +8,9 @@ using MediaBrowser.Controller.Configuration;
|
|
|
using MediaBrowser.Controller.Dto;
|
|
|
using MediaBrowser.Controller.Entities;
|
|
|
using MediaBrowser.Controller.Entities.TV;
|
|
|
+using MediaBrowser.Controller.IO;
|
|
|
using MediaBrowser.Controller.Library;
|
|
|
+using MediaBrowser.Controller.MediaSegments;
|
|
|
using MediaBrowser.Controller.Providers;
|
|
|
using MediaBrowser.Model.Entities;
|
|
|
using MediaBrowser.Model.Globalization;
|
|
@@ -16,269 +18,274 @@ using MediaBrowser.Model.IO;
|
|
|
using MediaBrowser.Providers.Manager;
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
-namespace MediaBrowser.Providers.TV
|
|
|
+namespace MediaBrowser.Providers.TV;
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// Service to manage series metadata.
|
|
|
+/// </summary>
|
|
|
+public class SeriesMetadataService : MetadataService<Series, SeriesInfo>
|
|
|
{
|
|
|
+ private readonly ILocalizationManager _localizationManager;
|
|
|
+
|
|
|
/// <summary>
|
|
|
- /// Service to manage series metadata.
|
|
|
+ /// Initializes a new instance of the <see cref="SeriesMetadataService"/> class.
|
|
|
/// </summary>
|
|
|
- public class SeriesMetadataService : MetadataService<Series, SeriesInfo>
|
|
|
+ /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param>
|
|
|
+ /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
|
|
|
+ /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
|
|
|
+ /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
|
|
|
+ /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
|
|
+ /// <param name="localizationManager">Instance of the <see cref="ILocalizationManager"/> interface.</param>
|
|
|
+ /// <param name="pathManager">Instance of the <see cref="IPathManager"/> interface.</param>
|
|
|
+ /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param>
|
|
|
+ /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param>
|
|
|
+ public SeriesMetadataService(
|
|
|
+ IServerConfigurationManager serverConfigurationManager,
|
|
|
+ ILogger<SeriesMetadataService> logger,
|
|
|
+ IProviderManager providerManager,
|
|
|
+ IFileSystem fileSystem,
|
|
|
+ ILibraryManager libraryManager,
|
|
|
+ ILocalizationManager localizationManager,
|
|
|
+ IPathManager pathManager,
|
|
|
+ IKeyframeManager keyframeManager,
|
|
|
+ IMediaSegmentManager mediaSegmentManager)
|
|
|
+ : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager)
|
|
|
{
|
|
|
- private readonly ILocalizationManager _localizationManager;
|
|
|
+ _localizationManager = localizationManager;
|
|
|
+ }
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Initializes a new instance of the <see cref="SeriesMetadataService"/> class.
|
|
|
- /// </summary>
|
|
|
- /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
|
|
- /// <param name="logger">Instance of the <see cref="ILogger{SeasonMetadataService}"/> interface.</param>
|
|
|
- /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
|
|
|
- /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
|
|
|
- /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
|
|
- /// <param name="localizationManager">Instance of the <see cref="ILocalizationManager"/> interface.</param>
|
|
|
- public SeriesMetadataService(
|
|
|
- IServerConfigurationManager serverConfigurationManager,
|
|
|
- ILogger<SeriesMetadataService> logger,
|
|
|
- IProviderManager providerManager,
|
|
|
- IFileSystem fileSystem,
|
|
|
- ILibraryManager libraryManager,
|
|
|
- ILocalizationManager localizationManager)
|
|
|
- : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager)
|
|
|
+ /// <inheritdoc />
|
|
|
+ public override async Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ if (item is Series series)
|
|
|
{
|
|
|
- _localizationManager = localizationManager;
|
|
|
- }
|
|
|
+ var seasons = series.GetRecursiveChildren(i => i is Season).ToList();
|
|
|
|
|
|
- /// <inheritdoc />
|
|
|
- public override async Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
|
|
|
- {
|
|
|
- if (item is Series series)
|
|
|
+ foreach (var season in seasons)
|
|
|
{
|
|
|
- var seasons = series.GetRecursiveChildren(i => i is Season).ToList();
|
|
|
-
|
|
|
- foreach (var season in seasons)
|
|
|
+ var hasUpdate = refreshOptions is not null && season.BeforeMetadataRefresh(refreshOptions.ReplaceAllMetadata);
|
|
|
+ if (hasUpdate)
|
|
|
{
|
|
|
- var hasUpdate = refreshOptions is not null && season.BeforeMetadataRefresh(refreshOptions.ReplaceAllMetadata);
|
|
|
- if (hasUpdate)
|
|
|
- {
|
|
|
- await season.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
|
|
|
- }
|
|
|
+ await season.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- return await base.RefreshMetadata(item, refreshOptions, cancellationToken).ConfigureAwait(false);
|
|
|
}
|
|
|
|
|
|
- /// <inheritdoc />
|
|
|
- protected override async Task AfterMetadataRefresh(Series item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
|
|
|
- {
|
|
|
- await base.AfterMetadataRefresh(item, refreshOptions, cancellationToken).ConfigureAwait(false);
|
|
|
+ return await base.RefreshMetadata(item, refreshOptions, cancellationToken).ConfigureAwait(false);
|
|
|
+ }
|
|
|
|
|
|
- RemoveObsoleteEpisodes(item);
|
|
|
- RemoveObsoleteSeasons(item);
|
|
|
- await CreateSeasonsAsync(item, cancellationToken).ConfigureAwait(false);
|
|
|
+ /// <inheritdoc />
|
|
|
+ protected override async Task AfterMetadataRefresh(Series item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ await base.AfterMetadataRefresh(item, refreshOptions, cancellationToken).ConfigureAwait(false);
|
|
|
+
|
|
|
+ RemoveObsoleteEpisodes(item);
|
|
|
+ RemoveObsoleteSeasons(item);
|
|
|
+ await CreateSeasonsAsync(item, cancellationToken).ConfigureAwait(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <inheritdoc />
|
|
|
+ protected override void MergeData(MetadataResult<Series> source, MetadataResult<Series> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
|
|
+ {
|
|
|
+ base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
|
|
+
|
|
|
+ var sourceItem = source.Item;
|
|
|
+ var targetItem = target.Item;
|
|
|
+
|
|
|
+ if (replaceData || string.IsNullOrEmpty(targetItem.AirTime))
|
|
|
+ {
|
|
|
+ targetItem.AirTime = sourceItem.AirTime;
|
|
|
}
|
|
|
|
|
|
- /// <inheritdoc />
|
|
|
- protected override void MergeData(MetadataResult<Series> source, MetadataResult<Series> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
|
|
+ if (replaceData || !targetItem.Status.HasValue)
|
|
|
{
|
|
|
- base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
|
|
+ targetItem.Status = sourceItem.Status;
|
|
|
+ }
|
|
|
|
|
|
- var sourceItem = source.Item;
|
|
|
- var targetItem = target.Item;
|
|
|
+ if (replaceData || targetItem.AirDays is null || targetItem.AirDays.Length == 0)
|
|
|
+ {
|
|
|
+ targetItem.AirDays = sourceItem.AirDays;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if (replaceData || string.IsNullOrEmpty(targetItem.AirTime))
|
|
|
+ private void RemoveObsoleteSeasons(Series series)
|
|
|
+ {
|
|
|
+ // TODO Legacy. It's not really "physical" seasons as any virtual seasons are always converted to non-virtual in CreateSeasonsAsync.
|
|
|
+ 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)
|
|
|
{
|
|
|
- targetItem.AirTime = sourceItem.AirTime;
|
|
|
+ physicalSeasonNumbers.Add(existingSeason.IndexNumber.Value);
|
|
|
}
|
|
|
-
|
|
|
- if (replaceData || !targetItem.Status.HasValue)
|
|
|
+ else if (existingSeason.LocationType == LocationType.Virtual)
|
|
|
{
|
|
|
- targetItem.Status = sourceItem.Status;
|
|
|
+ virtualSeasons.Add(existingSeason);
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- if (replaceData || targetItem.AirDays is null || targetItem.AirDays.Length == 0)
|
|
|
+ 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().Count == 0)
|
|
|
{
|
|
|
- targetItem.AirDays = sourceItem.AirDays;
|
|
|
+ Logger.LogInformation("Removing virtual season {SeasonNumber} in series {SeriesName}", virtualSeason.IndexNumber, series.Name);
|
|
|
+
|
|
|
+ LibraryManager.DeleteItem(
|
|
|
+ virtualSeason,
|
|
|
+ new DeleteOptions
|
|
|
+ {
|
|
|
+ // Internal metadata paths are removed regardless of this.
|
|
|
+ DeleteFileLocation = false
|
|
|
+ },
|
|
|
+ false);
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- private void RemoveObsoleteSeasons(Series series)
|
|
|
+ private void RemoveObsoleteEpisodes(Series series)
|
|
|
+ {
|
|
|
+ var episodesBySeason = series.GetEpisodes(null, new DtoOptions(), true)
|
|
|
+ .OfType<Episode>()
|
|
|
+ .GroupBy(e => e.ParentIndexNumber)
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ foreach (var seasonEpisodes in episodesBySeason)
|
|
|
{
|
|
|
- // TODO Legacy. It's not really "physical" seasons as any virtual seasons are always converted to non-virtual in CreateSeasonsAsync.
|
|
|
- var physicalSeasonNumbers = new HashSet<int>();
|
|
|
- var virtualSeasons = new List<Season>();
|
|
|
- foreach (var existingSeason in series.Children.OfType<Season>())
|
|
|
+ List<Episode> nonPhysicalEpisodes = [];
|
|
|
+ List<Episode> physicalEpisodes = [];
|
|
|
+ foreach (var episode in seasonEpisodes)
|
|
|
{
|
|
|
- if (existingSeason.LocationType != LocationType.Virtual && existingSeason.IndexNumber.HasValue)
|
|
|
+ if (episode.IsVirtualItem || episode.IsMissingEpisode)
|
|
|
{
|
|
|
- physicalSeasonNumbers.Add(existingSeason.IndexNumber.Value);
|
|
|
- }
|
|
|
- else if (existingSeason.LocationType == LocationType.Virtual)
|
|
|
- {
|
|
|
- virtualSeasons.Add(existingSeason);
|
|
|
+ nonPhysicalEpisodes.Add(episode);
|
|
|
+ continue;
|
|
|
}
|
|
|
+
|
|
|
+ physicalEpisodes.Add(episode);
|
|
|
}
|
|
|
|
|
|
- foreach (var virtualSeason in virtualSeasons)
|
|
|
+ // Only consider non-physical episodes
|
|
|
+ foreach (var episode in nonPhysicalEpisodes)
|
|
|
{
|
|
|
- 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().Count == 0)
|
|
|
- {
|
|
|
- Logger.LogInformation("Removing virtual season {SeasonNumber} in series {SeriesName}", virtualSeason.IndexNumber, series.Name);
|
|
|
+ // Episodes without an episode number are practically orphaned and should be deleted
|
|
|
+ // Episodes with a physical equivalent should be deleted (they are no longer missing)
|
|
|
+ var shouldKeep = episode.IndexNumber.HasValue && !physicalEpisodes.Any(e => e.ContainsEpisodeNumber(episode.IndexNumber.Value));
|
|
|
|
|
|
- LibraryManager.DeleteItem(
|
|
|
- virtualSeason,
|
|
|
- new DeleteOptions
|
|
|
- {
|
|
|
- // Internal metadata paths are removed regardless of this.
|
|
|
- DeleteFileLocation = false
|
|
|
- },
|
|
|
- false);
|
|
|
+ if (shouldKeep)
|
|
|
+ {
|
|
|
+ continue;
|
|
|
}
|
|
|
+
|
|
|
+ DeleteEpisode(episode);
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- private void RemoveObsoleteEpisodes(Series series)
|
|
|
- {
|
|
|
- var episodesBySeason = series.GetEpisodes(null, new DtoOptions(), true)
|
|
|
- .OfType<Episode>()
|
|
|
- .GroupBy(e => e.ParentIndexNumber)
|
|
|
- .ToList();
|
|
|
+ private void DeleteEpisode(Episode episode)
|
|
|
+ {
|
|
|
+ Logger.LogInformation(
|
|
|
+ "Removing virtual episode S{SeasonNumber}E{EpisodeNumber} in series {SeriesName}",
|
|
|
+ episode.ParentIndexNumber,
|
|
|
+ episode.IndexNumber,
|
|
|
+ episode.SeriesName);
|
|
|
|
|
|
- foreach (var seasonEpisodes in episodesBySeason)
|
|
|
+ LibraryManager.DeleteItem(
|
|
|
+ episode,
|
|
|
+ new DeleteOptions
|
|
|
{
|
|
|
- List<Episode> nonPhysicalEpisodes = [];
|
|
|
- List<Episode> physicalEpisodes = [];
|
|
|
- foreach (var episode in seasonEpisodes)
|
|
|
- {
|
|
|
- if (episode.IsVirtualItem || episode.IsMissingEpisode)
|
|
|
- {
|
|
|
- nonPhysicalEpisodes.Add(episode);
|
|
|
- continue;
|
|
|
- }
|
|
|
+ // Internal metadata paths are removed regardless of this.
|
|
|
+ DeleteFileLocation = false
|
|
|
+ },
|
|
|
+ false);
|
|
|
+ }
|
|
|
|
|
|
- physicalEpisodes.Add(episode);
|
|
|
- }
|
|
|
+ /// <summary>
|
|
|
+ /// Creates seasons for all episodes if they don't exist.
|
|
|
+ /// 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 CreateSeasonsAsync(Series series, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ var seriesChildren = series.GetRecursiveChildren(i => i is Episode || i is Season);
|
|
|
+ var seasons = seriesChildren.OfType<Season>().ToList();
|
|
|
+ var uniqueSeasonNumbers = seriesChildren
|
|
|
+ .OfType<Episode>()
|
|
|
+ .Select(e => e.ParentIndexNumber >= 0 ? e.ParentIndexNumber : null)
|
|
|
+ .Distinct();
|
|
|
|
|
|
- // Only consider non-physical episodes
|
|
|
- foreach (var episode in nonPhysicalEpisodes)
|
|
|
+ // Loop through the unique season numbers
|
|
|
+ foreach (var seasonNumber in uniqueSeasonNumbers)
|
|
|
+ {
|
|
|
+ // Null season numbers will have a 'dummy' season created because seasons are always required.
|
|
|
+ var existingSeason = seasons.FirstOrDefault(i => i.IndexNumber == seasonNumber);
|
|
|
+ if (existingSeason is null)
|
|
|
+ {
|
|
|
+ var seasonName = GetValidSeasonNameForSeries(series, null, seasonNumber);
|
|
|
+ await CreateSeasonAsync(series, seasonName, seasonNumber, cancellationToken).ConfigureAwait(false);
|
|
|
+ }
|
|
|
+ else if (existingSeason.IsVirtualItem)
|
|
|
+ {
|
|
|
+ var episodeCount = seriesChildren.OfType<Episode>().Count(e => e.ParentIndexNumber == seasonNumber && !e.IsMissingEpisode);
|
|
|
+ if (episodeCount > 0)
|
|
|
{
|
|
|
- // Episodes without an episode number are practically orphaned and should be deleted
|
|
|
- // Episodes with a physical equivalent should be deleted (they are no longer missing)
|
|
|
- var shouldKeep = episode.IndexNumber.HasValue && !physicalEpisodes.Any(e => e.ContainsEpisodeNumber(episode.IndexNumber.Value));
|
|
|
-
|
|
|
- if (shouldKeep)
|
|
|
- {
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- DeleteEpisode(episode);
|
|
|
+ existingSeason.IsVirtualItem = false;
|
|
|
+ await existingSeason.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- private void DeleteEpisode(Episode episode)
|
|
|
- {
|
|
|
- Logger.LogInformation(
|
|
|
- "Removing virtual episode S{SeasonNumber}E{EpisodeNumber} in series {SeriesName}",
|
|
|
- episode.ParentIndexNumber,
|
|
|
- episode.IndexNumber,
|
|
|
- episode.SeriesName);
|
|
|
-
|
|
|
- LibraryManager.DeleteItem(
|
|
|
- episode,
|
|
|
- new DeleteOptions
|
|
|
- {
|
|
|
- // Internal metadata paths are removed regardless of this.
|
|
|
- DeleteFileLocation = false
|
|
|
- },
|
|
|
- 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="seasonName">The season name.</param>
|
|
|
+ /// <param name="seasonNumber">The season number.</param>
|
|
|
+ /// <param name="cancellationToken">The cancellation token.</param>
|
|
|
+ /// <returns>The newly created season.</returns>
|
|
|
+ private async Task CreateSeasonAsync(
|
|
|
+ Series series,
|
|
|
+ string? seasonName,
|
|
|
+ int? seasonNumber,
|
|
|
+ CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ Logger.LogInformation("Creating Season {SeasonName} entry for {SeriesName}", seasonName, series.Name);
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Creates seasons for all episodes if they don't exist.
|
|
|
- /// 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 CreateSeasonsAsync(Series series, CancellationToken cancellationToken)
|
|
|
+ var season = new Season
|
|
|
{
|
|
|
- var seriesChildren = series.GetRecursiveChildren(i => i is Episode || i is Season);
|
|
|
- var seasons = seriesChildren.OfType<Season>().ToList();
|
|
|
- var uniqueSeasonNumbers = seriesChildren
|
|
|
- .OfType<Episode>()
|
|
|
- .Select(e => e.ParentIndexNumber >= 0 ? e.ParentIndexNumber : null)
|
|
|
- .Distinct();
|
|
|
+ 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,
|
|
|
+ SeriesPresentationUniqueKey = series.GetPresentationUniqueKey()
|
|
|
+ };
|
|
|
|
|
|
- // Loop through the unique season numbers
|
|
|
- foreach (var seasonNumber in uniqueSeasonNumbers)
|
|
|
- {
|
|
|
- // Null season numbers will have a 'dummy' season created because seasons are always required.
|
|
|
- var existingSeason = seasons.FirstOrDefault(i => i.IndexNumber == seasonNumber);
|
|
|
- if (existingSeason is null)
|
|
|
- {
|
|
|
- var seasonName = GetValidSeasonNameForSeries(series, null, seasonNumber);
|
|
|
- await CreateSeasonAsync(series, seasonName, seasonNumber, cancellationToken).ConfigureAwait(false);
|
|
|
- }
|
|
|
- else if (existingSeason.IsVirtualItem)
|
|
|
- {
|
|
|
- var episodeCount = seriesChildren.OfType<Episode>().Count(e => e.ParentIndexNumber == seasonNumber && !e.IsMissingEpisode);
|
|
|
- if (episodeCount > 0)
|
|
|
- {
|
|
|
- existingSeason.IsVirtualItem = false;
|
|
|
- await existingSeason.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ series.AddChild(season);
|
|
|
+ await season.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem)), 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="seasonName">The season name.</param>
|
|
|
- /// <param name="seasonNumber">The season number.</param>
|
|
|
- /// <param name="cancellationToken">The cancellation token.</param>
|
|
|
- /// <returns>The newly created season.</returns>
|
|
|
- private async Task CreateSeasonAsync(
|
|
|
- Series series,
|
|
|
- string? seasonName,
|
|
|
- int? seasonNumber,
|
|
|
- CancellationToken cancellationToken)
|
|
|
+ private string GetValidSeasonNameForSeries(Series series, string? seasonName, int? seasonNumber)
|
|
|
+ {
|
|
|
+ if (string.IsNullOrEmpty(seasonName))
|
|
|
{
|
|
|
- Logger.LogInformation("Creating Season {SeasonName} entry for {SeriesName}", seasonName, series.Name);
|
|
|
-
|
|
|
- var season = new Season
|
|
|
+ seasonName = seasonNumber switch
|
|
|
{
|
|
|
- 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,
|
|
|
- SeriesPresentationUniqueKey = series.GetPresentationUniqueKey()
|
|
|
+ null => _localizationManager.GetLocalizedString("NameSeasonUnknown"),
|
|
|
+ 0 => LibraryManager.GetLibraryOptions(series).SeasonZeroDisplayName,
|
|
|
+ _ => string.Format(CultureInfo.InvariantCulture, _localizationManager.GetLocalizedString("NameSeasonNumber"), seasonNumber.Value)
|
|
|
};
|
|
|
-
|
|
|
- series.AddChild(season);
|
|
|
- await season.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem)), cancellationToken).ConfigureAwait(false);
|
|
|
}
|
|
|
|
|
|
- private string GetValidSeasonNameForSeries(Series series, string? seasonName, int? seasonNumber)
|
|
|
- {
|
|
|
- if (string.IsNullOrEmpty(seasonName))
|
|
|
- {
|
|
|
- seasonName = seasonNumber switch
|
|
|
- {
|
|
|
- null => _localizationManager.GetLocalizedString("NameSeasonUnknown"),
|
|
|
- 0 => LibraryManager.GetLibraryOptions(series).SeasonZeroDisplayName,
|
|
|
- _ => string.Format(CultureInfo.InvariantCulture, _localizationManager.GetLocalizedString("NameSeasonNumber"), seasonNumber.Value)
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- return seasonName;
|
|
|
- }
|
|
|
+ return seasonName;
|
|
|
}
|
|
|
}
|