Winify – Diff between revs 66 and 67
?pathlinks?
Rev 66 | Rev 67 | |||
---|---|---|---|---|
Line 1... | Line 1... | |||
1 | using System; |
1 | using System; |
|
2 | using System.Collections.Concurrent; |
2 | using System.Collections.Concurrent; |
|
3 | using System.Collections.Generic; |
3 | using System.Collections.Generic; |
|
4 | using System.Diagnostics; |
4 | using System.Diagnostics; |
|
5 | using System.Drawing; |
5 | using System.Drawing; |
|
6 | using System.Globalization; |
- | ||
7 | using System.IO; |
6 | using System.IO; |
|
8 | using System.Linq; |
- | ||
9 | using System.Net; |
7 | using System.Net; |
|
10 | using System.Net.Http; |
8 | using System.Net.Http; |
|
11 | using System.Net.Http.Headers; |
9 | using System.Net.Http.Headers; |
|
12 | using System.Runtime.Caching; |
- | ||
13 | using System.Runtime.CompilerServices; |
10 | using System.Runtime.CompilerServices; |
|
14 | using System.Security.Authentication; |
11 | using System.Security.Authentication; |
|
15 | using System.Security.Cryptography.X509Certificates; |
12 | using System.Security.Cryptography.X509Certificates; |
|
16 | using System.Text; |
13 | using System.Text; |
|
17 | using System.Threading; |
14 | using System.Threading; |
|
18 | using System.Threading.Tasks; |
15 | using System.Threading.Tasks; |
|
19 | using System.Threading.Tasks.Dataflow; |
16 | using System.Threading.Tasks.Dataflow; |
|
20 | using System.Windows.Forms; |
- | ||
21 | using Newtonsoft.Json; |
17 | using Newtonsoft.Json; |
|
22 | using Org.BouncyCastle.Asn1.Pkcs; |
- | ||
23 | using Serilog; |
18 | using Serilog; |
|
24 | using Servers; |
19 | using Servers; |
|
25 | using WebSocketSharp; |
20 | using WebSocketSharp; |
|
26 | using WebSocketSharp.Net; |
21 | using WebSocketSharp.Net; |
|
27 | using Winify.Utilities; |
22 | using Winify.Utilities; |
|
Line 32... | Line 27... | |||
32 | { |
27 | { |
|
33 | public class GotifyConnection : IDisposable |
28 | public class GotifyConnection : IDisposable |
|
34 | { |
29 | { |
|
35 | #region Public Events & Delegates |
30 | #region Public Events & Delegates |
|
Line 36... | Line 31... | |||
36 | |
31 | |
|
Line 37... | Line 32... | |||
37 | public event EventHandler<GotifyNotificationEventArgs> GotifyNotification; |
32 | public event EventHandler<GotifyMessageEventArgs> GotifyMessage; |
|
Line 38... | Line 33... | |||
38 | |
33 | |
|
Line 55... | Line 50... | |||
55 | private readonly Uri _httpUri; |
50 | private readonly Uri _httpUri; |
|
56 | private WebSocket _webSocketSharp; |
51 | private WebSocket _webSocketSharp; |
|
57 | private readonly Configuration.Configuration _configuration; |
52 | private readonly Configuration.Configuration _configuration; |
|
58 | private IDisposable _tplRetrievePastMessagesLink; |
53 | private IDisposable _tplRetrievePastMessagesLink; |
|
59 | private IDisposable _tplWebSocketsBufferBlockTransformLink; |
54 | private IDisposable _tplWebSocketsBufferBlockTransformLink; |
|
60 | private IDisposable _tplWebSocketsTransformActionLink; |
- | ||
61 | private IDisposable _tplWebSocketsTransformActionNullLink; |
- | ||
62 | private readonly BufferBlock<GotifyConnectionData> _webSocketMessageBufferBlock; |
55 | private readonly BufferBlock<GotifyConnectionData> _webSocketMessageBufferBlock; |
|
63 | private readonly Stopwatch _webSocketsClientPingStopWatch; |
56 | private readonly Stopwatch _webSocketsClientPingStopWatch; |
|
64 | private readonly ScheduledContinuation _webSocketsServerResponseScheduledContinuation; |
57 | private readonly ScheduledContinuation _webSocketsServerResponseScheduledContinuation; |
|
Line 65... | Line 58... | |||
65 | |
58 | |
|
Line 80... | Line 73... | |||
80 | new DataflowBlockOptions |
73 | new DataflowBlockOptions |
|
81 | { |
74 | { |
|
82 | CancellationToken = _cancellationToken |
75 | CancellationToken = _cancellationToken |
|
83 | }); |
76 | }); |
|
Line 84... | Line 77... | |||
84 | |
77 | |
|
85 | var webSocketTransformBlock = new TransformBlock<GotifyConnectionData, GotifyMessage>(data => |
78 | var webSocketActionBlock = new ActionBlock<GotifyConnectionData>(async gotifyConnectionData => |
|
86 | { |
79 | { |
|
87 | if (data.Payload == null || data.Payload.Length == 0) |
80 | try |
|
- | 81 | { |
||
88 | { |
82 | using var memoryStream = new MemoryStream(gotifyConnectionData.Payload); |
|
89 | return null; |
83 | using var streamReader = new StreamReader(memoryStream); |
|
Line 90... | Line 84... | |||
90 | } |
84 | using var jsonTextReader = new JsonTextReader(streamReader); |
|
Line 91... | Line -... | |||
91 | |
- | ||
92 | GotifyMessage gotifyNotification; |
- | ||
93 | |
85 | |
|
Line 94... | Line 86... | |||
94 | try |
86 | var gotifyMessage = _jsonSerializer.Deserialize<GotifyMessage>(jsonTextReader) ?? throw new ArgumentNullException(); |
|
Line 95... | Line 87... | |||
95 | { |
87 | |
|
96 | var message = Encoding.UTF8.GetString(data.Payload, 0, data.Payload.Length); |
88 | gotifyMessage.Server = gotifyConnectionData.Server; |
|
- | 89 | |
||
- | 90 | using var imageStream = await RetrieveGotifyApplicationImage(gotifyMessage.AppId, _cancellationToken); |
||
97 | |
91 | |
|
98 | gotifyNotification = JsonConvert.DeserializeObject<GotifyMessage>(message); |
92 | if (imageStream == null || imageStream.Length == 0) |
|
Line -... | Line 93... | |||
- | 93 | { |
||
- | 94 | Log.Warning("Could not find any application image for notification."); |
||
99 | |
95 | |
|
- | 96 | return; |
||
- | 97 | } |
||
- | 98 | |
||
100 | if (gotifyNotification == null) |
99 | var image = new Bitmap(imageStream); |
|
101 | { |
100 | |
|
102 | throw new ArgumentNullException(); |
101 | GotifyMessage?.Invoke(this, |
|
103 | } |
102 | new GotifyMessageEventArgs(gotifyMessage, image)); |
|
104 | |
- | ||
105 | gotifyNotification.Server = data.Server; |
- | ||
106 | } |
103 | |
|
107 | catch (Exception exception) |
- | ||
108 | { |
104 | Log.Debug($"Notification message received: {gotifyMessage.Message}"); |
|
109 | Log.Warning(exception, "Could not deserialize notification."); |
- | ||
110 | |
- | ||
111 | return null; |
- | ||
112 | } |
- | ||
113 | |
- | ||
114 | return gotifyNotification; |
- | ||
115 | |
- | ||
116 | }, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken }); |
105 | } |
|
117 | |
106 | catch (JsonSerializationException exception) |
|
118 | var webSocketActionBlock = new ActionBlock<GotifyMessage>(async message => |
- | ||
119 | { |
- | ||
120 | using var imageStream = await RetrieveGotifyApplicationImage(message.AppId, _cancellationToken); |
107 | { |
|
Line 121... | Line -... | |||
121 | if (imageStream == null || imageStream.Length == 0) |
- | ||
122 | { |
- | ||
123 | Log.Warning("Could not find any application image for notification."); |
- | ||
124 | |
- | ||
125 | return; |
- | ||
126 | } |
- | ||
127 | |
- | ||
128 | var image = new Bitmap(imageStream); |
108 | Log.Warning(exception, "Could not deserialize notification message."); |
|
Line 129... | Line 109... | |||
129 | |
109 | } |
|
130 | GotifyNotification?.Invoke(this, |
110 | catch (Exception exception) |
|
131 | new GotifyNotificationEventArgs(message, image)); |
- | ||
132 | |
- | ||
133 | Log.Debug($"Notification message received: {message.Message}"); |
- | ||
134 | |
- | ||
135 | }, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken }); |
111 | { |
|
Line 136... | Line 112... | |||
136 | |
112 | Log.Warning(exception, "Generic failure."); |
|
137 | _tplWebSocketsBufferBlockTransformLink = _webSocketMessageBufferBlock.LinkTo(webSocketTransformBlock, |
113 | } |
|
138 | new DataflowLinkOptions { PropagateCompletion = true }); |
114 | |
|
Line 206... | Line 182... | |||
206 | { |
182 | { |
|
207 | _tplWebSocketsBufferBlockTransformLink.Dispose(); |
183 | _tplWebSocketsBufferBlockTransformLink.Dispose(); |
|
208 | _tplWebSocketsBufferBlockTransformLink = null; |
184 | _tplWebSocketsBufferBlockTransformLink = null; |
|
209 | } |
185 | } |
|
Line 210... | Line -... | |||
210 | |
- | ||
211 | if (_tplWebSocketsTransformActionLink != null) |
- | ||
212 | { |
- | ||
213 | _tplWebSocketsTransformActionLink.Dispose(); |
- | ||
214 | _tplWebSocketsTransformActionLink = null; |
- | ||
215 | } |
- | ||
216 | |
- | ||
217 | if (_tplWebSocketsTransformActionNullLink != null) |
- | ||
218 | { |
- | ||
219 | _tplWebSocketsTransformActionNullLink.Dispose(); |
- | ||
220 | _tplWebSocketsTransformActionNullLink = null; |
- | ||
221 | } |
- | ||
222 | |
186 | |
|
223 | if (_tplRetrievePastMessagesLink != null) |
187 | if (_tplRetrievePastMessagesLink != null) |
|
224 | { |
188 | { |
|
225 | _tplRetrievePastMessagesLink.Dispose(); |
189 | _tplRetrievePastMessagesLink.Dispose(); |
|
226 | _tplRetrievePastMessagesLink = null; |
190 | _tplRetrievePastMessagesLink = null; |
|
Line 307... | Line 271... | |||
307 | _webSocketsServerResponseScheduledContinuation.Schedule(TimeSpan.FromMinutes(1), OnUnresponsiveServer, _cancellationToken); |
271 | _webSocketsServerResponseScheduledContinuation.Schedule(TimeSpan.FromMinutes(1), OnUnresponsiveServer, _cancellationToken); |
|
308 | } |
272 | } |
|
Line 309... | Line 273... | |||
309 | |
273 | |
|
310 | private void OnUnresponsiveServer() |
274 | private void OnUnresponsiveServer() |
|
311 | { |
275 | { |
|
312 | Log.Warning($"Server {_server} has not responded in a long while..."); |
276 | Log.Warning($"Server {_server.Name} has not responded in a long while..."); |
|
Line 313... | Line 277... | |||
313 | } |
277 | } |
|
314 | |
278 | |
|
315 | private async void WebSocketSharp_OnError(object sender, ErrorEventArgs e) |
279 | private async void WebSocketSharp_OnError(object sender, ErrorEventArgs e) |
|
Line 327... | Line 291... | |||
327 | |
291 | |
|
328 | private async void WebSocketSharp_OnMessage(object sender, MessageEventArgs e) |
292 | private async void WebSocketSharp_OnMessage(object sender, MessageEventArgs e) |
|
329 | { |
293 | { |
|
330 | if (e.IsPing) |
294 | if (e.IsPing) |
|
331 | { |
295 | { |
|
Line 332... | Line 296... | |||
332 | Log.Information($"Server {_server} sent PING message"); |
296 | Log.Information($"Server {_server.Name} sent PING message"); |
|
333 | |
297 | |
|
334 | _webSocketsServerResponseScheduledContinuation.Schedule(TimeSpan.FromMinutes(1), OnUnresponsiveServer, _cancellationToken); |
298 | _webSocketsServerResponseScheduledContinuation.Schedule(TimeSpan.FromMinutes(1), OnUnresponsiveServer, _cancellationToken); |
|
Line 380... | Line 344... | |||
380 | Log.Error($"Could not get application message Uri {gotifyConnectionApplication.Application.Id}."); |
344 | Log.Error($"Could not get application message Uri {gotifyConnectionApplication.Application.Id}."); |
|
Line 381... | Line 345... | |||
381 | |
345 | |
|
382 | return; |
346 | return; |
|
Line 383... | Line -... | |||
383 | } |
- | ||
384 | |
347 | } |
|
385 | GotifyMessageQuery gotifyMessageQuery; |
348 | |
|
386 | try |
349 | try |
|
387 | { |
350 | { |
|
388 | using var messageStream = await _httpClient.GetStreamAsync(combinedUri); |
351 | using var messageStream = await _httpClient.GetStreamAsync(combinedUri); |
|
Line 389... | Line 352... | |||
389 | using var streamReader = new StreamReader(messageStream); |
352 | using var streamReader = new StreamReader(messageStream); |
|
- | 353 | using var jsonTextReader = new JsonTextReader(streamReader); |
||
- | 354 | |
||
- | 355 | var gotifyMessageQuery = _jsonSerializer.Deserialize<GotifyMessageQuery>(jsonTextReader) ?? |
||
- | 356 | throw new ArgumentNullException(); |
||
- | 357 | |
||
- | 358 | if (gotifyMessageQuery.Messages == null) |
||
- | 359 | { |
||
- | 360 | Log.Warning("Invalid application messages deserialized deserialized."); |
||
- | 361 | |
||
- | 362 | return; |
||
- | 363 | } |
||
- | 364 | |
||
- | 365 | foreach (var message in gotifyMessageQuery.Messages) |
||
- | 366 | { |
||
- | 367 | if (message.Date < |
||
- | 368 | DateTime.Now - TimeSpan.FromHours(_configuration.RetrievePastNotificationHours)) |
||
- | 369 | { |
||
- | 370 | continue; |
||
- | 371 | } |
||
- | 372 | |
||
- | 373 | using var imageStream = await RetrieveGotifyApplicationImage(message.AppId, _cancellationToken); |
||
- | 374 | if (imageStream == null || imageStream.Length == 0) |
||
- | 375 | { |
||
- | 376 | Log.Warning("Could not find any application image for notification."); |
||
- | 377 | |
||
- | 378 | continue; |
||
- | 379 | } |
||
- | 380 | |
||
- | 381 | var image = new Bitmap(imageStream); |
||
- | 382 | message.Server = gotifyConnectionApplication.Server; |
||
- | 383 | |
||
390 | using var jsonTextReader = new JsonTextReader(streamReader); |
384 | GotifyMessage?.Invoke(this, |
|
391 | |
385 | new GotifyMessageEventArgs(message, image)); |
|
392 | gotifyMessageQuery = _jsonSerializer.Deserialize<GotifyMessageQuery>(jsonTextReader); |
386 | } |
|
393 | } |
387 | } |
|
394 | catch (HttpRequestException exception) |
- | ||
395 | { |
- | ||
396 | Log.Warning(exception, $"Could not get application {gotifyConnectionApplication.Application.Id}."); |
388 | catch (HttpRequestException exception) |
|
397 | |
389 | { |
|
398 | return; |
390 | Log.Warning(exception, $"Could not get application {gotifyConnectionApplication.Application.Id}."); |
|
399 | } |
- | ||
400 | catch (JsonSerializationException exception) |
- | ||
401 | { |
391 | } |
|
402 | Log.Warning(exception,$"Could not deserialize the message response for application {gotifyConnectionApplication.Application.Id}."); |
- | ||
403 | |
- | ||
404 | return; |
- | ||
405 | } |
- | ||
406 | |
392 | catch (JsonSerializationException exception) |
|
407 | if (gotifyMessageQuery == null || gotifyMessageQuery.Messages == null) |
- | ||
408 | { |
- | ||
409 | Log.Warning("Invalid application messages deserialized deserialized."); |
393 | { |
|
410 | |
- | ||
411 | return; |
394 | Log.Warning(exception, |
|
412 | } |
395 | $"Could not deserialize the message response for application {gotifyConnectionApplication.Application.Id}."); |
|
413 | |
- | ||
414 | foreach (var message in gotifyMessageQuery.Messages) |
- | ||
415 | { |
- | ||
416 | if (message.Date < DateTime.Now - TimeSpan.FromHours(_configuration.RetrievePastNotificationHours)) |
- | ||
417 | { |
- | ||
418 | continue; |
- | ||
419 | } |
- | ||
420 | |
- | ||
421 | using var imageStream = await RetrieveGotifyApplicationImage(message.AppId, _cancellationToken); |
- | ||
422 | if (imageStream == null || imageStream.Length == 0) |
- | ||
423 | { |
- | ||
424 | Log.Warning("Could not find any application image for notification."); |
- | ||
425 | |
- | ||
426 | continue; |
396 | } |
|
427 | } |
- | ||
428 | |
- | ||
429 | var image = new Bitmap(imageStream); |
- | ||
430 | message.Server = gotifyConnectionApplication.Server; |
- | ||
431 | |
397 | catch (Exception exception) |
|
432 | GotifyNotification?.Invoke(this, |
- | ||
433 | new GotifyNotificationEventArgs(message, image)); |
398 | { |
|
Line 434... | Line 399... | |||
434 | } |
399 | Log.Warning(exception, "Generic failure."); |
|
435 | |
400 | } |
|
Line 446... | Line 411... | |||
446 | } |
411 | } |
|
Line 447... | Line 412... | |||
447 | |
412 | |
|
448 | await Task.WhenAll(tasks); |
413 | await Task.WhenAll(tasks); |
|
449 | gotifyApplicationBufferBlock.Complete(); |
414 | gotifyApplicationBufferBlock.Complete(); |
|
450 | await gotifyApplicationActionBlock.Completion; |
- | ||
451 | |
415 | await gotifyApplicationActionBlock.Completion; |
|
Line 452... | Line 416... | |||
452 | } |
416 | } |
|
453 | |
417 | |
|
454 | private async Task HeartBeat(CancellationToken cancellationToken) |
418 | private async Task HeartBeat(CancellationToken cancellationToken) |
|
Line 460... | Line 424... | |||
460 | await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken); |
424 | await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken); |
|
Line 461... | Line 425... | |||
461 | |
425 | |
|
462 | _webSocketsClientPingStopWatch.Restart(); |
426 | _webSocketsClientPingStopWatch.Restart(); |
|
463 | if (!_webSocketSharp.Ping()) |
427 | if (!_webSocketSharp.Ping()) |
|
464 | { |
428 | { |
|
465 | Log.Warning($"Server {_server} did not respond to PING message."); |
429 | Log.Warning($"Server {_server.Name} did not respond to PING message."); |
|
466 | continue; |
430 | continue; |
|
Line 467... | Line 431... | |||
467 | } |
431 | } |
|
Line 468... | Line 432... | |||
468 | |
432 | |
|
Line 469... | Line 433... | |||
469 | var delta = _webSocketsClientPingStopWatch.ElapsedMilliseconds; |
433 | var delta = _webSocketsClientPingStopWatch.ElapsedMilliseconds; |
|
470 | |
434 | |
|
471 | Log.Information($"PING response latency for {_server} is {delta}ms"); |
435 | Log.Information($"PING response latency for {_server.Name} is {delta}ms"); |
|
472 | |
436 | |
|
473 | _webSocketsServerResponseScheduledContinuation.Schedule(TimeSpan.FromMinutes(1), OnUnresponsiveServer, _cancellationToken); |
437 | _webSocketsServerResponseScheduledContinuation.Schedule(TimeSpan.FromMinutes(1), OnUnresponsiveServer, _cancellationToken); |
|
474 | } while (!cancellationToken.IsCancellationRequested); |
438 | } while (!cancellationToken.IsCancellationRequested); |
|
475 | } |
439 | } |
|
476 | catch (Exception exception) when (exception is OperationCanceledException || |
440 | catch (Exception exception) when (exception is OperationCanceledException || |
|
477 | exception is ObjectDisposedException) |
441 | exception is ObjectDisposedException) |
|
478 | { |
442 | { |
|
479 | } |
443 | } |
|
480 | catch (Exception exception) |
444 | catch (Exception exception) |
|
Line 481... | Line 445... | |||
481 | { |
445 | { |
|
482 | Log.Warning(exception, $"Heartbeat for server {_server} has failed due to {exception.Message}"); |
446 | Log.Warning(exception, $"Heartbeat for server {_server.Name} has failed."); |
|
Line 535... | Line 499... | |||
535 | continue; |
499 | continue; |
|
536 | } |
500 | } |
|
Line 537... | Line 501... | |||
537 | |
501 | |
|
538 | try |
502 | try |
|
539 | { |
503 | { |
|
Line 540... | Line 504... | |||
540 | var imageResponse = await _httpClient.GetAsync(applicationImageUri, cancellationToken); |
504 | var imageResponse = await _httpClient.GetStreamAsync(applicationImageUri); |
|
Line 541... | Line 505... | |||
541 | |
505 | |
|
Line 542... | Line 506... | |||
542 | var memoryStream = new MemoryStream(); |
506 | var memoryStream = new MemoryStream(); |
|
543 | |
507 | |
|
544 | await imageResponse.Content.CopyToAsync(memoryStream); |
508 | await imageResponse.CopyToAsync(memoryStream); |
|
545 | |
509 | |
|
546 | return memoryStream; |
510 | return memoryStream; |
|
547 | } |
511 | } |
|
548 | catch (Exception exception) |
512 | catch (Exception exception) |
|
Line 549... | Line 513... | |||
549 | { |
513 | { |
|
550 | Log.Error($"Could not retrieve application image: {exception.Message}"); |
514 | Log.Error(exception,"Could not retrieve application image."); |