using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Reflection; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media; using System.Windows.Shapes; using GUIControls.RichText; using JetBrains.Annotations; using JetBrains.Omea.OpenAPI; using JetBrains.UI.Avalon; using JetBrains.Util; using OmniaMea; namespace JetBrains.Omea.Plugins { /// /// Contains the info that has to be loaded from an assembly. /// internal class OmeaPluginsPageAssemblyInfo { #region Data /// /// The assembly, if already loaded. /// [CanBeNull] private readonly Assembly _assembly; /// /// Lazy-init . /// [CanBeNull] private FlowDocument _description; private readonly bool _isLoaded; private readonly bool _isPrimary; private string _PluginAssemblyDisplayName; private ImageSource _PluginAssemblyIcon; private readonly PluginLoader.PossiblyPluginFileInfo _pluginfileinfo; private readonly PluginDescription[] _plugins; private readonly string _sPluginAssemblyName; private readonly string _sRuntimeLoadError; #endregion #region Init /// /// Not-loaded-mode ctor. /// /// The assembly name. Mandatory. For display needs only. /// Plugin file of origin. /// If the plugin failed to be loaded at runtime, records the load error. private OmeaPluginsPageAssemblyInfo([NotNull] string sAssemblyName, PluginLoader.PossiblyPluginFileInfo pluginfileinfo, [NotNull] string sRuntimeLoadError) { if(sAssemblyName.IsEmpty()) throw new ArgumentNullException("sAssemblyName"); _sPluginAssemblyName = sAssemblyName; _pluginfileinfo = pluginfileinfo; _sRuntimeLoadError = sRuntimeLoadError; } /// /// Loaded-mode ctor. /// /// The assembly, if loaded. /// Preloaded plugin types to create the descriptions from. /// Plugin file origin information. /// If the plugin failed to be loaded at runtime, records the load error. private OmeaPluginsPageAssemblyInfo([NotNull] Assembly assembly, [NotNull] IList plugintypes, PluginLoader.PossiblyPluginFileInfo pluginfileinfo, [NotNull] string sRuntimeLoadError) : this(assembly.GetName().Name, pluginfileinfo, sRuntimeLoadError) { // Called non-loaded base ctor _isLoaded = true; // Assembly _assembly = assembly; // Is Primary? try { var fiAssembly = new FileInfo(new Uri(assembly.CodeBase).LocalPath); string sError; _isPrimary = (_pluginfileinfo.Folder.IsPrimary) && (PluginLoader.IsPrimaryAssemblyStrongNameOk(assembly.GetName(), fiAssembly.FullName, out sError)); _plugins = LoadPluginsList(plugintypes); } catch(Exception ex) { Core.ReportBackgroundException(ex); } } #endregion #region Attributes /// /// Gets the cached description. /// [NotNull] public FlowDocument Description { get { return _description ?? (_description = LoadPluginDescription()); } } /// /// Whether the item has been loaded and contains the live info, otherwise, a stub. /// public bool IsLoaded { get { return _isLoaded; } } /// /// Gets whether this is a Core Plugin. /// public bool IsPrimary { get { return _isPrimary; } } /// /// Makes a display name out of the assembly name by dropping the technical information (eg ".OmeaPlugin."). /// [NotNull] public string PluginAssemblyDisplayName { get { // Choose a display name: name of the plugin if loaded and only one plugin in the assembly return _PluginAssemblyDisplayName ?? (_PluginAssemblyDisplayName = (IsLoaded) && (Plugins != null) && (Plugins.Length == 1) ? Plugins[0].Title : PluginLoader.AssemblyNameToPluginDisplayName(PluginAssemblyName)); } } /// /// Icon for the plugin assembly. A plugin icon if loaded and single-plugin, a generic icon otherwise. /// [NotNull] public ImageSource PluginAssemblyIcon { get { return _PluginAssemblyIcon ?? (_PluginAssemblyIcon = LoadPluginAssemblyIcon()); } } /// /// Name of the assembly, AS IS, including the ".OmeaPlugin." part. /// [NotNull] public string PluginAssemblyName { get { return _sPluginAssemblyName; } } #endregion #region Operations /// /// Creates a new instance, fully-initialized upon an assembly. /// [NotNull] public static OmeaPluginsPageAssemblyInfo CreateFromAssembly([NotNull] Assembly assembly, [NotNull] IList plugintypes, PluginLoader.PossiblyPluginFileInfo pluginfileinfo, [NotNull] string sRuntimeLoadError) { return new OmeaPluginsPageAssemblyInfo(assembly, plugintypes, pluginfileinfo, sRuntimeLoadError); } /// /// Creates a new instance that has its False. /// [NotNull] public static OmeaPluginsPageAssemblyInfo CreateNotLoaded([NotNull] string sAssemblyName, PluginLoader.PossiblyPluginFileInfo pluginfileinfo, [NotNull] string sRuntimeLoadError) { return new OmeaPluginsPageAssemblyInfo(sAssemblyName, pluginfileinfo, sRuntimeLoadError); } #endregion #region Implementation /// /// Chooses the icon for an assembly from its plugins. /// private ImageSource LoadPluginAssemblyIcon() { // Icon yet unknown? if((!IsLoaded) || (Plugins == null)) return PluginDescription.GenericPluginIcon; // Check for the one and only plugin icon ImageSource icondata = null; foreach(PluginDescription plugin in Plugins) { if((plugin.Icon == null) || (plugin.Icon.Width == 0) || (plugin.Icon.Height == 0)) continue; if(icondata == null) // First met icondata = plugin.Icon; else { // Two plugins, each with an icon // If different icons, return a generic one for the assembly string sA = TypeDescriptor.GetConverter(typeof(ImageSource)).ConvertToString(icondata); string sB = TypeDescriptor.GetConverter(typeof(ImageSource)).ConvertToString(plugin.Icon); if(sA != sB) return PluginDescription.GenericPluginIcon; } } // Inambiguous icon? return icondata ?? PluginDescription.GenericPluginIcon; } /// /// Lazy-loads the author and description infos for all the plugins in the assembly. /// Should not be done in ctor 'cause sometimes the assembly is loaded from a file, and there's the some conversion to be done. /// [NotNull] private FlowDocument LoadPluginDescription() { try { FlowDocument document = new FlowDocument().SetSystemFont(); // Render each plugin bool bNotFirst = false; foreach(PluginDescription plugindesc in Plugins ?? new PluginDescription[] {}) { // Plugin separator if(bNotFirst) document.Blocks.Add(new BlockUIContainer(new Rectangle {Height = 2, Fill = SystemColors.ControlDarkBrush})); else bNotFirst = true; var sectionPlugin = new Section(); document.Blocks.Add(sectionPlugin); // Plugin heading sectionPlugin.Blocks.Add(new Paragraph {Inlines = {new InlineUIContainer(new Image {Source = plugindesc.Icon, Width = 16, Height = 16, Stretch = Stretch.Uniform, Margin = new Thickness(0, 0, 3, 0)}) {BaselineAlignment = BaselineAlignment.Bottom}, new Bold(new Run(plugindesc.Title)), new Run(" " + Stringtable.PluginWrittenBy + " "), new Run(plugindesc.Author)}}); // Plugin description var sectionDesc = new Section(); sectionDesc.Margin = sectionDesc.Margin.Add(24, 0, 16, 0); sectionPlugin.Blocks.Add(sectionDesc); sectionDesc.Blocks.AddRange(new List(plugindesc.Description.Blocks)); } // “No plugins in this assembly” case, or “Looking for plugins…” case (not loaded) if((Plugins ?? new PluginDescription[] {}).Length == 0) document.Blocks.Add(new Paragraph(new Italic(new Run(IsLoaded ? Stringtable.NoPluginsInAssembly : Stringtable.LookingForPlugins))) {TextAlignment = TextAlignment.Center}); if(OmeaPluginsPage._showDebugInfo) { // Separator document.Blocks.Add(new BlockUIContainer(new Rectangle {Height = 2, Fill = SystemColors.ControlDarkBrush})); // Debug info Paragraph para = document.AddPara(); para.Append(IsPrimary ? Stringtable.PluginDescDebug_IsPrimary : Stringtable.PluginDescDebug_IsNonPrimary); para.Append(string.Format(" {2} {0} ({1}).", _pluginfileinfo.Folder.Name, _pluginfileinfo.Folder.Location, Stringtable.PluginDescDebug_LoadedFrom)); document.AddPara().Append(string.Format("{1}: {0}", _pluginfileinfo.File.FullName, Stringtable.File)); if(!_pluginfileinfo.Reason.IsEmpty()) // File-lookup-time errors document.AddPara().Append(_pluginfileinfo.Reason); if(!_sRuntimeLoadError.IsEmpty()) // Runtime load errors document.AddPara().Append(_sRuntimeLoadError); } return document; } catch(Exception ex) { Core.ReportBackgroundException(ex); return RichContentConverter.DocumentFromException(ex); } } /// /// Fetches the plugin descriptions out of the assembly. /// private PluginDescription[] LoadPluginsList([NotNull] IList plugintypes) { if(plugintypes == null) throw new ArgumentNullException("plugintypes"); if(_assembly == null) throw new InvalidOperationException(string.Format("Need an assembly to load the plugins list.")); // Create descriptions var descriptions = new List(); foreach(Type plugintype in plugintypes) { try { descriptions.Add(PluginDescription.CreateFromPluginType(plugintype)); } catch(Exception ex) { Core.ReportBackgroundException(ex); } } return descriptions.ToArray(); } /// /// Gets the list of plugins, if . /// Otherwise, Null. /// [CanBeNull] private PluginDescription[] Plugins { get { return _plugins; } } #endregion } }