macCarbFileio.mm 30 KB

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