// -----------------------------------------------------------------------
// 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