Compare commits

...

22 Commits

Author SHA1 Message Date
Jellyfin Release Bot a5b771861f Bump version to 10.10.6 2025-02-16 16:55:41 -05:00
gnattu 3f539472f3 Fix image encoding concurrency limit (#13532)
* Fix image encoding concurrency limit

The current FFmpeg image extractor is configured to use a resource pool size that always equals 2 times the number of CPU cores, which is somewhat excessive. Make the default equal to the core count instead of twice, and respect the `ParallelImageEncodingLimit` option.

* Fix code stype

* Check null value for unit tests
2025-02-12 18:45:36 -07:00
Cody Robibero 7f43521b64 Merge pull request #13489 from gnattu/fix-subnet-detect
Fix subnet contains check
2025-02-12 07:55:53 -07:00
gnattu 99006c370f Fix more typo 2025-02-10 05:20:34 +08:00
Cody Robibero e3f9f0a7f3 Update dependency z440.atl.core to 6.16.0 (#13526) 2025-02-09 18:41:41 +01:00
elfalem d1fbdcee34 Skip allowed tags check for parents of an item (#12721) 2025-02-09 09:49:02 -07:00
Tim Eisele 21e398ba0c Fix SchedulesDirect image prefetching (#13469)
* Only prefetch images for new and updated programs

* Update src/Jellyfin.LiveTv/Guide/GuideManager.cs
2025-02-09 09:45:03 -07:00
Marc Brooks 8544e7fc72 Fix LiveTV Guide Backdrop image not updating (#13504)
If the guide data changes an any other image, the Backdrop would not be updated.

Fixes #13496
2025-02-09 09:44:47 -07:00
Joshua M. Boniface 117d2082aa Merge pull request #13490 from gnattu/fix-audio-date-number-fallback
Correctly handle audio number tag fallbacks
2025-02-09 00:15:13 -05:00
Joshua M. Boniface 03082e90f9 Merge pull request #13499 from crobibero/api-key-websocket
Allow api key to subscribe to admin websockets
2025-02-09 00:14:46 -05:00
Cody Robibero 88026518b1 Allow api key to subscribe to admin websockets 2025-02-04 17:33:56 -07:00
Bond-009 5f1fb26382 Fix rating mistake in us.csv (#13487) 2025-02-04 17:10:31 -07:00
gnattu 070d04c1b2 Typo
Co-authored-by: Cody Robibero <cody@robibe.ro>
2025-02-05 08:04:43 +08:00
Cody Robibero 8aa4e2e320 Merge pull request #13439 from Bond-009/fallbackmimetype
Fall back to calculating mime type from path when needed
2025-02-04 17:00:55 -07:00
JPVenson 49bb5a6442 Merge pull request #13459 from JPVenson/bugfix/13457_FixWebSocketControllerConcurrency
Fixed Websocket not locking state correctly
2025-02-04 14:59:06 +01:00
Bond_009 9e869b4541 Simplify logic in ProviderManager.SaveImage 2025-02-04 14:56:52 +01:00
gnattu 710e877762 Correctly handle audio number tag fallbacks
Although the number type is nullable from the type definition of ATL, the lib might still normalize all unknown values to 0 which makes doing null check only not enough. Fallback to ffprobe results when the number is 0 as well.
2025-02-04 07:21:18 +08:00
gnattu f536e08e14 Fix subnet contains check
We are still using `Subnet.Contains` a lot but that does not handle IPv4 mapped to IPv6 addresses at all. It was partially fixed by #12094 in local network checking, but it may not always happen on LAN.

Also make all local network checking to use IsInLocalNetwork method instead of just performing `Subnet.Contains` which is not accurate.

Filter out all link-local addresses for external interface matching.
2025-02-04 07:07:21 +08:00
Bond_009 4eecfee29f Fall back to calculating mime type from path when needed
Should fix #12593
2025-02-03 17:45:48 +01:00
Bond-009 731874429c Merge pull request #13448 from Shadowghost/fix-interface-selection-again
Fix interface ordering again
2025-02-03 17:43:11 +01:00
Tim Eisele e6c6441abf Take subnet size into account 2025-01-27 10:59:50 +01:00
Tim Eisele 4d89a095ed Fix interface ordering again 2025-01-27 10:55:30 +01:00
25 changed files with 222 additions and 152 deletions
+1 -1
View File
@@ -80,7 +80,7 @@
<PackageVersion Include="System.Text.Json" Version="8.0.5" /> <PackageVersion Include="System.Text.Json" Version="8.0.5" />
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="8.0.1" /> <PackageVersion Include="System.Threading.Tasks.Dataflow" Version="8.0.1" />
<PackageVersion Include="TagLibSharp" Version="2.3.0" /> <PackageVersion Include="TagLibSharp" Version="2.3.0" />
<PackageVersion Include="z440.atl.core" Version="6.11.0" /> <PackageVersion Include="z440.atl.core" Version="6.16.0" />
<PackageVersion Include="TMDbLib" Version="2.2.0" /> <PackageVersion Include="TMDbLib" Version="2.2.0" />
<PackageVersion Include="UTF.Unknown" Version="2.5.1" /> <PackageVersion Include="UTF.Unknown" Version="2.5.1" />
<PackageVersion Include="Xunit.Priority" Version="1.1.6" /> <PackageVersion Include="Xunit.Priority" Version="1.1.6" />
+1 -1
View File
@@ -36,7 +36,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Naming</PackageId> <PackageId>Jellyfin.Naming</PackageId>
<VersionPrefix>10.10.5</VersionPrefix> <VersionPrefix>10.10.6</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
@@ -15,7 +15,7 @@ TV-PG-DS,10
TV-PG-DV,10 TV-PG-DV,10
TV-PG-LS,10 TV-PG-LS,10
TV-PG-LV,10 TV-PG-LV,10
TV-PG-SV,1 TV-PG-SV,10
TV-PG-DLS,10 TV-PG-DLS,10
TV-PG-DLV,10 TV-PG-DLV,10
TV-PG-DSV,10 TV-PG-DSV,10
1 Approved 0
15 TV-PG-DV 10
16 TV-PG-LS 10
17 TV-PG-LV 10
18 TV-PG-SV 1 10
19 TV-PG-DLS 10
20 TV-PG-DLV 10
21 TV-PG-DSV 10
@@ -21,6 +21,7 @@ namespace Emby.Server.Implementations.Session
private readonly SessionInfo _session; private readonly SessionInfo _session;
private readonly List<IWebSocketConnection> _sockets; private readonly List<IWebSocketConnection> _sockets;
private readonly ReaderWriterLockSlim _socketsLock;
private bool _disposed = false; private bool _disposed = false;
public WebSocketController( public WebSocketController(
@@ -31,10 +32,26 @@ namespace Emby.Server.Implementations.Session
_logger = logger; _logger = logger;
_session = session; _session = session;
_sessionManager = sessionManager; _sessionManager = sessionManager;
_sockets = new List<IWebSocketConnection>(); _sockets = new();
_socketsLock = new();
} }
private bool HasOpenSockets => GetActiveSockets().Any(); private bool HasOpenSockets
{
get
{
ObjectDisposedException.ThrowIf(_disposed, this);
try
{
_socketsLock.EnterReadLock();
return _sockets.Any(i => i.State == WebSocketState.Open);
}
finally
{
_socketsLock.ExitReadLock();
}
}
}
/// <inheritdoc /> /// <inheritdoc />
public bool SupportsMediaControl => HasOpenSockets; public bool SupportsMediaControl => HasOpenSockets;
@@ -42,23 +59,38 @@ namespace Emby.Server.Implementations.Session
/// <inheritdoc /> /// <inheritdoc />
public bool IsSessionActive => HasOpenSockets; public bool IsSessionActive => HasOpenSockets;
private IEnumerable<IWebSocketConnection> GetActiveSockets()
=> _sockets.Where(i => i.State == WebSocketState.Open);
public void AddWebSocket(IWebSocketConnection connection) public void AddWebSocket(IWebSocketConnection connection)
{ {
_logger.LogDebug("Adding websocket to session {Session}", _session.Id); _logger.LogDebug("Adding websocket to session {Session}", _session.Id);
_sockets.Add(connection); ObjectDisposedException.ThrowIf(_disposed, this);
try
connection.Closed += OnConnectionClosed; {
_socketsLock.EnterWriteLock();
_sockets.Add(connection);
connection.Closed += OnConnectionClosed;
}
finally
{
_socketsLock.ExitWriteLock();
}
} }
private async void OnConnectionClosed(object? sender, EventArgs e) private async void OnConnectionClosed(object? sender, EventArgs e)
{ {
var connection = sender as IWebSocketConnection ?? throw new ArgumentException($"{nameof(sender)} is not of type {nameof(IWebSocketConnection)}", nameof(sender)); var connection = sender as IWebSocketConnection ?? throw new ArgumentException($"{nameof(sender)} is not of type {nameof(IWebSocketConnection)}", nameof(sender));
_logger.LogDebug("Removing websocket from session {Session}", _session.Id); _logger.LogDebug("Removing websocket from session {Session}", _session.Id);
_sockets.Remove(connection); ObjectDisposedException.ThrowIf(_disposed, this);
connection.Closed -= OnConnectionClosed; try
{
_socketsLock.EnterWriteLock();
_sockets.Remove(connection);
connection.Closed -= OnConnectionClosed;
}
finally
{
_socketsLock.ExitWriteLock();
}
await _sessionManager.CloseIfNeededAsync(_session).ConfigureAwait(false); await _sessionManager.CloseIfNeededAsync(_session).ConfigureAwait(false);
} }
@@ -69,7 +101,17 @@ namespace Emby.Server.Implementations.Session
T data, T data,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var socket = GetActiveSockets().MaxBy(i => i.LastActivityDate); ObjectDisposedException.ThrowIf(_disposed, this);
IWebSocketConnection? socket;
try
{
_socketsLock.EnterReadLock();
socket = _sockets.Where(i => i.State == WebSocketState.Open).MaxBy(i => i.LastActivityDate);
}
finally
{
_socketsLock.ExitReadLock();
}
if (socket is null) if (socket is null)
{ {
@@ -94,12 +136,23 @@ namespace Emby.Server.Implementations.Session
return; return;
} }
foreach (var socket in _sockets) try
{ {
socket.Closed -= OnConnectionClosed; _socketsLock.EnterWriteLock();
socket.Dispose(); foreach (var socket in _sockets)
{
socket.Closed -= OnConnectionClosed;
socket.Dispose();
}
_sockets.Clear();
}
finally
{
_socketsLock.ExitWriteLock();
} }
_socketsLock.Dispose();
_disposed = true; _disposed = true;
} }
@@ -110,12 +163,23 @@ namespace Emby.Server.Implementations.Session
return; return;
} }
foreach (var socket in _sockets) try
{ {
socket.Closed -= OnConnectionClosed; _socketsLock.EnterWriteLock();
await socket.DisposeAsync().ConfigureAwait(false); foreach (var socket in _sockets)
{
socket.Closed -= OnConnectionClosed;
await socket.DisposeAsync().ConfigureAwait(false);
}
_sockets.Clear();
}
finally
{
_socketsLock.ExitWriteLock();
} }
_socketsLock.Dispose();
_disposed = true; _disposed = true;
} }
} }
@@ -70,7 +70,7 @@ public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<Activi
/// <param name="message">The message.</param> /// <param name="message">The message.</param>
protected override void Start(WebSocketMessageInfo message) protected override void Start(WebSocketMessageInfo message)
{ {
if (!message.Connection.AuthorizationInfo.User.HasPermission(PermissionKind.IsAdministrator)) if (!message.Connection.AuthorizationInfo.User.HasPermission(PermissionKind.IsAdministrator) && !message.Connection.AuthorizationInfo.IsApiKey)
{ {
throw new AuthenticationException("Only admin users can retrieve the activity log."); throw new AuthenticationException("Only admin users can retrieve the activity log.");
} }
@@ -79,7 +79,7 @@ public class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnume
/// <param name="message">The message.</param> /// <param name="message">The message.</param>
protected override void Start(WebSocketMessageInfo message) protected override void Start(WebSocketMessageInfo message)
{ {
if (!message.Connection.AuthorizationInfo.User.HasPermission(PermissionKind.IsAdministrator)) if (!message.Connection.AuthorizationInfo.User.HasPermission(PermissionKind.IsAdministrator) && !message.Connection.AuthorizationInfo.IsApiKey)
{ {
throw new AuthenticationException("Only admin users can subscribe to session information."); throw new AuthenticationException("Only admin users can subscribe to session information.");
} }
+1 -1
View File
@@ -18,7 +18,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Data</PackageId> <PackageId>Jellyfin.Data</PackageId>
<VersionPrefix>10.10.5</VersionPrefix> <VersionPrefix>10.10.6</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
@@ -8,7 +8,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Common</PackageId> <PackageId>Jellyfin.Common</PackageId>
<VersionPrefix>10.10.5</VersionPrefix> <VersionPrefix>10.10.6</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
+2 -2
View File
@@ -22,7 +22,7 @@ namespace MediaBrowser.Controller.Channels
[JsonIgnore] [JsonIgnore]
public override SourceType SourceType => SourceType.Channel; public override SourceType SourceType => SourceType.Channel;
public override bool IsVisible(User user) public override bool IsVisible(User user, bool skipAllowedTagsCheck = false)
{ {
var blockedChannelsPreference = user.GetPreferenceValues<Guid>(PreferenceKind.BlockedChannels); var blockedChannelsPreference = user.GetPreferenceValues<Guid>(PreferenceKind.BlockedChannels);
if (blockedChannelsPreference.Length != 0) if (blockedChannelsPreference.Length != 0)
@@ -41,7 +41,7 @@ namespace MediaBrowser.Controller.Channels
} }
} }
return base.IsVisible(user); return base.IsVisible(user, skipAllowedTagsCheck);
} }
protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query) protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
+9 -7
View File
@@ -1299,7 +1299,7 @@ namespace MediaBrowser.Controller.Entities
return false; return false;
} }
if (GetParents().Any(i => !i.IsVisible(user))) if (GetParents().Any(i => !i.IsVisible(user, true)))
{ {
return false; return false;
} }
@@ -1521,13 +1521,14 @@ namespace MediaBrowser.Controller.Entities
/// Determines if a given user has access to this item. /// Determines if a given user has access to this item.
/// </summary> /// </summary>
/// <param name="user">The user.</param> /// <param name="user">The user.</param>
/// <param name="skipAllowedTagsCheck">Don't check for allowed tags.</param>
/// <returns><c>true</c> if [is parental allowed] [the specified user]; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if [is parental allowed] [the specified user]; otherwise, <c>false</c>.</returns>
/// <exception cref="ArgumentNullException">If user is null.</exception> /// <exception cref="ArgumentNullException">If user is null.</exception>
public bool IsParentalAllowed(User user) public bool IsParentalAllowed(User user, bool skipAllowedTagsCheck)
{ {
ArgumentNullException.ThrowIfNull(user); ArgumentNullException.ThrowIfNull(user);
if (!IsVisibleViaTags(user)) if (!IsVisibleViaTags(user, skipAllowedTagsCheck))
{ {
return false; return false;
} }
@@ -1599,7 +1600,7 @@ namespace MediaBrowser.Controller.Entities
return list.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); return list.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
} }
private bool IsVisibleViaTags(User user) private bool IsVisibleViaTags(User user, bool skipAllowedTagsCheck)
{ {
var allTags = GetInheritedTags(); var allTags = GetInheritedTags();
if (user.GetPreference(PreferenceKind.BlockedTags).Any(i => allTags.Contains(i, StringComparison.OrdinalIgnoreCase))) if (user.GetPreference(PreferenceKind.BlockedTags).Any(i => allTags.Contains(i, StringComparison.OrdinalIgnoreCase)))
@@ -1614,7 +1615,7 @@ namespace MediaBrowser.Controller.Entities
} }
var allowedTagsPreference = user.GetPreference(PreferenceKind.AllowedTags); var allowedTagsPreference = user.GetPreference(PreferenceKind.AllowedTags);
if (allowedTagsPreference.Length != 0 && !allowedTagsPreference.Any(i => allTags.Contains(i, StringComparison.OrdinalIgnoreCase))) if (!skipAllowedTagsCheck && allowedTagsPreference.Length != 0 && !allowedTagsPreference.Any(i => allTags.Contains(i, StringComparison.OrdinalIgnoreCase)))
{ {
return false; return false;
} }
@@ -1654,13 +1655,14 @@ namespace MediaBrowser.Controller.Entities
/// Default is just parental allowed. Can be overridden for more functionality. /// Default is just parental allowed. Can be overridden for more functionality.
/// </summary> /// </summary>
/// <param name="user">The user.</param> /// <param name="user">The user.</param>
/// <param name="skipAllowedTagsCheck">Don't check for allowed tags.</param>
/// <returns><c>true</c> if the specified user is visible; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the specified user is visible; otherwise, <c>false</c>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="user" /> is <c>null</c>.</exception> /// <exception cref="ArgumentNullException"><paramref name="user" /> is <c>null</c>.</exception>
public virtual bool IsVisible(User user) public virtual bool IsVisible(User user, bool skipAllowedTagsCheck = false)
{ {
ArgumentNullException.ThrowIfNull(user); ArgumentNullException.ThrowIfNull(user);
return IsParentalAllowed(user); return IsParentalAllowed(user, skipAllowedTagsCheck);
} }
public virtual bool IsVisibleStandalone(User user) public virtual bool IsVisibleStandalone(User user)
@@ -96,11 +96,11 @@ namespace MediaBrowser.Controller.Entities
return GetLibraryOptions(Path); return GetLibraryOptions(Path);
} }
public override bool IsVisible(User user) public override bool IsVisible(User user, bool skipAllowedTagsCheck = false)
{ {
if (GetLibraryOptions().Enabled) if (GetLibraryOptions().Enabled)
{ {
return base.IsVisible(user); return base.IsVisible(user, skipAllowedTagsCheck);
} }
return false; return false;
+2 -2
View File
@@ -217,7 +217,7 @@ namespace MediaBrowser.Controller.Entities
LibraryManager.CreateItem(item, this); LibraryManager.CreateItem(item, this);
} }
public override bool IsVisible(User user) public override bool IsVisible(User user, bool skipAllowedTagsCheck = false)
{ {
if (this is ICollectionFolder && this is not BasePluginFolder) if (this is ICollectionFolder && this is not BasePluginFolder)
{ {
@@ -239,7 +239,7 @@ namespace MediaBrowser.Controller.Entities
} }
} }
return base.IsVisible(user); return base.IsVisible(user, skipAllowedTagsCheck);
} }
/// <summary> /// <summary>
@@ -144,14 +144,14 @@ namespace MediaBrowser.Controller.Entities.Movies
return GetItemLookupInfo<BoxSetInfo>(); return GetItemLookupInfo<BoxSetInfo>();
} }
public override bool IsVisible(User user) public override bool IsVisible(User user, bool skipAllowedTagsCheck = false)
{ {
if (IsLegacyBoxSet) if (IsLegacyBoxSet)
{ {
return base.IsVisible(user); return base.IsVisible(user, skipAllowedTagsCheck);
} }
if (base.IsVisible(user)) if (base.IsVisible(user, skipAllowedTagsCheck))
{ {
if (LinkedChildren.Length == 0) if (LinkedChildren.Length == 0)
{ {
@@ -8,7 +8,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Controller</PackageId> <PackageId>Jellyfin.Controller</PackageId>
<VersionPrefix>10.10.5</VersionPrefix> <VersionPrefix>10.10.6</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
@@ -227,11 +227,11 @@ namespace MediaBrowser.Controller.Playlists
return [item]; return [item];
} }
public override bool IsVisible(User user) public override bool IsVisible(User user, bool skipAllowedTagsCheck = false)
{ {
if (!IsSharedItem) if (!IsSharedItem)
{ {
return base.IsVisible(user); return base.IsVisible(user, skipAllowedTagsCheck);
} }
if (OpenAccess) if (OpenAccess)
@@ -122,7 +122,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
_jsonSerializerOptions = new JsonSerializerOptions(JsonDefaults.Options); _jsonSerializerOptions = new JsonSerializerOptions(JsonDefaults.Options);
_jsonSerializerOptions.Converters.Add(new JsonBoolStringConverter()); _jsonSerializerOptions.Converters.Add(new JsonBoolStringConverter());
var semaphoreCount = 2 * Environment.ProcessorCount; // Although the type is not nullable, this might still be null during unit tests
var semaphoreCount = serverConfig.Configuration?.ParallelImageEncodingLimit ?? 0;
if (semaphoreCount < 1)
{
semaphoreCount = Environment.ProcessorCount;
}
_thumbnailResourcePool = new(semaphoreCount); _thumbnailResourcePool = new(semaphoreCount);
} }
+1 -1
View File
@@ -8,7 +8,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Model</PackageId> <PackageId>Jellyfin.Model</PackageId>
<VersionPrefix>10.10.5</VersionPrefix> <VersionPrefix>10.10.6</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
@@ -6,6 +6,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Mime;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
@@ -551,10 +552,16 @@ namespace MediaBrowser.Providers.Manager
var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
await using (stream.ConfigureAwait(false)) await using (stream.ConfigureAwait(false))
{ {
var mimetype = response.Content.Headers.ContentType?.MediaType;
if (mimetype is null || mimetype.Equals(MediaTypeNames.Application.Octet, StringComparison.OrdinalIgnoreCase))
{
mimetype = MimeTypes.GetMimeType(response.RequestMessage.RequestUri.GetLeftPart(UriPartial.Path));
}
await _providerManager.SaveImage( await _providerManager.SaveImage(
item, item,
stream, stream,
response.Content.Headers.ContentType?.MediaType, mimetype,
type, type,
null, null,
cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
@@ -677,10 +684,16 @@ namespace MediaBrowser.Providers.Manager
var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
await using (stream.ConfigureAwait(false)) await using (stream.ConfigureAwait(false))
{ {
var mimetype = response.Content.Headers.ContentType?.MediaType;
if (mimetype is null || mimetype.Equals(MediaTypeNames.Application.Octet, StringComparison.OrdinalIgnoreCase))
{
mimetype = MimeTypes.GetMimeType(response.RequestMessage.RequestUri.GetLeftPart(UriPartial.Path));
}
await _providerManager.SaveImage( await _providerManager.SaveImage(
item, item,
stream, stream,
response.Content.Headers.ContentType?.MediaType, mimetype,
imageType, imageType,
null, null,
cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
@@ -204,20 +204,10 @@ namespace MediaBrowser.Providers.Manager
{ {
contentType = MediaTypeNames.Image.Png; contentType = MediaTypeNames.Image.Png;
} }
else
{
throw new HttpRequestException("Invalid image received: contentType not set.", null, response.StatusCode);
}
} }
// TVDb will sometimes serve a rubbish 404 html page with a 200 OK code, because reasons... // some providers don't correctly report media type, extract from url if no extension found
if (contentType.Equals(MediaTypeNames.Text.Html, StringComparison.OrdinalIgnoreCase)) if (contentType is null || contentType.Equals(MediaTypeNames.Application.Octet, StringComparison.OrdinalIgnoreCase))
{
throw new HttpRequestException("Invalid image received.", null, HttpStatusCode.NotFound);
}
// some iptv/epg providers don't correctly report media type, extract from url if no extension found
if (string.IsNullOrWhiteSpace(MimeTypes.ToExtension(contentType)))
{ {
// Strip query parameters from url to get actual path. // Strip query parameters from url to get actual path.
contentType = MimeTypes.GetMimeType(new Uri(url).GetLeftPart(UriPartial.Path)); contentType = MimeTypes.GetMimeType(new Uri(url).GetLeftPart(UriPartial.Path));
@@ -225,7 +215,7 @@ namespace MediaBrowser.Providers.Manager
if (!contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) if (!contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
{ {
throw new HttpRequestException($"Request returned {contentType} instead of an image type", null, HttpStatusCode.NotFound); throw new HttpRequestException($"Request returned '{contentType}' instead of an image type", null, HttpStatusCode.NotFound);
} }
var responseBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false); var responseBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
@@ -172,9 +172,9 @@ namespace MediaBrowser.Providers.MediaInfo
track.Title = string.IsNullOrEmpty(track.Title) ? mediaInfo.Name : track.Title; track.Title = string.IsNullOrEmpty(track.Title) ? mediaInfo.Name : track.Title;
track.Album = string.IsNullOrEmpty(track.Album) ? mediaInfo.Album : track.Album; track.Album = string.IsNullOrEmpty(track.Album) ? mediaInfo.Album : track.Album;
track.Year ??= mediaInfo.ProductionYear; track.Year = track.Year is null or 0 ? mediaInfo.ProductionYear : track.Year;
track.TrackNumber ??= mediaInfo.IndexNumber; track.TrackNumber = track.TrackNumber is null or 0 ? mediaInfo.IndexNumber : track.TrackNumber;
track.DiscNumber ??= mediaInfo.ParentIndexNumber; track.DiscNumber = track.DiscNumber is null or 0 ? mediaInfo.ParentIndexNumber : track.DiscNumber;
if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast)) if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast))
{ {
+2 -2
View File
@@ -1,4 +1,4 @@
using System.Reflection; using System.Reflection;
[assembly: AssemblyVersion("10.10.5")] [assembly: AssemblyVersion("10.10.6")]
[assembly: AssemblyFileVersion("10.10.5")] [assembly: AssemblyFileVersion("10.10.6")]
+1 -1
View File
@@ -66,7 +66,7 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
var semaphoreCount = config.Configuration.ParallelImageEncodingLimit; var semaphoreCount = config.Configuration.ParallelImageEncodingLimit;
if (semaphoreCount < 1) if (semaphoreCount < 1)
{ {
semaphoreCount = 2 * Environment.ProcessorCount; semaphoreCount = Environment.ProcessorCount;
} }
_parallelEncodingLimit = new(semaphoreCount); _parallelEncodingLimit = new(semaphoreCount);
@@ -15,7 +15,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Extensions</PackageId> <PackageId>Jellyfin.Extensions</PackageId>
<VersionPrefix>10.10.5</VersionPrefix> <VersionPrefix>10.10.6</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
+46 -62
View File
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities.Libraries;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using Jellyfin.LiveTv.Configuration; using Jellyfin.LiveTv.Configuration;
@@ -210,7 +209,7 @@ public class GuideManager : IGuideManager
progress.Report(15); progress.Report(15);
numComplete = 0; numComplete = 0;
var programs = new List<LiveTvProgram>(); var programIds = new List<Guid>();
var channels = new List<Guid>(); var channels = new List<Guid>();
var guideDays = GetGuideDays(); var guideDays = GetGuideDays();
@@ -243,8 +242,8 @@ public class GuideManager : IGuideManager
DtoOptions = new DtoOptions(true) DtoOptions = new DtoOptions(true)
}).Cast<LiveTvProgram>().ToDictionary(i => i.Id); }).Cast<LiveTvProgram>().ToDictionary(i => i.Id);
var newPrograms = new List<Guid>(); var newPrograms = new List<LiveTvProgram>();
var updatedPrograms = new List<Guid>(); var updatedPrograms = new List<LiveTvProgram>();
foreach (var program in channelPrograms) foreach (var program in channelPrograms)
{ {
@@ -252,14 +251,14 @@ public class GuideManager : IGuideManager
var id = programItem.Id; var id = programItem.Id;
if (isNew) if (isNew)
{ {
newPrograms.Add(id); newPrograms.Add(programItem);
} }
else if (isUpdated) else if (isUpdated)
{ {
updatedPrograms.Add(id); updatedPrograms.Add(programItem);
} }
programs.Add(programItem); programIds.Add(programItem.Id);
isMovie |= program.IsMovie; isMovie |= program.IsMovie;
isSeries |= program.IsSeries; isSeries |= program.IsSeries;
@@ -276,21 +275,21 @@ public class GuideManager : IGuideManager
if (newPrograms.Count > 0) if (newPrograms.Count > 0)
{ {
var newProgramDtos = programs.Where(b => newPrograms.Contains(b.Id)).ToList(); _libraryManager.CreateItems(newPrograms, currentChannel, cancellationToken);
_libraryManager.CreateItems(newProgramDtos, null, cancellationToken);
await PreCacheImages(newPrograms, maxCacheDate).ConfigureAwait(false);
} }
if (updatedPrograms.Count > 0) if (updatedPrograms.Count > 0)
{ {
var updatedProgramDtos = programs.Where(b => updatedPrograms.Contains(b.Id)).ToList();
await _libraryManager.UpdateItemsAsync( await _libraryManager.UpdateItemsAsync(
updatedProgramDtos, updatedPrograms,
currentChannel, currentChannel,
ItemUpdateType.MetadataImport, ItemUpdateType.MetadataImport,
cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
}
await PreCacheImages(programs, maxCacheDate).ConfigureAwait(false); await PreCacheImages(updatedPrograms, maxCacheDate).ConfigureAwait(false);
}
currentChannel.IsMovie = isMovie; currentChannel.IsMovie = isMovie;
currentChannel.IsNews = isNews; currentChannel.IsNews = isNews;
@@ -326,7 +325,6 @@ public class GuideManager : IGuideManager
} }
progress.Report(100); progress.Report(100);
var programIds = programs.Select(p => p.Id).ToList();
return new Tuple<List<Guid>, List<Guid>>(channels, programIds); return new Tuple<List<Guid>, List<Guid>>(channels, programIds);
} }
@@ -502,35 +500,27 @@ public class GuideManager : IGuideManager
forceUpdate = true; forceUpdate = true;
} }
var seriesId = info.SeriesId; var channelId = channel.Id;
if (!item.ParentId.Equals(channelId))
if (!item.ParentId.Equals(channel.Id))
{ {
item.ParentId = channel.Id;
forceUpdate = true; forceUpdate = true;
} }
item.ParentId = channel.Id;
item.Audio = info.Audio; item.Audio = info.Audio;
item.ChannelId = channel.Id; item.ChannelId = channelId;
item.CommunityRating ??= info.CommunityRating; item.CommunityRating = info.CommunityRating;
if ((item.CommunityRating ?? 0).Equals(0))
{
item.CommunityRating = null;
}
item.EpisodeTitle = info.EpisodeTitle; item.EpisodeTitle = info.EpisodeTitle;
item.ExternalId = info.Id; item.ExternalId = info.Id;
if (!string.IsNullOrWhiteSpace(seriesId) && !string.Equals(item.ExternalSeriesId, seriesId, StringComparison.Ordinal)) var seriesId = info.SeriesId;
if (!string.IsNullOrWhiteSpace(seriesId) && !string.Equals(item.ExternalSeriesId, seriesId, StringComparison.OrdinalIgnoreCase))
{ {
item.ExternalSeriesId = seriesId;
forceUpdate = true; forceUpdate = true;
} }
item.ExternalSeriesId = seriesId;
var isSeries = info.IsSeries || !string.IsNullOrEmpty(info.EpisodeTitle); var isSeries = info.IsSeries || !string.IsNullOrEmpty(info.EpisodeTitle);
if (isSeries || !string.IsNullOrEmpty(info.EpisodeTitle)) if (isSeries || !string.IsNullOrEmpty(info.EpisodeTitle))
{ {
item.SeriesName = info.Name; item.SeriesName = info.Name;
@@ -578,7 +568,6 @@ public class GuideManager : IGuideManager
} }
item.Tags = tags.ToArray(); item.Tags = tags.ToArray();
item.Genres = info.Genres.ToArray(); item.Genres = info.Genres.ToArray();
if (info.IsHD ?? false) if (info.IsHD ?? false)
@@ -589,41 +578,35 @@ public class GuideManager : IGuideManager
item.IsMovie = info.IsMovie; item.IsMovie = info.IsMovie;
item.IsRepeat = info.IsRepeat; item.IsRepeat = info.IsRepeat;
if (item.IsSeries != isSeries) if (item.IsSeries != isSeries)
{ {
item.IsSeries = isSeries;
forceUpdate = true; forceUpdate = true;
} }
item.IsSeries = isSeries;
item.Name = info.Name; item.Name = info.Name;
item.OfficialRating ??= info.OfficialRating; item.OfficialRating = info.OfficialRating;
item.Overview ??= info.Overview; item.Overview = info.Overview;
item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks; item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks;
item.ProviderIds = info.ProviderIds;
foreach (var providerId in info.SeriesProviderIds) foreach (var providerId in info.SeriesProviderIds)
{ {
info.ProviderIds["Series" + providerId.Key] = providerId.Value; info.ProviderIds["Series" + providerId.Key] = providerId.Value;
} }
item.ProviderIds = info.ProviderIds;
if (item.StartDate != info.StartDate) if (item.StartDate != info.StartDate)
{ {
item.StartDate = info.StartDate;
forceUpdate = true; forceUpdate = true;
} }
item.StartDate = info.StartDate;
if (item.EndDate != info.EndDate) if (item.EndDate != info.EndDate)
{ {
item.EndDate = info.EndDate;
forceUpdate = true; forceUpdate = true;
} }
item.EndDate = info.EndDate;
item.ProductionYear = info.ProductionYear; item.ProductionYear = info.ProductionYear;
if (!isSeries || info.IsRepeat) if (!isSeries || info.IsRepeat)
{ {
item.PremiereDate = info.OriginalAirDate; item.PremiereDate = info.OriginalAirDate;
@@ -632,37 +615,35 @@ public class GuideManager : IGuideManager
item.IndexNumber = info.EpisodeNumber; item.IndexNumber = info.EpisodeNumber;
item.ParentIndexNumber = info.SeasonNumber; item.ParentIndexNumber = info.SeasonNumber;
forceUpdate = forceUpdate || UpdateImages(item, info); forceUpdate |= UpdateImages(item, info);
if (isNew) if (isNew)
{ {
item.OnMetadataChanged(); item.OnMetadataChanged();
return (item, isNew, false); return (item, true, false);
} }
var isUpdated = false; var isUpdated = forceUpdate;
if (forceUpdate || string.IsNullOrWhiteSpace(info.Etag)) var etag = info.Etag;
if (string.IsNullOrWhiteSpace(etag))
{ {
isUpdated = true; isUpdated = true;
} }
else else if (!string.Equals(etag, item.GetProviderId(EtagKey), StringComparison.OrdinalIgnoreCase))
{ {
var etag = info.Etag; item.SetProviderId(EtagKey, etag);
isUpdated = true;
if (!string.Equals(etag, item.GetProviderId(EtagKey), StringComparison.OrdinalIgnoreCase))
{
item.SetProviderId(EtagKey, etag);
isUpdated = true;
}
} }
if (isUpdated) if (isUpdated)
{ {
item.OnMetadataChanged(); item.OnMetadataChanged();
return (item, false, true);
} }
return (item, isNew, isUpdated); return (item, false, false);
} }
private static bool UpdateImages(BaseItem item, ProgramInfo info) private static bool UpdateImages(BaseItem item, ProgramInfo info)
@@ -679,7 +660,9 @@ public class GuideManager : IGuideManager
updated |= UpdateImage(ImageType.Logo, item, info); updated |= UpdateImage(ImageType.Logo, item, info);
// Backdrop // Backdrop
return updated || UpdateImage(ImageType.Backdrop, item, info); updated |= UpdateImage(ImageType.Backdrop, item, info);
return updated;
} }
private static bool UpdateImage(ImageType imageType, BaseItem item, ProgramInfo info) private static bool UpdateImage(ImageType imageType, BaseItem item, ProgramInfo info)
@@ -689,7 +672,7 @@ public class GuideManager : IGuideManager
var newImagePath = imageType switch var newImagePath = imageType switch
{ {
ImageType.Primary => info.ImagePath, ImageType.Primary => info.ImagePath,
_ => string.Empty _ => null
}; };
var newImageUrl = imageType switch var newImageUrl = imageType switch
{ {
@@ -697,12 +680,12 @@ public class GuideManager : IGuideManager
ImageType.Logo => info.LogoImageUrl, ImageType.Logo => info.LogoImageUrl,
ImageType.Primary => info.ImageUrl, ImageType.Primary => info.ImageUrl,
ImageType.Thumb => info.ThumbImageUrl, ImageType.Thumb => info.ThumbImageUrl,
_ => string.Empty _ => null
}; };
var differentImage = newImageUrl?.Equals(currentImagePath, StringComparison.OrdinalIgnoreCase) == false var sameImage = (currentImagePath?.Equals(newImageUrl, StringComparison.OrdinalIgnoreCase) ?? false)
|| newImagePath?.Equals(currentImagePath, StringComparison.OrdinalIgnoreCase) == false; || (currentImagePath?.Equals(newImagePath, StringComparison.OrdinalIgnoreCase) ?? false);
if (!differentImage) if (sameImage)
{ {
return false; return false;
} }
@@ -757,6 +740,7 @@ public class GuideManager : IGuideManager
var imageInfo = program.ImageInfos[i]; var imageInfo = program.ImageInfos[i];
if (!imageInfo.IsLocalFile) if (!imageInfo.IsLocalFile)
{ {
_logger.LogDebug("Caching image locally: {Url}", imageInfo.Path);
try try
{ {
program.ImageInfos[i] = await _libraryManager.ConvertImageToLocal( program.ImageInfos[i] = await _libraryManager.ConvertImageToLocal(
@@ -689,10 +689,10 @@ public class NetworkManager : INetworkManager, IDisposable
{ {
// Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely.
// If left blank, all remote addresses will be allowed. // If left blank, all remote addresses will be allowed.
if (_remoteAddressFilter.Any() && !_lanSubnets.Any(x => x.Contains(remoteIP))) if (_remoteAddressFilter.Any() && !IsInLocalNetwork(remoteIP))
{ {
// remoteAddressFilter is a whitelist or blacklist. // remoteAddressFilter is a whitelist or blacklist.
var matches = _remoteAddressFilter.Count(remoteNetwork => remoteNetwork.Contains(remoteIP)); var matches = _remoteAddressFilter.Count(remoteNetwork => SubnetContainsAddress(remoteNetwork, remoteIP));
if ((!config.IsRemoteIPFilterBlacklist && matches > 0) if ((!config.IsRemoteIPFilterBlacklist && matches > 0)
|| (config.IsRemoteIPFilterBlacklist && matches == 0)) || (config.IsRemoteIPFilterBlacklist && matches == 0))
{ {
@@ -816,7 +816,7 @@ public class NetworkManager : INetworkManager, IDisposable
_logger.LogWarning("IPv4 is disabled in Jellyfin, but enabled in the OS. This may affect how the interface is selected."); _logger.LogWarning("IPv4 is disabled in Jellyfin, but enabled in the OS. This may affect how the interface is selected.");
} }
bool isExternal = !_lanSubnets.Any(network => network.Contains(source)); bool isExternal = !IsInLocalNetwork(source);
_logger.LogDebug("Trying to get bind address for source {Source} - External: {IsExternal}", source, isExternal); _logger.LogDebug("Trying to get bind address for source {Source} - External: {IsExternal}", source, isExternal);
if (!skipOverrides && MatchesPublishedServerUrl(source, isExternal, out result)) if (!skipOverrides && MatchesPublishedServerUrl(source, isExternal, out result))
@@ -863,7 +863,7 @@ public class NetworkManager : INetworkManager, IDisposable
// (For systems with multiple internal network cards, and multiple subnets) // (For systems with multiple internal network cards, and multiple subnets)
foreach (var intf in availableInterfaces) foreach (var intf in availableInterfaces)
{ {
if (intf.Subnet.Contains(source)) if (SubnetContainsAddress(intf.Subnet, source))
{ {
result = NetworkUtils.FormatIPString(intf.Address); result = NetworkUtils.FormatIPString(intf.Address);
_logger.LogDebug("{Source}: Found interface with matching subnet, using it as bind address: {Result}", source, result); _logger.LogDebug("{Source}: Found interface with matching subnet, using it as bind address: {Result}", source, result);
@@ -891,21 +891,11 @@ public class NetworkManager : INetworkManager, IDisposable
{ {
if (NetworkUtils.TryParseToSubnet(address, out var subnet)) if (NetworkUtils.TryParseToSubnet(address, out var subnet))
{ {
return IPAddress.IsLoopback(subnet.Prefix) || (_lanSubnets.Any(x => x.Contains(subnet.Prefix)) && !_excludedSubnets.Any(x => x.Contains(subnet.Prefix))); return IsInLocalNetwork(subnet.Prefix);
} }
if (NetworkUtils.TryParseHost(address, out var addresses, IsIPv4Enabled, IsIPv6Enabled)) return NetworkUtils.TryParseHost(address, out var addresses, IsIPv4Enabled, IsIPv6Enabled)
{ && addresses.Any(IsInLocalNetwork);
foreach (var ept in addresses)
{
if (IPAddress.IsLoopback(ept) || (_lanSubnets.Any(x => x.Contains(ept)) && !_excludedSubnets.Any(x => x.Contains(ept))))
{
return true;
}
}
}
return false;
} }
/// <summary> /// <summary>
@@ -919,6 +909,19 @@ public class NetworkManager : INetworkManager, IDisposable
return NetworkConstants.IPv4RFC3927LinkLocal.Contains(address) || address.IsIPv6LinkLocal; return NetworkConstants.IPv4RFC3927LinkLocal.Contains(address) || address.IsIPv6LinkLocal;
} }
private static bool SubnetContainsAddress(IPNetwork network, IPAddress address)
{
ArgumentNullException.ThrowIfNull(address);
ArgumentNullException.ThrowIfNull(network);
if (address.IsIPv4MappedToIPv6)
{
address = address.MapToIPv4();
}
return network.Contains(address);
}
/// <inheritdoc/> /// <inheritdoc/>
public bool IsInLocalNetwork(IPAddress address) public bool IsInLocalNetwork(IPAddress address)
{ {
@@ -940,6 +943,11 @@ public class NetworkManager : INetworkManager, IDisposable
return CheckIfLanAndNotExcluded(address); return CheckIfLanAndNotExcluded(address);
} }
/// <summary>
/// Check if the address is in the LAN and not excluded.
/// </summary>
/// <param name="address">The IP address to check. The caller should make sure this is not an IPv4MappedToIPv6 address.</param>
/// <returns>Boolean indicates whether the address is in LAN.</returns>
private bool CheckIfLanAndNotExcluded(IPAddress address) private bool CheckIfLanAndNotExcluded(IPAddress address)
{ {
foreach (var lanSubnet in _lanSubnets) foreach (var lanSubnet in _lanSubnets)
@@ -979,7 +987,7 @@ public class NetworkManager : INetworkManager, IDisposable
{ {
// Only use matching internal subnets // Only use matching internal subnets
// Prefer more specific (bigger subnet prefix) overrides // Prefer more specific (bigger subnet prefix) overrides
validPublishedServerUrls = _publishedServerUrls.Where(x => x.IsInternalOverride && x.Data.Subnet.Contains(source)) validPublishedServerUrls = _publishedServerUrls.Where(x => x.IsInternalOverride && SubnetContainsAddress(x.Data.Subnet, source))
.OrderByDescending(x => x.Data.Subnet.PrefixLength) .OrderByDescending(x => x.Data.Subnet.PrefixLength)
.ToList(); .ToList();
} }
@@ -987,7 +995,7 @@ public class NetworkManager : INetworkManager, IDisposable
{ {
// Only use matching external subnets // Only use matching external subnets
// Prefer more specific (bigger subnet prefix) overrides // Prefer more specific (bigger subnet prefix) overrides
validPublishedServerUrls = _publishedServerUrls.Where(x => x.IsExternalOverride && x.Data.Subnet.Contains(source)) validPublishedServerUrls = _publishedServerUrls.Where(x => x.IsExternalOverride && SubnetContainsAddress(x.Data.Subnet, source))
.OrderByDescending(x => x.Data.Subnet.PrefixLength) .OrderByDescending(x => x.Data.Subnet.PrefixLength)
.ToList(); .ToList();
} }
@@ -995,7 +1003,7 @@ public class NetworkManager : INetworkManager, IDisposable
foreach (var data in validPublishedServerUrls) foreach (var data in validPublishedServerUrls)
{ {
// Get interface matching override subnet // Get interface matching override subnet
var intf = _interfaces.OrderBy(x => x.Index).FirstOrDefault(x => data.Data.Subnet.Contains(x.Address)); var intf = _interfaces.OrderBy(x => x.Index).FirstOrDefault(x => SubnetContainsAddress(data.Data.Subnet, 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.InterNetwork && data.Data.Address.Equals(IPAddress.Any))
@@ -1058,6 +1066,7 @@ public class NetworkManager : INetworkManager, IDisposable
if (isInExternalSubnet) if (isInExternalSubnet)
{ {
var externalInterfaces = _interfaces.Where(x => !IsInLocalNetwork(x.Address)) var externalInterfaces = _interfaces.Where(x => !IsInLocalNetwork(x.Address))
.Where(x => !IsLinkLocalAddress(x.Address))
.OrderBy(x => x.Index) .OrderBy(x => x.Index)
.ToList(); .ToList();
if (externalInterfaces.Count > 0) if (externalInterfaces.Count > 0)
@@ -1065,7 +1074,8 @@ public class NetworkManager : INetworkManager, IDisposable
// Check to see if any of the external bind interfaces are in the same subnet as the source. // Check to see if any of the external bind interfaces are in the same subnet as the source.
// If none exists, this will select the first external interface if there is one. // If none exists, this will select the first external interface if there is one.
bindAddress = externalInterfaces bindAddress = externalInterfaces
.OrderBy(x => x.Subnet.Contains(source)) .OrderByDescending(x => SubnetContainsAddress(x.Subnet, source))
.ThenByDescending(x => x.Subnet.PrefixLength)
.ThenBy(x => x.Index) .ThenBy(x => x.Index)
.Select(x => x.Address) .Select(x => x.Address)
.First(); .First();
@@ -1082,7 +1092,8 @@ public class NetworkManager : INetworkManager, IDisposable
// Check to see if any of the internal bind interfaces are in the same subnet as the source. // Check to see if any of the internal bind interfaces are in the same subnet as the source.
// If none exists, this will select the first internal interface if there is one. // If none exists, this will select the first internal interface if there is one.
bindAddress = _interfaces.Where(x => IsInLocalNetwork(x.Address)) bindAddress = _interfaces.Where(x => IsInLocalNetwork(x.Address))
.OrderBy(x => x.Subnet.Contains(source)) .OrderByDescending(x => SubnetContainsAddress(x.Subnet, source))
.ThenByDescending(x => x.Subnet.PrefixLength)
.ThenBy(x => x.Index) .ThenBy(x => x.Index)
.Select(x => x.Address) .Select(x => x.Address)
.FirstOrDefault(); .FirstOrDefault();
@@ -1125,7 +1136,7 @@ public class NetworkManager : INetworkManager, IDisposable
// (For systems with multiple network cards and/or multiple subnets) // (For systems with multiple network cards and/or multiple subnets)
foreach (var intf in extResult) foreach (var intf in extResult)
{ {
if (intf.Subnet.Contains(source)) if (SubnetContainsAddress(intf.Subnet, source))
{ {
result = NetworkUtils.FormatIPString(intf.Address); result = NetworkUtils.FormatIPString(intf.Address);
_logger.LogDebug("{Source}: Found external interface with matching subnet, using it as bind address: {Result}", source, result); _logger.LogDebug("{Source}: Found external interface with matching subnet, using it as bind address: {Result}", source, result);