2012-06-14 13:06:06 +00:00
// cFile.cpp
// Implements the cFile class providing an OS-independent abstraction of a file.
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
2012-09-23 21:23:33 +00:00
#include "File.h"
2013-10-09 14:19:14 +02:00
#include <fstream>
2014-06-30 21:41:14 +02:00
#ifdef _WIN32
#include <share.h> // for _SH_DENYWRITE
#endif // _WIN32
2012-06-14 13:06:06 +00:00
cFile :: cFile ( void ) :
2014-10-20 21:55:07 +01:00
m_File ( nullptr )
2012-06-14 13:06:06 +00:00
{
// Nothing needed yet
}
2013-02-07 09:15:55 +00:00
cFile :: cFile ( const AString & iFileName , eMode iMode ) :
2014-10-20 21:55:07 +01:00
m_File ( nullptr )
2012-06-14 13:06:06 +00:00
{
Open ( iFileName , iMode );
}
cFile ::~ cFile ()
{
if ( IsOpen ())
{
Close ();
}
}
2013-02-07 09:15:55 +00:00
bool cFile :: Open ( const AString & iFileName , eMode iMode )
2012-06-14 13:06:06 +00:00
{
ASSERT ( ! IsOpen ()); // You should close the file before opening another one
2016-02-05 23:45:45 +02:00
2012-06-14 13:06:06 +00:00
if ( IsOpen ())
{
Close ();
}
2016-02-05 23:45:45 +02:00
2014-10-20 21:55:07 +01:00
const char * Mode = nullptr ;
2012-06-14 13:06:06 +00:00
switch ( iMode )
{
case fmRead : Mode = "rb" ; break ;
case fmWrite : Mode = "wb" ; break ;
case fmReadWrite : Mode = "rb+" ; break ;
2014-08-10 19:34:11 +01:00
case fmAppend : Mode = "a+" ; break ;
2014-04-25 20:49:55 -07:00
}
2014-10-20 21:55:07 +01:00
if ( Mode == nullptr )
2014-04-25 20:49:55 -07:00
{
ASSERT ( ! "Unhandled file mode" );
return false ;
2012-06-14 13:06:06 +00:00
}
2014-02-03 22:25:16 +00:00
2015-09-26 22:54:18 +02:00
#ifdef _WIN32
m_File = _fsopen (( FILE_IO_PREFIX + iFileName ). c_str (), Mode , _SH_DENYWR );
#else
m_File = fopen (( FILE_IO_PREFIX + iFileName ). c_str (), Mode );
#endif // _WIN32
2014-02-03 22:25:16 +00:00
2014-10-20 21:55:07 +01:00
if (( m_File == nullptr ) && ( iMode == fmReadWrite ))
2012-06-14 13:06:06 +00:00
{
// Fix for MS not following C spec, opening "a" mode files for writing at the end only
// The file open operation has been tried with "read update", fails if file not found
// So now we know either the file doesn't exist or we don't have rights, no need to worry about file contents.
// Simply re-open for read-writing, erasing existing contents:
2014-02-03 22:25:16 +00:00
2015-09-26 22:54:18 +02:00
#ifdef _WIN32
m_File = _fsopen (( FILE_IO_PREFIX + iFileName ). c_str (), "wb+" , _SH_DENYWR );
#else
m_File = fopen (( FILE_IO_PREFIX + iFileName ). c_str (), "wb+" );
#endif // _WIN32
2014-02-03 22:25:16 +00:00
2012-06-14 13:06:06 +00:00
}
2014-10-20 21:55:07 +01:00
return ( m_File != nullptr );
2012-06-14 13:06:06 +00:00
}
void cFile :: Close ( void )
{
if ( ! IsOpen ())
{
2013-02-25 19:06:37 +00:00
// Closing an unopened file is a legal nop
2012-06-14 13:06:06 +00:00
return ;
}
fclose ( m_File );
2014-10-20 21:55:07 +01:00
m_File = nullptr ;
2012-06-14 13:06:06 +00:00
}
bool cFile :: IsOpen ( void ) const
{
2014-10-20 21:55:07 +01:00
return ( m_File != nullptr );
2012-06-14 13:06:06 +00:00
}
bool cFile :: IsEOF ( void ) const
{
ASSERT ( IsOpen ());
2016-02-05 23:45:45 +02:00
2012-06-14 13:06:06 +00:00
if ( ! IsOpen ())
{
// Unopened files behave as at EOF
return true ;
}
2016-02-05 23:45:45 +02:00
2012-06-14 13:06:06 +00:00
return ( feof ( m_File ) != 0 );
}
2015-06-24 16:38:40 +02:00
int cFile :: Read ( void * a_Buffer , size_t a_NumBytes )
2012-06-14 13:06:06 +00:00
{
ASSERT ( IsOpen ());
2016-02-05 23:45:45 +02:00
2012-06-14 13:06:06 +00:00
if ( ! IsOpen ())
{
return - 1 ;
}
2016-02-05 23:45:45 +02:00
2015-06-24 16:38:40 +02:00
return static_cast < int > ( fread ( a_Buffer , 1 , a_NumBytes , m_File )); // fread() returns the portion of Count parameter actually read, so we need to send a_a_NumBytes as Count
2012-06-14 13:06:06 +00:00
}
2015-06-24 16:38:40 +02:00
AString cFile :: Read ( size_t a_NumBytes )
{
ASSERT ( IsOpen ());
2016-02-05 23:45:45 +02:00
2015-06-24 16:38:40 +02:00
if ( ! IsOpen ())
{
return AString ();
}
// HACK: This depends on the knowledge that AString::data() returns the internal buffer, rather than a copy of it.
AString res ;
res . resize ( a_NumBytes );
auto newSize = fread ( const_cast < char *> ( res . data ()), 1 , a_NumBytes , m_File );
res . resize ( newSize );
return res ;
}
int cFile :: Write ( const void * a_Buffer , size_t a_NumBytes )
2012-06-14 13:06:06 +00:00
{
ASSERT ( IsOpen ());
2016-02-05 23:45:45 +02:00
2012-06-14 13:06:06 +00:00
if ( ! IsOpen ())
{
return - 1 ;
}
2015-06-24 16:38:40 +02:00
int res = static_cast < int > ( fwrite ( a_Buffer , 1 , a_NumBytes , m_File )); // fwrite() returns the portion of Count parameter actually written, so we need to send a_NumBytes as Count
2012-06-14 13:06:06 +00:00
return res ;
}
2015-05-19 13:33:34 +01:00
long cFile :: Seek ( int iPosition )
2012-06-14 13:06:06 +00:00
{
ASSERT ( IsOpen ());
2016-02-05 23:45:45 +02:00
2012-06-14 13:06:06 +00:00
if ( ! IsOpen ())
{
return - 1 ;
}
2016-02-05 23:45:45 +02:00
2012-06-14 13:06:06 +00:00
if ( fseek ( m_File , iPosition , SEEK_SET ) != 0 )
{
return - 1 ;
}
2015-05-19 13:33:34 +01:00
return ftell ( m_File );
2012-06-14 13:06:06 +00:00
}
2015-05-19 13:33:34 +01:00
long cFile :: Tell ( void ) const
2012-06-14 13:06:06 +00:00
{
ASSERT ( IsOpen ());
2016-02-05 23:45:45 +02:00
2012-06-14 13:06:06 +00:00
if ( ! IsOpen ())
{
return - 1 ;
}
2016-02-05 23:45:45 +02:00
2015-05-19 11:50:59 +01:00
return ftell ( m_File );
2012-06-14 13:06:06 +00:00
}
2015-05-19 13:33:34 +01:00
long cFile :: GetSize ( void ) const
2012-06-14 13:06:06 +00:00
{
ASSERT ( IsOpen ());
2016-02-05 23:45:45 +02:00
2012-06-14 13:06:06 +00:00
if ( ! IsOpen ())
{
return - 1 ;
}
2016-02-05 23:45:45 +02:00
2015-05-19 13:33:34 +01:00
long CurPos = Tell ();
2012-06-14 13:06:06 +00:00
if ( CurPos < 0 )
{
return - 1 ;
}
if ( fseek ( m_File , 0 , SEEK_END ) != 0 )
{
return - 1 ;
}
2015-05-19 13:33:34 +01:00
long res = Tell ();
2015-07-29 09:04:03 -06:00
if ( fseek ( m_File , static_cast < long > ( CurPos ), SEEK_SET ) != 0 )
2012-06-14 13:06:06 +00:00
{
return - 1 ;
}
return res ;
}
int cFile :: ReadRestOfFile ( AString & a_Contents )
{
ASSERT ( IsOpen ());
2016-02-05 23:45:45 +02:00
2012-06-14 13:06:06 +00:00
if ( ! IsOpen ())
{
return - 1 ;
}
2016-02-05 23:45:45 +02:00
2015-05-19 14:02:02 +01:00
long TotalSize = GetSize ();
2015-05-19 11:50:59 +01:00
if ( TotalSize < 0 )
{
return - 1 ;
}
2015-05-19 14:02:02 +01:00
long Position = Tell ();
2015-05-19 11:50:59 +01:00
if ( Position < 0 )
{
return - 1 ;
}
auto DataSize = static_cast < size_t > ( TotalSize - Position );
2016-02-05 23:45:45 +02:00
2012-06-14 13:06:06 +00:00
// HACK: This depends on the internal knowledge that AString's data() function returns the internal buffer directly
2014-08-15 07:19:13 +02:00
a_Contents . assign ( DataSize , '\0' );
2018-05-02 08:50:36 +01:00
return Read ( static_cast < void *> ( const_cast < char *> ( a_Contents . data ())), DataSize );
2012-06-14 13:06:06 +00:00
}
bool cFile :: Exists ( const AString & a_FileName )
{
cFile test ( a_FileName , fmRead );
return test . IsOpen ();
}
2012-09-29 13:50:05 +00:00
2015-09-26 22:54:18 +02:00
bool cFile :: Delete ( const AString & a_Path )
{
if ( IsFolder ( a_Path ))
{
return DeleteFolder ( a_Path );
}
else
{
return DeleteFile ( a_Path );
}
}
bool cFile :: DeleteFolder ( const AString & a_FolderName )
{
#ifdef _WIN32
return ( RemoveDirectoryA ( a_FolderName . c_str ()) != 0 );
#else // _WIN32
return ( rmdir ( a_FolderName . c_str ()) == 0 );
#endif // else _WIN32
}
bool cFile :: DeleteFolderContents ( const AString & a_FolderName )
{
auto Contents = cFile :: GetFolderContents ( a_FolderName );
2015-10-30 20:09:39 +01:00
for ( const auto & item : Contents )
2015-09-26 22:54:18 +02:00
{
// Remove the item:
auto WholePath = a_FolderName + GetPathSeparator () + item ;
if ( IsFolder ( WholePath ))
{
if ( ! DeleteFolderContents ( WholePath ))
{
return false ;
}
if ( ! DeleteFolder ( WholePath ))
{
return false ;
}
}
else
{
if ( ! DeleteFile ( WholePath ))
{
return false ;
}
}
} // for item - Contents[]
// All deletes succeeded
return true ;
}
bool cFile :: DeleteFile ( const AString & a_FileName )
2013-05-01 16:59:36 +00:00
{
return ( remove ( a_FileName . c_str ()) == 0 );
}
bool cFile :: Rename ( const AString & a_OrigFileName , const AString & a_NewFileName )
{
return ( rename ( a_OrigFileName . c_str (), a_NewFileName . c_str ()) == 0 );
}
2013-10-09 09:38:47 +02:00
bool cFile :: Copy ( const AString & a_SrcFileName , const AString & a_DstFileName )
{
#ifdef _WIN32
2015-09-26 22:54:18 +02:00
return ( CopyFileA ( a_SrcFileName . c_str (), a_DstFileName . c_str (), FALSE ) != 0 );
2013-10-09 09:38:47 +02:00
#else
// Other OSs don't have a direct CopyFile equivalent, do it the harder way:
2013-10-09 14:19:14 +02:00
std :: ifstream src ( a_SrcFileName . c_str (), std :: ios :: binary );
std :: ofstream dst ( a_DstFileName . c_str (), std :: ios :: binary );
2013-10-09 09:38:47 +02:00
if ( dst . good ())
{
dst << src . rdbuf ();
return true ;
}
else
{
return false ;
}
#endif
}
2013-09-18 18:43:03 +02:00
bool cFile :: IsFolder ( const AString & a_Path )
{
#ifdef _WIN32
2014-09-14 01:28:09 +02:00
DWORD FileAttrib = GetFileAttributesA ( a_Path . c_str ());
2013-10-09 09:57:48 +02:00
return (( FileAttrib != INVALID_FILE_ATTRIBUTES ) && (( FileAttrib & FILE_ATTRIBUTE_DIRECTORY ) != 0 ));
2013-09-18 18:43:03 +02:00
#else
2013-10-09 09:57:48 +02:00
struct stat st ;
return (( stat ( a_Path . c_str (), & st ) == 0 ) && S_ISDIR ( st . st_mode ));
2013-09-18 18:43:03 +02:00
#endif
}
2013-10-09 09:38:47 +02:00
bool cFile :: IsFile ( const AString & a_Path )
{
#ifdef _WIN32
2014-09-14 01:28:09 +02:00
DWORD FileAttrib = GetFileAttributesA ( a_Path . c_str ());
2013-10-09 09:57:48 +02:00
return (( FileAttrib != INVALID_FILE_ATTRIBUTES ) && (( FileAttrib & ( FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE )) == 0 ));
2013-10-09 09:38:47 +02:00
#else
2013-10-09 09:57:48 +02:00
struct stat st ;
return (( stat ( a_Path . c_str (), & st ) == 0 ) && S_ISREG ( st . st_mode ));
2013-10-09 09:38:47 +02:00
#endif
}
2015-05-19 13:33:34 +01:00
long cFile :: GetSize ( const AString & a_FileName )
2013-10-09 09:38:47 +02:00
{
struct stat st ;
if ( stat ( a_FileName . c_str (), & st ) == 0 )
{
2015-07-29 09:04:03 -06:00
return static_cast < int > ( st . st_size );
2013-10-09 09:38:47 +02:00
}
return - 1 ;
}
2013-10-09 09:57:48 +02:00
bool cFile :: CreateFolder ( const AString & a_FolderPath )
{
#ifdef _WIN32
2014-10-20 21:55:07 +01:00
return ( CreateDirectoryA ( a_FolderPath . c_str (), nullptr ) != 0 );
2013-10-09 09:57:48 +02:00
#else
return ( mkdir ( a_FolderPath . c_str (), S_IRWXU | S_IRWXG | S_IRWXO ) == 0 );
#endif
}
2015-09-26 22:54:18 +02:00
bool cFile :: CreateFolderRecursive ( const AString & a_FolderPath )
{
// Special case: Fail if the path is empty
if ( a_FolderPath . empty ())
{
return false ;
}
// Go through each path element and create the folder:
auto len = a_FolderPath . length ();
auto PathSep = GetPathSeparator ()[ 0 ];
for ( decltype ( len ) i = 0 ; i < len ; i ++ )
{
if ( a_FolderPath [ i ] == PathSep )
{
CreateFolder ( a_FolderPath . substr ( 0 , i ));
}
}
CreateFolder ( a_FolderPath );
// Check the result by querying whether the final path exists:
return IsFolder ( a_FolderPath );
}
2013-11-22 20:11:24 +01:00
AStringVector cFile :: GetFolderContents ( const AString & a_Folder )
{
AStringVector AllFiles ;
2016-02-05 23:45:45 +02:00
2013-11-22 20:11:24 +01:00
#ifdef _WIN32
2015-09-26 22:54:18 +02:00
// If the folder name doesn't contain the terminating slash / backslash, add it:
AString FileFilter = a_Folder ;
if (
! FileFilter . empty () &&
( FileFilter [ FileFilter . length () - 1 ] != '\\' ) &&
( FileFilter [ FileFilter . length () - 1 ] != '/' )
)
{
FileFilter . push_back ( '\\' );
}
2016-02-05 23:45:45 +02:00
2015-09-26 22:54:18 +02:00
// Find all files / folders:
FileFilter . append ( "*.*" );
HANDLE hFind ;
WIN32_FIND_DATAA FindFileData ;
if (( hFind = FindFirstFileA ( FileFilter . c_str (), & FindFileData )) != INVALID_HANDLE_VALUE )
2013-11-22 20:11:24 +01:00
{
2015-09-26 22:54:18 +02:00
do
{
2016-08-07 12:50:19 +02:00
if (
( strcmp ( FindFileData . cFileName , "." ) == 0 ) ||
( strcmp ( FindFileData . cFileName , ".." ) == 0 )
)
{
continue ;
}
2015-09-26 22:54:18 +02:00
AllFiles . push_back ( FindFileData . cFileName );
} while ( FindNextFileA ( hFind , & FindFileData ));
FindClose ( hFind );
}
2016-02-05 23:45:45 +02:00
2013-11-22 20:11:24 +01:00
#else // _WIN32
2015-09-26 22:54:18 +02:00
DIR * dp ;
AString Folder = a_Folder ;
if ( Folder . empty ())
2013-11-22 20:11:24 +01:00
{
2015-09-26 22:54:18 +02:00
Folder = "." ;
}
if (( dp = opendir ( Folder . c_str ())) == nullptr )
{
LOGERROR ( "Error (%i) opening directory \" %s \"\n " , errno , Folder . c_str ());
}
else
{
2015-12-16 19:31:57 +01:00
struct dirent * dirp ;
2015-09-26 22:54:18 +02:00
while (( dirp = readdir ( dp )) != nullptr )
{
2016-08-07 12:50:19 +02:00
if (
( strcmp ( dirp -> d_name , "." ) == 0 ) ||
( strcmp ( dirp -> d_name , ".." ) == 0 )
)
{
continue ;
}
2015-09-26 22:54:18 +02:00
AllFiles . push_back ( dirp -> d_name );
}
closedir ( dp );
2013-11-22 20:11:24 +01:00
}
2016-02-05 23:45:45 +02:00
2013-11-22 20:11:24 +01:00
#endif // else _WIN32
return AllFiles ;
}
2013-11-23 20:26:00 +01:00
AString cFile :: ReadWholeFile ( const AString & a_FileName )
{
cFile f ;
if ( ! f . Open ( a_FileName , fmRead ))
{
return "" ;
}
AString Contents ;
f . ReadRestOfFile ( Contents );
return Contents ;
}
2015-04-06 22:00:54 +02:00
AString cFile :: ChangeFileExt ( const AString & a_FileName , const AString & a_NewExt )
{
auto res = a_FileName ;
2015-04-10 21:40:45 +02:00
// If the path separator is the last character of the string, return the string unmodified (refers to a folder):
2015-04-11 10:06:08 +02:00
#if defined(_MSC_VER)
// Find either path separator - MSVC CRT accepts slashes as separators, too
auto LastPathSep = res . find_last_of ( "/ \\ " );
#elif defined(_WIN32)
// Windows with different CRTs support only the backslash separator
auto LastPathSep = res . rfind ( '\\' );
2015-04-10 21:40:45 +02:00
#else
2015-04-11 10:06:08 +02:00
// Linux supports only the slash separator
2015-04-10 21:40:45 +02:00
auto LastPathSep = res . rfind ( '/' );
#endif
if (( LastPathSep != AString :: npos ) && ( LastPathSep + 1 == res . size ()))
{
return res ;
}
2015-04-11 10:06:08 +02:00
// Append or replace the extension:
2015-04-06 22:00:54 +02:00
auto DotPos = res . rfind ( '.' );
2015-04-10 21:40:45 +02:00
if (
( DotPos == AString :: npos ) || // No dot found
(( LastPathSep != AString :: npos ) && ( LastPathSep > DotPos )) // Last dot is before the last path separator (-> in folder name)
)
2015-04-06 22:00:54 +02:00
{
2015-04-10 21:40:45 +02:00
// No extension, just append the new one:
2015-04-11 17:42:32 +02:00
if ( ! a_NewExt . empty () && ( a_NewExt [ 0 ] != '.' ))
{
// a_NewExt doesn't start with a dot, insert one:
res . push_back ( '.' );
}
2015-04-06 22:00:54 +02:00
res . append ( a_NewExt );
}
else
{
// Replace existing extension:
2015-04-11 17:42:32 +02:00
if ( ! a_NewExt . empty () && ( a_NewExt [ 0 ] != '.' ))
{
// a_NewExt doesn't start with a dot, keep the current one:
res . erase ( DotPos + 1 , AString :: npos );
}
else
{
res . erase ( DotPos , AString :: npos );
}
2015-04-06 22:00:54 +02:00
res . append ( a_NewExt );
}
return res ;
}
2015-04-11 10:06:08 +02:00
unsigned cFile :: GetLastModificationTime ( const AString & a_FileName )
{
struct stat st ;
if ( stat ( a_FileName . c_str (), & st ) < 0 )
{
return 0 ;
}
2016-11-07 22:15:07 +00:00
#if defined(_WIN32)
2015-04-11 10:06:08 +02:00
// Windows returns times in local time already
return static_cast < unsigned > ( st . st_mtime );
2016-11-07 22:15:07 +00:00
#elif defined(ANDROID)
// Identical to Linux below, but st_mtime is an unsigned long, so cast is needed:
auto Time = static_cast < time_t > ( st . st_mtime );
return static_cast < unsigned > ( mktime ( localtime ( & Time )));
2015-04-11 10:06:08 +02:00
#else
// Linux returns UTC time, convert to local timezone:
return static_cast < unsigned > ( mktime ( localtime ( & st . st_mtime )));
#endif
}
AString cFile :: GetPathSeparator ( void )
{
#ifdef _WIN32
return " \\ " ;
#else
return "/" ;
#endif
}
AString cFile :: GetExecutableExt ( void )
{
#ifdef _WIN32
return ".exe" ;
#else
return "" ;
#endif
}
2018-01-03 17:41:16 +00:00
int cFile :: Printf ( const char * a_Fmt , fmt :: ArgList a_ArgList )
2012-09-29 13:50:05 +00:00
{
2018-01-03 17:41:16 +00:00
AString buf = :: Printf ( a_Fmt , a_ArgList );
2014-08-10 19:34:11 +01:00
return Write ( buf . c_str (), buf . length ());
2012-09-29 13:50:05 +00:00
}
2014-01-25 15:27:34 +01:00
void cFile :: Flush ( void )
{
fflush ( m_File );
}