Winify – Blame information for rev 50

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