private static LoadPluginResult LoadPluginFromFolder(string pluginFolderPath, ICollection <string> installedPluginSystemNames) { Guard.ArgumentNotEmpty(() => pluginFolderPath); var folder = new DirectoryInfo(pluginFolderPath); if (!folder.Exists) { return(null); } var descriptionFile = new FileInfo(Path.Combine(pluginFolderPath, "Description.txt")); if (!descriptionFile.Exists) { return(null); } // load descriptor file (Description.txt) var descriptor = PluginFileParser.ParsePluginDescriptionFile(descriptionFile.FullName); // some validation if (descriptor.SystemName.IsEmpty()) { throw new Exception("The plugin descriptor '{0}' does not define a plugin system name. Try assigning the plugin a unique name and recompile.".FormatInvariant(descriptionFile.FullName)); } if (descriptor.PluginFileName.IsEmpty()) { throw new Exception("The plugin descriptor '{0}' does not define a plugin assembly file name. Try assigning the plugin a file name and recompile.".FormatInvariant(descriptionFile.FullName)); } var result = new LoadPluginResult { DescriptionFile = descriptionFile, Descriptor = descriptor }; //ensure that version of plugin is valid if (!IsAssumedCompatible(descriptor)) { result.IsIncompatible = true; return(result); } if (_referencedPlugins.ContainsKey(descriptor.SystemName)) { throw new Exception(string.Format("A plugin with system name '{0}' is already defined", descriptor.SystemName)); } if (installedPluginSystemNames == null) { installedPluginSystemNames = PluginFileParser.ParseInstalledPluginsFile(); } // set 'Installed' property descriptor.Installed = installedPluginSystemNames.Contains(descriptor.SystemName); try { // get list of all DLLs in plugin folders (not in 'bin' or '_Backup'!) var pluginBinaries = descriptionFile.Directory.GetFiles("*.dll", SearchOption.AllDirectories) // just make sure we're not registering shadow copied plugins .Where(x => IsPackagePluginFolder(x.Directory)) .ToList(); // other plugin description info var mainPluginFile = pluginBinaries.Where(x => x.Name.IsCaseInsensitiveEqual(descriptor.PluginFileName)).FirstOrDefault(); descriptor.OriginalAssemblyFile = mainPluginFile; // shadow copy main plugin file descriptor.ReferencedAssembly = Probe(mainPluginFile); if (!descriptor.Installed) { _inactiveAssemblies.Add(descriptor.ReferencedAssembly); } // load all other referenced assemblies now var otherAssemblies = from x in pluginBinaries where !x.Name.IsCaseInsensitiveEqual(mainPluginFile.Name) select x; foreach (var assemblyFile in otherAssemblies) { if (!IsAlreadyLoaded(assemblyFile)) { Probe(assemblyFile); } } // init plugin type (only one plugin per assembly is allowed) var exportedTypes = descriptor.ReferencedAssembly.ExportedTypes; bool pluginFound = false; bool preStarterFound = !descriptor.Installed; foreach (var t in exportedTypes) { if (typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface && t.IsClass && !t.IsAbstract) { descriptor.PluginType = t; descriptor.IsConfigurable = typeof(IConfigurable).IsAssignableFrom(t); pluginFound = true; } else if (descriptor.Installed && typeof(IPreApplicationStart).IsAssignableFrom(t) && !t.IsInterface && t.IsClass && !t.IsAbstract && t.HasDefaultConstructor()) { try { var preStarter = Activator.CreateInstance(t) as IPreApplicationStart; preStarter.Start(); } catch { } preStarterFound = true; } if (pluginFound && preStarterFound) { break; } } result.Success = true; } catch (ReflectionTypeLoadException ex) { var msg = string.Empty; foreach (var e in ex.LoaderExceptions) { msg += e.Message + Environment.NewLine; } var fail = new Exception(msg, ex); Debug.WriteLine(fail.Message, fail); throw fail; } return(result); }
/// <summary> /// Initialize /// </summary> public static void Initialize() { using (var updater = new AppUpdater()) { // update from NuGet package, if it exists and is valid if (updater.TryUpdateFromPackage()) { // [...] } // execute migrations updater.ExecuteMigrations(); } // adding a process-specific environment path (either bin/x86 or bin/amd64) // ensures that unmanaged native dependencies can be resolved successfully. SetPrivateEnvPath(); DynamicModuleUtility.RegisterModule(typeof(AutofacRequestLifetimeHttpModule)); using (Locker.GetWriteLock()) { // TODO: Add verbose exception handling / raising here since this is happening on app startup and could // prevent app from starting altogether var pluginFolderPath = CommonHelper.MapPath(_pluginsPath); _shadowCopyFolder = new DirectoryInfo(CommonHelper.MapPath(_shadowCopyPath)); var incompatiblePlugins = new List <string>(); _clearShadowDirectoryOnStartup = CommonHelper.GetAppSetting <bool>("sm:ClearPluginsShadowDirectoryOnStartup", true); try { Debug.WriteLine("Creating shadow copy folder and querying for dlls"); //ensure folders are created Directory.CreateDirectory(pluginFolderPath); Directory.CreateDirectory(_shadowCopyFolder.FullName); // get list of all files in bin var binFiles = _shadowCopyFolder.GetFiles("*", SearchOption.AllDirectories); if (_clearShadowDirectoryOnStartup) { // clear out shadow copied plugins foreach (var f in binFiles) { Debug.WriteLine("Deleting " + f.Name); try { File.Delete(f.FullName); } catch (Exception exc) { Debug.WriteLine("Error deleting file " + f.Name + ". Exception: " + exc); } } } // determine all plugin folders var pluginPaths = from x in Directory.EnumerateDirectories(pluginFolderPath) where !x.IsMatch("bin") && !x.IsMatch("_Backup") select Path.Combine(pluginFolderPath, x); var installedPluginSystemNames = PluginFileParser.ParseInstalledPluginsFile(); // now activate all plugins foreach (var pluginPath in pluginPaths) { var result = LoadPluginFromFolder(pluginPath, installedPluginSystemNames); if (result != null) { if (result.IsIncompatible) { incompatiblePlugins.Add(result.Descriptor.SystemName); } else if (result.Success) { _referencedPlugins[result.Descriptor.SystemName] = result.Descriptor; } } } } catch (Exception ex) { var msg = string.Empty; for (var e = ex; e != null; e = e.InnerException) { msg += e.Message + Environment.NewLine; } var fail = new Exception(msg, ex); Debug.WriteLine(fail.Message, fail); throw fail; } IncompatiblePlugins = incompatiblePlugins.AsReadOnly(); } }
///// <summary> ///// Loads and parses the descriptors of all installed plugins ///// </summary> ///// <returns>All descriptors</returns> //private static Task ReadPluginDescriptors(BlockingCollection<PluginDescriptor> bag) //{ // // TODO: Add verbose exception handling / raising here since this is happening on app startup and could // // prevent app from starting altogether // return Task.Factory.StartNew(() => // { // var pluginsDir = new DirectoryInfo(CommonHelper.MapPath(_pluginsPath)); // if (!pluginsDir.Exists) // { // pluginsDir.Create(); // } // else // { // // Determine all plugin folders: ~/Plugins/{SystemName} // var allPluginDirs = pluginsDir.EnumerateDirectories().ToArray() // .Where(x => !x.Name.IsMatch("bin") && !x.Name.IsMatch("_Backup")) // .OrderBy(x => x.Name) // .ToArray(); // var installedPluginSystemNames = PluginFileParser.ParseInstalledPluginsFile().AsSynchronized(); // // Load/activate all plugins // allPluginDirs // .AsParallel() // .AsOrdered() // .Select(d => LoadPluginDescriptor(d, installedPluginSystemNames)) // .Where(x => x != null) // .ForAll(x => bag.Add(x)); // } // bag.CompleteAdding(); // }); //} private static PluginDescriptor LoadPluginDescriptor(DirectoryInfo d, ICollection <string> installedPluginSystemNames) { var descriptionFile = new FileInfo(Path.Combine(d.FullName, "Description.txt")); if (!descriptionFile.Exists) { return(null); } // Load descriptor file (Description.txt) var descriptor = PluginFileParser.ParsePluginDescriptionFile(descriptionFile.FullName); // Some validation if (descriptor.SystemName.IsEmpty()) { throw new SmartException("The plugin descriptor '{0}' does not define a plugin system name. Try assigning the plugin a unique name and recompile.".FormatInvariant(descriptionFile.FullName)); } if (descriptor.PluginFileName.IsEmpty()) { throw new SmartException("The plugin descriptor '{0}' does not define a plugin assembly file name. Try assigning the plugin a file name and recompile.".FormatInvariant(descriptionFile.FullName)); } descriptor.VirtualPath = PluginsLocation + "/" + descriptor.FolderName; // Set 'Installed' property descriptor.Installed = installedPluginSystemNames.Contains(descriptor.SystemName); // Ensure that version of plugin is valid if (!IsAssumedCompatible(descriptor)) { // Set 'Incompatible' property and return descriptor.Incompatible = true; return(descriptor); } var skipDlls = new HashSet <string>(new[] { "log4net.dll" }, StringComparer.OrdinalIgnoreCase); // Get list of all DLLs in plugin folders (not in 'bin' or '_Backup'!) var pluginBinaries = descriptionFile.Directory.EnumerateFiles("*.dll", SearchOption.AllDirectories).ToArray() .Where(x => IsPackagePluginFolder(x.Directory) && !skipDlls.Contains(x.Name)) .OrderBy(x => x.Name) .ToDictionarySafe(x => x.Name, StringComparer.OrdinalIgnoreCase); // Set 'OriginalAssemblyFile' property descriptor.Assembly.OriginalFile = pluginBinaries.Get(descriptor.PluginFileName); if (descriptor.Assembly.OriginalFile == null) { throw new SmartException("The main assembly '{0}' for plugin '{1}' could not be found.".FormatInvariant(descriptor.PluginFileName, descriptor.SystemName)); } // Load all other referenced local assemblies now var otherAssemblyFiles = pluginBinaries .Where(x => !x.Key.IsCaseInsensitiveEqual(descriptor.PluginFileName)) .Select(x => x.Value); descriptor.ReferencedLocalAssemblies = otherAssemblyFiles.Select(x => new AssemblyReference { OriginalFile = x }).ToArray(); return(descriptor); }