volume.cpp

Engine/source/core/volume.cpp

More...

Classes:

Namespaces:

namespace
namespace

Detailed Description

   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#include "platform/platform.h"
  25#include "core/volume.h"
  26
  27#include "core/virtualMountSystem.h"
  28#include "core/strings/findMatch.h"
  29#include "core/util/journal/process.h"
  30#include "core/util/safeDelete.h"
  31#include "console/console.h"
  32
  33
  34namespace Torque
  35{
  36using namespace FS;
  37
  38//-----------------------------------------------------------------------------
  39
  40bool FileSystemChangeNotifier::addNotification( const Path &path, ChangeDelegate callback )
  41{
  42   // Notifications are for files... if the path is empty 
  43   // then there is nothing to do. 
  44   if ( path.isEmpty() )
  45      return false;
  46
  47   // strip the filename and extension - we notify on dirs
  48   Path dir(cleanPath(path));
  49   if (dir.isEmpty())
  50      return false;
  51
  52   dir.setFileName( String() );
  53   dir.setExtension( String () );
  54   
  55   // first lookup the dir to see if we already have an entry for it...
  56   DirMap::Iterator   itr = mDirMap.find( dir );
  57
  58   FileInfoList   *fileList = NULL;
  59   bool           addedDir = false;
  60
  61   // Note that GetFileAttributes can fail here if the file doesn't 
  62   // exist, but thats ok and expected.  You can have notifications 
  63   // on files that don't exist yet.
  64   FileNode::Attributes attr;
  65   GetFileAttributes(path, &attr);
  66
  67   if ( itr != mDirMap.end() )
  68   {
  69      fileList = &(itr->value);
  70
  71      // look for the file and return if we find it
  72      for ( U32 i = 0; i < fileList->getSize(); i++ )
  73      {
  74         FileInfo &fInfo = (*fileList)[i];
  75         if ( fInfo.filePath == path )
  76         {
  77            // NOTE: This is bad... we should store the mod
  78            // time for each callback seperately in the future.
  79            //
  80            fInfo.savedLastModTime = attr.mtime;
  81            fInfo.signal.notify(callback);
  82            return true;
  83         }
  84      }
  85   }
  86   else
  87   {
  88      // otherwise we need to add the dir to our map and let the inherited class add it
  89      itr = mDirMap.insert( dir, FileInfoList() );
  90
  91      fileList = &(itr->value);
  92
  93      addedDir = true;
  94   }
  95
  96   FileInfo newInfo;
  97   newInfo.signal.notify(callback);
  98   newInfo.filePath = path;
  99   newInfo.savedLastModTime = attr.mtime;
 100
 101   fileList->pushBack( newInfo );
 102
 103   return addedDir ? internalAddNotification( dir ) : true;
 104}
 105
 106bool FileSystemChangeNotifier::removeNotification( const Path &path, ChangeDelegate callback )
 107{
 108   if (path.isEmpty())
 109      return false;
 110
 111   // strip the filename and extension - we notify on dirs
 112   Path dir(cleanPath(path));
 113   if (dir.isEmpty())
 114      return false;
 115
 116   dir.setFileName( String() );
 117   dir.setExtension( String () );
 118
 119   DirMap::Iterator   itr = mDirMap.find( dir );
 120
 121   if ( itr == mDirMap.end() )
 122      return false;
 123
 124   FileInfoList   &fileList = itr->value;
 125
 126   // look for the file and return if we find it
 127   for ( U32 i = 0; i < fileList.getSize(); i++ )
 128   {
 129      FileInfo &fInfo = fileList[i];
 130      if ( fInfo.filePath == path )
 131      {
 132         fInfo.signal.remove(callback);
 133         if (fInfo.signal.isEmpty())
 134            fileList.erase( i );
 135         break;
 136      }
 137   }
 138
 139   // IF we removed the last file
 140   //    THEN get rid of the dir from our map and notify inherited classes
 141   if ( fileList.getSize() == 0 )
 142   {
 143      mDirMap.erase( dir );
 144
 145      return internalRemoveNotification( dir );
 146   }
 147
 148   return true;
 149}
 150
 151void  FileSystemChangeNotifier::startNotifier()
 152{
 153   // update the timestamps of all the files we are managing
 154
 155   DirMap::Iterator   itr = mDirMap.begin();
 156
 157   for ( ; itr != mDirMap.end(); ++itr )
 158   {
 159      FileInfoList   &fileList = itr->value;
 160
 161      for ( U32 i = 0; i < fileList.getSize(); i++ )
 162      {
 163         FileInfo &fInfo = fileList[i];
 164
 165         // This may fail if the file doesn't exist... thats ok.
 166         FileNode::Attributes attr;
 167         GetFileAttributes(fInfo.filePath, &attr);
 168
 169         fInfo.savedLastModTime = attr.mtime;
 170      }
 171   }
 172
 173   mNotifying = true;
 174
 175   Process::notify( this, &FileSystemChangeNotifier::process, PROCESS_LAST_ORDER );
 176}
 177
 178void FileSystemChangeNotifier::process()
 179{
 180   internalProcessOnce();
 181}
 182
 183void  FileSystemChangeNotifier::stopNotifier()
 184{
 185   mNotifying = false;
 186
 187   Process::remove( this, &FileSystemChangeNotifier::process );
 188}
 189
 190/// Makes sure paths going in and out of the notifier will have the same format
 191String FileSystemChangeNotifier::cleanPath(const Path& dir)
 192{
 193   // This "cleans up" the path, if we don't do this we can get mismatches on the path
 194   // coming from the notifier
 195   FileSystemRef fs = Torque::FS::GetFileSystem(dir);
 196   if (!fs)
 197      return String::EmptyString;
 198   return fs->mapFrom(fs->mapTo(dir));
 199}
 200
 201void FileSystemChangeNotifier::internalNotifyDirChanged( const Path &dir )
 202{
 203   DirMap::Iterator itr = mDirMap.find( dir );
 204   if ( itr == mDirMap.end() )
 205      return;
 206
 207   // Gather the changed file info.
 208   FileInfoList changedList;
 209   FileInfoList &fileList = itr->value;
 210   for ( U32 i = 0; i < fileList.getSize(); i++ )
 211   {
 212      FileInfo &fInfo = fileList[i];
 213
 214      FileNode::Attributes attr;
 215      bool success = GetFileAttributes(fInfo.filePath, &attr);
 216
 217      // Ignore the file if we couldn't get the attributes (it must have 
 218      // been deleted) or the last modification time isn't newer.
 219      if ( !success || attr.mtime <= fInfo.savedLastModTime )       
 220         continue;
 221
 222      // Store the new mod time.
 223      fInfo.savedLastModTime = attr.mtime;
 224
 225      // We're taking a copy of the FileInfo struct here so that the
 226      // callback can safely remove the notification without crashing.
 227      changedList.pushBack( fInfo );
 228   }
 229
 230   // Now signal all the changed files.
 231   for ( U32 i = 0; i < changedList.getSize(); i++ )
 232   {
 233      FileInfo &fInfo = changedList[i];
 234
 235      Con::warnf( "    : file changed [%s]", fInfo.filePath.getFullPath().c_str() );
 236      fInfo.signal.trigger( fInfo.filePath );
 237   }
 238}
 239
 240//-----------------------------------------------------------------------------
 241
 242FileSystem::FileSystem()
 243   :  mChangeNotifier( NULL ),
 244   mReadOnly(false)
 245{
 246}
 247
 248FileSystem::~FileSystem()
 249{
 250   delete mChangeNotifier;
 251   mChangeNotifier = NULL;
 252}
 253
 254File::File() {}
 255File::~File() {}
 256Directory::Directory() {}
 257Directory::~Directory() {}
 258
 259
 260FileNode::FileNode()
 261:  mChecksum(0)
 262{
 263}
 264
 265Time   FileNode::getModifiedTime()
 266{
 267   Attributes attrs;
 268
 269   bool  success = getAttributes( &attrs );
 270
 271   if ( !success )
 272      return Time();
 273
 274   return attrs.mtime;
 275}
 276
 277U64    FileNode::getSize()
 278{
 279   Attributes attrs;
 280
 281   bool  success = getAttributes( &attrs );
 282
 283   if ( !success )
 284      return 0;
 285
 286   return attrs.size;
 287}
 288
 289U32 FileNode::getChecksum()
 290{
 291   bool  calculateCRC = (mLastChecksum == Torque::Time());
 292
 293   if ( !calculateCRC )
 294   {
 295      Torque::Time   modTime = getModifiedTime();
 296
 297      calculateCRC = (modTime > mLastChecksum);
 298   }
 299
 300   if ( calculateCRC )
 301      mChecksum = calculateChecksum();
 302   
 303   if ( mChecksum )
 304      mLastChecksum = Time::getCurrentTime();
 305
 306   return mChecksum;
 307
 308}
 309
 310//-----------------------------------------------------------------------------
 311
 312class FileSystemRedirect: public FileSystem
 313{
 314   friend class FileSystemRedirectChangeNotifier;
 315public:
 316   FileSystemRedirect(MountSystem* mfs,const Path& path);
 317
 318   String   getTypeStr() const { return "Redirect"; }
 319
 320   FileNodeRef resolve(const Path& path);
 321   FileNodeRef create(const Path& path,FileNode::Mode);
 322   bool remove(const Path& path);
 323   bool rename(const Path& a,const Path& b);
 324   Path mapTo(const Path& path);
 325   Path mapFrom(const Path& path);
 326   
 327private:
 328   Path _merge(const Path& path);
 329
 330   Path        mPath;
 331   MountSystem *mMFS;
 332};
 333
 334class FileSystemRedirectChangeNotifier : public FileSystemChangeNotifier
 335{
 336public:
 337
 338   FileSystemRedirectChangeNotifier( FileSystem *fs );
 339
 340   bool addNotification( const Path &path, ChangeDelegate callback );   
 341   bool removeNotification( const Path &path, ChangeDelegate callback );
 342
 343protected:
 344
 345   virtual void  internalProcessOnce() {}
 346   virtual bool  internalAddNotification( const Path &dir ) { return false; }
 347   virtual bool  internalRemoveNotification( const Path &dir ) { return false; }
 348};
 349
 350FileSystemRedirectChangeNotifier::FileSystemRedirectChangeNotifier( FileSystem *fs ) 
 351: FileSystemChangeNotifier( fs )
 352{
 353
 354}   
 355
 356bool FileSystemRedirectChangeNotifier::addNotification( const Path &path, ChangeDelegate callback )
 357{   
 358   FileSystemRedirect *rfs = (FileSystemRedirect*)mFS;
 359   Path redirectPath = rfs->_merge( path );
 360
 361   FileSystemRef fs = rfs->mMFS->getFileSystem( redirectPath );   
 362   if ( !fs || !fs->getChangeNotifier() )
 363      return false;
 364
 365   return fs->getChangeNotifier()->addNotification( redirectPath, callback );
 366}
 367
 368bool FileSystemRedirectChangeNotifier::removeNotification( const Path &path, ChangeDelegate callback )
 369{
 370   FileSystemRedirect *rfs = (FileSystemRedirect*)mFS;
 371   Path redirectPath = rfs->_merge( path );
 372
 373  FileSystemRef fs = rfs->mMFS->getFileSystem( redirectPath ); 
 374   if ( !fs || !fs->getChangeNotifier() )
 375      return false;
 376
 377   return fs->getChangeNotifier()->removeNotification( redirectPath, callback );
 378}
 379
 380FileSystemRedirect::FileSystemRedirect(MountSystem* mfs,const Path& path)
 381{
 382   mMFS = mfs;
 383   mPath.setRoot(path.getRoot());
 384   mPath.setPath(path.getPath());
 385   mChangeNotifier = new FileSystemRedirectChangeNotifier( this );
 386}
 387
 388Path FileSystemRedirect::_merge(const Path& path)
 389{
 390   Path p = mPath;
 391   p.setPath(Path::Join(p.getPath(),'/',Path::CompressPath(path.getPath())));
 392   p.setFileName(path.getFileName());
 393   p.setExtension(path.getExtension());
 394   return p;
 395}
 396
 397FileNodeRef FileSystemRedirect::resolve(const Path& path)
 398{
 399   Path p = _merge(path);
 400   FileSystemRef fs = mMFS->getFileSystem(p);
 401   if (fs != NULL)
 402      return fs->resolve(p);
 403   return NULL;
 404}
 405
 406FileNodeRef FileSystemRedirect::create(const Path& path,FileNode::Mode mode)
 407{
 408   Path p = _merge(path);
 409   FileSystemRef fs = mMFS->getFileSystem(p);
 410   if (fs != NULL)
 411      return fs->create(p,mode);
 412   return NULL;
 413}
 414
 415bool FileSystemRedirect::remove(const Path& path)
 416{
 417   Path p = _merge(path);
 418   FileSystemRef fs = mMFS->getFileSystem(p);
 419   if (fs != NULL)
 420      return fs->remove(p);
 421   return false;
 422}
 423
 424bool FileSystemRedirect::rename(const Path& a,const Path& b)
 425{
 426   Path na = _merge(a);
 427   Path nb = _merge(b);
 428   FileSystemRef fsa = mMFS->getFileSystem(na);
 429   FileSystemRef fsb = mMFS->getFileSystem(nb);
 430   if (fsa.getPointer() == fsb.getPointer())
 431      return fsa->rename(na,nb);
 432   return false;
 433}
 434
 435Path FileSystemRedirect::mapTo(const Path& path)
 436{
 437   Path p = _merge(path);
 438   FileSystemRef fs = mMFS->getFileSystem(p);
 439   if (fs != NULL)
 440      return fs->mapTo(p);
 441   return NULL;
 442}
 443
 444Path FileSystemRedirect::mapFrom(const Path& path)
 445{
 446   Path p = _merge(path);
 447   FileSystemRef fs = mMFS->getFileSystem(p);
 448   if (fs != NULL)
 449      return fs->mapFrom(p);
 450   return NULL;
 451}
 452
 453//-----------------------------------------------------------------------------
 454
 455void MountSystem::_log(const String& msg)
 456{
 457   String newMsg = "MountSystem: " + msg;
 458   Con::warnf("%s", newMsg.c_str());
 459}
 460
 461FileSystemRef MountSystem::_removeMountFromList(String root)
 462{
 463   for (Vector<MountFS>::iterator itr = mMountList.begin(); itr != mMountList.end(); itr++)
 464   {
 465      if (root.equal( itr->root, String::NoCase ))
 466      {
 467         FileSystemRef fs = itr->fileSystem;
 468         mMountList.erase(itr);
 469         return fs;
 470      }
 471   }
 472   return NULL;
 473}
 474
 475FileSystemRef MountSystem::_getFileSystemFromList(const Path& path) const
 476{
 477   for (Vector<MountFS>::const_iterator itr = mMountList.begin(); itr != mMountList.end(); itr++)
 478   {
 479      if (itr->root.equal( path.getRoot(), String::NoCase ))
 480         return itr->fileSystem;
 481   }
 482
 483   return NULL;
 484}
 485
 486
 487Path MountSystem::_normalize(const Path& path)
 488{
 489   Path po = path;
 490
 491   // Assign to cwd root if none is specified.   
 492   if( po.getRoot().isEmpty() )
 493      po.setRoot( mCWD.getRoot() );
 494
 495   // Merge in current working directory if the path is relative to
 496   // the current cwd.
 497   if( po.getRoot().equal( mCWD.getRoot(), String::NoCase ) && po.isRelative() )
 498   {
 499      po.setPath( Path::CompressPath( Path::Join( mCWD.getPath(),'/',po.getPath() ) ) );
 500   }
 501   return po;
 502}
 503
 504FileRef MountSystem::createFile(const Path& path)
 505{
 506   Path np = _normalize(path);
 507   FileSystemRef fs = _getFileSystemFromList(np);
 508
 509   if (fs && fs->isReadOnly())
 510   {
 511      _log(String::ToString("Cannot create file %s, filesystem is read-only", path.getFullPath().c_str()));
 512      return NULL;
 513   }
 514
 515   if (fs != NULL)
 516      return static_cast<File*>(fs->create(np,FileNode::File).getPointer());
 517   return NULL;
 518}
 519
 520DirectoryRef MountSystem::createDirectory(const Path& path, FileSystemRef fs)
 521{
 522   Path np = _normalize(path);
 523   if (fs.isNull())
 524      fs = _getFileSystemFromList(np);
 525
 526   if (fs && fs->isReadOnly())
 527   {
 528      _log(String::ToString("Cannot create directory %s, filesystem is read-only", path.getFullPath().c_str()));
 529      return NULL;
 530   }
 531
 532   if (fs != NULL)
 533      return static_cast<Directory*>(fs->create(np,FileNode::Directory).getPointer());
 534   return NULL;
 535}
 536
 537FileRef MountSystem::openFile(const Path& path,File::AccessMode mode)
 538{
 539   FileNodeRef node = getFileNode(path);
 540   if (node != NULL)
 541   {
 542      FileRef file = dynamic_cast<File*>(node.getPointer());
 543      if (file != NULL)
 544      {
 545         if (file->open(mode))
 546            return file;
 547         else
 548            return NULL;
 549      }
 550   }
 551   else
 552   {
 553      if (mode != File::Read)
 554      {
 555         FileRef file = createFile(path);
 556
 557         if (file != NULL)
 558         {
 559            file->open(mode);
 560            return file;
 561         }
 562      }
 563   }
 564   return NULL;
 565}
 566
 567DirectoryRef MountSystem::openDirectory(const Path& path)
 568{
 569   FileNodeRef node = getFileNode(path);
 570
 571   if (node != NULL)
 572   {
 573      DirectoryRef dir = dynamic_cast<Directory*>(node.getPointer());
 574      if (dir != NULL)
 575      {
 576         dir->open();
 577         return dir;
 578      }
 579   }
 580   return NULL;
 581}
 582
 583bool MountSystem::remove(const Path& path)
 584{
 585   Path np = _normalize(path);
 586   FileSystemRef fs = _getFileSystemFromList(np);
 587   if (fs && fs->isReadOnly())
 588   {
 589      _log(String::ToString("Cannot remove path %s, filesystem is read-only", path.getFullPath().c_str()));
 590      return false;
 591   }
 592   if (fs != NULL)
 593      return fs->remove(np);
 594   return false;
 595}
 596
 597bool MountSystem::rename(const Path& from,const Path& to)
 598{
 599   // Will only rename files on the same filesystem
 600   Path pa = _normalize(from);
 601   Path pb = _normalize(to);
 602   FileSystemRef fsa = _getFileSystemFromList(pa);
 603   FileSystemRef fsb = _getFileSystemFromList(pb);
 604   if (!fsa || !fsb)
 605      return false;
 606   if (fsa.getPointer() != fsb.getPointer())
 607   {
 608      _log(String::ToString("Cannot rename path %s to a different filesystem", from.getFullPath().c_str()));
 609      return false;
 610   }
 611   if (fsa->isReadOnly() || fsb->isReadOnly())
 612   {
 613      _log(String::ToString("Cannot rename path %s; source or target filesystem is read-only", from.getFullPath().c_str()));
 614      return false;
 615   }
 616
 617   return fsa->rename(pa,pb);
 618}
 619
 620bool MountSystem::mount(String root,FileSystemRef fs)
 621{
 622   MountFS mount;
 623   mount.root = root;
 624   mount.path = "/";
 625   mount.fileSystem = fs;
 626   mMountList.push_back(mount);
 627   return true;
 628}
 629
 630bool MountSystem::mount(String root,const Path &path)
 631{
 632   return mount(root,new FileSystemRedirect(this,_normalize(path)));
 633}
 634
 635FileSystemRef MountSystem::unmount(String root)
 636{
 637   FileSystemRef first = _removeMountFromList(root);
 638
 639   // remove remaining FSes on this root
 640   while (!_removeMountFromList(root).isNull())
 641      ;
 642   
 643   return first;
 644}
 645
 646bool MountSystem::unmount(FileSystemRef fs)
 647{
 648   if (fs.isNull())
 649      return false;
 650
 651   // iterate back to front in case FS is in list multiple times.
 652   // also check that fs is not null each time since its a strong ref
 653   // so it could be nulled during removal.
 654   bool unmounted = false;
 655   for (S32 i = mMountList.size() - 1; !fs.isNull() && i >= 0; --i)
 656   {
 657      if (mMountList[i].fileSystem.getPointer() == fs.getPointer())
 658      {
 659         mMountList.erase(i);
 660         unmounted = true;
 661      }
 662   }
 663   return unmounted;
 664}
 665
 666bool MountSystem::setCwd(const Path& file)
 667{
 668   if (file.getPath().isEmpty())
 669      return false;
 670   mCWD.setRoot(file.getRoot());
 671   mCWD.setPath(file.getPath());
 672   return true;
 673}
 674
 675const Path& MountSystem::getCwd() const
 676{
 677   return mCWD;
 678}
 679
 680FileSystemRef MountSystem::getFileSystem(const Path& path)
 681{
 682   return _getFileSystemFromList(_normalize(path));
 683}
 684
 685bool MountSystem::getFileAttributes(const Path& path,FileNode::Attributes* attr)
 686{
 687   FileNodeRef file = getFileNode(path);
 688
 689   if (file != NULL)
 690   {
 691      bool result = file->getAttributes(attr);
 692      return result;
 693   }
 694
 695   return false;
 696}
 697
 698FileNodeRef MountSystem::getFileNode(const Path& path)
 699{
 700   Path np = _normalize(path);
 701   FileSystemRef fs = _getFileSystemFromList(np);
 702   if (fs != NULL)
 703      return fs->resolve(np);
 704   return NULL;
 705}
 706
 707bool  MountSystem::mapFSPath( const String &inRoot, const Path &inPath, Path &outPath )
 708{
 709   FileSystemRef fs = _getFileSystemFromList(inRoot);
 710
 711   if ( fs == NULL )
 712   {
 713      outPath = Path();
 714      return false;
 715   }
 716
 717   outPath = fs->mapFrom( inPath );
 718
 719   return outPath.getFullPath() != String();
 720}
 721
 722S32 MountSystem::findByPattern( const Path &inBasePath, const String &inFilePattern, bool inRecursive, Vector<String> &outList, bool includeDirs/* =false */, bool multiMatch /* = true */ )
 723{
 724   if (mFindByPatternOverrideFS.isNull() && !inBasePath.isDirectory() )
 725      return -1;
 726
 727   DirectoryRef   dir = NULL;
 728   if (mFindByPatternOverrideFS.isNull())
 729      // open directory using standard mount system search
 730      dir = openDirectory( inBasePath );
 731   else
 732   {
 733      // use specified filesystem to open directory
 734      FileNodeRef fNode = mFindByPatternOverrideFS->resolve(inBasePath);
 735      if (fNode && (dir = dynamic_cast<Directory*>(fNode.getPointer())) != NULL)
 736         dir->open();
 737   }
 738
 739   if ( dir == NULL )
 740      return -1;
 741
 742   if (includeDirs)
 743   {
 744      // prepend cheesy "DIR:" annotation for directories
 745      outList.push_back(String("DIR:") + inBasePath.getPath());
 746   }
 747
 748   FileNode::Attributes  attrs;
 749
 750   Vector<String>    recurseDirs;
 751
 752   while ( dir->read( &attrs ) )
 753   {
 754      // skip hidden files
 755      if ( attrs.name.c_str()[0] == '.' )
 756         continue;
 757
 758      String   name( attrs.name );
 759
 760      if ( (attrs.flags & FileNode::Directory) && inRecursive )
 761      {
 762         name += '/';
 763         String   path = Path::Join( inBasePath, '/', name );
 764         recurseDirs.push_back( path );
 765      }
 766      
 767      if ( !multiMatch && FindMatch::isMatch( inFilePattern, attrs.name, false ) )
 768      {
 769         String   path = Path::Join( inBasePath, '/', name );
 770         outList.push_back( path );
 771      }
 772      
 773      if ( multiMatch && FindMatch::isMatchMultipleExprs( inFilePattern, attrs.name, false ) )
 774      {
 775         String   path = Path::Join( inBasePath, '/', name );
 776         outList.push_back( path );
 777      }
 778   }
 779
 780   dir->close();
 781
 782   for ( S32 i = 0; i < recurseDirs.size(); i++ )
 783      findByPattern( recurseDirs[i], inFilePattern, true, outList, includeDirs, multiMatch );
 784
 785   return outList.size();
 786}
 787
 788bool MountSystem::isFile(const Path& path)
 789{
 790   FileNode::Attributes attr;
 791   if (getFileAttributes(path,&attr))
 792      return attr.flags & FileNode::File;
 793   return false;
 794}
 795
 796bool MountSystem::isDirectory(const Path& path, FileSystemRef fsRef)
 797{
 798   FileNode::Attributes attr;
 799
 800   if (fsRef.isNull())
 801   {
 802      if (getFileAttributes(path,&attr))
 803         return attr.flags & FileNode::Directory;
 804      return false;
 805   }
 806   else
 807   {
 808      FileNodeRef fnRef = fsRef->resolve(path);
 809      if (fnRef.isNull())
 810         return false;
 811
 812      if (fnRef->getAttributes(&attr))
 813         return attr.flags & FileNode::Directory;
 814      return false;
 815   }
 816}
 817
 818bool MountSystem::isReadOnly(const Path& path)
 819{
 820   // first check to see if filesystem is read only
 821   FileSystemRef fs = getFileSystem(path);
 822   if ( fs.isNull() )
 823      // no filesystem owns this file...oh well, return false
 824      return false;
 825   if (fs->isReadOnly())
 826      return true;
 827
 828   // check the file attributes, note that if the file does not exist,
 829   // this function returns false.  that should be ok since we know
 830   // the file system is writable at this point.
 831   FileNode::Attributes attr;
 832   if (getFileAttributes(path,&attr))
 833      return attr.flags & FileNode::ReadOnly;
 834   return false;
 835}
 836
 837void  MountSystem::startFileChangeNotifications()
 838{
 839   for ( U32 i = 0; i < mMountList.size(); i++ )
 840   {
 841      FileSystemChangeNotifier   *notifier = mMountList[i].fileSystem->getChangeNotifier();
 842
 843      if ( notifier != NULL && !notifier->isNotifying() )
 844         notifier->startNotifier();
 845   }
 846}
 847
 848void  MountSystem::stopFileChangeNotifications()
 849{
 850   for ( U32 i = 0; i < mMountList.size(); i++ )
 851   {
 852      FileSystemChangeNotifier   *notifier = mMountList[i].fileSystem->getChangeNotifier();
 853
 854      if ( notifier != NULL && notifier->isNotifying() )
 855         notifier->stopNotifier();
 856   }
 857}
 858
 859bool MountSystem::createPath(const Path& path)
 860{
 861   if (path.getPath().isEmpty())
 862      return true;
 863   
 864   // See if the pathectory exists
 865   Path dir;
 866   dir.setRoot(path.getRoot());
 867   dir.setPath(path.getPath());
 868
 869   // in a virtual mount system, isDirectory may return true if the directory exists in a read only FS,
 870   // but the directory may not exist on a writeable filesystem that is also mounted.  
 871   // So get the target filesystem that will
 872   // be used for the full writable path and and make sure the directory exists on it.
 873   FileSystemRef fsRef = getFileSystem(path);
 874   
 875   if (isDirectory(dir,fsRef))
 876      return true;
 877   
 878   // Start from the top and work our way down
 879   Path sub;
 880   dir.setPath(path.isAbsolute()? String("/"): String());
 881   for (U32 i = 0; i < path.getDirectoryCount(); i++)
 882   {
 883      sub.setPath(path.getDirectory(i));
 884      dir.appendPath(sub);
 885      if (!isDirectory(dir,fsRef))
 886      {
 887         if (!createDirectory(dir,fsRef))
 888            return false;
 889      }
 890   }
 891   return true;
 892}
 893
 894
 895//-----------------------------------------------------------------------------
 896
 897// Default global mount system
 898#ifndef TORQUE_DISABLE_VIRTUAL_MOUNT_SYSTEM
 899// Note that the Platform::FS::MountZips() must be called in platformVolume.cpp for zip support to work.
 900static VirtualMountSystem   sgMountSystem;
 901#else
 902static MountSystem   sgMountSystem;
 903#endif
 904
 905namespace FS
 906{
 907
 908FileRef CreateFile(const Path &path)
 909{
 910   return sgMountSystem.createFile(path);
 911}
 912
 913DirectoryRef CreateDirectory(const Path &path)
 914{
 915   return sgMountSystem.createDirectory(path);
 916}
 917
 918FileRef OpenFile(const Path &path, File::AccessMode mode)
 919{
 920   return sgMountSystem.openFile(path,mode);
 921}
 922
 923bool ReadFile(const Path &inPath, void *&outData, U32 &outSize, bool inNullTerminate )
 924{
 925   FileRef  fileR = OpenFile( inPath, File::Read );
 926 
 927   outData = NULL;
 928   outSize = 0;
 929
 930   // We'll get a NULL file reference if 
 931   // the file failed to open.
 932   if ( fileR == NULL )
 933      return false;
 934
 935   outSize = fileR->getSize();
 936
 937   // Its not a failure to read an empty
 938   // file... but we can exit early.
 939   if ( outSize == 0 )
 940      return true;
 941
 942   U32 sizeRead = 0;
 943
 944   if ( inNullTerminate )
 945   {
 946      outData = new char [outSize+1];
 947      if( !outData )
 948      {
 949          // out of memory
 950          return false;
 951      }
 952      sizeRead = fileR->read(outData, outSize);
 953      static_cast<char *>(outData)[outSize] = '\0';
 954   }
 955   else
 956   {
 957      outData = new char [outSize];
 958      if( !outData )
 959      {
 960          // out of memory
 961          return false;
 962      }
 963      sizeRead = fileR->read(outData, outSize);
 964   }
 965
 966   if ( sizeRead != outSize )
 967   {
 968      delete static_cast<char *>(outData);
 969      outData = NULL;
 970      outSize = 0;
 971      return false;
 972   }
 973
 974   return true;
 975}
 976
 977DirectoryRef OpenDirectory(const Path &path)
 978{
 979   return sgMountSystem.openDirectory(path);
 980}
 981
 982bool Remove(const Path &path)
 983{
 984   return sgMountSystem.remove(path);
 985}
 986
 987bool Rename(const Path &from, const Path &to)
 988{
 989   return sgMountSystem.rename(from,to);
 990}
 991
 992bool Mount(String root, FileSystemRef fs)
 993{
 994   return sgMountSystem.mount(root,fs);
 995}
 996
 997bool Mount(String root, const Path &path)
 998{
 999   return sgMountSystem.mount(root,path);
1000}
1001
1002FileSystemRef Unmount(String root)
1003{
1004   return sgMountSystem.unmount(root);
1005}
1006
1007bool Unmount(FileSystemRef fs)
1008{
1009   return sgMountSystem.unmount(fs);
1010}
1011
1012bool SetCwd(const Path &file)
1013{
1014   return sgMountSystem.setCwd(file);
1015}
1016
1017const Path& GetCwd()
1018{
1019   return sgMountSystem.getCwd();
1020}
1021
1022FileSystemRef GetFileSystem(const Path &path)
1023{
1024   return sgMountSystem.getFileSystem(path);
1025}
1026
1027bool GetFileAttributes(const Path &path, FileNode::Attributes* attr)
1028{
1029   return sgMountSystem.getFileAttributes(path,attr);
1030}
1031
1032S32 CompareModifiedTimes(const Path& p1, const Path& p2)
1033{
1034   FileNode::Attributes a1, a2;
1035   if (!Torque::FS::GetFileAttributes(p1, &a1))
1036      return -1;
1037   if (!Torque::FS::GetFileAttributes(p2, &a2))
1038      return -1;
1039   if (a1.mtime < a2.mtime)
1040      return -1;
1041   if (a1.mtime == a2.mtime)
1042      return 0;
1043   return 1;
1044}
1045
1046FileNodeRef GetFileNode(const Path &path)
1047{
1048   return sgMountSystem.getFileNode(path);
1049}
1050
1051bool  MapFSPath( const String &inRoot, const Path &inPath, Path &outPath )
1052{
1053   return sgMountSystem.mapFSPath( inRoot, inPath, outPath );
1054}
1055
1056bool GetFSPath( const Path &inPath, Path &outPath )
1057{
1058   FileSystemRef sys = GetFileSystem( inPath );
1059   if ( sys )
1060   {
1061      outPath = sys->mapTo( inPath );
1062      return true;
1063   }
1064
1065   return false;
1066}
1067
1068S32 FindByPattern( const Path &inBasePath, const String &inFilePattern, bool inRecursive, Vector<String> &outList, bool multiMatch )
1069{
1070   return sgMountSystem.findByPattern(inBasePath, inFilePattern, inRecursive, outList, false, multiMatch);
1071}
1072
1073bool IsFile(const Path &path)
1074{
1075   return sgMountSystem.isFile(path);
1076}
1077
1078bool IsDirectory(const Path &path)
1079{
1080   return sgMountSystem.isDirectory(path);
1081}
1082
1083bool IsReadOnly(const Path &path)
1084{
1085   return sgMountSystem.isReadOnly(path);
1086}
1087
1088String MakeUniquePath( const char *path, const char *fileName, const char *ext )
1089{
1090   Path filePath;
1091
1092   filePath.setPath( path );
1093   filePath.setFileName( fileName );
1094   filePath.setExtension( ext );
1095
1096   // First get an upper bound on the range of filenames to search. This lets us
1097   // quickly skip past a large number of existing filenames.
1098   // Note: upper limit of 2^31 added to handle the degenerate case of a folder
1099   // with files named using the powers of 2, but plenty of space in between!
1100   U32 high = 1;
1101   while ( IsFile( filePath ) && ( high < 0x80000000 ) )
1102   {
1103      high = high * 2;
1104      filePath.setFileName( String::ToString( "%s%d", fileName, high ) );
1105   }
1106
1107   // Now perform binary search for first filename in the range that doesn't exist
1108   // Note that the returned name will not be strictly numerically *first* if the
1109   // existing filenames are non-sequential (eg. 4,6,7), but it will still be unique.
1110   if ( high > 1 )
1111   {
1112      U32 low = high / 2;
1113      while ( high - low > 1 )
1114      {
1115         U32 probe = low + ( high - low ) / 2;
1116         filePath.setFileName( String::ToString( "%s%d", fileName, probe ) );
1117         if ( IsFile( filePath ) )
1118            low = probe;
1119         else
1120            high = probe;
1121      }
1122
1123      // The 'high' index is guaranteed not to exist
1124      filePath.setFileName( String::ToString( "%s%d", fileName, high ) );
1125   }
1126
1127   return filePath.getFullPath();
1128}
1129
1130void  StartFileChangeNotifications() { sgMountSystem.startFileChangeNotifications(); }
1131void  StopFileChangeNotifications() { sgMountSystem.stopFileChangeNotifications(); }
1132
1133S32      GetNumMounts() { return sgMountSystem.getNumMounts(); }
1134String   GetMountRoot( S32 index ) { return sgMountSystem.getMountRoot(index); }
1135String   GetMountPath( S32 index ) { return sgMountSystem.getMountPath(index); }
1136String   GetMountType( S32 index ) { return sgMountSystem.getMountType(index); }
1137
1138bool CreatePath(const Path& path)
1139{
1140   return sgMountSystem.createPath(path);
1141}
1142
1143} // Namespace FS
1144
1145} // Namespace Torque
1146
1147
1148