//#define COSMOSDEBUG
using System;
using System.IO;
using Cosmos.System.FileSystem.FAT.Listing;
namespace Cosmos.System.FileSystem.FAT
{
///
/// FAT stream class.
///
internal class FatStream : Stream
{
protected byte[] mReadBuffer;
protected long? mReadBufferPosition;
protected long mPosition;
private readonly FatDirectoryEntry mDirectoryEntry;
private readonly FatFileSystem mFS;
//TODO: In future we might read this in as needed rather than
// all at once. This structure will also consume 2% of file size in RAM
// (for default cluster size of 2kb, ie 4 bytes per cluster)
// so we might consider a way to flush it and only keep parts.
// Example, a 100 MB file will require 2MB for this structure. That is
// probably acceptable for the mid term future.
private uint[] mFatTable;
private long mSize;
///
/// Initializes a new instance of the class.
///
/// A directory entry to open stream to.
/// Thrown when aEntry is null / corrupted.
/// Thrown when FAT table not found or null / out of memory / invalid aData size.
/// Thrown when the size of the chain is less then zero.
/// Thrown when the number of clusters in the FAT entry is greater than Int32.MaxValue
/// Thrown when FAT type is unknown.
/// Thrown when FAT type is unknown.
public FatStream(FatDirectoryEntry aEntry)
{
mDirectoryEntry = aEntry ?? throw new ArgumentNullException(nameof(aEntry));
mFS = aEntry.GetFileSystem();
mFatTable = aEntry.GetFatTable();
mSize = aEntry.mSize;
if (mFatTable == null)
{
throw new Exception("The fat chain returned for the directory entry was null.");
}
}
///
/// Check if can seek the stream.
/// Returns true.
///
public override bool CanSeek => true;
///
/// Check if can read the stream.
/// Returns true.
///
public override bool CanRead => true;
///
/// Check if can write the stream.
/// Returns true.
///
public override bool CanWrite => true;
///
/// Get the length of the stream.
///
public sealed override long Length
{
get
{
Global.mFileSystemDebugger.SendInternal("-- FatStream.get_Length --");
Global.mFileSystemDebugger.SendInternal("Length =");
Global.mFileSystemDebugger.SendInternal(mSize);
return mSize;
}
}
///
/// Get and set the position in the stream.
///
/// (set) Thrown when value is smaller than 0L.
public override long Position
{
get
{
Global.mFileSystemDebugger.SendInternal("-- FatStream.get_Position --");
Global.mFileSystemDebugger.SendInternal("Position =");
Global.mFileSystemDebugger.SendInternal(mPosition);
return mPosition;
}
set
{
if (value < 0L)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
Global.mFileSystemDebugger.SendInternal("-- FatStream.set_Position --");
Global.mFileSystemDebugger.SendInternal("Position =");
Global.mFileSystemDebugger.SendInternal(mPosition);
mPosition = value;
}
}
///
/// Flush stream.
/// Not implemented.
///
/// Always thrown.
public override void Flush()
{
throw new NotImplementedException();
}
///
/// Seek the position in the stream.
///
/// The offset.
/// The position in the stream to start the seek from.
/// Possible values:
///
/// - SeekOrigin.Begin
/// - SeekOrigin.Current
/// - SeekOrigin.End
///
///
/// long value.
/// Thrown when invalid seek position in passed.
public override long Seek(long offset, SeekOrigin origin)
{
switch (origin)
{
case SeekOrigin.Begin:
Position = offset;
break;
case SeekOrigin.Current:
Position += offset;
break;
case SeekOrigin.End:
Position = Length + offset;
break;
default:
throw new NotImplementedException();
}
return Position;
}
///
/// Set the length of the stream.
///
/// Stream length.
///
///
/// - Thrown when trying to change root directory matadata.
/// - data size invalid.
/// - invalid directory entry type.
/// - FAT table not found.
/// - out of memory.
/// - invalid aData size.
///
///
/// Thrown when data lenght is greater then Int32.MaxValue.
///
///
/// - Thrown when entrys aValue is null.
/// - Thrown when entrys aData is null.
/// - Out of memory.
///
///
/// Thrown on fatal error (contact support).
/// Thrown on fatal error (contact support).
/// Thrown when the data in aValue is corrupted.
///
///
/// - Thrown when the data length is 0 or greater then Int32.MaxValue.
/// - Entrys matadata offset value is invalid.
/// - Thrown when aSize is smaller than 0.
///
///
///
///
/// - Thrown when aName is null or empty string.
/// - aData length is 0.
/// - FAT type is unknown.
///
///
/// Thrown when FAT type is unknown.
public override void SetLength(long value)
{
Global.mFileSystemDebugger.SendInternal("-- FatStream.SetLength --");
Global.mFileSystemDebugger.SendInternal("value =");
Global.mFileSystemDebugger.SendInternal(value);
Global.mFileSystemDebugger.SendInternal("mFatTable.Length =");
Global.mFileSystemDebugger.SendInternal(mFatTable.Length);
mDirectoryEntry.SetSize(value);
mSize = value;
mFatTable = mDirectoryEntry.GetFatTable();
}
///
/// Read data from stream.
///
/// A destination buffer.
/// A offset in the buffer.
/// Number of bytes to read.
/// int value.
/// Thrown if aCount or aOffset is smaller than 0 or bigger than Int32.MaxValue.
/// Thrown on invalid offset length.
/// Thrown when data lenght is greater then Int32.MaxValue.
/// Thrown when data size invalid.
/// Thrown when aBuffer is null / memory error.
/// Thrown on fatal error.
/// Thrown on fatal error.
/// Thrown on memory error.
public override int Read(byte[] aBuffer, int aOffset, int aCount)
{
Global.mFileSystemDebugger.SendInternal("-- FatStream.Read --");
Global.mFileSystemDebugger.SendInternal("aBuffer.Length = " + aBuffer.Length);
Global.mFileSystemDebugger.SendInternal("aOffset = " + aOffset);
Global.mFileSystemDebugger.SendInternal("aCount = " + aCount);
if (aCount < 0)
{
throw new ArgumentOutOfRangeException(nameof(aCount));
}
if (aOffset < 0)
{
throw new ArgumentOutOfRangeException(nameof(aOffset));
}
if (aCount + aOffset > aBuffer?.Length)
{
throw new ArgumentException("Invalid offset length.");
}
if (mFatTable.Length == 0 || mFatTable[0] == 0)
{
return 0;
}
if (mPosition >= mDirectoryEntry.mSize)
{
return 0;
}
long xMaxReadableBytes = mDirectoryEntry.mSize - mPosition;
long xCount = aCount;
long xOffset = aOffset;
if (xCount > xMaxReadableBytes)
{
xCount = xMaxReadableBytes;
}
long xClusterSize = mFS.BytesPerCluster;
while (xCount > 0)
{
long xClusterIdx = mPosition / xClusterSize;
long xPosInCluster = mPosition % xClusterSize;
mFS.Read(mFatTable[(int)xClusterIdx], out byte[] xCluster);
long xReadSize;
if (xPosInCluster + xCount > xClusterSize)
{
xReadSize = xClusterSize - xPosInCluster; // -1
}
else
{
xReadSize = xCount;
}
Global.mFileSystemDebugger.SendInternal("xClusterIdx = " + xClusterIdx);
Global.mFileSystemDebugger.SendInternal("xPosInCluster = " + xPosInCluster);
Global.mFileSystemDebugger.SendInternal("xReadSize = " + xReadSize);
Array.Copy(xCluster, xPosInCluster, aBuffer, xOffset, xReadSize);
xOffset += xReadSize;
xCount -= xReadSize;
mPosition += xReadSize;
}
return (int)xOffset;
}
///
/// Write to stream.
///
/// A source buffer.
/// A offset in the buffer.
/// Number of bytes to read.
///
///
/// - Thrown when trying to change root directory matadata.
/// - data size invalid.
/// - invalid directory entry type.
/// - FAT table not found.
/// - out of memory.
/// - invalid aData size.
///
///
/// Thrown when data lenght is greater then Int32.MaxValue.
///
///
/// - Thrown when entrys aValue is null.
/// - Thrown when entrys aData is null.
/// - Thrown when aBuffer is null.
/// - Out of memory.
///
///
/// Thrown on fatal error (contact support).
/// Thrown on fatal error (contact support).
/// Thrown when the data in aValue is corrupted.
///
///
/// - Thrown if aCount or aOffset is smaller than 0 or bigger than Int32.MaxValue.
/// - Thrown when the data length is 0 or greater then Int32.MaxValue.
/// - Entrys matadata offset value is invalid.
/// - Thrown when aSize is smaller than 0.
///
///
///
///
/// - Thrown on invalid offset length.
/// - Thrown when aName is null or empty string.
/// - aData length is 0.
/// - FAT type is unknown.
///
///
/// Thrown when FAT type is unknown.
public override void Write(byte[] aBuffer, int aOffset, int aCount)
{
Global.mFileSystemDebugger.SendInternal("-- FatStream.Write --");
Global.mFileSystemDebugger.SendInternal("aBuffer.Length =");
Global.mFileSystemDebugger.SendInternal(aBuffer.Length);
Global.mFileSystemDebugger.SendInternal("aOffset =");
Global.mFileSystemDebugger.SendInternal(aOffset);
Global.mFileSystemDebugger.SendInternal("aCount =");
Global.mFileSystemDebugger.SendInternal(aCount);
if (aCount < 0)
{
throw new ArgumentOutOfRangeException(nameof(aCount));
}
if (aOffset < 0)
{
throw new ArgumentOutOfRangeException(nameof(aOffset));
}
if (aOffset + aCount > aBuffer.Length)
{
throw new ArgumentException("Invalid offset length.");
}
long xCount = aCount;
long xClusterSize = mFS.BytesPerCluster;
long xOffset = aOffset;
long xTotalLength = (mPosition + xCount);
if (xTotalLength > Length)
{
SetLength(xTotalLength);
}
while (xCount > 0)
{
long xWriteSize;
long xClusterIdx = mPosition / xClusterSize;
long xPosInCluster = mPosition % xClusterSize;
if (xPosInCluster + xCount > xClusterSize)
{
xWriteSize = xClusterSize - xPosInCluster;
}
else
{
xWriteSize = xCount;
}
mFS.Read(mFatTable[xClusterIdx], out byte[] xCluster);
Array.Copy(aBuffer, aOffset, xCluster, (int)xPosInCluster, (int)xWriteSize);
mFS.Write(mFatTable[xClusterIdx], xCluster);
Global.mFileSystemDebugger.SendInternal("xClusterIdx =");
Global.mFileSystemDebugger.SendInternal(xClusterIdx);
Global.mFileSystemDebugger.SendInternal("xPosInCluster =");
Global.mFileSystemDebugger.SendInternal(xPosInCluster);
Global.mFileSystemDebugger.SendInternal("xWriteSize =");
Global.mFileSystemDebugger.SendInternal(xWriteSize);
xOffset += xWriteSize;
xCount -= xWriteSize;
aOffset += (int)xWriteSize;
mPosition += xWriteSize;
}
}
}
}