private async Task InstallOrUpdateAsync(PluginControlPair pluginInfoControlsPair, string pluginUUID)
        {
            var pluginInfoItem    = pluginInfoControlsPair.Item;
            var pluginInfoDetails = pluginInfoControlsPair.Details;
            // update
            var cancelInstall     = new CancellationTokenSource();
            var pluginPackageInfo = MinerPluginsManager.Plugins[pluginUUID];

            try
            {
                AddActiveTask(pluginUUID);
                pluginInfoItem.OnButtonCancel = (s, e) => cancelInstall.Cancel();
                pluginInfoItem.SwapInstallRemoveButtonWithCancelButton(true);
                pluginInfoItem.ProgressBarVisible = true;
                pluginInfoItem.StatusVisible      = true;
                //pluginInfoItem.ButtonUpdateEnabled = false;

                pluginInfoDetails.OnButtonCancel = (s, e) => cancelInstall.Cancel();
                pluginInfoDetails.SwapInstallRemoveButtonWithCancelButton(true);
                pluginInfoDetails.ProgressBarVisible = true;
                pluginInfoDetails.StatusVisible      = true;

                pluginInfoItem.StatusText    = "Pending Install";
                pluginInfoDetails.StatusText = "Pending Install";


                var downloadAndInstallUpdate = new Progress <Tuple <ProgressState, int> >(statePerc =>
                {
                    var state      = statePerc.Item1;
                    var progress   = statePerc.Item2;
                    var statusText = "";
                    switch (state)
                    {
                    case ProgressState.DownloadingMiner:
                        statusText = $"Downloading Miner: {progress} %";
                        break;

                    case ProgressState.DownloadingPlugin:
                        statusText = $"Downloading Plugin: {progress} %";
                        break;

                    case ProgressState.ExtractingMiner:
                        statusText = $"Extracting Miner: {progress} %";
                        break;

                    case ProgressState.ExtractingPlugin:
                        statusText = $"Extracting Plugin: {progress} %";
                        break;
                    }
                    // SafeInvoke is not needed inside a Progress
                    //FormHelpers.SafeInvoke(pluginInfoItem, () => {
                    pluginInfoItem.StatusText       = statusText;
                    pluginInfoItem.ProgressBarValue = progress;
                    //});
                    //FormHelpers.SafeInvoke(pluginInfoDetails, () => {
                    pluginInfoDetails.StatusText       = statusText;
                    pluginInfoDetails.ProgressBarValue = progress;
                    //});
                });
                await MinerPluginsManager.DownloadAndInstall(pluginPackageInfo, downloadAndInstallUpdate, cancelInstall.Token);
            }
            catch (Exception e)
            {
            }
            finally
            {
                pluginInfoItem.ProgressBarVisible    = false;
                pluginInfoDetails.ProgressBarVisible = false;
                pluginInfoItem.SwapInstallRemoveButtonWithCancelButton(false);
                pluginInfoDetails.SwapInstallRemoveButtonWithCancelButton(false);
                setPluginInfoItem(pluginInfoItem, pluginPackageInfo);
                setPluginInfoDetails(pluginInfoDetails, pluginPackageInfo);
                RemoveActiveTask(pluginUUID);
            }
        }
 private async Task InstallOrUpdateAsync(PluginControlPair pluginInfoControlsPair, string pluginUUID)
 {
     await MinerPluginsManager.DownloadAndInstall(pluginUUID, pluginInfoControlsPair.Progress);
 }
        private void FormShown(object sender, EventArgs e)
        {
            // TODO blocking make it async
            MinerPluginsManager.CrossReferenceInstalledWithOnline();

            var rankedUUIDs = MinerPluginsManager.RankedPlugins.Select(plugin => plugin.PluginUUID).ToList();

            // update existing that are not in a task
            var ignoreActive = GetActiveTasks();

            foreach (var controlsPair in _pluginInfoDetails.Values)
            {
                var uuid = controlsPair.Item.PluginUUID;
                if (ignoreActive.Contains(uuid))
                {
                    continue;
                }
                var plugin = MinerPluginsManager.Plugins[uuid];
                setPluginInfoItem(controlsPair.Item, plugin);
                setPluginInfoDetails(controlsPair.Details, plugin);
            }

            var newPlugins        = MinerPluginsManager.RankedPlugins.Where(plugin => _pluginInfoDetails.ContainsKey(plugin.PluginUUID) == false).ToList();
            var lastSingleItemRow = rankedUUIDs.Count % 2 == 1;

            // create and add new plugins
            foreach (var plugin in newPlugins)
            {
                var controlsPair = new PluginControlPair
                {
                    Item    = new PluginInfoItem(),
                    Details = CreatePluginInfoDetails()
                };
                // add control pairs
                _pluginInfoDetails.Add(plugin.PluginUUID, controlsPair);
                Controls.Add(controlsPair.Details);

                setPluginInfoItem(controlsPair.Item, plugin);
                setPluginInfoDetails(controlsPair.Details, plugin);
            }

            // get row count
            var rowsNeeded = rankedUUIDs.Count / 2 + rankedUUIDs.Count % 2;
            var rowsAdded  = flowLayoutPanelPluginsLV.Controls.Count;

            // we have new rows
            if (rowsAdded < rowsNeeded)
            {
                for (int add = 0; add < rowsNeeded - rowsAdded; add++)
                {
                    var pluginRowItem = new PluginInfoItemRow();
                    flowLayoutPanelPluginsLV.Controls.Add(pluginRowItem);
                }
            }
            // we have too many
            else if (rowsAdded > rowsNeeded)
            {
                var toRemoveCount = rowsAdded - rowsNeeded;
                for (int remove = 0; remove < toRemoveCount; remove++)
                {
                    var lastIndex = flowLayoutPanelPluginsLV.Controls.Count - 1;
                    var lastRow   = flowLayoutPanelPluginsLV.Controls[lastIndex] as PluginInfoItemRow;
                    if (lastRow != null)
                    {
                        lastRow.RemovePluginInfoItem1();
                        lastRow.RemovePluginInfoItem2();
                    }
                    flowLayoutPanelPluginsLV.Controls.RemoveAt(lastIndex);
                }
            }

            for (int item = 0; item < rankedUUIDs.Count; item++)
            {
                var uuid     = rankedUUIDs[item];
                var rowIndex = (item / 2);
                var isFirst  = (item % 2) == 0;

                var controlsPair = _pluginInfoDetails[uuid];

                var row = flowLayoutPanelPluginsLV.Controls[rowIndex] as PluginInfoItemRow;
                if (row == null)
                {
                    continue;
                }
                if (isFirst)
                {
                    row.SetPluginInfoItem1(controlsPair.Item);
                }
                else
                {
                    row.SetPluginInfoItem2(controlsPair.Item);
                }
            }
            if (lastSingleItemRow)
            {
                var lastIndex = flowLayoutPanelPluginsLV.Controls.Count - 1;
                var lastRow   = flowLayoutPanelPluginsLV.Controls[lastIndex] as PluginInfoItemRow;
                if (lastRow != null)
                {
                    lastRow.RemovePluginInfoItem2();
                }
            }
        }
        private IProgress <Tuple <PluginInstallProgressState, int> > CreateProgressForPluginControlPair(PluginControlPair pluginInfoControlsPair)
        {
            var pluginInfoItem    = pluginInfoControlsPair.Item;
            var pluginInfoDetails = pluginInfoControlsPair.Details;

            var downloadAndInstallUpdate = new Progress <Tuple <PluginInstallProgressState, int> >(statePerc =>
            {
                var state      = statePerc.Item1;
                var progress   = statePerc.Item2;
                var statusText = "";
                switch (state)
                {
                case PluginInstallProgressState.Pending:
                    statusText = Tr("Pending Install");
                    break;

                case PluginInstallProgressState.DownloadingMiner:
                    statusText = Tr("Downloading Miner: {0} %", $"{progress:F2}");
                    break;

                case PluginInstallProgressState.DownloadingPlugin:
                    statusText = Tr("Downloading Plugin: {0} %", $"{progress:F2}");
                    break;

                case PluginInstallProgressState.ExtractingMiner:
                    statusText = Tr("Extracting Miner: {0} %", $"{progress:F2}");
                    break;

                case PluginInstallProgressState.ExtractingPlugin:
                    statusText = Tr("Extracting Plugin: {0} %", $"{progress:F2}");
                    break;

                default:
                    statusText = Tr("Pending Install");
                    break;
                }

                pluginInfoItem.StatusText       = statusText;
                pluginInfoItem.ProgressBarValue = progress;

                pluginInfoDetails.StatusText       = statusText;
                pluginInfoDetails.ProgressBarValue = progress;

                var installing = state < PluginInstallProgressState.FailedDownloadingPlugin;
                pluginInfoItem.SwapInstallRemoveButtonWithCancelButton(installing);
                pluginInfoItem.ProgressBarVisible = installing;
                pluginInfoItem.StatusVisible      = installing;

                pluginInfoDetails.SwapInstallRemoveButtonWithCancelButton(installing);
                pluginInfoDetails.ProgressBarVisible = installing;
                pluginInfoDetails.StatusVisible      = installing;

                if (state == PluginInstallProgressState.Success)
                {
                    var pluginPackageInfo = MinerPluginsManager.Plugins[pluginInfoItem.PluginUUID];
                    setPluginInfoItem(pluginInfoItem, pluginPackageInfo);
                    setPluginInfoDetails(pluginInfoDetails, pluginPackageInfo);
                }
            });

            return(downloadAndInstallUpdate);
        }