Winify – Rev 28

Subversion Repositories:
Rev:
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using AutoUpdaterDotNET;
using Serilog;
using Servers;
using ToastNotifications;
using Winify.Gotify;
using Winify.Servers.Serialization;
using Winify.Settings;
using Winify.Utilities;

namespace Winify
{
    public partial class Form1 : Form
    {
        #region Private Delegates, Events, Enums, Properties, Indexers and Fields

        private readonly TaskScheduler _uiTaskScheduler;

        private AboutForm _aboutForm;

        private ConcurrentBag<GotifyConnection> _gotifyConnections;

        private SettingsForm _settingsForm;

        #endregion

        #region Constructors, Destructors and Finalizers

        public Form1(Mutex mutex)
        {
            InitializeComponent();

            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .WriteTo.File(Path.Combine(Constants.UserApplicationDirectory, "Logs", $"{Constants.AssemblyName}.log"),
                    rollingInterval: RollingInterval.Day)
                .CreateLogger();

            // Upgrade settings if required.
            if (!ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).HasFile)
                if (Properties.Settings.Default.UpdateRequired)
                {
                    Properties.Settings.Default.Upgrade();
                    Properties.Settings.Default.Reload();

                    Properties.Settings.Default.UpdateRequired = false;
                    Properties.Settings.Default.Save();

                    mutex.ReleaseMutex();
                    Process.Start(Application.ExecutablePath);
                    Environment.Exit(0);
                }

            // Bind to settings changed event.
            Properties.Settings.Default.SettingsLoaded += Default_SettingsLoaded;
            Properties.Settings.Default.SettingsSaving += Default_SettingsSaving;
            Properties.Settings.Default.PropertyChanged += Default_PropertyChanged;

            // Store UI thread context.
            _uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();

            LoadServers().ContinueWith(async task =>
            {
                var restoredServers = await task;

                _gotifyConnections = new ConcurrentBag<GotifyConnection>();

                foreach (var server in restoredServers.Server)
                {
                    var gotifyConnection = new GotifyConnection(server);
                    gotifyConnection.GotifyNotification += GotifyConnection_GotifyNotification;
                    gotifyConnection.Start();
                    _gotifyConnections.Add(gotifyConnection);
                }
            });

            // Start application update.
            AutoUpdater.Start("http://winify.grimore.org/update/winify.xml");
        }

        /// <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)
            {
                Properties.Settings.Default.SettingsLoaded -= Default_SettingsLoaded;
                Properties.Settings.Default.SettingsSaving -= Default_SettingsSaving;
                Properties.Settings.Default.PropertyChanged -= Default_PropertyChanged;

                components.Dispose();
            }

            base.Dispose(disposing);
        }

        #endregion

        #region Event Handlers

        private static void Default_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Properties.Settings.Default.Save();
        }

        private static void Default_SettingsSaving(object sender, CancelEventArgs e)
        {
        }

        private static void Default_SettingsLoaded(object sender, SettingsLoadedEventArgs e)
        {
        }

        private async void SettingsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (_settingsForm != null) return;

            var servers = await LoadServers();
            var announcements = await LoadAnnouncements();

            _settingsForm = new SettingsForm(servers, announcements);
            _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 void GotifyConnection_GotifyNotification(object sender, GotifyNotificationEventArgs e)
        {
            Task.Factory.StartNew(async () =>
            {
                var announcements = await LoadAnnouncements();

                foreach (var announcement in announcements.Announcement)
                    if (announcement.AppId == e.Notification.AppId)
                    {
                        var configuredNotification = new Notification(
                            $"{e.Notification.Title} ({e.Notification.Server.Name}/{e.Notification.AppId})",
                            e.Notification.Message, announcement.LingerTime, e.Image,
                            FormAnimator.AnimationMethod.Slide,
                            FormAnimator.AnimationDirection.Up);

                        configuredNotification.Show();

                        return;
                    }

                var notification = new Notification(
                    $"{e.Notification.Title} ({e.Notification.Server.Name}/{e.Notification.AppId})",
                    e.Notification.Message, 5000, e.Image, FormAnimator.AnimationMethod.Slide,
                    FormAnimator.AnimationDirection.Up);

                notification.Show();
            }, CancellationToken.None, TaskCreationOptions.LongRunning, _uiTaskScheduler);
        }

        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();

            Environment.Exit(0);
        }

        private void UpdateToolStripMenuItem_Click(object sender, EventArgs e)
        {
            AutoUpdater.Start("http://winify.grimore.org/update/winify.xml");
        }

        #endregion

        #region Private Methods

        private static async Task SaveAnnouncements(Announcements.Announcements announcements)
        {
            switch (await ServersSerialization.Serialize(announcements, Constants.AnnouncementsFile, "Announcements",
                        "<!ATTLIST Announcements xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>"))
            {
                case SerializationFailure serializationFailure:
                    Log.Warning(serializationFailure.Exception, "Unable to serialize announcements.");
                    break;
            }
        }

        private static async Task SaveServers(global::Servers.Servers servers)
        {
            // Encrypt password for all servers.
            var deviceId = Miscellaneous.GetMachineGuid();
            var @protected = new global::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 ServersSerialization.Serialize(@protected, Constants.ServersFile, "Servers",
                        "<!ATTLIST Servers xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>"))
            {
                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 ServersSerialization.Deserialize<Announcements.Announcements>(Constants.AnnouncementsFile,
                    "urn:winify-announcements-schema", "Announcements.xsd");

            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<global::Servers.Servers> LoadServers()
        {
            if (!Directory.Exists(Constants.UserApplicationDirectory))
                Directory.CreateDirectory(Constants.UserApplicationDirectory);

            var deserializationResult =
                await ServersSerialization.Deserialize<global::Servers.Servers>(Constants.ServersFile,
                    "urn:winify-servers-schema", "Servers.xsd");

            switch (deserializationResult)
            {
                case SerializationSuccess<global::Servers.Servers> serializationSuccess:
                    // Decrypt password.
                    var deviceId = Miscellaneous.GetMachineGuid();
                    var @protected = new global::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 global::Servers.Servers();

                default:
                    return new global::Servers.Servers();
            }
        }

        #endregion
    }
}

Generated by GNU Enscript 1.6.5.90.