Zzz – Rev 5
?pathlinks?
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.