123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- //-----------------------------------------------------------------------------
- // Copyright (c) 2012 GarageGames, LLC
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to
- // deal in the Software without restriction, including without limitation the
- // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- // sell copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- // IN THE SOFTWARE.
- //-----------------------------------------------------------------------------
- #include "platform/platform.h"
- #include "core/stream/fileStream.h"
- //-----------------------------------------------------------------------------
- // FileStream methods...
- //-----------------------------------------------------------------------------
- //-----------------------------------------------------------------------------
- FileStream::FileStream()
- {
- dMemset(mBuffer, 0, sizeof(mBuffer));
- // initialize the file stream
- init();
- }
- FileStream *FileStream::createAndOpen(const String &inFileName, Torque::FS::File::AccessMode inMode)
- {
- FileStream *newStream = new FileStream;
- bool success = newStream->open( inFileName, inMode );
- if ( !success )
- {
- delete newStream;
- newStream = NULL;
- }
- return newStream;
- }
- //-----------------------------------------------------------------------------
- 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(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;
-
- // FIXME [tom, 9/5/2006] This needs to be checked. Basically, when seeking within
- // the buffer, if the stream has an EOS status before the seek then if you try to
- // read immediately after seeking, you'll incorrectly get an EOS.
- //
- // I am not 100% sure if this fix is correct, but it seems to be working for the undo system.
- if(mBuffPos < mBuffTail)
- Stream::setStatus(Ok);
-
- return(true);
- }
- // otherwise the new position lies in some block not in memory
- else
- {
- if (mDirty)
- flush();
- clearBuffer();
- mFile->setPosition(i_newPosition, Torque::FS::File::Begin);
- setStatus();
-
- if (mFile->getStatus() == Torque::FS::FileNode::EndOfFile)
- mEOF = true;
- 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 (mDirty)
- return(getMax((U32)(mFile->getSize()), mBuffTail + 1)); ///<@todo U64 vs U32 issue
- // otherwise just get the size on disk...
- else
- return(mFile->getSize());
- }
- //-----------------------------------------------------------------------------
- bool FileStream::open(const String &inFileName, Torque::FS::File::AccessMode inMode)
- {
- AssertWarn(0 == mStreamCaps, "FileStream::setPosition: the stream is already open");
- AssertFatal(inFileName.isNotEmpty(), "FileStream::open: empty filename");
- // make sure the file stream's state is clean
- clearBuffer();
- Torque::Path filePath(inFileName);
- // IF we are writing, make sure the path exists
- if( inMode == Torque::FS::File::Write || inMode == Torque::FS::File::WriteAppend || inMode == Torque::FS::File::ReadWrite )
- Torque::FS::CreatePath(filePath);
- mFile = Torque::FS::OpenFile(filePath, inMode);
- if (mFile != NULL)
- {
- setStatus();
- switch (inMode)
- {
- case Torque::FS::File::Read:
- mStreamCaps = U32(StreamRead) |
- U32(StreamPosition);
- break;
- case Torque::FS::File::Write:
- case Torque::FS::File::WriteAppend:
- mStreamCaps = U32(StreamWrite) |
- U32(StreamPosition);
- break;
- case Torque::FS::File::ReadWrite:
- mStreamCaps = U32(StreamRead) |
- U32(StreamWrite) |
- U32(StreamPosition);
- break;
- default:
- AssertFatal(false, String::ToString( "FileStream::open: bad access mode on %s", inFileName.c_str() ));
- }
- }
- else
- {
- Stream::setStatus(IOError);
- return(false);
- }
- return getStatus() == Ok;
- }
- //-----------------------------------------------------------------------------
- void FileStream::close()
- {
- if (getStatus() == Closed)
- return;
- if (mFile != NULL )
- {
- // make sure nothing in the buffer differs from what is on disk
- if (mDirty)
- flush();
- // and close the file
- mFile->close();
- AssertFatal(mFile->getStatus() == Torque::FS::FileNode::Closed, "FileStream::close: close failed");
- mFile = NULL;
- }
- // 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 (mDirty)
- {
- AssertFatal(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, Torque::FS::File::Begin);
- if (mFile->getStatus() != Torque::FS::FileNode::Open && mFile->getStatus() != Torque::FS::FileNode::EndOfFile)
- return(false);
- }
- // write contents of the buffer to disk
- U32 blockHead;
- calcBlockHead(mBuffHead, &blockHead);
- mFile->write((char *)mBuffer + (mBuffHead - blockHead), mBuffTail - mBuffHead + 1);
- // 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 (!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
- bytesRead = mFile->read((char *)pDst, remaining);
- 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 (!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 (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
- bytesWrit = mFile->write((char *)pSrc, remaining);
- 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, Torque::FS::File::Begin);
- if (mFile->getStatus() != Torque::FS::FileNode::Open && mFile->getStatus() != Torque::FS::FileNode::EndOfFile)
- {
- 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 blockHead;
- // locate bounds of buffer containing current position
- calcBlockHead(mBuffPos, &blockHead);
- // read as much as possible from input file
- U32 bytesRead = mFile->read((char *)mBuffer + (i_startPosition - blockHead), BUFFER_SIZE - (i_startPosition - blockHead));
- 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 Torque::FS::FileNode::Open:
- Stream::setStatus(Ok);
- break;
- case Torque::FS::FileNode::Closed:
- Stream::setStatus(Closed);
- break;
- case Torque::FS::FileNode::EndOfFile:
- Stream::setStatus(EOS);
- break;
- case Torque::FS::FileNode::FileSystemFull:
- case Torque::FS::FileNode::NoSuchFile:
- case Torque::FS::FileNode::AccessDenied:
- case Torque::FS::FileNode::NoDisk:
- case Torque::FS::FileNode::SharingViolation:
- Stream::setStatus(IOError);
- break;
- case Torque::FS::FileNode::IllegalCall:
- Stream::setStatus(IllegalCall);
- break;
- case Torque::FS::FileNode::UnknownError:
- Stream::setStatus(UnknownError);
- break;
- default:
- AssertFatal(false, "FileStream::setStatus: invalid error mode");
- }
- }
- FileStream* FileStream::clone() const
- {
- Torque::FS::File::AccessMode mode;
- if( hasCapability( StreamWrite ) && hasCapability( StreamRead ) )
- mode = Torque::FS::File::ReadWrite;
- else if( hasCapability( StreamWrite ) )
- mode = Torque::FS::File::Write;
- else
- mode = Torque::FS::File::Read;
- FileStream* copy = createAndOpen( mFile->getName(), mode );
- if( copy && copy->setPosition( getPosition() ) )
- return copy;
- delete copy;
- return NULL;
- }
|