From 5033886b90d749896bbe73e8b64d1b70368283c0 Mon Sep 17 00:00:00 2001 From: margaale Date: Tue, 7 Mar 2017 16:43:58 -0300 Subject: [PATCH] Fixed: DownloadStation api client for DSM 5.x. --- .../TorrentDownloadStationFixture.cs | 18 +- .../UsenetDownloadStationFixture.cs | 16 +- .../DownloadStation/DiskStationApiInfo.cs | 8 +- .../DownloadStation/Proxies/DSMInfoProxy.cs | 16 +- .../Proxies/DiskStationProxyBase.cs | 268 +++++++++--------- .../Proxies/DownloadStationInfoProxy.cs | 29 ++ .../Proxies/DownloadStationProxy.cs | 121 -------- .../Proxies/DownloadStationTaskProxy.cs | 79 ++++++ .../Proxies/FileStationProxy.cs | 29 +- .../DownloadStation/TorrentDownloadStation.cs | 28 +- .../DownloadStation/UsenetDownloadStation.cs | 25 +- src/NzbDrone.Core/NzbDrone.Core.csproj | 3 +- 12 files changed, 322 insertions(+), 318 deletions(-) create mode 100644 src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationInfoProxy.cs delete mode 100644 src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationProxy.cs create mode 100644 src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxy.cs diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/TorrentDownloadStationFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/TorrentDownloadStationFixture.cs index d48b29e11..8269acda6 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/TorrentDownloadStationFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/TorrentDownloadStationFixture.cs @@ -275,7 +275,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests { "default_destination", _defaultDestination }, }; - Mocker.GetMock() + Mocker.GetMock() .Setup(v => v.GetConfig(It.IsAny())) .Returns(_downloadStationConfigItems); } @@ -311,7 +311,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests torrents = new List(); } - Mocker.GetMock() + Mocker.GetMock() .Setup(s => s.GetTasks(It.IsAny())) .Returns(torrents); } @@ -330,11 +330,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests .Setup(s => s.Get(It.IsAny())) .Returns(r => new HttpResponse(r, new HttpHeader(), new byte[1000])); - Mocker.GetMock() + Mocker.GetMock() .Setup(s => s.AddTaskFromUrl(It.IsAny(), It.IsAny(), It.IsAny())) .Callback(PrepareClientToReturnQueuedItem); - Mocker.GetMock() + Mocker.GetMock() .Setup(s => s.AddTaskFromData(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback(PrepareClientToReturnQueuedItem); } @@ -352,7 +352,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests { var tasks = new List() { _queued, _completed, _failed, _downloading, _seeding }; - Mocker.GetMock() + Mocker.GetMock() .Setup(d => d.GetTasks(_settings)) .Returns(tasks); @@ -372,7 +372,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests id.Should().NotBeNullOrEmpty(); - Mocker.GetMock() + Mocker.GetMock() .Verify(v => v.AddTaskFromUrl(It.IsAny(), _tvDirectory, It.IsAny()), Times.Once()); } @@ -389,7 +389,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests id.Should().NotBeNullOrEmpty(); - Mocker.GetMock() + Mocker.GetMock() .Verify(v => v.AddTaskFromUrl(It.IsAny(), $"{_defaultDestination}/{_category}", It.IsAny()), Times.Once()); } @@ -405,7 +405,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests id.Should().NotBeNullOrEmpty(); - Mocker.GetMock() + Mocker.GetMock() .Verify(v => v.AddTaskFromUrl(It.IsAny(), null, It.IsAny()), Times.Once()); } @@ -482,7 +482,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests Assert.Throws(Is.InstanceOf(), () => Subject.Download(remoteEpisode)); - Mocker.GetMock() + Mocker.GetMock() .Verify(v => v.AddTaskFromUrl(It.IsAny(), null, _settings), Times.Never()); } diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/UsenetDownloadStationFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/UsenetDownloadStationFixture.cs index 2e44c60ba..48df65841 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/UsenetDownloadStationFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/UsenetDownloadStationFixture.cs @@ -177,7 +177,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests { "default_destination", _defaultDestination }, }; - Mocker.GetMock() + Mocker.GetMock() .Setup(v => v.GetConfig(It.IsAny())) .Returns(_downloadStationConfigItems); } @@ -213,7 +213,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests nzbs = new List(); } - Mocker.GetMock() + Mocker.GetMock() .Setup(s => s.GetTasks(It.IsAny())) .Returns(nzbs); } @@ -233,7 +233,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests .Returns(r => new HttpResponse(r, new HttpHeader(), new byte[1000])); */ - Mocker.GetMock() + Mocker.GetMock() .Setup(s => s.AddTaskFromData(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback(PrepareClientToReturnQueuedItem); } @@ -242,7 +242,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests { var tasks = new List() { _queued, _completed, _failed, _downloading, _seeding }; - Mocker.GetMock() + Mocker.GetMock() .Setup(d => d.GetTasks(_settings)) .Returns(tasks); } @@ -260,7 +260,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests id.Should().NotBeNullOrEmpty(); - Mocker.GetMock() + Mocker.GetMock() .Verify(v => v.AddTaskFromData(It.IsAny(), It.IsAny(), _tvDirectory, It.IsAny()), Times.Once()); } @@ -277,7 +277,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests id.Should().NotBeNullOrEmpty(); - Mocker.GetMock() + Mocker.GetMock() .Verify(v => v.AddTaskFromData(It.IsAny(), It.IsAny(), $"{_defaultDestination}/{_category}", It.IsAny()), Times.Once()); } @@ -293,7 +293,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests id.Should().NotBeNullOrEmpty(); - Mocker.GetMock() + Mocker.GetMock() .Verify(v => v.AddTaskFromData(It.IsAny(), It.IsAny(), null, It.IsAny()), Times.Once()); } @@ -370,7 +370,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests Assert.Throws(Is.InstanceOf(), () => Subject.Download(remoteEpisode)); - Mocker.GetMock() + Mocker.GetMock() .Verify(v => v.AddTaskFromUrl(It.IsAny(), null, _settings), Times.Never()); } diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/DiskStationApiInfo.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/DiskStationApiInfo.cs index c222a258d..c2a6667ab 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/DiskStationApiInfo.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/DiskStationApiInfo.cs @@ -1,13 +1,19 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation { public class DiskStationApiInfo - { + { private string _path; public int MaxVersion { get; set; } public int MinVersion { get; set; } + public DiskStationApi Type { get; set; } + + public string Name { get; set; } + + public bool NeedsAuthentication { get; set; } + public string Path { get { return _path; } diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DSMInfoProxy.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DSMInfoProxy.cs index b5687a0e0..9c18d5702 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DSMInfoProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DSMInfoProxy.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using NLog; +using NzbDrone.Common.Cache; using NzbDrone.Common.Http; using NzbDrone.Core.Download.Clients.DownloadStation.Responses; @@ -13,20 +14,19 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies public class DSMInfoProxy : DiskStationProxyBase, IDSMInfoProxy { - public DSMInfoProxy(IHttpClient httpClient, Logger logger) : - base(httpClient, logger) + public DSMInfoProxy(IHttpClient httpClient, ICacheManager cacheManager, Logger logger) : + base(DiskStationApi.DSMInfo, "SYNO.DSM.Info", httpClient, cacheManager, logger) { } public string GetSerialNumber(DownloadStationSettings settings) { - var arguments = new Dictionary() { - { "api", "SYNO.DSM.Info" }, - { "version", "2" }, - { "method", "getinfo" } - }; + var info = GetApiInfo(settings); + + var requestBuilder = BuildRequest(settings, "getinfo", info.MinVersion); + + var response = ProcessRequest(requestBuilder, "get serial number", settings); - var response = ProcessRequest(DiskStationApi.DSMInfo, arguments, settings, "get serial number"); return response.Data.SerialNumber; } } diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DiskStationProxyBase.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DiskStationProxyBase.cs index edaa2ebf6..2c677a713 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DiskStationProxyBase.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DiskStationProxyBase.cs @@ -1,63 +1,77 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Net; using NLog; +using NzbDrone.Common.Cache; using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; using NzbDrone.Core.Download.Clients.DownloadStation.Responses; namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies { - public abstract class DiskStationProxyBase + public interface IDiskStationProxy { - private static readonly Dictionary Resources; + DiskStationApiInfo GetApiInfo(DownloadStationSettings settings); + } + + public abstract class DiskStationProxyBase : IDiskStationProxy + { + protected readonly Logger _logger; private readonly IHttpClient _httpClient; - protected readonly Logger _logger; - private bool _authenticated; + private readonly ICached _infoCache; + private readonly ICached _sessionCache; + private readonly DiskStationApi _apiType; + private readonly string _apiName; + + private static readonly DiskStationApiInfo _apiInfo; static DiskStationProxyBase() { - Resources = new Dictionary + _apiInfo = new DiskStationApiInfo() { - { DiskStationApi.Info, "query.cgi" } + Type = DiskStationApi.Info, + Name = "SYNO.API.Info", + Path = "query.cgi", + MaxVersion = 1, + MinVersion = 1, + NeedsAuthentication = false }; } - public DiskStationProxyBase(IHttpClient httpClient, Logger logger) + public DiskStationProxyBase(DiskStationApi apiType, + string apiName, + IHttpClient httpClient, + ICacheManager cacheManager, + Logger logger) { _httpClient = httpClient; _logger = logger; + _infoCache = cacheManager.GetCache(typeof(DiskStationProxyBase), "apiInfo"); + _sessionCache = cacheManager.GetCache(typeof(DiskStationProxyBase), "sessions"); + _apiType = apiType; + _apiName = apiName; } - - protected DiskStationResponse ProcessRequest(DiskStationApi api, - Dictionary arguments, - DownloadStationSettings settings, - string operation, - HttpMethod method = HttpMethod.GET) + private string GenerateSessionCacheKey(DownloadStationSettings settings) { - return ProcessRequest(api, arguments, settings, operation, method); + return $"{settings.Username}@{settings.Host}:{settings.Port}"; } - protected DiskStationResponse ProcessRequest(DiskStationApi api, - Dictionary arguments, - DownloadStationSettings settings, - string operation, - HttpMethod method = HttpMethod.GET, - int retries = 0) where T : new() + protected DiskStationResponse ProcessRequest(HttpRequestBuilder requestBuilder, + string operation, + DownloadStationSettings settings) where T : new() { - if (retries == 5) - { - throw new DownloadClientException("Try to process request to {0} with {1} more than 5 times", api, arguments.ToJson().ToString()); - } + return ProcessRequest(requestBuilder, operation, _apiType, settings); + } - if (!_authenticated && api != DiskStationApi.Info && api != DiskStationApi.DSMInfo) - { - AuthenticateClient(settings); - } - - var request = BuildRequest(settings, api, arguments, method); + private DiskStationResponse ProcessRequest(HttpRequestBuilder requestBuilder, + string operation, + DiskStationApi api, + DownloadStationSettings settings) where T : new() + { + var request = requestBuilder.Build(); var response = _httpClient.Execute(request); _logger.Debug("Trying to {0}", operation); @@ -77,16 +91,14 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies if (responseContent.Error.SessionError) { - _authenticated = false; + _sessionCache.Remove(GenerateSessionCacheKey(settings)); if (responseContent.Error.Code == 105) { throw new DownloadClientAuthenticationException(msg); } - - return ProcessRequest(api, arguments, settings, operation, method, ++retries); } - + throw new DownloadClientException(msg); } } @@ -96,124 +108,126 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies } } - private void AuthenticateClient(DownloadStationSettings settings) + private string AuthenticateClient(DownloadStationSettings settings) { - var arguments = new Dictionary - { - { "api", "SYNO.API.Auth" }, - { "version", "1" }, - { "method", "login" }, - { "account", settings.Username }, - { "passwd", settings.Password }, - { "format", "cookie" }, - { "session", "DownloadStation" }, - }; + var authInfo = GetApiInfo(DiskStationApi.Auth, settings); - var authLoginRequest = BuildRequest(settings, DiskStationApi.Auth, arguments, HttpMethod.GET); - authLoginRequest.StoreResponseCookie = true; + var requestBuilder = BuildRequest(settings, authInfo, "login", 2); + requestBuilder.AddQueryParam("account", settings.Username); + requestBuilder.AddQueryParam("passwd", settings.Password); + requestBuilder.AddQueryParam("format", "sid"); + requestBuilder.AddQueryParam("session", Guid.NewGuid().ToString()); - var response = _httpClient.Execute(authLoginRequest); + var authResponse = ProcessRequest(requestBuilder, "login", DiskStationApi.Auth, settings); - var downloadStationResponse = Json.Deserialize>(response.Content); - - var authResponse = Json.Deserialize>(response.Content); - - _authenticated = authResponse.Success; - - if (!_authenticated) - { - throw new DownloadClientAuthenticationException(downloadStationResponse.Error.GetMessage(DiskStationApi.Auth)); - } + return authResponse.Data.SId; } - private HttpRequest BuildRequest(DownloadStationSettings settings, DiskStationApi api, Dictionary arguments, HttpMethod method) + protected HttpRequestBuilder BuildRequest(DownloadStationSettings settings, string methodName, int apiVersion, HttpMethod httpVerb = HttpMethod.GET) { - if (!Resources.ContainsKey(api)) - { - GetApiVersion(settings, api); - } + var info = GetApiInfo(_apiType, settings); - var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port).Resource($"webapi/{Resources[api]}"); - requestBuilder.Method = method; + return BuildRequest(settings, info, methodName, apiVersion, httpVerb); + } + + private HttpRequestBuilder BuildRequest(DownloadStationSettings settings, DiskStationApiInfo apiInfo, string methodName, int apiVersion, HttpMethod httpVerb = HttpMethod.GET) + { + var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port).Resource($"webapi/{apiInfo.Path}"); + requestBuilder.Method = httpVerb; requestBuilder.LogResponseContent = true; requestBuilder.SuppressHttpError = true; requestBuilder.AllowAutoRedirect = false; + requestBuilder.Headers.ContentType = "application/json"; - if (requestBuilder.Method == HttpMethod.POST) + if (apiVersion < apiInfo.MinVersion || apiVersion > apiInfo.MaxVersion) { - if (api == DiskStationApi.DownloadStationTask && arguments.ContainsKey("file")) - { - requestBuilder.Headers.ContentType = "multipart/form-data"; + throw new ArgumentOutOfRangeException(nameof(apiVersion)); + } - foreach (var arg in arguments) - { - if (arg.Key == "file") - { - Dictionary file = (Dictionary)arg.Value; - requestBuilder.AddFormUpload(arg.Key, file["name"].ToString(), (byte[])file["data"]); - } - else - { - requestBuilder.AddFormParameter(arg.Key, arg.Value); - } - } - } - else + if (httpVerb == HttpMethod.POST) + { + if (apiInfo.NeedsAuthentication) { - requestBuilder.Headers.ContentType = "application/json"; + requestBuilder.AddFormParameter("_sid", _sessionCache.Get(GenerateSessionCacheKey(settings), () => AuthenticateClient(settings), TimeSpan.FromHours(6))); } + + requestBuilder.AddFormParameter("api", apiInfo.Name); + requestBuilder.AddFormParameter("version", apiVersion); + requestBuilder.AddFormParameter("method", methodName); } else { - foreach (var arg in arguments) + if (apiInfo.NeedsAuthentication) { - requestBuilder.AddQueryParam(arg.Key, arg.Value); + requestBuilder.AddQueryParam("_sid", _sessionCache.Get(GenerateSessionCacheKey(settings), () => AuthenticateClient(settings), TimeSpan.FromHours(6))); + } + + requestBuilder.AddQueryParam("api", apiInfo.Name); + requestBuilder.AddQueryParam("version", apiVersion); + requestBuilder.AddQueryParam("method", methodName); + } + + return requestBuilder; + } + + private string GenerateInfoCacheKey(DownloadStationSettings settings, DiskStationApi api) + { + return $"{settings.Host}:{settings.Port}->{api}"; + } + + private void UpdateApiInfo(DownloadStationSettings settings) + { + var apis = new Dictionary() + { + { "SYNO.API.Auth", DiskStationApi.Auth }, + { _apiName, _apiType } + }; + + var requestBuilder = BuildRequest(settings, _apiInfo, "query", _apiInfo.MinVersion); + requestBuilder.AddQueryParam("query", string.Join(",", apis.Keys)); + + var infoResponse = ProcessRequest(requestBuilder, "get api info", _apiInfo.Type, settings); + + foreach (var data in infoResponse.Data) + { + if (apis.ContainsKey(data.Key)) + { + data.Value.Name = data.Key; + data.Value.Type = apis[data.Key]; + data.Value.NeedsAuthentication = apis[data.Key] != DiskStationApi.Auth; + + _infoCache.Set(GenerateInfoCacheKey(settings, apis[data.Key]), data.Value, TimeSpan.FromHours(1)); + } + } + } + + private DiskStationApiInfo GetApiInfo(DiskStationApi api, DownloadStationSettings settings) + { + if (api == DiskStationApi.Info) + { + return _apiInfo; + } + + var key = GenerateInfoCacheKey(settings, api); + var info = _infoCache.Find(key); + + if (info == null) + { + UpdateApiInfo(settings); + info = _infoCache.Find(key); + + if (info == null) + { + throw new DownloadClientException("Info of {0} not found on {1}:{2}", api, settings.Host, settings.Port); } } - return requestBuilder.Build(); + return info; } - protected IEnumerable GetApiVersion(DownloadStationSettings settings, DiskStationApi api) + public DiskStationApiInfo GetApiInfo(DownloadStationSettings settings) { - var arguments = new Dictionary - { - { "api", "SYNO.API.Info" }, - { "version", "1" }, - { "method", "query" }, - { "query", "SYNO.API.Auth, SYNO.DownloadStation.Info, SYNO.DownloadStation.Task, SYNO.FileStation.List, SYNO.DSM.Info" }, - }; - - var infoResponse = ProcessRequest(DiskStationApi.Info, arguments, settings, "Get api version"); - - //TODO: Refactor this into more elegant code - var infoResponeDSAuth = infoResponse.Data["SYNO.API.Auth"]; - var infoResponeDSInfo = infoResponse.Data["SYNO.DownloadStation.Info"]; - var infoResponeDSTask = infoResponse.Data["SYNO.DownloadStation.Task"]; - var infoResponseFSList = infoResponse.Data["SYNO.FileStation.List"]; - var infoResponseDSMInfo = infoResponse.Data["SYNO.DSM.Info"]; - - Resources[DiskStationApi.Auth] = infoResponeDSAuth.Path; - Resources[DiskStationApi.DownloadStationInfo] = infoResponeDSInfo.Path; - Resources[DiskStationApi.DownloadStationTask] = infoResponeDSTask.Path; - Resources[DiskStationApi.FileStationList] = infoResponseFSList.Path; - Resources[DiskStationApi.DSMInfo] = infoResponseDSMInfo.Path; - - switch (api) - { - case DiskStationApi.Auth: - return Enumerable.Range(infoResponeDSAuth.MinVersion, infoResponeDSAuth.MaxVersion - infoResponeDSAuth.MinVersion + 1); - case DiskStationApi.DownloadStationInfo: - return Enumerable.Range(infoResponeDSInfo.MinVersion, infoResponeDSInfo.MaxVersion - infoResponeDSInfo.MinVersion + 1); - case DiskStationApi.DownloadStationTask: - return Enumerable.Range(infoResponeDSTask.MinVersion, infoResponeDSTask.MaxVersion - infoResponeDSTask.MinVersion + 1); - case DiskStationApi.FileStationList: - return Enumerable.Range(infoResponseFSList.MinVersion, infoResponseFSList.MaxVersion - infoResponseFSList.MinVersion + 1); - case DiskStationApi.DSMInfo: - return Enumerable.Range(infoResponseDSMInfo.MinVersion, infoResponseDSMInfo.MaxVersion - infoResponseDSMInfo.MinVersion + 1); - default: - throw new DownloadClientException("Api not implemented"); - } + return GetApiInfo(_apiType, settings); } } } diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationInfoProxy.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationInfoProxy.cs new file mode 100644 index 000000000..9b0cb00ed --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationInfoProxy.cs @@ -0,0 +1,29 @@ +using NLog; +using NzbDrone.Common.Http; +using System.Collections.Generic; +using NzbDrone.Common.Cache; + +namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies +{ + public interface IDownloadStationInfoProxy : IDiskStationProxy + { + Dictionary GetConfig(DownloadStationSettings settings); + } + + public class DownloadStationInfoProxy : DiskStationProxyBase, IDownloadStationInfoProxy + { + public DownloadStationInfoProxy(IHttpClient httpClient, ICacheManager cacheManager, Logger logger) : + base(DiskStationApi.DownloadStationInfo, "SYNO.DownloadStation.Info", httpClient, cacheManager, logger) + { + } + + public Dictionary GetConfig(DownloadStationSettings settings) + { + var requestBuilder = BuildRequest(settings, "getConfig", 1); + + var response = ProcessRequest>(requestBuilder, "get config", settings); + + return response.Data; + } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationProxy.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationProxy.cs deleted file mode 100644 index 427cd3d5b..000000000 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationProxy.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NLog; -using NzbDrone.Common.Extensions; -using NzbDrone.Common.Http; -using NzbDrone.Core.Download.Clients.DownloadStation.Responses; - -namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies -{ - public interface IDownloadStationProxy - { - IEnumerable GetTasks(DownloadStationSettings settings); - Dictionary GetConfig(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 GetApiVersion(DownloadStationSettings settings); - } - - public class DownloadStationProxy : DiskStationProxyBase, IDownloadStationProxy - { - public DownloadStationProxy(IHttpClient httpClient, Logger logger) - : base(httpClient, logger) - { - } - - public void AddTaskFromData(byte[] data, string filename, string downloadDirectory, DownloadStationSettings settings) - { - var arguments = new Dictionary - { - { "api", "SYNO.DownloadStation.Task" }, - { "version", "2" }, - { "method", "create" } - }; - - if (downloadDirectory.IsNotNullOrWhiteSpace()) - { - arguments.Add("destination", downloadDirectory); - } - - arguments.Add("file", new Dictionary() { { "name", filename }, { "data", data } }); - - var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"add task from data {filename}", HttpMethod.POST); - } - - public void AddTaskFromUrl(string url, string downloadDirectory, DownloadStationSettings settings) - { - var arguments = new Dictionary - { - { "api", "SYNO.DownloadStation.Task" }, - { "version", "3" }, - { "method", "create" }, - { "uri", url } - }; - - if (downloadDirectory.IsNotNullOrWhiteSpace()) - { - arguments.Add("destination", downloadDirectory); - } - - var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"add task from url {url}"); - } - - public IEnumerable GetTasks(DownloadStationSettings settings) - { - var arguments = new Dictionary - { - { "api", "SYNO.DownloadStation.Task" }, - { "version", "1" }, - { "method", "list" }, - { "additional", "detail,transfer" } - }; - - try - { - var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, "get tasks"); - - return response.Data.Tasks; - } - catch (DownloadClientException e) - { - _logger.Error(e); - return new List(); - } - } - - public Dictionary GetConfig(DownloadStationSettings settings) - { - var arguments = new Dictionary - { - { "api", "SYNO.DownloadStation.Info" }, - { "version", "1" }, - { "method", "getconfig" } - }; - - var response = ProcessRequest>(DiskStationApi.DownloadStationInfo, arguments, settings, "get config"); - - return response.Data; - } - - public void RemoveTask(string downloadId, DownloadStationSettings settings) - { - var arguments = new Dictionary - { - { "api", "SYNO.DownloadStation.Task" }, - { "version", "1" }, - { "method", "delete" }, - { "id", downloadId }, - { "force_complete", false } - }; - - var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"remove item {downloadId}"); - } - - public IEnumerable GetApiVersion(DownloadStationSettings settings) - { - return base.GetApiVersion(settings, DiskStationApi.DownloadStationInfo); - } - } -} diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxy.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxy.cs new file mode 100644 index 000000000..1e6849dac --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxy.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +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 interface IDownloadStationTaskProxy : IDiskStationProxy + { + 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) + : base(DiskStationApi.DownloadStationTask, "SYNO.DownloadStation.Task", httpClient, cacheManager, logger) + { + } + + public void AddTaskFromData(byte[] data, string filename, string downloadDirectory, DownloadStationSettings settings) + { + var requestBuilder = BuildRequest(settings, "create", 2, HttpMethod.POST); + + if (downloadDirectory.IsNotNullOrWhiteSpace()) + { + requestBuilder.AddFormParameter("destination", downloadDirectory); + } + + requestBuilder.AddFormUpload("file", filename, data); + + var response = ProcessRequest(requestBuilder, $"add task from data {filename}", settings); + } + + public void AddTaskFromUrl(string url, string downloadDirectory, DownloadStationSettings settings) + { + var requestBuilder = BuildRequest(settings, "create", 3); + requestBuilder.AddQueryParam("uri", url); + + if (downloadDirectory.IsNotNullOrWhiteSpace()) + { + requestBuilder.AddQueryParam("destination", downloadDirectory); + } + + var response = ProcessRequest(requestBuilder, $"add task from url {url}", settings); + } + + public IEnumerable GetTasks(DownloadStationSettings settings) + { + try + { + var requestBuilder = BuildRequest(settings, "list", 1); + requestBuilder.AddQueryParam("additional", "detail,transfer"); + + var response = ProcessRequest(requestBuilder, "get tasks", settings); + + return response.Data.Tasks; + } + catch (DownloadClientException e) + { + _logger.Error(e); + return new List(); + } + } + + public void RemoveTask(string downloadId, DownloadStationSettings settings) + { + var requestBuilder = BuildRequest(settings, "delete", 1); + requestBuilder.AddQueryParam("id", downloadId); + requestBuilder.AddQueryParam("force_complete", false); + + var response = ProcessRequest(requestBuilder, $"remove item {downloadId}", settings); + } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/FileStationProxy.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/FileStationProxy.cs index 4beacd8e9..29031e0eb 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/FileStationProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/FileStationProxy.cs @@ -2,31 +2,27 @@ using System.Collections.Generic; using System.Linq; using NLog; +using NzbDrone.Common.Cache; using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; using NzbDrone.Core.Download.Clients.DownloadStation.Responses; namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies { - public interface IFileStationProxy + public interface IFileStationProxy : IDiskStationProxy { SharedFolderMapping GetSharedFolderMapping(string sharedFolder, DownloadStationSettings settings); - IEnumerable GetApiVersion(DownloadStationSettings settings); + FileStationListFileInfoResponse GetInfoFileOrDirectory(string path, DownloadStationSettings settings); } public class FileStationProxy : DiskStationProxyBase, IFileStationProxy { - public FileStationProxy(IHttpClient httpClient, Logger logger) - : base(httpClient, logger) + public FileStationProxy(IHttpClient httpClient, ICacheManager cacheManager, Logger logger) + : base(DiskStationApi.FileStationList, "SYNO.FileStation.List", httpClient, cacheManager, logger) { } - - public IEnumerable GetApiVersion(DownloadStationSettings settings) - { - return base.GetApiVersion(settings, DiskStationApi.FileStationList); - } - + public SharedFolderMapping GetSharedFolderMapping(string sharedFolder, DownloadStationSettings settings) { var info = GetInfoFileOrDirectory(sharedFolder, settings); @@ -38,16 +34,11 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies public FileStationListFileInfoResponse GetInfoFileOrDirectory(string path, DownloadStationSettings settings) { - var arguments = new Dictionary - { - { "api", "SYNO.FileStation.List" }, - { "version", "2" }, - { "method", "getinfo" }, - { "path", new [] { path }.ToJson() }, - { "additional", $"[\"real_path\"]" } - }; + var requestBuilder = BuildRequest(settings, "getinfo", 2); + requestBuilder.AddQueryParam("path", new[] { path }.ToJson()); + requestBuilder.AddQueryParam("additional", "[\"real_path\"]"); - var response = ProcessRequest(DiskStationApi.FileStationList, arguments, settings, $"get info of {path}"); + var response = ProcessRequest(requestBuilder, $"get info of {path}", settings); return response.Data.Files.First(); } diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs index 8378a6815..911152694 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Net; using FluentValidation.Results; using NLog; -using NzbDrone.Common.Cache; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; @@ -20,7 +19,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation { public class TorrentDownloadStation : TorrentClientBase { - protected readonly IDownloadStationProxy _proxy; + protected readonly IDownloadStationInfoProxy _dsInfoProxy; + protected readonly IDownloadStationTaskProxy _dsTaskProxy; protected readonly ISharedFolderResolver _sharedFolderResolver; protected readonly ISerialNumberProvider _serialNumberProvider; protected readonly IFileStationProxy _fileStationProxy; @@ -28,7 +28,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation public TorrentDownloadStation(ISharedFolderResolver sharedFolderResolver, ISerialNumberProvider serialNumberProvider, IFileStationProxy fileStationProxy, - IDownloadStationProxy proxy, + IDownloadStationInfoProxy dsInfoProxy, + IDownloadStationTaskProxy dsTaskProxy, ITorrentFileInfoReader torrentFileInfoReader, IHttpClient httpClient, IConfigService configService, @@ -37,7 +38,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation Logger logger) : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) { - _proxy = proxy; + _dsInfoProxy = dsInfoProxy; + _dsTaskProxy = dsTaskProxy; _fileStationProxy = fileStationProxy; _sharedFolderResolver = sharedFolderResolver; _serialNumberProvider = serialNumberProvider; @@ -47,7 +49,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation protected IEnumerable GetTasks() { - return _proxy.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() @@ -129,7 +131,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation DeleteItemData(downloadId); } - _proxy.RemoveTask(ParseDownloadId(downloadId), Settings); + _dsTaskProxy.RemoveTask(ParseDownloadId(downloadId), Settings); _logger.Debug("{0} removed correctly", downloadId); } @@ -148,7 +150,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation { var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings); - _proxy.AddTaskFromUrl(magnetLink, GetDownloadDirectory(), Settings); + _dsTaskProxy.AddTaskFromUrl(magnetLink, GetDownloadDirectory(), Settings); var item = GetTasks().SingleOrDefault(t => t.Additional.Detail["uri"] == magnetLink); @@ -167,7 +169,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation { var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings); - _proxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings); + _dsTaskProxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings); var items = GetTasks().Where(t => t.Additional.Detail["uri"] == Path.GetFileNameWithoutExtension(filename)); @@ -358,13 +360,13 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation protected ValidationFailure ValidateVersion() { - var versionRange = _proxy.GetApiVersion(Settings); + var info = _dsTaskProxy.GetApiInfo(Settings); - _logger.Debug("Download Station api version information: Min {0} - Max {1}", versionRange.Min(), versionRange.Max()); + _logger.Debug("Download Station api version information: Min {0} - Max {1}", info.MinVersion, info.MaxVersion); - if (!versionRange.Contains(2)) + if (info.MinVersion > 2 || info.MaxVersion < 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 new ValidationFailure(string.Empty, $"Download Station API version not supported, should be at least 2. It supports from {info.MinVersion} to {info.MaxVersion}"); } return null; @@ -395,7 +397,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation protected string GetDefaultDir() { - var config = _proxy.GetConfig(Settings); + var config = _dsInfoProxy.GetConfig(Settings); var path = config["default_destination"] as string; diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs index ccbe4f75e..ad7045cbb 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs @@ -17,7 +17,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation { public class UsenetDownloadStation : UsenetClientBase { - protected readonly IDownloadStationProxy _proxy; + protected readonly IDownloadStationInfoProxy _dsInfoProxy; + protected readonly IDownloadStationTaskProxy _dsTaskProxy; protected readonly ISharedFolderResolver _sharedFolderResolver; protected readonly ISerialNumberProvider _serialNumberProvider; protected readonly IFileStationProxy _fileStationProxy; @@ -25,7 +26,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation public UsenetDownloadStation(ISharedFolderResolver sharedFolderResolver, ISerialNumberProvider serialNumberProvider, IFileStationProxy fileStationProxy, - IDownloadStationProxy proxy, + IDownloadStationInfoProxy dsInfoProxy, + IDownloadStationTaskProxy dsTaskProxy, IHttpClient httpClient, IConfigService configService, IDiskProvider diskProvider, @@ -34,7 +36,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation ) : base(httpClient, configService, diskProvider, remotePathMappingService, logger) { - _proxy = proxy; + _dsInfoProxy = dsInfoProxy; + _dsTaskProxy = dsTaskProxy; _fileStationProxy = fileStationProxy; _sharedFolderResolver = sharedFolderResolver; _serialNumberProvider = serialNumberProvider; @@ -44,7 +47,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation protected IEnumerable GetTasks() { - return _proxy.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() @@ -153,7 +156,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation DeleteItemData(downloadId); } - _proxy.RemoveTask(ParseDownloadId(downloadId), Settings); + _dsTaskProxy.RemoveTask(ParseDownloadId(downloadId), Settings); _logger.Debug("{0} removed correctly", downloadId); } @@ -161,7 +164,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation { var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings); - _proxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings); + _dsTaskProxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings); var items = GetTasks().Where(t => t.Additional.Detail["uri"] == filename); @@ -276,13 +279,13 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation protected ValidationFailure ValidateVersion() { - var versionRange = _proxy.GetApiVersion(Settings); + var info = _dsTaskProxy.GetApiInfo(Settings); - _logger.Debug("Download Station api version information: Min {0} - Max {1}", versionRange.Min(), versionRange.Max()); + _logger.Debug("Download Station api version information: Min {0} - Max {1}", info.MinVersion, info.MaxVersion); - if (!versionRange.Contains(2)) + if (info.MinVersion > 2 || info.MaxVersion < 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 new ValidationFailure(string.Empty, $"Download Station API version not supported, should be at least 2. It supports from {info.MinVersion} to {info.MaxVersion}"); } return null; @@ -394,7 +397,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation protected string GetDefaultDir() { - var config = _proxy.GetConfig(Settings); + var config = _dsInfoProxy.GetConfig(Settings); var path = config["default_destination"] as string; diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 5fa5f5167..932dd485c 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -355,8 +355,9 @@ + - +