macFileIO.mm 34 KB

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