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