/trunk/Winify/Gotify/GotifyConnection.cs |
@@ -3,7 +3,6 @@ |
using System.IO; |
using System.Net.Http; |
using System.Net.Http.Headers; |
using System.Net.WebSockets; |
using System.Text; |
using System.Threading; |
using System.Threading.Tasks; |
@@ -10,7 +9,9 @@ |
using Newtonsoft.Json; |
using Serilog; |
using Servers; |
using ClientWebSocket = System.Net.WebSockets.Managed.ClientWebSocket; |
using WebSocketSharp; |
using Configuration = Configuration.Configuration; |
using ErrorEventArgs = WebSocketSharp.ErrorEventArgs; |
|
namespace Winify.Gotify |
{ |
@@ -34,34 +35,57 @@ |
|
private HttpClient _httpClient; |
|
private readonly string _auth; |
|
private readonly Uri _webSocketsUri; |
|
private readonly Uri _httpUri; |
private WebSocket _webSocketSharp; |
private readonly global::Configuration.Configuration _configuration; |
|
#endregion |
|
#region Constructors, Destructors and Finalizers |
|
public GotifyConnection() |
private GotifyConnection() |
{ |
_httpClient = new HttpClient(); |
|
} |
|
public GotifyConnection(Server server) : this() |
public GotifyConnection(Server server, global::Configuration.Configuration configuration) : this() |
{ |
_server = server; |
_configuration = configuration; |
|
_auth = Convert.ToBase64String(Encoding.Default.GetBytes($"{_server.Username}:{_server.Password}")); |
_httpClient.DefaultRequestHeaders.Authorization = |
new AuthenticationHeaderValue("Basic", _auth); |
var httpClientHandler = new HttpClientHandler(); |
_httpClient = new HttpClient(httpClientHandler); |
if (_configuration.IgnoreSelfSignedCertificates) |
{ |
httpClientHandler.ServerCertificateCustomValidationCallback = |
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; |
} |
|
_httpClient = new HttpClient(httpClientHandler) |
{ |
DefaultRequestHeaders = |
{ |
Authorization = new AuthenticationHeaderValue("Basic", |
Convert.ToBase64String(Encoding.Default.GetBytes($"{_server.Username}:{_server.Password}"))) |
} |
}; |
|
if (Uri.TryCreate(_server.Url, UriKind.Absolute, out _httpUri)) |
{ |
// Build the web sockets URI. |
var webSocketsUriBuilder = new UriBuilder(_httpUri); |
webSocketsUriBuilder.Scheme = "ws"; |
switch (webSocketsUriBuilder.Scheme.ToUpperInvariant()) |
{ |
case "HTTP": |
webSocketsUriBuilder.Scheme = "ws"; |
break; |
case "HTTPS": |
webSocketsUriBuilder.Scheme = "wss"; |
break; |
} |
|
webSocketsUriBuilder.Path = Path.Combine(webSocketsUriBuilder.Path, "stream"); |
_webSocketsUri = webSocketsUriBuilder.Uri; |
} |
@@ -75,6 +99,9 @@ |
_cancellationTokenSource = null; |
} |
|
_webSocketSharp.Close(); |
_webSocketSharp = null; |
|
_httpClient.Dispose(); |
_httpClient = null; |
} |
@@ -88,99 +115,136 @@ |
_cancellationTokenSource = new CancellationTokenSource(); |
_cancellationToken = _cancellationTokenSource.Token; |
|
Connect(); |
|
_runTask = Run(_cancellationToken); |
} |
|
public void Stop() |
private void Connect() |
{ |
if (_cancellationTokenSource != null) |
_webSocketSharp = new WebSocket(_webSocketsUri.AbsoluteUri); |
_webSocketSharp.SetCredentials(_server.Username, _server.Password, true); |
if (_configuration.IgnoreSelfSignedCertificates) |
{ |
_cancellationTokenSource.Cancel(); |
_webSocketSharp.SslConfiguration.ServerCertificateValidationCallback += |
(sender, certificate, chain, errors) => true; |
} |
|
_webSocketSharp.OnMessage += WebSocketSharp_OnMessage; |
_webSocketSharp.OnError += WebSocketSharp_OnError; |
_webSocketSharp.OnOpen += WebSocketSharp_OnOpen; |
_webSocketSharp.OnClose += WebSocketSharp_OnClose; |
_webSocketSharp.ConnectAsync(); |
} |
|
#endregion |
private void WebSocketSharp_OnClose(object sender, CloseEventArgs e) |
{ |
Log.Information($"Connection to server closed with reason: {e.Reason}"); |
} |
|
#region Private Methods |
private void WebSocketSharp_OnOpen(object sender, EventArgs e) |
{ |
Log.Information("Connection to server is now open."); |
} |
|
private async Task Run(CancellationToken cancellationToken) |
private async void WebSocketSharp_OnError(object sender, ErrorEventArgs e) |
{ |
try |
if (_cancellationToken.IsCancellationRequested) |
{ |
do |
{ |
try |
{ |
using (var webSocketClient = new ClientWebSocket()) |
{ |
webSocketClient.Options.SetRequestHeader("Authorization", $"Basic {_auth}"); |
Stop(); |
return; |
} |
|
await webSocketClient.ConnectAsync(_webSocketsUri, cancellationToken); |
await Task.Delay(TimeSpan.FromSeconds(1), _cancellationToken); |
Log.Information("Reconnecting to websocket server."); |
|
do |
{ |
var payload = new ArraySegment<byte>(new byte[1024]); |
Connect(); |
} |
|
var result = await webSocketClient.ReceiveAsync(payload, cancellationToken); |
private async void WebSocketSharp_OnMessage(object sender, MessageEventArgs e) |
{ |
if (e.RawData.Length == 0) |
{ |
Log.Warning($"Empty message received from server."); |
return; |
} |
|
if (result.Count == 0) |
{ |
continue; |
} |
var message = Encoding.UTF8.GetString(e.RawData, 0, e.RawData.Length); |
|
if (payload.Array == null || payload.Count == 0) |
{ |
continue; |
} |
GotifyNotification gotifyNotification; |
|
var message = Encoding.UTF8.GetString(payload.Array, 0, payload.Count); |
try |
{ |
gotifyNotification = JsonConvert.DeserializeObject<GotifyNotification>(message); |
} |
catch (JsonSerializationException exception) |
{ |
Log.Warning($"Could not deserialize notification: {exception.Message}"); |
return; |
} |
|
var gotifyNotification = JsonConvert.DeserializeObject<GotifyNotification>(message); |
if (gotifyNotification == null) |
{ |
Log.Warning($"Could not deserialize gotify notification: {message}"); |
|
if (gotifyNotification == null) |
{ |
Log.Warning($"Could not deserialize gotify notification: {message}"); |
return; |
} |
|
continue; |
} |
gotifyNotification.Server = _server; |
|
gotifyNotification.Server = _server; |
if (!Uri.TryCreate(Path.Combine($"{_httpUri}", "application"), UriKind.Absolute, |
out var applicationUri)) |
{ |
Log.Warning($"Could not build an URI to an application."); |
return; |
} |
|
if (!Uri.TryCreate(Path.Combine($"{_httpUri}", "application"), UriKind.Absolute, |
out var applicationUri)) |
{ |
continue; |
} |
using (var imageStream = |
await RetrieveGotifyApplicationImage(gotifyNotification.AppId, applicationUri, _cancellationToken)) |
{ |
if (imageStream == null) |
{ |
Log.Warning("Could not find any application image for notification."); |
return; |
} |
|
var image = await RetrieveGotifyApplicationImage(gotifyNotification.AppId, |
applicationUri, cancellationToken); |
var image = Image.FromStream(imageStream); |
|
GotifyNotification?.Invoke(this, |
new GotifyNotificationEventArgs(gotifyNotification, image)); |
GotifyNotification?.Invoke(this, |
new GotifyNotificationEventArgs(gotifyNotification, image)); |
} |
|
Log.Debug($"Notification message received: {gotifyNotification.Message}"); |
} while (!cancellationToken.IsCancellationRequested); |
} |
} |
catch (Exception ex) when (ex is WebSocketException || ex is HttpRequestException) |
{ |
// Reconnect |
Log.Warning($"Unable to connect to gotify server: {ex.Message}"); |
Log.Debug($"Notification message received: {gotifyNotification.Message}"); |
} |
|
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); |
} |
public void Stop() |
{ |
if (_cancellationTokenSource != null) _cancellationTokenSource.Cancel(); |
} |
|
#endregion |
|
#region Private Methods |
|
private async Task Run(CancellationToken cancellationToken) |
{ |
try |
{ |
do |
{ |
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); |
} while (!cancellationToken.IsCancellationRequested); |
} |
catch (Exception ex) when (ex is OperationCanceledException || ex is ObjectDisposedException) |
catch (Exception exception) when (exception is OperationCanceledException || exception is ObjectDisposedException) |
{ |
} |
catch (Exception ex) |
catch (Exception exception) |
{ |
Log.Warning(ex, "Failure running connection loop."); |
Log.Warning(exception, "Failure running connection loop."); |
} |
} |
|
private async Task<Image> RetrieveGotifyApplicationImage(int appId, Uri applicationUri, |
private async Task<Stream> RetrieveGotifyApplicationImage(int appId, Uri applicationUri, |
CancellationToken cancellationToken) |
{ |
var applicationResponse = await _httpClient.GetAsync(applicationUri, cancellationToken); |
@@ -187,35 +251,37 @@ |
|
var applications = await applicationResponse.Content.ReadAsStringAsync(); |
|
var gotifyApplications = |
JsonConvert.DeserializeObject<GotifyApplication[]>(applications); |
GotifyApplication[] gotifyApplications; |
try |
{ |
gotifyApplications = |
JsonConvert.DeserializeObject<GotifyApplication[]>(applications); |
} |
catch (JsonSerializationException exception) |
{ |
Log.Warning($"Could not deserialize the list of applications from the server: {exception.Message}"); |
|
if (gotifyApplications == null) |
{ |
return null; |
} |
|
foreach (var application in gotifyApplications) |
{ |
if (application.Id != appId) |
{ |
continue; |
} |
if (application.Id != appId) continue; |
|
if (!Uri.TryCreate(Path.Combine($"{_httpUri}", $"{application.Image}"), UriKind.Absolute, |
out var applicationImageUri)) |
{ |
Log.Warning("Could not build URL path to application icon."); |
continue; |
} |
|
var imageResponse = await _httpClient.GetAsync(applicationImageUri, cancellationToken); |
|
using (var memoryStream = new MemoryStream()) |
{ |
await imageResponse.Content.CopyToAsync(memoryStream); |
var memoryStream = new MemoryStream(); |
|
return Image.FromStream(memoryStream); |
} |
await imageResponse.Content.CopyToAsync(memoryStream); |
|
return memoryStream; |
} |
|
return null; |