Korero – Rev 1

Subversion Repositories:
Rev:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Korero.Chat;
using Korero.Communication;
using Korero.Profile;
using Korero.Properties;
using Korero.Serialization;
using Korero.Teleport;
using Korero.Utilities;
using Serilog;

namespace Korero.Land
{
    public partial class LandForm : Form
    {
        #region Private Delegates, Events, Enums, Properties, Indexers and Fields

        private readonly CancellationToken _cancellationToken;

        private readonly CancellationTokenSource _cancellationTokenSource;

        private readonly MainForm _mainForm;

        private readonly SolidBrush _mapAvatarPositionBrushOuter;

        private readonly Pen _mapAvatarPositionBrushSelect;

        private readonly ConcurrentDictionary<string, MapAvatarPosition> _mapAvatarPositions;

        private readonly MqttCommunication _mqttCommunication;

        private AvatarProfileForm _avatarProfileForm;

        private SolidBrush _mapAvatarPositionBrush;

        private int _mapAvatarPositionDiameter;

        private int _mapAvatarPositionOuterDiameter;

        private int _mapAvatarPositionSelectDiameter;

        private volatile Image _mapImage;

        private volatile Image _mapImageAvatars;

        private TeleportForm _teleportForm;

        #endregion

        #region Constructors, Destructors and Finalizers

        public LandForm()
        {
            InitializeComponent();
            Utilities.WindowState.FormTracker.Track(this);

            _cancellationTokenSource = new CancellationTokenSource();
            _cancellationToken = _cancellationTokenSource.Token;

            Settings.Default.PropertyChanged += Default_PropertyChanged;

            _mapAvatarPositionDiameter = Settings.Default.MapAvatarDotMultiplier * 8;
            _mapAvatarPositionOuterDiameter = Settings.Default.MapAvatarDotMultiplier * 10;
            _mapAvatarPositionSelectDiameter = Settings.Default.MapAvatarDotMultiplier * 12;

            _mapAvatarPositions = new ConcurrentDictionary<string, MapAvatarPosition>();
            _mapAvatarPositionBrush = new SolidBrush(Settings.Default.MapDotColor);
            _mapAvatarPositionBrushOuter = new SolidBrush(Color.Black);
            _mapAvatarPositionBrushSelect = new Pen(Color.Red);
        }

        public LandForm(MainForm mainForm, MqttCommunication mqttCommunication) : this()
        {
            _mainForm = mainForm;
            _mqttCommunication = mqttCommunication;
            _mqttCommunication.NotificationReceived += MqttCommunication_NotificationReceived;
        }

        /// <summary>
        ///     Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && components != null)
            {
                components.Dispose();
            }

            Settings.Default.PropertyChanged -= Default_PropertyChanged;
            _mqttCommunication.NotificationReceived -= MqttCommunication_NotificationReceived;

            base.Dispose(disposing);
        }

        #endregion

        #region Event Handlers

        private void Default_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            _mapAvatarPositionDiameter = Settings.Default.MapAvatarDotMultiplier * 8;
            _mapAvatarPositionOuterDiameter = Settings.Default.MapAvatarDotMultiplier * 10;
            _mapAvatarPositionSelectDiameter = Settings.Default.MapAvatarDotMultiplier * 12;

            _mapAvatarPositionBrush = new SolidBrush(Settings.Default.MapDotColor);
        }

        private void MqttCommunication_NotificationReceived(object sender, MqttNotificationEventArgs e)
        {
            switch (e.Notification["notification"])
            {
                case "statistics":
                    var statistics = e.Notification["statistics"];
                    if (string.IsNullOrEmpty(statistics))
                    {
                        break;
                    }

                    var metrics = new CSV(statistics).ToArray();
                    for (var i = 0; i < metrics.Length; ++i)
                    {
                        var value = metrics.ElementAtOrDefault(i + 1);
                        switch (metrics[i])
                        {
                            case "ScriptedObjects":
                                scriptedObjectsTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
                                break;
                            case "Objects":
                                objectsTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
                                break;
                            case "FPS":
                                fpsTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
                                break;
                            case "PhysicsFPS":
                                physicsFpsTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
                                break;
                            case "ActiveScripts":
                                activeScriptsTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
                                break;
                            case "ScriptTime":
                                scriptTimeTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
                                break;
                            case "FrameTime":
                                frameTimeTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
                                break;
                            case "Dilation":
                                timeDilationTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
                                break;
                            case "LastLag":
                                lastLagTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
                                break;
                            case "Agents":
                                agentsTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
                                break;
                            case "PhysicsTime":
                                physicsTimeTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
                                break;
                            case "NetTime":
                                netTimeTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
                                break;
                        }
                    }

                    break;
                case "map":
                    var region = e.Notification["region"];
                    textBox2.InvokeIfRequired(textBox => { textBox.Text = region; });

                    var mapImageBase64 = e.Notification["texture"];
                    if (string.IsNullOrEmpty(mapImageBase64))
                    {
                        return;
                    }

                    var mapBytes = Convert.FromBase64String(mapImageBase64);

                    if (mapBytes.Length == 0)
                    {
                        return;
                    }

                    using (var memoryStream = new MemoryStream(mapBytes))
                    {
                        if (_mapImage != null)
                        {
                            _mapImage.Dispose();
                            _mapImage = null;
                        }

                        _mapImage = Image.FromStream(memoryStream);
                    }

                    break;
                case "parcel":
                    var parcelName = e.Notification["name"];
                    var description = e.Notification["description"];
                    textBox1.InvokeIfRequired(textBox => { textBox.Text = parcelName; });
                    richTextBox2.InvokeIfRequired(textBox => { textBox.Text = description; });
                    break;
                case "coarse":
                    var oldAvatars = e.Notification["old"];

                    foreach (var avatar in CsvStride(oldAvatars, 2))
                    {
                        var name = avatar[0];
                        var id = avatar[1];

                        listBox1.InvokeIfRequired(listBox =>
                        {
                            if (listBox.Items.OfType<string>().Any(item => item == name))
                            {
                                listBox.Items.Remove(name);
                            }
                        });
                    }

                    var newAvatars = e.Notification["new"];

                    foreach (var avatar in CsvStride(newAvatars, 3))
                    {
                        var name = avatar[0];
                        var id = avatar[1];
                        var position = avatar[2];

                        listBox1.InvokeIfRequired(listBox =>
                        {
                            if (listBox.Items.OfType<string>().All(item => item != name))
                            {
                                listBox.Items.Add(name);
                            }
                        });
                    }

                    break;
            }
        }

        private void ChatToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var index = listBox1.SelectedIndex;
            if (index == -1)
            {
                return;
            }

            var selectedItem = (string) listBox1.SelectedItem;

            if (_mainForm.ChatForm != null)
            {
                _mainForm.ChatForm.CreateOrSelect(selectedItem);
                return;
            }

            _mainForm.ChatForm = new ChatForm(_mainForm, _mqttCommunication);
            _mainForm.ChatForm.Closing += ChatForm_Closing;
            _mainForm.ChatForm.Show();
            _mainForm.ChatForm.CreateOrSelect(selectedItem);
        }

        private void ChatForm_Closing(object sender, CancelEventArgs e)
        {
            if (_mainForm.ChatForm == null)
            {
                return;
            }

            _mainForm.ChatForm.Closing -= ChatForm_Closing;
            _mainForm.ChatForm.Dispose();
            _mainForm.ChatForm = null;
        }

        private void ListBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            var listBox = (ListBox) sender;
            var index = listBox.SelectedIndex;
            if (index == -1)
            {
                return;
            }

            var item = (string) listBox.SelectedItem;

            if (!_mapAvatarPositions.TryGetValue(item, out var selectedMapAvatarPosition))
            {
                return;
            }

            if (_mapImageAvatars == null)
            {
                return;
            }

            var bitmap = new Bitmap(_mapImageAvatars.Width, _mapImageAvatars.Height);
            using (var graphics = Graphics.FromImage(bitmap))
            {
                graphics.DrawImage(_mapImageAvatars, Point.Empty);
                foreach (var mapAvatarPosition in _mapAvatarPositions.Values)
                {
                    graphics.FillEllipse(_mapAvatarPositionBrushOuter,
                        mapAvatarPosition.X - _mapAvatarPositionOuterDiameter / 2,
                        _mapImage.Height - mapAvatarPosition.Y - _mapAvatarPositionOuterDiameter / 2,
                        _mapAvatarPositionOuterDiameter, _mapAvatarPositionOuterDiameter);
                    graphics.FillEllipse(_mapAvatarPositionBrush, mapAvatarPosition.X - _mapAvatarPositionDiameter / 2,
                        _mapImage.Height - mapAvatarPosition.Y - _mapAvatarPositionDiameter / 2,
                        _mapAvatarPositionDiameter, _mapAvatarPositionDiameter);
                }

                graphics.DrawEllipse(_mapAvatarPositionBrushSelect,
                    selectedMapAvatarPosition.X - _mapAvatarPositionSelectDiameter / 2,
                    _mapImageAvatars.Height - selectedMapAvatarPosition.Y - _mapAvatarPositionSelectDiameter / 2,
                    _mapAvatarPositionSelectDiameter, _mapAvatarPositionSelectDiameter);
            }

            _mapImageAvatars = bitmap;

            pictureBox1.InvokeIfRequired(pictureBox =>
            {
                if (pictureBox.Image != null)
                {
                    pictureBox.Image.Dispose();
                    pictureBox.Image = null;
                }

                pictureBox.Image = bitmap;
            });
        }

        private async void FlyToToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var index = listBox1.SelectedIndex;
            if (index == -1)
            {
                return;
            }

            var selectedItem = (string) listBox1.SelectedItem;

            if (!_mapAvatarPositions.TryGetValue(selectedItem, out var mapAvatarPosition))
            {
                return;
            }

            var data = new Dictionary<string, string>
            {
                {"command", "flyto"},
                {"group", Settings.Default.Group},
                {"password", Settings.Default.Password},
                {"position", $"<{mapAvatarPosition.X}, {mapAvatarPosition.Y}, {mapAvatarPosition.Z}>"},
                {"vicinity", $"{Settings.Default.FlyToVicinity}"},
                {"duration", $"{Settings.Default.FlyToDuration}"}
            };

            var callback = await _mqttCommunication.SendCommand(new Command(data), _cancellationToken);

            if (callback == null || !callback.Success)
            {
                if (callback != null)
                {
                    Log.Warning("Command {Command} has failed with {Error}.",
                        callback.Command, callback.Error);
                }
            }
        }

        private async void TeleportToToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var index = listBox1.SelectedIndex;
            if (index == -1)
            {
                return;
            }

            var selectedItem = (string) listBox1.SelectedItem;

            if (!_mapAvatarPositions.TryGetValue(selectedItem, out var mapAvatarPosition))
            {
                return;
            }

            var data = new Dictionary<string, string>
            {
                {"command", "getregiondata"},
                {"group", Settings.Default.Group},
                {"password", Settings.Default.Password},
                {"data", "Name"}
            };

            var callback = await _mqttCommunication.SendCommand(new Command(data), _cancellationToken);

            if (callback == null || !callback.Success)
            {
                if (callback != null)
                {
                    Log.Warning("Command {Command} has failed with {Error}.",
                        callback.Command, callback.Error);
                }

                return;
            }

            var region = callback["Name"].FirstOrDefault();

            if (string.IsNullOrEmpty(region))
            {
                return;
            }

            data = new Dictionary<string, string>
            {
                {"command", "teleport"},
                {"group", Settings.Default.Group},
                {"password", Settings.Default.Password},
                {"entity", "region"},
                {"region", region},
                {"position", $"<{mapAvatarPosition.X}, {mapAvatarPosition.Y}, {mapAvatarPosition.Z}>"},
                {"fly", "True"}
            };

            callback = await _mqttCommunication.SendCommand(new Command(data), _cancellationToken);

            if (callback == null || !callback.Success)
            {
                if (callback != null)
                {
                    Log.Warning("Command {Command} has failed with {Error}.",
                        callback.Command, callback.Error);
                }
            }
        }

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

            _teleportForm = new TeleportForm(_mainForm, _mqttCommunication);
            _teleportForm.Closing += TeleportForm_Closing;
            _teleportForm.Show();
        }

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

            _teleportForm.Closing -= TeleportForm_Closing;
            _teleportForm.Dispose();
            _teleportForm = null;
        }

        private void ProfileToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var index = listBox1.SelectedIndex;
            if (index == -1)
            {
                return;
            }

            var selectedItem = (string) listBox1.SelectedItem;

            if (_avatarProfileForm != null)
            {
                return;
            }

            _avatarProfileForm = new AvatarProfileForm(_mainForm, selectedItem, _mqttCommunication);
            _avatarProfileForm.Closing += AvatarProfileForm_Closing;
            _avatarProfileForm.Show();
        }

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

            _avatarProfileForm.Closing -= AvatarProfileForm_Closing;
            _avatarProfileForm.Dispose();
            _avatarProfileForm = null;
        }

        private void RichTextBox2_LinkClicked(object sender, LinkClickedEventArgs e)
        {
            Process.Start(e.LinkText);
        }

        private void LandForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            _cancellationTokenSource.Cancel();

            if (pictureBox1.Image != null)
            {
                pictureBox1.Image.Dispose();
                pictureBox1.Image = null;
            }
        }

        private async void OfferTeleportToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var index = listBox1.SelectedIndex;
            if (index == -1)
            {
                return;
            }

            var selectedItem = (string) listBox1.SelectedItem;
            var firstName = selectedItem.Split(' ')[0];
            var lastName = selectedItem.Split(' ')[1];

            var data = new Dictionary<string, string>
            {
                {"command", "lure"},
                {"group", Settings.Default.Group},
                {"password", Settings.Default.Password},
                {"firstname", firstName},
                {"lastname", lastName}
            };

            var callback = await _mqttCommunication.SendCommand(new Command(data), _cancellationToken);

            if (callback == null || !callback.Success)
            {
                if (callback != null)
                {
                    Log.Warning("Command {Command} has failed with {Error}.",
                        callback.Command, callback.Error);
                }
            }
        }

        private async void SetHomeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var data = new Dictionary<string, string>
            {
                {"command", "sethome"},
                {"group", Settings.Default.Group},
                {"password", Settings.Default.Password}
            };

            var callback = await _mqttCommunication.SendCommand(new Command(data), _cancellationToken);

            if (callback == null || !callback.Success)
            {
                toolStripStatusLabel1.Text = "Could not set home.";

                return;
            }

            toolStripStatusLabel1.Text = "Home set.";
        }

        private async void GoHomeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var data = new Dictionary<string, string>
            {
                {"command", "gohome"},
                {"group", Settings.Default.Group},
                {"password", Settings.Default.Password}
            };

            var callback = await _mqttCommunication.SendCommand(new Command(data), _cancellationToken);

            if (callback == null || !callback.Success)
            {
                if (callback != null)
                {
                    Log.Warning("Command {Command} has failed with {Error}.",
                        callback.Command, callback.Error);
                }

                toolStripStatusLabel1.Text = "Could not teleport home.";

                return;
            }

            toolStripStatusLabel1.Text = "Teleported home.";
        }

        private async void LandForm_Shown(object sender, EventArgs e)
        {
            await Task.WhenAll(LoadMap(), GetAvatars(), GetParcelDetails(), CreateMapImageAvatarOverlay());
        }

        #endregion

        #region Private Methods

        private async Task CreateMapImageAvatarOverlay()
        {
            try
            {
                do
                {
                    try
                    {
                        await Task.Delay(1000, _cancellationToken);

                        if (_mapImage == null)
                        {
                            continue;
                        }

                        var bitmap = new Bitmap(_mapImage.Width, _mapImage.Height);
                        using (var graphics = Graphics.FromImage(bitmap))
                        {
                            graphics.DrawImage(_mapImage, Point.Empty);
                            foreach (var mapAvatarPosition in _mapAvatarPositions.Values)
                            {
                                graphics.FillEllipse(_mapAvatarPositionBrushOuter,
                                    mapAvatarPosition.X - _mapAvatarPositionOuterDiameter / 2,
                                    _mapImage.Height - mapAvatarPosition.Y - _mapAvatarPositionOuterDiameter / 2,
                                    _mapAvatarPositionOuterDiameter, _mapAvatarPositionOuterDiameter);
                                graphics.FillEllipse(_mapAvatarPositionBrush,
                                    mapAvatarPosition.X - _mapAvatarPositionDiameter / 2,
                                    _mapImage.Height - mapAvatarPosition.Y - _mapAvatarPositionDiameter / 2,
                                    _mapAvatarPositionDiameter, _mapAvatarPositionDiameter);

                                listBox1.InvokeIfRequired(listBox =>
                                {
                                    if (listBox.SelectedIndex == -1)
                                    {
                                        return;
                                    }

                                    if ((string) listBox.SelectedItem == mapAvatarPosition.Name)
                                    {
                                        graphics.DrawEllipse(_mapAvatarPositionBrushSelect,
                                            mapAvatarPosition.X - _mapAvatarPositionSelectDiameter / 2,
                                            _mapImageAvatars.Height - mapAvatarPosition.Y -
                                            _mapAvatarPositionSelectDiameter / 2, _mapAvatarPositionSelectDiameter,
                                            _mapAvatarPositionSelectDiameter);
                                    }
                                });
                            }
                        }

                        if (_mapImageAvatars != null)
                        {
                            _mapImageAvatars?.Dispose();
                            _mapImageAvatars = null;
                        }

                        _mapImageAvatars = bitmap;

                        pictureBox1.InvokeIfRequired(pictureBox =>
                        {
                            if (pictureBox.Image != null)
                            {
                                pictureBox.Image.Dispose();
                                pictureBox.Image = null;
                            }

                            pictureBox.Image = bitmap;
                        });
                    }
                    catch (Exception ex)
                    {
                        Log.Warning(ex, "Unable to create and populate map and avatar image.");
                    }
                } while (!_cancellationToken.IsCancellationRequested);
            }
            catch (Exception ex) when (ex is ObjectDisposedException || ex is OperationCanceledException)
            {
            }
            catch (Exception ex)
            {
                Log.Warning(ex, "Map and avatar image thread failure.");
            }
        }

        private static IEnumerable<string[]> CsvStride(string input, int stride)
        {
            var i = 0;
            return new CSV(input).Select(s => new {s, num = i++})
                .GroupBy(t => t.num / stride, t => t.s)
                .Select(g => g.ToArray());
        }

        private async Task GetParcelDetails()
        {
            var data = new Dictionary<string, string>
            {
                {"command", "getparceldata"},
                {"group", Settings.Default.Group},
                {"password", Settings.Default.Password},
                {"data", new CSV(new[] {"Name", "Desc"})}
            };

            var callback = await _mqttCommunication.SendCommand(new Command(data), _cancellationToken);

            if (callback == null || !callback.Success)
            {
                if (callback != null)
                {
                    Log.Warning("Command {Command} has failed with {Error}.",
                        callback.Command, callback.Error);
                }

                return;
            }

            textBox1.InvokeIfRequired(textBox => { textBox.Text = callback["Name"].FirstOrDefault(); });

            richTextBox2.InvokeIfRequired(textBox => { textBox.Text = callback["Desc"].FirstOrDefault(); });
        }

        private async Task GetAvatars()
        {
            try
            {
                do
                {
                    await Task.Delay(1000, _cancellationToken);

                    if (!_mqttCommunication.IsConnected)
                    {
                        continue;
                    }

                    var data = new Dictionary<string, string>
                    {
                        {"command", "getavatarpositions"},
                        {"group", Settings.Default.Group},
                        {"password", Settings.Default.Password},
                        {"entity", "region"}
                    };

                    var callback = await _mqttCommunication.SendCommand(new Command(data), _cancellationToken);

                    if (callback == null || !callback.Success)
                    {
                        if (callback != null)
                        {
                            Log.Warning("Command {Command} has failed with {Error}.",
                                callback.Command, callback.Error);
                        }

                        continue;
                    }

                    // Add avatars.
                    _mapAvatarPositions.Clear();
                    foreach (var tuple in CsvStride(callback.Data, 3))
                    {
                        var name = tuple[0];
                        var id = tuple[1];
                        var position = tuple[2];

                        // Update avatar list.
                        listBox1.InvokeIfRequired(listBox =>
                        {
                            if (listBox.Items.OfType<string>().All(item => item != name))
                            {
                                listBox.Items.Add(name);
                            }
                        });

                        var vector = position.Split('<', '>', ',', ' ')
                            .Where(segment => !string.IsNullOrEmpty(segment)).ToArray();

                        if (!float.TryParse(vector[0], out var x) ||
                            !float.TryParse(vector[1], out var y) ||
                            !float.TryParse(vector[2], out var z))
                        {
                            continue;
                        }

                        _mapAvatarPositions.TryAdd(name, new MapAvatarPosition(name, x, y, z));
                    }

                    // Remove avatars that are not there anymore.
                    listBox1.InvokeIfRequired(listBox =>
                    {
                        var remove = new List<string>();
                        foreach (var avatar in listBox.Items.OfType<string>())
                        {
                            if (!_mapAvatarPositions.ContainsKey(avatar))
                            {
                                remove.Add(avatar);
                            }
                        }

                        foreach (var avatar in remove)
                        {
                            listBox.Items.Remove(avatar);
                        }
                    });
                } while (!_cancellationToken.IsCancellationRequested);
            }
            catch (Exception ex) when (ex is ObjectDisposedException || ex is OperationCanceledException)
            {
            }
            catch (Exception ex)
            {
                Log.Warning(ex, "Error while retrieving avatars.");
            }
        }

        private async Task LoadMap()
        {
            var data = new Dictionary<string, string>
            {
                {"command", "getgridregiondata"},
                {"group", Settings.Default.Group},
                {"password", Settings.Default.Password},
                {"data", new CSV(new[] {"MapImageID", "Name"})}
            };

            var callback = await _mqttCommunication.SendCommand(new Command(data), _cancellationToken);

            if (callback == null || !callback.Success)
            {
                if (callback != null)
                {
                    Log.Warning("Command {Command} has failed with {Error}.",
                        callback.Command, callback.Error);
                }

                return;
            }

            var regionName = callback["Name"].FirstOrDefault();
            if (!string.IsNullOrEmpty(regionName))
            {
                textBox2.InvokeIfRequired(textBox => { textBox.Text = regionName; });
            }

            var callbackMapImageId = callback["MapImageID"].FirstOrDefault();

            if (!string.IsNullOrEmpty(callbackMapImageId) && Guid.TryParse(callbackMapImageId, out var mapImageId) &&
                mapImageId != Guid.Empty)
            {
                var mapImage =
                    await Communication.Utilities.DownloadImage(mapImageId, _mainForm.MqttCommunication,
                        _cancellationToken);
                if (mapImage != null)
                {
                    if (_mapImage != null)
                    {
                        _mapImage.Dispose();
                        _mapImage = null;
                    }

                    _mapImage = mapImage;
                }
            }
        }

        #endregion
    }
}

Generated by GNU Enscript 1.6.5.90.