split off FDB
This commit is contained in:
+3
-450
@@ -11,22 +11,15 @@
|
||||
*/
|
||||
|
||||
#include "RageUtil.h"
|
||||
#include "RageUtil_FileDB.h"
|
||||
#include "RageTimer.h"
|
||||
#include "RageLog.h"
|
||||
|
||||
#include <numeric>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include "RageTimer.h"
|
||||
#include "RageLog.h"
|
||||
|
||||
#if !defined(WIN32)
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
|
||||
unsigned long randseed = time(NULL);
|
||||
|
||||
@@ -348,15 +341,6 @@ void GetDirListing( CString sPath, CStringArray &AddTo, bool bOnlyDirs, bool bRe
|
||||
}
|
||||
#endif
|
||||
|
||||
void GetCwd(CString &s)
|
||||
{
|
||||
char buf[PATH_MAX];
|
||||
bool ret = getcwd(buf, PATH_MAX) != NULL;
|
||||
ASSERT(ret);
|
||||
|
||||
s = buf;
|
||||
}
|
||||
|
||||
/* Reference: http://www.theorem.com/java/CRC32.java, rewritten by Glenn Maynard.
|
||||
* Public domain. */
|
||||
unsigned int GetHashForString ( CString s )
|
||||
@@ -385,12 +369,6 @@ unsigned int GetHashForString ( CString s )
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool DoStat(CString sPath, struct stat *st)
|
||||
{
|
||||
TrimRight(sPath, "/\\");
|
||||
return stat(sPath.c_str(), st) != -1;
|
||||
}
|
||||
|
||||
unsigned int GetHashForFile( CString sPath )
|
||||
{
|
||||
unsigned int hash = 0;
|
||||
@@ -423,42 +401,6 @@ unsigned int GetHashForDirectory( CString sDir )
|
||||
return hash;
|
||||
}
|
||||
|
||||
unsigned GetFileSizeInBytes( const CString &sFilePath )
|
||||
{
|
||||
struct stat st;
|
||||
if(!DoStat(sFilePath, &st))
|
||||
return 0;
|
||||
|
||||
return st.st_size;
|
||||
}
|
||||
#if 0
|
||||
bool DoesFileExist( const CString &sPath ) { return FDB.DoesFileExist(sPath); }
|
||||
bool IsAFile( const CString &sPath ) { return FDB.IsAFile(sPath); }
|
||||
bool IsADirectory( const CString &sPath ) { return FDB.IsADirectory(sPath); }
|
||||
#else
|
||||
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);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool CompareCStringsAsc(const CString &str1, const CString &str2)
|
||||
{
|
||||
return str1.CompareNoCase( str2 ) < 0;
|
||||
@@ -893,41 +835,6 @@ int memicmp(const char *s1, const char *s2, size_t n)
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ASCII-only case insensitivity. */
|
||||
struct char_traits_char_nocase: public char_traits<char>
|
||||
{
|
||||
static bool eq( char c1, char c2 )
|
||||
{ return toupper(c1) == toupper(c2); }
|
||||
|
||||
static bool ne( char c1, char c2 )
|
||||
{ return toupper(c1) != toupper(c2); }
|
||||
|
||||
static bool lt( char c1, char c2 )
|
||||
{ return toupper(c1) < toupper(c2); }
|
||||
|
||||
static int compare( const char* s1, const char* s2, size_t n ) {
|
||||
return memicmp( s1, s2, n );
|
||||
}
|
||||
|
||||
static inline char fasttoupper(char a)
|
||||
{
|
||||
if(a < 'a' || a > 'z')
|
||||
return a;
|
||||
return a+('A'-'a');
|
||||
}
|
||||
|
||||
static const char *find( const char* s, int n, char a ) {
|
||||
a = fasttoupper(a);
|
||||
while( n-- > 0 && fasttoupper(*s) != a ) {
|
||||
++s;
|
||||
}
|
||||
if(fasttoupper(*s) == a)
|
||||
return s;
|
||||
return NULL;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* Replace &#nnnn; (decimal) &xnnnn; (hex) with corresponding UTF-8 characters. */
|
||||
void Replace_Unicode_Markers( CString &Text )
|
||||
{
|
||||
@@ -1002,359 +909,6 @@ CString WcharDisplayText(wchar_t c)
|
||||
return chr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
typedef basic_string<char,char_traits_char_nocase> 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<rhs.name; }
|
||||
|
||||
bool equal(const File &rhs) const { return name == rhs.name; }
|
||||
bool equal(const CString &rhs) const {
|
||||
return !stricmp(name.c_str(), rhs.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
struct FileSet
|
||||
{
|
||||
set<File> files;
|
||||
RageTimer age;
|
||||
void LoadFromDir(const CString &dir);
|
||||
void GetFilesMatching(
|
||||
const CString &beginning, const CString &containing, const CString &ending,
|
||||
vector<CString> &out, bool bOnlyDirs) const;
|
||||
void GetFilesEqualTo(const CString &pat, vector<CString> &out, bool bOnlyDirs) const;
|
||||
bool DoesFileExist(const CString &path) const;
|
||||
bool IsADirectory(const CString &path) const;
|
||||
bool IsAFile(const CString &path) const;
|
||||
};
|
||||
|
||||
void FileSet::LoadFromDir(const CString &dir)
|
||||
{
|
||||
age.GetDeltaTime(); /* reset */
|
||||
files.clear();
|
||||
|
||||
CString oldpath;
|
||||
GetCwd(oldpath);
|
||||
if(chdir(dir) == -1) return;
|
||||
|
||||
#if defined(WIN32)
|
||||
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);
|
||||
#else
|
||||
DIR *d = opendir(".");
|
||||
|
||||
while(struct dirent *ent = readdir(d))
|
||||
{
|
||||
if(!strcmp(ent->d_name, ".")) continue;
|
||||
if(!strcmp(ent->d_name, "..")) continue;
|
||||
|
||||
File f;
|
||||
f.dir = IsADirectory(ent->d_name);
|
||||
f.name=ent->d_name;
|
||||
|
||||
files.insert(f);
|
||||
}
|
||||
|
||||
closedir(d);
|
||||
#endif
|
||||
chdir(oldpath);
|
||||
}
|
||||
|
||||
/* Search for "beginning*containing*ending". */
|
||||
void FileSet::GetFilesMatching(const CString &beginning, const CString &containing, const CString &ending, vector<CString> &out, bool bOnlyDirs) const
|
||||
{
|
||||
set<File>::const_iterator i = files.lower_bound(File(beginning.c_str()));
|
||||
for( ; i != files.end(); ++i)
|
||||
{
|
||||
if(bOnlyDirs && !i->dir) continue;
|
||||
|
||||
/* Check beginning. */
|
||||
if(beginning.size() > i->name.size()) continue; /* can't start with it */
|
||||
if(strnicmp(i->name.c_str(), beginning.c_str(), beginning.size())) continue; /* 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.c_str())) 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.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void FileSet::GetFilesEqualTo(const CString &str, vector<CString> &out, bool bOnlyDirs) const
|
||||
{
|
||||
set<File>::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());
|
||||
}
|
||||
|
||||
bool FileSet::DoesFileExist(const CString &path) const
|
||||
{
|
||||
return files.find(File(path.c_str())) != files.end();
|
||||
}
|
||||
|
||||
bool FileSet::IsADirectory(const CString &path) const
|
||||
{
|
||||
set<File>::const_iterator i = files.find(File(path.c_str()));
|
||||
if(i == files.end())
|
||||
return false;
|
||||
return i->dir;
|
||||
}
|
||||
|
||||
bool FileSet::IsAFile(const CString &path) const
|
||||
{
|
||||
set<File>::const_iterator i = files.find(File(path.c_str()));
|
||||
if(i == files.end())
|
||||
return false;
|
||||
return !i->dir;
|
||||
}
|
||||
|
||||
/* Given "foo/bar/baz/" or "foo/bar/baz", return "foo/bar/" and "baz". */
|
||||
static void SplitPath( CString Path, CString &Dir, CString &Name )
|
||||
{
|
||||
/* Must always have at least one slash. */
|
||||
static Regex split("(.*/)([^/]+)");
|
||||
CStringArray match;
|
||||
if(split.Compare(Path, match)) {
|
||||
Dir = match[0];
|
||||
Name = match[1];
|
||||
} else {
|
||||
/* No slash. */
|
||||
Dir = "./";
|
||||
Name = Path;
|
||||
}
|
||||
}
|
||||
|
||||
bool FilenameDB::DoesFileExist( const CString &sPath )
|
||||
{
|
||||
CString Dir, Name;
|
||||
SplitPath(sPath, Dir, Name);
|
||||
FileSet &fs = GetFileSet(Dir);
|
||||
return fs.DoesFileExist(Name);
|
||||
}
|
||||
|
||||
bool FilenameDB::IsAFile( const CString &sPath )
|
||||
{
|
||||
CString Dir, Name;
|
||||
SplitPath(sPath, Dir, Name);
|
||||
FileSet &fs = GetFileSet(Dir);
|
||||
return fs.IsAFile(Name);
|
||||
}
|
||||
|
||||
bool FilenameDB::IsADirectory( const CString &sPath )
|
||||
{
|
||||
CString Dir, Name;
|
||||
SplitPath(sPath, Dir, Name);
|
||||
FileSet &fs = GetFileSet(Dir);
|
||||
return fs.IsADirectory(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;
|
||||
|
||||
path.Replace("\\", "/");
|
||||
|
||||
/* Split path into components. */
|
||||
vector<CString> 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<CString> 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].c_str(), lst[1].c_str());
|
||||
|
||||
ret += lst[0];
|
||||
}
|
||||
|
||||
if(path[path.size()-1] == '/')
|
||||
path = ret + "/";
|
||||
else
|
||||
path = ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
void FilenameDB::GetFilesMatching(const CString &dir, const CString &beginning, const CString &containing, const CString &ending, vector<CString> &out, bool bOnlyDirs)
|
||||
{
|
||||
FileSet &fs = GetFileSet(dir);
|
||||
fs.GetFilesMatching(beginning, containing, ending, out, bOnlyDirs);
|
||||
}
|
||||
|
||||
void FilenameDB::GetFilesEqualTo(const CString &dir, const CString &fn, vector<CString> &out, bool bOnlyDirs)
|
||||
{
|
||||
FileSet &fs = GetFileSet(dir);
|
||||
fs.GetFilesEqualTo(fn, out, bOnlyDirs);
|
||||
}
|
||||
|
||||
|
||||
void FilenameDB::GetFilesSimpleMatch(const CString &dir, const CString &fn, vector<CString> &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". */
|
||||
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, bool ResolveCase)
|
||||
{
|
||||
/* Normalize the path. */
|
||||
dir.Replace("\\", "/"); /* foo\bar -> foo/bar */
|
||||
dir.Replace("//", "/"); /* foo//bar -> foo/bar */
|
||||
|
||||
FileSet *ret;
|
||||
map<CString, FileSet *>::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 "./" */
|
||||
|
||||
/* Strip off the last path element and use it as a mask. */
|
||||
unsigned pos = sPath.find_last_of("/\\");
|
||||
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();
|
||||
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
|
||||
@@ -1370,7 +924,6 @@ CString Basename(CString dir)
|
||||
return dir;
|
||||
}
|
||||
|
||||
|
||||
CString Capitalize( CString s )
|
||||
{
|
||||
if( s.GetLength()==0 )
|
||||
|
||||
+36
-35
@@ -13,6 +13,7 @@
|
||||
*/
|
||||
|
||||
#include <map>
|
||||
#include "RageUtil_FileDB.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// SAFE_ Macros
|
||||
@@ -186,12 +187,6 @@ unsigned int GetHashForString( CString s );
|
||||
unsigned int GetHashForFile( CString sPath );
|
||||
unsigned int GetHashForDirectory( CString sDir ); // a hash value that remains the same as long as nothing in the directory has changed
|
||||
|
||||
bool DoStat(CString sPath, struct stat *st);
|
||||
bool DoesFileExist( const CString &sPath );
|
||||
bool IsAFile( const CString &sPath );
|
||||
bool IsADirectory( const CString &sPath );
|
||||
unsigned GetFileSizeInBytes( const CString &sFilePath );
|
||||
|
||||
bool CompareCStringsAsc(const CString &str1, const CString &str2);
|
||||
bool CompareCStringsDesc(const CString &str1, const CString &str2);
|
||||
void SortCStringArray( CStringArray &AddTo, const bool bSortAcsending = true );
|
||||
@@ -223,35 +218,6 @@ public:
|
||||
bool Compare(const CString &str, vector<CString> &matches);
|
||||
};
|
||||
|
||||
struct FileSet;
|
||||
class FilenameDB
|
||||
{
|
||||
FileSet &GetFileSet(CString dir, bool ResolveCase = true);
|
||||
|
||||
/* Directories we have cached: */
|
||||
map<CString, FileSet *> dirs;
|
||||
|
||||
void GetFilesEqualTo(const CString &dir, const CString &fn, vector<CString> &out, bool bOnlyDirs);
|
||||
void GetFilesMatching(const CString &dir,
|
||||
const CString &beginning, const CString &containing, const CString &ending,
|
||||
vector<CString> &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<CString> &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);
|
||||
|
||||
bool DoesFileExist(const CString &path);
|
||||
bool IsAFile(const CString &path);
|
||||
bool IsADirectory(const CString &path);
|
||||
|
||||
void FlushDirCache();
|
||||
};
|
||||
extern FilenameDB FDB;
|
||||
|
||||
void Replace_Unicode_Markers( CString &Text );
|
||||
void ReplaceText( CString &Text, const map<CString,CString> &m );
|
||||
@@ -265,4 +231,39 @@ CString Capitalize( CString s );
|
||||
#include <unistd.h> /* correct place with correct definitions */
|
||||
#endif
|
||||
|
||||
/* ASCII-only case insensitivity. */
|
||||
struct char_traits_char_nocase: public char_traits<char>
|
||||
{
|
||||
static bool eq( char c1, char c2 )
|
||||
{ return toupper(c1) == toupper(c2); }
|
||||
|
||||
static bool ne( char c1, char c2 )
|
||||
{ return toupper(c1) != toupper(c2); }
|
||||
|
||||
static bool lt( char c1, char c2 )
|
||||
{ return toupper(c1) < toupper(c2); }
|
||||
|
||||
static int compare( const char* s1, const char* s2, size_t n ) {
|
||||
return memicmp( s1, s2, n );
|
||||
}
|
||||
|
||||
static inline char fasttoupper(char a)
|
||||
{
|
||||
if(a < 'a' || a > 'z')
|
||||
return a;
|
||||
return a+('A'-'a');
|
||||
}
|
||||
|
||||
static const char *find( const char* s, int n, char a ) {
|
||||
a = fasttoupper(a);
|
||||
while( n-- > 0 && fasttoupper(*s) != a ) {
|
||||
++s;
|
||||
}
|
||||
if(fasttoupper(*s) == a)
|
||||
return s;
|
||||
return NULL;
|
||||
}
|
||||
};
|
||||
typedef basic_string<char,char_traits_char_nocase> istring;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,451 @@
|
||||
#include "global.h"
|
||||
|
||||
#include "RageUtil_FileDB.h"
|
||||
#include "RageUtil.h"
|
||||
#include "RageTimer.h"
|
||||
#include "RageLog.h"
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#if !defined(WIN32)
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
|
||||
static void GetCwd(CString &s)
|
||||
{
|
||||
char buf[PATH_MAX];
|
||||
bool ret = getcwd(buf, PATH_MAX) != NULL;
|
||||
ASSERT(ret);
|
||||
|
||||
s = buf;
|
||||
}
|
||||
|
||||
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<rhs.name; }
|
||||
|
||||
bool equal(const File &rhs) const { return name == rhs.name; }
|
||||
bool equal(const CString &rhs) const {
|
||||
return !stricmp(name.c_str(), rhs.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
/* This represents a directory. */
|
||||
struct FileSet
|
||||
{
|
||||
set<File> files;
|
||||
RageTimer age;
|
||||
void LoadFromDir(const CString &dir);
|
||||
void GetFilesMatching(
|
||||
const CString &beginning, const CString &containing, const CString &ending,
|
||||
vector<CString> &out, bool bOnlyDirs) const;
|
||||
void GetFilesEqualTo(const CString &pat, vector<CString> &out, bool bOnlyDirs) const;
|
||||
bool DoesFileExist(const CString &path) const;
|
||||
bool IsADirectory(const CString &path) const;
|
||||
bool IsAFile(const CString &path) const;
|
||||
};
|
||||
|
||||
void FileSet::LoadFromDir(const CString &dir)
|
||||
{
|
||||
age.GetDeltaTime(); /* reset */
|
||||
files.clear();
|
||||
|
||||
CString oldpath;
|
||||
GetCwd(oldpath);
|
||||
if(chdir(dir) == -1) return;
|
||||
|
||||
#if defined(WIN32)
|
||||
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;
|
||||
f.name=fd.cFileName;
|
||||
f.dir = !!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
|
||||
|
||||
files.insert(f);
|
||||
} while( FindNextFile( hFind, &fd ) );
|
||||
FindClose(hFind);
|
||||
#else
|
||||
DIR *d = opendir(".");
|
||||
|
||||
while(struct dirent *ent = readdir(d))
|
||||
{
|
||||
if(!strcmp(ent->d_name, ".")) continue;
|
||||
if(!strcmp(ent->d_name, "..")) continue;
|
||||
|
||||
File f;
|
||||
f.name=ent->d_name;
|
||||
f.dir = IsADirectory(ent->d_name);
|
||||
|
||||
files.insert(f);
|
||||
}
|
||||
|
||||
closedir(d);
|
||||
#endif
|
||||
chdir(oldpath);
|
||||
}
|
||||
|
||||
/* Search for "beginning*containing*ending". */
|
||||
void FileSet::GetFilesMatching(const CString &beginning, const CString &containing, const CString &ending, vector<CString> &out, bool bOnlyDirs) const
|
||||
{
|
||||
set<File>::const_iterator i = files.lower_bound(File(beginning.c_str()));
|
||||
for( ; i != files.end(); ++i)
|
||||
{
|
||||
if(bOnlyDirs && !i->dir) continue;
|
||||
|
||||
/* Check beginning. */
|
||||
if(beginning.size() > i->name.size()) continue; /* can't start with it */
|
||||
if(strnicmp(i->name.c_str(), beginning.c_str(), beginning.size())) continue; /* 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.c_str())) 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.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void FileSet::GetFilesEqualTo(const CString &str, vector<CString> &out, bool bOnlyDirs) const
|
||||
{
|
||||
set<File>::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());
|
||||
}
|
||||
|
||||
bool FileSet::DoesFileExist(const CString &path) const
|
||||
{
|
||||
return files.find(File(path.c_str())) != files.end();
|
||||
}
|
||||
|
||||
bool FileSet::IsADirectory(const CString &path) const
|
||||
{
|
||||
set<File>::const_iterator i = files.find(File(path.c_str()));
|
||||
if(i == files.end())
|
||||
return false;
|
||||
return i->dir;
|
||||
}
|
||||
|
||||
bool FileSet::IsAFile(const CString &path) const
|
||||
{
|
||||
set<File>::const_iterator i = files.find(File(path.c_str()));
|
||||
if(i == files.end())
|
||||
return false;
|
||||
return !i->dir;
|
||||
}
|
||||
|
||||
/* Given "foo/bar/baz/" or "foo/bar/baz", return "foo/bar/" and "baz". */
|
||||
static void SplitPath( CString Path, CString &Dir, CString &Name )
|
||||
{
|
||||
static Regex split("(.*/)([^/]+)");
|
||||
CStringArray match;
|
||||
if(split.Compare(Path, match)) {
|
||||
/* At least one slash. */
|
||||
Dir = match[0];
|
||||
Name = match[1];
|
||||
} else {
|
||||
/* No slash. */
|
||||
Dir = "./";
|
||||
Name = Path;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class FilenameDB
|
||||
{
|
||||
FileSet &GetFileSet(CString dir, bool ResolveCase = true);
|
||||
|
||||
/* Directories we have cached: */
|
||||
map<istring, FileSet *> dirs;
|
||||
|
||||
void GetFilesEqualTo(const CString &dir, const CString &fn, vector<CString> &out, bool bOnlyDirs);
|
||||
void GetFilesMatching(const CString &dir,
|
||||
const CString &beginning, const CString &containing, const CString &ending,
|
||||
vector<CString> &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<CString> &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);
|
||||
|
||||
bool DoesFileExist(const CString &path);
|
||||
bool IsAFile(const CString &path);
|
||||
bool IsADirectory(const CString &path);
|
||||
|
||||
void FlushDirCache();
|
||||
};
|
||||
|
||||
bool FilenameDB::DoesFileExist( const CString &sPath )
|
||||
{
|
||||
CString Dir, Name;
|
||||
SplitPath(sPath, Dir, Name);
|
||||
FileSet &fs = GetFileSet(Dir.c_str());
|
||||
return fs.DoesFileExist(Name);
|
||||
}
|
||||
|
||||
bool FilenameDB::IsAFile( const CString &sPath )
|
||||
{
|
||||
CString Dir, Name;
|
||||
SplitPath(sPath, Dir, Name);
|
||||
FileSet &fs = GetFileSet(Dir.c_str());
|
||||
return fs.IsAFile(Name);
|
||||
}
|
||||
|
||||
bool FilenameDB::IsADirectory( const CString &sPath )
|
||||
{
|
||||
CString Dir, Name;
|
||||
SplitPath(sPath, Dir, Name);
|
||||
FileSet &fs = GetFileSet(Dir.c_str());
|
||||
return fs.IsADirectory(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;
|
||||
|
||||
path.Replace("\\", "/");
|
||||
|
||||
/* Split path into components. */
|
||||
vector<CString> p;
|
||||
split(path, "/", p, true);
|
||||
|
||||
/* 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 += "/";
|
||||
|
||||
vector<CString> lst;
|
||||
FileSet &fs = GetFileSet(ret.c_str());
|
||||
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].c_str(), lst[1].c_str());
|
||||
|
||||
ret += lst[0];
|
||||
}
|
||||
|
||||
if(path[path.size()-1] == '/')
|
||||
path = ret + "/";
|
||||
else
|
||||
path = ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
void FilenameDB::GetFilesMatching(const CString &dir, const CString &beginning, const CString &containing, const CString &ending, vector<CString> &out, bool bOnlyDirs)
|
||||
{
|
||||
FileSet &fs = GetFileSet(dir.c_str());
|
||||
fs.GetFilesMatching(beginning, containing, ending, out, bOnlyDirs);
|
||||
}
|
||||
|
||||
void FilenameDB::GetFilesEqualTo(const CString &dir, const CString &fn, vector<CString> &out, bool bOnlyDirs)
|
||||
{
|
||||
FileSet &fs = GetFileSet(dir.c_str());
|
||||
fs.GetFilesEqualTo(fn, out, bOnlyDirs);
|
||||
}
|
||||
|
||||
|
||||
void FilenameDB::GetFilesSimpleMatch(const CString &dir, const CString &fn, vector<CString> &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". */
|
||||
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, bool ResolveCase)
|
||||
{
|
||||
/* Normalize the path. */
|
||||
dir.Replace("\\", "/"); /* foo\bar -> foo/bar */
|
||||
dir.Replace("//", "/"); /* foo//bar -> foo/bar */
|
||||
|
||||
FileSet *ret;
|
||||
map<istring, FileSet *>::iterator i = dirs.find(dir.c_str());
|
||||
bool reload = false;
|
||||
if(i == dirs.end())
|
||||
{
|
||||
ret = new FileSet;
|
||||
dirs[dir.c_str()] = 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.c_str()] = ret;
|
||||
}
|
||||
|
||||
ret->LoadFromDir(RealDir);
|
||||
}
|
||||
return *ret;
|
||||
}
|
||||
|
||||
void FilenameDB::FlushDirCache()
|
||||
{
|
||||
dirs.clear();
|
||||
}
|
||||
|
||||
|
||||
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 "./" */
|
||||
|
||||
/* Strip off the last path element and use it as a mask. */
|
||||
unsigned pos = sPath.find_last_of("/\\");
|
||||
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();
|
||||
FDB.GetFilesSimpleMatch(sPath, fn, AddTo, bOnlyDirs);
|
||||
|
||||
if(bReturnPathToo && start < AddTo.size())
|
||||
{
|
||||
FDB.ResolvePath(sPath);
|
||||
while(start < AddTo.size())
|
||||
{
|
||||
AddTo[start] = sPath + AddTo[start];
|
||||
start++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
bool DoesFileExist( const CString &sPath ) { return FDB.DoesFileExist(sPath); }
|
||||
bool IsAFile( const CString &sPath ) { return FDB.IsAFile(sPath); }
|
||||
bool IsADirectory( const CString &sPath ) { return FDB.IsADirectory(sPath); }
|
||||
#else
|
||||
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);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* XXX */
|
||||
bool DoStat(CString sPath, struct stat *st)
|
||||
{
|
||||
TrimRight(sPath, "/\\");
|
||||
return stat(sPath.c_str(), st) != -1;
|
||||
}
|
||||
|
||||
unsigned GetFileSizeInBytes( const CString &sFilePath )
|
||||
{
|
||||
struct stat st;
|
||||
if(!DoStat(sFilePath, &st))
|
||||
return 0;
|
||||
|
||||
return st.st_size;
|
||||
}
|
||||
|
||||
void FlushDirCache()
|
||||
{
|
||||
FDB.FlushDirCache();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef RAGE_UTIL_FILEDB
|
||||
#define RAGE_UTIL_FILEDB 1
|
||||
|
||||
bool DoesFileExist( const CString &sPath );
|
||||
bool IsAFile( const CString &sPath );
|
||||
bool IsADirectory( const CString &sPath );
|
||||
unsigned GetFileSizeInBytes( const CString &sFilePath );
|
||||
bool DoStat(CString sPath, struct stat *st);
|
||||
|
||||
void FlushDirCache();
|
||||
|
||||
#endif
|
||||
+35
-22
@@ -1,5 +1,5 @@
|
||||
# Microsoft Developer Studio Project File - Name="StepMania" - Package Owner=<4>
|
||||
# Microsoft Developer Studio Generated Build File, Format Version 60000
|
||||
# Microsoft Developer Studio Generated Build File, Format Version 6.00
|
||||
# ** DO NOT EDIT **
|
||||
|
||||
# TARGTYPE "Win32 (x86) Application" 0x0101
|
||||
@@ -61,10 +61,10 @@ LINK32=link.exe
|
||||
# SUBTRACT LINK32 /verbose /profile /pdb:none /incremental:no /nodefaultlib
|
||||
# Begin Special Build Tool
|
||||
IntDir=.\../Debug6
|
||||
TargetDir=\stepmania\stepmania
|
||||
TargetDir=\temp\stepmania
|
||||
TargetName=StepMania-debug
|
||||
SOURCE="$(InputPath)"
|
||||
PreLink_Cmds=disasm\verinc cl /Zl /nologo /c verstub.cpp /Fo$(IntDir)\
|
||||
PreLink_Cmds=disasm\verinc cl /Zl /nologo /c verstub.cpp /Fo$(IntDir)\
|
||||
PostBuild_Cmds=disasm\mapconv $(IntDir)\$(TargetName).map $(TargetDir)\StepMania.vdi ia32.vdi
|
||||
# End Special Build Tool
|
||||
|
||||
@@ -82,29 +82,25 @@ PostBuild_Cmds=disasm\mapconv $(IntDir)\$(TargetName).map $(TargetDir)\StepMania
|
||||
# PROP Intermediate_Dir "StepMania___Xbox_Debug___VC6"
|
||||
# PROP Ignore_Export_Lib 0
|
||||
# PROP Target_Dir ""
|
||||
CPP=cl.exe
|
||||
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /I "." /I "SDL-1.2.5\include" /I "SDL_image-1.2" /I "plib-1.6.0" /D "WIN32" /D "_XBOX" /D "_DEBUG" /Fr /YX"global.h" /FD /c
|
||||
# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "." /I "SDL-1.2.5\include" /I "SDL_image-1.2" /I "plib-1.6.0" /D "WIN32" /D "_XBOX" /D "_DEBUG" /Fr /YX"global.h" /FD /c
|
||||
BSC32=bscmake.exe
|
||||
# ADD BASE BSC32 /nologo
|
||||
# ADD BSC32 /nologo
|
||||
XBCP=xbecopy.exe
|
||||
# ADD BASE XBCP /NOLOGO
|
||||
# ADD XBCP /NOLOGO
|
||||
XBE=imagebld.exe
|
||||
# ADD BASE XBE /nologo /stack:0x10000 /debug
|
||||
# ADD XBE /nologo /stack:0x10000 /debug
|
||||
LINK32=link.exe
|
||||
# ADD BASE LINK32 $(intdir)\verstub.obj kernel32.lib shell32.lib user32.lib gdi32.lib advapi32.lib winmm.lib /nologo /pdb:"../debug6/StepMania-debug.pdb" /map /debug /machine:IX86 /nodefaultlib:"libcmtd.lib" /out:"../StepMania-debug.exe"
|
||||
# SUBTRACT BASE LINK32 /verbose /profile /pdb:none /incremental:no /nodefaultlib
|
||||
# ADD LINK32 $(intdir)\verstub.obj kernel32.lib shell32.lib user32.lib gdi32.lib advapi32.lib winmm.lib /nologo /pdb:"../debug6/StepMania-debug.pdb" /map /debug /machine:IX86 /nodefaultlib:"libcmtd.lib" /out:"../StepMania-debug.exe"
|
||||
# SUBTRACT LINK32 /verbose /profile /pdb:none /incremental:no /nodefaultlib
|
||||
XBE=imagebld.exe
|
||||
# ADD BASE XBE /nologo /stack:0x10000 /debug
|
||||
# ADD XBE /nologo /stack:0x10000 /debug
|
||||
XBCP=xbecopy.exe
|
||||
# ADD BASE XBCP /NOLOGO
|
||||
# ADD XBCP /NOLOGO
|
||||
BSC32=bscmake.exe
|
||||
# ADD BASE BSC32 /nologo
|
||||
# ADD BSC32 /nologo
|
||||
CPP=cl.exe
|
||||
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /I "." /I "SDL-1.2.5\include" /I "SDL_image-1.2" /I "plib-1.6.0" /D "WIN32" /D "_XBOX" /D "_DEBUG" /Fr /YX"global.h" /FD /c
|
||||
# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "." /I "SDL-1.2.5\include" /I "SDL_image-1.2" /I "plib-1.6.0" /D "WIN32" /D "_XBOX" /D "_DEBUG" /Fr /YX"global.h" /FD /c
|
||||
# Begin Special Build Tool
|
||||
IntDir=.\StepMania___Xbox_Debug___VC6
|
||||
TargetDir=.\StepMania___Xbox_Debug___VC6
|
||||
TargetName=StepMania
|
||||
SOURCE="$(InputPath)"
|
||||
PreLink_Cmds=disasm\verinc cl /Zl /nologo /c verstub.cpp /Fo$(IntDir)\
|
||||
PreLink_Cmds=disasm\verinc cl /Zl /nologo /c verstub.cpp /Fo$(IntDir)\
|
||||
PostBuild_Cmds=disasm\mapconv $(IntDir)\$(TargetName).map $(TargetDir)\StepMania.vdi ia32.vdi
|
||||
# End Special Build Tool
|
||||
|
||||
@@ -141,10 +137,10 @@ LINK32=link.exe
|
||||
# SUBTRACT LINK32 /verbose /pdb:none
|
||||
# Begin Special Build Tool
|
||||
IntDir=.\../Release6
|
||||
TargetDir=\stepmania\stepmania
|
||||
TargetDir=\temp\stepmania
|
||||
TargetName=StepMania
|
||||
SOURCE="$(InputPath)"
|
||||
PreLink_Cmds=disasm\verinc cl /Zl /nologo /c verstub.cpp /Fo$(IntDir)\
|
||||
PreLink_Cmds=disasm\verinc cl /Zl /nologo /c verstub.cpp /Fo$(IntDir)\
|
||||
PostBuild_Cmds=disasm\mapconv $(IntDir)\$(TargetName).map $(TargetDir)\StepMania.vdi ia32.vdi
|
||||
# End Special Build Tool
|
||||
|
||||
@@ -669,6 +665,23 @@ SOURCE=.\RageUtil_CharConversions.cpp
|
||||
|
||||
SOURCE=.\RageUtil_CharConversions.h
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\RageUtil_FileDB.cpp
|
||||
|
||||
!IF "$(CFG)" == "StepMania - Win32 Debug"
|
||||
|
||||
!ELSEIF "$(CFG)" == "StepMania - Xbox Debug"
|
||||
|
||||
!ELSEIF "$(CFG)" == "StepMania - Win32 Release"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\RageUtil_FileDB.h
|
||||
# End Source File
|
||||
# End Group
|
||||
# Begin Group "Data Structures"
|
||||
|
||||
|
||||
@@ -1605,6 +1605,12 @@ cl /Zl /nologo /c verstub.cpp /Fo"$(IntDir)"\
|
||||
<File
|
||||
RelativePath="RageUtil_CharConversions.h">
|
||||
</File>
|
||||
<File
|
||||
RelativePath="RageUtil_FileDB.cpp">
|
||||
</File>
|
||||
<File
|
||||
RelativePath="RageUtil_FileDB.h">
|
||||
</File>
|
||||
<Filter
|
||||
Name="Helpers"
|
||||
Filter="">
|
||||
|
||||
@@ -154,7 +154,7 @@ try_element_again:
|
||||
|
||||
if( asElementPaths.size() > 1 )
|
||||
{
|
||||
FDB.FlushDirCache();
|
||||
FlushDirCache();
|
||||
|
||||
CString message = ssprintf(
|
||||
"There is more than one theme element element that matches "
|
||||
|
||||
Reference in New Issue
Block a user