Files
itgmania212121/stepmania/src/RageFileManager.cpp
T

955 lines
27 KiB
C++
Raw Normal View History

2003-12-04 08:25:59 +00:00
#include "global.h"
#include "RageFileManager.h"
#include "RageFileDriver.h"
2005-09-03 21:28:34 +00:00
#include "RageFile.h"
#include "RageUtil.h"
#include "RageUtil_FileDB.h"
2003-12-06 05:52:44 +00:00
#include "RageLog.h"
2004-01-14 23:44:42 +00:00
#include "RageThreads.h"
2005-07-31 05:41:32 +00:00
#include "Foreach.h"
2003-12-12 22:27:21 +00:00
2004-04-05 05:22:32 +00:00
#include <cerrno>
2003-12-12 22:27:21 +00:00
#if defined(LINUX)
#include <sys/stat.h>
#endif
2003-12-04 08:25:59 +00:00
2005-09-03 20:57:58 +00:00
#if defined(WIN32) && !defined(XBOX)
2005-02-07 09:19:53 +00:00
#include <windows.h>
#endif
2003-12-04 08:25:59 +00:00
RageFileManager *FILEMAN = NULL;
2004-01-14 23:44:42 +00:00
/* Lock this before touching any of these globals (except FILEMAN itself). */
static RageEvent *g_Mutex;
2004-01-14 23:44:42 +00:00
CString InitialWorkingDirectory;
CString DirOfExecutable;
struct LoadedDriver
{
/* A loaded driver may have a base path, which modifies the path we
* pass to the driver. For example, if the base is "Songs/", and we
* want to send the path "Songs/Foo/Bar" to it, then we actually
* only send "Foo/Bar". The path "Themes/Foo" is out of the scope
* of the driver, and GetPath returns false. */
2005-09-03 21:18:59 +00:00
RageFileDriver *m_pDriver;
CString m_sType, m_sRoot, m_sMountPoint;
int m_iRefs;
2005-09-03 21:18:59 +00:00
LoadedDriver() { m_pDriver = NULL; m_iRefs = 0; }
CString GetPath( const CString &sPath ) const;
};
static vector<LoadedDriver *> g_pDrivers;
static void ReferenceAllDrivers( vector<LoadedDriver *> &apDriverList )
{
g_Mutex->Lock();
apDriverList = g_pDrivers;
for( unsigned i = 0; i < apDriverList.size(); ++i )
++apDriverList[i]->m_iRefs;
g_Mutex->Unlock();
}
static void UnreferenceAllDrivers( vector<LoadedDriver *> &apDriverList )
{
g_Mutex->Lock();
for( unsigned i = 0; i < apDriverList.size(); ++i )
--apDriverList[i]->m_iRefs;
g_Mutex->Broadcast();
g_Mutex->Unlock();
/* Clear the temporary list, to make it clear that the drivers may no longer be accessed. */
apDriverList.clear();
}
RageFileDriver *RageFileManager::GetFileDriver( CString sMountpoint )
{
FixSlashesInPlace( sMountpoint );
if( sMountpoint.size() && sMountpoint.Right(1) != "/" )
sMountpoint += '/';
g_Mutex->Lock();
RageFileDriver *pRet = NULL;
for( unsigned i = 0; i < g_pDrivers.size(); ++i )
{
if( g_pDrivers[i]->m_sMountPoint.CompareNoCase( sMountpoint ) )
continue;
pRet = g_pDrivers[i]->m_pDriver;
++g_pDrivers[i]->m_iRefs;
break;
}
g_Mutex->Unlock();
return pRet;
}
void RageFileManager::ReleaseFileDriver( RageFileDriver *pDriver )
{
ASSERT( pDriver != NULL );
g_Mutex->Lock();
unsigned i;
for( i = 0; i < g_pDrivers.size(); ++i )
{
if( g_pDrivers[i]->m_pDriver == pDriver )
break;
}
ASSERT( i != g_pDrivers.size() );
--g_pDrivers[i]->m_iRefs;
g_Mutex->Broadcast();
g_Mutex->Unlock();
}
/* Wait for the given driver to become unreferenced, and remove it from the list
* to get exclusive access to it. Returns false if the driver is no longer available
* (somebody else got it first). */
2005-09-03 21:18:59 +00:00
#if 0
static bool GrabDriver( RageFileDriver *pDriver )
{
g_Mutex->Lock();
while(1)
{
unsigned i;
for( i = 0; i < g_pDrivers.size(); ++i )
if( g_pDrivers[i]->m_pDriver == pDriver )
break;
if( i == g_pDrivers.size() )
{
g_Mutex->Unlock();
return false;
}
if( g_pDrivers[i]->m_iRefs == 0 )
{
g_pDrivers.erase( g_pDrivers.begin()+i );
return true;
}
/* The driver is in use. Wait for somebody to release a driver, and
* try again. */
g_Mutex->Wait();
}
}
2005-09-03 21:18:59 +00:00
#endif
2003-12-12 22:27:21 +00:00
// Mountpoints as directories cause a problem. If "Themes/default" is a mountpoint, and
// doesn't exist anywhere else, then GetDirListing("Themes/*") must return "default". The
// driver containing "Themes/default" won't do this; its world view begins at "BGAnimations"
// (inside "Themes/default"). We need a dummy driver that handles mountpoints. */
class RageFileDriverMountpoints: public RageFileDriver
{
public:
RageFileDriverMountpoints(): RageFileDriver( new FilenameDB ) { }
2005-09-03 21:18:59 +00:00
RageFileBasic *Open( const CString &sPath, int iMode, int &iError )
{
2005-09-03 21:18:59 +00:00
iError = (iMode == RageFile::WRITE)? ERROR_WRITING_NOT_SUPPORTED:ENOENT;
return NULL;
}
/* Never flush FDB, except in LoadFromDrivers. */
void FlushDirCache( const CString &sPath ) { }
void LoadFromDrivers( const vector<LoadedDriver *> &apDrivers )
2004-03-14 01:56:18 +00:00
{
/* XXX: Even though these two operations lock on their own, lock around
* them, too. That way, nothing can sneak in and get incorrect
* results between the flush and the re-population. */
FDB->FlushDirCache();
for( unsigned i = 0; i < apDrivers.size(); ++i )
if( apDrivers[i]->m_sMountPoint != "/" )
FDB->AddFile( apDrivers[i]->m_sMountPoint, 0, 0 );
2004-03-14 01:56:18 +00:00
}
};
static RageFileDriverMountpoints *g_Mountpoints = NULL;
2004-04-24 19:22:53 +00:00
static CString GetDirOfExecutable( CString argv0 )
{
#ifdef _XBOX
2004-04-24 19:22:53 +00:00
return "D:\\";
#else
/* argv[0] can be wrong in most OS's; try to avoid using it. */
CString sPath;
2005-09-03 20:57:58 +00:00
#if defined(WIN32)
2005-09-03 21:18:59 +00:00
char szBuf[MAX_PATH];
GetModuleFileName( NULL, szBuf, sizeof(szBuf) );
sPath = szBuf;
#else
sPath = argv0;
#endif
2004-04-24 19:22:53 +00:00
sPath.Replace( "\\", "/" );
2005-09-03 21:18:59 +00:00
bool bIsAbsolutePath = false;
2004-04-24 19:22:53 +00:00
if( sPath.size() == 0 || sPath[0] == '/' )
2005-09-03 21:18:59 +00:00
bIsAbsolutePath = true;
2005-09-03 20:57:58 +00:00
#if defined(WIN32)
2004-04-24 19:22:53 +00:00
if( sPath.size() > 2 && sPath[1] == ':' && sPath[2] == '/' )
2005-09-03 21:18:59 +00:00
bIsAbsolutePath = true;
#endif
// strip off executable name
2004-06-16 07:01:12 +00:00
size_t n = sPath.find_last_of("/");
2004-04-24 19:22:53 +00:00
if( n != sPath.npos )
sPath.erase(n);
else
2004-04-24 19:22:53 +00:00
sPath.erase();
2005-09-03 21:18:59 +00:00
if( !bIsAbsolutePath )
{
2004-04-24 19:22:53 +00:00
sPath = GetCwd() + "/" + sPath;
sPath.Replace( "\\", "/" );
}
2004-04-24 19:22:53 +00:00
return sPath;
#endif
}
static void ChangeToDirOfExecutable( CString argv0 )
{
InitialWorkingDirectory = GetCwd();
DirOfExecutable = GetDirOfExecutable( argv0 );
/* Set the CWD. Any effects of this is platform-specific; most files are read and
* written through RageFile. See also RageFileManager::RageFileManager. */
2004-06-20 01:35:25 +00:00
#if defined(_WINDOWS)
chdir( DirOfExecutable + "/.." );
2005-06-21 08:56:36 +00:00
#elif defined(__MACOSX__)
2005-09-03 21:18:59 +00:00
chdir( DirOfExecutable + "/../../.." );
#endif
}
RageFileManager::RageFileManager( CString argv0 )
2003-12-04 08:25:59 +00:00
{
2004-04-10 21:45:14 +00:00
CHECKPOINT_M( argv0 );
ChangeToDirOfExecutable( argv0 );
g_Mutex = new RageEvent("RageFileManager");
2004-01-14 23:44:42 +00:00
g_Mountpoints = new RageFileDriverMountpoints;
LoadedDriver *pLoadedDriver = new LoadedDriver;
pLoadedDriver->m_pDriver = g_Mountpoints;
pLoadedDriver->m_sMountPoint = "/";
g_pDrivers.push_back( pLoadedDriver );
2004-09-06 09:36:12 +00:00
/* The mount path is unused, but must be nonempty. */
RageFileManager::Mount( "mem", "(cache)", "/@mem" );
2004-02-10 23:39:45 +00:00
}
void RageFileManager::MountInitialFilesystems()
{
2003-12-05 06:12:47 +00:00
/* Add file search paths, higher priority first. */
2004-08-10 20:56:16 +00:00
#if defined(XBOX)
RageFileManager::Mount( "dir", "D:\\", "/" );
2003-12-06 05:45:36 +00:00
#elif defined(LINUX)
2005-05-22 02:24:59 +00:00
/* Mount the root filesystem, so we can read files in /proc, /etc, and so on.
* This is /rootfs, not /root, to avoid confusion with root's home directory. */
RageFileManager::Mount( "dir", "/", "/rootfs" );
2005-09-03 21:18:59 +00:00
/* Mount /proc, so Alsa9Buf::GetSoundCardDebugInfo() and others can access it.
2005-05-22 02:24:59 +00:00
* (Deprecated; use rootfs.) */
RageFileManager::Mount( "dir", "/proc", "/proc" );
2003-12-12 22:27:21 +00:00
2003-12-05 06:12:47 +00:00
/* We can almost do this, to have machine profiles be system-global to eg. share
* scores. It would need to handle permissions properly. */
/* RageFileManager::Mount( "dir", "/var/lib/games/stepmania", "/Data/Profiles" ); */
2003-12-05 06:12:47 +00:00
2003-12-06 05:45:36 +00:00
// CString Home = getenv( "HOME" ) + "/" + PRODUCT_NAME;
2003-12-05 06:12:47 +00:00
/*
* Next: path to write general mutable user data. If the above path fails (eg.
* wrong permissions, doesn't exist), machine memcard data will also go in here.
* XXX: It seems silly to have two ~ directories. If we're going to create a
* directory on our own, it seems like it should be a dot directory, but it
* seems wrong to put lots of data (eg. music) in one. Hmm.
*/
/* XXX: create */
/* RageFileManager::Mount( "dir", Home + "." PRODUCT_NAME, "/Data" ); */
2003-12-05 06:12:47 +00:00
/* Next, search ~/StepMania. This is where users can put music, themes, etc. */
/* RageFileManager::Mount( "dir", Home + PRODUCT_NAME, "/" ); */
2003-12-05 06:12:47 +00:00
2003-12-12 22:27:21 +00:00
/* Search for a directory with "Songs" in it. Be careful: the CWD is likely to
* be ~, and it's possible that some users will have a ~/Songs/ directory that
* has nothing to do with us, so check the initial directory last. */
CString Root = "";
struct stat st;
if( Root == "" && !stat( DirOfExecutable + "/Songs", &st ) && st.st_mode&S_IFDIR )
Root = DirOfExecutable;
if( Root == "" && !stat( InitialWorkingDirectory + "/Songs", &st ) && st.st_mode&S_IFDIR )
Root = InitialWorkingDirectory;
if( Root == "" )
RageException::Throw( "Couldn't find \"Songs\"" );
RageFileManager::Mount( "dir", Root, "/" );
2003-12-12 09:06:31 +00:00
#elif defined(_WINDOWS)
/* All Windows data goes in the directory one level above the executable. */
2004-04-10 21:45:14 +00:00
CHECKPOINT_M( ssprintf( "DOE \"%s\"", DirOfExecutable.c_str()) );
2003-12-12 09:06:31 +00:00
CStringArray parts;
split( DirOfExecutable, "/", parts );
2004-04-10 21:45:14 +00:00
CHECKPOINT_M( ssprintf( "... %i parts", parts.size()) );
2004-06-16 00:38:31 +00:00
ASSERT_M( parts.size() > 1, ssprintf("Strange DirOfExecutable: %s", DirOfExecutable.c_str()) );
2003-12-12 09:06:31 +00:00
CString Dir = join( "/", parts.begin(), parts.end()-1 );
RageFileManager::Mount( "dir", Dir, "/" );
2005-06-21 08:56:36 +00:00
#elif defined(__MACOSX__)
CHECKPOINT_M( ssprintf("DOE \"%s\"", DirOfExecutable.c_str()) );
CStringArray parts;
split( DirOfExecutable, "/", parts );
ASSERT( parts.size() > 3 );
CString Dir = '/' + join( "/", parts.begin(), parts.end()-3 );
RageFileManager::Mount( "dir", Dir, "/" );
2003-12-05 06:12:47 +00:00
#else
/* Paths relative to the CWD: */
RageFileManager::Mount( "dir", ".", "/" );
#endif
2003-12-04 08:25:59 +00:00
}
RageFileManager::~RageFileManager()
{
/* Note that drivers can use previously-loaded drivers, eg. to load a ZIP
* from the FS. Unload drivers in reverse order. */
for( int i = g_pDrivers.size()-1; i >= 0; --i )
{
delete g_pDrivers[i]->m_pDriver;
delete g_pDrivers[i];
}
g_pDrivers.clear();
// delete g_Mountpoints; // g_Mountpoints was in g_pDrivers
g_Mountpoints = NULL;
2004-01-14 23:44:42 +00:00
delete g_Mutex;
2004-01-15 00:45:53 +00:00
g_Mutex = NULL;
2003-12-04 08:25:59 +00:00
}
/* path must be normalized (FixSlashesInPlace, CollapsePath). */
2005-09-03 21:18:59 +00:00
CString LoadedDriver::GetPath( const CString &sPath ) const
2003-12-04 08:25:59 +00:00
{
/* If the path begins with /@, only match mountpoints that begin with /@. */
2005-09-03 21:18:59 +00:00
if( sPath.size() >= 2 && sPath[1] == '@' )
{
2005-09-03 21:18:59 +00:00
if( m_sMountPoint.size() < 2 || m_sMountPoint[1] != '@' )
return CString();
}
2003-12-04 08:25:59 +00:00
2005-09-03 21:18:59 +00:00
if( sPath.Left(m_sMountPoint.size()).CompareNoCase(m_sMountPoint) )
return CString(); /* no match */
2003-12-04 08:25:59 +00:00
/* Add one, so we don't cut off the leading slash. */
2005-09-03 21:18:59 +00:00
CString sRet = sPath.Right( sPath.size() - m_sMountPoint.size() + 1 );
return sRet;
2003-12-04 08:25:59 +00:00
}
static void NormalizePath( CString &sPath )
{
FixSlashesInPlace( sPath );
2004-06-03 23:17:27 +00:00
CollapsePath( sPath, true );
if( sPath.size() == 0 || sPath[0] != '/' )
sPath.insert( sPath.begin(), '/' );
}
2003-12-07 06:21:39 +00:00
bool ilt( const CString &a, const CString &b ) { return a.CompareNoCase(b) < 0; }
bool ieq( const CString &a, const CString &b ) { return a.CompareNoCase(b) == 0; }
void RageFileManager::GetDirListing( CString sPath, CStringArray &AddTo, bool bOnlyDirs, bool bReturnPathToo )
2003-12-05 02:25:32 +00:00
{
NormalizePath( sPath );
vector<LoadedDriver *> apDriverList;
ReferenceAllDrivers( apDriverList );
int iDriversThatReturnedFiles = 0;
int iOldSize = AddTo.size();
for( unsigned i = 0; i < apDriverList.size(); ++i )
2003-12-05 02:25:32 +00:00
{
LoadedDriver *pLoadedDriver = apDriverList[i];
const CString p = pLoadedDriver->GetPath( sPath );
2003-12-05 02:25:32 +00:00
if( p.size() == 0 )
continue;
const unsigned OldStart = AddTo.size();
pLoadedDriver->m_pDriver->GetDirListing( p, AddTo, bOnlyDirs, bReturnPathToo );
if( AddTo.size() != OldStart )
++iDriversThatReturnedFiles;
2003-12-05 02:25:32 +00:00
/* If returning the path, prepend the mountpoint name to the files this driver returned. */
if( bReturnPathToo && pLoadedDriver->m_sMountPoint.size() > 0 )
{
2003-12-05 02:25:32 +00:00
for( unsigned j = OldStart; j < AddTo.size(); ++j )
{
/* Skip the trailing slash on the mountpoint; there's already a slash there. */
CString &sPath = AddTo[j];
sPath.insert( 0, pLoadedDriver->m_sMountPoint, pLoadedDriver->m_sMountPoint.size()-1 );
}
}
2003-12-05 02:25:32 +00:00
}
UnreferenceAllDrivers( apDriverList );
if( iDriversThatReturnedFiles > 1 )
{
/* More than one driver returned files. Remove duplicates (case-insensitively). */
sort( AddTo.begin()+iOldSize, AddTo.end(), ilt );
CStringArray::iterator it = unique( AddTo.begin()+iOldSize, AddTo.end(), ieq );
AddTo.erase(it, AddTo.end());
}
2003-12-05 02:25:32 +00:00
}
2003-12-04 08:25:59 +00:00
bool RageFileManager::Remove( CString sPath )
2003-12-16 07:23:54 +00:00
{
vector<LoadedDriver *> apDriverList;
ReferenceAllDrivers( apDriverList );
2004-01-14 23:44:42 +00:00
NormalizePath( sPath );
2003-12-16 07:23:54 +00:00
/* Multiple drivers may have the same file. */
2005-09-03 21:18:59 +00:00
bool bDeleted = false;
for( unsigned i = 0; i < apDriverList.size(); ++i )
2003-12-16 07:23:54 +00:00
{
const CString p = apDriverList[i]->GetPath( sPath );
2003-12-16 07:23:54 +00:00
if( p.size() == 0 )
continue;
bool ret = apDriverList[i]->m_pDriver->Remove( p );
if( ret )
2005-09-03 21:18:59 +00:00
bDeleted = true;
2003-12-16 07:23:54 +00:00
}
UnreferenceAllDrivers( apDriverList );
2005-09-03 21:18:59 +00:00
return bDeleted;
2003-12-16 07:23:54 +00:00
}
void RageFileManager::CreateDir( CString sDir )
{
CString sTempFile = sDir + "temp";
RageFile f;
f.Open( sTempFile, RageFile::WRITE );
f.Close();
// YUCK: The dir cache doesn't have this new file we just created,
// so the delete will fail unless we flush.
FILEMAN->FlushDirCache( sDir );
FILEMAN->Remove( sTempFile );
}
2005-06-25 22:34:54 +00:00
static void AdjustMountpoint( CString &sMountPoint )
2003-12-04 08:25:59 +00:00
{
2005-06-25 22:34:54 +00:00
FixSlashesInPlace( sMountPoint );
ASSERT_M( sMountPoint.Left(1) == "/", "Mountpoints must be absolute: " + sMountPoint );
2005-06-25 22:34:54 +00:00
if( sMountPoint.size() && sMountPoint.Right(1) != "/" )
sMountPoint += '/';
2005-06-25 22:34:54 +00:00
if( sMountPoint.Left(1) != "/" )
sMountPoint = "/" + sMountPoint;
}
static void AddFilesystemDriver( LoadedDriver *pLoadedDriver, bool bAddToEnd )
2005-06-25 22:34:54 +00:00
{
g_Mutex->Lock();
g_pDrivers.insert( bAddToEnd? g_pDrivers.end():g_pDrivers.begin(), pLoadedDriver );
g_Mountpoints->LoadFromDrivers( g_pDrivers );
2005-06-25 22:34:54 +00:00
g_Mutex->Unlock();
}
2005-09-03 21:18:59 +00:00
void RageFileManager::Mount( CString sType, CString sRoot, CString sMountPoint, bool bAddToEnd )
2005-06-25 22:34:54 +00:00
{
2005-09-03 21:18:59 +00:00
FixSlashesInPlace( sRoot );
AdjustMountpoint( sMountPoint );
2005-09-03 21:18:59 +00:00
ASSERT( !sRoot.empty() );
2003-12-04 08:25:59 +00:00
2005-09-03 21:18:59 +00:00
CHECKPOINT_M( ssprintf("\"%s\", \"%s\", \"%s\"", sType.c_str(), sRoot.c_str(), sMountPoint.c_str() ) );
2004-04-10 21:45:14 +00:00
// Unmount anything that was previously mounted here.
2005-09-03 21:18:59 +00:00
Unmount( sType, sRoot, sMountPoint );
2004-04-10 21:45:14 +00:00
CHECKPOINT;
2005-09-03 21:18:59 +00:00
RageFileDriver *pDriver = MakeFileDriver( sType, sRoot );
if( pDriver == NULL )
2003-12-06 05:52:44 +00:00
{
2004-04-10 21:45:14 +00:00
CHECKPOINT;
2004-02-11 04:18:03 +00:00
if( LOG )
2005-09-03 21:18:59 +00:00
LOG->Warn("Can't mount unknown VFS type \"%s\", root \"%s\"", sType.c_str(), sRoot.c_str() );
else
2005-09-03 21:18:59 +00:00
fprintf( stderr, "Can't mount unknown VFS type \"%s\", root \"%s\"\n", sType.c_str(), sRoot.c_str() );
2003-12-04 08:25:59 +00:00
return;
2003-12-06 05:52:44 +00:00
}
2004-04-10 21:45:14 +00:00
CHECKPOINT;
LoadedDriver *pLoadedDriver = new LoadedDriver;
pLoadedDriver->m_pDriver = pDriver;
pLoadedDriver->m_sType = sType;
pLoadedDriver->m_sRoot = sRoot;
pLoadedDriver->m_sMountPoint = sMountPoint;
AddFilesystemDriver( pLoadedDriver, bAddToEnd );
2005-06-25 22:34:54 +00:00
}
/* Mount a custom filesystem. */
void RageFileManager::Mount( RageFileDriver *pDriver, CString sMountPoint, bool bAddToEnd )
{
AdjustMountpoint( sMountPoint );
LoadedDriver *pLoadedDriver = new LoadedDriver;
pLoadedDriver->m_pDriver = pDriver;
pLoadedDriver->m_sType = "";
pLoadedDriver->m_sRoot = "";
pLoadedDriver->m_sMountPoint = sMountPoint;
2005-06-25 22:34:54 +00:00
AddFilesystemDriver( pLoadedDriver, bAddToEnd );
2003-12-04 08:25:59 +00:00
}
2003-12-05 00:07:18 +00:00
2005-09-03 21:18:59 +00:00
void RageFileManager::Unmount( CString sType, CString sRoot, CString sMountPoint )
2004-03-14 01:56:18 +00:00
{
2005-09-03 21:18:59 +00:00
FixSlashesInPlace( sRoot );
FixSlashesInPlace( sMountPoint );
2005-09-03 21:18:59 +00:00
if( sMountPoint.size() && sMountPoint.Right(1) != "/" )
sMountPoint += '/';
2004-04-10 21:45:14 +00:00
/* Find all drivers we want to delete. Remove them from g_pDrivers, and move them
* into aDriverListToUnmount. */
vector<LoadedDriver *> apDriverListToUnmount;
g_Mutex->Lock();
for( unsigned i = 0; i < g_pDrivers.size(); ++i )
{
if( !sType.empty() && g_pDrivers[i]->m_sType.CompareNoCase( sType ) )
continue;
if( !sRoot.empty() && g_pDrivers[i]->m_sRoot.CompareNoCase( sRoot ) )
continue;
if( !sMountPoint.empty() && g_pDrivers[i]->m_sMountPoint.CompareNoCase( sMountPoint ) )
continue;
2004-04-10 21:45:14 +00:00
++g_pDrivers[i]->m_iRefs;
apDriverListToUnmount.push_back( g_pDrivers[i] );
g_pDrivers.erase( g_pDrivers.begin()+i );
--i;
}
2004-03-14 01:56:18 +00:00
g_Mountpoints->LoadFromDrivers( g_pDrivers );
g_Mutex->Unlock();
/* Now we have a list of drivers to remove. */
while( apDriverListToUnmount.size() )
{
/* If the driver has more than one reference, somebody other than us is
* using it; wait for that operation to complete. Note that two Unmount()
* calls that want to remove the same mountpoint will deadlock here. */
g_Mutex->Lock();
while( apDriverListToUnmount[0]->m_iRefs > 1 )
g_Mutex->Wait();
g_Mutex->Unlock();
delete apDriverListToUnmount[0]->m_pDriver;
delete apDriverListToUnmount[0];
apDriverListToUnmount.erase( apDriverListToUnmount.begin() );
}
2004-03-14 01:56:18 +00:00
}
2005-01-27 03:43:55 +00:00
void RageFileManager::Remount( CString sMountpoint, CString sPath )
{
RageFileDriver *pDriver = FILEMAN->GetFileDriver( sMountpoint );
if( pDriver == NULL )
{
2005-01-27 03:54:49 +00:00
if( LOG )
LOG->Warn( "Remount(%s,%s): mountpoint not found", sMountpoint.c_str(), sPath.c_str() );
2005-01-27 03:43:55 +00:00
return;
}
if( !pDriver->Remount(sPath) )
2005-03-02 07:05:17 +00:00
LOG->Warn( "Remount(%s,%s): remount failed (does the driver support remounting?)", sMountpoint.c_str(), sPath.c_str() );
2005-01-27 03:54:49 +00:00
else
pDriver->FlushDirCache( "" );
2005-01-27 03:43:55 +00:00
FILEMAN->ReleaseFileDriver( pDriver );
}
2003-12-05 00:07:18 +00:00
bool RageFileManager::IsMounted( CString MountPoint )
{
2004-01-14 23:44:42 +00:00
LockMut( *g_Mutex );
for( unsigned i = 0; i < g_pDrivers.size(); ++i )
if( !g_pDrivers[i]->m_sMountPoint.CompareNoCase( MountPoint ) )
2003-12-05 00:07:18 +00:00
return true;
return false;
}
2005-09-03 21:18:59 +00:00
void RageFileManager::GetLoadedDrivers( vector<DriverLocation> &asMounts )
2004-03-15 03:05:55 +00:00
{
LockMut( *g_Mutex );
for( unsigned i = 0; i < g_pDrivers.size(); ++i )
2004-03-15 03:05:55 +00:00
{
DriverLocation l;
l.MountPoint = g_pDrivers[i]->m_sMountPoint;
l.Type = g_pDrivers[i]->m_sType;
l.Root = g_pDrivers[i]->m_sRoot;
2005-09-03 21:18:59 +00:00
asMounts.push_back( l );
2004-03-15 03:05:55 +00:00
}
}
void RageFileManager::FlushDirCache( CString sPath )
2003-12-07 04:21:04 +00:00
{
LockMut( *g_Mutex );
if( sPath == "" )
2003-12-07 04:21:04 +00:00
{
for( unsigned i = 0; i < g_pDrivers.size(); ++i )
g_pDrivers[i]->m_pDriver->FlushDirCache( "" );
return;
}
/* Flush a specific path. */
NormalizePath( sPath );
for( unsigned i = 0; i < g_pDrivers.size(); ++i )
{
const CString &path = g_pDrivers[i]->GetPath( sPath );
if( path.size() == 0 )
continue;
g_pDrivers[i]->m_pDriver->FlushDirCache( path );
2003-12-07 04:21:04 +00:00
}
}
RageFileManager::FileType RageFileManager::GetFileType( CString sPath )
2003-12-04 08:25:59 +00:00
{
NormalizePath( sPath );
vector<LoadedDriver *> apDriverList;
ReferenceAllDrivers( apDriverList );
RageFileManager::FileType ret = TYPE_NONE;
for( unsigned i = 0; i < apDriverList.size(); ++i )
2003-12-04 08:25:59 +00:00
{
const CString p = apDriverList[i]->GetPath( sPath );
2003-12-04 08:25:59 +00:00
if( p.size() == 0 )
continue;
ret = apDriverList[i]->m_pDriver->GetFileType( p );
2003-12-04 08:25:59 +00:00
if( ret != TYPE_NONE )
break;
2003-12-04 08:25:59 +00:00
}
UnreferenceAllDrivers( apDriverList );
return ret;
2003-12-04 08:25:59 +00:00
}
int RageFileManager::GetFileSizeInBytes( CString sPath )
2003-12-04 08:25:59 +00:00
{
NormalizePath( sPath );
vector<LoadedDriver *> apDriverList;
ReferenceAllDrivers( apDriverList );
int iRet = -1;
for( unsigned i = 0; i < apDriverList.size(); ++i )
2003-12-04 08:25:59 +00:00
{
const CString p = apDriverList[i]->GetPath( sPath );
2003-12-04 08:25:59 +00:00
if( p.size() == 0 )
continue;
iRet = apDriverList[i]->m_pDriver->GetFileSizeInBytes( p );
if( iRet != -1 )
break;
2003-12-04 08:25:59 +00:00
}
UnreferenceAllDrivers( apDriverList );
2003-12-04 08:25:59 +00:00
return iRet;
2003-12-04 08:25:59 +00:00
}
int RageFileManager::GetFileHash( CString sPath )
2003-12-04 08:25:59 +00:00
{
NormalizePath( sPath );
vector<LoadedDriver *> apDriverList;
ReferenceAllDrivers( apDriverList );
int iRet = -1;
for( unsigned i = 0; i < apDriverList.size(); ++i )
2003-12-04 08:25:59 +00:00
{
const CString p = apDriverList[i]->GetPath( sPath );
2003-12-04 08:25:59 +00:00
if( p.size() == 0 )
continue;
iRet = apDriverList[i]->m_pDriver->GetFileHash( p );
if( iRet != -1 )
break;
2003-12-04 08:25:59 +00:00
}
UnreferenceAllDrivers( apDriverList );
2003-12-04 08:25:59 +00:00
return iRet;
2003-12-04 08:25:59 +00:00
}
2003-12-05 02:25:32 +00:00
static bool SortBySecond( const pair<int,int> &a, const pair<int,int> &b )
{
return a.second < b.second;
}
/*
* Return true if the given path should use slow, reliable writes.
*
* I havn't decided if it's better to do this here, or to specify SLOW_FLUSH
* manually each place we want it. This seems more reliable (we might forget
* somewhere and not notice), and easier (don't have to pass flags down to IniFile::Write,
* etc).
*/
static bool PathUsesSlowFlush( const CString &sPath )
{
static const char *FlushPaths[] =
{
"Data/"
};
2004-04-27 04:55:59 +00:00
for( unsigned i = 0; i < ARRAYSIZE(FlushPaths); ++i )
if( !strncmp( sPath, FlushPaths[i], strlen(FlushPaths[i]) ) )
return true;
return false;
}
2003-12-04 08:25:59 +00:00
/* Used only by RageFile: */
2004-12-11 02:25:38 +00:00
RageFileBasic *RageFileManager::Open( CString sPath, int mode, int &err )
2003-12-04 08:25:59 +00:00
{
err = ENOENT;
if( (mode & RageFile::WRITE) && PathUsesSlowFlush(sPath) )
mode |= RageFile::SLOW_FLUSH;
2005-03-17 23:48:32 +00:00
NormalizePath( sPath );
2003-12-05 02:25:32 +00:00
/* If writing, we need to do a heuristic to figure out which driver to write with--there
* may be several that will work. */
if( mode & RageFile::WRITE )
return OpenForWriting( sPath, mode, err );
2005-03-17 23:43:13 +00:00
else
return OpenForReading( sPath, mode, err );
}
RageFileBasic *RageFileManager::OpenForReading( CString sPath, int mode, int &err )
{
vector<LoadedDriver *> apDriverList;
ReferenceAllDrivers( apDriverList );
for( unsigned i = 0; i < apDriverList.size(); ++i )
2003-12-04 08:25:59 +00:00
{
LoadedDriver &ld = *apDriverList[i];
2003-12-11 05:32:35 +00:00
const CString path = ld.GetPath( sPath );
2003-12-04 08:25:59 +00:00
if( path.size() == 0 )
continue;
int error;
2005-09-03 21:18:59 +00:00
RageFileBasic *ret = ld.m_pDriver->Open( path, mode, error );
2003-12-04 08:25:59 +00:00
if( ret )
2003-12-11 05:32:35 +00:00
{
UnreferenceAllDrivers( apDriverList );
2003-12-04 08:25:59 +00:00
return ret;
2003-12-11 05:32:35 +00:00
}
2003-12-04 08:25:59 +00:00
/* ENOENT (File not found) is low-priority: if some other error
* was reported, return that instead. */
if( error != ENOENT )
err = error;
}
UnreferenceAllDrivers( apDriverList );
2003-12-04 08:25:59 +00:00
return NULL;
}
2005-09-03 21:18:59 +00:00
RageFileBasic *RageFileManager::OpenForWriting( CString sPath, int mode, int &iError )
2003-12-05 02:25:32 +00:00
{
/*
* The value for a driver to open a file is the number of directories and/or files
* that would have to be created in order to write it, or 0 if the file already exists.
* For example, if we're opening "foo/bar/baz.txt", and only "foo/" exists in a
* driver, we'd have to create the "bar" directory and the "baz.txt" file, so the
* value is 2. If "foo/bar/" exists, we'd only have to create the file, so the
* value is 1. Create the file with the driver that returns the lowest value;
* in case of a tie, earliest-loaded driver wins.
*
* The purpose of this is to create files in the expected place. For example, if we
* have both C:/games/StepMania and C:/games/DWI loaded, and we're writing
* "Songs/Music/Waltz/waltz.sm", and the song was loaded out of
* "C:/games/DWI/Songs/Music/Waltz/waltz.dwi", we want to write the new SM into the
* same directory (if possible). Don't split up files in the same directory any
* more than we have to.
*
* If the given path can not be created, return -1. This happens if a path
* that needs to be a directory is a file, or vice versa.
*/
vector<LoadedDriver *> apDriverList;
ReferenceAllDrivers( apDriverList );
2003-12-05 02:25:32 +00:00
vector< pair<int,int> > Values;
for( unsigned i = 0; i < apDriverList.size(); ++i )
2003-12-05 02:25:32 +00:00
{
LoadedDriver &ld = *apDriverList[i];
2003-12-05 02:25:32 +00:00
const CString path = ld.GetPath( sPath );
if( path.size() == 0 )
continue;
2005-09-03 21:18:59 +00:00
const int value = ld.m_pDriver->GetPathValue( path );
2003-12-05 02:25:32 +00:00
if( value == -1 )
continue;
2005-06-27 05:24:24 +00:00
Values.push_back( make_pair( i, value ) );
2003-12-05 02:25:32 +00:00
}
stable_sort( Values.begin(), Values.end(), SortBySecond );
2005-09-03 21:18:59 +00:00
iError = 0;
2004-09-21 07:53:39 +00:00
for( unsigned i = 0; i < Values.size(); ++i )
2003-12-05 02:25:32 +00:00
{
2005-09-03 21:18:59 +00:00
const int iDriver = Values[i].first;
LoadedDriver &ld = *apDriverList[iDriver];
2005-09-03 22:09:14 +00:00
const CString sDriverPath = ld.GetPath( sPath );
ASSERT( !sDriverPath.empty() );
2003-12-05 02:25:32 +00:00
2005-09-03 21:18:59 +00:00
int iThisError;
2005-09-03 22:09:14 +00:00
RageFileBasic *pRet = ld.m_pDriver->Open( sDriverPath, mode, iThisError );
2005-09-03 21:18:59 +00:00
if( pRet )
{
UnreferenceAllDrivers( apDriverList );
2005-09-03 21:18:59 +00:00
return pRet;
}
2004-04-21 04:16:31 +00:00
/* The drivers are in order of priority; if they all return error, return the
* first. Never return ERROR_WRITING_NOT_SUPPORTED. */
2005-09-03 21:18:59 +00:00
if( !iError && iThisError != RageFileDriver::ERROR_WRITING_NOT_SUPPORTED )
iError = iThisError;
2003-12-05 02:25:32 +00:00
}
2004-04-21 04:16:31 +00:00
2005-09-03 21:18:59 +00:00
if( !iError )
iError = EEXIST; /* no driver could write */
2004-04-21 04:16:31 +00:00
UnreferenceAllDrivers( apDriverList );
2003-12-05 02:25:32 +00:00
return NULL;
}
2003-12-04 08:25:59 +00:00
bool RageFileManager::IsAFile( const CString &sPath ) { return GetFileType(sPath) == TYPE_FILE; }
bool RageFileManager::IsADirectory( const CString &sPath ) { return GetFileType(sPath) == TYPE_DIR; }
bool RageFileManager::DoesFileExist( const CString &sPath ) { return GetFileType(sPath) != TYPE_NONE; }
2003-12-07 04:21:04 +00:00
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 );
}
2003-12-07 22:55:55 +00:00
void GetDirListing( const CString &sPath, CStringArray &AddTo, bool bOnlyDirs, bool bReturnPathToo )
2003-12-07 04:21:04 +00:00
{
FILEMAN->GetDirListing( sPath, AddTo, bOnlyDirs, bReturnPathToo );
}
void GetDirListingRecursive( const CString &sDir, const CString &sMatch, CStringArray &AddTo )
{
ASSERT( sDir.Right(1) == "/" );
GetDirListing( sDir+sMatch, AddTo, false, true );
GetDirListing( sDir+"*", AddTo, true, true );
for( unsigned i=0; i<AddTo.size(); i++ )
{
if( IsADirectory( AddTo[i] ) )
{
GetDirListing( AddTo[i]+"/"+sMatch, AddTo, false, true );
GetDirListing( AddTo[i]+"/*", AddTo, true, true );
AddTo.erase( AddTo.begin()+i );
i--;
}
}
}
2005-07-31 05:41:32 +00:00
bool DeleteRecursive( const CString &sDir )
{
ASSERT( sDir.Right(1) == "/" );
vector<CString> vsFiles;
GetDirListing( sDir+"*", vsFiles, false, true );
FOREACH_CONST( CString, vsFiles, s )
{
if( IsADirectory(*s) )
2005-08-05 10:01:45 +00:00
DeleteRecursive( *s+"/" );
2005-07-31 05:41:32 +00:00
else
FILEMAN->Remove( *s );
}
return FILEMAN->Remove( sDir );
}
2003-12-07 04:21:04 +00:00
void FlushDirCache()
{
FILEMAN->FlushDirCache( "" );
}
2003-12-10 07:07:42 +00:00
unsigned int GetHashForFile( const CString &sPath )
{
return FILEMAN->GetFileHash( sPath );
2003-12-10 07:07:42 +00:00
}
unsigned int GetHashForDirectory( const CString &sDir )
{
unsigned int hash = 0;
hash += GetHashForString( sDir );
2003-12-10 07:07:42 +00:00
CStringArray arrayFiles;
GetDirListing( sDir+"*", arrayFiles, false );
for( unsigned i=0; i<arrayFiles.size(); i++ )
{
const CString sFilePath = sDir + arrayFiles[i];
hash += GetHashForFile( sFilePath );
}
return hash;
}
2004-05-06 00:42:06 +00:00
/*
* Copyright (c) 2001-2004 Glenn Maynard, Chris Danford
* All rights reserved.
*
* 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, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* 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 OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/