// ----------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. // ----------------------------------------------------------------------- #if !SILVERLIGHT using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel.Composition.Diagnostics; using System.ComponentModel.Composition.Primitives; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using Microsoft.Internal; using Microsoft.Internal.Collections; using IOPath = System.IO.Path; namespace System.ComponentModel.Composition.Hosting { [DebuggerTypeProxy(typeof(DirectoryCatalogDebuggerProxy))] public partial class DirectoryCatalog : ComposablePartCatalog, INotifyComposablePartCatalogChanged, ICompositionElement { private readonly Lock _thisLock = new Lock(); private ComposablePartCatalogCollection _catalogCollection; private Dictionary _assemblyCatalogs; private volatile bool _isDisposed = false; private string _path; private string _fullPath; private string _searchPattern; private ReadOnlyCollection _loadedFiles; private IQueryable _partsQuery; /// /// Creates a catalog of s based on all the *.dll files /// in the given directory path. /// /// Possible exceptions that can be thrown are any that or /// can throw. /// /// /// Path to the directory to scan for assemblies to add to the catalog. /// The path needs to be absolute or relative to /// /// /// If is a zero-length string, contains only white space, or /// contains one or more implementation-specific invalid characters. /// /// /// is . /// /// /// The specified is invalid (for example, it is on an unmapped drive). /// /// /// The specified , file name, or both exceed the system-defined maximum length. /// For example, on Windows-based platforms, paths must be less than 248 characters and file names must /// be less than 260 characters. /// /// /// The caller does not have the required permission. /// public DirectoryCatalog(string path) : this(path, "*.dll") { } /// /// Creates a catalog of s based on all the given searchPattern /// over the files in the given directory path. /// /// Possible exceptions that can be thrown are any that or /// can throw. /// /// /// Path to the directory to scan for assemblies to add to the catalog. /// The path needs to be absolute or relative to /// /// /// Any valid searchPattern that will accept. /// /// /// If is a zero-length string, contains only white space, or /// contains one or more implementation-specific invalid characters. Or /// does not contain a valid pattern. /// /// /// is or is . /// /// /// The specified is invalid (for example, it is on an unmapped drive). /// /// /// The specified , file name, or both exceed the system-defined maximum length. /// For example, on Windows-based platforms, paths must be less than 248 characters and file names must /// be less than 260 characters. /// /// /// The caller does not have the required permission. /// public DirectoryCatalog(string path, string searchPattern) { Requires.NotNullOrEmpty(path, "path"); this.Initialize(path, searchPattern); } /// /// Translated absolute path of the path passed into the constructor of . /// public string FullPath { get { return this._fullPath; } } /// /// Set of files that have currently been loaded into the catalog. /// public ReadOnlyCollection LoadedFiles { get { using (new ReadLock(this._thisLock)) { return this._loadedFiles; } } } /// /// Path passed into the constructor of . /// public string Path { get { return this._path; } } /// /// Gets the part definitions of the directory catalog. /// /// /// A of objects of the /// . /// /// /// The has been disposed of. /// public override IQueryable Parts { get { this.ThrowIfDisposed(); return this._partsQuery; } } /// /// SearchPattern passed into the constructor of , or the default *.dll. /// public string SearchPattern { get { return this._searchPattern; } } /// /// Notify when the contents of the Catalog has changed. /// public event EventHandler Changed; /// /// Notify when the contents of the Catalog has changing. /// public event EventHandler Changing; /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { try { if (disposing) { if (!this._isDisposed) { bool disposeLock = false; ComposablePartCatalogCollection catalogs = null; try { using (new WriteLock(this._thisLock)) { if (!this._isDisposed) { disposeLock = true; catalogs = this._catalogCollection; this._catalogCollection = null; this._assemblyCatalogs = null; this._isDisposed = true; } } } finally { if (catalogs != null) { catalogs.Dispose(); } if (disposeLock) { this._thisLock.Dispose(); } } } } } finally { base.Dispose(disposing); } } /// /// Returns the export definitions that match the constraint defined by the specified definition. /// /// /// The that defines the conditions of the /// objects to return. /// /// /// An of containing the /// objects and their associated /// for objects that match the constraint defined /// by . /// /// /// is . /// /// /// The has been disposed of. /// public override IEnumerable> GetExports(ImportDefinition definition) { this.ThrowIfDisposed(); Requires.NotNull(definition, "definition"); return this._catalogCollection.SelectMany(catalog => catalog.GetExports(definition)); } /// /// Raises the event. /// /// /// An containing the data for the event. /// protected virtual void OnChanged(ComposablePartCatalogChangeEventArgs e) { EventHandler changedEvent = this.Changed; if (changedEvent != null) { changedEvent(this, e); } } /// /// Raises the event. /// /// /// An containing the data for the event. /// protected virtual void OnChanging(ComposablePartCatalogChangeEventArgs e) { EventHandler changingEvent = this.Changing; if (changingEvent != null) { changingEvent(this, e); } } /// /// Refreshes the s with the latest files in the directory that match /// the searchPattern. If any files have been added they will be added to the catalog and if any files were /// removed they will be removed from the catalog. For files that have been removed keep in mind that the /// assembly cannot be unloaded from the process so s for those files /// will simply be removed from the catalog. /// /// Possible exceptions that can be thrown are any that or /// can throw. /// /// /// The specified has been removed since object construction. /// public void Refresh() { this.ThrowIfDisposed(); Assumes.NotNull(this._loadedFiles); List> catalogsToAdd; List> catalogsToRemove; ComposablePartDefinition[] addedDefinitions; ComposablePartDefinition[] removedDefinitions; object changeReferenceObject; string[] afterFiles; string[] beforeFiles; while (true) { afterFiles = this.GetFiles(); using (new ReadLock(this._thisLock)) { changeReferenceObject = this._loadedFiles; beforeFiles = this._loadedFiles.ToArray(); } this.DiffChanges(beforeFiles, afterFiles, out catalogsToAdd, out catalogsToRemove); // Don't go any further if there's no work to do if (catalogsToAdd.Count == 0 && catalogsToRemove.Count == 0) { return; } // Notify listeners to give them a preview before completeting the changes addedDefinitions = catalogsToAdd .SelectMany(cat => cat.Item2.Parts) .ToArray(); removedDefinitions = catalogsToRemove .SelectMany(cat => cat.Item2.Parts) .ToArray(); using (var atomicComposition = new AtomicComposition()) { var changingArgs = new ComposablePartCatalogChangeEventArgs(addedDefinitions, removedDefinitions, atomicComposition); this.OnChanging(changingArgs); // if the change went through then write the catalog changes using (new WriteLock(this._thisLock)) { if (changeReferenceObject != this._loadedFiles) { // Someone updated the list while we were diffing so we need to try the diff again continue; } foreach (var catalogToAdd in catalogsToAdd) { this._assemblyCatalogs.Add(catalogToAdd.Item1, catalogToAdd.Item2); this._catalogCollection.Add(catalogToAdd.Item2); } foreach (var catalogToRemove in catalogsToRemove) { this._assemblyCatalogs.Remove(catalogToRemove.Item1); this._catalogCollection.Remove(catalogToRemove.Item2); } this._partsQuery = this._catalogCollection.AsQueryable().SelectMany(catalog => catalog.Parts); this._loadedFiles = afterFiles.ToReadOnlyCollection(); // Lastly complete any changes added to the atomicComposition during the change event atomicComposition.Complete(); // Break out of the while(true) break; } // WriteLock } // AtomicComposition } // while (true) var changedArgs = new ComposablePartCatalogChangeEventArgs(addedDefinitions, removedDefinitions, null); this.OnChanged(changedArgs); } /// /// Returns a string representation of the directory catalog. /// /// /// A containing the string representation of the . /// public override string ToString() { return GetDisplayName(); } private AssemblyCatalog CreateAssemblyCatalogGuarded(string assemblyFilePath) { Exception exception = null; try { return new AssemblyCatalog(assemblyFilePath, this); } catch (FileNotFoundException ex) { // Files should always exists but don't blow up here if they don't exception = ex; } catch (FileLoadException ex) { // File was found but could not be loaded exception = ex; } catch (BadImageFormatException ex) { // Dlls that contain native code are not loaded, but do not invalidate the Directory exception = ex; } catch (ReflectionTypeLoadException ex) { // Dlls that have missing Managed dependencies are not loaded, but do not invalidate the Directory exception = ex; } CompositionTrace.AssemblyLoadFailed(this, assemblyFilePath, exception); return null; } private void DiffChanges(string[] beforeFiles, string[] afterFiles, out List> catalogsToAdd, out List> catalogsToRemove) { catalogsToAdd = new List>(); catalogsToRemove = new List>(); IEnumerable filesToAdd = afterFiles.Except(beforeFiles); foreach (string file in filesToAdd) { AssemblyCatalog catalog = CreateAssemblyCatalogGuarded(file); if (catalog != null) { catalogsToAdd.Add(new Tuple(file, catalog)); } } IEnumerable filesToRemove = beforeFiles.Except(afterFiles); using (new ReadLock(this._thisLock)) { foreach (string file in filesToRemove) { AssemblyCatalog catalog; if (this._assemblyCatalogs.TryGetValue(file, out catalog)) { catalogsToRemove.Add(new Tuple(file, catalog)); } } } } private string GetDisplayName() { return string.Format(CultureInfo.CurrentCulture, "{0} (Path=\"{1}\")", // NOLOC this.GetType().Name, this._path); } private string[] GetFiles() { string[] files = Directory.GetFiles(this._fullPath, this._searchPattern); return Array.ConvertAll(files, (file) => file); } private static string GetFullPath(string path) { if (!IOPath.IsPathRooted(path) && AppDomain.CurrentDomain.BaseDirectory != null) { path = IOPath.Combine(AppDomain.CurrentDomain.BaseDirectory, path); } return IOPath.GetFullPath(path); } private void Initialize(string path, string searchPattern) { this._path = path; this._fullPath = GetFullPath(path); this._searchPattern = searchPattern; this._assemblyCatalogs = new Dictionary(); this._catalogCollection = new ComposablePartCatalogCollection(null); this._loadedFiles = GetFiles().ToReadOnlyCollection(); foreach (string file in this._loadedFiles) { AssemblyCatalog assemblyCatalog = null; assemblyCatalog = CreateAssemblyCatalogGuarded(file); if (assemblyCatalog != null) { this._assemblyCatalogs.Add(file, assemblyCatalog); this._catalogCollection.Add(assemblyCatalog); } } this._partsQuery = this._catalogCollection.AsQueryable().SelectMany(catalog => catalog.Parts); } private void ThrowIfDisposed() { if (this._isDisposed) { throw ExceptionBuilder.CreateObjectDisposed(this); } } /// /// Gets the display name of the directory catalog. /// /// /// A containing a human-readable display name of the . /// string ICompositionElement.DisplayName { get { return this.GetDisplayName(); } } /// /// Gets the composition element from which the directory catalog originated. /// /// /// This property always returns . /// ICompositionElement ICompositionElement.Origin { get { return null; } } } } #endif