Winify – Blame information for rev 73

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