2012-06-14 09:06:06 -04: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 17:23:33 -04:00
# include "File.h"
2013-10-09 08:19:14 -04:00
# include <fstream>
2014-06-30 15:41:14 -04:00
# ifdef _WIN32
# include <share.h> // for _SH_DENYWRITE
# endif // _WIN32
2012-06-14 09:06:06 -04:00
cFile : : cFile ( void ) :
2014-10-20 16:55:07 -04:00
m_File ( nullptr )
2012-06-14 09:06:06 -04:00
{
// Nothing needed yet
}
2013-02-07 04:15:55 -05:00
cFile : : cFile ( const AString & iFileName , eMode iMode ) :
2014-10-20 16:55:07 -04:00
m_File ( nullptr )
2012-06-14 09:06:06 -04:00
{
Open ( iFileName , iMode ) ;
}
cFile : : ~ cFile ( )
{
if ( IsOpen ( ) )
{
Close ( ) ;
}
}
2013-02-07 04:15:55 -05:00
bool cFile : : Open ( const AString & iFileName , eMode iMode )
2012-06-14 09:06:06 -04:00
{
ASSERT ( ! IsOpen ( ) ) ; // You should close the file before opening another one
if ( IsOpen ( ) )
{
Close ( ) ;
}
2014-10-20 16:55:07 -04:00
const char * Mode = nullptr ;
2012-06-14 09:06:06 -04:00
switch ( iMode )
{
case fmRead : Mode = " rb " ; break ;
case fmWrite : Mode = " wb " ; break ;
case fmReadWrite : Mode = " rb+ " ; break ;
2014-08-10 14:34:11 -04:00
case fmAppend : Mode = " a+ " ; break ;
2014-04-25 23:49:55 -04:00
}
2014-10-20 16:55:07 -04:00
if ( Mode = = nullptr )
2014-04-25 23:49:55 -04:00
{
ASSERT ( ! " Unhandled file mode " ) ;
return false ;
2012-06-14 09:06:06 -04:00
}
2014-02-03 17:25:16 -05:00
2015-09-26 16:54:18 -04: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 17:25:16 -05:00
2014-10-20 16:55:07 -04:00
if ( ( m_File = = nullptr ) & & ( iMode = = fmReadWrite ) )
2012-06-14 09:06:06 -04: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 17:25:16 -05:00
2015-09-26 16:54:18 -04: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 17:25:16 -05:00
2012-06-14 09:06:06 -04:00
}
2014-10-20 16:55:07 -04:00
return ( m_File ! = nullptr ) ;
2012-06-14 09:06:06 -04:00
}
void cFile : : Close ( void )
{
if ( ! IsOpen ( ) )
{
2013-02-25 14:06:37 -05:00
// Closing an unopened file is a legal nop
2012-06-14 09:06:06 -04:00
return ;
}
fclose ( m_File ) ;
2014-10-20 16:55:07 -04:00
m_File = nullptr ;
2012-06-14 09:06:06 -04:00
}
bool cFile : : IsOpen ( void ) const
{
2014-10-20 16:55:07 -04:00
return ( m_File ! = nullptr ) ;
2012-06-14 09:06:06 -04:00
}
bool cFile : : IsEOF ( void ) const
{
ASSERT ( IsOpen ( ) ) ;
if ( ! IsOpen ( ) )
{
// Unopened files behave as at EOF
return true ;
}
return ( feof ( m_File ) ! = 0 ) ;
}
2015-06-24 10:38:40 -04:00
int cFile : : Read ( void * a_Buffer , size_t a_NumBytes )
2012-06-14 09:06:06 -04:00
{
ASSERT ( IsOpen ( ) ) ;
if ( ! IsOpen ( ) )
{
return - 1 ;
}
2015-06-24 10:38:40 -04: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 09:06:06 -04:00
}
2015-06-24 10:38:40 -04:00
AString cFile : : Read ( size_t a_NumBytes )
{
ASSERT ( IsOpen ( ) ) ;
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 09:06:06 -04:00
{
ASSERT ( IsOpen ( ) ) ;
if ( ! IsOpen ( ) )
{
return - 1 ;
}
2015-06-24 10:38:40 -04: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 09:06:06 -04:00
return res ;
}
2015-05-19 08:33:34 -04:00
long cFile : : Seek ( int iPosition )
2012-06-14 09:06:06 -04:00
{
ASSERT ( IsOpen ( ) ) ;
if ( ! IsOpen ( ) )
{
return - 1 ;
}
if ( fseek ( m_File , iPosition , SEEK_SET ) ! = 0 )
{
return - 1 ;
}
2015-05-19 08:33:34 -04:00
return ftell ( m_File ) ;
2012-06-14 09:06:06 -04:00
}
2015-05-19 08:33:34 -04:00
long cFile : : Tell ( void ) const
2012-06-14 09:06:06 -04:00
{
ASSERT ( IsOpen ( ) ) ;
if ( ! IsOpen ( ) )
{
return - 1 ;
}
2015-05-19 06:50:59 -04:00
return ftell ( m_File ) ;
2012-06-14 09:06:06 -04:00
}
2015-05-19 08:33:34 -04:00
long cFile : : GetSize ( void ) const
2012-06-14 09:06:06 -04:00
{
ASSERT ( IsOpen ( ) ) ;
if ( ! IsOpen ( ) )
{
return - 1 ;
}
2015-05-19 08:33:34 -04:00
long CurPos = Tell ( ) ;
2012-06-14 09:06:06 -04:00
if ( CurPos < 0 )
{
return - 1 ;
}
if ( fseek ( m_File , 0 , SEEK_END ) ! = 0 )
{
return - 1 ;
}
2015-05-19 08:33:34 -04:00
long res = Tell ( ) ;
2015-07-29 11:04:03 -04:00
if ( fseek ( m_File , static_cast < long > ( CurPos ) , SEEK_SET ) ! = 0 )
2012-06-14 09:06:06 -04:00
{
return - 1 ;
}
return res ;
}
int cFile : : ReadRestOfFile ( AString & a_Contents )
{
ASSERT ( IsOpen ( ) ) ;
if ( ! IsOpen ( ) )
{
return - 1 ;
}
2015-05-19 09:02:02 -04:00
long TotalSize = GetSize ( ) ;
2015-05-19 06:50:59 -04:00
if ( TotalSize < 0 )
{
return - 1 ;
}
2015-05-19 09:02:02 -04:00
long Position = Tell ( ) ;
2015-05-19 06:50:59 -04:00
if ( Position < 0 )
{
return - 1 ;
}
auto DataSize = static_cast < size_t > ( TotalSize - Position ) ;
2012-06-14 09:06:06 -04:00
// HACK: This depends on the internal knowledge that AString's data() function returns the internal buffer directly
2014-08-15 01:19:13 -04:00
a_Contents . assign ( DataSize , ' \0 ' ) ;
2015-07-29 11:04:03 -04:00
return Read ( reinterpret_cast < void * > ( const_cast < char * > ( a_Contents . data ( ) ) ) , DataSize ) ;
2012-06-14 09:06:06 -04:00
}
bool cFile : : Exists ( const AString & a_FileName )
{
cFile test ( a_FileName , fmRead ) ;
return test . IsOpen ( ) ;
}
2012-09-29 09:50:05 -04:00
2015-09-26 16:54:18 -04: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 15:09:39 -04:00
for ( const auto & item : Contents )
2015-09-26 16:54:18 -04:00
{
// Skip "." and ".." altogether:
if ( ( item = = " . " ) | | ( item = = " .. " ) )
{
continue ;
}
// 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 12:59:36 -04: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 03:38:47 -04:00
bool cFile : : Copy ( const AString & a_SrcFileName , const AString & a_DstFileName )
{
# ifdef _WIN32
2015-09-26 16:54:18 -04:00
return ( CopyFileA ( a_SrcFileName . c_str ( ) , a_DstFileName . c_str ( ) , FALSE ) ! = 0 ) ;
2013-10-09 03:38:47 -04:00
# else
// Other OSs don't have a direct CopyFile equivalent, do it the harder way:
2013-10-09 08:19:14 -04:00
std : : ifstream src ( a_SrcFileName . c_str ( ) , std : : ios : : binary ) ;
std : : ofstream dst ( a_DstFileName . c_str ( ) , std : : ios : : binary ) ;
2013-10-09 03:38:47 -04:00
if ( dst . good ( ) )
{
dst < < src . rdbuf ( ) ;
return true ;
}
else
{
return false ;
}
# endif
}
2013-09-18 12:43:03 -04:00
bool cFile : : IsFolder ( const AString & a_Path )
{
# ifdef _WIN32
2014-09-13 19:28:09 -04:00
DWORD FileAttrib = GetFileAttributesA ( a_Path . c_str ( ) ) ;
2013-10-09 03:57:48 -04:00
return ( ( FileAttrib ! = INVALID_FILE_ATTRIBUTES ) & & ( ( FileAttrib & FILE_ATTRIBUTE_DIRECTORY ) ! = 0 ) ) ;
2013-09-18 12:43:03 -04:00
# else
2013-10-09 03:57:48 -04:00
struct stat st ;
return ( ( stat ( a_Path . c_str ( ) , & st ) = = 0 ) & & S_ISDIR ( st . st_mode ) ) ;
2013-09-18 12:43:03 -04:00
# endif
}
2013-10-09 03:38:47 -04:00
bool cFile : : IsFile ( const AString & a_Path )
{
# ifdef _WIN32
2014-09-13 19:28:09 -04:00
DWORD FileAttrib = GetFileAttributesA ( a_Path . c_str ( ) ) ;
2013-10-09 03:57:48 -04:00
return ( ( FileAttrib ! = INVALID_FILE_ATTRIBUTES ) & & ( ( FileAttrib & ( FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE ) ) = = 0 ) ) ;
2013-10-09 03:38:47 -04:00
# else
2013-10-09 03:57:48 -04:00
struct stat st ;
return ( ( stat ( a_Path . c_str ( ) , & st ) = = 0 ) & & S_ISREG ( st . st_mode ) ) ;
2013-10-09 03:38:47 -04:00
# endif
}
2015-05-19 08:33:34 -04:00
long cFile : : GetSize ( const AString & a_FileName )
2013-10-09 03:38:47 -04:00
{
struct stat st ;
if ( stat ( a_FileName . c_str ( ) , & st ) = = 0 )
{
2015-07-29 11:04:03 -04:00
return static_cast < int > ( st . st_size ) ;
2013-10-09 03:38:47 -04:00
}
return - 1 ;
}
2013-10-09 03:57:48 -04:00
bool cFile : : CreateFolder ( const AString & a_FolderPath )
{
# ifdef _WIN32
2014-10-20 16:55:07 -04:00
return ( CreateDirectoryA ( a_FolderPath . c_str ( ) , nullptr ) ! = 0 ) ;
2013-10-09 03:57:48 -04:00
# else
return ( mkdir ( a_FolderPath . c_str ( ) , S_IRWXU | S_IRWXG | S_IRWXO ) = = 0 ) ;
# endif
}
2015-09-26 16:54:18 -04: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 14:11:24 -05:00
AStringVector cFile : : GetFolderContents ( const AString & a_Folder )
{
AStringVector AllFiles ;
# ifdef _WIN32
2015-09-26 16:54:18 -04: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 ( ' \\ ' ) ;
}
2013-11-22 14:11:24 -05:00
2015-09-26 16:54:18 -04: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 14:11:24 -05:00
{
2015-09-26 16:54:18 -04:00
do
{
AllFiles . push_back ( FindFileData . cFileName ) ;
} while ( FindNextFileA ( hFind , & FindFileData ) ) ;
FindClose ( hFind ) ;
}
2013-11-22 14:11:24 -05:00
# else // _WIN32
2015-09-26 16:54:18 -04:00
DIR * dp ;
struct dirent * dirp ;
AString Folder = a_Folder ;
if ( Folder . empty ( ) )
2013-11-22 14:11:24 -05:00
{
2015-09-26 16:54:18 -04:00
Folder = " . " ;
}
if ( ( dp = opendir ( Folder . c_str ( ) ) ) = = nullptr )
{
LOGERROR ( " Error (%i) opening directory \" %s \" \n " , errno , Folder . c_str ( ) ) ;
}
else
{
while ( ( dirp = readdir ( dp ) ) ! = nullptr )
{
AllFiles . push_back ( dirp - > d_name ) ;
}
closedir ( dp ) ;
2013-11-22 14:11:24 -05:00
}
# endif // else _WIN32
return AllFiles ;
}
2013-11-23 14:26:00 -05: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 16:00:54 -04:00
AString cFile : : ChangeFileExt ( const AString & a_FileName , const AString & a_NewExt )
{
auto res = a_FileName ;
2015-04-10 15:40:45 -04:00
// If the path separator is the last character of the string, return the string unmodified (refers to a folder):
2015-04-11 04:06:08 -04: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 15:40:45 -04:00
# else
2015-04-11 04:06:08 -04:00
// Linux supports only the slash separator
2015-04-10 15:40:45 -04:00
auto LastPathSep = res . rfind ( ' / ' ) ;
# endif
if ( ( LastPathSep ! = AString : : npos ) & & ( LastPathSep + 1 = = res . size ( ) ) )
{
return res ;
}
2015-04-11 04:06:08 -04:00
// Append or replace the extension:
2015-04-06 16:00:54 -04:00
auto DotPos = res . rfind ( ' . ' ) ;
2015-04-10 15:40:45 -04: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 16:00:54 -04:00
{
2015-04-10 15:40:45 -04:00
// No extension, just append the new one:
2015-04-11 11:42:32 -04:00
if ( ! a_NewExt . empty ( ) & & ( a_NewExt [ 0 ] ! = ' . ' ) )
{
// a_NewExt doesn't start with a dot, insert one:
res . push_back ( ' . ' ) ;
}
2015-04-06 16:00:54 -04:00
res . append ( a_NewExt ) ;
}
else
{
// Replace existing extension:
2015-04-11 11:42:32 -04: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 16:00:54 -04:00
res . append ( a_NewExt ) ;
}
return res ;
}
2015-04-11 04:06:08 -04:00
unsigned cFile : : GetLastModificationTime ( const AString & a_FileName )
{
struct stat st ;
if ( stat ( a_FileName . c_str ( ) , & st ) < 0 )
{
return 0 ;
}
# ifdef _WIN32
// Windows returns times in local time already
return static_cast < unsigned > ( st . st_mtime ) ;
# 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
}
2012-09-29 09:50:05 -04:00
int cFile : : Printf ( const char * a_Fmt , . . . )
{
AString buf ;
2014-01-16 02:34:10 -05:00
va_list args ;
2012-09-29 09:50:05 -04:00
va_start ( args , a_Fmt ) ;
2014-01-16 02:34:10 -05:00
AppendVPrintf ( buf , a_Fmt , args ) ;
2012-09-29 09:50:05 -04:00
va_end ( args ) ;
2014-08-10 14:34:11 -04:00
return Write ( buf . c_str ( ) , buf . length ( ) ) ;
2012-09-29 09:50:05 -04:00
}
2014-01-25 09:27:34 -05:00
void cFile : : Flush ( void )
{
fflush ( m_File ) ;
}