Winify – Diff between revs 64 and 66

Subversion Repositories:
Rev:
Show entire fileIgnore whitespace
Rev 64 Rev 66
Line 1... Line 1...
1 using System; 1 using System;
-   2 using System.Collections.Concurrent;
2 using System.Collections.Generic; 3 using System.Collections.Generic;
3 using System.Diagnostics; 4 using System.Diagnostics;
4 using System.Drawing; 5 using System.Drawing;
5 using System.Globalization; 6 using System.Globalization;
6 using System.IO; 7 using System.IO;
Line 16... Line 17...
16 using System.Threading; 17 using System.Threading;
17 using System.Threading.Tasks; 18 using System.Threading.Tasks;
18 using System.Threading.Tasks.Dataflow; 19 using System.Threading.Tasks.Dataflow;
19 using System.Windows.Forms; 20 using System.Windows.Forms;
20 using Newtonsoft.Json; 21 using Newtonsoft.Json;
-   22 using Org.BouncyCastle.Asn1.Pkcs;
21 using Serilog; 23 using Serilog;
22 using Servers; 24 using Servers;
23 using WebSocketSharp; 25 using WebSocketSharp;
24 using WebSocketSharp.Net; 26 using WebSocketSharp.Net;
25 using Winify.Utilities; 27 using Winify.Utilities;
Line 42... Line 44...
42   44  
Line 43... Line 45...
43 private CancellationToken _cancellationToken; 45 private CancellationToken _cancellationToken;
Line 44... Line 46...
44   46  
Line 45... Line 47...
45 private CancellationTokenSource _cancellationTokenSource; 47 private CancellationTokenSource _cancellationTokenSource;
Line 46... Line 48...
46   48  
Line 47... Line 49...
47 private Task _runTask; 49 private Task _heartBeatTask;
48   50  
49 private HttpClient _httpClient; 51 private HttpClient _httpClient;
50   -  
51 private readonly Uri _webSocketsUri; 52  
52   53 private readonly Uri _webSocketsUri;
53 private readonly Uri _httpUri; 54  
54 private WebSocket _webSocketSharp; 55 private readonly Uri _httpUri;
55 private readonly Configuration.Configuration _configuration; 56 private WebSocket _webSocketSharp;
56 private Task _initTask; 57 private readonly Configuration.Configuration _configuration;
57 private IDisposable _tplRetrievePastMessagesLink; 58 private IDisposable _tplRetrievePastMessagesLink;
-   59 private IDisposable _tplWebSocketsBufferBlockTransformLink;
-   60 private IDisposable _tplWebSocketsTransformActionLink;
-   61 private IDisposable _tplWebSocketsTransformActionNullLink;
Line 58... Line -...
58 private IDisposable _tplWebSocketsBufferBlockTransformLink; -  
59 private IDisposable _tplWebSocketsTransformActionLink; 62 private readonly BufferBlock<GotifyConnectionData> _webSocketMessageBufferBlock;
Line 60... Line 63...
60 private IDisposable _tplWebSocketsTransformActionNullLink; 63 private readonly Stopwatch _webSocketsClientPingStopWatch;
Line 61... Line 64...
61 private readonly BufferBlock<byte[]> _webSocketMessageBufferBlock; 64 private readonly ScheduledContinuation _webSocketsServerResponseScheduledContinuation;
62 private readonly Stopwatch _webSocketsClientPingStopWatch; 65
63 private readonly ScheduledContinuation _webSocketsServerResponseScheduledContinuation; 66 private Task _retrievePastMessagesTask;
64   67 private static JsonSerializer _jsonSerializer;
65 private readonly MemoryCache _applicationImageCache; 68  
Line 66... Line 69...
66 #endregion 69 #endregion
-   70  
-   71 #region Constructors, Destructors and Finalizers
-   72  
-   73 private GotifyConnection()
-   74 {
67   75 _jsonSerializer = new JsonSerializer();
68 #region Constructors, Destructors and Finalizers 76 _webSocketsServerResponseScheduledContinuation = new ScheduledContinuation();
69   77 _webSocketsClientPingStopWatch = new Stopwatch();
70 private GotifyConnection() 78  
71 { 79 _webSocketMessageBufferBlock = new BufferBlock<GotifyConnectionData>(
72 _applicationImageCache = new MemoryCache("GotifyApplicationImageCache"); 80 new DataflowBlockOptions
Line 73... Line -...
73 _webSocketsServerResponseScheduledContinuation = new ScheduledContinuation(); -  
74 _webSocketsClientPingStopWatch = new Stopwatch(); -  
75   81 {
Line 76... Line 82...
76 _webSocketMessageBufferBlock = new BufferBlock<byte[]>(new DataflowBlockOptions { CancellationToken = _cancellationToken }); 82 CancellationToken = _cancellationToken
77 var webSocketTransformBlock = new TransformBlock<byte[], GotifyMessage>(bytes => 83 });
-   84  
-   85 var webSocketTransformBlock = new TransformBlock<GotifyConnectionData, GotifyMessage>(data =>
78 { 86 {
79 if (bytes.Length == 0) -  
80 { -  
81 return null; -  
82 } -  
Line -... Line 87...
-   87 if (data.Payload == null || data.Payload.Length == 0)
83   88 {
-   89 return null;
84 var message = Encoding.UTF8.GetString(bytes, 0, bytes.Length); 90 }
Line 85... Line 91...
85   91  
-   92 GotifyMessage gotifyNotification;
-   93  
86 GotifyMessage gotifyNotification; 94 try
87   95 {
Line 88... Line 96...
88 try 96 var message = Encoding.UTF8.GetString(data.Payload, 0, data.Payload.Length);
89 { 97  
Line 90... Line 98...
90 gotifyNotification = JsonConvert.DeserializeObject<GotifyMessage>(message); 98 gotifyNotification = JsonConvert.DeserializeObject<GotifyMessage>(message);
Line 91... Line 99...
91 } 99  
Line 92... Line 100...
92 catch (JsonSerializationException exception) 100 if (gotifyNotification == null)
93 { 101 {
94 Log.Warning($"Could not deserialize notification: {exception.Message}"); -  
95   -  
96 return null; 102 throw new ArgumentNullException();
97 } 103 }
98   104  
99 if (gotifyNotification == null) -  
100 { -  
101 Log.Warning($"Could not deserialize gotify notification: {message}"); -  
102   -  
103 return null; -  
104 } 105 gotifyNotification.Server = data.Server;
Line 105... Line 106...
105   106 }
106 return gotifyNotification; 107 catch (Exception exception)
Line 107... Line -...
107   -  
108 }, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken }); -  
109   -  
110 var webSocketActionBlock = new ActionBlock<GotifyMessage>(async message => -  
111 { -  
112 message.Server = _server; -  
113   -  
114 var cachedImage = _applicationImageCache.Get($"{message.AppId}"); -  
115 if (cachedImage is Stream cachedImageStream) -  
116 { -  
117 using var cachedImageMemoryStream = new MemoryStream(); -  
118 await cachedImageStream.CopyToAsync(cachedImageMemoryStream); 108 {
Line 119... Line -...
119   -  
120 var cachedApplicationImage = new Bitmap(cachedImageMemoryStream); -  
121 GotifyNotification?.Invoke(this, -  
122 new GotifyNotificationEventArgs(message, cachedApplicationImage)); -  
123   -  
124 return; -  
125 } -  
126   -  
127 using var imageStream = await RetrieveGotifyApplicationImage(message.AppId, _cancellationToken); 109 Log.Warning(exception, "Could not deserialize notification.");
128 if (imageStream == null || imageStream.Length == 0) 110  
Line 129... Line 111...
129 { 111 return null;
Line 130... Line 112...
130 Log.Warning("Could not find any application image for notification"); 112 }
Line 202... Line 184...
202 case "HTTPS": 184 case "HTTPS":
203 webSocketsUriBuilder.Scheme = "wss"; 185 webSocketsUriBuilder.Scheme = "wss";
204 break; 186 break;
205 } 187 }
Line 206... Line -...
206   -  
207 try -  
208 { 188  
209 webSocketsUriBuilder.Path = Path.Combine(webSocketsUriBuilder.Path, "stream"); -  
210 } -  
211 catch (ArgumentException exception) 189 if (!Uri.TryCreate(webSocketsUriBuilder.Uri, "stream", out var combinedUri))
212 { -  
213 Log.Error( 190 {
214 $"No WebSockets URL could be built from the provided URL {_server.Url} due to {exception.Message}"); 191 Log.Error($"No WebSockets URL could be built from the provided URL {_server.Url}.");
Line 215... Line 192...
215 } 192 }
216   193  
Line 217... Line 194...
217 _webSocketsUri = webSocketsUriBuilder.Uri; 194 _webSocketsUri = combinedUri;
218 } 195 }
219   196  
Line 275... Line 252...
275 } 252 }
Line 276... Line 253...
276   253  
277 _cancellationTokenSource = new CancellationTokenSource(); 254 _cancellationTokenSource = new CancellationTokenSource();
Line 278... Line 255...
278 _cancellationToken = _cancellationTokenSource.Token; 255 _cancellationToken = _cancellationTokenSource.Token;
279   -  
280 Connect(); -  
281   256  
282 if (_configuration.RetrievePastNotificationHours != 0) 257 if (_webSocketSharp != null)
283 { 258 {
Line 284... Line -...
284 _initTask = RetrievePastMessages(_cancellationToken); -  
285 } -  
286   -  
287 _runTask = HeartBeat(_cancellationToken); -  
288 } -  
289   259 return;
290 private void Connect() 260 }
291 { 261  
292 _webSocketSharp = new WebSocket(_webSocketsUri.AbsoluteUri); 262 _webSocketSharp = new WebSocket(_webSocketsUri.AbsoluteUri);
293 _webSocketSharp.EmitOnPing = true; 263 _webSocketSharp.EmitOnPing = true;
Line 313... Line 283...
313 _webSocketSharp.OnMessage += WebSocketSharp_OnMessage; 283 _webSocketSharp.OnMessage += WebSocketSharp_OnMessage;
314 _webSocketSharp.OnError += WebSocketSharp_OnError; 284 _webSocketSharp.OnError += WebSocketSharp_OnError;
315 _webSocketSharp.OnOpen += WebSocketSharp_OnOpen; 285 _webSocketSharp.OnOpen += WebSocketSharp_OnOpen;
316 _webSocketSharp.OnClose += WebSocketSharp_OnClose; 286 _webSocketSharp.OnClose += WebSocketSharp_OnClose;
Line 317... Line 287...
317   287  
-   288 _webSocketSharp.Connect();
-   289 _heartBeatTask = HeartBeat(_cancellationToken);
-   290  
-   291 if (_configuration.RetrievePastNotificationHours != 0)
-   292 {
-   293 _retrievePastMessagesTask = RetrievePastMessages(_cancellationToken);
318 _webSocketSharp.ConnectAsync(); 294 }
Line 319... Line 295...
319 } 295 }
320   296  
321 private void WebSocketSharp_OnClose(object sender, CloseEventArgs e) 297 private void WebSocketSharp_OnClose(object sender, CloseEventArgs e)
Line 340... Line 316...
340 { 316 {
341 Log.Error( 317 Log.Error(
342 $"Connection to WebSockets server {_webSocketsUri.AbsoluteUri} terminated unexpectedly with message {e.Message}", 318 $"Connection to WebSockets server {_webSocketsUri.AbsoluteUri} terminated unexpectedly with message {e.Message}",
343 e.Exception); 319 e.Exception);
Line 344... Line -...
344   -  
345 if (_cancellationToken.IsCancellationRequested) -  
346 { -  
347 Stop(); -  
348 return; -  
349 } -  
350   320  
Line 351... Line 321...
351 await Task.Delay(TimeSpan.FromSeconds(1), _cancellationToken); 321 await Task.Delay(TimeSpan.FromSeconds(1), _cancellationToken);
Line 352... Line 322...
352   322  
353 Log.Information($"Reconnecting to websocket server {_webSocketsUri.AbsoluteUri}"); 323 Log.Information($"Reconnecting to websocket server {_webSocketsUri.AbsoluteUri}");
Line 354... Line 324...
354   324  
355 Connect(); 325 await Stop().ContinueWith(task => Start(), CancellationToken.None);
356 } 326 }
357   327  
358 private async void WebSocketSharp_OnMessage(object sender, MessageEventArgs e) 328 private async void WebSocketSharp_OnMessage(object sender, MessageEventArgs e)
Line 359... Line 329...
359 { 329 {
360 if (e.IsPing) -  
361 { 330 if (e.IsPing)
362 Log.Information($"Server {_server} sent PING message"); 331 {
Line 363... Line 332...
363   332 Log.Information($"Server {_server} sent PING message");
364 _webSocketsServerResponseScheduledContinuation.Schedule(TimeSpan.FromMinutes(1), OnUnresponsiveServer, _cancellationToken); 333  
Line 365... Line 334...
365   334 _webSocketsServerResponseScheduledContinuation.Schedule(TimeSpan.FromMinutes(1), OnUnresponsiveServer, _cancellationToken);
366 return; 335 return;
-   336 }
367 } 337  
-   338 await _webSocketMessageBufferBlock.SendAsync(new GotifyConnectionData(e.RawData, _server), _cancellationToken);
-   339 }
-   340  
Line 368... Line 341...
368   341 public async Task Stop()
-   342 {
-   343  
-   344 if (_webSocketSharp == null || _cancellationTokenSource == null)
-   345 {
-   346 return;
-   347 }
-   348  
-   349 _cancellationTokenSource.Cancel();
-   350  
-   351 await _heartBeatTask;
-   352 await _retrievePastMessagesTask;
-   353  
-   354 if (_webSocketSharp != null)
-   355 {
-   356 _webSocketSharp.OnMessage -= WebSocketSharp_OnMessage;
-   357 _webSocketSharp.OnError -= WebSocketSharp_OnError;
-   358 _webSocketSharp.OnOpen -= WebSocketSharp_OnOpen;
369 await _webSocketMessageBufferBlock.SendAsync(e.RawData, _cancellationToken); 359 _webSocketSharp.OnClose -= WebSocketSharp_OnClose;
Line 370... Line 360...
370 } 360  
Line 371... Line 361...
371   361 _webSocketSharp.Close();
Line 372... Line 362...
372 public void Stop() 362 _webSocketSharp = null;
373 { 363 }
374 if (_cancellationTokenSource == null) return; -  
375   -  
376 _cancellationTokenSource.Cancel(); 364  
377 } 365 _cancellationTokenSource.Dispose();
378   366 _cancellationTokenSource = null;
379 #endregion -  
380   -  
381 #region Private Methods 367 }
382   -  
383 private async Task RetrievePastMessages(CancellationToken cancellationToken) -  
384 { -  
385 var messageUriBuilder = new UriBuilder(_httpUri); 368  
386   369 #endregion
Line 387... Line 370...
387 var gotifyApplicationBufferBlock = new BufferBlock<GotifyApplication>(new DataflowBlockOptions { CancellationToken = cancellationToken }); 370  
388 var gotifyApplicationActionBlock = new ActionBlock<GotifyApplication>(async application => 371 #region Private Methods
Line 389... Line 372...
389 { 372  
390 try 373 private async Task RetrievePastMessages(CancellationToken cancellationToken)
391 { 374 {
392 messageUriBuilder.Path = Path.Combine(messageUriBuilder.Path, "application", $"{application.Id}", 375 var gotifyApplicationBufferBlock = new BufferBlock<GotifyConnectionApplication>(new DataflowBlockOptions { CancellationToken = cancellationToken });
-   376 var gotifyApplicationActionBlock = new ActionBlock<GotifyConnectionApplication>(async gotifyConnectionApplication =>
-   377 {
-   378 if (!Uri.TryCreate(_httpUri, $"application/{gotifyConnectionApplication.Application.Id}/message", out var combinedUri))
-   379 {
393 "message"); 380 Log.Error($"Could not get application message Uri {gotifyConnectionApplication.Application.Id}.");
394 } 381  
395 catch (ArgumentException exception) 382 return;
396 { 383 }
Line 397... Line 384...
397 Log.Error($"No application URL could be built for {_server.Url} due to {exception.Message}"); 384  
398   385 GotifyMessageQuery gotifyMessageQuery;
399 return; -  
400 } -  
401   -  
402 HttpResponseMessage messagesResponse; -  
403 try -  
404 { -  
405 messagesResponse = await _httpClient.GetAsync(messageUriBuilder.Uri, cancellationToken); -  
406 } -  
407 catch (Exception exception) -  
408 { -  
409 Log.Error($"Could not get application {application.Id} due to {exception.Message}"); 386 try
410   387 {
411 return; 388 using var messageStream = await _httpClient.GetStreamAsync(combinedUri);
Line 412... Line 389...
412 } 389 using var streamReader = new StreamReader(messageStream);
413   390 using var jsonTextReader = new JsonTextReader(streamReader);
Line 414... Line 391...
414   391  
415 var messages = await messagesResponse.Content.ReadAsStringAsync(); 392 gotifyMessageQuery = _jsonSerializer.Deserialize<GotifyMessageQuery>(jsonTextReader);
416   -  
417 GotifyMessageQuery gotifyMessageQuery; -  
418 try 393 }
419 { -  
420 gotifyMessageQuery = -  
421 JsonConvert.DeserializeObject<GotifyMessageQuery>(messages); -  
422 } -  
Line 423... Line 394...
423 catch (JsonSerializationException exception) 394 catch (HttpRequestException exception)
424 { 395 {
425 Log.Warning($"Could not deserialize the message response: {exception.Message}"); -  
Line -... Line 396...
-   396 Log.Warning(exception, $"Could not get application {gotifyConnectionApplication.Application.Id}.");
-   397  
-   398 return;
-   399 }
426   400 catch (JsonSerializationException exception)
427 return; 401 {
428 } 402 Log.Warning(exception,$"Could not deserialize the message response for application {gotifyConnectionApplication.Application.Id}.");
429   403  
430 foreach (var message in gotifyMessageQuery.Messages.Where(message => message.Date >= DateTime.Now - TimeSpan.FromHours(_configuration.RetrievePastNotificationHours))) -  
431 { 404 return;
432 message.Server = _server; 405 }
433   406  
-   407 if (gotifyMessageQuery == null || gotifyMessageQuery.Messages == null)
434 var cachedImage = _applicationImageCache.Get($"{message.AppId}"); 408 {
435 if (cachedImage is Stream cachedImageStream) 409 Log.Warning("Invalid application messages deserialized deserialized.");
Line 436... Line -...
436 { -  
437 using var cachedImageMemoryStream = new MemoryStream(); -  
438 await cachedImageStream.CopyToAsync(cachedImageMemoryStream); -  
439   -  
440 var cachedApplicationImage = new Bitmap(cachedImageMemoryStream); 410  
441 GotifyNotification?.Invoke(this, -  
442 new GotifyNotificationEventArgs(message, cachedApplicationImage)); -  
443   -  
444 return; -  
445 } -  
446   -  
447 using var imageStream = await RetrieveGotifyApplicationImage(message.AppId, _cancellationToken); -  
448   411 return;
Line 449... Line 412...
449 if (imageStream == null || imageStream.Length == 0) 412 }
450 { 413  
451 Log.Warning("Could not find any application image for notification"); 414 foreach (var message in gotifyMessageQuery.Messages)
Line 452... Line 415...
452 continue; 415 {
Line 453... Line 416...
453 } 416 if (message.Date < DateTime.Now - TimeSpan.FromHours(_configuration.RetrievePastNotificationHours))
454   417 {
Line -... Line 418...
-   418 continue;
455 using var memoryStream = new MemoryStream(); 419 }
456   420
-   421 using var imageStream = await RetrieveGotifyApplicationImage(message.AppId, _cancellationToken);
457 await imageStream.CopyToAsync(memoryStream); 422 if (imageStream == null || imageStream.Length == 0)
458   423 {
Line -... Line 424...
-   424 Log.Warning("Could not find any application image for notification.");
459 var imageBytes = memoryStream.ToArray(); 425  
460   426 continue;
Line 461... Line 427...
461 _applicationImageCache.Add($"{message.AppId}", imageBytes, 427 }
Line 517... Line 483...
517 } 483 }
518 } 484 }
Line 519... Line 485...
519   485  
520 private async IAsyncEnumerable<GotifyApplication> RetrieveGotifyApplications([EnumeratorCancellation] CancellationToken cancellationToken) 486 private async IAsyncEnumerable<GotifyApplication> RetrieveGotifyApplications([EnumeratorCancellation] CancellationToken cancellationToken)
521 { 487 {
522 var applicationsUriBuilder = new UriBuilder(_httpUri); -  
523 try -  
524 { -  
525 applicationsUriBuilder.Path = Path.Combine(applicationsUriBuilder.Path, "application"); -  
526 } -  
527 catch (ArgumentException exception) 488 if (!Uri.TryCreate(_httpUri, "application", out var combinedUri))
528 { 489 {
Line 529... Line 490...
529 Log.Error($"No application URL could be built for {_server.Url} due to {exception}"); 490 Log.Error($"No application URL could be built for {_server.Url}.");
530   491  
Line 531... Line 492...
531 yield break; 492 yield break;
532 } -  
533   493 }
534 HttpResponseMessage applicationsResponse; 494  
535   495 GotifyApplication[] gotifyApplications;
536 try -  
537 { 496 try
538 applicationsResponse = await _httpClient.GetAsync(applicationsUriBuilder.Uri, cancellationToken); -  
539 } 497 {
Line 540... Line 498...
540 catch (Exception exception) 498 using var messageStream = await _httpClient.GetStreamAsync(combinedUri);
541 { -  
Line 542... Line -...
542 Log.Error($"Could not retrieve applications: {exception.Message}"); -  
543   -  
544 yield break; 499 using var streamReader = new StreamReader(messageStream);
545 } 500 using var jsonTextReader = new JsonTextReader(streamReader);
546   501  
547 var applications = await applicationsResponse.Content.ReadAsStringAsync(); 502 gotifyApplications = _jsonSerializer.Deserialize<GotifyApplication[]>(jsonTextReader);
548   -  
549 GotifyApplication[] gotifyApplications; 503  
550 try 504 if (gotifyApplications == null)
551 { 505 {
552 gotifyApplications = 506 throw new ArgumentNullException();
Line 553... Line 507...
553 JsonConvert.DeserializeObject<GotifyApplication[]>(applications); 507 }
554 } 508 }
Line 555... Line 509...
555 catch (JsonSerializationException exception) 509 catch (Exception exception)
Line 567... Line 521...
567   521  
568 private async Task<Stream> RetrieveGotifyApplicationImage(int appId, CancellationToken cancellationToken) 522 private async Task<Stream> RetrieveGotifyApplicationImage(int appId, CancellationToken cancellationToken)
569 { 523 {
570 await foreach (var application in RetrieveGotifyApplications(cancellationToken)) 524 await foreach (var application in RetrieveGotifyApplications(cancellationToken))
571 { 525 {
-   526 if (application.Id != appId)
-   527 {
-   528 continue;
Line 572... Line 529...
572 if (application.Id != appId) continue; 529 }
573   530  
574 if (!Uri.TryCreate(Path.Combine($"{_httpUri}", $"{application.Image}"), UriKind.Absolute, out var applicationImageUri)) 531 if (!Uri.TryCreate(Path.Combine($"{_httpUri}", $"{application.Image}"), UriKind.Absolute, out var applicationImageUri))
-   532 {
575 { 533 Log.Warning("Could not build URL path to application icon");
576 Log.Warning("Could not build URL path to application icon"); 534  
Line 577... Line -...
577 continue; -  
578 } -  
579   535 continue;
580 HttpResponseMessage imageResponse; 536 }
581   537  
-   538 try
-   539 {
-   540 var imageResponse = await _httpClient.GetAsync(applicationImageUri, cancellationToken);
-   541  
-   542 var memoryStream = new MemoryStream();
-   543  
582 try 544 await imageResponse.Content.CopyToAsync(memoryStream);
583 { 545  
584 imageResponse = await _httpClient.GetAsync(applicationImageUri, cancellationToken); 546 return memoryStream;
585 } 547 }
586 catch (Exception exception) -  
587 { -  
588 Log.Error($"Could not retrieve application image: {exception.Message}"); 548 catch (Exception exception)
-   549 {
Line 589... Line 550...
589   550 Log.Error($"Could not retrieve application image: {exception.Message}");
-   551 }
Line 590... Line 552...
590 return new MemoryStream(); 552 }
Line -... Line 553...
-   553  
-   554 return new MemoryStream();
-   555 }
591 } 556  
Line -... Line 557...
-   557 #endregion
-   558  
592   559 private class GotifyConnectionApplication
-   560 {
593 var memoryStream = new MemoryStream(); 561 public GotifyApplication Application { get; }
594   -  
595 await imageResponse.Content.CopyToAsync(memoryStream); -  
596   562 public Server Server { get; }
Line -... Line 563...
-   563  
-   564 public GotifyConnectionApplication(Server server, GotifyApplication application)
-   565 {
-   566 Server = server;
-   567 Application = application;
-   568 }
597 memoryStream.Position = 0L; 569 }
-   570  
-   571 private class GotifyConnectionData
-   572 {
-   573 public byte[] Payload { get; }
598   574 public Server Server { get; }
599 return memoryStream; 575  
600 } 576 public GotifyConnectionData(byte[] payload, Server server)