/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 |
} |
} |