Browse Source

fix path replacement bug (#677)

pull/678/head
Jason Dove 4 years ago committed by GitHub
parent
commit
fc1a051df5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 271
      ErsatzTV.Core.Tests/Emby/EmbyPathReplacementServiceTests.cs
  3. 64
      ErsatzTV.Core.Tests/Jellyfin/JellyfinPathReplacementServiceTests.cs
  4. 64
      ErsatzTV.Core.Tests/Plex/PlexPathReplacementServiceTests.cs
  5. 51
      ErsatzTV.Core/Emby/EmbyPathReplacementService.cs
  6. 53
      ErsatzTV.Core/Jellyfin/JellyfinPathReplacementService.cs
  7. 49
      ErsatzTV.Core/Plex/PlexPathReplacementService.cs

1
CHANGELOG.md

@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix bug scheduling mid-roll filler with content that contains one chapter
- No mid-roll filler will be inserted for content with zero or one chapters
- Fix thread sync bug with `HLS Segmenter` (and `MPEG-TS`) streaming modes
- Fix path replacement bug when media server path is left blank
### Added
- Add automated error reporting via Bugsnag

271
ErsatzTV.Core.Tests/Emby/EmbyPathReplacementServiceTests.cs

@ -0,0 +1,271 @@ @@ -0,0 +1,271 @@
using System.Runtime.InteropServices;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Runtime;
using ErsatzTV.Core.Emby;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
namespace ErsatzTV.Core.Tests.Emby;
[TestFixture]
public class EmbyPathReplacementServiceTests
{
[Test]
public async Task EmbyWindows_To_EtvWindows()
{
var replacements = new List<EmbyPathReplacement>
{
new()
{
Id = 1,
EmbyPath = @"C:\Something\Some Shared Folder",
LocalPath = @"C:\Something Else\Some Shared Folder",
EmbyMediaSource = new EmbyMediaSource { OperatingSystem = "Windows" }
}
};
var repo = new Mock<IMediaSourceRepository>();
repo.Setup(x => x.GetEmbyPathReplacementsByLibraryId(It.IsAny<int>())).Returns(replacements.AsTask());
var runtime = new Mock<IRuntimeInfo>();
runtime.Setup(x => x.IsOSPlatform(OSPlatform.Windows)).Returns(true);
var service = new EmbyPathReplacementService(
repo.Object,
runtime.Object,
new Mock<ILogger<EmbyPathReplacementService>>().Object);
string result = await service.GetReplacementEmbyPath(
0,
@"C:\Something\Some Shared Folder\Some Movie\Some Movie.mkv");
result.Should().Be(@"C:\Something Else\Some Shared Folder\Some Movie\Some Movie.mkv");
}
[Test]
public async Task EmbyWindows_To_EtvLinux()
{
var replacements = new List<EmbyPathReplacement>
{
new()
{
Id = 1,
EmbyPath = @"C:\Something\Some Shared Folder",
LocalPath = @"/mnt/something else/Some Shared Folder",
EmbyMediaSource = new EmbyMediaSource { OperatingSystem = "Windows" }
}
};
var repo = new Mock<IMediaSourceRepository>();
repo.Setup(x => x.GetEmbyPathReplacementsByLibraryId(It.IsAny<int>())).Returns(replacements.AsTask());
var runtime = new Mock<IRuntimeInfo>();
runtime.Setup(x => x.IsOSPlatform(OSPlatform.Windows)).Returns(false);
var service = new EmbyPathReplacementService(
repo.Object,
runtime.Object,
new Mock<ILogger<EmbyPathReplacementService>>().Object);
string result = await service.GetReplacementEmbyPath(
0,
@"C:\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 EmbyWindows_To_EtvLinux_UncPath()
{
var replacements = new List<EmbyPathReplacement>
{
new()
{
Id = 1,
EmbyPath = @"\\192.168.1.100\Something\Some Shared Folder",
LocalPath = @"/mnt/something else/Some Shared Folder",
EmbyMediaSource = new EmbyMediaSource { OperatingSystem = "Windows" }
}
};
var repo = new Mock<IMediaSourceRepository>();
repo.Setup(x => x.GetEmbyPathReplacementsByLibraryId(It.IsAny<int>())).Returns(replacements.AsTask());
var runtime = new Mock<IRuntimeInfo>();
runtime.Setup(x => x.IsOSPlatform(OSPlatform.Windows)).Returns(false);
var service = new EmbyPathReplacementService(
repo.Object,
runtime.Object,
new Mock<ILogger<EmbyPathReplacementService>>().Object);
string result = await service.GetReplacementEmbyPath(
0,
@"\\192.168.1.100\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 EmbyWindows_To_EtvLinux_UncPathWithTrailingSlash()
{
var replacements = new List<EmbyPathReplacement>
{
new()
{
Id = 1,
EmbyPath = @"\\192.168.1.100\Something\Some Shared Folder\",
LocalPath = @"/mnt/something else/Some Shared Folder/",
EmbyMediaSource = new EmbyMediaSource { OperatingSystem = "Windows" }
}
};
var repo = new Mock<IMediaSourceRepository>();
repo.Setup(x => x.GetEmbyPathReplacementsByLibraryId(It.IsAny<int>())).Returns(replacements.AsTask());
var runtime = new Mock<IRuntimeInfo>();
runtime.Setup(x => x.IsOSPlatform(OSPlatform.Windows)).Returns(false);
var service = new EmbyPathReplacementService(
repo.Object,
runtime.Object,
new Mock<ILogger<EmbyPathReplacementService>>().Object);
string result = await service.GetReplacementEmbyPath(
0,
@"\\192.168.1.100\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()
{
var replacements = new List<EmbyPathReplacement>
{
new()
{
Id = 1,
EmbyPath = @"/mnt/something/Some Shared Folder",
LocalPath = @"C:\Something Else\Some Shared Folder",
EmbyMediaSource = new EmbyMediaSource { OperatingSystem = "Linux" }
}
};
var repo = new Mock<IMediaSourceRepository>();
repo.Setup(x => x.GetEmbyPathReplacementsByLibraryId(It.IsAny<int>())).Returns(replacements.AsTask());
var runtime = new Mock<IRuntimeInfo>();
runtime.Setup(x => x.IsOSPlatform(OSPlatform.Windows)).Returns(true);
var service = new EmbyPathReplacementService(
repo.Object,
runtime.Object,
new Mock<ILogger<EmbyPathReplacementService>>().Object);
string result = await service.GetReplacementEmbyPath(
0,
@"/mnt/something/Some Shared Folder/Some Movie/Some Movie.mkv");
result.Should().Be(@"C:\Something Else\Some Shared Folder\Some Movie\Some Movie.mkv");
}
[Test]
public async Task EmbyLinux_To_EtvLinux()
{
var replacements = new List<EmbyPathReplacement>
{
new()
{
Id = 1,
EmbyPath = @"/mnt/something/Some Shared Folder",
LocalPath = @"/mnt/something else/Some Shared Folder",
EmbyMediaSource = new EmbyMediaSource { OperatingSystem = "Linux" }
}
};
var repo = new Mock<IMediaSourceRepository>();
repo.Setup(x => x.GetEmbyPathReplacementsByLibraryId(It.IsAny<int>())).Returns(replacements.AsTask());
var runtime = new Mock<IRuntimeInfo>();
runtime.Setup(x => x.IsOSPlatform(OSPlatform.Windows)).Returns(false);
var service = new EmbyPathReplacementService(
repo.Object,
runtime.Object,
new Mock<ILogger<EmbyPathReplacementService>>().Object);
string result = await service.GetReplacementEmbyPath(
0,
@"/mnt/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 Should_Not_Throw_For_Null_EmbyPath()
{
var replacements = new List<EmbyPathReplacement>
{
new()
{
Id = 1,
EmbyPath = null,
LocalPath = @"/mnt/something else/Some Shared Folder",
EmbyMediaSource = new EmbyMediaSource { OperatingSystem = "Linux" }
}
};
var repo = new Mock<IMediaSourceRepository>();
repo.Setup(x => x.GetEmbyPathReplacementsByLibraryId(It.IsAny<int>())).Returns(replacements.AsTask());
var runtime = new Mock<IRuntimeInfo>();
runtime.Setup(x => x.IsOSPlatform(OSPlatform.Windows)).Returns(false);
var service = new EmbyPathReplacementService(
repo.Object,
runtime.Object,
new Mock<ILogger<EmbyPathReplacementService>>().Object);
string result = await service.GetReplacementEmbyPath(
0,
@"/mnt/something/Some Shared Folder/Some Movie/Some Movie.mkv");
result.Should().Be(@"/mnt/something/Some Shared Folder/Some Movie/Some Movie.mkv");
}
[Test]
public async Task Should_Not_Throw_For_Null_LocalPath()
{
var replacements = new List<EmbyPathReplacement>
{
new()
{
Id = 1,
EmbyPath = @"/mnt/something/Some Shared Folder",
LocalPath = null,
EmbyMediaSource = new EmbyMediaSource { OperatingSystem = "Linux" }
}
};
var repo = new Mock<IMediaSourceRepository>();
repo.Setup(x => x.GetEmbyPathReplacementsByLibraryId(It.IsAny<int>())).Returns(replacements.AsTask());
var runtime = new Mock<IRuntimeInfo>();
runtime.Setup(x => x.IsOSPlatform(OSPlatform.Windows)).Returns(false);
var service = new EmbyPathReplacementService(
repo.Object,
runtime.Object,
new Mock<ILogger<EmbyPathReplacementService>>().Object);
string result = await service.GetReplacementEmbyPath(
0,
@"/mnt/something/Some Shared Folder/Some Movie/Some Movie.mkv");
result.Should().Be(@"/Some Movie/Some Movie.mkv");
}
}

64
ErsatzTV.Core.Tests/Jellyfin/JellyfinPathReplacementServiceTests.cs

@ -204,4 +204,68 @@ public class JellyfinPathReplacementServiceTests @@ -204,4 +204,68 @@ public class JellyfinPathReplacementServiceTests
result.Should().Be(@"/mnt/something else/Some Shared Folder/Some Movie/Some Movie.mkv");
}
[Test]
public async Task Should_Not_Throw_For_Null_JellyfinPath()
{
var replacements = new List<JellyfinPathReplacement>
{
new()
{
Id = 1,
JellyfinPath = null,
LocalPath = @"/mnt/something else/Some Shared Folder",
JellyfinMediaSource = new JellyfinMediaSource { OperatingSystem = "Linux" }
}
};
var repo = new Mock<IMediaSourceRepository>();
repo.Setup(x => x.GetJellyfinPathReplacementsByLibraryId(It.IsAny<int>())).Returns(replacements.AsTask());
var runtime = new Mock<IRuntimeInfo>();
runtime.Setup(x => x.IsOSPlatform(OSPlatform.Windows)).Returns(false);
var service = new JellyfinPathReplacementService(
repo.Object,
runtime.Object,
new Mock<ILogger<JellyfinPathReplacementService>>().Object);
string result = await service.GetReplacementJellyfinPath(
0,
@"/mnt/something/Some Shared Folder/Some Movie/Some Movie.mkv");
result.Should().Be(@"/mnt/something/Some Shared Folder/Some Movie/Some Movie.mkv");
}
[Test]
public async Task Should_Not_Throw_For_Null_LocalPath()
{
var replacements = new List<JellyfinPathReplacement>
{
new()
{
Id = 1,
JellyfinPath = @"/mnt/something/Some Shared Folder",
LocalPath = null,
JellyfinMediaSource = new JellyfinMediaSource { OperatingSystem = "Linux" }
}
};
var repo = new Mock<IMediaSourceRepository>();
repo.Setup(x => x.GetJellyfinPathReplacementsByLibraryId(It.IsAny<int>())).Returns(replacements.AsTask());
var runtime = new Mock<IRuntimeInfo>();
runtime.Setup(x => x.IsOSPlatform(OSPlatform.Windows)).Returns(false);
var service = new JellyfinPathReplacementService(
repo.Object,
runtime.Object,
new Mock<ILogger<JellyfinPathReplacementService>>().Object);
string result = await service.GetReplacementJellyfinPath(
0,
@"/mnt/something/Some Shared Folder/Some Movie/Some Movie.mkv");
result.Should().Be(@"/Some Movie/Some Movie.mkv");
}
}

64
ErsatzTV.Core.Tests/Plex/PlexPathReplacementServiceTests.cs

@ -204,4 +204,68 @@ public class PlexPathReplacementServiceTests @@ -204,4 +204,68 @@ public class PlexPathReplacementServiceTests
result.Should().Be(@"/mnt/something else/Some Shared Folder/Some Movie/Some Movie.mkv");
}
[Test]
public async Task Should_Not_Throw_For_Null_PlexPath()
{
var replacements = new List<PlexPathReplacement>
{
new()
{
Id = 1,
PlexPath = null,
LocalPath = @"/mnt/something else/Some Shared Folder",
PlexMediaSource = new PlexMediaSource { Platform = "Linux" }
}
};
var repo = new Mock<IMediaSourceRepository>();
repo.Setup(x => x.GetPlexPathReplacementsByLibraryId(It.IsAny<int>())).Returns(replacements.AsTask());
var runtime = new Mock<IRuntimeInfo>();
runtime.Setup(x => x.IsOSPlatform(OSPlatform.Windows)).Returns(false);
var service = new PlexPathReplacementService(
repo.Object,
runtime.Object,
new Mock<ILogger<PlexPathReplacementService>>().Object);
string result = await service.GetReplacementPlexPath(
0,
@"/mnt/something/Some Shared Folder/Some Movie/Some Movie.mkv");
result.Should().Be(@"/mnt/something/Some Shared Folder/Some Movie/Some Movie.mkv");
}
[Test]
public async Task Should_Not_Throw_For_Null_LocalPath()
{
var replacements = new List<PlexPathReplacement>
{
new()
{
Id = 1,
PlexPath = @"/mnt/something/Some Shared Folder",
LocalPath = null,
PlexMediaSource = new PlexMediaSource { Platform = "Linux" }
}
};
var repo = new Mock<IMediaSourceRepository>();
repo.Setup(x => x.GetPlexPathReplacementsByLibraryId(It.IsAny<int>())).Returns(replacements.AsTask());
var runtime = new Mock<IRuntimeInfo>();
runtime.Setup(x => x.IsOSPlatform(OSPlatform.Windows)).Returns(false);
var service = new PlexPathReplacementService(
repo.Object,
runtime.Object,
new Mock<ILogger<PlexPathReplacementService>>().Object);
string result = await service.GetReplacementPlexPath(
0,
@"/mnt/something/Some Shared Folder/Some Movie/Some Movie.mkv");
result.Should().Be(@"/Some Movie/Some Movie.mkv");
}
}

51
ErsatzTV.Core/Emby/EmbyPathReplacementService.cs

@ -40,6 +40,11 @@ public class EmbyPathReplacementService : IEmbyPathReplacementService @@ -40,6 +40,11 @@ public class EmbyPathReplacementService : IEmbyPathReplacementService
.SingleOrDefault(
r =>
{
if (string.IsNullOrWhiteSpace(r.EmbyPath))
{
return false;
}
string separatorChar = IsWindows(r.EmbyMediaSource, path) ? @"\" : @"/";
string prefix = r.EmbyPath.EndsWith(separatorChar)
? r.EmbyPath
@ -47,32 +52,32 @@ public class EmbyPathReplacementService : IEmbyPathReplacementService @@ -47,32 +52,32 @@ public class EmbyPathReplacementService : IEmbyPathReplacementService
return path.StartsWith(prefix);
});
return maybeReplacement.Match(
replacement =>
foreach (EmbyPathReplacement replacement in maybeReplacement)
{
string finalPath = path.Replace(replacement.EmbyPath, replacement.LocalPath);
if (IsWindows(replacement.EmbyMediaSource, path) && !_runtimeInfo.IsOSPlatform(OSPlatform.Windows))
{
string finalPath = path.Replace(replacement.EmbyPath, replacement.LocalPath);
if (IsWindows(replacement.EmbyMediaSource, path) && !_runtimeInfo.IsOSPlatform(OSPlatform.Windows))
{
finalPath = finalPath.Replace(@"\", @"/");
}
else if (!IsWindows(replacement.EmbyMediaSource, path) &&
_runtimeInfo.IsOSPlatform(OSPlatform.Windows))
{
finalPath = finalPath.Replace(@"/", @"\");
}
finalPath = finalPath.Replace(@"\", @"/");
}
else if (!IsWindows(replacement.EmbyMediaSource, path) &&
_runtimeInfo.IsOSPlatform(OSPlatform.Windows))
{
finalPath = finalPath.Replace(@"/", @"\");
}
if (log)
{
_logger.LogInformation(
"Replacing emby path {EmbyPath} with {LocalPath} resulting in {FinalPath}",
replacement.EmbyPath,
replacement.LocalPath,
finalPath);
}
if (log)
{
_logger.LogInformation(
"Replacing emby path {EmbyPath} with {LocalPath} resulting in {FinalPath}",
replacement.EmbyPath,
replacement.LocalPath,
finalPath);
}
return finalPath;
}
return finalPath;
},
() => path);
return path;
}
private static bool IsWindows(EmbyMediaSource embyMediaSource, string path)

53
ErsatzTV.Core/Jellyfin/JellyfinPathReplacementService.cs

@ -40,6 +40,11 @@ public class JellyfinPathReplacementService : IJellyfinPathReplacementService @@ -40,6 +40,11 @@ public class JellyfinPathReplacementService : IJellyfinPathReplacementService
.SingleOrDefault(
r =>
{
if (string.IsNullOrWhiteSpace(r.JellyfinPath))
{
return false;
}
string separatorChar = IsWindows(r.JellyfinMediaSource, path) ? @"\" : @"/";
string prefix = r.JellyfinPath.EndsWith(separatorChar)
? r.JellyfinPath
@ -47,33 +52,33 @@ public class JellyfinPathReplacementService : IJellyfinPathReplacementService @@ -47,33 +52,33 @@ public class JellyfinPathReplacementService : IJellyfinPathReplacementService
return path.StartsWith(prefix);
});
return maybeReplacement.Match(
replacement =>
foreach (JellyfinPathReplacement replacement in maybeReplacement)
{
string finalPath = path.Replace(replacement.JellyfinPath, replacement.LocalPath);
if (IsWindows(replacement.JellyfinMediaSource, path) &&
!_runtimeInfo.IsOSPlatform(OSPlatform.Windows))
{
string finalPath = path.Replace(replacement.JellyfinPath, replacement.LocalPath);
if (IsWindows(replacement.JellyfinMediaSource, path) &&
!_runtimeInfo.IsOSPlatform(OSPlatform.Windows))
{
finalPath = finalPath.Replace(@"\", @"/");
}
else if (!IsWindows(replacement.JellyfinMediaSource, path) &&
_runtimeInfo.IsOSPlatform(OSPlatform.Windows))
{
finalPath = finalPath.Replace(@"/", @"\");
}
finalPath = finalPath.Replace(@"\", @"/");
}
else if (!IsWindows(replacement.JellyfinMediaSource, path) &&
_runtimeInfo.IsOSPlatform(OSPlatform.Windows))
{
finalPath = finalPath.Replace(@"/", @"\");
}
if (log)
{
_logger.LogInformation(
"Replacing jellyfin path {JellyfinPath} with {LocalPath} resulting in {FinalPath}",
replacement.JellyfinPath,
replacement.LocalPath,
finalPath);
}
if (log)
{
_logger.LogInformation(
"Replacing jellyfin path {JellyfinPath} with {LocalPath} resulting in {FinalPath}",
replacement.JellyfinPath,
replacement.LocalPath,
finalPath);
}
return finalPath;
}
return finalPath;
},
() => path);
return path;
}
private static bool IsWindows(JellyfinMediaSource jellyfinMediaSource, string path)

49
ErsatzTV.Core/Plex/PlexPathReplacementService.cs

@ -37,36 +37,41 @@ public class PlexPathReplacementService : IPlexPathReplacementService @@ -37,36 +37,41 @@ public class PlexPathReplacementService : IPlexPathReplacementService
.SingleOrDefault(
r =>
{
if (string.IsNullOrWhiteSpace(r.PlexPath))
{
return false;
}
string separatorChar = IsWindows(r.PlexMediaSource) ? @"\" : @"/";
string prefix = r.PlexPath.EndsWith(separatorChar) ? r.PlexPath : r.PlexPath + separatorChar;
return path.StartsWith(prefix);
});
return maybeReplacement.Match(
replacement =>
foreach (PlexPathReplacement replacement in maybeReplacement)
{
string finalPath = path.Replace(replacement.PlexPath, replacement.LocalPath);
if (IsWindows(replacement.PlexMediaSource) && !_runtimeInfo.IsOSPlatform(OSPlatform.Windows))
{
string finalPath = path.Replace(replacement.PlexPath, replacement.LocalPath);
if (IsWindows(replacement.PlexMediaSource) && !_runtimeInfo.IsOSPlatform(OSPlatform.Windows))
{
finalPath = finalPath.Replace(@"\", @"/");
}
else if (!IsWindows(replacement.PlexMediaSource) && _runtimeInfo.IsOSPlatform(OSPlatform.Windows))
{
finalPath = finalPath.Replace(@"/", @"\");
}
finalPath = finalPath.Replace(@"\", @"/");
}
else if (!IsWindows(replacement.PlexMediaSource) && _runtimeInfo.IsOSPlatform(OSPlatform.Windows))
{
finalPath = finalPath.Replace(@"/", @"\");
}
if (log)
{
_logger.LogInformation(
"Replacing plex path {PlexPath} with {LocalPath} resulting in {FinalPath}",
replacement.PlexPath,
replacement.LocalPath,
finalPath);
}
if (log)
{
_logger.LogInformation(
"Replacing plex path {PlexPath} with {LocalPath} resulting in {FinalPath}",
replacement.PlexPath,
replacement.LocalPath,
finalPath);
}
return finalPath;
}
return finalPath;
},
() => path);
return path;
}
private static bool IsWindows(PlexMediaSource plexMediaSource) =>

Loading…
Cancel
Save