소스 검색

Add IGuideManager service

Patrick Barron 1 년 전
부모
커밋
59c2ae944d

+ 5 - 3
Jellyfin.Api/Controllers/LiveTvController.cs

@@ -42,6 +42,7 @@ namespace Jellyfin.Api.Controllers;
 public class LiveTvController : BaseJellyfinApiController
 {
     private readonly ILiveTvManager _liveTvManager;
+    private readonly IGuideManager _guideManager;
     private readonly ITunerHostManager _tunerHostManager;
     private readonly IUserManager _userManager;
     private readonly IHttpClientFactory _httpClientFactory;
@@ -55,6 +56,7 @@ public class LiveTvController : BaseJellyfinApiController
     /// Initializes a new instance of the <see cref="LiveTvController"/> class.
     /// </summary>
     /// <param name="liveTvManager">Instance of the <see cref="ILiveTvManager"/> interface.</param>
+    /// <param name="guideManager">Instance of the <see cref="IGuideManager"/> interface.</param>
     /// <param name="tunerHostManager">Instance of the <see cref="ITunerHostManager"/> interface.</param>
     /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
     /// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
@@ -65,6 +67,7 @@ public class LiveTvController : BaseJellyfinApiController
     /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
     public LiveTvController(
         ILiveTvManager liveTvManager,
+        IGuideManager guideManager,
         ITunerHostManager tunerHostManager,
         IUserManager userManager,
         IHttpClientFactory httpClientFactory,
@@ -75,6 +78,7 @@ public class LiveTvController : BaseJellyfinApiController
         ITranscodeManager transcodeManager)
     {
         _liveTvManager = liveTvManager;
+        _guideManager = guideManager;
         _tunerHostManager = tunerHostManager;
         _userManager = userManager;
         _httpClientFactory = httpClientFactory;
@@ -940,9 +944,7 @@ public class LiveTvController : BaseJellyfinApiController
     [Authorize(Policy = Policies.LiveTvAccess)]
     [ProducesResponseType(StatusCodes.Status200OK)]
     public ActionResult<GuideInfo> GetGuideInfo()
-    {
-        return _liveTvManager.GetGuideInfo();
-    }
+        => _guideManager.GetGuideInfo();
 
     /// <summary>
     /// Adds a tuner host.

+ 26 - 0
MediaBrowser.Controller/LiveTv/IGuideManager.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.LiveTv;
+
+namespace MediaBrowser.Controller.LiveTv;
+
+/// <summary>
+/// Service responsible for managing the Live TV guide.
+/// </summary>
+public interface IGuideManager
+{
+    /// <summary>
+    /// Gets the guide information.
+    /// </summary>
+    /// <returns>The <see cref="GuideInfo"/>.</returns>
+    GuideInfo GetGuideInfo();
+
+    /// <summary>
+    /// Refresh the guide.
+    /// </summary>
+    /// <param name="progress">The <see cref="IProgress{T}"/> to use.</param>
+    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use.</param>
+    /// <returns>Task representing the refresh operation.</returns>
+    Task RefreshGuide(IProgress<double> progress, CancellationToken cancellationToken);
+}

+ 0 - 6
MediaBrowser.Controller/LiveTv/ILiveTvManager.cs

@@ -174,12 +174,6 @@ namespace MediaBrowser.Controller.LiveTv
         /// <returns>Task.</returns>
         Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken);
 
-        /// <summary>
-        /// Gets the guide information.
-        /// </summary>
-        /// <returns>GuideInfo.</returns>
-        GuideInfo GetGuideInfo();
-
         /// <summary>
         /// Gets the recommended programs.
         /// </summary>

+ 2 - 0
src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs

@@ -1,4 +1,5 @@
 using Jellyfin.LiveTv.Channels;
+using Jellyfin.LiveTv.Guide;
 using Jellyfin.LiveTv.TunerHosts;
 using Jellyfin.LiveTv.TunerHosts.HdHomerun;
 using MediaBrowser.Controller.Channels;
@@ -24,6 +25,7 @@ public static class LiveTvServiceCollectionExtensions
         services.AddSingleton<IChannelManager, ChannelManager>();
         services.AddSingleton<IStreamHelper, StreamHelper>();
         services.AddSingleton<ITunerHostManager, TunerHostManager>();
+        services.AddSingleton<IGuideManager, GuideManager>();
 
         services.AddSingleton<ITunerHost, HdHomerunHost>();
         services.AddSingleton<ITunerHost, M3UTunerHost>();

+ 713 - 0
src/Jellyfin.LiveTv/Guide/GuideManager.cs

@@ -0,0 +1,713 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Jellyfin.Data.Enums;
+using Jellyfin.LiveTv.Configuration;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Progress;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.LiveTv;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.LiveTv.Guide;
+
+/// <inheritdoc />
+public class GuideManager : IGuideManager
+{
+    private const int MaxGuideDays = 14;
+    private const string EtagKey = "ProgramEtag";
+    private const string ExternalServiceTag = "ExternalServiceId";
+
+    private readonly ILogger<GuideManager> _logger;
+    private readonly IConfigurationManager _config;
+    private readonly IFileSystem _fileSystem;
+    private readonly IItemRepository _itemRepo;
+    private readonly ILibraryManager _libraryManager;
+    private readonly ILiveTvManager _liveTvManager;
+    private readonly ITunerHostManager _tunerHostManager;
+    private readonly LiveTvDtoService _tvDtoService;
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="GuideManager"/> class.
+    /// </summary>
+    /// <param name="logger">The <see cref="ILogger{TCategoryName}"/>.</param>
+    /// <param name="config">The <see cref="IConfigurationManager"/>.</param>
+    /// <param name="fileSystem">The <see cref="IFileSystem"/>.</param>
+    /// <param name="itemRepo">The <see cref="IItemRepository"/>.</param>
+    /// <param name="libraryManager">The <see cref="ILibraryManager"/>.</param>
+    /// <param name="liveTvManager">The <see cref="ILiveTvManager"/>.</param>
+    /// <param name="tunerHostManager">The <see cref="ITunerHostManager"/>.</param>
+    /// <param name="tvDtoService">The <see cref="LiveTvDtoService"/>.</param>
+    public GuideManager(
+        ILogger<GuideManager> logger,
+        IConfigurationManager config,
+        IFileSystem fileSystem,
+        IItemRepository itemRepo,
+        ILibraryManager libraryManager,
+        ILiveTvManager liveTvManager,
+        ITunerHostManager tunerHostManager,
+        LiveTvDtoService tvDtoService)
+    {
+        _logger = logger;
+        _config = config;
+        _fileSystem = fileSystem;
+        _itemRepo = itemRepo;
+        _libraryManager = libraryManager;
+        _liveTvManager = liveTvManager;
+        _tunerHostManager = tunerHostManager;
+        _tvDtoService = tvDtoService;
+    }
+
+    /// <inheritdoc />
+    public GuideInfo GetGuideInfo()
+    {
+        var startDate = DateTime.UtcNow;
+        var endDate = startDate.AddDays(GetGuideDays());
+
+        return new GuideInfo
+        {
+            StartDate = startDate,
+            EndDate = endDate
+        };
+    }
+
+    /// <inheritdoc />
+    public async Task RefreshGuide(IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        ArgumentNullException.ThrowIfNull(progress);
+
+        await EmbyTV.EmbyTV.Current.CreateRecordingFolders().ConfigureAwait(false);
+
+        await _tunerHostManager.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false);
+
+        var numComplete = 0;
+        double progressPerService = _liveTvManager.Services.Count == 0
+            ? 0
+            : 1.0 / _liveTvManager.Services.Count;
+
+        var newChannelIdList = new List<Guid>();
+        var newProgramIdList = new List<Guid>();
+
+        var cleanDatabase = true;
+
+        foreach (var service in _liveTvManager.Services)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            _logger.LogDebug("Refreshing guide from {Name}", service.Name);
+
+            try
+            {
+                var innerProgress = new ActionableProgress<double>();
+                innerProgress.RegisterAction(p => progress.Report(p * progressPerService));
+
+                var idList = await RefreshChannelsInternal(service, innerProgress, cancellationToken).ConfigureAwait(false);
+
+                newChannelIdList.AddRange(idList.Item1);
+                newProgramIdList.AddRange(idList.Item2);
+            }
+            catch (OperationCanceledException)
+            {
+                throw;
+            }
+            catch (Exception ex)
+            {
+                cleanDatabase = false;
+                _logger.LogError(ex, "Error refreshing channels for service");
+            }
+
+            numComplete++;
+            double percent = numComplete;
+            percent /= _liveTvManager.Services.Count;
+
+            progress.Report(100 * percent);
+        }
+
+        if (cleanDatabase)
+        {
+            CleanDatabase(newChannelIdList.ToArray(), [BaseItemKind.LiveTvChannel], progress, cancellationToken);
+            CleanDatabase(newProgramIdList.ToArray(), [BaseItemKind.LiveTvProgram], progress, cancellationToken);
+        }
+
+        var coreService = _liveTvManager.Services.OfType<EmbyTV.EmbyTV>().FirstOrDefault();
+        if (coreService is not null)
+        {
+            await coreService.RefreshSeriesTimers(cancellationToken).ConfigureAwait(false);
+            await coreService.RefreshTimers(cancellationToken).ConfigureAwait(false);
+        }
+
+        // Load these now which will prefetch metadata
+        var dtoOptions = new DtoOptions();
+        var fields = dtoOptions.Fields.ToList();
+        dtoOptions.Fields = fields.ToArray();
+
+        progress.Report(100);
+    }
+
+    private double GetGuideDays()
+    {
+        var config = _config.GetLiveTvConfiguration();
+
+        return config.GuideDays.HasValue
+            ? Math.Max(1, Math.Min(config.GuideDays.Value, MaxGuideDays))
+            : 7;
+    }
+
+    private async Task<Tuple<List<Guid>, List<Guid>>> RefreshChannelsInternal(ILiveTvService service, ActionableProgress<double> progress, CancellationToken cancellationToken)
+    {
+        progress.Report(10);
+
+        var allChannelsList = (await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false))
+            .Select(i => new Tuple<string, ChannelInfo>(service.Name, i))
+            .ToList();
+
+        var list = new List<LiveTvChannel>();
+
+        var numComplete = 0;
+        var parentFolder = _liveTvManager.GetInternalLiveTvFolder(cancellationToken);
+
+        foreach (var channelInfo in allChannelsList)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            try
+            {
+                var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, parentFolder, cancellationToken).ConfigureAwait(false);
+
+                list.Add(item);
+            }
+            catch (OperationCanceledException)
+            {
+                throw;
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "Error getting channel information for {Name}", channelInfo.Item2.Name);
+            }
+
+            numComplete++;
+            double percent = numComplete;
+            percent /= allChannelsList.Count;
+
+            progress.Report((5 * percent) + 10);
+        }
+
+        progress.Report(15);
+
+        numComplete = 0;
+        var programs = new List<Guid>();
+        var channels = new List<Guid>();
+
+        var guideDays = GetGuideDays();
+
+        _logger.LogInformation("Refreshing guide with {0} days of guide data", guideDays);
+
+        foreach (var currentChannel in list)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+            channels.Add(currentChannel.Id);
+
+            try
+            {
+                var start = DateTime.UtcNow.AddHours(-1);
+                var end = start.AddDays(guideDays);
+
+                var isMovie = false;
+                var isSports = false;
+                var isNews = false;
+                var isKids = false;
+                var isSeries = false;
+
+                var channelPrograms = (await service.GetProgramsAsync(currentChannel.ExternalId, start, end, cancellationToken).ConfigureAwait(false)).ToList();
+
+                var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery
+                {
+                    IncludeItemTypes = [BaseItemKind.LiveTvProgram],
+                    ChannelIds = new[] { currentChannel.Id },
+                    DtoOptions = new DtoOptions(true)
+                }).Cast<LiveTvProgram>().ToDictionary(i => i.Id);
+
+                var newPrograms = new List<LiveTvProgram>();
+                var updatedPrograms = new List<BaseItem>();
+
+                foreach (var program in channelPrograms)
+                {
+                    var (programItem, isNew, isUpdated) = GetProgram(program, existingPrograms, currentChannel);
+                    if (isNew)
+                    {
+                        newPrograms.Add(programItem);
+                    }
+                    else if (isUpdated)
+                    {
+                        updatedPrograms.Add(programItem);
+                    }
+
+                    programs.Add(programItem.Id);
+
+                    isMovie |= program.IsMovie;
+                    isSeries |= program.IsSeries;
+                    isSports |= program.IsSports;
+                    isNews |= program.IsNews;
+                    isKids |= program.IsKids;
+                }
+
+                _logger.LogDebug("Channel {0} has {1} new programs and {2} updated programs", currentChannel.Name, newPrograms.Count, updatedPrograms.Count);
+
+                if (newPrograms.Count > 0)
+                {
+                    _libraryManager.CreateItems(newPrograms, null, cancellationToken);
+                }
+
+                if (updatedPrograms.Count > 0)
+                {
+                    await _libraryManager.UpdateItemsAsync(
+                        updatedPrograms,
+                        currentChannel,
+                        ItemUpdateType.MetadataImport,
+                        cancellationToken).ConfigureAwait(false);
+                }
+
+                currentChannel.IsMovie = isMovie;
+                currentChannel.IsNews = isNews;
+                currentChannel.IsSports = isSports;
+                currentChannel.IsSeries = isSeries;
+
+                if (isKids)
+                {
+                    currentChannel.AddTag("Kids");
+                }
+
+                await currentChannel.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
+                await currentChannel.RefreshMetadata(
+                    new MetadataRefreshOptions(new DirectoryService(_fileSystem))
+                    {
+                        ForceSave = true
+                    },
+                    cancellationToken).ConfigureAwait(false);
+            }
+            catch (OperationCanceledException)
+            {
+                throw;
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "Error getting programs for channel {Name}", currentChannel.Name);
+            }
+
+            numComplete++;
+            double percent = numComplete / (double)allChannelsList.Count;
+
+            progress.Report((85 * percent) + 15);
+        }
+
+        progress.Report(100);
+        return new Tuple<List<Guid>, List<Guid>>(channels, programs);
+    }
+
+    private void CleanDatabase(Guid[] currentIdList, BaseItemKind[] validTypes, IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        var list = _itemRepo.GetItemIdsList(new InternalItemsQuery
+        {
+            IncludeItemTypes = validTypes,
+            DtoOptions = new DtoOptions(false)
+        });
+
+        var numComplete = 0;
+
+        foreach (var itemId in list)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            if (itemId.Equals(default))
+            {
+                // Somehow some invalid data got into the db. It probably predates the boundary checking
+                continue;
+            }
+
+            if (!currentIdList.Contains(itemId))
+            {
+                var item = _libraryManager.GetItemById(itemId);
+
+                if (item is not null)
+                {
+                    _libraryManager.DeleteItem(
+                        item,
+                        new DeleteOptions
+                        {
+                            DeleteFileLocation = false,
+                            DeleteFromExternalProvider = false
+                        },
+                        false);
+                }
+            }
+
+            numComplete++;
+            double percent = numComplete / (double)list.Count;
+
+            progress.Report(100 * percent);
+        }
+    }
+
+    private async Task<LiveTvChannel> GetChannel(
+        ChannelInfo channelInfo,
+        string serviceName,
+        BaseItem parentFolder,
+        CancellationToken cancellationToken)
+    {
+        var parentFolderId = parentFolder.Id;
+        var isNew = false;
+        var forceUpdate = false;
+
+        var id = _tvDtoService.GetInternalChannelId(serviceName, channelInfo.Id);
+
+        if (_libraryManager.GetItemById(id) is not LiveTvChannel item)
+        {
+            item = new LiveTvChannel
+            {
+                Name = channelInfo.Name,
+                Id = id,
+                DateCreated = DateTime.UtcNow
+            };
+
+            isNew = true;
+        }
+
+        if (channelInfo.Tags is not null)
+        {
+            if (!channelInfo.Tags.SequenceEqual(item.Tags, StringComparer.OrdinalIgnoreCase))
+            {
+                isNew = true;
+            }
+
+            item.Tags = channelInfo.Tags;
+        }
+
+        if (!item.ParentId.Equals(parentFolderId))
+        {
+            isNew = true;
+        }
+
+        item.ParentId = parentFolderId;
+
+        item.ChannelType = channelInfo.ChannelType;
+        item.ServiceName = serviceName;
+
+        if (!string.Equals(item.GetProviderId(ExternalServiceTag), serviceName, StringComparison.OrdinalIgnoreCase))
+        {
+            forceUpdate = true;
+        }
+
+        item.SetProviderId(ExternalServiceTag, serviceName);
+
+        if (!string.Equals(channelInfo.Id, item.ExternalId, StringComparison.Ordinal))
+        {
+            forceUpdate = true;
+        }
+
+        item.ExternalId = channelInfo.Id;
+
+        if (!string.Equals(channelInfo.Number, item.Number, StringComparison.Ordinal))
+        {
+            forceUpdate = true;
+        }
+
+        item.Number = channelInfo.Number;
+
+        if (!string.Equals(channelInfo.Name, item.Name, StringComparison.Ordinal))
+        {
+            forceUpdate = true;
+        }
+
+        item.Name = channelInfo.Name;
+
+        if (!item.HasImage(ImageType.Primary))
+        {
+            if (!string.IsNullOrWhiteSpace(channelInfo.ImagePath))
+            {
+                item.SetImagePath(ImageType.Primary, channelInfo.ImagePath);
+                forceUpdate = true;
+            }
+            else if (!string.IsNullOrWhiteSpace(channelInfo.ImageUrl))
+            {
+                item.SetImagePath(ImageType.Primary, channelInfo.ImageUrl);
+                forceUpdate = true;
+            }
+        }
+
+        if (isNew)
+        {
+            _libraryManager.CreateItem(item, parentFolder);
+        }
+        else if (forceUpdate)
+        {
+            await _libraryManager.UpdateItemAsync(item, parentFolder, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
+        }
+
+        return item;
+    }
+
+    private (LiveTvProgram Item, bool IsNew, bool IsUpdated) GetProgram(
+        ProgramInfo info,
+        Dictionary<Guid, LiveTvProgram> allExistingPrograms,
+        LiveTvChannel channel)
+    {
+        var id = _tvDtoService.GetInternalProgramId(info.Id);
+
+        var isNew = false;
+        var forceUpdate = false;
+
+        if (!allExistingPrograms.TryGetValue(id, out var item))
+        {
+            isNew = true;
+            item = new LiveTvProgram
+            {
+                Name = info.Name,
+                Id = id,
+                DateCreated = DateTime.UtcNow,
+                DateModified = DateTime.UtcNow
+            };
+
+            if (!string.IsNullOrEmpty(info.Etag))
+            {
+                item.SetProviderId(EtagKey, info.Etag);
+            }
+        }
+
+        if (!string.Equals(info.ShowId, item.ShowId, StringComparison.OrdinalIgnoreCase))
+        {
+            item.ShowId = info.ShowId;
+            forceUpdate = true;
+        }
+
+        var seriesId = info.SeriesId;
+
+        if (!item.ParentId.Equals(channel.Id))
+        {
+            forceUpdate = true;
+        }
+
+        item.ParentId = channel.Id;
+
+        item.Audio = info.Audio;
+        item.ChannelId = channel.Id;
+        item.CommunityRating ??= info.CommunityRating;
+        if ((item.CommunityRating ?? 0).Equals(0))
+        {
+            item.CommunityRating = null;
+        }
+
+        item.EpisodeTitle = info.EpisodeTitle;
+        item.ExternalId = info.Id;
+
+        if (!string.IsNullOrWhiteSpace(seriesId) && !string.Equals(item.ExternalSeriesId, seriesId, StringComparison.Ordinal))
+        {
+            forceUpdate = true;
+        }
+
+        item.ExternalSeriesId = seriesId;
+
+        var isSeries = info.IsSeries || !string.IsNullOrEmpty(info.EpisodeTitle);
+
+        if (isSeries || !string.IsNullOrEmpty(info.EpisodeTitle))
+        {
+            item.SeriesName = info.Name;
+        }
+
+        var tags = new List<string>();
+        if (info.IsLive)
+        {
+            tags.Add("Live");
+        }
+
+        if (info.IsPremiere)
+        {
+            tags.Add("Premiere");
+        }
+
+        if (info.IsNews)
+        {
+            tags.Add("News");
+        }
+
+        if (info.IsSports)
+        {
+            tags.Add("Sports");
+        }
+
+        if (info.IsKids)
+        {
+            tags.Add("Kids");
+        }
+
+        if (info.IsRepeat)
+        {
+            tags.Add("Repeat");
+        }
+
+        if (info.IsMovie)
+        {
+            tags.Add("Movie");
+        }
+
+        if (isSeries)
+        {
+            tags.Add("Series");
+        }
+
+        item.Tags = tags.ToArray();
+
+        item.Genres = info.Genres.ToArray();
+
+        if (info.IsHD ?? false)
+        {
+            item.Width = 1280;
+            item.Height = 720;
+        }
+
+        item.IsMovie = info.IsMovie;
+        item.IsRepeat = info.IsRepeat;
+
+        if (item.IsSeries != isSeries)
+        {
+            forceUpdate = true;
+        }
+
+        item.IsSeries = isSeries;
+
+        item.Name = info.Name;
+        item.OfficialRating ??= info.OfficialRating;
+        item.Overview ??= info.Overview;
+        item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks;
+        item.ProviderIds = info.ProviderIds;
+
+        foreach (var providerId in info.SeriesProviderIds)
+        {
+            info.ProviderIds["Series" + providerId.Key] = providerId.Value;
+        }
+
+        if (item.StartDate != info.StartDate)
+        {
+            forceUpdate = true;
+        }
+
+        item.StartDate = info.StartDate;
+
+        if (item.EndDate != info.EndDate)
+        {
+            forceUpdate = true;
+        }
+
+        item.EndDate = info.EndDate;
+
+        item.ProductionYear = info.ProductionYear;
+
+        if (!isSeries || info.IsRepeat)
+        {
+            item.PremiereDate = info.OriginalAirDate;
+        }
+
+        item.IndexNumber = info.EpisodeNumber;
+        item.ParentIndexNumber = info.SeasonNumber;
+
+        if (!item.HasImage(ImageType.Primary))
+        {
+            if (!string.IsNullOrWhiteSpace(info.ImagePath))
+            {
+                item.SetImage(
+                    new ItemImageInfo
+                    {
+                        Path = info.ImagePath,
+                        Type = ImageType.Primary
+                    },
+                    0);
+            }
+            else if (!string.IsNullOrWhiteSpace(info.ImageUrl))
+            {
+                item.SetImage(
+                    new ItemImageInfo
+                    {
+                        Path = info.ImageUrl,
+                        Type = ImageType.Primary
+                    },
+                    0);
+            }
+        }
+
+        if (!item.HasImage(ImageType.Thumb))
+        {
+            if (!string.IsNullOrWhiteSpace(info.ThumbImageUrl))
+            {
+                item.SetImage(
+                    new ItemImageInfo
+                    {
+                        Path = info.ThumbImageUrl,
+                        Type = ImageType.Thumb
+                    },
+                    0);
+            }
+        }
+
+        if (!item.HasImage(ImageType.Logo))
+        {
+            if (!string.IsNullOrWhiteSpace(info.LogoImageUrl))
+            {
+                item.SetImage(
+                    new ItemImageInfo
+                    {
+                        Path = info.LogoImageUrl,
+                        Type = ImageType.Logo
+                    },
+                    0);
+            }
+        }
+
+        if (!item.HasImage(ImageType.Backdrop))
+        {
+            if (!string.IsNullOrWhiteSpace(info.BackdropImageUrl))
+            {
+                item.SetImage(
+                    new ItemImageInfo
+                    {
+                        Path = info.BackdropImageUrl,
+                        Type = ImageType.Backdrop
+                    },
+                    0);
+            }
+        }
+
+        var isUpdated = false;
+        if (isNew)
+        {
+        }
+        else if (forceUpdate || string.IsNullOrWhiteSpace(info.Etag))
+        {
+            isUpdated = true;
+        }
+        else
+        {
+            var etag = info.Etag;
+
+            if (!string.Equals(etag, item.GetProviderId(EtagKey), StringComparison.OrdinalIgnoreCase))
+            {
+                item.SetProviderId(EtagKey, etag);
+                isUpdated = true;
+            }
+        }
+
+        if (isNew || isUpdated)
+        {
+            item.OnMetadataChanged();
+        }
+
+        return (item, isNew, isUpdated);
+    }
+}

+ 1 - 667
src/Jellyfin.LiveTv/LiveTvManager.cs

@@ -14,20 +14,16 @@ using Jellyfin.Data.Enums;
 using Jellyfin.Data.Events;
 using Jellyfin.LiveTv.Configuration;
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Progress;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.IO;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Tasks;
@@ -40,24 +36,16 @@ namespace Jellyfin.LiveTv
     /// </summary>
     public class LiveTvManager : ILiveTvManager
     {
-        private const int MaxGuideDays = 14;
-        private const string ExternalServiceTag = "ExternalServiceId";
-
-        private const string EtagKey = "ProgramEtag";
-
         private readonly IServerConfigurationManager _config;
         private readonly ILogger<LiveTvManager> _logger;
-        private readonly IItemRepository _itemRepo;
         private readonly IUserManager _userManager;
         private readonly IDtoService _dtoService;
         private readonly IUserDataManager _userDataManager;
         private readonly ILibraryManager _libraryManager;
         private readonly ITaskManager _taskManager;
         private readonly ILocalizationManager _localization;
-        private readonly IFileSystem _fileSystem;
         private readonly IChannelManager _channelManager;
         private readonly LiveTvDtoService _tvDtoService;
-        private readonly ITunerHostManager _tunerHostManager;
 
         private ILiveTvService[] _services = Array.Empty<ILiveTvService>();
         private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>();
@@ -65,31 +53,25 @@ namespace Jellyfin.LiveTv
         public LiveTvManager(
             IServerConfigurationManager config,
             ILogger<LiveTvManager> logger,
-            IItemRepository itemRepo,
             IUserDataManager userDataManager,
             IDtoService dtoService,
             IUserManager userManager,
             ILibraryManager libraryManager,
             ITaskManager taskManager,
             ILocalizationManager localization,
-            IFileSystem fileSystem,
             IChannelManager channelManager,
-            LiveTvDtoService liveTvDtoService,
-            ITunerHostManager tunerHostManager)
+            LiveTvDtoService liveTvDtoService)
         {
             _config = config;
             _logger = logger;
-            _itemRepo = itemRepo;
             _userManager = userManager;
             _libraryManager = libraryManager;
             _taskManager = taskManager;
             _localization = localization;
-            _fileSystem = fileSystem;
             _dtoService = dtoService;
             _userDataManager = userDataManager;
             _channelManager = channelManager;
             _tvDtoService = liveTvDtoService;
-            _tunerHostManager = tunerHostManager;
         }
 
         public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
@@ -400,355 +382,6 @@ namespace Jellyfin.LiveTv
             }
         }
 
-        private async Task<LiveTvChannel> GetChannelAsync(ChannelInfo channelInfo, string serviceName, BaseItem parentFolder, CancellationToken cancellationToken)
-        {
-            var parentFolderId = parentFolder.Id;
-            var isNew = false;
-            var forceUpdate = false;
-
-            var id = _tvDtoService.GetInternalChannelId(serviceName, channelInfo.Id);
-
-            var item = _libraryManager.GetItemById(id) as LiveTvChannel;
-
-            if (item is null)
-            {
-                item = new LiveTvChannel
-                {
-                    Name = channelInfo.Name,
-                    Id = id,
-                    DateCreated = DateTime.UtcNow
-                };
-
-                isNew = true;
-            }
-
-            if (channelInfo.Tags is not null)
-            {
-                if (!channelInfo.Tags.SequenceEqual(item.Tags, StringComparer.OrdinalIgnoreCase))
-                {
-                    isNew = true;
-                }
-
-                item.Tags = channelInfo.Tags;
-            }
-
-            if (!item.ParentId.Equals(parentFolderId))
-            {
-                isNew = true;
-            }
-
-            item.ParentId = parentFolderId;
-
-            item.ChannelType = channelInfo.ChannelType;
-            item.ServiceName = serviceName;
-
-            if (!string.Equals(item.GetProviderId(ExternalServiceTag), serviceName, StringComparison.OrdinalIgnoreCase))
-            {
-                forceUpdate = true;
-            }
-
-            item.SetProviderId(ExternalServiceTag, serviceName);
-
-            if (!string.Equals(channelInfo.Id, item.ExternalId, StringComparison.Ordinal))
-            {
-                forceUpdate = true;
-            }
-
-            item.ExternalId = channelInfo.Id;
-
-            if (!string.Equals(channelInfo.Number, item.Number, StringComparison.Ordinal))
-            {
-                forceUpdate = true;
-            }
-
-            item.Number = channelInfo.Number;
-
-            if (!string.Equals(channelInfo.Name, item.Name, StringComparison.Ordinal))
-            {
-                forceUpdate = true;
-            }
-
-            item.Name = channelInfo.Name;
-
-            if (!item.HasImage(ImageType.Primary))
-            {
-                if (!string.IsNullOrWhiteSpace(channelInfo.ImagePath))
-                {
-                    item.SetImagePath(ImageType.Primary, channelInfo.ImagePath);
-                    forceUpdate = true;
-                }
-                else if (!string.IsNullOrWhiteSpace(channelInfo.ImageUrl))
-                {
-                    item.SetImagePath(ImageType.Primary, channelInfo.ImageUrl);
-                    forceUpdate = true;
-                }
-            }
-
-            if (isNew)
-            {
-                _libraryManager.CreateItem(item, parentFolder);
-            }
-            else if (forceUpdate)
-            {
-                await _libraryManager.UpdateItemAsync(item, parentFolder, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
-            }
-
-            return item;
-        }
-
-        private (LiveTvProgram Item, bool IsNew, bool IsUpdated) GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel)
-        {
-            var id = _tvDtoService.GetInternalProgramId(info.Id);
-
-            var isNew = false;
-            var forceUpdate = false;
-
-            if (!allExistingPrograms.TryGetValue(id, out LiveTvProgram item))
-            {
-                isNew = true;
-                item = new LiveTvProgram
-                {
-                    Name = info.Name,
-                    Id = id,
-                    DateCreated = DateTime.UtcNow,
-                    DateModified = DateTime.UtcNow
-                };
-
-                if (!string.IsNullOrEmpty(info.Etag))
-                {
-                    item.SetProviderId(EtagKey, info.Etag);
-                }
-            }
-
-            if (!string.Equals(info.ShowId, item.ShowId, StringComparison.OrdinalIgnoreCase))
-            {
-                item.ShowId = info.ShowId;
-                forceUpdate = true;
-            }
-
-            var seriesId = info.SeriesId;
-
-            if (!item.ParentId.Equals(channel.Id))
-            {
-                forceUpdate = true;
-            }
-
-            item.ParentId = channel.Id;
-
-            item.Audio = info.Audio;
-            item.ChannelId = channel.Id;
-            item.CommunityRating ??= info.CommunityRating;
-            if ((item.CommunityRating ?? 0).Equals(0))
-            {
-                item.CommunityRating = null;
-            }
-
-            item.EpisodeTitle = info.EpisodeTitle;
-            item.ExternalId = info.Id;
-
-            if (!string.IsNullOrWhiteSpace(seriesId) && !string.Equals(item.ExternalSeriesId, seriesId, StringComparison.Ordinal))
-            {
-                forceUpdate = true;
-            }
-
-            item.ExternalSeriesId = seriesId;
-
-            var isSeries = info.IsSeries || !string.IsNullOrEmpty(info.EpisodeTitle);
-
-            if (isSeries || !string.IsNullOrEmpty(info.EpisodeTitle))
-            {
-                item.SeriesName = info.Name;
-            }
-
-            var tags = new List<string>();
-            if (info.IsLive)
-            {
-                tags.Add("Live");
-            }
-
-            if (info.IsPremiere)
-            {
-                tags.Add("Premiere");
-            }
-
-            if (info.IsNews)
-            {
-                tags.Add("News");
-            }
-
-            if (info.IsSports)
-            {
-                tags.Add("Sports");
-            }
-
-            if (info.IsKids)
-            {
-                tags.Add("Kids");
-            }
-
-            if (info.IsRepeat)
-            {
-                tags.Add("Repeat");
-            }
-
-            if (info.IsMovie)
-            {
-                tags.Add("Movie");
-            }
-
-            if (isSeries)
-            {
-                tags.Add("Series");
-            }
-
-            item.Tags = tags.ToArray();
-
-            item.Genres = info.Genres.ToArray();
-
-            if (info.IsHD ?? false)
-            {
-                item.Width = 1280;
-                item.Height = 720;
-            }
-
-            item.IsMovie = info.IsMovie;
-            item.IsRepeat = info.IsRepeat;
-
-            if (item.IsSeries != isSeries)
-            {
-                forceUpdate = true;
-            }
-
-            item.IsSeries = isSeries;
-
-            item.Name = info.Name;
-            item.OfficialRating ??= info.OfficialRating;
-            item.Overview ??= info.Overview;
-            item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks;
-            item.ProviderIds = info.ProviderIds;
-
-            foreach (var providerId in info.SeriesProviderIds)
-            {
-                info.ProviderIds["Series" + providerId.Key] = providerId.Value;
-            }
-
-            if (item.StartDate != info.StartDate)
-            {
-                forceUpdate = true;
-            }
-
-            item.StartDate = info.StartDate;
-
-            if (item.EndDate != info.EndDate)
-            {
-                forceUpdate = true;
-            }
-
-            item.EndDate = info.EndDate;
-
-            item.ProductionYear = info.ProductionYear;
-
-            if (!isSeries || info.IsRepeat)
-            {
-                item.PremiereDate = info.OriginalAirDate;
-            }
-
-            item.IndexNumber = info.EpisodeNumber;
-            item.ParentIndexNumber = info.SeasonNumber;
-
-            if (!item.HasImage(ImageType.Primary))
-            {
-                if (!string.IsNullOrWhiteSpace(info.ImagePath))
-                {
-                    item.SetImage(
-                        new ItemImageInfo
-                        {
-                            Path = info.ImagePath,
-                            Type = ImageType.Primary
-                        },
-                        0);
-                }
-                else if (!string.IsNullOrWhiteSpace(info.ImageUrl))
-                {
-                    item.SetImage(
-                        new ItemImageInfo
-                        {
-                            Path = info.ImageUrl,
-                            Type = ImageType.Primary
-                        },
-                        0);
-                }
-            }
-
-            if (!item.HasImage(ImageType.Thumb))
-            {
-                if (!string.IsNullOrWhiteSpace(info.ThumbImageUrl))
-                {
-                    item.SetImage(
-                        new ItemImageInfo
-                        {
-                            Path = info.ThumbImageUrl,
-                            Type = ImageType.Thumb
-                        },
-                        0);
-                }
-            }
-
-            if (!item.HasImage(ImageType.Logo))
-            {
-                if (!string.IsNullOrWhiteSpace(info.LogoImageUrl))
-                {
-                    item.SetImage(
-                        new ItemImageInfo
-                        {
-                            Path = info.LogoImageUrl,
-                            Type = ImageType.Logo
-                        },
-                        0);
-                }
-            }
-
-            if (!item.HasImage(ImageType.Backdrop))
-            {
-                if (!string.IsNullOrWhiteSpace(info.BackdropImageUrl))
-                {
-                    item.SetImage(
-                        new ItemImageInfo
-                        {
-                            Path = info.BackdropImageUrl,
-                            Type = ImageType.Backdrop
-                        },
-                        0);
-                }
-            }
-
-            var isUpdated = false;
-            if (isNew)
-            {
-            }
-            else if (forceUpdate || string.IsNullOrWhiteSpace(info.Etag))
-            {
-                isUpdated = true;
-            }
-            else
-            {
-                var etag = info.Etag;
-
-                if (!string.Equals(etag, item.GetProviderId(EtagKey), StringComparison.OrdinalIgnoreCase))
-                {
-                    item.SetProviderId(EtagKey, etag);
-                    isUpdated = true;
-                }
-            }
-
-            if (isNew || isUpdated)
-            {
-                item.OnMetadataChanged();
-            }
-
-            return (item, isNew, isUpdated);
-        }
-
         public async Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
         {
             var program = _libraryManager.GetItemById(id);
@@ -1000,293 +633,6 @@ namespace Jellyfin.LiveTv
             }
         }
 
-        internal Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            return RefreshChannelsInternal(progress, cancellationToken);
-        }
-
-        private async Task RefreshChannelsInternal(IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            await EmbyTV.EmbyTV.Current.CreateRecordingFolders().ConfigureAwait(false);
-
-            await _tunerHostManager.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false);
-
-            var numComplete = 0;
-            double progressPerService = _services.Length == 0
-                ? 0
-                : 1.0 / _services.Length;
-
-            var newChannelIdList = new List<Guid>();
-            var newProgramIdList = new List<Guid>();
-
-            var cleanDatabase = true;
-
-            foreach (var service in _services)
-            {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                _logger.LogDebug("Refreshing guide from {Name}", service.Name);
-
-                try
-                {
-                    var innerProgress = new ActionableProgress<double>();
-                    innerProgress.RegisterAction(p => progress.Report(p * progressPerService));
-
-                    var idList = await RefreshChannelsInternal(service, innerProgress, cancellationToken).ConfigureAwait(false);
-
-                    newChannelIdList.AddRange(idList.Item1);
-                    newProgramIdList.AddRange(idList.Item2);
-                }
-                catch (OperationCanceledException)
-                {
-                    throw;
-                }
-                catch (Exception ex)
-                {
-                    cleanDatabase = false;
-                    _logger.LogError(ex, "Error refreshing channels for service");
-                }
-
-                numComplete++;
-                double percent = numComplete;
-                percent /= _services.Length;
-
-                progress.Report(100 * percent);
-            }
-
-            if (cleanDatabase)
-            {
-                CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { BaseItemKind.LiveTvChannel }, progress, cancellationToken);
-                CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { BaseItemKind.LiveTvProgram }, progress, cancellationToken);
-            }
-
-            var coreService = _services.OfType<EmbyTV.EmbyTV>().FirstOrDefault();
-
-            if (coreService is not null)
-            {
-                await coreService.RefreshSeriesTimers(cancellationToken).ConfigureAwait(false);
-                await coreService.RefreshTimers(cancellationToken).ConfigureAwait(false);
-            }
-
-            // Load these now which will prefetch metadata
-            var dtoOptions = new DtoOptions();
-            var fields = dtoOptions.Fields.ToList();
-            dtoOptions.Fields = fields.ToArray();
-
-            progress.Report(100);
-        }
-
-        private async Task<Tuple<List<Guid>, List<Guid>>> RefreshChannelsInternal(ILiveTvService service, ActionableProgress<double> progress, CancellationToken cancellationToken)
-        {
-            progress.Report(10);
-
-            var allChannelsList = (await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false))
-                .Select(i => new Tuple<string, ChannelInfo>(service.Name, i))
-                .ToList();
-
-            var list = new List<LiveTvChannel>();
-
-            var numComplete = 0;
-            var parentFolder = GetInternalLiveTvFolder(cancellationToken);
-
-            foreach (var channelInfo in allChannelsList)
-            {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                try
-                {
-                    var item = await GetChannelAsync(channelInfo.Item2, channelInfo.Item1, parentFolder, cancellationToken).ConfigureAwait(false);
-
-                    list.Add(item);
-                }
-                catch (OperationCanceledException)
-                {
-                    throw;
-                }
-                catch (Exception ex)
-                {
-                    _logger.LogError(ex, "Error getting channel information for {Name}", channelInfo.Item2.Name);
-                }
-
-                numComplete++;
-                double percent = numComplete;
-                percent /= allChannelsList.Count;
-
-                progress.Report((5 * percent) + 10);
-            }
-
-            progress.Report(15);
-
-            numComplete = 0;
-            var programs = new List<Guid>();
-            var channels = new List<Guid>();
-
-            var guideDays = GetGuideDays();
-
-            _logger.LogInformation("Refreshing guide with {0} days of guide data", guideDays);
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            foreach (var currentChannel in list)
-            {
-                channels.Add(currentChannel.Id);
-                cancellationToken.ThrowIfCancellationRequested();
-
-                try
-                {
-                    var start = DateTime.UtcNow.AddHours(-1);
-                    var end = start.AddDays(guideDays);
-
-                    var isMovie = false;
-                    var isSports = false;
-                    var isNews = false;
-                    var isKids = false;
-                    var iSSeries = false;
-
-                    var channelPrograms = (await service.GetProgramsAsync(currentChannel.ExternalId, start, end, cancellationToken).ConfigureAwait(false)).ToList();
-
-                    var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery
-                    {
-                        IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
-                        ChannelIds = new Guid[] { currentChannel.Id },
-                        DtoOptions = new DtoOptions(true)
-                    }).Cast<LiveTvProgram>().ToDictionary(i => i.Id);
-
-                    var newPrograms = new List<LiveTvProgram>();
-                    var updatedPrograms = new List<BaseItem>();
-
-                    foreach (var program in channelPrograms)
-                    {
-                        var programTuple = GetProgram(program, existingPrograms, currentChannel);
-                        var programItem = programTuple.Item;
-
-                        if (programTuple.IsNew)
-                        {
-                            newPrograms.Add(programItem);
-                        }
-                        else if (programTuple.IsUpdated)
-                        {
-                            updatedPrograms.Add(programItem);
-                        }
-
-                        programs.Add(programItem.Id);
-
-                        isMovie |= program.IsMovie;
-                        iSSeries |= program.IsSeries;
-                        isSports |= program.IsSports;
-                        isNews |= program.IsNews;
-                        isKids |= program.IsKids;
-                    }
-
-                    _logger.LogDebug("Channel {0} has {1} new programs and {2} updated programs", currentChannel.Name, newPrograms.Count, updatedPrograms.Count);
-
-                    if (newPrograms.Count > 0)
-                    {
-                        _libraryManager.CreateItems(newPrograms, null, cancellationToken);
-                    }
-
-                    if (updatedPrograms.Count > 0)
-                    {
-                        await _libraryManager.UpdateItemsAsync(
-                            updatedPrograms,
-                            currentChannel,
-                            ItemUpdateType.MetadataImport,
-                            cancellationToken).ConfigureAwait(false);
-                    }
-
-                    currentChannel.IsMovie = isMovie;
-                    currentChannel.IsNews = isNews;
-                    currentChannel.IsSports = isSports;
-                    currentChannel.IsSeries = iSSeries;
-
-                    if (isKids)
-                    {
-                        currentChannel.AddTag("Kids");
-                    }
-
-                    await currentChannel.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
-                    await currentChannel.RefreshMetadata(
-                        new MetadataRefreshOptions(new DirectoryService(_fileSystem))
-                        {
-                            ForceSave = true
-                        },
-                        cancellationToken).ConfigureAwait(false);
-                }
-                catch (OperationCanceledException)
-                {
-                    throw;
-                }
-                catch (Exception ex)
-                {
-                    _logger.LogError(ex, "Error getting programs for channel {Name}", currentChannel.Name);
-                }
-
-                numComplete++;
-                double percent = numComplete / (double)allChannelsList.Count;
-
-                progress.Report((85 * percent) + 15);
-            }
-
-            progress.Report(100);
-            return new Tuple<List<Guid>, List<Guid>>(channels, programs);
-        }
-
-        private void CleanDatabaseInternal(Guid[] currentIdList, BaseItemKind[] validTypes, IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            var list = _itemRepo.GetItemIdsList(new InternalItemsQuery
-            {
-                IncludeItemTypes = validTypes,
-                DtoOptions = new DtoOptions(false)
-            });
-
-            var numComplete = 0;
-
-            foreach (var itemId in list)
-            {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                if (itemId.Equals(default))
-                {
-                    // Somehow some invalid data got into the db. It probably predates the boundary checking
-                    continue;
-                }
-
-                if (!currentIdList.Contains(itemId))
-                {
-                    var item = _libraryManager.GetItemById(itemId);
-
-                    if (item is not null)
-                    {
-                        _libraryManager.DeleteItem(
-                            item,
-                            new DeleteOptions
-                            {
-                                DeleteFileLocation = false,
-                                DeleteFromExternalProvider = false
-                            },
-                            false);
-                    }
-                }
-
-                numComplete++;
-                double percent = numComplete / (double)list.Count;
-
-                progress.Report(100 * percent);
-            }
-        }
-
-        private double GetGuideDays()
-        {
-            var config = _config.GetLiveTvConfiguration();
-
-            if (config.GuideDays.HasValue)
-            {
-                return Math.Max(1, Math.Min(config.GuideDays.Value, MaxGuideDays));
-            }
-
-            return 7;
-        }
-
         private async Task<QueryResult<BaseItem>> GetEmbyRecordingsAsync(RecordingQuery query, DtoOptions dtoOptions, User user)
         {
             if (user is null)
@@ -2056,18 +1402,6 @@ namespace Jellyfin.LiveTv
             await service.UpdateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
         }
 
-        public GuideInfo GetGuideInfo()
-        {
-            var startDate = DateTime.UtcNow;
-            var endDate = startDate.AddDays(GetGuideDays());
-
-            return new GuideInfo
-            {
-                StartDate = startDate,
-                EndDate = endDate
-            };
-        }
-
         private LiveTvServiceInfo[] GetServiceInfos()
         {
             return Services.Select(GetServiceInfo).ToArray();

+ 8 - 6
src/Jellyfin.LiveTv/RefreshGuideScheduledTask.cs

@@ -15,16 +15,22 @@ namespace Jellyfin.LiveTv
     public class RefreshGuideScheduledTask : IScheduledTask, IConfigurableScheduledTask
     {
         private readonly ILiveTvManager _liveTvManager;
+        private readonly IGuideManager _guideManager;
         private readonly IConfigurationManager _config;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="RefreshGuideScheduledTask"/> class.
         /// </summary>
         /// <param name="liveTvManager">The live tv manager.</param>
+        /// <param name="guideManager">The guide manager.</param>
         /// <param name="config">The configuration manager.</param>
-        public RefreshGuideScheduledTask(ILiveTvManager liveTvManager, IConfigurationManager config)
+        public RefreshGuideScheduledTask(
+            ILiveTvManager liveTvManager,
+            IGuideManager guideManager,
+            IConfigurationManager config)
         {
             _liveTvManager = liveTvManager;
+            _guideManager = guideManager;
             _config = config;
         }
 
@@ -51,11 +57,7 @@ namespace Jellyfin.LiveTv
 
         /// <inheritdoc />
         public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            var manager = (LiveTvManager)_liveTvManager;
-
-            return manager.RefreshChannels(progress, cancellationToken);
-        }
+            => _guideManager.RefreshGuide(progress, cancellationToken);
 
         /// <inheritdoc />
         public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()