osxFileIO.mm 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2013 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #import "platformOSX/platformOSX.h"
  23. #include "platform/platform.h"
  24. #include "platform/platformFileIO.h"
  25. #include "console/console.h"
  26. #include "string/stringTable.h"
  27. #include "collection/vector.h"
  28. #include "io/resource/resourceManager.h"
  29. #include "debug/profiler.h"
  30. #include <sys/stat.h>
  31. #include <sys/time.h>
  32. // Maximum character length for file paths
  33. #define MAX_MAC_PATH_LONG 2048
  34. #pragma mark ---- File IO Helper Functions ----
  35. //-----------------------------------------------------------------------------
  36. //-----------------------------------------------------------------------------
  37. // utility for platform::hasSubDirectory() and platform::dumpDirectories()
  38. // ensures that the entry is a directory, and isnt on the ignore lists.
  39. inline bool isGoodDirectory(const char* path)
  40. {
  41. return (Platform::isDirectory(path) // is a dir
  42. && dStrcmp(path,".") != 0 // not here
  43. && dStrcmp(path,"..") != 0 // not parent
  44. && !Platform::isExcludedDirectory(path)); // not excluded
  45. }
  46. //-----------------------------------------------------------------------------
  47. static bool isMainDotCsPresent(char *dir)
  48. {
  49. // Create the full path using the dir and pre-determined script name of "main.cs"
  50. char maincsbuf[MAX_MAC_PATH_LONG];
  51. const char *maincsname = "/main.cs";
  52. U32 len = dStrlen(dir) + dStrlen(maincsname);
  53. // The length of the above file path is too long. Error out
  54. AssertISV(len < MAX_MAC_PATH_LONG, "Sorry, path is too long, I can't run from this folder.");
  55. // Store the main.cs file path
  56. dSprintf(maincsbuf,MAX_MAC_PATH_LONG,"%s%s", dir, maincsname);
  57. // Check to see if the main.cs exists and return the result
  58. return Platform::isFile(maincsbuf);
  59. }
  60. //-----------------------------------------------------------------------------
  61. void recurseDumpDirectories(const char* basePath, const char* subPath, Vector<StringTableEntry> &directoryVector, S32 depth, bool noBasePath)
  62. {
  63. // Is path a directory?
  64. if ( !Platform::isDirectory(basePath) )
  65. {
  66. // No, so warn.
  67. Con::warnf("recurseDumpDirectories: %s is not a directory", basePath);
  68. // Fnish.
  69. return;
  70. }
  71. // Fetch the shared file manager.
  72. NSFileManager* fileManager = [NSFileManager defaultManager];
  73. // Convert the path to a NSString, then to a URL
  74. NSString* directoryPath = [[NSString stringWithFormat:@"%s/%s", basePath, subPath] stringByStandardizingPath];
  75. NSURL* directoryToScan = [NSURL fileURLWithPath:directoryPath];
  76. // Fetch the contents of the directory.
  77. NSArray* directoryContents = [fileManager contentsOfDirectoryAtURL:directoryToScan
  78. includingPropertiesForKeys:[NSArray arrayWithObjects:NSURLNameKey, NSURLIsDirectoryKey, NSURLIsPackageKey, nil]
  79. options:NSDirectoryEnumerationSkipsHiddenFiles
  80. error:nil];
  81. // Enumerate sub-directories.
  82. for( NSURL* itemURL in directoryContents )
  83. {
  84. // Is the item a directory?
  85. NSNumber* isDirectory;
  86. [itemURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL];
  87. // Skip if this is NOT a directory.
  88. if ( [isDirectory boolValue] == false )
  89. continue;
  90. // Is the sub-directory a package directory?
  91. NSNumber* isPackageDirectory;
  92. [itemURL getResourceValue:&isPackageDirectory forKey:NSURLIsPackageKey error:NULL];
  93. // Skip if this is a package directory.
  94. if ( [isPackageDirectory boolValue] == true )
  95. continue;
  96. // Fetch the sub-directory name.
  97. NSString* subDirectoryName;
  98. [itemURL getResourceValue:&subDirectoryName forKey:NSURLNameKey error:NULL];
  99. // Fetch UTF8 sub-directory name.
  100. const char* pSubDirectoryName = [subDirectoryName UTF8String];
  101. // Skip if this is an excluded directory.
  102. if ( Platform::isExcludedDirectory(pSubDirectoryName) )
  103. continue;
  104. // Format new sub-path.
  105. const char* pNewSubPath = dStrlen(subPath) > 0 ? [[NSString stringWithFormat:@"%s/%s", subPath, pSubDirectoryName] UTF8String] : pSubDirectoryName;
  106. // Format with base-path accordingly.
  107. if ( noBasePath )
  108. {
  109. // Store the directory excluding the base-path.
  110. directoryVector.push_back( StringTable->insert( pNewSubPath ) );
  111. }
  112. else
  113. {
  114. // Store the directory with the bath-path.
  115. directoryVector.push_back( StringTable->insert( [[itemURL path] UTF8String] ) );
  116. }
  117. // Skip if no further recursion is required.
  118. if ( depth == 0 )
  119. continue;
  120. // Recurse the sub-directory.
  121. recurseDumpDirectories( basePath, pNewSubPath, directoryVector, depth-1, noBasePath );
  122. }
  123. }
  124. //-----------------------------------------------------------------------------
  125. static void recurseDumpPath(const char* curPath, Vector<Platform::FileInfo>& fileVector, U32 depth)
  126. {
  127. // Fetch the shared file manager.
  128. NSFileManager* fileManager = [NSFileManager defaultManager];
  129. // Convert the path to a NSString, then to a URL
  130. NSString* directoryPath = [[NSString stringWithUTF8String:curPath] stringByStandardizingPath];
  131. NSURL* directoryToScan = [NSURL fileURLWithPath:directoryPath];
  132. const char* pDirectoryPath = [directoryPath UTF8String];
  133. // Fetch the contents of the directory.
  134. NSArray* directoryContents = [fileManager contentsOfDirectoryAtURL:directoryToScan
  135. includingPropertiesForKeys:[NSArray arrayWithObjects:NSURLNameKey, NSURLIsDirectoryKey, NSURLIsPackageKey, NSURLFileSizeKey, nil]
  136. options:NSDirectoryEnumerationSkipsHiddenFiles
  137. error:nil];
  138. // Enumerate files.
  139. for( NSURL* itemURL in directoryContents )
  140. {
  141. // Is the item a directory?
  142. NSNumber* isDirectory;
  143. [itemURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL];
  144. // Skip if this is a directory.
  145. if ( [isDirectory boolValue] == true )
  146. continue;
  147. // Fetch the file-name.
  148. NSString* fileName;
  149. [itemURL getResourceValue:&fileName forKey:NSURLNameKey error:NULL];
  150. // Fetch the file size.
  151. NSNumber* fileSize;
  152. [itemURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL];
  153. // Allocate a file entry.
  154. fileVector.increment();
  155. Platform::FileInfo& info = fileVector.last();
  156. // Populate file entry.
  157. info.pFullPath = StringTable->insert( pDirectoryPath );
  158. info.pFileName = StringTable->insert( [fileName UTF8String] );
  159. info.fileSize = [fileSize intValue];
  160. }
  161. // Finish if no further recursion is required.
  162. if ( depth == 0 )
  163. return;
  164. // Enumerate sub-directories.
  165. for( NSURL* itemURL in directoryContents )
  166. {
  167. // Is the item a directory?
  168. NSNumber* isDirectory;
  169. [itemURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL];
  170. // Skip if this is NOT a directory.
  171. if ( [isDirectory boolValue] == false )
  172. continue;
  173. // Is the sub-directory a package directory?
  174. NSNumber* isPackageDirectory;
  175. [itemURL getResourceValue:&isPackageDirectory forKey:NSURLIsPackageKey error:NULL];
  176. // Skip if this is a package directory.
  177. if ( [isPackageDirectory boolValue] == true )
  178. continue;
  179. // Fetch the sub-directory name.
  180. NSString* subDirectoryName;
  181. [itemURL getResourceValue:&subDirectoryName forKey:NSURLNameKey error:NULL];
  182. // Skip if this is an excluded directory.
  183. if ( Platform::isExcludedDirectory([subDirectoryName UTF8String]) )
  184. continue;
  185. // Recurse the sub-directory.
  186. recurseDumpPath( [[itemURL path] UTF8String], fileVector, depth-1 );
  187. }
  188. }
  189. //-----------------------------------------------------------------------------
  190. #pragma mark ---- File Class Methods ----
  191. //-----------------------------------------------------------------------------
  192. File::File() : currentStatus(Closed), capability(0)
  193. {
  194. handle = NULL;
  195. }
  196. //-----------------------------------------------------------------------------
  197. File::~File()
  198. {
  199. close();
  200. handle = NULL;
  201. }
  202. //-----------------------------------------------------------------------------
  203. File::Status File::open(const char *filename, const AccessMode openMode)
  204. {
  205. if (dStrlen(filename) > MAX_MAC_PATH_LONG)
  206. Con::warnf("File::open: Filename length is pretty long...");
  207. // Close the file if it was already open...
  208. if (currentStatus != Closed)
  209. close();
  210. // create the appropriate type of file...
  211. switch (openMode)
  212. {
  213. case Read:
  214. handle = (void *)fopen(filename, "rb"); // read only
  215. break;
  216. case Write:
  217. handle = (void *)fopen(filename, "wb"); // write only
  218. break;
  219. case ReadWrite:
  220. handle = (void *)fopen(filename, "ab+"); // write(append) and read
  221. break;
  222. case WriteAppend:
  223. handle = (void *)fopen(filename, "ab"); // write(append) only
  224. break;
  225. default:
  226. AssertFatal(false, "File::open: bad access mode");
  227. }
  228. // handle not created successfully
  229. if (handle == NULL)
  230. return setStatus();
  231. // successfully created file, so set the file capabilities...
  232. switch (openMode)
  233. {
  234. case Read:
  235. capability = FileRead;
  236. break;
  237. case Write:
  238. case WriteAppend:
  239. capability = FileWrite;
  240. break;
  241. case ReadWrite:
  242. capability = FileRead | FileWrite;
  243. break;
  244. default:
  245. AssertFatal(false, "File::open: bad access mode");
  246. }
  247. // must set the file status before setting the position.
  248. currentStatus = Ok;
  249. if (openMode == ReadWrite)
  250. setPosition(0);
  251. // success!
  252. return currentStatus;
  253. }
  254. //-----------------------------------------------------------------------------
  255. U32 File::getPosition() const
  256. {
  257. // Before proceeding, make sure the file is open and the handle is valid
  258. AssertFatal(currentStatus != Closed , "File::getPosition: file closed");
  259. AssertFatal(handle != NULL, "File::getPosition: invalid file handle");
  260. // Return the position (aka, offset)
  261. return (U32)ftell((FILE*)handle);
  262. }
  263. //-----------------------------------------------------------------------------
  264. File::Status File::setPosition( S32 position, bool absolutePos )
  265. {
  266. // Before proceeding, make sure the file is open and the handle is valid
  267. AssertFatal(Closed != currentStatus, "File::setPosition: file closed");
  268. AssertFatal(handle != NULL, "File::setPosition: invalid file handle");
  269. // Check for valid status and not end of the file stream
  270. if (currentStatus != Ok && currentStatus != EOS )
  271. return currentStatus;
  272. // This will hold the final position of the current stream
  273. U32 finalPos;
  274. // Is the position absolute?
  275. if( absolutePos )
  276. {
  277. // Yes, so sanity!
  278. AssertFatal(0 <= position, "File::setPosition: negative absolute position");
  279. // Position beyond EOS is OK
  280. fseek((FILE*)handle, position, SEEK_SET);
  281. finalPos = (U32)ftell((FILE*)handle);
  282. }
  283. else
  284. {
  285. // No, so sanity!
  286. AssertFatal((getPosition() + position) >= 0, "File::setPosition: negative relative position");
  287. // Position beyond EOS is OK
  288. fseek((FILE*)handle, position, SEEK_CUR);
  289. finalPos = (U32)ftell((FILE*)handle);
  290. }
  291. // ftell returns -1 on error. set error status
  292. if (0xffffffff == finalPos)
  293. {
  294. return setStatus(IOError);
  295. }
  296. else if ( finalPos >= getSize() )
  297. {
  298. // Success, at end of file
  299. return currentStatus = EOS;
  300. }
  301. else
  302. {
  303. // Success!
  304. return currentStatus = Ok;
  305. }
  306. }
  307. //-----------------------------------------------------------------------------
  308. U32 File::getSize() const
  309. {
  310. // Before proceeding, make sure the file is open and the handle is valid
  311. AssertWarn(Closed != currentStatus, "File::getSize: file closed");
  312. AssertFatal(handle != NULL, "File::getSize: invalid file handle");
  313. if ( Ok == currentStatus || EOS == currentStatus )
  314. {
  315. struct stat statData;
  316. if(fstat(fileno((FILE*)handle), &statData) != 0)
  317. return 0;
  318. // return the size in bytes
  319. return (U32)statData.st_size;
  320. }
  321. return 0;
  322. }
  323. //-----------------------------------------------------------------------------
  324. File::Status File::flush()
  325. {
  326. // Before proceeding, make sure the file is open, the handle is valid, and the file can
  327. // be written to.
  328. AssertFatal(Closed != currentStatus, "File::flush: file closed");
  329. AssertFatal(handle != NULL, "File::flush: invalid file handle");
  330. AssertFatal(true == hasCapability(FileWrite), "File::flush: cannot flush a read-only file");
  331. if (fflush((FILE*)handle) != 0)
  332. return setStatus();
  333. else
  334. return currentStatus = Ok;
  335. }
  336. //-----------------------------------------------------------------------------
  337. File::Status File::close()
  338. {
  339. // check if it's already closed...
  340. if (Closed == currentStatus)
  341. return currentStatus;
  342. // it's not, so close it...
  343. if (handle != NULL)
  344. {
  345. if (fclose((FILE*)handle) != 0)
  346. return setStatus();
  347. }
  348. handle = NULL;
  349. return currentStatus = Closed;
  350. }
  351. //-----------------------------------------------------------------------------
  352. File::Status File::getStatus() const
  353. {
  354. return currentStatus;
  355. }
  356. //-----------------------------------------------------------------------------
  357. File::Status File::setStatus()
  358. {
  359. strerror(errno);
  360. switch (errno)
  361. {
  362. case EACCES: // permission denied
  363. currentStatus = IOError;
  364. break;
  365. case EBADF: // Bad File Pointer
  366. case EINVAL: // Invalid argument
  367. case ENOENT: // file not found
  368. case ENAMETOOLONG:
  369. default:
  370. currentStatus = UnknownError;
  371. }
  372. return currentStatus;
  373. }
  374. //-----------------------------------------------------------------------------
  375. File::Status File::setStatus(File::Status status)
  376. {
  377. return currentStatus = status;
  378. }
  379. //-----------------------------------------------------------------------------
  380. File::Status File::read(U32 size, char *dst, U32 *bytesRead)
  381. {
  382. // Before proceeding, make sure the file is not closed, the handle is valid,
  383. // the destination is not null, the file can be read, and the read size is
  384. // greater than 0.
  385. AssertFatal(Closed != currentStatus, "File::read: file closed");
  386. AssertFatal(handle != NULL, "File::read: invalid file handle");
  387. AssertFatal(NULL != dst, "File::read: NULL destination pointer");
  388. AssertFatal(true == hasCapability(FileRead), "File::read: file lacks capability");
  389. AssertWarn(0 != size, "File::read: size of zero");
  390. if (Ok != currentStatus || 0 == size)
  391. return currentStatus;
  392. // read from stream
  393. U32 nBytes = (U32)fread(dst, 1, size, (FILE*)handle);
  394. // did we hit the end of the stream?
  395. if( nBytes != size)
  396. currentStatus = EOS;
  397. // if bytesRead is a valid pointer, send number of bytes read there.
  398. if(bytesRead)
  399. *bytesRead = nBytes;
  400. // successfully read size bytes
  401. return currentStatus;
  402. }
  403. //-----------------------------------------------------------------------------
  404. File::Status File::write(U32 size, const char *src, U32 *bytesWritten)
  405. {
  406. // Before proceeding, make sure the file is not closed, the handle is valid,
  407. // the source is not null, the file can be written to, and the write size is
  408. // greater than 0.
  409. AssertFatal(Closed != currentStatus, "File::write: file closed");
  410. AssertFatal(handle != NULL, "File::write: invalid file handle");
  411. AssertFatal(NULL != src, "File::write: NULL source pointer");
  412. AssertFatal(true == hasCapability(FileWrite), "File::write: file lacks capability");
  413. AssertWarn(0 != size, "File::write: size of zero");
  414. if ((Ok != currentStatus && EOS != currentStatus) || 0 == size)
  415. return currentStatus;
  416. // write bytes to the stream
  417. U32 nBytes = (U32)fwrite(src, 1, size,(FILE*)handle);
  418. // if we couldn't write everything, we've got a problem. set error status.
  419. if(nBytes != size)
  420. setStatus();
  421. // if bytesWritten is a valid pointer, put number of bytes read there.
  422. if(bytesWritten)
  423. *bytesWritten = nBytes;
  424. // return current File status, whether good or ill.
  425. return currentStatus;
  426. }
  427. //-----------------------------------------------------------------------------
  428. bool File::hasCapability(Capability cap) const
  429. {
  430. return (0 != (U32(cap) & capability));
  431. }
  432. #pragma mark ---- Platform Namespace Methods ----
  433. //-----------------------------------------------------------------------------
  434. bool Platform::fileDelete(const char * name)
  435. {
  436. // Invalid pointer, return false
  437. if (!name)
  438. return false;
  439. // Name is longer than our limit, throw a warning
  440. if (dStrlen(name) > MAX_MAC_PATH_LONG)
  441. Con::warnf("Platform::fileDelete() - Filename length is pretty long...");
  442. // Atandard auto-release pool
  443. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  444. // Get the default NSFileManager
  445. NSFileManager* fileManager = [NSFileManager defaultManager];
  446. // Convert the name char* to a NSString
  447. NSString* filePath = [[NSString stringWithUTF8String:name] stringByStandardizingPath];
  448. // Delete the file
  449. // Returns YES if the item was removed successfully or if path was nil.
  450. // Returns NO if an error occurred. If the delegate aborts the operation for a file, this method returns YES.
  451. // However, if the delegate aborts the operation for a directory, this method returns NO.
  452. NSError* errorResponse = nil;
  453. BOOL result = [fileManager removeItemAtPath:filePath error:&errorResponse];
  454. // Could not delete the file, so print the error
  455. if (!result)
  456. {
  457. Con::errorf("Platform::fileDelete: %s", [[errorResponse localizedDescription] UTF8String]);
  458. }
  459. // Drain the memory and release the pool
  460. [pool drain];
  461. // Return results of the deletion
  462. return result;
  463. }
  464. //-----------------------------------------------------------------------------
  465. bool Platform::fileTouch(const char *path)
  466. {
  467. if (!path || !*path)
  468. return false;
  469. // set file at path's modification and access times to now.
  470. return( utimes( path, NULL) == 0); // utimes returns 0 on success.
  471. }
  472. //-----------------------------------------------------------------------------
  473. S32 Platform::compareFileTimes(const FileTime &a, const FileTime &b)
  474. {
  475. if(a > b)
  476. return 1;
  477. if(a < b)
  478. return -1;
  479. return 0;
  480. }
  481. //-----------------------------------------------------------------------------
  482. // either time param COULD be null.
  483. //-----------------------------------------------------------------------------
  484. bool Platform::getFileTimes(const char *path, FileTime *createTime, FileTime *modifyTime)
  485. {
  486. if (!path || !*path)
  487. return false;
  488. struct stat statData;
  489. if (stat(path, &statData) == -1)
  490. return false;
  491. if (createTime)
  492. *createTime = statData.st_ctime;
  493. if (modifyTime)
  494. *modifyTime = statData.st_mtime;
  495. return true;
  496. // Make sure the path pointer is valid
  497. /*if (!path || !*path)
  498. return false;
  499. struct stat statData;
  500. // Standard auto-release pool
  501. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  502. // Convert the path to a NSString
  503. NSString* convertedPath = [[NSString stringWithUTF8String:path] stringByStandardizingPath];
  504. // Error catcher
  505. NSError* errorResponse = nil;
  506. // Get the file attributes
  507. NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:convertedPath error:&errorResponse];
  508. // If we failed to get the file attributes, something is wrong with the file or logic
  509. if (attributes == nil)
  510. {
  511. // Report the error and assign 0 file times
  512. Con::errorf("File::getSize: %s", [[errorResponse localizedDescription] UTF8String]);
  513. *createTime = 0;
  514. *modifyTime = 0;
  515. return false;
  516. }
  517. else
  518. {
  519. // Assign the creation and modified file times
  520. *createTime = [[attributes objectForKey:NSFileCreationDate] unsignedLongLongValue];
  521. *modifyTime = [[attributes objectForKey:NSFileModificationDate] unsignedLongLongValue];
  522. }
  523. // Auto-release memory
  524. [pool drain];
  525. return true;*/
  526. }
  527. //-----------------------------------------------------------------------------
  528. bool Platform::createPath(const char *file)
  529. {
  530. // Make sure the file pointer is valid
  531. if (!file || !*file)
  532. return false;
  533. // Ensure that the path has no file on the end.
  534. char pathBuffer[1024];
  535. dSprintf(pathBuffer, sizeof(pathBuffer), "%s", file );
  536. const S32 pathLength = dStrlen(pathBuffer);
  537. if ( pathLength == sizeof(pathBuffer)-1 )
  538. {
  539. Con::warnf("Could not create path as it was too long: '%s", file);
  540. return false;
  541. }
  542. char* pFinalSlash = dStrrchr(pathBuffer, '/');
  543. if ( pFinalSlash != pathBuffer+pathLength-1 )
  544. {
  545. pFinalSlash[1] = 0;
  546. }
  547. file = pathBuffer;
  548. // Standard auto-release pool
  549. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  550. // Conver the file path to a NSString
  551. NSString* convertedPath = [[NSString stringWithUTF8String:file] stringByStandardizingPath];
  552. // Get the shared file manager
  553. NSFileManager* fileManager = [NSFileManager defaultManager];
  554. // Error catcher
  555. NSError* createError = nil;
  556. // Try to create the path
  557. BOOL result = [fileManager createDirectoryAtPath:convertedPath withIntermediateDirectories:YES attributes:nil error:&createError];
  558. // If the create path failed, report the error and return
  559. if (!result)
  560. {
  561. Con::errorf("Platform::createPath error: %s", [[createError localizedDescription] UTF8String]);
  562. [pool drain];
  563. return false;
  564. }
  565. // Auto-release memory
  566. [pool drain];
  567. // Success
  568. return true;
  569. }
  570. //-----------------------------------------------------------------------------
  571. StringTableEntry Platform::getCurrentDirectory()
  572. {
  573. // Use the shared file manager to get the current path
  574. return [[[NSFileManager defaultManager] currentDirectoryPath] UTF8String];
  575. }
  576. //-----------------------------------------------------------------------------
  577. bool Platform::setCurrentDirectory(StringTableEntry newDir)
  578. {
  579. // Standard auto-release pool
  580. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  581. // Get the shared file manager
  582. NSFileManager* fileManager = [NSFileManager defaultManager];
  583. // Convert the newDir to a NSString
  584. NSString* convertedString = [[NSString stringWithUTF8String:newDir] stringByStandardizingPath];
  585. // Attempt to set the current directory to what was passed
  586. BOOL result = [fileManager changeCurrentDirectoryPath:convertedString];
  587. // Auto-release memory
  588. [pool drain];
  589. // Return the result
  590. return result;
  591. }
  592. //-----------------------------------------------------------------------------
  593. void Platform::openFolder(const char* path )
  594. {
  595. // Standard auto-release pool
  596. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  597. // Convert the path to a NSString
  598. NSString* convertedPath = [[NSString stringWithUTF8String:path] stringByStandardizingPath];
  599. // Try to open the folder
  600. BOOL result = [[NSWorkspace sharedWorkspace] openFile:convertedPath];
  601. // Auto-release the memory
  602. [pool drain];
  603. // If the operation failed, print an error
  604. if (!result)
  605. Con::errorf("Platform::openFolder: Failed to open folder %s", path);
  606. }
  607. //-----------------------------------------------------------------------------
  608. /// Finds and sets the current working directory.
  609. /// Torque tries to automatically detect whether you have placed the game files
  610. /// inside or outside the application's bundle. It checks for the presence of
  611. /// the file 'main.cs'. If it finds it, Torque will assume that the other game
  612. /// files are there too. If Torque does not see 'main.cs' inside its bundle, it
  613. /// will assume the files are outside the bundle.
  614. /// Since you probably don't want to copy the game files into the app every time
  615. /// you build, you will want to leave them outside the bundle for development.
  616. ///
  617. /// Placing all content inside the application bundle gives a much better user
  618. /// experience when you distribute your app.
  619. StringTableEntry Platform::getExecutablePath()
  620. {
  621. // Get the shared OSX platform state
  622. osxPlatState * platState = [osxPlatState sharedPlatState];
  623. // If we already have a main.cs file, return that
  624. if (platState.mainCSDirectory)
  625. return [[platState mainCSDirectory] UTF8String];
  626. // Get the main application bundle
  627. char cwd_buf[MAX_MAC_PATH_LONG];
  628. CFBundleRef mainBundle = CFBundleGetMainBundle();
  629. CFURLRef bundleUrl = CFBundleCopyBundleURL(mainBundle);
  630. // Used to determine where the executable exists.
  631. bool inside = true;
  632. bool outside = false;
  633. bool done = false;
  634. while(!done)
  635. {
  636. // first look for game content inside the application bundle.
  637. // then we look outside the bundle
  638. // then we assume it's a tool, and the "bundle" = the binary file.
  639. CFURLRef workingUrl;
  640. if (inside)
  641. workingUrl = CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault,bundleUrl,CFSTR("Contents/Resources"),true);
  642. else if(outside)
  643. workingUrl = CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorSystemDefault, bundleUrl);
  644. else
  645. {
  646. workingUrl = bundleUrl;
  647. CFRetain(workingUrl); // so that we can release bundleUrl twice.
  648. }
  649. CFStringRef workingString = CFURLCopyFileSystemPath(workingUrl, kCFURLPOSIXPathStyle);
  650. CFMutableStringRef normalizedString = CFStringCreateMutableCopy(NULL, 0, workingString);
  651. CFStringNormalize(normalizedString,kCFStringNormalizationFormC);
  652. CFStringGetCString(normalizedString, cwd_buf, sizeof(cwd_buf)-1, kCFStringEncodingUTF8);
  653. // if we dont see main.cs inside the bundle, try again looking outside
  654. // we're done if we find it, or if we find it neither inside or outside.
  655. if (isMainDotCsPresent(cwd_buf) || (!inside && !outside))
  656. done = true;
  657. if (inside)
  658. inside = false, outside = true;
  659. else if (outside)
  660. outside = false;
  661. CFRelease(workingUrl);
  662. CFRelease(workingString);
  663. CFRelease(normalizedString);
  664. }
  665. CFRelease(bundleUrl);
  666. char* ret = NULL;
  667. if (StringTable)
  668. platState.mainCSDirectory = [NSString stringWithUTF8String: StringTable->insert(cwd_buf)];
  669. else
  670. ret = dStrdup(cwd_buf);
  671. return ret ? ret : [platState.mainCSDirectory UTF8String];
  672. }
  673. //-----------------------------------------------------------------------------
  674. StringTableEntry Platform::getExecutableName()
  675. {
  676. char path_buf[MAX_MAC_PATH_LONG];
  677. // get a cfurl to the executable name
  678. CFBundleRef mainBundle = CFBundleGetMainBundle();
  679. CFURLRef bundleUrl = CFBundleCopyBundleURL(mainBundle);
  680. // get a cfstring of just the app name
  681. CFStringRef workingString = CFURLCopyLastPathComponent(bundleUrl);
  682. CFMutableStringRef normalizedString = CFStringCreateMutableCopy(NULL, 0, workingString);
  683. CFStringNormalize(normalizedString,kCFStringNormalizationFormC);
  684. CFStringGetCString(normalizedString, path_buf, sizeof(path_buf)-1, kCFStringEncodingUTF8);
  685. CFRelease(bundleUrl);
  686. CFRelease(workingString);
  687. CFRelease(normalizedString);
  688. return StringTable->insert(path_buf);
  689. }
  690. //-----------------------------------------------------------------------------
  691. bool Platform::isFile(const char *path)
  692. {
  693. // Make sure a valid pointer was passed
  694. if (!path || !*path)
  695. return false;
  696. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  697. // Convert the path to a temp NSString
  698. NSString* filePath = [[NSString stringWithUTF8String:path] stringByStandardizingPath];
  699. // This is required to explicitly tell the file manager to determine if the file
  700. // in question is a directory
  701. BOOL isDirectory;
  702. // Scan the path location
  703. bool exists = [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory];
  704. // Clean up the temp string
  705. [pool drain];
  706. // Return the results of the scan
  707. return !isDirectory && exists;
  708. }
  709. //-----------------------------------------------------------------------------
  710. bool Platform::isDirectory(const char *path)
  711. {
  712. // Make sure a valid pointer was passed
  713. if (!path || !*path)
  714. return false;
  715. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  716. // Convert the path to a temp NSString
  717. NSString* folderPath = [[NSString stringWithUTF8String:path] stringByStandardizingPath];
  718. // This is required to explicitly tell the file manager to determine if the file
  719. // in question is a directory
  720. BOOL isDirectory;
  721. // Scan the path location
  722. bool exists = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isDirectory];
  723. // Clean up the temp string
  724. [pool drain];
  725. // Return the results of the scan
  726. return isDirectory && exists;
  727. }
  728. //-----------------------------------------------------------------------------
  729. S32 Platform::getFileSize(const char* pFilePath)
  730. {
  731. // Make sure a valid pointer was passed
  732. if (!pFilePath || !*pFilePath)
  733. return 0;
  734. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  735. // This will catch an error if the file manager fails to gather attributes
  736. NSError* fileError = nil;
  737. // Convert pFilePath to a NSString
  738. NSString* path = [[NSString stringWithUTF8String:pFilePath] stringByStandardizingPath];
  739. // Get all the attributes of the file
  740. NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&fileError];
  741. // File manager could not gather attributes
  742. if (attributes == nil)
  743. {
  744. // Get the error message as a char*
  745. const char* errorMessage = [[fileError localizedDescription] UTF8String];
  746. // Print the error to the console
  747. Con::errorf("Platform::getFileSize: %s", errorMessage);
  748. [pool drain];
  749. // Return a 0 filesize
  750. return 0;
  751. }
  752. // Get the file size
  753. U32 fileLength = (U32)[[attributes objectForKey:NSFileSize] integerValue];
  754. // Clean up temp string
  755. [pool drain];
  756. // Return the result
  757. return fileLength;
  758. }
  759. //-----------------------------------------------------------------------------
  760. bool Platform::isSubDirectory(const char *pathParent, const char *pathSub)
  761. {
  762. // Concatenate the parent and sub directories
  763. char fullpath[MAX_MAC_PATH_LONG];
  764. dStrcpyl(fullpath, MAX_MAC_PATH_LONG, pathParent, "/", pathSub, NULL);
  765. // Return the isDirectory check
  766. return isDirectory((const char *)fullpath);
  767. }
  768. //-----------------------------------------------------------------------------
  769. // Check to see if the path has sub-directories
  770. // First pass at the OSX Cocoa implementation started with [NSFileManager subpathsAtPath]
  771. // That implementation was very simple, but the performance cost would be unacceptable
  772. // with large file structures. Since Torque 2D has many sub-directories, it makes more
  773. // sense to manually walk through directory contents using [NSFileManager enumeratorAtPath]
  774. // This approach is used in other functions where directories are dumped recursively
  775. bool Platform::hasSubDirectory(const char *path)
  776. {
  777. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  778. // Convert the path to a NSString
  779. NSString* filePath = [[NSString stringWithUTF8String:path] stringByStandardizingPath];
  780. // Check to make sure we are scannning a directory
  781. if (Platform::isDirectory(path))
  782. {
  783. // This will hold a list of subpaths
  784. NSDirectoryEnumerator* dirEnum = nil;
  785. // Get the default NSFileManager
  786. NSFileManager *fileManager = [NSFileManager defaultManager];
  787. // Initially, assume there is not a sub-directory
  788. BOOL foundDirectory = NO;
  789. // Enumerate the contes of the file path.
  790. dirEnum = [fileManager enumeratorAtPath:filePath];
  791. // This will walk the contents of the enumerated object
  792. NSString* entry;
  793. // Start walking through the enumerator
  794. while (entry = [dirEnum nextObject])
  795. {
  796. // Is the current entry a directory?
  797. if (Platform::isDirectory([entry UTF8String]))
  798. {
  799. // Success. Store the result and exit the loop
  800. foundDirectory = YES;
  801. break;
  802. }
  803. }
  804. // Clean up the temp string
  805. [pool drain];
  806. // Return the result of the scan
  807. return foundDirectory;
  808. }
  809. else
  810. {
  811. // Path was not a directory or did not exist
  812. Con::errorf("Platform::hasSubDirectory: path is not a directory");
  813. // Clean up the temp string
  814. [pool drain];
  815. // Return false
  816. return false;
  817. }
  818. // Clean up temp string
  819. [pool drain];
  820. // Something went wrong, so return false
  821. return false;
  822. }
  823. //-----------------------------------------------------------------------------
  824. bool Platform::dumpDirectories(const char *path, Vector<StringTableEntry> &directoryVector, S32 depth, bool noBasePath)
  825. {
  826. // Start profiling
  827. PROFILE_START(dumpDirectories);
  828. // Initialize the excluded directories so they are ignored
  829. ResourceManager->initExcludedDirectories();
  830. // Get the length of the path and pre-allocate the new path
  831. int len = dStrlen(path);
  832. char newpath[len+1];
  833. // Copy in the original path to the new path variable
  834. dSprintf(newpath, len, "%s", path );
  835. // cut off the trailing slash, if there is one
  836. if (newpath[len - 1] == '/')
  837. newpath[len - 1] = '\0';
  838. // Insert base path to follow what Windows does.
  839. if (!noBasePath )
  840. directoryVector.push_back(StringTable->insert(newpath));
  841. // Standard auto-release pool
  842. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  843. // Recursively dump the directories
  844. recurseDumpDirectories(newpath, "", directoryVector, depth, noBasePath);
  845. // Drain the pool.
  846. [pool drain];
  847. // End profiling
  848. PROFILE_END();
  849. // Return the result of recursively dumping the directories
  850. return true;
  851. }
  852. //-----------------------------------------------------------------------------
  853. bool Platform::dumpPath(const char *path, Vector<Platform::FileInfo>& fileVector, S32 depth)
  854. {
  855. // Start profiling
  856. PROFILE_START(dumpPath);
  857. // Initialize the excluded directories so they are ignored
  858. ResourceManager->initExcludedDirectories();
  859. // Get the length of the path and pre-allocate the new path variable
  860. int len = dStrlen(path);
  861. char newpath[len+1];
  862. // Copy the source path into the new path
  863. dSprintf(newpath, len, "%s", path);
  864. // Cut off the trailing slash, if there is one
  865. if (newpath[len - 1] == '/')
  866. newpath[len - 1] = '\0';
  867. // Is the initial directory excluded?
  868. if ( Platform::isExcludedDirectory(path) )
  869. {
  870. // Yes, so stop profiling
  871. PROFILE_END();
  872. return false;
  873. }
  874. // Standard auto-release pool
  875. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  876. // Recursively dump the contents of the path
  877. recurseDumpPath(newpath, fileVector, depth);
  878. // Drain the pool.
  879. [pool drain];
  880. // Stop profiling
  881. PROFILE_END();
  882. return true;
  883. }
  884. //-----------------------------------------------------------------------------
  885. bool Platform::fileRename(const char *source, const char *dest)
  886. {
  887. // Make sure the pointers are valid
  888. if (source == NULL || dest == NULL)
  889. return false;
  890. // Get the shared file manager
  891. NSFileManager *manager = [NSFileManager defaultManager];
  892. // Conver the source and dest pointers to NSString
  893. NSString *nsource = [manager stringWithFileSystemRepresentation:source length:dStrlen(source)];
  894. NSString *ndest = [manager stringWithFileSystemRepresentation:dest length:dStrlen(dest)];
  895. // If the source file doesn't exist, error out
  896. if (![manager fileExistsAtPath:nsource])
  897. {
  898. Con::errorf("Platform::fileRename: no file exists at %s",source);
  899. return false;
  900. }
  901. // If the destination file already exists, throw a warning. We are about to overwrite it
  902. if ([manager fileExistsAtPath:ndest] )
  903. {
  904. Con::warnf("Platform::fileRename: Deleting files at path: %s", dest);
  905. }
  906. // Error catcher
  907. NSError* errorResponse = nil;
  908. // Rename the file
  909. bool result = [manager moveItemAtPath:nsource toPath:ndest error:&errorResponse];
  910. // Rename operation failed
  911. if (!result)
  912. Con::errorf("Platform::fileRename: %S", [[errorResponse localizedDescription] UTF8String] );
  913. // Return the result
  914. return result;
  915. }
  916. bool Platform::pathCopy(const char* source, const char* dest, bool nooverwrite)
  917. {
  918. // Standard auto-release pool
  919. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  920. // Get the shared file manager
  921. NSFileManager *manager = [NSFileManager defaultManager];
  922. // Convert the char pointers to NSString
  923. NSString *nsource = [[NSString stringWithUTF8String:source] stringByStandardizingPath];
  924. NSString *ndest = [[NSString stringWithUTF8String:dest] stringByStandardizingPath];
  925. NSString *ndestFolder = [ndest stringByDeletingLastPathComponent];
  926. // If the source file does not exist, error out
  927. if (![manager fileExistsAtPath:nsource])
  928. {
  929. Con::errorf("Platform::pathCopy: no file exists at %s",source);
  930. return false;
  931. }
  932. // If the destination file already exists
  933. if ([manager fileExistsAtPath:ndest])
  934. {
  935. // If we can't overwrite, error out
  936. if(nooverwrite)
  937. {
  938. Con::errorf("Platform::pathCopy: file already exists at %s",dest);
  939. return false;
  940. }
  941. // Warn that we are about to overwrite the file
  942. Con::warnf("Deleting files at path: %s", dest);
  943. // Error catcher
  944. NSError* errorResponse = nil;
  945. // Try to delete the item
  946. bool deleted = [manager removeItemAtPath:ndest error:&errorResponse];
  947. // Failed to delete, so print the error and return
  948. if(!deleted)
  949. {
  950. Con::errorf("Platform::pathCopy failed at %s. %s", dest, [[errorResponse localizedDescription] UTF8String]);
  951. return false;
  952. }
  953. }
  954. if ([manager fileExistsAtPath:ndestFolder] == NO)
  955. {
  956. ndestFolder = [ndestFolder stringByAppendingString:@"/"]; // createpath requires a trailing slash
  957. Platform::createPath([ndestFolder UTF8String]);
  958. }
  959. NSError* errorResponse = nil;
  960. bool ret = [manager copyItemAtPath:nsource toPath:ndest error:&errorResponse];
  961. if (errorResponse != nil)
  962. {
  963. Con::errorf("Platform::pathCopy: %s", [[errorResponse localizedDescription] UTF8String]);
  964. }
  965. [pool drain];
  966. return ret;
  967. }
  968. StringTableEntry Platform::getUserHomeDirectory()
  969. {
  970. return StringTable->insert([[@"~/Documents" stringByStandardizingPath] UTF8String]);
  971. }
  972. const char* Platform::getUserDataDirectory()
  973. {
  974. // orb: Getting the path of the Application Support directory is only the first step.
  975. NSArray* paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
  976. if ([paths count] == 0)
  977. {
  978. // orb: This is a catastrophic failure - the system doesn't know where ~/Library/Application Support is!
  979. return NULL;
  980. }
  981. NSString* fullPath = [paths objectAtIndex:0];
  982. BOOL exists;
  983. BOOL isDir;
  984. NSFileManager* fileManager = [NSFileManager defaultManager];
  985. exists = [fileManager fileExistsAtPath:fullPath isDirectory:&isDir];
  986. if (!exists || !isDir)
  987. {
  988. // orb: This is probably an extremely rare occurence, but I have seen a few disk checks
  989. // converting directories into files before.
  990. if (exists)
  991. {
  992. NSError* errorResponse = nil;
  993. BOOL success = [fileManager removeItemAtPath:fullPath error:&errorResponse];
  994. if (!success)
  995. {
  996. Con::errorf("Platform::getUserDataDirectory: %s", [[errorResponse localizedDescription] UTF8String]);
  997. }
  998. }
  999. NSError* errorResponse = nil;
  1000. BOOL success = [fileManager createDirectoryAtPath:fullPath withIntermediateDirectories:YES attributes:nil error:&errorResponse];
  1001. if (!success)
  1002. {
  1003. Con::errorf("Platform::getUserDataDirectory: %s", [[errorResponse localizedDescription] UTF8String]);
  1004. return NULL;
  1005. }
  1006. }
  1007. // The directory exists and can be returned.
  1008. return StringTable->insert([fullPath UTF8String]);
  1009. }
  1010. StringTableEntry Platform::osGetTemporaryDirectory()
  1011. {
  1012. NSString *tdir = NSTemporaryDirectory();
  1013. const char *path = [tdir UTF8String];
  1014. return StringTable->insert(path);
  1015. }