diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 1b2f5750f6..da68c72c99 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -43,6 +43,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;
@@ -56,6 +57,7 @@ public class LiveTvController : BaseJellyfinApiController
/// Initializes a new instance of the class.
///
/// Instance of the interface.
+ /// Instance of the interface.
/// Instance of the interface.
/// Instance of the interface.
/// Instance of the interface.
@@ -66,6 +68,7 @@ public class LiveTvController : BaseJellyfinApiController
/// Instance of the interface.
public LiveTvController(
ILiveTvManager liveTvManager,
+ IGuideManager guideManager,
ITunerHostManager tunerHostManager,
IUserManager userManager,
IHttpClientFactory httpClientFactory,
@@ -76,6 +79,7 @@ public class LiveTvController : BaseJellyfinApiController
ITranscodeManager transcodeManager)
{
_liveTvManager = liveTvManager;
+ _guideManager = guideManager;
_tunerHostManager = tunerHostManager;
_userManager = userManager;
_httpClientFactory = httpClientFactory;
@@ -941,9 +945,7 @@ public class LiveTvController : BaseJellyfinApiController
[Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult GetGuideInfo()
- {
- return _liveTvManager.GetGuideInfo();
- }
+ => _guideManager.GetGuideInfo();
///
/// Adds a tuner host.
diff --git a/MediaBrowser.Controller/LiveTv/IGuideManager.cs b/MediaBrowser.Controller/LiveTv/IGuideManager.cs
new file mode 100644
index 0000000000..9883b9283c
--- /dev/null
+++ b/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;
+
+///
+/// Service responsible for managing the Live TV guide.
+///
+public interface IGuideManager
+{
+ ///
+ /// Gets the guide information.
+ ///
+ /// The .
+ GuideInfo GetGuideInfo();
+
+ ///
+ /// Refresh the guide.
+ ///
+ /// The to use.
+ /// The to use.
+ /// Task representing the refresh operation.
+ Task RefreshGuide(IProgress progress, CancellationToken cancellationToken);
+}
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
index 26f9fe42d3..2dbc2cf82e 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
@@ -174,12 +174,6 @@ namespace MediaBrowser.Controller.LiveTv
/// Task.
Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken);
- ///
- /// Gets the guide information.
- ///
- /// GuideInfo.
- GuideInfo GetGuideInfo();
-
///
/// Gets the recommended programs.
///
diff --git a/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs b/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs
index 5490547ec3..21dab69e05 100644
--- a/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs
+++ b/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();
services.AddSingleton();
services.AddSingleton();
+ services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
diff --git a/src/Jellyfin.LiveTv/Guide/GuideManager.cs b/src/Jellyfin.LiveTv/Guide/GuideManager.cs
new file mode 100644
index 0000000000..bfbc6d4cc6
--- /dev/null
+++ b/src/Jellyfin.LiveTv/Guide/GuideManager.cs
@@ -0,0 +1,709 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
+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;
+
+///
+public class GuideManager : IGuideManager
+{
+ private const int MaxGuideDays = 14;
+ private const string EtagKey = "ProgramEtag";
+ private const string ExternalServiceTag = "ExternalServiceId";
+
+ private readonly ILogger _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;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ public GuideManager(
+ ILogger 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;
+ }
+
+ ///
+ public GuideInfo GetGuideInfo()
+ {
+ var startDate = DateTime.UtcNow;
+ var endDate = startDate.AddDays(GetGuideDays());
+
+ return new GuideInfo
+ {
+ StartDate = startDate,
+ EndDate = endDate
+ };
+ }
+
+ ///
+ public async Task RefreshGuide(IProgress 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();
+ var newProgramIdList = new List();
+
+ var cleanDatabase = true;
+
+ foreach (var service in _liveTvManager.Services)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ _logger.LogDebug("Refreshing guide from {Name}", service.Name);
+
+ try
+ {
+ var innerProgress = new ActionableProgress();
+ 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().FirstOrDefault();
+ if (coreService is not null)
+ {
+ await coreService.RefreshSeriesTimers(cancellationToken).ConfigureAwait(false);
+ await coreService.RefreshTimers(cancellationToken).ConfigureAwait(false);
+ }
+
+ progress.Report(100);
+ }
+
+ private double GetGuideDays()
+ {
+ var config = _config.GetLiveTvConfiguration();
+
+ return config.GuideDays.HasValue
+ ? Math.Clamp(config.GuideDays.Value, 1, MaxGuideDays)
+ : 7;
+ }
+
+ private async Task, List>> RefreshChannelsInternal(ILiveTvService service, ActionableProgress progress, CancellationToken cancellationToken)
+ {
+ progress.Report(10);
+
+ var allChannelsList = (await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false))
+ .Select(i => new Tuple(service.Name, i))
+ .ToList();
+
+ var list = new List();
+
+ 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();
+ var channels = new List();
+
+ 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 = [currentChannel.Id],
+ DtoOptions = new DtoOptions(true)
+ }).Cast().ToDictionary(i => i.Id);
+
+ var newPrograms = new List();
+ var updatedPrograms = new List();
+
+ 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>(channels, programs);
+ }
+
+ private void CleanDatabase(Guid[] currentIdList, BaseItemKind[] validTypes, IProgress 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.IsEmpty())
+ {
+ // 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 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 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();
+ 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);
+ }
+}
diff --git a/src/Jellyfin.LiveTv/Guide/RefreshGuideScheduledTask.cs b/src/Jellyfin.LiveTv/Guide/RefreshGuideScheduledTask.cs
new file mode 100644
index 0000000000..a9fde08501
--- /dev/null
+++ b/src/Jellyfin.LiveTv/Guide/RefreshGuideScheduledTask.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Jellyfin.LiveTv.Configuration;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.Tasks;
+
+namespace Jellyfin.LiveTv.Guide;
+
+///
+/// The "Refresh Guide" scheduled task.
+///
+public class RefreshGuideScheduledTask : IScheduledTask, IConfigurableScheduledTask
+{
+ private readonly ILiveTvManager _liveTvManager;
+ private readonly IGuideManager _guideManager;
+ private readonly IConfigurationManager _config;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The live tv manager.
+ /// The guide manager.
+ /// The configuration manager.
+ public RefreshGuideScheduledTask(
+ ILiveTvManager liveTvManager,
+ IGuideManager guideManager,
+ IConfigurationManager config)
+ {
+ _liveTvManager = liveTvManager;
+ _guideManager = guideManager;
+ _config = config;
+ }
+
+ ///
+ public string Name => "Refresh Guide";
+
+ ///
+ public string Description => "Downloads channel information from live tv services.";
+
+ ///
+ public string Category => "Live TV";
+
+ ///
+ public bool IsHidden => _liveTvManager.Services.Count == 1 && _config.GetLiveTvConfiguration().TunerHosts.Length == 0;
+
+ ///
+ public bool IsEnabled => true;
+
+ ///
+ public bool IsLogged => true;
+
+ ///
+ public string Key => "RefreshGuide";
+
+ ///
+ public Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken)
+ => _guideManager.RefreshGuide(progress, cancellationToken);
+
+ ///
+ public IEnumerable GetDefaultTriggers()
+ {
+ return new[]
+ {
+ new TaskTriggerInfo
+ {
+ Type = TaskTriggerInfo.TriggerInterval,
+ IntervalTicks = TimeSpan.FromHours(24).Ticks
+ }
+ };
+ }
+}
diff --git a/src/Jellyfin.LiveTv/LiveTvManager.cs b/src/Jellyfin.LiveTv/LiveTvManager.cs
index bada4249aa..aa3be2048a 100644
--- a/src/Jellyfin.LiveTv/LiveTvManager.cs
+++ b/src/Jellyfin.LiveTv/LiveTvManager.cs
@@ -12,23 +12,19 @@ using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Data.Events;
-using Jellyfin.Extensions;
using Jellyfin.LiveTv.Configuration;
+using Jellyfin.LiveTv.Guide;
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;
@@ -41,24 +37,16 @@ namespace Jellyfin.LiveTv
///
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 _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();
private IListingsProvider[] _listingProviders = Array.Empty();
@@ -66,31 +54,25 @@ namespace Jellyfin.LiveTv
public LiveTvManager(
IServerConfigurationManager config,
ILogger 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> SeriesTimerCancelled;
@@ -151,7 +133,7 @@ namespace Jellyfin.LiveTv
public QueryResult GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken)
{
- var user = query.UserId.IsEmpty()
+ var user = query.UserId.Equals(default)
? null
: _userManager.GetUserById(query.UserId);
@@ -401,355 +383,6 @@ namespace Jellyfin.LiveTv
}
}
- private async Task 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 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();
- 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 GetProgram(string id, CancellationToken cancellationToken, User user = null)
{
var program = _libraryManager.GetItemById(id);
@@ -1001,293 +634,6 @@ namespace Jellyfin.LiveTv
}
}
- internal Task RefreshChannels(IProgress progress, CancellationToken cancellationToken)
- {
- return RefreshChannelsInternal(progress, cancellationToken);
- }
-
- private async Task RefreshChannelsInternal(IProgress 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();
- var newProgramIdList = new List();
-
- var cleanDatabase = true;
-
- foreach (var service in _services)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- _logger.LogDebug("Refreshing guide from {Name}", service.Name);
-
- try
- {
- var innerProgress = new ActionableProgress();
- 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().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, List>> RefreshChannelsInternal(ILiveTvService service, ActionableProgress progress, CancellationToken cancellationToken)
- {
- progress.Report(10);
-
- var allChannelsList = (await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false))
- .Select(i => new Tuple(service.Name, i))
- .ToList();
-
- var list = new List();
-
- 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();
- var channels = new List();
-
- 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().ToDictionary(i => i.Id);
-
- var newPrograms = new List();
- var updatedPrograms = new List();
-
- 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>(channels, programs);
- }
-
- private void CleanDatabaseInternal(Guid[] currentIdList, BaseItemKind[] validTypes, IProgress 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.IsEmpty())
- {
- // 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> GetEmbyRecordingsAsync(RecordingQuery query, DtoOptions dtoOptions, User user)
{
if (user is null)
@@ -1505,7 +851,7 @@ namespace Jellyfin.LiveTv
public async Task> GetRecordingsAsync(RecordingQuery query, DtoOptions options)
{
- var user = query.UserId.IsEmpty()
+ var user = query.UserId.Equals(default)
? null
: _userManager.GetUserById(query.UserId);
@@ -2057,18 +1403,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();
diff --git a/src/Jellyfin.LiveTv/RefreshGuideScheduledTask.cs b/src/Jellyfin.LiveTv/RefreshGuideScheduledTask.cs
deleted file mode 100644
index 18bd61d999..0000000000
--- a/src/Jellyfin.LiveTv/RefreshGuideScheduledTask.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using Jellyfin.LiveTv.Configuration;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Model.Tasks;
-
-namespace Jellyfin.LiveTv
-{
- ///
- /// The "Refresh Guide" scheduled task.
- ///
- public class RefreshGuideScheduledTask : IScheduledTask, IConfigurableScheduledTask
- {
- private readonly ILiveTvManager _liveTvManager;
- private readonly IConfigurationManager _config;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The live tv manager.
- /// The configuration manager.
- public RefreshGuideScheduledTask(ILiveTvManager liveTvManager, IConfigurationManager config)
- {
- _liveTvManager = liveTvManager;
- _config = config;
- }
-
- ///
- public string Name => "Refresh Guide";
-
- ///
- public string Description => "Downloads channel information from live tv services.";
-
- ///
- public string Category => "Live TV";
-
- ///
- public bool IsHidden => _liveTvManager.Services.Count == 1 && _config.GetLiveTvConfiguration().TunerHosts.Length == 0;
-
- ///
- public bool IsEnabled => true;
-
- ///
- public bool IsLogged => true;
-
- ///
- public string Key => "RefreshGuide";
-
- ///
- public Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken)
- {
- var manager = (LiveTvManager)_liveTvManager;
-
- return manager.RefreshChannels(progress, cancellationToken);
- }
-
- ///
- public IEnumerable GetDefaultTriggers()
- {
- return new[]
- {
- // Every so often
- new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks }
- };
- }
- }
-}
diff --git a/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs b/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs
index 3e4b0e13fc..60be19c68a 100644
--- a/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs
@@ -6,6 +6,7 @@ using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.LiveTv.Configuration;
+using Jellyfin.LiveTv.Guide;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.LiveTv;