Winify – Blame information for rev 47

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  
44 office 113 _webSocketSharp.Close();
114 _webSocketSharp = null;
115  
39 office 116 _httpClient.Dispose();
117 _httpClient = null;
1 office 118 }
119  
120 #endregion
121  
122 #region Public Methods
123  
25 office 124 public void Start()
1 office 125 {
47 office 126 if (_webSocketsUri == null || _httpUri == null)
127 {
128 Log.Error("Could not start connection to server due to unreadable URLs.");
129 return;
130 }
131  
1 office 132 _cancellationTokenSource = new CancellationTokenSource();
133 _cancellationToken = _cancellationTokenSource.Token;
134  
44 office 135 Connect();
136  
39 office 137 _runTask = Run(_cancellationToken);
1 office 138 }
139  
44 office 140 private void Connect()
1 office 141 {
44 office 142 _webSocketSharp = new WebSocket(_webSocketsUri.AbsoluteUri);
47 office 143 if (!string.IsNullOrEmpty(_server.Username) && !string.IsNullOrEmpty(_server.Password))
144 {
145 _webSocketSharp.SetCredentials(_server.Username, _server.Password, true);
146 }
147  
44 office 148 if (_configuration.IgnoreSelfSignedCertificates)
39 office 149 {
44 office 150 _webSocketSharp.SslConfiguration.ServerCertificateValidationCallback +=
151 (sender, certificate, chain, errors) => true;
39 office 152 }
44 office 153  
154 _webSocketSharp.OnMessage += WebSocketSharp_OnMessage;
155 _webSocketSharp.OnError += WebSocketSharp_OnError;
156 _webSocketSharp.OnOpen += WebSocketSharp_OnOpen;
157 _webSocketSharp.OnClose += WebSocketSharp_OnClose;
158 _webSocketSharp.ConnectAsync();
1 office 159 }
160  
44 office 161 private void WebSocketSharp_OnClose(object sender, CloseEventArgs e)
162 {
163 Log.Information($"Connection to server closed with reason: {e.Reason}");
164 }
1 office 165  
44 office 166 private void WebSocketSharp_OnOpen(object sender, EventArgs e)
167 {
168 Log.Information("Connection to server is now open.");
169 }
1 office 170  
44 office 171 private async void WebSocketSharp_OnError(object sender, ErrorEventArgs e)
1 office 172 {
44 office 173 if (_cancellationToken.IsCancellationRequested)
1 office 174 {
44 office 175 Stop();
176 return;
177 }
18 office 178  
44 office 179 await Task.Delay(TimeSpan.FromSeconds(1), _cancellationToken);
180 Log.Information("Reconnecting to websocket server.");
18 office 181  
44 office 182 Connect();
183 }
1 office 184  
44 office 185 private async void WebSocketSharp_OnMessage(object sender, MessageEventArgs e)
186 {
187 if (e.RawData.Length == 0)
188 {
189 Log.Warning($"Empty message received from server.");
190 return;
191 }
1 office 192  
44 office 193 var message = Encoding.UTF8.GetString(e.RawData, 0, e.RawData.Length);
1 office 194  
44 office 195 GotifyNotification gotifyNotification;
1 office 196  
44 office 197 try
198 {
199 gotifyNotification = JsonConvert.DeserializeObject<GotifyNotification>(message);
200 }
201 catch (JsonSerializationException exception)
202 {
203 Log.Warning($"Could not deserialize notification: {exception.Message}");
204 return;
205 }
12 office 206  
44 office 207 if (gotifyNotification == null)
208 {
209 Log.Warning($"Could not deserialize gotify notification: {message}");
28 office 210  
44 office 211 return;
212 }
25 office 213  
44 office 214 gotifyNotification.Server = _server;
1 office 215  
44 office 216 if (!Uri.TryCreate(Path.Combine($"{_httpUri}", "application"), UriKind.Absolute,
217 out var applicationUri))
218 {
219 Log.Warning($"Could not build an URI to an application.");
220 return;
221 }
24 office 222  
44 office 223 using (var imageStream =
224 await RetrieveGotifyApplicationImage(gotifyNotification.AppId, applicationUri, _cancellationToken))
225 {
226 if (imageStream == null)
227 {
228 Log.Warning("Could not find any application image for notification.");
229 return;
230 }
3 office 231  
44 office 232 var image = Image.FromStream(imageStream);
12 office 233  
44 office 234 GotifyNotification?.Invoke(this,
235 new GotifyNotificationEventArgs(gotifyNotification, image));
236 }
3 office 237  
44 office 238 Log.Debug($"Notification message received: {gotifyNotification.Message}");
239 }
12 office 240  
44 office 241 public void Stop()
242 {
243 if (_cancellationTokenSource != null) _cancellationTokenSource.Cancel();
244 }
245  
246 #endregion
247  
248 #region Private Methods
249  
250 private async Task Run(CancellationToken cancellationToken)
251 {
252 try
253 {
254 do
255 {
256 await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
1 office 257 } while (!cancellationToken.IsCancellationRequested);
258 }
44 office 259 catch (Exception exception) when (exception is OperationCanceledException || exception is ObjectDisposedException)
1 office 260 {
261 }
44 office 262 catch (Exception exception)
1 office 263 {
44 office 264 Log.Warning(exception, "Failure running connection loop.");
1 office 265 }
266 }
267  
44 office 268 private async Task<Stream> RetrieveGotifyApplicationImage(int appId, Uri applicationUri,
28 office 269 CancellationToken cancellationToken)
25 office 270 {
39 office 271 var applicationResponse = await _httpClient.GetAsync(applicationUri, cancellationToken);
25 office 272  
39 office 273 var applications = await applicationResponse.Content.ReadAsStringAsync();
25 office 274  
44 office 275 GotifyApplication[] gotifyApplications;
276 try
277 {
278 gotifyApplications =
279 JsonConvert.DeserializeObject<GotifyApplication[]>(applications);
280 }
281 catch (JsonSerializationException exception)
282 {
283 Log.Warning($"Could not deserialize the list of applications from the server: {exception.Message}");
25 office 284  
39 office 285 return null;
286 }
25 office 287  
39 office 288 foreach (var application in gotifyApplications)
289 {
44 office 290 if (application.Id != appId) continue;
25 office 291  
39 office 292 if (!Uri.TryCreate(Path.Combine($"{_httpUri}", $"{application.Image}"), UriKind.Absolute,
293 out var applicationImageUri))
25 office 294 {
44 office 295 Log.Warning("Could not build URL path to application icon.");
39 office 296 continue;
297 }
25 office 298  
39 office 299 var imageResponse = await _httpClient.GetAsync(applicationImageUri, cancellationToken);
25 office 300  
44 office 301 var memoryStream = new MemoryStream();
25 office 302  
44 office 303 await imageResponse.Content.CopyToAsync(memoryStream);
304  
305 return memoryStream;
25 office 306 }
307  
308 return null;
309 }
310  
1 office 311 #endregion
312 }
313 }