Zzz – Rev 5

Subversion Repositories:
Rev:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Runtime;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Configuration;
using NetSparkleUpdater.Enums;
using NetSparkleUpdater.SignatureVerifiers;
using NetSparkleUpdater.UI.WinForms;
using NetSparkleUpdater;
using Serilog;
using Zzz.Action;
using Zzz.Clients;
using Zzz.Idle;
using Zzz.Properties;
using Zzz.Utilities;
using Zzz.Utilities.Serialization;
using System.Reflection;
using System.Net;
using MQTTnet.Client;
using MqttClient = Zzz.Clients.MqttClient;

namespace Zzz
{
    public partial class MainForm : Form
    {
        #region Public Enums, Properties and Fields

        public bool MemorySinkEnabled { get; set; }

        public Configuration.Configuration Configuration { get; set; }

        public ScheduledContinuation ChangedConfigurationContinuation { get; set; }

        #endregion

        #region Private Delegates, Events, Enums, Properties, Indexers and Fields

        private Idler _defaultIdler;

        private MqttClient _mqttClient;

        private readonly ActionTrigger _trigger;

        private AboutForm _aboutForm;

        private SettingsForm _settingsForm;

        private Idler _hibernateIdler;

        private LogMemorySink _memorySink;

        private readonly object _memorySinkLock;

        private LogViewForm _logViewForm;

        private SparkleUpdater _sparkle;

        private readonly CancellationToken _cancellationToken;

        private readonly CancellationTokenSource _cancellationTokenSource;

        #endregion

        #region Constructors, Destructors and Finalizers

        public MainForm(Mutex mutex) : this()
        {
            InitializeComponent();
            _memorySink = new LogMemorySink();
            _memorySinkLock = new object();

            lock (_memorySinkLock)
            {
                Log.Logger = new LoggerConfiguration()
                    .MinimumLevel.Debug()
                    .WriteTo.Conditional(condition => MemorySinkEnabled,
                        configureSink => configureSink.Sink(_memorySink))
                    .WriteTo.File(
                        Path.Combine(Constants.UserApplicationDirectory, "Logs", $"{Constants.AssemblyName}.log"),
                        rollingInterval: RollingInterval.Day)
                    .CreateLogger();
            }

            // Initialize the sleeper.
            _trigger = new ActionTrigger(Handle);
            _trigger.Action += Trigger_Action;

            // Start application update.
            var manifestModuleName = Assembly.GetEntryAssembly().ManifestModule.FullyQualifiedName;
            var icon = Icon.ExtractAssociatedIcon(manifestModuleName);

            _sparkle = new SparkleUpdater("https://zzz.grimore.org/update/appcast.xml",
                new Ed25519Checker(SecurityMode.Strict, "LonrgxVjSF0GnY4hzwlRJnLkaxnDn2ikdmOifILzLJY="))
            {
                UIFactory = new UIFactory(icon),
                RelaunchAfterUpdate = true,
                SecurityProtocolType = SecurityProtocolType.Tls12
            };
            _sparkle.StartLoop(true, true);
        }

        private MainForm()
        {
            _cancellationTokenSource = new CancellationTokenSource();
            _cancellationToken = _cancellationTokenSource.Token;

            ChangedConfigurationContinuation = new ScheduledContinuation();
        }

        /// <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?.Dispose();
            }

            base.Dispose(disposing);
        }

        #endregion

        #region Event Handlers

        private async void MainForm_Load(object sender, EventArgs e)
        {
            Configuration = await LoadConfiguration();

            // Initialize the idle timer.
            _defaultIdler = new Idler(Configuration, Resources.Default);
            if (Configuration.Enabled)
            {
                _defaultIdler.Start(TimeSpan.FromMinutes((int)Configuration.Timeout));
            }
            // Bind to the idle notification.
            _defaultIdler.Idle += DefaultIdler_Idle;
            _defaultIdler.IdleImminent += DefaultIdler_IdleImminent;

            _hibernateIdler = new Idler(Configuration, Resources.Hibernate);
            if (Configuration.HibernateEnabled)
            {
                _hibernateIdler.Start(TimeSpan.FromMinutes((int)Configuration.HibernateTimeout));
            }
            _hibernateIdler.Idle += HibernateIdler_Idle;
            _hibernateIdler.IdleImminent += HibernateIdler_IdleImminent;

            toolStripEnabledMenuItem.Checked = Configuration.Enabled;
            hibernateToolStripMenuItem.Checked = Configuration.HibernateEnabled;

            // Initialize MQTT client.
            _mqttClient = new MqttClient(Configuration);
            _mqttClient.MqttSubscribeSucceeded += MqttClient_MqttSubscribeSucceeded;
            _mqttClient.MqttSubscribeFailed += MqttClient_MqttSubscribeFailed;
            _mqttClient.MqttStateReceived += MqttClient_MqttStateReceived;
            _mqttClient.MqttActionReceived += MqttClient_MqttActionReceived;
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
            _mqttClient.Start();
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed

        }

        private void LogViewToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (_logViewForm != null)
            {
                return;
            }

            lock (_memorySinkLock)
            {
                _logViewForm = new LogViewForm(this, _memorySink, _memorySinkLock);
                _logViewForm.Closing += LogViewFormClosing;
                _logViewForm.Show();
            }
        }

        private void LogViewFormClosing(object sender, CancelEventArgs e)
        {
            if (_logViewForm == null)
            {
                return;
            }

            _logViewForm.Closing -= LogViewFormClosing;
            _logViewForm.Close();
            _logViewForm = null;
        }

        private async void Trigger_Action(object sender, ActionEventArgs e)
        {
            if (Configuration.MqttEnable)
            {
                await _mqttClient.Publish(e.Action);
            }
        }

        private void MqttClient_MqttActionReceived(object sender, MqttActionReceivedEventArgs e)
        {
            Log.Information($"MQTT action {e.Action} received.");

            _trigger.Execute(e.Action);
        }

        private void MqttClient_MqttStateReceived(object sender, MqttStateReceivedEventArgs e)
        {
            Log.Information($"MQTT state {e.ZzzState.State} received.");

            switch (e.ZzzState.State)
            {
                case State.State.Enabled:
                    Configuration.Enabled = true;

                    this.InvokeIfRequired(form =>
                    {
                        form.toolStripEnabledMenuItem.Checked = true;
                        form.toolStripEnabledMenuItem.CheckState = CheckState.Checked;

                        form.notifyIcon1.BalloonTipText =
                            Resources.Zzz_enabled;
                        form.notifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
                        form.notifyIcon1.BalloonTipTitle = Resources.Enabled;
                        form.notifyIcon1.ShowBalloonTip(TimeSpan.MaxValue.Milliseconds);
                    });
                    break;
                case State.State.Disabled:
                    Configuration.Enabled = false;

                    this.InvokeIfRequired(form =>
                    {
                        form.toolStripEnabledMenuItem.Checked = false;
                        form.toolStripEnabledMenuItem.CheckState = CheckState.Unchecked;

                        form.notifyIcon1.BalloonTipText =
                            Resources.Zzz_disabled;
                        form.notifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
                        form.notifyIcon1.BalloonTipTitle = Resources.Disabled;
                        form.notifyIcon1.ShowBalloonTip(TimeSpan.MaxValue.Milliseconds);
                    });
                    break;
            }
        }

        private void MqttClient_MqttSubscribeFailed(object sender, MqttClientSubscribeResultCode e)
        {
            Log.Warning(Resources.Unable_to_subscribe_to_MQTT_topic);
        }

        private void MqttClient_MqttSubscribeSucceeded(object sender, MqttClientSubscribeResultCode e)
        {
            Log.Information(Resources.Subscribed_to_MQTT_topic_and_waiting_for_messages);
        }

        private void DefaultIdler_IdleImminent(object sender, IdleImminentEventArgs e)
        {
            this.InvokeIfRequired(form =>
            {
                form.notifyIcon1.BalloonTipText =
                    Resources.Your_computer_is_becoming_idle_and_will_go_to_sleep_in_a_minute;
                form.notifyIcon1.BalloonTipIcon = ToolTipIcon.Warning;
                form.notifyIcon1.BalloonTipTitle = Resources.Sleeping_soon;
                form.notifyIcon1.ShowBalloonTip(TimeSpan.MaxValue.Milliseconds);
            });
        }

        private void DefaultIdler_Idle(object sender, IdleEventArgs e)
        {
            Log.Information(Resources.Sleeping);

            // Sleep!
            _trigger.Execute(new ZzzAction(Configuration.Action));
        }

        private void HibernateIdler_IdleImminent(object sender, IdleImminentEventArgs e)
        {
            this.InvokeIfRequired(form =>
            {
                form.notifyIcon1.BalloonTipText =
                    Resources.Your_computer_will_enter_hiberation;
                form.notifyIcon1.BalloonTipIcon = ToolTipIcon.Warning;
                form.notifyIcon1.BalloonTipTitle = Resources.Hibernating_soon;
                form.notifyIcon1.ShowBalloonTip(TimeSpan.MaxValue.Milliseconds);
            });
        }

        private void HibernateIdler_Idle(object sender, IdleEventArgs e)
        {
            Log.Information(Resources.Hibernating);

            _trigger.Execute(new ZzzAction(Action.Action.Hibernate));
        }

        private void OnContextMenuQuitClick(object sender, EventArgs e)
        {
            Environment.Exit(0);
        }

        private void OnContextMenuAboutClick(object sender, EventArgs e)
        {
            if (_aboutForm != null)
            {
                return;
            }

            // Show the about form.
            _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 ToolStripMenuEnabledMenuItem_CheckedChanged(object sender, EventArgs e)
        {
            Configuration.Enabled = ((ToolStripMenuItem) sender).Checked;
            
            switch(Configuration.Enabled)
            {
                case true:
                    if(!_defaultIdler.IsRunning)
                    {
                        _defaultIdler.Start(TimeSpan.FromMinutes((int)Configuration.HibernateTimeout));
                    }
                    break;
                default:
                    _defaultIdler.Stop();
                    break;
            }
        }

        private void HibernateToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
        {
            Configuration.HibernateEnabled = ((ToolStripMenuItem)sender).Checked;

            switch (Configuration.HibernateEnabled)
            {
                case true:
                    if (!_hibernateIdler.IsRunning)
                    {
                        _hibernateIdler.Start(TimeSpan.FromMinutes((int)Configuration.HibernateTimeout));
                    }
                    break;
                default:
                    _hibernateIdler.Stop();
                    break;
            }
        }

        private void OnNotifyIconMouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                return;
            }

            Task.Delay((int)Configuration.ClickActionDelay).ContinueWith(task =>
            {
                _trigger.Execute(new ZzzAction(Configuration.ActionDoubleClick));
            });
        }

        private void OnNotifyIconMouseClick(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                return;
            }

            Task.Delay((int)Configuration.ClickActionDelay).ContinueWith(task =>
            {
                _trigger.Execute(new ZzzAction(Configuration.ActionClick));
            });
        }

        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(Resources.No_updates_available_at_this_time, Resources.Zzz, MessageBoxButtons.OK,
                MessageBoxIcon.Asterisk,
                MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly, false);
        }

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

            _settingsForm = new SettingsForm(_mqttClient, Configuration);
            _settingsForm.Closing += SettingsForm_Closing;
            _settingsForm.Show();
        }

        private async void SettingsForm_Closing(object sender, CancelEventArgs e)
        {
            if (_settingsForm == null)
            {
                return;
            }

            _settingsForm.Closing -= SettingsForm_Closing;
            _settingsForm.Dispose();
            _settingsForm = null;

            // Commit the configuration.
            ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1),
                async () => { await SaveConfiguration(); }, _cancellationToken);

            Miscellaneous.LaunchOnBootSet(Configuration.LaunchOnBoot);

            // Update idle timer parameters.
            _defaultIdler.IdleTimeout = TimeSpan.FromMinutes((int)Configuration.Timeout);
            _hibernateIdler.IdleTimeout = TimeSpan.FromMinutes((int)Configuration.HibernateTimeout);

            // Restart MQTT client.
            try
            {
                await _mqttClient.Restart();
            }
            catch (Exception ex)
            {
                Log.Warning(ex, "Unable to restart MQTT client.");
            }
        }

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

Generated by GNU Enscript 1.6.5.90.