diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/TorrentDownloadStationFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/TorrentDownloadStationFixture.cs index ed9bc321f..09f445ee1 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/TorrentDownloadStationFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/TorrentDownloadStationFixture.cs @@ -287,6 +287,10 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests Mocker.GetMock() .Setup(v => v.GetConfig(It.IsAny())) .Returns(_downloadStationConfigItems); + + Mocker.GetMock() + .Setup(s => s.GetProxy(It.IsAny())) + .Returns(Mocker.GetMock().Object); } protected void GivenSharedFolder() diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/UsenetDownloadStationFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/UsenetDownloadStationFixture.cs index 14c94e808..3b9e7262b 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/UsenetDownloadStationFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/UsenetDownloadStationFixture.cs @@ -180,6 +180,10 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests Mocker.GetMock() .Setup(v => v.GetConfig(It.IsAny())) .Returns(_downloadStationConfigItems); + + Mocker.GetMock() + .Setup(s => s.GetProxy(It.IsAny())) + .Returns(Mocker.GetMock().Object); } protected void GivenSharedFolder() diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/DiskStationApi.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/DiskStationApi.cs index 8fcefdd51..37ed34b80 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/DiskStationApi.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/DiskStationApi.cs @@ -6,6 +6,7 @@ Auth, DownloadStationInfo, DownloadStationTask, + DownloadStation2Task, FileStationList, DSMInfo, } diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStation2Task.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStation2Task.cs new file mode 100644 index 000000000..1240f2c87 --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStation2Task.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using NzbDrone.Common.Serializer; + +namespace NzbDrone.Core.Download.Clients.DownloadStation +{ + public class DownloadStation2Task + { + public string Username { get; set; } + + public string Id { get; set; } + + public string Title { get; set; } + + public long Size { get; set; } + + /// + /// /// Possible values are: BT, NZB, http, ftp, eMule and https + /// + public string Type { get; set; } + + public int Status { get; set; } + + public DownloadStationTaskAdditional Additional { get; set; } + + public override string ToString() + { + return this.Title; + } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DiskStationProxyBase.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DiskStationProxyBase.cs index fb132641a..e8271618c 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DiskStationProxyBase.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DiskStationProxyBase.cs @@ -166,7 +166,14 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies { if (apiInfo.NeedsAuthentication) { - requestBuilder.AddFormParameter("_sid", _sessionCache.Get(GenerateSessionCacheKey(settings), () => AuthenticateClient(settings), TimeSpan.FromHours(6))); + if (_apiType == DiskStationApi.DownloadStation2Task) + { + requestBuilder.AddQueryParam("_sid", _sessionCache.Get(GenerateSessionCacheKey(settings), () => AuthenticateClient(settings), TimeSpan.FromHours(6))); + } + else + { + requestBuilder.AddFormParameter("_sid", _sessionCache.Get(GenerateSessionCacheKey(settings), () => AuthenticateClient(settings), TimeSpan.FromHours(6))); + } } requestBuilder.AddFormParameter("api", apiInfo.Name); @@ -236,7 +243,14 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies if (info == null) { - throw new DownloadClientException("Info of {0} not found on {1}:{2}", api, settings.Host, settings.Port); + if (api == DiskStationApi.DownloadStation2Task) + { + _logger.Warn("Info of {0} not found on {1}:{2}", api, settings.Host, settings.Port); + } + else + { + throw new DownloadClientException("Info of {0} not found on {1}:{2}", api, settings.Host, settings.Port); + } } } diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxySelector.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxySelector.cs new file mode 100644 index 000000000..7a9531ccd --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxySelector.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using NLog; +using NzbDrone.Common.Cache; +using NzbDrone.Common.Http; + +namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies +{ + public interface IDownloadStationTaskProxy : IDiskStationProxy + { + bool IsApiSupported(DownloadStationSettings settings); + IEnumerable GetTasks(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); + } + + public interface IDownloadStationTaskProxySelector + { + IDownloadStationTaskProxy GetProxy(DownloadStationSettings settings); + } + + public class DownloadStationTaskProxySelector : IDownloadStationTaskProxySelector + { + private readonly ICached _proxyCache; + private readonly Logger _logger; + + private readonly IDownloadStationTaskProxy _proxyV1; + private readonly IDownloadStationTaskProxy _proxyV2; + + public DownloadStationTaskProxySelector(DownloadStationTaskProxyV1 proxyV1, DownloadStationTaskProxyV2 proxyV2, ICacheManager cacheManager, Logger logger) + { + _proxyCache = cacheManager.GetCache(GetType(), "taskProxy"); + _logger = logger; + + _proxyV1 = proxyV1; + _proxyV2 = proxyV2; + } + + public IDownloadStationTaskProxy GetProxy(DownloadStationSettings settings) + { + return GetProxyCache(settings); + } + + private IDownloadStationTaskProxy GetProxyCache(DownloadStationSettings settings) + { + var propKey = $"{settings.Host}_{settings.Port}"; + + return _proxyCache.Get(propKey, () => FetchProxy(settings), TimeSpan.FromMinutes(10.0)); + } + + private IDownloadStationTaskProxy FetchProxy(DownloadStationSettings settings) + { + if (_proxyV2.IsApiSupported(settings)) + { + _logger.Trace("Using DownloadStation Task API v2"); + return _proxyV2; + } + + if (_proxyV1.IsApiSupported(settings)) + { + _logger.Trace("Using DownloadStation Task API v1"); + return _proxyV1; + } + + throw new DownloadClientException("Unable to determine DownloadStations Task API version"); + } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxy.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxyV1.cs similarity index 79% rename from src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxy.cs rename to src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxyV1.cs index 1e6849dac..8180a70ba 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxyV1.cs @@ -7,21 +7,18 @@ using NzbDrone.Core.Download.Clients.DownloadStation.Responses; namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies { - public interface IDownloadStationTaskProxy : IDiskStationProxy + public class DownloadStationTaskProxyV1 : DiskStationProxyBase, IDownloadStationTaskProxy { - IEnumerable GetTasks(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); - } - - public class DownloadStationTaskProxy : DiskStationProxyBase, IDownloadStationTaskProxy - { - public DownloadStationTaskProxy(IHttpClient httpClient, ICacheManager cacheManager, Logger logger) + public DownloadStationTaskProxyV1(IHttpClient httpClient, ICacheManager cacheManager, Logger logger) : base(DiskStationApi.DownloadStationTask, "SYNO.DownloadStation.Task", httpClient, cacheManager, logger) { } + public bool IsApiSupported(DownloadStationSettings settings) + { + return GetApiInfo(settings) != null; + } + public void AddTaskFromData(byte[] data, string filename, string downloadDirectory, DownloadStationSettings settings) { var requestBuilder = BuildRequest(settings, "create", 2, HttpMethod.POST); diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxyV2.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxyV2.cs new file mode 100644 index 000000000..e8a7aaa0b --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxyV2.cs @@ -0,0 +1,117 @@ +using System.Collections.Generic; +using System.Linq; +using NLog; +using NzbDrone.Common.Cache; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; +using NzbDrone.Core.Download.Clients.DownloadStation.Responses; + +namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies +{ + public class DownloadStationTaskProxyV2 : DiskStationProxyBase, IDownloadStationTaskProxy + { + public DownloadStationTaskProxyV2(IHttpClient httpClient, ICacheManager cacheManager, Logger logger) + : base(DiskStationApi.DownloadStation2Task, "SYNO.DownloadStation2.Task", httpClient, cacheManager, logger) + { + } + + public bool IsApiSupported(DownloadStationSettings settings) + { + return GetApiInfo(settings) != null; + } + + public void AddTaskFromData(byte[] data, string filename, string downloadDirectory, DownloadStationSettings settings) + { + var requestBuilder = BuildRequest(settings, "create", 2, HttpMethod.POST); + + requestBuilder.AddFormParameter("type", "\"file\""); + requestBuilder.AddFormParameter("file", "[\"fileData\"]"); + requestBuilder.AddFormParameter("create_list", "false"); + + if (downloadDirectory.IsNotNullOrWhiteSpace()) + { + requestBuilder.AddFormParameter("destination", $"\"{downloadDirectory}\""); + } + + requestBuilder.AddFormUpload("fileData", filename, data); + + ProcessRequest(requestBuilder, $"add task from data {filename}", settings); + } + + public void AddTaskFromUrl(string url, string downloadDirectory, DownloadStationSettings settings) + { + var requestBuilder = BuildRequest(settings, "create", 2); + + requestBuilder.AddQueryParam("type", "url"); + requestBuilder.AddQueryParam("url", url); + requestBuilder.AddQueryParam("create_list", "false"); + + if (downloadDirectory.IsNotNullOrWhiteSpace()) + { + requestBuilder.AddQueryParam("destination", downloadDirectory); + } + + ProcessRequest(requestBuilder, $"add task from url {url}", settings); + } + + public IEnumerable GetTasks(DownloadStationSettings settings) + { + try + { + var result = new List(); + + var requestBuilder = BuildRequest(settings, "list", 1); + requestBuilder.AddQueryParam("additional", "detail"); + + var response = ProcessRequest(requestBuilder, "get tasks with additional detail", settings); + + if (response.Success && response.Data.Total > 0) + { + requestBuilder.AddQueryParam("additional", "transfer"); + var responseTransfer = ProcessRequest(requestBuilder, "get tasks with additional transfer", settings); + + if (responseTransfer.Success) + { + foreach(var task in response.Data.Task) + { + var taskTransfer = responseTransfer.Data.Task.Where(t => t.Id == task.Id).First(); + + var combinedTask = new DownloadStationTask + { + Username = task.Username, + Id = task.Id, + Title = task.Title, + Size = task.Size, + Status = (DownloadStationTaskStatus)task.Status, + Type = task.Type, + Additional = new DownloadStationTaskAdditional + { + Detail = task.Additional.Detail, + Transfer = taskTransfer.Additional.Transfer + } + }; + + result.Add(combinedTask); + } + } + } + + return result; + } + catch (DownloadClientException e) + { + _logger.Error(e); + return new List(); + } + } + + public void RemoveTask(string downloadId, DownloadStationSettings settings) + { + var requestBuilder = BuildRequest(settings, "delete", 2); + requestBuilder.AddQueryParam("id", downloadId); + requestBuilder.AddQueryParam("force_complete", "false"); + + ProcessRequest(requestBuilder, $"remove item {downloadId}", settings); + } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/Responses/DiskStationError.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/Responses/DiskStationError.cs index edb82465b..35b75cbec 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/Responses/DiskStationError.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/Responses/DiskStationError.cs @@ -85,7 +85,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses return AuthMessages[Code]; } - if (api == DiskStationApi.DownloadStationTask && DownloadStationTaskMessages.ContainsKey(Code)) + if ((api == DiskStationApi.DownloadStationTask || api == DiskStationApi.DownloadStation2Task) && DownloadStationTaskMessages.ContainsKey(Code)) { return DownloadStationTaskMessages[Code]; } diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/Responses/DownloadStation2TaskInfoResponse.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/Responses/DownloadStation2TaskInfoResponse.cs new file mode 100644 index 000000000..d8d6b2e33 --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/Responses/DownloadStation2TaskInfoResponse.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses +{ + public class DownloadStation2TaskInfoResponse + { + public int Offset { get; set; } + public List Task {get;set;} + public int Total { get; set; } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs index 2e4bfa067..a95549b15 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs @@ -21,7 +21,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation public class TorrentDownloadStation : TorrentClientBase { protected readonly IDownloadStationInfoProxy _dsInfoProxy; - protected readonly IDownloadStationTaskProxy _dsTaskProxy; + protected readonly IDownloadStationTaskProxySelector _dsTaskProxySelector; protected readonly ISharedFolderResolver _sharedFolderResolver; protected readonly ISerialNumberProvider _serialNumberProvider; protected readonly IFileStationProxy _fileStationProxy; @@ -30,7 +30,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation ISerialNumberProvider serialNumberProvider, IFileStationProxy fileStationProxy, IDownloadStationInfoProxy dsInfoProxy, - IDownloadStationTaskProxy dsTaskProxy, + IDownloadStationTaskProxySelector dsTaskProxySelector, ITorrentFileInfoReader torrentFileInfoReader, IHttpClient httpClient, IConfigService configService, @@ -40,7 +40,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) { _dsInfoProxy = dsInfoProxy; - _dsTaskProxy = dsTaskProxy; + _dsTaskProxySelector = dsTaskProxySelector; _fileStationProxy = fileStationProxy; _sharedFolderResolver = sharedFolderResolver; _serialNumberProvider = serialNumberProvider; @@ -50,9 +50,11 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation public override ProviderMessage Message => new ProviderMessage("Sonarr is unable to connect to Download Station if 2-Factor Authentication is enabled on your DSM account", ProviderMessageType.Warning); + private IDownloadStationTaskProxy DsTaskProxy => _dsTaskProxySelector.GetProxy(Settings); + protected IEnumerable GetTasks() { - return _dsTaskProxy.GetTasks(Settings).Where(v => v.Type.ToLower() == DownloadStationTaskType.BT.ToString().ToLower()); + return DsTaskProxy.GetTasks(Settings).Where(v => v.Type.ToLower() == DownloadStationTaskType.BT.ToString().ToLower()); } public override IEnumerable GetItems() @@ -136,7 +138,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation DeleteItemData(item); } - _dsTaskProxy.RemoveTask(ParseDownloadId(item.DownloadId), Settings); + DsTaskProxy.RemoveTask(ParseDownloadId(item.DownloadId), Settings); _logger.Debug("{0} removed correctly", item.DownloadId); } @@ -155,7 +157,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation { var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings); - _dsTaskProxy.AddTaskFromUrl(magnetLink, GetDownloadDirectory(), Settings); + DsTaskProxy.AddTaskFromUrl(magnetLink, GetDownloadDirectory(), Settings); var item = GetTasks().SingleOrDefault(t => t.Additional.Detail["uri"] == magnetLink); @@ -174,7 +176,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation { var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings); - _dsTaskProxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings); + DsTaskProxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings); var items = GetTasks().Where(t => t.Additional.Detail["uri"] == Path.GetFileNameWithoutExtension(filename)); @@ -389,7 +391,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation protected ValidationFailure ValidateVersion() { - var info = _dsTaskProxy.GetApiInfo(Settings); + var info = DsTaskProxy.GetApiInfo(Settings); _logger.Debug("Download Station api version information: Min {0} - Max {1}", info.MinVersion, info.MaxVersion); diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs index fa470562b..6466c406c 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs @@ -19,7 +19,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation public class UsenetDownloadStation : UsenetClientBase { protected readonly IDownloadStationInfoProxy _dsInfoProxy; - protected readonly IDownloadStationTaskProxy _dsTaskProxy; + protected readonly IDownloadStationTaskProxySelector _dsTaskProxySelector; protected readonly ISharedFolderResolver _sharedFolderResolver; protected readonly ISerialNumberProvider _serialNumberProvider; protected readonly IFileStationProxy _fileStationProxy; @@ -28,7 +28,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation ISerialNumberProvider serialNumberProvider, IFileStationProxy fileStationProxy, IDownloadStationInfoProxy dsInfoProxy, - IDownloadStationTaskProxy dsTaskProxy, + IDownloadStationTaskProxySelector dsTaskProxySelector, IHttpClient httpClient, IConfigService configService, IDiskProvider diskProvider, @@ -39,7 +39,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger) { _dsInfoProxy = dsInfoProxy; - _dsTaskProxy = dsTaskProxy; + _dsTaskProxySelector = dsTaskProxySelector; _fileStationProxy = fileStationProxy; _sharedFolderResolver = sharedFolderResolver; _serialNumberProvider = serialNumberProvider; @@ -49,9 +49,11 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation public override ProviderMessage Message => new ProviderMessage("Sonarr is unable to connect to Download Station if 2-Factor Authentication is enabled on your DSM account", ProviderMessageType.Warning); + private IDownloadStationTaskProxy DsTaskProxy => _dsTaskProxySelector.GetProxy(Settings); + protected IEnumerable GetTasks() { - return _dsTaskProxy.GetTasks(Settings).Where(v => v.Type.ToLower() == DownloadStationTaskType.NZB.ToString().ToLower()); + return DsTaskProxy.GetTasks(Settings).Where(v => v.Type.ToLower() == DownloadStationTaskType.NZB.ToString().ToLower()); } public override IEnumerable GetItems() @@ -161,7 +163,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation DeleteItemData(item); } - _dsTaskProxy.RemoveTask(ParseDownloadId(item.DownloadId), Settings); + DsTaskProxy.RemoveTask(ParseDownloadId(item.DownloadId), Settings); _logger.Debug("{0} removed correctly", item.DownloadId); } @@ -169,7 +171,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation { var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings); - _dsTaskProxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings); + DsTaskProxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings); var items = GetTasks().Where(t => t.Additional.Detail["uri"] == filename); @@ -288,7 +290,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation protected ValidationFailure ValidateVersion() { - var info = _dsTaskProxy.GetApiInfo(Settings); + var info = DsTaskProxy.GetApiInfo(Settings); _logger.Debug("Download Station api version information: Min {0} - Max {1}", info.MinVersion, info.MaxVersion);