Ejemplo n.º 1
0
        /// <summary>
        /// Get latest aircraftlist.json
        /// </summary>
        public static void GetAircraft()
        {
            JObject responseJson;

            try {
                //Generate aircraftlist.json url
                string url = "";
                if (!Settings.acListUrl.Contains("?"))
                {
                    url = Settings.acListUrl + "?trFmt=f&refreshTrails=1&lat=" + Settings.Lat + "&lng=" + Settings.Long;
                }
                else
                {
                    url = Settings.acListUrl + "&trFmt=f&refreshTrails=1&lat=" + Settings.Lat + "&lng=" + Settings.Long;
                }
                //Create request
                request        = (HttpWebRequest)WebRequest.Create(url);
                request.Method = "GET";
                request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
                request.Timeout = Settings.timeout * 1000;
                //Add credentials if they are provided
                if (Settings.VRSAuthenticate)
                {
                    string encoded = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(Settings.VRSUsr + ":" + Settings.VRSPwd));
                    request.Headers.Add("Authorization", "Basic " + encoded);
                }
                //Send request and parse json response
                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                    using (Stream responsestream = response.GetResponseStream())
                        using (StreamReader reader = new StreamReader(responsestream))
                            using (JsonTextReader jsonreader = new JsonTextReader(reader))
                                responseJson = JsonSerializer.Create().Deserialize <JObject>(jsonreader);
                //Check if we actually got aircraft data
                if (responseJson["acList"] == null)
                {
                    Core.UI.writeToConsole("ERROR: Invalid response recieved from server", Color.Red);
                    Thread.Sleep(5000);
                    return;
                }
                //Throw error if server time was not parsed
                if (responseJson["stm"] == null)
                {
                    throw new JsonReaderException();
                }
                //Parse aircraft data
                Core.aircraftlist.Clear();
                foreach (JObject a in responseJson["acList"].ToList())
                {
                    //Ignore if no icao is provided
                    if (a["Icao"] == null)
                    {
                        continue;
                    }
                    //Create new aircraft
                    Core.Aircraft aircraft = new Core.Aircraft(a["Icao"].ToString());
                    //Parse aircraft trail
                    if (a["Cot"] != null)
                    {
                        aircraft.Trail = new double[a["Cot"].Count()];
                    }
                    for (int i = 0; i < aircraft.Trail.Length - 1; i++)
                    {
                        if (a["Cot"][i].Value <string>() != null)
                        {
                            aircraft.Trail[i] = Convert.ToDouble(a["Cot"][i].Value <string>());
                        }
                        else
                        {
                            aircraft.Trail[i] = 0;
                        }
                    }
                    //Parse aircraft properties
                    List <JProperty> properties = a.Properties().ToList();
                    for (int i = 0; i < properties.Count(); i++)
                    {
                        aircraft.AddProperty(properties[i].Name, properties[i].Value.ToString());
                    }
                    properties = null;
                    //Add aircraft to list
                    Core.aircraftlist.Add(aircraft);
                }
                //Get list of receivers
                Core.receivers.Clear();
                foreach (JObject f in responseJson["feeds"])
                {
                    Core.receivers.Add(new Core.Reciever(f["id"].ToString(), f["name"].ToString()));
                }
                //Try to clean up json parsing
                responseJson.RemoveAll();
                responseJson = null;
                GC.Collect(2, GCCollectionMode.Forced);
            }
            catch (UriFormatException) {
                Core.UI.writeToConsole("ERROR: AircraftList.json url invalid (" + Settings.acListUrl + ")", Color.Red);
                return;
            }
            catch (InvalidDataException) {
                Core.UI.writeToConsole("ERROR: Data returned from " + Settings.acListUrl + " was not gzip compressed", Color.Red);
                return;
            }
            catch (WebException e) {
                Core.UI.writeToConsole("ERROR: Error while connecting to AircraftList.json (" + e.Message + ")", Color.Red);
                return;
            }
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Constructor
        /// </summary>
        public static void Start()
        {
            //Name of receiver that provided the aircraft information
            string recieverName;
            //Number of triggers matching for condition
            int triggersMatching;
            //VRS name of property
            string propertyInternalName;
            //Index of match in waiting matches
            int wmIcaoIndex;
            //Alert message to send
            MailMessage message;
            //Text to display email property value
            string emailPropertyInfo;

            while (ThreadManager.threadStatus != ThreadManager.CheckerStatus.Stopping)
            {
                ThreadManager.threadStatus = ThreadManager.CheckerStatus.Running;
                //Set next check time
                nextCheck = DateTime.Now.AddSeconds(Settings.refreshRate);
                //Notify user that aircraft info is being downloaded
                Core.UI.updateStatusLabel("Downloading Aircraft Info...");
                recieverName = "";
                //Get latest aircraft information
                GetAircraft();

                //Check if there are aircraft to check
                if (Core.aircraftlist.Count != 0)
                {
                    //Aircraft number to be shown on UI
                    int aircraftCount = 1;
                    //Current condition being checked
                    Core.Condition condition;
                    foreach (Core.Aircraft aircraft in Core.aircraftlist.ToList())
                    {
                        //Update UI with aircraft being checked
                        Core.UI.updateStatusLabel("Checking conditions for aircraft " + aircraftCount + " of " + Core.aircraftlist.Count());
                        aircraftCount++;

                        //Iterate conditions
                        foreach (int conditionid in Core.conditions.Keys.ToList())
                        {
                            condition = Core.conditions[conditionid];
                            //Skip if condition is disabled or condition is already matched
                            if (condition.alertType == Core.AlertType.Disabled || (Core.activeMatches.ContainsKey(aircraft.ICAO) && Core.activeMatches[aircraft.ICAO].Conditions.Exists(x => x.ConditionID == conditionid)))
                            {
                                continue;
                            }

                            triggersMatching = 0;
                            //Iterate triggers for condition
                            foreach (Core.Trigger trigger in condition.triggers.Values)
                            {
                                //LEGEND
                                //A = Equals/Not Equals
                                //B = Higher Than + Lower Than
                                //C = True/False Boolean
                                //D = Starts With + Ends With
                                //E = Contains

                                //Get internal name for property to compare
                                propertyInternalName = Core.vrsPropertyData[trigger.Property][2].ToString();

                                //If aircraft properties do not contain property, skip
                                if (aircraft.GetProperty(propertyInternalName) == null)
                                {
                                    continue;
                                }
                                //Check property against value
                                if (trigger.ComparisonType == "Equals" && aircraft.GetProperty(propertyInternalName) == trigger.Value)
                                {
                                    triggersMatching++;
                                }
                                else if (trigger.ComparisonType == "Not Equals" && aircraft.GetProperty(propertyInternalName) != trigger.Value)
                                {
                                    triggersMatching++;
                                }
                                else if (trigger.ComparisonType == "Contains" && aircraft.GetProperty(propertyInternalName).Contains(trigger.Value))
                                {
                                    triggersMatching++;
                                }
                                else if (trigger.ComparisonType == "Higher Than" && Convert.ToDouble(aircraft.GetProperty(propertyInternalName)) > Convert.ToDouble(trigger.Value))
                                {
                                    triggersMatching++;
                                }
                                else if (trigger.ComparisonType == "Lower Than" && Convert.ToDouble(aircraft.GetProperty(propertyInternalName)) < Convert.ToDouble(trigger.Value))
                                {
                                    triggersMatching++;
                                }
                                else if (trigger.ComparisonType == "Starts With" && (aircraft.GetProperty(propertyInternalName).Length > trigger.Value.Length && aircraft.GetProperty(propertyInternalName).Substring(0, trigger.Value.Length) == trigger.Value))
                                {
                                    triggersMatching++;
                                }
                                else if (trigger.ComparisonType == "Ends With" && (aircraft.GetProperty(propertyInternalName).ToString().Length > trigger.Value.Length && aircraft.GetProperty(propertyInternalName).Substring(aircraft.GetProperty(propertyInternalName).Length - trigger.Value.Length) == trigger.Value))
                                {
                                    triggersMatching++;
                                }
                            }

                            //Get position in waiting matches
                            wmIcaoIndex = -1;
                            for (int i = 0; i < Core.waitingMatches.Count; i++)
                            {
                                if (Core.waitingMatches[i][0] == aircraft.ICAO && Core.waitingMatches[i][1] == conditionid.ToString())
                                {
                                    wmIcaoIndex = i;
                                    break;
                                }
                            }
                            //If condition doesn't exist in waiting matches and all triggers match, add to waiting matches
                            if (wmIcaoIndex == -1 && triggersMatching == condition.triggers.Count)
                            {
                                Core.waitingMatches.Add(new string[] { aircraft.ICAO, conditionid.ToString() });
                                //If ignore following is true, skip the following conditions
                                if (condition.ignoreFollowing)
                                {
                                    break;
                                }
                                else
                                {
                                    continue;
                                }
                            }
                            //If condition exists in waiting matches with this condition id, remove from waiting matches and send alert
                            if (wmIcaoIndex != -1 && Core.waitingMatches[wmIcaoIndex][1] == conditionid.ToString())
                            {
                                Core.waitingMatches.RemoveAt(wmIcaoIndex);

                                //Get receiver name
                                foreach (Core.Reciever reciever in Core.receivers)
                                {
                                    if (reciever.Id == aircraft.GetProperty("Rcvr"))
                                    {
                                        recieverName = reciever.Name;
                                    }
                                }

                                //Create email message
                                message = new MailMessage();
                                //Set subject and email property info
                                if (aircraft.GetProperty(Core.vrsPropertyData[(Core.vrsProperty)Enum.Parse(typeof(Core.vrsProperty), condition.emailProperty.ToString())][2].ToString()) == null)
                                {
                                    message.Subject   = "First Contact Alert! " + condition.conditionName;
                                    emailPropertyInfo = condition.emailProperty.ToString() + ": No Value";
                                }
                                else
                                {
                                    message.Subject   = "First Contact Alert! " + condition.conditionName + ": " + aircraft.GetProperty(Core.vrsPropertyData[(Core.vrsProperty)Enum.Parse(typeof(Core.vrsProperty), condition.emailProperty.ToString())][2].ToString());
                                    emailPropertyInfo = condition.emailProperty.ToString() + ": " + aircraft.GetProperty(Core.vrsPropertyData[(Core.vrsProperty)Enum.Parse(typeof(Core.vrsProperty), condition.emailProperty.ToString())][2].ToString());
                                }
                                //If active matches contains aircraft, add condition to the match
                                if (Core.activeMatches.ContainsKey(aircraft.ICAO))
                                {
                                    Core.activeMatches[aircraft.ICAO].AddCondition(conditionid, condition, aircraft);
                                }
                                //Else add to active matches
                                else
                                {
                                    Core.Match m = new Core.Match(aircraft.ICAO);
                                    m.AddCondition(conditionid, condition, aircraft);
                                    Core.activeMatches.Add(aircraft.ICAO, m);
                                }

                                //Update stats and log to console
                                Stats.updateStats();
                                Core.UI.writeToConsole(DateTime.Now.ToLongTimeString() + " | ADDED      | " + aircraft.ICAO + " | Condition: " + condition.conditionName + " (" + emailPropertyInfo + ")", Color.LightGreen);

                                //Send alert to emails in condition
                                if (condition.alertType == Core.AlertType.First_and_Last_Contact || condition.alertType == Core.AlertType.First_Contact)
                                {
                                    foreach (string email in condition.recieverEmails)
                                    {
                                        Email.SendEmail(email, message, condition, aircraft, recieverName, emailPropertyInfo, true);
                                    }
                                }
                            }
                        }
                        //If active matches contains this aircraft, update aircraft info
                        if (Core.activeMatches.ContainsKey(aircraft.ICAO))
                        {
                            foreach (Core.MatchedCondition c in Core.activeMatches[aircraft.ICAO].Conditions)
                            {
                                c.AircraftInfo = aircraft;
                            }
                        }
                    }
                    //Check if aircraft have lost signal and remove aircraft that have timed out
                    Core.UI.updateStatusLabel("Checking aircraft are still on radar...");
                    //Iterate active matches
                    foreach (Core.Match match in Core.activeMatches.Values.ToList())
                    {
                        //Iterate match conditions
                        foreach (Core.MatchedCondition c in match.Conditions)
                        {
                            //Check if signal has been lost for more than the removal timeout
                            if (match.SignalLostTime != DateTime.MinValue && DateTime.Compare(match.SignalLostTime, DateTime.Now.AddSeconds((Settings.removalTimeout - (Settings.removalTimeout * 2)))) < 0)
                            {
                                //Log to UI
                                Core.UI.writeToConsole(DateTime.Now.ToLongTimeString() + " | REMOVING   | " + match.Icao + " | " + c.DisplayName, Color.Orange);
                                //Remove from active matches
                                Core.activeMatches.Remove(match.Icao);
                                //Update aircraft info
                                Core.Aircraft aircraft = c.AircraftInfo;
                                //Alert if alert type is both or last
                                if (c.Match.alertType == Core.AlertType.First_and_Last_Contact || c.Match.alertType == Core.AlertType.Last_Contact)
                                {
                                    condition = c.Match;

                                    //Create new email message
                                    message = new MailMessage();

                                    //Set email subject and email property info
                                    if (aircraft.GetProperty(Core.vrsPropertyData[(Core.vrsProperty)Enum.Parse(typeof(Core.vrsProperty), condition.emailProperty.ToString())][2].ToString()) == null)
                                    {
                                        message.Subject   = "Last Contact Alert!  " + condition.conditionName;
                                        emailPropertyInfo = condition.emailProperty.ToString() + ": No Value";
                                    }
                                    else
                                    {
                                        message.Subject   = "Last Contact Alert!  " + condition.conditionName + ": " + aircraft.GetProperty(Core.vrsPropertyData[(Core.vrsProperty)Enum.Parse(typeof(Core.vrsProperty), condition.emailProperty.ToString())][2].ToString());
                                        emailPropertyInfo = condition.emailProperty.ToString() + ": " + aircraft.GetProperty(Core.vrsPropertyData[(Core.vrsProperty)Enum.Parse(typeof(Core.vrsProperty), condition.emailProperty.ToString())][2].ToString());
                                    }
                                    //Make a ding noise or something
                                    SystemSounds.Exclamation.Play();
                                    //Show notification
                                    Core.UI.notifyIcon.ShowBalloonTip(5000, "Plane Alert!", "Condition: " + condition.conditionName + " (" + emailPropertyInfo + ")\nRegistration: " + aircraft.GetProperty("Reg"), ToolTipIcon.Info);
                                    //Send alerts to all the emails
                                    foreach (string email in condition.recieverEmails)
                                    {
                                        Email.SendEmail(email, message, condition, aircraft, recieverName, emailPropertyInfo, false);
                                    }
                                }
                                break;
                            }
                            //Check if signal has been lost/returned
                            bool stillActive = false;
                            foreach (Core.Aircraft aircraft in Core.aircraftlist)
                            {
                                if (aircraft.ICAO == match.Icao)
                                {
                                    stillActive = true;
                                }
                            }
                            if (!stillActive && match.SignalLost == false)
                            {
                                Core.activeMatches[match.Icao].SignalLostTime = DateTime.Now;
                                Core.activeMatches[match.Icao].SignalLost     = true;
                                Core.UI.writeToConsole(DateTime.Now.ToLongTimeString() + " | LOST SGNL  | " + match.Icao + " | " + match.DisplayName, Color.LightGoldenrodYellow);
                            }
                            if (stillActive && match.SignalLost == true)
                            {
                                Core.activeMatches[match.Icao].SignalLost = false;
                                Core.UI.writeToConsole(DateTime.Now.ToLongTimeString() + " | RETND SGNL | " + match.Icao + " | " + match.DisplayName, Color.LightGoldenrodYellow);
                            }
                        }
                    }
                }
                //Cancel if thread is supposed to stop
                if (ThreadManager.threadStatus == ThreadManager.CheckerStatus.Stopping)
                {
                    return;
                }
                //Set thread status to waiting
                Core.UI.updateStatusLabel("Waiting for next check...");
                ThreadManager.threadStatus = ThreadManager.CheckerStatus.Waiting;
                //Wait until the next check time
                while (DateTime.Compare(DateTime.Now, nextCheck) < 0)
                {
                    //Cancel if thread is supposed to stop
                    if (ThreadManager.threadStatus == ThreadManager.CheckerStatus.Stopping)
                    {
                        return;
                    }
                    Thread.Sleep(1000);
                }
            }
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Send alert email
        /// </summary>
        /// <param name="emailaddress">Email address to send to</param>
        /// <param name="message">Message to send</param>
        /// <param name="condition">Condition that triggered alert</param>
        /// <param name="aircraft">Aircraft information for matched aircraft</param>
        /// <param name="recieverName">Name of receiver that got the last aircraft information</param>
        /// <param name="emailPropertyInfo">String for displaying email property value</param>
        /// <param name="isDetection">Is this a detection, not a removal?</param>
        public static void SendEmail(string emailaddress, MailMessage message, Core.Condition condition, Core.Aircraft aircraft, string recieverName, string emailPropertyInfo, bool isDetection)
        {
            //Array to store position trail
            Dictionary <int, string[]> pathArray = new Dictionary <int, string[]>();
            //Google status map for position trail's url
            string staticMapUrl = "";
            //Google maps url
            string googleMapsUrl = "";
            //Transponder type from aircraft info
            string transponderName = "";

            //Types of transponders
            string[] transponderTypes = new string[] { "Unknown", "Mode-S", "ADS-B", "ADS-Bv1", "ADS-Bv2" };
            //Aircraft image urls
            string[] imageLinks = new string[2];
            //Table for displaying aircraft property values
            string aircraftTable = "<table style='border: 2px solid;border-radius: 10px;border-spacing: 0px;' id='acTable'>";
            //HTML for aircraft images
            string imageHTML = "";
            //Airframes.org url
            string airframesUrl = "";
            //Report url
            string reportUrl = "";

            //Set message type to html
            message.IsBodyHtml = true;

            //Add email to message receiver list
            try {
                message.To.Clear();
                message.To.Add(emailaddress);
            }
            catch {
                Core.UI.writeToConsole("ERROR: Email to send to is invalid (" + emailaddress + ")", Color.Red);
                return;
            }

            //Set sender email to the one set in settings
            try {
                message.From = new MailAddress(Settings.senderEmail, "PlaneAlerter Alerts");
            }
            catch {
                Core.UI.writeToConsole("ERROR: Email to send from is invalid (" + Settings.senderEmail + ")", Color.Red);
                return;
            }

            if (Settings.EmailContentConfig.TwitterOptimised)
            {
                string typestring          = "First Contact";
                string regostring          = "No rego, ";
                string callsignstring      = "No callsign, ";
                string operatorstring      = "No operator, ";
                string emailpropertystring = "No " + condition.emailProperty.ToString();
                if (!isDetection)
                {
                    typestring = "Last Contact";
                }
                if (aircraft.GetProperty("Reg") != null)
                {
                    regostring = aircraft.GetProperty("Reg") + ", ";
                }
                if (aircraft.GetProperty("Call") != null)
                {
                    callsignstring = aircraft.GetProperty("Call") + ", ";
                }
                if (aircraft.GetProperty("OpIcao") != null)
                {
                    operatorstring = aircraft.GetProperty("OpIcao") + ", ";
                }
                string emailpropertyvalue = aircraft.GetProperty(Core.vrsPropertyData[condition.emailProperty][2]);
                if (emailpropertyvalue != null)
                {
                    emailpropertystring = emailpropertyvalue;
                }
                message.Body = "[" + typestring + "] " + condition.conditionName + " (" + emailpropertystring + "), " + regostring + operatorstring + aircraft.GetProperty("Type") + ", " + callsignstring + Settings.radarUrl;
                //[Last Contact] USAF (RCH9701), 79-1951, RCH, DC10, RCH9701 http://seqldradar.net
            }
            else
            {
                //Create request for aircraft image urls
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Settings.acListUrl.Substring(0, Settings.acListUrl.LastIndexOf("/") + 1) + "AirportDataThumbnails.json?icao=" + aircraft.ICAO + "&numThumbs=" + imageLinks.Length);
                request.Method = "GET";
                request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
                request.Timeout = 5000;

                //If vrs authentication is used, add credentials to request
                if (Settings.VRSAuthenticate)
                {
                    string encoded = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(Settings.VRSUsr + ":" + Settings.VRSPwd));
                    request.Headers.Add("Authorization", "Basic " + encoded);
                }

                //Send request and parse response
                try {
                    //Get response
                    HttpWebResponse imageResponse      = (HttpWebResponse)request.GetResponse();
                    byte[]          imageResponseBytes = new byte[32768];
                    imageResponse.GetResponseStream().Read(imageResponseBytes, 0, 32768);
                    string imageResponseText = Encoding.ASCII.GetString(imageResponseBytes);

                    //Parse json
                    JObject imageResponseJson = (JObject)JsonConvert.DeserializeObject(imageResponseText);

                    //If status is not 404, add images to image HTML
                    if (imageResponseJson["status"].ToString() != "404")
                    {
                        foreach (JObject image in imageResponseJson["data"])
                        {
                            imageHTML += "<img style='margin: 5px;border: 2px solid;border-radius: 10px;' src='" + image + "' />";
                        }
                    }
                    imageHTML += "<br>";
                }
                catch (Exception) {
                }

                //If aircraft has a position, generate a google map url
                if (aircraft.GetProperty("Lat") != null)
                {
                    if (aircraft.Trail.Count() != 3)
                    {
                        staticMapUrl = "http://maps.googleapis.com/maps/api/staticmap?center=" + aircraft.GetProperty("Lat") + "," + aircraft.GetProperty("Long") + "&amp;size=800x800&amp;markers=" + aircraft.GetProperty("Lat") + "," + aircraft.GetProperty("Long") + "&amp;key=AIzaSyCJxiyiDWBHiYSMm7sjSTJkQubuo3XuR7s&amp;path=color:0x000000|";
                    }
                    else
                    {
                        staticMapUrl = "http://maps.googleapis.com/maps/api/staticmap?center=" + aircraft.GetProperty("Lat") + "," + aircraft.GetProperty("Long") + "&amp;size=800x800&amp;zoom=8&amp;markers=" + aircraft.GetProperty("Lat") + "," + aircraft.GetProperty("Long") + "&amp;key=AIzaSyCJxiyiDWBHiYSMm7sjSTJkQubuo3XuR7s&amp;path=color:0x000000|";
                    }
                }

                //Process aircraft trail
                for (int i = 0; i < aircraft.Trail.Count() / 3; i++)
                {
                    //Get coordinate
                    string[] coord = new string[] {
                        aircraft.Trail[i * 3].ToString(),
                        aircraft.Trail[i * 3 + 1].ToString(),
                        aircraft.Trail[i * 3 + 2].ToString()
                    };
                    //Add coordinate to google map url
                    staticMapUrl += coord[0] + "," + coord[1];
                    //If this is not the last coordinate, add a separator
                    if (i != (aircraft.Trail.Count() / 3) - 1)
                    {
                        staticMapUrl += "|";
                    }
                }

                //Generate google maps url
                googleMapsUrl = "https://www.google.com.au/maps/search/" + aircraft.GetProperty("Lat") + "," + aircraft.GetProperty("Long");

                //Generate airframes.org url
                if (aircraft.GetProperty("Reg") != null && aircraft.GetProperty("Reg") != "")
                {
                    airframesUrl = "<h3><a style='text-decoration: none;' href='http://www.airframes.org/reg/" + aircraft.GetProperty("Reg").Replace("-", "").ToUpper() + "'>Airframes.org Lookup</a></h3>";
                }

                //Generate radar url
                try {
                    if (Settings.radarUrl[Settings.radarUrl.Length - 1] == '/')
                    {
                        reportUrl = Settings.radarUrl + "desktopReport.html?icao-Q=" + aircraft.ICAO;
                    }
                    else
                    {
                        reportUrl = Settings.radarUrl + "/desktopReport.html?icao-Q=" + aircraft.ICAO;
                    }
                }
                catch {
                    Core.UI.writeToConsole("ERROR: No radar url specified.", Color.Red);
                    ThreadManager.StartOrRestart();
                }

                //Get name of transponder type
                transponderName = transponderTypes[Convert.ToInt32(aircraft.GetProperty("Trt")) - 1];

                //Write to log and UI
                Core.logFileSW.WriteLine(DateTime.Now.ToLongTimeString() + " | SENDING ALERT: " + Environment.NewLine + aircraft.ToString() + Environment.NewLine + Environment.NewLine);
                Core.UI.writeToConsole(DateTime.Now.ToLongTimeString() + " | SENDING    | " + aircraft.ICAO + " | Condition: " + condition.conditionName + " (" + emailPropertyInfo + ")", Color.LightBlue);

                //Generate aircraft property value table
                bool isAlternateStyle = false;
                foreach (string child in aircraft.GetPropertyKeys())
                {
                    //TODO ADD TO VRSPROPERTIES
                    string parameter = "UNKNOWN_PARAMETER";
                    //Set parameter to a readable name if it's not included in vrs property info
                    switch (child.ToString())
                    {
                    case "CNum":
                        parameter = "Aircraft_Serial";
                        break;

                    case "EngMount":
                        parameter = "Engine_Mount";
                        break;

                    case "FSeen":
                        parameter = "First_Seen";
                        break;

                    case "HasPic":
                        parameter = "Has_Picture";
                        break;

                    case "Id":
                        parameter = "Id";
                        break;

                    case "Lat":
                        parameter = "Latitude";
                        break;

                    case "Long":
                        parameter = "Longitude";
                        break;

                    case "PosTime":
                        parameter = "Position_Time";
                        break;

                    case "ResetTrail":
                        parameter = "Reset_Trail";
                        break;

                    case "Tisb":
                        parameter = "TIS-B";
                        break;

                    case "Trak":
                        parameter = "Track";
                        break;

                    case "TrkH":
                        parameter = "Is_Track_Heading";
                        break;

                    case "Year":
                        parameter = "Year";
                        break;
                    }
                    //If parameter is set (not in vrs property info list) and property list type is set to essentials, skip this property
                    if (parameter != "UNKNOWN_PARAMETER" && Settings.EmailContentConfig.PropertyList == Core.PropertyListType.Essentials)
                    {
                        continue;
                    }
                    //Get parameter information from vrs property info
                    if (parameter == "UNKNOWN_PARAMETER")
                    {
                        foreach (Core.vrsProperty property in Core.vrsPropertyData.Keys)
                        {
                            if (Core.vrsPropertyData[property][2] == child.ToString())
                            {
                                //If property list type is essentials and this property is not in the list of essentials, leave this property as unknown so it can be skipped
                                if (Settings.EmailContentConfig.PropertyList == Core.PropertyListType.Essentials && !Core.essentialProperties.Contains(property))
                                {
                                    continue;
                                }
                                parameter = property.ToString();
                            }
                        }
                        if (parameter == "UNKNOWN_PARAMETER")
                        {
                            continue;
                        }
                    }
                    //Add html for property
                    if (isAlternateStyle)
                    {
                        aircraftTable += "<tr style='background-color:#CCC'>";
                    }
                    else
                    {
                        aircraftTable += "<tr>";
                    }
                    aircraftTable   += "<td style='padding: 3px;font-weight:bold;'>" + parameter + "</td>";
                    aircraftTable   += "<td style='padding: 3px'>" + aircraft.GetProperty(child) + "</td>";
                    aircraftTable   += "</tr>";
                    isAlternateStyle = !isAlternateStyle;
                }
                aircraftTable += "</table>";

                //Create the main html document
                message.Body =
                    "<!DOCTYPE html PUBLIC ' -W3CDTD XHTML 1.0 TransitionalEN' 'http:www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html><body>";
                if (isDetection)
                {
                    message.Body += "<h1>Plane Alert - Last Contact</h1>";
                }
                else
                {
                    message.Body += "<h1>Plane Alert - First Contact</h1>";
                }

                //Condition name + email property
                message.Body += "<h2 style='margin: 0px;margin-bottom: 2px;margin-left: 10px;'>Condition: " + condition.conditionName + " (" + emailPropertyInfo + ")</h2>";
                //Receiver name
                if (Settings.EmailContentConfig.ReceiverName)
                {
                    message.Body += "<h2 style='margin: 0px;margin-bottom: 2px;margin-left: 10px;'>Reciever: " + recieverName + "</h2>";
                }
                //Transponder type
                if (Settings.EmailContentConfig.TransponderType)
                {
                    message.Body += "<h2 style='margin: 0px;margin-bottom: 2px;margin-left: 10px;'>Transponder: " + transponderName + "</h2>";
                }
                //Radar url
                if (Settings.EmailContentConfig.RadarLink)
                {
                    message.Body += "<h3><a style='text-decoration: none;' href='" + Settings.radarUrl + "'>Goto Radar</a></h3>";
                }
                //Report url
                if (Settings.EmailContentConfig.ReportLink)
                {
                    message.Body += "<h3><a style='text-decoration: none;' href='" + reportUrl + "'>VRS Report Lookup</a></h3>";
                }
                //Airframes.org url
                if (Settings.EmailContentConfig.AfLookup)
                {
                    message.Body += airframesUrl;
                }

                message.Body += "<table><tr><td>";
                //Property list
                if (Settings.EmailContentConfig.PropertyList != Core.PropertyListType.Hidden)
                {
                    message.Body += aircraftTable;
                }
                message.Body += "</td><td style='padding: 10px;vertical-align: top;'>";
                //Aircraft photos
                if (Settings.EmailContentConfig.AircraftPhotos)
                {
                    message.Body += imageHTML;
                }
                //Map
                if (Settings.EmailContentConfig.Map && aircraft.GetProperty("Lat") != null)
                {
                    message.Body += "<h3 style='margin: 0px'><a style='text-decoration: none' href=" + googleMapsUrl + ">Open in Google Maps</a></h3><br />" +
                                    "<img style='border: 2px solid;border-radius: 10px;' alt='Loading Map...' src='" + staticMapUrl + "' />";
                }
                message.Body += "</td></tr></table></body></html>";
            }

            //Send the alert
            try {
                mailClient.Send(message);
            }
            catch (SmtpException e) {
                if (e.InnerException != null)
                {
                    Core.UI.writeToConsole("SMTP ERROR: " + e.Message + " (" + e.InnerException.Message + ")", Color.Red);
                    return;
                }
                Core.UI.writeToConsole("SMTP ERROR: " + e.Message, Color.Red);
                return;
            }
            catch (InvalidOperationException e) {
                if (e.InnerException != null)
                {
                    Core.UI.writeToConsole("SMTP ERROR: " + e.Message + " (" + e.InnerException.Message + ")", Color.Red);
                    return;
                }
                Core.UI.writeToConsole("SMTP ERROR: " + e.Message, Color.Red);
                return;
            }

            //Increase sent emails for condition and update stats
            condition.increaseSentEmails();
            Stats.updateStats();

            //Log to UI
            Core.UI.writeToConsole(DateTime.Now.ToLongTimeString() + " | SENT       | " + aircraft.ICAO + " | Condition: " + condition.conditionName + " (" + emailPropertyInfo + ")", Color.LightBlue);
        }