Winify – Blame information for rev 63
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | using System; |
61 | office | 2 | using System.Collections.Generic; |
3 | using System.Diagnostics; |
||
1 | office | 4 | using System.Drawing; |
61 | office | 5 | using System.Globalization; |
1 | office | 6 | using System.IO; |
61 | office | 7 | using System.Linq; |
50 | office | 8 | using System.Net; |
1 | office | 9 | using System.Net.Http; |
10 | using System.Net.Http.Headers; |
||
61 | office | 11 | using System.Runtime.Caching; |
12 | using System.Runtime.CompilerServices; |
||
50 | office | 13 | using System.Security.Authentication; |
46 | office | 14 | using System.Security.Cryptography.X509Certificates; |
1 | office | 15 | using System.Text; |
16 | using System.Threading; |
||
17 | using System.Threading.Tasks; |
||
61 | office | 18 | using System.Threading.Tasks.Dataflow; |
19 | using System.Windows.Forms; |
||
1 | office | 20 | using Newtonsoft.Json; |
18 | office | 21 | using Serilog; |
24 | office | 22 | using Servers; |
44 | office | 23 | using WebSocketSharp; |
50 | office | 24 | using WebSocketSharp.Net; |
61 | office | 25 | using Winify.Utilities; |
44 | office | 26 | using ErrorEventArgs = WebSocketSharp.ErrorEventArgs; |
50 | office | 27 | using NetworkCredential = System.Net.NetworkCredential; |
1 | office | 28 | |
29 | namespace Winify.Gotify |
||
30 | { |
||
31 | public class GotifyConnection : IDisposable |
||
32 | { |
||
33 | #region Public Events & Delegates |
||
34 | |||
35 | public event EventHandler<GotifyNotificationEventArgs> GotifyNotification; |
||
36 | |||
37 | #endregion |
||
38 | |||
39 | #region Private Delegates, Events, Enums, Properties, Indexers and Fields |
||
40 | |||
25 | office | 41 | private readonly Server _server; |
42 | |||
1 | office | 43 | private CancellationToken _cancellationToken; |
44 | |||
45 | private CancellationTokenSource _cancellationTokenSource; |
||
46 | |||
47 | private Task _runTask; |
||
48 | |||
39 | office | 49 | private HttpClient _httpClient; |
50 | |||
51 | private readonly Uri _webSocketsUri; |
||
52 | |||
53 | private readonly Uri _httpUri; |
||
44 | office | 54 | private WebSocket _webSocketSharp; |
59 | office | 55 | private readonly Configuration.Configuration _configuration; |
56 | private Task _initTask; |
||
61 | office | 57 | private IDisposable _tplRetrievePastMessagesLink; |
58 | private IDisposable _tplWebSocketsBufferBlockTransformLink; |
||
59 | private IDisposable _tplWebSocketsTransformActionLink; |
||
60 | private IDisposable _tplWebSocketsTransformActionNullLink; |
||
61 | private readonly BufferBlock<byte[]> _webSocketMessageBufferBlock; |
||
62 | private readonly Stopwatch _webSocketsClientPingStopWatch; |
||
63 | private readonly ScheduledContinuation _webSocketsServerResponseScheduledContinuation; |
||
39 | office | 64 | |
61 | office | 65 | private readonly MemoryCache _applicationImageCache; |
25 | office | 66 | #endregion |
1 | office | 67 | |
25 | office | 68 | #region Constructors, Destructors and Finalizers |
24 | office | 69 | |
44 | office | 70 | private GotifyConnection() |
24 | office | 71 | { |
61 | office | 72 | _applicationImageCache = new MemoryCache("GotifyApplicationImageCache"); |
73 | _webSocketsServerResponseScheduledContinuation = new ScheduledContinuation(); |
||
74 | _webSocketsClientPingStopWatch = new Stopwatch(); |
||
75 | |||
76 | _webSocketMessageBufferBlock = new BufferBlock<byte[]>(new DataflowBlockOptions { CancellationToken = _cancellationToken }); |
||
77 | var webSocketTransformBlock = new TransformBlock<byte[], GotifyMessage>(bytes => |
||
78 | { |
||
79 | if (bytes.Length == 0) |
||
80 | { |
||
81 | return null; |
||
82 | } |
||
83 | |||
84 | var message = Encoding.UTF8.GetString(bytes, 0, bytes.Length); |
||
85 | |||
86 | GotifyMessage gotifyNotification; |
||
87 | |||
88 | try |
||
89 | { |
||
90 | gotifyNotification = JsonConvert.DeserializeObject<GotifyMessage>(message); |
||
91 | } |
||
92 | catch (JsonSerializationException exception) |
||
93 | { |
||
94 | Log.Warning($"Could not deserialize notification: {exception.Message}"); |
||
95 | |||
96 | return null; |
||
97 | } |
||
98 | |||
99 | if (gotifyNotification == null) |
||
100 | { |
||
101 | Log.Warning($"Could not deserialize gotify notification: {message}"); |
||
102 | |||
103 | return null; |
||
104 | } |
||
105 | |||
106 | return gotifyNotification; |
||
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 Image applicationImage) |
||
116 | { |
||
117 | GotifyNotification?.Invoke(this, |
||
118 | new GotifyNotificationEventArgs(message, applicationImage)); |
||
119 | return; |
||
120 | } |
||
121 | |||
122 | using (var imageStream = await RetrieveGotifyApplicationImage(message.AppId, _cancellationToken)) |
||
123 | { |
||
124 | if (imageStream == null || imageStream.Length == 0) |
||
125 | { |
||
126 | Log.Warning("Could not find any application image for notification"); |
||
127 | return; |
||
128 | } |
||
129 | |||
130 | var image = Image.FromStream(imageStream); |
||
131 | |||
63 | office | 132 | var clone = new Bitmap(image); |
62 | office | 133 | |
134 | _applicationImageCache.Add($"{message.AppId}", clone, |
||
61 | office | 135 | new CacheItemPolicy |
136 | { |
||
137 | SlidingExpiration = TimeSpan.FromHours(1) |
||
138 | }); |
||
139 | |||
140 | GotifyNotification?.Invoke(this, |
||
141 | new GotifyNotificationEventArgs(message, image)); |
||
142 | } |
||
143 | |||
144 | Log.Debug($"Notification message received: {message.Message}"); |
||
145 | |||
146 | }, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken }); |
||
147 | |||
148 | _tplWebSocketsBufferBlockTransformLink = _webSocketMessageBufferBlock.LinkTo(webSocketTransformBlock, |
||
149 | new DataflowLinkOptions { PropagateCompletion = true }); |
||
150 | _tplWebSocketsTransformActionLink = webSocketTransformBlock.LinkTo(webSocketActionBlock, |
||
151 | new DataflowLinkOptions { PropagateCompletion = true }, message => message != null); |
||
152 | _tplWebSocketsTransformActionNullLink = webSocketTransformBlock.LinkTo(DataflowBlock.NullTarget<GotifyMessage>(), |
||
153 | new DataflowLinkOptions() { PropagateCompletion = true }); |
||
39 | office | 154 | } |
155 | |||
59 | office | 156 | public GotifyConnection(Server server, Configuration.Configuration configuration) : this() |
39 | office | 157 | { |
24 | office | 158 | _server = server; |
44 | office | 159 | _configuration = configuration; |
39 | office | 160 | |
54 | office | 161 | ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; |
59 | office | 162 | var httpClientHandler = new HttpClientHandler |
50 | office | 163 | { |
54 | office | 164 | // mono does not implement this |
165 | //SslProtocols = SslProtocols.Tls12 |
||
50 | office | 166 | }; |
167 | |||
44 | office | 168 | _httpClient = new HttpClient(httpClientHandler); |
169 | if (_configuration.IgnoreSelfSignedCertificates) |
||
170 | httpClientHandler.ServerCertificateCustomValidationCallback = |
||
46 | office | 171 | (httpRequestMessage, cert, cetChain, policyErrors) => true; |
39 | office | 172 | |
50 | office | 173 | if (_configuration.Proxy.Enable) |
174 | httpClientHandler.Proxy = new WebProxy(_configuration.Proxy.Url, false, new string[] { }, |
||
175 | new NetworkCredential(_configuration.Proxy.Username, _configuration.Proxy.Password)); |
||
176 | |||
47 | office | 177 | _httpClient = new HttpClient(httpClientHandler); |
178 | if (!string.IsNullOrEmpty(_server.Username) && !string.IsNullOrEmpty(_server.Password)) |
||
179 | _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", |
||
180 | Convert.ToBase64String(Encoding.Default.GetBytes($"{_server.Username}:{_server.Password}"))); |
||
44 | office | 181 | |
47 | office | 182 | if (!Uri.TryCreate(_server.Url, UriKind.Absolute, out _httpUri)) |
39 | office | 183 | { |
51 | office | 184 | Log.Error($"No HTTP URL could be built out of the supplied server URI {_server.Url}"); |
47 | office | 185 | return; |
186 | } |
||
44 | office | 187 | |
47 | office | 188 | // Build the web sockets URI. |
189 | var webSocketsUriBuilder = new UriBuilder(_httpUri); |
||
190 | switch (webSocketsUriBuilder.Scheme.ToUpperInvariant()) |
||
191 | { |
||
192 | case "HTTP": |
||
193 | webSocketsUriBuilder.Scheme = "ws"; |
||
194 | break; |
||
195 | case "HTTPS": |
||
196 | webSocketsUriBuilder.Scheme = "wss"; |
||
197 | break; |
||
198 | } |
||
199 | |||
200 | try |
||
201 | { |
||
39 | office | 202 | webSocketsUriBuilder.Path = Path.Combine(webSocketsUriBuilder.Path, "stream"); |
203 | } |
||
47 | office | 204 | catch (ArgumentException exception) |
205 | { |
||
59 | office | 206 | Log.Error( |
207 | $"No WebSockets URL could be built from the provided URL {_server.Url} due to {exception.Message}"); |
||
47 | office | 208 | } |
209 | |||
210 | _webSocketsUri = webSocketsUriBuilder.Uri; |
||
24 | office | 211 | } |
212 | |||
1 | office | 213 | public void Dispose() |
214 | { |
||
215 | if (_cancellationTokenSource != null) |
||
216 | { |
||
217 | _cancellationTokenSource.Dispose(); |
||
218 | _cancellationTokenSource = null; |
||
219 | } |
||
39 | office | 220 | |
61 | office | 221 | if (_tplWebSocketsBufferBlockTransformLink != null) |
222 | { |
||
223 | _tplWebSocketsBufferBlockTransformLink.Dispose(); |
||
224 | _tplWebSocketsBufferBlockTransformLink = null; |
||
225 | } |
||
226 | |||
227 | if (_tplWebSocketsTransformActionLink != null) |
||
228 | { |
||
229 | _tplWebSocketsTransformActionLink.Dispose(); |
||
230 | _tplWebSocketsTransformActionLink = null; |
||
231 | } |
||
232 | |||
233 | if (_tplWebSocketsTransformActionNullLink != null) |
||
234 | { |
||
235 | _tplWebSocketsTransformActionNullLink.Dispose(); |
||
236 | _tplWebSocketsTransformActionNullLink = null; |
||
237 | } |
||
238 | |||
239 | if (_tplRetrievePastMessagesLink != null) |
||
240 | { |
||
241 | _tplRetrievePastMessagesLink.Dispose(); |
||
242 | _tplRetrievePastMessagesLink = null; |
||
243 | } |
||
244 | |||
48 | office | 245 | if (_webSocketSharp != null) |
246 | { |
||
247 | _webSocketSharp.Close(); |
||
248 | _webSocketSharp = null; |
||
249 | } |
||
44 | office | 250 | |
48 | office | 251 | if (_httpClient != null) |
252 | { |
||
253 | _httpClient.Dispose(); |
||
254 | _httpClient = null; |
||
255 | } |
||
1 | office | 256 | } |
257 | |||
258 | #endregion |
||
259 | |||
260 | #region Public Methods |
||
261 | |||
25 | office | 262 | public void Start() |
1 | office | 263 | { |
47 | office | 264 | if (_webSocketsUri == null || _httpUri == null) |
265 | { |
||
51 | office | 266 | Log.Error("Could not start connection to server due to unreadable URLs"); |
47 | office | 267 | return; |
268 | } |
||
269 | |||
1 | office | 270 | _cancellationTokenSource = new CancellationTokenSource(); |
271 | _cancellationToken = _cancellationTokenSource.Token; |
||
272 | |||
44 | office | 273 | Connect(); |
274 | |||
59 | office | 275 | if (_configuration.RetrievePastNotificationHours != 0) |
276 | { |
||
277 | _initTask = RetrievePastMessages(_cancellationToken); |
||
278 | } |
||
279 | |||
61 | office | 280 | _runTask = HeartBeat(_cancellationToken); |
1 | office | 281 | } |
282 | |||
44 | office | 283 | private void Connect() |
1 | office | 284 | { |
44 | office | 285 | _webSocketSharp = new WebSocket(_webSocketsUri.AbsoluteUri); |
61 | office | 286 | _webSocketSharp.EmitOnPing = true; |
287 | _webSocketSharp.WaitTime = TimeSpan.FromMinutes(1); |
||
50 | office | 288 | _webSocketSharp.SslConfiguration = new ClientSslConfiguration(_webSocketsUri.Host, |
289 | new X509CertificateCollection(new X509Certificate[] { }), SslProtocols.Tls12, false); |
||
290 | if (_configuration.Proxy.Enable) |
||
59 | office | 291 | _webSocketSharp.SetProxy(_configuration.Proxy.Url, _configuration.Proxy.Username, |
292 | _configuration.Proxy.Password); |
||
50 | office | 293 | |
47 | office | 294 | if (!string.IsNullOrEmpty(_server.Username) && !string.IsNullOrEmpty(_server.Password)) |
295 | _webSocketSharp.SetCredentials(_server.Username, _server.Password, true); |
||
296 | |||
44 | office | 297 | if (_configuration.IgnoreSelfSignedCertificates) |
298 | _webSocketSharp.SslConfiguration.ServerCertificateValidationCallback += |
||
299 | (sender, certificate, chain, errors) => true; |
||
300 | |||
51 | office | 301 | _webSocketSharp.Log.Output = (logData, s) => |
302 | { |
||
303 | Log.Information($"WebSockets low level logging reported: {logData.Message}"); |
||
304 | }; |
||
305 | |||
44 | office | 306 | _webSocketSharp.OnMessage += WebSocketSharp_OnMessage; |
307 | _webSocketSharp.OnError += WebSocketSharp_OnError; |
||
308 | _webSocketSharp.OnOpen += WebSocketSharp_OnOpen; |
||
309 | _webSocketSharp.OnClose += WebSocketSharp_OnClose; |
||
59 | office | 310 | |
44 | office | 311 | _webSocketSharp.ConnectAsync(); |
1 | office | 312 | } |
313 | |||
44 | office | 314 | private void WebSocketSharp_OnClose(object sender, CloseEventArgs e) |
315 | { |
||
59 | office | 316 | Log.Information( |
317 | $"WebSockets connection to server {_webSocketsUri.AbsoluteUri} closed with reason {e.Reason}"); |
||
44 | office | 318 | } |
1 | office | 319 | |
44 | office | 320 | private void WebSocketSharp_OnOpen(object sender, EventArgs e) |
321 | { |
||
51 | office | 322 | Log.Information($"WebSockets connection to server {_webSocketsUri.AbsoluteUri} is now open"); |
61 | office | 323 | |
324 | _webSocketsServerResponseScheduledContinuation.Schedule(TimeSpan.FromMinutes(1), OnUnresponsiveServer, _cancellationToken); |
||
44 | office | 325 | } |
1 | office | 326 | |
61 | office | 327 | private void OnUnresponsiveServer() |
328 | { |
||
329 | Log.Warning($"Server {_server} has not responded in a long while..."); |
||
330 | } |
||
331 | |||
44 | office | 332 | private async void WebSocketSharp_OnError(object sender, ErrorEventArgs e) |
1 | office | 333 | { |
59 | office | 334 | Log.Error( |
335 | $"Connection to WebSockets server {_webSocketsUri.AbsoluteUri} terminated unexpectedly with message {e.Message}", |
||
336 | e.Exception); |
||
48 | office | 337 | |
44 | office | 338 | if (_cancellationToken.IsCancellationRequested) |
1 | office | 339 | { |
44 | office | 340 | Stop(); |
341 | return; |
||
342 | } |
||
18 | office | 343 | |
44 | office | 344 | await Task.Delay(TimeSpan.FromSeconds(1), _cancellationToken); |
61 | office | 345 | |
51 | office | 346 | Log.Information($"Reconnecting to websocket server {_webSocketsUri.AbsoluteUri}"); |
18 | office | 347 | |
44 | office | 348 | Connect(); |
349 | } |
||
1 | office | 350 | |
44 | office | 351 | private async void WebSocketSharp_OnMessage(object sender, MessageEventArgs e) |
352 | { |
||
61 | office | 353 | if (e.IsPing) |
44 | office | 354 | { |
61 | office | 355 | Log.Information($"Server {_server} sent PING message"); |
1 | office | 356 | |
61 | office | 357 | _webSocketsServerResponseScheduledContinuation.Schedule(TimeSpan.FromMinutes(1), OnUnresponsiveServer, _cancellationToken); |
1 | office | 358 | |
44 | office | 359 | return; |
360 | } |
||
12 | office | 361 | |
61 | office | 362 | await _webSocketMessageBufferBlock.SendAsync(e.RawData, _cancellationToken); |
44 | office | 363 | } |
12 | office | 364 | |
44 | office | 365 | public void Stop() |
366 | { |
||
59 | office | 367 | if (_cancellationTokenSource == null) return; |
368 | |||
369 | _cancellationTokenSource.Cancel(); |
||
44 | office | 370 | } |
371 | |||
372 | #endregion |
||
373 | |||
374 | #region Private Methods |
||
375 | |||
59 | office | 376 | private async Task RetrievePastMessages(CancellationToken cancellationToken) |
377 | { |
||
378 | var messageUriBuilder = new UriBuilder(_httpUri); |
||
61 | office | 379 | |
380 | var gotifyApplicationBufferBlock = new BufferBlock<GotifyApplication>(new DataflowBlockOptions { CancellationToken = cancellationToken }); |
||
381 | var gotifyApplicationActionBlock = new ActionBlock<GotifyApplication>(async application => |
||
59 | office | 382 | { |
383 | try |
||
384 | { |
||
385 | messageUriBuilder.Path = Path.Combine(messageUriBuilder.Path, "application", $"{application.Id}", |
||
386 | "message"); |
||
387 | } |
||
388 | catch (ArgumentException exception) |
||
389 | { |
||
390 | Log.Error($"No application URL could be built for {_server.Url} due to {exception.Message}"); |
||
391 | |||
61 | office | 392 | return; |
59 | office | 393 | } |
394 | |||
61 | office | 395 | HttpResponseMessage messagesResponse; |
396 | try |
||
397 | { |
||
398 | messagesResponse = await _httpClient.GetAsync(messageUriBuilder.Uri, cancellationToken); |
||
399 | } |
||
400 | catch (Exception exception) |
||
401 | { |
||
402 | Log.Error($"Could not get application {application.Id} due to {exception.Message}"); |
||
59 | office | 403 | |
61 | office | 404 | return; |
405 | } |
||
59 | office | 406 | |
61 | office | 407 | |
59 | office | 408 | var messages = await messagesResponse.Content.ReadAsStringAsync(); |
409 | |||
410 | GotifyMessageQuery gotifyMessageQuery; |
||
411 | try |
||
412 | { |
||
413 | gotifyMessageQuery = |
||
414 | JsonConvert.DeserializeObject<GotifyMessageQuery>(messages); |
||
415 | } |
||
416 | catch (JsonSerializationException exception) |
||
417 | { |
||
418 | Log.Warning($"Could not deserialize the message response: {exception.Message}"); |
||
419 | |||
61 | office | 420 | return; |
59 | office | 421 | } |
422 | |||
61 | office | 423 | foreach (var message in gotifyMessageQuery.Messages.Where(message => message.Date >= DateTime.Now - TimeSpan.FromHours(_configuration.RetrievePastNotificationHours))) |
59 | office | 424 | { |
61 | office | 425 | message.Server = _server; |
59 | office | 426 | |
61 | office | 427 | var cachedImage = _applicationImageCache.Get($"{message.AppId}"); |
428 | if (cachedImage is Image applicationImage) |
||
429 | { |
||
430 | GotifyNotification?.Invoke(this, |
||
431 | new GotifyNotificationEventArgs(message, applicationImage)); |
||
432 | return; |
||
433 | } |
||
59 | office | 434 | |
61 | office | 435 | using var imageStream = await RetrieveGotifyApplicationImage(message.AppId, _cancellationToken); |
436 | if (imageStream == null || imageStream.Length == 0) |
||
437 | { |
||
438 | Log.Warning("Could not find any application image for notification"); |
||
59 | office | 439 | continue; |
61 | office | 440 | } |
59 | office | 441 | |
61 | office | 442 | var image = Image.FromStream(imageStream); |
59 | office | 443 | |
63 | office | 444 | var clone = new Bitmap(image); |
62 | office | 445 | |
446 | _applicationImageCache.Add($"{message.AppId}", clone, |
||
61 | office | 447 | new CacheItemPolicy |
59 | office | 448 | { |
61 | office | 449 | SlidingExpiration = TimeSpan.FromHours(1) |
450 | }); |
||
59 | office | 451 | |
61 | office | 452 | GotifyNotification?.Invoke(this, |
453 | new GotifyNotificationEventArgs(message, image)); |
||
454 | } |
||
59 | office | 455 | |
61 | office | 456 | }, new ExecutionDataflowBlockOptions { CancellationToken = cancellationToken }); |
457 | |||
458 | gotifyApplicationBufferBlock.LinkTo(gotifyApplicationActionBlock, |
||
459 | new DataflowLinkOptions { PropagateCompletion = true }); |
||
460 | |||
461 | await foreach (var application in RetrieveGotifyApplications(cancellationToken)) |
||
462 | { |
||
463 | await gotifyApplicationBufferBlock.SendAsync(application, cancellationToken); |
||
59 | office | 464 | } |
61 | office | 465 | |
466 | gotifyApplicationBufferBlock.Complete(); |
||
467 | await gotifyApplicationActionBlock.Completion; |
||
468 | |||
59 | office | 469 | } |
470 | |||
61 | office | 471 | private async Task HeartBeat(CancellationToken cancellationToken) |
44 | office | 472 | { |
473 | try |
||
474 | { |
||
475 | do |
||
476 | { |
||
61 | office | 477 | await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken); |
478 | |||
479 | _webSocketsClientPingStopWatch.Restart(); |
||
480 | if (!_webSocketSharp.Ping()) |
||
481 | { |
||
482 | Log.Warning($"Server {_server} did not respond to PING message."); |
||
483 | continue; |
||
484 | } |
||
485 | |||
486 | var delta = _webSocketsClientPingStopWatch.ElapsedMilliseconds; |
||
487 | |||
488 | Log.Information($"PING response latency for {_server} is {delta}ms"); |
||
489 | |||
490 | _webSocketsServerResponseScheduledContinuation.Schedule(TimeSpan.FromMinutes(1), OnUnresponsiveServer, _cancellationToken); |
||
1 | office | 491 | } while (!cancellationToken.IsCancellationRequested); |
492 | } |
||
59 | office | 493 | catch (Exception exception) when (exception is OperationCanceledException || |
494 | exception is ObjectDisposedException) |
||
1 | office | 495 | { |
496 | } |
||
44 | office | 497 | catch (Exception exception) |
1 | office | 498 | { |
61 | office | 499 | Log.Warning(exception, $"Heartbeat for server {_server} has failed due to {exception.Message}"); |
1 | office | 500 | } |
501 | } |
||
502 | |||
61 | office | 503 | private async IAsyncEnumerable<GotifyApplication> RetrieveGotifyApplications([EnumeratorCancellation] CancellationToken cancellationToken) |
59 | office | 504 | { |
505 | var applicationsUriBuilder = new UriBuilder(_httpUri); |
||
506 | try |
||
507 | { |
||
508 | applicationsUriBuilder.Path = Path.Combine(applicationsUriBuilder.Path, "application"); |
||
509 | } |
||
510 | catch (ArgumentException exception) |
||
511 | { |
||
512 | Log.Error($"No application URL could be built for {_server.Url} due to {exception}"); |
||
61 | office | 513 | |
514 | yield break; |
||
59 | office | 515 | } |
516 | |||
61 | office | 517 | HttpResponseMessage applicationsResponse; |
59 | office | 518 | |
519 | try |
||
520 | { |
||
61 | office | 521 | applicationsResponse = await _httpClient.GetAsync(applicationsUriBuilder.Uri, cancellationToken); |
59 | office | 522 | } |
61 | office | 523 | catch (Exception exception) |
59 | office | 524 | { |
61 | office | 525 | Log.Error($"Could not retrieve applications: {exception.Message}"); |
59 | office | 526 | |
61 | office | 527 | yield break; |
59 | office | 528 | } |
529 | |||
61 | office | 530 | var applications = await applicationsResponse.Content.ReadAsStringAsync(); |
59 | office | 531 | |
44 | office | 532 | GotifyApplication[] gotifyApplications; |
533 | try |
||
534 | { |
||
535 | gotifyApplications = |
||
536 | JsonConvert.DeserializeObject<GotifyApplication[]>(applications); |
||
537 | } |
||
538 | catch (JsonSerializationException exception) |
||
539 | { |
||
61 | office | 540 | Log.Warning($"Could not deserialize the list of applications from the server: {exception}"); |
25 | office | 541 | |
61 | office | 542 | yield break; |
39 | office | 543 | } |
25 | office | 544 | |
39 | office | 545 | foreach (var application in gotifyApplications) |
546 | { |
||
61 | office | 547 | yield return application; |
548 | } |
||
549 | } |
||
550 | |||
551 | private async Task<Stream> RetrieveGotifyApplicationImage(int appId, CancellationToken cancellationToken) |
||
552 | { |
||
553 | await foreach (var application in RetrieveGotifyApplications(cancellationToken)) |
||
554 | { |
||
44 | office | 555 | if (application.Id != appId) continue; |
25 | office | 556 | |
61 | office | 557 | if (!Uri.TryCreate(Path.Combine($"{_httpUri}", $"{application.Image}"), UriKind.Absolute, out var applicationImageUri)) |
25 | office | 558 | { |
51 | office | 559 | Log.Warning("Could not build URL path to application icon"); |
39 | office | 560 | continue; |
561 | } |
||
25 | office | 562 | |
61 | office | 563 | HttpResponseMessage imageResponse; |
25 | office | 564 | |
61 | office | 565 | try |
566 | { |
||
567 | imageResponse = await _httpClient.GetAsync(applicationImageUri, cancellationToken); |
||
568 | } |
||
569 | catch (Exception exception) |
||
570 | { |
||
571 | Log.Error($"Could not retrieve application image: {exception.Message}"); |
||
572 | |||
573 | return new MemoryStream(); |
||
574 | } |
||
575 | |||
44 | office | 576 | var memoryStream = new MemoryStream(); |
25 | office | 577 | |
44 | office | 578 | await imageResponse.Content.CopyToAsync(memoryStream); |
579 | |||
61 | office | 580 | memoryStream.Position = 0L; |
581 | |||
44 | office | 582 | return memoryStream; |
25 | office | 583 | } |
584 | |||
61 | office | 585 | return new MemoryStream(); |
25 | office | 586 | } |
587 | |||
1 | office | 588 | #endregion |
589 | } |
||
590 | } |