Esempio n. 1
0
        public async Task <IEnumerable <RemoteSearchResult> > GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
        {
            var list = new List <RemoteSearchResult>();

            string seriesTvdbId;

            searchInfo.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out seriesTvdbId);

            if (!string.IsNullOrEmpty(seriesTvdbId))
            {
                await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesTvdbId, searchInfo.MetadataLanguage,
                                                                  cancellationToken).ConfigureAwait(false);

                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesTvdbId);

                try
                {
                    var item = FetchEpisodeData(searchInfo, seriesDataPath, cancellationToken);

                    if (item != null)
                    {
                        list.Add(new RemoteSearchResult
                        {
                            IndexNumber        = item.IndexNumber,
                            Name               = item.Name,
                            ParentIndexNumber  = item.ParentIndexNumber,
                            PremiereDate       = item.PremiereDate,
                            ProductionYear     = item.ProductionYear,
                            ProviderIds        = item.ProviderIds,
                            SearchProviderName = Name,
                            IndexNumberEnd     = item.IndexNumberEnd
                        });
                    }
                }
                catch (FileNotFoundException)
                {
                    // Don't fail the provider because this will just keep on going and going.
                }
            }

            return(list);
        }
        public async Task <IEnumerable <RemoteImageInfo> > GetImages(IHasImages item, CancellationToken cancellationToken)
        {
            var season = (Season)item;
            var series = season.Series;

            var seriesId = series != null?series.GetProviderId(MetadataProviders.Tvdb) : null;

            if (!string.IsNullOrEmpty(seriesId) && season.IndexNumber.HasValue)
            {
                await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesId, series.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false);

                // Process images
                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesId);

                var path = Path.Combine(seriesDataPath, "banners.xml");

                var identity = season.Identities.OfType <SeasonIdentity>()
                               .FirstOrDefault(id => id.Type == MetadataProviders.Tvdb.ToString());

                var seasonNumber = season.IndexNumber.Value;

                if (identity != null)
                {
                    seasonNumber = AdjustForSeriesOffset(series, identity.SeasonIndex);
                }

                try
                {
                    return(GetImages(path, item.GetPreferredMetadataLanguage(), seasonNumber, cancellationToken));
                }
                catch (FileNotFoundException)
                {
                    // No tvdb data yet. Don't blow up
                }
                catch (DirectoryNotFoundException)
                {
                    // No tvdb data yet. Don't blow up
                }
            }

            return(new RemoteImageInfo[] { });
        }
Esempio n. 3
0
        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
        {
            if (!TvdbSeriesProvider.Current.GetTvDbOptions().EnableAutomaticUpdates)
            {
                return(false);
            }

            var episode = (Episode)item;
            var series  = episode.Series;

            if (series != null && TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
            {
                // Process images
                var seriesXmlPath = TvdbSeriesProvider.Current.GetSeriesXmlPath(series.ProviderIds, series.GetPreferredMetadataLanguage());

                return(_fileSystem.GetLastWriteTimeUtc(seriesXmlPath) > item.DateLastRefreshed);
            }

            return(false);
        }
        public async Task <MetadataResult <Episode> > GetMetadata(EpisodeInfo searchInfo, CancellationToken cancellationToken)
        {
            var result = new MetadataResult <Episode>();

            result.QueriedById = true;

            if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds) &&
                (searchInfo.IndexNumber.HasValue || searchInfo.PremiereDate.HasValue))
            {
                await TvdbSeriesProvider.Current.EnsureSeriesInfo(searchInfo.SeriesProviderIds, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false);

                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, searchInfo.SeriesProviderIds);

                var searchNumbers = new EpisodeNumbers();
                if (searchInfo.IndexNumber.HasValue)
                {
                    searchNumbers.EpisodeNumber = searchInfo.IndexNumber.Value;
                }
                searchNumbers.SeasonNumber     = searchInfo.ParentIndexNumber;
                searchNumbers.EpisodeNumberEnd = searchInfo.IndexNumberEnd ?? searchNumbers.EpisodeNumber;

                try
                {
                    result = FetchEpisodeData(searchInfo, searchNumbers, seriesDataPath, cancellationToken);
                }
                catch (FileNotFoundException)
                {
                    // Don't fail the provider because this will just keep on going and going.
                }
                catch (DirectoryNotFoundException)
                {
                    // Don't fail the provider because this will just keep on going and going.
                }
            }
            else
            {
                _logger.Debug("No series identity found for {0}", searchInfo.Name);
            }

            return(result);
        }
Esempio n. 5
0
        public Task <IEnumerable <RemoteImageInfo> > GetAllImages(BaseItem item, CancellationToken cancellationToken)
        {
            var episode = (Episode)item;

            var seriesId = episode.Series != null?episode.Series.GetProviderId(MetadataProviders.Tvdb) : null;

            if (!string.IsNullOrEmpty(seriesId))
            {
                // Process images
                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesId);

                var files = TvdbEpisodeProvider.Current.GetEpisodeXmlFiles(episode, seriesDataPath);

                var result = files.Select(i => GetImageInfo(i, cancellationToken))
                             .Where(i => i != null);

                return(Task.FromResult(result));
            }

            return(Task.FromResult <IEnumerable <RemoteImageInfo> >(new RemoteImageInfo[] { }));
        }
        public Task <IEnumerable <RemoteImageInfo> > GetImages(IHasMetadata item, CancellationToken cancellationToken)
        {
            var episode = (Episode)item;
            var series  = episode.Series;

            if (series != null && TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
            {
                // Process images
                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, series.ProviderIds);

                var nodes = TvdbEpisodeProvider.Current.GetEpisodeXmlNodes(seriesDataPath, episode.GetLookupInfo());

                var result = nodes.Select(i => GetImageInfo(i, cancellationToken))
                             .Where(i => i != null)
                             .ToList();

                return(Task.FromResult <IEnumerable <RemoteImageInfo> >(result));
            }

            return(Task.FromResult <IEnumerable <RemoteImageInfo> >(new RemoteImageInfo[] { }));
        }
Esempio n. 7
0
        public bool HasChanged(IHasMetadata item, MetadataStatus status, IDirectoryService directoryService)
        {
            if (!TvdbSeriesProvider.Current.GetTvDbOptions().EnableAutomaticUpdates)
            {
                return(false);
            }

            var tvdbId = item.GetProviderId(MetadataProviders.Tvdb);

            if (!String.IsNullOrEmpty(tvdbId))
            {
                // Process images
                var imagesXmlPath = Path.Combine(TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, tvdbId), "banners.xml");

                var fileInfo = _fileSystem.GetFileInfo(imagesXmlPath);

                return(fileInfo.Exists && _fileSystem.GetLastWriteTimeUtc(fileInfo) > (status.DateLastMetadataRefresh ?? DateTime.MinValue));
            }

            return(false);
        }
        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
        {
            if (!_config.Configuration.EnableTvDbUpdates)
            {
                return(false);
            }

            var tvdbId = item.GetProviderId(MetadataProviders.Tvdb);

            if (!String.IsNullOrEmpty(tvdbId))
            {
                // Process images
                var imagesXmlPath = Path.Combine(TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, tvdbId), "banners.xml");

                var fileInfo = new FileInfo(imagesXmlPath);

                return(fileInfo.Exists && _fileSystem.GetLastWriteTimeUtc(fileInfo) > date);
            }

            return(false);
        }
Esempio n. 9
0
        protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
        {
            var episode = (Episode)item;

            var seriesId = episode.Series != null?episode.Series.GetProviderId(MetadataProviders.Tvdb) : null;

            if (!string.IsNullOrEmpty(seriesId))
            {
                // Process images
                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId);

                var files = GetEpisodeXmlFiles(episode, seriesDataPath);

                if (files.Count > 0)
                {
                    return(files.Select(i => _fileSystem.GetLastWriteTimeUtc(i)).Max() > providerInfo.LastRefreshed);
                }
            }

            return(false);
        }
Esempio n. 10
0
        public Task <IEnumerable <RemoteImageInfo> > GetImages(IHasImages item, CancellationToken cancellationToken)
        {
            var episode = (Episode)item;
            var series  = episode.Series;

            if (series != null && TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
            {
                // Process images
                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, series.ProviderIds);
                var indexOffset    = TvdbSeriesProvider.GetSeriesOffset(series.ProviderIds) ?? 0;

                var files = TvdbEpisodeProvider.Current.GetEpisodeXmlFiles(episode.ParentIndexNumber + indexOffset, episode.IndexNumber, episode.IndexNumberEnd, seriesDataPath);

                var result = files.Select(i => GetImageInfo(i, cancellationToken))
                             .Where(i => i != null);

                return(Task.FromResult(result));
            }

            return(Task.FromResult <IEnumerable <RemoteImageInfo> >(new RemoteImageInfo[] { }));
        }
Esempio n. 11
0
        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
        {
            // Only enable for virtual items
            if (item.LocationType != LocationType.Virtual)
            {
                return(false);
            }

            var episode = (Episode)item;
            var series  = episode.Series;

            if (series != null && TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
            {
                // Process images
                var seriesXmlPath = TvdbSeriesProvider.Current.GetSeriesXmlPath(series.ProviderIds, series.GetPreferredMetadataLanguage());

                return(_fileSystem.GetLastWriteTimeUtc(seriesXmlPath) > date);
            }

            return(false);
        }
        public async Task <IEnumerable <RemoteSearchResult> > GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
        {
            var list = new List <RemoteSearchResult>();

            var identity = searchInfo.Identities.FirstOrDefault(id => id.Type == MetadataProviders.Tvdb.ToString()) ?? await FindIdentity(searchInfo).ConfigureAwait(false);

            if (identity != null)
            {
                await TvdbSeriesProvider.Current.EnsureSeriesInfo(identity.SeriesId, searchInfo.MetadataLanguage,
                                                                  cancellationToken).ConfigureAwait(false);

                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, identity.SeriesId);

                try
                {
                    var item = FetchEpisodeData(searchInfo, identity, seriesDataPath, searchInfo.SeriesProviderIds, cancellationToken);

                    if (item != null)
                    {
                        list.Add(new RemoteSearchResult
                        {
                            IndexNumber        = item.IndexNumber,
                            Name               = item.Name,
                            ParentIndexNumber  = item.ParentIndexNumber,
                            PremiereDate       = item.PremiereDate,
                            ProductionYear     = item.ProductionYear,
                            ProviderIds        = item.ProviderIds,
                            SearchProviderName = Name,
                            IndexNumberEnd     = item.IndexNumberEnd
                        });
                    }
                }
                catch (FileNotFoundException)
                {
                    // Don't fail the provider because this will just keep on going and going.
                }
            }

            return(list);
        }
Esempio n. 13
0
        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
        {
            if (!TvdbSeriesProvider.Current.GetTvDbOptions().EnableAutomaticUpdates)
            {
                return(false);
            }

            var season = (Season)item;
            var series = season.Series;

            if (series != null && TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
            {
                // Process images
                var imagesXmlPath = Path.Combine(TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, series.ProviderIds), "banners.xml");

                var fileInfo = _fileSystem.GetFileInfo(imagesXmlPath);

                return(fileInfo.Exists && _fileSystem.GetLastWriteTimeUtc(fileInfo) > item.DateLastRefreshed);
            }

            return(false);
        }
        public async Task <IEnumerable <RemoteImageInfo> > GetImages(IHasImages item, CancellationToken cancellationToken)
        {
            var series   = (Series)item;
            var seriesId = series.GetProviderId(MetadataProviders.Tvdb);

            if (!string.IsNullOrEmpty(seriesId))
            {
                var language = item.GetPreferredMetadataLanguage();

                await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesId, language, cancellationToken).ConfigureAwait(false);

                // Process images
                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesId);

                var path = Path.Combine(seriesDataPath, "banners.xml");

                try
                {
                    var seriesOffset = TvdbSeriesProvider.GetSeriesOffset(series.ProviderIds);
                    if (seriesOffset != null && seriesOffset.Value != 0)
                    {
                        return(TvdbSeasonImageProvider.GetImages(path, language, seriesOffset.Value + 1, cancellationToken));
                    }

                    return(GetImages(path, language, cancellationToken));
                }
                catch (FileNotFoundException)
                {
                    // No tvdb data yet. Don't blow up
                }
                catch (DirectoryNotFoundException)
                {
                    // No tvdb data yet. Don't blow up
                }
            }

            return(new RemoteImageInfo[] { });
        }
        public async Task <MetadataResult <Episode> > GetMetadata(EpisodeInfo searchInfo, CancellationToken cancellationToken)
        {
            var identity = searchInfo.Identities.FirstOrDefault(id => id.Type == MetadataProviders.Tvdb.ToString()) ?? await FindIdentity(searchInfo).ConfigureAwait(false);

            var result = new MetadataResult <Episode>();

            if (identity != null)
            {
                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, identity.SeriesId);

                try
                {
                    result.Item        = FetchEpisodeData(searchInfo, identity, seriesDataPath, searchInfo.SeriesProviderIds, cancellationToken);
                    result.HasMetadata = result.Item != null;
                }
                catch (FileNotFoundException)
                {
                    // Don't fail the provider because this will just keep on going and going.
                }
            }

            return(result);
        }
Esempio n. 16
0
        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
        {
            // Only enable for virtual items
            if (item.LocationType != LocationType.Virtual)
            {
                return(false);
            }

            var episode = (Episode)item;
            var series  = episode.Series;

            if (series != null && TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
            {
                // Process images
                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, series.ProviderIds);

                var files = GetEpisodeXmlFiles(episode.ParentIndexNumber, episode.IndexNumber, episode.IndexNumberEnd, seriesDataPath);

                return(files.Any(i => _fileSystem.GetLastWriteTimeUtc(i) > date));
            }

            return(false);
        }
Esempio n. 17
0
        public async Task <MetadataResult <Episode> > GetMetadata(EpisodeInfo searchInfo, CancellationToken cancellationToken)
        {
            var identity = Identity.ParseIdentity(searchInfo.GetProviderId(FullIdKey));

            if (identity == null)
            {
                await Identify(searchInfo).ConfigureAwait(false);

                identity = Identity.ParseIdentity(searchInfo.GetProviderId(FullIdKey));
            }

            var result = new MetadataResult <Episode>();

            if (identity != null)
            {
                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, identity.Value.SeriesId);

                try
                {
                    result = FetchEpisodeData(searchInfo, identity.Value, seriesDataPath, searchInfo.SeriesProviderIds, cancellationToken);
                }
                catch (FileNotFoundException)
                {
                    // Don't fail the provider because this will just keep on going and going.
                }
                catch (DirectoryNotFoundException)
                {
                    // Don't fail the provider because this will just keep on going and going.
                }
            }
            else
            {
                _logger.Debug("No series identity found for {0}", searchInfo.Name);
            }

            return(result);
        }
Esempio n. 18
0
        public async Task <MetadataResult <Episode> > GetMetadata(EpisodeInfo searchInfo, CancellationToken cancellationToken)
        {
            var result = new MetadataResult <Episode>();

            result.QueriedById = true;

            if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds) &&
                (searchInfo.IndexNumber.HasValue || searchInfo.PremiereDate.HasValue))
            {
                var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(searchInfo.SeriesProviderIds, null, null, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false);

                if (string.IsNullOrEmpty(seriesDataPath))
                {
                    return(result);
                }

                try
                {
                    result = FetchEpisodeData(searchInfo, seriesDataPath, cancellationToken);
                }
                catch (FileNotFoundException)
                {
                    // Don't fail the provider because this will just keep on going and going.
                }
                catch (IOException)
                {
                    // Don't fail the provider because this will just keep on going and going.
                }
            }
            else
            {
                _logger.LogDebug("No series identity found for {0}", searchInfo.Name);
            }

            return(result);
        }
Esempio n. 19
0
        /// <summary>
        /// Removes the virtual entry after a corresponding physical version has been added
        /// </summary>
        private async Task <bool> RemoveObsoleteOrMissingEpisodes(IEnumerable <Series> series,
                                                                  IEnumerable <Tuple <int, int> > episodeLookup)
        {
            var existingEpisodes = (from s in series
                                    let seasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1)
                                                       from c in s.GetRecursiveChildren().OfType <Episode>()
                                                       select new { SeasonOffset = seasonOffset, Episode = c })
                                   .ToList();

            var physicalEpisodes = existingEpisodes
                                   .Where(i => i.Episode.LocationType != LocationType.Virtual)
                                   .ToList();

            var virtualEpisodes = existingEpisodes
                                  .Where(i => i.Episode.LocationType == LocationType.Virtual)
                                  .ToList();

            var episodesToRemove = virtualEpisodes
                                   .Where(i =>
            {
                if (i.Episode.IndexNumber.HasValue && i.Episode.ParentIndexNumber.HasValue)
                {
                    var seasonNumber  = i.Episode.ParentIndexNumber.Value + i.SeasonOffset;
                    var episodeNumber = i.Episode.IndexNumber.Value;

                    // If there's a physical episode with the same season and episode number, delete it
                    if (physicalEpisodes.Any(p =>
                                             p.Episode.ParentIndexNumber.HasValue && (p.Episode.ParentIndexNumber.Value + p.SeasonOffset) == seasonNumber &&
                                             p.Episode.ContainsEpisodeNumber(episodeNumber)))
                    {
                        return(true);
                    }

                    // If the episode no longer exists in the remote lookup, delete it
                    if (!episodeLookup.Any(e => e.Item1 == seasonNumber && e.Item2 == episodeNumber))
                    {
                        return(true);
                    }

                    return(false);
                }

                return(true);
            })
                                   .ToList();

            var hasChanges = false;

            foreach (var episodeToRemove in episodesToRemove.Select(e => e.Episode))
            {
                _logger.Info("Removing missing/unaired episode {0} {1}x{2}", episodeToRemove.Series.Name, episodeToRemove.ParentIndexNumber, episodeToRemove.IndexNumber);

                await episodeToRemove.Delete(new DeleteOptions
                {
                    DeleteFileLocation = true
                }).ConfigureAwait(false);

                hasChanges = true;
            }

            return(hasChanges);
        }
Esempio n. 20
0
        private Series DetermineAppropriateSeries(IEnumerable <Series> series, int seasonNumber)
        {
            var seriesAndOffsets = series.Select(s => new { Series = s, SeasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1) }).ToList();

            var bestMatch = seriesAndOffsets.FirstOrDefault(s => s.Series.GetRecursiveChildren().OfType <Season>().Any(season => (season.IndexNumber + s.SeasonOffset) == seasonNumber)) ??
                            seriesAndOffsets.FirstOrDefault(s => s.Series.GetRecursiveChildren().OfType <Season>().Any(season => (season.IndexNumber + s.SeasonOffset) == 1)) ??
                            seriesAndOffsets.OrderBy(s => s.Series.GetRecursiveChildren().OfType <Season>().Select(season => season.IndexNumber + s.SeasonOffset).Min()).First();

            return(bestMatch.Series);
        }
Esempio n. 21
0
        /// <summary>
        /// Adds the missing episodes.
        /// </summary>
        /// <param name="series">The series.</param>
        /// <param name="seriesHasBadData">if set to <c>true</c> [series has bad data].</param>
        /// <param name="seriesDataPath">The series data path.</param>
        /// <param name="episodeLookup">The episode lookup.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>Task.</returns>
        private async Task <bool> AddMissingEpisodes(List <Series> series,
                                                     bool seriesHasBadData,
                                                     string seriesDataPath,
                                                     IEnumerable <Tuple <int, int> > episodeLookup,
                                                     CancellationToken cancellationToken)
        {
            var existingEpisodes = (from s in series
                                    let seasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1)
                                                       from c in s.GetRecursiveChildren().OfType <Episode>()
                                                       select new Tuple <int, Episode>((c.ParentIndexNumber ?? 0) + seasonOffset, c))
                                   .ToList();

            var lookup = episodeLookup as IList <Tuple <int, int> > ?? episodeLookup.ToList();

            var seasonCounts = (from e in lookup
                                group e by e.Item1 into g
                                select g)
                               .ToDictionary(g => g.Key, g => g.Count());

            var hasChanges = false;

            foreach (var tuple in lookup)
            {
                if (tuple.Item1 <= 0)
                {
                    // Ignore season zeros
                    continue;
                }

                if (tuple.Item2 <= 0)
                {
                    // Ignore episode zeros
                    continue;
                }

                var existingEpisode = GetExistingEpisode(existingEpisodes, seasonCounts, tuple);

                if (existingEpisode != null)
                {
                    continue;
                }

                var airDate = GetAirDate(seriesDataPath, tuple.Item1, tuple.Item2);

                if (!airDate.HasValue)
                {
                    continue;
                }
                var now = DateTime.UtcNow;

                var targetSeries = DetermineAppropriateSeries(series, tuple.Item1);
                var seasonOffset = TvdbSeriesProvider.GetSeriesOffset(targetSeries.ProviderIds) ?? ((targetSeries.AnimeSeriesIndex ?? 1) - 1);

                if (airDate.Value < now)
                {
                    // Be conservative here to avoid creating missing episodes for ones they already have
                    if (!seriesHasBadData)
                    {
                        // tvdb has a lot of nearly blank episodes
                        _logger.Info("Creating virtual missing episode {0} {1}x{2}", targetSeries.Name, tuple.Item1, tuple.Item2);
                        await AddEpisode(targetSeries, tuple.Item1 - seasonOffset, tuple.Item2, cancellationToken).ConfigureAwait(false);

                        hasChanges = true;
                    }
                }
                else if (airDate.Value > now)
                {
                    // tvdb has a lot of nearly blank episodes
                    _logger.Info("Creating virtual unaired episode {0} {1}x{2}", targetSeries.Name, tuple.Item1, tuple.Item2);
                    await AddEpisode(targetSeries, tuple.Item1 - seasonOffset, tuple.Item2, cancellationToken).ConfigureAwait(false);

                    hasChanges = true;
                }
            }

            return(hasChanges);
        }
        /// <summary>
        /// Removes the obsolete or missing seasons.
        /// </summary>
        /// <param name="series">The series.</param>
        /// <param name="episodeLookup">The episode lookup.</param>
        /// <param name="forceRemoveAll">if set to <c>true</c> [force remove all].</param>
        /// <returns>Task{System.Boolean}.</returns>
        private async Task <bool> RemoveObsoleteOrMissingSeasons(IEnumerable <Series> series,
                                                                 IEnumerable <Tuple <int, int> > episodeLookup,
                                                                 bool forceRemoveAll)
        {
            var existingSeasons = (from s in series
                                   let seasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1)
                                                      from c in s.Children.OfType <Season>()
                                                      select new { SeasonOffset = seasonOffset, Season = c })
                                  .ToList();

            var physicalSeasons = existingSeasons
                                  .Where(i => i.Season.LocationType != LocationType.Virtual)
                                  .ToList();

            var virtualSeasons = existingSeasons
                                 .Where(i => i.Season.LocationType == LocationType.Virtual)
                                 .ToList();

            var seasonsToRemove = virtualSeasons
                                  .Where(i =>
            {
                if (forceRemoveAll)
                {
                    return(true);
                }

                if (i.Season.IndexNumber.HasValue)
                {
                    var seasonNumber = i.Season.IndexNumber.Value + i.SeasonOffset;

                    // If there's a physical season with the same number, delete it
                    if (physicalSeasons.Any(p => p.Season.IndexNumber.HasValue && (p.Season.IndexNumber.Value + p.SeasonOffset) == seasonNumber))
                    {
                        return(true);
                    }

                    // If the season no longer exists in the remote lookup, delete it
                    if (episodeLookup.All(e => e.Item1 != seasonNumber))
                    {
                        return(true);
                    }

                    return(false);
                }

                return(true);
            })
                                  .ToList();

            var hasChanges = false;

            foreach (var seasonToRemove in seasonsToRemove.Select(s => s.Season))
            {
                _logger.Info("Removing virtual season {0} {1}", seasonToRemove.Series.Name, seasonToRemove.IndexNumber);

                await _libraryManager.DeleteItem(seasonToRemove).ConfigureAwait(false);

                hasChanges = true;
            }

            return(hasChanges);
        }
Esempio n. 23
0
        private async Task Run(IGrouping <string, Series> group, bool addNewItems, CancellationToken cancellationToken)
        {
            var seriesList = group.ToList();
            var tvdbId     = seriesList
                             .Select(i => i.GetProviderId(MetadataProviders.Tvdb))
                             .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));

            // Todo: Support series by imdb id
            var seriesProviderIds = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);

            seriesProviderIds[MetadataProviders.Tvdb.ToString()] = tvdbId;

            var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesProviderIds);

            // Doesn't have required provider id's
            if (string.IsNullOrWhiteSpace(seriesDataPath))
            {
                return;
            }

            var episodeFiles = _fileSystem.GetFilePaths(seriesDataPath)
                               .Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase))
                               .Select(Path.GetFileNameWithoutExtension)
                               .Where(i => i.StartsWith("episode-", StringComparison.OrdinalIgnoreCase))
                               .ToList();

            var episodeLookup = episodeFiles
                                .Select(i =>
            {
                var parts = i.Split('-');

                if (parts.Length == 3)
                {
                    int seasonNumber;

                    if (int.TryParse(parts[1], NumberStyles.Integer, _usCulture, out seasonNumber))
                    {
                        int episodeNumber;

                        if (int.TryParse(parts[2], NumberStyles.Integer, _usCulture, out episodeNumber))
                        {
                            return(new Tuple <int, int>(seasonNumber, episodeNumber));
                        }
                    }
                }

                return(new Tuple <int, int>(-1, -1));
            })
                                .Where(i => i.Item1 != -1 && i.Item2 != -1)
                                .ToList();

            var hasBadData = HasInvalidContent(seriesList);

            // Be conservative here to avoid creating missing episodes for ones they already have
            var addMissingEpisodes = !hasBadData && seriesList.All(i => _libraryManager.GetLibraryOptions(i).ImportMissingEpisodes);

            var anySeasonsRemoved = await RemoveObsoleteOrMissingSeasons(seriesList, episodeLookup)
                                    .ConfigureAwait(false);

            var anyEpisodesRemoved = await RemoveObsoleteOrMissingEpisodes(seriesList, episodeLookup, addMissingEpisodes)
                                     .ConfigureAwait(false);

            var hasNewEpisodes = false;

            if (addNewItems && seriesList.All(i => i.IsInternetMetadataEnabled()))
            {
                var seriesConfig = _config.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, typeof(Series).Name, StringComparison.OrdinalIgnoreCase));

                if (seriesConfig == null || !seriesConfig.DisabledMetadataFetchers.Contains(TvdbSeriesProvider.Current.Name, StringComparer.OrdinalIgnoreCase))
                {
                    hasNewEpisodes = await AddMissingEpisodes(seriesList, addMissingEpisodes, seriesDataPath, episodeLookup, cancellationToken)
                                     .ConfigureAwait(false);
                }
            }

            if (hasNewEpisodes || anySeasonsRemoved || anyEpisodesRemoved)
            {
                foreach (var series in seriesList)
                {
                    var directoryService = new DirectoryService(_logger, _fileSystem);

                    await series.RefreshMetadata(new MetadataRefreshOptions(directoryService), cancellationToken).ConfigureAwait(false);

                    await series.ValidateChildren(new Progress <double>(), cancellationToken, new MetadataRefreshOptions(directoryService), true)
                    .ConfigureAwait(false);
                }
            }
        }
Esempio n. 24
0
        /// <summary>
        /// Fetches the episode data.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <param name="identity">The identity.</param>
        /// <param name="seriesDataPath">The series data path.</param>
        /// <param name="seriesProviderIds">The series provider ids.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>Task{System.Boolean}.</returns>
        private MetadataResult <Episode> FetchEpisodeData(EpisodeInfo id, Identity?identity, string seriesDataPath, Dictionary <string, string> seriesProviderIds, CancellationToken cancellationToken)
        {
            var episodeNumber = identity.HasValue ? (identity.Value.EpisodeNumber) : id.IndexNumber.Value;
            var seasonOffset  = TvdbSeriesProvider.GetSeriesOffset(seriesProviderIds) ?? 0;
            var seasonNumber  = identity.HasValue ? (identity.Value.SeasonIndex + seasonOffset) : id.ParentIndexNumber;

            string file;
            var    usingAbsoluteData = false;

            var result = new MetadataResult <Episode>()
            {
                Item = new Episode
                {
                    IndexNumber       = id.IndexNumber,
                    ParentIndexNumber = id.ParentIndexNumber,
                    IndexNumberEnd    = id.IndexNumberEnd
                }
            };

            try
            {
                if (seasonNumber != null)
                {
                    file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber));
                    FetchMainEpisodeInfo(result, file, cancellationToken);

                    result.HasMetadata = true;
                }
            }
            catch (FileNotFoundException)
            {
                // Could be using absolute numbering
                if (seasonNumber.HasValue && seasonNumber.Value != 1)
                {
                    throw;
                }
            }

            if (!result.HasMetadata)
            {
                file = Path.Combine(seriesDataPath, string.Format("episode-abs-{0}.xml", episodeNumber));

                FetchMainEpisodeInfo(result, file, cancellationToken);
                result.HasMetadata = true;
                usingAbsoluteData  = true;
            }

            var end = identity.HasValue ? (identity.Value.EpisodeNumberEnd ?? episodeNumber) : (id.IndexNumberEnd ?? episodeNumber);

            episodeNumber++;

            while (episodeNumber <= end)
            {
                if (usingAbsoluteData)
                {
                    file = Path.Combine(seriesDataPath, string.Format("episode-abs-{0}.xml", episodeNumber));
                }
                else
                {
                    file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber));
                }

                try
                {
                    FetchAdditionalPartInfo(result, file, cancellationToken);
                }
                catch (FileNotFoundException)
                {
                    break;
                }
                catch (DirectoryNotFoundException)
                {
                    break;
                }

                episodeNumber++;
            }

            return(result);
        }
Esempio n. 25
0
        public async Task <MetadataResult <Episode> > GetMetadata(EpisodeInfo searchInfo, CancellationToken cancellationToken)
        {
            var identity = Identity.ParseIdentity(searchInfo.GetProviderId(FullIdKey));

            if (identity == null)
            {
                await Identify(searchInfo).ConfigureAwait(false);

                identity = Identity.ParseIdentity(searchInfo.GetProviderId(FullIdKey));
            }

            var result = new MetadataResult <Episode>();

            if (identity != null)
            {
                var seriesProviderIds = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);
                seriesProviderIds[MetadataProviders.Tvdb.ToString()] = identity.Value.SeriesId;
                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesProviderIds);

                var searchNumbers = new EpisodeNumbers();
                searchNumbers.EpisodeNumber = identity.Value.EpisodeNumber;
                var seasonOffset = TvdbSeriesProvider.GetSeriesOffset(searchInfo.SeriesProviderIds) ?? 0;
                searchNumbers.SeasonNumber     = identity.Value.SeasonIndex + seasonOffset;
                searchNumbers.EpisodeNumberEnd = identity.Value.EpisodeNumberEnd ?? searchNumbers.EpisodeNumber;

                try
                {
                    result = FetchEpisodeData(searchInfo, searchNumbers, seriesDataPath, cancellationToken);
                }
                catch (FileNotFoundException)
                {
                    // Don't fail the provider because this will just keep on going and going.
                }
                catch (DirectoryNotFoundException)
                {
                    // Don't fail the provider because this will just keep on going and going.
                }
            }
            else if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds) && searchInfo.IndexNumber.HasValue)
            {
                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, searchInfo.SeriesProviderIds);

                var searchNumbers = new EpisodeNumbers();
                searchNumbers.EpisodeNumber    = searchInfo.IndexNumber.Value;
                searchNumbers.SeasonNumber     = searchInfo.ParentIndexNumber;
                searchNumbers.EpisodeNumberEnd = searchInfo.IndexNumberEnd ?? searchNumbers.EpisodeNumber;

                try
                {
                    result = FetchEpisodeData(searchInfo, searchNumbers, seriesDataPath, cancellationToken);
                }
                catch (FileNotFoundException)
                {
                    // Don't fail the provider because this will just keep on going and going.
                }
                catch (DirectoryNotFoundException)
                {
                    // Don't fail the provider because this will just keep on going and going.
                }
            }
            else
            {
                _logger.Debug("No series identity found for {0}", searchInfo.Name);
            }

            return(result);
        }
Esempio n. 26
0
        /// <summary>
        /// Runs the specified progress.
        /// </summary>
        /// <param name="progress">The progress.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>Task.</returns>
        public async Task Run(IProgress <double> progress, CancellationToken cancellationToken)
        {
            var seriesConfig = _config.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, typeof(Series).Name, StringComparison.OrdinalIgnoreCase));

            if (seriesConfig != null && seriesConfig.DisabledMetadataFetchers.Contains(TvdbSeriesProvider.Current.Name, StringComparer.OrdinalIgnoreCase))
            {
                progress.Report(100);
                return;
            }

            var path = TvdbSeriesProvider.GetSeriesDataPath(_config.CommonApplicationPaths);

            _fileSystem.CreateDirectory(path);

            var timestampFile = Path.Combine(path, "time.txt");

            var timestampFileInfo = _fileSystem.GetFileInfo(timestampFile);

            // Don't check for tvdb updates anymore frequently than 24 hours
            if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 1)
            {
                return;
            }

            // Find out the last time we queried tvdb for updates
            var lastUpdateTime = timestampFileInfo.Exists ? _fileSystem.ReadAllText(timestampFile, Encoding.UTF8) : string.Empty;

            string newUpdateTime;

            var existingDirectories = _fileSystem.GetDirectoryPaths(path)
                                      .Select(Path.GetFileName)
                                      .ToList();

            var seriesList = _libraryManager.GetItemList(new InternalItemsQuery()
            {
                IncludeItemTypes             = new[] { typeof(Series).Name },
                Recursive                    = true,
                GroupByPresentationUniqueKey = false,
                DtoOptions                   = new DtoOptions(false)
                {
                    EnableImages = false
                }
            }).Cast <Series>()
                             .ToList();

            var seriesIdsInLibrary = seriesList
                                     .Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb)))
                                     .Select(i => i.GetProviderId(MetadataProviders.Tvdb))
                                     .ToList();

            var missingSeries = seriesIdsInLibrary.Except(existingDirectories, StringComparer.OrdinalIgnoreCase)
                                .ToList();

            var enableInternetProviders = seriesList.Count == 0 ? false : seriesList[0].IsInternetMetadataEnabled();

            if (!enableInternetProviders)
            {
                progress.Report(100);
                return;
            }

            // If this is our first time, update all series
            if (string.IsNullOrEmpty(lastUpdateTime))
            {
                // First get tvdb server time
                using (var stream = await _httpClient.Get(new HttpRequestOptions
                {
                    Url = ServerTimeUrl,
                    CancellationToken = cancellationToken,
                    EnableHttpCompression = true,
                    BufferContent = false
                }).ConfigureAwait(false))
                {
                    newUpdateTime = GetUpdateTime(stream);
                }

                existingDirectories.AddRange(missingSeries);

                await UpdateSeries(existingDirectories, path, null, progress, cancellationToken).ConfigureAwait(false);
            }
            else
            {
                var seriesToUpdate = await GetSeriesIdsToUpdate(existingDirectories, lastUpdateTime, cancellationToken).ConfigureAwait(false);

                newUpdateTime = seriesToUpdate.Item2;

                long lastUpdateValue;

                long.TryParse(lastUpdateTime, NumberStyles.Any, UsCulture, out lastUpdateValue);

                var nullableUpdateValue = lastUpdateValue == 0 ? (long?)null : lastUpdateValue;

                var listToUpdate = seriesToUpdate.Item1.ToList();
                listToUpdate.AddRange(missingSeries);

                await UpdateSeries(listToUpdate, path, nullableUpdateValue, progress, cancellationToken).ConfigureAwait(false);
            }

            _fileSystem.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8);
            progress.Report(100);
        }
Esempio n. 27
0
        public async Task Run(Series series, CancellationToken cancellationToken)
        {
            var tvdbId = series.GetProviderId(MetadataProviders.Tvdb);

            // Can't proceed without a tvdb id
            if (string.IsNullOrEmpty(tvdbId))
            {
                return;
            }

            var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, tvdbId);

            var episodeFiles = Directory.EnumerateFiles(seriesDataPath, "*.xml", SearchOption.TopDirectoryOnly)
                               .Select(Path.GetFileNameWithoutExtension)
                               .Where(i => i.StartsWith("episode-", StringComparison.OrdinalIgnoreCase))
                               .ToList();

            var episodeLookup = episodeFiles
                                .Select(i =>
            {
                var parts = i.Split('-');

                if (parts.Length == 3)
                {
                    int seasonNumber;

                    if (int.TryParse(parts[1], NumberStyles.Integer, UsCulture, out seasonNumber))
                    {
                        int episodeNumber;

                        if (int.TryParse(parts[2], NumberStyles.Integer, UsCulture, out episodeNumber))
                        {
                            return(new Tuple <int, int>(seasonNumber, episodeNumber));
                        }
                    }
                }

                return(new Tuple <int, int>(-1, -1));
            })
                                .Where(i => i.Item1 != -1 && i.Item2 != -1)
                                .ToList();

            var anySeasonsRemoved = await RemoveObsoleteOrMissingSeasons(series, episodeLookup, cancellationToken)
                                    .ConfigureAwait(false);

            var anyEpisodesRemoved = await RemoveObsoleteOrMissingEpisodes(series, episodeLookup, cancellationToken)
                                     .ConfigureAwait(false);

            var hasNewEpisodes = false;

            if (_config.Configuration.EnableInternetProviders)
            {
                hasNewEpisodes = await AddMissingEpisodes(series, seriesDataPath, episodeLookup, cancellationToken)
                                 .ConfigureAwait(false);
            }

            if (hasNewEpisodes || anySeasonsRemoved || anyEpisodesRemoved)
            {
                await series.RefreshMetadata(cancellationToken, true)
                .ConfigureAwait(false);

                await series.ValidateChildren(new Progress <double>(), cancellationToken, true)
                .ConfigureAwait(false);
            }
        }
Esempio n. 28
0
        /// <summary>
        /// Removes the obsolete or missing seasons.
        /// </summary>
        /// <param name="series">The series.</param>
        /// <param name="episodeLookup">The episode lookup.</param>
        /// <returns>Task{System.Boolean}.</returns>
        private async Task <bool> RemoveObsoleteOrMissingSeasons(IEnumerable <Series> series,
                                                                 IEnumerable <Tuple <int, int> > episodeLookup)
        {
            var existingSeasons = (from s in series
                                   let seasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1)
                                                      from c in s.Children.OfType <Season>()
                                                      select new { SeasonOffset = seasonOffset, Season = c })
                                  .ToList();

            var physicalSeasons = existingSeasons
                                  .Where(i => i.Season.LocationType != LocationType.Virtual)
                                  .ToList();

            var virtualSeasons = existingSeasons
                                 .Where(i => i.Season.LocationType == LocationType.Virtual)
                                 .ToList();

            var seasonsToRemove = virtualSeasons
                                  .Where(i =>
            {
                if (i.Season.IndexNumber.HasValue)
                {
                    var seasonNumber = i.Season.IndexNumber.Value + i.SeasonOffset;

                    // If there's a physical season with the same number, delete it
                    if (physicalSeasons.Any(p => p.Season.IndexNumber.HasValue && (p.Season.IndexNumber.Value + p.SeasonOffset) == seasonNumber))
                    {
                        return(true);
                    }

                    // If the season no longer exists in the remote lookup, delete it
                    if (episodeLookup.All(e => e.Item1 != seasonNumber))
                    {
                        return(true);
                    }

                    return(false);
                }

                // Season does not have a number
                // Remove if there are no episodes directly in series without a season number
                return(i.Season.Series.GetRecursiveChildren().OfType <Episode>().All(s => s.ParentIndexNumber.HasValue || !s.IsInSeasonFolder));
            })
                                  .ToList();

            var hasChanges = false;

            foreach (var seasonToRemove in seasonsToRemove.Select(s => s.Season))
            {
                _logger.Info("Removing virtual season {0} {1}", seasonToRemove.Series.Name, seasonToRemove.IndexNumber);

                await seasonToRemove.Delete(new DeleteOptions
                {
                    DeleteFileLocation = true
                }).ConfigureAwait(false);

                hasChanges = true;
            }

            return(hasChanges);
        }
Esempio n. 29
0
        private async Task Run(IGrouping <string, Series> group, CancellationToken cancellationToken)
        {
            var tvdbId = group.Key;

            // Todo: Support series by imdb id
            var seriesProviderIds = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);

            seriesProviderIds[MetadataProviders.Tvdb.ToString()] = tvdbId;

            var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesProviderIds);

            var episodeFiles = Directory.EnumerateFiles(seriesDataPath, "*.xml", SearchOption.TopDirectoryOnly)
                               .Select(Path.GetFileNameWithoutExtension)
                               .Where(i => i.StartsWith("episode-", StringComparison.OrdinalIgnoreCase))
                               .ToList();

            var episodeLookup = episodeFiles
                                .Select(i =>
            {
                var parts = i.Split('-');

                if (parts.Length == 3)
                {
                    int seasonNumber;

                    if (int.TryParse(parts[1], NumberStyles.Integer, _usCulture, out seasonNumber))
                    {
                        int episodeNumber;

                        if (int.TryParse(parts[2], NumberStyles.Integer, _usCulture, out episodeNumber))
                        {
                            return(new Tuple <int, int>(seasonNumber, episodeNumber));
                        }
                    }
                }

                return(new Tuple <int, int>(-1, -1));
            })
                                .Where(i => i.Item1 != -1 && i.Item2 != -1)
                                .ToList();

            var hasBadData = HasInvalidContent(group);

            var anySeasonsRemoved = await RemoveObsoleteOrMissingSeasons(group, episodeLookup)
                                    .ConfigureAwait(false);

            var anyEpisodesRemoved = await RemoveObsoleteOrMissingEpisodes(group, episodeLookup)
                                     .ConfigureAwait(false);

            var hasNewEpisodes = false;

            if (_config.Configuration.EnableInternetProviders)
            {
                var seriesConfig = _config.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, typeof(Series).Name, StringComparison.OrdinalIgnoreCase));

                if (seriesConfig == null || !seriesConfig.DisabledMetadataFetchers.Contains(TvdbSeriesProvider.Current.Name, StringComparer.OrdinalIgnoreCase))
                {
                    hasNewEpisodes = await AddMissingEpisodes(group.ToList(), hasBadData, seriesDataPath, episodeLookup, cancellationToken)
                                     .ConfigureAwait(false);
                }
            }

            if (hasNewEpisodes || anySeasonsRemoved || anyEpisodesRemoved)
            {
                foreach (var series in group)
                {
                    var directoryService = new DirectoryService(_fileSystem);

                    await series.RefreshMetadata(new MetadataRefreshOptions(directoryService)
                    {
                    }, cancellationToken).ConfigureAwait(false);

                    await series.ValidateChildren(new Progress <double>(), cancellationToken, new MetadataRefreshOptions(directoryService), true)
                    .ConfigureAwait(false);
                }
            }
        }
Esempio n. 30
0
        public async Task <bool> Run(Series series, bool addNewItems, CancellationToken cancellationToken)
        {
            var tvdbId = series.GetProviderId(MetadataProviders.Tvdb);

            // Todo: Support series by imdb id
            var seriesProviderIds = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);

            seriesProviderIds[MetadataProviders.Tvdb.ToString()] = tvdbId;

            var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesProviderIds);

            // Doesn't have required provider id's
            if (string.IsNullOrWhiteSpace(seriesDataPath))
            {
                return(false);
            }

            // Check this in order to avoid logging an exception due to directory not existing
            if (!_fileSystem.DirectoryExists(seriesDataPath))
            {
                return(false);
            }

            var episodeFiles = _fileSystem.GetFilePaths(seriesDataPath)
                               .Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase))
                               .Select(Path.GetFileNameWithoutExtension)
                               .Where(i => i.StartsWith("episode-", StringComparison.OrdinalIgnoreCase))
                               .ToList();

            var episodeLookup = episodeFiles
                                .Select(i =>
            {
                var parts = i.Split('-');

                if (parts.Length == 3)
                {
                    int seasonNumber;

                    if (int.TryParse(parts[1], NumberStyles.Integer, _usCulture, out seasonNumber))
                    {
                        int episodeNumber;

                        if (int.TryParse(parts[2], NumberStyles.Integer, _usCulture, out episodeNumber))
                        {
                            return(new Tuple <int, int>(seasonNumber, episodeNumber));
                        }
                    }
                }

                return(new Tuple <int, int>(-1, -1));
            })
                                .Where(i => i.Item1 != -1 && i.Item2 != -1)
                                .ToList();

            var allRecursiveChildren = series.GetRecursiveChildren();

            var hasBadData = HasInvalidContent(series, allRecursiveChildren);

            // Be conservative here to avoid creating missing episodes for ones they already have
            var addMissingEpisodes = !hasBadData && _libraryManager.GetLibraryOptions(series).ImportMissingEpisodes;

            var anySeasonsRemoved = RemoveObsoleteOrMissingSeasons(series, allRecursiveChildren, episodeLookup);

            if (anySeasonsRemoved)
            {
                // refresh this
                allRecursiveChildren = series.GetRecursiveChildren();
            }

            var anyEpisodesRemoved = RemoveObsoleteOrMissingEpisodes(series, allRecursiveChildren, episodeLookup, addMissingEpisodes);

            if (anyEpisodesRemoved)
            {
                // refresh this
                allRecursiveChildren = series.GetRecursiveChildren();
            }

            var hasNewEpisodes = false;

            if (addNewItems && series.IsMetadataFetcherEnabled(_libraryManager.GetLibraryOptions(series), TvdbSeriesProvider.Current.Name))
            {
                hasNewEpisodes = await AddMissingEpisodes(series, allRecursiveChildren, addMissingEpisodes, seriesDataPath, episodeLookup, cancellationToken)
                                 .ConfigureAwait(false);
            }

            if (hasNewEpisodes || anySeasonsRemoved || anyEpisodesRemoved)
            {
                return(true);
            }

            return(false);
        }