private async Task HandleSeries(Series series) { if (!series.TryGetProviderId(MetadataProvider.Tvdb.ToString(), out var tvdbIdTxt)) { return; } var tvdbId = Convert.ToInt32(tvdbIdTxt, CultureInfo.InvariantCulture); var children = series.GetRecursiveChildren(); var existingSeasons = new List <Season>(); var existingEpisodes = new Dictionary <int, List <Episode> >(); for (var i = 0; i < children.Count; i++) { switch (children[i]) { case Season season: if (season.IndexNumber.HasValue) { existingSeasons.Add(season); } break; case Episode episode: var seasonNumber = episode.ParentIndexNumber ?? 1; if (!existingEpisodes.ContainsKey(seasonNumber)) { existingEpisodes[seasonNumber] = new List <Episode>(); } existingEpisodes[seasonNumber].Add(episode); break; } } var allEpisodes = await GetAllEpisodes(tvdbId, series.GetPreferredMetadataLanguage()).ConfigureAwait(false); var allSeasons = allEpisodes .Where(ep => ep.AiredSeason.HasValue) .Select(ep => ep.AiredSeason !.Value) .Distinct() .ToList(); // Add missing seasons var newSeasons = AddMissingSeasons(series, existingSeasons, allSeasons); AddMissingEpisodes(existingEpisodes, allEpisodes, existingSeasons.Concat(newSeasons).ToList()); }
private async Task<bool> AddDummySeasonFolders(Series series, CancellationToken cancellationToken) { var episodesInSeriesFolder = series.GetRecursiveChildren() .OfType<Episode>() .Where(i => !i.IsInSeasonFolder) .ToList(); var hasChanges = false; // Loop through the unique season numbers foreach (var seasonNumber in episodesInSeriesFolder.Select(i => i.ParentIndexNumber ?? -1) .Where(i => i >= 0) .Distinct() .ToList()) { var hasSeason = series.Children.OfType<Season>() .Any(i => i.IndexNumber.HasValue && i.IndexNumber.Value == seasonNumber); if (!hasSeason) { await AddSeason(series, seasonNumber, cancellationToken).ConfigureAwait(false); hasChanges = true; } } // Unknown season - create a dummy season to put these under if (episodesInSeriesFolder.Any(i => !i.ParentIndexNumber.HasValue)) { var hasSeason = series.Children.OfType<Season>() .Any(i => !i.IndexNumber.HasValue); if (!hasSeason) { await AddSeason(series, null, cancellationToken).ConfigureAwait(false); hasChanges = true; } } return hasChanges; }
/// <summary> /// Gets the season folder path. /// </summary> /// <param name="series">The series.</param> /// <param name="seasonNumber">The season number.</param> /// <param name="options">The options.</param> /// <returns>System.String.</returns> private string GetSeasonFolderPath(Series series, int seasonNumber, TvFileOrganizationOptions options) { // If there's already a season folder, use that var season = series .GetRecursiveChildren(i => i is Season && i.LocationType == LocationType.FileSystem && i.IndexNumber.HasValue && i.IndexNumber.Value == seasonNumber) .FirstOrDefault(); if (season != null) { return season.Path; } var path = series.Path; if (series.ContainsEpisodesWithoutSeasonFolders) { return path; } if (seasonNumber == 0) { return Path.Combine(path, _fileSystem.GetValidFilename(options.SeasonZeroFolderName)); } var seasonFolderName = options.SeasonFolderPattern .Replace("%s", seasonNumber.ToString(_usCulture)) .Replace("%0s", seasonNumber.ToString("00", _usCulture)) .Replace("%00s", seasonNumber.ToString("000", _usCulture)); return Path.Combine(path, _fileSystem.GetValidFilename(seasonFolderName)); }
private List<string> GetOtherDuplicatePaths(string targetPath, Series series, int? seasonNumber, int? episodeNumber, int? endingEpisodeNumber) { // TODO: Support date-naming? if (!seasonNumber.HasValue || !episodeNumber.HasValue) { return new List<string>(); } var episodePaths = series.GetRecursiveChildren() .OfType<Episode>() .Where(i => { var locationType = i.LocationType; // Must be file system based and match exactly if (locationType != LocationType.Remote && locationType != LocationType.Virtual && i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == seasonNumber && i.IndexNumber.HasValue && i.IndexNumber.Value == episodeNumber) { if (endingEpisodeNumber.HasValue || i.IndexNumberEnd.HasValue) { return endingEpisodeNumber.HasValue && i.IndexNumberEnd.HasValue && endingEpisodeNumber.Value == i.IndexNumberEnd.Value; } return true; } return false; }) .Select(i => i.Path) .ToList(); var folder = Path.GetDirectoryName(targetPath); var targetFileNameWithoutExtension = _fileSystem.GetFileNameWithoutExtension(targetPath); try { var filesOfOtherExtensions = _fileSystem.GetFilePaths(folder) .Where(i => _libraryManager.IsVideoFile(i) && string.Equals(_fileSystem.GetFileNameWithoutExtension(i), targetFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)); episodePaths.AddRange(filesOfOtherExtensions); } catch (DirectoryNotFoundException) { // No big deal. Maybe the season folder doesn't already exist. } return episodePaths.Where(i => !string.Equals(i, targetPath, StringComparison.OrdinalIgnoreCase)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); }
private async Task<bool> RemoveObsoleteSeasons(Series series) { var existingSeasons = series.Children.OfType<Season>().ToList(); var physicalSeasons = existingSeasons .Where(i => i.LocationType != LocationType.Virtual) .ToList(); var virtualSeasons = existingSeasons .Where(i => i.LocationType == LocationType.Virtual) .ToList(); var episodes = series.GetRecursiveChildren().OfType<Episode>().ToList(); var seasonsToRemove = virtualSeasons .Where(i => { if (i.IndexNumber.HasValue) { var seasonNumber = i.IndexNumber.Value; // If there's a physical season with the same number, delete it if (physicalSeasons.Any(p => p.IndexNumber.HasValue && (p.IndexNumber.Value == seasonNumber))) { return true; } // If there are no episodes with this season number, delete it if (episodes.All(e => !e.ParentIndexNumber.HasValue || e.ParentIndexNumber.Value != 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 episodes.All(s => s.ParentIndexNumber.HasValue || !s.IsInSeasonFolder); }) .ToList(); var hasChanges = false; foreach (var seasonToRemove in seasonsToRemove) { _logger.Info("Removing virtual season {0} {1}", seasonToRemove.Series.Name, seasonToRemove.IndexNumber); await _libraryManager.DeleteItem(seasonToRemove).ConfigureAwait(false); hasChanges = true; } return hasChanges; }