Korero – Rev 2

Subversion Repositories:
Rev:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Korero.Communication;
using Korero.Properties;
using Korero.Selection;
using Korero.Serialization;
using Korero.Utilities;
using Serilog;

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

        private readonly CancellationToken _cancellationToken;

        private readonly CancellationTokenSource _cancellationTokenSource;

        private readonly MainForm _mainForm;

        private readonly MqttCommunication _mqttCommunication;

        private AvatarSelectionForm _avatarSelectionForm;

        private CancellationToken _refreshCancellationToken;

        private CancellationTokenSource _refreshCancellationTokenSource;

        private Task _refreshTask;

        #endregion

        #region Constructors, Destructors and Finalizers

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

            treeView1.InvokeIfRequired(treeView =>
            {
                treeView.Nodes[0].Nodes.Add(Guid.Empty.ToString(), "Loading...");
                treeView.Nodes[1].Nodes.Add(Guid.Empty.ToString(), "Loading...");
            });

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

            _refreshCancellationTokenSource = new CancellationTokenSource();
            _refreshCancellationToken = _refreshCancellationTokenSource.Token;
        }

        public InventoryForm(MainForm form, MqttCommunication mqttCommunication) : this()
        {
            _mainForm = form;
            _mqttCommunication = mqttCommunication;
        }

        /// <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();
            }

            base.Dispose(disposing);
        }

        #endregion

        #region Event Handlers
        private void InventoryForm_Load(object sender, EventArgs e)
        {
            Utilities.WindowState.FormTracker.Track(this);
        }
        private async void TreeView1_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            await UpdateTreeNode(e.Node);
        }

        private async void InventoryForm_Shown(object sender, EventArgs e)
        {
            _refreshTask = RefreshInventory();

            await _refreshTask;
        }

        private async void RefreshToolStripMenuItem_Click(object sender, EventArgs e)
        {
            _refreshCancellationTokenSource.Cancel();
            await _refreshTask;

            _refreshCancellationTokenSource = new CancellationTokenSource();
            _refreshCancellationToken = _refreshCancellationTokenSource.Token;

            _refreshTask = RefreshInventory();
            await _refreshTask;
        }

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

            var path = treeView1.SelectedNode.FullPath;

            _avatarSelectionForm = new AvatarSelectionForm(_mqttCommunication, new {Path = path, Action = "give"});
            _avatarSelectionForm.AvatarSelected += AvatarSelectionForm_AvatarSelected;
            _avatarSelectionForm.Closing += AvatarSelectionForm_Closing;
            _avatarSelectionForm.Show();
        }

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

            _avatarSelectionForm.AvatarSelected -= AvatarSelectionForm_AvatarSelected;
            _avatarSelectionForm.Closing -= AvatarSelectionForm_Closing;
            _avatarSelectionForm.Dispose();
            _avatarSelectionForm = null;
        }

        private async void AvatarSelectionForm_AvatarSelected(object sender, AvatarSelectedEventArgs e)
        {
            switch (e.Data.Action)
            {
                case "give":
                    var data = new Dictionary<string, string>
                    {
                        {"command", "give"},
                        {"group", Settings.Default.Group},
                        {"password", Settings.Default.Password},
                        {"entity", "avatar"},
                        {"item", e.Data.Path},
                        {"firstname", e.AvatarSelection.FirstName},
                        {"lastname", e.AvatarSelection.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);
                        }

                        toolStripStatusLabel1.Text =
                            $"Unable to give inventory item {e.Data.Path} to {e.AvatarSelection.FirstName} {e.AvatarSelection.LastName}";
                        return;
                    }

                    toolStripStatusLabel1.Text =
                        $"Gave {e.Data.Path} to {e.AvatarSelection.FirstName} {e.AvatarSelection.LastName}";
                    break;
            }
        }

        private void InventoryForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            _refreshCancellationTokenSource.Cancel();
            _cancellationTokenSource.Cancel();
        }

        #endregion

        #region Private Methods

        private async Task RefreshInventory()
        {
            using (var combinedCancellationTokenSource =
                CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, _refreshCancellationToken))
            {
                var cancellationToken = combinedCancellationTokenSource.Token;

                // Breadth-first traversal.
                var stack = new Queue<TreeNode>();
                stack.Enqueue(treeView1.Nodes[0]);
                stack.Enqueue(treeView1.Nodes[1]);

                do
                {
                    var node = stack.Dequeue();

                    await UpdateTreeNode(node);

                    foreach (var child in node.Nodes.OfType<TreeNode>())
                    {
                        if (child.Nodes.Count != 0)
                        {
                            stack.Enqueue(child);
                        }
                    }

                    toolStripStatusLabel1.Text = $"Updating {stack.Count} folders...";
                } while (!cancellationToken.IsCancellationRequested && stack.Count != 0);
            }
        }

        private async Task UpdateTreeNode(TreeNode node)
        {
            // Skip nodes that have already been updated.
            if (!node.Nodes.ContainsKey(Guid.Empty.ToString()))
            {
                return;
            }

            var data = new Dictionary<string, string>
            {
                {"command", "inventory"},
                {"group", Settings.Default.Group},
                {"password", Settings.Default.Password},
                {"action", "ls"},
                {"path", node.FullPath}
            };

            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;
            }

            // Remove folder dummy node.
            node.Nodes.RemoveByKey(Guid.Empty.ToString());

            foreach (var item in CsvStride(callback.Data, 10))
            {
                var name = item[1];
                var type = item[5];
                var guid = item[3];

                // If the node already exists in the tree then skip adding nodes.
                if (node.Nodes.ContainsKey(guid))
                {
                    continue;
                }

                // TODO: different menu strips depending on the item type.
                var childNode = node.Nodes.Add(guid, name);
                childNode.ContextMenuStrip = contextMenuStrip1;

                // Set the icon depending on the inventory type.
                var typeIndex = imageList1.Images.IndexOfKey(type);
                switch (typeIndex)
                {
                    case -1:
                        childNode.ImageIndex = 0;
                        childNode.SelectedImageIndex = 0;
                        break;
                    default:
                        childNode.ImageIndex = typeIndex;
                        childNode.SelectedImageIndex = typeIndex;
                        break;
                }

                switch (type)
                {
                    case "Folder":
                        // Add folder dummy node.
                        childNode.Nodes.Add(Guid.Empty.ToString(), "Loading...");
                        break;
                }
            }
        }

        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());
        }

        #endregion

        
    }
}

Generated by GNU Enscript 1.6.5.90.