New: Added global Remote Path mapping table to replace individual Local Category Path settings.
This commit is contained in:
parent
8281063698
commit
525f1aa9dd
|
@ -96,6 +96,8 @@
|
||||||
<Compile Include="ClientSchema\SelectOption.cs" />
|
<Compile Include="ClientSchema\SelectOption.cs" />
|
||||||
<Compile Include="Commands\CommandModule.cs" />
|
<Compile Include="Commands\CommandModule.cs" />
|
||||||
<Compile Include="Commands\CommandResource.cs" />
|
<Compile Include="Commands\CommandResource.cs" />
|
||||||
|
<Compile Include="RemotePathMappings\RemotePathMappingModule.cs" />
|
||||||
|
<Compile Include="RemotePathMappings\RemotePathMappingResource.cs" />
|
||||||
<Compile Include="Config\UiConfigModule.cs" />
|
<Compile Include="Config\UiConfigModule.cs" />
|
||||||
<Compile Include="Config\UiConfigResource.cs" />
|
<Compile Include="Config\UiConfigResource.cs" />
|
||||||
<Compile Include="Config\DownloadClientConfigModule.cs" />
|
<Compile Include="Config\DownloadClientConfigModule.cs" />
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Api.Mapping;
|
||||||
|
using NzbDrone.Common;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.RemotePathMappings;
|
||||||
|
using NzbDrone.Core.Validation.Paths;
|
||||||
|
using Omu.ValueInjecter;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Config
|
||||||
|
{
|
||||||
|
public class RemotePathMappingModule : NzbDroneRestModule<RemotePathMappingResource>
|
||||||
|
{
|
||||||
|
private readonly IRemotePathMappingService _remotePathMappingService;
|
||||||
|
|
||||||
|
public RemotePathMappingModule(IConfigService configService, IRemotePathMappingService remotePathMappingService, PathExistsValidator pathExistsValidator)
|
||||||
|
{
|
||||||
|
_remotePathMappingService = remotePathMappingService;
|
||||||
|
|
||||||
|
GetResourceAll = GetMappings;
|
||||||
|
GetResourceById = GetMappingById;
|
||||||
|
CreateResource = CreateMapping;
|
||||||
|
DeleteResource = DeleteMapping;
|
||||||
|
UpdateResource = UpdateMapping;
|
||||||
|
|
||||||
|
SharedValidator.RuleFor(c => c.Host)
|
||||||
|
.NotEmpty();
|
||||||
|
|
||||||
|
// We cannot use IsValidPath here, because it's a remote path, possibly other OS.
|
||||||
|
SharedValidator.RuleFor(c => c.RemotePath)
|
||||||
|
.NotEmpty();
|
||||||
|
|
||||||
|
SharedValidator.RuleFor(c => c.LocalPath)
|
||||||
|
.Cascade(CascadeMode.StopOnFirstFailure)
|
||||||
|
.IsValidPath()
|
||||||
|
.SetValidator(pathExistsValidator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RemotePathMappingResource GetMappingById(int id)
|
||||||
|
{
|
||||||
|
return _remotePathMappingService.Get(id).InjectTo<RemotePathMappingResource>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int CreateMapping(RemotePathMappingResource rootFolderResource)
|
||||||
|
{
|
||||||
|
return GetNewId<RemotePathMapping>(_remotePathMappingService.Add, rootFolderResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RemotePathMappingResource> GetMappings()
|
||||||
|
{
|
||||||
|
return ToListResource(_remotePathMappingService.All);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteMapping(int id)
|
||||||
|
{
|
||||||
|
_remotePathMappingService.Remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateMapping(RemotePathMappingResource resource)
|
||||||
|
{
|
||||||
|
var mapping = _remotePathMappingService.Get(resource.Id);
|
||||||
|
|
||||||
|
mapping.InjectFrom(resource);
|
||||||
|
|
||||||
|
_remotePathMappingService.Update(mapping);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
using System;
|
||||||
|
using NzbDrone.Api.REST;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Config
|
||||||
|
{
|
||||||
|
public class RemotePathMappingResource : RestResource
|
||||||
|
{
|
||||||
|
public String Host { get; set; }
|
||||||
|
public String RemotePath { get; set; }
|
||||||
|
public String LocalPath { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,8 @@ using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.RemotePathMappings;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
||||||
{
|
{
|
||||||
|
@ -29,13 +31,15 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
||||||
|
|
||||||
Mocker.GetMock<IParsingService>()
|
Mocker.GetMock<IParsingService>()
|
||||||
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), null))
|
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), null))
|
||||||
.Returns(CreateRemoteEpisode());
|
.Returns(() => CreateRemoteEpisode());
|
||||||
|
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>()
|
Mocker.GetMock<IHttpClient>()
|
||||||
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
||||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new Byte[0]));
|
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new Byte[0]));
|
||||||
|
|
||||||
|
Mocker.GetMock<IRemotePathMappingService>()
|
||||||
|
.Setup(v => v.RemapRemoteToLocal(It.IsAny<String>(), It.IsAny<String>()))
|
||||||
|
.Returns<String, String>((h,r) => r);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual RemoteEpisode CreateRemoteEpisode()
|
protected virtual RemoteEpisode CreateRemoteEpisode()
|
||||||
|
|
|
@ -13,6 +13,7 @@ using NzbDrone.Core.Download.Clients.Nzbget;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Core.RemotePathMappings;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
|
namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
|
||||||
{
|
{
|
||||||
|
@ -92,11 +93,6 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
|
||||||
.Returns(configItems);
|
.Returns(configItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void GivenMountPoint(String mountPath)
|
|
||||||
{
|
|
||||||
(Subject.Definition.Settings as NzbgetSettings).TvCategoryLocalPath = mountPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void GivenFailedDownload()
|
protected void GivenFailedDownload()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<INzbgetProxy>()
|
Mocker.GetMock<INzbgetProxy>()
|
||||||
|
@ -251,7 +247,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_status_with_mounted_outputdir()
|
public void should_return_status_with_mounted_outputdir()
|
||||||
{
|
{
|
||||||
GivenMountPoint(@"O:\mymount".AsOsAgnostic());
|
Mocker.GetMock<IRemotePathMappingService>()
|
||||||
|
.Setup(v => v.RemapRemoteToLocal("127.0.0.1", "/remote/mount/tv"))
|
||||||
|
.Returns(@"O:\mymount".AsOsAgnostic());
|
||||||
|
|
||||||
var result = Subject.GetStatus();
|
var result = Subject.GetStatus();
|
||||||
|
|
||||||
|
@ -263,7 +261,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
|
||||||
[Test]
|
[Test]
|
||||||
public void should_remap_storage_if_mounted()
|
public void should_remap_storage_if_mounted()
|
||||||
{
|
{
|
||||||
GivenMountPoint(@"O:\mymount".AsOsAgnostic());
|
Mocker.GetMock<IRemotePathMappingService>()
|
||||||
|
.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());
|
||||||
|
|
||||||
GivenQueue(null);
|
GivenQueue(null);
|
||||||
GivenHistory(_completed);
|
GivenHistory(_completed);
|
||||||
|
|
|
@ -14,6 +14,7 @@ using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
|
using NzbDrone.Core.RemotePathMappings;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||||
{
|
{
|
||||||
|
@ -106,11 +107,6 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||||
.Returns(_config);
|
.Returns(_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void GivenMountPoint(String mountPath)
|
|
||||||
{
|
|
||||||
(Subject.Definition.Settings as SabnzbdSettings).TvCategoryLocalPath = mountPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void GivenFailedDownload()
|
protected void GivenFailedDownload()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<ISabnzbdProxy>()
|
Mocker.GetMock<ISabnzbdProxy>()
|
||||||
|
@ -303,7 +299,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||||
[Test]
|
[Test]
|
||||||
public void should_remap_storage_if_mounted()
|
public void should_remap_storage_if_mounted()
|
||||||
{
|
{
|
||||||
GivenMountPoint(@"O:\mymount".AsOsAgnostic());
|
Mocker.GetMock<IRemotePathMappingService>()
|
||||||
|
.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());
|
||||||
|
|
||||||
GivenQueue(null);
|
GivenQueue(null);
|
||||||
GivenHistory(_completed);
|
GivenHistory(_completed);
|
||||||
|
@ -361,7 +359,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_status_with_mounted_outputdir()
|
public void should_return_status_with_mounted_outputdir()
|
||||||
{
|
{
|
||||||
GivenMountPoint(@"O:\mymount".AsOsAgnostic());
|
Mocker.GetMock<IRemotePathMappingService>()
|
||||||
|
.Setup(v => v.RemapRemoteToLocal("127.0.0.1", "/remote/mount/vv"))
|
||||||
|
.Returns(@"O:\mymount".AsOsAgnostic());
|
||||||
|
|
||||||
GivenQueue(null);
|
GivenQueue(null);
|
||||||
|
|
||||||
|
|
|
@ -197,6 +197,7 @@
|
||||||
<Compile Include="MediaFiles\EpisodeImport\Specifications\UpgradeSpecificationFixture.cs" />
|
<Compile Include="MediaFiles\EpisodeImport\Specifications\UpgradeSpecificationFixture.cs" />
|
||||||
<Compile Include="MediaFiles\ImportApprovedEpisodesFixture.cs" />
|
<Compile Include="MediaFiles\ImportApprovedEpisodesFixture.cs" />
|
||||||
<Compile Include="MediaFiles\MediaFileRepositoryFixture.cs" />
|
<Compile Include="MediaFiles\MediaFileRepositoryFixture.cs" />
|
||||||
|
<Compile Include="RemotePathMappingsTests\RemotePathMappingServiceFixture.cs" />
|
||||||
<Compile Include="OrganizerTests\CleanFixture.cs" />
|
<Compile Include="OrganizerTests\CleanFixture.cs" />
|
||||||
<Compile Include="MediaFiles\MediaFileServiceTests\FilterFixture.cs" />
|
<Compile Include="MediaFiles\MediaFileServiceTests\FilterFixture.cs" />
|
||||||
<Compile Include="MediaFiles\MediaFileTableCleanupServiceFixture.cs" />
|
<Compile Include="MediaFiles\MediaFileTableCleanupServiceFixture.cs" />
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Core.RemotePathMappings;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.RemotePathMappingsTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class RemotePathMappingServiceFixture : CoreTest<RemotePathMappingService>
|
||||||
|
{
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IDiskProvider>()
|
||||||
|
.Setup(m => m.FolderExists(It.IsAny<string>()))
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
Mocker.GetMock<IRemotePathMappingRepository>()
|
||||||
|
.Setup(s => s.All())
|
||||||
|
.Returns(new List<RemotePathMapping>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenMapping()
|
||||||
|
{
|
||||||
|
var mappings = Builder<RemotePathMapping>.CreateListOfSize(1)
|
||||||
|
.All()
|
||||||
|
.With(v => v.Host = "my-server.localdomain")
|
||||||
|
.With(v => v.RemotePath = "/mnt/storage/")
|
||||||
|
.With(v => v.LocalPath = @"D:\mountedstorage\".AsOsAgnostic())
|
||||||
|
.BuildListOfNew();
|
||||||
|
|
||||||
|
Mocker.GetMock<IRemotePathMappingRepository>()
|
||||||
|
.Setup(s => s.All())
|
||||||
|
.Returns(mappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WithNonExistingFolder()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IDiskProvider>()
|
||||||
|
.Setup(m => m.FolderExists(It.IsAny<string>()))
|
||||||
|
.Returns(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("my-first-server.localdomain", "/mnt/storage", @"D:\storage1")]
|
||||||
|
[TestCase("my-server.localdomain", "/mnt/storage2", @"D:\storage2")]
|
||||||
|
public void should_be_able_to_add_new_mapping(String host, String remotePath, String localPath)
|
||||||
|
{
|
||||||
|
GivenMapping();
|
||||||
|
|
||||||
|
localPath = localPath.AsOsAgnostic();
|
||||||
|
|
||||||
|
var mapping = new RemotePathMapping { Host = host, RemotePath = remotePath, LocalPath = localPath };
|
||||||
|
|
||||||
|
Subject.Add(mapping);
|
||||||
|
|
||||||
|
Mocker.GetMock<IRemotePathMappingRepository>().Verify(c => c.Insert(mapping), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_be_able_to_remove_mapping()
|
||||||
|
{
|
||||||
|
Subject.Remove(1);
|
||||||
|
Mocker.GetMock<IRemotePathMappingRepository>().Verify(c => c.Delete(1), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("my-server.localdomain", "/mnt/storage", @"D:\mountedstorage")]
|
||||||
|
[TestCase("my-server.localdomain", "/mnt/storage", @"D:\mountedstorage2")]
|
||||||
|
public void adding_duplicated_mapping_should_throw(String host, String remotePath, String localPath)
|
||||||
|
{
|
||||||
|
localPath = localPath.AsOsAgnostic();
|
||||||
|
|
||||||
|
GivenMapping();
|
||||||
|
|
||||||
|
var mapping = new RemotePathMapping { Host = host, RemotePath = remotePath, LocalPath = localPath };
|
||||||
|
|
||||||
|
Assert.Throws<InvalidOperationException>(() => Subject.Add(mapping));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("my-server.localdomain", "/mnt/storage/downloads/tv", @"D:\mountedstorage\downloads\tv")]
|
||||||
|
[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_remote_to_local(String host, String remotePath, String expectedLocalPath)
|
||||||
|
{
|
||||||
|
expectedLocalPath = expectedLocalPath.AsOsAgnostic();
|
||||||
|
|
||||||
|
GivenMapping();
|
||||||
|
|
||||||
|
var result = Subject.RemapRemoteToLocal(host, 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-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)
|
||||||
|
{
|
||||||
|
localPath = localPath.AsOsAgnostic();
|
||||||
|
|
||||||
|
GivenMapping();
|
||||||
|
|
||||||
|
var result = Subject.RemapLocalToRemote(host, localPath);
|
||||||
|
|
||||||
|
result.Should().Be(expectedRemotePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(63)]
|
||||||
|
public class add_remotepathmappings : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Create.TableForModel("RemotePathMappings")
|
||||||
|
.WithColumn("Host").AsString()
|
||||||
|
.WithColumn("RemotePath").AsString()
|
||||||
|
.WithColumn("LocalPath").AsString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ using NzbDrone.Core.Jobs;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Metadata;
|
using NzbDrone.Core.Metadata;
|
||||||
using NzbDrone.Core.Metadata.Files;
|
using NzbDrone.Core.Metadata.Files;
|
||||||
|
using NzbDrone.Core.RemotePathMappings;
|
||||||
using NzbDrone.Core.Notifications;
|
using NzbDrone.Core.Notifications;
|
||||||
using NzbDrone.Core.Organizer;
|
using NzbDrone.Core.Organizer;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
@ -87,6 +88,8 @@ namespace NzbDrone.Core.Datastore
|
||||||
|
|
||||||
Mapper.Entity<PendingRelease>().RegisterModel("PendingReleases")
|
Mapper.Entity<PendingRelease>().RegisterModel("PendingReleases")
|
||||||
.Ignore(e => e.RemoteEpisode);
|
.Ignore(e => e.RemoteEpisode);
|
||||||
|
|
||||||
|
Mapper.Entity<RemotePathMapping>().RegisterModel("RemotePathMappings");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RegisterMappers()
|
private static void RegisterMappers()
|
||||||
|
|
|
@ -12,6 +12,7 @@ using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Validation;
|
using NzbDrone.Core.Validation;
|
||||||
|
using NzbDrone.Core.RemotePathMappings;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download.Clients.Nzbget
|
namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||||
{
|
{
|
||||||
|
@ -24,8 +25,9 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||||
IConfigService configService,
|
IConfigService configService,
|
||||||
IDiskProvider diskProvider,
|
IDiskProvider diskProvider,
|
||||||
IParsingService parsingService,
|
IParsingService parsingService,
|
||||||
|
IRemotePathMappingService remotePathMappingService,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
: base(httpClient, configService, diskProvider, parsingService, logger)
|
: base(httpClient, configService, diskProvider, parsingService, remotePathMappingService, logger)
|
||||||
{
|
{
|
||||||
_proxy = proxy;
|
_proxy = proxy;
|
||||||
}
|
}
|
||||||
|
@ -145,7 +147,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||||
historyItem.DownloadClientId = droneParameter == null ? item.Id.ToString() : droneParameter.Value.ToString();
|
historyItem.DownloadClientId = droneParameter == null ? item.Id.ToString() : droneParameter.Value.ToString();
|
||||||
historyItem.Title = item.Name;
|
historyItem.Title = item.Name;
|
||||||
historyItem.TotalSize = MakeInt64(item.FileSizeHi, item.FileSizeLo);
|
historyItem.TotalSize = MakeInt64(item.FileSizeHi, item.FileSizeLo);
|
||||||
historyItem.OutputPath = item.DestDir;
|
historyItem.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, item.DestDir);
|
||||||
historyItem.Category = item.Category;
|
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.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;
|
historyItem.Status = DownloadItemStatus.Completed;
|
||||||
|
@ -172,31 +174,12 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||||
|
|
||||||
public override IEnumerable<DownloadClientItem> GetItems()
|
public override IEnumerable<DownloadClientItem> GetItems()
|
||||||
{
|
{
|
||||||
Dictionary<String, String> config = null;
|
MigrateLocalCategoryPath();
|
||||||
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)
|
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;
|
||||||
|
|
||||||
|
@ -230,14 +213,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||||
|
|
||||||
if (category != null)
|
if (category != null)
|
||||||
{
|
{
|
||||||
if (Settings.TvCategoryLocalPath.IsNullOrWhiteSpace())
|
status.OutputRootFolders = new List<String> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, category.DestDir) };
|
||||||
{
|
|
||||||
status.OutputRootFolders = new List<String> { category.DestDir };
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
status.OutputRootFolders = new List<String> { Settings.TvCategoryLocalPath };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
|
@ -279,11 +255,6 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||||
{
|
{
|
||||||
failures.AddIfNotNull(TestConnection());
|
failures.AddIfNotNull(TestConnection());
|
||||||
failures.AddIfNotNull(TestCategory());
|
failures.AddIfNotNull(TestCategory());
|
||||||
|
|
||||||
if (!Settings.TvCategoryLocalPath.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
failures.AddIfNotNull(TestFolder(Settings.TvCategoryLocalPath, "TvCategoryLocalPath"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ValidationFailure TestConnection()
|
private ValidationFailure TestConnection()
|
||||||
|
@ -333,5 +304,35 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove around January 2015, this code moves the settings to the RemotePathMappingService.
|
||||||
|
private void MigrateLocalCategoryPath()
|
||||||
|
{
|
||||||
|
if (!Settings.TvCategoryLocalPath.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.Debug("Has legacy TvCategoryLocalPath, trying to migrate to RemotePathMapping list.");
|
||||||
|
|
||||||
|
var config = _proxy.GetConfig(Settings);
|
||||||
|
var category = GetCategories(config).FirstOrDefault(v => v.Name == Settings.TvCategory);
|
||||||
|
|
||||||
|
if (category != null)
|
||||||
|
{
|
||||||
|
var localPath = Settings.TvCategoryLocalPath;
|
||||||
|
Settings.TvCategoryLocalPath = null;
|
||||||
|
|
||||||
|
_remotePathMappingService.MigrateLocalCategoryPath(Definition.Id, Settings, Settings.Host, category.DestDir, localPath);
|
||||||
|
|
||||||
|
_logger.Info("Discovered Local Category Path for {0}, the setting was automatically moved to the Remote Path Mapping table.", Definition.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (DownloadClientException ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Unable to migrate local category path", ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -49,16 +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 = "Category Local Path", Type = FieldType.Textbox, Advanced = true, HelpText = "Local path to the category output dir. Useful if Nzbget runs on another computer.")]
|
// TODO: Remove around January 2015, this setting was superceded by the RemotePathMappingService, but has to remain for a while to properly migrate.
|
||||||
public String TvCategoryLocalPath { get; set; }
|
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")]
|
[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")]
|
||||||
public Int32 RecentTvPriority { get; set; }
|
public Int32 RecentTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(7, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
|
[FieldDefinition(6, 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(8, Label = "Use SSL", Type = FieldType.Checkbox)]
|
[FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox)]
|
||||||
public Boolean UseSsl { get; set; }
|
public Boolean UseSsl { get; set; }
|
||||||
|
|
||||||
public ValidationResult Validate()
|
public ValidationResult Validate()
|
||||||
|
|
|
@ -8,6 +8,7 @@ using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
|
using NzbDrone.Core.RemotePathMappings;
|
||||||
using NzbDrone.Core.Organizer;
|
using NzbDrone.Core.Organizer;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
@ -22,8 +23,9 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
|
||||||
IConfigService configService,
|
IConfigService configService,
|
||||||
IDiskProvider diskProvider,
|
IDiskProvider diskProvider,
|
||||||
IParsingService parsingService,
|
IParsingService parsingService,
|
||||||
|
IRemotePathMappingService remotePathMappingService,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
: base(configService, diskProvider, parsingService, logger)
|
: base(configService, diskProvider, parsingService, remotePathMappingService, logger)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Validation;
|
using NzbDrone.Core.Validation;
|
||||||
|
using NzbDrone.Core.RemotePathMappings;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
{
|
{
|
||||||
|
@ -25,8 +26,9 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
IConfigService configService,
|
IConfigService configService,
|
||||||
IDiskProvider diskProvider,
|
IDiskProvider diskProvider,
|
||||||
IParsingService parsingService,
|
IParsingService parsingService,
|
||||||
|
IRemotePathMappingService remotePathMappingService,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
: base(httpClient, configService, diskProvider, parsingService, logger)
|
: base(httpClient, configService, diskProvider, parsingService, remotePathMappingService, logger)
|
||||||
{
|
{
|
||||||
_proxy = proxy;
|
_proxy = proxy;
|
||||||
}
|
}
|
||||||
|
@ -147,11 +149,13 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
historyItem.Status = DownloadItemStatus.Downloading;
|
historyItem.Status = DownloadItemStatus.Downloading;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sabHistoryItem.Storage.IsNullOrWhiteSpace())
|
var outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, sabHistoryItem.Storage);
|
||||||
{
|
|
||||||
historyItem.OutputPath = sabHistoryItem.Storage;
|
|
||||||
|
|
||||||
var parent = sabHistoryItem.Storage.GetParentPath();
|
if (!outputPath.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
historyItem.OutputPath = outputPath;
|
||||||
|
|
||||||
|
var parent = outputPath.GetParentPath();
|
||||||
while (parent != null)
|
while (parent != null)
|
||||||
{
|
{
|
||||||
if (Path.GetFileName(parent) == sabHistoryItem.Title)
|
if (Path.GetFileName(parent) == sabHistoryItem.Title)
|
||||||
|
@ -170,31 +174,12 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
|
|
||||||
public override IEnumerable<DownloadClientItem> GetItems()
|
public override IEnumerable<DownloadClientItem> GetItems()
|
||||||
{
|
{
|
||||||
SabnzbdConfig config = null;
|
MigrateLocalCategoryPath();
|
||||||
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)
|
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;
|
||||||
|
|
||||||
|
@ -323,14 +308,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
|
|
||||||
if (category != null)
|
if (category != null)
|
||||||
{
|
{
|
||||||
if (Settings.TvCategoryLocalPath.IsNullOrWhiteSpace())
|
status.OutputRootFolders = new List<String> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, category.FullPath) };
|
||||||
{
|
|
||||||
status.OutputRootFolders = new List<String> { category.FullPath };
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
status.OutputRootFolders = new List<String> { Settings.TvCategoryLocalPath };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
|
@ -342,12 +320,6 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
failures.AddIfNotNull(TestAuthentication());
|
failures.AddIfNotNull(TestAuthentication());
|
||||||
failures.AddIfNotNull(TestGlobalConfig());
|
failures.AddIfNotNull(TestGlobalConfig());
|
||||||
failures.AddIfNotNull(TestCategory());
|
failures.AddIfNotNull(TestCategory());
|
||||||
|
|
||||||
if (!Settings.TvCategoryLocalPath.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
failures.AddIfNotNull(TestFolder(Settings.TvCategoryLocalPath, "TvCategoryLocalPath"));
|
|
||||||
failures.AddIfNotNull(TestCategoryLocalPath());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ValidationFailure TestConnection()
|
private ValidationFailure TestConnection()
|
||||||
|
@ -447,14 +419,34 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ValidationFailure TestCategoryLocalPath()
|
private void MigrateLocalCategoryPath()
|
||||||
{
|
{
|
||||||
if (Settings.Host == "127.0.0.1" || Settings.Host == "localhost")
|
// TODO: Remove around January 2015, this code moves the settings to the RemotePathMappingService.
|
||||||
|
if (!Settings.TvCategoryLocalPath.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
return new ValidationFailure("TvCategoryLocalPath", "Do not set when SABnzbd is running on the same system as NzbDrone");
|
try
|
||||||
}
|
{
|
||||||
|
_logger.Debug("Has legacy TvCategoryLocalPath, trying to migrate to RemotePathMapping list.");
|
||||||
|
|
||||||
return null;
|
var config = _proxy.GetConfig(Settings);
|
||||||
|
var category = GetCategories(config).FirstOrDefault(v => v.Name == Settings.TvCategory);
|
||||||
|
|
||||||
|
if (category != null)
|
||||||
|
{
|
||||||
|
var localPath = Settings.TvCategoryLocalPath;
|
||||||
|
Settings.TvCategoryLocalPath = null;
|
||||||
|
|
||||||
|
_remotePathMappingService.MigrateLocalCategoryPath(Definition.Id, Settings, Settings.Host, category.FullPath, localPath);
|
||||||
|
|
||||||
|
_logger.Info("Discovered Local Category Path for {0}, the setting was automatically moved to the Remote Path Mapping table.", Definition.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (DownloadClientException ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Unable to migrate local category path", ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -25,9 +25,6 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,16 +59,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 = "Category Local Path", Type = FieldType.Textbox, Advanced = true, HelpText = "Local path to the category output dir. Useful if Sabnzbd runs on another computer.")]
|
// TODO: Remove around January 2015, this setting was superceded by the RemotePathMappingService, but has to remain for a while to properly migrate.
|
||||||
public String TvCategoryLocalPath { get; set; }
|
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")]
|
[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")]
|
||||||
public Int32 RecentTvPriority { get; set; }
|
public Int32 RecentTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(8, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
|
[FieldDefinition(7, 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(9, 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()
|
||||||
|
|
|
@ -14,6 +14,7 @@ using NzbDrone.Core.MediaFiles;
|
||||||
using NLog;
|
using NLog;
|
||||||
using Omu.ValueInjecter;
|
using Omu.ValueInjecter;
|
||||||
using FluentValidation.Results;
|
using FluentValidation.Results;
|
||||||
|
using NzbDrone.Core.RemotePathMappings;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download.Clients.UsenetBlackhole
|
namespace NzbDrone.Core.Download.Clients.UsenetBlackhole
|
||||||
{
|
{
|
||||||
|
@ -27,8 +28,9 @@ namespace NzbDrone.Core.Download.Clients.UsenetBlackhole
|
||||||
IConfigService configService,
|
IConfigService configService,
|
||||||
IDiskProvider diskProvider,
|
IDiskProvider diskProvider,
|
||||||
IParsingService parsingService,
|
IParsingService parsingService,
|
||||||
|
IRemotePathMappingService remotePathMappingService,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
: base(configService, diskProvider, parsingService, logger)
|
: base(configService, diskProvider, parsingService, remotePathMappingService, logger)
|
||||||
{
|
{
|
||||||
_diskScanService = diskScanService;
|
_diskScanService = diskScanService;
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
|
|
|
@ -12,6 +12,7 @@ using NzbDrone.Core.Configuration;
|
||||||
using NLog;
|
using NLog;
|
||||||
using FluentValidation.Results;
|
using FluentValidation.Results;
|
||||||
using NzbDrone.Core.Validation;
|
using NzbDrone.Core.Validation;
|
||||||
|
using NzbDrone.Core.RemotePathMappings;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download
|
namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
|
@ -21,6 +22,7 @@ namespace NzbDrone.Core.Download
|
||||||
protected readonly IConfigService _configService;
|
protected readonly IConfigService _configService;
|
||||||
protected readonly IDiskProvider _diskProvider;
|
protected readonly IDiskProvider _diskProvider;
|
||||||
protected readonly IParsingService _parsingService;
|
protected readonly IParsingService _parsingService;
|
||||||
|
protected readonly IRemotePathMappingService _remotePathMappingService;
|
||||||
protected readonly Logger _logger;
|
protected readonly Logger _logger;
|
||||||
|
|
||||||
public Type ConfigContract
|
public Type ConfigContract
|
||||||
|
@ -49,11 +51,12 @@ namespace NzbDrone.Core.Download
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DownloadClientBase(IConfigService configService, IDiskProvider diskProvider, IParsingService parsingService, Logger logger)
|
protected DownloadClientBase(IConfigService configService, IDiskProvider diskProvider, IParsingService parsingService, IRemotePathMappingService remotePathMappingService, Logger logger)
|
||||||
{
|
{
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
_parsingService = parsingService;
|
_parsingService = parsingService;
|
||||||
|
_remotePathMappingService = remotePathMappingService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,23 +87,6 @@ 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValidationResult Test()
|
public ValidationResult Test()
|
||||||
{
|
{
|
||||||
var failures = new List<ValidationFailure>();
|
var failures = new List<ValidationFailure>();
|
||||||
|
|
|
@ -15,6 +15,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 NzbDrone.Core.RemotePathMappings;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download
|
namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
|
@ -27,8 +28,9 @@ namespace NzbDrone.Core.Download
|
||||||
IConfigService configService,
|
IConfigService configService,
|
||||||
IDiskProvider diskProvider,
|
IDiskProvider diskProvider,
|
||||||
IParsingService parsingService,
|
IParsingService parsingService,
|
||||||
|
IRemotePathMappingService remotePathMappingService,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
: base(configService, diskProvider, parsingService, logger)
|
: base(configService, diskProvider, parsingService, remotePathMappingService, logger)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,6 +223,7 @@
|
||||||
<Compile Include="Datastore\Migration\057_convert_episode_file_path_to_relative.cs" />
|
<Compile Include="Datastore\Migration\057_convert_episode_file_path_to_relative.cs" />
|
||||||
<Compile Include="Datastore\Migration\058_drop_epsiode_file_path.cs" />
|
<Compile Include="Datastore\Migration\058_drop_epsiode_file_path.cs" />
|
||||||
<Compile Include="Datastore\Migration\059_add_enable_options_to_indexers.cs" />
|
<Compile Include="Datastore\Migration\059_add_enable_options_to_indexers.cs" />
|
||||||
|
<Compile Include="Datastore\Migration\063_add_remotepathmappings.cs" />
|
||||||
<Compile Include="Datastore\Migration\061_clear_bad_scene_names.cs" />
|
<Compile Include="Datastore\Migration\061_clear_bad_scene_names.cs" />
|
||||||
<Compile Include="Datastore\Migration\060_remove_enable_from_indexers.cs" />
|
<Compile Include="Datastore\Migration\060_remove_enable_from_indexers.cs" />
|
||||||
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
|
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
|
||||||
|
@ -560,6 +561,9 @@
|
||||||
<Compile Include="MetadataSource\Trakt\TraktException.cs" />
|
<Compile Include="MetadataSource\Trakt\TraktException.cs" />
|
||||||
<Compile Include="MetadataSource\TraktProxy.cs" />
|
<Compile Include="MetadataSource\TraktProxy.cs" />
|
||||||
<Compile Include="MetadataSource\Tvdb\TvdbProxy.cs" />
|
<Compile Include="MetadataSource\Tvdb\TvdbProxy.cs" />
|
||||||
|
<Compile Include="RemotePathMappings\RemotePathMapping.cs" />
|
||||||
|
<Compile Include="RemotePathMappings\RemotePathMappingRepository.cs" />
|
||||||
|
<Compile Include="RemotePathMappings\RemotePathMappingService.cs" />
|
||||||
<Compile Include="Notifications\DownloadMessage.cs" />
|
<Compile Include="Notifications\DownloadMessage.cs" />
|
||||||
<Compile Include="Notifications\Email\Email.cs">
|
<Compile Include="Notifications\Email\Email.cs">
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.RemotePathMappings
|
||||||
|
{
|
||||||
|
public class RemotePathMapping : ModelBase
|
||||||
|
{
|
||||||
|
public String Host { get; set; }
|
||||||
|
public String RemotePath { get; set; }
|
||||||
|
public String LocalPath { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.RemotePathMappings
|
||||||
|
{
|
||||||
|
public interface IRemotePathMappingRepository : IBasicRepository<RemotePathMapping>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RemotePathMappingRepository : BasicRepository<RemotePathMapping>, IRemotePathMappingRepository
|
||||||
|
{
|
||||||
|
|
||||||
|
public RemotePathMappingRepository(IDatabase database, IEventAggregator eventAggregator)
|
||||||
|
: base(database, eventAggregator)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool PublishModelEvents
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
using System.Linq;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Common.Instrumentation;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Common.Cache;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.RemotePathMappings
|
||||||
|
{
|
||||||
|
public interface IRemotePathMappingService
|
||||||
|
{
|
||||||
|
List<RemotePathMapping> All();
|
||||||
|
RemotePathMapping Add(RemotePathMapping mapping);
|
||||||
|
void Remove(int id);
|
||||||
|
RemotePathMapping Get(int id);
|
||||||
|
RemotePathMapping Update(RemotePathMapping mapping);
|
||||||
|
|
||||||
|
String RemapRemoteToLocal(String host, String remotePath);
|
||||||
|
String RemapLocalToRemote(String host, String 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RemotePathMappingService : IRemotePathMappingService
|
||||||
|
{
|
||||||
|
// TODO: Remove DownloadClientRepository reference around January 2015. Used to migrate legacy Local Category Path settings.
|
||||||
|
private readonly IDownloadClientRepository _downloadClientRepository;
|
||||||
|
private readonly IRemotePathMappingRepository _remotePathMappingRepository;
|
||||||
|
private readonly IDiskProvider _diskProvider;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
private readonly ICached<List<RemotePathMapping>> _cache;
|
||||||
|
|
||||||
|
public RemotePathMappingService(IDownloadClientRepository downloadClientRepository,
|
||||||
|
IRemotePathMappingRepository remotePathMappingRepository,
|
||||||
|
IDiskProvider diskProvider,
|
||||||
|
ICacheManager cacheManager,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_downloadClientRepository = downloadClientRepository;
|
||||||
|
_remotePathMappingRepository = remotePathMappingRepository;
|
||||||
|
_diskProvider = diskProvider;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
|
_cache = cacheManager.GetCache<List<RemotePathMapping>>(GetType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RemotePathMapping> All()
|
||||||
|
{
|
||||||
|
return _cache.Get("all", () => _remotePathMappingRepository.All().ToList(), TimeSpan.FromSeconds(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemotePathMapping Add(RemotePathMapping mapping)
|
||||||
|
{
|
||||||
|
mapping.LocalPath = CleanPath(mapping.LocalPath);
|
||||||
|
mapping.RemotePath = CleanPath(mapping.RemotePath);
|
||||||
|
|
||||||
|
var all = All();
|
||||||
|
|
||||||
|
ValidateMapping(all, mapping);
|
||||||
|
|
||||||
|
var result = _remotePathMappingRepository.Insert(mapping);
|
||||||
|
|
||||||
|
_cache.Clear();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(int id)
|
||||||
|
{
|
||||||
|
_remotePathMappingRepository.Delete(id);
|
||||||
|
|
||||||
|
_cache.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemotePathMapping Get(int id)
|
||||||
|
{
|
||||||
|
return _remotePathMappingRepository.Get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemotePathMapping Update(RemotePathMapping mapping)
|
||||||
|
{
|
||||||
|
var existing = All().Where(v => v.Id != mapping.Id).ToList();
|
||||||
|
|
||||||
|
ValidateMapping(existing, mapping);
|
||||||
|
|
||||||
|
var result = _remotePathMappingRepository.Update(mapping);
|
||||||
|
|
||||||
|
_cache.Clear();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateMapping(List<RemotePathMapping> existing, RemotePathMapping mapping)
|
||||||
|
{
|
||||||
|
if (mapping.Host.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid Host");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapping.RemotePath.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid RemotePath");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapping.LocalPath.IsNullOrWhiteSpace() || !Path.IsPathRooted(mapping.LocalPath))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid LocalPath");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_diskProvider.FolderExists(mapping.LocalPath))
|
||||||
|
{
|
||||||
|
throw new DirectoryNotFoundException("Can't add mount point directory that doesn't exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing.Exists(r => r.Host == mapping.Host && r.RemotePath == mapping.RemotePath))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("RemotePath already mounted.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String RemapRemoteToLocal(String host, String remotePath)
|
||||||
|
{
|
||||||
|
if (remotePath.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return remotePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cleanRemotePath = CleanPath(remotePath);
|
||||||
|
|
||||||
|
foreach (var mapping in All())
|
||||||
|
{
|
||||||
|
if (host == mapping.Host && cleanRemotePath.StartsWith(mapping.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('/', '\\');
|
||||||
|
}
|
||||||
|
|
||||||
|
return localPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return remotePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String RemapLocalToRemote(String host, String localPath)
|
||||||
|
{
|
||||||
|
if (localPath.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return localPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cleanLocalPath = CleanPath(localPath);
|
||||||
|
|
||||||
|
foreach (var mapping in All())
|
||||||
|
{
|
||||||
|
if (host != mapping.Host) continue;
|
||||||
|
|
||||||
|
if (cleanLocalPath.StartsWith(mapping.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('/', '\\');
|
||||||
|
}
|
||||||
|
|
||||||
|
return remotePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return localPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
_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))
|
||||||
|
{
|
||||||
|
Add(new RemotePathMapping { Host = host, RemotePath = remotePath, LocalPath = localPath });
|
||||||
|
}
|
||||||
|
|
||||||
|
var downloadClient = _downloadClientRepository.Get(downloadClientId);
|
||||||
|
downloadClient.Settings = newSettings;
|
||||||
|
_downloadClientRepository.Update(downloadClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String CleanPath(String path)
|
||||||
|
{
|
||||||
|
if (path.Contains('\\'))
|
||||||
|
{
|
||||||
|
return path.TrimEnd('\\', '/') + "\\";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return path.TrimEnd('\\', '/') + "/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,9 +4,11 @@ define([
|
||||||
'marionette',
|
'marionette',
|
||||||
'Settings/DownloadClient/DownloadClientCollection',
|
'Settings/DownloadClient/DownloadClientCollection',
|
||||||
'Settings/DownloadClient/DownloadClientCollectionView',
|
'Settings/DownloadClient/DownloadClientCollectionView',
|
||||||
|
'Settings/DownloadClient/DownloadHandling/DownloadHandlingView',
|
||||||
'Settings/DownloadClient/DroneFactory/DroneFactoryView',
|
'Settings/DownloadClient/DroneFactory/DroneFactoryView',
|
||||||
'Settings/DownloadClient/DownloadHandling/DownloadHandlingView'
|
'Settings/DownloadClient/RemotePathMapping/RemotePathMappingCollection',
|
||||||
], function (Marionette, DownloadClientCollection, CollectionView, DroneFactoryView, DownloadHandlingView) {
|
'Settings/DownloadClient/RemotePathMapping/RemotePathMappingCollectionView'
|
||||||
|
], function (Marionette, DownloadClientCollection, DownloadClientCollectionView, DownloadHandlingView, DroneFactoryView, RemotePathMappingCollection, RemotePathMappingCollectionView) {
|
||||||
|
|
||||||
return Marionette.Layout.extend({
|
return Marionette.Layout.extend({
|
||||||
template : 'Settings/DownloadClient/DownloadClientLayoutTemplate',
|
template : 'Settings/DownloadClient/DownloadClientLayoutTemplate',
|
||||||
|
@ -14,18 +16,22 @@ define([
|
||||||
regions: {
|
regions: {
|
||||||
downloadClients : '#x-download-clients-region',
|
downloadClients : '#x-download-clients-region',
|
||||||
downloadHandling : '#x-download-handling-region',
|
downloadHandling : '#x-download-handling-region',
|
||||||
droneFactory : '#x-dronefactory-region'
|
droneFactory : '#x-dronefactory-region',
|
||||||
|
remotePathMappings : '#x-remotepath-mapping-region'
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize: function () {
|
initialize: function () {
|
||||||
this.downloadClientsCollection = new DownloadClientCollection();
|
this.downloadClientsCollection = new DownloadClientCollection();
|
||||||
this.downloadClientsCollection.fetch();
|
this.downloadClientsCollection.fetch();
|
||||||
|
this.remotePathMappingCollection = new RemotePathMappingCollection();
|
||||||
|
this.remotePathMappingCollection.fetch();
|
||||||
},
|
},
|
||||||
|
|
||||||
onShow: function () {
|
onShow: function () {
|
||||||
this.downloadClients.show(new CollectionView({ collection: this.downloadClientsCollection }));
|
this.downloadClients.show(new DownloadClientCollectionView({ collection: this.downloadClientsCollection }));
|
||||||
this.downloadHandling.show(new DownloadHandlingView({ model: this.model }));
|
this.downloadHandling.show(new DownloadHandlingView({ model: this.model }));
|
||||||
this.droneFactory.show(new DroneFactoryView({ model: this.model }));
|
this.droneFactory.show(new DroneFactoryView({ model: this.model }));
|
||||||
|
this.remotePathMappings.show(new RemotePathMappingCollectionView({ collection: this.remotePathMappingCollection }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -1,6 +1,6 @@
|
||||||
<div id="x-download-clients-region"></div>
|
<div id="x-download-clients-region"></div>
|
||||||
<div class="form-horizontal">
|
<div class="form-horizontal">
|
||||||
|
|
||||||
<div id="x-download-handling-region"></div>
|
<div id="x-download-handling-region"></div>
|
||||||
<div id="x-dronefactory-region"></div>
|
<div id="x-dronefactory-region"></div>
|
||||||
|
<div id="x-remotepath-mapping-region"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
'use strict';
|
||||||
|
define([
|
||||||
|
'backbone',
|
||||||
|
'Settings/DownloadClient/RemotePathMapping/RemotePathMappingModel'
|
||||||
|
], function (Backbone, RemotePathMappingModel) {
|
||||||
|
|
||||||
|
return Backbone.Collection.extend({
|
||||||
|
model : RemotePathMappingModel,
|
||||||
|
url : window.NzbDrone.ApiRoot + '/remotePathMapping'
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,28 @@
|
||||||
|
'use strict';
|
||||||
|
define([
|
||||||
|
'AppLayout',
|
||||||
|
'marionette',
|
||||||
|
'Settings/DownloadClient/RemotePathMapping/RemotePathMappingItemView',
|
||||||
|
'Settings/DownloadClient/RemotePathMapping/RemotePathMappingEditView',
|
||||||
|
'Settings/DownloadClient/RemotePathMapping/RemotePathMappingModel',
|
||||||
|
'bootstrap'
|
||||||
|
], function (AppLayout, Marionette, RemotePathMappingItemView, EditView, RemotePathMappingModel) {
|
||||||
|
|
||||||
|
return Marionette.CompositeView.extend({
|
||||||
|
template : 'Settings/DownloadClient/RemotePathMapping/RemotePathMappingCollectionViewTemplate',
|
||||||
|
itemViewContainer : '.x-rows',
|
||||||
|
itemView : RemotePathMappingItemView,
|
||||||
|
|
||||||
|
events: {
|
||||||
|
'click .x-add' : '_addMapping'
|
||||||
|
},
|
||||||
|
|
||||||
|
_addMapping: function() {
|
||||||
|
var model = new RemotePathMappingModel();
|
||||||
|
model.collection = this.collection;
|
||||||
|
|
||||||
|
var view = new EditView({ model: model, targetCollection: this.collection});
|
||||||
|
AppLayout.modalRegion.show(view);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,24 @@
|
||||||
|
<fieldset class="advanced-setting">
|
||||||
|
<legend>Remote Path Mappings</legend>
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div id="remotepath-mapping-list">
|
||||||
|
<div class="remotepath-header x-header hidden-xs">
|
||||||
|
<div class="row">
|
||||||
|
<span class="col-sm-2">Host</span>
|
||||||
|
<span class="col-sm-5">Remote Path</span>
|
||||||
|
<span class="col-sm-4">Local Path</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="rows x-rows">
|
||||||
|
</div>
|
||||||
|
<div class="remotepath-footer">
|
||||||
|
<div class="pull-right">
|
||||||
|
<span class="add-remotepath-mapping">
|
||||||
|
<i class="icon-nd-add x-add" title="Add new mapping" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
|
@ -0,0 +1,23 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
define([
|
||||||
|
'vent',
|
||||||
|
'marionette'
|
||||||
|
], function (vent, Marionette) {
|
||||||
|
return Marionette.ItemView.extend({
|
||||||
|
template: 'Settings/DownloadClient/RemotePathMapping/RemotePathMappingDeleteViewTemplate',
|
||||||
|
|
||||||
|
events: {
|
||||||
|
'click .x-confirm-delete': '_delete'
|
||||||
|
},
|
||||||
|
|
||||||
|
_delete: function () {
|
||||||
|
this.model.destroy({
|
||||||
|
wait : true,
|
||||||
|
success: function () {
|
||||||
|
vent.trigger(vent.Commands.CloseModalCommand);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,13 @@
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h3>Delete Mapping</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Are you sure you want to delete the mapping for '{{localPath}}'?</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn" data-dismiss="modal">cancel</button>
|
||||||
|
<button class="btn btn-danger x-confirm-delete">delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,51 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
define([
|
||||||
|
'underscore',
|
||||||
|
'vent',
|
||||||
|
'AppLayout',
|
||||||
|
'marionette',
|
||||||
|
'Settings/DownloadClient/RemotePathMapping/RemotePathMappingDeleteView',
|
||||||
|
'Commands/CommandController',
|
||||||
|
'Mixins/AsModelBoundView',
|
||||||
|
'Mixins/AsValidatedView',
|
||||||
|
'Mixins/AsEditModalView',
|
||||||
|
'Mixins/AutoComplete',
|
||||||
|
'bootstrap'
|
||||||
|
], function (_, vent, AppLayout, Marionette, DeleteView, CommandController, AsModelBoundView, AsValidatedView, AsEditModalView) {
|
||||||
|
|
||||||
|
var view = Marionette.ItemView.extend({
|
||||||
|
template : 'Settings/DownloadClient/RemotePathMapping/RemotePathMappingEditViewTemplate',
|
||||||
|
|
||||||
|
ui : {
|
||||||
|
path : '.x-path',
|
||||||
|
modalBody : '.modal-body'
|
||||||
|
},
|
||||||
|
|
||||||
|
_deleteView: DeleteView,
|
||||||
|
|
||||||
|
initialize : function (options) {
|
||||||
|
this.targetCollection = options.targetCollection;
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow : function () {
|
||||||
|
//Hack to deal with modals not overflowing
|
||||||
|
if (this.ui.path.length > 0) {
|
||||||
|
this.ui.modalBody.addClass('modal-overflow');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ui.path.autoComplete('/directories');
|
||||||
|
},
|
||||||
|
|
||||||
|
_onAfterSave : function () {
|
||||||
|
this.targetCollection.add(this.model, { merge : true });
|
||||||
|
vent.trigger(vent.Commands.CloseModalCommand);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AsModelBoundView.call(view);
|
||||||
|
AsValidatedView.call(view);
|
||||||
|
AsEditModalView.call(view);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
});
|
|
@ -0,0 +1,63 @@
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
{{#if id}}
|
||||||
|
<h3>Edit Mapping</h3>
|
||||||
|
{{else}}
|
||||||
|
<h3>Add Mapping</h3>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<div class="modal-body remotepath-mapping-modal">
|
||||||
|
<div class="form-horizontal">
|
||||||
|
<div>
|
||||||
|
<p>Use this feature if you have a remotely running Download Client. NzbDrone will use the information provided to translate the paths provided by the Download Client API to something NzbDrone can access and import.</p>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-3 control-label">Host</label>
|
||||||
|
|
||||||
|
<div class="col-sm-1 col-sm-push-3 help-inline">
|
||||||
|
<i class="icon-nd-form-info" title="Host you specified for the remote Download Client." />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-3 col-sm-pull-1">
|
||||||
|
<input type="text" name="host" class="form-control"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-3 control-label">Remote Path</label>
|
||||||
|
|
||||||
|
<div class="col-sm-1 col-sm-push-5 help-inline">
|
||||||
|
<i class="icon-nd-form-info" title="Root path to the directory that the Download Client accesses." />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-5 col-sm-pull-1">
|
||||||
|
<input type="text" name="remotePath" class="form-control"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-3 control-label">Local Path</label>
|
||||||
|
|
||||||
|
<div class="col-sm-1 col-sm-push-5 help-inline">
|
||||||
|
<i class="icon-nd-form-info" title="Path that NzbDrone should use to access the same directory remotely." />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-5 col-sm-pull-1">
|
||||||
|
<input type="text" name="localPath" class="form-control x-path"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
{{#if id}}
|
||||||
|
<button class="btn btn-danger pull-left x-delete">delete</button>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<button class="btn" data-dismiss="modal">cancel</button>
|
||||||
|
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn btn-primary x-save">save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,26 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
define([
|
||||||
|
'AppLayout',
|
||||||
|
'marionette',
|
||||||
|
'Settings/DownloadClient/RemotePathMapping/RemotePathMappingEditView'
|
||||||
|
], function (AppLayout, Marionette, EditView) {
|
||||||
|
|
||||||
|
return Marionette.ItemView.extend({
|
||||||
|
template : 'Settings/DownloadClient/RemotePathMapping/RemotePathMappingItemViewTemplate',
|
||||||
|
className : 'row',
|
||||||
|
|
||||||
|
events: {
|
||||||
|
'click .x-edit' : '_editMapping'
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function () {
|
||||||
|
this.listenTo(this.model, 'sync', this.render);
|
||||||
|
},
|
||||||
|
|
||||||
|
_editMapping: function() {
|
||||||
|
var view = new EditView({ model: this.model, targetCollection: this.model.collection});
|
||||||
|
AppLayout.modalRegion.show(view);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,12 @@
|
||||||
|
<span class="col-sm-2">
|
||||||
|
<div>{{host}}</div>
|
||||||
|
</span>
|
||||||
|
<span class="col-sm-5">
|
||||||
|
<div>{{remotePath}}</div>
|
||||||
|
</span>
|
||||||
|
<span class="col-sm-4">
|
||||||
|
<div>{{localPath}}</div>
|
||||||
|
</span>
|
||||||
|
<span class="col-sm-1">
|
||||||
|
<div class="pull-right"><i class="icon-nd-edit x-edit" title="" data-original-title="Edit Mapping"></i></div>
|
||||||
|
</span>
|
|
@ -0,0 +1,10 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
define([
|
||||||
|
'jquery',
|
||||||
|
'backbone.deepmodel'
|
||||||
|
], function ($, DeepModel) {
|
||||||
|
return DeepModel.DeepModel.extend({
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
|
@ -31,3 +31,30 @@
|
||||||
width: 33%;
|
width: 33%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.add-remotepath-mapping {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 6px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#remotepath-mapping-list {
|
||||||
|
|
||||||
|
.remotepath-header .row {
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rows .row {
|
||||||
|
line-height : 30px;
|
||||||
|
border-top : 1px solid #ddd;
|
||||||
|
vertical-align : middle;
|
||||||
|
padding : 5px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ define(
|
||||||
return Marionette.CompositeView.extend({
|
return Marionette.CompositeView.extend({
|
||||||
template: 'Settings/Quality/Definition/QualityDefinitionCollectionTemplate',
|
template: 'Settings/Quality/Definition/QualityDefinitionCollectionTemplate',
|
||||||
|
|
||||||
itemViewContainer: ".x-rows",
|
itemViewContainer: '.x-rows',
|
||||||
|
|
||||||
itemView: QualityDefinitionView
|
itemView: QualityDefinitionView
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue