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

Last change on this file since 600 was 600, checked in by gkronber, 14 years ago

fixed #281 (Delete button doesn't work in PluginManager GUI)

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