using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Data.Common; using System.Diagnostics; using System.Linq; using System.Net; using System.Reflection.Metadata.Ecma335; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Enums; using Jellyfin.Database.Implementations.Entities; using Jellyfin.Database.Implementations.Entities.Libraries; using Jellyfin.Plugin.MediaCleaner.Configuration; using Jellyfin.Plugin.MediaCleaner.Helpers; using Jellyfin.Plugin.MediaCleaner.Models; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; namespace Jellyfin.Plugin.MediaCleaner.ScheduledTasks; /// /// A task to scan media for stale files. /// public sealed class StaleMediaTask : IScheduledTask { private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly LoggingHelper _loggingHelper; private readonly MovieHelper _movieHelper; private readonly SeriesHelper _seriesHelper; /// /// Initializes a new instance of the class. /// /// Logger for StaleMediaTask. /// Accesses jellyfin's library manager for media. public StaleMediaTask(ILogger logger, ILibraryManager libraryManager) { _logger = logger; _libraryManager = libraryManager; _loggingHelper = new LoggingHelper(_logger); _movieHelper = new MovieHelper(_logger); _seriesHelper = new SeriesHelper(_logger); } private static PluginConfiguration Configuration => Plugin.Instance!.Configuration; string IScheduledTask.Name => "Scan Stale Media"; string IScheduledTask.Key => "Stale Media"; string IScheduledTask.Description => "Scan Stale Media"; string IScheduledTask.Category => "Media"; Task IScheduledTask.ExecuteAsync(IProgress progress, CancellationToken cancellationToken) { _loggingHelper.StartLogging(); var query = new InternalItemsQuery { IncludeItemTypes = [BaseItemKind.Movie, BaseItemKind.Series], Recursive = true }; List allItems = [.. _libraryManager.GetItemsResult(query).Items]; _loggingHelper.PrintItemsInformation(allItems); List series = [.. allItems.Where(item => item.GetBaseItemKind() == BaseItemKind.Series)]; List movies = [.. allItems.Where(item => item.GetBaseItemKind() == BaseItemKind.Movie)]; _loggingHelper.StartScanningSeriesItems(); List staleEpisodes = [.. series.SelectMany(GetStaleEpisodes)]; _loggingHelper.StartScanningMoviesItems(); List staleMovies = [.. GetStaleMovies(movies)]; _loggingHelper.PrintStaleMoviesInformation(staleMovies); _loggingHelper.PrintStaleEpisodesInformation(FindSeriesInfoFromEpisodes, staleEpisodes); _loggingHelper.EndLogging(); return Task.CompletedTask; } private List GetStaleMovies(List movies) { List staleMovies = []; staleMovies.AddRange(movies.Where(_movieHelper.IsMovieStale)); return staleMovies; } private List GetStaleEpisodes(BaseItem item) { List staleEpisodes = []; // Gets each season in a show var seasons = _libraryManager.GetItemList(new InternalItemsQuery { ParentId = item.Id, Recursive = false }); _loggingHelper.PrintDebugDataForSeries(item); foreach (var season in seasons) { // Gets each episode, to access user data. var episodes = _libraryManager.GetItemList(new InternalItemsQuery { ParentId = season.Id, Recursive = false }); _loggingHelper.PrintDebugSeasonInfo(); bool seasonHasUserData = episodes.Any(episode => episode.UserData.Count > 0); bool seasonIsStale = seasonHasUserData && _seriesHelper.IsSeasonUserDataStale(episodes); if (seasonIsStale) { if (!seasonHasUserData) { _loggingHelper.PrintDebugEpisodeCreationInfo(episodes); } staleEpisodes.AddRange(episodes); } } _loggingHelper.PrintDebugEndOfScanningForSeries(item); return staleEpisodes; } private List FindSeriesInfoFromEpisodes(IReadOnlyCollection episodes) { Guid[] seasonIds = [.. episodes.Select(episode => episode.ParentId).Distinct()]; var seasons = _libraryManager.GetItemList(new InternalItemsQuery { ItemIds = seasonIds }); Guid[] seriesIds = [.. seasons.Select(season => season.ParentId).Distinct()]; var series = _libraryManager.GetItemList(new InternalItemsQuery { ItemIds = seriesIds }).ToList(); List seriesNames = [.. series.Select(series => series.Name).Distinct()]; List seriesInfoList = []; series.ForEach(series => { seriesInfoList.Add(new SeriesInfo { Id = series.Id, SeriesName = series.Name, Seasons = [.. seasons.Where(season => season.ParentId == series.Id).Select(season => season.Name)] }); }); return seriesInfoList; } IEnumerable IScheduledTask.GetDefaultTriggers() { // Run this task every 24 hours yield return new TaskTriggerInfo { Type = TaskTriggerInfoType.IntervalTrigger, IntervalTicks = TimeSpan.FromHours(24).Ticks }; } }