Winify – Blame information for rev 48

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