Cosmos/source/Cosmos.System/FileSystem/FAT/FatStream.cs

302 lines
10 KiB
C#

//#define COSMOSDEBUG
using System;
using System.IO;
using Cosmos.Common;
using Cosmos.Debug.Kernel;
using Cosmos.System.FileSystem.FAT.Listing;
namespace Cosmos.System.FileSystem.FAT
{
internal class FatStream : Stream
{
private readonly FatDirectoryEntry mDirectoryEntry;
private readonly FatFileSystem mFS;
protected byte[] mReadBuffer;
//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 ulong[] mFatTable;
protected ulong? mReadBufferPosition;
protected ulong mPosition;
private ulong mSize;
public FatStream(FatDirectoryEntry aEntry)
{
Global.mFileSystemDebugger.SendInternal("FatStream.Ctor");
mDirectoryEntry = aEntry;
mFS = mDirectoryEntry.GetFileSystem();
mSize = mDirectoryEntry.mSize;
Global.mFileSystemDebugger.SendInternal("mSize =");
Global.mFileSystemDebugger.SendInternal(mSize.ToString());
// We get always the FatTable if the file is empty too
mFatTable = mDirectoryEntry.GetFatTable();
// What to do if this should happen? Throw exception?
if (mFatTable == null)
{
Global.mFileSystemDebugger.SendInternal("FatTable got but it is null!");
}
}
public override bool CanSeek
{
get
{
return true;
}
}
public override bool CanRead
{
get
{
return true;
}
}
public override bool CanWrite
{
get
{
return true;
}
}
public sealed override long Length
{
get
{
if (mDirectoryEntry == null)
{
throw new NullReferenceException("The stream does not currently have an open entry.");
}
Global.mFileSystemDebugger.SendInternal($"FatStream.get_Length : Length = {(long)mSize}");
return (long)mSize;
}
}
public override long Position
{
get
{
Global.mFileSystemDebugger.SendInternal("FatStream.get_Position:");
Global.mFileSystemDebugger.SendInternal("Position =");
Global.mFileSystemDebugger.SendInternal(mPosition.ToString());
return (long)mPosition;
}
set
{
if (value < 0L)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
Global.mFileSystemDebugger.SendInternal("FatStream.set_Position:");
Global.mFileSystemDebugger.SendInternal("Position =");
Global.mFileSystemDebugger.SendInternal(mPosition.ToString());
mPosition = (ulong)value;
}
}
public override int Read(byte[] aBuffer, int aOffset, int aCount)
{
Global.mFileSystemDebugger.SendInternal("FatStream.Read:");
return Read(aBuffer, aOffset, aCount);
}
protected int Read(byte[] aBuffer, long aOffset, long aCount)
{
Global.mFileSystemDebugger.SendInternal("FatStream.Read:");
if (aCount < 0)
{
throw new ArgumentOutOfRangeException("aCount");
}
if (aOffset < 0)
{
throw new ArgumentOutOfRangeException("aOffset");
}
if (aBuffer == null || aBuffer.Length - aOffset < aCount)
{
throw new ArgumentException("Invalid offset length!");
}
if (mFatTable.Length == 0 || mFatTable[0] == 0)
{
// FirstSector can be 0 for 0 length files
return 0;
}
if (mPosition == mDirectoryEntry.mSize)
{
// EOF
return 0;
}
Global.mFileSystemDebugger.SendInternal("aBuffer.Length =");
Global.mFileSystemDebugger.SendInternal((uint)aBuffer.Length);
Global.mFileSystemDebugger.SendInternal("aOffset =");
Global.mFileSystemDebugger.SendInternal((uint)aOffset);
Global.mFileSystemDebugger.SendInternal("aCount = ");
Global.mFileSystemDebugger.SendInternal((uint)aCount);
// reduce count, so that no out of bound exception occurs if not existing
// entry is used in line mFS.ReadCluster(mFatTable[(int)xClusterIdx], xCluster);
ulong xMaxReadableBytes = mDirectoryEntry.mSize - mPosition;
ulong xCount = (ulong)aCount;
if (xCount > xMaxReadableBytes)
{
xCount = xMaxReadableBytes;
}
var xCluster = mFS.NewClusterArray();
uint xClusterSize = mFS.BytesPerCluster;
while (xCount > 0)
{
ulong xClusterIdx = mPosition / xClusterSize;
ulong xPosInCluster = mPosition % xClusterSize;
mFS.Read(mFatTable[(int)xClusterIdx], out xCluster);
long xReadSize;
if (xPosInCluster + xCount > xClusterSize)
{
xReadSize = (long)(xClusterSize - xPosInCluster - 1);
}
else
{
xReadSize = (long)xCount;
}
// no need for a long version, because internal Array.Copy() does a cast down to int, and a range check,
// or we do a semantic change here
Array.Copy(xCluster, (long)xPosInCluster, aBuffer, aOffset, xReadSize);
aOffset += xReadSize;
xCount -= (ulong)xReadSize;
}
mPosition += (ulong)aOffset;
return (int)aOffset;
}
public override void Flush()
{
throw new NotImplementedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
Global.mFileSystemDebugger.SendInternal("FatStream.SetLength:");
Global.mFileSystemDebugger.SendInternal("value =");
Global.mFileSystemDebugger.SendInternal(value.ToString());
mDirectoryEntry.SetSize(value);
mSize = (ulong)value;
}
public override void Write(byte[] aBuffer, int aOffset, int aCount)
{
Global.mFileSystemDebugger.SendInternal("FatStream.Write:");
if (aCount < 0)
{
throw new ArgumentOutOfRangeException(nameof(aCount));
}
if (aOffset < 0)
{
throw new ArgumentOutOfRangeException(nameof(aOffset));
}
if (aBuffer == null || aBuffer.Length - aOffset < aCount)
{
throw new ArgumentException("Invalid offset length!");
}
Write(aBuffer, aOffset, aCount);
}
protected void Write(byte[] aBuffer, long aOffset, long aCount)
{
Global.mFileSystemDebugger.SendInternal("FatStream.Write:");
if (aCount < 0)
{
throw new ArgumentOutOfRangeException(nameof(aCount));
}
if (aOffset < 0)
{
throw new ArgumentOutOfRangeException(nameof(aOffset));
}
if (aBuffer == null || aBuffer.Length - aOffset < aCount)
{
throw new ArgumentException("Invalid offset length!");
}
Global.mFileSystemDebugger.SendInternal("aBuffer.Length =");
Global.mFileSystemDebugger.SendInternal((uint)aBuffer.Length);
Global.mFileSystemDebugger.SendInternal("aOffset =");
Global.mFileSystemDebugger.SendInternal((uint)aOffset);
Global.mFileSystemDebugger.SendInternal("aCount =");
Global.mFileSystemDebugger.SendInternal((uint)aCount);
ulong xCount = (ulong)aCount;
var xCluster = mFS.NewClusterArray();
uint xClusterSize = mFS.BytesPerCluster;
long xTotalLength = (long)(mPosition + xCount);
if (xTotalLength > Length)
{
SetLength(xTotalLength);
}
while (xCount > 0)
{
long xWriteSize;
ulong xClusterIdx = mPosition / xClusterSize;
ulong xPosInCluster = mPosition % xClusterSize;
if (xPosInCluster + xCount > xClusterSize)
{
xWriteSize = (long)(xClusterSize - xPosInCluster - 1);
}
else
{
xWriteSize = (long)xCount;
}
mFS.Read(xClusterIdx, out xCluster);
Global.mFileSystemDebugger.SendInternal("Cluster index =");
Global.mFileSystemDebugger.SendInternal(xClusterIdx.ToString());
Global.mFileSystemDebugger.SendInternal("Cluster write offset =");
Global.mFileSystemDebugger.SendInternal(xPosInCluster.ToString());
Global.mFileSystemDebugger.SendInternal("Write buffer offset =");
Global.mFileSystemDebugger.SendInternal(aOffset.ToString());
Array.Copy(aBuffer, aOffset, xCluster, (long)xPosInCluster, xWriteSize);
mFS.Write(mFatTable[(int)xClusterIdx], xCluster);
Global.mFileSystemDebugger.SendInternal("Data written");
aOffset += xWriteSize;
xCount -= (ulong)xWriteSize;
}
mPosition += (ulong)aOffset;
}
}
}