using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using JetBrains.Annotations;
using Kaleida.ServiceMonitor.Framework;
using Kaleida.ServiceMonitor.Model;
using Kaleida.ServiceMonitor.Model.Parsing;
using Kaleida.ServiceMonitor.Model.Runtime;
using log4net;
using System.Linq;
using Monitor = Kaleida.ServiceMonitor.Model.Monitor;
using Timer = System.Timers.Timer;
namespace Kaleida.ServiceMonitor.UI
{
public partial class MainForm : Form
{
private readonly Monitor monitor;
private readonly string initialScriptName;
private bool autoStart;
private bool windowIsActive = true;
private static readonly ILog log = LogManager.GetLogger(typeof (MainForm));
private readonly NotifyIcon trayIcon;
private readonly ContextMenu trayMenu;
private readonly Timer pollTimer = new Timer();
private bool updatingUI;
readonly IList<INotificationPanel> notificationPanels;
readonly ISet<object> naughtyList = new HashSet<object>();
private AboutControl aboutControl;
private readonly IList<IServiceMonExtension> operationWatchers;
public MainForm([CanBeNull]string scriptName = "")
{
var configuration = new FileBasedConfiguration();
monitor = new Monitor(configuration);
initialScriptName = scriptName ?? configuration.InitialScriptName ?? "";
Application.ThreadException += UnhandledExceptionHandler;
TryInitialiseFromConfig();
trayMenu = new ContextMenu();
trayMenu.MenuItems.Add("Exit", TrayIconMenuExitClicked);
trayIcon = new NotifyIcon {ContextMenu = trayMenu, Visible = true};
trayIcon.MouseUp += OnTrayIconClick;
InitializeComponent();
lblScriptSummary.Text = "";
workspaceControl.Workspace = monitor.Workspace;
workspaceControl.ScriptDirectoryPath = ServiceMon.ScriptPath;
monitor.ScriptCompilationComplete += ScriptCompilationComplete;
monitor.Workspace.CurrentScriptChanged += SelectedScriptChanged;
monitor.State.MonitorStarted += OnStartedMonitoring;
monitor.State.MonitorStopped += OnStoppedMonitoring;
monitor.OperationSuccessful += OperationSuccessful;
monitor.OperationFailed += OperationFailed;
monitor.ErrorsAcknowledged += ErrorsAcknowledged;
operationWatchers = BuildOperationWatchers();
notificationPanels = NotificationPanelFactory.BuildPanels();
AddNotificationTabPanels();
AddHelpTabPage();
}
private IList<IServiceMonExtension> BuildOperationWatchers()
{
var watchers = new List<IServiceMonExtension>
{
new ThresholdChangeEmailer(monitor),
new ScheduledSummaryEmailer(monitor)
};
return watchers;
}
private void AddNotificationTabPanels()
{
var notificationTabPages = notificationPanels.Select(BuildAndInitialiseNotificationPanel);
tabControl.TabPages.AddRange(notificationTabPages.Cast<TabPage>().ToArray());
}
private NotificationPanelTabPage BuildAndInitialiseNotificationPanel(INotificationPanel panel)
{
var page = new NotificationPanelTabPage(panel);
panel.Initialise(monitor);
return page;
}
private void AddHelpTabPage()
{
aboutControl = new AboutControl {Dock = DockStyle.Fill};
var tabPage = new TabPage("Help");
tabPage.Controls.Add(aboutControl);
tabControl.TabPages.Add(tabPage);
}
public void TryInitialiseFromConfig()
{
try
{
autoStart = monitor.Configuration.AutoStart;
}
catch (Exception exception)
{
MessageBox.Show(string.Format("Error Reading config: {0}", exception));
}
}
private void UnhandledExceptionHandler(object sender, ThreadExceptionEventArgs e)
{
log.Fatal(e.Exception);
MessageBox.Show(string.Format("ERROR: {0}\r\nSee log files for more information", e.Exception.Message), "An unexpected error occured", MessageBoxButtons.OK, MessageBoxIcon.Error);
Application.Exit();
}
protected override void OnLoad(EventArgs e)
{
ScriptParser.Initialise(ServiceMon.OperationsPath);
aboutControl.PopulateOperationsHelp(ScriptParser.GetRuntimeEnvironment());
UpdateWindowTitle();
Icon = ApplicationIcon.Application;
components = new Container();
components.Add(trayIcon);
pollTimer.Elapsed += PollTimerTick;
workspaceControl.LoadScriptsFromDisk();
workspaceControl.TrySelectScript(initialScriptName);
monitor.AcknowledgeErrors();
if (autoStart && monitor.State.HasSuccessfullyCompiledScript)
{
StartMonitoring();
}
else
{
StopMonitoring();
}
Visible = false;
ThreadPool.QueueUserWorkItem(i => CheckForNewVersion());
base.OnLoad(e);
}
private void CheckForNewVersion()
{
var versionCheck = new CodePlexWebVersionCheck();
var result = versionCheck.CheckLatestVersion();
BeginInvoke(new EventHandler(delegate
{
newVersionLink.Text = "";
if (!result.Success)
{
aboutControl.SetVersionCheckStatus("Could not check for new version", result.Exception.Message);
}
else
{
if (ServiceMon.VersionNumber == result.LatestVersionNumber)
{
aboutControl.SetVersionCheckStatus("This is the latest recommended version");
}
else if (ServiceMon.VersionNumber > result.LatestVersionNumber)
{
aboutControl.SetVersionCheckStatus("This is a pre-release version. The latest recommended version is v" + result.LatestVersionNumber);
}
else
{
var linkText = string.Format("Version '{0}' is available. Click to download", result.LatestVersionNumber);
newVersionLink.Text = linkText;
newVersionLink.Tag = result.LatestVersionUrl;
newVersionLink.Visible = true;
}
}
}));
}
private void UpdateWindowTitle()
{
var title = new StringBuilder();
title.AppendFormat("{0} - ", monitor.Workspace.CurrentScript.Name);
title.AppendFormat("ServiceMon v{0}", ServiceMon.VersionNumber);
Text = title.ToString();
}
private void OperationSuccessful(object sender, OperationSuccessfulEventArgs e)
{
SendMessageToExtensions(i => i.OnOperationSuccessful(e));
}
private void OperationFailed(object sender, OperationFailedEventArgs e)
{
SendMessageToExtensions(i => i.OnOperationFailed(e));
}
private void ErrorsAcknowledged(object sender, EventArgs e)
{
SendMessageToExtensions(i => i.OnErrorsAcknowledged());
}
private void SelectedScriptChanged(object sender, EventArgs e)
{
SendMessageToNotificationPanels(i => i.RefreshContent());
UpdateWindowTitle();
tabControl.SelectTab(tabPageScript);
}
private void SendMessageToExtensions(Action<IServiceMonExtension> message)
{
foreach (var watcher in operationWatchers)
{
if (naughtyList.Contains(watcher)) continue;
try
{
message(watcher);
}
catch (Exception exception)
{
naughtyList.Add(watcher);
log.Error(string.Format("OperationWatcher '{0}' threw an unhandled exception: {1}", watcher.GetType().Name, exception));
}
}
}
private void SendMessageToNotificationPanels(Action<INotificationPanel> message)
{
foreach (var notificationPanel in notificationPanels)
{
if (naughtyList.Contains(notificationPanel)) continue;
try
{
message(notificationPanel);
}
catch (Exception exception)
{
naughtyList.Add(notificationPanel);
log.Error(string.Format("Panel '{0}' threw an unhandled exception: {1}", notificationPanel.PanelName, exception));
notificationPanel.GetPanelControl().Controls.Clear();
var errorMessage = new StringBuilder();
errorMessage.AppendFormat("An unhandled Exception occured: {0}\r\n\r\n", exception.Message);
errorMessage.AppendFormat("This panel will not be sent new messages until ServiceMon is restarted\r\n");
errorMessage.AppendFormat("Please see logs for more information");
var errorLabel = new Label
{
AutoSize = true,
ForeColor = Color.Red,
Text = errorMessage.ToString()
};
notificationPanel.GetPanelControl().Controls.Add(errorLabel);
}
}
}
private void ScriptCompilationComplete(object sender, ScriptCompilationCompleteEventArgs args)
{
var compilation = args.ScriptCompilation;
lock (this)
{
if (compilation.Succeeded)
{
monitor.Statistics.Initialise(compilation.CompiledScript.Operations);
pollFrequencyControl.Value = compilation.CompiledScript.PollFrequency;
SendMessageToExtensions(i => i.ScriptCompilationComplete(args));
}
}
}
private void OnTrayIconClick(object o, EventArgs e)
{
ShowInTaskbar = true;
Visible = true;
WindowState = FormWindowState.Normal;
}
protected override void OnResize(EventArgs e)
{
if (WindowState == FormWindowState.Minimized)
{
ShowInTaskbar = false;
Visible = false;
}
else
{
base.OnResize(e);
}
}
private void TrayIconMenuExitClicked(object sender, EventArgs e)
{
Application.Exit();
}
private void StartMonitoring()
{
if (!monitor.State.CanBeginMonitoring)
return;
monitor.State.StartMonitoring();
if (tabControl.SelectedTab == tabPageScript)
{
TrySelectFirstNotificationPanel();
}
ThreadPool.QueueUserWorkItem(i => monitor.InvokeNextOperation());
}
private void TrySelectFirstNotificationPanel()
{
var firstPanel = tabControl.TabPages.Cast<TabPage>().FirstOrDefault(i => i is NotificationPanelTabPage);
if (firstPanel != null)
{
tabControl.SelectTab(firstPanel);
}
}
private void StopMonitoring()
{
if (!monitor.State.IsMonitoring)
return;
monitor.State.StopMonitoring();
if (monitor.State.ScriptCompilation != null && monitor.State.ScriptCompilation.Succeeded)
pollFrequencyControl.Value = monitor.State.ScriptCompilation.CompiledScript.PollFrequency;
}
private void OnStartedMonitoring(object sender, EventArgs args)
{
lock (this)
{
SendMessageToExtensions(i => i.OnStartedMonitoring());
UpdateTimerFromState();
}
}
private void OnStoppedMonitoring(object sender, EventArgs args)
{
lock (this)
{
UpdateTimerFromState();
}
}
private void UpdateTimerFromState()
{
pollTimer.Interval = PollIntervalMilliseconds;
pollTimer.Enabled = monitor.State.IsMonitoring;
}
private string GetSystemTrayTitle(string message)
{
var title = new StringBuilder();
title.AppendFormat("{0}: ", monitor.Workspace.CurrentScript.Name);
title.Append(message);
return title.ToString();
}
private void SetSystemTray(string message, Icon icon)
{
var text = GetSystemTrayTitle(message);
var shortText = text.Abbreviate(60);
text = shortText;
trayIcon.Icon = new Icon(icon, 16, 16);
trayIcon.Text = text;
}
private void UpdateCurrentScriptSummary()
{
var compilation = monitor.State.ScriptCompilation;
if (compilation == null)
{
lblScriptSummary.Text = "";
lblScriptSummary.ForeColor = Color.Black;
}
else if (compilation.Succeeded)
{
lblScriptSummary.Text = compilation.CompiledScript.BuildSummary();
lblScriptSummary.ForeColor = Color.Black;
}
else
{
var message = "Cannot Parse Script: " + compilation.Exception.Message;
SetSystemTray(message, ApplicationIcon.Error);
lblScriptSummary.Text = message;
lblScriptSummary.ForeColor = Color.Red;
}
}
private void UpdateUIFromModel()
{
lblPluginErrorSummary.Text = naughtyList.Any() ? "Error with plugin. Please see log." : "";
lblPluginErrorSummary.Visible = naughtyList.Any();
btnStart.Enabled = monitor.State.CanBeginMonitoring;
btnStop.Enabled = monitor.State.IsMonitoring;
pollFrequencyControl.Enabled = monitor.State.IsMonitoring;
btnAckError.Enabled = monitor.State.HasOperationFailures;
var focusedControl = FindFocusedControl(this);
SendMessageToNotificationPanels(i=>i.RefreshContent());
if (windowIsActive && focusedControl!= null)
{
focusedControl.Focus();
}
Icon systemTrayIcon = GetSystemTrayIcon();
string statusSummary = monitor.State.DefaultPresentation.GetStateSummary();
SetSystemTray(statusSummary, systemTrayIcon);
string statusBarMessage = statusSummary + (monitor.State.IsMonitoring ? " " + GetSpinner() : "");
tsStatusLabel.SetMessage(statusBarMessage, monitor.State.HasScriptError || monitor.State.HasOperationFailures ? Color.Red : monitor.State.IsMonitoring ? Color.Green : Color.Black);
UpdateCurrentScriptSummary();
}
[CanBeNull]
public static Control FindFocusedControl(Control control)
{
var container = control as ContainerControl;
while (container != null)
{
control = container.ActiveControl;
container = control as ContainerControl;
}
return control;
}
private Icon GetSystemTrayIcon()
{
return monitor.State.HasOperationFailures || monitor.State.HasScriptError
? ApplicationIcon.Error
: monitor.State.IsMonitoring ? ApplicationIcon.Running : ApplicationIcon.Stopped;
}
private string GetSpinner()
{
const int width = 100;
var position = monitor.Statistics.Combined.ProcessedCount % width;
return new string('.', position) + new string(' ', width - position);
}
private int PollIntervalMilliseconds
{
get
{
var duration = pollFrequencyControl.Value;
return (int) (duration.ToTimeSpan().TotalMilliseconds);
}
}
private void StartButtonClick(object sender, EventArgs e)
{
log.DebugFormat("Starting to poll every {0}ms", PollIntervalMilliseconds);
StartMonitoring();
}
private void StopButtonClick(object sender, EventArgs e)
{
log.Debug("Stopping poll");
StopMonitoring();
}
private void UIUpdateTimerTick(object sender, EventArgs e)
{
if (updatingUI) return;
updatingUI = true;
UpdateUIFromModel();
updatingUI = false;
}
private void PollTimerTick(object sender, EventArgs e)
{
monitor.InvokeNextOperation();
}
private void pollFrequencyControl_ValueChanging(object sender, PollFrequencyValueChangingEventArgs e)
{
e.NewValue = PollFrequencyLimit.EnsureMet(e.NewValue);
}
private void pollFrequencyControl_ValueChanged(object sender, EventArgs e)
{
pollTimer.Interval = PollIntervalMilliseconds;
}
private void AcknowledgeErrorClick(object sender, EventArgs e)
{
monitor.AcknowledgeErrors();
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = workspaceControl.AllowUserToSaveDirty();
}
private static void BrowseTo(string url)
{
var sInfo = new ProcessStartInfo(url);
Process.Start(sInfo);
}
private void newVersionLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
var url = (string) newVersionLink.Tag;
if (!string.IsNullOrEmpty(url))
{
BrowseTo(url);
}
}
private void MainForm_Enter(object sender, EventArgs e)
{
windowIsActive = true;
}
private void MainForm_Leave(object sender, EventArgs e)
{
windowIsActive = false;
}
private void MainForm_Resize(object sender, EventArgs e)
{
var isMini = Height < 300 || Width < 580;
statusStrip.Visible = !isMini;
pnlTopBar.Visible = !isMini;
newVersionLink.Visible = newVersionLink.Text != "" && !isMini;
if (isMini)
{
tabControl.Dock = DockStyle.Fill;
}
else
{
tabControl.Dock = DockStyle.None;
tabControl.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
tabControl.Location = new Point(0, 36);
tabControl.Size = new Size(Width - 16, Height - 100);
}
lblScriptSummary.Visible = !(Height < 450);
}
}
}