DataSource is a Stream type now

This commit is contained in:
Matt Nadareski
2025-08-23 13:18:46 -04:00
parent c87a456a46
commit 70fc686249
4 changed files with 212 additions and 258 deletions

View File

@@ -1,227 +0,0 @@
using System;
using System.IO;
using SabreTools.IO.Extensions;
namespace SabreTools.Serialization.Wrappers
{
/// <summary>
/// Represents the data source backing the wrapper
/// </summary>
public class DataSource
{
#region Properties
/// <summary>
/// Filename from the source, if possible
/// </summary>
/// <returns>String representing the filename on success, null otherwise</returns>
/// <remarks>This only works if the source was a <see cref="FileStream"/></remarks>
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;
}
}
/// <summary>
/// Usable length of the underlying data
/// </summary>
/// <returns>The usable length on success, -1 on error</returns>
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
/// <summary>
/// Source of the original data
/// </summary>
private readonly DataSourceType _dataSourceType = DataSourceType.UNKNOWN;
/// <summary>
/// Lock object for reading from the source
/// </summary>
private readonly object _streamDataLock = new();
/// <summary>
/// Initial position of the data source
/// </summary>
/// <remarks>Populated for both <see cref="DataSourceType.ByteArray"/> and <see cref="DataSourceType.Stream"/></remarks>
protected long _initialPosition = 0;
/// <summary>
/// Source byte array data
/// </summary>
/// <remarks>This is only populated if <see cref="_dataSourceType"/> is <see cref="DataSourceType.ByteArray"/></remarks>
protected byte[]? _byteArrayData = null;
/// <summary>
/// Source Stream data
/// </summary>
/// <remarks>This is only populated if <see cref="_dataSourceType"/> is <see cref="DataSourceType.Stream"/></remarks>
protected Stream? _streamData = null;
#endregion
#region Constructors
/// <summary>
/// Construct a new DataSource from a Stream
/// </summary>
/// <param name="data"></param>
public DataSource(Stream data)
{
_dataSourceType = DataSourceType.Stream;
_initialPosition = data.Position;
_streamData = data;
}
/// <summary>
/// Construct a new DataSource from a byte array
/// </summary>
/// <param name="data"></param>
/// <param name="offset"></param>
public DataSource(byte[] data, int offset)
{
_dataSourceType = DataSourceType.ByteArray;
_initialPosition = offset;
_byteArrayData = data;
}
#endregion
#region Data
/// <summary>
/// Return the underlying data as a stream
/// </summary>
/// <returns>Stream representing the data source on success, null on error</returns>
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,
};
}
/// <summary>
/// Read data from the source
/// </summary>
/// <param name="position">Position in the source to read from</param>
/// <param name="length">Length of the requested data</param>
/// <returns>Byte array containing the requested data, null on error</returns>
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;
}
}
/// <summary>
/// Validate the backing data source
/// </summary>
/// <returns>True if the data source is valid, false otherwise</returns>
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,
};
}
/// <summary>
/// Check if a data segment is valid in the data source
/// </summary>
/// <param name="position">Position in the source</param>
/// <param name="length">Length of the data to check</param>
/// <returns>True if the positional data is valid, false otherwise</returns>
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
}
}

View File

@@ -1,23 +0,0 @@
namespace SabreTools.Serialization.Wrappers
{
/// <summary>
/// Location that the data originated from
/// </summary>
public enum DataSourceType
{
/// <summary>
/// Unknown origin / testing
/// </summary>
UNKNOWN = 0,
/// <summary>
/// Byte array with offset
/// </summary>
ByteArray = 1,
/// <summary>
/// Stream
/// </summary>
Stream = 2,
}
}

View File

@@ -0,0 +1,180 @@
using System;
using System.IO;
namespace SabreTools.Serialization.Wrappers
{
/// <summary>
/// Represents the data source backing the wrapper
/// </summary>
public class ViewStream : Stream
{
#region Properties
/// <summary>
/// Filename from the source, if possible
/// </summary>
/// <returns>String representing the filename on success, null otherwise</returns>
/// <remarks>This only works if the source was a <see cref="FileStream"/></remarks>
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;
}
}
/// <inheritdoc/>
public override long Length => _length;
/// <inheritdoc/>
public override long Position
{
get { return _streamData.Position - _initialPosition; }
set { _streamData.Position = value + _initialPosition; }
}
#endregion
#region Instance Variables
/// <summary>
/// Lock object for reading from the source
/// </summary>
private readonly object _streamDataLock = new();
/// <summary>
/// Initial position of the data source
/// </summary>
protected long _initialPosition;
/// <summary>
/// Usable length of the underlying data
/// </summary>
protected long _length;
/// <summary>
/// Source Stream data
/// </summary>
protected Stream _streamData;
#endregion
#region Constructors
/// <summary>
/// Construct a new ViewStream from a Stream
/// </summary>
public ViewStream(Stream data, long offset, long length)
{
_streamData = data;
_initialPosition = offset;
_length = length;
}
/// <summary>
/// Construct a new ViewStream from a byte array
/// </summary>
public ViewStream(byte[] data, long offset, long length)
{
_streamData = new MemoryStream(data, (int)offset, (int)length);
_initialPosition = 0;
_length = length;
}
#endregion
#region Data
/// <summary>
/// Check if a data segment is valid in the data source
/// </summary>
/// <param name="offset">Position in the source</param>
/// <param name="count">Length of the data to check</param>
/// <returns>True if the positional data is valid, false otherwise</returns>
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
/// <inheritdoc/>
public override bool CanRead => true;
/// <inheritdoc/>
public override bool CanWrite => false;
/// <inheritdoc/>
public override bool CanSeek => _streamData.CanSeek;
/// <inheritdoc/>
public override void Flush() => _streamData.Flush();
/// <inheritdoc/>
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;
}
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
public override void SetLength(long value)
=> throw new NotImplementedException();
/// <inheritdoc/>
public override void Write(byte[] buffer, int offset, int count)
=> throw new NotImplementedException();
#endregion
}
}

View File

@@ -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
/// <inheritdoc cref="DataSource.AsStream"/>
public Stream? DataSourceStream => _dataSource.AsStream();
/// <summary>
/// Data source as a stream
/// </summary>
public Stream? DataSourceStream => _dataSource;
/// <inheritdoc cref="DataSource.Filename"/>
/// <inheritdoc cref="ViewStream.Filename"/>
public string? Filename => _dataSource.Filename;
/// <inheritdoc cref="DataSource.Length"/>
/// <inheritdoc cref="ViewStream.Length"/>
public long Length => _dataSource.Length;
#endregion
@@ -38,7 +41,7 @@ namespace SabreTools.Serialization.Wrappers
/// <summary>
/// Source of the original data
/// </summary>
private readonly DataSource _dataSource;
private readonly ViewStream _dataSource;
#if NETCOREAPP
/// <summary>
@@ -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);
}
/// <summary>
@@ -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
/// <param name="length">Length of the requested data</param>
/// <returns>Byte array containing the requested data, null on error</returns>
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;
}
}
/// <summary>
/// Read string data from the source