// ----------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. // ----------------------------------------------------------------------- using System; using System.Collections.Generic; using System.ComponentModel.Composition.Primitives; using System.Linq; using System.Net; using System.Reflection; using System.Threading; using Microsoft.Internal; #if (SILVERLIGHT) namespace System.ComponentModel.Composition.Hosting { /// /// Implements a MEF catalog that supports Asynchronous download of Silverlast Xap files. /// public class DeploymentCatalog : ComposablePartCatalog, INotifyComposablePartCatalogChanged { static class State { public const int Created = 0; public const int Initialized = 1000; public const int DownloadStarted = 2000; public const int DownloadCompleted = 3000; public const int DownloadCancelled = 4000; } private Lock _lock = new Lock(); private volatile bool _isDisposed = false; private Uri _uri = null; private int _state = State.Created; private AggregateCatalog _catalogCollection = new AggregateCatalog(); private WebClient _webClient = null; /// /// Construct a Deployment catalog with the parts from the main Xap. /// public DeploymentCatalog() { this.DiscoverParts(Package.CurrentAssemblies); this._state = State.Initialized; } /// /// Construct a Deployment catalog with a string form relative uri. /// /// /// A relative Uri to the Download Xap file /// . /// /// /// The argument is null or an empty string. /// public DeploymentCatalog(string uriRelative) { Requires.NotNullOrEmpty(uriRelative, "uriRelative"); this._uri = new Uri(uriRelative, UriKind.Relative); } /// /// Construct a Deployment catalog with the parts from uri. /// /// /// A Uri to the Download Xap file /// . /// /// /// The argument is null. /// public DeploymentCatalog(Uri uri) { Requires.NotNull(uri, "uri"); this._uri = uri; } /// /// Notify when the contents of the Catalog has changed. /// public event EventHandler Changed; /// /// Notify when the contents of the Catalog is changing. /// public event EventHandler Changing; /// /// Notify when the download has been completed. /// public event EventHandler DownloadCompleted; /// /// Notify when the contents of the Progress of the download has changed. /// public event EventHandler DownloadProgressChanged; /// /// Retrieve or create the WebClient. /// /// /// The has been disposed of. /// private WebClient WebClient { get { this.ThrowIfDisposed(); if(this._webClient == null) { Interlocked.CompareExchange(ref this._webClient, new WebClient(), null); } return this._webClient; } } /// /// Gets the part definitions of the Deployment catalog. /// /// /// A of objects of the /// . /// /// /// The has been disposed of. /// public override IQueryable Parts { get { this.ThrowIfDisposed(); return this._catalogCollection.Parts; } } /// /// Gets the Uri of this catalog /// /// /// The has been disposed of. /// public Uri Uri { get { this.ThrowIfDisposed(); return this._uri; } } /// /// /// /// /// /// The has been disposed of. /// private void DiscoverParts(IEnumerable assemblies) { this.ThrowIfDisposed(); var addedDefinitions = new List(); var addedCatalogs = new Dictionary(); using(new ReadLock(this._lock)) { foreach (var assembly in assemblies) { if (addedCatalogs.ContainsKey(assembly.FullName)) { // Nothing to do because the assembly has already been added. continue; } var catalog = new AssemblyCatalog(assembly); addedDefinitions.AddRange(catalog.Parts); addedCatalogs.Add(assembly.FullName, catalog); } } // Generate notifications using (var atomicComposition = new AtomicComposition()) { var changingArgs = new ComposablePartCatalogChangeEventArgs(addedDefinitions, Enumerable.Empty(), atomicComposition); this.OnChanging(changingArgs); using (new WriteLock(this._lock)) { foreach (var item in addedCatalogs) { this._catalogCollection.Catalogs.Add(item.Value); } } atomicComposition.Complete(); } var changedArgs = new ComposablePartCatalogChangeEventArgs(addedDefinitions, Enumerable.Empty(), null); this.OnChanged(changedArgs); } /// /// 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.GetExports(definition); } /// /// Cancel the async operation. /// public void CancelAsync() { ThrowIfDisposed(); MutateStateOrThrow(State.DownloadCancelled, State.DownloadStarted); this.WebClient.CancelAsync(); } /// /// Begin the asynchronous download. /// public void DownloadAsync() { ThrowIfDisposed(); if (Interlocked.CompareExchange(ref this._state, State.DownloadStarted, State.Created) == State.Created) { // Created with Downloadable content do download this.WebClient.OpenReadCompleted += new OpenReadCompletedEventHandler(HandleOpenReadCompleted); this.WebClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(HandleDownloadProgressChanged); this.WebClient.OpenReadAsync(Uri, this); } else { // Created with LocalAssemblies MutateStateOrThrow(State.DownloadCompleted, State.Initialized); this.OnDownloadCompleted(new AsyncCompletedEventArgs(null, false, this)); } } void HandleDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) { EventHandler downloadProgressChangedEvent = this.DownloadProgressChanged; if (downloadProgressChangedEvent != null) { downloadProgressChangedEvent(this, e); } } private void HandleOpenReadCompleted(object sender, OpenReadCompletedEventArgs e) { Exception error = e.Error; bool cancelled = e.Cancelled; // Possible valid current states are DownloadStarted and DownloadCancelled. int currentState = Interlocked.CompareExchange(ref this._state, State.DownloadCompleted, State.DownloadStarted); if (currentState != State.DownloadStarted) { cancelled = true; } if (error == null && !cancelled) { try { var assemblies = Package.LoadPackagedAssemblies(e.Result); this.DiscoverParts(assemblies); } catch (Exception ex) { error = new InvalidOperationException(Strings.InvalidOperationException_ErrorReadingXap, ex); } } this.OnDownloadCompleted(new AsyncCompletedEventArgs(error, cancelled, this)); } /// /// 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); } } /// /// Raises the event. /// /// /// An containing the data for the event. /// protected virtual void OnDownloadCompleted(AsyncCompletedEventArgs e) { EventHandler downloadCompletedEvent = this.DownloadCompleted; if (downloadCompletedEvent != null) { downloadCompletedEvent(this, e); } } /// /// Raises the event. /// /// /// An containing the data for the event. /// protected virtual void OnDownloadProgressChanged(DownloadProgressChangedEventArgs e) { EventHandler downloadProgressChangedEvent = this.DownloadProgressChanged; if (downloadProgressChangedEvent != null) { downloadProgressChangedEvent(this, e); } } protected override void Dispose(bool disposing) { try { if (disposing) { if (!this._isDisposed) { AggregateCatalog catalogs = null; bool disposeLock = false; try { using (new WriteLock(this._lock)) { if (!this._isDisposed) { disposeLock = true; catalogs = this._catalogCollection; this._catalogCollection = null; this._isDisposed = true; } } } finally { if (catalogs != null) { catalogs.Dispose(); } if (disposeLock) { this._lock.Dispose(); } } } } } finally { base.Dispose(disposing); } } private void ThrowIfDisposed() { if (this._isDisposed) { throw new ObjectDisposedException(this.GetType().ToString()); } } private void MutateStateOrThrow(int toState, int fromState) { int currentState = Interlocked.CompareExchange(ref this._state, toState, fromState); if(currentState != fromState) { throw new InvalidOperationException(Strings.InvalidOperationException_DeploymentCatalogInvalidStateChange); } } } } #endif