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 FileNode::Attributes attr; 813 if (fnRef->getAttributes(&attr)) 814 return attr.flags & FileNode::Directory; 815 return false; 816 } 817} 818 819bool MountSystem::isReadOnly(const Path& path) 820{ 821 // first check to see if filesystem is read only 822 FileSystemRef fs = getFileSystem(path); 823 if ( fs.isNull() ) 824 // no filesystem owns this file...oh well, return false 825 return false; 826 if (fs->isReadOnly()) 827 return true; 828 829 // check the file attributes, note that if the file does not exist, 830 // this function returns false. that should be ok since we know 831 // the file system is writable at this point. 832 FileNode::Attributes attr; 833 if (getFileAttributes(path,&attr)) 834 return attr.flags & FileNode::ReadOnly; 835 return false; 836} 837 838void MountSystem::startFileChangeNotifications() 839{ 840 for ( U32 i = 0; i < mMountList.size(); i++ ) 841 { 842 FileSystemChangeNotifier *notifier = mMountList[i].fileSystem->getChangeNotifier(); 843 844 if ( notifier != NULL && !notifier->isNotifying() ) 845 notifier->startNotifier(); 846 } 847} 848 849void MountSystem::stopFileChangeNotifications() 850{ 851 for ( U32 i = 0; i < mMountList.size(); i++ ) 852 { 853 FileSystemChangeNotifier *notifier = mMountList[i].fileSystem->getChangeNotifier(); 854 855 if ( notifier != NULL && notifier->isNotifying() ) 856 notifier->stopNotifier(); 857 } 858} 859 860bool MountSystem::createPath(const Path& path) 861{ 862 if (path.getPath().isEmpty()) 863 return true; 864 865 // See if the pathectory exists 866 Path dir; 867 dir.setRoot(path.getRoot()); 868 dir.setPath(path.getPath()); 869 870 // in a virtual mount system, isDirectory may return true if the directory exists in a read only FS, 871 // but the directory may not exist on a writeable filesystem that is also mounted. 872 // So get the target filesystem that will 873 // be used for the full writable path and and make sure the directory exists on it. 874 FileSystemRef fsRef = getFileSystem(path); 875 876 if (isDirectory(dir,fsRef)) 877 return true; 878 879 // Start from the top and work our way down 880 Path sub; 881 dir.setPath(path.isAbsolute()? String("/"): String()); 882 for (U32 i = 0; i < path.getDirectoryCount(); i++) 883 { 884 sub.setPath(path.getDirectory(i)); 885 dir.appendPath(sub); 886 if (!isDirectory(dir,fsRef)) 887 { 888 if (!createDirectory(dir,fsRef)) 889 return false; 890 } 891 } 892 return true; 893} 894 895 896//----------------------------------------------------------------------------- 897 898// Default global mount system 899#ifndef TORQUE_DISABLE_VIRTUAL_MOUNT_SYSTEM 900// Note that the Platform::FS::MountZips() must be called in platformVolume.cpp for zip support to work. 901static VirtualMountSystem sgMountSystem; 902#else 903static MountSystem sgMountSystem; 904#endif 905 906namespace FS 907{ 908 909FileRef CreateFile(const Path &path) 910{ 911 return sgMountSystem.createFile(path); 912} 913 914DirectoryRef CreateDirectory(const Path &path) 915{ 916 return sgMountSystem.createDirectory(path); 917} 918 919FileRef OpenFile(const Path &path, File::AccessMode mode) 920{ 921 return sgMountSystem.openFile(path,mode); 922} 923 924bool ReadFile(const Path &inPath, void *&outData, U32 &outSize, bool inNullTerminate ) 925{ 926 FileRef fileR = OpenFile( inPath, File::Read ); 927 928 outData = NULL; 929 outSize = 0; 930 931 // We'll get a NULL file reference if 932 // the file failed to open. 933 if ( fileR == NULL ) 934 return false; 935 936 outSize = fileR->getSize(); 937 938 // Its not a failure to read an empty 939 // file... but we can exit early. 940 if ( outSize == 0 ) 941 return true; 942 943 U32 sizeRead = 0; 944 945 if ( inNullTerminate ) 946 { 947 outData = new char [outSize+1]; 948 if( !outData ) 949 { 950 // out of memory 951 return false; 952 } 953 sizeRead = fileR->read(outData, outSize); 954 static_cast<char *>(outData)[outSize] = '\0'; 955 } 956 else 957 { 958 outData = new char [outSize]; 959 if( !outData ) 960 { 961 // out of memory 962 return false; 963 } 964 sizeRead = fileR->read(outData, outSize); 965 } 966 967 if ( sizeRead != outSize ) 968 { 969 delete static_cast<char *>(outData); 970 outData = NULL; 971 outSize = 0; 972 return false; 973 } 974 975 return true; 976} 977 978DirectoryRef OpenDirectory(const Path &path) 979{ 980 return sgMountSystem.openDirectory(path); 981} 982 983bool Remove(const Path &path) 984{ 985 return sgMountSystem.remove(path); 986} 987 988bool Rename(const Path &from, const Path &to) 989{ 990 return sgMountSystem.rename(from,to); 991} 992 993bool Mount(String root, FileSystemRef fs) 994{ 995 return sgMountSystem.mount(root,fs); 996} 997 998bool Mount(String root, const Path &path) 999{ 1000 return sgMountSystem.mount(root,path); 1001} 1002 1003FileSystemRef Unmount(String root) 1004{ 1005 return sgMountSystem.unmount(root); 1006} 1007 1008bool Unmount(FileSystemRef fs) 1009{ 1010 return sgMountSystem.unmount(fs); 1011} 1012 1013bool SetCwd(const Path &file) 1014{ 1015 return sgMountSystem.setCwd(file); 1016} 1017 1018const Path& GetCwd() 1019{ 1020 return sgMountSystem.getCwd(); 1021} 1022 1023FileSystemRef GetFileSystem(const Path &path) 1024{ 1025 return sgMountSystem.getFileSystem(path); 1026} 1027 1028bool GetFileAttributes(const Path &path, FileNode::Attributes* attr) 1029{ 1030 return sgMountSystem.getFileAttributes(path,attr); 1031} 1032 1033S32 CompareModifiedTimes(const Path& p1, const Path& p2) 1034{ 1035 FileNode::Attributes a1, a2; 1036 if (!Torque::FS::GetFileAttributes(p1, &a1)) 1037 return -1; 1038 if (!Torque::FS::GetFileAttributes(p2, &a2)) 1039 return -1; 1040 if (a1.mtime < a2.mtime) 1041 return -1; 1042 if (a1.mtime == a2.mtime) 1043 return 0; 1044 return 1; 1045} 1046 1047FileNodeRef GetFileNode(const Path &path) 1048{ 1049 return sgMountSystem.getFileNode(path); 1050} 1051 1052bool MapFSPath( const String &inRoot, const Path &inPath, Path &outPath ) 1053{ 1054 return sgMountSystem.mapFSPath( inRoot, inPath, outPath ); 1055} 1056 1057bool GetFSPath( const Path &inPath, Path &outPath ) 1058{ 1059 FileSystemRef sys = GetFileSystem( inPath ); 1060 if ( sys ) 1061 { 1062 outPath = sys->mapTo( inPath ); 1063 return true; 1064 } 1065 1066 return false; 1067} 1068 1069S32 FindByPattern( const Path &inBasePath, const String &inFilePattern, bool inRecursive, Vector<String> &outList, bool multiMatch ) 1070{ 1071 return sgMountSystem.findByPattern(inBasePath, inFilePattern, inRecursive, outList, false, multiMatch); 1072} 1073 1074bool IsFile(const Path &path) 1075{ 1076 return sgMountSystem.isFile(path); 1077} 1078 1079bool IsDirectory(const Path &path) 1080{ 1081 return sgMountSystem.isDirectory(path); 1082} 1083 1084bool IsReadOnly(const Path &path) 1085{ 1086 return sgMountSystem.isReadOnly(path); 1087} 1088 1089String MakeUniquePath( const char *path, const char *fileName, const char *ext ) 1090{ 1091 Path filePath; 1092 1093 filePath.setPath( path ); 1094 filePath.setFileName( fileName ); 1095 filePath.setExtension( ext ); 1096 1097 // First get an upper bound on the range of filenames to search. This lets us 1098 // quickly skip past a large number of existing filenames. 1099 // Note: upper limit of 2^31 added to handle the degenerate case of a folder 1100 // with files named using the powers of 2, but plenty of space in between! 1101 U32 high = 1; 1102 while ( IsFile( filePath ) && ( high < 0x80000000 ) ) 1103 { 1104 high = high * 2; 1105 filePath.setFileName( String::ToString( "%s%d", fileName, high ) ); 1106 } 1107 1108 // Now perform binary search for first filename in the range that doesn't exist 1109 // Note that the returned name will not be strictly numerically *first* if the 1110 // existing filenames are non-sequential (eg. 4,6,7), but it will still be unique. 1111 if ( high > 1 ) 1112 { 1113 U32 low = high / 2; 1114 while ( high - low > 1 ) 1115 { 1116 U32 probe = low + ( high - low ) / 2; 1117 filePath.setFileName( String::ToString( "%s%d", fileName, probe ) ); 1118 if ( IsFile( filePath ) ) 1119 low = probe; 1120 else 1121 high = probe; 1122 } 1123 1124 // The 'high' index is guaranteed not to exist 1125 filePath.setFileName( String::ToString( "%s%d", fileName, high ) ); 1126 } 1127 1128 return filePath.getFullPath(); 1129} 1130 1131void StartFileChangeNotifications() { sgMountSystem.startFileChangeNotifications(); } 1132void StopFileChangeNotifications() { sgMountSystem.stopFileChangeNotifications(); } 1133 1134S32 GetNumMounts() { return sgMountSystem.getNumMounts(); } 1135String GetMountRoot( S32 index ) { return sgMountSystem.getMountRoot(index); } 1136String GetMountPath( S32 index ) { return sgMountSystem.getMountPath(index); } 1137String GetMountType( S32 index ) { return sgMountSystem.getMountType(index); } 1138 1139bool CreatePath(const Path& path) 1140{ 1141 return sgMountSystem.createPath(path); 1142} 1143 1144} // Namespace FS 1145 1146} // Namespace Torque 1147 1148 1149
