Winify – Blame information for rev 54

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 using System;
2 using System.Drawing;
3 using System.IO;
50 office 4 using System.Net;
1 office 5 using System.Net.Http;
6 using System.Net.Http.Headers;
46 office 7 using System.Net.Security;
50 office 8 using System.Security.Authentication;
46 office 9 using System.Security.Cryptography.X509Certificates;
1 office 10 using System.Text;
11 using System.Threading;
12 using System.Threading.Tasks;
13 using Newtonsoft.Json;
18 office 14 using Serilog;
24 office 15 using Servers;
44 office 16 using WebSocketSharp;
50 office 17 using WebSocketSharp.Net;
18 using static System.Windows.Forms.VisualStyles.VisualStyleElement.StartPanel;
44 office 19 using Configuration = Configuration.Configuration;
20 using ErrorEventArgs = WebSocketSharp.ErrorEventArgs;
50 office 21 using NetworkCredential = System.Net.NetworkCredential;
1 office 22  
23 namespace Winify.Gotify
24 {
25 public class GotifyConnection : IDisposable
26 {
27 #region Public Events & Delegates
28  
29 public event EventHandler<GotifyNotificationEventArgs> GotifyNotification;
30  
31 #endregion
32  
33 #region Private Delegates, Events, Enums, Properties, Indexers and Fields
34  
25 office 35 private readonly Server _server;
36  
1 office 37 private CancellationToken _cancellationToken;
38  
39 private CancellationTokenSource _cancellationTokenSource;
40  
41 private Task _runTask;
42  
39 office 43 private HttpClient _httpClient;
44  
45 private readonly Uri _webSocketsUri;
46  
47 private readonly Uri _httpUri;
44 office 48 private WebSocket _webSocketSharp;
49 private readonly global::Configuration.Configuration _configuration;
39 office 50  
25 office 51 #endregion
1 office 52  
25 office 53 #region Constructors, Destructors and Finalizers
24 office 54  
44 office 55 private GotifyConnection()
24 office 56 {
44 office 57  
39 office 58 }
59  
44 office 60 public GotifyConnection(Server server, global::Configuration.Configuration configuration) : this()
39 office 61 {
24 office 62 _server = server;
44 office 63 _configuration = configuration;
39 office 64  
54 office 65 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
50 office 66 var httpClientHandler = new HttpClientHandler()
67 {
54 office 68 // mono does not implement this
69 //SslProtocols = SslProtocols.Tls12
50 office 70 };
71  
44 office 72 _httpClient = new HttpClient(httpClientHandler);
73 if (_configuration.IgnoreSelfSignedCertificates)
74 {
75 httpClientHandler.ServerCertificateCustomValidationCallback =
46 office 76 (httpRequestMessage, cert, cetChain, policyErrors) => true;
44 office 77 }
39 office 78  
50 office 79 if (_configuration.Proxy.Enable)
80 {
81 httpClientHandler.Proxy = new WebProxy(_configuration.Proxy.Url, false, new string[] { },
82 new NetworkCredential(_configuration.Proxy.Username, _configuration.Proxy.Password));
83 }
84  
47 office 85 _httpClient = new HttpClient(httpClientHandler);
86 if (!string.IsNullOrEmpty(_server.Username) && !string.IsNullOrEmpty(_server.Password))
44 office 87 {
47 office 88 _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
89 Convert.ToBase64String(Encoding.Default.GetBytes($"{_server.Username}:{_server.Password}")));
90 }
44 office 91  
47 office 92 if (!Uri.TryCreate(_server.Url, UriKind.Absolute, out _httpUri))
39 office 93 {
51 office 94 Log.Error($"No HTTP URL could be built out of the supplied server URI {_server.Url}");
47 office 95 return;
96 }
44 office 97  
47 office 98 // Build the web sockets URI.
99 var webSocketsUriBuilder = new UriBuilder(_httpUri);
100 switch (webSocketsUriBuilder.Scheme.ToUpperInvariant())
101 {
102 case "HTTP":
103 webSocketsUriBuilder.Scheme = "ws";
104 break;
105 case "HTTPS":
106 webSocketsUriBuilder.Scheme = "wss";
107 break;
108 }
109  
110 try
111 {
39 office 112 webSocketsUriBuilder.Path = Path.Combine(webSocketsUriBuilder.Path, "stream");
113 }
47 office 114 catch (ArgumentException exception)
115 {
51 office 116 Log.Error($"No WebSockets URL could be built from the provided URL {_server.Url} due to {exception.Message}");
47 office 117 }
118  
119 _webSocketsUri = webSocketsUriBuilder.Uri;
24 office 120 }
121  
1 office 122 public void Dispose()
123 {
124 if (_cancellationTokenSource != null)
125 {
126 _cancellationTokenSource.Dispose();
127 _cancellationTokenSource = null;
128 }
39 office 129  
48 office 130 if (_webSocketSharp != null)
131 {
132 _webSocketSharp.Close();
133 _webSocketSharp = null;
134 }
44 office 135  
48 office 136 if (_httpClient != null)
137 {
138 _httpClient.Dispose();
139 _httpClient = null;
140 }
1 office 141 }
142  
143 #endregion
144  
145 #region Public Methods
146  
25 office 147 public void Start()
1 office 148 {
47 office 149 if (_webSocketsUri == null || _httpUri == null)
150 {
51 office 151 Log.Error("Could not start connection to server due to unreadable URLs");
47 office 152 return;
153 }
154  
1 office 155 _cancellationTokenSource = new CancellationTokenSource();
156 _cancellationToken = _cancellationTokenSource.Token;
157  
44 office 158 Connect();
159  
39 office 160 _runTask = Run(_cancellationToken);
1 office 161 }
162  
44 office 163 private void Connect()
1 office 164 {
44 office 165 _webSocketSharp = new WebSocket(_webSocketsUri.AbsoluteUri);
50 office 166 _webSocketSharp.SslConfiguration = new ClientSslConfiguration(_webSocketsUri.Host,
167 new X509CertificateCollection(new X509Certificate[] { }), SslProtocols.Tls12, false);
168 if (_configuration.Proxy.Enable)
169 {
170 _webSocketSharp.SetProxy(_configuration.Proxy.Url, _configuration.Proxy.Username, _configuration.Proxy.Password);
171 }
172  
47 office 173 if (!string.IsNullOrEmpty(_server.Username) && !string.IsNullOrEmpty(_server.Password))
174 {
175 _webSocketSharp.SetCredentials(_server.Username, _server.Password, true);
176 }
177  
44 office 178 if (_configuration.IgnoreSelfSignedCertificates)
39 office 179 {
44 office 180 _webSocketSharp.SslConfiguration.ServerCertificateValidationCallback +=
181 (sender, certificate, chain, errors) => true;
39 office 182 }
44 office 183  
51 office 184 _webSocketSharp.Log.Output = (logData, s) =>
185 {
186 Log.Information($"WebSockets low level logging reported: {logData.Message}");
187 };
188  
44 office 189 _webSocketSharp.OnMessage += WebSocketSharp_OnMessage;
190 _webSocketSharp.OnError += WebSocketSharp_OnError;
191 _webSocketSharp.OnOpen += WebSocketSharp_OnOpen;
192 _webSocketSharp.OnClose += WebSocketSharp_OnClose;
51 office 193  
44 office 194 _webSocketSharp.ConnectAsync();
1 office 195 }
196  
44 office 197 private void WebSocketSharp_OnClose(object sender, CloseEventArgs e)
198 {
51 office 199 Log.Information($"WebSockets connection to server {_webSocketsUri.AbsoluteUri} closed with reason {e.Reason}");
44 office 200 }
1 office 201  
44 office 202 private void WebSocketSharp_OnOpen(object sender, EventArgs e)
203 {
51 office 204 Log.Information($"WebSockets connection to server {_webSocketsUri.AbsoluteUri} is now open");
44 office 205 }
1 office 206  
44 office 207 private async void WebSocketSharp_OnError(object sender, ErrorEventArgs e)
1 office 208 {
51 office 209 Log.Error($"Connection to WebSockets server {_webSocketsUri.AbsoluteUri} terminated unexpectedly with message {e.Message}", e.Exception);
48 office 210  
44 office 211 if (_cancellationToken.IsCancellationRequested)
1 office 212 {
44 office 213 Stop();
214 return;
215 }
18 office 216  
44 office 217 await Task.Delay(TimeSpan.FromSeconds(1), _cancellationToken);
51 office 218 Log.Information($"Reconnecting to websocket server {_webSocketsUri.AbsoluteUri}");
18 office 219  
44 office 220 Connect();
221 }
1 office 222  
44 office 223 private async void WebSocketSharp_OnMessage(object sender, MessageEventArgs e)
224 {
225 if (e.RawData.Length == 0)
226 {
51 office 227 Log.Warning($"Empty message received from server");
44 office 228 return;
229 }
1 office 230  
44 office 231 var message = Encoding.UTF8.GetString(e.RawData, 0, e.RawData.Length);
1 office 232  
44 office 233 GotifyNotification gotifyNotification;
1 office 234  
44 office 235 try
236 {
237 gotifyNotification = JsonConvert.DeserializeObject<GotifyNotification>(message);
238 }
239 catch (JsonSerializationException exception)
240 {
241 Log.Warning($"Could not deserialize notification: {exception.Message}");
242 return;
243 }
12 office 244  
44 office 245 if (gotifyNotification == null)
246 {
247 Log.Warning($"Could not deserialize gotify notification: {message}");
28 office 248  
44 office 249 return;
250 }
25 office 251  
44 office 252 gotifyNotification.Server = _server;
1 office 253  
44 office 254 if (!Uri.TryCreate(Path.Combine($"{_httpUri}", "application"), UriKind.Absolute,
255 out var applicationUri))
256 {
51 office 257 Log.Warning($"Could not build an URI to an application");
44 office 258 return;
259 }
24 office 260  
44 office 261 using (var imageStream =
262 await RetrieveGotifyApplicationImage(gotifyNotification.AppId, applicationUri, _cancellationToken))
263 {
264 if (imageStream == null)
265 {
51 office 266 Log.Warning("Could not find any application image for notification");
44 office 267 return;
268 }
3 office 269  
44 office 270 var image = Image.FromStream(imageStream);
12 office 271  
44 office 272 GotifyNotification?.Invoke(this,
273 new GotifyNotificationEventArgs(gotifyNotification, image));
274 }
3 office 275  
44 office 276 Log.Debug($"Notification message received: {gotifyNotification.Message}");
277 }
12 office 278  
44 office 279 public void Stop()
280 {
281 if (_cancellationTokenSource != null) _cancellationTokenSource.Cancel();
282 }
283  
284 #endregion
285  
286 #region Private Methods
287  
288 private async Task Run(CancellationToken cancellationToken)
289 {
290 try
291 {
292 do
293 {
294 await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
1 office 295 } while (!cancellationToken.IsCancellationRequested);
296 }
44 office 297 catch (Exception exception) when (exception is OperationCanceledException || exception is ObjectDisposedException)
1 office 298 {
299 }
44 office 300 catch (Exception exception)
1 office 301 {
51 office 302 Log.Warning(exception, "Failure running connection loop");
1 office 303 }
304 }
305  
44 office 306 private async Task<Stream> RetrieveGotifyApplicationImage(int appId, Uri applicationUri,
28 office 307 CancellationToken cancellationToken)
25 office 308 {
39 office 309 var applicationResponse = await _httpClient.GetAsync(applicationUri, cancellationToken);
25 office 310  
39 office 311 var applications = await applicationResponse.Content.ReadAsStringAsync();
25 office 312  
44 office 313 GotifyApplication[] gotifyApplications;
314 try
315 {
316 gotifyApplications =
317 JsonConvert.DeserializeObject<GotifyApplication[]>(applications);
318 }
319 catch (JsonSerializationException exception)
320 {
321 Log.Warning($"Could not deserialize the list of applications from the server: {exception.Message}");
25 office 322  
39 office 323 return null;
324 }
25 office 325  
39 office 326 foreach (var application in gotifyApplications)
327 {
44 office 328 if (application.Id != appId) continue;
25 office 329  
39 office 330 if (!Uri.TryCreate(Path.Combine($"{_httpUri}", $"{application.Image}"), UriKind.Absolute,
331 out var applicationImageUri))
25 office 332 {
51 office 333 Log.Warning("Could not build URL path to application icon");
39 office 334 continue;
335 }
25 office 336  
39 office 337 var imageResponse = await _httpClient.GetAsync(applicationImageUri, cancellationToken);
25 office 338  
44 office 339 var memoryStream = new MemoryStream();
25 office 340  
44 office 341 await imageResponse.Content.CopyToAsync(memoryStream);
342  
343 return memoryStream;
25 office 344 }
345  
346 return null;
347 }
348  
1 office 349 #endregion
350 }
351 }