Winify – Blame information for rev 24
?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.Net.WebSockets; |
||
7 | using System.Text; |
||
8 | using System.Threading; |
||
9 | using System.Threading.Tasks; |
||
10 | using Newtonsoft.Json; |
||
18 | office | 11 | using Serilog; |
24 | office | 12 | using Servers; |
1 | office | 13 | using ClientWebSocket = System.Net.WebSockets.Managed.ClientWebSocket; |
14 | |||
15 | namespace Winify.Gotify |
||
16 | { |
||
17 | public class GotifyConnection : IDisposable |
||
18 | { |
||
19 | #region Public Events & Delegates |
||
20 | |||
21 | public event EventHandler<GotifyNotificationEventArgs> GotifyNotification; |
||
22 | |||
23 | #endregion |
||
24 | |||
25 | #region Private Delegates, Events, Enums, Properties, Indexers and Fields |
||
26 | |||
27 | private CancellationToken _cancellationToken; |
||
28 | |||
29 | private CancellationTokenSource _cancellationTokenSource; |
||
30 | |||
31 | private HttpClient _httpClient; |
||
32 | |||
33 | private Task _runTask; |
||
34 | |||
35 | private ClientWebSocket _webSocketClient; |
||
36 | |||
24 | office | 37 | private readonly Server _server; |
38 | |||
39 | public GotifyConnection(Server server) |
||
40 | { |
||
41 | _server = server; |
||
42 | } |
||
43 | |||
1 | office | 44 | #endregion |
45 | |||
46 | #region Constructors, Destructors and Finalizers |
||
47 | |||
48 | public void Dispose() |
||
49 | { |
||
50 | if (_cancellationTokenSource != null) |
||
51 | { |
||
52 | _cancellationTokenSource.Dispose(); |
||
53 | _cancellationTokenSource = null; |
||
54 | } |
||
55 | |||
56 | if (_webSocketClient != null) |
||
57 | { |
||
58 | _webSocketClient.Dispose(); |
||
59 | _webSocketClient = null; |
||
60 | } |
||
61 | |||
62 | if (_httpClient != null) |
||
63 | { |
||
64 | _httpClient.Dispose(); |
||
65 | _httpClient = null; |
||
66 | } |
||
67 | } |
||
68 | |||
69 | #endregion |
||
70 | |||
71 | #region Public Methods |
||
72 | |||
12 | office | 73 | public void Start(string username, string password, string url) |
1 | office | 74 | { |
12 | office | 75 | if (!Uri.TryCreate(url, UriKind.Absolute, out var httpUri)) |
1 | office | 76 | { |
77 | return; |
||
78 | } |
||
79 | |||
12 | office | 80 | // Build the web sockets URI. |
81 | var webSocketsUriBuilder = new UriBuilder(httpUri); |
||
82 | webSocketsUriBuilder.Scheme = "ws"; |
||
83 | webSocketsUriBuilder.Path = $"{webSocketsUriBuilder.Path}/stream"; |
||
84 | var webSocketsUri = webSocketsUriBuilder.Uri; |
||
1 | office | 85 | |
86 | _cancellationTokenSource = new CancellationTokenSource(); |
||
87 | _cancellationToken = _cancellationTokenSource.Token; |
||
88 | |||
12 | office | 89 | _httpClient = new HttpClient(); |
18 | office | 90 | |
1 | office | 91 | var auth = Convert.ToBase64String(Encoding.Default.GetBytes($"{username}:{password}")); |
18 | office | 92 | |
1 | office | 93 | _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth); |
94 | |||
12 | office | 95 | _runTask = Run(webSocketsUri, httpUri, username, password, _cancellationToken); |
1 | office | 96 | } |
97 | |||
98 | public void Stop() |
||
99 | { |
||
100 | if (_cancellationTokenSource != null) |
||
101 | { |
||
102 | _cancellationTokenSource.Cancel(); |
||
103 | } |
||
24 | office | 104 | |
105 | _runTask.Wait(CancellationToken.None); |
||
1 | office | 106 | } |
107 | |||
108 | #endregion |
||
109 | |||
110 | #region Private Methods |
||
111 | |||
12 | office | 112 | private async Task Run(Uri webSocketsUri, Uri httpUri, string username, string password, |
113 | CancellationToken cancellationToken) |
||
1 | office | 114 | { |
115 | try |
||
116 | { |
||
117 | do |
||
118 | { |
||
119 | try |
||
120 | { |
||
21 | office | 121 | using (_webSocketClient = new ClientWebSocket()) |
122 | { |
||
123 | var auth = Convert.ToBase64String(Encoding.Default.GetBytes($"{username}:{password}")); |
||
18 | office | 124 | |
21 | office | 125 | _webSocketClient.Options.SetRequestHeader("Authorization", $"Basic {auth}"); |
18 | office | 126 | |
21 | office | 127 | await _webSocketClient.ConnectAsync(webSocketsUri, cancellationToken); |
1 | office | 128 | |
21 | office | 129 | do |
1 | office | 130 | { |
21 | office | 131 | var payload = new ArraySegment<byte>(new byte[1024]); |
1 | office | 132 | |
21 | office | 133 | var result = await _webSocketClient.ReceiveAsync(payload, cancellationToken); |
1 | office | 134 | |
21 | office | 135 | if (result.Count == 0) |
136 | { |
||
137 | continue; |
||
138 | } |
||
1 | office | 139 | |
21 | office | 140 | if (payload.Array == null || payload.Count == 0) |
141 | { |
||
142 | continue; |
||
143 | } |
||
1 | office | 144 | |
21 | office | 145 | var message = Encoding.UTF8.GetString(payload.Array, 0, payload.Count); |
12 | office | 146 | |
21 | office | 147 | var gotifyNotification = JsonConvert.DeserializeObject<GotifyNotification>(message); |
148 | if (gotifyNotification == null) |
||
1 | office | 149 | { |
150 | continue; |
||
151 | } |
||
152 | |||
24 | office | 153 | gotifyNotification.Server = _server; |
154 | |||
21 | office | 155 | if (!Uri.TryCreate($"{httpUri}/application", UriKind.Absolute, out var applicationUri)) |
12 | office | 156 | { |
157 | continue; |
||
158 | } |
||
3 | office | 159 | |
21 | office | 160 | var applications = await _httpClient.GetStringAsync(applicationUri); |
12 | office | 161 | |
21 | office | 162 | var gotifyApplications = |
163 | JsonConvert.DeserializeObject<GotifyApplication[]>(applications); |
||
164 | if (gotifyApplications == null) |
||
3 | office | 165 | { |
166 | continue; |
||
167 | } |
||
168 | |||
21 | office | 169 | foreach (var application in gotifyApplications) |
1 | office | 170 | { |
21 | office | 171 | if (application.Id != gotifyNotification.AppId) |
172 | { |
||
173 | continue; |
||
174 | } |
||
1 | office | 175 | |
21 | office | 176 | if (!Uri.TryCreate($"{httpUri}/{application.Image}", UriKind.Absolute, |
177 | out var applicationImageUri)) |
||
178 | { |
||
179 | continue; |
||
180 | } |
||
181 | |||
182 | var imageBytes = await _httpClient.GetByteArrayAsync(applicationImageUri); |
||
183 | |||
184 | if (imageBytes == null || imageBytes.Length == 0) |
||
185 | { |
||
186 | continue; |
||
187 | } |
||
188 | |||
189 | using (var memoryStream = new MemoryStream(imageBytes)) |
||
190 | { |
||
191 | var image = Image.FromStream(memoryStream); |
||
192 | |||
193 | GotifyNotification?.Invoke(this, |
||
194 | new GotifyNotificationEventArgs(gotifyNotification, image)); |
||
195 | } |
||
196 | |||
197 | |||
198 | break; |
||
1 | office | 199 | } |
200 | |||
21 | office | 201 | Log.Debug($"Notification message received: {gotifyNotification.Message}"); |
202 | } while (!cancellationToken.IsCancellationRequested); |
||
1 | office | 203 | |
21 | office | 204 | await _webSocketClient.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, |
205 | CancellationToken.None); |
||
206 | } |
||
1 | office | 207 | |
21 | office | 208 | _webSocketClient = null; |
1 | office | 209 | } |
12 | office | 210 | catch (Exception ex) when (ex is WebSocketException || ex is HttpRequestException) |
1 | office | 211 | { |
18 | office | 212 | Log.Warning($"Unable to connect to gotify server: {ex.Message}"); |
12 | office | 213 | |
1 | office | 214 | // Reconnect |
215 | if (_webSocketClient != null) |
||
216 | { |
||
217 | _webSocketClient.Abort(); |
||
218 | _webSocketClient.Dispose(); |
||
219 | _webSocketClient = null; |
||
220 | } |
||
221 | |||
222 | await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); |
||
223 | } |
||
224 | } while (!cancellationToken.IsCancellationRequested); |
||
225 | } |
||
226 | catch (Exception ex) when (ex is OperationCanceledException || ex is ObjectDisposedException) |
||
227 | { |
||
228 | } |
||
229 | catch (Exception ex) |
||
230 | { |
||
18 | office | 231 | Log.Warning(ex, "Failure running connection loop."); |
1 | office | 232 | } |
233 | } |
||
234 | |||
235 | #endregion |
||
236 | } |
||
237 | } |