Fixed: Updating series path from different OS paths

Closes #6953
This commit is contained in:
Mark McDowall 2024-07-13 15:00:45 -07:00
parent e97e5bfe8f
commit 7fd5302177
5 changed files with 137 additions and 33 deletions

View File

@ -133,7 +133,7 @@ namespace NzbDrone.Common.Test
[TestCase(@"C:\test\", @"C:\Test\mydir")] [TestCase(@"C:\test\", @"C:\Test\mydir")]
[TestCase(@"C:\test", @"C:\Test\mydir\")] [TestCase(@"C:\test", @"C:\Test\mydir\")]
public void path_should_be_parent_on_windows_only(string parentPath, string childPath) public void windows_path_should_be_parent(string parentPath, string childPath)
{ {
var expectedResult = OsInfo.IsWindows; var expectedResult = OsInfo.IsWindows;
@ -145,22 +145,22 @@ namespace NzbDrone.Common.Test
[TestCase(@"C:\", null)] [TestCase(@"C:\", null)]
[TestCase(@"\\server\share", null)] [TestCase(@"\\server\share", null)]
[TestCase(@"\\server\share\test", @"\\server\share")] [TestCase(@"\\server\share\test", @"\\server\share")]
public void path_should_return_parent_windows(string path, string parentPath) public void windows_path_should_return_parent(string path, string parentPath)
{ {
WindowsOnly();
path.GetParentPath().Should().Be(parentPath); path.GetParentPath().Should().Be(parentPath);
} }
[TestCase(@"/", null)] [TestCase(@"/", null)]
[TestCase(@"/test", "/")] [TestCase(@"/test", "/")]
public void path_should_return_parent_mono(string path, string parentPath) [TestCase(@"/test/tv", "/test")]
public void unix_path_should_return_parent(string path, string parentPath)
{ {
PosixOnly();
path.GetParentPath().Should().Be(parentPath); path.GetParentPath().Should().Be(parentPath);
} }
[TestCase(@"C:\Test\mydir", "Test")] [TestCase(@"C:\Test\mydir", "Test")]
[TestCase(@"C:\Test\", @"C:\")] [TestCase(@"C:\Test\", @"C:\")]
[TestCase(@"C:\Test", @"C:\")]
[TestCase(@"C:\", null)] [TestCase(@"C:\", null)]
[TestCase(@"\\server\share", null)] [TestCase(@"\\server\share", null)]
[TestCase(@"\\server\share\test", @"\\server\share")] [TestCase(@"\\server\share\test", @"\\server\share")]
@ -172,12 +172,31 @@ namespace NzbDrone.Common.Test
[TestCase(@"/", null)] [TestCase(@"/", null)]
[TestCase(@"/test", "/")] [TestCase(@"/test", "/")]
[TestCase(@"/test/tv", "test")]
public void path_should_return_parent_name_mono(string path, string parentPath) public void path_should_return_parent_name_mono(string path, string parentPath)
{ {
PosixOnly(); PosixOnly();
path.GetParentName().Should().Be(parentPath); path.GetParentName().Should().Be(parentPath);
} }
[TestCase(@"C:\Test\mydir", "mydir")]
[TestCase(@"C:\Test\", "Test")]
[TestCase(@"C:\Test", "Test")]
[TestCase(@"C:\", "C:\\")]
[TestCase(@"\\server\share", @"\\server\share")]
[TestCase(@"\\server\share\test", "test")]
public void path_should_return_directory_name_windows(string path, string parentPath)
{
path.GetDirectoryName().Should().Be(parentPath);
}
[TestCase(@"/test", "test")]
[TestCase(@"/test/tv", "tv")]
public void path_should_return_directory_name_mono(string path, string parentPath)
{
path.GetDirectoryName().Should().Be(parentPath);
}
[Test] [Test]
public void path_should_return_parent_for_oversized_path() public void path_should_return_parent_for_oversized_path()
{ {

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.RegularExpressions;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Disk namespace NzbDrone.Common.Disk
@ -9,6 +10,8 @@ namespace NzbDrone.Common.Disk
private readonly string _path; private readonly string _path;
private readonly OsPathKind _kind; private readonly OsPathKind _kind;
private static readonly Regex UncPathRegex = new Regex(@"^\\\\(?:\?\\UNC\\)?[^\\]+\\[^\\]+(?:\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public OsPath(string path) public OsPath(string path)
{ {
if (path == null) if (path == null)
@ -19,7 +22,7 @@ namespace NzbDrone.Common.Disk
else else
{ {
_kind = DetectPathKind(path); _kind = DetectPathKind(path);
_path = FixSlashes(path, _kind); _path = TrimTrailingSlashes(FixSlashes(path, _kind), _kind);
} }
} }
@ -33,7 +36,7 @@ namespace NzbDrone.Common.Disk
else else
{ {
_kind = kind; _kind = kind;
_path = FixSlashes(path, kind); _path = TrimTrailingSlashes(FixSlashes(path, kind), kind);
} }
} }
@ -96,6 +99,19 @@ namespace NzbDrone.Common.Disk
return path; return path;
} }
private static string TrimTrailingSlashes(string path, OsPathKind kind)
{
switch (kind)
{
case OsPathKind.Windows when !path.EndsWith(":\\"):
return path.TrimEnd('\\');
case OsPathKind.Unix when path != "/":
return path.TrimEnd('/');
}
return path;
}
public OsPathKind Kind => _kind; public OsPathKind Kind => _kind;
public bool IsWindowsPath => _kind == OsPathKind.Windows; public bool IsWindowsPath => _kind == OsPathKind.Windows;
@ -130,7 +146,19 @@ namespace NzbDrone.Common.Disk
if (index == -1) if (index == -1)
{ {
return new OsPath(null); return Null;
}
var rootLength = GetRootLength();
if (rootLength == _path.Length)
{
return Null;
}
if (rootLength > index)
{
return new OsPath(_path.Substring(0, rootLength));
} }
return new OsPath(_path.Substring(0, index), _kind).AsDirectory(); return new OsPath(_path.Substring(0, index), _kind).AsDirectory();
@ -190,11 +218,50 @@ namespace NzbDrone.Common.Disk
return index; return index;
} }
private int GetRootLength()
{
if (!IsRooted)
{
return 0;
}
if (_kind == OsPathKind.Unix)
{
return 1;
}
if (_kind == OsPathKind.Windows)
{
if (HasWindowsDriveLetter(_path))
{
return 3;
}
var uncMatch = UncPathRegex.Match(_path);
// \\?\UNC\server\share\ or \\server\share
if (uncMatch.Success)
{
return uncMatch.Length;
}
// \\?\C:\
if (_path.StartsWith(@"\\?\"))
{
return 7;
}
}
return 0;
}
private string[] GetFragments() private string[] GetFragments()
{ {
return _path.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries); return _path.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries);
} }
public static OsPath Null => new (null);
public override string ToString() public override string ToString()
{ {
return _path; return _path;

View File

@ -99,7 +99,9 @@ namespace NzbDrone.Common.Extensions
return null; return null;
} }
return Directory.GetParent(cleanPath)?.FullName; var path = new OsPath(cleanPath).Directory.AsDirectory();
return path == OsPath.Null ? null : path.FullPath;
} }
public static string GetParentName(this string childPath) public static string GetParentName(this string childPath)
@ -114,6 +116,20 @@ namespace NzbDrone.Common.Extensions
return Directory.GetParent(cleanPath)?.Name; return Directory.GetParent(cleanPath)?.Name;
} }
public static string GetDirectoryName(this string childPath)
{
var cleanPath = childPath.GetCleanPath();
if (cleanPath.IsNullOrWhiteSpace())
{
return null;
}
var directoryInfo = new DirectoryInfo(cleanPath);
return directoryInfo.Name;
}
public static string GetCleanPath(this string path) public static string GetCleanPath(this string path)
{ {
var cleanPath = OsInfo.IsWindows var cleanPath = OsInfo.IsWindows
@ -125,27 +141,17 @@ namespace NzbDrone.Common.Extensions
public static bool IsParentPath(this string parentPath, string childPath) public static bool IsParentPath(this string parentPath, string childPath)
{ {
if (parentPath != "/" && !parentPath.EndsWith(":\\")) var parent = new OsPath(parentPath);
{ var child = new OsPath(childPath);
parentPath = parentPath.TrimEnd(Path.DirectorySeparatorChar);
}
if (childPath != "/" && !parentPath.EndsWith(":\\")) while (child.Directory != OsPath.Null)
{ {
childPath = childPath.TrimEnd(Path.DirectorySeparatorChar); if (child.Directory.Equals(parent))
}
var parent = new DirectoryInfo(parentPath);
var child = new DirectoryInfo(childPath);
while (child.Parent != null)
{
if (child.Parent.FullName.Equals(parent.FullName, DiskProviderBase.PathStringComparison))
{ {
return true; return true;
} }
child = child.Parent; child = child.Directory;
} }
return false; return false;

View File

@ -940,15 +940,15 @@ namespace NzbDrone.Core.Parser
public static string RemoveFileExtension(string title) public static string RemoveFileExtension(string title)
{ {
title = FileExtensionRegex.Replace(title, m => title = FileExtensionRegex.Replace(title, m =>
{
var extension = m.Value.ToLower();
if (MediaFiles.MediaFileExtensions.Extensions.Contains(extension) || new[] { ".par2", ".nzb" }.Contains(extension))
{ {
var extension = m.Value.ToLower(); return string.Empty;
if (MediaFiles.MediaFileExtensions.Extensions.Contains(extension) || new[] { ".par2", ".nzb" }.Contains(extension)) }
{
return string.Empty;
}
return m.Value; return m.Value;
}); });
return title; return title;
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Organizer; using NzbDrone.Core.Organizer;
using NzbDrone.Core.RootFolders; using NzbDrone.Core.RootFolders;
@ -15,11 +16,13 @@ namespace NzbDrone.Core.Tv
{ {
private readonly IBuildFileNames _fileNameBuilder; private readonly IBuildFileNames _fileNameBuilder;
private readonly IRootFolderService _rootFolderService; private readonly IRootFolderService _rootFolderService;
private readonly Logger _logger;
public SeriesPathBuilder(IBuildFileNames fileNameBuilder, IRootFolderService rootFolderService) public SeriesPathBuilder(IBuildFileNames fileNameBuilder, IRootFolderService rootFolderService, Logger logger)
{ {
_fileNameBuilder = fileNameBuilder; _fileNameBuilder = fileNameBuilder;
_rootFolderService = rootFolderService; _rootFolderService = rootFolderService;
_logger = logger;
} }
public string BuildPath(Series series, bool useExistingRelativeFolder) public string BuildPath(Series series, bool useExistingRelativeFolder)
@ -42,7 +45,16 @@ namespace NzbDrone.Core.Tv
{ {
var rootFolderPath = _rootFolderService.GetBestRootFolderPath(series.Path); var rootFolderPath = _rootFolderService.GetBestRootFolderPath(series.Path);
return rootFolderPath.GetRelativePath(series.Path); if (rootFolderPath.IsParentPath(series.Path))
{
return rootFolderPath.GetRelativePath(series.Path);
}
var directoryName = series.Path.GetDirectoryName();
_logger.Warn("Unable to get relative path for series path {0}, using series folder name {1}", series.Path, directoryName);
return directoryName;
} }
} }
} }