using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Plugin.Jellypod.Services; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; namespace Jellyfin.Plugin.Jellypod.ScheduledTasks; /// /// Scheduled task that updates all podcast feeds and downloads new episodes. /// public class PodcastUpdateTask : IScheduledTask { private readonly ILogger _logger; private readonly IRssFeedService _rssFeedService; private readonly IPodcastStorageService _storageService; private readonly IPodcastDownloadService _downloadService; /// /// Initializes a new instance of the class. /// /// Logger instance. /// RSS feed service. /// Storage service. /// Download service. public PodcastUpdateTask( ILogger logger, IRssFeedService rssFeedService, IPodcastStorageService storageService, IPodcastDownloadService downloadService) { _logger = logger; _rssFeedService = rssFeedService; _storageService = storageService; _downloadService = downloadService; } /// public string Name => "Update Podcast Feeds"; /// public string Key => "JellypodUpdateFeeds"; /// public string Description => "Checks all subscribed podcasts for new episodes and downloads them."; /// public string Category => "Jellypod"; /// public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) { _logger.LogInformation("Starting podcast feed update task"); var podcasts = await _storageService.GetAllPodcastsAsync().ConfigureAwait(false); var totalPodcasts = podcasts.Count; var processedCount = 0; foreach (var podcast in podcasts) { cancellationToken.ThrowIfCancellationRequested(); try { _logger.LogDebug("Updating podcast: {Title}", podcast.Title); var updatedPodcast = await _rssFeedService.FetchPodcastAsync(podcast.FeedUrl, cancellationToken).ConfigureAwait(false); if (updatedPodcast != null) { // Find new episodes (by GUID) var existingGuids = podcast.Episodes .Select(e => e.EpisodeGuid) .Where(g => !string.IsNullOrEmpty(g)) .ToHashSet(StringComparer.Ordinal); var newEpisodes = updatedPodcast.Episodes .Where(e => !string.IsNullOrEmpty(e.EpisodeGuid) && !existingGuids.Contains(e.EpisodeGuid)) .ToList(); if (newEpisodes.Count > 0) { _logger.LogInformation("Found {Count} new episodes for {Title}", newEpisodes.Count, podcast.Title); // Add new episodes to podcast foreach (var episode in newEpisodes) { episode.PodcastId = podcast.Id; podcast.Episodes.Insert(0, episode); } podcast.LastUpdated = DateTime.UtcNow; // Auto-download if enabled var config = Plugin.Instance?.Configuration; if (config?.GlobalAutoDownloadEnabled == true && podcast.AutoDownloadEnabled) { foreach (var episode in newEpisodes) { await _downloadService.QueueDownloadAsync(podcast, episode).ConfigureAwait(false); } } await _storageService.UpdatePodcastAsync(podcast).ConfigureAwait(false); } else { _logger.LogDebug("No new episodes for {Title}", podcast.Title); } } } catch (Exception ex) { _logger.LogError(ex, "Failed to update podcast: {Title}", podcast.Title); } processedCount++; progress.Report((double)processedCount / totalPodcasts * 100); } progress.Report(100); _logger.LogInformation("Podcast feed update task completed"); } /// public IEnumerable GetDefaultTriggers() { var config = Plugin.Instance?.Configuration; var intervalHours = config?.UpdateIntervalHours ?? 6; return new[] { new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(intervalHours).Ticks } }; } }