Winify – Rev 44

Subversion Repositories:
Rev:
using System;
using System.Drawing;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Serilog;
using Servers;
using WebSocketSharp;
using Configuration = Configuration.Configuration;
using ErrorEventArgs = WebSocketSharp.ErrorEventArgs;

namespace Winify.Gotify
{
    public class GotifyConnection : IDisposable
    {
        #region Public Events & Delegates

        public event EventHandler<GotifyNotificationEventArgs> GotifyNotification;

        #endregion

        #region Private Delegates, Events, Enums, Properties, Indexers and Fields

        private readonly Server _server;

        private CancellationToken _cancellationToken;

        private CancellationTokenSource _cancellationTokenSource;

        private Task _runTask;

        private HttpClient _httpClient;

        private readonly Uri _webSocketsUri;

        private readonly Uri _httpUri;
        private WebSocket _webSocketSharp;
        private readonly global::Configuration.Configuration _configuration;

        #endregion

        #region Constructors, Destructors and Finalizers

        private GotifyConnection()
        {

        }

        public GotifyConnection(Server server, global::Configuration.Configuration configuration) : this()
        {
            _server = server;
            _configuration = configuration;

            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);
                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;
            }
        }

        public void Dispose()
        {
            if (_cancellationTokenSource != null)
            {
                _cancellationTokenSource.Dispose();
                _cancellationTokenSource = null;
            }

            _webSocketSharp.Close();
            _webSocketSharp = null;

            _httpClient.Dispose();
            _httpClient = null;
        }

        #endregion

        #region Public Methods

        public void Start()
        {
            _cancellationTokenSource = new CancellationTokenSource();
            _cancellationToken = _cancellationTokenSource.Token;

            Connect();

            _runTask = Run(_cancellationToken);
        }

        private void Connect()
        {
            _webSocketSharp = new WebSocket(_webSocketsUri.AbsoluteUri);
            _webSocketSharp.SetCredentials(_server.Username, _server.Password, true);
            if (_configuration.IgnoreSelfSignedCertificates)
            {
                _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();
        }

        private void WebSocketSharp_OnClose(object sender, CloseEventArgs e)
        {
            Log.Information($"Connection to server closed with reason: {e.Reason}");
        }

        private void WebSocketSharp_OnOpen(object sender, EventArgs e)
        {
            Log.Information("Connection to server is now open.");
        }

        private async void WebSocketSharp_OnError(object sender, ErrorEventArgs e)
        {
            if (_cancellationToken.IsCancellationRequested)
            {
                Stop();
                return;
            }

            await Task.Delay(TimeSpan.FromSeconds(1), _cancellationToken);
            Log.Information("Reconnecting to websocket server.");

            Connect();
        }

        private async void WebSocketSharp_OnMessage(object sender, MessageEventArgs e)
        {
            if (e.RawData.Length == 0)
            {
                Log.Warning($"Empty message received from server.");
                return;
            }

            var message = Encoding.UTF8.GetString(e.RawData, 0, e.RawData.Length);

            GotifyNotification gotifyNotification;

            try
            { 
                gotifyNotification = JsonConvert.DeserializeObject<GotifyNotification>(message);
            }
            catch (JsonSerializationException exception)
            {
                Log.Warning($"Could not deserialize notification: {exception.Message}");
                return;
            }

            if (gotifyNotification == null)
            {
                Log.Warning($"Could not deserialize gotify notification: {message}");

                return;
            }

            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;
            }

            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 = Image.FromStream(imageStream);

                GotifyNotification?.Invoke(this,
                    new GotifyNotificationEventArgs(gotifyNotification, image));
            }

            Log.Debug($"Notification message received: {gotifyNotification.Message}");
        }

        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 exception) when (exception is OperationCanceledException || exception is ObjectDisposedException)
            {
            }
            catch (Exception exception)
            {
                Log.Warning(exception, "Failure running connection loop.");
            }
        }

        private async Task<Stream> RetrieveGotifyApplicationImage(int appId, Uri applicationUri,
            CancellationToken cancellationToken)
        {
            var applicationResponse = await _httpClient.GetAsync(applicationUri, cancellationToken);

            var applications = await applicationResponse.Content.ReadAsStringAsync();

            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}");

                return null;
            }

            foreach (var application in gotifyApplications)
            {
                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);

                var memoryStream = new MemoryStream();

                await imageResponse.Content.CopyToAsync(memoryStream);

                return memoryStream;
            }

            return null;
        }

        #endregion
    }
}

Generated by GNU Enscript 1.6.5.90.