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