Skip to content
Snippets Groups Projects
AutoTxTray.cs 13.23 KiB
using System;
using System.Diagnostics;
using System.Timers;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using ATXCommon.Serializables;
using Microsoft.WindowsAPICodePack.Dialogs;
using NLog;
using NLog.Config;
using NLog.Targets;
using Timer = System.Timers.Timer;

namespace ATXTray
{
    public class AutoTxTray : ApplicationContext
    {
        private static readonly Logger Log = LogManager.GetCurrentClassLogger();

        private static readonly string AppTitle = Path.GetFileNameWithoutExtension(Application.ExecutablePath);
        private static readonly Timer AppTimer = new Timer(1000);
        private static bool _terminate = false;

        private static readonly string BaseDir = AppDomain.CurrentDomain.BaseDirectory;
        private static readonly string ConfigFile = Path.Combine(BaseDir, "configuration.xml");
        private static readonly string StatusFile = Path.Combine(BaseDir, "status.xml");
        private static DateTime _statusAge;
        private static ServiceConfig _config;
        private static ServiceStatus _status;

        private static bool _statusChanged = false;
        private static bool _svcRunning = false;
        private static bool _svcSuspended = true;
        private static string _svcSuspendReason;

        private static bool _txInProgress = false;
        private static long _txSize;
        
        private readonly NotifyIcon _notifyIcon = new NotifyIcon();
        private readonly Icon _tiDefault = new Icon("icon-default.ico");
        private readonly Icon _tiStopped = new Icon("icon-stopped.ico");
        private readonly Icon _tiSuspended = new Icon("icon-suspended.ico");
        private readonly Icon _tiTx0 = new Icon("icon-tx-0.ico");
        private readonly Icon _tiTx1 = new Icon("icon-tx-1.ico");
        private readonly ContextMenuStrip _cmStrip = new ContextMenuStrip();
        private readonly ToolStripMenuItem _miExit = new ToolStripMenuItem();
        private readonly ToolStripMenuItem _miTitle = new ToolStripMenuItem();
        private readonly ToolStripMenuItem _miSvcRunning = new ToolStripMenuItem();
        private readonly ToolStripMenuItem _miSvcSuspended = new ToolStripMenuItem();
        private readonly ToolStripMenuItem _miTxProgress = new ToolStripMenuItem();
        private readonly ToolStripMenuItem _miTxEnqueue = new ToolStripMenuItem();

        public AutoTxTray() {
            
            #region logging configuration
            
            var logConfig = new LoggingConfiguration();
            var fileTarget = new FileTarget {
                FileName = AppTitle + ".log",
                Layout = @"${date:format=yyyy-MM-dd HH\:mm\:ss} [${level}] ${message}"
                // Layout = @"${date:format=yyyy-MM-dd HH\:mm\:ss} [${level}] (${logger}) ${message}"
            };
            logConfig.AddTarget("file", fileTarget);
            var logRule = new LoggingRule("*", LogLevel.Debug, fileTarget);
            logConfig.LoggingRules.Add(logRule);
            LogManager.Configuration = logConfig;
            
            #endregion

            Log.Info("{0} initializing...", AppTitle);
            Log.Debug(" - config file: [{0}]", ConfigFile);
            Log.Debug(" - status file: [{0}]", StatusFile);

            _notifyIcon.Icon = _tiStopped;
            _notifyIcon.Visible = true;
            _notifyIcon.DoubleClick += StartNewTransfer;

            // this doesn't work properly, the menu will not close etc. so we disable it for now:
            // _notifyIcon.Click += ShowContextMenu;

            Log.Trace("Trying to read service config and status files...");
            try {
                _config = ServiceConfig.Deserialize(ConfigFile);
                ReadStatus();
                Log.Trace("Completed reading service config and status files.");
                SetupContextMenu();
            }
            catch (Exception ex) {
                var msg = "Error during initialization: " + ex.Message;
                Log.Error(msg);
                _notifyIcon.ShowBalloonTip(5000, AppTitle, msg, ToolTipIcon.Error);
                // we cannot terminate the message loop (Application.Run()) while the constructor
                // is being run as it is not active yet - therefore we simply remember that we want
                // to exit and evaluate this in the "Elapsed" handler:
                _terminate = true;
                System.Threading.Thread.Sleep(5000);
            }

            AppTimer.Elapsed += AppTimerElapsed;
            AppTimer.Enabled = true;
            Log.Trace("Enabled timer.");

            Log.Info("AutoTxTray initialization completed.");
        }

        private void SetupContextMenu() {
            Log.Trace("Building context menu...");
            _miExit.Text = @"Exit";
            _miExit.Click += MiExitClick;

            _miTitle.Font = new Font(_cmStrip.Font, FontStyle.Bold);
            _miTitle.Text = AppTitle;
            _miTitle.Image = Image.FromFile("icon-default.ico");
            _miTitle.BackColor = Color.LightCoral;
            _miTitle.Click += ShowContextMenu;

            _miSvcRunning.Text = @"Service NOT RUNNING!";
            _miSvcRunning.BackColor = Color.LightCoral;
            _miSvcRunning.Click += ShowContextMenu;

            _miSvcSuspended.Text = @"No limits apply, service active.";
            _miSvcSuspended.Click += ShowContextMenu;

            _miTxProgress.Text = @"No transfer running.";
            _miTxProgress.Click += ShowContextMenu;

            _miTxEnqueue.Text = @"+++ Add new directory for transfer. +++";
            _miTxEnqueue.Click += StartNewTransfer;

            _cmStrip.Items.AddRange(new ToolStripItem[] {
                _miTitle,
                _miSvcRunning,
                _miSvcSuspended,
                _miTxProgress,
                new ToolStripSeparator(),
                _miTxEnqueue,
                new ToolStripSeparator(),
                _miExit
            });

            _notifyIcon.ContextMenuStrip = _cmStrip;
            Log.Trace("Finished building context menu.");
        }
        
        private void AutoTxTrayExit() {
            Log.Info("Shutting down {0}.", AppTitle);
            _notifyIcon.Visible = false;
            Application.Exit();
        }

        /// <summary>
        /// Update the tooltip making sure not to exceed the 63 characters limit.
        /// </summary>
        /// <param name="msg"></param>
        private void UpdateHoverText(string msg) {
            if (msg.Length > 63) {
                msg = msg.Substring(0, 60) + "...";
            }
            _notifyIcon.Text = msg;
        }

        private void AppTimerElapsed(object sender, ElapsedEventArgs e) {
            if (_terminate) {
                AutoTxTrayExit();
                return;
            }

            UpdateSvcRunning();

            var heartBeat = "?";
            var serviceRunning = "stopped";
            var txProgress = "No";

            if (_svcRunning) {
                serviceRunning = "OK";
                ReadStatus();
                UpdateSvcSuspended();
                UpdateTxInProgress();
                if ((DateTime.Now - _status.LastStatusUpdate).TotalSeconds < 60)
                    heartBeat = "OK";
                if (_txInProgress)
                    txProgress = _txSize.ToString();
            }

            UpdateTrayIcon();

            if (!_statusChanged)
                return;

            UpdateHoverText(string.Format("AutoTx [svc={0}] [hb={1}] [tx={2}]",
                serviceRunning, heartBeat, txProgress));
        }

        private void MiExitClick(object sender, EventArgs e) {
            AutoTxTrayExit();
        }

        private void ShowContextMenu(object sender, EventArgs e) {
            // just show the menu again, to avoid that clicking the menu item closes the context
            // menu without having to disable the item (which would grey out the text and icon):
            _notifyIcon.ContextMenuStrip.Show();
        }

        private static void StartNewTransfer(object sender, EventArgs e) {
            var dirDialog = new CommonOpenFileDialog {
                Title = @"Select directory to be transferred",
                IsFolderPicker = true,
                EnsurePathExists = true,
                DefaultDirectory = _config.SourceDrive
            };
            if (dirDialog.ShowDialog() == CommonFileDialogResult.Ok) {
                MessageBox.Show("Directory\nselected:\n\n" + dirDialog.FileName +
                    "\n\nWARNING: adding new transfers is NOT YET IMPLEMENTED!",
                    "New transfer confirmation", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            }
        }

        /// <summary>
        /// Read (or re-read) the service status file if it has changed since last time.
        /// </summary>
        private static void ReadStatus() {
            var age = new FileInfo(StatusFile).LastWriteTime;
            if (age == _statusAge)
                return;

            Log.Debug("Status file was updated, trying to re-read...");
            _statusAge = age;
            _status = ServiceStatus.Deserialize(StatusFile, _config);
        }

        /// <summary>
        /// Check if a process with the expeced name of the service is currently running.
        /// </summary>
        /// <returns>True if such a process exists, false otherwise.</returns>
        private static bool ServiceProcessRunning() {
            var plist = Process.GetProcessesByName("AutoTx");
            return plist.Length > 0;
        }

        private void UpdateSvcRunning() {
            var curSvcRunState = ServiceProcessRunning();
            if (_svcRunning == curSvcRunState)
                return;

            _statusChanged = true;
            _svcRunning = curSvcRunState;
            if (_svcRunning) {
                _miSvcRunning.Text = @"Service running.";
                _miSvcRunning.BackColor = Color.LightGreen;
                _miTitle.BackColor = Color.LightGreen;
                _miSvcSuspended.Enabled = true;
                _notifyIcon.ShowBalloonTip(500, AppTitle,
                    "Service running.", ToolTipIcon.Info);
            } else {
                _miSvcRunning.Text = @"Service NOT RUNNING!";
                _miSvcRunning.BackColor = Color.LightCoral;
                _miTitle.BackColor = Color.LightCoral;
                _miSvcSuspended.Enabled = false;
                _notifyIcon.ShowBalloonTip(500, AppTitle,
                    "Service stopped.", ToolTipIcon.Error);
            }
        }

        private void UpdateSvcSuspended() {
            // first update the suspend reason as this can possibly change even if the service
            // never leaves the suspended state and we should still display the correct reason:
            if (_svcSuspendReason == _status.LimitReason &&
                _svcSuspended == _status.ServiceSuspended)
                return;

            _statusChanged = true;
            _svcSuspended = _status.ServiceSuspended;
            _svcSuspendReason = _status.LimitReason;
            if (_svcSuspended) {
                _miSvcSuspended.Text = @"Service suspended, reason: " + _svcSuspendReason;
                _miSvcSuspended.BackColor = Color.LightSalmon;
                /*
                _notifyIcon.ShowBalloonTip(500, "AutoTx Monitor",
                    "Service suspended: " + _status.LimitReason, ToolTipIcon.Warning);
                 */
            } else {
                _miSvcSuspended.Text = @"No limits apply, service active.";
                _miSvcSuspended.BackColor = Color.LightGreen;
                /*
                _notifyIcon.ShowBalloonTip(500, "AutoTx Monitor",
                    "Service resumed, no limits apply.", ToolTipIcon.Info);
                 */
            }
        }

        private void UpdateTxInProgress() {
            if (_txInProgress == _status.TransferInProgress &&
                _txSize == _status.CurrentTransferSize)
                return;

            _statusChanged = true;
            _txInProgress = _status.TransferInProgress;
            _txSize = _status.CurrentTransferSize;
            if (_txInProgress) {
                _miTxProgress.Text = @"Transfer in progress (size: " + _txSize + ")";
                _miTxProgress.BackColor = Color.LightGreen;
                _notifyIcon.ShowBalloonTip(500, AppTitle,
                    "New transfer started (size: " + _txSize + ").", ToolTipIcon.Info);
            } else {
                _miTxProgress.Text = @"No transfer running.";
                _miTxProgress.ResetBackColor();
                _notifyIcon.ShowBalloonTip(500, AppTitle,
                    "Transfer completed.", ToolTipIcon.Info);                
            }
        }

        private void UpdateTrayIcon() {
            if (_txInProgress &&
                !_svcSuspended) {
                if (DateTime.Now.Second % 2 == 0) {
                    _notifyIcon.Icon = _tiTx0;
                } else {
                    _notifyIcon.Icon = _tiTx1;
                }
            }

            if (!_statusChanged)
                return;

            if (!_svcRunning) {
                _notifyIcon.Icon = _tiStopped;
                return;
            }

            if (_svcSuspended) {
                _notifyIcon.Icon = _tiSuspended;
                return;
            }

            if (!_txInProgress) {
                _notifyIcon.Icon = _tiDefault;
            }
        }
    }
}