1 // 2 // AddinService.cs 3 // 4 // Author: 5 // Lluis Sanchez Gual 6 // 7 // Copyright (C) 2007 Novell, Inc (http://www.novell.com) 8 // 9 // Permission is hereby granted, free of charge, to any person obtaining 10 // a copy of this software and associated documentation files (the 11 // "Software"), to deal in the Software without restriction, including 12 // without limitation the rights to use, copy, modify, merge, publish, 13 // distribute, sublicense, and/or sell copies of the Software, and to 14 // permit persons to whom the Software is furnished to do so, subject to 15 // the following conditions: 16 // 17 // The above copyright notice and this permission notice shall be 18 // included in all copies or substantial portions of the Software. 19 // 20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 // 28 29 30 using System; 31 using System.Linq; 32 using System.Xml; 33 using System.Collections; 34 using System.Reflection; 35 36 using Mono.Addins.Description; 37 using Mono.Addins.Database; 38 using Mono.Addins.Localization; 39 using System.Collections.Generic; 40 41 namespace Mono.Addins 42 { 43 /// <summary> 44 /// An add-in engine. 45 /// </summary> 46 /// <remarks> 47 /// This class allows hosting several independent add-in engines in a single application domain. 48 /// In general, applications use the AddinManager class to query and manage extensions. Most of the API is 49 /// static, so easily accessible. However, some kind applications may need to use several isolated 50 /// add-in engines, and in this case the AddinManager class can't be used, because it is bound to a single 51 /// add-in engine. Those applications can instead create several instances of the AddinEngine class. Each 52 /// add-in engine can be independently initialized with different add-in registries and extension models. 53 /// </remarks> 54 public class AddinEngine: ExtensionContext 55 { 56 bool initialized; 57 string startupDirectory; 58 AddinRegistry registry; 59 IAddinInstaller installer; 60 61 bool checkAssemblyLoadConflicts; 62 Dictionary<string,RuntimeAddin> loadedAddins = new Dictionary<string,RuntimeAddin> (); 63 Dictionary<string,ExtensionNodeSet> nodeSets = new Dictionary<string, ExtensionNodeSet> (); 64 Hashtable autoExtensionTypes = new Hashtable (); 65 Dictionary<Assembly,RuntimeAddin> loadedAssemblies = new Dictionary<Assembly,RuntimeAddin> (); 66 AddinLocalizer defaultLocalizer; 67 IProgressStatus defaultProgressStatus = new ConsoleProgressStatus (false); 68 69 /// <summary> 70 /// Raised when there is an error while loading an add-in 71 /// </summary> 72 public static event AddinErrorEventHandler AddinLoadError; 73 74 /// <summary> 75 /// Raised when an add-in is loaded 76 /// </summary> 77 public static event AddinEventHandler AddinLoaded; 78 79 /// <summary> 80 /// Raised when an add-in is unloaded 81 /// </summary> 82 public static event AddinEventHandler AddinUnloaded; 83 84 /// <summary> 85 /// Initializes a new instance of the <see cref="Mono.Addins.AddinEngine"/> class. 86 /// </summary> AddinEngine()87 public AddinEngine () 88 { 89 } 90 91 /// <summary> 92 /// Initializes the add-in engine 93 /// </summary> 94 /// <param name="configDir"> 95 /// Location of the add-in registry. 96 /// </param> 97 /// <remarks>The add-in engine needs to be initialized before doing any add-in operation. 98 /// When initialized with this method, it will look for add-in in the add-in registry 99 /// located in the specified path. 100 /// </remarks> Initialize(string configDir)101 public void Initialize (string configDir) 102 { 103 if (initialized) 104 return; 105 106 Assembly asm = Assembly.GetEntryAssembly (); 107 if (asm == null) asm = Assembly.GetCallingAssembly (); 108 Initialize (asm, configDir, null, null); 109 } 110 111 /// <summary> 112 /// Initializes the add-in engine. 113 /// </summary> 114 /// <param name='configDir'> 115 /// Location of the add-in registry. 116 /// </param> 117 /// <param name='addinsDir'> 118 /// Add-ins directory. If the path is relative, it is considered to be relative 119 /// to the configDir directory. 120 /// </param> 121 /// <remarks> 122 /// The add-in engine needs to be initialized before doing any add-in operation. 123 /// Configuration information about the add-in registry will be stored in the 124 /// provided location. The add-in engine will look for add-ins in the provided 125 /// 'addinsDir' directory. 126 /// 127 /// When specifying a path, it is possible to use a special folder name as root. 128 /// For example: [Personal]/.config/MyApp. In this case, [Personal] will be replaced 129 /// by the location of the Environment.SpecialFolder.Personal folder. Any value 130 /// of the Environment.SpecialFolder enumeration can be used (always between square 131 /// brackets) 132 /// </remarks> Initialize(string configDir, string addinsDir)133 public void Initialize (string configDir, string addinsDir) 134 { 135 if (initialized) 136 return; 137 138 Assembly asm = Assembly.GetEntryAssembly (); 139 if (asm == null) asm = Assembly.GetCallingAssembly (); 140 Initialize (asm, configDir, addinsDir, null); 141 } 142 143 /// <summary> 144 /// Initializes the add-in engine. 145 /// </summary> 146 /// <param name='configDir'> 147 /// Location of the add-in registry. 148 /// </param> 149 /// <param name='addinsDir'> 150 /// Add-ins directory. If the path is relative, it is considered to be relative 151 /// to the configDir directory. 152 /// </param> 153 /// <param name='databaseDir'> 154 /// Location of the add-in database. If the path is relative, it is considered to be relative 155 /// to the configDir directory. 156 /// </param> 157 /// <remarks> 158 /// The add-in engine needs to be initialized before doing any add-in operation. 159 /// Configuration information about the add-in registry will be stored in the 160 /// provided location. The add-in engine will look for add-ins in the provided 161 /// 'addinsDir' directory. Cached information about add-ins will be stored in 162 /// the 'databaseDir' directory. 163 /// 164 /// When specifying a path, it is possible to use a special folder name as root. 165 /// For example: [Personal]/.config/MyApp. In this case, [Personal] will be replaced 166 /// by the location of the Environment.SpecialFolder.Personal folder. Any value 167 /// of the Environment.SpecialFolder enumeration can be used (always between square 168 /// brackets) 169 /// </remarks> Initialize(string configDir, string addinsDir, string databaseDir)170 public void Initialize (string configDir, string addinsDir, string databaseDir) 171 { 172 if (initialized) 173 return; 174 175 Assembly asm = Assembly.GetEntryAssembly (); 176 if (asm == null) asm = Assembly.GetCallingAssembly (); 177 Initialize (asm, configDir, addinsDir, databaseDir); 178 } 179 Initialize(Assembly startupAsm, string configDir, string addinsDir, string databaseDir)180 internal void Initialize (Assembly startupAsm, string configDir, string addinsDir, string databaseDir) 181 { 182 lock (LocalLock) { 183 if (initialized) 184 return; 185 186 Initialize (this); 187 188 string asmFile = new Uri (startupAsm.CodeBase).LocalPath; 189 startupDirectory = System.IO.Path.GetDirectoryName (asmFile); 190 191 string customDir = Environment.GetEnvironmentVariable ("MONO_ADDINS_REGISTRY"); 192 if (customDir != null && customDir.Length > 0) 193 configDir = customDir; 194 195 if (string.IsNullOrEmpty (configDir)) 196 registry = AddinRegistry.GetGlobalRegistry (this, startupDirectory); 197 else 198 registry = new AddinRegistry (this, configDir, startupDirectory, addinsDir, databaseDir); 199 200 if (registry.CreateHostAddinsFile (asmFile) || registry.UnknownDomain) 201 registry.Update (new ConsoleProgressStatus (false)); 202 203 initialized = true; 204 205 ActivateRoots (); 206 OnAssemblyLoaded (null, null); 207 AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler (OnAssemblyLoaded); 208 AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainAssemblyResolve; 209 } 210 } 211 CurrentDomainAssemblyResolve(object sender, ResolveEventArgs args)212 Assembly CurrentDomainAssemblyResolve(object sender, ResolveEventArgs args) 213 { 214 lock (LocalLock) { 215 // MS.NET is more strict than Mono when loading assemblies. Assemblies loaded in the "Load" context can't see assemblies loaded 216 // in the "LoadFrom" context, unless assemblies are explicitly resolved in the AssemblyResolve event. 217 return loadedAddins.Values.Where(a => a.AssembliesLoaded).SelectMany(a => a.Assemblies).FirstOrDefault(a => a.FullName.ToString () == args.Name); 218 } 219 } 220 221 /// <summary> 222 /// Finalizes the add-in engine. 223 /// </summary> Shutdown()224 public void Shutdown () 225 { 226 lock (LocalLock) { 227 initialized = false; 228 AppDomain.CurrentDomain.AssemblyLoad -= new AssemblyLoadEventHandler (OnAssemblyLoaded); 229 AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomainAssemblyResolve; 230 loadedAddins = new Dictionary<string, RuntimeAddin>(); 231 loadedAssemblies = new Dictionary<Assembly, RuntimeAddin> (); 232 registry.Dispose (); 233 registry = null; 234 startupDirectory = null; 235 ClearContext (); 236 } 237 } 238 239 /// <summary> 240 /// Sets the default localizer to be used for this add-in engine 241 /// </summary> 242 /// <param name="localizer"> 243 /// The add-in localizer 244 /// </param> InitializeDefaultLocalizer(IAddinLocalizer localizer)245 public void InitializeDefaultLocalizer (IAddinLocalizer localizer) 246 { 247 CheckInitialized (); 248 lock (LocalLock) { 249 if (localizer != null) 250 defaultLocalizer = new AddinLocalizer (localizer); 251 else 252 defaultLocalizer = null; 253 } 254 } 255 256 internal string StartupDirectory { 257 get { return startupDirectory; } 258 } 259 260 /// <summary> 261 /// Gets whether the add-in engine has been initialized. 262 /// </summary> 263 public bool IsInitialized { 264 get { return initialized; } 265 } 266 267 /// <summary> 268 /// Gets the default add-in installer 269 /// </summary> 270 /// <remarks> 271 /// The default installer is used by the CheckInstalled method to request 272 /// the installation of missing add-ins. 273 /// </remarks> 274 public IAddinInstaller DefaultInstaller { 275 get { return installer; } 276 set { installer = value; } 277 } 278 279 /// <summary> 280 /// Gets the default localizer for this add-in engine 281 /// </summary> 282 public AddinLocalizer DefaultLocalizer { 283 get { 284 CheckInitialized (); 285 var loc = defaultLocalizer; 286 return loc ?? NullLocalizer.Instance; 287 } 288 } 289 290 internal ExtensionContext DefaultContext { 291 get { return this; } 292 } 293 294 /// <summary> 295 /// Gets the localizer for the add-in that is invoking this property 296 /// </summary> 297 public AddinLocalizer CurrentLocalizer { 298 get { 299 CheckInitialized (); 300 Assembly asm = Assembly.GetCallingAssembly (); 301 RuntimeAddin addin = GetAddinForAssembly (asm); 302 if (addin != null) 303 return addin.Localizer; 304 else 305 return DefaultLocalizer; 306 } 307 } 308 309 /// <summary> 310 /// Gets a reference to the RuntimeAddin object for the add-in that is invoking this property 311 /// </summary> 312 public RuntimeAddin CurrentAddin { 313 get { 314 CheckInitialized (); 315 Assembly asm = Assembly.GetCallingAssembly (); 316 return GetAddinForAssembly (asm); 317 } 318 } 319 320 /// <summary> 321 /// Gets the add-in registry bound to this add-in engine 322 /// </summary> 323 public AddinRegistry Registry { 324 get { 325 CheckInitialized (); 326 return registry; 327 } 328 } 329 GetAddinForAssembly(Assembly asm)330 internal RuntimeAddin GetAddinForAssembly (Assembly asm) 331 { 332 ValidateAddinRoots (); 333 RuntimeAddin ad; 334 loadedAssemblies.TryGetValue (asm, out ad); 335 return ad; 336 } 337 338 /// <summary> 339 /// Checks if the provided add-ins are installed, and requests the installation of those 340 /// which aren't. 341 /// </summary> 342 /// <param name="message"> 343 /// Message to show to the user when new add-ins have to be installed. 344 /// </param> 345 /// <param name="addinIds"> 346 /// List of IDs of the add-ins to be checked. 347 /// </param> 348 /// <remarks> 349 /// This method checks if the specified add-ins are installed. 350 /// If some of the add-ins are not installed, it will use 351 /// the installer assigned to the DefaultAddinInstaller property 352 /// to install them. If the installation fails, or if DefaultAddinInstaller 353 /// is not set, an exception will be thrown. 354 /// </remarks> CheckInstalled(string message, params string[] addinIds)355 public void CheckInstalled (string message, params string[] addinIds) 356 { 357 ArrayList notInstalled = new ArrayList (); 358 foreach (string id in addinIds) { 359 Addin addin = Registry.GetAddin (id, false); 360 if (addin != null) { 361 // The add-in is already installed 362 // If the add-in is disabled, enable it now 363 if (!addin.Enabled) 364 addin.Enabled = true; 365 } else { 366 notInstalled.Add (id); 367 } 368 } 369 if (notInstalled.Count == 0) 370 return; 371 372 var ins = installer; 373 374 if (ins == null) 375 throw new InvalidOperationException ("Add-in installer not set"); 376 377 // Install the add-ins 378 ins.InstallAddins (Registry, message, (string[]) notInstalled.ToArray (typeof(string))); 379 } 380 381 // Enables or disables conflict checking while loading assemblies. 382 // Disabling makes loading faster, but less safe. 383 internal bool CheckAssemblyLoadConflicts { 384 get { return checkAssemblyLoadConflicts; } 385 set { checkAssemblyLoadConflicts = value; } 386 } 387 388 /// <summary> 389 /// Checks if an add-in has been loaded. 390 /// </summary> 391 /// <param name="id"> 392 /// Full identifier of the add-in. 393 /// </param> 394 /// <returns> 395 /// True if the add-in is loaded. 396 /// </returns> IsAddinLoaded(string id)397 public bool IsAddinLoaded (string id) 398 { 399 CheckInitialized (); 400 ValidateAddinRoots (); 401 return loadedAddins.ContainsKey (Addin.GetIdName (id)); 402 } 403 GetAddin(string id)404 internal RuntimeAddin GetAddin (string id) 405 { 406 ValidateAddinRoots (); 407 RuntimeAddin a; 408 loadedAddins.TryGetValue (Addin.GetIdName (id), out a); 409 return a; 410 } 411 ActivateAddin(string id)412 internal void ActivateAddin (string id) 413 { 414 ActivateAddinExtensions (id); 415 } 416 UnloadAddin(string id)417 internal void UnloadAddin (string id) 418 { 419 RemoveAddinExtensions (id); 420 421 RuntimeAddin addin = GetAddin (id); 422 if (addin != null) { 423 addin.UnloadExtensions (); 424 lock (LocalLock) { 425 var loadedAddinsCopy = new Dictionary<string,RuntimeAddin> (loadedAddins); 426 loadedAddinsCopy.Remove (Addin.GetIdName (id)); 427 loadedAddins = loadedAddinsCopy; 428 if (addin.AssembliesLoaded) { 429 var loadedAssembliesCopy = new Dictionary<Assembly,RuntimeAddin> (loadedAssemblies); 430 foreach (Assembly asm in addin.Assemblies) 431 loadedAssembliesCopy.Remove (asm); 432 loadedAssemblies = loadedAssembliesCopy; 433 } 434 } 435 ReportAddinUnload (id); 436 } 437 } 438 439 /// <summary> 440 /// Forces the loading of an add-in. 441 /// </summary> 442 /// <param name="statusMonitor"> 443 /// Status monitor to keep track of the loading process. 444 /// </param> 445 /// <param name="id"> 446 /// Full identifier of the add-in to load. 447 /// </param> 448 /// <remarks> 449 /// This method loads all assemblies that belong to an add-in in memory. 450 /// All add-ins on which the specified add-in depends will also be loaded. 451 /// Notice that in general add-ins don't need to be explicitly loaded using 452 /// this method, since the add-in engine will load them on demand. 453 /// </remarks> LoadAddin(IProgressStatus statusMonitor, string id)454 public void LoadAddin (IProgressStatus statusMonitor, string id) 455 { 456 CheckInitialized (); 457 if (LoadAddin (statusMonitor, id, true)) { 458 var adn = GetAddin (id); 459 adn.EnsureAssembliesLoaded (); 460 } 461 } 462 LoadAddin(IProgressStatus statusMonitor, string id, bool throwExceptions)463 internal bool LoadAddin (IProgressStatus statusMonitor, string id, bool throwExceptions) 464 { 465 try { 466 lock (LocalLock) { 467 if (IsAddinLoaded (id)) 468 return true; 469 470 if (!Registry.IsAddinEnabled (id)) { 471 string msg = GettextCatalog.GetString ("Disabled add-ins can't be loaded."); 472 ReportError (msg, id, null, false); 473 if (throwExceptions) 474 throw new InvalidOperationException (msg); 475 return false; 476 } 477 478 ArrayList addins = new ArrayList (); 479 Stack depCheck = new Stack (); 480 ResolveLoadDependencies (addins, depCheck, id, false); 481 addins.Reverse (); 482 483 if (statusMonitor != null) 484 statusMonitor.SetMessage ("Loading Addins"); 485 486 for (int n=0; n<addins.Count; n++) { 487 488 if (statusMonitor != null) 489 statusMonitor.SetProgress ((double) n / (double)addins.Count); 490 491 Addin iad = (Addin) addins [n]; 492 if (IsAddinLoaded (iad.Id)) 493 continue; 494 495 if (statusMonitor != null) 496 statusMonitor.SetMessage (string.Format(GettextCatalog.GetString("Loading {0} add-in"), iad.Id)); 497 498 if (!InsertAddin (statusMonitor, iad)) 499 return false; 500 } 501 return true; 502 } 503 } 504 catch (Exception ex) { 505 ReportError ("Add-in could not be loaded: " + ex.Message, id, ex, false); 506 if (statusMonitor != null) 507 statusMonitor.ReportError ("Add-in '" + id + "' could not be loaded.", ex); 508 if (throwExceptions) 509 throw; 510 return false; 511 } 512 } 513 ResetCachedData()514 internal override void ResetCachedData () 515 { 516 foreach (RuntimeAddin ad in loadedAddins.Values) 517 ad.Addin.ResetCachedData (); 518 base.ResetCachedData (); 519 } 520 InsertAddin(IProgressStatus statusMonitor, Addin iad)521 bool InsertAddin (IProgressStatus statusMonitor, Addin iad) 522 { 523 try { 524 RuntimeAddin p = new RuntimeAddin (this); 525 526 // Read the config file and load the add-in assemblies 527 AddinDescription description = p.Load (iad); 528 529 // Register the add-in 530 var loadedAddinsCopy = new Dictionary<string,RuntimeAddin> (loadedAddins); 531 loadedAddinsCopy [Addin.GetIdName (p.Id)] = p; 532 loadedAddins = loadedAddinsCopy; 533 534 if (!AddinDatabase.RunningSetupProcess) { 535 // Load the extension points and other addin data 536 537 RegisterNodeSets (iad.Id, description.ExtensionNodeSets); 538 539 foreach (ConditionTypeDescription cond in description.ConditionTypes) { 540 Type ctype = p.GetType (cond.TypeName, true); 541 RegisterCondition (cond.Id, ctype); 542 } 543 } 544 545 foreach (ExtensionPoint ep in description.ExtensionPoints) 546 InsertExtensionPoint (p, ep); 547 548 // Fire loaded event 549 NotifyAddinLoaded (p); 550 ReportAddinLoad (p.Id); 551 return true; 552 } 553 catch (Exception ex) { 554 ReportError ("Add-in could not be loaded", iad.Id, ex, false); 555 if (statusMonitor != null) 556 statusMonitor.ReportError ("Add-in '" + iad.Id + "' could not be loaded.", ex); 557 return false; 558 } 559 } 560 RegisterAssemblies(RuntimeAddin addin)561 internal void RegisterAssemblies (RuntimeAddin addin) 562 { 563 lock (LocalLock) { 564 var loadedAssembliesCopy = new Dictionary<Assembly,RuntimeAddin> (loadedAssemblies); 565 foreach (Assembly asm in addin.Assemblies) 566 loadedAssembliesCopy [asm] = addin; 567 loadedAssemblies = loadedAssembliesCopy; 568 } 569 } 570 InsertExtensionPoint(RuntimeAddin addin, ExtensionPoint ep)571 internal void InsertExtensionPoint (RuntimeAddin addin, ExtensionPoint ep) 572 { 573 CreateExtensionPoint (ep); 574 foreach (ExtensionNodeType nt in ep.NodeSet.NodeTypes) { 575 if (nt.ObjectTypeName.Length > 0) { 576 Type ntype = addin.GetType (nt.ObjectTypeName, true); 577 RegisterAutoTypeExtensionPoint (ntype, ep.Path); 578 } 579 } 580 } 581 ResolveLoadDependencies(ArrayList addins, Stack depCheck, string id, bool optional)582 bool ResolveLoadDependencies (ArrayList addins, Stack depCheck, string id, bool optional) 583 { 584 if (IsAddinLoaded (id)) 585 return true; 586 587 if (depCheck.Contains (id)) 588 throw new InvalidOperationException ("A cyclic addin dependency has been detected."); 589 590 depCheck.Push (id); 591 592 Addin iad = Registry.GetAddin (id); 593 if (iad == null || !iad.Enabled) { 594 if (optional) 595 return false; 596 else if (iad != null && !iad.Enabled) 597 throw new MissingDependencyException (GettextCatalog.GetString ("The required addin '{0}' is disabled.", id)); 598 else 599 throw new MissingDependencyException (GettextCatalog.GetString ("The required addin '{0}' is not installed.", id)); 600 } 601 602 // If this addin has already been requested, bring it to the head 603 // of the list, so it is loaded earlier than before. 604 addins.Remove (iad); 605 addins.Add (iad); 606 607 foreach (Dependency dep in iad.AddinInfo.Dependencies) { 608 AddinDependency adep = dep as AddinDependency; 609 if (adep != null) { 610 try { 611 string adepid = Addin.GetFullId (iad.AddinInfo.Namespace, adep.AddinId, adep.Version); 612 ResolveLoadDependencies (addins, depCheck, adepid, false); 613 } catch (MissingDependencyException) { 614 if (optional) 615 return false; 616 else 617 throw; 618 } 619 } 620 } 621 622 if (iad.AddinInfo.OptionalDependencies != null) { 623 foreach (Dependency dep in iad.AddinInfo.OptionalDependencies) { 624 AddinDependency adep = dep as AddinDependency; 625 if (adep != null) { 626 string adepid = Addin.GetFullId (iad.Namespace, adep.AddinId, adep.Version); 627 if (!ResolveLoadDependencies (addins, depCheck, adepid, true)) 628 return false; 629 } 630 } 631 } 632 633 depCheck.Pop (); 634 return true; 635 } 636 RegisterNodeSets(string addinId, ExtensionNodeSetCollection nsets)637 void RegisterNodeSets (string addinId, ExtensionNodeSetCollection nsets) 638 { 639 lock (LocalLock) { 640 var nodeSetsCopy = new Dictionary<string,ExtensionNodeSet> (nodeSets); 641 foreach (ExtensionNodeSet nset in nsets) { 642 nset.SourceAddinId = addinId; 643 nodeSetsCopy [nset.Id] = nset; 644 } 645 nodeSets = nodeSetsCopy; 646 } 647 } 648 UnregisterAddinNodeSets(string addinId)649 internal void UnregisterAddinNodeSets (string addinId) 650 { 651 lock (LocalLock) { 652 var nodeSetsCopy = new Dictionary<string,ExtensionNodeSet> (nodeSets); 653 foreach (var nset in nodeSetsCopy.Values.Where (n => n.SourceAddinId == addinId).ToArray ()) 654 nodeSetsCopy.Remove (nset.Id); 655 nodeSets = nodeSetsCopy; 656 } 657 } 658 GetNodeTypeAddin(ExtensionNodeSet nset, string type, string callingAddinId)659 internal string GetNodeTypeAddin (ExtensionNodeSet nset, string type, string callingAddinId) 660 { 661 ExtensionNodeType nt = FindType (nset, type, callingAddinId); 662 if (nt != null) 663 return nt.AddinId; 664 else 665 return null; 666 } 667 FindType(ExtensionNodeSet nset, string name, string callingAddinId)668 internal ExtensionNodeType FindType (ExtensionNodeSet nset, string name, string callingAddinId) 669 { 670 if (nset == null) 671 return null; 672 673 foreach (ExtensionNodeType nt in nset.NodeTypes) { 674 if (nt.Id == name) 675 return nt; 676 } 677 678 foreach (string ns in nset.NodeSets) { 679 ExtensionNodeSet regSet; 680 if (!nodeSets.TryGetValue (ns, out regSet)) { 681 ReportError ("Unknown node set: " + ns, callingAddinId, null, false); 682 return null; 683 } 684 ExtensionNodeType nt = FindType (regSet, name, callingAddinId); 685 if (nt != null) 686 return nt; 687 } 688 return null; 689 } 690 RegisterAutoTypeExtensionPoint(Type type, string path)691 internal void RegisterAutoTypeExtensionPoint (Type type, string path) 692 { 693 autoExtensionTypes [type] = path; 694 } 695 UnregisterAutoTypeExtensionPoint(Type type, string path)696 internal void UnregisterAutoTypeExtensionPoint (Type type, string path) 697 { 698 autoExtensionTypes.Remove (type); 699 } 700 GetAutoTypeExtensionPoint(Type type)701 internal string GetAutoTypeExtensionPoint (Type type) 702 { 703 return autoExtensionTypes [type] as string; 704 } 705 OnAssemblyLoaded(object s, AssemblyLoadEventArgs a)706 void OnAssemblyLoaded (object s, AssemblyLoadEventArgs a) 707 { 708 if (a != null) { 709 lock (pendingRootChecks) { 710 pendingRootChecks.Add (a.LoadedAssembly); 711 } 712 } 713 } 714 715 List<Assembly> pendingRootChecks = new List<Assembly> (); 716 ValidateAddinRoots()717 internal void ValidateAddinRoots () 718 { 719 List<Assembly> copy = null; 720 lock (pendingRootChecks) { 721 if (pendingRootChecks.Count > 0) { 722 copy = new List<Assembly> (pendingRootChecks); 723 pendingRootChecks.Clear (); 724 } 725 } 726 if (copy != null) { 727 foreach (Assembly asm in copy) 728 CheckHostAssembly (asm); 729 } 730 } 731 ActivateRoots()732 internal void ActivateRoots () 733 { 734 lock (pendingRootChecks) 735 pendingRootChecks.Clear (); 736 foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies ()) 737 CheckHostAssembly (asm); 738 } 739 CheckHostAssembly(Assembly asm)740 void CheckHostAssembly (Assembly asm) 741 { 742 if (AddinDatabase.RunningSetupProcess || asm is System.Reflection.Emit.AssemblyBuilder || asm.IsDynamic) 743 return; 744 string codeBase; 745 try { 746 codeBase = asm.CodeBase; 747 } catch { 748 return; 749 } 750 751 Uri u; 752 if (!Uri.TryCreate (codeBase, UriKind.Absolute, out u)) 753 return; 754 755 string asmFile = u.LocalPath; 756 Addin ainfo; 757 try { 758 ainfo = Registry.GetAddinForHostAssembly (asmFile); 759 } catch (Exception ex) { 760 // GetAddinForHostAssembly may crash if the add-in db has been corrupted. In this case, update the db 761 // and try getting the add-in info again. If it crashes again, then this is a bug. 762 defaultProgressStatus.ReportError ("Add-in description could not be loaded.", ex); 763 Registry.Update (null); 764 ainfo = Registry.GetAddinForHostAssembly (asmFile); 765 } 766 767 if (ainfo != null && !IsAddinLoaded (ainfo.Id)) { 768 AddinDescription adesc = null; 769 try { 770 adesc = ainfo.Description; 771 } catch (Exception ex) { 772 defaultProgressStatus.ReportError ("Add-in description could not be loaded.", ex); 773 } 774 if (adesc == null || adesc.FilesChanged ()) { 775 // If the add-in has changed, update the add-in database. 776 // We do it here because once loaded, add-in roots can't be 777 // reloaded like regular add-ins. 778 Registry.Update (null); 779 ainfo = Registry.GetAddinForHostAssembly (asmFile); 780 if (ainfo == null) 781 return; 782 } 783 LoadAddin (null, ainfo.Id, false); 784 } 785 } 786 787 /// <summary> 788 /// Creates a new extension context. 789 /// </summary> 790 /// <returns> 791 /// The new extension context. 792 /// </returns> 793 /// <remarks> 794 /// Extension contexts can be used to query the extension model using particular condition values. 795 /// </remarks> CreateExtensionContext()796 public ExtensionContext CreateExtensionContext () 797 { 798 CheckInitialized (); 799 return CreateChildContext (); 800 } 801 CheckInitialized()802 internal void CheckInitialized () 803 { 804 if (!initialized) 805 throw new InvalidOperationException ("Add-in engine not initialized."); 806 } 807 ReportError(string message, string addinId, Exception exception, bool fatal)808 internal void ReportError (string message, string addinId, Exception exception, bool fatal) 809 { 810 var handler = AddinLoadError; 811 if (handler != null) 812 handler (null, new AddinErrorEventArgs (message, addinId, exception)); 813 else { 814 Console.WriteLine (message); 815 if (exception != null) 816 Console.WriteLine (exception); 817 } 818 } 819 ReportAddinLoad(string id)820 internal void ReportAddinLoad (string id) 821 { 822 var handler = AddinLoaded; 823 if (handler != null) { 824 try { 825 handler (null, new AddinEventArgs (id)); 826 } catch { 827 // Ignore subscriber exceptions 828 } 829 } 830 } 831 ReportAddinUnload(string id)832 internal void ReportAddinUnload (string id) 833 { 834 var handler = AddinUnloaded; 835 if (handler != null) { 836 try { 837 handler (null, new AddinEventArgs (id)); 838 } catch { 839 // Ignore subscriber exceptions 840 } 841 } 842 } 843 } 844 845 } 846