1 // -----------------------------------------------------------------------
\r
2 // Copyright (c) Microsoft Corporation. All rights reserved.
\r
3 // -----------------------------------------------------------------------
\r
5 using System.Collections.Generic;
\r
6 using System.ComponentModel.Composition.Primitives;
\r
9 using System.Reflection;
\r
10 using System.Threading;
\r
11 using Microsoft.Internal;
\r
14 namespace System.ComponentModel.Composition.Hosting
\r
17 /// Implements a MEF catalog that supports Asynchronous download of Silverlast Xap files.
\r
19 public class DeploymentCatalog : ComposablePartCatalog, INotifyComposablePartCatalogChanged
\r
23 public const int Created = 0;
\r
24 public const int Initialized = 1000;
\r
25 public const int DownloadStarted = 2000;
\r
26 public const int DownloadCompleted = 3000;
\r
27 public const int DownloadCancelled = 4000;
\r
30 private Lock _lock = new Lock();
\r
31 private volatile bool _isDisposed = false;
\r
32 private Uri _uri = null;
\r
33 private int _state = State.Created;
\r
34 private AggregateCatalog _catalogCollection = new AggregateCatalog();
\r
35 private WebClient _webClient = null;
\r
38 /// Construct a Deployment catalog with the parts from the main Xap.
\r
40 public DeploymentCatalog()
\r
42 this.DiscoverParts(Package.CurrentAssemblies);
\r
43 this._state = State.Initialized;
\r
47 /// Construct a Deployment catalog with a string form relative uri.
\r
50 /// A relative Uri to the Download Xap file
\r
51 /// <see cref="DeploymentCatalog"/>.
\r
53 /// <exception cref="ArgumentException">
\r
54 /// The argument is null or an empty string.
\r
56 public DeploymentCatalog(string uriRelative)
\r
58 Requires.NotNullOrEmpty(uriRelative, "uriRelative");
\r
59 this._uri = new Uri(uriRelative, UriKind.Relative);
\r
63 /// Construct a Deployment catalog with the parts from uri.
\r
66 /// A Uri to the Download Xap file
\r
67 /// <see cref="System.Uri"/>.
\r
69 /// <exception cref="ArgumentException">
\r
70 /// The argument is null.
\r
72 public DeploymentCatalog(Uri uri)
\r
74 Requires.NotNull<Uri>(uri, "uri");
\r
79 /// Notify when the contents of the Catalog has changed.
\r
81 public event EventHandler<ComposablePartCatalogChangeEventArgs> Changed;
\r
84 /// Notify when the contents of the Catalog is changing.
\r
86 public event EventHandler<ComposablePartCatalogChangeEventArgs> Changing;
\r
89 /// Notify when the download has been completed.
\r
91 public event EventHandler<AsyncCompletedEventArgs> DownloadCompleted;
\r
94 /// Notify when the contents of the Progress of the download has changed.
\r
96 public event EventHandler<DownloadProgressChangedEventArgs> DownloadProgressChanged;
\r
99 /// Retrieve or create the WebClient.
\r
101 /// <exception cref="ObjectDisposedException">
\r
102 /// The <see cref="DeploymentCatalog"/> has been disposed of.
\r
104 private WebClient WebClient
\r
108 this.ThrowIfDisposed();
\r
109 if(this._webClient == null)
\r
111 Interlocked.CompareExchange<WebClient>(ref this._webClient, new WebClient(), null);
\r
113 return this._webClient;
\r
118 /// Gets the part definitions of the Deployment catalog.
\r
121 /// A <see cref="IQueryable{T}"/> of <see cref="ComposablePartDefinition"/> objects of the
\r
122 /// <see cref="DeploymentCatalog"/>.
\r
124 /// <exception cref="ObjectDisposedException">
\r
125 /// The <see cref="DeploymentCatalog"/> has been disposed of.
\r
127 public override IQueryable<ComposablePartDefinition> Parts
\r
131 this.ThrowIfDisposed();
\r
132 return this._catalogCollection.Parts;
\r
137 /// Gets the Uri of this catalog
\r
139 /// <exception cref="ObjectDisposedException">
\r
140 /// The <see cref="DeploymentCatalog"/> has been disposed of.
\r
146 this.ThrowIfDisposed();
\r
153 /// <param name="assemblies">
\r
155 /// <exception cref="ObjectDisposedException">
\r
156 /// The <see cref="DeploymentCatalog"/> has been disposed of.
\r
158 private void DiscoverParts(IEnumerable<Assembly> assemblies)
\r
160 this.ThrowIfDisposed();
\r
162 var addedDefinitions = new List<ComposablePartDefinition>();
\r
163 var addedCatalogs = new Dictionary<string, ComposablePartCatalog>();
\r
164 using(new ReadLock(this._lock))
\r
166 foreach (var assembly in assemblies)
\r
168 if (addedCatalogs.ContainsKey(assembly.FullName))
\r
170 // Nothing to do because the assembly has already been added.
\r
174 var catalog = new AssemblyCatalog(assembly);
\r
175 addedDefinitions.AddRange(catalog.Parts);
\r
176 addedCatalogs.Add(assembly.FullName, catalog);
\r
180 // Generate notifications
\r
181 using (var atomicComposition = new AtomicComposition())
\r
183 var changingArgs = new ComposablePartCatalogChangeEventArgs(addedDefinitions, Enumerable.Empty<ComposablePartDefinition>(), atomicComposition);
\r
184 this.OnChanging(changingArgs);
\r
186 using (new WriteLock(this._lock))
\r
188 foreach (var item in addedCatalogs)
\r
190 this._catalogCollection.Catalogs.Add(item.Value);
\r
193 atomicComposition.Complete();
\r
196 var changedArgs = new ComposablePartCatalogChangeEventArgs(addedDefinitions, Enumerable.Empty<ComposablePartDefinition>(), null);
\r
197 this.OnChanged(changedArgs);
\r
201 /// Returns the export definitions that match the constraint defined by the specified definition.
\r
203 /// <param name="definition">
\r
204 /// The <see cref="ImportDefinition"/> that defines the conditions of the
\r
205 /// <see cref="ExportDefinition"/> objects to return.
\r
208 /// An <see cref="IEnumerable{T}"/> of <see cref="Tuple{T1, T2}"/> containing the
\r
209 /// <see cref="ExportDefinition"/> objects and their associated
\r
210 /// <see cref="ComposablePartDefinition"/> for objects that match the constraint defined
\r
211 /// by <paramref name="definition"/>.
\r
213 /// <exception cref="ArgumentNullException">
\r
214 /// <paramref name="definition"/> is <see langword="null"/>.
\r
216 /// <exception cref="ObjectDisposedException">
\r
217 /// The <see cref="DeploymentCatalog"/> has been disposed of.
\r
219 public override IEnumerable<Tuple<ComposablePartDefinition, ExportDefinition>> GetExports(ImportDefinition definition)
\r
221 this.ThrowIfDisposed();
\r
222 Requires.NotNull(definition, "definition");
\r
224 return this._catalogCollection.GetExports(definition);
\r
228 /// Cancel the async operation.
\r
230 public void CancelAsync()
\r
233 MutateStateOrThrow(State.DownloadCancelled, State.DownloadStarted);
\r
234 this.WebClient.CancelAsync();
\r
238 /// Begin the asynchronous download.
\r
240 public void DownloadAsync()
\r
244 if (Interlocked.CompareExchange(ref this._state, State.DownloadStarted, State.Created) == State.Created)
\r
246 // Created with Downloadable content do download
\r
247 this.WebClient.OpenReadCompleted += new OpenReadCompletedEventHandler(HandleOpenReadCompleted);
\r
248 this.WebClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(HandleDownloadProgressChanged);
\r
249 this.WebClient.OpenReadAsync(Uri, this);
\r
253 // Created with LocalAssemblies
\r
254 MutateStateOrThrow(State.DownloadCompleted, State.Initialized);
\r
256 this.OnDownloadCompleted(new AsyncCompletedEventArgs(null, false, this));
\r
260 void HandleDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
\r
262 EventHandler<DownloadProgressChangedEventArgs> downloadProgressChangedEvent = this.DownloadProgressChanged;
\r
263 if (downloadProgressChangedEvent != null)
\r
265 downloadProgressChangedEvent(this, e);
\r
269 private void HandleOpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
\r
271 Exception error = e.Error;
\r
272 bool cancelled = e.Cancelled;
\r
274 // Possible valid current states are DownloadStarted and DownloadCancelled.
\r
275 int currentState = Interlocked.CompareExchange(ref this._state, State.DownloadCompleted, State.DownloadStarted);
\r
277 if (currentState != State.DownloadStarted)
\r
282 if (error == null && !cancelled)
\r
286 var assemblies = Package.LoadPackagedAssemblies(e.Result);
\r
287 this.DiscoverParts(assemblies);
\r
289 catch (Exception ex)
\r
291 error = new InvalidOperationException(Strings.InvalidOperationException_ErrorReadingXap, ex);
\r
295 this.OnDownloadCompleted(new AsyncCompletedEventArgs(error, cancelled, this));
\r
299 /// Raises the <see cref="INotifyComposablePartCatalogChanged.Changed"/> event.
\r
301 /// <param name="e">
\r
302 /// An <see cref="ComposablePartCatalogChangeEventArgs"/> containing the data for the event.
\r
304 protected virtual void OnChanged(ComposablePartCatalogChangeEventArgs e)
\r
306 EventHandler<ComposablePartCatalogChangeEventArgs> changedEvent = this.Changed;
\r
307 if (changedEvent != null)
\r
309 changedEvent(this, e);
\r
314 /// Raises the <see cref="INotifyComposablePartCatalogChanged.Changing"/> event.
\r
316 /// <param name="e">
\r
317 /// An <see cref="ComposablePartCatalogChangeEventArgs"/> containing the data for the event.
\r
319 protected virtual void OnChanging(ComposablePartCatalogChangeEventArgs e)
\r
321 EventHandler<ComposablePartCatalogChangeEventArgs> changingEvent = this.Changing;
\r
322 if (changingEvent != null)
\r
324 changingEvent(this, e);
\r
329 /// Raises the <see cref="DownloadCompleted"/> event.
\r
331 /// <param name="e">
\r
332 /// An <see cref="AsyncCompletedEventArgs"/> containing the data for the event.
\r
334 protected virtual void OnDownloadCompleted(AsyncCompletedEventArgs e)
\r
336 EventHandler<AsyncCompletedEventArgs> downloadCompletedEvent = this.DownloadCompleted;
\r
337 if (downloadCompletedEvent != null)
\r
339 downloadCompletedEvent(this, e);
\r
344 /// Raises the <see cref="DownloadProgressChanged"/> event.
\r
346 /// <param name="e">
\r
347 /// An <see cref="ProgressChangedEventArgs"/> containing the data for the event.
\r
349 protected virtual void OnDownloadProgressChanged(DownloadProgressChangedEventArgs e)
\r
351 EventHandler<DownloadProgressChangedEventArgs> downloadProgressChangedEvent = this.DownloadProgressChanged;
\r
352 if (downloadProgressChangedEvent != null)
\r
354 downloadProgressChangedEvent(this, e);
\r
358 protected override void Dispose(bool disposing)
\r
364 if (!this._isDisposed)
\r
366 AggregateCatalog catalogs = null;
\r
367 bool disposeLock = false;
\r
370 using (new WriteLock(this._lock))
\r
372 if (!this._isDisposed)
\r
374 disposeLock = true;
\r
375 catalogs = this._catalogCollection;
\r
376 this._catalogCollection = null;
\r
377 this._isDisposed = true;
\r
383 if (catalogs != null)
\r
385 catalogs.Dispose();
\r
390 this._lock.Dispose();
\r
398 base.Dispose(disposing);
\r
402 private void ThrowIfDisposed()
\r
404 if (this._isDisposed)
\r
406 throw new ObjectDisposedException(this.GetType().ToString());
\r
410 private void MutateStateOrThrow(int toState, int fromState)
\r
412 int currentState = Interlocked.CompareExchange(ref this._state, toState, fromState);
\r
413 if(currentState != fromState)
\r
415 throw new InvalidOperationException(Strings.InvalidOperationException_DeploymentCatalogInvalidStateChange);
\r