HamBook – Rev 14

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;
using HamBook.Utilities.Controls;
using RJCP.IO.Ports;
using System.Text;
using System.Collections.ObjectModel;
using Configuration;
using System.Collections.Generic;

namespace HamBook
{
    public partial class Form1 : Form
    {
        private ScheduledContinuation _changedConfigurationContinuation;
        private Configuration.Configuration Configuration { get; set; }
        private SerialPortStream _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 BandScan _bandScan;
        private SpectrogramForm _spectrogramForm;

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

        private async void Form1_Load(object sender, EventArgs e)
        {
            _sparkle.StartLoop(true, true);

            Configuration = await LoadConfiguration();

            _serialPort = InitializeSerialPort(Configuration);

            _catAssemblies = InitializeAssemblies(_serialPort);

            try
            {
                switch (await _catAssemblies.CatReadAsync<PowerState>("PS", new object[] { }, _cancellationToken))
                {
                    case PowerState.ON:
                        Log.Information(Resources.Attempting_to_initialize_radio);
                        if(!await InitializeRadio())
                        {
                            return;
                        }

                        Log.Information(Resources.Initializing_GUI);
                        break;
                }
            }
            catch(Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_power_state);
            }
        }

        private async void quitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if(_bandScan != null)
            {
                await _bandScan.Stop();
                _bandScan = null;
            }

            // Save configuration on quit.
            await SaveConfiguration();

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

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


                        Miscellaneous.LaunchOnBootSet(Configuration.LaunchOnBoot);

                        _serialPort = InitializeSerialPort(Configuration);

                        _catAssemblies = InitializeAssemblies(_serialPort);

                        try
                        {
                            switch (await _catAssemblies.CatReadAsync<PowerState>("PS", new object[] { }, _cancellationToken))
                            {
                                case PowerState.ON:
                                    Log.Information(Resources.Attempting_to_initialize_radio);
                                    if (!await InitializeRadio())
                                    {
                                        return;
                                    }
                                    Log.Information(Resources.Initializing_GUI);
                                    break;
                            }
                        }
                        catch (Exception exception)
                        {
                            Log.Error(exception, Resources.Failed_to_read_power_state);
                        }

                    }, _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 async Task<bool> InitializeRadio()
        {
            try
            {
                await _catAssemblies.CatWriteAsync<InformationState>("AI", new object[] { InformationState.OFF }, _cancellationToken);

                return await _catAssemblies.CatReadAsync<bool>("ID", new object[] { }, _cancellationToken);
            }
            catch(Exception exception)
            {
                Log.Error(exception, Resources.Unable_to_initialize_radio);

                return false;
            }
        }

        private CatAssemblies InitializeAssemblies(SerialPortStream serialPort)
        {
            if(_catAssemblies != null)
            {
                _catAssemblies.Dispose();
                _catAssemblies = null;
            }

            return new CatAssemblies(serialPort, Configuration.Radio);
        }

        private SerialPortStream InitializeSerialPort(Configuration.Configuration configuration)
        {
            if (_serialPort != null)
            {
                if (_serialPort.IsOpen)
                {
                    _serialPort.Close();
                }
                _serialPort.Dispose();
                _serialPort = null;
            }

            // Set up serial connection.
            var serialPort = new SerialPortStream(configuration.Port, configuration.Speed, configuration.DataBits, configuration.Parity, configuration.StopBits);
            serialPort.ReadTimeout = configuration.SerialPortTimeout.Read;
            serialPort.WriteTimeout = configuration.SerialPortTimeout.Write;
            serialPort.Handshake = configuration.Handshake;
            serialPort.Encoding = Encoding.ASCII;

            Log.Information($"{Resources.Initialized_serial_port} {configuration.Port} {configuration.Speed} {configuration.Parity} {configuration.DataBits} {configuration.StopBits}");

            return serialPort;
        }

        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 async void toolStripComboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            var toolStripComboBox = (ToolStripComboBox)sender;
            if(RadioMode.TryParse(toolStripComboBox.Text, out var radioMode))
            {
                try
                {
                    await _catAssemblies.CatSetAsync<RadioMode>("MD", new object[] { radioMode }, _cancellationToken);
                }
                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.CatSetAsync<PowerState>("PS", new object[] { PowerState.ON }, _cancellationToken);
            } 
            catch(Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_set_power_state);
            }
        }

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

        private async void toolStripComboBox2_MouseWheel(object sender, MouseEventArgs e)
        {
            using (var soundPlayer = new SoundPlayer(Assembly.GetExecutingAssembly().GetManifestResourceStream("HamBook.Effects.pot.wav")))
            {
                var toolStripComboBox = (ScrollableToolStripComboBox)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
                    {
                        await _catAssemblies.CatSetAsync<int>("FA", new object[] { frequency }, _cancellationToken);
                        toolStripComboBox.Text = $"{frequency}";

                        Log.Information($"{Resources.Set_VFO_A_frequency} {frequency}Hz");
                    }
                    catch (Exception exception)
                    {
                        Log.Error(exception, Resources.Failed_to_set_VFO_A_frequency);
                    }
                }
            }
        }

        private async void scrollableToolStripComboBox1_MouseWheel(object sender, MouseEventArgs e)
        {
            using (var soundPlayer = new SoundPlayer(Assembly.GetExecutingAssembly().GetManifestResourceStream("HamBook.Effects.pot.wav")))
            {
                var toolStripComboBox = (ScrollableToolStripComboBox)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
                    {
                        await _catAssemblies.CatSetAsync<int>("FB", new object[] { frequency }, _cancellationToken);
                        toolStripComboBox.Text = $"{frequency}";

                        Log.Information($"{Resources.Set_VFO_B_frequency} {frequency}Hz");
                    }
                    catch (Exception exception)
                    {
                        Log.Error(exception, Resources.Failed_to_set_VFO_B_frequency);
                    }
                }
            }
        }

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

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

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

            await _bandScan.Stop();
            _bandScan = null;
        }

        private async 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)
            {
                await _bandScan.Stop();
                _bandScan = null;
            }

            _bandScan = new BandScan(_catAssemblies, band.Min, band.Max, _serialPort, Configuration);

            _bandScan.Start(step, pause, Configuration.ScanDetectPause);
        }

        private void modeToolStripMenuItem_DropDownOpened(object sender, EventArgs e)
        {
            Task.Delay(TimeSpan.FromSeconds(1), _cancellationToken).ContinueWith(async task =>
            {
                try
                {
                    var mode = await _catAssemblies.CatReadAsync<RadioMode>("MD", new object[] { }, _cancellationToken);

                    contextMenuStrip1.InvokeIfRequired(contextMenuStrip =>
                    {
                        toolStripComboBox1.Text = mode;
                    });
                }
                catch (Exception exception)
                {
                    Log.Error(exception, Resources.Failed_to_read_radio_mode);
                }

                try
                {
                    var fa = await _catAssemblies.CatReadAsync<int>("FA", new object[] { }, _cancellationToken);

                    contextMenuStrip1.InvokeIfRequired(contextMenuStrip =>
                    {
                        scrollableToolStripComboBox1.Text = $"{fa}";

                    });
                    
                }
                catch (Exception exception)
                {
                    Log.Error(exception, Resources.Failed_to_read_VFO_A);
                }

                try
                {
                    var fb = await _catAssemblies.CatReadAsync<int>("FB", new object[] { }, _cancellationToken);

                    contextMenuStrip1.InvokeIfRequired(contextMenuStrip =>
                    {
                        scrollableToolStripComboBox2.Text = $"{fb}";

                    });
                }
                catch (Exception exception)
                {
                    Log.Error(exception, Resources.Failed_to_read_VFO_B);
                }
            }, _cancellationToken);
        }

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

            _spectrogramForm = new SpectrogramForm(Configuration, _cancellationToken);
            _spectrogramForm.Closing += SpectrogramForm_Closing;
            _spectrogramForm.Show();
        }

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

            _spectrogramForm.Dispose();
            _spectrogramForm = null;

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