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