Winify – Blame information for rev 83

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 using System;
25 office 2 using System.Collections.Concurrent;
1 office 3 using System.ComponentModel;
30 office 4 using System.Drawing;
4 office 5 using System.IO;
48 office 6 using System.Net;
30 office 7 using System.Reflection;
15 office 8 using System.Text;
19 office 9 using System.Threading;
4 office 10 using System.Threading.Tasks;
1 office 11 using System.Windows.Forms;
77 office 12 using Microsoft.Win32;
30 office 13 using NetSparkleUpdater;
14 using NetSparkleUpdater.Enums;
15 using NetSparkleUpdater.SignatureVerifiers;
16 using NetSparkleUpdater.UI.WinForms;
18 office 17 using Serilog;
15 office 18 using Servers;
29 office 19 using Toasts;
1 office 20 using Winify.Gotify;
25 office 21 using Winify.Settings;
15 office 22 using Winify.Utilities;
30 office 23 using Winify.Utilities.Serialization;
56 office 24 using ScheduledContinuation = Toasts.ScheduledContinuation;
1 office 25  
26 namespace Winify
27 {
30 office 28 public partial class MainForm : Form
1 office 29 {
30 office 30 #region Public Enums, Properties and Fields
31  
32 public Configuration.Configuration Configuration { get; set; }
33  
34 public ScheduledContinuation ChangedConfigurationContinuation { get; set; }
35  
43 office 36 public bool MemorySinkEnabled { get; set; }
37  
30 office 38 #endregion
39  
1 office 40 #region Private Delegates, Events, Enums, Properties, Indexers and Fields
41  
42 private AboutForm _aboutForm;
43  
25 office 44 private ConcurrentBag<GotifyConnection> _gotifyConnections;
1 office 45  
46 private SettingsForm _settingsForm;
47  
30 office 48 private readonly SparkleUpdater _sparkle;
49  
50 private readonly CancellationTokenSource _cancellationTokenSource;
51  
52 private readonly CancellationToken _cancellationToken;
53  
43 office 54 private LogViewForm _logViewForm;
55  
56 private readonly LogMemorySink _memorySink;
57  
78 office 58 private readonly Toasts.ToastDisplay _toastDisplay;
56 office 59  
1 office 60 #endregion
61  
62 #region Constructors, Destructors and Finalizers
63  
30 office 64 public MainForm()
1 office 65 {
67 office 66 InitializeComponent();
67  
77 office 68 SystemEvents.PowerModeChanged += OnPowerModeChanged;
69  
30 office 70 _cancellationTokenSource = new CancellationTokenSource();
71 _cancellationToken = _cancellationTokenSource.Token;
72  
73 ChangedConfigurationContinuation = new ScheduledContinuation();
56 office 74  
78 office 75 _toastDisplay = new Toasts.ToastDisplay(_cancellationToken);
30 office 76 }
77  
78 public MainForm(Mutex mutex) : this()
79 {
43 office 80 _memorySink = new LogMemorySink();
18 office 81 Log.Logger = new LoggerConfiguration()
82 .MinimumLevel.Debug()
43 office 83 .WriteTo.Conditional(condition => MemorySinkEnabled, configureSink => configureSink.Sink(_memorySink))
18 office 84 .WriteTo.File(Path.Combine(Constants.UserApplicationDirectory, "Logs", $"{Constants.AssemblyName}.log"),
85 rollingInterval: RollingInterval.Day)
86 .CreateLogger();
87  
30 office 88 // Start application update.
89 var manifestModuleName = Assembly.GetEntryAssembly().ManifestModule.FullyQualifiedName;
90 var icon = Icon.ExtractAssociatedIcon(manifestModuleName);
19 office 91  
30 office 92 _sparkle = new SparkleUpdater("https://winify.grimore.org/update/appcast.xml",
93 new Ed25519Checker(SecurityMode.Strict, "LonrgxVjSF0GnY4hzwlRJnLkaxnDn2ikdmOifILzLJY="))
4 office 94 {
30 office 95 UIFactory = new UIFactory(icon),
48 office 96 RelaunchAfterUpdate = true,
97 SecurityProtocolType = SecurityProtocolType.Tls12
30 office 98 };
99 _sparkle.StartLoop(true, true);
1 office 100 }
101  
102 /// <summary>
103 /// Clean up any resources being used.
104 /// </summary>
105 /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
106 protected override void Dispose(bool disposing)
107 {
30 office 108 if (disposing && components != null) components.Dispose();
7 office 109  
1 office 110 base.Dispose(disposing);
111 }
112  
113 #endregion
114  
115 #region Event Handlers
116  
77 office 117 private async void OnPowerModeChanged(object sender, PowerModeChangedEventArgs e)
118 {
119 switch (e.Mode)
120 {
121 case PowerModes.Resume:
122 // Refresh connection to gotify server.
123 while (_gotifyConnections.TryTake(out var gotifyConnection))
124 {
125 gotifyConnection.GotifyMessage -= GotifyConnectionGotifyMessage;
126 await gotifyConnection.Stop();
127 gotifyConnection.Dispose();
128 }
129  
130 var servers = await LoadServers();
131 foreach (var server in servers.Server)
132 {
83 office 133 var gotifyConnection = new GotifyConnection(server, Configuration, _cancellationToken);
77 office 134 gotifyConnection.GotifyMessage += GotifyConnectionGotifyMessage;
135 gotifyConnection.Start();
136 _gotifyConnections.Add(gotifyConnection);
137 }
138 break;
139 }
140 }
141  
30 office 142 private async void MainForm_Load(object sender, EventArgs e)
21 office 143 {
30 office 144 Configuration = await LoadConfiguration();
21 office 145  
30 office 146 var servers = await LoadServers();
147 _gotifyConnections = new ConcurrentBag<GotifyConnection>();
148 foreach (var server in servers.Server)
149 {
83 office 150 var gotifyConnection = new GotifyConnection(server, Configuration, _cancellationToken);
67 office 151 gotifyConnection.GotifyMessage += GotifyConnectionGotifyMessage;
30 office 152 gotifyConnection.Start();
153 _gotifyConnections.Add(gotifyConnection);
154 }
21 office 155 }
156  
56 office 157 private void LogViewToolStripMenuItem_Click(object sender, EventArgs e)
158 {
67 office 159 if (_logViewForm != null)
160 {
161 return;
162 }
56 office 163  
164 _logViewForm = new LogViewForm(this, _memorySink, _cancellationToken);
165 _logViewForm.Closing += LogViewForm_Closing;
166 _logViewForm.Show();
167 }
168  
169 private void LogViewForm_Closing(object sender, CancelEventArgs e)
170 {
67 office 171 if (_logViewForm == null)
172 {
173 return;
174 }
56 office 175  
176 _logViewForm.Closing -= LogViewForm_Closing;
177 _logViewForm.Close();
178 _logViewForm = null;
179 }
180  
25 office 181 private async void SettingsToolStripMenuItem_Click(object sender, EventArgs e)
11 office 182 {
30 office 183 if (_settingsForm == null)
184 {
185 var servers = await LoadServers();
186 var announcements = await LoadAnnouncements();
25 office 187  
30 office 188 _settingsForm = new SettingsForm(this, servers, announcements, _cancellationToken);
189 _settingsForm.Save += SettingsForm_Save;
190 _settingsForm.Closing += SettingsForm_Closing;
191 _settingsForm.Show();
192 }
25 office 193 }
194  
195 private async void SettingsForm_Save(object sender, SettingsSavedEventArgs e)
196 {
55 office 197 // Save the configuration.
198 Miscellaneous.LaunchOnBootSet(Configuration.LaunchOnBoot);
199 ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1),
200 async () => { await SaveConfiguration(); }, _cancellationToken);
201  
25 office 202 // Save the servers.
203 await Task.WhenAll(SaveServers(e.Servers), SaveAnnouncements(e.Announcements));
204  
205 // Update connections to gotify servers.
206 while (_gotifyConnections.TryTake(out var gotifyConnection))
207 {
67 office 208 gotifyConnection.GotifyMessage -= GotifyConnectionGotifyMessage;
209 await gotifyConnection.Stop();
25 office 210 gotifyConnection.Dispose();
211 }
212  
213 foreach (var server in e.Servers.Server)
214 {
83 office 215 var gotifyConnection = new GotifyConnection(server, Configuration, _cancellationToken);
67 office 216 gotifyConnection.GotifyMessage += GotifyConnectionGotifyMessage;
25 office 217 gotifyConnection.Start();
218 _gotifyConnections.Add(gotifyConnection);
219 }
220 }
221  
67 office 222 private async void GotifyConnectionGotifyMessage(object sender, GotifyMessageEventArgs e)
25 office 223 {
30 office 224 var announcements = await LoadAnnouncements();
25 office 225  
30 office 226 foreach (var announcement in announcements.Announcement)
67 office 227 {
228 if (announcement.AppId != e.Message.AppId)
30 office 229 {
67 office 230 continue;
30 office 231 }
24 office 232  
71 office 233 if (announcement.Ignore)
234 {
235 return;
236 }
237  
238 if (announcement.LingerTime <= 0)
239 {
240 return;
241 }
242  
78 office 243 await _toastDisplay.Queue(
244 new ToastDisplayData
245 {
246 Title = $"{e.Message.Title} ({e.Message.Server.Name}/{e.Message.AppId})",
247 Body = e.Message.Message,
248 EnableChime = announcement.EnableChime,
249 Chime = announcement.Chime ?? Configuration.Chime,
250 LingerTime = (int)announcement.LingerTime,
251 Image = e.Image,
252 Content = e.Message.Extras.GotifyMessageExtrasClientDisplay.ContentType
253 });
67 office 254  
255 return;
256 }
257  
55 office 258 if (Configuration.InfiniteToastDuration)
259 {
78 office 260 await _toastDisplay.Queue(new ToastDisplayData
73 office 261 {
78 office 262 Title = $"{e.Message.Title} ({e.Message.Server.Name}/{e.Message.AppId})",
263 Body = e.Message.Message,
75 office 264 Chime = Configuration.Chime,
78 office 265 Image = e.Image,
266 Content = e.Message.Extras.GotifyMessageExtrasClientDisplay.ContentType
267 });
55 office 268  
269 return;
270 }
271  
78 office 272 await _toastDisplay.Queue(new ToastDisplayData
73 office 273 {
78 office 274 Title = $"{e.Message.Title} ({e.Message.Server.Name}/{e.Message.AppId})",
275 Body = e.Message.Message,
75 office 276 Chime = Configuration.Chime,
73 office 277 LingerTime = Configuration.ToastDuration,
78 office 278 Image = e.Image,
279 Content = e.Message.Extras.GotifyMessageExtrasClientDisplay.ContentType
280 });
11 office 281 }
282  
1 office 283 private void SettingsForm_Closing(object sender, CancelEventArgs e)
284 {
67 office 285 if (_settingsForm == null)
286 {
287 return;
288 }
6 office 289  
25 office 290 _settingsForm.Save -= SettingsForm_Save;
1 office 291 _settingsForm.Closing -= SettingsForm_Closing;
292 _settingsForm.Dispose();
293 _settingsForm = null;
294 }
295  
296 private void AboutToolStripMenuItem_Click(object sender, EventArgs e)
297 {
67 office 298 if (_aboutForm != null)
299 {
300 return;
301 }
1 office 302  
303 _aboutForm = new AboutForm();
304 _aboutForm.Closing += AboutForm_Closing;
305 _aboutForm.Show();
306 }
307  
308 private void AboutForm_Closing(object sender, CancelEventArgs e)
309 {
67 office 310 if (_aboutForm == null)
311 {
312 return;
313 }
1 office 314  
315 _aboutForm.Closing -= AboutForm_Closing;
316 _aboutForm.Dispose();
317 _aboutForm = null;
318 }
319  
320 private void QuitToolStripMenuItem_Click(object sender, EventArgs e)
321 {
17 office 322 Close();
30 office 323 }
17 office 324  
30 office 325 private async void UpdateToolStripMenuItem_Click(object sender, EventArgs e)
326 {
327 // Manually check for updates, this will not show a ui
328 var result = await _sparkle.CheckForUpdatesQuietly();
48 office 329 var updates = result.Updates;
30 office 330 if (result.Status == UpdateStatus.UpdateAvailable)
331 {
332 // if update(s) are found, then we have to trigger the UI to show it gracefully
333 _sparkle.ShowUpdateNeededUI();
334 return;
335 }
336  
49 office 337 MessageBox.Show("No updates available at this time.", "Winify", MessageBoxButtons.OK,
30 office 338 MessageBoxIcon.Asterisk,
339 MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly, false);
1 office 340 }
341  
30 office 342 #endregion
343  
344 #region Public Methods
345  
346 public async Task SaveConfiguration()
9 office 347 {
30 office 348 if (!Directory.Exists(Constants.UserApplicationDirectory))
349 Directory.CreateDirectory(Constants.UserApplicationDirectory);
350  
351 switch (await Serialization.Serialize(Configuration, Constants.ConfigurationFile, "Configuration",
352 "<!ATTLIST Configuration xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>",
353 CancellationToken.None))
354 {
355 case SerializationSuccess<Configuration.Configuration> _:
356 Log.Information("Serialized configuration.");
357 break;
358 case SerializationFailure serializationFailure:
359 Log.Warning(serializationFailure.Exception.Message, "Failed to serialize configuration.");
360 break;
361 }
9 office 362 }
363  
30 office 364 public static async Task<Configuration.Configuration> LoadConfiguration()
365 {
366 if (!Directory.Exists(Constants.UserApplicationDirectory))
367 Directory.CreateDirectory(Constants.UserApplicationDirectory);
368  
369 var deserializationResult =
370 await Serialization.Deserialize<Configuration.Configuration>(Constants.ConfigurationFile,
371 Constants.ConfigurationNamespace, Constants.ConfigurationXsd, CancellationToken.None);
372  
373 switch (deserializationResult)
374 {
375 case SerializationSuccess<Configuration.Configuration> serializationSuccess:
376 return serializationSuccess.Result;
377 case SerializationFailure serializationFailure:
378 Log.Warning(serializationFailure.Exception, "Failed to load configuration.");
379 return new Configuration.Configuration();
380 default:
381 return new Configuration.Configuration();
382 }
383 }
384  
1 office 385 #endregion
4 office 386  
387 #region Private Methods
388  
25 office 389 private static async Task SaveAnnouncements(Announcements.Announcements announcements)
21 office 390 {
30 office 391 switch (await Serialization.Serialize(announcements, Constants.AnnouncementsFile, "Announcements",
392 "<!ATTLIST Announcements xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>",
393 CancellationToken.None))
21 office 394 {
395 case SerializationFailure serializationFailure:
396 Log.Warning(serializationFailure.Exception, "Unable to serialize announcements.");
397 break;
398 }
399 }
400  
30 office 401 private static async Task SaveServers(Servers.Servers servers)
21 office 402 {
403 // Encrypt password for all servers.
404 var deviceId = Miscellaneous.GetMachineGuid();
30 office 405 var @protected = new Servers.Servers
21 office 406 {
407 Server = new BindingListWithCollectionChanged<Server>()
408 };
43 office 409  
25 office 410 foreach (var server in servers.Server)
21 office 411 {
41 office 412 var password = Encoding.UTF8.GetBytes(server.Password);
413 var encrypted = await AES.Encrypt(password, deviceId);
21 office 414 var armored = Convert.ToBase64String(encrypted);
415  
416 @protected.Server.Add(new Server(server.Name, server.Url, server.Username, armored));
417 }
418  
30 office 419 switch (await Serialization.Serialize(@protected, Constants.ServersFile, "Servers",
420 "<!ATTLIST Servers xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>",
421 CancellationToken.None))
21 office 422 {
423 case SerializationFailure serializationFailure:
424 Log.Warning(serializationFailure.Exception, "Unable to serialize servers.");
425 break;
426 }
427 }
428  
15 office 429 private static async Task<Announcements.Announcements> LoadAnnouncements()
430 {
431 if (!Directory.Exists(Constants.UserApplicationDirectory))
432 Directory.CreateDirectory(Constants.UserApplicationDirectory);
433  
434 var deserializationResult =
30 office 435 await Serialization.Deserialize<Announcements.Announcements>(Constants.AnnouncementsFile,
436 "urn:winify-announcements-schema", "Announcements.xsd", CancellationToken.None);
15 office 437  
438 switch (deserializationResult)
439 {
440 case SerializationSuccess<Announcements.Announcements> serializationSuccess:
441 return serializationSuccess.Result;
21 office 442 case SerializationFailure serializationFailure:
443 Log.Warning(serializationFailure.Exception, "Unable to load announcements.");
444 return new Announcements.Announcements();
15 office 445 default:
446 return new Announcements.Announcements();
447 }
448 }
449  
30 office 450 private static async Task<Servers.Servers> LoadServers()
4 office 451 {
452 if (!Directory.Exists(Constants.UserApplicationDirectory))
453 Directory.CreateDirectory(Constants.UserApplicationDirectory);
454  
15 office 455 var deserializationResult =
30 office 456 await Serialization.Deserialize<Servers.Servers>(Constants.ServersFile,
457 "urn:winify-servers-schema", "Servers.xsd", CancellationToken.None);
4 office 458  
459 switch (deserializationResult)
460 {
30 office 461 case SerializationSuccess<Servers.Servers> serializationSuccess:
15 office 462 // Decrypt password.
463 var deviceId = Miscellaneous.GetMachineGuid();
30 office 464 var @protected = new Servers.Servers
15 office 465 {
466 Server = new BindingListWithCollectionChanged<Server>()
467 };
468 foreach (var server in serializationSuccess.Result.Server)
469 {
470 var unarmored = Convert.FromBase64String(server.Password);
72 office 471 byte[] decrypted;
472 try
473 {
474 decrypted = await AES.Decrypt(unarmored, deviceId);
475 }
476 catch(Exception exception)
477 {
478 Log.Warning(exception, $"Could not decrypt password for server {server.Name} in configuration file.");
479 continue;
480 }
4 office 481  
72 office 482 var password = Encoding.UTF8.GetString(decrypted);
483  
484 @protected.Server.Add(new Server(server.Name, server.Url, server.Username, password));
15 office 485 }
486  
487 return @protected;
488  
21 office 489 case SerializationFailure serializationFailure:
490 Log.Warning(serializationFailure.Exception, "Unable to load servers.");
30 office 491 return new Servers.Servers();
21 office 492  
4 office 493 default:
30 office 494 return new Servers.Servers();
4 office 495 }
496 }
497  
498 #endregion
1 office 499 }
500 }