Winify – Blame information for rev 75

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