File: Utilities\SubsetStream.cs
Web Access
Project: src\src\Microsoft.ML.Core\Microsoft.ML.Core.csproj (Microsoft.ML.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.IO;
using Microsoft.ML.Runtime;
 
namespace Microsoft.ML.Internal.Utilities
{
    /// <summary>
    /// Returns a "view" stream, which appears to be a possibly truncated
    /// version of the stream. Reads on this containing stream will also
    /// advance the wrapped stream. If truncated, reads will not progress
    /// beyond the indicated length, and writes will fail beyond the
    /// indicated length. This stream supports seeking (and associated
    /// operations) if the underlying stream supports seeking, where it is
    /// supposed that the returned <c>SubsetStream</c> instance has position
    /// 0 during creation, corresponding to whatever the position of the
    /// enclosed stream was during creation, so this stream will act as an
    /// "offset" version of the enclosed stream. As this is intended to
    /// operate over a subset of a stream, during closing or disposal of the
    /// subset stream, the underlying stream will always remain open and
    /// undisposed.
    /// </summary>
    [BestFriend]
    internal sealed class SubsetStream : Stream
    {
        private readonly Stream _stream;
        // The position of the stream.
        private readonly long _offset;
        // Negative value if unbounded.
        private long _remaining;
 
        /// <summary>
        /// Construct the view stream.
        /// </summary>
        /// <param name="stream">The underlying stream</param>
        /// <param name="length">The maximum length this containing
        /// stream should appear to have, or null if unbounded</param>
        public SubsetStream(Stream stream, long? length = null)
        {
            Contracts.AssertValue(stream);
            Contracts.Assert(!length.HasValue || length >= 0);
            _stream = stream;
            _remaining = length.GetValueOrDefault(-1);
 
            try
            {
                _offset = stream.Position;
            }
            catch (NotSupportedException)
            {
                // Doesn't matter in this case anyway. Any operations depending
                // on _offset should fail, assuming this is a "proper" implementation
                // of stream.
                _offset = -1;
            }
        }
 
        public override int Read(byte[] buffer, int offset, int count)
        {
            if (_remaining < 0)
                return _stream.Read(buffer, offset, count);
            if (count > _remaining)
                count = (int)_remaining;
            int retval = _stream.Read(buffer, offset, count);
            _remaining -= retval;
            return retval;
        }
 
        public override void SetLength(long value)
        {
            _stream.SetLength(value + _offset);
        }
 
        public override long Seek(long offset, SeekOrigin origin)
        {
            long oldPosition = Position;
            long newPosition;
            if (origin == SeekOrigin.End)
                newPosition = _stream.Seek(offset + Length + _offset, SeekOrigin.Begin) - _offset;
            else
                newPosition = _stream.Seek(offset + _offset, origin) - _offset;
            if (_remaining >= 0)
                _remaining -= newPosition - oldPosition;
            return newPosition;
        }
 
        public override bool CanRead { get { return _stream.CanRead; } }
        public override bool CanWrite { get { return _stream.CanWrite; } }
        public override bool CanTimeout { get { return _stream.CanTimeout; } }
        public override bool CanSeek { get { return _stream.CanSeek; } }
 
        public override long Position
        {
            get { return _stream.Position - _offset; }
            set { Seek(value, SeekOrigin.Begin); }
        }
 
        public override long Length
        {
            get
            {
                // This may fail with a not supported operation, due to
                // the underlying stream not supporting these operations.
                // But that is fine as this is the expected behavior for
                // this code as well.
                if (_remaining < 0)
                    return _stream.Length - _offset;
                return Position + _remaining;
            }
        }
 
        public override void Flush()
        {
            _stream.Flush();
        }
 
        public override void Write(byte[] buffer, int offset, int count)
        {
            if (_remaining >= 0 && count > _remaining)
                throw Contracts.Except("cannot write {0} bytes to stream bounded at {1} bytes", count, _remaining);
            _stream.Write(buffer, offset, count);
            if (_remaining >= 0)
                _remaining -= count;
        }
    }
}