//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "platform/platform.h" #include "core/volume.h" #include "core/virtualMountSystem.h" #include "core/strings/findMatch.h" #include "core/util/journal/process.h" #include "core/util/safeDelete.h" #include "console/console.h" namespace Torque { using namespace FS; //----------------------------------------------------------------------------- bool FileSystemChangeNotifier::addNotification( const Path &path, ChangeDelegate callback ) { // Notifications are for files... if the path is empty // then there is nothing to do. if ( path.isEmpty() ) return false; // strip the filename and extension - we notify on dirs Path dir(cleanPath(path)); if (dir.isEmpty()) return false; dir.setFileName( String() ); dir.setExtension( String () ); // first lookup the dir to see if we already have an entry for it... DirMap::Iterator itr = mDirMap.find( dir ); FileInfoList *fileList = NULL; bool addedDir = false; // Note that GetFileAttributes can fail here if the file doesn't // exist, but thats ok and expected. You can have notifications // on files that don't exist yet. FileNode::Attributes attr; GetFileAttributes(path, &attr); if ( itr != mDirMap.end() ) { fileList = &(itr->value); // look for the file and return if we find it for ( U32 i = 0; i < fileList->getSize(); i++ ) { FileInfo &fInfo = (*fileList)[i]; if ( fInfo.filePath == path ) { // NOTE: This is bad... we should store the mod // time for each callback seperately in the future. // fInfo.savedLastModTime = attr.mtime; fInfo.signal.notify(callback); return true; } } } else { // otherwise we need to add the dir to our map and let the inherited class add it itr = mDirMap.insert( dir, FileInfoList() ); fileList = &(itr->value); addedDir = true; } FileInfo newInfo; newInfo.signal.notify(callback); newInfo.filePath = path; newInfo.savedLastModTime = attr.mtime; fileList->pushBack( newInfo ); return addedDir ? internalAddNotification( dir ) : true; } bool FileSystemChangeNotifier::removeNotification( const Path &path, ChangeDelegate callback ) { if (path.isEmpty()) return false; // strip the filename and extension - we notify on dirs Path dir(cleanPath(path)); if (dir.isEmpty()) return false; dir.setFileName( String() ); dir.setExtension( String () ); DirMap::Iterator itr = mDirMap.find( dir ); if ( itr == mDirMap.end() ) return false; FileInfoList &fileList = itr->value; // look for the file and return if we find it for ( U32 i = 0; i < fileList.getSize(); i++ ) { FileInfo &fInfo = fileList[i]; if ( fInfo.filePath == path ) { fInfo.signal.remove(callback); if (fInfo.signal.isEmpty()) fileList.erase( i ); break; } } // IF we removed the last file // THEN get rid of the dir from our map and notify inherited classes if ( fileList.getSize() == 0 ) { mDirMap.erase( dir ); return internalRemoveNotification( dir ); } return true; } void FileSystemChangeNotifier::startNotifier() { // update the timestamps of all the files we are managing DirMap::Iterator itr = mDirMap.begin(); for ( ; itr != mDirMap.end(); ++itr ) { FileInfoList &fileList = itr->value; for ( U32 i = 0; i < fileList.getSize(); i++ ) { FileInfo &fInfo = fileList[i]; // This may fail if the file doesn't exist... thats ok. FileNode::Attributes attr; GetFileAttributes(fInfo.filePath, &attr); fInfo.savedLastModTime = attr.mtime; } } mNotifying = true; Process::notify( this, &FileSystemChangeNotifier::process, PROCESS_LAST_ORDER ); } void FileSystemChangeNotifier::process() { internalProcessOnce(); } void FileSystemChangeNotifier::stopNotifier() { mNotifying = false; Process::remove( this, &FileSystemChangeNotifier::process ); } /// Makes sure paths going in and out of the notifier will have the same format String FileSystemChangeNotifier::cleanPath(const Path& dir) { // This "cleans up" the path, if we don't do this we can get mismatches on the path // coming from the notifier FileSystemRef fs = Torque::FS::GetFileSystem(dir); if (!fs) return String::EmptyString; return fs->mapFrom(fs->mapTo(dir)); } void FileSystemChangeNotifier::internalNotifyDirChanged( const Path &dir ) { DirMap::Iterator itr = mDirMap.find( dir ); if ( itr == mDirMap.end() ) return; // Gather the changed file info. FileInfoList changedList; FileInfoList &fileList = itr->value; for ( U32 i = 0; i < fileList.getSize(); i++ ) { FileInfo &fInfo = fileList[i]; FileNode::Attributes attr; bool success = GetFileAttributes(fInfo.filePath, &attr); // Ignore the file if we couldn't get the attributes (it must have // been deleted) or the last modification time isn't newer. if ( !success || attr.mtime <= fInfo.savedLastModTime ) continue; // Store the new mod time. fInfo.savedLastModTime = attr.mtime; // We're taking a copy of the FileInfo struct here so that the // callback can safely remove the notification without crashing. changedList.pushBack( fInfo ); } // Now signal all the changed files. for ( U32 i = 0; i < changedList.getSize(); i++ ) { FileInfo &fInfo = changedList[i]; Con::warnf( " : file changed [%s]", fInfo.filePath.getFullPath().c_str() ); fInfo.signal.trigger( fInfo.filePath ); } } //----------------------------------------------------------------------------- FileSystem::FileSystem() : mChangeNotifier( NULL ), mReadOnly(false) { } FileSystem::~FileSystem() { delete mChangeNotifier; mChangeNotifier = NULL; } File::File() {} File::~File() {} Directory::Directory() {} Directory::~Directory() {} bool Directory::dump(Vector& out) { const Path sourcePath = getName(); FileNode::Attributes currentAttributes; while (read(¤tAttributes)) { Path currentPath = sourcePath; currentPath.appendPath(currentPath.getFileName()); currentPath.setFileName(currentAttributes.name); out.push_back(currentPath); } return true; } bool Directory::dumpFiles(Vector& out) { const Path sourcePath = getName(); FileNode::Attributes currentAttributes; while (read(¤tAttributes)) { Path currentPath = sourcePath; currentPath.appendPath(currentPath.getFileName()); currentPath.setFileName(currentAttributes.name); if (IsFile(currentPath)) { out.push_back(currentPath); } } return true; } bool Directory::dumpDirectories(Vector& out) { const Path sourcePath = getName(); FileNode::Attributes currentAttributes; while (read(¤tAttributes)) { Path currentPath = sourcePath; currentPath.appendPath(currentPath.getFileName()); currentPath.setFileName(currentAttributes.name); const bool result = IsDirectory(currentPath); if (result) { out.push_back(currentPath); } } return true; } FileNode::FileNode() : mChecksum(0) { } Time FileNode::getModifiedTime() { Attributes attrs; bool success = getAttributes( &attrs ); if ( !success ) return Time(); return attrs.mtime; } Time FileNode::getCreatedTime() { Attributes attrs; bool success = getAttributes(&attrs); if (!success) return Time(); return attrs.ctime; } U64 FileNode::getSize() { Attributes attrs; bool success = getAttributes( &attrs ); if ( !success ) return 0; return attrs.size; } U32 FileNode::getChecksum() { bool calculateCRC = (mLastChecksum == Torque::Time()); if ( !calculateCRC ) { Torque::Time modTime = getModifiedTime(); calculateCRC = (modTime > mLastChecksum); } if ( calculateCRC ) mChecksum = calculateChecksum(); if ( mChecksum ) mLastChecksum = Time::getCurrentTime(); return mChecksum; } //----------------------------------------------------------------------------- class FileSystemRedirect: public FileSystem { friend class FileSystemRedirectChangeNotifier; public: FileSystemRedirect(MountSystem* mfs,const Path& path); String getTypeStr() const { return "Redirect"; } FileNodeRef resolve(const Path& path); FileNodeRef create(const Path& path,FileNode::Mode); bool remove(const Path& path); bool rename(const Path& a,const Path& b); Path mapTo(const Path& path); Path mapFrom(const Path& path); private: Path _merge(const Path& path); Path mPath; MountSystem *mMFS; }; class FileSystemRedirectChangeNotifier : public FileSystemChangeNotifier { public: FileSystemRedirectChangeNotifier( FileSystem *fs ); bool addNotification( const Path &path, ChangeDelegate callback ); bool removeNotification( const Path &path, ChangeDelegate callback ); protected: virtual void internalProcessOnce() {} virtual bool internalAddNotification( const Path &dir ) { return false; } virtual bool internalRemoveNotification( const Path &dir ) { return false; } }; FileSystemRedirectChangeNotifier::FileSystemRedirectChangeNotifier( FileSystem *fs ) : FileSystemChangeNotifier( fs ) { } bool FileSystemRedirectChangeNotifier::addNotification( const Path &path, ChangeDelegate callback ) { FileSystemRedirect *rfs = (FileSystemRedirect*)mFS; Path redirectPath = rfs->_merge( path ); FileSystemRef fs = rfs->mMFS->getFileSystem( redirectPath ); if ( !fs || !fs->getChangeNotifier() ) return false; return fs->getChangeNotifier()->addNotification( redirectPath, callback ); } bool FileSystemRedirectChangeNotifier::removeNotification( const Path &path, ChangeDelegate callback ) { FileSystemRedirect *rfs = (FileSystemRedirect*)mFS; Path redirectPath = rfs->_merge( path ); FileSystemRef fs = rfs->mMFS->getFileSystem( redirectPath ); if ( !fs || !fs->getChangeNotifier() ) return false; return fs->getChangeNotifier()->removeNotification( redirectPath, callback ); } FileSystemRedirect::FileSystemRedirect(MountSystem* mfs,const Path& path) { mMFS = mfs; mPath.setRoot(path.getRoot()); mPath.setPath(path.getPath()); mChangeNotifier = new FileSystemRedirectChangeNotifier( this ); } Path FileSystemRedirect::_merge(const Path& path) { Path p = mPath; p.setPath(Path::Join(p.getPath(),'/',Path::CompressPath(path.getPath()))); p.setFileName(path.getFileName()); p.setExtension(path.getExtension()); return p; } FileNodeRef FileSystemRedirect::resolve(const Path& path) { Path p = _merge(path); FileSystemRef fs = mMFS->getFileSystem(p); if (fs != NULL) return fs->resolve(p); return NULL; } FileNodeRef FileSystemRedirect::create(const Path& path,FileNode::Mode mode) { Path p = _merge(path); FileSystemRef fs = mMFS->getFileSystem(p); if (fs != NULL) return fs->create(p,mode); return NULL; } bool FileSystemRedirect::remove(const Path& path) { Path p = _merge(path); FileSystemRef fs = mMFS->getFileSystem(p); if (fs != NULL) return fs->remove(p); return false; } bool FileSystemRedirect::rename(const Path& a,const Path& b) { Path na = _merge(a); Path nb = _merge(b); FileSystemRef fsa = mMFS->getFileSystem(na); FileSystemRef fsb = mMFS->getFileSystem(nb); if (fsa.getPointer() == fsb.getPointer()) return fsa->rename(na,nb); return false; } Path FileSystemRedirect::mapTo(const Path& path) { Path p = _merge(path); FileSystemRef fs = mMFS->getFileSystem(p); if (fs != NULL) return fs->mapTo(p); return NULL; } Path FileSystemRedirect::mapFrom(const Path& path) { Path p = _merge(path); FileSystemRef fs = mMFS->getFileSystem(p); if (fs != NULL) return fs->mapFrom(p); return NULL; } //----------------------------------------------------------------------------- void MountSystem::_log(const String& msg) { String newMsg = "MountSystem: " + msg; Con::warnf("%s", newMsg.c_str()); } FileSystemRef MountSystem::_removeMountFromList(String root) { for (Vector::iterator itr = mMountList.begin(); itr != mMountList.end(); itr++) { if (root.equal( itr->root, String::NoCase )) { FileSystemRef fs = itr->fileSystem; mMountList.erase(itr); return fs; } } return NULL; } FileSystemRef MountSystem::_getFileSystemFromList(const Path& path) const { for (Vector::const_iterator itr = mMountList.begin(); itr != mMountList.end(); itr++) { if (itr->root.equal( path.getRoot(), String::NoCase )) return itr->fileSystem; } return NULL; } Path MountSystem::_normalize(const Path& path) { Path po = path; // Assign to cwd root if none is specified. if( po.getRoot().isEmpty() ) po.setRoot( mCWD.getRoot() ); // Merge in current working directory if the path is relative to // the current cwd. if( po.getRoot().equal( mCWD.getRoot(), String::NoCase ) && po.isRelative() ) { po.setPath( Path::CompressPath( Path::Join( mCWD.getPath(),'/',po.getPath() ) ) ); } return po; } bool MountSystem::copyFile(const Path& source, const Path& destination, bool noOverwrite) { // Exit out early if we're not overriding if (isFile(destination) && noOverwrite) { return true; } FileRef sourceFile = openFile(source, FS::File::AccessMode::Read); if (!sourceFile) return false; const U64 sourceFileSize = sourceFile->getSize(); void* writeBuffer = dMalloc(sourceFileSize); sourceFile->read(writeBuffer, sourceFileSize); FileRef destinationFile = openFile(destination, FS::File::AccessMode::Write); const bool success = destinationFile->write(writeBuffer, sourceFileSize) == sourceFileSize; dFree(writeBuffer); return success; } bool MountSystem::_dumpDirectories(DirectoryRef directory, Vector& directories, S32 depth, bool noBasePath, S32 currentDepth, const Path& basePath) { Vector directoryPaths; if (!directory->dumpDirectories(directoryPaths)) { return false; } // Queries against / will return a directory count of 1, but the code relies on actual directory entries (Eg. /data) so we handle that special case const U32 basePathDirectoryCount = String::compare(basePath.getFullPathWithoutRoot(), "/") == 0 ? basePath.getDirectoryCount() - 1 : basePath.getDirectoryCount(); for (U32 iteration = 0; iteration < directoryPaths.size(); ++iteration) { const Path& directoryPath = directoryPaths[iteration]; // Load the full path to the directory unless we're not supposed to include base paths String directoryPathString = directoryPath.getFullPath().c_str(); if (noBasePath) { // Build a path representing the directory tree *after* the base path query but excluding the base // So if we queried for data/ and are currently processing data/ExampleModule/datablocks we want to output // ExampleModule/datablocks Path newDirectoryPath; for (U32 iteration = basePathDirectoryCount; iteration < directoryPath.getDirectoryCount(); ++iteration) { if (iteration > basePathDirectoryCount) { newDirectoryPath.setPath(newDirectoryPath.getPath() + "/"); } newDirectoryPath.setPath(newDirectoryPath.getPath() + directoryPath.getDirectory(iteration)); } newDirectoryPath.setFileName(directoryPath.getFileName()); newDirectoryPath.setExtension(directoryPath.getExtension()); directoryPathString = newDirectoryPath.getFullPathWithoutRoot(); } // Output result and enumerate subdirectories if we're not too deep according to the depth parameter directories.push_back(StringTable->insert(directoryPathString, true)); if (currentDepth <= depth) { const String subdirectoryPath = directoryPath.getFullPath() + "/"; DirectoryRef nextDirectory = OpenDirectory(subdirectoryPath); _dumpDirectories(nextDirectory, directories, depth, noBasePath, currentDepth + 1, basePath); } } return true; } bool MountSystem::dumpDirectories(const Path& path, Vector& directories, S32 depth, bool noBasePath) { if (!isDirectory(path)) { return false; } DirectoryRef sourceDirectory = openDirectory(path); return _dumpDirectories(sourceDirectory, directories, depth, noBasePath, 1, path); } FileRef MountSystem::createFile(const Path& path) { Path np = _normalize(path); FileSystemRef fs = _getFileSystemFromList(np); if (fs && fs->isReadOnly()) { _log(String::ToString("Cannot create file %s, filesystem is read-only", path.getFullPath().c_str())); return NULL; } if (fs != NULL) return static_cast(fs->create(np,FileNode::File).getPointer()); return NULL; } DirectoryRef MountSystem::createDirectory(const Path& path, FileSystemRef fs) { Path np = _normalize(path); if (fs.isNull()) fs = _getFileSystemFromList(np); if (fs && fs->isReadOnly()) { _log(String::ToString("Cannot create directory %s, filesystem is read-only", path.getFullPath().c_str())); return NULL; } if (fs != NULL) return static_cast(fs->create(np,FileNode::Directory).getPointer()); return NULL; } FileRef MountSystem::openFile(const Path& path,File::AccessMode mode) { FileNodeRef node = getFileNode(path); if (node != NULL) { FileRef file = dynamic_cast(node.getPointer()); if (file != NULL) { if (file->open(mode)) return file; else return NULL; } } else { if (mode != File::Read) { FileRef file = createFile(path); if (file != NULL) { file->open(mode); return file; } } } return NULL; } DirectoryRef MountSystem::openDirectory(const Path& path) { FileNodeRef node = getFileNode(path); if (node != NULL) { DirectoryRef dir = dynamic_cast(node.getPointer()); if (dir != NULL) { dir->open(); return dir; } } return NULL; } bool MountSystem::remove(const Path& path) { Path np = _normalize(path); FileSystemRef fs = _getFileSystemFromList(np); if (fs && fs->isReadOnly()) { _log(String::ToString("Cannot remove path %s, filesystem is read-only", path.getFullPath().c_str())); return false; } if (fs != NULL) return fs->remove(np); return false; } bool MountSystem::rename(const Path& from,const Path& to) { // Will only rename files on the same filesystem Path pa = _normalize(from); Path pb = _normalize(to); FileSystemRef fsa = _getFileSystemFromList(pa); FileSystemRef fsb = _getFileSystemFromList(pb); if (!fsa || !fsb) return false; if (fsa.getPointer() != fsb.getPointer()) { _log(String::ToString("Cannot rename path %s to a different filesystem", from.getFullPath().c_str())); return false; } if (fsa->isReadOnly() || fsb->isReadOnly()) { _log(String::ToString("Cannot rename path %s; source or target filesystem is read-only", from.getFullPath().c_str())); return false; } return fsa->rename(pa,pb); } bool MountSystem::mount(String root,FileSystemRef fs) { MountFS mount; mount.root = root; mount.path = "/"; mount.fileSystem = fs; mMountList.push_back(mount); return true; } bool MountSystem::mount(String root,const Path &path) { return mount(root,new FileSystemRedirect(this,_normalize(path))); } FileSystemRef MountSystem::unmount(String root) { FileSystemRef first = _removeMountFromList(root); // remove remaining FSes on this root while (!_removeMountFromList(root).isNull()) ; return first; } bool MountSystem::unmount(FileSystemRef fs) { if (fs.isNull()) return false; // iterate back to front in case FS is in list multiple times. // also check that fs is not null each time since its a strong ref // so it could be nulled during removal. bool unmounted = false; for (S32 i = mMountList.size() - 1; !fs.isNull() && i >= 0; --i) { if (mMountList[i].fileSystem.getPointer() == fs.getPointer()) { mMountList.erase(i); unmounted = true; } } return unmounted; } bool MountSystem::setCwd(const Path& file) { if (file.getPath().isEmpty()) return false; mCWD.setRoot(file.getRoot()); mCWD.setPath(file.getPath()); return true; } const Path& MountSystem::getCwd() const { return mCWD; } FileSystemRef MountSystem::getFileSystem(const Path& path) { return _getFileSystemFromList(_normalize(path)); } bool MountSystem::getFileAttributes(const Path& path,FileNode::Attributes* attr) { FileNodeRef file = getFileNode(path); if (file != NULL) { bool result = file->getAttributes(attr); return result; } return false; } FileNodeRef MountSystem::getFileNode(const Path& path) { Path np = _normalize(path); FileSystemRef fs = _getFileSystemFromList(np); if (fs != NULL) return fs->resolve(np); return NULL; } bool MountSystem::mapFSPath( const String &inRoot, const Path &inPath, Path &outPath ) { FileSystemRef fs = _getFileSystemFromList(inRoot); if ( fs == NULL ) { outPath = Path(); return false; } outPath = fs->mapFrom( inPath ); return outPath.getFullPath() != String(); } S32 MountSystem::findByPattern( const Path &inBasePath, const String &inFilePattern, bool inRecursive, Vector &outList, bool includeDirs/* =false */, bool multiMatch /* = true */ ) { if (mFindByPatternOverrideFS.isNull() && !inBasePath.isDirectory() ) return -1; DirectoryRef dir = NULL; if (mFindByPatternOverrideFS.isNull()) // open directory using standard mount system search dir = openDirectory( inBasePath ); else { // use specified filesystem to open directory FileNodeRef fNode = mFindByPatternOverrideFS->resolveLoose(inBasePath); if (fNode && (dir = dynamic_cast(fNode.getPointer())) != NULL) dir->open(); } if ( dir == NULL ) return -1; if (includeDirs) { // prepend cheesy "DIR:" annotation for directories outList.push_back(String("DIR:") + inBasePath.getPath()); } FileNode::Attributes attrs; Vector recurseDirs; while ( dir->read( &attrs ) ) { String name( attrs.name ); if ( (attrs.flags & FileNode::Directory) && inRecursive ) { name += '/'; String path = Path::Join( inBasePath, '/', name ); recurseDirs.push_back( path ); } if ( !multiMatch && FindMatch::isMatch( inFilePattern, attrs.name, false ) ) { String path = Path::Join( inBasePath, '/', name ); outList.push_back( path ); } if ( multiMatch && FindMatch::isMatchMultipleExprs( inFilePattern, attrs.name, false ) ) { String path = Path::Join( inBasePath, '/', name ); outList.push_back( path ); } } dir->close(); for ( S32 i = 0; i < recurseDirs.size(); i++ ) findByPattern( recurseDirs[i], inFilePattern, true, outList, includeDirs, multiMatch ); return outList.size(); } bool MountSystem::isFile(const Path& path) { FileNode::Attributes attr; if (getFileAttributes(path,&attr)) return attr.flags & FileNode::File; return false; } bool MountSystem::isDirectory(const Path& path, FileSystemRef fsRef) { FileNode::Attributes attr; bool result = false; if (fsRef.isNull()) { if (getFileAttributes(path, &attr)) result = (attr.flags & FileNode::Directory) != 0; } else { FileNodeRef fnRef = fsRef->resolve(path); if (!fnRef.isNull() && fnRef->getAttributes(&attr)) result = (attr.flags & FileNode::Directory) != 0; } return result; } bool MountSystem::isReadOnly(const Path& path) { // first check to see if filesystem is read only FileSystemRef fs = getFileSystem(path); if ( fs.isNull() ) // no filesystem owns this file...oh well, return false return false; if (fs->isReadOnly()) return true; // check the file attributes, note that if the file does not exist, // this function returns false. that should be ok since we know // the file system is writable at this point. FileNode::Attributes attr; if (getFileAttributes(path,&attr)) return attr.flags & FileNode::ReadOnly; return false; } void MountSystem::startFileChangeNotifications() { for ( U32 i = 0; i < mMountList.size(); i++ ) { FileSystemChangeNotifier *notifier = mMountList[i].fileSystem->getChangeNotifier(); if ( notifier != NULL && !notifier->isNotifying() ) notifier->startNotifier(); } } void MountSystem::stopFileChangeNotifications() { for ( U32 i = 0; i < mMountList.size(); i++ ) { FileSystemChangeNotifier *notifier = mMountList[i].fileSystem->getChangeNotifier(); if ( notifier != NULL && notifier->isNotifying() ) notifier->stopNotifier(); } } bool MountSystem::createPath(const Path& path) { if (path.getPath().isEmpty()) return true; // See if the pathectory exists Path dir; dir.setRoot(path.getRoot()); dir.setPath(path.getPath()); // in a virtual mount system, isDirectory may return true if the directory exists in a read only FS, // but the directory may not exist on a writeable filesystem that is also mounted. // So get the target filesystem that will // be used for the full writable path and and make sure the directory exists on it. FileSystemRef fsRef = getFileSystem(path); if (isDirectory(dir,fsRef)) return true; // Start from the top and work our way down Path sub; dir.setPath(path.isAbsolute()? String("/"): String()); for (U32 i = 0; i < path.getDirectoryCount(); i++) { sub.setPath(path.getDirectory(i)); dir.appendPath(sub); if (!isDirectory(dir,fsRef)) { if (!createDirectory(dir,fsRef)) return false; } } return true; } //----------------------------------------------------------------------------- // Default global mount system #ifndef TORQUE_DISABLE_VIRTUAL_MOUNT_SYSTEM // Note that the Platform::FS::MountZips() must be called in platformVolume.cpp for zip support to work. static VirtualMountSystem sgMountSystem; #else static MountSystem sgMountSystem; #endif namespace FS { FileRef CreateFile(const Path &path) { return sgMountSystem.createFile(path); } bool CopyFile(const Path& source, const Path& destination, bool noOverwrite) { return sgMountSystem.copyFile(source, destination, noOverwrite); } bool DumpDirectories(const Path& path, Vector& directories, S32 depth, bool noBasePath) { return sgMountSystem.dumpDirectories(path, directories, depth, noBasePath); } DirectoryRef CreateDirectory(const Path &path) { return sgMountSystem.createDirectory(path); } FileRef OpenFile(const Path &path, File::AccessMode mode) { return sgMountSystem.openFile(path,mode); } bool ReadFile(const Path &inPath, void *&outData, U32 &outSize, bool inNullTerminate ) { FileRef fileR = OpenFile( inPath, File::Read ); outData = NULL; outSize = 0; // We'll get a NULL file reference if // the file failed to open. if ( fileR == NULL ) return false; outSize = fileR->getSize(); // Its not a failure to read an empty // file... but we can exit early. if ( outSize == 0 ) return true; U32 sizeRead = 0; if ( inNullTerminate ) { outData = new char [outSize+1]; if( !outData ) { // out of memory return false; } sizeRead = fileR->read(outData, outSize); static_cast(outData)[outSize] = '\0'; } else { outData = new char [outSize]; if( !outData ) { // out of memory return false; } sizeRead = fileR->read(outData, outSize); } if ( sizeRead != outSize ) { delete static_cast(outData); outData = NULL; outSize = 0; return false; } return true; } DirectoryRef OpenDirectory(const Path &path) { return sgMountSystem.openDirectory(path); } bool Remove(const Path &path) { return sgMountSystem.remove(path); } bool Rename(const Path &from, const Path &to) { return sgMountSystem.rename(from,to); } bool Mount(String root, FileSystemRef fs) { return sgMountSystem.mount(root,fs); } bool Mount(String root, const Path &path) { return sgMountSystem.mount(root,path); } FileSystemRef Unmount(String root) { return sgMountSystem.unmount(root); } bool Unmount(FileSystemRef fs) { return sgMountSystem.unmount(fs); } bool SetCwd(const Path &file) { return sgMountSystem.setCwd(file); } const Path& GetCwd() { return sgMountSystem.getCwd(); } FileSystemRef GetFileSystem(const Path &path) { return sgMountSystem.getFileSystem(path); } bool GetFileAttributes(const Path &path, FileNode::Attributes* attr) { return sgMountSystem.getFileAttributes(path,attr); } S32 CompareModifiedTimes(const Path& p1, const Path& p2) { FileNode::Attributes a1, a2; if (!Torque::FS::GetFileAttributes(p1, &a1)) return -1; if (!Torque::FS::GetFileAttributes(p2, &a2)) return -1; if (a1.mtime < a2.mtime) return -1; if (a1.mtime == a2.mtime) return 0; return 1; } FileNodeRef GetFileNode(const Path &path) { return sgMountSystem.getFileNode(path); } bool MapFSPath( const String &inRoot, const Path &inPath, Path &outPath ) { return sgMountSystem.mapFSPath( inRoot, inPath, outPath ); } bool GetFSPath( const Path &inPath, Path &outPath ) { FileSystemRef sys = GetFileSystem( inPath ); if ( sys ) { outPath = sys->mapTo( inPath ); return true; } return false; } S32 FindByPattern( const Path &inBasePath, const String &inFilePattern, bool inRecursive, Vector &outList, bool multiMatch ) { return sgMountSystem.findByPattern(inBasePath, inFilePattern, inRecursive, outList, false, multiMatch); } bool IsFile(const Path &path) { return sgMountSystem.isFile(path); } bool IsScriptFile(const char* pFilePath) { return (sgMountSystem.isFile(pFilePath) || sgMountSystem.isFile(pFilePath + String(".dso")) || sgMountSystem.isFile(pFilePath + String(".mis")) || sgMountSystem.isFile(pFilePath + String(".mis.dso")) || sgMountSystem.isFile(pFilePath + String(".gui")) || sgMountSystem.isFile(pFilePath + String(".gui.dso")) || sgMountSystem.isFile(pFilePath + String("." TORQUE_SCRIPT_EXTENSION)) || sgMountSystem.isFile(pFilePath + String("." TORQUE_SCRIPT_EXTENSION) + String(".dso"))); } bool IsDirectory(const Path &path) { return sgMountSystem.isDirectory(path); } bool IsReadOnly(const Path &path) { return sgMountSystem.isReadOnly(path); } String MakeUniquePath( const char *path, const char *fileName, const char *ext ) { Path filePath; filePath.setPath( path ); filePath.setFileName( fileName ); filePath.setExtension( ext ); // First get an upper bound on the range of filenames to search. This lets us // quickly skip past a large number of existing filenames. // Note: upper limit of 2^31 added to handle the degenerate case of a folder // with files named using the powers of 2, but plenty of space in between! U32 high = 1; while ( IsFile( filePath ) && ( high < 0x80000000 ) ) { high = high * 2; filePath.setFileName( String::ToString( "%s%d", fileName, high ) ); } // Now perform binary search for first filename in the range that doesn't exist // Note that the returned name will not be strictly numerically *first* if the // existing filenames are non-sequential (eg. 4,6,7), but it will still be unique. if ( high > 1 ) { U32 low = high / 2; while ( high - low > 1 ) { U32 probe = low + ( high - low ) / 2; filePath.setFileName( String::ToString( "%s%d", fileName, probe ) ); if ( IsFile( filePath ) ) low = probe; else high = probe; } // The 'high' index is guaranteed not to exist filePath.setFileName( String::ToString( "%s%d", fileName, high ) ); } return filePath.getFullPath(); } void StartFileChangeNotifications() { sgMountSystem.startFileChangeNotifications(); } void StopFileChangeNotifications() { sgMountSystem.stopFileChangeNotifications(); } S32 GetNumMounts() { return sgMountSystem.getNumMounts(); } String GetMountRoot( S32 index ) { return sgMountSystem.getMountRoot(index); } String GetMountPath( S32 index ) { return sgMountSystem.getMountPath(index); } String GetMountType( S32 index ) { return sgMountSystem.getMountType(index); } bool CreatePath(const Path& path) { return sgMountSystem.createPath(path); } } // Namespace FS } // Namespace Torque