123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523 |
- //-----------------------------------------------------------------------------
- // Copyright (c) 2013 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 "fileStream.h"
- #include "platform/platform.h"
- #include "math/mMath.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;
-
- // 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
- {
- // 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();
-
- File::Status status = mFile.open(i_pFilename, (File::AccessMode)i_openMode);
- if (status == File::Ok || status == File::EOS)
- {
- 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;
- 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");
- }
- }
|