fileStream.cpp
Engine/source/core/stream/fileStream.cpp
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