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