diff --git a/stepmania/src/RageFileDriverDirect.cpp b/stepmania/src/RageFileDriverDirect.cpp index c9db248878..3b3ffdc122 100644 --- a/stepmania/src/RageFileDriverDirect.cpp +++ b/stepmania/src/RageFileDriverDirect.cpp @@ -6,8 +6,13 @@ #include #include #include +#include -#if defined(WIN32) +#if !defined(WIN32) +#include +#include +#else +#include #include #endif @@ -15,10 +20,101 @@ #define O_BINARY 0 #endif -/* XXX: Drop FileDB and cache/resolve in here. */ #include "RageUtil_FileDB.h" /* This driver handles direct file access. */ +class DirectFilenameDB: public FilenameDB +{ +protected: + virtual void PopulateFileSet( FileSet &fs, const CString &sPath ); +public: + DirectFilenameDB() { ExpireSeconds = 30; } +}; + +void DirectFilenameDB::PopulateFileSet( FileSet &fs, const CString &path ) +{ + CString sPath = path; + + /* Resolve path cases (path/Path -> PATH/path). */ + ResolvePath( sPath ); + + fs.age.GetDeltaTime(); /* reset */ + fs.files.clear(); + +#if defined(WIN32) + WIN32_FIND_DATA fd; + + if ( sPath.size() > 0 && sPath.Right(1) == SLASH ) + sPath.erase( sPath.size() - 1 ); + + HANDLE hFind = FindFirstFile( sPath+SLASH "*", &fd ); + + if( hFind == INVALID_HANDLE_VALUE ) + return; + + do { + if(!strcmp(fd.cFileName, ".") || !strcmp(fd.cFileName, "..")) + continue; + + File f; + f.SetName( fd.cFileName ); + f.dir = !!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + f.size = fd.nFileSizeLow; + f.mtime = fd.ftLastWriteTime.dwLowDateTime; + + fs.files.insert(f); + } while( FindNextFile( hFind, &fd ) ); + FindClose(hFind); +#else + int OldDir = open(".", O_RDONLY); + if( OldDir == -1 ) + RageException::Throw( "Couldn't open(.): %s", strerror(errno) ); + + if( chdir(sPath) == -1 ) + { + /* Only log once per dir. */ + if( LOG ) + LOG->MapLog("chdir " + sPath, "Couldn't chdir(%s): %s", sPath.c_str(), strerror(errno) ); + close( OldDir ); + return; + } + DIR *d = opendir("."); + + while(struct dirent *ent = readdir(d)) + { + if(!strcmp(ent->d_name, ".")) continue; + if(!strcmp(ent->d_name, "..")) continue; + + File f; + f.SetName( ent->d_name ); + + struct stat st; + if( stat(ent->d_name, &st) == -1 ) + { + /* If it's a broken symlink, ignore it. Otherwise, warn. */ + if( lstat(ent->d_name, &st) == 0 ) + continue; + + /* Huh? */ + if(LOG) + LOG->Warn("Got file '%s' in '%s' from list, but can't stat? (%s)", + ent->d_name, sPath.c_str(), strerror(errno)); + continue; + } else { + f.dir = (st.st_mode & S_IFDIR); + f.size = st.st_size; + f.mtime = st.st_mtime; + } + + fs.files.insert(f); + } + + closedir(d); + if( fchdir( OldDir ) == -1 ) + RageException::Throw( "Couldn't fchdir(): %s", strerror(errno) ); + close( OldDir ); +#endif +} class RageFileObjDirect: public RageFileObj { @@ -40,16 +136,21 @@ public: RageFileDriverDirect::RageFileDriverDirect( CString root_ ): root(root_) { + FDB = new DirectFilenameDB; + if( root.Right(1) != "/" ) root += '/'; } -void FDB_GetDirListing( CString sPath, CStringArray &AddTo, bool bOnlyDirs, bool bReturnPathToo ); +RageFileDriverDirect::~RageFileDriverDirect() +{ + delete FDB; +} void RageFileDriverDirect::GetDirListing( CString sPath, CStringArray &AddTo, bool bOnlyDirs, bool bReturnPathToo ) { const unsigned OldStart = AddTo.size(); - FDB_GetDirListing( root+sPath, AddTo, bOnlyDirs, bReturnPathToo ); + FDB->GetDirListing( root+sPath, AddTo, bOnlyDirs, bReturnPathToo ); if( bReturnPathToo ) { @@ -59,15 +160,70 @@ void RageFileDriverDirect::GetDirListing( CString sPath, CStringArray &AddTo, bo } } +/* mkdir -p. Doesn't fail if Path already exists and is a directory. */ +static bool CreateDirectories( CString Path ) +{ + CStringArray parts; + CString curpath; + split(Path, SLASH, parts); + + for(unsigned i = 0; i < parts.size(); ++i) + { + curpath += parts[i] + SLASH; + if( mkdir(curpath, 0755) == 0 ) + continue; + + if(errno == EEXIST) + continue; // we expect to see this error + + // Log the error, but continue on. + /* When creating a directory that already exists over Samba, Windows is + * returning ENOENT instead of EEXIST. */ + /* On Win32 when Path is only a drive letter (e.g. "i:\"), the result is + * EINVAL. */ + if( LOG ) + LOG->Warn("Couldn't create %s: %s", curpath.c_str(), strerror(errno) ); + + /* Make sure it's a directory. */ + FlushDirCache(); + if( !IsADirectory(curpath) ) + { + if( LOG ) + LOG->Warn("Couldn't create %s: path exists and is not a directory", curpath.c_str() ); + + // HACK: IsADirectory doesn't work if Path contains a drive letter. + // So, ignore IsADirectory's result and continue trying to create + // directories anyway. This shouldn't change behavior, but + // is inefficient because we don't bail early on an error. + //return false; + } + } + + return true; +} + RageFileObj *RageFileDriverDirect::Open( CString sPath, RageFile::OpenMode mode, RageFile &p, int &err ) { sPath = root + sPath; - ResolvePath( sPath ); + /* XXX: make sure this will partially resolve. eg. if "abc/def" exists, + * and we're opening "ABC/DEF/GHI/jkl/mno", make sure this will resolve + * to "abc/def/GHI/jkl/mno"; we'll create the missing ones later. */ + FDB->ResolvePath( sPath ); + int flags = O_BINARY; if( mode == RageFile::READ ) flags |= O_RDONLY; else + { + CString dir = Dirname(sPath); + if( this->GetFileType(dir) != RageFileManager::TYPE_DIR ) + CreateDirectories( dir ); flags |= O_WRONLY|O_CREAT|O_TRUNC; + } + +#if defined(XBOX) + sPath.Replace( "/", "\\" ); +#endif int fd = open( sPath, flags, 0644 ); if( fd == -1 ) @@ -79,48 +235,20 @@ RageFileObj *RageFileDriverDirect::Open( CString sPath, RageFile::OpenMode mode, return new RageFileObjDirect( fd, p ); } -static bool DoStat(CString sPath, struct stat *st) -{ - TrimRight(sPath, "/\\"); - return stat(sPath.c_str(), st) != -1; -} - RageFileManager::FileType RageFileDriverDirect::GetFileType( CString sPath ) { - sPath = root + sPath; - - ResolvePath( sPath ); - - struct stat st; - if( !DoStat(sPath, &st) ) - return RageFileManager::TYPE_NONE; - - if( st.st_mode & S_IFDIR ) - return RageFileManager::TYPE_DIR; - - return RageFileManager::TYPE_FILE; + /* XXX */ + return (RageFileManager::FileType) FDB->GetFileType( root + sPath ); } int RageFileDriverDirect::GetFileSizeInBytes( CString sPath ) { - sPath = root + sPath; - - struct stat st; - if( !DoStat(sPath, &st) ) - return -1; - - return st.st_size; + return FDB->GetFileSize( root + sPath ); } int RageFileDriverDirect::GetFileModTime( CString sPath ) { - sPath = root + sPath; - - struct stat st; - if( !DoStat(sPath, &st) ) - return -1; - - return st.st_mtime; + return FDB->GetFileModTime( root + sPath ); } #ifdef _WINDOWS @@ -173,6 +301,12 @@ bool RageFileDriverDirect::Ready() #endif } +void RageFileDriverDirect::FlushDirCache( const CString &sPath ) +{ + FDB->FlushDirCache(); +} + + RageFileObjDirect::RageFileObjDirect( int fd_, RageFile &p ): RageFileObj( p ) { diff --git a/stepmania/src/RageFileDriverDirect.h b/stepmania/src/RageFileDriverDirect.h index 628bb410a7..eb8d5a5631 100644 --- a/stepmania/src/RageFileDriverDirect.h +++ b/stepmania/src/RageFileDriverDirect.h @@ -3,11 +3,12 @@ #include "RageFileDriver.h" +class FilenameDB; class RageFileDriverDirect: public RageFileDriver { public: RageFileDriverDirect( CString root ); - virtual ~RageFileDriverDirect() { } + virtual ~RageFileDriverDirect(); RageFileObj *Open( CString path, RageFile::OpenMode mode, RageFile &p, int &err ); void GetDirListing( CString sPath, CStringArray &AddTo, bool bOnlyDirs, bool bReturnPathToo ); @@ -15,9 +16,11 @@ public: int GetFileSizeInBytes( CString sFilePath ); int GetFileModTime( CString sPath ); bool Ready(); + void FlushDirCache( const CString &sPath ); private: CString root; + FilenameDB *FDB; }; #endif diff --git a/stepmania/src/RageUtil_FileDB.cpp b/stepmania/src/RageUtil_FileDB.cpp index a17fac6123..434a5d748e 100644 --- a/stepmania/src/RageUtil_FileDB.cpp +++ b/stepmania/src/RageUtil_FileDB.cpp @@ -2,161 +2,9 @@ #include "RageUtil_FileDB.h" #include "RageUtil.h" -#include "RageTimer.h" #include "RageLog.h" -#include -#include -#include -#include -#include -#include "RageFile.h" #include "arch/arch.h" -#if !defined(WIN32) -#include -#include -#else -#include "windows.h" -#endif - -enum FileType { TTYPE_FILE, TTYPE_DIR, TTYPE_NONE }; - -struct File -{ - CString name; - CString lname; - - void SetName( const CString &fn ) - { - name = fn; - lname = name; - lname.MakeLower(); - } - - bool dir; - int size; - /* Modification time of the file. The contents of this is undefined, except that - * when the file has been modified, this value will change. */ - int mtime; - - File() { dir=false; size=-1; mtime=-1; } - File( const CString &fn ) - { - SetName( fn ); - dir=false; size=-1; mtime=-1; - } - - bool operator== (const File &rhs) const { return lname==rhs.lname; } - bool operator< (const File &rhs) const { return lname files; - RageTimer age; - void LoadFromDir(const CString &dir); - void GetFilesMatching( - const CString &beginning, const CString &containing, const CString &ending, - vector &out, bool bOnlyDirs) const; - void GetFilesEqualTo(const CString &pat, vector &out, bool bOnlyDirs) const; - - FileType GetFileType( const CString &path ) const; - int GetFileSize(const CString &path) const; - int GetFileModTime(const CString &path) const; -}; - -void FileSet::LoadFromDir(const CString &dir) -{ - age.GetDeltaTime(); /* reset */ - files.clear(); - -#if defined(WIN32) - WIN32_FIND_DATA fd; - CString dirHolder = dir ; - - if ( dirHolder.size() > 0 && dirHolder.Right(1) == SLASH ) - { - dirHolder.erase( dirHolder.size() - 1 ) ; - } - - HANDLE hFind = FindFirstFile( dirHolder+SLASH "*", &fd ); - - if( hFind == INVALID_HANDLE_VALUE ) - return; - - do { - if(!strcmp(fd.cFileName, ".") || !strcmp(fd.cFileName, "..")) - continue; - - File f; - f.SetName( fd.cFileName ); - f.dir = !!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); - f.size = fd.nFileSizeLow; - f.mtime = fd.ftLastWriteTime.dwLowDateTime; - - files.insert(f); - } while( FindNextFile( hFind, &fd ) ); - FindClose(hFind); -#else - int OldDir = open(".", O_RDONLY); - if( OldDir == -1 ) - RageException::Throw( "Couldn't open(.): %s", strerror(errno) ); - - if( chdir(dir) == -1 ) - { - /* Only log once per dir. */ - if( LOG ) - LOG->MapLog("chdir " + dir, "Couldn't chdir(%s): %s", dir.c_str(), strerror(errno) ); - close( OldDir ); - return; - } - DIR *d = opendir("."); - - while(struct dirent *ent = readdir(d)) - { - if(!strcmp(ent->d_name, ".")) continue; - if(!strcmp(ent->d_name, "..")) continue; - - File f; - f.SetName( ent->d_name ); - - struct stat st; - if( stat(ent->d_name, &st) == -1 ) - { - /* If it's a broken symlink, ignore it. Otherwise, warn. */ - if( lstat(ent->d_name, &st) == 0 ) - continue; - - /* Huh? */ - if(LOG) - LOG->Warn("Got file '%s' in '%s' from list, but can't stat? (%s)", - ent->d_name, dir.c_str(), strerror(errno)); - continue; - } else { - f.dir = (st.st_mode & S_IFDIR); - f.size = st.st_size; - f.mtime = st.st_mtime; - } - - files.insert(f); - } - - closedir(d); - if( fchdir( OldDir ) == -1 ) - RageException::Throw( "Couldn't fchdir(): %s", strerror(errno) ); - close( OldDir ); -#endif -} - /* Search for "beginning*containing*ending". */ void FileSet::GetFilesMatching(const CString &beginning, const CString &containing, const CString &ending, vector &out, bool bOnlyDirs) const { @@ -236,11 +84,7 @@ static void SplitPath( CString Path, CString &Dir, CString &Name ) if( Path.size() > 0 && Path.Right(1) == SLASH ) Path.erase( Path.size()-1 ); -#ifdef _XBOX - static Regex split("(.*\\\\)([^\\\\]+)"); -#else static Regex split("(.*/)([^/]+)"); -#endif CStringArray match; if(split.Compare(Path, match)) { @@ -249,47 +93,13 @@ static void SplitPath( CString Path, CString &Dir, CString &Name ) Name = match[1]; } else { /* No slash. */ -#ifdef _XBOX - Dir = "D:\\" ; - Name = "" ; -#else Dir = "." SLASH; Name = Path; -#endif } } -class FilenameDB -{ - FileSet &GetFileSet( CString dir ); - - /* Directories we have cached: */ - map dirs; - - void GetFilesEqualTo(const CString &dir, const CString &fn, vector &out, bool bOnlyDirs); - void GetFilesMatching(const CString &dir, - const CString &beginning, const CString &containing, const CString &ending, - vector &out, bool bOnlyDirs); - -public: - /* This handles at most one * wildcard. If we need anything more complicated, - * we'll need to use fnmatch or regex. */ - void GetFilesSimpleMatch(const CString &dir, const CString &fn, vector &out, bool bOnlyDirs); - - /* Search for "path" case-insensitively and replace it with the correct - * case. If "path" doesn't exist at all, return false and don't change it. */ - bool ResolvePath(CString &path); - - FileType GetFileType( const CString &path ); - int GetFileSize(const CString &path); - int GetFileModTime( const CString &sFilePath ); - - void FlushDirCache(); -}; - - FileType FilenameDB::GetFileType( const CString &sPath ) { CString Dir, Name; @@ -399,57 +209,43 @@ FileSet &FilenameDB::GetFileSet( CString dir ) { /* Normalize the path. */ dir.Replace("\\", SLASH); /* foo\bar -> foo/bar */ - dir.Replace("/", SLASH); /* foo//bar -> foo/bar */ dir.Replace("//", SLASH); /* foo//bar -> foo/bar */ + CString lower = dir; lower.MakeLower(); - FileSet *ret; map::iterator i = dirs.find( lower ); - bool reload = false; - if(i == dirs.end()) + if( ExpireSeconds != -1 && i != dirs.end() && i->second->age.PeekDeltaTime() >= ExpireSeconds ) { - ret = new FileSet; - dirs[lower] = ret; - reload = true; - } - else - { - ret = i->second; - if(ret->age.PeekDeltaTime() > 30) - reload = true; + delete i->second; + i = dirs.end(); } - if(reload) - { - CString RealDir = dir; + if( i != dirs.end() ) + return *i->second; - /* Resolve path cases (path/Path -> PATH/path). */ - ResolvePath( RealDir ); - - ret->LoadFromDir(RealDir); - } + FileSet *ret = new FileSet; + PopulateFileSet( *ret, dir ); + AddFileSet( dir, ret ); return *ret; } +void FilenameDB::AddFileSet( CString sPath, FileSet *fs ) +{ + sPath.MakeLower(); + dirs[sPath] = fs; +} + void FilenameDB::FlushDirCache() { - set freed; for( map::iterator i = dirs.begin(); i != dirs.end(); ++i ) - { - if( freed.find(i->second) != freed.end() ) - continue; - delete i->second; - freed.insert( i->second ); - } dirs.clear(); } -FilenameDB FDB; -void FDB_GetDirListing( CString sPath, CStringArray &AddTo, bool bOnlyDirs, bool bReturnPathToo ) +void FilenameDB::GetDirListing( CString sPath, CStringArray &AddTo, bool bOnlyDirs, bool bReturnPathToo ) { // LOG->Trace( "GetDirListing( %s )", sPath.c_str() ); @@ -475,11 +271,11 @@ void FDB_GetDirListing( CString sPath, CStringArray &AddTo, bool bOnlyDirs, bool fn = "*"; unsigned start = AddTo.size(); - FDB.GetFilesSimpleMatch(sPath, fn, AddTo, bOnlyDirs); + GetFilesSimpleMatch(sPath, fn, AddTo, bOnlyDirs); if(bReturnPathToo && start < AddTo.size()) { - FDB.ResolvePath(sPath); + ResolvePath(sPath); while(start < AddTo.size()) { AddTo[start] = sPath + AddTo[start]; @@ -488,12 +284,7 @@ void FDB_GetDirListing( CString sPath, CStringArray &AddTo, bool bOnlyDirs, bool } } -bool ResolvePath(CString &path) { return FDB.ResolvePath(path); } - -void FlushDirCache() -{ - FDB.FlushDirCache(); -} +bool ResolvePath(CString &path) { return true; } // XXX #if 0 @@ -511,83 +302,6 @@ unsigned GetFileSizeInBytes( const CString &sPath ) } void GetDirListing( CString sPath, CStringArray &AddTo, bool bOnlyDirs, bool bReturnPathToo ) { - FDB_GetDirListing( sPath, AddTo, bOnlyDirs, bReturnPathToo ); -} -#elif 0 -static bool DoStat(CString sPath, struct stat *st) -{ - TrimRight(sPath, "/\\"); - return stat(sPath, st) != -1; -} -bool DoesFileExist( const CString &sPath ) -{ - if(sPath.empty()) return false; - struct stat st; - return DoStat(sPath, &st); -} - -bool IsAFile( const CString &sPath ) -{ - return DoesFileExist(sPath) && !IsADirectory(sPath); -} - -bool IsADirectory( const CString &sPath ) -{ - if(sPath.empty()) return false; - struct stat st; - if (!DoStat(sPath, &st)) - return false; - - return !!(st.st_mode & S_IFDIR); -} - -unsigned GetFileSizeInBytes( const CString &sFilePath ) -{ - struct stat st; - if(!DoStat(sFilePath, &st)) - return 0; - - return st.st_size; -} - -int GetFileModTime( const CString &sPath ) -{ - struct stat st; - if(!DoStat(sFilePath, &st)) - return -1; - - return st.st_mtime; -} -#else -#include "RageFileManager.h" - -bool DoesFileExist( const CString &sPath ) -{ - return FILEMAN->DoesFileExist( sPath ); -} - -bool IsAFile( const CString &sPath ) -{ - return FILEMAN->IsAFile( sPath ); -} - -bool IsADirectory( const CString &sPath ) -{ - return FILEMAN->IsADirectory( sPath ); -} - -unsigned GetFileSizeInBytes( const CString &sPath ) -{ - return FILEMAN->GetFileSizeInBytes( sPath ); -} - -int GetFileModTime( const CString &sPath ) -{ - return FILEMAN->GetFileModTime( sPath ); -} - -void GetDirListing( CString sPath, CStringArray &AddTo, bool bOnlyDirs, bool bReturnPathToo ) -{ - FILEMAN->GetDirListing( sPath, AddTo, bOnlyDirs, bReturnPathToo ); + FDB.GetDirListing( sPath, AddTo, bOnlyDirs, bReturnPathToo ); } #endif diff --git a/stepmania/src/RageUtil_FileDB.h b/stepmania/src/RageUtil_FileDB.h index c35b35a5bd..3d29c845eb 100644 --- a/stepmania/src/RageUtil_FileDB.h +++ b/stepmania/src/RageUtil_FileDB.h @@ -1,13 +1,104 @@ #ifndef RAGE_UTIL_FILEDB #define RAGE_UTIL_FILEDB 1 -bool DoesFileExist( const CString &sPath ); -bool IsAFile( const CString &sPath ); -bool IsADirectory( const CString &sPath ); -bool ResolvePath(CString &path); -unsigned GetFileSizeInBytes( const CString &sFilePath ); -int GetFileModTime( const CString &sPath ); +#include +#include +#include "RageTimer.h" -void FlushDirCache(); +enum FileType { TTYPE_FILE, TTYPE_DIR, TTYPE_NONE }; + +struct File +{ + CString name; + CString lname; + + void SetName( const CString &fn ) + { + name = fn; + lname = name; + lname.MakeLower(); + } + + bool dir; + int size; + /* Modification time of the file. The contents of this is undefined, except that + * when the file has been modified, this value will change. */ + int mtime; + + File() { dir=false; size=-1; mtime=-1; } + File( const CString &fn ) + { + SetName( fn ); + dir=false; size=-1; mtime=-1; + } + + bool operator== (const File &rhs) const { return lname==rhs.lname; } + bool operator< (const File &rhs) const { return lname files; + RageTimer age; + void GetFilesMatching( + const CString &beginning, const CString &containing, const CString &ending, + vector &out, bool bOnlyDirs) const; + void GetFilesEqualTo(const CString &pat, vector &out, bool bOnlyDirs) const; + + FileType GetFileType( const CString &path ) const; + int GetFileSize(const CString &path) const; + int GetFileModTime(const CString &path) const; +}; + +class FilenameDB +{ +protected: + FileSet &GetFileSet( CString dir ); + + /* Directories we have cached: */ + map dirs; + + int ExpireSeconds; + + void GetFilesEqualTo(const CString &dir, const CString &fn, vector &out, bool bOnlyDirs); + void GetFilesMatching(const CString &dir, + const CString &beginning, const CString &containing, const CString &ending, + vector &out, bool bOnlyDirs); + void AddFileSet( CString sPath, FileSet *fs ); + + /* The given path wasn't cached. Cache it, and return the cached FileSet or + * NULL on failure. */ + virtual void PopulateFileSet( FileSet &fs, const CString &sPath ) = 0; + + +public: + FilenameDB::FilenameDB(): + ExpireSeconds( -1 ) { } + FilenameDB::~FilenameDB() { FlushDirCache(); } + + /* This handles at most two * wildcards. If we need anything more complicated, + * we'll need to use fnmatch or regex. */ + void GetFilesSimpleMatch(const CString &dir, const CString &fn, vector &out, bool bOnlyDirs); + + /* Search for "path" case-insensitively and replace it with the correct + * case. If "path" doesn't exist at all, return false and don't change it. */ + bool ResolvePath(CString &path); + + FileType GetFileType( const CString &path ); + int GetFileSize(const CString &path); + int GetFileModTime( const CString &sFilePath ); + void GetDirListing( CString sPath, CStringArray &AddTo, bool bOnlyDirs, bool bReturnPathToo ); + + void FlushDirCache(); +}; #endif