aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs64
-rw-r--r--Emby.Server.Implementations/Library/PathExtensions.cs80
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs23
3 files changed, 117 insertions, 50 deletions
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index d9ffe64b3..973b2df8a 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -2788,10 +2788,9 @@ namespace Emby.Server.Implementations.Library
continue;
}
- var substitutionResult = SubstitutePathInternal(path, pathInfo.Path, pathInfo.NetworkPath);
- if (substitutionResult.Item2)
+ if (path.TryReplaceSubPath(pathInfo.Path, pathInfo.NetworkPath, out var newPath))
{
- return substitutionResult.Item1;
+ return newPath;
}
}
}
@@ -2802,22 +2801,22 @@ namespace Emby.Server.Implementations.Library
if (!string.IsNullOrWhiteSpace(metadataPath) && !string.IsNullOrWhiteSpace(metadataNetworkPath))
{
- var metadataSubstitutionResult = SubstitutePathInternal(path, metadataPath, metadataNetworkPath);
- if (metadataSubstitutionResult.Item2)
+ if (path.TryReplaceSubPath(metadataPath, metadataNetworkPath, out var newPath))
{
- return metadataSubstitutionResult.Item1;
+ return newPath;
}
}
foreach (var map in _configurationManager.Configuration.PathSubstitutions)
{
- if (!string.IsNullOrWhiteSpace(map.From))
+ if (string.IsNullOrWhiteSpace(map.From))
{
- var substitutionResult = SubstitutePathInternal(path, map.From, map.To);
- if (substitutionResult.Item2)
- {
- return substitutionResult.Item1;
- }
+ continue;
+ }
+
+ if (path.TryReplaceSubPath(map.From, map.To, out var newPath))
+ {
+ return newPath;
}
}
@@ -2826,47 +2825,12 @@ namespace Emby.Server.Implementations.Library
public string SubstitutePath(string path, string from, string to)
{
- return SubstitutePathInternal(path, from, to).Item1;
- }
-
- private Tuple<string, bool> SubstitutePathInternal(string path, string from, string to)
- {
- if (string.IsNullOrWhiteSpace(path))
- {
- throw new ArgumentNullException(nameof(path));
- }
-
- if (string.IsNullOrWhiteSpace(from))
- {
- throw new ArgumentNullException(nameof(from));
- }
-
- if (string.IsNullOrWhiteSpace(to))
+ if (path.TryReplaceSubPath(from, to, out var newPath))
{
- throw new ArgumentNullException(nameof(to));
+ return newPath;
}
- from = from.Trim();
- to = to.Trim();
-
- var newPath = path.Replace(from, to, StringComparison.OrdinalIgnoreCase);
- var changed = false;
-
- if (!string.Equals(newPath, path, StringComparison.Ordinal))
- {
- if (to.IndexOf('/', StringComparison.Ordinal) != -1)
- {
- newPath = newPath.Replace('\\', '/');
- }
- else
- {
- newPath = newPath.Replace('/', '\\');
- }
-
- changed = true;
- }
-
- return new Tuple<string, bool>(newPath, changed);
+ return path;
}
private void SetExtraTypeFromFilename(Video item)
diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs
index 06ff3e611..1fc5526ae 100644
--- a/Emby.Server.Implementations/Library/PathExtensions.cs
+++ b/Emby.Server.Implementations/Library/PathExtensions.cs
@@ -1,6 +1,7 @@
#nullable enable
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
namespace Emby.Server.Implementations.Library
@@ -47,5 +48,84 @@ namespace Emby.Server.Implementations.Library
return null;
}
+
+ /// <summary>
+ /// Replaces a sub path with another sub path and normalizes the final path.
+ /// </summary>
+ /// <param name="path">The original path.</param>
+ /// <param name="subPath">The original sub path.</param>
+ /// <param name="newSubPath">The new sub path.</param>
+ /// <param name="newPath">The result of the sub path replacement</param>
+ /// <returns>The path after replacing the sub path.</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="path" />, <paramref name="newSubPath" /> or <paramref name="newSubPath" /> is empty.</exception>
+ public static bool TryReplaceSubPath(this string path, string subPath, string newSubPath, [NotNullWhen(true)] out string? newPath)
+ {
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ if (string.IsNullOrWhiteSpace(subPath))
+ {
+ throw new ArgumentNullException(nameof(subPath));
+ }
+
+ if (string.IsNullOrWhiteSpace(newSubPath))
+ {
+ throw new ArgumentNullException(nameof(newSubPath));
+ }
+
+ char oldDirectorySeparatorChar;
+ char newDirectorySeparatorChar;
+ // True normalization is still not possible https://github.com/dotnet/runtime/issues/2162
+ // The reasoning behind this is that a forward slash likely means it's a Linux path and
+ // so the whole path should be normalized to use / and vice versa for Windows (although Windows doesn't care much).
+ if (newSubPath.Contains('/', StringComparison.Ordinal))
+ {
+ oldDirectorySeparatorChar = '\\';
+ newDirectorySeparatorChar = '/';
+ }
+ else
+ {
+ oldDirectorySeparatorChar = '/';
+ newDirectorySeparatorChar = '\\';
+ }
+
+ if (path.Contains(oldDirectorySeparatorChar, StringComparison.Ordinal))
+ {
+ path = path.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar);
+ }
+
+ if (subPath.Contains(oldDirectorySeparatorChar, StringComparison.Ordinal))
+ {
+ subPath = subPath.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar);
+ }
+
+ // We have to ensure that the sub path ends with a directory separator otherwise we'll get weird results
+ // when the sub path matches a similar but in-complete subpath
+ if (!subPath.EndsWith(newDirectorySeparatorChar))
+ {
+ subPath += newDirectorySeparatorChar;
+ }
+
+ if (newSubPath.Contains(oldDirectorySeparatorChar, StringComparison.Ordinal))
+ {
+ newSubPath = newSubPath.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar);
+ }
+
+ if (!newSubPath.EndsWith(newDirectorySeparatorChar))
+ {
+ newSubPath += newDirectorySeparatorChar;
+ }
+
+ if (!path.Contains(subPath, StringComparison.OrdinalIgnoreCase))
+ {
+ newPath = null;
+ return false;
+ }
+
+ newPath = path.Replace(subPath, newSubPath, StringComparison.OrdinalIgnoreCase);
+ return true;
+ }
}
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
index 6d768af89..a96a05377 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
@@ -24,5 +24,28 @@ namespace Jellyfin.Server.Implementations.Tests.Library
{
Assert.Throws<ArgumentException>(() => PathExtensions.GetAttributeValue(input, attribute));
}
+
+ [Theory]
+ [InlineData("C:/Users/jeff/myfile.mkv", "C:/Users/jeff", "/home/jeff", true, "/home/jeff/myfile.mkv")]
+ [InlineData("C:/Users/jeff/myfile.mkv", "C:/Users/jeff/", "/home/jeff", true, "/home/jeff/myfile.mkv")]
+ [InlineData("/home/jeff/music/jeff's band/consistently inconsistent.mp3", "/home/jeff/music/not jeff's band", "/home/not jeff", false, null)]
+ [InlineData("/home/jeff/music/jeff's band/consistently inconsistent.mp3", "/home/jeff/music/jeff's band", "/home/not jeff", true, "/home/not jeff/consistently inconsistent.mp3")]
+ [InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff", "/home/jeff", true, "/home/jeff/myfile.mkv")]
+ public void TryReplaceSubPath_ValidArgs_Correct(string path, string subPath, string newSubPath, bool succeeded, string? expectedResult)
+ {
+ var status = PathExtensions.TryReplaceSubPath(path, subPath, newSubPath, out var result);
+ Assert.Equal(succeeded, status);
+ Assert.Equal(expectedResult, result);
+ }
+
+ [Theory]
+ [InlineData("", "", "")]
+ [InlineData("/my/path", "", "")]
+ [InlineData("", "/another/path", "")]
+ [InlineData("", "", "/new/subpath")]
+ public void TryReplaceSubPath_EmptyString_ThrowsArgumentNullException(string path, string subPath, string newSubPath)
+ {
+ Assert.Throws<ArgumentNullException>(() => PathExtensions.TryReplaceSubPath(path, subPath, newSubPath, out _));
+ }
}
}