macCarbFileio.mm 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 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 <stdio.h>
  23. #include <stdlib.h>
  24. #include <errno.h>
  25. #include <utime.h>
  26. #include <sys/time.h>
  27. #include <sys/types.h>
  28. #include <dirent.h>
  29. #include <unistd.h>
  30. #include <sys/stat.h>
  31. // Get our GL header included before Apple's
  32. #include "platformMac/platformMacCarb.h"
  33. // Don't include Apple's
  34. #define __gl_h_
  35. #include "platform/tmm_off.h"
  36. #include <Cocoa/Cocoa.h>
  37. #include "platform/tmm_on.h"
  38. #include "core/fileio.h"
  39. #include "core/util/tVector.h"
  40. #include "core/stringTable.h"
  41. #include "core/strings/stringFunctions.h"
  42. #include "console/console.h"
  43. #include "platform/profiler.h"
  44. #include "cinterface/cinterface.h";
  45. #include "core/volume.h"
  46. //TODO: file io still needs some work...
  47. #define MAX_MAC_PATH_LONG 2048
  48. //-----------------------------------------------------------------------------
  49. #if defined(TORQUE_OS_MAC)
  50. #include <CoreFoundation/CFBundle.h>
  51. #else
  52. #include <CFBundle.h>
  53. #endif
  54. //-----------------------------------------------------------------------------
  55. bool dFileDelete(const char * name)
  56. {
  57. if(!name )
  58. return(false);
  59. if (dStrlen(name) > MAX_MAC_PATH_LONG)
  60. Con::warnf("dFileDelete: Filename length is pretty long...");
  61. return(remove(name) == 0); // remove returns 0 on success
  62. }
  63. //-----------------------------------------------------------------------------
  64. bool dFileTouch(const char *path)
  65. {
  66. if (!path || !*path)
  67. return false;
  68. // set file at path's modification and access times to now.
  69. return( utimes( path, NULL) == 0); // utimes returns 0 on success.
  70. }
  71. //-----------------------------------------------------------------------------
  72. // Constructors & Destructor
  73. //-----------------------------------------------------------------------------
  74. //-----------------------------------------------------------------------------
  75. // After construction, the currentStatus will be Closed and the capabilities
  76. // will be 0.
  77. //-----------------------------------------------------------------------------
  78. File::File()
  79. : currentStatus(Closed), capability(0)
  80. {
  81. handle = NULL;
  82. }
  83. //-----------------------------------------------------------------------------
  84. // insert a copy constructor here... (currently disabled)
  85. //-----------------------------------------------------------------------------
  86. //-----------------------------------------------------------------------------
  87. // Destructor
  88. //-----------------------------------------------------------------------------
  89. File::~File()
  90. {
  91. close();
  92. handle = NULL;
  93. }
  94. //-----------------------------------------------------------------------------
  95. // Open a file in the mode specified by openMode (Read, Write, or ReadWrite).
  96. // Truncate the file if the mode is either Write or ReadWrite and truncate is
  97. // true.
  98. //
  99. // Sets capability appropriate to the openMode.
  100. // Returns the currentStatus of the file.
  101. //-----------------------------------------------------------------------------
  102. File::Status File::open(const char *filename, const AccessMode openMode)
  103. {
  104. if (dStrlen(filename) > MAX_MAC_PATH_LONG)
  105. Con::warnf("File::open: Filename length is pretty long...");
  106. // Close the file if it was already open...
  107. if (currentStatus != Closed)
  108. close();
  109. // create the appropriate type of file...
  110. switch (openMode)
  111. {
  112. case Read:
  113. handle = (void *)fopen(filename, "rb"); // read only
  114. break;
  115. case Write:
  116. handle = (void *)fopen(filename, "wb"); // write only
  117. break;
  118. case ReadWrite:
  119. handle = (void *)fopen(filename, "ab+"); // write(append) and read
  120. break;
  121. case WriteAppend:
  122. handle = (void *)fopen(filename, "ab"); // write(append) only
  123. break;
  124. default:
  125. AssertFatal(false, "File::open: bad access mode");
  126. }
  127. // handle not created successfully
  128. if (handle == NULL)
  129. return setStatus();
  130. // successfully created file, so set the file capabilities...
  131. switch (openMode)
  132. {
  133. case Read:
  134. capability = FileRead;
  135. break;
  136. case Write:
  137. case WriteAppend:
  138. capability = FileWrite;
  139. break;
  140. case ReadWrite:
  141. capability = FileRead | FileWrite;
  142. break;
  143. default:
  144. AssertFatal(false, "File::open: bad access mode");
  145. }
  146. // must set the file status before setting the position.
  147. currentStatus = Ok;
  148. if (openMode == ReadWrite)
  149. setPosition(0);
  150. // success!
  151. return currentStatus;
  152. }
  153. //-----------------------------------------------------------------------------
  154. // Get the current position of the file pointer.
  155. //-----------------------------------------------------------------------------
  156. U32 File::getPosition() const
  157. {
  158. AssertFatal(currentStatus != Closed , "File::getPosition: file closed");
  159. AssertFatal(handle != NULL, "File::getPosition: invalid file handle");
  160. return ftell((FILE*)handle);
  161. }
  162. //-----------------------------------------------------------------------------
  163. // Set the position of the file pointer.
  164. // Absolute and relative positioning is supported via the absolutePos
  165. // parameter.
  166. //
  167. // If positioning absolutely, position MUST be positive - an IOError results if
  168. // position is negative.
  169. // Position can be negative if positioning relatively, however positioning
  170. // before the start of the file is an IOError.
  171. //
  172. // Returns the currentStatus of the file.
  173. //-----------------------------------------------------------------------------
  174. File::Status File::setPosition(S32 position, bool absolutePos)
  175. {
  176. AssertFatal(Closed != currentStatus, "File::setPosition: file closed");
  177. AssertFatal(handle != NULL, "File::setPosition: invalid file handle");
  178. if (currentStatus != Ok && currentStatus != EOS )
  179. return currentStatus;
  180. U32 finalPos;
  181. if(absolutePos)
  182. {
  183. // absolute position
  184. AssertFatal(0 <= position, "File::setPosition: negative absolute position");
  185. // position beyond EOS is OK
  186. fseek((FILE*)handle, position, SEEK_SET);
  187. finalPos = ftell((FILE*)handle);
  188. }
  189. else
  190. {
  191. // relative position
  192. AssertFatal((getPosition() + position) >= 0, "File::setPosition: negative relative position");
  193. // position beyond EOS is OK
  194. fseek((FILE*)handle, position, SEEK_CUR);
  195. finalPos = ftell((FILE*)handle);
  196. }
  197. // ftell returns -1 on error. set error status
  198. if (0xffffffff == finalPos)
  199. return setStatus();
  200. // success, at end of file
  201. else if (finalPos >= getSize())
  202. return currentStatus = EOS;
  203. // success!
  204. else
  205. return currentStatus = Ok;
  206. }
  207. //-----------------------------------------------------------------------------
  208. // Get the size of the file in bytes.
  209. // It is an error to query the file size for a Closed file, or for one with an
  210. // error status.
  211. //-----------------------------------------------------------------------------
  212. U32 File::getSize() const
  213. {
  214. AssertWarn(Closed != currentStatus, "File::getSize: file closed");
  215. AssertFatal(handle != NULL, "File::getSize: invalid file handle");
  216. if (Ok == currentStatus || EOS == currentStatus)
  217. {
  218. struct stat statData;
  219. if(fstat(fileno((FILE*)handle), &statData) != 0)
  220. return 0;
  221. // return the size in bytes
  222. return statData.st_size;
  223. }
  224. return 0;
  225. }
  226. //-----------------------------------------------------------------------------
  227. // Flush the file.
  228. // It is an error to flush a read-only file.
  229. // Returns the currentStatus of the file.
  230. //-----------------------------------------------------------------------------
  231. File::Status File::flush()
  232. {
  233. AssertFatal(Closed != currentStatus, "File::flush: file closed");
  234. AssertFatal(handle != NULL, "File::flush: invalid file handle");
  235. AssertFatal(true == hasCapability(FileWrite), "File::flush: cannot flush a read-only file");
  236. if (fflush((FILE*)handle) != 0)
  237. return setStatus();
  238. else
  239. return currentStatus = Ok;
  240. }
  241. //-----------------------------------------------------------------------------
  242. // Close the File.
  243. //
  244. // Returns the currentStatus
  245. //-----------------------------------------------------------------------------
  246. File::Status File::close()
  247. {
  248. // check if it's already closed...
  249. if (Closed == currentStatus)
  250. return currentStatus;
  251. // it's not, so close it...
  252. if (handle != NULL)
  253. {
  254. if (fclose((FILE*)handle) != 0)
  255. return setStatus();
  256. }
  257. handle = NULL;
  258. return currentStatus = Closed;
  259. }
  260. //-----------------------------------------------------------------------------
  261. // Self-explanatory.
  262. //-----------------------------------------------------------------------------
  263. File::Status File::getStatus() const
  264. {
  265. return currentStatus;
  266. }
  267. //-----------------------------------------------------------------------------
  268. // Sets and returns the currentStatus when an error has been encountered.
  269. //-----------------------------------------------------------------------------
  270. File::Status File::setStatus()
  271. {
  272. switch (errno)
  273. {
  274. case EACCES: // permission denied
  275. currentStatus = IOError;
  276. break;
  277. case EBADF: // Bad File Pointer
  278. case EINVAL: // Invalid argument
  279. case ENOENT: // file not found
  280. case ENAMETOOLONG:
  281. default:
  282. currentStatus = UnknownError;
  283. }
  284. return currentStatus;
  285. }
  286. //-----------------------------------------------------------------------------
  287. // Sets and returns the currentStatus to status.
  288. //-----------------------------------------------------------------------------
  289. File::Status File::setStatus(File::Status status)
  290. {
  291. return currentStatus = status;
  292. }
  293. //-----------------------------------------------------------------------------
  294. // Read from a file.
  295. // The number of bytes to read is passed in size, the data is returned in src.
  296. // The number of bytes read is available in bytesRead if a non-Null pointer is
  297. // provided.
  298. //-----------------------------------------------------------------------------
  299. File::Status File::read(U32 size, char *dst, U32 *bytesRead)
  300. {
  301. AssertFatal(Closed != currentStatus, "File::read: file closed");
  302. AssertFatal(handle != NULL, "File::read: invalid file handle");
  303. AssertFatal(NULL != dst, "File::read: NULL destination pointer");
  304. AssertFatal(true == hasCapability(FileRead), "File::read: file lacks capability");
  305. AssertWarn(0 != size, "File::read: size of zero");
  306. if (Ok != currentStatus || 0 == size)
  307. return currentStatus;
  308. // read from stream
  309. U32 nBytes = fread(dst, 1, size, (FILE*)handle);
  310. // did we hit the end of the stream?
  311. if( nBytes != size)
  312. currentStatus = EOS;
  313. // if bytesRead is a valid pointer, send number of bytes read there.
  314. if(bytesRead)
  315. *bytesRead = nBytes;
  316. // successfully read size bytes
  317. return currentStatus;
  318. }
  319. //-----------------------------------------------------------------------------
  320. // Write to a file.
  321. // The number of bytes to write is passed in size, the data is passed in src.
  322. // The number of bytes written is available in bytesWritten if a non-Null
  323. // pointer is provided.
  324. //-----------------------------------------------------------------------------
  325. File::Status File::write(U32 size, const char *src, U32 *bytesWritten)
  326. {
  327. AssertFatal(Closed != currentStatus, "File::write: file closed");
  328. AssertFatal(handle != NULL, "File::write: invalid file handle");
  329. AssertFatal(NULL != src, "File::write: NULL source pointer");
  330. AssertFatal(true == hasCapability(FileWrite), "File::write: file lacks capability");
  331. AssertWarn(0 != size, "File::write: size of zero");
  332. if ((Ok != currentStatus && EOS != currentStatus) || 0 == size)
  333. return currentStatus;
  334. // write bytes to the stream
  335. U32 nBytes = fwrite(src, 1, size,(FILE*)handle);
  336. // if we couldn't write everything, we've got a problem. set error status.
  337. if(nBytes != size)
  338. setStatus();
  339. // if bytesWritten is a valid pointer, put number of bytes read there.
  340. if(bytesWritten)
  341. *bytesWritten = nBytes;
  342. // return current File status, whether good or ill.
  343. return currentStatus;
  344. }
  345. //-----------------------------------------------------------------------------
  346. // Self-explanatory.
  347. //-----------------------------------------------------------------------------
  348. bool File::hasCapability(Capability cap) const
  349. {
  350. return (0 != (U32(cap) & capability));
  351. }
  352. //-----------------------------------------------------------------------------
  353. S32 Platform::compareFileTimes(const FileTime &a, const FileTime &b)
  354. {
  355. if(a > b)
  356. return 1;
  357. if(a < b)
  358. return -1;
  359. return 0;
  360. }
  361. //-----------------------------------------------------------------------------
  362. // either time param COULD be null.
  363. //-----------------------------------------------------------------------------
  364. bool Platform::getFileTimes(const char *path, FileTime *createTime, FileTime *modifyTime)
  365. {
  366. // MacOSX is NOT guaranteed to be running off a HFS volume,
  367. // and UNIX does not keep a record of a file's creation time anywhere.
  368. // So instead of creation time we return changed time,
  369. // just like the Linux platform impl does.
  370. if (!path || !*path)
  371. return false;
  372. struct stat statData;
  373. if (stat(path, &statData) == -1)
  374. return false;
  375. if(createTime)
  376. *createTime = statData.st_ctime;
  377. if(modifyTime)
  378. *modifyTime = statData.st_mtime;
  379. return true;
  380. }
  381. //-----------------------------------------------------------------------------
  382. bool Platform::createPath(const char *file)
  383. {
  384. // if the path exists, we're done.
  385. struct stat statData;
  386. if( stat(file, &statData) == 0 )
  387. {
  388. return true; // exists, rejoice.
  389. }
  390. Con::warnf( "creating path %s", file );
  391. // get the parent path.
  392. // we're not using basename because it's not thread safe.
  393. U32 len = dStrlen(file);
  394. char parent[len];
  395. bool isDirPath = false;
  396. dStrncpy(parent,file,len);
  397. parent[len] = '\0';
  398. if(parent[len - 1] == '/')
  399. {
  400. parent[len - 1] = '\0'; // cut off the trailing slash, if there is one
  401. isDirPath = true; // we got a trailing slash, so file is a directory.
  402. }
  403. // recusively create the parent path.
  404. // only recurse if newpath has a slash that isn't a leading slash.
  405. char *slash = dStrrchr(parent,'/');
  406. if( slash && slash != parent)
  407. {
  408. // snip the path just after the last slash.
  409. slash[1] = '\0';
  410. // recusively create the parent path. fail if parent path creation failed.
  411. if(!Platform::createPath(parent))
  412. return false;
  413. }
  414. // create *file if it is a directory path.
  415. if(isDirPath)
  416. {
  417. // try to create the directory
  418. if( mkdir(file, 0777) != 0) // app may reside in global apps dir, and so must be writable to all.
  419. return false;
  420. }
  421. return true;
  422. }
  423. //-----------------------------------------------------------------------------
  424. bool Platform::cdFileExists(const char *filePath, const char *volumeName, S32 serialNum)
  425. {
  426. return true;
  427. }
  428. #pragma mark ---- Directories ----
  429. //-----------------------------------------------------------------------------
  430. StringTableEntry Platform::getCurrentDirectory()
  431. {
  432. // get the current directory, the one that would be opened if we did a fopen(".")
  433. char* cwd = getcwd(NULL, 0);
  434. StringTableEntry ret = StringTable->insert(cwd);
  435. free(cwd);
  436. return ret;
  437. }
  438. //-----------------------------------------------------------------------------
  439. bool Platform::setCurrentDirectory(StringTableEntry newDir)
  440. {
  441. return (chdir(newDir) == 0);
  442. }
  443. //-----------------------------------------------------------------------------
  444. void Platform::openFolder(const char* path )
  445. {
  446. // TODO: users can still run applications by calling openfolder on an app bundle.
  447. // this may be a bad thing.
  448. if(!Platform::isDirectory(path))
  449. {
  450. Con::errorf(avar("Error: not a directory: %s",path));
  451. return;
  452. }
  453. const char* arg = avar("open '%s'", path);
  454. U32 ret = system(arg);
  455. if(ret != 0)
  456. Con::printf(strerror(errno));
  457. }
  458. void Platform::openFile(const char* path )
  459. {
  460. if( !Platform::isFile( path ) )
  461. {
  462. Con::errorf( avar( "Error: not a file: %s", path ) );
  463. return;
  464. }
  465. const char* arg = avar( "open '%s'", path );
  466. U32 ret = system( arg );
  467. if( ret != 0 )
  468. Con::printf( strerror( errno ) );
  469. }
  470. // helper func for getWorkingDirectory
  471. bool isMainDotCsPresent(NSString* dir)
  472. {
  473. return [[NSFileManager defaultManager] fileExistsAtPath:[dir stringByAppendingPathComponent:@"main.cs"]] == YES;
  474. }
  475. //-----------------------------------------------------------------------------
  476. /// Finds and sets the current working directory.
  477. /// Torque tries to automatically detect whether you have placed the game files
  478. /// inside or outside the application's bundle. It checks for the presence of
  479. /// the file 'main.cs'. If it finds it, Torque will assume that the other game
  480. /// files are there too. If Torque does not see 'main.cs' inside its bundle, it
  481. /// will assume the files are outside the bundle.
  482. /// Since you probably don't want to copy the game files into the app every time
  483. /// you build, you will want to leave them outside the bundle for development.
  484. ///
  485. /// Placing all content inside the application bundle gives a much better user
  486. /// experience when you distribute your app.
  487. StringTableEntry Platform::getExecutablePath()
  488. {
  489. static const char* cwd = NULL;
  490. // this isn't actually being used due to some static constructors at bundle load time
  491. // calling this method (before there is a chance to set it)
  492. // for instance, FMOD sound provider (this should be fixed in FMOD as it is with windows)
  493. if (!cwd && torque_getexecutablepath())
  494. {
  495. // we're in a plugin using the cinterface
  496. cwd = torque_getexecutablepath();
  497. chdir(cwd);
  498. }
  499. else if(!cwd)
  500. {
  501. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  502. //first check the cwd for main.cs
  503. static char buf[4096];
  504. NSString* currentDir = [[NSString alloc ] initWithCString:getcwd(buf,(4096 * sizeof(char))) ];
  505. if (isMainDotCsPresent(currentDir))
  506. {
  507. cwd = buf;
  508. [pool release];
  509. return cwd;
  510. }
  511. NSString* string = [[NSBundle mainBundle] pathForResource:@"main" ofType:@"cs"];
  512. if(!string)
  513. string = [[NSBundle mainBundle] bundlePath];
  514. string = [string stringByDeletingLastPathComponent];
  515. AssertISV(isMainDotCsPresent(string), "Platform::getExecutablePath - Failed to find main.cs!");
  516. cwd = dStrdup([string UTF8String]);
  517. chdir(cwd);
  518. [pool release];
  519. }
  520. return cwd;
  521. }
  522. //-----------------------------------------------------------------------------
  523. StringTableEntry Platform::getExecutableName()
  524. {
  525. static const char* name = NULL;
  526. if(!name)
  527. name = [[[[NSBundle mainBundle] bundlePath] lastPathComponent] UTF8String];
  528. return name;
  529. }
  530. //-----------------------------------------------------------------------------
  531. bool Platform::isFile(const char *path)
  532. {
  533. if (!path || !*path)
  534. return false;
  535. // make sure we can stat the file
  536. struct stat statData;
  537. if( stat(path, &statData) < 0 )
  538. {
  539. // Since file does not exist on disk see if it exists in a zip file loaded
  540. return Torque::FS::IsFile(path);
  541. }
  542. // now see if it's a regular file
  543. if( (statData.st_mode & S_IFMT) == S_IFREG)
  544. return true;
  545. return false;
  546. }
  547. //-----------------------------------------------------------------------------
  548. bool Platform::isDirectory(const char *path)
  549. {
  550. if (!path || !*path)
  551. return false;
  552. // make sure we can stat the file
  553. struct stat statData;
  554. if( stat(path, &statData) < 0 )
  555. return false;
  556. // now see if it's a directory
  557. if( (statData.st_mode & S_IFMT) == S_IFDIR)
  558. return true;
  559. return false;
  560. }
  561. S32 Platform::getFileSize(const char* pFilePath)
  562. {
  563. if (!pFilePath || !*pFilePath)
  564. return 0;
  565. struct stat statData;
  566. if( stat(pFilePath, &statData) < 0 )
  567. return 0;
  568. // and return it's size in bytes
  569. return (S32)statData.st_size;
  570. }
  571. //-----------------------------------------------------------------------------
  572. bool Platform::isSubDirectory(const char *pathParent, const char *pathSub)
  573. {
  574. char fullpath[MAX_MAC_PATH_LONG];
  575. dStrcpyl(fullpath, MAX_MAC_PATH_LONG, pathParent, "/", pathSub, NULL);
  576. return isDirectory((const char *)fullpath);
  577. }
  578. //-----------------------------------------------------------------------------
  579. // utility for platform::hasSubDirectory() and platform::dumpDirectories()
  580. // ensures that the entry is a directory, and isnt on the ignore lists.
  581. inline bool isGoodDirectory(dirent* entry)
  582. {
  583. return (entry->d_type == DT_DIR // is a dir
  584. && dStrcmp(entry->d_name,".") != 0 // not here
  585. && dStrcmp(entry->d_name,"..") != 0 // not parent
  586. && !Platform::isExcludedDirectory(entry->d_name)); // not excluded
  587. }
  588. //-----------------------------------------------------------------------------
  589. bool Platform::hasSubDirectory(const char *path)
  590. {
  591. DIR *dir;
  592. dirent *entry;
  593. dir = opendir(path);
  594. if(!dir)
  595. return false; // we got a bad path, so no, it has no subdirectory.
  596. while( (entry = readdir(dir)) )
  597. {
  598. if(isGoodDirectory(entry) )
  599. {
  600. closedir(dir);
  601. return true; // we have a subdirectory, that isnt on the exclude list.
  602. }
  603. }
  604. closedir(dir);
  605. return false; // either this dir had no subdirectories, or they were all on the exclude list.
  606. }
  607. //-----------------------------------------------------------------------------
  608. bool recurseDumpDirectories(const char *basePath, const char *path, Vector<StringTableEntry> &directoryVector, S32 depth, bool noBasePath)
  609. {
  610. DIR *dir;
  611. dirent *entry;
  612. U32 len = dStrlen(basePath) + dStrlen(path) + 2;
  613. char pathbuf[len];
  614. // construct the file path
  615. dSprintf(pathbuf, len, "%s/%s", basePath, path);
  616. pathbuf[len] = '\0';
  617. // be sure it opens.
  618. dir = opendir(pathbuf);
  619. if(!dir)
  620. return false;
  621. // look inside the current directory
  622. while( (entry = readdir(dir)) )
  623. {
  624. // we just want directories.
  625. if(!isGoodDirectory(entry))
  626. continue;
  627. // TODO: better unicode file name handling
  628. // // Apple's file system stores unicode file names in decomposed form.
  629. // // ATSUI will not reliably draw out just the accent character by itself,
  630. // // so our text renderer has no chance of rendering decomposed form unicode.
  631. // // We have to convert the entry name to precomposed normalized form.
  632. // CFStringRef cfdname = CFStringCreateWithCString(NULL,entry->d_name,kCFStringEncodingUTF8);
  633. // CFMutableStringRef cfentryName = CFStringCreateMutableCopy(NULL,0,cfdname);
  634. // CFStringNormalize(cfentryName,kCFStringNormalizationFormC);
  635. //
  636. // U32 entryNameLen = CFStringGetLength(cfentryName) * 4 + 1;
  637. // char entryName[entryNameLen];
  638. // CFStringGetCString(cfentryName, entryName, entryNameLen, kCFStringEncodingUTF8);
  639. // entryName[entryNameLen-1] = NULL; // sometimes, CFStringGetCString() doesn't null terminate.
  640. // CFRelease(cfentryName);
  641. // CFRelease(cfdname);
  642. // construct the new path string, we'll need this below.
  643. U32 newpathlen = dStrlen(path) + dStrlen(entry->d_name) + 2;
  644. char newpath[newpathlen];
  645. if(dStrlen(path) > 0) // prevent extra slashes in the path
  646. dSprintf(newpath, newpathlen,"%s/%s",path,entry->d_name);
  647. else
  648. dStrncpy(newpath,entry->d_name, newpathlen);
  649. newpath[newpathlen] = '\0';
  650. // we have a directory, add it to the list.
  651. if( noBasePath )
  652. directoryVector.push_back(StringTable->insert(newpath));
  653. else {
  654. U32 fullpathlen = dStrlen(basePath) + dStrlen(newpath) + 2;
  655. char fullpath[fullpathlen];
  656. dSprintf(fullpath,fullpathlen,"%s/%s",basePath,newpath);
  657. fullpath[fullpathlen] = '\0';
  658. directoryVector.push_back(StringTable->insert(fullpath));
  659. }
  660. // and recurse into it, unless we've run out of depth
  661. if( depth != 0) // passing a val of -1 as the recurse depth means go forever
  662. recurseDumpDirectories(basePath, newpath, directoryVector, depth-1, noBasePath);
  663. }
  664. closedir(dir);
  665. return true;
  666. }
  667. //-----------------------------------------------------------------------------
  668. bool Platform::dumpDirectories(const char *path, Vector<StringTableEntry> &directoryVector, S32 depth, bool noBasePath)
  669. {
  670. PROFILE_START(dumpDirectories);
  671. int len = dStrlen(path);
  672. char newpath[len];
  673. dStrncpy(newpath,path,len);
  674. newpath[len] = '\0';
  675. if(newpath[len - 1] == '/')
  676. newpath[len - 1] = '\0'; // cut off the trailing slash, if there is one
  677. bool ret = recurseDumpDirectories(newpath, "", directoryVector, depth, noBasePath);
  678. PROFILE_END();
  679. return ret;
  680. }
  681. //-----------------------------------------------------------------------------
  682. static bool recurseDumpPath(const char* curPath, Vector<Platform::FileInfo>& fileVector, U32 depth)
  683. {
  684. DIR *dir;
  685. dirent *entry;
  686. // be sure it opens.
  687. dir = opendir(curPath);
  688. if(!dir)
  689. return false;
  690. // look inside the current directory
  691. while( (entry = readdir(dir)) )
  692. {
  693. // construct the full file path. we need this to get the file size and to recurse
  694. U32 len = dStrlen(curPath) + entry->d_namlen + 2;
  695. char pathbuf[len];
  696. dSprintf( pathbuf, len, "%s/%s", curPath, entry->d_name);
  697. pathbuf[len] = '\0';
  698. // ok, deal with directories and files seperately.
  699. if( entry->d_type == DT_DIR )
  700. {
  701. if( depth == 0)
  702. continue;
  703. // filter out dirs we dont want.
  704. if( !isGoodDirectory(entry) )
  705. continue;
  706. // recurse into the dir
  707. recurseDumpPath( pathbuf, fileVector, depth-1);
  708. }
  709. else
  710. {
  711. //add the file entry to the list
  712. // unlike recurseDumpDirectories(), we need to return more complex info here.
  713. U32 fileSize = Platform::getFileSize(pathbuf);
  714. fileVector.increment();
  715. Platform::FileInfo& rInfo = fileVector.last();
  716. rInfo.pFullPath = StringTable->insert(curPath);
  717. rInfo.pFileName = StringTable->insert(entry->d_name);
  718. rInfo.fileSize = fileSize;
  719. }
  720. }
  721. closedir(dir);
  722. return true;
  723. }
  724. //-----------------------------------------------------------------------------
  725. bool Platform::dumpPath(const char *path, Vector<Platform::FileInfo>& fileVector, S32 depth)
  726. {
  727. PROFILE_START(dumpPath);
  728. int len = dStrlen(path);
  729. char newpath[len+1];
  730. dStrncpy(newpath,path,len);
  731. newpath[len] = '\0'; // null terminate
  732. if(newpath[len - 1] == '/')
  733. newpath[len - 1] = '\0'; // cut off the trailing slash, if there is one
  734. bool ret = recurseDumpPath( newpath, fileVector, depth);
  735. PROFILE_END();
  736. return ret;
  737. }
  738. // TODO: implement stringToFileTime()
  739. bool Platform::stringToFileTime(const char * string, FileTime * time) { return false;}
  740. // TODO: implement fileTimeToString()
  741. bool Platform::fileTimeToString(FileTime * time, char * string, U32 strLen) { return false;}
  742. //-----------------------------------------------------------------------------
  743. #if defined(TORQUE_DEBUG)
  744. ConsoleFunction(testHasSubdir,void,2,2,"tests platform::hasSubDirectory") {
  745. Con::printf("testing %s",argv[1]);
  746. Platform::addExcludedDirectory(".svn");
  747. if(Platform::hasSubDirectory(argv[1]))
  748. Con::printf(" has subdir");
  749. else
  750. Con::printf(" does not have subdir");
  751. }
  752. ConsoleFunction(testDumpDirectories,void,4,4,"testDumpDirectories('path', int depth, bool noBasePath)") {
  753. Vector<StringTableEntry> paths;
  754. const S32 depth = dAtoi(argv[2]);
  755. const bool noBasePath = dAtob(argv[3]);
  756. Platform::addExcludedDirectory(".svn");
  757. Platform::dumpDirectories(argv[1], paths, depth, noBasePath);
  758. Con::printf("Dumping directories starting from %s with depth %i", argv[1],depth);
  759. for(Vector<StringTableEntry>::iterator itr = paths.begin(); itr != paths.end(); itr++) {
  760. Con::printf(*itr);
  761. }
  762. }
  763. ConsoleFunction(testDumpPaths, void, 3, 3, "testDumpPaths('path', int depth)")
  764. {
  765. Vector<Platform::FileInfo> files;
  766. S32 depth = dAtoi(argv[2]);
  767. Platform::addExcludedDirectory(".svn");
  768. Platform::dumpPath(argv[1], files, depth);
  769. for(Vector<Platform::FileInfo>::iterator itr = files.begin(); itr != files.end(); itr++) {
  770. Con::printf("%s/%s",itr->pFullPath, itr->pFileName);
  771. }
  772. }
  773. //-----------------------------------------------------------------------------
  774. ConsoleFunction(testFileTouch, bool , 2,2, "testFileTouch('path')")
  775. {
  776. return dFileTouch(argv[1]);
  777. }
  778. ConsoleFunction(testGetFileTimes, bool, 2,2, "testGetFileTimes('path')")
  779. {
  780. FileTime create, modify;
  781. bool ok = Platform::getFileTimes(argv[1], &create, &modify);
  782. Con::printf("%s Platform::getFileTimes %i, %i", ok ? "+OK" : "-FAIL", create, modify);
  783. return ok;
  784. }
  785. #endif