diff --git a/src/Jhu.SharpFitsIO/SpillMemoryStream.cs b/src/Jhu.SharpFitsIO/SpillMemoryStream.cs index 3702e50..0af0342 100644 --- a/src/Jhu.SharpFitsIO/SpillMemoryStream.cs +++ b/src/Jhu.SharpFitsIO/SpillMemoryStream.cs @@ -10,51 +10,70 @@ namespace Jhu.SharpFitsIO /// Implements a write-only memory stream that spills onto the /// disk when a given size is reached. /// - internal class SpillMemoryStream : Stream + /// + /// This class only implements the write functions of a standard + /// stream. When all data is written to the stream, it can be + /// written to another using the WriteTo function. + /// + internal class SpillMemoryStream : Stream, IDisposable { #region Private member varibles private long position; private long spillLimit; private string spillPath; - private MemoryStream memory; - private FileStream spill; - #endregion + /// + /// Internal memory buffer to store data temporarily until the + /// spill limit is reached. + /// + private MemoryStream memoryBuffer; - #region Constructors and initializers + /// + /// External file buffer to store data temporarily after the + /// spill limit is reached. + /// + private FileStream spillBuffer; - public SpillMemoryStream() - { - InitializeMembers(); - } + #endregion + #region Properties - public SpillMemoryStream(long spillLimit) + /// + /// Gets the current position of the stream. + /// + public override long Position { - InitializeMembers(); - - this.spillLimit = spillLimit; + get { return position; } + set { throw new InvalidOperationException(); } } - public SpillMemoryStream(long spillLimit, string spillPath) + /// + /// Gets or sets the size limit at which data is spilled to the disk. + /// + public long SpillLimit { - InitializeMembers(); - - this.spillLimit = spillLimit; - this.spillPath = spillPath; + get { return spillLimit; } + set + { + EnsureNotOpen(); + spillLimit = value; + } } - private void InitializeMembers() + /// + /// Gets or sets the path of temporary file used when data is spilled + /// to the disk. + /// + public string SpillPath { - this.position = 0; - this.spillLimit = long.MaxValue; // 1MB - this.spillPath = null; - this.memory = new MemoryStream(); - this.spill = null; + get { return spillPath; } + set + { + EnsureNotOpen(); + spillPath = value; + } } - #endregion - public override bool CanRead { get { return false; } @@ -80,51 +99,85 @@ public override long Length get { return position; } } - public override long Position + #endregion + #region Constructors and initializers + + public SpillMemoryStream() { - get { return position; } - set { throw new InvalidOperationException(); } + InitializeMembers(); } - public override void Close() + public SpillMemoryStream(long spillLimit) { - if (spill != null) - { - spill.Close(); - } + InitializeMembers(); - if (memory != null) - { - memory.Close(); - } + this.spillLimit = spillLimit; + } + + public SpillMemoryStream(long spillLimit, string spillPath) + { + InitializeMembers(); + + this.spillLimit = spillLimit; + this.spillPath = spillPath; + } + + private void InitializeMembers() + { + this.position = 0; + this.spillLimit = 0x100000; // 1MB + this.spillPath = null; + this.memoryBuffer = null; + this.spillBuffer = null; } protected override void Dispose(bool disposing) { if (disposing) { - if (spill != null) + if (spillBuffer != null) + { + spillBuffer.Dispose(); + } + + if (memoryBuffer != null) { - spill.Dispose(); + memoryBuffer.Dispose(); } - if (memory != null) + if (spillPath != null && File.Exists(spillPath)) { - memory.Dispose(); + File.Delete(spillPath); } } + } - if (spillPath != null && File.Exists(spillPath)) + public void Dispose() + { + Dispose(true); + } + + #endregion + #region Stream implementation + + public override void Close() + { + if (spillBuffer != null) + { + spillBuffer.Close(); + } + + if (memoryBuffer != null) { - File.Delete(spillPath); + memoryBuffer.Close(); } } public override void Flush() { - if (spill != null) + if (spillBuffer != null) { - spill.Flush(); + spillBuffer.Flush(); } } @@ -150,15 +203,15 @@ public override void SetLength(long value) public override void Write(byte[] buffer, int offset, int count) { - if (spill == null && position + count < spillLimit) + if (position + count < spillLimit) { - memory.Write(buffer, offset, count); + OpenMemoryBuffer(); + memoryBuffer.Write(buffer, offset, count); } else { - OpenSpillFile(); - - spill.Write(buffer, offset, count); + OpenSpillBuffer(); + spillBuffer.Write(buffer, offset, count); } position += count; @@ -166,53 +219,69 @@ public override void Write(byte[] buffer, int offset, int count) public override void WriteByte(byte value) { - if (spill == null && position + 1 < spillLimit) + if (position + 1 < spillLimit) { - memory.WriteByte(value); + OpenMemoryBuffer(); + memoryBuffer.WriteByte(value); } else { - OpenSpillFile(); - - spill.WriteByte(value); + OpenSpillBuffer(); + spillBuffer.WriteByte(value); } + + position++; } + #endregion + + /// + /// Writes the content of both buffers to an output stream. + /// + /// public void WriteTo(Stream stream) { // TODO: this function could use async copy but that just // overcomplicates things // Flush memory to stream - if (memory != null) + if (memoryBuffer != null) { - memory.WriteTo(stream); + memoryBuffer.WriteTo(stream); } - if (spill != null) + if (spillBuffer != null) { // Rewind file but remember position - var pos = spill.Position; - spill.Seek(0, SeekOrigin.Begin); + var pos = spillBuffer.Position; + spillBuffer.Seek(0, SeekOrigin.Begin); // Copy file to output stream var i = 0; var buffer = new byte[0x10000]; // 64k while (i < pos) { - var count = spill.Read(buffer, 0, buffer.Length); + var count = spillBuffer.Read(buffer, 0, buffer.Length); stream.Write(buffer, 0, count); i += count; } - spill.Seek(pos, SeekOrigin.Begin); + spillBuffer.Seek(pos, SeekOrigin.Begin); } } - private void OpenSpillFile() + private void OpenMemoryBuffer() { - if (spill != null) + if (memoryBuffer == null && spillLimit > 0) + { + memoryBuffer = new MemoryStream(); + } + } + + private void OpenSpillBuffer() + { + if (spillBuffer == null) { // If path is not set use temp if (spillPath == null) @@ -220,7 +289,15 @@ private void OpenSpillFile() spillPath = Path.GetTempFileName(); } - spill = new FileStream(spillPath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None); + spillBuffer = new FileStream(spillPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); + } + } + + private void EnsureNotOpen() + { + if (memoryBuffer != null || spillBuffer != null) + { + throw new InvalidOperationException("Stream is already open."); // TODO *** } } } diff --git a/test/Jhu.SharpFitsIO.Test/Jhu.SharpFitsIO.Test.csproj b/test/Jhu.SharpFitsIO.Test/Jhu.SharpFitsIO.Test.csproj index fbd17ae..72cdb5c 100644 --- a/test/Jhu.SharpFitsIO.Test/Jhu.SharpFitsIO.Test.csproj +++ b/test/Jhu.SharpFitsIO.Test/Jhu.SharpFitsIO.Test.csproj @@ -67,6 +67,7 @@ + diff --git a/test/Jhu.SharpFitsIO.Test/SpillMemoryStreamTest.cs b/test/Jhu.SharpFitsIO.Test/SpillMemoryStreamTest.cs new file mode 100644 index 0000000..757adc3 --- /dev/null +++ b/test/Jhu.SharpFitsIO.Test/SpillMemoryStreamTest.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Jhu.SharpFitsIO +{ + [TestClass] + public class SpillMemoryStreamTest + { + [TestMethod] + public void MemoryOnlyTest() + { + var buffer = new byte[0x10000]; // 64k + + using (var sms = new SpillMemoryStream()) + { + sms.Write(buffer, 0, buffer.Length); + + var ms = new MemoryStream(); + sms.WriteTo(ms); + + Assert.AreEqual(0x10000, ms.Position); + } + } + + [TestMethod] + public void SpillToTempTest() + { + var buffer = new byte[0x10000]; // 64k + + using (var sms = new SpillMemoryStream()) + { + // Write 2M + for (int i = 0; i < 32; i++) + { + sms.Write(buffer, 0, buffer.Length); + } + + var ms = new MemoryStream(); + sms.WriteTo(ms); + + Assert.AreEqual(0x200000, ms.Position); + } + } + } +}