aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Jellyfin.Drawing.Skia/SkiaEncoder.cs4
-rw-r--r--src/Jellyfin.LiveTv/Guide/GuideManager.cs191
-rw-r--r--src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs31
-rw-r--r--src/Jellyfin.Networking/Manager/NetworkManager.cs9
4 files changed, 134 insertions, 101 deletions
diff --git a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
index c5aadc890..2dac5598f 100644
--- a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
+++ b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
@@ -195,8 +195,10 @@ public class SkiaEncoder : IImageEncoder
return string.Empty;
}
+ // Use FileStream with FileShare.Read instead of having Skia open the file to allow concurrent read access
+ using var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
// Any larger than 128x128 is too slow and there's no visually discernible difference
- return BlurHashEncoder.Encode(xComp, yComp, path, 128, 128);
+ return BlurHashEncoder.Encode(xComp, yComp, fileStream, 128, 128);
}
private bool RequiresSpecialCharacterHack(string path)
diff --git a/src/Jellyfin.LiveTv/Guide/GuideManager.cs b/src/Jellyfin.LiveTv/Guide/GuideManager.cs
index ff31b7123..b75cc0fb2 100644
--- a/src/Jellyfin.LiveTv/Guide/GuideManager.cs
+++ b/src/Jellyfin.LiveTv/Guide/GuideManager.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Data.Entities.Libraries;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using Jellyfin.LiveTv.Configuration;
@@ -40,6 +41,11 @@ public class GuideManager : IGuideManager
private readonly LiveTvDtoService _tvDtoService;
/// <summary>
+ /// Amount of days images are pre-cached from external sources.
+ /// </summary>
+ public const int MaxCacheDays = 2;
+
+ /// <summary>
/// Initializes a new instance of the <see cref="GuideManager"/> class.
/// </summary>
/// <param name="logger">The <see cref="ILogger{TCategoryName}"/>.</param>
@@ -204,14 +210,14 @@ public class GuideManager : IGuideManager
progress.Report(15);
numComplete = 0;
- var programs = new List<Guid>();
+ var programs = new List<LiveTvProgram>();
var channels = new List<Guid>();
var guideDays = GetGuideDays();
- _logger.LogInformation("Refreshing guide with {0} days of guide data", guideDays);
+ _logger.LogInformation("Refreshing guide with {Days} days of guide data", guideDays);
- var maxCacheDate = DateTime.UtcNow.AddDays(2);
+ var maxCacheDate = DateTime.UtcNow.AddDays(MaxCacheDays);
foreach (var currentChannel in list)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -237,22 +243,23 @@ public class GuideManager : IGuideManager
DtoOptions = new DtoOptions(true)
}).Cast<LiveTvProgram>().ToDictionary(i => i.Id);
- var newPrograms = new List<LiveTvProgram>();
- var updatedPrograms = new List<BaseItem>();
+ var newPrograms = new List<Guid>();
+ var updatedPrograms = new List<Guid>();
foreach (var program in channelPrograms)
{
var (programItem, isNew, isUpdated) = GetProgram(program, existingPrograms, currentChannel);
+ var id = programItem.Id;
if (isNew)
{
- newPrograms.Add(programItem);
+ newPrograms.Add(id);
}
else if (isUpdated)
{
- updatedPrograms.Add(programItem);
+ updatedPrograms.Add(id);
}
- programs.Add(programItem.Id);
+ programs.Add(programItem);
isMovie |= program.IsMovie;
isSeries |= program.IsSeries;
@@ -261,24 +268,30 @@ public class GuideManager : IGuideManager
isKids |= program.IsKids;
}
- _logger.LogDebug("Channel {0} has {1} new programs and {2} updated programs", currentChannel.Name, newPrograms.Count, updatedPrograms.Count);
+ _logger.LogDebug(
+ "Channel {Name} has {NewCount} new programs and {UpdatedCount} updated programs",
+ currentChannel.Name,
+ newPrograms.Count,
+ updatedPrograms.Count);
if (newPrograms.Count > 0)
{
- _libraryManager.CreateOrUpdateItems(newPrograms, null, cancellationToken);
- await PrecacheImages(newPrograms, maxCacheDate).ConfigureAwait(false);
+ var newProgramDtos = programs.Where(b => newPrograms.Contains(b.Id)).ToList();
+ _libraryManager.CreateOrUpdateItems(newProgramDtos, null, cancellationToken);
}
if (updatedPrograms.Count > 0)
{
+ var updatedProgramDtos = programs.Where(b => updatedPrograms.Contains(b.Id)).ToList();
await _libraryManager.UpdateItemsAsync(
- updatedPrograms,
+ updatedProgramDtos,
currentChannel,
ItemUpdateType.MetadataImport,
cancellationToken).ConfigureAwait(false);
- await PrecacheImages(updatedPrograms, maxCacheDate).ConfigureAwait(false);
}
+ await PreCacheImages(programs, maxCacheDate).ConfigureAwait(false);
+
currentChannel.IsMovie = isMovie;
currentChannel.IsNews = isNews;
currentChannel.IsSports = isSports;
@@ -313,7 +326,8 @@ public class GuideManager : IGuideManager
}
progress.Report(100);
- return new Tuple<List<Guid>, List<Guid>>(channels, programs);
+ var programIds = programs.Select(p => p.Id).ToList();
+ return new Tuple<List<Guid>, List<Guid>>(channels, programIds);
}
private void CleanDatabase(Guid[] currentIdList, BaseItemKind[] validTypes, IProgress<double> progress, CancellationToken cancellationToken)
@@ -618,77 +632,17 @@ public class GuideManager : IGuideManager
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);
- }
- }
+ forceUpdate = forceUpdate || UpdateImages(item, info);
- 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 (isNew)
{
- if (!string.IsNullOrWhiteSpace(info.LogoImageUrl))
- {
- item.SetImage(
- new ItemImageInfo
- {
- Path = info.LogoImageUrl,
- Type = ImageType.Logo
- },
- 0);
- }
- }
+ item.OnMetadataChanged();
- if (!item.HasImage(ImageType.Backdrop))
- {
- if (!string.IsNullOrWhiteSpace(info.BackdropImageUrl))
- {
- item.SetImage(
- new ItemImageInfo
- {
- Path = info.BackdropImageUrl,
- Type = ImageType.Backdrop
- },
- 0);
- }
+ return (item, isNew, false);
}
var isUpdated = false;
- if (isNew)
- {
- }
- else if (forceUpdate || string.IsNullOrWhiteSpace(info.Etag))
+ if (forceUpdate || string.IsNullOrWhiteSpace(info.Etag))
{
isUpdated = true;
}
@@ -703,7 +657,7 @@ public class GuideManager : IGuideManager
}
}
- if (isNew || isUpdated)
+ if (isUpdated)
{
item.OnMetadataChanged();
}
@@ -711,7 +665,80 @@ public class GuideManager : IGuideManager
return (item, isNew, isUpdated);
}
- private async Task PrecacheImages(IReadOnlyList<BaseItem> programs, DateTime maxCacheDate)
+ private static bool UpdateImages(BaseItem item, ProgramInfo info)
+ {
+ var updated = false;
+
+ // Primary
+ updated |= UpdateImage(ImageType.Primary, item, info);
+
+ // Thumbnail
+ updated |= UpdateImage(ImageType.Thumb, item, info);
+
+ // Logo
+ updated |= UpdateImage(ImageType.Logo, item, info);
+
+ // Backdrop
+ return updated || UpdateImage(ImageType.Backdrop, item, info);
+ }
+
+ private static bool UpdateImage(ImageType imageType, BaseItem item, ProgramInfo info)
+ {
+ var image = item.GetImages(imageType).FirstOrDefault();
+ var currentImagePath = image?.Path;
+ var newImagePath = imageType switch
+ {
+ ImageType.Primary => info.ImagePath,
+ _ => string.Empty
+ };
+ var newImageUrl = imageType switch
+ {
+ ImageType.Backdrop => info.BackdropImageUrl,
+ ImageType.Logo => info.LogoImageUrl,
+ ImageType.Primary => info.ImageUrl,
+ ImageType.Thumb => info.ThumbImageUrl,
+ _ => string.Empty
+ };
+
+ var differentImage = newImageUrl?.Equals(currentImagePath, StringComparison.OrdinalIgnoreCase) == false
+ || newImagePath?.Equals(currentImagePath, StringComparison.OrdinalIgnoreCase) == false;
+ if (!differentImage)
+ {
+ return false;
+ }
+
+ if (!string.IsNullOrWhiteSpace(newImagePath))
+ {
+ item.SetImage(
+ new ItemImageInfo
+ {
+ Path = newImagePath,
+ Type = imageType
+ },
+ 0);
+
+ return true;
+ }
+
+ if (!string.IsNullOrWhiteSpace(newImageUrl))
+ {
+ item.SetImage(
+ new ItemImageInfo
+ {
+ Path = newImageUrl,
+ Type = imageType
+ },
+ 0);
+
+ return true;
+ }
+
+ item.RemoveImage(image);
+
+ return false;
+ }
+
+ private async Task PreCacheImages(IReadOnlyList<BaseItem> programs, DateTime maxCacheDate)
{
await Parallel.ForEachAsync(
programs
@@ -741,7 +768,7 @@ public class GuideManager : IGuideManager
}
catch (Exception ex)
{
- _logger.LogWarning(ex, "Unable to precache {Url}", imageInfo.Path);
+ _logger.LogWarning(ex, "Unable to pre-cache {Url}", imageInfo.Path);
}
}
}
diff --git a/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs
index 790f60cf0..d6f15906e 100644
--- a/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs
@@ -19,6 +19,7 @@ using System.Threading.Tasks;
using AsyncKeyedLock;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
+using Jellyfin.LiveTv.Guide;
using Jellyfin.LiveTv.Listings.SchedulesDirectDtos;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Authentication;
@@ -38,7 +39,7 @@ namespace Jellyfin.LiveTv.Listings
private readonly IHttpClientFactory _httpClientFactory;
private readonly AsyncNonKeyedLocker _tokenLock = new(1);
- private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>();
+ private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new();
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private DateTime _lastErrorResponse;
private bool _disposed = false;
@@ -86,7 +87,7 @@ namespace Jellyfin.LiveTv.Listings
{
_logger.LogWarning("SchedulesDirect token is empty, returning empty program list");
- return Enumerable.Empty<ProgramInfo>();
+ return [];
}
var dates = GetScheduleRequestDates(startDateUtc, endDateUtc);
@@ -94,7 +95,7 @@ namespace Jellyfin.LiveTv.Listings
_logger.LogInformation("Channel Station ID is: {ChannelID}", channelId);
var requestList = new List<RequestScheduleForChannelDto>()
{
- new RequestScheduleForChannelDto()
+ new()
{
StationId = channelId,
Date = dates
@@ -109,7 +110,7 @@ namespace Jellyfin.LiveTv.Listings
var dailySchedules = await Request<IReadOnlyList<DayDto>>(options, true, info, cancellationToken).ConfigureAwait(false);
if (dailySchedules is null)
{
- return Array.Empty<ProgramInfo>();
+ return [];
}
_logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId);
@@ -120,17 +121,17 @@ namespace Jellyfin.LiveTv.Listings
var programIds = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct();
programRequestOptions.Content = JsonContent.Create(programIds, options: _jsonOptions);
- var programDetails = await Request<IReadOnlyList<ProgramDetailsDto>>(programRequestOptions, true, info, cancellationToken)
- .ConfigureAwait(false);
+ var programDetails = await Request<IReadOnlyList<ProgramDetailsDto>>(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false);
if (programDetails is null)
{
- return Array.Empty<ProgramInfo>();
+ return [];
}
var programDict = programDetails.ToDictionary(p => p.ProgramId, y => y);
var programIdsWithImages = programDetails
- .Where(p => p.HasImageArtwork).Select(p => p.ProgramId)
+ .Where(p => p.HasImageArtwork)
+ .Select(p => p.ProgramId)
.ToList();
var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false);
@@ -138,17 +139,15 @@ namespace Jellyfin.LiveTv.Listings
var programsInfo = new List<ProgramInfo>();
foreach (ProgramDto schedule in dailySchedules.SelectMany(d => d.Programs))
{
- // _logger.LogDebug("Processing Schedule for station ID " + stationID +
- // " which corresponds to channel " + channelNumber + " and program id " +
- // schedule.ProgramId + " which says it has images? " +
- // programDict[schedule.ProgramId].hasImageArtwork);
-
if (string.IsNullOrEmpty(schedule.ProgramId))
{
continue;
}
- if (images is not null)
+ // Only add images which will be pre-cached until we can implement dynamic token fetching
+ var endDate = schedule.AirDateTime?.AddSeconds(schedule.Duration);
+ var willBeCached = endDate.HasValue && endDate.Value < DateTime.UtcNow.AddDays(GuideManager.MaxCacheDays);
+ if (willBeCached && images is not null)
{
var imageIndex = images.FindIndex(i => i.ProgramId == schedule.ProgramId[..10]);
if (imageIndex > -1)
@@ -456,7 +455,7 @@ namespace Jellyfin.LiveTv.Listings
if (programIds.Count == 0)
{
- return Array.Empty<ShowImagesDto>();
+ return [];
}
StringBuilder str = new StringBuilder("[", 1 + (programIds.Count * 13));
@@ -483,7 +482,7 @@ namespace Jellyfin.LiveTv.Listings
{
_logger.LogError(ex, "Error getting image info from schedules direct");
- return Array.Empty<ShowImagesDto>();
+ return [];
}
}
diff --git a/src/Jellyfin.Networking/Manager/NetworkManager.cs b/src/Jellyfin.Networking/Manager/NetworkManager.cs
index 3f71770b5..dd01e9533 100644
--- a/src/Jellyfin.Networking/Manager/NetworkManager.cs
+++ b/src/Jellyfin.Networking/Manager/NetworkManager.cs
@@ -702,7 +702,7 @@ public class NetworkManager : INetworkManager, IDisposable
return false;
}
}
- else if (!_lanSubnets.Any(x => x.Contains(remoteIP)))
+ else if (!IsInLocalNetwork(remoteIP))
{
// Remote not enabled. So everyone should be LAN.
return false;
@@ -997,7 +997,9 @@ public class NetworkManager : INetworkManager, IDisposable
// Get interface matching override subnet
var intf = _interfaces.OrderBy(x => x.Index).FirstOrDefault(x => data.Data.Subnet.Contains(x.Address));
- if (intf?.Address is not null)
+ if (intf?.Address is not null
+ || (data.Data.AddressFamily == AddressFamily.InterNetwork && data.Data.Address.Equals(IPAddress.Any))
+ || (data.Data.AddressFamily == AddressFamily.InterNetworkV6 && data.Data.Address.Equals(IPAddress.IPv6Any)))
{
// If matching interface is found, use override
bindPreference = data.OverrideUri;
@@ -1025,6 +1027,7 @@ public class NetworkManager : INetworkManager, IDisposable
}
_logger.LogDebug("{Source}: Matching bind address override found: {Address}", source, bindPreference);
+
return true;
}
@@ -1063,6 +1066,7 @@ public class NetworkManager : INetworkManager, IDisposable
// If none exists, this will select the first external interface if there is one.
bindAddress = externalInterfaces
.OrderByDescending(x => x.Subnet.Contains(source))
+ .ThenByDescending(x => x.Subnet.PrefixLength)
.ThenBy(x => x.Index)
.Select(x => x.Address)
.First();
@@ -1080,6 +1084,7 @@ public class NetworkManager : INetworkManager, IDisposable
// If none exists, this will select the first internal interface if there is one.
bindAddress = _interfaces.Where(x => IsInLocalNetwork(x.Address))
.OrderByDescending(x => x.Subnet.Contains(source))
+ .ThenByDescending(x => x.Subnet.PrefixLength)
.ThenBy(x => x.Index)
.Select(x => x.Address)
.FirstOrDefault();