fileStream.cc 18 KB

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