New: It is now possible to use Completed Download Handling with remote download clients by specifying the local mount in settings.

This commit is contained in:
Taloth Saldono 2014-07-05 16:21:44 +02:00
parent 822de39a9e
commit 257cdf9382
8 changed files with 218 additions and 32 deletions

View File

@ -26,14 +26,6 @@ namespace NzbDrone.Common.Test.DiskProviderTests
Subject.GetAvailableSpace(Path.Combine(path, "invalidFolder")).Should().NotBe(0); Subject.GetAvailableSpace(Path.Combine(path, "invalidFolder")).Should().NotBe(0);
} }
[Test]
public void should_get_free_space_for_drive_that_doesnt_exist()
{
WindowsOnly();
Assert.Throws<DirectoryNotFoundException>(() => Subject.GetAvailableSpace("J:\\").Should().NotBe(0));
}
[Test] [Test]
public void should_be_able_to_check_space_on_ramdrive() public void should_be_able_to_check_space_on_ramdrive()
{ {

View File

@ -1,17 +1,18 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Collections.Generic;
using FizzWare.NBuilder; using FizzWare.NBuilder;
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common; using NzbDrone.Common;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients.Nzbget; using NzbDrone.Core.Download.Clients.Nzbget;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Test.Common;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using System.Collections.Generic;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
{ {
@ -28,7 +29,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
Subject.Definition = new DownloadClientDefinition(); Subject.Definition = new DownloadClientDefinition();
Subject.Definition.Settings = new NzbgetSettings Subject.Definition.Settings = new NzbgetSettings
{ {
Host = "192.168.5.55", Host = "127.0.0.1",
Port = 2222, Port = 2222,
Username = "admin", Username = "admin",
Password = "pass", Password = "pass",
@ -65,7 +66,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
FileSizeLo = 1000, FileSizeLo = 1000,
Category = "tv", Category = "tv",
Name = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE", Name = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE",
DestDir = "somedirectory", DestDir = "/remote/mount/tv/Droned.S01E01.Pilot.1080p.WEB-DL-DRONE",
Parameters = new List<NzbgetParameter> { new NzbgetParameter { Name = "drone", Value = "id" } }, Parameters = new List<NzbgetParameter> { new NzbgetParameter { Name = "drone", Value = "id" } },
ParStatus = "SUCCESS", ParStatus = "SUCCESS",
UnpackStatus = "NONE", UnpackStatus = "NONE",
@ -81,6 +82,19 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
{ {
DownloadRate = 7000000 DownloadRate = 7000000
}); });
var configItems = new Dictionary<String, String>();
configItems.Add("Category1.Name", "tv");
configItems.Add("Category1.DestDir", @"/remote/mount/tv");
Mocker.GetMock<INzbgetProxy>()
.Setup(v => v.GetConfig(It.IsAny<NzbgetSettings>()))
.Returns(configItems);
}
protected void WithMountPoint(String mountPath)
{
(Subject.Definition.Settings as NzbgetSettings).TvCategoryLocalPath = mountPath;
} }
protected void WithFailedDownload() protected void WithFailedDownload()
@ -223,5 +237,40 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
items.Should().BeEmpty(); items.Should().BeEmpty();
} }
[Test]
public void should_return_status_with_outputdir()
{
var result = Subject.GetStatus();
result.IsLocalhost.Should().BeTrue();
result.OutputRootFolders.Should().NotBeNull();
result.OutputRootFolders.First().Should().Be(@"/remote/mount/tv");
}
[Test]
public void should_return_status_with_mounted_outputdir()
{
WithMountPoint(@"O:\mymount".AsOsAgnostic());
var result = Subject.GetStatus();
result.IsLocalhost.Should().BeTrue();
result.OutputRootFolders.Should().NotBeNull();
result.OutputRootFolders.First().Should().Be(@"O:\mymount".AsOsAgnostic());
}
[Test]
public void should_remap_storage_if_mounted()
{
WithMountPoint(@"O:\mymount".AsOsAgnostic());
WithQueue(null);
WithHistory(_completed);
var result = Subject.GetItems().Single();
result.OutputPath.Should().Be(@"O:\mymount\Droned.S01E01.Pilot.1080p.WEB-DL-DRONE".AsOsAgnostic());
}
} }
} }

View File

@ -30,7 +30,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
Subject.Definition = new DownloadClientDefinition(); Subject.Definition = new DownloadClientDefinition();
Subject.Definition.Settings = new SabnzbdSettings Subject.Definition.Settings = new SabnzbdSettings
{ {
Host = "192.168.5.55", Host = "127.0.0.1",
Port = 2222, Port = 2222,
ApiKey = "5c770e3197e4fe763423ee7c392c25d1", ApiKey = "5c770e3197e4fe763423ee7c392c25d1",
Username = "admin", Username = "admin",
@ -82,10 +82,29 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
Category = "tv", Category = "tv",
Id = "sabnzbd_nzb12345", Id = "sabnzbd_nzb12345",
Title = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE", Title = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE",
Storage = "somedirectory" Storage = "/remote/mount/vv/Droned.S01E01.Pilot.1080p.WEB-DL-DRONE"
} }
} }
}; };
Mocker.GetMock<ISabnzbdProxy>()
.Setup(s => s.GetConfig(It.IsAny<SabnzbdSettings>()))
.Returns(new SabnzbdConfig
{
Misc = new SabnzbdConfigMisc
{
complete_dir = "/remote/mount/"
},
Categories = new List<SabnzbdCategory>
{
new SabnzbdCategory { Name = "tv", Dir = "vv" }
}
});
}
protected void WithMountPoint(String mountPath)
{
(Subject.Definition.Settings as SabnzbdSettings).TvCategoryLocalPath = mountPath;
} }
protected void WithFailedDownload() protected void WithFailedDownload()
@ -269,6 +288,19 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
result.OutputPath.Should().Be(@"C:\sorted\Droned.S01E01.Pilot.1080p.WEB-DL-DRONE".AsOsAgnostic()); result.OutputPath.Should().Be(@"C:\sorted\Droned.S01E01.Pilot.1080p.WEB-DL-DRONE".AsOsAgnostic());
} }
[Test]
public void should_remap_storage_if_mounted()
{
WithMountPoint(@"O:\mymount".AsOsAgnostic());
WithQueue(null);
WithHistory(_completed);
var result = Subject.GetItems().Single();
result.OutputPath.Should().Be(@"O:\mymount\Droned.S01E01.Pilot.1080p.WEB-DL-DRONE".AsOsAgnostic());
}
[Test] [Test]
public void should_not_blow_up_if_storage_is_drive_root() public void should_not_blow_up_if_storage_is_drive_root()
{ {
@ -281,5 +313,27 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
result.OutputPath.Should().Be(@"C:\".AsOsAgnostic()); result.OutputPath.Should().Be(@"C:\".AsOsAgnostic());
} }
[Test]
public void should_return_status_with_outputdir()
{
var result = Subject.GetStatus();
result.IsLocalhost.Should().BeTrue();
result.OutputRootFolders.Should().NotBeNull();
result.OutputRootFolders.First().Should().Be(@"/remote/mount/vv");
}
[Test]
public void should_return_status_with_mounted_outputdir()
{
WithMountPoint(@"O:\mymount".AsOsAgnostic());
var result = Subject.GetStatus();
result.IsLocalhost.Should().BeTrue();
result.OutputRootFolders.Should().NotBeNull();
result.OutputRootFolders.First().Should().Be(@"O:\mymount".AsOsAgnostic());
}
} }
} }

View File

@ -189,14 +189,36 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
public override IEnumerable<DownloadClientItem> GetItems() public override IEnumerable<DownloadClientItem> GetItems()
{ {
Dictionary<String,String> config = null;
NzbgetCategory category = null;
try
{
if (!Settings.TvCategoryLocalPath.IsNullOrWhiteSpace())
{
config = _proxy.GetConfig(Settings);
category = GetCategories(config).FirstOrDefault(v => v.Name == Settings.TvCategory);
}
}
catch (DownloadClientException ex)
{
_logger.ErrorException(ex.Message, ex);
yield break;
}
foreach (var downloadClientItem in GetQueue().Concat(GetHistory())) foreach (var downloadClientItem in GetQueue().Concat(GetHistory()))
{ {
if (downloadClientItem.Category != Settings.TvCategory) continue; if (downloadClientItem.Category == Settings.TvCategory)
{
if (category != null)
{
RemapStorage(downloadClientItem, category.DestDir, Settings.TvCategoryLocalPath);
}
downloadClientItem.RemoteEpisode = GetRemoteEpisode(downloadClientItem.Title); downloadClientItem.RemoteEpisode = GetRemoteEpisode(downloadClientItem.Title);
if (downloadClientItem.RemoteEpisode == null) continue; if (downloadClientItem.RemoteEpisode == null) continue;
yield return downloadClientItem; yield return downloadClientItem;
}
} }
} }
@ -223,7 +245,14 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
if (category != null) if (category != null)
{ {
status.OutputRootFolders = new List<string> { category.DestDir }; if (Settings.TvCategoryLocalPath.IsNullOrWhiteSpace())
{
status.OutputRootFolders = new List<String> { category.DestDir };
}
else
{
status.OutputRootFolders = new List<String> { Settings.TvCategoryLocalPath };
}
} }
return status; return status;

View File

@ -3,6 +3,7 @@ using FluentValidation;
using FluentValidation.Results; using FluentValidation.Results;
using NzbDrone.Core.Annotations; using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation.Paths;
namespace NzbDrone.Core.Download.Clients.Nzbget namespace NzbDrone.Core.Download.Clients.Nzbget
{ {
@ -14,6 +15,9 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
RuleFor(c => c.Port).GreaterThan(0); RuleFor(c => c.Port).GreaterThan(0);
RuleFor(c => c.Username).NotEmpty().When(c => !String.IsNullOrWhiteSpace(c.Password)); RuleFor(c => c.Username).NotEmpty().When(c => !String.IsNullOrWhiteSpace(c.Password));
RuleFor(c => c.Password).NotEmpty().When(c => !String.IsNullOrWhiteSpace(c.Username)); RuleFor(c => c.Password).NotEmpty().When(c => !String.IsNullOrWhiteSpace(c.Username));
RuleFor(c => c.TvCategory).NotEmpty().When(c => !String.IsNullOrWhiteSpace(c.TvCategoryLocalPath));
RuleFor(c => c.TvCategoryLocalPath).IsValidPath().When(c => !String.IsNullOrWhiteSpace(c.TvCategoryLocalPath));
} }
} }
@ -45,13 +49,16 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox)] [FieldDefinition(4, Label = "Category", Type = FieldType.Textbox)]
public String TvCategory { get; set; } public String TvCategory { get; set; }
[FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] [FieldDefinition(5, Label = "Category Local Path", Type = FieldType.Textbox, Advanced = true, HelpText = "Local path to the category output dir. Useful if Nzbget runs on another computer.")]
public String TvCategoryLocalPath { get; set; }
[FieldDefinition(6, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
public Int32 RecentTvPriority { get; set; } public Int32 RecentTvPriority { get; set; }
[FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] [FieldDefinition(7, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
public Int32 OlderTvPriority { get; set; } public Int32 OlderTvPriority { get; set; }
[FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox)] [FieldDefinition(8, Label = "Use SSL", Type = FieldType.Checkbox)]
public Boolean UseSsl { get; set; } public Boolean UseSsl { get; set; }
public ValidationResult Validate() public ValidationResult Validate()

View File

@ -182,14 +182,36 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
public override IEnumerable<DownloadClientItem> GetItems() public override IEnumerable<DownloadClientItem> GetItems()
{ {
SabnzbdConfig config = null;
SabnzbdCategory category = null;
try
{
if (!Settings.TvCategoryLocalPath.IsNullOrWhiteSpace())
{
config = _proxy.GetConfig(Settings);
category = GetCategories(config).FirstOrDefault(v => v.Name == Settings.TvCategory);
}
}
catch (DownloadClientException ex)
{
_logger.ErrorException(ex.Message, ex);
yield break;
}
foreach (var downloadClientItem in GetQueue().Concat(GetHistory())) foreach (var downloadClientItem in GetQueue().Concat(GetHistory()))
{ {
if (downloadClientItem.Category != Settings.TvCategory) continue; if (downloadClientItem.Category == Settings.TvCategory)
{
if (category != null)
{
RemapStorage(downloadClientItem, category.FullPath, Settings.TvCategoryLocalPath);
}
downloadClientItem.RemoteEpisode = GetRemoteEpisode(downloadClientItem.Title); downloadClientItem.RemoteEpisode = GetRemoteEpisode(downloadClientItem.Title);
if (downloadClientItem.RemoteEpisode == null) continue; if (downloadClientItem.RemoteEpisode == null) continue;
yield return downloadClientItem; yield return downloadClientItem;
}
} }
} }
@ -268,7 +290,14 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
if (category != null) if (category != null)
{ {
status.OutputRootFolders = new List<String> { category.FullPath }; if (Settings.TvCategoryLocalPath.IsNullOrWhiteSpace())
{
status.OutputRootFolders = new List<String> { category.FullPath };
}
else
{
status.OutputRootFolders = new List<String> { Settings.TvCategoryLocalPath };
}
} }
return status; return status;

View File

@ -3,6 +3,7 @@ using FluentValidation;
using FluentValidation.Results; using FluentValidation.Results;
using NzbDrone.Core.Annotations; using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation.Paths;
namespace NzbDrone.Core.Download.Clients.Sabnzbd namespace NzbDrone.Core.Download.Clients.Sabnzbd
{ {
@ -21,10 +22,12 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
.WithMessage("Username is required when API key is not configured") .WithMessage("Username is required when API key is not configured")
.When(c => String.IsNullOrWhiteSpace(c.ApiKey)); .When(c => String.IsNullOrWhiteSpace(c.ApiKey));
RuleFor(c => c.Password).NotEmpty() RuleFor(c => c.Password).NotEmpty()
.WithMessage("Password is required when API key is not configured") .WithMessage("Password is required when API key is not configured")
.When(c => String.IsNullOrWhiteSpace(c.ApiKey)); .When(c => String.IsNullOrWhiteSpace(c.ApiKey));
RuleFor(c => c.TvCategory).NotEmpty().When(c => !String.IsNullOrWhiteSpace(c.TvCategoryLocalPath));
RuleFor(c => c.TvCategoryLocalPath).IsValidPath().When(c => !String.IsNullOrWhiteSpace(c.TvCategoryLocalPath));
} }
} }
@ -59,13 +62,16 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox)] [FieldDefinition(5, Label = "Category", Type = FieldType.Textbox)]
public String TvCategory { get; set; } public String TvCategory { get; set; }
[FieldDefinition(6, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] [FieldDefinition(6, Label = "Category Local Path", Type = FieldType.Textbox, Advanced = true, HelpText = "Local path to the category output dir. Useful if Sabnzbd runs on another computer.")]
public String TvCategoryLocalPath { get; set; }
[FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
public Int32 RecentTvPriority { get; set; } public Int32 RecentTvPriority { get; set; }
[FieldDefinition(7, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] [FieldDefinition(8, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
public Int32 OlderTvPriority { get; set; } public Int32 OlderTvPriority { get; set; }
[FieldDefinition(8, Label = "Use SSL", Type = FieldType.Checkbox)] [FieldDefinition(9, Label = "Use SSL", Type = FieldType.Checkbox)]
public Boolean UseSsl { get; set; } public Boolean UseSsl { get; set; }
public ValidationResult Validate() public ValidationResult Validate()

View File

@ -1,6 +1,8 @@
using System; using System;
using System.IO;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using FluentValidation.Results; using NzbDrone.Common;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
@ -8,6 +10,7 @@ using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NLog; using NLog;
using FluentValidation.Results;
namespace NzbDrone.Core.Download namespace NzbDrone.Core.Download
{ {
@ -81,6 +84,23 @@ namespace NzbDrone.Core.Download
return remoteEpisode; return remoteEpisode;
} }
protected void RemapStorage(DownloadClientItem downloadClientItem, String remotePath, String localPath)
{
if (downloadClientItem.OutputPath.IsNullOrWhiteSpace() || localPath.IsNullOrWhiteSpace())
{
return;
}
remotePath = remotePath.TrimEnd('/', '\\');
localPath = localPath.TrimEnd('/', '\\');
if (downloadClientItem.OutputPath.StartsWith(remotePath))
{
downloadClientItem.OutputPath = localPath + downloadClientItem.OutputPath.Substring(remotePath.Length);
downloadClientItem.OutputPath = downloadClientItem.OutputPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
}
}
protected ValidationFailure TestFolder(String folder, String propertyName, Boolean mustBeWritable = true) protected ValidationFailure TestFolder(String folder, String propertyName, Boolean mustBeWritable = true)
{ {
if (!_diskProvider.FolderExists(folder)) if (!_diskProvider.FolderExists(folder))