//----------------------------------------------------------------------------- // Copyright (c) 2013 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #import "platformOSX/platformOSX.h" #include "platform/platform.h" #include "platform/platformFileIO.h" #include "console/console.h" #include "string/stringTable.h" #include "collection/vector.h" #include "io/resource/resourceManager.h" #include "debug/profiler.h" #include #include // Maximum character length for file paths #define MAX_MAC_PATH_LONG 2048 #pragma mark ---- File IO Helper Functions ---- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // utility for platform::hasSubDirectory() and platform::dumpDirectories() // ensures that the entry is a directory, and isnt on the ignore lists. inline bool isGoodDirectory(const char* path) { return (Platform::isDirectory(path) // is a dir && dStrcmp(path,".") != 0 // not here && dStrcmp(path,"..") != 0 // not parent && !Platform::isExcludedDirectory(path)); // not excluded } //----------------------------------------------------------------------------- static bool isMainDotCsPresent(char *dir) { // Create the full path using the dir and pre-determined script name of "main.cs" char maincsbuf[MAX_MAC_PATH_LONG]; const char *maincsname = "/main.cs"; U32 len = dStrlen(dir) + dStrlen(maincsname); // The length of the above file path is too long. Error out AssertISV(len < MAX_MAC_PATH_LONG, "Sorry, path is too long, I can't run from this folder."); // Store the main.cs file path dSprintf(maincsbuf,MAX_MAC_PATH_LONG,"%s%s", dir, maincsname); // Check to see if the main.cs exists and return the result return Platform::isFile(maincsbuf); } //----------------------------------------------------------------------------- void recurseDumpDirectories(const char* basePath, const char* subPath, Vector &directoryVector, S32 depth, bool noBasePath) { // Is path a directory? if ( !Platform::isDirectory(basePath) ) { // No, so warn. Con::warnf("recurseDumpDirectories: %s is not a directory", basePath); // Fnish. return; } // Fetch the shared file manager. NSFileManager* fileManager = [NSFileManager defaultManager]; // Convert the path to a NSString, then to a URL NSString* directoryPath = [[NSString stringWithFormat:@"%s/%s", basePath, subPath] stringByStandardizingPath]; NSURL* directoryToScan = [NSURL fileURLWithPath:directoryPath]; // Fetch the contents of the directory. NSArray* directoryContents = [fileManager contentsOfDirectoryAtURL:directoryToScan includingPropertiesForKeys:[NSArray arrayWithObjects:NSURLNameKey, NSURLIsDirectoryKey, NSURLIsPackageKey, nil] options:NSDirectoryEnumerationSkipsHiddenFiles error:nil]; // Enumerate sub-directories. for( NSURL* itemURL in directoryContents ) { // Is the item a directory? NSNumber* isDirectory; [itemURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL]; // Skip if this is NOT a directory. if ( [isDirectory boolValue] == false ) continue; // Is the sub-directory a package directory? NSNumber* isPackageDirectory; [itemURL getResourceValue:&isPackageDirectory forKey:NSURLIsPackageKey error:NULL]; // Skip if this is a package directory. if ( [isPackageDirectory boolValue] == true ) continue; // Fetch the sub-directory name. NSString* subDirectoryName; [itemURL getResourceValue:&subDirectoryName forKey:NSURLNameKey error:NULL]; // Fetch UTF8 sub-directory name. const char* pSubDirectoryName = [subDirectoryName UTF8String]; // Skip if this is an excluded directory. if ( Platform::isExcludedDirectory(pSubDirectoryName) ) continue; // Format new sub-path. const char* pNewSubPath = dStrlen(subPath) > 0 ? [[NSString stringWithFormat:@"%s/%s", subPath, pSubDirectoryName] UTF8String] : pSubDirectoryName; // Format with base-path accordingly. if ( noBasePath ) { // Store the directory excluding the base-path. directoryVector.push_back( StringTable->insert( pNewSubPath ) ); } else { // Store the directory with the bath-path. directoryVector.push_back( StringTable->insert( [[itemURL path] UTF8String] ) ); } // Skip if no further recursion is required. if ( depth == 0 ) continue; // Recurse the sub-directory. recurseDumpDirectories( basePath, pNewSubPath, directoryVector, depth-1, noBasePath ); } } //----------------------------------------------------------------------------- static void recurseDumpPath(const char* curPath, Vector& fileVector, U32 depth) { // Fetch the shared file manager. NSFileManager* fileManager = [NSFileManager defaultManager]; // Convert the path to a NSString, then to a URL NSString* directoryPath = [[NSString stringWithUTF8String:curPath] stringByStandardizingPath]; NSURL* directoryToScan = [NSURL fileURLWithPath:directoryPath]; const char* pDirectoryPath = [directoryPath UTF8String]; // Fetch the contents of the directory. NSArray* directoryContents = [fileManager contentsOfDirectoryAtURL:directoryToScan includingPropertiesForKeys:[NSArray arrayWithObjects:NSURLNameKey, NSURLIsDirectoryKey, NSURLIsPackageKey, NSURLFileSizeKey, nil] options:NSDirectoryEnumerationSkipsHiddenFiles error:nil]; // Enumerate files. for( NSURL* itemURL in directoryContents ) { // Is the item a directory? NSNumber* isDirectory; [itemURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL]; // Skip if this is a directory. if ( [isDirectory boolValue] == true ) continue; // Fetch the file-name. NSString* fileName; [itemURL getResourceValue:&fileName forKey:NSURLNameKey error:NULL]; // Fetch the file size. NSNumber* fileSize; [itemURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL]; // Allocate a file entry. fileVector.increment(); Platform::FileInfo& info = fileVector.last(); // Populate file entry. info.pFullPath = StringTable->insert( pDirectoryPath ); info.pFileName = StringTable->insert( [fileName UTF8String] ); info.fileSize = [fileSize intValue]; } // Finish if no further recursion is required. if ( depth == 0 ) return; // Enumerate sub-directories. for( NSURL* itemURL in directoryContents ) { // Is the item a directory? NSNumber* isDirectory; [itemURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL]; // Skip if this is NOT a directory. if ( [isDirectory boolValue] == false ) continue; // Is the sub-directory a package directory? NSNumber* isPackageDirectory; [itemURL getResourceValue:&isPackageDirectory forKey:NSURLIsPackageKey error:NULL]; // Skip if this is a package directory. if ( [isPackageDirectory boolValue] == true ) continue; // Fetch the sub-directory name. NSString* subDirectoryName; [itemURL getResourceValue:&subDirectoryName forKey:NSURLNameKey error:NULL]; // Skip if this is an excluded directory. if ( Platform::isExcludedDirectory([subDirectoryName UTF8String]) ) continue; // Recurse the sub-directory. recurseDumpPath( [[itemURL path] UTF8String], fileVector, depth-1 ); } } //----------------------------------------------------------------------------- #pragma mark ---- File Class Methods ---- //----------------------------------------------------------------------------- File::File() : currentStatus(Closed), capability(0) { handle = NULL; } //----------------------------------------------------------------------------- File::~File() { close(); handle = NULL; } //----------------------------------------------------------------------------- File::Status File::open(const char *filename, const AccessMode openMode) { if (dStrlen(filename) > MAX_MAC_PATH_LONG) Con::warnf("File::open: Filename length is pretty long..."); // Close the file if it was already open... if (currentStatus != Closed) close(); // create the appropriate type of file... switch (openMode) { case Read: handle = (void *)fopen(filename, "rb"); // read only break; case Write: handle = (void *)fopen(filename, "wb"); // write only break; case ReadWrite: handle = (void *)fopen(filename, "ab+"); // write(append) and read break; case WriteAppend: handle = (void *)fopen(filename, "ab"); // write(append) only break; default: AssertFatal(false, "File::open: bad access mode"); } // handle not created successfully if (handle == NULL) return setStatus(); // successfully created file, so set the file capabilities... switch (openMode) { case Read: capability = FileRead; break; case Write: case WriteAppend: capability = FileWrite; break; case ReadWrite: capability = FileRead | FileWrite; break; default: AssertFatal(false, "File::open: bad access mode"); } // must set the file status before setting the position. currentStatus = Ok; if (openMode == ReadWrite) setPosition(0); // success! return currentStatus; } //----------------------------------------------------------------------------- U32 File::getPosition() const { // Before proceeding, make sure the file is open and the handle is valid AssertFatal(currentStatus != Closed , "File::getPosition: file closed"); AssertFatal(handle != NULL, "File::getPosition: invalid file handle"); // Return the position (aka, offset) return (U32)ftell((FILE*)handle); } //----------------------------------------------------------------------------- File::Status File::setPosition( S32 position, bool absolutePos ) { // Before proceeding, make sure the file is open and the handle is valid AssertFatal(Closed != currentStatus, "File::setPosition: file closed"); AssertFatal(handle != NULL, "File::setPosition: invalid file handle"); // Check for valid status and not end of the file stream if (currentStatus != Ok && currentStatus != EOS ) return currentStatus; // This will hold the final position of the current stream U32 finalPos; // Is the position absolute? if( absolutePos ) { // Yes, so sanity! AssertFatal(0 <= position, "File::setPosition: negative absolute position"); // Position beyond EOS is OK fseek((FILE*)handle, position, SEEK_SET); finalPos = (U32)ftell((FILE*)handle); } else { // No, so sanity! AssertFatal((getPosition() + position) >= 0, "File::setPosition: negative relative position"); // Position beyond EOS is OK fseek((FILE*)handle, position, SEEK_CUR); finalPos = (U32)ftell((FILE*)handle); } // ftell returns -1 on error. set error status if (0xffffffff == finalPos) { return setStatus(IOError); } else if ( finalPos >= getSize() ) { // Success, at end of file return currentStatus = EOS; } else { // Success! return currentStatus = Ok; } } //----------------------------------------------------------------------------- U32 File::getSize() const { // Before proceeding, make sure the file is open and the handle is valid AssertWarn(Closed != currentStatus, "File::getSize: file closed"); AssertFatal(handle != NULL, "File::getSize: invalid file handle"); if ( Ok == currentStatus || EOS == currentStatus ) { struct stat statData; if(fstat(fileno((FILE*)handle), &statData) != 0) return 0; // return the size in bytes return (U32)statData.st_size; } return 0; } //----------------------------------------------------------------------------- File::Status File::flush() { // Before proceeding, make sure the file is open, the handle is valid, and the file can // be written to. AssertFatal(Closed != currentStatus, "File::flush: file closed"); AssertFatal(handle != NULL, "File::flush: invalid file handle"); AssertFatal(true == hasCapability(FileWrite), "File::flush: cannot flush a read-only file"); if (fflush((FILE*)handle) != 0) return setStatus(); else return currentStatus = Ok; } //----------------------------------------------------------------------------- File::Status File::close() { // check if it's already closed... if (Closed == currentStatus) return currentStatus; // it's not, so close it... if (handle != NULL) { if (fclose((FILE*)handle) != 0) return setStatus(); } handle = NULL; return currentStatus = Closed; } //----------------------------------------------------------------------------- File::Status File::getStatus() const { return currentStatus; } //----------------------------------------------------------------------------- File::Status File::setStatus() { strerror(errno); switch (errno) { case EACCES: // permission denied currentStatus = IOError; break; case EBADF: // Bad File Pointer case EINVAL: // Invalid argument case ENOENT: // file not found case ENAMETOOLONG: default: currentStatus = UnknownError; } return currentStatus; } //----------------------------------------------------------------------------- File::Status File::setStatus(File::Status status) { return currentStatus = status; } //----------------------------------------------------------------------------- File::Status File::read(U32 size, char *dst, U32 *bytesRead) { // Before proceeding, make sure the file is not closed, the handle is valid, // the destination is not null, the file can be read, and the read size is // greater than 0. AssertFatal(Closed != currentStatus, "File::read: file closed"); AssertFatal(handle != NULL, "File::read: invalid file handle"); AssertFatal(NULL != dst, "File::read: NULL destination pointer"); AssertFatal(true == hasCapability(FileRead), "File::read: file lacks capability"); AssertWarn(0 != size, "File::read: size of zero"); if (Ok != currentStatus || 0 == size) return currentStatus; // read from stream U32 nBytes = (U32)fread(dst, 1, size, (FILE*)handle); // did we hit the end of the stream? if( nBytes != size) currentStatus = EOS; // if bytesRead is a valid pointer, send number of bytes read there. if(bytesRead) *bytesRead = nBytes; // successfully read size bytes return currentStatus; } //----------------------------------------------------------------------------- File::Status File::write(U32 size, const char *src, U32 *bytesWritten) { // Before proceeding, make sure the file is not closed, the handle is valid, // the source is not null, the file can be written to, and the write size is // greater than 0. AssertFatal(Closed != currentStatus, "File::write: file closed"); AssertFatal(handle != NULL, "File::write: invalid file handle"); AssertFatal(NULL != src, "File::write: NULL source pointer"); AssertFatal(true == hasCapability(FileWrite), "File::write: file lacks capability"); AssertWarn(0 != size, "File::write: size of zero"); if ((Ok != currentStatus && EOS != currentStatus) || 0 == size) return currentStatus; // write bytes to the stream U32 nBytes = (U32)fwrite(src, 1, size,(FILE*)handle); // if we couldn't write everything, we've got a problem. set error status. if(nBytes != size) setStatus(); // if bytesWritten is a valid pointer, put number of bytes read there. if(bytesWritten) *bytesWritten = nBytes; // return current File status, whether good or ill. return currentStatus; } //----------------------------------------------------------------------------- bool File::hasCapability(Capability cap) const { return (0 != (U32(cap) & capability)); } #pragma mark ---- Platform Namespace Methods ---- //----------------------------------------------------------------------------- bool Platform::fileDelete(const char * name) { // Invalid pointer, return false if (!name) return false; // Name is longer than our limit, throw a warning if (dStrlen(name) > MAX_MAC_PATH_LONG) Con::warnf("Platform::fileDelete() - Filename length is pretty long..."); // Atandard auto-release pool NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Get the default NSFileManager NSFileManager* fileManager = [NSFileManager defaultManager]; // Convert the name char* to a NSString NSString* filePath = [[NSString stringWithUTF8String:name] stringByStandardizingPath]; // Delete the file // Returns YES if the item was removed successfully or if path was nil. // Returns NO if an error occurred. If the delegate aborts the operation for a file, this method returns YES. // However, if the delegate aborts the operation for a directory, this method returns NO. NSError* errorResponse = nil; BOOL result = [fileManager removeItemAtPath:filePath error:&errorResponse]; // Could not delete the file, so print the error if (!result) { Con::errorf("Platform::fileDelete: %s", [[errorResponse localizedDescription] UTF8String]); } // Drain the memory and release the pool [pool drain]; // Return results of the deletion return result; } //----------------------------------------------------------------------------- bool Platform::fileTouch(const char *path) { if (!path || !*path) return false; // set file at path's modification and access times to now. return( utimes( path, NULL) == 0); // utimes returns 0 on success. } //----------------------------------------------------------------------------- S32 Platform::compareFileTimes(const FileTime &a, const FileTime &b) { if(a > b) return 1; if(a < b) return -1; return 0; } //----------------------------------------------------------------------------- // either time param COULD be null. //----------------------------------------------------------------------------- bool Platform::getFileTimes(const char *path, FileTime *createTime, FileTime *modifyTime) { if (!path || !*path) return false; struct stat statData; if (stat(path, &statData) == -1) return false; if (createTime) *createTime = statData.st_ctime; if (modifyTime) *modifyTime = statData.st_mtime; return true; // Make sure the path pointer is valid /*if (!path || !*path) return false; struct stat statData; // Standard auto-release pool NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Convert the path to a NSString NSString* convertedPath = [[NSString stringWithUTF8String:path] stringByStandardizingPath]; // Error catcher NSError* errorResponse = nil; // Get the file attributes NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:convertedPath error:&errorResponse]; // If we failed to get the file attributes, something is wrong with the file or logic if (attributes == nil) { // Report the error and assign 0 file times Con::errorf("File::getSize: %s", [[errorResponse localizedDescription] UTF8String]); *createTime = 0; *modifyTime = 0; return false; } else { // Assign the creation and modified file times *createTime = [[attributes objectForKey:NSFileCreationDate] unsignedLongLongValue]; *modifyTime = [[attributes objectForKey:NSFileModificationDate] unsignedLongLongValue]; } // Auto-release memory [pool drain]; return true;*/ } //----------------------------------------------------------------------------- bool Platform::createPath(const char *file) { // Make sure the file pointer is valid if (!file || !*file) return false; // Ensure that the path has no file on the end. char pathBuffer[1024]; dSprintf(pathBuffer, sizeof(pathBuffer), "%s", file ); const S32 pathLength = dStrlen(pathBuffer); if ( pathLength == sizeof(pathBuffer)-1 ) { Con::warnf("Could not create path as it was too long: '%s", file); return false; } char* pFinalSlash = dStrrchr(pathBuffer, '/'); if ( pFinalSlash != pathBuffer+pathLength-1 ) { pFinalSlash[1] = 0; } file = pathBuffer; // Standard auto-release pool NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Conver the file path to a NSString NSString* convertedPath = [[NSString stringWithUTF8String:file] stringByStandardizingPath]; // Get the shared file manager NSFileManager* fileManager = [NSFileManager defaultManager]; // Error catcher NSError* createError = nil; // Try to create the path BOOL result = [fileManager createDirectoryAtPath:convertedPath withIntermediateDirectories:YES attributes:nil error:&createError]; // If the create path failed, report the error and return if (!result) { Con::errorf("Platform::createPath error: %s", [[createError localizedDescription] UTF8String]); [pool drain]; return false; } // Auto-release memory [pool drain]; // Success return true; } //----------------------------------------------------------------------------- StringTableEntry Platform::getCurrentDirectory() { // Use the shared file manager to get the current path return [[[NSFileManager defaultManager] currentDirectoryPath] UTF8String]; } //----------------------------------------------------------------------------- bool Platform::setCurrentDirectory(StringTableEntry newDir) { // Standard auto-release pool NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Get the shared file manager NSFileManager* fileManager = [NSFileManager defaultManager]; // Convert the newDir to a NSString NSString* convertedString = [[NSString stringWithUTF8String:newDir] stringByStandardizingPath]; // Attempt to set the current directory to what was passed BOOL result = [fileManager changeCurrentDirectoryPath:convertedString]; // Auto-release memory [pool drain]; // Return the result return result; } //----------------------------------------------------------------------------- void Platform::openFolder(const char* path ) { // Standard auto-release pool NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Convert the path to a NSString NSString* convertedPath = [[NSString stringWithUTF8String:path] stringByStandardizingPath]; // Try to open the folder BOOL result = [[NSWorkspace sharedWorkspace] openFile:convertedPath]; // Auto-release the memory [pool drain]; // If the operation failed, print an error if (!result) Con::errorf("Platform::openFolder: Failed to open folder %s", path); } //----------------------------------------------------------------------------- /// Finds and sets the current working directory. /// Torque tries to automatically detect whether you have placed the game files /// inside or outside the application's bundle. It checks for the presence of /// the file 'main.cs'. If it finds it, Torque will assume that the other game /// files are there too. If Torque does not see 'main.cs' inside its bundle, it /// will assume the files are outside the bundle. /// Since you probably don't want to copy the game files into the app every time /// you build, you will want to leave them outside the bundle for development. /// /// Placing all content inside the application bundle gives a much better user /// experience when you distribute your app. StringTableEntry Platform::getExecutablePath() { // Get the shared OSX platform state osxPlatState * platState = [osxPlatState sharedPlatState]; // If we already have a main.cs file, return that if (platState.mainCSDirectory) return [[platState mainCSDirectory] UTF8String]; // Get the main application bundle char cwd_buf[MAX_MAC_PATH_LONG]; CFBundleRef mainBundle = CFBundleGetMainBundle(); CFURLRef bundleUrl = CFBundleCopyBundleURL(mainBundle); // Used to determine where the executable exists. bool inside = true; bool outside = false; bool done = false; while(!done) { // first look for game content inside the application bundle. // then we look outside the bundle // then we assume it's a tool, and the "bundle" = the binary file. CFURLRef workingUrl; if (inside) workingUrl = CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault,bundleUrl,CFSTR("Contents/Resources"),true); else if(outside) workingUrl = CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorSystemDefault, bundleUrl); else { workingUrl = bundleUrl; CFRetain(workingUrl); // so that we can release bundleUrl twice. } CFStringRef workingString = CFURLCopyFileSystemPath(workingUrl, kCFURLPOSIXPathStyle); CFMutableStringRef normalizedString = CFStringCreateMutableCopy(NULL, 0, workingString); CFStringNormalize(normalizedString,kCFStringNormalizationFormC); CFStringGetCString(normalizedString, cwd_buf, sizeof(cwd_buf)-1, kCFStringEncodingUTF8); // if we dont see main.cs inside the bundle, try again looking outside // we're done if we find it, or if we find it neither inside or outside. if (isMainDotCsPresent(cwd_buf) || (!inside && !outside)) done = true; if (inside) inside = false, outside = true; else if (outside) outside = false; CFRelease(workingUrl); CFRelease(workingString); CFRelease(normalizedString); } CFRelease(bundleUrl); char* ret = NULL; if (StringTable) platState.mainCSDirectory = [NSString stringWithUTF8String: StringTable->insert(cwd_buf)]; else ret = dStrdup(cwd_buf); return ret ? ret : [platState.mainCSDirectory UTF8String]; } //----------------------------------------------------------------------------- StringTableEntry Platform::getExecutableName() { char path_buf[MAX_MAC_PATH_LONG]; // get a cfurl to the executable name CFBundleRef mainBundle = CFBundleGetMainBundle(); CFURLRef bundleUrl = CFBundleCopyBundleURL(mainBundle); // get a cfstring of just the app name CFStringRef workingString = CFURLCopyLastPathComponent(bundleUrl); CFMutableStringRef normalizedString = CFStringCreateMutableCopy(NULL, 0, workingString); CFStringNormalize(normalizedString,kCFStringNormalizationFormC); CFStringGetCString(normalizedString, path_buf, sizeof(path_buf)-1, kCFStringEncodingUTF8); CFRelease(bundleUrl); CFRelease(workingString); CFRelease(normalizedString); return StringTable->insert(path_buf); } //----------------------------------------------------------------------------- bool Platform::isFile(const char *path) { // Make sure a valid pointer was passed if (!path || !*path) return false; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Convert the path to a temp NSString NSString* filePath = [[NSString stringWithUTF8String:path] stringByStandardizingPath]; // This is required to explicitly tell the file manager to determine if the file // in question is a directory BOOL isDirectory; // Scan the path location bool exists = [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory]; // Clean up the temp string [pool drain]; // Return the results of the scan return !isDirectory && exists; } //----------------------------------------------------------------------------- bool Platform::isDirectory(const char *path) { // Make sure a valid pointer was passed if (!path || !*path) return false; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Convert the path to a temp NSString NSString* folderPath = [[NSString stringWithUTF8String:path] stringByStandardizingPath]; // This is required to explicitly tell the file manager to determine if the file // in question is a directory BOOL isDirectory; // Scan the path location bool exists = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isDirectory]; // Clean up the temp string [pool drain]; // Return the results of the scan return isDirectory && exists; } //----------------------------------------------------------------------------- S32 Platform::getFileSize(const char* pFilePath) { // Make sure a valid pointer was passed if (!pFilePath || !*pFilePath) return 0; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // This will catch an error if the file manager fails to gather attributes NSError* fileError = nil; // Convert pFilePath to a NSString NSString* path = [[NSString stringWithUTF8String:pFilePath] stringByStandardizingPath]; // Get all the attributes of the file NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&fileError]; // File manager could not gather attributes if (attributes == nil) { // Get the error message as a char* const char* errorMessage = [[fileError localizedDescription] UTF8String]; // Print the error to the console Con::errorf("Platform::getFileSize: %s", errorMessage); [pool drain]; // Return a 0 filesize return 0; } // Get the file size U32 fileLength = (U32)[[attributes objectForKey:NSFileSize] integerValue]; // Clean up temp string [pool drain]; // Return the result return fileLength; } //----------------------------------------------------------------------------- bool Platform::isSubDirectory(const char *pathParent, const char *pathSub) { // Concatenate the parent and sub directories char fullpath[MAX_MAC_PATH_LONG]; dStrcpyl(fullpath, MAX_MAC_PATH_LONG, pathParent, "/", pathSub, NULL); // Return the isDirectory check return isDirectory((const char *)fullpath); } //----------------------------------------------------------------------------- // Check to see if the path has sub-directories // First pass at the OSX Cocoa implementation started with [NSFileManager subpathsAtPath] // That implementation was very simple, but the performance cost would be unacceptable // with large file structures. Since Torque 2D has many sub-directories, it makes more // sense to manually walk through directory contents using [NSFileManager enumeratorAtPath] // This approach is used in other functions where directories are dumped recursively bool Platform::hasSubDirectory(const char *path) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Convert the path to a NSString NSString* filePath = [[NSString stringWithUTF8String:path] stringByStandardizingPath]; // Check to make sure we are scannning a directory if (Platform::isDirectory(path)) { // This will hold a list of subpaths NSDirectoryEnumerator* dirEnum = nil; // Get the default NSFileManager NSFileManager *fileManager = [NSFileManager defaultManager]; // Initially, assume there is not a sub-directory BOOL foundDirectory = NO; // Enumerate the contes of the file path. dirEnum = [fileManager enumeratorAtPath:filePath]; // This will walk the contents of the enumerated object NSString* entry; // Start walking through the enumerator while (entry = [dirEnum nextObject]) { // Is the current entry a directory? if (Platform::isDirectory([entry UTF8String])) { // Success. Store the result and exit the loop foundDirectory = YES; break; } } // Clean up the temp string [pool drain]; // Return the result of the scan return foundDirectory; } else { // Path was not a directory or did not exist Con::errorf("Platform::hasSubDirectory: path is not a directory"); // Clean up the temp string [pool drain]; // Return false return false; } // Clean up temp string [pool drain]; // Something went wrong, so return false return false; } //----------------------------------------------------------------------------- bool Platform::dumpDirectories(const char *path, Vector &directoryVector, S32 depth, bool noBasePath) { // Start profiling PROFILE_START(dumpDirectories); // Initialize the excluded directories so they are ignored ResourceManager->initExcludedDirectories(); // Get the length of the path and pre-allocate the new path int len = dStrlen(path); char newpath[len+1]; // Copy in the original path to the new path variable dSprintf(newpath, len, "%s", path ); // cut off the trailing slash, if there is one if (newpath[len - 1] == '/') newpath[len - 1] = '\0'; // Insert base path to follow what Windows does. if (!noBasePath ) directoryVector.push_back(StringTable->insert(newpath)); // Standard auto-release pool NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Recursively dump the directories recurseDumpDirectories(newpath, "", directoryVector, depth, noBasePath); // Drain the pool. [pool drain]; // End profiling PROFILE_END(); // Return the result of recursively dumping the directories return true; } //----------------------------------------------------------------------------- bool Platform::dumpPath(const char *path, Vector& fileVector, S32 depth) { // Start profiling PROFILE_START(dumpPath); // Initialize the excluded directories so they are ignored ResourceManager->initExcludedDirectories(); // Get the length of the path and pre-allocate the new path variable int len = dStrlen(path); char newpath[len+1]; // Copy the source path into the new path dSprintf(newpath, len, "%s", path); // Cut off the trailing slash, if there is one if (newpath[len - 1] == '/') newpath[len - 1] = '\0'; // Is the initial directory excluded? if ( Platform::isExcludedDirectory(path) ) { // Yes, so stop profiling PROFILE_END(); return false; } // Standard auto-release pool NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; // Recursively dump the contents of the path recurseDumpPath(newpath, fileVector, depth); // Drain the pool. [pool drain]; // Stop profiling PROFILE_END(); return true; } //----------------------------------------------------------------------------- bool Platform::fileRename(const char *source, const char *dest) { // Make sure the pointers are valid if (source == NULL || dest == NULL) return false; // Get the shared file manager NSFileManager *manager = [NSFileManager defaultManager]; // Conver the source and dest pointers to NSString NSString *nsource = [manager stringWithFileSystemRepresentation:source length:dStrlen(source)]; NSString *ndest = [manager stringWithFileSystemRepresentation:dest length:dStrlen(dest)]; // If the source file doesn't exist, error out if (![manager fileExistsAtPath:nsource]) { Con::errorf("Platform::fileRename: no file exists at %s",source); return false; } // If the destination file already exists, throw a warning. We are about to overwrite it if ([manager fileExistsAtPath:ndest] ) { Con::warnf("Platform::fileRename: Deleting files at path: %s", dest); } // Error catcher NSError* errorResponse = nil; // Rename the file bool result = [manager moveItemAtPath:nsource toPath:ndest error:&errorResponse]; // Rename operation failed if (!result) Con::errorf("Platform::fileRename: %S", [[errorResponse localizedDescription] UTF8String] ); // Return the result return result; } bool Platform::pathCopy(const char* source, const char* dest, bool nooverwrite) { // Standard auto-release pool NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Get the shared file manager NSFileManager *manager = [NSFileManager defaultManager]; // Convert the char pointers to NSString NSString *nsource = [[NSString stringWithUTF8String:source] stringByStandardizingPath]; NSString *ndest = [[NSString stringWithUTF8String:dest] stringByStandardizingPath]; NSString *ndestFolder = [ndest stringByDeletingLastPathComponent]; // If the source file does not exist, error out if (![manager fileExistsAtPath:nsource]) { Con::errorf("Platform::pathCopy: no file exists at %s",source); return false; } // If the destination file already exists if ([manager fileExistsAtPath:ndest]) { // If we can't overwrite, error out if(nooverwrite) { Con::errorf("Platform::pathCopy: file already exists at %s",dest); return false; } // Warn that we are about to overwrite the file Con::warnf("Deleting files at path: %s", dest); // Error catcher NSError* errorResponse = nil; // Try to delete the item bool deleted = [manager removeItemAtPath:ndest error:&errorResponse]; // Failed to delete, so print the error and return if(!deleted) { Con::errorf("Platform::pathCopy failed at %s. %s", dest, [[errorResponse localizedDescription] UTF8String]); return false; } } if ([manager fileExistsAtPath:ndestFolder] == NO) { ndestFolder = [ndestFolder stringByAppendingString:@"/"]; // createpath requires a trailing slash Platform::createPath([ndestFolder UTF8String]); } NSError* errorResponse = nil; bool ret = [manager copyItemAtPath:nsource toPath:ndest error:&errorResponse]; if (errorResponse != nil) { Con::errorf("Platform::pathCopy: %s", [[errorResponse localizedDescription] UTF8String]); } [pool drain]; return ret; } StringTableEntry Platform::getUserHomeDirectory() { return StringTable->insert([[@"~/Documents" stringByStandardizingPath] UTF8String]); } const char* Platform::getUserDataDirectory() { // orb: Getting the path of the Application Support directory is only the first step. NSArray* paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); if ([paths count] == 0) { // orb: This is a catastrophic failure - the system doesn't know where ~/Library/Application Support is! return NULL; } NSString* fullPath = [paths objectAtIndex:0]; BOOL exists; BOOL isDir; NSFileManager* fileManager = [NSFileManager defaultManager]; exists = [fileManager fileExistsAtPath:fullPath isDirectory:&isDir]; if (!exists || !isDir) { // orb: This is probably an extremely rare occurence, but I have seen a few disk checks // converting directories into files before. if (exists) { NSError* errorResponse = nil; BOOL success = [fileManager removeItemAtPath:fullPath error:&errorResponse]; if (!success) { Con::errorf("Platform::getUserDataDirectory: %s", [[errorResponse localizedDescription] UTF8String]); } } NSError* errorResponse = nil; BOOL success = [fileManager createDirectoryAtPath:fullPath withIntermediateDirectories:YES attributes:nil error:&errorResponse]; if (!success) { Con::errorf("Platform::getUserDataDirectory: %s", [[errorResponse localizedDescription] UTF8String]); return NULL; } } // The directory exists and can be returned. return StringTable->insert([fullPath UTF8String]); } StringTableEntry Platform::osGetTemporaryDirectory() { NSString *tdir = NSTemporaryDirectory(); const char *path = [tdir UTF8String]; return StringTable->insert(path); }