#include "global.h" #include "RageUtil_FileDB.h" #include "RageUtil.h" #include "RageLog.h" #include "arch/arch.h" /* Search for "beginning*containing*ending". */ void FileSet::GetFilesMatching(const CString &beginning, const CString &containing, const CString &ending, vector &out, bool bOnlyDirs) const { /* "files" is a case-insensitive mapping, by filename. Use lower_bound to figure * out where to start. */ set::const_iterator i = files.lower_bound( File(beginning) ); for( ; i != files.end(); ++i) { if(bOnlyDirs && !i->dir) continue; /* Check beginning. Once we hit a filename that no longer matches beginning, * we're past all possible matches in the sort, so stop. */ if(beginning.size() > i->name.size()) break; /* can't start with it */ if(strnicmp(i->name, beginning, beginning.size())) break; /* doesn't start with it */ /* Position the end starts on: */ int end_pos = int(i->name.size())-int(ending.size()); /* Check end. */ if(end_pos < 0) continue; /* can't end with it */ if( stricmp(i->name.c_str()+end_pos, ending) ) continue; /* doesn't end with it */ /* Check containing. Do this last, since it's the slowest (substring * search instead of string match). */ if(containing.size()) { unsigned pos = i->name.find(containing, beginning.size()); if(pos == i->name.npos) continue; /* doesn't contain it */ if(pos + containing.size() > unsigned(end_pos)) continue; /* found it but it overlaps with the end */ } out.push_back( i->name ); } } void FileSet::GetFilesEqualTo(const CString &str, vector &out, bool bOnlyDirs) const { set::const_iterator i = files.find( File(str) ); if(i == files.end()) return; if(bOnlyDirs && !i->dir) return; out.push_back( i->name ); } FileType FileSet::GetFileType(const CString &path ) const { set::const_iterator i = files.find( File(path) ); if(i == files.end()) return TTYPE_NONE; return i->dir? TTYPE_DIR:TTYPE_FILE; } int FileSet::GetFileSize(const CString &path) const { set::const_iterator i = files.find( File(path) ); if(i == files.end()) return -1; return i->size; } int FileSet::GetFileModTime(const CString &path) const { set::const_iterator i = files.find( File(path) ); if(i == files.end()) return -1; return i->mtime; } /* Given "foo/bar/baz/" or "foo/bar/baz", return "foo/bar/" and "baz". */ static void SplitPath( CString Path, CString &Dir, CString &Name ) { /* Strip off any trailing slashes. */ if( Path.size() > 0 && Path.Right(1) == SLASH ) Path.erase( Path.size()-1 ); static Regex split("(.*/)([^/]+)"); CStringArray match; if(split.Compare(Path, match)) { /* At least one slash. */ Dir = match[0]; Name = match[1]; } else { /* No slash. */ Dir = "." SLASH; Name = Path; } } FileType FilenameDB::GetFileType( const CString &sPath ) { CString Dir, Name; SplitPath( sPath, Dir, Name ); FileSet &fs = GetFileSet( Dir ); return fs.GetFileType( Name ); } int FilenameDB::GetFileSize( const CString &sPath ) { CString Dir, Name; SplitPath(sPath, Dir, Name); FileSet &fs = GetFileSet( Dir ); return fs.GetFileSize(Name); } int FilenameDB::GetFileModTime( const CString &sPath ) { CString Dir, Name; SplitPath(sPath, Dir, Name); FileSet &fs = GetFileSet( Dir ); return fs.GetFileModTime(Name); } /* XXX: this won't work right for URIs, eg \\foo\bar */ bool FilenameDB::ResolvePath(CString &path) { if(path == ".") return true; if(path == "") return true; /* Split path into components. */ vector p; split(path, SLASH, p, true); /* If we have "/foo", then add a blank entry to the beginning to line things up. */ if( path.Left( strlen(SLASH) ) == SLASH ) p.insert( p.begin(), "" ); /* Resolve each component. Assume the first component is correct. XXX * don't do that! "Songs/" vs "songs/" */ CString ret = p[0]; for(unsigned i = 1; i < p.size(); ++i) { ret += SLASH; 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 ) LOG->Warn("Ambiguous filenames '%s' and '%s'", lst[0].c_str(), lst[1].c_str()); ret += lst[0]; } if(path.Right(1) == SLASH) path = ret + SLASH; else path = ret; return true; } void FilenameDB::GetFilesMatching(const CString &dir, const CString &beginning, const CString &containing, const CString &ending, vector &out, bool bOnlyDirs) { FileSet &fs = GetFileSet( dir ); fs.GetFilesMatching(beginning, containing, 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 first_pos = fn.find_first_of('*'); if(first_pos == fn.npos) { /* No; just do a regular search. */ GetFilesEqualTo(dir, fn, out, bOnlyDirs); } else { unsigned second_pos = fn.find_first_of('*', first_pos+1); if(second_pos == fn.npos) { /* Only one *: "A*B". */ /* XXX: "_blank.png*.png" shouldn't match the file "_blank.png". */ GetFilesMatching(dir, fn.substr(0, first_pos), "", fn.substr(first_pos+1), out, bOnlyDirs); } else { /* Two *s: "A*B*C". */ GetFilesMatching(dir, fn.substr(0, first_pos), fn.substr(first_pos+1, second_pos-first_pos-1), fn.substr(second_pos+1), out, bOnlyDirs); } } } FileSet &FilenameDB::GetFileSet( CString dir ) { /* Normalize the path. */ dir.Replace("\\", SLASH); /* foo\bar -> foo/bar */ dir.Replace("//", SLASH); /* foo//bar -> foo/bar */ CString lower = dir; lower.MakeLower(); map::iterator i = dirs.find( lower ); if( ExpireSeconds != -1 && i != dirs.end() && i->second->age.PeekDeltaTime() >= ExpireSeconds ) { delete i->second; dirs.erase( i ); i = dirs.end(); } if( i != dirs.end() ) return *i->second; FileSet *ret = new FileSet; PopulateFileSet( *ret, dir ); AddFileSet( dir, ret ); return *ret; } void FilenameDB::AddFileSet( CString sPath, FileSet *fs ) { sPath.MakeLower(); map::iterator it = dirs.find( sPath ); if( it != dirs.end() ) delete it->second; dirs[sPath] = fs; } /* Add the file or directory "sPath". sPath is a directory if it ends with * a slash. */ void FilenameDB::AddFile( const CString &sPath, int size, int mtime ) { vector parts; split( sPath, "/", parts, false ); CStringArray::const_iterator begin = parts.begin(); CStringArray::const_iterator end = parts.end(); bool IsDir = true; if( sPath[sPath.size()-1] != '/' ) IsDir = false; else --end; do { /* Combine all but the last part. */ CString dir = join( "/", begin, end-1 ) + "/"; const CString &fn = *(end-1); FileSet &fs = GetFileSet( dir ); File f; f.SetName( fn ); if( fs.files.find( f ) == fs.files.end() ) { f.dir = IsDir; f.size = size; f.mtime = mtime; fs.files.insert( f ); } IsDir = true; --end; } while( begin != end ); } void FilenameDB::FlushDirCache() { for( map::iterator i = dirs.begin(); i != dirs.end(); ++i ) delete i->second; dirs.clear(); } void FilenameDB::GetDirListing( CString sPath, CStringArray &AddTo, bool bOnlyDirs, bool bReturnPathToo ) { // LOG->Trace( "GetDirListing( %s )", sPath.c_str() ); /* 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 "./" */ /* Strip off the last path element and use it as a mask. */ unsigned pos = sPath.find_last_of( SLASH ); CString fn; if(pos != sPath.npos) { fn = sPath.substr(pos+1); sPath = sPath.substr(0, pos+1); } /* If there was only one path element, or if the last element was empty, * use "*". */ if(fn.size() == 0) fn = "*"; unsigned start = AddTo.size(); GetFilesSimpleMatch(sPath, fn, AddTo, bOnlyDirs); if(bReturnPathToo && start < AddTo.size()) { ResolvePath(sPath); while(start < AddTo.size()) { AddTo[start] = sPath + AddTo[start]; start++; } } } bool ResolvePath(CString &path) { return true; } // XXX #if 0 bool DoesFileExist( const CString &sPath ) { return FDB.GetFileType( sPath ) != TTYPE_NONE; } bool IsAFile( const CString &sPath ) { return FDB.GetFileType( sPath ) == TTYPE_FILE; } bool IsADirectory( const CString &sPath ) { return FDB.GetFileType( sPath ) == TTYPE_DIR; } int GetFileModTime( const CString &sPath ) { return FDB.GetFileModTime(sPath); } unsigned GetFileSizeInBytes( const CString &sPath ) { int ret = FDB.GetFileSize(sPath); // LOG->Trace("file '%s' size %i", sPath.c_str(), ret); if( ret == -1 ) return 0; return ret; } void GetDirListing( CString sPath, CStringArray &AddTo, bool bOnlyDirs, bool bReturnPathToo ) { FDB.GetDirListing( sPath, AddTo, bOnlyDirs, bReturnPathToo ); } #endif