diff --git a/SabreTools.Serialization/Wrappers/DataSource.cs b/SabreTools.Serialization/Wrappers/DataSource.cs deleted file mode 100644 index 17745e0a..00000000 --- a/SabreTools.Serialization/Wrappers/DataSource.cs +++ /dev/null @@ -1,227 +0,0 @@ -using System; -using System.IO; -using SabreTools.IO.Extensions; - -namespace SabreTools.Serialization.Wrappers -{ - /// - /// Represents the data source backing the wrapper - /// - public class DataSource - { - #region Properties - - /// - /// Filename from the source, if possible - /// - /// String representing the filename on success, null otherwise - /// This only works if the source was a - public string? Filename - { - get - { - // Only streams can have a filename - if (_dataSourceType != DataSourceType.Stream) - return null; - - // Only file streams can have a filename - if (_streamData == null || _streamData is not FileStream fs) - return null; - - // Return the name - return fs.Name; - } - } - - /// - /// Usable length of the underlying data - /// - /// The usable length on success, -1 on error - public long Length - { - get - { - return _dataSourceType switch - { - DataSourceType.ByteArray => _byteArrayData!.Length - _initialPosition, - DataSourceType.Stream => _streamData!.Length - _initialPosition, - - // Everything else is invalid - _ => -1, - }; - } - } - - #endregion - - #region Instance Variables - - /// - /// Source of the original data - /// - private readonly DataSourceType _dataSourceType = DataSourceType.UNKNOWN; - - /// - /// Lock object for reading from the source - /// - private readonly object _streamDataLock = new(); - - /// - /// Initial position of the data source - /// - /// Populated for both and - protected long _initialPosition = 0; - - /// - /// Source byte array data - /// - /// This is only populated if is - protected byte[]? _byteArrayData = null; - - /// - /// Source Stream data - /// - /// This is only populated if is - protected Stream? _streamData = null; - - #endregion - - #region Constructors - - /// - /// Construct a new DataSource from a Stream - /// - /// - public DataSource(Stream data) - { - _dataSourceType = DataSourceType.Stream; - _initialPosition = data.Position; - _streamData = data; - } - - /// - /// Construct a new DataSource from a byte array - /// - /// - /// - public DataSource(byte[] data, int offset) - { - _dataSourceType = DataSourceType.ByteArray; - _initialPosition = offset; - _byteArrayData = data; - } - - #endregion - - #region Data - - /// - /// Return the underlying data as a stream - /// - /// Stream representing the data source on success, null on error - public Stream? AsStream() - { - return _dataSourceType switch - { - DataSourceType.ByteArray => new MemoryStream(_byteArrayData!, (int)_initialPosition, (int)Length), - DataSourceType.Stream => _streamData, // TODO: This should be wrapped better - _ => null, - }; - } - - /// - /// Read data from the source - /// - /// Position in the source to read from - /// Length of the requested data - /// Byte array containing the requested data, null on error - public byte[]? Read(int position, int length) - { - // Validate the data source - if (!IsValid()) - return null; - - // Validate the requested segment - if (!SegmentValid(position, length)) - return null; - - try - { - // Read and return the data - byte[]? sectionData = null; - switch (_dataSourceType) - { - case DataSourceType.ByteArray: - sectionData = new byte[length]; - Array.Copy(_byteArrayData!, _initialPosition + position, sectionData, 0, length); - break; - - case DataSourceType.Stream: - lock (_streamDataLock) - { - long currentLocation = _streamData!.Position; - _streamData.Seek(_initialPosition + position, SeekOrigin.Begin); - sectionData = _streamData.ReadBytes(length); - _streamData.Seek(currentLocation, SeekOrigin.Begin); - break; - } - } - - return sectionData; - - } - catch - { - // Absorb the error - return null; - } - } - - /// - /// Validate the backing data source - /// - /// True if the data source is valid, false otherwise - private bool IsValid() - { - return _dataSourceType switch - { - // Byte array data requires both a valid array and offset - DataSourceType.ByteArray => _byteArrayData != null && _initialPosition >= 0, - - // Stream data requires both a valid stream - DataSourceType.Stream => _streamData != null && _initialPosition >= 0 && _streamData.CanRead && _streamData.CanSeek, - - // Everything else is invalid - _ => false, - }; - } - - /// - /// Check if a data segment is valid in the data source - /// - /// Position in the source - /// Length of the data to check - /// True if the positional data is valid, false otherwise - private bool SegmentValid(int position, int length) - { - // Validate the data souece - if (!IsValid()) - return false; - - // If we have an invalid position - if (position < 0 || position >= Length) - return false; - - return _dataSourceType switch - { - DataSourceType.ByteArray => _initialPosition + position + length <= _byteArrayData!.Length, - DataSourceType.Stream => _initialPosition + position + length <= _streamData!.Length, - - // Everything else is invalid - _ => false, - }; - } - - #endregion - } -} \ No newline at end of file diff --git a/SabreTools.Serialization/Wrappers/Enums.cs b/SabreTools.Serialization/Wrappers/Enums.cs deleted file mode 100644 index 6609d876..00000000 --- a/SabreTools.Serialization/Wrappers/Enums.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace SabreTools.Serialization.Wrappers -{ - /// - /// Location that the data originated from - /// - public enum DataSourceType - { - /// - /// Unknown origin / testing - /// - UNKNOWN = 0, - - /// - /// Byte array with offset - /// - ByteArray = 1, - - /// - /// Stream - /// - Stream = 2, - } -} diff --git a/SabreTools.Serialization/Wrappers/ViewStream.cs b/SabreTools.Serialization/Wrappers/ViewStream.cs new file mode 100644 index 00000000..db401d54 --- /dev/null +++ b/SabreTools.Serialization/Wrappers/ViewStream.cs @@ -0,0 +1,180 @@ +using System; +using System.IO; + +namespace SabreTools.Serialization.Wrappers +{ + /// + /// Represents the data source backing the wrapper + /// + public class ViewStream : Stream + { + #region Properties + + /// + /// Filename from the source, if possible + /// + /// String representing the filename on success, null otherwise + /// This only works if the source was a + public string? Filename + { + get + { + // Only file streams can have a filename + if (_streamData == null || _streamData is not FileStream fs) + return null; + + // Return the name + return fs.Name; + } + } + + /// + public override long Length => _length; + + /// + public override long Position + { + get { return _streamData.Position - _initialPosition; } + set { _streamData.Position = value + _initialPosition; } + } + + #endregion + + #region Instance Variables + + /// + /// Lock object for reading from the source + /// + private readonly object _streamDataLock = new(); + + /// + /// Initial position of the data source + /// + protected long _initialPosition; + + /// + /// Usable length of the underlying data + /// + protected long _length; + + /// + /// Source Stream data + /// + protected Stream _streamData; + + #endregion + + #region Constructors + + /// + /// Construct a new ViewStream from a Stream + /// + public ViewStream(Stream data, long offset, long length) + { + _streamData = data; + _initialPosition = offset; + _length = length; + } + + /// + /// Construct a new ViewStream from a byte array + /// + public ViewStream(byte[] data, long offset, long length) + { + _streamData = new MemoryStream(data, (int)offset, (int)length); + _initialPosition = 0; + _length = length; + } + + #endregion + + #region Data + + /// + /// Check if a data segment is valid in the data source + /// + /// Position in the source + /// Length of the data to check + /// True if the positional data is valid, false otherwise + public bool SegmentValid(int offset, int count) + { + // If we have an invalid position + if (offset < 0 || offset >= Length) + return false; + + return _initialPosition + offset + count <= Length; + } + + #endregion + + #region Stream Implementations + + /// + public override bool CanRead => true; + + /// + public override bool CanWrite => false; + + /// + public override bool CanSeek => _streamData.CanSeek; + + /// + public override void Flush() => _streamData.Flush(); + + /// + public override int Read(byte[] buffer, int offset, int count) + { + // Validate the requested segment + if (!SegmentValid(offset, count)) + return 0; + + try + { + // Correct the read offset + offset += (int)_initialPosition; + lock (_streamDataLock) + { + return _streamData.Read(buffer, offset, count); + } + + } + catch + { + // Absorb the error + return 0; + } + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + // Handle the "seek" + switch (origin) + { + case SeekOrigin.Begin: Position = offset; break; + case SeekOrigin.Current: Position += offset; break; + case SeekOrigin.End: Position = _length + offset - 1; break; + default: throw new ArgumentException($"Invalid value for {nameof(origin)}"); + } + ; + + // Handle out-of-bounds seeks + if (Position < 0) + Position = 0; + else if (Position >= _length) + Position = _length - 1; + + return Position; + } + + /// + public override void SetLength(long value) + => throw new NotImplementedException(); + + /// + public override void Write(byte[] buffer, int offset, int count) + => throw new NotImplementedException(); + + #endregion + } +} \ No newline at end of file diff --git a/SabreTools.Serialization/Wrappers/WrapperBase.cs b/SabreTools.Serialization/Wrappers/WrapperBase.cs index ca93c334..abce2e33 100644 --- a/SabreTools.Serialization/Wrappers/WrapperBase.cs +++ b/SabreTools.Serialization/Wrappers/WrapperBase.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; +using SabreTools.IO.Extensions; using SabreTools.Serialization.Interfaces; namespace SabreTools.Serialization.Wrappers @@ -22,13 +23,15 @@ namespace SabreTools.Serialization.Wrappers #region Properties - /// - public Stream? DataSourceStream => _dataSource.AsStream(); + /// + /// Data source as a stream + /// + public Stream? DataSourceStream => _dataSource; - /// + /// public string? Filename => _dataSource.Filename; - /// + /// public long Length => _dataSource.Length; #endregion @@ -38,7 +41,7 @@ namespace SabreTools.Serialization.Wrappers /// /// Source of the original data /// - private readonly DataSource _dataSource; + private readonly ViewStream _dataSource; #if NETCOREAPP /// @@ -75,7 +78,7 @@ namespace SabreTools.Serialization.Wrappers if (offset < 0 || offset >= data.Length) throw new ArgumentOutOfRangeException(nameof(offset)); - _dataSource = new DataSource(data, offset); + _dataSource = new ViewStream(data, offset, data.Length - offset); } /// @@ -88,7 +91,7 @@ namespace SabreTools.Serialization.Wrappers if (!data.CanSeek || !data.CanRead) throw new ArgumentOutOfRangeException(nameof(data)); - _dataSource = new DataSource(data); + _dataSource = new ViewStream(data, data.Position, data.Length - data.Position); } #endregion @@ -102,7 +105,28 @@ namespace SabreTools.Serialization.Wrappers /// Length of the requested data /// Byte array containing the requested data, null on error public byte[]? ReadFromDataSource(int position, int length) - => _dataSource.Read(position, length); + { + // Validate the requested segment + if (!_dataSource.SegmentValid(position, length)) + return null; + + try + { + long currentLocation = _dataSource.Position; + + _dataSource.Seek(position, SeekOrigin.Begin); + byte[] sectionData = _dataSource.ReadBytes(length); + _dataSource.Seek(currentLocation, SeekOrigin.Begin); + + return sectionData; + + } + catch + { + // Absorb the error + return null; + } + } /// /// Read string data from the source