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