diff --git a/Nuclex.Support (Xbox 360).csproj b/Nuclex.Support (Xbox 360).csproj
index 9b9788f..435e336 100644
--- a/Nuclex.Support (Xbox 360).csproj
+++ b/Nuclex.Support (Xbox 360).csproj
@@ -118,6 +118,10 @@
ReverseComparer.cs
+
+
+ PartialStream.cs
+
RingMemoryStream.cs
diff --git a/Nuclex.Support.csproj b/Nuclex.Support.csproj
index 1095550..9aae330 100644
--- a/Nuclex.Support.csproj
+++ b/Nuclex.Support.csproj
@@ -100,6 +100,10 @@
ReverseComparer.cs
+
+
+ PartialStream.cs
+
RingMemoryStream.cs
diff --git a/Source/IO/ChainStream.Test.cs b/Source/IO/ChainStream.Test.cs
index f1e9fc4..48085d9 100644
--- a/Source/IO/ChainStream.Test.cs
+++ b/Source/IO/ChainStream.Test.cs
@@ -227,7 +227,7 @@ namespace Nuclex.Support.IO {
[Test]
public void TestPartitionedRead() {
ChainStream chainer = chainTwoStreamsOfTenBytes();
-
+
((MemoryStream)chainer.ChainedStreams[0]).Write(
new byte[] { 1, 2, 3, 4, 5 }, 0, 5
);
@@ -310,7 +310,7 @@ namespace Nuclex.Support.IO {
byte[] buffer = new byte[5];
int readByteCount = chainer.Read(buffer, 0, 3);
-
+
Assert.AreEqual(3, readByteCount);
Assert.AreEqual(new byte[] { 0, 9, 8, 0, 0 }, buffer);
diff --git a/Source/IO/PartialStream.Test.cs b/Source/IO/PartialStream.Test.cs
new file mode 100644
index 0000000..8328fa7
--- /dev/null
+++ b/Source/IO/PartialStream.Test.cs
@@ -0,0 +1,515 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2009 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
+
+#if UNITTEST
+
+using System;
+using System.IO;
+
+using NUnit.Framework;
+
+namespace Nuclex.Support.IO {
+
+ /// Unit Test for the partial stream
+ [TestFixture]
+ public class PartialStreamTest {
+
+ #region class TestStream
+
+ /// Testing stream that allows specific features to be disabled
+ private class TestStream : Stream {
+
+ /// Initializes a new test stream
+ /// Stream that will be wrapped
+ /// Whether to allow reading from the stream
+ /// Whether to allow writing to the stream
+ /// Whether to allow seeking within the stream
+ public TestStream(
+ Stream wrappedStream, bool allowRead, bool allowWrite, bool allowSeek
+ ) {
+ this.stream = wrappedStream;
+ this.readAllowed = allowRead;
+ this.writeAllowed = allowWrite;
+ this.seekAllowed = allowSeek;
+ }
+
+ /// Whether data can be read from the stream
+ public override bool CanRead {
+ get { return this.readAllowed; }
+ }
+
+ /// Whether the stream supports seeking
+ public override bool CanSeek {
+ get { return this.seekAllowed; }
+ }
+
+ /// Whether data can be written into the stream
+ public override bool CanWrite {
+ get { return this.writeAllowed; }
+ }
+
+ ///
+ /// Clears all buffers for this stream and causes any buffered data to be written
+ /// to the underlying device.
+ ///
+ public override void Flush() {
+ ++this.flushCallCount;
+ this.stream.Flush();
+ }
+
+ /// Length of the stream in bytes
+ public override long Length {
+ get {
+ enforceSeekAllowed();
+ return this.stream.Length;
+ }
+ }
+
+ /// Absolute position of the file pointer within the stream
+ ///
+ /// At least one of the chained streams does not support seeking
+ ///
+ public override long Position {
+ get {
+ enforceSeekAllowed();
+ return this.stream.Position;
+ }
+ set {
+ enforceSeekAllowed();
+ this.stream.Position = value;
+ }
+ }
+
+ ///
+ /// Reads a sequence of bytes from the stream and advances the position of
+ /// the file pointer by the number of bytes read.
+ ///
+ /// Buffer that will receive the data read from the stream
+ ///
+ /// Offset in the buffer at which the stream will place the data read
+ ///
+ /// Maximum number of bytes that will be read
+ ///
+ /// The number of bytes that were actually read from the stream and written into
+ /// the provided buffer
+ ///
+ public override int Read(byte[] buffer, int offset, int count) {
+ enforceReadAllowed();
+ return this.stream.Read(buffer, offset, count);
+ }
+
+ /// Changes the position of the file pointer
+ ///
+ /// Offset to move the file pointer by, relative to the position indicated by
+ /// the parameter.
+ ///
+ ///
+ /// Reference point relative to which the file pointer is placed
+ ///
+ /// The new absolute position within the stream
+ public override long Seek(long offset, SeekOrigin origin) {
+ enforceSeekAllowed();
+ return this.stream.Seek(offset, origin);
+ }
+
+ /// Changes the length of the stream
+ /// New length the stream shall have
+ public override void SetLength(long value) {
+ enforceSeekAllowed();
+ this.stream.SetLength(value);
+ }
+
+ ///
+ /// Writes a sequence of bytes to the stream and advances the position of
+ /// the file pointer by the number of bytes written.
+ ///
+ ///
+ /// Buffer containing the data that will be written to the stream
+ ///
+ ///
+ /// Offset in the buffer at which the data to be written starts
+ ///
+ /// Number of bytes that will be written into the stream
+ public override void Write(byte[] buffer, int offset, int count) {
+ enforceWriteAllowed();
+ this.stream.Write(buffer, offset, count);
+ }
+
+ /// Number of times the Flush() method has been called
+ public int FlushCallCount {
+ get { return this.flushCallCount; }
+ }
+
+ /// Throws an exception if reading is not allowed
+ private void enforceReadAllowed() {
+ if(!this.readAllowed) {
+ throw new NotSupportedException("Reading has been disabled");
+ }
+ }
+
+ /// Throws an exception if writing is not allowed
+ private void enforceWriteAllowed() {
+ if(!this.writeAllowed) {
+ throw new NotSupportedException("Writing has been disabled");
+ }
+ }
+
+ /// Throws an exception if seeking is not allowed
+ private void enforceSeekAllowed() {
+ if(!this.seekAllowed) {
+ throw new NotSupportedException("Seeking has been disabled");
+ }
+ }
+
+ /// Stream being wrapped for testing
+ private Stream stream;
+ /// whether to allow reading from the wrapped stream
+ private bool readAllowed;
+ /// Whether to allow writing to the wrapped stream
+ private bool writeAllowed;
+ /// Whether to allow seeking within the wrapped stream
+ private bool seekAllowed;
+ /// Number of times the Flush() method has been called
+ private int flushCallCount;
+
+ }
+
+ #endregion // class TestStream
+
+ /// Tests whether the partial stream constructor is working
+ [Test]
+ public void TestConstructor() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ memoryStream.SetLength(123);
+ PartialStream partialStream = new PartialStream(memoryStream, 23, 100);
+ Assert.AreEqual(100, partialStream.Length);
+ }
+ }
+
+ ///
+ /// Verifies that the partial stream constructor throws an exception if
+ /// it's invoked with an invalid start offset
+ ///
+ [Test, ExpectedException(typeof(ArgumentException))]
+ public void TestThrowOnInvalidStartInConstructor() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ memoryStream.SetLength(123);
+ PartialStream partialStream = new PartialStream(memoryStream, -1, 10);
+ Assert.AreNotEqual(partialStream.Length, partialStream.Length);
+ }
+ }
+
+ ///
+ /// Verifies that the partial stream constructor throws an exception if
+ /// it's invoked with an invalid start offset
+ ///
+ [Test, ExpectedException(typeof(ArgumentException))]
+ public void TestThrowOnInvalidLengthInConstructor() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ memoryStream.SetLength(123);
+ PartialStream partialStream = new PartialStream(memoryStream, 100, 24);
+ Assert.AreNotEqual(partialStream.Length, partialStream.Length);
+ }
+ }
+
+ ///
+ /// Verifies that the partial stream constructor throws an exception if
+ /// it's invoked with a start offset on an unseekable stream
+ ///
+ [Test, ExpectedException(typeof(ArgumentException))]
+ public void TestThrowOnUnseekableStreamWithOffsetInConstructor() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ memoryStream.SetLength(123);
+ TestStream testStream = new TestStream(memoryStream, true, true, false);
+ PartialStream partialStream = new PartialStream(testStream, 23, 100);
+ Assert.AreNotEqual(partialStream.Length, partialStream.Length);
+ }
+ }
+
+ ///
+ /// Tests whether the CanRead property reports its status correctly
+ ///
+ [Test]
+ public void TestCanReadProperty() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ TestStream yesStream = new TestStream(memoryStream, true, true, true);
+ TestStream noStream = new TestStream(memoryStream, false, true, true);
+
+ Assert.IsTrue(new PartialStream(yesStream, 0, 0).CanRead);
+ Assert.IsFalse(new PartialStream(noStream, 0, 0).CanRead);
+ }
+ }
+
+ ///
+ /// Tests whether the CanWrite property reports its status correctly
+ ///
+ [Test]
+ public void TestCanWriteProperty() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ TestStream yesStream = new TestStream(memoryStream, true, true, true);
+ TestStream noStream = new TestStream(memoryStream, true, false, true);
+
+ Assert.IsTrue(new PartialStream(yesStream, 0, 0).CanWrite);
+ Assert.IsFalse(new PartialStream(noStream, 0, 0).CanWrite);
+ }
+ }
+
+ ///
+ /// Tests whether the CanSeek property reports its status correctly
+ ///
+ [Test]
+ public void TestCanSeekProperty() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ TestStream yesStream = new TestStream(memoryStream, true, true, true);
+ TestStream noStream = new TestStream(memoryStream, true, true, false);
+
+ Assert.IsTrue(new PartialStream(yesStream, 0, 0).CanSeek);
+ Assert.IsFalse(new PartialStream(noStream, 0, 0).CanSeek);
+ }
+ }
+
+ ///
+ /// Tests whether the CompleteStream property returns the original stream
+ ///
+ [Test]
+ public void TestCompleteStreamProperty() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ PartialStream partialStream = new PartialStream(memoryStream, 0, 0);
+ Assert.AreSame(memoryStream, partialStream.CompleteStream);
+ }
+ }
+
+ /// Tests whether the Flush() method can be called
+ [Test]
+ public void TestFlush() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ PartialStream partialStream = new PartialStream(memoryStream, 0, 0);
+ partialStream.Flush();
+ }
+ }
+
+ ///
+ /// Tests whether the Position property correctly reports the file pointer position
+ ///
+ [Test]
+ public void TestGetPosition() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ memoryStream.SetLength(123);
+ PartialStream partialStream = new PartialStream(memoryStream, 23, 100);
+
+ Assert.AreEqual(0, partialStream.Position);
+
+ byte[] test = new byte[10];
+ int bytesRead = partialStream.Read(test, 0, 10);
+
+ Assert.AreEqual(10, bytesRead);
+ Assert.AreEqual(10, partialStream.Position);
+
+ bytesRead = partialStream.Read(test, 0, 10);
+
+ Assert.AreEqual(10, bytesRead);
+ Assert.AreEqual(20, partialStream.Position);
+
+ }
+ }
+
+ ///
+ /// Tests whether the Position property is correctly updated
+ ///
+ [Test]
+ public void TestSetPosition() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ memoryStream.SetLength(123);
+ PartialStream partialStream = new PartialStream(memoryStream, 23, 100);
+
+ Assert.AreEqual(0, partialStream.Position);
+ partialStream.Position = 7;
+ Assert.AreEqual(partialStream.Position, 7);
+ partialStream.Position = 14;
+ Assert.AreEqual(partialStream.Position, 14);
+ }
+ }
+
+ ///
+ /// Tests whether the Position property throws an exception if the stream does
+ /// not support seeking.
+ ///
+ [Test, ExpectedException(typeof(NotSupportedException))]
+ public void TestThrowOnGetPositionOnUnseekableStream() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ TestStream testStream = new TestStream(memoryStream, true, true, false);
+
+ PartialStream partialStream = new PartialStream(testStream, 0, 0);
+ Assert.AreEqual(-12345, partialStream.Position);
+ }
+ }
+
+ ///
+ /// Tests whether the Position property throws an exception if the stream does
+ /// not support seeking.
+ ///
+ [Test, ExpectedException(typeof(NotSupportedException))]
+ public void TestThrowOnSetPositionOnUnseekableStream() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ TestStream testStream = new TestStream(memoryStream, true, true, false);
+
+ PartialStream partialStream = new PartialStream(testStream, 0, 0);
+ partialStream.Position = 0;
+ }
+ }
+
+ ///
+ /// Tests whether the Read() method throws an exception if the stream does
+ /// not support reading
+ ///
+ [Test, ExpectedException(typeof(NotSupportedException))]
+ public void TestThrowOnReadFromUnreadableStream() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ TestStream testStream = new TestStream(memoryStream, false, true, true);
+ PartialStream partialStream = new PartialStream(testStream, 0, 0);
+
+ byte[] test = new byte[10];
+ int bytesRead = partialStream.Read(test, 0, 10);
+
+ Assert.AreNotEqual(bytesRead, bytesRead);
+ }
+ }
+
+ ///
+ /// Tests whether the Seek() method of the partial stream is working
+ ///
+ [Test]
+ public void TestSeeking() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ memoryStream.SetLength(20);
+ PartialStream partialStream = new PartialStream(memoryStream, 0, 20);
+
+ Assert.AreEqual(7, partialStream.Seek(-13, SeekOrigin.End));
+ Assert.AreEqual(14, partialStream.Seek(7, SeekOrigin.Current));
+ Assert.AreEqual(11, partialStream.Seek(11, SeekOrigin.Begin));
+ }
+ }
+
+ ///
+ /// Tests whether the Seek() method throws an exception if an invalid
+ /// reference point is provided
+ ///
+ [Test, ExpectedException(typeof(ArgumentException))]
+ public void TestThrowOnInvalidSeekReferencePoint() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ PartialStream partialStream = new PartialStream(memoryStream, 0, 0);
+ partialStream.Seek(1, (SeekOrigin)12345);
+ }
+ }
+
+ ///
+ /// Verifies that the partial stream throws an exception if the attempt is
+ /// made to change the length of the stream
+ ///
+ [Test, ExpectedException(typeof(NotSupportedException))]
+ public void TestThrowOnLengthChange() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ PartialStream partialStream = new PartialStream(memoryStream, 0, 0);
+ partialStream.SetLength(123);
+ }
+ }
+
+ ///
+ /// Tests whether the Read() method returns 0 bytes if the attempt is made
+ /// to read data from an invalid position
+ ///
+ [Test]
+ public void TestReadFromInvalidPosition() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ memoryStream.SetLength(123);
+
+ memoryStream.Position = 1123;
+ byte[] test = new byte[10];
+
+ Assert.AreEqual(0, memoryStream.Read(test, 0, 10));
+ }
+ }
+
+ /// Verifies that the Read() method is working
+ [Test]
+ public void TestReadFromPartialStream() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ memoryStream.SetLength(123);
+
+ memoryStream.Position = 100;
+ memoryStream.Write(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, 0, 10);
+
+ PartialStream partialStream = new PartialStream(memoryStream, 95, 10);
+
+ byte[] buffer = new byte[15];
+ int bytesRead = partialStream.Read(buffer, 0, 15);
+
+ Assert.AreEqual(10, bytesRead);
+ Assert.AreEqual(
+ new byte[] { 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0 }, buffer
+ );
+ }
+ }
+
+ /// Verifies that the Write() method is working
+ [Test]
+ public void TestWriteToPartialStream() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ memoryStream.SetLength(123);
+
+ memoryStream.Position = 60;
+ memoryStream.Write(new byte[] { 11, 12, 13, 14, 15 }, 0, 5);
+
+ PartialStream partialStream = new PartialStream(memoryStream, 50, 15);
+ partialStream.Position = 3;
+ partialStream.Write(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, 0, 10);
+
+ byte[] buffer = new byte[17];
+ memoryStream.Position = 49;
+ int bytesRead = memoryStream.Read(buffer, 0, 17);
+
+ Assert.AreEqual(17, bytesRead);
+ Assert.AreEqual(
+ new byte[] { 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 14, 15, 0 },
+ buffer
+ );
+ }
+ }
+
+ ///
+ /// Verifies that an exception is thrown if the Write() method of the partial stream
+ /// is attempted to be used to extend the partial stream's length
+ ///
+ [Test, ExpectedException(typeof(NotSupportedException))]
+ public void TestThrowOnExtendPartialStream() {
+ using(MemoryStream memoryStream = new MemoryStream()) {
+ memoryStream.SetLength(25);
+
+ PartialStream partialStream = new PartialStream(memoryStream, 10, 10);
+ partialStream.Position = 5;
+ partialStream.Write(new byte[] { 1, 2, 3, 4, 5, 6 }, 0, 6);
+ }
+ }
+
+ }
+
+} // namespace Nuclex.Support.IO
+
+#endif // UNITTEST
diff --git a/Source/IO/PartialStream.cs b/Source/IO/PartialStream.cs
new file mode 100644
index 0000000..a9f8e6f
--- /dev/null
+++ b/Source/IO/PartialStream.cs
@@ -0,0 +1,264 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2009 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.Diagnostics;
+using System.IO;
+
+namespace Nuclex.Support.IO {
+
+ /// Wraps a stream and exposes only a limited region of its data
+ public class PartialStream : Stream {
+
+ /// Initializes a new partial stream
+ ///
+ /// Stream the wrapper will make a limited region accessible of
+ ///
+ ///
+ /// Start index in the stream which becomes the beginning for the wrapper
+ ///
+ ///
+ /// Length the wrapped stream should report and allow access to
+ ///
+ public PartialStream(Stream stream, long start, long length) {
+ if(start < 0) {
+ throw new ArgumentException("Start index must not be less than 0", "start");
+ }
+
+ if(!stream.CanSeek) {
+ if(start != 0) {
+ throw new ArgumentException(
+ "The only valid start for unseekable streams is 0", "start"
+ );
+ }
+ } else {
+ if(start + length > stream.Length) {
+ throw new ArgumentException(
+ "Partial stream exceeds end of full stream", "length"
+ );
+ }
+ }
+
+ this.stream = stream;
+ this.start = start;
+ this.length = length;
+ }
+
+ /// Whether data can be read from the stream
+ public override bool CanRead {
+ get { return this.stream.CanRead; }
+ }
+
+ /// Whether the stream supports seeking
+ public override bool CanSeek {
+ get { return this.stream.CanSeek; }
+ }
+
+ /// Whether data can be written into the stream
+ public override bool CanWrite {
+ get { return this.stream.CanWrite; }
+ }
+
+ ///
+ /// Clears all buffers for this stream and causes any buffered data to be written
+ /// to the underlying device.
+ ///
+ public override void Flush() {
+ this.stream.Flush();
+ }
+
+ /// Length of the stream in bytes
+ ///
+ /// The wrapped stream does not support seeking
+ ///
+ public override long Length {
+ get { return this.length; }
+ }
+
+ /// Absolute position of the file pointer within the stream
+ ///
+ /// The wrapped stream does not support seeking
+ ///
+ public override long Position {
+ get {
+ if(!this.stream.CanSeek) {
+ throw makeSeekNotSupportedException("seek");
+ }
+
+ return this.position;
+ }
+ set { moveFilePointer(value); }
+ }
+
+ ///
+ /// Reads a sequence of bytes from the stream and advances the position of
+ /// the file pointer by the number of bytes read.
+ ///
+ /// Buffer that will receive the data read from the stream
+ ///
+ /// Offset in the buffer at which the stream will place the data read
+ ///
+ /// Maximum number of bytes that will be read
+ ///
+ /// The number of bytes that were actually read from the stream and written into
+ /// the provided buffer
+ ///
+ ///
+ /// The wrapped stream does not support reading
+ ///
+ public override int Read(byte[] buffer, int offset, int count) {
+ if(!this.stream.CanRead) {
+ throw new NotSupportedException(
+ "Can't read: the wrapped stream doesn't support reading"
+ );
+ }
+
+ long remaining = this.length - this.position;
+ int bytesToRead = (int)Math.Min(count, remaining);
+
+ if(this.stream.CanSeek) {
+ this.stream.Position = this.position + this.start;
+ }
+ int bytesRead = this.stream.Read(buffer, offset, bytesToRead);
+ this.position += bytesRead;
+
+ return bytesRead;
+ }
+
+ /// Changes the position of the file pointer
+ ///
+ /// Offset to move the file pointer by, relative to the position indicated by
+ /// the parameter.
+ ///
+ ///
+ /// Reference point relative to which the file pointer is placed
+ ///
+ /// The new absolute position within the stream
+ public override long Seek(long offset, SeekOrigin origin) {
+ switch(origin) {
+ case SeekOrigin.Begin: {
+ return Position = offset;
+ }
+ case SeekOrigin.Current: {
+ return Position += offset;
+ }
+ case SeekOrigin.End: {
+ return Position = (Length + offset);
+ }
+ default: {
+ throw new ArgumentException("Invalid seek origin", "origin");
+ }
+ }
+ }
+
+ /// Changes the length of the stream
+ /// New length the stream shall have
+ ///
+ /// Always, the stream chainer does not support the SetLength() operation
+ ///
+ public override void SetLength(long value) {
+ throw new NotSupportedException("Resizing partial streams is not supported");
+ }
+
+ ///
+ /// Writes a sequence of bytes to the stream and advances the position of
+ /// the file pointer by the number of bytes written.
+ ///
+ ///
+ /// Buffer containing the data that will be written to the stream
+ ///
+ ///
+ /// Offset in the buffer at which the data to be written starts
+ ///
+ /// Number of bytes that will be written into the stream
+ ///
+ /// The behavior of this method is as follows: If one or more chained streams
+ /// do not support seeking, all data is appended to the final stream in the
+ /// chain. Otherwise, writing will begin with the stream the current file pointer
+ /// offset falls into. If the end of that stream is reached, writing continues
+ /// in the next stream. On the last stream, writing more data into the stream
+ /// that it current size allows will enlarge the stream.
+ ///
+ public override void Write(byte[] buffer, int offset, int count) {
+ long remaining = this.length - this.position;
+ if(count > remaining) {
+ throw new NotSupportedException(
+ "Cannot extend the length of the partial stream"
+ );
+ }
+
+ if(this.stream.CanSeek) {
+ this.stream.Position = this.position + this.start;
+ }
+ this.stream.Write(buffer, offset, count);
+
+ this.position += count;
+ }
+
+ /// Stream being wrapped by the partial stream wrapper
+ public Stream CompleteStream {
+ get { return this.stream; }
+ }
+
+ /// Moves the file pointer
+ /// New position the file pointer will be moved to
+ private void moveFilePointer(long position) {
+ if(!this.stream.CanSeek) {
+ throw makeSeekNotSupportedException("seek");
+ }
+
+ // Seemingly, it is okay to move the file pointer beyond the end of
+ // the stream until you try to Read() or Write()
+ this.position = position;
+ }
+
+ ///
+ /// Constructs a NotSupportException for an error caused by the wrapped
+ /// stream having no seek support
+ ///
+ /// Action that was tried to perform
+ /// The newly constructed NotSupportedException
+ private static NotSupportedException makeSeekNotSupportedException(string action) {
+ return new NotSupportedException(
+ string.Format(
+ "Can't {0}: the wrapped stream does not support seeking",
+ action
+ )
+ );
+ }
+
+ /// Streams that have been chained together
+ private Stream stream;
+ /// Start index of the partial stream in the wrapped stream
+ private long start;
+ /// Zero-based position of the partial stream's file pointer
+ ///
+ /// If the stream does not support seeking, the position will simply be counted
+ /// up until it reaches .
+ ///
+ private long position;
+ /// Length of the partial stream
+ private long length;
+
+
+ }
+
+} // namespace Nuclex.Support.IO
diff --git a/Source/Scheduling/OperationQueue.cs b/Source/Scheduling/OperationQueue.cs
index 7546324..7155fc5 100644
--- a/Source/Scheduling/OperationQueue.cs
+++ b/Source/Scheduling/OperationQueue.cs
@@ -119,10 +119,10 @@ namespace Nuclex.Support.Scheduling {
copy(this, eventArguments);
}
- /// Prepares the current operation and calls its Begin() method
+ /// Prepares the current operation and calls its Start() method
///
/// This subscribes the queue to the events of to the current operation
- /// and launches the operation by calling its Begin() method.
+ /// and launches the operation by calling its Start() method.
///
private void startCurrentOperation() {
OperationType operation = this.children[this.currentOperationIndex].Transaction;
@@ -168,8 +168,8 @@ namespace Nuclex.Support.Scheduling {
/// Called when the current executing operation ends
/// Operation that ended
- /// Not used
- private void asyncOperationEnded(object sender, EventArgs e) {
+ /// Not used
+ private void asyncOperationEnded(object sender, EventArgs arguments) {
// Unsubscribe from the current operation's events and update the
// accumulating progress counter
@@ -196,16 +196,17 @@ namespace Nuclex.Support.Scheduling {
/// Called when currently executing operation makes progress
/// Operation that has achieved progress
- /// Not used
- private void asyncOperationProgressChanged(object sender, ProgressReportEventArgs e) {
+ /// Not used
+ private void asyncOperationProgressChanged(
+ object sender, ProgressReportEventArgs arguments
+ ) {
// Determine the completed weight of the currently executing operation
- float currentOperationCompletedWeight =
- e.Progress * this.children[this.currentOperationIndex].Weight;
+ float operationWeight = this.children[this.currentOperationIndex].Weight;
+ float operationCompletedWeight = arguments.Progress * operationWeight;
// Build the total normalized amount of progress for the queue
- float progress =
- (this.completedWeight + currentOperationCompletedWeight) / this.totalWeight;
+ float progress = (this.completedWeight + operationCompletedWeight) / this.totalWeight;
// Done, we can send the actual progress to any event subscribers
OnAsyncProgressChanged(progress);