Winify

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ 29  →  ?path2? @ 30
/trunk/Winify/MainForm.cs
@@ -0,0 +1,368 @@
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using NetSparkleUpdater;
using NetSparkleUpdater.Enums;
using NetSparkleUpdater.SignatureVerifiers;
using NetSparkleUpdater.UI.WinForms;
using Serilog;
using Servers;
using Toasts;
using Winify.Gotify;
using Winify.Settings;
using Winify.Utilities;
using Winify.Utilities.Serialization;
 
namespace Winify
{
public partial class MainForm : Form
{
#region Public Enums, Properties and Fields
 
public Configuration.Configuration Configuration { get; set; }
 
public ScheduledContinuation ChangedConfigurationContinuation { get; set; }
 
#endregion
 
#region Private Delegates, Events, Enums, Properties, Indexers and Fields
 
private AboutForm _aboutForm;
 
private ConcurrentBag<GotifyConnection> _gotifyConnections;
 
private SettingsForm _settingsForm;
 
private readonly SparkleUpdater _sparkle;
 
private readonly CancellationTokenSource _cancellationTokenSource;
 
private readonly CancellationToken _cancellationToken;
 
#endregion
 
#region Constructors, Destructors and Finalizers
 
public MainForm()
{
_cancellationTokenSource = new CancellationTokenSource();
_cancellationToken = _cancellationTokenSource.Token;
 
ChangedConfigurationContinuation = new ScheduledContinuation();
}
 
public MainForm(Mutex mutex) : this()
{
InitializeComponent();
 
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.File(Path.Combine(Constants.UserApplicationDirectory, "Logs", $"{Constants.AssemblyName}.log"),
rollingInterval: RollingInterval.Day)
.CreateLogger();
 
// Start application update.
var manifestModuleName = Assembly.GetEntryAssembly().ManifestModule.FullyQualifiedName;
var icon = Icon.ExtractAssociatedIcon(manifestModuleName);
 
_sparkle = new SparkleUpdater("https://winify.grimore.org/update/appcast.xml",
new Ed25519Checker(SecurityMode.Strict, "LonrgxVjSF0GnY4hzwlRJnLkaxnDn2ikdmOifILzLJY="))
{
UIFactory = new UIFactory(icon),
RelaunchAfterUpdate = true
};
_sparkle.StartLoop(true, true);
}
 
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && components != null) components.Dispose();
 
base.Dispose(disposing);
}
 
#endregion
 
#region Event Handlers
 
private async void MainForm_Load(object sender, EventArgs e)
{
Configuration = await LoadConfiguration();
 
var servers = await LoadServers();
_gotifyConnections = new ConcurrentBag<GotifyConnection>();
foreach (var server in servers.Server)
{
var gotifyConnection = new GotifyConnection(server);
gotifyConnection.GotifyNotification += GotifyConnection_GotifyNotification;
gotifyConnection.Start();
_gotifyConnections.Add(gotifyConnection);
}
}
 
private async void SettingsToolStripMenuItem_Click(object sender, EventArgs e)
{
if (_settingsForm == null)
{
var servers = await LoadServers();
var announcements = await LoadAnnouncements();
 
_settingsForm = new SettingsForm(this, servers, announcements, _cancellationToken);
_settingsForm.Save += SettingsForm_Save;
_settingsForm.Closing += SettingsForm_Closing;
_settingsForm.Show();
}
}
 
private async void SettingsForm_Save(object sender, SettingsSavedEventArgs e)
{
// Save the servers.
await Task.WhenAll(SaveServers(e.Servers), SaveAnnouncements(e.Announcements));
 
// Update connections to gotify servers.
while (_gotifyConnections.TryTake(out var gotifyConnection))
{
gotifyConnection.GotifyNotification -= GotifyConnection_GotifyNotification;
gotifyConnection.Stop();
gotifyConnection.Dispose();
gotifyConnection = null;
}
 
foreach (var server in e.Servers.Server)
{
var gotifyConnection = new GotifyConnection(server);
gotifyConnection.GotifyNotification += GotifyConnection_GotifyNotification;
gotifyConnection.Start();
_gotifyConnections.Add(gotifyConnection);
}
}
 
private async void GotifyConnection_GotifyNotification(object sender, GotifyNotificationEventArgs e)
{
var announcements = await LoadAnnouncements();
 
foreach (var announcement in announcements.Announcement)
if (announcement.AppId == e.Notification.AppId)
{
var configuredNotification = new ToastForm(
$"{e.Notification.Title} ({e.Notification.Server.Name}/{e.Notification.AppId})",
e.Notification.Message, announcement.LingerTime, e.Image);
 
configuredNotification.Show();
 
return;
}
 
var notification = new ToastForm(
$"{e.Notification.Title} ({e.Notification.Server.Name}/{e.Notification.AppId})",
e.Notification.Message, 5000, e.Image);
 
notification.Show();
}
 
private void SettingsForm_Closing(object sender, CancelEventArgs e)
{
if (_settingsForm == null) return;
 
_settingsForm.Save -= SettingsForm_Save;
_settingsForm.Closing -= SettingsForm_Closing;
_settingsForm.Dispose();
_settingsForm = null;
}
 
private void AboutToolStripMenuItem_Click(object sender, EventArgs e)
{
if (_aboutForm != null) return;
 
_aboutForm = new AboutForm();
_aboutForm.Closing += AboutForm_Closing;
_aboutForm.Show();
}
 
private void AboutForm_Closing(object sender, CancelEventArgs e)
{
if (_aboutForm == null) return;
 
_aboutForm.Closing -= AboutForm_Closing;
_aboutForm.Dispose();
_aboutForm = null;
}
 
private void QuitToolStripMenuItem_Click(object sender, EventArgs e)
{
Close();
}
 
private async void UpdateToolStripMenuItem_Click(object sender, EventArgs e)
{
// Manually check for updates, this will not show a ui
var result = await _sparkle.CheckForUpdatesQuietly();
if (result.Status == UpdateStatus.UpdateAvailable)
{
// if update(s) are found, then we have to trigger the UI to show it gracefully
_sparkle.ShowUpdateNeededUI();
return;
}
 
MessageBox.Show("No updates available at this time.", "Horizon", MessageBoxButtons.OK,
MessageBoxIcon.Asterisk,
MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly, false);
}
 
#endregion
 
#region Public Methods
 
public async Task SaveConfiguration()
{
if (!Directory.Exists(Constants.UserApplicationDirectory))
Directory.CreateDirectory(Constants.UserApplicationDirectory);
 
switch (await Serialization.Serialize(Configuration, Constants.ConfigurationFile, "Configuration",
"<!ATTLIST Configuration xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>",
CancellationToken.None))
{
case SerializationSuccess<Configuration.Configuration> _:
Log.Information("Serialized configuration.");
break;
case SerializationFailure serializationFailure:
Log.Warning(serializationFailure.Exception.Message, "Failed to serialize configuration.");
break;
}
}
 
public static async Task<Configuration.Configuration> LoadConfiguration()
{
if (!Directory.Exists(Constants.UserApplicationDirectory))
Directory.CreateDirectory(Constants.UserApplicationDirectory);
 
var deserializationResult =
await Serialization.Deserialize<Configuration.Configuration>(Constants.ConfigurationFile,
Constants.ConfigurationNamespace, Constants.ConfigurationXsd, CancellationToken.None);
 
switch (deserializationResult)
{
case SerializationSuccess<Configuration.Configuration> serializationSuccess:
return serializationSuccess.Result;
case SerializationFailure serializationFailure:
Log.Warning(serializationFailure.Exception, "Failed to load configuration.");
return new Configuration.Configuration();
default:
return new Configuration.Configuration();
}
}
 
#endregion
 
#region Private Methods
 
private static async Task SaveAnnouncements(Announcements.Announcements announcements)
{
switch (await Serialization.Serialize(announcements, Constants.AnnouncementsFile, "Announcements",
"<!ATTLIST Announcements xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>",
CancellationToken.None))
{
case SerializationFailure serializationFailure:
Log.Warning(serializationFailure.Exception, "Unable to serialize announcements.");
break;
}
}
 
private static async Task SaveServers(Servers.Servers servers)
{
// Encrypt password for all servers.
var deviceId = Miscellaneous.GetMachineGuid();
var @protected = new Servers.Servers
{
Server = new BindingListWithCollectionChanged<Server>()
};
foreach (var server in servers.Server)
{
var encrypted = AES.Encrypt(Encoding.UTF8.GetBytes(server.Password), deviceId);
var armored = Convert.ToBase64String(encrypted);
 
@protected.Server.Add(new Server(server.Name, server.Url, server.Username, armored));
}
 
switch (await Serialization.Serialize(@protected, Constants.ServersFile, "Servers",
"<!ATTLIST Servers xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>",
CancellationToken.None))
{
case SerializationFailure serializationFailure:
Log.Warning(serializationFailure.Exception, "Unable to serialize servers.");
break;
}
}
 
private static async Task<Announcements.Announcements> LoadAnnouncements()
{
if (!Directory.Exists(Constants.UserApplicationDirectory))
Directory.CreateDirectory(Constants.UserApplicationDirectory);
 
var deserializationResult =
await Serialization.Deserialize<Announcements.Announcements>(Constants.AnnouncementsFile,
"urn:winify-announcements-schema", "Announcements.xsd", CancellationToken.None);
 
switch (deserializationResult)
{
case SerializationSuccess<Announcements.Announcements> serializationSuccess:
return serializationSuccess.Result;
case SerializationFailure serializationFailure:
Log.Warning(serializationFailure.Exception, "Unable to load announcements.");
return new Announcements.Announcements();
default:
return new Announcements.Announcements();
}
}
 
private static async Task<Servers.Servers> LoadServers()
{
if (!Directory.Exists(Constants.UserApplicationDirectory))
Directory.CreateDirectory(Constants.UserApplicationDirectory);
 
var deserializationResult =
await Serialization.Deserialize<Servers.Servers>(Constants.ServersFile,
"urn:winify-servers-schema", "Servers.xsd", CancellationToken.None);
 
switch (deserializationResult)
{
case SerializationSuccess<Servers.Servers> serializationSuccess:
// Decrypt password.
var deviceId = Miscellaneous.GetMachineGuid();
var @protected = new Servers.Servers
{
Server = new BindingListWithCollectionChanged<Server>()
};
foreach (var server in serializationSuccess.Result.Server)
{
var unarmored = Convert.FromBase64String(server.Password);
var decrypted = Encoding.UTF8.GetString(AES.Decrypt(unarmored, deviceId));
 
@protected.Server.Add(new Server(server.Name, server.Url, server.Username, decrypted));
}
 
return @protected;
 
case SerializationFailure serializationFailure:
Log.Warning(serializationFailure.Exception, "Unable to load servers.");
return new Servers.Servers();
 
default:
return new Servers.Servers();
}
}
 
#endregion
}
}