Winify – Blame information for rev 84

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