diff --git a/Nuclex.Support.csproj b/Nuclex.Support.csproj
index 6957b75..23bfbb8 100644
--- a/Nuclex.Support.csproj
+++ b/Nuclex.Support.csproj
@@ -118,6 +118,7 @@
CommandLineParser.cs
+
PathHelper.cs
@@ -176,23 +177,44 @@
StringSegment.cs
-
-
+
+ IdleStateEventArgs.cs
+
+
+
+ ObservedWeightedTransaction.cs
+
+
+
+ WeightedTransactionWrapperCollection.cs
+
+
+ ProgressReportEventArgs.cs
+
ProgressTracker.cs
-
-
- WaitableGroup.cs
+
+ StatusReportEventArgs.cs
+
+
+ Transaction.cs
+
+
+
+ TransactionGroup.cs
-
-
+
+
+
+ WeightedTransaction.cs
+
WeakReference.cs
diff --git a/Source/Parsing/CommandLine.cs b/Source/Parsing/CommandLine.cs
new file mode 100644
index 0000000..76df0d7
--- /dev/null
+++ b/Source/Parsing/CommandLine.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Nuclex.Support.Source.Parsing {
+
+#if false
+ ///
+ /// Parses an application's command line parameters for easier consumption
+ ///
+ ///
+ ///
+ /// At the time of the creation of this parser, there are already several command line
+ /// parsing libraries out there. Most of them, however, do way too much at once or at
+ /// the very least use one huge, untested clutter of classes and methods to arrive
+ /// at their results.
+ ///
+ ///
+ /// This parser does nothing more than parse the command line arguments. It doesn't
+ /// interpret them and it doesn't check them for validity. Due to this, it can easily
+ /// be unit-tested and is an ideal building block to create actual command line
+ /// interpreters that connect the parameters to program instructions and or fill
+ /// structures in code.
+ ///
+ ///
+ public class CommandLine {
+ public static CommandLine Parse(string commandLine) {}
+ }
+
+ public struct CommandLineOption {
+
+ /// Contains the raw string the command line argument was parsed from
+ public string Raw;
+ /// Method used to specify the argument (either '-', '--' or '/')
+ public string Method;
+ /// Name of the command line argument
+ public string Name;
+ /// Value that has been assigned to the command line argument
+ public string Value;
+ /// Method used to assign the value (either '=', ':' or ' ')
+ public string Assignment;
+
+ }
+#endif
+
+}
diff --git a/Source/Plugins/PluginRepository.Test.cs b/Source/Plugins/PluginRepository.Test.cs
index 4de2c19..56910f1 100644
--- a/Source/Plugins/PluginRepository.Test.cs
+++ b/Source/Plugins/PluginRepository.Test.cs
@@ -77,9 +77,9 @@ namespace Nuclex.Support.Plugins {
///
[Test]
public void TestAddFilesWithOwnAssembly() {
- Assembly self = Assembly.GetAssembly(GetType());
-
PluginRepository testRepository = new PluginRepository();
+
+ Assembly self = Assembly.GetAssembly(GetType());
testRepository.AddFiles(self.Location);
}
diff --git a/Source/Scheduling/OperationQueue.Test.cs b/Source/Scheduling/OperationQueue.Test.cs
index df1e49a..01a4a61 100644
--- a/Source/Scheduling/OperationQueue.Test.cs
+++ b/Source/Scheduling/OperationQueue.Test.cs
@@ -35,24 +35,24 @@ namespace Nuclex.Support.Scheduling {
[TestFixture]
public class OperationQueueTest {
- #region interface IQueueOperationSubscriber
+ #region interface IOperationQueueSubscriber
- /// Interface used to test the set waitable.
- public interface IQueueOperationSubscriber {
+ /// Interface used to test the operation queue
+ public interface IOperationQueueSubscriber {
- /// Called when the queue operations's progress changes
- /// Queue operation whose progress has changed
- /// Contains the new progress achieved
- void ProgressChanged(object sender, ProgressReportEventArgs e);
+ /// Called when the operations queue's progress changes
+ /// Operation queue whose progress has changed
+ /// Contains the new progress achieved
+ void ProgressChanged(object sender, ProgressReportEventArgs arguments);
- /// Called when the queue operation has ended
- /// Queue operation that as ended
- /// Not used
- void Ended(object sender, EventArgs e);
+ /// Called when the operation queue has ended
+ /// Operation queue that as ended
+ /// Not used
+ void Ended(object sender, EventArgs arguments);
}
- #endregion // interface IQueueOperationSubscriber
+ #endregion // interface IOperationQueueSubscriber
#region class ProgressUpdateEventArgsMatcher
@@ -180,7 +180,7 @@ namespace Nuclex.Support.Scheduling {
new TestOperation[] { operation1, operation2 }
);
- IQueueOperationSubscriber mockedSubscriber = mockSubscriber(testQueueOperation);
+ IOperationQueueSubscriber mockedSubscriber = mockSubscriber(testQueueOperation);
testQueueOperation.Start();
@@ -212,9 +212,9 @@ namespace Nuclex.Support.Scheduling {
/// Mocks a subscriber for the events of an operation
/// Operation to mock an event subscriber for
/// The mocked event subscriber
- private IQueueOperationSubscriber mockSubscriber(Operation operation) {
- IQueueOperationSubscriber mockedSubscriber =
- this.mockery.NewMock();
+ private IOperationQueueSubscriber mockSubscriber(Operation operation) {
+ IOperationQueueSubscriber mockedSubscriber =
+ this.mockery.NewMock();
operation.AsyncEnded += new EventHandler(mockedSubscriber.Ended);
(operation as IProgressReporter).AsyncProgressChanged +=
diff --git a/Source/Scheduling/OperationQueue.cs b/Source/Scheduling/OperationQueue.cs
index bfd441c..816524b 100644
--- a/Source/Scheduling/OperationQueue.cs
+++ b/Source/Scheduling/OperationQueue.cs
@@ -42,23 +42,23 @@ namespace Nuclex.Support.Scheduling {
///
public OperationQueue(IEnumerable childs) : this() {
- // Construct a WeightedWaitable with the default weight for each
- // waitable and wrap it in an ObservedWaitable
+ // Construct a WeightedTransaction with the default weight for each
+ // transaction and wrap it in an ObservedTransaction
foreach(OperationType operation in childs)
- this.children.Add(new WeightedWaitable(operation));
+ this.children.Add(new WeightedTransaction(operation));
- // Since all waitables have a weight of 1.0, the total weight is
- // equal to the number of waitables in our list
+ // Since all transactions have a weight of 1.0, the total weight is
+ // equal to the number of transactions in our list
this.totalWeight = (float)this.children.Count;
}
/// Initializes a new queue operation with custom weights
/// Child operations to execute in this operation
- public OperationQueue(IEnumerable> childs) : this() {
+ public OperationQueue(IEnumerable> childs) : this() {
- // Construct an ObservedWaitablen around each of the WeightedWaitables
- foreach(WeightedWaitable operation in childs) {
+ // Construct an ObservedTransactionn around each of the WeightedTransactions
+ foreach(WeightedTransaction operation in childs) {
this.children.Add(operation);
// Sum up the total weight
@@ -74,11 +74,11 @@ namespace Nuclex.Support.Scheduling {
asyncOperationProgressChanged
);
- this.children = new List>();
+ this.children = new List>();
}
/// Provides access to the child operations of this queue
- public IList> Children {
+ public IList> Children {
get { return this.children; }
}
@@ -99,7 +99,7 @@ namespace Nuclex.Support.Scheduling {
/// Fires the progress update event
/// Progress to report (ranging from 0.0 to 1.0)
///
- /// Informs the observers of this waitable about the achieved progress.
+ /// Informs the observers of this transaction about the achieved progress.
///
protected virtual void OnAsyncProgressChanged(float progress) {
OnAsyncProgressChanged(new ProgressReportEventArgs(progress));
@@ -108,10 +108,10 @@ namespace Nuclex.Support.Scheduling {
/// Fires the progress update event
/// Progress to report (ranging from 0.0 to 1.0)
///
- /// Informs the observers of this waitable about the achieved progress.
- /// Allows for classes derived from the Waitable class to easily provide
+ /// Informs the observers of this transaction about the achieved progress.
+ /// Allows for classes derived from the transaction class to easily provide
/// a custom event arguments class that has been derived from the
- /// waitable's ProgressUpdateEventArgs class.
+ /// transaction's ProgressUpdateEventArgs class.
///
protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) {
EventHandler copy = AsyncProgressChanged;
@@ -125,7 +125,7 @@ namespace Nuclex.Support.Scheduling {
/// and launches the operation by calling its Begin() method.
///
private void startCurrentOperation() {
- OperationType operation = this.children[this.currentOperationIndex].Waitable;
+ OperationType operation = this.children[this.currentOperationIndex].Transaction;
operation.AsyncEnded += this.asyncOperationEndedDelegate;
@@ -143,7 +143,7 @@ namespace Nuclex.Support.Scheduling {
/// counts up the accumulated progress of the queue.
///
private void endCurrentOperation() {
- OperationType operation = this.children[this.currentOperationIndex].Waitable;
+ OperationType operation = this.children[this.currentOperationIndex].Transaction;
// Disconnect from the operation's events
operation.AsyncEnded -= this.asyncOperationEndedDelegate;
@@ -217,7 +217,7 @@ namespace Nuclex.Support.Scheduling {
/// Delegate to the asyncOperationProgressUpdated() method
private EventHandler asyncOperationProgressChangedDelegate;
/// Operations being managed in the queue
- private List> children;
+ private List> children;
/// Summed weight of all operations in the queue
private float totalWeight;
/// Accumulated weight of the operations already completed
diff --git a/Source/Tracking/IdleStateEventArgs.Test.cs b/Source/Tracking/IdleStateEventArgs.Test.cs
new file mode 100644
index 0000000..7b673d4
--- /dev/null
+++ b/Source/Tracking/IdleStateEventArgs.Test.cs
@@ -0,0 +1,59 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2008 Nuclex Development Labs
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the IBM Common Public License as
+published by the IBM Corporation; either version 1.0 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+IBM Common Public License for more details.
+
+You should have received a copy of the IBM Common Public
+License along with this library
+*/
+#endregion
+
+using System;
+using System.IO;
+
+#if UNITTEST
+
+using NUnit.Framework;
+using NUnit.Framework.SyntaxHelpers;
+
+namespace Nuclex.Support.Tracking {
+
+ /// Unit Test for the "idle state" event argument container
+ [TestFixture]
+ public class IdleStateEventArgsTest {
+
+ ///
+ /// Tests whether the idle state event arguments correctly report a non-idle state
+ ///
+ [Test]
+ public void TestIdleStateChangedToFalse() {
+ IdleStateEventArgs idleStateFalse = new IdleStateEventArgs(false);
+
+ Assert.IsFalse(idleStateFalse.Idle);
+ }
+
+ ///
+ /// Tests whether the idle state event arguments correctly report an idle state
+ ///
+ [Test]
+ public void TestIdleStateChangedToTrue() {
+ IdleStateEventArgs idleStateFalse = new IdleStateEventArgs(true);
+
+ Assert.IsTrue(idleStateFalse.Idle);
+ }
+
+ }
+
+} // namespace Nuclex.Support.Tracking
+
+#endif // UNITTEST
diff --git a/Source/Tracking/Internal/ObservedWeightedTransaction.Test.cs b/Source/Tracking/Internal/ObservedWeightedTransaction.Test.cs
new file mode 100644
index 0000000..077f828
--- /dev/null
+++ b/Source/Tracking/Internal/ObservedWeightedTransaction.Test.cs
@@ -0,0 +1,158 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2008 Nuclex Development Labs
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the IBM Common Public License as
+published by the IBM Corporation; either version 1.0 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+IBM Common Public License for more details.
+
+You should have received a copy of the IBM Common Public
+License along with this library
+*/
+#endregion
+
+using System;
+using System.IO;
+using System.Threading;
+
+#if UNITTEST
+
+using NUnit.Framework;
+using NUnit.Framework.SyntaxHelpers;
+using NMock2;
+
+namespace Nuclex.Support.Tracking {
+
+ /// Unit Test for the observation wrapper of weighted transactions
+ [TestFixture]
+ public class ObservedWeightedTransactionTest {
+
+ #region interface IObservationSubscriber
+
+ ///
+ /// Interface used to test the observation wrapper of weighted transactions
+ ///
+ public interface IObservationSubscriber {
+
+ /// Will be invoked when an observed transaction's progress changes
+ void ProgressUpdated();
+
+ /// Will be invoked when an observed transaction completes
+ void Ended();
+
+ }
+
+ #endregion // interface IObservationSubscriber
+
+ #region class FunkyTransaction
+
+ ///
+ /// Transaction that goes into the 'ended' state as soon as someone registers for
+ /// state change notifications
+ ///
+ private class FunkyTransaction : Transaction {
+
+ /// Manages registrations to the AsyncEnded event
+ public override event EventHandler AsyncEnded {
+ add {
+ base.AsyncEnded += value;
+
+ // To deterministically provoke an 'Ended' event just after registration we
+ // will switch the transaction into the 'ended' state right here
+ int oldValue = Interlocked.Exchange(ref this.alreadyEnded, 1);
+ if(oldValue != 1) {
+ OnAsyncEnded();
+ }
+ }
+ remove {
+ base.AsyncEnded -= value;
+ }
+ }
+
+ /// Whether the transaction has already been ended
+ private int alreadyEnded;
+
+ }
+
+ #endregion // class FunkyTransaction
+
+ /// Initialization routine executed before each test is run
+ [SetUp]
+ public void Setup() {
+ this.mockery = new Mockery();
+ }
+
+ /// Verifies that the constructor of the observation wrapper works
+ [Test]
+ public void TestConstructorWithAlreadyEndedTransaction() {
+ WeightedTransaction testTransaction = new WeightedTransaction(
+ Transaction.EndedDummy
+ );
+
+ IObservationSubscriber subscriber = this.mockery.NewMock();
+
+ Expect.AtLeast(0).On(subscriber).Method("ProgressUpdated");
+ Expect.Once.On(subscriber).Method("Ended");
+
+ using(
+ ObservedWeightedTransaction test =
+ new ObservedWeightedTransaction(
+ testTransaction,
+ new ObservedWeightedTransaction.ReportDelegate(
+ subscriber.ProgressUpdated
+ ),
+ new ObservedWeightedTransaction.ReportDelegate(
+ subscriber.Ended
+ )
+ )
+ ) {
+ this.mockery.VerifyAllExpectationsHaveBeenMet();
+ }
+ }
+
+ ///
+ /// Verifies that the constructor of the observation wrapper can handle a transaction
+ /// entering the 'ended' state right on subscription
+ ///
+ [Test]
+ public void TestConstructorWithEndingTransaction() {
+ WeightedTransaction testTransaction = new WeightedTransaction(
+ new FunkyTransaction()
+ );
+
+ IObservationSubscriber subscriber = this.mockery.NewMock();
+
+ Expect.AtLeast(0).On(subscriber).Method("ProgressUpdated");
+ Expect.Once.On(subscriber).Method("Ended");
+
+ using(
+ ObservedWeightedTransaction test =
+ new ObservedWeightedTransaction(
+ testTransaction,
+ new ObservedWeightedTransaction.ReportDelegate(
+ subscriber.ProgressUpdated
+ ),
+ new ObservedWeightedTransaction.ReportDelegate(
+ subscriber.Ended
+ )
+ )
+ ) {
+ this.mockery.VerifyAllExpectationsHaveBeenMet();
+ }
+ }
+
+ /// Mock object factory
+ private Mockery mockery;
+
+ }
+
+} // namespace Nuclex.Support.Tracking
+
+#endif // UNITTEST
diff --git a/Source/Tracking/Internal/ObservedWeightedTransaction.cs b/Source/Tracking/Internal/ObservedWeightedTransaction.cs
new file mode 100644
index 0000000..32ae33f
--- /dev/null
+++ b/Source/Tracking/Internal/ObservedWeightedTransaction.cs
@@ -0,0 +1,177 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2008 Nuclex Development Labs
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the IBM Common Public License as
+published by the IBM Corporation; either version 1.0 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+IBM Common Public License for more details.
+
+You should have received a copy of the IBM Common Public
+License along with this library
+*/
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace Nuclex.Support.Tracking {
+
+ /// Transaction being observed by another object
+ ///
+ /// Type of the transaction that is being observed
+ ///
+ internal class ObservedWeightedTransaction : IDisposable
+ where TransactionType : Transaction {
+
+ /// Delegate for reporting progress updates
+ public delegate void ReportDelegate();
+
+ /// Initializes a new observed transaction
+ /// Weighted transaction being observed
+ ///
+ /// Callback to invoke when the transaction's progress changes
+ ///
+ ///
+ /// Callback to invoke when the transaction has ended
+ ///
+ internal ObservedWeightedTransaction(
+ WeightedTransaction weightedTransaction,
+ ReportDelegate progressUpdateCallback,
+ ReportDelegate endedCallback
+ ) {
+ this.weightedTransaction = weightedTransaction;
+
+ // See if this transaction has already ended (initial check for performance)
+ if(weightedTransaction.Transaction.Ended) {
+
+ // Since we don't subscribe to the .Ended event (which would be fired immediately on
+ // subscription if the transaction was already finished), we will emulate this
+ // behavior here. There is no race condition here: The transition to .Ended occurs
+ // only once and will never happen in reverse. This is just a minor optimization to
+ // prevent object coupling where none is neccessary and to save some processing time.
+ this.progress = 1.0f;
+ progressUpdateCallback();
+ endedCallback();
+
+ return;
+
+ }
+
+ this.endedCallback = endedCallback;
+ this.progressUpdateCallback = progressUpdateCallback;
+
+ // This might trigger the event handler to be invoked right here if the transaction
+ // ended between our initial optimization attempt and this line. It's unlikely,
+ // however, so we'll not waste time with another optimization attempt.
+ this.weightedTransaction.Transaction.AsyncEnded += new EventHandler(asyncEnded);
+
+ // See whether this transaction implements the IProgressReporter interface and if
+ // so, connect to its progress report event in order to pass these reports on
+ // to whomever created ourselfes.
+ this.progressReporter = this.weightedTransaction.Transaction as IProgressReporter;
+ if(this.progressReporter != null) {
+ this.asyncProgressChangedEventHandler = new EventHandler(
+ asyncProgressChanged
+ );
+ this.progressReporter.AsyncProgressChanged += this.asyncProgressChangedEventHandler;
+ }
+ }
+
+ /// Immediately releases all resources owned by the object
+ public void Dispose() {
+ asyncDisconnectEvents();
+ }
+
+ /// Weighted transaction being observed
+ public WeightedTransaction WeightedTransaction {
+ get { return this.weightedTransaction; }
+ }
+
+ /// Amount of progress this transaction has achieved so far
+ public float Progress {
+ get { return this.progress; }
+ }
+
+ /// Called when the observed transaction has ended
+ /// Transaction that has ended
+ /// Not used
+ private void asyncEnded(object sender, EventArgs e) {
+ ReportDelegate savedEndedCallback = this.endedCallback;
+ ReportDelegate savedProgressUpdateCallback = this.progressUpdateCallback;
+
+ asyncDisconnectEvents(); // We don't need those anymore!
+
+ // If the progress hasn't reached 1.0 yet, make a fake report so that even
+ // when a transaction doesn't report any progress at all, the set or queue
+ // owning us will have a percentage of transactions completed.
+ //
+ // There is the possibility of a race condition here, as a final progress
+ // report could have been generated by a thread running the transaction
+ // that was preempted by this thread. This would cause the progress to
+ // jump to 1.0 and then back to whatever the waiting thread will report.
+ if(this.progress != 1.0f) {
+ this.progress = 1.0f;
+ savedProgressUpdateCallback();
+ }
+
+ savedEndedCallback();
+ }
+
+ /// Called when the progress of the observed transaction changes
+ /// Transaction whose progress has changed
+ /// Contains the updated progress
+ private void asyncProgressChanged(object sender, ProgressReportEventArgs e) {
+ this.progress = e.Progress;
+ this.progressUpdateCallback();
+ }
+
+ /// Unsubscribes from all events of the observed transaction
+ private void asyncDisconnectEvents() {
+
+ // Make use of the double check locking idiom to avoid the costly lock when
+ // the events have already been unsubscribed
+ if(this.endedCallback != null) {
+
+ // This is an internal class with special knowledge that there
+ // is no risk of deadlock involved, so we don't need a fancy syncRoot!
+ lock(this) {
+ if(this.endedCallback != null) {
+ this.weightedTransaction.Transaction.AsyncEnded -= new EventHandler(asyncEnded);
+
+ if(this.progressReporter != null) {
+ this.progressReporter.AsyncProgressChanged -=
+ this.asyncProgressChangedEventHandler;
+
+ this.asyncProgressChangedEventHandler = null;
+ }
+
+ this.endedCallback = null;
+ this.progressUpdateCallback = null;
+ }
+ }
+
+ } // endedCallback != null
+
+ }
+
+ private EventHandler asyncProgressChangedEventHandler;
+ /// The observed transaction's progress reporting interface
+ private IProgressReporter progressReporter;
+ /// The weighted wable that is being observed
+ private WeightedTransaction weightedTransaction;
+ /// Callback to invoke when the progress updates
+ private volatile ReportDelegate progressUpdateCallback;
+ /// Callback to invoke when the transaction ends
+ private volatile ReportDelegate endedCallback;
+ /// Progress achieved so far
+ private volatile float progress;
+ }
+
+} // namespace Nuclex.Support.Tracking
diff --git a/Source/Tracking/Internal/ObservedWeightedWaitable.cs b/Source/Tracking/Internal/ObservedWeightedWaitable.cs
deleted file mode 100644
index 1fa6c76..0000000
--- a/Source/Tracking/Internal/ObservedWeightedWaitable.cs
+++ /dev/null
@@ -1,177 +0,0 @@
-#region CPL License
-/*
-Nuclex Framework
-Copyright (C) 2002-2008 Nuclex Development Labs
-
-This library is free software; you can redistribute it and/or
-modify it under the terms of the IBM Common Public License as
-published by the IBM Corporation; either version 1.0 of the
-License, or (at your option) any later version.
-
-This library is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-IBM Common Public License for more details.
-
-You should have received a copy of the IBM Common Public
-License along with this library
-*/
-#endregion
-
-using System;
-using System.Collections.Generic;
-
-namespace Nuclex.Support.Tracking {
-
- /// Waitable being observed by another object
- ///
- /// Type of the waitable that is being observed
- ///
- internal class ObservedWeightedWaitable : IDisposable
- where WaitableType : Waitable {
-
- /// Delegate for reporting progress updates
- public delegate void ReportDelegate();
-
- /// Initializes a new observed waitable
- /// Weighted waitable being observed
- ///
- /// Callback to invoke when the waitable's progress changes
- ///
- ///
- /// Callback to invoke when the waitable has ended
- ///
- internal ObservedWeightedWaitable(
- WeightedWaitable weightedWaitable,
- ReportDelegate progressUpdateCallback,
- ReportDelegate endedCallback
- ) {
- this.weightedWaitable = weightedWaitable;
-
- // See if this waitable has already ended (initial check for performance)
- if(weightedWaitable.Waitable.Ended) {
-
- this.progress = 1.0f;
-
- } else {
-
- this.endedCallback = endedCallback;
- this.progressUpdateCallback = progressUpdateCallback;
-
- this.weightedWaitable.Waitable.AsyncEnded +=
- new EventHandler(asyncEnded);
-
- // Check whether this waitable might have ended before we were able to
- // attach ourselfes to its event. If so, don't bother registering to the
- // other event and (important) set our progress to 1.0 because, since we
- // might not have gotten the 'Ended' event, it might otherwise stay at 0.0
- // even though the waitable is in the 'Ended' state.
- if(weightedWaitable.Waitable.Ended) {
- this.progress = 1.0f;
- } else {
- this.progressReporter = this.weightedWaitable.Waitable as IProgressReporter;
-
- if(this.progressReporter != null) {
- this.asyncProgressChangedEventHandler = new EventHandler(
- asyncProgressChanged
- );
-
- this.progressReporter.AsyncProgressChanged +=
- this.asyncProgressChangedEventHandler;
- }
- }
-
- }
- }
-
- /// Immediately releases all resources owned by the object
- public void Dispose() {
- asyncDisconnectEvents();
- }
-
- /// Weighted waitable being observed
- public WeightedWaitable WeightedWaitable {
- get { return this.weightedWaitable; }
- }
-
- /// Amount of progress this waitable has achieved so far
- public float Progress {
- get { return this.progress; }
- }
-
- /// Called when the observed waitable has ended
- /// Waitable that has ended
- /// Not used
- private void asyncEnded(object sender, EventArgs e) {
- ReportDelegate endedCallback = this.endedCallback;
- ReportDelegate progressUpdateCallback = this.progressUpdateCallback;
-
- asyncDisconnectEvents(); // We don't need those anymore!
-
- // If the progress hasn't reached 1.0 yet, make a fake report so that even
- // when a waitable doesn't report any progress at all, the set or queue
- // owning us will have a percentage of waitables completed.
- //
- // There is the possibility of a race condition here, as a final progress
- // report could have been generated by a thread running the waitable
- // that was preempted by this thread. This would cause the progress to
- // jump to 1.0 and then back to whatever the waiting thread will report.
- if(this.progress != 1.0f) {
- this.progress = 1.0f;
- progressUpdateCallback();
- }
-
- endedCallback();
- }
-
- /// Called when the progress of the observed waitable changes
- /// Waitable whose progress has changed
- /// Contains the updated progress
- private void asyncProgressChanged(object sender, ProgressReportEventArgs e) {
- this.progress = e.Progress;
- this.progressUpdateCallback();
- }
-
- /// Unsubscribes from all events of the observed waitable
- private void asyncDisconnectEvents() {
-
- // Make use of the double check locking idiom to avoid the costly lock when
- // the events have already been unsubscribed
- if(this.endedCallback != null) {
-
- // This is an internal class with special knowledge that there
- // is no risk of deadlock involved, so we don't need a fancy syncRoot!
- lock(this) {
- if(this.endedCallback != null) {
- this.weightedWaitable.Waitable.AsyncEnded -= new EventHandler(asyncEnded);
-
- if(this.progressReporter != null) {
- this.progressReporter.AsyncProgressChanged -=
- this.asyncProgressChangedEventHandler;
-
- this.asyncProgressChangedEventHandler = null;
- }
-
- this.endedCallback = null;
- this.progressUpdateCallback = null;
- }
- }
-
- } // endedCallback != null
-
- }
-
- private EventHandler asyncProgressChangedEventHandler;
- /// The observed waitable's progress reporting interface
- private IProgressReporter progressReporter;
- /// The weighted wable that is being observed
- private WeightedWaitable weightedWaitable;
- /// Callback to invoke when the progress updates
- private volatile ReportDelegate progressUpdateCallback;
- /// Callback to invoke when the waitable ends
- private volatile ReportDelegate endedCallback;
- /// Progress achieved so far
- private volatile float progress;
- }
-
-} // namespace Nuclex.Support.Tracking
diff --git a/Source/Tracking/Internal/WeightedTransactionWrapperCollection.Test.cs b/Source/Tracking/Internal/WeightedTransactionWrapperCollection.Test.cs
new file mode 100644
index 0000000..b87b7f1
--- /dev/null
+++ b/Source/Tracking/Internal/WeightedTransactionWrapperCollection.Test.cs
@@ -0,0 +1,70 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2008 Nuclex Development Labs
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the IBM Common Public License as
+published by the IBM Corporation; either version 1.0 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+IBM Common Public License for more details.
+
+You should have received a copy of the IBM Common Public
+License along with this library
+*/
+#endregion
+
+using System;
+using System.IO;
+using System.Threading;
+
+#if UNITTEST
+
+using NUnit.Framework;
+using NUnit.Framework.SyntaxHelpers;
+using NMock2;
+
+namespace Nuclex.Support.Tracking {
+
+ /// Unit Test for the observation wrapper collection of weighted transactions
+ [TestFixture]
+ public class WeightedTransactionWrapperCollectionTest {
+
+ ///
+ /// Tests whether the wrapper collection is handing out the unwrapped transactions
+ ///
+ [Test]
+ public void TestWrapperCollection() {
+ WeightedTransaction transaction = new WeightedTransaction(
+ Transaction.EndedDummy
+ );
+
+ ObservedWeightedTransaction observed = new ObservedWeightedTransaction(
+ transaction,
+ endedCallback,
+ progressUpdatedCallback
+ );
+
+ WeightedTransactionWrapperCollection wrapper =
+ new WeightedTransactionWrapperCollection(
+ new ObservedWeightedTransaction[] { observed }
+ );
+
+ Assert.AreSame(transaction, wrapper[0]);
+ }
+
+ /// Dummy callback used as event subscriber in the tests
+ private void endedCallback() { }
+
+ /// Dummy callback used as event subscriber in the tests
+ private void progressUpdatedCallback() { }
+
+ }
+
+} // namespace Nuclex.Support.Tracking
+
+#endif // UNITTEST
diff --git a/Source/Tracking/Internal/WeightedWaitableWrapperCollection.cs b/Source/Tracking/Internal/WeightedTransactionWrapperCollection.cs
similarity index 50%
rename from Source/Tracking/Internal/WeightedWaitableWrapperCollection.cs
rename to Source/Tracking/Internal/WeightedTransactionWrapperCollection.cs
index 8ffb4ef..0b90517 100644
--- a/Source/Tracking/Internal/WeightedWaitableWrapperCollection.cs
+++ b/Source/Tracking/Internal/WeightedTransactionWrapperCollection.cs
@@ -27,36 +27,36 @@ using Nuclex.Support.Collections;
namespace Nuclex.Support.Tracking {
- /// Collection of waitables with a weighting value
- /// Type of waitables to manage
+ /// Collection of transactions with a weighting value
+ /// Type of transactions to manage
///
///
/// This collection is exposed as a read-only collection to the user that
- /// stores WeightedWaitables. Internally, it merely wraps a collection of
- /// an internal type used to keep track of the individual waitable's
- /// progress in the WaitableSet and OperationQueue classes.
+ /// stores WeightedTransactions. Internally, it merely wraps a collection of
+ /// an internal type used to keep track of the individual transaction's
+ /// progress in the TransactionGroup and OperationQueue classes.
///
///
- /// It is read-only because the design requires a waitable to only ever
- /// finish once. If it was possible eg. to add items after a WaitableSet
- /// had signalled itself as being finished, it would be moved into an
- /// unfinished state again. Also, an empty WaitableSet is, by definition,
- /// finished (simply because there is no work to do) - unless the contents
- /// of set are passed to the WaitableSet's constructor and never modified
- /// at all, the design would be violated as soon as ab instance of the
- /// WaitableSet or OperationQueue classes was created.
+ /// It is read-only because the design requires a transaction to only ever finish
+ /// once. If it was possible eg. to add items after a TransactionGroup had signalled
+ /// itself as being finished, it would be moved into an unfinished state again.
+ /// Also, an empty TransactionGroup is, by definition, finished (simply because
+ /// there is no work to do) - unless the contents of the group are passed to the
+ /// TransactionGroup's constructor and never modified at all, the design would be
+ /// violated as soon as an instance of the TransactionGroup or OperationQueue
+ /// classes was created.
///
///
- internal class WeightedWaitableWrapperCollection :
+ internal class WeightedTransactionWrapperCollection :
TransformingReadOnlyCollection<
- ObservedWeightedWaitable, WeightedWaitable
+ ObservedWeightedTransaction, WeightedTransaction
>
- where WaitableType : Waitable {
+ where TransactionType : Transaction {
- /// Initializes a new weighted waitable collection wrapper
- /// Items to be exposed as weighted waitables
- internal WeightedWaitableWrapperCollection(
- IList> items
+ /// Initializes a new weighted transaction collection wrapper
+ /// Items to be exposed as weighted transactions
+ internal WeightedTransactionWrapperCollection(
+ IList> items
)
: base(items) { }
@@ -69,10 +69,10 @@ namespace Nuclex.Support.Tracking {
/// be called frequently, because the TransformingReadOnlyCollection does
/// not cache otherwise store the transformed items.
///
- protected override WeightedWaitable Transform(
- ObservedWeightedWaitable item
+ protected override WeightedTransaction Transform(
+ ObservedWeightedTransaction item
) {
- return item.WeightedWaitable;
+ return item.WeightedTransaction;
}
}
diff --git a/Source/Tracking/ProgressReportEventArgs.Test.cs b/Source/Tracking/ProgressReportEventArgs.Test.cs
new file mode 100644
index 0000000..84cdc9e
--- /dev/null
+++ b/Source/Tracking/ProgressReportEventArgs.Test.cs
@@ -0,0 +1,59 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2008 Nuclex Development Labs
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the IBM Common Public License as
+published by the IBM Corporation; either version 1.0 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+IBM Common Public License for more details.
+
+You should have received a copy of the IBM Common Public
+License along with this library
+*/
+#endregion
+
+using System;
+using System.IO;
+
+#if UNITTEST
+
+using NUnit.Framework;
+using NUnit.Framework.SyntaxHelpers;
+
+namespace Nuclex.Support.Tracking {
+
+ /// Unit Test for the progress report event argument container
+ [TestFixture]
+ public class ProgressReportEventArgsTest {
+
+ ///
+ /// Tests whether the progress report event arguments correctly report zero progress
+ ///
+ [Test]
+ public void TestZeroProgress() {
+ ProgressReportEventArgs zeroProgress = new ProgressReportEventArgs(0.0f);
+
+ Assert.AreEqual(0.0f, zeroProgress.Progress);
+ }
+
+ ///
+ /// Tests whether the progress report event arguments correctly report complete progress
+ ///
+ [Test]
+ public void TestCompleteProgress() {
+ ProgressReportEventArgs zeroProgress = new ProgressReportEventArgs(1.0f);
+
+ Assert.AreEqual(1.0f, zeroProgress.Progress);
+ }
+
+ }
+
+} // namespace Nuclex.Support.Tracking
+
+#endif // UNITTEST
diff --git a/Source/Tracking/ProgressTracker.Test.cs b/Source/Tracking/ProgressTracker.Test.cs
index 248b125..c778ebc 100644
--- a/Source/Tracking/ProgressTracker.Test.cs
+++ b/Source/Tracking/ProgressTracker.Test.cs
@@ -91,21 +91,21 @@ namespace Nuclex.Support.Tracking {
#endregion // class ProgressUpdateEventArgsMatcher
- #region class TestWaitable
+ #region class TestTransaction
- /// Waitable used for testing in this unit test
- private class TestWaitable : Waitable, IProgressReporter {
+ /// Transaction used for testing in this unit test
+ private class TestTransaction : Transaction, IProgressReporter {
/// will be triggered to report when progress has been achieved
public event EventHandler AsyncProgressChanged;
- /// Changes the testing waitable's indicated progress
- /// New progress to be reported by the testing waitable
+ /// Changes the testing transaction's indicated progress
+ /// New progress to be reported by the testing transaction
public void ChangeProgress(float progress) {
OnAsyncProgressChanged(progress);
}
- /// Transitions the waitable into the ended state
+ /// Transitions the transaction into the ended state
public void End() {
OnAsyncEnded();
}
@@ -113,7 +113,7 @@ namespace Nuclex.Support.Tracking {
/// Fires the progress update event
/// Progress to report (ranging from 0.0 to 1.0)
///
- /// Informs the observers of this waitable about the achieved progress.
+ /// Informs the observers of this transaction about the achieved progress.
///
protected virtual void OnAsyncProgressChanged(float progress) {
OnAsyncProgressChanged(new ProgressReportEventArgs(progress));
@@ -122,10 +122,10 @@ namespace Nuclex.Support.Tracking {
/// Fires the progress update event
/// Progress to report (ranging from 0.0 to 1.0)
///
- /// Informs the observers of this waitable about the achieved progress.
- /// Allows for classes derived from the Waitable class to easily provide
+ /// Informs the observers of this transaction about the achieved progress.
+ /// Allows for classes derived from the transaction class to easily provide
/// a custom event arguments class that has been derived from the
- /// waitable's ProgressUpdateEventArgs class.
+ /// transaction's ProgressUpdateEventArgs class.
///
protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) {
EventHandler copy = AsyncProgressChanged;
@@ -146,204 +146,238 @@ namespace Nuclex.Support.Tracking {
/// Validates that the tracker properly sums the progress
[Test]
public void TestSummedProgress() {
- ProgressTracker tracker = new ProgressTracker();
+ using(ProgressTracker tracker = new ProgressTracker()) {
+ IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
- IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
+ TestTransaction test1 = new TestTransaction();
+ TestTransaction test2 = new TestTransaction();
- Expect.Once.On(mockedSubscriber).
- Method("IdleStateChanged").
- WithAnyArguments();
+ // Step 1
+ {
+ Expect.Once.On(mockedSubscriber).
+ Method("IdleStateChanged").
+ WithAnyArguments();
- Expect.Exactly(2).On(mockedSubscriber).
- Method("ProgressChanged").
- With(
- new Matcher[] {
- new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
- new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.0f))
- }
- );
+ // Since the progress is already at 0, these redundant reports are optional
+ Expect.Between(0, 2).On(mockedSubscriber).
+ Method("ProgressChanged").
+ With(
+ new Matcher[] {
+ new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
+ new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.0f))
+ }
+ );
- TestWaitable test1 = new TestWaitable();
- tracker.Track(test1);
- TestWaitable test2 = new TestWaitable();
- tracker.Track(test2);
+ tracker.Track(test1);
+ tracker.Track(test2);
+ }
- Expect.Once.On(mockedSubscriber).
- Method("ProgressChanged").
- With(
- new Matcher[] {
- new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
- new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.25f))
- }
- );
+ // Step 2
+ {
+ Expect.Once.On(mockedSubscriber).
+ Method("ProgressChanged").
+ With(
+ new Matcher[] {
+ new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
+ new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.25f))
+ }
+ );
- test1.ChangeProgress(0.5f);
+ test1.ChangeProgress(0.5f);
+ }
+ }
this.mockery.VerifyAllExpectationsHaveBeenMet();
}
///
- /// Validates that the tracker only removes waitables when the whole
+ /// Validates that the tracker only removes transactions when the whole
/// tracking list has reached the 'ended' state.
///
///
- /// If the tracker would remove ended waitables right when they finished,
+ /// If the tracker would remove ended transactions right when they finished,
/// the total progress would jump back each time. This is unwanted, of course.
///
[Test]
public void TestDelayedRemoval() {
- ProgressTracker tracker = new ProgressTracker();
+ using(ProgressTracker tracker = new ProgressTracker()) {
+ IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
- IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
+ TestTransaction test1 = new TestTransaction();
+ TestTransaction test2 = new TestTransaction();
- Expect.Once.On(mockedSubscriber).
- Method("IdleStateChanged").
- WithAnyArguments();
+ // Step 1
+ {
+ Expect.Once.On(mockedSubscriber).
+ Method("IdleStateChanged").
+ WithAnyArguments();
- Expect.Exactly(2).On(mockedSubscriber).
- Method("ProgressChanged").
- With(
- new Matcher[] {
- new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
- new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.0f))
- }
- );
+ // This is optional. The tracker's progress is currently 0, so there's no need
+ // to send out superfluous progress reports.
+ Expect.Between(0, 2).On(mockedSubscriber).
+ Method("ProgressChanged").
+ With(
+ new Matcher[] {
+ new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
+ new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.0f))
+ }
+ );
- TestWaitable test1 = new TestWaitable();
- tracker.Track(test1);
- TestWaitable test2 = new TestWaitable();
- tracker.Track(test2);
+ tracker.Track(test1);
+ tracker.Track(test2);
+ }
- Expect.Once.On(mockedSubscriber).
- Method("ProgressChanged").
- With(
- new Matcher[] {
- new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
- new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.25f))
- }
- );
+ // Step 2
+ {
+ Expect.Once.On(mockedSubscriber).
+ Method("ProgressChanged").
+ With(
+ new Matcher[] {
+ new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
+ new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.25f))
+ }
+ );
- test1.ChangeProgress(0.5f);
+ // Total progress should be 0.25 after this call (two transactions, one with
+ // 0% progress and one with 50% progress)
+ test1.ChangeProgress(0.5f);
+ }
- Expect.Once.On(mockedSubscriber).
- Method("ProgressChanged").
- With(
- new Matcher[] {
- new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
- new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.75f))
- }
- );
+ // Step 3
+ {
+ Expect.Once.On(mockedSubscriber).
+ Method("ProgressChanged").
+ With(
+ new Matcher[] {
+ new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
+ new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.75f))
+ }
+ );
- // Total progress should be 0.75 after this call (one waitable at 1.0,
- // the other one at 0.5). If the second waitable would be removed,
- // the progress would jump to 0.5 instead.
- test2.End();
+ // Total progress should be 0.75 after this call (one transaction at 100%,
+ // the other one at 50%). If the second transaction would be removed by the tracker,
+ // (which would be inappropriate!) the progress would falsely jump to 0.5 instead.
+ test2.End();
+ }
- Expect.Once.On(mockedSubscriber).
- Method("ProgressChanged").
- With(
- new Matcher[] {
- new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
- new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(1.0f))
- }
- );
+ // Step 4
+ {
+ Expect.Once.On(mockedSubscriber).
+ Method("ProgressChanged").
+ With(
+ new Matcher[] {
+ new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
+ new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(1.0f))
+ }
+ );
- Expect.Once.On(mockedSubscriber).
- Method("IdleStateChanged").
- WithAnyArguments();
+ Expect.Once.On(mockedSubscriber).
+ Method("IdleStateChanged").
+ WithAnyArguments();
- test1.End();
+ test1.End();
+ }
+ }
this.mockery.VerifyAllExpectationsHaveBeenMet();
}
///
- /// Validates that the tracker behaves correctly if it is fed with waitables
+ /// Validates that the tracker behaves correctly if it is fed with transactions
/// that have already ended.
///
[Test]
- public void TestSoleEndedWaitable() {
- ProgressTracker tracker = new ProgressTracker();
+ public void TestSoleEndedTransaction() {
+ using(ProgressTracker tracker = new ProgressTracker()) {
+ IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
- IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
-
- tracker.Track(Waitable.EndedDummy);
+ tracker.Track(Transaction.EndedDummy);
+ }
this.mockery.VerifyAllExpectationsHaveBeenMet();
}
///
- /// Validates that the tracker behaves correctly if it is fed with waitables
- /// that have already ended in addition to waitables that are actively executing.
+ /// Validates that the tracker behaves correctly if it is fed with transactions
+ /// that have already ended in addition to transactions that are actively executing.
///
[Test]
- public void TestEndedWaitable() {
- ProgressTracker tracker = new ProgressTracker();
+ public void TestEndedTransaction() {
+ using(ProgressTracker tracker = new ProgressTracker()) {
+ IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
- IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
+ TestTransaction test1 = new TestTransaction();
- Expect.Once.On(mockedSubscriber).
- Method("IdleStateChanged").
- WithAnyArguments();
+ // Step 1
+ {
+ Expect.Once.On(mockedSubscriber).
+ Method("IdleStateChanged").
+ WithAnyArguments();
- Expect.Once.On(mockedSubscriber).
- Method("ProgressChanged").
- With(
- new Matcher[] {
- new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
- new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.0f))
- }
- );
+ Expect.Between(0, 1).On(mockedSubscriber).
+ Method("ProgressChanged").
+ With(
+ new Matcher[] {
+ new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
+ new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.0f))
+ }
+ );
- TestWaitable test1 = new TestWaitable();
- tracker.Track(test1);
+ tracker.Track(test1);
+ }
- Expect.Once.On(mockedSubscriber).
- Method("ProgressChanged").
- With(
- new Matcher[] {
- new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
- new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.5f))
- }
- );
+ // Step 2
+ {
+ Expect.Once.On(mockedSubscriber).
+ Method("ProgressChanged").
+ With(
+ new Matcher[] {
+ new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
+ new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.5f))
+ }
+ );
- tracker.Track(Waitable.EndedDummy);
+ tracker.Track(Transaction.EndedDummy);
+ }
- Expect.Once.On(mockedSubscriber).
- Method("ProgressChanged").
- With(
- new Matcher[] {
- new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
- new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(1.0f))
- }
- );
+ // Step 3
+ {
+ Expect.Once.On(mockedSubscriber).
+ Method("ProgressChanged").
+ With(
+ new Matcher[] {
+ new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
+ new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(1.0f))
+ }
+ );
- Expect.Once.On(mockedSubscriber).
- Method("IdleStateChanged").
- WithAnyArguments();
+ Expect.Once.On(mockedSubscriber).
+ Method("IdleStateChanged").
+ WithAnyArguments();
- test1.End();
+ test1.End();
+ }
+ }
this.mockery.VerifyAllExpectationsHaveBeenMet();
}
///
- /// Tries to provoke a deadlock by re-entering the tracker from one of
- /// its own events.
+ /// Tries to provoke a deadlock by re-entering the tracker from one of its own events
///
[Test]
public void TestProvokedDeadlock() {
- ProgressTracker tracker = new ProgressTracker();
+ using(ProgressTracker tracker = new ProgressTracker()) {
+ TestTransaction test1 = new TestTransaction();
+ tracker.Track(test1);
- TestWaitable test1 = new TestWaitable();
- tracker.Track(test1);
+ tracker.AsyncIdleStateChanged +=
+ (EventHandler)delegate(object sender, IdleStateEventArgs arguments) {
+ tracker.Track(Transaction.EndedDummy);
+ };
- tracker.AsyncIdleStateChanged +=
- (EventHandler)delegate(object sender, IdleStateEventArgs arguments) {
- tracker.Track(Waitable.EndedDummy);
- };
-
- test1.End();
+ test1.End();
+ }
}
/// Mocks a subscriber for the events of a tracker
diff --git a/Source/Tracking/ProgressTracker.cs b/Source/Tracking/ProgressTracker.cs
index b28a917..5e8a025 100644
--- a/Source/Tracking/ProgressTracker.cs
+++ b/Source/Tracking/ProgressTracker.cs
@@ -25,208 +25,195 @@ using System.Threading;
namespace Nuclex.Support.Tracking {
///
- /// Helps tracking the progress of one or more waitable background processes
+ /// Helps tracking the progress of one or more background transactions
///
///
///
/// This is useful if you want to display a progress bar for multiple
- /// Waitables but can not guarantee that no additional Waitables
+ /// transactions but can not guarantee that no additional transactions
/// will appear inmidst of execution.
///
///
- /// This class does not implement the interface itself
- /// in order to not violate the design principles of Waitables which
- /// guarantee that a will only finish once (whereas the
+ /// This class does not implement the interface itself
+ /// in order to not violate the design principles of transactions which
+ /// guarantee that a will only finish once (whereas the
/// progress tracker might 'finish' any number of times).
///
///
public class ProgressTracker : IDisposable, IProgressReporter {
- #region class WaitableMatcher
+ #region class TransactionMatcher
- /// Matches a direct Waitable to a fully wrapped one
- private class WaitableMatcher {
+ /// Matches a direct transaction to a fully wrapped one
+ private class TransactionMatcher {
///
- /// Initializes a new Waitable matcher that matches against
- /// the specified Waitable
+ /// Initializes a new transaction matcher that matches against
+ /// the specified transaction
///
- /// Waitable to match against
- public WaitableMatcher(Waitable toMatch) {
+ /// Transaction to match against
+ public TransactionMatcher(Transaction toMatch) {
this.toMatch = toMatch;
}
///
- /// Checks whether the provided Waitable matches the comparison
- /// Waitable of the instance
+ /// Checks whether the provided transaction matches the comparison
+ /// transaction of the instance
///
- /// Waitable to match to the comparison Waitable
- public bool Matches(ObservedWeightedWaitable other) {
- return ReferenceEquals(other.WeightedWaitable.Waitable, this.toMatch);
+ /// Transaction to match to the comparison transaction
+ public bool Matches(ObservedWeightedTransaction other) {
+ return ReferenceEquals(other.WeightedTransaction.Transaction, this.toMatch);
}
- /// Waitable this instance compares against
- private Waitable toMatch;
+ /// Transaction this instance compares against
+ private Transaction toMatch;
}
- #endregion // class WaitableMatcher
+ #endregion // class TransactionMatcher
/// Triggered when the idle state of the tracker changes
///
- /// The tracker is idle when no Waitables are being tracked in it. If you're
+ /// The tracker is idle when no transactions are being tracked in it. If you're
/// using this class to feed a progress bar, this would be the event to use for
/// showing or hiding the progress bar. The tracker starts off as idle because,
- /// upon construction, its list of Waitables will be empty.
+ /// upon construction, its list of transactions will be empty.
///
public event EventHandler AsyncIdleStateChanged;
/// Triggered when the total progress has changed
public event EventHandler AsyncProgressChanged;
- /// Initializes a new Waitable tracker
+ /// Initializes a new transaction tracker
public ProgressTracker() {
- this.trackedWaitables = new List>();
+ this.trackedTransactions = new List>();
this.idle = true;
this.asyncEndedDelegate =
- new ObservedWeightedWaitable.ReportDelegate(asyncEnded);
+ new ObservedWeightedTransaction.ReportDelegate(asyncEnded);
this.asyncProgressUpdatedDelegate =
- new ObservedWeightedWaitable.ReportDelegate(asyncProgressChanged);
+ new ObservedWeightedTransaction.ReportDelegate(asyncProgressChanged);
}
/// Immediately releases all resources owned by the instance
public void Dispose() {
- lock(this.trackedWaitables) {
+ lock(this.trackedTransactions) {
- // Get rid of all Waitables we're tracking. This unsubscribes the
- // observers from the events of the Waitables and stops us from
+ // Get rid of all transactions we're tracking. This unsubscribes the
+ // observers from the events of the transactions and stops us from
// being kept alive and receiving any further events if some of the
- // tracked Waitables are still executing.
- for(int index = 0; index < this.trackedWaitables.Count; ++index)
- this.trackedWaitables[index].Dispose();
+ // tracked transactions are still executing.
+ for(int index = 0; index < this.trackedTransactions.Count; ++index)
+ this.trackedTransactions[index].Dispose();
// Help the GC a bit by untangling the references :)
- this.trackedWaitables.Clear();
- this.trackedWaitables = null;
+ this.trackedTransactions.Clear();
+ this.trackedTransactions = null;
} // lock
}
- /// Begins tracking the specified waitable background process
- /// Waitable background process to be tracked
- public void Track(Waitable waitable) {
- Track(waitable, 1.0f);
+ /// Begins tracking the specified background transactions
+ /// Background transaction to be tracked
+ public void Track(Transaction transaction) {
+ Track(transaction, 1.0f);
}
- /// Begins tracking the specified waitable background process
- /// Waitable background process to be tracked
- /// Weight to assign to this waitable background process
- public void Track(Waitable waitable, float weight) {
+ /// Begins tracking the specified background transaction
+ /// Background transaction to be tracked
+ /// Weight to assign to this background transaction
+ public void Track(Transaction transaction, float weight) {
- // Add the new Waitable into the tracking list. This has to be done
+ // Add the new transaction into the tracking list. This has to be done
// inside a lock to prevent issues with the progressUpdate callback, which could
// access the totalWeight field before it has been updated to reflect the
- // new Waitable added to the collection.
- lock(this.trackedWaitables) {
+ // new transaction added to the collection.
+ lock(this.trackedTransactions) {
- bool wasEmpty = (this.trackedWaitables.Count == 0);
+ bool wasEmpty = (this.trackedTransactions.Count == 0);
+
+ if(transaction.Ended) {
+
+ // If the ended transaction would become the only transaction in the list,
+ // there's no sense in doing anything at all because it would have to be
+ // thrown right out again. Only add the transaction when there are other
+ // running transactions to properly sum total progress for consistency.
+ if(!wasEmpty) {
+
+ // Construct a new observation wrapper. This is done inside the lock
+ // because as soon as we are subscribed to the events, we can potentially
+ // receive them. The lock eliminates the risk of processing a progress update
+ // before the transaction has been added to the tracked transactions list.
+ this.trackedTransactions.Add(
+ new ObservedWeightedTransaction(
+ new WeightedTransaction(transaction, weight),
+ this.asyncProgressUpdatedDelegate,
+ this.asyncEndedDelegate
+ )
+ );
+
+ }
+
+ } else { // Not ended -- Transation is still running
+
+ // Construct a new transation observer and add the transaction to our
+ // list of tracked transactions.
+ ObservedWeightedTransaction observedTransaction =
+ new ObservedWeightedTransaction(
+ new WeightedTransaction(transaction, weight),
+ this.asyncProgressUpdatedDelegate,
+ this.asyncEndedDelegate
+ );
+
+ this.trackedTransactions.Add(observedTransaction);
+
+ // If this is the first transaction to be added to the list, tell our
+ // owner that we're idle no longer!
+ if(wasEmpty)
+ setIdle(false);
+
+ } // if transaction ended
// This can be done after we registered the wrapper to our delegates because
// any incoming progress updates will be stopped from the danger of a
// division-by-zero from the potentially still zeroed totalWeight by the lock.
this.totalWeight += weight;
- if(waitable.Ended) {
-
- // If the ended Waitable would become the only Waitable in the list,
- // there's no sense in doing anything at all because it would have to be
- // thrown right out again. Only add the Waitable when there are other
- // running Waitables to properly sum total progress for consistency.
- if(!wasEmpty) {
-
- // Construct a new observation wrapper. This is done inside the lock
- // because as soon as we are subscribed to the events, we can potentially
- // receive them. The lock eliminates the risk of processing a progress update
- // before the Waitable has been added to the tracked Waitables list.
- this.trackedWaitables.Add(
- new ObservedWeightedWaitable(
- new WeightedWaitable(waitable, weight),
- this.asyncProgressUpdatedDelegate,
- this.asyncEndedDelegate
- )
- );
-
- // All done, the total progress is different now, so force a recalculation and
- // send out the AsyncProgressUpdated event.
- recalculateProgress();
-
- }
-
- } else { // Not ended -- Waitable is still running
-
- // Construct a new Waitable observer and add the Waitable to our
- // list of tracked Waitables.
- ObservedWeightedWaitable observedWaitable =
- new ObservedWeightedWaitable(
- new WeightedWaitable(waitable, weight),
- this.asyncProgressUpdatedDelegate,
- this.asyncEndedDelegate
- );
-
- this.trackedWaitables.Add(observedWaitable);
-
- // If this is the first Waitable to be added to the list, tell our
- // owner that we're idle no longer!
- if(wasEmpty)
- setIdle(false);
-
- // All done, the total progress is different now, so force a recalculation and
- // send out the AsyncProgressUpdated event.
- recalculateProgress();
-
- // The Waitable might have ended before we had registered to its AsyncEnded
- // event, so we have to do this to be on the safe side. This might cause
- // asyncEnded() to be called twice, but that's not a problem in this
- // implementation and improves performance and simplicity for the normal path.
- if(waitable.Ended) {
- asyncEnded();
- observedWaitable.Dispose();
- }
-
- } // if Waitable ended
+ // All done, the total progress is different now, so force a recalculation and
+ // send out the AsyncProgressUpdated event.
+ recalculateProgress();
} // lock
}
- /// Stops tracking the specified waitable background process
- /// Waitable background process to stop tracking of
- public void Untrack(Waitable waitable) {
- lock(this.trackedWaitables) {
+ /// Stops tracking the specified background transaction
+ /// Background transaction to stop tracking of
+ public void Untrack(Transaction transaction) {
+ lock(this.trackedTransactions) {
// Locate the object to be untracked in our collection
- int removeIndex = this.trackedWaitables.FindIndex(
- new Predicate>(
- new WaitableMatcher(waitable).Matches
+ int removeIndex = this.trackedTransactions.FindIndex(
+ new Predicate>(
+ new TransactionMatcher(transaction).Matches
)
);
if(removeIndex == -1)
throw new InvalidOperationException("Item is not being tracked");
- // Remove and dispose the Waitable the user wants to untrack
+ // Remove and dispose the transaction the user wants to untrack
{
- ObservedWeightedWaitable wrappedWaitable =
- this.trackedWaitables[removeIndex];
+ ObservedWeightedTransaction wrappedTransaction =
+ this.trackedTransactions[removeIndex];
- this.trackedWaitables.RemoveAt(removeIndex);
- wrappedWaitable.Dispose();
+ this.trackedTransactions.RemoveAt(removeIndex);
+ wrappedTransaction.Dispose();
}
// If the list is empty, then we're back in the idle state
- if(this.trackedWaitables.Count == 0) {
+ if(this.trackedTransactions.Count == 0) {
this.totalWeight = 0.0f;
@@ -235,12 +222,12 @@ namespace Nuclex.Support.Tracking {
} else {
- // Rebuild the total weight from scratch. Subtracting the removed Waitable's
+ // Rebuild the total weight from scratch. Subtracting the removed transaction's
// weight would work, too, but we might accumulate rounding errors making the sum
// drift slowly away from the actual value.
this.totalWeight = 0.0f;
- for(int index = 0; index < this.trackedWaitables.Count; ++index)
- this.totalWeight += this.trackedWaitables[index].WeightedWaitable.Weight;
+ for(int index = 0; index < this.trackedTransactions.Count; ++index)
+ this.totalWeight += this.trackedTransactions[index].WeightedTransaction.Weight;
}
@@ -252,7 +239,7 @@ namespace Nuclex.Support.Tracking {
get { return this.idle; }
}
- /// Current summed progress of the tracked Waitables
+ /// Current summed progress of the tracked transactions
public float Progress {
get { return this.progress; }
}
@@ -278,59 +265,61 @@ namespace Nuclex.Support.Tracking {
float totalProgress = 0.0f;
// Lock the collection to avoid trouble when someone tries to remove one
- // of our tracked Waitables while we're just doing a progress update
- lock(this.trackedWaitables) {
+ // of our tracked transactions while we're just doing a progress update
+ lock(this.trackedTransactions) {
- // This is a safety measure. In theory, even after all Waitables have
- // ended and the collection of tracked Waitables is cleared, a waiting
+ // This is a safety measure. In theory, even after all transactions have
+ // ended and the collection of tracked transactions is cleared, a waiting
// thread might deliver another progress update causing this method to
// be entered. In this case, the right thing is to do nothing at all.
if(this.totalWeight == 0.0f)
return;
// Sum up the total progress
- for(int index = 0; index < this.trackedWaitables.Count; ++index) {
- float weight = this.trackedWaitables[index].WeightedWaitable.Weight;
- totalProgress += this.trackedWaitables[index].Progress * weight;
+ for(int index = 0; index < this.trackedTransactions.Count; ++index) {
+ float weight = this.trackedTransactions[index].WeightedTransaction.Weight;
+ totalProgress += this.trackedTransactions[index].Progress * weight;
}
// This also needs to be in the lock to guarantee that the totalWeight is
- // the one for the number of Waitables we just summed -- by design,
+ // the one for the number of transactions we just summed -- by design,
// the total weight always has to be updated at the same time as the collection.
totalProgress /= this.totalWeight;
- // Finally, trigger the event
- this.progress = totalProgress;
- OnAsyncProgressUpdated(totalProgress);
+ // Finally, trigger the event if the progress has changed
+ if(totalProgress != this.progress) {
+ this.progress = totalProgress;
+ OnAsyncProgressUpdated(totalProgress);
+ }
} // lock
}
- /// Called when one of the tracked Waitables has ended
+ /// Called when one of the tracked transactions has ended
private void asyncEnded() {
- lock(this.trackedWaitables) {
+ lock(this.trackedTransactions) {
- // If any Waitables in the list are still going, keep the entire list.
+ // If any transactions in the list are still going, keep the entire list.
// This behavior is intentional in order to prevent the tracker's progress from
- // jumping back repeatedly when multiple tracked Waitables come to an end.
- for(int index = 0; index < this.trackedWaitables.Count; ++index)
- if(!this.trackedWaitables[index].WeightedWaitable.Waitable.Ended)
+ // jumping back repeatedly when multiple tracked transactions come to an end.
+ for(int index = 0; index < this.trackedTransactions.Count; ++index)
+ if(!this.trackedTransactions[index].WeightedTransaction.Transaction.Ended)
return;
- // All Waitables have finished, get rid of the wrappers and make a
- // fresh start for future Waitables to be tracked. No need to call
- // Dispose() since, as a matter of fact, when the Waitable
- this.trackedWaitables.Clear();
+ // All transactions have finished, get rid of the wrappers and make a
+ // fresh start for future transactions to be tracked. No need to call
+ // Dispose() since, as a matter of fact, when the transaction
+ this.trackedTransactions.Clear();
this.totalWeight = 0.0f;
// Notify our owner that we're idle now. This line is only reached when all
- // Waitables were finished, so it's safe to trigger this here.
+ // transactions were finished, so it's safe to trigger this here.
setIdle(true);
} // lock
}
- /// Called when one of the tracked Waitables has achieved progress
+ /// Called when one of the tracked transactions has achieved progress
private void asyncProgressChanged() {
recalculateProgress();
}
@@ -338,7 +327,7 @@ namespace Nuclex.Support.Tracking {
/// Changes the idle state
/// Whether or not the tracker is currently idle
///
- /// This method expects to be called during a lock() on trackedWaitables!
+ /// This method expects to be called during a lock() on trackedTransactions!
///
private void setIdle(bool idle) {
this.idle = idle;
@@ -348,16 +337,16 @@ namespace Nuclex.Support.Tracking {
/// Whether the tracker is currently idle
private volatile bool idle;
- /// Current summed progress of the tracked Waitables
+ /// Current summed progress of the tracked transactions
private volatile float progress;
- /// Total weight of all Waitables being tracked
+ /// Total weight of all transactions being tracked
private volatile float totalWeight;
- /// Waitables being tracked by this tracker
- private List> trackedWaitables;
+ /// Transactions being tracked by this tracker
+ private List> trackedTransactions;
/// Delegate for the asyncEnded() method
- private ObservedWeightedWaitable.ReportDelegate asyncEndedDelegate;
+ private ObservedWeightedTransaction.ReportDelegate asyncEndedDelegate;
/// Delegate for the asyncProgressUpdated() method
- private ObservedWeightedWaitable.ReportDelegate asyncProgressUpdatedDelegate;
+ private ObservedWeightedTransaction.ReportDelegate asyncProgressUpdatedDelegate;
}
diff --git a/Source/Tracking/Request.cs b/Source/Tracking/Request.cs
index d3380eb..c7be01e 100644
--- a/Source/Tracking/Request.cs
+++ b/Source/Tracking/Request.cs
@@ -33,11 +33,11 @@ namespace Nuclex.Support.Tracking {
/// store a new exception) and re-throw them when in ReraiseExceptions()
///
///
- /// Like in the Waitable class, the contract requires you to always call
+ /// Like in the transaction class, the contract requires you to always call
/// OnAsyncEnded(), no matter what the outcome of your operation is.
///
///
- public abstract class Request : Waitable {
+ public abstract class Request : Transaction {
#region class EndedDummyRequest
diff --git a/Source/Tracking/StatusReportEventArgs.Test.cs b/Source/Tracking/StatusReportEventArgs.Test.cs
new file mode 100644
index 0000000..57b55a6
--- /dev/null
+++ b/Source/Tracking/StatusReportEventArgs.Test.cs
@@ -0,0 +1,60 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2008 Nuclex Development Labs
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the IBM Common Public License as
+published by the IBM Corporation; either version 1.0 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+IBM Common Public License for more details.
+
+You should have received a copy of the IBM Common Public
+License along with this library
+*/
+#endregion
+
+using System;
+using System.IO;
+
+#if UNITTEST
+
+using NUnit.Framework;
+using NUnit.Framework.SyntaxHelpers;
+
+namespace Nuclex.Support.Tracking {
+
+ /// Unit Test for the status report event argument container
+ [TestFixture]
+ public class StatusReportEventArgsTest {
+
+ ///
+ /// Tests whether the status report event arguments correctly reports an empty status
+ ///
+ [Test]
+ public void TestEmptyStatus() {
+ StatusReportEventArgs emptyStatus = new StatusReportEventArgs(string.Empty);
+
+ Assert.AreEqual(string.Empty, emptyStatus.Status);
+ }
+
+ ///
+ /// Tests whether the status report event arguments correctly reports simple
+ /// status indications
+ ///
+ [Test]
+ public void TestSimpleStatus() {
+ StatusReportEventArgs emptyStatus = new StatusReportEventArgs("hello world");
+
+ Assert.AreEqual("hello world", emptyStatus.Status);
+ }
+
+ }
+
+} // namespace Nuclex.Support.Tracking
+
+#endif // UNITTEST
diff --git a/Source/Tracking/Transaction.Test.cs b/Source/Tracking/Transaction.Test.cs
new file mode 100644
index 0000000..35e7515
--- /dev/null
+++ b/Source/Tracking/Transaction.Test.cs
@@ -0,0 +1,189 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2008 Nuclex Development Labs
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the IBM Common Public License as
+published by the IBM Corporation; either version 1.0 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+IBM Common Public License for more details.
+
+You should have received a copy of the IBM Common Public
+License along with this library
+*/
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+
+#if UNITTEST
+
+using NUnit.Framework;
+using NMock2;
+
+namespace Nuclex.Support.Tracking {
+
+ /// Unit Test for the transaction class
+ [TestFixture]
+ public class TransactionTest {
+
+ #region interface ITransactionSubscriber
+
+ /// Interface used to test the transaction
+ public interface ITransactionSubscriber {
+
+ /// Called when the set transaction has ended
+ /// Transaction group that as ended
+ /// Not used
+ void Ended(object sender, EventArgs arguments);
+
+ }
+
+ #endregion // interface ITransactionGroupSubscriber
+
+ #region class TestTransaction
+
+ /// Transaction used for testing in this unit test
+ private class TestTransaction : Transaction {
+
+ /// Transitions the transaction into the ended state
+ public void End() {
+ OnAsyncEnded();
+ }
+
+ }
+
+ #endregion // class TestWiatable
+
+ /// Initialization routine executed before each test is run
+ [SetUp]
+ public void Setup() {
+ this.mockery = new Mockery();
+ }
+
+ ///
+ /// Verifies that the transaction throws an exception when it is ended multiple times
+ ///
+ [Test, ExpectedException(typeof(InvalidOperationException))]
+ public void TestThrowOnRepeatedlyEndedTransaction() {
+ TestTransaction test = new TestTransaction();
+ test.End();
+ test.End();
+ }
+
+ ///
+ /// Tests whether the Ended event of the transaction is correctly delivered if
+ /// the transaction ends after the subscription already took place
+ ///
+ [Test]
+ public void TestEndedEventAfterSubscription() {
+ TestTransaction test = new TestTransaction();
+
+ ITransactionSubscriber mockedSubscriber = mockSubscriber(test);
+ Expect.Once.On(mockedSubscriber).
+ Method("Ended").
+ WithAnyArguments();
+
+ test.End();
+
+ this.mockery.VerifyAllExpectationsHaveBeenMet();
+ }
+
+ ///
+ /// Tests whether the Ended event of the transaction is correctly delivered if
+ /// the transaction is already done when the subscription takes place
+ ///
+ [Test]
+ public void TestEndedEventDuingSubscription() {
+ TestTransaction test = new TestTransaction();
+ test.End();
+
+ ITransactionSubscriber mockedSubscriber =
+ this.mockery.NewMock();
+
+ Expect.Once.On(mockedSubscriber).
+ Method("Ended").
+ WithAnyArguments();
+
+ test.AsyncEnded += new EventHandler(mockedSubscriber.Ended);
+
+ this.mockery.VerifyAllExpectationsHaveBeenMet();
+ }
+
+ ///
+ /// Verifies that the Wait() method of the transaction works as expected
+ ///
+ [Test]
+ public void TestWaitUnlimited() {
+ TestTransaction test = new TestTransaction();
+
+ // We can only do a positive test here without slowing down the unit test
+ test.End();
+ test.Wait();
+ }
+
+ ///
+ /// Verifies that the Wait() method of the transaction works as expected using
+ /// a millisecond count as its argument
+ ///
+ [Test]
+ public void TestWaitMilliseconds() {
+ TestTransaction test = new TestTransaction();
+
+ // Wait 0 milliseconds for the transaction to end. Of course, this will not happen,
+ // so a timeout occurs and false is returned
+ Assert.IsFalse(test.Wait(0));
+
+ test.End();
+
+ // Wait another 0 milliseconds for the transaction to end. Now it has already ended
+ // and no timeout will occur, even with a wait time of 0 milliseconds.
+ Assert.IsTrue(test.Wait(0));
+ }
+
+ ///
+ /// Verifies that the Wait() method of the transaction works as expected using
+ /// a TimeSpan as its argument
+ ///
+ [Test]
+ public void TestWaitTimeSpan() {
+ TestTransaction test = new TestTransaction();
+
+ // Wait 0 milliseconds for the transaction to end. Of course, this will not happen,
+ // so a timeout occurs and false is returned
+ Assert.IsFalse(test.Wait(TimeSpan.Zero));
+
+ test.End();
+
+ // Wait another 0 milliseconds for the transaction to end. Now it has already ended
+ // and no timeout will occur, even with a wait time of 0 milliseconds.
+ Assert.IsTrue(test.Wait(TimeSpan.Zero));
+ }
+
+ /// Mocks a subscriber for the events of a transaction
+ /// Transaction to mock an event subscriber for
+ /// The mocked event subscriber
+ private ITransactionSubscriber mockSubscriber(Transaction transaction) {
+ ITransactionSubscriber mockedSubscriber =
+ this.mockery.NewMock();
+
+ transaction.AsyncEnded += new EventHandler(mockedSubscriber.Ended);
+
+ return mockedSubscriber;
+ }
+
+ /// Mock object factory
+ private Mockery mockery;
+
+ }
+
+} // namespace Nuclex.Support.Tracking
+
+#endif // UNITTEST
diff --git a/Source/Tracking/Waitable.cs b/Source/Tracking/Transaction.cs
similarity index 83%
rename from Source/Tracking/Waitable.cs
rename to Source/Tracking/Transaction.cs
index a981523..f5d5992 100644
--- a/Source/Tracking/Waitable.cs
+++ b/Source/Tracking/Transaction.cs
@@ -28,46 +28,46 @@ namespace Nuclex.Support.Tracking {
///
///
/// By encapsulating long-running operations which will ideally be running in
- /// a background thread in a class that's derived from
+ /// a background thread in a class that's derived from
/// you can wait for the completion of the operation and optionally even receive
/// feedback on the achieved progress. This is useful for displaying a progress
/// bar, loading screen or some other means of entertaining the user while he
/// waits for the task to complete.
///
///
- /// You can register callbacks which will be fired once the
+ /// You can register callbacks which will be fired once the
/// task has completed. This class deliberately does not provide an Execute()
/// method or anything similar to clearly seperate the initiation of an operation
/// from just monitoring it. By omitting an Execute() method, it also becomes
- /// possible to construct a Waitable just-in-time when it is explicitely being
+ /// possible to construct a transaction just-in-time when it is explicitely being
/// asked for.
///
///
- public abstract class Waitable {
+ public abstract class Transaction {
- #region class EndedDummyWaitable
+ #region class EndedDummyTransaction
- /// Dummy waitable which always is in the 'ended' state
- private class EndedDummyWaitable : Waitable {
+ /// Dummy transaction which always is in the 'ended' state
+ private class EndedDummyTransaction : Transaction {
- /// Initializes a new ended dummy waitable
- public EndedDummyWaitable() {
+ /// Initializes a new ended dummy transaction
+ public EndedDummyTransaction() {
OnAsyncEnded();
}
}
- #endregion // class EndedDummyWaitable
+ #endregion // class EndedDummyTransaction
- /// A dummy waitable that's always in the 'ended' state
+ /// A dummy transaction that's always in the 'ended' state
///
/// Useful if an operation is already complete when it's being asked for or
- /// when a waitable that's lazily created is accessed after the original
+ /// when a transaction that's lazily created is accessed after the original
/// operation has ended already.
///
- public static readonly Waitable EndedDummy = new EndedDummyWaitable();
+ public static readonly Transaction EndedDummy = new EndedDummyTransaction();
- /// Will be triggered when the Waitable has ended
+ /// Will be triggered when the transaction has ended
///
/// If the process is already finished when a client registers to this event,
/// the registered callback will be invoked synchronously right when the
@@ -149,12 +149,12 @@ namespace Nuclex.Support.Tracking {
return WaitHandle.WaitOne(timeoutMilliseconds, false);
}
- /// Whether the Waitable has ended already
+ /// Whether the transaction has ended already
public virtual bool Ended {
get { return this.ended; }
}
- /// WaitHandle that can be used to wait for the Waitable to end
+ /// WaitHandle that can be used to wait for the transaction to end
public virtual WaitHandle WaitHandle {
get {
@@ -186,14 +186,14 @@ namespace Nuclex.Support.Tracking {
///
///
/// Calling this method is mandatory. Implementers need to take care that
- /// the OnAsyncEnded() method is called on any instance of Waitable that's
+ /// the OnAsyncEnded() method is called on any instance of transaction that's
/// being created. This method also must not be called more than once.
///
///
protected virtual void OnAsyncEnded() {
- // Make sure the waitable is not ended more than once. By guaranteeing that
- // a waitable can only be ended once, we allow users of this class to
+ // Make sure the transaction is not ended more than once. By guaranteeing that
+ // a transaction can only be ended once, we allow users of this class to
// skip some safeguards against notifications arriving twice.
lock(this) {
@@ -202,7 +202,7 @@ namespace Nuclex.Support.Tracking {
// to waste any effort optimizing the speed at which an implementation fault
// will be noticed.
if(this.ended)
- throw new InvalidOperationException("The Waitable has already been ended");
+ throw new InvalidOperationException("The transaction has already been ended");
this.ended = true;
@@ -236,7 +236,7 @@ namespace Nuclex.Support.Tracking {
protected volatile List endedEventSubscribers;
/// Whether the operation has completed yet
protected volatile bool ended;
- /// Event that will be set when the waitable is completed
+ /// Event that will be set when the transaction is completed
///
/// This event is will only be created when it is specifically asked for using
/// the WaitHandle property.
diff --git a/Source/Tracking/TransactionGroup.Test.cs b/Source/Tracking/TransactionGroup.Test.cs
new file mode 100644
index 0000000..3c548f7
--- /dev/null
+++ b/Source/Tracking/TransactionGroup.Test.cs
@@ -0,0 +1,263 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2008 Nuclex Development Labs
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the IBM Common Public License as
+published by the IBM Corporation; either version 1.0 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+IBM Common Public License for more details.
+
+You should have received a copy of the IBM Common Public
+License along with this library
+*/
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+#if UNITTEST
+
+using NUnit.Framework;
+using NMock2;
+
+namespace Nuclex.Support.Tracking {
+
+ /// Unit Test for the transaction group class
+ [TestFixture]
+ public class TransactionGroupTest {
+
+ #region interface ITransactionGroupSubscriber
+
+ /// Interface used to test the transaction group
+ public interface ITransactionGroupSubscriber {
+
+ /// Called when the transaction group's progress changes
+ /// Transaction group whose progress has changed
+ /// Contains the new progress achieved
+ void ProgressChanged(object sender, ProgressReportEventArgs arguments);
+
+ /// Called when the transaction group has ended
+ /// Transaction group that as ended
+ /// Not used
+ void Ended(object sender, EventArgs arguments);
+
+ }
+
+ #endregion // interface ITransactionGroupSubscriber
+
+ #region class ProgressUpdateEventArgsMatcher
+
+ /// Compares two ProgressUpdateEventArgsInstances for NMock validation
+ private class ProgressUpdateEventArgsMatcher : Matcher {
+
+ /// Initializes a new ProgressUpdateEventArgsMatcher
+ /// Expected progress update event arguments
+ public ProgressUpdateEventArgsMatcher(ProgressReportEventArgs expected) {
+ this.expected = expected;
+ }
+
+ ///
+ /// Called by NMock to verfiy the ProgressUpdateEventArgs match the expected value
+ ///
+ /// Actual value to compare to the expected value
+ ///
+ /// True if the actual value matches the expected value; otherwise false
+ ///
+ public override bool Matches(object actualAsObject) {
+ ProgressReportEventArgs actual = (actualAsObject as ProgressReportEventArgs);
+ if(actual == null)
+ return false;
+
+ return (actual.Progress == this.expected.Progress);
+ }
+
+ /// Creates a string representation of the expected value
+ /// Writer to write the string representation into
+ public override void DescribeTo(TextWriter writer) {
+ writer.Write(this.expected.Progress.ToString());
+ }
+
+ /// Expected progress update event args value
+ private ProgressReportEventArgs expected;
+
+ }
+
+ #endregion // class ProgressUpdateEventArgsMatcher
+
+ #region class TestTransaction
+
+ /// Transaction used for testing in this unit test
+ private class TestTransaction : Transaction, IProgressReporter {
+
+ /// will be triggered to report when progress has been achieved
+ public event EventHandler AsyncProgressChanged;
+
+ /// Changes the testing transaction's indicated progress
+ ///
+ /// New progress to be reported by the testing transaction
+ ///
+ public void ChangeProgress(float progress) {
+ OnAsyncProgressChanged(progress);
+ }
+
+ /// Transitions the transaction into the ended state
+ public void End() {
+ OnAsyncEnded();
+ }
+
+ /// Fires the progress update event
+ /// Progress to report (ranging from 0.0 to 1.0)
+ ///
+ /// Informs the observers of this transaction about the achieved progress.
+ ///
+ protected virtual void OnAsyncProgressChanged(float progress) {
+ OnAsyncProgressChanged(new ProgressReportEventArgs(progress));
+ }
+
+ /// Fires the progress update event
+ /// Progress to report (ranging from 0.0 to 1.0)
+ ///
+ /// Informs the observers of this transaction about the achieved progress.
+ /// Allows for classes derived from the transaction class to easily provide
+ /// a custom event arguments class that has been derived from the
+ /// transaction's ProgressUpdateEventArgs class.
+ ///
+ protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) {
+ EventHandler copy = AsyncProgressChanged;
+ if(copy != null)
+ copy(this, eventArguments);
+ }
+
+ }
+
+ #endregion // class TestTransaction
+
+ /// Initialization routine executed before each test is run
+ [SetUp]
+ public void Setup() {
+ this.mockery = new Mockery();
+ }
+
+ /// Validates that the set transaction properly sums the progress
+ [Test]
+ public void TestSummedProgress() {
+ using(
+ TransactionGroup testTransactionGroup =
+ new TransactionGroup(
+ new TestTransaction[] { new TestTransaction(), new TestTransaction() }
+ )
+ ) {
+ ITransactionGroupSubscriber mockedSubscriber = mockSubscriber(testTransactionGroup);
+
+ Expect.Once.On(mockedSubscriber).
+ Method("ProgressChanged").
+ With(
+ new Matcher[] {
+ new NMock2.Matchers.TypeMatcher(typeof(TransactionGroup)),
+ new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.25f))
+ }
+ );
+
+ testTransactionGroup.Children[0].Transaction.ChangeProgress(0.5f);
+
+ this.mockery.VerifyAllExpectationsHaveBeenMet();
+ }
+ }
+
+ /// Validates that the transaction group respects the weights
+ [Test]
+ public void TestWeightedSummedProgress() {
+ using(
+ TransactionGroup testTransactionGroup =
+ new TransactionGroup(
+ new WeightedTransaction[] {
+ new WeightedTransaction(new TestTransaction(), 1.0f),
+ new WeightedTransaction(new TestTransaction(), 2.0f)
+ }
+ )
+ ) {
+ ITransactionGroupSubscriber mockedSubscriber = mockSubscriber(testTransactionGroup);
+
+ Expect.Once.On(mockedSubscriber).
+ Method("ProgressChanged").
+ With(
+ new Matcher[] {
+ new NMock2.Matchers.TypeMatcher(typeof(TransactionGroup)),
+ new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.5f / 3.0f))
+ }
+ );
+
+ testTransactionGroup.Children[0].Transaction.ChangeProgress(0.5f);
+
+ Expect.Once.On(mockedSubscriber).
+ Method("ProgressChanged").
+ With(
+ new Matcher[] {
+ new NMock2.Matchers.TypeMatcher(typeof(TransactionGroup)),
+ new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.5f))
+ }
+ );
+
+ testTransactionGroup.Children[1].Transaction.ChangeProgress(0.5f);
+
+ this.mockery.VerifyAllExpectationsHaveBeenMet();
+ }
+ }
+
+ ///
+ /// Validates that the ended event is triggered when the last transaction ends
+ ///
+ [Test]
+ public void TestEndedEvent() {
+ using(
+ TransactionGroup testTransactionGroup =
+ new TransactionGroup(
+ new TestTransaction[] { new TestTransaction(), new TestTransaction() }
+ )
+ ) {
+ ITransactionGroupSubscriber mockedSubscriber = mockSubscriber(testTransactionGroup);
+
+ Expect.Exactly(2).On(mockedSubscriber).
+ Method("ProgressChanged").
+ WithAnyArguments();
+
+ Expect.Once.On(mockedSubscriber).
+ Method("Ended").
+ WithAnyArguments();
+
+ testTransactionGroup.Children[0].Transaction.End();
+ testTransactionGroup.Children[1].Transaction.End();
+
+ this.mockery.VerifyAllExpectationsHaveBeenMet();
+ }
+ }
+
+ /// Mocks a subscriber for the events of a transaction
+ /// Transaction to mock an event subscriber for
+ /// The mocked event subscriber
+ private ITransactionGroupSubscriber mockSubscriber(Transaction transaction) {
+ ITransactionGroupSubscriber mockedSubscriber =
+ this.mockery.NewMock();
+
+ transaction.AsyncEnded += new EventHandler(mockedSubscriber.Ended);
+ (transaction as IProgressReporter).AsyncProgressChanged +=
+ new EventHandler(mockedSubscriber.ProgressChanged);
+
+ return mockedSubscriber;
+ }
+
+ /// Mock object factory
+ private Mockery mockery;
+
+ }
+
+} // namespace Nuclex.Support.Tracking
+
+#endif // UNITTEST
diff --git a/Source/Tracking/TransactionGroup.cs b/Source/Tracking/TransactionGroup.cs
new file mode 100644
index 0000000..ab23a55
--- /dev/null
+++ b/Source/Tracking/TransactionGroup.cs
@@ -0,0 +1,210 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2008 Nuclex Development Labs
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the IBM Common Public License as
+published by the IBM Corporation; either version 1.0 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+IBM Common Public License for more details.
+
+You should have received a copy of the IBM Common Public
+License along with this library
+*/
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+using Nuclex.Support.Collections;
+
+namespace Nuclex.Support.Tracking {
+
+ /// Forms a single transaction from a group of transactions
+ /// Type of transactions to manage as a set
+ public class TransactionGroup : Transaction, IDisposable, IProgressReporter
+ where TransactionType : Transaction {
+
+ /// will be triggered to report when progress has been achieved
+ public event EventHandler AsyncProgressChanged;
+
+ /// Initializes a new transaction group
+ /// Transactions to track with this group
+ ///
+ /// Uses a default weighting factor of 1.0 for all transactions.
+ ///
+ public TransactionGroup(IEnumerable childs)
+ : this() {
+
+ // Construct a WeightedTransaction with the default weight for each
+ // transaction and wrap it in an ObservedTransaction
+ foreach(TransactionType transaction in childs) {
+ this.children.Add(
+ new ObservedWeightedTransaction(
+ new WeightedTransaction(transaction),
+ new ObservedWeightedTransaction.ReportDelegate(
+ asyncProgressUpdated
+ ),
+ new ObservedWeightedTransaction.ReportDelegate(
+ asyncChildEnded
+ )
+ )
+ );
+ }
+
+ // Since all transactions have a weight of 1.0, the total weight is
+ // equal to the number of transactions in our list
+ this.totalWeight = (float)this.children.Count;
+
+ }
+
+ /// Initializes a new transaction group
+ /// Transactions to track with this group
+ public TransactionGroup(
+ IEnumerable> childs
+ )
+ : this() {
+
+ // Construct an ObservedTransaction around each of the WeightedTransactions
+ foreach(WeightedTransaction transaction in childs) {
+ this.children.Add(
+ new ObservedWeightedTransaction(
+ transaction,
+ new ObservedWeightedTransaction.ReportDelegate(
+ asyncProgressUpdated
+ ),
+ new ObservedWeightedTransaction.ReportDelegate(
+ asyncChildEnded
+ )
+ )
+ );
+
+ // Sum up the total weight
+ this.totalWeight += transaction.Weight;
+ }
+
+ }
+
+ /// Performs common initialization for the public constructors
+ private TransactionGroup() {
+ this.children = new List>();
+ }
+
+ /// Immediately releases all resources owned by the object
+ public void Dispose() {
+
+ if(this.children != null) {
+
+ // Dispose all the observed transactions, disconnecting the events from the
+ // actual transactions so the GC can more easily collect this class
+ for(int index = 0; index < this.children.Count; ++index)
+ this.children[index].Dispose();
+
+ this.children = null;
+ this.wrapper = null;
+
+ }
+
+ }
+
+ /// Childs contained in the transaction set
+ public IList> Children {
+ get {
+
+ // The wrapper is constructed only when needed. Most of the time, users will
+ // just create a transaction group and monitor its progress without ever using
+ // the Childs collection.
+ if(this.wrapper == null) {
+
+ // This doesn't need a lock because it's a stateless wrapper.
+ // If it is constructed twice, then so be it, no problem at all.
+ this.wrapper = new WeightedTransactionWrapperCollection(
+ this.children
+ );
+
+ }
+
+ return this.wrapper;
+
+ }
+ }
+
+ /// Fires the progress update event
+ /// Progress to report (ranging from 0.0 to 1.0)
+ ///
+ /// Informs the observers of this transactions about the achieved progress.
+ ///
+ protected virtual void OnAsyncProgressChanged(float progress) {
+ OnAsyncProgressChanged(new ProgressReportEventArgs(progress));
+ }
+
+ /// Fires the progress update event
+ /// Progress to report (ranging from 0.0 to 1.0)
+ ///
+ /// Informs the observers of this transaction about the achieved progress.
+ /// Allows for classes derived from the transaction class to easily provide
+ /// a custom event arguments class that has been derived from the
+ /// transaction's ProgressUpdateEventArgs class.
+ ///
+ protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) {
+ EventHandler copy = AsyncProgressChanged;
+ if(copy != null)
+ copy(this, eventArguments);
+ }
+
+ ///
+ /// Called when the progress of one of the observed transactions changes
+ ///
+ private void asyncProgressUpdated() {
+ float totalProgress = 0.0f;
+
+ // Calculate the sum of the progress reported by our child transactions,
+ // scaled to the weight each transaction has assigned to it.
+ for(int index = 0; index < this.children.Count; ++index) {
+ totalProgress +=
+ this.children[index].Progress * this.children[index].WeightedTransaction.Weight;
+ }
+
+ // Calculate the actual combined progress
+ if(this.totalWeight > 0.0f)
+ totalProgress /= this.totalWeight;
+
+ // Send out the progress update
+ OnAsyncProgressChanged(totalProgress);
+ }
+
+ ///
+ /// Called when an observed transaction ends
+ ///
+ private void asyncChildEnded() {
+
+ // If there's still at least one transaction going, don't report that
+ // the transaction group has finished yet.
+ for(int index = 0; index < this.children.Count; ++index)
+ if(!this.children[index].WeightedTransaction.Transaction.Ended)
+ return;
+
+ // All child transactions have ended, so the set has now ended as well
+ OnAsyncEnded();
+
+ }
+
+ /// Transactions being managed in the set
+ private List> children;
+ ///
+ /// Wrapper collection for exposing the child transactions under the
+ /// WeightedTransaction interface
+ ///
+ private volatile WeightedTransactionWrapperCollection wrapper;
+ /// Summed weight of all transactions in the set
+ private float totalWeight;
+
+ }
+
+} // namespace Nuclex.Support.Tracking
diff --git a/Source/Tracking/WaitableGroup.Test.cs b/Source/Tracking/WaitableGroup.Test.cs
deleted file mode 100644
index 9fcd81b..0000000
--- a/Source/Tracking/WaitableGroup.Test.cs
+++ /dev/null
@@ -1,257 +0,0 @@
-#region CPL License
-/*
-Nuclex Framework
-Copyright (C) 2002-2008 Nuclex Development Labs
-
-This library is free software; you can redistribute it and/or
-modify it under the terms of the IBM Common Public License as
-published by the IBM Corporation; either version 1.0 of the
-License, or (at your option) any later version.
-
-This library is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-IBM Common Public License for more details.
-
-You should have received a copy of the IBM Common Public
-License along with this library
-*/
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-#if UNITTEST
-
-using NUnit.Framework;
-using NMock2;
-
-namespace Nuclex.Support.Tracking {
-
- /// Unit Test for the waitable group class
- [TestFixture]
- public class WaitableGroupTest {
-
- #region interface IWaitableGroupSubscriber
-
- /// Interface used to test the set waitable.
- public interface IWaitableGroupSubscriber {
-
- /// Called when the set waitable's progress changes
- /// Waitable group whose progress has changed
- /// Contains the new progress achieved
- void ProgressChanged(object sender, ProgressReportEventArgs e);
-
- /// Called when the set waitable has ended
- /// Waitable group that as ended
- /// Not used
- void Ended(object sender, EventArgs e);
-
- }
-
- #endregion // interface IWaitableGroupSubscriber
-
- #region class ProgressUpdateEventArgsMatcher
-
- /// Compares two ProgressUpdateEventArgsInstances for NMock validation
- private class ProgressUpdateEventArgsMatcher : Matcher {
-
- /// Initializes a new ProgressUpdateEventArgsMatcher
- /// Expected progress update event arguments
- public ProgressUpdateEventArgsMatcher(ProgressReportEventArgs expected) {
- this.expected = expected;
- }
-
- ///
- /// Called by NMock to verfiy the ProgressUpdateEventArgs match the expected value
- ///
- /// Actual value to compare to the expected value
- ///
- /// True if the actual value matches the expected value; otherwise false
- ///
- public override bool Matches(object actualAsObject) {
- ProgressReportEventArgs actual = (actualAsObject as ProgressReportEventArgs);
- if(actual == null)
- return false;
-
- return (actual.Progress == this.expected.Progress);
- }
-
- /// Creates a string representation of the expected value
- /// Writer to write the string representation into
- public override void DescribeTo(TextWriter writer) {
- writer.Write(this.expected.Progress.ToString());
- }
-
- /// Expected progress update event args value
- private ProgressReportEventArgs expected;
-
- }
-
- #endregion // class ProgressUpdateEventArgsMatcher
-
- #region class TestWaitable
-
- /// Waitable used for testing in this unit test
- private class TestWaitable : Waitable, IProgressReporter {
-
- /// will be triggered to report when progress has been achieved
- public event EventHandler AsyncProgressChanged;
-
- /// Changes the testing waitable's indicated progress
- ///
- /// New progress to be reported by the testing waitable
- ///
- public void ChangeProgress(float progress) {
- OnAsyncProgressChanged(progress);
- }
-
- /// Transitions the waitable into the ended state
- public void End() {
- OnAsyncEnded();
- }
-
- /// Fires the progress update event
- /// Progress to report (ranging from 0.0 to 1.0)
- ///
- /// Informs the observers of this waitable about the achieved progress.
- ///
- protected virtual void OnAsyncProgressChanged(float progress) {
- OnAsyncProgressChanged(new ProgressReportEventArgs(progress));
- }
-
- /// Fires the progress update event
- /// Progress to report (ranging from 0.0 to 1.0)
- ///
- /// Informs the observers of this waitable about the achieved progress.
- /// Allows for classes derived from the Waitable class to easily provide
- /// a custom event arguments class that has been derived from the
- /// Waitable's ProgressUpdateEventArgs class.
- ///
- protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) {
- EventHandler copy = AsyncProgressChanged;
- if(copy != null)
- copy(this, eventArguments);
- }
-
- }
-
- #endregion // class TestWaitable
-
- /// Initialization routine executed before each test is run
- [SetUp]
- public void Setup() {
- this.mockery = new Mockery();
- }
-
- /// Validates that the set waitable properly sums the progress
- [Test]
- public void TestSummedProgress() {
- WaitableGroup testWaitableGroup =
- new WaitableGroup(
- new TestWaitable[] { new TestWaitable(), new TestWaitable() }
- );
-
- IWaitableGroupSubscriber mockedSubscriber = mockSubscriber(testWaitableGroup);
-
- Expect.Once.On(mockedSubscriber).
- Method("ProgressChanged").
- With(
- new Matcher[] {
- new NMock2.Matchers.TypeMatcher(typeof(WaitableGroup)),
- new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.25f))
- }
- );
-
- testWaitableGroup.Children[0].Waitable.ChangeProgress(0.5f);
-
- this.mockery.VerifyAllExpectationsHaveBeenMet();
- }
-
- /// Validates that the waitable group respects the weights
- [Test]
- public void TestWeightedSummedProgress() {
- WaitableGroup testWaitableGroup =
- new WaitableGroup(
- new WeightedWaitable[] {
- new WeightedWaitable(new TestWaitable(), 1.0f),
- new WeightedWaitable(new TestWaitable(), 2.0f)
- }
- );
-
- IWaitableGroupSubscriber mockedSubscriber = mockSubscriber(testWaitableGroup);
-
- Expect.Once.On(mockedSubscriber).
- Method("ProgressChanged").
- With(
- new Matcher[] {
- new NMock2.Matchers.TypeMatcher(typeof(WaitableGroup)),
- new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.5f / 3.0f))
- }
- );
-
- testWaitableGroup.Children[0].Waitable.ChangeProgress(0.5f);
-
- Expect.Once.On(mockedSubscriber).
- Method("ProgressChanged").
- With(
- new Matcher[] {
- new NMock2.Matchers.TypeMatcher(typeof(WaitableGroup)),
- new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.5f))
- }
- );
-
- testWaitableGroup.Children[1].Waitable.ChangeProgress(0.5f);
-
- this.mockery.VerifyAllExpectationsHaveBeenMet();
- }
-
- ///
- /// Validates that the ended event is triggered when the last waitable ends
- ///
- [Test]
- public void TestEndedEvent() {
- WaitableGroup testWaitableGroup =
- new WaitableGroup(
- new TestWaitable[] { new TestWaitable(), new TestWaitable() }
- );
-
- IWaitableGroupSubscriber mockedSubscriber = mockSubscriber(testWaitableGroup);
-
- Expect.Exactly(2).On(mockedSubscriber).
- Method("ProgressChanged").
- WithAnyArguments();
-
- Expect.Once.On(mockedSubscriber).
- Method("Ended").
- WithAnyArguments();
-
- testWaitableGroup.Children[0].Waitable.End();
- testWaitableGroup.Children[1].Waitable.End();
-
- this.mockery.VerifyAllExpectationsHaveBeenMet();
- }
-
- /// Mocks a subscriber for the events of a waitable
- /// Waitable to mock an event subscriber for
- /// The mocked event subscriber
- private IWaitableGroupSubscriber mockSubscriber(Waitable waitable) {
- IWaitableGroupSubscriber mockedSubscriber =
- this.mockery.NewMock();
-
- waitable.AsyncEnded += new EventHandler(mockedSubscriber.Ended);
- (waitable as IProgressReporter).AsyncProgressChanged +=
- new EventHandler(mockedSubscriber.ProgressChanged);
-
- return mockedSubscriber;
- }
-
- /// Mock object factory
- private Mockery mockery;
-
- }
-
-} // namespace Nuclex.Support.Tracking
-
-#endif // UNITTEST
diff --git a/Source/Tracking/WaitableGroup.cs b/Source/Tracking/WaitableGroup.cs
deleted file mode 100644
index ff24fb7..0000000
--- a/Source/Tracking/WaitableGroup.cs
+++ /dev/null
@@ -1,202 +0,0 @@
-#region CPL License
-/*
-Nuclex Framework
-Copyright (C) 2002-2008 Nuclex Development Labs
-
-This library is free software; you can redistribute it and/or
-modify it under the terms of the IBM Common Public License as
-published by the IBM Corporation; either version 1.0 of the
-License, or (at your option) any later version.
-
-This library is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-IBM Common Public License for more details.
-
-You should have received a copy of the IBM Common Public
-License along with this library
-*/
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-
-using Nuclex.Support.Collections;
-
-namespace Nuclex.Support.Tracking {
-
- /// Forms a single waitable from a group of waitables
- /// Type of waitables to manage as a set
- public class WaitableGroup : Waitable, IDisposable, IProgressReporter
- where WaitableType : Waitable {
-
- /// will be triggered to report when progress has been achieved
- public event EventHandler AsyncProgressChanged;
-
- /// Initializes a new waitable group
- /// Waitables to track with this group
- ///
- /// Uses a default weighting factor of 1.0 for all waitables.
- ///
- public WaitableGroup(IEnumerable childs)
- : this() {
-
- // Construct a WeightedWaitable with the default weight for each
- // waitable and wrap it in an ObservedWaitable
- foreach(WaitableType waitable in childs) {
- this.children.Add(
- new ObservedWeightedWaitable(
- new WeightedWaitable(waitable),
- new ObservedWeightedWaitable.ReportDelegate(asyncProgressUpdated),
- new ObservedWeightedWaitable.ReportDelegate(asyncChildEnded)
- )
- );
- }
-
- // Since all waitables have a weight of 1.0, the total weight is
- // equal to the number of waitables in our list
- this.totalWeight = (float)this.children.Count;
-
- }
-
- /// Initializes a new waitable group
- /// Waitables to track with this group
- public WaitableGroup(
- IEnumerable> childs
- )
- : this() {
-
- // Construct an ObservedWaitable around each of the WeightedWaitables
- foreach(WeightedWaitable waitable in childs) {
- this.children.Add(
- new ObservedWeightedWaitable(
- waitable,
- new ObservedWeightedWaitable.ReportDelegate(asyncProgressUpdated),
- new ObservedWeightedWaitable.ReportDelegate(asyncChildEnded)
- )
- );
-
- // Sum up the total weight
- this.totalWeight += waitable.Weight;
- }
-
- }
-
- /// Performs common initialization for the public constructors
- private WaitableGroup() {
- this.children = new List>();
- }
-
- /// Immediately releases all resources owned by the object
- public void Dispose() {
-
- if(this.children != null) {
-
- // Dispose all the observed waitables, disconnecting the events from the
- // actual waitables so the GC can more easily collect this class
- for(int index = 0; index < this.children.Count; ++index)
- this.children[index].Dispose();
-
- this.children = null;
- this.wrapper = null;
-
- }
-
- }
-
- /// Childs contained in the waitable set
- public IList> Children {
- get {
-
- // The wrapper is constructed only when needed. Most of the time, users will
- // just create a waitable group and monitor its progress without ever using
- // the Childs collection.
- if(this.wrapper == null) {
-
- // This doesn't need a lock because it's a stateless wrapper.
- // If it is constructed twice, then so be it, no problem at all.
- this.wrapper = new WeightedWaitableWrapperCollection(
- this.children
- );
-
- }
-
- return this.wrapper;
-
- }
- }
-
- /// Fires the progress update event
- /// Progress to report (ranging from 0.0 to 1.0)
- ///
- /// Informs the observers of this waitables about the achieved progress.
- ///
- protected virtual void OnAsyncProgressChanged(float progress) {
- OnAsyncProgressChanged(new ProgressReportEventArgs(progress));
- }
-
- /// Fires the progress update event
- /// Progress to report (ranging from 0.0 to 1.0)
- ///
- /// Informs the observers of this waitable about the achieved progress.
- /// Allows for classes derived from the Waitable class to easily provide
- /// a custom event arguments class that has been derived from the
- /// waitable's ProgressUpdateEventArgs class.
- ///
- protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) {
- EventHandler copy = AsyncProgressChanged;
- if(copy != null)
- copy(this, eventArguments);
- }
-
- ///
- /// Called when the progress of one of the observed waitables changes
- ///
- private void asyncProgressUpdated() {
- float totalProgress = 0.0f;
-
- // Calculate the sum of the progress reported by our child waitables,
- // scaled to the weight each waitable has assigned to it.
- for(int index = 0; index < this.children.Count; ++index) {
- totalProgress +=
- this.children[index].Progress * this.children[index].WeightedWaitable.Weight;
- }
-
- // Calculate the actual combined progress
- if(this.totalWeight > 0.0f)
- totalProgress /= this.totalWeight;
-
- // Send out the progress update
- OnAsyncProgressChanged(totalProgress);
- }
-
- ///
- /// Called when an observed waitable ends
- ///
- private void asyncChildEnded() {
-
- // If there's still at least one waitable going, don't report that
- // the waitable group has finished yet.
- for(int index = 0; index < this.children.Count; ++index)
- if(!this.children[index].WeightedWaitable.Waitable.Ended)
- return;
-
- // All child waitables have ended, so the set has now ended as well
- OnAsyncEnded();
-
- }
-
- /// Waitables being managed in the set
- private List> children;
- ///
- /// Wrapper collection for exposing the child waitables under the
- /// WeightedWaitable interface
- ///
- private volatile WeightedWaitableWrapperCollection wrapper;
- /// Summed weight of all waitables in the set
- private float totalWeight;
-
- }
-
-} // namespace Nuclex.Support.Tracking
diff --git a/Source/Tracking/WeightedTransaction.Test.cs b/Source/Tracking/WeightedTransaction.Test.cs
new file mode 100644
index 0000000..ea914e2
--- /dev/null
+++ b/Source/Tracking/WeightedTransaction.Test.cs
@@ -0,0 +1,88 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2008 Nuclex Development Labs
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the IBM Common Public License as
+published by the IBM Corporation; either version 1.0 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+IBM Common Public License for more details.
+
+You should have received a copy of the IBM Common Public
+License along with this library
+*/
+#endregion
+
+using System;
+using System.IO;
+
+#if UNITTEST
+
+using NUnit.Framework;
+using NUnit.Framework.SyntaxHelpers;
+
+namespace Nuclex.Support.Tracking {
+
+ /// Unit Test for the weighted transaction wrapper
+ [TestFixture]
+ public class WeightedTransactionTest {
+
+ #region class TestTransaction
+
+ /// Transaction used for testing in this unit test
+ private class TestTransaction : Transaction { }
+
+ #endregion // class TestTransaction
+
+ ///
+ /// Tests whether the weighted transaction wrapper correctly stores the transaction
+ /// it was given in the constructor
+ ///
+ [Test]
+ public void TestTransactionStorage() {
+ TestTransaction transaction = new TestTransaction();
+ WeightedTransaction testWrapper = new WeightedTransaction(
+ transaction
+ );
+
+ Assert.AreSame(transaction, testWrapper.Transaction);
+ }
+
+ ///
+ /// Tests whether the weighted transaction wrapper correctly applies the default
+ /// unit weight to the transaction if no explicit weight was specified
+ ///
+ [Test]
+ public void TestDefaultWeight() {
+ TestTransaction transaction = new TestTransaction();
+ WeightedTransaction testWrapper = new WeightedTransaction(
+ transaction
+ );
+
+ Assert.AreEqual(1.0f, testWrapper.Weight);
+ }
+
+ ///
+ /// Tests whether the weighted transaction wrapper correctly stores the weight
+ /// it was given in the constructor
+ ///
+ [Test]
+ public void TestWeightStorage() {
+ TestTransaction transaction = new TestTransaction();
+ WeightedTransaction testWrapper = new WeightedTransaction(
+ transaction, 12.0f
+ );
+
+ Assert.AreEqual(12.0f, testWrapper.Weight);
+ }
+
+ }
+
+} // namespace Nuclex.Support.Tracking
+
+#endif // UNITTEST
diff --git a/Source/Tracking/WeightedTransaction.cs b/Source/Tracking/WeightedTransaction.cs
new file mode 100644
index 0000000..464f564
--- /dev/null
+++ b/Source/Tracking/WeightedTransaction.cs
@@ -0,0 +1,60 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2008 Nuclex Development Labs
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the IBM Common Public License as
+published by the IBM Corporation; either version 1.0 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+IBM Common Public License for more details.
+
+You should have received a copy of the IBM Common Public
+License along with this library
+*/
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace Nuclex.Support.Tracking {
+
+ /// Transaction with an associated weight for the total progress
+ public class WeightedTransaction where TransactionType : Transaction {
+
+ ///
+ /// Initializes a new weighted transaction with a default weight of 1.0
+ ///
+ /// Transaction whose progress to monitor
+ public WeightedTransaction(TransactionType transaction) : this(transaction, 1.0f) { }
+
+ /// Initializes a new weighted transaction
+ /// transaction whose progress to monitor
+ /// Weighting of the transaction's progress
+ public WeightedTransaction(TransactionType transaction, float weight) {
+ this.transaction = transaction;
+ this.weight = weight;
+ }
+
+ /// Transaction being wrapped by this weighted transaction
+ public TransactionType Transaction {
+ get { return this.transaction; }
+ }
+
+ /// The contribution of this transaction to the total progress
+ public float Weight {
+ get { return this.weight; }
+ }
+
+ /// Transaction whose progress we're tracking
+ private TransactionType transaction;
+ /// Weighting of this transaction in the total progress
+ private float weight;
+
+ }
+
+} // namespace Nuclex.Support.Tracking
diff --git a/Source/Tracking/WeightedWaitable.cs b/Source/Tracking/WeightedWaitable.cs
deleted file mode 100644
index 3a0ed56..0000000
--- a/Source/Tracking/WeightedWaitable.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-#region CPL License
-/*
-Nuclex Framework
-Copyright (C) 2002-2008 Nuclex Development Labs
-
-This library is free software; you can redistribute it and/or
-modify it under the terms of the IBM Common Public License as
-published by the IBM Corporation; either version 1.0 of the
-License, or (at your option) any later version.
-
-This library is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-IBM Common Public License for more details.
-
-You should have received a copy of the IBM Common Public
-License along with this library
-*/
-#endregion
-
-using System;
-using System.Collections.Generic;
-
-namespace Nuclex.Support.Tracking {
-
- /// Waitable with an associated weight for the total progress
- public class WeightedWaitable where WaitableType : Waitable {
-
- ///
- /// Initializes a new weighted waitable with a default weight of 1.0
- ///
- /// Waitable whose progress to monitor
- public WeightedWaitable(WaitableType waitable) : this(waitable, 1.0f) { }
-
- /// Initializes a new weighted waitable
- /// Waitable whose progress to monitor
- /// Weighting of the waitable's progress
- public WeightedWaitable(WaitableType waitable, float weight) {
- this.waitable = waitable;
- this.weight = weight;
- }
-
- /// Waitable being wrapped by this weighted waitable
- public WaitableType Waitable {
- get { return this.waitable; }
- }
-
- /// The contribution of this waitable to the total progress
- public float Weight {
- get { return this.weight; }
- }
-
- /// Waitable whose progress we're tracking
- private WaitableType waitable;
- /// Weighting of this waitable in the total progress
- private float weight;
-
- }
-
-} // namespace Nuclex.Support.Tracking