using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using PocketSharp.Models;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
namespace PocketSharp
{
///
/// PocketClient
///
public partial class PocketClient : IPocketClient
{
///
/// REST client used for the API communication
///
protected readonly HttpClient _restClient;
///
/// Caches HTTP headers from last response
///
public HttpResponseHeaders lastHeaders;
///
/// Caches JSON data from last response
///
public string lastResponseData;
///
/// The base URL for the Pocket API
///
protected static Uri baseUri = new Uri("https://getpocket.com/v3/");
///
/// The authentification URL
///
protected string authentificationUri = "https://getpocket.com/auth/authorize?request_token={0}&redirect_uri={1}&mobile={2}";
///
/// Indicates, whether this client is used for mobile or desktop
///
protected bool isMobileClient = true;
///
/// callback URLi for API calls
///
///
/// The callback URI.
///
public string CallbackUri { get; set; }
///
/// Accessor for the Pocket API key
/// see: http://getpocket.com/developer
///
///
/// The consumer key.
///
public string ConsumerKey { get; set; }
///
/// Code retrieved on authentification
///
///
/// The request code.
///
public string RequestCode { get; set; }
///
/// Code retrieved on authentification-success
///
///
/// The access code.
///
public string AccessCode { get; set; }
///
/// Action which is executed before every request
///
///
/// The pre request callback.
///
public Action PreRequest { get; set; }
///
/// Initializes a new instance of the class.
///
/// The API key
/// Provide an access code if the user is already authenticated
/// The callback URL is called by Pocket after authentication
/// The HttpMessage handler.
/// Request timeout (in seconds).
/// Indicates, whether this client is used for mobile or desktop
public PocketClient(
string consumerKey,
string accessCode = null,
string callbackUri = null,
HttpMessageHandler handler = null,
int? timeout = null,
bool isMobileClient = true)
{
// assign public properties
ConsumerKey = consumerKey;
this.isMobileClient = isMobileClient;
// assign access code if submitted
if (accessCode != null)
{
AccessCode = accessCode.ToString();
}
// assign callback uri if submitted
if (callbackUri != null)
{
CallbackUri = Uri.EscapeUriString(callbackUri.ToString());
}
// initialize REST client
_restClient = new HttpClient(handler ?? new HttpClientHandler()
{
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
});
if (timeout.HasValue)
{
_restClient.Timeout = TimeSpan.FromSeconds(timeout.Value);
}
// set base uri
_restClient.BaseAddress = baseUri;
// Pocket needs this specific Accept header :-S
_restClient.DefaultRequestHeaders.Add("Accept", "*/*");
// defines the response format (according to the Pocket docs)
_restClient.DefaultRequestHeaders.Add("X-Accept", "application/json");
}
///
/// Fetches a typed resource
///
///
/// Requested method (path after /v3/)
/// The cancellation token.
/// Additional POST parameters
/// if set to true [require auth].
///
/// No access token available. Use authentification first.
protected async Task Request(
string method,
CancellationToken cancellationToken,
Dictionary parameters = null,
bool requireAuth = true) where T : class, new()
{
if (requireAuth && AccessCode == null)
{
throw new PocketException("SDK error: No access token available. Use authentication first.");
}
// every single Pocket API endpoint requires HTTP POST data
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, method);
HttpResponseMessage response = null;
if (parameters == null)
{
parameters = new Dictionary();
}
// add consumer key to each request
parameters.Add("consumer_key", ConsumerKey);
// add access token (necessary for all requests except authentification)
if (AccessCode != null)
{
parameters.Add("access_token", AccessCode);
}
// content of the request
request.Content = new FormUrlEncodedContent(parameters);
// call pre request action
if (PreRequest != null)
{
PreRequest(method);
}
// make async request
try
{
response = await _restClient.SendAsync(request, cancellationToken);
}
catch (HttpRequestException exc)
{
throw new PocketException(exc.Message, exc);
}
// validate HTTP response
ValidateResponse(response);
// cache headers
lastHeaders = response.Headers;
// read response
var responseString = await response.Content.ReadAsStringAsync();
// cache response
lastResponseData = responseString;
responseString = responseString.Replace("[]", "{}");
// deserialize object
T parsedResponse = JsonConvert.DeserializeObject(
responseString,
new JsonSerializerSettings
{
Error = (object sender, ErrorEventArgs args) =>
{
throw new PocketException(String.Format("Parse error: {0}", args.ErrorContext.Error.Message));
},
Converters =
{
new PocketItemConverter(),
new BoolConverter(),
new UnixDateTimeConverter(),
new NullableIntConverter(),
new UriConverter()
}
}
);
return parsedResponse;
}
///
/// Sends a list of actions
///
/// The action parameters.
///
internal async Task Send(List actionParameters, CancellationToken cancellationToken)
{
List> actionParamList = new List>();
foreach (var action in actionParameters)
{
actionParamList.Add(action.Convert());
}
Dictionary parameters = new Dictionary() {{
"actions", JsonConvert.SerializeObject(actionParamList)
}};
Modify response = await Request("send", cancellationToken, parameters);
return response.Status;
}
///
/// Sends an action
///
/// The action parameter.
///
internal async Task Send(PocketAction actionParameter, CancellationToken cancellationToken)
{
return await Send(new List() { actionParameter }, cancellationToken);
}
///
/// Validates the response.
///
/// The response.
///
///
/// Error retrieving response
///
protected void ValidateResponse(HttpResponseMessage response)
{
// no error found
if (response.StatusCode == HttpStatusCode.OK)
{
return;
}
string exceptionString = response.ReasonPhrase;
bool isPocketError = response.Headers.Contains("X-Error");
// fetch custom pocket headers
string error = TryGetHeaderValue(response.Headers, "X-Error");
int errorCode = Convert.ToInt32(TryGetHeaderValue(response.Headers, "X-Error-Code"));
// create exception strings
if (isPocketError)
{
exceptionString = String.Format("Pocket error: {0} ({1}) ", error, errorCode);
}
else
{
exceptionString = String.Format("Request error: {0} ({1})", response.ReasonPhrase, (int)response.StatusCode);
}
// create exception
PocketException exception = new PocketException(exceptionString);
if (isPocketError)
{
// add custom pocket fields
exception.PocketError = error;
exception.PocketErrorCode = errorCode;
// add to generic exception data
exception.Data.Add("X-Error", error);
exception.Data.Add("X-Error-Code", errorCode);
}
throw exception;
}
///
/// Tries to fetch a header value.
///
/// The headers.
/// The key.
///
protected string TryGetHeaderValue(HttpResponseHeaders headers, string key)
{
string result = null;
if (headers == null || String.IsNullOrEmpty(key))
{
return null;
}
foreach (var header in headers)
{
if (header.Key == key)
{
var headerEnumerator = header.Value.GetEnumerator();
headerEnumerator.MoveNext();
result = headerEnumerator.Current;
break;
}
}
return result;
}
}
}