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