From 2c1d3339d099c35779269bb09c458446f22f059d Mon Sep 17 00:00:00 2001 From: Keivan Beigi Date: Thu, 11 Sep 2014 16:49:41 -0700 Subject: [PATCH] HttpClient --- src/NzbDrone.Api/Update/UpdateModule.cs | 5 +- .../Http/HttpClientFixture.cs | 85 +++++++++ .../Http/HttpRequestBuilderFixture.cs | 22 +++ .../Http/HttpRequestFixture.cs | 29 +++ .../NzbDrone.Common.Test.csproj | 3 + src/NzbDrone.Common.Test/WebClientTests.cs | 8 - src/NzbDrone.Common/Cloud/CloudClient.cs | 19 ++ src/NzbDrone.Common/Http/GZipWebClient.cs | 15 ++ src/NzbDrone.Common/Http/HttpClient.cs | 160 +++++++++++++++++ src/NzbDrone.Common/Http/HttpException.cs | 27 +++ src/NzbDrone.Common/Http/HttpHeader.cs | 70 ++++++++ src/NzbDrone.Common/Http/HttpMethod.cs | 13 ++ src/NzbDrone.Common/Http/HttpProvider.cs | 102 +---------- src/NzbDrone.Common/Http/HttpRequest.cs | 66 +++++++ .../Http/HttpRequestBuilder.cs | 40 +++++ src/NzbDrone.Common/Http/HttpResponse.cs | 65 +++++++ src/NzbDrone.Common/Http/UriExtensions.cs | 21 +++ src/NzbDrone.Common/NzbDrone.Common.csproj | 16 +- src/NzbDrone.Common/StringExtensions.cs | 5 + src/NzbDrone.Common/packages.config | 1 - .../DailySeriesDataProxyFixture.cs | 28 +++ .../Scene/SceneMappingProxyFixture.cs | 40 ++--- .../Blackhole/UsenetBlackholeFixture.cs | 10 +- .../DownloadClientFixtureBase.cs | 10 +- .../PneumaticProviderFixture.cs | 11 +- .../Files/SceneMappings.json | 32 ---- src/NzbDrone.Core.Test/Framework/CoreTest.cs | 3 + .../CoverExistsSpecificationFixture.cs | 14 +- .../MetadataSourceTests/TraktProxyFixture.cs | 19 +- .../MetadataSourceTests/TvdbProxyFixture.cs | 89 +--------- .../NzbDrone.Core.Test.csproj | 4 +- .../TvTests/RefreshEpisodeServiceFixture.cs | 2 + .../UpdatePackageProviderFixture.cs | 19 +- .../UpdateTests/UpdateServiceFixture.cs | 4 +- .../DailySeries/DailySeries.cs | 7 + .../DailySeries/DailySeriesDataProxy.cs | 37 ++-- .../DailySeries/DailySeriesService.cs | 34 ++-- .../Scene/SceneMappingProxy.cs | 15 +- .../DataAugmentation/Xem/XemProxy.cs | 44 ++--- .../Download/Clients/Nzbget/Nzbget.cs | 16 +- .../Download/Clients/Pneumatic/Pneumatic.cs | 8 +- .../Download/Clients/Sabnzbd/Sabnzbd.cs | 10 +- .../UsenetBlackhole/UsenetBlackhole.cs | 8 +- .../CoverAlreadyExistsSpecification.cs | 32 +--- .../MediaCover/MediaCoverService.cs | 12 +- src/NzbDrone.Core/MetaData/MetadataService.cs | 8 +- .../MetadataSource/TraktProxy.cs | 165 ++++++++++-------- .../MetadataSource/Tvdb/TvdbProxy.cs | 41 ++--- src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + .../Update/InstallUpdateService.cs | 8 +- .../Update/RecentUpdateProvider.cs | 3 +- src/NzbDrone.Core/Update/UpdatePackage.cs | 1 - .../Update/UpdatePackageProvider.cs | 45 ++--- src/NzbDrone.Test.Common/LoggingTest.cs | 6 +- src/NzbDrone.Test.Common/TestBase.cs | 9 +- 55 files changed, 990 insertions(+), 577 deletions(-) create mode 100644 src/NzbDrone.Common.Test/Http/HttpClientFixture.cs create mode 100644 src/NzbDrone.Common.Test/Http/HttpRequestBuilderFixture.cs create mode 100644 src/NzbDrone.Common.Test/Http/HttpRequestFixture.cs create mode 100644 src/NzbDrone.Common/Cloud/CloudClient.cs create mode 100644 src/NzbDrone.Common/Http/GZipWebClient.cs create mode 100644 src/NzbDrone.Common/Http/HttpClient.cs create mode 100644 src/NzbDrone.Common/Http/HttpException.cs create mode 100644 src/NzbDrone.Common/Http/HttpHeader.cs create mode 100644 src/NzbDrone.Common/Http/HttpMethod.cs create mode 100644 src/NzbDrone.Common/Http/HttpRequest.cs create mode 100644 src/NzbDrone.Common/Http/HttpRequestBuilder.cs create mode 100644 src/NzbDrone.Common/Http/HttpResponse.cs create mode 100644 src/NzbDrone.Common/Http/UriExtensions.cs create mode 100644 src/NzbDrone.Core.Test/DataAugmentation/DailySeries/DailySeriesDataProxyFixture.cs delete mode 100644 src/NzbDrone.Core.Test/Files/SceneMappings.json create mode 100644 src/NzbDrone.Core/DataAugmentation/DailySeries/DailySeries.cs diff --git a/src/NzbDrone.Api/Update/UpdateModule.cs b/src/NzbDrone.Api/Update/UpdateModule.cs index 959dd0d7c..e0041438c 100644 --- a/src/NzbDrone.Api/Update/UpdateModule.cs +++ b/src/NzbDrone.Api/Update/UpdateModule.cs @@ -9,13 +9,10 @@ namespace NzbDrone.Api.Update public class UpdateModule : NzbDroneRestModule { private readonly IRecentUpdateProvider _recentUpdateProvider; - private readonly IInstallUpdates _installUpdateService; - public UpdateModule(IRecentUpdateProvider recentUpdateProvider, - IInstallUpdates installUpdateService) + public UpdateModule(IRecentUpdateProvider recentUpdateProvider) { _recentUpdateProvider = recentUpdateProvider; - _installUpdateService = installUpdateService; GetResourceAll = GetRecentUpdates; } diff --git a/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs b/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs new file mode 100644 index 000000000..b571ee9fa --- /dev/null +++ b/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Net; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Common.Http; +using NzbDrone.Test.Common; +using NzbDrone.Test.Common.Categories; + +namespace NzbDrone.Common.Test.Http +{ + [TestFixture] + [IntegrationTest] + public class RestClientFixture : TestBase + { + [Test] + public void should_execute_simple_get() + { + var request = new HttpRequest("http://eu.httpbin.org/get"); + + var response = Subject.Execute(request); + + response.Content.Should().NotBeNullOrWhiteSpace(); + } + + [Test] + public void should_execute_typed_get() + { + var request = new HttpRequest("http://eu.httpbin.org/get"); + + var response = Subject.Get(request); + + response.Resource.Url.Should().Be(request.Url.ToString()); + } + + [TestCase("gzip")] + public void should_execute_get_using_gzip(string compression) + { + var request = new HttpRequest("http://eu.httpbin.org/" + compression); + + var response = Subject.Get(request); + + response.Resource.Headers["Accept-Encoding"].ToString().Should().Be(compression); + response.Headers.ContentLength.Should().BeLessOrEqualTo(response.Content.Length); + } + + [TestCase(HttpStatusCode.Unauthorized)] + [TestCase(HttpStatusCode.Forbidden)] + [TestCase(HttpStatusCode.NotFound)] + [TestCase(HttpStatusCode.InternalServerError)] + [TestCase(HttpStatusCode.ServiceUnavailable)] + [TestCase(HttpStatusCode.BadGateway)] + public void should_throw_on_unsuccessful_status_codes(HttpStatusCode statusCode) + { + var request = new HttpRequest("http://eu.httpbin.org/status/" + (int)statusCode); + + var exception = Assert.Throws(() => Subject.Get(request)); + + exception.Response.StatusCode.Should().Be(statusCode); + } + + + [TestCase(HttpStatusCode.Moved)] + [TestCase(HttpStatusCode.MovedPermanently)] + public void should_not_follow_redirects_when_not_in_production(HttpStatusCode statusCode) + { + var request = new HttpRequest("http://eu.httpbin.org/status/" + (int)statusCode); + + Assert.Throws(() => Subject.Get(request)); + + } + } + + + public class HttpBinResource + { + public Dictionary Headers { get; set; } + public string Origin { get; set; } + public string Url { get; set; } + } + + + + +} \ No newline at end of file diff --git a/src/NzbDrone.Common.Test/Http/HttpRequestBuilderFixture.cs b/src/NzbDrone.Common.Test/Http/HttpRequestBuilderFixture.cs new file mode 100644 index 000000000..3c4373bf0 --- /dev/null +++ b/src/NzbDrone.Common.Test/Http/HttpRequestBuilderFixture.cs @@ -0,0 +1,22 @@ +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Common.Http; +using NzbDrone.Test.Common; + +namespace NzbDrone.Common.Test.Http +{ + [TestFixture] + public class HttpRequestBuilderFixture : TestBase + { + [Test] + public void should_remove_duplicated_slashes() + { + var builder = new HttpRequestBuilder("http://domain/"); + + var request = builder.Build("/v1/"); + + request.Url.ToString().Should().Be("http://domain/v1/"); + + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Common.Test/Http/HttpRequestFixture.cs b/src/NzbDrone.Common.Test/Http/HttpRequestFixture.cs new file mode 100644 index 000000000..9ec350c05 --- /dev/null +++ b/src/NzbDrone.Common.Test/Http/HttpRequestFixture.cs @@ -0,0 +1,29 @@ +using System; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Common.Http; + +namespace NzbDrone.Common.Test.Http +{ + [TestFixture] + public class HttpRequestFixture + { + [TestCase("http://host/{seg}/some", "http://host/dir/some")] + [TestCase("http://host/some/{seg}", "http://host/some/dir")] + public void should_add_single_segment_url_segments(string url, string result) + { + var request = new HttpRequest(url); + + request.AddSegment("seg", "dir"); + + request.Url.Should().Be(result); + } + + [Test] + public void shouldnt_add_value_for_nonexisting_segment() + { + var request = new HttpRequest("http://host/{seg}/some"); + Assert.Throws(() => request.AddSegment("seg2", "dir")); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj b/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj index 4135a24d3..6a33fdd79 100644 --- a/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj +++ b/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj @@ -73,6 +73,9 @@ + + + diff --git a/src/NzbDrone.Common.Test/WebClientTests.cs b/src/NzbDrone.Common.Test/WebClientTests.cs index 9e8b81ffd..899fbadbd 100644 --- a/src/NzbDrone.Common.Test/WebClientTests.cs +++ b/src/NzbDrone.Common.Test/WebClientTests.cs @@ -26,13 +26,5 @@ namespace NzbDrone.Common.Test Assert.Throws(() => Subject.DownloadString(url)); ExceptionVerification.ExpectedWarns(1); } - - - [Test] - public void should_get_headers() - { - Subject.GetHeader("http://www.google.com").Should().NotBeEmpty(); - } } - } diff --git a/src/NzbDrone.Common/Cloud/CloudClient.cs b/src/NzbDrone.Common/Cloud/CloudClient.cs new file mode 100644 index 000000000..928041f7e --- /dev/null +++ b/src/NzbDrone.Common/Cloud/CloudClient.cs @@ -0,0 +1,19 @@ +using NzbDrone.Common.Http; + +namespace NzbDrone.Common.Cloud +{ + public interface IDroneServicesRequestBuilder + { + HttpRequest Build(string path); + } + + public class DroneServicesHttpRequestBuilder : HttpRequestBuilder, IDroneServicesRequestBuilder + { + private const string ROOT_URL = "http://services.nzbdrone.com/v1/"; + + public DroneServicesHttpRequestBuilder() + : base(ROOT_URL) + { + } + } +} diff --git a/src/NzbDrone.Common/Http/GZipWebClient.cs b/src/NzbDrone.Common/Http/GZipWebClient.cs new file mode 100644 index 000000000..6b8ddb582 --- /dev/null +++ b/src/NzbDrone.Common/Http/GZipWebClient.cs @@ -0,0 +1,15 @@ +using System; +using System.Net; + +namespace NzbDrone.Common.Http +{ + public class GZipWebClient : WebClient + { + protected override WebRequest GetWebRequest(Uri address) + { + var request = (HttpWebRequest)base.GetWebRequest(address); + request.AutomaticDecompression = DecompressionMethods.GZip; + return request; + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Common/Http/HttpClient.cs b/src/NzbDrone.Common/Http/HttpClient.cs new file mode 100644 index 000000000..708091f09 --- /dev/null +++ b/src/NzbDrone.Common/Http/HttpClient.cs @@ -0,0 +1,160 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Net; +using NLog; +using NzbDrone.Common.EnvironmentInfo; + +namespace NzbDrone.Common.Http +{ + public interface IHttpClient + { + HttpResponse Execute(HttpRequest request); + void DownloadFile(string url, string fileName); + HttpResponse Get(HttpRequest request); + HttpResponse Get(HttpRequest request) where T : new(); + HttpResponse Head(HttpRequest request); + } + + public class HttpClient : IHttpClient + { + private readonly Logger _logger; + private readonly string _userAgent; + + public HttpClient(Logger logger) + { + _logger = logger; + _userAgent = String.Format("NzbDrone {0}", BuildInfo.Version); + ServicePointManager.DefaultConnectionLimit = 12; + } + + public HttpResponse Execute(HttpRequest request) + { + _logger.Trace(request); + + var webRequest = (HttpWebRequest)WebRequest.Create(request.Url); + + // Deflate is not a standard and could break depending on implementation. + // we should just stick with the more compatible Gzip + //http://stackoverflow.com/questions/8490718/how-to-decompress-stream-deflated-with-java-util-zip-deflater-in-net + webRequest.AutomaticDecompression = DecompressionMethods.GZip; + + webRequest.Credentials = request.NetworkCredential; + webRequest.Method = request.Method.ToString(); + webRequest.KeepAlive = false; + + if (!RuntimeInfoBase.IsProduction) + { + webRequest.AllowAutoRedirect = false; + } + + var stopWatch = Stopwatch.StartNew(); + + if (!request.Body.IsNullOrWhiteSpace()) + { + var bytes = new byte[request.Body.Length * sizeof(char)]; + Buffer.BlockCopy(request.Body.ToCharArray(), 0, bytes, 0, bytes.Length); + + webRequest.ContentLength = bytes.Length; + using (var writeStream = webRequest.GetRequestStream()) + { + writeStream.Write(bytes, 0, bytes.Length); + } + } + + HttpWebResponse httpWebResponse; + + try + { + httpWebResponse = (HttpWebResponse)webRequest.GetResponse(); + } + catch (WebException e) + { + httpWebResponse = (HttpWebResponse)e.Response; + } + + string content = null; + + using (var responseStream = httpWebResponse.GetResponseStream()) + { + if (responseStream != null) + { + using (var reader = new StreamReader(responseStream)) + { + content = reader.ReadToEnd(); + } + } + } + + stopWatch.Stop(); + + var response = new HttpResponse(request, new HttpHeader(httpWebResponse.Headers), content, httpWebResponse.StatusCode); + _logger.Trace("{0} ({1:n0} ms)", response, stopWatch.ElapsedMilliseconds); + + if (!RuntimeInfoBase.IsProduction && + (response.StatusCode == HttpStatusCode.Moved || + response.StatusCode == HttpStatusCode.MovedPermanently)) + { + throw new Exception("Server requested a redirect to [" + response.Headers["Location"] + "]. Update the request URL to avoid this redirect."); + } + + if (!request.SuppressHttpError && response.HasHttpError) + { + _logger.Warn("HTTP Error - {0}", response); + throw new HttpException(request, response); + } + + return response; + } + + public void DownloadFile(string url, string fileName) + { + try + { + var fileInfo = new FileInfo(fileName); + if (fileInfo.Directory != null && !fileInfo.Directory.Exists) + { + fileInfo.Directory.Create(); + } + + _logger.Debug("Downloading [{0}] to [{1}]", url, fileName); + + var stopWatch = Stopwatch.StartNew(); + var webClient = new GZipWebClient(); + webClient.Headers.Add(HttpRequestHeader.UserAgent, _userAgent); + webClient.DownloadFile(url, fileName); + stopWatch.Stop(); + _logger.Debug("Downloading Completed. took {0:0}s", stopWatch.Elapsed.Seconds); + } + catch (WebException e) + { + _logger.Warn("Failed to get response from: {0} {1}", url, e.Message); + throw; + } + catch (Exception e) + { + _logger.WarnException("Failed to get response from: " + url, e); + throw; + } + } + + public HttpResponse Get(HttpRequest request) + { + request.Method = HttpMethod.GET; + return Execute(request); + } + + public HttpResponse Get(HttpRequest request) where T : new() + { + var response = Get(request); + return new HttpResponse(response); + } + + public HttpResponse Head(HttpRequest request) + { + request.Method = HttpMethod.HEAD; + return Execute(request); + } + + } +} \ No newline at end of file diff --git a/src/NzbDrone.Common/Http/HttpException.cs b/src/NzbDrone.Common/Http/HttpException.cs new file mode 100644 index 000000000..3c52fc111 --- /dev/null +++ b/src/NzbDrone.Common/Http/HttpException.cs @@ -0,0 +1,27 @@ +using System; + +namespace NzbDrone.Common.Http +{ + public class HttpException : Exception + { + public HttpRequest Request { get; private set; } + public HttpResponse Response { get; private set; } + + public HttpException(HttpRequest request, HttpResponse response) + : base(string.Format("HTTP request failed: [{0}] [{1}] at [{2}]", (int)response.StatusCode, request.Method, request.Url.ToString())) + { + Request = request; + Response = response; + } + + public override string ToString() + { + if (Response != null) + { + return base.ToString() + Environment.NewLine + Response.Content; + } + + return base.ToString(); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Common/Http/HttpHeader.cs b/src/NzbDrone.Common/Http/HttpHeader.cs new file mode 100644 index 000000000..b7bb3c617 --- /dev/null +++ b/src/NzbDrone.Common/Http/HttpHeader.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; + +namespace NzbDrone.Common.Http +{ + public class HttpHeader : Dictionary + { + public HttpHeader(NameValueCollection headers) + { + foreach (var key in headers.AllKeys) + { + this[key] = headers[key]; + } + } + + public HttpHeader() + { + + } + + public long? ContentLength + { + get + { + if (!ContainsKey("Content-Length")) + { + return null; + } + return Convert.ToInt64(this["Content-Length"]); + } + set + { + this["Content-Length"] = value; + } + } + + public string ContentType + { + get + { + if (!ContainsKey("Content-Type")) + { + return null; + } + return this["Content-Type"].ToString(); + } + set + { + this["Content-Type"] = value; + } + } + + public string Accept + { + get + { + if (!ContainsKey("Accept")) + { + return null; + } + return this["Accept"].ToString(); + } + set + { + this["Accept"] = value; + } + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Common/Http/HttpMethod.cs b/src/NzbDrone.Common/Http/HttpMethod.cs new file mode 100644 index 000000000..1fa33a823 --- /dev/null +++ b/src/NzbDrone.Common/Http/HttpMethod.cs @@ -0,0 +1,13 @@ +namespace NzbDrone.Common.Http +{ + public enum HttpMethod + { + GET, + PUT, + POST, + HEAD, + DELETE, + PATCH, + OPTIONS + } +} \ No newline at end of file diff --git a/src/NzbDrone.Common/Http/HttpProvider.cs b/src/NzbDrone.Common/Http/HttpProvider.cs index b122b9464..0d6634497 100644 --- a/src/NzbDrone.Common/Http/HttpProvider.cs +++ b/src/NzbDrone.Common/Http/HttpProvider.cs @@ -1,40 +1,23 @@ using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; using System.Net; -using System.Text; using NLog; using NzbDrone.Common.EnvironmentInfo; namespace NzbDrone.Common.Http { + [Obsolete("Use IHttpClient")] public interface IHttpProvider { string DownloadString(string url); string DownloadString(string url, string username, string password); - string DownloadString(string url, ICredentials credentials); - Dictionary GetHeader(string url); - Stream DownloadStream(string url, NetworkCredential credential = null); - void DownloadFile(string url, string fileName); - string PostCommand(string address, string username, string password, string command); } + + [Obsolete("Use HttpProvider")] public class HttpProvider : IHttpProvider { - private class GZipWebClient : WebClient - { - protected override WebRequest GetWebRequest(Uri address) - { - HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(address); - request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; - return request; - } - } - private readonly Logger _logger; - public const string CONTENT_LENGTH_HEADER = "Content-Length"; private readonly string _userAgent; @@ -55,7 +38,7 @@ namespace NzbDrone.Common.Http return DownloadString(url, new NetworkCredential(username, password)); } - public string DownloadString(string url, ICredentials identity) + private string DownloadString(string url, ICredentials identity) { try { @@ -75,81 +58,6 @@ namespace NzbDrone.Common.Http } } - public Dictionary GetHeader(string url) - { - var headers = new Dictionary(); - var request = WebRequest.Create(url); - request.Method = "HEAD"; - - var response = request.GetResponse(); - - foreach (var key in response.Headers.AllKeys) - { - headers.Add(key, response.Headers[key]); - } - - return headers; - } - - public Stream DownloadStream(string url, NetworkCredential credential = null) - { - var request = (HttpWebRequest)WebRequest.Create(url); - request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; - request.UserAgent = _userAgent; - request.Timeout = 20 * 1000; - - request.Credentials = credential; - var response = request.GetResponse(); - - return response.GetResponseStream(); - } - - public void DownloadFile(string url, string fileName) - { - try - { - var fileInfo = new FileInfo(fileName); - if (fileInfo.Directory != null && !fileInfo.Directory.Exists) - { - fileInfo.Directory.Create(); - } - - _logger.Debug("Downloading [{0}] to [{1}]", url, fileName); - - var stopWatch = Stopwatch.StartNew(); - var webClient = new GZipWebClient(); - webClient.Headers.Add(HttpRequestHeader.UserAgent, _userAgent); - webClient.DownloadFile(url, fileName); - stopWatch.Stop(); - _logger.Debug("Downloading Completed. took {0:0}s", stopWatch.Elapsed.Seconds); - } - catch (WebException e) - { - _logger.Warn("Failed to get response from: {0} {1}", url, e.Message); - throw; - } - catch (Exception e) - { - _logger.WarnException("Failed to get response from: " + url, e); - throw; - } - } - - public string PostCommand(string address, string username, string password, string command) - { - address = String.Format("http://{0}/jsonrpc", address); - - _logger.Debug("Posting command: {0}, to {1}", command, address); - - byte[] byteArray = Encoding.ASCII.GetBytes(command); - - var wc = new NzbDroneWebClient(); - wc.Credentials = new NetworkCredential(username, password); - - var response = wc.UploadData(address, "POST", byteArray); - var text = Encoding.ASCII.GetString(response); - - return text.Replace(" ", " "); - } + } } \ No newline at end of file diff --git a/src/NzbDrone.Common/Http/HttpRequest.cs b/src/NzbDrone.Common/Http/HttpRequest.cs new file mode 100644 index 000000000..8ba571207 --- /dev/null +++ b/src/NzbDrone.Common/Http/HttpRequest.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Net; + +namespace NzbDrone.Common.Http +{ + public class HttpRequest + { + + private readonly Dictionary _segments; + + public HttpRequest(string url) + { + UriBuilder = new UriBuilder(url); + Headers = new HttpHeader(); + _segments = new Dictionary(); + + Headers.Accept = "application/json"; + } + + public UriBuilder UriBuilder { get; private set; } + + public Uri Url + { + get + { + var uri = UriBuilder.Uri.ToString(); + + foreach (var segment in _segments) + { + uri = uri.Replace(segment.Key, segment.Value); + } + + return new Uri(uri); + } + } + + public HttpMethod Method { get; set; } + public HttpHeader Headers { get; set; } + public string Body { get; set; } + public NetworkCredential NetworkCredential { get; set; } + public bool SuppressHttpError { get; set; } + + public override string ToString() + { + if (Body == null) + { + return string.Format("Req: [{0}] {1}", Method, Url); + } + + return string.Format("Req: [{0}] {1} {2} {3}", Method, Url, Environment.NewLine, Body); + } + + public void AddSegment(string segment, string value) + { + var key = "{" + segment + "}"; + + if (!UriBuilder.Uri.ToString().Contains(key)) + { + throw new InvalidOperationException("Segment " + key +" is not defined in Uri"); + } + + _segments.Add(key, value); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Common/Http/HttpRequestBuilder.cs b/src/NzbDrone.Common/Http/HttpRequestBuilder.cs new file mode 100644 index 000000000..ca3b83faf --- /dev/null +++ b/src/NzbDrone.Common/Http/HttpRequestBuilder.cs @@ -0,0 +1,40 @@ +using System; +using System.Net; + +namespace NzbDrone.Common.Http +{ + public class HttpRequestBuilder + { + public Uri BaseUri { get; private set; } + public bool SupressHttpError { get; set; } + public NetworkCredential NetworkCredential { get; set; } + + public Action PostProcess { get; set; } + + public HttpRequestBuilder(string baseUri) + { + BaseUri = new Uri(baseUri); + } + + public virtual HttpRequest Build(string path) + { + if (BaseUri.ToString().EndsWith("/")) + { + path = path.TrimStart('/'); + } + + var request = new HttpRequest(BaseUri + path) + { + SuppressHttpError = SupressHttpError, + NetworkCredential = NetworkCredential + }; + + if (PostProcess != null) + { + PostProcess(request); + } + + return request; + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Common/Http/HttpResponse.cs b/src/NzbDrone.Common/Http/HttpResponse.cs new file mode 100644 index 000000000..dd75c22cb --- /dev/null +++ b/src/NzbDrone.Common/Http/HttpResponse.cs @@ -0,0 +1,65 @@ +using System; +using System.IO; +using System.Net; +using NzbDrone.Common.Serializer; + +namespace NzbDrone.Common.Http +{ + public class HttpResponse + { + public HttpResponse(HttpRequest request, HttpHeader headers, string content, HttpStatusCode statusCode) + { + Request = request; + Headers = headers; + Content = content; + StatusCode = statusCode; + } + + public HttpRequest Request { get; private set; } + public HttpHeader Headers { get; private set; } + public HttpStatusCode StatusCode { get; private set; } + public string Content { get; private set; } + + public bool HasHttpError + { + get + { + return (int)StatusCode >= 400; + } + } + + public override string ToString() + { + var result = string.Format("Res: [{0}] {1} : {2}.{3}", Request.Method, Request.Url, (int)StatusCode, StatusCode); + + if (HasHttpError) + { + result += Environment.NewLine + Content; + } + + return result; + } + + public Stream GetStream() + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(Content); + writer.Flush(); + stream.Position = 0; + return stream; + } + } + + + public class HttpResponse : HttpResponse where T : new() + { + public HttpResponse(HttpResponse response) + : base(response.Request, response.Headers, response.Content, response.StatusCode) + { + Resource = Json.Deserialize(response.Content); + } + + public T Resource { get; private set; } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Common/Http/UriExtensions.cs b/src/NzbDrone.Common/Http/UriExtensions.cs new file mode 100644 index 000000000..dbc16138b --- /dev/null +++ b/src/NzbDrone.Common/Http/UriExtensions.cs @@ -0,0 +1,21 @@ +using System; + +namespace NzbDrone.Common.Http +{ + public static class UriExtensions + { + public static void SetQueryParam(this UriBuilder uriBuilder, string key, object value) + { + var query = uriBuilder.Query; + + if (query.IsNotNullOrWhiteSpace()) + { + query += "&"; + } + + uriBuilder.Query = query.Trim('?') + (key + "=" + value); + } + + + } +} \ No newline at end of file diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj index e93248231..30dbe2478 100644 --- a/src/NzbDrone.Common/NzbDrone.Common.csproj +++ b/src/NzbDrone.Common/NzbDrone.Common.csproj @@ -58,16 +58,13 @@ ..\packages\NLog.2.1.0\lib\net40\NLog.dll - - False - ..\packages\RestSharp.104.4.0\lib\net4\RestSharp.dll - + @@ -114,10 +111,21 @@ + + Component + + + + + + + Component + + diff --git a/src/NzbDrone.Common/StringExtensions.cs b/src/NzbDrone.Common/StringExtensions.cs index cb0137bed..d09c7c0d9 100644 --- a/src/NzbDrone.Common/StringExtensions.cs +++ b/src/NzbDrone.Common/StringExtensions.cs @@ -74,6 +74,11 @@ namespace NzbDrone.Common return String.IsNullOrWhiteSpace(text); } + public static bool IsNotNullOrWhiteSpace(this string text) + { + return !String.IsNullOrWhiteSpace(text); + } + public static bool ContainsIgnoreCase(this string text, string contains) { return text.IndexOf(contains, StringComparison.InvariantCultureIgnoreCase) > -1; diff --git a/src/NzbDrone.Common/packages.config b/src/NzbDrone.Common/packages.config index c2a05c7b8..3d90efbb1 100644 --- a/src/NzbDrone.Common/packages.config +++ b/src/NzbDrone.Common/packages.config @@ -3,6 +3,5 @@ - \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/DataAugmentation/DailySeries/DailySeriesDataProxyFixture.cs b/src/NzbDrone.Core.Test/DataAugmentation/DailySeries/DailySeriesDataProxyFixture.cs new file mode 100644 index 000000000..0508d7e19 --- /dev/null +++ b/src/NzbDrone.Core.Test/DataAugmentation/DailySeries/DailySeriesDataProxyFixture.cs @@ -0,0 +1,28 @@ +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.DataAugmentation.DailySeries; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common; +using NzbDrone.Test.Common.Categories; + +namespace NzbDrone.Core.Test.DataAugmentation.DailySeries +{ + [TestFixture] + [IntegrationTest] + public class DailySeriesDataProxyFixture : CoreTest + { + [SetUp] + public void Setup() + { + UseRealHttp(); + } + + [Test] + public void should_get_list_of_daily_series() + { + var list = Subject.GetDailySeriesIds(); + list.Should().NotBeEmpty(); + list.Should().OnlyHaveUniqueItems(); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/DataAugmentationFixture/Scene/SceneMappingProxyFixture.cs b/src/NzbDrone.Core.Test/DataAugmentationFixture/Scene/SceneMappingProxyFixture.cs index 93e2664e3..1b4246374 100644 --- a/src/NzbDrone.Core.Test/DataAugmentationFixture/Scene/SceneMappingProxyFixture.cs +++ b/src/NzbDrone.Core.Test/DataAugmentationFixture/Scene/SceneMappingProxyFixture.cs @@ -1,53 +1,33 @@ -using System; -using System.Net; using FluentAssertions; -using Newtonsoft.Json; using NUnit.Framework; using NzbDrone.Common; -using NzbDrone.Common.Http; using NzbDrone.Core.DataAugmentation.Scene; using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common.Categories; namespace NzbDrone.Core.Test.DataAugmentationFixture.Scene { [TestFixture] - + [IntegrationTest] public class SceneMappingProxyFixture : CoreTest { - private const string SCENE_MAPPING_URL = "http://services.nzbdrone.com/v1/SceneMapping"; + [SetUp] + public void Setup() + { + UseRealHttp(); + } [Test] public void fetch_should_return_list_of_mappings() { - Mocker.GetMock() - .Setup(s => s.DownloadString(SCENE_MAPPING_URL)) - .Returns(ReadAllText("Files", "SceneMappings.json")); - var mappings = Subject.Fetch(); mappings.Should().NotBeEmpty(); - mappings.Should().NotContain(c => String.IsNullOrWhiteSpace(c.SearchTerm)); - mappings.Should().NotContain(c => String.IsNullOrWhiteSpace(c.Title)); - mappings.Should().NotContain(c => c.TvdbId == 0); + mappings.Should().NotContain(c => c.SearchTerm.IsNullOrWhiteSpace()); + mappings.Should().NotContain(c => c.Title.IsNullOrWhiteSpace()); + mappings.Should().Contain(c => c.SeasonNumber > 0); } - [Test] - public void should_throw_on_server_error() - { - Mocker.GetMock() - .Setup(s => s.DownloadString(SCENE_MAPPING_URL)) - .Throws(new WebException()); - Assert.Throws(() => Subject.Fetch()); - } - - [Test] - public void should_throw_on_bad_json() - { - Mocker.GetMock() - .Setup(s => s.DownloadString(SCENE_MAPPING_URL)) - .Returns("bad json"); - Assert.Throws(() => Subject.Fetch()); - } } } diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/UsenetBlackholeFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/UsenetBlackholeFixture.cs index f19023b9c..0e858be68 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/UsenetBlackholeFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/UsenetBlackholeFixture.cs @@ -5,13 +5,9 @@ using Moq; using NUnit.Framework; using FluentAssertions; using NzbDrone.Test.Common; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Common; using NzbDrone.Common.Disk; using NzbDrone.Common.Http; -using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Download; -using NzbDrone.Core.Download.Clients; using NzbDrone.Core.Download.Clients.UsenetBlackhole; namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole @@ -46,7 +42,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole protected void WithFailedDownload() { - Mocker.GetMock() + Mocker.GetMock() .Setup(c => c.DownloadFile(It.IsAny(), It.IsAny())) .Throws(new WebException()); } @@ -84,7 +80,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole Subject.Download(remoteEpisode); - Mocker.GetMock().Verify(c => c.DownloadFile(_downloadUrl, _filePath), Times.Once()); + Mocker.GetMock().Verify(c => c.DownloadFile(_downloadUrl, _filePath), Times.Once()); } [Test] @@ -98,7 +94,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole Subject.Download(remoteEpisode); - Mocker.GetMock().Verify(c => c.DownloadFile(It.IsAny(), expectedFilename), Times.Once()); + Mocker.GetMock().Verify(c => c.DownloadFile(It.IsAny(), expectedFilename), Times.Once()); } [Test] diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs index 2ba721adb..7f44406c8 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs @@ -1,10 +1,10 @@ using System; -using System.Text; -using System.Linq; +using System.Net; using System.Collections.Generic; using Moq; using NUnit.Framework; using FluentAssertions; +using NzbDrone.Common.Http; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser; @@ -30,6 +30,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests Mocker.GetMock() .Setup(s => s.Map(It.IsAny(), It.IsAny(), null)) .Returns(CreateRemoteEpisode()); + + + Mocker.GetMock() + .Setup(c => c.Get(It.IsAny())) + .Returns(new HttpResponse(null, null, "", HttpStatusCode.OK)); + } protected virtual RemoteEpisode CreateRemoteEpisode() diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/PneumaticProviderFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/PneumaticProviderFixture.cs index a0e33f1bd..7e57ff0fd 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/PneumaticProviderFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/PneumaticProviderFixture.cs @@ -51,14 +51,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests }; } - private void WithExistingFile() - { - Mocker.GetMock().Setup(c => c.FileExists(_nzbPath)).Returns(true); - } - private void WithFailedDownload() { - Mocker.GetMock().Setup(c => c.DownloadFile(It.IsAny(), It.IsAny())).Throws(new WebException()); + Mocker.GetMock().Setup(c => c.DownloadFile(It.IsAny(), It.IsAny())).Throws(new WebException()); } [Test] @@ -66,7 +61,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests { Subject.Download(_remoteEpisode); - Mocker.GetMock().Verify(c => c.DownloadFile(_nzbUrl, _nzbPath), Times.Once()); + Mocker.GetMock().Verify(c => c.DownloadFile(_nzbUrl, _nzbPath), Times.Once()); } @@ -102,7 +97,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests Subject.Download(_remoteEpisode); - Mocker.GetMock().Verify(c => c.DownloadFile(It.IsAny(), expectedFilename), Times.Once()); + Mocker.GetMock().Verify(c => c.DownloadFile(It.IsAny(), expectedFilename), Times.Once()); } } } diff --git a/src/NzbDrone.Core.Test/Files/SceneMappings.json b/src/NzbDrone.Core.Test/Files/SceneMappings.json deleted file mode 100644 index 71e1f5937..000000000 --- a/src/NzbDrone.Core.Test/Files/SceneMappings.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "title": "Adventure Time", - "searchTitle": "Adventure Time", - "season": -1, - "tvdbId": 152831 - }, - { - "title": "Americas Funniest Home Videos", - "searchTitle": "Americas Funniest Home Videos", - "season": -1, - "tvdbId": 76235 - }, - { - "title": "Antiques Roadshow UK", - "searchTitle": "Antiques Roadshow UK", - "season": -1, - "tvdbId": 83774 - }, - { - "title": "Aqua Something You Know Whatever", - "searchTitle": "Aqua Something You Know Whatever", - "season": 9, - "tvdbId": 77120 - }, - { - "title": "Aqua Teen Hunger Force", - "searchTitle": "Aqua Teen Hunger Force", - "season": -1, - "tvdbId": 77120 - } -] \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/Framework/CoreTest.cs b/src/NzbDrone.Core.Test/Framework/CoreTest.cs index 686964552..aa045bda9 100644 --- a/src/NzbDrone.Core.Test/Framework/CoreTest.cs +++ b/src/NzbDrone.Core.Test/Framework/CoreTest.cs @@ -1,5 +1,6 @@ using System.IO; using NUnit.Framework; +using NzbDrone.Common.Cloud; using NzbDrone.Common.Http; using NzbDrone.Test.Common; @@ -15,6 +16,8 @@ namespace NzbDrone.Core.Test.Framework protected void UseRealHttp() { Mocker.SetConstant(new HttpProvider(TestLogger)); + Mocker.SetConstant(new HttpClient(TestLogger)); + Mocker.SetConstant(new DroneServicesHttpRequestBuilder()); } } diff --git a/src/NzbDrone.Core.Test/MediaCoverTests/CoverExistsSpecificationFixture.cs b/src/NzbDrone.Core.Test/MediaCoverTests/CoverExistsSpecificationFixture.cs index 58f842b65..a839cae22 100644 --- a/src/NzbDrone.Core.Test/MediaCoverTests/CoverExistsSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/MediaCoverTests/CoverExistsSpecificationFixture.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Net; using FluentAssertions; using Moq; using NUnit.Framework; @@ -14,14 +15,14 @@ namespace NzbDrone.Core.Test.MediaCoverTests [TestFixture] public class CoverAlreadyExistsSpecificationFixture : CoreTest { - private Dictionary _headers; + private HttpResponse _httpResponse; [SetUp] public void Setup() { - _headers = new Dictionary(); + _httpResponse = new HttpResponse(null, new HttpHeader(), null, HttpStatusCode.OK); Mocker.GetMock().Setup(c => c.GetFileSize(It.IsAny())).Returns(100); - Mocker.GetMock().Setup(c => c.GetHeader(It.IsAny())).Returns(_headers); + Mocker.GetMock().Setup(c => c.Head(It.IsAny())).Returns(_httpResponse); } @@ -50,7 +51,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests public void should_return_false_if_file_exists_but_diffrent_size() { GivenExistingFileSize(100); - _headers.Add(HttpProvider.CONTENT_LENGTH_HEADER, "200"); + _httpResponse.Headers.ContentLength = 200; Subject.AlreadyExists("http://url", "c:\\file.exe").Should().BeFalse(); } @@ -60,8 +61,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests public void should_return_ture_if_file_exists_and_same_size() { GivenExistingFileSize(100); - _headers.Add(HttpProvider.CONTENT_LENGTH_HEADER, "100"); - + _httpResponse.Headers.ContentLength = 100; Subject.AlreadyExists("http://url", "c:\\file.exe").Should().BeTrue(); } @@ -70,8 +70,6 @@ namespace NzbDrone.Core.Test.MediaCoverTests { GivenExistingFileSize(100); Subject.AlreadyExists("http://url", "c:\\file.exe").Should().BeFalse(); - - ExceptionVerification.ExpectedWarns(1); } } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs b/src/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs index 1e8d4df5c..b50d1ee9a 100644 --- a/src/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs +++ b/src/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Linq; using FluentAssertions; using NUnit.Framework; +using NzbDrone.Common.Http; using NzbDrone.Core.MetadataSource; -using NzbDrone.Core.Rest; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Test.Common; @@ -16,12 +16,27 @@ namespace NzbDrone.Core.Test.MetadataSourceTests [IntegrationTest] public class TraktProxyFixture : CoreTest { + + + [SetUp] + public void Setup() + { + UseRealHttp(); + } + [TestCase("The Simpsons", "The Simpsons")] [TestCase("South Park", "South Park")] [TestCase("Franklin & Bash", "Franklin & Bash")] [TestCase("Mr. D", "Mr. D")] [TestCase("Rob & Big", "Rob and Big")] [TestCase("M*A*S*H", "M*A*S*H")] + [TestCase("imdb:tt0436992", "Doctor Who (2005)")] + [TestCase("imdb:0436992", "Doctor Who (2005)")] + [TestCase("IMDB:0436992", "Doctor Who (2005)")] + [TestCase("IMDB: 0436992 ", "Doctor Who (2005)")] + [TestCase("tvdb:78804", "Doctor Who (2005)")] + [TestCase("TVDB:78804", "Doctor Who (2005)")] + [TestCase("TVDB: 78804 ", "Doctor Who (2005)")] public void successful_search(string title, string expected) { var result = Subject.SearchForNewSeries(title); @@ -52,7 +67,7 @@ namespace NzbDrone.Core.Test.MetadataSourceTests [Test] public void getting_details_of_invalid_series() { - Assert.Throws(() => Subject.GetSeriesInfo(Int32.MaxValue)); + Assert.Throws(() => Subject.GetSeriesInfo(Int32.MaxValue)); ExceptionVerification.ExpectedWarns(1); } diff --git a/src/NzbDrone.Core.Test/MetadataSourceTests/TvdbProxyFixture.cs b/src/NzbDrone.Core.Test/MetadataSourceTests/TvdbProxyFixture.cs index 2b60efce9..60ec11516 100644 --- a/src/NzbDrone.Core.Test/MetadataSourceTests/TvdbProxyFixture.cs +++ b/src/NzbDrone.Core.Test/MetadataSourceTests/TvdbProxyFixture.cs @@ -1,14 +1,10 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using FluentAssertions; using NUnit.Framework; -using NzbDrone.Core.MetadataSource; using NzbDrone.Core.MetadataSource.Tvdb; -using NzbDrone.Core.Rest; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; -using NzbDrone.Test.Common; using NzbDrone.Test.Common.Categories; namespace NzbDrone.Core.Test.MetadataSourceTests @@ -17,70 +13,15 @@ namespace NzbDrone.Core.Test.MetadataSourceTests [IntegrationTest] public class TvdbProxyFixture : CoreTest { -// [TestCase("The Simpsons", "The Simpsons")] -// [TestCase("South Park", "South Park")] -// [TestCase("Franklin & Bash", "Franklin & Bash")] -// [TestCase("Mr. D", "Mr. D")] -// [TestCase("Rob & Big", "Rob and Big")] -// [TestCase("M*A*S*H", "M*A*S*H")] -// public void successful_search(string title, string expected) -// { -// var result = Subject.SearchForNewSeries(title); -// -// result.Should().NotBeEmpty(); -// -// result[0].Title.Should().Be(expected); -// } -// -// [Test] -// public void no_search_result() -// { -// var result = Subject.SearchForNewSeries(Guid.NewGuid().ToString()); -// result.Should().BeEmpty(); -// } - [TestCase(88031)] [TestCase(179321)] public void should_be_able_to_get_series_detail(int tvdbId) { - var details = Subject.GetSeriesInfo(tvdbId); - - //ValidateSeries(details.Item1); - ValidateEpisodes(details.Item2); - } + UseRealHttp(); -// [Test] -// public void getting_details_of_invalid_series() -// { -// Assert.Throws(() => Subject.GetSeriesInfo(Int32.MaxValue)); -// -// ExceptionVerification.ExpectedWarns(1); -// } -// -// [Test] -// public void should_not_have_period_at_start_of_title_slug() -// { -// var details = Subject.GetSeriesInfo(79099); -// -// details.Item1.TitleSlug.Should().Be("dothack"); -// } + var episodes = Subject.GetEpisodeInfo(tvdbId); - private void ValidateSeries(Series series) - { - series.Should().NotBeNull(); - series.Title.Should().NotBeNullOrWhiteSpace(); - series.CleanTitle.Should().Be(Parser.Parser.CleanSeriesTitle(series.Title)); - series.Overview.Should().NotBeNullOrWhiteSpace(); - series.AirTime.Should().NotBeNullOrWhiteSpace(); - series.FirstAired.Should().HaveValue(); - series.FirstAired.Value.Kind.Should().Be(DateTimeKind.Utc); - series.Images.Should().NotBeEmpty(); - series.ImdbId.Should().NotBeNullOrWhiteSpace(); - series.Network.Should().NotBeNullOrWhiteSpace(); - series.Runtime.Should().BeGreaterThan(0); - series.TitleSlug.Should().NotBeNullOrWhiteSpace(); - series.TvRageId.Should().BeGreaterThan(0); - series.TvdbId.Should().BeGreaterThan(0); + ValidateEpisodes(episodes); } private void ValidateEpisodes(List episodes) @@ -91,30 +32,10 @@ namespace NzbDrone.Core.Test.MetadataSourceTests .Max(e => e.Count()).Should().Be(1); episodes.Should().Contain(c => c.SeasonNumber > 0); -// episodes.Should().Contain(c => !string.IsNullOrWhiteSpace(c.Overview)); - - foreach (var episode in episodes) - { - ValidateEpisode(episode); - //if atleast one episdoe has title it means parse it working. -// episodes.Should().Contain(c => !string.IsNullOrWhiteSpace(c.Title)); - } + episodes.Should().OnlyContain(c => c.SeasonNumber > 0 || c.EpisodeNumber > 0); } - private void ValidateEpisode(Episode episode) - { - episode.Should().NotBeNull(); - - //TODO: Is there a better way to validate that episode number or season number is greater than zero? - (episode.EpisodeNumber + episode.SeasonNumber).Should().NotBe(0); - episode.Should().NotBeNull(); - -// if (episode.AirDateUtc.HasValue) -// { -// episode.AirDateUtc.Value.Kind.Should().Be(DateTimeKind.Utc); -// } - } } } diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 06eefa992..d9fd547e2 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -107,6 +107,7 @@ + @@ -380,9 +381,6 @@ - - Always - Always diff --git a/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs b/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs index 16de304c9..bdada91a1 100644 --- a/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs @@ -24,6 +24,8 @@ namespace NzbDrone.Core.Test.TvTests [TestFixtureSetUp] public void TestFixture() { + UseRealHttp(); + _gameOfThrones = Mocker.Resolve().GetSeriesInfo(121361);//Game of thrones // Remove specials. diff --git a/src/NzbDrone.Core.Test/UpdateTests/UpdatePackageProviderFixture.cs b/src/NzbDrone.Core.Test/UpdateTests/UpdatePackageProviderFixture.cs index 7e7603c37..928b34241 100644 --- a/src/NzbDrone.Core.Test/UpdateTests/UpdatePackageProviderFixture.cs +++ b/src/NzbDrone.Core.Test/UpdateTests/UpdatePackageProviderFixture.cs @@ -1,6 +1,7 @@ using System; using FluentAssertions; using NUnit.Framework; +using NzbDrone.Common; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Update; @@ -12,7 +13,7 @@ namespace NzbDrone.Core.Test.UpdateTests public void no_update_when_version_higher() { UseRealHttp(); - Subject.GetLatestUpdate("master", new Version(10,0)).Should().BeNull(); + Subject.GetLatestUpdate("master", new Version(10, 0)).Should().BeNull(); } [Test] @@ -21,5 +22,21 @@ namespace NzbDrone.Core.Test.UpdateTests UseRealHttp(); Subject.GetLatestUpdate("master", new Version(2, 0)).Should().NotBeNull(); } + + + [Test] + public void should_get_recent_updates() + { + const string branch = "master"; + UseRealHttp(); + var recent = Subject.GetRecentUpdates(branch, 2); + + recent.Should().NotBeEmpty(); + recent.Should().OnlyContain(c => c.Hash.IsNotNullOrWhiteSpace()); + recent.Should().OnlyContain(c => c.FileName.Contains("Drone.master.2")); + recent.Should().OnlyContain(c => c.ReleaseDate.Year == 2014); + recent.Should().OnlyContain(c => c.Changes.New != null); + recent.Should().OnlyContain(c => c.Changes.Fixed != null); + } } } diff --git a/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs b/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs index e08094aa2..dd030e34f 100644 --- a/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs +++ b/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs @@ -104,7 +104,7 @@ namespace NzbDrone.Core.Test.UpdateTests Subject.Execute(new ApplicationUpdateCommand()); - Mocker.GetMock().Verify(c => c.DownloadFile(_updatePackage.Url, updateArchive)); + Mocker.GetMock().Verify(c => c.DownloadFile(_updatePackage.Url, updateArchive)); } [Test] @@ -124,8 +124,6 @@ namespace NzbDrone.Core.Test.UpdateTests Subject.Execute(new ApplicationUpdateCommand()); - - Mocker.GetMock().Verify(c => c.MoveFolder(updateClientFolder, _sandboxFolder)); } diff --git a/src/NzbDrone.Core/DataAugmentation/DailySeries/DailySeries.cs b/src/NzbDrone.Core/DataAugmentation/DailySeries/DailySeries.cs new file mode 100644 index 000000000..829ce6a24 --- /dev/null +++ b/src/NzbDrone.Core/DataAugmentation/DailySeries/DailySeries.cs @@ -0,0 +1,7 @@ +namespace NzbDrone.Core.DataAugmentation.DailySeries +{ + public class DailySeries + { + public int TvdbId { get; set; } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesDataProxy.cs b/src/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesDataProxy.cs index 9040f3944..9a432144e 100644 --- a/src/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesDataProxy.cs +++ b/src/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesDataProxy.cs @@ -1,27 +1,27 @@ using System; using System.Collections.Generic; +using System.Linq; using NLog; -using NzbDrone.Common; +using NzbDrone.Common.Cloud; using NzbDrone.Common.Http; -using NzbDrone.Common.Serializer; namespace NzbDrone.Core.DataAugmentation.DailySeries { - public interface IDailySeriesDataProxy { IEnumerable GetDailySeriesIds(); - bool IsDailySeries(int tvdbid); } public class DailySeriesDataProxy : IDailySeriesDataProxy { - private readonly IHttpProvider _httpProvider; + private readonly IHttpClient _httpClient; + private readonly IDroneServicesRequestBuilder _requestBuilder; private readonly Logger _logger; - public DailySeriesDataProxy(IHttpProvider httpProvider, Logger logger) + public DailySeriesDataProxy(IHttpClient httpClient, IDroneServicesRequestBuilder requestBuilder, Logger logger) { - _httpProvider = httpProvider; + _httpClient = httpClient; + _requestBuilder = requestBuilder; _logger = logger; } @@ -29,32 +29,15 @@ namespace NzbDrone.Core.DataAugmentation.DailySeries { try { - var dailySeriesIds = _httpProvider.DownloadString(Services.RootUrl + "/v1/DailySeries"); - - var seriesIds = Json.Deserialize>(dailySeriesIds); - - return seriesIds; + var dailySeriesRequest = _requestBuilder.Build("dailyseries"); + var response = _httpClient.Get>(dailySeriesRequest); + return response.Resource.Select(c => c.TvdbId); } catch (Exception ex) { _logger.WarnException("Failed to get Daily Series", ex); return new List(); } - - } - - public bool IsDailySeries(int tvdbid) - { - try - { - var result = _httpProvider.DownloadString(Services.RootUrl + "/v1/DailySeries?seriesId=" + tvdbid); - return Convert.ToBoolean(result); - } - catch (Exception ex) - { - _logger.WarnException("Failed to check Daily Series status for: " + tvdbid, ex); - return false; - } } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesService.cs b/src/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesService.cs index 823570958..6eb5f874a 100644 --- a/src/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesService.cs +++ b/src/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesService.cs @@ -1,44 +1,30 @@ -using NzbDrone.Core.Tv; +using System; +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Common.Cache; namespace NzbDrone.Core.DataAugmentation.DailySeries { public interface IDailySeriesService { - void UpdateDailySeries(); bool IsDailySeries(int tvdbid); } public class DailySeriesService : IDailySeriesService { - //TODO: add timer command - private readonly IDailySeriesDataProxy _proxy; - private readonly ISeriesService _seriesService; + private readonly ICached> _cache; - public DailySeriesService(IDailySeriesDataProxy proxy, ISeriesService seriesService) + public DailySeriesService(IDailySeriesDataProxy proxy, ICacheManager cacheManager) { _proxy = proxy; - _seriesService = seriesService; - } - - public void UpdateDailySeries() - { - var dailySeries = _proxy.GetDailySeriesIds(); - - foreach (var tvdbId in dailySeries) - { - var series = _seriesService.FindByTvdbId(tvdbId); - - if (series != null) - { - _seriesService.SetSeriesType(series.Id, SeriesTypes.Daily); - } - } + _cache = cacheManager.GetCache>(GetType()); } public bool IsDailySeries(int tvdbid) { - return _proxy.IsDailySeries(tvdbid); + var dailySeries = _cache.Get("all", () => _proxy.GetDailySeriesIds().ToList(), TimeSpan.FromHours(1)); + return dailySeries.Any(i => i == tvdbid); } } -} +} \ No newline at end of file diff --git a/src/NzbDrone.Core/DataAugmentation/Scene/SceneMappingProxy.cs b/src/NzbDrone.Core/DataAugmentation/Scene/SceneMappingProxy.cs index 54bea3a48..5fd62a5e1 100644 --- a/src/NzbDrone.Core/DataAugmentation/Scene/SceneMappingProxy.cs +++ b/src/NzbDrone.Core/DataAugmentation/Scene/SceneMappingProxy.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; -using NzbDrone.Common; +using NzbDrone.Common.Cloud; using NzbDrone.Common.Http; -using NzbDrone.Common.Serializer; namespace NzbDrone.Core.DataAugmentation.Scene { @@ -12,17 +11,19 @@ namespace NzbDrone.Core.DataAugmentation.Scene public class SceneMappingProxy : ISceneMappingProxy { - private readonly IHttpProvider _httpProvider; + private readonly IHttpClient _httpClient; + private readonly IDroneServicesRequestBuilder _requestBuilder; - public SceneMappingProxy(IHttpProvider httpProvider) + public SceneMappingProxy(IHttpClient httpClient, IDroneServicesRequestBuilder requestBuilder) { - _httpProvider = httpProvider; + _httpClient = httpClient; + _requestBuilder = requestBuilder; } public List Fetch() { - var mappingsJson = _httpProvider.DownloadString(Services.RootUrl + "/v1/SceneMapping"); - return Json.Deserialize>(mappingsJson); + var request = _requestBuilder.Build("/scenemapping"); + return _httpClient.Get>(request).Resource; } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/DataAugmentation/Xem/XemProxy.cs b/src/NzbDrone.Core/DataAugmentation/Xem/XemProxy.cs index 009fc8146..ba0186bf5 100644 --- a/src/NzbDrone.Core/DataAugmentation/Xem/XemProxy.cs +++ b/src/NzbDrone.Core/DataAugmentation/Xem/XemProxy.cs @@ -3,10 +3,9 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; using NLog; +using NzbDrone.Common.Http; using NzbDrone.Core.DataAugmentation.Scene; using NzbDrone.Core.DataAugmentation.Xem.Model; -using NzbDrone.Core.Rest; -using RestSharp; namespace NzbDrone.Core.DataAugmentation.Xem { @@ -20,34 +19,32 @@ namespace NzbDrone.Core.DataAugmentation.Xem public class XemProxy : IXemProxy { private readonly Logger _logger; + private readonly IHttpClient _httpClient; private const string XEM_BASE_URL = "http://thexem.de/map/"; private static readonly string[] IgnoredErrors = { "no single connection", "no show with the tvdb_id" }; + private HttpRequestBuilder _xemRequestBuilder; - public XemProxy(Logger logger) + public XemProxy(Logger logger, IHttpClient httpClient) { _logger = logger; - } - + _httpClient = httpClient; - private static RestRequest BuildRequest(string resource) - { - var req = new RestRequest(resource, Method.GET); - req.AddParameter("origin", "tvdb"); - return req; + _xemRequestBuilder = new HttpRequestBuilder(XEM_BASE_URL) + { + PostProcess = r => r.UriBuilder.SetQueryParam("origin", "tvdb") + }; } + public List GetXemSeriesIds() { _logger.Debug("Fetching Series IDs from"); - var restClient = RestClientFactory.BuildClient(XEM_BASE_URL); - - var request = BuildRequest("havemap"); - - var response = restClient.ExecuteAndValidate>>(request); + var request = _xemRequestBuilder.Build("/havemap"); + var response = _httpClient.Get>>(request).Resource; CheckForFailureResult(response); return response.Data.ToList(); @@ -57,13 +54,11 @@ namespace NzbDrone.Core.DataAugmentation.Xem { _logger.Debug("Fetching Mappings for: {0}", id); - var restClient = RestClientFactory.BuildClient(XEM_BASE_URL); - var request = BuildRequest("all"); - request.AddParameter("id", id); + var request = _xemRequestBuilder.Build("/all"); + request.UriBuilder.SetQueryParam("id", id); - var response = restClient.ExecuteAndValidate>>(request); - CheckForFailureResult(response); + var response = _httpClient.Get>>(request).Resource; return response.Data.Where(c => c.Scene != null).ToList(); } @@ -71,14 +66,11 @@ namespace NzbDrone.Core.DataAugmentation.Xem public List GetSceneTvdbNames() { _logger.Debug("Fetching alternate names"); - var restClient = RestClientFactory.BuildClient(XEM_BASE_URL); - var request = BuildRequest("allNames"); - request.AddParameter("origin", "tvdb"); - request.AddParameter("seasonNumbers", true); + var request = _xemRequestBuilder.Build("/allNames"); + request.UriBuilder.SetQueryParam("seasonNumbers", true); - var response = restClient.ExecuteAndValidate>>>(request); - CheckForFailureResult(response); + var response = _httpClient.Get>>>(request).Resource; var result = new List(); diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs index 76a87c911..2b492e490 100644 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs +++ b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs @@ -18,10 +18,10 @@ namespace NzbDrone.Core.Download.Clients.Nzbget public class Nzbget : DownloadClientBase { private readonly INzbgetProxy _proxy; - private readonly IHttpProvider _httpProvider; + private readonly IHttpClient _httpClient; public Nzbget(INzbgetProxy proxy, - IHttpProvider httpProvider, + IHttpClient httpClient, IConfigService configService, IDiskProvider diskProvider, IParsingService parsingService, @@ -29,7 +29,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget : base(configService, diskProvider, parsingService, logger) { _proxy = proxy; - _httpProvider = httpProvider; + _httpClient = httpClient; } public override DownloadProtocol Protocol @@ -49,7 +49,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget _logger.Info("Adding report [{0}] to the queue.", title); - using (var nzb = _httpProvider.DownloadStream(url)) + using (var nzb = _httpClient.Get(new HttpRequest(url)).GetStream()) { _logger.Info("Adding report [{0}] to the queue.", title); var response = _proxy.DownloadNzb(nzb, title, category, priority, Settings); @@ -87,7 +87,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget var totalSize = MakeInt64(item.FileSizeHi, item.FileSizeLo); var pausedSize = MakeInt64(item.PausedSizeHi, item.PausedSizeLo); var remainingSize = MakeInt64(item.RemainingSizeHi, item.RemainingSizeLo); - + var droneParameter = item.Parameters.SingleOrDefault(p => p.Name == "drone"); var queueItem = new DownloadClientItem(); @@ -152,7 +152,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget } var historyItems = new List(); - var successStatus = new[] {"SUCCESS", "NONE"}; + var successStatus = new[] { "SUCCESS", "NONE" }; foreach (var item in history) { @@ -190,7 +190,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget public override IEnumerable GetItems() { - Dictionary config = null; + Dictionary config = null; NzbgetCategory category = null; try { @@ -270,7 +270,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget if (name == null) yield break; var destDir = config.GetValueOrDefault("Category" + i + ".DestDir"); - + if (destDir.IsNullOrWhiteSpace()) { var mainDir = config.GetValueOrDefault("MainDir"); diff --git a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs index 44834d277..cc6d92c96 100644 --- a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs +++ b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs @@ -16,16 +16,16 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic { public class Pneumatic : DownloadClientBase { - private readonly IHttpProvider _httpProvider; + private readonly IHttpClient _httpClient; - public Pneumatic(IHttpProvider httpProvider, + public Pneumatic(IHttpClient httpClient, IConfigService configService, IDiskProvider diskProvider, IParsingService parsingService, Logger logger) : base(configService, diskProvider, parsingService, logger) { - _httpProvider = httpProvider; + _httpClient = httpClient; } public override DownloadProtocol Protocol @@ -52,7 +52,7 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic var nzbFile = Path.Combine(Settings.NzbFolder, title + ".nzb"); _logger.Debug("Downloading NZB from: {0} to: {1}", url, nzbFile); - _httpProvider.DownloadFile(url, nzbFile); + _httpClient.DownloadFile(url, nzbFile); _logger.Debug("NZB Download succeeded, saved to: {0}", nzbFile); diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs index 2256c0628..c6bf31b6f 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs @@ -18,11 +18,11 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd { public class Sabnzbd : DownloadClientBase { - private readonly IHttpProvider _httpProvider; private readonly ISabnzbdProxy _proxy; + private readonly IHttpClient _httpClient; public Sabnzbd(ISabnzbdProxy proxy, - IHttpProvider httpProvider, + IHttpClient httpClient, IConfigService configService, IDiskProvider diskProvider, IParsingService parsingService, @@ -30,7 +30,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd : base(configService, diskProvider, parsingService, logger) { _proxy = proxy; - _httpProvider = httpProvider; + _httpClient = httpClient; } public override DownloadProtocol Protocol @@ -48,7 +48,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd var category = Settings.TvCategory; var priority = remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority; - using (var nzb = _httpProvider.DownloadStream(url)) + using (var nzb = _httpClient.Get(new HttpRequest(url)).GetStream()) { _logger.Info("Adding report [{0}] to the queue.", title); var response = _proxy.DownloadNzb(nzb, title, category, priority, Settings); @@ -401,7 +401,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd return null; } - + private ValidationFailure TestGlobalConfig() { var config = _proxy.GetConfig(Settings); diff --git a/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs b/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs index 3dfe01648..4e0560764 100644 --- a/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs +++ b/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs @@ -19,10 +19,10 @@ namespace NzbDrone.Core.Download.Clients.UsenetBlackhole public class UsenetBlackhole : DownloadClientBase { private readonly IDiskScanService _diskScanService; - private readonly IHttpProvider _httpProvider; + private readonly IHttpClient _httpClient; public UsenetBlackhole(IDiskScanService diskScanService, - IHttpProvider httpProvider, + IHttpClient httpClient, IConfigService configService, IDiskProvider diskProvider, IParsingService parsingService, @@ -30,7 +30,7 @@ namespace NzbDrone.Core.Download.Clients.UsenetBlackhole : base(configService, diskProvider, parsingService, logger) { _diskScanService = diskScanService; - _httpProvider = httpProvider; + _httpClient = httpClient; } public override DownloadProtocol Protocol @@ -51,7 +51,7 @@ namespace NzbDrone.Core.Download.Clients.UsenetBlackhole var filename = Path.Combine(Settings.NzbFolder, title + ".nzb"); _logger.Debug("Downloading NZB from: {0} to: {1}", url, filename); - _httpProvider.DownloadFile(url, filename); + _httpClient.DownloadFile(url, filename); _logger.Debug("NZB Download succeeded, saved to: {0}", filename); return null; diff --git a/src/NzbDrone.Core/MediaCover/CoverAlreadyExistsSpecification.cs b/src/NzbDrone.Core/MediaCover/CoverAlreadyExistsSpecification.cs index ef0c1104b..83f1e13de 100644 --- a/src/NzbDrone.Core/MediaCover/CoverAlreadyExistsSpecification.cs +++ b/src/NzbDrone.Core/MediaCover/CoverAlreadyExistsSpecification.cs @@ -1,8 +1,5 @@ -using NLog; -using NzbDrone.Common; -using NzbDrone.Common.Disk; +using NzbDrone.Common.Disk; using NzbDrone.Common.Http; -using NzbDrone.Common.Serializer; namespace NzbDrone.Core.MediaCover { @@ -14,14 +11,12 @@ namespace NzbDrone.Core.MediaCover public class CoverAlreadyExistsSpecification : ICoverExistsSpecification { private readonly IDiskProvider _diskProvider; - private readonly IHttpProvider _httpProvider; - private readonly Logger _logger; + private readonly IHttpClient _httpClient; - public CoverAlreadyExistsSpecification(IDiskProvider diskProvider, IHttpProvider httpProvider, Logger logger) + public CoverAlreadyExistsSpecification(IDiskProvider diskProvider, IHttpClient httpClient) { _diskProvider = diskProvider; - _httpProvider = httpProvider; - _logger = logger; + _httpClient = httpClient; } public bool AlreadyExists(string url, string path) @@ -31,22 +26,9 @@ namespace NzbDrone.Core.MediaCover return false; } - var headers = _httpProvider.GetHeader(url); - - string sizeString; - - if (headers.TryGetValue(HttpProvider.CONTENT_LENGTH_HEADER, out sizeString)) - { - int size; - int.TryParse(sizeString, out size); - var fileSize = _diskProvider.GetFileSize(path); - - return fileSize == size; - } - - _logger.Warn("Couldn't find content-length header {0}", headers.ToJson()); - - return false; + var headers = _httpClient.Head(new HttpRequest(url)).Headers; + var fileSize = _diskProvider.GetFileSize(path); + return fileSize == headers.ContentLength; } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs index 2c5469de6..98b3d93df 100644 --- a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs +++ b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs @@ -25,7 +25,7 @@ namespace NzbDrone.Core.MediaCover IHandleAsync, IMapCoversToLocal { - private readonly IHttpProvider _httpProvider; + private readonly IHttpClient _httpClient; private readonly IDiskProvider _diskProvider; private readonly ICoverExistsSpecification _coverExistsSpecification; private readonly IConfigFileProvider _configFileProvider; @@ -34,15 +34,15 @@ namespace NzbDrone.Core.MediaCover private readonly string _coverRootFolder; - public MediaCoverService(IHttpProvider httpProvider, + public MediaCoverService(IHttpClient httpClient, IDiskProvider diskProvider, IAppFolderInfo appFolderInfo, - ICoverExistsSpecification coverExistsSpecification, - IConfigFileProvider configFileProvider, + ICoverExistsSpecification coverExistsSpecification, + IConfigFileProvider configFileProvider, IEventAggregator eventAggregator, Logger logger) { - _httpProvider = httpProvider; + _httpClient = httpClient; _diskProvider = diskProvider; _coverExistsSpecification = coverExistsSpecification; _configFileProvider = configFileProvider; @@ -106,7 +106,7 @@ namespace NzbDrone.Core.MediaCover var fileName = GetCoverPath(series.Id, cover.CoverType); _logger.Info("Downloading {0} for {1} {2}", cover.CoverType, series, cover.Url); - _httpProvider.DownloadFile(cover.Url, fileName); + _httpClient.DownloadFile(cover.Url, fileName); } public void HandleAsync(SeriesUpdatedEvent message) diff --git a/src/NzbDrone.Core/MetaData/MetadataService.cs b/src/NzbDrone.Core/MetaData/MetadataService.cs index 80d1d01d0..01fde63fd 100644 --- a/src/NzbDrone.Core/MetaData/MetadataService.cs +++ b/src/NzbDrone.Core/MetaData/MetadataService.cs @@ -29,7 +29,7 @@ namespace NzbDrone.Core.Metadata private readonly IMediaFileService _mediaFileService; private readonly IEpisodeService _episodeService; private readonly IDiskProvider _diskProvider; - private readonly IHttpProvider _httpProvider; + private readonly IHttpClient _httpClient; private readonly IConfigService _configService; private readonly IEventAggregator _eventAggregator; private readonly Logger _logger; @@ -40,7 +40,7 @@ namespace NzbDrone.Core.Metadata IMediaFileService mediaFileService, IEpisodeService episodeService, IDiskProvider diskProvider, - IHttpProvider httpProvider, + IHttpClient httpClient, IConfigService configService, IEventAggregator eventAggregator, Logger logger) @@ -51,7 +51,7 @@ namespace NzbDrone.Core.Metadata _mediaFileService = mediaFileService; _episodeService = episodeService; _diskProvider = diskProvider; - _httpProvider = httpProvider; + _httpClient = httpClient; _configService = configService; _eventAggregator = eventAggregator; _logger = logger; @@ -336,7 +336,7 @@ namespace NzbDrone.Core.Metadata { try { - _httpProvider.DownloadFile(url, path); + _httpClient.DownloadFile(url, path); SetFilePermissions(path); } catch (WebException e) diff --git a/src/NzbDrone.Core/MetadataSource/TraktProxy.cs b/src/NzbDrone.Core/MetadataSource/TraktProxy.cs index c5f4d8724..8e33a55f1 100644 --- a/src/NzbDrone.Core/MetadataSource/TraktProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/TraktProxy.cs @@ -2,86 +2,93 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net; using System.Text.RegularExpressions; -using System.Web; using NLog; using NzbDrone.Common; +using NzbDrone.Common.Http; using NzbDrone.Core.MediaCover; using NzbDrone.Core.MetadataSource.Trakt; using NzbDrone.Core.Tv; -using RestSharp; using Episode = NzbDrone.Core.Tv.Episode; -using NzbDrone.Core.Rest; namespace NzbDrone.Core.MetadataSource { public class TraktProxy : ISearchForNewSeries, IProvideSeriesInfo { private readonly Logger _logger; + private readonly IHttpClient _httpClient; private static readonly Regex CollapseSpaceRegex = new Regex(@"\s+", RegexOptions.Compiled); private static readonly Regex InvalidSearchCharRegex = new Regex(@"(?:\*|\(|\)|'|!|@|\+)", RegexOptions.Compiled); - public TraktProxy(Logger logger) + + private readonly HttpRequestBuilder _requestBuilder; + + public TraktProxy(Logger logger, IHttpClient httpClient) { + _requestBuilder = new HttpRequestBuilder("http://api.trakt.tv/{path}/{resource}.json/bc3c2c460f22cbb01c264022b540e191"); _logger = logger; + _httpClient = httpClient; } - public List SearchForNewSeries(string title) + private IEnumerable SearchTrakt(string title) { - try + + HttpRequest request; + + var lowerTitle = title.ToLowerInvariant(); + + if (lowerTitle.StartsWith("tvdb:") || lowerTitle.StartsWith("tvdbid:") || lowerTitle.StartsWith("slug:")) { - if (title.StartsWith("imdb:") || title.StartsWith("imdbid:")) + var slug = lowerTitle.Split(':')[1].Trim(); + + if (slug.IsNullOrWhiteSpace() || slug.Any(char.IsWhiteSpace)) { - var slug = title.Split(':')[1].TrimStart('t'); + return Enumerable.Empty(); + } - if (slug.IsNullOrWhiteSpace() || !slug.All(char.IsDigit) || slug.Length < 7) - { - return new List(); - } + request = _requestBuilder.Build("/{slug}/extended"); - title = "tt" + slug; - } + request.AddSegment("path", "show"); + request.AddSegment("resource", "summary"); + request.AddSegment("slug", GetSearchTerm(slug)); - if (title.StartsWith("tvdb:") || title.StartsWith("tvdbid:") || title.StartsWith("slug:")) - { - try - { - var slug = title.Split(':')[1]; + return new List { _httpClient.Get(request).Resource }; + } - if (slug.IsNullOrWhiteSpace() || slug.Any(char.IsWhiteSpace)) - { - return new List(); - } + if (lowerTitle.StartsWith("imdb:") || lowerTitle.StartsWith("imdbid:")) + { + var slug = lowerTitle.Split(':')[1].TrimStart('t').Trim(); - var client = BuildClient("show", "summary"); - var restRequest = new RestRequest(GetSearchTerm(slug) + "/extended"); - var response = client.ExecuteAndValidate(restRequest); - - return new List { MapSeries(response) }; - } - catch (RestException ex) - { - if (ex.Response.StatusCode == HttpStatusCode.NotFound) - { - return new List(); - } - - throw; - } - } - else + if (slug.IsNullOrWhiteSpace() || !slug.All(char.IsDigit) || slug.Length < 7) { - var client = BuildClient("search", "shows"); - var restRequest = new RestRequest(GetSearchTerm(title) + "/30/seasons"); - var response = client.ExecuteAndValidate>(restRequest); - - return response.Select(MapSeries) - .OrderBy(v => title.LevenshteinDistanceClean(v.Title)) - .ToList(); + return Enumerable.Empty(); } + + title = "tt" + slug; } - catch (WebException) + + request = _requestBuilder.Build(""); + + request.AddSegment("path", "search"); + request.AddSegment("resource", "shows"); + request.UriBuilder.SetQueryParam("query", GetSearchTerm(title)); + request.UriBuilder.SetQueryParam("seasons", true); + + return _httpClient.Get>(request).Resource; + } + + + public List SearchForNewSeries(string title) + { + try + { + var series = SearchTrakt(title.Trim()); + + return series.Select(MapSeries) + .OrderBy(s => title.LevenshteinDistanceClean(s.Title)) + .ToList(); + } + catch (HttpException) { throw new TraktException("Search for '{0}' failed. Unable to communicate with Trakt.", title); } @@ -94,20 +101,31 @@ namespace NzbDrone.Core.MetadataSource public Tuple> GetSeriesInfo(int tvdbSeriesId) { - var client = BuildClient("show", "summary"); - var restRequest = new RestRequest(tvdbSeriesId.ToString() + "/extended"); - var response = client.ExecuteAndValidate(restRequest); + + var request = _requestBuilder.Build("/{tvdbId}/extended"); + + request.AddSegment("path", "show"); + request.AddSegment("resource", "summary"); + request.AddSegment("tvdbId", tvdbSeriesId.ToString()); + + var response = _httpClient.Get(request).Resource; + + /* + + var client = BuildClient("show", "summary"); + var restRequest = new RestRequest(tvdbSeriesId + "/extended"); + var response = client.ExecuteAndValidate(restRequest);*/ var episodes = response.seasons.SelectMany(c => c.episodes).Select(MapEpisode).ToList(); var series = MapSeries(response); return new Tuple>(series, episodes); } - - private static IRestClient BuildClient(string resource, string method) - { - return RestClientFactory.BuildClient(string.Format("http://api.trakt.tv/{0}/{1}.json/bc3c2c460f22cbb01c264022b540e191", resource, method)); - } + /* + private static IRestClient BuildClient(string resource, string method) + { + return RestClientFactory.BuildClient(string.Format("http://api.trakt.tv/{0}/{1}.json/bc3c2c460f22cbb01c264022b540e191", resource, method)); + }*/ private static Series MapSeries(Show show) { @@ -139,6 +157,18 @@ namespace NzbDrone.Core.MetadataSource return series; } + private static String GetTitleSlug(String url) + { + var slug = url.ToLower().Replace("http://trakt.tv/show/", ""); + + if (slug.StartsWith(".")) + { + slug = "dot" + slug.Substring(1); + } + + return slug; + } + private static Episode MapEpisode(Trakt.Episode traktEpisode) { var episode = new Episode(); @@ -179,13 +209,6 @@ namespace NzbDrone.Core.MetadataSource return SeriesStatusType.Continuing; } - private static DateTime? FromEpoch(long ticks) - { - if (ticks == 0) return null; - - return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Unspecified).AddSeconds(ticks); - } - private static DateTime? FromIso(string iso) { DateTime result; @@ -213,7 +236,7 @@ namespace NzbDrone.Core.MetadataSource phrase = InvalidSearchCharRegex.Replace(phrase, ""); phrase = CollapseSpaceRegex.Replace(phrase, " ").Trim().ToLower(); phrase = phrase.Trim('-'); - phrase = HttpUtility.UrlEncode(phrase); + phrase = System.Web.HttpUtility.UrlEncode(phrase); return phrase; } @@ -279,23 +302,13 @@ namespace NzbDrone.Core.MetadataSource { season.Images.Add(new MediaCover.MediaCover(MediaCoverTypes.Poster, traktSeason.images.poster)); } - + seasons.Add(season); } return seasons; } - private static String GetTitleSlug(String url) - { - var slug = url.ToLower().Replace("http://trakt.tv/show/", ""); - - if (slug.StartsWith(".")) - { - slug = "dot" + slug.Substring(1); - } - return slug; - } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MetadataSource/Tvdb/TvdbProxy.cs b/src/NzbDrone.Core/MetadataSource/Tvdb/TvdbProxy.cs index 88af8bad4..39a5772c7 100644 --- a/src/NzbDrone.Core/MetadataSource/Tvdb/TvdbProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/Tvdb/TvdbProxy.cs @@ -1,12 +1,10 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Linq; -using NzbDrone.Core.Rest; +using NzbDrone.Common.Http; using NzbDrone.Core.Indexers; using NzbDrone.Core.Tv; -using RestSharp; namespace NzbDrone.Core.MetadataSource.Tvdb { @@ -17,37 +15,22 @@ namespace NzbDrone.Core.MetadataSource.Tvdb public class TvdbProxy : ITvdbProxy { - public Tuple> GetSeriesInfo(int tvdbSeriesId) - { - var client = BuildClient("series"); - var request = new RestRequest(tvdbSeriesId + "/all"); - - var response = client.Execute(request); - - var xml = XDocument.Load(new StringReader(response.Content)); - - var episodes = xml.Descendants("Episode").Select(MapEpisode).ToList(); - var series = MapSeries(xml.Element("Series")); - - return new Tuple>(series, episodes); - } + private readonly IHttpClient _httpClient; - public List GetEpisodeInfo(int tvdbSeriesId) - { - return GetSeriesInfo(tvdbSeriesId).Item2; - } - - private static IRestClient BuildClient(string resource) + public TvdbProxy(IHttpClient httpClient) { - return RestClientFactory.BuildClient(String.Format("http://thetvdb.com/data/{0}", resource)); + _httpClient = httpClient; } - private static Series MapSeries(XElement item) + public List GetEpisodeInfo(int tvdbSeriesId) { - //TODO: We should map all the data incase we want to actually use it - var series = new Series(); + var httpRequest = new HttpRequest("http://thetvdb.com/data/series/{tvdbId}/all/"); + httpRequest.AddSegment("tvdbId", tvdbSeriesId.ToString()); + var response = _httpClient.Get(httpRequest); - return series; + var xml = XDocument.Load(new StringReader(response.Content)); + var episodes = xml.Descendants("Episode").Select(MapEpisode).ToList(); + return episodes; } private static Episode MapEpisode(XElement item) diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index c92a948cb..539e182d3 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -128,6 +128,7 @@ + diff --git a/src/NzbDrone.Core/Update/InstallUpdateService.cs b/src/NzbDrone.Core/Update/InstallUpdateService.cs index 83d459f4e..64a17cafc 100644 --- a/src/NzbDrone.Core/Update/InstallUpdateService.cs +++ b/src/NzbDrone.Core/Update/InstallUpdateService.cs @@ -26,7 +26,7 @@ namespace NzbDrone.Core.Update private readonly IAppFolderInfo _appFolderInfo; private readonly IDiskProvider _diskProvider; - private readonly IHttpProvider _httpProvider; + private readonly IHttpClient _httpClient; private readonly IArchiveService _archiveService; private readonly IProcessProvider _processProvider; private readonly IVerifyUpdates _updateVerifier; @@ -36,7 +36,7 @@ namespace NzbDrone.Core.Update public InstallUpdateService(ICheckUpdateService checkUpdateService, IAppFolderInfo appFolderInfo, - IDiskProvider diskProvider, IHttpProvider httpProvider, + IDiskProvider diskProvider, IHttpClient httpClient, IArchiveService archiveService, IProcessProvider processProvider, IVerifyUpdates updateVerifier, IConfigFileProvider configFileProvider, @@ -51,7 +51,7 @@ namespace NzbDrone.Core.Update _checkUpdateService = checkUpdateService; _appFolderInfo = appFolderInfo; _diskProvider = diskProvider; - _httpProvider = httpProvider; + _httpClient = httpClient; _archiveService = archiveService; _processProvider = processProvider; _updateVerifier = updateVerifier; @@ -79,7 +79,7 @@ namespace NzbDrone.Core.Update _logger.ProgressInfo("Downloading update {0}", updatePackage.Version); _logger.Debug("Downloading update package from [{0}] to [{1}]", updatePackage.Url, packageDestination); - _httpProvider.DownloadFile(updatePackage.Url, packageDestination); + _httpClient.DownloadFile(updatePackage.Url, packageDestination); _logger.ProgressInfo("Verifying update package"); diff --git a/src/NzbDrone.Core/Update/RecentUpdateProvider.cs b/src/NzbDrone.Core/Update/RecentUpdateProvider.cs index 96a915694..4245cdde8 100644 --- a/src/NzbDrone.Core/Update/RecentUpdateProvider.cs +++ b/src/NzbDrone.Core/Update/RecentUpdateProvider.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Core.Configuration; namespace NzbDrone.Core.Update @@ -23,7 +24,7 @@ namespace NzbDrone.Core.Update public List GetRecentUpdatePackages() { var branch = _configFileProvider.Branch; - return _updatePackageProvider.GetRecentUpdates(branch); + return _updatePackageProvider.GetRecentUpdates(branch, BuildInfo.Version.Major); } } } diff --git a/src/NzbDrone.Core/Update/UpdatePackage.cs b/src/NzbDrone.Core/Update/UpdatePackage.cs index 94ffa1fd0..299abd3de 100644 --- a/src/NzbDrone.Core/Update/UpdatePackage.cs +++ b/src/NzbDrone.Core/Update/UpdatePackage.cs @@ -6,7 +6,6 @@ namespace NzbDrone.Core.Update public class UpdatePackage { public Version Version { get; set; } - public String Branch { get; set; } public DateTime ReleaseDate { get; set; } public String FileName { get; set; } public String Url { get; set; } diff --git a/src/NzbDrone.Core/Update/UpdatePackageProvider.cs b/src/NzbDrone.Core/Update/UpdatePackageProvider.cs index 5096a9788..3a2fabe19 100644 --- a/src/NzbDrone.Core/Update/UpdatePackageProvider.cs +++ b/src/NzbDrone.Core/Update/UpdatePackageProvider.cs @@ -1,50 +1,51 @@ using System; using System.Collections.Generic; -using NzbDrone.Common; +using NzbDrone.Common.Cloud; using NzbDrone.Common.EnvironmentInfo; -using RestSharp; -using NzbDrone.Core.Rest; +using NzbDrone.Common.Http; namespace NzbDrone.Core.Update { public interface IUpdatePackageProvider { UpdatePackage GetLatestUpdate(string branch, Version currentVersion); - List GetRecentUpdates(string branch); + List GetRecentUpdates(string branch, int majorVersion); } public class UpdatePackageProvider : IUpdatePackageProvider { - public UpdatePackage GetLatestUpdate(string branch, Version currentVersion) - { - var restClient = RestClientFactory.BuildClient(Services.RootUrl); + private readonly IHttpClient _httpClient; + private readonly IDroneServicesRequestBuilder _requestBuilder; - var request = new RestRequest("/v1/update/{branch}"); + public UpdatePackageProvider(IHttpClient httpClient, IDroneServicesRequestBuilder requestBuilder) + { + _httpClient = httpClient; + _requestBuilder = requestBuilder; + } - request.AddParameter("version", currentVersion); - request.AddParameter("os", OsInfo.Os.ToString().ToLowerInvariant()); - request.AddUrlSegment("branch", branch); + public UpdatePackage GetLatestUpdate(string branch, Version currentVersion) + { + var request = _requestBuilder.Build("/update/{branch}"); + request.UriBuilder.SetQueryParam("version", currentVersion); + request.UriBuilder.SetQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant()); + request.AddSegment("branch", branch); - var update = restClient.ExecuteAndValidate(request); + var update = _httpClient.Get(request).Resource; if (!update.Available) return null; return update.UpdatePackage; } - public List GetRecentUpdates(string branch) + public List GetRecentUpdates(string branch, int majorVersion) { - var restClient = RestClientFactory.BuildClient(Services.RootUrl); - - var request = new RestRequest("/v1/update/{branch}/changes"); - - request.AddParameter("majorVersion", BuildInfo.Version.Major); - request.AddParameter("os", OsInfo.Os.ToString().ToLowerInvariant()); - request.AddUrlSegment("branch", branch); + var request = _requestBuilder.Build("/update/{branch}/changes"); + request.UriBuilder.SetQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant()); + request.AddSegment("branch", branch); - var updates = restClient.ExecuteAndValidate>(request); + var updates = _httpClient.Get>(request); - return updates; + return updates.Resource; } } } \ No newline at end of file diff --git a/src/NzbDrone.Test.Common/LoggingTest.cs b/src/NzbDrone.Test.Common/LoggingTest.cs index dd0434b1a..bec3ba054 100644 --- a/src/NzbDrone.Test.Common/LoggingTest.cs +++ b/src/NzbDrone.Test.Common/LoggingTest.cs @@ -8,14 +8,12 @@ namespace NzbDrone.Test.Common { public abstract class LoggingTest { - protected static Logger TestLogger; + protected static readonly Logger TestLogger = LogManager.GetLogger("TestLogger"); protected static void InitLogging() { new StartupContext(); - TestLogger = LogManager.GetLogger("TestLogger"); - if (LogManager.Configuration == null || LogManager.Configuration is XmlLoggingConfiguration) { LogManager.Configuration = new LoggingConfiguration(); @@ -44,8 +42,6 @@ namespace NzbDrone.Test.Common [TearDown] public void LoggingDownBase() { - - //can't use because of a bug in mono with 2.6.2, //https://bugs.launchpad.net/nunitv2/+bug/1076932 if (BuildInfo.IsDebug && TestContext.CurrentContext.Result.State == TestState.Success) diff --git a/src/NzbDrone.Test.Common/TestBase.cs b/src/NzbDrone.Test.Common/TestBase.cs index b04231d57..08e7b3451 100644 --- a/src/NzbDrone.Test.Common/TestBase.cs +++ b/src/NzbDrone.Test.Common/TestBase.cs @@ -54,6 +54,9 @@ namespace NzbDrone.Test.Common if (_mocker == null) { _mocker = new AutoMoqer(); + _mocker.SetConstant(new CacheManager()); + _mocker.SetConstant(new StartupContext(new string[0])); + _mocker.SetConstant(TestLogger); } return _mocker; @@ -89,11 +92,7 @@ namespace NzbDrone.Test.Common GetType().IsPublic.Should().BeTrue("All Test fixtures should be public to work in mono."); - Mocker.SetConstant(new CacheManager()); - Mocker.SetConstant(LogManager.GetLogger("TestLogger")); - - Mocker.SetConstant(new StartupContext(new string[0])); LogManager.ReconfigExistingLoggers(); @@ -140,7 +139,7 @@ namespace NzbDrone.Test.Common { if (!OsInfo.IsMono) { - throw new IgnoreException("linux specific test"); + throw new IgnoreException("mono specific test"); } }