Refactored HttpRequest and HttpRequestBuilder, moving most of the logic to the HttpRequestBuilder.
Added ContentSummary to be able to describe the ContentData in a human readable form. (Useful for JsonRpc and FormData).
This commit is contained in:
parent
7818f0c59b
commit
2ffbbb0e71
|
@ -58,18 +58,20 @@ namespace NzbDrone.Common.Test.Http
|
||||||
|
|
||||||
var response = Subject.Get<HttpBinResource>(request);
|
var response = Subject.Get<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Url.Should().Be(request.Url.ToString());
|
response.Resource.Url.Should().Be(request.Url.AbsoluteUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_execute_simple_post()
|
public void should_execute_simple_post()
|
||||||
{
|
{
|
||||||
|
var message = "{ my: 1 }";
|
||||||
|
|
||||||
var request = new HttpRequest("http://eu.httpbin.org/post");
|
var request = new HttpRequest("http://eu.httpbin.org/post");
|
||||||
request.Body = "{ my: 1 }";
|
request.SetContent(message);
|
||||||
|
|
||||||
var response = Subject.Post<HttpBinResource>(request);
|
var response = Subject.Post<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Data.Should().Be(request.Body);
|
response.Resource.Data.Should().Be(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("gzip")]
|
[TestCase("gzip")]
|
||||||
|
@ -162,7 +164,7 @@ namespace NzbDrone.Common.Test.Http
|
||||||
public void should_send_cookie()
|
public void should_send_cookie()
|
||||||
{
|
{
|
||||||
var request = new HttpRequest("http://eu.httpbin.org/get");
|
var request = new HttpRequest("http://eu.httpbin.org/get");
|
||||||
request.AddCookie("my", "cookie");
|
request.Cookies["my"] = "cookie";
|
||||||
|
|
||||||
var response = Subject.Get<HttpBinResource>(request);
|
var response = Subject.Get<HttpBinResource>(request);
|
||||||
|
|
||||||
|
@ -176,7 +178,7 @@ namespace NzbDrone.Common.Test.Http
|
||||||
public void GivenOldCookie()
|
public void GivenOldCookie()
|
||||||
{
|
{
|
||||||
var oldRequest = new HttpRequest("http://eu.httpbin.org/get");
|
var oldRequest = new HttpRequest("http://eu.httpbin.org/get");
|
||||||
oldRequest.AddCookie("my", "cookie");
|
oldRequest.Cookies["my"] = "cookie";
|
||||||
|
|
||||||
var oldClient = new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<ICacheManager>(), Mocker.Resolve<IRateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), Mocker.Resolve<Logger>());
|
var oldClient = new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<ICacheManager>(), Mocker.Resolve<IRateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), Mocker.Resolve<Logger>());
|
||||||
|
|
||||||
|
@ -260,7 +262,7 @@ namespace NzbDrone.Common.Test.Http
|
||||||
var requestSet = new HttpRequest("http://eu.httpbin.org/cookies/set?my=cookie");
|
var requestSet = new HttpRequest("http://eu.httpbin.org/cookies/set?my=cookie");
|
||||||
requestSet.AllowAutoRedirect = false;
|
requestSet.AllowAutoRedirect = false;
|
||||||
requestSet.StoreResponseCookie = true;
|
requestSet.StoreResponseCookie = true;
|
||||||
requestSet.AddCookie("my", "oldcookie");
|
requestSet.Cookies["my"] = "oldcookie";
|
||||||
|
|
||||||
var responseSet = Subject.Get(requestSet);
|
var responseSet = Subject.Get(requestSet);
|
||||||
|
|
||||||
|
@ -322,10 +324,10 @@ namespace NzbDrone.Common.Test.Http
|
||||||
{
|
{
|
||||||
// the date is bad in the below - should be 13-Jul-2016
|
// the date is bad in the below - should be 13-Jul-2016
|
||||||
string malformedCookie = @"__cfduid=d29e686a9d65800021c66faca0a29b4261436890790; expires=Wed, 13-Jul-16 16:19:50 GMT; path=/; HttpOnly";
|
string malformedCookie = @"__cfduid=d29e686a9d65800021c66faca0a29b4261436890790; expires=Wed, 13-Jul-16 16:19:50 GMT; path=/; HttpOnly";
|
||||||
string url = "http://eu.httpbin.org/response-headers?Set-Cookie=" +
|
var requestSet = new HttpRequestBuilder("http://eu.httpbin.org/response-headers")
|
||||||
System.Uri.EscapeUriString(malformedCookie);
|
.AddQueryParam("Set-Cookie", malformedCookie)
|
||||||
|
.Build();
|
||||||
|
|
||||||
var requestSet = new HttpRequest(url);
|
|
||||||
requestSet.AllowAutoRedirect = false;
|
requestSet.AllowAutoRedirect = false;
|
||||||
requestSet.StoreResponseCookie = true;
|
requestSet.StoreResponseCookie = true;
|
||||||
|
|
||||||
|
@ -376,6 +378,21 @@ namespace NzbDrone.Common.Test.Http
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void should_submit_formparameters_in_body()
|
||||||
|
{
|
||||||
|
Assert.Fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void should_submit_attachments_as_multipart()
|
||||||
|
{
|
||||||
|
Assert.Fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void should_submit_formparameters_as_multipart_if_attachments_exist()
|
||||||
|
{
|
||||||
|
Assert.Fail();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HttpBinResource
|
public class HttpBinResource
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using FluentAssertions;
|
using System;
|
||||||
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
|
@ -8,14 +9,32 @@ namespace NzbDrone.Common.Test.Http
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class HttpRequestBuilderFixture : TestBase
|
public class HttpRequestBuilderFixture : TestBase
|
||||||
{
|
{
|
||||||
|
[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 requestBuilder = new HttpRequestBuilder(url);
|
||||||
|
|
||||||
|
requestBuilder.SetSegment("seg", "dir");
|
||||||
|
|
||||||
|
requestBuilder.Build().Url.Should().Be(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void shouldnt_add_value_for_nonexisting_segment()
|
||||||
|
{
|
||||||
|
var requestBuilder = new HttpRequestBuilder("http://host/{seg}/some");
|
||||||
|
Assert.Throws<InvalidOperationException>(() => requestBuilder.SetSegment("seg2", "dir"));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_remove_duplicated_slashes()
|
public void should_remove_duplicated_slashes()
|
||||||
{
|
{
|
||||||
var builder = new HttpRequestBuilder("http://domain/");
|
var builder = new HttpRequestBuilder("http://domain/");
|
||||||
|
|
||||||
var request = builder.Build("/v1/");
|
var request = builder.Resource("/v1/").Build();
|
||||||
|
|
||||||
request.Url.ToString().Should().Be("http://domain/v1/");
|
request.Url.AbsoluteUri.Should().Be("http://domain/v1/");
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,22 +8,5 @@ namespace NzbDrone.Common.Test.Http
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class HttpRequestFixture
|
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<InvalidOperationException>(() => request.AddSegment("seg2", "dir"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,19 +0,0 @@
|
||||||
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.sonarr.tv/v1/";
|
|
||||||
|
|
||||||
public DroneServicesHttpRequestBuilder()
|
|
||||||
: base(ROOT_URL)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
|
||||||
|
namespace NzbDrone.Common.Cloud
|
||||||
|
{
|
||||||
|
public interface ISonarrCloudRequestBuilder
|
||||||
|
{
|
||||||
|
IHttpRequestBuilderFactory Services { get; }
|
||||||
|
IHttpRequestBuilderFactory SkyHookTvdb { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SonarrCloudRequestBuilder : ISonarrCloudRequestBuilder
|
||||||
|
{
|
||||||
|
public SonarrCloudRequestBuilder()
|
||||||
|
{
|
||||||
|
Services = new HttpRequestBuilder("http://services.sonarr.tv/v1/")
|
||||||
|
.CreateFactory();
|
||||||
|
|
||||||
|
SkyHookTvdb = new HttpRequestBuilder("http://skyhook.sonarr.tv/v1/tvdb/{route}/{language}/")
|
||||||
|
.SetSegment("language", "en")
|
||||||
|
.CreateFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IHttpRequestBuilderFactory Services { get; private set; }
|
||||||
|
|
||||||
|
public IHttpRequestBuilderFactory SkyHookTvdb { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ namespace NzbDrone.Common.Extensions
|
||||||
|
|
||||||
public static void Add<TKey, TValue>(this ICollection<KeyValuePair<TKey, TValue>> collection, TKey key, TValue value)
|
public static void Add<TKey, TValue>(this ICollection<KeyValuePair<TKey, TValue>> collection, TKey key, TValue value)
|
||||||
{
|
{
|
||||||
collection.Add(key, value);
|
collection.Add(new KeyValuePair<TKey, TValue>(key, value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,6 +86,11 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||||
curlEasy.UserAgent = UserAgentBuilder.UserAgent;
|
curlEasy.UserAgent = UserAgentBuilder.UserAgent;
|
||||||
curlEasy.FollowLocation = request.AllowAutoRedirect;
|
curlEasy.FollowLocation = request.AllowAutoRedirect;
|
||||||
|
|
||||||
|
if (request.RequestTimeout != TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
curlEasy.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
if (OsInfo.IsWindows)
|
if (OsInfo.IsWindows)
|
||||||
{
|
{
|
||||||
curlEasy.CaInfo = "curl-ca-bundle.crt";
|
curlEasy.CaInfo = "curl-ca-bundle.crt";
|
||||||
|
@ -96,11 +101,10 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||||
curlEasy.Cookie = cookies.GetCookieHeader(request.Url);
|
curlEasy.Cookie = cookies.GetCookieHeader(request.Url);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!request.Body.IsNullOrWhiteSpace())
|
if (request.ContentData != null)
|
||||||
{
|
{
|
||||||
// TODO: This might not go well with encoding.
|
curlEasy.PostFieldSize = request.ContentData.Length;
|
||||||
curlEasy.PostFieldSize = request.Body.Length;
|
curlEasy.SetOpt(CurlOption.CopyPostFields, new string(Array.ConvertAll(request.ContentData, v => (char)v)));
|
||||||
curlEasy.SetOpt(CurlOption.CopyPostFields, request.Body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Yes, we have to keep a ref to the object to prevent corrupting the unmanaged state
|
// Yes, we have to keep a ref to the object to prevent corrupting the unmanaged state
|
||||||
|
|
|
@ -23,19 +23,22 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||||
webRequest.ContentLength = 0;
|
webRequest.ContentLength = 0;
|
||||||
webRequest.CookieContainer = cookies;
|
webRequest.CookieContainer = cookies;
|
||||||
|
|
||||||
|
if (request.RequestTimeout != TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
webRequest.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalMilliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
if (request.Headers != null)
|
if (request.Headers != null)
|
||||||
{
|
{
|
||||||
AddRequestHeaders(webRequest, request.Headers);
|
AddRequestHeaders(webRequest, request.Headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!request.Body.IsNullOrWhiteSpace())
|
if (request.ContentData != null)
|
||||||
{
|
{
|
||||||
var bytes = request.Headers.GetEncodingFromContentType().GetBytes(request.Body.ToCharArray());
|
webRequest.ContentLength = request.ContentData.Length;
|
||||||
|
|
||||||
webRequest.ContentLength = bytes.Length;
|
|
||||||
using (var writeStream = webRequest.GetRequestStream())
|
using (var writeStream = webRequest.GetRequestStream())
|
||||||
{
|
{
|
||||||
writeStream.Write(bytes, 0, bytes.Length);
|
writeStream.Write(request.ContentData, 0, request.ContentData.Length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,43 +78,43 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||||
switch (header.Key)
|
switch (header.Key)
|
||||||
{
|
{
|
||||||
case "Accept":
|
case "Accept":
|
||||||
webRequest.Accept = header.Value.ToString();
|
webRequest.Accept = header.Value;
|
||||||
break;
|
break;
|
||||||
case "Connection":
|
case "Connection":
|
||||||
webRequest.Connection = header.Value.ToString();
|
webRequest.Connection = header.Value;
|
||||||
break;
|
break;
|
||||||
case "Content-Length":
|
case "Content-Length":
|
||||||
webRequest.ContentLength = Convert.ToInt64(header.Value);
|
webRequest.ContentLength = Convert.ToInt64(header.Value);
|
||||||
break;
|
break;
|
||||||
case "Content-Type":
|
case "Content-Type":
|
||||||
webRequest.ContentType = header.Value.ToString();
|
webRequest.ContentType = header.Value;
|
||||||
break;
|
break;
|
||||||
case "Date":
|
case "Date":
|
||||||
webRequest.Date = (DateTime)header.Value;
|
webRequest.Date = HttpHeader.ParseDateTime(header.Value);
|
||||||
break;
|
break;
|
||||||
case "Expect":
|
case "Expect":
|
||||||
webRequest.Expect = header.Value.ToString();
|
webRequest.Expect = header.Value;
|
||||||
break;
|
break;
|
||||||
case "Host":
|
case "Host":
|
||||||
webRequest.Host = header.Value.ToString();
|
webRequest.Host = header.Value;
|
||||||
break;
|
break;
|
||||||
case "If-Modified-Since":
|
case "If-Modified-Since":
|
||||||
webRequest.IfModifiedSince = (DateTime)header.Value;
|
webRequest.IfModifiedSince = HttpHeader.ParseDateTime(header.Value);
|
||||||
break;
|
break;
|
||||||
case "Range":
|
case "Range":
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
case "Referer":
|
case "Referer":
|
||||||
webRequest.Referer = header.Value.ToString();
|
webRequest.Referer = header.Value;
|
||||||
break;
|
break;
|
||||||
case "Transfer-Encoding":
|
case "Transfer-Encoding":
|
||||||
webRequest.TransferEncoding = header.Value.ToString();
|
webRequest.TransferEncoding = header.Value;
|
||||||
break;
|
break;
|
||||||
case "User-Agent":
|
case "User-Agent":
|
||||||
throw new NotSupportedException("User-Agent other than Sonarr not allowed.");
|
throw new NotSupportedException("User-Agent other than Sonarr not allowed.");
|
||||||
case "Proxy-Connection":
|
case "Proxy-Connection":
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
default:
|
default:
|
||||||
webRequest.Headers.Add(header.Key, header.Value.ToString());
|
webRequest.Headers.Add(header.Key, header.Value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,11 @@ using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Cache;
|
using NzbDrone.Common.Cache;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Common.Http.Dispatchers;
|
using NzbDrone.Common.Http.Dispatchers;
|
||||||
using NzbDrone.Common.TPL;
|
using NzbDrone.Common.TPL;
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace NzbDrone.Common.Http
|
||||||
public HttpResponse Response { get; private set; }
|
public HttpResponse Response { get; private set; }
|
||||||
|
|
||||||
public HttpException(HttpRequest request, HttpResponse response)
|
public HttpException(HttpRequest request, HttpResponse response)
|
||||||
: base(string.Format("HTTP request failed: [{0}:{1}] [{2}] at [{3}]", (int)response.StatusCode, response.StatusCode, request.Method, request.Url.ToString()))
|
: base(string.Format("HTTP request failed: [{0}:{1}] [{2}] at [{3}]", (int)response.StatusCode, response.StatusCode, request.Method, request.Url.AbsoluteUri))
|
||||||
{
|
{
|
||||||
Request = request;
|
Request = request;
|
||||||
Response = response;
|
Response = response;
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace NzbDrone.Common.Http
|
||||||
|
{
|
||||||
|
public class HttpFormData
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string FileName { get; set; }
|
||||||
|
public byte[] ContentData { get; set; }
|
||||||
|
public string ContentType { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,37 +4,92 @@ using System.Collections.Generic;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace NzbDrone.Common.Http
|
namespace NzbDrone.Common.Http
|
||||||
{
|
{
|
||||||
public class HttpHeader : Dictionary<string, object>
|
public class HttpHeader : NameValueCollection, IEnumerable<KeyValuePair<string, string>>, IEnumerable
|
||||||
{
|
{
|
||||||
public HttpHeader(NameValueCollection headers) : base(StringComparer.OrdinalIgnoreCase)
|
public HttpHeader(NameValueCollection headers)
|
||||||
|
: base(headers)
|
||||||
{
|
{
|
||||||
foreach (var key in headers.AllKeys)
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpHeader()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsKey(string key)
|
||||||
|
{
|
||||||
|
key = key.ToLowerInvariant();
|
||||||
|
return AllKeys.Any(v => v.ToLowerInvariant() == key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSingleValue(string key)
|
||||||
|
{
|
||||||
|
var values = GetValues(key);
|
||||||
|
if (values == null || values.Length == 0)
|
||||||
{
|
{
|
||||||
this[key] = headers[key];
|
return null;
|
||||||
|
}
|
||||||
|
if (values.Length > 1)
|
||||||
|
{
|
||||||
|
throw new ApplicationException(string.Format("Expected {0} to occur only once.", key));
|
||||||
|
}
|
||||||
|
|
||||||
|
return values[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected T? GetSingleValue<T>(string key, Func<string, T> converter) where T : struct
|
||||||
|
{
|
||||||
|
var value = GetSingleValue(key);
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return converter(value);
|
||||||
|
}
|
||||||
|
protected void SetSingleValue(string key, string value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
Remove(key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Set(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpHeader() : base(StringComparer.OrdinalIgnoreCase)
|
protected void SetSingleValue<T>(string key, T? value, Func<T, string> converter = null) where T : struct
|
||||||
{
|
{
|
||||||
|
if (!value.HasValue)
|
||||||
|
{
|
||||||
|
Remove(key);
|
||||||
|
}
|
||||||
|
else if (converter != null)
|
||||||
|
{
|
||||||
|
Set(key, converter(value.Value));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Set(key, value.Value.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long? ContentLength
|
public long? ContentLength
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!ContainsKey("Content-Length"))
|
return GetSingleValue("Content-Length", Convert.ToInt64);
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return Convert.ToInt64(this["Content-Length"]);
|
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
this["Content-Length"] = value;
|
SetSingleValue("Content-Length", value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,15 +97,11 @@ namespace NzbDrone.Common.Http
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!ContainsKey("Content-Type"))
|
return GetSingleValue("Content-Type");
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return this["Content-Type"].ToString();
|
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
this["Content-Type"] = value;
|
SetSingleValue("Content-Type", value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,25 +109,36 @@ namespace NzbDrone.Common.Http
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!ContainsKey("Accept"))
|
return GetSingleValue("Accept");
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return this["Accept"].ToString();
|
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
this["Accept"] = value;
|
SetSingleValue("Accept", value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public new IEnumerator<KeyValuePair<string, string>> GetEnumerator()
|
||||||
|
{
|
||||||
|
return AllKeys.SelectMany(GetValues, (k, c) => new KeyValuePair<string, string>(k, c)).ToList().GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return base.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
public Encoding GetEncodingFromContentType()
|
public Encoding GetEncodingFromContentType()
|
||||||
|
{
|
||||||
|
return GetEncodingFromContentType(ContentType ?? string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Encoding GetEncodingFromContentType(string contentType)
|
||||||
{
|
{
|
||||||
Encoding encoding = null;
|
Encoding encoding = null;
|
||||||
|
|
||||||
if (ContentType.IsNotNullOrWhiteSpace())
|
if (contentType.IsNotNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
var charset = ContentType.ToLowerInvariant()
|
var charset = contentType.ToLowerInvariant()
|
||||||
.Split(';', '=', ' ')
|
.Split(';', '=', ' ')
|
||||||
.SkipWhile(v => v != "charset")
|
.SkipWhile(v => v != "charset")
|
||||||
.Skip(1).FirstOrDefault();
|
.Skip(1).FirstOrDefault();
|
||||||
|
@ -99,5 +161,18 @@ namespace NzbDrone.Common.Http
|
||||||
|
|
||||||
return encoding;
|
return encoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static DateTime ParseDateTime(string value)
|
||||||
|
{
|
||||||
|
return DateTime.ParseExact(value, "R", CultureInfo.InvariantCulture.DateTimeFormat, DateTimeStyles.AssumeUniversal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<KeyValuePair<string, string>> ParseCookies(string cookies)
|
||||||
|
{
|
||||||
|
return cookies.Split(';')
|
||||||
|
.Select(v => v.Trim().Split('='))
|
||||||
|
.Select(v => new KeyValuePair<string, string>(v[0], v[1]))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,19 +1,19 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
|
||||||
namespace NzbDrone.Common.Http
|
namespace NzbDrone.Common.Http
|
||||||
{
|
{
|
||||||
public class HttpRequest
|
public class HttpRequest
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, string> _segments;
|
public HttpRequest(string uri, HttpAccept httpAccept = null)
|
||||||
|
|
||||||
public HttpRequest(string url, HttpAccept httpAccept = null)
|
|
||||||
{
|
{
|
||||||
UriBuilder = new UriBuilder(url);
|
UrlBuilder = new UriBuilder(uri);
|
||||||
Headers = new HttpHeader();
|
Headers = new HttpHeader();
|
||||||
_segments = new Dictionary<string, string>();
|
|
||||||
AllowAutoRedirect = true;
|
AllowAutoRedirect = true;
|
||||||
Cookies = new Dictionary<string, string>();
|
Cookies = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
@ -28,73 +28,41 @@ namespace NzbDrone.Common.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public UriBuilder UriBuilder { get; private set; }
|
public UriBuilder UrlBuilder { get; private set; }
|
||||||
|
public Uri Url { get { return UrlBuilder.Uri; } }
|
||||||
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 HttpMethod Method { get; set; }
|
||||||
public HttpHeader Headers { get; set; }
|
public HttpHeader Headers { get; set; }
|
||||||
public string Body { get; set; }
|
public byte[] ContentData { get; set; }
|
||||||
|
public string ContentSummary { get; set; }
|
||||||
public NetworkCredential NetworkCredential { get; set; }
|
public NetworkCredential NetworkCredential { get; set; }
|
||||||
public bool SuppressHttpError { get; set; }
|
public bool SuppressHttpError { get; set; }
|
||||||
public bool AllowAutoRedirect { get; set; }
|
public bool AllowAutoRedirect { get; set; }
|
||||||
public Dictionary<string, string> Cookies { get; private set; }
|
public Dictionary<string, string> Cookies { get; private set; }
|
||||||
public bool StoreResponseCookie { get; set; }
|
public bool StoreResponseCookie { get; set; }
|
||||||
|
public TimeSpan RequestTimeout { get; set; }
|
||||||
public TimeSpan RateLimit { get; set; }
|
public TimeSpan RateLimit { get; set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if (Body == null)
|
if (ContentSummary == null)
|
||||||
{
|
{
|
||||||
return string.Format("Req: [{0}] {1}", Method, Url);
|
return string.Format("Req: [{0}] {1}", Method, Url);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
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");
|
return string.Format("Req: [{0}] {1}: {2}", Method, Url, ContentSummary);
|
||||||
}
|
}
|
||||||
|
|
||||||
_segments.Add(key, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddQueryParam(string segment, string value)
|
public void SetContent(byte[] data)
|
||||||
{
|
{
|
||||||
UriBuilder.SetQueryParam(segment, value);
|
ContentData = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddCookie(string key, string value)
|
public void SetContent(string data)
|
||||||
{
|
{
|
||||||
Cookies[key] = value;
|
var encoding = HttpHeader.GetEncodingFromContentType(Headers.ContentType);
|
||||||
}
|
ContentData = encoding.GetBytes(data);
|
||||||
|
|
||||||
public void AddCookie(string cookies)
|
|
||||||
{
|
|
||||||
foreach (var pair in cookies.Split(';'))
|
|
||||||
{
|
|
||||||
var split = pair.Split('=');
|
|
||||||
|
|
||||||
Cookies[split[0].Trim()] = split[1].Trim();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,33 +1,123 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
|
||||||
namespace NzbDrone.Common.Http
|
namespace NzbDrone.Common.Http
|
||||||
{
|
{
|
||||||
public class HttpRequestBuilder
|
public class HttpRequestBuilder
|
||||||
{
|
{
|
||||||
public Uri BaseUri { get; private set; }
|
public HttpMethod Method { get; set; }
|
||||||
public bool SupressHttpError { get; set; }
|
public HttpAccept HttpAccept { get; set; }
|
||||||
|
public Uri BaseUrl { get; private set; }
|
||||||
|
public string ResourceUrl { get; set; }
|
||||||
|
public List<KeyValuePair<string, string>> QueryParams { get; private set; }
|
||||||
|
public List<KeyValuePair<string, string>> SuffixQueryParams { get; private set; }
|
||||||
|
public Dictionary<string, string> Segments { get; private set; }
|
||||||
|
public HttpHeader Headers { get; private set; }
|
||||||
|
public bool SuppressHttpError { get; set; }
|
||||||
|
public bool AllowAutoRedirect { get; set; }
|
||||||
public NetworkCredential NetworkCredential { get; set; }
|
public NetworkCredential NetworkCredential { get; set; }
|
||||||
|
public Dictionary<string, string> Cookies { get; private set; }
|
||||||
|
|
||||||
public Action<HttpRequest> PostProcess { get; set; }
|
public Action<HttpRequest> PostProcess { get; set; }
|
||||||
|
|
||||||
public HttpRequestBuilder(string baseUri)
|
public HttpRequestBuilder(string baseUrl)
|
||||||
{
|
{
|
||||||
BaseUri = new Uri(baseUri);
|
BaseUrl = new Uri(baseUrl);
|
||||||
|
ResourceUrl = string.Empty;
|
||||||
|
Method = HttpMethod.GET;
|
||||||
|
QueryParams = new List<KeyValuePair<string, string>>();
|
||||||
|
SuffixQueryParams = new List<KeyValuePair<string, string>>();
|
||||||
|
Segments = new Dictionary<string, string>();
|
||||||
|
Headers = new HttpHeader();
|
||||||
|
Cookies = new Dictionary<string, string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual HttpRequest Build(string path)
|
public HttpRequestBuilder(bool useHttps, string host, int port, string urlBase = null)
|
||||||
|
: this(BuildBaseUrl(useHttps, host, port, urlBase))
|
||||||
{
|
{
|
||||||
if (BaseUri.ToString().EndsWith("/"))
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string BuildBaseUrl(bool useHttps, string host, int port, string urlBase = null)
|
||||||
|
{
|
||||||
|
var protocol = useHttps ? "https" : "http";
|
||||||
|
|
||||||
|
if (urlBase.IsNotNullOrWhiteSpace() && !urlBase.StartsWith("/"))
|
||||||
{
|
{
|
||||||
path = path.TrimStart('/');
|
urlBase = "/" + urlBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
var request = new HttpRequest(BaseUri + path)
|
return string.Format("{0}://{1}:{2}{3}", protocol, host, port, urlBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual HttpRequestBuilder Clone()
|
||||||
|
{
|
||||||
|
var clone = MemberwiseClone() as HttpRequestBuilder;
|
||||||
|
clone.QueryParams = new List<KeyValuePair<string, string>>(clone.QueryParams);
|
||||||
|
clone.SuffixQueryParams = new List<KeyValuePair<string, string>>(clone.SuffixQueryParams);
|
||||||
|
clone.Segments = new Dictionary<string, string>(clone.Segments);
|
||||||
|
clone.Headers = new HttpHeader(clone.Headers);
|
||||||
|
clone.Cookies = new Dictionary<string, string>(clone.Cookies);
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual Uri CreateUri()
|
||||||
|
{
|
||||||
|
var builder = new UriBuilder(new Uri(BaseUrl, ResourceUrl));
|
||||||
|
|
||||||
|
foreach (var queryParam in QueryParams.Concat(SuffixQueryParams))
|
||||||
{
|
{
|
||||||
SuppressHttpError = SupressHttpError,
|
builder.SetQueryParam(queryParam.Key, queryParam.Value);
|
||||||
NetworkCredential = NetworkCredential
|
}
|
||||||
};
|
|
||||||
|
if (Segments.Any())
|
||||||
|
{
|
||||||
|
var url = builder.Uri.ToString();
|
||||||
|
|
||||||
|
foreach (var segment in Segments)
|
||||||
|
{
|
||||||
|
url = url.Replace(segment.Key, segment.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder = new UriBuilder(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.Uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual HttpRequest CreateRequest()
|
||||||
|
{
|
||||||
|
return new HttpRequest(CreateUri().ToString(), HttpAccept);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Apply(HttpRequest request)
|
||||||
|
{
|
||||||
|
request.Method = Method;
|
||||||
|
request.SuppressHttpError = SuppressHttpError;
|
||||||
|
request.AllowAutoRedirect = AllowAutoRedirect;
|
||||||
|
request.NetworkCredential = NetworkCredential;
|
||||||
|
|
||||||
|
foreach (var header in Headers)
|
||||||
|
{
|
||||||
|
request.Headers.Set(header.Key, header.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var cookie in Cookies)
|
||||||
|
{
|
||||||
|
request.Cookies[cookie.Key] = cookie.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual HttpRequest Build()
|
||||||
|
{
|
||||||
|
var request = CreateRequest();
|
||||||
|
|
||||||
|
Apply(request);
|
||||||
|
|
||||||
if (PostProcess != null)
|
if (PostProcess != null)
|
||||||
{
|
{
|
||||||
|
@ -36,5 +126,102 @@ namespace NzbDrone.Common.Http
|
||||||
|
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IHttpRequestBuilderFactory CreateFactory()
|
||||||
|
{
|
||||||
|
return new HttpRequestBuilderFactory(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual HttpRequestBuilder Resource(string resourceUrl)
|
||||||
|
{
|
||||||
|
if (!ResourceUrl.IsNotNullOrWhiteSpace() || resourceUrl.StartsWith("/"))
|
||||||
|
{
|
||||||
|
ResourceUrl = resourceUrl.TrimStart('/');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ResourceUrl = string.Format("{0}/{1}", ResourceUrl.TrimEnd('/'), resourceUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual HttpRequestBuilder Post()
|
||||||
|
{
|
||||||
|
Method = HttpMethod.POST;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual HttpRequestBuilder Accept(HttpAccept accept)
|
||||||
|
{
|
||||||
|
HttpAccept = accept;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual HttpRequestBuilder SetHeader(string name, string value)
|
||||||
|
{
|
||||||
|
Headers.Set(name, value);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual HttpRequestBuilder AddQueryParam(string key, object value, bool replace = false)
|
||||||
|
{
|
||||||
|
if (replace)
|
||||||
|
{
|
||||||
|
QueryParams.RemoveAll(v => v.Key == key);
|
||||||
|
SuffixQueryParams.RemoveAll(v => v.Key == key);
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryParams.Add(key, value.ToString());
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual HttpRequestBuilder AddSuffixQueryParam(string key, object value, bool replace = false)
|
||||||
|
{
|
||||||
|
if (replace)
|
||||||
|
{
|
||||||
|
QueryParams.RemoveAll(v => v.Key == key);
|
||||||
|
SuffixQueryParams.RemoveAll(v => v.Key == key);
|
||||||
|
}
|
||||||
|
|
||||||
|
SuffixQueryParams.Add(new KeyValuePair<string, string>(key, value.ToString()));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual HttpRequestBuilder SetSegment(string segment, string value, bool dontCheck = false)
|
||||||
|
{
|
||||||
|
var key = string.Concat("{", segment, "}");
|
||||||
|
|
||||||
|
if (!dontCheck && !CreateUri().ToString().Contains(key))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(string.Format("Segment {0} is not defined in Uri", segment));
|
||||||
|
}
|
||||||
|
|
||||||
|
Segments[key] = value;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual HttpRequestBuilder SetCookies(IEnumerable<KeyValuePair<string, string>> cookies)
|
||||||
|
{
|
||||||
|
foreach (var cookie in cookies)
|
||||||
|
{
|
||||||
|
Cookies[cookie.Key] = cookie.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual HttpRequestBuilder SetCookie(string key, string value)
|
||||||
|
{
|
||||||
|
Cookies[key] = value;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace NzbDrone.Common.Http
|
||||||
|
{
|
||||||
|
public interface IHttpRequestBuilderFactory
|
||||||
|
{
|
||||||
|
HttpRequestBuilder Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HttpRequestBuilderFactory : IHttpRequestBuilderFactory
|
||||||
|
{
|
||||||
|
private HttpRequestBuilder _rootBuilder;
|
||||||
|
|
||||||
|
public HttpRequestBuilderFactory(HttpRequestBuilder rootBuilder)
|
||||||
|
{
|
||||||
|
SetRootBuilder(rootBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HttpRequestBuilderFactory()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void SetRootBuilder(HttpRequestBuilder rootBuilder)
|
||||||
|
{
|
||||||
|
_rootBuilder = rootBuilder.Clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpRequestBuilder Create()
|
||||||
|
{
|
||||||
|
return _rootBuilder.Clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,16 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Common.Serializer;
|
using NzbDrone.Common.Serializer;
|
||||||
|
|
||||||
namespace NzbDrone.Common.Http
|
namespace NzbDrone.Common.Http
|
||||||
{
|
{
|
||||||
public class HttpResponse
|
public class HttpResponse
|
||||||
{
|
{
|
||||||
|
private static readonly Regex RegexSetCookie = new Regex("^(.*?)=(.*?)(?:;|$)", RegexOptions.Compiled);
|
||||||
|
|
||||||
public HttpResponse(HttpRequest request, HttpHeader headers, byte[] binaryData, HttpStatusCode statusCode = HttpStatusCode.OK)
|
public HttpResponse(HttpRequest request, HttpHeader headers, byte[] binaryData, HttpStatusCode statusCode = HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
Request = request;
|
Request = request;
|
||||||
|
@ -52,11 +57,27 @@ namespace NzbDrone.Common.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, string> GetCookies()
|
||||||
|
{
|
||||||
|
var result = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
foreach (var cookie in Headers.GetValues("Set-Cookie"))
|
||||||
|
{
|
||||||
|
var match = RegexSetCookie.Match(cookie);
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
result[match.Groups[1].Value] = match.Groups[2].Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
var result = string.Format("Res: [{0}] {1} : {2}.{3}", Request.Method, Request.Url, (int)StatusCode, StatusCode);
|
var result = string.Format("Res: [{0}] {1} : {2}.{3}", Request.Method, Request.Url, (int)StatusCode, StatusCode);
|
||||||
|
|
||||||
if (HasHttpError && !Headers.ContentType.Equals("text/html", StringComparison.InvariantCultureIgnoreCase))
|
if (HasHttpError && Headers.ContentType.IsNotNullOrWhiteSpace() && !Headers.ContentType.Equals("text/html", StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
result += Environment.NewLine + Content;
|
result += Environment.NewLine + Content;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,32 +7,87 @@ namespace NzbDrone.Common.Http
|
||||||
{
|
{
|
||||||
public class JsonRpcRequestBuilder : HttpRequestBuilder
|
public class JsonRpcRequestBuilder : HttpRequestBuilder
|
||||||
{
|
{
|
||||||
public string Method { get; private set; }
|
public static HttpAccept JsonRpcHttpAccept = new HttpAccept("application/json-rpc, application/json");
|
||||||
public List<object> Parameters { get; private set; }
|
public static string JsonRpcContentType = "application/json-rpc";
|
||||||
|
|
||||||
public JsonRpcRequestBuilder(string baseUri, string method, IEnumerable<object> parameters)
|
public string JsonMethod { get; private set; }
|
||||||
: base (baseUri)
|
public List<object> JsonParameters { get; private set; }
|
||||||
|
|
||||||
|
public JsonRpcRequestBuilder(string baseUrl)
|
||||||
|
: base(baseUrl)
|
||||||
{
|
{
|
||||||
Method = method;
|
Method = HttpMethod.POST;
|
||||||
Parameters = parameters.ToList();
|
JsonParameters = new List<object>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override HttpRequest Build(string path)
|
public JsonRpcRequestBuilder(string baseUrl, string method, IEnumerable<object> parameters)
|
||||||
|
: base (baseUrl)
|
||||||
{
|
{
|
||||||
var request = base.Build(path);
|
Method = HttpMethod.POST;
|
||||||
request.Method = HttpMethod.POST;
|
JsonMethod = method;
|
||||||
request.Headers.Accept = "application/json-rpc, application/json";
|
JsonParameters = parameters.ToList();
|
||||||
request.Headers.ContentType = "application/json-rpc";
|
}
|
||||||
|
|
||||||
|
public override HttpRequestBuilder Clone()
|
||||||
|
{
|
||||||
|
var clone = base.Clone() as JsonRpcRequestBuilder;
|
||||||
|
clone.JsonParameters = new List<object>(JsonParameters);
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonRpcRequestBuilder Call(string method, params object[] parameters)
|
||||||
|
{
|
||||||
|
var clone = Clone() as JsonRpcRequestBuilder;
|
||||||
|
clone.JsonMethod = method;
|
||||||
|
clone.JsonParameters = parameters.ToList();
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Apply(HttpRequest request)
|
||||||
|
{
|
||||||
|
base.Apply(request);
|
||||||
|
|
||||||
|
request.Headers.ContentType = JsonRpcContentType;
|
||||||
|
|
||||||
|
var parameterData = new object[JsonParameters.Count];
|
||||||
|
var parameterSummary = new string[JsonParameters.Count];
|
||||||
|
|
||||||
|
for (var i = 0; i < JsonParameters.Count; i++)
|
||||||
|
{
|
||||||
|
ConvertParameter(JsonParameters[i], out parameterData[i], out parameterSummary[i]);
|
||||||
|
}
|
||||||
|
|
||||||
var message = new Dictionary<string, object>();
|
var message = new Dictionary<string, object>();
|
||||||
message["jsonrpc"] = "2.0";
|
message["jsonrpc"] = "2.0";
|
||||||
message["method"] = Method;
|
message["method"] = JsonMethod;
|
||||||
message["params"] = Parameters;
|
message["params"] = parameterData;
|
||||||
message["id"] = CreateNextId();
|
message["id"] = CreateNextId();
|
||||||
|
|
||||||
request.Body = message.ToJson();
|
request.SetContent(message.ToJson());
|
||||||
|
|
||||||
return request;
|
if (request.ContentSummary == null)
|
||||||
|
{
|
||||||
|
request.ContentSummary = string.Format("{0}({1})", JsonMethod, string.Join(", ", parameterSummary));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConvertParameter(object value, out object data, out string summary)
|
||||||
|
{
|
||||||
|
if (value is byte[])
|
||||||
|
{
|
||||||
|
data = Convert.ToBase64String(value as byte[]);
|
||||||
|
summary = string.Format("[blob {0} bytes]", (value as byte[]).Length);
|
||||||
|
}
|
||||||
|
else if (value is Array && ((Array)value).Length > 0)
|
||||||
|
{
|
||||||
|
data = value;
|
||||||
|
summary = "[...]";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data = value;
|
||||||
|
summary = data.ToJson();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string CreateNextId()
|
public string CreateNextId()
|
||||||
|
|
|
@ -67,7 +67,7 @@
|
||||||
<Compile Include="Cache\CachedDictionary.cs" />
|
<Compile Include="Cache\CachedDictionary.cs" />
|
||||||
<Compile Include="Cache\ICached.cs" />
|
<Compile Include="Cache\ICached.cs" />
|
||||||
<Compile Include="Cache\ICachedDictionary.cs" />
|
<Compile Include="Cache\ICachedDictionary.cs" />
|
||||||
<Compile Include="Cloud\CloudClient.cs" />
|
<Compile Include="Cloud\SonarrCloudRequestBuilder.cs" />
|
||||||
<Compile Include="Composition\Container.cs" />
|
<Compile Include="Composition\Container.cs" />
|
||||||
<Compile Include="Composition\ContainerBuilderBase.cs" />
|
<Compile Include="Composition\ContainerBuilderBase.cs" />
|
||||||
<Compile Include="Composition\IContainer.cs" />
|
<Compile Include="Composition\IContainer.cs" />
|
||||||
|
@ -156,6 +156,7 @@
|
||||||
<Compile Include="Http\HttpAccept.cs" />
|
<Compile Include="Http\HttpAccept.cs" />
|
||||||
<Compile Include="Http\HttpClient.cs" />
|
<Compile Include="Http\HttpClient.cs" />
|
||||||
<Compile Include="Http\HttpException.cs" />
|
<Compile Include="Http\HttpException.cs" />
|
||||||
|
<Compile Include="Http\HttpFormData.cs" />
|
||||||
<Compile Include="Http\HttpHeader.cs" />
|
<Compile Include="Http\HttpHeader.cs" />
|
||||||
<Compile Include="Http\HttpMethod.cs" />
|
<Compile Include="Http\HttpMethod.cs" />
|
||||||
<Compile Include="Http\HttpProvider.cs" />
|
<Compile Include="Http\HttpProvider.cs" />
|
||||||
|
@ -168,6 +169,7 @@
|
||||||
<SubType>Component</SubType>
|
<SubType>Component</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Http\HttpRequestBuilder.cs" />
|
<Compile Include="Http\HttpRequestBuilder.cs" />
|
||||||
|
<Compile Include="Http\HttpRequestBuilderFactory.cs" />
|
||||||
<Compile Include="Http\TooManyRequestsException.cs" />
|
<Compile Include="Http\TooManyRequestsException.cs" />
|
||||||
<Compile Include="Http\UriExtensions.cs" />
|
<Compile Include="Http\UriExtensions.cs" />
|
||||||
<Compile Include="Extensions\IEnumerableExtensions.cs" />
|
<Compile Include="Extensions\IEnumerableExtensions.cs" />
|
||||||
|
|
|
@ -112,7 +112,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||||
|
|
||||||
Subject.Download(remoteEpisode);
|
Subject.Download(remoteEpisode);
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.ToString() == _downloadUrl)), Times.Once());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.AbsoluteUri == _downloadUrl)), Times.Once());
|
||||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Once());
|
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Once());
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
@ -128,7 +128,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||||
|
|
||||||
Subject.Download(remoteEpisode);
|
Subject.Download(remoteEpisode);
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.ToString() == _downloadUrl)), Times.Once());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.AbsoluteUri == _downloadUrl)), Times.Once());
|
||||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(expectedFilename), Times.Once());
|
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(expectedFilename), Times.Once());
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||||
|
|
||||||
Subject.Download(remoteEpisode);
|
Subject.Download(remoteEpisode);
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.ToString() == _downloadUrl)), Times.Once());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.AbsoluteUri == _downloadUrl)), Times.Once());
|
||||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Once());
|
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Once());
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||||
|
|
||||||
Subject.Download(remoteEpisode);
|
Subject.Download(remoteEpisode);
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.ToString() == _downloadUrl)), Times.Once());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.AbsoluteUri == _downloadUrl)), Times.Once());
|
||||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(expectedFilename), Times.Once());
|
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(expectedFilename), Times.Once());
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,7 +110,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
|
||||||
httpHeader["Location"] = "http://test.sonarr.tv/not-a-real-torrent.torrent";
|
httpHeader["Location"] = "http://test.sonarr.tv/not-a-real-torrent.torrent";
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>()
|
Mocker.GetMock<IHttpClient>()
|
||||||
.Setup(s => s.Get(It.Is<HttpRequest>(h => h.Url.AbsoluteUri == _downloadUrl)))
|
.Setup(s => s.Get(It.Is<HttpRequest>(h => h.Url.ToString() == _downloadUrl)))
|
||||||
.Returns<HttpRequest>(r => new HttpResponse(r, httpHeader, new byte[0], System.Net.HttpStatusCode.Found));
|
.Returns<HttpRequest>(r => new HttpResponse(r, httpHeader, new byte[0], System.Net.HttpStatusCode.Found));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,90 +84,6 @@ namespace NzbDrone.Core.Test
|
||||||
dateTime.ToBestDateString().Should().Be(dateTime.ToShortDateString());
|
dateTime.ToBestDateString().Should().Be(dateTime.ToShortDateString());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void ParentUriString_should_return_self_if_already_parent()
|
|
||||||
{
|
|
||||||
|
|
||||||
var url = "http://www.sonarr.tv";
|
|
||||||
var uri = new Uri(url);
|
|
||||||
|
|
||||||
|
|
||||||
var result = uri.ParentUriString();
|
|
||||||
|
|
||||||
//Resolve
|
|
||||||
result.Should().Be(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void ParentUriString_should_return_parent_url_when_path_is_passed()
|
|
||||||
{
|
|
||||||
|
|
||||||
var url = "http://www.sonarr.tv/test/";
|
|
||||||
var uri = new Uri(url);
|
|
||||||
|
|
||||||
|
|
||||||
var result = uri.ParentUriString();
|
|
||||||
|
|
||||||
//Resolve
|
|
||||||
result.Should().Be("http://www.sonarr.tv");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void ParentUriString_should_return_parent_url_when_multiple_paths_are_passed()
|
|
||||||
{
|
|
||||||
|
|
||||||
var url = "http://www.sonarr.tv/test/test2";
|
|
||||||
var uri = new Uri(url);
|
|
||||||
|
|
||||||
|
|
||||||
var result = uri.ParentUriString();
|
|
||||||
|
|
||||||
//Resolve
|
|
||||||
result.Should().Be("http://www.sonarr.tv");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void ParentUriString_should_return_parent_url_when_url_with_query_string_is_passed()
|
|
||||||
{
|
|
||||||
|
|
||||||
var url = "http://www.sonarr.tv/test.aspx?test=10";
|
|
||||||
var uri = new Uri(url);
|
|
||||||
|
|
||||||
|
|
||||||
var result = uri.ParentUriString();
|
|
||||||
|
|
||||||
//Resolve
|
|
||||||
result.Should().Be("http://www.sonarr.tv");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void ParentUriString_should_return_parent_url_when_url_with_path_and_query_strings_is_passed()
|
|
||||||
{
|
|
||||||
|
|
||||||
var url = "http://www.sonarr.tv/tester/test.aspx?test=10";
|
|
||||||
var uri = new Uri(url);
|
|
||||||
|
|
||||||
|
|
||||||
var result = uri.ParentUriString();
|
|
||||||
|
|
||||||
//Resolve
|
|
||||||
result.Should().Be("http://www.sonarr.tv");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void ParentUriString_should_return_parent_url_when_url_with_query_strings_is_passed()
|
|
||||||
{
|
|
||||||
|
|
||||||
var url = "http://www.sonarr.tv/test.aspx?test=10&test2=5";
|
|
||||||
var uri = new Uri(url);
|
|
||||||
|
|
||||||
|
|
||||||
var result = uri.ParentUriString();
|
|
||||||
|
|
||||||
//Resolve
|
|
||||||
result.Should().Be("http://www.sonarr.tv");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void MaxOrDefault_should_return_zero_when_collection_is_empty()
|
public void MaxOrDefault_should_return_zero_when_collection_is_empty()
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,7 +19,7 @@ namespace NzbDrone.Core.Test.Framework
|
||||||
{
|
{
|
||||||
Mocker.SetConstant<IHttpProvider>(new HttpProvider(TestLogger));
|
Mocker.SetConstant<IHttpProvider>(new HttpProvider(TestLogger));
|
||||||
Mocker.SetConstant<IHttpClient>(new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<CacheManager>(), Mocker.Resolve<RateLimitService>(), TestLogger));
|
Mocker.SetConstant<IHttpClient>(new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<CacheManager>(), Mocker.Resolve<RateLimitService>(), TestLogger));
|
||||||
Mocker.SetConstant<IDroneServicesRequestBuilder>(new DroneServicesHttpRequestBuilder());
|
Mocker.SetConstant<ISonarrCloudRequestBuilder>(new SonarrCloudRequestBuilder());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,13 +15,13 @@ namespace NzbDrone.Core.DataAugmentation.DailySeries
|
||||||
public class DailySeriesDataProxy : IDailySeriesDataProxy
|
public class DailySeriesDataProxy : IDailySeriesDataProxy
|
||||||
{
|
{
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly IDroneServicesRequestBuilder _requestBuilder;
|
private readonly IHttpRequestBuilderFactory _requestBuilder;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public DailySeriesDataProxy(IHttpClient httpClient, IDroneServicesRequestBuilder requestBuilder, Logger logger)
|
public DailySeriesDataProxy(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBuilder, Logger logger)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_requestBuilder = requestBuilder;
|
_requestBuilder = requestBuilder.Services;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,10 @@ namespace NzbDrone.Core.DataAugmentation.DailySeries
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var dailySeriesRequest = _requestBuilder.Build("dailyseries");
|
var dailySeriesRequest = _requestBuilder.Create()
|
||||||
|
.Resource("/dailyseries")
|
||||||
|
.Build();
|
||||||
|
|
||||||
var response = _httpClient.Get<List<DailySeries>>(dailySeriesRequest);
|
var response = _httpClient.Get<List<DailySeries>>(dailySeriesRequest);
|
||||||
return response.Resource.Select(c => c.TvdbId);
|
return response.Resource.Select(c => c.TvdbId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,17 +12,20 @@ namespace NzbDrone.Core.DataAugmentation.Scene
|
||||||
public class SceneMappingProxy : ISceneMappingProxy
|
public class SceneMappingProxy : ISceneMappingProxy
|
||||||
{
|
{
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly IDroneServicesRequestBuilder _requestBuilder;
|
private readonly IHttpRequestBuilderFactory _requestBuilder;
|
||||||
|
|
||||||
public SceneMappingProxy(IHttpClient httpClient, IDroneServicesRequestBuilder requestBuilder)
|
public SceneMappingProxy(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBuilder)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_requestBuilder = requestBuilder;
|
_requestBuilder = requestBuilder.Services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<SceneMapping> Fetch()
|
public List<SceneMapping> Fetch()
|
||||||
{
|
{
|
||||||
var request = _requestBuilder.Build("/scenemapping");
|
var request = _requestBuilder.Create()
|
||||||
|
.Resource("/scenemapping")
|
||||||
|
.Build();
|
||||||
|
|
||||||
return _httpClient.Get<List<SceneMapping>>(request).Resource;
|
return _httpClient.Get<List<SceneMapping>>(request).Resource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using NzbDrone.Common.Cloud;
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
using NzbDrone.Core.DataAugmentation.Scene;
|
using NzbDrone.Core.DataAugmentation.Scene;
|
||||||
using NzbDrone.Core.DataAugmentation.Xem.Model;
|
using NzbDrone.Core.DataAugmentation.Xem.Model;
|
||||||
|
@ -18,32 +19,32 @@ namespace NzbDrone.Core.DataAugmentation.Xem
|
||||||
|
|
||||||
public class XemProxy : IXemProxy
|
public class XemProxy : IXemProxy
|
||||||
{
|
{
|
||||||
|
private const string ROOT_URL = "http://thexem.de/map/";
|
||||||
|
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
|
private readonly IHttpRequestBuilderFactory _xemRequestBuilder;
|
||||||
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 static readonly string[] IgnoredErrors = { "no single connection", "no show with the tvdb_id" };
|
||||||
private HttpRequestBuilder _xemRequestBuilder;
|
|
||||||
|
|
||||||
|
public XemProxy(IHttpClient httpClient, Logger logger)
|
||||||
public XemProxy(Logger logger, IHttpClient httpClient)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
_xemRequestBuilder = new HttpRequestBuilder(XEM_BASE_URL)
|
_xemRequestBuilder = new HttpRequestBuilder(ROOT_URL)
|
||||||
{
|
.AddSuffixQueryParam("origin", "tvdb")
|
||||||
PostProcess = r => r.UriBuilder.SetQueryParam("origin", "tvdb")
|
.CreateFactory();
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<int> GetXemSeriesIds()
|
public List<int> GetXemSeriesIds()
|
||||||
{
|
{
|
||||||
_logger.Debug("Fetching Series IDs from");
|
_logger.Debug("Fetching Series IDs from");
|
||||||
|
|
||||||
var request = _xemRequestBuilder.Build("/havemap");
|
var request = _xemRequestBuilder.Create()
|
||||||
|
.Resource("/havemap")
|
||||||
|
.Build();
|
||||||
|
|
||||||
var response = _httpClient.Get<XemResult<List<string>>>(request).Resource;
|
var response = _httpClient.Get<XemResult<List<string>>>(request).Resource;
|
||||||
CheckForFailureResult(response);
|
CheckForFailureResult(response);
|
||||||
|
|
||||||
|
@ -60,9 +61,10 @@ namespace NzbDrone.Core.DataAugmentation.Xem
|
||||||
{
|
{
|
||||||
_logger.Debug("Fetching Mappings for: {0}", id);
|
_logger.Debug("Fetching Mappings for: {0}", id);
|
||||||
|
|
||||||
|
var request = _xemRequestBuilder.Create()
|
||||||
var request = _xemRequestBuilder.Build("/all");
|
.Resource("/all")
|
||||||
request.UriBuilder.SetQueryParam("id", id);
|
.AddQueryParam("id", id)
|
||||||
|
.Build();
|
||||||
|
|
||||||
var response = _httpClient.Get<XemResult<List<XemSceneTvdbMapping>>>(request).Resource;
|
var response = _httpClient.Get<XemResult<List<XemSceneTvdbMapping>>>(request).Resource;
|
||||||
|
|
||||||
|
@ -73,8 +75,10 @@ namespace NzbDrone.Core.DataAugmentation.Xem
|
||||||
{
|
{
|
||||||
_logger.Debug("Fetching alternate names");
|
_logger.Debug("Fetching alternate names");
|
||||||
|
|
||||||
var request = _xemRequestBuilder.Build("/allNames");
|
var request = _xemRequestBuilder.Create()
|
||||||
request.UriBuilder.SetQueryParam("seasonNumbers", true);
|
.Resource("/allNames")
|
||||||
|
.AddQueryParam("seasonNumbers", true)
|
||||||
|
.Build();
|
||||||
|
|
||||||
var response = _httpClient.Get<XemResult<Dictionary<int, List<JObject>>>>(request).Resource;
|
var response = _httpClient.Get<XemResult<Dictionary<int, List<JObject>>>>(request).Resource;
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,7 @@ namespace NzbDrone.Core.Download
|
||||||
|
|
||||||
if (response.StatusCode == HttpStatusCode.SeeOther || response.StatusCode == HttpStatusCode.Found)
|
if (response.StatusCode == HttpStatusCode.SeeOther || response.StatusCode == HttpStatusCode.Found)
|
||||||
{
|
{
|
||||||
var locationHeader = (string)response.Headers.GetValueOrDefault("Location", null);
|
var locationHeader = response.Headers.GetSingleValue("Location");
|
||||||
|
|
||||||
_logger.Trace("Torrent request is being redirected to: {0}", locationHeader);
|
_logger.Trace("Torrent request is being redirected to: {0}", locationHeader);
|
||||||
|
|
||||||
|
|
|
@ -63,11 +63,6 @@ namespace NzbDrone.Core
|
||||||
return dateTime.ToShortDateString();
|
return dateTime.ToShortDateString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ParentUriString(this Uri uri)
|
|
||||||
{
|
|
||||||
return uri.AbsoluteUri.Remove(uri.AbsoluteUri.Length - string.Join("", uri.Segments).Length - uri.Query.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int MaxOrDefault(this IEnumerable<int> ints)
|
public static int MaxOrDefault(this IEnumerable<int> ints)
|
||||||
{
|
{
|
||||||
if (ints == null)
|
if (ints == null)
|
||||||
|
|
|
@ -13,9 +13,9 @@ namespace NzbDrone.Core.Http
|
||||||
{
|
{
|
||||||
// torcache behaves strangely when it has query params and/or no Referer or browser User-Agent.
|
// torcache behaves strangely when it has query params and/or no Referer or browser User-Agent.
|
||||||
// It's a bit vague, and we don't need the query params. So we remove the query params and set a Referer to be safe.
|
// It's a bit vague, and we don't need the query params. So we remove the query params and set a Referer to be safe.
|
||||||
if (request.Url.Host == "torcache.net")
|
if (request.UrlBuilder.Host == "torcache.net")
|
||||||
{
|
{
|
||||||
request.UriBuilder.Query = string.Empty;
|
request.UrlBuilder.Query = string.Empty;
|
||||||
request.Headers.Add("Referer", request.Url.Scheme + @"://torcache.net/");
|
request.Headers.Add("Referer", request.Url.Scheme + @"://torcache.net/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,10 @@ namespace NzbDrone.Core.Indexers.BitMeTv
|
||||||
{
|
{
|
||||||
var request = new IndexerRequest(string.Format("{0}/rss.php?uid={1}&passkey={2}", Settings.BaseUrl.Trim().TrimEnd('/'), Settings.UserId, Settings.RssPasskey), HttpAccept.Html);
|
var request = new IndexerRequest(string.Format("{0}/rss.php?uid={1}&passkey={2}", Settings.BaseUrl.Trim().TrimEnd('/'), Settings.UserId, Settings.RssPasskey), HttpAccept.Html);
|
||||||
|
|
||||||
request.HttpRequest.AddCookie(Settings.Cookie);
|
foreach (var cookie in HttpHeader.ParseCookies(Settings.Cookie))
|
||||||
|
{
|
||||||
|
request.HttpRequest.Cookies[cookie.Key] = cookie.Value;
|
||||||
|
}
|
||||||
|
|
||||||
yield return request;
|
yield return request;
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,14 +172,15 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
|
||||||
parameters = new BroadcastheNetTorrentQuery();
|
parameters = new BroadcastheNetTorrentQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
var builder = new JsonRpcRequestBuilder(Settings.BaseUrl, "getTorrents", new object[] { Settings.ApiKey, parameters, PageSize, 0 });
|
var builder = new JsonRpcRequestBuilder(Settings.BaseUrl)
|
||||||
builder.SupressHttpError = true;
|
.Call("getTorrents", Settings.ApiKey, parameters, PageSize, 0);
|
||||||
|
builder.SuppressHttpError = true;
|
||||||
|
|
||||||
for (var page = 0; page < maxPages;page++)
|
for (var page = 0; page < maxPages;page++)
|
||||||
{
|
{
|
||||||
builder.Parameters[3] = page * PageSize;
|
builder.JsonParameters[3] = page * PageSize;
|
||||||
|
|
||||||
yield return new IndexerRequest(builder.Build(""));
|
yield return new IndexerRequest(builder.Build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,8 +114,9 @@ namespace NzbDrone.Core.Indexers.HDBits
|
||||||
|
|
||||||
private IEnumerable<IndexerRequest> GetRequest(TorrentQuery query)
|
private IEnumerable<IndexerRequest> GetRequest(TorrentQuery query)
|
||||||
{
|
{
|
||||||
var builder = new HttpRequestBuilder(Settings.BaseUrl);
|
var request = new HttpRequestBuilder(Settings.BaseUrl)
|
||||||
var request = builder.Build("/api/torrents");
|
.Resource("/api/torrents")
|
||||||
|
.Build();
|
||||||
|
|
||||||
request.Method = HttpMethod.POST;
|
request.Method = HttpMethod.POST;
|
||||||
const string appJson = "application/json";
|
const string appJson = "application/json";
|
||||||
|
@ -125,7 +126,7 @@ namespace NzbDrone.Core.Indexers.HDBits
|
||||||
query.Username = Settings.Username;
|
query.Username = Settings.Username;
|
||||||
query.Passkey = Settings.ApiKey;
|
query.Passkey = Settings.ApiKey;
|
||||||
|
|
||||||
request.Body = query.ToJson();
|
request.SetContent(query.ToJson());
|
||||||
|
|
||||||
yield return new IndexerRequest(request);
|
yield return new IndexerRequest(request);
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,7 +137,7 @@ namespace NzbDrone.Core.Indexers
|
||||||
|
|
||||||
foreach (var request in pageableRequest)
|
foreach (var request in pageableRequest)
|
||||||
{
|
{
|
||||||
url = request.Url.ToString();
|
url = request.Url.AbsoluteUri;
|
||||||
|
|
||||||
var page = FetchPage(request, parser);
|
var page = FetchPage(request, parser);
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||||
throw new ApiKeyException("Invalid API key");
|
throw new ApiKeyException("Invalid API key");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!indexerResponse.Request.Url.ToString().Contains("apikey=") && (errorMessage == "Missing parameter" || errorMessage.Contains("apikey")))
|
if (!indexerResponse.Request.Url.AbsoluteUri.Contains("apikey=") && (errorMessage == "Missing parameter" || errorMessage.Contains("apikey")))
|
||||||
{
|
{
|
||||||
throw new ApiKeyException("Indexer requires an API key");
|
throw new ApiKeyException("Indexer requires an API key");
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,30 +75,34 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||||
|
|
||||||
private IEnumerable<IndexerRequest> GetPagedRequests(string mode, int? tvdbId, string query, params object[] args)
|
private IEnumerable<IndexerRequest> GetPagedRequests(string mode, int? tvdbId, string query, params object[] args)
|
||||||
{
|
{
|
||||||
|
var requestBuilder = new HttpRequestBuilder(Settings.BaseUrl)
|
||||||
|
.Resource("/pubapi_v2.php")
|
||||||
|
.Accept(HttpAccept.Json);
|
||||||
|
|
||||||
var httpRequest = new HttpRequest(Settings.BaseUrl + "/pubapi_v2.php", HttpAccept.Json);
|
var httpRequest = new HttpRequest(Settings.BaseUrl + "/pubapi_v2.php", HttpAccept.Json);
|
||||||
|
|
||||||
httpRequest.AddQueryParam("mode", mode);
|
requestBuilder.AddQueryParam("mode", mode);
|
||||||
|
|
||||||
if (tvdbId.HasValue)
|
if (tvdbId.HasValue)
|
||||||
{
|
{
|
||||||
httpRequest.AddQueryParam("search_tvdb", tvdbId.Value.ToString());
|
requestBuilder.AddQueryParam("search_tvdb", tvdbId.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.IsNotNullOrWhiteSpace())
|
if (query.IsNotNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
httpRequest.AddQueryParam("search_string", string.Format(query, args));
|
requestBuilder.AddQueryParam("search_string", string.Format(query, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Settings.RankedOnly)
|
if (!Settings.RankedOnly)
|
||||||
{
|
{
|
||||||
httpRequest.AddQueryParam("ranked", "0");
|
requestBuilder.AddQueryParam("ranked", "0");
|
||||||
}
|
}
|
||||||
|
|
||||||
httpRequest.AddQueryParam("category", "18;41");
|
requestBuilder.AddQueryParam("category", "18;41");
|
||||||
httpRequest.AddQueryParam("limit", "100");
|
requestBuilder.AddQueryParam("limit", "100");
|
||||||
httpRequest.AddQueryParam("token", _tokenProvider.GetToken(Settings));
|
requestBuilder.AddQueryParam("token", _tokenProvider.GetToken(Settings));
|
||||||
httpRequest.AddQueryParam("format", "json_extended");
|
requestBuilder.AddQueryParam("format", "json_extended");
|
||||||
httpRequest.AddQueryParam("app_id", "Sonarr");
|
requestBuilder.AddQueryParam("app_id", "Sonarr");
|
||||||
|
|
||||||
yield return new IndexerRequest(httpRequest);
|
yield return new IndexerRequest(httpRequest);
|
||||||
}
|
}
|
||||||
|
|
|
@ -266,18 +266,18 @@ namespace NzbDrone.Core.Indexers
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var uri = new Uri(value, UriKind.RelativeOrAbsolute);
|
var url = new Uri(value, UriKind.RelativeOrAbsolute);
|
||||||
|
|
||||||
if (!uri.IsAbsoluteUri)
|
if (!url.IsAbsoluteUri)
|
||||||
{
|
{
|
||||||
uri = new Uri(_indexerResponse.HttpRequest.Url, uri);
|
url = new Uri(_indexerResponse.HttpRequest.Url, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return uri.AbsoluteUri;
|
return url.AbsoluteUri;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Debug(ex, string.Format("Failed to parse Uri {0}, ignoring.", value));
|
_logger.Debug(ex, string.Format("Failed to parse Url {0}, ignoring.", value));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,10 @@ namespace NzbDrone.Core.Indexers.TorrentRss
|
||||||
|
|
||||||
if (Settings.Cookie.IsNotNullOrWhiteSpace())
|
if (Settings.Cookie.IsNotNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
request.HttpRequest.AddCookie(Settings.Cookie);
|
foreach (var cookie in HttpHeader.ParseCookies(Settings.Cookie))
|
||||||
|
{
|
||||||
|
request.HttpRequest.Cookies[cookie.Key] = cookie.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return request;
|
yield return request;
|
||||||
|
|
|
@ -23,7 +23,7 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||||
|
|
||||||
if (code >= 100 && code <= 199) throw new ApiKeyException("Invalid API key");
|
if (code >= 100 && code <= 199) throw new ApiKeyException("Invalid API key");
|
||||||
|
|
||||||
if (!indexerResponse.Request.Url.ToString().Contains("apikey=") && errorMessage == "Missing parameter")
|
if (!indexerResponse.Request.Url.AbsoluteUri.Contains("apikey=") && errorMessage == "Missing parameter")
|
||||||
{
|
{
|
||||||
throw new ApiKeyException("Indexer requires an API key");
|
throw new ApiKeyException("Indexer requires an API key");
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using NzbDrone.Common.Cloud;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
using NzbDrone.Core.Exceptions;
|
using NzbDrone.Core.Exceptions;
|
||||||
|
@ -16,20 +17,23 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||||
{
|
{
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
private readonly HttpRequestBuilder _requestBuilder;
|
|
||||||
|
|
||||||
public SkyHookProxy(IHttpClient httpClient, Logger logger)
|
private readonly IHttpRequestBuilderFactory _requestBuilder;
|
||||||
|
|
||||||
|
public SkyHookProxy(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBuilder, Logger logger)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
|
_requestBuilder = requestBuilder.SkyHookTvdb;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
_requestBuilder = new HttpRequestBuilder("http://skyhook.sonarr.tv/v1/tvdb/{route}/en/");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Tuple<Series, List<Episode>> GetSeriesInfo(int tvdbSeriesId)
|
public Tuple<Series, List<Episode>> GetSeriesInfo(int tvdbSeriesId)
|
||||||
{
|
{
|
||||||
var httpRequest = _requestBuilder.Build(tvdbSeriesId.ToString());
|
var httpRequest = _requestBuilder.Create()
|
||||||
httpRequest.AddSegment("route", "shows");
|
.SetSegment("route", "shows")
|
||||||
|
.Resource(tvdbSeriesId.ToString())
|
||||||
|
.Build();
|
||||||
|
|
||||||
httpRequest.AllowAutoRedirect = true;
|
httpRequest.AllowAutoRedirect = true;
|
||||||
httpRequest.SuppressHttpError = true;
|
httpRequest.SuppressHttpError = true;
|
||||||
|
|
||||||
|
@ -81,9 +85,10 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||||
}
|
}
|
||||||
|
|
||||||
var term = System.Web.HttpUtility.UrlEncode((title.ToLower().Trim()));
|
var term = System.Web.HttpUtility.UrlEncode((title.ToLower().Trim()));
|
||||||
var httpRequest = _requestBuilder.Build("?term={term}");
|
var httpRequest = _requestBuilder.Create()
|
||||||
httpRequest.AddSegment("route", "search");
|
.SetSegment("route", "search")
|
||||||
httpRequest.AddSegment("term", term);
|
.AddQueryParam("term", title.ToLower().Trim())
|
||||||
|
.Build();
|
||||||
|
|
||||||
var httpResponse = _httpClient.Get<List<ShowResource>>(httpRequest);
|
var httpResponse = _httpClient.Get<List<ShowResource>>(httpRequest);
|
||||||
|
|
||||||
|
|
|
@ -21,15 +21,14 @@ namespace NzbDrone.Core.Notifications.MediaBrowser
|
||||||
{
|
{
|
||||||
var path = "/Notifications/Admin";
|
var path = "/Notifications/Admin";
|
||||||
var request = BuildRequest(path, settings);
|
var request = BuildRequest(path, settings);
|
||||||
|
request.Headers.ContentType = "application/json";
|
||||||
|
|
||||||
request.Body = new
|
request.SetContent(new
|
||||||
{
|
{
|
||||||
Name = title,
|
Name = title,
|
||||||
Description = message,
|
Description = message,
|
||||||
ImageUrl = "https://raw.github.com/NzbDrone/NzbDrone/develop/Logo/64.png"
|
ImageUrl = "https://raw.github.com/NzbDrone/NzbDrone/develop/Logo/64.png"
|
||||||
}.ToJson();
|
}.ToJson());
|
||||||
|
|
||||||
request.Headers.ContentType = "application/json";
|
|
||||||
|
|
||||||
ProcessRequest(request, settings);
|
ProcessRequest(request, settings);
|
||||||
}
|
}
|
||||||
|
@ -58,7 +57,7 @@ namespace NzbDrone.Core.Notifications.MediaBrowser
|
||||||
{
|
{
|
||||||
var url = string.Format(@"http://{0}/mediabrowser", settings.Address);
|
var url = string.Format(@"http://{0}/mediabrowser", settings.Address);
|
||||||
|
|
||||||
return new HttpRequestBuilder(url).Build(path);
|
return new HttpRequestBuilder(url).Resource(path).Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckForError(HttpResponse response)
|
private void CheckForError(HttpResponse response)
|
||||||
|
|
|
@ -15,20 +15,22 @@ namespace NzbDrone.Core.Update
|
||||||
public class UpdatePackageProvider : IUpdatePackageProvider
|
public class UpdatePackageProvider : IUpdatePackageProvider
|
||||||
{
|
{
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly IDroneServicesRequestBuilder _requestBuilder;
|
private readonly IHttpRequestBuilderFactory _requestBuilder;
|
||||||
|
|
||||||
public UpdatePackageProvider(IHttpClient httpClient, IDroneServicesRequestBuilder requestBuilder)
|
public UpdatePackageProvider(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBuilder)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_requestBuilder = requestBuilder;
|
_requestBuilder = requestBuilder.Services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UpdatePackage GetLatestUpdate(string branch, Version currentVersion)
|
public UpdatePackage GetLatestUpdate(string branch, Version currentVersion)
|
||||||
{
|
{
|
||||||
var request = _requestBuilder.Build("/update/{branch}");
|
var request = _requestBuilder.Create()
|
||||||
request.UriBuilder.SetQueryParam("version", currentVersion);
|
.Resource("/update/{branch}")
|
||||||
request.UriBuilder.SetQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant());
|
.AddQueryParam("version", currentVersion)
|
||||||
request.AddSegment("branch", branch);
|
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
|
||||||
|
.SetSegment("branch", branch)
|
||||||
|
.Build();
|
||||||
|
|
||||||
var update = _httpClient.Get<UpdatePackageAvailable>(request).Resource;
|
var update = _httpClient.Get<UpdatePackageAvailable>(request).Resource;
|
||||||
|
|
||||||
|
@ -39,10 +41,12 @@ namespace NzbDrone.Core.Update
|
||||||
|
|
||||||
public List<UpdatePackage> GetRecentUpdates(string branch, Version currentVersion)
|
public List<UpdatePackage> GetRecentUpdates(string branch, Version currentVersion)
|
||||||
{
|
{
|
||||||
var request = _requestBuilder.Build("/update/{branch}/changes");
|
var request = _requestBuilder.Create()
|
||||||
request.UriBuilder.SetQueryParam("version", currentVersion);
|
.Resource("/update/{branch}/changes")
|
||||||
request.UriBuilder.SetQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant());
|
.AddQueryParam("version", currentVersion)
|
||||||
request.AddSegment("branch", branch);
|
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
|
||||||
|
.SetSegment("branch", branch)
|
||||||
|
.Build();
|
||||||
|
|
||||||
var updates = _httpClient.Get<List<UpdatePackage>>(request);
|
var updates = _httpClient.Get<List<UpdatePackage>>(request);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue