Winify – Diff between revs 51 and 54

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