volume.cpp
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