|
|
@@ -1,4 +1,5 @@
|
|
|
using System;
|
|
|
+using System.Collections.Generic;
|
|
|
using System.IO;
|
|
|
using System.Linq;
|
|
|
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);
|
|
|
+ }
|
|
|
}
|