Commit 73fdd36a authored by Andreas Müller's avatar Andreas Müller
Browse files

Creating Response class and improving code

parent c7dad255
......@@ -12,7 +12,7 @@
<Description>Implementation of the Pushover.net API reference</Description>
<PackageLicenseUrl>https://am-wd.de/?p=about#license</PackageLicenseUrl>
<PackageProjectUrl>https://pushover.net</PackageProjectUrl>
<RepositoryUrl>https://gitlab.com/blackpanther</RepositoryUrl>
<RepositoryUrl>https://gitlab.com/blackpanther/amwd.net.push.pushover</RepositoryUrl>
<PackageTags>push;pushover;api</PackageTags>
<Authors>Andreas Müller</Authors>
<Company />
......
......@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
namespace AMWD.Net.Push
......@@ -132,14 +133,11 @@ namespace AMWD.Net.Push
public string Sound { get; set; }
/// <summary>
/// Gets or sets an image content.
/// Gets or sets an image.
/// </summary>
public byte[] ImageBytes { get; set; }
public PoImage Image { get; set; }
/// <summary>
/// Gets or sets the image type, e.g. image/jpeg.
/// </summary>
public string ImageType { get; set; }
#region readonly
/// <summary>
/// Gets a list of all available sounds.
......@@ -156,6 +154,28 @@ namespace AMWD.Net.Push
}
}
/// <summary>
/// Gets the id of the last request sent to the API.
/// </summary>
public string LastRequestId { get; private set; }
/// <summary>
/// Gets the limit of messages for the app (identified by the AppToken).
/// </summary>
public int? MessageLimit { get; private set; }
/// <summary>
/// Gets the remaining messages for the app.
/// </summary>
public int? RemainingMessages { get; private set; }
/// <summary>
/// Gets the time when the remaining messages are reset.
/// </summary>
public DateTime? ResetAt { get; private set; }
#endregion readonly
#endregion Properties
#region Public methods
......@@ -164,7 +184,7 @@ namespace AMWD.Net.Push
/// Validates the user's key along with the application's token.
/// </summary>
/// <returns>true if the key is valid, otherwise false.</returns>
public async Task<bool> ValidateKey()
public async Task<bool> ValidateKey(CancellationToken cancellationToken = default(CancellationToken))
{
if (isDisposed)
{
......@@ -180,20 +200,26 @@ namespace AMWD.Net.Push
}
var param = new Dictionary<string, string>
{
{ "token", AppToken },
{ "user", UserKey }
};
var response = await Request(RequestMethods.Post, "users/validate.json", param);
{
{ "token", AppToken },
{ "user", UserKey }
};
var response = await Request(RequestMethods.Post, "users/validate.json", param, cancellationToken);
return (int)response["status"] == 1;
LastRequestId = response.RequestId;
MessageLimit = response.Limit.MessageLimit;
RemainingMessages = response.Limit.RemainingMessages;
ResetAt = response.Limit.ResetAt;
return response.IsSuccess;
}
/// <summary>
/// Sends the message.
/// </summary>
/// <returns>The response parameters.</returns>
public async Task<JObject> Send()
public async Task<Response> Send(CancellationToken cancellationToken = default(CancellationToken))
{
if (isDisposed)
{
......@@ -248,7 +274,45 @@ namespace AMWD.Net.Push
param.Add("url_title", UrlTitle);
}
return await Request(RequestMethods.Post, "messages.json", param);
var response = await Request(RequestMethods.Post, "messages.json", param, cancellationToken);
LastRequestId = response.RequestId;
MessageLimit = response.Limit.MessageLimit;
RemainingMessages = response.Limit.RemainingMessages;
ResetAt = response.Limit.ResetAt;
return response;
}
/// <summary>
/// Retrieves the limits for the application.
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<Limits> GetLimits(CancellationToken cancellationToken = default(CancellationToken))
{
if (isDisposed)
{
throw new ObjectDisposedException(GetType().FullName);
}
if (string.IsNullOrWhiteSpace(AppToken))
{
throw new ArgumentNullException(nameof(AppToken));
}
var param = new Dictionary<string, string>
{
{ "token", AppToken }
};
var response = await Request(RequestMethods.Get, "limits.json", param, cancellationToken);
LastRequestId = response.RequestId;
MessageLimit = response.Limit.MessageLimit;
RemainingMessages = response.Limit.RemainingMessages;
ResetAt = response.Limit.ResetAt;
return response.Limit;
}
#endregion Public methods
......@@ -265,30 +329,38 @@ namespace AMWD.Net.Push
{
throw new ArgumentNullException(nameof(AppToken));
}
var dict = new Dictionary<string, string>();
var param = new Dictionary<string, string>
{
{ "token", AppToken }
};
var response = await Request(RequestMethods.Get, "sounds.json", param);
if ((int)response["status"] == 1)
LastRequestId = response.RequestId;
MessageLimit = response.Limit.MessageLimit;
RemainingMessages = response.Limit.RemainingMessages;
ResetAt = response.Limit.ResetAt;
var dict = new Dictionary<string, string>();
if (response.IsSuccess)
{
foreach (JProperty sound in response["sounds"])
foreach (JProperty sound in response.Raw["sounds"])
{
dict.Add(sound.Name, sound.Value.ToString());
}
}
return dict;
}
private async Task<JObject> Request(RequestMethods method, string path, Dictionary<string, string> param = null)
private async Task<Response> Request(RequestMethods method, string path, Dictionary<string, string> param = null, CancellationToken cancellationToken = default(CancellationToken))
{
if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentNullException(nameof(path));
}
client.DefaultRequestHeaders.Clear();
HttpResponseMessage response = null;
switch (method)
{
......@@ -298,7 +370,7 @@ namespace AMWD.Net.Push
{
queryString = "?" + string.Join("&", param.Select(kvp => kvp.Key + "=" + kvp.Value));
}
response = await client.GetAsync($"/{Version}/{path.Trim('/')}{queryString}");
response = await client.GetAsync($"/{Version}/{path.Trim('/')}{queryString}", cancellationToken);
break;
case RequestMethods.Post:
if (param == null)
......@@ -314,19 +386,19 @@ namespace AMWD.Net.Push
form.Add(new StringContent(kvp.Value), kvp.Key);
}
}
if (ImageBytes?.Length > 0)
if (Image?.Bytes?.Length > 0)
{
if (string.IsNullOrWhiteSpace(ImageType))
if (string.IsNullOrWhiteSpace(Image.Type))
{
throw new ArgumentException("Image type not set.");
}
var bac = new ByteArrayContent(ImageBytes);
bac.Headers.ContentType = new MediaTypeHeaderValue(ImageType);
var bac = new ByteArrayContent(Image.Bytes);
bac.Headers.ContentType = new MediaTypeHeaderValue(Image.Type);
bac.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
form.Add(bac, "attachment");
}
response = await client.PostAsync($"/{Version}/{path.Trim('/')}", form);
response = await client.PostAsync($"/{Version}/{path.Trim('/')}", form, cancellationToken);
}
break;
default:
......@@ -334,16 +406,24 @@ namespace AMWD.Net.Push
}
var json = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<JObject>(json);
var result = new Response(json);
result.ParseHeaders(response.Headers);
return result;
}
private int ToUnixTimestamp(DateTime dt)
private static int ToUnixTimestamp(DateTime datetime)
{
return (int)dt.ToUniversalTime()
return (int)datetime.ToUniversalTime()
.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc))
.TotalSeconds;
}
private static DateTime FromUnixTimestamp(int unix)
{
return new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)
.AddSeconds(unix);
}
#endregion Private methods
#region IDisposable implementation
......@@ -408,5 +488,197 @@ namespace AMWD.Net.Push
}
#endregion Enums
#region Classes
/// <summary>
/// Contains the response content.
/// </summary>
public class Response
{
/// <summary>
/// Initializes a new instance of the <see cref="Pushover.Response"/> class.
/// </summary>
public Response()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Pushover.Response"/> class.
/// </summary>
/// <param name="json">JSON serialized data.</param>
public Response(string json)
: this(JObject.Parse(json))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Pushover.Response"/> class.
/// </summary>
/// <param name="jObject">Data of the response.</param>
public Response(JObject jObject)
{
Raw = jObject;
Status = jObject["status"].Value<int>();
RequestId = jObject["request"].Value<string>();
if (jObject.ContainsKey("receipt"))
{
Receipt = jObject["receipt"].Value<string>();
}
if (jObject.ContainsKey("errors"))
{
foreach (var error in jObject["errors"] as JArray)
{
Errors.Add(error.Value<string>());
}
}
if (jObject.ContainsKey("limit"))
{
Limit = new Limits
{
MessageLimit = jObject["limit"].Value<int>(),
RemainingMessages = jObject["remaining"].Value<int>(),
ResetAt = FromUnixTimestamp(jObject["reset"].Value<int>())
};
}
if (jObject.ContainsKey("app_limits"))
{
Limit = jObject["app_limits"].Value<Limits>();
}
}
/// <summary>
/// Gets or sets the status.
/// </summary>
[JsonProperty("status")]
public int Status { get; set; }
/// <summary>
/// Gets a value indicating whether the request was successful.
/// </summary>
[JsonIgnore]
public bool IsSuccess => Status == 1;
/// <summary>
/// Gets or sets the request id.
/// </summary>
[JsonProperty("request")]
public string RequestId { get; set; }
/// <summary>
/// Gets the list of errors (if IsSuccess == false)
/// </summary>
[JsonProperty("errors")]
public List<string> Errors { get; } = new List<string>();
/// <summary>
/// Gets or sets the receipt url if <see cref="Pushover.Priority"/> was <see cref="Pushover.Priorities.Emergency"/>.
/// </summary>
[JsonProperty("receipt")]
public string Receipt { get; set; }
/// <summary>
/// Gets or sets the application limits (if availble).
/// </summary>
[JsonProperty("app_limits")]
public Limits Limit { get; set; }
/// <summary>
/// Gets the raw JSON object.
/// </summary>
public JToken Raw { get; private set; }
/// <summary>
/// Serializes this instance to JSON.
/// </summary>
/// <returns></returns>
public string ToJson()
{
return JsonConvert.SerializeObject(this);
}
/// <summary>
/// Parses the headers for limits.
/// </summary>
/// <param name="headers">The headers of the response.</param>
public void ParseHeaders(HttpResponseHeaders headers)
{
if (headers.TryGetValues("X-Request-Id", out IEnumerable<string> outRequest))
{
RequestId = outRequest.First();
}
if (headers.TryGetValues("X-Limit-App-Limit", out IEnumerable<string> outLimit))
{
if (Limit == null)
{
Limit = new Limits();
}
Limit.MessageLimit = int.Parse(outLimit.First());
}
if (headers.TryGetValues("X-Limit-App-Remaining", out IEnumerable<string> outRemaining))
{
if (Limit == null)
{
Limit = new Limits();
}
Limit.RemainingMessages = int.Parse(outRemaining.First());
}
if (headers.TryGetValues("X-Limit-App-Reset", out IEnumerable<string> outReset))
{
if (Limit == null)
{
Limit = new Limits();
}
Limit.ResetAt = FromUnixTimestamp(int.Parse(outReset.First()));
}
}
}
/// <summary>
/// Contains the application limits.
/// </summary>
public class Limits
{
/// <summary>
/// Gets or sets the limit.
/// </summary>
public int MessageLimit { get; set; }
/// <summary>
/// Gets or sets the remaining messages.
/// </summary>
public int RemainingMessages { get; set; }
/// <summary>
/// Gets or sets the time when the remaining messages are reset.
/// </summary>
public DateTime ResetAt { get; set; }
}
/// <summary>
/// Contains the data of an image for pushover.
/// </summary>
public class PoImage
{
/// <summary>
/// Gets or sets an image content.
/// </summary>
public byte[] Bytes { get; set; }
/// <summary>
/// Gets or sets the image type, e.g. image/jpeg.
/// </summary>
public string Type { get; set; }
}
#endregion Classes
}
}
......@@ -3,7 +3,7 @@
Implements the [Pushover](https://pushover.net) API reference.
This library is written in .NET Standard 1.1.
[![NuGet](https://img.shields.io/nuget/v/AMWD.Net.Push.Pushover.svg)](https://www.nuget.org/packages/AMWD.Net.Push.Pushover)
[![NuGet](https://img.shields.io/nuget/v/AMWD.Net.Push.Pushover.svg?logo=nuget&label=-&colorA=white&style=popout-square)](https://www.nuget.org/packages/AMWD.Net.Push.Pushover)
## License
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment