Backport pull request #15263 from jellyfin/release-10.11.z

Resolve symlinks for static media source infos

Original-merge: 3b2d64995aab63ebaa6832c059a3cc0bdebe90dc

Merged-by: crobibero <cody@robibe.ro>

Backported-by: Bond_009 <bond.009@outlook.com>
This commit is contained in:
revam 2025-11-17 14:08:47 -05:00 committed by Bond_009
parent 06fb300cff
commit 5ea3910af9
5 changed files with 91 additions and 5 deletions

View File

@ -6,6 +6,7 @@ using System.Linq;
using System.Security; using System.Security;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -260,7 +261,7 @@ namespace Emby.Server.Implementations.IO
{ {
try try
{ {
var targetFileInfo = (FileInfo?)fileInfo.ResolveLinkTarget(returnFinalTarget: true); var targetFileInfo = FileSystemHelper.ResolveLinkTarget(fileInfo, returnFinalTarget: true);
if (targetFileInfo is not null) if (targetFileInfo is not null)
{ {
result.Exists = targetFileInfo.Exists; result.Exists = targetFileInfo.Exists;

View File

@ -1,6 +1,7 @@
using System; using System;
using System.IO; using System.IO;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
@ -92,7 +93,7 @@ public class DotIgnoreIgnoreRule : IResolverIgnoreRule
private static string GetFileContent(FileInfo dirIgnoreFile) private static string GetFileContent(FileInfo dirIgnoreFile)
{ {
dirIgnoreFile = (FileInfo?)dirIgnoreFile.ResolveLinkTarget(returnFinalTarget: true) ?? dirIgnoreFile; dirIgnoreFile = FileSystemHelper.ResolveLinkTarget(dirIgnoreFile, returnFinalTarget: true) ?? dirIgnoreFile;
if (!dirIgnoreFile.Exists) if (!dirIgnoreFile.Exists)
{ {
return string.Empty; return string.Empty;

View File

@ -254,10 +254,10 @@ public class TrickplayManager : ITrickplayManager
} }
// We support video backdrops, but we should not generate trickplay images for them // We support video backdrops, but we should not generate trickplay images for them
var parentDirectory = Directory.GetParent(mediaPath); var parentDirectory = Directory.GetParent(video.Path);
if (parentDirectory is not null && string.Equals(parentDirectory.Name, "backdrops", StringComparison.OrdinalIgnoreCase)) if (parentDirectory is not null && string.Equals(parentDirectory.Name, "backdrops", StringComparison.OrdinalIgnoreCase))
{ {
_logger.LogDebug("Ignoring backdrop media found at {Path} for item {ItemID}", mediaPath, video.Id); _logger.LogDebug("Ignoring backdrop media found at {Path} for item {ItemID}", video.Path, video.Id);
return; return;
} }

View File

@ -24,6 +24,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaSegments; using MediaBrowser.Controller.MediaSegments;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
@ -1127,6 +1128,15 @@ namespace MediaBrowser.Controller.Entities
var protocol = item.PathProtocol; var protocol = item.PathProtocol;
// Resolve the item path so everywhere we use the media source it will always point to
// the correct path even if symlinks are in use. Calling ResolveLinkTarget on a non-link
// path will return null, so it's safe to check for all paths.
var itemPath = item.Path;
if (protocol is MediaProtocol.File && FileSystemHelper.ResolveLinkTarget(itemPath, returnFinalTarget: true) is { Exists: true } linkInfo)
{
itemPath = linkInfo.FullName;
}
var info = new MediaSourceInfo var info = new MediaSourceInfo
{ {
Id = item.Id.ToString("N", CultureInfo.InvariantCulture), Id = item.Id.ToString("N", CultureInfo.InvariantCulture),
@ -1134,7 +1144,7 @@ namespace MediaBrowser.Controller.Entities
MediaStreams = MediaSourceManager.GetMediaStreams(item.Id), MediaStreams = MediaSourceManager.GetMediaStreams(item.Id),
MediaAttachments = MediaSourceManager.GetMediaAttachments(item.Id), MediaAttachments = MediaSourceManager.GetMediaAttachments(item.Id),
Name = GetMediaSourceName(item), Name = GetMediaSourceName(item),
Path = enablePathSubstitution ? GetMappedPath(item, item.Path, protocol) : item.Path, Path = enablePathSubstitution ? GetMappedPath(item, itemPath, protocol) : itemPath,
RunTimeTicks = item.RunTimeTicks, RunTimeTicks = item.RunTimeTicks,
Container = item.Container, Container = item.Container,
Size = item.Size, Size = item.Size,

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
@ -61,4 +62,77 @@ public static class FileSystemHelper
} }
} }
} }
/// <summary>
/// Gets the target of the specified file link.
/// </summary>
/// <remarks>
/// This helper exists because of this upstream runtime issue; https://github.com/dotnet/runtime/issues/92128.
/// </remarks>
/// <param name="linkPath">The path of the file link.</param>
/// <param name="returnFinalTarget">true to follow links to the final target; false to return the immediate next link.</param>
/// <returns>
/// A <see cref="FileInfo"/> if the <paramref name="linkPath"/> is a link, regardless of if the target exists; otherwise, <c>null</c>.
/// </returns>
public static FileInfo? ResolveLinkTarget(string linkPath, bool returnFinalTarget = false)
{
// Check if the file exists so the native resolve handler won't throw at us.
if (!File.Exists(linkPath))
{
return null;
}
if (!returnFinalTarget)
{
return File.ResolveLinkTarget(linkPath, returnFinalTarget: false) as FileInfo;
}
if (File.ResolveLinkTarget(linkPath, returnFinalTarget: false) is not FileInfo targetInfo)
{
return null;
}
var currentPath = targetInfo.FullName;
var visited = new HashSet<string>(StringComparer.Ordinal) { linkPath, currentPath };
while (File.ResolveLinkTarget(currentPath, returnFinalTarget: false) is FileInfo linkInfo)
{
var targetPath = linkInfo.FullName;
// If an infinite loop is detected, return the file info for the
// first link in the loop we encountered.
if (!visited.Add(targetPath))
{
return new FileInfo(targetPath);
}
targetInfo = linkInfo;
currentPath = targetPath;
// Exit if the target doesn't exist, so the native resolve handler won't throw at us.
if (!targetInfo.Exists)
{
break;
}
}
return targetInfo;
}
/// <summary>
/// Gets the target of the specified file link.
/// </summary>
/// <remarks>
/// This helper exists because of this upstream runtime issue; https://github.com/dotnet/runtime/issues/92128.
/// </remarks>
/// <param name="fileInfo">The file info of the file link.</param>
/// <param name="returnFinalTarget">true to follow links to the final target; false to return the immediate next link.</param>
/// <returns>
/// A <see cref="FileInfo"/> if the <paramref name="fileInfo"/> is a link, regardless of if the target exists; otherwise, <c>null</c>.
/// </returns>
public static FileInfo? ResolveLinkTarget(FileInfo fileInfo, bool returnFinalTarget = false)
{
ArgumentNullException.ThrowIfNull(fileInfo);
return ResolveLinkTarget(fileInfo.FullName, returnFinalTarget);
}
} }