source: trunk/sources/HeuristicLab.PluginInfrastructure.GUI/ManagerForm.cs @ 1394

Last change on this file since 1394 was 1394, checked in by gkronber, 12 years ago

Fixed #526 (UI feedback when starting plugin-manager). Mouse cursor is changed before loading the plugin manager form and set back to normal once all plugins are loaded.

File size: 42.4 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2008 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.ComponentModel;
25using System.Data;
26using System.Drawing;
27using System.Text;
28using System.Windows.Forms;
29using System.Diagnostics;
30using System.Net;
31using System.IO;
32using ICSharpCode.SharpZipLib.Zip;
33
34namespace HeuristicLab.PluginInfrastructure.GUI {
35  public partial class ManagerForm : Form {
36    private List<PluginTag> allTags = new List<PluginTag>();
37    private Dictionary<PluginTag, PluginAction> actions = new Dictionary<PluginTag, PluginAction>();
38    private List<PluginDescription> allAvailablePlugins = new List<PluginDescription>();
39    private string pluginDir = Application.StartupPath + "/" + HeuristicLab.PluginInfrastructure.Properties.Settings.Default.PluginDir;
40    private string cacheDir = Application.StartupPath + "/" + HeuristicLab.PluginInfrastructure.GUI.Properties.Settings.Default.CacheDir;
41    private string backupDir = Application.StartupPath + "/" + HeuristicLab.PluginInfrastructure.GUI.Properties.Settings.Default.BackupDir;
42    private string tempDir = Application.StartupPath + "/" + HeuristicLab.PluginInfrastructure.GUI.Properties.Settings.Default.TempDir;
43    private const string AVAILABLE_PLUGINS = "Available plugins";
44    private const string DISABLED_PLUGINS = "Disabled plugins";
45    private const string INSTALLED_PLUGINS = "Installed plugins";
46
47    public ManagerForm() {
48      InitializeComponent();
49      InitializePlugins();
50      Cursor = Cursors.Arrow;
51    }
52
53    private void InitializePlugins() {
54      listView.View = View.Details;
55      listView.FullRowSelect = true;
56      listView.Items.Clear();
57      allTags.Clear();
58      actions.Clear();
59
60      publishButton.Enabled = false;
61      publishMenuItem.Enabled = false;
62      installButton.Enabled = false;
63      installButton.Checked = false;
64      installMenuItem.Enabled = false;
65      installMenuItem.Checked = false;
66      deleteButton.Enabled = false;
67      deleteButton.Checked = false;
68      deleteMenuItem.Enabled = false;
69      deleteMenuItem.Checked = false;
70
71      foreach(PluginInfo pluginInfo in PluginManager.Manager.ActivePlugins) {
72        // create a new PluginAction tag for the plugin
73        PluginTag tag = new PluginTag(allTags, pluginInfo, PluginState.Installed);
74        allTags.Add(tag);
75        // add to "installed plugins" item
76        ListViewItem installedPluginsItem = new ListViewItem(pluginInfo.Name);
77        installedPluginsItem.Tag = tag;
78        installedPluginsItem.ImageIndex = 0;
79        installedPluginsItem.Group = listView.Groups[INSTALLED_PLUGINS];
80        installedPluginsItem.SubItems.Add(pluginInfo.Version.ToString());
81        listView.Items.Add(installedPluginsItem);
82      }
83      foreach(PluginInfo pluginInfo in PluginManager.Manager.DisabledPlugins) {
84        PluginTag tag = new PluginTag(allTags, pluginInfo, PluginState.Disabled);
85        allTags.Add(tag);
86        ListViewItem disabledPluginsItem = new ListViewItem(pluginInfo.Name);
87        disabledPluginsItem.Tag = tag;
88        disabledPluginsItem.ImageIndex = 0;
89        disabledPluginsItem.Group = listView.Groups[DISABLED_PLUGINS];
90        disabledPluginsItem.SubItems.Add(pluginInfo.Version.ToString());
91        listView.Items.Add(disabledPluginsItem);
92      }
93
94      allAvailablePlugins = FilterMostRecentPluginVersions(allAvailablePlugins);
95      // find all plugins that are installed for which a new version is available
96      List<PluginDescription> upgrades = FindUpgrades(allTags, allAvailablePlugins);
97      // find all available plugins that are not installed and new (= new name not new version) since the last update
98      List<PluginDescription> newPlugins = FindNewPlugins(allTags, allAvailablePlugins);
99      // find all plugins that are available (not installed) for which a new version has been released since the last update
100      List<PluginDescription> overridingPlugins = FindOverridingPlugins(allTags, allAvailablePlugins);
101      newPlugins.ForEach(delegate(PluginDescription plugin) {
102        PluginTag tag = new PluginTag(allTags, plugin, PluginState.Available);
103        allTags.Add(tag);
104        ListViewItem availableItem = new ListViewItem(plugin.Name);
105        availableItem.Tag = tag;
106        availableItem.ImageIndex = 0;
107        availableItem.Group = listView.Groups[AVAILABLE_PLUGINS];
108        availableItem.SubItems.Add(plugin.Version.ToString());
109        listView.Items.Add(availableItem);
110      });
111      upgrades.ForEach(delegate(PluginDescription upgrade) {
112        // find the installed plugins that have the same name
113        List<PluginTag> oldPlugins = allTags.FindAll(delegate(PluginTag tag) {
114          return tag.PluginName == upgrade.Name;
115        });
116        PluginTag oldPlugin = oldPlugins[0];
117        // store the upgrade in the old plugin
118        oldPlugin.UpgradePluginDescription = upgrade;
119        UpdateTreeNodes(oldPlugins);
120      });
121      overridingPlugins.ForEach(delegate(PluginDescription overridingPlugin) {
122        List<PluginTag> currentPlugins = allTags.FindAll(delegate(PluginTag tag) {
123          return tag.PluginName == overridingPlugin.Name;
124        });
125        PluginTag currentPlugin = currentPlugins[0];
126        // replace the plugin description of the available plugin to point to the overriding plugin
127        currentPlugin.PluginDescription = overridingPlugin;
128      });
129      toolStripStatusLabel.Text = "Installed: " + listView.Groups[INSTALLED_PLUGINS].Items.Count +
130        " Updates: " + upgrades.Count + " Available: " + listView.Groups[AVAILABLE_PLUGINS].Items.Count;
131      RebuildActionHulls();
132
133      listView.Sort();
134    }
135
136    private void aboutToolStripMenuItem_Click(object sender, EventArgs e) {
137      AboutBox box = new AboutBox();
138      box.ShowDialog();
139    }
140
141    private void publishButton_Click(object sender, EventArgs args) {
142      if(listView.SelectedItems.Count == 0) return;
143      List<PluginInfo> plugins = new List<PluginInfo>();
144      foreach(ListViewItem item in listView.SelectedItems) {
145        plugins.Add(((PluginTag)item.Tag).Plugin);
146      }
147      PublishPlugin(plugins);
148    }
149
150    private void PublishPlugin(List<PluginInfo> plugins) {
151      StringBuilder xmlEntries = new StringBuilder();
152      try {
153        infoTextBox.Text = "Publishing plugin:\n";
154        foreach(PluginInfo plugin in plugins) {
155          string packageFileName = plugin.Name + "-" + plugin.Version + ".zip";
156          ZipFile zipFile = ZipFile.Create(packageFileName);
157          zipFile.NameTransform = new PluginNameTransform();
158          zipFile.BeginUpdate();
159
160          infoTextBox.Text+="Creating " + packageFileName + "...\n";
161          foreach(string filename in plugin.Files) {
162            infoTextBox.Text += "Adding " + filename + "\n";
163            zipFile.Add(filename);
164          }
165
166          zipFile.CommitUpdate();
167          zipFile.Close();
168          FileInfo fileInfo = new FileInfo(packageFileName);
169          infoTextBox.Text += "Created " + packageFileName + " (" + fileInfo.Length + " bytes)\n";
170          xmlEntries.Append("  <Plugin Name=\"").Append(plugin.Name).Append("\" Version=\"").Append(plugin.Version)
171            .Append("\" Build=\"").Append(plugin.BuildDate.ToUniversalTime().ToString(System.Globalization.CultureInfo.InvariantCulture.DateTimeFormat))
172            .Append("\">").AppendLine();
173          foreach(PluginInfo dependency in plugin.Dependencies) {
174            xmlEntries.Append("    <Dependency Name=\"").Append(dependency.Name).Append("\" />").AppendLine();
175          }
176          xmlEntries.Append("  </Plugin>").AppendLine();
177        }
178
179        infoTextBox.Text += "Upload the zip files to your plugin source and add the following to" +
180" the file plugins.xml residing in the base directory of your plugin source.\n\n";
181        infoTextBox.Text += xmlEntries.ToString();
182      } catch(Exception exception) {
183        infoTextBox.Text += "\nThere was an error!\n" + exception;
184      }
185    }
186
187    private void updateButton_Click(object sender, EventArgs e) {
188      // connect to all plugin sources and get a list of available plugins
189      // log progress in the infoPane
190      BackgroundWorker worker = new BackgroundWorker();
191      worker.WorkerSupportsCancellation = true;
192      worker.WorkerReportsProgress = true;
193
194      DownloaderDialog dialog = new DownloaderDialog();
195      dialog.OnCancel += delegate() {
196        worker.CancelAsync();
197      };
198
199      worker.ProgressChanged += delegate(object progressChangedSender, ProgressChangedEventArgs args) {
200        dialog.SetProgress(args.ProgressPercentage);
201      };
202
203      worker.DoWork += delegate(object doWorkSender, DoWorkEventArgs args) {
204        allAvailablePlugins.Clear();
205        dialog.SetDownloadDescription("Updating available plugins...");
206        int i = 0;
207        int n = HeuristicLab.PluginInfrastructure.GUI.Properties.Settings.Default.PluginSources.Count;
208        foreach(string pluginSourceUrl in HeuristicLab.PluginInfrastructure.GUI.Properties.Settings.Default.PluginSources) {
209          if(!worker.CancellationPending) {
210            dialog.SetDownloadDescription("Connecting to " + pluginSourceUrl + "...");
211            PluginSource source = PluginSource.TryCreate(pluginSourceUrl);
212            if(source == null) {
213              Invoke((MethodInvoker)delegate() {
214                infoTextBox.Text += "Error! Couldn't access the plugin source " + pluginSourceUrl + ".\n";
215              });
216            } else {
217              dialog.SetDownloadDescription("Getting list of available plugins from " + pluginSourceUrl + "...");
218              List<PluginDescription> availablePlugins = source.AvailablePlugins();
219              allAvailablePlugins.AddRange(availablePlugins);
220              Invoke((MethodInvoker)delegate() {
221                infoTextBox.Text += pluginSourceUrl + " " + availablePlugins.Count + " plugins available.\n";
222              });
223
224              worker.ReportProgress((int)((++i / (double)n) * 100.0));
225            }
226          }
227        }
228        if(worker.CancellationPending) {
229          args.Cancel = true;
230        } else {
231          args.Cancel = false;
232          args.Result = allAvailablePlugins;
233        }
234      };
235
236      worker.RunWorkerCompleted += delegate(object runWorkerCompletedSender, RunWorkerCompletedEventArgs args) {
237        if(!args.Cancelled && args.Error == null) {
238          InitializePlugins();
239        }
240        dialog.Close();
241      };
242
243      dialog.Show();
244      worker.RunWorkerAsync();
245    }
246
247
248    private List<PluginDescription> FilterMostRecentPluginVersions(List<PluginDescription> list) {
249      List<PluginDescription> newList = new List<PluginDescription>();
250
251      list.ForEach(delegate(PluginDescription tag) {
252        // find all entries with the same plugin name
253        List<PluginDescription> samePlugins = list.FindAll(delegate(PluginDescription otherTag) {
254          return tag.Name == otherTag.Name;
255        });
256
257        // keep only the most recent one
258        PluginDescription mostRecentVersion = samePlugins[0];
259        if(samePlugins.Count > 1) {
260          samePlugins.ForEach(delegate(PluginDescription tag2) {
261            if(tag2.Version > mostRecentVersion.Version ||
262              (tag2.Version == mostRecentVersion.Version && tag2.BuildDate>mostRecentVersion.BuildDate)) {
263              mostRecentVersion = tag2;
264            }
265          });
266        }
267        if(!newList.Contains(mostRecentVersion)) {
268          newList.Add(mostRecentVersion);
269        }
270
271      });
272
273      return newList;
274    }
275
276    private List<PluginDescription> FindNewPlugins(List<PluginTag> allTags, List<PluginDescription> allAvailablePlugins) {
277      List<PluginDescription> newPlugins = new List<PluginDescription>();
278      // for each of the available plugins check if there is an installed plugin that has the same name
279      // only if there is no installed plugin with the same name then it is a new plugin
280      // NOTE: make sure to keep only the most recent entry of a plugin in the allAvailablePlugins list
281      allAvailablePlugins.ForEach(delegate(PluginDescription availablePlugin) {
282        if(!allTags.Exists(delegate(PluginTag tag2) {
283          return availablePlugin.Name == tag2.PluginName;
284        })) {
285          newPlugins.Add(availablePlugin);
286        }
287      });
288
289      return newPlugins;
290    }
291
292    private List<PluginDescription> FindUpgrades(List<PluginTag> allTags, List<PluginDescription> allAvailablePlugins) {
293      List<PluginDescription> upgrades = new List<PluginDescription>();
294      // for each of the available plugins check if there is an installed plugin that has the same name
295      // only if there is an installed plugin with the same name and the available plugin has a more recent version it is an upgrade
296      // NOTE: make sure to keep only the most recent entry of a plugin in the allAvailablePlugins list
297      allAvailablePlugins.ForEach(delegate(PluginDescription availablePlugin) {
298        List<PluginTag> oldPlugins = allTags.FindAll(delegate(PluginTag currentPlugin) {
299          return currentPlugin.PluginName == availablePlugin.Name &&
300            (currentPlugin.State & (PluginState.Installed | PluginState.Disabled)) != 0;
301        });
302
303        if(oldPlugins.Count == 1) {
304          if(oldPlugins[0].PluginVersion < availablePlugin.Version ||
305            (oldPlugins[0].PluginVersion == availablePlugin.Version && oldPlugins[0].PluginBuildDate<availablePlugin.BuildDate)) {
306            upgrades.Add(availablePlugin);
307          }
308        }
309      });
310
311      return upgrades;
312    }
313
314    private List<PluginDescription> FindOverridingPlugins(List<PluginTag> allTags, List<PluginDescription> allAvailablePlugins) {
315      List<PluginDescription> overrides = new List<PluginDescription>();
316      allAvailablePlugins.ForEach(delegate(PluginDescription availablePlugin) {
317        List<PluginTag> currentPlugins = allTags.FindAll(delegate(PluginTag currentPlugin) {
318          return currentPlugin.PluginName == availablePlugin.Name && currentPlugin.State == PluginState.Available;
319        });
320
321        if(currentPlugins.Count == 1 && (currentPlugins[0].PluginVersion < availablePlugin.Version ||
322          (currentPlugins[0].PluginVersion==availablePlugin.Version && currentPlugins[0].PluginBuildDate<availablePlugin.BuildDate))) {
323          overrides.Add(availablePlugin);
324        }
325      });
326
327      return overrides;
328    }
329
330    private void RebuildActionHulls() {
331      Dictionary<PluginTag, PluginAction> oldActions = new Dictionary<PluginTag, PluginAction>(actions);
332      actions.Clear();
333
334      foreach(PluginAction action in oldActions.Values) {
335        if(action.Action == ManagerAction.Install) {
336          MarkInstall(action.Plugin);
337        } else if(action.Action == ManagerAction.Remove) {
338          MarkRemove(action.Plugin);
339        } else
340          throw new InvalidOperationException();
341      }
342
343      // update the GUI to represent new state of the selected plugin
344      if(listView.SelectedItems.Count>0 && listView.SelectedItems[0].Tag is PluginTag) {
345        UpdateActionButtons((PluginTag)listView.SelectedItems[0].Tag);
346        DisplayPluginInfo(((PluginTag)listView.SelectedItems[0].Tag).GetPluginDetails());
347      }
348    }
349
350    private void MarkInstall(PluginTag actionTag) {
351      if(!CheckInstallConflicts(actionTag)) {
352        CreateNewInstallAction(actionTag);
353      } else {
354        HandleInstallConflict(actionTag);
355      }
356    }
357
358    private void UnmarkInstall(PluginTag actionTag) {
359      if(!CheckNoActionConflict(actionTag)) {
360        List<PluginAction> rootActions = GetActionsInvolving(actionTag);
361        PluginAction rootAction = rootActions[0];
362        actions.Remove(rootAction.Plugin);
363        UpdateTreeNodes(rootAction.Hull);
364      } else {
365        HandleNoActionConflict(actionTag);
366      }
367    }
368
369    private void HandleNoActionConflict(PluginTag actionTag) {
370      List<PluginAction> conflictingActions = GetOverlappingActions(actionTag, ManagerAction.Any);
371      PluginAction theAction = GetSmallestActionInvolving(actionTag, conflictingActions);
372      conflictingActions.Remove(theAction);
373      string action = theAction.GetInverseActionString();
374      DialogResult userResult = ShowGenericConflictDialog(action, theAction, conflictingActions);
375      if(userResult == DialogResult.OK) {
376        conflictingActions.ForEach(delegate(PluginAction conflictingAction) {
377          actions.Remove(conflictingAction.Plugin);
378          UpdateTreeNodes(conflictingAction.Hull);
379        });
380
381        PluginAction rootAction = GetSmallestActionInvolving(actionTag, GetOverlappingActions(actionTag, ManagerAction.Any));
382        actions.Remove(rootAction.Plugin);
383        UpdateTreeNodes(rootAction.Hull);
384      }
385    }
386
387    private bool CheckNoActionConflict(PluginTag actionTag) {
388      return GetOverlappingActions(actionTag, ManagerAction.Any).Count > 1;
389    }
390
391    private void MarkRemove(PluginTag actionTag) {
392      if(!CheckRemoveConflicts(actionTag)) {
393        CreateNewRemoveAction(actionTag);
394      } else {
395        HandleRemoveConflict(actionTag);
396      }
397    }
398
399    private void UnmarkRemove(PluginTag pluginTag) {
400      if(!CheckNoActionConflict(pluginTag)) {
401        List<PluginAction> rootActions = GetActionsInvolving(pluginTag);
402        // if there is no conflict then there can only be one action connected to the pluginTag
403        PluginAction rootAction = rootActions[0];
404        // kill the root action
405        actions.Remove(rootAction.Plugin);
406        UpdateTreeNodes(rootAction.Hull);
407      } else {
408        HandleNoActionConflict(pluginTag);
409      }
410    }
411
412    private void CreateNewRemoveAction(PluginTag actionTag) {
413      CreateNewAction(actionTag, ManagerAction.Remove, actionTag.GetDependentTags());
414    }
415
416    private void CreateNewInstallAction(PluginTag actionTag) {
417      CreateNewAction(actionTag, ManagerAction.Install, actionTag.GetDependencyTags());
418    }
419
420    private void CreateNewAction(PluginTag actionTag, ManagerAction action, List<PluginTag> hull) {
421      PluginAction newAction = new PluginAction();
422      newAction.Action = action;
423      newAction.Plugin = actionTag;
424      newAction.Hull = hull;
425      newAction.Hull.Add(actionTag);
426      actions[actionTag] = newAction;
427      UpdateTreeNodes(newAction.Hull);
428    }
429
430    private bool CheckRemoveConflicts(PluginTag actionTag) {
431      return GetOverlappingActions(actionTag, ManagerAction.Install).Count > 0;
432    }
433
434    private void HandleRemoveConflict(PluginTag actionTag) {
435      List<PluginAction> conflictingActions = GetOverlappingActions(actionTag, ManagerAction.Install);
436
437      // create a planned action to display in the conflict message box
438      PluginAction plannedRemoveAction = new PluginAction();
439      plannedRemoveAction.Action = ManagerAction.Remove;
440      plannedRemoveAction.Plugin = actionTag;
441
442      DialogResult userResult = ShowGenericConflictDialog("Remove", plannedRemoveAction, conflictingActions);
443      if(userResult == DialogResult.OK) {
444        // if user says ok then remove the old actions and create a new remove action
445        conflictingActions.ForEach(delegate(PluginAction conflictingAction) {
446          actions.Remove(conflictingAction.Plugin);
447          UpdateTreeNodes(conflictingAction.Hull);
448        });
449
450        CreateNewRemoveAction(actionTag);
451      }
452    }
453
454    private bool CheckInstallConflicts(PluginTag actionTag) {
455      return GetOverlappingActions(actionTag, ManagerAction.Remove).Count > 0;
456    }
457
458    private void HandleInstallConflict(PluginTag actionTag) {
459      List<PluginAction> conflictingActions = GetOverlappingActions(actionTag, ManagerAction.Remove);
460
461      // create a planned action to display in the conflict message box
462      PluginAction plannedInstallAction = new PluginAction();
463      plannedInstallAction.Action = ManagerAction.Install;
464      plannedInstallAction.Plugin = actionTag;
465
466      DialogResult userResult = ShowGenericConflictDialog("Install", plannedInstallAction, conflictingActions);
467
468      if(userResult == DialogResult.OK) {
469        conflictingActions.ForEach(delegate(PluginAction conflictingAction) {
470          actions.Remove(conflictingAction.Plugin);
471          UpdateTreeNodes(conflictingAction.Hull);
472        });
473
474        CreateNewInstallAction(actionTag);
475      }
476    }
477
478    private DialogResult ShowGenericConflictDialog(string action, PluginAction theAction, List<PluginAction> conflictingActions) {
479      string message = "The actions:\n\n";
480      conflictingActions.ForEach(delegate(PluginAction conflictingAction) {
481        message += " - " + conflictingAction.Action + " " + conflictingAction.Plugin.PluginName + "\n";
482      });
483      message += "\nconflict with the action: " + action + " " + theAction.Plugin.PluginName + ".\n\n";
484      message += "\nSolution:\n";
485      conflictingActions.ForEach(delegate(PluginAction conflictingAction) {
486        message += " - " + conflictingAction.GetInverseActionString() + " " + conflictingAction.Plugin.PluginName + "\n";
487      });
488      message += " - " + action + " " + theAction.Plugin.PluginName + "\n\n";
489      message += "Do you want to continue?";
490
491      return MessageBox.Show(message, "Resolve conflicting actions", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning);
492
493    }
494
495    private List<PluginAction> GetActionsInvolving(PluginTag actionTag) {
496      List<PluginAction> filteredActions = new List<PluginAction>();
497      foreach(PluginAction action in actions.Values) {
498        if(action.Hull.Contains(actionTag)) {
499          filteredActions.Add(action);
500        }
501      }
502      return filteredActions;
503    }
504
505    private PluginAction GetSmallestActionInvolving(PluginTag actionTag, List<PluginAction> actions) {
506      PluginAction smallestAction;
507      // if there is an action defined for which actionTag is the root then us it as root tag
508      // otherwise use the root of the action that contains pluginTag in its hull and has the smallest hull of all actions
509      smallestAction = actions.Find(delegate(PluginAction action) {
510        return action.Plugin == actionTag;
511      });
512      // if nothing is found then get the action with the smallest hull
513      if(smallestAction == null) {
514        // find the action with the smallest hull
515        smallestAction = actions[0];
516        actions.ForEach(delegate(PluginAction action) {
517          if(action.Hull.Count < smallestAction.Hull.Count)
518            smallestAction = action;
519        });
520      }
521
522      return smallestAction;
523    }
524
525    /// <summary>
526    /// Similar to GetRootActions but filter only specific ManagerActions
527    /// </summary>
528    /// <param name="pluginTag"></param>
529    /// <param name="overlapAction"></param>
530    /// <returns></returns>
531    private List<PluginAction> GetOverlappingActions(PluginTag actionTag, ManagerAction overlapAction) {
532      List<PluginAction> overlappingActions = new List<PluginAction>();
533      foreach(PluginAction action in actions.Values) {
534        if(((action.Action & overlapAction) > 0) &&
535          action.Hull.Contains(actionTag)) {
536          overlappingActions.Add(action);
537        }
538      }
539
540      return overlappingActions;
541    }
542
543    private void UpdateTreeNodes(List<PluginTag> hull) {
544      hull.ForEach(delegate(PluginTag tag) {
545        FindPluginItems(tag).ForEach(delegate(ListViewItem item) {
546          ManagerAction action = GetAction(tag);
547          if(action != ManagerAction.None) {
548            item.Text = tag.PluginName + " - Action: " + action;
549            if(action == ManagerAction.Remove) {
550              item.ImageIndex = 3;
551            } else if(action == ManagerAction.Install) {
552              item.ImageIndex = 2;
553            }
554          } else if(tag.State == PluginState.Upgradeable) {
555            item.Text = tag.PluginName + " - Upgrade: " + tag.PluginVersion + " -> " + tag.UpgradePluginDescription.Version;
556            item.ImageIndex = 2;
557          } else {
558            item.Text = tag.PluginName;
559            item.ImageIndex = 0;
560          }
561        });
562      });
563    }
564
565    private void UpdateActionButtons(PluginTag tag) {
566      publishButton.Enabled = (tag.State & PluginState.Installed) == PluginState.Installed;
567      installButton.Enabled = (tag.State & PluginState.Available) == PluginState.Available;
568      deleteButton.Enabled = (tag.State & (PluginState.Installed | PluginState.Upgradeable | PluginState.Disabled)) != 0;
569
570      installButton.Checked = GetAction(tag) == ManagerAction.Install;
571      deleteButton.Checked = GetAction(tag) == ManagerAction.Remove;
572
573      publishMenuItem.Enabled = publishButton.Enabled;
574      installMenuItem.Enabled = installButton.Enabled;
575      deleteMenuItem.Enabled = deleteButton.Enabled;
576      installMenuItem.Checked = installButton.Checked;
577      deleteMenuItem.Checked = deleteButton.Checked;
578    }
579
580    private ManagerAction GetAction(PluginTag tag) {
581      ManagerAction plannedAction = ManagerAction.None;
582      if(actions.ContainsKey(tag)) {
583        plannedAction = actions[tag].Action;
584      } else {
585        foreach(PluginAction action in actions.Values) {
586          if(action.Hull.Contains(tag)) {
587            plannedAction = action.Action;
588          }
589        }
590      }
591
592      if(plannedAction == ManagerAction.Install
593        && (tag.State == PluginState.Installed || tag.State == PluginState.Upgradeable)) {
594        return ManagerAction.None;
595      } else if(plannedAction == ManagerAction.Remove
596        && tag.State == PluginState.Available) {
597        return ManagerAction.None;
598      } else {
599        return plannedAction;
600      }
601    }
602
603    private List<ListViewItem> FindPluginItems(PluginTag pluginTag) {
604      List<ListViewItem> items = new List<ListViewItem>();
605      foreach(ListViewGroup group in listView.Groups) {
606        foreach(ListViewItem item in group.Items) {
607          if(pluginTag.Equals(item.Tag)) {
608            items.Add(item);
609          }
610        }
611      }
612      return items;
613    }
614    private void DisplayPluginInfo(string pluginInformation) {
615      infoTextBox.Text = pluginInformation;
616    }
617    private void upgradeButton_Click(object sender, EventArgs args) {
618      try {
619        ClearTemporaryFiles();
620        if(!DownloadFiles()) {
621          return;
622        }
623        OnDeletePlugins();
624        OnPreUpgradePlugins();
625        PluginManager.Manager.UnloadAllPlugins();
626        BackupOldFiles();
627        DeleteOldFiles();
628        InstallNewFiles();
629        PluginManager.Manager.LoadAllPlugins();
630        InitializePlugins();
631        OnPostUpgradePlugins();
632        OnInstallPlugins();
633        ClearTemporaryFiles();
634      } catch(Exception e) {
635        ShowErrorDialog(e + "");
636      }
637    }
638    private void OnDeletePlugins() {
639      allTags.ForEach(delegate(PluginTag tag) {
640        if(tag.State == PluginState.Installed) {
641          List<PluginAction> actions = GetActionsInvolving(tag);
642          if(actions.Count > 0 && actions[0].Action == ManagerAction.Remove) {
643            PluginManager.Manager.OnDelete(tag.Plugin);
644          }
645        }
646      });
647    }
648    private void OnInstallPlugins() {
649      allTags.ForEach(delegate(PluginTag tag) {
650        if(tag.State == PluginState.Available) {
651          List<PluginAction> actions = GetActionsInvolving(tag);
652          if(actions.Count > 0 && actions[0].Action == ManagerAction.Install) {
653            PluginManager.Manager.OnInstall(tag.Plugin);
654          }
655        }
656      });
657    }
658    private List<string> upgradedPlugins = new List<string>();
659    private void OnPreUpgradePlugins() {
660      upgradedPlugins.Clear();
661      allTags.ForEach(delegate(PluginTag tag) {
662        if(tag.State == PluginState.Upgradeable) {
663          PluginManager.Manager.OnPreUpdate(tag.Plugin);
664
665          // save the name of the plugin in  a list that is used later to call OnPostUpdate for all plugins
666          upgradedPlugins.Add(tag.PluginName);
667        }
668      });
669    }
670    private void OnPostUpgradePlugins() {
671      allTags.ForEach(delegate(PluginTag tag) {
672        if(upgradedPlugins.Contains(tag.PluginName)) {
673          PluginManager.Manager.OnPostUpdate(tag.Plugin);
674        }
675      });
676      upgradedPlugins.Clear();
677    }
678    /// <summary>
679    /// Deletes all files in the directories cacheDir, backupDir, tempDir
680    /// </summary>
681    private void ClearTemporaryFiles() {
682      // can't really handle exceptions here -> let higher layer handle them
683      string[] filenames = Directory.GetFiles(cacheDir, "*", SearchOption.AllDirectories);
684      foreach(string filename in filenames) {
685        File.Delete(filename);
686      }
687      filenames = Directory.GetFiles(backupDir, "*", SearchOption.AllDirectories);
688      foreach(string filename in filenames) {
689        File.Delete(filename);
690      }
691      filenames = Directory.GetFiles(tempDir, "*", SearchOption.AllDirectories);
692      foreach(string filename in filenames) {
693        File.Delete(filename);
694      }
695    }
696
697    /// <summary>
698    /// Extracts zip packages in cacheDir to tempDir and then copies the files from tempDir to pluginDir
699    /// When there is a problem on extraction or later when copying the files to the pluginDir the method
700    /// delets all files that have already been copied from tempDir to pluginDir (the filename exists in both
701    /// locations) and then copies all files in the backup directory back to the plugin directory.
702    /// </summary>
703    /// <returns></returns>
704    private void InstallNewFiles() {
705      try {
706        // extract all packages
707        string[] packages = Directory.GetFiles(cacheDir, "*", SearchOption.AllDirectories);
708        FastZip fastZip = new FastZip();
709        foreach(string package in packages) {
710          fastZip.ExtractZip(package, tempDir, String.Empty);
711        }
712
713        // copy extracted files to plugin dir
714        string[] extractedFiles = Directory.GetFiles(tempDir, "*", SearchOption.AllDirectories);
715        foreach(string extractedFile in extractedFiles) {
716          File.Copy(extractedFile, pluginDir + extractedFile.Remove(0, tempDir.Length));
717        }
718      } catch(Exception e) {
719        infoTextBox.Text = e + "";
720        // remove already copied files
721        string[] extractedFiles = Directory.GetFiles(tempDir, "*", SearchOption.AllDirectories);
722        foreach(string extractedFile in extractedFiles) {
723          string filename = pluginDir + extractedFile.Remove(0, tempDir.Length);
724          if(File.Exists(filename)) {
725            File.Delete(filename);
726          }
727        }
728
729        // restore files from backup
730        string[] backupFiles = Directory.GetFiles(backupDir, "*", SearchOption.AllDirectories);
731        foreach(string backupFile in backupFiles) {
732          File.Copy(backupFile, pluginDir + backupFile.Remove(0, backupDir.Length));
733        }
734
735        throw e;
736      }
737    }
738
739    /// <summary>
740    /// Deletes all files of plugins that have been marked as 'Remove' or 'Upgrade'.
741    /// If there is a problem when removing the files then all files that have been removed earlier
742    /// (the filename existis in backupDir but not in pluginDir) are copied from the backupDir to the pluginDir
743    /// </summary>
744    /// <returns></returns>
745    private void DeleteOldFiles() {
746      try {
747        allTags.ForEach(delegate(PluginTag tag) {
748          List<PluginAction> involvingActions = GetActionsInvolving(tag);
749
750          if(tag.State == PluginState.Upgradeable || (involvingActions.Count > 0 && involvingActions[0].Action == ManagerAction.Remove)) {
751            tag.Plugin.Files.ForEach(delegate(string filename) {
752              File.Delete(filename);
753            });
754          }
755        });
756      } catch(Exception e) {
757        infoTextBox.Text = e + "";
758        // restore deleted files from backup
759        string[] backupFiles = Directory.GetFiles(backupDir, "*", SearchOption.AllDirectories);
760        foreach(string backupFile in backupFiles) {
761          string oldFileName = pluginDir + backupFile.Remove(0, backupDir.Length);
762          if(!File.Exists(oldFileName)) {
763            File.Move(backupFile, oldFileName);
764          }
765        }
766        throw e;
767      }
768    }
769
770    /// <summary>
771    /// Copies all files of plugins that are marked with 'Remove' or 'Upgrade' to the backup directory.
772    /// When there is a problem all files in the backup directory are deleted.
773    /// </summary>
774    /// <returns></returns>
775    private void BackupOldFiles() {
776      try {
777        allTags.ForEach(delegate(PluginTag tag) {
778          List<PluginAction> actionsInvolving = GetActionsInvolving(tag);
779
780          if(tag.State == PluginState.Upgradeable || (actionsInvolving.Count > 0 && actionsInvolving[0].Action == ManagerAction.Remove)) {
781            tag.Plugin.Files.ForEach(delegate(string filename) {
782              File.Copy(filename, backupDir + filename.Remove(0, pluginDir.Length));
783            });
784          }
785        });
786      } catch(Exception e) {
787        infoTextBox.Text = e + "";
788        // delete all files in the backup directory
789        string[] copiedFiles = Directory.GetFiles(backupDir, "*", SearchOption.AllDirectories);
790        string filesString = "";
791        foreach(string fs in copiedFiles) {
792          filesString += fs + "\n";
793        }
794        foreach(string copiedFile in copiedFiles) {
795          File.Delete(copiedFile);
796        }
797        throw e;
798      }
799    }
800
801    private bool DownloadFiles() {
802      DownloaderDialog dialog = new DownloaderDialog();
803      IEnumerator<PluginTag> pluginEnumerator = allTags.GetEnumerator();
804      BackgroundWorker worker = new BackgroundWorker();
805      worker.WorkerReportsProgress = true;
806      worker.WorkerSupportsCancellation = true;
807
808      worker.DoWork += delegate(object sender, DoWorkEventArgs args) {
809        // count number of plugins to download
810        int numberOfPlugins = 0;
811        allTags.ForEach(delegate(PluginTag current) {
812          if(current.UpgradeAvailable()) {
813            numberOfPlugins++;
814          } else {
815            List<PluginAction> actionsInvolving = GetActionsInvolving(current);
816
817            if(actionsInvolving.Count > 0 && actionsInvolving[0].Action == ManagerAction.Install && current.State == PluginState.Available) {
818              numberOfPlugins++;
819            }
820          }
821        });
822
823        // download
824        int downloaded = 0;
825        Invoke((MethodInvoker)delegate() {
826          infoTextBox.Text = "Downloading " + numberOfPlugins + " plugins.\n";
827        });
828
829        allTags.ForEach(delegate(PluginTag current) {
830          if(worker.CancellationPending) {
831            args.Cancel = true;
832            return;
833          }
834          List<PluginAction> actionsInvolving = GetActionsInvolving(current);
835          if(current.UpgradeAvailable() ||
836            (actionsInvolving.Count > 0 && actionsInvolving[0].Action == ManagerAction.Install && current.State == PluginState.Available)) {
837            dialog.SetDownloadDescription("Downloading " + current.PluginName + " ...");
838            Invoke((MethodInvoker)delegate() { infoTextBox.Text += "Downloading " + current.PluginName + " ..."; });
839
840            long nBytes = 0;
841            if(current.UpgradeAvailable()) {
842              nBytes = current.UpgradePluginDescription.Source.DownloadPlugin(current.UpgradePluginDescription);
843            } else {
844              nBytes = current.PluginDescription.Source.DownloadPlugin(current.PluginDescription);
845            }
846
847            worker.ReportProgress(downloaded / numberOfPlugins);
848            Invoke((MethodInvoker)delegate() { infoTextBox.Text += " " + nBytes + " bytes.\n"; });
849          }
850        });
851      };
852
853      worker.ProgressChanged += delegate(object sender, ProgressChangedEventArgs args) {
854        dialog.SetProgress(args.ProgressPercentage);
855      };
856
857      worker.RunWorkerCompleted += delegate(object sender, RunWorkerCompletedEventArgs args) {
858        if(args.Cancelled) {
859          infoTextBox.Text += "Cancelled downloading plugins";
860          dialog.DialogResult = DialogResult.Cancel;
861        } else if(args.Error != null) {
862          infoTextBox.Text += "Error while downloading plugins:\n" + args.Error;
863          dialog.DialogResult = DialogResult.Cancel;
864        }
865        dialog.DialogResult = DialogResult.OK;
866        dialog.Close();
867      };
868
869
870      dialog.Shown += delegate(object sender, EventArgs args) {
871        worker.RunWorkerAsync();
872      };
873
874      dialog.OnCancel += delegate() {
875        worker.CancelAsync();
876        dialog.SetDownloadDescription("Cancelling download...");
877        infoTextBox.Text += "Cancelled!\n";
878      };
879
880      if(dialog.ShowDialog() == DialogResult.Cancel) {
881        return false;
882      } else {
883        return true;
884      }
885    }
886
887    private void removeButton_Clicked(object sender, EventArgs e) {
888      // get the tag of the selected listViewItem
889      PluginTag actionTag = (PluginTag)listView.SelectedItems[0].Tag;
890      List<PluginAction> rootActions = GetActionsInvolving(actionTag);
891      if(rootActions.Count > 0) {
892        UnmarkRemove(actionTag);
893      } else {
894        MarkRemove(actionTag);
895      }
896
897      // reflect the change of plugin actions in the install/remove buttons
898      UpdateActionButtons(actionTag);
899      // update the plugin detail information of the selected plugin
900      DisplayPluginInfo(actionTag.GetPluginDetails());
901    }
902
903    private void installButton_Clicked(object sender, EventArgs e) {
904      // get the tag of the selected listViewItem
905      PluginTag actionTag = (PluginTag)listView.SelectedItems[0].Tag;
906      List<PluginAction> rootActions = GetActionsInvolving(actionTag);
907
908      if(rootActions.Count > 0) {
909        UnmarkInstall(actionTag);
910      } else {
911        MarkInstall(actionTag);
912      }
913
914      // reflect the change of plugin actions in the install/remove buttons
915      UpdateActionButtons(actionTag);
916      // update the plugin detail information of the selected plugin
917      DisplayPluginInfo(actionTag.GetPluginDetails());
918    }
919
920    private void ShowErrorDialog(string message) {
921      ErrorDialog dialog = new ErrorDialog(message, "Exception");
922      dialog.ShowDialog();
923    }
924
925    private void managePluginSourcesToolStripMenuItem_Click(object sender, EventArgs e) {
926      PluginSourceEditor sourceEditor = new PluginSourceEditor();
927      sourceEditor.ShowDialog();
928    }
929
930    private void refreshPluginListToolStripMenuItem_Click(object sender, EventArgs e) {
931      PluginManager.Manager.UnloadAllPlugins();
932      PluginManager.Manager.LoadAllPlugins();
933
934      InitializePlugins();
935    }
936
937    private void closeToolStripMenuItem_Click(object sender, EventArgs e) {
938      Close();
939    }
940
941    private void installPluginFromFileToolStripMenuItem_Click(object sender, EventArgs args) {
942      OpenFileDialog dialog = new OpenFileDialog();
943      dialog.Multiselect = false;
944      DialogResult result = dialog.ShowDialog();
945      if(result == DialogResult.OK) {
946        string packageName = dialog.FileName;
947        try {
948          ClearTemporaryFiles();
949
950          FastZip fastZip = new FastZip();
951          fastZip.ExtractZip(packageName, cacheDir, String.Empty);
952
953          // check if none of the files exist
954          foreach(string filename in Directory.GetFiles(cacheDir)) {
955            if(File.Exists(pluginDir + filename.Remove(0, cacheDir.Length))) {
956              ShowErrorDialog("Sorry can't install the plugin " + packageName + "\nThe file: " + filename.Remove(0, cacheDir.Length) + " already exist in " + pluginDir + "\nIt seems the plugin is already installed.");
957              ClearTemporaryFiles();
958              return;
959            }
960          }
961
962          PluginManager.Manager.UnloadAllPlugins();
963          // move the files
964          foreach(string filename in Directory.GetFiles(cacheDir)) {
965            File.Move(filename, pluginDir + filename.Remove(0, cacheDir.Length));
966          }
967          PluginManager.Manager.LoadAllPlugins();
968          InitializePlugins();
969
970        } catch(Exception e) {
971          ShowErrorDialog(e + "");
972        } finally {
973          ClearTemporaryFiles();
974        }
975      }
976    }
977
978    private void pluginTreeView_KeyDown(object sender, KeyEventArgs e) {
979      if(e.KeyData == Keys.I && installButton.Enabled) {
980        e.Handled = true;
981        e.SuppressKeyPress = true;
982        installButton_Clicked(sender, e);
983      } else if((e.KeyData == Keys.D || e.KeyData == Keys.Delete) && deleteButton.Enabled) {
984        e.Handled = true;
985        e.SuppressKeyPress = true;
986        removeButton_Clicked(sender, e);
987      }
988    }
989
990    private void listView_MouseDown(object sender, MouseEventArgs e) {
991      // dumb solution to automatically select the node on right clicks which opens the context menu because
992      // per default the treeview doesn't select nodes on right click
993      if(e.Button == MouseButtons.Right) {
994        ListViewItem clickedItem = listView.GetItemAt(e.X, e.Y);
995        if (clickedItem != null) {
996          // clear previous selection
997          while (listView.SelectedItems.Count > 0)
998            listView.SelectedItems[0].Selected = false;
999          // select clicked item
1000          clickedItem.Selected = true;
1001        }
1002      }
1003    }
1004
1005    private void listView_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e) {
1006      if(e.Item.Tag != null && e.IsSelected) {
1007        UpdateActionButtons((PluginTag)e.Item.Tag);
1008        // display the plugin details in the lower pane
1009        DisplayPluginInfo(((PluginTag)e.Item.Tag).GetPluginDetails());
1010      } else if(e.Item.Tag!=null) {
1011        // when an item was 'unselected' or was selected but doesn't represent a plugin then install and remove are not possible
1012        publishButton.Enabled = false;
1013        installButton.Enabled = false;
1014        deleteButton.Enabled = false;
1015        publishMenuItem.Enabled = false;
1016        installMenuItem.Enabled = false;
1017        deleteMenuItem.Enabled = false;
1018      }
1019    }
1020  }
1021}
Note: See TracBrowser for help on using the repository browser.