mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-09-23 07:14:57 +00:00
DataSource is a Stream type now
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
180
SabreTools.Serialization/Wrappers/ViewStream.cs
Normal file
180
SabreTools.Serialization/Wrappers/ViewStream.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user