diff --git a/stepmania/src/RageUtil.cpp b/stepmania/src/RageUtil.cpp index 98688588e4..e1342cf37c 100644 --- a/stepmania/src/RageUtil.cpp +++ b/stepmania/src/RageUtil.cpp @@ -278,6 +278,7 @@ bool CreateDirectories( CString Path ) return true; } +#if 0 void GetDirListing( CString sPath, CStringArray &AddTo, bool bOnlyDirs, bool bReturnPathToo ) { CString sDir, sThrowAway; @@ -311,6 +312,7 @@ void GetDirListing( CString sPath, CStringArray &AddTo, bool bOnlyDirs, bool bRe } while( ::FindNextFile( hFind, &fd ) ); ::FindClose( hFind ); } +#endif void GetCwd(CString &s) { @@ -892,6 +894,257 @@ CString WcharDisplayText(wchar_t c) + +typedef basic_string istring; +struct File { + istring name; + bool dir; + + File() { dir=false; } + File(istring fn, bool dir_=false): name(fn), dir(dir) { } + + bool operator== (const File &rhs) const { return name==rhs.name; } + bool operator< (const File &rhs) const { return name name.size()) return false; + return !strnicmp(name.c_str(), txt.c_str(), txt.size()); + } + + bool FilenameEndsWith(const CString &ext) const + { + if(ext.empty()) return true; + int ignore = int(name.size())-int(ext.size()); + if(ignore < 0) return false; + + return !stricmp(name.c_str()+ignore, ext.c_str()); + } + + bool equal(const File &rhs) const { return name == rhs.name; } + bool equal(const CString &rhs) const { + return !stricmp(name.c_str(), rhs.c_str()); + } +}; + +#include "RageTimer.h" +#include "RageLog.h" + +struct FileSet +{ + set files; + RageTimer age; + void LoadFromDir(const CString &dir); + void GetFilesStartingAndEndingWith(const CString &beginning, const CString &ending, vector &out, bool bOnlyDirs) const; + void GetFilesEqualTo(const CString &pat, vector &out, bool bOnlyDirs) const; +}; + +void FileSet::LoadFromDir(const CString &dir) +{ + age.GetDeltaTime(); /* reset */ + files.clear(); + + CString oldpath; + GetCwd(oldpath); + if(chdir(dir) == -1) return; + + WIN32_FIND_DATA fd; + HANDLE hFind = FindFirstFile( "*", &fd ); + + if( hFind == INVALID_HANDLE_VALUE ) + { + chdir(oldpath); + return; + } + + do { + if(!strcmp(fd.cFileName, ".") || !strcmp(fd.cFileName, "..")) + continue; + + File f; + if(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + f.dir = true; + f.name=fd.cFileName; + + files.insert(f); + } while( FindNextFile( hFind, &fd ) ); + FindClose(hFind); + + chdir(oldpath); +} + +void FileSet::GetFilesStartingAndEndingWith(const CString &beginning, const CString &ending, vector &out, bool bOnlyDirs) const +{ + set::const_iterator i = files.lower_bound(File(beginning.c_str())); +// set::const_iterator end = files.upper_bound(File(beginning.c_str())); + for( ; i != files.end(); ++i) + { + if(bOnlyDirs && !i->dir) continue; + if(!i->FilenameBeginsWith(beginning)) return; + if(!i->FilenameEndsWith(ending)) continue; + + out.push_back(i->name.c_str()); + } +} + +void FileSet::GetFilesEqualTo(const CString &str, vector &out, bool bOnlyDirs) const +{ + set::const_iterator i = files.find(File(str.c_str())); + if(i == files.end()) + return; + + if(bOnlyDirs && !i->dir) + return; + + out.push_back(i->name.c_str()); +} + +/* XXX: this won't work right for URIs, eg \\foo\bar */ +bool FilenameDB::ResolvePath(CString &path) +{ + if(path == ".") return true; + if(path == "") return true; + + path.Replace("\\", "/"); + + /* Split path into components. */ + vector p; + split(path, "/", p, true); + + /* Resolve each component. Assume the first component is correct. */ + CString ret = p[0]; + for(unsigned i = 1; i < p.size(); ++i) + { + ret += "/"; + + vector lst; + FileSet &fs = GetFileSet(ret); + fs.GetFilesEqualTo(p[i], lst, false); + + /* If there were no matches, the path isn't found. */ + if(lst.empty()) return false; + + if(lst.size() > 1) + LOG->Warn("Ambiguous filenames \"%s\" and \"%s\"", + lst[0].GetString(), lst[1].GetString()); + + ret += lst[0]; + } + + if(path[path.size()-1] == '/') + path = ret + "/"; + else + path = ret; + return true; +} + +void FilenameDB::GetFilesStartingAndEndingWith(const CString &dir, const CString &beginning, const CString &ending, vector &out, bool bOnlyDirs) +{ + FileSet &fs = GetFileSet(dir); + fs.GetFilesStartingAndEndingWith(beginning, ending, out, bOnlyDirs); +} + +void FilenameDB::GetFilesEqualTo(const CString &dir, const CString &fn, vector &out, bool bOnlyDirs) +{ + FileSet &fs = GetFileSet(dir); + fs.GetFilesEqualTo(fn, out, bOnlyDirs); +} + + +void FilenameDB::GetFilesSimpleMatch(const CString &dir, const CString &fn, vector &out, bool bOnlyDirs) +{ + /* Does this contain a wildcard? */ + unsigned pos = fn.find_first_of('*'); + if(pos == fn.npos) + { + /* No; just do a regular search. */ + GetFilesEqualTo(dir, fn, out, bOnlyDirs); + } else { + /* Only one *! */ + if(pos) + ASSERT(fn.find_first_of('*', pos+1) == fn.npos); + GetFilesStartingAndEndingWith(dir, fn.substr(0, pos), fn.substr(pos+1), out, bOnlyDirs); + } +} + +FileSet &FilenameDB::GetFileSet(CString dir, bool ResolveCase) +{ + /* Normalize the path. */ + dir.Replace("\\", "/"); /* foo\bar -> foo/bar */ + dir.Replace("//", "/"); /* foo//bar -> foo/bar */ + + FileSet *ret; + map::iterator i = dirs.find(dir); + bool reload = false; + if(i == dirs.end()) + { + ret = new FileSet; + dirs[dir] = ret; + reload = true; + } + else + { + ret = i->second; + if(ret->age.PeekDeltaTime() > 30) + reload = true; + } + + if(reload) + { + CString RealDir = dir; + if(ResolveCase) + { + /* Resolve path cases (path/Path -> PATH/path). */ + ResolvePath(RealDir); + + /* Alias this name, too. */ + dirs[RealDir] = ret; + } + + ret->LoadFromDir(RealDir); + } + return *ret; +} + +FilenameDB FDB; + +void GetDirListing( CString sPath, CStringArray &AddTo, bool bOnlyDirs, bool bReturnPathToo ) +{ + /* If you want the CWD, use ".". */ + ASSERT(!sPath.empty()); + + /* XXX: for case-insensitive resolving, we assume the first element is + * correct (we need a place to start from); so if sPath is relative, + * prepend "./" */ + + unsigned pos = sPath.find_last_of("/\\"); + CString fn="*"; + if(pos != sPath.npos) + { + fn = sPath.substr(pos+1); + sPath = sPath.substr(0, pos+1); + } + + unsigned start = AddTo.size(); + FDB.GetFilesSimpleMatch(sPath, fn, AddTo, bOnlyDirs); + + if(bReturnPathToo && start < AddTo.size()) + { + FDB.ResolvePath(sPath); + while(start < AddTo.size()) + { + AddTo[start] = sPath + AddTo[start]; + start++; + } + } +} + +void FilenameDB::FlushDirCache() +{ + dirs.clear(); +} + /* Return the last named component of dir: * a/b/c -> c * a/b/c/ -> c