diff --git a/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj b/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj
index f2e2d3429..a4199d64b 100644
--- a/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj
+++ b/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj
@@ -80,6 +80,7 @@
+
diff --git a/src/NzbDrone.Common.Test/OsPathFixture.cs b/src/NzbDrone.Common.Test/OsPathFixture.cs
new file mode 100644
index 000000000..1e58b7eb3
--- /dev/null
+++ b/src/NzbDrone.Common.Test/OsPathFixture.cs
@@ -0,0 +1,237 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+using NzbDrone.Common.Disk;
+using NzbDrone.Test.Common;
+using FluentAssertions;
+
+namespace NzbDrone.Common.Test
+{
+ public class OsPathFixture : TestBase
+ {
+ [TestCase(@"C:\rooted\windows\path\", OsPathKind.Windows)]
+ [TestCase(@"C:\rooted\windows\path", OsPathKind.Windows)]
+ [TestCase(@"C:\", OsPathKind.Windows)]
+ [TestCase(@"C:", OsPathKind.Windows)]
+ [TestCase(@"\\rooted\unc\path\", OsPathKind.Windows)]
+ [TestCase(@"\\rooted\unc\path", OsPathKind.Windows)]
+ [TestCase(@"\relative\windows\path\", OsPathKind.Windows)]
+ [TestCase(@"\relative\windows\path", OsPathKind.Windows)]
+ [TestCase(@"relative\windows\path\", OsPathKind.Windows)]
+ [TestCase(@"relative\windows\path", OsPathKind.Windows)]
+ [TestCase(@"relative\", OsPathKind.Windows)]
+ [TestCase(@"relative", OsPathKind.Unknown)]
+ [TestCase("/rooted/linux/path/", OsPathKind.Unix)]
+ [TestCase("/rooted/linux/path", OsPathKind.Unix)]
+ [TestCase("/", OsPathKind.Unix)]
+ [TestCase("linux/path", OsPathKind.Unix)]
+ public void should_auto_detect_kind(String path, OsPathKind kind)
+ {
+ var result = new OsPath(path);
+
+ result.Kind.Should().Be(kind);
+
+ if (kind == OsPathKind.Windows)
+ {
+ result.IsWindowsPath.Should().BeTrue();
+ result.IsUnixPath.Should().BeFalse();
+ }
+ else if (kind == OsPathKind.Unix)
+ {
+ result.IsWindowsPath.Should().BeFalse();
+ result.IsUnixPath.Should().BeTrue();
+ }
+ else
+ {
+ result.IsWindowsPath.Should().BeFalse();
+ result.IsUnixPath.Should().BeFalse();
+ }
+ }
+
+ [Test]
+ public void should_add_directory_slash()
+ {
+ var osPath = new OsPath(@"C:\rooted\windows\path\");
+
+ osPath.Directory.Should().NotBeNull();
+ osPath.Directory.ToString().Should().Be(@"C:\rooted\windows\");
+ }
+
+ [TestCase(@"C:\rooted\windows\path", @"C:\rooted\windows\")]
+ [TestCase(@"C:\rooted", @"C:\")]
+ [TestCase(@"C:", null)]
+ [TestCase("/rooted/linux/path", "/rooted/linux/")]
+ [TestCase("/rooted", "/")]
+ [TestCase("/", null)]
+ public void should_return_parent_directory(String path, String expectedParent)
+ {
+ var osPath = new OsPath(path);
+
+ osPath.Directory.Should().NotBeNull();
+ osPath.Directory.Should().Be(new OsPath(expectedParent));
+ }
+
+ [Test]
+ public void should_return_empty_as_parent_of_root_unc()
+ {
+ var osPath = new OsPath(@"\\unc");
+
+ osPath.Directory.IsEmpty.Should().BeTrue();
+ }
+
+ [TestCase(@"C:\rooted\windows\path")]
+ [TestCase(@"C:")]
+ [TestCase(@"\\blaat")]
+ [TestCase("/rooted/linux/path")]
+ [TestCase("/")]
+ public void should_detect_rooted_ospaths(String path)
+ {
+ var osPath = new OsPath(path);
+
+ osPath.IsRooted.Should().BeTrue();
+ }
+
+ [TestCase(@"\rooted\windows\path")]
+ [TestCase(@"rooted\windows\path")]
+ [TestCase(@"path")]
+ [TestCase("linux/path")]
+ public void should_detect_unrooted_ospaths(String path)
+ {
+ var osPath = new OsPath(path);
+
+ osPath.IsRooted.Should().BeFalse();
+ }
+
+ [TestCase(@"C:\rooted\windows\path", "path")]
+ [TestCase(@"C:", "C:")]
+ [TestCase(@"\\blaat", "blaat")]
+ [TestCase("/rooted/linux/path", "path")]
+ [TestCase("/", null)]
+ [TestCase(@"\rooted\windows\path\", "path")]
+ [TestCase(@"rooted\windows\path", "path")]
+ [TestCase(@"path", "path")]
+ [TestCase("linux/path", "path")]
+ public void should_return_filename(String path, String expectedFilePath)
+ {
+ var osPath = new OsPath(path);
+
+ osPath.FileName.Should().Be(expectedFilePath);
+ }
+
+ [Test]
+ public void should_compare_windows_ospathkind_case_insensitive()
+ {
+ var left = new OsPath(@"C:\rooted\Windows\path");
+ var right = new OsPath(@"C:\rooted\windows\path");
+
+ left.Should().Be(right);
+ }
+
+ [Test]
+ public void should_compare_unix_ospathkind_case_sensitive()
+ {
+ var left = new OsPath(@"/rooted/Linux/path");
+ var right = new OsPath(@"/rooted/linux/path");
+
+ left.Should().NotBe(right);
+ }
+
+ [Test]
+ public void should_not_ignore_trailing_slash_during_compare()
+ {
+ var left = new OsPath(@"/rooted/linux/path/");
+ var right = new OsPath(@"/rooted/linux/path");
+
+ left.Should().NotBe(right);
+ }
+
+ [TestCase(@"C:\Test", @"sub", @"C:\Test\sub")]
+ [TestCase(@"C:\Test", @"sub\test", @"C:\Test\sub\test")]
+ [TestCase(@"C:\Test\", @"\sub", @"C:\Test\sub")]
+ [TestCase(@"C:\Test", @"sub\", @"C:\Test\sub\")]
+ [TestCase(@"C:\Test", @"C:\Test2\sub", @"C:\Test2\sub")]
+ [TestCase(@"/Test", @"sub", @"/Test/sub")]
+ [TestCase(@"/Test", @"sub/", @"/Test/sub/")]
+ [TestCase(@"/Test", @"sub/", @"/Test/sub/")]
+ [TestCase(@"/Test/", @"sub/test/", @"/Test/sub/test/")]
+ [TestCase(@"/Test/", @"/Test2/", @"/Test2/")]
+ [TestCase(@"C:\Test", "", @"C:\Test")]
+ public void should_combine_path(String left, String right, String expectedResult)
+ {
+ var osPathLeft = new OsPath(left);
+ var osPathRight = new OsPath(right);
+
+ var result = osPathLeft + osPathRight;
+
+ result.FullPath.Should().Be(expectedResult);
+ }
+
+ [Test]
+ public void should_fix_slashes_windows()
+ {
+ var osPath = new OsPath(@"C:/on/windows/transmission\uses/forward/slashes");
+
+ osPath.Kind.Should().Be(OsPathKind.Windows);
+ osPath.FullPath.Should().Be(@"C:\on\windows\transmission\uses\forward\slashes");
+ }
+
+ [Test]
+ public void should_fix_slashes_unix()
+ {
+ var osPath = new OsPath(@"/just/a/test\to\verify the/slashes\");
+
+ osPath.Kind.Should().Be(OsPathKind.Unix);
+ osPath.FullPath.Should().Be(@"/just/a/test/to/verify the/slashes/");
+ }
+
+ [Test]
+ public void should_combine_mixed_slashes()
+ {
+ var left = new OsPath(@"C:/on/windows/transmission");
+ var right = new OsPath(@"uses/forward/slashes", OsPathKind.Unknown);
+
+ var osPath = left + right;
+
+ osPath.Kind.Should().Be(OsPathKind.Windows);
+ osPath.FullPath.Should().Be(@"C:\on\windows\transmission\uses\forward\slashes");
+ }
+
+ [TestCase(@"C:\Test\Data\", @"C:\Test\Data\Sub\Folder", @"Sub\Folder")]
+ [TestCase(@"C:\Test\Data\", @"C:\Test\Data2\Sub\Folder", @"..\Data2\Sub\Folder")]
+ [TestCase(@"/parent/folder", @"/parent/folder/Sub/Folder", @"Sub/Folder")]
+ public void should_create_relative_path(String parent, String child, String expected)
+ {
+ var left = new OsPath(child);
+ var right = new OsPath(parent);
+
+ var osPath = left - right;
+
+ osPath.Kind.Should().Be(OsPathKind.Unknown);
+ osPath.FullPath.Should().Be(expected);
+ }
+
+ [Test]
+ public void should_parse_null_as_empty()
+ {
+ var result = new OsPath(null);
+
+ result.FullPath.Should().BeEmpty();
+ result.IsEmpty.Should().BeTrue();
+ }
+
+ [TestCase(@"C:\Test\", @"C:\Test", true)]
+ [TestCase(@"C:\Test\", @"C:\Test\Contains\", true)]
+ [TestCase(@"C:\Test\", @"C:\Other\", false)]
+ public void should_evaluate_contains(String parent, String child, Boolean expectedResult)
+ {
+ var left = new OsPath(parent);
+ var right = new OsPath(child);
+
+ var result = left.Contains(right);
+
+ result.Should().Be(expectedResult);
+ }
+ }
+}
diff --git a/src/NzbDrone.Common/Disk/OsPath.cs b/src/NzbDrone.Common/Disk/OsPath.cs
new file mode 100644
index 000000000..540f70c25
--- /dev/null
+++ b/src/NzbDrone.Common/Disk/OsPath.cs
@@ -0,0 +1,406 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NzbDrone.Common.Exceptions;
+
+namespace NzbDrone.Common.Disk
+{
+ public struct OsPath : IEquatable
+ {
+ private readonly String _path;
+ private readonly OsPathKind _kind;
+
+ public OsPath(String path)
+ {
+ if (path == null)
+ {
+ _kind = OsPathKind.Unknown;
+ _path = String.Empty;
+ }
+ else
+ {
+ _kind = DetectPathKind(path);
+ _path = FixSlashes(path, _kind);
+ }
+ }
+
+ public OsPath(String path, OsPathKind kind)
+ {
+ if (path == null)
+ {
+ _kind = kind;
+ _path = String.Empty;
+ }
+ else
+ {
+ _kind = kind;
+ _path = FixSlashes(path, kind);
+ }
+ }
+
+ private static OsPathKind DetectPathKind(String path)
+ {
+ if (path.StartsWith("/"))
+ {
+ return OsPathKind.Unix;
+ }
+ if (path.Contains(':') || path.Contains('\\'))
+ {
+ return OsPathKind.Windows;
+ }
+ else if (path.Contains('/'))
+ {
+ return OsPathKind.Unix;
+ }
+ else
+ {
+ return OsPathKind.Unknown;
+ }
+ }
+
+ private static String FixSlashes(String path, OsPathKind kind)
+ {
+ if (kind == OsPathKind.Windows)
+ {
+ return path.Replace('/', '\\');
+ }
+ else if (kind == OsPathKind.Unix)
+ {
+ return path.Replace('\\', '/');
+ }
+
+ return path;
+ }
+
+ public OsPathKind Kind
+ {
+ get { return _kind; }
+ }
+
+ public Boolean IsWindowsPath
+ {
+ get { return _kind == OsPathKind.Windows; }
+ }
+
+ public Boolean IsUnixPath
+ {
+ get { return _kind == OsPathKind.Unix; }
+ }
+
+ public Boolean IsEmpty
+ {
+ get
+ {
+ return _path.IsNullOrWhiteSpace();
+ }
+ }
+
+ public Boolean IsRooted
+ {
+ get
+ {
+ if (IsWindowsPath)
+ {
+ return _path.StartsWith(@"\\") || _path.Contains(':');
+ }
+ else if (IsUnixPath)
+ {
+ return _path.StartsWith("/");
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ public OsPath Directory
+ {
+ get
+ {
+ var index = GetFileNameIndex();
+
+ if (index == -1)
+ {
+ return new OsPath(null);
+ }
+ else
+ {
+ return new OsPath(_path.Substring(0, index), _kind).AsDirectory();
+ }
+ }
+ }
+
+ public String FullPath
+ {
+ get
+ {
+ return _path;
+ }
+ }
+
+ public String FileName
+ {
+ get
+ {
+ var index = GetFileNameIndex();
+
+ if (index == -1)
+ {
+ var path = _path.Trim('\\', '/');
+
+ if (path.Length == 0)
+ {
+ return null;
+ }
+
+ return path;
+ }
+ else
+ {
+ return _path.Substring(index).Trim('\\', '/');
+ }
+ }
+ }
+
+ private Int32 GetFileNameIndex()
+ {
+ if (_path.Length < 2)
+ {
+ return -1;
+ }
+
+ var index = _path.LastIndexOfAny(new[] { '/', '\\' }, _path.Length - 2);
+
+ if (index == -1)
+ {
+ return -1;
+ }
+
+ if (_path.StartsWith(@"\\") && index < 2)
+ {
+ return -1;
+ }
+
+ if (_path.StartsWith("/") && index == 0)
+ {
+ index++;
+ }
+
+ return index;
+ }
+
+ private String[] GetFragments()
+ {
+ return _path.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries);
+ }
+
+ public override String ToString()
+ {
+ return _path;
+ }
+
+ public override Int32 GetHashCode()
+ {
+ return _path.ToLowerInvariant().GetHashCode();
+ }
+
+ public override Boolean Equals(Object obj)
+ {
+ if (obj is OsPath)
+ {
+ return Equals((OsPath)obj);
+ }
+ else if (obj is String)
+ {
+ return Equals(new OsPath(obj as String));
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public OsPath AsDirectory()
+ {
+ if (IsEmpty)
+ {
+ return this;
+ }
+
+ if (Kind == OsPathKind.Windows)
+ {
+ return new OsPath(_path.TrimEnd('\\') + "\\", _kind);
+ }
+ else if (Kind == OsPathKind.Unix)
+ {
+ return new OsPath(_path.TrimEnd('/') + "/", _kind);
+ }
+ else
+ {
+ return this;
+ }
+ }
+
+ public Boolean Contains(OsPath other)
+ {
+ if (!IsRooted || !other.IsRooted)
+ {
+ return false;
+ }
+
+ var leftFragments = GetFragments();
+ var rightFragments = other.GetFragments();
+
+ if (rightFragments.Length < leftFragments.Length)
+ {
+ return false;
+ }
+
+ var stringComparison = (Kind == OsPathKind.Windows || other.Kind == OsPathKind.Windows) ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture;
+
+ for (int i = 0; i < leftFragments.Length; i++)
+ {
+ if (!String.Equals(leftFragments[i], rightFragments[i], stringComparison))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public Boolean Equals(OsPath other)
+ {
+ if (ReferenceEquals(other, null)) return false;
+
+ if (_path == other._path)
+ {
+ return true;
+ }
+
+ var left = _path;
+ var right = other._path;
+
+ if (Kind == OsPathKind.Windows || other.Kind == OsPathKind.Windows)
+ {
+ return String.Equals(left, right, StringComparison.InvariantCultureIgnoreCase);
+ }
+ else
+ {
+ return String.Equals(left, right, StringComparison.InvariantCulture);
+ }
+ }
+
+ public static Boolean operator ==(OsPath left, OsPath right)
+ {
+ if (ReferenceEquals(left, null)) return ReferenceEquals(right, null);
+
+ return left.Equals(right);
+ }
+
+ public static Boolean operator !=(OsPath left, OsPath right)
+ {
+ if (ReferenceEquals(left, null)) return !ReferenceEquals(right, null);
+
+ return !left.Equals(right);
+ }
+
+ public static OsPath operator +(OsPath left, OsPath right)
+ {
+ if (left.Kind != right.Kind && right.Kind != OsPathKind.Unknown)
+ {
+ throw new Exception(String.Format("Cannot combine OsPaths of different platforms ('{0}' + '{1}')", left, right));
+ }
+
+ if (right.IsEmpty)
+ {
+ return left;
+ }
+
+ if (right.IsRooted)
+ {
+ return right;
+ }
+
+ if (left.Kind == OsPathKind.Windows || right.Kind == OsPathKind.Windows)
+ {
+ return new OsPath(String.Join("\\", left._path.TrimEnd('\\'), right._path.TrimStart('\\')), OsPathKind.Windows);
+ }
+ else if (left.Kind == OsPathKind.Unix || right.Kind == OsPathKind.Unix)
+ {
+ return new OsPath(String.Join("/", left._path.TrimEnd('/'), right._path), OsPathKind.Unix);
+ }
+ else
+ {
+ return new OsPath(String.Join("/", left._path, right._path), OsPathKind.Unknown);
+ }
+ }
+
+ public static OsPath operator +(OsPath left, String right)
+ {
+ return left + new OsPath(right);
+ }
+
+ public static OsPath operator -(OsPath left, OsPath right)
+ {
+ if (!left.IsRooted || !right.IsRooted)
+ {
+ throw new ArgumentException("Cannot determine relative path for unrooted paths.");
+ }
+
+ var leftFragments = left.GetFragments();
+ var rightFragments = right.GetFragments();
+
+ var stringComparison = (left.Kind == OsPathKind.Windows || right.Kind == OsPathKind.Windows) ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture;
+
+ int i;
+ for (i = 0; i < leftFragments.Length && i < rightFragments.Length; i++)
+ {
+ if (!String.Equals(leftFragments[i], rightFragments[i], stringComparison))
+ {
+ break;
+ }
+ }
+
+ if (i == 0)
+ {
+ return right;
+ }
+
+ var newFragments = new List();
+
+ for (int j = i; j < rightFragments.Length; j++)
+ {
+ newFragments.Add("..");
+ }
+
+ for (int j = i; j < leftFragments.Length; j++)
+ {
+ newFragments.Add(leftFragments[j]);
+ }
+
+ if (left.FullPath.EndsWith("\\") || left.FullPath.EndsWith("/"))
+ {
+ newFragments.Add(String.Empty);
+ }
+
+ if (left.Kind == OsPathKind.Windows || right.Kind == OsPathKind.Windows)
+ {
+ return new OsPath(String.Join("\\", newFragments), OsPathKind.Unknown);
+ }
+ else
+ {
+ return new OsPath(String.Join("/", newFragments), OsPathKind.Unknown);
+ }
+ }
+ }
+
+ public enum OsPathKind
+ {
+ Unknown,
+ Windows,
+ Unix
+ }
+}
diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj
index adb025be3..1933d084c 100644
--- a/src/NzbDrone.Common/NzbDrone.Common.csproj
+++ b/src/NzbDrone.Common/NzbDrone.Common.csproj
@@ -71,6 +71,7 @@
+
diff --git a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs
index 51f81076e..116ac2874 100644
--- a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs
@@ -31,7 +31,7 @@ namespace NzbDrone.Core.Test.Download
_completed = Builder.CreateListOfSize(1)
.All()
.With(h => h.Status = DownloadItemStatus.Completed)
- .With(h => h.OutputPath = @"C:\DropFolder\MyDownload".AsOsAgnostic())
+ .With(h => h.OutputPath = new OsPath(@"C:\DropFolder\MyDownload".AsOsAgnostic()))
.With(h => h.Title = "Drone.S01E01.HDTV")
.Build()
.ToList();
@@ -325,7 +325,7 @@ namespace NzbDrone.Core.Test.Download
_completed.AddRange(Builder.CreateListOfSize(2)
.All()
.With(h => h.Status = DownloadItemStatus.Completed)
- .With(h => h.OutputPath = @"C:\DropFolder\MyDownload".AsOsAgnostic())
+ .With(h => h.OutputPath = new OsPath(@"C:\DropFolder\MyDownload".AsOsAgnostic()))
.With(h => h.Title = "Drone.S01E01.HDTV")
.Build());
diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs
index 9186072a1..0a7272e1b 100644
--- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs
+++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs
@@ -39,8 +39,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
.Returns(r => new HttpResponse(r, new HttpHeader(), new Byte[0]));
Mocker.GetMock()
- .Setup(v => v.RemapRemoteToLocal(It.IsAny(), It.IsAny()))
- .Returns((h,r) => r);
+ .Setup(v => v.RemapRemoteToLocal(It.IsAny(), It.IsAny()))
+ .Returns((h, r) => r);
}
protected virtual RemoteEpisode CreateRemoteEpisode()
diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetTests/NzbgetFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetTests/NzbgetFixture.cs
index 87d193506..a6b0e1cfc 100644
--- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetTests/NzbgetFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetTests/NzbgetFixture.cs
@@ -14,6 +14,7 @@ using NzbDrone.Core.Parser.Model;
using NzbDrone.Test.Common;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.RemotePathMappings;
+using NzbDrone.Common.Disk;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
{
@@ -300,8 +301,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
public void should_return_status_with_mounted_outputdir()
{
Mocker.GetMock()
- .Setup(v => v.RemapRemoteToLocal("127.0.0.1", "/remote/mount/tv"))
- .Returns(@"O:\mymount".AsOsAgnostic());
+ .Setup(v => v.RemapRemoteToLocal("127.0.0.1", It.IsAny()))
+ .Returns(new OsPath(@"O:\mymount".AsOsAgnostic()));
var result = Subject.GetStatus();
@@ -314,8 +315,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
public void should_remap_storage_if_mounted()
{
Mocker.GetMock()
- .Setup(v => v.RemapRemoteToLocal("127.0.0.1", "/remote/mount/tv/Droned.S01E01.Pilot.1080p.WEB-DL-DRONE"))
- .Returns(@"O:\mymount\Droned.S01E01.Pilot.1080p.WEB-DL-DRONE".AsOsAgnostic());
+ .Setup(v => v.RemapRemoteToLocal("127.0.0.1", It.IsAny()))
+ .Returns(new OsPath(@"O:\mymount\Droned.S01E01.Pilot.1080p.WEB-DL-DRONE".AsOsAgnostic()));
GivenQueue(null);
GivenHistory(_completed);
diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs
index 7a5549bbf..686d6b75d 100644
--- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs
@@ -11,6 +11,7 @@ using NzbDrone.Core.Download.Clients.Sabnzbd.Responses;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
using NzbDrone.Core.RemotePathMappings;
+using NzbDrone.Common.Disk;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
{
@@ -303,15 +304,15 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
var result = Subject.GetItems().Single();
- result.OutputPath.Should().Be((@"C:\sorted\" + title).AsOsAgnostic());
+ result.OutputPath.Should().Be(new OsPath((@"C:\sorted\" + title).AsOsAgnostic()).AsDirectory());
}
[Test]
public void should_remap_storage_if_mounted()
{
Mocker.GetMock()
- .Setup(v => v.RemapRemoteToLocal("127.0.0.1", "/remote/mount/vv/Droned.S01E01.Pilot.1080p.WEB-DL-DRONE"))
- .Returns(@"O:\mymount\Droned.S01E01.Pilot.1080p.WEB-DL-DRONE".AsOsAgnostic());
+ .Setup(v => v.RemapRemoteToLocal("127.0.0.1", It.IsAny()))
+ .Returns(new OsPath(@"O:\mymount\Droned.S01E01.Pilot.1080p.WEB-DL-DRONE".AsOsAgnostic()));
GivenQueue(null);
GivenHistory(_completed);
@@ -370,8 +371,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
public void should_return_status_with_mounted_outputdir()
{
Mocker.GetMock()
- .Setup(v => v.RemapRemoteToLocal("127.0.0.1", "/remote/mount/vv"))
- .Returns(@"O:\mymount".AsOsAgnostic());
+ .Setup(v => v.RemapRemoteToLocal("127.0.0.1", It.IsAny()))
+ .Returns(new OsPath(@"O:\mymount".AsOsAgnostic()));
GivenQueue(null);
diff --git a/src/NzbDrone.Core.Test/HealthCheck/Checks/ImportMechanismCheckFixture.cs b/src/NzbDrone.Core.Test/HealthCheck/Checks/ImportMechanismCheckFixture.cs
index 4e0724e84..8260c03ee 100644
--- a/src/NzbDrone.Core.Test/HealthCheck/Checks/ImportMechanismCheckFixture.cs
+++ b/src/NzbDrone.Core.Test/HealthCheck/Checks/ImportMechanismCheckFixture.cs
@@ -39,7 +39,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.With(v => v.State == TrackedDownloadState.Downloading)
.With(v => v.DownloadItem = new DownloadClientItem())
.With(v => v.DownloadItem.Status = DownloadItemStatus.Completed)
- .With(v => v.DownloadItem.OutputPath = @"C:\Test\DropFolder\myfile.mkv".AsOsAgnostic())
+ .With(v => v.DownloadItem.OutputPath = new OsPath(@"C:\Test\DropFolder\myfile.mkv".AsOsAgnostic()))
.Build();
Mocker.GetMock()
@@ -78,7 +78,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
GivenCompletedDownloadHandling(true);
GivenDroneFactoryFolder(true);
- _completed.First().DownloadItem.OutputPath = (DRONE_FACTORY_FOLDER + @"\myfile.mkv").AsOsAgnostic();
+ _completed.First().DownloadItem.OutputPath = new OsPath((DRONE_FACTORY_FOLDER + @"\myfile.mkv").AsOsAgnostic());
Subject.Check().ShouldBeWarning();
}
diff --git a/src/NzbDrone.Core.Test/RemotePathMappingsTests/RemotePathMappingServiceFixture.cs b/src/NzbDrone.Core.Test/RemotePathMappingsTests/RemotePathMappingServiceFixture.cs
index dea074bce..872ca995b 100644
--- a/src/NzbDrone.Core.Test/RemotePathMappingsTests/RemotePathMappingServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/RemotePathMappingsTests/RemotePathMappingServiceFixture.cs
@@ -97,13 +97,13 @@ namespace NzbDrone.Core.Test.RemotePathMappingsTests
GivenMapping();
- var result = Subject.RemapRemoteToLocal(host, remotePath);
+ var result = Subject.RemapRemoteToLocal(host, new OsPath(remotePath));
result.Should().Be(expectedLocalPath);
}
[TestCase("my-server.localdomain", "/mnt/storage/downloads/tv", @"D:\mountedstorage\downloads\tv")]
- [TestCase("my-server.localdomain", "/mnt/storage", @"D:\mountedstorage")]
+ [TestCase("my-server.localdomain", "/mnt/storage/", @"D:\mountedstorage")]
[TestCase("my-2server.localdomain", "/mnt/storage/downloads/tv", "/mnt/storage/downloads/tv")]
[TestCase("my-server.localdomain", "/mnt/storageabc/downloads/tv", "/mnt/storageabc/downloads/tv")]
public void should_remap_local_to_remote(String host, String expectedRemotePath, String localPath)
@@ -112,7 +112,7 @@ namespace NzbDrone.Core.Test.RemotePathMappingsTests
GivenMapping();
- var result = Subject.RemapLocalToRemote(host, localPath);
+ var result = Subject.RemapLocalToRemote(host, new OsPath(localPath));
result.Should().Be(expectedRemotePath);
}
diff --git a/src/NzbDrone.Core/Datastore/Converters/OsPathConverter.cs b/src/NzbDrone.Core/Datastore/Converters/OsPathConverter.cs
new file mode 100644
index 000000000..9e460300c
--- /dev/null
+++ b/src/NzbDrone.Core/Datastore/Converters/OsPathConverter.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Marr.Data.Converters;
+using Marr.Data.Mapping;
+using Newtonsoft.Json;
+using NzbDrone.Common.Disk;
+
+namespace NzbDrone.Core.Datastore.Converters
+{
+ public class OsPathConverter : IConverter
+ {
+ public Object FromDB(ConverterContext context)
+ {
+ if (context.DbValue == DBNull.Value)
+ {
+ return DBNull.Value;
+ }
+
+ var value = (String)context.DbValue;
+
+ return new OsPath(value);
+ }
+
+ public Object FromDB(ColumnMap map, Object dbValue)
+ {
+ return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
+ }
+
+ public Object ToDB(Object clrValue)
+ {
+ var value = (OsPath)clrValue;
+
+ return value.FullPath;
+ }
+
+ public Type DbType
+ {
+ get { return typeof(String); }
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs
index 875ff2420..f6f90609f 100644
--- a/src/NzbDrone.Core/Datastore/TableMapping.cs
+++ b/src/NzbDrone.Core/Datastore/TableMapping.cs
@@ -28,6 +28,7 @@ using NzbDrone.Core.SeriesStats;
using NzbDrone.Core.Tags;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Tv;
+using NzbDrone.Common.Disk;
namespace NzbDrone.Core.Datastore
{
@@ -114,6 +115,7 @@ namespace NzbDrone.Core.Datastore
MapRepository.Instance.RegisterTypeConverter(typeof(ParsedEpisodeInfo), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(ReleaseInfo), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(HashSet), new EmbeddedDocumentConverter());
+ MapRepository.Instance.RegisterTypeConverter(typeof(OsPath), new OsPathConverter());
}
private static void RegisterProviderSettingConverter()
diff --git a/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs b/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs
index 90cdb1703..aa20fafa0 100644
--- a/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs
+++ b/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs
@@ -115,8 +115,8 @@ namespace NzbDrone.Core.Download.Clients.Deluge
item.DownloadClient = Definition.Name;
item.DownloadTime = TimeSpan.FromSeconds(torrent.SecondsDownloading);
- var outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, torrent.DownloadPath);
- item.OutputPath = Path.Combine(outputPath, torrent.Name);
+ var outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(torrent.DownloadPath));
+ item.OutputPath = outputPath + torrent.Name;
item.RemainingSize = torrent.Size - torrent.BytesDownloaded;
item.RemainingTime = TimeSpan.FromSeconds(torrent.Eta);
item.TotalSize = torrent.Size;
@@ -172,11 +172,11 @@ namespace NzbDrone.Core.Download.Clients.Deluge
{
var config = _proxy.GetConfig(Settings);
- var destDir = config.GetValueOrDefault("download_location") as string;
+ var destDir = new OsPath(config.GetValueOrDefault("download_location") as string);
if (config.GetValueOrDefault("move_completed", false).ToString() == "True")
{
- destDir = config.GetValueOrDefault("move_completed_path") as string;
+ destDir = new OsPath(config.GetValueOrDefault("move_completed_path") as string);
}
var status = new DownloadClientStatus
@@ -184,9 +184,9 @@ namespace NzbDrone.Core.Download.Clients.Deluge
IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost"
};
- if (destDir != null)
+ if (!destDir.IsEmpty)
{
- status.OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, destDir) };
+ status.OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, destDir) };
}
return status;
diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs
index 75e57c55b..6330c417f 100644
--- a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs
+++ b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs
@@ -131,7 +131,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
historyItem.DownloadClientId = droneParameter == null ? item.Id.ToString() : droneParameter.Value.ToString();
historyItem.Title = item.Name;
historyItem.TotalSize = MakeInt64(item.FileSizeHi, item.FileSizeLo);
- historyItem.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, item.DestDir);
+ historyItem.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(item.DestDir));
historyItem.Category = item.Category;
historyItem.Message = String.Format("PAR Status: {0} - Unpack Status: {1} - Move Status: {2} - Script Status: {3} - Delete Status: {4} - Mark Status: {5}", item.ParStatus, item.UnpackStatus, item.MoveStatus, item.ScriptStatus, item.DeleteStatus, item.MarkStatus);
historyItem.Status = DownloadItemStatus.Completed;
@@ -215,7 +215,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
if (category != null)
{
- status.OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, category.DestDir) };
+ status.OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(category.DestDir)) };
}
return status;
@@ -321,10 +321,10 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
if (category != null)
{
- var localPath = Settings.TvCategoryLocalPath;
+ var localPath = new OsPath(Settings.TvCategoryLocalPath);
Settings.TvCategoryLocalPath = null;
- _remotePathMappingService.MigrateLocalCategoryPath(Definition.Id, Settings, Settings.Host, category.DestDir, localPath);
+ _remotePathMappingService.MigrateLocalCategoryPath(Definition.Id, Settings, Settings.Host, new OsPath(category.DestDir), localPath);
_logger.Info("Discovered Local Category Path for {0}, the setting was automatically moved to the Remote Path Mapping table.", Definition.Name);
}
diff --git a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs
index de8182094..11bd0fce1 100644
--- a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs
+++ b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs
@@ -89,7 +89,7 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
TotalSize = _diskProvider.GetFileSize(file),
- OutputPath = file
+ OutputPath = new OsPath(file)
};
if (_diskProvider.IsFileLocked(file))
diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs
index 150fe24cc..9b59132d4 100644
--- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs
+++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs
@@ -156,20 +156,20 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
historyItem.Status = DownloadItemStatus.Downloading;
}
- var outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, sabHistoryItem.Storage);
+ var outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(sabHistoryItem.Storage));
- if (!outputPath.IsNullOrWhiteSpace())
+ if (!outputPath.IsEmpty)
{
historyItem.OutputPath = outputPath;
- var parent = outputPath.GetParentPath();
- while (parent != null)
+ var parent = outputPath.Directory;
+ while (!parent.IsEmpty)
{
- if (Path.GetFileName(parent) == sabHistoryItem.Title)
+ if (parent.FileName == sabHistoryItem.Title)
{
historyItem.OutputPath = parent;
}
- parent = parent.GetParentPath();
+ parent = parent.Directory;
}
}
@@ -259,52 +259,21 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
protected IEnumerable GetCategories(SabnzbdConfig config)
{
- var completeDir = config.Misc.complete_dir.TrimEnd('\\', '/');
+ var completeDir = new OsPath(config.Misc.complete_dir);
- if (!completeDir.StartsWith("/") && !completeDir.StartsWith("\\") && !completeDir.Contains(':'))
+ if (!completeDir.IsRooted)
{
var queue = _proxy.GetQueue(0, 1, Settings);
+ var defaultRootFolder = new OsPath(queue.DefaultRootFolder);
- if (queue.DefaultRootFolder.StartsWith("/"))
- {
- completeDir = queue.DefaultRootFolder + "/" + completeDir;
- }
- else
- {
- completeDir = queue.DefaultRootFolder + "\\" + completeDir;
- }
+ completeDir = defaultRootFolder + completeDir;
}
foreach (var category in config.Categories)
{
- var relativeDir = category.Dir.TrimEnd('*');
+ var relativeDir = new OsPath(category.Dir.TrimEnd('*'));
- if (relativeDir.IsNullOrWhiteSpace())
- {
- category.FullPath = completeDir;
- }
- else if (completeDir.StartsWith("/"))
- { // Process remote Linux paths irrespective of our own OS.
- if (relativeDir.StartsWith("/"))
- {
- category.FullPath = relativeDir;
- }
- else
- {
- category.FullPath = completeDir + "/" + relativeDir;
- }
- }
- else
- { // Process remote Windows paths irrespective of our own OS.
- if (relativeDir.StartsWith("\\") || relativeDir.Contains(':'))
- {
- category.FullPath = relativeDir;
- }
- else
- {
- category.FullPath = completeDir + "\\" + relativeDir;
- }
- }
+ category.FullPath = completeDir + relativeDir;
yield return category;
}
@@ -329,7 +298,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
if (category != null)
{
- status.OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, category.FullPath) };
+ status.OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, category.FullPath) };
}
return status;
@@ -454,7 +423,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
if (category != null)
{
- var localPath = Settings.TvCategoryLocalPath;
+ var localPath = new OsPath(Settings.TvCategoryLocalPath);
Settings.TvCategoryLocalPath = null;
_remotePathMappingService.MigrateLocalCategoryPath(Definition.Id, Settings, Settings.Host, category.FullPath, localPath);
diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs
index 2068cd0a8..18f72d271 100644
--- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs
+++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using NzbDrone.Common.Disk;
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
@@ -30,6 +31,6 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
public String Script { get; set; }
public String Dir { get; set; }
- public String FullPath { get; set; }
+ public OsPath FullPath { get; set; }
}
}
diff --git a/src/NzbDrone.Core/Download/Clients/TorrentBlackhole/TorrentBlackhole.cs b/src/NzbDrone.Core/Download/Clients/TorrentBlackhole/TorrentBlackhole.cs
index a4f1b4f69..cb011f82f 100644
--- a/src/NzbDrone.Core/Download/Clients/TorrentBlackhole/TorrentBlackhole.cs
+++ b/src/NzbDrone.Core/Download/Clients/TorrentBlackhole/TorrentBlackhole.cs
@@ -78,7 +78,7 @@ namespace NzbDrone.Core.Download.Clients.TorrentBlackhole
TotalSize = files.Select(_diskProvider.GetFileSize).Sum(),
- OutputPath = folder
+ OutputPath = new OsPath(folder)
};
if (files.Any(_diskProvider.IsFileLocked))
@@ -108,7 +108,7 @@ namespace NzbDrone.Core.Download.Clients.TorrentBlackhole
TotalSize = _diskProvider.GetFileSize(videoFile),
- OutputPath = videoFile
+ OutputPath = new OsPath(videoFile)
};
if (_diskProvider.IsFileLocked(videoFile))
@@ -140,7 +140,7 @@ namespace NzbDrone.Core.Download.Clients.TorrentBlackhole
return new DownloadClientStatus
{
IsLocalhost = true,
- OutputRootFolders = new List { Settings.WatchFolder }
+ OutputRootFolders = new List { new OsPath(Settings.WatchFolder) }
};
}
diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs b/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs
index 4d040ebbc..ea52abe9d 100644
--- a/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs
+++ b/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs
@@ -94,19 +94,11 @@ namespace NzbDrone.Core.Download.Clients.Transmission
foreach (var torrent in torrents)
{
- var outputPath = torrent.DownloadDir;
-
- // Transmission always returns path with forward slashes, even on windows.
- if (outputPath.IsNotNullOrWhiteSpace() && (outputPath.StartsWith(@"\\") || outputPath.Contains(':')))
- {
- outputPath = outputPath.Replace('/', '\\');
- }
-
- outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, outputPath);
+ var outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(torrent.DownloadDir));
if (Settings.TvCategory.IsNotNullOrWhiteSpace())
{
- var directories = outputPath.Split('\\', '/');
+ var directories = outputPath.FullPath.Split('\\', '/');
if (!directories.Contains(String.Format(".{0}", Settings.TvCategory))) continue;
}
@@ -119,7 +111,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
item.DownloadTime = TimeSpan.FromSeconds(torrent.SecondsDownloading);
item.Message = torrent.ErrorString;
- item.OutputPath = Path.Combine(outputPath, torrent.Name);
+ item.OutputPath = outputPath + torrent.Name;
item.RemainingSize = torrent.LeftUntilDone;
item.RemainingTime = TimeSpan.FromSeconds(torrent.Eta);
item.TotalSize = torrent.TotalSize;
@@ -173,16 +165,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission
destDir = String.Format("{0}/.{1}", destDir, Settings.TvCategory);
}
- // Transmission always returns path with forward slashes, even on windows.
- if (destDir.StartsWith(@"\\") || destDir.Contains(':'))
- {
- destDir = destDir.Replace('/', '\\');
- }
-
return new DownloadClientStatus
{
IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost",
- OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, destDir) }
+ OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(destDir)) }
};
}
diff --git a/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs b/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs
index 02ddc5640..333c2598c 100644
--- a/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs
+++ b/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs
@@ -75,7 +75,7 @@ namespace NzbDrone.Core.Download.Clients.UsenetBlackhole
TotalSize = files.Select(_diskProvider.GetFileSize).Sum(),
- OutputPath = folder
+ OutputPath = new OsPath(folder)
};
if (files.Any(_diskProvider.IsFileLocked))
@@ -105,7 +105,7 @@ namespace NzbDrone.Core.Download.Clients.UsenetBlackhole
TotalSize = _diskProvider.GetFileSize(videoFile),
- OutputPath = videoFile
+ OutputPath = new OsPath(videoFile)
};
if (_diskProvider.IsFileLocked(videoFile))
@@ -137,7 +137,7 @@ namespace NzbDrone.Core.Download.Clients.UsenetBlackhole
return new DownloadClientStatus
{
IsLocalhost = true,
- OutputRootFolders = new List { Settings.WatchFolder }
+ OutputRootFolders = new List { new OsPath(Settings.WatchFolder) }
};
}
diff --git a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs
index 9e419e935..48ba7c935 100644
--- a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs
+++ b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs
@@ -102,19 +102,15 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
item.RemainingTime = TimeSpan.FromSeconds(torrent.Eta);
}
- var outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, torrent.RootDownloadPath);
+ var outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(torrent.RootDownloadPath));
- if (outputPath == null || Path.GetFileName(outputPath) == torrent.Name)
+ if (outputPath == null || outputPath.FileName == torrent.Name)
{
item.OutputPath = outputPath;
}
- else if (OsInfo.IsWindows && outputPath.EndsWith(":"))
- {
- item.OutputPath = Path.Combine(outputPath + Path.DirectorySeparatorChar, torrent.Name);
- }
else
{
- item.OutputPath = Path.Combine(outputPath, torrent.Name);
+ item.OutputPath = outputPath + torrent.Name;
}
if (torrent.Status.HasFlag(UTorrentTorrentStatus.Error))
@@ -162,20 +158,20 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
{
var config = _proxy.GetConfig(Settings);
- String destDir = null;
+ OsPath destDir = new OsPath(null);
if (config.GetValueOrDefault("dir_active_download_flag") == "true")
{
- destDir = config.GetValueOrDefault("dir_active_download");
+ destDir = new OsPath(config.GetValueOrDefault("dir_active_download"));
}
if (config.GetValueOrDefault("dir_completed_download_flag") == "true")
{
- destDir = config.GetValueOrDefault("dir_completed_download");
+ destDir = new OsPath(config.GetValueOrDefault("dir_completed_download"));
if (config.GetValueOrDefault("dir_add_label") == "true")
{
- destDir = Path.Combine(destDir, Settings.TvCategory);
+ destDir = destDir + Settings.TvCategory;
}
}
@@ -184,9 +180,9 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost"
};
- if (destDir != null)
+ if (!destDir.IsEmpty)
{
- status.OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, destDir) };
+ status.OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, destDir) };
}
return status;
diff --git a/src/NzbDrone.Core/Download/CompletedDownloadService.cs b/src/NzbDrone.Core/Download/CompletedDownloadService.cs
index 54cd15cd6..f9d6462bf 100644
--- a/src/NzbDrone.Core/Download/CompletedDownloadService.cs
+++ b/src/NzbDrone.Core/Download/CompletedDownloadService.cs
@@ -79,16 +79,16 @@ namespace NzbDrone.Core.Download
else
{
- var downloadedEpisodesFolder = _configService.DownloadedEpisodesFolder;
+ var downloadedEpisodesFolder = new OsPath(_configService.DownloadedEpisodesFolder);
var downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath;
- if (downloadItemOutputPath.IsNullOrWhiteSpace())
+ if (downloadItemOutputPath.IsEmpty)
{
UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Download doesn't contain intermediate path, ignoring download.");
return;
}
- if (!downloadedEpisodesFolder.IsNullOrWhiteSpace() && (downloadedEpisodesFolder.PathEquals(downloadItemOutputPath) || downloadedEpisodesFolder.IsParentPath(downloadItemOutputPath)))
+ if (!downloadedEpisodesFolder.IsEmpty && downloadedEpisodesFolder.Contains(downloadItemOutputPath))
{
UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Intermediate Download path inside drone factory, ignoring download.");
return;
@@ -113,7 +113,7 @@ namespace NzbDrone.Core.Download
public List Import(TrackedDownload trackedDownload, String overrideOutputPath = null)
{
var importResults = new List();
- var outputPath = overrideOutputPath ?? trackedDownload.DownloadItem.OutputPath;
+ var outputPath = overrideOutputPath ?? trackedDownload.DownloadItem.OutputPath.FullPath;
if (_diskProvider.FolderExists(outputPath))
{
@@ -216,16 +216,16 @@ namespace NzbDrone.Core.Download
_logger.Debug("[{0}] Removing completed download from history.", trackedDownload.DownloadItem.Title);
downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadClientId);
- if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath))
+ if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
{
_logger.Debug("Removing completed download directory: {0}",
trackedDownload.DownloadItem.OutputPath);
- _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath, true);
+ _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true);
}
- else if (_diskProvider.FileExists(trackedDownload.DownloadItem.OutputPath))
+ else if (_diskProvider.FileExists(trackedDownload.DownloadItem.OutputPath.FullPath))
{
_logger.Debug("Removing completed download file: {0}", trackedDownload.DownloadItem.OutputPath);
- _diskProvider.DeleteFile(trackedDownload.DownloadItem.OutputPath);
+ _diskProvider.DeleteFile(trackedDownload.DownloadItem.OutputPath.FullPath);
}
trackedDownload.State = TrackedDownloadState.Removed;
diff --git a/src/NzbDrone.Core/Download/DownloadClientItem.cs b/src/NzbDrone.Core/Download/DownloadClientItem.cs
index e82cde7bd..11c360994 100644
--- a/src/NzbDrone.Core/Download/DownloadClientItem.cs
+++ b/src/NzbDrone.Core/Download/DownloadClientItem.cs
@@ -1,4 +1,6 @@
using System;
+using NzbDrone.Common.Disk;
+using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download
{
@@ -14,7 +16,7 @@ namespace NzbDrone.Core.Download
public TimeSpan? DownloadTime { get; set; }
public TimeSpan? RemainingTime { get; set; }
- public String OutputPath { get; set; }
+ public OsPath OutputPath { get; set; }
public String Message { get; set; }
public DownloadItemStatus Status { get; set; }
diff --git a/src/NzbDrone.Core/Download/DownloadClientStatus.cs b/src/NzbDrone.Core/Download/DownloadClientStatus.cs
index ef4f71b38..6bb47c3a2 100644
--- a/src/NzbDrone.Core/Download/DownloadClientStatus.cs
+++ b/src/NzbDrone.Core/Download/DownloadClientStatus.cs
@@ -2,12 +2,13 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using NzbDrone.Common.Disk;
namespace NzbDrone.Core.Download
{
public class DownloadClientStatus
{
public Boolean IsLocalhost { get; set; }
- public List OutputRootFolders { get; set; }
+ public List OutputRootFolders { get; set; }
}
}
diff --git a/src/NzbDrone.Core/HealthCheck/Checks/ImportMechanismCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/ImportMechanismCheck.cs
index 6a0ca2f7a..eb21be154 100644
--- a/src/NzbDrone.Core/HealthCheck/Checks/ImportMechanismCheck.cs
+++ b/src/NzbDrone.Core/HealthCheck/Checks/ImportMechanismCheck.cs
@@ -25,12 +25,12 @@ namespace NzbDrone.Core.HealthCheck.Checks
public override HealthCheck Check()
{
- var droneFactoryFolder = _configService.DownloadedEpisodesFolder;
+ var droneFactoryFolder = new OsPath(_configService.DownloadedEpisodesFolder);
var downloadClients = _provideDownloadClient.GetDownloadClients().Select(v => new { downloadClient = v, status = v.GetStatus() }).ToList();
var downloadClientIsLocalHost = downloadClients.All(v => v.status.IsLocalhost);
- var downloadClientOutputInDroneFactory = !droneFactoryFolder.IsNullOrWhiteSpace()
- && downloadClients.Any(v => v.status.OutputRootFolders != null && v.status.OutputRootFolders.Contains(droneFactoryFolder, PathEqualityComparer.Instance));
+ var downloadClientOutputInDroneFactory = !droneFactoryFolder.IsEmpty
+ && downloadClients.Any(v => v.status.OutputRootFolders != null && v.status.OutputRootFolders.Any(droneFactoryFolder.Contains));
if (!_configService.IsDefined("EnableCompletedDownloadHandling"))
{
@@ -66,14 +66,14 @@ namespace NzbDrone.Core.HealthCheck.Checks
}
}
- if (!_configService.EnableCompletedDownloadHandling && droneFactoryFolder.IsNullOrWhiteSpace())
+ if (!_configService.EnableCompletedDownloadHandling && droneFactoryFolder.IsEmpty)
{
return new HealthCheck(GetType(), HealthCheckResult.Warning, "Enable Completed Download Handling or configure Drone factory");
}
- if (_configService.EnableCompletedDownloadHandling && !droneFactoryFolder.IsNullOrWhiteSpace())
+ if (_configService.EnableCompletedDownloadHandling && !droneFactoryFolder.IsEmpty)
{
- if (_downloadTrackingService.GetCompletedDownloads().Any(v => droneFactoryFolder.PathEquals(v.DownloadItem.OutputPath) || droneFactoryFolder.IsParentPath(v.DownloadItem.OutputPath)))
+ if (_downloadTrackingService.GetCompletedDownloads().Any(v => droneFactoryFolder.Contains(v.DownloadItem.OutputPath)))
{
return new HealthCheck(GetType(), HealthCheckResult.Warning, "Completed Download Handling conflict with Drone Factory (Conflicting History Item)", "Migrating-to-Completed-Download-Handling#conflicting-download-client-category");
}
diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj
index a1efd10cc..fc25420d8 100644
--- a/src/NzbDrone.Core/NzbDrone.Core.csproj
+++ b/src/NzbDrone.Core/NzbDrone.Core.csproj
@@ -148,6 +148,7 @@
+
diff --git a/src/NzbDrone.Core/RemotePathMappings/RemotePathMapping.cs b/src/NzbDrone.Core/RemotePathMappings/RemotePathMapping.cs
index 17b2c8692..e2cdc4c17 100644
--- a/src/NzbDrone.Core/RemotePathMappings/RemotePathMapping.cs
+++ b/src/NzbDrone.Core/RemotePathMappings/RemotePathMapping.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using NzbDrone.Common.Disk;
using NzbDrone.Core.Datastore;
diff --git a/src/NzbDrone.Core/RemotePathMappings/RemotePathMappingService.cs b/src/NzbDrone.Core/RemotePathMappings/RemotePathMappingService.cs
index dda99ae13..7a69a1b7f 100644
--- a/src/NzbDrone.Core/RemotePathMappings/RemotePathMappingService.cs
+++ b/src/NzbDrone.Core/RemotePathMappings/RemotePathMappingService.cs
@@ -22,11 +22,11 @@ namespace NzbDrone.Core.RemotePathMappings
RemotePathMapping Get(int id);
RemotePathMapping Update(RemotePathMapping mapping);
- String RemapRemoteToLocal(String host, String remotePath);
- String RemapLocalToRemote(String host, String localPath);
+ OsPath RemapRemoteToLocal(String host, OsPath remotePath);
+ OsPath RemapLocalToRemote(String host, OsPath localPath);
// TODO: Remove around January 2015. Used to migrate legacy Local Category Path settings.
- void MigrateLocalCategoryPath(Int32 downloadClientId, IProviderConfig newSettings, String host, String remotePath, String localPath);
+ void MigrateLocalCategoryPath(Int32 downloadClientId, IProviderConfig newSettings, String host, OsPath remotePath, OsPath localPath);
}
public class RemotePathMappingService : IRemotePathMappingService
@@ -60,8 +60,8 @@ namespace NzbDrone.Core.RemotePathMappings
public RemotePathMapping Add(RemotePathMapping mapping)
{
- mapping.LocalPath = CleanPath(mapping.LocalPath);
- mapping.RemotePath = CleanPath(mapping.RemotePath);
+ mapping.LocalPath = new OsPath(mapping.LocalPath).AsDirectory().FullPath;
+ mapping.RemotePath = new OsPath(mapping.RemotePath).AsDirectory().FullPath;
var all = All();
@@ -106,17 +106,20 @@ namespace NzbDrone.Core.RemotePathMappings
throw new ArgumentException("Invalid Host");
}
- if (mapping.RemotePath.IsNullOrWhiteSpace())
+ var remotePath = new OsPath(mapping.RemotePath);
+ var localPath = new OsPath(mapping.LocalPath);
+
+ if (remotePath.IsEmpty)
{
throw new ArgumentException("Invalid RemotePath");
}
- if (mapping.LocalPath.IsNullOrWhiteSpace() || !Path.IsPathRooted(mapping.LocalPath))
+ if (localPath.IsEmpty || !localPath.IsRooted)
{
throw new ArgumentException("Invalid LocalPath");
}
- if (!_diskProvider.FolderExists(mapping.LocalPath))
+ if (!_diskProvider.FolderExists(localPath.FullPath))
{
throw new DirectoryNotFoundException("Can't add mount point directory that doesn't exist.");
}
@@ -127,27 +130,18 @@ namespace NzbDrone.Core.RemotePathMappings
}
}
- public String RemapRemoteToLocal(String host, String remotePath)
+ public OsPath RemapRemoteToLocal(String host, OsPath remotePath)
{
- if (remotePath.IsNullOrWhiteSpace())
+ if (remotePath.IsEmpty)
{
return remotePath;
}
- var cleanRemotePath = CleanPath(remotePath);
-
foreach (var mapping in All())
{
- if (host == mapping.Host && cleanRemotePath.StartsWith(mapping.RemotePath))
+ if (host == mapping.Host && new OsPath(mapping.RemotePath).Contains(remotePath))
{
- var localPath = mapping.LocalPath + cleanRemotePath.Substring(mapping.RemotePath.Length);
-
- localPath = localPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
-
- if (!remotePath.EndsWith("/") && !remotePath.EndsWith("\\"))
- {
- localPath = localPath.TrimEnd('/', '\\');
- }
+ var localPath = new OsPath(mapping.LocalPath) + (remotePath - new OsPath(mapping.RemotePath));
return localPath;
}
@@ -156,29 +150,18 @@ namespace NzbDrone.Core.RemotePathMappings
return remotePath;
}
- public String RemapLocalToRemote(String host, String localPath)
+ public OsPath RemapLocalToRemote(String host, OsPath localPath)
{
- if (localPath.IsNullOrWhiteSpace())
+ if (localPath.IsEmpty)
{
return localPath;
}
- var cleanLocalPath = CleanPath(localPath);
-
foreach (var mapping in All())
{
- if (host != mapping.Host) continue;
-
- if (cleanLocalPath.StartsWith(mapping.LocalPath))
+ if (host == mapping.Host && new OsPath(mapping.LocalPath).Contains(localPath))
{
- var remotePath = mapping.RemotePath + cleanLocalPath.Substring(mapping.LocalPath.Length);
-
- remotePath = remotePath.Replace(Path.DirectorySeparatorChar, mapping.RemotePath.Contains('\\') ? '\\' : '/');
-
- if (!localPath.EndsWith("/") && !localPath.EndsWith("\\"))
- {
- remotePath = remotePath.TrimEnd('/', '\\');
- }
+ var remotePath = new OsPath(mapping.RemotePath) + (localPath - new OsPath(mapping.LocalPath));
return remotePath;
}
@@ -188,35 +171,20 @@ namespace NzbDrone.Core.RemotePathMappings
}
// TODO: Remove around January 2015. Used to migrate legacy Local Category Path settings.
- public void MigrateLocalCategoryPath(Int32 downloadClientId, IProviderConfig newSettings, String host, String remotePath, String localPath)
+ public void MigrateLocalCategoryPath(Int32 downloadClientId, IProviderConfig newSettings, String host, OsPath remotePath, OsPath localPath)
{
_logger.Debug("Migrating local category path for Host {0}/{1} to {2}", host, remotePath, localPath);
var existingMappings = All().Where(v => v.Host == host).ToList();
- remotePath = CleanPath(remotePath);
- localPath = CleanPath(localPath);
-
- if (!existingMappings.Any(v => v.LocalPath == localPath && v.RemotePath == remotePath))
+ if (!existingMappings.Any(v => new OsPath(v.LocalPath) == localPath && new OsPath(v.RemotePath) == remotePath))
{
- Add(new RemotePathMapping { Host = host, RemotePath = remotePath, LocalPath = localPath });
+ Add(new RemotePathMapping { Host = host, RemotePath = remotePath.FullPath, LocalPath = localPath.FullPath });
}
var downloadClient = _downloadClientRepository.Get(downloadClientId);
downloadClient.Settings = newSettings;
_downloadClientRepository.Update(downloadClient);
}
-
- private static String CleanPath(String path)
- {
- if (path.StartsWith(@"\\") || path.Contains(':'))
- {
- return path.TrimEnd('\\', '/').Replace('/', '\\') + "\\";
- }
- else
- {
- return path.TrimEnd('\\', '/').Replace('\\', '/') + "/";
- }
- }
}
}
\ No newline at end of file