Winify – Diff between revs 39 and 44

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