AndroidFileio.cpp 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367
  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 "platform/platform.h"
  23. #include "platformAndroid/platformAndroid.h"
  24. #include "platform/platformFileIO.h"
  25. #include "collection/vector.h"
  26. #include "string/stringTable.h"
  27. #include "string/stringUnit.h"
  28. #include "console/console.h"
  29. #include "debug/profiler.h"
  30. #include "io/resource/resourceManager.h"
  31. #include <stdio.h>
  32. #include <stdlib.h>
  33. #include <errno.h>
  34. #include <utime.h>
  35. #include <sys/types.h>
  36. #include <dirent.h>
  37. #include <unistd.h>
  38. #include <sys/stat.h>
  39. #include <sys/time.h>
  40. #define MAX_MAC_PATH_LONG 2048
  41. //-----------------------------------------------------------------------------
  42. //Cache handling functions
  43. //-----------------------------------------------------------------------------
  44. bool isCachePath(const char* path)
  45. {
  46. if (!path || !*path)
  47. return false;
  48. if (path[0] == '/')
  49. {
  50. if (strstr(path, Platform::osGetTemporaryDirectory()) != NULL)
  51. {
  52. return true;
  53. }
  54. else
  55. {
  56. return false;
  57. }
  58. }
  59. else
  60. {
  61. const char* tmp = Platform::osGetTemporaryDirectory();
  62. if (strstr(path, tmp+1) != NULL)
  63. {
  64. return true;
  65. }
  66. else
  67. {
  68. return false;
  69. }
  70. }
  71. }
  72. bool isUserDataPath(const char* path)
  73. {
  74. if (!path || !*path)
  75. return false;
  76. if (path[0] == '/')
  77. {
  78. if (strstr(path, Platform::getUserDataDirectory()) != NULL)
  79. {
  80. return true;
  81. }
  82. else
  83. {
  84. return false;
  85. }
  86. }
  87. else
  88. {
  89. const char* tmp = Platform::getUserDataDirectory();
  90. if (strstr(path, tmp+1) != NULL)
  91. {
  92. return true;
  93. }
  94. else
  95. {
  96. return false;
  97. }
  98. }
  99. }
  100. //-----------------------------------------------------------------------------
  101. bool Platform::fileDelete(const char * name)
  102. {
  103. if(!name )
  104. return(false);
  105. if (dStrlen(name) > MAX_MAC_PATH_LONG)
  106. Con::warnf("Platform::FileDelete() - Filename length is pretty long...");
  107. return(remove(name) == 0); // remove returns 0 on success
  108. }
  109. //-----------------------------------------------------------------------------
  110. bool dFileTouch(const char *path)
  111. {
  112. if (!path || !*path)
  113. return false;
  114. // set file at path's modification and access times to now.
  115. return( utimes( path, NULL) == 0); // utimes returns 0 on success.
  116. }
  117. //-----------------------------------------------------------------------------
  118. // Constructors & Destructor
  119. //-----------------------------------------------------------------------------
  120. //-----------------------------------------------------------------------------
  121. // After construction, the currentStatus will be Closed and the capabilities
  122. // will be 0.
  123. //-----------------------------------------------------------------------------
  124. File::File()
  125. : currentStatus(Closed), capability(0)
  126. {
  127. buffer = NULL;
  128. size = 0;
  129. filePointer = 0;
  130. handle = NULL;
  131. }
  132. //-----------------------------------------------------------------------------
  133. // insert a copy constructor here... (currently disabled)
  134. //-----------------------------------------------------------------------------
  135. //-----------------------------------------------------------------------------
  136. // Destructor
  137. //-----------------------------------------------------------------------------
  138. File::~File()
  139. {
  140. if (buffer != NULL || handle != NULL)
  141. close();
  142. handle = NULL;
  143. }
  144. //-----------------------------------------------------------------------------
  145. // Open a file in the mode specified by openMode (Read, Write, or ReadWrite).
  146. // Truncate the file if the mode is either Write or ReadWrite and truncate is
  147. // true.
  148. //
  149. // Sets capability appropriate to the openMode.
  150. // Returns the currentStatus of the file.
  151. //-----------------------------------------------------------------------------
  152. File::Status File::open(const char *filename, const AccessMode openMode)
  153. {
  154. //If its a cache path then we need to open it using C methods not AssetManager
  155. if (isCachePath(filename) || isUserDataPath(filename))
  156. {
  157. if (dStrlen(filename) > MAX_MAC_PATH_LONG)
  158. Con::warnf("File::open: Filename length is pretty long...");
  159. // Close the file if it was already open...
  160. if (currentStatus != Closed)
  161. close();
  162. // create the appropriate type of file...
  163. switch (openMode)
  164. {
  165. case Read:
  166. capability = FileRead;
  167. filePointer = 0;
  168. buffer = (U8*)_AndroidLoadInternalFile(filename, &size);
  169. if (buffer == NULL)
  170. currentStatus = UnknownError;
  171. else
  172. currentStatus = Ok;
  173. return currentStatus;
  174. break;
  175. case Write:
  176. handle = (void *)fopen(filename, "wb"); // write only
  177. break;
  178. case ReadWrite:
  179. handle = (void *)fopen(filename, "ab+"); // write(append) and read
  180. break;
  181. case WriteAppend:
  182. handle = (void *)fopen(filename, "ab"); // write(append) only
  183. break;
  184. default:
  185. AssertFatal(false, "File::open: bad access mode");
  186. }
  187. // handle not created successfully
  188. if (handle == NULL)
  189. return setStatus();
  190. // successfully created file, so set the file capabilities...
  191. switch (openMode)
  192. {
  193. case Read:
  194. capability = FileRead;
  195. break;
  196. case Write:
  197. case WriteAppend:
  198. capability = FileWrite;
  199. break;
  200. case ReadWrite:
  201. capability = FileRead | FileWrite;
  202. break;
  203. default:
  204. AssertFatal(false, "File::open: bad access mode");
  205. }
  206. // must set the file status before setting the position.
  207. currentStatus = Ok;
  208. if (openMode == ReadWrite)
  209. setPosition(0);
  210. // success!
  211. return currentStatus;
  212. }
  213. if (dStrlen(filename) > MAX_MAC_PATH_LONG)
  214. Con::warnf("File::open: Filename length is pretty long...");
  215. // Close the file if it was already open...
  216. if (currentStatus != Closed || buffer != NULL)
  217. close();
  218. // create the appropriate type of file...
  219. switch (openMode)
  220. {
  221. case Read:
  222. filePointer = 0;
  223. buffer = (U8*)_AndroidLoadFile(filename, &size);
  224. if (buffer == NULL) {
  225. currentStatus = UnknownError;
  226. capability = FileRead;
  227. return currentStatus;
  228. }
  229. break;
  230. case Write:
  231. //AssertFatal(false, "File::open: Write not supported on Android");
  232. //Platform::getUserDataDirectory()
  233. return currentStatus;
  234. case ReadWrite:
  235. //AssertFatal(false, "File::open: ReadWrite not supported on Android");
  236. return currentStatus;
  237. case WriteAppend:
  238. //AssertFatal(false, "File::open: WriteAppend not supported on Android");
  239. return currentStatus;
  240. default:
  241. AssertFatal(false, "File::open: bad access mode");
  242. }
  243. capability = FileRead;
  244. // must set the file status before setting the position.
  245. currentStatus = Ok;
  246. // success!
  247. return currentStatus;
  248. }
  249. //-----------------------------------------------------------------------------
  250. // Get the current position of the file pointer.
  251. //-----------------------------------------------------------------------------
  252. U32 File::getPosition() const
  253. {
  254. if (handle != NULL)
  255. {
  256. AssertFatal(currentStatus != Closed , "File::getPosition: file closed");
  257. AssertFatal(handle != NULL, "File::getPosition: invalid file handle");
  258. return ftell((FILE*)handle);
  259. }
  260. AssertFatal(currentStatus != Closed , "File::getPosition: file closed");
  261. AssertFatal(buffer != NULL, "File::getPosition: invalid file buffer");
  262. return filePointer;
  263. }
  264. //-----------------------------------------------------------------------------
  265. // Set the position of the file pointer.
  266. // Absolute and relative positioning is supported via the absolutePos
  267. // parameter.
  268. //
  269. // If positioning absolutely, position MUST be positive - an IOError results if
  270. // position is negative.
  271. // Position can be negative if positioning relatively, however positioning
  272. // before the start of the file is an IOError.
  273. //
  274. // Returns the currentStatus of the file.
  275. //-----------------------------------------------------------------------------
  276. File::Status File::setPosition(S32 position, bool absolutePos)
  277. {
  278. if (handle != NULL)
  279. {
  280. AssertFatal(Closed != currentStatus, "File::setPosition: file closed");
  281. AssertFatal(handle != NULL, "File::setPosition: invalid file handle");
  282. if (currentStatus != Ok && currentStatus != EOS )
  283. return currentStatus;
  284. U32 finalPos;
  285. if(absolutePos)
  286. {
  287. // absolute position
  288. AssertFatal(0 <= position, "File::setPosition: negative absolute position");
  289. // position beyond EOS is OK
  290. fseek((FILE*)handle, position, SEEK_SET);
  291. finalPos = ftell((FILE*)handle);
  292. }
  293. else
  294. {
  295. // relative position
  296. AssertFatal((getPosition() + position) >= 0, "File::setPosition: negative relative position");
  297. // position beyond EOS is OK
  298. fseek((FILE*)handle, position, SEEK_CUR);
  299. finalPos = ftell((FILE*)handle);
  300. }
  301. // ftell returns -1 on error. set error status
  302. if (0xffffffff == finalPos)
  303. return setStatus();
  304. // success, at end of file
  305. else if (finalPos >= getSize())
  306. return currentStatus = EOS;
  307. // success!
  308. else
  309. return currentStatus = Ok;
  310. }
  311. AssertFatal(Closed != currentStatus, "File::setPosition: file closed");
  312. AssertFatal(buffer != NULL, "File::setPosition: invalid file buffer");
  313. if (currentStatus != Ok && currentStatus != EOS )
  314. return currentStatus;
  315. U32 finalPos;
  316. if(absolutePos)
  317. {
  318. // absolute position
  319. AssertFatal(0 <= position, "File::setPosition: negative absolute position");
  320. // position beyond EOS is OK
  321. filePointer = position;
  322. finalPos = filePointer;
  323. }
  324. else
  325. {
  326. // relative position
  327. AssertFatal((getPosition() + position) >= 0, "File::setPosition: negative relative position");
  328. // position beyond EOS is OK
  329. filePointer += position;
  330. finalPos = filePointer;
  331. }
  332. // ftell returns -1 on error. set error status
  333. if (0xffffffff == finalPos)
  334. return setStatus();
  335. // success, at end of file
  336. else if (finalPos >= getSize())
  337. return currentStatus = EOS;
  338. // success!
  339. else
  340. return currentStatus = Ok;
  341. }
  342. //-----------------------------------------------------------------------------
  343. // Get the size of the file in bytes.
  344. // It is an error to query the file size for a Closed file, or for one with an
  345. // error status.
  346. //-----------------------------------------------------------------------------
  347. U32 File::getSize() const
  348. {
  349. if (handle != NULL)
  350. {
  351. AssertWarn(Closed != currentStatus, "File::getSize: file closed");
  352. AssertFatal(handle != NULL, "File::getSize: invalid file handle");
  353. if (Ok == currentStatus || EOS == currentStatus)
  354. {
  355. struct stat statData;
  356. if(fstat(fileno((FILE*)handle), &statData) != 0)
  357. return 0;
  358. // return the size in bytes
  359. return statData.st_size;
  360. }
  361. return 0;
  362. }
  363. AssertWarn(Closed != currentStatus, "File::getSize: file closed");
  364. AssertFatal(buffer != NULL, "File::getSize: invalid file buffer");
  365. if (Ok == currentStatus || EOS == currentStatus)
  366. {
  367. return size;
  368. }
  369. return 0;
  370. }
  371. //-----------------------------------------------------------------------------
  372. // Flush the file.
  373. // It is an error to flush a read-only file.
  374. // Returns the currentStatus of the file.
  375. //-----------------------------------------------------------------------------
  376. File::Status File::flush()
  377. {
  378. if (handle != NULL)
  379. {
  380. AssertFatal(Closed != currentStatus, "File::flush: file closed");
  381. AssertFatal(handle != NULL, "File::flush: invalid file handle");
  382. AssertFatal(true == hasCapability(FileWrite), "File::flush: cannot flush a read-only file");
  383. if (fflush((FILE*)handle) != 0)
  384. return setStatus();
  385. else
  386. return currentStatus = Ok;
  387. }
  388. AssertFatal(Closed != currentStatus, "File::flush: file closed");
  389. AssertFatal(buffer != NULL, "File::flush: invalid file buffer");
  390. AssertFatal(true == hasCapability(FileWrite), "File::flush: cannot flush a read-only file");
  391. return setStatus();
  392. }
  393. //-----------------------------------------------------------------------------
  394. // Close the File.
  395. //
  396. // Returns the currentStatus
  397. //-----------------------------------------------------------------------------
  398. File::Status File::close()
  399. {
  400. if (handle != NULL)
  401. {
  402. // check if it's already closed...
  403. if (Closed == currentStatus)
  404. return currentStatus;
  405. // it's not, so close it...
  406. if (handle != NULL)
  407. {
  408. if (fclose((FILE*)handle) != 0)
  409. return setStatus();
  410. }
  411. handle = NULL;
  412. return currentStatus = Closed;
  413. }
  414. // check if it's already closed...
  415. if (Closed == currentStatus)
  416. return currentStatus;
  417. // it's not, so close it...
  418. if (buffer != NULL)
  419. {
  420. delete[] buffer;
  421. buffer = NULL;
  422. size = 0;
  423. filePointer = 0;
  424. }
  425. return currentStatus = Closed;
  426. }
  427. //-----------------------------------------------------------------------------
  428. // Self-explanatory.
  429. //-----------------------------------------------------------------------------
  430. File::Status File::getStatus() const
  431. {
  432. return currentStatus;
  433. }
  434. //-----------------------------------------------------------------------------
  435. // Sets and returns the currentStatus when an error has been encountered.
  436. //-----------------------------------------------------------------------------
  437. File::Status File::setStatus()
  438. {
  439. if (handle != NULL)
  440. {
  441. switch (errno)
  442. {
  443. case EACCES: // permission denied
  444. currentStatus = IOError;
  445. break;
  446. case EBADF: // Bad File Pointer
  447. case EINVAL: // Invalid argument
  448. case ENOENT: // file not found
  449. case ENAMETOOLONG:
  450. default:
  451. currentStatus = UnknownError;
  452. }
  453. return currentStatus;
  454. }
  455. currentStatus = UnknownError;
  456. return currentStatus;
  457. }
  458. //-----------------------------------------------------------------------------
  459. // Sets and returns the currentStatus to status.
  460. //-----------------------------------------------------------------------------
  461. File::Status File::setStatus(File::Status status)
  462. {
  463. return currentStatus = status;
  464. }
  465. //-----------------------------------------------------------------------------
  466. // Read from a file.
  467. // The number of bytes to read is passed in size, the data is returned in src.
  468. // The number of bytes read is available in bytesRead if a non-Null pointer is
  469. // provided.
  470. //-----------------------------------------------------------------------------
  471. File::Status File::read(U32 _size, char *dst, U32 *bytesRead)
  472. {
  473. if (handle != NULL)
  474. {
  475. AssertFatal(Closed != currentStatus, "File::read: file closed");
  476. AssertFatal(handle != NULL, "File::read: invalid file handle");
  477. AssertFatal(NULL != dst, "File::read: NULL destination pointer");
  478. AssertFatal(true == hasCapability(FileRead), "File::read: file lacks capability");
  479. AssertWarn(0 != size, "File::read: size of zero");
  480. if (Ok != currentStatus || 0 == size)
  481. return currentStatus;
  482. // read from stream
  483. U32 nBytes = fread(dst, 1, size, (FILE*)handle);
  484. // did we hit the end of the stream?
  485. if( nBytes != size)
  486. currentStatus = EOS;
  487. // if bytesRead is a valid pointer, send number of bytes read there.
  488. if(bytesRead)
  489. *bytesRead = nBytes;
  490. // successfully read size bytes
  491. return currentStatus;
  492. }
  493. AssertFatal(Closed != currentStatus, "File::read: file closed");
  494. AssertFatal(buffer != NULL, "File::read: invalid file buffer");
  495. AssertFatal(NULL != dst, "File::read: NULL destination pointer");
  496. AssertFatal(true == hasCapability(FileRead), "File::read: file lacks capability");
  497. AssertWarn(0 != size, "File::read: size of zero");
  498. if (Ok != currentStatus || 0 == size)
  499. return currentStatus;
  500. // read from stream
  501. U32 nBytes = 0;
  502. if ((size-filePointer) > (_size))
  503. {
  504. memcpy(dst, buffer+filePointer, _size);
  505. nBytes = _size;
  506. }
  507. else if (size-filePointer <= 0)
  508. {
  509. nBytes = 0;
  510. }
  511. else
  512. {
  513. memcpy(dst, buffer+filePointer, size-filePointer);
  514. nBytes = size-filePointer;
  515. }
  516. //Advanced the pointer
  517. filePointer += nBytes;
  518. // did we hit the end of the stream?
  519. if( nBytes != _size)
  520. currentStatus = EOS;
  521. // if bytesRead is a valid pointer, send number of bytes read there.
  522. if(bytesRead)
  523. *bytesRead = nBytes;
  524. // successfully read size bytes
  525. return currentStatus;
  526. }
  527. //-----------------------------------------------------------------------------
  528. // Write to a file.
  529. // The number of bytes to write is passed in size, the data is passed in src.
  530. // The number of bytes written is available in bytesWritten if a non-Null
  531. // pointer is provided.
  532. //-----------------------------------------------------------------------------
  533. File::Status File::write(U32 size, const char *src, U32 *bytesWritten)
  534. {
  535. if (handle != NULL)
  536. {
  537. AssertFatal(Closed != currentStatus, "File::write: file closed");
  538. AssertFatal(handle != NULL, "File::write: invalid file handle");
  539. AssertFatal(NULL != src, "File::write: NULL source pointer");
  540. AssertFatal(true == hasCapability(FileWrite), "File::write: file lacks capability");
  541. AssertWarn(0 != size, "File::write: size of zero");
  542. if ((Ok != currentStatus && EOS != currentStatus) || 0 == size)
  543. return currentStatus;
  544. // write bytes to the stream
  545. U32 nBytes = fwrite(src, 1, size,(FILE*)handle);
  546. // if we couldn't write everything, we've got a problem. set error status.
  547. if(nBytes != size)
  548. setStatus();
  549. // if bytesWritten is a valid pointer, put number of bytes read there.
  550. if(bytesWritten)
  551. *bytesWritten = nBytes;
  552. // return current File status, whether good or ill.
  553. return currentStatus;
  554. }
  555. AssertFatal(0, "File::write: Not supported on Android.");
  556. return setStatus();
  557. }
  558. //-----------------------------------------------------------------------------
  559. // Self-explanatory.
  560. //-----------------------------------------------------------------------------
  561. bool File::hasCapability(Capability cap) const
  562. {
  563. return (0 != (U32(cap) & capability));
  564. }
  565. //-----------------------------------------------------------------------------
  566. S32 Platform::compareFileTimes(const FileTime &a, const FileTime &b)
  567. {
  568. if(a > b)
  569. return 1;
  570. if(a < b)
  571. return -1;
  572. return 0;
  573. }
  574. //-----------------------------------------------------------------------------
  575. // either time param COULD be null.
  576. //-----------------------------------------------------------------------------
  577. bool Platform::getFileTimes(const char *path, FileTime *createTime, FileTime *modifyTime)
  578. {
  579. // MacOSX is NOT guaranteed to be running off a HFS volume,
  580. // and UNIX does not keep a record of a file's creation time anywhere.
  581. // So instead of creation time we return changed time,
  582. // just like the Linux platform impl does.
  583. if (!path || !*path)
  584. return false;
  585. struct stat statData;
  586. if (stat(path, &statData) == -1)
  587. return false;
  588. if(createTime)
  589. *createTime = statData.st_ctime;
  590. if(modifyTime)
  591. *modifyTime = statData.st_mtime;
  592. return true;
  593. }
  594. //-----------------------------------------------------------------------------
  595. bool Platform::createPath(const char *file)
  596. {
  597. //<Mat> needless console noise
  598. //Con::warnf("creating path %s",file);
  599. // if the path exists, we're done.
  600. struct stat statData;
  601. if( stat(file, &statData) == 0 )
  602. {
  603. return true; // exists, rejoice.
  604. }
  605. // get the parent path.
  606. // we're not using basename because it's not thread safe.
  607. const U32 len = dStrlen(file) + 1;
  608. char parent[len];
  609. bool isDirPath = false;
  610. dSprintf(parent, len, "%s", file);
  611. if(parent[len - 2] == '/')
  612. {
  613. parent[len - 2] = '\0'; // cut off the trailing slash, if there is one
  614. isDirPath = true; // we got a trailing slash, so file is a directory.
  615. }
  616. // recusively create the parent path.
  617. // only recurse if newpath has a slash that isn't a leading slash.
  618. char *slash = dStrrchr(parent,'/');
  619. if( slash && slash != parent)
  620. {
  621. // snip the path just after the last slash.
  622. slash[1] = '\0';
  623. // recusively create the parent path. fail if parent path creation failed.
  624. if(!Platform::createPath(parent))
  625. return false;
  626. }
  627. // create *file if it is a directory path.
  628. if(isDirPath)
  629. {
  630. // try to create the directory
  631. if( mkdir(file, 0777) != 0) // app may reside in global apps dir, and so must be writable to all.
  632. return false;
  633. }
  634. return true;
  635. }
  636. #pragma mark ---- Directories ----
  637. //-----------------------------------------------------------------------------
  638. StringTableEntry Platform::getCurrentDirectory()
  639. {
  640. // get the current directory, the one that would be opened if we did a fopen(".")
  641. char* cwd = getcwd(NULL, 0);
  642. StringTableEntry ret = StringTable->insert(cwd);
  643. free(cwd);
  644. return ret;
  645. }
  646. //-----------------------------------------------------------------------------
  647. bool Platform::setCurrentDirectory(StringTableEntry newDir)
  648. {
  649. return (chdir(newDir) == 0);
  650. }
  651. //-----------------------------------------------------------------------------
  652. void Platform::openFolder(const char* path )
  653. {
  654. // TODO: users can still run applications by calling openfolder on an app bundle.
  655. // this may be a bad thing.
  656. if(!Platform::isDirectory(path))
  657. {
  658. Con::errorf(avar("Error: not a directory: %s",path));
  659. return;
  660. }
  661. const char* arg = avar("open '%s'", path);
  662. U32 ret = system(arg);
  663. if(ret != 0)
  664. Con::printf(strerror(errno));
  665. }
  666. static bool isMainDotCsPresent(char *dir)
  667. {
  668. char maincsbuf[MAX_MAC_PATH_LONG];
  669. const char *maincsname = "/main.cs";
  670. const U32 len = dStrlen(dir) + dStrlen(maincsname)+1;
  671. AssertISV(len < MAX_MAC_PATH_LONG, "Sorry, path is too long, I can't run from this folder.");
  672. dSprintf(maincsbuf,MAX_MAC_PATH_LONG,"%s%s", dir, maincsname);
  673. return Platform::isFile(maincsbuf);
  674. }
  675. //-----------------------------------------------------------------------------
  676. /// Finds and sets the current working directory.
  677. /// Torque tries to automatically detect whether you have placed the game files
  678. /// inside or outside the application's bundle. It checks for the presence of
  679. /// the file 'main.cs'. If it finds it, Torque will assume that the other game
  680. /// files are there too. If Torque does not see 'main.cs' inside its bundle, it
  681. /// will assume the files are outside the bundle.
  682. /// Since you probably don't want to copy the game files into the app every time
  683. /// you build, you will want to leave them outside the bundle for development.
  684. ///
  685. /// Android reads all assets out of compressed bundle so we dont realy have an executable path
  686. StringTableEntry Platform::getExecutablePath()
  687. {
  688. if(platState.mainDotCsDir)
  689. return platState.mainDotCsDir;
  690. char* ret = NULL;
  691. if(StringTable)
  692. platState.mainDotCsDir = StringTable->insert(".");
  693. else
  694. ret = dStrdup(".");
  695. return ret ? ret : platState.mainDotCsDir;
  696. }
  697. //-----------------------------------------------------------------------------
  698. StringTableEntry Platform::getExecutableName()
  699. {
  700. return StringTable->insert("Torque2D");
  701. }
  702. //-----------------------------------------------------------------------------
  703. bool Platform::isFile(const char *path)
  704. {
  705. if (!path || !*path)
  706. return false;
  707. if (isCachePath(path) || isUserDataPath(path))
  708. {
  709. // make sure we can stat the file
  710. struct stat statData;
  711. if( stat(path, &statData) < 0 )
  712. return false;
  713. // now see if it's a regular file
  714. if( (statData.st_mode & S_IFMT) == S_IFREG)
  715. return true;
  716. return false;
  717. }
  718. //Checking for . to avoid multiple JNI calls for performance.
  719. if (strstr(path, ".") == NULL)
  720. return false;
  721. return true;
  722. //return android_IsFile(path);
  723. }
  724. //-----------------------------------------------------------------------------
  725. bool Platform::isDirectory(const char *path)
  726. {
  727. if (!path || !*path)
  728. return false;
  729. if (isCachePath(path) || isUserDataPath(path))
  730. {
  731. // make sure we can stat the file
  732. struct stat statData;
  733. if( stat(path, &statData) < 0 )
  734. return false;
  735. // now see if it's a directory
  736. if( (statData.st_mode & S_IFMT) == S_IFDIR)
  737. return true;
  738. return false;
  739. }
  740. return android_IsDir(path);
  741. }
  742. S32 Platform::getFileSize(const char* pFilePath)
  743. {
  744. if (!pFilePath || !*pFilePath)
  745. return 0;
  746. if (isCachePath(pFilePath) || isUserDataPath(pFilePath))
  747. {
  748. struct stat statData;
  749. if( stat(pFilePath, &statData) < 0 )
  750. return 0;
  751. // and return it's size in bytes
  752. return (S32)statData.st_size;
  753. }
  754. return android_GetFileSize(pFilePath);
  755. }
  756. //-----------------------------------------------------------------------------
  757. bool Platform::isSubDirectory(const char *pathParent, const char *pathSub)
  758. {
  759. char fullpath[MAX_MAC_PATH_LONG];
  760. dStrcpyl(fullpath, MAX_MAC_PATH_LONG, pathParent, "/", pathSub, NULL);
  761. return isDirectory((const char *)fullpath);
  762. }
  763. void getDirectoryName(const char* path, char* name)
  764. {
  765. int cnt = StringUnit::getUnitCount(path, "/");
  766. strcpy(name,StringUnit::getUnit(path, cnt-1, "/"));
  767. }
  768. //-----------------------------------------------------------------------------
  769. // utility for platform::hasSubDirectory() and platform::dumpDirectories()
  770. // ensures that the entry is a directory, and isnt on the ignore lists.
  771. inline bool isGoodDirectory(const char* path)
  772. {
  773. char name[80];
  774. getDirectoryName(path, name);
  775. return (Platform::isDirectory(path) // is a dir
  776. && dStrcmp(name,".") != 0 // not here
  777. && dStrcmp(name,"..") != 0 // not parent
  778. && !Platform::isExcludedDirectory(name)); // not excluded
  779. }
  780. inline bool isGoodDirectoryCache(dirent* entry)
  781. {
  782. return (entry->d_type == DT_DIR // is a dir
  783. && dStrcmp(entry->d_name,".") != 0 // not here
  784. && dStrcmp(entry->d_name,"..") != 0 // not parent
  785. && !Platform::isExcludedDirectory(entry->d_name)); // not excluded
  786. }
  787. //-----------------------------------------------------------------------------
  788. bool Platform::hasSubDirectory(const char *path)
  789. {
  790. if (isCachePath(path) || isUserDataPath(path))
  791. {
  792. DIR *dir;
  793. dirent *entry;
  794. dir = opendir(path);
  795. if(!dir)
  796. return false; // we got a bad path, so no, it has no subdirectory.
  797. while( true )
  798. {
  799. entry = readdir(dir);
  800. if ( entry == NULL )
  801. break;
  802. if(isGoodDirectoryCache(entry) )
  803. {
  804. closedir(dir);
  805. return true; // we have a subdirectory, that isnt on the exclude list.
  806. }
  807. }
  808. closedir(dir);
  809. return false; // either this dir had no subdirectories, or they were all on the exclude list.
  810. }
  811. android_InitDirList(path);
  812. char dir[80];
  813. char pdir[255];
  814. strcpy(dir,"");
  815. android_GetNextDir(path, dir);
  816. while(strcmp(dir,"") != 0)
  817. {
  818. sprintf(pdir, "%s/%s", path, dir);
  819. if (isGoodDirectory(pdir))
  820. return true;
  821. android_GetNextDir(path, dir);
  822. }
  823. return false;
  824. }
  825. //-----------------------------------------------------------------------------
  826. /*bool recurseDumpDirectories(const char *basePath, const char *path, Vector<StringTableEntry> &directoryVector, S32 depth, bool noBasePath)
  827. {
  828. char aPath[80];
  829. if (basePath[0] == '/')
  830. {
  831. strcpy(aPath, basePath+1);
  832. }
  833. else
  834. {
  835. strcpy(aPath, basePath);
  836. }
  837. const U32 len = dStrlen(aPath) + dStrlen(path) + 2;
  838. char pathbuf[len];
  839. // construct the file path
  840. if (strcmp(path,"") != 0)
  841. dSprintf(pathbuf, len, "%s/%s", aPath, path);
  842. else
  843. strcpy(pathbuf, aPath);
  844. // be sure it opens.
  845. android_InitDirList(pathbuf);
  846. // look inside the current directory
  847. char dir[80];
  848. char pdir[255];
  849. strcpy(dir,"");
  850. android_GetNextDir(pathbuf, dir);
  851. while(strcmp(dir,"") != 0)
  852. {
  853. sprintf(pdir, "%s/%s", pathbuf, dir);
  854. if (!isGoodDirectory(pdir))
  855. return false;
  856. // construct the new path string, we'll need this below.
  857. const U32 newpathlen = dStrlen(path) + dStrlen(dir) + 2;
  858. char newpath[newpathlen];
  859. if(dStrlen(path) > 0)
  860. {
  861. dSprintf(newpath, newpathlen, "%s/%s", path, dir);
  862. }
  863. else
  864. {
  865. dSprintf(newpath, newpathlen, "%s", dir);
  866. }
  867. // we have a directory, add it to the list.
  868. if( noBasePath )
  869. {
  870. directoryVector.push_back(StringTable->insert(newpath));
  871. }
  872. else
  873. {
  874. const U32 fullpathlen = dStrlen(aPath) + dStrlen(newpath) + 2;
  875. char fullpath[fullpathlen];
  876. dSprintf(fullpath, fullpathlen, "%s/%s",aPath,newpath);
  877. directoryVector.push_back(StringTable->insert(fullpath));
  878. }
  879. // and recurse into it, unless we've run out of depth
  880. if( depth != 0) // passing a val of -1 as the recurse depth means go forever
  881. recurseDumpDirectories(aPath, newpath, directoryVector, depth-1, noBasePath);
  882. android_GetNextDir(pathbuf, dir);
  883. }
  884. return true;
  885. }*/
  886. //-----------------------------------------------------------------------------
  887. bool recurseDumpDirectoriesCache(const char *basePath, const char *path, Vector<StringTableEntry> &directoryVector, S32 depth, bool noBasePath)
  888. {
  889. DIR *dir;
  890. dirent *entry;
  891. const U32 len = dStrlen(basePath) + dStrlen(path) + 2;
  892. char pathbuf[len];
  893. // construct the file path
  894. dSprintf(pathbuf, len, "%s/%s", basePath, path);
  895. // be sure it opens.
  896. dir = opendir(pathbuf);
  897. if(!dir)
  898. return false;
  899. // look inside the current directory
  900. while( true )
  901. {
  902. entry = readdir(dir);
  903. if ( entry == NULL )
  904. break;
  905. // we just want directories.
  906. if(!isGoodDirectoryCache(entry))
  907. continue;
  908. // TODO: better unicode file name handling
  909. // // Apple's file system stores unicode file names in decomposed form.
  910. // // ATSUI will not reliably draw out just the accent character by itself,
  911. // // so our text renderer has no chance of rendering decomposed form unicode.
  912. // // We have to convert the entry name to precomposed normalized form.
  913. // CFStringRef cfdname = CFStringCreateWithCString(NULL,entry->d_name,kCFStringEncodingUTF8);
  914. // CFMutableStringRef cfentryName = CFStringCreateMutableCopy(NULL,0,cfdname);
  915. // CFStringNormalize(cfentryName,kCFStringNormalizationFormC);
  916. //
  917. // U32 entryNameLen = CFStringGetLength(cfentryName) * 4 + 1;
  918. // char entryName[entryNameLen];
  919. // CFStringGetCString(cfentryName, entryName, entryNameLen, kCFStringEncodingUTF8);
  920. // entryName[entryNameLen-1] = NULL; // sometimes, CFStringGetCString() doesn't null terminate.
  921. // CFRelease(cfentryName);
  922. // CFRelease(cfdname);
  923. // construct the new path string, we'll need this below.
  924. const U32 newpathlen = dStrlen(path) + dStrlen(entry->d_name) + 2;
  925. char newpath[newpathlen];
  926. if(dStrlen(path) > 0)
  927. {
  928. dSprintf(newpath, newpathlen, "%s/%s", path, entry->d_name);
  929. }
  930. else
  931. {
  932. dSprintf(newpath, newpathlen, "%s", entry->d_name);
  933. }
  934. // we have a directory, add it to the list.
  935. if( noBasePath )
  936. {
  937. directoryVector.push_back(StringTable->insert(newpath));
  938. }
  939. else
  940. {
  941. const U32 fullpathlen = dStrlen(basePath) + dStrlen(newpath) + 2;
  942. char fullpath[fullpathlen];
  943. dSprintf(fullpath, fullpathlen, "%s/%s",basePath,newpath);
  944. directoryVector.push_back(StringTable->insert(fullpath));
  945. }
  946. // and recurse into it, unless we've run out of depth
  947. if( depth != 0) // passing a val of -1 as the recurse depth means go forever
  948. recurseDumpDirectoriesCache(basePath, newpath, directoryVector, depth-1, noBasePath);
  949. }
  950. closedir(dir);
  951. return true;
  952. }
  953. //-----------------------------------------------------------------------------
  954. bool Platform::dumpDirectories(const char *path, Vector<StringTableEntry> &directoryVector, S32 depth, bool noBasePath)
  955. {
  956. if (isCachePath(path) || isUserDataPath(path))
  957. {
  958. PROFILE_START(dumpDirectories);
  959. ResourceManager->initExcludedDirectories();
  960. const S32 len = dStrlen(path)+1;
  961. char newpath[len];
  962. dSprintf(newpath, len, "%s", path);
  963. if(newpath[len - 1] == '/')
  964. newpath[len - 1] = '\0'; // cut off the trailing slash, if there is one
  965. // Insert base path to follow what Windows does.
  966. if ( !noBasePath )
  967. directoryVector.push_back(StringTable->insert(newpath));
  968. bool ret = recurseDumpDirectoriesCache(newpath, "", directoryVector, depth, noBasePath);
  969. PROFILE_END();
  970. return ret;
  971. }
  972. PROFILE_START(dumpDirectories);
  973. ResourceManager->initExcludedDirectories();
  974. bool ret = android_DumpDirectories(path, "", directoryVector, depth, noBasePath);
  975. PROFILE_END();
  976. return ret;
  977. }
  978. //-----------------------------------------------------------------------------
  979. /*static bool recurseDumpPath(const char* curPath, Vector<Platform::FileInfo>& fileVector, U32 depth)
  980. {
  981. android_InitDirList(curPath);
  982. // look inside the current directory
  983. char dir[80];
  984. char file[80];
  985. strcpy(dir,"");
  986. strcpy(file,"");
  987. android_GetNextDir(curPath, dir);
  988. android_GetNextFile(curPath, file);
  989. while(strcmp(file,"") != 0)
  990. {
  991. // construct the full file path. we need this to get the file size and to recurse
  992. const U32 len = dStrlen(curPath) + dStrlen(file) + 2;
  993. char pathbuf[len];
  994. dSprintf( pathbuf, len, "%s/%s", curPath, file);
  995. //add the file entry to the list
  996. // unlike recurseDumpDirectories(), we need to return more complex info here.
  997. //<Mat> commented this out in case we ever want a dir file printout again
  998. //printf( "File Name: %s ", entry->d_name );
  999. const U32 fileSize = Platform::getFileSize(pathbuf);
  1000. fileVector.increment();
  1001. Platform::FileInfo& rInfo = fileVector.last();
  1002. rInfo.pFullPath = StringTable->insert(curPath);
  1003. rInfo.pFileName = StringTable->insert(file);
  1004. rInfo.fileSize = fileSize;
  1005. android_GetNextFile(curPath, file);
  1006. }
  1007. while(strcmp(dir,"") != 0)
  1008. {
  1009. // construct the full file path. we need this to get the file size and to recurse
  1010. const U32 len = dStrlen(curPath) + dStrlen(dir) + 2;
  1011. char pathbuf[len];
  1012. if (strcmp(curPath,"") == 0)
  1013. dSprintf( pathbuf, len, "%s", dir);
  1014. else
  1015. dSprintf( pathbuf, len, "%s/%s", curPath, dir);
  1016. if( depth == 0)
  1017. {
  1018. android_GetNextDir(curPath, dir);
  1019. continue;
  1020. }
  1021. // filter out dirs we dont want.
  1022. if( !isGoodDirectory(pathbuf) )
  1023. {
  1024. android_GetNextDir(curPath, dir);
  1025. continue;
  1026. }
  1027. // recurse into the dir
  1028. recurseDumpPath( pathbuf, fileVector, depth-1);
  1029. android_GetNextDir(curPath, dir);
  1030. }
  1031. return true;
  1032. }*/
  1033. //-----------------------------------------------------------------------------
  1034. static bool recurseDumpPathCache(const char* curPath, Vector<Platform::FileInfo>& fileVector, U32 depth)
  1035. {
  1036. DIR *dir;
  1037. dirent *entry;
  1038. // be sure it opens.
  1039. dir = opendir(curPath);
  1040. if(!dir)
  1041. return false;
  1042. // look inside the current directory
  1043. while( true )
  1044. {
  1045. entry = readdir(dir);
  1046. if ( entry == NULL )
  1047. break;
  1048. // construct the full file path. we need this to get the file size and to recurse
  1049. const U32 len = dStrlen(curPath) + entry->d_reclen + 2;
  1050. char pathbuf[255];
  1051. sprintf( pathbuf, "%s/%s", curPath, entry->d_name);
  1052. // ok, deal with directories and files seperately.
  1053. if( entry->d_type == DT_DIR )
  1054. {
  1055. if( depth == 0)
  1056. continue;
  1057. // filter out dirs we dont want.
  1058. if( !isGoodDirectoryCache(entry) )
  1059. continue;
  1060. // recurse into the dir
  1061. recurseDumpPathCache( pathbuf, fileVector, depth-1);
  1062. }
  1063. else
  1064. {
  1065. //add the file entry to the list
  1066. // unlike recurseDumpDirectories(), we need to return more complex info here.
  1067. //<Mat> commented this out in case we ever want a dir file printout again
  1068. //printf( "File Name: %s ", entry->d_name );
  1069. const U32 fileSize = Platform::getFileSize(pathbuf);
  1070. fileVector.increment();
  1071. Platform::FileInfo& rInfo = fileVector.last();
  1072. rInfo.pFullPath = StringTable->insert(curPath);
  1073. rInfo.pFileName = StringTable->insert(entry->d_name);
  1074. rInfo.fileSize = fileSize;
  1075. }
  1076. }
  1077. closedir(dir);
  1078. return true;
  1079. }
  1080. //-----------------------------------------------------------------------------
  1081. bool Platform::dumpPath(const char *path, Vector<Platform::FileInfo>& fileVector, S32 depth)
  1082. {
  1083. if (isCachePath(path) || isUserDataPath(path))
  1084. {
  1085. PROFILE_START(dumpPath);
  1086. const S32 len = dStrlen(path) + 1;
  1087. char newpath[255];
  1088. strcpy(newpath, path);
  1089. if(newpath[len - 2] == '/')
  1090. newpath[len - 2] = '\0'; // cut off the trailing slash, if there is one
  1091. bool ret = recurseDumpPathCache( newpath, fileVector, depth);
  1092. PROFILE_END();
  1093. return ret;
  1094. }
  1095. PROFILE_START(dumpPath);
  1096. bool ret = android_DumpPath( path, fileVector, depth);
  1097. PROFILE_END();
  1098. return ret;
  1099. }
  1100. //-----------------------------------------------------------------------------
  1101. #if defined(TORQUE_DEBUG)
  1102. ConsoleFunction(testHasSubdir,void,2,2,"tests platform::hasSubDirectory") {
  1103. Con::printf("testing %s",argv[1]);
  1104. Platform::addExcludedDirectory(".svn");
  1105. if(Platform::hasSubDirectory(argv[1]))
  1106. Con::printf(" has subdir");
  1107. else
  1108. Con::printf(" does not have subdir");
  1109. }
  1110. ConsoleFunction(testDumpDirectories,void,4,4,"testDumpDirectories('path', int depth, bool noBasePath)") {
  1111. Vector<StringTableEntry> paths;
  1112. S32 depth = dAtoi(argv[2]);
  1113. Platform::addExcludedDirectory(".svn");
  1114. Platform::dumpDirectories(argv[1],paths,dAtoi(argv[2]),dAtob(argv[3]));
  1115. Con::printf("Dumping directories starting from %s with depth %i", argv[1],depth);
  1116. for(Vector<StringTableEntry>::iterator itr = paths.begin(); itr != paths.end(); itr++) {
  1117. Con::printf(*itr);
  1118. }
  1119. }
  1120. ConsoleFunction(testDumpPaths, void, 3, 3, "testDumpPaths('path', int depth)")
  1121. {
  1122. Vector<Platform::FileInfo> files;
  1123. S32 depth = dAtoi(argv[2]);
  1124. Platform::addExcludedDirectory(".svn");
  1125. Platform::dumpPath(argv[1], files, depth);
  1126. for(Vector<Platform::FileInfo>::iterator itr = files.begin(); itr != files.end(); itr++) {
  1127. Con::printf("%s/%s",itr->pFullPath, itr->pFileName);
  1128. }
  1129. }
  1130. //-----------------------------------------------------------------------------
  1131. ConsoleFunction(testFileTouch, bool , 2,2, "testFileTouch('path')")
  1132. {
  1133. return dFileTouch(argv[1]);
  1134. }
  1135. ConsoleFunction(testGetFileTimes, bool, 2,2, "testGetFileTimes('path')")
  1136. {
  1137. FileTime create, modify;
  1138. bool ok;
  1139. ok = Platform::getFileTimes(argv[1],&create, &modify);
  1140. Con::printf("%s Platform::getFileTimes %i, %i", ok ? "+OK" : "-FAIL", create, modify);
  1141. return ok;
  1142. }
  1143. #endif