Winify – Blame information for rev 77

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  
67 office 58 private readonly Toasts.Toasts _toasts;
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  
67 office 75 _toasts = new Toasts.Toasts(_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 {
133 var gotifyConnection = new GotifyConnection(server, Configuration);
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 {
44 office 150 var gotifyConnection = new GotifyConnection(server, Configuration);
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 {
44 office 215 var gotifyConnection = new GotifyConnection(server, Configuration);
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  
67 office 243 var configuredNotification = new ToastForm(
244 $"{e.Message.Title} ({e.Message.Server.Name}/{e.Message.AppId})",
73 office 245 e.Message.Message)
246 {
247 EnableChime = announcement.EnableChime,
75 office 248 Chime = announcement.Chime ?? Configuration.Chime,
73 office 249 LingerTime = (int)announcement.LingerTime,
250 Image = e.Image
251 };
67 office 252  
253 await _toasts.Queue(configuredNotification);
254  
255 return;
256 }
257  
55 office 258 if (Configuration.InfiniteToastDuration)
259 {
260 var infiniteToastForm = new ToastForm(
67 office 261 $"{e.Message.Title} ({e.Message.Server.Name}/{e.Message.AppId})",
73 office 262 e.Message.Message)
263 {
75 office 264 Chime = Configuration.Chime,
73 office 265 Image = e.Image
266 };
55 office 267  
56 office 268 await _toasts.Queue(infiniteToastForm);
55 office 269  
270 return;
271 }
272  
273 var toastForm = new ToastForm(
67 office 274 $"{e.Message.Title} ({e.Message.Server.Name}/{e.Message.AppId})",
73 office 275 e.Message.Message)
276 {
75 office 277 Chime = Configuration.Chime,
73 office 278 LingerTime = Configuration.ToastDuration,
279 Image = e.Image
280 };
67 office 281  
56 office 282 await _toasts.Queue(toastForm);
11 office 283 }
284  
1 office 285 private void SettingsForm_Closing(object sender, CancelEventArgs e)
286 {
67 office 287 if (_settingsForm == null)
288 {
289 return;
290 }
6 office 291  
25 office 292 _settingsForm.Save -= SettingsForm_Save;
1 office 293 _settingsForm.Closing -= SettingsForm_Closing;
294 _settingsForm.Dispose();
295 _settingsForm = null;
296 }
297  
298 private void AboutToolStripMenuItem_Click(object sender, EventArgs e)
299 {
67 office 300 if (_aboutForm != null)
301 {
302 return;
303 }
1 office 304  
305 _aboutForm = new AboutForm();
306 _aboutForm.Closing += AboutForm_Closing;
307 _aboutForm.Show();
308 }
309  
310 private void AboutForm_Closing(object sender, CancelEventArgs e)
311 {
67 office 312 if (_aboutForm == null)
313 {
314 return;
315 }
1 office 316  
317 _aboutForm.Closing -= AboutForm_Closing;
318 _aboutForm.Dispose();
319 _aboutForm = null;
320 }
321  
322 private void QuitToolStripMenuItem_Click(object sender, EventArgs e)
323 {
17 office 324 Close();
30 office 325 }
17 office 326  
30 office 327 private async void UpdateToolStripMenuItem_Click(object sender, EventArgs e)
328 {
329 // Manually check for updates, this will not show a ui
330 var result = await _sparkle.CheckForUpdatesQuietly();
48 office 331 var updates = result.Updates;
30 office 332 if (result.Status == UpdateStatus.UpdateAvailable)
333 {
334 // if update(s) are found, then we have to trigger the UI to show it gracefully
335 _sparkle.ShowUpdateNeededUI();
336 return;
337 }
338  
49 office 339 MessageBox.Show("No updates available at this time.", "Winify", MessageBoxButtons.OK,
30 office 340 MessageBoxIcon.Asterisk,
341 MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly, false);
1 office 342 }
343  
30 office 344 #endregion
345  
346 #region Public Methods
347  
348 public async Task SaveConfiguration()
9 office 349 {
30 office 350 if (!Directory.Exists(Constants.UserApplicationDirectory))
351 Directory.CreateDirectory(Constants.UserApplicationDirectory);
352  
353 switch (await Serialization.Serialize(Configuration, Constants.ConfigurationFile, "Configuration",
354 "<!ATTLIST Configuration xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>",
355 CancellationToken.None))
356 {
357 case SerializationSuccess<Configuration.Configuration> _:
358 Log.Information("Serialized configuration.");
359 break;
360 case SerializationFailure serializationFailure:
361 Log.Warning(serializationFailure.Exception.Message, "Failed to serialize configuration.");
362 break;
363 }
9 office 364 }
365  
30 office 366 public static async Task<Configuration.Configuration> LoadConfiguration()
367 {
368 if (!Directory.Exists(Constants.UserApplicationDirectory))
369 Directory.CreateDirectory(Constants.UserApplicationDirectory);
370  
371 var deserializationResult =
372 await Serialization.Deserialize<Configuration.Configuration>(Constants.ConfigurationFile,
373 Constants.ConfigurationNamespace, Constants.ConfigurationXsd, CancellationToken.None);
374  
375 switch (deserializationResult)
376 {
377 case SerializationSuccess<Configuration.Configuration> serializationSuccess:
378 return serializationSuccess.Result;
379 case SerializationFailure serializationFailure:
380 Log.Warning(serializationFailure.Exception, "Failed to load configuration.");
381 return new Configuration.Configuration();
382 default:
383 return new Configuration.Configuration();
384 }
385 }
386  
1 office 387 #endregion
4 office 388  
389 #region Private Methods
390  
25 office 391 private static async Task SaveAnnouncements(Announcements.Announcements announcements)
21 office 392 {
30 office 393 switch (await Serialization.Serialize(announcements, Constants.AnnouncementsFile, "Announcements",
394 "<!ATTLIST Announcements xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>",
395 CancellationToken.None))
21 office 396 {
397 case SerializationFailure serializationFailure:
398 Log.Warning(serializationFailure.Exception, "Unable to serialize announcements.");
399 break;
400 }
401 }
402  
30 office 403 private static async Task SaveServers(Servers.Servers servers)
21 office 404 {
405 // Encrypt password for all servers.
406 var deviceId = Miscellaneous.GetMachineGuid();
30 office 407 var @protected = new Servers.Servers
21 office 408 {
409 Server = new BindingListWithCollectionChanged<Server>()
410 };
43 office 411  
25 office 412 foreach (var server in servers.Server)
21 office 413 {
41 office 414 var password = Encoding.UTF8.GetBytes(server.Password);
415 var encrypted = await AES.Encrypt(password, deviceId);
21 office 416 var armored = Convert.ToBase64String(encrypted);
417  
418 @protected.Server.Add(new Server(server.Name, server.Url, server.Username, armored));
419 }
420  
30 office 421 switch (await Serialization.Serialize(@protected, Constants.ServersFile, "Servers",
422 "<!ATTLIST Servers xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>",
423 CancellationToken.None))
21 office 424 {
425 case SerializationFailure serializationFailure:
426 Log.Warning(serializationFailure.Exception, "Unable to serialize servers.");
427 break;
428 }
429 }
430  
15 office 431 private static async Task<Announcements.Announcements> LoadAnnouncements()
432 {
433 if (!Directory.Exists(Constants.UserApplicationDirectory))
434 Directory.CreateDirectory(Constants.UserApplicationDirectory);
435  
436 var deserializationResult =
30 office 437 await Serialization.Deserialize<Announcements.Announcements>(Constants.AnnouncementsFile,
438 "urn:winify-announcements-schema", "Announcements.xsd", CancellationToken.None);
15 office 439  
440 switch (deserializationResult)
441 {
442 case SerializationSuccess<Announcements.Announcements> serializationSuccess:
443 return serializationSuccess.Result;
21 office 444 case SerializationFailure serializationFailure:
445 Log.Warning(serializationFailure.Exception, "Unable to load announcements.");
446 return new Announcements.Announcements();
15 office 447 default:
448 return new Announcements.Announcements();
449 }
450 }
451  
30 office 452 private static async Task<Servers.Servers> LoadServers()
4 office 453 {
454 if (!Directory.Exists(Constants.UserApplicationDirectory))
455 Directory.CreateDirectory(Constants.UserApplicationDirectory);
456  
15 office 457 var deserializationResult =
30 office 458 await Serialization.Deserialize<Servers.Servers>(Constants.ServersFile,
459 "urn:winify-servers-schema", "Servers.xsd", CancellationToken.None);
4 office 460  
461 switch (deserializationResult)
462 {
30 office 463 case SerializationSuccess<Servers.Servers> serializationSuccess:
15 office 464 // Decrypt password.
465 var deviceId = Miscellaneous.GetMachineGuid();
30 office 466 var @protected = new Servers.Servers
15 office 467 {
468 Server = new BindingListWithCollectionChanged<Server>()
469 };
470 foreach (var server in serializationSuccess.Result.Server)
471 {
472 var unarmored = Convert.FromBase64String(server.Password);
72 office 473 byte[] decrypted;
474 try
475 {
476 decrypted = await AES.Decrypt(unarmored, deviceId);
477 }
478 catch(Exception exception)
479 {
480 Log.Warning(exception, $"Could not decrypt password for server {server.Name} in configuration file.");
481 continue;
482 }
4 office 483  
72 office 484 var password = Encoding.UTF8.GetString(decrypted);
485  
486 @protected.Server.Add(new Server(server.Name, server.Url, server.Username, password));
15 office 487 }
488  
489 return @protected;
490  
21 office 491 case SerializationFailure serializationFailure:
492 Log.Warning(serializationFailure.Exception, "Unable to load servers.");
30 office 493 return new Servers.Servers();
21 office 494  
4 office 495 default:
30 office 496 return new Servers.Servers();
4 office 497 }
498 }
499  
500 #endregion
1 office 501 }
502 }