diff ServerMonitor/Forms/ServerSummaryForm.cs @ 16:7626b099aefd

More comments.
author Brad Greco <brad@bgreco.net>
date Tue, 30 Apr 2019 20:40:58 -0400
parents 75ca86e0862c
children b3128fe10d57
line wrap: on
line diff
--- a/ServerMonitor/Forms/ServerSummaryForm.cs	Mon Apr 22 21:11:27 2019 -0400
+++ b/ServerMonitor/Forms/ServerSummaryForm.cs	Tue Apr 30 20:40:58 2019 -0400
@@ -1,6 +1,5 @@
 using NAppUpdate.Framework;
 using NAppUpdate.Framework.Sources;
-using NAppUpdate.Framework.Tasks;
 using ServerMonitorApp.Properties;
 using System;
 using System.Collections.Generic;
@@ -8,11 +7,11 @@
 using System.Data;
 using System.Drawing;
 using System.Linq;
-using System.Text;
 using System.Windows.Forms;
 
 namespace ServerMonitorApp
 {
+    /// <summary>Main application form that shows an overview of all servers.</summary>
     public partial class ServerSummaryForm : Form
     {
         private readonly Dictionary<Server, ServerForm> serverForms = new Dictionary<Server, ServerForm>();
@@ -23,17 +22,135 @@
             InitializeComponent();
         }
 
+        private void ServerSummaryForm_Load(object sender, EventArgs e)
+        {
+            // Restore the window size from the previous session.
+            Size size = Settings.Default.SummaryFormSize;
+            if (size.Height > 0 && size.Width > 0)
+                Size = size;
+
+            // Resize the images in buttons to fit the button size.
+            Helpers.FormatImageButton(NewServerButton);
+            Helpers.FormatImageButton(SettingsButton);
+            // Create the global server monitor object.
+            monitor = new ServerMonitor(this);
+            // Load the server configuration file.
+            while (true)
+            {
+                try
+                {
+                    // If the configuration file is loaded successfully, proceed.
+                    monitor.LoadServers();
+                    break;
+                }
+                catch (Exception ex)
+                {
+                    // If there was an error loading the config file, show it.
+                    DialogResult result = MessageBox.Show("Could not load servers. Please fix or delete the file " + monitor.ConfigFile + Environment.NewLine + Environment.NewLine
+                        + "Error details:" + Environment.NewLine + ex.GetAllMessages(),
+                        "Error loading servers", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error);
+                    // If the error message was cancelled, exit. Otherwise, retry by continuing the loop.
+                    if (result == DialogResult.Cancel)
+                    {
+                        Environment.Exit(1);
+                    }
+                }
+            }
+            // Listen to server monitor events.
+            monitor.CheckStatusChanged += Monitor_CheckStatusChanged;
+            // Show the servers.
+            RefreshDisplay();
+            // If any servers have encrypted private keys, attempt to open them immediately
+            // rather than interrupting the user later when they are first used.
+            CollectPrivateKeyPasswords();
+            CheckForUpdate();
+        }
+
+        /// <summary>Shows a form to edit or create a server.</summary>
+        /// <param name="server">The server to edit. If null, a new server will be created.</param>
+        /// <param name="activate">Whether the server form should be activated.</param>
+        /// <returns>The created or existing server for for the server.</returns>
+        private ServerForm ShowServerForm(Server server, bool activate = true)
+        {
+            bool isNewServer = false;
+            if (server == null)
+            {
+                // Create a new server if none was given.
+                server = new Server();
+                // The server is added to the server monitor immediately so that
+                // checks can be created and run. If the server was created by
+                // mistake, it will automatically be removed when the form is closed
+                // as long as no information was entered into the form.
+                monitor.AddServer(server);
+                isNewServer = true;
+            }
+            if (serverForms.TryGetValue(server, out ServerForm form))
+            {
+                // If the server form is already open, just activate it if requested.
+                if (activate)
+                    form.Activate();
+            }
+            else
+            {
+                // Open a new server form for the server.
+                form = new ServerForm(monitor, server, isNewServer);
+                // Keep track of the form so it can be activated later if the server is clicked again.
+                serverForms[server] = form;
+                form.FormClosing += ServerForm_FormClosing;
+                form.Show(activate);
+            }
+            return form;
+        }
+
+        /// <summary>Refreshes the server list with the server monitor data.</summary>
+        private void RefreshDisplay()
+        {
+            // Delete all server controls and recreate them.
+            ServerPanel.Controls.Clear();
+            foreach (Server server in monitor.Servers)
+            {
+                // Subscribe to server events.
+                server.EnabledChanged -= Server_EnabledChanged;
+                server.EnabledChanged += Server_EnabledChanged;
+                // Create a server control and add it to the panel.
+                ServerSummaryControl control = new ServerSummaryControl(server);
+                control.ContextMenuStrip = ServerContextMenu;
+                control.Click += ServerSummaryControl_Click;
+                ServerPanel.Controls.Add(control);
+            }
+            // Refresh the form icon that depends on the status of all servers.
+            UpdateIcon();
+        }
+
+        /// <summary>Refreshes a single server control.</summary>
+        /// <param name="server">The server to refresh.</param>
+        private void RefreshServer(Server server)
+        {
+            ServerPanel.Controls.Cast<ServerSummaryControl>().FirstOrDefault(c => c.Server == server).Refresh();
+            // The server's status might have changed, so refresh the form icon.
+            UpdateIcon();
+        }
+
+        /// <summary>Flashes the taskbar button for a server form.</summary>
+        /// <param name="check">The check that needs attention.</param>
         public void AlertServerForm(Check check)
         {
-            bool existingForm = serverForms.ContainsKey(check.Server);
+            // Show the form, but don't activate it since the user did not initiate this event.
             ServerForm form = ShowServerForm(check.Server, false);
+            // Flash the taskbar button.
             Win32Helpers.FlashWindowEx(form);
-            if (!existingForm)
+            // If the form was not already open, focus the Log tab and display
+            // only the log entries for this check. Do not do this if the form
+            // was already open since the user might be in the middle of doing
+            // something with it.
+            if (!serverForms.ContainsKey(check.Server))
             {
                 form.ShowLog(check);
             }
         }
 
+        /// <summary>Shows a balloon popup with the results of a failed check.</summary>
+        /// <param name="check">The check that failed.</param>
         public void ShowBalloon(CheckResult result)
         {
             string title = string.Format("{0}: {1} failed", result.Check.Server.Name, result.Check.Name);
@@ -41,86 +158,16 @@
             NotifyIcon.ShowBalloonTip(30000, title, result.Message, GetToolTipIcon(result.CheckStatus));
         }
 
-        private void ServerSummaryForm_Load(object sender, EventArgs e)
-        {
-            Size size = Settings.Default.SummaryFormSize;
-            if (size.Height > 0 && size.Width > 0)
-                Size = size;
-
-            Helpers.FormatImageButton(NewServerButton);
-            Helpers.FormatImageButton(SettingsButton);
-            monitor = new ServerMonitor(this);
-            while (true)
-            {
-                try
-                {
-                    monitor.LoadServers();
-                    break;
-                }
-                catch (Exception ex)
-                {
-                    DialogResult result = MessageBox.Show("Could not load servers. Please fix or delete the file " + monitor.ConfigFile + Environment.NewLine + Environment.NewLine
-                        + "Error details:" + Environment.NewLine + ex.GetAllMessages(),
-                        "Error loading servers", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error);
-                    if (result == DialogResult.Cancel)
-                    {
-                        Environment.Exit(1);
-                    }
-                }
-            }
-            monitor.CheckStatusChanged += Monitor_CheckStatusChanged;
-            RefreshDisplay();
-            CollectPrivateKeyPasswords();
-            CheckForUpdate();
-        }
-
-        private ServerForm ShowServerForm(Server server, bool activate = true)
-        {
-            bool isNewServer = false;
-            if (server == null)
-            {
-                server = new Server();
-                monitor.AddServer(server);
-                isNewServer = true;
-            }
-            if (serverForms.TryGetValue(server, out ServerForm form))
-            {
-                if (activate)
-                    form.Activate();
-            }
-            else
-            {
-                form = new ServerForm(monitor, server, isNewServer);
-                serverForms[server] = form;
-                form.FormClosing += ServerForm_FormClosing;
-                form.Show(activate);
-            }
-            return form;
-        }
-
-        private void RefreshDisplay()
-        {
-            ServerPanel.Controls.Clear();
-            foreach (Server server in monitor.Servers)
-            {
-                server.EnabledChanged -= Server_EnabledChanged;
-                server.EnabledChanged += Server_EnabledChanged;
-                ServerSummaryControl control = new ServerSummaryControl(server);
-                control.ContextMenuStrip = ServerContextMenu;
-                control.Click += ServerSummaryControl_Click;
-                ServerPanel.Controls.Add(control);
-            }
-            UpdateIcon();
-        }
-
-        private void RefreshServer(Server server)
-        {
-            ServerPanel.Controls.Cast<ServerSummaryControl>().FirstOrDefault(c => c.Server == server).Refresh();
-            UpdateIcon();
-        }
-
+        /// <summary>Updates the form icon to reflect a summary of the status of all servers.</summary>
         private void UpdateIcon()
         {
+            // The status for the summary icon is the most severe status of all servers.
+            // When performing the comparison:
+            //  - Enabled servers use their current status.
+            //  - If a server is disabled due to a locked private key, report a warning.
+            //    Otherwise, report success to effectively ignore it.
+            // The integer value of the CheckStatus enum increases with the severity,
+            // so the maximum value of all servers gives the most severe status.
             CheckStatus status = monitor.Servers
                 .Select(s => s.Enabled
                     ? s.Status
@@ -131,23 +178,31 @@
             NotifyIcon.Icon = Icon;
         }
 
+        /// <summary>Prompts the user for the passwords to open all encrypted private keys.</summary>
         private void CollectPrivateKeyPasswords()
         {
+            // List of paths to keyfiles.
             List<string> triedKeys = new List<string>();
             foreach (Server server in monitor.Servers)
             {
+                // If the same private key is used for multiple servers, don't prompt
+                // the user multiple times to open the same keyfile.
                 if (triedKeys.Contains(server.KeyFile))
                     continue;
+                // Show the prompt.
                 ServerForm.OpenPrivateKey(monitor, server, this);
+                // Keep track of the keyfile so we don't needlessly ask again.
                 triedKeys.Add(server.KeyFile);
             }
         }
 
+        /// <summary>Refreshes a server control when the server state changes.</summary>
         private void Server_EnabledChanged(object sender, EventArgs e)
         {
             RefreshServer((Server)sender);
         }
 
+        /// <summary>Refreshes a server control when the server status might have changed.</summary>
         private void Monitor_CheckStatusChanged(object sender, CheckStatusChangedEventArgs e)
         {
             if (e.CheckResult != null)
@@ -156,6 +211,8 @@
             }
         }
 
+        /// <summary>Gets a Windows tooltip icon based on the severity of the message.</summary>
+        /// <param name="status">The status of the check that will be reported in the balloon tip.</param>
         private ToolTipIcon GetToolTipIcon(CheckStatus status)
         {
             switch (status)
@@ -167,17 +224,23 @@
             }
         }
 
+        /// <summary>Shows a server form when a server control is clicked.</summary>
         private void ServerSummaryControl_Click(object sender, EventArgs e)
         {
             ShowServerForm(((ServerSummaryControl)sender).Server);
         }
 
+        /// <summary>Handles the closing of a server form.</summary>
         private void ServerForm_FormClosing(object sender, FormClosingEventArgs e)
         {
             ServerForm form = (ServerForm)sender;
             form.FormClosing -= ServerForm_FormClosing;
             Server server = form.Server;
+            // Remove the closed form from the list of open forms.
             serverForms.Remove(form.Server);
+            // If there is no user data associated with the server, it can be deleted.
+            // This usually happens when the New Server button is clicked and the server form
+            // is closed without entering any information.
             if (server.IsEmpty())
             {
                 monitor.DeleteServer(server);
@@ -185,11 +248,14 @@
             RefreshDisplay();
         }
 
+        /// <summary>Shows a server form to create a new server.</summary>
         private void NewServerButton_Click(object sender, EventArgs e)
         {
             ShowServerForm(null);
         }
 
+        /// <summary>Hides the main form instead of exiting the application based on user preferences.</summary>
+        /// <remarks>Allows the monitor to run in the background without being shown in the taskbar.</remarks>
         private void ServerSummaryForm_FormClosing(object sender, FormClosingEventArgs e)
         {
             if ((e.CloseReason == CloseReason.None || e.CloseReason == CloseReason.UserClosing) && Settings.Default.HideToNotificationArea)
@@ -199,11 +265,13 @@
             }
         }
 
+        /// <summary>Shows the settings form.</summary>
         private void SettingsButton_Click(object sender, EventArgs e)
         {
             new SettingsForm().Show();
         }
 
+        /// <summary>Shows the details of a failed check when the balloon notification is clicked.</summary>
         private void NotifyIcon_BalloonTipClicked(object sender, EventArgs e)
         {
             CheckResult result = (CheckResult)(sender as NotifyIcon).Tag;
@@ -212,17 +280,21 @@
             form.WindowState = FormWindowState.Normal;
         }
 
+        /// <summary>Handles the server context menu.</summary>
         private void ServerContextMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
         {
             Server server = GetClickedServer((ContextMenuStrip)e.ClickedItem.Owner);
             if (e.ClickedItem == DeleteServerMenuItem)
             {
+                // Close the menu immediately so it doesn't stay open while the messagebox is shown.
                 ServerContextMenu.Close();
+                // Show the server delete confirmation dialog. No option to not ask again
+                // since it's a rare and very destructive event.
                 DialogResult result = MessageBox.Show(
                     string.Format("The server \"{0}\" and its {1} {2} will be deleted.", server.Name, server.Checks.Count, server.Checks.Count == 1 ? "check" : "checks"),
                     "Delete server",
                     MessageBoxButtons.OKCancel,
-                    MessageBoxIcon.Warning  );
+                    MessageBoxIcon.Warning);
                 if (result == DialogResult.OK)
                 {
                     monitor.DeleteServer(server);
@@ -234,7 +306,10 @@
                 bool enable = ToggleEnableServerMenuItem.Text == "Enable";
                 if (enable)
                 {
+                    // Close the menu immediately so it doesn't stay open while the messagebox is shown.
                     ServerContextMenu.Close();
+                    // Attempt to open the private key for the server immediately since it has not
+                    // been opened yet.
                     ServerForm.OpenPrivateKey(monitor, server, this);
                 }
                 server.Enabled = enable;
@@ -242,23 +317,31 @@
             }
         }
 
+        /// <summary>Activates the appropriate Enable/Disable menu option based on the server's current state.</summary>
         private void ServerContextMenu_Opening(object sender, CancelEventArgs e)
         {
             Server server = GetClickedServer((ContextMenuStrip)sender);
             ToggleEnableServerMenuItem.Text = server.Enabled ? "Disable" : "Enable";
         }
 
+        /// <summary>Gets the server corresponding to a server context menu.</summary>
         private Server GetClickedServer(ContextMenuStrip menu)
         {
             return ((ServerSummaryControl)menu.SourceControl).Server;
         }
 
+        /// <summary>Saves the window size after it is resized so it can be restored next time the program is run.</summary>
         private void ServerSummaryForm_ResizeEnd(object sender, EventArgs e)
         {
             Settings.Default.SummaryFormSize = Size;
             Settings.Default.Save();
         }
 
+        /// <summary>Shows the main form when the WM_SHOWMONITOR message is received.</summary>
+        /// <remarks>
+        /// This is used to make this a single-instance application. When a second copy of the program
+        /// is launched, it sends this message to activate the first copy and then exits.
+        /// </remarks>
         protected override void WndProc(ref Message m)
         {
             if (m.Msg == Win32Helpers.WM_SHOWMONITOR)
@@ -266,6 +349,7 @@
             base.WndProc(ref m);
         }
 
+        /// <summary>Handles clicks on the notification area icon.</summary>
         private void NotifyIcon_MouseClick(object sender, MouseEventArgs e)
         {
             if (e.Button == MouseButtons.Left)
@@ -274,16 +358,20 @@
                 NotificationIconMenu.Show();
         }
 
+        /// <summary>Shows the window.</summary>
         private void ShowWindow()
         {
             if (WindowState == FormWindowState.Minimized)
                 WindowState = FormWindowState.Normal;
+            // Do various things to try to get this window on top.
+            // We only do this as a result of user input, so it's ok. ;)
             Show();
             TopMost = true;
             TopMost = false;
             Activate();
         }
 
+        /// <summary>Handles clicks on the notification icon menu.</summary>
         private void NotificationIconMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
         {
             if (e.ClickedItem == ShowServerMonitorMenuItem)
@@ -292,27 +380,32 @@
                 Application.Exit();
         }
 
+        /// <summary>Begins checking for program updates in the background.</summary>
         private void CheckForUpdate()
         {
-            //System.Threading.Thread.Sleep(5000);
             UpdateManager manager = UpdateManager.Instance;
+            // Make the update manager happy if the program was just restarted to apply an update.
             manager.ReinstateIfRestarted();
             manager.UpdateSource = new SimpleWebSource("https://www.bgreco.net/test/servermonitor.xml");
             if (manager.State == UpdateManager.UpdateProcessState.NotChecked)
                 manager.BeginCheckForUpdates(CheckForUpdatesCallback, null);
         }
 
+        /// <summary>Callback after the program update check completes.</summary>
         private void CheckForUpdatesCallback(IAsyncResult result)
         {
             UpdateManager manager = UpdateManager.Instance;
             if (manager.UpdatesAvailable > 0)
             {
+                // Extract the new version number from the result.
                 GetUpdateInfo(out string version, out string _);
+                // If the user has not chosen to ignore this update, show a notification.
                 if (Settings.Default.IgnoreUpdate != version)
                     Invoke((MethodInvoker)(() => UpdatePanel.Show()));
             }
         }
 
+        /// <summary>Applies the program updates.</summary>
         private void PrepareUpdatesCallback(IAsyncResult result)
         {
             UpdateManager manager = UpdateManager.Instance;
@@ -320,8 +413,10 @@
             manager.ApplyUpdates(true);
         }
 
+        /// <summary>Shows information about a program update when the notification is clicked.</summary>
         private void UpdateLabel_Click(object sender, EventArgs e)
         {
+            // Extract the update information from the update manager result.
             GetUpdateInfo(out string version, out string changeMessage);
             string message = "Server Monitor version {0} is available for download." + Environment.NewLine
                 + Environment.NewLine
@@ -332,20 +427,25 @@
             using (UpdateDialog dialog = new UpdateDialog { Message = string.Format(message, version, changeMessage) })
             {
                 DialogResult result = dialog.ShowDialog();
+                // If the user declined the update and asked not to be notified again,
+                // save the preference so they will not be asked again for this version.
                 if (dialog.Checked && result == DialogResult.Cancel)
                 {
                     Settings.Default.IgnoreUpdate = version;
                     Settings.Default.Save();
                     UpdatePanel.Hide();
                 }
+                // If Yes was not chosen, do not apply the update.
                 if (result != DialogResult.OK)
                     return;
             }
             UpdateManager.Instance.BeginPrepareUpdates(PrepareUpdatesCallback, null);
         }
 
+        /// <summary>Extracts the update information from the update manager result.</summary>
         private void GetUpdateInfo(out string version, out string changeMessage)
         {
+            // The update description is in the form {version}:{change message}.
             string[] parts = UpdateManager.Instance.Tasks.First().Description.Split(new char[] { ':' }, 2);
             version = parts[0];
             changeMessage = parts[1];