fileStream.cpp

Engine/source/core/stream/fileStream.cpp

More...

Detailed Description

  1
  2//-----------------------------------------------------------------------------
  3// Copyright (c) 2012 GarageGames, LLC
  4//
  5// Permission is hereby granted, free of charge, to any person obtaining a copy
  6// of this software and associated documentation files (the "Software"), to
  7// deal in the Software without restriction, including without limitation the
  8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  9// sell copies of the Software, and to permit persons to whom the Software is
 10// furnished to do so, subject to the following conditions:
 11//
 12// The above copyright notice and this permission notice shall be included in
 13// all copies or substantial portions of the Software.
 14//
 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 21// IN THE SOFTWARE.
 22//-----------------------------------------------------------------------------
 23
 24#include "platform/platform.h"
 25#include "core/stream/fileStream.h"
 26
 27
 28//-----------------------------------------------------------------------------
 29// FileStream methods...
 30//-----------------------------------------------------------------------------
 31
 32//-----------------------------------------------------------------------------
 33FileStream::FileStream()
 34{
 35   dMemset(mBuffer, 0, sizeof(mBuffer));
 36   // initialize the file stream
 37   init();
 38}
 39
 40FileStream *FileStream::createAndOpen(const String &inFileName, Torque::FS::File::AccessMode inMode)
 41{
 42   FileStream  *newStream = new FileStream;
 43
 44   bool success = newStream->open( inFileName, inMode );
 45
 46   if ( !success )
 47   {
 48      delete newStream;
 49      newStream = NULL;
 50   }
 51
 52   return newStream;
 53}
 54
 55//-----------------------------------------------------------------------------
 56FileStream::~FileStream()
 57{
 58   // make sure the file stream is closed
 59   close();
 60}
 61
 62//-----------------------------------------------------------------------------
 63bool FileStream::hasCapability(const Capability i_cap) const
 64{
 65   return(0 != (U32(i_cap) & mStreamCaps));
 66}
 67
 68//-----------------------------------------------------------------------------
 69U32 FileStream::getPosition() const
 70{
 71   AssertFatal(0 != mStreamCaps, "FileStream::getPosition: the stream isn't open");
 72   //AssertFatal(true == hasCapability(StreamPosition), "FileStream::getPosition(): lacks positioning capability");
 73
 74   // return the position inside the buffer if its valid, otherwise return the underlying file position
 75   return((BUFFER_INVALID != mBuffHead) ? mBuffPos : mFile->getPosition());
 76}
 77
 78//-----------------------------------------------------------------------------
 79bool FileStream::setPosition(const U32 i_newPosition)
 80{
 81   AssertFatal(0 != mStreamCaps, "FileStream::setPosition: the stream isn't open");
 82   AssertFatal(hasCapability(StreamPosition), "FileStream::setPosition: lacks positioning capability");
 83
 84   // if the buffer is valid, test the new position against the bounds of the buffer
 85   if ((BUFFER_INVALID != mBuffHead) && (i_newPosition >= mBuffHead) && (i_newPosition <= mBuffTail))
 86   {
 87      // set the position and return
 88      mBuffPos = i_newPosition;
 89      
 90      // FIXME [tom, 9/5/2006] This needs to be checked. Basically, when seeking within
 91      // the buffer, if the stream has an EOS status before the seek then if you try to
 92      // read immediately after seeking, you'll incorrectly get an EOS.
 93      //
 94      // I am not 100% sure if this fix is correct, but it seems to be working for the undo system.
 95      if(mBuffPos < mBuffTail)
 96         Stream::setStatus(Ok);
 97      
 98      return(true);
 99   }
100   // otherwise the new position lies in some block not in memory
101   else
102   {
103      if (mDirty)
104         flush();
105
106      clearBuffer();
107
108      mFile->setPosition(i_newPosition, Torque::FS::File::Begin);
109
110      setStatus();
111      
112      if (mFile->getStatus() == Torque::FS::FileNode::EndOfFile)
113         mEOF = true;
114
115      return(Ok == getStatus() || EOS == getStatus());
116   }
117}
118
119//-----------------------------------------------------------------------------
120U32 FileStream::getStreamSize()
121{
122   AssertWarn(0 != mStreamCaps, "FileStream::getStreamSize: the stream isn't open");
123   AssertFatal((BUFFER_INVALID != mBuffHead && true == mDirty) || false == mDirty, "FileStream::getStreamSize: buffer must be valid if its dirty");
124
125   // the stream size may not match the size on-disk if its been written to...
126   if (mDirty)
127      return(getMax((U32)(mFile->getSize()), mBuffTail + 1));  ///<@todo U64 vs U32 issue
128   // otherwise just get the size on disk...
129   else
130      return(mFile->getSize());
131}
132
133//-----------------------------------------------------------------------------
134bool FileStream::open(const String &inFileName, Torque::FS::File::AccessMode inMode)
135{
136   AssertWarn(0 == mStreamCaps, "FileStream::setPosition: the stream is already open");
137   AssertFatal(inFileName.isNotEmpty(), "FileStream::open: empty filename");
138
139   // make sure the file stream's state is clean
140   clearBuffer();
141
142   Torque::Path   filePath(inFileName);
143
144   // IF we are writing, make sure the path exists
145   if( inMode == Torque::FS::File::Write || inMode == Torque::FS::File::WriteAppend || inMode == Torque::FS::File::ReadWrite )
146      Torque::FS::CreatePath(filePath);
147
148   mFile = Torque::FS::OpenFile(filePath, inMode);
149
150   if (mFile != NULL)
151   {
152      setStatus();
153      switch (inMode)
154      {
155         case Torque::FS::File::Read:
156            mStreamCaps = U32(StreamRead) |
157                          U32(StreamPosition);
158            break;
159         case Torque::FS::File::Write:
160         case Torque::FS::File::WriteAppend:
161            mStreamCaps = U32(StreamWrite) |
162                          U32(StreamPosition);
163            break;
164         case Torque::FS::File::ReadWrite:
165            mStreamCaps = U32(StreamRead)  |
166                          U32(StreamWrite) |
167                          U32(StreamPosition);
168            break;
169         default:
170            AssertFatal(false, String::ToString( "FileStream::open: bad access mode on %s", inFileName.c_str() ));
171      }
172   }
173   else
174   {
175      Stream::setStatus(IOError);
176      return(false);
177   }
178
179   return getStatus() == Ok;
180}
181
182//-----------------------------------------------------------------------------
183void FileStream::close()
184{
185   if (getStatus() == Closed)
186      return;
187
188   if (mFile != NULL )
189   {
190      // make sure nothing in the buffer differs from what is on disk
191      if (mDirty)
192         flush();
193
194      // and close the file
195      mFile->close();
196
197      AssertFatal(mFile->getStatus() == Torque::FS::FileNode::Closed, "FileStream::close: close failed");
198
199      mFile = NULL;
200   }
201
202   // clear the file stream's state
203   init();
204}
205
206//-----------------------------------------------------------------------------
207bool FileStream::flush()
208{
209   AssertWarn(0 != mStreamCaps, "FileStream::flush: the stream isn't open");
210   AssertFatal(false == mDirty || BUFFER_INVALID != mBuffHead, "FileStream::flush: buffer must be valid if its dirty");
211
212   // if the buffer is dirty
213   if (mDirty)
214   {
215      AssertFatal(hasCapability(StreamWrite), "FileStream::flush: a buffer without write-capability should never be dirty");
216      
217      // align the file pointer to the buffer head
218      if (mBuffHead != mFile->getPosition())
219      {
220         mFile->setPosition(mBuffHead, Torque::FS::File::Begin);
221         if (mFile->getStatus() != Torque::FS::FileNode::Open && mFile->getStatus() != Torque::FS::FileNode::EndOfFile)
222            return(false);
223      }
224
225      // write contents of the buffer to disk
226      U32 blockHead;
227      calcBlockHead(mBuffHead, &blockHead);
228      mFile->write((char *)mBuffer + (mBuffHead - blockHead), mBuffTail - mBuffHead + 1);
229      // and update the file stream's state
230      setStatus();
231      if (EOS == getStatus())
232         mEOF = true;
233
234      if (Ok == getStatus() || EOS == getStatus())
235         // and update the status of the buffer
236         mDirty = false;
237      else
238         return(false);
239   }
240   return(true);
241}
242
243//-----------------------------------------------------------------------------
244bool FileStream::_read(const U32 i_numBytes, void *o_pBuffer)
245{
246   AssertFatal(0 != mStreamCaps, "FileStream::_read: the stream isn't open");
247   AssertFatal(NULL != o_pBuffer || i_numBytes == 0, "FileStream::_read: NULL destination pointer with non-zero read request");
248
249   if (!hasCapability(Stream::StreamRead))
250   {
251      AssertFatal(false, "FileStream::_read: file stream lacks capability");
252      Stream::setStatus(IllegalCall);
253      return(false);
254   }
255
256   // exit on pre-existing errors
257   if (Ok != getStatus())
258      return(false);
259
260   // if a request of non-zero length was made
261   if (0 != i_numBytes)
262   {
263      U8 *pDst = (U8 *)o_pBuffer;
264      U32 readSize;
265      U32 remaining = i_numBytes;
266      U32 bytesRead;
267      U32 blockHead;
268      U32 blockTail;
269
270      // check if the buffer has some data in it
271      if (BUFFER_INVALID != mBuffHead)
272      {
273         // copy as much as possible from the buffer into the destination
274         readSize = ((mBuffTail + 1) >= mBuffPos) ? (mBuffTail + 1 - mBuffPos) : 0;
275         readSize = getMin(readSize, remaining);
276         calcBlockHead(mBuffPos, &blockHead);
277         dMemcpy(pDst, mBuffer + (mBuffPos - blockHead), readSize);
278         // reduce the remaining amount to read
279         remaining -= readSize;
280         // advance the buffer pointers
281         mBuffPos += readSize;
282         pDst += readSize;
283
284         if (mBuffPos > mBuffTail && remaining != 0)
285         {
286            flush();
287            mBuffHead = BUFFER_INVALID;
288            if (mEOF == true)
289               Stream::setStatus(EOS);
290         }
291      }
292
293      // if the request wasn't satisfied by the buffer and the file has more data
294      if (false == mEOF && 0 < remaining)
295      {
296         // flush the buffer if its dirty, since we now need to go to disk
297         if (true == mDirty)
298            flush();
299
300         // make sure we know the current read location in the underlying file
301         mBuffPos = mFile->getPosition();
302         calcBlockBounds(mBuffPos, &blockHead, &blockTail);
303
304         // check if the data to be read falls within a single block
305         if ((mBuffPos + remaining) <= blockTail)
306         {
307            // fill the buffer from disk
308            if (true == fillBuffer(mBuffPos))
309            {
310               // copy as much as possible from the buffer to the destination
311               remaining = getMin(remaining, mBuffTail - mBuffPos + 1);
312               dMemcpy(pDst, mBuffer + (mBuffPos - blockHead), remaining);
313               // advance the buffer pointer
314               mBuffPos += remaining;
315            }
316            else
317               return(false);
318         }
319         // otherwise the remaining spans multiple blocks
320         else
321         {
322            clearBuffer();
323            // read from disk directly into the destination
324            bytesRead = mFile->read((char *)pDst, remaining);
325            setStatus();
326            // check to make sure we read as much as expected
327            if (Ok == getStatus() || EOS == getStatus())
328            {
329               // if not, update the end-of-file status
330               if (0 != bytesRead && EOS == getStatus())
331               {
332                  Stream::setStatus(Ok);
333                  mEOF = true;
334               }
335            }
336            else
337               return(false);
338         }
339      }
340   }
341   return(true);
342}
343
344//-----------------------------------------------------------------------------
345bool FileStream::_write(const U32 i_numBytes, const void *i_pBuffer)
346{
347   AssertFatal(0 != mStreamCaps, "FileStream::_write: the stream isn't open");
348   AssertFatal(NULL != i_pBuffer || i_numBytes == 0, "FileStream::_write: NULL source buffer pointer on non-zero write request");
349
350   if (!hasCapability(Stream::StreamWrite))
351   {
352      AssertFatal(false, "FileStream::_write: file stream lacks capability");
353      Stream::setStatus(IllegalCall);
354      return(false);
355   }
356
357   // exit on pre-existing errors
358   if (Ok != getStatus() && EOS != getStatus())
359      return(false);
360
361   // if a request of non-zero length was made
362   if (0 != i_numBytes)
363   {
364      U8 *pSrc = (U8 *)i_pBuffer;
365      U32 writeSize;
366      U32 remaining = i_numBytes;
367      U32 bytesWrit;
368      U32 blockHead;
369      U32 blockTail;
370
371      // check if the buffer is valid
372      if (BUFFER_INVALID != mBuffHead)
373      {
374         // copy as much as possible from the source to the buffer
375         calcBlockBounds(mBuffHead, &blockHead, &blockTail);
376         writeSize = (mBuffPos > blockTail) ? 0 : blockTail - mBuffPos + 1;
377         writeSize = getMin(writeSize, remaining);
378
379         AssertFatal(0 == writeSize || (mBuffPos - blockHead) < BUFFER_SIZE, "FileStream::_write: out of bounds buffer position");
380         dMemcpy(mBuffer + (mBuffPos - blockHead), pSrc, writeSize);
381         // reduce the remaining amount to be written
382         remaining -= writeSize;
383         // advance the buffer pointers
384         mBuffPos += writeSize;
385         mBuffTail = getMax(mBuffTail, mBuffPos - 1);
386         pSrc += writeSize;
387         // mark the buffer dirty
388         if (0 < writeSize)
389            mDirty = true;
390      }
391
392      // if the request wasn't satisfied by the buffer
393      if (0 < remaining)
394      {
395         // flush the buffer if its dirty, since we now need to go to disk
396         if (mDirty)
397            flush();
398
399         // make sure we know the current write location in the underlying file
400         mBuffPos = mFile->getPosition();
401         calcBlockBounds(mBuffPos, &blockHead, &blockTail);
402
403         // check if the data to be written falls within a single block
404         if ((mBuffPos + remaining) <= blockTail)
405         {
406            // write the data to the buffer
407            dMemcpy(mBuffer + (mBuffPos - blockHead), pSrc, remaining);
408            // update the buffer pointers
409            mBuffHead = mBuffPos;
410            mBuffPos += remaining;
411            mBuffTail = mBuffPos - 1;
412            // mark the buffer dirty
413            mDirty = true;
414         }
415         // otherwise the remaining spans multiple blocks
416         else
417         {
418            clearBuffer();
419            // write to disk directly from the source
420            bytesWrit = mFile->write((char *)pSrc, remaining);
421            setStatus();
422            return(Ok == getStatus() || EOS == getStatus());
423         }
424      }
425   }
426   return(true);
427}
428
429//-----------------------------------------------------------------------------
430void FileStream::init()
431{
432   mStreamCaps = 0;
433   Stream::setStatus(Closed);
434   clearBuffer();
435}
436
437//-----------------------------------------------------------------------------
438bool FileStream::fillBuffer(const U32 i_startPosition)
439{
440   AssertFatal(0 != mStreamCaps, "FileStream::fillBuffer: the stream isn't open");
441   AssertFatal(false == mDirty, "FileStream::fillBuffer: buffer must be clean to fill");
442
443   // make sure start position and file pointer jive
444   if (i_startPosition != mFile->getPosition())
445   {
446      mFile->setPosition(i_startPosition, Torque::FS::File::Begin);
447      if (mFile->getStatus() != Torque::FS::FileNode::Open && mFile->getStatus() != Torque::FS::FileNode::EndOfFile)
448      {
449         setStatus();
450         return(false);
451      }
452      else
453         // update buffer pointer
454         mBuffPos = i_startPosition;
455   }
456
457   // check if file pointer is at end-of-file
458   if (EOS == getStatus())
459   {
460      // invalidate the buffer
461      mBuffHead = BUFFER_INVALID;
462      // set the status to end-of-stream
463      mEOF = true;
464   }
465   // otherwise
466   else
467   {
468      U32 blockHead;
469      // locate bounds of buffer containing current position
470      calcBlockHead(mBuffPos, &blockHead);
471      // read as much as possible from input file
472      U32 bytesRead = mFile->read((char *)mBuffer + (i_startPosition - blockHead), BUFFER_SIZE - (i_startPosition - blockHead));
473      setStatus();
474      if (Ok == getStatus() || EOS == getStatus())
475      {
476         // update buffer pointers
477         mBuffHead = i_startPosition;
478         mBuffPos = i_startPosition;
479         mBuffTail = i_startPosition + bytesRead - 1;
480         // update end-of-file status
481         if (0 != bytesRead && EOS == getStatus())
482         {
483            Stream::setStatus(Ok);
484            mEOF = true;
485         }
486      }
487      else
488      {
489         mBuffHead = BUFFER_INVALID;
490         return(false);
491      }
492   }
493   return(true);
494}
495
496//-----------------------------------------------------------------------------
497void FileStream::clearBuffer()
498{
499   mBuffHead = BUFFER_INVALID;
500   mBuffPos  = 0;
501   mBuffTail = 0;
502   mDirty = false;
503   mEOF = false;
504}
505
506//-----------------------------------------------------------------------------
507void FileStream::calcBlockHead(const U32 i_position, U32 *o_blockHead)
508{
509   AssertFatal(NULL != o_blockHead, "FileStream::calcBlockHead: NULL pointer passed for block head");
510
511   *o_blockHead = i_position/<a href="/coding/class/classfilestream/#classfilestream_1a31c88470ee6a7902ad0fb3c54708fc03a136dddaef2fabee3f459b39a95c3654f">BUFFER_SIZE</a> * BUFFER_SIZE;
512}
513
514//-----------------------------------------------------------------------------
515void FileStream::calcBlockBounds(const U32 i_position, U32 *o_blockHead, U32 *o_blockTail)
516{
517   AssertFatal(NULL != o_blockHead, "FileStream::calcBlockBounds: NULL pointer passed for block head");
518   AssertFatal(NULL != o_blockTail, "FileStream::calcBlockBounds: NULL pointer passed for block tail");
519
520   *o_blockHead = i_position/<a href="/coding/class/classfilestream/#classfilestream_1a31c88470ee6a7902ad0fb3c54708fc03a136dddaef2fabee3f459b39a95c3654f">BUFFER_SIZE</a> * BUFFER_SIZE;
521   *o_blockTail = *o_blockHead + BUFFER_SIZE - 1;
522}
523
524//-----------------------------------------------------------------------------
525void FileStream::setStatus()
526{
527   switch (mFile->getStatus())
528   {
529      case Torque::FS::FileNode::Open:
530         Stream::setStatus(Ok);
531         break;
532
533      case Torque::FS::FileNode::Closed:
534         Stream::setStatus(Closed);
535         break;
536
537      case Torque::FS::FileNode::EndOfFile:
538         Stream::setStatus(EOS);
539         break;
540
541      case Torque::FS::FileNode::FileSystemFull:
542      case Torque::FS::FileNode::NoSuchFile:
543      case Torque::FS::FileNode::AccessDenied:
544      case Torque::FS::FileNode::NoDisk:
545      case Torque::FS::FileNode::SharingViolation:
546         Stream::setStatus(IOError);
547         break;
548
549      case Torque::FS::FileNode::IllegalCall:
550         Stream::setStatus(IllegalCall);
551         break;
552
553      case Torque::FS::FileNode::UnknownError:
554         Stream::setStatus(UnknownError);
555         break;
556
557      default:
558         AssertFatal(false, "FileStream::setStatus: invalid error mode");
559   }
560}
561
562FileStream* FileStream::clone() const
563{
564   Torque::FS::File::AccessMode mode;
565   if( hasCapability( StreamWrite ) && hasCapability( StreamRead ) )
566      mode = Torque::FS::File::ReadWrite;
567   else if( hasCapability( StreamWrite ) )
568      mode = Torque::FS::File::Write;
569   else
570      mode = Torque::FS::File::Read;
571
572   FileStream* copy = createAndOpen( mFile->getName(), mode );
573   if( copy && copy->setPosition( getPosition() ) )
574      return copy;
575
576   delete copy;
577   return NULL;
578}
579