Changeset 29


Ignore:
Timestamp:
02/28/08 18:02:45 (13 years ago)
Author:
gkronber
Message:
  • refactored pluginInfrastructure
  • fixed #8
Location:
trunk/sources
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/sources/HeuristicLab.PluginInfrastructure.GUI/ManagerForm.cs

    r18 r29  
    3737    private TreeNode availablePlugins;
    3838    private TreeNode allPlugins;
     39    private TreeNode disabledPlugins;
    3940    private List<PluginTag> allTags = new List<PluginTag>();
    4041    private Dictionary<PluginTag, PluginAction> actions = new Dictionary<PluginTag, PluginAction>();
     
    7576      allPlugins.ImageIndex = 1;
    7677      allPlugins.SelectedImageIndex = 1;
     78      disabledPlugins = new TreeNode("Disabled plugins");
     79      disabledPlugins.ImageIndex = 1;
     80      disabledPlugins.SelectedImageIndex = 1;
    7781
    7882      pluginTreeView.Nodes.Add(installedPlugins);
    7983      pluginTreeView.Nodes.Add(availablePlugins);
     84      pluginTreeView.Nodes.Add(disabledPlugins);
    8085      pluginTreeView.Nodes.Add(allPlugins);
    8186
    82       foreach(PluginInfo pluginInfo in PluginManager.Manager.InstalledPlugins) {
     87      foreach(PluginInfo pluginInfo in PluginManager.Manager.ActivePlugins) {
    8388        // create a new PluginAction tag for the plugin
    8489        PluginTag tag = new PluginTag(allTags, pluginInfo, PluginState.Installed);
     
    9196        installedPlugins.Nodes.Add(installedPluginsNode);
    9297
     98        // add to all "plugins node"
     99        TreeNode allPluginsNode = new TreeNode(pluginInfo.Name);
     100        allPluginsNode.ContextMenuStrip = pluginContextMenuStrip;
     101        allPluginsNode.Tag = tag;
     102        allPluginsNode.ImageIndex = 0;
     103        allPlugins.Nodes.Add(allPluginsNode);
     104      }
     105      foreach(PluginInfo pluginInfo in PluginManager.Manager.DisabledPlugins) {
     106        PluginTag tag = new PluginTag(allTags, pluginInfo, PluginState.Disabled);
     107        allTags.Add(tag);
     108        TreeNode disabledPluginsNode = new TreeNode(pluginInfo.Name);
     109        disabledPluginsNode.ContextMenuStrip = pluginContextMenuStrip;
     110        disabledPluginsNode.Tag = tag;
     111        disabledPluginsNode.ImageIndex = 0;
     112        disabledPlugins.Nodes.Add(disabledPluginsNode);
    93113        // add to all "plugins node"
    94114        TreeNode allPluginsNode = new TreeNode(pluginInfo.Name);
     
    290310      allAvailablePlugins.ForEach(delegate(PluginDescription availablePlugin) {
    291311        List<PluginTag> oldPlugins = allTags.FindAll(delegate(PluginTag currentPlugin) {
    292           return currentPlugin.PluginName == availablePlugin.Name && currentPlugin.State == PluginState.Installed;
     312          return currentPlugin.PluginName == availablePlugin.Name && currentPlugin.State == (PluginState.Installed | PluginState.Disabled);
    293313        });
    294314
     
    567587      publishButton.Enabled = (tag.State & PluginState.Installed) == PluginState.Installed;
    568588      installButton.Enabled = (tag.State & PluginState.Available) == PluginState.Available;
    569       deleteButton.Enabled = (tag.State & (PluginState.Installed | PluginState.Upgradeable)) != 0;
     589      deleteButton.Enabled = (tag.State & (PluginState.Installed | PluginState.Upgradeable | PluginState.Disabled)) != 0;
    570590
    571591      installButton.Checked = GetAction(tag) == ManagerAction.Install;
     
    605625    private List<TreeNode> FindPluginNodes(PluginTag pluginTag) {
    606626      List<TreeNode> nodes = new List<TreeNode>();
    607       foreach(TreeNode rootNode in new TreeNode[] { installedPlugins, availablePlugins, allPlugins }) {
     627      foreach(TreeNode rootNode in new TreeNode[] { installedPlugins, availablePlugins, allPlugins, disabledPlugins }) {
    608628        foreach(TreeNode node in rootNode.Nodes) {
    609629          if(pluginTag.Equals(node.Tag)) {
  • trunk/sources/HeuristicLab.PluginInfrastructure.GUI/PluginTag.cs

    r2 r29  
    3131    Available = 2,
    3232    Upgradeable = 4,
     33    Disabled = 8,
    3334  };
    3435
     
    218219
    219220    public override int GetHashCode() {
    220       return pluginName.GetHashCode() + pluginVersion.GetHashCode();
     221      if(pluginVersion != null) {
     222        return pluginName.GetHashCode() + pluginVersion.GetHashCode();
     223      } else return pluginName.GetHashCode();
    221224    }
    222225  }
  • trunk/sources/HeuristicLab.PluginInfrastructure/DiscoveryService.cs

    r2 r29  
    3333    public PluginInfo[] Plugins {
    3434      get {
    35         return PluginManager.Manager.LoadedPlugins;
     35        PluginInfo[] plugins = new PluginInfo[PluginManager.Manager.LoadedPlugins.Count];
     36        PluginManager.Manager.LoadedPlugins.CopyTo(plugins, 0);
     37        return plugins;
    3638      }
    3739    }
  • trunk/sources/HeuristicLab.PluginInfrastructure/Loader.cs

    r28 r29  
    3333    public delegate void PluginLoadFailedEventHandler(string pluginName, string args);
    3434
    35     private Dictionary<PluginInfo, IPlugin> activePlugins = new Dictionary<PluginInfo, IPlugin>();
     35    private Dictionary<PluginInfo, List<string>> pluginDependencies = new Dictionary<PluginInfo, List<string>>();
     36    private List<PluginInfo> preloadedPluginInfos = new List<PluginInfo>();
     37    private Dictionary<IPlugin, PluginInfo> pluginInfos = new Dictionary<IPlugin, PluginInfo>();
    3638    private Dictionary<PluginInfo, IPlugin> allPlugins = new Dictionary<PluginInfo, IPlugin>();
    37     private Dictionary<IPlugin, PluginInfo> pluginInfos = new Dictionary<IPlugin, PluginInfo>();
    38 
    39     private Dictionary<string, List<string>> pluginDependencies = new Dictionary<string, List<string>>();
    40     private Dictionary<string, List<string>> pluginAssemblies = new Dictionary<string, List<string>>();
    41 
    42     private List<string> loadablePlugins = new List<string>();
     39    private List<PluginInfo> disabledPlugins = new List<PluginInfo>();
    4340    private string pluginDir = Application.StartupPath + "/" + HeuristicLab.PluginInfrastructure.Properties.Settings.Default.PluginDir;
    4441
    4542    internal event PluginLoadFailedEventHandler MissingPluginFile;
    46 
    4743    internal event PluginManagerActionEventHandler PluginAction;
    4844
    49     internal PluginInfo[] ActivePlugins {
     45    internal ICollection<PluginInfo> ActivePlugins {
    5046      get {
    51         PluginInfo[] plugins = new PluginInfo[activePlugins.Count];
    52         activePlugins.Keys.CopyTo(plugins, 0);
    53         return plugins;
    54       }
    55     }
    56 
    57     internal List<PluginInfo> InstalledPlugins {
     47        List<PluginInfo> list = new List<PluginInfo>();
     48        foreach(PluginInfo info in allPlugins.Keys) {
     49          if(!disabledPlugins.Exists(delegate(PluginInfo disabledInfo) { return info.Name == disabledInfo.Name; })) {
     50            list.Add(info);
     51          }
     52        }
     53        return list;
     54      }
     55    }
     56
     57    internal ICollection<PluginInfo> InstalledPlugins {
    5858      get {
    5959        return new List<PluginInfo>(allPlugins.Keys);
     
    6161    }
    6262
    63     private ApplicationInfo[] applications;
    64     internal ApplicationInfo[] InstalledApplications {
     63    internal ICollection<PluginInfo> DisabledPlugins {
     64      get {
     65        return disabledPlugins;
     66      }
     67    }
     68
     69    private ICollection<ApplicationInfo> applications;
     70    internal ICollection<ApplicationInfo> InstalledApplications {
    6571      get {
    6672        return applications;
     
    6975
    7076    private IPlugin FindPlugin(PluginInfo plugin) {
    71       return activePlugins[plugin];
     77      return allPlugins[plugin];
    7278    }
    7379
     
    8591    internal void Init() {
    8692      AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += delegate(object sender, ResolveEventArgs args) { return Assembly.ReflectionOnlyLoad(args.Name); };
    87       activePlugins.Clear();
    8893      allPlugins.Clear();
     94      disabledPlugins.Clear();
    8995      pluginInfos.Clear();
    9096      pluginsByName.Clear();
    91       loadablePlugins.Clear();
    9297      pluginDependencies.Clear();
    93       pluginAssemblies.Clear();
    9498
    9599      List<Assembly> assemblies = ReflectionOnlyLoadDlls();
    96100      CheckAssemblyDependencies(assemblies);
     101      CheckPluginFiles();
     102      CheckPluginDependencies();
    97103      LoadPlugins();
    98       CheckPluginFiles();
    99104
    100105      DiscoveryService service = new DiscoveryService();
    101106      IApplication[] apps = service.GetInstances<IApplication>();
    102       applications = new ApplicationInfo[apps.Length];
    103 
    104       int i = 0;
     107      applications = new List<ApplicationInfo>();
     108
    105109      foreach(IApplication application in apps) {
    106110        ApplicationInfo info = new ApplicationInfo();
     
    111115        info.PluginType = application.GetType().Namespace + "." + application.GetType().Name;
    112116
    113         applications[i++] = info;
     117        applications.Add(info);
    114118      }
    115119    }
     
    129133
    130134    private void CheckAssemblyDependencies(List<Assembly> assemblies) {
    131 
    132135      foreach(Assembly assembly in assemblies) {
    133 
    134136        // GetExportedTypes throws FileNotFoundException when a referenced assembly
    135137        // of the current assembly is missing.
     
    138140
    139141          foreach(Type t in exported) {
    140             // if the type implements IPlugin
     142            // if there is a type that implements IPlugin
    141143            if(Array.Exists<Type>(t.GetInterfaces(), delegate(Type iface) {
    142144              // use AssemblyQualifiedName to compare the types because we can't directly
     
    144146              return iface.AssemblyQualifiedName == typeof(IPlugin).AssemblyQualifiedName;
    145147            })) {
     148              // fetch the attributes of the IPlugin type
    146149              GetPluginAttributeData(t);
    147150            }
    148 
    149151          }
    150152        } catch(FileNotFoundException) {
    151153          // when a referenced assembly cannot be loaded then ignore this assembly in the plugin discovery
    152           // TASK: add the assembly to some kind of unloadable assemblies list
    153           // this list could be displayed to the user for diagnosis         
    154         }
    155       }
    156 
    157       foreach(string pluginName in this.pluginDependencies.Keys) {
    158         allDependencies.Clear();
    159         CheckPluginDependencies(pluginName);
     154        }
    160155      }
    161156    }
     
    170165      // get all attributes of that type
    171166      IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(t);
    172 
    173167      List<string> pluginAssemblies = new List<string>();
    174168      List<string> pluginDependencies = new List<string>();
     169      List<string> pluginFiles = new List<string>();
    175170      string pluginName = "";
    176 
    177       // extract relevant parameters
    178171      // iterate through all custom attributes and search for named arguments that we are interested in
    179172      foreach(CustomAttributeData attributeData in attributes) {
    180173        List<CustomAttributeNamedArgument> namedArguments = new List<CustomAttributeNamedArgument>(attributeData.NamedArguments);
    181 
    182174        // if the current attribute contains a named argument with the name "Name" then extract the plugin name
    183175        CustomAttributeNamedArgument pluginNameArgument = namedArguments.Find(delegate(CustomAttributeNamedArgument arg) {
     
    187179          pluginName = (string)pluginNameArgument.TypedValue.Value;
    188180        }
    189 
    190181        // if the current attribute contains a named argument with the name "Dependency" then extract the dependency
    191182        // and store it in the list of all dependencies
     
    193184          return arg.MemberInfo.Name == "Dependency";
    194185        });
    195 
    196186        if(dependencyNameArgument.MemberInfo != null) {
    197187          pluginDependencies.Add((string)dependencyNameArgument.TypedValue.Value);
    198188        }
    199 
    200189        // if the current attribute has a named argument "Filename" then find if the argument "Filetype" is also supplied
    201190        // and if the filetype is Assembly then store the name of the assembly in the list of assemblies
     
    207196        });
    208197        if(filenameArg.MemberInfo != null && filetypeArg.MemberInfo != null) {
     198          pluginFiles.Add(pluginDir + "/" + (string)filenameArg.TypedValue.Value);
    209199          if((PluginFileType)filetypeArg.TypedValue.Value == PluginFileType.Assembly) {
    210200            pluginAssemblies.Add(pluginDir + "/" + (string)filenameArg.TypedValue.Value);
     
    213203      }
    214204
    215       // make sure that we found reasonable values
     205      // minimal sanity check of the attribute values
    216206      if(pluginName != "" && pluginAssemblies.Count > 0) {
    217         this.pluginDependencies[pluginName] = pluginDependencies;
    218         this.pluginAssemblies[pluginName] = pluginAssemblies;
     207        // create a temporary PluginInfo that contains the attribute values
     208        PluginInfo info = new PluginInfo();
     209        info.Name = pluginName;
     210        info.Assemblies = pluginAssemblies;
     211        info.Files.AddRange(pluginFiles);
     212        info.Assemblies.AddRange(pluginAssemblies);
     213        this.pluginDependencies[info] = pluginDependencies;
     214        preloadedPluginInfos.Add(info);
    219215      } else {
    220216        throw new InvalidPluginException();
     
    222218    }
    223219
    224     private List<string> allDependencies = new List<string>();
     220    private void CheckPluginDependencies() {
     221      foreach(PluginInfo pluginInfo in preloadedPluginInfos) {
     222        // don't need to check plugins that are already disabled
     223        if(disabledPlugins.Contains(pluginInfo)) {
     224          continue;
     225        }
     226        visitedDependencies.Clear();
     227        if(!CheckPluginDependencies(pluginInfo.Name)) {
     228          disabledPlugins.Add(pluginInfo);
     229        }
     230      }
     231    }
     232
     233    private List<string> visitedDependencies = new List<string>();
    225234    private bool CheckPluginDependencies(string pluginName) {
    226       // when we already checked the dependencies of this plugin earlier then just return true
    227       if(loadablePlugins.Contains(pluginName)) {
    228         return true;
    229       } else if(!pluginAssemblies.ContainsKey(pluginName) || allDependencies.Contains(pluginName)) {
     235      if(!preloadedPluginInfos.Exists(delegate(PluginInfo info) { return pluginName == info.Name; }) ||
     236        disabledPlugins.Exists(delegate(PluginInfo info) { return pluginName == info.Name; }) ||
     237        visitedDependencies.Contains(pluginName)) {
    230238        // when the plugin is not available return false;
    231239        return false;
     
    233241        // otherwise check if all dependencies of the plugin are OK
    234242        // if yes then this plugin is also ok and we store it in the list of loadable plugins
    235         allDependencies.Add(pluginName);
    236         foreach(string dependency in pluginDependencies[pluginName]) {
     243
     244        PluginInfo matchingInfo = preloadedPluginInfos.Find(delegate(PluginInfo info) { return info.Name == pluginName; });
     245        if(matchingInfo == null) throw new InvalidProgramException(); // shouldn't happen
     246        foreach(string dependency in pluginDependencies[matchingInfo]) {
     247          visitedDependencies.Add(pluginName);
    237248          if(CheckPluginDependencies(dependency) == false) {
    238249            // if only one dependency is not available that means that the current plugin also is unloadable
    239250            return false;
    240251          }
    241         }
    242         // all dependencies OK -> add to loadable list and return true
    243         loadablePlugins.Add(pluginName);
     252          visitedDependencies.Remove(pluginName);
     253        }
     254        // all dependencies OK
    244255        return true;
    245256      }
     
    248259
    249260    private Dictionary<string, IPlugin> pluginsByName = new Dictionary<string, IPlugin>();
    250 
    251261    private void LoadPlugins() {
    252262      // load all loadable plugins (all dependencies available) into the execution context
    253       foreach(string plugin in loadablePlugins) {
    254         {
    255           foreach(string assembly in pluginAssemblies[plugin]) {
     263      foreach(PluginInfo pluginInfo in preloadedPluginInfos) {
     264        if(!disabledPlugins.Contains(pluginInfo)) {
     265          foreach(string assembly in pluginInfo.Assemblies) {
    256266            Assembly.LoadFrom(assembly);
    257267          }
     
    266276          continue;
    267277        Type[] availablePluginTypes = service.GetTypes(typeof(IPlugin), assembly);
    268 
    269 
    270278        foreach(Type pluginType in availablePluginTypes) {
    271279          if(!pluginType.IsAbstract && !pluginType.IsInterface && !pluginType.HasElementType) {
    272280            IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType);
    273281            PluginAction(this, new PluginManagerActionEventArgs(plugin.Name, PluginManagerAction.InitializingPlugin));
    274 
    275282            pluginsByName.Add(plugin.Name, plugin);
    276             PluginInfo pluginInfo = GetPluginInfo(plugin);
    277 
    278 
    279             allPlugins.Add(pluginInfo, plugin);
    280             PluginAction(this, new PluginManagerActionEventArgs(plugin.Name, PluginManagerAction.InitializedPlugin));
    281           }
    282         }
    283       }
    284     }
    285 
    286 
     283          }
     284        }
     285      }
     286
     287      foreach(IPlugin plugin in pluginsByName.Values) {
     288        PluginInfo pluginInfo = GetPluginInfo(plugin);
     289        allPlugins.Add(pluginInfo, plugin);
     290        PluginAction(this, new PluginManagerActionEventArgs(plugin.Name, PluginManagerAction.InitializedPlugin));
     291      }
     292    }
    287293    private PluginInfo GetPluginInfo(IPlugin plugin) {
    288294      if(pluginInfos.ContainsKey(plugin)) {
    289295        return pluginInfos[plugin];
    290296      }
    291 
    292297      // store the data of the plugin in a description file which can be used without loading the plugin assemblies
    293298      PluginInfo pluginInfo = new PluginInfo();
     
    302307      });
    303308
    304       // each plugin can have multiple assemlies associated
    305       // for each assembly of the plugin find the dependencies
    306       // and get the pluginDescriptions for all dependencies
    307       foreach(string assembly in pluginAssemblies[plugin.Name]) {
     309      PluginInfo preloadedInfo = preloadedPluginInfos.Find(delegate(PluginInfo info) { return info.Name == plugin.Name; });
     310      foreach(string assembly in preloadedInfo.Assemblies) {
    308311        // always use \ as directory separator (this is necessary for discovery of types in
    309312        // plugins see DiscoveryService.GetTypes()
    310313        pluginInfo.Assemblies.Add(assembly.Replace('/', '\\'));
    311 
    312       }
    313       foreach(string dependency in pluginDependencies[plugin.Name]) {
     314      }
     315      foreach(string dependency in pluginDependencies[preloadedInfo]) {
    314316        // accumulate the dependencies of each assembly into the dependencies of the whole plugin
    315317        PluginInfo dependencyInfo = GetPluginInfo(pluginsByName[dependency]);
    316318        pluginInfo.Dependencies.Add(dependencyInfo);
    317319      }
    318 
    319320      pluginInfos[plugin] = pluginInfo;
    320 
    321321      return pluginInfo;
    322322    }
    323323
    324324    private void CheckPluginFiles() {
    325       foreach(PluginInfo plugin in allPlugins.Keys) {
    326         CheckPluginFiles(plugin);
     325      foreach(PluginInfo plugin in preloadedPluginInfos) {
     326        if(!CheckPluginFiles(plugin)) {
     327          disabledPlugins.Add(plugin);
     328        }
    327329      }
    328330    }
    329331
    330332    private bool CheckPluginFiles(PluginInfo pluginInfo) {
    331       if(activePlugins.ContainsKey(pluginInfo)) {
    332         return true;
    333       }
    334       foreach(PluginInfo dependency in pluginInfo.Dependencies) {
    335         if(!CheckPluginFiles(dependency)) {
    336           return false;
    337         }
    338       }
    339333      foreach(string filename in pluginInfo.Files) {
    340334        if(!File.Exists(filename)) {
    341           MissingPluginFile(pluginInfo.Name, filename);
     335          if(MissingPluginFile != null) {
     336            MissingPluginFile(pluginInfo.Name, filename);
     337          }
    342338          return false;
    343339        }
    344340      }
    345 
    346       activePlugins.Add(pluginInfo, allPlugins[pluginInfo]);
    347341      return true;
    348342    }
  • trunk/sources/HeuristicLab.PluginInfrastructure/PluginInfo.cs

    r2 r29  
    8383
    8484    public override int GetHashCode() {
    85       return name.GetHashCode() + version.GetHashCode();
     85      if(version != null) {
     86        return name.GetHashCode() + version.GetHashCode();
     87      } else return name.GetHashCode();
    8688    }
    8789  }
  • trunk/sources/HeuristicLab.PluginInfrastructure/PluginManager.cs

    r2 r29  
    5353    }
    5454
    55     public List<PluginInfo> InstalledPlugins {
     55    public ICollection<PluginInfo> InstalledPlugins {
    5656      get { return remoteLoader.InstalledPlugins; }
    5757    }
    5858
    59     public ApplicationInfo[] InstalledApplications {
     59    public ICollection<PluginInfo> DisabledPlugins {
     60      get { return remoteLoader.DisabledPlugins; }
     61    }
     62
     63    public ICollection<PluginInfo> ActivePlugins {
     64      get { return remoteLoader.ActivePlugins; }
     65    }
     66
     67    public ICollection<ApplicationInfo> InstalledApplications {
    6068      get { return remoteLoader.InstalledApplications; }
    6169    }
    6270
    63     private PluginInfo[] loadedPlugins;
    64     public PluginInfo[] LoadedPlugins {
     71    private ICollection<PluginInfo> loadedPlugins;
     72    public ICollection<PluginInfo> LoadedPlugins {
    6573      get { return loadedPlugins; }
    6674      internal set { loadedPlugins = value; }
     
    7280    public void Initialize() {
    7381      NotifyListeners(PluginManagerAction.Initializing, "-");
    74 
    7582      AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
    7683      setup.PrivateBinPath = pluginDir;
    7784      pluginDomain = AppDomain.CreateDomain("plugin domain", null, setup);
    78 
    7985      remoteLoader = (Loader)pluginDomain.CreateInstanceAndUnwrap("HeuristicLab.PluginInfraStructure", "HeuristicLab.PluginInfrastructure.Loader");
    80 
    8186      remoteLoader.PluginAction += delegate(object sender, PluginManagerActionEventArgs args) { if(Action != null) Action(this, args); };
    8287      remoteLoader.Init();
    83 
    8488      NotifyListeners(PluginManagerAction.Initialized, "-");
    8589    }
     
    99103      setup.PrivateBinPath = pluginDir;
    100104      AppDomain applicationDomain = AppDomain.CreateDomain(appInfo.Name + " AppDomain", null, setup);
    101      
     105
    102106      Runner remoteRunner = (Runner)applicationDomain.CreateInstanceAndUnwrap("HeuristicLab.PluginInfrastructure", "HeuristicLab.PluginInfrastructure.Runner");
    103107      NotifyListeners(PluginManagerAction.Initializing, "All plugins");
     
    116120    public List<PluginInfo> GetDependentPlugins(PluginInfo pluginInfo) {
    117121      List<PluginInfo> mergedList = new List<PluginInfo>();
    118       InstalledPlugins.ForEach(delegate(PluginInfo plugin) {
     122      foreach(PluginInfo plugin in InstalledPlugins) {
    119123        if(plugin.Dependencies.Contains(pluginInfo)) {
    120124          if(!mergedList.Contains(plugin)) {
     
    125129          GetDependentPlugins(plugin).ForEach(delegate(PluginInfo dependentPlugin) {
    126130            if(!mergedList.Contains(dependentPlugin)) {
    127               mergedList.Add(dependentPlugin);             
     131              mergedList.Add(dependentPlugin);
    128132            }
    129133          });
    130134        }
    131       });
    132      
     135      }
    133136      return mergedList;
    134137    }
  • trunk/sources/HeuristicLab.PluginInfrastructure/Runner.cs

    r2 r29  
    2828  internal class Runner : MarshalByRefObject {
    2929
    30     public void LoadPlugins(PluginInfo[] plugins) {
     30    public void LoadPlugins(ICollection<PluginInfo> plugins) {
    3131      foreach(PluginInfo pluginInfo in plugins) {
    3232        foreach(string assemblyName in pluginInfo.Assemblies) {
     
    3434        }
    3535      }
    36 
    3736      PluginManager.Manager.LoadedPlugins = plugins;
    3837    }
Note: See TracChangeset for help on using the changeset viewer.