497 lines
16 KiB
C++
Executable File
497 lines
16 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "core/fileStream.h"
|
|
#include "platform/platform.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// FileStream methods...
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
FileStream::FileStream()
|
|
{
|
|
// initialize the file stream
|
|
init();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
FileStream::~FileStream()
|
|
{
|
|
// make sure the file stream is closed
|
|
close();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool FileStream::hasCapability(const Capability i_cap) const
|
|
{
|
|
return(0 != (U32(i_cap) & mStreamCaps));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
U32 FileStream::getPosition() const
|
|
{
|
|
AssertFatal(0 != mStreamCaps, "FileStream::getPosition: the stream isn't open");
|
|
AssertFatal(true == hasCapability(StreamPosition), "FileStream::getPosition(): lacks positioning capability");
|
|
|
|
// return the position inside the buffer if its valid, otherwise return the underlying file position
|
|
return((BUFFER_INVALID != mBuffHead) ? mBuffPos : mFile.getPosition());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool FileStream::setPosition(const U32 i_newPosition)
|
|
{
|
|
AssertFatal(0 != mStreamCaps, "FileStream::setPosition: the stream isn't open");
|
|
AssertFatal(true == hasCapability(StreamPosition), "FileStream::setPosition: lacks positioning capability");
|
|
|
|
// if the buffer is valid, test the new position against the bounds of the buffer
|
|
if ((BUFFER_INVALID != mBuffHead) && (i_newPosition >= mBuffHead) && (i_newPosition <= mBuffTail))
|
|
{
|
|
// set the position and return
|
|
mBuffPos = i_newPosition;
|
|
|
|
if(mBuffPos < mBuffTail)
|
|
Stream::setStatus(Ok);
|
|
|
|
return(true);
|
|
}
|
|
// otherwise the new position lies in some block not in memory
|
|
else
|
|
{
|
|
// flush the buffer if its dirty
|
|
if (true == mDirty)
|
|
flush();
|
|
// and clear out the state of the file stream
|
|
clearBuffer();
|
|
|
|
// and set the new position
|
|
mFile.setPosition((S32)i_newPosition);
|
|
// update the stream to reflect the file's state
|
|
setStatus();
|
|
// taking end-of-file into consideration
|
|
if ((S32)EOS == (S32)(mFile.getStatus()))
|
|
mEOF = true;
|
|
// and return good states
|
|
return(Ok == getStatus() || EOS == getStatus());
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
U32 FileStream::getStreamSize()
|
|
{
|
|
AssertWarn(0 != mStreamCaps, "FileStream::getStreamSize: the stream isn't open");
|
|
AssertFatal((BUFFER_INVALID != mBuffHead && true == mDirty) || false == mDirty, "FileStream::getStreamSize: buffer must be valid if its dirty");
|
|
|
|
// the stream size may not match the size on-disk if its been written to...
|
|
if (true == mDirty)
|
|
return(getMax(mFile.getSize(), mBuffTail + 1));
|
|
// otherwise just get the size on disk...
|
|
else
|
|
return(mFile.getSize());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool FileStream::open(const char *i_pFilename, AccessMode i_openMode)
|
|
{
|
|
AssertWarn(0 == mStreamCaps, "FileStream::setPosition: the stream is already open");
|
|
AssertFatal(NULL != i_pFilename, "FileStream::open: NULL filename");
|
|
|
|
// make sure the file stream's state is clean
|
|
clearBuffer();
|
|
|
|
if (File::Ok == mFile.open(i_pFilename, (File::AccessMode)i_openMode))
|
|
{
|
|
setStatus();
|
|
switch (i_openMode)
|
|
{
|
|
case Read:
|
|
mStreamCaps = U32(StreamRead) |
|
|
U32(StreamPosition);
|
|
break;
|
|
case Write:
|
|
case WriteAppend:
|
|
mStreamCaps = U32(StreamWrite) |
|
|
U32(StreamPosition);
|
|
break;
|
|
case ReadWrite:
|
|
mStreamCaps = U32(StreamRead) |
|
|
U32(StreamWrite) |
|
|
U32(StreamPosition);
|
|
break;
|
|
default:
|
|
AssertFatal(false, "FileStream::open: bad access mode");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
setStatus();
|
|
return(false);
|
|
}
|
|
return(true);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void FileStream::close()
|
|
{
|
|
if (Closed == getStatus())
|
|
return;
|
|
|
|
// make sure nothing in the buffer differs from what is on disk
|
|
if (true == mDirty)
|
|
flush();
|
|
// and close the file
|
|
File::Status closeResult = mFile.close();
|
|
|
|
AssertFatal(File::Closed == closeResult, "FileStream::close: close failed");
|
|
|
|
// clear the file stream's state
|
|
init();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool FileStream::flush()
|
|
{
|
|
AssertWarn(0 != mStreamCaps, "FileStream::flush: the stream isn't open");
|
|
AssertFatal(false == mDirty || BUFFER_INVALID != mBuffHead, "FileStream::flush: buffer must be valid if its dirty");
|
|
|
|
// if the buffer is dirty
|
|
if (true == mDirty)
|
|
{
|
|
AssertFatal(true == hasCapability(StreamWrite), "FileStream::flush: a buffer without write-capability should never be dirty");
|
|
// align the file pointer to the buffer head
|
|
if (mBuffHead != mFile.getPosition())
|
|
{
|
|
mFile.setPosition(mBuffHead);
|
|
if (File::Ok != mFile.getStatus() && File::EOS != mFile.getStatus())
|
|
return(false);
|
|
}
|
|
// write contents of the buffer to disk
|
|
U32 blockHead;
|
|
calcBlockHead(mBuffHead, &blockHead);
|
|
mFile.write(mBuffTail - mBuffHead + 1, (char *)mBuffer + (mBuffHead - blockHead));
|
|
// and update the file stream's state
|
|
setStatus();
|
|
if (EOS == getStatus())
|
|
mEOF = true;
|
|
|
|
if (Ok == getStatus() || EOS == getStatus())
|
|
// and update the status of the buffer
|
|
mDirty = false;
|
|
else
|
|
return(false);
|
|
}
|
|
return(true);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool FileStream::_read(const U32 i_numBytes, void *o_pBuffer)
|
|
{
|
|
AssertFatal(0 != mStreamCaps, "FileStream::_read: the stream isn't open");
|
|
AssertFatal(NULL != o_pBuffer || i_numBytes == 0, "FileStream::_read: NULL destination pointer with non-zero read request");
|
|
|
|
if (false == hasCapability(Stream::StreamRead))
|
|
{
|
|
AssertFatal(false, "FileStream::_read: file stream lacks capability");
|
|
Stream::setStatus(IllegalCall);
|
|
return(false);
|
|
}
|
|
|
|
// exit on pre-existing errors
|
|
if (Ok != getStatus())
|
|
return(false);
|
|
|
|
// if a request of non-zero length was made
|
|
if (0 != i_numBytes)
|
|
{
|
|
U8 *pDst = (U8 *)o_pBuffer;
|
|
U32 readSize;
|
|
U32 remaining = i_numBytes;
|
|
U32 bytesRead;
|
|
U32 blockHead;
|
|
U32 blockTail;
|
|
|
|
// check if the buffer has some data in it
|
|
if (BUFFER_INVALID != mBuffHead)
|
|
{
|
|
// copy as much as possible from the buffer into the destination
|
|
readSize = ((mBuffTail + 1) >= mBuffPos) ? (mBuffTail + 1 - mBuffPos) : 0;
|
|
readSize = getMin(readSize, remaining);
|
|
calcBlockHead(mBuffPos, &blockHead);
|
|
dMemcpy(pDst, mBuffer + (mBuffPos - blockHead), readSize);
|
|
// reduce the remaining amount to read
|
|
remaining -= readSize;
|
|
// advance the buffer pointers
|
|
mBuffPos += readSize;
|
|
pDst += readSize;
|
|
|
|
if (mBuffPos > mBuffTail && remaining != 0)
|
|
{
|
|
flush();
|
|
mBuffHead = BUFFER_INVALID;
|
|
if (mEOF == true)
|
|
Stream::setStatus(EOS);
|
|
}
|
|
}
|
|
|
|
// if the request wasn't satisfied by the buffer and the file has more data
|
|
if (false == mEOF && 0 < remaining)
|
|
{
|
|
// flush the buffer if its dirty, since we now need to go to disk
|
|
if (true == mDirty)
|
|
flush();
|
|
|
|
// make sure we know the current read location in the underlying file
|
|
mBuffPos = mFile.getPosition();
|
|
calcBlockBounds(mBuffPos, &blockHead, &blockTail);
|
|
|
|
// check if the data to be read falls within a single block
|
|
if ((mBuffPos + remaining) <= blockTail)
|
|
{
|
|
// fill the buffer from disk
|
|
if (true == fillBuffer(mBuffPos))
|
|
{
|
|
// copy as much as possible from the buffer to the destination
|
|
remaining = getMin(remaining, mBuffTail - mBuffPos + 1);
|
|
dMemcpy(pDst, mBuffer + (mBuffPos - blockHead), remaining);
|
|
// advance the buffer pointer
|
|
mBuffPos += remaining;
|
|
}
|
|
else
|
|
return(false);
|
|
}
|
|
// otherwise the remaining spans multiple blocks
|
|
else
|
|
{
|
|
clearBuffer();
|
|
// read from disk directly into the destination
|
|
mFile.read(remaining, (char *)pDst, &bytesRead);
|
|
setStatus();
|
|
// check to make sure we read as much as expected
|
|
if (Ok == getStatus() || EOS == getStatus())
|
|
{
|
|
// if not, update the end-of-file status
|
|
if (0 != bytesRead && EOS == getStatus())
|
|
{
|
|
Stream::setStatus(Ok);
|
|
mEOF = true;
|
|
}
|
|
}
|
|
else
|
|
return(false);
|
|
}
|
|
}
|
|
}
|
|
return(true);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool FileStream::_write(const U32 i_numBytes, const void *i_pBuffer)
|
|
{
|
|
AssertFatal(0 != mStreamCaps, "FileStream::_write: the stream isn't open");
|
|
AssertFatal(NULL != i_pBuffer || i_numBytes == 0, "FileStream::_write: NULL source buffer pointer on non-zero write request");
|
|
|
|
if (false == hasCapability(Stream::StreamWrite))
|
|
{
|
|
AssertFatal(false, "FileStream::_write: file stream lacks capability");
|
|
Stream::setStatus(IllegalCall);
|
|
return(false);
|
|
}
|
|
|
|
// exit on pre-existing errors
|
|
if (Ok != getStatus() && EOS != getStatus())
|
|
return(false);
|
|
|
|
// if a request of non-zero length was made
|
|
if (0 != i_numBytes)
|
|
{
|
|
U8 *pSrc = (U8 *)i_pBuffer;
|
|
U32 writeSize;
|
|
U32 remaining = i_numBytes;
|
|
U32 bytesWrit;
|
|
U32 blockHead;
|
|
U32 blockTail;
|
|
|
|
// check if the buffer is valid
|
|
if (BUFFER_INVALID != mBuffHead)
|
|
{
|
|
// copy as much as possible from the source to the buffer
|
|
calcBlockBounds(mBuffHead, &blockHead, &blockTail);
|
|
writeSize = (mBuffPos > blockTail) ? 0 : blockTail - mBuffPos + 1;
|
|
writeSize = getMin(writeSize, remaining);
|
|
|
|
AssertFatal(0 == writeSize || (mBuffPos - blockHead) < BUFFER_SIZE, "FileStream::_write: out of bounds buffer position");
|
|
dMemcpy(mBuffer + (mBuffPos - blockHead), pSrc, writeSize);
|
|
// reduce the remaining amount to be written
|
|
remaining -= writeSize;
|
|
// advance the buffer pointers
|
|
mBuffPos += writeSize;
|
|
mBuffTail = getMax(mBuffTail, mBuffPos - 1);
|
|
pSrc += writeSize;
|
|
// mark the buffer dirty
|
|
if (0 < writeSize)
|
|
mDirty = true;
|
|
}
|
|
|
|
// if the request wasn't satisfied by the buffer
|
|
if (0 < remaining)
|
|
{
|
|
// flush the buffer if its dirty, since we now need to go to disk
|
|
if (true == mDirty)
|
|
flush();
|
|
|
|
// make sure we know the current write location in the underlying file
|
|
mBuffPos = mFile.getPosition();
|
|
calcBlockBounds(mBuffPos, &blockHead, &blockTail);
|
|
|
|
// check if the data to be written falls within a single block
|
|
if ((mBuffPos + remaining) <= blockTail)
|
|
{
|
|
// write the data to the buffer
|
|
dMemcpy(mBuffer + (mBuffPos - blockHead), pSrc, remaining);
|
|
// update the buffer pointers
|
|
mBuffHead = mBuffPos;
|
|
mBuffPos += remaining;
|
|
mBuffTail = mBuffPos - 1;
|
|
// mark the buffer dirty
|
|
mDirty = true;
|
|
}
|
|
// otherwise the remaining spans multiple blocks
|
|
else
|
|
{
|
|
clearBuffer();
|
|
// write to disk directly from the source
|
|
mFile.write(remaining, (char *)pSrc, &bytesWrit);
|
|
setStatus();
|
|
return(Ok == getStatus() || EOS == getStatus());
|
|
}
|
|
}
|
|
}
|
|
return(true);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void FileStream::init()
|
|
{
|
|
mStreamCaps = 0;
|
|
Stream::setStatus(Closed);
|
|
clearBuffer();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool FileStream::fillBuffer(const U32 i_startPosition)
|
|
{
|
|
AssertFatal(0 != mStreamCaps, "FileStream::fillBuffer: the stream isn't open");
|
|
AssertFatal(false == mDirty, "FileStream::fillBuffer: buffer must be clean to fill");
|
|
|
|
// make sure start position and file pointer jive
|
|
if (i_startPosition != mFile.getPosition())
|
|
{
|
|
mFile.setPosition(i_startPosition);
|
|
if (File::Ok != mFile.getStatus() && File::EOS != mFile.getStatus())
|
|
{
|
|
setStatus();
|
|
return(false);
|
|
}
|
|
else
|
|
// update buffer pointer
|
|
mBuffPos = i_startPosition;
|
|
}
|
|
|
|
// check if file pointer is at end-of-file
|
|
if (EOS == getStatus())
|
|
{
|
|
// invalidate the buffer
|
|
mBuffHead = BUFFER_INVALID;
|
|
// set the status to end-of-stream
|
|
mEOF = true;
|
|
}
|
|
// otherwise
|
|
else
|
|
{
|
|
U32 bytesRead = 0;
|
|
U32 blockHead;
|
|
// locate bounds of buffer containing current position
|
|
calcBlockHead(mBuffPos, &blockHead);
|
|
// read as much as possible from input file
|
|
mFile.read(BUFFER_SIZE - (i_startPosition - blockHead), (char *)mBuffer + (i_startPosition - blockHead), &bytesRead);
|
|
setStatus();
|
|
if (Ok == getStatus() || EOS == getStatus())
|
|
{
|
|
// update buffer pointers
|
|
mBuffHead = i_startPosition;
|
|
mBuffPos = i_startPosition;
|
|
mBuffTail = i_startPosition + bytesRead - 1;
|
|
// update end-of-file status
|
|
if (0 != bytesRead && EOS == getStatus())
|
|
{
|
|
Stream::setStatus(Ok);
|
|
mEOF = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mBuffHead = BUFFER_INVALID;
|
|
return(false);
|
|
}
|
|
}
|
|
return(true);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void FileStream::clearBuffer()
|
|
{
|
|
mBuffHead = BUFFER_INVALID;
|
|
mBuffPos = 0;
|
|
mBuffTail = 0;
|
|
mDirty = false;
|
|
mEOF = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void FileStream::calcBlockHead(const U32 i_position, U32 *o_blockHead)
|
|
{
|
|
AssertFatal(NULL != o_blockHead, "FileStream::calcBlockHead: NULL pointer passed for block head");
|
|
|
|
*o_blockHead = i_position/BUFFER_SIZE * BUFFER_SIZE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void FileStream::calcBlockBounds(const U32 i_position, U32 *o_blockHead, U32 *o_blockTail)
|
|
{
|
|
AssertFatal(NULL != o_blockHead, "FileStream::calcBlockBounds: NULL pointer passed for block head");
|
|
AssertFatal(NULL != o_blockTail, "FileStream::calcBlockBounds: NULL pointer passed for block tail");
|
|
|
|
*o_blockHead = i_position/BUFFER_SIZE * BUFFER_SIZE;
|
|
*o_blockTail = *o_blockHead + BUFFER_SIZE - 1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void FileStream::setStatus()
|
|
{
|
|
switch (mFile.getStatus())
|
|
{
|
|
case File::Ok:
|
|
Stream::setStatus(Ok);
|
|
break;
|
|
case File::IOError:
|
|
Stream::setStatus(IOError);
|
|
break;
|
|
case File::EOS:
|
|
Stream::setStatus(EOS);
|
|
break;
|
|
case File::IllegalCall:
|
|
Stream::setStatus(IllegalCall);
|
|
break;
|
|
case File::Closed:
|
|
Stream::setStatus(Closed);
|
|
break;
|
|
case File::UnknownError:
|
|
Stream::setStatus(UnknownError);
|
|
break;
|
|
default:
|
|
AssertFatal(false, "FileStream::setStatus: invalid error mode");
|
|
}
|
|
}
|