Add NZB Station for Synology (#841)

Devin Buhl 8 years ago committed by GitHub
parent 97ee66465d
commit 731e607666

@ -20,16 +20,16 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
{
protected DownloadStationSettings _settings;
protected DownloadStationTorrent _queued;
protected DownloadStationTorrent _downloading;
protected DownloadStationTorrent _failed;
protected DownloadStationTorrent _completed;
protected DownloadStationTorrent _seeding;
protected DownloadStationTorrent _magnet;
protected DownloadStationTorrent _singleFile;
protected DownloadStationTorrent _multipleFiles;
protected DownloadStationTorrent _singleFileCompleted;
protected DownloadStationTorrent _multipleFilesCompleted;
protected DownloadStationTask _queued;
protected DownloadStationTask _downloading;
protected DownloadStationTask _failed;
protected DownloadStationTask _completed;
protected DownloadStationTask _seeding;
protected DownloadStationTask _magnet;
protected DownloadStationTask _singleFile;
protected DownloadStationTask _multipleFiles;
protected DownloadStationTask _singleFileCompleted;
protected DownloadStationTask _multipleFilesCompleted;
protected string _serialNumber = "SERIALNUMBER";
protected string _category = "sonarr";
@ -55,15 +55,15 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
Subject.Definition = new DownloadClientDefinition();
Subject.Definition.Settings = _settings;
_queued = new DownloadStationTorrent()
_queued = new DownloadStationTask()
{
Id = "id1",
Size = 1000,
Status = DownloadStationTaskStatus.Waiting,
Type = DownloadStationTaskType.BT,
Type = DownloadStationTaskType.BT.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTorrentAdditional
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
@ -78,15 +78,15 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
}
};
_completed = new DownloadStationTorrent()
_completed = new DownloadStationTask()
{
Id = "id2",
Size = 1000,
Status = DownloadStationTaskStatus.Finished,
Type = DownloadStationTaskType.BT,
Type = DownloadStationTaskType.BT.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTorrentAdditional
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
@ -101,15 +101,15 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
}
};
_seeding = new DownloadStationTorrent()
_seeding = new DownloadStationTask()
{
Id = "id2",
Size = 1000,
Status = DownloadStationTaskStatus.Seeding,
Type = DownloadStationTaskType.BT,
Type = DownloadStationTaskType.BT.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTorrentAdditional
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
@ -124,15 +124,15 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
}
};
_downloading = new DownloadStationTorrent()
_downloading = new DownloadStationTask()
{
Id = "id3",
Size = 1000,
Status = DownloadStationTaskStatus.Downloading,
Type = DownloadStationTaskType.BT,
Type = DownloadStationTaskType.BT.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTorrentAdditional
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
@ -147,15 +147,15 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
}
};
_failed = new DownloadStationTorrent()
_failed = new DownloadStationTask()
{
Id = "id4",
Size = 1000,
Status = DownloadStationTaskStatus.Error,
Type = DownloadStationTaskType.BT,
Type = DownloadStationTaskType.BT.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTorrentAdditional
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
@ -170,15 +170,15 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
}
};
_singleFile = new DownloadStationTorrent()
_singleFile = new DownloadStationTask()
{
Id = "id5",
Size = 1000,
Status = DownloadStationTaskStatus.Seeding,
Type = DownloadStationTaskType.BT,
Type = DownloadStationTaskType.BT.ToString(),
Username = "admin",
Title = "a.mkv",
Additional = new DownloadStationTorrentAdditional
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
@ -193,15 +193,15 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
}
};
_multipleFiles = new DownloadStationTorrent()
_multipleFiles = new DownloadStationTask()
{
Id = "id6",
Size = 1000,
Status = DownloadStationTaskStatus.Seeding,
Type = DownloadStationTaskType.BT,
Type = DownloadStationTaskType.BT.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTorrentAdditional
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
@ -216,15 +216,15 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
}
};
_singleFileCompleted = new DownloadStationTorrent()
_singleFileCompleted = new DownloadStationTask()
{
Id = "id6",
Size = 1000,
Status = DownloadStationTaskStatus.Finished,
Type = DownloadStationTaskType.BT,
Type = DownloadStationTaskType.BT.ToString(),
Username = "admin",
Title = "a.mkv",
Additional = new DownloadStationTorrentAdditional
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
@ -239,15 +239,15 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
}
};
_multipleFilesCompleted = new DownloadStationTorrent()
_multipleFilesCompleted = new DownloadStationTask()
{
Id = "id6",
Size = 1000,
Status = DownloadStationTaskStatus.Finished,
Type = DownloadStationTaskType.BT,
Type = DownloadStationTaskType.BT.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTorrentAdditional
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
@ -304,21 +304,21 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
_settings.TvDirectory = _tvDirectory;
}
protected virtual void GivenTorrents(List<DownloadStationTorrent> torrents)
protected virtual void GivenTasks(List<DownloadStationTask> torrents)
{
if (torrents == null)
{
torrents = new List<DownloadStationTorrent>();
torrents = new List<DownloadStationTask>();
}
Mocker.GetMock<IDownloadStationProxy>()
.Setup(s => s.GetTorrents(It.IsAny<DownloadStationSettings>()))
.Setup(s => s.GetTasks(It.IsAny<DownloadStationSettings>()))
.Returns(torrents);
}
protected void PrepareClientToReturnQueuedItem()
{
GivenTorrents(new List<DownloadStationTorrent>
GivenTasks(new List<DownloadStationTask>
{
_queued
});
@ -331,11 +331,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[1000]));
Mocker.GetMock<IDownloadStationProxy>()
.Setup(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>()))
.Setup(s => s.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>()))
.Callback(PrepareClientToReturnQueuedItem);
Mocker.GetMock<IDownloadStationProxy>()
.Setup(s => s.AddTorrentFromData(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>()))
.Setup(s => s.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>()))
.Callback(PrepareClientToReturnQueuedItem);
}
@ -350,10 +350,10 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
protected int GivenAllKindOfTasks()
{
var tasks = new List<DownloadStationTorrent>() { _queued, _completed, _failed, _downloading, _seeding };
var tasks = new List<DownloadStationTask>() { _queued, _completed, _failed, _downloading, _seeding };
Mocker.GetMock<IDownloadStationProxy>()
.Setup(d => d.GetTorrents(_settings))
.Setup(d => d.GetTasks(_settings))
.Returns(tasks);
return tasks.Count;
@ -373,7 +373,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
id.Should().NotBeNullOrEmpty();
Mocker.GetMock<IDownloadStationProxy>()
.Verify(v => v.AddTorrentFromUrl(It.IsAny<string>(), _tvDirectory, It.IsAny<DownloadStationSettings>()), Times.Once());
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), _tvDirectory, It.IsAny<DownloadStationSettings>()), Times.Once());
}
[Test]
@ -390,7 +390,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
id.Should().NotBeNullOrEmpty();
Mocker.GetMock<IDownloadStationProxy>()
.Verify(v => v.AddTorrentFromUrl(It.IsAny<string>(), $"{_defaultDestination}/{_category}", It.IsAny<DownloadStationSettings>()), Times.Once());
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), $"{_defaultDestination}/{_category}", It.IsAny<DownloadStationSettings>()), Times.Once());
}
[Test]
@ -406,7 +406,29 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
id.Should().NotBeNullOrEmpty();
Mocker.GetMock<IDownloadStationProxy>()
.Verify(v => v.AddTorrentFromUrl(It.IsAny<string>(), null, It.IsAny<DownloadStationSettings>()), Times.Once());
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, It.IsAny<DownloadStationSettings>()), Times.Once());
}
[Test]
public void GetItems_should_return_empty_list_if_no_tasks_available()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask>());
Subject.GetItems().Should().BeEmpty();
}
[Test]
public void GetItems_should_return_ignore_tasks_of_unknown_type()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask> { _completed });
_completed.Type = "ipfs";
Subject.GetItems().Should().BeEmpty();
}
[Test]
@ -416,7 +438,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
GivenSerialNumber();
GivenSharedFolder();
GivenTorrents(new List<DownloadStationTorrent> { _completed });
GivenTasks(new List<DownloadStationTask> { _completed });
Subject.GetItems().Should().BeEmpty();
}
@ -450,7 +472,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
}
[Test]
public void Download_should_throw_and_not_add_torrent_if_cannot_get_serial_number()
public void Download_should_throw_and_not_add_task_if_cannot_get_serial_number()
{
var remoteEpisode = CreateRemoteEpisode();
@ -461,16 +483,16 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.Download(remoteEpisode));
Mocker.GetMock<IDownloadStationProxy>()
.Verify(v => v.AddTorrentFromUrl(It.IsAny<string>(), null, _settings), Times.Never());
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, _settings), Times.Never());
}
[Test]
public void GetItems_should_set_outputPath_to_base_folder_when_single_file_non_finished_torrent()
public void GetItems_should_set_outputPath_to_base_folder_when_single_file_non_finished_tasks()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTorrents(new List<DownloadStationTorrent>() { _singleFile });
GivenTasks(new List<DownloadStationTask>() { _singleFile });
var items = Subject.GetItems();
@ -479,12 +501,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
}
[Test]
public void GetItems_should_set_outputPath_to_torrent_folder_when_multiple_files_non_finished_torrent()
public void GetItems_should_set_outputPath_to_torrent_folder_when_multiple_files_non_finished_tasks()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTorrents(new List<DownloadStationTorrent>() { _multipleFiles });
GivenTasks(new List<DownloadStationTask>() { _multipleFiles });
var items = Subject.GetItems();
@ -493,12 +515,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
}
[Test]
public void GetItems_should_set_outputPath_to_base_folder_when_single_file_finished_torrent()
public void GetItems_should_set_outputPath_to_base_folder_when_single_file_finished_tasks()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTorrents(new List<DownloadStationTorrent>() { _singleFileCompleted });
GivenTasks(new List<DownloadStationTask>() { _singleFileCompleted });
var items = Subject.GetItems();
@ -507,12 +529,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
}
[Test]
public void GetItems_should_set_outputPath_to_torrent_folder_when_multiple_files_finished_torrent()
public void GetItems_should_set_outputPath_to_torrent_folder_when_multiple_files_finished_tasks()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTorrents(new List<DownloadStationTorrent>() { _multipleFilesCompleted });
GivenTasks(new List<DownloadStationTask>() { _multipleFilesCompleted });
var items = Subject.GetItems();
@ -521,12 +543,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
}
[Test]
public void GetItems_should_not_map_outputpath_for_queued_or_downloading_torrents()
public void GetItems_should_not_map_outputpath_for_queued_or_downloading_tasks()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTorrents(new List<DownloadStationTorrent>
GivenTasks(new List<DownloadStationTask>
{
_queued, _downloading
});
@ -538,12 +560,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
}
[Test]
public void GetItems_should_map_outputpath_for_completed_or_failed_torrents()
public void GetItems_should_map_outputpath_for_completed_or_failed_tasks()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTorrents(new List<DownloadStationTorrent>
GivenTasks(new List<DownloadStationTask>
{
_completed, _failed, _seeding
});
@ -565,7 +587,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
_queued.Status = apiStatus;
GivenTorrents(new List<DownloadStationTorrent>() { _queued });
GivenTasks(new List<DownloadStationTask>() { _queued });
var items = Subject.GetItems();
@ -589,7 +611,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
_queued.Status = apiStatus;
GivenTorrents(new List<DownloadStationTorrent>() { _queued });
GivenTasks(new List<DownloadStationTask>() { _queued });
var items = Subject.GetItems();
items.Should().HaveCount(1);
@ -597,4 +619,4 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
items.First().Status.Should().Be(expectedItemStatus);
}
}
}
}

@ -0,0 +1,452 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Http;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients.DownloadStation;
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Test.Common;
using NzbDrone.Core.Organizer;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
{
[TestFixture]
public class UsenetDownloadStationFixture : DownloadClientFixtureBase<UsenetDownloadStation>
{
protected DownloadStationSettings _settings;
protected DownloadStationTask _queued;
protected DownloadStationTask _downloading;
protected DownloadStationTask _failed;
protected DownloadStationTask _completed;
protected DownloadStationTask _seeding;
protected string _serialNumber = "SERIALNUMBER";
protected string _category = "sonarr";
protected string _tvDirectory = @"video/Series";
protected string _defaultDestination = "somepath";
protected OsPath _physicalPath = new OsPath("/mnt/sdb1/mydata");
protected RemoteEpisode _remoteEpisode;
protected Dictionary<string, object> _downloadStationConfigItems;
[SetUp]
public void Setup()
{
_remoteEpisode = CreateRemoteEpisode();
_settings = new DownloadStationSettings()
{
Host = "127.0.0.1",
Port = 5000,
Username = "admin",
Password = "pass"
};
Subject.Definition = new DownloadClientDefinition();
Subject.Definition.Settings = _settings;
_queued = new DownloadStationTask()
{
Id = "id1",
Size = 1000,
Status = DownloadStationTaskStatus.Waiting,
Type = DownloadStationTaskType.NZB.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
{ "destination","shared/folder" },
{ "uri", FileNameBuilder.CleanFileName(_remoteEpisode.Release.Title) + ".nzb" }
},
Transfer = new Dictionary<string, string>
{
{ "size_downloaded", "0"},
{ "speed_download", "0" }
}
}
};
_completed = new DownloadStationTask()
{
Id = "id2",
Size = 1000,
Status = DownloadStationTaskStatus.Finished,
Type = DownloadStationTaskType.NZB.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
{ "destination","shared/folder" },
{ "uri", FileNameBuilder.CleanFileName(_remoteEpisode.Release.Title) + ".nzb" }
},
Transfer = new Dictionary<string, string>
{
{ "size_downloaded", "1000"},
{ "speed_download", "0" }
},
}
};
_seeding = new DownloadStationTask()
{
Id = "id2",
Size = 1000,
Status = DownloadStationTaskStatus.Seeding,
Type = DownloadStationTaskType.NZB.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
{ "destination","shared/folder" },
{ "uri", FileNameBuilder.CleanFileName(_remoteEpisode.Release.Title) + ".nzb" }
},
Transfer = new Dictionary<string, string>
{
{ "size_downloaded", "1000"},
{ "speed_download", "0" }
}
}
};
_downloading = new DownloadStationTask()
{
Id = "id3",
Size = 1000,
Status = DownloadStationTaskStatus.Downloading,
Type = DownloadStationTaskType.NZB.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
{ "destination","shared/folder" },
{ "uri", FileNameBuilder.CleanFileName(_remoteEpisode.Release.Title) + ".nzb" }
},
Transfer = new Dictionary<string, string>
{
{ "size_downloaded", "100"},
{ "speed_download", "50" }
}
}
};
_failed = new DownloadStationTask()
{
Id = "id4",
Size = 1000,
Status = DownloadStationTaskStatus.Error,
Type = DownloadStationTaskType.NZB.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
{ "destination","shared/folder" },
{ "uri", FileNameBuilder.CleanFileName(_remoteEpisode.Release.Title) + ".nzb" }
},
Transfer = new Dictionary<string, string>
{
{ "size_downloaded", "10"},
{ "speed_download", "0" }
}
}
};
Mocker.GetMock<IHttpClient>()
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[0]));
_downloadStationConfigItems = new Dictionary<string, object>
{
{ "default_destination", _defaultDestination },
};
Mocker.GetMock<IDownloadStationProxy>()
.Setup(v => v.GetConfig(It.IsAny<DownloadStationSettings>()))
.Returns(_downloadStationConfigItems);
}
protected void GivenSharedFolder()
{
Mocker.GetMock<ISharedFolderResolver>()
.Setup(s => s.RemapToFullPath(It.IsAny<OsPath>(), It.IsAny<DownloadStationSettings>(), It.IsAny<string>()))
.Returns<OsPath, DownloadStationSettings, string>((path, setttings, serial) => _physicalPath);
}
protected void GivenSerialNumber()
{
Mocker.GetMock<ISerialNumberProvider>()
.Setup(s => s.GetSerialNumber(It.IsAny<DownloadStationSettings>()))
.Returns(_serialNumber);
}
protected void GivenTvCategory()
{
_settings.TvCategory = _category;
}
protected void GivenTvDirectory()
{
_settings.TvDirectory = _tvDirectory;
}
protected virtual void GivenTasks(List<DownloadStationTask> nzbs)
{
if (nzbs == null)
{
nzbs = new List<DownloadStationTask>();
}
Mocker.GetMock<IDownloadStationProxy>()
.Setup(s => s.GetTasks(It.IsAny<DownloadStationSettings>()))
.Returns(nzbs);
}
protected void PrepareClientToReturnQueuedItem()
{
GivenTasks(new List<DownloadStationTask>
{
_queued
});
}
protected void GivenSuccessfulDownload()
{/*
Mocker.GetMock<IHttpClient>()
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[1000]));
*/
Mocker.GetMock<IDownloadStationProxy>()
.Setup(s => s.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>()))
.Callback(PrepareClientToReturnQueuedItem);
}
protected void GivenAllKindOfTasks()
{
var tasks = new List<DownloadStationTask>() { _queued, _completed, _failed, _downloading, _seeding };
Mocker.GetMock<IDownloadStationProxy>()
.Setup(d => d.GetTasks(_settings))
.Returns(tasks);
}
[Test]
public void Download_with_TvDirectory_should_force_directory()
{
GivenSerialNumber();
GivenTvDirectory();
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode();
var id = Subject.Download(remoteEpisode);
id.Should().NotBeNullOrEmpty();
Mocker.GetMock<IDownloadStationProxy>()
.Verify(v => v.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), _tvDirectory, It.IsAny<DownloadStationSettings>()), Times.Once());
}
[Test]
public void Download_with_category_should_force_directory()
{
GivenSerialNumber();
GivenTvCategory();
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode();
var id = Subject.Download(remoteEpisode);
id.Should().NotBeNullOrEmpty();
Mocker.GetMock<IDownloadStationProxy>()
.Verify(v => v.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), $"{_defaultDestination}/{_category}", It.IsAny<DownloadStationSettings>()), Times.Once());
}
[Test]
public void Download_without_TvDirectory_and_Category_should_use_default()
{
GivenSerialNumber();
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode();
var id = Subject.Download(remoteEpisode);
id.Should().NotBeNullOrEmpty();
Mocker.GetMock<IDownloadStationProxy>()
.Verify(v => v.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), null, It.IsAny<DownloadStationSettings>()), Times.Once());
}
[Test]
public void GetItems_should_return_empty_list_if_no_tasks_available()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask>());
Subject.GetItems().Should().BeEmpty();
}
[Test]
public void GetItems_should_return_ignore_tasks_of_unknown_type()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask> { _completed });
_completed.Type = "ipfs";
Subject.GetItems().Should().BeEmpty();
}
[Test]
public void GetItems_should_ignore_downloads_in_wrong_folder()
{
_settings.TvDirectory = @"/shared/folder/sub";
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask> { _completed });
Subject.GetItems().Should().BeEmpty();
}
[Test]
public void GetItems_should_throw_if_shared_folder_resolve_fails()
{
Mocker.GetMock<ISharedFolderResolver>()
.Setup(s => s.RemapToFullPath(It.IsAny<OsPath>(), It.IsAny<DownloadStationSettings>(), It.IsAny<string>()))
.Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException"));
GivenSerialNumber();
GivenAllKindOfTasks();
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.GetItems());
ExceptionVerification.ExpectedErrors(0);
}
[Test]
public void GetItems_should_throw_if_serial_number_unavailable()
{
Mocker.GetMock<ISerialNumberProvider>()
.Setup(s => s.GetSerialNumber(_settings))
.Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException"));
GivenSharedFolder();
GivenAllKindOfTasks();
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.GetItems());
ExceptionVerification.ExpectedErrors(0);
}
[Test]
public void Download_should_throw_and_not_add_task_if_cannot_get_serial_number()
{
var remoteEpisode = CreateRemoteEpisode();
Mocker.GetMock<ISerialNumberProvider>()
.Setup(s => s.GetSerialNumber(_settings))
.Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException"));
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.Download(remoteEpisode));
Mocker.GetMock<IDownloadStationProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, _settings), Times.Never());
}
[Test]
public void GetItems_should_not_map_outputpath_for_queued_or_downloading_tasks()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask>
{
_queued, _downloading
});
var items = Subject.GetItems();
items.Should().HaveCount(2);
items.Should().OnlyContain(v => v.OutputPath.IsEmpty);
}
[Test]
public void GetItems_should_map_outputpath_for_completed_or_failed_tasks()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask>
{
_completed, _failed, _seeding
});
var items = Subject.GetItems();
items.Should().HaveCount(3);
items.Should().OnlyContain(v => !v.OutputPath.IsEmpty);
}
[TestCase(DownloadStationTaskStatus.Downloading, DownloadItemStatus.Downloading, true)]
[TestCase(DownloadStationTaskStatus.Finished, DownloadItemStatus.Completed, false)]
[TestCase(DownloadStationTaskStatus.Waiting, DownloadItemStatus.Queued, true)]
public void GetItems_should_return_readonly_expected(DownloadStationTaskStatus apiStatus, DownloadItemStatus expectedItemStatus, bool readOnlyExpected)
{
GivenSerialNumber();
GivenSharedFolder();
_queued.Status = apiStatus;
GivenTasks(new List<DownloadStationTask>() { _queued });
var items = Subject.GetItems();
items.Should().HaveCount(1);
items.First().IsReadOnly.Should().Be(readOnlyExpected);
}
[TestCase(DownloadStationTaskStatus.Downloading, DownloadItemStatus.Downloading)]
[TestCase(DownloadStationTaskStatus.Error, DownloadItemStatus.Failed)]
[TestCase(DownloadStationTaskStatus.Extracting, DownloadItemStatus.Downloading)]
[TestCase(DownloadStationTaskStatus.Finished, DownloadItemStatus.Completed)]
[TestCase(DownloadStationTaskStatus.Finishing, DownloadItemStatus.Downloading)]
[TestCase(DownloadStationTaskStatus.HashChecking, DownloadItemStatus.Downloading)]
[TestCase(DownloadStationTaskStatus.Paused, DownloadItemStatus.Paused)]
[TestCase(DownloadStationTaskStatus.Waiting, DownloadItemStatus.Queued)]
public void GetItems_should_return_item_as_downloadItemStatus(DownloadStationTaskStatus apiStatus, DownloadItemStatus expectedItemStatus)
{
GivenSerialNumber();
GivenSharedFolder();
_queued.Status = apiStatus;
GivenTasks(new List<DownloadStationTask>() { _queued });
var items = Subject.GetItems();
items.Should().HaveCount(1);
items.First().Status.Should().Be(expectedItemStatus);
}
}
}

@ -186,6 +186,7 @@
<Compile Include="Download\DownloadClientTests\DownloadStationTests\TorrentDownloadStationFixture.cs" />
<Compile Include="Download\DownloadClientTests\DownloadStationTests\SerialNumberProviderFixture.cs" />
<Compile Include="Download\DownloadClientTests\DownloadStationTests\SharedFolderResolverFixture.cs" />
<Compile Include="Download\DownloadClientTests\DownloadStationTests\UsenetDownloadStationFixture.cs" />
<Compile Include="Download\DownloadServiceFixture.cs" />
<Compile Include="Download\FailedDownloadServiceFixture.cs" />
<Compile Include="Download\Pending\PendingReleaseServiceTests\PendingReleaseServiceFixture.cs" />

@ -42,7 +42,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
[FieldDefinition(3, Label = "Password", Type = FieldType.Password)]
public string Password { get; set; }
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]
public string TvCategory { get; set; }
[FieldDefinition(5, Label = "Directory", Type = FieldType.Textbox, HelpText = "Optional shared folder to put downloads into, leave blank to use the default Download Station location")]

@ -5,7 +5,7 @@ using Newtonsoft.Json.Converters;
namespace NzbDrone.Core.Download.Clients.DownloadStation
{
public class DownloadStationTorrent
public class DownloadStationTask
{
public string Username { get; set; }
@ -15,8 +15,10 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
public long Size { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public DownloadStationTaskType Type { get; set; }
/// <summary>
/// /// Possible values are: BT, NZB, http, ftp, eMule and https
/// </summary>
public string Type { get; set; }
[JsonProperty(PropertyName = "status_extra")]
public Dictionary<string, string> StatusExtra { get; set; }
@ -24,7 +26,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
[JsonConverter(typeof(StringEnumConverter))]
public DownloadStationTaskStatus Status { get; set; }
public DownloadStationTorrentAdditional Additional { get; set; }
public DownloadStationTaskAdditional Additional { get; set; }
public override string ToString()
{
@ -34,7 +36,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
public enum DownloadStationTaskType
{
BT, NZB, http, ftp, eMule
BT, NZB, http, ftp, eMule, https
}
public enum DownloadStationTaskStatus

@ -3,13 +3,13 @@ using System.Collections.Generic;
namespace NzbDrone.Core.Download.Clients.DownloadStation
{
public class DownloadStationTorrentAdditional
public class DownloadStationTaskAdditional
{
public Dictionary<string, string> Detail { get; set; }
public Dictionary<string, string> Transfer { get; set; }
[JsonProperty("File")]
public List<DownloadStationTorrentFile> Files { get; set; }
public List<DownloadStationTaskFile> Files { get; set; }
}
}

@ -4,11 +4,11 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using static NzbDrone.Core.Download.Clients.DownloadStation.DownloadStationTorrent;
using static NzbDrone.Core.Download.Clients.DownloadStation.DownloadStationTask;
namespace NzbDrone.Core.Download.Clients.DownloadStation
{
public class DownloadStationTorrentFile
public class DownloadStationTaskFile
{
public string FileName { get; set; }

@ -30,4 +30,4 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
return response.Data.SerialNumber;
}
}
}
}

@ -77,7 +77,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
_authenticated = false;
return ProcessRequest<T>(api, arguments, settings, operation, method, retries++);
}
var msg = $"Failed to {operation}. Reason: {responseContent.Error.GetMessage(api)}";
_logger.Error(msg);
@ -210,4 +210,4 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
}
}
}
}
}

@ -10,11 +10,11 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
{
public interface IDownloadStationProxy
{
IEnumerable<DownloadStationTorrent> GetTorrents(DownloadStationSettings settings);
IEnumerable<DownloadStationTask> GetTasks(DownloadStationSettings settings);
Dictionary<string, object> GetConfig(DownloadStationSettings settings);
void RemoveTorrent(string downloadId, DownloadStationSettings settings);
void AddTorrentFromUrl(string url, string downloadDirectory, DownloadStationSettings settings);
void AddTorrentFromData(byte[] torrentData, string filename, string downloadDirectory, DownloadStationSettings settings);
void RemoveTask(string downloadId, DownloadStationSettings settings);
void AddTaskFromUrl(string url, string downloadDirectory, DownloadStationSettings settings);
void AddTaskFromData(byte[] data, string filename, string downloadDirectory, DownloadStationSettings settings);
IEnumerable<int> GetApiVersion(DownloadStationSettings settings);
}
@ -25,7 +25,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
{
}
public void AddTorrentFromData(byte[] torrentData, string filename, string downloadDirectory, DownloadStationSettings settings)
public void AddTaskFromData(byte[] data, string filename, string downloadDirectory, DownloadStationSettings settings)
{
var arguments = new Dictionary<string, object>
{
@ -39,19 +39,19 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
arguments.Add("destination", downloadDirectory);
}
arguments.Add("file", new Dictionary<string, object>() { { "name", filename }, { "data", torrentData } });
arguments.Add("file", new Dictionary<string, object>() { { "name", filename }, { "data", data } });
var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"add torrent from data {filename}", HttpMethod.POST);
var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"add task from data {filename}", HttpMethod.POST);
}
public void AddTorrentFromUrl(string torrentUrl, string downloadDirectory, DownloadStationSettings settings)
public void AddTaskFromUrl(string url, string downloadDirectory, DownloadStationSettings settings)
{
var arguments = new Dictionary<string, object>
{
{ "api", "SYNO.DownloadStation.Task" },
{ "version", "3" },
{ "method", "create" },
{ "uri", torrentUrl }
{ "uri", url }
};
if (downloadDirectory.IsNotNullOrWhiteSpace())
@ -59,10 +59,10 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
arguments.Add("destination", downloadDirectory);
}
var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"add torrent from url {torrentUrl}", HttpMethod.GET);
var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"add task from url {url}");
}
public IEnumerable<DownloadStationTorrent> GetTorrents(DownloadStationSettings settings)
public IEnumerable<DownloadStationTask> GetTasks(DownloadStationSettings settings)
{
var arguments = new Dictionary<string, object>
{
@ -74,14 +74,14 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
try
{
var response = ProcessRequest<DownloadStationTaskInfoResponse>(DiskStationApi.DownloadStationTask, arguments, settings, "get torrents");
var response = ProcessRequest<DownloadStationTaskInfoResponse>(DiskStationApi.DownloadStationTask, arguments, settings, "get tasks");
return response.Data.Tasks.Where(t => t.Type == DownloadStationTaskType.BT);
return response.Data.Tasks;
}
catch (DownloadClientException e)
{
_logger.Error(e);
return new List<DownloadStationTorrent>();
return new List<DownloadStationTask>();
}
}
@ -99,7 +99,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
return response.Data;
}
public void RemoveTorrent(string downloadId, DownloadStationSettings settings)
public void RemoveTask(string downloadId, DownloadStationSettings settings)
{
var arguments = new Dictionary<string, object>
{
@ -111,7 +111,6 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
};
var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"remove item {downloadId}");
_logger.Trace("Item {0} removed from Download Station", downloadId);
}
public IEnumerable<int> GetApiVersion(DownloadStationSettings settings)
@ -119,4 +118,4 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
return base.GetApiVersion(settings, DiskStationApi.DownloadStationInfo);
}
}
}
}

@ -52,4 +52,4 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
return response.Data.Files.First();
}
}
}
}

@ -83,16 +83,23 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
{
return AuthMessages[Code];
}
if (api == DiskStationApi.DownloadStationTask && DownloadStationTaskMessages.ContainsKey(Code))
{
return DownloadStationTaskMessages[Code];
}
if (api == DiskStationApi.FileStationList && FileStationMessages.ContainsKey(Code))
{
return FileStationMessages[Code];
}
return CommonMessages[Code];
if (CommonMessages.ContainsKey(Code))
{
return CommonMessages[Code];
}
return $"{ Code } - Unknown error";
}
}
}

@ -5,7 +5,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses
public class DownloadStationTaskInfoResponse
{
public int Offset { get; set; }
public List<DownloadStationTorrent> Tasks {get;set;}
public List<DownloadStationTask> Tasks {get;set;}
public int Total { get; set; }
}
}

@ -25,30 +25,34 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
protected readonly ISerialNumberProvider _serialNumberProvider;
protected readonly IFileStationProxy _fileStationProxy;
public TorrentDownloadStation(IDownloadStationProxy proxy,
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
Logger logger,
ICacheManager cacheManager,
ISharedFolderResolver sharedFolderResolver,
ISerialNumberProvider serialNumberProvider,
IFileStationProxy fileStationProxy)
public TorrentDownloadStation(ISharedFolderResolver sharedFolderResolver,
ISerialNumberProvider serialNumberProvider,
IFileStationProxy fileStationProxy,
IDownloadStationProxy proxy,
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger)
{
_proxy = proxy;
_fileStationProxy = fileStationProxy;
_sharedFolderResolver = sharedFolderResolver;
_serialNumberProvider = serialNumberProvider;
_fileStationProxy = fileStationProxy;
}
public override string Name => "Download Station";
protected IEnumerable<DownloadStationTask> GetTasks()
{
return _proxy.GetTasks(Settings).Where(v => v.Type == DownloadStationTaskType.BT.ToString());
}
public override IEnumerable<DownloadClientItem> GetItems()
{
var torrents = _proxy.GetTorrents(Settings);
var torrents = GetTasks();
var serialNumber = _serialNumberProvider.GetSerialNumber(Settings);
var items = new List<DownloadClientItem>();
@ -125,11 +129,11 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
DeleteItemData(downloadId);
}
_proxy.RemoveTorrent(ParseDownloadId(downloadId), Settings);
_proxy.RemoveTask(ParseDownloadId(downloadId), Settings);
_logger.Debug("{0} removed correctly", downloadId);
}
protected OsPath GetOutputPath(OsPath outputPath, DownloadStationTorrent torrent, string serialNumber)
protected OsPath GetOutputPath(OsPath outputPath, DownloadStationTask torrent, string serialNumber)
{
var fullPath = _sharedFolderResolver.RemapToFullPath(outputPath, Settings, serialNumber);
@ -154,9 +158,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{
var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings);
_proxy.AddTorrentFromUrl(magnetLink, GetDownloadDirectory(), Settings);
_proxy.AddTaskFromUrl(magnetLink, GetDownloadDirectory(), Settings);
var item = _proxy.GetTorrents(Settings).SingleOrDefault(t => t.Additional.Detail["uri"] == magnetLink);
var item = GetTasks().SingleOrDefault(t => t.Additional.Detail["uri"] == magnetLink);
if (item != null)
{
@ -173,9 +177,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{
var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings);
_proxy.AddTorrentFromData(fileContent, filename, GetDownloadDirectory(), Settings);
_proxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings);
var items = _proxy.GetTorrents(Settings).Where(t => t.Additional.Detail["uri"] == Path.GetFileNameWithoutExtension(filename));
var items = GetTasks().Where(t => t.Additional.Detail["uri"] == Path.GetFileNameWithoutExtension(filename));
var item = items.SingleOrDefault();
@ -195,16 +199,15 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
failures.AddIfNotNull(TestConnection());
if (failures.Any()) return;
failures.AddIfNotNull(TestOutputPath());
if (failures.Any()) return;
failures.AddIfNotNull(TestGetTorrents());
}
protected bool IsFinished(DownloadStationTorrent torrent)
protected bool IsFinished(DownloadStationTask torrent)
{
return torrent.Status == DownloadStationTaskStatus.Finished;
}
protected string GetMessage(DownloadStationTorrent torrent)
protected string GetMessage(DownloadStationTask torrent)
{
if (torrent.StatusExtra != null)
{
@ -222,7 +225,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
return null;
}
protected DownloadItemStatus GetStatus(DownloadStationTorrent torrent)
protected DownloadItemStatus GetStatus(DownloadStationTask torrent)
{
switch (torrent.Status)
{
@ -240,7 +243,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
return DownloadItemStatus.Downloading;
}
protected long GetRemainingSize(DownloadStationTorrent torrent)
protected long GetRemainingSize(DownloadStationTask torrent)
{
var downloadedString = torrent.Additional.Transfer["size_downloaded"];
long downloadedSize;
@ -254,7 +257,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
return torrent.Size - Math.Max(0, downloadedSize);
}
protected TimeSpan? GetRemainingTime(DownloadStationTorrent torrent)
protected TimeSpan? GetRemainingTime(DownloadStationTask torrent)
{
var speedString = torrent.Additional.Transfer["speed_download"];
long downloadSpeed;
@ -279,18 +282,38 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{
try
{
var folderInfo = _fileStationProxy.GetInfoFileOrDirectory($"/{GetDownloadDirectory()}", Settings);
var downloadDir = GetDownloadDirectory();
if (!folderInfo.IsDir || folderInfo.Additional == null)
if (downloadDir != null)
{
throw new Exception($"{folderInfo.Path} is not a shared folder or it doesn't exist");
var sharedFolder = downloadDir.Split('\\', '/')[0];
var fieldName = Settings.TvDirectory.IsNotNullOrWhiteSpace() ? nameof(Settings.TvDirectory) : nameof(Settings.TvCategory);
var folderInfo = _fileStationProxy.GetInfoFileOrDirectory($"/{downloadDir}", Settings);
if (folderInfo.Additional == null)
{
return new NzbDroneValidationFailure(fieldName, $"Shared folder does not exist")
{
DetailedDescription = $"The DownloadStation does not have a Shared Folder with the name '{sharedFolder}', are you sure you specified it correctly?"
};
}
if (!folderInfo.IsDir)
{
return new NzbDroneValidationFailure(fieldName, $"Folder does not exist")
{
DetailedDescription = $"The folder '{downloadDir}' does not exist, it must be created manually inside the Shared Folder '{sharedFolder}'."
};
}
}
return null;
}
catch (Exception ex)
{
return new NzbDroneValidationFailure(string.Empty, $"Failed to get output path: {ex.Message}");
_logger.Error(ex);
return new NzbDroneValidationFailure(string.Empty, $"Unknown exception: {ex.Message}");
}
}
@ -305,7 +328,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
_logger.Error(ex, ex.Message);
return new NzbDroneValidationFailure("Username", "Authentication failure")
{
DetailedDescription = $"Please verify your username and password. Also verify if the host running Radarr isn't blocked from accessing {Name} by WhiteList limitations in the {Name} configuration."
DetailedDescription = $"Please verify your username and password. Also verify if the host running Sonarr isn't blocked from accessing {Name} by WhiteList limitations in the {Name} configuration."
};
}
catch (WebException ex)
@ -346,7 +369,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{
try
{
_proxy.GetTorrents(Settings);
GetItems();
return null;
}
catch (Exception ex)

@ -0,0 +1,410 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Download.Clients.DownloadStation
{
public class UsenetDownloadStation : UsenetClientBase<DownloadStationSettings>
{
protected readonly IDownloadStationProxy _proxy;
protected readonly ISharedFolderResolver _sharedFolderResolver;
protected readonly ISerialNumberProvider _serialNumberProvider;
protected readonly IFileStationProxy _fileStationProxy;
public UsenetDownloadStation(ISharedFolderResolver sharedFolderResolver,
ISerialNumberProvider serialNumberProvider,
IFileStationProxy fileStationProxy,
IDownloadStationProxy proxy,
IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
Logger logger
)
: base(httpClient, configService, diskProvider, remotePathMappingService, logger)
{
_proxy = proxy;
_fileStationProxy = fileStationProxy;
_sharedFolderResolver = sharedFolderResolver;
_serialNumberProvider = serialNumberProvider;
}
public override string Name => "Download Station";
protected IEnumerable<DownloadStationTask> GetTasks()
{
return _proxy.GetTasks(Settings).Where(v => v.Type == DownloadStationTaskType.NZB.ToString());
}
public override IEnumerable<DownloadClientItem> GetItems()
{
var nzbTasks = GetTasks();
var serialNumber = _serialNumberProvider.GetSerialNumber(Settings);
var items = new List<DownloadClientItem>();
long totalRemainingSize = 0;
long globalSpeed = nzbTasks.Where(t => t.Status == DownloadStationTaskStatus.Downloading)
.Select(GetDownloadSpeed)
.Sum();
foreach (var nzb in nzbTasks)
{
var outputPath = new OsPath($"/{nzb.Additional.Detail["destination"]}");
var taskRemainingSize = GetRemainingSize(nzb);
if (nzb.Status != DownloadStationTaskStatus.Paused)
{
totalRemainingSize += taskRemainingSize;
}
if (Settings.TvDirectory.IsNotNullOrWhiteSpace())
{
if (!new OsPath($"/{Settings.TvDirectory}").Contains(outputPath))
{
continue;
}
}
else if (Settings.TvCategory.IsNotNullOrWhiteSpace())
{
var directories = outputPath.FullPath.Split('\\', '/');
if (!directories.Contains(Settings.TvCategory))
{
continue;
}
}
var item = new DownloadClientItem()
{
Category = Settings.TvCategory,
DownloadClient = Definition.Name,
DownloadId = CreateDownloadId(nzb.Id, serialNumber),
Title = nzb.Title,
TotalSize = nzb.Size,
RemainingSize = taskRemainingSize,
Status = GetStatus(nzb),
Message = GetMessage(nzb),
IsReadOnly = !IsFinished(nzb)
};
if (item.Status != DownloadItemStatus.Paused)
{
item.RemainingTime = GetRemainingTime(totalRemainingSize, globalSpeed);
}
if (item.Status == DownloadItemStatus.Completed || item.Status == DownloadItemStatus.Failed)
{
item.OutputPath = GetOutputPath(outputPath, nzb, serialNumber);
}
items.Add(item);
}
return items;
}
protected OsPath GetOutputPath(OsPath outputPath, DownloadStationTask task, string serialNumber)
{
var fullPath = _sharedFolderResolver.RemapToFullPath(outputPath, Settings, serialNumber);
var remotePath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, fullPath);
var finalPath = remotePath + task.Title;
return finalPath;
}
public override DownloadClientStatus GetStatus()
{
try
{
var path = GetDownloadDirectory();
return new DownloadClientStatus
{
IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost",
OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(path)) }
};
}
catch (DownloadClientException e)
{
_logger.Debug(e, "Failed to get config from Download Station");
throw e;
}
}
public override void RemoveItem(string downloadId, bool deleteData)
{
if (deleteData)
{
DeleteItemData(downloadId);
}
_proxy.RemoveTask(ParseDownloadId(downloadId), Settings);
_logger.Debug("{0} removed correctly", downloadId);
}
protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContent)
{
throw new DownloadClientException("Episodes are not working with Radarr");
}
protected override string AddFromNzbFile(RemoteMovie remoteEpisode, string filename, byte[] fileContent)
{
var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings);
_proxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings);
var items = GetTasks().Where(t => t.Additional.Detail["uri"] == filename);
var item = items.SingleOrDefault();
if (item != null)
{
_logger.Debug("{0} added correctly", remoteEpisode);
return CreateDownloadId(item.Id, hashedSerialNumber);
}
_logger.Debug("No such task {0} in Download Station", filename);
throw new DownloadClientException("Failed to add NZB task to Download Station");
}
protected override void Test(List<ValidationFailure> failures)
{
failures.AddIfNotNull(TestConnection());
if (failures.Any()) return;
failures.AddIfNotNull(TestOutputPath());
failures.AddIfNotNull(TestGetNZB());
}
protected ValidationFailure TestOutputPath()
{
try
{
var downloadDir = GetDownloadDirectory();
if (downloadDir != null)
{
var sharedFolder = downloadDir.Split('\\', '/')[0];
var fieldName = Settings.TvDirectory.IsNotNullOrWhiteSpace() ? nameof(Settings.TvDirectory) : nameof(Settings.TvCategory);
var folderInfo = _fileStationProxy.GetInfoFileOrDirectory($"/{downloadDir}", Settings);
if (folderInfo.Additional == null)
{
return new NzbDroneValidationFailure(fieldName, $"Shared folder does not exist")
{
DetailedDescription = $"The DownloadStation does not have a Shared Folder with the name '{sharedFolder}', are you sure you specified it correctly?"
};
}
if (!folderInfo.IsDir)
{
return new NzbDroneValidationFailure(fieldName, $"Folder does not exist")
{
DetailedDescription = $"The folder '{downloadDir}' does not exist, it must be created manually inside the Shared Folder '{sharedFolder}'."
};
}
}
return null;
}
catch (Exception ex)
{
_logger.Error(ex);
return new NzbDroneValidationFailure(string.Empty, $"Unknown exception: {ex.Message}");
}
}
protected ValidationFailure TestConnection()
{
try
{
return ValidateVersion();
}
catch (DownloadClientAuthenticationException ex)
{
_logger.Error(ex, ex.Message);
return new NzbDroneValidationFailure("Username", "Authentication failure")
{
DetailedDescription = $"Please verify your username and password. Also verify if the host running Sonarr isn't blocked from accessing {Name} by WhiteList limitations in the {Name} configuration."
};
}
catch (WebException ex)
{
_logger.Error(ex);
if (ex.Status == WebExceptionStatus.ConnectFailure)
{
return new NzbDroneValidationFailure("Host", "Unable to connect")
{
DetailedDescription = "Please verify the hostname and port."
};
}
return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message);
}
catch (Exception ex)
{
_logger.Error(ex);
return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message);
}
}
protected ValidationFailure ValidateVersion()
{
var versionRange = _proxy.GetApiVersion(Settings);
_logger.Debug("Download Station api version information: Min {0} - Max {1}", versionRange.Min(), versionRange.Max());
if (!versionRange.Contains(2))
{
return new ValidationFailure(string.Empty, $"Download Station API version not supported, should be at least 2. It supports from {versionRange.Min()} to {versionRange.Max()}");
}
return null;
}
protected bool IsFinished(DownloadStationTask task)
{
return task.Status == DownloadStationTaskStatus.Finished;
}
protected string GetMessage(DownloadStationTask task)
{
if (task.StatusExtra != null)
{
if (task.Status == DownloadStationTaskStatus.Extracting)
{
return $"Extracting: {int.Parse(task.StatusExtra["unzip_progress"])}%";
}
if (task.Status == DownloadStationTaskStatus.Error)
{
return task.StatusExtra["error_detail"];
}
}
return null;
}
protected DownloadItemStatus GetStatus(DownloadStationTask task)
{
switch (task.Status)
{
case DownloadStationTaskStatus.Waiting:
return task.Size == 0 || GetRemainingSize(task) > 0 ? DownloadItemStatus.Queued : DownloadItemStatus.Completed;
case DownloadStationTaskStatus.Paused:
return DownloadItemStatus.Paused;
case DownloadStationTaskStatus.Finished:
case DownloadStationTaskStatus.Seeding:
return DownloadItemStatus.Completed;
case DownloadStationTaskStatus.Error:
return DownloadItemStatus.Failed;
}
return DownloadItemStatus.Downloading;
}
protected long GetRemainingSize(DownloadStationTask task)
{
var downloadedString = task.Additional.Transfer["size_downloaded"];
long downloadedSize;
if (downloadedString.IsNullOrWhiteSpace() || !long.TryParse(downloadedString, out downloadedSize))
{
_logger.Debug("Task {0} has invalid size_downloaded: {1}", task.Title, downloadedString);
downloadedSize = 0;
}
return task.Size - Math.Max(0, downloadedSize);
}
protected long GetDownloadSpeed(DownloadStationTask task)
{
var speedString = task.Additional.Transfer["speed_download"];
long downloadSpeed;
if (speedString.IsNullOrWhiteSpace() || !long.TryParse(speedString, out downloadSpeed))
{
_logger.Debug("Task {0} has invalid speed_download: {1}", task.Title, speedString);
downloadSpeed = 0;
}
return Math.Max(downloadSpeed, 0);
}
protected TimeSpan? GetRemainingTime(long remainingSize, long downloadSpeed)
{
if (downloadSpeed > 0)
{
return TimeSpan.FromSeconds(remainingSize / downloadSpeed);
}
else
{
return null;
}
}
protected ValidationFailure TestGetNZB()
{
try
{
GetItems();
return null;
}
catch (Exception ex)
{
return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of NZBs: " + ex.Message);
}
}
protected string ParseDownloadId(string id)
{
return id.Split(':')[1];
}
protected string CreateDownloadId(string id, string hashedSerialNumber)
{
return $"{hashedSerialNumber}:{id}";
}
protected string GetDefaultDir()
{
var config = _proxy.GetConfig(Settings);
var path = config["default_destination"] as string;
return path;
}
protected string GetDownloadDirectory()
{
if (Settings.TvDirectory.IsNotNullOrWhiteSpace())
{
return Settings.TvDirectory.TrimStart('/');
}
else if (Settings.TvCategory.IsNotNullOrWhiteSpace())
{
var destDir = GetDefaultDir();
return $"{destDir.TrimEnd('/')}/{Settings.TvCategory}";
}
return null;
}
}
}

@ -406,30 +406,16 @@
<Compile Include="DecisionEngine\Specifications\UpgradeDiskSpecification.cs" />
<Compile Include="DiskSpace\DiskSpace.cs" />
<Compile Include="DiskSpace\DiskSpaceService.cs" />
<Compile Include="Download\CheckForFinishedDownloadCommand.cs" />
<Compile Include="Download\Clients\Blackhole\ScanWatchFolder.cs" />
<Compile Include="Download\Clients\Blackhole\WatchFolderItem.cs" />
<Compile Include="Download\Clients\Deluge\Deluge.cs" />
<Compile Include="Download\Clients\Deluge\DelugeError.cs" />
<Compile Include="Download\Clients\Deluge\DelugeException.cs" />
<Compile Include="Download\Clients\Deluge\DelugeProxy.cs" />
<Compile Include="Download\Clients\Deluge\DelugeSettings.cs" />
<Compile Include="Download\Clients\Deluge\DelugeTorrent.cs" />
<Compile Include="Download\Clients\Deluge\DelugeTorrentStatus.cs" />
<Compile Include="Download\Clients\Deluge\DelugePriority.cs" />
<Compile Include="Download\Clients\Deluge\DelugeUpdateUIResult.cs" />
<Compile Include="Download\Clients\DownloadClientAuthenticationException.cs" />
<Compile Include="Download\Clients\DownloadClientException.cs" />
<Compile Include="Download\Clients\DownloadStation\TorrentDownloadStation.cs" />
<Compile Include="Download\Clients\DownloadStation\Proxies\DownloadStationProxy.cs" />
<Compile Include="Download\Clients\DownloadStation\DownloadStationSettings.cs" />
<Compile Include="Download\Clients\DownloadStation\DownloadStationTorrent.cs" />
<Compile Include="Download\Clients\DownloadStation\DownloadStationTorrentAdditional.cs" />
<Compile Include="Download\Clients\DownloadStation\DownloadStationTask.cs" />
<Compile Include="Download\Clients\DownloadStation\DownloadStationTaskAdditional.cs" />
<Compile Include="Download\Clients\DownloadStation\Proxies\DSMInfoProxy.cs" />
<Compile Include="Download\Clients\DownloadStation\Proxies\FileStationProxy.cs" />
<Compile Include="Download\Clients\DownloadStation\Proxies\DiskStationProxyBase.cs" />
<Compile Include="Download\Clients\DownloadStation\Responses\DiskStationAuthResponse.cs" />
<Compile Include="Download\Clients\DownloadStation\DownloadStationTorrentFile.cs" />
<Compile Include="Download\Clients\DownloadStation\DownloadStationTaskFile.cs" />
<Compile Include="Download\Clients\DownloadStation\Responses\DSMInfoResponse.cs" />
<Compile Include="Download\Clients\DownloadStation\Responses\FileStationListFileInfoResponse.cs" />
<Compile Include="Download\Clients\DownloadStation\Responses\FileStationListResponse.cs" />
@ -442,6 +428,21 @@
<Compile Include="Download\Clients\DownloadStation\SharedFolderMapping.cs" />
<Compile Include="Download\Clients\DownloadStation\SharedFolderResolver.cs" />
<Compile Include="Download\Clients\DownloadStation\DiskStationApi.cs" />
<Compile Include="Download\Clients\DownloadStation\UsenetDownloadStation.cs" />
<Compile Include="Download\CheckForFinishedDownloadCommand.cs" />
<Compile Include="Download\Clients\Blackhole\ScanWatchFolder.cs" />
<Compile Include="Download\Clients\Blackhole\WatchFolderItem.cs" />
<Compile Include="Download\Clients\Deluge\Deluge.cs" />
<Compile Include="Download\Clients\Deluge\DelugeError.cs" />
<Compile Include="Download\Clients\Deluge\DelugeException.cs" />
<Compile Include="Download\Clients\Deluge\DelugeProxy.cs" />
<Compile Include="Download\Clients\Deluge\DelugeSettings.cs" />
<Compile Include="Download\Clients\Deluge\DelugeTorrent.cs" />
<Compile Include="Download\Clients\Deluge\DelugeTorrentStatus.cs" />
<Compile Include="Download\Clients\Deluge\DelugePriority.cs" />
<Compile Include="Download\Clients\Deluge\DelugeUpdateUIResult.cs" />
<Compile Include="Download\Clients\DownloadClientAuthenticationException.cs" />
<Compile Include="Download\Clients\DownloadClientException.cs" />
<Compile Include="Download\Clients\Hadouken\Hadouken.cs" />
<Compile Include="Download\Clients\Hadouken\HadoukenProxy.cs" />
<Compile Include="Download\Clients\Hadouken\HadoukenSettings.cs" />

Loading…
Cancel
Save