diff --git a/CHANGELOG.md b/CHANGELOG.md index 530683996..9aca1acfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Fix AV1 software decoder priority (`libdav1d`, `libaom-av1`, `av1`) - Fix some stream failures caused by loudnorm filter - Fix multi-collection editor improperly disabling collections/smart collections that haven't already been added to the multi-collection +- Fix path replacement logic when media server paths use inconsistent casing (e.g. `\\SERVERNAME` AND `\\ServerName`) ### Changed - Log search index updates under scanner category at debug level, to indicate a potential cause for the UI being out of date diff --git a/ErsatzTV.Core.Tests/Emby/EmbyPathReplacementServiceTests.cs b/ErsatzTV.Core.Tests/Emby/EmbyPathReplacementServiceTests.cs index 6835c4b2d..0e8f37daa 100644 --- a/ErsatzTV.Core.Tests/Emby/EmbyPathReplacementServiceTests.cs +++ b/ErsatzTV.Core.Tests/Emby/EmbyPathReplacementServiceTests.cs @@ -164,6 +164,38 @@ public class EmbyPathReplacementServiceTests result.Should().Be(@"/mnt/something else/Some Shared Folder/Some Movie/Some Movie.mkv"); } + + [Test] + public async Task EmbyWindows_To_EtvLinux_UncPathWithMixedCaseServerName() + { + var replacements = new List + { + new() + { + Id = 1, + EmbyPath = @"\\ServerName\Something\Some Shared Folder\", + LocalPath = @"/mnt/something else/Some Shared Folder/", + EmbyMediaSource = new EmbyMediaSource { OperatingSystem = "Windows" } + } + }; + + IMediaSourceRepository repo = Substitute.For(); + repo.GetEmbyPathReplacementsByLibraryId(Arg.Any()).Returns(replacements.AsTask()); + + IRuntimeInfo runtime = Substitute.For(); + runtime.IsOSPlatform(OSPlatform.Windows).Returns(false); + + var service = new EmbyPathReplacementService( + repo, + runtime, + Substitute.For>()); + + string result = await service.GetReplacementEmbyPath( + 0, + @"\\SERVERNAME\Something\Some Shared Folder\Some Movie\Some Movie.mkv"); + + result.Should().Be(@"/mnt/something else/Some Shared Folder/Some Movie/Some Movie.mkv"); + } [Test] public async Task EmbyLinux_To_EtvWindows() diff --git a/ErsatzTV.Core.Tests/Jellyfin/JellyfinPathReplacementServiceTests.cs b/ErsatzTV.Core.Tests/Jellyfin/JellyfinPathReplacementServiceTests.cs index 9a1ffd59f..6adc621ac 100644 --- a/ErsatzTV.Core.Tests/Jellyfin/JellyfinPathReplacementServiceTests.cs +++ b/ErsatzTV.Core.Tests/Jellyfin/JellyfinPathReplacementServiceTests.cs @@ -164,6 +164,38 @@ public class JellyfinPathReplacementServiceTests result.Should().Be(@"/mnt/something else/Some Shared Folder/Some Movie/Some Movie.mkv"); } + + [Test] + public async Task JellyfinWindows_To_EtvLinux_UncPathWithMixedCaseServerName() + { + var replacements = new List + { + new() + { + Id = 1, + JellyfinPath = @"\\ServerName\Something\Some Shared Folder\", + LocalPath = @"/mnt/something else/Some Shared Folder/", + JellyfinMediaSource = new JellyfinMediaSource { OperatingSystem = "Windows" } + } + }; + + IMediaSourceRepository repo = Substitute.For(); + repo.GetJellyfinPathReplacementsByLibraryId(Arg.Any()).Returns(replacements.AsTask()); + + IRuntimeInfo runtime = Substitute.For(); + runtime.IsOSPlatform(OSPlatform.Windows).Returns(false); + + var service = new JellyfinPathReplacementService( + repo, + runtime, + Substitute.For>()); + + string result = await service.GetReplacementJellyfinPath( + 0, + @"\\SERVERNAME\Something\Some Shared Folder\Some Movie\Some Movie.mkv"); + + result.Should().Be(@"/mnt/something else/Some Shared Folder/Some Movie/Some Movie.mkv"); + } [Test] public async Task JellyfinLinux_To_EtvWindows() diff --git a/ErsatzTV.Core.Tests/Plex/PlexPathReplacementServiceTests.cs b/ErsatzTV.Core.Tests/Plex/PlexPathReplacementServiceTests.cs index 06d436cdd..f0ed6686c 100644 --- a/ErsatzTV.Core.Tests/Plex/PlexPathReplacementServiceTests.cs +++ b/ErsatzTV.Core.Tests/Plex/PlexPathReplacementServiceTests.cs @@ -140,6 +140,38 @@ public class PlexPathReplacementServiceTests result.Should().Be(@"/mnt/something else/Some Shared Folder/Some Movie/Some Movie.mkv"); } + + [Test] + public async Task PlexWindows_To_EtvLinux_UncPathWithMixedCaseServerName() + { + var replacements = new List + { + new() + { + Id = 1, + PlexPath = @"\\ServerName\Something\Some Shared Folder\", + LocalPath = @"/mnt/something else/Some Shared Folder/", + PlexMediaSource = new PlexMediaSource { Platform = "Windows" } + } + }; + + IMediaSourceRepository repo = Substitute.For(); + repo.GetPlexPathReplacementsByLibraryId(Arg.Any()).Returns(replacements.AsTask()); + + IRuntimeInfo runtime = Substitute.For(); + runtime.IsOSPlatform(OSPlatform.Windows).Returns(false); + + var service = new PlexPathReplacementService( + repo, + runtime, + Substitute.For>()); + + string result = await service.GetReplacementPlexPath( + 0, + @"\\SERVERNAME\Something\Some Shared Folder\Some Movie\Some Movie.mkv"); + + result.Should().Be(@"/mnt/something else/Some Shared Folder/Some Movie/Some Movie.mkv"); + } [Test] public async Task PlexLinux_To_EtvWindows() diff --git a/ErsatzTV.Core/Emby/EmbyPathReplacementService.cs b/ErsatzTV.Core/Emby/EmbyPathReplacementService.cs index 6be92fec4..50601b172 100644 --- a/ErsatzTV.Core/Emby/EmbyPathReplacementService.cs +++ b/ErsatzTV.Core/Emby/EmbyPathReplacementService.cs @@ -1,4 +1,5 @@ using System.Runtime.InteropServices; +using System.Text.RegularExpressions; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Emby; using ErsatzTV.Core.Interfaces.Repositories; @@ -85,7 +86,10 @@ public class EmbyPathReplacementService : IEmbyPathReplacementService foreach (EmbyPathReplacement replacement in maybeReplacement) { - string finalPath = path.Replace(replacement.EmbyPath, replacement.LocalPath); + string finalPath = Regex.Replace(path, + Regex.Escape(replacement.EmbyPath), + Regex.Replace(replacement.LocalPath ?? string.Empty, "\\$[0-9]+", @"$$$0"), + RegexOptions.IgnoreCase); if (IsWindows(replacement.EmbyMediaSource, path) && !isTargetPlatformWindows) { finalPath = finalPath.Replace(@"\", @"/"); diff --git a/ErsatzTV.Core/Jellyfin/JellyfinPathReplacementService.cs b/ErsatzTV.Core/Jellyfin/JellyfinPathReplacementService.cs index 6bb6ae521..948ac12e5 100644 --- a/ErsatzTV.Core/Jellyfin/JellyfinPathReplacementService.cs +++ b/ErsatzTV.Core/Jellyfin/JellyfinPathReplacementService.cs @@ -1,4 +1,5 @@ using System.Runtime.InteropServices; +using System.Text.RegularExpressions; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Jellyfin; using ErsatzTV.Core.Interfaces.Repositories; @@ -85,7 +86,10 @@ public class JellyfinPathReplacementService : IJellyfinPathReplacementService foreach (JellyfinPathReplacement replacement in maybeReplacement) { - string finalPath = path.Replace(replacement.JellyfinPath, replacement.LocalPath); + string finalPath = Regex.Replace(path, + Regex.Escape(replacement.JellyfinPath), + Regex.Replace(replacement.LocalPath ?? string.Empty, "\\$[0-9]+", @"$$$0"), + RegexOptions.IgnoreCase); if (IsWindows(replacement.JellyfinMediaSource, path) && !isTargetPlatformWindows) { finalPath = finalPath.Replace(@"\", @"/"); diff --git a/ErsatzTV.Core/Plex/PlexPathReplacementService.cs b/ErsatzTV.Core/Plex/PlexPathReplacementService.cs index 2bc3d2bb6..2e263ad74 100644 --- a/ErsatzTV.Core/Plex/PlexPathReplacementService.cs +++ b/ErsatzTV.Core/Plex/PlexPathReplacementService.cs @@ -1,4 +1,5 @@ using System.Runtime.InteropServices; +using System.Text.RegularExpressions; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Plex; using ErsatzTV.Core.Interfaces.Repositories; @@ -51,7 +52,10 @@ public class PlexPathReplacementService : IPlexPathReplacementService foreach (PlexPathReplacement replacement in maybeReplacement) { - string finalPath = path.Replace(replacement.PlexPath, replacement.LocalPath); + string finalPath = Regex.Replace(path, + Regex.Escape(replacement.PlexPath), + Regex.Replace(replacement.LocalPath ?? string.Empty, "\\$[0-9]+", @"$$$0"), + RegexOptions.IgnoreCase); if (IsWindows(replacement.PlexMediaSource) && !_runtimeInfo.IsOSPlatform(OSPlatform.Windows)) { finalPath = finalPath.Replace(@"\", @"/");