Winify – Blame information for rev 51

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 {
51 office 92 Log.Error($"No HTTP URL could be built out of the supplied server URI {_server.Url}");
47 office 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 {
51 office 114 Log.Error($"No WebSockets URL could be built from the provided URL {_server.Url} due to {exception.Message}");
47 office 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 {
51 office 149 Log.Error("Could not start connection to server due to unreadable URLs");
47 office 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  
51 office 182 _webSocketSharp.Log.Output = (logData, s) =>
183 {
184 Log.Information($"WebSockets low level logging reported: {logData.Message}");
185 };
186  
44 office 187 _webSocketSharp.OnMessage += WebSocketSharp_OnMessage;
188 _webSocketSharp.OnError += WebSocketSharp_OnError;
189 _webSocketSharp.OnOpen += WebSocketSharp_OnOpen;
190 _webSocketSharp.OnClose += WebSocketSharp_OnClose;
51 office 191  
44 office 192 _webSocketSharp.ConnectAsync();
1 office 193 }
194  
44 office 195 private void WebSocketSharp_OnClose(object sender, CloseEventArgs e)
196 {
51 office 197 Log.Information($"WebSockets connection to server {_webSocketsUri.AbsoluteUri} closed with reason {e.Reason}");
44 office 198 }
1 office 199  
44 office 200 private void WebSocketSharp_OnOpen(object sender, EventArgs e)
201 {
51 office 202 Log.Information($"WebSockets connection to server {_webSocketsUri.AbsoluteUri} is now open");
44 office 203 }
1 office 204  
44 office 205 private async void WebSocketSharp_OnError(object sender, ErrorEventArgs e)
1 office 206 {
51 office 207 Log.Error($"Connection to WebSockets server {_webSocketsUri.AbsoluteUri} terminated unexpectedly with message {e.Message}", e.Exception);
48 office 208  
44 office 209 if (_cancellationToken.IsCancellationRequested)
1 office 210 {
44 office 211 Stop();
212 return;
213 }
18 office 214  
44 office 215 await Task.Delay(TimeSpan.FromSeconds(1), _cancellationToken);
51 office 216 Log.Information($"Reconnecting to websocket server {_webSocketsUri.AbsoluteUri}");
18 office 217  
44 office 218 Connect();
219 }
1 office 220  
44 office 221 private async void WebSocketSharp_OnMessage(object sender, MessageEventArgs e)
222 {
223 if (e.RawData.Length == 0)
224 {
51 office 225 Log.Warning($"Empty message received from server");
44 office 226 return;
227 }
1 office 228  
44 office 229 var message = Encoding.UTF8.GetString(e.RawData, 0, e.RawData.Length);
1 office 230  
44 office 231 GotifyNotification gotifyNotification;
1 office 232  
44 office 233 try
234 {
235 gotifyNotification = JsonConvert.DeserializeObject<GotifyNotification>(message);
236 }
237 catch (JsonSerializationException exception)
238 {
239 Log.Warning($"Could not deserialize notification: {exception.Message}");
240 return;
241 }
12 office 242  
44 office 243 if (gotifyNotification == null)
244 {
245 Log.Warning($"Could not deserialize gotify notification: {message}");
28 office 246  
44 office 247 return;
248 }
25 office 249  
44 office 250 gotifyNotification.Server = _server;
1 office 251  
44 office 252 if (!Uri.TryCreate(Path.Combine($"{_httpUri}", "application"), UriKind.Absolute,
253 out var applicationUri))
254 {
51 office 255 Log.Warning($"Could not build an URI to an application");
44 office 256 return;
257 }
24 office 258  
44 office 259 using (var imageStream =
260 await RetrieveGotifyApplicationImage(gotifyNotification.AppId, applicationUri, _cancellationToken))
261 {
262 if (imageStream == null)
263 {
51 office 264 Log.Warning("Could not find any application image for notification");
44 office 265 return;
266 }
3 office 267  
44 office 268 var image = Image.FromStream(imageStream);
12 office 269  
44 office 270 GotifyNotification?.Invoke(this,
271 new GotifyNotificationEventArgs(gotifyNotification, image));
272 }
3 office 273  
44 office 274 Log.Debug($"Notification message received: {gotifyNotification.Message}");
275 }
12 office 276  
44 office 277 public void Stop()
278 {
279 if (_cancellationTokenSource != null) _cancellationTokenSource.Cancel();
280 }
281  
282 #endregion
283  
284 #region Private Methods
285  
286 private async Task Run(CancellationToken cancellationToken)
287 {
288 try
289 {
290 do
291 {
292 await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
1 office 293 } while (!cancellationToken.IsCancellationRequested);
294 }
44 office 295 catch (Exception exception) when (exception is OperationCanceledException || exception is ObjectDisposedException)
1 office 296 {
297 }
44 office 298 catch (Exception exception)
1 office 299 {
51 office 300 Log.Warning(exception, "Failure running connection loop");
1 office 301 }
302 }
303  
44 office 304 private async Task<Stream> RetrieveGotifyApplicationImage(int appId, Uri applicationUri,
28 office 305 CancellationToken cancellationToken)
25 office 306 {
39 office 307 var applicationResponse = await _httpClient.GetAsync(applicationUri, cancellationToken);
25 office 308  
39 office 309 var applications = await applicationResponse.Content.ReadAsStringAsync();
25 office 310  
44 office 311 GotifyApplication[] gotifyApplications;
312 try
313 {
314 gotifyApplications =
315 JsonConvert.DeserializeObject<GotifyApplication[]>(applications);
316 }
317 catch (JsonSerializationException exception)
318 {
319 Log.Warning($"Could not deserialize the list of applications from the server: {exception.Message}");
25 office 320  
39 office 321 return null;
322 }
25 office 323  
39 office 324 foreach (var application in gotifyApplications)
325 {
44 office 326 if (application.Id != appId) continue;
25 office 327  
39 office 328 if (!Uri.TryCreate(Path.Combine($"{_httpUri}", $"{application.Image}"), UriKind.Absolute,
329 out var applicationImageUri))
25 office 330 {
51 office 331 Log.Warning("Could not build URL path to application icon");
39 office 332 continue;
333 }
25 office 334  
39 office 335 var imageResponse = await _httpClient.GetAsync(applicationImageUri, cancellationToken);
25 office 336  
44 office 337 var memoryStream = new MemoryStream();
25 office 338  
44 office 339 await imageResponse.Content.CopyToAsync(memoryStream);
340  
341 return memoryStream;
25 office 342 }
343  
344 return null;
345 }
346  
1 office 347 #endregion
348 }
349 }