HamBook – Rev 5

Subversion Repositories:
Rev:
using HamBook.Radios;
using HamBook.Utilities;
using HamBook.Utilities.Serialization;
using NetSparkleUpdater.Enums;
using NetSparkleUpdater.SignatureVerifiers;
using NetSparkleUpdater.UI.WinForms;
using NetSparkleUpdater;
using Serilog;
using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.IO.Ports;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using HamBook.Properties;
using HamBook.Radios.Generic;
using PowerState = HamBook.Radios.Generic.PowerState;
using System.Media;

namespace HamBook
{
    public partial class Form1 : Form
    {
        private ScheduledContinuation _changedConfigurationContinuation;
        private Configuration.Configuration Configuration { get; set; }
        private SerialPort _serialPort;
        private LogMemorySink _memorySink;
        private ViewLogsForm _viewLogsForm;
        private AboutForm _aboutForm;
        private SettingsForm _settingsForm;
        private SparkleUpdater _sparkle;
        private readonly CancellationToken _cancellationToken;
        private readonly CancellationTokenSource _cancellationTokenSource;
        private CatAssemblies _catAssemblies;
        private CancellationTokenSource _scanningCancellationTokenSource;
        private CancellationToken _scanningCancellationToken;
        private BandScan _bandScan;

        public bool MemorySinkEnabled { get; set; }

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

            _changedConfigurationContinuation = new ScheduledContinuation();

        }

        public Form1(Mutex mutex) : this()
        {
            InitializeComponent();

            _memorySink = new LogMemorySink();

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

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

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

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

            // Set up serial connection.
            _serialPort = new SerialPort(Configuration.Port, Configuration.Speed, Configuration.Parity, Configuration.DataBits, Configuration.StopBits);
            //TODO: move to settings
            //_serialPort.ReadTimeout = TimeSpan.FromSeconds(1).Milliseconds;

            _catAssemblies = new CatAssemblies(_serialPort, Configuration.Radio);
            _catAssemblies.CatSet<InformationState>("AI", new object[] { InformationState.OFF });

            try
            {
                switch (await _catAssemblies.CatRead<PowerState>("PS", new object[] { }, _cancellationToken))
                {
                    case PowerState.ON:
                        Initialize();
                        break;
                }
            }
            catch(Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_power_state);
            }
        }

        private void quitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }

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

            _viewLogsForm = new ViewLogsForm(this, _memorySink, _cancellationToken);
            _viewLogsForm.Closing += ViewLogsForm_Closing;
            _viewLogsForm.Show();
        }

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

            _viewLogsForm.Closing -= ViewLogsForm_Closing;
            _viewLogsForm.Close();
            _viewLogsForm = null;
        }

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

            _aboutForm = new AboutForm(_cancellationToken);
            _aboutForm.Closing += AboutForm_Closing;
            _aboutForm.Show();
        }

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

            _aboutForm.Dispose();
            _aboutForm = null;
        }

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

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

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

            if(_settingsForm.SaveOnClose)
            {
                // Commit the configuration.
                _changedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1),
                    async () => { 
                        await SaveConfiguration();

                        Miscellaneous.LaunchOnBootSet(Configuration.LaunchOnBoot);

                        _serialPort.Dispose();
                        _serialPort = new SerialPort(Configuration.Port, Configuration.Speed, Configuration.Parity, Configuration.DataBits, Configuration.StopBits);

                    }, _cancellationToken);
            }

            _settingsForm.Dispose();
            _settingsForm = null;
        }

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

        private void Initialize()
        {
            try
            {
                var mode = _catAssemblies.CatRead<RadioMode>("MD", new object[] { });
                toolStripComboBox1.Text = mode;
            }
            catch(Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_radio_mode);
            }

            try
            {
                var fa = _catAssemblies.CatRead<int>("FA", new object[] { });
                scrollableToolStripComboBox1.Text = $"{fa}";
            }
            catch(Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_VFO_A);
            }

            try
            {
                var fb = _catAssemblies.CatRead<int>("FB", new object[] { });
                scrollableToolStripComboBox2.Text = $"{fb}";
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_VFO_B);
            }


        }

        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 == NetSparkleUpdater.Enums.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.HamBook, MessageBoxButtons.OK,
                MessageBoxIcon.Asterisk,
                MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly, false);
        }

        private void toolStripComboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            var toolStripComboBox = (ToolStripComboBox)sender;
            if(RadioMode.TryParse(toolStripComboBox.Text, out var radioMode))
            {
                try
                {
                    _catAssemblies.CatSet<RadioMode>("MD", new object[] { radioMode });
                }
                catch (Exception exception)
                {
                    Log.Error(exception, Resources.Failed_to_set_radio_mode, radioMode);
                }
            }
        }

        private async void onToolStripMenuItem_Click(object sender, EventArgs e)
        {
            try
            {
                await _catAssemblies.CatSet<PowerState>("PS", new object[] { PowerState.ON }, _cancellationToken);
                Initialize();
            } 
            catch(Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_set_power_state);
            }
        }

        private async void offToolStripMenuItem_Click(object sender, EventArgs e)
        {
            try
            {
                await _catAssemblies.CatSet<PowerState>("PS", new object[] { PowerState.OFF }, _cancellationToken);
            } 
            catch(Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_set_power_state);
            }
        }

        private void toolStripComboBox2_MouseWheel(object sender, MouseEventArgs e)
        {
            using (var soundPlayer = new SoundPlayer(Assembly.GetExecutingAssembly().GetManifestResourceStream("HamBook.Effects.pot.wav")))
            {
                var toolStripComboBox = (ToolStripComboBox)sender;
                if (int.TryParse(toolStripComboBox.Text, out var frequency))
                {
                    switch (Math.Sign(e.Delta))
                    {
                        case -1:
                            frequency = frequency - 100;
                            break;
                        case 1:
                            frequency = frequency + 100;
                            break;
                    }

                    soundPlayer.Play();

                    try
                    {
                        _catAssemblies.CatSet<int>("FA", new object[] { frequency });
                        toolStripComboBox.Text = $"{frequency}";
                    }
                    catch (Exception exception)
                    {
                        Log.Error(exception, Resources.Failed_to_set_VFO_A_frequency);
                    }
                }
            }
        }

        private void scrollableToolStripComboBox1_MouseWheel(object sender, MouseEventArgs e)
        {
            using (var soundPlayer = new SoundPlayer(Assembly.GetExecutingAssembly().GetManifestResourceStream("HamBook.Effects.pot.wav")))
            {
                var toolStripComboBox = (ToolStripComboBox)sender;
                if (int.TryParse(toolStripComboBox.Text, out var frequency))
                {
                    switch (Math.Sign(e.Delta))
                    {
                        case -1:
                            frequency = frequency - 100;
                            break;
                        case 1:
                            frequency = frequency + 100;
                            break;
                    }

                    try
                    {
                        _catAssemblies.CatSet<int>("FB", new object[] { frequency });
                        toolStripComboBox.Text = $"{frequency}";
                    }
                    catch (Exception exception)
                    {
                        Log.Error(exception, Resources.Failed_to_set_VFO_B_frequency);
                    }
                }
            }
        }

        private void scrollableToolStripComboBox1_TextChanged(object sender, EventArgs e)
        {
            var toolStripComboBox = (ToolStripComboBox)sender;
            if (int.TryParse(toolStripComboBox.Text, out var frequency))
            {
                try
                {
                    _catAssemblies.CatSet<int>("FA", new object[] { frequency });
                }
                catch (Exception exception)
                {
                    Log.Error(exception, Resources.Failed_to_set_VFO_A_frequency);
                }
            }
        }

        private void scrollableToolStripComboBox2_TextChanged(object sender, EventArgs e)
        {
            var toolStripComboBox = (ToolStripComboBox)sender;
            if (int.TryParse(toolStripComboBox.Text, out var frequency))
            {
                try
                {
                    _catAssemblies.CatSet<int>("FB", new object[] { frequency });
                }
                catch (Exception exception)
                {
                    Log.Error(exception, Resources.Failed_to_set_VFO_B_frequency);
                }
            }
        }

        private void toolStripMenuItem1_Click(object sender, EventArgs e)
        {
            if (_bandScan == null)
            {
                return;
            }

            _bandScan.Stop();
            _bandScan = null;
        }

        private void scanToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (!(sender is ToolStripMenuItem toolStripMenuItem) ||
                !int.TryParse(toolStripMenuItem.Tag.ToString(), out var meters))
            {
                return;
            }

            if (!int.TryParse(scrollableToolStripComboBox3.Text, out var pause))
            {
                pause = 5;
            }

            if(!int.TryParse(scrollableToolStripComboBox4.Text, out var step))
            {
                step = 5000;
            }

            if (!Configuration.Definitions.TryGetBand(meters, out var band))
            {
                return;
            }

            if (_bandScan != null)
            {
                _bandScan.Stop();
            }

            _bandScan = new BandScan(_catAssemblies, band.Min, band.Max, _serialPort);
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
            _bandScan.Start(step, pause);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
        }

        private void modeToolStripMenuItem_DropDownOpened(object sender, EventArgs e)
        {
            Initialize();
        }
    }
}