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