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

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

fixed #99 (Possibility to publish multiple plugins in one action)

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